Comment créer une relation plusieurs à plusieurs entre deux types de publication personnalisés?
Étant donné le type A et le type B, j'aimerais pouvoir afficher tous les Bs qui se rapportent à un A donné et tous les As qui se rapportent à un B. donné.
Comme l'a souligné Milo, il n'existe pas de méthode intégrée permettant de créer une relation plusieurs à plusieurs entre deux types de publication personnalisés.
Cela étant dit, ces jours-ci, il n'est pas trop difficile de le faire vous-même sans plugin si vous le souhaitez.
Je fais beaucoup de relations entre les types de publication personnalisés en utilisant des méta de publication non singulières.
par exemple. Livres et auteurs
Coller un author_id post meta (non-singulier) sur un livre et vous pouvez maintenant avoir:
Ajout des relations dans les post-éditeurs
La classe ci-dessous s'occupe de créer ce type de relation et de l'ajouter aux rédacteurs. Pour cet exemple, on suppose qu'un type de publication est appelé "livre" et l'autre "auteur". Oui, nom terrible ... mais cela aide à illustrer comment cela fonctionne.
Il se peut que vous deviez changer "auteur" en "écrivain" ou un autre type dont je ne me souviens pas si mon auteur est un type de message 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 méta-boîtes d'auteur
Nous avons configuré une méta-boîte d'auteur pour montrer les relations entre les livres et une autre pour stocker des données supplémentaires sur l'auteur. Nous ne mettons pas tout cela dans un même champ méta, car nous voulons pouvoir facilement interroger les relations entre auteurs de livres. Si nous collions les informations book_ids dans le tableau sérialisé utilisé pour les données supplémentaires de l'auteur, nous ne pourrions plus l'interroger facilement.
En utilisant des méta-messages non singuliers nommés '_author_id' sur chaque livre, nous obtenons la possibilité de:
Si nous voulions pouvoir interroger facilement d'autres choses sur un auteur, nous pouvions soit les séparer du tableau sérialisé, soit utiliser des clauses semblables à celles du code SQL pour les rechercher, même si elles se trouvaient dans un tableau, mais leur éclatement était beaucoup plus simple.
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 car nous avons besoin de la méta de détails dans plusieurs fonctions. Nous utilisons également un appel get_defaults afin de pouvoir conserver les valeurs par défaut enregistrées une seule fois au cas où nous aurions besoin de les modifier.
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 venons de saisir leur méta et de l'afficher dans les zones 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
<p>
<label for="author_favorite_color">Favorite Color:</label>
<input type="text" name="author_favorite_color" value="{$current_meta['favorite_color']}" id="author_favorite_color" />
</p>
<p>
<label for="author_height">Height:</label>
<input type="text" name="author_height" value="{$current_meta['height']}" id="author_height" />
</p>
<p>
<label for="author_eye_color">Eye Color:</label>
<input type="text" name="author_eye_color" value="{$current_meta['eye_color']}" id="author_eye_color" />
</p>
HTML;
# No need for nonce - already added in related books
}
La boîte à livres liés
Nous construisons une liste de cases à cocher pour cet exemple afin que l'utilisateur puisse toutes les définir s'il le souhaite. Nous pourrions ajouter une boîte de type check_all avec du javascript, les séparer 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'en avons que quelques-uns. cent 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 = '<p>No books found in the system.</p>';
} 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[] = <<<HTML
<label><input type="checkbox" name="book_ids[]" value="{$book->ID}" {$checked}/> {$display_name}</label>
HTML;
}
$choice_block = implode("\r\n", $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 publication.
# 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;
}
Obtenir des livres pour un auteur
Les livres ont un _author_id défini sur eux qui est une méta post-valeur à valeurs multiples afin que nous puissions avoir un livre lié à plusieurs auteurs.
Cette fonction récupère les identifiants de livre 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;
}
Configuration des auteurs liés au livre
Nous aurons juste une boîte d'auteurs liés pour le moment.
Nous le dessinons tout comme la boîte à livres de 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 = '<p>No authors found in the system.</p>';
} 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[] = <<<HTML
<label><input type="checkbox" name="author_ids[]" value="{$author->ID}" {$checked}/> {$display_name}</label>
HTML;
}
$choice_block = implode("\r\n", $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 un auteur lié aux livres
Il s’agit simplement de saisir le post meta et de noter que ce n’est pas singulier.
# 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;
}
Sauvegarde de nos données de meta box
Nous effectuons une série de contrôles de cohérence et, si nécessaire, nous transférons le traitement réel aux fonctions d'assistance.
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 d'auteur
Nous avons deux méta-boîtes à traiter, notre boîte de détails et notre boîte de livres connexes.
Pour la boîte de détails, le traitement est simple. Je ne mets à jour que si absolument nécessaire.
Pour la zone des livres liés, nous comparons nos livres actuellement liés à ceux vérifiés par l'utilisateur et ajoutons ou supprimons des relations si nécessaire, en prenant soin de ne pas annuler la relation plusieurs à plusieurs que nous avons configurée.
Remarque: Nous travaillons sur des métadonnées de livre dans la section des livres connexes, pas dans la nôtre.
# 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 );
}
}
}
Sauvegarde des auteurs associés aux livres
Quasiment la même chose que de sauver des livres liés pour les auteurs, sauf que nous travaillons à notre propre méta 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 travailler la classe
Tant que vous chargez la classe avant que les choses ne soient configurées (incluez-la ou insérez-la dans un plugin, localisez la fonction de classe dans votre thème functions.php, etc.), vous pouvez l'utiliser facilement comme suit:
if ( is_admin() ) {
new Many_To_Many_Linker();
}
Il se chargera de tout mettre en place Quelques exemples d'utilisations frontales
Maintenant que nous avons configuré des méta-boîtes dans les panneaux d’administration qui nous permettent de marquer facilement tous les livres d’un auteur et inversement, mettons les choses au travail.
Tout d’abord, quelques fonctions d’aide de départ:
Obtenir tous les livres d'un auteur donné
A partir d'un identifiant d'auteur, retourne un tableau d'objets de publication.
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 des données supplémentaires des auteurs
Nous pourrions définir cela en définissant les valeurs par défaut et/ou en mappant les clés sur des noms plus agréables. 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 un livre et des données supplémentaires dans la boucle principale
Cet exemple suppose que vous êtes dans la boucle principale (par exemple, l'une des conditions suivantes est vraie)
Cela étant dit, partout où nous avons un identifiant d'auteur connu, nous pouvons utiliser les fonctions d'aide ci-dessus pour extraire 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 '<p>This author has published ' . count($books) . ' books:</p><ul>';
foreach ( $books as $book ) {
$book_link = get_permalink( $book->ID );
echo '<li><a href="' . $book_link . '">' . $book->post_title . '</a></li>';
}
echo '</ul>';
} else {
echo '<p>This author has no published books on record.</p>';
}
$author_data = get_author_extra_data_for_author_id( $post_id );
if ( array_key_exists('favorite_color', $author_data ) ) {
echo '<p>The authors favorite color is ' . $author_data['favorite_color'] . '</p>';
}
}
Obtenir des auteurs de livres
Vous trouverez ci-dessous une fonction d'aide permettant de tirer tous les auteurs à l'aide d'un identifiant 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 pouvez l’utiliser sur un seul modèle de livre ou sur 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 '<p>This book has no known author...</p>';
} else {
echo '<p>Authors:</p><ul>';
foreach ( $authors as $author_id => $data ) {
$author = $data['post'];
$author_info = get_author_extra_data_for_author_id( $author->ID );
echo '<li><a href="' . get_permalink( $author->ID ) . '">' . $author->post_title . '</a>';
if ( array_key_exists('favorite_color', $author_info) ) {
echo '<br />Favorite color: ' . $author_info['favorite_color'];
}
echo '</li>';
}
}
}
Résultats finaux
Vous avez maintenant deux types de publication avec une relation plusieurs à plusieurs et des fonctions d'assistance pour extraire les données si nécessaire.
Ce code offre de nombreuses améliorations, mais il devrait faire l'affaire.
WordPress ne permet pas de créer de manière native des relations plusieurs-à-plusieurs, consultez le plugin Posts 2 Posts pour l'activer.