Completed
Push — master ( 7ecb24...006012 )
by Tobias
06:02
created

XliffLoader::getXmlErrors()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 0
cts 0
cp 0
rs 9.2
c 0
b 0
f 0
cc 4
eloc 13
nc 2
nop 1
crap 20
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 6
    public function load($resource, $locale, $domain = 'messages')
38
    {
39
        if (!stream_is_local($resource)) {
40 6
            throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
41 6
        }
42 2
43 2
        if (!file_exists($resource)) {
44
            throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
45 4
        }
46 4
47 4
        $catalogue = new MessageCatalogue($locale);
48 4
        $content = file_get_contents($resource);
49
        $this->extractFromContent($content, $catalogue, $domain);
50
51
        if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
52
            $catalogue->addResource(new FileResource($resource));
53
        }
54
55
        return $catalogue;
56 4
    }
57 2
58 2
    /**
59 2
     * @param string           $content   xml content
60 1
     * @param MessageCatalogue $catalogue
61
     * @param string           $domain
62 1
     */
63
    public function extractFromContent($content, MessageCatalogue $catalogue, $domain)
64
    {
65 2
        try {
66
            $dom = $this->loadFileContent($content);
67 4
        } catch (\InvalidArgumentException $e) {
68 2
            throw new InvalidResourceException(sprintf('Unable to load data: %s', $e->getMessage()), $e->getCode(), $e);
69 2
        }
70 2
71 2
        if (method_exists($this, 'getVersionNumber')) {
72 2
            $xliffVersion = NSA::invokeMethod($this, 'getVersionNumber', $dom);
73 4
            NSA::invokeMethod($this, 'validateSchema', $xliffVersion, $dom, NSA::invokeMethod($this, 'getSchema', $xliffVersion));
74
        } 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
        if ('1.2' === $xliffVersion) {
83
            if (method_exists($this, 'extractXliff1')) {
84
                NSA::invokeMethod($this, 'extractXliff1', $dom, $catalogue, $domain);
85
            } else {
86
                if (null === $this->sfPort) {
87
                    $this->sfPort = new SymfonyPort();
88
                }
89 6
                $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 6
        }
92 1
93
        if ('2.0' === $xliffVersion) {
94
            if (null === $this->sfPort) {
95 5
                $this->sfPort = new SymfonyPort();
96 5
            }
97 5
            $this->sfPort->extractXliff2($dom, $catalogue, $domain);
98
        }
99 5
    }
100 5
101 5
    /**
102 1
     * Loads an XML file.
103
     *
104 1
     * Taken and modified from Symfony\Component\Config\Util\XmlUtils
105
     *
106
     * @author Fabien Potencier <[email protected]>
107 4
     * @author Martin Hasoň <[email protected]>
108
     *
109 4
     * @param string $content An XML file path
110 4
     *
111
     * @return \DOMDocument
112 4
     *
113 4
     * @throws \InvalidArgumentException When loading of XML file returns error
114
     */
115
    private function loadFileContent($content)
116 4
    {
117
        if ('' === trim($content)) {
118 4
            throw new \InvalidArgumentException('Content does not contain valid XML, it is empty.');
119 4
        }
120
121 4
        $internalErrors = libxml_use_internal_errors(true);
122
        $disableEntities = libxml_disable_entity_loader(true);
123
        libxml_clear_errors();
124 1
125
        $dom = new \DOMDocument();
126 1
        $dom->validateOnParse = true;
127 1
        if (!$dom->loadXML($content, LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) {
128 1
            libxml_disable_entity_loader($disableEntities);
129 1
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 1
        }
132 1
133 1
        $dom->normalizeDocument();
134 1
135 1
        libxml_use_internal_errors($internalErrors);
136 1
        libxml_disable_entity_loader($disableEntities);
137
138 1
        foreach ($dom->childNodes as $child) {
139 1
            if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
140
                throw new \InvalidArgumentException('Document types are not allowed.');
141 1
            }
142
        }
143
144
        libxml_clear_errors();
145
        libxml_use_internal_errors($internalErrors);
146
147
        return $dom;
148
    }
149
150
    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
        $errors = [];
153
        foreach (libxml_get_errors() as $error) {
154
            $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
155
                LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
156
                $error->code,
157
                trim($error->message),
158
                $error->file ?: 'n/a',
159
                $error->line,
160
                $error->column
161
            );
162
        }
163
164
        libxml_clear_errors();
165
        libxml_use_internal_errors($internalErrors);
166
167
        return $errors;
168
    }
169
}
170