Cloning selected CPT fields from one CPT to another

16 Replies ·

  1. Hi Justin,

    Hope you rest well during holidays and that you are ready to fire a new year !

    Here is my first challenge for 2018 :

    I have one CPT named Conferences and one CPT named Events. The Conferences CPT holds all conferences data. Events CPT is used to program events on my site. I have used ACF plugin to add a relationship field in the Events CPT so I can select a predefined conference when editing a new event.

    I would like to be able to copy automatically all fields from the selected Conferences CPT into the current Event being edited so I don’t have to enter everything by hand. I would then have to select a date and a venue for this event and publish the event. I have tried several method without success. Here is my latest try (for cloning a single field as a start) :

    function rc_Conferences_CPT_Cloning () {
    $current_post_id = get_the_ID(); /* Trying to get the ID of the current Event post being edited*/
    $selected_conference = esc_html( get_post_meta($post_id, 'ACF_field_slug', true) );  /* Should I get the entire post at this time  */
    
       if (! empty ( $selected_conference ) ) { 
           update_post_meta( $post_id, 'title', $selected_conference ); /* Trying to copy the ACF selected conference CPT "title" into the Events CPT title */
       }    
    }
    add_action( 'edit_post',  'rc_Conferences_CPT_Cloning' );
    
    

    I am confused between the post ID of the current Events post I am trying to update and the post ID of the selected Conference post. I am not sure what I am doing at this time ! I am not even sure I am using the right admin action hook as it may require 2 arguments as per Wordpress Codex.

    Any guidance would be welcome !

    Thanks in advance…

    Patrick

  2. Justin Tadlock

    The code definitely needs work, and we can focus on that. However, first, let’s talk about whether you need to clone the fields in the first place.

    Why clone the fields from one CPT to another? Will the fields ever be different? For example, will you ever need to change the fields to something different for the event so that it no longer matches the conference?

    If not, you absolutely have no need to clone the fields. It’s a waste of database space and just creates more overhead for your application.

    You said there was a “relationship” field I assume you’re storing a conference post ID in that field. That’s all you need for the event. You can easily pull that data at any time.

    Also, when you say your CPT names are “Conferences” and “Events”, I assume those are just the labels and you actually named them conference and event. Or, is it something different?

  3. Patrick Sergerie

    Hi Justin !

    Thanks for your reply ! All good questions !

    Here are some clarifications…

    The fields in both CPT will not be different 99% of the time. There may be slight modifications from time to time but that would be insignificant for the sake of our discussion.

    So, you are absolutely right when you say that it is a waste of database space and that it would be possible to query from both CPT to build the site. However, as a start, my event CPT is not home made. I am currently using the Event Espresso plugin for my events for the following reasons :

    • Some of the events need transactional features (i.e. Event registration, payment gateways such as Paypal or Stripe, messaging system et so on…).
    • My current developement skills are limited, so I do not want (at this moment) to dive into building an event management system on my own as it would significantly delay the release of my site (FYI, my current development skills are estimated as follow on a scale of 1 to 10 : PHP=2, HTML=6, CSS=6, JQuery=0.5. Believe me, when my PHP and JQuery skills reach 6 on a scale of 10, it will be a whole different story !)

    That being said, if I limit the information in the Event Espresso plugin to the minimum such as the dates and the venues, I would then have to modify templates, widgets and most probably other plugin functions to make everything work. As for the database space, please note that I have 145 conferences and less than 100 events per year, with 1 or 2 needing registrations and transactions.

    Regarding the relationship field set by the Advanced Custom Fields plugin in the Event page, it is a drop-down list that shows all the selectable conferences. When selected, the title of the conference is stored in the Event page being edited.

    Finally, yes I was using the labels for both CPT. The ID slugs are respectively conferences and espresso_events.

    As additional information, there are 7 fields to clone from the conference CPT :

    Title
    Description
    Excerpt
    Format (custom field)
    Name of speaker (custom field)
    Title of speaker (custom field)
    Entrance fee (custom field)

    There may be other custom fields in the conferences CPT that will used elsewhere in the member section of my site so they don’t have to be cloned.

    Hope that clarifies (a bit) things…

  4. Justin Tadlock

    Regarding the relationship field set by the Advanced Custom Fields plugin in the Event page, it is a drop-down list that shows all the selectable conferences. When selected, the title of the conference is stored in the Event page being edited.

    This must change. You need to store the conference post ID, not the title. I’m not familiar with ACF, but I’d think that’d be the default way of doing this or that it’d have a way to change it to store by ID at the very least.

    IDs are permanent and won’t mess up stuff.


    As for auto-cloning the fields, you’ll want something like this:

    add_action( 'save_post', 'th_save_event_meta', 10, 2 );
    
    function th_save_event_meta( $post_id, $post = '' ) {
    
        // If not editing an event, bail.
        if ( ! is_object( $post ) || 'espresso_events' !== $post->post_type )
            return;
    
        // Need to get the conference ID here.
        $conference_id = absint( get_post_meta( $post_id, 'your_conference_id', true ) );
    
        // If we don't have a conference ID, bail.
        if ( ! $conference_id )
            return;
    
        // Add the exact meta keys you want to clone.
        $clone_keys = array(
            'field_1',
            'field_2',
            'field_3'
        );
    
        foreach ( $clone_keys as $key ) {
    
            $event_meta = get_post_meta( $post_id, $key, true );
    
            $conference_meta = get_post_meta( $conference_id, $key, true );
    
            // If there is no new meta value but an old value exists, delete it.
            if ( '' == $conference_meta && $event_meta )
                delete_post_meta( $post_id, $key );
    
            // If a new meta value was added and there was no previous value, add it.
            elseif ( $event_meta !== $conference_meta )
                update_post_meta( $post_id, $key, $conference_meta );
        }
    }
    
  5. Patrick Sergerie

    Thanks Justin !

    OK for saving the conference ID, i’ll take care of that. But I am unable to make your code work. I have set a fix conference ID for test purposes like this :

    $conference_id = 4867;

    I have also limited the cloning array to the post title like this :

    $clone_keys = array(
            'title'
        );
    

    Here are a few questions :

    1) My understanding of the save_post action hook is that we need to hit the Save Draft button or the Publish button in order for the function to fire. Am I right ?
    2) Why are we setting $post = ” in the function argument ? Don’t we want to have the current $post array values to work with inside the function ?
    3) Why do we need to delete_post_meta if there is no new meta value but an old value exists ? I thought that the update_post_meta() function would overwrite anyways.

  6. Patrick Sergerie

    Hi Justin,

    Although questions 2 and 3 may still be valid, here is an update of my tests.

    I can make the function work when cloning custom fields only.

    Nothing happen when I try to clone CPT supported fields such as title or excerpt.

    For example, for the post title, I have tried post_title instead of title as indicated in my previous reply.

    Any clue ?

  7. Justin Tadlock

    1) My understanding of the save_post action hook is that we need to hit the Save Draft button or the Publish button in order for the function to fire. Am I right ?

    Yes, it is fired at that point.

    2) Why are we setting $post = ” in the function argument ? Don’t we want to have the current $post array values to work with inside the function ?

    Function parameters are merely defaults. Not setting it to '' will give a fatal error on some versions of WP when save_post is fired for attachment posts because they do not pass a post object (not sure if that’s been fixed yet). So, it’s best to set a default.

    3) Why do we need to delete_post_meta if there is no new meta value but an old value exists ? I thought that the update_post_meta() function would overwrite anyways.

    You said you wanted to copy the fields. If the fields in the conference post have been deleted, I’d assume you’d want them to also be deleted for the event. You didn’t specify.

    Nothing happen when I try to clone CPT supported fields such as title or excerpt.

    I didn’t realize you wanted to copy non-meta post fields. Those are a bit different. We could use wp_update_post(), but that’d cause an infinite loop (recalling save_post over and over) without some sort of check.

    It’s probably best to use the wp_insert_post_data hook when handling those particular fields: https://developer.wordpress.org/reference/hooks/wp_insert_post_data/

    For that, you’ll need something like:

    add_filter( 'wp_insert_post_data', 'th_insert_post_data', 10, 2 );
    
    function th_insert_post_data( $data, $postarr ) {
    
        if ( 'espresso_events' !== $data['post_type'] )
            return $data;
    
        $conference_id = 1000; // Get your conference ID here.
    
        $conference = get_post( $conference_id );
    
        if ( ! is_null( $conference ) ) {
    
            $data['post_title'] = $conference->post_title;
    
            $data['post_excerpt'] = $conference->post_excerpt;
        }
    
        return $data;
    }
    
  8. Patrick Sergerie

    Thank you Justin. It’s working as expected. I will then use both functions to populate all required event’s fields.

    Now I need a way to pass the conference ID variable to the wp_insert_post_data() function, which is not obvious for me at this point.

  9. Justin Tadlock

    You should be able to check for it with $_POST['your_conference_field_name']. But, since you’re using ACF, I’m not sure what that field is. The ACF docs or plugin author should be able to provide that info.


    Aside: Ideally (and I know you’re working with a third-party plugin here), the conference ID would actually be stored in the $post->post_parent field for the event. That would simplify things.

  10. Patrick Sergerie

    Thanks again Justin !

    I was not able to get the conference ID through $_POST (that does not mean it’s not because of my poor knowledge of PHP !). As per your $post->post_parent storage recommendation, I am unable (at this moment) to hack the plugin code to allow the page_attribute / hierarchy CPT support. I was able to grab the ACF custom conference field using a combination of get_post_meta() and get_the_ID() on one hand, and a combination of get_post_meta() and $post_id on the other hand (extensive trials and errors here, no idea why it’s working !).

    Anyway, after 4 days of suffering and humility, here where I am with a somehow working code (note that I have added an overwrite condition on both functions to allow data tuning when editing an event) :

    // Code from Justin Tadlock, clone supported post fields
    
    add_filter( 'wp_insert_post_data', 'rc_insert_post_data', 10, 2 );
    
    function rc_insert_post_data( $data, $postarr ) {
    
        $manual_overwrite_native = absint( get_post_meta( get_the_ID(), 'modifier_manuellement_la_conference' , true ) );
    
        if ( 'espresso_events' !== $data['post_type'] || 0 !== $manual_overwrite_native )
            return $data;
    
        $conference_id_native = absint( get_post_meta( get_the_ID(), 'selectionner_une_conference' , true ) );
        $conference = get_post( $conference_id_native );
    
        if ( ! is_null( $conference ) ) {
    
            $data['post_title'] = $conference->post_title;
    
            $data['post_content'] = $conference->post_content;
    
            $data['post_excerpt'] = $conference->post_excerpt;
    
        }
    
        return $data;
    }
    
    
    // Code from Justin Tadlock, clone custom ACF fields
    
    add_action( 'save_post', 'rc_save_event_meta', 10, 2 );
    
    function rc_save_event_meta( $post_id, $post = '') {
    
    
        $manual_overwrite_custom = absint( get_post_meta( $post_id, 'modifier_manuellement_la_conference' , true ) );
    
        // If not editing an event, bail.
        if ( ! is_object( $post ) || 'espresso_events' !== $post->post_type || 0 !== $manual_overwrite_custom )
            return;
    
        // Need to get the conference ID here.
        $conference_id_custom = absint( get_post_meta( $post_id, 'selectionner_une_conference' , true ) );
    
    
        // If we don't have a conference ID, bail.
        if ( ! $conference_id_custom )
            return;
    
        // Add the exact meta keys you want to clone.
        $clone_keys = array(
            'nom_du_conferencier',
            'titre_du_conferencier',
            'format',
            'droit_dentree_general',
            'droit_dentree_etudiants'
        );
    
        foreach ( $clone_keys as $key ) {
    
            $event_meta = get_post_meta( $post_id, $key, true );
    
            $conference_meta = get_post_meta( $conference_id_custom, $key, true );
    
            // If there is no new meta value but an old value exists, delete it.
            if ( '' == $conference_meta && $event_meta )
                delete_post_meta( $post_id, $key );
    
            // If a new meta value was added and there was no previous value, add it.
            elseif ( $event_meta !== $conference_meta )
                update_post_meta( $post_id, $key, $conference_meta );
        }
    
    }
    
    

    Here are the remaining problems :

    • Need to hit the “Save Draft” button twice to fire the rc_insert_post_data() function. I have tried changing the filter priority to 1, 9, 11 and 99 without success. The rc_save_event_meta() function works on a single “Save Draft” depress.
    • Impossible (up now!) to clone taxonomies (categories and tags). Should I build another function using wp_set_post_categories() and wp_set_post_tags() ? Hope not !!!

    Aside : Maybe I should drop ACF and start building my own custom fields ?

    To be continued…

  11. Justin Tadlock

    Need to hit the “Save Draft” button twice to fire the rc_insert_post_data() function. I have tried changing the filter priority to 1, 9, 11 and 99 without success. The rc_save_event_meta() function works on a single “Save Draft” depress.

    The priority isn’t going to matter here. wp_insert_post_data is going to be executed before save_post.

    That’s what my previous reply was about.

    To get the $_POST field (and I don’t know why I didn’t think of this before), you can simply use your browser inspector to view the code for the “conference field”. I assume you have something like a select drop-down. It should look something like this in the source:

    <select name="name_of_the_field">
        <option></option>
        <option></option>
    </select>
    

    If you find the name there, that’ll give you the $_POST['name_of_the_field'] (which should be your conference ID).

    Other than that, we can change the conference to the event post parent. I’m fairly certain I have some code lying around to make that relatively easy.

    Impossible (up now!) to clone taxonomies (categories and tags). Should I build another function using wp_set_post_categories() and wp_set_post_tags() ? Hope not !!!

    You can stick that in your rc_save_event_meta() function.

    Aside : Maybe I should drop ACF and start building my own custom fields ?

    I’m not wholly against meta box frameworks (I have one of my own), and they can be very helpful when you have a lot of meta fields. But, for just a few fields, I always hand code them.

  12. Patrick Sergerie

    Hi Justin,

    Just to let you know that I have not withdraw yet. This is the seventh day that I am trying to program the (apparently simple at first) cloning function but I do not despair yet. I’m trying out tons of combinations to try to learn how the functions work. Up now, I was unable to get the ID through $_POST (yes I have found the name of the field in the Chrome inspector). I cannot clone categories and tags either.

    I will continue to learn before asking (and bothering you !) again as I cannot formulate at this time an intelligent question.

    In the mean time, take care…

    Patrick

  13. Justin Tadlock

    Categories and tags shouldn’t be too tough. I’d be glad to help with that. I’m guessing these are custom taxonomies and not the core WP categories and tags.

    Before doing any cloning, you’d want to make sure the taxonomies are registered to both post types. I’m not sure if you have this set up or not.

  14. Patrick Sergerie

    Hi Justin,

    I have set up the custom taxonomies in both post types. In fact, Event Espresso uses WP core tags, which I also have registered in my conferences CPT. On the other hand, Event Espresso is using custom categories named espresso_event_categories which I have equally set in my conferences CPT using this function :

    add_action( 'init', 'add_categories_to_cpt' );
    function add_categories_to_cpt() {
        register_taxonomy_for_object_type('espresso_event_categories', 'conferences');
    }
    

    My custom categories are hierarchical (3 levels).

    At first, I built this function :

    $conference_categories = get_the_category( $conference_id_custom );
    
        if ( $conference_categories ) {
    
            foreach( $conference_categories as $cat ) {
    
            wp_set_post_categories( $post_id, $cat, true );  // Not sure if we should append as we don't want to keep existing event categories if they exists
    
            }    
        }
    

    I noticed afterward in the Codex that the get_the_category() function can be used only to return WP core categories and that we should use get_the_terms() instead. So I did :

    $conference_categories = get_the_terms( $conference_id_custom, 'espresso_event_categories' );
    
        if ( $conference_categories ) {
    
            foreach( $conference_categories as $cat ) {
    
            wp_set_post_categories( $post_id, $cat, true );
    
            }    
        }
    

    Without success ! Of course, I did try the same function for tags using get_the_tags() and wp_set_post_tags() but I quickly realized that I would need to deal with appending a comma between each tag and with some sort of a counter (beginner intuition !). I then came across this CODEX example :

    $terms = get_the_terms( $post_id , array( 'teams_positions') );
    // init counter
    $i = 1;
    foreach ( $terms as $term ) {
     $term_link = get_term_link( $term, array( 'teams_positions') );
     if( is_wp_error( $term_link ) )
     continue;
     echo $term->name;
     //  Add comma (except after the last theme)
     echo ($i < count($terms))? " / " : "";
     // Increment counter
     $i++;
    }
    

    I feel that I am getting closer but a little help would be welcome…

    As for using the $_POST[] to get the conferences ID inside the rc_insert_post_data() function that uses the wp_insert_post_data() filter hook, I can't make it work, even when using the ACF field name grabbed in the chrome inspector. I am wondering if this is because the event post being edited is not saved when the function is fired ? I was under the impression that $_POST coud return the content of a field in the admin edit form even if the post was not saved.

  15. Justin Tadlock

    Categories:

    Make sure $conference_id and $event_id are the correct IDs in the following code.

    $conference_categories = get_the_terms( $conference_id, 'espresso_event_categories' );
    
    if ( $conference_categories ) {
    
        $term_ids = array();
    
        foreach ( $conference_categories as $cat ) {
    
            $term_ids[] = $cat->term_id;
        }
    
        if ( $term_ids ) {
    
            wp_set_post_terms( $event_id, $term_ids, 'espresso_event_categories' );
        }
    }
    

    Tags:

    Make sure $conference_id and $event_id are the correct IDs in the following code.

    $conference_tags = get_the_tags( $conference_id );
    
    if ( $conference_tags ) {
    
        $term_slugs = array();
    
        foreach ( $conference_tags as $tag ) {
    
            $term_slugs[] = $tag->slug;
        }
    
        if ( $term_ids ) {
    
            wp_set_post_tags( $event_id, $term_slugs );
        }
    }
    

    Getting the $_POST variable:

    $_POST data is sent when a form is submitted. It’s globally available in PHP. You use it like so:

    if ( isset( $_POST['name_of_form_field'] ) ) {
    
        // sanitize with absint() since this is an ID.
        $conference_id = absint( $_POST['name_of_form_field'] );
    }
    

    If you want to do a quick debugging test, run this code in your function and see what shows up:

    wp_die( $_POST['name_of_form_field'] );
    

    Outside of that, there’s really not much I can help with here on my end simply because I don’t have all the necessary information — namely, I’ve never used ACF and am not familiar with how it works. This is something that the ACF author should be able to help with.

    Here’s all you need to ask of them:

    Hi. I’m writing a custom script and need to get a field’s value in a callback function on the wp_insert_post_data hook, which is fired before the field is saved.

    They should be able to help from that point.

  16. Patrick Sergerie

    Fantastic ! Categories and tags are now getting cloned ! My script (or should I say your script !!!) is now 99% completed. In fact, despite the $_POST[] issue, I can go forward and use it making sure to hit twice the Save Draft button to have all fields cloned (not elegant but functional !). Many thanks for your invaluable support !

    As for your suggestion to ask ACF support with respect to my $_POST issue, I will get into it in the next days and keep you informed. I will also try to create a custom field on my own to reduce ACF dependencies. I am starting experimenting custom metabox coding with the help one one of your tutorial here (https://www.smashingmagazine.com/2011/10/create-custom-post-meta-boxes-wordpress/).

    Cheers !

    Patrick