PluginProbe
Advanced Ads – Ad Manager & AdSense / 1.40.1
Advanced Ads – Ad Manager & AdSense v1.40.1
2.0.26 2.0.25 2.0.24 2.0.23 2.0.22 2.0.21 1.38.0 1.39.0 1.39.1 1.39.2 1.39.3 1.39.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.40.0 1.40.1 1.40.2 All 348 releases
advanced-ads / modules / import-export / classes / XmlEncoder.php
XmlEncoder.php
347 lines 11.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Encodes XML data.
4 *
5 * Based on code from the Symfony package
6 *
7 * Copyright (c) 2004-2016 Fabien Potencier <fabien@symfony.com>
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining a copy
10 * of this software and associated documentation files (the "Software"), to deal
11 * in the Software without restriction, including without limitation the rights
12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 * copies of the Software, and to permit persons to whom the Software is furnished
14 * to do so, subject to the following conditions:
15
16 * The above copyright notice and this permission notice shall be included in all
17 * copies or substantial portions of the Software.
18
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 * THE SOFTWARE.
26 *
27 * @author Jordi Boggiano <j.boggiano@seld.be>
28 * @author John Wards <jwards@whiteoctober.co.uk>
29 * @author Fabian Vogler <fabian@equivalence.ch>
30 * @author Kévin Dunglas <dunglas@gmail.com>
31 */
32 class Advanced_Ads_XmlEncoder
33 {
34 /**
35 * @var DOMDocument
36 */
37 private $dom;
38
39 /**
40 * @var Advanced_Ads_XmlEncoder
41 */
42 private static $instance;
43
44 private function __construct() {}
45
46 /**
47 * @return Advanced_Ads_XmlEncoder
48 */
49 public static function get_instance()
50 {
51 if ( ! isset(self::$instance) ) {
52 self::$instance = new self;
53 }
54
55 return self::$instance;
56 }
57
58
59 public function encode( $data, $options = []) {
60 if ( ! extension_loaded( 'simplexml' ) ) {
61 throw new Exception( sprintf( __( 'The %s extension(s) is not loaded', 'advanced-ads' ), 'simplexml' ) );
62 }
63 if ( ! extension_loaded( 'dom' ) ) {
64 throw new Exception( sprintf( __( 'The %s extension(s) is not loaded', 'advanced-ads' ), 'dom' ) );
65 }
66
67 $this->dom = new DOMDocument();
68 $this->dom->preserveWhiteSpace = false;
69 $this->dom->formatOutput = true;
70 if (isset($options['encoding'])) {
71 $this->dom->encoding = $options['encoding'];
72 }
73
74 if ( ! is_array($data) ) {
75 throw new UnexpectedValueException( _x( 'The data must be an array', 'import_export', 'advanced-ads' ) );
76 }
77
78 if (isset($options['skip_root'])) {
79 $this->buildXml($this->dom, $data );
80 } else {
81 // create root <advads-export> tag
82 $root = $this->dom->createElement('advads-export');
83 $this->dom->appendChild($root);
84 $this->buildXml($root, $data );
85 }
86
87
88 return $this->dom->saveXML();
89 }
90
91 /**
92 * Parse the data and convert it to DOMElements.
93 */
94 private function buildXml(DOMNode $parentNode, $data ) {
95 $append = true;
96
97 foreach ($data as $key => $data) {
98 if (is_numeric($key) ) {
99 $append = $this->appendNode($parentNode, $data, 'item', $key);
100 } elseif ( $this->isElementNameValid($key) ) {
101 $append = $this->appendNode($parentNode, $data, $key);
102 }
103 }
104
105 return $append;
106 }
107
108
109 /**
110 * Selects the type of node to create and appends it to the parent.
111 *
112 * @param DOMNode $parentNode
113 * @param array|object $data
114 * @param string $nodeName
115 * @param string $key
116 *
117 * @return bool
118 */
119 private function appendNode(DOMNode $parentNode, $data, $nodeName, $key = null) {
120 $node = $this->dom->createElement($nodeName);
121
122 if (null !== $key) {
123 $node->setAttribute('key', $key);
124 }
125
126 $appendNode = false;
127 if (is_array($data)) {
128 $node->setAttribute('type', 'array' );
129 $appendNode = $this->buildXml($node, $data);
130 } elseif (is_numeric($data)) {
131 $node->setAttribute('type', is_string( $data) ? 'string' : 'numeric' );
132 $appendNode = $this->appendText($node, (string) $data);
133 } elseif (is_string($data)) {
134 $node->setAttribute('type', 'string');
135 $appendNode = $this->needsCdataWrapping($data) ? $this->appendCData($node, $data) : $this->appendText($node, $data);
136 } elseif (is_bool($data)) {
137 $node->setAttribute('type', 'boolean');
138 $appendNode = $this->appendText($node, (int) $data);
139 } elseif (is_null($data)) {
140 $node->setAttribute('type', 'null');
141 $appendNode = $this->appendText($node, '');
142 }
143
144 if ($appendNode) {
145 $parentNode->appendChild($node);
146 } else {
147 throw new UnexpectedValueException( sprintf( _x( 'An unexpected value could not be serialized: %s', 'import_export', 'advanced-ads' ), var_export($data, true) ) );
148 }
149
150 return $appendNode;
151 }
152
153 final protected function appendText(DOMNode $node, $val) {
154 $nodeText = $this->dom->createTextNode($val);
155 $node->appendChild($nodeText);
156
157 return true;
158 }
159
160 final protected function appendCData(DOMNode $node, $val) {
161 $nodeText = $this->dom->createCDATASection($val);
162 $node->appendChild($nodeText);
163
164 return true;
165 }
166
167 /**
168 * Checks if a value contains any characters which would require CDATA wrapping.
169 *
170 * @param string $val
171 *
172 * @return bool
173 */
174 private function needsCdataWrapping($val) {
175 return preg_match('/[<>&]/', $val);
176 }
177
178 /**
179 * Checks the name is a valid xml element name.
180 *
181 * @param string $name
182 *
183 * @return bool
184 */
185 final protected function isElementNameValid($name) {
186 return $name && false === strpos($name, ' ') && preg_match('#^[\pL_][\pL0-9._:-]*$#ui', $name);
187 }
188
189 /**
190 * Decode XML data.
191 *
192 * @throws Exception If an extension is lt loaded.
193 * @throws UnexpectedValueException If XML data is invalid.
194 *
195 * @param string $data XML data.
196 * @return array Decoded XML data.
197 */
198 public function decode( $data ) {
199 if ( ! extension_loaded( 'simplexml' ) ) {
200 /* translators: %s: A name of not loaded extension. */
201 throw new Exception( sprintf( __( 'The %s extension(s) is not loaded', 'advanced-ads' ), 'simplexml' ) );
202 }
203 if ( ! extension_loaded( 'dom' ) ) {
204 /* translators: %s: A name of not loaded extension. */
205 throw new Exception( sprintf( __( 'The %s extension(s) is not loaded', 'advanced-ads' ), 'dom' ) );
206 }
207
208
209 if ('' === trim($data)) {
210 throw new UnexpectedValueException( _x( 'Invalid XML data, it can not be empty', 'import_export', 'advanced-ads' ) );
211 }
212
213 $internal_errors = libxml_use_internal_errors( true );
214
215 if ( LIBXML_VERSION < 20900 ) {
216 // The `libxml_disable_entity_loading` function has been deprecated in PHP 8.0 because in
217 // libxml >= 2.9.0 (that is required by PHP 8), external entity loading is disabled by default,
218 // so this function is no longer needed to protect against XXE attacks.
219 // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.libxml_disable_entity_loaderDeprecated
220 $disable_entities = libxml_disable_entity_loader( true );
221 }
222
223 libxml_clear_errors();
224
225 $dom = new DOMDocument();
226
227 if ( strpos( $data, '<advads-export>' ) === false ) {
228 $data = preg_replace('/^<\?xml.*?\?>/', '', $data );
229 $data = '<advads-export>' . $data . '</advads-export>';
230 }
231
232 $dom->loadXML($data, LIBXML_NONET | LIBXML_NOBLANKS);
233
234 libxml_use_internal_errors( $internal_errors );
235
236 if ( LIBXML_VERSION < 20900 ) {
237 // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.libxml_disable_entity_loaderDeprecated -- see L215ff. for an explanation
238 libxml_disable_entity_loader( $disable_entities );
239 }
240
241 if ($error = libxml_get_last_error()) {
242 libxml_clear_errors();
243
244 throw new UnexpectedValueException( sprintf( _x( 'XML error: %s', 'import_export', 'advanced-ads' ), $error->message ) );
245
246 }
247
248 // <advads-export>
249 $rootNode = $dom->firstChild;
250
251 if ($rootNode->hasChildNodes()) {
252 return $this->parseXml($rootNode);
253 }
254 }
255
256 /**
257 * Parse the input DOMNode into an array or a string.
258 *
259 * @param DOMNode $node xml to parse
260 *
261 * @return array|string
262 */
263 private function parseXml(DOMNode $node) {
264 // Parse the input DOMNode value (content and children) into an array or a string
265 $data = [];
266 if ( $node->hasAttributes() ) {
267 foreach ($node->attributes as $attr) {
268 if (ctype_digit($attr->nodeValue)) {
269 $data['@'.$attr->nodeName] = (int) $attr->nodeValue;
270 } else {
271 $data['@'.$attr->nodeName] = $attr->nodeValue;
272 }
273 }
274 }
275
276 $text_type = isset($data['@type']) ? $data['@type'] : null;
277 unset( $data['@type'] );
278
279 // Parse the input DOMNode value (content and children) into an array or a string.
280 if (!$node->hasChildNodes()) {
281 $value = $node->nodeValue;
282 } elseif (1 === $node->childNodes->length && in_array($node->firstChild->nodeType, [XML_TEXT_NODE, XML_CDATA_SECTION_NODE])) {
283 $value = $node->firstChild->nodeValue;
284 } else {
285
286
287 $value = [];
288
289 foreach ($node->childNodes as $subnode) {
290 $val = $this->parseXml($subnode);
291
292 if ('item' === $subnode->nodeName && is_array($val) && isset($val['@key'])) {
293 $a = $val['@key'];
294 if (isset($val['#'])) {
295 $value[$a] = $val['#'] !== 'null' ? $val['#'] : null;
296 } else {
297 $value[$a] = $val !== 'null' ? $val : null;
298 }
299
300 } else {
301 $value[$subnode->nodeName][] = $val === 'null' ? null : $val;
302 }
303 }
304 foreach ($value as $key => $val) {
305 if (is_array($val) && 1 === count($val)) {
306 $value[$key] = current($val);
307 } else if ( is_array( $value[$key] ) && isset( $value[$key]['@key'] ) ) {
308 unset( $value[$key]['@key'] );
309 }
310 }
311 }
312
313 if (!count($data)) {
314 $value = $this->changeType( $value, $text_type );
315 return $value;
316 }
317
318 if (!is_array($value)) {
319 $value = $this->changeType( $value, $text_type );
320 $data['#'] = $value;
321 return $data;
322 }
323
324 if (1 === count($value) && key($value)) {
325 $data[key($value)] = current($value);
326
327 return $data;
328 }
329
330 foreach ($value as $key => $val) {
331 $data[$key] = $val;
332 }
333
334 return $data;
335 }
336
337 private function changeType( $text, $type ) {
338 if ( $type === 'string' ) return (string) $text;
339 if ( $type === 'numeric' ) return 0 + $text;
340 if ( $type === 'boolean' ) return (boolean) $text;
341 if ( $type === 'array' && $text=== '' ) return [];
342 if ( $type === 'null' ) return 'null';
343 return $text;
344 }
345
346 }
347