Issues (1131)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/config/XmlConfigParser.class.php (20 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Agavi\Config;
3
4
// +---------------------------------------------------------------------------+
5
// | This file is part of the Agavi package.                                   |
6
// | Copyright (c) 2005-2011 the Agavi Project.                                |
7
// |                                                                           |
8
// | For the full copyright and license information, please view the LICENSE   |
9
// | file that was distributed with this source code. You can also view the    |
10
// | LICENSE file online at http://www.agavi.org/LICENSE.txt                   |
11
// |   vi: set noexpandtab:                                                    |
12
// |   Local Variables:                                                        |
13
// |   indent-tabs-mode: t                                                     |
14
// |   End:                                                                    |
15
// +---------------------------------------------------------------------------+
16
17
18
use Agavi\Util\SchematronProcessor;
19
use Agavi\Util\Toolkit;
20
use Agavi\Exception\ParseException;
21
use Agavi\Exception\UnreadableException;
22
use Agavi\Config\Util\Dom\XmlConfigDomDocument;
23
use Agavi\Config\Util\Dom\XmlConfigDomElement;
24
use Agavi\Util\XsltProcessor;
25
26
/**
27
 * XmlConfigParser handles both Agavi and foreign XML configuration files,
28
 * deals with XIncludes, XSL transformations and validation as well as filtering
29
 * and ordering of configuration blocks and parent file resolution and parsing.
30
 *
31
 * @package    agavi
32
 * @subpackage config
33
 *
34
 * @author     David Zülke <[email protected]>
35
 * @author     Noah Fontes <[email protected]>
36
 * @copyright  Authors
37
 * @copyright  The Agavi Project
38
 *
39
 * @since      0.11.0
40
 *
41
 * @version    $Id$
42
 */
43
44
class XmlConfigParser
45
{
46
    const NAMESPACE_AGAVI_ENVELOPE_0_11 = 'http://agavi.org/agavi/1.0/config';
47
    
48
    const NAMESPACE_AGAVI_ENVELOPE_1_0 = 'http://agavi.org/agavi/config/global/envelope/1.0';
49
    
50
    const NAMESPACE_AGAVI_ENVELOPE_1_1 = 'http://agavi.org/agavi/config/global/envelope/1.1';
51
    
52
    const NAMESPACE_AGAVI_ENVELOPE_LATEST = self::NAMESPACE_AGAVI_ENVELOPE_1_1;
53
    
54
    const NAMESPACE_AGAVI_ANNOTATIONS_1_0 = 'http://agavi.org/agavi/config/global/annotations/1.0';
55
    
56
    const NAMESPACE_AGAVI_ANNOTATIONS_LATEST = self::NAMESPACE_AGAVI_ANNOTATIONS_1_0;
57
    
58
    const VALIDATION_TYPE_XMLSCHEMA = 'xml_schema';
59
    
60
    const VALIDATION_TYPE_RELAXNG = 'relax_ng';
61
    
62
    const VALIDATION_TYPE_SCHEMATRON = 'schematron';
63
    
64
    const NAMESPACE_SCHEMATRON_ISO = 'http://purl.oclc.org/dsdl/schematron';
65
    
66
    const NAMESPACE_SVRL_ISO = 'http://purl.oclc.org/dsdl/svrl';
67
    
68
    const NAMESPACE_XML_1998 = 'http://www.w3.org/XML/1998/namespace';
69
    
70
    const NAMESPACE_XMLNS_2000 = 'http://www.w3.org/2000/xmlns/';
71
    
72
    const NAMESPACE_XSL_1999 = 'http://www.w3.org/1999/XSL/Transform';
73
    
74
    const NAMESPACE_XINCLUDE_2001 = 'http://www.w3.org/2001/XInclude';
75
    
76
    const STAGE_SINGLE = 'single';
77
    
78
    const STAGE_COMPILATION = 'compilation';
79
    
80
    const STEP_TRANSFORMATIONS_BEFORE = 'transformations_before';
81
    
82
    const STEP_TRANSFORMATIONS_AFTER = 'transformations_after';
83
    
84
    /**
85
     * @var        array A list of XML namespaces for Agavi configuration files as
86
     *                   keys and their associated XPath namespace prefix (value).
87
     */
88
    public static $agaviEnvelopeNamespaces = array(
89
        self::NAMESPACE_AGAVI_ENVELOPE_0_11 => 'agavi_envelope_0_11',
90
        self::NAMESPACE_AGAVI_ENVELOPE_1_0 => 'agavi_envelope_1_0',
91
        self::NAMESPACE_AGAVI_ENVELOPE_1_1 => 'agavi_envelope_1_1',
92
    );
93
    
94
    /**
95
     * @var        array A list of all XML namespaces that are used internally by
96
     *                   the configuration parser.
97
     */
98
    public static $agaviNamespaces = array(
99
        self::NAMESPACE_AGAVI_ENVELOPE_0_11 => 'agavi_envelope_0_11',
100
        self::NAMESPACE_AGAVI_ENVELOPE_1_0 => 'agavi_envelope_1_0',
101
        self::NAMESPACE_AGAVI_ENVELOPE_1_1 => 'agavi_envelope_1_1',
102
        self::NAMESPACE_AGAVI_ANNOTATIONS_1_0 => 'agavi_annotations_1_0',
103
    );
104
    
105
    /**
106
     * @var        string Path to the config file we're parsing in this instance.
107
     */
108
    protected $path = '';
109
    
110
    /**
111
     * @var        string The name of the current environment.
112
     */
113
    protected $environment = '';
114
    
115
    /**
116
     * @var        string The name of the current context.
117
     */
118
    protected $context = null;
119
    
120
    /**
121
     * @var        \DOMDocument|XmlConfigDomDocument The document we're parsing here.
122
     */
123
    protected $doc = null;
124
    
125
    /**
126
     * Test if the given document looks like an Agavi config file.
127
     *
128
     * @param      \DOMDocument $doc The document to test.
129
     *
130
     * @return     bool True, if it is an Agavi config document, false otherwise.
131
     *
132
     * @author     David Zülke <[email protected]>
133
     * @since      1.0.0
134
     */
135
    public static function isAgaviConfigurationDocument(\DOMDocument $doc)
136
    {
137
        return $doc->documentElement && $doc->documentElement->localName == 'configurations' && self::isAgaviEnvelopeNamespace($doc->documentElement->namespaceURI);
138
    }
139
    
140
    /**
141
     * Check if the given namespace URI is a valid Agavi envelope namespace.
142
     *
143
     * @param      string $namespaceUri The namespace URI.
144
     *
145
     * @return     bool True, if the given URI is a valid namespace URI, or false.
146
     *
147
     * @author     David Zülke <[email protected]>
148
     * @since      1.0.0
149
     */
150
    public static function isAgaviEnvelopeNamespace($namespaceUri)
151
    {
152
        return isset(self::$agaviEnvelopeNamespaces[$namespaceUri]);
153
    }
154
    
155
    /**
156
     * Check if a given namespace URI is a valid Agavi namespace.
157
     *
158
     * @param      string $namespaceUri The namespace URI.
159
     *
160
     * @return     bool True if the given URI is a valid namespace URI,
161
     *                  false otherwise.
162
     *
163
     * @author     Noah Fontes <[email protected]>
164
     * @since      1.0.0
165
     */
166
    public static function isAgaviNamespace($namespaceUri)
167
    {
168
        return isset(self::$agaviNamespaces[$namespaceUri]);
169
    }
170
    
171
    /**
172
     * Retrieves an XPath namespace prefix based on a given namespace URI.
173
     *
174
     * @param      string $namespaceUri The namespace URI.
175
     *
176
     * @return     string The prefix for the namespace URI, or null if none
177
     *                    exists.
178
     *
179
     * @author     Noah Fontes <[email protected]>
180
     * @since      1.0.0
181
     */
182
    public static function getAgaviNamespacePrefix($namespaceUri)
183
    {
184
        if (self::isAgaviNamespace($namespaceUri)) {
185
            return self::$agaviNamespaces[$namespaceUri];
186
        }
187
        return null;
188
    }
189
    
190
    /**
191
     * Register Agavi namespace prefixes in a given document.
192
     *
193
     * @param      XmlConfigDomDocument $document The document.
194
     *
195
     * @author     Noah Fontes <[email protected]>
196
     * @since      1.0.0
197
     */
198
    public static function registerAgaviNamespaces(XmlConfigDomDocument $document)
199
    {
200
        $xpath = $document->getXpath();
201
        
202
        foreach (self::$agaviNamespaces as $namespaceUri => $prefix) {
203
            $xpath->registerNamespace($prefix, $namespaceUri);
204
        }
205
        
206
        /* Register the latest namespaces. */
207
        $xpath->registerNamespace('agavi_envelope_latest', self::NAMESPACE_AGAVI_ENVELOPE_LATEST);
208
        $xpath->registerNamespace('agavi_annotations_latest', self::NAMESPACE_AGAVI_ANNOTATIONS_LATEST);
209
    }
210
                                                     
211
    /**
212
     * @param      string $path               An absolute filesystem path to a configuration file.
213
     * @param      string $environment        The environment name.
214
     * @param      string $context            The optional context name.
215
     * @param      array  $transformationInfo An associative array of transformation information.
216
     * @param      array  $validationInfo     An associative array of validation information.
217
     *
218
     * @return     XmlConfigDomDocument A properly merged DOMDocument.
219
     *
220
     * @author     David Zülke <[email protected]>
221
     * @author     Dominik del Bondio <[email protected]>
222
     * @author     Noah Fontes <[email protected]>
223
     * @since      0.11.0
224
     */
225
    public static function run($path, $environment, $context = null, array $transformationInfo = array(), array $validationInfo = array())
226
    {
227
        $isAgaviConfigFormat = true;
228
        // build an array of documents (this one, and the parents)
229
        $docs = array();
230
        $previousPaths = array();
231
        $nextPath = $path;
232
        while ($nextPath !== null) {
233
            // run the single stage parser
234
            $parser = new XmlConfigParser($nextPath, $environment, $context);
235
            $doc = $parser->execute($transformationInfo[self::STAGE_SINGLE], $validationInfo[self::STAGE_SINGLE]);
236
            
237
            // put the new document in the list
238
            $docs[] = $doc;
239
            
240
            // make sure it (still) is a <configurations> file with the proper Agavi namespace
241
            if ($isAgaviConfigFormat) {
242
                $isAgaviConfigFormat = self::isAgaviConfigurationDocument($doc);
243
            }
244
            
245
            // is it an Agavi <configurations> element? does it have a parent attribute? yes? good. parse that next
246
            // TODO: support future namespaces
247
            if ($isAgaviConfigFormat && $doc->documentElement->hasAttribute('parent')) {
248
                $theNextPath = Toolkit::literalize($doc->documentElement->getAttribute('parent'));
249
                
250
                // no infinite loop plz, kthx
251
                if ($nextPath === $theNextPath) {
252
                    throw new ParseException(sprintf("Agavi detected an infinite loop while processing parent configuration files of \n%s\n\nFile\n%s\nincludes itself as a parent.", $path, $theNextPath));
253
                } elseif (isset($previousPaths[$theNextPath])) {
254
                    throw new ParseException(sprintf("Agavi detected an infinite loop while processing parent configuration files of \n%s\n\nFile\n%s\nhas previously been included by\n%s", $path, $theNextPath, $previousPaths[$theNextPath]));
255
                } else {
256
                    $previousPaths[$theNextPath] = $nextPath;
257
                    $nextPath = $theNextPath;
258
                }
259
            } else {
260
                $nextPath = null;
261
            }
262
        }
263
        
264
        // TODO: use our own classes here that extend DOM*
265
        $retval = new XmlConfigDomDocument();
266
        foreach (self::$agaviEnvelopeNamespaces as $envelopeNamespaceUri => $envelopeNamespacePrefix) {
267
            $retval->getXpath()->registerNamespace($envelopeNamespacePrefix, $envelopeNamespaceUri);
268
        }
269
        
270
        if ($isAgaviConfigFormat) {
271
            // if it is an Agavi config, we'll create a new document with all files' <configuration> blocks inside
272
            $retval->appendChild(new XmlConfigDomElement('configurations', null, self::NAMESPACE_AGAVI_ENVELOPE_LATEST));
273
            
274
            // reverse the array - we want the parents first!
275
            $docs = array_reverse($docs);
276
            
277
            $configurationElements = array();
278
            
279
            // TODO: I bet this leaks memory due to the nodes being taken out of the docs. beware circular refs!
280
            /** @var XmlConfigDomDocument $doc */
281
            foreach ($docs as $doc) {
282
                // iterate over all nodes (attributes, <sandbox>, <configuration> etc) inside the document element and append them to the <configurations> element in our final document
283
                foreach ($doc->documentElement->childNodes as $node) {
284
                    if ($node->nodeType == XML_ELEMENT_NODE && $node->localName == 'configuration' && self::isAgaviEnvelopeNamespace($node->namespaceURI)) {
285
                        // it's a <configuration> element - put that on a stack for processing
286
                        $configurationElements[] = $node;
287
                    } else {
288
                        // import the node, recursively, and store the imported node
289
                        $importedNode = $retval->importNode($node, true);
290
                        // now append it to the <configurations> element
291
                        $retval->documentElement->appendChild($importedNode);
292
                    }
293
                }
294
                // if it's a <configurations> element, then we need to copy the attributes from there
295
                if ($doc->isAgaviConfiguration()) {
296
                    $namespaces = $doc->getXPath()->query('namespace::*');
297
                    foreach ($namespaces as $namespace) {
298
                        if ($namespace->localName !== 'xml' && $namespace->localName != 'xmlns') {
299
                            $retval->documentElement->setAttributeNS(self::NAMESPACE_XMLNS_2000, 'xmlns:' . $namespace->localName, $namespace->namespaceURI);
300
                        }
301
                    }
302
                    foreach ($doc->documentElement->attributes as $attribute) {
303
                        // but not the "parent" attributes...
304
                        if ($attribute->namespaceURI === null && $attribute->localName === 'parent') {
305
                            continue;
306
                        }
307
                        $importedAttribute = $retval->importNode($attribute, true);
308
                        $retval->documentElement->setAttributeNode($importedAttribute);
309
                    }
310
                }
311
            }
312
            
313
            // generic <configuration> first, then those with an environment attribute, then those with context, then those with both
314
            $configurationOrder = array(
315
                'count(self::node()[@agavi_annotations_latest:matched and not(@environment) and not(@context)])',
316
                'count(self::node()[@agavi_annotations_latest:matched and @environment and not(@context)])',
317
                'count(self::node()[@agavi_annotations_latest:matched and not(@environment) and @context])',
318
                'count(self::node()[@agavi_annotations_latest:matched and @environment and @context])',
319
            );
320
            
321
            // now we sort the nodes according to the rules
322
            foreach ($configurationOrder as $xpath) {
323
                // append all matching nodes from the order array...
324
                foreach ($configurationElements as &$element) {
325
                    // ... if the xpath matches, that is!
326
                    if ($element->ownerDocument->getXpath()->evaluate($xpath, $element)) {
327
                        // it did, so import the node and append it to the result doc
328
                        $importedNode = $retval->importNode($element, true);
329
                        $retval->documentElement->appendChild($importedNode);
330
                    }
331
                }
332
            }
333
            
334
            // run the compilation stage parser
335
            $retval = self::executeCompilation($retval, $environment, $context, $transformationInfo[self::STAGE_COMPILATION], $validationInfo[self::STAGE_COMPILATION]);
336
        } else {
337
            // it's not an agavi config file. just pass it through then
338
            $retval->appendChild($retval->importNode($doc->documentElement, true));
0 ignored issues
show
The variable $doc does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
339
        }
340
        
341
        // cleanup attempt
342
        unset($docs);
343
        
344
        // set the pseudo-document URI
345
        $retval->documentURI = $path;
346
        
347
        return $retval;
348
    }
349
    
350
    /**
351
     * Builds a proper regular expression from the input pattern to test against
352
     * the given subject. This is for "environment" and "context" attributes of
353
     * configuration blocks in the files.
354
     *
355
     * @param      string $pattern A regular expression chunk without delimiters/anchors.
356
     * @param      string $subject The subject to test against
357
     *
358
     * @return     bool Whether or not the subject matched the pattern.
359
     *
360
     * @author     David Zülke <[email protected]>
361
     * @since      1.0.0
362
     */
363
    public static function testPattern($pattern, $subject)
364
    {
365
        // four backslashes mean one literal backslash
366
        $pattern = preg_replace('/\\\\+#/', '\\#', $pattern);
367
        return (preg_match('#^(' . implode('|', array_map('trim', explode(' ', $pattern))) . ')$#', $subject) > 0);
368
    }
369
    
370
    /**
371
     * The constructor.
372
     * Will make a DOMDocument instance using the given path.
373
     *
374
     * @param      string $path The path to the configuration file.
375
     * @param      string $environment The optional name of the current environment.
376
     * @param      string $context The optional name of the current context.
377
     *
378
     * @author     David Zülke <[email protected]>
379
     * @author     Noah Fontes <[email protected]>
380
     * @since      1.0.0
381
     */
382
    public function __construct($path, $environment = null, $context = null)
383
    {
384
        // store environment...
385
        if ($environment === null) {
386
            $environment = Config::get('core.environment');
387
        }
388
        $this->environment = $environment;
389
        // ... and context names
390
        $this->context = $context;
391
        
392 View Code Duplication
        if (!is_readable($path)) {
0 ignored issues
show
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...
393
            $error = 'Configuration file "' . $path . '" does not exist or is unreadable';
394
            throw new UnreadableException($error);
395
        }
396
        
397
        // store path to the config file
398
        $this->path = $path;
399
        
400
        // AgaviXmlConfigDomDocument has convenience methods!
401
        try {
402
            $this->doc = new XmlConfigDomDocument();
403
            $this->doc->substituteEntities = true;
404
            $this->doc->load($path);
405
        } catch (\DOMException $dome) {
406
            throw new ParseException(sprintf('Configuration file "%s" could not be parsed: %s', $path, $dome->getMessage()), 0, $dome);
407
        }
408
    }
409
    
410
    /**
411
     * Destructor to do the cleaning up.
412
     *
413
     * @author     David Zülke <[email protected]>
414
     * @since      1.0.0
415
     */
416
    public function __destruct()
417
    {
418
        unset($this->doc);
419
    }
420
    
421
    /**
422
     * @param      array $transformationInfo An array of XSL paths for transformation.
423
     * @param      array $validationInfo An associative array of validation information.
424
     *
425
     * @return     \DOMDocument Our DOMDocument.
426
     *
427
     * @author     David Zülke <[email protected]>
428
     * @author     Dominik del Bondio <[email protected]>
429
     * @author     Noah Fontes <[email protected]>
430
     * @since      0.11.0
431
     */
432
    public function execute(array $transformationInfo = array(), array $validationInfo = array())
433
    {
434
        // resolve xincludes
435
        self::xinclude($this->doc);
0 ignored issues
show
It seems like $this->doc can also be of type object<DOMDocument>; however, Agavi\Config\XmlConfigParser::xinclude() does only seem to accept object<Agavi\Config\Util...m\XmlConfigDomDocument>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
436
        
437
        // validate XMLSchema-instance declarations
438
        self::validateXsi($this->doc);
0 ignored issues
show
It seems like $this->doc can also be of type object<DOMDocument>; however, Agavi\Config\XmlConfigParser::validateXsi() does only seem to accept object<Agavi\Config\Util...m\XmlConfigDomDocument>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
439
        
440
        // validate pre-transformation
441
        self::validate($this->doc, $this->environment, $this->context, $validationInfo[XmlConfigParser::STEP_TRANSFORMATIONS_BEFORE]);
0 ignored issues
show
It seems like $this->doc can also be of type object<DOMDocument>; however, Agavi\Config\XmlConfigParser::validate() does only seem to accept object<Agavi\Config\Util...m\XmlConfigDomDocument>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
442
        
443
        // mark document for merging
444
        self::match($this->doc, $this->environment, $this->context);
0 ignored issues
show
It seems like $this->doc can also be of type object<DOMDocument>; however, Agavi\Config\XmlConfigParser::match() does only seem to accept object<Agavi\Config\Util...m\XmlConfigDomDocument>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
445
        
446
        if (!Config::get('core.skip_config_transformations', false)) {
0 ignored issues
show
false is of type boolean, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
447
            // run inline transformations
448
            $this->doc = self::transformProcessingInstructions($this->doc, $this->environment, $this->context);
0 ignored issues
show
It seems like $this->doc can also be of type object<DOMDocument>; however, Agavi\Config\XmlConfigPa...rocessingInstructions() does only seem to accept object<Agavi\Config\Util...m\XmlConfigDomDocument>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
449
            
450
            // perform XSL transformations
451
            $this->doc = self::transform($this->doc, $this->environment, $this->context, $transformationInfo);
452
            
453
            // resolve xincludes again, since transformations may have introduced some
454
            self::xinclude($this->doc);
455
        }
456
        
457
        // validate post-transformation
458
        self::validate($this->doc, $this->environment, $this->context, $validationInfo[XmlConfigParser::STEP_TRANSFORMATIONS_AFTER]);
0 ignored issues
show
It seems like $this->doc can also be of type object<DOMDocument>; however, Agavi\Config\XmlConfigParser::validate() does only seem to accept object<Agavi\Config\Util...m\XmlConfigDomDocument>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
459
        
460
        // clean up the document
461
        self::cleanup($this->doc);
0 ignored issues
show
It seems like $this->doc can also be of type object<DOMDocument>; however, Agavi\Config\XmlConfigParser::cleanup() does only seem to accept object<Agavi\Config\Util...m\XmlConfigDomDocument>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
462
        
463
        return $this->doc;
464
    }
465
    
466
    /**
467
     * Executes the parser for a compilation document.
468
     *
469
     * @param      XmlConfigDomDocument $document           The document to act upon.
470
     * @param      string               $environment        The environment name.
471
     * @param      string               $context            The context name.
472
     * @param      array                $transformationInfo An array of XSL paths for transformation.
473
     * @param      array                $validationInfo     An associative array of validation information.
474
     *
475
     * @author     Noah Fontes <[email protected]>
476
     * @since      1.0.0
477
     */
478
    public static function executeCompilation(XmlConfigDomDocument $document, $environment, $context, array $transformationInfo = array(), array $validationInfo = array())
479
    {
480
        // resolve xincludes
481
        self::xinclude($document);
482
        
483
        // validate pre-transformation
484
        self::validate($document, $environment, $context, $validationInfo[XmlConfigParser::STEP_TRANSFORMATIONS_BEFORE]);
485
        
486
        if (!Config::get('core.skip_config_transformations', false)) {
0 ignored issues
show
false is of type boolean, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
487
            // perform XSL transformations
488
            $document = self::transform($document, $environment, $context, $transformationInfo);
489
            
490
            // resolve xincludes again, since transformations may have introduced some
491
            self::xinclude($document);
492
        }
493
        
494
        // validate post-transformation
495
        self::validate($document, $environment, $context, $validationInfo[XmlConfigParser::STEP_TRANSFORMATIONS_AFTER]);
496
        
497
        return $document;
498
    }
499
    
500
    /**
501
     * Resolve xinclude directives on a given document.
502
     *
503
     * @param      XmlConfigDomDocument $document The document to act upon.
504
     *
505
     * @author     David Zülke <[email protected]>
506
     * @author     Noah Fontes <[email protected]>
507
     * @since      1.0.0
508
     */
509
    public static function xinclude(XmlConfigDomDocument $document)
510
    {
511
        // expand directives, resolve globs and encode paths in XInclude href attributes
512
        $elements = $document->getElementsByTagNameNS(self::NAMESPACE_XINCLUDE_2001, 'include');
513
        $length = $elements->length;
514
        // we can't foreach() over the DOMNodeList as we're modifying it further below
515
        // see http://php.net/manual/en/class.domnodelist.php#83178
516
        for ($i = 0; $i < $length; $i++) {
517
            $element = $elements->item($i);
518
            if ($element->hasAttribute('href')) {
0 ignored issues
show
The method hasAttribute() does not exist on DOMNode. Did you maybe mean hasAttributes()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
519
                $attribute = $element->getAttributeNode('href');
520
                $parts = explode('#', $attribute->nodeValue, 2);
521
                $parts[0] = str_replace('\\', '/', Toolkit::expandDirectives($parts[0]));
522
                $attribute->nodeValue = rawurlencode($parts[0]) . (isset($parts[1]) ? '#' . $parts[1] : '');
523
                if (strpos($parts[0], '*') !== false || strpos($parts[0], '{') !== false) {
524
                    $glob = glob($parts[0], GLOB_BRACE);
525
                    if ($glob) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $glob of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
526
                        $glob = array_unique($glob); // it could be that someone used /path/to/{Foo,*}/burp.xml so Foo would come before all others, that's why we need to remove duplicates as the * would match Foo again
527
                        foreach ($glob as $path) {
528
                            $new = $element->cloneNode(true);
529
                            $new->setAttribute('href', rawurlencode($path) . (isset($parts[1]) ? '#' . $parts[1] : ''));
530
                            $element->parentNode->insertBefore($new, $element);
531
                            ++$i;
532
                        }
533
                        $element->parentNode->removeChild($element);
534
                    }
535
                }
536
            }
537
        }
538
        
539
        // perform xincludes
540
        try {
541
            $document->xinclude();
542
        } catch (\DOMException $dome) {
543
            throw new ParseException(sprintf('Configuration file "%s" could not be parsed: %s', $document->documentURI, $dome->getMessage()), 0, $dome);
544
        }
545
        
546
        // remove all xml:base attributes inserted by XIncludes
547
        $nodes = $document->getXpath()->query('//@xml:base', $document);
548
        foreach ($nodes as $node) {
549
            $node->ownerElement->removeAttributeNode($node);
550
        }
551
    }
552
    
553
    /**
554
     * Annotate the document with matched attributes against each configuration
555
     * element that matches the given context and environment.
556
     *
557
     * @param      XmlConfigDomDocument $document    The document to act upon.
558
     * @param      string               $environment The environment name.
559
     * @param      string               $context     The context name.
560
     *
561
     * @author     David Zülke <[email protected]>
562
     * @author     Noah Fontes <[email protected]>
563
     * @since      1.0.0
564
     */
565
    public static function match(XmlConfigDomDocument $document, $environment, $context)
566
    {
567
        if ($document->isAgaviConfiguration()) {
568
            // it's an agavi config, so we need to set "matched" flags on all <configuration> elements where "context" and "environment" attributes match the values below
569
            $testAttributes = array(
570
                'context' => $context,
571
                'environment' => $environment,
572
            );
573
            
574
            foreach ($document->getConfigurationElements() as $configuration) {
575
                // assume that the element counts as matched, in case it doesn't have "context" or "environment" attributes
576
                $matched = true;
577
                foreach ($testAttributes as $attributeName => $attributeValue) {
578
                    if ($configuration->hasAttribute($attributeName)) {
579
                        $matched = $matched && self::testPattern($configuration->getAttribute($attributeName), $attributeValue);
580
                    }
581
                }
582
                if ($matched) {
583
                    // if all was fine, we set the attribute. the element will then be kept in the merged result doc later
584
                    $configuration->setAttributeNS(self::NAMESPACE_AGAVI_ANNOTATIONS_LATEST, 'agavi_annotations_latest:matched', 'true');
585
                }
586
            }
587
        }
588
    }
589
    
590
    /**
591
     * Transform the document using info from embedded processing instructions
592
     * and given stylesheets.
593
     *
594
     * @param      XmlConfigDomDocument $document The document to act upon.
595
     * @param      string $environment The environment name.
596
     * @param      string $context The context name.
597
     * @param      array  $transformationInfo An array of transformation information.
598
     * @param      array  $transformations An array of XSL stylesheets in DOMDocument instances.
599
     *
600
     * @return     XmlConfigDomDocument The transformed document.
601
     *
602
     * @author     David Zülke <[email protected]>
603
     * @author     Noah Fontes <[email protected]>
604
     * @since      0.11.0
605
     */
606
    public static function transform(XmlConfigDomDocument $document, $environment, $context, array $transformationInfo = array(), $transformations = array())
607
    {
608
        // loop over all the paths we found and load the files
609
        foreach ($transformationInfo as $href) {
610
            try {
611
                $xsl = new XmlConfigDomDocument();
612
                $xsl->load($href);
613
            } catch (\DOMException $dome) {
614
                throw new ParseException(sprintf('Configuration file "%s" could not be parsed: Could not load XSL stylesheet "%s": %s', $document->documentURI, $href, $dome->getMessage()), 0, $dome);
615
            }
616
            
617
            // add them to the list of transformations to be done
618
            $transformations[] = $xsl;
619
        }
620
        
621
        // now let's perform the transformations
622
        foreach ($transformations as $xsl) {
623
            // load the stylesheet document into an XSLTProcessor instance
624
            try {
625
                $proc = new XsltProcessor();
626
                $proc->registerPHPFunctions();
627
                $proc->importStylesheet($xsl);
628
            } catch (\Exception $e) {
629
                throw new ParseException(sprintf('Configuration file "%s" could not be parsed: Could not import XSL stylesheet "%s": %s', $document->documentURI, $xsl->documentURI, $e->getMessage()), 0, $e);
630
            }
631
            
632
            // set some info (config file path, context name, environment name) as params
633
            // first arg is the namespace URI, which PHP doesn't support. awesome. see http://bugs.php.net/bug.php?id=30622 for the sad details
634
            // we could use "agavi:context" etc, that does work even without such a prefix being declared in the stylesheet, but that would be completely non-XML-ish, confusing, and against the spec. so we use dots instead.
635
            // the string casts are required for hhvm ($context could be null for example and hhvm bails out on that)
636
            $proc->setParameter('', array(
637
                'agavi.config_path' => (string)$document->documentURI,
638
                'agavi.environment' => (string)$environment,
639
                'agavi.context' => (string)$context,
640
            ));
641
            
642
            try {
643
                // transform the doc
644
                $newdoc = $proc->transformToDoc($document);
645
            } catch (\Exception $e) {
646
                throw new ParseException(sprintf('Configuration file "%s" could not be parsed: Could not transform the document using the XSL stylesheet "%s": %s', $document->documentURI, $xsl->documentURI, $e->getMessage()), 0, $e);
647
            }
648
            
649
            // no errors and we got a document back? excellent. this will be our new baby from now. time to kill the old one
650
            
651
            // get the old document URI
652
            $documentUri = $document->documentURI;
653
            
654
            // and assign the new document to the old one
655
            $document = $newdoc;
656
            
657
            // save the old document URI just in case
658
            $document->documentURI = $documentUri;
659
        }
660
        
661
        return $document;
662
    }
663
    
664
    /**
665
     * Transforms a given document according to xml-stylesheet processing
666
     * instructions
667
     *
668
     * @param      XmlConfigDomDocument $document The document to act upon.
669
     * @param      string $environment The environment name.
670
     * @param      string $context The context name.
671
     *
672
     * @return     XmlConfigDomDocument The transformed document.
673
     *
674
     * @author     David Zülke <[email protected]>
675
     * @author     Noah Fontes <[email protected]>
676
     * @since      1.0.0
677
     */
678
    public static function transformProcessingInstructions(XmlConfigDomDocument $document, $environment, $context)
679
    {
680
        $transformations = array();
681
        $transformationInfo = array();
682
        
683
        $xpath = $document->getXpath();
684
        
685
        // see if there are <?xml-stylesheet... processing instructions
686
        $stylesheetProcessingInstructions = $xpath->query("//processing-instruction('xml-stylesheet')", $document);
687
        foreach ($stylesheetProcessingInstructions as $pi) {
688
            // yes! alright. trick: we create a doc fragment with the contents so we don't have to parse things by hand...
689
            $fragment = $document->createDocumentFragment();
690
            $fragment->appendXml('<foo ' . $pi->data . ' />');
691
            $type = $fragment->firstChild->getAttribute('type');
692
            // we process only the types below...
693
            if (in_array($type, array('text/xml', 'text/xsl', 'application/xml', 'application/xsl+xml'))) {
694
                $href = $href = $fragment->firstChild->getAttribute('href');
695
                
696
                if (strpos($href, '#') === 0) {
697
                    // the href points to an embedded XSL stylesheet (with ID reference), so let's see if we can find it
698
                    $stylesheets = $xpath->query("//*[@id='" . substr($href, 1) . "']", $document);
699
                    if ($stylesheets->length) {
700
                        // excellent. make a new doc from that element!
701
                        try {
702
                            $xsl = new XmlConfigDomDocument();
703
                            $xsl->appendChild($xsl->importNode($stylesheets->item(0), true));
704
                        } catch (\DOMException $dome) {
705
                            throw new ParseException(sprintf('Configuration file "%s" could not be parsed: Could not load XSL stylesheet "%s": %s', $document->documentURI, $href, $dome->getMessage()), 0, $dome);
706
                        }
707
                        
708
                        // and append to the list of XSLs to process
709
                        // TODO: spec mandates that external XSLs be processed first!
710
                        $transformations[] = $xsl;
711
                    } else {
712
                        throw new ParseException(sprintf('Configuration file "%s" could not be parsed because the inline stylesheet "%s" referenced in the "xml-stylesheet" processing instruction could not be found in the document.', $document->documentURI, $href));
713
                    }
714
                } else {
715
                    // href references an xsl file, remember the path
716
                    $transformationInfo[] = Toolkit::expandDirectives($href);
717
                }
718
                
719
                // remove the processing instructions after we dealt with them
720
                $pi->parentNode->removeChild($pi);
721
            }
722
        }
723
        
724
        return self::transform($document, $environment, $context, $transformationInfo, $transformations);
725
    }
726
    
727
    /**
728
     * Perform validation on a given document.
729
     *
730
     * @param      XmlConfigDomDocument $document       The document to act upon.
731
     * @param      string               $environment    The environment name.
732
     * @param      string               $context        The context name.
733
     * @param      array                $validationInfo An array of validation information.
734
     *
735
     * @author     David Zülke <[email protected]>
736
     * @author     Noah Fontes <[email protected]>
737
     * @since      0.11.0
738
     */
739
    public static function validate(XmlConfigDomDocument $document, $environment, $context, array $validationInfo = array())
740
    {
741
        // bail out right away if validation is disabled
742
        if (Config::get('core.skip_config_validation', false)) {
0 ignored issues
show
false is of type boolean, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
743
            return;
744
        }
745
        
746
        $errors = array();
747
        
748
        foreach ($validationInfo as $type => $files) {
749
            try {
750
                switch ($type) {
751
                    case self::VALIDATION_TYPE_XMLSCHEMA:
752
                        self::validateXmlschema($document, (array) $files);
753
                        break;
754
                    case self::VALIDATION_TYPE_RELAXNG:
755
                        self::validateRelaxng($document, (array) $files);
756
                        break;
757
                    case self::VALIDATION_TYPE_SCHEMATRON:
758
                        self::validateSchematron($document, $environment, $context, (array) $files);
759
                        break;
760
                }
761
            } catch (ParseException $e) {
762
                $errors[] = $e->getMessage();
763
            }
764
        }
765
        
766
        if ($errors) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
767
            throw new ParseException(sprintf('Validation of configuration file "%s" failed:' . "\n\n%s", $document->documentURI, implode("\n\n", $errors)));
768
        }
769
    }
