Make a php function recursive - php

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;

Related

Recursive foreach() producing duplicate results when looping

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.

Replace PHP code in Wordpress functions.php

I'm trying to add a last item to the wordpress primary menu. So here is my question:
I have a code in functions.php that works well:
function add_last_nav_item($items) {
return $items .= '<li><a href="#" >Contact</a></li>';
}
add_filter('wp_nav_menu_items','add_last_nav_item');
I need to replace which is inside the <li> tags with this:
<li><?php if(function_exists(wp_forecast)) { wp_forecast( wp_forecast("A") ); } ?></li>
I tried but it doesn't work.
Try this:
function add_last_nav_item($items) {
ob_start();
echo '<li>';
if (function_exists('wp_forecast')) {
wp_forecast( wp_forecast("A") );
}
echo '</li>';
$end = ob_get_clean();
$items = $items . $end;
return $items;
}
add_filter('wp_nav_menu_items','add_last_nav_item');

Php Multidimensional array for navigation

Needed Navigation Html
Home
Pages
About
Services
Products
Contact
FAQs
Sitemap
Privacy Policy
Column Layouts
1 Column
2 Column (Left Sidebar)
2 Column (Right Sidebar)
3 Column
4 Column
I want to use php arrays and foreach loops to output the needed html.
The php code I have thus far is:
<?php
$data = array("navigation");
$data['navigation']['Home'] = base_url();
$data['navigation']['Pages'] = base_url('pages');
$data['navigation']['Pages']['About'] = base_url('pages/about');
echo '<ul>';
foreach($data as $nav) {
foreach($nav as $subNavKey => $subNavHref) {
echo "<li><a href='$subNavHref'>$subNavKey</a>";
}
}
echo '</ul>';
?>
I was thinking I would need three foreach loops nested but php warnings/errors are generated when the third loop is reached on lines such as:
$data['navigation']['Home'] = base_url();
$data['navigation']['Pages'] = base_url('pages');
I'm not quite sure how to test for 3rd level depths such as:
$data['navigation']['Pages']['About'] = base_url('pages/about');
Also, outputting the needed li and ul tags in the proper positions has given me trouble aswell.
Use recursion
$data['navigation']['Home'] = base_url();
$data['navigation']['Pages'] = base_url('pages');
$data['navigation']['Pages']['About'] = base_url('pages/about');
$data['navigation']['Pages']['About']['Team'] = base_url('pages/team');
$data['navigation']['Pages']['About']['Team']['Nate'] = base_url('pages/nate');
echo "<ul>"
print_list($data);
echo "</ul>"
function print_list($menu) {
foreach($menu as $key=>$item) {
echo "<li>";
if(is_array($item)) {
echo "<ul>";
print_list($item);
echo "</ul>";
} else {
echo "<a href='{$val}'>$key</a>";
}
echo "</li>";
}
}
<?php
function nav($data) {
$html = '<ul>';
foreach ($data as $k => $v) {
if (is_array($v)) {
$html .= "<li>$k" . nav($v) . "</li>";
}
else {
$html .= "<li><a href='$k'>$v</a>";
}
}
$html .= '</ul>';
return $html;
}
echo nav($data);
A recursive function can get the job done:
$items = array(
"Home",
"Pages" => array(
"About",
"Services",
"Products",
"Contact",
"FAQs",
"Sitemap",
"Privacy Policy",
"Column Layouts" => array(
"1 Column",
"2 Column (Left Sidebar)",
"2 Column (Right Sidebar)",
"3 Column",
"4 Column"
)
)
);
function getMenu($array) {
foreach($array as $key => $value) {
if(is_array($value)) {
echo "<li>" . $key . "</li>";
echo "<ul>";
getMenu($value);
echo "</ul>";
} else {
echo "<li>" . $value . "</li>";
}
}
}
echo "<ul>";
getMenu($items);
echo "</ul>";
Output:
You should use a recursive function, for example (Working Demo):
function makeMenu($array)
{
$menu = '';
foreach($array as $key => $value) {
if(is_array($value)) {
$menu .= '<li>' . $key . '<ul>' . makeMenu($value) . '</ul></li>';
}
else {
$menu .= "<li><a href='". $value ."'>" . $value ."</a></li>";
}
}
return $menu;
}
Then call it like:
$data = array(
"Home",
"Pages" => array("About", "Services"),
"Column Layouts" => array("1 Column", "2 Column (Left Sidebar)")
);
echo '<ul>' . makeMenu($data) . '</ul>';

Is there a good way to separate categories from subcategories in wp

