将两种不同的自定义帖子类型链接在一起

时间:2014-05-05 作者:Kieran

我希望有人能帮忙,因为这是非常令人沮丧的。。。

我正试图在Wordpress中建立一个属性站点,并希望将两种自定义帖子类型链接在一起-

一个是开发区,另一个是该开发区内的个人财产

我知道我可以创建一个名为“开发”的CPT和另一个名为“属性”的CPT,但我如何将它们联系在一起呢?。。例如,如果我创建了一个属性,并试图将其附加到开发中,如果它们是单独的自定义帖子类型,这将如何工作?

我读到了一个类似的问题-How to connect different CPTs together? 斯库巴·凯的回答几乎是我所需要的,但我不知道你会如何查询属于某个开发项目的某个物业

提前感谢!

3 个回复
SO网友:Privateer

如果我了解你在做什么,你就是在寻求在开发区和房地产之间建立一对多的关系。

下面是一个类,如果加载并实例化,它将:

为名为development\\u area的帖子类型创建一个元框,其中包含所有可用属性的复选框列表,以便可以轻松更新两者之间的关系

Note:

此代码说明了如何执行此操作。虽然我已经检查了它是否存在愚蠢的错误,并再次检查了逻辑,但您可能不得不进行调整,因为我没有在运行时测试它。我一直这样做,效果很好。

下面是一个如何使其工作的示例,然后是几个如何使用该类设置的关系的示例。

class One_To_Many_Linker {

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

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

Setting up the meta boxes and save functionality

