Attach several custom posts to one meta field

All you need is to store the post IDs of the products together with the order (easiest is through post_meta). Then you can run a WP_query to fetch all the products.

The example below is a system that grants users access to a custom post type and charge credits, and gives options to send e-mail on update, set the access status and manually charge credits.

Multiple users

Though its not quite the same as yours, the same principles are at play.

I am assuming you are already confortable with WordPress hooks, CPTs metaboxes etc.

You need to:

  • Add products to a form (Search and fetch the IDs of the products using ajax)
  • Hook into update_post to save the form into post_meta
  • List items from post_meta onto the page.

Imagine you have a metabox with the form:

<form>
   <?php

     // lets get all your products saved as a serialized list
     // more on how thats done later
     $product_ids = get_post_meta( $post_id, 'product_ids', true );

     // if no value, set default empty array
     if ( empty( $product_ids ) ) { 
       $product_ids = array();
     } else {
       // otherwise, deserialize the value
       $product_ids = explode( $product_ids, ',' );
     } 

     /*
        Two things to note about this code:

        1. We are saving the IDs as a string, with ids separated by ','
        which we explode into an array to use

        2. We are using the post_meta 'single' argument instead of 
        saving multiple metas and retrieving as array.

        You could do save multitple meta values, but it will use up more of 
        your database and is generally harder to maintain
     */
  ?>
   <table id="product_list">
     <?php foreach( $product_ids as $p_id ) : ?>
     <tr>
       <td><input type="text" name="product_id[]" value="<?php echo $p_id; ?>"></td>
     </tr>
     <?php endforeach; ?>

     <!-- 
        - Each row has an input with the same name ( id[] ) 
        - Each input value must be a valid ID

        You have to use javascript to populate this table with new items
        which can be fetched using ajax through an input elsewhere in the page

        Note that to remove a field, just remove it from the DOM
        When saving, we'll overwrite the whole array, so any missing
        items won't be saved ... as if they were never there!
     -->
   </table>
</form>

You can hook into save_post. $_POST[ 'product_id' ] now has an array with all posts IDs. Handy!

function yourdomain_save_post( $post_id ) {

  // check we are updating your product CPT
  if ( get_post_type( $post_id ) != 'your_products_cpt' ) { return; }

  // Parse the product id
  // make sure to check the values receieved, nonces, sanitization etc!
  $product_ids = join( $_POST[ 'product_id' ], ',' );
  // this will serialize your product IDs: "2,34,92,3"
  // So it can be used with the display code in the previous example

  // save the list
  update_post_meta( $post_id, 'product_ids', $product_ids, true );

}

Now, if you want to pull each product information (say its name), you can use a WP_Query:

// get comma separated string with product IDs
$product_ids = get_post_meta( $post_id, 'product_ids', true );

// create WP_Query    
$args = array(
  'post_type' => 'your_products_cpt', 
  'post__in'  => $product_ids             // you can use a comma separated list
                                          // of IDs here ... coincidence..?
);
$query = new WP_Query( $args );

if ( $query->have_posts() ) {
  // pretty standard loop from now on
  // You're a pro at this I am sure!
}

That’s it really!

I know its a bit rushed, but hope its enough to get your started.

Once these items are in place you can start adding better functionality and UX, such as using jQuery-ui autocomplete with Ajax to search for product names, use names (with ID as hidden field) in the table, show product pictures … the sky’s the limit.

If you want a quick way to get it done, check out ACF relationship fields.

Also spend some time in the codex for metaboxes and save post hooks

For Ajax, I always refer to an excelent article on Smashingmag

And remember to check your IDs, sanitize input and protect your site with NONCEs!

Cheers!