Skip to content

Instantly share code, notes, and snippets.

@hitautodestruct
Forked from anonymous/gist:4344870
Last active September 26, 2022 11:25
Show Gist options
  • Save hitautodestruct/4345363 to your computer and use it in GitHub Desktop.
Save hitautodestruct/4345363 to your computer and use it in GitHub Desktop.
Generate a custom structure for Wordpress menus.

This gist is for showing an example of a custom wordpress menu.

If you want to get more from the menu item simply have a look at the $item object. i.e:

// Will return a large object with lots of props like title, url, description, id etc.
var_dump( $item );

This code works on Wordpress 4.1.1 as of 31st of March 2015

<?php
$menu_name = 'main_nav';
$locations = get_nav_menu_locations();
$menu = wp_get_nav_menu_object( $locations[ $menu_name ] );
$menuitems = wp_get_nav_menu_items( $menu->term_id, array( 'order' => 'DESC' ) );
?>
<nav>
<ul class="main-nav">
<?php
$count = 0;
$submenu = false;
foreach( $menuitems as $item ):
$link = $item->url;
$title = $item->title;
// item does not have a parent so menu_item_parent equals 0 (false)
if ( !$item->menu_item_parent ):
// save this id for later comparison with sub-menu items
$parent_id = $item->ID;
?>
<li class="item">
<a href="<?php echo $link; ?>" class="title">
<?php echo $title; ?>
</a>
<?php endif; ?>
<?php if ( $parent_id == $item->menu_item_parent ): ?>
<?php if ( !$submenu ): $submenu = true; ?>
<ul class="sub-menu">
<?php endif; ?>
<li class="item">
<a href="<?php echo $link; ?>" class="title"><?php echo $title; ?></a>
</li>
<?php if ( $menuitems[ $count + 1 ]->menu_item_parent != $parent_id && $submenu ): ?>
</ul>
<?php $submenu = false; endif; ?>
<?php endif; ?>
<?php if ( $menuitems[ $count + 1 ]->menu_item_parent != $parent_id ): ?>
</li>
<?php $submenu = false; endif; ?>
<?php $count++; endforeach; ?>
</ul>
</nav>
@SpartakusMd
Copy link

Hi. Your code is buggy. If you add a category to menu a error is shown. $page->post_title change with $item->title and $link = get_page_link( $id ) with $link = $item->url;. Also $page->post_excerpt won't exist.

@karlhadwen
Copy link

SpartakusMd is right. I have to add that you must also have an 'isset' on both '($menu_items[$count + 1]->menu_item_parent)' pieces of code, or you'll get a property of non-object error. Also on the first 'isset' since you're closing the '' tag, make sure you use an else statement to close the tag even if it isn't set, or else you'll be missing a closing tag. I also tested it to multiple levels but it doesn't go that far.

@beesmart
Copy link

I've run into a problem this code, it works great until you need to add a 3rd level menu-item, at which point the html elements come undone and the child menu items don't show up. I've checked the code to see what could be causing this or what I could do to fix it but to no avail. I've posted a questions here:

http://wordpress.stackexchange.com/questions/173573/wp-custom-nav-menu-problem

If you have any advice/help I'd greatly appreciate it.

@mashro3na
Copy link

its worked with me by change

$page = get_page( $id );
$link = get_page_link( $id );
to
$title = $item->title;
$url = $item->url;
$class = $item->classes[0];

good work you saved my day! Thank you.

@karlhadwen
Copy link

@mashro3na can you elaborate? you can't just replace two variables. @hitautodestruct can you fix this?

@hitautodestruct
Copy link
Author

@karlhadwen Replaced in gist 😄

@Valonix
Copy link

Valonix commented Apr 21, 2015

if you add menu item like "LINK" you get a issue

@dmmarmol
Copy link

dmmarmol commented Jun 8, 2015

@hitautodestruct I had the same issue that @theodorocaliari. I can't print more than one submenu list and I need to nest every <ul class="submenu"> inside a parent <li>, depending on how much submenu i have.

How can i evaluate the php( $parent_id == $item->menu_item_parent ) when the $item it has more than one parent element?

@SimonBak
Copy link

Hello,

Thanks for your nice work.
Do you have an idea if i ant to add a third level more ?
Thanks.

@gnowland
Copy link

gnowland commented Oct 6, 2015

@beesmart, you're absolutely right, there's an error in the code.

Line 41 should be:

<?php if ( !isset($menu_items[ $count + 1 ]) || $menuitems[ $count + 1 ]->menu_item_parent != $parent_id && $submenu ): ?>

and Line 47 should be:

<?php if ( !isset($menu_items[ $count + 1 ]) || $menuitems[ $count + 1 ]->menu_item_parent != $parent_id ): ?>

