Posts 2 Posts - Version 0.7

Version Description

  • improved UI
  • added 'fields', 'context' and 'prevent_duplicates' args to p2p_register_connection_type()
  • more info
Download this release

Release Info

Developer scribu
Plugin Icon wp plugin Posts 2 Posts
Version 0.7
Comparing to
See all releases

Code changes from version 0.6 to 0.7

api.php CHANGED
@@ -5,10 +5,13 @@
5
  * This creates the appropriate meta box in the admin edit screen
6
  *
7
  * @param array $args Can be:
8
- * - 'from' string|array The first end of the connection
9
- * - 'to' string|array The second end of the connection
10
- * - 'title' string The box's title
11
- * - 'reciprocal' bool wether to show the box on both sides of the connection
 
 
 
12
  * - 'box' string A class that handles displaying and saving connections. Default: P2P_Box_Multiple
13
  */
14
  function p2p_register_connection_type( $args ) {
@@ -16,20 +19,27 @@ function p2p_register_connection_type( $args ) {
16
 
17
  if ( count( $argv ) > 1 ) {
18
  $args = array();
19
- list( $args['from'], $args['to'], $args['reciprocal'] ) = $argv;
20
  }
21
 
22
- foreach ( (array) $args['from'] as $from ) {
23
- foreach ( (array) $args['to'] as $to ) {
24
- $args['from'] = $from;
25
- $args['to'] = $to;
26
- P2P_Connection_Types::register( $args );
27
- }
28
- }
 
 
 
 
 
 
 
29
  }
30
 
31
  /**
32
- * Connect a post to one or more other posts
33
  *
34
  * @param int|array $from The first end of the connection
35
  * @param int|array $to The second end of the connection
@@ -44,7 +54,7 @@ function p2p_connect( $from, $to, $data = array() ) {
44
  }
45
 
46
  /**
47
- * Disconnect a post from or more other posts
48
  *
49
  * @param int|array $from The first end of the connection
50
  * @param int|array|string $to The second end of the connection
@@ -59,7 +69,9 @@ function p2p_disconnect( $from, $to, $data = array() ) {
59
  }
60
 
61
  /**
62
- * Get a list of connected posts
 
 
63
  *
64
  * @param int $post_id One end of the connection
65
  * @param string $direction The direction of the connection. Can be 'to', 'from' or 'any'
@@ -68,17 +80,6 @@ function p2p_disconnect( $from, $to, $data = array() ) {
68
  * @return array( p2p_id => post_id )
69
  */
70
  function p2p_get_connected( $post_id, $direction = 'any', $data = array() ) {
71
- if ( 'any' == $direction ) {
72
- $from = P2P_Connections::get( $post_id, 'from', $data );
73
- $to = P2P_Connections::get( $post_id, 'to', $data );
74
-
75
- foreach ( $to as $p2p_id => $post_id ) {
76
- $from[ $p2p_id ] = $post_id;
77
- }
78
-
79
- return $from;
80
- }
81
-
82
  return P2P_Connections::get( $post_id, $direction, $data );
83
  }
84
 
@@ -242,7 +243,10 @@ class P2P_Query {
242
  if ( !$found )
243
  return $the_posts;
244
 
245
- list( $search, $key, $direction ) = $found;
 
 
 
246
 
247
  p2p_each_connected( $direction, $key, $search, $wp_query );
248
 
@@ -251,6 +255,7 @@ class P2P_Query {
251
 
252
  private function find_qv( $wp_query, $prefix = '' ) {
253
  foreach ( self::$qv_map as $qv => $direction ) {
 
254
  $search = $wp_query->get( $prefix . $qv );
255
  if ( !empty( $search ) )
256
  break;
5
  * This creates the appropriate meta box in the admin edit screen
6
  *
7
  * @param array $args Can be:
8
+ * - 'from' string The first end of the connection.
9
+ * - 'to' string The second end of the connection.
10
+ * - 'fields' array Additional metadata fields (optional).
11
+ * - 'prevent_duplicates' bool Wether to disallow duplicate connections between the same two posts. Default: true.
12
+ * - 'reciprocal' bool Wether to show the box on both sides of the connection. Default: false.
13
+ * - 'title' string The box's title. Default: 'Connected {$post_type}s'
14
+ * - 'context' string Where should the box show up by default. Possible values: 'advanced' or 'side'
15
  * - 'box' string A class that handles displaying and saving connections. Default: P2P_Box_Multiple
16
  */
17
  function p2p_register_connection_type( $args ) {
19
 
20
  if ( count( $argv ) > 1 ) {
21
  $args = array();
22
+ @list( $args['from'], $args['to'], $args['reciprocal'] ) = $argv;
23
  }
24
 
25
+ $defaults = array(
26
+ 'from' => '',
27
+ 'to' => '',
28
+ 'fields' => array(),
29
+ 'prevent_duplicates' => true,
30
+ 'reciprocal' => false,
31
+ 'title' => '',
32
+ 'context' => 'side',
33
+ 'box' => 'P2P_Box_Multiple',
34
+ );
35
+
36
+ $args = wp_parse_args( $args, $defaults );
37
+
38
+ P2P_Connection_Types::register( $args );
39
  }
40
 
41
  /**
42
+ * Connect a post to one or more other posts.
43
  *
44
  * @param int|array $from The first end of the connection
45
  * @param int|array $to The second end of the connection
54
  }
55
 
56
  /**
57
+ * Disconnect a post from or more other posts.
58
  *
59
  * @param int|array $from The first end of the connection
60
  * @param int|array|string $to The second end of the connection
69
  }
70
 
71
  /**
72
+ * Get a list of connected posts.
73
+ *
74
+ * Low-level function. Use new WP_Query( array( 'connected' => $post_id ) ) instead.
75
  *
76
  * @param int $post_id One end of the connection
77
  * @param string $direction The direction of the connection. Can be 'to', 'from' or 'any'
80
  * @return array( p2p_id => post_id )
81
  */
82
  function p2p_get_connected( $post_id, $direction = 'any', $data = array() ) {
 
 
 
 
 
 
 
 
 
 
 
83
  return P2P_Connections::get( $post_id, $direction, $data );
84
  }
85
 
243
  if ( !$found )
244
  return $the_posts;
245
 
246
+ list( $search, $qv, $direction ) = $found;
247
+
248
+ $qv = explode('_', $qv);
249
+ $key = isset( $qv[1] ) ? $qv[1] : '';
250
 
251
  p2p_each_connected( $direction, $key, $search, $wp_query );
252
 
255
 
256
  private function find_qv( $wp_query, $prefix = '' ) {
257
  foreach ( self::$qv_map as $qv => $direction ) {
258
+
259
  $search = $wp_query->get( $prefix . $qv );
260
  if ( !empty( $search ) )
261
  break;
lang/posts-to-posts-ro_RO.mo CHANGED
Binary file
lang/posts-to-posts-ro_RO.po CHANGED
@@ -7,9 +7,9 @@ msgid ""
7
  msgstr ""
8
  "Project-Id-Version: Posts 2 Posts 0.3\n"
9
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/posts-to-posts\n"
10
- "POT-Creation-Date: 2010-10-11 13:40+0300\n"
11
- "PO-Revision-Date: 2010-10-11 13:42+0200\n"
12
- "Last-Translator: scribu <scribu@gmail.com>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
14
  "Language: \n"
15
  "MIME-Version: 1.0\n"
@@ -29,21 +29,49 @@ msgstr "Salvează schimbările"
29
  msgid "Settings"
30
  msgstr "Setări"
31
 
32
- #: ui/boxes.php:59
33
- msgid "No connections."
34
- msgstr "Nu există conexiuni."
35
 
36
- #: ui/boxes.php:71
37
- msgid "Search"
38
- msgstr "Căutare"
39
 
40
- #: ui/boxes.php:79
41
- msgid "Start typing the title of a post you want to connect and then click on to connect it."
42
- msgstr "Începe să scrii titlul post-ului de conectat și apoi fă click pentru a-l conecta."
43
 
44
- #: ui/boxes.php:89
45
- msgid "Enter IDs of posts to connect, separated by commas."
46
- msgstr "Intrdu ID-uri ale post-urilor de conectat, separate prin virgulă."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
  #. Plugin Name of the plugin/theme
49
  msgid "Posts 2 Posts"
@@ -65,6 +93,14 @@ msgstr ""
65
  msgid "http://scribu.net/"
66
  msgstr ""
67
 
68
- #~ msgid "Connected"
69
- #~ msgstr "Conectat"
 
 
 
 
 
 
 
 
70
 
7
  msgstr ""
8
  "Project-Id-Version: Posts 2 Posts 0.3\n"
9
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/posts-to-posts\n"
10
+ "POT-Creation-Date: 2011-03-30 21:56+0300\n"
11
+ "PO-Revision-Date: 2011-03-30 21:56+0200\n"
12
+ "Last-Translator: scribu <mail@scribu.net>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
14
  "Language: \n"
15
  "MIME-Version: 1.0\n"
29
  msgid "Settings"
30
  msgstr "Setări"
31
 
32
+ #: ui/boxes.php:88
33
+ msgid "Create connections:"
34
+ msgstr "Creează conexiuni:"
35
 
36
+ #: ui/boxes.php:108
37
+ msgid "Previous"
38
+ msgstr "Anterioare"
39
 
40
+ #: ui/boxes.php:109
41
+ msgid "of"
42
+ msgstr "din"
43
 
44
+ #: ui/boxes.php:110
45
+ msgid "Next"
46
+ msgstr "Următoare"
47
+
48
+ #: ui/boxes.php:114
49
+ msgid "Recent"
50
+ msgstr "Recente"
51
+
52
+ #: ui/boxes.php:174
53
+ #: ui/boxes.php:175
54
+ msgid "Create connection"
55
+ msgstr "Crează conexiune"
56
+
57
+ #: ui/boxes.php:182
58
+ #: ui/boxes.php:183
59
+ msgid "Delete connection"
60
+ msgstr "Șterge conexiune"
61
+
62
+ #: ui/boxes.php:189
63
+ #: ui/boxes.php:190
64
+ msgid "Delete all connections"
65
+ msgstr "Șterge toate conexiunile"
66
+
67
+ #: ui/ui.php:40
68
+ #, php-format
69
+ msgid "Connected %s"
70
+ msgstr "%s conectate"
71
+
72
+ #: ui/ui.php:87
73
+ msgid "Are you sure you want to delete all connections?"
74
+ msgstr "Sigur vreți să ștergeți toate conexiunile?"
75
 
76
  #. Plugin Name of the plugin/theme
77
  msgid "Posts 2 Posts"
93
  msgid "http://scribu.net/"
94
  msgstr ""
95
 
96
+ #~ msgid "Search"
97
+ #~ msgstr "Căutare"
98
+ #~ msgid ""
99
+ #~ "Start typing the title of a post you want to connect and then click on to "
100
+ #~ "connect it."
101
+ #~ msgstr ""
102
+ #~ "Începe să scrii titlul post-ului de conectat și apoi fă click pentru a-l "
103
+ #~ "conecta."
104
+ #~ msgid "Enter IDs of posts to connect, separated by commas."
105
+ #~ msgstr "Intrdu ID-uri ale post-urilor de conectat, separate prin virgulă."
106
 
lang/posts-to-posts.pot CHANGED
@@ -1,15 +1,15 @@
1
- # Translation of the WordPress plugin Posts 2 Posts 0.4 by scribu.
2
- # Copyright (C) 2010 scribu
3
  # This file is distributed under the same license as the Posts 2 Posts package.
4
- # FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
5
  #
6
  #, fuzzy
7
  msgid ""
8
  msgstr ""
9
- "Project-Id-Version: Posts 2 Posts 0.4\n"
10
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/posts-to-posts\n"
11
- "POT-Creation-Date: 2010-10-11 13:40+0300\n"
12
- "PO-Revision-Date: 2010-MO-DA HO:MI+ZONE\n"
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
15
  "Language: \n"
@@ -29,22 +29,45 @@ msgstr ""
29
  msgid "Settings"
30
  msgstr ""
31
 
32
- #: ui/boxes.php:59
33
- msgid "No connections."
34
  msgstr ""
35
 
36
- #: ui/boxes.php:71
37
- msgid "Search"
38
  msgstr ""
39
 
40
- #: ui/boxes.php:79
41
- msgid ""
42
- "Start typing the title of a post you want to connect and then click on to "
43
- "connect it."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  msgstr ""
45
 
46
- #: ui/boxes.php:89
47
- msgid "Enter IDs of posts to connect, separated by commas."
48
  msgstr ""
49
 
50
  #. Plugin Name of the plugin/theme
1
+ # Translation of the WordPress plugin Posts 2 Posts 0.7-beta by scribu.
2
+ # Copyright (C) 2011 scribu
3
  # This file is distributed under the same license as the Posts 2 Posts package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
5
  #
6
  #, fuzzy
7
  msgid ""
8
  msgstr ""
9
+ "Project-Id-Version: Posts 2 Posts 0.7-beta\n"
10
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/posts-to-posts\n"
11
+ "POT-Creation-Date: 2011-03-30 21:56+0300\n"
12
+ "PO-Revision-Date: 2011-MO-DA HO:MI+ZONE\n"
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
15
  "Language: \n"
29
  msgid "Settings"
30
  msgstr ""
31
 
32
+ #: ui/boxes.php:88
33
+ msgid "Create connections:"
34
  msgstr ""
35
 
36
+ #: ui/boxes.php:108
37
+ msgid "Previous"
38
  msgstr ""
39
 
40
+ #: ui/boxes.php:109
41
+ msgid "of"
42
+ msgstr ""
43
+
44
+ #: ui/boxes.php:110
45
+ msgid "Next"
46
+ msgstr ""
47
+
48
+ #: ui/boxes.php:114
49
+ msgid "Recent"
50
+ msgstr ""
51
+
52
+ #: ui/boxes.php:174 ui/boxes.php:175
53
+ msgid "Create connection"
54
+ msgstr ""
55
+
56
+ #: ui/boxes.php:182 ui/boxes.php:183
57
+ msgid "Delete connection"
58
+ msgstr ""
59
+
60
+ #: ui/boxes.php:189 ui/boxes.php:190
61
+ msgid "Delete all connections"
62
+ msgstr ""
63
+
64
+ #: ui/ui.php:40
65
+ #, php-format
66
+ msgid "Connected %s"
67
  msgstr ""
68
 
69
+ #: ui/ui.php:87
70
+ msgid "Are you sure you want to delete all connections?"
71
  msgstr ""
72
 
73
  #. Plugin Name of the plugin/theme
posts-to-posts.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
  /*
3
  Plugin Name: Posts 2 Posts
4
- Version: 0.6
5
  Plugin Author: scribu
6
  Description: Create many-to-many relationships between all types of posts
7
  Author URI: http://scribu.net/
@@ -39,7 +39,6 @@ function _p2p_init() {
39
  P2P_Connections::init( __FILE__ );
40
  P2P_Query::init();
41
  P2P_Connection_Types::init();
42
- P2P_Box_Multiple::init();
43
 
44
  P2P_Migrate::init();
45
  }
1
  <?php
2
  /*
3
  Plugin Name: Posts 2 Posts
4
+ Version: 0.7
5
  Plugin Author: scribu
6
  Description: Create many-to-many relationships between all types of posts
7
  Author URI: http://scribu.net/
39
  P2P_Connections::init( __FILE__ );
40
  P2P_Query::init();
41
  P2P_Connection_Types::init();
 
42
 
43
  P2P_Migrate::init();
44
  }
readme.txt CHANGED
@@ -1,10 +1,10 @@
1
  === Posts 2 Posts ===
2
- Contributors: scribu
3
  Donate link: http://scribu.net/paypal
4
  Tags: cms, custom post types, relationships, many-to-many
5
  Requires at least: 3.0
6
  Tested up to: 3.1
7
- Stable tag: 0.6
8
 
9
  Create connections between posts
10
 
@@ -12,20 +12,15 @@ Create connections between posts
12
 
13
  This plugin allows you to create many-to-many relationships between posts of all types.
14
 
15
- To register a connection type, add this code in your theme's functions.php file:
16
 
17
- `
18
- function my_connection_types() {
19
- if ( !function_exists('p2p_register_connection_type') )
20
- return;
21
 
22
- p2p_register_connection_type( 'post', 'page' );
23
- }
24
- add_action('init', 'my_connection_types', 100);
25
- `
26
- <br>
27
 
28
- Links: [API](http://plugins.trac.wordpress.org/browser/posts-to-posts/trunk/api.php) | [Plugin News](http://scribu.net/wordpress/posts-to-posts) | [Author's Site](http://scribu.net)
29
 
30
  == Installation ==
31
 
@@ -45,10 +40,16 @@ Make sure your host is running PHP 5. The only foolproof way to do this is to ad
45
 
46
  == Screenshots ==
47
 
48
- 1. The metabox on the post editing screen
 
49
 
50
  == Changelog ==
51
 
 
 
 
 
 
52
  = 0.6 =
53
  * added p2p_each_connected()
54
  * fixed p2p_is_connected()
1
  === Posts 2 Posts ===
2
+ Contributors: scribu, ciobi
3
  Donate link: http://scribu.net/paypal
4
  Tags: cms, custom post types, relationships, many-to-many
5
  Requires at least: 3.0
6
  Tested up to: 3.1
7
+ Stable tag: 0.7
8
 
9
  Create connections between posts
10
 
12
 
13
  This plugin allows you to create many-to-many relationships between posts of all types.
14
 
15
+ You can use it to manually create lists of related posts.
16
 
17
+ Or, if you have a 'product' post type, you can make a by-directional connection to a 'review' post type.
 
 
 
18
 
19
+ The possibilities are endless.
20
+
21
+ Head out to the [Wiki](https://github.com/scribu/wp-posts-to-posts/wiki) for tutorials and other resources.
 
 
22
 
23
+ Links: [Plugin News](http://scribu.net/wordpress/posts-to-posts) | [Author's Site](http://scribu.net)
24
 
25
  == Installation ==
26
 
40
 
41
  == Screenshots ==
42
 
43
+ 1. Simple connection metabox
44
+ 2. Advanced connection metabox
45
 
46
  == Changelog ==
47
 
48
+ = 0.7 =
49
+ * improved UI
50
+ * added 'fields', 'context' and 'prevent_duplicates' args to p2p_register_connection_type()
51
+ * [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-7.html)
52
+
53
  = 0.6 =
54
  * added p2p_each_connected()
55
  * fixed p2p_is_connected()
screenshot-1.png CHANGED
Binary file
screenshot-2.png ADDED
Binary file
storage.php CHANGED
@@ -44,20 +44,36 @@ class P2P_Connections {
44
  * @return array( p2p_id => post_id ) if $to is string
45
  * @return array( p2p_id ) if $to is int
46
  */
47
- function get( $from, $to, $data = array(), $_return_p2p_ids = false ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  global $wpdb;
49
 
50
  $fields = "$wpdb->p2p.p2p_id";
51
  $where = '';
52
  $join = '';
53
 
 
54
  switch ( $to ) {
55
  case 'from':
56
- $fields .= $_return_p2p_ids ? '' : ', p2p_to AS post_id';
57
  $where .= $wpdb->prepare( "p2p_from = %d", $from );
58
  break;
59
  case 'to':
60
- $fields .= $_return_p2p_ids ? '' : ', p2p_from AS post_id';
61
  $where .= $wpdb->prepare( "p2p_to = %d", $from );
62
  break;
63
  default:
@@ -91,10 +107,11 @@ class P2P_Connections {
91
  * @param int $from post id
92
  * @param int $to post id
93
  * @param array $data additional data about the connection
 
94
  *
95
  * @return int|bool connection id or False on failure
96
  */
97
- function connect( $from, $to, $data = array() ) {
98
  global $wpdb;
99
 
100
  $from = absint( $from );
@@ -103,11 +120,6 @@ class P2P_Connections {
103
  if ( !$from || !$to )
104
  return false;
105
 
106
- $p2p_ids = self::get( $from, $to, $data, true );
107
-
108
- if ( !empty( $p2p_ids ) )
109
- return $p2p_ids[0];
110
-
111
  $wpdb->insert( $wpdb->p2p, array( 'p2p_from' => $from, 'p2p_to' => $to ), '%d' );
112
 
113
  $p2p_id = $wpdb->insert_id;
@@ -128,7 +140,13 @@ class P2P_Connections {
128
  * @return int Number of connections deleted
129
  */
130
  function disconnect( $from, $to, $data = array() ) {
131
- return self::delete( self::get( $from, $to, $data, true ) );
 
 
 
 
 
 
132
  }
133
 
134
  /**
44
  * @return array( p2p_id => post_id ) if $to is string
45
  * @return array( p2p_id ) if $to is int
46
  */
47
+ function get( $post_id, $direction, $data = array() ) {
48
+ if ( 'any' == $direction ) {
49
+ $from = self::_get( $post_id, 'from', $data );
50
+ $to = self::_get( $post_id, 'to', $data );
51
+
52
+ foreach ( $to as $p2p_id => $post_id ) {
53
+ $from[ $p2p_id ] = $post_id;
54
+ }
55
+
56
+ return $from;
57
+ }
58
+
59
+ return self::_get( $post_id, $direction, $data );
60
+ }
61
+
62
+ private function _get( $from, $to, $data = array() ) {
63
  global $wpdb;
64
 
65
  $fields = "$wpdb->p2p.p2p_id";
66
  $where = '';
67
  $join = '';
68
 
69
+ $_return_p2p_ids = false;
70
  switch ( $to ) {
71
  case 'from':
72
+ $fields .= ', p2p_to AS post_id';
73
  $where .= $wpdb->prepare( "p2p_from = %d", $from );
74
  break;
75
  case 'to':
76
+ $fields .= ', p2p_from AS post_id';
77
  $where .= $wpdb->prepare( "p2p_to = %d", $from );
78
  break;
79
  default:
107
  * @param int $from post id
108
  * @param int $to post id
109
  * @param array $data additional data about the connection
110
+ * @param string $duplicates Duplicate prevention strategy: 'none', 'matching_data', 'strict'
111
  *
112
  * @return int|bool connection id or False on failure
113
  */
114
+ function connect( $from, $to, $data = array(), $duplicates = 'matching_data' ) {
115
  global $wpdb;
116
 
117
  $from = absint( $from );
120
  if ( !$from || !$to )
121
  return false;
122
 
 
 
 
 
 
123
  $wpdb->insert( $wpdb->p2p, array( 'p2p_from' => $from, 'p2p_to' => $to ), '%d' );
124
 
125
  $p2p_id = $wpdb->insert_id;
140
  * @return int Number of connections deleted
141
  */
142
  function disconnect( $from, $to, $data = array() ) {
143
+ $connections = self::get( $from, $to, $data );
144
+
145
+ // We're interested in the p2p_ids
146
+ if ( !(int) $to )
147
+ $connections = array_keys( $connections );
148
+
149
+ return self::delete( $connections );
150
  }
151
 
152
  /**
tests.php CHANGED
@@ -15,12 +15,46 @@ class P2P_Test {
15
  }
16
 
17
  function _init() {
18
- register_post_type('actor', array('label' => 'Actors', 'public' => true));
19
- register_post_type('movie', array('label' => 'Movies', 'public' => true));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
- # p2p_register_connection_type('actor', 'actor', true);
22
- p2p_register_connection_type('actor', 'movie', true);
23
- p2p_register_connection_type('actor', 'actor', true);
 
 
 
 
 
 
 
 
 
 
24
  }
25
 
26
  function setup() {
@@ -134,18 +168,18 @@ class P2P_Test {
134
 
135
  assert( 'array_intersect_assoc($r, $raw) == $r' );
136
 
137
- # // test 'each_*' query vars
138
- # $posts = get_posts( array(
139
- # 'post_type' => 'actor',
140
- # 'post_status' => 'any',
141
- # 'nopaging' => true,
142
- # 'each_connected' => array(
143
- # 'post_type' => 'movie',
144
- # 'post_status' => 'any',
145
- # 'nopaging' => true,
146
- # ),
147
- # 'suppress_filters' => false
148
- # ) );
149
 
150
  # self::walk( $posts );
151
 
15
  }
16
 
17
  function _init() {
18
+ register_post_type('actor', array(
19
+ 'public' => true,
20
+ 'labels' => array(
21
+ 'name' => 'Actors',
22
+ 'singular_name' => 'Actor',
23
+ 'search_items' => 'Search Actors',
24
+ 'not_found' => 'No actors found.'
25
+ ),
26
+ 'has_archive' => 'actors'
27
+ ));
28
+ register_post_type('movie', array(
29
+ 'public' => true,
30
+ 'labels' => array(
31
+ 'name' => 'Movies',
32
+ 'singular_name' => 'Movie',
33
+ 'search_items' => 'Search movies',
34
+ 'not_found' => 'No movies found.',
35
+ )
36
+ ) );
37
+
38
+ p2p_register_connection_type( array(
39
+ 'from' => 'actor',
40
+ 'to' => 'actor',
41
+ 'reciprocal' => true,
42
+ 'title' => array( 'from' => 'Doubles', 'to' => 'Main Actor' )
43
+ ) );
44
 
45
+ p2p_register_connection_type( array(
46
+ 'from' => 'actor',
47
+ 'to' => 'movie',
48
+ 'reciprocal' => true,
49
+ 'fields' => array(
50
+ 'role' => 'Role',
51
+ 'role_type' => 'Role Type'
52
+ ),
53
+ 'prevent_duplicates' => false,
54
+ 'title' => array( 'from' => 'Played In', 'to' => 'Cast' ),
55
+ ) );
56
+
57
+ p2p_register_connection_type('actor', 'post');
58
  }
59
 
60
  function setup() {
168
 
169
  assert( 'array_intersect_assoc($r, $raw) == $r' );
170
 
171
+ // test 'each_*' query vars
172
+ $posts = get_posts( array(
173
+ 'post_type' => 'actor',
174
+ 'post_status' => 'any',
175
+ 'nopaging' => true,
176
+ 'each_connected' => array(
177
+ 'post_type' => 'movie',
178
+ 'post_status' => 'any',
179
+ 'nopaging' => true,
180
+ ),
181
+ 'suppress_filters' => false
182
+ ) );
183
 
184
  # self::walk( $posts );
185
 
ui/boxes.php CHANGED
@@ -2,115 +2,192 @@
2
 
3
  class P2P_Box_Multiple extends P2P_Box {
4
 
5
- protected $meta_keys = array();
 
6
 
7
- function init() {
8
- add_action( 'admin_print_styles-post.php', array( __CLASS__, 'scripts' ) );
9
- add_action( 'admin_print_styles-post-new.php', array( __CLASS__, 'scripts' ) );
 
 
10
  }
11
 
12
- function scripts() {
13
- wp_enqueue_script( 'p2p-admin-js', plugins_url( 'ui.js', __FILE__ ), array( 'jquery' ), '0.4-alpha6', true );
 
14
 
15
- ?>
16
- <style type="text/css">
17
- .p2p_connected {margin: 10px 4px}
18
- .p2p_results {margin: -5px 6px 10px}
19
- .p2p_metabox .waiting {vertical-align: -.4em}
20
- </style>
21
- <?php
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  }
23
 
24
- function save( $post_a, $data ) {
25
- if ( !empty( $data['all'] ) )
26
- p2p_delete_connection( array_diff( $data['all'], (array) @$data['enabled'] ) );
27
 
28
- foreach ( explode( ',', $data[ 'ids' ] ) as $i => $post_b ) {
29
- $meta = array();
30
- foreach ( $this->meta_keys as $meta_key ) {
31
- $meta_value = $data[ $meta_key ][ $i ];
32
 
33
- if ( empty( $meta_value ) )
34
- continue;
35
 
36
- $meta[ $meta_key ] = $meta_value;
37
- }
 
38
 
39
- if ( $this->reversed )
40
- p2p_connect( $post_b, $post_a, $meta );
41
- else
42
- p2p_connect( $post_a, $post_b, $meta );
43
- }
44
  }
45
 
46
  function box( $post_id ) {
47
  $connected_ids = $this->get_connected_ids( $post_id );
48
 
49
- foreach ( array_keys( $connected_ids ) as $p2p_id ) { ?>
50
- <input type="hidden" name="<?php echo $this->input_name( array( 'all', '' ) ); ?>" value="<?php echo $p2p_id; ?>">
51
- <?php } ?>
 
52
 
53
- <div id="p2p-box-<?php echo $this->box_id; ?>" class="p2p_metabox<?php if ( $this->reversed ) echo ' reversed'; ?>">
54
- <div class="hide-if-no-js checkboxes">
55
- <ul class="p2p_connected">
56
- <?php if ( empty( $connected_ids ) ) { ?>
57
- <li class="howto"><?php _e( 'No connections.', 'posts-to-posts' ); ?></li>
58
- <?php } else {
59
- foreach ( $connected_ids as $p2p_id => $post_b ) {
60
- $this->connection_template( $post_b, $p2p_id );
61
- }
62
- } ?>
63
- </ul>
64
-
65
- <?php echo html( 'p class="p2p_search"',
66
- scbForms::input( array(
67
- 'type' => 'text',
68
- 'name' => 'p2p_search_' . $this->to,
69
- 'desc' => __( 'Search', 'posts-to-posts' ) . ':',
70
- 'desc_pos' => 'before',
71
- 'extra' => array( 'autocomplete' => 'off' ),
72
- ) )
73
- . '<img alt="" src="' . admin_url( 'images/wpspin_light.gif' ) . '" class="waiting" style="display: none;">'
74
- ); ?>
75
-
76
- <ul class="p2p_results"></ul>
77
- <p class="howto"><?php _e( 'Start typing the title of a post you want to connect and then click on to connect it.', 'posts-to-posts' ); ?></p>
78
- </div>
79
 
80
- <div class="hide-if-js">
81
- <?php echo scbForms::input( array(
82
- 'type' => 'text',
83
- 'name' => $this->input_name( 'ids' ),
84
- 'value' => '',
85
- 'extra' => array( 'class' => 'p2p_to_connect' ),
86
- ) ); ?>
87
- <p class="howto"><?php _e( 'Enter IDs of posts to connect, separated by commas.', 'posts-to-posts' ); ?></p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  </div>
89
 
90
- <?php // TODO: move to footer, to avoid $_POST polution ?>
91
- <div style="display:none" class="connection-template">
92
- <?php $this->connection_template(); ?>
 
 
93
  </div>
94
  </div>
 
 
95
  <?php
96
  }
97
 
98
- function connection_template( $post_id = 0, $p2p_id = 0 ) {
99
- if ( $post_id ) {
100
- $post_title = get_the_title( $post_id );
101
- } else {
102
- $post_id = '%post_id%';
103
- $post_title = '%post_title%';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  }
105
 
106
- ?>
107
- <li>
108
- <label>
109
- <input type="checkbox" checked="checked" name="<?php echo $this->input_name( array( 'enabled', '' ) ); ?>" value="<?php echo $p2p_id; ?>">
110
- <?php echo $post_title; ?>
111
- </label>
112
- </li>
113
- <?php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  }
115
 
116
  protected function get_connected_ids( $post_id ) {
@@ -132,18 +209,36 @@ class P2P_Box_Multiple extends P2P_Box {
132
  return array_intersect( $connected_posts, $post_ids ); // to preserve p2p_id keys
133
  }
134
 
135
- function get_search_args( $search ) {
136
- return array(
137
- 's' => $search,
138
  'post_type' => $this->to,
139
  'post_status' => 'any',
140
  'posts_per_page' => 5,
141
- 'order' => 'ASC',
142
- 'orderby' => 'title',
143
  'suppress_filters' => false,
144
  'update_post_term_cache' => false,
145
  'update_post_meta_cache' => false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  );
 
 
 
 
 
147
  }
148
  }
149
 
2
 
3
  class P2P_Box_Multiple extends P2P_Box {
4
 
5
+ function setup() {
6
+ $ptype_obj = get_post_type_object( $this->to );
7
 
8
+ $this->columns = array_merge(
9
+ array( 'delete' => $this->clear_connections_link() ),
10
+ array( 'title' => $ptype_obj->labels->singular_name ),
11
+ $this->fields
12
+ );
13
  }
14
 
15
+ function connect() {
16
+ $from = absint( $_POST['from'] );
17
+ $to = absint( $_POST['to'] );
18
 
19
+ if ( !$from || !$to )
20
+ die(-1);
21
+
22
+ $args = array( $from, $to );
23
+
24
+ if ( $this->reversed )
25
+ $args = array_reverse( $args );
26
+
27
+ $p2p_id = false;
28
+ if ( $this->prevent_duplicates ) {
29
+ $p2p_ids = P2P_Connections::get( $args[0], $args[1] );
30
+
31
+ if ( !empty( $p2p_ids ) )
32
+ $p2p_id = $p2p_ids[0];
33
+ }
34
+
35
+ if ( !$p2p_id )
36
+ $p2p_id = P2P_Connections::connect( $args[0], $args[1] );
37
+
38
+ $this->connection_row( $p2p_id, $to );
39
  }
40
 
41
+ function disconnect() {
42
+ $p2p_id = absint( $_POST['p2p_id'] );
 
43
 
44
+ p2p_delete_connection( $p2p_id );
 
 
 
45
 
46
+ die(1);
47
+ }
48
 
49
+ function clear_connections() {
50
+ $post_id = absint( $_POST['post_id'] );
51
+ p2p_disconnect( $post_id, $this->direction );
52
 
53
+ die(1);
 
 
 
 
54
  }
55
 
56
  function box( $post_id ) {
57
  $connected_ids = $this->get_connected_ids( $post_id );
58
 
59
+ $data_attr = array();
60
+ foreach ( array( 'box_id', 'direction', 'prevent_duplicates' ) as $key )
61
+ $data_attr[] = "data-$key='" . $this->$key . "'";
62
+ $data_attr = implode( ' ', $data_attr );
63
 
64
+ ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
+ <div class="p2p-box">
67
+ <table class="p2p-connections" <?php if ( empty( $connected_ids ) ) echo 'style="display:none"'; ?>>
68
+ <thead>
69
+ <tr>
70
+ <?php
71
+ foreach ( $this->columns as $key => $title ) {
72
+ echo html( 'th', array( 'class' => "p2p-col-$key" ), $title );
73
+ }
74
+ ?>
75
+ </tr>
76
+ </thead>
77
+
78
+ <tbody>
79
+ <?php
80
+ foreach ( $connected_ids as $p2p_id => $post_b ) {
81
+ $this->connection_row( $p2p_id, $post_b );
82
+ }
83
+ ?>
84
+ </tbody>
85
+ </table>
86
+
87
+ <div class="p2p-add-new" <?php echo $data_attr; ?>>
88
+ <p><strong><?php _e( 'Create connections:', 'posts-to-posts' ); ?></strong></p>
89
+
90
+ <div class="p2p-search">
91
+ <?php echo html( 'input', array(
92
+ 'type' => 'text',
93
+ 'name' => 'p2p_search_' . $this->to,
94
+ 'autocomplete' => 'off',
95
+ 'placeholder' => get_post_type_object( $this->to )->labels->search_items
96
+ ) ); ?>
97
+ </div>
98
+
99
+ <table class="p2p-results">
100
+ <tbody>
101
+ </tbody>
102
+ </table>
103
+ </div><!--.p2p-add-new-->
104
+ </div><!--.p2p-box-->
105
+
106
+ <div class="p2p-footer">
107
+ <div class="p2p-nav">
108
+ <div class="p2p-prev button" title="<?php _e( 'Previous', 'p2p-textdomain' ); ?>">&lsaquo;</div>
109
+ <div><span class="p2p-current"></span> <? _e( 'of', 'p2p-textdomain' ); ?> <span class="p2p-total"></span></div>
110
+ <div class="p2p-next button" title="<?php _e( 'Next', 'p2p-textdomain' ); ?>">&rsaquo;</div>
111
  </div>
112
 
113
+ <a href="#" class="p2p-recent button" name="p2p-recent">
114
+ <?php _e( 'Recent', 'posts-to-posts' ); ?>
115
+ </a>
116
+ <div class="clear">
117
+ <!-- Clearfix would be better -->
118
  </div>
119
  </div>
120
+
121
+
122
  <?php
123
  }
124
 
125
+ protected function connection_row( $p2p_id, $post_id ) {
126
+ echo '<tr>';
127
+
128
+ foreach ( array_keys( $this->columns ) as $key ) {
129
+ switch ( $key ) {
130
+ case 'title':
131
+ $value = $this->column_title( $post_id );
132
+ break;
133
+
134
+ case 'delete':
135
+ $value = $this->column_delete( $p2p_id );
136
+ break;
137
+
138
+ default:
139
+ $value = html( 'input', array(
140
+ 'type' => 'text',
141
+ 'name' => "p2p_meta[$p2p_id][$key]",
142
+ 'value' => p2p_get_meta( $p2p_id, $key, true )
143
+ ) );
144
+ }
145
+
146
+ echo html( 'td', array( 'class' => "p2p-col-$key" ), $value );
147
+ }
148
+
149
+ echo '</tr>';
150
+ }
151
+
152
+ public function results_row( $post ) {
153
+ echo '<tr>';
154
+
155
+ foreach ( array( 'add', 'title' ) as $key ) {
156
+ $method = "column_$key";
157
+ echo html( 'td', array( 'class' => "p2p-col-$key" ), $this->$method( $post->ID ) );
158
  }
159
 
160
+ echo '</tr>';
161
+ }
162
+
163
+ protected function column_title( $post_id ) {
164
+ return html( 'a', array(
165
+ 'href' => str_replace( '&amp;', '&', get_edit_post_link( $post_id ) ),
166
+ 'title' => get_post_type_object( get_post_type( $post_id ) )->labels->edit_item,
167
+ ), get_post_field( 'post_title', $post_id ) );
168
+ }
169
+
170
+ protected function column_add( $post_id ) {
171
+ return html( 'a', array(
172
+ 'data-post_id' => $post_id,
173
+ 'href' => '#',
174
+ 'title' => __( 'Create connection', 'posts-to-posts' )
175
+ ), __( 'Create connection', 'posts-to-posts' ) );
176
+ }
177
+
178
+ protected function column_delete( $p2p_id ) {
179
+ return html( 'a', array(
180
+ 'data-p2p_id' => $p2p_id,
181
+ 'href' => '#',
182
+ 'title' => __( 'Delete connection', 'posts-to-posts' )
183
+ ), __( 'Delete connection', 'posts-to-posts' ) );
184
+ }
185
+
186
+ protected function clear_connections_link() {
187
+ return html( 'a', array(
188
+ 'href' => '#',
189
+ 'title' => __( 'Delete all connections', 'posts-to-posts' )
190
+ ), __( 'Delete all connections', 'posts-to-posts' ) );
191
  }
192
 
193
  protected function get_connected_ids( $post_id ) {
209
  return array_intersect( $connected_posts, $post_ids ); // to preserve p2p_id keys
210
  }
211
 
212
+ function get_search_args( $args, $post_id ) {
213
+ $args = array_merge( $args, array(
 
214
  'post_type' => $this->to,
215
  'post_status' => 'any',
216
  'posts_per_page' => 5,
 
 
217
  'suppress_filters' => false,
218
  'update_post_term_cache' => false,
219
  'update_post_meta_cache' => false
220
+ ) );
221
+
222
+ if ( $this->prevent_duplicates )
223
+ $args['post__not_in'] = p2p_get_connected( $post_id, $this->direction );
224
+
225
+ return $args;
226
+ }
227
+
228
+ function get_recent_args( $post_id ) {
229
+ $args = array(
230
+ 'numberposts' => 10,
231
+ 'orderby' => 'post_date',
232
+ 'order' => 'DESC',
233
+ 'post_type' => $this->to,
234
+ 'post_status' => 'publish',
235
+ 'suppress_filters' => false //true
236
  );
237
+
238
+ if ( $this->prevent_duplicates )
239
+ $args['post__not_in'] = p2p_get_connected( $post_id, $this->direction );
240
+
241
+ return $args;
242
  }
243
  }
244
 
ui/images/add.png ADDED
Binary file
ui/images/delete.png ADDED
Binary file
ui/ui.css ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* General */
2
+
3
+ .button.inactive, .button.inactive:hover {
4
+ border-color: #bbb;
5
+ color: #aaa;
6
+ background: #f6f6f6;
7
+ text-shadow: none;
8
+ cursor: default;
9
+ }
10
+
11
+ .p2p-footer {
12
+ background-color: #f1f1f1;
13
+ border-top: 1px solid #ddd;
14
+ clear: both;
15
+ padding: 7px;
16
+ margin: 0 -6px -8px;
17
+ }
18
+
19
+ .p2p-footer .p2p-recent {
20
+ float: right;
21
+ }
22
+
23
+ .p2p-placeholder {
24
+ color: #AAA;
25
+ }
26
+
27
+ .p2p-connections,
28
+ .p2p-search,
29
+ .p2p-results {
30
+ margin-bottom: 8px;
31
+ }
32
+
33
+ /* Connections */
34
+
35
+ .p2p-results,
36
+ .p2p-connections {
37
+ background-color: #F9F9F9;
38
+ border-color: #DFDFDF;
39
+ border-radius: 3px;
40
+ border-spacing: 0;
41
+ border-style: solid;
42
+ border-width: 1px;
43
+ }
44
+
45
+ .p2p-results {
46
+ display: none;
47
+ }
48
+
49
+ .p2p-results td,
50
+ .p2p-connections td {
51
+ border-top: 1px solid #DFDFDF;
52
+ }
53
+
54
+ .p2p-connections th,
55
+ .p2p-connections td,
56
+ .p2p-results td {
57
+ padding-left: 6px;
58
+ padding-right: 6px;
59
+ }
60
+
61
+ th.p2p-col-delete,
62
+ td.p2p-col-delete,
63
+ td.p2p-col-add {
64
+ padding: 0;
65
+ }
66
+
67
+ .p2p-col-delete a,
68
+ .p2p-col-add a,
69
+ .p2p-col-delete img,
70
+ .p2p-col-add img {
71
+ padding: 5px 6px;
72
+ }
73
+
74
+ th.p2p-col-delete a {
75
+ padding-bottom: 8px;
76
+ }
77
+
78
+ .p2p-results tr:first-child td {
79
+ border-top: 0;
80
+ }
81
+
82
+ .p2p-connections {
83
+ width: 100%;
84
+ }
85
+
86
+ .p2p-connections th {
87
+ background-color: #F1F1F1;
88
+ text-align: left;
89
+ }
90
+
91
+ .p2p-connections td input {
92
+ width: 100%;
93
+ }
94
+
95
+ .p2p-col-add,
96
+ .p2p-col-delete {
97
+ width: 16px;
98
+ }
99
+
100
+ .p2p-col-add a,
101
+ .p2p-col-delete a {
102
+ display: block;
103
+ height: 16px;
104
+ margin-top: 1px;
105
+ opacity: 0.5;
106
+ text-indent: -9999px;
107
+ width: 16px;
108
+ }
109
+
110
+ .p2p-col-add a:hover,
111
+ .p2p-col-delete a:hover {
112
+ background-color: #f5f5f5;
113
+ border-right: 1px solid #ddd;
114
+ opacity: 1;
115
+ width: 15px;
116
+ }
117
+
118
+ .p2p-col-add a {
119
+ background: url("images/add.png") no-repeat scroll 50% 50% transparent;
120
+ }
121
+
122
+ .p2p-col-delete a {
123
+ background: url("images/delete.png") no-repeat scroll 50% 50% transparent;
124
+ }
125
+
126
+ #poststuff .p2p-box .p2p-add-new p {
127
+ margin-left: 5px;
128
+ }
129
+
130
+ .p2p-search .waiting {
131
+ margin-left: -20px;
132
+ opacity: 0.8;
133
+ vertical-align: -0.3em;
134
+ }
135
+
136
+ .p2p-search input {
137
+ background-color: transparent;
138
+ width: 150px;
139
+ padding-right: 22px;
140
+ margin: 0;
141
+ }
142
+
143
+ .p2p-notice {
144
+ margin-left: 5px;
145
+ }
146
+
147
+ #side-sortables .p2p-notice {
148
+ display: block;
149
+ margin-top: 10px;
150
+ }
151
+
152
+ .p2p-footer .waiting {
153
+ float: right;
154
+ margin: 3px 6px 0;
155
+ }
156
+
157
+ .p2p-nav {
158
+ display: none;
159
+ }
160
+
161
+ .p2p-nav > * {
162
+ float: left;
163
+ line-height: 13px;
164
+ padding-top: 3px;
165
+ padding-bottom: 3px;
166
+ margin-right: 5px;
167
+ }
168
+
ui/ui.js CHANGED
@@ -1,73 +1,254 @@
1
  jQuery(document).ready(function($) {
2
- $('.p2p_results').delegate('a', 'click', function() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  var $self = $(this),
4
- $metabox = $self.parents('.p2p_metabox'),
5
- $list = $metabox.find('.p2p_connected'),
6
- post_id = $self.attr('name');
 
 
 
7
 
8
- $list.append(
9
- $metabox.find('.connection-template').html()
10
- .replace( '%post_id%', post_id )
11
- .replace( '%post_title%', $self.html() )
12
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- $metabox.find('.p2p_connected .howto').remove();
15
 
16
- var $connected = $metabox.find('.p2p_to_connect');
 
17
 
18
- $connected.val( $connected.val() + post_id + ',' );
 
 
19
 
20
  return false;
21
  });
22
 
23
- $('.p2p_search :text').keypress(function (ev) {
24
- if ( 13 === ev.keyCode )
25
- return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  });
27
 
28
- var delayed, old_value = '';
 
 
29
 
30
- $('.p2p_search :text').keyup(function (ev) {
 
31
 
32
- if ( undefined !== delayed ) {
33
- clearTimeout(delayed);
 
 
 
 
 
 
 
 
 
 
34
  }
 
35
 
36
- var $self = $(this),
37
- $metabox = $self.parents('.p2p_metabox'),
38
- $results = $metabox.find('.p2p_results'),
39
- $spinner = $metabox.find('.waiting');
40
-
41
- delayed = setTimeout(function() {
42
- if ( !$self.val().length ) {
43
- $results.html('');
44
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
- if ( $self.val() === old_value ) {
48
- return;
 
 
 
 
 
 
 
 
 
 
49
  }
50
- old_value = $self.val();
51
 
52
- $spinner.show();
53
-
54
- var data = {
55
- action: 'p2p_search',
56
- q: $self.val(),
57
- box_id: $metabox.attr('id').replace('p2p-box-', ''),
58
- reversed: +$metabox.hasClass('reversed')
59
- };
60
 
61
- $.getJSON(ajaxurl, data, function(data) {
62
- $spinner.hide();
63
 
64
- $results.html('');
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
- $.each(data, function(id, title) {
67
- $results.append('<li><a href="#" name="' + id + '">' + title + '</a></li>');
68
- });
69
- });
70
- }, 400);
 
 
 
 
 
 
71
  });
72
  });
 
73
 
1
  jQuery(document).ready(function($) {
2
+
3
+ // Placeholder support for IE
4
+ if (!jQuery('<input placeholder="1" />')[0].placeholder) {
5
+ jQuery('.p2p-search input[placeholder]').each(function(){
6
+ var $this = $(this);
7
+ if (!$this.val()) {
8
+ $this.val($this.attr('placeholder'));
9
+ $this.addClass('p2p-placeholder');
10
+ }
11
+ }).focus(function(e){
12
+ var $this = $(this);
13
+ if ($this.hasClass('p2p-placeholder')) {
14
+ $this.val('');
15
+ $this.removeClass('p2p-placeholder');
16
+ }
17
+ }).blur(function(e){
18
+ var $this = $(this);
19
+ if (!$this.val()) {
20
+ $this.addClass('p2p-placeholder');
21
+ $this.val($this.attr('placeholder'));
22
+ }
23
+ });
24
+ }
25
+
26
+ $('.p2p-add-new').each(function() {
27
+ var $metabox = $(this).closest('.inside'),
28
+ $connections = $metabox.find('.p2p-connections'),
29
+ $results = $metabox.find('.p2p-results'),
30
+ $searchInput = $metabox.find('.p2p-search :text'),
31
+ $pagination = $metabox.find('.p2p-nav'),
32
+ $addNew = $metabox.find('.p2p-add-new'),
33
+ base_data = {
34
+ box_id: $addNew.attr('data-box_id'),
35
+ direction: $addNew.attr('data-direction')
36
+ };
37
+
38
+ // Save the wp-spinner
39
+ var $spinner = $('#publishing-action .ajax-loading')
40
+ .clone()
41
+ .removeAttr('id')
42
+ .removeClass('ajax-loading')
43
+ .addClass('waiting');
44
+
45
+ // Delete all connections
46
+ $metabox.delegate('th.p2p-col-delete a', 'click', function() {
47
+ if ( !confirm(P2PAdmin_I18n.deleteConfirmMessage) )
48
+ return false;
49
+
50
  var $self = $(this),
51
+ $td = $self.closest('td'),
52
+ data = $.extend( base_data, {
53
+ action: 'p2p_connections',
54
+ subaction: 'clear_connections',
55
+ post_id: $('#post_ID').val()
56
+ } );
57
 
58
+ $td.html( $spinner.show() );
59
+
60
+ $.post(ajaxurl, data, function(response) {
61
+ $connections.hide()
62
+ .find('tbody').html('');
63
+
64
+ $td.html( $self );
65
+ });
66
+
67
+ return false;
68
+ });
69
+
70
+ // Delete connection
71
+ $metabox.delegate('td.p2p-col-delete a', 'click', function() {
72
+ var $self = $(this),
73
+ $td = $self.closest('td'),
74
+ data = $.extend( base_data, {
75
+ action: 'p2p_connections',
76
+ subaction: 'disconnect',
77
+ p2p_id: $self.attr('data-p2p_id')
78
+ } );
79
 
80
+ $td.html( $spinner.show() );
81
 
82
+ $.post(ajaxurl, data, function(response) {
83
+ $td.closest('tr').remove();
84
 
85
+ if ( !$connections.find('tbody tr').length )
86
+ $connections.hide();
87
+ });
88
 
89
  return false;
90
  });
91
 
92
+ // Create connection
93
+ $metabox.delegate('td.p2p-col-add a', 'click', function() {
94
+ var $self = $(this),
95
+ $td = $self.closest('td'),
96
+ data = $.extend( base_data, {
97
+ action: 'p2p_connections',
98
+ subaction: 'connect',
99
+ from: $('#post_ID').val(),
100
+ to: $self.attr('data-post_id')
101
+ } );
102
+
103
+ $td.html( $spinner.show() );
104
+
105
+ $.post(ajaxurl, data, function(response) {
106
+ $connections.show()
107
+ .find('tbody').append(response);
108
+
109
+ if ( $addNew.attr('data-prevent_duplicates') ) {
110
+ $td.closest('tr').remove();
111
+
112
+ if ( !$results.find('tbody tr').length )
113
+ $results.hide();
114
+ } else {
115
+ $td.html( $self );
116
+ }
117
+ });
118
+
119
+ return false;
120
  });
121
 
122
+ // Pagination
123
+ var current_page = 1,
124
+ total_pages = 0;
125
 
126
+ function find_posts(new_page, action, callback) {
127
+ new_page = new_page ? ( new_page > total_pages ? current_page : new_page ) : current_page;
128
 
129
+ var data = $.extend( base_data, {
130
+ action: 'p2p_search',
131
+ s: $searchInput.val(),
132
+ paged: new_page,
133
+ post_id: $('#post_ID').val()
134
+ } );
135
+
136
+ // show spinner
137
+ if ( 'search' === action ) {
138
+ $spinner.insertAfter( $searchInput );
139
+ } else {
140
+ $spinner.insertAfter( $metabox.find('.p2p-recent') );
141
  }
142
+ $spinner.show();
143
 
144
+ $.getJSON(ajaxurl, data, function(response) {
145
+ $spinner.remove();
146
+
147
+ current_page = new_page;
148
+
149
+ $metabox.find('.p2p-search').find('.p2p-notice').remove();
150
+
151
+ $pagination.hide();
152
+
153
+ if ( 'undefined' === typeof response.rows ) {
154
+ $metabox.find('.p2p-search').append('<p class="p2p-notice">' + response.msg + '</p>');
155
+ $results.hide()
156
+ .find('tbody').html('');
157
+ } else {
158
+ $results.show()
159
+ .find('tbody').html(response.rows);
160
+
161
+ total_pages = response.pages;
162
+
163
+ // update pagination
164
+ if ( total_pages > 1 ) {
165
+ $pagination.show();
166
+ }
167
+
168
+ if ( 1 === current_page ) {
169
+ $metabox.find('.p2p-prev').addClass('inactive');
170
+ } else {
171
+ $metabox.find('.p2p-prev').removeClass('inactive');
172
+ }
173
+
174
+ if ( total_pages === current_page ) {
175
+ $metabox.find('.p2p-next').addClass('inactive');
176
+ } else {
177
+ $metabox.find('.p2p-next').removeClass('inactive');
178
+ }
179
+
180
+ $metabox.find('.p2p-current').html(current_page);
181
+ $metabox.find('.p2p-total').html(total_pages);
182
+
183
+ if ( undefined !== callback )
184
+ callback();
185
  }
186
+ });
187
+ }
188
+
189
+ // Delegate recent
190
+ $metabox.delegate('.p2p-recent', 'click', function() {
191
+ var $button = $(this);
192
+
193
+ if ( $button.hasClass('inactive') )
194
+ return false;
195
+
196
+ $metabox.find('.p2p-search :text')
197
+ .val('')
198
+ .blur(); // so that placeholder is shown again in IE
199
+
200
+ $button.addClass('inactive');
201
+ find_posts(1, 'recent', function() {
202
+ $button.removeClass('inactive');
203
+ });
204
+
205
+ return false;
206
+ });
207
 
208
+ // Search posts
209
+ var delayed, old_value = '';
210
+
211
+ $metabox.find('.p2p-search :text')
212
+ .keypress(function (ev) {
213
+ if ( 13 === ev.keyCode )
214
+ return false;
215
+ })
216
+
217
+ .keyup(function (ev) {
218
+ if ( undefined !== delayed ) {
219
+ clearTimeout(delayed);
220
  }
 
221
 
222
+ var $self = $(this);
 
 
 
 
 
 
 
223
 
224
+ delayed = setTimeout(function() {
 
225
 
226
+ if ( $self.val() === old_value ) {
227
+ return;
228
+ }
229
+ old_value = $self.val();
230
+
231
+ find_posts(1, 'search');
232
+ }, 400);
233
+ });
234
+
235
+ // Pagination
236
+ $metabox.delegate('.p2p-prev, .p2p-next', 'click', function() {
237
+ var $self = $(this),
238
+ new_page = current_page;
239
 
240
+ if ( $self.hasClass('inactive') )
241
+ return false;
242
+
243
+ if ( $self.hasClass('p2p-prev') )
244
+ new_page--;
245
+ else
246
+ new_page++;
247
+
248
+ find_posts(new_page, 'paginate');
249
+
250
+ return false;
251
  });
252
  });
253
+ });
254
 
ui/ui.php CHANGED
@@ -1,19 +1,17 @@
1
  <?php
2
 
3
  abstract class P2P_Box {
 
 
4
 
5
  protected $reversed;
6
  protected $direction;
7
 
8
  protected $box_id;
9
- private $input;
10
 
11
- abstract function save( $post_id, $data );
12
  abstract function box( $post_id );
13
 
14
- protected function input_name( $name ) {
15
- return $this->input->get_name( $name );
16
- }
17
 
18
 
19
  // Internal stuff
@@ -25,162 +23,180 @@ abstract class P2P_Box {
25
 
26
  $this->box_id = $box_id;
27
 
28
- $this->input = new p2pInput( array( 'p2p', $box_id ) );
29
-
30
  $this->direction = $direction;
31
 
32
  $this->reversed = ( 'to' == $direction );
33
 
34
  if ( $this->reversed )
35
  list( $this->to, $this->from ) = array( $this->from, $this->to );
 
 
36
  }
37
 
38
  function _register( $from ) {
39
- $title = $this->title;
 
 
 
 
 
 
 
 
 
40
 
41
- if ( empty( $title ) )
42
- $title = get_post_type_object( $this->to )->labels->name;
 
43
 
44
  add_meta_box(
45
  'p2p-connections-' . $this->box_id,
46
  $title,
47
  array( $this, '_box' ),
48
  $from,
49
- 'side',
50
  'default'
51
  );
52
  }
53
 
54
- function _save( $post_id ) {
55
- $data = $this->input->extract( $_POST );
56
-
57
- if ( is_null( $data ) )
58
- return;
59
-
60
- $this->save( $post_id, $data );
61
- }
62
-
63
  function _box( $post ) {
64
  $this->box( $post->ID );
65
  }
66
  }
67
 
68
 
69
- class p2pInput {
70
-
71
- private $prefix;
72
-
73
- function __construct( $prefix = array() ) {
74
- $this->prefix = $prefix;
75
- }
76
-
77
- function get_name( $suffix ) {
78
- $name_a = array_merge( $this->prefix, (array) $suffix );
79
-
80
- $name = array_shift( $name_a );
81
- foreach ( $name_a as $key )
82
- $name .= '[' . esc_attr( $key ) . ']';
83
-
84
- return $name;
85
- }
86
-
87
- function extract( $value, $suffix = array() ) {
88
- $name_a = array_merge( $this->prefix, (array) $suffix );
89
-
90
- foreach ( $name_a as $key ) {
91
- if ( !isset( $value[ $key ] ) )
92
- return null;
93
-
94
- $value = $value[$key];
95
- }
96
-
97
- return $value;
98
- }
99
- }
100
-
101
-
102
  class P2P_Connection_Types {
103
 
104
  private static $ctypes = array();
105
 
106
  static public function register( $args ) {
107
- $args = wp_parse_args( $args, array(
108
- 'from' => '',
109
- 'to' => '',
110
- 'box' => 'P2P_Box_Multiple',
111
- 'title' => '',
112
- 'reciprocal' => false
113
- ) );
114
-
115
  self::$ctypes[] = $args;
116
  }
117
 
118
  static function init() {
119
  add_action( 'add_meta_boxes', array( __CLASS__, '_register' ) );
120
- add_action( 'save_post', array( __CLASS__, '_save' ), 10 );
 
121
  add_action( 'wp_ajax_p2p_search', array( __CLASS__, 'ajax_search' ) );
 
122
  }
123
 
124
  static function _register( $from ) {
125
- foreach ( self::filter_ctypes( $from ) as $ctype ) {
 
 
 
 
 
126
  $ctype->_register( $from );
127
  }
 
 
 
 
 
 
128
  }
129
 
130
- static function _save( $post_id ) {
131
- $from = get_post_type( $post_id );
132
- if ( defined( 'DOING_AJAX' ) || defined( 'DOING_CRON' ) || empty( $_POST ) || 'revision' == $from )
133
  return;
134
 
135
- foreach ( self::filter_ctypes( $from ) as $ctype ) {
136
- $ctype->_save( $post_id );
 
 
137
  }
138
  }
139
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  function ajax_search() {
141
- add_filter( 'posts_search', array( __CLASS__, '_search_by_title' ) );
142
 
143
- $box_id = absint( $_GET['box_id'] );
144
- $reversed = (bool) $_GET['reversed'];
145
 
146
- if ( !isset( self::$ctypes[ $box_id ] ) )
147
- die(0);
 
 
148
 
149
- $args = self::$ctypes[ $box_id ];
150
- $box = new $args['box']($args, $reversed, $box_id);
151
 
152
- $posts = get_posts( $box->get_search_args( $_GET['q'] ) );
 
 
 
 
 
 
 
 
153
 
154
- $results = array();
155
- foreach ( $posts as $post ) {
156
- $GLOBALS['post'] = $post;
157
- $results[ $post->ID ] = apply_filters( 'the_title', $post->post_title );
158
  }
159
 
160
- die( json_encode( $results ) );
 
 
161
  }
162
 
163
- function _search_by_title( $sql ) {
164
  remove_filter( current_filter(), array( __CLASS__, __FUNCTION__ ) );
 
 
 
 
 
 
 
 
165
 
166
- list( $sql ) = explode( ' OR ', $sql, 2 );
 
 
167
 
168
- return $sql . '))';
 
 
 
 
 
169
  }
170
 
171
  private static function filter_ctypes( $post_type ) {
172
  $r = array();
 
173
  foreach ( self::$ctypes as $box_id => $args ) {
174
  $direction = false;
175
 
176
- if ( $args['reciprocal'] && $args['from'] == $args['to'] ) {
177
- $direction = 'any';
178
  } elseif ( $args['reciprocal'] && $post_type == $args['to'] ) {
179
  $direction = 'to';
180
  } elseif ( $post_type == $args['from'] ) {
181
  $direction = 'from';
182
  } else {
183
- continue;
184
  }
185
 
186
  if ( !$direction )
1
  <?php
2
 
3
  abstract class P2P_Box {
4
+ public $from;
5
+ public $to;
6
 
7
  protected $reversed;
8
  protected $direction;
9
 
10
  protected $box_id;
 
11
 
 
12
  abstract function box( $post_id );
13
 
14
+ function setup() {}
 
 
15
 
16
 
17
  // Internal stuff
23
 
24
  $this->box_id = $box_id;
25
 
 
 
26
  $this->direction = $direction;
27
 
28
  $this->reversed = ( 'to' == $direction );
29
 
30
  if ( $this->reversed )
31
  list( $this->to, $this->from ) = array( $this->from, $this->to );
32
+
33
+ $this->setup();
34
  }
35
 
36
  function _register( $from ) {
37
+ if ( is_array( $this->title ) ) {
38
+ $key = $this->reversed ? 'to' : 'from';
39
+
40
+ if ( isset( $this->title[ $key ] ) )
41
+ $title = $this->title[ $key ];
42
+ else
43
+ $title = '';
44
+ } else {
45
+ $title = $this->title;
46
+ }
47
 
48
+ if ( empty( $title ) ) {
49
+ $title = sprintf( __( 'Connected %s', 'posts-to-posts' ), get_post_type_object( $this->to )->labels->name );
50
+ }
51
 
52
  add_meta_box(
53
  'p2p-connections-' . $this->box_id,
54
  $title,
55
  array( $this, '_box' ),
56
  $from,
57
+ $this->context,
58
  'default'
59
  );
60
  }
61
 
 
 
 
 
 
 
 
 
 
62
  function _box( $post ) {
63
  $this->box( $post->ID );
64
  }
65
  }
66
 
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  class P2P_Connection_Types {
69
 
70
  private static $ctypes = array();
71
 
72
  static public function register( $args ) {
 
 
 
 
 
 
 
 
73
  self::$ctypes[] = $args;
74
  }
75
 
76
  static function init() {
77
  add_action( 'add_meta_boxes', array( __CLASS__, '_register' ) );
78
+
79
+ add_action( 'save_post', array( __CLASS__, 'save' ), 10, 2 );
80
  add_action( 'wp_ajax_p2p_search', array( __CLASS__, 'ajax_search' ) );
81
+ add_action( 'wp_ajax_p2p_connections', array( __CLASS__, 'ajax_connections' ) );
82
  }
83
 
84
  static function _register( $from ) {
85
+ $filtered = self::filter_ctypes( $from );
86
+
87
+ if ( empty( $filtered ) )
88
+ return;
89
+
90
+ foreach ( $filtered as $ctype ) {
91
  $ctype->_register( $from );
92
  }
93
+
94
+ wp_enqueue_style( 'p2p-admin', plugins_url( 'ui.css', __FILE__ ), array(), '0.7-beta' );
95
+ wp_enqueue_script( 'p2p-admin', plugins_url( 'ui.js', __FILE__ ), array( 'jquery' ), '0.7-beta', true );
96
+ wp_localize_script( 'p2p-admin', 'P2PAdmin_I18n', array(
97
+ 'deleteConfirmMessage' => __( 'Are you sure you want to delete all connections?', 'posts-to-posts' ),
98
+ ) );
99
  }
100
 
101
+ function save( $post_id, $post ) {
102
+ if ( 'revision' == $post->post_type || !isset( $_POST['p2p_meta'] ) )
 
103
  return;
104
 
105
+ foreach ( $_POST['p2p_meta'] as $p2p_id => $data ) {
106
+ foreach ( $data as $key => $value ) {
107
+ p2p_update_meta( $p2p_id, $key, $value );
108
+ }
109
  }
110
  }
111
 
112
+ function ajax_connections() {
113
+ $box = self::ajax_make_box();
114
+
115
+ $ptype_obj = get_post_type_object( $box->from );
116
+ if ( !current_user_can( $ptype_obj->cap->edit_posts ) )
117
+ die(-1);
118
+
119
+ $subaction = $_POST['subaction'];
120
+
121
+ $box->$subaction();
122
+ }
123
+
124
+ function ajax_disconnect() {
125
+ $box = self::ajax_make_box();
126
+
127
+ $box->disconnect();
128
+ }
129
+
130
  function ajax_search() {
131
+ add_filter( 'posts_search', array( __CLASS__, '_search_by_title' ), 10, 2 );
132
 
133
+ $box = self::ajax_make_box();
 
134
 
135
+ $args = array(
136
+ 's' => $_GET['s'],
137
+ 'paged' => $_GET['paged']
138
+ );
139
 
140
+ $query = new WP_Query( $box->get_search_args( $args, $_GET['post_id'] ) );
 
141
 
142
+ if ( !$query->have_posts() ) {
143
+ $results = array(
144
+ 'msg' => get_post_type_object( $box->to )->labels->not_found,
145
+ );
146
+ } else {
147
+ ob_start();
148
+ foreach ( $query->posts as $post ) {
149
+ $box->results_row( $post );
150
+ }
151
 
152
+ $results = array(
153
+ 'rows' => ob_get_clean(),
154
+ 'pages' => $query->max_num_pages
155
+ );
156
  }
157
 
158
+ echo json_encode( $results );
159
+
160
+ die;
161
  }
162
 
163
+ function _search_by_title( $sql, $wp_query ) {
164
  remove_filter( current_filter(), array( __CLASS__, __FUNCTION__ ) );
165
+
166
+ if ( $wp_query->is_search ) {
167
+ list( $sql ) = explode( ' OR ', $sql, 2 );
168
+ return $sql . '))';
169
+ }
170
+
171
+ return $sql;
172
+ }
173
 
174
+ private static function ajax_make_box() {
175
+ $box_id = absint( $_REQUEST['box_id'] );
176
+ $direction = $_REQUEST['direction'];
177
 
178
+ if ( !isset( self::$ctypes[ $box_id ] ) )
179
+ die(0);
180
+
181
+ $args = self::$ctypes[ $box_id ];
182
+
183
+ return new $args['box']($args, $direction, $box_id);
184
  }
185
 
186
  private static function filter_ctypes( $post_type ) {
187
  $r = array();
188
+
189
  foreach ( self::$ctypes as $box_id => $args ) {
190
  $direction = false;
191
 
192
+ if ( $args['reciprocal'] && $post_type == $args['from'] && $args['from'] == $args['to'] ) {
193
+ $direction = 'any';
194
  } elseif ( $args['reciprocal'] && $post_type == $args['to'] ) {
195
  $direction = 'to';
196
  } elseif ( $post_type == $args['from'] ) {
197
  $direction = 'from';
198
  } else {
199
+ continue;
200
  }
201
 
202
  if ( !$direction )