web-dev-qa-db-fra.com

Intégration d'un type d'article personnalisé dans une hiérarchie de pages

Je crée un thème avec un type de publication personnalisé pour les membres de l'équipe. J'ai également la structure de page suivante:

about  <-- this is a page
about/team-members  <-- this is a page, lists all the team members
about/team-members/joe-bloggs  <-- this is a custom post type (team member) entry

La troisième structure ici utilise les pages about et team member, mais utilise ensuite le slug de type de message personnalisé pour donner l’impression que ses parents sont membres de l’équipe et environ. Pour ce faire, j'ai défini les options suivantes dans le type de publication personnalisé:

...
'rewrite' => array( 'slug' => 'about/team-members', 'with_front' => false)
...

Cela fonctionne très bien. Toutefois, lorsque je suis au niveau de publication du membre de l'équipe, je ne reçois plus les classes de la page actuelle, des ancêtres actuels sur les pages parentes. Je sais pourquoi, parce que techniquement, nous ne sommes pas sur une page parent de ces pages. Cependant, est-il possible de tromper/corriger/bodge pour que les pages apparaissent en tant que parents?

J'avais très bien réussi cela en utilisant des pages pour les membres de l'équipe. Cependant, un type de publication personnalisé a été choisi à la place pour faciliter son utilisation par l'administrateur.

Merci les gars + les filles!

14
Ben Everard

Lorsque vous travaillez avec des pages, vous pouvez sélectionner une page parent et cette valeur est enregistrée en tant que numéro d'identification de la page parent dans le champ post_parent de la page enfant dans la base de données.

Dans votre cas, vous utilisez un type de publication personnalisé. Vous devez donc créer votre propre métabox pour la page parent. quelque chose comme:

/* Define the custom box */
add_action('add_meta_boxes', 'child_cpt_add_custom_box');

/* Adds a box to the main column on the custom post type edit screens */
function child_cpt_add_custom_box() {
    add_meta_box('child_cpt', __( 'My child_cpt parent'),'team_member_inner_custom_box','team_member');
}

/* Prints the box content */
function team_member_inner_custom_box() {
    global $post;
    // Use nonce for verification
    wp_nonce_field( plugin_basename(__FILE__), 'team_member_inner_custom_box' );
    echo 'Select the parent page';
    $mypages = get_pages();
    echo '<select name="cpt_parent">';
    foreach($mypages as $page){     
        echo '<option value="'.$page->ID.'"';
        if ($page->ID == $post->post_parent) {echo ' selected';}
        echo '>"'.$page->post_title.'</option>';
    }
    echo '</select>';
}
/* Do something with the data entered */
add_action('wp_insert_post_data', 'myplugin_save_postdata');

/* When the post is saved, saves our custom data */
function myplugin_save_postdata( $data, $postarr ) {
    global $post;
      // verify this came from the our screen and with proper authorization,
      // because save_post can be triggered at other times

      if ( !wp_verify_nonce( $_POST['team_member_inner_custom_box'], plugin_basename(__FILE__) ) )
          return $data;

      // verify if this is an auto save routine. 
      // If it is our form has not been submitted, so we dont want to do anything
      if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) 
          return $data;
      // OK, we're authenticated: we need to find and save the data

      if ($post->post_type == "team_member")
          $data['post_parent'] = $_POST['cpt_parent'];

     return $data;
}

Cela n'a rien à voir avec register_post_type. Vous incitez WordPress à penser qu'il s'agit d'une page enfant d'un autre type de message (page).

6
Bainternet

Disclaimer: Après avoir essayé, cela ne me semble plus un problème car, du moins pour moi, cela ne fonctionne que sur mon WP 3.9.2. Impossible de trouver un traqueur de bogues selon.


J'ai ensemble un petit plugin pour tester cela, ce qui pourrait aider quelqu'un. Mais comme je l’ai dit plus haut, je ne pouvais pas reproduire le problème dans une installation WordPress courante. J'ai séparé le plugin en quatre fichiers, ils vont ensemble dans un répertoire à l'intérieur du répertoire du plugin.

