1<?php
2
3namespace Sabre\DAV\Xml\Element;
4
5use Sabre\DAV\Xml\Property\Complex;
6use Sabre\Xml\Reader;
7use Sabre\Xml\XmlDeserializable;
8
9/**
10 * This class is responsible for decoding the {DAV:}prop element as it appears
11 * in {DAV:}property-update.
12 *
13 * This class doesn't return an instance of itself. It just returns a
14 * key->value array.
15 *
16 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
17 * @author Evert Pot (http://evertpot.com/)
18 * @license http://sabre.io/license/ Modified BSD License
19 */
20class Prop implements XmlDeserializable {
21
22    /**
23     * The deserialize method is called during xml parsing.
24     *
25     * This method is called statically, this is because in theory this method
26     * may be used as a type of constructor, or factory method.
27     *
28     * Often you want to return an instance of the current class, but you are
29     * free to return other data as well.
30     *
31     * You are responsible for advancing the reader to the next element. Not
32     * doing anything will result in a never-ending loop.
33     *
34     * If you just want to skip parsing for this element altogether, you can
35     * just call $reader->next();
36     *
37     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
38     * the next element.
39     *
40     * @param Reader $reader
41     * @return mixed
42     */
43    static function xmlDeserialize(Reader $reader) {
44
45        // If there's no children, we don't do anything.
46        if ($reader->isEmptyElement) {
47            $reader->next();
48            return [];
49        }
50
51        $values = [];
52
53        $reader->read();
54        do {
55
56            if ($reader->nodeType === Reader::ELEMENT) {
57
58                $clark = $reader->getClark();
59                $values[$clark] = self::parseCurrentElement($reader)['value'];
60
61            } else {
62                $reader->read();
63            }
64
65        } while ($reader->nodeType !== Reader::END_ELEMENT);
66
67        $reader->read();
68
69        return $values;
70
71    }
72
73    /**
74     * This function behaves similar to Sabre\Xml\Reader::parseCurrentElement,
75     * but instead of creating deep xml array structures, it will turn any
76     * top-level element it doesn't recognize into either a string, or an
77     * XmlFragment class.
78     *
79     * This method returns arn array with 2 properties:
80     *   * name - A clark-notation XML element name.
81     *   * value - The parsed value.
82     *
83     * @param Reader $reader
84     * @return array
85     */
86    private static function parseCurrentElement(Reader $reader) {
87
88        $name = $reader->getClark();
89
90        if (array_key_exists($name, $reader->elementMap)) {
91            $deserializer = $reader->elementMap[$name];
92            if (is_subclass_of($deserializer, 'Sabre\\Xml\\XmlDeserializable')) {
93                $value = call_user_func([$deserializer, 'xmlDeserialize'], $reader);
94            } elseif (is_callable($deserializer)) {
95                $value = call_user_func($deserializer, $reader);
96            } else {
97                $type = gettype($deserializer);
98                if ($type === 'string') {
99                    $type .= ' (' . $deserializer . ')';
100                } elseif ($type === 'object') {
101                    $type .= ' (' . get_class($deserializer) . ')';
102                }
103                throw new \LogicException('Could not use this type as a deserializer: ' . $type);
104            }
105        } else {
106            $value = Complex::xmlDeserialize($reader);
107        }
108
109        return [
110            'name'  => $name,
111            'value' => $value,
112        ];
113
114    }
115
116}
117