Recursive foreach() producing duplicate results when looping - php

This class is designed to iterate over a WordPress menu structure (nested arrays/objects) to produce a finished menu. While the source of my data is WordPress, I feel this question belongs on SO instead of WP.SO because the issue is rooted more in PHP (applies to anyone attempting a recursion).
For some reason, I'm seeing duplicate results in the hierarchy. Also, I'm noticing that certain HTML elements are not closing properly. It seems like I've nested everything properly, but the result is what you see here.
To assist with debugging, I've added some * to impact the markup visually. Maybe you guys know something I don't. Fingers crossed and thanks in advance for your input!
My class
class Nav_Menu
{
public $wp_nav;
public $nested_nav;
public $recursion_depth = 0;
function __construct( $menu, $args = array() )
{
$format = new Format;
if( $menu )
{
$this->wp_nav = wp_get_nav_menu_items($menu, $args);
$this->nested_nav = $this->build_tree($this->wp_nav);
$output = $this->build_output($this->nested_nav);
$output_formatted = $format->HTML($output);
// echo $output;
echo $output_formatted;
}
}
private function build_output( $menu = array() )
{
$output = '**';
$output.= $this->recurse_menu($menu, $output);
return $output;
}
private function recurse_menu( $menu = array(), $output )
{
global $post;
if( !empty($menu) && !empty($output) )
{
$this->recursion_depth++;
// ul classes
$classes_ul = array();
$classes_ul[] = ( $this->recursion_depth > 1 ? 'sub-menu' : '' );
$classes_ul[] = 'depth-' . $this->recursion_depth;
// process list wrappers
$output.= '<ul class="' . $this->process_classes($classes_ul) . '">';
// loop through menu items
foreach( $menu as $menu_key => $menu_val )
{
// process list items
$output.= '<li>' . $menu_val->title;
// if necessary, handle children and recurse
if( !empty($menu_val->children) )
{
// recurse, and call this again
$output.= $this->recurse_menu($menu_val->children, $output);
}
// process list items
$output.= '</li>';
}
// process list wrappers
$output.= '</ul>';
}
return $output;
}
private function process_classes($classes = array())
{
if( !$classes )
{
return;
}
return trim(implode(' ', $classes));
}
private function build_tree( $elements = array(), $parent_id = 0 )
{
$branch = array();
foreach($elements as $element)
{
if ($element->menu_item_parent == $parent_id)
{
$children = $this->build_tree($elements, $element->ID);
if ($children)
{
$element->children = $children;
}
$branch[] = $element;
}
}
return $branch;
}
}
$mynav = new Nav_Menu('Test Menu');
The resulting output
****
<ul class="depth-1">
<li>
One**
<ul class="depth-1">
<li>
One
<ul class="sub-menu depth-2">
<li>
Sub One
</li>
<li>
Sub Two
</li>
<li>
Sub Three
</li>
</ul>
</li>
<li>
Two
</li>
<li>
Three**
<ul class="depth-1">
<li>
One**
<ul class="depth-1">
<li>
One
<ul class="sub-menu depth-2">
<li>
Sub One
</li>
<li>
Sub Two
</li>
<li>
Sub Three
</li>
</ul>
</li>
<li>
Two
</li>
<li>
Three
<ul class="sub-menu depth-3">
<li>
Sub One
</li>
<li>
Sub Two
</li>
</ul>
</li>
<li>
Four
</li>
</ul>
WordPress menu in the backend

