Completed
Pull Request — master (#82)
by
unknown
01:32 queued 10s
created

AbstractFrame::set()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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