Skip Navigation

[Resolved] Bind a CPT to multiple users and show entry’s for bound users

This thread is resolved. Here is a description of the problem and solution.

Problem: I would like to connect Users and Places in a M2M relationship. I have another post type Contact that can be associated with one Place. I would like to create a View of Contacts so each User can see a list of Contacts that are related to the same Places the User is related to.

Solution:
- Create a custom post type "Places" and add some Places posts.
- Create a custom post type to hold the Form submissions. I'll call it "Contacts" but you can call it whatever makes sense for you.
- Create a custom field group that holds all the custom Contact fields you want to collect in the Form. Use a post reference field to link this Contact to one Place.
- Create a "Create Contact" new post Form and place it on your site where visitors can access the Form.
- Create a proxy post type for Users, I'll call it "Engineers". Create Engineer posts for a few Users to test with.
- Set up a many-to-many (M2M) relationship between Engineers and Places. In my example the relationship slug is "engineer-place", Engineer is the parent and Place is the child.
- Connect some Engineers and some Places in wp-admin or with a Relationship Form. Note that you cannot connect Engineers and Places when adding or editing an Engineer or Place with Forms, a separate Relationship Form is required.
- Create a View of Contacts, filtered by Post Reference, where the post reference ID is set by a shortcode attribute. Use the "in" operator so you can pass an array of values into the shortcode.
- In the Loop of this View of Contacts, build the table shown in the Engineer portal. Skip the reply link and come back to that later.
- Place this View on the Engineer portal page. You will see all Contacts now, since we have not set the filter values yet.
- Create a custom shortcode that will return a comma-separated list of Place IDs, filtered by post relationship using the current User's Engineer ID. The goal is to produce a list of the Engineer's related Places like 12, 34, 56 so we can pass that into the View as a shortcode attribute "placereference". This is a general guide you can start with:

function get_related_place_ids_for_current_engineer( $atts )
{
  global $current_user;
 
  // get the current user's engineer proxy post
  $engineer_args = array(
    'post_type'   => 'engineer',
    'post_status' => 'publish',
    'author' => $current_user->ID,
  );
  $engineer_posts = get_posts($engineer_args);
 
  // there should be a proxy post, but check anyway and exit if not
  if( !isset($engineer_posts[0]))
    return;
 
  $engineer = $engineer_posts[0];
 
  // get all the places related to this engineer in m2m 'engineer-place'
  $places = toolset_get_related_posts(
    $engineer,
    'engineer-place',
    'parent',
    1000000,
    0,
    array(),
    'post_id',
    'child'
  );
  return implode(', ', $places);
}
add_shortcode( 'engineer-place-ids', 'get_related_place_ids_for_current_engineer' );

- Modify the slugs and parent / child as needed.
- Register this shortcode in Toolset > Settings > Frontend Content > Third party shortcode arguments.
- Now add the custom shortcode to the View shortcode attribute filter:

[wpv-view name='your-view-slug' placereference='[engineer-place-ids]']

Relevant Documentation:
https://toolset.com/documentation/customizing-sites-using-php/post-relationships-api/#toolset_get_related_posts
https://toolset.com/documentation/user-guides/passing-arguments-to-views/

This support ticket is created 6 years, 6 months ago. There's a good chance that you are reading advice that it now obsolete.

This is the technical support forum for Toolset - a suite of plugins for developing WordPress sites without writing PHP.

Everyone can read this forum, but only Toolset clients can post in it. Toolset support works 6 days per week, 19 hours per day.

Sun Mon Tue Wed Thu Fri Sat
8:00 – 12:00 8:00 – 12:00 8:00 – 12:00 8:00 – 12:00 8:00 – 12:00 - -
13:00 – 17:00 13:00 – 17:00 13:00 – 17:00 13:00 – 17:00 13:00 – 17:00 - -

Supporter timezone: America/New_York (GMT-04:00)

This topic contains 12 replies, has 2 voices.

Last updated by benv-2 6 years, 5 months ago.

Assisted by: Christian Cox.

Author
Posts
#987603

Dear support,

The toolset suite makes it a lot easier for me to create CPT's and the various relationships between them, thank you for the great work put in to this.
Mostly i'll figure this stuff out myself, but this time i've a bit of a time rush. I hope you guys could help me with this use-case:

