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

AbstractFrame::__construct()   B

Complexity

Conditions 6
Paths 20

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

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