If someone knows the reason why, I'd be willing to know, so I'll reserve choosing an answer for now. My guess is some kind of odd namespacing/scope issue for the variable $output. Who knows, I'm kinda tired right now.
The fix to get a legit structure was this...
Class
class Nav_Menu
{
public $wp_nav;
public $nested_nav;
public $recursion_depth = 0;
public $output = '';
function __construct( $menu, $args = array() )
{
$format = new Format;
if( $menu )
{
$this->wp_nav = wp_get_nav_menu_items($menu, $args);
$this->nested_nav = $this->build_tree($this->wp_nav);
$this->build_output($this->nested_nav);
$output_formatted = $format->HTML($this->output);
// echo $this->output;
echo $output_formatted;
}
}
private function build_output( $menu = array() )
{
$this->recurse_menu($menu);
}
private function recurse_menu( $menu = array() )
{
global $post;
if( !empty($menu) )
{
$this->recursion_depth++;
// ul classes
$classes_ul = array();
$classes_ul[] = ( $this->recursion_depth > 1 ? 'sub-menu' : '' );
$classes_ul[] = 'depth-' . $this->recursion_depth;
// process list wrappers
$this->output.= '<ul class="' . $this->process_classes($classes_ul) . '">';
// loop through menu items
foreach( $menu as $menu_key => $menu_val )
{
// process list items
$this->output.= '<li>';
$this->output.= $menu_val->title;
// if necessary, handle children and recurse
if( !empty($menu_val->children) )
{
// recurse, and call this again
$this->recurse_menu($menu_val->children);
}
// process list items
$this->output.= '</li>';
}
// process list wrappers
$this->output.= '</ul>';
}
}
private function process_classes($classes = array())
{
if( !$classes )
{
return;
}
return trim(implode(' ', $classes));
}
private function build_tree( $elements = array(), $parent_id = 0 )
{
$branch = array();
foreach($elements as $element)
{
if ($element->menu_item_parent == $parent_id)
{
$children = $this->build_tree($elements, $element->ID);
if ($children)
{
$element->children = $children;
}
$branch[] = $element;
}
}
return $branch;
}
}
$mynav = new Nav_Menu('Test Menu'); exit;
Resulting output
<ul class="depth-1">
<li>
One
<ul class="sub-menu depth-2">
<li>
Sub One
</li>
<li>
Sub Two
</li>
<li>
Sub Three
</li>
</ul>
</li>
<li>
Two
</li>
<li>
Three
<ul class="sub-menu depth-3">
<li>
Sub One
</li>
<li>
Sub Two
</li>
</ul>
</li>
<li>
Four
</li>
</ul>
I just created a private variable for the class, and each time I need to reference it as a storage location, I just append to it. Same as before, but no more having to pass $output down some crazy method chain.
If anyone has any other ideas that could help the community, please share!

Update your build_output method as below:
private function build_output( $menu = array() )
{
$output = '<ul>';
$output = $this->recurse_menu($menu, $output);
$output.= '</ul>';
return $output;
}
Update your recurse_menu method as below:
private function recurse_menu( $menu = array(), $output = '')
{
global $post;
if( !empty($menu))
{
$this->recursion_depth++;
// ul classes
$classes_ul = array();
$classes_ul[] = ( $this->recursion_depth > 1 ? 'sub-menu' : '' );
$classes_ul[] = 'depth-' . $this->recursion_depth;
// loop through menu items
foreach( $menu as $menu_key => $menu_val )
{
// if necessary, handle children and recurse
if( !empty($menu_val->children) )
{
// recurse, and call this again
$output.= '<li>'.$menu_val->title.'<ul class="' . $this->process_classes($classes_ul) . '">'.$this->recurse_menu($menu_val->children).'</ul></li>';
}
else {
$output.= '<li>'.$menu_val->title.'</li>';
}
}
}
return $output;
}
NOTE: I have tested it with more further sub-level and it is working fine.

Related

Add attributes on li tags for FuelPHP ul method

Using FuelPHP, I was wondering how to add attributes on li tags for the ul method ul($list, $style = false).
https://fuelphp.com/docs/classes/html.html
Here is what I want to achieve
<ul id="sortable">
<li id="red">red</li>
<li id="blue">blue</li>
</ul>
Here is what I tried but no luck
public static function get_ads_order_ul(){
$items =array(array('id'=>'red','value'=>'red'),array('id'=>'blue','value'=>'blue'));
$attr = array('id' => 'sortable');
return Html::ul($items, $attr);
}
I ended up adding new function in /fuel/core/classes/html.php object.
$items = array('red'=>array('id'=>'red'),'blue'=>array('id'=>'blue'),'green'=> null);
$attr = array('id' => 'sortable');
# The function 'ul_custom' calls 'build_list_custom' instead of 'build_list'
return Html::ul_custom($items, $attr);
protected static function build_list_custom($type = 'ul', array $list = array(), $attr = false, $indent = '')
{
if ( ! is_array($list))
{
$result = false;
}
$out = '';
foreach ($list as $key => $li_attr)
{
$out .= $indent."\t".html_tag('li', $li_attr, $key).PHP_EOL;
}
$result = $indent.html_tag($type, $attr, PHP_EOL.$out.$indent).PHP_EOL;
return $result;
}
result
<ul id="sortable" class="ui-sortable">
<li id="red">red</li>
<li id="blue">blue</li>
<li>green</li>
</ul>

PHP array with levels to HTML tree

