Passed
Push — master ( 1bf36a...3f521f )
by Pauli
02:26
created

XMLResponse::keyMayDefineAttribute()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 5
ccs 0
cts 4
cp 0
crap 6
rs 10
1
<?php
2
3
/**
4
 * ownCloud - Music app
5
 *
6
 * This file is licensed under the Affero General Public License version 3 or
7
 * later. See the COPYING file.
8
 *
9
 * @author Pauli Järvinen <[email protected]>
10
 * @copyright Pauli Järvinen 2019, 2020
11
 */
12
13
namespace OCA\Music\Http;
14
15
use OCP\AppFramework\Http\Response;
0 ignored issues
show
Bug introduced by
The type OCP\AppFramework\Http\Response was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
17
/**
18
 * This class creates an XML response out of a passed in associative array,
19
 * similarly how the class JSONResponse works. The content is described with
20
 * a recursive array structure, where arrays may have string or integer keys.
21
 * One array should not mix string and integer keys, that will lead to undefined
22
 * outcome. Furthermore, array with integer keys is supported only as payload of
23
 * an array with string keys.
24
 * 
25
 * Note that this response type has been created to fulfill the needs of the
26
 * SubsonicController and may not be suitable for all other purposes.
27
 */
28
class XMLResponse extends Response {
29
30
	private $content;
31
	private $doc;
32
	private $attributeKeys;
33
34
	/**
35
	 * @param array $content
36
	 * @param bool|string[] $attributes If true, then key-value pair is made into attribute if possible.
37
	 *                                  If false, then key-value pairs are never made into attributes.
38
	 *                                  If an array, then keys found from the array are made into attributes if possible.
39
	 */
40
	public function __construct(array $content, $attributes=true) {
41
		$this->addHeader('Content-Type', 'application/xml');
42
43
		// The content must have exactly one root element, add one if necessary
44
		if (\count($content) != 1) {
45
			$content = ['root' => $content];
46
		}
47
		$this->content = $content;
48
		$this->doc = new \DOMDocument();
49
		$this->doc->formatOutput = true;
50
		$this->attributeKeys = $attributes;
51
	}
52
53
	public function render() {
54
		$rootName = \array_keys($this->content)[0];
55
		$this->addChildElement($this->doc, $rootName, $this->content[$rootName]);
56
		return $this->doc->saveXML();
57
	}
58
59
	private function addChildElement($parentNode, $key, $value, $allowAttribute=true) {
60
		if (\is_bool($value)) {
61
			$value = $value ? 'true' : 'false';
62
		}
63
64
		if (\is_string($value) || \is_numeric($value)) {
65
			// key 'value' has a special meaning
66
			if ($key == 'value') {
67
				$parentNode->appendChild($this->doc->createTextNode($value));
68
			} elseif ($allowAttribute && $this->keyMayDefineAttribute($key)) {
69
				$parentNode->setAttribute($key, $value);
70
			} else {
71
				$child = $this->doc->createElement($key);
72
				$child->appendChild($this->doc->createTextNode($value));
73
				$parentNode->appendChild($child);
74
			}
75
		}
76
		elseif (\is_array($value)) {
77
			if (self::arrayIsIndexed($value)) {
78
				foreach ($value as $child) {
79
					$this->addChildElement($parentNode, $key, $child, /*allowAttribute=*/false);
80
				}
81
			}
82
			else { // associative array
83
				$element = $this->doc->createElement($key);
84
				$parentNode->appendChild($element);
85
				foreach ($value as $childKey => $childValue) {
86
					$this->addChildElement($element, $childKey, $childValue);
87
				}
88
			}
89
		}
90
		elseif ($value instanceof \stdClass) {
91
			// empty element
92
			$element = $this->doc->createElement($key);
93
			$parentNode->appendChild($element);
94
		}
95
		elseif ($value === null) {
96
			// skip
97
		}
98
		else {
99
			throw new \Exception("Unexpected value type for key $key");
100
		}
101
	}
102
103
	private function keyMayDefineAttribute($key) {
104
		if (\is_array($this->attributeKeys)) {
105
			return \in_array($key, $this->attributeKeys);
106
		} else {
107
			return \boolval($this->attributeKeys);
108
		}
109
	}
110
111
	/**
112
	 * Array is considered to be "indexed" if its first element has numerical key.
113
	 * Empty array is considered to be "indexed".
114
	 * @param array $array
115
	 */
116
	private static function arrayIsIndexed(array $array) {
117
		reset($array);
118
		return empty($array) || \is_int(key($array));
119
	}
120
}