Skip to content

Commit

Permalink
[FEATURE] Introduce event to prevent downloading of language packs
Browse files Browse the repository at this point in the history
With the newly introduced event, it is possible to ignore extensions or
individual language packs for extensions when downloading the language
packs.

Resolves: #98394
Releases: main
Change-Id: I5ff3b3bcdc4de41338d6925412c81ccb1df022e3
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/77485
Tested-by: core-ci <typo3@b13.com>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Stefan Bürk <stefan@buerk.tech>
  • Loading branch information
kevin-appelt authored and lolli42 committed Jan 20, 2023
1 parent b933845 commit 4ca1de8
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 47 deletions.
Expand Up @@ -48,6 +48,7 @@ class LanguagePacks extends AbstractInteractableModule {
updated: 0,
new: 0,
failed: 0,
skipped: 0,
};

private notifications: Array<any> = [];
Expand Down Expand Up @@ -203,6 +204,7 @@ class LanguagePacks extends AbstractInteractableModule {
updated: 0,
new: 0,
failed: 0,
skipped: 0,
};

$outputContainer.empty().append(
Expand Down Expand Up @@ -246,6 +248,8 @@ class LanguagePacks extends AbstractInteractableModule {
this.packsUpdateDetails.new++;
} else if (data.packResult === 'update') {
this.packsUpdateDetails.updated++;
} else if (data.packResult === 'skipped') {
this.packsUpdateDetails.skipped++;
} else {
this.packsUpdateDetails.failed++;
}
Expand Down Expand Up @@ -276,6 +280,7 @@ class LanguagePacks extends AbstractInteractableModule {
'Language packs updated',
this.packsUpdateDetails.new + ' new language ' + LanguagePacks.pluralize(this.packsUpdateDetails.new) + ' downloaded, ' +
this.packsUpdateDetails.updated + ' language ' + LanguagePacks.pluralize(this.packsUpdateDetails.updated) + ' updated, ' +
this.packsUpdateDetails.skipped + ' language ' + LanguagePacks.pluralize(this.packsUpdateDetails.skipped) + ' skipped, ' +
this.packsUpdateDetails.failed + ' language ' + LanguagePacks.pluralize(this.packsUpdateDetails.failed) + ' not available',
);
this.addNotification(message);
Expand Down Expand Up @@ -458,33 +463,47 @@ class LanguagePacks extends AbstractInteractableModule {
$('<td>').html(extensionTitle.html()),
$('<td>').text(extension.key),
);
extension.packs.forEach((pack: any): void => {
const $column = $('<td>');
$tr.append($column);
if (pack.exists !== true) {
if (pack.lastUpdate !== null) {
tooltip = 'No language pack available for ' + pack.iso + ' when tried at ' + pack.lastUpdate + '. Click to re-try.';
} else {
tooltip = 'Language pack not downloaded. Click to download';

data.activeLanguages.forEach((language: string): void => {
let packFoundForLanguage: boolean = false;
extension.packs.forEach((pack: any): void => {
if (pack.iso !== language) {
return;
}
} else {
if (pack.lastUpdate === null) {
tooltip = 'Downloaded. Click to renew';
packFoundForLanguage = true;
const $column = $('<td>');
$tr.append($column);
if (pack.exists !== true) {
if (pack.lastUpdate !== null) {
tooltip = 'No language pack available for ' + pack.iso + ' when tried at ' + pack.lastUpdate + '. Click to re-try.';
} else {
tooltip = 'Language pack not downloaded. Click to download';
}
} else {
tooltip = 'Language pack downloaded at ' + pack.lastUpdate + '. Click to renew';
if (pack.lastUpdate === null) {
tooltip = 'Downloaded. Click to renew';
} else {
tooltip = 'Language pack downloaded at ' + pack.lastUpdate + '. Click to renew';
}
}
$column.append(
$('<a>', {
'class': 'btn btn-default t3js-languagePacks-update',
'data-extension': extension.key,
'data-iso': pack.iso,
'data-bs-toggle': 'tooltip',
'title': securityUtility.encodeHtml(tooltip),
}).append(updateIcon),
);
});
// Render empty colum to avoid disturbed table build up if pack was not found for language.
if (!packFoundForLanguage) {
const $column = $('<td>');
$tr.append($column).append('&nbsp;');
}
$column.append(
$('<a>', {
'class': 'btn btn-default t3js-languagePacks-update',
'data-extension': extension.key,
'data-iso': pack.iso,
'data-bs-toggle': 'tooltip',
'title': securityUtility.encodeHtml(tooltip),
}).append(updateIcon),
);
});
$tbody.append($tr);

});

$markupContainer.append(
Expand Down
@@ -0,0 +1,59 @@
.. include:: /Includes.rst.txt

.. _feature-98394-1674070213:

==========================================================================
Feature: #98394 - Introduce event to prevent downloading of language packs
==========================================================================

See :issue:`98394`

Description
===========

.. code-block:: yaml
:caption: Configuration/Services.yaml
services:
KA\Myext\EventListener\ModifyLanguagePacks:
tags:
- name: event.listener
identifier: 'modifyLanguagePacks'
event: TYPO3\CMS\Install\Service\Event\ModifyLanguagePacksEvent
method: 'modifyLanguagePacks'
.. code-block:: php
:caption: Classes/EventListener/ModifyLanguagePacks.php
<?php
namespace KA\Myext\EventListener;
use TYPO3\CMS\Install\Service\Event\ModifyLanguagePacksEvent;
class ModifyLanguagePacks
{
public function modifyLanguagePacks(ModifyLanguagePacksEvent $event): void
{
$extensions = $event->getExtensions();
foreach ($extensions as $key => $extension){
if($extension['type'] === 'typo3-cms-framework'){
$event->removeExtension($key);
}
}
$event->removeIsoFromExtension('de', 'styleguide');
}
}
Impact
======

With the newly introduced event, it is possible to ignore extensions or
individual language packs for extensions when downloading the language packs.
It is still the case that only language packs for extensions and languages
available in the system can be downloaded. The options of the language:update
command can be used to further restrict the download (ignore additional
extensions or download only specific languages), but not to ignore decisions
made by the event.

.. index:: ext:install
43 changes: 30 additions & 13 deletions typo3/sysext/install/Classes/Command/LanguagePackCommand.php
Expand Up @@ -103,10 +103,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$isos = $languagePackService->getActiveLanguages();
}

$output->writeln(sprintf(
'<info>Updating language packs of all activated extensions for locale(s) "%s"</info>',
implode('", "', $isos)
));
$output->writeln('<info>Updating language packs</info>');

$extensions = $languagePackService->getExtensionLanguagePackDetails();

Expand All @@ -115,26 +112,46 @@ protected function execute(InputInterface $input, OutputInterface $output): int
} else {
$progressBarOutput = $output;
}
$progressBar = new ProgressBar($progressBarOutput, count($isos) * count($extensions));
foreach ($isos as $iso) {
foreach ($extensions as $extension) {
if (in_array($extension['key'], $skipExtensions, true)) {

$downloads = [];
$packageCount = 0;
foreach ($extensions as $extensionKey => $extension) {
if (in_array($extensionKey, $skipExtensions, true)) {
continue;
}
$downloads[$extensionKey] = [];
foreach ($extension['packs'] as $iso => $pack) {
if (!in_array($iso, $isos, true)) {
continue;
}
$downloads[$extensionKey][] = $iso;
$packageCount++;
}

if (empty($downloads[$extensionKey])) {
unset($downloads[$extensionKey]);
}
}
$progressBar = new ProgressBar($progressBarOutput, $packageCount);
foreach ($downloads as $extension => $extensionLanguages) {
foreach ($isos as $iso) {
if ($noProgress) {
$output->writeln(sprintf('<info>Fetching pack for language "%s" for extension "%s"</info>', $iso, $extension['key']), $output::VERBOSITY_VERY_VERBOSE);
$output->writeln(sprintf('<info>Fetching pack for language "%s" for extension "%s"</info>', $iso, $extension), $output::VERBOSITY_VERY_VERBOSE);
}
$result = $languagePackService->languagePackDownload($extension['key'], $iso);
$result = $languagePackService->languagePackDownload($extension, $iso);
if ($noProgress) {
switch ($result) {
case 'failed':
$output->writeln(sprintf('<comment>Fetching pack for language "%s" for extension "%s" failed</comment>', $iso, $extension['key']));
$output->writeln(sprintf('<comment>Fetching pack for language "%s" for extension "%s" failed</comment>', $iso, $extension));
break;
case 'update':
$output->writeln(sprintf('<info>Updated pack for language "%s" for extension "%s"</info>', $iso, $extension['key']));
$output->writeln(sprintf('<info>Updated pack for language "%s" for extension "%s"</info>', $iso, $extension));
break;
case 'new':
$output->writeln(sprintf('<info>Fetching new pack for language "%s" for extension "%s"</info>', $iso, $extension['key']));
$output->writeln(sprintf('<info>Fetching new pack for language "%s" for extension "%s"</info>', $iso, $extension));
break;
case 'skipped':
$output->writeln(sprintf('<info>Skipped pack for language "%s" for extension "%s"</info>', $iso, $extension));
break;
}
}
Expand Down
Expand Up @@ -593,10 +593,14 @@ public function languagePacksGetDataAction(ServerRequestInterface $request): Res
$container = $this->lateBootService->loadExtLocalconfDatabaseAndExtTables(false, true);
$languagePackService = $container->get(LanguagePackService::class);
$extensions = $languagePackService->getExtensionLanguagePackDetails();
$extensionList = array_map(function (array $extension) {
$extension['packs'] = array_values($extension['packs']);
return $extension;
}, array_values($extensions));
return new JsonResponse([
'success' => true,
'languages' => $languagePackService->getLanguageDetails(),
'extensions' => $extensions,
'extensions' => $extensionList,
'activeLanguages' => $languagePackService->getActiveLanguages(),
'activeExtensions' => array_column($extensions, 'key'),
'html' => $view->render('Maintenance/LanguagePacks'),
Expand Down
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

/*
* 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!
*/

namespace TYPO3\CMS\Install\Service\Event;

/**
* Event to modify the language pack array
*/
final class ModifyLanguagePacksEvent
{
public function __construct(private array $extensions)
{
}

public function getExtensions(): array
{
return $this->extensions;
}

public function removeExtension(string $extension): void
{
unset($this->extensions[$extension]);
}

public function removeIsoFromExtension(string $iso, string $extension): void
{
unset($this->extensions[$extension]['packs'][$iso]);
}
}
25 changes: 14 additions & 11 deletions typo3/sysext/install/Classes/Service/LanguagePackService.php
Expand Up @@ -32,6 +32,7 @@
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Install\Service\Event\ModifyLanguagePackRemoteBaseUrlEvent;
use TYPO3\CMS\Install\Service\Event\ModifyLanguagePacksEvent;

/**
* Service class handling language pack details
Expand Down Expand Up @@ -168,6 +169,7 @@ public function getExtensionLanguagePackDetails(): array
$extension = [
'key' => $key,
'title' => $title,
'type' => $package->getPackageMetaData()->getPackageType(),
];
if (!empty(ExtensionManagementUtility::getExtensionIcon($path, false))) {
$extension['icon'] = PathUtility::getAbsoluteWebPath(ExtensionManagementUtility::getExtensionIcon($path, true));
Expand All @@ -176,30 +178,25 @@ public function getExtensionLanguagePackDetails(): array
foreach ($activeLanguages as $iso) {
$isLanguagePackDownloaded = is_dir(Environment::getLabelsPath() . '/' . $iso . '/' . $key . '/');
$lastUpdate = $this->registry->get('languagePacks', $iso . '-' . $key);
$extension['packs'][] = [
$extension['packs'][$iso] = [
'iso' => $iso,
'exists' => $isLanguagePackDownloaded,
'lastUpdate' => $this->getFormattedDate($lastUpdate),
];
}
$extensions[] = $extension;
$extensions[$key] = $extension;
}
usort($extensions, static function ($a, $b) {
// Sort extensions by key
if ($a['key'] === $b['key']) {
return 0;
}
return $a['key'] < $b['key'] ? -1 : 1;
});
return $extensions;
ksort($extensions);
$event = $this->eventDispatcher->dispatch(new ModifyLanguagePacksEvent($extensions));
return $event->getExtensions();
}

/**
* Download and unpack a single language pack of one extension.
*
* @param string $key Extension key
* @param string $iso Language iso code
* @return string One of 'update', 'new' or 'failed'
* @return string One of 'update', 'new', 'skipped' or 'failed'
* @throws \RuntimeException
*/
public function languagePackDownload(string $key, string $iso): string
Expand All @@ -216,6 +213,12 @@ public function languagePackDownload(string $key, string $iso): string
throw new \RuntimeException('Extension ' . (string)$key . ' not loaded', 1520117245);
}

// Kinda hacky, but we need this as the install tool requests every language for every extension
$extensions = $this->getExtensionLanguagePackDetails();
if (!isset($extensions[$key]['packs'][$iso])) {
return 'skipped';
}

$languagePackBaseUrl = self::LANGUAGE_PACK_URL;

// Allow to modify the base url on the fly
Expand Down

0 comments on commit 4ca1de8

Please sign in to comment.