770
771
    /**
772
     * Clean up a given document.
773
     *
774
     * @param      XmlConfigDomDocument $document The document to clean up.
775
     *
776
     * @author     David Zülke <[email protected]>
777
     * @since      0.11.0
778
     */
779
    public static function cleanup(XmlConfigDomDocument $document)
780
    {
781
        // remove top-level <sandbox> element
782
        if ($sandbox = $document->getSandbox()) {
783
            $sandbox->parentNode->removeChild($sandbox);
784
        }
785
    }
786
    
787
    /**
788
     * Validate a given document according to XMLSchema-instance (xsi)
789
     * declarations.
790
     *
791
     * @param      XmlConfigDomDocument $document The document to act upon.
792
     *
793
     * @author     David Zülke <[email protected]>
794
     * @author     Noah Fontes <[email protected]>
795
     * @since      1.0.0
796
     */
797
    public static function validateXsi(XmlConfigDomDocument $document)
798
    {
799
        // next, find (and validate against) XML schema instance declarations
800
        $sources = array();
801
        if ($document->documentElement->hasAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation')) {
802
            // find locations. for namespaces, they are space separated pairs of a namespace URI and a schema location
803
            $locations = preg_split('/\s+/', $document->documentElement->getAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation'));
804
            for ($i = 1; $i < count($locations); $i = $i + 2) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
805
                $sources[] = $locations[$i];
806
            }
807
        }
808
        // no namespace? then it's only one schema location in this attribute
809
        if ($document->documentElement->hasAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'noNamespaceSchemaLocation')) {
810
            $sources[] = $document->documentElement->getAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'noNamespaceSchemaLocation');
