Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bidirectional relationship #28

Open
mralessio opened this issue Sep 1, 2016 · 6 comments
Open

Bidirectional relationship #28

mralessio opened this issue Sep 1, 2016 · 6 comments

Comments

@mralessio
Copy link

Hi! Is there any way to make a bidirectional relationship?

@jtsternberg
Copy link
Member

This would be a great core feature of this plugin, but is not currently supported. There are plenty of hooks during the save process (both in WordPress and in CMB2) that could be leveraged to save the bidirectional data.

@mralessio
Copy link
Author

I'm trying to use cmb2_save_field_{$field_id} hook and update post meta fields for post ids listed in the field with the new values getting them $field->escaped_value() accordingly. It works well, but the problem is how to remove the relations from posts that are not related any more (removed from the field list) Is there any way how can I retrieve old values?

In this case I could compare old and new values and remove old meta values from posts that not connected anymore. I thought I could make a database query and get all posts that have this meta key and value (which is a current post id), but it's not possible as the ids from the Attached Posts Field stored in a serialized array.

@jtsternberg
Copy link
Member

You're best bet is to get the previous values before CMB2 updates them to the DB (maybe using the 'pre_post_update' WordPress hook), then using the hook you mentioned, get a diff of the old values from the new, and perform the required action. (delete/update meta for the attached items)

@mralessio
Copy link
Author

Justin, thank you! I managed to solve it.. used the hook as you said. Works.

@jtsternberg
Copy link
Member

Would you consider a pull request to optionally enable this feature?

@mralessio
Copy link
Author

mralessio commented Sep 2, 2016

I would be glad, but I'm not sure that I'm skilled enough to make it flawlesly, Actually never done PR before. I've just recently dived into the theme of coding after 15 years pause :)

I could share what I reached in my case, hopefully it may be helpful.
But there is a specific case when a relation could be lost. I don;t know how to solve it.

Post A and Post B are edited in the same time. Make a relation from Post B: B<->A, saved. Relation done for both posts.
At the time Post A is being edited as well and wasn't refreshed, so we don't see previously made connection B<->A. From Post A we make a relation A->C, saved. In this case we loose A<->B relation and will see only A<->C.

Standard way of initializing

function cmb2_attached_posts_for_posts() {
    $prefix = '_cmb2_attached_posts';

    $cmb_post = new_cmb2_box( array(
        'id'           => $prefix . '_metabox',
        'title'        => __( 'Attached Posts', 'cmb2' ),
        'object_types' => array( 'post','project' ), // Post type
        'context'      => 'normal',
        'priority'     => 'high',
        'show_names'   => false, // Show field names on the left
    ) );
    $cmb_post->add_field( array(
        'name'    => __( 'Attached Posts', 'cmb2' ),
        'desc'    => __( 'Drag posts from the left column to the right column to attach them to this page.<br />You may rearrange the order of the posts in the right column by dragging and dropping.', 'cmb2' ),
        'id'      => $prefix . '_ids',
        'type'    => 'custom_attached_posts',
        'options' => 'do_attached_posts_options_cb',
        'old_values' => '', // store old values taken in 'pre_post_update' hook , used for bidirectional relation
    ) );
}
add_action( 'cmb2_init', 'cmb2_attached_posts_for_posts' );

// Options callback
function do_attached_posts_options_cb( $field ) {
    return array(
        'show_thumbnails' => true, 
        'filter_boxes'    => true, 
        'query_args' => array(
            'post__not_in'   => array( $field->object_id ),
            'posts_per_page' => 10,
        ),
    );
}

Using 'pre_post_update' as you advised and getting attached posts before the new update in order to compare later and destroy canceled relations

function attached_posts_for_posts_get_old_values( $post_ID ) {

    // check if post type is not a 'revision'. Otherwise the hook fires more than once.
    if ( 'revision' !== get_post_type( $post_ID ) ) {

        $old_values = get_post_meta( $post_ID,'_cmb2_attached_posts_ids', true );

        // Retrieve a CMB2 instance
        $cmb = cmb2_get_metabox( '_cmb2_attached_posts_metabox' );
        //$old_values = $cmb->get_field( '_cmb2_attached_posts_ids' )->escaped_value();
        $cmb->update_field_property( '_cmb2_attached_posts_ids', 'old_values', $old_values );

    }
}
add_action( 'pre_post_update', 'attached_posts_for_posts_get_old_values' );

Using 'before_delete_post' hook to remove relations in case of deleting post

function attached_posts_for_posts_delete( $post_ID ) {

    if ( ! $unbind_posts = get_post_meta( $post_ID, '_cmb2_attached_posts_ids', true ) ) {
        return;
    }

    foreach ( $unbind_posts as $value => $id ) {
        $post_values = get_post_meta( $id, '_cmb2_attached_posts_ids', true );
        $pos = array_search( $post_ID, $post_values );
        unset( $post_values[ $pos ] );
        update_post_meta( $id, '_cmb2_attached_posts_ids', $post_values );
    }

}
add_action( 'before_delete_post', 'attached_posts_for_posts_delete' );

To store and pass old values that I took in 'before_delete_post' hook I'm using a custom property 'old_values' in the field

/**
 * Bidirectional relation
 * @param bool              $updated Whether the metadata update action occurred.
 * @param string            $action  Action performed. Could be "repeatable", "updated", or "removed".
 * @param CMB2_Field object $field   This field object
 */
function attached_posts_for_posts_bidirectional( $updated, $action, $field ) {

    if ( ! $updated ) {
        return;
    }
    // getting old values
    $old_values = $field->args['old_values'];
    // getting current post id
    $object_id = $field->object_id;
    // getting meta key
    $meta_key = $field->args['id'];

    // getting new values from the Attached Posts field
    //$related_posts = $field->escaped_value();
    $related_posts = get_post_meta( $object_id, $meta_key, true );

    $field_ids = array();

    // update meta field for each selected post with current Post ID
    foreach ( (array) $related_posts as $post => $id ) {

        $field_ids = get_post_meta( $id, $meta_key, true );

        if ( $field_ids && in_array( $object_id ,$field_ids ) ) {
            continue;
        } else {
            $field_ids[] = $object_id;
        }

        update_post_meta( $id, $meta_key, $field_ids );
    }

    // deleting removed relationships
    $unbind_posts = array();

    if ( ! empty( $related_posts ) && ! empty( $old_values ) ) {
        $unbind_posts = array_diff( $old_values, $related_posts );
    } elseif ( ! empty( $old_values ) ) {
        $unbind_posts = $old_values;
    }

    foreach ( (array) $unbind_posts as $value => $post_id ) {

        // check if there is no meta field by some reason
        if ( ! $post_values = get_post_meta( $post_id, $meta_key, true ) ) {
            continue;
        }

        $pos = array_search( $object_id, $post_values );
        unset( $post_values[ $pos ] );
        update_post_meta( $post_id, $meta_key, $post_values );
    }

}
add_action( 'cmb2_save_field__cmb2_attached_posts_ids', 'attached_posts_for_posts_bidirectional', 10, 3 );

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants