/** * Installer's AJAX frontend handler */ (function($) { // Avoid conflicts with other libraries 'use strict'; // Installer variables var pollTimer = null; var nextReadPosition = 0; var progressBarTriggered = false; var progressTimer = null; var currentProgress = 0; var refreshRequested = false; var transmissionOver = false; var statusCount = 0; // Template related variables var $contentWrapper = $('.install-body').find('.main'); // Intercept form submits interceptFormSubmit($('#install_install')); /** * Creates an XHR object * * jQuery cannot be used as the response is streamed, and * as of now, jQuery does not provide access to the response until * the connection is not closed. * * @return XMLHttpRequest */ function createXhrObject() { return new XMLHttpRequest(); } /** * Displays error, warning and log messages * * @param type * @param messages */ function addMessage(type, messages) { // Get message containers var $errorContainer = $('#error-container'); var $warningContainer = $('#warning-container'); var $logContainer = $('#log-container'); var $title, $description, $msgElement, arraySize = messages.length; for (var i = 0; i < arraySize; i++) { $msgElement = $('
'); $title = $(''); $title.text(messages[i].title); $msgElement.append($title); if (messages[i].hasOwnProperty('description')) { $description = $(''); $description.html(messages[i].description); $msgElement.append($description); } switch (type) { case 'error': $msgElement.addClass('errorbox'); $errorContainer.append($msgElement); break; case 'warning': $msgElement.addClass('warningbox'); $warningContainer.append($msgElement); break; case 'log': $msgElement.addClass('log'); $logContainer.prepend($msgElement); $logContainer.addClass('show_log_container'); break; case 'success': $msgElement.addClass('successbox'); $errorContainer.prepend($msgElement); break; } } } /** * Render a download box */ function addDownloadBox(downloadArray) { var $downloadContainer = $('#download-wrapper'); var $downloadBox, $title, $content, $link; for (var i = 0; i < downloadArray.length; i++) { $downloadBox = $(''); $downloadBox.addClass('download-box'); $title = $(''); $title.text(downloadArray[i].title); $downloadBox.append($title); if (downloadArray[i].hasOwnProperty('msg')) { $content = $(''); $content.text(downloadArray[i].msg); $downloadBox.append($content); } $link = $(''); $link.addClass('button1'); $link.attr('href', downloadArray[i].href); $link.text(downloadArray[i].download); $downloadBox.append($link); $downloadContainer.append($downloadBox); } } /** * Render update files' status */ function addUpdateFileStatus(fileStatus) { var $statusContainer = $('#file-status-wrapper'); $statusContainer.html(fileStatus); } /** * Displays a form from the response * * @param formHtml */ function addForm(formHtml) { var $formContainer = $('#form-wrapper'); $formContainer.html(formHtml); var $form = $('#install_install'); interceptFormSubmit($form); } /** * Handles navigation status updates * * @param navObj */ function updateNavbarStatus(navObj) { var navID, $stage, $stageListItem, $active; $active = $('#activemenu'); if (navObj.hasOwnProperty('finished')) { // This should be an Array var navItems = navObj.finished; for (var i = 0; i < navItems.length; i++) { navID = 'installer-stage-' + navItems[i]; $stage = $('#' + navID); $stageListItem = $stage.parent(); if ($active.length && $active.is($stageListItem)) { $active.removeAttr('id'); } $stage.addClass('completed'); } } if (navObj.hasOwnProperty('active')) { navID = 'installer-stage-' + navObj.active; $stage = $('#' + navID); $stageListItem = $stage.parent(); if ($active.length && !$active.is($stageListItem)) { $active.removeAttr('id'); } $stageListItem.attr('id', 'activemenu'); } } /** * Renders progress bar * * @param progressObject */ function setProgress(progressObject) { var $statusText, $progressBar, $progressText, $progressFiller, $progressFillerText; if (progressObject.task_name.length) { if (!progressBarTriggered) { // Create progress bar var $progressBarWrapper = $('#progress-bar-container'); // Create progress bar elements $progressBar = $(''); $progressBar.attr('id', 'progress-bar'); $progressText = $(''); $progressText.attr('id', 'progress-bar-text'); $progressFiller = $(''); $progressFiller.attr('id', 'progress-bar-filler'); $progressFillerText = $(''); $progressFillerText.attr('id', 'progress-bar-filler-text'); $statusText = $(''); $statusText.attr('id', 'progress-status-text'); $progressFiller.append($progressFillerText); $progressBar.append($progressText); $progressBar.append($progressFiller); $progressBarWrapper.append($statusText); $progressBarWrapper.append($progressBar); $progressFillerText.css('width', $progressBar.width()); progressBarTriggered = true; } else if (progressObject.hasOwnProperty('restart')) { clearInterval(progressTimer); $progressFiller = $('#progress-bar-filler'); $progressFillerText = $('#progress-bar-filler-text'); $progressText = $('#progress-bar-text'); $statusText = $('#progress-status-text'); $progressText.text('0%'); $progressFillerText.text('0%'); $progressFiller.css('width', '0%'); currentProgress = 0; } else { $statusText = $('#progress-status-text'); } // Update progress bar $statusText.text(progressObject.task_name + '…'); incrementProgressBar(Math.round(progressObject.task_num / progressObject.task_count * 100)); } } // Set cookies function setCookies(cookies) { var cookie; for (var i = 0; i < cookies.length; i++) { // Set cookie name and value cookie = encodeURIComponent(cookies[i].name) + '=' + encodeURIComponent(cookies[i].value); // Set path cookie += '; path=/'; document.cookie = cookie; } } // Redirects user function redirect(url, use_ajax) { if (use_ajax) { resetPolling(); var xhReq = createXhrObject(); xhReq.open('GET', url, true); xhReq.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhReq.send(); startPolling(xhReq); } else { window.location.href = url; } } /** * Parse messages from the response object * * @param messageJSON */ function parseMessage(messageJSON) { $('#loading_indicator').css('display', 'none'); var responseObject; try { responseObject = JSON.parse(messageJSON); } catch (err) { if (window.console) { console.log('Failed to parse JSON object\n\nMessage: ' + err.message + '\n\nServer Response: ' + messageJSON); } else { alert('Failed to parse JSON object\n\nMessage: ' + err.message + '\n\nServer Response: ' + messageJSON); } resetPolling(); return; } // Parse object if (responseObject.hasOwnProperty('errors')) { addMessage('error', responseObject.errors); } if (responseObject.hasOwnProperty('warnings')) { addMessage('warning', responseObject.warnings); } if (responseObject.hasOwnProperty('logs')) { addMessage('log', responseObject.logs); } if (responseObject.hasOwnProperty('success')) { addMessage('success', responseObject.success); } if (responseObject.hasOwnProperty('form')) { addForm(responseObject.form); } if (responseObject.hasOwnProperty('progress')) { setProgress(responseObject.progress); } if (responseObject.hasOwnProperty('download')) { addDownloadBox(responseObject.download); } if (responseObject.hasOwnProperty('file_status')) { addUpdateFileStatus(responseObject.file_status); } if (responseObject.hasOwnProperty('nav')) { updateNavbarStatus(responseObject.nav); } if (responseObject.hasOwnProperty('cookies')) { setCookies(responseObject.cookies); } if (responseObject.hasOwnProperty('refresh')) { refreshRequested = true; } if (responseObject.hasOwnProperty('redirect')) { redirect(responseObject.redirect.url, responseObject.redirect.use_ajax); } if (responseObject.hasOwnProperty('over')) { if (responseObject.over) { transmissionOver = true; } } } /** * Processes status data * * @param status */ function processTimeoutResponse(status) { if (statusCount === 12) { // 1 minute hard cap status = 'fail'; } if (status === 'continue') { refreshRequested = false; doRefresh(); } else if (status === 'running') { statusCount++; $('#loading_indicator').css('display', 'block'); setTimeout(queryInstallerStatus, 5000); } else { $('#loading_indicator').css('display', 'none'); addMessage('error', [{ title: installLang.title, description: installLang.msg }] ); } } /** * Queries the installer's status */ function queryInstallerStatus() { var url = $(location).attr('pathname'); var lookUp = 'install/app.php'; var position = url.indexOf(lookUp); if (position === -1) { lookUp = 'install'; position = url.indexOf(lookUp); if (position === -1) { return false; } } url = url.substring(0, position) + lookUp + '/installer/status'; $.getJSON(url, function(data) { processTimeoutResponse(data.status); }); } /** * Process updates in streamed response * * @param xhReq XHR object */ function pollContent(xhReq) { var messages = xhReq.responseText; var msgSeparator = '}\n\n'; var unprocessed, messageEndIndex, endOfMessageIndex, message; do { unprocessed = messages.substring(nextReadPosition); messageEndIndex = unprocessed.indexOf(msgSeparator); if (messageEndIndex !== -1) { endOfMessageIndex = messageEndIndex + msgSeparator.length; message = unprocessed.substring(0, endOfMessageIndex); parseMessage($.trim(message)); nextReadPosition += endOfMessageIndex; } } while (messageEndIndex !== -1); if (xhReq.readyState === 4) { $('#loading_indicator').css('display', 'none'); resetPolling(); var timeoutDetected = !transmissionOver; if (refreshRequested) { refreshRequested = false; doRefresh(); } if (timeoutDetected) { statusCount = 0; queryInstallerStatus(); } } } /** * Animates the progress bar * * @param $progressText * @param $progressFiller * @param $progressFillerText * @param progressLimit */ function incrementFiller($progressText, $progressFiller, $progressFillerText, progressLimit) { if (currentProgress >= progressLimit || currentProgress >= 100) { clearInterval(progressTimer); return; } var $progressBar = $('#progress-bar'); currentProgress++; $progressFillerText.css('width', $progressBar.width()); $progressFillerText.text(currentProgress + '%'); $progressText.text(currentProgress + '%'); $progressFiller.css('width', currentProgress + '%'); } /** * Wrapper function for progress bar rendering and animating * * @param progressLimit */ function incrementProgressBar(progressLimit) { var $progressFiller = $('#progress-bar-filler'); var $progressFillerText = $('#progress-bar-filler-text'); var $progressText = $('#progress-bar-text'); var progressStart = $progressFiller.width() / $progressFiller.offsetParent().width() * 100; currentProgress = Math.floor(progressStart); clearInterval(progressTimer); progressTimer = setInterval(function() { incrementFiller($progressText, $progressFiller, $progressFillerText, progressLimit); }, 10); } /** * Resets the polling timer */ function resetPolling() { clearInterval(pollTimer); nextReadPosition = 0; } /** * Sets up timer for processing the streamed HTTP response * * @param xhReq */ function startPolling(xhReq) { resetPolling(); transmissionOver = false; pollTimer = setInterval(function () { pollContent(xhReq); }, 250); } /** * Refresh page */ function doRefresh() { resetPolling(); var xhReq = createXhrObject(); xhReq.open('GET', $(location).attr('pathname'), true); xhReq.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhReq.send(); startPolling(xhReq); } /** * Renders the AJAX UI layout */ function setupAjaxLayout() { progressBarTriggered = false; // Clear content $contentWrapper.html(''); var $header = $(''); $header.attr('id', 'header-container'); $contentWrapper.append($header); var $description = $(''); $description.attr('id', 'description-container'); $contentWrapper.append($description); var $errorContainer = $(''); $errorContainer.attr('id', 'error-container'); $contentWrapper.append($errorContainer); var $warningContainer = $(''); $warningContainer.attr('id', 'warning-container'); $contentWrapper.append($warningContainer); var $progressContainer = $(''); $progressContainer.attr('id', 'progress-bar-container'); $contentWrapper.append($progressContainer); var $logContainer = $(''); $logContainer.attr('id', 'log-container'); $contentWrapper.append($logContainer); var $installerContentWrapper = $(''); $installerContentWrapper.attr('id', 'content-container'); $contentWrapper.append($installerContentWrapper); var $installerDownloadWrapper = $(''); $installerDownloadWrapper.attr('id', 'download-wrapper'); $installerContentWrapper.append($installerDownloadWrapper); var $updaterFileStatusWrapper = $(''); $updaterFileStatusWrapper.attr('id', 'file-status-wrapper'); $installerContentWrapper.append($updaterFileStatusWrapper); var $formWrapper = $(''); $formWrapper.attr('id', 'form-wrapper'); $installerContentWrapper.append($formWrapper); var $spinner = $(''); $spinner.attr('id', 'loading_indicator'); $spinner.html(' '); $contentWrapper.append($spinner); } // Submits a form function submitForm($form, $submitBtn) { $form.css('display', 'none'); var xhReq = createXhrObject(); xhReq.open('POST', $form.attr('action'), true); xhReq.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhReq.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhReq.send(getFormFields($form, $submitBtn)); // Disable language selector $('#language_selector :input, label').css('display', 'none'); // Clear content setupAjaxLayout(); $('#loading_indicator').css('display', 'block'); startPolling(xhReq); } /** * Add submit button to the POST information * * @param $form * @param $submitBtn * * @returns {*} */ function getFormFields($form, $submitBtn) { var formData = $form.serialize(); formData += ((formData.length) ? '&' : '') + encodeURIComponent($submitBtn.attr('name')) + '='; formData += encodeURIComponent($submitBtn.attr('value')); return formData; } /** * Intercept form submit events and determine the submit button used * * @param $form */ function interceptFormSubmit($form) { if (!$form.length) { return; } $form.find(':submit').bind('click', function (event) { event.preventDefault(); submitForm($form, $(this)); }); } })(jQuery); // Avoid conflicts with other libraries