Estructura de URL para Custom Post Type y Taxonomía

13 feb 2018, 21:33:31
Vistas: 23.6K
Votos: 8

Tengo un custom post type products y una taxonomía personalizada 'Categorías de Productos' con el slug products.

Mi objetivo: Estoy tratando de crear una estructura de URL así:

Archivo del Post Type de Productos: products/

Archivos de Categorías de Productos de primer nivel: products/product-category

Archivos de Categorías de Productos: products/product-category/product-sub-category

Página de Producto: products/product-category/product-sub-category/product-page

El problema: Las páginas de productos parecen querer usar solo categorías reales. Por lo que la estructura de URL se ve como products/uncategorized/product-page. Las páginas de archivo de categorías no mantienen la base del CPT. Entonces resultan algo como category/sub-category.

Lo que he intentado: Innumerables búsquedas en Google, varios snippets (incluiré abajo los que recuerdo), algunas funciones personalizadas y algunos plugins. Sin éxito.

Tengo el siguiente código.

add_action( 'init', 'products_cpt', 0);
add_action( 'init', 'register_taxonomies', 0 );

//Un custom post type para todos los productos
function products_cpt(){
    $labels = array(
        'name'               => _x('Productos', 'Nombre General del Tipo de Post'),
        'singular_name'      => _x('Producto', 'Nombre Singular del Tipo de Post'),
        'menu_name'          => __('Productos'),
        'parent_item_colon'  => __('Producto Padre'),
        'all_items'          => __('Todos los Productos'),
        'view_item'          => __('Ver Producto'),
        'add_new_item'       => __('Añadir Nuevo Producto'),
        'add_new'            => __('Añadir Nuevo'),
        'edit_item'          => __('Editar Producto'),
        'update_item'        => __('Actualizar Producto'),
        'search_items'       => __('Buscar Productos'),
        'not_found'          => __('No Encontrado'),
        'not_found_in_trash' => __('No Encontrado en la Papelera')
    );
    $supports = array( 
        'title', 
        'editor', 
        'excerpt', 
        'thumbnail', 
        'custom-fields', 
        'revisions' 
    );
    $args = array(
        'labels'              => $labels,
        'hierarchical'        => true,
        'public'              => true,
        'exclude_from_search' => false,
        'show_in_admin_bar'   => true,
        'show_in_nav_menus'   => true,
        'publicly_queryable'  => true,
        'query_var'           => true,
        'taxonomies'          => array( 'chemicals' ),
        'supports'            => $supports,
        'has_archive'         => 'products'
    );
    register_post_type('products', $args);
}

function register_taxonomies() {
    $taxonomies = array(
        'products' => array(
            'Producto',
            'Productos'
        ),
    );
    foreach($taxonomies as $slug => $name){
        create_product_taxonomy($slug,$name[0],$name[1]);
    }
}

function create_product_taxonomy($slug, $singular, $plural) {
    $labels = array(
        'name'                       => _x( $singular.' Categorías', 'Nombre General de Taxonomía', 'text_domain' ),
        'singular_name'              => _x( $singular.' Categoría', 'Nombre Singular de Taxonomía', 'text_domain' ),
        'menu_name'                  => __( $singular.' Categorías', 'text_domain' ),
        'all_items'                  => __( 'Todas las '.$singular.' Categorías', 'text_domain' ),
        'parent_item'                => __( 'Categoría '.$singular.' Padre', 'text_domain' ),
        'parent_item_colon'          => __( 'Categoría '.$singular.' Padre:', 'text_domain' ),
        'new_item_name'              => __( 'Nuevo Nombre de Categoría '.$singular, 'text_domain' ),
        'add_new_item'               => __( 'Añadir Nueva Categoría '.$singular, 'text_domain' ),
        'edit_item'                  => __( 'Editar Categoría '.$singular, 'text_domain' ),
        'update_item'                => __( 'Actualizar Categoría '.$singular, 'text_domain' ),
        'view_item'                  => __( 'Ver Categoría '.$singular, 'text_domain' ),
        'separate_items_with_commas' => __( 'Separar Categorías '.$singular.' con comas', 'text_domain' ),
        'add_or_remove_items'        => __( 'Añadir o eliminar Categorías '.$singular, 'text_domain' ),
        'choose_from_most_used'      => __( 'Elegir entre las Categorías '.$singular.' más usadas', 'text_domain' ),
        'popular_items'              => __( 'Categorías '.$singular.' Populares', 'text_domain' ),
        'search_items'               => __( 'Buscar Categorías '.$singular, 'text_domain' ),
        'not_found'                  => __( 'No Encontrado', 'text_domain' ),
        'no_terms'                   => __( 'No hay Categorías '.$singular, 'text_domain' ),
        'items_list'                 => __( 'Lista de Categorías '.$singular, 'text_domain' ),
        'items_list_navigation'      => __( 'Navegación de lista de Categorías '.$singular, 'text_domain' ),
    );
     $args = array(
        'labels'                     => $labels,
        'hierarchical'               => true,
        'public'                     => true,
        'show_ui'                    => true,
        'show_admin_column'          => true,
        'show_in_nav_menus'          => true,
        'show_tagcloud'              => true,
        'has_archive'                => $plural,
        'rewrite' => array( 
            'slug'         => 'products',
            'with_front'   => true,
            'hierarchical' => true
        )
    );
    register_taxonomy( $slug, 'products', $args );
}