@hitautodestruct you may want to update this in the gist.

@loorlab
Copy link

loorlab commented Oct 11, 2015

@gnowland & @hitautodestruct Thanks for your solutions !

@thedgbrt
Copy link

thedgbrt commented Dec 1, 2015

@gnowland there's an error in your correction, it should be $menuitems[ $count + 1 ] instead of $menu_items[ $count + 1 ]

@michellomp
Copy link

I made changes of @gnowland and @dagobertrenouf but the problem is the same i only get one level drop down menu
Does anybody succeed having multi level menu with this code

@christianschoenmakers
Copy link

christianschoenmakers commented Jun 13, 2017

The problem is in the ( $parent_id == $item->menu_item_parent ) statements.

While looping a third level menu-item, the parent_id is the ID of the parent item (so second level), while the $parent_id is the top-parent ID.

We've solved this to change $parent_id to an array which contains all of the previous menu-item ID's.
After that, improve the statements with the in_array($item->menu_item_parent, $parent_id) function.

@skillmatic-co
Copy link

skillmatic-co commented Jan 23, 2018

I can't seem to figure out how to put a class (like .menu-item-has-children for example) on the li that has children. Any help?

@enesaltuntas
Copy link

enesaltuntas commented Mar 31, 2018

Hi,

@hitautodestruct How can i get thee child menu items?

@mrkkr
Copy link

mrkkr commented May 15, 2018

@wespiremedia Could paste code with third level menu?

@enesaltuntas, I found custom Navwalker, third and fourth level children works -> http://cssmenumaker.com/blog/wordpress-3-drop-down-menu-tutorial/

@deepk10
Copy link

deepk10 commented Jul 30, 2018

Superb.

@deepk10
Copy link

deepk10 commented Jul 30, 2018

I have used only this code.

term_id, array( 'order' => 'DESC' ) ); ?>

And get my required output.

@ctrlsam
Copy link

ctrlsam commented Sep 8, 2018

I have modified this script and improved it if anyone is interested.
https://gist.github.com/SillySam/842d8b5cade983c194a3b8d1a0171287

@mostafa272
Copy link

Why don't you use recursion? It is a very simple way and works for the menus with multiple levels.

@speedwheel
Copy link

speedwheel commented Jan 11, 2019

Anyway made it work with unlimited levels?

@pritamnskr2019
Copy link

pritamnskr2019 commented Feb 21, 2019

url; $title = $item->title; // item does not have a parent so menu_item_parent equals 0 (false) if ( !$item->menu_item_parent ): // save this id for later comparison with sub-menu items $parent_id[] = $item->ID; ?>
  • class="first_child menuactive menuparent" class="" class="last_child menuactive menuparent"
              <?php }?>>
              <a href="<?php echo $link; ?>" class="title">
                  <?php echo $title; ?>
              </a>
          <?php endif; ?>
    
              <?php if (in_array($item->menu_item_parent, $parent_id)): ?>
    
                  <?php if ( !$submenu ): $submenu = true; ?>
    
                  <ul class="unli">
                  <?php endif; ?>
    
                      <li class="item">
                          <a href="<?php echo $link; ?>" class="title">
                              <?php echo $title; ?>
                          </a>
                          <?php
                            global $wpdb;
                            $sql = "SELECT `post_id` FROM `wp_postmeta` WHERE `meta_key` = '_menu_item_menu_item_parent' AND `meta_value` = '".$item->ID."'";
                            $results = $wpdb->get_results($sql,ARRAY_N);
                            
                            if(!empty($results )){
                              $subsubmenu = array();
                                foreach($results as $result){
                                  
                                  $subsubmenu[] = $result[0];
                                }
                              $allids = implode(",",$subsubmenu);
                              $sql2 = "SELECT `ID` FROM `wp_posts` WHERE `ID` IN($allids)";
                              $results2 = $wpdb->get_results($sql2,ARRAY_N);
                              
                                $submenus = array();
                                foreach($results2 as $result2){
                                  
                                  $submenus[] = $result2[0];
                                }
                                
                                $allpostids = implode(",",$submenus);
                                $sql3 = "SELECT `meta_value` FROM `wp_postmeta` WHERE `post_id` IN($allpostids) AND `meta_key`='_menu_item_object_id'";
                                $results3 = $wpdb->get_results($sql3,ARRAY_N);
                                
                                if(!empty($results3)){
                                  $orginalmenuitemid = array();
                                  foreach($results3 as $result3){
                                    $orginalmenuitemid[] = $result3[0];
                                  }
                                  //print_r($orginalmenuitemid);
                          ?>
                                    <ul class="unli">
                  
                          <?php        
                                    $p =1;
    
                                    foreach($orginalmenuitemid as $originalmenuitemids){
                                      //echo $originalmenuitemids;
                          ?>
                                        <li <?php if($p=1){ ?> class="first_child" <?php }elseif($p == count($originalmenuitemid)){?>
                                              class="last_child"
                                          <?php }else{?>
                                              class=""
                                          <?php }?>>
                                          <a href="<?php echo get_permalink($originalmenuitemids);?>"><span>
                                            <?php echo get_the_title($originalmenuitemids);?>
                                          </span></a>
                                        </li>
                          <?php        
                                      $p=$p+1;
                                    }
                          ?>
                                    </ul>
    
                          <?php        
                                }
                            }
                          ?>
                      </li>
    
                  <?php if ( $menuitems[ $count + 1 ]->menu_item_parent != $parent_id && $submenu ): ?>
                  </ul>
                  <?php $submenu = false; endif; ?>     
              <?php endif; ?>
    
          <?php if ( $menuitems[ $count + 1 ]->menu_item_parent != $parent_id ): ?>
          </li>                           
          <?php $submenu = false; endif; ?>
    
      <?php 
        $count++; endforeach; 
        $i++;
    
      ?>
      </ul>
    

    Third level menu

