I have created a menu which have several items (WooCommerce categories), each of them having few child items (WooCommerce products).
I'm struggling to retrieve the sub items of a parent menu item.
Im getting the parent item using this code:
$the_menu = wp_get_nav_menu_object('Some Menu');
$the_menu_items = wp_get_nav_menu_items($the_menu);
foreach ($the_menu_items as $index => $menu_item) {
if ($menu_item->object_id == $category->term_id ) {
$category_submenu = $menu_item;
}
}
How can I retrieve the child items of current parent item?
Thank you in advance!
You need a recursion for all childs.
Here the example
if ( ! function_exists( 'recursive_mitems_to_array' ) ) {
/**
* #param $items
* #param int $parent
*
* #return array
*/
function recursive_mitems_to_array( $items, $parent = 0 )
{
$bundle = [];
foreach ( $items as $item ) {
if ( $item->menu_item_parent == $parent ) {
$child = recursive_mitems_to_array( $items, $item->ID );
$bundle[ $item->ID ] = [
'item' => $item,
'childs' => $child
];
}
}
return $bundle;
}
}
Usage:
$items = wp_get_nav_menu_items( "Default Theme Menu" ); // Your menu title
$build_tree = recursive_mitems_to_array( $items );
var_dump(build_tree);
Note: correct for your needs.
maybe you need to output html with values.
This example should return nested array (not fully tested)
Here is my solution. Looping through menu "MENU NAME" and then showing only items that have the parent PARENT_ID.
<ul>
<? foreach (wp_get_nav_menu_items('MENU_NAME') as $r) {
if ($r->menu_item_parent == PARENT_ID) { echo '<li>'.$r->title.'</li>'; }
} ?>
</ul>
As a workaround I have this temporary solution:
$the_menu = wp_get_nav_menu_object('Some Menu');
$the_menu_items = wp_get_nav_menu_items($the_menu);
$category_products = [];
foreach ($the_menu_items as $index => $menu_category) {
if ($menu_category->object_id == $category->term_id ) {
$category_submenu = $menu_category;
}
}
foreach ($the_menu_items as $index => $menu_product) {
if ($menu_product->type_label == 'Product'
&& $menu_product->menu_item_parent == $category_submenu->ID
) {
$category_products[] = $menu_product;
}
}
I'm parsing all menu items (both parent items and sub items), getting the necessary item based on current category ID and then retrieving the sub items (products) of each category by parsing all the items one more time and checking if current item (product) parent ID matches with the parent category item ID and I just put it into an array.
Related
I am adding in my main WordPress menu to WooCommerce product category menu items, the children subcategory terms as submenu items with the following code and it works.
The code:
add_filter("wp_get_nav_menu_items", function ($items, $menu, $args) {
// don't add child categories in administration of menus
if (is_admin()) {
return $items;
}
foreach ($items as $index => $i) {
if ("product_cat" !== $i->object) {
continue;
}
$term_children = get_term_children($i->object_id, "product_cat");
// add child categories
foreach ($term_children as $index2 => $child_id) {
$child = get_term($child_id);
$url = get_term_link($child);
$e = new \stdClass();
$e->title = $child->name;
$e->url = $url;
$e->menu_order = 500 * ($index + 1) + $index2;
$e->post_type = "nav_menu_item";
$e->post_status = "published";
$e->post_parent = $i->ID;
$e->menu_item_parent = $i->ID;
$e->type = "custom";
$e->object = "custom";
$e->description = "";
$e->object_id = 0;
$e->db_id = 0;
$e->ID = 0;
$e->position = 0;
$e->classes = array();
$items[] = $e;
}
}
return $items;
}, 10, 3);
But I would like to sort that submenu items alphabetically in Ascending order and I didn't find a way to do it yet. How can I sort that submenu items by name (alphabetically) in Ascending order?
An additional foreach loop is required, to sort category sub menu items by name. Also there was some little oversights in your code (to avoid some errors). To finish you should always name your hooked functions for many different reasons.
The revisited code:
add_filter( 'wp_get_nav_menu_items', 'custom_submenu_product_categories', 10, 3 );
function custom_submenu_product_categories( $items, $menu, $args ) {
// don't add child categories in administration of menus
if (is_admin()) {
return $items;
}
$taxonomy = 'product_cat';
foreach ($items as $index => $post) {
if ( $taxonomy !== $post->object ) {
continue;
}
$children_terms_ids = get_term_children( $post->object_id, $taxonomy );
$unsorted_terms = array(); // Initializing
// Loop through children terms to prepare them to be sorted by name
foreach ( $children_terms_ids as $child_id ) {
$child_term = get_term( $child_id, $taxonomy );
$unsorted_terms[$child_term->name] = $child_term; // Set each term in an array for sorting
}
ksort($unsorted_terms); // Sort menu sub-items by term name ASC
$index2 = 0; // Initializing
// Loop through sorted child terms to set them as sorted sub menu items
foreach ( $unsorted_terms as $child_term_name => $child_term ) {
$item = new \stdClass();
$item->title = $child_term_name;
$item->url = get_term_link( $child_term, $taxonomy );
$item->menu_order = 500 * ($index + 1) + $index2;
$item->post_type = 'nav_menu_item';
$item->post_status = 'published';
$item->post_parent = $post->ID;
$item->menu_item_parent = $post->ID;
$item->type = 'custom';
$item->object = 'custom';
$item->description = '';
$item->object_id = 0;
$item->db_id = 0;
$item->ID = 0;
$item->position = 0;
$item->classes = array();
$item->target = ''; // <= Missing - Mandatory to avoid an error
$item->xfn = ''; // <= Missing - Mandatory to avoid an error
$items[] = $item;
$index2++;
}
}
return $items;
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
I have this loop that simply shows all child pages of the current page:
<?php
$args = array(
'parent' => $post->ID,
'post_type' => 'page',
'sort_order' => 'ASC'
);
$pages = get_pages($args); ?>
<?php foreach( $pages as $page ) { ?>
<div>
<p><?php echo $page->post_title; ?></p>
</div>
<?php } ?>
The Nav for this page looks like this:
Parent Page
- Child page
- Child page
- Child page
- Custom Link (added in appearance > menus)
- Custom link (added in appearance > menus)
- Page which has another parent (added in appearance > menus)
The code above correctly shows all of the direct child pages, but I would like it to show the custom links and other page I have added to the menu dropdown.
Ive tried playing with wp_get_nav_menu_items in place of get_pages and also using 'post_type' => 'page' but I can't seem to get this working correctly. I can either show a full list of all pages or just the direct child pages.
Can anyone tell me where I'm going wrong please? I seems like it should be a really easy thing to do...
Ok, I've found a way to get this to work using a custom walker class inside the functions.php file, like so:
class Selective_Walker extends Walker_Nav_Menu
{
function walk( $elements, $max_depth) {
$args = array_slice(func_get_args(), 2);
$output = '';
if ($max_depth < -1) //invalid parameter
return $output;
if (empty($elements)) //nothing to walk
return $output;
$id_field = $this->db_fields['id'];
$parent_field = $this->db_fields['parent'];
// flat display
if ( -1 == $max_depth ) {
$empty_array = array();
foreach ( $elements as $e )
$this->display_element( $e, $empty_array, 1, 0, $args, $output );
return $output;
}
/*
* need to display in hierarchical order
* separate elements into two buckets: top level and children elements
* children_elements is two dimensional array, eg.
* children_elements[10][] contains all sub-elements whose parent is 10.
*/
$top_level_elements = array();
$children_elements = array();
foreach ( $elements as $e) {
if ( 0 == $e->$parent_field )
$top_level_elements[] = $e;
else
$children_elements[ $e->$parent_field ][] = $e;
}
/*
* when none of the elements is top level
* assume the first one must be root of the sub elements
*/
if ( empty($top_level_elements) ) {
$first = array_slice( $elements, 0, 1 );
$root = $first[0];
$top_level_elements = array();
$children_elements = array();
foreach ( $elements as $e) {
if ( $root->$parent_field == $e->$parent_field )
$top_level_elements[] = $e;
else
$children_elements[ $e->$parent_field ][] = $e;
}
}
$current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' ); //added by continent7
foreach ( $top_level_elements as $e ){ //changed by continent7
// descend only on current tree
$descend_test = array_intersect( $current_element_markers, $e->classes );
if ( !empty( $descend_test ) )
$this->display_element( $e, $children_elements, 2, 0, $args, $output );
}
/*
* if we are displaying all levels, and remaining children_elements is not empty,
* then we got orphans, which should be displayed regardless
*/
/* removed by continent7
if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
$empty_array = array();
foreach ( $children_elements as $orphans )
foreach( $orphans as $op )
$this->display_element( $op, $empty_array, 1, 0, $args, $output );
}
*/
return $output;
}
}
Then using this to my template page to simply the menu:
<?php
$menuParameters = array(
'theme_location' =>'primary',
'walker'=>new Selective_Walker()
);
echo wp_nav_menu( $menuParameters );
?>
This helpfully shows the current page title as well as ALL of the sub nav items, not just the page items.
I would like to know how to retrieve as variable the name of the current page.
For exemple, My menu is like :
Item 1
Item 2 => sub-item 1
Item 1 & 2 are custom links. sub-item1 is my page.
When I'm on this page, I want to retrieve "Item 2" name (to build my custom breadcumbs)
Thanks
BREADCRUMBS:
Here is a breadcrumbs function that work with the hierarchy of your menu. Add this to your theme's 'functions.php'.
function my_menu_breadcrumb($theme_location, $separator = ' > ') {
$locations = get_nav_menu_locations();
if ( isset( $locations[ $theme_location ] ) ) {
$menu = wp_get_nav_menu_object( $locations[ $theme_location ] );
$menu_items = wp_get_nav_menu_items($menu->term_id);
_wp_menu_item_classes_by_context( $menu_items );
$breadcrumbs = array();
foreach ( $menu_items as $menu_item ) {
if ($menu_item->current) {
$breadcrumbs[] = "<span title=\"{$menu_item->title}\">{$menu_item->title}</span>";
}
else if ($menu_item->current_item_ancestor) {
$breadcrumbs[] = "{$menu_item->title}";
}
}
echo implode($separator, $breadcrumbs);
}
}
You can then call as <?php my_menu_breadcrumb('header-menu'); ?> where 'header-menu' is the menu location name. In the loop, $menu_item->title will return page title and $menu_item->url it's URL.
PARENT MENU TITLE:
Here is the function to get parent menu item title(s) of current page - add this to your theme's 'functions.php'.
function my_menu_parent($theme_location) {
$locations = get_nav_menu_locations();
if ( isset( $locations[ $theme_location ] ) ) {
$menu = wp_get_nav_menu_object( $locations[ $theme_location ] );
$menu_items = wp_get_nav_menu_items($menu->term_id);
_wp_menu_item_classes_by_context( $menu_items );
$breadcrumbs = array();
foreach ( $menu_items as $menu_item ) {
if ($menu_item->current_item_ancestor) {
$breadcrumbs[] = $menu_item->title;
}
}
return $breadcrumbs;
}
}
You can then call as below where 'header-menu' is the menu location name.
$parentitems = my_menu_parent( 'header-menu' );
foreach ( $parentitems as $parentitem ) {
echo $parentitem."<br>";
}
You can user this
$pagename = get_query_var('pagename');
if ( !$pagename && $id > 0 ) {
// If a static page is set as the front page, $pagename will not be set. Retrieve it from the queried object
$post = $wp_query->get_queried_object();
$pagename = $post->post_name;
}
There are plenty of tutorials and code snippets online describing how to show the active page’s children (or if on one of those children, the active page’s siblings) as a sub-menu in WordPress. Usually something like this:
<?php
$ref_post = empty($post->post_parent) ? $post->ID : $post->post_parent;
$children = wp_list_pages('title_li=&child_of='.$ref_post.'&echo=0');
if ($children) {
echo "<ul>$children</ul>";
}
?>
for ref:-https://www.minddevelopmentanddesign.com/blog/showing-current-pages-parents-sub-menu-items-custom-nav-menu-wordpress/
I want to create a drop down menu on first it shows main categories then all child categories with all of their sub child list like the one shown in below pic desired drop down menu
I am using a readymade hiearchical database. my databse structure is like my database structure
here is my false approach categoriesTest.php
<?php
include 'common.php';
$query = "SELECT cat_id, parent_id, cat_name FROM " .$DBPrefix. "categories ORDER BY cat_name";
$res = mysql_query($query);
$system->check_mysql($res, $query, __LINE__, __FILE__);
$items = mysql_fetch_assoc($res);
$html = '';
$parent = 0;
$parent_stack = array();
// $items contains the results of the SQL query
$children = array();
foreach ( $items as $item )
$children[$items['parent_id']][] = $item;
while ( $option = each( $children[$parent] ) )
{
if ( !empty( $option ) )
{
// 1) The item contains children:
// store current parent in the stack, and update current parent
if ( !empty( $children[$option['value']['id']] ) )
{
$html .= '<li>' . $option['value']['title'] . '</li>';
$html .= '<ul>';
array_push( $parent_stack, $parent );
$parent = $option['value']['id'];
}
// 2) The item does not contain children
else
$html .= '<li>' . $option['value']['title'] . '</li>';
}
// 3) Current parent has no more children:
// jump back to the previous menu level
else
{
$html .= '</ul>';
$parent = array_pop( $parent_stack );
}
}
// At this point, the HTML is already built
echo $html;
?>
the above code does not do the job.
I am quiet newbie and cant figure out.
tried all stacked questions but no success
can anyone suggest me the correct php code?
I have a category "Decouverte" witch has three subcategories : "actualité", "vidéo" and "coup de coeur".
When I sort the posts according to their subcategories, everything works except for the category "video"; the category_slug displays "decouverte".
On my dashboard, I see this :
The subcategory "Video" is displayed last, maybe it's why it doesn't work ?
My code :
$categories = get_the_category();
$category_slug = $categories[0]->slug;
echo $category_slug;
if ( $category_slug == 'actualite' ) { ?>
<div class="picto_home icone_actualite"></div><?php
}
elseif ( $category_slug == 'video' ) { ?>
<div class="picto_home icone_video"></div><?php
}
elseif ( $category_slug == 'coupdecoeur' ) { ?>
<div class="picto_home icone_coupdecoeur"></div><?php
}
else { echo "Doesn't work"; } ?>
And the website : http://www.overso.me/ It's on the right block
Problem source
It's because you have got two categories applied to all posts.
Categories in each post are sotrted alphabetically.
In your case you take $category_slug = $categories[0]->slug;, which means:
Take first category slug name from categories array
For each post it is:
$categories[0] = Decouverte, $categories[1] = Video because D < V
$categories[0] = Actualite, $categories[1] = Decouverte because A < D
$categories[0] = Coup de coeur, $categories[1] = Decouverte because C < D
How to make it work as you want
Ok so here is my approach for your problem.
Before checking for $categories[0]->slug, lets check is $categories[0] object a PARENT category. You can do this by checking:
$categories = get_the_category();
// Remove parent category object from array if it's on first place
if($categories[0]->parent == 0){
unset($categories[0]);
$categories = array_values($categories);
}
As you see if $categories[0] is a parent category (if($categories[0]->parent == 0)), then remove this result from array $categories - unset($categories[0]).
Now you can just reset indexing of this array to starting from 0, by calling $categories = array_values($categories).
From now, you can easly call $categories[0]->slug, because there is no parent category on first place for sure.
Full code:
$categories = get_the_category();
// Remove parent category object from array if it's on first place
if($categories[0]->parent == 0){
unset($categories[0]);
$categories = array_values($categories);
}
$category_slug = $categories[0]->slug;
if ( $category_slug == 'child-1' ) {
echo "1111";
}
elseif ( $category_slug == 'child-2' ) {
echo "2222";
}
elseif ( $category_slug == 'child-3' ) {
echo "3333";
}
else { echo "Doesn't work"; }
Of course change if statements logic to your data.
More general solution for future reference
Ok, here is some general solution I wrote.
If you need to get only subcategories for any post, for any number of categories applied to the post, and no matter on which place parent category will be in the categories array, you should try this.
First, add this function to your functions.php file (or to your plugin file).
function du_get_post_subcategories( $post_id ){
$categories = get_the_category( $post_id );
$i = 0;
foreach ($categories as $category) {
if($category->parent == 0){
unset($categories[$i]);
}
$i++;
}
$categories = array_values($categories);
return $categories;
}
And then inside your loop you can call
$subcategories = du_get_post_subcategories( $post->ID );
If you have post ID, you can call this outside the loop as well
$subcategories = du_get_post_subcategories( $your_post_id );
This function will return all subcategories in the same format as get_the_category() function.
If you provide array with only one, parent category. Function will return an empty array.