Commit 1ab7b9af authored by Thomas Löffler's avatar Thomas Löffler
Browse files

Create new command to check outdated extension versions

parent 0667d169
Pipeline #9452 failed with stages
in 3 minutes and 38 seconds
<?php
declare(strict_types = 1);
namespace T3o\TerFe2\Command;
/*
* This file is part of a TYPO3 extension.
*
* 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!
*/
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use T3o\TerFe2\Domain\Repository\CombinedExtensionRepository;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class CheckForOutdatedExtensions extends Command implements \Psr\Log\LoggerAwareInterface
{
use \Psr\Log\LoggerAwareTrait;
/**
* @var CombinedExtensionRepository
*/
protected $combinedExtensionRepository;
public function __construct(
string $name = null,
CombinedExtensionRepository $combinedExtensionRepository = null
) {
$this->combinedExtensionRepository = $combinedExtensionRepository ?? GeneralUtility::makeInstance(CombinedExtensionRepository::class);
parent::__construct($name);
}
protected function configure()
{
$this->setDescription('Checks all versions if they are outdated (not supporting an actively supported TYPO3 CMS version).');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$io->section('Fetching not outdated and not insecure versions');
$versions = $this->getAllNonOutdatedAndNotInsecureVersions();
$io->success('Found ' . count($versions) . ' versions to check.');
$outdatedVersions = 0;
if ($versions) {
$progressBar = new \Symfony\Component\Console\Helper\ProgressBar($output, count($versions));
$progressBar->setFormat('very_verbose');
$outdatedVersionService = GeneralUtility::makeInstance(\T3o\TerFe2\Service\OutdatedVersionService::class);
$io->section('Checking versions...');
$progressBar->start();
foreach ($versions as $version) {
[$minimumVersion, $maximumVersion] = $this->getTypo3Dependency($version['uid']);
if (!$outdatedVersionService->isVersionDependingOnAnActiveSupportedTypo3Version($minimumVersion, $maximumVersion)) {
$this->setVersionAsOutdated($version['uid']);
$outdatedVersions++;
}
$progressBar->advance();
}
$progressBar->finish();
$io->writeln('');
}
$io->success('Set ' . $outdatedVersions . ' versions as outdated');
}
protected function getAllNonOutdatedAndNotInsecureVersions(): ?array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_terfe2_domain_model_version');
$queryBuilder->getRestrictions()->removeAll();
return $queryBuilder
->select('uid')
->from('tx_terfe2_domain_model_version')
->where(
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0),
$queryBuilder->expr()->gte('review_state', 0)
)
->orderBy('upload_date', 'ASC')
->execute()
->fetchAll(\PDO::FETCH_ASSOC);
}
protected function getTypo3Dependency(int $versionUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_terfe2_domain_model_relation');
$queryBuilder->getRestrictions()->removeAll();
$typo3Dependency = $queryBuilder
->select(...['minimum_version', 'maximum_version'])
->from('tx_terfe2_domain_model_relation')
->where(
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0),
$queryBuilder->expr()->eq('relation_key', $queryBuilder->createNamedParameter('typo3')),
$queryBuilder->expr()->eq('version', $versionUid)
)
->execute()
->fetch(\PDO::FETCH_ASSOC);
return [
$typo3Dependency['minimum_version'],
$typo3Dependency['maximum_version'],
];
}
protected function setVersionAsOutdated(int $versionUid)
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_terfe2_domain_model_version');
$queryBuilder
->update('tx_terfe2_domain_model_version')
->set('review_state', -2)
->where($queryBuilder->expr()->eq('uid', $versionUid))
->execute();
}
}
<?php
declare(strict_types = 1);
namespace T3o\TerFe2\Service;
/*
* This file is part of a TYPO3 extension.
*
* 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!
*/
use TYPO3\CMS\Core\Utility\GeneralUtility;
class OutdatedVersionService
{
public $supportedCoreVersions = [];
public function __construct()
{
$this->supportedCoreVersions = GeneralUtility::makeInstance(LTSVersionService::class)->getActiveVersions();
}
/**
* Checks if the version range of an extension is currently
* supported by one of the active TYPO3 CMS core versions.
*
* @param int $minimumExtensionVersion
* @param int $maximumExtensionVersion
* @return bool
*/
public function isVersionDependingOnAnActiveSupportedTypo3Version(int $minimumExtensionVersion, int $maximumExtensionVersion): bool
{
foreach ($this->supportedCoreVersions as $supportedCoreFirstVersion) {
$minimumCoreVersion = $supportedCoreFirstVersion;
$maximumCoreVersion = $supportedCoreFirstVersion + 999;
if ($minimumExtensionVersion >= $minimumCoreVersion && $minimumExtensionVersion < $maximumCoreVersion) {
return true;
}
if ($maximumExtensionVersion >= $minimumCoreVersion && $maximumExtensionVersion < $maximumCoreVersion) {
return true;
}
if ($minimumExtensionVersion < $minimumCoreVersion && $maximumExtensionVersion > $maximumCoreVersion) {
return true;
}
}
return false;
}
}
<?php
namespace T3o\TerFe2\Task;
/*
* 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!
*/
use ApacheSolrForTypo3\Solr\IndexQueue\Queue;
use Psr\Log\LoggerInterface;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
use TYPO3\CMS\Extbase\Persistence\Generic\Session;
use TYPO3\CMS\Extbase\Scheduler\Task;
/**
* Class CheckForOutdatedExtensions
*/
class CheckForOutdatedExtensions extends Task
{
/**
* @var \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager
*/
protected $persistenceManager;
/**
* @var \T3o\TerFe2\Domain\Repository\VersionRepository
*/
protected $versionRepository;
/**
* @var array
*/
protected $coreVersions;
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManager
*/
protected $objectManager;
/**
* @var \TYPO3\CMS\Extbase\Persistence\Generic\Session
*/
protected $session;
/**
* @var array
*/
protected $supportedCoreVersions = [];
/**
* @var int
*/
protected $releaseDateOfOldestSupportedTypo3Version;
/**
* @var \ApacheSolrForTypo3\Solr\IndexQueue\Queue
*/
protected $solrIndexQueue;
/**
* @var LoggerInterface
*/
protected $logger;
/**
* Initialize Task
*/
public function initializeTask()
{
$this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$this->persistenceManager = $this->objectManager->get(PersistenceManager::class);
$this->session = $this->objectManager->get(Session::class);
$this->versionRepository = $this->objectManager->get(\T3o\TerFe2\Domain\Repository\VersionRepository::class);
$this->coreVersions = json_decode(
GeneralUtility::getURL(Environment::getPublicPath() . '/' . $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] . 'currentcoredata.json'),
true
);
$this->solrIndexQueue = $this->objectManager->get(Queue::class);
}
/**
* Execute Task
*
* @return bool
*/
public function execute()
{
$this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
$this->logger->info('Task CheckForOutdatedExtensions started');
$this->initializeTask();
// Find all extension versions which are not outdated.
$versions = $this->getNotOutdatedAndSecureVersions();
$this->getLatestAndOldestSupportedTypo3Versions();
$this->releaseDateOfOldestSupportedTypo3Version = $this->getReleaseDateOfOldestSupportedTypo3Version();
// Foreach extension
foreach ($versions as $version) {
$this->checkVersion($version);
}
$this->logger->info('Task CheckForOutdatedExtensions finished');
return true;
}
/**
* Get not outdated extensions
*
* @return mixed
*/
public function getNotOutdatedAndSecureVersions()
{
$tableName = 'tx_terfe2_domain_model_version';
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($tableName);
$queryBuilder->getRestrictions()->removeAll();
$statement = $queryBuilder
->select('uid')
->from($tableName)
->where(
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0),
$queryBuilder->expr()->gte('review_state', 0)
)
->orderBy('upload_date', 'ASC')
->execute();
$statement->execute();
return $statement->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Get the release date of the oldest supported TYPO3 Version.
*
* @return int
*/
public function getReleaseDateOfOldestSupportedTypo3Version()
{
$oldestMinorVersion = explode('.', $this->supportedCoreVersions['oldest']);
$oldestMinorVersion = $oldestMinorVersion[0];
$releaseDate = $this->coreVersions[$oldestMinorVersion]['releases'][$this->supportedCoreVersions['oldest']]['date'];
return strtotime($releaseDate);
}
/**
* Get the latest and oldest supported TYPO3 Versions.
*
* @throws \RuntimeException
*/
public function getLatestAndOldestSupportedTypo3Versions()
{
if ($this->coreVersions === null) {
throw new \RuntimeException('typo3.org JSON not accessible!', 1399140291);
}
// Collect currently supported core versions
$oldestSupportedCoreVersion = '99.99.99';
$latestSupportedCoreVersion = '0.0.0';
$allSupportedCoreVersions = [];
foreach ($this->coreVersions as $version => $coreInfo) {
// Only use keys that represent a branch number
if (preg_match('/^\d+\.\d+$/', $version) || preg_match('/^\d+$/', $version)) {
if ($coreInfo['active'] === true) {
$allSupportedCoreVersions[] = $version;
// Checks the latest version
$latestBranchVersion = $coreInfo['latest'];
if (!preg_match('/dev|alpha/', $latestBranchVersion)) {
if (version_compare($latestSupportedCoreVersion, $latestBranchVersion, '<')) {
$latestSupportedCoreVersion = $latestBranchVersion;
}
}
// Check the oldest active version
if (version_compare($version . '.0', $oldestSupportedCoreVersion, '<')) {
$oldestSupportedCoreVersion = $version;
}
}
}
}
// get first beta of oldest active version
$oldestSupportedCoreVersionReleases = array_reverse($this->coreVersions[$oldestSupportedCoreVersion]['releases']);
foreach ($oldestSupportedCoreVersionReleases as $subVersion => $subVersionInfo) {
if (!preg_match('/dev|alpha/', $subVersion)) {
$oldestSupportedCoreVersion = $subVersion;
break;
}
}
$this->supportedCoreVersions = [
'latest' => $latestSupportedCoreVersion,
'oldest' => $oldestSupportedCoreVersion,
'all' => $allSupportedCoreVersions,
];
}
/**
* @param \T3o\TerFe2\Domain\Model\Relation $dependency
*
* @return bool
*/
public function isVersionDependingOnAnActiveSupportedTypo3Version($dependency)
{
$result = false;
if ($dependency instanceof \T3o\TerFe2\Domain\Model\Relation) {
$extensionMinimumVersion = $dependency->getMinimumVersion();
$extensionMaximumVersion = $dependency->getMaximumVersion();
foreach ($this->supportedCoreVersions['all'] as $version) {
$version = (string)$version;
// gets core version x.x.0
$supportedMinimumVersion = VersionNumberUtility::convertVersionNumberToInteger($version . '.0');
$extensionMinimumVersionAsString = VersionNumberUtility::convertIntegerToVersionNumber($extensionMinimumVersion);
/*
* checks if extension dependency lies within the first release of the main release version
* or extension minimum version begins with main release version
*/
if (($supportedMinimumVersion >= $extensionMinimumVersion || strpos($extensionMinimumVersionAsString, $version) === 0)
&& $supportedMinimumVersion <= $extensionMaximumVersion
) {
$result = true;
break;
}
}
}
return $result;
}
/**
* check if the given version is outdated and mark it in database
*
* @param int $versionRecord
*/
protected function checkVersion($versionRecord)
{
/** @var \T3o\TerFe2\Domain\Model\Version $version */
$version = $this->versionRepository->findByUid($versionRecord['uid']);
if (!$version instanceof \T3o\TerFe2\Domain\Model\Version) {
return;
}
$isOutdated = false;
if ($version->getUploadDate() === null) {
$isOutdated = true;
// Check if date is set
} elseif ($version->getUploadDate() < $this->releaseDateOfOldestSupportedTypo3Version) {
$isOutdated = true;
// Check upload date against oldestActiveTYPO3Version first release date.
} elseif (!$this->isVersionDependingOnAnActiveSupportedTypo3Version($version->getTypo3Dependency())) {
$isOutdated = true;
// Check against dependency against TYPO3 not actively supported
}
if ($isOutdated) {
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
$queryBuilder = $connectionPool->getQueryBuilderForTable('tx_terfe2_domain_model_version');
$queryBuilder
->update('tx_terfe2_domain_model_version')
->set('review_state', -2)
->where(
$queryBuilder->expr()->eq('uid', $version->getUid())
)
->execute();
if ($version->getExtension() && $version->getExtension()->getUid()) {
$queryBuilder = $connectionPool->getQueryBuilderForTable('tx_terfe2_domain_model_extension');
$queryBuilder
->update('tx_terfe2_domain_model_extension')
->set('tstamp', time())
->where(
$queryBuilder->expr()->eq('uid', $version->getUid())
)
->execute();
$this->solrIndexQueue->updateItem('tx_terfe2_domain_model_extension', $version->getExtension()->getUid());
}
}
$this->cleanupMemory($version);
}
/**
* free some memory after checking a version
*
* prevent memory leaks on the long running scheduler task
*
* @param \T3o\TerFe2\Domain\Model\Version $version
*/
public function cleanupMemory($version)
{
$this->session->unregisterReconstitutedEntity($version);
foreach ($version->getSoftwareRelations() as $relation) {
/** @var $relation \T3o\TerFe2\Domain\Model\Relation */
$this->session->unregisterReconstitutedEntity($relation);
}
}
}
...@@ -23,4 +23,7 @@ return [ ...@@ -23,4 +23,7 @@ return [
'ter:fetchDocumentationStatus' => [ 'ter:fetchDocumentationStatus' => [
'class' => \T3o\TerFe2\Command\FetchDocumentationStatus::class 'class' => \T3o\TerFe2\Command\FetchDocumentationStatus::class
], ],
'ter:checkForOutdatedExtensions' => [
'class' => \T3o\TerFe2\Command\CheckForOutdatedExtensions::class
],
]; ];
<?php <?php
namespace T3o\TerFe2\Tests\Unit\Task; declare(strict_types=1);
namespace T3o\TerFe2\Tests\Service;
/******************************************************************* /*
* Copyright notice * This file is part of a TYPO3 extension.
* *
* (c) 2014 Thomas Löffler <thomas.loeffler@typo3.org> * It is free software; you can redistribute it and/or modify it under
* (c) 2014 Philipp Gampe <philipp.gampe@typo3.org> * the terms of the GNU General Public License, either version 2
* of the License, or any later version.
* *
* All rights reserved * For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
* *
* This script is part of the TYPO3 project. The TYPO3 project is * The TYPO3 project - inspiring people to share!
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
******************************************************************/
/**
* Class CheckForOutdatedExtensionsTest
*
* @author Thomas Löffler <loeffler@spooner-web.de>
*/ */
class CheckForOutdatedExtensionsTest extends \Nimut\TestingFramework\TestCase\UnitTestCase
{
/** use TYPO3\CMS\Core\Utility\VersionNumberUtility;
* @var \T3o\TerFe2\Task\CheckForOutdatedExtensions
*/
protected $subject = null;
class OutdatedVersionServiceTest extends \Nimut\TestingFramework\TestCase\AbstractTestCase
{