Version 1.1

I’m pleased to announce that the Posts 2 Posts plugin now also supports posts-to-users connections. (It supports users-to-users connections as well, but there’s no UI for them):

https://github.com/scribu/wp-posts-to-posts/wiki/Posts-2-Users

This is made possible by a new p2p_type column on the wp_p2p table, with which we can clearly distinguish between user ids and post ids. If you’re upgrading from a previous version of Posts 2 Posts, all the existing connections will be automatically updated to have the correct value for this column.

For the next version, I’m thinking of supporting connections between posts on different blogs in a multisite network.

Version 1.0

It’s been a year and a half since the initial 0.1 release. Posts 2 Posts has come a long way since then and it’s been a very interesting journey.

Here’s what’s new in version 1.0:

Indeterminate connection types

Initially, connection types were organised exclusively around post types. Since version 0.9.5, you can use arbitrary query variables to define the sides of a connection type.

And with this release, there’s a clear distinction between regular connection types and indeterminate connection types, which are a lot easier to control now.

Improved support for ordered connections

You can now order connections both ways. Also, you can get the previous and next post in the list. See Connection ordering.

Related posts

If you have actors and movies, it’s a lot easier now to get other actors that have played in the same movies as a particular actor. See the wiki page about related posts.

Additionally, you can show related posts using the widget.

More connection field types

In previous versions, a connection field could be either a text input or a dropdown. Now, you can also add radio buttons, checkboxes and textareas. See Connection information.

You can see all the tickets closed for this release on github:

https://github.com/scribu/wp-posts-to-posts/issues?milestone=2&state=closed

Version 0.9.5

In short, this version improves the connection type based API introduced in 0.9 and removes the old API.

Connection Type IDs

Previously, you had to keep track of connection type instances yourself, making it hard to work with more than one connection type.

Old:

global $my_connection_type;
 
$my_connection_type = p2p_register_connection_type( array(
	'from' => 'post',
	'to' => 'page'
) );

New:

p2p_register_connection_type( array(
	'id' => 'posts_to_pages',
	'from' => 'post',
	'to' => 'page'
) );

And, to get a list of connected items, instead of:

global $my_connection_type;
 
$connected = $my_connection_type->get_connected( get_queried_object_id() );

you can now do it in one line:

$connected = p2p_type( 'posts_to_pages' )->get_connected( get_queried_object_id() );

One-To-Many Connections

There’s a new ‘cardinality’argument:

p2p_register_connection_type( array(
  'from' => 'post',
  'to' => 'page',
  'cardinality' => 'one-to-many',
  'reciprocal' => true
) );

When editing a page, you will only be able to connect it to a single post and only to a post that doesn’t have a connected page yet.

'cardinality' => 'many-to-one' has the same effect, in reverse.

Arbitrary query variables

You can now distinguish between connection types based on any query variable, not just ‘post_type’:

$types = array(
    'bug' => 'Bug',
    'feature' => 'Feature'
);
 
foreach ( $types as $type => $title ) {
    p2p_register_connection_type( array(
        'from' => 'contact',
        'to' => 'ticket',
        'to_query_vars' => array(
            'meta_key' => 'type',
            'meta_value' => $type
        ),
        'title' => array( 'from' => $title ),
    );
}

The above code will create two meta boxes: in one, contacts will only be able to create connections to ‘bug’-type tickets and in the other only to ‘feature’-type tickets.

There’s a matching ‘from_query_vars’argument as well, so you could, for example, create a connection type between posts from category X to posts with tag Y etc.

Removed old API

I went ahead and removed the old API. Here’s the migration path:

Old New
p2p_connect() p2p_type( ‘my_connection_type’)->connect()
p2p_disconnect() p2p_type( ‘my_connection_type’)->disconnect()
p2p_get_connected() p2p_type( ‘my_connection_type’)->get_connected()

The benefit, of course, is that each of the new methods enforces the rules establised when registering the connection type.

Finally, I set up a code reference site, just to play around with DocBlox. Let me know if you find it useful.

Version 0.9

Admin Box Enhancements

admin box

In the above screenshot you can see the following new features:

Multiple Connections Per Post Type

Besides the ‘fields’parameter, there’s also a ‘data’parameter. This can be used to distinguish between connection types:

p2p_register_connection_type( array(
	'from' => 'actor',
	'to' => 'actor',
	'reciprocal' => true,
	'title' => array( 'from' => 'Doubles', 'to' => 'Main Actor' ),
	'data' => array( 'type' => 'doubles' ),
) );
 
p2p_register_connection_type( array(
	'from' => 'actor',
	'to' => 'actor',
	'reciprocal' => true,
	'title' => 'Friends with',
	'data' => array( 'type' => 'friends' )
) );

API Overhaul

Should I use ‘connected_to’or ‘connected_from’? Erm… I’ll just use ‘connected’.

Even I was tired of having to think about it. No more.

In previous versions, connection types were tightly coupled with the admin box code. I extracted all the logic into a P2P_Connection_Type class, which should make querying for posts a lot easier.

The old way of doing things still works, except in the following cases:

  • p2p_register_connection_type() returns a P2P_Connection_Type instance now. Therefore, 'from' => array( ... ) and 'to' => array( ... ) are not accepted anymore.
  • p2p_each_connected() functions and 'each_connected' query vars are gone. Use P2P_Connection_Type->each_connected() instead:

Old:

$my_query = new WP_Query( array(
    'ignore_sticky_posts' => true,
    'post_type' => 'artist',
    'each_connected' => array(
        'post_type' => 'art',
    )
) );

New:

$my_query = new WP_Query( array(
    'ignore_sticky_posts' => true,
    'post_type' => 'artist',
) );
 
$my_connection_type->each_connected( $my_query );

All the tutorials on the wiki have been updated to use the hot new API, so feel free to dig in. :)

