Importer::processConfig()   B
last analyzed

Complexity

Conditions 7
Paths 4

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 12
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 23
ccs 0
cts 18
cp 0
crap 56
rs 8.8333
1
<?php
2
3
/*
4
 * This file is part of the PHP Translation package.
5
 *
6
 * (c) PHP Translation team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Translation\Bundle\Service;
13
14
use Symfony\Component\Finder\Finder;
15
use Symfony\Component\Translation\MessageCatalogue;
16
use Translation\Bundle\Catalogue\Operation\ReplaceOperation;
17
use Translation\Bundle\Model\ImportResult;
18
use Translation\Bundle\Model\Metadata;
19
use Translation\Bundle\Twig\Visitor\DefaultApplyingNodeVisitor;
20
use Translation\Bundle\Twig\Visitor\RemovingNodeVisitor;
21
use Translation\Extractor\Extractor;
22
use Translation\Extractor\Model\SourceCollection;
23
use Translation\Extractor\Model\SourceLocation;
24
use Twig\Environment;
25
26
/**
27
 * Use extractors to import translations to message catalogues.
28
 *
29
 * @author Tobias Nyholm <[email protected]>
30
 */
31
final class Importer
32
{
33
    /**
34
     * @var Extractor
35
     */
36
    private $extractor;
37
38
    /**
39
     * @var array
40
     */
41
    private $config;
42
43
    /**
44
     * @var Environment
45
     */
46
    private $twig;
47
48
    /**
49
     * @var string
50
     */
51
    private $defaultLocale;
52
53
    public function __construct(Extractor $extractor, Environment $twig, string $defaultLocale)
54
    {
55
        $this->extractor = $extractor;
56
        $this->twig = $twig;
57
        $this->defaultLocale = $defaultLocale;
58
    }
59
60
    /**
61
     * @param MessageCatalogue[] $catalogues
62
     * @param array              $config     {
63
     *
64
     *     @var array $blacklist_domains Blacklist the domains we should exclude. Cannot be used with whitelist.
65
     *     @var array $whitelist_domains Whitelist the domains we should include. Cannot be used with blacklist.
66
     *     @var string $project_root The project root will be removed from the source location.
67
     * }
68
     */
69
    public function extractToCatalogues(Finder $finder, array $catalogues, array $config = []): ImportResult
70
    {
71
        $this->processConfig($config);
72
        $this->disableTwigVisitors();
73
        $sourceCollection = $this->extractor->extract($finder);
74
        $results = [];
75
        foreach ($catalogues as $catalogue) {
76
            $target = new MessageCatalogue($catalogue->getLocale());
77
            $this->convertSourceLocationsToMessages($target, $sourceCollection);
78
79
            // Remove all SourceLocation and State form catalogue.
80
            foreach ($catalogue->getDomains() as $domain) {
81
                foreach ($catalogue->all($domain) as $key => $translation) {
82
                    $meta = $this->getMetadata($catalogue, $key, $domain);
83
                    $meta->removeAllInCategory('file-source');
84
                    $meta->removeAllInCategory('state');
85
                    $this->setMetadata($catalogue, $key, $domain, $meta);
86
                }
87
            }
88
89
            $merge = new ReplaceOperation($target, $catalogue);
90
            $result = $merge->getResult();
91
            $domains = $merge->getDomains();
92
93
            // Mark new messages as new/obsolete
94
            foreach ($domains as $domain) {
95
                foreach ($merge->getNewMessages($domain) as $key => $translation) {
96
                    $meta = $this->getMetadata($result, $key, $domain);
97
                    $meta->setState('new');
98
                    $this->setMetadata($result, $key, $domain, $meta);
99
100
                    // Add custom translations that we found in the source
101
                    if (null === $translation) {
102
                        if (null !== $newTranslation = $meta->getTranslation()) {
103
                            $result->set($key, $newTranslation, $domain);
104
                            // We do not want "translation" key stored anywhere.
105
                            $meta->removeAllInCategory('translation');
106
                        } elseif (null !== ($newTranslation = $meta->getDesc()) && $catalogue->getLocale() === $this->defaultLocale) {
107
                            $result->set($key, $newTranslation, $domain);
108
                        }
109
                    }
110
                }
111
                foreach ($merge->getObsoleteMessages($domain) as $key => $translation) {
112
                    $meta = $this->getMetadata($result, $key, $domain);
113
                    $meta->setState('obsolete');
114
                    $this->setMetadata($result, $key, $domain, $meta);
115
                }
116
            }
117
            $results[] = $result;
118
        }
119
120
        return new ImportResult($results, $sourceCollection->getErrors());
121
    }
122
123
    private function convertSourceLocationsToMessages(MessageCatalogue $catalogue, SourceCollection $collection): void
124
    {
125
        /** @var SourceLocation $sourceLocation */
126
        foreach ($collection as $sourceLocation) {
127
            $context = $sourceLocation->getContext();
128
            $domain = $context['domain'] ?? 'messages';
129
            // Check with white/black list
130
            if (!$this->isValidDomain($domain)) {
131
                continue;
132
            }
133
134
            $key = $sourceLocation->getMessage();
135
            $catalogue->add([$key => null], $domain);
136
            $trimLength = 1 + \strlen($this->config['project_root']);
137
138
            $meta = $this->getMetadata($catalogue, $key, $domain);
139
            $meta->addCategory('file-source', \sprintf('%s:%s', \substr($sourceLocation->getPath(), $trimLength), $sourceLocation->getLine()));
140
            if (isset($sourceLocation->getContext()['desc'])) {
141
                $meta->addCategory('desc', $sourceLocation->getContext()['desc']);
142
            }
143
            if (isset($sourceLocation->getContext()['translation'])) {
144
                $meta->addCategory('translation', $sourceLocation->getContext()['translation']);
145
            }
146
            $this->setMetadata($catalogue, $key, $domain, $meta);
147
        }
148
    }
149
150
    private function getMetadata(MessageCatalogue $catalogue, string $key, string $domain): Metadata
151
    {
152
        return new Metadata($catalogue->getMetadata($key, $domain));
153
    }
154
155
    private function setMetadata(MessageCatalogue $catalogue, string $key, string $domain, Metadata $metadata): void
156
    {
157
        $catalogue->setMetadata($key, $metadata->toArray(), $domain);
158
    }
159
160
    private function isValidDomain(string $domain): bool
161
    {
162
        if (!empty($this->config['blacklist_domains']) && \in_array($domain, $this->config['blacklist_domains'], true)) {
163
            return false;
164
        }
165
        if (!empty($this->config['whitelist_domains']) && !\in_array($domain, $this->config['whitelist_domains'], true)) {
166
            return false;
167
        }
168
169
        return true;
170
    }
171
172
    /**
173
     * Make sure the configuration is valid.
174
     */
175
    private function processConfig(array $config): void
176
    {
177
        $default = [
178
            'project_root' => '',
179
            'blacklist_domains' => [],
180
            'whitelist_domains' => [],
181
        ];
182
183
        $config = \array_merge($default, $config);
184
185
        if (!empty($config['blacklist_domains']) && !empty($config['whitelist_domains'])) {
186
            throw new \InvalidArgumentException('Cannot use "blacklist_domains" and "whitelist_domains" at the same time');
187
        }
188
189
        if (!empty($config['blacklist_domains']) && !\is_array($config['blacklist_domains'])) {
190
            throw new \InvalidArgumentException('Config parameter "blacklist_domains" must be an array');
191
        }
192
193
        if (!empty($config['whitelist_domains']) && !\is_array($config['whitelist_domains'])) {
194
            throw new \InvalidArgumentException('Config parameter "whitelist_domains" must be an array');
195
        }
196
197
        $this->config = $config;
198
    }
199
200
    private function disableTwigVisitors(): void
201
    {
202
        foreach ($this->twig->getNodeVisitors() as $visitor) {
203
            if ($visitor instanceof DefaultApplyingNodeVisitor) {
204
                $visitor->setEnabled(false);
205
            }
206
            if ($visitor instanceof RemovingNodeVisitor) {
207
                $visitor->setEnabled(false);
208
            }
209
        }
210
    }
211
}
212