Skip to content

Commit 469031f

Browse files
committed
Merge branch 'release/1.1.6'
2 parents 3704551 + b752917 commit 469031f

File tree

10 files changed

+154
-16
lines changed

10 files changed

+154
-16
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ node_modules/
2929
###> phpunit/phpunit ###
3030
/phpunit.xml
3131
.phpunit.result.cache
32+
coverage
3233
###< phpunit/phpunit ###
3334

3435
###> liip/imagine-bundle ###

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ See [keep a changelog] for information about writing changes to this log.
88

99
## [Unreleased]
1010

11+
## [1.1.6] - 2025-03-27
12+
13+
- Fix special char handling in event excerpt field, fix wrong chars in existing excerpt fields
14+
- Fix start/end not required for occurrences in EasyAdmin
15+
- Set "updated at" to newest of either entity or entity relations when indexing
16+
1117
## [1.1.5] - 2025-03-12
1218

1319
- Add labels to Woodpecker workflow
@@ -95,7 +101,8 @@ See [keep a changelog] for information about writing changes to this log.
95101
- Consolidate scheduled feed import and index populate in one command
96102

97103
[keep a changelog]: https://keepachangelog.com/en/1.1.0/
98-
[unreleased]: https://github.com/itk-dev/event-database-imports/compare/1.1.5...HEAD
104+
[unreleased]: https://github.com/itk-dev/event-database-imports/compare/1.1.6...HEAD
105+
[1.1.6]: https://github.com/itk-dev/event-database-imports/releases/tag/1.1.6
99106
[1.1.5]: https://github.com/itk-dev/event-database-imports/releases/tag/1.1.5
100107
[1.1.4]: https://github.com/itk-dev/event-database-imports/releases/tag/1.1.4
101108
[1.1.3]: https://github.com/itk-dev/event-database-imports/releases/tag/1.1.3
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoctrineMigrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Auto-generated Migration: Please modify to your needs!
12+
*/
13+
final class Version20250326151430 extends AbstractMigration
14+
{
15+
public function getDescription(): string
16+
{
17+
return 'Convert HTML character entities in excerpt fields to their actual characters';
18+
}
19+
20+
public function up(Schema $schema): void
21+
{
22+
// Get all records that have excerpt fields containing '&' (indicating HTML entities)
23+
$qb = $this->connection->createQueryBuilder();
24+
$records = $qb->select('e.id', 'e.excerpt')
25+
->from('event', 'e')
26+
->where($qb->expr()->like('e.excerpt', ':pattern'))
27+
->setParameter('pattern', '%&%')
28+
->executeQuery()
29+
->fetchAllAssociative();
30+
31+
// Process each record
32+
foreach ($records as $record) {
33+
if (null === $record['excerpt']) {
34+
continue; // Skip if excerpt is null
35+
}
36+
37+
// Decode HTML entities using PHP's native function
38+
$decodedExcerpt = html_entity_decode($record['excerpt'], ENT_QUOTES | ENT_HTML5);
39+
40+
// Only update if something changed
41+
if ($decodedExcerpt !== $record['excerpt']) {
42+
$this->connection->createQueryBuilder()
43+
->update('event', 'e')
44+
->set('e.excerpt', ':excerpt')
45+
->where('e.id = :id')
46+
->setParameter('excerpt', $decodedExcerpt)
47+
->setParameter('id', $record['id'])
48+
->executeStatement();
49+
}
50+
}
51+
}
52+
53+
public function down(Schema $schema): void
54+
{
55+
// No need to revert this change as it just formats data without functional changes
56+
}
57+
}

phpstan-baseline.neon

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,6 @@ parameters:
246246
count: 1
247247
path: src/Repository/VocabularyRepository.php
248248

249-
-
250-
message: '#^Call to an undefined method App\\Service\\Indexing\\IndexItemInterface\:\:getEvent\(\)\.$#'
251-
identifier: method.notFound
252-
count: 1
253-
path: src/Service/Indexing/IndexingDailyOccurrences.php
254-
255249
-
256250
message: '#^Call to an undefined method Symfony\\Component\\Serializer\\SerializerInterface\:\:normalize\(\)\.$#'
257251
identifier: method.notFound
@@ -288,12 +282,6 @@ parameters:
288282
count: 1
289283
path: src/Service/Indexing/IndexingLocations.php
290284