I have an array in php that looks kind of like this:
array[x][0]=item name
array[x][1]=level
so, for example,
array[0][0]="Node 1"
array[0][1]=0
array[1][0]="Node 2"
array[1][1]=0
array[2][0]="Node 2.1"
array[2][1]=1
array[3][0]="Node 2.1.1"
array[3][1]=2
array[4][0]="Node 2.2"
array[4][1]=1
I need to turn it into an html ul list, but one that looks like this:
<ul>
<li>
<input type="checkbox" /><span>Node 1</span>
</li>
<li>
<input type="checkbox" /><span>Node 2</span>
<ul>
<li>
<input type="checkbox" /><span>Node 2.1</span>
<ul>
<li><input type="checkbox"><span>Node 2.1.1</span></li>
</ul>
</li>
<li>
<input type="checkbox" /><span>Node 2.2</span>
</li>
</ul>
</li>
</ul>
The biggest catch is the position of the </li> tag - it needs to be after the next <ul> tag. So it's <li> current node <ul><li> child node </li></ul></li>. It's that last </li> that gets me.
This format is necessary to work with the jquery plugin I'm using (https://github.com/daredevel/jquery-tree).
I've seen the suggestions here: create html list from array with levels but that doesn't work due to the extra .
I think there's a solution to this using recursion and array slicing, but that seems inefficient. So far, my attempts at an iterative solution have failed...
You can use this function to convert your array to a nested HTML list (echo nestedHtmlList($array);)
function nestedHtmlList($array) {
$depth = 0;
$result = "";
foreach ($array as $node) {
$newDepth = $node[1];
if ($newDepth > $depth) {
$result .= "<ul><li>";
} else if ($newDepth < $depth) {
for ($d = $newDepth; $d < $depth; $d++) {
$result .= "</li></ul>";
}
$result .= "</li><li>";
} else if ($result == "") {
$result .= "<ul><li>";
} else {
$result .= "</li><li>";
}
$result .= "<input type='checkbox' /><span>";
$result .= $node[0];
$result .= "</span>";
$depth = $newDepth;
}
while ($depth > 0) {
$result .= "</li></ul>";
$depth -= 1;
}
return $result;
}
Tell me what you think:
Code
$array = [];
$array[0][0]="Node 1";
$array[0][1]=0;
$array[1][0]="Node 2";
$array[1][1]=0;
$array[2][0]="Node 2.1";
$array[2][1]=1;
$array[3][0]="Node 2.1.1";
$array[3][1]=2;
$array[4][0]="Node 2.2";
$array[4][1]=1;
function makeList( &$array, $level = null ) {
$html = '<ul>';
$html .= '<li>Level: ' . $level;
while( $element = array_shift( $array ) ) {
if( $level == $element[1] ) {
$html .= '</li><li><label><input type="checkbox"/> ' . $element[0] . '</label>';
} else if( $level < $element[1] ) {
array_unshift( $array, $element );
$html .= makeList( $array, $element[1] ) . '</li>';
} else {
array_unshift( $array, $element );
break;
}
}
$html .= '</ul>';
return $html;
}
echo '<pre>';
echo makeList($array);
Output (human rendered html :D)
Level:
[] Node 1
[] Node 2
Level: 1
[] Node 2.1
Level: 2
[] Node 2.1.1
[] Node 2.2

PHP Recursive bootstrap dropdown menu