Primero, configuré los ajustes de permalinks de la siguiente manera: Configuración de Enlaces Permanentes

Luego, intenté agregar el siguiente código, sin embargo solo funcionó para las páginas de productos. Los archivos de categorías de productos seguían dando 404.

add_filter( 'post_type_link', 'products_post_link', 1, 3 );
function products_post_link( $post_link, $id = 0 ){
    $post = get_post($id);  
    if ( is_object( $post ) ){
        $terms = wp_get_object_terms( $post->ID, 'products_category' );
        $slug_url = '';
        $last_id = 0;
        if( $terms ){
            foreach($terms as $term) {
                if ($term === reset($terms)){
                    foreach($terms as $termInner){
                        if($termInner->term_id == 0){
                            $slug_url .= $termInner->slug.'/';
                        }
                    }
                }elseif ($term === end($terms)){
                    foreach($terms as $termInner){
                        if($termInner->parent == $last_id){
                            $slug_url .= $termInner->slug;
                            $last_id = $termInner->term_id;
                        }
                    }
                }else{
                    foreach($terms as $termInner){
                        if($termInner->parent == $last_id){
                            $slug_url .= $termInner->slug.'/';
                            $last_id = $termInner->term_id;
                        }
                    }
                }
            }
            return str_replace( '%category%' , $slug_url , $post_link );
        }
    }
    return $post_link;  
}

Y esto a la función de inicialización del CPT:

    'rewrite' => array(
        'slug'         => 'products/%category%',
        'with_front'   => false,
        'hierarchical' => true,
    ),

También probé lo siguiente, no recuerdo qué pasó pero recuerdo que no funcionó:

add_action('init', 'custom_resource_rewrite_rules');
function custom_resource_rewrite_rules() {
    add_rewrite_rule('products/([A-Za-z0-9\-\_]+)/?', '$matches[1]', 'top');
}

También intenté jugar con el filtro term_link. Sin éxito.

Por último, probé los siguientes Plugins, también sin suerte.

Enlaces Permanentes Personalizados

Personalizador de Enlaces Permanentes

Enlaces Permanentes para Custom Post Types

¿Alguien tiene una solución para esto? Me estoy arrancando el pelo.

2
Comentarios

¿Podrías tener alguna vez products/product-category/product-page?

MikeSchinkel MikeSchinkel
15 feb 2018 04:51:21

@MikeSchinkel Supongo que sí, si la única categoría seleccionada era una categoría sin categorías padre

J Robz J Robz
18 feb 2018 00:21:35
Todas las respuestas a la pregunta 2
2

¡Después de mucho tiempo, encontré una solución!

Primero: registramos el tipo de contenido personalizado y la taxonomía personalizada:

add_action( 'init', 'register_sps_products_post_type' );
function register_sps_products_post_type() {
    register_post_type( 'sps-product',
        array(
            'labels' => array(
                'name' => 'Productos',
                'menu_name' => 'Gestor de Productos',
                'singular_name' => 'Producto',
                'all_items' => 'Todos los Productos'
            ),
            'public' => true,
            'publicly_queryable' => true,
            'show_ui' => true,
            'show_in_menu' => true,
            'show_in_nav_menus' => true,
            'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'comments', 'post-formats', 'revisions' ),
            'hierarchical' => false,
            'has_archive' => 'products',
            'taxonomies' => array('product-category'),
            'rewrite' => array( 'slug' => 'products/%product_category%', 'hierarchical' => true, 'with_front' => false )
        )
    );
    register_taxonomy( 'product-category', array( 'sps-product' ),
        array(
            'labels' => array(
                'name' => 'Categorías de Productos',
                'menu_name' => 'Categorías de Productos',
                'singular_name' => 'Categoría de Producto',
                'all_items' => 'Todas las Categorías'
            ),
            'public' => true,
            'hierarchical' => true,
            'show_ui' => true,
            'rewrite' => array( 'slug' => 'products', 'hierarchical' => true, 'with_front' => false ),
        )
    );
}

