Commit 6ef39dac authored by Christian Kuhn's avatar Christian Kuhn
Browse files

[!!!][TASK] Drop class.ext_update.php handling

The old upgrade functionality within the extension
manager has been superseded by the upgrade wizard
API of the install tool since core v9. The old API
based on the file class.ext_update.php in extensions
has been marked as discouraged in the docs since then.

Many extensions use the new API successfully. It comes
with much more flexibility and also includes happy
little features like a CLI interface.

The patch drops handling of class.ext_update.php without
further deprecation. Extensions still using that script
should migrate to the new API when preparing core v11
readiness.

Change-Id: I1cb40f1222a37763e8d9549af53520597f739d1a
Resolves: #93083
Releases: master
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/67142

Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent 32867a4a
.. include:: ../../Includes.txt
========================================================
Breaking: #93083 - class.ext_update.php handling removed
========================================================
See :issue:`93083`
Description
===========
Handling of old :file:`class.ext_update.php` update scripts has been
dropped: The core introduced a much more solid API for extensions to
perform upgrades with the release of core v9 already. That API matured
and many extensions use it in favor of the clumsy
:file:`class.ext_update.php` solution. Removal of this functionality
within the extension manager has been long overdue and is finally done
with core v11.
Impact
======
The :file:`class.ext_update.php` was an old way for extensions to
perform upgrade steps. The TYPO3 core no longer supports this API.
Affected Installations
======================
Some old-style extensions may still rely on this script. It's usage
has been discouraged since the new upgrade wizards API.
Migration
=========
Migrate :file:`class.ext_update.php` to the :ref:`upgrade wizard API of the
Install Tool <t3coreapi:upgrade-wizards>`.
.. index:: PHP-API, NotScanned, ext:extensionmanager
<?php
/*
* 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\Template\Components\ButtonBar;
use TYPO3\CMS\Backend\View\BackendTemplateView;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
use TYPO3\CMS\Extensionmanager\Utility\UpdateScriptUtility;
/**
* Controller for configuration related actions.
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
*/
class UpdateScriptController extends AbstractModuleController
{
/**
* Set up the doc header properly here
*
* @param ViewInterface $view
*/
protected function initializeView(ViewInterface $view)
{
if ($view instanceof BackendTemplateView) {
/** @var BackendTemplateView $view */
parent::initializeView($view);
$this->generateMenu();
$this->registerDocheaderButtons();
}
}
/**
* Show the content of the update script (if any).
*
* @param string $extensionKey Extension key
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
*/
public function showAction($extensionKey)
{
$updateScriptUtility = GeneralUtility::makeInstance(UpdateScriptUtility::class);
$updateScriptResult = $updateScriptUtility->executeUpdateIfNeeded($extensionKey);
$this->view
->assign('updateScriptResult', $updateScriptResult)
->assign('extensionKey', $extensionKey);
}
/**
* Registers the Icons into the docheader
*
* @throws \InvalidArgumentException
*/
protected function registerDocheaderButtons()
{
/** @var ButtonBar $buttonBar */
$buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
$uri = $this->uriBuilder->reset()->uriFor('index', [], 'List');
$title = $this->translate('extConfTemplate.backToList');
$icon = $this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL);
$button = $buttonBar->makeLinkButton()
->setHref($uri)
->setTitle($title)
->setIcon($icon);
$buttonBar->addButton($button, ButtonBar::BUTTON_POSITION_LEFT);
}
}
<?php
/*
* 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\Utility;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
/**
* Utility to find and execute class.ext_update.php scripts of extensions
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class UpdateScriptUtility
{
/**
* Returns true, if ext_update class says it wants to run.
*
* @param string $extensionKey extension key
* @return mixed NULL, if update is not available, else update script return
*/
public function executeUpdateIfNeeded($extensionKey)
{
$className = $this->requireUpdateScript($extensionKey);
$scriptObject = GeneralUtility::makeInstance($className);
// old em always assumed the method exist, we do so too.
// @TODO: Make this smart, let scripts implement interfaces
// @TODO: With current ext_update construct it is impossible to enforce some type of return
return $scriptObject->access() ? $scriptObject->main() : null;
}
/**
* Require update script.
* Throws exception if update script does not exist, so checkUpdateScriptExists()
* should be called before
*
* @param string $extensionKey
* @return string Class name of update script
* @throws ExtensionManagerException
*/
protected function requireUpdateScript($extensionKey)
{
if (class_exists('ext_update', false)) {
throw new ExtensionManagerException(
'class ext_update for this run does already exist, requiring impossible',
1359748085
);
}
$className = $this->determineUpdateClassName($extensionKey);
if ($className === '') {
throw new ExtensionManagerException(
'Requested update script of extension does not exist',
1359747976
);
}
return $className;
}
/**
* Checks if an update class file exists.
*
* Does not check if some update is needed.
*
* @param string $extensionKey Extension key
* @return bool TRUE, if there is some update script and it needs to be executed
*/
public function checkUpdateScriptExists($extensionKey)
{
$className = $this->determineUpdateClassName($extensionKey);
if ($className !== '') {
$updater = GeneralUtility::makeInstance($className);
return $updater->access();
}
return false;
}
/**
* Determine the real class name to use
*
* @param string $extensionKey
* @return string Returns the final class name if an update script is present, otherwise empty string
* @throws ExtensionManagerException If an update script is present but no ext_update class can be loaded
*/
protected function determineUpdateClassName($extensionKey)
{
$updateScript = GeneralUtility::getFileAbsFileName('EXT:' . $extensionKey . '/class.ext_update.php');
if (!file_exists($updateScript)) {
return '';
}
// get script contents
$scriptSourceCode = GeneralUtility::getUrl($updateScript);
// check if it has a namespace
if (!preg_match('/<\?php.*namespace\s+([^;]+);.*class/is', $scriptSourceCode, $matches)) {
// if no, rename the class with a unique name
$className = 'ext_update' . md5($extensionKey . $scriptSourceCode);
$temporaryFileName = Environment::getVarPath() . '/transient/' . $className . '.php';
if (!file_exists(GeneralUtility::getFileAbsFileName($temporaryFileName))) {
$scriptSourceCode = preg_replace('/^\s*class\s+ext_update\s+/m', 'class ' . $className . ' ', $scriptSourceCode);
GeneralUtility::writeFileToTypo3tempDir($temporaryFileName, $scriptSourceCode);
}
$updateScript = $temporaryFileName;
} else {
$className = $matches[1] . '\ext_update';
}
include_once $updateScript;
if (!class_exists($className, false)) {
throw new ExtensionManagerException(
sprintf('class.ext_update.php of extension "%s" did not declare ext_update class', $extensionKey),
1428176468
);
}
return $className;
}
}
<?php
/*
* 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\ViewHelpers;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
use TYPO3\CMS\Extensionmanager\Utility\UpdateScriptUtility;
use TYPO3\CMS\Fluid\ViewHelpers\Link\ActionViewHelper;
/**
* ViewHelper for update script link
* @internal
*/
class UpdateScriptViewHelper extends ActionViewHelper
{
/**
* initialize arguments
*/
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('extensionKey', 'string', 'Extension key', true);
}
/**
* Renders a link to the update script screen if the extension has one
*
* @return string The rendered a tag
*/
public function render()
{
$extensionKey = $this->arguments['extensionKey'];
// If the "class.ext_update.php" file exists, build link to the update script screen
$updateScriptUtility = GeneralUtility::makeInstance(UpdateScriptUtility::class);
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
if ($updateScriptUtility->checkUpdateScriptExists($extensionKey)) {
/** @var UriBuilder $uriBuilder */
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$uriBuilder->setRequest($this->renderingContext->getRequest());
$action = 'show';
$uri = $uriBuilder->reset()->uriFor(
$action,
['extensionKey' => $extensionKey],
'UpdateScript'
);
$this->tag->addAttribute('href', $uri);
$this->tag->addAttribute('title', LocalizationUtility::translate('extensionList.update.script', 'extensionmanager'));
$this->tag->setContent($iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL)->render());
$tag = $this->tag->render();
} else {
return '<span class="btn btn-default disabled">' . $iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
}
return $tag;
}
}
......@@ -30,12 +30,6 @@
<trans-unit id="extConfTemplate.backToList" resname="extConfTemplate.backToList">
<source>Back to list</source>
</trans-unit>
<trans-unit id="updateScript.headline" resname="updateScript.headline">
<source>Update script for %s</source>
</trans-unit>
<trans-unit id="updateScript.none" resname="updateScript.none">
<source>No update necessary.</source>
</trans-unit>
<trans-unit id="button.cancel" resname="button.cancel">
<source>Cancel</source>
</trans-unit>
......@@ -57,9 +51,6 @@
<trans-unit id="button.resolveDependenciesIgnore" resname="button.resolveDependenciesIgnore">
<source>I know what I'm doing, continue anyway</source>
</trans-unit>
<trans-unit id="extensionList.update.script" resname="extensionList.update.script">
<source>Execute the update script</source>
</trans-unit>
<trans-unit id="extensionList.downloadsql" resname="extensionList.downloadsql">
<source>Download SQL Dump</source>
</trans-unit>
......
......@@ -111,7 +111,6 @@
<td>
<div class="btn-group">
<em:processAvailableActions extension="{extension}">
<em:updateScript class="btn btn-default" extensionKey="{extension.key}" />
<f:if condition="!{isComposerMode}">
<em:removeExtension class="btn btn-default" extension="{extension}" />
</f:if>
......
{namespace em=TYPO3\CMS\Extensionmanager\ViewHelpers}
<f:layout name="main" />
<f:section name="content">
<h1><f:translate key="updateScript.headline" arguments="{0: extensionKey}" /></h1>
<div class="update-script">
<f:if condition="{updateScriptResult}">
<f:then>
<f:format.raw>{updateScriptResult}</f:format.raw>
</f:then>
<f:else>
<f:translate key="updateScript.none" />
</f:else>
</f:if>
</div>
</f:section>
......@@ -11,7 +11,6 @@ defined('TYPO3') or die();
\TYPO3\CMS\Extensionmanager\Controller\ListController::class => 'index,unresolvedDependencies,ter,showAllVersions,distributions',
\TYPO3\CMS\Extensionmanager\Controller\ActionController::class => 'toggleExtensionInstallationState,installExtensionWithoutSystemDependencyCheck,removeExtension,downloadExtensionZip,reloadExtensionData',
\TYPO3\CMS\Extensionmanager\Controller\DownloadController::class => 'checkDependencies,installFromTer,installExtensionWithoutSystemDependencyCheck,installDistribution,updateExtension,updateCommentForUpdatableVersions',
\TYPO3\CMS\Extensionmanager\Controller\UpdateScriptController::class => 'show',
\TYPO3\CMS\Extensionmanager\Controller\UpdateFromTerController::class => 'updateExtensionListFromTer',
\TYPO3\CMS\Extensionmanager\Controller\UploadExtensionFileController::class => 'form,extract',
\TYPO3\CMS\Extensionmanager\Controller\DistributionController::class => 'show'
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment