Add support for invisible reCAPTCHA
[GitHub/WoltLab/WCF.git] / com.woltlab.wcf / templates / recaptcha.tpl
CommitLineData
96714cab
MS
1{if $recaptchaLegacyMode|empty}
2 {include file='captcha'}
3{else}
ec9e64f0
TD
4 {* No explicit keys were set, use legacy V1 API and WoltLab's OEM keys *}
5 {if RECAPTCHA_PUBLICKEY === '' || RECAPTCHA_PRIVATEKEY === ''}
95961bdf 6 <section class="section">
d2d216fb
MW
7 <header class="sectionHeader">
8 <h2 class="sectionTitle">{lang}wcf.recaptcha.title{/lang}</h2>
114b5320 9 <p class="sectionDescription">{lang}wcf.recaptcha.description{/lang}</p>
d2d216fb 10 </header>
376d7839 11
96714cab
MS
12 <dl class="wide reCaptcha{if $errorField|isset && $errorField == 'recaptchaString'} formError{/if}">
13 {if !$ajaxCaptcha|isset || !$ajaxCaptcha}
14 <script data-relocate="true">
96714cab
MS
15 var RecaptchaOptions = {
16 lang: '{@$recaptchaLanguageCode}',
17 theme : 'custom'
18 }
96714cab
MS
19 </script>
20 {/if}
21 <dt class="jsOnly">
22 <label for="recaptcha_response_field">reCAPTCHA</label>
23 </dt>
24 <dd class="jsOnly">
95961bdf 25 <div id="recaptcha_image"></div>
e5f9b56c 26 <input type="text" id="recaptcha_response_field" name="recaptcha_response_field" class="medium">
96714cab
MS
27 {if (($errorType|isset && $errorType|is_array && $errorType[recaptchaString]|isset) || ($errorField|isset && $errorField == 'recaptchaString'))}
28 {if $errorType|is_array && $errorType[recaptchaString]|isset}
29 {assign var='__errorType' value=$errorType[recaptchaString]}
30 {else}
31 {assign var='__errorType' value=$errorType}
32 {/if}
b9f4bd69 33 <small class="innerError">
96714cab
MS
34 {if $__errorType == 'empty'}
35 {lang}wcf.global.form.error.empty{/lang}
36 {else}
37 {lang}wcf.recaptcha.error.recaptchaString.{$__errorType}{/lang}
38 {/if}
b9f4bd69
MS
39 </small>
40 {/if}
96714cab
MS
41 </dd>
42
43 {event name='fields'}
44
45 <dd class="jsOnly">
46 <ul class="buttonList smallButtons">
ca8bfa53
MS
47 <li><a href="javascript:Recaptcha.reload()" class="button small"><span class="icon icon16 fa-repeat"></span> <span>{lang}wcf.recaptcha.reload{/lang}</span></a></li>
48 <li class="recaptcha_only_if_image"><a href="javascript:Recaptcha.switch_type('audio')" class="button small"><span class="icon icon16 fa-volume-up"></span> <span>{lang}wcf.recaptcha.audio{/lang}</span></a></li>
49 <li class="recaptcha_only_if_audio"><a href="javascript:Recaptcha.switch_type('image')" class="button small"><span class="icon icon16 fa-eye"></span> <span>{lang}wcf.recaptcha.image{/lang}</span></a></li>
50 <li><a href="javascript:Recaptcha.showhelp()" class="button small"><span class="icon icon16 fa-question"></span> <span>{lang}wcf.recaptcha.help{/lang}</span></a></li>
96714cab
MS
51 {event name='buttons'}
52 </ul>
53 </dd>
54
55 {if !$ajaxCaptcha|isset || !$ajaxCaptcha}
2a83aa92 56 <script data-relocate="true" src="//www.google.com/recaptcha/api/challenge?k={$recaptchaPublicKey}"></script>
96714cab
MS
57 <noscript>
58 <dd>
e5f9b56c 59 <iframe src="//www.google.com/recaptcha/api/noscript?k={$recaptchaPublicKey}" height="300" width="500" seamless="seamless"></iframe><br>
96714cab 60 <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
e5f9b56c 61 <input type="hidden" name="recaptcha_response_field" value="manual_challenge">
96714cab
MS
62 </dd>
63 {if (($errorType|isset && $errorType|is_array && $errorType[recaptchaString]|isset) || ($errorField|isset && $errorField == 'recaptchaString'))}
64 {if $errorType|is_array && $errorType[recaptchaString]|isset}
65 {assign var='__errorType' value=$errorType[recaptchaString]}
66 {else}
67 {assign var='__errorType' value=$errorType}
68 {/if}
69 <small class="innerError">
70 {if $errorType == 'empty'}
71 {lang}wcf.global.form.error.empty{/lang}
72 {else}
73 {lang}wcf.recaptcha.error.recaptchaString.{$__errorType}{/lang}
74 {/if}
75 </small>
76 {/if}
77 </noscript>
78 {else}
79 <script data-relocate="true">
2a83aa92
MS
80 $.getScript('//www.google.com/recaptcha/api/js/recaptcha_ajax.js', function() {
81 Recaptcha.create("{$recaptchaPublicKey}", "recaptcha_image", {
82 lang: '{@$recaptchaLanguageCode}',
83 theme : 'custom'
84 });
85
86 WCF.System.Captcha.addCallback('{$captchaID}', function() {
87 return {
88 recaptcha_challenge_field: Recaptcha.get_challenge(),
89 recaptcha_response_field: Recaptcha.get_response()
90 };
91 });
96714cab 92 });
96714cab
MS
93 </script>
94 {/if}
95 </dl>
95961bdf 96 </section>
ec9e64f0 97 {else}
6b3d5c38
TD
98 {if $supportsAsyncCaptcha|isset && $supportsAsyncCaptcha}
99 <section class="section">
100 <h2 class="sectionTitle">{lang}wcf.recaptcha.title{/lang}</h2>
101 {assign var="recaptchaBucketID" value=true|microtime|sha1}
102 <dl class="{if $errorField|isset && $errorField == 'recaptchaString'}formError{/if}">
103 <dt></dt>
104 <dd>
105 <div id="recaptchaBucket{$recaptchaBucketID}"></div>
106 <noscript>
107 <div style="width: 302px; height: 473px;">
01f0f832 108 <div style="width: 302px; height: 422px; position: relative;">
6b3d5c38
TD
109 <div style="width: 302px; height: 422px; position: relative;">
110 <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>
111 </div>
112 <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;">
113 <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>
114 </div>
2ca3c9cb
TD
115 </div>
116 </div>
6b3d5c38
TD
117 </noscript>
118 {if (($errorType|isset && $errorType|is_array && $errorType[recaptchaString]|isset) || ($errorField|isset && $errorField == 'recaptchaString'))}
119 {if $errorType|is_array && $errorType[recaptchaString]|isset}
120 {assign var='__errorType' value=$errorType[recaptchaString]}
ec9e64f0 121 {else}
6b3d5c38 122 {assign var='__errorType' value=$errorType}
ec9e64f0 123 {/if}
6b3d5c38
TD
124 <small class="innerError">
125 {if $__errorType == 'empty'}
126 {lang}wcf.global.form.error.empty{/lang}
127 {else}
128 {lang}wcf.captcha.recaptchaInvisible.error.recaptchaString.{$__errorType}{/lang}
129 {/if}
130 </small>
131 {/if}
132 </dd>
133 </dl>
134 <script data-relocate="true">
135 if (!WCF.recaptcha) {
136 WCF.recaptcha = {
137 queue: [],
138 callbackCalled: false,
139 mapping: { }
140 };
141
142 // this needs to be in global scope
143 function recaptchaCallback() {
144 var bucketId;
145 WCF.recaptcha.callbackCalled = true;
146
147 // clear queue
148 while (config = WCF.recaptcha.queue.shift()) {
149 (function (config) {
150 var bucketId = config.bucket;
151
152 require(['Dom/Traverse', 'Dom/Util'], function (DomTraverse, DomUtil) {
153 var bucket = elById(bucketId);
154
155 var promise = new Promise(function (resolve, reject) {
156 WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}'] = grecaptcha.render(bucket, {
157 sitekey: '{RECAPTCHA_PUBLICKEY|encodeJS}',
158 size: 'invisible',
159 badge: 'inline',
160 callback: resolve
161 });
162 });
163
164 if (config.ajaxCaptcha) {
165 WCF.System.Captcha.addCallback(config.ajaxCaptcha, function() {
166 grecaptcha.execute(WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}']);
167 return promise.then(function (token) {
168 return {
169 'g-recaptcha-response': token
170 };
171 });
172 });
173 }
174 else {
175 var form = DomTraverse.parentByTag(bucket, 'FORM');
176
177 var pressed = undefined;
178 elBySelAll('input[type=submit]', form, function (button) {
179 button.addEventListener('click', function (event) {
180 pressed = button;
181 });
182 });
183
184 var listener = function (event) {
185 event.preventDefault();
186 promise.then(function (token) {
187 form.removeEventListener('submit', listener);
188 pressed.disabled = false;
189 pressed.click();
190 });
191 grecaptcha.execute(WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}']);
192 }
193 form.addEventListener('submit', listener);
194 }
195
196 });
197 })(config);
198 }
199 }
200 }
201
202 // add captcha to queue
203 WCF.recaptcha.queue.push({
204 bucket: 'recaptchaBucket{$recaptchaBucketID}'
205 {if $ajaxCaptcha|isset && $ajaxCaptcha}
206 , ajaxCaptcha: '{$captchaID}'
ec9e64f0 207 {/if}
6b3d5c38
TD
208 });
209
210 // trigger callback immediately, if API already is available
211 if (WCF.recaptcha.callbackCalled) setTimeout(recaptchaCallback, 1);
ec9e64f0 212
6b3d5c38
TD
213 // ensure recaptcha API is loaded at most once
214 if (!window.grecaptcha) $.getScript('https://www.google.com/recaptcha/api.js?render=explicit&onload=recaptchaCallback');
215 </script>
216 </section>
217 {else}
218 <section class="section">
219 <h2 class="sectionTitle">{lang}wcf.recaptcha.title{/lang}</h2>
220 {assign var="recaptchaBucketID" value=true|microtime|sha1}
221 <dl class="{if $errorField|isset && $errorField == 'recaptchaString'}formError{/if}">
222 <dt></dt>
223 <dd>
224 <div id="recaptchaBucket{$recaptchaBucketID}"></div>
225 <noscript>
226 <div style="width: 302px; height: 473px;">
227 <div style="width: 302px; height: 422px; position: relative;">
228 <div style="width: 302px; height: 422px; position: relative;">
229 <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>
230 </div>
231 <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;">
232 <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>
233 </div>
234 </div>
235 </div>
236 </noscript>
237 {if (($errorType|isset && $errorType|is_array && $errorType[recaptchaString]|isset) || ($errorField|isset && $errorField == 'recaptchaString'))}
238 {if $errorType|is_array && $errorType[recaptchaString]|isset}
239 {assign var='__errorType' value=$errorType[recaptchaString]}
240 {else}
241 {assign var='__errorType' value=$errorType}
242 {/if}
243 <small class="innerError">
244 {if $__errorType == 'empty'}
245 {lang}wcf.global.form.error.empty{/lang}
246 {else}
247 {lang}wcf.captcha.recaptchaV2.error.recaptchaString.{$__errorType}{/lang}
248 {/if}
249 </small>
250 {/if}
251 </dd>
252 </dl>
253 <script data-relocate="true">
254 if (!WCF.recaptcha) {
255 WCF.recaptcha = {
256 queue: [],
257 callbackCalled: false,
258 mapping: { }
259 };
ec9e64f0 260
6b3d5c38
TD
261 // this needs to be in global scope
262 function recaptchaCallback() {
263 var bucket;
264 WCF.recaptcha.callbackCalled = true;
265
266 // clear queue
267 while (bucket = WCF.recaptcha.queue.shift()) {
268 WCF.recaptcha.mapping[bucket] = grecaptcha.render(bucket, {
269 'sitekey' : '{RECAPTCHA_PUBLICKEY|encodeJS}'
270 });
271 }
ec9e64f0
TD
272 }
273 }
6b3d5c38
TD
274
275 // add captcha to queue
276 WCF.recaptcha.queue.push('recaptchaBucket{$recaptchaBucketID}');
277
278 // trigger callback immediately, if API already is available
279 if (WCF.recaptcha.callbackCalled) setTimeout(recaptchaCallback, 1);
280
281 {if $ajaxCaptcha|isset && $ajaxCaptcha}
282 WCF.System.Captcha.addCallback('{$captchaID}', function() {
283 return {
284 'g-recaptcha-response': grecaptcha.getResponse(WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}'])
285 };
286 });
287 {/if}
288
289 // ensure recaptcha API is loaded at most once
290 if (!window.grecaptcha) $.getScript('https://www.google.com/recaptcha/api.js?render=explicit&onload=recaptchaCallback');
291 </script>
292 </section>
ec9e64f0 293 {/if}
ec9e64f0 294 {/if}
96714cab 295{/if}