Skip to content

Wordpress - Relation de plusieurs à plusieurs entre deux types de postes personnalisés.

Notre équipe de travail a passé beaucoup de temps à chercher la solution à votre recherche, nous vous offrons la solution donc notre désir est de vous être d'une grande aide.

Solution :

Comme Milo l'a noté, il n'y a pas de moyen intégré pour faire une relation de plusieurs à plusieurs entre deux types de post personnalisés.

Cela étant dit, de nos jours, il n'est pas trop difficile de le faire vous-même sans plugin si vous le souhaitez.

Je fais des relations many to many entre les types de post personnalisés en utilisant des méta post non singulières.

par exemple, Livres et Auteurs

Collez un post meta author_id (non-singulier) sur un livre et vous pouvez maintenant avoir :

  • Un nombre quelconque d'auteurs liés à un livre.
  • N'importe quel nombre de livres liés à un auteur
  • Un moyen assez facile pour l'un d'interroger l'autre.

Ajout des relations dans les éditeurs de billets

La classe ci-dessous se charge de créer ce type de relation et de l'ajouter aux éditeurs de post. Pour cet exemple, on suppose qu'un type de post s'appelle 'livre' et que l'autre s'appelle 'auteur'. Oui, une dénomination terrible ... mais cela permet d'illustrer comment cela fonctionne.

On pourrait avoir besoin de changer 'author' en 'writer' ou quelque chose comme ça, car je ne me souviens pas de mémoire si author est un post type réel ou non.

class Many_To_Many_Linker {

    protected $_already_saved = false;  # Used to avoid saving twice

    public function __construct() {
        $this->do_initialize();
    }

    protected function do_initialize() {

        add_action(
            'save_post',
            array( $this, 'save_meta_box_data' ),
            10,
            2
        );

        add_action(
            "add_meta_boxes_for_author",
            array( $this, 'setup_author_boxes' )
        );

        add_action(
            "add_meta_boxes_for_book",
            array( $this, 'setup_book_boxes' )
        );

    }

Mise en place de 2 boîtes méta d'auteur.

Nous mettons en place une méta-boîte d'auteur pour montrer les relations du livre et une autre pour contenir quelques données supplémentaires sur l'auteur.
Nous ne mettons pas tout cela dans un seul champ méta parce que nous voulons être en mesure d'interroger facilement les relations entre auteurs de livres.
Si nous collons les book_ids dans le tableau sérialisé utilisé pour les données sur l'auteur, nous ne pourrions pas les interroger facilement.

En utilisant un post meta non singulier nommé '_author_id' sur chaque livre, nous obtenons la possibilité de :

  • Interroger facilement tous les auteurs liés à un livre spécifique.
  • Interroger facilement tous les livres liés à un auteur spécifique.

Si nous voulions pouvoir facilement interroger d'autres choses sur un auteur, nous pourrions soit les sortir du tableau sérialisé, soit utiliser des clauses de type SQL pour les rechercher même s'ils sont dans un tableau, mais les sortir est beaucoup plus propre.

    public function setup_author_boxes( WP_Post $post ) {
        add_meta_box(
            'author_related_books_box',
            __('Related Books', 'language'),
            array( $this, 'draw_author_books_box' ),
            $post->post_type,
            'advanced',
            'default'
        );

        add_meta_box(
            'author_extra_data',
            __('Author Details', 'language'),
            array( $this, 'draw_author_details_box' ),
            $post->post_type,
            'advanced',
            'default'
        );
    }

Nous utilisons une fonction d'appel puisque nous avons besoin des détails méta dans plusieurs fonctions.
Nous utilisons également un appel get_defaults afin que nous puissions garder les valeurs par défaut notées à un seul endroit au cas où nous devrions les changer.

    protected function get_author_details_meta( $post_id = 0 ) {
        $default = $this->get_default_author_details_meta();
        $current = get_post_meta( $post_id, '_author_info', true );
        if ( !is_array( $current ) ) {
            $current = $default;
        } else {
            foreach ( $default as $k => $v ) {
                if ( !array_key_exists( "{$k}", $current ) ) {
                    $current["{$k}"] = $v;
                }
            }
        }
        return $current;
    }

    protected function get_default_author_details_meta() {
        return array(
            'favorite_color' => '',
            'height' => '',
            'eye_color' => ''
        );
    }

La boîte de détails de l'auteur

Nous récupérons simplement leur méta et l'affichons dans des boîtes de texte pour cet exemple.