    protected function do_initialize() {

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

        add_action(
            "add_meta_boxes_for_development_area",
            array( $this, \'setup_development_area_boxes\' )
        );

        add_action(
            "add_meta_boxes_for_property",
            array( $this, \'setup_property_boxes\' )
        );

    }
Creating our needed meta box其他代谢箱也可以在这里轻松设置。

    # Development area post type
    # - this post type can have multiple related properties posts
    public function setup_development_area_boxes( \\WP_Post $post ) {
        add_meta_box(
            \'area_related_properties_box\',
            __(\'Related Properties\', \'language\'),
            array( $this, \'draw_area_related_properties_box\' ),
            $post->post_type,
            \'advanced\',
            \'default\'
        );
    }
Drawing the Related Properties此代码将相关属性绘制为一系列复选框。

    public function draw_area_related_properties_box( \\WP_Post $post ) {

        $all_properties = $this->get_all_of_post_type( \'property\' );

        $linked_property_ids = $this->get_linked_property_ids( $post->ID );

        if ( 0 == count($all_properties) ) {
            $choice_block = \'<p>No properties currently in system.</p>\';
        } else {
            $choices = array();
            foreach ( $all_properties as $property ) {
                $checked = ( in_array( $property->ID, $linked_property_ids ) ) ? \' checked="checked"\' : \'\';

                $display_name = esc_attr( $property->post_title );
                $choices[] = <<<HTML
<label><input type="checkbox" name="property_ids[]" value="{$property->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 lists of posts这将获取特定类型的所有帖子。如果使用粘滞贴子,则需要在args参数中取消设置粘滞标志。

    # 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 linked property ids for a development area

给定开发区域id,这将返回所有链接属性帖子id的数组。

    protected function get_linked_property_ids( $area_id = 0 ) {
        $ids = array();
        if ( 0 < $area_id ) {
            $args = array(
                \'post_type\' => \'property\',
                \'posts_per_page\' => -1,
                \'order\' => \'ASC\',
                \'orderby\' => \'title\',
                \'meta_query\' => array(
                    array(
                        \'key\' => \'_development_area_id\',
                        \'value\' => (int)$area_id,
                        \'type\' => \'NUMERIC\',
                        \'compare\' => \'=\'
                    )
                )
            );
            $results = new \\WP_Query( $args );
            if ( $results->have_posts() ) {
                while ( $results->have_posts() ) {
                    $item = $results->next_post();
                    $ids[] = $item->ID;
                }
            }
        }
        return $ids;
    }

Setting up the Property meta boxes

如果需要,您可以在此处轻松添加更多元框。

    # Post type metabox setup
    public function setup_property_boxes( \\WP_Post $post ) {
        add_meta_box(
            \'property_linked_area_box\',
            __(\'Related Development Area\', \'language\'),
            array( $this, \'draw_property_linked_area_box\' ),
            $post->post_type,
            \'advanced\',
            \'default\'
        );
    }

Drawing the Property editor meta box

这将绘制一个选择元素(下拉框),允许用户选择与属性相关的开发区域。我们使用选择框来确保只能指定一个属性,但单选框列表也可以。

    public function draw_property_linked_area_box( \\WP_Post $post ) {

        $all_areas = $this->get_all_of_post_type(\'development_area\');

        $related_area_id = $this->get_property_linked_area_id( $post->ID );

        if ( 0 == $all_areas ) {
            $choice_block = \'<p>No development areas to choose from yet.</p>\';
        } else {
            $choices = array();
            $selected = ( 0 == $related_area_id )? \' selected="selected"\':\'\';
            $choices[] = \'<option value=""\' . $selected . \'> -- None -- </option>\';
            foreach ( $all_areas as $area ) {
                $selected = ( $area->ID == (int)$related_area_id ) ? \' selected="selected"\' : \'\';

                $display_name = esc_attr( $area->post_title );
                $choices[] = <<<HTML
<option value="{$area->ID}" {$selected}>{$display_name}</option>
HTML;

            }
            $choice_list = implode("\\r\\n" . $choices);
            $choice_block = <<<HTML
<select name="development_area_id">
{$choice_list}
</select>
HTML;

        }

        wp_nonce_field(
            "updating_{$post->post_type}_meta_fields",
            $post->post_type . \'_meta_nonce\'
        );

        echo $choice_block;
    }

The method of linking

请注意,我们通过设置_development_area_id 每个属性上的键。

开发区可以使用此键查询属性以显示它们,属性可以提取自己的元或过滤查询以提取开发数据

protected function get_property_linked_area_id( $property_id = 0 ) {
    $area_id = 0;
    if ( 0 < $property_id ) {
        $area_id = (int) get_post_meta( $property_id, \'_development_area_id\', true );
    }
    return $area_id;
}

Saving the meta data

我们只在必要和适当的时候才努力储蓄。请参见代码注释。

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

        $do_save = true;

        $allowed_post_types = array(
            \'development_area\',
            \'property\'
        );

        # 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 "development_area":
                    $this->handle_development_area_meta_changes( $post_id, $_POST );
                    break;
                case "property":
                    $this->handle_property_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;
    }

Updating Development Properties

我们读取已检查属性帖子类型的列表,获取当前相关属性帖子类型的列表,然后使用这些列表确定要更新的内容。

Notice: 我们正在更新这里的物业帖子类型元数据,而不是在我们编辑的开发区。

    # Development areas can link to multiple properties
    # but each property can only link to a single development area
    protected function handle_development_area_meta_changes( $post_id = 0, $data = array() ) {

        # Get the currently linked property ids for this development area
        $linked_property_ids = $this->get_linked_property_ids( $post_id );

        # Get the list of property ids checked when the user saved changes
        if ( array_key_exists(\'property_ids\', $data) && is_array( $data[\'property_ids\'] ) ) {
            $chosen_property_ids = $data[\'property_ids\'];
        } else {
            $chosen_property_ids = array();
        }

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

        if ( 0 < count( $chosen_property_ids ) ) {
            # The user chose at least one property to link to
            if ( 0 < count( $linked_property_ids ) ) {
                # We already had at least one property linked

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

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

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

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

        if ( 0 < count($to_add) ) {
            foreach ( $to_add as $property_id ) {
                # This will overwrite any existing value for the linking key
                # to ensure we maintain only one dev area linked by each property.
                update_post_meta( $property_id, \'_development_area_id\', $post_id );
            }
        }

        if ( 0 < count( $to_remove ) ) {
            foreach ( $to_remove as $property_id ) {
                # This will delete all existing linked areas for the property
                # to ensure we only ever have one linked area per property
                delete_post_meta( $property_id, \'_development_area_id\' );
            }
        }
    }

Saving our Property Changes

由于我们的元键位于每个属性上,因此我们只需在必要时更新元数据。由于mysql中的读取速度几乎总是快于写入速度,因此我们只在绝对必要时进行更新。

    # Properties only relate to a single development area
    protected function handle_property_meta_changes( $post_id = 0, $data = array() ) {

        # Get any currently linked development area
        $linked_area_id = $this->get_property_linked_area_id( $post_id );
        if ( empty($linked_area_id) ) {
            $linked_area_id = 0;
        }

        if ( array_key_exists( \'development_area_id\', $data ) && !empty($data[\'development_area_id\'] ) ) {
            $received_area_id = (int)$data[\'development_area_id\'];
        } else {
            $received_area_id = 0;
        }

        if ( $received_area_id != $linked_area_id ) {
            # This will overwrite any and all existing copies of our meta key
            # so we can ensure we only have one linked area per property
            update_post_meta( $post_id, \'_development_area_id\', $received_area_id );
        }
    }
}

How To Use The Class

如果在主题函数文件或插件中加载该类,则可以使用以下方法进行操作:

if ( is_admin() ) {
    new One_To_Many_Linker();
}
Some Use Cases Follow下面,我提供了几个前端用例。

显示当前开发区的所有属性显示存档或单个属性中某个属性的开发区

Showing all properties related to the currently displayed development area

global $wp_query;
$area_id = $wp_query->get_queried_object_id();
$args = array(
    \'post_type\' => \'property\',
    \'posts_per_page\' => -1,
    \'meta_query\' => array(
        array(
            \'key\' => \'_development_area_id\',
            \'value\' => $area_id,
            \'compare\' => \'=\',
            \'type\' => \'NUMERIC\'
        )
    )
);
$properties = new \\WP_Query( $args );
if ( $properties->have_posts() ) {
    while( $properties->have_posts() ) {
        $property = $properties->next_post();
        # do something with the property
        $property_link = get_permalink( $property->ID );
        $property_name = esc_attr( $property->post_title );
        echo \'<a href="\' . $property_link . \'">\' . $property_name . \'</a>\';
    }
}

Showing Linked Development Areas

Method 1: 抓取post元,加载区域,并使用数据

适用于单数(\'property\')为true的页面

global $post;
while ( have_posts() ) {
    the_post();
    $post_id = get_the_ID(); # could use $post->ID

    $dev_area_id = get_post_meta( $post_id, \'_development_area_id\', true);
    if ( !empty( $dev_area_id ) ) {
        $development_area = get_post( $dev_area_id );
        # do something...
        $dev_area_link = get_permalink ( $development_area->ID );
        $dev_area_title = $development_area->post_title;
        $dev_area_content = $development_area->post_content;
        echo \'<a href="\' . $dev_area_link . \'">\' . $dev_area_title . \'</a><br />\' . $dev_area_content;
    }
}
Method 2: 使用查询筛选器

适用于is单数(\'property\')为true的页面

  • 适用于is post\\u type\\u archive(\'property\')为true的页面
    • 请注意,这避免了必须提取post type meta和对开发区数据进行额外查询。当您在单个页面上显示更多属性时,这将为您节省越来越多的处理能力。

      在何处放置以下代码:

      在您创建的新插件中recommendedadd_filter(\'posts_clauses\', \'do_my_maybe_modify_queries\', 10, 2); function do_my_maybe_modify_queries( $pieces, \\WP_Query $wp_query ) { if ( !is_admin() && $wp_query->is_main_query() ) { # We are not in the admin panels and we are the primary query for the template if ( array_key_exists(\'post_type\', $wp_query->query_vars) ) { # A post type of some kind was queried. # Grab it as an array of the types specified $value = $wp_query->query_vars[\'post_type\']; if ( !is_array( $value ) ) { if ( empty( $value ) ) { $post_types = array(); } else { $post_types = array( $value ); } } else { $post_types = $value; } if ( in_array(\'property\', $post_types) ) { # We were asked for a property if ( $wp_query->is_post_type_archive || $wp_query->is_singular ) { # Showing the property post type archive or a singular property. # We want to add our development area id, title, and content to the returned fields global $wpdb; # Link the development post to each property through its postmeta key # Since there is only 1 development area per property, this works fine $pieces[\'join\'] .= <<<SQL LEFT JOIN {$wpdb->prefix}postmeta AS dev_pm ON {$wpdb->prefix}posts.ID = dev_pm.post_id AND dev_pm.meta_key = \'_development_area_id\' LEFT JOIN {$wpdb->prefix}posts AS dev_post ON dev_post.ID = dev_pm.meta_value SQL; # Add our wanted development post fields to those returned by the query $pieces[\'fields\'] .= ", IFNULL( dev_pm.meta_value, 0 ) as development_area_id"; $pieces[\'fields\'] .= ", IFNULL( dev_post.post_title, \'\') as development_area_title"; $pieces[\'fields\'] .= ", IFNULL( dev_post.post_content, \'\') as development_area_content"; } } } } return $pieces; } 有了上述内容,您现在可以在单个属性页或属性存档中访问以下数据。我把与属性相关的分类法作为练习留给读者。

      if ( have_posts() ) {
          global $post;
          while ( have_posts() ) {
              the_post();
              if ( property_exists( $post, \'development_area_id\' ) ) {
                  $dev_area_id = $post->development_area_id;
                  $dev_area_title = $post->development_area_title;
                  $dev_area_content = $post->development_area_content;
                  $dev_area_link = get_permalink( $dev_area_id );
                  echo \'<a href="\' . $dev_area_link . \'">\' . $dev_area_title . \'</a><br />\' . $dev_area_content;
              }
          }
      }
      
      Method 3: 筛选WP_查询

      与上面的filter方法类似,但可用于使用WP\\u Query的自定义查询。如果您想编写一个显示一组属性的短代码或小部件,那就太好了。

      首先,我们创建过滤器(非常类似于方法2所示的过滤器)

      function add_dev_data_to_wp_query( $pieces, \\WP_Query $wp_query ) {
          global $wpdb;
      
          if ( !is_admin() && !$wp_query->is_main_query() ) {
              # Link the development post to each property through its postmeta key
              # Since there is only 1 development area per property, this works fine
              $pieces[\'join\'] .= <<<SQL
       LEFT JOIN {$wpdb->prefix}postmeta AS dev_pm ON {$wpdb->prefix}posts.ID = dev_pm.post_id AND dev_pm.meta_key = \'_development_area_id\'
          LEFT JOIN {$wpdb->prefix}posts AS dev_post ON dev_post.ID = dev_pm.meta_value 
      SQL;
      
              # Add our wanted development post fields to those returned by the query
              $pieces[\'fields\'] .= ", IFNULL( dev_pm.meta_value, 0 ) as development_area_id";
              $pieces[\'fields\'] .= ", IFNULL( dev_post.post_title, \'\') as development_area_title";
              $pieces[\'fields\'] .= ", IFNULL( dev_post.post_content, \'\') as development_area_content";
          }
          return $pieces;
      }
      
      接下来,我们在创建查询之前应用过滤器,然后立即删除它,以确保不会更改其他查询。

      $args = array(
          \'post_type\' => \'property\',
          \'posts_per_page\' => -1
      );
      
      # Apply our filter
      add_filter(\'posts_clauses\', \'add_dev_data_to_wp_query\', 10, 2);
      
      # Run the query
      $properties = new \\WP_Query( $args );
      
      # Remove our filter
      remove_filter(\'posts_clauses\', \'add_dev_data_to_wp_query\', 10);
      
      if ( $properties->have_posts() ) {
          while( $properties->have_posts() ) {
              $property = $properties->next_post();
      
              # Do stuff with your properties
              echo \'<p>The property name is \' . $property->post_title . \'</p>\';
      
              # Do stuff with related development areas if that data is available
              if ( property_exists( $property, \'development_area_id\' ) ) {
                  echo \'<p>Part of the development area named \' . $property->development_area_title . \'</p>\';
              }
          }
      }
      
      虽然这样做需要更多的前期工作,但它提供了一些很好的好处

      您只需执行1个查询,而不是2到3个查询来显示相关信息。这加起来很快

    • 一旦你对它感到满意,你可以使用与多个对象的关系,这将为你节省多个额外的查询。每个
    • 使用一个变体,你可以向编辑器列表列添加标题(以及查看和编辑链接)。例如,在上述场景中,您可以在管理中的属性列表中列出每个属性的开发
    SO网友:MikO

    您可以免费使用Advanced Custom Fields 插件,它允许您创建Relationship 自定义字段,将一篇文章与另一篇文章相关联。

    无论如何,首先你应该确保你真的需要两种自定义的帖子类型,并且你不能使用分类法或者仅仅是自定义字段。例如,单个属性可能是post类型,它们所属的开发区域可能是分类法或包含该开发区域名称的自定义字段。。。这基本上取决于您需要保留多少关于开发区域和属性的信息。。。

    SO网友:campatsky

    我会保持简单,只需将开发中找到的属性转换为父开发帖子的子帖子。您还可以使用标准类别分类法,并将每个“子”属性标记为更大开发的一部分。您只需将这些开发作为类别输入分类中即可。这样,您可以将所有属性保存在一个长且易于管理的列表中。调用此列表时,可以使用深度参数或分类法包含或排除所需的任何内容。

    结束

    相关推荐