Passed
Push — main ( 28d658...6dddcc )
by Michiel
06:12
created

XmlLintTask::logError()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 7
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 6
1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Task\System;
22
23
use DOMDocument;
24
use Phing\Exception\BuildException;
25
use Phing\Io\File;
26
use Phing\Project;
27
use Phing\Task;
28
use Phing\Type\Element\FileSetAware;
29
30
/**
31
 * A XML lint task. Checking syntax of one or more XML files against an XML Schema using the DOM extension.
32
 *
33
 * @author  Knut Urdalen <[email protected]>
34
 */
35
class XmlLintTask extends Task
36
{
37
    use FileSetAware;
38
39
    protected $file; // the source file (from xml attribute)
40
    protected $schema; // the schema file (from xml attribute)
41
    protected $useRNG = false;
42
43
    protected $haltonfailure = true;
44
45
    /**
46
     * File to be performed syntax check on.
47
     */
48 1
    public function setFile(File $file): void
49
    {
50 1
        $this->file = $file;
51
    }
52
53
    /**
54
     * XML Schema Description file to validate against.
55
     */
56 1
    public function setSchema(File $schema): void
57
    {
58 1
        $this->schema = $schema;
59
    }
60
61
    /**
62
     * Use RNG instead of DTD schema validation.
63
     */
64 1
    public function setUseRNG(bool $bool): void
65
    {
66 1
        $this->useRNG = $bool;
67
    }
68
69
    /**
70
     * Sets the haltonfailure attribute.
71
     */
72 1
    public function setHaltonfailure(bool $haltonfailure): void
73
    {
74 1
        $this->haltonfailure = $haltonfailure;
75
    }
76
77
    /**
78
     * Execute lint check against PhingFile or a FileSet.
79
     *
80
     * {@inheritdoc}
81
     *
82
     * @throws BuildException
83
     */
84 1
    public function main()
85
    {
86 1
        libxml_use_internal_errors(true);
87 1
        if (isset($this->schema) && !file_exists($this->schema->getPath())) {
88
            throw new BuildException('Schema file not found: ' . $this->schema->getPath());
89
        }
90 1
        if (!isset($this->file) && 0 === count($this->filesets)) {
91
            throw new BuildException("Missing either a nested fileset or attribute 'file' set");
92
        }
93
94 1
        set_error_handler([$this, 'errorHandler']);
95 1
        if ($this->file instanceof File) {
96 1
            $this->lint($this->file->getPath());
97
        } else { // process filesets
98
            $project = $this->getProject();
99
            foreach ($this->filesets as $fs) {
100
                $ds = $fs->getDirectoryScanner($project);
101
                $files = $ds->getIncludedFiles();
102
                $dir = $fs->getDir($this->project)->getPath();
103
                foreach ($files as $file) {
104
                    $this->lint($dir . DIRECTORY_SEPARATOR . $file);
105
                }
106
            }
107
        }
108 1
        restore_error_handler();
109
    }
110
111
    /**
112
     * Local error handler to catch validation errors and log them through Phing.
113
     *
114
     * @param int    $level
115
     * @param string $message
116
     * @param string $file
117
     * @param int    $line
118
     */
119
    public function errorHandler($level, $message, $file, $line): void
0 ignored issues
show
Unused Code introduced by
The parameter $file is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

119
    public function errorHandler($level, $message, /** @scrutinizer ignore-unused */ $file, $line): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $line is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

119
    public function errorHandler($level, $message, $file, /** @scrutinizer ignore-unused */ $line): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
120
    {
121
        $matches = [];
122
        preg_match('/^.*\(\): (.*)$/', $message, $matches);
123
        $this->log($matches[1], Project::MSG_ERR);
124
    }
125
126
    /**
127
     * @param $message
128
     *
129
     * @throws BuildException
130
     */
131
    protected function logError($message): void
132
    {
133
        if ($this->haltonfailure) {
134
            throw new BuildException($message);
135
        }
136
137
        $this->log($message, Project::MSG_ERR);
138
    }
139
140
    /**
141
     * Performs validation.
142
     *
143
     * @param string $file
144
     */
145 1
    protected function lint($file): void
146
    {
147 1
        if (file_exists($file)) {
148 1
            if (is_readable($file)) {
149 1
                $dom = new DOMDocument();
150 1
                if (false === $dom->load($file)) {
151
                    $this->libxmlGetErrors();
152
                    $this->logError($file . ' is not well-formed (See messages above)');
153
                } else {
154 1
                    if (isset($this->schema)) {
155 1
                        if ($this->useRNG) {
156 1
                            if ($dom->relaxNGValidate($this->schema->getPath())) {
157 1
                                $this->log($file . ' validated with RNG grammar');
158
                            } else {
159
                                $this->libxmlGetErrors();
160
                                $this->logError($file . ' fails to validate (See messages above)');
161
                            }
162
                        } else {
163
                            if ($dom->schemaValidate($this->schema->getPath())) {
164
                                $this->log($file . ' validated with schema');
165
                            } else {
166
                                $this->libxmlGetErrors();
167
                                $this->logError($file . ' fails to validate (See messages above)');
168
                            }
169
                        }
170
                    } else {
171
                        $this->log(
172
                            $file . ' is well-formed (not validated due to missing schema specification)'
173
                        );
174
                    }
175
                }
176
            } else {
177
                $this->logError('Permission denied to read file: ' . $file);
178
            }
179
        } else {
180
            $this->logError('File not found: ' . $file);
181
        }
182
    }
183
184
    private function libxmlGetErrors(): void
185
    {
186
        $errors = libxml_get_errors();
187
        foreach ($errors as $error) {
188
            [$severity, $message] = $this->libxmlGetError($error);
189
            $this->log($message, 'error' === $severity ? Project::MSG_ERR : Project::MSG_WARN);
190
        }
191
        libxml_clear_errors();
192
    }
193
194
    private function libxmlGetError($error): array
195
    {
196
        $return = '';
197
        $severity = '';
198
199
        switch ($error->level) {
200
            case LIBXML_ERR_WARNING:
201
                $return .= "Warning {$error->code}: ";
202
                $severity = 'warn';
203
204
                break;
205
206
            case LIBXML_ERR_ERROR:
207
                $return .= "Error {$error->code}: ";
208
                $severity = 'error';
209
210
                break;
211
212
            case LIBXML_ERR_FATAL:
213
                $return .= "Fatal Error {$error->code}: ";
214
                $severity = 'error';
215
216
                break;
217
        }
218
        $return .= trim($error->message);
219
        if ($error->file) {
220
            $return .= " in {$error->file}";
221
        }
222
        $return .= " on line {$error->line}";
223
224
        return [$severity, $return];
225
    }
226
}
227