Commit 905f6ce9 authored by Thomas Löffler's avatar Thomas Löffler

Get supported versions automatically

Also:
* Add composer as facet
* Restructure list view
* Restructure detail view
* Add and improve tests
parent 8238e9fa
...@@ -615,4 +615,14 @@ class Extension extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity ...@@ -615,4 +615,14 @@ class Extension extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{ {
$this->notifications = $notifications; $this->notifications = $notifications;
} }
public function getMatrixOfSupportedTypo3Versions(): array
{
$supportedTypo3Versions = [];
foreach ($this->versions as $version) {
$version->getMatrixOfSupportedTypo3Versions($supportedTypo3Versions);
}
return $supportedTypo3Versions;
}
} }
...@@ -21,6 +21,8 @@ use TYPO3\CMS\Extbase\Persistence\ObjectStorage; ...@@ -21,6 +21,8 @@ use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
*/ */
class Version extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity class Version extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{ {
const VERSION_IS_INSECURE = -1;
const VERSION_IS_OUTDATED = -2;
/** /**
* Title of the extension * Title of the extension
...@@ -1151,31 +1153,59 @@ class Version extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity ...@@ -1151,31 +1153,59 @@ class Version extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
} }
/** /**
* @param array $supportedTypo3Versions
* @return array * @return array
*/ */
public function getMatrixOfSupportedTypo3Versions(): array public function getMatrixOfSupportedTypo3Versions(array $supportedTypo3Versions = []): array
{ {
// @todo: Get these versions automatically $ltsVersionService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\T3o\TerFe2\Service\LTSVersionService::class);
$typo3Versions = [ $oldLTSVersions = $ltsVersionService->getOldLTSVersions();
'7 LTS' => 7006000, $this->getSupportedTypo3Versions($supportedTypo3Versions, $oldLTSVersions, 'warning');
'8 LTS' => 8007000,
'9 LTS' => 9005000
];
$supportedTypo3Versions = [];
foreach ($typo3Versions as $label => $mainVersion) { $currentStableVersions = $ltsVersionService->getCurrentStableVersions(true);
$supportedTypo3Versions[$label] = \T3o\TerFe2\Utility\VersionUtility::doesExtensionSupportTypo3Version($this->getExtension(), $mainVersion); $this->getSupportedTypo3Versions($supportedTypo3Versions, $currentStableVersions, 'success');
if ($this->doesSupportTypo3Version($ltsVersionService->getLatestDevelopmentVersion(true))) {
$supportedTypo3Versions[$ltsVersionService->getVersionLabelOfRelease($ltsVersionService->getLatestDevelopmentVersion(true))] = [
'label' => $ltsVersionService->getVersionLabelOfRelease($ltsVersionService->getLatestDevelopmentVersion(true)) . '-dev',
'badgeClass' => 'info'
];
} }
return $supportedTypo3Versions; return $supportedTypo3Versions;
} }
/** public function doesSupportTypo3Version(int $versionNumber): bool
* @return bool
*/
public function hasTypo3Dependency()
{ {
return $this->getTypo3Dependency() !== null; if ($this->getTypo3Dependency() === null || $this->getReviewState() === \T3o\TerFe2\Domain\Model\Version::VERSION_IS_INSECURE) {
return false;
}
// reset from e.g. 7.6.21 to 7.6.0 for valid check
$resetMinimumVersion = (int)($this->getTypo3Dependency()->getMinimumVersion() / \T3o\TerFe2\Service\LTSVersionService::FACTOR_MINOR_VERSION) * \T3o\TerFe2\Service\LTSVersionService::FACTOR_MINOR_VERSION;
$supportsTypo3Version = $resetMinimumVersion <= $versionNumber && $this->getTypo3Dependency()->getMaximumVersion() >= $versionNumber;
if ($supportsTypo3Version) {
return true;
}
return false;
}
private function getSupportedTypo3Versions(array &$supportedTypo3Versions, array $versions, string $badgeClass)
{
$ltsVersionService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\T3o\TerFe2\Service\LTSVersionService::class);
foreach ($versions as $versionNumber) {
$mainVersion = floor($versionNumber / $ltsVersionService::FACTOR_MINOR_VERSION * $ltsVersionService::FACTOR_MINOR_VERSION);
$label = $ltsVersionService->getVersionLabelOfRelease($versionNumber) . ' LTS';
if ($this->doesSupportTypo3Version($mainVersion)) {
$supportedTypo3Versions[$ltsVersionService->getVersionLabelOfRelease($versionNumber)] = [
'label' => $label,
'badgeClass' => $badgeClass
];
}
}
} }
/** /**
......
...@@ -21,11 +21,20 @@ use TYPO3\CMS\Core\Utility\VersionNumberUtility; ...@@ -21,11 +21,20 @@ use TYPO3\CMS\Core\Utility\VersionNumberUtility;
*/ */
class LTSVersionService class LTSVersionService
{ {
const FACTOR_MAIN_VERSION = 1000000;
const FACTOR_MINOR_VERSION = 1000;
const FIRST_LTS_VERSION_WITH_MAIN_VERSION_NUMBER = 7;
/** /**
* @var array * @var array
*/ */
public $coreData = ''; public $coreData = '';
public $ltsVersionsWithMinorVersions = [
'4.5',
'6.2'
];
public function __construct() public function __construct()
{ {
// Will be automatically update with UpdateCurrentVersionListTask see T3o\Ter\Task\UpdateCurrentVersionListTask // Will be automatically update with UpdateCurrentVersionListTask see T3o\Ter\Task\UpdateCurrentVersionListTask
...@@ -37,28 +46,72 @@ class LTSVersionService ...@@ -37,28 +46,72 @@ class LTSVersionService
/** /**
* Get the current stable versions * Get the current stable versions
* *
* @param bool $fullVersionNumber
* @return array * @return array
*/ */
public function getCurrentStableVersions() public function getCurrentStableVersions($fullVersionNumber = false): array
{ {
return [ return [
$this->getLatestOldLTS(), $this->getLatestOldLTS($fullVersionNumber),
$this->getLatestLTS() $this->getLatestLTS($fullVersionNumber)
]; ];
} }
/** /**
* Returns all maintained versions. * Returns all maintained versions.
* *
* @param bool $fullVersionNumber
* @return array * @return array
*/ */
public function getAllMaintainedVersions() public function getAllMaintainedVersions($fullVersionNumber = false): array
{ {
$versions = $this->getCurrentStableVersions(); $versions = $this->getCurrentStableVersions($fullVersionNumber);
array_push($versions, $this->getLatestDevelopmentVersion()); array_push($versions, $this->getLatestDevelopmentVersion($fullVersionNumber));
return $versions; return $versions;
} }
/**
* Get all LTS versions ever
*
* @return array
*/
public function getAllLTSVersions(): array
{
$ltsVersions = [];
foreach ($this->coreData as $releaseVersion => $releaseData) {
if (in_array($releaseVersion, $this->ltsVersionsWithMinorVersions) || $releaseVersion >= self::FIRST_LTS_VERSION_WITH_MAIN_VERSION_NUMBER) {
$latestVersionParts = explode('.', $releaseData['latest']);
if ((int)$latestVersionParts[1] === 0) {
continue;
}
$minimumVersion = $latestVersionParts[0] . '.' . $latestVersionParts[1] . '.0';
$ltsVersions[] = VersionNumberUtility::convertVersionNumberToInteger($minimumVersion);
}
}
sort($ltsVersions);
return $ltsVersions;
}
/**
* Get all LTS versions but current maintained
*
* @return array
*/
public function getOldLTSVersions(): array
{
$oldLTSVersions = $this->getAllLTSVersions();
foreach ($oldLTSVersions as $key => $version) {
$mainVersion = floor($version / self::FACTOR_MAIN_VERSION);
if (in_array($mainVersion, $this->getCurrentStableVersions())) {
unset($oldLTSVersions[$key]);
}
}
return $oldLTSVersions;
}
/** /**
* Get latest version within a specific release * Get latest version within a specific release
* *
...@@ -78,30 +131,57 @@ class LTSVersionService ...@@ -78,30 +131,57 @@ class LTSVersionService
/** /**
* Get latest development version * Get latest development version
* *
* @return string * @param bool $fullVersionNumber
* @return int
*/ */
public function getLatestDevelopmentVersion() public function getLatestDevelopmentVersion($fullVersionNumber = false): int
{ {
return substr($this->coreData['latest_stable'], 0, 3); if ($fullVersionNumber) {
return VersionNumberUtility::convertVersionNumberToInteger($this->coreData['latest_stable']);
}
return floor(VersionNumberUtility::convertVersionNumberToInteger($this->coreData['latest_stable']) / self::FACTOR_MAIN_VERSION);
}
public function getVersionLabelOfRelease(int $release): string
{
if ($release >= (self::FIRST_LTS_VERSION_WITH_MAIN_VERSION_NUMBER * self::FACTOR_MAIN_VERSION)) {
$versionLabel = (string)floor($release / self::FACTOR_MAIN_VERSION);
} else {
$fullVersionLabelParts = explode('.', VersionNumberUtility::convertIntegerToVersionNumber($release));
$versionLabel = $fullVersionLabelParts[0] . '.' . $fullVersionLabelParts[1];
}
return $versionLabel;
} }
/** /**
* Get latest LTS Version * Get latest LTS Version
* *
* @param bool $fullVersionNumber
* @return int * @return int
*/ */
private function getLatestLTS() public function getLatestLTS($fullVersionNumber = false): int
{ {
return (int)substr($this->coreData['latest_lts'], 0, 1); if ($fullVersionNumber) {
return VersionNumberUtility::convertVersionNumberToInteger($this->coreData['latest_lts']);
}
return floor(VersionNumberUtility::convertVersionNumberToInteger($this->coreData['latest_lts']) / self::FACTOR_MAIN_VERSION);
} }
/** /**
* Get latest "old" LTS Version * Get latest "old" LTS Version
* *
* @param bool $fullVersionNumber
* @return int * @return int
*/ */
private function getLatestOldLTS() public function getLatestOldLTS($fullVersionNumber = false): int
{ {
return (int)substr($this->coreData['latest_old_lts'], 0, 1); if ($fullVersionNumber) {
return VersionNumberUtility::convertVersionNumberToInteger($this->coreData['latest_old_lts']);
}
return floor(VersionNumberUtility::convertVersionNumberToInteger($this->coreData['latest_old_lts']) / self::FACTOR_MAIN_VERSION);
} }
} }
...@@ -109,24 +109,34 @@ class TerIndexer extends \ApacheSolrForTypo3\Solr\IndexQueue\Indexer ...@@ -109,24 +109,34 @@ class TerIndexer extends \ApacheSolrForTypo3\Solr\IndexQueue\Indexer
} }
// does this extension supports different versions? // does this extension supports different versions?
// @todo: use JSON with all versions to get them $supportMaintainedVersions = [];
$document->setField('supports7_boolS', false); $supportDevVersion = '';
$document->setField('supports8_boolS', false); $supportOlderVersions = [];
$document->setField('supports9_boolS', false);
$typo3Support = []; $ltsVersionService = GeneralUtility::makeInstance(\T3o\TerFe2\Service\LTSVersionService::class);
if (VersionUtility::doesExtensionSupportTypo3Version($extension, 7006000)) { foreach ($ltsVersionService->getCurrentStableVersions(true) as $versionNumber) {
$typo3Support[] = '7 LTS'; if (VersionUtility::doesExtensionSupportTypo3Version($extension, $versionNumber)) {
$document->setField('supports7_boolS', true); $supportMaintainedVersions[] = $ltsVersionService->getVersionLabelOfRelease($versionNumber) . ' LTS';
}
} }
if (VersionUtility::doesExtensionSupportTypo3Version($extension, 8007000)) { if ($ltsVersionService->getLatestDevelopmentVersion() !== $ltsVersionService->getLatestLTS() && VersionUtility::doesExtensionSupportTypo3Version($extension, $ltsVersionService->getLatestDevelopmentVersion(true))) {
$typo3Support[] = '8 LTS'; $supportDevVersion = $ltsVersionService->getLatestDevelopmentVersion() . '-dev';
$document->setField('supports8_boolS', true);
} }
if (VersionUtility::doesExtensionSupportTypo3Version($extension, 9005000)) {
$typo3Support[] = '9 LTS'; foreach ($ltsVersionService->getOldLTSVersions() as $versionNumber) {
$document->setField('supports9_boolS', true); if (VersionUtility::doesExtensionSupportTypo3Version($extension, $versionNumber)) {
$supportOlderVersions[] = $ltsVersionService->getVersionLabelOfRelease($versionNumber) . ' LTS';
}
}
$document->setField('typo3support_stringM', $supportMaintainedVersions);
$facetVersions = $supportMaintainedVersions;
if ($supportDevVersion) {
$facetVersions = array_merge($supportMaintainedVersions, [$supportDevVersion]);
} }
$document->setField('typo3support_stringM', $typo3Support); $document->setField('typo3supportfacets_stringM', $facetVersions);
$document->setField('typo3supportdev_stringS', $supportDevVersion);
$document->setField('typo3supportold_stringM', $supportOlderVersions);
$extensionIcon = $this->getExtensionIcon($itemRecord['ext_key'], $extension->getLastVersion()->getVersionString()); $extensionIcon = $this->getExtensionIcon($itemRecord['ext_key'], $extension->getLastVersion()->getVersionString());
$document->setField('extensionIcon_stringS', $extensionIcon); $document->setField('extensionIcon_stringS', $extensionIcon);
......
...@@ -49,7 +49,7 @@ class VersionUtility ...@@ -49,7 +49,7 @@ class VersionUtility
public static function doesExtensionSupportTypo3Version(\T3o\TerFe2\Domain\Model\Extension $extension, int $mainVersion): bool public static function doesExtensionSupportTypo3Version(\T3o\TerFe2\Domain\Model\Extension $extension, int $mainVersion): bool
{ {
foreach ($extension->getVersions() as $version) { foreach ($extension->getVersions() as $version) {
if ($version->hasTypo3Dependency() === false || $version->isLive() === false) { if ($version->hasTypo3Dependency() === false || $version->getReviewState() === \T3o\TerFe2\Domain\Model\Version::VERSION_IS_INSECURE) {
continue; continue;
} }
......
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true"> <html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<f:if condition="{version.matrixOfSupportedTypo3Versions}"> <f:if condition="{version.typo3Dependency}">
<ul class="list-inline"> <h4>This version supports TYPO3</h4>
<f:for each="{version.matrixOfSupportedTypo3Versions}" key="label" as="supported"> <p>
<f:if condition="{supported}"> <f:for each="{version.matrixOfSupportedTypo3Versions}" as="version">
<li class="list-inline-item badge badge-success">{label}</li> <button class="btn btn-{version.badgeClass} btn-sm mr-2" disabled>{version.label}</button>
</f:if>
</f:for> </f:for>
</ul> </p>
</f:if> </f:if>
</html> </html>
...@@ -90,17 +90,15 @@ ...@@ -90,17 +90,15 @@
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-8"> <div class="col-md-8">
<p itemprop="description">{extension.lastVersion.description}</p> <p itemprop="description">{extension.lastVersion.description}</p>
<h3> <f:render partial="VersionSupportForTypo3" arguments="{version: extension.lastVersion}" />
<f:render partial="VersionSupportForTypo3" arguments="{version: extension.lastVersion}" />
</h3>
<f:if condition="{extension.composerName}"> <f:if condition="{extension.composerName}">
<p class="mb-3"> <p class="mb-3">
<h3>Composer support</h3> <h4>Composer support</h4>
<f:render partial="ComposerNameWithClipboard" arguments="{composerName: extension.composerName}" /> <f:render partial="ComposerNameWithClipboard" arguments="{composerName: extension.composerName}" />
</p> </p>
</f:if> </f:if>
<f:if condition="{extension.tags}"> <f:if condition="{extension.tags}">
<h3>Tags</h3> <h4>Tags</h4>
<p class="tags"> <p class="tags">
<f:for each="{extension.tags}" as="tag"> <f:for each="{extension.tags}" as="tag">
<f:link.page class="btn btn-outline-info mb-1" pageUid="{settings.pages.searchResultsPid}" additionalParams="{tx_solr: {filter: {0: 'tags:{tag.title}'}}}"> <f:link.page class="btn btn-outline-info mb-1" pageUid="{settings.pages.searchResultsPid}" additionalParams="{tx_solr: {filter: {0: 'tags:{tag.title}'}}}">
...@@ -116,16 +114,16 @@ ...@@ -116,16 +114,16 @@
</f:for> </f:for>
</div> </div>
</f:if> </f:if>
<h3> <h4>
<f:translate key="last_upload_comment" /> <f:translate key="last_upload_comment" />
</h3> </h4>
<p itemprop="releaseNotes"> <p itemprop="releaseNotes">
<f:format.nl2br>{extension.lastVersion.uploadComment}</f:format.nl2br> <f:format.nl2br>{extension.lastVersion.uploadComment}</f:format.nl2br>
</p> </p>
<f:if condition="{versionHistory->f:count()} >= 1}"> <f:if condition="{versionHistory->f:count()} >= 1}">
<h3> <h4>
<f:translate key="downloads_by_version" /> <f:translate key="downloads_by_version" />
</h3> </h4>
<script type="text/javascript"> <script type="text/javascript">
var versionChartData = {extension.downloadsByVersionsAsJson -> f:format.raw()}; var versionChartData = {extension.downloadsByVersionsAsJson -> f:format.raw()};
</script> </script>
......
...@@ -40,7 +40,15 @@ class LTSVersionServiceTest extends UnitTestCase ...@@ -40,7 +40,15 @@ class LTSVersionServiceTest extends UnitTestCase
*/ */
public function getCurrentStableVersions() public function getCurrentStableVersions()
{ {
$this->assertSame([7, 8], $this->subject->getCurrentStableVersions()); self::assertSame([8, 9], $this->subject->getCurrentStableVersions());
}
/**
* @test
*/
public function getCurrentStableVersionsAsFullVersionNumber()
{
self::assertSame([8007009, 9005005], $this->subject->getCurrentStableVersions(true));
} }
/** /**
...@@ -48,7 +56,15 @@ class LTSVersionServiceTest extends UnitTestCase ...@@ -48,7 +56,15 @@ class LTSVersionServiceTest extends UnitTestCase
*/ */
public function getAllMaintainedVersions() public function getAllMaintainedVersions()
{ {
$this->assertSame([7, 8, '9.0'], $this->subject->getAllMaintainedVersions()); self::assertSame([8, 9, 10], $this->subject->getAllMaintainedVersions());
}
/**
* @test
*/
public function getAllMaintainedVersionsAsFullVersionNumber()
{
self::assertSame([8007009, 9005005, 10000000], $this->subject->getAllMaintainedVersions(true));
} }
/** /**
...@@ -56,7 +72,7 @@ class LTSVersionServiceTest extends UnitTestCase ...@@ -56,7 +72,7 @@ class LTSVersionServiceTest extends UnitTestCase
*/ */
public function getLatestVersionOfRelease() public function getLatestVersionOfRelease()
{ {
$this->assertSame('7.6.21', $this->subject->getLatestVersionOfRelease(7)); self::assertSame('7.6.21', $this->subject->getLatestVersionOfRelease(7));
} }
/** /**
...@@ -64,7 +80,69 @@ class LTSVersionServiceTest extends UnitTestCase ...@@ -64,7 +80,69 @@ class LTSVersionServiceTest extends UnitTestCase
*/ */
public function getLatestDevelopmentVersion() public function getLatestDevelopmentVersion()
{ {
$this->assertSame('9.0', $this->subject->getLatestDevelopmentVersion()); self::assertSame(10, $this->subject->getLatestDevelopmentVersion());
}
/**
* @test
*/
public function getLatestDevelopmentVersionAsFullVersionNumber()
{
self::assertSame(10000000, $this->subject->getLatestDevelopmentVersion(true));
}
/**
* @test
*/
public function getAllLTSVersionsAsFullVersionNumber()
{
self::assertSame(
[
4005000,
6002000,
7006000,
8007000,
9005000
],
$this->subject->getAllLTSVersions()
);
}
/**
* @test
*/
public function getOldLTSVersionsAsFullVersionNumber()
{
self::assertSame(
[
4005000,
6002000,
7006000
],
$this->subject->getOldLTSVersions()
);
}
/**
* @test
*/
public function getVersionLabelOfNewVersion()
{
self::assertSame(
'8',
$this->subject->getVersionLabelOfRelease(8007004)
);
}
/**
* @test
*/
public function getVersionLabelOfLegacyVersion()
{
self::assertSame(
'4.5',
$this->subject->getVersionLabelOfRelease(4005031)
);
} }
/** /**
...@@ -75,7 +153,8 @@ class LTSVersionServiceTest extends UnitTestCase ...@@ -75,7 +153,8 @@ class LTSVersionServiceTest extends UnitTestCase
private function getSampleData() private function getSampleData()
{ {
$versions = [ $versions = [
'7' => [ 7 => [
'latest' => '7.6.25',
'releases' => [ 'releases' => [
'7.6.21' => [ '7.6.21' => [
'version' => '7.6.21' 'version' => '7.6.21'
...@@ -88,10 +167,44 @@ class LTSVersionServiceTest extends UnitTestCase ...@@ -88,10 +167,44 @@ class LTSVersionServiceTest extends UnitTestCase
], ],
] ]
], ],
'latest_stable' => '9.0.0', 8 => [
'latest_old_stable' => '8.7.9', 'latest' => '8.7.9',
'latest_lts' => '8.7.9', 'releases' => [
'latest_old_lts' => '7.6.21' '8.7.9' => [
'version' => '8.7.9'
],
'8.7.8' => [
'version' => '8.7.8'
],
'8.7.7' => [
'version' => '8.7.7'
],
]
],
9 => [
'latest' => '9.5.5',
'releases' => [
'9.5.5' => [
'version' => '9.5.5'
],
'9.5.4' => [
'version' => '9.5.4'
]
]
],
10 => [
'latest' => '10.0.0'
],
'6.2' => [
'latest' => '6.2.31'
],
'4.5' => [
'latest' => '4.5.40'
],
'latest_stable' => '10.0.0',
'latest_old_stable' => '9.5.5',
'latest_lts' => '9.5.5',
'latest_old_lts' => '8.7.9'
]; ];
return $versions; return $versions;
......
...@@ -19,10 +19,24 @@ plugin.tx_solr { ...@@ -19,10 +19,24 @@ plugin.tx_solr {
facets { facets {
typo3 { typo3 {
label = Works with TYPO3 label = Works with TYPO3
field = typo3support_stringM field = typo3supportfacets_stringM
partialName = OptionsTypo3 partialName = OptionsTypo3
itemClass = success itemClass = success
sortBy = alpha }
composer {
label = Composer support
field = supportsComposer_boolS
partialName = OptionsComposer
itemClass = info
excludeValues = 0
renderingInstruction = CASE
renderingInstruction {
key.field = optionValue
1 = TEXT
1.value = Composer support
}
} }
tags { tags {
......