Add support for invisible reCAPTCHA
authorTim Düsterhus <duesterhus@woltlab.com>
Mon, 27 Mar 2017 19:43:09 +0000 (21:43 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Tue, 28 Mar 2017 20:42:48 +0000 (22:42 +0200)
see #2242

com.woltlab.wcf/templates/lostPassword.tpl
com.woltlab.wcf/templates/mail.tpl
com.woltlab.wcf/templates/recaptcha.tpl
com.woltlab.wcf/templates/register.tpl
com.woltlab.wcf/templates/search.tpl
wcfsetup/install/files/acp/templates/login.tpl
wcfsetup/install/files/acp/templates/recaptcha.tpl
wcfsetup/install/files/acp/templates/rescueMode.tpl
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Reply.js
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

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 277ad1e3c1e8a9a923147f59917887c6406ef705..a961aff4298d61e6580019aa1aeca6e2fb8588e6 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}
+               <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;">
-                                                               <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|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|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
+                                                                                       };
+                                                                               });
+                                                                       });
+                                                               }
+                                                               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>
+                                       <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}'])
+                               };
+                       });
+                       {/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 3985962bc5284abd69005e8dff3e75cf58c7652c..5a5aa72561145f7a34e611e83242eda4c3158310 100644 (file)
        
        {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 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 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">
index 0ebd9336b7c89a74c0e0ff952755874b5e1a740b..a961aff4298d61e6580019aa1aeca6e2fb8588e6 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}
+               <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;">
+                                                               <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>
-                                       </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|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
+                                                                                       };
+                                                                               });
+                                                                       });
+                                                               }
+                                                               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>
+                                       <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}'])
+                               };
+                       });
+                       {/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 304b48aad52529c55379e336a3505f0f82587a8b..b2d20971bb9836494170da250b827a541a8ee9e6 100644 (file)
@@ -83,7 +83,7 @@
                </dl>
        </section>
        
-       {include file='captcha'}
+       {include file='captcha' supportsAsyncCaptcha=true}
        
        <section class="section">
                <header class="sectionHeader">
index 7d7cc7c0f32b680b99f529171ba5d130d29d3f85..04f41d04e915d4e624533dc0cee1c0f7e5e1134b 100644 (file)
@@ -97,10 +97,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);
                },
                
                /**
index 5e2c45c3db8bdcee8af68becd626588f99ebbe5d..d4963f7f3c03351a6acfbdc96387024be29861fa 100644 (file)
@@ -2109,6 +2109,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>
index 49914c59c62c12c03c51d542839632f7956b38fe..ce114247063e261522493275a6b950efabe556c9 100644 (file)
@@ -2064,6 +2064,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[List Categories]]></item>