Completed
Pull Request — master (#82)
by
unknown
01:30
created

AbstractFrame::get()   B

Complexity

Conditions 8
Paths 3

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 8.4444
c 0
b 0
f 0
cc 8
nc 3
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
    /**
26
     * @var \DOMElement[]
27
     */
28
    protected $nodes;
29
    protected $format;
30
    protected $command;
31
    protected $mapping;
32
    protected $extension;
33
    /**
34
     * @var bool whether to ignore command part when building realxpath
35
     */
36
    protected $ignore_command = false;
37
38
    /**
39
     * @var ObjectSpec custom objectspec used to create XML
40
     */
41
    protected $objectSpec;
42
43
    /**
44
     * Construct (with import if specified) frame
45
     *
46
     * Pass a DOMDocument instance to have it imported as a frame.
47
     * Pass an ObjectSpec instance to have it set as used ObjectSpec
48
     * More arguments will be ignored and only the last one will be used.
49
     */
50
    public function __construct()
51
    {
52
        parent::__construct('1.0', 'UTF-8');
53
        $this->xmlStandalone = false;
54
        $this->formatOutput = true;
55
56
        $import = null;
57
58
        $args = \func_get_args();
59
        foreach ($args as $arg) {
60
            if ($arg instanceof DOMDocument) {
61
                $import = $arg;
62
            }
63
            if ($arg instanceof ObjectSpec) {
64
                $this->objectSpec = $arg;
65
            }
66
        }
67
68
        if (\is_null($this->objectSpec)) {
69
            $this->objectSpec = new ObjectSpec();
70
        }
71
72
        $this->import($import);
73
74
        $this->registerXpath();
75
76
        $this->registerNodeClass('\DOMElement', '\AfriCC\EPP\DOM\DOMElement');
77
78
        $this->getStructure();
79
    }
80
81
    /**
82
     * Import frame data
83
     *
84
     * @param \DOMDocument $import
85
     */
86
    private function import(DOMDocument $import = null)
87
    {
88
        if (is_null($import)) {
89
            return;
90
        }
91
        $node = $this->importNode($import->documentElement, true);
92
        $this->appendChild($node);
93
    }
94
95
    private function registerXpath()
96
    {
97
        // register namespaces
98
        $this->xpath = new DOMXPath($this);
99
        foreach ($this->objectSpec->specs as $prefix => $spec) {
100
            $this->xpath->registerNamespace($prefix, $spec['xmlns']);
101
        }
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     *
107
     * @see \AfriCC\EPP\FrameInterface::set()
108
     */
109
    public function set($path = null, $value = null)
110
    {
111
        $path = $this->realxpath($path);
112
113
        if (!isset($this->nodes[$path])) {
114
            $path = $this->createNodes($path);
115
        }
116
117
        if ($value !== null) {
118
            $this->nodes[$path]->nodeValue = htmlspecialchars($value, ENT_XML1, 'UTF-8');
119
        }
120
121
        return $this->nodes[$path];
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     *
127
     * @see \AfriCC\EPP\FrameInterface::get()
128
     */
129
    public function get($query)
130
    {
131
        $nodes = $this->xpath->query($query);
132
        if ($nodes === null || $nodes->length === 0) {
133
            return false;
134
        }
135
136
        // try to figure out what type is being requested
137
        $last_bit = substr(strrchr($query, '/'), 1);
138
139
        // @see http://stackoverflow.com/a/24730245/567193
140
        if ($nodes->length === 1 && (
141
                ($last_bit[0] === '@' && $nodes->item(0)->nodeType === XML_ATTRIBUTE_NODE) ||
142
                (stripos($last_bit, 'text()') === 0 && $nodes->item(0)->nodeType === XML_TEXT_NODE)
143
            )) {
144
            return $nodes->item(0)->nodeValue;
145
        } else {
146
            return $nodes;
147
        }
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     *
153
     * @see \AfriCC\EPP\FrameInterface::__toString()
154
     */
155
    public function __toString()
156
    {
157
        return $this->saveXML();
158
    }
159
160
    /**
161
     * Create nodes specified by path
162
     *
163
     * @param string $path
164
     *
165
     * @throws Exception
166
     *
167
     * @return null|string
168
     */
169
    protected function createNodes($path)
170
    {
171
        $path_parts = explode('/', $path);
172
        $node_path = null;
173
174
        for ($i = 0, $limit = count($path_parts); $i < $limit; ++$i) {
175
            $attr_name = $attr_value = $matches = null;
176
177
            // if no namespace given, use root-namespace
178
            if (strpos($path_parts[$i], ':') === false) {
179
                $node_ns = 'epp';
180
                $node_name = $path_parts[$i];
181
                $path_parts[$i] = $node_ns . ':' . $node_name;
182
            } else {
183
                list($node_ns, $node_name) = explode(':', $path_parts[$i], 2);
184
            }
185
186
            // check for node-array
187
            if (substr($node_name, -2) === '[]') {
188
                $node_name = substr($node_name, 0, -2);
189
                // get next key
190
                $next_key = -1;
191
                foreach (array_keys($this->nodes) as $each) {
192
                    if (preg_match('/' . preg_quote($node_ns . ':' . $node_name, '/') . '\[(\d+)\]$/', $each, $matches)) {
193
                        if ($matches[1] > $next_key) {
194
                            $next_key = (int) $matches[1];
195
                        }
196
                    }
197
                }
198
                ++$next_key;
199
                $path_parts[$i] = sprintf('%s:%s[%d]', $node_ns, $node_name, $next_key);
200
            }
201
202
            if (preg_match('/^(.*)\[(\d+)\]$/', $node_name, $matches)) {
203
                // direct node-array access
204
                $node_name = $matches[1];
205
            } elseif (preg_match('/^(.*)\[@([a-z0-9]+)=\'([a-z0-9_]+)\'\]$/i', $node_name, $matches)) {
206
                // check if attribute needs to be set
207
                $node_name = $matches[1];
208
                $attr_name = $matches[2];
209
                $attr_value = $matches[3];
210
            }
211
212
            $node_path = implode('/', array_slice($path_parts, 0, $i + 1));
213
214
            if (isset($this->nodes[$node_path])) {
215
                continue;
216
            }
217
218
            // resolve node namespace
219
            $node_xmlns = $this->objectSpec->xmlns($node_ns);
220
            if ($node_xmlns === false) {
221
                throw new Exception(sprintf('unknown namespace: %s', $node_ns));
222
            }
223
224
            // create node (but don't explicitly define root-node)
225
            if ($node_ns === 'epp') {
226
                $this->nodes[$node_path] = $this->createElementNS($node_xmlns, $node_name);
227
            } else {
228
                $this->nodes[$node_path] = $this->createElementNS($node_xmlns, $node_ns . ':' . $node_name);
229
            }
230
231
            // set attribute
232
            if ($attr_name !== null && $attr_value !== null) {
233
                $this->nodes[$node_path]->setAttribute($attr_name, $attr_value);
234
            }
235
236
            // now append node to parent
237
            if ($i === 0) {
238
                $parent = $this;
239
            } else {
240
                $parent = $this->nodes[implode('/', array_slice($path_parts, 0, $i))];
241
            }
242
            $parent->appendChild($this->nodes[$node_path]);
243
        }
244
245
        return $node_path;
246
    }
247
248
    /**
249
     * Get Real XPath for provided path
250
     *
251
     * @param string $path
252
     *
253
     * @return string
254
     */
255
    protected function realxpath($path)
256
    {
257
        if ($path === null) {
258
            $path_parts = [];
259
        } elseif (isset($path[1]) && $path[0] === '/' && $path[1] === '/') {
260
            // absolute path
261
            return substr($path, 2);
262
        } else {
263
            $path_parts = explode('/', $path);
264
        }
265
266
        if (!empty($this->mapping) && !empty($this->command)) {
267
            array_unshift($path_parts, $this->mapping . ':' . $this->command);
268
        }
269
270
        if (!empty($this->command) && !$this->ignore_command) {
271
            array_unshift($path_parts, 'epp:' . $this->command);
272
        }
273
274
        if (!empty($this->format)) {
275
            array_unshift($path_parts, 'epp:' . $this->format);
276
        }
277
278
        array_unshift($path_parts, 'epp:epp');
279
280
        return implode('/', $path_parts);
281
    }
282
283
    private function getStructure()
284
    {
285
        // get class structure
286
        $classes = [get_class($this)];
287
        $classes = array_merge($classes, class_parents($this));
288
289
        foreach ($classes as $class) {
290
            $bare_class = $this->className($class);
291
292
            // stop when we reach self
293
            if ($bare_class === $this->className(__CLASS__)) {
294
                break;
295
            }
296
297
            // try to figure out the structure
298
            $parent_class = $this->className(get_parent_class($class));
299
            if ($parent_class === false) {
300
                continue;
301
            } elseif (empty($this->mapping) && in_array(strtolower($parent_class), $this->objectSpec->mappings)) {
302
                $this->mapping = strtolower($bare_class);
303
            } elseif (empty($this->command) && $parent_class === 'Command') {
304
                $this->command = strtolower($bare_class);
305
            } elseif ($parent_class === 'AbstractFrame') {
306
                $this->format = strtolower($bare_class);
307
            }
308
        }
309
310
        if ($this instanceof ExtensionInterface) {
311
            // automatically guess extension according to class name if not defined in class
312
            if (!isset($this->extension)) {
313
                $this->extension = $this->getExtensionName();
314
            }
315
316
            // add to object spec
317
            $this->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...
318
        }
319
    }
320
321
    /**
322
     * Get Class name from full class
323
     *
324
     * @param string $class
325
     *
326
     * @return string
327
     */
328
    private function className($class)
329
    {
330
        if (!is_string($class)) {
331
            return $class;
332
        }
333
        if (($pos = strrpos($class, '\\')) === false) {
334
            return $class;
335
        }
336
337
        return substr($class, $pos + 1);
338
    }
339
340
    public function getExtensionName()
341
    {
342
        return strtolower($this->className(get_class($this)));
343
    }
344
}
345