Issues (10)

src/Entity/Article.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ProjetNormandie\ArticleBundle\Entity;
6
7
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
8
use ApiPlatform\Metadata\ApiFilter;
9
use ApiPlatform\Metadata\ApiResource;
10
use ApiPlatform\Metadata\GetCollection;
11
use ApiPlatform\Metadata\Get;
12
use Doctrine\Common\Collections\ArrayCollection;
13
use Doctrine\Common\Collections\Collection;
14
use Doctrine\ORM\Mapping as ORM;
15
use Gedmo\Timestampable\Traits\TimestampableEntity;
16
use ProjetNormandie\ArticleBundle\Filter\TranslationSearchFilter;
17
use ProjetNormandie\ArticleBundle\Repository\ArticleRepository;
18
use ProjetNormandie\ArticleBundle\Enum\ArticleStatus;
19
use Symfony\Component\Serializer\Attribute\Groups;
20
use Symfony\Component\Validator\Constraints as Assert;
21
use DateTime;
22
23
#[ORM\Table(name:'pna_article')]
24
#[ORM\Entity(repositoryClass: ArticleRepository::class)]
25
#[ORM\EntityListeners(["ProjetNormandie\ArticleBundle\EventListener\Entity\ArticleListener"])]
26
#[ApiResource(
27
    operations: [
28
        new GetCollection(),
29
        new Get(),
30
    ],
31
    normalizationContext: ['groups' => ['article:read', 'article:author', 'user:read']],
32
    order: ['publishedAt' => 'DESC']
33
)]
34
#[ApiFilter(
35
    SearchFilter::class,
36
    properties: [
37
        'status' => 'exact',
38
    ]
39
)]
40
#[ApiFilter(TranslationSearchFilter::class)]
41
class Article
42
{
43
    use TimestampableEntity;
44
45
    // FALLBACK language
46
    private const string DEFAULT_LOCALE = 'en';
0 ignored issues
show
A parse error occurred: Syntax error, unexpected T_STRING, expecting '=' on line 46 at column 25
Loading history...
47
48
    #[Groups(['article:read'])]
49
    #[ORM\Id, ORM\Column, ORM\GeneratedValue]
50
    private ?int $id = null;
51
52
    #[ORM\Column(type: 'string', enumType: ArticleStatus::class)]
53
    private ArticleStatus $status = ArticleStatus::UNDER_CONSTRUCTION;
54
55
    #[Groups(['article:read'])]
56
    #[ORM\Column(nullable: false, options: ['default' => 0])]
57
    private int $nbComment = 0;
58
59
    #[Groups(['article:author'])]
60
    #[ORM\ManyToOne(targetEntity: UserInterface::class, fetch: 'EAGER')]
61
    #[ORM\JoinColumn(name:'author_id', referencedColumnName:'id', nullable:false)]
62
    private $author;
63
64
    #[Groups(['article:read'])]
65
    #[ORM\Column(nullable: true)]
66
    private ?DateTime $publishedAt = null;
67
68
    /**
69
     * @var Collection<Comment>
70
     */
71
    #[ORM\OneToMany(mappedBy: 'article', targetEntity: Comment::class)]
72
    private Collection $comments;
73
74
    /** @var Collection<ArticleTranslation> */
75
    #[ORM\OneToMany(
76
        mappedBy: 'translatable',
77
        targetEntity: ArticleTranslation::class,
78
        cascade: ['persist', 'remove'],
79
        orphanRemoval: true,
80
        indexBy: 'locale'
81
    )]
82
    private Collection $translations;
83
84
    #[Groups(['article:read'])]
85
    #[ORM\Column(length: 255, unique: false)]
86
    private string $slug;
87
88
    private ?string $currentLocale = null;
89
90
    public function __construct()
91
    {
92
        $this->comments = new ArrayCollection();
93
        $this->translations = new ArrayCollection();
94
    }
95
96
    public function __toString()
97
    {
98
        return sprintf('%s [%s]', $this->getDefaultTitle(), $this->id);
99
    }
100
101
    public function getDefaultTitle(): string
102
    {
103
        return $this->getTitle(self::DEFAULT_LOCALE) ?: 'Untitled';
104
    }
105
106
    public function getDefaultContent(): string
107
    {
108
        return $this->getContent(self::DEFAULT_LOCALE) ?: '';
109
    }
110
111
    public function setId(int $id): void
112
    {
113
        $this->id = $id;
114
    }
115
116
    public function getId(): ?int
117
    {
118
        return $this->id;
119
    }
120
121
    public function setStatus(ArticleStatus $status): void
122
    {
123
        $this->status = $status;
124
    }
125
126
    public function getStatus(): ArticleStatus
127
    {
128
        return $this->status;
129
    }
130
131
    public function getArticleStatus(): ArticleStatus
132
    {
133
        return $this->status;
134
    }
135
136
    public function setNbComment(int $nbComment): void
137
    {
138
        $this->nbComment = $nbComment;
139
    }
140
141
    public function getNbComment(): int
142
    {
143
        return $this->nbComment;
144
    }
145
146
    public function getAuthor()
147
    {
148
        return $this->author;
149
    }
