最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

metabox - Relate two custom post type using meta box

programmeradmin2浏览0评论

I've two custom post type:

1.products 2.seller

Now I want to relate both the post types using the meta box. I want to display products in seller posts so that sellers can select the required multiple products and assign a price to each selected product. Finally, In the seller's post, those products need to be displayed with prices which he entered in the meta box.

Please help me with this and I want to do this without the plugin. I am trying to code. If I get any idea will post here. Thanks in advance.

I've two custom post type:

1.products 2.seller

Now I want to relate both the post types using the meta box. I want to display products in seller posts so that sellers can select the required multiple products and assign a price to each selected product. Finally, In the seller's post, those products need to be displayed with prices which he entered in the meta box.

Please help me with this and I want to do this without the plugin. I am trying to code. If I get any idea will post here. Thanks in advance.

Share Improve this question edited Aug 21, 2019 at 13:50 fuxia 107k39 gold badges255 silver badges459 bronze badges asked Aug 21, 2019 at 11:20 PraveenPraveen 2405 silver badges19 bronze badges 1
  • It is hard to determine the best solution for your intended use. I am assuming that there will be purchases made from a sellers post of products that should be set to the price the seller has set, is that correct? if so, it seems that another approach to this goal would be to identify products that the seller posts and actually programmatically create a duplicate product that is priced by sellers desire and this will avoid the issue of checkout price setting per seller. Can you elaborate further on your goal? – Mr.Lister Commented Aug 29, 2019 at 21:09
Add a comment  | 

4 Answers 4

Reset to default 6

I assume you would like to end up with something like this:

Code way

If you want to put this logic in your template/plugin, as you state in your question, there is quite a bit of coding.

You need to:

  • create custom post types
  • create metabox for seller
  • create content for metabox - that's called "callback" in the meta_box docs
  • create logic that saves data from the meta box
  • create metabox for product
  • retrieve the data on front end

Create custom post types

I assume you already have this done, but let's register custom post types product and seller manually here:

Products ('product')

/**
 * Post Type: Products.
 */
function se345571_register_cpt_product() {

  $labels = array(
    "name" => __( "Products", "mytheme_textdomain" ),
    "singular_name" => __( "Product", "mytheme_textdomain" ),
  );

  $args = array(
    "label" => __( "Products", "mytheme_textdomain" ),
    "labels" => $labels,
    "description" => "",
    "public" => true,
    "publicly_queryable" => true,
    "show_ui" => true,
    "delete_with_user" => false,
    "show_in_rest" => true,
    "rest_base" => "",
    "rest_controller_class" => "WP_REST_Posts_Controller",
    "has_archive" => false,
    "show_in_menu" => true,
    "show_in_nav_menus" => true,
    "exclude_from_search" => false,
    "capability_type" => "post",
    "map_meta_cap" => true,
    "hierarchical" => false,
    "rewrite" => array( "slug" => "product", "with_front" => true ),
    "query_var" => true,
    "supports" => array( "title", "editor", "thumbnail" ),
  );

  register_post_type( "product", $args );
}

add_action( 'init', 'se345571_register_cpt_product' );

Sellers ('seller')

/**
 * Post Type: Sellers.
 */
function se345571_register_cpt_seller() {

  $labels = array(
    "name" => __( "Sellers", "mytheme_textdomain" ),
    "singular_name" => __( "Seller", "mytheme_textdomain" ),
  );

  $args = array(
    "label" => __( "Sellers", "mytheme_textdomain" ),
    "labels" => $labels,
    "description" => "",
    "public" => true,
    "publicly_queryable" => true,
    "show_ui" => true,
    "delete_with_user" => false,
    "show_in_rest" => true,
    "rest_base" => "",
    "rest_controller_class" => "WP_REST_Posts_Controller",
    "has_archive" => false,
    "show_in_menu" => true,
    "show_in_nav_menus" => true,
    "exclude_from_search" => false,
    "capability_type" => "post",
    "map_meta_cap" => true,
    "hierarchical" => false,
    "rewrite" => array( "slug" => "seller", "with_front" => true ),
    "query_var" => true,
    "supports" => array( "title", "editor", "thumbnail" ),
  );

  register_post_type( "seller", $args );
}

add_action( 'init', 'se345571_register_cpt_seller' );

Create metabox for product

/**
 * Adds a box to "advanced" part on the Seller edit screen.
 * - See the different screens defined in $screens array.
 */
function se345571_add_seller_meta_box() {

  $screens = array( 'seller' );

  foreach ( $screens as $screen ) {

    // https://codex.wordpress/Function_Reference/add_meta_box - add_meta_box(), see for further params
    add_meta_box(
      'product_settings_box',                           // HTML 'id' attribute of the edit screen section
      __( 'Product settings', 'mytheme_textdomain' ),   // Title of the edit screen section, visible to user
      'se345571_product_settings_meta_box_callback',    // Function that prints out the HTML for the edit screen section.
      $screen                                           // Which writing screen ('post','page','dashboard','link','attachment','custom_post_type','comment')
    );

  }
}
add_action( 'add_meta_boxes', 'se345571_add_seller_meta_box' );