Use case:
- I have to bind users with a specific user-role to a place.
- I want to store this places within a CPT (Places)
- Then i want to create a different CPT which handles forms submits and has a dropdown which is filled with entries of the places-CPT
- When a form submission is submitted, the user which is bound by the selected place, needs to see this entry.

This will look something like:
- User A can be bound to multiple places (E.g. New York, Dalles, Boston, etc.)
- User B can be bound to one place (E.g. Chicago)
- User C can be bound to one place (E.g. New York)

When a form is submitted with place 'New York', user A and user C needs to see this entry.

Is there a way to realise this with Toolset?

I thought about creating a custom user field for a specific role and fill a multi-select dropdown where the user can 'register' it's own places.
But i'm not sure if this is the right way to realise this.

I just bought toolset and still learning the basics.

Thanks in regards.

#1014347

Hi, first I want to explain the different possible ways for connecting Users and Posts.
- Authorship. You can associate a User with a post by making the User the author of the post. However, this does not allow for many-to-many relationships, because a post can only have one author.
- Custom fields. You can store the ID of a User in a repeating custom field on a Post, or you can store the ID of a Post in a repeating custom field on a User. This will allow you to connect many Posts to each User, and each User can be connected to many Posts. This method can be a challenge to handle in the backend because you must know the numeric IDs of each Post or User you want to connect - you cannot search by User Name or by Post Title. However, it is faster to set up than the next method and does not require an additional post type.
- User proxy CPT. In this method, you create a CPT that acts as a proxy for the User, like "Members". When a new User registers, you can create this Member post automatically with code, or you can require the new User to create a new Member post using Forms. The User will be the author of only one Member post. Then you can use post relationship features to connect Members and other post types. See the documentation here for more information about this: https://toolset.com/documentation/user-guides/how-to-create-custom-searches-and-relationships-for-users/
This method allows you to use the post relationships features and is simpler to maintain in the back-end of the site, but requires an extra post type.

When a form is submitted with place 'New York', user A and user C needs to see this entry.
I'm not sure I understand what you mean. Do you want to redirect the User to the single post for New York after the Form is submitted? If so, then you can use the Forms API cred_success_redirect to programmatically change the Form redirect URL based on a User custom field, or a toolset post relationship, or some other criteria. We offer documentation for this API with examples here: https://toolset.com/documentation/programmer-reference/cred-api/#cred_success_redirect

I can help you set up this custom redirect if you can tell me how you want to connect Users and Posts.

#1019513
UseCase.png

Dear Christian,

Thank you for your detailed reply! Great!
I'm still learning all the concepts that Toolset provides. Thank you for collaberating.

English isn't my native language, which makes it somehow difficult to explain the use-case.
I'll give it another try:

My website will get a form called Contact An Engineer.
This form contains 20+ open fields, with one dropdown field.
This dropdown field has to contain pre-defined places.

When a visitor submits this form, with a choosen place, this form has to go to a WordPress user, that is subscribed to this place.
This wordpress user is an Engineer.

The Engineer is subscribed to a place.

So the example will be:
Engineer A is subscribed to place 'New York'. The website visitor submits a form with place 'New York.
Engineer A recieves a message that he got a message and logs in to wordpress (A front-end page) where he can look into the entry that was submitted by the visitor.

Fact is that multiple engineers can be subscribed to one place, so every subscribed engineer can see the form submission.

In my opinion, the form submission should be a CPT (Contact an Engineer), the places should be an CPT (Places) which holds town names, postal codes and etc. and the Engineer should be a WordPress user with an extra CPT bound to him,
just like you explained in the User Proxy CPT concept.

I've created a mockup, which hopefully makes it easier to understand and added it to this post.

Dear Regards

Edit: The engineers will have to be able to send a reply to an submission. They can reply to an submission and this reply will be send by e-mail on first. This customisation can't be to hard for me, because the contact forms will contain an e-mail field. Only constraint is that a engineers have to reply in 3 days, otherwise the submission will be closed. i'll have to code a bit for that.

#1069318
query-filter.png

Okay thank you, I understand better now. Most of this can be accomplished in the wp-admin area, but at least one bit of custom code is required to filter the View of submissions. Here's how I would set this up in Toolset:
- Create a custom post type "Places" and add some Places posts.
- Create a custom post type to hold the Form submissions. I'll call it "Contacts" but you can call it whatever makes sense for you.
- Create a custom field group that holds all the custom Contact fields you want to collect in the Form. Use a post reference field to link this Contact to one Place.
- Create a "Create Contact" new post Form and place it on your site where visitors can access the Form.
- Create a proxy post type for Users, I'll call it "Engineers". Create Engineer posts for a few Users to test with.
- Set up a many-to-many (M2M) relationship between Engineers and Places. In my example the relationship slug is "engineer-place", Engineer is the parent and Place is the child.
- Connect some Engineers and some Places in wp-admin or with a Relationship Form. Note that you cannot connect Engineers and Places when adding or editing an Engineer or Place with Forms, a separate Relationship Form is required.
- Create a View of Contacts, filtered by Post Reference, where the post reference ID is set by a shortcode attribute. Use the "in" operator so you can pass an array of values into the shortcode. See "query-filter.png" for an example of this Query Filter.
- In the Loop of this View of Contacts, build the table shown in the Engineer portal. Skip the reply link and come back to that later.
- Place this View on the Engineer portal page. You will see all Contacts now, since we have not set the filter values yet.
- Create a custom shortcode that will return a comma-separated list of Place IDs, filtered by post relationship using the current User's Engineer ID. The goal is to produce a list of the Engineer's related Places like 12, 34, 56 so we can pass that into the View as a shortcode attribute "placereference". It sounds like you're comfortable with PHP so this is a general guide you can start with:

function get_related_place_ids_for_current_engineer( $atts )
{
  global $current_user;

  // get the current user's engineer proxy post
  $engineer_args = array(
    'post_type'   => 'engineer',
    'post_status' => 'publish',
    'author' => $current_user->ID,
  );
  $engineer_posts = get_posts($engineer_args);

  // there should be a proxy post, but check anyway and exit if not
  if( !isset($engineer_posts[0]))
    return;

  $engineer = $engineer_posts[0];

  // get all the places related to this engineer in m2m 'engineer-place'
  $places = toolset_get_related_posts(
    $engineer,
    'engineer-place',
    'parent',
    1000000,
    0,
    array(),
    'post_id',
    'child'
  );
  return implode(', ', $places);
}
add_shortcode( 'engineer-place-ids', 'get_related_place_ids_for_current_engineer' );

- Modify the slugs and parent / child as needed.
- Register this shortcode in Toolset > Settings > Frontend Content > Third party shortcode arguments.
- Now add the custom shortcode to the View shortcode attribute filter:

[wpv-view name='your-view-slug' placereference='[engineer-place-ids]']

More information about the post relationships API and passing arguments to Views:
https://toolset.com/documentation/customizing-sites-using-php/post-relationships-api/#toolset_get_related_posts
https://toolset.com/documentation/user-guides/passing-arguments-to-views/

Let me know how much of this you're able to accomplish, or if you get stuck.

#1069538

Hi Christian,

Much thanks for your greatly detailed answer.
I'm following the steps you've written out.
I've also created the user proxy CPT Engineers and created a form where logged-in users with the role Engineer can create their profile.
Do you know if there is way to programmaticaly post an Engineer CPT when registering an User? Something like when a engineer registers, automatically an engineer post is created with the correct author connected?

Or is it possible to add the fields of the Engineers CPT to the register form? This option is more preferable.

Thank you so far Christian.

Dear regards,

#1069552

Do you know if there is way to programmaticaly post an Engineer CPT when registering an User?
Yes, you can use the cred_save_data API to programmatically create an Engineer CPT post with the new User as the author. Here is an example:

add_action('cred_save_data', 'new_user_profile_action',10,2);
function new_user_profile_action($user_id, $form_data)
{
    // if a specific form
    if ($form_data['id']==12345)
    {
      // Create Engineer post object
      $my_post = array(
        'post_title'    => $_POST['first_name'] . ' ' . $_POST['last_name'],
        'post_content'  => '',
        'post_status'   => 'publish',
        'post_author'   => $user_id,
        'post_type' => 'engineer'
      );

      // Insert the post into the database
      wp_insert_post( $my_post );
    }
}