Here are my costume functions that I wrote for the menu and submenu in my wordpress theme, but after I tested it the submenu disappeared when I clicked on a submenu, because wordpress doesnt separate categories from subcategories, so the parameter for them is "cat", which means that when I click on a submenu then the function that creates the submenu checks if cat=id in the url has child categories but it doesnt becasue it is a child category, I am new into wordpress and I dont know how to deal with it:
function costume_menu() {
$categories = get_categories('hide_empty=0&style=none&parent=0');
foreach ($categories as $category) {
(is_category($category->term_id)) ? $active = 'class="active_menu"' : $active = '';
$nav = '<li>';
$nav .= '<a '.$active.'href="'.get_category_link($category->term_id).'">'.strtoupper($category->cat_name).'</a>';
$nav .= '</li>';
echo $nav;
}
}
function costume_submenu($cat) {
$categories = get_categories("child_of=$cat&hide_empty=0");
foreach ($categories as $category) {
(is_category($category->term_id)) ? $active = 'class="active_menu"' : $active = '';
$nav = '<li>';
$nav .= '<a '.$active.'href="'.get_category_link($category->term_id).'">'.strtoupper($category->cat_name).'</a>';
$nav .= '</li>';
echo $nav;
}
}
Try the function like this:
function costume_submenu($cat) {
$current_cat=get_category($cat);
if($current_cat->parent==0)
$parent_cat=$cat;
else
$parent_cat=$current_cat->parent;
$categories = get_categories("child_of=$parent_cat&hide_empty=0");
foreach ($categories as $category) {
(is_category($category->term_id)) ? $active = 'class="active_menu"' : $active = '';
$nav = '<li>';
$nav .= '<a '.$active.'href="'.get_category_link($category->term_id).'">'.strtoupper($category->cat_name).'</a>';
$nav .= '</li>';
echo $nav;
}
}

How to increment $var value without a loop in PHP?

I have this issue where i want to increase a value with 1 and apply this to my HTML, but i can't use a for() or while() loop (at least i think i can't). I'm customizing an e-merchandise program (opencart) and my php knowledge isn't enough to tackle the problem.
There is this function which displays the categories from the store. It uses a variable that is constantly updated via $var .= "value".
I'm so far that i know how many sub-categories there are, but i don't know how to apply this range to my HTML.
I'm working towards a situation like below
<ul id="cats">
<li id="cat1">Cat
<ul id="sub1">
<li>item</li>
<li>item</li>
</ul>
</li>
<li id="cat2">Cat
<ul id="sub2">
<li>item</li>
<li>item</li>
</ul>
</li>
</ul>
I don't have a clue how to increase the count of the second unordered lists. Below the code where the second unordered lists are generated.
[..]
$cPiD = strlen($parent_id);
if ($results) {
if ($parent_id == 0) {
$output .= '<ul id="cats">';
} else {
$output .= '<ul id="sub'.$cPiD.'">';
}
}
[..]
The variable $cPiD holds the total amount of sub categories (in this case 2). I want this variable to automatically apply the correct number to the unordered list (so apply id="sub1" to the first unordered list and id="sub2" tot he second one (as in my example above)).
The problem is that i can't use a for() loop after the else part, because in my HTML i wil get two <ul> tags instead of one.
Below the PHP code where it all happens
$category_id = array_shift($this->path);
$output = '';
$results = $this->model_catalog_category->getCategories($parent_id);
$count = 0;
$cPiD = strlen($parent_id);
if ($results) {
if ($parent_id == 0) {
$output .= '<ul id="cats">';
} else {
$output .= '<ul id="sub'.$cPiD.'">';
}
}
foreach ($results as $result) {
$count++;
if (!$current_path) {
$new_path = $result['category_id'];
$output .= '<li id="cat'.$count.'">';
} else {
$new_path = $current_path . '_' . $result['category_id'];
$output .= '<li>';
}
$children = '';
$children = $this->getCategories($result['category_id'], $new_path);
$output .= $result['name'];
$output .= $children;
if (!$current_path) {
$output .= '</li>';
} else {
$output .= '</li>';
}
}
if ($results) {
if ($parent_id == 0) {
$output .= '</ul>';
} else {
$output .= '</ul>';
}
}
Does anybody maybe have an idea how to solve this?
EDIT:
Oh, i tries adding the following construction in the foreach() loop, but that gave problems when a certain categories don't have any sub categories.
if (!$current_path) {
$output .= '$result['name'] . ' <ul id="sub'.$count.'">';
}else{
$output .= $result['name'];
}
You can use this:
// at the top of your code (ouside of the loop)
$cPiD = 1;
// inside the loop you need to increment the parameter
$output .= '<ul id="sub'.$cPiD++.'">';
After each time the item is used, its value will be incremented by 1. (after already using it)

Categories