jQuery(function ($) { // Let's initiate the XHR. new DLM_XHR_Download(); }); class DLM_XHR_Download { responsHeaders = {}; constructor() { // dlmXHRinstance defined in inline script dlmXHRinstance = this; this.init(); } init() { dlmXHRinstance.attachButtonEvent(); } // Function to attach events for the download buttons attachButtonEvent() { // Stop propagation on child elements jQuery('html,body').on( 'click', '.dlm-no-access-modal-window > div', function (e){ e.stopPropagation(); } ); // Close modal on these clicks jQuery('html, body').on('click', '.dlm-no-access-modal-overlay, .dlm-no-access-modal-close, .dlm-no-access-modal-window', function (e) { e.stopPropagation(); jQuery('#dlm-no-access-modal').remove(); }); // Close modal on ESC key press jQuery(document).on('keydown', function (e) { if (e.key === 'Escape') { jQuery('#dlm-no-access-modal').remove(); } }); jQuery('html, body').on('click', 'a', function (e) { let url = jQuery(this).attr('href'); // Let's see if we need to do XHR download on this link. let noXHR = false; if (jQuery(this).hasClass('dlm-no-xhr-download')) { noXHR = true; } if ('undefined' !== typeof dlmNonXHRGlobalLinks && dlmNonXHRGlobalLinks.length > 0) { if ('undefined' != typeof url) { dlmNonXHRGlobalLinks.forEach((element) => { if (url.indexOf(element) >= 0) { noXHR = true; } }); } } if (noXHR) { // No XHR so return; jQuery('#dlm-no-access-modal').remove(); // Close the modal also in case we opened it before. return; } if (typeof url === 'string') { try { url = new URL(url, window.location.origin).href; } catch (e) {} } jQuery(document).trigger('dlm-xhr-download-button-click', [url, this, dlmXHRGlobalLinks]); if ('undefined' != typeof url && url.indexOf(dlmXHRGlobalLinks) >= 0) { const redirect_link = jQuery(this).data('redirect'), target = jQuery(this).attr('target'); // Check if the link has a target attribute, and if it does, we need to open it in a new window. if ('undefined' !== typeof redirect_link && true === redirect_link && '_blank' === target) { // If we need to redirect/open in a new tab, then we do not need to do XHR, so return. return; } dlmXHRinstance.handleDownloadClick(this, e); } }); } handleDownloadClick(obj, e) { e.stopPropagation(); const href = obj.getAttribute('href'); let triggerObject = { button : obj, href : href, buttonObj: jQuery(obj), }; // Show the progress bar after complete download also if (triggerObject.href.indexOf('blob:http') !== -1) { /*triggerObject.buttonObj.addClass('download-100'); setTimeout(function () { triggerObject.buttonObj.removeClass('download-100'); }, 1500);*/ return; } if ('#' === triggerObject.href) { return; } e.preventDefault(); dlmXHRinstance.retrieveBlob(triggerObject); } retrieveBlob(triggerObject) { const instance = this; let { button, href, buttonObj, } = triggerObject; // This will hold the file as a local object URL let _OBJECT_URL; const request = new XMLHttpRequest(); const $setCookie = dlmXHR.prevent_duplicates, buttonTarget = buttonObj.attr('target'); let buttonClass = buttonObj.attr('class'); buttonClass = ('undefined' !== typeof buttonClass && '' !== buttonClass) ? buttonClass.replace('dlm-download-started', '').replace('dlm-download-completed', '') : ''; buttonObj.addClass('dlm-download-started'); button.setAttribute('href', '#'); button.removeAttribute('download'); button.setAttribute('disabled', 'disabled'); if (dlmXHRProgress) { const loading_gif = ''; button.innerHTML += loading_gif; } // Trigger the `dlm_download_triggered` action jQuery(document).trigger('dlm_download_triggered', [this, button, buttonObj, _OBJECT_URL, request]); let check_headers = false; request.responseType = 'blob'; request.onreadystatechange = function () { let { status, readyState, statusText } = request; let headers = request .getAllResponseHeaders() .split('\r\n') .reduce((result, current) => { let [name, value] = current.split(': '); result[name] = value; return result; }, {}); if (Object.keys(headers).length > 1) { check_headers = true; // Set the response headers to the instance so that we can use it later. instance.responseHeaders = headers; } let file_name = 'download'; let dlmFilenameHeader = false, dlmNoWaypoints = false, dlmRedirectHeader = false, dlmExternalDownloadHeader = false, dlmNoAccessHeader = null, dlmNoAccessModalHeader = false, dlmErrorHeader = false, dlmDownloadIdHeader = false, dlmDownloadVersionHeader = false, dlmNoAccessModalTextHeader = false; /** * Old headers check */ if (check_headers && 'undefined' !== typeof instance.responseHeaders['dlm-file-name']) { dlmFilenameHeader = instance.responseHeaders['dlm-file-name']; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['dlm-no-waypoints']) { dlmNoWaypoints = true; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['dlm-redirect']) { dlmRedirectHeader = instance.responseHeaders['dlm-redirect']; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['dlm-external-download']) { dlmExternalDownloadHeader = true; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['dlm-no-access']) { dlmNoAccessHeader = instance.responseHeaders['dlm-no-access']; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['dlm-no-access-modal']) { dlmNoAccessModalHeader = instance.responseHeaders['dlm-no-access-modal']; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['dlm-error']) { dlmErrorHeader = instance.responseHeaders['dlm-error']; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['dlm-download-id']) { dlmDownloadIdHeader = instance.responseHeaders['dlm-download-id']; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['dlm-version-id']) { dlmDownloadVersionHeader = instance.responseHeaders['dlm-version-id']; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['dlm-no-access-modal-text']) { dlmNoAccessModalTextHeader = instance.responseHeaders['dlm-no-access-modal-text']; } // End old headers check /** * New headers check */ if (check_headers && 'undefined' !== typeof instance.responseHeaders['x-dlm-file-name']) { dlmFilenameHeader = instance.responseHeaders['x-dlm-file-name']; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['x-dlm-no-waypoints']) { dlmNoWaypoints = true; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['x-dlm-redirect']) { dlmRedirectHeader = instance.responseHeaders['x-dlm-redirect']; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['x-dlm-external-download']) { dlmExternalDownloadHeader = true; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['x-dlm-no-access']) { dlmNoAccessHeader = instance.responseHeaders['x-dlm-no-access']; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['x-dlm-no-access-modal']) { dlmNoAccessModalHeader = instance.responseHeaders['x-dlm-no-access-modal']; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['x-dlm-error']) { dlmErrorHeader = instance.responseHeaders['x-dlm-error']; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['x-dlm-download-id']) { dlmDownloadIdHeader = instance.responseHeaders['x-dlm-download-id']; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['x-dlm-version-id']) { dlmDownloadVersionHeader = instance.responseHeaders['x-dlm-version-id']; } if (check_headers && 'undefined' !== typeof instance.responseHeaders['x-dlm-no-access-modal-text']) { dlmNoAccessModalTextHeader = instance.responseHeaders['x-dlm-no-access-modal-text']; } // End new headers check if (dlmFilenameHeader) { file_name = dlmFilenameHeader.replace(/\"/g, '').replace(';', ''); file_name = decodeURI(file_name); } else if (check_headers && 'undefined' !== typeof instance.responseHeaders['content-disposition']) { file_name = instance.responseHeaders['content-disposition'].split(/(?:filename\*=UTF-8'')|(?:filename=)/)[1]; file_name = file_name.replace(/\"/g, '').replace(';', ''); // We use this method because we urlencoded it on the server so that characters like chinese or persian are not broken file_name = decodeURI(file_name); } // Error translations if (dlmErrorHeader) { //var obj = { key: undefined }; if (dlmErrorHeader in dlmXHRtranslations) { dlmErrorHeader = dlmXHRtranslations[dlmErrorHeader]; } else { dlmErrorHeader = dlmXHRtranslations['error']; } } // Let's check for DLM request headers if (request.readyState === 2) { // Add other checks for responseHeaders that can be taken care of from extensions. if (check_headers && 'undefined' !== typeof instance.responseHeaders['x-dlm-force-abort'] && '' !== instance.responseHeaders['x-dlm-force-abort']) { button.removeAttribute('download'); button.setAttribute('href', href); buttonObj.removeClass().addClass(buttonClass).find('span.dlm-xhr-progress').remove(); buttonObj.find('.dlm-xhr-loading-gif').remove(); request.abort(); jQuery('#dlm-no-access-modal').remove(); return; } // If the dlm-no-waypoints header is set we need to redirect. if (dlmNoWaypoints) { request.abort(); if (dlmRedirectHeader) { window.location.href = dlmRedirectHeader; return; } window.location.href = href; return; } // If it's an external link we need to redirect. if (dlmExternalDownloadHeader) { request.abort(); dlmXHRinstance.dlmExternalDownload(instance.responseHeaders, button, buttonObj, file_name, href); return; } // If there are no specific DLM headers then we need to abort and redirect. const responseDLM = Object.keys(instance.responseHeaders).filter((element) => { return element.indexOf('dlm-') !== -1; }).length; if (0 === responseDLM) { request.abort(); window.location.href = href; return; } if (dlmNoAccessHeader && 'true' === dlmNoAccessHeader) { if (dlmNoAccessModalHeader && 0 != dlmNoAccessModalHeader) { dlmXHRinstance.dlmNoAccessModal(instance.responseHeaders); button.removeAttribute('download'); button.setAttribute('href', href); buttonObj.removeClass().addClass(buttonClass).find('span.dlm-xhr-progress').remove(); buttonObj.find('.dlm-xhr-loading-gif').remove(); request.abort(); return; } } // If there is a dlm-error headers means we have an error. Display the error and abort. if (dlmErrorHeader && '' !== dlmErrorHeader && null !== dlmErrorHeader) { dlmXHRinstance.dlmLogDownload(instance.responseHeaders, 'failed', false); button.removeAttribute('download'); button.setAttribute('href', href); buttonObj.removeClass().addClass(buttonClass).find('span.dlm-xhr-progress').remove(); buttonObj.find('.dlm-xhr-loading-gif').remove(); request.abort(); if (dlmNoAccessModalHeader && 0 != dlmNoAccessModalHeader) { dlmXHRinstance.dlmNoAccessModal(instance.responseHeaders); } else { buttonObj.find('.dlm-xhr-error').remove(); buttonObj.append('' + dlmErrorHeader + ''); } return; } // If we have a x-dlm-redirect header means this is a redirect. Let's do that. if (dlmRedirectHeader && '' !== dlmRedirectHeader && null !== dlmRedirectHeader) { dlmXHRinstance.dlmLogDownload(instance.responseHeaders, 'redirected', false, dlmRedirectHeader, dlmNoAccessHeader, buttonTarget); button.removeAttribute('download'); button.setAttribute('href', href); buttonObj.removeClass().addClass(buttonClass).find('span.dlm-xhr-progress').remove(); buttonObj.find('.dlm-xhr-loading-gif').remove(); request.abort(); return; } } if (status == 404 && readyState == 2) { let p = document.createElement('p'); p.innerHTML = statusText; button.parentNode.appendChild(p); } if (status == 401 && readyState == 2) { window.location.href = statusText; return; } if (status == 403 && readyState == 2) { let p = document.createElement('p'); p.innerHTML = statusText; button.parentNode.appendChild(p); } if (status == 200 && readyState == 4) { let blob = request.response; _OBJECT_URL = URL.createObjectURL(blob); // Remove event listener button.removeEventListener('click', dlmXHRinstance.handleDownloadClick); // Set the download attribute of the a.download-complete to the file name button.setAttribute('download', `${file_name}`); // Set the href of the a.download-complete to the object URL button.setAttribute('href', _OBJECT_URL); // Trigger click on a.download-complete button.click(); buttonObj.removeClass().addClass(buttonClass + ' dlm-download-complete'); dlmXHRinstance.attachButtonEvent(); // Append the paragraph to the download-contaner // Trigger the `dlm_download_complete` action jQuery(document).trigger('dlm_download_complete', [this, button, buttonObj, _OBJECT_URL]); dlmXHRinstance.dlmLogDownload(instance.responseHeaders, 'completed', $setCookie); // Recommended : Revoke the object URL after some time to free up resources window.URL.revokeObjectURL(_OBJECT_URL); button.removeAttribute('download'); button.setAttribute('href', href); buttonObj.find('.dlm-xhr-loading-gif').remove(); // There is no way to find out whether user finished downloading setTimeout(function () { buttonObj.removeClass().addClass(buttonClass).find('span.dlm-xhr-progress').remove(); }, 4000); } }; if (dlmXHRProgress) { request.addEventListener('progress', function (e) { let total = e.total; // Set the total file size from the custom response header if ('undefined' === typeof e.total || e.total === 'undefined' || 0 === e.total) { if ('undefined' !== typeof instance.responseHeaders['x-dlm-filesize']) { total = instance.responseHeaders['x-dlm-filesize']; } } let percent_complete = (e.loaded / total) * 100; // Force perfect complete to have 2 digits percent_complete = percent_complete.toFixed(); let $class = 'dlm-download-started'; buttonObj.find('span.dlm-xhr-progress').remove(); // Comment below lines for the new XHR loader so that we know where to rever $class = $class + ' download-' + Math.ceil(percent_complete / 10) * 10; if (Infinity != percent_complete) { buttonObj.append(' ' + percent_complete + '%'); } // Show spinner buttonObj.removeClass().addClass(buttonClass + ' ' + $class); // Trigger the `dlm_download_progress` action jQuery(document).trigger('dlm_download_progress', [this, button, buttonObj, _OBJECT_URL, e, percent_complete]); }); } request.onerror = function () { button.removeAttribute('download'); button.setAttribute('href', href); buttonObj.removeClass().addClass(buttonClass + ' dlm-no-xhr-download').find('span.dlm-xhr-progress').remove(); buttonObj.find('.dlm-xhr-error').remove(); buttonObj.append('' + dlmXHRtranslations.error + ''); console.log('** An error occurred during the transaction'); }; request.open('GET', href, true); request.setRequestHeader('Cache-Control', 'no-store, no-cache, no-transform, max-age=0'); request.setRequestHeader('dlm-xhr-request', 'dlm_XMLHttpRequest'); request.send(); } dlmLogDownload(headers, status, cookie, redirect_path = null, no_access = null, target = '_self') { const currentURL = window.location.href; const download_id = headers['x-dlm-download-id'] ?? headers['dlm-download-id']; const version_id = headers['x-dlm-version-id'] ?? headers['dlm-version-id']; const ua = navigator.userAgent || ''; const isSafari = /^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(ua); const isAppleSafari = isSafari && !/CriOS|FxiOS|EdgiOS/i.test(ua); // Early redirect — skip logging if no_access = true if (no_access && redirect_path) { if (isAppleSafari) { window.location.href = redirect_path; } else { const tgt = target || '_self'; const downloadTab = window.open(redirect_path, tgt, 'noopener'); if (downloadTab) { downloadTab.location.href = redirect_path; } } return; } const params = new URLSearchParams({ action: 'log_dlm_xhr_download', download_id, version_id, status, cookie, currentURL, nonce: headers['x-dlm-nonce'] }); for (const [key, value] of Object.entries(headers)) { params.append(`responseHeaders[${key}]`, value); } try { navigator.sendBeacon( dlmXHR.ajaxUrl, new Blob([params.toString()], { type: 'application/x-www-form-urlencoded;charset=UTF-8' }) ); } catch (e) { try { fetch(dlmXHR.ajaxUrl, { method: 'POST', body: params, keepalive: true }); } catch (_) {} } if (!redirect_path) return; if (isAppleSafari) { window.location.href(redirect_path); return; } const tgt = target || '_self'; const downloadTab = window.open(redirect_path, tgt, 'noopener'); if (no_access && downloadTab) { downloadTab.location.href = redirect_path; } } dlmNoAccessModal(headers) { const instance = this; let download = 'empty-download', version = 'empty-version', restriction = 'empty-restriction', text = ''; /** * Old headers, will be removed in a future release. DLM Amazon S3 adds these headers. */ if ('undefined' !== typeof headers['dlm-download-id']) { download = headers['dlm-download-id']; } if ('undefined' !== typeof headers['dlm-version-id']) { version = headers['dlm-version-id']; } if ('undefined' !== typeof headers['dlm-no-access-modal-text']) { text = headers['dlm-no-access-modal-text']; } if ('undefined' !== typeof headers['dlm-no-access-restriction']) { restriction = headers['dlm-no-access-restriction']; } // End of old headers. /** * New headers. */ if ('undefined' !== typeof headers['x-dlm-download-id']) { download = headers['x-dlm-download-id']; } if ('undefined' !== typeof headers['x-dlm-version-id']) { version = headers['x-dlm-version-id']; } if ('undefined' !== typeof headers['x-dlm-no-access-modal-text']) { text = headers['x-dlm-no-access-modal-text']; } if ('undefined' !== typeof headers['x-dlm-no-access-restriction']) { restriction = headers['x-dlm-no-access-restriction']; } // End of new headers. let data = { download_id: download, version_id : version, modal_text : text, restriction, action : 'no_access_dlm_xhr_download', nonce : headers['x-dlm-nonce'] }; jQuery(document).trigger('dlm-xhr-modal-data', [data, headers]); document.dispatchEvent( new CustomEvent('dlm-xhr-modal-data', {detail: {data:data, headers:headers}}) ); jQuery.post(dlmXHR.ajaxUrl, data, function (response) { jQuery('#dlm-no-access-modal').remove(); //document.body.insertAdjacentHTML('beforeend', response); const tempDiv = document.createElement('div'); tempDiv.innerHTML = response; // Extract and execute scripts from the response using a regular expression const scriptRegex = /]*>([\s\S]*?)<\/script>/gm; // Remove script tags from tempDiv.innerHTML tempDiv.innerHTML = tempDiv.innerHTML.replace(scriptRegex, ''); let match; while ((match = scriptRegex.exec(response)) !== null) { const scriptTag = match[0]; const scriptContent = match[1]; const newScript = document.createElement('script'); // Check if the script has a src attribute const srcMatch = scriptTag.match(/src=["']([^"']+)["']/); if (srcMatch) { newScript.src = srcMatch[1]; } else { newScript.textContent = scriptContent; } // Append the new script to the modal if exists const modal = tempDiv.querySelector('#dlm-no-access-modal'); if (modal) { modal.appendChild(newScript); } else { document.body.appendChild(newScript); } } // Append the parsed HTML elements to the body while (tempDiv.firstChild) { document.body.appendChild(tempDiv.firstChild); } //jQuery('body').append(response); jQuery(document).trigger(data['action'], [response, data]); document.dispatchEvent( new CustomEvent(data['action'], {detail: [response, data]}) ); }); } dlmExternalDownload(headers, button, buttonObj, file_name, href) { const request = new XMLHttpRequest(), buttonTarget = buttonObj.attr('target'); let buttonClass = buttonObj.attr('class'), _OBJECT_URL, uri = ''; if ('undefined' !== typeof headers['dlm-external-download']) { uri = headers['dlm-external-download']; } if ('undefined' !== typeof headers['x-dlm-external-download']) { uri = headers['x-dlm-external-download']; } buttonClass = ('undefined' !== typeof buttonClass && '' !== buttonClass) ? buttonClass.replace('dlm-download-started', '').replace('dlm-download-completed', '') : ''; buttonObj.addClass('dlm-download-started'); button.setAttribute('href', '#'); button.removeAttribute('download'); button.setAttribute('disabled', 'disabled'); // Trigger the `dlm_download_triggered` action jQuery(document).trigger('dlm_download_triggered', [this, button, buttonObj, _OBJECT_URL, request]); request.responseType = 'blob'; request.onreadystatechange = function () { let { status, readyState, statusText } = request; if (403 === status) { dlmXHRinstance.dlmLogDownload(headers, 'failed', false); request.abort(); buttonObj.find('.dlm-xhr-error').remove(); buttonObj.append('Acces Denied to file.'); return; } if (status == 200 && readyState == 4) { let blob = request.response; _OBJECT_URL = URL.createObjectURL(blob); // Remove event listener button.removeEventListener('click', dlmXHRinstance.handleDownloadClick); // Set the download attribute of the a.download-complete to the file name button.setAttribute('download', `${file_name}`); // Set the href of the a.download-complete to the object URL button.setAttribute('href', _OBJECT_URL); // Trigger click on a.download-complete button.click(); buttonObj.removeClass().addClass(buttonClass + ' dlm-download-complete'); dlmXHRinstance.attachButtonEvent(); // Append the paragraph to the download-contaner // Trigger the `dlm_download_complete` action jQuery(document).trigger('dlm_download_complete', [this, button, buttonObj, _OBJECT_URL]); dlmXHRinstance.dlmLogDownload(headers, 'completed', false); // Recommended : Revoke the object URL after some time to free up resources window.URL.revokeObjectURL(_OBJECT_URL); button.removeAttribute('download'); button.setAttribute('href', href); // There is no way to find out whether user finished downloading buttonObj.find('.dlm-xhr-loading-gif').remove(); setTimeout(function () { buttonObj.removeClass().addClass(buttonClass).find('span.dlm-xhr-progress').remove(); }, 1000); } }; if (dlmXHRProgress) { request.addEventListener('progress', function (e) { let total = e.total; if ('undefined' === typeof e.total || e.total === 'undefined') { total = request.getResponseHeader('X-DLM-Filesize'); } let percent_complete = (e.loaded / total) * 100; // Force perfect complete to have 2 digits percent_complete = percent_complete.toFixed(); let $class = 'dlm-download-started'; buttonObj.find('span.dlm-xhr-progress').remove(); // Comment below lines for the new XHR loader so that we know where to rever $class = $class + ' download-' + Math.ceil(percent_complete / 10) * 10; if (Infinity != percent_complete) { buttonObj.append(' ' + percent_complete + '%'); } // Show spinner buttonObj.removeClass().addClass(buttonClass + ' ' + $class); // Trigger the `dlm_download_progress` action jQuery(document).trigger('dlm_download_progress', [this, button, buttonObj, _OBJECT_URL, e, percent_complete]); }); } request.onerror = function () { button.removeAttribute('download'); button.setAttribute('href', href); buttonObj.removeClass().addClass(buttonClass + ' .dlm-no-xhr-download').find('span.dlm-xhr-progress').remove(); buttonObj.find('.dlm-xhr-error').remove(); buttonObj.append('' + dlmXHRtranslations.error + ''); console.log('** An error occurred during the transaction'); }; request.open('GET', uri, true); request.setRequestHeader('Cache-Control', 'no-store, no-cache, no-transform, max-age=0'); request.setRequestHeader('dlm-xhr-request', 'dlm_XMLHttpRequest'); request.send(); } }