| Current Path : /proc/1908984/root/proc/2603263/cwd/ |
| Current File : //proc/1908984/root/proc/2603263/cwd/com_joomlaupdate.tar |
js/default.js 0000644 00000034657 15234424455 0007171 0 ustar 00 function extractionMethodHandler(target, prefix)
{
jQuery(function ($) {
$em = $(target);
displayStyle = ($em.val() === 'direct') ? 'none' : 'table-row';
document.getElementById(prefix + '_notice').style.display = displayStyle;
document.getElementById(prefix + '_hostname').style.display = displayStyle;
document.getElementById(prefix + '_port').style.display = displayStyle;
document.getElementById(prefix + '_username').style.display = displayStyle;
document.getElementById(prefix + '_password').style.display = displayStyle;
document.getElementById(prefix + '_directory').style.display = displayStyle;
});
}
(function($, document, window) {
/**
* PreUpdateChecker
*
* @type {Object}
*/
var PreUpdateChecker = {};
/**
* Warning visibility flags
*
* @type {Boolean}
*/
var showorangewarning = false;
var showyellowwarning = false;
/**
* Config object
*
* @type {{serverUrl: string, selector: string}}
*/
PreUpdateChecker.config = {
serverUrl: 'index.php?option=com_joomlaupdate&task=update.fetchextensioncompatibility',
selector: '.extension-check'
};
/**
* Extension compatibility states returned by the server.
*
* @type {{INCOMPATIBLE: number, COMPATIBLE: number, MISSING_COMPATIBILITY_TAG: number, SERVER_ERROR: number}}
*/
PreUpdateChecker.STATE = {
INCOMPATIBLE: 0,
COMPATIBLE: 1,
MISSING_COMPATIBILITY_TAG: 2,
SERVER_ERROR: 3
};
/**
* Run the PreUpdateChecker.
* Called by document ready, setup below.
*/
PreUpdateChecker.run = function () {
$('.settingstoggle').css('float', 'right').css('cursor', 'pointer');
$('.settingstoggle').on('click', function(toggle, index)
{
var settingsfieldset = $(this).closest('fieldset');
if($(this).data('state') == 'closed')
{
$(this).data('state', 'open');
$(this).html( COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_SHOW_LESS_COMPATIBILITY_INFORMATION);
settingsfieldset.find('.settingsInfo').removeClass('hidden');
}
else
{
$(this).data('state', 'closed');
$(this).html( COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_SHOW_MORE_COMPATIBILITY_INFORMATION);
settingsfieldset.find('.settingsInfo').addClass('hidden');
}
});
PreUpdateChecker.nonCoreCriticalPlugins = Joomla.getOptions('nonCoreCriticalPlugins', []);
// If there are no non Core Critical Plugins installed then disable the warnings upfront
if (PreUpdateChecker.nonCoreCriticalPlugins.length === 0)
{
$('#preupdateCheckWarning, #preupdateconfirmation, #preupdatecheckbox, #preupdatecheckheadings').css('display', 'none');
$('#preupdatecheckbox #noncoreplugins').prop('checked', true);
$('button.submitupdate').removeClass('disabled');
$('button.submitupdate').prop('disabled', false);
}
// Grab all extensions based on the selector set in the config object
var $extensions = $(PreUpdateChecker.config.selector);
// If there are no extensions to be checked we can exit here
if ($extensions.length === 0)
{
return;
}
$('#preupdatecheckbox #noncoreplugins').on('change', function ()
{
if ($('#preupdatecheckbox #noncoreplugins').is(':checked')) {
if (confirm(Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_POTENTIALLY_DANGEROUS_PLUGIN_CONFIRM_MESSAGE'))) {
$('button.submitupdate').removeClass('disabled');
$('button.submitupdate').prop('disabled', false);
}
else
{
$('#preupdatecheckbox #noncoreplugins').prop('checked', false);
}
} else {
$('button.submitupdate').addClass('disabled');
$('button.submitupdate').prop('disabled', true);
}
});
// Get version of the available joomla update
PreUpdateChecker.joomlaTargetVersion = window.joomlaTargetVersion;
PreUpdateChecker.joomlaCurrentVersion = window.joomlaCurrentVersion;
// No point creating and loading a component stylesheet for 4 settings
$('.compatibilitytypes img').css('height', '20px');
$('.compatibilitytypes').css('display', 'none').css('margin-left', 0);
// The currently processing line should show until it’s finished
$('#compatibilitytype0').css('display', 'block');
$('.compatibilitytoggle').css('float', 'right').css('cursor', 'pointer');
$('.compatibilitytoggle').on('click', function(toggle, index)
{
var compatibilitytypes = $(this).closest('fieldset.compatibilitytypes');
if($(this).data('state') == 'closed')
{
$(this).data('state', 'open');
$(this).html( COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_SHOW_LESS_COMPATIBILITY_INFORMATION);
compatibilitytypes.find('.exname').removeClass('span8').addClass('span4');
compatibilitytypes.find('.extype').removeClass('span4').addClass('span1');
compatibilitytypes.find('.upcomp').removeClass('hidden').addClass('span3');
compatibilitytypes.find('.currcomp').removeClass('hidden').addClass('span3');
compatibilitytypes.find('.instver').removeClass('hidden').addClass('span1');
if (PreUpdateChecker.showyellowwarning)
{
compatibilitytypes.find("#updateyellowwarning").removeClass('hidden');
}
if (PreUpdateChecker.showorangewarning)
{
compatibilitytypes.find("#updateorangewarning").removeClass('hidden');
}
}
else
{
$(this).data('state', 'closed');
$(this).html( COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_SHOW_MORE_COMPATIBILITY_INFORMATION);
compatibilitytypes.find('.exname').addClass('span8').removeClass('span4');
compatibilitytypes.find('.extype').addClass('span4').removeClass('span1');
compatibilitytypes.find('.upcomp').addClass('hidden').removeClass('span3');
compatibilitytypes.find('.currcomp').addClass('hidden').removeClass('span3');
compatibilitytypes.find('.instver').addClass('hidden').removeClass('span1');
compatibilitytypes.find("#updateyellowwarning").addClass('hidden');
compatibilitytypes.find("#updateorangewarning").addClass('hidden');
}
});
$extensions.each(function () {
// Check compatibility for each extension, pass jQuery object and a callback
// function after completing the request
PreUpdateChecker.checkCompatibility($(this), PreUpdateChecker.setResultView);
});
}
/**
* Check the compatibility for a single extension.
* Requests the server checking the compatibility based on the data set in the element's data attributes.
*
* @param {Object} $extension
* @param {callable} callback
*/
PreUpdateChecker.checkCompatibility = function ($extension, callback) {
// Result object passed to the callback
// Set to server error by default
var extension = {
$element: $extension,
compatibleVersion: 0,
serverError: 1
};
// Request the server to check the compatiblity for the passed extension and joomla version
$.getJSON(PreUpdateChecker.config.serverUrl, {
'joomla-target-version': PreUpdateChecker.joomlaTargetVersion,
'joomla-current-version': PreUpdateChecker.joomlaCurrentVersion,
'extension-version': $extension.data('extension-current-version'),
'extension-id': $extension.data('extensionId')
}).done(function(response) {
// Extract the data from the JResponseJson object
extension.serverError = 0;
extension.compatibilityData = response.data;
}).always(function(e) {
// Pass the retrieved data to the callback
callback(extension);
});
}
/**
* Set the result for a passed extensionData object containing state, jQuery object and compatible version
*
* @param {Object} extensionData
*/
PreUpdateChecker.setResultView = function (extensionData) {
var html = '';
// Process Target Version Extension Compatibility
if (extensionData.serverError) {
// An error occurred -> show unknown error note
html = Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_SERVER_ERROR');
// force result into group 4 = Pre update checks failed
extensionData.compatibilityData = {
'resultGroup' : 4
}
}
else {
// Switch the compatibility state
switch (extensionData.compatibilityData.upgradeCompatibilityStatus.state) {
case PreUpdateChecker.STATE.COMPATIBLE:
if (extensionData.compatibilityData.upgradeWarning)
{
html = '<span class="label label-warning">' + extensionData.compatibilityData.upgradeCompatibilityStatus.compatibleVersion + '</span>';
PreUpdateChecker.showyellowwarning = true;
}
else {
html = extensionData.compatibilityData.upgradeCompatibilityStatus.compatibleVersion == false ? Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_NO_COMPATIBILITY_INFORMATION') : extensionData.compatibilityData.upgradeCompatibilityStatus.compatibleVersion;
}
break;
case PreUpdateChecker.STATE.INCOMPATIBLE:
// No compatible version found -> display error label
html = Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_NO_COMPATIBILITY_INFORMATION');
PreUpdateChecker.showorangewarning = true;
break;
case PreUpdateChecker.STATE.MISSING_COMPATIBILITY_TAG:
// Could not check compatibility state -> display warning
html = Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_NO_COMPATIBILITY_INFORMATION');
PreUpdateChecker.showorangewarning = true;
break;
default:
// An error occured -> show unknown error note
html = Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_WARNING_UNKNOWN');
}
}
// Insert the generated html
extensionData.$element.html(html);
// Process Current Version Extension Compatibility
html = '';
if (extensionData.serverError) {
// An error occured -> show unknown error note
html = Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_SERVER_ERROR');
}
else {
// Switch the compatibility state
switch (extensionData.compatibilityData.currentCompatibilityStatus.state) {
case PreUpdateChecker.STATE.COMPATIBLE:
html = extensionData.compatibilityData.currentCompatibilityStatus.compatibleVersion == false ? Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_NO_COMPATIBILITY_INFORMATION') : extensionData.compatibilityData.currentCompatibilityStatus.compatibleVersion;
break;
case PreUpdateChecker.STATE.INCOMPATIBLE:
// No compatible version found -> display error label
html = Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_NO_COMPATIBILITY_INFORMATION');
break;
case PreUpdateChecker.STATE.MISSING_COMPATIBILITY_TAG:
// Could not check compatibility state -> display warning
html = Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_NO_COMPATIBILITY_INFORMATION');
break;
default:
// An error occured -> show unknown error note
html = Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_WARNING_UNKNOWN');
}
}
// Insert the generated html
var extensionId = extensionData.$element.data('extensionId')
document.getElementById('available-version-' + extensionId ).innerHTML = html;
extensionData.$element.closest('tr').appendTo($('#compatibilitytype' + extensionData.compatibilityData.resultGroup + ' tbody'));
$('#compatibilitytype' + extensionData.compatibilityData.resultGroup).css('display', 'block');
document.getElementById('compatibilitytype0').style.display = 'block';
// Process the nonCoreCriticalPlugin list
if (extensionData.compatibilityData.resultGroup === 3)
{
var pluginInfo;
for (var i = PreUpdateChecker.nonCoreCriticalPlugins.length - 1; i >= 0; i--)
{
pluginInfo = PreUpdateChecker.nonCoreCriticalPlugins[i];
if (pluginInfo.package_id == extensionId || pluginInfo.extension_id == extensionId)
{
$('#plg_' + pluginInfo.extension_id).remove();
PreUpdateChecker.nonCoreCriticalPlugins.splice(i, 1);
}
}
}
// Have we finished running through the potentially critical plugins - if so we can hide the warning before all the checks are completed
if ($('#preupdatecheckheadings table td').length == 0) {
$('#preupdatecheckheadings').css('display', 'none');
}
// Have we finished?
if ($('#compatibilitytype0 tbody td').length == 0) {
$('#compatibilitytype0').css('display', 'none');
for (var cpi in PreUpdateChecker.nonCoreCriticalPlugins)
{
var problemPluginRow = $('td[data-extension-id=' + PreUpdateChecker.nonCoreCriticalPlugins[cpi].extension_id +']');
if (!problemPluginRow.length)
{
problemPluginRow = $('td[data-extension-id=' + PreUpdateChecker.nonCoreCriticalPlugins[cpi].package_id +']');
}
if (problemPluginRow.length)
{
var tableRow = problemPluginRow.closest('tr');
tableRow.addClass('error');
var pluginTitleTableCell = tableRow.find('td:first-child');
pluginTitleTableCell.html(pluginTitleTableCell.html()
+ '<span class="label label-warning " >'
+ '<span class="icon-warning"></span>'
+ Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_POTENTIALLY_DANGEROUS_PLUGIN')
+ '</span>'
+ '<span class="label label-info hasPopover" '
+ ' title="' + Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_POTENTIALLY_DANGEROUS_PLUGIN') +'"'
+ ' data-content="' + Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_POTENTIALLY_DANGEROUS_PLUGIN_DESC') +'"'
+ '>'
+ '<span class="icon-help"></span>'
+ Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_HELP')
+ '</span>'
);
var popoverElement = pluginTitleTableCell.find('.hasPopover');
popoverElement.css('cursor', 'pointer')
popoverElement.popover({"placement": "top","trigger": "focus click"});
}
}
if (PreUpdateChecker.nonCoreCriticalPlugins.length == 0)
{
$('#preupdateCheckWarning, #preupdateconfirmation, #preupdatecheckbox, #preupdatecheckheadings').css('display', 'none');
$('#preupdatecheckbox #noncoreplugins').prop('checked', true);
$('button.submitupdate').removeClass('disabled');
$('button.submitupdate').prop('disabled', false);
}
else {
$('#preupdateCheckWarning').addClass('hidden');
$('#preupdateCheckCompleteProblems').removeClass('hidden');
$('#preupdateconfirmation .preupdateconfirmation_label h3').html(Joomla.JText._('COM_JOOMLAUPDATE_VIEW_DEFAULT_POTENTIALLY_DANGEROUS_PLUGIN_LIST'))
$('#preupdateconfirmation .preupdateconfirmation_label').removeClass('label-warning').addClass('label-important')
}
}
}
// Run PreUpdateChecker on document ready
document.addEventListener( "DOMContentLoaded", function() {
$(PreUpdateChecker.run);
} );
})(jQuery, document, window);
js/default.min.js 0000644 00000020005 15234424455 0007731 0 ustar 00 function extractionMethodHandler(e,t){jQuery(function(a){$em=a(e),displayStyle="direct"===$em.val()?"none":"table-row",document.getElementById(t+"_notice").style.display=displayStyle,document.getElementById(t+"_hostname").style.display=displayStyle,document.getElementById(t+"_port").style.display=displayStyle,document.getElementById(t+"_username").style.display=displayStyle,document.getElementById(t+"_password").style.display=displayStyle,document.getElementById(t+"_directory").style.display=displayStyle})}!function(e,t,a){var i={};i.config={serverUrl:"index.php?option=com_joomlaupdate&task=update.fetchextensioncompatibility",selector:".extension-check"},i.STATE={INCOMPATIBLE:0,COMPATIBLE:1,MISSING_COMPATIBILITY_TAG:2,SERVER_ERROR:3},i.run=function(){e(".settingstoggle").css("float","right").css("cursor","pointer"),e(".settingstoggle").on("click",function(t,a){var i=e(this).closest("fieldset");"closed"==e(this).data("state")?(e(this).data("state","open"),e(this).html(COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_SHOW_LESS_COMPATIBILITY_INFORMATION),i.find(".settingsInfo").removeClass("hidden")):(e(this).data("state","closed"),e(this).html(COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_SHOW_MORE_COMPATIBILITY_INFORMATION),i.find(".settingsInfo").addClass("hidden"))}),i.nonCoreCriticalPlugins=Joomla.getOptions("nonCoreCriticalPlugins",[]),0===i.nonCoreCriticalPlugins.length&&(e("#preupdateCheckWarning, #preupdateconfirmation, #preupdatecheckbox, #preupdatecheckheadings").css("display","none"),e("#preupdatecheckbox #noncoreplugins").prop("checked",!0),e("button.submitupdate").removeClass("disabled"),e("button.submitupdate").prop("disabled",!1));var t=e(i.config.selector);0!==t.length&&(e("#preupdatecheckbox #noncoreplugins").on("change",function(){e("#preupdatecheckbox #noncoreplugins").is(":checked")?confirm(Joomla.JText._("COM_JOOMLAUPDATE_VIEW_DEFAULT_POTENTIALLY_DANGEROUS_PLUGIN_CONFIRM_MESSAGE"))?(e("button.submitupdate").removeClass("disabled"),e("button.submitupdate").prop("disabled",!1)):e("#preupdatecheckbox #noncoreplugins").prop("checked",!1):(e("button.submitupdate").addClass("disabled"),e("button.submitupdate").prop("disabled",!0))}),i.joomlaTargetVersion=a.joomlaTargetVersion,i.joomlaCurrentVersion=a.joomlaCurrentVersion,e(".compatibilitytypes img").css("height","20px"),e(".compatibilitytypes").css("display","none").css("margin-left",0),e("#compatibilitytype0").css("display","block"),e(".compatibilitytoggle").css("float","right").css("cursor","pointer"),e(".compatibilitytoggle").on("click",function(t,a){var s=e(this).closest("fieldset.compatibilitytypes");"closed"==e(this).data("state")?(e(this).data("state","open"),e(this).html(COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_SHOW_LESS_COMPATIBILITY_INFORMATION),s.find(".exname").removeClass("span8").addClass("span4"),s.find(".extype").removeClass("span4").addClass("span1"),s.find(".upcomp").removeClass("hidden").addClass("span3"),s.find(".currcomp").removeClass("hidden").addClass("span3"),s.find(".instver").removeClass("hidden").addClass("span1"),i.showyellowwarning&&s.find("#updateyellowwarning").removeClass("hidden"),i.showorangewarning&&s.find("#updateorangewarning").removeClass("hidden")):(e(this).data("state","closed"),e(this).html(COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_SHOW_MORE_COMPATIBILITY_INFORMATION),s.find(".exname").addClass("span8").removeClass("span4"),s.find(".extype").addClass("span4").removeClass("span1"),s.find(".upcomp").addClass("hidden").removeClass("span3"),s.find(".currcomp").addClass("hidden").removeClass("span3"),s.find(".instver").addClass("hidden").removeClass("span1"),s.find("#updateyellowwarning").addClass("hidden"),s.find("#updateorangewarning").addClass("hidden"))}),t.each(function(){i.checkCompatibility(e(this),i.setResultView)}))},i.checkCompatibility=function(t,a){var s={$element:t,compatibleVersion:0,serverError:1};e.getJSON(i.config.serverUrl,{"joomla-target-version":i.joomlaTargetVersion,"joomla-current-version":i.joomlaCurrentVersion,"extension-version":t.data("extension-current-version"),"extension-id":t.data("extensionId")}).done(function(e){s.serverError=0,s.compatibilityData=e.data}).always(function(e){a(s)})},i.setResultView=function(a){var s="";if(a.serverError)s=Joomla.JText._("COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_SERVER_ERROR"),a.compatibilityData={resultGroup:4};else switch(a.compatibilityData.upgradeCompatibilityStatus.state){case i.STATE.COMPATIBLE:a.compatibilityData.upgradeWarning?(s='<span class="label label-warning">'+a.compatibilityData.upgradeCompatibilityStatus.compatibleVersion+"</span>",i.showyellowwarning=!0):s=0==a.compatibilityData.upgradeCompatibilityStatus.compatibleVersion?Joomla.JText._("COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_NO_COMPATIBILITY_INFORMATION"):a.compatibilityData.upgradeCompatibilityStatus.compatibleVersion;break;case i.STATE.INCOMPATIBLE:case i.STATE.MISSING_COMPATIBILITY_TAG:s=Joomla.JText._("COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_NO_COMPATIBILITY_INFORMATION"),i.showorangewarning=!0;break;default:s=Joomla.JText._("COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_WARNING_UNKNOWN")}if(a.$element.html(s),s="",a.serverError)s=Joomla.JText._("COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_SERVER_ERROR");else switch(a.compatibilityData.currentCompatibilityStatus.state){case i.STATE.COMPATIBLE:s=0==a.compatibilityData.currentCompatibilityStatus.compatibleVersion?Joomla.JText._("COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_NO_COMPATIBILITY_INFORMATION"):a.compatibilityData.currentCompatibilityStatus.compatibleVersion;break;case i.STATE.INCOMPATIBLE:case i.STATE.MISSING_COMPATIBILITY_TAG:s=Joomla.JText._("COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_NO_COMPATIBILITY_INFORMATION");break;default:s=Joomla.JText._("COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_WARNING_UNKNOWN")}var n=a.$element.data("extensionId");if(t.getElementById("available-version-"+n).innerHTML=s,a.$element.closest("tr").appendTo(e("#compatibilitytype"+a.compatibilityData.resultGroup+" tbody")),e("#compatibilitytype"+a.compatibilityData.resultGroup).css("display","block"),t.getElementById("compatibilitytype0").style.display="block",3===a.compatibilityData.resultGroup)for(var o,l=i.nonCoreCriticalPlugins.length-1;l>=0;l--)(o=i.nonCoreCriticalPlugins[l]).package_id!=n&&o.extension_id!=n||(e("#plg_"+o.extension_id).remove(),i.nonCoreCriticalPlugins.splice(l,1));if(0==e("#preupdatecheckheadings table td").length&&e("#preupdatecheckheadings").css("display","none"),0==e("#compatibilitytype0 tbody td").length){for(var r in e("#compatibilitytype0").css("display","none"),i.nonCoreCriticalPlugins){var d=e("td[data-extension-id="+i.nonCoreCriticalPlugins[r].extension_id+"]");if(d.length||(d=e("td[data-extension-id="+i.nonCoreCriticalPlugins[r].package_id+"]")),d.length){var p=d.closest("tr");p.addClass("error");var c=p.find("td:first-child");c.html(c.html()+'<span class="label label-warning " ><span class="icon-warning"></span>'+Joomla.JText._("COM_JOOMLAUPDATE_VIEW_DEFAULT_POTENTIALLY_DANGEROUS_PLUGIN")+'</span><span class="label label-info hasPopover" title="'+Joomla.JText._("COM_JOOMLAUPDATE_VIEW_DEFAULT_POTENTIALLY_DANGEROUS_PLUGIN")+'" data-content="'+Joomla.JText._("COM_JOOMLAUPDATE_VIEW_DEFAULT_POTENTIALLY_DANGEROUS_PLUGIN_DESC")+'"><span class="icon-help"></span>'+Joomla.JText._("COM_JOOMLAUPDATE_VIEW_DEFAULT_HELP")+"</span>");var _=c.find(".hasPopover");_.css("cursor","pointer"),_.popover({placement:"top",trigger:"focus click"})}}0==i.nonCoreCriticalPlugins.length?(e("#preupdateCheckWarning, #preupdateconfirmation, #preupdatecheckbox, #preupdatecheckheadings").css("display","none"),e("#preupdatecheckbox #noncoreplugins").prop("checked",!0),e("button.submitupdate").removeClass("disabled"),e("button.submitupdate").prop("disabled",!1)):(e("#preupdateCheckWarning").addClass("hidden"),e("#preupdateCheckCompleteProblems").removeClass("hidden"),e("#preupdateconfirmation .preupdateconfirmation_label h3").html(Joomla.JText._("COM_JOOMLAUPDATE_VIEW_DEFAULT_POTENTIALLY_DANGEROUS_PLUGIN_LIST")),e("#preupdateconfirmation .preupdateconfirmation_label").removeClass("label-warning").addClass("label-important"))}},t.addEventListener("DOMContentLoaded",function(){e(i.run)})}(jQuery,document,window);
js/encryption.js 0000644 00000046064 15234424455 0007732 0 ustar 00 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* AES implementation in JavaScript (c) Chris Veness 2005-2010 */
/* - see http://csrc.nist.gov/publications/PubsFIPS.html#197 */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var Aes = {}; // Aes namespace
/**
* AES Cipher function: encrypt 'input' state with Rijndael algorithm
* applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage
*
* @param {Number[]} input 16-byte (128-bit) input state array
* @param {Number[][]} w Key schedule as 2D byte-array (Nr+1 x Nb bytes)
* @returns {Number[]} Encrypted output state array
*/
Aes.Cipher = function(input, w) { // main Cipher function [§5.1]
var Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
var Nr = w.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys
var state = [[],[],[],[]]; // initialise 4xNb byte-array 'state' with input [§3.4]
for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i];
state = Aes.AddRoundKey(state, w, 0, Nb);
for (var round=1; round<Nr; round++) {
state = Aes.SubBytes(state, Nb);
state = Aes.ShiftRows(state, Nb);
state = Aes.MixColumns(state, Nb);
state = Aes.AddRoundKey(state, w, round, Nb);
}
state = Aes.SubBytes(state, Nb);
state = Aes.ShiftRows(state, Nb);
state = Aes.AddRoundKey(state, w, Nr, Nb);
var output = new Array(4*Nb); // convert state to 1-d array before returning [§3.4]
for (var i=0; i<4*Nb; i++) output[i] = state[i%4][Math.floor(i/4)];
return output;
}
/**
* Perform Key Expansion to generate a Key Schedule
*
* @param {Number[]} key Key as 16/24/32-byte array
* @returns {Number[][]} Expanded key schedule as 2D byte-array (Nr+1 x Nb bytes)
*/
Aes.KeyExpansion = function(key) { // generate Key Schedule (byte-array Nr+1 x Nb) from Key [§5.2]
var Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
var Nk = key.length/4 // key length (in words): 4/6/8 for 128/192/256-bit keys
var Nr = Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys
var w = new Array(Nb*(Nr+1));
var temp = new Array(4);
for (var i=0; i<Nk; i++) {
var r = [key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]];
w[i] = r;
}
for (var i=Nk; i<(Nb*(Nr+1)); i++) {
w[i] = new Array(4);
for (var t=0; t<4; t++) temp[t] = w[i-1][t];
if (i % Nk == 0) {
temp = Aes.SubWord(Aes.RotWord(temp));
for (var t=0; t<4; t++) temp[t] ^= Aes.Rcon[i/Nk][t];
} else if (Nk > 6 && i%Nk == 4) {
temp = Aes.SubWord(temp);
}
for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t];
}
return w;
}
/*
* ---- remaining routines are private, not called externally ----
*/
Aes.SubBytes = function(s, Nb) { // apply SBox to state S [§5.1.1]
for (var r=0; r<4; r++) {
for (var c=0; c<Nb; c++) s[r][c] = Aes.Sbox[s[r][c]];
}
return s;
}
Aes.ShiftRows = function(s, Nb) { // shift row r of state S left by r bytes [§5.1.2]
var t = new Array(4);
for (var r=1; r<4; r++) {
for (var c=0; c<4; c++) t[c] = s[r][(c+r)%Nb]; // shift into temp copy
for (var c=0; c<4; c++) s[r][c] = t[c]; // and copy back
} // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
return s; // see asmaes.sourceforge.net/rijndael/rijndaelImplementation.pdf
}
Aes.MixColumns = function(s, Nb) { // combine bytes of each col of state S [§5.1.3]
for (var c=0; c<4; c++) {
var a = new Array(4); // 'a' is a copy of the current column from 's'
var b = new Array(4); // 'b' is a•{02} in GF(2^8)
for (var i=0; i<4; i++) {
a[i] = s[i][c];
b[i] = s[i][c]&0x80 ? s[i][c]<<1 ^ 0x011b : s[i][c]<<1;
}
// a[n] ^ b[n] is a•{03} in GF(2^8)
s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3
s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3
}
return s;
}
Aes.AddRoundKey = function(state, w, rnd, Nb) { // xor Round Key into state S [§5.1.4]
for (var r=0; r<4; r++) {
for (var c=0; c<Nb; c++) state[r][c] ^= w[rnd*4+c][r];
}
return state;
}
Aes.SubWord = function(w) { // apply SBox to 4-byte word w
for (var i=0; i<4; i++) w[i] = Aes.Sbox[w[i]];
return w;
}
Aes.RotWord = function(w) { // rotate 4-byte word w left by one byte
var tmp = w[0];
for (var i=0; i<3; i++) w[i] = w[i+1];
w[3] = tmp;
return w;
}
// Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [§5.1.1]
Aes.Sbox = [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16];
// Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2]
Aes.Rcon = [ [0x00, 0x00, 0x00, 0x00],
[0x01, 0x00, 0x00, 0x00],
[0x02, 0x00, 0x00, 0x00],
[0x04, 0x00, 0x00, 0x00],
[0x08, 0x00, 0x00, 0x00],
[0x10, 0x00, 0x00, 0x00],
[0x20, 0x00, 0x00, 0x00],
[0x40, 0x00, 0x00, 0x00],
[0x80, 0x00, 0x00, 0x00],
[0x1b, 0x00, 0x00, 0x00],
[0x36, 0x00, 0x00, 0x00] ];
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* AES Counter-mode implementation in JavaScript (c) Chris Veness 2005-2010 */
/* - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var AesCtr = {}; // AesCtr namespace
/**
* Encrypt a text using AES encryption in Counter mode of operation
*
* Unicode multi-byte character safe
*
* @param {String} plaintext Source text to be encrypted
* @param {String} password The password to use to generate a key
* @param {Number} nBits Number of bits to be used in the key (128, 192, or 256)
* @returns {string} Encrypted text
*/
AesCtr.encrypt = function(plaintext, password, nBits) {
var blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys
plaintext = Utf8.encode(plaintext);
password = Utf8.encode(password);
//var t = new Date(); // timer
// use AES itself to encrypt password to get cipher key (using plain password as source for key
// expansion) - gives us well encrypted key
var nBytes = nBits/8; // no bytes in key
var pwBytes = new Array(nBytes);
for (var i=0; i<nBytes; i++) {
pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i);
}
var key = Aes.Cipher(pwBytes, Aes.KeyExpansion(pwBytes)); // gives us 16-byte key
key = key.concat(key.slice(0, nBytes-16)); // expand key to 16/24/32 bytes long
// initialise counter block (NIST SP800-38A §B.2): millisecond time-stamp for nonce in 1st 8 bytes,
// block counter in 2nd 8 bytes
var counterBlock = new Array(blockSize);
var nonce = (new Date()).getTime(); // timestamp: milliseconds since 1-Jan-1970
var nonceSec = Math.floor(nonce/1000);
var nonceMs = nonce%1000;
// encode nonce with seconds in 1st 4 bytes, and (repeated) ms part filling 2nd 4 bytes
for (var i=0; i<4; i++) counterBlock[i] = (nonceSec >>> i*8) & 0xff;
for (var i=0; i<4; i++) counterBlock[i+4] = nonceMs & 0xff;
// and convert it to a string to go on the front of the ciphertext
var ctrTxt = '';
for (var i=0; i<8; i++) ctrTxt += String.fromCharCode(counterBlock[i]);
// generate key schedule - an expansion of the key into distinct Key Rounds for each round
var keySchedule = Aes.KeyExpansion(key);
var blockCount = Math.ceil(plaintext.length/blockSize);
var ciphertxt = new Array(blockCount); // ciphertext as array of strings
for (var b=0; b<blockCount; b++) {
// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
// done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
for (var c=0; c<4; c++) counterBlock[15-c] = (b >>> c*8) & 0xff;
for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8)
var cipherCntr = Aes.Cipher(counterBlock, keySchedule); // -- encrypt counter block --
// block size is reduced on final block
var blockLength = b<blockCount-1 ? blockSize : (plaintext.length-1)%blockSize+1;
var cipherChar = new Array(blockLength);
for (var i=0; i<blockLength; i++) { // -- xor plaintext with ciphered counter char-by-char --
cipherChar[i] = cipherCntr[i] ^ plaintext.charCodeAt(b*blockSize+i);
cipherChar[i] = String.fromCharCode(cipherChar[i]);
}
ciphertxt[b] = cipherChar.join('');
}
// Array.join is more efficient than repeated string concatenation in IE
var ciphertext = ctrTxt + ciphertxt.join('');
ciphertext = Base64.encode(ciphertext); // encode in base64
//alert((new Date()) - t);
return ciphertext;
}
/**
* Decrypt a text encrypted by AES in counter mode of operation
*
* @param {String} ciphertext Source text to be encrypted
* @param {String} password The password to use to generate a key
* @param {Number} nBits Number of bits to be used in the key (128, 192, or 256)
* @returns {String} Decrypted text
*/
AesCtr.decrypt = function(ciphertext, password, nBits) {
var blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys
ciphertext = Base64.decode(ciphertext);
password = Utf8.encode(password);
//var t = new Date(); // timer
// use AES to encrypt password (mirroring encrypt routine)
var nBytes = nBits/8; // no bytes in key
var pwBytes = new Array(nBytes);
for (var i=0; i<nBytes; i++) {
pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i);
}
var key = Aes.Cipher(pwBytes, Aes.KeyExpansion(pwBytes));
key = key.concat(key.slice(0, nBytes-16)); // expand key to 16/24/32 bytes long
// recover nonce from 1st 8 bytes of ciphertext
var counterBlock = new Array(8);
ctrTxt = ciphertext.slice(0, 8);
for (var i=0; i<8; i++) counterBlock[i] = ctrTxt.charCodeAt(i);
// generate key schedule
var keySchedule = Aes.KeyExpansion(key);
// separate ciphertext into blocks (skipping past initial 8 bytes)
var nBlocks = Math.ceil((ciphertext.length-8) / blockSize);
var ct = new Array(nBlocks);
for (var b=0; b<nBlocks; b++) ct[b] = ciphertext.slice(8+b*blockSize, 8+b*blockSize+blockSize);
ciphertext = ct; // ciphertext is now array of block-length strings
// plaintext will get generated block-by-block into array of block-length strings
var plaintxt = new Array(ciphertext.length);
for (var b=0; b<nBlocks; b++) {
// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
for (var c=0; c<4; c++) counterBlock[15-c] = ((b) >>> c*8) & 0xff;
for (var c=0; c<4; c++) counterBlock[15-c-4] = (((b+1)/0x100000000-1) >>> c*8) & 0xff;
var cipherCntr = Aes.Cipher(counterBlock, keySchedule); // encrypt counter block
var plaintxtByte = new Array(ciphertext[b].length);
for (var i=0; i<ciphertext[b].length; i++) {
// -- xor plaintxt with ciphered counter byte-by-byte --
plaintxtByte[i] = cipherCntr[i] ^ ciphertext[b].charCodeAt(i);
plaintxtByte[i] = String.fromCharCode(plaintxtByte[i]);
}
plaintxt[b] = plaintxtByte.join('');
}
// join array of blocks into single plaintext string
var plaintext = plaintxt.join('');
plaintext = Utf8.decode(plaintext); // decode from UTF8 back to Unicode multi-byte chars
//alert((new Date()) - t);
return plaintext;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Base64 class: Base 64 encoding / decoding (c) Chris Veness 2002-2010 */
/* note: depends on Utf8 class */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var Base64 = {}; // Base64 namespace
Base64.code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
/**
* Encode string into Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648]
* (instance method extending String object). As per RFC 4648, no newlines are added.
*
* @param {String} str The string to be encoded as base-64
* @param {Boolean} [utf8encode=false] Flag to indicate whether str is Unicode string to be encoded
* to UTF8 before conversion to base64; otherwise string is assumed to be 8-bit characters
* @returns {String} Base64-encoded string
*/
Base64.encode = function(str, utf8encode) { // http://tools.ietf.org/html/rfc4648
utf8encode = (typeof utf8encode == 'undefined') ? false : utf8encode;
var o1, o2, o3, bits, h1, h2, h3, h4, e=[], pad = '', c, plain, coded;
var b64 = Base64.code;
plain = utf8encode ? str.encodeUTF8() : str;
c = plain.length % 3; // pad string to length of multiple of 3
if (c > 0) { while (c++ < 3) { pad += '='; plain += '\0'; } }
// note: doing padding here saves us doing special-case packing for trailing 1 or 2 chars
for (c=0; c<plain.length; c+=3) { // pack three octets into four hexets
o1 = plain.charCodeAt(c);
o2 = plain.charCodeAt(c+1);
o3 = plain.charCodeAt(c+2);
bits = o1<<16 | o2<<8 | o3;
h1 = bits>>18 & 0x3f;
h2 = bits>>12 & 0x3f;
h3 = bits>>6 & 0x3f;
h4 = bits & 0x3f;
// use hextets to index into code string
e[c/3] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
}
coded = e.join(''); // join() is far faster than repeated string concatenation in IE
// replace 'A's from padded nulls with '='s
coded = coded.slice(0, coded.length-pad.length) + pad;
return coded;
}
/**
* Decode string from Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648]
* (instance method extending String object). As per RFC 4648, newlines are not catered for.
*
* @param {String} str The string to be decoded from base-64
* @param {Boolean} [utf8decode=false] Flag to indicate whether str is Unicode string to be decoded
* from UTF8 after conversion from base64
* @returns {String} decoded string
*/
Base64.decode = function(str, utf8decode) {
utf8decode = (typeof utf8decode == 'undefined') ? false : utf8decode;
var o1, o2, o3, h1, h2, h3, h4, bits, d=[], plain, coded;
var b64 = Base64.code;
coded = utf8decode ? str.decodeUTF8() : str;
for (var c=0; c<coded.length; c+=4) { // unpack four hexets into three octets
h1 = b64.indexOf(coded.charAt(c));
h2 = b64.indexOf(coded.charAt(c+1));
h3 = b64.indexOf(coded.charAt(c+2));
h4 = b64.indexOf(coded.charAt(c+3));
bits = h1<<18 | h2<<12 | h3<<6 | h4;
o1 = bits>>>16 & 0xff;
o2 = bits>>>8 & 0xff;
o3 = bits & 0xff;
d[c/4] = String.fromCharCode(o1, o2, o3);
// check for padding
if (h4 == 0x40) d[c/4] = String.fromCharCode(o1, o2);
if (h3 == 0x40) d[c/4] = String.fromCharCode(o1);
}
plain = d.join(''); // join() is far faster than repeated string concatenation in IE
return utf8decode ? plain.decodeUTF8() : plain;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Utf8 class: encode / decode between multi-byte Unicode characters and UTF-8 multiple */
/* single-byte character encoding (c) Chris Veness 2002-2010 */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var Utf8 = {}; // Utf8 namespace
/**
* Encode multi-byte Unicode string into utf-8 multiple single-byte characters
* (BMP / basic multilingual plane only)
*
* Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars
*
* @param {String} strUni Unicode string to be encoded as UTF-8
* @returns {String} encoded string
*/
Utf8.encode = function(strUni) {
// use regular expressions & String.replace callback function for better efficiency
// than procedural approaches
var strUtf = strUni.replace(
/[\u0080-\u07ff]/g, // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz
function(c) {
var cc = c.charCodeAt(0);
return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f); }
);
strUtf = strUtf.replace(
/[\u0800-\uffff]/g, // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz
function(c) {
var cc = c.charCodeAt(0);
return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f); }
);
return strUtf;
}
/**
* Decode utf-8 encoded string back into multi-byte Unicode characters
*
* @param {String} strUtf UTF-8 string to be decoded back to Unicode
* @returns {String} decoded string
*/
Utf8.decode = function(strUtf) {
var strUni = strUtf.replace(
/[\u00c0-\u00df][\u0080-\u00bf]/g, // 2-byte chars
function(c) { // (note parentheses for precence)
var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f;
return String.fromCharCode(cc); }
);
strUni = strUni.replace(
/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, // 3-byte chars
function(c) { // (note parentheses for precence)
var cc = ((c.charCodeAt(0)&0x0f)<<12) | ((c.charCodeAt(1)&0x3f)<<6) | ( c.charCodeAt(2)&0x3f);
return String.fromCharCode(cc); }
);
return strUni;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ js/encryption.min.js 0000644 00000013464 15234424455 0010512 0 ustar 00 var Aes={};Aes.Cipher=function(r,e){for(var o=4,n=e.length/o-1,a=[[],[],[],[]],t=0;4*o>t;t++)a[t%4][Math.floor(t/4)]=r[t];a=Aes.AddRoundKey(a,e,0,o);for(var f=1;n>f;f++)a=Aes.SubBytes(a,o),a=Aes.ShiftRows(a,o),a=Aes.MixColumns(a,o),a=Aes.AddRoundKey(a,e,f,o);a=Aes.SubBytes(a,o),a=Aes.ShiftRows(a,o),a=Aes.AddRoundKey(a,e,n,o);for(var c=new Array(4*o),t=0;4*o>t;t++)c[t]=a[t%4][Math.floor(t/4)];return c},Aes.KeyExpansion=function(r){for(var e=4,o=r.length/4,n=o+6,a=new Array(e*(n+1)),t=new Array(4),f=0;o>f;f++){var c=[r[4*f],r[4*f+1],r[4*f+2],r[4*f+3]];a[f]=c}for(var f=o;e*(n+1)>f;f++){a[f]=new Array(4);for(var A=0;4>A;A++)t[A]=a[f-1][A];if(f%o==0){t=Aes.SubWord(Aes.RotWord(t));for(var A=0;4>A;A++)t[A]^=Aes.Rcon[f/o][A]}else o>6&&f%o==4&&(t=Aes.SubWord(t));for(var A=0;4>A;A++)a[f][A]=a[f-o][A]^t[A]}return a},Aes.SubBytes=function(r,e){for(var o=0;4>o;o++)for(var n=0;e>n;n++)r[o][n]=Aes.Sbox[r[o][n]];return r},Aes.ShiftRows=function(r,e){for(var o=new Array(4),n=1;4>n;n++){for(var a=0;4>a;a++)o[a]=r[n][(a+n)%e];for(var a=0;4>a;a++)r[n][a]=o[a]}return r},Aes.MixColumns=function(r,e){for(var o=0;4>o;o++){for(var n=new Array(4),a=new Array(4),t=0;4>t;t++)n[t]=r[t][o],a[t]=128&r[t][o]?r[t][o]<<1^283:r[t][o]<<1;r[0][o]=a[0]^n[1]^a[1]^n[2]^n[3],r[1][o]=n[0]^a[1]^n[2]^a[2]^n[3],r[2][o]=n[0]^n[1]^a[2]^n[3]^a[3],r[3][o]=n[0]^a[0]^n[1]^n[2]^a[3]}return r},Aes.AddRoundKey=function(r,e,o,n){for(var a=0;4>a;a++)for(var t=0;n>t;t++)r[a][t]^=e[4*o+t][a];return r},Aes.SubWord=function(r){for(var e=0;4>e;e++)r[e]=Aes.Sbox[r[e]];return r},Aes.RotWord=function(r){for(var e=r[0],o=0;3>o;o++)r[o]=r[o+1];return r[3]=e,r},Aes.Sbox=[99,124,119,123,242,107,111,197,48,1,103,43,254,215,171,118,202,130,201,125,250,89,71,240,173,212,162,175,156,164,114,192,183,253,147,38,54,63,247,204,52,165,229,241,113,216,49,21,4,199,35,195,24,150,5,154,7,18,128,226,235,39,178,117,9,131,44,26,27,110,90,160,82,59,214,179,41,227,47,132,83,209,0,237,32,252,177,91,106,203,190,57,74,76,88,207,208,239,170,251,67,77,51,133,69,249,2,127,80,60,159,168,81,163,64,143,146,157,56,245,188,182,218,33,16,255,243,210,205,12,19,236,95,151,68,23,196,167,126,61,100,93,25,115,96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,219,224,50,58,10,73,6,36,92,194,211,172,98,145,149,228,121,231,200,55,109,141,213,78,169,108,86,244,234,101,122,174,8,186,120,37,46,28,166,180,198,232,221,116,31,75,189,139,138,112,62,181,102,72,3,246,14,97,53,87,185,134,193,29,158,225,248,152,17,105,217,142,148,155,30,135,233,206,85,40,223,140,161,137,13,191,230,66,104,65,153,45,15,176,84,187,22],Aes.Rcon=[[0,0,0,0],[1,0,0,0],[2,0,0,0],[4,0,0,0],[8,0,0,0],[16,0,0,0],[32,0,0,0],[64,0,0,0],[128,0,0,0],[27,0,0,0],[54,0,0,0]];var AesCtr={};AesCtr.encrypt=function(r,e,o){var n=16;if(128!=o&&192!=o&&256!=o)return"";r=Utf8.encode(r),e=Utf8.encode(e);for(var a=o/8,t=new Array(a),f=0;a>f;f++)t[f]=isNaN(e.charCodeAt(f))?0:e.charCodeAt(f);var c=Aes.Cipher(t,Aes.KeyExpansion(t));c=c.concat(c.slice(0,a-16));for(var A=new Array(n),d=(new Date).getTime(),i=Math.floor(d/1e3),u=d%1e3,f=0;4>f;f++)A[f]=i>>>8*f&255;for(var f=0;4>f;f++)A[f+4]=255&u;for(var s="",f=0;8>f;f++)s+=String.fromCharCode(A[f]);for(var h=Aes.KeyExpansion(c),v=Math.ceil(r.length/n),C=new Array(v),y=0;v>y;y++){for(var l=0;4>l;l++)A[15-l]=y>>>8*l&255;for(var l=0;4>l;l++)A[15-l-4]=y/4294967296>>>8*l;for(var g=Aes.Cipher(A,h),S=v-1>y?n:(r.length-1)%n+1,w=new Array(S),f=0;S>f;f++)w[f]=g[f]^r.charCodeAt(y*n+f),w[f]=String.fromCharCode(w[f]);C[y]=w.join("")}var p=s+C.join("");return p=Base64.encode(p)},AesCtr.decrypt=function(r,e,o){var n=16;if(128!=o&&192!=o&&256!=o)return"";r=Base64.decode(r),e=Utf8.encode(e);for(var a=o/8,t=new Array(a),f=0;a>f;f++)t[f]=isNaN(e.charCodeAt(f))?0:e.charCodeAt(f);var c=Aes.Cipher(t,Aes.KeyExpansion(t));c=c.concat(c.slice(0,a-16));var A=new Array(8);ctrTxt=r.slice(0,8);for(var f=0;8>f;f++)A[f]=ctrTxt.charCodeAt(f);for(var d=Aes.KeyExpansion(c),i=Math.ceil((r.length-8)/n),u=new Array(i),s=0;i>s;s++)u[s]=r.slice(8+s*n,8+s*n+n);r=u;for(var h=new Array(r.length),s=0;i>s;s++){for(var v=0;4>v;v++)A[15-v]=s>>>8*v&255;for(var v=0;4>v;v++)A[15-v-4]=(s+1)/4294967296-1>>>8*v&255;for(var C=Aes.Cipher(A,d),y=new Array(r[s].length),f=0;f<r[s].length;f++)y[f]=C[f]^r[s].charCodeAt(f),y[f]=String.fromCharCode(y[f]);h[s]=y.join("")}var l=h.join("");return l=Utf8.decode(l)};var Base64={};Base64.code="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",Base64.encode=function(r,e){e="undefined"==typeof e?!1:e;var o,n,a,t,f,c,A,d,i,u,s,h=[],v="",C=Base64.code;if(u=e?r.encodeUTF8():r,i=u.length%3,i>0)for(;i++<3;)v+="=",u+="\x00";for(i=0;i<u.length;i+=3)o=u.charCodeAt(i),n=u.charCodeAt(i+1),a=u.charCodeAt(i+2),t=o<<16|n<<8|a,f=t>>18&63,c=t>>12&63,A=t>>6&63,d=63&t,h[i/3]=C.charAt(f)+C.charAt(c)+C.charAt(A)+C.charAt(d);return s=h.join(""),s=s.slice(0,s.length-v.length)+v},Base64.decode=function(r,e){e="undefined"==typeof e?!1:e;var o,n,a,t,f,c,A,d,i,u,s=[],h=Base64.code;u=e?r.decodeUTF8():r;for(var v=0;v<u.length;v+=4)t=h.indexOf(u.charAt(v)),f=h.indexOf(u.charAt(v+1)),c=h.indexOf(u.charAt(v+2)),A=h.indexOf(u.charAt(v+3)),d=t<<18|f<<12|c<<6|A,o=d>>>16&255,n=d>>>8&255,a=255&d,s[v/4]=String.fromCharCode(o,n,a),64==A&&(s[v/4]=String.fromCharCode(o,n)),64==c&&(s[v/4]=String.fromCharCode(o));return i=s.join(""),e?i.decodeUTF8():i};var Utf8={};Utf8.encode=function(r){var e=r.replace(/[\u0080-\u07ff]/g,function(r){var e=r.charCodeAt(0);return String.fromCharCode(192|e>>6,128|63&e)});return e=e.replace(/[\u0800-\uffff]/g,function(r){var e=r.charCodeAt(0);return String.fromCharCode(224|e>>12,128|e>>6&63,128|63&e)})},Utf8.decode=function(r){var e=r.replace(/[\u00c0-\u00df][\u0080-\u00bf]/g,function(r){var e=(31&r.charCodeAt(0))<<6|63&r.charCodeAt(1);return String.fromCharCode(e)});return e=e.replace(/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g,function(r){var e=(15&r.charCodeAt(0))<<12|(63&r.charCodeAt(1))<<6|63&r.charCodeAt(2);return String.fromCharCode(e)})};
js/json2.js 0000644 00000006465 15234424455 0006574 0 ustar 00 /*
* json2.js
* 2014-02-04
* Public Domain.
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
* See http://www.JSON.org/js.html
*/
if(typeof JSON!=='object'){JSON={}}(function(){'use strict';function f(n){return n<10?'0'+n:n}if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+f(this.getUTCMonth()+1)+'-'+f(this.getUTCDate())+'T'+f(this.getUTCHours())+':'+f(this.getUTCMinutes())+':'+f(this.getUTCSeconds())+'Z':null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()}}var e,escapable,gap,indent,meta,rep;function quote(b){escapable.lastIndex=0;return escapable.test(b)?'"'+b.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+b+'"'}function str(a,b){var i,k,v,length,mind=gap,partial,value=b[a];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(a)}if(typeof rep==='function'){value=rep.call(b,a,value)}switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null'}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null'}v=partial.length===0?'[]':gap?'[\n'+gap+partial.join(',\n'+gap)+'\n'+mind+']':'['+partial.join(',')+']';gap=mind;return v}if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){if(typeof rep[i]==='string'){k=rep[i];v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v)}}}}else{for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v)}}}}v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+mind+'}':'{'+partial.join(',')+'}';gap=mind;return v}}if(typeof JSON.stringify!=='function'){escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};JSON.stringify=function(a,b,c){var i;gap='';indent='';if(typeof c==='number'){for(i=0;i<c;i+=1){indent+=' '}}else if(typeof c==='string'){indent=c}rep=b;if(b&&typeof b!=='function'&&(typeof b!=='object'||typeof b.length!=='number')){throw new Error('JSON.stringify');}return str('',{'':a})}}if(typeof JSON.parse!=='function'){e=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;JSON.parse=function(c,d){var j;function walk(a,b){var k,v,value=a[b];if(value&&typeof value==='object'){for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return d.call(a,b,value)}c=String(c);e.lastIndex=0;if(e.test(c)){c=c.replace(e,function(a){return'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(c.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+c+')');return typeof d==='function'?walk({'':j},''):j}throw new SyntaxError('JSON.parse');}}}());
js/json2.min.js 0000644 00000005562 15234424455 0007353 0 ustar 00 "object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(t){return 10>t?"0"+t:t}function quote(t){return escapable.lastIndex=0,escapable.test(t)?'"'+t.replace(escapable,function(t){var e=meta[t];return"string"==typeof e?e:"\\u"+("0000"+t.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+t+'"'}function str(t,e){var n,r,o,f,u,p=gap,a=e[t];switch(a&&"object"==typeof a&&"function"==typeof a.toJSON&&(a=a.toJSON(t)),"function"==typeof rep&&(a=rep.call(e,t,a)),typeof a){case"string":return quote(a);case"number":return isFinite(a)?String(a):"null";case"boolean":case"null":return String(a);case"object":if(!a)return"null";if(gap+=indent,u=[],"[object Array]"===Object.prototype.toString.apply(a)){for(f=a.length,n=0;f>n;n+=1)u[n]=str(n,a)||"null";return o=0===u.length?"[]":gap?"[\n"+gap+u.join(",\n"+gap)+"\n"+p+"]":"["+u.join(",")+"]",gap=p,o}if(rep&&"object"==typeof rep)for(f=rep.length,n=0;f>n;n+=1)"string"==typeof rep[n]&&(r=rep[n],o=str(r,a),o&&u.push(quote(r)+(gap?": ":":")+o));else for(r in a)Object.prototype.hasOwnProperty.call(a,r)&&(o=str(r,a),o&&u.push(quote(r)+(gap?": ":":")+o));return o=0===u.length?"{}":gap?"{\n"+gap+u.join(",\n"+gap)+"\n"+p+"}":"{"+u.join(",")+"}",gap=p,o}}"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()});var e,escapable,gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(t,e,n){var r;if(gap="",indent="","number"==typeof n)for(r=0;n>r;r+=1)indent+=" ";else"string"==typeof n&&(indent=n);if(rep=e,e&&"function"!=typeof e&&("object"!=typeof e||"number"!=typeof e.length))throw new Error("JSON.stringify");return str("",{"":t})}),"function"!=typeof JSON.parse&&(e=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,JSON.parse=function(c,d){function walk(t,e){var n,r,o=t[e];if(o&&"object"==typeof o)for(n in o)Object.prototype.hasOwnProperty.call(o,n)&&(r=walk(o,n),void 0!==r?o[n]=r:delete o[n]);return d.call(t,e,o)}var j;if(c=String(c),e.lastIndex=0,e.test(c)&&(c=c.replace(e,function(t){return"\\u"+("0000"+t.charCodeAt(0).toString(16)).slice(-4)})),/^[\],:{}\s]*$/.test(c.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+c+")"),"function"==typeof d?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}();
js/update.js 0000644 00000020234 15234424455 0007011 0 ustar 00 /**
* @package AkeebaCMSUpdate
* @copyright Copyright (c)2010-2014 Nicholas K. Dionysopoulos
* @license GNU General Public License version 3, or later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
var stat_total = 0;
var stat_files = 0;
var stat_inbytes = 0;
var stat_outbytes = 0;
/**
* An extremely simple error handler, dumping error messages to screen
*
* @param error The error message string
*/
function error_callback(error)
{
alert("ERROR:\n"+error);
}
/**
* Performs an encrypted AJAX request and returns the parsed JSON output.
* The window.ajax_url is used as the AJAX proxy URL.
* If there is no errorCallback, the window.error_callback is used.
*
* @param object data An object with the query data, e.g. a serialized form
* @param function successCallback A function accepting a single object parameter, called on success
* @param function errorCallback A function accepting a single string parameter, called on failure
*/
doEncryptedAjax = function(data, successCallback, errorCallback)
{
var json = JSON.stringify(data);
if( joomlaupdate_password.length > 0 )
{
json = AesCtr.encrypt( json, joomlaupdate_password, 128 );
}
var post_data = {
'json': json
};
var structure =
{
type: "POST",
url: joomlaupdate_ajax_url,
cache: false,
data: post_data,
timeout: 600000,
success: function(msg, responseXML)
{
// Initialize
var junk = null;
var message = "";
// Get rid of junk before the data
var valid_pos = msg.indexOf('###');
if( valid_pos == -1 )
{
// Valid data not found in the response
msg = 'Invalid AJAX data:\n' + msg;
if (errorCallback == null)
{
if(error_callback != null)
{
error_callback(msg);
}
}
else
{
errorCallback(msg);
}
return;
}
else if( valid_pos != 0 )
{
// Data is prefixed with junk
junk = msg.substr(0, valid_pos);
message = msg.substr(valid_pos);
}
else
{
message = msg;
}
message = message.substr(3); // Remove triple hash in the beginning
// Get of rid of junk after the data
var valid_pos = message.lastIndexOf('###');
message = message.substr(0, valid_pos); // Remove triple hash in the end
// Decrypt if required
var data = null;
if( joomlaupdate_password.length > 0 )
{
try
{
var data = JSON.parse(message);
}
catch(err)
{
message = AesCtr.decrypt(message, joomlaupdate_password, 128);
}
}
try
{
if (empty(data))
{
data = JSON.parse(message);
}
}
catch(err)
{
var msg = err.message + "\n<br/>\n<pre>\n" + message + "\n</pre>";
if (errorCallback == null)
{
if (error_callback != null)
{
error_callback(msg);
}
}
else
{
errorCallback(msg);
}
return;
}
// Call the callback function
successCallback(data);
},
error: function(req)
{
var message = 'AJAX Loading Error: ' + req.statusText;
if(errorCallback == null)
{
if (error_callback != null)
{
error_callback(message);
}
}
else
{
errorCallback(message);
}
}
};
jQuery.ajax( structure );
};
/**
* Pings the update script (making sure its executable)
*/
pingExtract = function()
{
// Reset variables
this.stat_files = 0;
this.stat_inbytes = 0;
this.stat_outbytes = 0;
// Do AJAX post
var post = {task : 'ping'};
this.doEncryptedAjax(post,
function(data) {
startExtract(data);
});
};
startExtract = function()
{
// Reset variables
this.stat_files = 0;
this.stat_inbytes = 0;
this.stat_outbytes = 0;
var post = { task : 'startRestore' };
this.doEncryptedAjax(post, function(data){
stepExtract(data);
});
};
stepExtract = function(data)
{
if(data.status == false)
{
// handle failure
error_callback(data.message);
return;
}
if( !empty(data.Warnings) )
{
// @todo Handle warnings
/**
$.each(data.Warnings, function(i, item){
$('#warnings').append(
$(document.createElement('div'))
.html(item)
);
$('#warningsBox').show('fast');
});
/**/
}
if (!empty(data.factory))
{
extract_factory = data.factory;
}
if(data.done)
{
finalizeUpdate();
}
else
{
// Add data to variables
stat_inbytes += data.bytesIn;
stat_percent = (stat_inbytes * 100) / joomlaupdate_totalsize;
// Update GUI
stat_outbytes += data.bytesOut;
stat_files += data.files;
if (stat_percent < 100)
{
jQuery('#progress-bar').css('width', stat_percent + '%').attr('aria-valuenow', stat_percent);
}
else if (stat_percent > 100)
{
stat_percent = 100;
jQuery('#progress-bar').css('width', stat_percent + '%').attr('aria-valuenow', stat_percent);
}
else
{
jQuery('#progress-bar').removeClass('bar-success');
}
jQuery('#extpercent').text(stat_percent.toFixed(1) + '%');
jQuery('#extbytesin').text(stat_inbytes);
jQuery('#extbytesout').text(stat_outbytes);
jQuery('#extfiles').text(stat_files);
// Do AJAX post
post = {
task: 'stepRestore',
factory: data.factory
};
doEncryptedAjax(post, function(data){
stepExtract(data);
});
}
};
finalizeUpdate = function ()
{
// Do AJAX post
var post = { task : 'finalizeRestore', factory: window.factory };
doEncryptedAjax(post, function(data){
window.location = joomlaupdate_return_url;
});
};
/**
* Is a variable empty?
*
* Part of php.js
*
* @see http://phpjs.org/
*
* @param mixed mixed_var The variable
*
* @returns boolean True if empty
*/
function empty (mixed_var)
{
var key;
if (mixed_var === "" ||
mixed_var === 0 ||
mixed_var === "0" ||
mixed_var === null ||
mixed_var === false ||
typeof mixed_var === 'undefined'
){
return true;
}
if (typeof mixed_var == 'object')
{
for (key in mixed_var)
{
return false;
}
return true;
}
return false;
}
/**
* Is the variable an array?
*
* Part of php.js
*
* @see http://phpjs.org/
*
* @param mixed mixed_var The variable
*
* @returns boolean True if it is an array or an object
*/
function is_array (mixed_var)
{
var key = '';
var getFuncName = function (fn) {
var name = (/\W*function\s+([\w\$]+)\s*\(/).exec(fn);
if (!name) {
return '(Anonymous)';
}
return name[1];
};
if (!mixed_var)
{
return false;
}
// BEGIN REDUNDANT
this.php_js = this.php_js || {};
this.php_js.ini = this.php_js.ini || {};
// END REDUNDANT
if (typeof mixed_var === 'object')
{
if (this.php_js.ini['phpjs.objectsAsArrays'] && // Strict checking for being a JavaScript array (only check this way if call ini_set('phpjs.objectsAsArrays', 0) to disallow objects as arrays)
(
(this.php_js.ini['phpjs.objectsAsArrays'].local_value.toLowerCase &&
this.php_js.ini['phpjs.objectsAsArrays'].local_value.toLowerCase() === 'off') ||
parseInt(this.php_js.ini['phpjs.objectsAsArrays'].local_value, 10) === 0)
) {
return mixed_var.hasOwnProperty('length') && // Not non-enumerable because of being on parent class
!mixed_var.propertyIsEnumerable('length') && // Since is own property, if not enumerable, it must be a built-in function
getFuncName(mixed_var.constructor) !== 'String'; // exclude String()
}
if (mixed_var.hasOwnProperty)
{
for (key in mixed_var) {
// Checks whether the object has the specified property
// if not, we figure it's not an object in the sense of a php-associative-array.
if (false === mixed_var.hasOwnProperty(key)) {
return false;
}
}
}
// Read discussion at: http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_is_array/
return true;
}
return false;
}
js/update.min.js 0000644 00000005765 15234424455 0007607 0 ustar 00 var stat_total=0,stat_files=0,stat_inbytes=0,stat_outbytes=0;function error_callback(t){alert("ERROR:\n"+t)}function empty(t){var r;if(""===t||0===t||"0"===t||null===t||!1===t||void 0===t)return!0;if("object"==typeof t){for(r in t)return!1;return!0}return!1}function is_array(t){var r,e,s="";if(!t)return!1;if(this.php_js=this.php_js||{},this.php_js.ini=this.php_js.ini||{},"object"==typeof t){if(this.php_js.ini["phpjs.objectsAsArrays"]&&(this.php_js.ini["phpjs.objectsAsArrays"].local_value.toLowerCase&&"off"===this.php_js.ini["phpjs.objectsAsArrays"].local_value.toLowerCase()||0===parseInt(this.php_js.ini["phpjs.objectsAsArrays"].local_value,10)))return t.hasOwnProperty("length")&&!t.propertyIsEnumerable("length")&&"String"!==(r=t.constructor,(e=/\W*function\s+([\w\$]+)\s*\(/.exec(r))?e[1]:"(Anonymous)");if(t.hasOwnProperty)for(s in t)if(!1===t.hasOwnProperty(s))return!1;return!0}return!1}doEncryptedAjax=function(t,r,e){var s=JSON.stringify(t);joomlaupdate_password.length>0&&(s=AesCtr.encrypt(s,joomlaupdate_password,128));var a={type:"POST",url:joomlaupdate_ajax_url,cache:!1,data:{json:s},timeout:6e5,success:function(t,s){var a="";if(-1==(n=t.indexOf("###")))return t="Invalid AJAX data:\n"+t,void(null==e?null!=error_callback&&error_callback(t):e(t));0!=n?(t.substr(0,n),a=t.substr(n)):a=t;var n=(a=a.substr(3)).lastIndexOf("###");a=a.substr(0,n);var o=null;if(joomlaupdate_password.length>0)try{o=JSON.parse(a)}catch(t){a=AesCtr.decrypt(a,joomlaupdate_password,128)}try{empty(o)&&(o=JSON.parse(a))}catch(r){t=r.message+"\n<br/>\n<pre>\n"+a+"\n</pre>";return void(null==e?null!=error_callback&&error_callback(t):e(t))}r(o)},error:function(t){var r="AJAX Loading Error: "+t.statusText;null==e?null!=error_callback&&error_callback(r):e(r)}};jQuery.ajax(a)},pingExtract=function(){this.stat_files=0,this.stat_inbytes=0,this.stat_outbytes=0;this.doEncryptedAjax({task:"ping"},function(t){startExtract(t)})},startExtract=function(){this.stat_files=0,this.stat_inbytes=0,this.stat_outbytes=0;this.doEncryptedAjax({task:"startRestore"},function(t){stepExtract(t)})},stepExtract=function(t){0!=t.status?(empty(t.Warnings),empty(t.factory)||(extract_factory=t.factory),t.done?finalizeUpdate():(stat_inbytes+=t.bytesIn,stat_percent=100*stat_inbytes/joomlaupdate_totalsize,stat_outbytes+=t.bytesOut,stat_files+=t.files,stat_percent<100?jQuery("#progress-bar").css("width",stat_percent+"%").attr("aria-valuenow",stat_percent):stat_percent>100?(stat_percent=100,jQuery("#progress-bar").css("width",stat_percent+"%").attr("aria-valuenow",stat_percent)):jQuery("#progress-bar").removeClass("bar-success"),jQuery("#extpercent").text(stat_percent.toFixed(1)+"%"),jQuery("#extbytesin").text(stat_inbytes),jQuery("#extbytesout").text(stat_outbytes),jQuery("#extfiles").text(stat_files),post={task:"stepRestore",factory:t.factory},doEncryptedAjax(post,function(t){stepExtract(t)}))):error_callback(t.message)},finalizeUpdate=function(){var t={task:"finalizeRestore",factory:window.factory};doEncryptedAjax(t,function(t){window.location=joomlaupdate_return_url})};
index.html 0000644 00000000037 15234424455 0006551 0 ustar 00 <!DOCTYPE html><title></title>
controllers/index.html 0000644 00000000037 15234617001 0011106 0 ustar 00 <!DOCTYPE html><title></title>
controllers/update.php 0000644 00000041236 15234617001 0011112 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/**
* The Joomla! update controller for the Update view
*
* @since 2.5.4
*/
class JoomlaupdateControllerUpdate extends JControllerLegacy
{
/**
* Performs the download of the update package
*
* @return void
*
* @since 2.5.4
*/
public function download()
{
$this->checkToken();
$options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
$options['text_file'] = 'joomla_update.php';
JLog::addLogger($options, JLog::INFO, array('Update', 'databasequery', 'jerror'));
$user = JFactory::getUser();
try
{
JLog::add(JText::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_START', $user->id, $user->name, JVERSION), JLog::INFO, 'Update');
}
catch (RuntimeException $exception)
{
// Informational log only
}
$this->_applyCredentials();
/** @var JoomlaupdateModelDefault $model */
$model = $this->getModel('Default');
$result = $model->download();
$file = $result['basename'];
$message = null;
$messageType = null;
// The validation was not successful for now just a warning.
// TODO: In Joomla 4 this will abort the installation
if ($result['check'] === false)
{
$message = JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_CHECKSUM_WRONG');
$messageType = 'warning';
try
{
JLog::add($message, JLog::INFO, 'Update');
}
catch (RuntimeException $exception)
{
// Informational log only
}
}
if ($file)
{
JFactory::getApplication()->setUserState('com_joomlaupdate.file', $file);
$url = 'index.php?option=com_joomlaupdate&task=update.install&' . JFactory::getSession()->getFormToken() . '=1';
try
{
JLog::add(JText::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_FILE', $file), JLog::INFO, 'Update');
}
catch (RuntimeException $exception)
{
// Informational log only
}
}
else
{
JFactory::getApplication()->setUserState('com_joomlaupdate.file', null);
$url = 'index.php?option=com_joomlaupdate';
$message = JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_DOWNLOADFAILED');
$messageType = 'error';
}
$this->setRedirect($url, $message, $messageType);
}
/**
* Start the installation of the new Joomla! version
*
* @return void
*
* @since 2.5.4
*/
public function install()
{
$this->checkToken('get');
JFactory::getApplication()->setUserState('com_joomlaupdate.oldversion', JVERSION);
$options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
$options['text_file'] = 'joomla_update.php';
JLog::addLogger($options, JLog::INFO, array('Update', 'databasequery', 'jerror'));
try
{
JLog::add(JText::_('COM_JOOMLAUPDATE_UPDATE_LOG_INSTALL'), JLog::INFO, 'Update');
}
catch (RuntimeException $exception)
{
// Informational log only
}
$this->_applyCredentials();
/** @var JoomlaupdateModelDefault $model */
$model = $this->getModel('Default');
$file = JFactory::getApplication()->getUserState('com_joomlaupdate.file', null);
$model->createRestorationFile($file);
$this->display();
}
/**
* Finalise the upgrade by running the necessary scripts
*
* @return void
*
* @since 2.5.4
*/
public function finalise()
{
/*
* Finalize with login page. Used for pre-token check versions
* to allow updates without problems but with a maximum of security.
*/
if (!JSession::checkToken('get'))
{
$this->setRedirect('index.php?option=com_joomlaupdate&view=update&layout=finaliseconfirm');
return false;
}
$options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
$options['text_file'] = 'joomla_update.php';
JLog::addLogger($options, JLog::INFO, array('Update', 'databasequery', 'jerror'));
try
{
JLog::add(JText::_('COM_JOOMLAUPDATE_UPDATE_LOG_FINALISE'), JLog::INFO, 'Update');
}
catch (RuntimeException $exception)
{
// Informational log only
}
$this->_applyCredentials();
/** @var JoomlaupdateModelDefault $model */
$model = $this->getModel('Default');
$model->finaliseUpgrade();
$url = 'index.php?option=com_joomlaupdate&task=update.cleanup&' . JFactory::getSession()->getFormToken() . '=1';
$this->setRedirect($url);
}
/**
* Clean up after ourselves
*
* @return void
*
* @since 2.5.4
*/
public function cleanup()
{
/*
* Cleanup with login page. Used for pre-token check versions to be able to update
* from =< 3.2.7 to allow updates without problems but with a maximum of security.
*/
if (!JSession::checkToken('get'))
{
$this->setRedirect('index.php?option=com_joomlaupdate&view=update&layout=finaliseconfirm');
return false;
}
$options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
$options['text_file'] = 'joomla_update.php';
JLog::addLogger($options, JLog::INFO, array('Update', 'databasequery', 'jerror'));
try
{
JLog::add(JText::_('COM_JOOMLAUPDATE_UPDATE_LOG_CLEANUP'), JLog::INFO, 'Update');
}
catch (RuntimeException $exception)
{
// Informational log only
}
$this->_applyCredentials();
/** @var JoomlaupdateModelDefault $model */
$model = $this->getModel('Default');
$model->cleanUp();
$url = 'index.php?option=com_joomlaupdate&view=default&layout=complete';
$this->setRedirect($url);
try
{
JLog::add(JText::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_COMPLETE', JVERSION), JLog::INFO, 'Update');
}
catch (RuntimeException $exception)
{
// Informational log only
}
}
/**
* Purges updates.
*
* @return void
*
* @since 3.0
*/
public function purge()
{
// Check for request forgeries
$this->checkToken();
// Purge updates
/** @var JoomlaupdateModelDefault $model */
$model = $this->getModel('Default');
$model->purge();
$url = 'index.php?option=com_joomlaupdate';
$this->setRedirect($url, $model->_message);
}
/**
* Uploads an update package to the temporary directory, under a random name
*
* @return void
*
* @since 3.6.0
*/
public function upload()
{
// Check for request forgeries
$this->checkToken();
// Did a non Super User tried to upload something (a.k.a. pathetic hacking attempt)?
JFactory::getUser()->authorise('core.admin') or jexit(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'));
$this->_applyCredentials();
/** @var JoomlaupdateModelDefault $model */
$model = $this->getModel('Default');
try
{
$model->upload();
}
catch (RuntimeException $e)
{
$url = 'index.php?option=com_joomlaupdate';
$this->setRedirect($url, $e->getMessage(), 'error');
return;
}
$token = JSession::getFormToken();
$url = 'index.php?option=com_joomlaupdate&task=update.captive&' . $token . '=1';
$this->setRedirect($url);
}
/**
* Checks there is a valid update package and redirects to the captive view for super admin authentication.
*
* @return array
*
* @since 3.6.0
*/
public function captive()
{
// Check for request forgeries
$this->checkToken('get');
// Did a non Super User tried to upload something (a.k.a. pathetic hacking attempt)?
if (!JFactory::getUser()->authorise('core.admin'))
{
throw new RuntimeException(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
}
// Do I really have an update package?
$tempFile = JFactory::getApplication()->getUserState('com_joomlaupdate.temp_file', null);
JLoader::import('joomla.filesystem.file');
if (empty($tempFile) || !JFile::exists($tempFile))
{
throw new RuntimeException(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
}
$this->input->set('view', 'upload');
$this->input->set('layout', 'captive');
$this->display();
}
/**
* Checks the admin has super administrator privileges and then proceeds with the update.
*
* @return array
*
* @since 3.6.0
*/
public function confirm()
{
// Check for request forgeries
$this->checkToken();
// Did a non Super User tried to upload something (a.k.a. pathetic hacking attempt)?
if (!JFactory::getUser()->authorise('core.admin'))
{
throw new RuntimeException(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
}
// Get the model
/** @var JoomlaupdateModelDefault $model */
$model = $this->getModel('default');
// Get the captive file before the session resets
$tempFile = JFactory::getApplication()->getUserState('com_joomlaupdate.temp_file', null);
// Do I really have an update package?
if (!$model->captiveFileExists())
{
throw new RuntimeException(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
}
// Try to log in
$credentials = array(
'username' => $this->input->post->get('username', '', 'username'),
'password' => $this->input->post->get('passwd', '', 'raw'),
'secretkey' => $this->input->post->get('secretkey', '', 'raw'),
);
$result = $model->captiveLogin($credentials);
if (!$result)
{
$model->removePackageFiles();
throw new RuntimeException(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
}
// Set the update source in the session
JFactory::getApplication()->setUserState('com_joomlaupdate.file', basename($tempFile));
try
{
JLog::add(JText::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_FILE', $tempFile), JLog::INFO, 'Update');
}
catch (RuntimeException $exception)
{
// Informational log only
}
// Redirect to the actual update page
$url = 'index.php?option=com_joomlaupdate&task=update.install&' . JFactory::getSession()->getFormToken() . '=1';
$this->setRedirect($url);
}
/**
* Method to display a view.
*
* @param boolean $cachable If true, the view output will be cached
* @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link JFilterInput::clean()}.
*
* @return JoomlaupdateControllerUpdate This object to support chaining.
*
* @since 2.5.4
*/
public function display($cachable = false, $urlparams = array())
{
// Get the document object.
$document = JFactory::getDocument();
// Set the default view name and format from the Request.
$vName = $this->input->get('view', 'update');
$vFormat = $document->getType();
$lName = $this->input->get('layout', 'default', 'string');
// Get and render the view.
if ($view = $this->getView($vName, $vFormat))
{
// Get the model for the view.
/** @var JoomlaupdateModelDefault $model */
$model = $this->getModel('Default');
// Push the model into the view (as default).
$view->setModel($model, true);
$view->setLayout($lName);
// Push document object into the view.
$view->document = $document;
$view->display();
}
return $this;
}
/**
* Applies FTP credentials to Joomla! itself, when required
*
* @return void
*
* @since 2.5.4
*/
protected function _applyCredentials()
{
JFactory::getApplication()->getUserStateFromRequest('com_joomlaupdate.method', 'method', 'direct', 'cmd');
if (!JClientHelper::hasCredentials('ftp'))
{
$user = JFactory::getApplication()->getUserStateFromRequest('com_joomlaupdate.ftp_user', 'ftp_user', null, 'raw');
$pass = JFactory::getApplication()->getUserStateFromRequest('com_joomlaupdate.ftp_pass', 'ftp_pass', null, 'raw');
if ($user != '' && $pass != '')
{
// Add credentials to the session
if (!JClientHelper::setCredentials('ftp', $user, $pass))
{
JError::raiseWarning(500, JText::_('JLIB_CLIENT_ERROR_HELPER_SETCREDENTIALSFROMREQUEST_FAILED'));
}
}
}
}
/**
* Checks the admin has super administrator privileges and then proceeds with the final & cleanup steps.
*
* @return array
*
* @since 3.6.3
*/
public function finaliseconfirm()
{
// Check for request forgeries
$this->checkToken();
// Did a non Super User try do this?
if (!JFactory::getUser()->authorise('core.admin'))
{
throw new RuntimeException(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
}
// Get the model
/** @var JoomlaupdateModelDefault $model */
$model = $this->getModel('default');
// Try to log in
$credentials = array(
'username' => $this->input->post->get('username', '', 'username'),
'password' => $this->input->post->get('passwd', '', 'raw'),
'secretkey' => $this->input->post->get('secretkey', '', 'raw'),
);
$result = $model->captiveLogin($credentials);
// The login fails?
if (!$result)
{
JFactory::getApplication()->enqueueMessage(JText::_('JGLOBAL_AUTH_INVALID_PASS'), 'warning');
$this->setRedirect('index.php?option=com_joomlaupdate&view=update&layout=finaliseconfirm');
return false;
}
// Redirect back to the actual finalise page
$this->setRedirect('index.php?option=com_joomlaupdate&task=update.finalise&' . JFactory::getSession()->getFormToken() . '=1');
}
/**
* Fetch Extension update XML proxy. Used to prevent Access-Control-Allow-Origin errors.
* Prints a JSON string.
* Called from JS.
*
* @since 3.10.0
*
* @return void
*/
public function fetchExtensionCompatibility()
{
$extensionID = $this->input->get('extension-id', '', 'DEFAULT');
$joomlaTargetVersion = $this->input->get('joomla-target-version', '', 'DEFAULT');
$joomlaCurrentVersion = $this->input->get('joomla-current-version', '', JVERSION);
$extensionVersion = $this->input->get('extension-version', '', 'DEFAULT');
/** @var JoomlaupdateModelDefault $model */
$model = $this->getModel('default');
$upgradeCompatibilityStatus = $model->fetchCompatibility($extensionID, $joomlaTargetVersion);
$currentCompatibilityStatus = $model->fetchCompatibility($extensionID, $joomlaCurrentVersion);
$upgradeUpdateVersion = false;
$currentUpdateVersion = false;
$upgradeWarning = 0;
if ($upgradeCompatibilityStatus->state == 1 && !empty($upgradeCompatibilityStatus->compatibleVersions))
{
$upgradeUpdateVersion = end($upgradeCompatibilityStatus->compatibleVersions);
}
if ($currentCompatibilityStatus->state == 1 && !empty($currentCompatibilityStatus->compatibleVersions))
{
$currentUpdateVersion = end($currentCompatibilityStatus->compatibleVersions);
}
if ($upgradeUpdateVersion !== false)
{
$upgradeOldestVersion = $upgradeCompatibilityStatus->compatibleVersions[0];
if ($currentUpdateVersion !== false)
{
// If there are updates compatible with both CMS versions use these
$bothCompatibleVersions = array_values(
array_intersect($upgradeCompatibilityStatus->compatibleVersions, $currentCompatibilityStatus->compatibleVersions)
);
if (!empty($bothCompatibleVersions))
{
$upgradeOldestVersion = $bothCompatibleVersions[0];
$upgradeUpdateVersion = end($bothCompatibleVersions);
}
}
if (version_compare($upgradeOldestVersion, $extensionVersion, '>'))
{
// Installed version is empty or older than the oldest compatible update: Update required
$resultGroup = 2;
}
else
{
// Current version is compatible
$resultGroup = 3;
}
if ($currentUpdateVersion !== false && version_compare($upgradeUpdateVersion, $currentUpdateVersion, '<'))
{
// Special case warning when version compatible with target is lower than current
$upgradeWarning = 2;
}
}
elseif ($currentUpdateVersion !== false)
{
// No compatible version for target version but there is a compatible version for current version
$resultGroup = 1;
}
else
{
// No update server available
$resultGroup = 1;
}
// Do we need to capture
$combinedCompatibilityStatus = array(
'upgradeCompatibilityStatus' => (object) array(
'state' => $upgradeCompatibilityStatus->state,
'compatibleVersion' => $upgradeUpdateVersion
),
'currentCompatibilityStatus' => (object) array(
'state' => $currentCompatibilityStatus->state,
'compatibleVersion' => $currentUpdateVersion
),
'resultGroup' => $resultGroup,
'upgradeWarning' => $upgradeWarning,
);
$this->app = JFactory::getApplication();
$this->app->mimeType = 'application/json';
$this->app->charSet = 'utf-8';
$this->app->setHeader('Content-Type', $this->app->mimeType . '; charset=' . $this->app->charSet);
$this->app->sendHeaders();
try
{
echo new JResponseJson($combinedCompatibilityStatus);
}
catch (Exception $e)
{
echo $e;
}
$this->app->close();
}
/**
* Fetch and report updates in JSON format, for AJAX requests
*
* @return void
*
* @since 3.10.10
*/
public function ajax()
{
$app = JFactory::getApplication();
if (!JSession::checkToken('get'))
{
$app->setHeader('status', 403, true);
$app->sendHeaders();
echo JText::_('JINVALID_TOKEN_NOTICE');
$app->close();
}
$model = $this->getModel('default');
$updateInfo = $model->getUpdateInformation();
$update = array();
$update[] = array('version' => $updateInfo['latest']);
echo json_encode($update);
$app->close();
}
}
helpers/index.html 0000644 00000000037 15234617001 0010202 0 ustar 00 <!DOCTYPE html><title></title>
helpers/joomlaupdate.php 0000644 00000001666 15234617001 0011413 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/**
* Joomla! update helper.
*
* @since 2.5.4
*/
class JoomlaupdateHelper
{
/**
* Gets a list of the actions that can be performed.
*
* @return JObject
*
* @since 2.5.4
* @deprecated 3.2 Use JHelperContent::getActions() instead
*/
public static function getActions()
{
// Log usage of deprecated function
try
{
JLog::add(
sprintf('%s() is deprecated. Use JHelperContent::getActions() with new arguments order instead.', __METHOD__),
JLog::WARNING,
'deprecated'
);
}
catch (RuntimeException $exception)
{
// Informational log only
}
// Get list of actions
return JHelperContent::getActions('com_joomlaupdate');
}
}
helpers/select.php 0000644 00000002326 15234617001 0010200 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/**
* Joomla! update selection list helper.
*
* @since 2.5.4
*/
class JoomlaupdateHelperSelect
{
/**
* Returns an HTML select element with the different extraction modes
*
* @param string $default The default value of the select element
* @param string $name The name of the form field
* @param string $id The id of the select field
*
* @return string
*
* @since 2.5.4
*/
public static function getMethods($default = 'hybrid', $name = 'method', $id = 'extraction_method')
{
$options = array();
$options[] = JHtml::_('select.option', 'direct', JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_METHOD_DIRECT'));
$options[] = JHtml::_('select.option', 'hybrid', JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_METHOD_HYBRID'));
$options[] = JHtml::_('select.option', 'ftp', JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_METHOD_FTP'));
return JHtml::_('select.genericlist', $options, $name, '', 'value', 'text', $default, $id);
}
}
models/default.php 0000644 00000142237 15234617001 0010174 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\Extension\ExtensionHelper;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Http\HttpFactory;
use Joomla\Registry\Registry;
jimport('joomla.filesystem.folder');
jimport('joomla.filesystem.file');
/**
* Joomla! update overview Model
*
* @since 2.5.4
*/
class JoomlaupdateModelDefault extends JModelLegacy
{
/**
* @var array $updateInformation null
* Holds the update information evaluated in getUpdateInformation.
*
* @since 3.10.0
*/
private $updateInformation = null;
/**
* Detects if the Joomla! update site currently in use matches the one
* configured in this component. If they don't match, it changes it.
*
* @return void
*
* @since 2.5.4
*/
public function applyUpdateSite()
{
// Determine the intended update URL.
$params = JComponentHelper::getParams('com_joomlaupdate');
switch ($params->get('updatesource', 'nochange'))
{
// "Minor & Patch Release for Current version AND Next Major Release".
case 'next':
$updateURL = 'https://update.joomla.org/core/sts/list_sts.xml';
break;
// "Testing"
case 'testing':
$updateURL = 'https://update.joomla.org/core/test/list_test.xml';
break;
// "Custom"
// TODO: check if the customurl is valid and not just "not empty".
case 'custom':
if (trim($params->get('customurl', '')) != '')
{
$updateURL = trim($params->get('customurl', ''));
}
else
{
return JError::raiseWarning(403, JText::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_CUSTOM_ERROR'));
}
break;
/**
* "Minor & Patch Release for Current version (recommended and default)".
* The commented "case" below are for documenting where 'default' and legacy options falls
* case 'default':
* case 'lts':
* case 'sts': (It's shown as "Default" because that option does not exist any more)
* case 'nochange':
*/
default:
$updateURL = 'https://update.joomla.org/core/list.xml';
}
$db = $this->getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('us') . '.*')
->from($db->quoteName('#__update_sites_extensions') . ' AS ' . $db->quoteName('map'))
->join(
'INNER', $db->quoteName('#__update_sites') . ' AS ' . $db->quoteName('us')
. ' ON (' . 'us.update_site_id = map.update_site_id)'
)
->where('map.extension_id = ' . $db->quote(700));
$db->setQuery($query);
$update_site = $db->loadObject();
if ($update_site->location != $updateURL)
{
// Modify the database record.
$update_site->last_check_timestamp = 0;
$update_site->location = $updateURL;
$db->updateObject('#__update_sites', $update_site, 'update_site_id');
// Remove cached updates.
$query->clear()
->delete($db->quoteName('#__updates'))
->where($db->quoteName('extension_id') . ' = ' . $db->quote('700'));
$db->setQuery($query);
$db->execute();
}
}
/**
* Makes sure that the Joomla! update cache is up-to-date.
*
* @param boolean $force Force reload, ignoring the cache timeout.
*
* @return void
*
* @since 2.5.4
*/
public function refreshUpdates($force = false)
{
if ($force)
{
$cache_timeout = 0;
}
else
{
$cache_timeout = 3600 * JComponentHelper::getParams('com_installer')->get('cachetimeout', 6, 'int');
}
$updater = JUpdater::getInstance();
$minimumStability = JUpdater::STABILITY_STABLE;
$comJoomlaupdateParams = JComponentHelper::getParams('com_joomlaupdate');
if (in_array($comJoomlaupdateParams->get('updatesource', 'nochange'), array('testing', 'custom')))
{
$minimumStability = $comJoomlaupdateParams->get('minimum_stability', JUpdater::STABILITY_STABLE);
}
$reflection = new ReflectionObject($updater);
$reflectionMethod = $reflection->getMethod('findUpdates');
$methodParameters = $reflectionMethod->getParameters();
if (count($methodParameters) >= 4)
{
// Reinstall support is available in JUpdater
$updater->findUpdates(700, $cache_timeout, $minimumStability, true);
}
else
{
$updater->findUpdates(700, $cache_timeout, $minimumStability);
}
}
/**
* Returns an array with the Joomla! update information.
*
* @return array
*
* @since 2.5.4
*/
public function getUpdateInformation()
{
if ($this->updateInformation)
{
return $this->updateInformation;
}
// Initialise the return array.
$this->updateInformation = array(
'installed' => JVERSION,
'latest' => null,
'object' => null,
'hasUpdate' => false,
'current' => JVERSION, // This is deprecated please use 'installed' or JVERSION directly
);
// Fetch the update information from the database.
$db = $this->getDbo();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__updates'))
->where($db->quoteName('extension_id') . ' = ' . $db->quote(700));
$db->setQuery($query);
$updateObject = $db->loadObject();
if (is_null($updateObject))
{
$this->updateInformation['latest'] = JVERSION;
return $this->updateInformation;
}
// Check whether this is a valid update or not
if (version_compare($updateObject->version, JVERSION, '<'))
{
// This update points to an outdated version we should not offer to update to this
$this->updateInformation['latest'] = JVERSION;
return $this->updateInformation;
}
$minimumStability = JUpdater::STABILITY_STABLE;
$comJoomlaupdateParams = JComponentHelper::getParams('com_joomlaupdate');
if (in_array($comJoomlaupdateParams->get('updatesource', 'nochange'), array('testing', 'custom')))
{
$minimumStability = $comJoomlaupdateParams->get('minimum_stability', JUpdater::STABILITY_STABLE);
}
// Fetch the full update details from the update details URL.
jimport('joomla.updater.update');
$update = new JUpdate;
$update->loadFromXML($updateObject->detailsurl, $minimumStability);
// Make sure we use the current information we got from the detailsurl
$this->updateInformation['object'] = $update;
$this->updateInformation['latest'] = $updateObject->version;
// Check whether we have got an update from the detailsurl or not.
if (version_compare($this->updateInformation['latest'], JVERSION, '>'))
{
$this->updateInformation['hasUpdate'] = true;
}
return $this->updateInformation;
}
/**
* Returns an array with the configured FTP options.
*
* @return array
*
* @since 2.5.4
*/
public function getFTPOptions()
{
$config = JFactory::getConfig();
return array(
'host' => $config->get('ftp_host'),
'port' => $config->get('ftp_port'),
'username' => $config->get('ftp_user'),
'password' => $config->get('ftp_pass'),
'directory' => $config->get('ftp_root'),
'enabled' => $config->get('ftp_enable'),
);
}
/**
* Removes all of the updates from the table and enable all update streams.
*
* @return boolean Result of operation.
*
* @since 3.0
*/
public function purge()
{
$db = $this->getDbo();
// Reset the last update check timestamp
$query = $db->getQuery(true)
->update($db->quoteName('#__update_sites'))
->set($db->quoteName('last_check_timestamp') . ' = 0');
$db->setQuery($query);
$db->execute();
// We should delete all core updates here
$query = $db->getQuery(true)
->delete($db->quoteName('#__updates'))
->where($db->quoteName('element') . ' = ' . $db->quote('joomla'))
->where($db->quoteName('type') . ' = ' . $db->quote('file'));
$db->setQuery($query);
if ($db->execute())
{
$this->_message = JText::_('COM_JOOMLAUPDATE_CHECKED_UPDATES');
return true;
}
else
{
$this->_message = JText::_('COM_JOOMLAUPDATE_FAILED_TO_CHECK_UPDATES');
return false;
}
}
/**
* Downloads the update package to the site.
*
* @return boolean|string False on failure, basename of the file in any other case.
*
* @since 2.5.4
*/
public function download()
{
$updateInfo = $this->getUpdateInformation();
$packageURL = trim($updateInfo['object']->downloadurl->_data);
$sources = $updateInfo['object']->get('downloadSources', array());
// We have to manually follow the redirects here so we set the option to false.
$httpOptions = new Registry;
$httpOptions->set('follow_location', false);
try
{
$head = HttpFactory::getHttp($httpOptions)->head($packageURL);
}
catch (RuntimeException $e)
{
// Passing false here -> download failed message
$response['basename'] = false;
return $response;
}
// Follow the Location headers until the actual download URL is known
while (isset($head->headers['location']))
{
$packageURL = $head->headers['location'];
try
{
$head = HttpFactory::getHttp($httpOptions)->head($packageURL);
}
catch (RuntimeException $e)
{
// Passing false here -> download failed message
$response['basename'] = false;
return $response;
}
}
// Remove protocol, path and query string from URL
$basename = basename($packageURL);
if (strpos($basename, '?') !== false)
{
$basename = substr($basename, 0, strpos($basename, '?'));
}
// Find the path to the temp directory and the local package.
$config = JFactory::getConfig();
$tempdir = (string) InputFilter::getInstance(array(), array(), 1, 1)->clean($config->get('tmp_path'), 'path');
$target = $tempdir . '/' . $basename;
$response = array();
// Do we have a cached file?
$exists = JFile::exists($target);
if (!$exists)
{
// Not there, let's fetch it.
$mirror = 0;
while (!($download = $this->downloadPackage($packageURL, $target)) && isset($sources[$mirror]))
{
$name = $sources[$mirror];
$packageURL = trim($name->url);
$mirror++;
}
$response['basename'] = $download;
}
else
{
// Is it a 0-byte file? If so, re-download please.
$filesize = @filesize($target);
if (empty($filesize))
{
$mirror = 0;
while (!($download = $this->downloadPackage($packageURL, $target)) && isset($sources[$mirror]))
{
$name = $sources[$mirror];
$packageURL = trim($name->url);
$mirror++;
}
$response['basename'] = $download;
}
// Yes, it's there, skip downloading.
$response['basename'] = $basename;
}
$response['check'] = $this->isChecksumValid($target, $updateInfo['object']);
return $response;
}
/**
* Return the result of the checksum of a package with the SHA256/SHA384/SHA512 tags in the update server manifest
*
* @param string $packagefile Location of the package to be installed
* @param JUpdate $updateObject The Update Object
*
* @return boolean False in case the validation did not work; true in any other case.
*
* @note This method has been forked from (JInstallerHelper::isChecksumValid) so it
* does not depend on an up-to-date InstallerHelper at the update time
*
* @since 3.9.0
*/
private function isChecksumValid($packagefile, $updateObject)
{
$hashes = array('sha256', 'sha384', 'sha512');
foreach ($hashes as $hash)
{
if ($updateObject->get($hash, false))
{
$hashPackage = hash_file($hash, $packagefile);
$hashRemote = $updateObject->$hash->_data;
if ($hashPackage !== $hashRemote)
{
// Return false in case the hash did not match
return false;
}
}
}
// Well nothing was provided or all worked
return true;
}
/**
* Downloads a package file to a specific directory
*
* @param string $url The URL to download from
* @param string $target The directory to store the file
*
* @return boolean True on success
*
* @since 2.5.4
*/
protected function downloadPackage($url, $target)
{
JLoader::import('helpers.download', JPATH_COMPONENT_ADMINISTRATOR);
try
{
JLog::add(JText::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_URL', $url), JLog::INFO, 'Update');
}
catch (RuntimeException $exception)
{
// Informational log only
}
// Get the handler to download the package
try
{
$http = JHttpFactory::getHttp(null, array('curl', 'stream'));
}
catch (RuntimeException $e)
{
return false;
}
jimport('joomla.filesystem.file');
// Make sure the target does not exist.
JFile::delete($target);
// Download the package
try
{
$result = $http->get($url);
}
catch (RuntimeException $e)
{
return false;
}
if (!$result || ($result->code != 200 && $result->code != 310))
{
return false;
}
// Write the file to disk
JFile::write($target, $result->body);
return basename($target);
}
/**
* Create restoration file.
*
* @param string $basename Optional base path to the file.
*
* @return boolean True if successful; false otherwise.
*
* @since 2.5.4
*/
public function createRestorationFile($basename = null)
{
// Get a password
$password = JUserHelper::genRandomPassword(32);
$app = JFactory::getApplication();
$app->setUserState('com_joomlaupdate.password', $password);
// Do we have to use FTP?
$method = JFactory::getApplication()->getUserStateFromRequest('com_joomlaupdate.method', 'method', 'direct', 'cmd');
// Get the absolute path to site's root.
$siteroot = JPATH_SITE;
// If the package name is not specified, get it from the update info.
if (empty($basename))
{
$updateInfo = $this->getUpdateInformation();
$packageURL = $updateInfo['object']->downloadurl->_data;
$basename = basename($packageURL);
}
// Get the package name.
$config = JFactory::getConfig();
$tempdir = $config->get('tmp_path');
$file = $tempdir . '/' . $basename;
$filesize = @filesize($file);
$app->setUserState('com_joomlaupdate.password', $password);
$app->setUserState('com_joomlaupdate.filesize', $filesize);
$data = "<?php\ndefined('_AKEEBA_RESTORATION') or die('Restricted access');\n";
$data .= '$restoration_setup = array(' . "\n";
$data .= <<<ENDDATA
'kickstart.security.password' => '$password',
'kickstart.tuning.max_exec_time' => '5',
'kickstart.tuning.run_time_bias' => '75',
'kickstart.tuning.min_exec_time' => '0',
'kickstart.procengine' => '$method',
'kickstart.setup.sourcefile' => '$file',
'kickstart.setup.destdir' => '$siteroot',
'kickstart.setup.restoreperms' => '0',
'kickstart.setup.filetype' => 'zip',
'kickstart.setup.dryrun' => '0',
'kickstart.setup.renamefiles' => array(),
'kickstart.setup.postrenamefiles' => false
ENDDATA;
if ($method != 'direct')
{
/*
* Fetch the FTP parameters from the request. Note: The password should be
* allowed as raw mode, otherwise something like !@<sdf34>43H% would be
* sanitised to !@43H% which is just plain wrong.
*/
$ftp_host = $app->input->get('ftp_host', '');
$ftp_port = $app->input->get('ftp_port', '21');
$ftp_user = $app->input->get('ftp_user', '');
$ftp_pass = addcslashes($app->input->get('ftp_pass', '', 'raw'), "'\\");
$ftp_root = $app->input->get('ftp_root', '');
// Is the tempdir really writable?
$writable = @is_writeable($tempdir);
if ($writable)
{
// Let's be REALLY sure.
$fp = @fopen($tempdir . '/test.txt', 'w');
if ($fp === false)
{
$writable = false;
}
else
{
fclose($fp);
unlink($tempdir . '/test.txt');
}
}
// If the tempdir is not writable, create a new writable subdirectory.
if (!$writable)
{
$FTPOptions = JClientHelper::getCredentials('ftp');
$ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
$dest = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $tempdir . '/admintools'), '/');
if (!@mkdir($tempdir . '/admintools'))
{
$ftp->mkdir($dest);
}
if (!@chmod($tempdir . '/admintools', 511))
{
$ftp->chmod($dest, 511);
}
$tempdir .= '/admintools';
}
// Just in case the temp-directory was off-root, try using the default tmp directory.
$writable = @is_writeable($tempdir);
if (!$writable)
{
$tempdir = JPATH_ROOT . '/tmp';
// Does the JPATH_ROOT/tmp directory exist?
if (!is_dir($tempdir))
{
JFolder::create($tempdir, 511);
$htaccessContents = "order deny,allow\ndeny from all\nallow from none\n";
JFile::write($tempdir . '/.htaccess', $htaccessContents);
}
// If it exists and it is unwritable, try creating a writable admintools subdirectory.
if (!is_writable($tempdir))
{
$FTPOptions = JClientHelper::getCredentials('ftp');
$ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
$dest = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $tempdir . '/admintools'), '/');
if (!@mkdir($tempdir . '/admintools'))
{
$ftp->mkdir($dest);
}
if (!@chmod($tempdir . '/admintools', 511))
{
$ftp->chmod($dest, 511);
}
$tempdir .= '/admintools';
}
}
// If we still have no writable directory, we'll try /tmp and the system's temp-directory.
$writable = @is_writeable($tempdir);
if (!$writable)
{
if (@is_dir('/tmp') && @is_writable('/tmp'))
{
$tempdir = '/tmp';
}
else
{
// Try to find the system temp path.
$tmpfile = @tempnam('dummy', '');
$systemp = @dirname($tmpfile);
@unlink($tmpfile);
if (!empty($systemp))
{
if (@is_dir($systemp) && @is_writable($systemp))
{
$tempdir = $systemp;
}
}
}
}
$data .= <<<ENDDATA
,
'kickstart.ftp.ssl' => '0',
'kickstart.ftp.passive' => '1',
'kickstart.ftp.host' => '$ftp_host',
'kickstart.ftp.port' => '$ftp_port',
'kickstart.ftp.user' => '$ftp_user',
'kickstart.ftp.pass' => '$ftp_pass',
'kickstart.ftp.dir' => '$ftp_root',
'kickstart.ftp.tempdir' => '$tempdir'
ENDDATA;
}
$data .= ');';
// Remove the old file, if it's there...
$configpath = JPATH_COMPONENT_ADMINISTRATOR . '/restoration.php';
if (JFile::exists($configpath))
{
JFile::delete($configpath);
}
// Write new file. First try with JFile.
$result = JFile::write($configpath, $data);
// In case JFile used FTP but direct access could help.
if (!$result)
{
if (function_exists('file_put_contents'))
{
$result = @file_put_contents($configpath, $data);
if ($result !== false)
{
$result = true;
}
}
else
{
$fp = @fopen($configpath, 'wt');
if ($fp !== false)
{
$result = @fwrite($fp, $data);
if ($result !== false)
{
$result = true;
}
@fclose($fp);
}
}
}
return $result;
}
/**
* Runs the schema update SQL files, the PHP update script and updates the
* manifest cache and #__extensions entry. Essentially, it is identical to
* JInstallerFile::install() without the file copy.
*
* @return boolean True on success.
*
* @since 2.5.4
*/
public function finaliseUpgrade()
{
$installer = JInstaller::getInstance();
$manifest = $installer->isManifest(JPATH_MANIFESTS . '/files/joomla.xml');
if ($manifest === false)
{
$installer->abort(JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));
return false;
}
$installer->manifest = $manifest;
$installer->setUpgrade(true);
$installer->setOverwrite(true);
$installer->extension = JTable::getInstance('extension');
$installer->extension->load(700);
$installer->setAdapter($installer->extension->type);
$installer->setPath('manifest', JPATH_MANIFESTS . '/files/joomla.xml');
$installer->setPath('source', JPATH_MANIFESTS . '/files');
$installer->setPath('extension_root', JPATH_ROOT);
// Run the script file.
JLoader::register('JoomlaInstallerScript', JPATH_ADMINISTRATOR . '/components/com_admin/script.php');
$manifestClass = new JoomlaInstallerScript;
ob_start();
ob_implicit_flush(false);
if ($manifestClass && method_exists($manifestClass, 'preflight'))
{
if ($manifestClass->preflight('update', $installer) === false)
{
$installer->abort(JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_CUSTOM_INSTALL_FAILURE'));
return false;
}
}
// Create msg object; first use here.
$msg = ob_get_contents();
ob_end_clean();
// Get a database connector object.
$db = $this->getDbo();
/*
* Check to see if a file extension by the same name is already installed.
* If it is, then update the table because if the files aren't there
* we can assume that it was (badly) uninstalled.
* If it isn't, add an entry to extensions.
*/
$query = $db->getQuery(true)
->select($db->quoteName('extension_id'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' . $db->quote('file'))
->where($db->quoteName('element') . ' = ' . $db->quote('joomla'));
$db->setQuery($query);
try
{
$db->execute();
}
catch (RuntimeException $e)
{
// Install failed, roll back changes.
$installer->abort(
JText::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', JText::_('JLIB_INSTALLER_UPDATE'), $e->getMessage())
);
return false;
}
$id = $db->loadResult();
$row = JTable::getInstance('extension');
if ($id)
{
// Load the entry and update the manifest_cache.
$row->load($id);
// Update name.
$row->set('name', 'files_joomla');
// Update manifest.
$row->manifest_cache = $installer->generateManifestCache();
if (!$row->store())
{
// Install failed, roll back changes.
$installer->abort(
JText::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', JText::_('JLIB_INSTALLER_UPDATE'), $row->getError())
);
return false;
}
}
else
{
// Add an entry to the extension table with a whole heap of defaults.
$row->set('name', 'files_joomla');
$row->set('type', 'file');
$row->set('element', 'joomla');
// There is no folder for files so leave it blank.
$row->set('folder', '');
$row->set('enabled', 1);
$row->set('protected', 0);
$row->set('access', 0);
$row->set('client_id', 0);
$row->set('params', '');
$row->set('system_data', '');
$row->set('manifest_cache', $installer->generateManifestCache());
if (!$row->store())
{
// Install failed, roll back changes.
$installer->abort(JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_ROLLBACK', $row->getError()));
return false;
}
// Set the insert id.
$row->set('extension_id', $db->insertid());
// Since we have created a module item, we add it to the installation step stack
// so that if we have to rollback the changes we can undo it.
$installer->pushStep(array('type' => 'extension', 'extension_id' => $row->extension_id));
}
$result = $installer->parseSchemaUpdates($manifest->update->schemas, $row->extension_id);
if ($result === false)
{
// Install failed, rollback changes.
$installer->abort(JText::sprintf('JLIB_INSTALLER_ABORT_FILE_UPDATE_SQL_ERROR', $db->stderr(true)));
return false;
}
// Start Joomla! 1.6.
ob_start();
ob_implicit_flush(false);
if ($manifestClass && method_exists($manifestClass, 'update'))
{
if ($manifestClass->update($installer) === false)
{
// Install failed, rollback changes.
$installer->abort(JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_CUSTOM_INSTALL_FAILURE'));
return false;
}
}
// Append messages.
$msg .= ob_get_contents();
ob_end_clean();
// Clobber any possible pending updates.
$update = JTable::getInstance('update');
$uid = $update->find(
array('element' => 'joomla', 'type' => 'file', 'client_id' => '0', 'folder' => '')
);
if ($uid)
{
$update->delete($uid);
}
// And now we run the postflight.
ob_start();
ob_implicit_flush(false);
if ($manifestClass && method_exists($manifestClass, 'postflight'))
{
$manifestClass->postflight('update', $installer);
}
// Append messages.
$msg .= ob_get_contents();
ob_end_clean();
if ($msg != '')
{
$installer->set('extension_message', $msg);
}
// Refresh versionable assets cache.
JFactory::getApplication()->flushAssets();
return true;
}
/**
* Removes the extracted package file.
*
* @return void
*
* @since 2.5.4
*/
public function cleanUp()
{
// Remove the update package.
$config = JFactory::getConfig();
$tempdir = $config->get('tmp_path');
$file = JFactory::getApplication()->getUserState('com_joomlaupdate.file', null);
$target = $tempdir . '/' . $file;
if (!@unlink($target))
{
JFile::delete($target);
}
// Remove the restoration.php file.
$target = JPATH_COMPONENT_ADMINISTRATOR . '/restoration.php';
if (!@unlink($target))
{
JFile::delete($target);
}
// Remove joomla.xml from the site's root.
$target = JPATH_ROOT . '/joomla.xml';
if (!@unlink($target))
{
JFile::delete($target);
}
// Unset the update filename from the session.
JFactory::getApplication()->setUserState('com_joomlaupdate.file', null);
$oldVersion = JFactory::getApplication()->getUserState('com_joomlaupdate.oldversion');
// Trigger event after joomla update.
JFactory::getApplication()->triggerEvent('onJoomlaAfterUpdate', array($oldVersion));
JFactory::getApplication()->setUserState('com_joomlaupdate.oldversion', null);
}
/**
* Uploads what is presumably an update ZIP file under a mangled name in the temporary directory.
*
* @return void
*
* @since 3.6.0
*/
public function upload()
{
// Get the uploaded file information.
$input = JFactory::getApplication()->input;
// Do not change the filter type 'raw'. We need this to let files containing PHP code to upload. See JInputFiles::get.
$userfile = $input->files->get('install_package', null, 'raw');
// Make sure that file uploads are enabled in php.
if (!(bool) ini_get('file_uploads'))
{
throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLFILE'), 500);
}
// Make sure that zlib is loaded so that the package can be unpacked.
if (!extension_loaded('zlib'))
{
throw new RuntimeException('COM_INSTALLER_MSG_INSTALL_WARNINSTALLZLIB', 500);
}
// If there is no uploaded file, we have a problem...
if (!is_array($userfile))
{
throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_NO_FILE_SELECTED'), 500);
}
// Is the PHP tmp directory missing?
if ($userfile['error'] && ($userfile['error'] == UPLOAD_ERR_NO_TMP_DIR))
{
throw new RuntimeException(
JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR') . '<br />' .
JText::_('COM_INSTALLER_MSG_WARNINGS_PHPUPLOADNOTSET'),
500
);
}
// Is the max upload size too small in php.ini?
if ($userfile['error'] && ($userfile['error'] == UPLOAD_ERR_INI_SIZE))
{
throw new RuntimeException(
JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR') . '<br />' . JText::_('COM_INSTALLER_MSG_WARNINGS_SMALLUPLOADSIZE'),
500
);
}
// Check if there was a different problem uploading the file.
if ($userfile['error'] || $userfile['size'] < 1)
{
throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR'), 500);
}
// Build the appropriate paths.
$config = JFactory::getConfig();
$tmp_dest = tempnam($config->get('tmp_path'), 'ju');
$tmp_src = $userfile['tmp_name'];
// Move uploaded file.
jimport('joomla.filesystem.file');
if (version_compare(JVERSION, '3.4.0', 'ge'))
{
$result = JFile::upload($tmp_src, $tmp_dest, false, true);
}
else
{
// Old Joomla! versions didn't have UploadShield and don't need the fourth parameter to accept uploads
$result = JFile::upload($tmp_src, $tmp_dest);
}
if (!$result)
{
throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR'), 500);
}
JFactory::getApplication()->setUserState('com_joomlaupdate.temp_file', $tmp_dest);
}
/**
* Checks the super admin credentials are valid for the currently logged in users
*
* @param array $credentials The credentials to authenticate the user with
*
* @return boolean
*
* @since 3.6.0
*/
public function captiveLogin($credentials)
{
// Make sure the username matches
$username = isset($credentials['username']) ? $credentials['username'] : null;
$user = JFactory::getUser();
if (strtolower($user->username) != strtolower($username))
{
return false;
}
// Make sure the user is authorised
if (!$user->authorise('core.admin'))
{
return false;
}
// Get the global JAuthentication object.
$authenticate = JAuthentication::getInstance();
$response = $authenticate->authenticate($credentials);
if ($response->status !== JAuthentication::STATUS_SUCCESS)
{
return false;
}
return true;
}
/**
* Does the captive (temporary) file we uploaded before still exist?
*
* @return boolean
*
* @since 3.6.0
*/
public function captiveFileExists()
{
$file = JFactory::getApplication()->getUserState('com_joomlaupdate.temp_file', null);
JLoader::import('joomla.filesystem.file');
if (empty($file) || !JFile::exists($file))
{
return false;
}
return true;
}
/**
* Remove the captive (temporary) file we uploaded before and the .
*
* @return void
*
* @since 3.6.0
*/
public function removePackageFiles()
{
$files = array(
JFactory::getApplication()->getUserState('com_joomlaupdate.temp_file', null),
JFactory::getApplication()->getUserState('com_joomlaupdate.file', null),
);
JLoader::import('joomla.filesystem.file');
foreach ($files as $file)
{
if (JFile::exists($file))
{
if (!@unlink($file))
{
JFile::delete($file);
}
}
}
}
/**
* Gets PHP options.
* TODO: Outsource, build common code base for pre install and pre update check
*
* @return array Array of PHP config options
*
* @since 3.10.0
*/
public function getPhpOptions()
{
$options = array();
/*
* Check the PHP Version. It is already checked in JUpdate.
* A Joomla! Update which is not supported by current PHP
* version is not shown. So this check is actually unnecessary.
*/
$option = new stdClass;
$option->label = JText::sprintf('INSTL_PHP_VERSION_NEWER', $this->getTargetMinimumPHPVersion());
$option->state = $this->isPhpVersionSupported();
$option->notice = null;
$options[] = $option;
// Only check if required PHP version is less than 7.
if (version_compare($this->getTargetMinimumPHPVersion(), '7', '<'))
{
// Check for magic quotes gpc.
$option = new stdClass;
$option->label = JText::_('INSTL_MAGIC_QUOTES_GPC');
$option->state = (ini_get('magic_quotes_gpc') == false);
$option->notice = null;
$options[] = $option;
// Check for register globals.
$option = new stdClass;
$option->label = JText::_('INSTL_REGISTER_GLOBALS');
$option->state = (ini_get('register_globals') == false);
$option->notice = null;
$options[] = $option;
}
// Check for zlib support.
$option = new stdClass;
$option->label = JText::_('INSTL_ZLIB_COMPRESSION_SUPPORT');
$option->state = extension_loaded('zlib');
$option->notice = null;
$options[] = $option;
// Check for XML support.
$option = new stdClass;
$option->label = JText::_('INSTL_XML_SUPPORT');
$option->state = extension_loaded('xml');
$option->notice = null;
$options[] = $option;
// Check for mbstring options.
if (extension_loaded('mbstring'))
{
// Check for default MB language.
$option = new stdClass;
$option->label = JText::_('INSTL_MB_LANGUAGE_IS_DEFAULT');
$option->state = strtolower(ini_get('mbstring.language')) === 'neutral';
$option->notice = $option->state ? null : JText::_('INSTL_NOTICEMBLANGNOTDEFAULT');
$options[] = $option;
// Check for MB function overload.
$option = new stdClass;
$option->label = JText::_('INSTL_MB_STRING_OVERLOAD_OFF');
$option->state = ini_get('mbstring.func_overload') == 0;
$option->notice = $option->state ? null : JText::_('INSTL_NOTICEMBSTRINGOVERLOAD');
$options[] = $option;
}
// Check for a missing native parse_ini_file implementation.
$option = new stdClass;
$option->label = JText::_('INSTL_PARSE_INI_FILE_AVAILABLE');
$option->state = $this->getIniParserAvailability();
$option->notice = null;
$options[] = $option;
// Check for missing native json_encode / json_decode support.
$option = new stdClass;
$option->label = JText::_('INSTL_JSON_SUPPORT_AVAILABLE');
$option->state = function_exists('json_encode') && function_exists('json_decode');
$option->notice = null;
$options[] = $option;
$updateInformation = $this->getUpdateInformation();
// Check if configured database is compatible with Joomla 4
if (version_compare($updateInformation['latest'], '4', '>='))
{
$option = new stdClass;
$option->label = JText::sprintf('INSTL_DATABASE_SUPPORTED', $this->getConfiguredDatabaseType());
$option->state = $this->isDatabaseTypeSupported();
$option->notice = null;
$options[] = $option;
}
// Check if database structure is up to date
$option = new stdClass;
$option->label = JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_DATABASE_STRUCTURE_TITLE');
$option->state = $this->getDatabaseSchemaCheck();
$option->notice = $option->state ? null : JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_DATABASE_STRUCTURE_NOTICE');
$options[] = $option;
return $options;
}
/**
* Gets PHP Settings.
* TODO: Outsource, build common code base for pre install and pre update check
*
* @return array
*
* @since 3.10.0
*/
public function getPhpSettings()
{
$settings = array();
// Check for display errors.
$setting = new stdClass;
$setting->label = JText::_('INSTL_DISPLAY_ERRORS');
$setting->state = (bool) ini_get('display_errors');
$setting->recommended = false;
$settings[] = $setting;
// Check for file uploads.
$setting = new stdClass;
$setting->label = JText::_('INSTL_FILE_UPLOADS');
$setting->state = (bool) ini_get('file_uploads');
$setting->recommended = true;
$settings[] = $setting;
// Only check if required PHP version is less than 7.
if (version_compare($this->getTargetMinimumPHPVersion(), '7', '<'))
{
// Check for magic quotes runtimes.
$setting = new stdClass;
$setting->label = JText::_('INSTL_MAGIC_QUOTES_RUNTIME');
$setting->state = (bool) ini_get('magic_quotes_runtime');
$setting->recommended = false;
$settings[] = $setting;
// Check for safe mode.
$setting = new stdClass;
$setting->label = JText::_('INSTL_SAFE_MODE');
$setting->state = (bool) ini_get('safe_mode');
$setting->recommended = false;
$settings[] = $setting;
}
// Check for output buffering.
$setting = new stdClass;
$setting->label = JText::_('INSTL_OUTPUT_BUFFERING');
$setting->state = (int) ini_get('output_buffering') !== 0;
$setting->recommended = false;
$settings[] = $setting;
// Check for session auto-start.
$setting = new stdClass;
$setting->label = JText::_('INSTL_SESSION_AUTO_START');
$setting->state = (bool) ini_get('session.auto_start');
$setting->recommended = false;
$settings[] = $setting;
// Check for native ZIP support.
$setting = new stdClass;
$setting->label = JText::_('INSTL_ZIP_SUPPORT_AVAILABLE');
$setting->state = function_exists('zip_open') && function_exists('zip_read');
$setting->recommended = true;
$settings[] = $setting;
return $settings;
}
/**
* Returns the configured database type id (mysqli or sqlsrv or ...)
*
* @return string
*
* @since 3.10.0
*/
private function getConfiguredDatabaseType()
{
return JFactory::getApplication()->get('dbtype');
}
/**
* Returns true, if J! version is < 4 or current configured
* database type is compatible with the update.
*
* @return boolean
*
* @since 3.10.0
*/
public function isDatabaseTypeSupported()
{
$updateInformation = $this->getUpdateInformation();
// Check if configured database is compatible with Joomla 4
if (version_compare($updateInformation['latest'], '4', '>='))
{
$unsupportedDatabaseTypes = array('sqlsrv', 'sqlazure');
$currentDatabaseType = $this->getConfiguredDatabaseType();
return !in_array($currentDatabaseType, $unsupportedDatabaseTypes);
}
return true;
}
/**
* Returns true, if current installed php version is compatible with the update.
*
* @return boolean
*
* @since 3.10.0
*/
public function isPhpVersionSupported()
{
return version_compare(PHP_VERSION, $this->getTargetMinimumPHPVersion(), '>=');
}
/**
* Returns the PHP minimum version for the update.
* Returns JOOMLA_MINIMUM_PHP, if there is no information given.
*
* @return string
*
* @since 3.10.0
*/
private function getTargetMinimumPHPVersion()
{
$updateInformation = $this->getUpdateInformation();
return isset($updateInformation['object']->php_minimum) ?
$updateInformation['object']->php_minimum->_data :
JOOMLA_MINIMUM_PHP;
}
/**
* Checks the availability of the parse_ini_file and parse_ini_string functions.
* TODO: Outsource, build common code base for pre install and pre update check
*
* @return boolean True if the method exists.
*
* @since 3.10.0
*/
public function getIniParserAvailability()
{
$disabledFunctions = ini_get('disable_functions');
if (!empty($disabledFunctions))
{
// Attempt to detect them in the disable_functions blacklist.
$disabledFunctions = explode(',', trim($disabledFunctions));
$numberOfDisabledFunctions = count($disabledFunctions);
for ($i = 0; $i < $numberOfDisabledFunctions; $i++)
{
$disabledFunctions[$i] = trim($disabledFunctions[$i]);
}
$result = !in_array('parse_ini_string', $disabledFunctions);
}
else
{
// Attempt to detect their existence; even pure PHP implementations of them will trigger a positive response, though.
$result = function_exists('parse_ini_string');
}
return $result;
}
/**
* Check if database structure is up to date
*
* @return boolean True if ok, false if not.
*
* @since 3.10.0
*/
private function getDatabaseSchemaCheck()
{
JModelLegacy::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_installer/models', 'InstallerModel');
// Get the database model
$model = JModelLegacy::getInstance('Database', 'InstallerModel');
// Check if no default text filters found
if (!$model->getDefaultTextFilters())
{
return false;
}
// Check if database update version does not match CMS version
if (version_compare($model->getUpdateVersion(), JVERSION) != 0)
{
return false;
}
// Get the schema change set
$changeSet = $model->getItems();
$changeSetCheck = $changeSet->check();
// Check if schema errors found
if (!empty($changeSetCheck))
{
return false;
}
// Check if database schema version does not match CMS version
if ($model->getSchemaVersion() != $changeSet->getSchema())
{
return false;
}
// No database problems found
return true;
}
/**
* Gets an array containing all installed extensions, that are not core extensions.
*
* @return array name,version,updateserver
*
* @since 3.10.0
*/
public function getNonCoreExtensions()
{
$db = $this->getDbo();
$query = $db->getQuery(true);
$query->select(
$db->qn('ex.name') . ', ' .
$db->qn('ex.extension_id') . ', ' .
$db->qn('ex.manifest_cache') . ', ' .
$db->qn('ex.type') . ', ' .
$db->qn('ex.folder') . ', ' .
$db->qn('ex.element') . ', ' .
$db->qn('ex.client_id')
)->from(
$db->qn('#__extensions', 'ex')
)->where(
$db->qn('ex.package_id') . ' = 0'
);
$db->setQuery($query);
$rows = $db->loadObjectList();
$rows = array_filter($rows, 'JoomlaupdateModelDefault::isNonCoreExtension');
foreach ($rows as $extension)
{
$decode = json_decode($extension->manifest_cache);
// Remove unused fields so they do not cause javascript errors during pre-update check
unset($decode->description);
unset($decode->copyright);
unset($decode->creationDate);
$this->translateExtensionName($extension);
$extension->version = isset($decode->version)
? $decode->version
: JText::_('COM_JOOMLAUPDATE_PREUPDATE_UNKNOWN_EXTENSION_MANIFESTCACHE_VERSION');
unset($extension->manifest_cache);
$extension->manifest_cache = $decode;
}
return $rows;
}
/**
* Checks if extension is non core extension.
*
* @param object $extension The extension to be checked
*
* @return bool true if extension is not a core extension
*
* @since 3.10.0
*/
private static function isNonCoreExtension($extension)
{
return !\JExtensionHelper::checkIfCoreExtension($extension->type, $extension->element, $extension->client_id, $extension->folder);
}
/**
* Gets an array containing all installed and enabled plugins, that are not core plugins.
*
* @param array $folderFilter Limit the list of plugins to a specific set of folder values
*
* @return array name,version,updateserver
*
* @since 3.10.0
*/
public function getNonCorePlugins($folderFilter = array())
{
$db = $this->getDbo();
$query = $db->getQuery(true);
$query->select(
$db->qn('ex.name') . ', ' .
$db->qn('ex.extension_id') . ', ' .
$db->qn('ex.manifest_cache') . ', ' .
$db->qn('ex.type') . ', ' .
$db->qn('ex.folder') . ', ' .
$db->qn('ex.element') . ', ' .
$db->qn('ex.client_id') . ', ' .
$db->qn('ex.package_id')
)->from(
$db->qn('#__extensions', 'ex')
)->where(
$db->qn('ex.type') . ' = ' . $db->quote('plugin')
)->where(
$db->qn('ex.enabled') . ' = 1'
);
if (count($folderFilter) > 0)
{
$folderFilter = array_map(array($db, 'quote'), $folderFilter);
$query->where($db->qn('folder') . ' IN (' . implode(',', $folderFilter) . ')');
}
$db->setQuery($query);
$rows = $db->loadObjectList();
$rows = array_filter($rows, 'JoomlaupdateModelDefault::isNonCoreExtension');
foreach ($rows as $plugin)
{
$decode = json_decode($plugin->manifest_cache);
// Remove unused fields so they do not cause javascript errors during pre-update check
unset($decode->description);
unset($decode->copyright);
unset($decode->creationDate);
$this->translateExtensionName($plugin);
$plugin->version = isset($decode->version)
? $decode->version
: JText::_('COM_JOOMLAUPDATE_PREUPDATE_UNKNOWN_EXTENSION_MANIFESTCACHE_VERSION');
unset($plugin->manifest_cache);
$plugin->manifest_cache = $decode;
}
return $rows;
}
/**
* Called by controller's fetchExtensionCompatibility, which is called via AJAX.
*
* @param string $extensionID The ID of the checked extension
* @param string $joomlaTargetVersion Target version of Joomla
*
* @return object
*
* @since 3.10.0
*/
public function fetchCompatibility($extensionID, $joomlaTargetVersion)
{
$updateSites = $this->getUpdateSitesInfo($extensionID);
if (empty($updateSites))
{
return (object) array('state' => 2);
}
foreach ($updateSites as $updateSite)
{
if ($updateSite['type'] === 'collection')
{
$updateFileUrls = $this->getCollectionDetailsUrls($updateSite, $joomlaTargetVersion);
foreach ($updateFileUrls as $updateFileUrl)
{
$compatibleVersions = $this->checkCompatibility($updateFileUrl, $joomlaTargetVersion);
// Return the compatible versions
return (object) array('state' => 1, 'compatibleVersions' => $compatibleVersions);
}
}
else
{
$compatibleVersions = $this->checkCompatibility($updateSite['location'], $joomlaTargetVersion);
// Return the compatible versions
return (object) array('state' => 1, 'compatibleVersions' => $compatibleVersions);
}
}
// In any other case we mark this extension as not compatible
return (object) array('state' => 0);
}
/**
* Returns records with update sites and extension information for a given extension ID.
*
* @param int $extensionID The extension ID
*
* @return array
*
* @since 3.10.0
*/
private function getUpdateSitesInfo($extensionID)
{
$db = $this->getDbo();
$query = $db->getQuery(true);
$query->select(
$db->qn('us.type') . ', ' .
$db->qn('us.location') . ', ' .
$db->qn('e.element') . ' AS ' . $db->qn('ext_element') . ', ' .
$db->qn('e.type') . ' AS ' . $db->qn('ext_type') . ', ' .
$db->qn('e.folder') . ' AS ' . $db->qn('ext_folder')
)->from(
$db->qn('#__update_sites', 'us')
)->leftJoin(
$db->qn('#__update_sites_extensions', 'ue')
. ' ON ' . $db->qn('ue.update_site_id') . ' = ' . $db->qn('us.update_site_id')
)->leftJoin(
$db->qn('#__extensions', 'e')
. ' ON ' . $db->qn('e.extension_id') . ' = ' . $db->qn('ue.extension_id')
)->where($db->qn('e.extension_id') . ' = ' . (int) $extensionID);
$db->setQuery($query);
$result = $db->loadAssocList();
if (!is_array($result))
{
return array();
}
return $result;
}
/**
* Method to get details URLs from a colletion update site for given extension and Joomla target version.
*
* @param array $updateSiteInfo The update site and extension information record to process
* @param string $joomlaTargetVersion The Joomla! version to test against,
*
* @return array An array of URLs.
*
* @since 3.10.0
*/
private function getCollectionDetailsUrls($updateSiteInfo, $joomlaTargetVersion)
{
$return = array();
$http = new JHttp;
try
{
$response = $http->get($updateSiteInfo['location']);
}
catch (RuntimeException $e)
{
$response = null;
}
if ($response === null || $response->code !== 200)
{
return $return;
}
$updateSiteXML = simplexml_load_string($response->body);
foreach ($updateSiteXML->extension as $extension)
{
$attribs = new stdClass;
$attribs->element = '';
$attribs->type = '';
$attribs->folder = '';
$attribs->targetplatformversion = '';
foreach ($extension->attributes() as $key => $value)
{
$attribs->$key = (string) $value;
}
if ($attribs->element === $updateSiteInfo['ext_element']
&& $attribs->type === $updateSiteInfo['ext_type']
&& $attribs->folder === $updateSiteInfo['ext_folder']
&& preg_match('/^' . $attribs->targetplatformversion . '/', $joomlaTargetVersion))
{
$return[] = (string) $extension['detailsurl'];
}
}
return $return;
}
/**
* Method to check non core extensions for compatibility.
*
* @param string $updateFileUrl The items update XML url.
* @param string $joomlaTargetVersion The Joomla! version to test against
*
* @return array An array of strings with compatible version numbers
*
* @since 3.10.0
*/
private function checkCompatibility($updateFileUrl, $joomlaTargetVersion)
{
// Get the minimum stability information from com_installer
$minimumStability = JComponentHelper::getParams('com_installer')->get('minimum_stability', JUpdater::STABILITY_STABLE);
$update = new JUpdate;
$update->set('jversion.full', $joomlaTargetVersion);
$update->loadFromXML($updateFileUrl, $minimumStability);
$compatibleVersions = $update->get('compatibleVersions');
// Check if old version of the updater library
if (!isset($compatibleVersions))
{
$downloadUrl = $update->get('downloadurl');
$updateVersion = $update->get('version');
return empty($downloadUrl) || empty($downloadUrl->_data) || empty($updateVersion) ? array() : array($updateVersion->_data);
}
usort($compatibleVersions, 'version_compare');
return $compatibleVersions;
}
/**
* Translates an extension name
*
* @param object &$item The extension of which the name needs to be translated
*
* @return void
*
* @since 3.10.0
*/
protected function translateExtensionName(&$item)
{
// ToDo: Cleanup duplicated code. from com_installer/models/extension.php
$lang = JFactory::getLanguage();
$path = $item->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE;
$extension = $item->element;
$source = JPATH_SITE;
switch ($item->type)
{
case 'component':
$extension = $item->element;
$source = $path . '/components/' . $extension;
break;
case 'module':
$extension = $item->element;
$source = $path . '/modules/' . $extension;
break;
case 'file':
$extension = 'files_' . $item->element;
break;
case 'library':
$extension = 'lib_' . $item->element;
break;
case 'plugin':
$extension = 'plg_' . $item->folder . '_' . $item->element;
$source = JPATH_PLUGINS . '/' . $item->folder . '/' . $item->element;
break;
case 'template':
$extension = 'tpl_' . $item->element;
$source = $path . '/templates/' . $item->element;
}
$lang->load("$extension.sys", JPATH_ADMINISTRATOR, null, false, true)
|| $lang->load("$extension.sys", $source, null, false, true);
$lang->load($extension, JPATH_ADMINISTRATOR, null, false, true)
|| $lang->load($extension, $source, null, false, true);
// Translate the extension name if possible
$item->name = strip_tags(JText::_($item->name));
}
/**
* Checks whether a given template is active
*
* @param string $template The template name to be checked
*
* @return boolean
*
* @since 3.10.4
*/
public function isTemplateActive($template)
{
$db = $this->getDbo();
$query = $db->getQuery(true);
$query->select(
$db->qn(
array(
'id',
'home'
)
)
)->from(
$db->qn('#__template_styles')
)->where(
$db->qn('template') . ' = ' . $db->q($template)
);
$templates = $db->setQuery($query)->loadObjectList();
$home = array_filter(
$templates,
function($value)
{
return $value->home > 0;
}
);
$ids = JArrayHelper::getColumn($templates, 'id');
$menu = false;
if (count($ids))
{
$query = $db->getQuery(true);
$query->select(
'COUNT(*)'
)->from(
$db->qn('#__menu')
)->where(
$db->qn('template_style_id') . ' IN(' . implode(',', $ids) . ')'
);
$menu = $db->setQuery($query)->loadResult() > 0;
}
return $home || $menu;
}
}
models/index.html 0000644 00000000037 15234617001 0010023 0 ustar 00 <!DOCTYPE html><title></title>
views/default/tmpl/complete.php 0000644 00000001304 15234617001 0012617 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
?>
<fieldset>
<legend>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_COMPLETE_HEADING'); ?>
</legend>
<p class="alert alert-success">
<?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_COMPLETE_MESSAGE', JVERSION); ?>
</p>
</fieldset>
<form action="<?php echo JRoute::_('index.php?option=com_joomlaupdate'); ?>" method="post" id="adminForm">
<input type="hidden" name="task" value="" />
<?php echo JHtml::_('form.token'); ?>
</form>
views/default/tmpl/default.php 0000644 00000011336 15234617001 0012441 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/** @var JoomlaupdateViewDefault $this */
JHtml::_('jquery.framework');
JHtml::_('bootstrap.tooltip');
JHtml::_('bootstrap.popover');
JHtml::_('formbehavior.chosen', 'select');
JHtml::_('script', 'com_joomlaupdate/default.js', array('version' => 'auto', 'relative' => true));
JText::script('JYES');
JText::script('JNO');
JText::script('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_NO_COMPATIBILITY_INFORMATION');
JText::script('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_WARNING_UNKNOWN');
JText::script('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_SERVER_ERROR');
JText::script('COM_JOOMLAUPDATE_VIEW_DEFAULT_POTENTIALLY_DANGEROUS_PLUGIN');
JText::script('COM_JOOMLAUPDATE_VIEW_DEFAULT_POTENTIALLY_DANGEROUS_PLUGIN_DESC');
JText::script('COM_JOOMLAUPDATE_VIEW_DEFAULT_POTENTIALLY_DANGEROUS_PLUGIN_LIST');
JText::script('COM_JOOMLAUPDATE_VIEW_DEFAULT_POTENTIALLY_DANGEROUS_PLUGIN_CONFIRM_MESSAGE');
JText::script('COM_JOOMLAUPDATE_VIEW_DEFAULT_HELP');
$latestJoomlaVersion = $this->updateInfo['latest'];
$currentJoomlaVersion = isset($this->updateInfo['installed']) ? $this->updateInfo['installed'] : JVERSION;
JFactory::getDocument()->addScriptDeclaration(
<<<JS
jQuery(document).ready(function($) {
$('#extraction_method').change(function(e){
extractionMethodHandler('#extraction_method', 'row_ftp');
});
$('#upload_method').change(function(e){
extractionMethodHandler('#upload_method', 'upload_ftp');
});
$('button.submit').on('click', function() {
$('div.download_message').show();
});
});
var joomlaTargetVersion = '$latestJoomlaVersion';
var joomlaCurrentVersion = '$currentJoomlaVersion';
JS
);
?>
<div id="joomlaupdate-wrapper">
<?php if ($this->showUploadAndUpdate) : ?>
<?php echo JHtml::_('bootstrap.startTabSet', 'joomlaupdate-tabs', array('active' => $this->shouldDisplayPreUpdateCheck() ? 'pre-update-check' : 'online-update')); ?>
<?php if ($this->shouldDisplayPreUpdateCheck()) : ?>
<?php echo JHtml::_('bootstrap.addTab', 'joomlaupdate-tabs', 'pre-update-check', JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_TAB_PRE_UPDATE_CHECK')); ?>
<?php echo $this->loadTemplate('preupdatecheck'); ?>
<?php echo JHtml::_('bootstrap.endTab'); ?>
<?php endif; ?>
<?php echo JHtml::_('bootstrap.addTab', 'joomlaupdate-tabs', 'online-update', JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_TAB_ONLINE')); ?>
<?php endif; ?>
<form enctype="multipart/form-data" action="index.php" method="post" id="adminForm" class="form-horizontal">
<?php if ($this->selfUpdate) : ?>
<?php // If we have a self update notice to install it first! ?>
<?php JFactory::getApplication()->enqueueMessage(JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_INSTALL_SELF_UPDATE_FIRST'), 'error'); ?>
<?php echo $this->loadTemplate('updatemefirst'); ?>
<?php else : ?>
<?php if ((!isset($this->updateInfo['object']->downloadurl->_data)
&& !$this->updateInfo['hasUpdate'])
|| !$this->getModel()->isDatabaseTypeSupported()
|| !$this->getModel()->isPhpVersionSupported()) : ?>
<?php // If we have no download URL or our PHP version or our DB type is not supported we can't reinstall or update ?>
<?php echo $this->loadTemplate('noupdate'); ?>
<?php elseif (!isset($this->updateInfo['object']->downloadurl->_data)) : ?>
<?php echo $this->loadTemplate('nodownload'); ?>
<?php elseif (!$this->updateInfo['hasUpdate']) : ?>
<?php // If we have no update but we have a downloadurl we can reinstall the core ?>
<?php echo $this->loadTemplate('reinstall'); ?>
<?php else : ?>
<?php // Ok let's show the update template ?>
<?php echo $this->loadTemplate('update'); ?>
<?php endif; ?>
<?php endif; ?>
<input type="hidden" name="task" value="update.download" />
<input type="hidden" name="option" value="com_joomlaupdate" />
<?php echo JHtml::_('form.token'); ?>
</form>
<?php // Only Super Users have access to the Update & Install for obvious security reasons ?>
<?php if ($this->showUploadAndUpdate) : ?>
<?php echo JHtml::_('bootstrap.endTab'); ?>
<?php echo JHtml::_('bootstrap.addTab', 'joomlaupdate-tabs', 'upload-update', JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_TAB_UPLOAD')); ?>
<?php echo $this->loadTemplate('upload'); ?>
<?php echo JHtml::_('bootstrap.endTab'); ?>
<?php echo JHtml::_('bootstrap.endTabSet'); ?>
<?php endif; ?>
<div class="download_message" style="display: none">
<p></p>
<p class="nowarning">
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_DOWNLOAD_IN_PROGRESS'); ?>
</p>
<div class="joomlaupdate_spinner"></div>
</div>
<div id="loading"></div>
</div>
views/default/tmpl/default.xml 0000644 00000000332 15234617001 0012444 0 ustar 00 <?xml version="1.0" encoding="utf-8"?>
<metadata>
<layout title="COM_JOOMLAUPDATE_DEFAULT_VIEW_DEFAULT_TITLE">
<message>
<![CDATA[COM_JOOMLAUPDATE_DEFAULT_VIEW_DEFAULT_DESC]]>
</message>
</layout>
</metadata>
views/default/tmpl/default_nodownload.php 0000644 00000002654 15234617001 0014670 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/** @var JoomlaupdateViewDefault $this */
?>
<fieldset>
<?php if (!$this->getModel()->isDatabaseTypeSupported()) : ?>
<legend>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_DB_NOT_SUPPORTED'); ?>
</legend>
<p>
<?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_DEFAULT_DB_NOT_SUPPORTED_DESC', $this->updateInfo['latest']); ?>
</p>
<?php endif; ?>
<?php if (!$this->getModel()->isPhpVersionSupported()) : ?>
<legend>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_PHP_VERSION_NOT_SUPPORTED'); ?>
</legend>
<p>
<?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_DEFAULT_PHP_VERSION_NOT_SUPPORTED_DESC', $this->updateInfo['latest']); ?>
</p>
<?php endif; ?>
<?php if (!isset($this->updateInfo['object']->downloadurl->_data) && $this->updateInfo['installed'] < $this->updateInfo['latest'] && $this->getModel()->isPhpVersionSupported() && $this->getModel()->isDatabaseTypeSupported()) : ?>
<legend>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_NO_DOWNLOAD_URL'); ?>
</legend>
<p>
<?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_DEFAULT_NO_DOWNLOAD_URL_DESC', $this->updateInfo['latest']); ?>
</p>
<?php endif; ?>
</fieldset>
views/default/tmpl/default_noupdate.php 0000644 00000001205 15234617001 0014332 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/** @var JoomlaupdateViewDefault $this */
?>
<fieldset>
<legend>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_NOUPDATES'); ?>
</legend>
<p>
<?php echo JText::sprintf($this->langKey, $this->updateSourceKey); ?>
</p>
<div class="alert alert-success">
<?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_DEFAULT_NOUPDATESNOTICE', JVERSION); ?>
</div>
</fieldset>
views/default/tmpl/default_preupdatecheck.php 0000644 00000023310 15234617001 0015503 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/** @var JoomlaupdateViewDefault $this */
// JText::script doesn't have a sprintf equivalent so work around this
JFactory::getDocument()->addScriptDeclaration("var COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_SHOW_MORE_COMPATIBILITY_INFORMATION = '" . JText::sprintf('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_SHOW_MORE_COMPATIBILITY_INFORMATION', '<span class="icon-chevron-right small"></span>', true) . "';");
JFactory::getDocument()->addScriptDeclaration("var COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_SHOW_LESS_COMPATIBILITY_INFORMATION = '" . JText::sprintf('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_SHOW_LESS_COMPATIBILITY_INFORMATION', '<span class="icon-chevron-up small"></span>', true) . "';");
JFactory::getDocument()->addScriptOptions('nonCoreCriticalPlugins', array_values($this->nonCoreCriticalPlugins));
$compatibilityTypes = array(
'COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_RUNNING_PRE_UPDATE_CHECKS' => array(
'class' => 'label-default',
'notes' => 'COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_RUNNING_PRE_UPDATE_CHECKS_NOTES',
'group' => 0
),
'COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_PRE_UPDATE_CHECKS_FAILED' => array(
'class' => 'label-important',
'notes' => 'COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_PRE_UPDATE_CHECKS_FAILED_NOTES',
'group' => 4
),
'COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_UPDATE_SERVER_OFFERS_NO_COMPATIBLE_VERSION' => array(
'class' => 'label-important',
'notes' => 'COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_UPDATE_SERVER_OFFERS_NO_COMPATIBLE_VERSION_NOTES',
'group' => 1
),
'COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_REQUIRING_UPDATES_TO_BE_COMPATIBLE' => array(
'class' => 'label-warning',
'notes' => 'COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_REQUIRING_UPDATES_TO_BE_COMPATIBLE_NOTES',
'group' => 2
),
'COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_PROBABLY_COMPATIBLE' => array(
'class' => 'label-success',
'notes' => 'COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_PROBABLY_COMPATIBLE_NOTES',
'group' => 3
)
);
if (version_compare($this->updateInfo['latest'], '4', '>=') && $this->isBackendTemplateIsis === false)
{
JFactory::getApplication()->enqueueMessage(
JText::_(
'COM_JOOMLAUPDATE_VIEW_DEFAULT_NON_CORE_BACKEND_TEMPLATE_USED_NOTICE'
),
'info'
);
}
?>
<h2>
<?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_DEFAULT_PREUPDATE_CHECK', $this->updateInfo['latest']); ?>
</h2>
<p>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXPLANATION_AND_LINK_TO_DOCS'); ?>
</p>
<div class="row-fluid">
<fieldset class="span6 ">
<?php $labelClass = 'success'; ?>
<?php foreach ($this->phpOptions as $option) : ?>
<?php if (!$option->state) : ?>
<?php $labelClass = 'important'; ?>
<?php break; ?>
<?php endif; ?>
<?php endforeach; ?>
<legend class="label label-<?php echo $labelClass;?>">
<h3>
<?php echo $labelClass === 'important' ? JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_REQUIRED_SETTINGS_WARNING') : JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_REQUIRED_SETTINGS_PASSED'); ?>
<div class="settingstoggle" data-state="closed">
<?php echo JText::sprintf(
'COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_SHOW_MORE_COMPATIBILITY_INFORMATION',
'<span class="icon-chevron-right small"></span>'
); ?>
</div>
</h3>
</legend>
<div class="settingsInfo hidden" >
<table class="table">
<thead>
<tr>
<th>
<?php echo JText::_('COM_JOOMLAUPDATE_PREUPDATE_HEADING_REQUIREMENT'); ?>
</th>
<th>
<?php echo JText::_('COM_JOOMLAUPDATE_PREUPDATE_HEADING_CHECKED'); ?>
</th>
</tr>
</thead>
<tbody>
<?php foreach ($this->phpOptions as $option) : ?>
<tr>
<td>
<?php echo $option->label; ?>
</td>
<td>
<span class="label label-<?php echo $option->state ? 'success' : 'important'; ?>">
<?php echo JText::_($option->state ? 'JYES' : 'JNO'); ?>
<?php if ($option->notice) : ?>
<span class="icon-info icon-white hasTooltip" title="<?php echo $option->notice; ?>"></span>
<?php endif; ?>
</span>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</fieldset>
<fieldset class="span6">
<?php $labelClass = 'success'; ?>
<?php foreach ($this->phpSettings as $setting) : ?>
<?php if ($setting->state !== $setting->recommended) : ?>
<?php $labelClass = 'warning'; ?>
<?php break; ?>
<?php endif; ?>
<?php endforeach; ?>
<legend class="label label-<?php echo $labelClass; ?>">
<h3>
<?php echo $labelClass === 'warning' ? JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_RECOMMENDED_SETTINGS_WARNING') : JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_RECOMMENDED_SETTINGS_PASSED'); ?>
<div class="settingstoggle" data-state="closed">
<?php echo JText::sprintf(
'COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_SHOW_MORE_COMPATIBILITY_INFORMATION',
'<span class="icon-chevron-right small"></span>'
); ?>
</div>
</h3>
</legend>
<div class="settingsInfo hidden" >
<p>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_RECOMMENDED_SETTINGS_DESC'); ?>
</p>
<table class="table">
<thead>
<tr>
<th>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_DIRECTIVE'); ?>
</th>
<th>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_RECOMMENDED'); ?>
</th>
<th>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_ACTUAL'); ?>
</th>
</tr>
</thead>
<tbody>
<?php foreach ($this->phpSettings as $setting) : ?>
<tr>
<td>
<?php echo $setting->label; ?>
</td>
<td>
<?php echo JText::_($setting->recommended ? 'JON' : 'JOFF'); ?>
</td>
<td>
<span class="label label-<?php echo ($setting->state === $setting->recommended) ? 'success' : 'warning'; ?>">
<?php echo JText::_($setting->state ? 'JON' : 'JOFF'); ?>
</span>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</fieldset>
</div>
<?php if (!empty($this->nonCoreExtensions)) : ?>
<div>
<h3>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS'); ?>
</h3>
<?php foreach ($compatibilityTypes as $compatibilityType => $compatibilityData) : ?>
<?php $compatibilityDisplayClass = $compatibilityData['class']; ?>
<?php $compatibilityDisplayNotes = $compatibilityData['notes']; ?>
<?php $compatibilityTypeGroup = $compatibilityData['group']; ?>
<fieldset id="compatibilitytype<?php echo $compatibilityTypeGroup;?>" class="span12 compatibilitytypes">
<legend class="label <?php echo $compatibilityDisplayClass; ?>">
<h3>
<?php if ($compatibilityType !== "COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_RUNNING_PRE_UPDATE_CHECKS") : ?>
<div class="compatibilitytoggle" data-state="closed">
<?php echo JText::sprintf(
'COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_SHOW_MORE_COMPATIBILITY_INFORMATION',
'<span class="icon-chevron-right small"></span>'
); ?>
</div>
<?php endif; ?>
<?php echo JText::_($compatibilityType); ?>
</h3>
</legend>
<div class="compatibilityNotes">
<?php echo JText::_($compatibilityDisplayNotes); ?>
</div>
<table class="table">
<thead class="row-fluid">
<tr>
<th class="exname span8">
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_NAME'); ?>
</th>
<th class="extype span4">
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_TYPE'); ?>
</th>
<th class="instver hidden">
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_INSTALLED_VERSION'); ?>
</th>
<th class="upcomp hidden">
<?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_COMPATIBLE_WITH_JOOMLA_VERSION', isset($this->updateInfo['installed']) ? $this->updateInfo['installed'] : JVERSION); ?>
</th>
<th class="currcomp hidden">
<?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSION_COMPATIBLE_WITH_JOOMLA_VERSION', $this->updateInfo['latest']); ?>
</th>
</tr>
</thead>
<tbody class="row-fluid">
<?php // Only include this row once since the javascript moves the results into the right place ?>
<?php if ($compatibilityType == "COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_RUNNING_PRE_UPDATE_CHECKS") : ?>
<?php foreach ($this->nonCoreExtensions as $extension) : ?>
<tr>
<td class="exname span8">
<?php echo JText::_($extension->name); ?>
</td>
<td class="extype span4">
<?php echo JText::_('COM_INSTALLER_TYPE_' . strtoupper($extension->type)); ?>
</td>
<td class="instver hidden">
<?php echo $extension->version; ?>
</td>
<td id="available-version-<?php echo $extension->extension_id; ?>" class="currcomp hidden"/>
<td
class="extension-check upcomp hidden"
data-extension-id="<?php echo $extension->extension_id; ?>"
data-extension-current-version="<?php echo $extension->version; ?>"
>
<img src="../media/jui/images/ajax-loader.gif" />
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</fieldset>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="row-fluid">
<div class="span6">
<h3>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS'); ?>
</h3>
<div class="alert alert-no-items">
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_EXTENSIONS_NONE'); ?>
</div>
</div>
</div>
<?php endif; ?>
views/default/tmpl/default_reinstall.php 0000644 00000007272 15234617001 0014522 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/** @var JoomlaupdateViewDefault $this */
?>
<fieldset>
<legend>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_NOUPDATES'); ?>
</legend>
<p>
<?php echo JText::sprintf($this->langKey, $this->updateSourceKey); ?>
</p>
<div class="alert alert-success">
<?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_DEFAULT_NOUPDATESNOTICE', JVERSION); ?>
</div>
<?php if (is_object($this->updateInfo['object']) && ($this->updateInfo['object'] instanceof JUpdate)) : ?>
<table class="table table-striped">
<tbody>
<tr>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_PACKAGE_REINSTALL'); ?>
</td>
<td>
<a href="<?php echo $this->updateInfo['object']->downloadurl->_data; ?>" target="_blank" rel="noopener noreferrer">
<?php echo $this->updateInfo['object']->downloadurl->_data; ?>
<span class="icon-out-2" aria-hidden="true"></span>
<span class="element-invisible"><?php echo JText::_('JBROWSERTARGET_NEW'); ?></span>
</a>
</td>
</tr>
<?php if (isset($this->updateInfo['object']->get('infourl')->_data)
&& isset($this->updateInfo['object']->get('infourl')->title)) : ?>
<tr>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_INFOURL'); ?>
</td>
<td>
<a href="<?php echo $this->updateInfo['object']->get('infourl')->_data; ?>" target="_blank" rel="noopener noreferrer">
<?php echo $this->updateInfo['object']->get('infourl')->title; ?>
<span class="icon-out-2" aria-hidden="true"></span>
<span class="element-invisible"><?php echo JText::_('JBROWSERTARGET_NEW'); ?></span>
</a>
</td>
</tr>
<?php endif; ?>
<tr>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_METHOD'); ?>
</td>
<td>
<?php echo $this->methodSelect; ?>
</td>
</tr>
<tr id="row_ftp_hostname" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_HOSTNAME'); ?>
</td>
<td>
<input type="text" name="ftp_host" value="<?php echo $this->ftp['host']; ?>" />
</td>
</tr>
<tr id="row_ftp_port" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_PORT'); ?>
</td>
<td>
<input type="text" name="ftp_port" value="<?php echo $this->ftp['port']; ?>" />
</td>
</tr>
<tr id="row_ftp_username" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_USERNAME'); ?>
</td>
<td>
<input type="text" name="ftp_user" value="<?php echo $this->ftp['username']; ?>" />
</td>
</tr>
<tr id="row_ftp_password" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_PASSWORD'); ?>
</td>
<td>
<input type="password" name="ftp_pass" value="<?php echo $this->ftp['password']; ?>" />
</td>
</tr>
<tr id="row_ftp_directory" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_DIRECTORY'); ?>
</td>
<td>
<input type="text" name="ftp_root" value="<?php echo $this->ftp['directory']; ?>" />
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>
</td>
<td>
<button class="btn btn-warning" type="submit">
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_INSTALLAGAIN'); ?>
</button>
</td>
</tr>
</tfoot>
</table>
<?php endif; ?>
</fieldset>
views/default/tmpl/default_update.php 0000644 00000020075 15234617001 0014003 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/** @var JoomlaupdateViewDefault $this */
?>
<fieldset>
<legend>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATEFOUND'); ?>
</legend>
<p>
<?php echo JText::sprintf($this->langKey, $this->updateSourceKey); ?>
</p>
<table class="table table-striped">
<tbody>
<tr>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_INSTALLED'); ?>
</td>
<td>
<?php echo '‎' . $this->updateInfo['installed']; ?>
</td>
</tr>
<tr>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_LATEST'); ?>
</td>
<td>
<?php echo '‎' . $this->updateInfo['latest']; ?>
</td>
</tr>
<tr>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_PACKAGE'); ?>
</td>
<td>
<a href="<?php echo $this->updateInfo['object']->downloadurl->_data; ?>" target="_blank" rel="noopener noreferrer">
<?php echo $this->updateInfo['object']->downloadurl->_data; ?>
<span class="icon-out-2" aria-hidden="true"></span>
<span class="element-invisible"><?php echo JText::_('JBROWSERTARGET_NEW'); ?></span>
</a>
</td>
</tr>
<?php if (isset($this->updateInfo['object']->get('infourl')->_data)
&& isset($this->updateInfo['object']->get('infourl')->title)) : ?>
<tr>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_INFOURL'); ?>
</td>
<td>
<a href="<?php echo $this->updateInfo['object']->get('infourl')->_data; ?>" target="_blank" rel="noopener noreferrer">
<?php echo $this->updateInfo['object']->get('infourl')->title; ?>
<span class="icon-out-2" aria-hidden="true"></span>
<span class="element-invisible"><?php echo JText::_('JBROWSERTARGET_NEW'); ?></span>
</a>
</td>
</tr>
<?php endif; ?>
<?php // Hide FTP settings when updating to Joomla 4 given that the supporting code has been dropped there ?>
<?php if (version_compare($this->updateInfo['latest'], '4', '<')) : ?>
<tr>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_METHOD'); ?>
</td>
<td>
<?php echo $this->methodSelect; ?>
</td>
</tr>
<tr id="row_ftp_hostname" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_HOSTNAME'); ?>
</td>
<td>
<input type="text" name="ftp_host" value="<?php echo $this->ftp['host']; ?>" />
</td>
</tr>
<tr id="row_ftp_port" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_PORT'); ?>
</td>
<td>
<input type="text" name="ftp_port" value="<?php echo $this->ftp['port']; ?>" />
</td>
</tr>
<tr id="row_ftp_username" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_USERNAME'); ?>
</td>
<td>
<input type="text" name="ftp_user" value="<?php echo $this->ftp['username']; ?>" />
</td>
</tr>
<tr id="row_ftp_password" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_PASSWORD'); ?>
</td>
<td>
<input type="password" name="ftp_pass" value="<?php echo $this->ftp['password']; ?>" />
</td>
</tr>
<tr id="row_ftp_directory" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_DIRECTORY'); ?>
</td>
<td>
<input type="text" name="ftp_root" value="<?php echo $this->ftp['directory']; ?>" />
</td>
</tr>
<?php endif; ?>
</tbody>
<tfoot>
<tr id="preupdateCheckWarning">
<td colspan="2">
<div class="alert">
<h4 class="alert-heading">
<?php echo JText::_('WARNING'); ?>
</h4>
<div class="alert-message">
<div class="preupdateCheckIncomplete">
<?php echo JText::_('COM_JOOMLAUPDATE_PREUPDATE_CHECK_NOT_COMPLETE'); ?>
</div>
</div>
</div>
</td>
</tr>
<tr id="preupdateCheckCompleteProblems" class="hidden">
<td colspan="2">
<div class="alert">
<h4 class="alert-heading">
<?php echo JText::_('WARNING'); ?>
</h4>
<div class="alert-message">
<div class="preupdateCheckComplete">
<?php echo JText::_('COM_JOOMLAUPDATE_PREUPDATE_CHECK_COMPLETED_YOU_HAVE_DANGEROUS_PLUGINS'); ?>
</div>
</div>
</div>
</td>
</tr>
<tr id="preupdateconfirmation" >
<td colspan="2">
<label class="preupdateconfirmation_label label label-warning span12">
<h3>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_NON_CORE_PLUGIN_BEING_CHECKED'); ?>
</h3>
</label>
</td>
</tr>
<tr id="preupdatecheckheadings">
<td colspan="2">
<table class="table table-striped">
<thead>
<th>
<?php echo JText::_('COM_INSTALLER_TYPE_PLUGIN'); ?>
</th>
<th>
<?php echo JText::_('COM_INSTALLER_TYPE_PACKAGE'); ?>
</th>
<th>
<?php echo JText::_('COM_INSTALLER_AUTHOR_INFORMATION'); ?>
</th>
<th>
<?php echo JText::_('COM_JOOMLAUPDATE_PREUPDATE_CHECK_EXTENSION_AUTHOR_URL'); ?>
</th>
</thead>
<tbody>
<?php foreach ($this->nonCoreCriticalPlugins as $nonCoreCriticalPlugin) : ?>
<tr id='plg_<?php echo $nonCoreCriticalPlugin->extension_id ?>'>
<td>
<?php echo JText::_($nonCoreCriticalPlugin->name); ?>
</td>
<?php if ($nonCoreCriticalPlugin->package_id > 0) : ?>
<?php foreach ($this->nonCoreExtensions as $nonCoreExtension) : ?>
<?php if ($nonCoreCriticalPlugin->package_id == $nonCoreExtension->extension_id) : ?>
<td>
<?php echo $nonCoreExtension->name; ?>
</td>
<?php endif; ?>
<?php endforeach; ?>
<?php else : ?>
<td/>
<?php endif; ?>
<td>
<?php if (isset($nonCoreCriticalPlugin->manifest_cache->author)) : ?>
<?php echo JText::_($nonCoreCriticalPlugin->manifest_cache->author); ?>
<?php elseif ($nonCoreCriticalPlugin->package_id > 0) : ?>
<?php foreach ($this->nonCoreExtensions as $nonCoreExtension) : ?>
<?php if ($nonCoreCriticalPlugin->package_id == $nonCoreExtension->extension_id) : ?>
<td>
<?php echo $nonCoreExtension->name; ?>
</td>
<?php endif; ?>
<?php endforeach; ?>
<?php endif; ?>
</td>
<td>
<?php $authorURL = ''; ?>
<?php if (isset($nonCoreCriticalPlugin->manifest_cache->authorUrl)) : ?>
<?php $authorURL = $nonCoreCriticalPlugin->manifest_cache->authorUrl; ?>
<?php elseif ($nonCoreCriticalPlugin->package_id > 0) : ?>
<?php foreach ($this->nonCoreExtensions as $nonCoreExtension) : ?>
<?php if ($nonCoreCriticalPlugin->package_id == $nonCoreExtension->extension_id) : ?>
<?php $authorURL = $nonCoreExtension->manifest_cache->authorUrl; ?>
<?php endif; ?>
<?php endforeach; ?>
<?php endif; ?>
<?php if (!empty($authorURL)) : ?>
<a href="<?php echo $authorURL; ?>" target="_blank">
<?php echo $authorURL; ?>
<span class="icon-out-2" aria-hidden="true"></span>
<span class="element-invisible">
<?php echo JText::_('JBROWSERTARGET_NEW'); ?>
</span>
</a>
<?php endif;?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</td>
</tr>
<tr id="preupdatecheckbox">
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_NON_CORE_PLUGIN_CONFIRMATION'); ?>
</td>
<td>
<input type="checkbox" id="noncoreplugins" name="noncoreplugins" value="1" required aria-required="true" />
</td>
</tr>
<tr>
<td>
</td>
<td>
<button class="btn btn-primary disabled submitupdate" type="submit" disabled>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_INSTALLUPDATE'); ?>
</button>
</td>
</tr>
</tfoot>
</table>
</fieldset>
views/default/tmpl/default_updatemefirst.php 0000644 00000001021 15234617001 0015363 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/** @var JoomlaupdateViewDefault $this */
?>
<fieldset>
<legend>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_NO_LIVE_UPDATE'); ?>
</legend>
<p>
<?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_DEFAULT_NO_LIVE_UPDATE_DESC'); ?>
</p>
</fieldset>
views/default/tmpl/default_upload.php 0000644 00000016115 15234617001 0014005 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/** @var JoomlaupdateViewDefault $this */
$errSelectPackage = JText::_('COM_INSTALLER_MSG_INSTALL_PLEASE_SELECT_A_PACKAGE', true);
$errPackageTooBig = JText::_('COM_INSTALLER_MSG_WARNINGS_UPLOADFILETOOBIG', true);
$txtPackageSize = JText::_('JGLOBAL_SELECTED_UPLOAD_FILE_SIZE', true);
$js = <<< JS
Joomla.submitbuttonUpload = function() {
var form = document.getElementById("uploadForm");
// do field validation
if (form.install_package.value == "") {
alert("$errSelectPackage");
}
else if (form.install_package.files[0].size > form.max_upload_size.value) {
alert("$errPackageTooBig");
}
else
{
jQuery("#loading").css("display", "block");
form.submit();
}
};
Joomla.installpackageChange = function() {
var form = document.getElementById('uploadForm');
var fileSize = form.install_package.files[0].size;
var fileSizeMB = fileSize * 1.0 / 1024.0 / 1024.0;
var fileSizeText = "$txtPackageSize";
var fileSizeElement = document.getElementById('file_size');
var warningElement = document.getElementById('max_upload_size_warn');
if (form.install_package.value == '') {
fileSizeElement.classList.add('hidden');
warningElement .classList.add('hidden');
}
else if (fileSize) {
fileSizeElement.classList.remove('hidden');
fileSizeElement.innerHTML = fileSizeText.replace('%s', fileSizeMB.toFixed(2) + ' MB');
if (fileSize > form.max_upload_size.value) {
warningElement .classList.remove('hidden');
} else {
warningElement .classList.add('hidden');
}
}
};
// Add spindle-wheel for installations:
jQuery(document).ready(function($) {
var outerDiv = $("#joomlaupdate-wrapper");
$("#loading")
.css("top", outerDiv.position().top - $(window).scrollTop())
.css("left", "0")
.css("width", "100%")
.css("height", "100%")
.css("display", "none")
.css("margin-top", "-10px");
});
JS;
JFactory::getDocument()->addScriptDeclaration($js);
$ajaxLoaderImage = JHtml::_('image', 'jui/ajax-loader.gif', '', null, true, true);
$css = <<< CSS
#loading {
background: rgba(255, 255, 255, .8) url('$ajaxLoaderImage') 50% 15% no-repeat;
position: fixed;
opacity: 1;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity = 80);
filter: alpha(opacity = 80);
overflow: hidden;
}
CSS;
JFactory::getDocument()->addStyleDeclaration($css);
?>
<div class="alert alert-info">
<p>
<span class="icon icon-info" aria-hidden="true"></span>
<?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_DEFAULT_UPLOAD_INTRO', 'https://downloads.joomla.org/latest'); ?>
</p>
</div>
<?php if (count($this->warnings)) : ?>
<fieldset>
<legend>
<?php echo JText::_('COM_INSTALLER_SUBMENU_WARNINGS'); ?>
</legend>
<?php $i = 0; ?>
<?php echo JHtml::_('bootstrap.startAccordion', 'warnings', array('active' => 'warning' . $i)); ?>
<?php foreach ($this->warnings as $message) : ?>
<?php echo JHtml::_('bootstrap.addSlide', 'warnings', $message['message'], 'warning' . ($i++)); ?>
<?php echo $message['description']; ?>
<?php echo JHtml::_('bootstrap.endSlide'); ?>
<?php endforeach; ?>
<?php echo JHtml::_('bootstrap.addSlide', 'warnings', JText::_('COM_INSTALLER_MSG_WARNINGFURTHERINFO'), 'furtherinfo'); ?>
<?php echo JText::_('COM_INSTALLER_MSG_WARNINGFURTHERINFODESC'); ?>
<?php echo JHtml::_('bootstrap.endSlide'); ?>
<?php echo JHtml::_('bootstrap.endAccordion'); ?>
</fieldset>
<?php endif; ?>
<form enctype="multipart/form-data" action="index.php" method="post" id="uploadForm" class="form-horizontal">
<fieldset class="uploadform">
<legend><?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_TAB_UPLOAD'); ?></legend>
<table class="table table-striped">
<tbody>
<tr>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPLOAD_PACKAGE_FILE'); ?>
</td>
<td>
<input class="input_box" id="install_package" name="install_package" type="file" size="57" accept=".zip,application/zip" onchange="Joomla.installpackageChange()" /><br>
<?php $maxSizeBytes = JUtility::getMaxUploadSize(); ?>
<?php $maxSize = JHtml::_('number.bytes', $maxSizeBytes); ?>
<input id="max_upload_size" name="max_upload_size" type="hidden" value="<?php echo $maxSizeBytes; ?>" />
<div class="small"><?php echo JText::sprintf('JGLOBAL_MAXIMUM_UPLOAD_SIZE_LIMIT', '‎' . $maxSize); ?></div>
<div class="small hidden" id="file_size" name="file_size"><?php echo JText::sprintf('JGLOBAL_SELECTED_UPLOAD_FILE_SIZE', '‎' . ''); ?></div>
<div class="alert alert-warning hidden" id="max_upload_size_warn">
<?php echo JText::_('COM_INSTALLER_MSG_WARNINGS_UPLOADFILETOOBIG'); ?>
</div>
</td>
</tr>
<tr>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_METHOD'); ?>
</td>
<td>
<?php echo $this->methodSelectUpload; ?>
</td>
</tr>
<tr id="upload_ftp_notice" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_NOTICE'); ?>
</td>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_NOTICE_MESSAGE'); ?>
</td>
</tr>
<tr id="upload_ftp_hostname" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_HOSTNAME'); ?>
</td>
<td>
<input type="text" name="ftp_host" value="<?php echo $this->ftp['host']; ?>" />
</td>
</tr>
<tr id="upload_ftp_port" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_PORT'); ?>
</td>
<td>
<input type="text" name="ftp_port" value="<?php echo $this->ftp['port']; ?>" />
</td>
</tr>
<tr id="upload_ftp_username" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_USERNAME'); ?>
</td>
<td>
<input type="text" name="ftp_user" value="<?php echo $this->ftp['username']; ?>" />
</td>
</tr>
<tr id="upload_ftp_password" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_PASSWORD'); ?>
</td>
<td>
<input type="password" name="ftp_pass" value="<?php echo $this->ftp['password']; ?>" />
</td>
</tr>
<tr id="upload_ftp_directory" <?php echo $this->ftpFieldsDisplay; ?>>
<td>
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_DIRECTORY'); ?>
</td>
<td>
<input type="text" name="ftp_root" value="<?php echo $this->ftp['directory']; ?>" />
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>
</td>
<td>
<button class="btn btn-primary" type="button" onclick="Joomla.submitbuttonUpload()"><?php echo JText::_('COM_INSTALLER_UPLOAD_AND_INSTALL'); ?></button>
</td>
</tr>
</tfoot>
</table>
</fieldset>
<input type="hidden" name="task" value="update.upload" />
<input type="hidden" name="option" value="com_joomlaupdate" />
<?php echo JHtml::_('form.token'); ?>
</form>
views/default/tmpl/index.html 0000644 00000000037 15234617001 0012275 0 ustar 00 <!DOCTYPE html><title></title>
views/default/index.html 0000644 00000000037 15234617001 0011321 0 ustar 00 <!DOCTYPE html><title></title>
views/default/view.html.php 0000644 00000017227 15234617001 0011763 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/**
* Joomla! Update's Default View
*
* @since 2.5.4
*/
class JoomlaupdateViewDefault extends JViewLegacy
{
/**
* An array with the Joomla! update information.
*
* @var array
*
* @since 3.6.0
*/
protected $updateInfo = null;
/**
* The form field for the extraction select
*
* @var string
*
* @since 3.6.0
*/
protected $methodSelect = null;
/**
* The form field for the upload select
*
* @var string
*
* @since 3.6.0
*/
protected $methodSelectUpload = null;
/**
* PHP options.
*
* @var array Array of PHP config options
*
* @since 3.10.0
*/
protected $phpOptions = null;
/**
* PHP settings.
*
* @var array Array of PHP settings
*
* @since 3.10.0
*/
protected $phpSettings = null;
/**
* Non Core Extensions.
*
* @var array Array of Non-Core-Extensions
*
* @since 3.10.0
*/
protected $nonCoreExtensions = null;
/**
* Renders the view
*
* @param string $tpl Template name
*
* @return void
*
* @since 2.5.4
*/
public function display($tpl = null)
{
// Get data from the model.
$this->state = $this->get('State');
// Load useful classes.
/** @var JoomlaupdateModelDefault $model */
$model = $this->getModel();
$this->loadHelper('select');
// Assign view variables.
$this->ftp = $model->getFTPOptions();
$defaultMethod = $this->ftp['enabled'] ? 'hybrid' : 'direct';
$this->updateInfo = $model->getUpdateInformation();
$this->methodSelect = JoomlaupdateHelperSelect::getMethods($defaultMethod);
$this->methodSelectUpload = JoomlaupdateHelperSelect::getMethods($defaultMethod, 'method', 'upload_method');
// Get results of pre update check evaluations
$this->phpOptions = $model->getPhpOptions();
$this->phpSettings = $model->getPhpSettings();
$this->nonCoreExtensions = $model->getNonCoreExtensions();
$this->isBackendTemplateIsis = (bool) $model->isTemplateActive('isis');
// Disable the critical plugins check for non-major updates.
$this->nonCoreCriticalPlugins = array();
if (version_compare($this->updateInfo['latest'], '4', '>='))
{
$this->nonCoreCriticalPlugins = $model->getNonCorePlugins(array('system','user','authentication','actionlog','twofactorauth'));
}
// Set the toolbar information.
JToolbarHelper::title(JText::_('COM_JOOMLAUPDATE_OVERVIEW'), 'loop install');
JToolbarHelper::custom('update.purge', 'loop', 'loop', 'COM_JOOMLAUPDATE_TOOLBAR_CHECK', false);
// Add toolbar buttons.
if (JFactory::getUser()->authorise('core.admin'))
{
JToolbarHelper::preferences('com_joomlaupdate');
}
JToolbarHelper::divider();
JToolbarHelper::help('JHELP_COMPONENTS_JOOMLA_UPDATE');
if (!is_null($this->updateInfo['object']))
{
// Show the message if an update is found.
JFactory::getApplication()->enqueueMessage(JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATE_NOTICE'), 'warning');
}
$this->ftpFieldsDisplay = $this->ftp['enabled'] ? '' : 'style = "display: none"';
$params = JComponentHelper::getParams('com_joomlaupdate');
switch ($params->get('updatesource', 'default'))
{
// "Minor & Patch Release for Current version AND Next Major Release".
case 'next':
$this->langKey = 'COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATES_INFO_NEXT';
$this->updateSourceKey = JText::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_NEXT');
break;
// "Testing"
case 'testing':
$this->langKey = 'COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATES_INFO_TESTING';
$this->updateSourceKey = JText::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_TESTING');
break;
// "Custom"
case 'custom':
$this->langKey = 'COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATES_INFO_CUSTOM';
$this->updateSourceKey = JText::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_CUSTOM');
break;
/**
* "Minor & Patch Release for Current version (recommended and default)".
* The commented "case" below are for documenting where 'default' and legacy options falls
* case 'default':
* case 'lts':
* case 'sts':
* case 'nochange':
*/
default:
$this->langKey = 'COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATES_INFO_DEFAULT';
$this->updateSourceKey = JText::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_DEFAULT');
}
$this->warnings = array();
/** @var InstallerModelWarnings $warningsModel */
$warningsModel = $this->getModel('warnings');
if (is_object($warningsModel) && $warningsModel instanceof JModelLegacy)
{
$language = JFactory::getLanguage();
$language->load('com_installer', JPATH_ADMINISTRATOR, 'en-GB', false, true);
$language->load('com_installer', JPATH_ADMINISTRATOR, null, true);
$this->warnings = $warningsModel->getItems();
}
$this->selfUpdate = $this->checkForSelfUpdate();
// Only Super Users have access to the Update & Install for obvious security reasons
$this->showUploadAndUpdate = JFactory::getUser()->authorise('core.admin');
// Remove temporary files
$model->removePackageFiles();
// Render the view.
parent::display($tpl);
}
/**
* Makes sure that the Joomla! Update Component Update is in the database and check if there is a new version.
*
* @return boolean True if there is an update else false
*
* @since 3.6.3
*/
private function checkForSelfUpdate()
{
$db = JFactory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('extension_id'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('com_joomlaupdate'));
$db->setQuery($query);
try
{
// Get the component extension ID
$joomlaUpdateComponentId = $db->loadResult();
}
catch (RuntimeException $e)
{
// Something is wrong here!
$joomlaUpdateComponentId = 0;
JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error');
}
// Try the update only if we have an extension id
if ($joomlaUpdateComponentId != 0)
{
// Always force to check for an update!
$cache_timeout = 0;
$updater = JUpdater::getInstance();
$updater->findUpdates($joomlaUpdateComponentId, $cache_timeout, JUpdater::STABILITY_STABLE);
// Fetch the update information from the database.
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__updates'))
->where($db->quoteName('extension_id') . ' = ' . $db->quote($joomlaUpdateComponentId));
$db->setQuery($query);
try
{
$joomlaUpdateComponentObject = $db->loadObject();
}
catch (RuntimeException $e)
{
// Something is wrong here!
$joomlaUpdateComponentObject = null;
JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error');
}
if (is_null($joomlaUpdateComponentObject))
{
// No Update great!
return false;
}
return true;
}
}
/**
* Returns true, if the pre update check should be displayed.
* This logic is not hardcoded in tmpl files, because it is
* used by the Hathor tmpl too.
*
* @return boolean
*
* @since 3.10.0
*/
public function shouldDisplayPreUpdateCheck()
{
// When the download URL is not found there is no core upgrade path
if (!isset($this->updateInfo['object']->downloadurl->_data))
{
return false;
}
$nextMinor = JVersion::MAJOR_VERSION . '.' . (JVersion::MINOR_VERSION + 1);
// Show only when we found a download URL, we have an update and when we update to the next minor or greater.
return $this->updateInfo['hasUpdate']
&& version_compare($this->updateInfo['latest'], $nextMinor, '>=');
}
}
views/update/tmpl/default.php 0000644 00000004533 15234617001 0012300 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
// Include jQuery.
JHtml::_('jquery.framework');
// Load the scripts
JHtml::_('script', 'com_joomlaupdate/json2.js', array('version' => 'auto', 'relative' => true));
JHtml::_('script', 'com_joomlaupdate/encryption.js', array('version' => 'auto', 'relative' => true));
JHtml::_('script', 'com_joomlaupdate/update.js', array('version' => 'auto', 'relative' => true));
$password = JFactory::getApplication()->getUserState('com_joomlaupdate.password', null);
$filesize = JFactory::getApplication()->getUserState('com_joomlaupdate.filesize', null);
$ajaxUrl = JUri::base() . 'components/com_joomlaupdate/restore.php';
$returnUrl = 'index.php?option=com_joomlaupdate&task=update.finalise&' . JFactory::getSession()->getFormToken() . '=1';
JFactory::getDocument()->addScriptDeclaration(
"
var joomlaupdate_password = '$password';
var joomlaupdate_totalsize = '$filesize';
var joomlaupdate_ajax_url = '$ajaxUrl';
var joomlaupdate_return_url = '$returnUrl';
jQuery(document).ready(function(){
window.pingExtract();
});
"
);
?>
<p class="nowarning"><?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_INPROGRESS'); ?></p>
<div id="update-progress">
<div id="extprogress">
<div id="progress" class="progress progress-striped active">
<div id="progress-bar" class="bar bar-success" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div class="extprogrow">
<span class="extlabel"><?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_PERCENT'); ?></span>
<span class="extvalue" id="extpercent"></span>
</div>
<div class="extprogrow">
<span class="extlabel"><?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_BYTESREAD'); ?></span>
<span class="extvalue" id="extbytesin"></span>
</div>
<div class="extprogrow">
<span class="extlabel"><?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_BYTESEXTRACTED'); ?></span>
<span class="extvalue" id="extbytesout"></span>
</div>
<div class="extprogrow">
<span class="extlabel"><?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_FILESEXTRACTED'); ?></span>
<span class="extvalue" id="extfiles"></span>
</div>
</div>
</div>
views/update/tmpl/finaliseconfirm.php 0000644 00000007177 15234617001 0014033 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
JHtml::_('behavior.keepalive');
JHtml::_('bootstrap.tooltip');
$twofactormethods = JAuthenticationHelper::getTwoFactorMethods();
?>
<div class="alert alert-warning">
<h4 class="alert-heading">
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_FINALISE_HEAD'); ?>
</h4>
<p>
<?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_UPDATE_FINALISE_HEAD_DESC', JFactory::getConfig()->get('sitename')); ?>
</p>
</div>
<hr/>
<form action="<?php echo JRoute::_('index.php', true); ?>" method="post" id="form-login" class="form-inline center">
<fieldset class="loginform">
<div class="control-group">
<div class="controls">
<div class="input-prepend input-append">
<span class="add-on">
<span class="icon-user hasTooltip" title="<?php echo JText::_('JGLOBAL_USERNAME'); ?>" aria-hidden="true"></span>
<label for="mod-login-username" class="element-invisible">
<?php echo JText::_('JGLOBAL_USERNAME'); ?>
</label>
</span>
<input name="username" tabindex="1" id="mod-login-username" type="text" class="input-medium" placeholder="<?php echo JText::_('JGLOBAL_USERNAME'); ?>" size="15" autofocus="true" />
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<div class="input-prepend input-append">
<span class="add-on">
<span class="icon-lock hasTooltip" title="<?php echo JText::_('JGLOBAL_PASSWORD'); ?>" aria-hidden="true"></span>
<label for="mod-login-password" class="element-invisible">
<?php echo JText::_('JGLOBAL_PASSWORD'); ?>
</label>
</span>
<input name="passwd" tabindex="2" id="mod-login-password" type="password" class="input-medium" placeholder="<?php echo JText::_('JGLOBAL_PASSWORD'); ?>" size="15"/>
</div>
</div>
</div>
<?php if (count($twofactormethods) > 1) : ?>
<div class="control-group">
<div class="controls">
<div class="input-prepend input-append">
<span class="add-on">
<span class="icon-star hasTooltip" title="<?php echo JText::_('JGLOBAL_SECRETKEY'); ?>" aria-hidden="true"></span>
<label for="mod-login-secretkey" class="element-invisible">
<?php echo JText::_('JGLOBAL_SECRETKEY'); ?>
</label>
</span>
<input name="secretkey" autocomplete="one-time-code" tabindex="3" id="mod-login-secretkey" type="text" class="input-medium" placeholder="<?php echo JText::_('JGLOBAL_SECRETKEY'); ?>" size="15"/>
<span class="btn width-auto hasTooltip" title="<?php echo JText::_('JGLOBAL_SECRETKEY_HELP'); ?>">
<span class="icon-help" aria-hidden="true"></span>
</span>
</div>
</div>
</div>
<?php endif; ?>
<div class="control-group">
<div class="controls">
<div class="btn-group">
<a tabindex="4" class="btn btn-danger btn-small" href="index.php?option=com_joomlaupdate">
<span class="icon-cancel icon-white" aria-hidden="true"></span> <?php echo JText::_('JCANCEL'); ?>
</a>
</div>
<div class="btn-group">
<button tabindex="5" class="btn btn-primary btn-large">
<span class="icon-play icon-white" aria-hidden="true"></span> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_FINALISE_CONFIRM_AND_CONTINUE'); ?>
</button>
</div>
</div>
</div>
<input type="hidden" name="option" value="com_joomlaupdate"/>
<input type="hidden" name="task" value="update.finaliseconfirm" />
<?php echo JHtml::_('form.token'); ?>
</fieldset>
</form>
views/update/tmpl/index.html 0000644 00000000037 15234617001 0012133 0 ustar 00 <!DOCTYPE html><title></title>
views/update/index.html 0000644 00000000037 15234617001 0011157 0 ustar 00 <!DOCTYPE html><title></title>
views/update/view.html.php 0000644 00000001565 15234617001 0011617 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/**
* Joomla! Update's Update View
*
* @since 2.5.4
*/
class JoomlaupdateViewUpdate extends JViewLegacy
{
/**
* Renders the view.
*
* @param string $tpl Template name.
*
* @return void
*/
public function display($tpl = null)
{
JFactory::getApplication()->input->set('hidemainmenu', true);
// Set the toolbar information.
JToolbarHelper::title(JText::_('COM_JOOMLAUPDATE_OVERVIEW'), 'loop install');
// Import com_login's model
JModelLegacy::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_login/models', 'LoginModel');
// Render the view.
parent::display($tpl);
}
}
views/upload/tmpl/captive.php 0000644 00000007110 15234617001 0012303 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
JHtml::_('behavior.keepalive');
JHtml::_('bootstrap.tooltip');
$twofactormethods = JAuthenticationHelper::getTwoFactorMethods();
?>
<div class="alert alert-warning">
<h4 class="alert-heading">
<?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPLOAD_CAPTIVE_INTRO_HEAD'); ?>
</h4>
<p>
<?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_UPLOAD_CAPTIVE_INTRO_BODY', JFactory::getConfig()->get('sitename')); ?>
</p>
</div>
<hr/>
<form action="<?php echo JRoute::_('index.php', true); ?>" method="post" id="form-login" class="form-inline center">
<fieldset class="loginform">
<div class="control-group">
<div class="controls">
<div class="input-prepend input-append">
<span class="add-on">
<span class="icon-user hasTooltip" title="<?php echo JText::_('JGLOBAL_USERNAME'); ?>" aria-hidden="true"></span>
<label for="mod-login-username" class="element-invisible">
<?php echo JText::_('JGLOBAL_USERNAME'); ?>
</label>
</span>
<input name="username" tabindex="1" id="mod-login-username" type="text" class="input-medium" placeholder="<?php echo JText::_('JGLOBAL_USERNAME'); ?>" size="15" autofocus="true" />
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<div class="input-prepend input-append">
<span class="add-on">
<span class="icon-lock hasTooltip" title="<?php echo JText::_('JGLOBAL_PASSWORD'); ?>" aria-hidden="true"></span>
<label for="mod-login-password" class="element-invisible">
<?php echo JText::_('JGLOBAL_PASSWORD'); ?>
</label>
</span>
<input name="passwd" tabindex="2" id="mod-login-password" type="password" class="input-medium" placeholder="<?php echo JText::_('JGLOBAL_PASSWORD'); ?>" size="15"/>
</div>
</div>
</div>
<?php if (count($twofactormethods) > 1) : ?>
<div class="control-group">
<div class="controls">
<div class="input-prepend input-append">
<span class="add-on">
<span class="icon-star hasTooltip" title="<?php echo JText::_('JGLOBAL_SECRETKEY'); ?>" aria-hidden="true"></span>
<label for="mod-login-secretkey" class="element-invisible">
<?php echo JText::_('JGLOBAL_SECRETKEY'); ?>
</label>
</span>
<input name="secretkey" autocomplete="one-time-code" tabindex="3" id="mod-login-secretkey" type="text" class="input-medium" placeholder="<?php echo JText::_('JGLOBAL_SECRETKEY'); ?>" size="15"/>
<span class="btn width-auto hasTooltip" title="<?php echo JText::_('JGLOBAL_SECRETKEY_HELP'); ?>">
<span class="icon-help" aria-hidden="true"></span>
</span>
</div>
</div>
</div>
<?php endif; ?>
<div class="control-group">
<div class="controls">
<div class="btn-group">
<a tabindex="4" class="btn btn-danger" href="index.php?option=com_joomlaupdate">
<span class="icon-cancel icon-white" aria-hidden="true"></span> <?php echo JText::_('JCANCEL'); ?>
</a>
</div>
<div class="btn-group">
<button tabindex="5" class="btn btn-primary">
<span class="icon-play icon-white" aria-hidden="true"></span> <?php echo JText::_('COM_INSTALLER_INSTALL_BUTTON'); ?>
</button>
</div>
</div>
</div>
<input type="hidden" name="option" value="com_joomlaupdate"/>
<input type="hidden" name="task" value="update.confirm"/>
<?php echo JHtml::_('form.token'); ?>
</fieldset>
</form>
views/upload/view.html.php 0000644 00000002201 15234617001 0011605 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/**
* Joomla! Update's Update View
*
* @since 3.6.0
*/
class JoomlaupdateViewUpload extends JViewLegacy
{
/**
* Renders the view.
*
* @param string $tpl Template name.
*
* @return void
*
* @since 3.6.0
*/
public function display($tpl = null)
{
// Set the toolbar information.
JToolbarHelper::title(JText::_('COM_JOOMLAUPDATE_OVERVIEW'), 'loop install');
JToolbarHelper::divider();
JToolbarHelper::help('JHELP_COMPONENTS_JOOMLA_UPDATE');
// Load com_installer's language
$language = JFactory::getLanguage();
$language->load('com_installer', JPATH_ADMINISTRATOR, 'en-GB', false, true);
$language->load('com_installer', JPATH_ADMINISTRATOR, null, true);
// Import com_login's model
JModelLegacy::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_login/models', 'LoginModel');
// Render the view.
parent::display($tpl);
}
}
views/index.html 0000644 00000000037 15234617001 0007675 0 ustar 00 <!DOCTYPE html><title></title>
config.xml 0000644 00000003356 15234617001 0006541 0 ustar 00 <?xml version="1.0" encoding="utf-8"?>
<config>
<fieldset
name="sources"
label="COM_JOOMLAUPDATE_CONFIG_SOURCES_LABEL"
description="COM_JOOMLAUPDATE_CONFIG_SOURCES_DESC"
>
<field
name="updatesource"
type="list"
label="COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_LABEL"
description="COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_DESC"
default="default"
>
<!-- Note: Changed the values lts to default and sts to next with 3.4.0 -->
<!-- Eliminated the 'nochange' option with 3.4.0 -->
<!-- All invalid/unsupported/obsolete options equated to default in code with 3.4.0 -->
<option value="default">COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_DEFAULT</option>
<option value="next">COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_NEXT</option>
<option value="testing">COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_TESTING</option>
<option value="custom">COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_CUSTOM</option>
</field>
<field
name="minimum_stability"
type="list"
label="COM_JOOMLAUPDATE_MINIMUM_STABILITY_LABEL"
description="COM_JOOMLAUPDATE_MINIMUM_STABILITY_DESC"
default="4"
showon="updatesource:testing[OR]updatesource:custom"
>
<option value="0">COM_JOOMLAUPDATE_MINIMUM_STABILITY_DEV</option>
<option value="1">COM_JOOMLAUPDATE_MINIMUM_STABILITY_ALPHA</option>
<option value="2">COM_JOOMLAUPDATE_MINIMUM_STABILITY_BETA</option>
<option value="3">COM_JOOMLAUPDATE_MINIMUM_STABILITY_RC</option>
<option value="4">COM_JOOMLAUPDATE_MINIMUM_STABILITY_STABLE</option>
</field>
<field
name="customurl"
type="text"
label="COM_JOOMLAUPDATE_CONFIG_CUSTOMURL_LABEL"
description="COM_JOOMLAUPDATE_CONFIG_CUSTOMURL_DESC"
default=""
length="50"
showon="updatesource:custom"
/>
</fieldset>
</config>
controller.php 0000644 00000004026 15234617001 0007441 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/**
* Joomla! Update Controller
*
* @since 2.5.4
*/
class JoomlaupdateController extends JControllerLegacy
{
/**
* Method to display a view.
*
* @param boolean $cachable If true, the view output will be cached.
* @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link JFilterInput::clean()}.
*
* @return JController This object to support chaining.
*
* @since 2.5.4
*/
public function display($cachable = false, $urlparams = false)
{
// Get the document object.
$document = JFactory::getDocument();
// Set the default view name and format from the Request.
$vName = $this->input->get('view', 'default');
$vFormat = $document->getType();
$lName = $this->input->get('layout', 'default', 'string');
// Get and render the view.
if ($view = $this->getView($vName, $vFormat))
{
$ftp = JClientHelper::setCredentialsFromRequest('ftp');
$view->ftp = &$ftp;
// Get the model for the view.
/** @var JoomlaupdateModelDefault $model */
$model = $this->getModel('default');
// Push the Installer Warnings model into the view, if we can load it
static::addModelPath(JPATH_ADMINISTRATOR . '/components/com_installer/models', 'InstallerModel');
$warningsModel = $this->getModel('warnings', 'InstallerModel');
if (is_object($warningsModel))
{
$view->setModel($warningsModel, false);
}
// Perform update source preference check and refresh update information.
$model->applyUpdateSite();
$model->refreshUpdates();
// Push the model into the view (as default).
$view->setModel($model, true);
$view->setLayout($lName);
// Push document object into the view.
$view->document = $document;
$view->display();
}
return $this;
}
}
joomlaupdate.php 0000644 00000001061 15234617001 0007736 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
if (!JFactory::getUser()->authorise('core.admin'))
{
throw new JAccessExceptionNotallowed(JText::_('JERROR_ALERTNOAUTHOR'), 403);
}
$controller = JControllerLegacy::getInstance('Joomlaupdate');
$controller->execute(JFactory::getApplication()->input->get('task'));
$controller->redirect();
joomlaupdate.xml 0000644 00000002552 15234617001 0007755 0 ustar 00 <?xml version="1.0" encoding="utf-8"?>
<extension type="component" version="3.1" method="upgrade">
<name>com_joomlaupdate</name>
<author>Joomla! Project</author>
<creationDate>February 2012</creationDate>
<copyright>(C) 2012 Open Source Matters, Inc.</copyright>
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
<authorEmail>admin@joomla.org</authorEmail>
<authorUrl>www.joomla.org</authorUrl>
<version>3.10.1</version>
<description>COM_JOOMLAUPDATE_XML_DESCRIPTION</description>
<media destination="com_joomlaupdate" folder="media">
<folder>js</folder>
</media>
<administration>
<menu img="class:joomlaupdate">com_joomlaupdate</menu>
<files folder="admin">
<filename>config.xml</filename>
<filename>controller.php</filename>
<filename>joomlaupdate.php</filename>
<filename>restore.php</filename>
<folder>controllers</folder>
<folder>helpers</folder>
<folder>models</folder>
<folder>views</folder>
</files>
<languages folder="admin">
<language tag="en-GB">language/en-GB.com_joomlaupdate.ini</language>
<language tag="en-GB">language/en-GB.com_joomlaupdate.sys.ini</language>
</languages>
</administration>
<updateservers>
<server type="extension" name="Joomla! Update Component Update Site">https://update.joomla.org/core/extensions/com_joomlaupdate.xml</server>
</updateservers>
</extension>
restore.php 0000644 00000375347 15234617001 0006762 0 ustar 00 <?php
/**
* Akeeba Restore
*
* An archive extraction engine for ZIP, JPA and JPS archives.
*
* @copyright 2008-2017 Nicholas K. Dionysopoulos / Akeeba Ltd.
* @license GNU GPL v2 or - at your option - any later version
* @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
*/
define('_AKEEBA_RESTORATION', 1);
defined('DS') or define('DS', DIRECTORY_SEPARATOR);
// Unarchiver run states
define('AK_STATE_NOFILE', 0); // File header not read yet
define('AK_STATE_HEADER', 1); // File header read; ready to process data
define('AK_STATE_DATA', 2); // Processing file data
define('AK_STATE_DATAREAD', 3); // Finished processing file data; ready to post-process
define('AK_STATE_POSTPROC', 4); // Post-processing
define('AK_STATE_DONE', 5); // Done with post-processing
/* Windows system detection */
if (!defined('_AKEEBA_IS_WINDOWS'))
{
if (function_exists('php_uname'))
{
define('_AKEEBA_IS_WINDOWS', stristr(php_uname(), 'windows'));
}
else
{
define('_AKEEBA_IS_WINDOWS', DIRECTORY_SEPARATOR == '\\');
}
}
// Get the file's root
if (!defined('KSROOTDIR'))
{
define('KSROOTDIR', dirname(__FILE__));
}
if (!defined('KSLANGDIR'))
{
define('KSLANGDIR', KSROOTDIR);
}
// Make sure the locale is correct for basename() to work
if (function_exists('setlocale'))
{
@setlocale(LC_ALL, 'en_US.UTF8');
}
// fnmatch not available on non-POSIX systems
// Thanks to soywiz@php.net for this useful alternative function [http://gr2.php.net/fnmatch]
if (!function_exists('fnmatch'))
{
function fnmatch($pattern, $string)
{
return @preg_match(
'/^' . strtr(addcslashes($pattern, '/\\.+^$(){}=!<>|'),
array('*' => '.*', '?' => '.?')) . '$/i', $string
);
}
}
// Unicode-safe binary data length function
if (!function_exists('akstringlen'))
{
if (function_exists('mb_strlen'))
{
function akstringlen($string)
{
return mb_strlen($string, '8bit');
}
}
else
{
function akstringlen($string)
{
return strlen($string);
}
}
}
if (!function_exists('aksubstr'))
{
if (function_exists('mb_strlen'))
{
function aksubstr($string, $start, $length = null)
{
return mb_substr($string, $start, $length, '8bit');
}
}
else
{
function aksubstr($string, $start, $length = null)
{
return substr($string, $start, $length);
}
}
}
/**
* Gets a query parameter from GET or POST data
*
* @param $key
* @param $default
*/
function getQueryParam($key, $default = null)
{
$value = $default;
if (array_key_exists($key, $_REQUEST))
{
$value = $_REQUEST[$key];
if (PHP_VERSION_ID < 50400 && get_magic_quotes_gpc() && !is_null($value))
{
$value = stripslashes($value);
}
}
return $value;
}
// Debugging function
function debugMsg($msg)
{
if (!defined('KSDEBUG'))
{
return;
}
$fp = fopen('debug.txt', 'at');
fwrite($fp, $msg . "\n");
fclose($fp);
// Echo to stdout if KSDEBUGCLI is defined
if (defined('KSDEBUGCLI'))
{
echo $msg . "\n";
}
}
/**
* The base class of Akeeba Engine objects. Allows for error and warnings logging
* and propagation. Largely based on the Joomla! 1.5 JObject class.
*/
abstract class AKAbstractObject
{
/** @var array The queue size of the $_errors array. Set to 0 for infinite size. */
protected $_errors_queue_size = 0;
/** @var array The queue size of the $_warnings array. Set to 0 for infinite size. */
protected $_warnings_queue_size = 0;
/** @var array An array of errors */
private $_errors = array();
/** @var array An array of warnings */
private $_warnings = array();
/**
* Get the most recent error message
*
* @param integer $i Optional error index
*
* @return string Error message
*/
public function getError($i = null)
{
return $this->getItemFromArray($this->_errors, $i);
}
/**
* Returns the last item of a LIFO string message queue, or a specific item
* if so specified.
*
* @param array $array An array of strings, holding messages
* @param int $i Optional message index
*
* @return mixed The message string, or false if the key doesn't exist
*/
private function getItemFromArray($array, $i = null)
{
// Find the item
if ($i === null)
{
// Default, return the last item
$item = end($array);
}
else if (!array_key_exists($i, $array))
{
// If $i has been specified but does not exist, return false
return false;
}
else
{
$item = $array[$i];
}
return $item;
}
/**
* Return all errors, if any
*
* @return array Array of error messages
*/
public function getErrors()
{
return $this->_errors;
}
/**
* Resets all error messages
*/
public function resetErrors()
{
$this->_errors = array();
}
/**
* Get the most recent warning message
*
* @param integer $i Optional warning index
*
* @return string Error message
*/
public function getWarning($i = null)
{
return $this->getItemFromArray($this->_warnings, $i);
}
/**
* Return all warnings, if any
*
* @return array Array of error messages
*/
public function getWarnings()
{
return $this->_warnings;
}
/**
* Resets all warning messages
*/
public function resetWarnings()
{
$this->_warnings = array();
}
/**
* Propagates errors and warnings to a foreign object. The foreign object SHOULD
* implement the setError() and/or setWarning() methods but DOESN'T HAVE TO be of
* AKAbstractObject type. For example, this can even be used to propagate to a
* JObject instance in Joomla!. Propagated items will be removed from ourselves.
*
* @param object $object The object to propagate errors and warnings to.
*/
public function propagateToObject(&$object)
{
// Skip non-objects
if (!is_object($object))
{
return;
}
if (method_exists($object, 'setError'))
{
if (!empty($this->_errors))
{
foreach ($this->_errors as $error)
{
$object->setError($error);
}
$this->_errors = array();
}
}
if (method_exists($object, 'setWarning'))
{
if (!empty($this->_warnings))
{
foreach ($this->_warnings as $warning)
{
$object->setWarning($warning);
}
$this->_warnings = array();
}
}
}
/**
* Propagates errors and warnings from a foreign object. Each propagated list is
* then cleared on the foreign object, as long as it implements resetErrors() and/or
* resetWarnings() methods.
*
* @param object $object The object to propagate errors and warnings from
*/
public function propagateFromObject(&$object)
{
if (method_exists($object, 'getErrors'))
{
$errors = $object->getErrors();
if (!empty($errors))
{
foreach ($errors as $error)
{
$this->setError($error);
}
}
if (method_exists($object, 'resetErrors'))
{
$object->resetErrors();
}
}
if (method_exists($object, 'getWarnings'))
{
$warnings = $object->getWarnings();
if (!empty($warnings))
{
foreach ($warnings as $warning)
{
$this->setWarning($warning);
}
}
if (method_exists($object, 'resetWarnings'))
{
$object->resetWarnings();
}
}
}
/**
* Add an error message
*
* @param string $error Error message
*/
public function setError($error)
{
if ($this->_errors_queue_size > 0)
{
if (count($this->_errors) >= $this->_errors_queue_size)
{
array_shift($this->_errors);
}
}
$this->_errors[] = $error;
}
/**
* Add an error message
*
* @param string $error Error message
*/
public function setWarning($warning)
{
if ($this->_warnings_queue_size > 0)
{
if (count($this->_warnings) >= $this->_warnings_queue_size)
{
array_shift($this->_warnings);
}
}
$this->_warnings[] = $warning;
}
/**
* Sets the size of the error queue (acts like a LIFO buffer)
*
* @param int $newSize The new queue size. Set to 0 for infinite length.
*/
protected function setErrorsQueueSize($newSize = 0)
{
$this->_errors_queue_size = (int) $newSize;
}
/**
* Sets the size of the warnings queue (acts like a LIFO buffer)
*
* @param int $newSize The new queue size. Set to 0 for infinite length.
*/
protected function setWarningsQueueSize($newSize = 0)
{
$this->_warnings_queue_size = (int) $newSize;
}
}
/**
* The superclass of all Akeeba Kickstart parts. The "parts" are intelligent stateful
* classes which perform a single procedure and have preparation, running and
* finalization phases. The transition between phases is handled automatically by
* this superclass' tick() final public method, which should be the ONLY public API
* exposed to the rest of the Akeeba Engine.
*/
abstract class AKAbstractPart extends AKAbstractObject
{
/**
* Indicates whether this part has finished its initialisation cycle
*
* @var boolean
*/
protected $isPrepared = false;
/**
* Indicates whether this part has more work to do (it's in running state)
*
* @var boolean
*/
protected $isRunning = false;
/**
* Indicates whether this part has finished its finalization cycle
*
* @var boolean
*/
protected $isFinished = false;
/**
* Indicates whether this part has finished its run cycle
*
* @var boolean
*/
protected $hasRan = false;
/**
* The name of the engine part (a.k.a. Domain), used in return table
* generation.
*
* @var string
*/
protected $active_domain = "";
/**
* The step this engine part is in. Used verbatim in return table and
* should be set by the code in the _run() method.
*
* @var string
*/
protected $active_step = "";
/**
* A more detailed description of the step this engine part is in. Used
* verbatim in return table and should be set by the code in the _run()
* method.
*
* @var string
*/
protected $active_substep = "";
/**
* Any configuration variables, in the form of an array.
*
* @var array
*/
protected $_parametersArray = array();
/** @var string The database root key */
protected $databaseRoot = array();
/** @var array An array of observers */
protected $observers = array();
/** @var int Last reported warnings's position in array */
private $warnings_pointer = -1;
/**
* The public interface to an engine part. This method takes care for
* calling the correct method in order to perform the initialisation -
* run - finalisation cycle of operation and return a proper response array.
*
* @return array A Response Array
*/
final public function tick()
{
// Call the right action method, depending on engine part state
switch ($this->getState())
{
case "init":
$this->_prepare();
break;
case "prepared":
case "running":
$this->_run();
break;
case "postrun":
$this->_finalize();
break;
}
// Send a Return Table back to the caller
$out = $this->_makeReturnTable();
return $out;
}
/**
* Returns the state of this engine part.
*
* @return string The state of this engine part. It can be one of
* error, init, prepared, running, postrun, finished.
*/
final public function getState()
{
if ($this->getError())
{
return "error";
}
if (!($this->isPrepared))
{
return "init";
}
if (!($this->isFinished) && !($this->isRunning) && !($this->hasRun) && ($this->isPrepared))
{
return "prepared";
}
if (!($this->isFinished) && $this->isRunning && !($this->hasRun))
{
return "running";
}
if (!($this->isFinished) && !($this->isRunning) && $this->hasRun)
{
return "postrun";
}
if ($this->isFinished)
{
return "finished";
}
// Unknown internal state. This should never happen.
return "error";
}
/**
* Runs the preparation for this part. Should set _isPrepared
* to true
*/
abstract protected function _prepare();
/**
* Runs the main functionality loop for this part. Upon calling,
* should set the _isRunning to true. When it finished, should set
* the _hasRan to true. If an error is encountered, setError should
* be used.
*/
abstract protected function _run();
/**
* Runs the finalisation process for this part. Should set
* _isFinished to true.
*/
abstract protected function _finalize();
/**
* Constructs a Response Array based on the engine part's state.
*
* @return array The Response Array for the current state
*/
final protected function _makeReturnTable()
{
// Get a list of warnings
$warnings = $this->getWarnings();
// Report only new warnings if there is no warnings queue size
if ($this->_warnings_queue_size == 0)
{
if (($this->warnings_pointer > 0) && ($this->warnings_pointer < (count($warnings))))
{
$warnings = array_slice($warnings, $this->warnings_pointer + 1);
$this->warnings_pointer += count($warnings);
}
else
{
$this->warnings_pointer = count($warnings);
}
}
$out = array(
'HasRun' => (!($this->isFinished)),
'Domain' => $this->active_domain,
'Step' => $this->active_step,
'Substep' => $this->active_substep,
'Error' => $this->getError(),
'Warnings' => $warnings
);
return $out;
}
/**
* Returns a copy of the class's status array
*
* @return array
*/
public function getStatusArray()
{
return $this->_makeReturnTable();
}
/**
* Sends any kind of setup information to the engine part. Using this,
* we avoid passing parameters to the constructor of the class. These
* parameters should be passed as an indexed array and should be taken
* into account during the preparation process only. This function will
* set the error flag if it's called after the engine part is prepared.
*
* @param array $parametersArray The parameters to be passed to the
* engine part.
*/
final public function setup($parametersArray)
{
if ($this->isPrepared)
{
$this->setState('error', "Can't modify configuration after the preparation of " . $this->active_domain);
}
else
{
$this->_parametersArray = $parametersArray;
if (array_key_exists('root', $parametersArray))
{
$this->databaseRoot = $parametersArray['root'];
}
}
}
/**
* Sets the engine part's internal state, in an easy to use manner
*
* @param string $state One of init, prepared, running, postrun, finished, error
* @param string $errorMessage The reported error message, should the state be set to error
*/
protected function setState($state = 'init', $errorMessage = 'Invalid setState argument')
{
switch ($state)
{
case 'init':
$this->isPrepared = false;
$this->isRunning = false;
$this->isFinished = false;
$this->hasRun = false;
break;
case 'prepared':
$this->isPrepared = true;
$this->isRunning = false;
$this->isFinished = false;
$this->hasRun = false;
break;
case 'running':
$this->isPrepared = true;
$this->isRunning = true;
$this->isFinished = false;
$this->hasRun = false;
break;
case 'postrun':
$this->isPrepared = true;
$this->isRunning = false;
$this->isFinished = false;
$this->hasRun = true;
break;
case 'finished':
$this->isPrepared = true;
$this->isRunning = false;
$this->isFinished = true;
$this->hasRun = false;
break;
case 'error':
default:
$this->setError($errorMessage);
break;
}
}
final public function getDomain()
{
return $this->active_domain;
}
final public function getStep()
{
return $this->active_step;
}
final public function getSubstep()
{
return $this->active_substep;
}
/**
* Attaches an observer object
*
* @param AKAbstractPartObserver $obs
*/
function attach(AKAbstractPartObserver $obs)
{
$this->observers["$obs"] = $obs;
}
/**
* Detaches an observer object
*
* @param AKAbstractPartObserver $obs
*/
function detach(AKAbstractPartObserver $obs)
{
unset($this->observers["$obs"]);
}
/**
* Sets the BREAKFLAG, which instructs this engine part that the current step must break immediately,
* in fear of timing out.
*/
protected function setBreakFlag()
{
AKFactory::set('volatile.breakflag', true);
}
final protected function setDomain($new_domain)
{
$this->active_domain = $new_domain;
}
final protected function setStep($new_step)
{
$this->active_step = $new_step;
}
final protected function setSubstep($new_substep)
{
$this->active_substep = $new_substep;
}
/**
* Notifies observers each time something interesting happened to the part
*
* @param mixed $message The event object
*/
protected function notify($message)
{
foreach ($this->observers as $obs)
{
$obs->update($this, $message);
}
}
}
/**
* The base class of unarchiver classes
*/
abstract class AKAbstractUnarchiver extends AKAbstractPart
{
/** @var array List of the names of all archive parts */
public $archiveList = array();
/** @var int The total size of all archive parts */
public $totalSize = array();
/** @var array Which files to rename */
public $renameFiles = array();
/** @var array Which directories to rename */
public $renameDirs = array();
/** @var array Which files to skip */
public $skipFiles = array();
/** @var string Archive filename */
protected $filename = null;
/** @var integer Current archive part number */
protected $currentPartNumber = -1;
/** @var integer The offset inside the current part */
protected $currentPartOffset = 0;
/** @var bool Should I restore permissions? */
protected $flagRestorePermissions = false;
/** @var AKAbstractPostproc Post processing class */
protected $postProcEngine = null;
/** @var string Absolute path to prepend to extracted files */
protected $addPath = '';
/** @var string Absolute path to remove from extracted files */
protected $removePath = '';
/** @var integer Chunk size for processing */
protected $chunkSize = 524288;
/** @var resource File pointer to the current archive part file */
protected $fp = null;
/** @var int Run state when processing the current archive file */
protected $runState = null;
/** @var stdClass File header data, as read by the readFileHeader() method */
protected $fileHeader = null;
/** @var int How much of the uncompressed data we've read so far */
protected $dataReadLength = 0;
/** @var array Unwriteable files in these directories are always ignored and do not cause errors when not extracted */
protected $ignoreDirectories = array();
/**
* Wakeup function, called whenever the class is unserialized
*/
public function __wakeup()
{
if ($this->currentPartNumber >= 0 && !empty($this->archiveList[$this->currentPartNumber]))
{
$this->fp = @fopen($this->archiveList[$this->currentPartNumber], 'rb');
if ((is_resource($this->fp)) && ($this->currentPartOffset > 0))
{
@fseek($this->fp, $this->currentPartOffset);
}
}
}
/**
* Sleep function, called whenever the class is serialized
*/
public function shutdown()
{
if (is_resource($this->fp))
{
$this->currentPartOffset = @ftell($this->fp);
@fclose($this->fp);
}
}
/**
* Is this file or directory contained in a directory we've decided to ignore
* write errors for? This is useful to let the extraction work despite write
* errors in the log, logs and tmp directories which MIGHT be used by the system
* on some low quality hosts and Plesk-powered hosts.
*
* @param string $shortFilename The relative path of the file/directory in the package
*
* @return boolean True if it belongs in an ignored directory
*/
public function isIgnoredDirectory($shortFilename)
{
// return false;
if (substr($shortFilename, -1) == '/')
{
$check = rtrim($shortFilename, '/');
}
else
{
$check = dirname($shortFilename);
}
return in_array($check, $this->ignoreDirectories);
}
/**
* Implements the abstract _prepare() method
*/
final protected function _prepare()
{
if (count($this->_parametersArray) > 0)
{
foreach ($this->_parametersArray as $key => $value)
{
switch ($key)
{
// Archive's absolute filename
case 'filename':
$this->filename = $value;
// Sanity check
if (!empty($value))
{
$value = strtolower($value);
if (strlen($value) > 6)
{
if (
(substr($value, 0, 7) == 'http://')
|| (substr($value, 0, 8) == 'https://')
|| (substr($value, 0, 6) == 'ftp://')
|| (substr($value, 0, 7) == 'ssh2://')
|| (substr($value, 0, 6) == 'ssl://')
)
{
$this->setState('error', 'Invalid archive location');
}
}
}
break;
// Should I restore permissions?
case 'restore_permissions':
$this->flagRestorePermissions = $value;
break;
// Should I use FTP?
case 'post_proc':
$this->postProcEngine = AKFactory::getPostProc($value);
break;
// Path to add in the beginning
case 'add_path':
$this->addPath = $value;
$this->addPath = str_replace('\\', '/', $this->addPath);
$this->addPath = rtrim($this->addPath, '/');
if (!empty($this->addPath))
{
$this->addPath .= '/';
}
break;
// Path to remove from the beginning
case 'remove_path':
$this->removePath = $value;
$this->removePath = str_replace('\\', '/', $this->removePath);
$this->removePath = rtrim($this->removePath, '/');
if (!empty($this->removePath))
{
$this->removePath .= '/';
}
break;
// Which files to rename (hash array)
case 'rename_files':
$this->renameFiles = $value;
break;
// Which files to rename (hash array)
case 'rename_dirs':
$this->renameDirs = $value;
break;
// Which files to skip (indexed array)
case 'skip_files':
$this->skipFiles = $value;
break;
// Which directories to ignore when we can't write files in them (indexed array)
case 'ignoredirectories':
$this->ignoreDirectories = $value;
break;
}
}
}
$this->scanArchives();
$this->readArchiveHeader();
$errMessage = $this->getError();
if (!empty($errMessage))
{
$this->setState('error', $errMessage);
}
else
{
$this->runState = AK_STATE_NOFILE;
$this->setState('prepared');
}
}
/**
* Scans for archive parts
*/
private function scanArchives()
{
if (defined('KSDEBUG'))
{
@unlink('debug.txt');
}
debugMsg('Preparing to scan archives');
$privateArchiveList = array();
// Get the components of the archive filename
$dirname = dirname($this->filename);
$base_extension = $this->getBaseExtension();
$basename = basename($this->filename, $base_extension);
$this->totalSize = 0;
// Scan for multiple parts until we don't find any more of them
$count = 0;
$found = true;
$this->archiveList = array();
while ($found)
{
++$count;
$extension = substr($base_extension, 0, 2) . sprintf('%02d', $count);
$filename = $dirname . DIRECTORY_SEPARATOR . $basename . $extension;
$found = file_exists($filename);
if ($found)
{
debugMsg('- Found archive ' . $filename);
// Add yet another part, with a numeric-appended filename
$this->archiveList[] = $filename;
$filesize = @filesize($filename);
$this->totalSize += $filesize;
$privateArchiveList[] = array($filename, $filesize);
}
else
{
debugMsg('- Found archive ' . $this->filename);
// Add the last part, with the regular extension
$this->archiveList[] = $this->filename;
$filename = $this->filename;
$filesize = @filesize($filename);
$this->totalSize += $filesize;
$privateArchiveList[] = array($filename, $filesize);
}
}
debugMsg('Total archive parts: ' . $count);
$this->currentPartNumber = -1;
$this->currentPartOffset = 0;
$this->runState = AK_STATE_NOFILE;
// Send start of file notification
$message = new stdClass;
$message->type = 'totalsize';
$message->content = new stdClass;
$message->content->totalsize = $this->totalSize;
$message->content->filelist = $privateArchiveList;
$this->notify($message);
}
/**
* Returns the base extension of the file, e.g. '.jpa'
*
* @return string
*/
private function getBaseExtension()
{
static $baseextension;
if (empty($baseextension))
{
$basename = basename($this->filename);
$lastdot = strrpos($basename, '.');
$baseextension = substr($basename, $lastdot);
}
return $baseextension;
}
/**
* Concrete classes are supposed to use this method in order to read the archive's header and
* prepare themselves to the point of being ready to extract the first file.
*/
protected abstract function readArchiveHeader();
protected function _run()
{
if ($this->getState() == 'postrun')
{
return;
}
$this->setState('running');
$timer = AKFactory::getTimer();
$status = true;
while ($status && ($timer->getTimeLeft() > 0))
{
switch ($this->runState)
{
case AK_STATE_NOFILE:
debugMsg(__CLASS__ . '::_run() - Reading file header');
$status = $this->readFileHeader();
if ($status)
{
// Send start of file notification
$message = new stdClass;
$message->type = 'startfile';
$message->content = new stdClass;
$message->content->realfile = $this->fileHeader->file;
$message->content->file = $this->fileHeader->file;
$message->content->uncompressed = $this->fileHeader->uncompressed;
if (array_key_exists('realfile', get_object_vars($this->fileHeader)))
{
$message->content->realfile = $this->fileHeader->realFile;
}
if (array_key_exists('compressed', get_object_vars($this->fileHeader)))
{
$message->content->compressed = $this->fileHeader->compressed;
}
else
{
$message->content->compressed = 0;
}
debugMsg(__CLASS__ . '::_run() - Preparing to extract ' . $message->content->realfile);
$this->notify($message);
}
else
{
debugMsg(__CLASS__ . '::_run() - Could not read file header');
}
break;
case AK_STATE_HEADER:
case AK_STATE_DATA:
debugMsg(__CLASS__ . '::_run() - Processing file data');
$status = $this->processFileData();
break;
case AK_STATE_DATAREAD:
case AK_STATE_POSTPROC:
debugMsg(__CLASS__ . '::_run() - Calling post-processing class');
$this->postProcEngine->timestamp = $this->fileHeader->timestamp;
$status = $this->postProcEngine->process();
$this->propagateFromObject($this->postProcEngine);
$this->runState = AK_STATE_DONE;
break;
case AK_STATE_DONE:
default:
if ($status)
{
debugMsg(__CLASS__ . '::_run() - Finished extracting file');
// Send end of file notification
$message = new stdClass;
$message->type = 'endfile';
$message->content = new stdClass;
if (array_key_exists('realfile', get_object_vars($this->fileHeader)))
{
$message->content->realfile = $this->fileHeader->realFile;
}
else
{
$message->content->realfile = $this->fileHeader->file;
}
$message->content->file = $this->fileHeader->file;
if (array_key_exists('compressed', get_object_vars($this->fileHeader)))
{
$message->content->compressed = $this->fileHeader->compressed;
}
else
{
$message->content->compressed = 0;
}
$message->content->uncompressed = $this->fileHeader->uncompressed;
$this->notify($message);
}
$this->runState = AK_STATE_NOFILE;
break;
}
}
$error = $this->getError();
if (!$status && ($this->runState == AK_STATE_NOFILE) && empty($error))
{
debugMsg(__CLASS__ . '::_run() - Just finished');
// We just finished
$this->setState('postrun');
}
elseif (!empty($error))
{
debugMsg(__CLASS__ . '::_run() - Halted with an error:');
debugMsg($error);
$this->setState('error', $error);
}
}
/**
* Concrete classes must use this method to read the file header
*
* @return bool True if reading the file was successful, false if an error occurred or we reached end of archive
*/
protected abstract function readFileHeader();
/**
* Concrete classes must use this method to process file data. It must set $runState to AK_STATE_DATAREAD when
* it's finished processing the file data.
*
* @return bool True if processing the file data was successful, false if an error occurred
*/
protected abstract function processFileData();
protected function _finalize()
{
// Nothing to do
$this->setState('finished');
}
/**
* Opens the next part file for reading
*/
protected function nextFile()
{
debugMsg('Current part is ' . $this->currentPartNumber . '; opening the next part');
++$this->currentPartNumber;
if ($this->currentPartNumber > (count($this->archiveList) - 1))
{
$this->setState('postrun');
return false;
}
else
{
if (is_resource($this->fp))
{
@fclose($this->fp);
}
debugMsg('Opening file ' . $this->archiveList[$this->currentPartNumber]);
$this->fp = @fopen($this->archiveList[$this->currentPartNumber], 'rb');
if ($this->fp === false)
{
debugMsg('Could not open file - crash imminent');
$this->setError(AKText::sprintf('ERR_COULD_NOT_OPEN_ARCHIVE_PART', $this->archiveList[$this->currentPartNumber]));
}
fseek($this->fp, 0);
$this->currentPartOffset = 0;
return true;
}
}
/**
* Returns true if we have reached the end of file
*
* @param $local bool True to return EOF of the local file, false (default) to return if we have reached the end of
* the archive set
*
* @return bool True if we have reached End Of File
*/
protected function isEOF($local = false)
{
$eof = @feof($this->fp);
if (!$eof)
{
// Border case: right at the part's end (eeeek!!!). For the life of me, I don't understand why
// feof() doesn't report true. It expects the fp to be positioned *beyond* the EOF to report
// true. Incredible! :(
$position = @ftell($this->fp);
$filesize = @filesize($this->archiveList[$this->currentPartNumber]);
if ($filesize <= 0)
{
// 2Gb or more files on a 32 bit version of PHP tend to get screwed up. Meh.
$eof = false;
}
elseif ($position >= $filesize)
{
$eof = true;
}
}
if ($local)
{
return $eof;
}
else
{
return $eof && ($this->currentPartNumber >= (count($this->archiveList) - 1));
}
}
/**
* Tries to make a directory user-writable so that we can write a file to it
*
* @param $path string A path to a file
*/
protected function setCorrectPermissions($path)
{
static $rootDir = null;
if (is_null($rootDir))
{
$rootDir = rtrim(AKFactory::get('kickstart.setup.destdir', ''), '/\\');
}
$directory = rtrim(dirname($path), '/\\');
if ($directory != $rootDir)
{
// Is this an unwritable directory?
if (!is_writable($directory))
{
$this->postProcEngine->chmod($directory, 0755);
}
}
$this->postProcEngine->chmod($path, 0644);
}
/**
* Reads data from the archive and notifies the observer with the 'reading' message
*
* @param $fp
* @param $length
*/
protected function fread($fp, $length = null)
{
if (is_numeric($length))
{
if ($length > 0)
{
$data = fread($fp, $length);
}
else
{
$data = fread($fp, PHP_INT_MAX);
}
}
else
{
$data = fread($fp, PHP_INT_MAX);
}
if ($data === false)
{
$data = '';
}
// Send start of file notification
$message = new stdClass;
$message->type = 'reading';
$message->content = new stdClass;
$message->content->length = strlen($data);
$this->notify($message);
return $data;
}
/**
* Removes the configured $removePath from the path $path
*
* @param string $path The path to reduce
*
* @return string The reduced path
*/
protected function removePath($path)
{
if (empty($this->removePath))
{
return $path;
}
if (strpos($path, $this->removePath) === 0)
{
$path = substr($path, strlen($this->removePath));
$path = ltrim($path, '/\\');
}
return $path;
}
}
/**
* File post processor engines base class
*/
abstract class AKAbstractPostproc extends AKAbstractObject
{
/** @var int The UNIX timestamp of the file's desired modification date */
public $timestamp = 0;
/** @var string The current (real) file path we'll have to process */
protected $filename = null;
/** @var int The requested permissions */
protected $perms = 0755;
/** @var string The temporary file path we gave to the unarchiver engine */
protected $tempFilename = null;
/**
* Processes the current file, e.g. moves it from temp to final location by FTP
*/
abstract public function process();
/**
* The unarchiver tells us the path to the filename it wants to extract and we give it
* a different path instead.
*
* @param string $filename The path to the real file
* @param int $perms The permissions we need the file to have
*
* @return string The path to the temporary file
*/
abstract public function processFilename($filename, $perms = 0755);
/**
* Recursively creates a directory if it doesn't exist
*
* @param string $dirName The directory to create
* @param int $perms The permissions to give to that directory
*/
abstract public function createDirRecursive($dirName, $perms);
abstract public function chmod($file, $perms);
abstract public function unlink($file);
abstract public function rmdir($directory);
abstract public function rename($from, $to);
}
/**
* Descendants of this class can be used in the unarchiver's observer methods (attach, detach and notify)
*
* @author Nicholas
*
*/
abstract class AKAbstractPartObserver
{
abstract public function update($object, $message);
}
/**
* Direct file writer
*/
class AKPostprocDirect extends AKAbstractPostproc
{
public function process()
{
$restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
if ($restorePerms)
{
@chmod($this->filename, $this->perms);
}
else
{
if (@is_file($this->filename))
{
@chmod($this->filename, 0644);
}
else
{
@chmod($this->filename, 0755);
}
}
if ($this->timestamp > 0)
{
@touch($this->filename, $this->timestamp);
}
if (substr($this->filename, -4) === '.php')
{
$this->clearFileInOPCache($this->filename);
}
return true;
}
public function processFilename($filename, $perms = 0755)
{
$this->perms = $perms;
$this->filename = $filename;
return $filename;
}
public function createDirRecursive($dirName, $perms)
{
if (AKFactory::get('kickstart.setup.dryrun', '0'))
{
return true;
}
if (@mkdir($dirName, 0755, true))
{
@chmod($dirName, 0755);
return true;
}
$root = AKFactory::get('kickstart.setup.destdir');
$root = rtrim(str_replace('\\', '/', $root), '/');
$dir = rtrim(str_replace('\\', '/', $dirName), '/');
if (strpos($dir, $root) === 0)
{
$dir = ltrim(substr($dir, strlen($root)), '/');
$root .= '/';
}
else
{
$root = '';
}
if (empty($dir))
{
return true;
}
$dirArray = explode('/', $dir);
$path = '';
foreach ($dirArray as $dir)
{
$path .= $dir . '/';
$ret = is_dir($root . $path) ? true : @mkdir($root . $path);
if (!$ret)
{
// Is this a file instead of a directory?
if (is_file($root . $path))
{
$this->clearFileInOPCache($root . $path);
@unlink($root . $path);
$ret = @mkdir($root . $path);
}
if (!$ret)
{
$this->setError(AKText::sprintf('COULDNT_CREATE_DIR', $path));
return false;
}
}
// Try to set new directory permissions to 0755
@chmod($root . $path, $perms);
}
return true;
}
public function chmod($file, $perms)
{
if (AKFactory::get('kickstart.setup.dryrun', '0'))
{
return true;
}
return @chmod($file, $perms);
}
public function unlink($file)
{
$this->clearFileInOPCache($file);
return @unlink($file);
}
public function rmdir($directory)
{
return @rmdir($directory);
}
public function rename($from, $to)
{
$this->clearFileInOPCache($from);
$ret = @rename($from, $to);
$this->clearFileInOPCache($to);
return $ret;
}
public function clearFileInOPCache($file){
if (ini_get('opcache.enable')
&& function_exists('opcache_invalidate')
&& (!ini_get('opcache.restrict_api') || stripos(realpath($_SERVER['SCRIPT_FILENAME']), ini_get('opcache.restrict_api')) === 0))
{
\opcache_invalidate($file, true);
}
}
}
/**
* JPA archive extraction class
*/
class AKUnarchiverJPA extends AKAbstractUnarchiver
{
protected $archiveHeaderData = array();
protected function readArchiveHeader()
{
debugMsg('Preparing to read archive header');
// Initialize header data array
$this->archiveHeaderData = new stdClass();
// Open the first part
debugMsg('Opening the first part');
$this->nextFile();
// Fail for unreadable files
if ($this->fp === false)
{
debugMsg('Could not open the first part');
return false;
}
// Read the signature
$sig = fread($this->fp, 3);
if ($sig != 'JPA')
{
// Not a JPA file
debugMsg('Invalid archive signature');
$this->setError(AKText::_('ERR_NOT_A_JPA_FILE'));
return false;
}
// Read and parse header length
$header_length_array = unpack('v', fread($this->fp, 2));
$header_length = $header_length_array[1];
// Read and parse the known portion of header data (14 bytes)
$bin_data = fread($this->fp, 14);
$header_data = unpack('Cmajor/Cminor/Vcount/Vuncsize/Vcsize', $bin_data);
// Load any remaining header data (forward compatibility)
$rest_length = $header_length - 19;
if ($rest_length > 0)
{
$junk = fread($this->fp, $rest_length);
}
else
{
$junk = '';
}
// Temporary array with all the data we read
$temp = array(
'signature' => $sig,
'length' => $header_length,
'major' => $header_data['major'],
'minor' => $header_data['minor'],
'filecount' => $header_data['count'],
'uncompressedsize' => $header_data['uncsize'],
'compressedsize' => $header_data['csize'],
'unknowndata' => $junk
);
// Array-to-object conversion
foreach ($temp as $key => $value)
{
$this->archiveHeaderData->{$key} = $value;
}
debugMsg('Header data:');
debugMsg('Length : ' . $header_length);
debugMsg('Major : ' . $header_data['major']);
debugMsg('Minor : ' . $header_data['minor']);
debugMsg('File count : ' . $header_data['count']);
debugMsg('Uncompressed size : ' . $header_data['uncsize']);
debugMsg('Compressed size : ' . $header_data['csize']);
$this->currentPartOffset = @ftell($this->fp);
$this->dataReadLength = 0;
return true;
}
/**
* Concrete classes must use this method to read the file header
*
* @return bool True if reading the file was successful, false if an error occurred or we reached end of archive
*/
protected function readFileHeader()
{
// If the current part is over, proceed to the next part please
if ($this->isEOF(true))
{
debugMsg('Archive part EOF; moving to next file');
$this->nextFile();
}
$this->currentPartOffset = ftell($this->fp);
debugMsg("Reading file signature; part $this->currentPartNumber, offset $this->currentPartOffset");
// Get and decode Entity Description Block
$signature = fread($this->fp, 3);
$this->fileHeader = new stdClass();
$this->fileHeader->timestamp = 0;
// Check signature
if ($signature != 'JPF')
{
if ($this->isEOF(true))
{
// This file is finished; make sure it's the last one
$this->nextFile();
if (!$this->isEOF(false))
{
debugMsg('Invalid file signature before end of archive encountered');
$this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset));
return false;
}
// We're just finished
return false;
}
else
{
$screwed = true;
if (AKFactory::get('kickstart.setup.ignoreerrors', false))
{
debugMsg('Invalid file block signature; launching heuristic file block signature scanner');
$screwed = !$this->heuristicFileHeaderLocator();
if (!$screwed)
{
$signature = 'JPF';
}
else
{
debugMsg('Heuristics failed. Brace yourself for the imminent crash.');
}
}
if ($screwed)
{
debugMsg('Invalid file block signature');
// This is not a file block! The archive is corrupt.
$this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset));
return false;
}
}
}
// This a JPA Entity Block. Process the header.
$isBannedFile = false;
// Read length of EDB and of the Entity Path Data
$length_array = unpack('vblocksize/vpathsize', fread($this->fp, 4));
// Read the path data
if ($length_array['pathsize'] > 0)
{
$file = fread($this->fp, $length_array['pathsize']);
}
else
{
$file = '';
}
// Handle file renaming
$isRenamed = false;
if (is_array($this->renameFiles) && (count($this->renameFiles) > 0))
{
if (array_key_exists($file, $this->renameFiles))
{
$file = $this->renameFiles[$file];
$isRenamed = true;
}
}
// Handle directory renaming
$isDirRenamed = false;
if (is_array($this->renameDirs) && (count($this->renameDirs) > 0))
{
if (array_key_exists(dirname($file), $this->renameDirs))
{
$file = rtrim($this->renameDirs[dirname($file)], '/') . '/' . basename($file);
$isRenamed = true;
$isDirRenamed = true;
}
}
// Read and parse the known data portion
$bin_data = fread($this->fp, 14);
$header_data = unpack('Ctype/Ccompression/Vcompsize/Vuncompsize/Vperms', $bin_data);
// Read any unknown data
$restBytes = $length_array['blocksize'] - (21 + $length_array['pathsize']);
if ($restBytes > 0)
{
// Start reading the extra fields
while ($restBytes >= 4)
{
$extra_header_data = fread($this->fp, 4);
$extra_header = unpack('vsignature/vlength', $extra_header_data);
$restBytes -= 4;
$extra_header['length'] -= 4;
switch ($extra_header['signature'])
{
case 256:
// File modified timestamp
if ($extra_header['length'] > 0)
{
$bindata = fread($this->fp, $extra_header['length']);
$restBytes -= $extra_header['length'];
$timestamps = unpack('Vmodified', substr($bindata, 0, 4));
$filectime = $timestamps['modified'];
$this->fileHeader->timestamp = $filectime;
}
break;
default:
// Unknown field
if ($extra_header['length'] > 0)
{
$junk = fread($this->fp, $extra_header['length']);
$restBytes -= $extra_header['length'];
}
break;
}
}
if ($restBytes > 0)
{
$junk = fread($this->fp, $restBytes);
}
}
$compressionType = $header_data['compression'];
// Populate the return array
$this->fileHeader->file = $file;
$this->fileHeader->compressed = $header_data['compsize'];
$this->fileHeader->uncompressed = $header_data['uncompsize'];
switch ($header_data['type'])
{
case 0:
$this->fileHeader->type = 'dir';
break;
case 1:
$this->fileHeader->type = 'file';
break;
case 2:
$this->fileHeader->type = 'link';
break;
}
switch ($compressionType)
{
case 0:
$this->fileHeader->compression = 'none';
break;
case 1:
$this->fileHeader->compression = 'gzip';
break;
case 2:
$this->fileHeader->compression = 'bzip2';
break;
}
$this->fileHeader->permissions = $header_data['perms'];
// Find hard-coded banned files
if ((basename($this->fileHeader->file) == ".") || (basename($this->fileHeader->file) == ".."))
{
$isBannedFile = true;
}
// Also try to find banned files passed in class configuration
if ((count($this->skipFiles) > 0) && (!$isRenamed))
{
if (in_array($this->fileHeader->file, $this->skipFiles))
{
$isBannedFile = true;
}
}
// If we have a banned file, let's skip it
if ($isBannedFile)
{
debugMsg('Skipping file ' . $this->fileHeader->file);
// Advance the file pointer, skipping exactly the size of the compressed data
$seekleft = $this->fileHeader->compressed;
while ($seekleft > 0)
{
// Ensure that we can seek past archive part boundaries
$curSize = @filesize($this->archiveList[$this->currentPartNumber]);
$curPos = @ftell($this->fp);
$canSeek = $curSize - $curPos;
if ($canSeek > $seekleft)
{
$canSeek = $seekleft;
}
@fseek($this->fp, $canSeek, SEEK_CUR);
$seekleft -= $canSeek;
if ($seekleft)
{
$this->nextFile();
}
}
$this->currentPartOffset = @ftell($this->fp);
$this->runState = AK_STATE_DONE;
return true;
}
// Remove the removePath, if any
$this->fileHeader->file = $this->removePath($this->fileHeader->file);
// Last chance to prepend a path to the filename
if (!empty($this->addPath) && !$isDirRenamed)
{
$this->fileHeader->file = $this->addPath . $this->fileHeader->file;
}
// Get the translated path name
$restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
if ($this->fileHeader->type == 'file')
{
// Regular file; ask the postproc engine to process its filename
if ($restorePerms)
{
$this->fileHeader->realFile =
$this->postProcEngine->processFilename($this->fileHeader->file, $this->fileHeader->permissions);
}
else
{
$this->fileHeader->realFile = $this->postProcEngine->processFilename($this->fileHeader->file);
}
}
elseif ($this->fileHeader->type == 'dir')
{
$dir = $this->fileHeader->file;
// Directory; just create it
if ($restorePerms)
{
$this->postProcEngine->createDirRecursive($this->fileHeader->file, $this->fileHeader->permissions);
}
else
{
$this->postProcEngine->createDirRecursive($this->fileHeader->file, 0755);
}
$this->postProcEngine->processFilename(null);
}
else
{
// Symlink; do not post-process
$this->postProcEngine->processFilename(null);
}
$this->createDirectory();
// Header is read
$this->runState = AK_STATE_HEADER;
$this->dataReadLength = 0;
return true;
}
protected function heuristicFileHeaderLocator()
{
$ret = false;
$fullEOF = false;
while (!$ret && !$fullEOF)
{
$this->currentPartOffset = @ftell($this->fp);
if ($this->isEOF(true))
{
$this->nextFile();
}
if ($this->isEOF(false))
{
$fullEOF = true;
continue;
}
// Read 512Kb
$chunk = fread($this->fp, 524288);
$size_read = mb_strlen($chunk, '8bit');
//$pos = strpos($chunk, 'JPF');
$pos = mb_strpos($chunk, 'JPF', 0, '8bit');
if ($pos !== false)
{
// We found it!
$this->currentPartOffset += $pos + 3;
@fseek($this->fp, $this->currentPartOffset, SEEK_SET);
$ret = true;
}
else
{
// Not yet found :(
$this->currentPartOffset = @ftell($this->fp);
}
}
return $ret;
}
/**
* Creates the directory this file points to
*/
protected function createDirectory()
{
if (AKFactory::get('kickstart.setup.dryrun', '0'))
{
return true;
}
// Do we need to create a directory?
if (empty($this->fileHeader->realFile))
{
$this->fileHeader->realFile = $this->fileHeader->file;
}
$lastSlash = strrpos($this->fileHeader->realFile, '/');
$dirName = substr($this->fileHeader->realFile, 0, $lastSlash);
$perms = $this->flagRestorePermissions ? $this->fileHeader->permissions : 0755;
$ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($dirName);
if (($this->postProcEngine->createDirRecursive($dirName, $perms) == false) && (!$ignore))
{
$this->setError(AKText::sprintf('COULDNT_CREATE_DIR', $dirName));
return false;
}
else
{
return true;
}
}
/**
* Concrete classes must use this method to process file data. It must set $runState to AK_STATE_DATAREAD when
* it's finished processing the file data.
*
* @return bool True if processing the file data was successful, false if an error occurred
*/
protected function processFileData()
{
switch ($this->fileHeader->type)
{
case 'dir':
return $this->processTypeDir();
break;
case 'link':
return $this->processTypeLink();
break;
case 'file':
switch ($this->fileHeader->compression)
{
case 'none':
return $this->processTypeFileUncompressed();
break;
case 'gzip':
case 'bzip2':
return $this->processTypeFileCompressedSimple();
break;
}
break;
default:
debugMsg('Unknown file type ' . $this->fileHeader->type);
break;
}
// Unknown file type. Play dumb.
return true;
}
/**
* Process the file data of a directory entry
*
* @return bool
*/
private function processTypeDir()
{
// Directory entries in the JPA do not have file data, therefore we're done processing the entry
$this->runState = AK_STATE_DATAREAD;
return true;
}
/**
* Process the file data of a link entry
*
* @return bool
*/
private function processTypeLink()
{
$readBytes = 0;
$toReadBytes = 0;
$leftBytes = $this->fileHeader->compressed;
$data = '';
while ($leftBytes > 0)
{
$toReadBytes = ($leftBytes > $this->chunkSize) ? $this->chunkSize : $leftBytes;
$mydata = $this->fread($this->fp, $toReadBytes);
$reallyReadBytes = akstringlen($mydata);
$data .= $mydata;
$leftBytes -= $reallyReadBytes;
if ($reallyReadBytes < $toReadBytes)
{
// We read less than requested! Why? Did we hit local EOF?
if ($this->isEOF(true) && !$this->isEOF(false))
{
// Yeap. Let's go to the next file
$this->nextFile();
}
else
{
debugMsg('End of local file before reading all data with no more parts left. The archive is corrupt or truncated.');
// Nope. The archive is corrupt
$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));
return false;
}
}
}
$filename = isset($this->fileHeader->realFile) ? $this->fileHeader->realFile : $this->fileHeader->file;
if (!AKFactory::get('kickstart.setup.dryrun', '0'))
{
// Try to remove an existing file or directory by the same name
if (file_exists($filename))
{
@unlink($filename);
@rmdir($filename);
}
// Remove any trailing slash
if (substr($filename, -1) == '/')
{
$filename = substr($filename, 0, -1);
}
// Create the symlink - only possible within PHP context. There's no support built in the FTP protocol, so no postproc use is possible here :(
@symlink($data, $filename);
}
$this->runState = AK_STATE_DATAREAD;
return true; // No matter if the link was created!
}
private function processTypeFileUncompressed()
{
// Uncompressed files are being processed in small chunks, to avoid timeouts
if (($this->dataReadLength == 0) && !AKFactory::get('kickstart.setup.dryrun', '0'))
{
// Before processing file data, ensure permissions are adequate
$this->setCorrectPermissions($this->fileHeader->file);
}
// Open the output file
if (!AKFactory::get('kickstart.setup.dryrun', '0'))
{
$ignore =
AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file);
if ($this->dataReadLength == 0)
{
$outfp = @fopen($this->fileHeader->realFile, 'wb');
}
else
{
$outfp = @fopen($this->fileHeader->realFile, 'ab');
}
// Can we write to the file?
if (($outfp === false) && (!$ignore))
{
// An error occurred
debugMsg('Could not write to output file');
$this->setError(AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile));
return false;
}
}
// Does the file have any data, at all?
if ($this->fileHeader->compressed == 0)
{
// No file data!
if (!AKFactory::get('kickstart.setup.dryrun', '0') && is_resource($outfp))
{
@fclose($outfp);
}
$this->runState = AK_STATE_DATAREAD;
return true;
}
// Reference to the global timer
$timer = AKFactory::getTimer();
$toReadBytes = 0;
$leftBytes = $this->fileHeader->compressed - $this->dataReadLength;
// Loop while there's data to read and enough time to do it
while (($leftBytes > 0) && ($timer->getTimeLeft() > 0))
{
$toReadBytes = ($leftBytes > $this->chunkSize) ? $this->chunkSize : $leftBytes;
$data = $this->fread($this->fp, $toReadBytes);
$reallyReadBytes = akstringlen($data);
$leftBytes -= $reallyReadBytes;
$this->dataReadLength += $reallyReadBytes;
if ($reallyReadBytes < $toReadBytes)
{
// We read less than requested! Why? Did we hit local EOF?
if ($this->isEOF(true) && !$this->isEOF(false))
{
// Yeap. Let's go to the next file
$this->nextFile();
}
else
{
// Nope. The archive is corrupt
debugMsg('Not enough data in file. The archive is truncated or corrupt.');
$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));
return false;
}
}
if (!AKFactory::get('kickstart.setup.dryrun', '0'))
{
if (is_resource($outfp))
{
@fwrite($outfp, $data);
}
}
}
// Close the file pointer
if (!AKFactory::get('kickstart.setup.dryrun', '0'))
{
if (is_resource($outfp))
{
@fclose($outfp);
}
}
// Was this a pre-timeout bail out?
if ($leftBytes > 0)
{
$this->runState = AK_STATE_DATA;
}
else
{
// Oh! We just finished!
$this->runState = AK_STATE_DATAREAD;
$this->dataReadLength = 0;
}
return true;
}
private function processTypeFileCompressedSimple()
{
if (!AKFactory::get('kickstart.setup.dryrun', '0'))
{
// Before processing file data, ensure permissions are adequate
$this->setCorrectPermissions($this->fileHeader->file);
// Open the output file
$outfp = @fopen($this->fileHeader->realFile, 'wb');
// Can we write to the file?
$ignore =
AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file);
if (($outfp === false) && (!$ignore))
{
// An error occurred
debugMsg('Could not write to output file');
$this->setError(AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile));
return false;
}
}
// Does the file have any data, at all?
if ($this->fileHeader->compressed == 0)
{
// No file data!
if (!AKFactory::get('kickstart.setup.dryrun', '0'))
{
if (is_resource($outfp))
{
@fclose($outfp);
}
}
$this->runState = AK_STATE_DATAREAD;
return true;
}
// Simple compressed files are processed as a whole; we can't do chunk processing
$zipData = $this->fread($this->fp, $this->fileHeader->compressed);
while (akstringlen($zipData) < $this->fileHeader->compressed)
{
// End of local file before reading all data, but have more archive parts?
if ($this->isEOF(true) && !$this->isEOF(false))
{
// Yeap. Read from the next file
$this->nextFile();
$bytes_left = $this->fileHeader->compressed - akstringlen($zipData);
$zipData .= $this->fread($this->fp, $bytes_left);
}
else
{
debugMsg('End of local file before reading all data with no more parts left. The archive is corrupt or truncated.');
$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));
return false;
}
}
if ($this->fileHeader->compression == 'gzip')
{
$unzipData = gzinflate($zipData);
}
elseif ($this->fileHeader->compression == 'bzip2')
{
$unzipData = bzdecompress($zipData);
}
unset($zipData);
// Write to the file.
if (!AKFactory::get('kickstart.setup.dryrun', '0') && is_resource($outfp))
{
@fwrite($outfp, $unzipData, $this->fileHeader->uncompressed);
@fclose($outfp);
}
unset($unzipData);
$this->runState = AK_STATE_DATAREAD;
return true;
}
}
/**
* ZIP archive extraction class
*
* Since the file data portion of ZIP and JPA are similarly structured (it's empty for dirs,
* linked node name for symlinks, dumped binary data for no compressions and dumped gzipped
* binary data for gzip compression) we just have to subclass AKUnarchiverJPA and change the
* header reading bits. Reusable code ;)
*/
class AKUnarchiverZIP extends AKUnarchiverJPA
{
var $expectDataDescriptor = false;
protected function readArchiveHeader()
{
debugMsg('Preparing to read archive header');
// Initialize header data array
$this->archiveHeaderData = new stdClass();
// Open the first part
debugMsg('Opening the first part');
$this->nextFile();
// Fail for unreadable files
if ($this->fp === false)
{
debugMsg('The first part is not readable');
return false;
}
// Read a possible multipart signature
$sigBinary = fread($this->fp, 4);
$headerData = unpack('Vsig', $sigBinary);
// Roll back if it's not a multipart archive
if ($headerData['sig'] == 0x04034b50)
{
debugMsg('The archive is not multipart');
fseek($this->fp, -4, SEEK_CUR);
}
else
{
debugMsg('The archive is multipart');
}
$multiPartSigs = array(
0x08074b50, // Multi-part ZIP
0x30304b50, // Multi-part ZIP (alternate)
0x04034b50 // Single file
);
if (!in_array($headerData['sig'], $multiPartSigs))
{
debugMsg('Invalid header signature ' . dechex($headerData['sig']));
$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));
return false;
}
$this->currentPartOffset = @ftell($this->fp);
debugMsg('Current part offset after reading header: ' . $this->currentPartOffset);
$this->dataReadLength = 0;
return true;
}
/**
* Concrete classes must use this method to read the file header
*
* @return bool True if reading the file was successful, false if an error occurred or we reached end of archive
*/
protected function readFileHeader()
{
// If the current part is over, proceed to the next part please
if ($this->isEOF(true))
{
debugMsg('Opening next archive part');
$this->nextFile();
}
$this->currentPartOffset = ftell($this->fp);
if ($this->expectDataDescriptor)
{
// The last file had bit 3 of the general purpose bit flag set. This means that we have a
// 12 byte data descriptor we need to skip. To make things worse, there might also be a 4
// byte optional data descriptor header (0x08074b50).
$junk = @fread($this->fp, 4);
$junk = unpack('Vsig', $junk);
if ($junk['sig'] == 0x08074b50)
{
// Yes, there was a signature
$junk = @fread($this->fp, 12);
debugMsg('Data descriptor (w/ header) skipped at ' . (ftell($this->fp) - 12));
}
else
{
// No, there was no signature, just read another 8 bytes
$junk = @fread($this->fp, 8);
debugMsg('Data descriptor (w/out header) skipped at ' . (ftell($this->fp) - 8));
}
// And check for EOF, too
if ($this->isEOF(true))
{
debugMsg('EOF before reading header');
$this->nextFile();
}
}
// Get and decode Local File Header
$headerBinary = fread($this->fp, 30);
$headerData =
unpack('Vsig/C2ver/vbitflag/vcompmethod/vlastmodtime/vlastmoddate/Vcrc/Vcompsize/Vuncomp/vfnamelen/veflen', $headerBinary);
// Check signature
if (!($headerData['sig'] == 0x04034b50))
{
debugMsg('Not a file signature at ' . (ftell($this->fp) - 4));
// The signature is not the one used for files. Is this a central directory record (i.e. we're done)?
if ($headerData['sig'] == 0x02014b50)
{
debugMsg('EOCD signature at ' . (ftell($this->fp) - 4));
// End of ZIP file detected. We'll just skip to the end of file...
while ($this->nextFile())
{
}
@fseek($this->fp, 0, SEEK_END); // Go to EOF
return false;
}
else
{
debugMsg('Invalid signature ' . dechex($headerData['sig']) . ' at ' . ftell($this->fp));
$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));
return false;
}
}
// If bit 3 of the bitflag is set, expectDataDescriptor is true
$this->expectDataDescriptor = ($headerData['bitflag'] & 4) == 4;
$this->fileHeader = new stdClass();
$this->fileHeader->timestamp = 0;
// Read the last modified data and time
$lastmodtime = $headerData['lastmodtime'];
$lastmoddate = $headerData['lastmoddate'];
if ($lastmoddate && $lastmodtime)
{
// ----- Extract time
$v_hour = ($lastmodtime & 0xF800) >> 11;
$v_minute = ($lastmodtime & 0x07E0) >> 5;
$v_seconde = ($lastmodtime & 0x001F) * 2;
// ----- Extract date
$v_year = (($lastmoddate & 0xFE00) >> 9) + 1980;
$v_month = ($lastmoddate & 0x01E0) >> 5;
$v_day = $lastmoddate & 0x001F;
// ----- Get UNIX date format
$this->fileHeader->timestamp = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year);
}
$isBannedFile = false;
$this->fileHeader->compressed = $headerData['compsize'];
$this->fileHeader->uncompressed = $headerData['uncomp'];
$nameFieldLength = $headerData['fnamelen'];
$extraFieldLength = $headerData['eflen'];
// Read filename field
$this->fileHeader->file = fread($this->fp, $nameFieldLength);
// Handle file renaming
$isRenamed = false;
if (is_array($this->renameFiles) && (count($this->renameFiles) > 0))
{
if (array_key_exists($this->fileHeader->file, $this->renameFiles))
{
$this->fileHeader->file = $this->renameFiles[$this->fileHeader->file];
$isRenamed = true;
}
}
// Handle directory renaming
$isDirRenamed = false;
if (is_array($this->renameDirs) && (count($this->renameDirs) > 0))
{
if (array_key_exists(dirname($this->fileHeader->file), $this->renameDirs))
{
$file =
rtrim($this->renameDirs[dirname($this->fileHeader->file)], '/') . '/' . basename($this->fileHeader->file);
$isRenamed = true;
$isDirRenamed = true;
}
}
// Read extra field if present
if ($extraFieldLength > 0)
{
$extrafield = fread($this->fp, $extraFieldLength);
}
debugMsg('*' . ftell($this->fp) . ' IS START OF ' . $this->fileHeader->file . ' (' . $this->fileHeader->compressed . ' bytes)');
// Decide filetype -- Check for directories
$this->fileHeader->type = 'file';
if (strrpos($this->fileHeader->file, '/') == strlen($this->fileHeader->file) - 1)
{
$this->fileHeader->type = 'dir';
}
// Decide filetype -- Check for symbolic links
if (($headerData['ver1'] == 10) && ($headerData['ver2'] == 3))
{
$this->fileHeader->type = 'link';
}
switch ($headerData['compmethod'])
{
case 0:
$this->fileHeader->compression = 'none';
break;
case 8:
$this->fileHeader->compression = 'gzip';
break;
}
// Find hard-coded banned files
if ((basename($this->fileHeader->file) == ".") || (basename($this->fileHeader->file) == ".."))
{
$isBannedFile = true;
}
// Also try to find banned files passed in class configuration
if ((count($this->skipFiles) > 0) && (!$isRenamed))
{
if (in_array($this->fileHeader->file, $this->skipFiles))
{
$isBannedFile = true;
}
}
// If we have a banned file, let's skip it
if ($isBannedFile)
{
// Advance the file pointer, skipping exactly the size of the compressed data
$seekleft = $this->fileHeader->compressed;
while ($seekleft > 0)
{
// Ensure that we can seek past archive part boundaries
$curSize = @filesize($this->archiveList[$this->currentPartNumber]);
$curPos = @ftell($this->fp);
$canSeek = $curSize - $curPos;
if ($canSeek > $seekleft)
{
$canSeek = $seekleft;
}
@fseek($this->fp, $canSeek, SEEK_CUR);
$seekleft -= $canSeek;
if ($seekleft)
{
$this->nextFile();
}
}
$this->currentPartOffset = @ftell($this->fp);
$this->runState = AK_STATE_DONE;
return true;
}
// Remove the removePath, if any
$this->fileHeader->file = $this->removePath($this->fileHeader->file);
// Last chance to prepend a path to the filename
if (!empty($this->addPath) && !$isDirRenamed)
{
$this->fileHeader->file = $this->addPath . $this->fileHeader->file;
}
// Get the translated path name
if ($this->fileHeader->type == 'file')
{
$this->fileHeader->realFile = $this->postProcEngine->processFilename($this->fileHeader->file);
}
elseif ($this->fileHeader->type == 'dir')
{
$this->fileHeader->timestamp = 0;
$dir = $this->fileHeader->file;
$this->postProcEngine->createDirRecursive($this->fileHeader->file, 0755);
$this->postProcEngine->processFilename(null);
}
else
{
// Symlink; do not post-process
$this->fileHeader->timestamp = 0;
$this->postProcEngine->processFilename(null);
}
$this->createDirectory();
// Header is read
$this->runState = AK_STATE_HEADER;
return true;
}
}
/**
* Timer class
*/
class AKCoreTimer extends AKAbstractObject
{
/** @var int Maximum execution time allowance per step */
private $max_exec_time = null;
/** @var int Timestamp of execution start */
private $start_time = null;
/**
* Public constructor, creates the timer object and calculates the execution time limits
*/
public function __construct()
{
// Initialize start time
$this->start_time = $this->microtime_float();
// Get configured max time per step and bias
$config_max_exec_time = AKFactory::get('kickstart.tuning.max_exec_time', 14);
$bias = AKFactory::get('kickstart.tuning.run_time_bias', 75) / 100;
// Get PHP's maximum execution time (our upper limit)
if (@function_exists('ini_get'))
{
$php_max_exec_time = @ini_get("maximum_execution_time");
if ((!is_numeric($php_max_exec_time)) || ($php_max_exec_time == 0))
{
// If we have no time limit, set a hard limit of about 10 seconds
// (safe for Apache and IIS timeouts, verbose enough for users)
$php_max_exec_time = 14;
}
}
else
{
// If ini_get is not available, use a rough default
$php_max_exec_time = 14;
}
// Apply an arbitrary correction to counter CMS load time
$php_max_exec_time--;
// Apply bias
$php_max_exec_time = $php_max_exec_time * $bias;
$config_max_exec_time = $config_max_exec_time * $bias;
// Use the most appropriate time limit value
if ($config_max_exec_time > $php_max_exec_time)
{
$this->max_exec_time = $php_max_exec_time;
}
else
{
$this->max_exec_time = $config_max_exec_time;
}
}
/**
* Returns the current timestampt in decimal seconds
*/
private function microtime_float()
{
list($usec, $sec) = explode(" ", microtime());
return ((float) $usec + (float) $sec);
}
/**
* Wake-up function to reset internal timer when we get unserialized
*/
public function __wakeup()
{
// Re-initialize start time on wake-up
$this->start_time = $this->microtime_float();
}
/**
* Gets the number of seconds left, before we hit the "must break" threshold
*
* @return float
*/
public function getTimeLeft()
{
return $this->max_exec_time - $this->getRunningTime();
}
/**
* Gets the time elapsed since object creation/unserialization, effectively how
* long Akeeba Engine has been processing data
*
* @return float
*/
public function getRunningTime()
{
return $this->microtime_float() - $this->start_time;
}
/**
* Enforce the minimum execution time
*/
public function enforce_min_exec_time()
{
// Try to get a sane value for PHP's maximum_execution_time INI parameter
if (@function_exists('ini_get'))
{
$php_max_exec = @ini_get("maximum_execution_time");
}
else
{
$php_max_exec = 10;
}
if (($php_max_exec == "") || ($php_max_exec == 0))
{
$php_max_exec = 10;
}
// Decrease $php_max_exec time by 500 msec we need (approx.) to tear down
// the application, as well as another 500msec added for rounding
// error purposes. Also make sure this is never gonna be less than 0.
$php_max_exec = max($php_max_exec * 1000 - 1000, 0);
// Get the "minimum execution time per step" Akeeba Backup configuration variable
$minexectime = AKFactory::get('kickstart.tuning.min_exec_time', 0);
if (!is_numeric($minexectime))
{
$minexectime = 0;
}
// Make sure we are not over PHP's time limit!
if ($minexectime > $php_max_exec)
{
$minexectime = $php_max_exec;
}
// Get current running time
$elapsed_time = $this->getRunningTime() * 1000;
// Only run a sleep delay if we haven't reached the minexectime execution time
if (($minexectime > $elapsed_time) && ($elapsed_time > 0))
{
$sleep_msec = $minexectime - $elapsed_time;
if (function_exists('usleep'))
{
usleep(1000 * $sleep_msec);
}
elseif (function_exists('time_nanosleep'))
{
$sleep_sec = floor($sleep_msec / 1000);
$sleep_nsec = 1000000 * ($sleep_msec - ($sleep_sec * 1000));
time_nanosleep($sleep_sec, $sleep_nsec);
}
elseif (function_exists('time_sleep_until'))
{
$until_timestamp = time() + $sleep_msec / 1000;
time_sleep_until($until_timestamp);
}
elseif (function_exists('sleep'))
{
$sleep_sec = ceil($sleep_msec / 1000);
sleep($sleep_sec);
}
}
elseif ($elapsed_time > 0)
{
// No sleep required, even if user configured us to be able to do so.
}
}
/**
* Reset the timer. It should only be used in CLI mode!
*/
public function resetTime()
{
$this->start_time = $this->microtime_float();
}
/**
* @param int $max_exec_time
*/
public function setMaxExecTime($max_exec_time)
{
$this->max_exec_time = $max_exec_time;
}
}
/**
* A filesystem scanner which uses opendir()
*/
class AKUtilsLister extends AKAbstractObject
{
public function &getFiles($folder, $pattern = '*')
{
// Initialize variables
$arr = array();
$false = false;
if (!is_dir($folder))
{
return $false;
}
$handle = @opendir($folder);
// If directory is not accessible, just return FALSE
if ($handle === false)
{
$this->setWarning('Unreadable directory ' . $folder);
return $false;
}
while (($file = @readdir($handle)) !== false)
{
if (!fnmatch($pattern, $file))
{
continue;
}
if (($file != '.') && ($file != '..'))
{
$ds =
($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ?
'' : DIRECTORY_SEPARATOR;
$dir = $folder . $ds . $file;
$isDir = is_dir($dir);
if (!$isDir)
{
$arr[] = $dir;
}
}
}
@closedir($handle);
return $arr;
}
public function &getFolders($folder, $pattern = '*')
{
// Initialize variables
$arr = array();
$false = false;
if (!is_dir($folder))
{
return $false;
}
$handle = @opendir($folder);
// If directory is not accessible, just return FALSE
if ($handle === false)
{
$this->setWarning('Unreadable directory ' . $folder);
return $false;
}
while (($file = @readdir($handle)) !== false)
{
if (!fnmatch($pattern, $file))
{
continue;
}
if (($file != '.') && ($file != '..'))
{
$ds =
($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ?
'' : DIRECTORY_SEPARATOR;
$dir = $folder . $ds . $file;
$isDir = is_dir($dir);
if ($isDir)
{
$arr[] = $dir;
}
}
}
@closedir($handle);
return $arr;
}
}
/**
* A simple INI-based i18n engine
*/
class AKText extends AKAbstractObject
{
/**
* The default (en_GB) translation used when no other translation is available
*
* @var array
*/
private $default_translation = array(
'ERR_NOT_A_JPA_FILE' => 'The file is not a JPA archive',
'ERR_CORRUPT_ARCHIVE' => 'The archive file is corrupt, truncated or archive parts are missing',
'ERR_INVALID_LOGIN' => 'Invalid login',
'COULDNT_CREATE_DIR' => 'Could not create %s folder',
'COULDNT_WRITE_FILE' => 'Could not open %s for writing.',
'INVALID_FILE_HEADER' => 'Invalid header in archive file, part %s, offset %s',
'ERR_COULD_NOT_OPEN_ARCHIVE_PART' => 'Could not open archive part file %s for reading. Check that the file exists, is readable by the web server and is not in a directory made out of reach by chroot, open_basedir restrictions or any other restriction put in place by your host.',
);
/**
* The array holding the translation keys
*
* @var array
*/
private $strings;
/**
* The currently detected language (ISO code)
*
* @var string
*/
private $language;
/*
* Initializes the translation engine
* @return AKText
*/
public function __construct()
{
// Start with the default translation
$this->strings = $this->default_translation;
// Try loading the translation file in English, if it exists
$this->loadTranslation('en-GB');
// Try loading the translation file in the browser's preferred language, if it exists
$this->getBrowserLanguage();
if (!is_null($this->language))
{
$this->loadTranslation();
}
}
private function loadTranslation($lang = null)
{
if (defined('KSLANGDIR'))
{
$dirname = KSLANGDIR;
}
else
{
$dirname = KSROOTDIR;
}
$basename = basename(__FILE__, '.php') . '.ini';
if (empty($lang))
{
$lang = $this->language;
}
$translationFilename = $dirname . DIRECTORY_SEPARATOR . $lang . '.' . $basename;
if (!@file_exists($translationFilename) && ($basename != 'kickstart.ini'))
{
$basename = 'kickstart.ini';
$translationFilename = $dirname . DIRECTORY_SEPARATOR . $lang . '.' . $basename;
}
if (!@file_exists($translationFilename))
{
return;
}
$temp = self::parse_ini_file($translationFilename, false);
if (!is_array($this->strings))
{
$this->strings = array();
}
if (empty($temp))
{
$this->strings = array_merge($this->default_translation, $this->strings);
}
else
{
$this->strings = array_merge($this->strings, $temp);
}
}
/**
* A PHP based INI file parser.
*
* Thanks to asohn ~at~ aircanopy ~dot~ net for posting this handy function on
* the parse_ini_file page on http://gr.php.net/parse_ini_file
*
* @param string $file Filename to process
* @param bool $process_sections True to also process INI sections
*
* @return array An associative array of sections, keys and values
* @access private
*/
public static function parse_ini_file($file, $process_sections = false, $raw_data = false)
{
$process_sections = ($process_sections !== true) ? false : true;
if (!$raw_data)
{
$ini = @file($file);
}
else
{
$ini = $file;
}
if (count($ini) == 0)
{
return array();
}
$sections = array();
$values = array();
$result = array();
$globals = array();
$i = 0;
if (!empty($ini))
{
foreach ($ini as $line)
{
$line = trim($line);
$line = str_replace("\t", " ", $line);
// Comments
if (!preg_match('/^[a-zA-Z0-9[]/', $line))
{
continue;
}
// Sections
if ($line[0] == '[')
{
$tmp = explode(']', $line);
$sections[] = trim(substr($tmp[0], 1));
$i++;
continue;
}
// Key-value pair
list($key, $value) = explode('=', $line, 2);
$key = trim($key);
$value = trim($value);
if (strstr($value, ";"))
{
$tmp = explode(';', $value);
if (count($tmp) == 2)
{
if ((($value[0] != '"') && ($value[0] != "'")) ||
preg_match('/^".*"\s*;/', $value) || preg_match('/^".*;[^"]*$/', $value) ||
preg_match("/^'.*'\s*;/", $value) || preg_match("/^'.*;[^']*$/", $value)
)
{
$value = $tmp[0];
}
}
else
{
if ($value[0] == '"')
{
$value = preg_replace('/^"(.*)".*/', '$1', $value);
}
elseif ($value[0] == "'")
{
$value = preg_replace("/^'(.*)'.*/", '$1', $value);
}
else
{
$value = $tmp[0];
}
}
}
$value = trim($value);
$value = trim($value, "'\"");
if ($i == 0)
{
if (substr($line, -1, 2) == '[]')
{
$globals[$key][] = $value;
}
else
{
$globals[$key] = $value;
}
}
else
{
if (substr($line, -1, 2) == '[]')
{
$values[$i - 1][$key][] = $value;
}
else
{
$values[$i - 1][$key] = $value;
}
}
}
}
for ($j = 0; $j < $i; $j++)
{
if ($process_sections === true)
{
$result[$sections[$j]] = $values[$j];
}
else
{
$result[] = $values[$j];
}
}
return $result + $globals;
}
public function getBrowserLanguage()
{
// Detection code from Full Operating system language detection, by Harald Hope
// Retrieved from http://techpatterns.com/downloads/php_language_detection.php
$user_languages = array();
//check to see if language is set
if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"]))
{
$languages = strtolower($_SERVER["HTTP_ACCEPT_LANGUAGE"]);
// $languages = ' fr-ch;q=0.3, da, en-us;q=0.8, en;q=0.5, fr;q=0.3';
// need to remove spaces from strings to avoid error
$languages = str_replace(' ', '', $languages);
$languages = explode(",", $languages);
foreach ($languages as $language_list)
{
// pull out the language, place languages into array of full and primary
// string structure:
$temp_array = array();
// slice out the part before ; on first step, the part before - on second, place into array
$temp_array[0] = substr($language_list, 0, strcspn($language_list, ';'));//full language
$temp_array[1] = substr($language_list, 0, 2);// cut out primary language
if ((strlen($temp_array[0]) == 5) && ((substr($temp_array[0], 2, 1) == '-') || (substr($temp_array[0], 2, 1) == '_')))
{
$langLocation = strtoupper(substr($temp_array[0], 3, 2));
$temp_array[0] = $temp_array[1] . '-' . $langLocation;
}
//place this array into main $user_languages language array
$user_languages[] = $temp_array;
}
}
else// if no languages found
{
$user_languages[0] = array('', ''); //return blank array.
}
$this->language = null;
$basename = basename(__FILE__, '.php') . '.ini';
// Try to match main language part of the filename, irrespective of the location, e.g. de_DE will do if de_CH doesn't exist.
if (class_exists('AKUtilsLister'))
{
$fs = new AKUtilsLister();
$iniFiles = $fs->getFiles(KSROOTDIR, '*.' . $basename);
if (empty($iniFiles) && ($basename != 'kickstart.ini'))
{
$basename = 'kickstart.ini';
$iniFiles = $fs->getFiles(KSROOTDIR, '*.' . $basename);
}
}
else
{
$iniFiles = null;
}
if (is_array($iniFiles))
{
foreach ($user_languages as $languageStruct)
{
if (is_null($this->language))
{
// Get files matching the main lang part
$iniFiles = $fs->getFiles(KSROOTDIR, $languageStruct[1] . '-??.' . $basename);
if (count($iniFiles) > 0)
{
$filename = $iniFiles[0];
$filename = substr($filename, strlen(KSROOTDIR) + 1);
$this->language = substr($filename, 0, 5);
}
else
{
$this->language = null;
}
}
}
}
if (is_null($this->language))
{
// Try to find a full language match
foreach ($user_languages as $languageStruct)
{
if (@file_exists($languageStruct[0] . '.' . $basename) && is_null($this->language))
{
$this->language = $languageStruct[0];
}
}
}
else
{
// Do we have an exact match?
foreach ($user_languages as $languageStruct)
{
if (substr($this->language, 0, strlen($languageStruct[1])) == $languageStruct[1])
{
if (file_exists($languageStruct[0] . '.' . $basename))
{
$this->language = $languageStruct[0];
}
}
}
}
// Now, scan for full language based on the partial match
}
public static function sprintf($key)
{
$text = self::getInstance();
$args = func_get_args();
if (count($args) > 0)
{
$args[0] = $text->_($args[0]);
return @call_user_func_array('sprintf', $args);
}
return '';
}
/**
* Singleton pattern for Language
*
* @return AKText The global AKText instance
*/
public static function &getInstance()
{
static $instance;
if (!is_object($instance))
{
$instance = new AKText();
}
return $instance;
}
public static function _($string)
{
$text = self::getInstance();
$key = strtoupper($string);
$key = substr($key, 0, 1) == '_' ? substr($key, 1) : $key;
if (isset ($text->strings[$key]))
{
$string = $text->strings[$key];
}
else
{
if (defined($string))
{
$string = constant($string);
}
}
return $string;
}
public function dumpLanguage()
{
$out = '';
foreach ($this->strings as $key => $value)
{
$out .= "$key=$value\n";
}
return $out;
}
public function asJavascript()
{
$out = '';
foreach ($this->strings as $key => $value)
{
$key = addcslashes($key, '\\\'"');
$value = addcslashes($value, '\\\'"');
if (!empty($out))
{
$out .= ",\n";
}
$out .= "'$key':\t'$value'";
}
return $out;
}
public function resetTranslation()
{
$this->strings = $this->default_translation;
}
public function addDefaultLanguageStrings($stringList = array())
{
if (!is_array($stringList))
{
return;
}
if (empty($stringList))
{
return;
}
$this->strings = array_merge($stringList, $this->strings);
}
}
/**
* The Akeeba Kickstart Factory class
* This class is reponssible for instanciating all Akeeba Kicsktart classes
*/
class AKFactory
{
/** @var array A list of instantiated objects */
private $objectlist = array();
/** @var array Simple hash data storage */
private $varlist = array();
/** @var self Static instance */
private static $instance = null;
/** Private constructor makes sure we can't directly instantiate the class */
private function __construct()
{
}
/**
* Gets a serialized snapshot of the Factory for safekeeping (hibernate)
*
* @return string The serialized snapshot of the Factory
*/
public static function serialize()
{
$engine = self::getUnarchiver();
$engine->shutdown();
$serialized = serialize(self::getInstance());
if (function_exists('base64_encode') && function_exists('base64_decode'))
{
$serialized = base64_encode($serialized);
}
return $serialized;
}
/**
* Gets the unarchiver engine
*/
public static function &getUnarchiver($configOverride = null)
{
static $class_name;
if (!empty($configOverride))
{
if ($configOverride['reset'])
{
$class_name = null;
}
}
if (empty($class_name))
{
$filetype = self::get('kickstart.setup.filetype', null);
if (empty($filetype))
{
$filename = self::get('kickstart.setup.sourcefile', null);
$basename = basename($filename);
$baseextension = strtoupper(substr($basename, -3));
switch ($baseextension)
{
case 'JPA':
$filetype = 'JPA';
break;
case 'JPS':
$filetype = 'JPS';
break;
case 'ZIP':
$filetype = 'ZIP';
break;
default:
die('Invalid archive type or extension in file ' . $filename);
break;
}
}
$class_name = 'AKUnarchiver' . ucfirst($filetype);
}
$destdir = self::get('kickstart.setup.destdir', null);
if (empty($destdir))
{
$destdir = KSROOTDIR;
}
$object = self::getClassInstance($class_name);
if ($object->getState() == 'init')
{
$sourcePath = self::get('kickstart.setup.sourcepath', '');
$sourceFile = self::get('kickstart.setup.sourcefile', '');
if (!empty($sourcePath))
{
$sourceFile = rtrim($sourcePath, '/\\') . '/' . $sourceFile;
}
// Initialize the object –– Any change here MUST be reflected to echoHeadJavascript (default values)
$config = array(
'filename' => $sourceFile,
'restore_permissions' => self::get('kickstart.setup.restoreperms', 0),
'post_proc' => self::get('kickstart.procengine', 'direct'),
'add_path' => self::get('kickstart.setup.targetpath', $destdir),
'remove_path' => self::get('kickstart.setup.removepath', ''),
'rename_files' => self::get('kickstart.setup.renamefiles', array(
'.htaccess' => 'htaccess.bak', 'php.ini' => 'php.ini.bak', 'web.config' => 'web.config.bak',
'.user.ini' => '.user.ini.bak'
)),
'skip_files' => self::get('kickstart.setup.skipfiles', array(
basename(__FILE__), 'kickstart.php', 'abiautomation.ini', 'htaccess.bak', 'php.ini.bak',
'cacert.pem'
)),
'ignoredirectories' => self::get('kickstart.setup.ignoredirectories', array(
'tmp', 'log', 'logs'
)),
);
if (!defined('KICKSTART'))
{
// In restore.php mode we have to exclude the restoration.php files
$moreSkippedFiles = array(
// Akeeba Backup for Joomla!
'administrator/components/com_akeeba/restoration.php',
// Joomla! Update
'administrator/components/com_joomlaupdate/restoration.php',
// Akeeba Backup for WordPress
'wp-content/plugins/akeebabackupwp/app/restoration.php',
'wp-content/plugins/akeebabackupcorewp/app/restoration.php',
'wp-content/plugins/akeebabackup/app/restoration.php',
'wp-content/plugins/akeebabackupwpcore/app/restoration.php',
// Akeeba Solo
'app/restoration.php',
);
$config['skip_files'] = array_merge($config['skip_files'], $moreSkippedFiles);
}
if (!empty($configOverride))
{
$config = array_merge($config, $configOverride);
}
$object->setup($config);
}
return $object;
}
// ========================================================================
// Public factory interface
// ========================================================================
public static function get($key, $default = null)
{
$self = self::getInstance();
if (array_key_exists($key, $self->varlist))
{
return $self->varlist[$key];
}
else
{
return $default;
}
}
/**
* Gets a single, internally used instance of the Factory
*
* @param string $serialized_data [optional] Serialized data to spawn the instance from
*
* @return AKFactory A reference to the unique Factory object instance
*/
protected static function &getInstance($serialized_data = null)
{
if (!is_object(self::$instance) || !is_null($serialized_data))
{
if (!is_null($serialized_data))
{
self::$instance = unserialize($serialized_data);
}
else
{
self::$instance = new self();
}
}
return self::$instance;
}
/**
* Internal function which instanciates a class named $class_name.
* The autoloader
*
* @param string $class_name
*
* @return object
*/
protected static function &getClassInstance($class_name)
{
$self = self::getInstance();
if (!isset($self->objectlist[$class_name]))
{
$self->objectlist[$class_name] = new $class_name;
}
return $self->objectlist[$class_name];
}
// ========================================================================
// Public hash data storage interface
// ========================================================================
/**
* Regenerates the full Factory state from a serialized snapshot (resume)
*
* @param string $serialized_data The serialized snapshot to resume from
*/
public static function unserialize($serialized_data)
{
if (function_exists('base64_encode') && function_exists('base64_decode'))
{
$serialized_data = base64_decode($serialized_data);
}
self::getInstance($serialized_data);
}
/**
* Reset the internal factory state, freeing all previously created objects
*/
public static function nuke()
{
self::$instance = null;
}
// ========================================================================
// Akeeba Kickstart classes
// ========================================================================
public static function set($key, $value)
{
$self = self::getInstance();
$self->varlist[$key] = $value;
}
/**
* Gets the post processing engine
*
* @param string $proc_engine
*/
public static function &getPostProc($proc_engine = null)
{
static $class_name;
if (empty($class_name))
{
if (empty($proc_engine))
{
$proc_engine = self::get('kickstart.procengine', 'direct');
}
$class_name = 'AKPostproc' . ucfirst($proc_engine);
}
return self::getClassInstance($class_name);
}
/**
* Get the a reference to the Akeeba Engine's timer
*
* @return AKCoreTimer
*/
public static function &getTimer()
{
/** @noinspection PhpIncompatibleReturnTypeInspection */
return self::getClassInstance('AKCoreTimer');
}
}
/**
* Interface for AES encryption adapters
*/
interface AKEncryptionAESAdapterInterface
{
/**
* Decrypts a string. Returns the raw binary ciphertext, zero-padded.
*
* @param string $plainText The plaintext to encrypt
* @param string $key The raw binary key (will be zero-padded or chopped if its size is different than the block size)
*
* @return string The raw encrypted binary string.
*/
public function decrypt($plainText, $key);
/**
* Returns the encryption block size in bytes
*
* @return int
*/
public function getBlockSize();
/**
* Is this adapter supported?
*
* @return bool
*/
public function isSupported();
}
/**
* Abstract AES encryption class
*/
abstract class AKEncryptionAESAdapterAbstract
{
/**
* Trims or zero-pads a key / IV
*
* @param string $key The key or IV to treat
* @param int $size The block size of the currently used algorithm
*
* @return null|string Null if $key is null, treated string of $size byte length otherwise
*/
public function resizeKey($key, $size)
{
if (empty($key))
{
return null;
}
$keyLength = strlen($key);
if (function_exists('mb_strlen'))
{
$keyLength = mb_strlen($key, 'ASCII');
}
if ($keyLength == $size)
{
return $key;
}
if ($keyLength > $size)
{
if (function_exists('mb_substr'))
{
return mb_substr($key, 0, $size, 'ASCII');
}
return substr($key, 0, $size);
}
return $key . str_repeat("\0", ($size - $keyLength));
}
/**
* Returns null bytes to append to the string so that it's zero padded to the specified block size
*
* @param string $string The binary string which will be zero padded
* @param int $blockSize The block size
*
* @return string The zero bytes to append to the string to zero pad it to $blockSize
*/
protected function getZeroPadding($string, $blockSize)
{
$stringSize = strlen($string);
if (function_exists('mb_strlen'))
{
$stringSize = mb_strlen($string, 'ASCII');
}
if ($stringSize == $blockSize)
{
return '';
}
if ($stringSize < $blockSize)
{
return str_repeat("\0", $blockSize - $stringSize);
}
$paddingBytes = $stringSize % $blockSize;
return str_repeat("\0", $blockSize - $paddingBytes);
}
}
class Mcrypt extends AKEncryptionAESAdapterAbstract implements AKEncryptionAESAdapterInterface
{
protected $cipherType = MCRYPT_RIJNDAEL_128;
protected $cipherMode = MCRYPT_MODE_CBC;
public function decrypt($cipherText, $key)
{
$iv_size = $this->getBlockSize();
$key = $this->resizeKey($key, $iv_size);
$iv = substr($cipherText, 0, $iv_size);
$cipherText = substr($cipherText, $iv_size);
$plainText = mcrypt_decrypt($this->cipherType, $key, $cipherText, $this->cipherMode, $iv);
return $plainText;
}
public function isSupported()
{
if (!function_exists('mcrypt_get_key_size'))
{
return false;
}
if (!function_exists('mcrypt_get_iv_size'))
{
return false;
}
if (!function_exists('mcrypt_create_iv'))
{
return false;
}
if (!function_exists('mcrypt_encrypt'))
{
return false;
}
if (!function_exists('mcrypt_decrypt'))
{
return false;
}
if (!function_exists('mcrypt_list_algorithms'))
{
return false;
}
if (!function_exists('hash'))
{
return false;
}
if (!function_exists('hash_algos'))
{
return false;
}
$algorightms = mcrypt_list_algorithms();
if (!in_array('rijndael-128', $algorightms))
{
return false;
}
if (!in_array('rijndael-192', $algorightms))
{
return false;
}
if (!in_array('rijndael-256', $algorightms))
{
return false;
}
$algorightms = hash_algos();
if (!in_array('sha256', $algorightms))
{
return false;
}
return true;
}
public function getBlockSize()
{
return mcrypt_get_iv_size($this->cipherType, $this->cipherMode);
}
}
class OpenSSL extends AKEncryptionAESAdapterAbstract implements AKEncryptionAESAdapterInterface
{
/**
* The OpenSSL options for encryption / decryption
*
* @var int
*/
protected $openSSLOptions = 0;
/**
* The encryption method to use
*
* @var string
*/
protected $method = 'aes-128-cbc';
public function __construct()
{
$this->openSSLOptions = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
}
public function decrypt($cipherText, $key)
{
$iv_size = $this->getBlockSize();
$key = $this->resizeKey($key, $iv_size);
$iv = substr($cipherText, 0, $iv_size);
$cipherText = substr($cipherText, $iv_size);
$plainText = openssl_decrypt($cipherText, $this->method, $key, $this->openSSLOptions, $iv);
return $plainText;
}
public function isSupported()
{
if (!function_exists('openssl_get_cipher_methods'))
{
return false;
}
if (!function_exists('openssl_random_pseudo_bytes'))
{
return false;
}
if (!function_exists('openssl_cipher_iv_length'))
{
return false;
}
if (!function_exists('openssl_encrypt'))
{
return false;
}
if (!function_exists('openssl_decrypt'))
{
return false;
}
if (!function_exists('hash'))
{
return false;
}
if (!function_exists('hash_algos'))
{
return false;
}
$algorightms = openssl_get_cipher_methods();
if (!in_array('aes-128-cbc', $algorightms))
{
return false;
}
$algorightms = hash_algos();
if (!in_array('sha256', $algorightms))
{
return false;
}
return true;
}
/**
* @return int
*/
public function getBlockSize()
{
return openssl_cipher_iv_length($this->method);
}
}
/**
* AES implementation in PHP (c) Chris Veness 2005-2016.
* Right to use and adapt is granted for under a simple creative commons attribution
* licence. No warranty of any form is offered.
*
* Heavily modified for Akeeba Backup by Nicholas K. Dionysopoulos
* Also added AES-128 CBC mode (with mcrypt and OpenSSL) on top of AES CTR
*/
class AKEncryptionAES
{
// Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [�5.1.1]
protected static $Sbox =
array(0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16);
// Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [�5.2]
protected static $Rcon = array(
array(0x00, 0x00, 0x00, 0x00),
array(0x01, 0x00, 0x00, 0x00),
array(0x02, 0x00, 0x00, 0x00),
array(0x04, 0x00, 0x00, 0x00),
array(0x08, 0x00, 0x00, 0x00),
array(0x10, 0x00, 0x00, 0x00),
array(0x20, 0x00, 0x00, 0x00),
array(0x40, 0x00, 0x00, 0x00),
array(0x80, 0x00, 0x00, 0x00),
array(0x1b, 0x00, 0x00, 0x00),
array(0x36, 0x00, 0x00, 0x00));
protected static $passwords = array();
/**
* The algorithm to use for PBKDF2. Must be a supported hash_hmac algorithm. Default: sha1
*
* @var string
*/
private static $pbkdf2Algorithm = 'sha1';
/**
* Number of iterations to use for PBKDF2
*
* @var int
*/
private static $pbkdf2Iterations = 1000;
/**
* Should we use a static salt for PBKDF2?
*
* @var int
*/
private static $pbkdf2UseStaticSalt = 0;
/**
* The static salt to use for PBKDF2
*
* @var string
*/
private static $pbkdf2StaticSalt = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
/**
* Encrypt a text using AES encryption in Counter mode of operation
* - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
*
* Unicode multi-byte character safe
*
* @param string $plaintext Source text to be encrypted
* @param string $password The password to use to generate a key
* @param int $nBits Number of bits to be used in the key (128, 192, or 256)
*
* @return string Encrypted text
*/
public static function AESEncryptCtr($plaintext, $password, $nBits)
{
$blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
if (!($nBits == 128 || $nBits == 192 || $nBits == 256))
{
return '';
} // standard allows 128/192/256 bit keys
// note PHP (5) gives us plaintext and password in UTF8 encoding!
// use AES itself to encrypt password to get cipher key (using plain password as source for
// key expansion) - gives us well encrypted key
$nBytes = $nBits / 8; // no bytes in key
$pwBytes = array();
for ($i = 0; $i < $nBytes; $i++)
{
$pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff;
}
$key = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
$key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long
// initialise counter block (NIST SP800-38A �B.2): millisecond time-stamp for nonce in
// 1st 8 bytes, block counter in 2nd 8 bytes
$counterBlock = array();
$nonce = floor(microtime(true) * 1000); // timestamp: milliseconds since 1-Jan-1970
$nonceSec = floor($nonce / 1000);
$nonceMs = $nonce % 1000;
// encode nonce with seconds in 1st 4 bytes, and (repeated) ms part filling 2nd 4 bytes
for ($i = 0; $i < 4; $i++)
{
$counterBlock[$i] = self::urs($nonceSec, $i * 8) & 0xff;
}
for ($i = 0; $i < 4; $i++)
{
$counterBlock[$i + 4] = $nonceMs & 0xff;
}
// and convert it to a string to go on the front of the ciphertext
$ctrTxt = '';
for ($i = 0; $i < 8; $i++)
{
$ctrTxt .= chr($counterBlock[$i]);
}
// generate key schedule - an expansion of the key into distinct Key Rounds for each round
$keySchedule = self::KeyExpansion($key);
$blockCount = ceil(strlen($plaintext) / $blockSize);
$ciphertxt = array(); // ciphertext as array of strings
for ($b = 0; $b < $blockCount; $b++)
{
// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
// done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
for ($c = 0; $c < 4; $c++)
{
$counterBlock[15 - $c] = self::urs($b, $c * 8) & 0xff;
}
for ($c = 0; $c < 4; $c++)
{
$counterBlock[15 - $c - 4] = self::urs($b / 0x100000000, $c * 8);
}
$cipherCntr = self::Cipher($counterBlock, $keySchedule); // -- encrypt counter block --
// block size is reduced on final block
$blockLength = $b < $blockCount - 1 ? $blockSize : (strlen($plaintext) - 1) % $blockSize + 1;
$cipherByte = array();
for ($i = 0; $i < $blockLength; $i++)
{ // -- xor plaintext with ciphered counter byte-by-byte --
$cipherByte[$i] = $cipherCntr[$i] ^ ord(substr($plaintext, $b * $blockSize + $i, 1));
$cipherByte[$i] = chr($cipherByte[$i]);
}
$ciphertxt[$b] = implode('', $cipherByte); // escape troublesome characters in ciphertext
}
// implode is more efficient than repeated string concatenation
$ciphertext = $ctrTxt . implode('', $ciphertxt);
$ciphertext = base64_encode($ciphertext);
return $ciphertext;
}
/**
* AES Cipher function: encrypt 'input' with Rijndael algorithm
*
* @param array $input Message as byte-array (16 bytes)
* @param array $w key schedule as 2D byte-array (Nr+1 x Nb bytes) -
* generated from the cipher key by KeyExpansion()
*
* @return string Ciphertext as byte-array (16 bytes)
*/
protected static function Cipher($input, $w)
{ // main Cipher function [�5.1]
$Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
$Nr = count($w) / $Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys
$state = array(); // initialise 4xNb byte-array 'state' with input [�3.4]
for ($i = 0; $i < 4 * $Nb; $i++)
{
$state[$i % 4][floor($i / 4)] = $input[$i];
}
$state = self::AddRoundKey($state, $w, 0, $Nb);
for ($round = 1; $round < $Nr; $round++)
{ // apply Nr rounds
$state = self::SubBytes($state, $Nb);
$state = self::ShiftRows($state, $Nb);
$state = self::MixColumns($state);
$state = self::AddRoundKey($state, $w, $round, $Nb);
}
$state = self::SubBytes($state, $Nb);
$state = self::ShiftRows($state, $Nb);
$state = self::AddRoundKey($state, $w, $Nr, $Nb);
$output = array(4 * $Nb); // convert state to 1-d array before returning [�3.4]
for ($i = 0; $i < 4 * $Nb; $i++)
{
$output[$i] = $state[$i % 4][floor($i / 4)];
}
return $output;
}
protected static function AddRoundKey($state, $w, $rnd, $Nb)
{ // xor Round Key into state S [�5.1.4]
for ($r = 0; $r < 4; $r++)
{
for ($c = 0; $c < $Nb; $c++)
{
$state[$r][$c] ^= $w[$rnd * 4 + $c][$r];
}
}
return $state;
}
protected static function SubBytes($s, $Nb)
{ // apply SBox to state S [�5.1.1]
for ($r = 0; $r < 4; $r++)
{
for ($c = 0; $c < $Nb; $c++)
{
$s[$r][$c] = self::$Sbox[$s[$r][$c]];
}
}
return $s;
}
protected static function ShiftRows($s, $Nb)
{ // shift row r of state S left by r bytes [�5.1.2]
$t = array(4);
for ($r = 1; $r < 4; $r++)
{
for ($c = 0; $c < 4; $c++)
{
$t[$c] = $s[$r][($c + $r) % $Nb];
} // shift into temp copy
for ($c = 0; $c < 4; $c++)
{
$s[$r][$c] = $t[$c];
} // and copy back
} // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
return $s; // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
}
protected static function MixColumns($s)
{
// combine bytes of each col of state S [�5.1.3]
for ($c = 0; $c < 4; $c++)
{
$a = array(4); // 'a' is a copy of the current column from 's'
$b = array(4); // 'b' is a�{02} in GF(2^8)
for ($i = 0; $i < 4; $i++)
{
$a[$i] = $s[$i][$c];
$b[$i] = $s[$i][$c] & 0x80 ? $s[$i][$c] << 1 ^ 0x011b : $s[$i][$c] << 1;
}
// a[n] ^ b[n] is a�{03} in GF(2^8)
$s[0][$c] = $b[0] ^ $a[1] ^ $b[1] ^ $a[2] ^ $a[3]; // 2*a0 + 3*a1 + a2 + a3
$s[1][$c] = $a[0] ^ $b[1] ^ $a[2] ^ $b[2] ^ $a[3]; // a0 * 2*a1 + 3*a2 + a3
$s[2][$c] = $a[0] ^ $a[1] ^ $b[2] ^ $a[3] ^ $b[3]; // a0 + a1 + 2*a2 + 3*a3
$s[3][$c] = $a[0] ^ $b[0] ^ $a[1] ^ $a[2] ^ $b[3]; // 3*a0 + a1 + a2 + 2*a3
}
return $s;
}
/**
* Key expansion for Rijndael Cipher(): performs key expansion on cipher key
* to generate a key schedule
*
* @param array $key Cipher key byte-array (16 bytes)
*
* @return array Key schedule as 2D byte-array (Nr+1 x Nb bytes)
*/
protected static function KeyExpansion($key)
{
// generate Key Schedule from Cipher Key [�5.2]
// block size (in words): no of columns in state (fixed at 4 for AES)
$Nb = 4;
// key length (in words): 4/6/8 for 128/192/256-bit keys
$Nk = (int) (count($key) / 4);
// no of rounds: 10/12/14 for 128/192/256-bit keys
$Nr = $Nk + 6;
$w = array();
$temp = array();
for ($i = 0; $i < $Nk; $i++)
{
$r = array($key[4 * $i], $key[4 * $i + 1], $key[4 * $i + 2], $key[4 * $i + 3]);
$w[$i] = $r;
}
for ($i = $Nk; $i < ($Nb * ($Nr + 1)); $i++)
{
$w[$i] = array();
for ($t = 0; $t < 4; $t++)
{
$temp[$t] = $w[$i - 1][$t];
}
if ($i % $Nk == 0)
{
$temp = self::SubWord(self::RotWord($temp));
for ($t = 0; $t < 4; $t++)
{
$rConIndex = (int) ($i / $Nk);
$temp[$t] ^= self::$Rcon[$rConIndex][$t];
}
}
else if ($Nk > 6 && $i % $Nk == 4)
{
$temp = self::SubWord($temp);
}
for ($t = 0; $t < 4; $t++)
{
$w[$i][$t] = $w[$i - $Nk][$t] ^ $temp[$t];
}
}
return $w;
}
protected static function SubWord($w)
{ // apply SBox to 4-byte word w
for ($i = 0; $i < 4; $i++)
{
$w[$i] = self::$Sbox[$w[$i]];
}
return $w;
}
/*
* Unsigned right shift function, since PHP has neither >>> operator nor unsigned ints
*
* @param a number to be shifted (32-bit integer)
* @param b number of bits to shift a to the right (0..31)
* @return a right-shifted and zero-filled by b bits
*/
protected static function RotWord($w)
{ // rotate 4-byte word w left by one byte
$tmp = $w[0];
for ($i = 0; $i < 3; $i++)
{
$w[$i] = $w[$i + 1];
}
$w[3] = $tmp;
return $w;
}
protected static function urs($a, $b)
{
$a &= 0xffffffff;
$b &= 0x1f; // (bounds check)
if ($a & 0x80000000 && $b > 0)
{ // if left-most bit set
$a = ($a >> 1) & 0x7fffffff; // right-shift one bit & clear left-most bit
$a = $a >> ($b - 1); // remaining right-shifts
}
else
{ // otherwise
$a = ($a >> $b); // use normal right-shift
}
return $a;
}
/**
* Decrypt a text encrypted by AES in counter mode of operation
*
* @param string $ciphertext Source text to be decrypted
* @param string $password The password to use to generate a key
* @param int $nBits Number of bits to be used in the key (128, 192, or 256)
*
* @return string Decrypted text
*/
public static function AESDecryptCtr($ciphertext, $password, $nBits)
{
$blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
if (!($nBits == 128 || $nBits == 192 || $nBits == 256))
{
return '';
}
// standard allows 128/192/256 bit keys
$ciphertext = base64_decode($ciphertext);
// use AES to encrypt password (mirroring encrypt routine)
$nBytes = $nBits / 8; // no bytes in key
$pwBytes = array();
for ($i = 0; $i < $nBytes; $i++)
{
$pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff;
}
$key = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
$key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long
// recover nonce from 1st element of ciphertext
$counterBlock = array();
$ctrTxt = substr($ciphertext, 0, 8);
for ($i = 0; $i < 8; $i++)
{
$counterBlock[$i] = ord(substr($ctrTxt, $i, 1));
}
// generate key schedule
$keySchedule = self::KeyExpansion($key);
// separate ciphertext into blocks (skipping past initial 8 bytes)
$nBlocks = ceil((strlen($ciphertext) - 8) / $blockSize);
$ct = array();
for ($b = 0; $b < $nBlocks; $b++)
{
$ct[$b] = substr($ciphertext, 8 + $b * $blockSize, 16);
}
$ciphertext = $ct; // ciphertext is now array of block-length strings
// plaintext will get generated block-by-block into array of block-length strings
$plaintxt = array();
for ($b = 0; $b < $nBlocks; $b++)
{
// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
for ($c = 0; $c < 4; $c++)
{
$counterBlock[15 - $c] = self::urs($b, $c * 8) & 0xff;
}
for ($c = 0; $c < 4; $c++)
{
$counterBlock[15 - $c - 4] = self::urs(($b + 1) / 0x100000000 - 1, $c * 8) & 0xff;
}
$cipherCntr = self::Cipher($counterBlock, $keySchedule); // encrypt counter block
$plaintxtByte = array();
for ($i = 0; $i < strlen($ciphertext[$b]); $i++)
{
// -- xor plaintext with ciphered counter byte-by-byte --
$plaintxtByte[$i] = $cipherCntr[$i] ^ ord(substr($ciphertext[$b], $i, 1));
$plaintxtByte[$i] = chr($plaintxtByte[$i]);
}
$plaintxt[$b] = implode('', $plaintxtByte);
}
// join array of blocks into single plaintext string
$plaintext = implode('', $plaintxt);
return $plaintext;
}
/**
* AES decryption in CBC mode. This is the standard mode (the CTR methods
* actually use Rijndael-128 in CTR mode, which - technically - isn't AES).
*
* It supports AES-128 only. It assumes that the last 4 bytes
* contain a little-endian unsigned long integer representing the unpadded
* data length.
*
* @since 3.0.1
* @author Nicholas K. Dionysopoulos
*
* @param string $ciphertext The data to encrypt
* @param string $password Encryption password
*
* @return string The plaintext
*/
public static function AESDecryptCBC($ciphertext, $password)
{
$adapter = self::getAdapter();
if (!$adapter->isSupported())
{
return false;
}
// Read the data size
$data_size = unpack('V', substr($ciphertext, -4));
// Do I have a PBKDF2 salt?
$salt = substr($ciphertext, -92, 68);
$rightStringLimit = -4;
$params = self::getKeyDerivationParameters();
$keySizeBytes = $params['keySize'];
$algorithm = $params['algorithm'];
$iterations = $params['iterations'];
$useStaticSalt = $params['useStaticSalt'];
if (substr($salt, 0, 4) == 'JPST')
{
// We have a stored salt. Retrieve it and tell decrypt to process the string minus the last 44 bytes
// (4 bytes for JPST, 16 bytes for the salt, 4 bytes for JPIV, 16 bytes for the IV, 4 bytes for the
// uncompressed string length - note that using PBKDF2 means we're also using a randomized IV per the
// format specification).
$salt = substr($salt, 4);
$rightStringLimit -= 68;
$key = self::pbkdf2($password, $salt, $algorithm, $iterations, $keySizeBytes);
}
elseif ($useStaticSalt)
{
// We have a static salt. Use it for PBKDF2.
$key = self::getStaticSaltExpandedKey($password);
}
else
{
// Get the expanded key from the password. THIS USES THE OLD, INSECURE METHOD.
$key = self::expandKey($password);
}
// Try to get the IV from the data
$iv = substr($ciphertext, -24, 20);
if (substr($iv, 0, 4) == 'JPIV')
{
// We have a stored IV. Retrieve it and tell mdecrypt to process the string minus the last 24 bytes
// (4 bytes for JPIV, 16 bytes for the IV, 4 bytes for the uncompressed string length)
$iv = substr($iv, 4);
$rightStringLimit -= 20;
}
else
{
// No stored IV. Do it the dumb way.
$iv = self::createTheWrongIV($password);
}
// Decrypt
$plaintext = $adapter->decrypt($iv . substr($ciphertext, 0, $rightStringLimit), $key);
// Trim padding, if necessary
if (strlen($plaintext) > $data_size)
{
$plaintext = substr($plaintext, 0, $data_size);
}
return $plaintext;
}
/**
* That's the old way of creating an IV that's definitely not cryptographically sound.
*
* DO NOT USE, EVER, UNLESS YOU WANT TO DECRYPT LEGACY DATA
*
* @param string $password The raw password from which we create an IV in a super bozo way
*
* @return string A 16-byte IV string
*/
public static function createTheWrongIV($password)
{
static $ivs = array();
$key = md5($password);
if (!isset($ivs[$key]))
{
$nBytes = 16; // AES uses a 128 -bit (16 byte) block size, hence the IV size is always 16 bytes
$pwBytes = array();
for ($i = 0; $i < $nBytes; $i++)
{
$pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff;
}
$iv = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
$newIV = '';
foreach ($iv as $int)
{
$newIV .= chr($int);
}
$ivs[$key] = $newIV;
}
return $ivs[$key];
}
/**
* Expand the password to an appropriate 128-bit encryption key
*
* @param string $password
*
* @return string
*
* @since 5.2.0
* @author Nicholas K. Dionysopoulos
*/
public static function expandKey($password)
{
// Try to fetch cached key or create it if it doesn't exist
$nBits = 128;
$lookupKey = md5($password . '-' . $nBits);
if (array_key_exists($lookupKey, self::$passwords))
{
$key = self::$passwords[$lookupKey];
return $key;
}
// use AES itself to encrypt password to get cipher key (using plain password as source for
// key expansion) - gives us well encrypted key.
$nBytes = $nBits / 8; // Number of bytes in key
$pwBytes = array();
for ($i = 0; $i < $nBytes; $i++)
{
$pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff;
}
$key = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
$key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long
$newKey = '';
foreach ($key as $int)
{
$newKey .= chr($int);
}
$key = $newKey;
self::$passwords[$lookupKey] = $key;
return $key;
}
/**
* Returns the correct AES-128 CBC encryption adapter
*
* @return AKEncryptionAESAdapterInterface
*
* @since 5.2.0
* @author Nicholas K. Dionysopoulos
*/
public static function getAdapter()
{
static $adapter = null;
if (is_object($adapter) && ($adapter instanceof AKEncryptionAESAdapterInterface))
{
return $adapter;
}
$adapter = new OpenSSL();
if (!$adapter->isSupported())
{
$adapter = new Mcrypt();
}
return $adapter;
}
/**
* @return string
*/
public static function getPbkdf2Algorithm()
{
return self::$pbkdf2Algorithm;
}
/**
* @param string $pbkdf2Algorithm
* @return void
*/
public static function setPbkdf2Algorithm($pbkdf2Algorithm)
{
self::$pbkdf2Algorithm = $pbkdf2Algorithm;
}
/**
* @return int
*/
public static function getPbkdf2Iterations()
{
return self::$pbkdf2Iterations;
}
/**
* @param int $pbkdf2Iterations
* @return void
*/
public static function setPbkdf2Iterations($pbkdf2Iterations)
{
self::$pbkdf2Iterations = $pbkdf2Iterations;
}
/**
* @return int
*/
public static function getPbkdf2UseStaticSalt()
{
return self::$pbkdf2UseStaticSalt;
}
/**
* @param int $pbkdf2UseStaticSalt
* @return void
*/
public static function setPbkdf2UseStaticSalt($pbkdf2UseStaticSalt)
{
self::$pbkdf2UseStaticSalt = $pbkdf2UseStaticSalt;
}
/**
* @return string
*/
public static function getPbkdf2StaticSalt()
{
return self::$pbkdf2StaticSalt;
}
/**
* @param string $pbkdf2StaticSalt
* @return void
*/
public static function setPbkdf2StaticSalt($pbkdf2StaticSalt)
{
self::$pbkdf2StaticSalt = $pbkdf2StaticSalt;
}
/**
* Get the parameters fed into PBKDF2 to expand the user password into an encryption key. These are the static
* parameters (key size, hashing algorithm and number of iterations). A new salt is used for each encryption block
* to minimize the risk of attacks against the password.
*
* @return array
*/
public static function getKeyDerivationParameters()
{
return array(
'keySize' => 16,
'algorithm' => self::$pbkdf2Algorithm,
'iterations' => self::$pbkdf2Iterations,
'useStaticSalt' => self::$pbkdf2UseStaticSalt,
'staticSalt' => self::$pbkdf2StaticSalt,
);
}
/**
* PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
*
* Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
*
* This implementation of PBKDF2 was originally created by https://defuse.ca
* With improvements by http://www.variations-of-shadow.com
* Modified for Akeeba Engine by Akeeba Ltd (removed unnecessary checks to make it faster)
*
* @param string $password The password.
* @param string $salt A salt that is unique to the password.
* @param string $algorithm The hash algorithm to use. Default is sha1.
* @param int $count Iteration count. Higher is better, but slower. Default: 1000.
* @param int $key_length The length of the derived key in bytes.
*
* @return string A string of $key_length bytes
*/
public static function pbkdf2($password, $salt, $algorithm = 'sha1', $count = 1000, $key_length = 16)
{
if (function_exists("hash_pbkdf2"))
{
return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, true);
}
$hash_length = akstringlen(hash($algorithm, "", true));
$block_count = ceil($key_length / $hash_length);
$output = "";
for ($i = 1; $i <= $block_count; $i++)
{
// $i encoded as 4 bytes, big endian.
$last = $salt . pack("N", $i);
// First iteration
$xorResult = hash_hmac($algorithm, $last, $password, true);
$last = $xorResult;
// Perform the other $count - 1 iterations
for ($j = 1; $j < $count; $j++)
{
$last = hash_hmac($algorithm, $last, $password, true);
$xorResult ^= $last;
}
$output .= $xorResult;
}
return aksubstr($output, 0, $key_length);
}
/**
* Get the expanded key from the user supplied password using a static salt. The results are cached for performance
* reasons.
*
* @param string $password The user-supplied password, UTF-8 encoded.
*
* @return string The expanded key
*/
private static function getStaticSaltExpandedKey($password)
{
$params = self::getKeyDerivationParameters();
$keySizeBytes = $params['keySize'];
$algorithm = $params['algorithm'];
$iterations = $params['iterations'];
$staticSalt = $params['staticSalt'];
$lookupKey = "PBKDF2-$algorithm-$iterations-" . md5($password . $staticSalt);
if (!array_key_exists($lookupKey, self::$passwords))
{
self::$passwords[$lookupKey] = self::pbkdf2($password, $staticSalt, $algorithm, $iterations, $keySizeBytes);
}
return self::$passwords[$lookupKey];
}
}
/**
* The Master Setup will read the configuration parameters from restoration.php or
* the JSON-encoded "configuration" input variable and return the status.
*
* @return bool True if the master configuration was applied to the Factory object
*/
function masterSetup()
{
// ------------------------------------------------------------
// 1. Import basic setup parameters
// ------------------------------------------------------------
$ini_data = null;
// Require restoration.php or fail
$setupFile = 'restoration.php';
if (!file_exists($setupFile))
{
AKFactory::set('kickstart.enabled', false);
return false;
}
// Load restoration.php. It creates a global variable named $restoration_setup
require_once $setupFile;
/** @var array $restoration_setup This is defined in the restoration.php file */
$ini_data = $restoration_setup;
if (empty($ini_data))
{
// No parameters fetched. Darn, how am I supposed to work like that?!
AKFactory::set('kickstart.enabled', false);
return false;
}
AKFactory::set('kickstart.enabled', true);
// Import any data from the $restoration_setup array read from restoration.php
if (!empty($ini_data))
{
foreach ($ini_data as $key => $value)
{
AKFactory::set($key, $value);
}
AKFactory::set('kickstart.enabled', true);
}
// Reinitialize $ini_data
$ini_data = null;
// ------------------------------------------------------------
// 2. Explode JSON parameters into $_REQUEST scope
// ------------------------------------------------------------
// Detect a JSON string in the request variable and store it.
$json = getQueryParam('json', null);
// Remove everything from the request, post and get arrays
if (!empty($_REQUEST))
{
foreach ($_REQUEST as $key => $value)
{
unset($_REQUEST[$key]);
}
}
if (!empty($_POST))
{
foreach ($_POST as $key => $value)
{
unset($_POST[$key]);
}
}
if (!empty($_GET))
{
foreach ($_GET as $key => $value)
{
unset($_GET[$key]);
}
}
// Decrypt a possibly encrypted JSON string
$password = AKFactory::get('kickstart.security.password', null);
if (!empty($json))
{
if (!empty($password))
{
$json = AKEncryptionAES::AESDecryptCtr($json, $password, 128);
if (empty($json))
{
die('###{"status":false,"message":"Invalid login"}###');
}
}
// Get the raw data
$raw = json_decode($json, true);
if (!empty($password) && (empty($raw)))
{
die('###{"status":false,"message":"Invalid login"}###');
}
// Pass all JSON data to the request array
if (!empty($raw))
{
foreach ($raw as $key => $value)
{
$_REQUEST[$key] = $value;
}
}
}
elseif (!empty($password))
{
die('###{"status":false,"message":"Invalid login"}###');
}
// ------------------------------------------------------------
// 3. Try the "factory" variable
// ------------------------------------------------------------
// A "factory" variable will override all other settings.
$serialized = getQueryParam('factory', null);
if (!is_null($serialized))
{
// Get the serialized factory
AKFactory::unserialize($serialized);
AKFactory::set('kickstart.enabled', true);
return true;
}
return AKFactory::get('kickstart.enabled', false);
}
// Mini-controller for restore.php
if (!defined('KICKSTART'))
{
// The observer class, used to report number of files and bytes processed
class RestorationObserver extends AKAbstractPartObserver
{
public $compressedTotal = 0;
public $uncompressedTotal = 0;
public $filesProcessed = 0;
public function update($object, $message)
{
if (!is_object($message))
{
return;
}
if (!array_key_exists('type', get_object_vars($message)))
{
return;
}
if ($message->type == 'startfile')
{
$this->filesProcessed++;
$this->compressedTotal += $message->content->compressed;
$this->uncompressedTotal += $message->content->uncompressed;
}
}
public function __toString()
{
return __CLASS__;
}
}
// Import configuration
masterSetup();
$retArray = array(
'status' => true,
'message' => null
);
$enabled = AKFactory::get('kickstart.enabled', false);
if ($enabled)
{
$task = getQueryParam('task');
switch ($task)
{
case 'ping':
// ping task - really does nothing!
$timer = AKFactory::getTimer();
$timer->enforce_min_exec_time();
break;
/**
* There are two separate steps here since we were using an inefficient restoration intialization method in
* the past. Now both startRestore and stepRestore are identical. The difference in behavior depends
* exclusively on the calling Javascript. If no serialized factory was passed in the request then we start a
* new restoration. If a serialized factory was passed in the request then the restoration is resumed. For
* this reason we should NEVER call AKFactory::nuke() in startRestore anymore: that would simply reset the
* extraction engine configuration which was done in masterSetup() leading to an error about the file being
* invalid (since no file is found).
*/
case 'startRestore':
case 'stepRestore':
$engine = AKFactory::getUnarchiver(); // Get the engine
$observer = new RestorationObserver(); // Create a new observer
$engine->attach($observer); // Attach the observer
$engine->tick();
$ret = $engine->getStatusArray();
if ($ret['Error'] != '')
{
$retArray['status'] = false;
$retArray['done'] = true;
$retArray['message'] = $ret['Error'];
}
elseif (!$ret['HasRun'])
{
$retArray['files'] = $observer->filesProcessed;
$retArray['bytesIn'] = $observer->compressedTotal;
$retArray['bytesOut'] = $observer->uncompressedTotal;
$retArray['status'] = true;
$retArray['done'] = true;
}
else
{
$retArray['files'] = $observer->filesProcessed;
$retArray['bytesIn'] = $observer->compressedTotal;
$retArray['bytesOut'] = $observer->uncompressedTotal;
$retArray['status'] = true;
$retArray['done'] = false;
$retArray['factory'] = AKFactory::serialize();
}
break;
case 'finalizeRestore':
$root = AKFactory::get('kickstart.setup.destdir');
// Remove the installation directory
recursive_remove_directory($root . '/installation');
$postproc = AKFactory::getPostProc();
/**
* Should I rename the htaccess.bak and web.config.bak files back to their live filenames...?
*/
$renameFiles = AKFactory::get('kickstart.setup.postrenamefiles', true);
if ($renameFiles)
{
// Rename htaccess.bak to .htaccess
if (file_exists($root . '/htaccess.bak'))
{
if (file_exists($root . '/.htaccess'))
{
$postproc->unlink($root . '/.htaccess');
}
$postproc->rename($root . '/htaccess.bak', $root . '/.htaccess');
}
// Rename htaccess.bak to .htaccess
if (file_exists($root . '/web.config.bak'))
{
if (file_exists($root . '/web.config'))
{
$postproc->unlink($root . '/web.config');
}
$postproc->rename($root . '/web.config.bak', $root . '/web.config');
}
}
// Remove restoration.php
$basepath = KSROOTDIR;
$basepath = rtrim(str_replace('\\', '/', $basepath), '/');
if (!empty($basepath))
{
$basepath .= '/';
}
$postproc->unlink($basepath . 'restoration.php');
// Import a custom finalisation file
$filename = dirname(__FILE__) . '/restore_finalisation.php';
if (file_exists($filename))
{
// We cannot use the Filesystem API here.
if (ini_get('opcache.enable')
&& function_exists('opcache_invalidate')
&& (!ini_get('opcache.restrict_api') || stripos(realpath($_SERVER['SCRIPT_FILENAME']), ini_get('opcache.restrict_api')) === 0)
)
{
\opcache_invalidate($filename, true);
}
if (function_exists('apc_compile_file'))
{
\apc_compile_file($filename);
}
if (function_exists('wincache_refresh_if_changed'))
{
\wincache_refresh_if_changed(array($filename));
}
if (function_exists('xcache_asm'))
{
xcache_asm($filename);
}
include_once $filename;
}
// Run a custom finalisation script
if (function_exists('finalizeRestore'))
{
finalizeRestore($root, $basepath);
}
break;
default:
// Invalid task!
$enabled = false;
break;
}
}
// Maybe we weren't authorized or the task was invalid?
if (!$enabled)
{
// Maybe the user failed to enter any information
$retArray['status'] = false;
$retArray['message'] = AKText::_('ERR_INVALID_LOGIN');
}
// JSON encode the message
$json = json_encode($retArray);
// Do I have to encrypt?
$password = AKFactory::get('kickstart.security.password', null);
if (!empty($password))
{
$json = AKEncryptionAES::AESEncryptCtr($json, $password, 128);
}
// Return the message
echo "###$json###";
}
// ------------ lixlpixel recursive PHP functions -------------
// recursive_remove_directory( directory to delete, empty )
// expects path to directory and optional TRUE / FALSE to empty
// of course PHP has to have the rights to delete the directory
// you specify and all files and folders inside the directory
// ------------------------------------------------------------
function recursive_remove_directory($directory)
{
// if the path has a slash at the end we remove it here
if (substr($directory, -1) == '/')
{
$directory = substr($directory, 0, -1);
}
// if the path is not valid or is not a directory ...
if (!file_exists($directory) || !is_dir($directory))
{
// ... we return false and exit the function
return false;
// ... if the path is not readable
}
elseif (!is_readable($directory))
{
// ... we return false and exit the function
return false;
// ... else if the path is readable
}
else
{
// we open the directory
$handle = opendir($directory);
$postproc = AKFactory::getPostProc();
// and scan through the items inside
while (false !== ($item = readdir($handle)))
{
// if the filepointer is not the current directory
// or the parent directory
if ($item != '.' && $item != '..')
{
// we build the new path to delete
$path = $directory . '/' . $item;
// if the new path is a directory
if (is_dir($path))
{
// we call this function with the new path
recursive_remove_directory($path);
// if the new path is a file
}
else
{
// we remove the file
$postproc->unlink($path);
}
}
}
// close the directory
closedir($handle);
// try to delete the now empty directory
if (!$postproc->rmdir($directory))
{
// return false if not possible
return false;
}
// return success
return true;
}
}
restore_finalisation.php 0000644 00000010171 15234617001 0011477 0 ustar 00 <?php
/**
* @package Joomla.Administrator
* @subpackage com_joomlaupdate
*
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
// Require the restoration environment or fail cold. Prevents direct web access.
defined('_AKEEBA_RESTORATION') or die();
// Fake a miniature Joomla environment
if (!defined('_JEXEC'))
{
define('_JEXEC', 1);
}
if (!function_exists('jimport'))
{
/**
* We don't use it but the post-update script is using it anyway, so LET'S FAKE IT!
*
* @param string $path A dot syntax path.
* @param string $base Search this directory for the class.
*
* @return boolean True on success.
*
* @since 3.5.1
*/
function jimport($path, $base = null)
{
// Do nothing
}
}
// Fake the JFile class, mapping it to Restore's post-processing class
if (!class_exists('JFile'))
{
/**
* JFile mock class proxing behaviour in the post-upgrade script to that of either native PHP or restore.php
*
* @since 3.5.1
*/
abstract class JFile
{
/**
* Proxies checking a folder exists to the native php version
*
* @param string $fileName The path to the file to be checked
*
* @return boolean
*
* @since 3.5.1
*/
public static function exists($fileName)
{
return @file_exists($fileName);
}
/**
* Proxies deleting a file to the restore.php version
*
* @param string $fileName The path to the file to be deleted
*
* @return boolean
*
* @since 3.5.1
*/
public static function delete($fileName)
{
$postproc = AKFactory::getPostProc();
$postproc->unlink($fileName);
}
}
}
// Fake the JFolder class, mapping it to Restore's post-processing class
if (!class_exists('JFolder'))
{
/**
* JFolder mock class proxing behaviour in the post-upgrade script to that of either native PHP or restore.php
*
* @since 3.5.1
*/
abstract class JFolder
{
/**
* Proxies checking a folder exists to the native php version
*
* @param string $folderName The path to the folder to be checked
*
* @return boolean
*
* @since 3.5.1
*/
public static function exists($folderName)
{
return @is_dir($folderName);
}
/**
* Proxies deleting a folder to the restore.php version
*
* @param string $folderName The path to the folder to be deleted
*
* @return void
*
* @since 3.5.1
*/
public static function delete($folderName)
{
recursive_remove_directory($folderName);
}
}
}
// Fake the JText class - we aren't going to show errors to people anyhow
if (!class_exists('JText'))
{
/**
* JText mock class proxing behaviour in the post-upgrade script to that of either native PHP or restore.php
*
* @since 3.5.1
*/
abstract class JText
{
/**
* No need for translations in a non-interactive script, so always return an empty string here
*
* @param string $text A language constant
*
* @return string
*
* @since 3.5.1
*/
public static function sprintf($text)
{
return '';
}
}
}
if (!function_exists('finalizeRestore'))
{
/**
* Run part of the Joomla! finalisation script, namely the part that cleans up unused files/folders
*
* @param string $siteRoot The root to the Joomla! site
* @param string $restorePath The base path to restore.php
*
* @return void
*
* @since 3.5.1
*/
function finalizeRestore($siteRoot, $restorePath)
{
if (!defined('JPATH_ROOT'))
{
define('JPATH_ROOT', $siteRoot);
}
$filePath = JPATH_ROOT . '/administrator/components/com_admin/script.php';
if (file_exists($filePath))
{
require_once $filePath;
}
// Make sure Joomla!'s code can figure out which files exist and need be removed
clearstatcache();
// Remove obsolete files - prevents errors occurring in some system plugins
if (class_exists('JoomlaInstallerScript'))
{
$script = new JoomlaInstallerScript;
$script->deleteUnexistingFiles();
}
// Clear OPcache
if (function_exists('opcache_reset'))
{
opcache_reset();
}
elseif (function_exists('apc_clear_cache'))
{
@apc_clear_cache();
}
}
}