Convierta la salida de elementos nav_menu en una matriz multidimensional en forma de árbol

11

¿Hay alguna forma de tomar los elementos del menú de navegación como una matriz multidimensional en lugar de una matriz plana?

Por una estructura en forma de árbol me refiero a algo que preservaría la relación entre los elementos secundarios y primarios, así (esto es solo un ejemplo) ...

array(
  array(
    'post_type' => 'page',
    'post_name' => 'Home',
    'children' => array() 
  ),
  array(
    'post_type' => 'page',
    'post_name' => 'About Us',
    'children' => array(
      array(
        'post_type' => 'page',
        'post_name' => 'Our History',
        'children' => array() 
      )
    ) 
  )
)

Hay una wp_get_nav_menu_items()función, pero devuelve una matriz unidimensional con todos los elementos en el mismo nivel, que no es lo que quiero. ¿WordPress incluye una forma integrada de obtener una matriz multidimensional para mis elementos de menú? Si no, ¿cuál es la mejor manera de obtener wp_get_nav_menu_items()una estructura similar a un árbol en una matriz multidimensional en términos de rendimiento?

YemSalat
fuente
3
esa matriz unidimensional contiene todos los datos que necesita para construir un árbol si usa una función recursiva. para cada una de las ID de elementos de menú, busque otros elementos de menú con ID coincidente en el campo primario del objeto, esos serán sus elementos secundarios.
Milo
Sé que puedo hacer un árbol con él, pero me preguntaba si ya existe tal opción en wp.
YemSalat
¿Cuál es su caso de uso? La Walkerclase maneja la profundidad de los elementos del menú de navegación ordenados automáticamente, incluso si la matriz es plana.
Matt van Andel
1
Tu edición es incorrecta. Volví a editar el título (cambié un par de palabras). La salida de nav_items es una matriz plana, no es un árbol en ningún sentido. Mi caso de uso es: quiero los elementos de navegación como un árbol, para poder hacerlo solo, sin tener que usar las abstracciones rotas de WP.
YemSalat
Aclaré un poco la pregunta, para dejar más claro lo que quiero.
YemSalat

Respuestas:

21

El problema de construir un árbol a partir de una matriz plana se ha resuelto aquí con esta solución recursiva ligeramente modificada:

/**
 * Modification of "Build a tree from a flat array in PHP"
 *
 * Authors: @DSkinner, @ImmortalFirefly and @SteveEdson
 *
 * @link https://stackoverflow.com/a/28429487/2078474
 */
function buildTree( array &$elements, $parentId = 0 )
{
    $branch = array();
    foreach ( $elements as &$element )
    {
        if ( $element->menu_item_parent == $parentId )
        {
            $children = buildTree( $elements, $element->ID );
            if ( $children )
                $element->wpse_children = $children;

            $branch[$element->ID] = $element;
            unset( $element );
        }
    }
    return $branch;
}

donde agregamos el wpse_childrenatributo prefijado para evitar la colisión de nombres.

Ahora solo tenemos que definir una función auxiliar simple:

/**
 * Transform a navigational menu to it's tree structure
 *
 * @uses  buildTree()
 * @uses  wp_get_nav_menu_items()
 *
 * @param  String     $menud_id 
 * @return Array|null $tree 
 */
function wpse_nav_menu_2_tree( $menu_id )
{
    $items = wp_get_nav_menu_items( $menu_id );
    return  $items ? buildTree( $items, 0 ) : null;
}

Ahora es muy fácil transformar un menú de navegación en su estructura de árbol con:

$tree = wpse_nav_menu_2_tree( 'my_menu' );  // <-- Modify this to your needs!
print_r( $tree );

Para JSON, simplemente podemos usar:

$json = json_encode( $tree );

Para una versión ligeramente diferente, donde seleccionamos a mano los atributos, mira la primera revisión de esta respuesta aquí .

Actualización: Clase Walker

Aquí hay una idea bastante esquemática de cómo podríamos intentar conectarnos con la parte recursiva del display_element()método de la Walkerclase abstracta .

