Completed
Push — master ( e4a30a...1a3d0b )
by Tobias
07:42
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 17
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\Translation\Loader\XliffFileLoader;
16
use Symfony\Component\Translation\MessageCatalogue;
17
use Symfony\Component\Translation\Exception\InvalidResourceException;
18
19
/**
20
 * This class is an ugly hack to allow loading Xliff from string content.
21
 *
22
 * @author Tobias Nyholm <[email protected]>
23
 */
24
final class XliffLoader extends XliffFileLoader
25
{
26
    /**
27
     * @param string           $content   xml content
28
     * @param MessageCatalogue $catalogue
29
     * @param string           $domain
30
     */
31
    public function extractFromContent($content, MessageCatalogue $catalogue, $domain)
32
    {
33
        try {
34
            $dom = $this->loadFileContent($content);
35
        } catch (\InvalidArgumentException $e) {
36
            throw new InvalidResourceException(sprintf('Unable to load data: %s', $e->getMessage()), $e->getCode(), $e);
37
        }
38
39
        if (!method_exists($this, 'getVersionNumber')) {
40
            // Symfony 2.7
41
            throw new \RuntimeException('Cannot use XliffLoader::extractFromContent with Symfony 2.7');
42
        }
43
44
        $xliffVersion = NSA::invokeMethod($this, 'getVersionNumber', $dom);
45
        NSA::invokeMethod($this, 'validateSchema', $xliffVersion, $dom, NSA::invokeMethod($this, 'getSchema', $xliffVersion));
46
47
        if ('1.2' === $xliffVersion) {
48
            NSA::invokeMethod($this, 'extractXliff1', $dom, $catalogue, $domain);
49
        }
50
51
        if ('2.0' === $xliffVersion) {
52
            NSA::invokeMethod($this, 'extractXliff2', $dom, $catalogue, $domain);
53
        }
54
    }
55
56
    /**
57
     * Loads an XML file.
58
     *
59
     * Taken and modified from Symfony\Component\Config\Util\XmlUtils
60
     *
61
     * @author Fabien Potencier <[email protected]>
62
     * @author Martin Hasoň <[email protected]>
63
     *
64
     * @param string               $content          An XML file path
65
     * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation
66
     *
67
     * @return \DOMDocument
68
     *
69
     * @throws \InvalidArgumentException When loading of XML file returns error
70
     */
71
    private function loadFileContent($content, $schemaOrCallable = null)
72
    {
73
        if ('' === trim($content)) {
74
            throw new \InvalidArgumentException('Content does not contain valid XML, it is empty.');
75
        }
76
77
        $internalErrors = libxml_use_internal_errors(true);
78
        $disableEntities = libxml_disable_entity_loader(true);
79
        libxml_clear_errors();
80
81
        $dom = new \DOMDocument();
82
        $dom->validateOnParse = true;
83
        if (!$dom->loadXML($content, LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) {
84
            libxml_disable_entity_loader($disableEntities);
85
86
            throw new \InvalidArgumentException(implode("\n", static::getXmlErrors($internalErrors)));
0 ignored issues
show
Comprehensibility introduced by
Since Translation\SymfonyStorage\Loader\XliffLoader is declared final, using late-static binding will have no effect. You might want to replace static with self instead.

Late static binding only has effect in subclasses. A final class cannot be extended anymore so late static binding cannot occurr. Consider replacing static:: with self::.

To learn more about late static binding, please refer to the PHP core documentation.

Loading history...
87
        }
88
89
        $dom->normalizeDocument();
90
91
        libxml_use_internal_errors($internalErrors);
92
        libxml_disable_entity_loader($disableEntities);
93
94
        foreach ($dom->childNodes as $child) {
95
            if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
96
                throw new \InvalidArgumentException('Document types are not allowed.');
97
            }
98
        }
99
100
        if (null !== $schemaOrCallable) {
101
            $internalErrors = libxml_use_internal_errors(true);
102
            libxml_clear_errors();
103
104
            $e = null;
105
            if (is_callable($schemaOrCallable)) {
106
                try {
107
                    $valid = call_user_func($schemaOrCallable, $dom, $internalErrors);
108
                } catch (\Exception $e) {
109
                    $valid = false;
110
                }
111
            } elseif (!is_array($schemaOrCallable) && is_file((string) $schemaOrCallable)) {
112
                $schemaSource = file_get_contents((string) $schemaOrCallable);
113
                $valid = @$dom->schemaValidateSource($schemaSource);
114
            } else {
115
                libxml_use_internal_errors($internalErrors);
116
117
                throw new \InvalidArgumentException('The schemaOrCallable argument has to be a valid path to XSD file or callable.');
118
            }
119
120
            if (!$valid) {
121
                $messages = $this->getXmlErrors($internalErrors);
122
                if (empty($messages)) {
123
                    $messages = ['The XML is not valid.'];
124
                }
125
                throw new \InvalidArgumentException(implode("\n", $messages), 0, $e);
126
            }
127
        }
128
129
        libxml_clear_errors();
130
        libxml_use_internal_errors($internalErrors);
131
132
        return $dom;
133
    }
134
135
    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...
136
    {
137
        $errors = [];
138
        foreach (libxml_get_errors() as $error) {
139
            $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
140
                LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
141
                $error->code,
142
                trim($error->message),
143
                $error->file ?: 'n/a',
144
                $error->line,
145
                $error->column
146
            );
147
        }
148
149
        libxml_clear_errors();
150
        libxml_use_internal_errors($internalErrors);
151
152
        return $errors;
153
    }
154
}
155