I use this code to create a bootstrap dropdown for an infinte menu
<ul class="nav">
<?php
$menu_items = array(
(object) array('id'=>'1', 'name'=>'Electronics', 'parent_menu_id'=>'0'),
(object) array('id'=>'2', 'name'=>'Books', 'parent_menu_id'=>'0'),
(object) array('id'=>'3', 'name'=>'Toys', 'parent_menu_id'=>'0'),
(object) array('id'=>'4', 'name'=>'Kitchen', 'parent_menu_id'=>'0'),
(object) array('id'=>'5', 'name'=>'Apparel', 'parent_menu_id'=>'0'),
(object) array('id'=>'6', 'name'=>'Shirts', 'parent_menu_id'=>'5'),
(object) array('id'=>'7', 'name'=>'Pants', 'parent_menu_id'=>'5'),
(object) array('id'=>'8', 'name'=>'Hats', 'parent_menu_id'=>'5'),
(object) array('id'=>'9', 'name'=>'Gloves', 'parent_menu_id'=>'5'),
(object) array('id'=>'10', 'name'=>'Ballcaps', 'parent_menu_id'=>'8'),
(object) array('id'=>'11', 'name'=>'Beanies', 'parent_menu_id'=>'8'),
(object) array('id'=>'12', 'name'=>'Wool', 'parent_menu_id'=>'11'),
(object) array('id'=>'13', 'name'=>'Polyester', 'parent_menu_id'=>'11'),
(object) array('id'=>'14', 'name'=>'Jimsider.com', 'parent_menu_id'=>'4'),
);
global $menuItems;
global $parentMenuIds;
//create an array of parent_menu_ids to search through and find out if the current items have an children
foreach($menu_items as $parentId)
{
$parentMenuIds[] = $parentId->parent_menu_id;
}
//assign the menu items to the global array to use in the function
$menuItems = $menu_items;
//recursive function that prints categories as a nested html unorderd list
function generate_menu($parent)
{
$has_childs = false;
//this prevents printing 'ul' if we don't have subcategories for this category
global $menuItems;
global $parentMenuIds;
//use global array variable instead of a local variable to lower stack memory requierment
foreach($menuItems as $key => $value)
{
if ($value->parent_menu_id == $parent)
{
//if this is the first child print '<ul>'
if ($has_childs === false)
{
//don't print '<ul>' multiple times
$has_childs = true;
if($parent != 0)
{
echo '<ul class="dropdown-menu">';
}
}
if($value->parent_menu_id == 0 && in_array($value->id, $parentMenuIds))
{
echo '<li class="dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#">' . $value->name . '<b class="caret"></b></a>';
}
else if($value->parent_menu_id != 0 && in_array($value->id, $parentMenuIds))
{
echo '<li class="dropdown-submenu">' . $value->name . '';
}
else
{
echo '<li>' . $value->name . '';
}
generate_menu($value->id);
//call function again to generate nested list for subcategories belonging to this category
echo '</li>';
}
}
if ($has_childs === true) echo '</ul>';
}
generate_menu(0);
?>
<!-- End Dynamic Nav -->
</li>
</ul>
But The output comes as
<ul class="nav">
<li>Electronics</li>
<li>Books</li>
<li>Toys</li>
<li class="dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#">Kitchen<b class="caret"></b></a>
<ul class="dropdown-menu">
<li>Jimsider.com</li>
</ul>
</li>
.......
</ul>
All the menu item with parent_menu_id = 0 gets listed as menu , instead I need everything listed under one menu 'Categories'
function toULlistproduct($arr)
{
$html = '<ul>'.PHP_EOL;
foreach ($arr as $k => $v)
{
if(is_array($v)){
$html .= '<li><a href="#" id="' . $k . '" >'.$k.'</a></li>' ;
$html .= toULlistproduct($v);
}else{
$html .= '<li><a href="#" id="' . $k . '" >'.$v.'</a></li>' ;
}
}
$html .= '</ul>'.PHP_EOL;
return $html;
}
$arr = array(
'a' => array(1,2,3,4,5,6,7,8,9,...),
);
echo toULlistproduct($arr);

Make a php function recursive