$w = new WPSE_Nav_Menu_Tree;
$args = (object) [ 'items_wrap' => '', 'depth' => 0, 'walker' => $w ];
$items = wp_get_nav_menu_items( 'my_menu' );
walk_nav_menu_tree( $items, $args->depth, $args );
print_r( $w->branch );  

donde WPSE_Nav_Menu_Treees una extensión de la Walker_Nav_Menuclase:

class WPSE_Nav_Menu_Tree extends Walker_Nav_Menu
{
   public $branch = [];

   public function display_element($element, &$children, $max_depth, $depth = 0, $args, &$output )
   {
      if( 0 == $depth )
         $this->branch[$element->ID] = $element;

      if ( isset($children[$element->ID] ) )
         $element->wpse_children = $children[$element->ID];

      parent::display_element($element, $children, $max_depth, $depth, $args, $output);
   }
}

Esto podría darnos un enfoque alternativo si funciona.

Birgire
fuente
gracias, siempre es interesante y divertido ver diferentes enfoques para la resolución de problemas: el suyo se ve muy bien +1. @ialocin
birgire
1
Lo mismo aquí, pero ya sabíamos quién votó :) ¡Explorar posibilidades es divertido! El resto a menudo es como el trabajo en línea de montaje, que es ... digamos que no es divertido.
Nicolai
Gracias, esperaba que hubiera una función WP "nativa" para esto. Esperaré un poco más para ver si alguien publica otras soluciones, de lo contrario, esta será la respuesta elegida.
YemSalat
Actualicé la respuesta con otro tipo de enfoque @YemSalat
birgire
Whoa! Eso hace que mi mente se arremoline. Nunca traté con la clase Walker antes (sé que existe) aunque esperaba que hubiera una forma más eficiente de hacerlo con un par de consultas SQL, pero realmente no quiero entrar en la estructura WP db. Por ahora, preferiría tu primer acercamiento en el que se repita wp_get_nav_menu_itemsrecursivamente.
YemSalat
3

En resumen, la función siguiente crea el árbol de objetos al colocar a los niños en una nueva propiedad de niños dentro del objeto padre.

Código:

function wpse170033_nav_menu_object_tree( $nav_menu_items_array ) {
    foreach ( $nav_menu_items_array as $key => $value ) {
        $value->children = array();
        $nav_menu_items_array[ $key ] = $value;
    }

    $nav_menu_levels = array();
    $index = 0;
    if ( ! empty( $nav_menu_items_array ) ) do {
        if ( $index == 0 ) {
            foreach ( $nav_menu_items_array as $key => $obj ) {
                if ( $obj->menu_item_parent == 0 ) {
                    $nav_menu_levels[ $index ][] = $obj;
                    unset( $nav_menu_items_array[ $key ] );
                }
            }
        } else {
            foreach ( $nav_menu_items_array as $key => $obj ) {
                if ( in_array( $obj->menu_item_parent, $last_level_ids ) ) {
                    $nav_menu_levels[ $index ][] = $obj;
                    unset( $nav_menu_items_array[ $key ] );
                }
            }
        }
        $last_level_ids = wp_list_pluck( $nav_menu_levels[ $index ], 'db_id' );
        $index++;
    } while ( ! empty( $nav_menu_items_array ) );

    $nav_menu_levels_reverse = array_reverse( $nav_menu_levels );

    $nav_menu_tree_build = array();
    $index = 0;
    if ( ! empty( $nav_menu_levels_reverse ) ) do {
        if ( count( $nav_menu_levels_reverse ) == 1 ) {
            $nav_menu_tree_build = $nav_menu_levels_reverse;
        }
        $current_level = array_shift( $nav_menu_levels_reverse );
        if ( isset( $nav_menu_levels_reverse[ $index ] ) ) {
            $next_level = $nav_menu_levels_reverse[ $index ];
            foreach ( $next_level as $nkey => $nval ) {
                foreach ( $current_level as $ckey => $cval ) {
                    if ( $nval->db_id == $cval->menu_item_parent ) {
                        $nval->children[] = $cval;
                    }
                }
            }
        }
    } while ( ! empty( $nav_menu_levels_reverse ) );

    $nav_menu_object_tree = $nav_menu_tree_build[ 0 ];
    return $nav_menu_object_tree;
}

Uso:

$nav_menu_items = wp_get_nav_menu_items( 'main-menu' );
wpse170033_nav_menu_object_tree( $nav_menu_items );

Salida:

Array
(
 [0] => WP_Post Object
  (
   [ID] => 51
   [post_author] => 1
   [post_date] => 2015-06-26 21:13:23
   [post_date_gmt] => 2015-06-26 19:13:23
   [post_content] => 
   [post_title] => 
   [post_excerpt] => 
   [post_status] => publish
   [comment_status] => open
   [ping_status] => open
   [post_password] => 
   [post_name] => 51
   [to_ping] => 
   [pinged] => 
   [post_modified] => 2015-07-29 20:55:10
   [post_modified_gmt] => 2015-07-29 18:55:10
   [post_content_filtered] => 
   [post_parent] => 0
   [guid] => http://example.com/?p=51
   [menu_order] => 1
   [post_type] => nav_menu_item
   [post_mime_type] => 
   [comment_count] => 0
   [filter] => raw
   [db_id] => 51
   [menu_item_parent] => 0
   [object_id] => 2
   [object] => page
   [type] => post_type
   [type_label] => Page
   [url] => http://example.com/example-page/
   [title] => Example-Page-1
   [target] => 
   [attr_title] => 
   [description] => 
   [classes] => Array
    (
     [0] => 
    )
   [xfn] => 
   [children] => Array
    (
     [0] => WP_Post Object
      (
       [ID] => 80
       [post_author] => 1
       [post_date] => 2015-06-27 14:03:31
       [post_date_gmt] => 2015-06-27 12:03:31
       [post_content] => 
       [post_title] => 
       [post_excerpt] => 
       [post_status] => publish
       [comment_status] => open
       [ping_status] => open
       [post_password] => 
       [post_name] => 80
       [to_ping] => 
       [pinged] => 
       [post_modified] => 2015-07-29 20:55:10
       [post_modified_gmt] => 2015-07-29 18:55:10
       [post_content_filtered] => 
       [post_parent] => 2
       [guid] => http://example.com/?p=80
       [menu_order] => 2
       [post_type] => nav_menu_item
       [post_mime_type] => 
       [comment_count] => 0
       [filter] => raw
       [db_id] => 80
       [menu_item_parent] => 51
       [object_id] => 69
       [object] => page
       [type] => post_type
       [type_label] => Page
       [url] => http://example.com/example-page/subpage-1/
       [title] => Subpage-1
       [target] => 
       [attr_title] => 
       [description] => 
       [classes] => Array
        (
         [0] => 
        )
       [xfn] => 
       [children] => Array
        (
        )
      )
    )
  )
 [1] => WP_Post Object
  (
   [ID] => 49
   [post_author] => 1
   [post_date] => 2015-06-26 21:13:23
   [post_date_gmt] => 2015-06-26 19:13:23
   [post_content] => 
   [post_title] => 
   [post_excerpt] => 
   [post_status] => publish
   [comment_status] => open
   [ping_status] => open
   [post_password] => 
   [post_name] => 49
   [to_ping] => 
   [pinged] => 
   [post_modified] => 2015-07-29 20:55:10
   [post_modified_gmt] => 2015-07-29 18:55:10
   [post_content_filtered] => 
   [post_parent] => 0
   [guid] => http://example.com/?p=49
   [menu_order] => 3
   [post_type] => nav_menu_item
   [post_mime_type] => 
   [comment_count] => 0
   [filter] => raw
   [db_id] => 49
   [menu_item_parent] => 0
   [object_id] => 41
   [object] => page
   [type] => post_type
   [type_label] => Page
   [url] => http://example.com/example-page-2/
   [title] => Example-Page-2
   [target] => 
   [attr_title] => 
   [description] => 
   [classes] => Array
    (
     [0] => 
    )
   [xfn] => 
   [children] => Array
    (
    )
  )
 [2] => WP_Post Object
  (
   [ID] => 48
   [post_author] => 1
   [post_date] => 2015-06-26 21:13:23
   [post_date_gmt] => 2015-06-26 19:13:23
   [post_content] => 
   [post_title] => 
   [post_excerpt] => 
   [post_status] => publish
   [comment_status] => open
   [ping_status] => open
   [post_password] => 
   [post_name] => 48
   [to_ping] => 
   [pinged] => 
   [post_modified] => 2015-07-29 20:55:10
   [post_modified_gmt] => 2015-07-29 18:55:10
   [post_content_filtered] => 
   [post_parent] => 0
   [guid] => http://example.com/?p=48
   [menu_order] => 4
   [post_type] => nav_menu_item
   [post_mime_type] => 
   [comment_count] => 0
   [filter] => raw
   [db_id] => 48
   [menu_item_parent] => 0
   [object_id] => 42
   [object] => page
   [type] => post_type
   [type_label] => Page
   [url] => http://example.com/example-page-3/
   [title] => Example-Page-3
   [target] => 
   [attr_title] => 
   [description] => 
   [classes] => Array
    (
     [0] => 
    )
   [xfn] => 
   [children] => Array
    (
     [0] => WP_Post Object
      (
       [ID] => 79
       [post_author] => 1
       [post_date] => 2015-06-27 14:03:31
       [post_date_gmt] => 2015-06-27 12:03:31
       [post_content] => 
       [post_title] => 
       [post_excerpt] => 
       [post_status] => publish
       [comment_status] => open
       [ping_status] => open
       [post_password] => 
       [post_name] => 79
       [to_ping] => 
       [pinged] => 
       [post_modified] => 2015-07-29 20:55:10
       [post_modified_gmt] => 2015-07-29 18:55:10
       [post_content_filtered] => 
       [post_parent] => 42
       [guid] => http://example.com/?p=79
       [menu_order] => 5
       [post_type] => nav_menu_item
       [post_mime_type] => 
       [comment_count] => 0
       [filter] => raw
       [db_id] => 79
       [menu_item_parent] => 48
       [object_id] => 70
       [object] => page
       [type] => post_type
       [type_label] => Page
       [url] => http://example.com/example-page-3/subpage-2/
       [title] => Subpage-2
       [target] => 
       [attr_title] => 
       [description] => 
       [classes] => Array
        (
         [0] => 
        )
       [xfn] => 
       [children] => Array
        (
         [0] => WP_Post Object
          (
           [ID] => 78
           [post_author] => 1
           [post_date] => 2015-06-27 14:03:31
           [post_date_gmt] => 2015-06-27 12:03:31
           [post_content] => 
           [post_title] => 
           [post_excerpt] => 
           [post_status] => publish
           [comment_status] => open
           [ping_status] => open
           [post_password] => 
           [post_name] => 78
           [to_ping] => 
           [pinged] => 
           [post_modified] => 2015-07-29 20:55:10
           [post_modified_gmt] => 2015-07-29 18:55:10
           [post_content_filtered] => 
           [post_parent] => 70
           [guid] => http://example.com/?p=78
           [menu_order] => 6
           [post_type] => nav_menu_item
           [post_mime_type] => 
           [comment_count] => 0
           [filter] => raw
           [db_id] => 78
           [menu_item_parent] => 79
           [object_id] => 76
           [object] => page
           [type] => post_type
           [type_label] => Page
           [url] => http://example.com/example-page-3/subpage-2/subpage-3/
           [title] => Subpage-3
           [target] => 
           [attr_title] => 
           [description] => 
           [classes] => Array
            (
             [0] => 
            )
           [xfn] => 
           [children] => Array
            (
             [0] => WP_Post Object
              (
               [ID] => 87
               [post_author] => 1
               [post_date] => 2015-06-27 15:27:08
               [post_date_gmt] => 2015-06-27 13:27:08
               [post_content] => 
               [post_title] => 
               [post_excerpt] => 
               [post_status] => publish
               [comment_status] => open
               [ping_status] => open
               [post_password] => 
               [post_name] => 87
               [to_ping] => 
               [pinged] => 
               [post_modified] => 2015-07-29 20:55:10
               [post_modified_gmt] => 2015-07-29 18:55:10
               [post_content_filtered] => 
               [post_parent] => 76
               [guid] => http://example.com/?p=87
               [menu_order] => 7
               [post_type] => nav_menu_item
               [post_mime_type] => 
               [comment_count] => 0
               [filter] => raw
               [db_id] => 87
               [menu_item_parent] => 78
               [object_id] => 85
               [object] => page
               [type] => post_type
               [type_label] => Page
               [url] => http://example.com/example-page-3/subpage-2/subpage-3/subpage-4/
               [title] => Subpage-4
               [target] => 
               [attr_title] => 
               [description] => 
               [classes] => Array
                (
                 [0] => 
                )
               [xfn] => 
               [children] => Array
                (
                 [0] => WP_Post Object
                  (
                   [ID] => 366
                   [post_author] => 1
                   [post_date] => 2015-07-29 20:52:46
                   [post_date_gmt] => 2015-07-29 18:52:46
                   [post_content] => 
                   [post_title] => 
                   [post_excerpt] => 
                   [post_status] => publish
                   [comment_status] => open
                   [ping_status] => open
                   [post_password] => 
                   [post_name] => 366
                   [to_ping] => 
                   [pinged] => 
                   [post_modified] => 2015-07-29 20:55:10
                   [post_modified_gmt] => 2015-07-29 18:55:10
                   [post_content_filtered] => 
                   [post_parent] => 85
                   [guid] => http://example.com/?p=366
                   [menu_order] => 8
                   [post_type] => nav_menu_item
                   [post_mime_type] => 
                   [comment_count] => 0
                   [filter] => raw
                   [db_id] => 366
                   [menu_item_parent] => 87
                   [object_id] => 112
                   [object] => page
                   [type] => post_type
                   [type_label] => Page
                   [url] => http://example.com/example-page-3/subpage-2/subpage-3/subpage-4/subpage-5/
                   [title] => Subpage-5
                   [target] => 
                   [attr_title] => 
                   [description] => 
                   [classes] => Array
                    (
                     [0] => 
                    )
                   [xfn] => 
                   [children] => Array
                    (
                    )
                  )
                )
              )
            )
          )
        )
      )
    )
  )
)
Nicolai
fuente
Una estructura similar a un árbol en WordPress no es una matriz multidimensional. Es una matriz de objetos con información de paternidad.
Matt van Andel
Intenté con 10 soluciones diferentes para este problema. Gracias por esta gran solución, la mantiene en una buena estructura de objetos WP. ¡Esto debe ser aceptado en realidad!
Drmzindec
@JohanPretorius Gracias y un placer. Bueno, la gente está buscando cosas diferentes. Supongo que el OP encontró la otra respuesta más útil. Está todo bien.
Nicolai
1

Versión modificada de la respuesta aceptada donde tiene en cuenta la menu_orderpropiedad de los elementos del menú para mantener el orden correcto de la matriz plana original. menu_orderWordPress lo configura automáticamente, por lo que no es necesario verificar nada:

function buildTree(array &$flatNav, $parentId = 0) {
    $branch = [];

    foreach ($flatNav as &$navItem) {
      if($navItem->menu_item_parent == $parentId) {
        $children = buildTree($flatNav, $navItem->ID);
        if($children) {
          $navItem->children = $children;
        }

        $branch[$navItem->menu_order] = $navItem;
        unset($navItem);
      }
    }

    return $branch;
}

Uso:

// get navs
$locations = get_nav_menu_locations();

// get menu items by menu name
$flatMainNav = wp_get_nav_menu_items($locations['main']);
$mainNav = buildTree($flatMainNav);
eballeste
fuente
-2

Puede haber un malentendido aquí sobre los elementos del menú de navegación de WordPress como estructuras en forma de árbol.

¡Las estructuras en forma de árbol en WordPress NO SON ARREGLOS MULTIDIMENSIONALES!

Tenga en cuenta que si bien la matriz de elementos de menú devueltos es plana, sigue siendo una estructura en forma de árbol porque cada elemento contiene información sobre su origen (el valor principal es 0 si el elemento no tiene elemento primario o la identificación del elemento principal si es hace).

Cuando pasa dicha matriz a la Walkerclase, recorre los resultados y crea dos matrices: una que contiene elementos de nivel superior y otra que contiene elementos secundarios en formato $parent_id => array()donde la matriz contiene elementos de menú que son elementos secundarios directos de ese elemento.

Luego, el caminante recorre la matriz de elementos de nivel superior, procesa ese elemento y luego verifica la matriz secundaria para ver si hay elementos secundarios para el elemento actual, y procesa cada uno de ellos de la misma manera, de forma recursiva.

Si desea saber cómo convertir una estructura similar a un árbol de WordPress en una matriz multidimensional, esa es una pregunta completamente diferente (y técnicamente no es una pregunta de WordPress). Pero la información que devuelve wp_get_nav_menu_items() es una estructura en forma de árbol ... y puede usarla Walkertal como está para manejarla.

Si desea ver el código exacto que ejecuta la clase Walker de WordPress para recorrer la matriz, eche un vistazo a Walker-> walk () en WordPress Trac desde las líneas 213-258 . Podría usar ese código tal como está para construir una matriz multidimensional, si eso es lo que busca.

Caminantes

WordPress está diseñado para usar la Walkerclase para procesar sus estructuras en forma de árbol. Si simplemente está renderizando un menú, o realmente solo necesita el resultado final, es posible que desee considerar usar wp_nav_menu()para generar su menú ...

Ejemplo:

wp_nav_menu(array(
    'menu' => 6, // your menu id, name, or slug
    'echo' => true, // set this to false if you want a string back instead
    'walker' => new Your_Walker(),
));

Extendería la clase Walker (por ejemplo Your_Walker()) para obtener cualquier salida que necesite. Para ver un ejemplo, vea esta entrada en el Codex .

Matt van Andel
fuente
2
En la opción A, $sorted_menu_itemssigue siendo una matriz "plana" y la salida de la opción B es una cadena.
birgire
Creo que hay un malentendido acerca de cómo WordPress define "estructuras en forma de árbol". wp_get_nav_menu_items()devuelve una estructura en forma de árbol, es decir, una matriz donde cada elemento contiene datos de parentesco. Estas estructuras están destinadas a representarse con una Walkerclase. Si el caso de uso aquí simplemente implica convertir una matriz "plana" en una matriz multidimensional basada en datos de parentesco (p 'post_parent' => 123. Ej. ), Esta pregunta no es técnicamente acerca de WordPress y debe moverse al Desbordamiento de pila.
Matt van Andel
1
Mira, no me importa lo que WordPress defina "estructuras en forma de árbol" (no creo que esta frase tenga sentido). Lo único que me importa es tener una matriz multidimensional, con la que puedo hacer cosas por mi cuenta.
YemSalat
NO vas a obtener eso como comportamiento predeterminado de WordPress. Como han dicho otros, tiene toda la información que necesita para reestructurar la matriz como lo desee, y lo vinculé a áreas específicas en el núcleo de WordPress para usar como referencia. Esta no es realmente una pregunta de WordPress tanto como una pregunta de PHP. Puede usar la clase Walker tal como está, o puede copiar las líneas relevantes de Walker :: walk () como dije, para construir su matriz.
Matt van Andel