811
        }
812
        if ($sources) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sources of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
813
            // we have instances to validate against...
814
            $schemas = array();
815
            foreach ($sources as &$source) {
816
                // so for each location, we need to grab the file and validate against this grabbed source code, as libxml often has a hard time retrieving stuff over HTTP
817
                $source = Toolkit::expandDirectives($source);
818
                if (parse_url($source, PHP_URL_SCHEME) === null && !Toolkit::isPathAbsolute($source)) {
819
                    // the schema location is relative to the XML file
820
                    $source = dirname($document->documentURI) . DIRECTORY_SEPARATOR . $source;
821
                }
822
                $schema = @file_get_contents($source);
823
                if ($schema === false) {
824
                    throw new UnreadableException(sprintf('XML Schema validation file "%s" for configuration file "%s" does not exist or is unreadable', $source, $document->documentURI));
825
                }
826
                $schemas[] = $schema;
827
            }
828
            // now validate them all
829
            self::validateXmlschemaSource($document, $schemas);
830
        }
831
    }
832
    
833
    /**
834
     * Validate the document against the given list of XML Schema files.
835
     *
836
     * @param      XmlConfigDomDocument $document        The document to act upon.
837
     * @param      array                $validationFiles An array of file names to validate against.
838
     *
839
     * @author     David Zülke <[email protected]>
840
     * @author     Noah Fontes <[email protected]>
841
     * @since      0.11.0
842
     */