    public function draw_author_details_box( WP_Post $post ) {

        $current_meta = $this->get_author_details_meta( $post->ID );

            echo <<
    
    

HTML; # No need for nonce - already added in related books }

La boîte des livres connexes

Nous construisons une liste de cases à cocher pour cet exemple afin que l'utilisateur puisse les définir toutes si nécessaire.
Nous pourrions ajouter une case de type check_all avec un peu de javascript, les répartir par catégorie, etc.
Et oui, si nous avions des milliers de livres dans le système, ce ne serait probablement pas le meilleur plan, mais cela fonctionne très bien si nous n'avons que quelques centaines de livres.

    public function draw_author_books_box( WP_Post $post ) {

        $all_books = $this->get_all_of_post_type( 'book' );

        $linked_book_ids = $this->get_author_book_ids( $post->ID );

        if ( 0 == count($all_books) ) {
            $choice_block = '

No books found in the system.

'; } else { $choices = array(); foreach ( $all_books as $book ) { $checked = ( in_array( $book->ID, $linked_book_ids ) ) ? ' checked="checked"' : ''; $display_name = esc_attr( $book->post_title ); $choices[] = << {$display_name} HTML; } $choice_block = implode("rn", $choices); } # Make sure the user intended to do this. wp_nonce_field( "updating_{$post->post_type}_meta_fields", $post->post_type . '_meta_nonce' ); echo $choice_block; }

Saisir tous les messages d'un type

Nous utilisons une fonction générique pour cela car nous en avons besoin pour les deux types de post.

    # Grab all posts of the specified type
    # Returns an array of post objects
    protected function get_all_of_post_type( $type_name = '') {
        $items = array();
        if ( !empty( $type_name ) ) {
            $args = array(
                'post_type' => "{$type_name}",
                'posts_per_page' => -1,
                'order' => 'ASC',
                'orderby' => 'title'
            );
            $results = new WP_Query( $args );
            if ( $results->have_posts() ) {
                while ( $results->have_posts() ) {
                    $items[] = $results->next_post();
                }
            }
        }
        return $items;
    }

Récupérer les livres d'un auteur

Les livres ont un _author_id défini sur eux qui est un post meta à valeurs multiples donc nous pouvons avoir un livre lié à plusieurs auteurs.

Cette fonction récupère les ids des livres sous forme de tableau.

    # Get array of book ids for a particular author id
    protected function get_author_book_ids( $author_id = 0 ) {
        $ids = array();
        if ( 0 < $author_id ) {
            $args = array(
                'post_type' => 'book',
                'posts_per_page' => -1,
                'order' => 'ASC',
                'orderby' => 'title',
                'meta_query' => array(
                    array(
                        'key' => '_author_id',
                        'value' => (int)$author_id,
                        'type' => 'NUMERIC',
                        'compare' => '='
                    )
                )
            );
            $results = new WP_Query( $args );
            if ( $results->have_posts() ) {
                while ( $results->have_posts() ) {
                    $item = $results->next_post();
                    if ( !in_array($item->ID, $ids) ) {
                        $ids[] = $item->ID;
                    }
                }
            }
        }
        return $ids;
    }

Mise en place des auteurs liés à un livre.

Nous aurons juste une boîte d'auteurs liés pour le moment.

Nous la dessinons comme la boîte des livres liés à l'auteur.

    public function setup_book_boxes( WP_Post $post ) {
        add_meta_box(
            'book_related_authors_box',
            __('Related Authors', 'language'),
            array( $this, 'draw_book_authors_box' ),
            $post->post_type,
            'advanced',
            'default'
        );
    }

    public function draw_book_authors_box( WP_Post $post ) {

        $all_authors = $this->get_all_of_post_type( 'author' );

        $linked_author_ids = $this->get_book_author_ids( $post->ID );

        if ( 0 == count($all_authors) ) {
            $choice_block = '

No authors found in the system.

'; } else { $choices = array(); foreach ( $all_authors as $author ) { $checked = ( in_array( $author->ID, $linked_author_ids ) ) ? ' checked="checked"' : ''; $display_name = esc_attr( $author->post_title ); $choices[] = << {$display_name} HTML; } $choice_block = implode("rn", $choices); } # Make sure the user intended to do this. wp_nonce_field( "updating_{$post->post_type}_meta_fields", $post->post_type . '_meta_nonce' ); echo $choice_block; }

Obtenir une boîte d'auteurs liés à des livres.

Il s'agit juste d'attraper la méta du post et de noter qu'elle n'est pas singulière.

    # Grab all properties related to a specific development area
    # Returns an array of property post ids
    protected function get_book_author_ids( $book_id = 0 ) {
        $ids = array();
        if ( 0 < $book_id ) {
            $matches = get_post_meta( $book_id, '_author_id', false);
            if ( 0 < count($matches) ) {
                $ids = $matches;
            }
        }
        return $ids;
    }

Sauvegarder les données de notre boîte méta

Nous faisons un tas de sanity checking et ensuite, si nécessaire, nous confions le traitement réel aux fonctions d'aide.

    public function save_meta_box_data( $post_id = 0, WP_Post $post = null ) {

        $do_save = true;

        $allowed_post_types = array(
            'book',
            'author'
        );

        # Do not save if we have already saved our updates
        if ( $this->_already_saved ) {
            $do_save = false;
        }

        # Do not save if there is no post id or post
        if ( empty($post_id) || empty( $post ) ) {
            $do_save = false;
        } else if ( ! in_array( $post->post_type, $allowed_post_types ) ) {
            $do_save = false;
        }

        # Do not save for revisions or autosaves
        if (
            defined('DOING_AUTOSAVE')
            && (
                is_int( wp_is_post_revision( $post ) )
                || is_int( wp_is_post_autosave( $post ) )
            )
        ) {
            $do_save = false;
        }

        # Make sure proper post is being worked on
        if ( !array_key_exists('post_ID', $_POST) || $post_id != $_POST['post_ID'] ) {
            $do_save = false;
        }

        # Make sure we have the needed permissions to save [ assumes both types use edit_post ]
        if ( ! current_user_can( 'edit_post', $post_id ) ) {
            $do_save = false;
        }

        # Make sure the nonce and referrer check out.
        $nonce_field_name = $post->post_type . '_meta_nonce';
        if ( ! array_key_exists( $nonce_field_name, $_POST) ) {
            $do_save = false;
        } else if ( ! wp_verify_nonce( $_POST["{$nonce_field_name}"], "updating_{$post->post_type}_meta_fields" ) ) {
            $do_save = false;
        } else if ( ! check_admin_referer( "updating_{$post->post_type}_meta_fields", $nonce_field_name ) ) {
            $do_save = false;
        }

        if ( $do_save ) {
            switch ( $post->post_type ) {
                case "book":
                    $this->handle_book_meta_changes( $post_id, $_POST );
                    break;
                case "author":
                    $this->handle_author_meta_changes( $post_id, $_POST );
                    break;
                default:
                    # We do nothing about other post types
                    break;
            }

            # Note that we saved our data
            $this->_already_saved = true;
        }
        return;
    }

Sauvegarde des données de l'auteur

Nous avons deux boîtes méta à traiter, notre boîte de détails et notre boîte de livres liés.

Pour la boîte de détails, le traitement est simple. Je ne mets à jour que si c'est absolument nécessaire.

Pour la boîte des livres liés, nous comparons nos livres actuellement liés à ceux cochés par l'utilisateur et ajoutons ou supprimons des relations si nécessaire, en parlant soin de s'assurer que nous n'écrasons pas la relation many to many que nous avons mise en place.

Note : Nous travaillons sur les métadonnées des livres dans la section des livres liés, pas les nôtres.

    # Authors can be linked to multiple books
    # Notice that we are editing book meta data here rather than author meta data
    protected function handle_author_meta_changes( $post_id = 0, $data = array() ) {

        # META BOX - Details
        $current_details = $this->get_author_details_meta( $post_id );

        if ( array_key_exists('favorite_color', $data) && !empty($data['favorite_color'] ) ) {
            $favorite_color = sanitize_text_field( $data['favorite_color'] );
        } else {
            $favorite_color = '';
        }
        if ( array_key_exists('height', $data) && !empty($data['height'] ) ) {
            $height = sanitize_text_field( $data['height'] );
        } else {
            $height = '';
        }
        if ( array_key_exists('eye_color', $data) && !empty($data['eye_color'] ) ) {
            $eye_color = sanitize_text_field( $data['eye_color'] );
        } else {
            $eye_color = '';
        }

        $changed = false;

        if ( $favorite_color != "{$current_details['favorite_color']}" ) {
            $current_details['favorite_color'] = $favorite_color;
            $changed = true;
        }

        if ( $height != "{$current_details['height']}" ) {
            $current_details['height'] = $height;
            $changed = true;
        }

        if ( $eye_color != "{$current_details['eye_color']}" ) {
            $current_details['eye_color'] = $eye_color;
            $changed = true;
        }

        if ( $changed ) {
            update_post_meta( $post_id, '_author_info', $current_details );
        }

        # META BOX - Related Books

        # Get the currently linked books for this author
        $linked_book_ids = $this->get_author_book_ids( $post_id );

        # Get the list of books checked by the user
        if ( array_key_exists('book_ids', $data) && is_array( $data['book_ids'] ) ) {
            $chosen_book_ids = $data['book_ids'];
        } else {
            $chosen_book_ids = array();
        }

        # Build a list of books to be linked or unlinked from this author
        $to_remove = array();
        $to_add = array();

        if ( 0 < count( $chosen_book_ids ) ) {
            # The user chose at least one book to link to
            if ( 0 < count( $linked_book_ids ) ) {
                # We already had at least one book linked

                # Cycle through existing and note any that the user did not have checked
                foreach ( $linked_book_ids as $book_id ) {
                    if ( ! in_array( $book_id, $chosen_book_ids ) ) {
                        # Currently linked, but not chosen. Remove it.
                        $to_remove[] = $book_id;
                    }
                }

                # Cycle through checked and note any that are not currently linked
                foreach ( $chosen_book_ids as $book_id ) {
                    if ( ! in_array( $book_id, $linked_book_ids ) ) {
                        # Chosen but not in currently linked. Add it.
                        $to_add[] = $book_id;
                    }
                }

            } else {
                # No previously chosen ids, simply add them all
                $to_add = $chosen_book_ids;
            }

        } else if ( 0 < count( $linked_book_ids ) ) {
            # No properties chosen to be linked. Remove all currently linked.
            $to_remove = $linked_book_ids;
        }

        if ( 0 < count($to_add) ) {
            foreach ( $to_add as $book_id ) {
                # We use add post meta with 4th parameter false to let us link
                # books to as many authors as we want.
                add_post_meta( $book_id, '_author_id', $post_id, false );
            }
        }

        if ( 0 < count( $to_remove ) ) {
            foreach ( $to_remove as $book_id ) {
                # We specify parameter 3 as we only want to delete the link
                # to this author
                delete_post_meta( $book_id, '_author_id', $post_id );
            }
        }
    }

Enregistrement des auteurs liés pour les livres

À peu près la même chose que l'enregistrement des livres connexes pour les auteurs, sauf que nous travaillons sur nos propres méta de post.

    # Books can be linked with multiple authors
    protected function handle_book_meta_changes( $post_id = 0, $data = array() ) {

        # Get the currently linked authors for this book
        $linked_author_ids = $this->get_book_author_ids( $post_id );

        # Get the list of authors checked by the user
        if ( array_key_exists('author_ids', $data) && is_array( $data['author_ids'] ) ) {
            $chosen_author_ids = $data['author_ids'];
        } else {
            $chosen_author_ids = array();
        }

        # Build a list of authors to be linked or unlinked with this book
        $to_remove = array();
        $to_add = array();

        if ( 0 < count( $chosen_author_ids ) ) {
            # The user chose at least one author to link to
            if ( 0 < count( $linked_author_ids ) ) {
                # We already had at least one author already linked

                # Cycle through existing and note any that the user did not have checked
                foreach ( $linked_author_ids as $author_id ) {
                    if ( ! in_array( $author_id, $chosen_author_ids ) ) {
                        # Currently linked, but not chosen. Remove it.
                        $to_remove[] = $author_id;
                    }
                }

                # Cycle through checked and note any that are not currently linked
                foreach ( $chosen_author_ids as $author_id ) {
                    if ( ! in_array( $author_id, $linked_author_ids ) ) {
                        # Chosen but not in currently linked. Add it.
                        $to_add[] = $author_id;
                    }
                }

            } else {
                # No previously chosen ids, simply add them all
                $to_add = $chosen_author_ids;
            }

        } else if ( 0 < count( $linked_author_ids ) ) {
            # No properties chosen to be linked. Remove all currently linked.
            $to_remove = $linked_author_ids;
        }

        if ( 0 < count($to_add) ) {
            foreach ( $to_add as $author_id ) {
                # We use add post meta with 4th parameter false to let us link
                # to as many authors as we want.
                add_post_meta( $post_id, '_author_id', $author_id, false );
            }
        }

        if ( 0 < count( $to_remove ) ) {
            foreach ( $to_remove as $author_id ) {
                # We specify parameter 3 as we only want to delete the link
                # to this author
                delete_post_meta( $post_id, '_author_id', $author_id );
            }
        }
    }

} # end of the class declaration

Faire fonctionner la classe

Tant que vous chargez la classe avant que les choses soient mises en place ( incluez-la ou inlinez-la dans un plugin, locate_template la fonction de classe dans les fonctions.php de votre thème, etc ), vous pouvez l'utiliser facilement comme suit :

if ( is_admin() ) {
    new Many_To_Many_Linker();
}

Elle se chargera de tout mettre en place
Quelques exemples d'utilisation en front-end

Maintenant que nous avons mis en place des boîtes méta dans les panneaux d'administration qui nous permettent de marquer facilement tous les livres pour un auteur et vice versa, mettons les choses au travail.

Tout d'abord, un couple de fonctions d'aide de départ :

Obtenir tous les livres pour un auteur donné.

Étant donné un id d'auteur, renvoie un tableau d'objets post de livres.

function get_books_for_author_id( $author_id = 0 ) {
    $found = array();

    if ( 0 < $author_id ) {
        $args = array(
            'post_type' => 'book',
            'posts_per_page' => -1,
            'meta_query' => array(
                array(
                    'key' => '_author_id',
                    'value' => $author_id,
                    'type' => 'NUMERIC',
                    'compare' => '='
                )
            )
        );
        $books = new WP_Query( $args );
        if ( $books->have_posts() ) {
            while ( $books->have_posts() ) {
                $book = $books->next_post();
                $found["{$book->ID}"] = $book;
            }
        }
    }

    return $found;
}

Récupérer les données supplémentaires d'un auteur

Nous pourrions améliorer cela en définissant des valeurs par défaut et/ou en attribuant des noms plus agréables aux clés.
Pour l'instant, nous voulons juste les données ou un tableau vide.

function get_author_extra_data_for_author_id( $author_id = 0 ) {
    $data = array();
    if ( 0 < $author_id ) {
        $current = get_post_meta( $author_id, '_author_info', true );
        if ( is_array($current) ) {
            $data = $current;
        }
    }
    return $data;
}

Obtenir le livre et les données supplémentaires dans la boucle principale.

Cet exemple suppose que vous êtes dans la boucle principale (par exemple, l'un des éléments suivants est vrai).

  • is_post_type_archive('auteur')
  • is_singular('auteur')

Cela étant dit, partout où nous avons un id d'auteur connu, nous pouvons utiliser les fonctions d'aide ci-dessus pour tirer leurs livres et des informations supplémentaires.

while ( have_posts() ) {
    the_post();
    $post_id = get_the_ID();

    $books = get_books_for_author_id( $post_id );
    if ( 0 < count($books) ) {
        echo '

This author has published ' . count($books) . ' books:

'; } else { echo '

This author has no published books on record.

'; } $author_data = get_author_extra_data_for_author_id( $post_id ); if ( array_key_exists('favorite_color', $author_data ) ) { echo '

The authors favorite color is ' . $author_data['favorite_color'] . '

'; } }

Obtenir les auteurs de livres

Ci-dessous est une fonction d'aide pour tirer tous les auteurs donnés un id de livre.

function get_authors_for_book_id( $book_id = 0 ) {
    $authors = array();

    if ( 0 < $book_id ) {
        $author_ids = get_post_meta( $book_id, '_author_id', false );
        if ( is_array( $author_ids ) && 0 < count($author_ids) ) {
            $args = array(
                'post_type' => 'author',
                'posts_per_page' => -1,
                'post__in' => $author_ids
            );
            $found = new WP_Query( $args );
            if ( $found->have_posts() ) {
                while ( $found->have_posts() ) {
                    $authors[] = $found->next_post();
                }
            }
        }
    }
    return $authors;
}

Pour utiliser get_authors_for_book_id, vous pourriez l'utiliser sur un modèle de livre unique ou un modèle d'archive de livre comme suit :

while ( have_posts() ) {
    the_post();
    $post_id = get_the_ID();

    # book display might go here as normal in the loop

    # Adding author data.
    $authors = get_authors_for_book_id( $post_id );
    if ( 0 == count($authors) ) {
        echo '

This book has no known author...

'; } else { echo '

Authors:

    '; foreach ( $authors as $author_id => $data ) { $author = $data['post']; $author_info = get_author_extra_data_for_author_id( $author->ID ); echo '
  • ID ) . '">' . $author->post_title . ''; if ( array_key_exists('favorite_color', $author_info) ) { echo '
    Favorite color: ' . $author_info['favorite_color']; } echo '
  • '; } } }

Résultats finaux

Vous avez maintenant deux types de post avec une relation many to many et quelques fonctions d'aide pour tirer des données lorsque cela est nécessaire.

Il y a beaucoup de place pour l'amélioration dans ce code, mais il devrait faire l'affaire.

WordPress n'a pas un moyen de faire nativement des relations many-to-many, jetez un coup d'œil au plugin Posts 2 Posts pour l'activer.

Notes et avis

Si vous avez trouvé cet article utile, il serait très utile que vous le partagiez avec plus de programmeurs et que vous nous aidiez à diffuser cette information.



Utilisez notre moteur de recherche

Ricerca
Generic filters

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.