AbstractXmlRetriever::recursiveRetrieve()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 16
c 1
b 0
f 0
nc 5
nop 5
dl 0
loc 28
ccs 17
cts 17
cp 1
crap 5
rs 9.4222
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Eclipxe\XmlResourceRetriever;
6
7
use DOMDocument;
8
use DOMElement;
9
use finfo;
10
use RuntimeException;
11
12
/**
13
 * This is an abstract implementation of Retriever interface when working with XML contents
14
 * Both, XsdRetriever and XsltRetriver depends on this class
15
 */
16
abstract class AbstractXmlRetriever extends AbstractBaseRetriever implements RetrieverInterface
17
{
18
    /**
19
     * Must return a string with the namespace to search for
20
     *
21
     * @return string
22
     */
23
    abstract protected function searchNamespace(): string;
24
25
    /**
26
     * Must return a table with rows (array of array)
27
     * every row must contain the keys element and attribute
28
     * "element" is the tag name to search for
29
     * "attribute" is the attribute name that contains the url
30
     *
31
     * @return array<array<string, string>>
32
     */
33
    abstract protected function searchElements(): array;
34
35 7
    public function retrieve(string $url): string
36
    {
37 7
        $this->clearHistory();
38 7
        return $this->doRetrieve($url);
39
    }
40
41
    /**
42
     * @param string $resource
43
     * @return string
44
     */
45 7
    private function doRetrieve(string $resource): string
46
    {
47 7
        $localFilename = $this->download($resource);
48 7
        $this->addToHistory($resource, $localFilename);
49
50 7
        $document = new DOMDocument();
51
        // this error silenced call is intentional,
52
        // don't need to change the value of libxml_use_internal_errors for this
53
        /** @noinspection PhpUsageOfSilenceOperatorInspection */
54 7
        $documentLoad = @$document->load($localFilename);
55 7
        if (false === $documentLoad) {
56 2
            unlink($localFilename);
57 2
            throw new RuntimeException("The source $resource contains errors");
58
        }
59
60
        // call recursive get searching on specified the elements
61 5
        $changed = false;
62 5
        foreach ($this->searchElements() as $search) {
63 5
            $recursiveRetrieve = $this->recursiveRetrieve(
64 5
                $document,
65 5
                $search['element'],
66 5
                $search['attribute'],
67 5
                $resource,
68 5
                $localFilename
69 5
            );
70 5
            if ($recursiveRetrieve) {
71 3
                $changed = true;
72
            }
73
        }
74
75 5
        if ($changed) {
76 3
            $document->save($localFilename);
77
        }
78 5
        return $localFilename;
79
    }
80
81 5
    private function recursiveRetrieve(
82
        DOMDocument $document,
83
        string $tagName,
84
        string $attributeName,
85
        string $currentUrl,
86
        string $currentFile
87
    ): bool {
88 5
        $modified = false;
89
        /** @var iterable<DOMElement> $elements */
90 5
        $elements = $document->getElementsByTagNameNS($this->searchNamespace(), $tagName);
91 5
        foreach ($elements as $element) {
92 3
            if (! $element->hasAttribute($attributeName)) {
93 1
                continue;
94
            }
95 3
            $location = $element->getAttribute($attributeName);
96 3
            if ('' === $location) {
97 1
                continue;
98
            }
99 3
            $location = $this->relativeToAbsoluteUrl($location, $currentUrl);
100 3
            if (array_key_exists($location, $this->retrieveHistory())) {
101 1
                continue;
102
            }
103 3
            $downloadedChild = $this->doRetrieve($location);
104 3
            $relative = Utils::relativePath($currentFile, $downloadedChild);
105 3
            $element->setAttribute($attributeName, $relative);
106 3
            $modified = true;
107
        }
108 5
        return $modified;
109
    }
110
111
    /**
112
     * This method checks if the recently downloaded file from $source located at $path
113
     * is a valid resource, if not will remove the file and throw an exception
114
     *
115
     * @param string $source
116
     * @param string $localpath
117
     * @return void
118
     * @throws RuntimeException when the source is not valid
119
     */
120 10
    protected function checkIsValidDownloadedFile(string $source, string $localpath): void
121
    {
122
        // check content is not empty
123 10
        if (0 === (int) filesize($localpath)) {
124 1
            unlink($localpath);
125 1
            throw new RuntimeException("The source $source is not an xml file because it is empty");
126
        }
127
        // check content is xml
128 9
        $mimetype = strval((new finfo())->file($localpath, FILEINFO_MIME_TYPE));
129 9
        if ('application/xml' !== $mimetype && 'text/' !== substr($mimetype, 0, 5)) {
130 1
            unlink($localpath);
131 1
            throw new RuntimeException("The source $source ($mimetype) is not an xml file");
132
        }
133
    }
134
135 3
    private function relativeToAbsoluteUrl(string $url, string $currentUrl): string
136
    {
137 3
        if (false !== $this->urlParts($url)) {
138 3
            return $url;
139
        }
140 1
        $currentParts = $this->urlParts($currentUrl);
141 1
        if (false === $currentParts) {
142
            $currentParts = [];
143
        }
144 1
        $currentParts['port'] = $currentParts['port'] ?? '';
145 1
        $currentParts['port'] = ('' !== $currentParts['port']) ? ':' . $currentParts['port'] : '';
146 1
        return implode('', [
147 1
            $currentParts['scheme'],
148 1
            '://',
149 1
            $currentParts['host'],
150 1
            $currentParts['port'],
151 1
            implode('/', Utils::simplifyPath(dirname($currentParts['path']) . '/' . $url)),
152 1
        ]);
153
    }
154
}
155