843 View Code Duplication
    public static function validateXmlschema(XmlConfigDomDocument $document, array $validationFiles = array())
0 ignored issues
show
This method seems to be duplicated in 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...
844
    {
845
        foreach ($validationFiles as $validationFile) {
846
            if (!is_resource($validationFile) && !is_readable($validationFile)) {
847
                throw new UnreadableException(sprintf('XML Schema validation file "%s" for configuration file "%s" does not exist or is unreadable', $validationFile, $document->documentURI));
848
            }
849
            
850
            try {
851
                $document->schemaValidate($validationFile);
852
            } catch (\DOMException $dome) {
853
                throw new ParseException(sprintf('XML Schema validation of configuration file "%s" failed:' . "\n\n%s", $document->documentURI, $dome->getMessage()), 0, $dome);
854
            }
855
        }
856
    }
857
    
858
    /**
859
     * Validate the document against the given list of XML Schema documents.
860
     *
861
     * @param      XmlConfigDomDocument $document          The document to act upon.
862
     * @param      array                $validationSources An array of schema documents to validate against.
863
     *
864
     * @author     David Zülke <[email protected]>
865
     * @author     Noah Fontes <[email protected]>
866
     * @since      1.0.0
867
     */
868
    public static function validateXmlschemaSource(XmlConfigDomDocument $document, array $validationSources = array())
