Completed
Push — master ( 006012...05b9c2 )
by Tobias
05:02
created

XliffLoader::load()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4.0582

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 11
cts 13
cp 0.8462
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 4
nop 3
crap 4.0582
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\SymfonyStorage\Loader;
13
14
use Nyholm\NSA;
15
use Symfony\Component\Config\Resource\FileResource;
16
use Symfony\Component\Translation\Exception\NotFoundResourceException;
17
use Symfony\Component\Translation\Loader\XliffFileLoader;
18
use Symfony\Component\Translation\MessageCatalogue;
19
use Symfony\Component\Translation\Exception\InvalidResourceException;
20
use Translation\SymfonyStorage\Loader\Port\SymfonyPort;
21
22
/**
23
 * This class is an ugly hack to allow loading Xliff from string content.
24
 *
25
 * @author Tobias Nyholm <[email protected]>
26
 */
27
class XliffLoader extends XliffFileLoader
28
{
29
    /**
30
     * @var SymfonyPort|null
31
     */
32
    private $sfPort;
33
34
    /**
35
     * {@inheritdoc}
36
     */
37 8
    public function load($resource, $locale, $domain = 'messages')
38
    {
39 6
        if (!stream_is_local($resource)) {
40
            throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
41
        }
42
43 8
        if (!file_exists($resource)) {
44
            throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
45 3
        }
46
47 6
        $catalogue = new MessageCatalogue($locale);
48 6
        $content = file_get_contents($resource);
49 6
        $this->extractFromContent($content, $catalogue, $domain);
50
51 6
        if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
52 6
            $catalogue->addResource(new FileResource($resource));
53 6
        }
54
55 6
        return $catalogue;
56
    }
57
58
    /**
59
     * @param string           $content   xml content
60
     * @param MessageCatalogue $catalogue
61
     * @param string           $domain
62
     */
63 12
    public function extractFromContent($content, MessageCatalogue $catalogue, $domain)
64
    {
65
        try {
66 12
            $dom = $this->loadFileContent($content);
67 12
        } catch (\InvalidArgumentException $e) {
68 2
            throw new InvalidResourceException(sprintf('Unable to load data: %s', $e->getMessage()), $e->getCode(), $e);
69
        }
70
71 10
        if (method_exists($this, 'getVersionNumber')) {
72 10
            $xliffVersion = NSA::invokeMethod($this, 'getVersionNumber', $dom);
73 10
            NSA::invokeMethod($this, 'validateSchema', $xliffVersion, $dom, NSA::invokeMethod($this, 'getSchema', $xliffVersion));
74 10
        } else {
75
            // Symfony 2.7
76
            if (null === $this->sfPort) {
77
                $this->sfPort = new SymfonyPort();
78
            }
79
            $xliffVersion = $this->sfPort->getVersionNumber($dom);
0 ignored issues
show
Deprecated Code introduced by
The method Translation\SymfonyStora...ort::getVersionNumber() has been deprecated with message: Will be removed when we drop support for SF2.7

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
80
        }
81
82 10
        if ('1.2' === $xliffVersion) {
83 7
            if (method_exists($this, 'extractXliff1')) {
84 7
                NSA::invokeMethod($this, 'extractXliff1', $dom, $catalogue, $domain);
85 7
            } else {
86
                if (null === $this->sfPort) {
87
                    $this->sfPort = new SymfonyPort();
88
                }
89
                $this->sfPort->extractXliff1($dom, $catalogue, $domain);
0 ignored issues
show
Deprecated Code introduced by
The method Translation\SymfonyStora...nyPort::extractXliff1() has been deprecated with message: Will be removed when we drop support for SF2.7

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
90
            }
91 7
        }
92
93 10
        if ('2.0' === $xliffVersion) {
94 3
            if (null === $this->sfPort) {
95 3
                $this->sfPort = new SymfonyPort();
96 3
            }
97 3
            $this->sfPort->extractXliff2($dom, $catalogue, $domain);
98 3
        }
99 10
    }
100
101
    /**
102
     * Loads an XML file.
103
     *
104
     * Taken and modified from Symfony\Component\Config\Util\XmlUtils
105
     *
106
     * @author Fabien Potencier <[email protected]>
107
     * @author Martin Hasoň <[email protected]>
108
     *
109
     * @param string $content An XML file path
110
     *
111
     * @return \DOMDocument
112
     *
113
     * @throws \InvalidArgumentException When loading of XML file returns error
114
     */
115 12
    private function loadFileContent($content)
116
    {
117 12
        if ('' === trim($content)) {
118 1
            throw new \InvalidArgumentException('Content does not contain valid XML, it is empty.');
119
        }
120
121 11
        $internalErrors = libxml_use_internal_errors(true);
122 11
        $disableEntities = libxml_disable_entity_loader(true);
123 11
        libxml_clear_errors();
124
125 11
        $dom = new \DOMDocument();
126 11
        $dom->validateOnParse = true;
127 11
        if (!$dom->loadXML($content, LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) {
128 1
            libxml_disable_entity_loader($disableEntities);
129
130 1
            throw new \InvalidArgumentException(implode("\n", static::getXmlErrors($internalErrors)));
0 ignored issues
show
Bug introduced by
Since getXmlErrors() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getXmlErrors() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
131
        }
132
133 10
        $dom->normalizeDocument();
134
135 10
        libxml_use_internal_errors($internalErrors);
136 10
        libxml_disable_entity_loader($disableEntities);
137
138 10
        foreach ($dom->childNodes as $child) {
139 10
            if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
140
                throw new \InvalidArgumentException('Document types are not allowed.');
141
            }
142 10
        }
143
144 10
        libxml_clear_errors();
145 10
        libxml_use_internal_errors($internalErrors);
146
147 10
        return $dom;
148
    }
149
150 1
    private function getXmlErrors($internalErrors)
0 ignored issues
show
Bug introduced by
Consider using a different method name as you override a private method of the parent class.

Overwriting private methods is generally fine as long as you also use private visibility. It might still be preferable for understandability to use a different method name.

Loading history...
151
    {
152 1
        $errors = [];
153 1
        foreach (libxml_get_errors() as $error) {
154 1
            $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
155 1
                LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
156 1
                $error->code,
157 1
                trim($error->message),
158 1
                $error->file ?: 'n/a',
159 1
                $error->line,
160 1
                $error->column
161 1
            );
162 1
        }
163
164 1
        libxml_clear_errors();
165 1
        libxml_use_internal_errors($internalErrors);
166
167 1
        return $errors;
168
    }
169
}
170