Completed
Push — master ( 97c539...e502a6 )
by Michael
02:16
created

Validator::getXsdFileContents()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 33
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 33
ccs 0
cts 0
cp 0
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 28
nc 4
nop 1
crap 20
1
<?php
2
declare(strict_types = 1);
3
/**
4
 * Contains Validator class.
5
 *
6
 * PHP version 7.0+
7
 *
8
 * LICENSE:
9
 * This file is part of Yet Another Php Eve Api Library also know as Yapeal
10
 * which can be used to access the Eve Online API data and place it into a
11
 * database.
12
 * Copyright (C) 2015-2016 Michael Cummings
13
 *
14
 * This program is free software: you can redistribute it and/or modify it
15
 * under the terms of the GNU Lesser General Public License as published by the
16
 * Free Software Foundation, either version 3 of the License, or (at your
17
 * option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful, but WITHOUT
20
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
22
 * for more details.
23
 *
24
 * You should have received a copy of the GNU Lesser General Public License
25
 * along with this program. If not, see
26
 * <http://spdx.org/licenses/LGPL-3.0.html>.
27
 *
28
 * You should be able to find a copy of this license in the COPYING-LESSER.md
29
 * file. A copy of the GNU GPL should also be available in the COPYING.md file.
30
 *
31
 * @copyright 2015-2016 Michael Cummings
32
 * @license   http://www.gnu.org/copyleft/lesser.html GNU LGPL
33
 * @author    Michael Cummings <[email protected]>
34
 */
35
namespace Yapeal\Xsd;
36
37
use Yapeal\Event\EveApiEventEmitterTrait;
38
use Yapeal\Event\EveApiEventInterface;
39
use Yapeal\Event\MediatorInterface;
40
use Yapeal\Event\YEMAwareInterface;
41
use Yapeal\Exception\YapealFileSystemException;
42
use Yapeal\FileSystem\RelativeFileSearchTrait;
43
use Yapeal\FileSystem\SafeFileHandlingTrait;
44
use Yapeal\Log\Logger;
45
use Yapeal\Xml\EveApiReadWriteInterface;
46
use Yapeal\Xml\LibXmlChecksTrait;
47
48
/**
49
 * Class Validator
50
 */