291-
-
292-
message: '#^Call to an undefined method App\\Service\\Indexing\\IndexItemInterface\:\:getEvent\(\)\.$#'
293-
identifier: method.notFound
294-
count: 1
295-
path: src/Service/Indexing/IndexingOccurrences.php
296-
297285
-
298286
message: '#^Call to an undefined method Symfony\\Component\\Serializer\\SerializerInterface\:\:normalize\(\)\.$#'
299287
identifier: method.notFound

src/Controller/Admin/EmbedOccurrenceCrudController.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ public function configureFields(string $pageName): iterable
2929
FormField::addFieldset('Dates')
3030
->setLabel(new TranslatableMessage('admin.occurrence.dates.headline')),
3131
DateTimeField::new('start')
32+
->setHtmlAttribute('required', 'required')
3233
->setLabel(new TranslatableMessage('admin.occurrence.dates.start'))
3334
->setColumns(6),
3435
DateTimeField::new('end')
36+
->setHtmlAttribute('required', 'required')
3537
->setLabel(new TranslatableMessage('admin.occurrence.dates.end'))
3638
->setColumns(6),
3739

src/Entity/Occurrence.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Gedmo\Timestampable\Traits\TimestampableEntity;
1414
use Symfony\Component\Serializer\Annotation\Groups;
1515
use Symfony\Component\Serializer\Annotation\SerializedPath;
16+
use Symfony\Component\Validator\Constraints as Assert;
1617

1718
#[ORM\Entity(repositoryClass: OccurrenceRepository::class)]
1819
class Occurrence implements IndexItemInterface, EditableEntityInterface
@@ -28,12 +29,17 @@ class Occurrence implements IndexItemInterface, EditableEntityInterface
2829
#[SerializedPath('[entityId]')]
2930
private ?int $id = null;
3031

31-
#[ORM\Column]
32+
#[ORM\Column(nullable: false)]
3233
#[Groups([IndexNames::Events->value, IndexNames::Occurrences->value])]
3334
private ?\DateTimeImmutable $start = null;
3435

35-
#[ORM\Column]
36+
#[ORM\Column(nullable: false)]
3637
#[Groups([IndexNames::Events->value, IndexNames::Occurrences->value])]
38+
#[Assert\DateTime]
39+
#[Assert\Expression(
40+
'this.getStart() < this.getEnd()',
41+
message: 'The start date must be before the end date'
42+
)]
3743
private ?\DateTimeImmutable $end = null;
3844

3945
#[ORM\Column(length: 255, nullable: true)]

src/EventSubscriber/EasyAdminSubscriber.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ private function handleNormalization(object $entity): void
8484

8585
$excerpt = $entity->getExcerpt();
8686
if (!empty($excerpt)) {
87-
$excerpt = $this->contentNormalizer->sanitize($excerpt);
8887
$excerpt = $this->contentNormalizer->trimLength($excerpt, Event::EXCERPT_MAX_LENGTH);
8988
$entity->setExcerpt($excerpt);
9089
} elseif (!is_null($description)) {

src/Service/Indexing/IndexingDailyOccurrences.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Service\Indexing;
44

5+
use App\Entity\DailyOccurrence;
56
use App\Model\Indexing\IndexFieldTypes;
67
use App\Model\Indexing\IndexNames;
78
use Elastic\Elasticsearch\Client;
@@ -25,6 +26,13 @@ public function __construct(
2526

2627
public function serialize(IndexItemInterface $item): array
2728
{
29+
if (!$item instanceof DailyOccurrence) {
30+
throw new \InvalidArgumentException('Item must be an instance of DailyOccurrence.');
31+
}
32+
33+
$updatedAt = $this->getUpdatedAt($item);
34+
$item->setUpdatedAt($updatedAt);
35+
2836
$contextBuilder = (new ObjectNormalizerContextBuilder())
2937
->withGroups([IndexNames::Occurrences->value]);
3038
$contextBuilder = (new DateTimeNormalizerContextBuilder())
@@ -39,4 +47,24 @@ public function serialize(IndexItemInterface $item): array
3947

4048
return $data;
4149
}
50+
51+
private function getUpdatedAt(DailyOccurrence $occurrence): \DateTime
52+
{
53+
$updatedAt = $occurrence->getUpdatedAt();
54+
$event = $occurrence->getEvent();
55+
56+
$updatedAt = max($updatedAt, $event->getOrganization()?->getUpdatedAt());
57+
$updatedAt = max($updatedAt, $event->getLocation()?->getUpdatedAt());
58+
$updatedAt = max($updatedAt, $event->getImage()?->getUpdatedAt());
59+
60+
foreach ($event->getPartners() as $partner) {
61+
$updatedAt = max($updatedAt, $partner->getUpdatedAt());
62+
}
63+
64+
foreach ($event->getOccurrences() as $occurrence) {
65+
$updatedAt = max($updatedAt, $occurrence->getUpdatedAt());
66+
}
67+
68+
return $updatedAt;
69+
}
4270
}

src/Service/Indexing/IndexingEvents.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ public function serialize(IndexItemInterface $item): array
3232
throw new \InvalidArgumentException('Item must be an instance of Event.');
3333
}
3434