@nickbluestone98
Copy link

nickbluestone98 commented Feb 11, 2020

That code just doesn't work properly, I ended up just adding the menu items into a PHP array so I could output easily.

Add the following into your functions.php then call in your theme passing the menu of choice.

function wp_get_menu_array($current_menu) {
    $array_menu = wp_get_nav_menu_items($current_menu);
    $menu = array();
    foreach ($array_menu as $m) {
        if (empty($m->menu_item_parent)) {
            $menu[$m->ID] = array();
            $menu[$m->ID]['ID']          =   $m->ID;
            $menu[$m->ID]['title']       =   $m->title;
            $menu[$m->ID]['url']         =   $m->url;
            $menu[$m->ID]['children']    =   array();
        }
    }
    $submenu = array();
    foreach ($array_menu as $m) {
        if ($m->menu_item_parent) {
            $submenu[$m->ID] = array();
            $submenu[$m->ID]['ID']       =   $m->ID;
            $submenu[$m->ID]['title']    =   $m->title;
            $submenu[$m->ID]['url']      =   $m->url;
            $menu[$m->menu_item_parent]['children'][$m->ID] = $submenu[$m->ID];
        }
    }
    return $menu;
}

Then call in your theme $menu_items = wp_get_menu_array('main-menu');

  <nav>
      <ul>
        <?php foreach ($menu_items as $item) : ?>
          <li>
            <a href="<?= $item['url'] ?>" title="<?= $item['title'] ?>"><?= $item['title'] ?></a>
            <?php if( !empty($item['children']) ):?>
            <ul class="sub-menu">
              <?php foreach($item['children'] as $child): ?>
                <li class="b-main-header__sub-menu__nav-item">
                  <a href="<?= $child['url'] ?>" title="<?= $child['title'] ?>"><?= $child['title'] ?></a>
                </li>
              <?php endforeach; ?>
            </ul>
            <?php endif; ?>
          </li>
        <?php endforeach; ?>
     <ul>
</nav>

@mehmetsarr
Copy link

mehmetsarr commented Oct 13, 2021

Hi, has anyone found a solution for the third level menu? The third level menu does not appear on my website.
In addition, the name of the main menu appears in the submenus. I have shared the sample picture.

menu

@josh-tt
Copy link

josh-tt commented Oct 22, 2021

That code just doesn't work properly, I ended up just adding the menu items into a PHP array so I could output easily.

Add the following into your functions.php then call in your theme passing the menu of choice.

function wp_get_menu_array($current_menu) {
    $array_menu = wp_get_nav_menu_items($current_menu);
    $menu = array();
    foreach ($array_menu as $m) {
        if (empty($m->menu_item_parent)) {
            $menu[$m->ID] = array();
            $menu[$m->ID]['ID']          =   $m->ID;
            $menu[$m->ID]['title']       =   $m->title;
            $menu[$m->ID]['url']         =   $m->url;
            $menu[$m->ID]['children']    =   array();
        }
    }
    $submenu = array();
    foreach ($array_menu as $m) {
        if ($m->menu_item_parent) {
            $submenu[$m->ID] = array();
            $submenu[$m->ID]['ID']       =   $m->ID;
            $submenu[$m->ID]['title']    =   $m->title;
            $submenu[$m->ID]['url']      =   $m->url;
            $menu[$m->menu_item_parent]['children'][$m->ID] = $submenu[$m->ID];
        }
    }
    return $menu;
}

