1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Translation;
13
14use Symfony\Component\Config\Resource\ResourceInterface;
15use Symfony\Component\Translation\Exception\LogicException;
16
17/**
18 * @author Fabien Potencier <fabien@symfony.com>
19 */
20class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface
21{
22    private $messages = [];
23    private $metadata = [];
24    private $resources = [];
25    private $locale;
26    private $fallbackCatalogue;
27    private $parent;
28
29    /**
30     * @param array $messages An array of messages classified by domain
31     */
32    public function __construct(?string $locale, array $messages = [])
33    {
34        if (null === $locale) {
35            @trigger_error(sprintf('Passing "null" to the first argument of the "%s" method has been deprecated since Symfony 4.4 and will throw an error in 5.0.', __METHOD__), \E_USER_DEPRECATED);
36        }
37
38        $this->locale = $locale;
39        $this->messages = $messages;
40    }
41
42    /**
43     * {@inheritdoc}
44     */
45    public function getLocale()
46    {
47        return $this->locale;
48    }
49
50    /**
51     * {@inheritdoc}
52     */
53    public function getDomains()
54    {
55        $domains = [];
56
57        foreach ($this->messages as $domain => $messages) {
58            if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) {
59                $domain = substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX));
60            }
61            $domains[$domain] = $domain;
62        }
63
64        return array_values($domains);
65    }
66
67    /**
68     * {@inheritdoc}
69     */
70    public function all($domain = null)
71    {
72        if (null !== $domain) {
73            // skip messages merge if intl-icu requested explicitly
74            if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) {
75                return $this->messages[$domain] ?? [];
76            }
77
78            return ($this->messages[$domain.self::INTL_DOMAIN_SUFFIX] ?? []) + ($this->messages[$domain] ?? []);
79        }
80
81        $allMessages = [];
82
83        foreach ($this->messages as $domain => $messages) {
84            if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) {
85                $domain = substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX));
86                $allMessages[$domain] = $messages + ($allMessages[$domain] ?? []);
87            } else {
88                $allMessages[$domain] = ($allMessages[$domain] ?? []) + $messages;
89            }
90        }
91
92        return $allMessages;
93    }
94
95    /**
96     * {@inheritdoc}
97     */
98    public function set($id, $translation, $domain = 'messages')
99    {
100        $this->add([$id => $translation], $domain);
101    }
102
103    /**
104     * {@inheritdoc}
105     */
106    public function has($id, $domain = 'messages')
107    {
108        if (isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) {
109            return true;
110        }
111
112        if (null !== $this->fallbackCatalogue) {
113            return $this->fallbackCatalogue->has($id, $domain);
114        }
115
116        return false;
117    }
118
119    /**
120     * {@inheritdoc}
121     */
122    public function defines($id, $domain = 'messages')
123    {
124        return isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]);
125    }
126
127    /**
128     * {@inheritdoc}
129     */
130    public function get($id, $domain = 'messages')
131    {
132        if (isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) {
133            return $this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id];
134        }
135
136        if (isset($this->messages[$domain][$id])) {
137            return $this->messages[$domain][$id];
138        }
139
140        if (null !== $this->fallbackCatalogue) {
141            return $this->fallbackCatalogue->get($id, $domain);
142        }
143
144        return $id;
145    }
146
147    /**
148     * {@inheritdoc}
149     */
150    public function replace($messages, $domain = 'messages')
151    {
152        unset($this->messages[$domain], $this->messages[$domain.self::INTL_DOMAIN_SUFFIX]);
153
154        $this->add($messages, $domain);
155    }
156
157    /**
158     * {@inheritdoc}
159     */
160    public function add($messages, $domain = 'messages')
161    {
162        if (!isset($this->messages[$domain])) {
163            $this->messages[$domain] = [];
164        }
165        $intlDomain = $domain;
166        if (!str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) {
167            $intlDomain .= self::INTL_DOMAIN_SUFFIX;
168        }
169        foreach ($messages as $id => $message) {
170            if (isset($this->messages[$intlDomain]) && \array_key_exists($id, $this->messages[$intlDomain])) {
171                $this->messages[$intlDomain][$id] = $message;
172            } else {
173                $this->messages[$domain][$id] = $message;
174            }
175        }
176    }
177
178    /**
179     * {@inheritdoc}
180     */
181    public function addCatalogue(MessageCatalogueInterface $catalogue)
182    {
183        if ($catalogue->getLocale() !== $this->locale) {
184            throw new LogicException(sprintf('Cannot add a catalogue for locale "%s" as the current locale for this catalogue is "%s".', $catalogue->getLocale(), $this->locale));
185        }
186
187        foreach ($catalogue->all() as $domain => $messages) {
188            if ($intlMessages = $catalogue->all($domain.self::INTL_DOMAIN_SUFFIX)) {
189                $this->add($intlMessages, $domain.self::INTL_DOMAIN_SUFFIX);
190                $messages = array_diff_key($messages, $intlMessages);
191            }
192            $this->add($messages, $domain);
193        }
194
195        foreach ($catalogue->getResources() as $resource) {
196            $this->addResource($resource);
197        }
198
199        if ($catalogue instanceof MetadataAwareInterface) {
200            $metadata = $catalogue->getMetadata('', '');
201            $this->addMetadata($metadata);
202        }
203    }
204
205    /**
206     * {@inheritdoc}
207     */
208    public function addFallbackCatalogue(MessageCatalogueInterface $catalogue)
209    {
210        // detect circular references
211        $c = $catalogue;
212        while ($c = $c->getFallbackCatalogue()) {
213            if ($c->getLocale() === $this->getLocale()) {
214                throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale()));
215            }
216        }
217
218        $c = $this;
219        do {
220            if ($c->getLocale() === $catalogue->getLocale()) {
221                throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale()));
222            }
223
224            foreach ($catalogue->getResources() as $resource) {
225                $c->addResource($resource);
226            }
227        } while ($c = $c->parent);
228
229        $catalogue->parent = $this;
230        $this->fallbackCatalogue = $catalogue;
231
232        foreach ($catalogue->getResources() as $resource) {
233            $this->addResource($resource);
234        }
235    }
236
237    /**
238     * {@inheritdoc}
239     */
240    public function getFallbackCatalogue()
241    {
242        return $this->fallbackCatalogue;
243    }
244
245    /**
246     * {@inheritdoc}
247     */
248    public function getResources()
249    {
250        return array_values($this->resources);
251    }
252
253    /**
254     * {@inheritdoc}
255     */
256    public function addResource(ResourceInterface $resource)
257    {
258        $this->resources[$resource->__toString()] = $resource;
259    }
260
261    /**
262     * {@inheritdoc}
263     */
264    public function getMetadata($key = '', $domain = 'messages')
265    {
266        if ('' == $domain) {
267            return $this->metadata;
268        }
269
270        if (isset($this->metadata[$domain])) {
271            if ('' == $key) {
272                return $this->metadata[$domain];
273            }
274
275            if (isset($this->metadata[$domain][$key])) {
276                return $this->metadata[$domain][$key];
277            }
278        }
279
280        return null;
281    }
282
283    /**
284     * {@inheritdoc}
285     */
286    public function setMetadata($key, $value, $domain = 'messages')
287    {
288        $this->metadata[$domain][$key] = $value;
289    }
290
291    /**
292     * {@inheritdoc}
293     */
294    public function deleteMetadata($key = '', $domain = 'messages')
295    {
296        if ('' == $domain) {
297            $this->metadata = [];
298        } elseif ('' == $key) {
299            unset($this->metadata[$domain]);
300        } else {
301            unset($this->metadata[$domain][$key]);
302        }
303    }
304
305    /**
306     * Adds current values with the new values.
307     *
308     * @param array $values Values to add
309     */
310    private function addMetadata(array $values)
311    {
312        foreach ($values as $domain => $keys) {
313            foreach ($keys as $key => $value) {
314                $this->setMetadata($key, $value, $domain);
315            }
316        }
317    }
318}
319