1<?php
2/*
3** Zabbix
4** Copyright (C) 2001-2021 Zabbix SIA
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19**/
20
21
22class CXmlImportReader extends CImportReader {
23
24	/**
25	 * Convert string with xml data to php array.
26	 *
27	 * @throws Exception
28	 *
29	 * @param string $string
30	 *
31	 * @return array
32	 */
33	public function read($string) {
34		if ($string === '') {
35			throw new Exception(_s('Cannot read XML: %1$s.', _('XML is empty')));
36		}
37
38		libxml_use_internal_errors(true);
39		libxml_disable_entity_loader(true);
40		$result = simplexml_load_string($string, null, LIBXML_IMPORT_FLAGS);
41		if (!$result) {
42			$errors = libxml_get_errors();
43			libxml_clear_errors();
44
45			foreach ($errors as $error) {
46				throw new Exception(_s('Cannot read XML: %1$s.', _s('%1$s [Line: %2$s | Column: %3$s]',
47					'('.$error->code.') '.trim($error->message), $error->line, $error->column
48				)));
49			}
50		}
51
52		$xml = new XMLReader();
53		$xml->xml($string);
54		$data = $this->xml_to_array($xml);
55		$xml->close();
56		return $data;
57	}
58
59	/**
60	 * Method for recursive processing of xml dom nodes.
61	 *
62	 * @param XMLReader $xml
63	 * @param string    $path
64	 *
65	 * @return array|string
66	 */
67	protected function xml_to_array(XMLReader $xml, $path = '') {
68		$data = null;
69
70		while ($xml->read()) {
71			switch ($xml->nodeType) {
72				case XMLReader::ELEMENT:
73					if ($data === null) {
74						$data = [];
75					}
76					elseif (!is_array($data)) {
77						throw new Exception(_s('Invalid tag "%1$s": %2$s.', $path,
78							_s('unexpected text "%1$s"', trim($data))
79						));
80					}
81
82					$node_name = $xml->name;
83					$sub_path = $path.'/'.$node_name;
84					if (array_key_exists($node_name, $data)) {
85						$node_name .= count($data);
86						$sub_path .= '('.count($data).')';
87					}
88
89					/*
90					 * A special case for 1.8 import where attributes are still used attributes must be added to the
91					 * array as if they where child elements.
92					 */
93					if ($xml->hasAttributes) {
94						while ($xml->moveToNextAttribute()) {
95							$data[$node_name][$xml->name] = $xml->value;
96						}
97
98						// it make $this->isEmptyElement valid after processing attributes
99						$xml->moveToElement();
100
101						/*
102						 * We assume that an element with attributes always contains child elements, not a text node
103						 * works for 1.8 XML.
104						 */
105						if (!$xml->isEmptyElement) {
106							$child_data = $this->xml_to_array($xml, $sub_path);
107							if (is_array($child_data)) {
108								foreach ($child_data as $child_node_name => $child_node_value) {
109									if (array_key_exists($child_node_name, $data[$node_name])) {
110										$child_node_name .= count($data[$node_name]);
111									}
112									$data[$node_name][$child_node_name] = $child_node_value;
113								}
114							}
115							elseif ($child_data !== '') {
116								throw new Exception(_s('Invalid tag "%1$s": %2$s.', $sub_path,
117									_s('unexpected text "%1$s"', trim($child_data))
118								));
119							}
120						}
121					}
122					else {
123						$data[$node_name] = $xml->isEmptyElement ? '' : $this->xml_to_array($xml, $sub_path);
124					}
125					break;
126
127				case XMLReader::CDATA:
128					// falls through
129				case XMLReader::TEXT:
130					if ($data === null) {
131						$data = $xml->value;
132					}
133					elseif (is_array($data)) {
134						throw new Exception(_s('Invalid tag "%1$s": %2$s.', $path,
135							_s('unexpected text "%1$s"', trim($xml->value))
136						));
137					}
138
139					break;
140
141				case XMLReader::END_ELEMENT:
142					/*
143					 * For tags with empty value: <dns></dns>.
144					 */
145					if ($data === null) {
146						$data = '';
147					}
148
149					return $data;
150			}
151		}
152
153		return $data;
154	}
155}
156