两个自定义发布类型之间的多对多关系

时间:2012-05-07 作者:Laizer

如何在两个自定义帖子类型之间创建多对多关系?

给定类型A和给定类型B,我希望能够显示与给定A相关的所有B,以及与给定B相关的所有A。

3 个回复
最合适的回答,由SO网友:Privateer 整理而成

正如Milo所指出的,没有内置的方法来处理两个自定义帖子类型之间的多对多关系。

也就是说,现在如果需要的话,没有插件自己做并不难。

我使用非单数post meta在自定义post类型之间建立多对多关系。

e、 g.书籍和作者

将author\\u id post meta(非单数)粘贴在一本书上,您现在可以拥有:

与一本书相关的任意数量的作者

Adding the relationships into the post editors

下面的类负责创建这种关系并将其添加到帖子编辑器中。在本例中,假设一种帖子类型称为“book”,另一种称为“author”。是的,很糟糕的名字。。。但它有助于说明它是如何工作的。

有人可能需要将“author”改为“writer”,或者如果作者是不是真正的博文类型,我就记不起来了。

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\' )
        );

    }

Setting up 2 author meta boxes

我们设置了一个author元框来显示图书关系,另一个用来保存关于作者的额外数据。我们不把这些都放在一个元字段中,因为我们希望能够轻松地查询图书作者关系。如果我们将book\\u ID插入用于作者额外数据的序列化数组中,我们将无法轻松查询它。

通过在每本书上使用名为“\\u author\\u id”的非单数post meta,我们可以:

轻松查询与特定书籍相关的所有作者轻松查询与特定作者相关的所有书籍如果我们希望能够轻松查询关于作者的其他信息,我们可以将其从序列化数组中分离出来,或者使用类似SQL的子句来查找它们,即使它们位于数组中,但分离出来要干净得多。

    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\'
        );
    }
我们使用调用函数,因为我们需要多个函数中的细节元。我们还使用get\\u defaults调用,以便在需要更改时将默认值保留在一次位置。

    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\' => \'\'
        );
    }

The Author Details Box

在本例中,我们只需抓取他们的元并将其显示在文本框中。

    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

    }

The Related Books Box

我们为本例构建了一个复选框列表,以便用户可以在需要时将其全部设置。我们可以用一些javascript添加一个check\\u all type框,按类别进行分类,等等。是的,如果系统中有数千本书,这可能不是最好的计划,但如果我们只有几百本书,效果会很好。

    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;
    }

Grabbing all posts of a type

对于这两种帖子类型,我们都需要一个泛型函数。

    # 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;
    }

Getting books for an author

书籍上有一个\\u author\\u id集,这是一个多值post meta,因此我们可以拥有一本与多个作者相关的书籍。

此函数将书本ID作为数组获取。

    # 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;
    }

Setting up book related authors

我们现在只会有一个相关作者框。

我们把它画出来,就像与作者相关的书一样。

    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;
    }

Getting a books related authors

这只是一个抓住后元并注意到它不是单数的问题。

    # 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;
    }

Saving our meta box data

我们做了一系列的健全性检查,然后,如果需要,将实际处理交给助手函数。

    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;
    }

Saving Author Data

我们有两个元框要处理,我们的详细信息框和相关书籍框。

对于“详细信息”框,处理很简单。我只在绝对需要时更新。

在“相关书籍”框中,我们将当前相关书籍与用户检查的书籍进行比较,并根据需要添加或删除关系,谨慎交谈以确保我们不会取消我们建立的多对多关系。

Note: 我们正在相关图书部分研究图书元数据,而不是我们自己的。

    # 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 );
            }
        }
    }

Saving Related Authors for Books

与为作者保存相关书籍几乎相同,只是我们正在开发自己的post meta。

    # 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

Getting the class to work

只要在设置之前加载该类(将其包含在插件中或将其内联,在theme functions.php中查找\\u template类函数等),就可以按如下方式轻松使用该类:

if ( is_admin() ) {
    new Many_To_Many_Linker();
}
它会处理好一切Some example front end uses

现在,我们已经在管理面板中设置了元框,可以方便地为作者标记所有书籍,反之亦然,让我们开始工作吧。

首先,有两个启动助手函数:

Getting all books for a given author

给定作者id,返回book post对象数组。

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;
}

Grab an authors extra data

我们可以通过设置默认值和/或将键映射到更好的名称来实现这一点。目前,我们只需要数据或空数组。

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;
}

Getting Book and Extra Data in the Main Loop

本例假设您处于主循环中(例如,以下情况之一为真)

也就是说,只要我们有一个已知的作者id,我们就可以使用上述帮助函数来获取他们的书籍和额外信息。

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>\';
    }

}

Getting Book Authors

下面是一个帮助函数,用于拉取给定图书id的所有作者。

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;
}
要使用get\\u authors\\u for\\u book\\u id,可以在单个图书模板或图书归档模板上使用它,如下所示:

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>\';
        }
    }
}

End Results

现在,您有了两种具有多对多关系的帖子类型和一些帮助函数,可以在需要的地方提取数据。

这段代码有很大的改进空间,但应该可以做到这一点。

SO网友:Milo

WordPress没有一种方法可以在本地建立多对多关系,看看Posts 2 Posts plugin 以启用此功能。

SO网友:Alex

现在,您应该使用add\\u meta\\u box{posttype}而不是add\\u meta\\u box\\u for{posttype}:

    add_action(
        "add_meta_boxes_author",
        array( $this, \'setup_author_boxes\' )
    );

    add_action(
        "add_meta_boxes_book",
        array( $this, \'setup_book_boxes\' )
    );

结束

相关推荐