Completed
Pull Request — master (#84)
by
unknown
01:28
created

AbstractFrame::getExtensionName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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