/**
* @package Regular Labs Extension Manager
* @version 9.1.0
*
* @author Peter van Westen <[email protected]>
* @link https://regularlabs.com
* @copyright Copyright © 2025 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
(function() {
'use strict';
window.RegularLabs = window.RegularLabs || {};
window.RegularLabs.Manager = window.RegularLabs.Manager || {
form : null,
container : null,
options : {},
tag_characters : {},
group : null,
do_update : false,
tag_type : '',
process_name : '',
last_form_data : '',
process_start_time: Date.now(),
time_to_process : 4000,
queue : [],
failed : [],
broken : [],
updates_available : [],
not_installed : [],
init: function() {
this.container = document.querySelector('#regularlabsmanager');
this.spinner = this.container.querySelector('#rlem_spinner');
this.content = this.container.querySelector('#rlem_content');
this.error = this.container.querySelector('#rlem_error');
this.form = this.container.querySelector('#regularlabsmanagerForm');
this.back_button = document.querySelector('.button-back');
this.refresh_button = document.querySelector('.button-refresh');
this.retry_button = document.querySelector('.button-retry');
this.update_all_button = document.querySelector('.button-update_all');
this.refresh();
},
startLoad: function(task) {
window.scrollTo({top: 0, left: 0});
this.container.style.height = this.container.offsetHeight + 'px';
Regular.removeClass(this.spinner, 'hidden');
Regular.addClass(this.content, 'hidden');
Regular.addClass(this.error, 'hidden');
this.back_button.blur();
this.refresh_button.blur();
Regular.addClass(this.back_button, 'hidden disabled');
Regular.addClass(this.refresh_button, 'hidden disabled');
Regular.addClass(this.update_all_button, 'hidden disabled');
Regular.addClass(this.retry_button, 'hidden disabled');
switch (task) {
case 'discover.display':
this.startLoadDiscover();
break;
case 'process.start':
case 'process.display':
this.startLoadProcess();
break;
default:
break;
}
},
endLoad: function(data = '', task = '') {
this.form = this.container.querySelector('#regularlabsmanagerForm');
switch (task) {
case 'discover.display':
this.endLoadDiscover(data);
break;
case 'process.display':
this.endLoadProcess(data);
break;
default:
break;
}
// disable buttons and links in parent element with class disabled
document.querySelectorAll('.disabled button, .disabled a').forEach((el) => {
Regular.addClass(el, 'disabled');
});
Regular.addClass(this.spinner, 'hidden');
document.dispatchEvent(new Event('rl-update-form-descriptions'));
},
startLoadDiscover: function() {
Regular.removeClasses(this.refresh_button, 'hidden');
},
endLoadDiscover: function(data = '') {
Regular.removeClasses(this.refresh_button, 'hidden disabled');
// show Update All button if updates are found
if (data && data.indexOf('rlem-update-all') !== -1) {
Regular.removeClass(this.update_all_button, 'hidden disabled');
}
// Hide the Update All button if the Extension Manager needs to be updated
if (Regular.hasClass(this.form, 'has_extensionmanager')) {
Regular.addClass(this.update_all_button, 'hidden disabled');
}
},
startLoadProcess: function() {
Regular.removeClasses(this.back_button, 'hidden');
},
endLoadProcess: function(data = '') {
Regular.removeClasses(this.back_button, 'hidden disabled');
if (this.failed.length) {
Regular.removeClass(this.retry_button, 'hidden disabled');
}
},
refresh: function(refresh = false) {
this.loadPage(
'discover.display',
{
'refresh': refresh ? '1' : '0'
},
(() => {
this.setExtensionsByStates();
})
);
},
setExtensionsByStates: function(extensions = []) {
const states = ['broken', 'not_installed', 'updates_available'];
states.forEach((state) => {
this[state] = [];
document.querySelectorAll(`[data-state="${state}"]`).forEach((el) => {
this[state].push(el.dataset.extension);
});
});
},
start: function(extensions = []) {
this.queue = [];
this.failed = [];
this.loadPage(
'process.start',
{
'process' : this.process_name,
'extensions': extensions
},
(() => {
this.startQueue();
})
);
},
update: function(extension = '') {
this.process_name = 'update';
const extensions = extension === ''
? this.updates_available
: [extension];
this.start(extensions);
},
install: function(extension = '') {
this.process_name = 'install';
const extensions = extension === ''
? this.getSelected()
: [extension];
if ( ! extensions.length) {
return;
}
this.start(extensions);
},
downgrade: function(extension = '') {
this.process_name = 'downgrade';
this.start([extension]);
},
uninstall: function(extension = '') {
this.process_name = 'uninstall';
this.start([extension]);
},
reinstall: function(extension = '') {
this.process_name = 'reinstall';
const extensions = extension === ''
? this.broken
: [extension];
this.start(extensions);
},
retry: function() {
const extensions = [];
this.failed.forEach((extension) => {
extensions.push(extension.extension);
});
this.failed = [];
this.start(extensions);
},
startQueue: function() {
const progress_bars = document.querySelectorAll('.progress-bar[data-extension]');
progress_bars.forEach((progress_bar) => {
this.queue.push({
extension: progress_bar.dataset.extension,
url : progress_bar.dataset.url,
bar : progress_bar
});
});
this.processNext(0);
},
processNext: function(id) {
if (this.queue[id] === undefined) {
this.loadPage('process.display');
return;
}
const current = this.queue[id];
// set to current time - start time
this.process_start_time = Date.now();
this.progressProgressBar(id);
const task = this.process_name === 'uninstall' ? 'uninstall' : 'install';
Regular.loadUrl(
'index.php',
{
'option' : 'com_regularlabsmanager',
'task' : 'process.' + task,
'extension' : current.extension,
'url' : current.url,
[Joomla.getOptions('csrf.token')]: 1,
},
(data) => {
this.finishProcess(id, data);
},
(data) => {
this.failProcess(id);
}
);
},
progressProgressBar: function(id, value = 0, className = '') {
if (this.queue[id] === undefined) {
return;
}
const current = this.queue[id];
// Add random jumps
const current_progress = this.progressBar(current.bar, value);
if (className) {
Regular.addClass(current.bar, className);
}
if (current_progress >= 100) {
Regular.removeClass(current.bar, 'progress-bar-striped');
}
if (value || current_progress >= 90) {
return;
}
// progress at a random interval between 0 and 10% of the total progress bar
const random = Math.floor(Math.random() * (this.time_to_process / 10)) + 1;
setTimeout(() => {
this.progressProgressBar(id);
}, Math.floor(random + 1));
},
finishProcess: function(id, data = '0') {
if (data === '0') {
this.failProcess(id);
return;
}
this.progressProgressBar(id, 100, 'bg-success');
// set to current time - start time
this.time_to_process = Date.now() - this.process_start_time;
this.processNext(id + 1);
},
failProcess: function(id) {
const current = this.queue[id];
this.failed.push(current);
this.progressProgressBar(id, 100, 'bg-danger');
// set to current time - start time
this.time_to_process = Date.now() - this.process_start_time;
this.processNext(id + 1);
},
progressBar: function(bar, progress = 0) {
progress = parseInt(progress);
if ( ! progress) {
// random value between 1 - 10
progress = Math.floor((Math.random() * 10) + 1);
}
const current_progress = parseInt(bar.getAttribute('aria-valuenow'));
const total_progress = Math.min(current_progress + progress, 100);
bar.style.width = `${total_progress}%`;
bar.setAttribute('aria-valuenow', total_progress);
return total_progress;
},
updateByUrl: function(url = '') {
this.loadPage('update.update', {'url': url});
},
loadPage: function(task, params = {}, success = null, fail = null) {
this.startLoad(task);
if (params.refresh === undefined || parseInt(params.refresh) === 1) {
const core_messages = document.querySelector('#system-message-container');
Regular.addClass(core_messages, 'hidden');
}
Regular.loadUrl(
'index.php',
{
'option': 'com_regularlabsmanager',
'task' : task,
...params
},
(data) => {
this.updatePage(data, success, task);
},
(data) => {
this.updatePage(null, fail, task);
}
);
},
updatePage: function(data, callback = null, task = '') {
this.container.style.height = 'auto';
if ( ! data) {
Regular.removeClass(this.error, 'hidden');
this.endLoad(null, task);
callback && callback();
return;
}
this.content.innerHTML = data;
Regular.removeClass(this.content, 'hidden');
this.endLoad(data, task);
callback && callback();
},
getSelected: function() {
const form_data = this.getFormData();
return form_data['extensions[]'];
},
getFormData: function() {
const form_data = new FormData(this.form);
const object = {};
form_data.forEach((value, key) => {
// Reflect.has in favor of: object.hasOwnProperty(key)
if (Reflect.has(object, key)) {
if ( ! Array.isArray(object[key])) {
object[key] = [object[key]];
}
object[key].push(value);
return;
}
object[key] = value;
});
return object;
},
};
})();