51
class Validator implements ValidatorInterface, YEMAwareInterface
52
{
53
    use EveApiEventEmitterTrait;
54
    use LibXmlChecksTrait;
55
    use RelativeFileSearchTrait;
56
    use SafeFileHandlingTrait;
57
    /**
58
     * Constructor.
59
     *
60
     * @param string $dir Base directory where Eve API XSD files can be found.
61
     */
62
    public function __construct(string $dir = __DIR__)
63
    {
64
        $this->setRelativeBaseDir($dir . '/');
65
    }
66
    /**
67
     * @param EveApiEventInterface $event
68
     * @param string               $eventName
69
     * @param MediatorInterface    $yem
70
     *
71
     * @return EveApiEventInterface
72
     * @throws \DomainException
73
     * @throws \InvalidArgumentException
74
     * @throws \LogicException
75
     * @throws \UnexpectedValueException
76
     */
77
    public function validateEveApi(
78
        EveApiEventInterface $event,
79
        string $eventName,
80
        MediatorInterface $yem
81
    ): EveApiEventInterface {
82
        $this->setYem($yem);
83
        $data = $event->getData();
84
        $yem->triggerLogEvent('Yapeal.Log.log',
85
            Logger::DEBUG,
86
            $this->getReceivedEventMessage($data, $eventName, __CLASS__));
87
        if ('' === $xml = $data->getEveApiXml()) {
88
            $messagePrefix = 'Given empty XML during the validation of';
89
            $yem->triggerLogEvent('Yapeal.Log.log', Logger::NOTICE, $this->createEveApiMessage($messagePrefix, $data));
90
            return $event;
91
        }
92
        if (false !== strpos($xml, '<!DOCTYPE html')) {
93
            $messagePrefix = 'Received HTML error doc instead of XML data during the validation of';
94
            $yem->triggerLogEvent('Yapeal.Log.log', Logger::NOTICE, $this->createEveApiMessage($messagePrefix, $data));
95
            // Cache received error html.
96
            $apiName = $data->getEveApiName();
97
            $data->setEveApiName('Invalid_' . $apiName);
98
            $this->emitEvents($data, 'preserve', 'Yapeal.Xml.Error');
99
            $data->setEveApiName($apiName);
100
            return $event;
101
        }
102
        if (false === $xsdContents = $this->getXsdFileContents($data)) {
103
            return $event;
104
        }
105
        libxml_clear_errors();
106
        libxml_use_internal_errors(true);
107
        $dom = $this->getDom();
108
        if (false === $dom->loadXML($xml)) {
109
            $messagePrefix = 'DOM could not load XML during the validation of';
110
            $yem->triggerLogEvent('Yapeal.Log.log', Logger::WARNING, $this->createEveApiMessage($messagePrefix, $data));
111
            $this->checkLibXmlErrors($data, $yem);
112
            libxml_use_internal_errors(false);
113
            return $event;
114
        }
115
        if (false === $dom->schemaValidateSource($xsdContents)) {
116
            $messagePrefix = 'DOM schema could not validate XML during the validation of';
117
            $yem->triggerLogEvent('Yapeal.Log.log', Logger::WARNING, $this->createEveApiMessage($messagePrefix, $data));
118
            // Cache error causing XML.
119
            $apiName = $data->getEveApiName();
120
            $data->setEveApiName('Invalid_' . $apiName);
121
            $this->emitEvents($data, 'preserve', 'Yapeal.Xml.Error');
122
            $data->setEveApiName($apiName);
123
            $this->checkLibXmlErrors($data, $yem);
124
            libxml_use_internal_errors(false);
125
            return $event;
126
        }
127
        if (false === $this->checkForValidDateTimes($dom, $data)) {
128
            return $event;
129
        }
130
        libxml_use_internal_errors(false);
131
        $messagePrefix = 'Successfully validated the XML during the validation of';
132
        $yem->triggerLogEvent('Yapeal.Log.log', Logger::INFO, $this->createEveApiMessage($messagePrefix, $data));
133
        // Check for XML error element.
134
        if (false !== strpos($data->getEveApiXml(), '<error ')) {
135
            $this->emitEvents($data, 'start', 'Yapeal.Xml.Error');
136
            return $event;
137
        }
138
        return $event->setHandledSufficiently();
139
    }
140
    /**
141
     * @param \DOMDocument             $dom
142
     * @param EveApiReadWriteInterface $data
143
     *
144
     * @return bool
145
     * @throws \DomainException
146
     * @throws \InvalidArgumentException
147
     * @throws \LogicException
148
     * @throws \UnexpectedValueException
149
     */
150
    private function checkForValidDateTimes(\DOMDocument $dom, EveApiReadWriteInterface $data)
151
    {
152
        libxml_clear_errors();
153
        libxml_use_internal_errors(true);
154
        if (false === $simple = simplexml_import_dom($dom)) {
155
            $messagePrefix = 'SimpleXMLElement could not import DOM during the validation of';
156
            $this->getYem()
157
                ->triggerLogEvent('Yapeal.Log.log', Logger::WARNING, $this->createEveApiMessage($messagePrefix, $data));
158
            $this->checkLibXmlErrors($data, $this->getYem());
159
            libxml_use_internal_errors(false);
160
            return false;
161
        }
162
        $eveFormat = 'Y-m-d H:i:sP';
163
        if (false === $current = \DateTimeImmutable::createFromFormat($eveFormat, $simple->currentTime[0] . '+00:00')) {
164
            $messagePrefix = 'Failed to get DateTime instance for currentTime during the retrieval of';
165
            $this->getYem()
166
                ->triggerLogEvent('Yapeal.Log.log', Logger::ERROR, $this->createEveApiMessage($messagePrefix, $data));
167
            return false;
168
        }
169
        if (false === $until = \DateTimeImmutable::createFromFormat($eveFormat, $simple->cachedUntil[0] . '+00:00')) {
170
            $messagePrefix = 'Failed to get DateTime instance for cachedUntil during the retrieval of';
171
            $this->getYem()
172
                ->triggerLogEvent('Yapeal.Log.log', Logger::ERROR, $this->createEveApiMessage($messagePrefix, $data));
173
            return false;
174
        }
175
        if ($until <= $current) {
176
            $messagePrefix = sprintf('CachedUntil is invalid was given %s and currentTime is %s during the validation of',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
177
                $until->format($eveFormat),
178
                $current->format($eveFormat));
179
            $this->getYem()
180
                ->triggerLogEvent('Yapeal.Log.log', Logger::WARNING, $this->createEveApiMessage($messagePrefix, $data));
181
            $mess = 'Please report the above logged error to CCP so they can fixed it';
182
            $this->getYem()
183
                ->triggerLogEvent('Yapeal.Log.log', Logger::WARNING, $mess);
184
            return false;
185
        }
186
        // Current plus a day.
187
        if ($until > $current->add(new \DateInterval('P1D'))) {
188
            $messagePrefix = sprintf('CachedUntil is excessively long was given %s and it is currently %s during the'
189
                . ' validation of',
190
                $until->format($eveFormat),
191
                $current->format($eveFormat));
192
            $this->getYem()
193
                ->triggerLogEvent('Yapeal.Log.log', Logger::NOTICE, $this->createEveApiMessage($messagePrefix, $data));
194
            return false;
195
        }
196
        return true;
197
    }
198
    /**
199
     * @return \DOMDocument
200
     */
201
    private function getDom(): \DOMDocument
202
    {
203
        if (null === $this->dom) {
204
            $this->dom = new \DOMDocument();
205
        }
206
        return $this->dom;
207
    }
208
    /**
209
     * @param EveApiReadWriteInterface $data
210
     *
211
     * @return false|string
212
     * @throws \DomainException
213
     * @throws \InvalidArgumentException
214
     * @throws \LogicException
215
     * @throws \UnexpectedValueException
216
     */
217
    private function getXsdFileContents(EveApiReadWriteInterface $data)
218
    {
219
        try {
220
            $xsdFile = $this->findRelativeFileWithPath(ucfirst($data->getEveApiSectionName()),
221
                $data->getEveApiName(),
222
                'xsl');
223
        } catch (YapealFileSystemException $exc) {
224
            $messagePrefix = 'Failed to find accessible XSD file during the validation of';
225
            $this->getYem()
226
                ->triggerLogEvent('Yapeal.Log.log',
227
                    Logger::NOTICE,
228
                    $this->createEveApiMessage($messagePrefix, $data),
229
                    ['exception' => $exc]);
230
            return false;
231
        }
232
        $contents = $this->safeFileRead($xsdFile);
233
        if (false === $contents) {
234
            $messagePrefix = sprintf('Failed to read XSD file %s during the validation of', $xsdFile);
235
            $this->getYem()
236
                ->triggerLogEvent('Yapeal.Log.log', Logger::NOTICE, $this->createEveApiMessage($messagePrefix, $data));
237
            return false;
238
        }
239
        if ('' === $contents) {
240
            $messagePrefix = sprintf('Received an empty XSD file %s during the validation of', $xsdFile);
241
            $this->getYem()
242
                ->triggerLogEvent('Yapeal.Log.log', Logger::NOTICE, $this->createEveApiMessage($messagePrefix, $data));
243
            return false;
244
        }
245
        $messagePrefix = sprintf('Using XSD file %s during the validation of', $xsdFile);
246
        $this->getYem()
247
            ->triggerLogEvent('Yapeal.Log.log', Logger::DEBUG, $this->createEveApiMessage($messagePrefix, $data));
248
        return $contents;
249
    }
250
    /**
251
     * @var \DOMDocument $dom
252
     */
253
    private $dom;
254
}
255