Version 0.8

UI Changes

With the WP 3.2 admin refresh, the connection box UI also received an overhaul:

A significant enhancement in this version is the ability to create draft posts from the connection box. The idea is to be able to make the connections first and then write the content. Hat tip to Oren Kolker.

Additionally, when a post is not published, you will see it’s status. Props Michael Fields for the suggestion.

API Changes

p2p_each_connected() has been revamped to, hopefully, make it easier to use. There’s a detailed tutorial on the wiki:

http://github.com/scribu/wp-posts-to-posts/wiki/Looping-The-Loop

Also, there’s a new function called p2p_list_posts() which takes a WP_Query object or an array of posts as the first argument and outputs a simple unordered list of links to those posts.

Finally, there are 3 new query vars that can be used to get connected posts ordered by a particular connection field. More details on the wiki:

http://github.com/scribu/wp-posts-to-posts/wiki/Connection-ordering

Version 0.7

As promised, this version comes with a much sexier metabox for managing connections:

Now you can:

  • Go to the connected post editing screen in one click.
  • See recent posts using the Recent button.
  • OMG! pagination.

It comes with an enhanced API to boot:

Connection information

Storing arbitrary data related to a particular connection has been possible since version 0.4, but being able to edit that data through the UI was far from easy.

So, this release brings a simple way to do that, with a new argument called ‘fields’:

p2p_register_connection_type( array(
	'from' => 'actor',
	'to' => 'movie',
 
	'fields' => array(
		'role' => 'Role',
		'role_type' => 'Role Type'
	),
	'context' => 'advanced'
) );

Here we just defined a connection type between actors and movies, with each connection having two additional fields: ‘role’– the role of the actor in that particular movie and ‘role type’.

Duplicates

If you want to enable more than one connection between the same two posts, set 'prevent_duplicates' => false when registering the connection type:

p2p_register_connection_type( array(
	'from' => 'actor',
	'to' => 'movie',
	'fields' => array(
		'role' => 'Role',
		'role_type' => 'Role Type'
	),
	'context' => 'advanced',
 
	'prevent_duplicates' => false
) );

This will allow you to have actors with two or more different roles in the same movie, for example.

Distinct titles

Finally, for reciprocal connections, you can set different titles for the metabox, depending on which screen you’re on:

p2p_register_connection_type( array(
	'from' => 'actor',
	'to' => 'movie',
	'fields' => array(
		'role' => 'Role',
		'role_type' => 'Role Type'
	),
	'context' => 'advanced',
	'prevent_duplicates' => false,
	'reciprocal' => true,
 
	'title' => array(
		'from' => 'Played In',
		'to' => 'Cast'
	)
) );

With all those settings in place, you get something like this:

Acknowledgements

Special thanks to ciobi for helping with the UI awesomeness.

Thanks to Alexey Egorov for the icons.

Version 0.6

Nested Queries

If, for each post in a loop, you find yourself doing a subquery using the ‘connected’query vars, I’ve got good news for you.

Replace something like this:

while ( have_posts() ) : the_post();
	$connected_writers = get_posts( array(
		'post_type' => 'writer',
		'nopaging' => true,
		'connected_to' => $post->ID,
		'suppress_filters' => false
	) );
 
	foreach ( $connected_writers as $writer ) {
		echo $writer->post_title;
	}
endwhile;

with this:

p2p_each_connected( 'to', 'writers', array(
	'post_type' => 'writer',
	'nopaging' => true,
) );
 
while ( have_posts() ) : the_post();
	foreach ( $post->connected_writers as $writer ) {
		echo $writer->post_title;
	}
endwhile;

Notice how each $post now has a connected_writers propery, which is an array containing the connected posts.

The main advantage of this method is that it reduces the number of SQL queries dramatically.

Here’s the original forum thread that spurred this feature.

Several bug fixes also went into this release:

  • fixed p2p_is_connected() returning incorrect results
  • made p2p_get_connected() return p2p_ids even with $direction = 'any'
  • made compatible with Proper Network Activation

In the next version, I will be focusing on improving the default admin UI.

Version 0.5

This release is focused on enhancing the API, making it easier to leverage the p2pmeta table.

First of all, a new variable for WP_Query is now available: connected_meta. It allows you to restrict connections based on what meta data they have. Here’s an example:

$my_query = new WP_Query( array(
    'post_type' => 'book',
    'connected_to' => 'any',
    'connected_meta' => array(
        'connection_date' => 'long ago',
    )
) );

This will retrieve any book that has a connection to any other post. Also, the connections have to have a custom field with the key connection_date and the value long ago.

And, if that’s not enough, you can use the advanced meta query syntax:

$my_query = new WP_Query( array(
    'post_type' => 'book',
    'connected_to' => 'any'
    'connected_meta' => array(
        array(
            'key' => 'connection_date',
            'value' => array( 'long ago', 'yesterday' ),
            'compare' => 'IN'
        )
    )
) );

Also, after you do the query, you can access the rest of the connection meta data via the p2p_id property, assigned to each found post:

while ( $my_query->have_posts() ) : $my_query->the_post();
 
    $connection_type = p2p_get_meta( $post->p2p_id, 'connection_type', true );
 
endwhile;

Version 0.4

Using a taxonomy to store post-to-post connections was an interesting exercise. It worked, but when it came time to extend it, it felled flat on its face.

That’s why connections are now stored in a custom table, with an additional meta table for adding arbitrary information per connection.

It is now possible to connect the same two posts twice, but with different metadata, something that would be needed in Justin Tadlock’s movies scenario, for example.

The procedure for migrating old connections is the same as in the previous version: go to /wp-admin/?migrate_p2p and you’re done. (Make sure you have a recent database backup before you do this.)

When searching for posts to connect, it will search only by post title and not by content, as in previous versions.

Reciprocal connections are now stored only once.

Speaking of reciprocal connections, most function in the API accepted a the third parameter, which used to be $reciprocal = false. It has been replaced with $data = array(). More info can be found in the file api.php.

Unless you’re doing advanced stuff, you shouldn’t need to touch the API, though. You can just use WP_Query or get_posts():

Get posts connected from a particular post: $post_id -> $posts

$connected = get_posts( array(
  'suppress_filters' => false,
  'post_type' => 'book',
  'connected_from' => $post_id,
) );

Get posts connected to a particular post: $post_id <- $posts

$connected = get_posts( array(
  'suppress_filters' => false,
  'post_type' => 'book',
  'connected_to' => $post_id,
) );

Get connected posts, regardless of direction: $post_id <- $posts OR $post_id -> $posts

$connected = get_posts( array(
  'suppress_filters' => false,
  'post_type' => 'book',
  'connected' => $post_id,
) );

Version 0.3

There was an interesting discussion on wp-hackers about how best to store many-to-many relationships between posts.

The conclusion was that custom fields are probably the worst solution. While creating a custom table is the most straightforward way to do it, using a custom taxonomy has the most benefits. The most important one is that, when done right, no hand-written SQL is required.

So, this version of the plugin uses a hidden taxonomy to store the connections between posts.

If you were using an older version of the plugin, go to /wp-admin/?migrate_p2p to migrate your connections. You should probably make a database backup beforehand, just in case.

Besides that, the parameter order for p2p_get_connected() and p2p_list_connected() were changed. See all the API changes.