wpmetabox / mb-relationships Goto Github PK
View Code? Open in Web Editor NEWA lightweight WordPress plugin for creating many-to-many relationships between posts, terms and users.
Home Page: https://metabox.io/plugins/mb-relationships/
A lightweight WordPress plugin for creating many-to-many relationships between posts, terms and users.
Home Page: https://metabox.io/plugins/mb-relationships/
As a developer I need to query related posts and render them in an order provided to the WP_Query.
MB Relationships forces the returned payload to be in order of relationship ID.
I wasn't able to query connected posts with post type 'event' from the plugin Events Manager. The relationship can be set when editing the event, but WP_Query always returns zero results. I checked the SQL statement and part of it said:
AND wp_posts.post_type IN ('post', 'page', 'attachment', 'product')
Obviously it does not include 'event', so I had to hook into 'posts_where' to modify this part of the SQL statement and add it back in.
I'm not sure if this is due to how the 'event' post type is set up in EM?
When I add relationships from the UI in an Edit Post screen, and then click update, the post gets saved as usual, but the relationships do not. No error appears. This problem presents on SiteGround, PHP 7.2.13. Locally everything works.
Here is the setup for the testing.
I have registered 2 custom post types, A
and B
and custom taxonomy for A say A_type
.
Then I connect 3 A items to B.
There is 2 custom post types(skipped) and one custom taxonomy for A (A_type)
$slug = 'A_type';
MB_Relationships_API::register(
array(
'id' => 'A_to_B',
'from' => array(
'object_type' => 'post',
'post_type' => 'A',
'admin_column' => 'after title',
),
'to' => array(
'object_type' => 'post',
'post_type' => 'B',
'admin_column' => 'after title',
),
)
);
$query_args = array(
'post_type' => 'B',
'posts_per_page' => -1,
);
$all_Bs = new WP_Query( $query_args );
// This gives all results without problem, all connected A will be put into `connected_A`.
MB_Relationships_API::each_connected(
[
'id' => 'A_to_B',
'to' => $all_Bs->posts,
'property' => 'connected_A', // B post object result with this property
],
);
If I want to filter specific taxonomy A_type from the above, I could add tax_query parameters.
I add the following into the each_connected
2nd argument.
[
'tax_query' => array(
array(
'taxonomy' => 'some_category_in_A',
'field' => 'term_id',
'terms' => 23,
'include_children' => false,
),
),
],
Because according to the source code of each_connected
, the 2nd argument takes usual WP_Query arguments which will be eventually put on WP_Query class to run.
However, the result have one problem.
Assume that there are 3 items from A connected to B.
Only the first one(In the dragging order of the relationship menu, only the top item) connected to respective A will be found and put in connected_A. i.e. If A1 is connected to B1, B2, B3 respectively, in the order of connection menu, if B2 is the first one. Then there is no connected_A result for B1 and B3 and the connection will be regarded and put to B2 connected_A only.
It seems that the query cannot see the other connected items with different order if introducing tax_query
parameters.
I think it might be related to the table join(inner join, left join and so on), because I am not an expert to the MySQL language, my analysis could only be up to this point.
Hopefully someone could help in finishing the remaining bug fixing or limitation fixing work.
Thanks in advance.
For reference purpose, I have copied the query request from the var_dump for further investigation.
# This is the query without tax_query parameter
SELECT prefix_posts.* , mbr.to AS `mbr_A_to_B_to` FROM prefix_posts
INNER JOIN prefix_mb_relationships AS mbr ON (mbr.from = prefix_posts.ID AND
mbr.type = 'A_to_B' AND
mbr.to IN (352,374,348,290,277,327,328))
WHERE 1=1 AND ((
prefix_posts.post_type = 'A' AND
(prefix_posts.post_status = 'publish'
OR prefix_posts.post_status = 'private')))
GROUP BY `mbr_A_to_B_to`, prefix_posts.ID ORDER BY mbr.order_to
# This is the query using tax_query parameter
SELECT prefix_posts.* , mbr.to AS `mbr_A_to_B_to` FROM prefix_posts
LEFT JOIN prefix_term_relationships ON (prefix_posts.ID = prefix_term_relationships.object_id)
INNER JOIN prefix_mb_relationships AS mbr ON (mbr.from = prefix_posts.ID AND
mbr.type = 'A_to_B' AND
mbr.to IN (352,374,348,290,277,327,328))
WHERE 1=1 AND (
prefix_term_relationships.term_taxonomy_id IN (23)
) AND ((prefix_posts.post_type = 'A' AND
(prefix_posts.post_status = 'publish'
OR prefix_posts.post_status = 'private')))
GROUP BY prefix_posts.ID, prefix_posts.ID ORDER BY mbr.order_to
I am a heavy user of relation
queries to get posts across multiple relationships at once. This is broken now unless I define reciprocal => true
on my relationships.
You get a duplicate mbr_origin
table alias which kills the query.
For one of our projects we need to have custom fields associated with the relationship itself rather than with the records to or from the relationship. In the past we have used Toolset, which accommodates custom meta for relationships, but MB Relationships seems to be missing this concept. Before we spend time developing this for ourselves, I wanted to check and see if there is a solution already available, or if such functionality is on the roadmap for MB Relationships.
Here is an example of how custom fields for relationships can be helpful. Let's imagine a site that tracks movies. It has a custom post type called movie
which holds information about each film, and another custom post type called people
which holds information about people in the film industry. We use MB Relationships to create a people-movie
relationship. Anyone who worked on a particular film is related to that movie.
But what if we want to record their role on a particular movie. Were they an actor or director? Were they credited in the film, or was their participation uncredited? If they were an actor, what role did they play? This information is not really about the movie or the person, it is about the nature of their relationship.
In MB terms, we would like to construct a set of custom fields that would be relevant to the relationship. One might be called job
and would be a select field with options like "Actor", "Director", "Gaffer", "Producer", and so on. Another might be a plain text field called role
which would be conditional on the job
being an actor and would let us fill in the role the actor played in this film. And finally we could have a checkbox called credited
which would record whether this relationship was included in the film's credits.
Again, in MB terms, we would like to assign this custom field set to the specific people-movie
relationship, so that every time we assign a new relationship, we are also asked to fill in these custom fields about the nature of the relationship. An then, when retrieving the relationship later in our code, we could also easily retrieve the value of these custom fields associated with the particular relationship record we are dealing with.
I hope that gives you an idea of what we need and what is missing from the current MB Relationships.
Is anything like this planned for MB Relationships? Is there any way we can be helpful in building it?
Do you have any suggestion for tools or approaches to create something like this alongside MB Relationships as it exists today? We'd love any thoughts on how best to implement something like this, since we will need it in place by this summer and I doubt MB Relationships will add anything like this before then.
Thanks!
Version 1.10.11
In the admin-columns.php, there is a parse_config method to take the config from the argument including the meta.
The current 'link' is static value instead of talking it from the parameters, I guess it is supposed to get the config from the meta value?
So that one could use "edit" instead of view.
private function parse_config( $side ) {
$admin_column = $this->$side['admin_column'];
$title = $this->$side['meta_box']['title'];
$config = array(
'position' => '',
'target' => '',
'title' => $title,
'link' => 'view',
);
// ...
So it is by design, according to the testing logic in post.php render_view method, there is possibility to use "edit" instead I think.
It could be something like,
// ...
$admin_column = $this->$side['admin_column'];
$title = $this->$side['meta_box']['title'];
$view = $this->$side['meta_box']['view'];
$config = array(
'position' => '',
'target' => '',
'title' => $title,
'link' => $view,
);
// ...
Is it correct?
If I am editing a post that has relationships and save that post as a draft, the relationship is defined in the database to the draft post. But if I then go to the record that was the target of that relationship, it will not show the connection to the draft item. Also, if I import records into the database that have connections to or from draft posts, those connections are not consistently shown in the post editor or retrieved by get_connected()
.
MB Relationships should allow connections to draft posts. In fact, even now it allows you to choose a draft post when building a relationship. But whether you create that relationship on the "to" or "from" side, if you then go look at the other related post in the editor, the relationship will be missing. If you look at the database, the relationship will be present.
To reproduce this, create a new post on the "from" side (in our case a new "ordination" post). Then select a related post (in our case a "priest" post). Then "Save draft" this "from" side post instead of publishing it. Now reload the editor, you will see that MB remembers the relationship and shows it properly. You will also find this relationship in the database. Now edit to the "to" side record (the "priest") you just connected to. MB will not show the relationship here. Even stranger, if you now add the relationship again back to the draft "from" record (which MB will allow you to do), then the relationship will become visible on the "to" record, even after reloading. But now, if you return to the "from" record, the relationship will have disappeared there!
Through all of this, the database remains correct, showing the relationship exists. But the meta boxes in the editor will be wrong, and the get_connected()
function will also not return the relationship.
Here is a specific example, the database record shows this...
ID from to type order_from order_to
329 328 198 priest-ordination-as-priest 1 0
When we call MB_Relationships_API::get_connected(['id' => 'priest-ordination-as-priest', 'from' => 328])
we get post ID 198 returned as expected. But when we call MB_Relationships_API::get_connected(['id' => 'priest-ordination-as-priest', 'to' => 198])
we get no posts returned.
What's up?
When deleting a (custom) post object, the following error is thrown:
wpdb::prepare was called incorrectly. The query does not contain the correct number of placeholders (3) for the number of arguments passed (2).
In consequence, no relationship is deleted from the db.
This originates from storage-handler.php::delete_object_relationships
As far as I can tell, depending on the value of $target
, the parameter $object_id
has to be passed as the forth argument again to wpdb->prepare
.
The resulting code would look something like this:
protected function delete_object_relationships( $object_id, $type, $target ) {
global $wpdb;
$sql = $target
? "DELETE FROM $wpdb->mb_relationships WHERE `type`=%s AND `$target`=%d"
: "DELETE FROM $wpdb->mb_relationships WHERE `type`=%s AND (`from`=%d OR `to`=%d)";
if ( $target ) {
$wpdb->query(
$wpdb->prepare(
$sql,
$type,
$object_id
)
);
} else {
$wpdb->query(
$wpdb->prepare(
$sql,
$type,
$object_id,
$object_id
)
);
}
}
mb-relationships: 1.10.8
wordpress: 5.8
Latest release on this repo is 1.5.0 but the wp.org repo is still at 1.4.1
Any reason? can you release latest on .org please?
Hi Anh,
I created a ticket in the metabox forum but I don't see it as published.
When I get the reciprocal relationship fields they seem sorted by post_date and they no longer follow the admin order.
new WP_Query(
array(
'relationship' => array(
'id' => 'posts_to_posts',
'to' => get_the_ID(),
),
'nopaging' => true,
)
);
I’m not sure if there are any additional parameters that I need to add to get the administrator order. Thank you!
Hi,
in the MB Relationships ADMIN “from”-Metabox, the sort order of the related “to”-posts gets lost whenever a “to”-post is edited and saved. Sometimes the sort order gets reversed.
So whenever I have edited and saved a “to”-post and later want to edit a “from”-post, I have to manually reorder all “to”-posts in the MB Relationships Metabox before saving. This is slightly annoying in development and a no-go in production.
Can you please look into this?
Thanks
Per
Here is my MB Relationships config:
MB_Relationships_API::register( array(
‘id’ => ‘tours_to_locations’,
‘from’ => array(
‘object_type’ => ‘post’,
‘post_type’ => ‘product’,
‘meta_box’ => array( ‘context’ => ‘advanced’, ‘title’ => ‘Locations’ ),
),
‘to’ => array(
‘object_type’ => ‘post’,
‘post_type’ => ‘location’,
‘admin_column’ => ‘after title’,
‘meta_box’ => array( ‘context’ => ‘normal’, ‘title’ => ‘Used in Tours’ ),
),
) );
Thanks for a lightwieght plugin.
I have follow complete guide as per below link and connected my 2 custom type of post "Post" and "Event".
https://docs.metabox.io/extensions/mb-relationships/#displaying-connected-posts
Please see below connecting snippet done for the same for understanding.....
if ( class_exists( 'MBR_Loader' ) ){
add_action('mb_relationships_init',function () {
if (post_type_exists( 'event' ))
MB_Relationships_API::register(
array(
'id' => 'posts_to_events',
'from' => array(
'object_type' => 'post',
'post_type' => 'post',
'admin_column' => 'after title',
'meta_box' => array(
'title' => 'Connect To Event',
'field_title' => 'Select Event',
),
),
'to' => array(
'object_type' => 'post',
'post_type' => 'event',
'admin_column' => 'after title',
'meta_box' => array(
'title' => 'Connect From Pulse',
),),));});}
Event post already working but connected post are not displaying in "post" type. To display the connected post using below in "post" page I am using below code.
if ( class_exists( 'MBR_Loader' ) ){
$relatedpost = new WP_Query( array(
'relationship' => array(
'id' => 'posts_to_events',
'to' => get_the_ID(),
),
'nopaging' => true,
) );
if ($relatedpost->found_posts > 0)
{
$output = '<div class="related-content"><strong>Related Post/s: </strong><br><ul>';
while ( $relatedpost->have_posts() ) {
$relatedpost->the_post();
$output .= '<li>' . get_the_date().' - <a href="'.get_permalink().'">'.get_the_title() . '</a> ('.get_post_type().')</li>';
}
wp_reset_postdata();
$output .= '</ul></div>';
echo $output;
}
}
Is MB Realtionship only works on connecting Post (here Event) not in connected Post (here Post). Is not bidirectional or I am doing wrong anywhere?
Hi, Im trying MB relationships plugin but after many hours I still cant use it. In WP-admin is everything OK, but when I try to query posts from template its failed. Can you take look at this bug? Its really flustrating and usseless now.
Init:
add_action( 'mb_relationships_init', function() {
MB_Relationships_API::register( array(
'id' => 'articlebanner_to_articles',
'from' => 'm_articlebanner',
'to' => 'blog_article'
) );
} );
Query function:
function getPostBanner(){
$connected = new WP_Query( array(
'post_type' => 'm_articlebanner',
'relationship' => array(
'id' => 'articlebanner_to_articles',
'to' => 3798,
),
'nopaging' => true
) );
return $connected;
}
Thanks for help.
Sometimes, there is a need to have a one-to-many (or many-to-one) relationship. I understand that all MetaBox relationships are many-to-many, and for querying that's OK. However, what is often important is to not let the user select more than one relationship. This could be easily solved through the UI, e.g. by always rendering exactly one field. This should be configurable, of course; perhaps another member for the meta_box
index.
it would be great adding a timestamp for each record in the relationships table
It would be very useful if the relationship metabox had a link to the thing that it represents the relationship to. For example, I have Questions and Answers custom post types. They are related many-to-many. It would be great if I could one-click go to the Questions list page (or even the Add New page) from an Answer's Edit page, and the other way around.
Another useful feature could be to go to the Edit page for the linked object itself, i.e. to its Edit page. So, following my example above, if I am in an Question's Edit page, I would be able to click on any related Anwers, and be taken to its edit page. This is especially helpful when there are multiple different objects of the same type and with the same title.
I am developing a theme using MB relationships and i want to know if theres a way to count the connected itens.
i.e:
custom post type: author
custom post type: artwork
relationship: author_to_artwork
and echo this: Van Gogh have 913 paintings
is it possible in an archive page?
Would be nice to show the post title with a (draft) or (private) next to it when applicable. Right now if a post is draft status, the select field shows the connection based on the database call but the placeholder text shows the default text of 'Select a ....'. See my screenshot below. Atlanta and San Diego are published but the middle on is a draft.
This is confusing and does not describe what is actually going to the end user.
Environment:
with referring to the setup example in this post
MB_Relationships_API::each_connected does not fetch anything
I found that the each_connected()
does not return item for user to post
relationship.
Here is the possible cause
for discussion and further investigation.
for instance, I try to var_dump
the output from each_connected
for the item. It gives result.
Then I find out that when it pass to distribute
and then filter
before returning anything.
The filter gives a clue about the cause.
If the query_object
is user_query
, the object missed the relationship_key
so when filter try to match anything with the relationship_key
passed by each_connected
, it return nothing because there is nothing to match.
This problem does not occur in post to post
relationship, only in user to post
relationship and user_query
(self::$$query_object missed relationship_key)
Because I am new to MB Relationship, I did not find out how the relationship_key
is added to the user_object yet.
Maybe someone familiar with it could provide a workaround or solutions to resolve the problem.
When using a query such as below, no results are returned if the post type of the relationship was registered with exclude_from_search
set to true
(example taken from docs):
$connected = new WP_Query( array(
'relationship' => array(
'id' => 'posts_to_pages',
'from' => get_the_ID(), // You can pass object ID or full object
),
'nopaging' => true,
) );
The plugin sets the post_type
argument to 'any'
for every post query which has the relationship
argument. When post_type
is set to 'any'
, according to WP Codex, post types that have exclude_from_search
set to true
will not be queried. In such cases, WordPress explicitly excludes posts that "shouldn't be searchable" from the list.
Normally, it is possible to query "non-searchable" post types by explicitly specifying the post type. However, this plugin overwrites the post_types
arg with any
.
To solve this, modify the plugin to only set post_types
to any if the argument isn't already present.
Hi!
wp-content/plugins/mb-relationships/inc/class-mb-relationships-relationship.php
public function get_object_type( $side ) {
return $this->$side['object_type'];
}
produces the warning "Illegal string offset 'object_type'" and should be changed to
return $this->{$side}['object_type'];
to work in older versions of PHP. (Sorry, incorrect title, I mixed it up)
I've been working on an integration plugin for MB Relationships and WPGraphQL.
What would be nice is a post-registration hook in this plugin, so once a user has called the API's register function I can execute the WPGraphQL registration. My plugin would also add an additional field to the settings (graphql_name
or similar), but this can be ignored by MBR.
The flow would look like this:
show_in_graphql => true
and have supplied graphql_name
as part of the to
and from
settingshi,
please adjust your create tables code to the official guide:
https://codex.wordpress.org/Creating_Tables_with_Plugins
In some cases the collate can be empty, failing to create the table, thus not saving:
mb-relationships/inc/database/table.php
Lines 27 to 47 in 58df92c
the get_charset_collate() takes care of this:
$charset_collate = $wpdb->get_charset_collate();
// Create new table.
$sql = "
CREATE TABLE {$wpdb->mb_relationships} (
ID
bigint(20) unsigned NOT NULL AUTO_INCREMENT,
from
bigint(20) unsigned NOT NULL,
to
bigint(20) unsigned NOT NULL,
type
varchar(44) NOT NULL default '',
order_from
bigint(20) unsigned NOT NULL,
order_to
bigint(20) unsigned NOT NULL,
PRIMARY KEY (ID
),
KEY from
(from
),
KEY to
(to
),
KEY type
(type
)
) $charset_collate;";
cheers!
~~Currently only published posts are shown, even if there is a connection to a private post. ~~ (can already be modified using query_vars argument)
Filtering the title displayed would also be helpful, so we can add the publish date to distinguish events, for example.
Whenever the Custom Post Types is set to exclude from search, it will be excluded from the results in the get_connected() return-data. This seems to be per WP-settings, when post_type == 'any' applies.
However, one might not want that CPT to appear within search results on frontend.
Hy everyone,
is there a way to customize the html of the relationships for the backend side?
I have to add more children's field in order to edit it directly from father view. I also have to add the drag feature so i can handle the print order of the custom post's children.
This is a screen from Band Edit View (I use the same example of the site tutorial)
In a few words, I have to recreate all the functionality of the existing "Text List" field type.
Thank you for your help
When retrieving related entities with WP_Query->query()
by ID or set of IDs, the resulting entities will have the ID of the related entities set as the mb_origin
property. This allows us to avoid having to query backwards for each entity. However, when returning the results, such as in a function e.g. get_answers_for_questions()
, the consumer of such a function cannot know whether the entities were retrieved by mb-relationships
, or in some other way, and therefore cannot rely on some generic mb_origin
property.
It could be useful to be able to set a name for it. In my case, when I try to get Answer posts related to questions with the specified IDs, it would be useful if the Question ID of each Answer was under e.g. question_id
. This could be as easy as changing the column alias name.
Also, if multiple Answers match each question ID, there will be more than one Answer in the result - each with a different mb_origin
. It could be extremely useful to have the ability to have only unique Answers, but their e.g. question_ids
parameter set to an array of IDs of related Questions.
Thanks for adding this option in the main plugin.
Can you add it to this extension as well?
On issue #20 I talked about two custom post-types that I created: authors and artworks.
Ive made a single-author template and then queried the relationships on artworks, but I couldnt get the pagination to work. Am I missing something?
The CPT declaration:
register_post_type(
'autores',
array(
'supports' => array(
'title',
'revisions',
'comments',
),
'public' => true,
'publicly_queryable' => true,
'show_in_menu' => false,
'has_archive' => true,
'hierarchical' => true,
'rewrite' => array(
'slug' => PLUGIN_SLUG . '/autor',
'with_front' => false,
'pages' => true,
),
)
);
The query:
$args = array(
'post_type' => 'obras',
'post_status' => 'any',
'relationship' => array(
'id' => 'obras_to_autores',
'to' => $post->ID,
),
'posts_per_page' => 6,
'paged' => 0,
);
$connected = new WP_Query($args);
When I click the number of the pagination menu, it redirects to the same page
I have 17 custom post types. And although not all will be loaded on a production site, each one relates to one main CPT. I normally have 3-6 cpts on a production site. Person is the main CPT and then all other cpts like presentation, publication, offices all relate to person. On each page reload in the admin, I am calling get_posts a ton, I think, because the metabox is querying each post type to populate the select fields.
Is there anything we can do to bundle these queries or even use WP_Query so caching can be utilized?
Should I suppress_filters => false
?
My main goal is reduce the number of queries meaning that I am open to ideas.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.