Skip to content

Commit

Permalink
Merge branch 'develop' into production
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminkott committed Dec 2, 2024
2 parents 124f6dd + cac345e commit b2a5c11
Show file tree
Hide file tree
Showing 18 changed files with 760 additions and 172 deletions.
1 change: 1 addition & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ jobs:
- validation
- php_coding_standards
- php_stan
- tests
if: (github.ref == 'refs/heads/production') && github.event_name == 'push' && (github.repository == 'TYPO3/get.typo3.org')
uses: ./.github/workflows/deployment.yml
secrets:
Expand Down
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@
"sort-packages": true
},
"extra": {
"runtime": {
"use_putenv": true
},
"bamarni-bin": {
"bin-links": true,
"forward-command": false,
Expand Down
319 changes: 161 additions & 158 deletions composer.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions config/packages/framework.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ framework:
php_errors:
log: true

serializer:
name_converter: 'serializer.name_converter.camel_case_to_snake_case'
default_context:
skip_null_values: true

when@test:
framework:
test: true
Expand Down
5 changes: 5 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,8 @@ services:
App\Service\CacheWarmupService:
arguments:
$baseUrl: '%env(BASE_URL)%'

serializer.normalizer.json_serializable:
class: Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer
tags:
- { name: 'serializer.normalizer', priority: -2048 }

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<f:media class="image-embed-item rounded" file="{file}" width="{dimensions.width}" height="{dimensions.height}" alt="{file.alternative}" title="{file.title}" loading="{settings.media.lazyLoading}" decoding="{settings.media.imageDecoding}" />
</html>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<f:layout name="Default" />
<f:section name="Main">

<f:render section="Menu" arguments="{menu: menu, level: 1}" />

</f:section>
<f:section name="Menu">

<f:if condition="{menu}">
<ul class="list-group ps-4 mt-2">
<f:for each="{menu}" as="page">
<li class="list-group-item">
<a href="{page.link}" class="text-decoration-none text-dark level-{level}{f:if(condition: '{level} < 2', then: ' fw-bold')}">
<span>{page.title}</span>
</a>
<f:render section="Menu" arguments="{menu: page.children, level: '{level + 1}'}" />
</li>
</f:for>
</ul>
</f:if>

</f:section>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<f:layout name="Default" />
<f:section name="Main">

<f:if condition="{menu}">
<ul class="nav flex-column">
<f:for each="{menu}" as="page">
<li class="nav-item">
<a href="{page.link}" class="nav-link{f:if(condition: 'page.active', then: ' active')}">
<span>{page.title}</span>
</a>
</li>
</f:for>
</ul>
</f:if>

</f:section>
</html>
15 changes: 8 additions & 7 deletions src/Entity/MajorVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,13 @@ public function setReleases(Collection $releases): void
*/
public function getReleases(): Collection
{
return $this->releases;
$sorted = $this->releases->toArray();
usort($sorted, function ($a, $b) {
return version_compare($a->getVersion(), $b->getVersion());
});
$sorted = array_reverse($sorted);

return new ArrayCollection($sorted);
}

public function setTitle(string $title): void
Expand Down Expand Up @@ -376,14 +382,9 @@ public function jsonSerialize(): array
$releaseData[$release->getVersion()] = $release;
}

uksort(
$releaseData,
static fn(string $a, string $b): int => version_compare($a, $b)
);
$desc = array_reverse($releaseData);
$latest = $this->getLatestRelease();
return [
'releases' => $desc,
'releases' => $releaseData,
'latest' => $latest !== null ? $latest->getVersion() : '',
'stable' => $latest !== null ? $latest->getVersion() : '',
'active' => $this->isActive(),
Expand Down
11 changes: 6 additions & 5 deletions src/Entity/Release.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
use App\Repository\ReleaseRepository;
use Doctrine\ORM\Mapping as ORM;
use OpenApi\Attributes as OA;
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Validator\Constraints as Assert;

#[OA\Schema(description: 'TYPO3 release', title: 'Release')]
Expand All @@ -39,17 +41,16 @@
class Release implements \JsonSerializable, \Stringable
{
#[OA\Property(example: '8.7.12')]
#[Assert\Regex(
'/^(\d+\.\d+\.\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/'
)]
#[Assert\Regex('/^(\d+\.\d+\.\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/')]
#[ORM\Id]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING)]
#[Groups(['content', 'data'])]
private string $version;

#[OA\Property(example: '2017-12-12T16:48:22+00:00')]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_MUTABLE)]
#[Groups(['data', 'content'])]
#[Groups(['content', 'data'])]
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d\\TH:i:sP'])]
private \DateTimeInterface $date;

#[Assert\Choice(callback: [ReleaseTypeEnum::class, 'getAvailableOptions'])]
Expand All @@ -60,7 +61,7 @@ class Release implements \JsonSerializable, \Stringable
#[OA\Property(example: true)]
#[Assert\Type('boolean')]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => 0])]
#[Groups(['data', 'content'])]
#[Groups(['content', 'data'])]
private bool $elts = false;

#[Assert\Valid]
Expand Down
95 changes: 95 additions & 0 deletions tests/Functional/Controller/Api/ApiCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,99 @@ protected function createRequirementFromJson(string $filePath, string $majorVers

return $this->client->getResponse();
}

/**
* @param array<int|string, mixed> $expectedStructure
* @param array<int|string, mixed> $actualArray
*/
protected function assertArrayStructure(array $expectedStructure, array $actualArray): void
{
// If the expected structure is an array (root level as list)
if (isset($expectedStructure[0])) {
self::assertIsArray($actualArray);

// Validate each item in the list
foreach ($actualArray as $item) {
self::assertIsArray($item);
/** @var array<int|string, array<int|string, mixed>> $expectedStructure */
/** @var array<int|string, mixed> $item */
$this->assertArrayStructure($expectedStructure[0], $item);
}
} else {
// Validate each key in the structure
foreach ($expectedStructure as $key => $value) {
$isOptional = is_string($key) && str_starts_with($key, '?');
$actualKey = $isOptional ? ltrim($key, '?') : $key;

if (array_key_exists($actualKey, $actualArray)) {
// If the key exists, validate its structure or type
if (is_array($value)) {
if ($this->isListStructure($value)) {
// Validate a list of items
self::assertIsArray($actualArray[$actualKey]);
foreach ($actualArray[$actualKey] as $item) {
/** @var array<int|string, array<int|string, mixed>> $value */
/** @var array<int|string, mixed> $item */
$this->assertArrayStructure($value[0], $item);
}
} else {
// Validate a single nested structure
/** @var array<int|string, mixed> $value */
/** @var array<int|string, array<int|string, mixed>> $actualArray */
$this->assertArrayStructure($value, $actualArray[$actualKey]);
}
} else {
/** @var string $value */
$this->assertIsType($value, $actualArray[$actualKey], "Key '$actualKey' does not match the expected type.");
}
} elseif (!$isOptional) {
// If the key is not optional, it must exist
self::fail("Missing required key: $actualKey");
}
}
}
}

protected function assertIsType(string $type, mixed $value, string $message): void
{
switch ($type) {
case 'string':
self::assertIsString($value, $message);
break;
case 'boolean':
self::assertIsBool($value, $message);
break;
case 'integer':
self::assertIsInt($value, $message);
break;
case 'float':
self::assertIsFloat($value, $message); // New check for float
break;
case 'array':
self::assertIsArray($value, $message);
break;
case 'datetime':
self::assertIsString($value, $message);
self::assertValidDateTime($value, $message);
break;
default:
self::fail("Unsupported type: $type");
}
}

protected function assertValidDateTime(string $value, string $message): void
{
// ISO 8601 datetime regex
$pattern = '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(\+\d{2}:\d{2}|Z)$/';
self::assertMatchesRegularExpression($pattern, $value, $message);
}

/**
* @param array<int|string, mixed> $structure
*/
protected function isListStructure(array $structure): bool
{
// Determines if the given structure is a list of items (e.g., [ { ... } ])
return count($structure) === 1 && array_keys($structure) === [0] && is_array($structure[0]);
}
}
Loading

0 comments on commit b2a5c11

Please sign in to comment.