Add guest dialog support for quick replies
authorMatthias Schmidt <gravatronics@live.com>
Sun, 24 Jul 2016 07:52:50 +0000 (09:52 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Sun, 24 Jul 2016 07:52:50 +0000 (09:52 +0200)
com.woltlab.wcf/templates/headIncludeJavaScript.tpl
com.woltlab.wcf/templates/messageQuickReplyGuestDialog.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/header.tpl
wcfsetup/install/files/js/WCF.js
wcfsetup/install/files/js/WoltLab/WCF/Controller/Captcha.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLab/WCF/Ui/Dialog.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Message/Reply.js
wcfsetup/install/files/js/WoltLab/WCF/User.js [new file with mode: 0644]
wcfsetup/install/files/js/require.config.js
wcfsetup/install/files/lib/data/TMessageQuickReplyGuestDialogAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/message/QuickReplyManager.class.php

index f14919480126301278775c711242d0ae9c42596a..dbe61852bc1bc92943e39a5f5728c34678e0d6a9 100644 (file)
@@ -22,7 +22,7 @@ requirejs.config({
 });
 </script>
 <script data-relocate="true">
-       require(['Language', 'WoltLab/WCF/BootstrapFrontend'], function(Language, BootstrapFrontend) {
+       require(['Language', 'WoltLab/WCF/BootstrapFrontend', 'User'], function(Language, BootstrapFrontend, User) {
                Language.addObject({
                        '__days': [ '{lang}wcf.date.day.sunday{/lang}', '{lang}wcf.date.day.monday{/lang}', '{lang}wcf.date.day.tuesday{/lang}', '{lang}wcf.date.day.wednesday{/lang}', '{lang}wcf.date.day.thursday{/lang}', '{lang}wcf.date.day.friday{/lang}', '{lang}wcf.date.day.saturday{/lang}' ],
                        '__daysShort': [ '{lang}wcf.date.day.sun{/lang}', '{lang}wcf.date.day.mon{/lang}', '{lang}wcf.date.day.tue{/lang}', '{lang}wcf.date.day.wed{/lang}', '{lang}wcf.date.day.thu{/lang}', '{lang}wcf.date.day.fri{/lang}', '{lang}wcf.date.day.sat{/lang}' ],
@@ -112,6 +112,8 @@ requirejs.config({
                        },
                        styleChanger: {if $__wcf->getStyleHandler()->countStyles() > 1}true{else}false{/if}
                });
+               
+               User.init({@$__wcf->user->userID}, '{@$__wcf->user->username|encodeJS}');
        });
        
        // prevent jQuery and other libraries from utilizing define()
diff --git a/com.woltlab.wcf/templates/messageQuickReplyGuestDialog.tpl b/com.woltlab.wcf/templates/messageQuickReplyGuestDialog.tpl
new file mode 100644 (file)
index 0000000..f5b41dd
--- /dev/null
@@ -0,0 +1,23 @@
+<div class="section">
+       <dl{if $errorType[username]|isset} class="formError"{/if}>
+               <dt><label for="username">{lang}wcf.user.username{/lang}</label></dt>
+               <dd>
+                       <input type="text" name="username" value="{$username}" required autofocus class="long">
+                       {if $errorType[username]|isset}
+                               <small class="innerError">
+                                       {if $errorType[username] == 'empty'}
+                                               {lang}wcf.global.form.error.empty{/lang}
+                                       {else}
+                                               {lang}wcf.user.username.error.{$errorType[username]}{/lang}
+                                       {/if}
+                               </small>
+                       {/if}
+               </dd>
+       </dl>
+</div>
+
+{include file='captcha'}
+
+<div class="formSubmit">
+       <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" data-captcha-id="{@$captchaID}">
+</div>
index af8e10da04e0ceee51f4ac03d1673e28af080ff0..5a1f774fd77bd8b148a1ee53772d3f0ed9a676b5 100644 (file)
        </script>
        <script>
                document.addEventListener('DOMContentLoaded', function() {
-               require(['Language', 'WoltLab/WCF/Acp/Bootstrap'], function(Language, AcpBootstrap) {
-                       Language.addObject({
-                               '__days': [ '{lang}wcf.date.day.sunday{/lang}', '{lang}wcf.date.day.monday{/lang}', '{lang}wcf.date.day.tuesday{/lang}', '{lang}wcf.date.day.wednesday{/lang}', '{lang}wcf.date.day.thursday{/lang}', '{lang}wcf.date.day.friday{/lang}', '{lang}wcf.date.day.saturday{/lang}' ],
-                               '__daysShort': [ '{lang}wcf.date.day.sun{/lang}', '{lang}wcf.date.day.mon{/lang}', '{lang}wcf.date.day.tue{/lang}', '{lang}wcf.date.day.wed{/lang}', '{lang}wcf.date.day.thu{/lang}', '{lang}wcf.date.day.fri{/lang}', '{lang}wcf.date.day.sat{/lang}' ],
-                               '__months': [ '{lang}wcf.date.month.january{/lang}', '{lang}wcf.date.month.february{/lang}', '{lang}wcf.date.month.march{/lang}', '{lang}wcf.date.month.april{/lang}', '{lang}wcf.date.month.may{/lang}', '{lang}wcf.date.month.june{/lang}', '{lang}wcf.date.month.july{/lang}', '{lang}wcf.date.month.august{/lang}', '{lang}wcf.date.month.september{/lang}', '{lang}wcf.date.month.october{/lang}', '{lang}wcf.date.month.november{/lang}', '{lang}wcf.date.month.december{/lang}' ], 
-                               '__monthsShort': [ '{lang}wcf.date.month.short.jan{/lang}', '{lang}wcf.date.month.short.feb{/lang}', '{lang}wcf.date.month.short.mar{/lang}', '{lang}wcf.date.month.short.apr{/lang}', '{lang}wcf.date.month.short.may{/lang}', '{lang}wcf.date.month.short.jun{/lang}', '{lang}wcf.date.month.short.jul{/lang}', '{lang}wcf.date.month.short.aug{/lang}', '{lang}wcf.date.month.short.sep{/lang}', '{lang}wcf.date.month.short.oct{/lang}', '{lang}wcf.date.month.short.nov{/lang}', '{lang}wcf.date.month.short.dec{/lang}' ],
-                               'wcf.acp.search.noResults': '{lang}wcf.acp.search.noResults{/lang}',
-                               'wcf.clipboard.item.unmarkAll': '{lang}wcf.clipboard.item.unmarkAll{/lang}',
-                               'wcf.date.relative.now': '{lang __literal=true}wcf.date.relative.now{/lang}',
-                               'wcf.date.relative.minutes': '{capture assign=relativeMinutes}{lang __literal=true}wcf.date.relative.minutes{/lang}{/capture}{@$relativeMinutes|encodeJS}',
-                               'wcf.date.relative.hours': '{capture assign=relativeHours}{lang __literal=true}wcf.date.relative.hours{/lang}{/capture}{@$relativeHours|encodeJS}',
-                               'wcf.date.relative.pastDays': '{capture assign=relativePastDays}{lang __literal=true}wcf.date.relative.pastDays{/lang}{/capture}{@$relativePastDays|encodeJS}',
-                               'wcf.date.dateFormat': '{lang}wcf.date.dateFormat{/lang}',
-                               'wcf.date.dateTimeFormat': '{lang}wcf.date.dateTimeFormat{/lang}',
-                               'wcf.date.shortDateTimeFormat': '{lang}wcf.date.shortDateTimeFormat{/lang}',
-                               'wcf.date.hour': '{lang}wcf.date.hour{/lang}',
-                               'wcf.date.minute': '{lang}wcf.date.minute{/lang}',
-                               'wcf.date.timeFormat': '{lang}wcf.date.timeFormat{/lang}',
-                               'wcf.date.firstDayOfTheWeek': '{lang}wcf.date.firstDayOfTheWeek{/lang}',
-                               'wcf.global.button.add': '{lang}wcf.global.button.add{/lang}',
-                               'wcf.global.button.cancel': '{lang}wcf.global.button.cancel{/lang}',
-                               'wcf.global.button.close': '{lang}wcf.global.button.close{/lang}',
-                               'wcf.global.button.collapsible': '{lang}wcf.global.button.collapsible{/lang}',
-                               'wcf.global.button.delete': '{lang}wcf.global.button.delete{/lang}',
-                               'wcf.global.button.disable': '{lang}wcf.global.button.disable{/lang}',
-                               'wcf.global.button.disabledI18n': '{lang}wcf.global.button.disabledI18n{/lang}',
-                               'wcf.global.button.edit': '{lang}wcf.global.button.edit{/lang}',
-                               'wcf.global.button.enable': '{lang}wcf.global.button.enable{/lang}',
-                               'wcf.global.button.hide': '{lang}wcf.global.button.hide{/lang}',
-                               'wcf.global.button.insert': '{lang}wcf.global.button.insert{/lang}',
-                               'wcf.global.button.next': '{lang}wcf.global.button.next{/lang}',
-                               'wcf.global.button.preview': '{lang}wcf.global.button.preview{/lang}',
-                               'wcf.global.button.reset': '{lang}wcf.global.button.reset{/lang}',
-                               'wcf.global.button.save': '{lang}wcf.global.button.save{/lang}',
-                               'wcf.global.button.search': '{lang}wcf.global.button.search{/lang}',
-                               'wcf.global.button.submit': '{lang}wcf.global.button.submit{/lang}',
-                               'wcf.global.button.upload': '{lang}wcf.global.button.upload{/lang}',
-                               'wcf.global.confirmation.cancel': '{lang}wcf.global.confirmation.cancel{/lang}',
-                               'wcf.global.confirmation.confirm': '{lang}wcf.global.confirmation.confirm{/lang}',
-                               'wcf.global.confirmation.title': '{lang}wcf.global.confirmation.title{/lang}',
-                               'wcf.global.decimalPoint': '{capture assign=decimalPoint}{lang}wcf.global.decimalPoint{/lang}{/capture}{$decimalPoint|encodeJS}',
-                               'wcf.global.error.timeout': '{lang}wcf.global.error.timeout{/lang}',
-                               'wcf.global.error.title': '{lang}wcf.global.error.title{/lang}',
-                               'wcf.global.form.error.empty': '{lang}wcf.global.form.error.empty{/lang}',
-                               '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.loading': '{lang}wcf.global.loading{/lang}',
-                               'wcf.global.noSelection': '{lang}wcf.global.noSelection{/lang}',
-                               'wcf.page.jumpTo': '{lang}wcf.page.jumpTo{/lang}',
-                               'wcf.page.jumpTo.description': '{lang}wcf.page.jumpTo.description{/lang}',
-                               'wcf.global.page.pagination': '{lang}wcf.global.page.pagination{/lang}',
-                               'wcf.global.page.next': '{capture assign=pageNext}{lang}wcf.global.page.next{/lang}{/capture}{@$pageNext|encodeJS}',
-                               'wcf.global.page.previous': '{capture assign=pagePrevious}{lang}wcf.global.page.previous{/lang}{/capture}{@$pagePrevious|encodeJS}',
-                               'wcf.global.pageDirection': '{lang}wcf.global.pageDirection{/lang}',
-                               'wcf.global.reason': '{lang}wcf.global.reason{/lang}',
-                               'wcf.global.scrollUp': '{lang}wcf.global.scrollUp{/lang}',
-                               'wcf.global.success': '{lang}wcf.global.success{/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}'
-                               {event name='javascriptLanguageImport'}
+                       require(['Language', 'WoltLab/WCF/Acp/Bootstrap', 'User'], function(Language, AcpBootstrap, User) {
+                               Language.addObject({
+                                       '__days': [ '{lang}wcf.date.day.sunday{/lang}', '{lang}wcf.date.day.monday{/lang}', '{lang}wcf.date.day.tuesday{/lang}', '{lang}wcf.date.day.wednesday{/lang}', '{lang}wcf.date.day.thursday{/lang}', '{lang}wcf.date.day.friday{/lang}', '{lang}wcf.date.day.saturday{/lang}' ],
+                                       '__daysShort': [ '{lang}wcf.date.day.sun{/lang}', '{lang}wcf.date.day.mon{/lang}', '{lang}wcf.date.day.tue{/lang}', '{lang}wcf.date.day.wed{/lang}', '{lang}wcf.date.day.thu{/lang}', '{lang}wcf.date.day.fri{/lang}', '{lang}wcf.date.day.sat{/lang}' ],
+                                       '__months': [ '{lang}wcf.date.month.january{/lang}', '{lang}wcf.date.month.february{/lang}', '{lang}wcf.date.month.march{/lang}', '{lang}wcf.date.month.april{/lang}', '{lang}wcf.date.month.may{/lang}', '{lang}wcf.date.month.june{/lang}', '{lang}wcf.date.month.july{/lang}', '{lang}wcf.date.month.august{/lang}', '{lang}wcf.date.month.september{/lang}', '{lang}wcf.date.month.october{/lang}', '{lang}wcf.date.month.november{/lang}', '{lang}wcf.date.month.december{/lang}' ], 
+                                       '__monthsShort': [ '{lang}wcf.date.month.short.jan{/lang}', '{lang}wcf.date.month.short.feb{/lang}', '{lang}wcf.date.month.short.mar{/lang}', '{lang}wcf.date.month.short.apr{/lang}', '{lang}wcf.date.month.short.may{/lang}', '{lang}wcf.date.month.short.jun{/lang}', '{lang}wcf.date.month.short.jul{/lang}', '{lang}wcf.date.month.short.aug{/lang}', '{lang}wcf.date.month.short.sep{/lang}', '{lang}wcf.date.month.short.oct{/lang}', '{lang}wcf.date.month.short.nov{/lang}', '{lang}wcf.date.month.short.dec{/lang}' ],
+                                       'wcf.acp.search.noResults': '{lang}wcf.acp.search.noResults{/lang}',
+                                       'wcf.clipboard.item.unmarkAll': '{lang}wcf.clipboard.item.unmarkAll{/lang}',
+                                       'wcf.date.relative.now': '{lang __literal=true}wcf.date.relative.now{/lang}',
+                                       'wcf.date.relative.minutes': '{capture assign=relativeMinutes}{lang __literal=true}wcf.date.relative.minutes{/lang}{/capture}{@$relativeMinutes|encodeJS}',
+                                       'wcf.date.relative.hours': '{capture assign=relativeHours}{lang __literal=true}wcf.date.relative.hours{/lang}{/capture}{@$relativeHours|encodeJS}',
+                                       'wcf.date.relative.pastDays': '{capture assign=relativePastDays}{lang __literal=true}wcf.date.relative.pastDays{/lang}{/capture}{@$relativePastDays|encodeJS}',
+                                       'wcf.date.dateFormat': '{lang}wcf.date.dateFormat{/lang}',
+                                       'wcf.date.dateTimeFormat': '{lang}wcf.date.dateTimeFormat{/lang}',
+                                       'wcf.date.shortDateTimeFormat': '{lang}wcf.date.shortDateTimeFormat{/lang}',
+                                       'wcf.date.hour': '{lang}wcf.date.hour{/lang}',
+                                       'wcf.date.minute': '{lang}wcf.date.minute{/lang}',
+                                       'wcf.date.timeFormat': '{lang}wcf.date.timeFormat{/lang}',
+                                       'wcf.date.firstDayOfTheWeek': '{lang}wcf.date.firstDayOfTheWeek{/lang}',
+                                       'wcf.global.button.add': '{lang}wcf.global.button.add{/lang}',
+                                       'wcf.global.button.cancel': '{lang}wcf.global.button.cancel{/lang}',
+                                       'wcf.global.button.close': '{lang}wcf.global.button.close{/lang}',
+                                       'wcf.global.button.collapsible': '{lang}wcf.global.button.collapsible{/lang}',
+                                       'wcf.global.button.delete': '{lang}wcf.global.button.delete{/lang}',
+                                       'wcf.global.button.disable': '{lang}wcf.global.button.disable{/lang}',
+                                       'wcf.global.button.disabledI18n': '{lang}wcf.global.button.disabledI18n{/lang}',
+                                       'wcf.global.button.edit': '{lang}wcf.global.button.edit{/lang}',
+                                       'wcf.global.button.enable': '{lang}wcf.global.button.enable{/lang}',
+                                       'wcf.global.button.hide': '{lang}wcf.global.button.hide{/lang}',
+                                       'wcf.global.button.insert': '{lang}wcf.global.button.insert{/lang}',
+                                       'wcf.global.button.next': '{lang}wcf.global.button.next{/lang}',
+                                       'wcf.global.button.preview': '{lang}wcf.global.button.preview{/lang}',
+                                       'wcf.global.button.reset': '{lang}wcf.global.button.reset{/lang}',
+                                       'wcf.global.button.save': '{lang}wcf.global.button.save{/lang}',
+                                       'wcf.global.button.search': '{lang}wcf.global.button.search{/lang}',
+                                       'wcf.global.button.submit': '{lang}wcf.global.button.submit{/lang}',
+                                       'wcf.global.button.upload': '{lang}wcf.global.button.upload{/lang}',
+                                       'wcf.global.confirmation.cancel': '{lang}wcf.global.confirmation.cancel{/lang}',
+                                       'wcf.global.confirmation.confirm': '{lang}wcf.global.confirmation.confirm{/lang}',
+                                       'wcf.global.confirmation.title': '{lang}wcf.global.confirmation.title{/lang}',
+                                       'wcf.global.decimalPoint': '{capture assign=decimalPoint}{lang}wcf.global.decimalPoint{/lang}{/capture}{$decimalPoint|encodeJS}',
+                                       'wcf.global.error.timeout': '{lang}wcf.global.error.timeout{/lang}',
+                                       'wcf.global.error.title': '{lang}wcf.global.error.title{/lang}',
+                                       'wcf.global.form.error.empty': '{lang}wcf.global.form.error.empty{/lang}',
+                                       '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.loading': '{lang}wcf.global.loading{/lang}',
+                                       'wcf.global.noSelection': '{lang}wcf.global.noSelection{/lang}',
+                                       'wcf.page.jumpTo': '{lang}wcf.page.jumpTo{/lang}',
+                                       'wcf.page.jumpTo.description': '{lang}wcf.page.jumpTo.description{/lang}',
+                                       'wcf.global.page.pagination': '{lang}wcf.global.page.pagination{/lang}',
+                                       'wcf.global.page.next': '{capture assign=pageNext}{lang}wcf.global.page.next{/lang}{/capture}{@$pageNext|encodeJS}',
+                                       'wcf.global.page.previous': '{capture assign=pagePrevious}{lang}wcf.global.page.previous{/lang}{/capture}{@$pagePrevious|encodeJS}',
+                                       'wcf.global.pageDirection': '{lang}wcf.global.pageDirection{/lang}',
+                                       'wcf.global.reason': '{lang}wcf.global.reason{/lang}',
+                                       'wcf.global.scrollUp': '{lang}wcf.global.scrollUp{/lang}',
+                                       'wcf.global.success': '{lang}wcf.global.success{/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}'
+                                       {event name='javascriptLanguageImport'}
+                               });
+                               
+                               AcpBootstrap.setup({
+                                       bootstrap: {
+                                               enableMobileMenu: {if $__isLogin|empty}true{else}false{/if}
+                                       }
+                               });
+                               
+                               User.init({@$__wcf->user->userID}, '{@$__wcf->user->username|encodeJS}');
                        });
-                       
-                       AcpBootstrap.setup({
-                               bootstrap: {
-                                       enableMobileMenu: {if $__isLogin|empty}true{else}false{/if}
-                               }
-                       });
-               });
                });
        </script>
        {js application='wcf' lib='jquery'}
index bf19636a5326438ca29fed4639a1f6c5d13d210a..201cb6afef91629bebc3b5d8f8f5255228d1cb24 100755 (executable)
@@ -3517,6 +3517,8 @@ WCF.Collapsible.SimpleRemote = WCF.Collapsible.Remote.extend({
 
 /**
  * Holds userdata of the current user
+ * 
+ * @deprecated use WCF/WoltLab/User
  */
 WCF.User = {
        /**
@@ -4964,12 +4966,6 @@ WCF.System.ObjectStore = {
  * Stores captcha callbacks used for captchas in AJAX contexts.
  */
 WCF.System.Captcha = {
-       /**
-        * adds call
-        * @var object<function>
-        */
-       _captchas: { },
-       
        /**
         * Adds a callback for a certain captcha.
         * 
@@ -4977,12 +4973,19 @@ WCF.System.Captcha = {
         * @param       function        callback
         */
        addCallback: function(captchaID, callback) {
-               if (!$.isFunction(callback)) {
-                       console.debug('[WCF.System.Captcha] Given callback is no function');
-                       return;
-               }
-               
-               this._captchas[captchaID] = callback;
+               require(['WoltLab/WCF/Controller/Captcha'], function(ControllerCaptcha) {
+                       try {
+                               ControllerCaptcha.add(captchaID, callback);
+                       }
+                       catch (e) {
+                               if (e instanceof TypeError) {
+                                       console.debug('[WCF.System.Captcha] Given callback is no function');
+                                       return;
+                               }
+                               
+                               // ignore other errors
+                       }
+               });
        },
        
        /**
@@ -4991,19 +4994,31 @@ WCF.System.Captcha = {
         * @return      object
         */
        getData: function(captchaID) {
-               if (this._captchas[captchaID] === undefined) {
-                       console.debug('[WCF.System.Captcha] Unknow captcha id "' + captchaID + '"');
-                       return;
-               }
+               var returnValue;
+               require(['WoltLab/WCF/Controller/Captcha'], function(ControllerCaptcha) {
+                       try {
+                               returnValue = ControllerCaptcha.getData(captchaID);
+                       }
+                       catch (e) {
+                               console.debug('[WCF.System.Captcha] Unknow captcha id "' + captchaID + '"');
+                       }
+               });
                
-               return this._captchas[captchaID]();
+               return returnValue;
        },
        
        /**
         * Removes the callback with the given captcha id.
         */
        removeCallback: function(captchaID) {
-               delete this._captchas[captchaID];
+               require(['WoltLab/WCF/Controller/Captcha'], function(ControllerCaptcha) {
+                       try {
+                               ControllerCaptcha.delete(captchaID);
+                       }
+                       catch (e) {
+                               // ignore errors for unknown captchas
+                       }
+               });
        }
 };
 
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Controller/Captcha.js b/wcfsetup/install/files/js/WoltLab/WCF/Controller/Captcha.js
new file mode 100644 (file)
index 0000000..252c698
--- /dev/null
@@ -0,0 +1,73 @@
+/**
+ * Provides data of the active user.
+ *
+ * @author     Matthias Schmidt
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLab/WCF/Controller/Captcha
+ */
+define(['Dictionary'], function(Dictionary) {
+       "use strict";
+       
+       var _captchas = new Dictionary();
+       
+       /**
+        * @exports     WoltLab/WCF/Controller/Captcha
+        */
+       return {
+               /**
+                * Registers a captcha with the given identifier and callback used to get captcha data.
+                * 
+                * @param       {string}        captchaId       captcha identifier
+                * @param       {function}      callback        callback to get captcha data
+                */
+               add: function(captchaId, callback) {
+                       if (_captchas.has(captchaId)) {
+                               throw new Error("Captcha with id '" + captchaId + "' is already registered.")
+                       }
+                       
+                       if (typeof callback !== 'function') {
+                               throw new TypeError("Expected a valid callback for parameter 'callback'.");
+                       }
+                       
+                       _captchas.set(captchaId, callback);
+               },
+               
+               /**
+                * Deletes the captcha with the given identifier.
+                * 
+                * @param       {string}        captchaId       identifier of the captcha to be deleted
+                */
+               'delete': function(captchaId) {
+                       if (!_captchas.has(captchaId)) {
+                               throw new Error("Unknown captcha with id '" + captchaId + "'.")
+                       }
+                       
+                       _captchas.delete(captchaId)();
+               },
+               
+               /**
+                * Returns true if a captcha with the given identifier exists.
+                * 
+                * @param       {string}        captchaId       captcha identifier
+                * @return      {boolean}
+                */
+               has: function(captchaId) {
+                       return _captchas.has(captchaId);
+               },
+               
+               /**
+                * Returns the data of the captcha with the given identifier.
+                * 
+                * @param       {string}        captchaId       captcha identifier
+                * @return      {Object}        captcha data
+                */
+               getData: function(captchaId) {
+                       if (!_captchas.has(captchaId)) {
+                               throw new Error("Unknown captcha with id '" + captchaId + "'.")
+                       }
+                       
+                       return _captchas.get(captchaId)();
+               }
+       };
+});
index e2e077592d6d3eaa998e65fc25d477d57d835a3c..01dad2b0d3c44f65f9000389dd5dc62c352c698b 100644 (file)
@@ -383,8 +383,6 @@ define(
                                elData(_container, 'close-on-click', (data.backdropCloseOnClick ? 'true' : 'false'));
                                _activeDialog = id;
                                
-                               this.rebuild(id);
-                               
                                // set focus on first applicable element
                                var focusElement = elBySel('.jsDialogAutoFocus', data.dialog);
                                if (focusElement !== null && focusElement.offsetParent !== null) {
@@ -396,6 +394,8 @@ define(
                                }
                        }
                        
+                       this.rebuild(id);
+                       
                        DomChangeListener.trigger();
                },
                
index 84934df5cf25ca4ac5e1c62d93c6b43a910a34fd..72cae533a037a67eddf8d3d9ba9010d9c9fda40a 100644 (file)
@@ -6,7 +6,8 @@
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLab/WCF/Ui/Message/Reply
  */
-define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Notification', '../Scroll'], function(Ajax, Core, EventHandler, Language, DomChangeListener, DomUtil, UiNotification, UiScroll) {
+define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Dom/Traverse', 'Ui/Dialog', 'Ui/Notification', '../Scroll', 'EventKey', 'User', 'WoltLab/WCF/Controller/Captcha'],
+       function(Ajax, Core, EventHandler, Language, DomChangeListener, DomUtil, DomTraverse, UiDialog, UiNotification, UiScroll, EventKey, User, ControllerCaptcha) {
        "use strict";
        
        /**
@@ -57,14 +58,61 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                        // TODO: add event listener for submit through keyboard in Redactor
                },
                
+               /**
+                * 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 === '') {
+                               var error = DomTraverse.nextByClass(usernameInput, 'innerError');
+                               if (!error) {
+                                       var 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');
+                               }
+                               
+                               return;
+                       }
+                       
+                       var parameters = {
+                               parameters: {
+                                       data: {
+                                               username: usernameInput.value
+                                       }
+                               }
+                       };
+                       
+                       var captchaId = elData(event.currentTarget, 'captcha-id');
+                       if (ControllerCaptcha.has(captchaId)) {
+                               parameters = Core.extend(parameters, ControllerCaptcha.getData(captchaId));
+                       }
+                       
+                       this._submit(undefined, parameters);
+               },
+               
                /**
                 * Validates the message and submits it to the server.
                 * 
-                * @param       {Event}         event   event object
+                * @param       {Event?}        event                   event object
+                * @param       {Object?}       additionalParameters    additional parameters sent to the server
                 * @protected
                 */
-               _submit: function(event) {
-                       event.preventDefault();
+               _submit: function(event, additionalParameters) {
+                       if (event) {
+                               event.preventDefault();
+                       }
                        
                        if (!this._validate()) {
                                // validation failed, bail out
@@ -80,9 +128,13 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                        
                        EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_text', parameters.data);
                        
-                       Ajax.api(this, {
+                       if (!User.userId && !additionalParameters) {
+                               parameters.requireGuestDialog = true;
+                       }
+                       
+                       Ajax.api(this, Core.extend({
                                parameters: parameters
-                       });
+                       }, additionalParameters));
                },
                
                /**
@@ -255,11 +307,31 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                },
                
                _ajaxSuccess: function(data) {
-                       this._insertMessage(data);
-                       
-                       this._reset();
+                       if (!User.userId && !data.returnValues.guestDialogID) {
+                               throw new Error("Missing 'guestDialogID' return value for guest.");
+                       }
                        
-                       this._hideLoadingOverlay();
+                       if (!User.userId && data.returnValues.guestDialog) {
+                               UiDialog.openStatic(data.returnValues.guestDialogID, data.returnValues.guestDialog, {
+                                       closable: false,
+                                       title: Language.get('wcf.global.confirmation.title')
+                               });
+                               
+                               var dialog = UiDialog.getDialog(data.returnValues.guestDialogID);
+                               elBySel('input[type=submit]', dialog.content).addEventListener(WCF_CLICK_EVENT, this._submitGuestDialog.bind(this));
+                               elBySel('input[type=text]', dialog.content).addEventListener('keypress', this._submitGuestDialog.bind(this));
+                       }
+                       else {
+                               this._insertMessage(data);
+                               
+                               if (!User.userId) {
+                                       UiDialog.close(data.returnValues.guestDialogID);
+                               }
+                               
+                               this._reset();
+                               
+                               this._hideLoadingOverlay();
+                       }
                },
                
                _ajaxFailure: function(data) {
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/User.js b/wcfsetup/install/files/js/WoltLab/WCF/User.js
new file mode 100644 (file)
index 0000000..17f1662
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * Provides data of the active user.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLab/WCF/User
+ */
+define([], function() {
+       "use strict";
+       
+       var _didInit = false;
+       
+       /**
+        * @exports     WoltLab/WCF/User
+        */
+       return {
+               /**
+                * Initializes the user object.
+                * 
+                * @param       {int}           userId          id of the user, `0` for guests
+                * @param       {string}        username        name of the user, empty for guests
+                */
+               init: function(userId, username) {
+                       if (_didInit) {
+                               throw new Error('User has already been initialized.');
+                       }
+                       
+                       // define non-writeable properties for userId and username
+                       Object.defineProperty(this, 'userId', {
+                               value: userId,
+                               writable: false
+                       });
+                       Object.defineProperty(this, 'username', {
+                               value: username,
+                               writable: false
+                       });
+                       
+                       _didInit = true;
+               }
+       };
+});
index 88110584a9bdb58f2603dc15fbb7042321e7af06..56b0daba3422b559e6fae388ea151f77266c39e1 100644 (file)
@@ -38,7 +38,8 @@ requirejs.config({
                        'Ui/Screen': 'WoltLab/WCF/Ui/Screen',
                        'Ui/SimpleDropdown': 'WoltLab/WCF/Ui/Dropdown/Simple',
                        'Ui/TabMenu': 'WoltLab/WCF/Ui/TabMenu',
-                       'Upload': 'WoltLab/WCF/Upload'
+                       'Upload': 'WoltLab/WCF/Upload',
+                       'User': 'WoltLab/WCF/User'
                }
        }
 });
diff --git a/wcfsetup/install/files/lib/data/TMessageQuickReplyGuestDialogAction.class.php b/wcfsetup/install/files/lib/data/TMessageQuickReplyGuestDialogAction.class.php
new file mode 100644 (file)
index 0000000..fdac473
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+namespace wcf\data;
+use wcf\data\object\type\ObjectType;
+use wcf\system\captcha\CaptchaHandler;
+use wcf\system\captcha\ICaptchaHandler;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+use wcf\util\UserUtil;
+
+/**
+ * Provdes methods related to the guest dialog of message quick reply.
+ * 
+ * @author     Matthias Schmudt
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data
+ * @since      3.0
+ */
+trait TMessageQuickReplyGuestDialogAction {
+       /**
+        * captcha object type used for guests or `null` if no captcha is used or available
+        * @var ObjectType
+        */
+       public $guestDialogCaptchaObjectType;
+       
+       /**
+        * list of errors in the guest dialog with field as key and error type as value
+        * @var string[]
+        */
+       public $guestDialogErrors = [];
+       
+       /**
+        * @see AbstractDatabaseObjectAction::$parameters
+        */
+       protected $parameters = [];
+       
+       /**
+        * @see AbstractDatabaseObjectAction::readString()
+        */
+       abstract protected function readString($variableName, $allowEmpty = false, $arrayIndex = '');
+       
+       /**
+        * Sets the guest dialog captcha.
+        * 
+        * Needs to be called before all other methods in this trait.
+        */
+       protected function setGuestDialogCaptcha() {
+               $this->guestDialogCaptchaObjectType = CaptchaHandler::getInstance()->getObjectTypeByName(CAPTCHA_TYPE);
+               if ($this->guestDialogCaptchaObjectType === null) {
+                       throw new \LogicException("Unknown captcha object type with name '".CAPTCHA_TYPE."'");
+               }
+               
+               /** @var ICaptchaHandler $processor */
+               $processor = $this->guestDialogCaptchaObjectType->getProcessor();
+               
+               if (!$processor->isAvailable()) {
+                       $this->guestDialogCaptchaObjectType = null;
+               }
+       }
+       
+       /**
+        * Validates the captcha in the guest dialog.
+        *
+        * @throws      \BadMethodCallException
+        * @throws      \LogicException
+        */
+       protected function validateGuestDialogCaptcha() {
+               // only relevant for guests
+               if (WCF::getUser()->userID) {
+                       throw new \BadMethodCallException("Guest dialogs are only relevant for guests");
+               }
+               
+               if (CAPTCHA_TYPE) {
+                       /** @var ICaptchaHandler $processor */
+                       $processor = $this->guestDialogCaptchaObjectType->getProcessor();
+                       
+                       if ($processor->isAvailable()) {
+                               try {
+                                       $processor->readFormParameters();
+                                       $processor->validate();
+                               }
+                               catch (UserInputException $e) {
+                                       $this->guestDialogErrors[$e->getField()] = $e->getType();
+                               }
+                       }
+               }
+       }
+       
+       /**
+        * Validates the entered username in the guest dialog.
+        * 
+        * @return      string          type of the validation error or empty if no error occured
+        * @throws      \BadMethodCallException
+        */
+       protected function validateGuestDialogUsername() {
+               // only relevant for guests
+               if (WCF::getUser()->userID) {
+                       throw new \BadMethodCallException("Guest dialogs are only relevant for guests");
+               }
+               
+               try {
+                       $this->readString('username', false, 'data');
+                       
+                       if (!UserUtil::isValidUsername($this->parameters['data']['username'])) {
+                               throw new UserInputException('username', 'notValid');
+                       }
+                       if (!UserUtil::isAvailableUsername($this->parameters['data']['username'])) {
+                               throw new UserInputException('username', 'notUnique');
+                       }
+               }
+               catch (UserInputException $e) {
+                       $this->guestDialogErrors['username'] = $e->getType();
+               }
+       }
+}
index 59c98fbae46d319985e47bf6661c50435da1f6e5..96e1ea3ad0e066fbccd08ec471c6ef7da8be351d 100644 (file)
@@ -179,8 +179,14 @@ class QuickReplyManager extends SingletonFactory {
                $tableIndexName = call_user_func([$this->container, 'getDatabaseTableIndexName']);
                $parameters['data'][$tableIndexName] = $parameters['objectID'];
                $parameters['data']['time'] = TIME_NOW;
-               $parameters['data']['userID'] = WCF::getUser()->userID ?: null;
-               $parameters['data']['username'] = WCF::getUser()->username;
+               
+               if (!isset($parameters['data']['userID'])) {
+                       $parameters['data']['userID'] = WCF::getUser()->userID ?: null;
+               }
+               
+               if (!isset($parameters['data']['username'])) {
+                       $parameters['data']['username'] = WCF::getUser()->username;
+               }
                
                // pre-parse message text
                /*if ($parameters['data']['preParse']) {