Completed
Push — master ( da6bb4...b1a77c )
by Tobias
09:49 queued 02:41
created

Importer::convertSourceLocationsToMessages()   B

Complexity

Conditions 6
Paths 11

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6.1979

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 26
ccs 14
cts 17
cp 0.8235
rs 8.439
cc 6
eloc 16
nc 11
nop 2
crap 6.1979
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\Model\ImportResult;
17
use Translation\Bundle\Model\Metadata;
18
use Translation\Bundle\Twig\Visitor\DefaultApplyingNodeVisitor;
19
use Translation\Bundle\Twig\Visitor\RemovingNodeVisitor;
20
use Translation\Extractor\Extractor;
21
use Translation\Extractor\Model\SourceCollection;
22
use Translation\Extractor\Model\SourceLocation;
23
use Translation\Bundle\Catalogue\Operation\ReplaceOperation;
24
25
/**
26
 * Use extractors to import translations to message catalogues.
27
 *
28
 * @author Tobias Nyholm <[email protected]>
29
 */
30
final class Importer
31
{
32
    /**
33
     * @var Extractor
34
     */
35
    private $extractor;
36
37
    /**
38
     * @var array
39
     */
40
    private $config;
41
42
    /**
43
     * @var \Twig_Environment
44
     */
45
    private $twig;
46
47
    /**
48
     * @param Extractor         $extractor
49
     * @param \Twig_Environment $twig
50
     */
51 1
    public function __construct(Extractor $extractor, \Twig_Environment $twig)
52
    {
53 1
        $this->extractor = $extractor;
54 1
        $this->twig = $twig;
55 1
    }
56
57
    /**
58
     * @param Finder             $finder
59
     * @param MessageCatalogue[] $catalogues
60
     * @param array              $config     {
61
     *
62
     *     @var array $blacklist_domains Blacklist the domains we should exclude. Cannot be used with whitelist.
63
     *     @var array $whitelist_domains Whitelist the domains we should include. Cannot be used with blacklist.
64
     *     @var string $project_root The project root will be removed from the source location.
65
     * }
66
     *
67
     * @return ImportResult
68
     */
69 1
    public function extractToCatalogues(Finder $finder, array $catalogues, array $config = [])
70
    {
71 1
        $this->processConfig($config);
72 1
        $this->disableTwigVisitors();
73 1
        $sourceCollection = $this->extractor->extract($finder);
74 1
        $results = [];
75 1
        foreach ($catalogues as $catalogue) {
76 1
            $target = new MessageCatalogue($catalogue->getLocale());
77 1
            $this->convertSourceLocationsToMessages($target, $sourceCollection);
78
79
            // Remove all SourceLocation and State form catalogue.
80 1
            foreach ($catalogue->getDomains() as $domain) {
81 1
                foreach ($catalogue->all($domain) as $key => $translation) {
82 1
                    $meta = $this->getMetadata($catalogue, $key, $domain);
83 1
                    $meta->removeAllInCategory('file-source');
84 1
                    $meta->removeAllInCategory('state');
85 1
                    $this->setMetadata($catalogue, $key, $domain, $meta);
86
                }
87
            }
88
89 1
            $merge = new ReplaceOperation($target, $catalogue);
90 1
            $result = $merge->getResult();
91 1
            $domains = $merge->getDomains();
92
93
            // Mark new messages as new/obsolete
94 1
            foreach ($domains as $domain) {
95 1
                foreach ($merge->getNewMessages($domain) as $key => $translation) {
96 1
                    $meta = $this->getMetadata($result, $key, $domain);
0 ignored issues
show
Compatibility introduced by
$result of type object<Symfony\Component...sageCatalogueInterface> is not a sub-type of object<Symfony\Component...ation\MessageCatalogue>. It seems like you assume a concrete implementation of the interface Symfony\Component\Transl...ssageCatalogueInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
97 1
                    $meta->setState('new');
98 1
                    $this->setMetadata($result, $key, $domain, $meta);
0 ignored issues
show
Compatibility introduced by
$result of type object<Symfony\Component...sageCatalogueInterface> is not a sub-type of object<Symfony\Component...ation\MessageCatalogue>. It seems like you assume a concrete implementation of the interface Symfony\Component\Transl...ssageCatalogueInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
99
100
                    // Add custom translations that we found in the source
101 1
                    if (null === $translation) {
102 1
                        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 1
                        } elseif (null !== $newTranslation = $meta->getDesc()) {
107 1
                            $result->set($key, $newTranslation, $domain);
108
                        }
109
                    }
110
                }
111 1
                foreach ($merge->getObsoleteMessages($domain) as $key => $translation) {
112 1
                    $meta = $this->getMetadata($result, $key, $domain);
0 ignored issues
show
Compatibility introduced by
$result of type object<Symfony\Component...sageCatalogueInterface> is not a sub-type of object<Symfony\Component...ation\MessageCatalogue>. It seems like you assume a concrete implementation of the interface Symfony\Component\Transl...ssageCatalogueInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
113 1
                    $meta->setState('obsolete');
114 1
                    $this->setMetadata($result, $key, $domain, $meta);
0 ignored issues
show
Compatibility introduced by
$result of type object<Symfony\Component...sageCatalogueInterface> is not a sub-type of object<Symfony\Component...ation\MessageCatalogue>. It seems like you assume a concrete implementation of the interface Symfony\Component\Transl...ssageCatalogueInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
115
                }
116
            }
117 1
            $results[] = $result;
118
        }
119
120 1
        return new ImportResult($results, $sourceCollection->getErrors());
121
    }