35+
$updatedAt = $this->getUpdatedAt($item);
36+
$item->setUpdatedAt($updatedAt);
37+
3538
$contextBuilder = (new ObjectNormalizerContextBuilder())
3639
->withGroups([IndexNames::Events->value]);
3740
$contextBuilder = (new DateTimeNormalizerContextBuilder())
@@ -61,4 +64,23 @@ public function serialize(IndexItemInterface $item): array
6164

6265
return $data;
6366
}
67+
68+
private function getUpdatedAt(Event $event): \DateTime
69+
{
70+
$updatedAt = $event->getUpdatedAt();
71+
72+
$updatedAt = max($updatedAt, $event->getOrganization()?->getUpdatedAt());
73+
$updatedAt = max($updatedAt, $event->getLocation()?->getUpdatedAt());
74+
$updatedAt = max($updatedAt, $event->getImage()?->getUpdatedAt());
75+
76+
foreach ($event->getPartners() as $partner) {
77+
$updatedAt = max($updatedAt, $partner->getUpdatedAt());
78+
}
79+
80+
foreach ($event->getOccurrences() as $occurrence) {
81+
$updatedAt = max($updatedAt, $occurrence->getUpdatedAt());
82+
}
83+
84+
return $updatedAt;
85+
}
6486
}

src/Service/Indexing/IndexingOccurrences.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Service\Indexing;
44

5+
use App\Entity\Occurrence;
56
use App\Model\Indexing\IndexFieldTypes;
67
use App\Model\Indexing\IndexNames;
78
use Elastic\Elasticsearch\Client;
@@ -25,6 +26,13 @@ public function __construct(
2526

2627
public function serialize(IndexItemInterface $item): array
2728
{
29+
if (!$item instanceof Occurrence) {
30+
throw new \InvalidArgumentException('Item must be an instance of Occurrence.');
31+
}
32+
33+
$updatedAt = $this->getUpdatedAt($item);
34+
$item->setUpdatedAt($updatedAt);
35+
2836
$contextBuilder = (new ObjectNormalizerContextBuilder())
2937
->withGroups([IndexNames::Occurrences->value]);
3038
$contextBuilder = (new DateTimeNormalizerContextBuilder())
@@ -39,4 +47,24 @@ public function serialize(IndexItemInterface $item): array
3947

4048
return $data;
4149
}
50+
51+
private function getUpdatedAt(Occurrence $occurrence): \DateTime
52+
{
53+
$updatedAt = $occurrence->getUpdatedAt();
54+
$event = $occurrence->getEvent();
55+
56+
$updatedAt = max($updatedAt, $event->getOrganization()?->getUpdatedAt());
57+
$updatedAt = max($updatedAt, $event->getLocation()?->getUpdatedAt());
58+
$updatedAt = max($updatedAt, $event->getImage()?->getUpdatedAt());
59+
60+
foreach ($event->getPartners() as $partner) {
61+
$updatedAt = max($updatedAt, $partner->getUpdatedAt());
62+
}
63+
64+
foreach ($event->getOccurrences() as $occurrence) {
65+
$updatedAt = max($updatedAt, $occurrence->getUpdatedAt());
66+
}
67+
68+
return $updatedAt;
69+
}
4270
}

0 commit comments

Comments
 (0)