Issues (1751)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/ar/html/menu.php (7 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
	/* TODO:
3
	- styling:
4
		- dropdown:
5
			fix hover bg color (is now set on entire submenu instead of single item)
6
			add hover fg color
7
			add normal fg color
8
	*/
9
	ar_pinp::allow('ar_html_menu');
10
11
	class ar_html_menu extends arBase {
12
13
		public static function bar( $options = array() ) {
14
			$options += array(
15
				'filter' => 'object.implements="pdir"'
16
			);
17
			$parent = $options['top'];
18
			if (!$parent) {
19
				$parent = ar::context()->getPath();
20
			}
21
			$query = "object.priority>=0 and object.parent = '$parent'";
22
			if ( $options['filter'] ) {
23
				$query .= ' and ( '.$options['filter'].' )';
24
			}
25
26
			return ar::get( $parent )->find( $query  );
27
		}
28
29
		public static function tree( $options = array() ) {
30
			$current = $options['current'];
31
			if (!$current) {
32
				$current = ar::context()->getPath();
33
			}
34
			$top = $options['top'];
35
			if (!$top) {
36
				$top = ar_store::currentSite( $current );
37
			}
38
			$options += array(
39
				'siblings'    => true,
40
				'children'    => true,
41
				'current'     => null,
42
				'skipTop'     => false,
43
				'filter'      => 'object.implements="pdir"',
44
				'maxDepth'    => 0
45
			);
46
			$query   = "";
47
			if ($top[strlen($top)-1] != '/') {
48
				$top = $top.'/';
49
			}
50
			if ($options['siblings'] && !$options['children']) {
51
				$current = dirname($current).'/';
52
			} else if (!$options['siblings'] && $options['children'] ) {
53
				$query .= "object.parent='".$current."' or ";
54
			}
55
			while (
56
				( substr( $current, 0, strlen( $top ) ) === $top )
57
				&& ar::exists( $current )
58
			) {
59
				if ($options['siblings']) {
60
					$query .= "object.parent='$current' or ";
61
				} else {
62
					$query .= "object.path='$current' or ";
63
				}
64
				$current = dirname($current).'/';
65
			}
66
			if ( !$options['skipTop'] ) {
67
				$query .= "object.path='$top' or ";
68
			}
69
			if ($query) {
70
				$query = " and ( " . substr($query, 0, -4) . " )";
71
			}
72
			$maxDepth = (int)$options['maxDepth'];
73 View Code Duplication
			if ($maxDepth>0) {
74
				$query .= " and object.path !~ '" . $top . str_repeat( '%/', $maxDepth + 1 ) . "'";
75
			}
76
			if ( $options['filter'] ) {
77
				$query .= ' and ( '.$options['filter'].' )';
78
			}
79
			return ar::get($top)->find("object.priority>=0".$query);
80
		}
81
82
		public static function sitemap( $options = array() ) {
83
			$top = $options['top'];
84
			if (!$top) {
85
				$top = ar_store::currentSite( ar::context()->getPath() );
86
			}
87
			$options += array(
88
				'skipTop'     => true,
89
				'filter'      => 'object.implements="pdir"',
90
				'maxDepth'    => 0
91
			);
92
			$query = "object.priority>=0";
93
			if ($options['skipTop']) {
94
				$query .= " and object.path != '".$top."'";
95
			}
96
			$maxDepth = (int)$options['maxDepth'];
97 View Code Duplication
			if ($maxDepth>0) {
98
				$query .= " and object.path !~ '" . $top . str_repeat( '%/', $maxDepth + 1 ) . "'";
99
			}
100
			if ( $options['filter'] ) {
101
				$query .= ' and ( '.$options['filter'].' )';
102
			}
103
			return ar::get( $top )->find( $query );
104
		}
105
106
		public static function crumbs( $options = array() ) {
107
			$current = $options['current'];
108
			if (!$current) {
109
				$current = ar::context()->getPath();
110
			}
111
			$top = $options['top'];
112
			if (!$top) {
113
				$top = ar_store::currentSite( $current );
114
			}
115
			$options += array(
116
				'current' => $current,
117
				'top'     => $top,
118
				'filter'  => ''
119
			);
120
			$current = $options['current'];
121
			if (!$current) {
122
				$current = $top;
123
			}
124
125
			if ( $options['filter'] ) {
126
				$query .= ' and ( '.$options['filter'].' )';
0 ignored issues
show
The variable $query does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
127
			}
128
			return ar::get( $current )->parents()->top( $top );
129
		}
130
131
		public static function el() {
132
			$args       = func_get_args();
133
			$name       = array_shift($args);
134
			$attributes = array();
135
			$content    = array();
136
			foreach ($args as $arg) {
137
				if ( is_array( $arg ) && !is_a( $arg, 'ar_xmlNodes' ) ) {
138
					$attributes = array_merge($attributes, $arg);
139
				} else if ($arg instanceof ar_xmlNodes) {
140
					$content    = array_merge( $content, (array) $arg);
141
				} else {
142
					$content[]  = $arg;
143
				}
144
			}
145
			if ( !count( $content ) ) {
146
				$content = null;
147
			} else {
148
				$content = new ar_htmlNodes( $content );
149
			}
150
			$el = new ar_html_menuElement( $name, $attributes, $content );
151
			$el->setAttribute('class', ['menuRoot' => 'menuRoot'] );
152
			return $el;
153
		}
154
155
		public static function element() {
156
			$args = func_get_args();
157
			return call_user_func_array( array( 'self', 'el' ), $args );
158
		}
159
160
	}
161
162
	class ar_html_menuElement extends ar_htmlElement {
163
164
		private $items   = null;
165
		private $root    = '';
166
		private $current = '';
167
		private $rooturl = '';
168
		private $filled  = false;
169
		private $stripeOptions = false;
170
		private $levelOptions  = false;
171
		private $autoIDOptions  = false;
172
		private $styleType = 'bar';
173
		private $options = array(
174
			'skipOrphans' => false,
175
			'itemTag'     => 'li',
176
			'listTag'     => 'ul'
177
		);
178
		private $prefix = '';
179
		public $viewmode = 'list';
180
		public $template = 'system.get.link.phtml';
181
		public $css = null;
182
183
		public function __construct( $tagName = 'ul', $attributes = array(), $childNodes = null, $parentNode = null ) {
184
			if (!$attributes['class'] && !$attributes['id']) {
185
				$attributes['class'] = 'menu';
186
			}
187
			if (!$tagName) {
188
				$tagName = 'ul';
189
			}
190
			$this->options['listTag'] = $tagName;
191
			switch ($tagName) {
192
				case 'ul':
193
				case 'ol':
194
					$this->options['itemTag'] = 'li';
195
				break;
196
				case 'dl':
197
					$this->options['itemTag'] = 'dt';
198
				break;
199
				default:
200
					$this->options['itemTag'] = $tagName;
201
				break;
202
			}
203
			if ( ! ( $childNodes instanceof ar_htmlNodes ) ) {
204
				$childNodes = null;
205
			}
206
			parent::__construct( $tagName, $attributes, $childNodes, $parentNode );
207
			$this->items['[root]'] = $this;
208
			$context = ar::context();
209
			$me = $context->getObject();
210
			if ( $me ) {
211
				$this->root    = $me->currentsite();
212
				$this->rooturl = $me->make_local_url( $this->root );
213
			}
214
			if ( !isset($this->root) ) {
215
				$this->root    = '/';
216
				$this->rooturl = '/';
217
			}
218
			$this->current = $this->root;
219
			$listTag = $this->options['listTag'];
220
			$itemTag = $this->options['itemTag'];
221
			if ($this->attributes['id']) {
222
				$prefix = '#'.$this->attributes['id'];
223
			} else {
224
				$prefix = $listTag . '.' . $this->attributes['class'];
225
			}
226
			$this->prefix = $prefix;
227
			$this->css = ar_css::stylesheet()
228
			->import("
229
				$prefix, $prefix $listTag {
230
					list-style: none;
231
					margin: 0px;
232
					padding: 0px;
233
				}
234
235
				$prefix $itemTag {
236
					margin: 0px;
237
					padding: 0px;
238
				}
239
240
				$prefix $itemTag a {
241
					text-decoration: none;
242
				}
243
			");
244
		}
245
246
		public function setAttribute( $attribute, $value ) {
247
			parent::setAttribute( $attribute, $value );
248
			if ($this->attributes['id']) {
249
				$this->prefix = '#'.$this->attributes['id'];
250
			} else {
251
				$this->prefix = $this->options['listTag'] . '.' . $this->attributes['class'];
252
			}
253
			return $this;
254
		}
255
256
		public function script( $type = '', $matches = array() ) {
257
			$script = '';
258
			switch ($type) {
259
				case 'pulldown' :
260
				case 'dropdown' :
261
					$listTagUp = strtoupper( $this->options['listTag'] );
262
					$itemTagUp = strtoupper( $this->options['itemTag'] );
263
					$script = <<<EOF
264
function arMenuHover( list ) {
265
	if ( list ) {
266
		if ( list[0]=='#' ) {
267
			var lis = document.getElementById( list.substring(1) ).getElementsByTagName("{$itemTagUp}");
268
		} else {
269
			if ( list[0] == '.') {
270
				list = list.substring(1);
271
			}
272
			var uls = document.getElementsByTagName( '{$listTagUp}' );
273
			var lis = [];
274
			var re = new RegExp('\\\\b' + list + '\\\\b' );
275
			for ( var i = uls.length-1; i>=0; i-- ) {
276
				if ( re.test(uls[i].className) ) {
277
					var newlis = uls[i].getElementsByTagName( '{$itemTagUp}' );
278
					for (var ii=newlis.length-1; ii>=0; ii--) {
279
						lis = lis.concat( newlis[ii] );
280
					}
281
				}
282
			}
283
		}
284
		for (var i=lis.length-1; i>=0; i--) {
285
			lis[i].onmouseover = function() {
286
				this.className += " menuHover";
287
			}
288
			lis[i].onmouseout = function() {
289
				this.className = this.className.replace(/ menuHover\b/, '');
290
			}
291
		}
292
	}
293
}
294
295
EOF;
296
				if ( is_string($matches) ) {
297
					$matches = array( $matches );
298
				}
299
				if ( is_array($matches) && count($matches) ) {
300
					$script .= "if (window.attachEvent && ( !document.documentMode || document.documentMode<8) ) {\n";
301
					foreach ( $matches as $match ) {
302
						$script .= " arMenuHover( '$match' );\n";
303
					}
304
					$script .= "}\n";
305
				}
306
				break;
307
			}
308
			return $script;
309
		}
310
311
		public function style( $type = '' ) {
312
			if (!$type) {
313
				$type = $this->styleType;
314
			}
315
			$itemTag = $this->options['itemTag'];
316
			$listTag = $this->options['listTag'];
317
			$prefix = $this->prefix;
318
			switch ($type) {
319
				case 'bar' :
320
					$this->css->import("
321
						$prefix $itemTag {
322
							float            : left;
323
							padding	         : 0px 10px;
324
						}
325
						$prefix $listTag $listTag {
326
							float            : left;
327
							padding          : 0px;
328
							margin           : 0px;
329
						}
330
						$prefix {
331
							overflow         : hidden;
332
							padding-right    : 20px;
333
						}");
334
				break;
335
				case 'sitemap' :
336
					$this->css->import("
337
						$prefix $itemTag {
338
							margin-left: 0px;
339
							padding-left: 0px;
340
						}
341
						$prefix $listTag {
342
							margin-left: 20px;
343
							padding-left: 0px;
344
						}
345
					");
346
				break;
347
				case 'tree' :
348
					$this->css->import("
349
						$prefix $itemTag {
350
							padding: 0px;
351
						}
352
						$prefix $itemTag a {
353
							padding-left: 20px;
354
						}
355
						$prefix $itemTag $itemTag a {
356
							padding-left: 40px;
357
						}
358
						$prefix $itemTag $itemTag $itemTag a {
359
							padding-left: 60px;
360
						}
361
						$prefix $itemTag $itemTag $itemTag $itemTag a {
362
							padding-left: 80px;
363
						}
364
					");
365
					break;
366
				case 'crumbs' :
367
					$this->css->import("
368
						$prefix $listTag {
369
							display      : inline;
370
						}
371
						$prefix $itemTag {
372
							padding-left : 0px;
373
							list-style   : none;
374
							display      : inline;
375
						}
376
						$prefix $itemTag $itemTag:before {
377
							content      : \"\\0020 \\00BB \\0020\"  /* >> or &raquo character */
378
						}
379
					");
380
				break;
381
				case 'pulldown' :
382
				case 'dropdown' :
383
					$this->css
384
						->bind('menuHeight', '1.5em')
385
						->bind('menuItemWidth', 'auto')
386
						->bind('menuSubItemWidth', '10em')
387
						->bind('menuHoverItemBgColor', '#E4E4E4')
388
						->bind('menuItemBgColor', 'transparent')
389
						->bind('menuSubItemBgColor', 'white')
390
						->bind('menuBorderColor', 'transparent')
391
						->import("
392
							$prefix {
393
								display          : block;
394
								height           : var(menuHeight);
395
							}
396
							$prefix $itemTag {
397
								width            : var(menuItemWidth);
398
								float            : left;
399
								position         : relative;
400
								background-color : var(menuItemBgColor);
401
							}
402
							$prefix $itemTag a {
403
								width            : var(menuItemWidth);
404
								padding          : 0px 10px 0px 0px;
405
							}
406
							$prefix $itemTag $itemTag a {
407
								width            : var(menuSubItemWidth);
408
								display          : block;
409
							}
410
							$prefix $itemTag:hover a {
411
								background-color : var(menuHoverItemBgColor);
412
							}
413
							$prefix $itemTag.menuHover a {
414
								background-color : var(menuHoverItemBgColor);
415
							}
416
							$prefix $itemTag $listTag {
417
								width            : var(menuSubItemWidth);
418
								position         : absolute;
419
								left             : -999em;
420
								background-color : var(menuSubItemBgColor);
421
								border           : 1px solid var(menuBorderColor);
422
							}
423
							$prefix $itemTag $itemTag a {
424
								width        : var(menuSubItemWidth);
425
								padding      : 0px;
426
							}
427
							$prefix $itemTag:hover $listTag {
428
								left         : auto;
429
							}
430
							$prefix $itemTag.menuHover $listTag {
431
								left         : 0px;
432
								top          : 1em;
433
							}
434
							$prefix $itemTag $listTag $listTag {
435
								margin       : -1em 0 0 var(menuSubItemWidth);
436
							}
437
							$prefix $itemTag:hover .$listTag $listTag {
438
								left         : -999em;
439
							}
440
							$prefix $itemTag.menuHover $listTag $listTag {
441
								left         : -999em;
442
							}
443
							$prefix $itemTag $itemTag:hover $listTag {
444
								left         : auto
445
							}
446
							$prefix $itemTag $itemTag.menuHover $listTag {
447
								left         : var(menuSubItemWidth);
448
								top          : 0px;
449
							}
450
							$prefix $itemTag:hover $listTag $listTag $listTag {
451
								left         : -999em;
452
							}
453
							$prefix $itemTag.menuHover $listTag $listTag $listTag {
454
								left         : -999em;
455
							}
456
							$prefix $itemTag $itemTag $itemTag:hover $listTag {
457
								left         : auto;
458
							}
459
							$prefix $itemTag $itemTag $itemTag.menuHover $listTag {
460
								left         : auto;
461
							)
462
						");
463
				break;
464
				case 'tabs' :
465
					$this->css
466
						->bind('menuBgColor',           'transparent')
467
						->bind('menuItemBgColor',       '#E4E4E4')
468
						->bind('menuActiveItemBgColor', 'white')
469
						->bind('menuBorderColor',       'black')
470
						->import("
471
							$prefix $itemTag {
472
								float            : left;
473
								background-color : var(menuItemBgColor);
474
								border           : 1px solid var(menuBorderColor);
475
								margin           : 0px 5px -1px 5px;
476
								padding          : 0px 5px;
477
								height           : 1.5em;
478
							}
479
							$prefix $itemTag.menuCurrent {
480
								background-color : var(menuActiveItemBgColor);
481
								border-bottom    : 1px solid var(menuActiveItemBgColor);
482
							}
483
							$prefix {
484
								height           : 1.5em;
485
								padding          : 0px 20px 1px 0px;
486
								border-bottom    : 1px solid var(menuBorderColor);
487
							}
488
						");
489
					break;
490
			}
491
			return $this->css;
492
		}
493
494
		public function root( $url, $path='/' ) {
495
			if ($this->root == $this->current) { // FIXME: this looks like nonsense
496
				$this->current = $path;
497
			}
498
			$this->rooturl = $url;
499
			$this->root    = $path;
500
			return $this;
501
		}
502
503
		public function template( $template ) {
504
			$this->template = $template;
505
			return $this;
506
		}
507
508
		public function toString( $indent = '', $current = 0 ) {
509
			if ( !$this->filled ) {
510
				// do a default menu.
511
				$this->bar();
512
			}
513
			if ( $this->stripeOptions ) {
514
				$this->stripe( $this->stripeOptions );
515
			}
516
			if ( $this->levelOptions ) {
517
				$this->levels( $this->levelOptions );
518
			}
519
			if ( $this->autoIDOptions ) {
520
				$this->autoID( $this->autoIDOptions );
521
			}
522
			return parent::toString( $indent, $current );
523
		}
524
525
		private function _makeURL( $path, $parent ) {
526
/*
527
			- ?bla=bla    -> parent url + arguments
528
			- #bla        -> parent url + fragment
529
			- /path        -> absolute path -> append to root url
530
			- http://.../ -> url
531
			  ftp://
532
			  mailto:
533
			  [a-z]+:
534
			- rest is a path -> append to parent path
535
*/
536
			if ($parent=='[root]') {
537
				$parent = '';
538
			}
539
			switch( $path[0] ) {
540
				case '?' :
541
					$qpos = ($parent) ? strpos( '?', $parent ) : false;
542
					if (false!==$qpos) {
543
						$url   = substr($parent, $qpos);
544
						$query = substr($parent, $qpos+1);
545
						$fpos  = strpos( '#', $query );
546
						if (false!==$fpos) {
547
							$fragment = substr($query, $fpos);
0 ignored issues
show
$fragment is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
548
							$query    = substr($query, 0, $fpos);
549
						} else {
550
							$fragment = '';
0 ignored issues
show
$fragment is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
551
						}
552
						parse_str($query, $params);
553
						parse_str(substr($path, 1), $newparams);
554
						$params = array_merge_recursive( $params, $newparams );
555
						$url   .= '?' . http_build_query( $params );
556
					} else {
557
						$url   = $parent . $path;
558
					}
559
				break;
560
				case '#' :
561
					$fpos = ($parent) ? strpos( '#', $parent ) : false;
562
					if ( false !== $fpos ) {
563
						$url = substr($parent, $fpos);
564
					} else {
565
						$url = $parent;
566
					}
567
					$url .= $path;
568
				break;
569
				case '/' :
570
					if ( substr( $path, 0, strlen( $this->root ) ) == $this->root ) {
571
						$path = substr($path, strlen($this->root));
572
					} else if ( substr( $this->rooturl, -1 ) == '/' ) {
573
						$path = substr($path, 1);
574
					}
575
					$url = $this->rooturl . $path;
576
				break;
577
				default :
578
					if ( preg_match( '/^[a-z]+:/', $path ) ) { // url
579
						$url = $path;
580
					} else { // relative path
581
						$url = $parent . $path;
582
					}
583
				break;
584
			}
585
			return $url;
586
		}
587
588
		private function _getItemInfo( $item, $key, $parent, $current ) {
589
			if ( $item instanceof ar_htmlElement ) {
590
				if ($item->attributes['href']) {
591
					$url = $item->attributes['href'];
592
				} else {
593
					$url = null;
594
				}
595
				$item = array( 'node' => $item, 'url' => $url );
596
			}
597
			if (!is_array($item)) {
598
				$item = array( 'name' => $item );
599
			}
600
			$path = $item['path'];
601
			if ( !isset($path) ) {
602
				$path = $key;
603
				$item['path'] = ( ( $parent!='[root]' ) ? $parent : '' ) . $key;
604
			}
605
			if ( !isset($item['url']) ) {
606
				$item['url'] = $this->_makeURL( $path, $parent );
607
			}
608
			if ( !isset($item['node']) ) {
609
				if ( !isset($item['tagName']) ) {
610
					$item['tagName'] = $this->options['itemTag'];
611
				}
612
				if ( !isset($item['attributes']) ) {
613
					//FIXME: ['attributes']['a'] / ['li'] / ['ul']
614
					// rest of ['attributes'] = ['attributes']['a']
615
					$item['attributes'] = array();
616
				}
617
				$linkAttributes = array( 'href' => $item['url'] );
618
				if ( isset($item['title']) ) {
619
					$linkAttributes['title'] = $item['title'];
620
				}
621
				if ( isset($item['class']) ) {
622
					$linkAttributes['class'][] = $item['class'];
623
				}
624
				if ( isset($item['id']) ) {
625
					$linkAttributes['id'] = $item['id'];
626
				}
627
				if ( ($item['path']==$current) || ($item['url'] == $current) ) {
628
					$linkAttributes['class']['menuCurrent'] = 'menuCurrent';
629 View Code Duplication
					if (!is_array($item['attributes']['class'])) {
630
						$item['attributes']['class'] = array( $item['attributes']['class'] );
631
					}
632
					$item['attributes']['class']['menuCurrent'] = 'menuCurrent';
633
				} else if ( ( strpos( $current, $item['path'] ) === 0 ) ||
634
					 ( strpos( $current, $item['url'] ) === 0 ) ) {
635 View Code Duplication
					if (!is_array($item['attributes']['class'])) {
636
						$item['attributes']['class'] = array( $item['attributes']['class'] );
637
					}
638
					$item['attributes']['class']['menuParent'] = 'menuParent';
639
				}
640
				$item['node'] = ar_html::tag( $item['tagName'], $item['attributes'],
641
					ar_html::tag( 'a', $linkAttributes, $item['name'])
642
				);
643
			} else {
644
				$link = $item['node']->a[0];
645
				if ( ($item['path']==$current) || ($item['url'] == $current) || ( $link && $link->attributes['href']==$current ) ) {
646
					if ($link) {
647
						$link->setAttribute('class', array( 'menuCurrent' => 'menuCurrent') );
648
					}
649
					$item['node']->setAttribute('class', array( 'menuCurrent' => 'menuCurrent') );
650
				} else if ( ( strpos( $current, $item['path'] ) === 0 ) ||
651
					 ( strpos( $current, $item['url'] ) === 0 ) ||
652
					 ( $link && strpos( $current, $link->attributes['href'] ) === 0 ) ) {
653
					$item['node']->setAttribute('class', array( 'menuParent' => 'menuParent' ) );
654
				}
655
656
			}
657
			return $item;
658
		}
659
660
		private function _fillFromArray( $list, $current, $parent = '[root]' ) {
661
			// first enter all nodes into the items list
662
			// then rearrange them into parent/child relations
663
			// otherwise you must always have the parent in the list before any child
664
665
			foreach ( $list as $key => $item ) {
666
				if ( !$item ) {
667
					continue;
668
				}
669
				$itemInfo = $this->_getItemInfo( $item, $key, $parent, $current );
670
				$itemNode = $itemInfo['node'];
671
				$this->items[$itemInfo['url']] = $itemNode;
672
673
				if ( $parent != '[root]') {
674
					$parentNode = $this->items[$parent];
675
					if ($parentNode) {
676
						$uls = $parentNode->getElementsByTagName( $this->options['listTag'], true );
677
						if (!$uls || !$uls[0]) {
678
							$ul = $parentNode->appendChild( ar_html::el( $this->options['listTag'] ) );
679
						} else {
680
							$ul = $uls[0];
681
						}
682
						$ul->appendChild($itemNode);
683
					}
684
				}
685
				if ($itemInfo['children']) {
686
					$this->_fillFromArray( $itemInfo['children'], $current, $itemInfo['url'] );
687
				}
688
			}
689
			foreach ( $this->items as $url => $itemNode ) {
690
				if ( $url != '[root]' ) { // do not remove, prevents infinite loop
691
					if ( $parent == '[root]' ) {
692
						if ($url == $this->rooturl) {
693
							$parentNode = $this;
694
						} else {
695
							$oldparent = '';
696
							$newparent = dirname( $url ).'/';
697
							if ( !$this->options['skipOrphans'] ) {
698
								while ($newparent!=$oldparent && !isset($this->items[$newparent]) && $newparent!=$this->rooturl) {
699
									$oldparent = $newparent;
700
									$newparent = dirname( $newparent ).'/';
701
								}
702
							}
703
704
							if ( isset($this->items[$newparent]) ) {
705
								$parentNode = current( $this->items[$newparent]->getElementsByTagName( $this->options['listTag'] ) );
706
								if (!$parentNode || !isset($parentNode)) {
707
									$parentNode = $this->items[$newparent]->appendChild( ar_html::el( $this->options['listTag'] ) );
708
								}
709
							} else if ($newparent == $this->rooturl) {
710
								$parentNode = $this;
711
							} else if (!$this->options['skipOrphans']) {
712
								$parentNode = $this;
713
							} else {
714
								$parentNode = null;
715
							}
716
						}
717
					} else if ( isset($this->items[$parent]) ) {
718
						$parentNode = $this->items[$parent];
719
					} else if ( !$this->options['skipOrphans'] ) {
720
						$parentNode = $this;
721
					} else {
722
						$parentNode = null;
723
					}
724
725
					if ($parentNode && ($parentNode !== $itemNode)) { // itemNode should not be parentNode to prevent loops
726
						$temp = $parentNode;
727
						$ok = true;
728
						while( $temp ) {
729
							if ( $temp === $itemNode ) {
730
								$ok = false;
731
								break;
732
							}
733
							$temp = $temp->parentNode;
734
						}
735
						if ( $ok ) {
736
							$parentNode->appendChild( $itemNode );
737
						}
738
					}
739
				}
740
			}
741
		}
742
743
		public function fill() {
744
			$args = func_get_args();
745
			foreach( $args as $list) {
746
				if ( ($list instanceof ar_storeFind) || ($list instanceof ar_storeParents) ) {
747
					$list = $list->call( $this->template, array( 'current' => $this->current, 'root' => $this->root ) );
748
				}
749
				if ( is_array($list) ) {
750
					$this->_fillFromArray( $list, $this->current );
751
				}
752
			}
753
			$this->filled = true;
754
			return $this;
755
		}
756
757
		public function stripe( $options = array() ) {
758
			$options += array(
759
				'striping'         => ar::listPattern( 'menuFirst .*', '(menuOdd menuEven?)*', '.* menuLast' ),
760
				'stripingContinue' => false
761
			);
762
			if ( $options['striping'] ) {
763
				if ( $options['stripingContinue'] ) {
764
					$this->getElementsByTagName( $this->options['itemTag'] )->setAttribute('class', array(
765
						'menuStriping' => $options['striping']
766
					) );
767
				} else {
768
					$this->childNodes->setAttribute( 'class', array(
769
						'menuStriping' => $options['striping']
770
					) );
771
					$uls = $this->getElementsByTagName( $this->options['listTag'] );
772
					foreach( $uls as $ul ) {
773
						$ul->childNodes->setAttribute( 'class', array(
774
							'menuStriping' => $options['striping']
775
						) );
776
					}
777
				}
778
			}
779
			$this->stripeOptions = $options;
0 ignored issues
show
Documentation Bug introduced by
It seems like $options of type array is incompatible with the declared type boolean of property $stripeOptions.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
780
			return $this;
781
		}
782
783
		public function levels( $options = array() ) {
784
			$options += array(
785
				'maxDepth'   => 5,
786
				'startLevel' => 0
787
			);
788
789
			// add level classes to the ul/li tags, level-0, level-1, etc.
790
			if ( $options['maxDepth'] == 0 ) {
791
				return $this;
792
			}
793
			if (!isset($options['rootNode'])) {
794
				$options['rootNode'] = $this;
795
			}
796
			if ( $options['rootNode'] instanceof ar_htmlElement ) {
797
				$options['rootNode'] = ar_html::nodes( $options['rootNode'] );
798
			}
799
			if ($options['rootNode'] instanceof ar_htmlNodes && count($options['rootNode']) ) {
800
				$options['rootNode']->setAttribute( 'class', array('menuLevels' => 'menuLevel-'.$options['startLevel']) );
801
				foreach( $options['rootNode'] as $element ) {
802
					$element->childNodes->setAttribute( 'class', array( 'menuLevels' => 'menuLevel-'.$options['startLevel'] ) );
803
					$this->levels( array(
804
						'maxDepth' => $options['maxDepth'] - 1,
805
						'startLevel' => $options['startLevel'] + 1,
806
						'rootNode' => $element->getElementsByTagName( $this->options['itemTag'], true )->getElementsByTagName( $this->options['listTag'], true )
807
					) );
808
				}
809
			}
810
			$this->levelOptions = $options;
0 ignored issues
show
Documentation Bug introduced by
It seems like $options of type array<string,?> is incompatible with the declared type boolean of property $levelOptions.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
811
			return $this;
812
		}
813
814
		public function autoID( $options = array() ) {
815
			$options += array(
816
				'rootID' => 'menu',
817
				'rootNode' => $element
0 ignored issues
show
The variable $element seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
818
			);
819
			// create unique id's per list item
820
			if (!isset($options['rootNode']) ) {
821
				$options['rootNode'] = $this;
822
			}
823
			$element = $options['rootNode'];
824
			if (!$element->attributes['id']) {
825
				$element->setAttribute( 'id', $options['rootID'] . '-ul' );
826
			}
827
			$list    = $element->li;
828
			$counter = 0;
829
			foreach ($list as $li) {
830
				$id = $options['rootID'] . '-' . $counter++;
831
				if ( !$li->attributes['id'] ) {
832
					$li->setAttribute( 'id', $id );
833
				}
834
				$ul = $li->getElementsByTagName( $this->options['listTag'], true );
835
				if (count($ul)) {
836
					$this->autoID( array(
837
						'rootID' => $id,
838
						'rootNode' => $ul[0]
839
					) );
840
				}
841
			}
842
			$this->autoIDOptions = $options;
0 ignored issues
show
Documentation Bug introduced by
It seems like $options of type array<string,?,{"rootNode":"?"}> is incompatible with the declared type boolean of property $autoIDOptions.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
843
			return $this;
844
		}
845
846
		public function childIndicators() {
847
			$list = $this->getElementsByTagName( $this->options['listTag'] );
848
			foreach( $list as $ul) {
849
				$ul->parentNode->setAttribute('class', array('menuChildIndicator' => 'menuHasChildren'));
850
			}
851
			return $this;
852
		}
853
854
		public function current( $path ) {
855
			$this->current = $path;
856
			return $this;
857
		}
858
859
		public function configure( $options ) {
860
			if ($options['top']) {
861
				$this->root( ar('loader')->makeURL($options['top']), $options['top'] );
862
			} else {
863
				$options['top'] = $this->root;
864
			}
865
			if ($options['current']) {
866
				$this->current( $options['current'] );
867
			} else {
868
				$options['current'] = $this->current;
869
			}
870
			$this->options = array_merge( $this->options, $options );
871
			return $this;
872
		}
873
874 View Code Duplication
		public function bar( $options = array() ) {
875
			$this->configure( $options );
876
			$this->fill( ar_html_menu::bar( $this->options ) );
877
			$this->styleType = 'bar';
878
			return $this;
879
		}
880
881 View Code Duplication
		public function tree( $options = array() ) {
882
			$this->configure( $options );
883
			$this->fill( ar_html_menu::tree( $this->options ) );
884
			$this->styleType = 'tree';
885
			return $this;
886
		}
887
888 View Code Duplication
		public function crumbs( $options = array() ) {
889
			$this->configure( $options );
890
			$this->fill( ar_html_menu::crumbs( $this->options ) );
891
			$this->styleType = 'crumbs';
892
			return $this;
893
		}
894
895 View Code Duplication
		public function sitemap( $options = array() ) {
896
			$this->configure( $options );
897
			$this->fill( ar_html_menu::sitemap( $this->options ) );
898
			$this->styleType = 'sitemap';
899
			return $this;
900
		}
901
902
	}
903