Completed
Push — master ( b659c5...c65770 )
by Michael
10:22
created

Transformer   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 249
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 95.83%

Importance

Changes 0
Metric Value
wmc 21
lcom 1
cbo 7
dl 0
loc 249
ccs 115
cts 120
cp 0.9583
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A transformEveApi() 0 20 2
B addYapealProcessingInstructionToXml() 0 25 3
B getStyleSheetInstance() 0 51 6
B getXmlInstance() 0 33 3
A getXslt() 0 7 2
B performTransform() 0 23 4
1
<?php
2
declare(strict_types = 1);
3
/**
4
 * Contains Transformer 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-2017 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-2017 Michael Cummings
32
 * @license   LGPL-3.0+
33
 * @author    Michael Cummings <[email protected]>
34
 */
35
namespace Yapeal\Xsl;
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 Transformer
50
 */
51
class Transformer implements TransformerInterface, YEMAwareInterface
52
{
53
    use EveApiEventEmitterTrait;
54
    use LibXmlChecksTrait;
55
    use RelativeFileSearchTrait;
56
    use SafeFileHandlingTrait;
57
    /**
58
     * Transformer Constructor.
59
     *
60
     * @param string $dir Base directory where Eve API XSL files can be found.
61
     */
62 13
    public function __construct(string $dir = __DIR__)
63
    {
64 13
        $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 12
    public function transformEveApi(EveApiEventInterface $event, string $eventName, MediatorInterface $yem)
78
    {
79 12
        $this->setYem($yem);
80 12
        $data = $event->getData();
81 12
        $yem->triggerLogEvent('Yapeal.Log.log',
82 12
            Logger::DEBUG,
83 12
            $this->getReceivedEventMessage($data, $eventName, __CLASS__));
84
        // Pretty up the XML to make other processing easier.
85 12
        $data->setEveApiXml(tidy_repair_string($data->getEveApiXml(), $this->tidyConfig, 'utf8'));
86 12
        $xml = $this->addYapealProcessingInstructionToXml($data)
87 12
            ->performTransform($data);
88 12
        if (false === $xml) {
89 7
            return $event;
90
        }
91
        // Pretty up the transformed XML.
92 5
        $data->setEveApiXml(tidy_repair_string($xml, $this->tidyConfig, 'utf8'));
93 5
        $messagePrefix = 'Successfully transformed the XML of';
94 5
        $yem->triggerLogEvent('Yapeal.Log.log', Logger::INFO, $this->createEveApiMessage($messagePrefix, $data));
95 5
        return $event->setHandledSufficiently();
96
    }
97
    /**
98
     * Adds Processing Instruction to XML containing json encoding of any post used during retrieve.
99
     *
100
     * NOTE: This use to be done directly in the network retriever but felt modifying the XML like that belonged in
101
     * transform instead.
102
     *
103
     * @param EveApiReadWriteInterface $data
104
     *
105
     * @return self Fluent interface.
106
     * @throws \DomainException
107
     * @throws \InvalidArgumentException
108
     * @throws \LogicException
109
     * @throws \UnexpectedValueException
110
     */
111 12
    private function addYapealProcessingInstructionToXml(EveApiReadWriteInterface $data): self
112
    {
113 12
        $xml = $data->getEveApiXml();
114 12
        if ('' === $xml) {
115 1
            return $this;
116
        }
117 11
        $arguments = $data->getEveApiArguments();
118
        // Include only partial vCode for security.
119 11
        if (!empty($arguments['vCode'])) {
120
            $arguments['vCode'] = substr($arguments['vCode'], 0, min(8, strlen($arguments['vCode']) - 1)) . '...';
121
        }
122
        // Remove arguments that never need to be included.
123 11
        unset($arguments['mask'], $arguments['rowCount']);
124 11
        ksort($arguments);
125
        /*
126
         * Ignoring untestable edge case of json_encode returning false. It would require $data to be broken in
127
         * some way that also breaks json_encode.
128
         */
129 11
        $json = json_encode($arguments);
130 11
        $xml = str_ireplace("=\"utf-8\"?>\n<eveapi",
131 11
            "=\"utf-8\"?>\n<?yapeal.parameters.json " . $json . "?>\n<eveapi",
132
            $xml);
133 11
        $data->setEveApiXml(tidy_repair_string($xml, $this->tidyConfig, 'utf8'));
134 11
        return $this;
135
    }
136
    /**
137
     * Handles loading, verifying, and return the correct XSL style sheet as a SimpleXMLElement instance.
138
     *
139
     * @param EveApiReadWriteInterface $data
140
     *
141
     * @return \SimpleXMLElement|false
142
     * @throws \DomainException
143
     * @throws \InvalidArgumentException
144
     * @throws \LogicException
145
     * @throws \UnexpectedValueException
146
     */
147 12
    private function getStyleSheetInstance(EveApiReadWriteInterface $data)
148
    {
149
        try {
150 12
            $xslFile = $this->findRelativeFileWithPath(ucfirst($data->getEveApiSectionName()),
151 12
                $data->getEveApiName(),
152 12
                'xsl');
153 2
        } catch (YapealFileSystemException $exc) {
154 2
            $messagePrefix = 'Failed to find accessible XSL file during the transform of';
155 2
            $this->getYem()
156 2
                ->triggerLogEvent('Yapeal.Log.log',
157 2
                    Logger::WARNING,
158 2
                    $this->createEveApiMessage($messagePrefix, $data),
159 2
                    ['exception' => $exc]);
160 2
            return false;
161
        }
162 10
        $styleSheet = $this->safeFileRead($xslFile);
163 10
        if (false === $styleSheet) {
164
            $messagePrefix = sprintf('Failed to read XSL file %s during the transform of', $xslFile);
165
            $this->getYem()
166
                ->triggerLogEvent('Yapeal.Log.log', Logger::WARNING, $this->createEveApiMessage($messagePrefix, $data));
167
            return false;
168
        }
169 10
        if ('' === $styleSheet) {
170 1
            $messagePrefix = sprintf('Received an empty XSL file %s during the transform of', $xslFile);
171 1
            $this->getYem()
172 1
                ->triggerLogEvent('Yapeal.Log.log', Logger::WARNING, $this->createEveApiMessage($messagePrefix, $data));
173 1
            return false;
174
        }
175 9
        libxml_clear_errors();
176 9
        libxml_use_internal_errors(true);
177 9
        $instance = false;
178
        try {
179 9
            $instance = new \SimpleXMLElement($styleSheet);
180 1
        } catch (\Exception $exc) {
181 1
            $messagePrefix = sprintf('SimpleXMLElement exception caused by XSL file %s during the transform of',
182
                $xslFile);
183 1
            $this->getYem()
184 1
                ->triggerLogEvent('Yapeal.Log.log',
185 1
                    Logger::WARNING,
186 1
                    $this->createEveApiMessage($messagePrefix, $data),
187 1
                    ['exception' => $exc]);
188 1
            $this->checkLibXmlErrors($data, $this->getYem());
189
        }
190 9
        libxml_use_internal_errors(false);
191 9
        if (false !== $instance) {
192 8
            $messagePrefix = sprintf('Using XSL file %s during the transform of', $xslFile);
193 8
            $this->getYem()
194 8
                ->triggerLogEvent('Yapeal.Log.log', Logger::INFO, $this->createEveApiMessage($messagePrefix, $data));
195
        }
196 9
        return $instance;
197
    }
198
    /**
199
     * @param EveApiReadWriteInterface $data
200
     *
201
     * @return false|\SimpleXMLElement
202
     * @throws \DomainException
203
     * @throws \InvalidArgumentException
204
     * @throws \LogicException
205
     * @throws \UnexpectedValueException
206
     */
207 8
    private function getXmlInstance(EveApiReadWriteInterface $data)
208
    {
209 8
        $xml = $data->getEveApiXml();
210 8
        if ('' === $xml) {
211 1
            $messagePrefix = 'Given empty XML during the transform of';
212 1
            $this->getYem()
213 1
                ->triggerLogEvent('Yapeal.Log.log', Logger::WARNING, $this->createEveApiMessage($messagePrefix, $data));
214 1
            return false;
215
        }
216 7
        libxml_clear_errors();
217 7
        libxml_use_internal_errors(true);
218 7
        $instance = false;
219
        try {
220 7
            $instance = new \SimpleXMLElement($xml);
221 1
        } catch (\Exception $exc) {
222 1
            $messagePrefix = 'The XML cause SimpleXMLElement exception during the transform of';
223 1
            $this->getYem()
224 1
                ->triggerLogEvent('Yapeal.Log.log',
225 1
                    Logger::WARNING,
226 1
                    $this->createEveApiMessage($messagePrefix, $data),
227 1
                    ['exception' => $exc]);
228
            // Cache error causing XML.
229 1
            $apiName = $data->getEveApiName();
230 1
            $data->setEveApiName('Untransformed_' . $apiName);
231 1
            $this->emitEvents($data, 'preserve', 'Yapeal.Xml.Error');
232 1
            $data->setEveApiName($apiName);
233
            // Empty invalid XML since it is not usable.
234 1
            $data->setEveApiXml('');
235 1
            $this->checkLibXmlErrors($data, $this->getYem());
236
        }
237 7
        libxml_use_internal_errors(false);
238 7
        return $instance;
239
    }
240
    /**
241
     * @return \XSLTProcessor
242
     */
243 6
    private function getXslt(): \XSLTProcessor
244
    {
245 6
        if (null === $this->xslt) {
246 6
            $this->xslt = new \XSLTProcessor();
247
        }
248 6
        return $this->xslt;
249
    }
250
    /**
251
     * Does actual XSL transform on the Eve API XML.
252
     *
253
     * @param EveApiReadWriteInterface $data
254
     *
255
     * @return string|false
256
     * @throws \DomainException
257
     * @throws \InvalidArgumentException
258
     * @throws \LogicException
259
     * @throws \UnexpectedValueException
260
     */
261 12
    private function performTransform(EveApiReadWriteInterface $data)
262
    {
263 12
        if (false === $styleInstance = $this->getStyleSheetInstance($data)) {
264 4
            return false;
265
        }
266 8
        if (false === $xmlInstance = $this->getXmlInstance($data)) {
267 2
            return false;
268
        }
269 6
        libxml_clear_errors();
270 6
        libxml_use_internal_errors(true);
271 6
        $xslt = $this->getXslt();
272 6
        if (false === $xslt->importStylesheet($styleInstance)) {
273 1
            $messagePrefix = 'XSLT could not import style sheet during the transform of';
274 1
            $this->getYem()
275 1
                ->triggerLogEvent('Yapeal.Log.log', Logger::WARNING, $this->createEveApiMessage($messagePrefix, $data));
276 1
            $this->checkLibXmlErrors($data, $this->getYem());
277 1
            libxml_use_internal_errors(false);
278 1
            return false;
279
        }
280 5
        $xml = $xslt->transformToXml($xmlInstance);
281 5
        libxml_use_internal_errors(false);
282 5
        return $xml;
283
    }
284
    /**
285
     * @var array $tidyConfig
286
     */
287
    private $tidyConfig = [
288
        'indent' => true,
289
        'indent-spaces' => 4,
290
        'input-xml' => true,
291
        'newline' => 'LF',
292
        'output-xml' => true,
293
        'wrap' => '250'
294
    ];
295
    /**
296
     * @var \XSLTProcessor $xslt
297
     */
298
    private $xslt;
299
}
300