122
123
    /**
124
     * @param MessageCatalogue $catalogue
125
     * @param SourceCollection $collection
126
     */
127 1
    private function convertSourceLocationsToMessages(MessageCatalogue $catalogue, SourceCollection $collection)
128
    {
129
        /** @var SourceLocation $sourceLocation */
130 1
        foreach ($collection as $sourceLocation) {
131 1
            $context = $sourceLocation->getContext();
132 1
            $domain = isset($context['domain']) ? $context['domain'] : 'messages';
133
            // Check with white/black list
134 1
            if (!$this->isValidDomain($domain)) {
135
                continue;
136
            }
137
138 1
            $key = $sourceLocation->getMessage();
139 1
            $catalogue->set($key, null, $domain);
140 1
            $trimLength = 1 + strlen($this->config['project_root']);
141
142 1
            $meta = $this->getMetadata($catalogue, $key, $domain);
143 1
            $meta->addCategory('file-source', sprintf('%s:%s', substr($sourceLocation->getPath(), $trimLength), $sourceLocation->getLine()));
144 1
            if (isset($sourceLocation->getContext()['desc'])) {
145
                $meta->addCategory('desc', $sourceLocation->getContext()['desc']);
146
            }
147 1
            if (isset($sourceLocation->getContext()['translation'])) {
148
                $meta->addCategory('translation', $sourceLocation->getContext()['translation']);
149
            }
150 1
            $this->setMetadata($catalogue, $key, $domain, $meta);
151
        }
152 1
    }
153
154
    /**
155
     * @param MessageCatalogue $catalogue
156
     * @param $key
157
     * @param $domain
158
     *
159
     * @return Metadata
160
     */
161 1
    private function getMetadata(MessageCatalogue $catalogue, $key, $domain)
162
    {
163 1
        return new Metadata($catalogue->getMetadata($key, $domain));
164
    }
165
166
    /**
167
     * @param MessageCatalogue $catalogue
168
     * @param $key
169
     * @param $domain
170
     * @param Metadata $metadata
171
     */
172 1
    private function setMetadata(MessageCatalogue $catalogue, $key, $domain, Metadata $metadata)
173
    {
174 1
        $catalogue->setMetadata($key, $metadata->toArray(), $domain);
175 1
    }
176
177
    /**
178
     * @param string $domain
179
     *
180
     * @return bool
181
     */
182 1
    private function isValidDomain($domain)
183
    {
184 1 View Code Duplication
        if (!empty($this->config['blacklist_domains']) && in_array($domain, $this->config['blacklist_domains'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
185
            return false;
186
        }
187 1 View Code Duplication
        if (!empty($this->config['whitelist_domains']) && !in_array($domain, $this->config['whitelist_domains'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
188
            return false;
189
        }
190
191 1
        return true;
192
    }
193
194
    /**
195
     * Make sure the configuration is valid.
196
     *
197
     * @param array $config
198
     */
199 1
    private function processConfig($config)
200
    {
201
        $default = [
202 1
            'project_root' => '',
203
            'blacklist_domains' => [],
204
            'whitelist_domains' => [],
205
        ];
206
207 1
        $config = array_merge($default, $config);
208
209 1 View Code Duplication
        if (!empty($config['blacklist_domains']) && !empty($config['whitelist_domains'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
210
            throw new \InvalidArgumentException('Cannot use "blacklist_domains" and "whitelist_domains" at the same time');
211
        }
212
213 1 View Code Duplication
        if (!empty($config['blacklist_domains']) && !is_array($config['blacklist_domains'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
214
            throw new \InvalidArgumentException('Config parameter "blacklist_domains" must be an array');
215
        }
216
217 1 View Code Duplication
        if (!empty($config['whitelist_domains']) && !is_array($config['whitelist_domains'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
218
            throw new \InvalidArgumentException('Config parameter "whitelist_domains" must be an array');
219
        }
220
221 1
        $this->config = $config;
222 1
    }
223
224 1
    private function disableTwigVisitors()
225
    {
226 1
        foreach ($this->twig->getNodeVisitors() as $visitor) {
227 1
            if ($visitor instanceof DefaultApplyingNodeVisitor) {
228 1
                $visitor->setEnabled(false);
229
            }
230 1
            if ($visitor instanceof RemovingNodeVisitor) {
231 1
                $visitor->setEnabled(false);
232
            }
233
        }
234 1
    }
235
}
236