1 2 3 4/*! 5 * Securimage CAPTCHA Audio Library 6 * https://www.phpcaptcha.org/ 7 * 8 * Copyright 2015 phpcaptcha.org 9 * Released under the BSD-3 license 10 * See https://github.com/dapphp/securimage/blob/master/README.md 11 */ 12 13var SecurimageAudio = function (options) { 14 this.html5Support = true; 15 this.flashFallback = false; 16 this.captchaId = null; 17 this.playing = false; 18 this.reload = false; 19 this.audioElement = null; 20 this.controlsElement = null; 21 this.playButton = null; 22 this.playButtonImage = null; 23 this.loadingImage = null; 24 25 if (options.audioElement) { 26 this.audioElement = document.getElementById(options.audioElement); 27 } 28 if (options.controlsElement) { 29 this.controlsElement = document.getElementById(options.controlsElement); 30 } 31 32 this.init(); 33} 34 35SecurimageAudio.prototype.init = function () { 36 var ua = navigator.userAgent.toLowerCase(); 37 var ieVer = (ua.indexOf('msie') != -1) ? parseInt(ua.split('msie')[1]) : false; 38 // ie 11+ detection 39 if (!ieVer && null != (ieVer = ua.match(/trident\/.*rv:(\d+\.\d+)/))) 40 ieVer = parseInt(ieVer[1]); 41 42 var objAu = this.audioElement.getElementsByTagName('object'); 43 if (objAu.length > 0) { 44 objAu = objAu[0]; 45 } else { 46 objAu = null; 47 } 48 49 if (ieVer) { 50 if (ieVer < 9) { 51 // no html5 audio support, hide player controls 52 this.controlsElement.style.display = 'none'; 53 this.html5Support = false; 54 return; 55 } else if ('' == this.audioElement.canPlayType('audio/wav')) { 56 // check for mpeg <source> tag - if not found then fallback to flash 57 var sources = this.audioElement.getElementsByTagName('source'); 58 var mp3support = false; 59 var type; 60 61 if (objAu) { 62 this.flashFallback = true; 63 } 64 65 for (var i = 0; i < sources.length; ++i) { 66 type = sources[i].attributes["type"].value; 67 if (type.toLowerCase().indexOf('mpeg') >= 0 || type.toLowerCase().indexOf('mp3') >= 0) { 68 mp3support = true; 69 break; 70 } 71 } 72 73 if (false == mp3support) { 74 // browser supports <audio> but does not support WAV audio and no flash audio available 75 this.html5Support = false; 76 77 if (this.flashFallback) { 78 // ie9+? bug - flash object does not display when moved from within audio tag to other dom node 79 var newObjAu = document.createElement('object'); 80 var newParams = document.createElement('param'); 81 var oldParams = objAu.getElementsByTagName('param'); 82 this.copyElementAttributes(newObjAu, objAu); 83 if (oldParams.length > 0) { 84 this.copyElementAttributes(newParams, oldParams[0]); 85 newObjAu.appendChild(newParams); 86 } 87 objAu.parentNode.removeChild(objAu); 88 this.audioElement.parentNode.appendChild(newObjAu); 89 } 90 91 this.audioElement.parentNode.removeChild(this.audioElement); 92 this.controlsElement.parentNode.removeChild(this.controlsElement); 93 94 return; 95 } 96 } 97 } 98 99 this.audioElement.addEventListener('playing', this.updateControls.bind(this), false); 100 this.audioElement.addEventListener('ended', this.audioStopped.bind(this), false); 101 102 // find the element used as the play button and register click event to play/stop audio 103 var children = this.controlsElement.getElementsByTagName('*'); 104 for (var i = 0; i < children.length; ++i) { 105 var el = children[i]; 106 if (undefined != el.className && (typeof el.className === 'string' || el.className instanceof String)) { 107 if (el.className.indexOf('play_button') >= 0) { 108 this.playButton = el; 109 el.addEventListener('click', this.play.bind(this), false); 110 } else if (el.className.indexOf('$audioObj_image') >= 0) { 111 this.playButtonImage = el; 112 } else if (el.className.indexOf('loading_image') >= 0) { 113 this.loadingImage = el; 114 } 115 } 116 } 117 118 if (objAu) { 119 // remove flash object from DOM 120 objAu.parentNode.removeChild(objAu); 121 } 122} 123 124SecurimageAudio.prototype.play = function (evt) { 125 if (null != this.playButton) { 126 this.playButton.blur(); 127 } 128 129 if (this.reload) { 130 this.replaceElements(); 131 this.reload = false; 132 } 133 134 try { 135 if (!this.playing) { 136 if (this.playButtonImage != null) { 137 this.playButtonImage.style.display = 'none'; 138 } 139 if (this.loadingImage != null) { 140 this.loadingImage.style.display = ''; 141 } 142 //TODO: FIX, most likely browser doesn't support audio type 143 this.audioElement.onerror = this.audioError; 144 this.audioElement.dataset.played = true; 145 try { 146 this.audioElement.play(); 147 } catch (ex) { 148 alert('Audio error: ' + ex); 149 } 150 } else { 151 this.audioElement.pause(); 152 if (this.loadingImage != null) { 153 this.loadingImage.style.display = 'none'; 154 } 155 if (this.playButtonImage != null) { 156 this.playButtonImage.style.display = ''; 157 } 158 this.playing = false;
159 } 160 } catch (ex) { 161 alert('Audio error: ' + ex); 162 } 163 164 if (undefined !== evt) { 165 evt.preventDefault(); 166 } 167 return false; 168} 169 170SecurimageAudio.prototype.refresh = function (captchaId) { 171 if (!this.html5Support) { 172 return; 173 } 174 175 if (undefined !== captchaId) { 176 this.captchaId = captchaId; 177 } 178 179 this.playing = true; 180 this.reload = false; 181 this.play(); // stops audio if playing 182 this.reload = true; 183 184 if (this.audioElement.dataset.played != undefined) { 185 186 console.log(this.audioElement.dataset.played); 187 setTimeout(() => { 188 this.play(); 189 this.reload = false; 190 }, 1000); 191 } 192 193 return false; 194} 195 196SecurimageAudio.prototype.copyElementAttributes = function (newEl, el) { 197 for (var i = 0, atts = el.attributes, n = atts.length; i < n; ++i) { 198 newEl.setAttribute(atts[i].nodeName, atts[i].value); 199 } 200 201 return newEl; 202} 203 204SecurimageAudio.prototype.replaceElements = function () { 205 var parent = this.audioElement.parentNode; 206 parent.removeChild(this.audioElement); 207 208 var newAudioEl = document.createElement('audio'); 209 210 for (var c = 0; c < this.audioElement.children.length; ++c) { 211 if (this.audioElement.children[c].tagName.toLowerCase() != 'source') continue; 212 var sourceEl = document.createElement('source'); 213 this.copyElementAttributes(sourceEl, this.audioElement.children[c]); 214 var cid = (null !== this.captchaId) ? this.captchaId : (Math.random() + '').replace('0.', ''); 215 sourceEl.src = sourceEl.src.replace(/id=[a-zA-Z0-9]+/, 'id=' + cid); 216 newAudioEl.appendChild(sourceEl); 217 } 218 219 this.audioElement = null; 220 this.audioElement = newAudioEl; 221 parent.appendChild(this.audioElement); 222 223 this.audioElement.addEventListener('playing', this.updateControls.bind(this), false); 224 this.audioElement.addEventListener('ended', this.audioStopped.bind(this), false); 225 226 if (null != this.playButton) { 227 this.playButton.blur(); 228 this.playButton.href = this.playButton.href + Math.random(); 229 } 230} 231 232SecurimageAudio.prototype.updateControls = function () { 233 this.playing = true; 234 if (this.loadingImage != null) { 235 this.loadingImage.style.display = 'none'; 236 } 237 if (this.playButtonImage != null) { 238 this.playButtonImage.style.display = ''; 239 } 240} 241 242SecurimageAudio.prototype.audioStopped = function () { 243 this.playing = false; 244} 245 246SecurimageAudio.prototype.audioError = function (err) { 247 alert('Audio playback error: ' + err.message); 248} 249 250 251 252function updateSICatpcha(captchaImage) { 253 if (captchaImage) { 254 var captchaImage = document.getElementById(captchaImage); 255 captchaImage.src = captchaImage.getAttribute('data-url') + '&' + Math.random(); 256 257 var captchaText = document.getElementById(captchaImage.getAttribute('data-text-input')); 258 if (captchaText) { 259 captchaText.value = ''; 260 // @todo: Implement focus() only when captcha was the only error with the form 261 // captchaText.focus(); 262 } 263 var captchaAudio = captchaImage.getAttribute('data-audioObj'); 264 if (captchaAudio && typeof window[captchaAudio] !== 'undefined') { 265 window[captchaAudio].refresh(); 266 } 267 } 268}; 269 270function updateCaptchas(timeout=1500) { 271 setTimeout( () =>{ 272 captchas = document.querySelectorAll('.img-securimage-captcha'); 273 let i = 0; 274 captchas.forEach(captcha => { 275 i++; 276 setTimeout(() => { 277 updateSICatpcha(captcha.id) 278 }, 250 * i); 279 }); 280 }, timeout); 281}; 282 283 284const SecurimageToken = { 285 getToken(el) { 286 const self = this; 287 form = el.closest('form'); 288 // Get the form data 289 formData = new FormData(form); 290 formData.delete('view'); 291 formData.delete('task'); 292 formData.append('component', formData.get('option')); 293 formData.delete('option'); 294 295 const msgContainerSelector = self.getSelector(el.parentNode.querySelector('.securimage-msg')); 296 let loader = document.createElement('img'); 297 loader.src = 'media/plg_captcha_securimage/images/loading/loading.gif'; 298 loader.style = 'width:18px;height:18px'; 299 document.querySelector(msgContainerSelector).appendChild(loader);
300 const catpchaContainer = el.closest('.captcha-container').querySelector('.captcha-container'); 301 const captchaInput = catpchaContainer.querySelector('.sicaptcha'); 302 el.disabled = true; 303 el.setAttribute('data-label', el.innerHTML); 304 305 306 Joomla.request({ 307 url: el.getAttribute('data-url') + '&' + Math.random(), 308 method: 'POST', 309 data: new URLSearchParams(formData).toString(), 310 perform: true, 311 onSuccess: function onSuccess(resp) { 312 313 catpchaContainer.style["display"] = 'none'; 314 315 // Remove loader image 316 loader.parentNode.removeChild(loader); 317 var response = {}; 318 try { 319 response = JSON.parse(resp); 320 } catch (e) { 321 Joomla.renderMessages({ 322 error: [Joomla.Text._('PLG_CAPTCHA_SECURIMAGE_TOKEN_ERROR_INVALID_RESPONSE')] 323 }, msgContainerSelector, false, 5000); 324 325 return; 326 } 327 328 if (response.success && response.data && response.data.length > 0) { 329 // Display all messages that we got 330 response.data.forEach(function (value) { 331 if (value === null) { 332 Joomla.renderMessages({ 333 error: ['NULL Ajax response'] 334 }, msgContainerSelector, false, 7000); 335 return; 336 } 337 338 // Display success alert 339 if (value.success) { 340 catpchaContainer.style["display"] = 'none'; 341 let timeleft = 60; 342 var tokenTimer = setInterval(function () { 343 if (timeleft <= 0) { 344 clearInterval(tokenTimer); 345 el.innerHTML = el.getAttribute('data-label'); 346 catpchaContainer.style["display"] = ''; 347 el.disabled = false; 348 } else { 349 el.innerHTML = Joomla.Text._('PLG_CAPTCHA_SECURIMAGE_TOKEN_WAIT_FOR_NEW_TOKEN').replace('[time]', timeleft); 350 } 351 timeleft -= 1; 352 }, 1000); 353 354 captchaInput.removeAttribute('data-required', 'required'); 355 captchaInput.classList.remove("required", "required-field", "validate-sicaptcha", "ce-validation-error"); 356 357 Joomla.renderMessages({ 358 message: [value.message] 359 }, msgContainerSelector, true); 360 } else { 361 updateCaptchas(); 362 catpchaContainer.style["display"] = ''; 363 el.disabled = false; 364 Joomla.renderMessages({ 365 error: [value.message] 366 }, msgContainerSelector, false, 7000); 367 } 368 }); 369 } 370 }, 371 onComplete: function onComplete() { 372 373 }, 374 onError: function onError() { 375 updateCaptchas(); 376 catpchaContainer.style["display"] = ''; 377 el.disabled = false; 378 Joomla.renderMessages({ 379 error: [Joomla.Text._('PLG_CAPTCHA_SECURIMAGE_TOKEN_ERROR_RESPONSE')], msgContainerSelector 380 }, msgContainerSelector, false, 5000); 381 } 382 }); 383 }, 384 385 getSelector(elm) { 386 if (elm.tagName === "BODY") return "BODY"; 387 const names = []; 388 while (elm.parentElement && elm.tagName !== "BODY") { 389 if (elm.id) { 390 names.unshift("#" + elm.getAttribute("id")); // getAttribute, because `elm.id` could also return a child element with name "id" 391 break; // Because ID should be unique, no more is needed. Remove the break, if you always want a full path. 392 } else { 393 let c = 1, e = elm; 394 for (; e.previousElementSibling; e = e.previousElementSibling, c++); 395 names.unshift(elm.tagName + ":nth-child(" + c + ")"); 396 } 397 elm = elm.parentElement; 398 } 399 return names.join(">"); 400 }, 401 init() { 402 const self = this; 403 tokenBtns = document.querySelectorAll('.btn-captcha-token'); 404 tokenBtns.forEach(btn => { 405 btn.addEventListener("click", () => { 406 self.getToken(btn); 407 }); 408 }); 409 }, 410}; 411 412 413 414// A delay of a second helps avoid issues 415// and no one is able to fill a form in less than one second, 416// so this is not an issue 417window.onload = () => { 418 SecurimageToken.init(); 419 updateCaptchas(); 420}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.