Here you can see, that add an action, which is called in add_meta_boxes - that means, when Wordpress is building up the meta boxes for writing screen, it's gonna call our custom se345571_add_seller_meta_box() function as well.

Create content for metabox

/**
 * Prints the box content.
 * 
 * @param WP_Post $post The object for the current post/page.
 */
function se345571_product_settings_meta_box_callback( $post, $box ) {

  // Add a nonce field so we can check for it later.
  wp_nonce_field( 'se345571_product_settings_meta_box_data', 'se345571_product_settings_meta_box_nonce' );

  /*
   * Use get_post_meta() to retrieve an existing value
   * from the database and use the value for the form.
   */
  $value = get_post_meta( $post->ID, '_product_settings', true );

  if ($value) {
    $product_settings = json_decode($value, true);
  }

  // Get available products so we can show them in select box
  $args = [
    'post_type' => 'product',
    'numberposts' => -1,
    'orderby' => 'id',
    'order' => 'ASC'
  ];

  $products = new WP_Query($args);

  // As you can see, i have 5 product fields, this can be just about any number
  $max = 5;

  ?>
  <table>
    <?php for ($index = 0; $index < $max; $index++) : ?>
    <tr>
      <td>
        <label for="product-<?php echo $index + 1 ?>-product"><?php _e( 'Product #' . ($index + 1), 'mytheme_textdomain' )?></label>
      </td>
      <td>
        <?php $productindex = 0; ?>
        <select name="product_settings[<?php echo $index ?>][product_id]" id="product-<?php echo ($index + 1) ?>-product">
          <?php while($products->have_posts()) : $products->the_post(); $productindex++; ?>
            <option value="<?php the_ID() ?>" <?php echo (isset($product_settings[$index]['product_id']) && (int)$product_settings[$index]['product_id'] === get_the_ID()) ? 'selected' : '' ?>><?php the_title() ?></option>
          <?php endwhile; ?>
        </select>
      </td>
      <td>
        <label for="product-<?php echo $index + 1 ?>-price"><?php _e( 'Price', 'mytheme_textdomain' )?></label>
      </td>
      <td>
        <input 
          name="product_settings[<?php echo $index ?>][price]" 
          type="text" 
          class="components-text-control__input" 
          id="product-<?php echo ($index + 1) ?>-price"
          value="<?php echo isset($product_settings[$index]['price']) ? $product_settings[$index]['price'] : '' ?>">
      </td>
    </tr>
    <?php endfor; ?>
  </table>
  <?php

  // Don't forget about this, otherwise you will mess up with other data on the page
  wp_reset_postdata();

}

Okay, here it comes with quite a bit of code. As other people in this thread suggested, you might find useful to extend this logic with javascript (such as select2.js). You can write that directly to HTML output of this function.

Saving the data


/**
 * When the post is saved, saves our custom data.
 *
 * @param int $post_id The ID of the post being saved.
 */
function se345571_product_settings_meta_box_data( $post_id, $post ) {

  // Check if our nonce is set.
  if ( ! isset( $_POST['se345571_product_settings_meta_box_nonce'] ) ) {
    return;
  }

  // Verify that the nonce is valid.
  if ( ! wp_verify_nonce( $_POST['se345571_product_settings_meta_box_nonce'], 'se345571_product_settings_meta_box_data' ) ) {
    return;
  }

  // If this is an autosave, our form has not been submitted, so we don't want to do anything.
  if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
    return;
  }

  /* OK, it's safe for us to save the data now. */

  // Make sure that it is set.
  if ( ! isset( $_POST['product_settings'] ) ) {
    return;
  }

  // HERE STARTS THE ACTUAL FUNCTIONALITY

  // Sanitize user input.
  $product_settings = json_encode( $_POST['product_settings'] );

  // Update the meta field in the database.
  update_post_meta( $post_id, '_product_settings', $product_settings );

}

add_action( 'save_post', 'se345571_product_settings_meta_box_data', 10, 2 );

Here's another hook, save_post and our custom logic to it. Basically, it's 1:1 what's found on Codex to the add_meta_box() function just swapped the POST field names.

Create metabox for product

You ask to display price the seller entered for different products, as i don't have any frontend here, i will show in the product admin page. But the same logic can be applied to show it on frontend for example.


/**
 * Adds a box to "advanced" part on the Seller edit screen.
 * - See the different screens defined in $screens array.
 */