plugin-cpt_menu_hierarchy.php :

<?php
defined( 'ABSPATH' ) OR exit;
/**
 * Plugin Name: CPT Menu Hierarchy Fix?
 * Description: CPT Menu Hierarchy Fix?
 * Author:      ialocin
 * Author URL:  http://wordpress.stackexchange.com/users/22534/ialocin
 * Plugin URL:  http://wordpress.stackexchange.com/q/13308/22534
 */

// registering nonsense post type
include 'include-register_post_type.php';

// adding meta box to nosense custom post type
include 'include-cpt_parent_meta_box.php';

// menu highlighting fix
include 'include-menu_highlighting.php';

include-register_post_type.php :

<?php
defined( 'ABSPATH' ) OR exit;

// See: http://codex.wordpress.org/Function_Reference/register_post_type
add_action( 'init', 'wpse13308_basic_reigister_post_type');
function wpse13308_basic_reigister_post_type() {
    $args = array(
        'public' => true,
        'label'  => 'Nonsense'
    );
    register_post_type( 'nonsense', $args );
}

include-cpt_parent_meta_box.php :

<?php
defined( 'ABSPATH' ) OR exit;

// pretty much like @bainternet's answer

// Add Meta Box
add_action( 'add_meta_boxes', 'nonsense_add_meta_box' );
function nonsense_add_meta_box() {
    add_meta_box(
        'nonsense',
        __( 'Nonsense parent' ),
        'nonsense_inner_meta_box',
        'nonsense'
    );
}

// Meta Box Content
function nonsense_inner_meta_box() {
    global $post;

    wp_nonce_field(
        plugin_basename( __FILE__ ),
        'nonsense_inner_meta_box'
    );
    echo 'Parent Page:&nbsp;&nbsp;';
    $mypages = get_pages();
    echo '<select name="cpt_parent">';
    foreach($mypages as $page){     
        echo '<option value="'.$page->ID.'"';
        if ($page->ID == $post->post_parent) {echo ' selected';}
        echo '>'.$page->post_title.'</option>';
    }
    echo '</select>';
}

// Save Data From Meta Box
add_action( 'wp_insert_post_data', 'nonsense_save_meta_box_data' );
function nonsense_save_meta_box_data( $data, $postarr ) {
    global $post;

    if (
        ! wp_verify_nonce(
            $_POST['nonsense_inner_meta_box'],
            plugin_basename( __FILE__ )
        )
    ) {
        return $data;
    }

    if (
        defined('DOING_AUTOSAVE')
        && DOING_AUTOSAVE
    ) {
        return $data;
    }

    if ( $post->post_type == 'nonsense' ) {
        $data['post_parent'] = $_POST['cpt_parent'];
    }
    return $data;
}

include-menu_highlighting.php :

<?php
defined( 'ABSPATH' ) OR exit;

// altering WordPress' nav menu classes via »nav_menu_css_class« filter
add_filter( 'nav_menu_css_class', 'wpse13308_fix_nav_menu_highlighting', 10, 2 );
function wpse13308_fix_nav_menu_highlighting( $classes, $item ) {
    // data of the current post
    global $post;

    // setting up some data from the current post
    $current_post_post_type = $post->post_type;
    $current_post_parent_id = $post->post_parent;
    // id of the post the current menu item represents
    $current_menu_item_id   = $item->object_id;

    // do this for a certain post type
    if( $current_post_post_type == 'nonsense' ) {
        // remove unwanted highlighting class via array_filter and callback
        // http://php.net/manual/de/function.array-filter.php
        $classes = array_filter(
            $classes,
            'wpse13308_remove_highlighting_classes'
        );
        // when the parents id equals the menu items id, we want to
        // highlight the parent menu item, so we check for:
        if( $current_post_parent_id == $current_menu_item_id ) {
            // use the css class used for highlighting
            $classes[] = 'replace-with-css-class';
        }
    }
    return $classes;
}

// callback to remove highlighting classes
function wpse13308_remove_highlighting_classes( $class ) {
    return
        (
            // use the class(es) you need, overview over nav menu item css classes:
            // http://codex.wordpress.org/Function_Reference/wp_nav_menu#Menu_Item_CSS_Classes
            $class == 'highlight-class'
            // uncomment next line if you want to check for more then one class
            // repeat the line if you want to check for a third, fourth and so on
            // || $class == 'replace-with-css-class'
        ) 
        ? false
        : true
    ;
}



  • Ceci est un exemple de code quelque peu généralisé.
  • Il doit être adapté au cas d'utilisation réel.
0
Nicolai

J'ai eu un peu plus de temps pour creuser moi-même (désolé si j'ai perdu du temps à quiconque), et je me suis dit que pour moi, le meilleur moyen de résoudre le problème de surbrillance serait de refaire ce que _wp_menu_item_classes_by_context() est en train de faire, c'est-à-dire itérera sur tous les parents et les ancêtres de l'élément de menu qui agit en tant que parent de mon type de publication personnalisé et ajoute les classes de manière appropriée.

Étant donné que je souhaitais également que la page parent de mon type de publication personnalisée soit fixe et facilement modifiable sans avoir à mettre à jour toutes les publications une fois que le parent a été modifié, j'ai décidé d'utiliser une option au lieu de renseigner le champ post_parent de mes publications de type publication personnalisée. . J'utilise ACF pour cela depuis que je l'utilise dans mon thème de toute façon, mais utiliser la fonctionnalité par défaut de WordPress le ferait bien sûr aussi.

Pour mes besoins, je pourrais utiliser le filtre wp_nav_menu_objects . De plus, je devais filtrer l'option page_for_posts pour qu'elle renvoie une valeur faussement/vide, cela évite que la page de publication par défaut ne soit également mise en surbrillance.

Notez que je ne suis pas allé jusqu'au bout, le filtre ajoute uniquement les classes current-menu-ancestor et current-menu-parent, car cela suffisait à mes besoins!

/**
 * Filters the `page_for_posts` option on specific custom post types in
 * order to avoid the wrong menu item being marked as
 * `current-page-parent`.
 *
 * @see _wp_menu_item_classes_by_context()
 */
function wpse13308_pre_option_page_for_posts_filter()
{
    $types = array
    (
        'my_custom_post_type_x',
        'my_custom_post_type_y',
        'my_custom_post_type_z'
    );
    if(in_array(get_post_type(), $types))
    {
        return 0;
    }
    return false;
}
add_filter('pre_option_page_for_posts', 'wpse13308_pre_option_page_for_posts_filter');


/**
 * Returns the current posts parent page ID
 *
 * @return int
 */
function wpse13308_get_parent_page_id()
{
    $postType = get_post_type();
    $parentPageId = null;
    switch($postType)
    {
        case 'my_custom_post_type_x':
        case 'my_custom_post_type_y':
        case 'my_custom_post_type_z':
            $parentPageId = (int)get_field('page_for_' . $postType, 'options')->ID;
            break;

        case 'post':
            $parentPageId = (int)get_option('page_for_posts');
            break;
    }
    return $parentPageId;
}

/**
 * Adds proper context based classes so that the parent menu items are
 * being highlighted properly for custom post types and regular posts.
 *
 * @param array $menuItems
 * @return array
 *
 * @see _wp_menu_item_classes_by_context()
 */
function wpse13308_wp_nav_menu_objects_filter(array $menuItems)
{
    $parentPageId = wpse13308_get_parent_page_id();

    if($parentPageId !== null)
    {
        $activeAncestorItemIds = array();
        $activeParentItemIds = array();
        foreach($menuItems as $menuItem)
        {
            if((int)$parentPageId === (int)$menuItem->object_id)
            {
                $ancestorId = (int)$menuItem->db_id;

                while
                (
                    ($ancestorId = (int)get_post_meta($ancestorId, '_menu_item_menu_item_parent', true)) &&
                    !in_array($ancestorId, $activeAncestorItemIds)
                )
                {
                    $activeAncestorItemIds[] = $ancestorId;
                }
                $activeParentItemIds[] = (int)$menuItem->db_id;
            }
        }
        $activeAncestorItemIds = array_filter(array_unique($activeAncestorItemIds));
        $activeParentItemIds = array_filter(array_unique($activeParentItemIds));

        foreach($menuItems as $key => $menuItem)
        {
            $classes = $menuItems[$key]->classes;
            if(in_array(intval($menuItem->db_id), $activeAncestorItemIds))
            {
                $classes[] = 'current-menu-ancestor';
                $menuItems[$key]->current_item_ancestor = true;
            }

            if(in_array($menuItem->db_id, $activeParentItemIds))
            {
                $classes[] = 'current-menu-parent';
                $menuItems[$key]->current_item_parent = true;
            }

            $menuItems[$key]->classes = array_unique($classes);
        }
    }

    return $menuItems;
}
add_filter('wp_nav_menu_objects', 'wpse13308_wp_nav_menu_objects_filter');

Par souci d'exhaustivité, lors du remplissage de post_parent (voir @ La réponse de @Bainternet ) au lieu d'utiliser des options, la récupération de l'ID parent pourrait ressembler à ceci:

/**
 * Returns the current posts parent page ID
 *
 * @return int
 */
function wpse13308_get_parent_page_id()
{
    $parentPageId = null;
    $post = get_post();
    switch($post->post_type)
    {
        case 'my_custom_post_type_x':
        case 'my_custom_post_type_y':
        case 'my_custom_post_type_z':
            $parentPageId = (int)$post->post_parent;
            break;

        case 'post':
            $parentPageId = (int)get_option('page_for_posts');
            break;
    }
    return $parentPageId;
}
0
ndm

J'y suis allé avec un lecteur personnalisé pour obtenir quelque chose de similaire ... évite les besoins en champs personnalisés, mais tous les articles d'un type doivent s'asseoir en dessous du même point dans l'arborescence des pages.

class Walker_Page_CustomPostTypeHack extends Walker_Page {
    function walk($elements, $max_depth) {
        $called_with = func_get_args();
        // current page is arg 3... see walk_page_tree for why
        $current_page = $called_with[3];

        // if there's no parent - see if we can find one.
        // some ACF options would be an easy way to make this configurable instad of constants
        if ($current_page === 0) {
            global $wp_query;
            $current_post = $wp_query->get_queried_object();
            switch ($current_post->post_type) {
                case 'course':
                    $current_page = POST_COURSES;
                    break;
                case 'project':
                    $current_page = POST_PROJECTS;
                    break;
                case 'story':
                    $current_page = POST_STORIES;
                    break;
            }
        }

        // now pass on into parent
        $called_with[3] = $current_page;
        return call_user_func_array(array('parent', 'walk'), $called_with);
    }

}
0
benlumley

Une solution possible est que chaque fois que le type de publication personnalisé est enregistré, vous pouvez définir son parent comme étant about/team-members de manière pragmatique.

Voici les étapes:

  1. Vous pouvez utiliser le crochet save_post pour "intercepter" chaque fois que quelqu'un tente de sauvegarder un message.
  2. Si ce message est le type de message personnalisé que vous recherchez, continuez.
  3. Assurez-vous de définir le parent de la publication personnalisée sur la page souhaitée (vous pouvez coder en dur l'ID de la page tant que vous ne le supprimez pas). Vous pouvez utiliser wp_update_post pour enregistrer le parent (je ne l’ai pas essayé moi-même, mais je ne vois pas pourquoi cela ne fonctionnerait pas).
0
Shahar Dekel