I have written a function for a multilevel wordpress menu, but I'd like it to work for any number of levels, at the moment it is written to work for 3 levels.
//only gets the top level items
$top_level_pages = get_pages('parent=0&sort_column=menu_order&exclude=129,2,13');
foreach($top_level_pages as $page){
//print_r($top_level_pages);
$p_id = $page->ID;
// gets all pages and subpages in one array
$children = get_pages("child_of=$p_id&sort_column=menu_order");
$immediate_children = get_pages("child_of=$p_id&parent=$p_id&sort_column=menu_order");
//print_r($immediate_children);
if($children) {
print '<li class="page_item page-item-'.$page->ID.'"><span class="first-level">'.$page->post_title;
print '</span><ul>';
foreach($immediate_children as $child){
$c_id = $child->ID;
//gets a preformatted menu
$grandchildren = wp_list_pages('depth=1&echo=0&parent='.$c_id.'&sort_column=menu_order&title_li=');
if($grandchildren) {
print '<li class="page_item page-item-'.$child->ID.'"><span class="second-level">'.$child->post_title;
print '</span><ul>';
print $grandchildren;
print '</ul></li>';
}
else {
print '<li class="page_item page-item-'.$child->ID.'">'.$child->post_title.'</li>';
}
}
print '</ul></li>';
}
else {
print '<li class="page_item page-item-'.$page->ID.'">'.$page->post_title.'</li>';
}
}
All we need to do to make your function recursive is move most of the loop into a recursive function:
<?php
//only gets the top level items
$top_level_pages = get_pages('parent=0&sort_column=menu_order&exclude=129,2,13');
foreach($top_level_pages as $page) {
//print_r($top_level_pages);
$p_id = $page->ID;
recursiveFunction($p_id);
}
function recursiveFunction($p_id){
$children = get_pages("child_of=$p_id&sort_column=menu_order");
$immediate_children = get_pages("child_of=$p_id&parent=$p_id&sort_column=menu_order");
//print_r($immediate_children);
if($children) {
print '<li class="page_item page-item-'.$page->ID.'"><span class="first-level">'.$page->post_title;
print '</span><ul>';
foreach($immediate_children as $child) {
recursiveFunction($child->ID);
}
print '</ul></li>';
}
else {
print '<li class="page_item page-item-'.$page->ID.'">'.$page->post_title.'</li>';
}
}
?>
The hard part is making the "first-level", "second-level" stuff work. I'd just change it "level-1", "level-2", etc. And then you can just start with $x = 1; and pass $x+1 each time you call the function recursively.
Something like this should probably do the trik (written in the stackoverflow textbox and untested).
function listChildren($parentID, &$menu)
{
static $options = 'parent=0&sort_column=menu_order&exclude=129,2,13';
static $level = 1;
foreach(get_pages(sprintf('child_of=%d&%s', $parentID, $options ) as $page)
{
$menu .= sprintf('<li class="level-%d">%s',
$level
get_page_link($page->ID),
$page->post_title);
if ($page->hasChildrenOrWhateverWPUses())
{
$level ++;
$menu .= sprintf('<ul class="menu-level-%d">', $level);
listChildren($page->ID, $menu);
$menu .= '</ul>';
$level --;
}
$menu .="</li>"
}
}
used like:
$menu = '<ul class="menu-tree">';
listChildren($rootPageID, $menu);
$menu .= '</ul>';
echo $menu;

Php: Generate ul-li tree from active directory results

I've got a result set from adLDAP of the form
OU=LEAF1,OU=PARENT1,OU=ROOT,DC=datacenter,DC=local
OU=PARENT1,OU=ROOT,DC=datacenter,DC=local
OU=ROOT,DC=datacenter,DC=local
OU=LEAF2,OU=CHILD,OU=PARENT2,OU=ROOT,DC=datacenter,DC=local
OU=CHILD,OU=PARENT2,OU=ROOT,DC=datacenter,DC=local
OU=PARENT2,OU=ROOT,DC=datacenter,DC=local
Where each line is a string element in an array.
The tree structure it represents is :
Root
|--Parent1
|--Leaf1
|--Parent2
|--Child
|--Leaf2
and I want to generate this
<ul>
<li>root
<ul>
<li>Parent1
<ul>
<li>leaf1</li>
</ul>
</li>
<li>Parent2
<ul>
<li>Child
<ul>
<li>Leaf2</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
</ul>
I know I need to process the strings backwards, and I know the solution is recursive, but it's friday afternoon, it's a long time since I've done it and my brain is stuck.
Here is my attempt:
<?php
$lines = array(
'OU=LEAF1,OU=PARENT1,OU=ROOT,DC=datacenter,DC=local',
'OU=PARENT1,OU=ROOT,DC=datacenter,DC=local',
'OU=ROOT,DC=datacenter,DC=local',
'OU=LEAF2,OU=CHILD,OU=PARENT2,OU=ROOT,DC=datacenter,DC=local',
'OU=CHILD,OU=PARENT2,OU=ROOT,DC=datacenter,DC=local',
'OU=PARENT2,OU=ROOT,DC=datacenter,DC=local',
);
//build tree structure
$tree = array();
foreach ($lines as $line) {
$ancestry = getLineAncestry($line);
$node = & $tree;
foreach ($ancestry as $nodeName) {
if (! isset($node[$nodeName])) {
$node[$nodeName] = array();
}
$node = & $node[$nodeName];
}
}
print makeUl($tree);
//recurse through tree to build unordered-list
function makeUl($array) {
$result = '<ul>';
foreach ($array as $nodeName => $children) {
$result .= '<li>' . ucfirst($nodeName);
if (count($children)) {
$result .= makeUl($children);
}
$result .= '</li>';
}
$result .= '</ul>';
return $result;
}
function getLineAncestry($line) {
$result = array();
$params = explode(',', $line);
foreach ($params as $param) {
$tmp = explode('=', $param);
if ($tmp[0] == 'OU') {
$result[] = $tmp[1];
}
}
$result = array_reverse($result);
return $result;
}

Categories