Completed
Push — master ( a8fd9c...7572da )
by Günter
10s
created

AbstractFrame::realxpath()   D

Complexity

Conditions 9
Paths 17

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 4.909
c 0
b 0
f 0
cc 9
eloc 15
nc 17
nop 1
1
<?php
2
3
/**
4
 * This file is part of the php-epp2 library.
5
 *
6
 * (c) Gunter Grodotzki <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE file
9
 * that was distributed with this source code.
10
 */
11
12
namespace AfriCC\EPP;
13
14
use DOMDocument;
15
use DOMXPath;
16
use Exception;
17
18
/**
19
 * This is a Frame implementation using DOMDocument that other Frames can
20
 * inherit from.
21
 */
22
abstract class AbstractFrame extends DOMDocument implements FrameInterface
23
{
24
    protected $xpath;
25
    protected $nodes;
26
    protected $format;
27
    protected $command;
28
    protected $mapping;
29
    protected $extension;
30
31
    public function __construct($import = null)
32
    {
33
        parent::__construct('1.0', 'UTF-8');
34
        $this->xmlStandalone = false;
35
        $this->formatOutput = true;
36
37
        if ($import instanceof DOMDocument) {
38
            $node = $this->importNode($import->documentElement, true);
39
            $this->appendChild($node);
40
41
            // register namespaces
42
            $this->xpath = new DOMXPath($this);
43
            foreach (ObjectSpec::$specs as $prefix => $spec) {
44
                $this->xpath->registerNamespace($prefix, $spec['xmlns']);
45
            }
46
47
            $this->registerNodeClass('\DOMElement', '\AfriCC\EPP\DOM\DOMElement');
48
        }
49
50
        $this->getStructure();
51
    }
52
53
    public function set($path = null, $value = null)
54
    {
55
        $path = $this->realxpath($path);
56
57
        if (!isset($this->nodes[$path])) {
58
            $path = $this->createNodes($path);
59
        }
60
61
        if ($value !== null) {
62
            $this->nodes[$path]->nodeValue = $value;
63
        }
64
65
        return $this->nodes[$path];
66
    }
67
68
    public function get($query)
69
    {
70
        $nodes = $this->xpath->query($query);
71
        if ($nodes === null || $nodes->length === 0) {
72
            return false;
73
        }
74
75
        // try to figure out what type is being requested
76
        $last_bit = substr(strrchr($query, '/'), 1);
77
78
        // @see http://stackoverflow.com/a/24730245/567193
79
        if ($nodes->length === 1 && (
80
                ($last_bit[0] === '@' && $nodes->item(0)->nodeType === XML_ATTRIBUTE_NODE) ||
81
                (stripos($last_bit, 'text()') === 0 && $nodes->item(0)->nodeType === XML_TEXT_NODE)
82
            )) {
83
            return $nodes->item(0)->nodeValue;
84
        } else {
85
            return $nodes;
86
        }
87
    }
88
89
    public function __toString()
90
    {
91
        return $this->saveXML();
92
    }
93
94
    protected function createNodes($path)
95
    {
96
        $path_parts = explode('/', $path);
97
        $node_path = null;
98
99
        for ($i = 0, $limit = count($path_parts); $i < $limit; ++$i) {
100
            $attr_name = $attr_value = null;
101
102
            // if no namespace given, use root-namespace
103
            if (strpos($path_parts[$i], ':') === false) {
104
                $node_ns = 'epp';
105
                $node_name = $path_parts[$i];
106
                $path_parts[$i] = $node_ns . ':' . $node_name;
107
            } else {
108
                list($node_ns, $node_name) = explode(':', $path_parts[$i], 2);
109
            }
110
111
            // check for node-array
112
            if (substr($node_name, -2) === '[]') {
113
                $node_name = substr($node_name, 0, -2);
114
                // get next key
115
                $next_key = -1;
116
                foreach (array_keys($this->nodes) as $each) {
117
                    if (preg_match('/' . preg_quote($node_ns . ':' . $node_name, '/') . '\[(\d+)\]$/', $each, $matches)) {
118
                        if ($matches[1] > $next_key) {
119
                            $next_key = (int) $matches[1];
120
                        }
121
                    }
122
                }
123
                ++$next_key;
124
                $path_parts[$i] = sprintf('%s:%s[%d]', $node_ns, $node_name, $next_key);
125
            }
126
            // direct node-array access
127
            if (preg_match('/^(.*)\[(\d+)\]$/', $node_name, $matches)) {
128
                $node_name = $matches[1];
129
            }
130
            // check if attribute needs to be set
131
            elseif (preg_match('/^(.*)\[@([a-z0-9]+)=\'([a-z0-9]+)\'\]$/i', $node_name, $matches)) {
132
                $node_name = $matches[1];
133
                $attr_name = $matches[2];
134
                $attr_value = $matches[3];
135
            }
136
137
            $node_path = implode('/', array_slice($path_parts, 0, $i + 1));
138
139
            if (isset($this->nodes[$node_path])) {
140
                continue;
141
            }
142
143
            // resolve node namespace
144
            $node_xmlns = ObjectSpec::xmlns($node_ns);
145
            if ($node_xmlns === false) {
146
                throw new Exception(sprintf('unknown namespace: %s', $node_ns));
147
            }
148
149
            // create node (but don't explicitely define root-node)
150
            if ($node_ns === 'epp') {
151
                $this->nodes[$node_path] = $this->createElementNS($node_xmlns, $node_name);
152
            } else {
153
                $this->nodes[$node_path] = $this->createElementNS($node_xmlns, $node_ns . ':' . $node_name);
154
            }
155
156
            // set attribute
157
            if ($attr_name !== null && $attr_value !== null) {
158
                $this->nodes[$node_path]->setAttribute($attr_name, $attr_value);
159
            }
160
161
            // now append node to parent
162
            if ($i === 0) {
163
                $parent = $this;
164
            } else {
165
                $parent = $this->nodes[implode('/', array_slice($path_parts, 0, $i))];
166
            }
167
            $parent->appendChild($this->nodes[$node_path]);
168
        }
169
170
        return $node_path;
171
    }
172
173
    protected function realxpath($path)
174
    {
175
        if ($path === null) {
176
            $path_parts = [];
177
        }
178
        // absolute path
179
        elseif (isset($path[1]) && $path[0] === '/' && $path[1] === '/') {
180
            return substr($path, 2);
181
        } else {
182
            $path_parts = explode('/', $path);
183
        }
184
185
        if (!empty($this->mapping) && !empty($this->command)) {
186
            array_unshift($path_parts, $this->mapping . ':' . $this->command);
187
        }
188
189
        if (!empty($this->command)) {
190
            array_unshift($path_parts, 'epp:' . $this->command);
191
        }
192
193
        if (!empty($this->format)) {
194
            array_unshift($path_parts, 'epp:' . $this->format);
195
        }
196
197
        array_unshift($path_parts, 'epp:epp');
198
199
        return implode('/', $path_parts);
200
    }
201
202
    private function getStructure()
203
    {
204
        // get class structure
205
        $classes = [get_class($this)];
206
        $classes = array_merge($classes, class_parents($this));
207
208
        foreach ($classes as $class) {
209
            $bare_class = $this->className($class);
210
211
            // stop when we reach self
212
            if ($bare_class === $this->className(__CLASS__)) {
213
                break;
214
            }
215
216
            // try to figure out the structure
217
            $parent_class = $this->className(get_parent_class($class));
218
            if ($parent_class === false) {
219
                continue;
220
            } elseif (empty($this->mapping) && in_array(strtolower($parent_class), ObjectSpec::$mappings)) {
221
                $this->mapping = strtolower($bare_class);
222
            } elseif (empty($this->command) && $parent_class === 'Command') {
223
                $this->command = strtolower($bare_class);
224
            } elseif ($parent_class === 'AbstractFrame') {
225
                $this->format = strtolower($bare_class);
226
            }
227
        }
228
229
        if ($this instanceof ExtensionInterface) {
230
            // automatically guess extension according to class name if not defined in class
231
            if (!isset($this->extension)) {
232
                $this->extension = $this->getExtensionName();
233
            }
234
235
            // add to object spec
236
            ObjectSpec::$specs[$this->extension]['xmlns'] = $this->getExtensionNamespace();
0 ignored issues
show
Bug introduced by
The method getExtensionNamespace() does not exist on AfriCC\EPP\AbstractFrame. Did you maybe mean getExtensionName()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
237
        }
238
    }
239
240
    private function className($class)
241
    {
242
        if (!is_string($class)) {
243
            return $class;
244
        }
245
        if (($pos = strrpos($class, '\\')) === false) {
246
            return $class;
247
        }
248
249
        return substr($class, $pos + 1);
250
    }
251
252
    public function getExtensionName()
253
    {
254
        return strtolower($this->className(get_class($this)));
255
    }
256
}
257