869
    {
870
        foreach ($validationSources as $validationSource) {
871
            try {
872
                $document->schemaValidateSource($validationSource);
873
            } catch (\DOMException $dome) {
874
                throw new ParseException(sprintf('XML Schema validation of configuration file "%s" failed:' . "\n\n%s", $document->documentURI, $dome->getMessage()), 0, $dome);
875
            }
876
        }
877
    }
878
    
879
    /**
880
     * Validate the document against the given list of RELAX NG files.
881
     *
882
     * @param      XmlConfigDomDocument $document        The document to act upon.
883
     * @param      array                $validationFiles An array of file names to validate against.
884
     *
885
     * @author     David Zülke <[email protected]>
886
     * @author     Noah Fontes <[email protected]>
887
     * @since      0.11.0
888
     */
889 View Code Duplication
    public static function validateRelaxng(XmlConfigDomDocument $document, array $validationFiles = array())
0 ignored issues
show
This method seems to be duplicated in 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...
890
    {
891
        foreach ($validationFiles as $validationFile) {
892
            if (!is_readable($validationFile)) {
893
                throw new UnreadableException(sprintf('RELAX NG validation file "%s" for configuration file "%s" does not exist or is unreadable', $validationFile, $document->documentURI));
894
            }
895
            
896
            try {
897
                $document->relaxNGValidate($validationFile);
898
            } catch (\DOMException $dome) {
899
                throw new ParseException(sprintf('RELAX NG validation of configuration file "%s" failed:' . "\n\n%s", $document->documentURI, $dome->getMessage()), 0, $dome);
900
            }
901
        }
902
    }
