Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[TASK] Introduce composer manifest checks
The extension manager now provides a new module, which allows an integrator to display all available extensions with composer deficits, like missing composer.json or missing extension-key. The new module informs about the deficit and automatically generates a valid composer.json. proposal. In case no composer.json exists, the corresponding ext_emconf is sent to a new TER endpoint (https://extensions.typo3.org/composerize). This endpoint then generates a new composer.json proposal by resolving all dependencies. Furthermore, a new report is added to EXT:reports which also informs about such extensions by directly linking to the new EM module. This helps especially in non-composer-mode installations to ease the upgrade path for future TYPO3 versions which (hopefully) will rely on composer.json only for e.g. PackageStates.php. Resolves: #93931 Releases: master, 10.4 Change-Id: I1230363d5d03e03bff39e7070faf4e331532a292 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/68778 Tested-by: core-ci <typo3@b13.com> Tested-by: Benni Mack <benni@typo3.org> Tested-by: Oliver Bartsch <bo@cedev.de> Tested-by: Jochen <rothjochen@gmail.com> Reviewed-by: Benni Mack <benni@typo3.org> Reviewed-by: Helmut Hummel <typo3@helhum.io> Reviewed-by: Oliver Bartsch <bo@cedev.de> Reviewed-by: Jochen <rothjochen@gmail.com>
- Loading branch information
Showing
15 changed files
with
777 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
2 changes: 1 addition & 1 deletion
2
typo3/sysext/backend/Resources/Public/JavaScript/ActionDispatcher.js
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
92 changes: 92 additions & 0 deletions
92
typo3/sysext/core/Classes/Package/ComposerDeficitDetector.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
<?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\Core\Package; | ||
|
||
use Symfony\Component\Finder\Finder; | ||
use TYPO3\CMS\Core\Core\Environment; | ||
use TYPO3\CMS\Core\Utility\GeneralUtility; | ||
|
||
/** | ||
* Detects extensions with composer deficits, e.g. missing | ||
* composer.json file or missing extension-key property. | ||
*/ | ||
class ComposerDeficitDetector | ||
{ | ||
public const EXTENSION_COMPOSER_MANIFEST_VALID = 0; | ||
public const EXTENSION_COMPOSER_MANIFEST_MISSING = 1; | ||
public const EXTENSION_KEY_MISSING = 2; | ||
|
||
/** | ||
* Get all extensions with composer deficit | ||
*/ | ||
public function getExtensionsWithComposerDeficit(): array | ||
{ | ||
$finder = Finder::create()->directories()->depth(0)->in(Environment::getExtensionsPath()); | ||
$extensionsWithDeficit = []; | ||
|
||
if ($finder->hasResults()) { | ||
foreach ($finder as $extensionFolder) { | ||
$extensionKey = $extensionFolder->getFilename(); | ||
try { | ||
$extensionComposerDeficit = $this->checkExtensionComposerDeficit($extensionKey); | ||
} catch (\InvalidArgumentException $e) { | ||
// Skip invalid extensions | ||
continue; | ||
} | ||
if ($extensionComposerDeficit !== self::EXTENSION_COMPOSER_MANIFEST_VALID) { | ||
$extensionsWithDeficit[$extensionKey] = $extensionComposerDeficit; | ||
} | ||
} | ||
} | ||
|
||
return $extensionsWithDeficit; | ||
} | ||
|
||
/** | ||
* Check an extension key for composer deficits like invalid or missing composer.json | ||
*/ | ||
public function checkExtensionComposerDeficit(string $extensionKey): int | ||
{ | ||
if (!$this->isValidExtensionKey($extensionKey)) { | ||
throw new \InvalidArgumentException('Extension key ' . $extensionKey . ' is not valid.', 1619446378); | ||
} | ||
|
||
$composerManifestPath = Environment::getExtensionsPath() . '/' . $extensionKey . '/composer.json'; | ||
|
||
if (!file_exists($composerManifestPath) || !($composerManifest = file_get_contents($composerManifestPath))) { | ||
return self::EXTENSION_COMPOSER_MANIFEST_MISSING; | ||
} | ||
|
||
$composerManifest = json_decode($composerManifest, true) ?? []; | ||
|
||
if (!is_array($composerManifest) || $composerManifest === []) { | ||
// Treat empty or invalid composer.json as missing | ||
return self::EXTENSION_COMPOSER_MANIFEST_MISSING; | ||
} | ||
|
||
return empty($composerManifest['extra']['typo3/cms']['extension-key']) | ||
? self::EXTENSION_KEY_MISSING | ||
: self::EXTENSION_COMPOSER_MANIFEST_VALID; | ||
} | ||
|
||
protected function isValidExtensionKey(string $extensionKey): bool | ||
{ | ||
return preg_match('/^[0-9a-z._\-]+$/i', $extensionKey) | ||
&& GeneralUtility::isAllowedAbsPath(Environment::getExtensionsPath() . '/' . $extensionKey); | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
...ion/Changelog/10.4.x/Important-93931-ValidationOfExensionsComposerjsonFiles.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
.. include:: ../../Includes.txt | ||
|
||
================================================================ | ||
Important: #93931 - Validation of Exensions' composer.json files | ||
================================================================ | ||
|
||
See :issue:`93931` | ||
|
||
Description | ||
=========== | ||
|
||
Future TYPO3 versions will require extensions to have a valid | ||
`composer.json` file as a replacement for `ext_emconf.php`. | ||
This description file is used to define dependencies and the | ||
loading order of extensions within TYPO3. | ||
|
||
In order to support site administrators by creating valid | ||
composer.json files for their extensions, the Extension manager | ||
now lists all affected extensions with details about the necessary | ||
adaptations. Site administrators can also use the new proposal | ||
functionality, which suggests a possible and valid composer.json | ||
file for those extensions by accessing TYPO3.org (TER). TYPO3.org | ||
is used to resolve dependencies to extensions, available in the TER. | ||
|
||
You can also check your current installation for such extensions | ||
in the reports module. | ||
|
||
Further information on the transition phase and examples | ||
of valid composer.json files for TYPO3 Extensions can be found on | ||
https://extensions.typo3.org/help/composer-support | ||
|
||
.. index:: Backend, ext:extensionmanager |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
189 changes: 189 additions & 0 deletions
189
typo3/sysext/extensionmanager/Classes/Controller/ExtensionComposerStatusController.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
<?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\Extensionmanager\Controller; | ||
|
||
use TYPO3\CMS\Backend\Form\FormResultCompiler; | ||
use TYPO3\CMS\Backend\Form\NodeFactory; | ||
use TYPO3\CMS\Core\Core\Environment; | ||
use TYPO3\CMS\Core\Imaging\Icon; | ||
use TYPO3\CMS\Core\Package\ComposerDeficitDetector; | ||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; | ||
use TYPO3\CMS\Core\Utility\GeneralUtility; | ||
use TYPO3\CMS\Core\Utility\MathUtility; | ||
use TYPO3\CMS\Core\Utility\PathUtility; | ||
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface; | ||
use TYPO3\CMS\Extensionmanager\Service\ComposerManifestProposalGenerator; | ||
use TYPO3\CMS\Extensionmanager\Utility\ListUtility; | ||
|
||
/** | ||
* Provide information about extensions' composer status | ||
* | ||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API. | ||
*/ | ||
class ExtensionComposerStatusController extends AbstractModuleController | ||
{ | ||
/** | ||
* @var ComposerDeficitDetector | ||
*/ | ||
protected $composerDeficitDetector; | ||
|
||
/** | ||
* @var ComposerDeficitDetector | ||
*/ | ||
protected $composerManifestProposalGenerator; | ||
|
||
/** | ||
* @var NodeFactory | ||
*/ | ||
protected $nodeFactory; | ||
|
||
/** | ||
* @var ListUtility | ||
*/ | ||
protected $listUtility; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
protected $returnUrl = ''; | ||
|
||
public function __construct( | ||
ComposerDeficitDetector $composerDeficitDetector, | ||
ComposerManifestProposalGenerator $composerManifestProposalGenerator, | ||
NodeFactory $nodeFactory, | ||
ListUtility $listUtility | ||
) { | ||
$this->composerDeficitDetector = $composerDeficitDetector; | ||
$this->composerManifestProposalGenerator = $composerManifestProposalGenerator; | ||
$this->nodeFactory = $nodeFactory; | ||
$this->listUtility = $listUtility; | ||
} | ||
|
||
protected function initializeAction(): void | ||
{ | ||
parent::initializeAction(); | ||
if ($this->request->hasArgument('returnUrl')) { | ||
$this->returnUrl = GeneralUtility::sanitizeLocalUrl( | ||
(string)$this->request->getArgument('returnUrl') | ||
); | ||
} | ||
} | ||
|
||
protected function initializeView(ViewInterface $view): void | ||
{ | ||
parent::initializeView($view); | ||
$this->registerDocHeaderButtons(); | ||
} | ||
|
||
public function listAction(): void | ||
{ | ||
$extensions = []; | ||
$basePackagePath = Environment::getExtensionsPath() . '/'; | ||
$detailLinkReturnUrl = $this->uriBuilder->reset()->uriFor('list', array_filter(['returnUrl' => $this->returnUrl])); | ||
foreach ($this->composerDeficitDetector->getExtensionsWithComposerDeficit() as $extensionKey => $deficit) { | ||
$extensionPath = $basePackagePath . $extensionKey . '/'; | ||
$extensions[$extensionKey] = [ | ||
'deficit' => $deficit, | ||
'packagePath' => $extensionPath, | ||
'icon' => $this->getExtensionIcon($extensionPath), | ||
'detailLink' => $this->uriBuilder->reset()->uriFor('detail', [ | ||
'extensionKey' => $extensionKey, | ||
'returnUrl' => $detailLinkReturnUrl | ||
]) | ||
]; | ||
} | ||
ksort($extensions); | ||
$this->view->assign('extensions', $this->listUtility->enrichExtensionsWithEmConfInformation($extensions)); | ||
$this->generateMenu(); | ||
} | ||
|
||
public function detailAction(string $extensionKey): void | ||
{ | ||
if ($extensionKey === '') { | ||
$this->redirect('list'); | ||
} | ||
|
||
$deficit = $this->composerDeficitDetector->checkExtensionComposerDeficit($extensionKey); | ||
$this->view->assignMultiple([ | ||
'extensionKey' => $extensionKey, | ||
'deficit' => $deficit | ||
]); | ||
|
||
if ($deficit !== ComposerDeficitDetector::EXTENSION_COMPOSER_MANIFEST_VALID) { | ||
$this->view->assign('composerManifestMarkup', $this->getComposerManifestMarkup($extensionKey)); | ||
} | ||
} | ||
|
||
protected function getComposerManifestMarkup(string $extensionKey): string | ||
{ | ||
$formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class); | ||
$composerManifest = $this->composerManifestProposalGenerator->getComposerManifestProposal($extensionKey); | ||
if ($composerManifest === '') { | ||
return ''; | ||
} | ||
$rows = MathUtility::forceIntegerInRange(count(explode(LF, $composerManifest)), 1, PHP_INT_MAX); | ||
$fakeFieldTca = [ | ||
'renderType' => 't3editor', | ||
'tableName' => $extensionKey, | ||
'fieldName' => 'composer.json', | ||
'effectivePid' => 0, | ||
'parameterArray' => [ | ||
'itemFormElName' => 'composerManifest-' . $extensionKey, | ||
'itemFormElValue' => $composerManifest, | ||
'fieldConf' => [ | ||
'config' => [ | ||
'readOnly' => true, | ||
'rows' => ++$rows, | ||
'codeMirrorFirstLineNumber' => 1, | ||
] | ||
] | ||
] | ||
]; | ||
$resultArray = $this->nodeFactory->create($fakeFieldTca)->render(); | ||
$formResultCompiler->mergeResult($resultArray); | ||
$formResultCompiler->addCssFiles(); | ||
$formResultCompiler->printNeededJSFunctions(); | ||
return $resultArray['html']; | ||
} | ||
|
||
protected function registerDocHeaderButtons(): void | ||
{ | ||
$buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar(); | ||
if ($this->returnUrl !== '') { | ||
$buttonBar->addButton( | ||
$buttonBar | ||
->makeLinkButton() | ||
->setHref($this->returnUrl) | ||
->setClasses('typo3-goBack') | ||
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.goBack')) | ||
->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL)) | ||
); | ||
} | ||
} | ||
|
||
protected function getExtensionIcon(string $extensionPath): string | ||
{ | ||
$icon = ExtensionManagementUtility::getExtensionIcon($extensionPath); | ||
return $icon ? PathUtility::getAbsoluteWebPath($extensionPath . $icon) : ''; | ||
} | ||
|
||
protected function getLanguageService() | ||
{ | ||
return $GLOBALS['LANG']; | ||
} | ||
} |
Oops, something went wrong.