Replace 12345 with the numeric ID of the registration Form. Change the post_title value if you want it to be something other than the first and last name, and change 'engineer' to match the correct post type slug.

Or is it possible to add the fields of the Engineers CPT to the register form? This option is more preferable.
Not exactly, but you can add generic fields in the register Form, then use the cred_save_data API to capture those values and create a new Engineer post. Similar to the example above, but using a generic field to set the Engineer post title:

add_action('cred_save_data', 'new_user_profile_action',10,2);
function new_user_profile_action($user_id, $form_data)
{
    // if a specific form
    if ($form_data['id']==12345)
    {
      // Create Engineer post object
      $my_post = array(
        'post_title'    => $_POST['generic-field-slug'],
        'post_content'  => '',
        'post_status'   => 'publish',
        'post_author'   => $user_id,
        'post_type' => 'engineer'
      );

      // Insert the post into the database
      wp_insert_post( $my_post );
    }
}

https://toolset.com/documentation/user-guides/inserting-generic-fields-into-forms/
https://toolset.com/documentation/programmer-reference/cred-api/#cred_save_data
https://developer.wordpress.org/reference/functions/wp_insert_post/

#1087048
View-query-filter.PNG
Shortcode-registered.PNG
Shortcode.PNG
Page.PNG

Dear Christian,

Sorry that i'm a little late, i was on a vacation.
Programmaticaly creating an Engineer CPT works like a charm, thank you!

There's only one thing that doesn't work.
I created the shortcode engineer-place-ids.
I've put an var_dump on the $places variable, like:

var_dump($places);

When i open the page, i see the connected ID's from the places, so this also works like a charm!

The only thing is, when i add the shortcode to my view, it looks like it doesn't do anything.
I still see all the Contacts, even the ones that aren't associated with the current user (engineer).
I've added some screenshots to give some more sense.

Do you have any idea how i could solve this issue?

#1087071

Please add some debug information like this:

Unfiltered View:<br />
[wpv-view name="offerte-overzicht"]<br />

Installateurplaatsids:<br />
[installateurplaatsids]<br />

Filtered View:<br />
[wpv-view name="offerte-overzicht" placereference='[installateurplaatsids]']

Then take a screenshot showing the results. Paste that here for me to review.

#1087378
Debug-view.PNG

I added the code to the page and deleted al the places and offers, and recreated them.
Surprisingly i got it working, but i don't really know how. I guess deleting the posts did the trick (see attachment).

At this point i want to make it possible that the engineer can respond to an offer.
To respond i want to create an e-mail within the front-end and send it through SMTP or WP_mail.
A response can only be send one time.
Could i achieve this through functions.php?

Also i want to hide the offers that are older then 7 days. I guess i can achieve this with the query filter?

#1087479

Also i want to hide the offers that are older then 7 days. I guess i can achieve this with the query filter?
Yes, the Query Filter can be used to filter by post date or custom field date.

At this point i want to make it possible that the engineer can respond to an offer.
Please create a new ticket if you need assistance creating Forms with email notifications.

#1088356

Dear Christian,

Thank you for your help!
I'll create a new support-call on how to reply through e-mail.

I've one question left on this ongoing subject:
I created an Template for a page, which shows the fields of a single Contact post.
The Engineer can press on a link in the Contacts view and open a single Contact.
In the view, the engineer can only see the contacts that are bound for him through the m2m relation between Place and Contact.
Is it also possible to restrict a single page for the bound engineer?

For example:
Engineer A is bound to place New York. In the contacts view he can only see the contacts from New York.
He tries to modify the slug to e.g. /contact/test12345. This contact isn't bound to New York, but he can still open this one by modifing the slug.

New threads created by Christian Cox and linked to this one are listed below:

https://toolset.com/forums/topic/restrict-access-to-cpts-using-user-proxy-to-post-relationships/

#1088514
Single-entry-restrict.png

I have added an mockup to this post to clarify my previous post.

#1097358
OnlyAfterRe-submission.png

Dear Christian,

Sorry for re-opening this support-call, but this issue i'm having is deeply related to my initial question.

When an website guest does a Contact submission, the engineer thats connected to the Place doesn't see the post.
Only if i manually go to WP-Admin, open the Contact submission and press Update.
After doing that, the engineer sees the submission.

I've added an screenshot for clarification.

Could you help me with this?