Populating meta box with select-list of existing posts, and assigning it to custom post types

I believe this is what you were looking for.

I moved back to using a class because what you were using would very easily conflict with someone else’s code who decided to use the function names select_box_add_meta_box(), select_box_content() and/or select_box_save_postdata(), which is reasonably likely. The class name WPSE_85107 is only likely to conflict with somebody else answering this question! Of course I’d suggest choosing a better name for your actual plugin but at least pick something that is unlike to conflict.

Using a class also allows us to use short and clear method names that (typically) can just match the hook name. This makes both writing and understanding code so much easier IMO. It is a little more complex when you use add_action() but you only have to learn how once:

add_action( 'add_meta_boxes', 'select_box_add_meta_box' );

vs:

add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );

And you were misusing 'save_post'; it needs two parameters which is the reason there is a 2 after the 10 where the 10 is simply the default priority and is only needed because we had to specify the number of parameters:

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

The correct parameters for save_post() are this:

function save_post( $post_id, $post )

vs. this:

function select_box_save_postdata($data, $postarr) 

Also in the save_post() method I tested to make sure it’s the post type we care about and that there is indeed there is a valid element of $_POST['selected_post'] which should contain the post ID that was selected.

I chose to create (pseudo-)constants and instance variables so that it would be easier for you to see what parameters are required for the various functions such as add_meta_box() and wp_dropdown_pages(). I didn’t add comments because I think if I did you’d see it as being more complicated and thus overwhelming like the other example.

Note that the wp_dropdown_pages() requires a 'hierarchical' post type which is why I had to hack it here to get it to list $post_type=='post'. The test site I used to test this example only has 'post' and 'page' post types; you should be able to change the two lines with $FOR_POST_TYPE and $SELECT_POST_TYPE to get the post types you want.

I used the translation function __() for the labels in case you are creating a plugin that ever needs to work in other languages.

I used admin_init() so that the other code is only run when in the admin but not on the front end. It’s not a big deal but every little bit helps.

And that’s about it. Here’s the code:

<?php
/**
 * Plugin Name: @WPSE 85107
 * Description: <a target="_blank" href="http://wordpress.stackexchange.com/q/85107/89">WPSE 85107</a>
 */

class WPSE_85107 {
  var $FOR_POST_TYPE = 'page';
  var $SELECT_POST_TYPE = 'post';
  var $SELECT_POST_LABEL = 'Post';
  var $box_id;
  var $box_label;
  var $field_id;
  var $field_label;
  var $field_name;
  var $meta_key;
  function __construct() {
    add_action( 'admin_init', array( $this, 'admin_init' ) );
  }
  function admin_init() {
    add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
    add_action( 'save_post', array( $this, 'save_post' ), 10, 2 );
    $this->meta_key     = "_selected_{$this->SELECT_POST_TYPE}";
    $this->box_id       = "select-{$this->SELECT_POST_TYPE}-metabox";
    $this->field_id     = "selected-{$this->SELECT_POST_TYPE}";
    $this->field_name   = "selected_{$this->SELECT_POST_TYPE}";
    $this->box_label    = __( "Select {$this->SELECT_POST_LABEL}", 'wpse-85107' );
    $this->field_label  = __( "Choose {$this->SELECT_POST_LABEL}", 'wpse-85107' );
  }
  function add_meta_boxes() {
    add_meta_box(
      $this->box_id,
      $this->box_label,
      array( $this, 'select_box' ),
      $this->FOR_POST_TYPE,
      'side'
    );
  }
  function select_box( $post ) {
    $selected_post_id = get_post_meta( $post->ID, $this->meta_key, true );
    global $wp_post_types;
    $save_hierarchical = $wp_post_types[$this->SELECT_POST_TYPE]->hierarchical;
    $wp_post_types[$this->SELECT_POST_TYPE]->hierarchical = true;
    wp_dropdown_pages( array(
      'id' => $this->field_id,
      'name' => $this->field_name,
      'selected' => empty( $selected_post_id ) ? 0 : $selected_post_id,
      'post_type' => $this->SELECT_POST_TYPE,
      'show_option_none' => $this->field_label,
    ));
    $wp_post_types[$this->SELECT_POST_TYPE]->hierarchical = $save_hierarchical;
  }
  function save_post( $post_id, $post ) {
    if ( $post->post_type == $this->FOR_POST_TYPE && isset( $_POST[$this->field_name] ) ) {
      update_post_meta( $post_id, $this->meta_key, $_POST[$this->field_name] );
    }
  }
}
new WPSE_85107();

I know you wanted it to be simple so you could understand it but this is pretty much the bare minimum you want for your plugin given the functionality you are asking for. Anything less and you will be cutting off meat and not fat.

What I did not do was add in several of the features that I think are really mandatory if you are building a plugin for someone else to use. But you can read about those features here.

Leave a Comment