903
    
904
    /**
905
     * Validate the document against the given list of Schematron files.
906
     *
907
     * @param      XmlConfigDomDocument $document        The document to act upon.
908
     * @param      string               $environment     The environment name.
909
     * @param      string               $context         The context name.
910
     * @param      array                $validationFiles An array of file names to validate against.
911
     *
912
     * @author     David Zülke <[email protected]>
913
     * @author     Noah Fontes <[email protected]>
914
     * @since      0.11.0
915
     */
916
    public static function validateSchematron(XmlConfigDomDocument $document, $environment, $context, array $validationFiles = array())
917
    {
918
        if (Config::get('core.skip_config_transformations', false)) {
0 ignored issues
show
false is of type boolean, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
919
            return;
920
        }
921
        
922
        // load the schematron processor
923
        $schematron = new SchematronProcessor();
924
        $schematron->setNode($document);
925
        // set some info (config file path, context name, environment name) as params
926
        // first arg is the namespace URI, which PHP doesn't support. awesome. see http://bugs.php.net/bug.php?id=30622 for the sad details
927
        // we could use "agavi:context" etc, that does work even without such a prefix being declared in the stylesheet, but that would be completely non-XML-ish, confusing, and against the spec. so we use dots instead.
928
        $schematron->setParameters(array(
929
            'agavi.config_path' => $document->documentURI,
930
            'agavi.environment' => $environment,
931
            'agavi.context' => $context,
932
        ));
933
        
934
        // loop over all validation files. those are .sch schematron schemas, which we transform to an XSL document that is then used to validate the source document :)
935
        foreach ($validationFiles as $href) {
936
            if (!is_readable($href)) {
937
                throw new UnreadableException(sprintf('Schematron validation file "%s" for configuration file "%s" does not exist or is unreadable', $href, $document->documentURI));
938
            }
939
            
940
            // load the .sch file
941
            try {
942
                $sch = new XmlConfigDomDocument();
943
                $sch->load($href);
944
            } catch (\DOMException $dome) {
945
                throw new ParseException(sprintf('Schematron validation of configuration file "%s" failed: Could not load schema file "%s": %s', $document->documentURI, $href, $dome->getMessage()), 0, $dome);
946
            }
947
            
948
            // perform the validation transformation
949
            try {
950
                $result = $schematron->transform($sch);
951
            } catch (\Exception $e) {
952
                throw new ParseException(sprintf('Schematron validation of configuration file "%s" failed: Transformation failed: %s', $document->documentURI, $e->getMessage()), 0, $e);
953
            }
954
            
955
            // validation ran okay, now we need to look at the result document to see if there are errors
956
            /** @var \DOMXPath $xpath */
957
            $xpath = $result->getXpath();
958
            $xpath->registerNamespace('svrl', self::NAMESPACE_SVRL_ISO);
959
            
960
            $results = $xpath->query('/svrl:schematron-output/svrl:failed-assert/svrl:text');
961
            if ($results->length) {
962
                $errors = array('Failed assertions:');
963
                
964
                foreach ($results as $result) {
965
                    $errors[] = $result->nodeValue;
966
                }
967
                
968
                $results = $xpath->query('/svrl:schematron-output/svrl:successful-report/svrl:text');
969
                if ($results->length) {
970
                    $errors[] = '';
971
                    $errors[] = 'Successful reports:';
972
                    foreach ($results as $result) {
973
                        $errors[] = $result->nodeValue;
974
                    }
975
                }
976
                
977
                throw new ParseException(sprintf('Schematron validation of configuration file "%s" failed:' . "\n\n%s", $document->documentURI, implode("\n", $errors)));
978
            }
979
        }
980
    }
981
}
982