function se345571_add_product_meta_box() {

  $screens = array( 'product' );

  foreach ( $screens as $screen ) {

    // https://codex.wordpress/Function_Reference/add_meta_box - add_meta_box(), see for further params
    add_meta_box(
      'product_settings_box',                           // HTML 'id' attribute of the edit screen section
      __( 'Seller prices', 'mytheme_textdomain' ),      // Title of the edit screen section, visible to user
      'se345571_seller_prices_meta_box_callback',       // Function that prints out the HTML for the edit screen section.
      $screen                                           // Which writing screen ('post','page','dashboard','link','attachment','custom_post_type','comment')
    );

  }
}
add_action( 'add_meta_boxes', 'se345571_add_product_meta_box' );

/**
 * Prints the box content.
 * 
 * @param WP_Post $post The object for the current post/page.
 */
function se345571_seller_prices_meta_box_callback( $post, $box ) {

  $product_id = get_the_ID();

  $args = [
    'post_type' => 'seller',
    'numberposts' => -1,
    'meta_query' => [
      [
        'key' => '_product_settings',
        'value' => '"product_id":"' .$product_id. '"',
        'compare' => 'LIKE',
      ]
    ]
  ];

  $sellers = new WP_Query($args);

  while($sellers->have_posts()) : $sellers->the_post();

    $seller_prices = json_decode(get_post_meta( get_the_ID(), '_product_settings', true ), true);

    $seller_prices_for_product = array_filter($seller_prices, function($element) use ($product_id) { 
      if (isset($element['product_id'])) {
        return (int)$element['product_id'] === $product_id;
      }
      return false;
    });

    foreach($seller_prices_for_product as $price) :
      ?>
      <p>
        <?php the_title(); ?> <?php _e('for', 'mytheme_textdomain'); ?> <?php echo $price['price'] ?>
      </p>
      <?php
    endforeach;

  endwhile;

  // Don't forget about this, otherwise you will mess up with other data on the page
  wp_reset_postdata();

}

Retrieving data

Finally when you need to grab your data on the frontend, you can go to template and use something like:

$value = get_post_meta( get_the_ID(), '_product_settings', true );
$product_settings = json_decode($value, true);

get_post_meta() uses your current post ID, key of the field you want to retrieve (optional) and whatever you want only one or all meta (optional - not set returns all custom fields).

This said, i'd actually go the plugin way (unless you are writing plugin yourself).

Plugin way

For cases like this, as other people pointed out, i'd pick Advanced Custom Fields plugin. Which can basically do the same, what is coded above.

Setting up the meta box is done via user interface.

Retrieving data

For retrieving data you use functions get_field() or the_field() - whatever you want to return the value or print the value right away (same naming convention as in WP all around).

get_field('your_field_name');

Sources and further reading

http://themefoundation/wordpress-meta-boxes-guide/ https://codex.wordpress/Function_Reference/add_meta_box http://www.smashingmagazine/2011/10/04/create-custom-post-meta-boxes-wordpress/

You'd need a repeater relationship custom field. The easiest way would be still to user a plugin for this part – I'm very happy with ACF1 – but there are quite a few options. One advantage is they provide a font end forms option so you could move the editing for your customers in the front-end (without the need of visiting /wp-admin) – this requires a bit of coding.

Then, if you're really keen of not using a plugin you could see how that works and replicate the functionality yourself – note that you'd also need to replicate the repeater metabox UI – which might not be that trivial.

Alternatively, you could include ACF in your code – meaning that you won't use it as an installeg plugin – but you'd still need to include one required ACF script in yout plugin/theme.

1 The repeater field would require the PRO version – for a prototype you can find it down at gpldl.

It's a bit more complicated than just using a plugin but it's certainly doable. Since you don't provide examples of your code (as of 2019-08-28) I cannot use it in my answer so I'll try to be as specific as possible in pseudo-code.

The way I usually do it is to create a meta_field (get_post_meta, update_post_meta) with your custom post type (CPT) ID. In this case however, that would mean probably an array of seller_ids for each product and an array of product_ids for each seller. This is not practical at all, it breaks the idea of relationships and it opens the door to data corruption if one record updates but not the other.

Since you have a many to many relationship (sellers can have different products and products can have different sellers) you want to create your own join table with only 2 fields: product_id and seller_id with each ID being the CPT's post ID. See this related post for details on a many to many relationship.

Then, when you need to display either the product or the seller, you can see which of the other you would need to load. Simply load all of the products or sellers where the ID from the relationship table match the ID of the product or seller you are showing.

You will need to do the following logic:

  1. in sellers post type: create a meta box that selects the seller products from products post type, you can use select2.js to make it easy to select the seller products.

  2. save the selected products in the seller post as an array of product ids using update_post_meta.

  3. at the seller single page you can retrieve the selected seller product ids that we saved in the previous step using get_post_meta().

  4. create a WP_Query with the given ids from the previous step to display the products of the current seller.

发布评论

评论列表(0)

  1. 暂无评论