150
151
    public function setAuthor($author): void
152
    {
153
        $this->author = $author;
154
    }
155
156
    public function getPublishedAt(): ?DateTime
157
    {
158
        return $this->publishedAt;
159
    }
160
161
    public function setPublishedAt(?DateTime $publishedAt = null): void
162
    {
163
        $this->publishedAt = $publishedAt;
164
    }
165
166
    public function setComments(Collection $comments): void
167
    {
168
        $this->comments = $comments;
169
    }
170
171
    public function getComments(): Collection
172
    {
173
        return $this->comments;
174
    }
175
176
    public function getSlug(): ?string
177
    {
178
        return $this->slug;
179
    }
180
181
    public function setSlug(string $slug): void
182
    {
183
        $this->slug = $slug;
184
    }
185
186
    // Translation methods for A2lix compatibility
187
    public function getTranslations(): Collection
188
    {
189
        return $this->translations;
190
    }
191
192
    public function setTranslations(Collection $translations): void
193
    {
194
        $this->translations = $translations;
195
    }
196
197
    public function addTranslation(ArticleTranslation $translation): void
198
    {
199
        if (!$this->translations->contains($translation)) {
200
            $translation->setTranslatable($this);
201
            $this->translations->set($translation->getLocale(), $translation);
202
        }
203
    }
204
205
    public function removeTranslation(ArticleTranslation $translation): void
206
    {
207
        $this->translations->removeElement($translation);
208
    }
209
210
    /**
211
     * Retrieves a translation with intelligent fallback logic.
212
     * Ensures content quality by checking for non-empty translations.
213
     */
214
    public function translate(?string $locale = null, bool $fallbackToDefault = true): ?ArticleTranslation
215
    {
216
        $locale = $locale ?: $this->currentLocale ?: self::DEFAULT_LOCALE;
217
218
        // If translation exists for requested locale
219
        if ($this->translations->containsKey($locale)) {
220
            $translation = $this->translations->get($locale);
221
            // Check that translation is not empty
222
            if (!empty($translation->getTitle()) || !empty($translation->getContent())) {
223
                return $translation;
224
            }
225
        }
226
227
        // Fallback to default locale if enabled and different from requested locale
228
        if ($fallbackToDefault && $locale !== self::DEFAULT_LOCALE && $this->translations->containsKey(self::DEFAULT_LOCALE)) {
229
            $translation = $this->translations->get(self::DEFAULT_LOCALE);
230
            if (!empty($translation->getTitle()) || !empty($translation->getContent())) {
231
                return $translation;
232
            }
233
        }
234
235
        // If no valid translation exists, return first non-empty available
236
        foreach ($this->translations as $translation) {
237
            if (!empty($translation->getTitle()) || !empty($translation->getContent())) {
238
                return $translation;
239
            }
240
        }
241
242
        // Last resort: return first translation even if empty
243
        return $this->translations->first() ?: null;
244
    }
245
246
    /**
247
     * Vérifie si une traduction existe pour une locale donnée
248
     */
249
    public function hasTranslation(string $locale): bool
250
    {
251
        return $this->translations->containsKey($locale);
252
    }
253
254
    /**
255
     * Retourne toutes les locales disponibles pour cet article
256
     */
257
    public function getAvailableLocales(): array
258
    {
259
        return $this->translations->getKeys();
260
    }
261
262
    public function setCurrentLocale(string $locale): void
263
    {
264
        $this->currentLocale = $locale;
265
    }
266
267
    public function getCurrentLocale(): ?string
268
    {
269
        return $this->currentLocale;
270
    }
271
272
    public function setTitle(string $title, ?string $locale = null): void
273
    {
274
        $locale = $locale ?: $this->currentLocale ?: self::DEFAULT_LOCALE;
275
276
        if (!$this->translations->containsKey($locale)) {
277
            $translation = new ArticleTranslation();
278
            $translation->setTranslatable($this);
279
            $translation->setLocale($locale);
280
            $this->translations->set($locale, $translation);
281
        }
282
283
        $this->translations->get($locale)->setTitle($title);
284
    }
285
286
    #[Groups(['article:read'])]
287
    public function getTitle(?string $locale = null): ?string
288
    {
289
        $translation = $this->translate($locale);
290
        return $translation?->getTitle();
291
    }
292
293
    public function setContent(string $content, ?string $locale = null): void
294
    {
295
        $locale = $locale ?: $this->currentLocale ?: self::DEFAULT_LOCALE;
296
297
        if (!$this->translations->containsKey($locale)) {
298
            $translation = new ArticleTranslation();
299
            $translation->setTranslatable($this);
300
            $translation->setLocale($locale);
301
            $this->translations->set($locale, $translation);
302
        }
303
304
        $this->translations->get($locale)->setContent($content);
305
    }
306
307
    #[Groups(['article:read'])]
308
    public function getContent(?string $locale = null): ?string
309
    {
310
        $translation = $this->translate($locale);
311
        return $translation?->getContent();
312
    }
313
314
    // Old methods for backward compatibility
315
    public function mergeNewTranslations(): void
316
    {
317
        // Not needed anymore
318
    }
319
}
320