Commit 6414ef98 authored by Frank Nägler's avatar Frank Nägler Committed by Andreas Fernandez
Browse files

[TASK] Migrate TYPO3/CMS/Extensionmanager/* to TypeScript

Resolves: #88040
Releases: master
Change-Id: I5408c30fef735fe52a4b58bda3fb5b4d3b8e002e
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/60353


Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: default avatarSteffen Frese <steffenf14@gmail.com>
Tested-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: default avatarSteffen Frese <steffenf14@gmail.com>
Reviewed-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
parent 0bb4e8f0
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
import * as $ from 'jquery';
import * as NProgress from 'nprogress';
import Modal = require('TYPO3/CMS/Backend/Modal');
import SplitButtons = require('TYPO3/CMS/Backend/SplitButtons');
import Tooltip = require('TYPO3/CMS/Backend/Tooltip');
import Severity = require('TYPO3/CMS/Backend/Severity');
import SecurityUtility = require('TYPO3/CMS/Core/SecurityUtility');
import ExtensionManagerRepository = require('./Repository');
import ExtensionManagerUpdate = require('./Update');
import ExtensionManagerUploadForm = require('./UploadForm');
import 'datatables';
import 'TYPO3/CMS/Backend/jquery.clearable';
const securityUtility = new SecurityUtility();
enum ExtensionManagerIdentifier {
extensionlist = '#typo3-extension-list',
searchField = '#Tx_Extensionmanager_extensionkey',
}
/**
* Module: TYPO3/CMS/Extensionmanager/Main
* main logic holding everything together, consists of multiple parts
* ExtensionManager => Various functions for displaying the extension list / sorting
* Repository => Various AJAX functions for TER downloads
* ExtensionManager.Update => Various AJAX functions to display updates
* ExtensionManager.uploadForm => helper to show the upload form
*/
class ExtensionManager {
public Update: ExtensionManagerUpdate;
public UploadForm: ExtensionManagerUploadForm;
public Repository: ExtensionManagerRepository;
constructor() {
$(() => {
$.fn.dataTableExt.oSort['extension-asc'] = (a: string, b: string) => {
return ExtensionManager.extensionCompare(a, b);
};
$.fn.dataTableExt.oSort['extension-desc'] = (a: string, b: string) => {
let result = ExtensionManager.extensionCompare(a, b);
return result * -1;
};
$.fn.dataTableExt.oSort['version-asc'] = (a: string, b: string) => {
let result = ExtensionManager.versionCompare(a, b);
return result * -1;
};
$.fn.dataTableExt.oSort['version-desc'] = (a: string, b: string) => {
return ExtensionManager.versionCompare(a, b);
};
this.Update = new ExtensionManagerUpdate();
this.UploadForm = new ExtensionManagerUploadForm();
this.Repository = new ExtensionManagerRepository();
const dataTable: DataTables.Api = this.manageExtensionListing();
$(document).on('click', '.onClickMaskExtensionManager', (): void => {
NProgress.start();
}).on('click', 'a[data-action=update-extension]', (e: JQueryEventObject): void => {
e.preventDefault();
$.ajax({
url: $(this).attr('href'),
dataType: 'json',
beforeSend: (): void => {
NProgress.start();
},
success: this.updateExtension,
});
}).on('change', 'input[name=unlockDependencyIgnoreButton]', (e: JQueryEventObject): void => {
const $actionButton = $('.t3js-dependencies');
$actionButton.toggleClass('disabled', !$(e.currentTarget).prop('checked'));
});
$(ExtensionManagerIdentifier.searchField).clearable({
onClear: (): void => {
dataTable.search('').draw();
},
});
$(document).on('click', '.t3-button-action-installdistribution', (): void => {
NProgress.start();
});
SplitButtons.addPreSubmitCallback((e: JQueryEventObject): void => {
if ($(e.target).hasClass('t3js-save-close')) {
$('#configurationform').append($('<input />', {
type: 'hidden',
name: 'tx_extensionmanager_tools_extensionmanagerextensionmanager[action]',
value: 'saveAndClose',
}));
}
});
this.Repository.initDom();
this.Update.initializeEvents();
this.UploadForm.initializeEvents();
Tooltip.initialize('#typo3-extension-list [title]', {
delay: {
show: 500,
hide: 100,
},
trigger: 'hover',
container: 'body',
});
});
}
private manageExtensionListing(): DataTables.Api {
const $searchField = $(ExtensionManagerIdentifier.searchField);
const dataTable = $(ExtensionManagerIdentifier.extensionlist).DataTable({
paging: false,
dom: 'lrtip',
lengthChange: false,
pageLength: 15,
stateSave: true,
drawCallback: this.bindExtensionListActions,
columns: [
null,
null,
{
type: 'extension',
},
null,
{
type: 'version',
}, {
orderable: false,
},
null,
null,
],
});
$searchField.parents('form').on('submit', () => {
return false;
});
const getVars: any = ExtensionManager.getUrlVars();
// restore filter
const currentSearch = (getVars.search ? getVars.search : dataTable.search());
$searchField.val(currentSearch);
$searchField.on('input', (e: JQueryEventObject): void => {
dataTable.search($(e.currentTarget).val()).draw();
});
return dataTable;
}
private bindExtensionListActions = (): void => {
$('.removeExtension').not('.transformed').each((index: number, element: any) => {
const $me = $(element);
$me.data('href', $me.attr('href'));
$me.attr('href', '#');
$me.addClass('transformed');
$me.click((): void => {
Modal.confirm(
TYPO3.lang['extensionList.removalConfirmation.title'],
TYPO3.lang['extensionList.removalConfirmation.question'],
Severity.error,
[
{
text: TYPO3.lang['button.cancel'],
active: true,
btnClass: 'btn-default',
trigger: (): void => {
Modal.dismiss();
},
}, {
text: TYPO3.lang['button.remove'],
btnClass: 'btn-danger',
trigger: (): void => {
this.removeExtensionFromDisk($me);
Modal.dismiss();
},
},
],
);
});
});
}
private removeExtensionFromDisk($extension: JQuery): void {
$.ajax({
url: $extension.data('href'),
beforeSend: (): void => {
NProgress.start();
},
success: (): void => {
location.reload();
},
complete: (): void => {
NProgress.done();
},
});
}
private static getUrlVars(): any {
let vars: any = [];
let hash: Array<string>;
let hashes: Array<string> = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for (let i = 0; i < hashes.length; i++) {
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
return vars;
}
/**
* Special sorting for the extension version column
*/
private static versionCompare(a: string, b: string): number {
if (a === b) {
return 0;
}
const a_components = a.split('.');
const b_components = b.split('.');
const len = Math.min(a_components.length, b_components.length);
// loop while the components are equal
for (let i = 0; i < len; i++) {
// A bigger than B
if (parseInt(a_components[i], 10) > parseInt(b_components[i], 10)) {
return 1;
}
// B bigger than A
if (parseInt(a_components[i], 10) < parseInt(b_components[i], 10)) {
return -1;
}
}
// If one's a prefix of the other, the longer one is greaRepository.
if (a_components.length > b_components.length) {
return 1;
}
if (a_components.length < b_components.length) {
return -1;
}
// Otherwise they are the same.
return 0;
}
/**
* The extension name column can contain various forms of HTML that
* break a direct comparison of values
*/
private static extensionCompare(a: string, b: string): number {
const div = document.createElement('div');
div.innerHTML = a;
const aStr = div.textContent || div.innerText || a;
div.innerHTML = b;
const bStr = div.textContent || div.innerText || b;
return aStr.trim().localeCompare(bStr.trim());
}
private updateExtension(data: any): void {
let i = 0;
const $form = $('<form>');
$.each(data.updateComments, (version: string, comment: string): void => {
const $input = $('<input>').attr({type: 'radio', name: 'version'}).val(version);
if (i === 0) {
$input.attr('checked', 'checked');
}
$form.append([
$('<h3>').append([
$input,
' ' + securityUtility.encodeHtml(version),
]),
$('<div>')
.append(
comment
.replace(/(\r\n|\n\r|\r|\n)/g, '\n')
.split(/\n/).map((line: string): string => {
return securityUtility.encodeHtml(line);
})
.join('<br>'),
),
]);
i++;
});
const $container = $('<div>').append([
$('<h1>').text(TYPO3.lang['extensionList.updateConfirmation.title']),
$('<h2>').text(TYPO3.lang['extensionList.updateConfirmation.message']),
$form,
]);
NProgress.done();
Modal.confirm(
TYPO3.lang['extensionList.updateConfirmation.questionVersionComments'],
$container,
Severity.warning,
[
{
text: TYPO3.lang['button.cancel'],
active: true,
btnClass: 'btn-default',
trigger: (): void => {
Modal.dismiss();
},
}, {
text: TYPO3.lang['button.updateExtension'],
btnClass: 'btn-warning',
trigger: (): void => {
$.ajax({
url: data.url,
data: {
tx_extensionmanager_tools_extensionmanagerextensionmanager: {
version: $('input:radio[name=version]:checked', Modal.currentModal).val(),
},
},
dataType: 'json',
beforeSend: (): void => {
NProgress.start();
},
complete: (): void => {
location.reload();
},
});
Modal.dismiss();
},
},
],
);
}
}
let extensionManagerObject = new ExtensionManager();
if (typeof TYPO3.ExtensionManager === 'undefined') {
TYPO3.ExtensionManager = extensionManagerObject;
}
export = extensionManagerObject;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
import * as $ from 'jquery';
import * as NProgress from 'nprogress';
import Modal = require('TYPO3/CMS/Backend/Modal');
import Notification = require('TYPO3/CMS/Backend/Notification');
import Severity = require('TYPO3/CMS/Backend/Severity');
import 'datatables';
import 'TYPO3/CMS/Backend/jquery.clearable';
class Repository {
public downloadPath: string = '';
public initDom = (): void => {
NProgress.configure({parent: '.module-loading-indicator', showSpinner: false});
$('#terTable').DataTable({
lengthChange: false,
pageLength: 15,
stateSave: false,
info: false,
paging: false,
searching: false,
ordering: false,
drawCallback: this.bindDownload,
});
$('#terVersionTable').DataTable({
lengthChange: false,
pageLength: 15,
stateSave: false,
info: false,
paging: false,
searching: false,
drawCallback: this.bindDownload,
order: [
[2, 'asc'],
],
columns: [
{orderable: false},
null,
{type: 'version'},
null,
null,
null,
],
});
$('#terSearchTable').DataTable({
paging: false,
lengthChange: false,
stateSave: false,
searching: false,
language: {
search: 'Filter results:',
},
ordering: false,
drawCallback: this.bindDownload,
});
this.bindDownload();
this.bindSearchFieldResetter();
}
private bindDownload = (): void => {
const installButtons = $('.downloadFromTer form.download button[type=submit]');
installButtons.off('click');
installButtons.on('click', (event: JQueryEventObject): void => {
event.preventDefault();
const $element: any = $(event.currentTarget);
const url = $($element.form).attr('data-href');
this.downloadPath = $($element.form).find('input.downloadPath:checked').val();
$.ajax({
url: url,
dataType: 'json',
beforeSend: (): void => {
NProgress.start();
},
success: this.getDependencies,
});
});
}
private getDependencies = (data: any): boolean => {
NProgress.done();
if (data.hasDependencies) {
Modal.confirm(data.title, $(data.message), Severity.info, [
{
text: TYPO3.lang['button.cancel'],
active: true,
btnClass: 'btn-default',
trigger: (): void => {
Modal.dismiss();
},
}, {
text: TYPO3.lang['button.resolveDependencies'],
btnClass: 'btn-info',
trigger: (): void => {
this.getResolveDependenciesAndInstallResult(data.url
+ '&tx_extensionmanager_tools_extensionmanagerextensionmanager[downloadPath]=' + this.downloadPath);
Modal.dismiss();
},
},
]);
} else {
if (data.hasErrors) {
Notification.error(data.title, data.message, 15);
} else {
this.getResolveDependenciesAndInstallResult(data.url
+ '&tx_extensionmanager_tools_extensionmanagerextensionmanager[downloadPath]=' + this.downloadPath);
}
}
return false;
}
private getResolveDependenciesAndInstallResult = (url: string) => {
$.ajax({
url: url,
dataType: 'json',
beforeSend: (): void => {
NProgress.start();
},
success: (data: any): void => {
if (data.errorCount > 0) {
Modal.confirm(data.errorTitle, $(data.errorMessage), Severity.error, [
{
text: TYPO3.lang['button.cancel'],
active: true,
btnClass: 'btn-default',
trigger: (): void => {
Modal.dismiss();
},
}, {
text: TYPO3.lang['button.resolveDependenciesIgnore'],
btnClass: 'btn-danger disabled t3js-dependencies',
trigger: (e: JQueryEventObject): void => {
if (!$(e.currentTarget).hasClass('disabled')) {
this.getResolveDependenciesAndInstallResult(data.skipDependencyUri);
Modal.dismiss();
}
},
},
]);
Modal.currentModal.on('shown.bs.modal', (): void => {
const $actionButton = Modal.currentModal.find('.t3js-dependencies');
$('input[name="unlockDependencyIgnoreButton"]', Modal.currentModal).on('change', (e: JQueryEventObject): void => {
$actionButton.toggleClass('disabled', !$(e.currentTarget).prop('checked'));
});
});
} else {
let successMessage = TYPO3.lang['extensionList.dependenciesResolveDownloadSuccess.message'
+ data.installationTypeLanguageKey].replace(/\{0\}/g, data.extension);
successMessage += '\n' + TYPO3.lang['extensionList.dependenciesResolveDownloadSuccess.header'] + ': ';
$.each(data.result, (index: number, value: any): void => {
successMessage += '\n\n' + TYPO3.lang['extensionList.dependenciesResolveDownloadSuccess.item'] + ' ' + index + ': ';
$.each(value, (extkey: string): void => {
successMessage += '\n* ' + extkey;
});
});
Notification.info(
TYPO3.lang['extensionList.dependenciesResolveFlashMessage.title' + data.installationTypeLanguageKey]
.replace(/\{0\}/g, data.extension),
successMessage,
15,
);
top.TYPO3.ModuleMenu.App.refreshMenu();
}
},
complete: (): void => {
NProgress.done();
},
});
}
private bindSearchFieldResetter(): void {
const $searchFields = $('.typo3-extensionmanager-searchTerForm input[type="text"]');
const searchResultShown = ('' !== $searchFields.first().val());
$searchFields.clearable(
{
onClear: (e: JQueryEventObject): void => {
if (searchResultShown) {
$(e.currentTarget).closest('form').submit();
}
},
},
);
}
}
export = Repository;