Luego, añadimos una nueva regla de reescritura para que WordPress sepa cómo interpretar nuestra nueva estructura de enlaces permanentes:

add_action( 'generate_rewrite_rules', 'register_product_rewrite_rules' );
function register_product_rewrite_rules( $wp_rewrite ) {
    $new_rules = array( 
        'products/([^/]+)/?$' => 'index.php?product-category=' . $wp_rewrite->preg_index( 1 ), // 'products/cualquier-caracter/'
        'products/([^/]+)/([^/]+)/?$' => 'index.php?post_type=sps-product&product-category=' . $wp_rewrite->preg_index( 1 ) . '&sps-product=' . $wp_rewrite->preg_index( 2 ), // 'products/cualquier-caracter/slug-del-post/'
        'products/([^/]+)/([^/]+)/page/(\d{1,})/?$' => 'index.php?post_type=sps-product&product-category=' . $wp_rewrite->preg_index( 1 ) . '&paged=' . $wp_rewrite->preg_index( 3 ), // coincide con resultados paginados para un archivo de subcategoría
        'products/([^/]+)/([^/]+)/([^/]+)/?$' => 'index.php?post_type=sps-product&product-category=' . $wp_rewrite->preg_index( 2 ) . '&sps-product=' . $wp_rewrite->preg_index( 3 ), // 'products/cualquier-caracter/subcategoria/slug-del-post/'
        'products/([^/]+)/([^/]+)/([^/]+)/([^/]+)/?$' => 'index.php?post_type=sps-product&product-category=' . $wp_rewrite->preg_index( 3 ) . '&sps-product=' . $wp_rewrite->preg_index( 4 ), // 'products/cualquier-caracter/subcategoria/sub-subcategoria/slug-del-post/'
    );
    $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
}

La siguiente función soluciona el problema con las subcategorías. El problema consiste en que cuando intentas cargar una página con URL como faq/categoria/subcategoria/, WordPress intentaría cargar un post con slug "subcategoria" en lugar de la subcategoría con slug "subcategoria".

// Una solución improvisada para añadir soporte a enlaces permanentes personalizados flexibles
// Hay un caso en el que fallan las reglas de reescritura de register_kb_rewrite_rules():
// Cuando visitas la página de archivo para una sección hija (por ejemplo: http://ejemplo.com/faq/categoria/subcategoria)
// El problema es que en esta situación, la URL se interpreta como un post de Knowledgebase con slug "subcategoria" de la sección "categoria"
function fix_product_subcategory_query($query) {
    if ( isset( $query['post_type'] ) && 'sps-product' == $query['post_type'] ) {
        if ( isset( $query['sps-product'] ) && $query['sps-product'] && isset( $query['product-category'] ) && $query['product-category'] ) {
            $query_old = $query;
            // Verificar si es un resultado paginado (como resultados de búsqueda)
            if ( 'page' == $query['product-category'] ) {
                $query['paged'] = $query['name'];
                unset( $query['product-category'], $query['name'], $query['sps-product'] );
            }
            // Hacerlo más fácil para la base de datos
            $query['fields'] = 'ids';
            $query['posts_per_page'] = 1;
            // Ver si tenemos resultados o no
            $_query = new WP_Query( $query );
            if ( ! $_query->posts ) {
                $query = array( 'product-category' => $query['sps-product'] );
                if ( isset( $query_old['product-category'] ) && 'page' == $query_old['product-category'] ) {
                    $query['paged'] = $query_old['name'];
                }
            }
        }
    }
    return $query;
}
add_filter( 'request', 'fix_product_subcategory_query', 10 );

Esta función le indica a WordPress cómo manejar %product_category% en la estructura de reescritura de slugs de tu tipo de contenido personalizado:

function filter_post_type_link($link, $post)
{
    if ($post->post_type != 'sps-product')
        return $link;

    if ($cats = get_the_terms($post->ID, 'product-category'))
    {
        $link = str_replace('%product_category%', get_taxonomy_parents(array_pop($cats)->term_id, 'product-category', false, '/', true), $link); // ver función personalizada definida abajo\
        $link = str_replace('//', '/', $link);
        $link = str_replace('http:/', 'http://', $link);
    }
    return $link;
}
add_filter('post_type_link', 'filter_post_type_link', 10, 2);

Una función personalizada basada en get_category_parents. Obtiene los padres de la taxonomía:

// Mi propia función para hacer lo que get_category_parents hace para otras taxonomías
function get_taxonomy_parents($id, $taxonomy, $link = false, $separator = '/', $nicename = false, $visited = array()) {    
    $chain = '';   
    $parent = &get_term($id, $taxonomy);

    if (is_wp_error($parent)) {
        return $parent;
    }

    if ($nicename)    
        $name = $parent -> slug;        
else    
        $name = $parent -> name;

    if ($parent -> parent && ($parent -> parent != $parent -> term_id) && !in_array($parent -> parent, $visited)) {    
        $visited[] = $parent -> parent;    
        $chain .= get_taxonomy_parents($parent -> parent, $taxonomy, $link, $separator, $nicename, $visited);

    }

    if ($link) {
        // nada, no puedo hacer que esto funcione :(
    } else    
        $chain .= $name . $separator;    
    return $chain;    
}

Fuentes:

19 feb 2018 23:27:17
Comentarios

Este código parece funcionar pero causa un par de problemas cuando lo implemento en mi sitio web. Primero, hace que las Entradas y Páginas se rompan - solo los elementos bajo un tipo de entrada personalizado funcionan. En segundo lugar, por alguna razón el código hace que la plantilla individual para mi tipo de entrada personalizado sea ignorada por completo. ¡He estado intentando descubrir la causa, pero todavía sin éxito!

Zach Nicodemous Zach Nicodemous
24 sept 2019 22:04:16

He resuelto el primer problema, pero con respecto al segundo problema, la función fix_product_subcategory_query() rompe la plantilla individual para mi tipo de entrada personalizado. Específicamente, solo carga la plantilla genérica "single.php" y no la específica para mi CPT. ¿Puedes ofrecer alguna solución?

Zach Nicodemous Zach Nicodemous
25 sept 2019 03:25:22
0

En realidad, esta es una forma horrible de hacer esto.

Si agregas la taxonomía antes del tipo de entrada personalizada usando el momento del add_action, podrás usar la regla de reescritura del tipo de entrada personalizada para prefijar la taxonomía.

ej.

    
    // Registrar Taxonomía Personalizada
    function type_taxonomy() {
    
        $labels = array(
            'name'                       => _x( 'Tipos', 'Nombre General de la Taxonomía', 'asw' ),
            'singular_name'              => _x( 'Tipo', 'Nombre Singular de la Taxonomía', 'asw' ),
        );
        $rewrite = array(
            'slug'                       => 'actualizacion/tipo',
            'with_front'                 => false,
            'hierarchical'               => false,
        );
        $args = array(
            'labels'                     => $labels,
            'hierarchical'               => true,
            'public'                     => true,
            'show_ui'                    => true,
            'show_admin_column'          => true,
            'show_in_nav_menus'          => true,
            'show_tagcloud'              => true,
            'rewrite'                    => $rewrite,
            'show_in_rest'               => true,
        );
        register_taxonomy( 'type', array( 'updates' ), $args );
    
    }
// Establecer el momento antes del tipo de entrada
    add_action( 'init', 'type_taxonomy', $timing = 0 );
    
    }
    
    if ( ! function_exists('updates_post_type') ) {
    
    // Registrar Tipo de Entrada Personalizada
    function updates_post_type() {
    
        $labels = array(
            'name'                  => _x( 'Actualizaciones', 'Nombre General del Tipo de Entrada', 'asw' ),
            'singular_name'         => _x( 'Actualización', 'Nombre Singular del Tipo de Entrada', 'asw' ),
        );
        $rewrite = array(
            'slug'                  => 'actualizacion',
            'with_front'            => true,
            'pages'                 => true,
            'feeds'                 => true,
        );
        $args = array(
            'label'                 => __( 'Actualizaciones', 'asw' ),
            'description'           => __( 'Las entradas deben incluir actualizaciones importantes sobre salud, bienestar y comunidad', 'asw' ),
            'labels'                => $labels,
            'supports'              => array( 'title', 'editor', 'thumbnail' ),
            'taxonomies'            => array( 'type', 'category', 'post_tag', 'brands', 'markets' ),
            'hierarchical'          => false,
            'public'                => true,
            'show_ui'               => true,
            'show_in_menu'          => true,
            'menu_position'         => 5,
            'menu_icon'             => 'dashicons-heart',
            'show_in_admin_bar'     => true,
            'show_in_nav_menus'     => true,
            'can_export'            => true,
            'has_archive'           => true,
            'exclude_from_search'   => false,
            'publicly_queryable'    => true,
            'rewrite'               => $rewrite,
            'capability_type'       => 'page',
            'show_in_rest'          => true,
        );
        register_post_type( 'updates', $args );
    
    }
// Establecer el momento después de la taxonomía 
    add_action( 'init', 'updates_post_type', $timing = 10 );
    
    }
14 abr 2020 13:22:02