Test Failed
Pull Request — master (#160)
by
unknown
11:10
created

InnerNode::insertAfter()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
cc 3
nc 3
nop 2
1
<?php
2
namespace PHPHtmlParser\Dom;
3
4
use PHPHtmlParser\Exceptions\ChildNotFoundException;
5
use PHPHtmlParser\Exceptions\CircularException;
6
use stringEncode\Encode;
7
8
/**
9
 * Inner node of the html tree, might have children.
10
 *
11
 * @package PHPHtmlParser\Dom
12
 */
13
abstract class InnerNode extends ArrayNode
14
{
15
16
    /**
17
     * An array of all the children.
18
     *
19
     * @var array
20
     */
21
    protected $children = [];
22
23
    /**
24
     * Sets the encoding class to this node and propagates it
25
     * to all its children.
26
     *
27
     * @param Encode $encode
28
     * @return void
29
     */
30
    public function propagateEncoding(Encode $encode)
31
    {
32
        $this->encode = $encode;
33
        $this->tag->setEncoding($encode);
34
        // check children
35
        foreach ($this->children as $id => $child) {
36
            /** @var AbstractNode $node */
37
            $node = $child['node'];
38
            $node->propagateEncoding($encode);
39
        }
40
    }
41
42
    /**
43
     * Checks if this node has children.
44
     *
45
     * @return bool
46
     */
47
    public function hasChildren()
48
    {
49
        return ! empty($this->children);
50
    }
51
52
    /**
53
     * Returns the child by id.
54
     *
55
     * @param int $id
56
     * @return AbstractNode
57
     * @throws ChildNotFoundException
58
     */
59
    public function getChild($id)
60
    {
61
        if ( ! isset($this->children[$id])) {
62
            throw new ChildNotFoundException("Child '$id' not found in this node.");
63
        }
64
65
        return $this->children[$id]['node'];
66
    }
67
68
    /**
69
     * Returns a new array of child nodes
70
     *
71
     * @return array
72
     */
73
    public function getChildren()
74
    {
75
        $nodes = [];
76
        try {
77
            $child = $this->firstChild();
78
            do {
79
                $nodes[] = $child;
80
                $child   = $this->nextChild($child->id());
81
            } while ( ! is_null($child));
82
        } catch (ChildNotFoundException $e) {
83
            // we are done looking for children
84
        }
85
86
        return $nodes;
87
    }
88
89
    /**
90
     * Counts children
91
     *
92
     * @return int
93
     */
94
    public function countChildren()
95
    {
96
        return count($this->children);
97
    }
98
99
    /**
100
     * Adds a child node to this node and returns the id of the child for this
101
     * parent.
102
     *
103
     * @param AbstractNode $child
104
     * @return bool
105
     * @throws CircularException
106
     */
107
    public function addChild(AbstractNode $child, $before = null)
108
    {
109
        $key = null;
110
111
        // check integrity
112
        if ($this->isAncestor($child->id())) {
113
            throw new CircularException('Can not add child. It is my ancestor.');
114
        }
115
116
        // check if child is itself
117
        if ($child->id() == $this->id) {
118
            throw new CircularException('Can not set itself as a child.');
119
        }
120
121
		$next = null;
122
123
        if ($this->hasChildren()) {
124
			if (isset($this->children[$child->id()])) {
125
				// we already have this child
126
				return false;
127
			}
128
129
			if ($before) {
130
				if (!isset($this->children[$before])) {
131
					return false;
132
				}
133
134
				$key = $this->children[$before]['prev'];
135
136
				if($key){
137
					$this->children[$key]['next'] = $child->id();
138
				}
139
140
				$this->children[$before]['prev'] = $child->id();
141
				$next = $before;
142
			} else {
143
				$sibling = $this->lastChild();
144
				$key = $sibling->id();
145
146
				$this->children[$key]['next'] = $child->id();
147
			}
148
        }
149
150
		$keys = array_keys($this->children);
151
152
		$insert = [
153
			'node' => $child,
154
			'next' => $next,
155
			'prev' => $key,
156
		];
157
158
		$index = $key ? (array_search($key, $keys, true) + 1) : 0;
159
		array_splice($keys, $index, 0, $child->id());
160
161
		$children = array_values($this->children);
162
		array_splice($children, $index, 0, [$insert]);
163
164
		// add the child
165
		$this->children = array_combine($keys, $children);
166
167
        // tell child I am the new parent
168
        $child->setParent($this);
169
170
        //clear any cache
171
        $this->clear();
172
173
        return true;
174
    }
175
176
	/**
177
	 * Insert element before child with provided id
178
	 *
179
	 * @param AbstractNode $child
180
	 * @return bool
181
	 * @param int $id
182
	 */
183
	public function insertBefore(AbstractNode $child, $id){
184
		$this->addChild($child, $id);
185
	}
186
187
	/**
188
	 * Insert element before after with provided id
189
	 *
190
	 * @param AbstractNode $child
191
	 * @return bool
192
	 * @param int $id
193
	 */
194
	public function insertAfter(AbstractNode $child, $id){
195
		if (!isset($this->children[$id])) {
196
			return false;
197
		}
198
199
		if ($this->children[$id]['next']) {
200
			return $this->addChild($child, $this->children[$id]['next']);
201
		}
202
203
		return $this->addChild($child);
204
	}
205
206
    /**
207
     * Removes the child by id.
208
     *
209
     * @param int $id
210
     * @return $this
211
     */
212
    public function removeChild($id)
213
    {
214
        if ( ! isset($this->children[$id])) {
215
            return $this;
216
        }
217
218
        // handle moving next and previous assignments.
219
        $next = $this->children[$id]['next'];
220
        $prev = $this->children[$id]['prev'];
221
        if ( ! is_null($next)) {
222
            $this->children[$next]['prev'] = $prev;
223
        }
224
        if ( ! is_null($prev)) {
225
            $this->children[$prev]['next'] = $next;
226
        }
227
228
        // remove the child
229
        unset($this->children[$id]);
230
231
        //clear any cache
232
        $this->clear();
233
234
        return $this;
235
    }
236
237
    /**
238
     * Check if has next Child
239
     *
240
     * @param $id childId
241
     * @return mixed
242
     */
243
    public function hasNextChild($id)
244
    {
245
        $child= $this->getChild($id);
246
        return $this->children[$child->id()]['next'];
247
    }
248
249
    /**
250
     * Attempts to get the next child.
251
     *
252
     * @param int $id
253
     * @return AbstractNode
254
     * @uses $this->getChild()
255
     * @throws ChildNotFoundException
256
     */
257
    public function nextChild($id)
258
    {
259
        $child = $this->getChild($id);
260
        $next  = $this->children[$child->id()]['next'];
261
262
        return $this->getChild($next);
263
    }
264
265
    /**
266
     * Attempts to get the previous child.
267
     *
268
     * @param int $id
269
     * @return AbstractNode
270
     * @uses $this->getChild()
271
     * @throws ChildNotFoundException
272
     */
273
    public function previousChild($id)
274
    {
275
        $child = $this->getchild($id);
276
        $next  = $this->children[$child->id()]['prev'];
277
278
        return $this->getChild($next);
279
    }
280
281
    /**
282
     * Checks if the given node id is a child of the
283
     * current node.
284
     *
285
     * @param int $id
286
     * @return bool
287
     */
288
    public function isChild($id)
289
    {
290
        foreach ($this->children as $childId => $child) {
291
            if ($id == $childId) {
292
                return true;
293
            }
294
        }
295
296
        return false;
297
    }
298
299
    /**
300
     * Removes the child with id $childId and replace it with the new child
301
     * $newChild.
302
     *
303
     * @param int $childId
304
     * @param AbstractNode $newChild
305
     * @throws ChildNotFoundException
306
     */
307
    public function replaceChild($childId, AbstractNode $newChild)
308
    {
309
        $oldChild = $this->children[$childId];
310
311
        $newChild->prev = $oldChild['prev'];
0 ignored issues
show
Bug introduced by
The property prev does not seem to exist in PHPHtmlParser\Dom\AbstractNode.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
312
        $newChild->next = $oldChild['next'];
0 ignored issues
show
Bug introduced by
The property next does not seem to exist in PHPHtmlParser\Dom\AbstractNode.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
313
314
        $keys = array_keys($this->children);
315
        $index = array_search($childId, $keys, true);
316
        $keys[$index] = $newChild->id();
317
        $this->children = array_combine($keys, $this->children);
318
        $this->children[$newChild->id()] = array(
319
            'prev' => $oldChild['prev'],
320
            'node' => $newChild,
321
            'next' => $oldChild['next']
322
        );
323
324
        if ($oldChild['prev'] && isset($this->children[$newChild->prev])) {
0 ignored issues
show
Documentation introduced by
The property prev does not exist on object<PHPHtmlParser\Dom\AbstractNode>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
325
            $this->children[$oldChild['prev']]['next'] = $newChild->id();
326
        }
327
328
        if ($oldChild['next'] && isset($this->children[$newChild->next])) {
0 ignored issues
show
Documentation introduced by
The property next does not exist on object<PHPHtmlParser\Dom\AbstractNode>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
329
            $this->children[$oldChild['next']]['prev'] = $newChild->id();
330
        }
331
    }
332
333
    /**
334
     * Shortcut to return the first child.
335
     *
336
     * @return AbstractNode
337
     * @uses $this->getChild()
338
     */
339
    public function firstChild()
340
    {
341
        reset($this->children);
342
        $key = key($this->children);
343
344
        return $this->getChild($key);
345
    }
346
347
    /**
348
     * Attempts to get the last child.
349
     *
350
     * @return AbstractNode
351
     */
352
    public function lastChild()
353
    {
354
        end($this->children);
355
        $key = key($this->children);
356
357
        return $this->getChild($key);
358
    }
359
360
    /**
361
     * Checks if the given node id is a descendant of the
362
     * current node.
363
     *
364
     * @param int $id
365
     * @return bool
366
     */
367
    public function isDescendant($id)
368
    {
369
        if ($this->isChild($id)) {
370
            return true;
371
        }
372
373
        foreach ($this->children as $childId => $child) {
374
            /** @var InnerNode $node */
375
            $node = $child['node'];
376
            if ($node instanceof InnerNode &&
377
                $node->hasChildren() &&
378
                $node->isDescendant($id)
379
            ) {
380
                return true;
381
            }
382
        }
383
384
        return false;
385
    }
386
387
    /**
388
     * Sets the parent node.
389
     *
390
     * @param InnerNode $parent
391
     * @return $this
392
     * @throws CircularException
393
     */
394
    public function setParent(InnerNode $parent)
395
    {
396
        // check integrity
397
        if ($this->isDescendant($parent->id())) {
398
            throw new CircularException('Can not add descendant "'.$parent->id().'" as my parent.');
399
        }
400
401
        return parent::setParent($parent);
402
    }
403
}
404