Then call in your theme $menu_items = wp_get_menu_array('main-menu');

  <nav>
      <ul>
        <?php foreach ($menu_items as $item) : ?>
          <li>
            <a href="<?= $item['url'] ?>" title="<?= $item['title'] ?>"><?= $item['title'] ?></a>
            <?php if( !empty($item['children']) ):?>
            <ul class="sub-menu">
              <?php foreach($item['children'] as $child): ?>
                <li class="b-main-header__sub-menu__nav-item">
                  <a href="<?= $child['url'] ?>" title="<?= $child['title'] ?>"><?= $child['title'] ?></a>
                </li>
              <?php endforeach; ?>
            </ul>
            <?php endif; ?>
          </li>
        <?php endforeach; ?>
     <ul>
</nav>

This works nicely, thanks. Any tips on how to approach getting the current menu item styled?

@mehmetsarr
Copy link

Bu kod düzgün çalışmıyor, kolayca çıktı alabilmek için menü öğelerini bir PHP dizisine ekledim.

Aşağıdakileri function.php dosyanıza ekleyin ve ardından seçtiğiniz menüden temanızı çağırın.

function wp_get_menu_array($current_menu) {
    $array_menu = wp_get_nav_menu_items($current_menu);
    $menu = array();
    foreach ($array_menu as $m) {
        if (empty($m->menu_item_parent)) {
            $menu[$m->ID] = array();
            $menu[$m->ID]['ID']          =   $m->ID;
            $menu[$m->ID]['title']       =   $m->title;
            $menu[$m->ID]['url']         =   $m->url;
            $menu[$m->ID]['children']    =   array();
        }
    }
    $submenu = array();
    foreach ($array_menu as $m) {
        if ($m->menu_item_parent) {
            $submenu[$m->ID] = array();
            $submenu[$m->ID]['ID']       =   $m->ID;
            $submenu[$m->ID]['title']    =   $m->title;
            $submenu[$m->ID]['url']      =   $m->url;
            $menu[$m->menu_item_parent]['children'][$m->ID] = $submenu[$m->ID];
        }
    }
    return $menu;
}

Ardından temanızı arayın $menu_items = wp_get_menu_array('main-menu');

  <nav>
      <ul>
        <?php foreach ($menu_items as $item) : ?>
          <li>
            <a href="<?= $item['url'] ?>" title="<?= $item['title'] ?>"><?= $item['title'] ?></a>
            <?php if( !empty($item['children']) ):?>
            <ul class="sub-menu">
              <?php foreach($item['children'] as $child): ?>
                <li class="b-main-header__sub-menu__nav-item">
                  <a href="<?= $child['url'] ?>" title="<?= $child['title'] ?>"><?= $child['title'] ?></a>
                </li>
              <?php endforeach; ?>
            </ul>
            <?php endif; ?>
          </li>
        <?php endforeach; ?>
     <ul>
</nav>

This code works fine but when I add 3rd level menu it gives the following warning. How can I fix this?

NOTICE: UNDEFINED INDEX: TITLE IN /HOME/ZIRKON/PUBLIC_HTML/DEMO/CLEANMAX/WP-CONTENT/THEMES/CLEANMAX-MAIN/HEADER.PHP ON LINE 70

@OmarHossamEldin
Copy link

<nav class='navbar navbar-expand-lg navbar-dark bg-dark'>
            <div class="container-fluid">
                <a class="navbar-brand" href="<?= home_url() ?>"><?= bloginfo() ?></a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                        <li class="nav-item">
                            <a class="nav-link active" aria-current="page" href="<?= home_url() ?>">Home</a>
                        </li>
                        <?php foreach (get_pages() as $page) : ?>
                            <li class="nav-item">
                                <a class="nav-link active" aria-current="page" href="<?= home_url() . '/' . $page->post_name ?>"><?= $page->post_title ?></a>
                            </li>
                        <?php endforeach; ?>
                    </ul>
                </div>
            </div>
        </nav>

@OmarHossamEldin
Copy link

<nav class='navbar navbar-expand-lg navbar-dark bg-dark'>
            <div class="container-fluid">
                <a class="navbar-brand" href="<?= home_url() ?>"><?= bloginfo() ?></a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                        <li class="nav-item">
                            <a class="nav-link active" aria-current="page" href="<?= home_url() ?>">Home</a>
                        </li>
                        <?php foreach (get_pages() as $page) : ?>
                            <li class="nav-item">
                                <a class="nav-link active" aria-current="page" href="<?= home_url() . '/' . $page->post_name ?>"><?= $page->post_title ?></a>
                            </li>
                        <?php endforeach; ?>
                    </ul>
                </div>
            </div>
        </nav>

better code readability

@mehmetsarr
Copy link

<?= $page->post_title ?>

this is not happening. main code becomes but dropdown icon disappears

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment