¿Dónde almacena WordPress los menús personalizados?

14 minutos de lectura

avatar de usuario
martti laine

Tuve que copiar una instalación completa de wordpress a otro servidor. Simplemente instalé un nuevo WordPress allí e importé todos los datos de un archivo xml que WordPress anterior “exportó” para mí. De todos modos, los menús personalizados no estaban incluidos. ¿Sabes en qué parte de la base de datos están ubicados, para poder tomarlos desde allí?

avatar de usuario
nombre_de_usuario_aleatorio

Tengo un sitio con una instalación de Magento y una instalación de WordPress uno al lado del otro y con enlaces cruzados.

Recientemente pasé varias horas escribiendo una clase para acceder a la instalación de Magento y así poder representar el menú de WordPress como menú de navegación en el sitio de Magento.

Las publicaciones aquí han sido útiles, pero ninguna de ellas ha explicado completamente la estructura de cómo se almacenan los menús de WordPress. Como muchas cosas de WP, se almacena en una serie de relaciones. Aquí está la estructura:

(tenga en cuenta que este ejemplo supone un prefijo de tabla si es “wp_”)

  1. En primer lugar, es importante reconocer que un elemento de menú puede ser un correo (técnicamente es una página, pero las páginas se almacenan en la tabla de publicaciones), un categoríao puede ser un disfraz Enlace.
  2. Debido a que WP admite múltiples menús, primero busca en el wp_term_taxonomy table table para buscar cualquier término con la taxonomía de ‘nav_menu’. Tenga en cuenta el term_id de esa tabla.
  3. Para encontrar el nombre y la barra del menú, visite el wp_terms table y busque el término con el id anotado en el paso 2 anterior.
  4. Ir wp_term_relationships y enumere todos los registros con term_taxonomy_id que coincidieron con term_id del paso 1. El campo object_id le indica el registro wp_post.id donde puede encontrar el registro de menú.
  5. Finalmente, ve a wp_postmeta para encontrar muchos elementos que describen el menú. De particular interés son:
    • _menu_item_object – el TIPO de elemento del menú (página, personalizado o categoría)
    • _menu_item_object_id – la identificación del POST real (o categoría, si es una categoría) a la que hace referencia el elemento del menú
    • _menu_item_menu_item_parent – la estructura jerárquica de los padres del MENÚ (que puede ser diferente de las relaciones posteriores a los padres)
  6. _menu_item_url – el slug del elemento del menú (si es un enlace personalizado opción del menú)

Ejemplos de instrucciones SQL para realizar la operación descrita anteriormente:

SELECT t.term_id 
FROM wp_term_taxonomy as tax 
LEFT JOIN wp_terms as t ON tax.term_id = t.term_id 
WHERE taxonomy = 'nav_menu' and name like '%top%'

(busca un elemento de menú con el nombre ‘Top’ y obtiene el término id)

SELECT p.ID, p.post_title, p.post_name, p.menu_order, n.post_name as n_name, n.post_title as n_title, m.meta_value, pp.meta_value as menu_parent
FROM wp_term_relationships as txr 
INNER JOIN wp_posts as p ON txr.object_id = p.ID 
LEFT JOIN wp_postmeta as m ON p.ID = m.post_id 
LEFT JOIN wp_postmeta as pl ON p.ID = pl.post_id AND pl.meta_key = '_menu_item_object_id' 
LEFT JOIN wp_postmeta as pp ON p.ID = pp.post_id AND pp.meta_key = '_menu_item_menu_item_parent' 
LEFT JOIN wp_posts as n ON pl.meta_value = n.ID 
WHERE txr.term_taxonomy_id = 3 AND p.post_status="publish" 
    AND p.post_type="nav_menu_item" AND m.meta_key = '_menu_item_url' 
ORDER BY p.menu_order

(carga los datos para el menú, según el term_id de 3)

Tenga en cuenta que esta instrucción sql funcionará para páginas y menús personalizados (no tengo ninguna categoría, así que no la incluí). Los datos cargados le permitirán crear el enlace permanente utilizando la URL del sitio de la opciones_wp y agregando post_name al final (técnicamente, no obtiene la estructura principal, pero WP encuentra la página/publicación correctamente sin ella)

Actualizar

Un comentarista preguntó acerca de ensamblar los elementos del menú secundario con los elementos del menú principal. Eso tendrá que hacerse con PHP. Algo como lo siguiente hará eso por ti:

// run the query from above
$results = $wpdb->get_results('SELECT....');

// declare new variable to store "assembled" menu
$menu = array();

// loop over the items assigning children to parents
foreach( $results AS $row ) {
    // assemble key bits for the menu item
    $item = array(
        // handles custom navigation labels
        'title' => ( $row->post_title ) ? $row->post_title : $row->n_title,
        // handles custom links
        'permalink' => ( $row->meta_value ) ? $row->meta_value : get_permalink( $row->ID ),
        // declares empty placeholder for any child items
        'children' => array()
    );

    // if the menu item has a parent, assign as child of the parent
    if ( $row->menu_parent ) {
        $menu[ $row->menu_parent ][ 'children' ][] = $item;
    } else {
        $menu[ $row->ID ] = $item;
    }
}

var_dump( $menu );

// outputs something like below:
/**
 * array (size=6)
 *  77 => 
 *     array (size=3)
 *       'title' => string 'About Us' (length=8)
 *       'permalink' => string 'http://www.example.com/about' (length=33)
 *       'children' => 
 *        array (size=7)
 *          0 => 
 *            array (size=3)
 *              'title' => string 'Welcome' (length=22)
 *              'permalink' => string 'http://www.example.com/welcome' (length=35)
 *              'children' => 
 *                array (size=0)
 *                  empty
 *          1 => 
 *            array (size=3)
 *              'title' => string 'Mission' (length=20)
 *              'permalink' => string 'http://www.example.com/mission' (length=33)
 *              'children' => 
 *                array (size=0)
 *                  empty
 *  90 => 
 *    array (size=3)
 *      'title' => string 'Contact Us' (length=10)
 *      'permalink' => string 'http://www.example.com/contact' (length=33)
 *      'children' => 
 *        array (size=5)
 *          0 => 
 *            array (size=3)
 *              'title' => string 'Why Us' (length=12)
 *              'permalink' => string 'http://www.example.com/why' (length=35)
 *              'children' => 
 *                array (size=0)
 *                  empty
 *  1258 => 
 *    array (size=3)
 *      'title' => string 'Login' (length=12)
 *      'permalink' => string 'https://customlink.example.com/some/path/login.php' (length=82)
 *      'children' => 
 *        array (size=0)
 *          empty
 */

  • Esto me ayudó mucho a darme cuenta de que el problema que estaba teniendo en realidad estaba en el código y no en la base de datos.

    – Jonathan Sofer

    15 de marzo de 2016 a las 13:07

  • Este sql no funciona correctamente para mí. Obtengo la mayoría de los elementos del menú del nivel principal, excepto el texto de la página Contáctenos. Tampoco puedo averiguar cómo asociar los elementos del menú secundario con su elemento principal.

    – VenomRush

    3 oct 2017 a las 8:20

  • @VenomRush: acabo de hacer algunas pruebas profundas y estoy bastante seguro de que las cosas funcionan como deberían. Algunas notas para usted: 1. El n_title es el título del menú, a no ser que hay un valor en post_titleen ese caso post_title es el título “personalizado” para ese elemento del menú. 2. Deberá ensamblar elementos secundarios con sus padres en PHP, con un bucle. Ampliaré el código anterior con un código de muestra para lograrlo.

    – nombre_usuario_aleatorio

    3 oct 2017 a las 13:37

  • Gracias, ya usé una declaración condicional para usar n_title si post_title está en blanco.

    – VenomRush

    6 de octubre de 2017 a las 12:29

avatar de usuario
SUMA1

Para las personas que todavía llegan a esta pregunta, lo expresaré en términos simples de phpMyAdmin.

Hay 6 mesas involucradas.

1. wp_term_taxonomy

WordPress mantiene cada ubicación del menú de navegación como un registro en la tabla ‘wp_term_taxonomy’, pero el único identificador único es un ID de número; 1, 2, 3, etc

ingrese la descripción de la imagen aquí

También puede ver las cifras de ‘recuento’ que muestran la cantidad de elementos en cada ubicación del menú.

Esto es lo que se crea cuando escribes

register_nav_menu('your-navmenu', 'Your Navmenu');

No encontrará el nombre ‘Su menú de navegación’ en ninguna parte de la base de datos, solo se usa para etiquetarlo en la interfaz.

Sin embargo, encontrará ‘your-navmenu’ en otro lugar.

2. wp_terms

Sus menús reales se almacenan en ‘wp_terms’.

WordPress es confuso con esta terminología. En la interfaz, tiene ‘ubicaciones de visualización’ y ‘menús’. La ubicación es lo que es creado por register_nav_menu(). Es mejor considerar los ‘menús’ en la interfaz como listas de páginas.

Les he dado el nombre de ‘navlist’ aquí.

ingrese la descripción de la imagen aquí

Los menús también obtienen sus propios ID, que a menudo son los mismos que los ID de ubicación (porque las personas suelen crear un menú para una ubicación al mismo tiempo), lo que puede resultar confuso.

Este elemento es creado por la página ‘Menús’ en la interfaz de administración:

ingrese la descripción de la imagen aquí

3. wp_opciones

Verá el slug ‘your-navmenu’ en ‘wp_options’. Aquí, WordPress almacena su configuración actual para los menús de navegación. Lo almacena en una matriz serializada (usando PHP’s serialize() función).

ingrese la descripción de la imagen aquí

La matriz original (obtenida usando unserialize()) Se ve como esto.

array (
  'custom_css_post_id' => 56,
  'nav_menu_locations' => 
  array (
    'your-navmenu' => 2,
    'another-navmenu' => 3,
  ),
)

Esto cambia según el menú (por su ID; ‘2’, ‘3’) que establezca en qué ubicación (por su slug; ‘your-navmenu’, ‘another-navmenu’).

4. wp_posts

Las entradas del menú en sí se almacenan en una tabla diferente, ‘wp_posts’.

Puede encontrarlos buscando ‘nav_menu_item’:

ingrese la descripción de la imagen aquí

Recorté la mayoría de las columnas por razones de brevedad y conservé las relevantes.

Aquí puede ver la columna ‘menu_order’, que almacena su pedido en el menú en el que se encuentran.

Las entradas del menú se almacenan como publicaciones reales, con una ID de publicación y una URL (pero traerá un 404 si lo visitas, y no tienen ‘post_content’).

Todos los elementos del menú almacenados como subelementos tendrán un ID ‘post_parent’. Esta es la ID de la página real a la que se vincula su padre, no su ID de elemento de menú.

5. wp_postmeta

Los elementos del menú están vinculados a sus respectivas páginas en la tabla ‘wp_postmeta’.

El ID del elemento del menú (‘post_id’) se almacena en relación con el ID de la publicación (‘meta_value’) en las filas ‘_menu_item_object_id’, mientras que los subelementos están vinculados a sus elementos principales en las filas ‘_menu_item_menu_item_parent’.

ingrese la descripción de la imagen aquí

Es fácil confundirse aquí.

‘post_id’ es el ID del elemento del menú, no la publicación. ‘meta_value’ es el ID de la publicación, no el elemento del menú, en las filas ‘_menu_item_object_id’, pero es el ID del elemento del menú principal, no una publicación, en las filas ‘_menu_item_menu_item_parent’.

6. wp_term_relationships

Los enlaces entre cada elemento del menú y cada ubicación del menú se almacenan en ‘wp_term_relationships’.

ingrese la descripción de la imagen aquí

Aquí, ‘object_ID’ es la ID de la publicación del elemento del menú (como se ve en ‘wp_posts’), y ‘term_taxonomy_id’ es la ID de la ubicación del menú (como se ve en ‘wp_term_taxonomy’).

Espero que esto lo haya aclarado para algunas personas. Sé que estaba muy confundido al principio.

  • Gracias por este gran desglose

    – Tami

    4 de diciembre de 2021 a las 20:26

avatar de usuario
ben erwin

Encontré esto solo porque yo mismo estaba buscando la respuesta. Veo que tu publicación es bastante antigua, pero la respuesta está en wp_postmeta, ejecuta esta consulta:

SELECT *
FROM `wp_postmeta`
WHERE meta_key LIKE '%menu%'
LIMIT 0, 30

Encontrarás muchas entradas.

  • Lo aprecio, estaba cambiando todos los ID de publicación y de término a * 1000 y no sabía por qué los menús no funcionaban.

    – bysanchy

    19 de junio de 2017 a las 4:58

Esta configuración ocurre en la tabla wp_posts. Busque en la tabla los registros en los que menu_order sea mayor que cero.

select * from wp_posts where menu_order > 0;

También te dará el nombre de la opción en la tabla wp_options donde está configurada la opción del menú.

select * from wp_options where option_name = "nav_menu_options";

También tenga en cuenta que esa herramienta de importación/exportación de wordpress no importará medios (imágenes, videos, etc.) de la biblioteca de medios que no se estén utilizando en las publicaciones. Si tiene cosas a las que se vinculó directamente, tampoco se moverán.

He estado buscando por todas partes la estructura completa y finalmente descifré el código:

SELECT
p.ID,
m.meta_value,
md.post_author,
wp_users.user_nicename,
p.post_parent,
p.menu_order,
md.post_title
FROM
wp_posts AS p
INNER JOIN wp_postmeta AS m ON m.post_id = p.ID
INNER JOIN wp_posts AS md ON md.ID = m.meta_value AND m.meta_value = md.ID
INNER JOIN wp_users ON md.post_author = wp_users.ID
WHERE
p.menu_order > 0 AND
p.post_type="nav_menu_item" AND
m.meta_key = '_menu_item_object_id'
ORDER BY
p.menu_order ASC

avatar de usuario
scott8035

Para simplificar/complejar las dos consultas de @random_user_name, puede condensarlas en 1 consulta para que el único parámetro de entrada necesario sea el nombre del menú. Como esto:

SELECT p.ID, p.post_title, p.post_name, p.menu_order, 
       n.post_name as n_name, n.post_title as n_title, 
       m.meta_value, pp.meta_value as menu_parent
  FROM       wp_term_taxonomy      as tax 
  INNER JOIN wp_terms              as t   ON tax.term_id   = t.term_id
  INNER JOIN wp_term_relationships as txr ON t.term_id     = txr.term_taxonomy_id
  INNER JOIN wp_posts              as p   ON txr.object_id = p.ID       AND p.post_status="publish" AND p.post_type="nav_menu_item"
  LEFT  JOIN wp_postmeta           as m   ON p.ID          = m.post_id  AND m.meta_key    = '_menu_item_url'
  LEFT  JOIN wp_postmeta           as pl  ON p.ID          = pl.post_id AND pl.meta_key   = '_menu_item_object_id' 
  LEFT  JOIN wp_postmeta           as pp  ON p.ID          = pp.post_id AND pp.meta_key   = '_menu_item_menu_item_parent' 
  LEFT  JOIN wp_posts              as n   ON pl.meta_value = n.ID 
 WHERE tax.taxonomy = 'nav_menu' 
   AND t.name    like '%main nav%'
 ORDER BY p.menu_order

avatar de usuario
scott8035

Creo que esta declaración SQL hace el trabajo un poco más a fondo y de manera legible que el resto. Todo lo que necesita hacer es poner el nombre de su menú en t.name="main nav" en la primera unión. Muestra cada detalle de la información del menú. excepto el campo XFN.

 SELECT t.name as 'menu name',
        p1.ID as 'post id', p1.post_title as title, p1.post_name as slug, p1.menu_order as 'order',
        m1.meta_value as classes, m2.meta_value as menu_item_parent, m3.meta_value as object, m4.meta_value as object_id,
        m5.meta_value as target, m6.meta_value as type, m7.meta_value as url,
        p2.post_title as 'title-2', p2.post_name as 'slug-2'
   FROM       wp_term_taxonomy      as tax
   INNER JOIN wp_terms              as t   ON t.term_id            = tax.term_id   AND t.name="main nav"
   INNER JOIN wp_term_relationships as txr ON txr.term_taxonomy_id = t.term_id
   INNER JOIN wp_posts              as p1  ON p1.ID                = txr.object_id AND p1.post_status="publish" AND p1.post_type="nav_menu_item"
   LEFT  JOIN wp_postmeta           as m1  ON m1.post_id           = txr.object_id AND m1.meta_key    = '_menu_item_classes'
   LEFT  JOIN wp_postmeta           as m2  ON m2.post_id           = txr.object_id AND m2.meta_key    = '_menu_item_menu_item_parent'
   LEFT  JOIN wp_postmeta           as m3  ON m3.post_id           = txr.object_id AND m3.meta_key    = '_menu_item_object'
   LEFT  JOIN wp_postmeta           as m4  ON m4.post_id           = txr.object_id AND m4.meta_key    = '_menu_item_object_id'
   LEFT  JOIN wp_postmeta           as m5  ON m5.post_id           = txr.object_id AND m5.meta_key    = '_menu_item_target'
   LEFT  JOIN wp_postmeta           as m6  ON m6.post_id           = txr.object_id AND m6.meta_key    = '_menu_item_type'
   LEFT  JOIN wp_postmeta           as m7  ON m7.post_id           = txr.object_id AND m7.meta_key    = '_menu_item_url'
   LEFT  JOIN wp_posts              as p2  ON p2.ID                = m4.meta_value AND p2.post_status="publish" AND m6.meta_value="post_type"
  WHERE tax.taxonomy = 'nav_menu'
  ORDER BY p1.menu_order

¿Ha sido útil esta solución?