Posts 2 Posts - Version 1.4

Version Description

  • added 'p2p_init' hook
  • replaced 'View All' button with '+ Create connections' toggle
  • improved usability of connection candidate UI
  • fixed issues related to auto-drafts
  • show columns on the admin user list screen
Download this release

Release Info

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

Code changes from version 1.3.1 to 1.4

admin/box-factory.php CHANGED
@@ -2,19 +2,17 @@
2
 
3
  define( 'P2P_BOX_NONCE', 'p2p-box' );
4
 
5
- class P2P_Box_Factory {
6
 
7
- private static $box_args = array();
 
8
 
9
- static function init() {
10
- add_filter( 'p2p_connection_type_args', array( __CLASS__, 'filter_args' ) );
11
-
12
- add_action( 'add_meta_boxes', array( __CLASS__, 'add_meta_boxes' ) );
13
- add_action( 'save_post', array( __CLASS__, 'save_post' ), 10, 2 );
14
- add_action( 'wp_ajax_p2p_box', array( __CLASS__, 'wp_ajax_p2p_box' ) );
15
  }
16
 
17
- static function filter_args( $args ) {
18
  if ( isset( $args['admin_box'] ) ) {
19
  $box_args = _p2p_pluck( $args, 'admin_box' );
20
  if ( !is_array( $box_args ) )
@@ -29,92 +27,79 @@ class P2P_Box_Factory {
29
  }
30
  }
31
 
32
- self::register( $args['name'], $box_args );
33
-
34
- return $args;
35
- }
36
-
37
- static function register( $p2p_type, $box_args ) {
38
- if ( isset( self::$box_args[$p2p_type] ) )
39
- return false;
40
-
41
- $box_args = (object) wp_parse_args( $box_args, array(
42
  'show' => 'any',
43
  'context' => 'side',
 
44
  'can_create_post' => true
45
  ) );
46
 
47
- if ( !$box_args->show )
48
- return false;
49
 
50
- self::$box_args[$p2p_type] = $box_args;
 
51
 
52
- return true;
 
53
  }
54
 
55
- static function add_meta_boxes( $post_type ) {
56
- foreach ( self::$box_args as $p2p_type => $box_args ) {
57
- $ctype = p2p_type( $p2p_type );
58
 
59
- $dir = self::get_visible_directions( $post_type, $ctype, $box_args->show );
 
60
 
61
- $title = $ctype->title;
 
 
 
 
 
 
 
62
 
63
- if ( count( $dir ) > 1 && $title['from'] == $title['to'] ) {
64
- $title['from'] .= __( ' (from)', P2P_TEXTDOMAIN );
65
- $title['to'] .= __( ' (to)', P2P_TEXTDOMAIN );
66
- }
67
 
68
- foreach ( $dir as $direction ) {
69
- $key = ( 'to' == $direction ) ? 'to' : 'from';
70
 
71
- $directed = $ctype->set_direction( $direction );
 
72
 
73
- $box = new P2P_Box( $box_args, $directed );
 
74
 
75
- if ( !$box->check_capability() )
76
- continue;
77
 
78
- add_meta_box(
79
- "p2p-{$direction}-{$ctype->name}",
80
- $title[$key],
81
- array( $box, 'render' ),
82
- $post_type,
83
- $box_args->context,
84
- 'default'
85
- );
86
 
87
- $box->init_scripts();
88
- }
89
  }
90
- }
91
-
92
- private static function get_visible_directions( $post_type, $ctype, $show_ui ) {
93
- $direction = $ctype->find_direction( $post_type, false );
94
- if ( !$direction )
95
- return array();
96
 
97
- if ( $ctype->indeterminate && !$ctype->reciprocal ) {
98
- return _p2p_expand_direction( $show_ui );
99
  }
100
 
101
- if ( 'any' == $show_ui || $direction == $show_ui )
102
- return array( $direction );
103
-
104
- return array();
105
  }
106
 
107
  /**
108
  * Collect metadata from all boxes.
109
  */
110
- static function save_post( $post_id, $post ) {
111
  if ( 'revision' == $post->post_type || defined( 'DOING_AJAX' ) )
112
  return;
113
 
114
  if ( isset( $_POST['p2p_connections'] ) ) {
115
  // Loop through the hidden fields instead of through $_POST['p2p_meta'] because empty checkboxes send no data.
116
  foreach ( $_POST['p2p_connections'] as $p2p_id ) {
117
- $data = scbForms::get_value( array( 'p2p_meta', $p2p_id ), $_POST, array() );
118
 
119
  $connection = p2p_get_connection( $p2p_id );
120
 
@@ -126,7 +111,7 @@ class P2P_Box_Factory {
126
 
127
  $data = scbForms::validate_post_data( $fields, $data );
128
 
129
- scbForms::update_meta( $fields, $data, $p2p_id, 'p2p' );
130
  }
131
  }
132
 
@@ -140,34 +125,41 @@ class P2P_Box_Factory {
140
  }
141
  }
142
 
 
 
 
 
 
 
 
143
  /**
144
  * Controller for all box ajax requests.
145
  */
146
- static function wp_ajax_p2p_box() {
147
  check_ajax_referer( P2P_BOX_NONCE, 'nonce' );
148
 
149
  $ctype = p2p_type( $_REQUEST['p2p_type'] );
150
- if ( !$ctype || !isset( self::$box_args[$ctype->name] ) )
151
- die(0);
152
-
153
- $post_type = get_post_type( $_REQUEST['from'] );
154
- if ( !$post_type )
155
  die(0);
156
 
157
  $directed = $ctype->set_direction( $_REQUEST['direction'] );
158
  if ( !$directed )
159
  die(0);
160
 
161
- $box = new P2P_Box( self::$box_args[$ctype->name], $directed, $post_type );
 
 
162
 
163
- if ( !$box->check_capability() )
164
  die(-1);
165
 
 
 
166
  $method = 'ajax_' . $_REQUEST['subaction'];
167
 
168
  $box->$method();
169
  }
170
  }
171
 
172
- P2P_Box_Factory::init();
173
 
2
 
3
  define( 'P2P_BOX_NONCE', 'p2p-box' );
4
 
5
+ class P2P_Box_Factory extends P2P_Factory {
6
 
7
+ function __construct() {
8
+ add_filter( 'p2p_connection_type_args', array( $this, 'filter_ctypes' ) );
9
 
10
+ add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
11
+ add_action( 'save_post', array( $this, 'save_post' ), 10, 2 );
12
+ add_action( 'wp_ajax_p2p_box', array( $this, 'wp_ajax_p2p_box' ) );
 
 
 
13
  }
14
 
15
+ function filter_ctypes( $args ) {
16
  if ( isset( $args['admin_box'] ) ) {
17
  $box_args = _p2p_pluck( $args, 'admin_box' );
18
  if ( !is_array( $box_args ) )
27
  }
28
  }
29
 
30
+ $box_args = wp_parse_args( $box_args, array(
 
 
 
 
 
 
 
 
 
31
  'show' => 'any',
32
  'context' => 'side',
33
+ 'priority' => 'default',
34
  'can_create_post' => true
35
  ) );
36
 
37
+ $this->register( $args['name'], $box_args );
 
38
 
39
+ return $args;
40
+ }
41
 
42
+ function add_meta_boxes( $post_type ) {
43
+ $this->filter( 'post', $post_type );
44
  }
45
 
46
+ function add_item( $directed, $object_type, $post_type, $title ) {
47
+ if ( !self::show_box( $directed, $GLOBALS['post'] ) )
48
+ return;
49
 
50
+ $box = $this->create_box( $directed );
51
+ $box_args = $this->queue[ $directed->name ];
52
 
53
+ add_meta_box(
54
+ sprintf( 'p2p-%s-%s', $directed->get_direction(), $directed->name ),
55
+ $title,
56
+ array( $box, 'render' ),
57
+ $post_type,
58
+ $box_args->context,
59
+ $box_args->priority
60
+ );
61
 
62
+ $box->init_scripts();
63
+ }
 
 
64
 
65
+ private static function show_box( $directed, $post ) {
66
+ $show = $directed->get_opposite( 'side' )->can_edit_connections();
67
 
68
+ return apply_filters( 'p2p_admin_box_show', $show, $directed, $post );
69
+ }
70
 
71
+ private function create_box( $directed ) {
72
+ $box_args = $this->queue[ $directed->name ];
73
 
74
+ $title_class = 'P2P_Field_Title_' . ucfirst( $directed->get_opposite( 'object' ) );
 
75
 
76
+ $columns = array(
77
+ 'delete' => new P2P_Field_Delete,
78
+ 'title' => new $title_class( $directed->get_opposite( 'labels' )->singular_name ),
79
+ );
 
 
 
 
80
 
81
+ foreach ( $directed->fields as $key => $data ) {
82
+ $columns[ 'meta-' . $key ] = new P2P_Field_Generic( $key, $data );
83
  }
 
 
 
 
 
 
84
 
85
+ if ( $orderby_key = $directed->get_orderby_key() ) {
86
+ $columns['order'] = new P2P_Field_Order( $orderby_key );
87
  }
88
 
89
+ return new P2P_Box( $box_args, $columns, $directed );
 
 
 
90
  }
91
 
92
  /**
93
  * Collect metadata from all boxes.
94
  */
95
+ function save_post( $post_id, $post ) {
96
  if ( 'revision' == $post->post_type || defined( 'DOING_AJAX' ) )
97
  return;
98
 
99
  if ( isset( $_POST['p2p_connections'] ) ) {
100
  // Loop through the hidden fields instead of through $_POST['p2p_meta'] because empty checkboxes send no data.
101
  foreach ( $_POST['p2p_connections'] as $p2p_id ) {
102
+ $data = stripslashes_deep( scbForms::get_value( array( 'p2p_meta', $p2p_id ), $_POST, array() ) );
103
 
104
  $connection = p2p_get_connection( $p2p_id );
105
 
111
 
112
  $data = scbForms::validate_post_data( $fields, $data );
113
 
114
+ scbForms::update_meta( $fields, self::addslashes_deep( $data ), $p2p_id, 'p2p' );
115
  }
116
  }
117
 
125
  }
126
  }
127
 
128
+ private function addslashes_deep( $value ) {
129
+ if ( is_array( $value ) )
130
+ return array_map( array( __CLASS__, __METHOD__ ), $value );
131
+
132
+ return addslashes( $value );
133
+ }
134
+
135
  /**
136
  * Controller for all box ajax requests.
137
  */
138
+ function wp_ajax_p2p_box() {
139
  check_ajax_referer( P2P_BOX_NONCE, 'nonce' );
140
 
141
  $ctype = p2p_type( $_REQUEST['p2p_type'] );
142
+ if ( !$ctype || !isset( $this->queue[$ctype->name] ) )
 
 
 
 
143
  die(0);
144
 
145
  $directed = $ctype->set_direction( $_REQUEST['direction'] );
146
  if ( !$directed )
147
  die(0);
148
 
149
+ $post = get_post( $_REQUEST['from'] );
150
+ if ( !$post )
151
+ die(0);
152
 
153
+ if ( !self::show_box( $directed, $post ) )
154
  die(-1);
155
 
156
+ $box = $this->create_box( $directed );
157
+
158
  $method = 'ajax_' . $_REQUEST['subaction'];
159
 
160
  $box->$method();
161
  }
162
  }
163
 
164
+ new P2P_Box_Factory;
165
 
admin/box.css CHANGED
@@ -2,12 +2,12 @@
2
  color: #AAA;
3
  }
4
 
5
- .p2p-box a {
6
  text-decoration: none;
7
  }
8
 
9
- .p2p-create-connections p {
10
- margin-bottom: .5em;
11
  }
12
 
13
  /* Tabs */
@@ -47,6 +47,7 @@
47
  }
48
 
49
  .p2p-search + .p2p-results,
 
50
  .p2p-results + .p2p-navigation {
51
  margin-top: 8px;
52
  }
@@ -68,14 +69,6 @@ td.p2p-col-create {
68
  padding: 0;
69
  }
70
 
71
- .p2p-col-delete a,
72
- .p2p-col-create a,
73
- .p2p-col-delete img,
74
- .p2p-col-create img {
75
- padding: 5px 6px;
76
- vertical-align: middle;
77
- }
78
-
79
  .p2p-results tr:first-child td {
80
  border-top: 0;
81
  }
@@ -89,52 +82,59 @@ td.p2p-col-create {
89
  width: 100%;
90
  }
91
 
92
- .p2p-col-create,
93
  .p2p-col-delete {
94
  width: 16px;
 
95
  }
96
 
97
- .p2p-col-create a,
98
- .p2p-col-delete a {
99
  display: block;
100
  height: 16px;
101
  width: 16px;
 
102
  }
103
 
104
- .p2p-col-create,
105
- .p2p-col-delete {
106
- border-right: 1px solid transparent;
 
 
 
 
 
107
  }
108
 
109
  .p2p-col-create:hover,
110
  .p2p-col-delete:hover {
111
  background-color: #f5f5f5;
112
  border-right-color: #ddd;
 
113
  }
114
 
115
- .p2p-col-create a,
116
- .p2p-col-delete a {
117
- opacity: 0.5;
118
  }
119
 
120
- .p2p-col-create:hover a,
121
- .p2p-col-delete:hover a {
122
- opacity: 1;
123
  }
124
 
125
- .p2p-col-create a {
126
  background: url("images/add.png") no-repeat scroll 50% 50% transparent;
 
 
127
  }
128
 
129
- .p2p-col-delete a {
130
- background: url("images/delete.png") no-repeat scroll 50% 50% transparent;
131
  }
132
 
133
- .p2p-col-title img {
134
  display: block;
135
  margin: 2px 0;
136
- max-height: 32px;
137
- max-width: 40px;
138
  }
139
 
140
  td.p2p-col-order {
@@ -163,11 +163,6 @@ tr:hover td.p2p-col-order {
163
  display: block;
164
  }
165
 
166
- #side-sortables .p2p-notice {
167
- display: block;
168
- margin-top: 8px;
169
- }
170
-
171
  /* Pagination */
172
 
173
  .p2p-navigation {
2
  color: #AAA;
3
  }
4
 
5
+ .p2p-box td a {
6
  text-decoration: none;
7
  }
8
 
9
+ .p2p-toggle-tabs {
10
+ font-weight: bold;
11
  }
12
 
13
  /* Tabs */
47
  }
48
 
49
  .p2p-search + .p2p-results,
50
+ .p2p-search + .p2p-notice,
51
  .p2p-results + .p2p-navigation {
52
  margin-top: 8px;
53
  }
69
  padding: 0;
70
  }
71
 
 
 
 
 
 
 
 
 
72
  .p2p-results tr:first-child td {
73
  border-top: 0;
74
  }
82
  width: 100%;
83
  }
84
 
 
85
  .p2p-col-delete {
86
  width: 16px;
87
+ border-right: 1px solid transparent;
88
  }
89
 
90
+ .p2p-icon {
 
91
  display: block;
92
  height: 16px;
93
  width: 16px;
94
+ opacity: 0.5;
95
  }
96
 
97
+ .p2p-col-create div,
98
+ .p2p-icon {
99
+ padding: 5px 6px;
100
+ vertical-align: middle;
101
+ }
102
+
103
+ td.p2p-col-create .p2p-icon {
104
+ padding: 0;
105
  }
106
 
107
  .p2p-col-create:hover,
108
  .p2p-col-delete:hover {
109
  background-color: #f5f5f5;
110
  border-right-color: #ddd;
111
+ cursor: pointer;
112
  }
113
 
114
+ .p2p-col-create:hover .p2p-icon,
115
+ .p2p-col-delete:hover .p2p-icon {
116
+ opacity: 1;
117
  }
118
 
119
+ .p2p-col-delete .p2p-icon {
120
+ background: url("images/delete.png") no-repeat scroll 50% 50% transparent;
 
121
  }
122
 
123
+ .p2p-col-create .p2p-icon {
124
  background: url("images/add.png") no-repeat scroll 50% 50% transparent;
125
+ float: left;
126
+ margin-right: 6px;
127
  }
128
 
129
+ .p2p-box .post-state {
130
+ font-style: italic;
131
  }
132
 
133
+ .p2p-box td img {
134
  display: block;
135
  margin: 2px 0;
136
+ max-height: 60px;
137
+ max-width: 80px;
138
  }
139
 
140
  td.p2p-col-order {
163
  display: block;
164
  }
165
 
 
 
 
 
 
166
  /* Pagination */
167
 
168
  .p2p-navigation {
admin/box.js CHANGED
@@ -1,4 +1,3 @@
1
- // Generated by CoffeeScript 1.3.1
2
  (function() {
3
 
4
  jQuery(function() {
@@ -11,7 +10,7 @@
11
  $this.val($this.attr('placeholder'));
12
  $this.addClass('p2p-placeholder');
13
  }
14
- return;
15
  };
16
  clearVal = function() {
17
  var $this;
@@ -20,12 +19,12 @@
20
  $this.val('');
21
  $this.removeClass('p2p-placeholder');
22
  }
23
- return;
24
  };
25
  jQuery('.p2p-search input[placeholder]').each(setVal).focus(clearVal).blur(setVal);
26
  }
27
  return jQuery('.p2p-box').each(function() {
28
- var $connections, $createButton, $createInput, $metabox, $searchInput, $spinner, $viewAll, PostsTab, ajax_request, append_connection, clear_connections, create_connection, delete_connection, refresh_candidates, remove_row, row_ajax_request, searchTab, switch_to_tab;
29
  $metabox = jQuery(this);
30
  $connections = $metabox.find('.p2p-connections');
31
  $spinner = jQuery('<img>', {
@@ -49,7 +48,11 @@
49
  handler = function(response) {
50
  try {
51
  response = jQuery.parseJSON(response);
52
- return callback(response);
 
 
 
 
53
  } catch (e) {
54
  return typeof console !== "undefined" && console !== null ? console.error('Malformed response', response) : void 0;
55
  }
@@ -63,8 +66,6 @@
63
  };
64
  PostsTab = (function() {
65
 
66
- PostsTab.name = 'PostsTab';
67
-
68
  function PostsTab(selector) {
69
  var _this = this;
70
  this.tab = $metabox.find(selector);
@@ -112,12 +113,8 @@
112
  PostsTab.prototype.update_rows = function(response) {
113
  $spinner.remove();
114
  this.tab.find('button, .p2p-results, .p2p-navigation, .p2p-notice').remove();
115
- if (!response.rows) {
116
- return this.tab.append(jQuery('<div class="p2p-notice">').html(response.msg));
117
- } else {
118
- this.tab.append(response.rows);
119
- return this.init_pagination_data();
120
- }
121
  };
122
 
123
  return PostsTab;
@@ -125,7 +122,7 @@
125
  })();
126
  searchTab = new PostsTab('.p2p-tab-search');
127
  row_ajax_request = function($td, data, callback) {
128
- $td.html($spinner.show());
129
  return ajax_request(data, callback);
130
  };
131
  remove_row = function($td) {
@@ -147,66 +144,70 @@
147
  return searchTab.update_rows(results);
148
  };
149
  clear_connections = function(ev) {
150
- var $self, $td, data,
151
  _this = this;
 
152
  if (!confirm(P2PAdmin.deleteConfirmMessage)) {
153
- return false;
154
  }
155
- $self = jQuery(ev.target);
156
- $td = $self.closest('td');
157
  data = {
158
  subaction: 'clear_connections'
159
  };
160
  row_ajax_request($td, data, function(response) {
161
  $connections.hide().find('tbody').html('');
162
- $td.html($self);
163
  return refresh_candidates(response);
164
  });
165
- return false;
166
  };
167
  delete_connection = function(ev) {
168
- var $self, $td, data,
169
  _this = this;
170
- $self = jQuery(ev.target);
171
- $td = $self.closest('td');
172
  data = {
173
  subaction: 'disconnect',
174
- p2p_id: $self.closest('td').find('input').val()
175
  };
176
  row_ajax_request($td, data, function(response) {
177
  remove_row($td);
178
  return refresh_candidates(response);
179
  });
180
- return false;
181
  };
182
  create_connection = function(ev) {
183
- var $self, $td, data,
184
  _this = this;
185
- $self = jQuery(ev.target);
186
- $td = $self.closest('td');
187
  data = {
188
  subaction: 'connect',
189
- to: $self.data('post_id')
190
  };
191
  row_ajax_request($td, data, function(response) {
192
  append_connection(response);
193
  if ($metabox.data('duplicate_connections')) {
194
- return $td.html($self);
195
  } else {
196
  return remove_row($td);
197
  }
198
  });
199
- return false;
200
  };
201
- switch_to_tab = function() {
 
 
 
 
 
202
  var $tab;
 
203
  $tab = jQuery(this);
204
  $metabox.find('.wp-tab-bar li').removeClass('wp-tab-active');
205
  $tab.addClass('wp-tab-active');
206
- $metabox.find('.tabs-panel').hide().end().find($tab.data('ref')).show().find(':text').focus();
207
- return false;
208
  };
209
- $metabox.delegate('th.p2p-col-delete a', 'click', clear_connections).delegate('td.p2p-col-delete a', 'click', delete_connection).delegate('td.p2p-col-create a', 'click', create_connection).delegate('.wp-tab-bar li', 'click', switch_to_tab);
210
  if ($connections.find('th.p2p-col-order').length) {
211
  $connections.find('tbody').sortable({
212
  handle: 'td.p2p-col-order',
@@ -220,22 +221,18 @@
220
  }
221
  });
222
  }
223
- $viewAll = $metabox.find('.p2p-tab-search button');
224
- $viewAll.click(function() {
225
- searchTab.find_posts(1);
226
- return false;
227
- });
228
  $searchInput = $metabox.find('.p2p-tab-search :text');
229
  $searchInput.keypress(function(ev) {
230
  if (ev.keyCode === 13) {
231
- return false;
232
  }
 
233
  }).keyup(function(ev) {
234
  var delayed;
235
  if (delayed !== void 0) {
236
  clearTimeout(delayed);
237
  }
238
- return delayed = setTimeout(function() {
239
  var searchStr;
240
  searchStr = $searchInput.val();
241
  if (searchStr === searchTab.params.s) {
@@ -245,19 +242,21 @@
245
  $spinner.insertAfter($searchInput).show();
246
  return searchTab.find_posts(1);
247
  }, 400);
 
248
  });
249
  $createButton = $metabox.find('.p2p-tab-create-post button');
250
  $createInput = $metabox.find('.p2p-tab-create-post :text');
251
- $createButton.click(function() {
252
  var $button, data, title;
 
253
  $button = jQuery(this);
254
  if ($button.hasClass('inactive')) {
255
- return false;
256
  }
257
  title = $createInput.val();
258
  if (title === '') {
259
  $createInput.focus();
260
- return false;
261
  }
262
  $button.addClass('inactive');
263
  data = {
@@ -269,13 +268,14 @@
269
  $createInput.val('');
270
  return $button.removeClass('inactive');
271
  });
272
- return false;
273
  });
274
  return $createInput.keypress(function(ev) {
275
  if (13 === ev.keyCode) {
276
  $createButton.click();
277
- return false;
278
  }
 
279
  });
280
  });
281
  });
 
1
  (function() {
2
 
3
  jQuery(function() {
10
  $this.val($this.attr('placeholder'));
11
  $this.addClass('p2p-placeholder');
12
  }
13
+ return void 0;
14
  };
15
  clearVal = function() {
16
  var $this;
19
  $this.val('');
20
  $this.removeClass('p2p-placeholder');
21
  }
22
+ return void 0;
23
  };
24
  jQuery('.p2p-search input[placeholder]').each(setVal).focus(clearVal).blur(setVal);
25
  }
26
  return jQuery('.p2p-box').each(function() {
27
+ var $connections, $createButton, $createInput, $metabox, $searchInput, $spinner, PostsTab, ajax_request, append_connection, clear_connections, create_connection, delete_connection, refresh_candidates, remove_row, row_ajax_request, searchTab, switch_to_tab, toggle_tabs;
28
  $metabox = jQuery(this);
29
  $connections = $metabox.find('.p2p-connections');
30
  $spinner = jQuery('<img>', {
48
  handler = function(response) {
49
  try {
50
  response = jQuery.parseJSON(response);
51
+ if (response.error) {
52
+ return alert(response.error);
53
+ } else {
54
+ return callback(response);
55
+ }
56
  } catch (e) {
57
  return typeof console !== "undefined" && console !== null ? console.error('Malformed response', response) : void 0;
58
  }
66
  };
67
  PostsTab = (function() {
68
 
 
 
69
  function PostsTab(selector) {
70
  var _this = this;
71
  this.tab = $metabox.find(selector);
113
  PostsTab.prototype.update_rows = function(response) {
114
  $spinner.remove();
115
  this.tab.find('button, .p2p-results, .p2p-navigation, .p2p-notice').remove();
116
+ this.tab.append(response.rows);
117
+ return this.init_pagination_data();
 
 
 
 
118
  };
119
 
120
  return PostsTab;
122
  })();
123
  searchTab = new PostsTab('.p2p-tab-search');
124
  row_ajax_request = function($td, data, callback) {
125
+ $td.find('.p2p-icon').css('background-image', 'url(' + P2PAdmin.spinner + ')');
126
  return ajax_request(data, callback);
127
  };
128
  remove_row = function($td) {
144
  return searchTab.update_rows(results);
145
  };
146
  clear_connections = function(ev) {
147
+ var $td, data,
148
  _this = this;
149
+ ev.preventDefault();
150
  if (!confirm(P2PAdmin.deleteConfirmMessage)) {
151
+ return;
152
  }
153
+ $td = jQuery(ev.target).closest('td');
 
154
  data = {
155
  subaction: 'clear_connections'
156
  };
157
  row_ajax_request($td, data, function(response) {
158
  $connections.hide().find('tbody').html('');
 
159
  return refresh_candidates(response);
160
  });
161
+ return null;
162
  };
163
  delete_connection = function(ev) {
164
+ var $td, data,
165
  _this = this;
166
+ ev.preventDefault();
167
+ $td = jQuery(ev.target).closest('td');
168
  data = {
169
  subaction: 'disconnect',
170
+ p2p_id: $td.find('input').val()
171
  };
172
  row_ajax_request($td, data, function(response) {
173
  remove_row($td);
174
  return refresh_candidates(response);
175
  });
176
+ return null;
177
  };
178
  create_connection = function(ev) {
179
+ var $td, data,
180
  _this = this;
181
+ ev.preventDefault();
182
+ $td = jQuery(ev.target).closest('td');
183
  data = {
184
  subaction: 'connect',
185
+ to: $td.find('div').data('item-id')
186
  };
187
  row_ajax_request($td, data, function(response) {
188
  append_connection(response);
189
  if ($metabox.data('duplicate_connections')) {
190
+ return $td.find('.p2p-icon').css('background-image', '');
191
  } else {
192
  return remove_row($td);
193
  }
194
  });
195
+ return null;
196
  };
197
+ toggle_tabs = function(ev) {
198
+ ev.preventDefault();
199
+ $metabox.find('.p2p-create-connections-tabs').toggle();
200
+ return null;
201
+ };
202
+ switch_to_tab = function(ev) {
203
  var $tab;
204
+ ev.preventDefault();
205
  $tab = jQuery(this);
206
  $metabox.find('.wp-tab-bar li').removeClass('wp-tab-active');
207
  $tab.addClass('wp-tab-active');
208
+ return $metabox.find('.tabs-panel').hide().end().find($tab.data('ref')).show().find(':text').focus();
 
209
  };
210
+ $metabox.delegate('th.p2p-col-delete .p2p-icon', 'click', clear_connections).delegate('td.p2p-col-delete .p2p-icon', 'click', delete_connection).delegate('td.p2p-col-create div', 'click', create_connection).delegate('.p2p-toggle-tabs', 'click', toggle_tabs).delegate('.wp-tab-bar li', 'click', switch_to_tab);
211
  if ($connections.find('th.p2p-col-order').length) {
212
  $connections.find('tbody').sortable({
213
  handle: 'td.p2p-col-order',
221
  }
222
  });
223
  }
 
 
 
 
 
224
  $searchInput = $metabox.find('.p2p-tab-search :text');
225
  $searchInput.keypress(function(ev) {
226
  if (ev.keyCode === 13) {
227
+ ev.preventDefault();
228
  }
229
+ return null;
230
  }).keyup(function(ev) {
231
  var delayed;
232
  if (delayed !== void 0) {
233
  clearTimeout(delayed);
234
  }
235
+ delayed = setTimeout(function() {
236
  var searchStr;
237
  searchStr = $searchInput.val();
238
  if (searchStr === searchTab.params.s) {
242
  $spinner.insertAfter($searchInput).show();
243
  return searchTab.find_posts(1);
244
  }, 400);
245
+ return null;
246
  });
247
  $createButton = $metabox.find('.p2p-tab-create-post button');
248
  $createInput = $metabox.find('.p2p-tab-create-post :text');
249
+ $createButton.click(function(ev) {
250
  var $button, data, title;
251
+ ev.preventDefault();
252
  $button = jQuery(this);
253
  if ($button.hasClass('inactive')) {
254
+ return;
255
  }
256
  title = $createInput.val();
257
  if (title === '') {
258
  $createInput.focus();
259
+ return;
260
  }
261
  $button.addClass('inactive');
262
  data = {
268
  $createInput.val('');
269
  return $button.removeClass('inactive');
270
  });
271
+ return null;
272
  });
273
  return $createInput.keypress(function(ev) {
274
  if (13 === ev.keyCode) {
275
  $createButton.click();
276
+ ev.preventDefault();
277
  }
278
+ return null;
279
  });
280
  });
281
  });
admin/box.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  interface P2P_Field {
4
  function get_title();
5
- function render( $p2p_id, $post_id );
6
  }
7
 
8
  class P2P_Box {
@@ -10,8 +10,6 @@ class P2P_Box {
10
 
11
  private $args;
12
 
13
- private $ptype;
14
-
15
  private $columns;
16
 
17
  private static $admin_box_qv = array(
@@ -20,14 +18,14 @@ class P2P_Box {
20
  'post_status' => 'any',
21
  );
22
 
23
- function __construct( $args, $ctype ) {
24
  $this->args = $args;
25
 
 
 
26
  $this->ctype = $ctype;
27
 
28
  $this->labels = $this->ctype->get_opposite( 'labels' );
29
-
30
- $this->init_columns();
31
  }
32
 
33
  public function init_scripts() {
@@ -41,31 +39,13 @@ class P2P_Box {
41
  ) );
42
  }
43
 
44
- protected function init_columns() {
45
- $title_class = $this->get_column_title_class();
46
-
47
- $this->columns = array(
48
- 'delete' => new P2P_Field_Delete,
49
- 'title' => new $title_class( $this->labels->singular_name ),
50
- );
51
-
52
- foreach ( $this->ctype->fields as $key => $data ) {
53
- $this->columns[ 'meta-' . $key ] = new P2P_Field_Generic( $key, $data );
54
- }
55
-
56
- if ( $orderby_key = $this->ctype->get_orderby_key() ) {
57
- $this->columns['order'] = new P2P_Field_Order( $orderby_key );
58
- }
59
- }
60
-
61
- protected function get_column_title_class() {
62
- $object_type = $this->ctype->get_opposite( 'object' );
63
-
64
- return 'P2P_Field_Title_' . ucfirst( $object_type );
65
- }
66
-
67
  function render( $post ) {
68
- $this->connected_items = $this->get_folded_connections( $post->ID );
 
 
 
 
 
69
 
70
  $data = array(
71
  'attributes' => $this->render_data_attributes(),
@@ -76,17 +56,6 @@ class P2P_Box {
76
  echo P2P_Mustache::render( 'box', $data );
77
  }
78
 
79
- protected function get_folded_connections( $post ) {
80
- $extra_qv = array_merge( self::$admin_box_qv, array(
81
- 'p2p:context' => 'admin_box',
82
- 'p2p:per_page' => -1
83
- ) );
84
-
85
- $query = $this->ctype->get_connected( $post, $extra_qv, 'abstract' );
86
-
87
- return scb_list_fold( $query->items, 'p2p_id', 'ID' );
88
- }
89
-
90
  protected function render_data_attributes() {
91
  $data_attr = array(
92
  'p2p_type' => $this->ctype->name,
@@ -109,8 +78,8 @@ class P2P_Box {
109
  $data['hide'] = 'style="display:none"';
110
 
111
  $tbody = array();
112
- foreach ( $this->connected_items as $p2p_id => $item_id ) {
113
- $tbody[] = $this->connection_row( $p2p_id, $item_id );
114
  }
115
  $data['tbody'] = $tbody;
116
 
@@ -126,16 +95,18 @@ class P2P_Box {
126
 
127
  protected function render_create_connections( $post ) {
128
  $data = array(
129
- 'label' => __( 'Create connections:', P2P_TEXTDOMAIN )
130
  );
131
 
132
- if ( 'one' == $this->ctype->get_opposite( 'cardinality' ) && !empty( $this->connected_items ) )
133
- $data['hide'] = 'style="display:none"';
 
 
134
 
135
  // Search tab
136
  $tab_content = P2P_Mustache::render( 'tab-search', array(
137
  'placeholder' => $this->labels->search_items,
138
- 'view-all' => __( 'View All', P2P_TEXTDOMAIN ),
139
  ) );
140
 
141
  $data['tabs'][] = array(
@@ -163,17 +134,17 @@ class P2P_Box {
163
  return $data;
164
  }
165
 
166
- protected function connection_row( $p2p_id, $post_id, $render = false ) {
167
- return $this->table_row( $this->columns, $p2p_id, $post_id, $render );
168
  }
169
 
170
- protected function table_row( $columns, $p2p_id, $post_id, $render = false ) {
171
  $data = array();
172
 
173
  foreach ( $columns as $key => $field ) {
174
  $data['columns'][] = array(
175
  'column' => $key,
176
- 'content' => $field->render( $p2p_id, $post_id )
177
  );
178
  }
179
 
@@ -192,18 +163,18 @@ class P2P_Box {
192
 
193
  $candidate = $this->ctype->get_connectable( $current_post_id, $extra_qv );
194
 
195
- if ( empty( $candidate->items ) )
196
- return false;
 
197
 
198
  $data = array();
199
 
200
  $columns = array(
201
- 'create' => new P2P_Field_Create,
202
- 'title' => $this->columns['title']
203
  );
204
 
205
  foreach ( $candidate->items as $item ) {
206
- $data['rows'][] = $this->table_row( $columns, 0, $item->ID );
207
  }
208
 
209
  if ( $candidate->total_pages > 1 ) {
@@ -236,7 +207,7 @@ class P2P_Box {
236
  $args = array(
237
  'post_title' => $_POST['post_title'],
238
  'post_author' => get_current_user_id(),
239
- 'post_type' => $this->ctype->get_opposite( 'side' )->post_type[0]
240
  );
241
 
242
  $from = absint( $_POST['from'] );
@@ -259,10 +230,20 @@ class P2P_Box {
259
 
260
  $p2p_id = $this->ctype->connect( $from, $to );
261
 
262
- if ( is_wp_error( $p2p_id ) )
263
- $r = array( 'error' => sprintf( __( "Can't create connection: %s", P2P_TEXTDOMAIN ), $p2p_id->get_error_message() ) );
264
- else
265
- $r = array( 'row' => $this->connection_row( $p2p_id, $to, true ) );
 
 
 
 
 
 
 
 
 
 
266
 
267
  die( json_encode( $r ) );
268
  }
@@ -280,51 +261,24 @@ class P2P_Box {
280
  }
281
 
282
  public function ajax_search() {
283
- die( json_encode( $this->_ajax_search( $_GET ) ) );
284
  }
285
 
286
  private function refresh_candidates() {
287
- $results = $this->_ajax_search( $_POST );
288
 
289
- die( json_encode( $results ) );
290
- }
291
-
292
- private function _ajax_search( $args ) {
293
- $rows = $this->post_rows( $args['from'], $args['paged'], $args['s'] );
294
 
295
- if ( $rows ) {
296
- $results = compact( 'rows' );
297
- } else {
298
- $results = array(
299
- 'msg' => $this->labels->not_found,
300
- );
301
- }
302
-
303
- return $results;
304
  }
305
 
306
  protected function can_create_post() {
307
  if ( !$this->args->can_create_post )
308
  return false;
309
 
310
- if ( 'post' != $this->ctype->get_opposite( 'object' ) )
311
- return false;
312
-
313
  $side = $this->ctype->get_opposite( 'side' );
314
 
315
- if ( count( $side->post_type ) > 1 )
316
- return false;
317
-
318
- if ( count( $side->query_vars ) > 1 )
319
- return false;
320
-
321
- return true;
322
- }
323
-
324
- public function check_capability() {
325
- $show = $this->ctype->get_opposite( 'side' )->check_capability();
326
-
327
- return apply_filters( 'p2p_admin_box_show', $show, $this->ctype, $GLOBALS['post'] );
328
  }
329
  }
330
 
2
 
3
  interface P2P_Field {
4
  function get_title();
5
+ function render( $p2p_id, $item );
6
  }
7
 
8
  class P2P_Box {
10
 
11
  private $args;
12
 
 
 
13
  private $columns;
14
 
15
  private static $admin_box_qv = array(
18
  'post_status' => 'any',
19
  );
20
 
21
+ function __construct( $args, $columns, $ctype ) {
22
  $this->args = $args;
23
 
24
+ $this->columns = $columns;
25
+
26
  $this->ctype = $ctype;
27
 
28
  $this->labels = $this->ctype->get_opposite( 'labels' );
 
 
29
  }
30
 
31
  public function init_scripts() {
39
  ) );
40
  }
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  function render( $post ) {
43
+ $extra_qv = array_merge( self::$admin_box_qv, array(
44
+ 'p2p:context' => 'admin_box',
45
+ 'p2p:per_page' => -1
46
+ ) );
47
+
48
+ $this->connected_items = $this->ctype->get_connected( $post, $extra_qv, 'abstract' )->items;
49
 
50
  $data = array(
51
  'attributes' => $this->render_data_attributes(),
56
  echo P2P_Mustache::render( 'box', $data );
57
  }
58
 
 
 
 
 
 
 
 
 
 
 
 
59
  protected function render_data_attributes() {
60
  $data_attr = array(
61
  'p2p_type' => $this->ctype->name,
78
  $data['hide'] = 'style="display:none"';
79
 
80
  $tbody = array();
81
+ foreach ( $this->connected_items as $item ) {
82
+ $tbody[] = $this->connection_row( $item->p2p_id, $item );
83
  }
84
  $data['tbody'] = $tbody;
85
 
95
 
96
  protected function render_create_connections( $post ) {
97
  $data = array(
98
+ 'label' => $this->labels->create,
99
  );
100
 
101
+ if ( 'one' == $this->ctype->get_opposite( 'cardinality' ) ) {
102
+ if ( !empty( $this->connected_items ) )
103
+ $data['hide'] = 'style="display:none"';
104
+ }
105
 
106
  // Search tab
107
  $tab_content = P2P_Mustache::render( 'tab-search', array(
108
  'placeholder' => $this->labels->search_items,
109
+ 'candidates' => $this->post_rows( $post->ID )
110
  ) );
111
 
112
  $data['tabs'][] = array(
134
  return $data;
135
  }
136
 
137
+ protected function connection_row( $p2p_id, $item, $render = false ) {
138
+ return $this->table_row( $this->columns, $p2p_id, $item, $render );
139
  }
140
 
141
+ protected function table_row( $columns, $p2p_id, $item, $render = false ) {
142
  $data = array();
143
 
144
  foreach ( $columns as $key => $field ) {
145
  $data['columns'][] = array(
146
  'column' => $key,
147
+ 'content' => $field->render( $p2p_id, $item )
148
  );
149
  }
150
 
163
 
164
  $candidate = $this->ctype->get_connectable( $current_post_id, $extra_qv );
165
 
166
+ if ( empty( $candidate->items ) ) {
167
+ return html( 'div class="p2p-notice"', $this->labels->not_found );
168
+ }
169
 
170
  $data = array();
171
 
172
  $columns = array(
173
+ 'create' => new P2P_Field_Create( $this->columns['title'] ),
 
174
  );
175
 
176
  foreach ( $candidate->items as $item ) {
177
+ $data['rows'][] = $this->table_row( $columns, 0, $item );
178
  }
179
 
180
  if ( $candidate->total_pages > 1 ) {
207
  $args = array(
208
  'post_title' => $_POST['post_title'],
209
  'post_author' => get_current_user_id(),
210
+ 'post_type' => $this->ctype->get_opposite( 'side' )->first_post_type()
211
  );
212
 
213
  $from = absint( $_POST['from'] );
230
 
231
  $p2p_id = $this->ctype->connect( $from, $to );
232
 
233
+ if ( is_wp_error( $p2p_id ) ) {
234
+ $r = array(
235
+ 'error' => sprintf(
236
+ __( "Can't create connection: %s", P2P_TEXTDOMAIN ),
237
+ $p2p_id->get_error_message()
238
+ )
239
+ );
240
+ } else {
241
+ $item = $this->ctype->get_opposite('side')->item_recognize( $to );
242
+
243
+ $r = array(
244
+ 'row' => $this->connection_row( $p2p_id, $item, true )
245
+ );
246
+ }
247
 
248
  die( json_encode( $r ) );
249
  }
261
  }
262
 
263
  public function ajax_search() {
264
+ $this->refresh_candidates();
265
  }
266
 
267
  private function refresh_candidates() {
268
+ $rows = $this->post_rows( $_REQUEST['from'], $_REQUEST['paged'], $_REQUEST['s'] );
269
 
270
+ $results = compact( 'rows' );
 
 
 
 
271
 
272
+ die( json_encode( $results ) );
 
 
 
 
 
 
 
 
273
  }
274
 
275
  protected function can_create_post() {
276
  if ( !$this->args->can_create_post )
277
  return false;
278
 
 
 
 
279
  $side = $this->ctype->get_opposite( 'side' );
280
 
281
+ return $side->can_create_item();
 
 
 
 
 
 
 
 
 
 
 
 
282
  }
283
  }
284
 
admin/column-factory.php CHANGED
@@ -1,66 +1,54 @@
1
  <?php
2
 
3
- class P2P_Column_Factory {
4
 
5
- private static $column_args = array();
 
6
 
7
- static function init() {
8
- add_filter( 'p2p_connection_type_args', array( __CLASS__, 'filter_args' ) );
9
-
10
- add_action( 'admin_print_styles', array( __CLASS__, 'add_columns' ) );
11
  }
12
 
13
- static function filter_args( $args ) {
14
  if ( isset( $args['admin_column'] ) ) {
15
  $column_args = _p2p_pluck( $args, 'admin_column' );
 
 
16
  } else {
17
- $column_args = false;
18
  }
19
 
20
- self::register( $args['name'], $column_args );
21
-
22
- return $args;
23
- }
24
 
25
- static function register( $p2p_type, $column_args ) {
26
- if ( isset( self::$column_args[$p2p_type] ) )
27
- return false;
28
 
29
- if ( !$column_args )
30
- return false;
31
-
32
- self::$column_args[$p2p_type] = $column_args;
33
-
34
- return true;
35
  }
36
 
37
- static function add_columns() {
38
  $screen = get_current_screen();
39
 
40
- if ( 'edit' != $screen->base )
41
- return;
42
-
43
- $post_type = $screen->post_type;
44
-
45
- foreach ( self::$column_args as $p2p_type => $column_args ) {
46
- $ctype = p2p_type( $p2p_type );
47
 
48
- $directed = $ctype->find_direction( $post_type );
49
- if ( !$directed )
50
- continue;
51
 
52
- if ( !( 'any' == $column_args || $directed->get_direction() == $column_args ) )
53
- continue;
54
 
55
- $column = new P2P_Column( $directed );
 
56
 
57
- $column->styles();
 
 
58
 
59
- add_filter( "manage_{$screen->id}_columns", array( $column, 'add_column' ) );
60
- add_action( "manage_{$post_type}_posts_custom_column", array( $column, 'display_column' ), 10, 2 );
61
- }
62
  }
63
  }
64
 
65
- P2P_Column_Factory::init();
66
 
1
  <?php
2
 
3
+ class P2P_Column_Factory extends P2P_Factory {
4
 
5
+ function __construct() {
6
+ add_filter( 'p2p_connection_type_args', array( $this, 'filter_ctypes' ) );
7
 
8
+ add_action( 'admin_print_styles', array( $this, 'add_columns' ) );
 
 
 
9
  }
10
 
11
+ function filter_ctypes( $args ) {
12
  if ( isset( $args['admin_column'] ) ) {
13
  $column_args = _p2p_pluck( $args, 'admin_column' );
14
+ if ( !is_array( $column_args ) )
15
+ $column_args = array( 'show' => $column_args );
16
  } else {
17
+ $column_args = array();
18
  }
19
 
20
+ $column_args = wp_parse_args( $column_args, array(
21
+ 'show' => false,
22
+ ) );
 
23
 
24
+ $this->register( $args['name'], $column_args );
 
 
25
 
26
+ return $args;
 
 
 
 
 
27
  }
28
 
29
+ function add_columns() {
30
  $screen = get_current_screen();
31
 
32
+ $screen_map = array(
33
+ 'edit' => 'post',
34
+ 'users' => 'user'
35
+ );
 
 
 
36
 
37
+ if ( !isset( $screen_map[ $screen->base ] ) )
38
+ return;
 
39
 
40
+ $object_type = $screen_map[ $screen->base ];
 
41
 
42
+ $this->filter( $object_type, $screen->post_type );
43
+ }
44
 
45
+ function add_item( $directed, $object_type, $post_type, $title ) {
46
+ $class = 'P2P_Column_' . ucfirst( $object_type );
47
+ $column = new $class( $directed );
48
 
49
+ $column->styles();
 
 
50
  }
51
  }
52
 
53
+ new P2P_Column_Factory;
54
 
admin/column.php CHANGED
@@ -1,23 +1,35 @@
1
  <?php
2
 
3
- class P2P_Column {
4
 
5
  protected $ctype;
6
 
7
  protected $connected = array();
8
 
9
- function __construct( $directed ) {
10
  $this->ctype = $directed;
11
 
12
- $extra_qv = array( 'p2p:context' => 'admin_column' );
 
 
 
13
 
14
- $this->ctype->lose_direction()->each_connected( $GLOBALS['wp_query'], $extra_qv );
15
 
16
- $this->connected = scb_list_fold( $GLOBALS['wp_query']->posts, 'ID', 'connected' );
 
 
 
 
17
  }
18
 
19
  function add_column( $columns ) {
20
- $columns[ $this->ctype->name ] = $this->ctype->get_current( 'title' );
 
 
 
 
 
21
 
22
  return $columns;
23
  }
@@ -33,34 +45,81 @@ class P2P_Column {
33
  <?php
34
  }
35
 
36
- function display_column( $column, $item_id ) {
37
- if ( $this->ctype->name != $column )
 
 
 
 
 
38
  return;
39
 
40
- $opposite_direction = array(
41
- 'from' => 'to',
42
- 'to' => 'from',
43
- 'any' => 'any'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  );
45
 
46
- $direction = $opposite_direction[ $this->ctype->get_direction() ];
 
 
 
 
 
 
 
47
 
48
- $side = $this->ctype->side[ $direction ];
49
 
50
- echo '<ul>';
51
- foreach ( $this->connected[ $item_id ] as $item ) {
52
- $args = array(
53
- 'connected_type' => $this->ctype->name,
54
- 'connected_direction' => $direction,
55
- 'connected_items' => $item->ID,
56
- 'post_type' => get_current_screen()->post_type
57
- );
58
 
59
- $url = add_query_arg( $args, admin_url( 'edit.php' ) );
60
 
61
- echo html( 'li', html_link( $url, $side->item_title( $item ) ) );
62
- }
63
- echo '</ul>';
 
 
 
 
 
 
 
 
 
 
 
 
64
  }
65
  }
66
 
1
  <?php
2
 
3
+ abstract class P2P_Column {
4
 
5
  protected $ctype;
6
 
7
  protected $connected = array();
8
 
9
+ function __construct( $directed, $items ) {
10
  $this->ctype = $directed;
11
 
12
+ $extra_qv = array(
13
+ 'p2p:per_page' => -1,
14
+ 'p2p:context' => 'admin_column'
15
+ );
16
 
17
+ $connected = $this->ctype->get_connected( $items, $extra_qv, 'abstract' );
18
 
19
+ $this->connected = p2p_triage_connected( $connected->items );
20
+
21
+ $screen = get_current_screen();
22
+
23
+ add_filter( "manage_{$screen->id}_columns", array( $this, 'add_column' ) );
24
  }
25
 
26
  function add_column( $columns ) {
27
+ $this->column_id = sprintf( 'p2p-%s-%s',
28
+ $this->ctype->get_direction(),
29
+ $this->ctype->name
30
+ );
31
+
32
+ $columns[ $this->column_id ] = $this->ctype->get_current( 'title' );
33
 
34
  return $columns;
35
  }
45
  <?php
46
  }
47
 
48
+ abstract function get_admin_link( $item );
49
+
50
+ protected function render_column( $column, $item_id ) {
51
+ if ( $this->column_id != $column )
52
+ return;
53
+
54
+ if ( !isset( $this->connected[ $item_id ] ) )
55
  return;
56
 
57
+ $out = '<ul>';
58
+ foreach ( $this->connected[ $item_id ] as $item ) {
59
+ $out .= html( 'li', html_link( $this->get_admin_link( $item ), $item->get_title() ) );
60
+ }
61
+ $out .= '</ul>';
62
+
63
+ return $out;
64
+ }
65
+ }
66
+
67
+
68
+ class P2P_Column_Post extends P2P_Column {
69
+
70
+ function __construct( $directed ) {
71
+ global $wp_query;
72
+
73
+ $this->ctype = $directed;
74
+
75
+ $extra_qv = array( 'p2p:context' => 'admin_column' );
76
+
77
+ parent::__construct( $directed, $wp_query->posts );
78
+
79
+ $screen = get_current_screen();
80
+
81
+ add_action( "manage_{$screen->post_type}_posts_custom_column", array( $this, 'display_column' ), 10, 2 );
82
+ }
83
+
84
+ function get_admin_link( $item ) {
85
+ $args = array(
86
+ 'connected_type' => $this->ctype->name,
87
+ 'connected_direction' => $this->ctype->flip_direction()->get_direction(),
88
+ 'connected_items' => $item->get_id(),
89
+ 'post_type' => get_current_screen()->post_type
90
  );
91
 
92
+ return add_query_arg( $args, admin_url( 'edit.php' ) );
93
+ }
94
+
95
+ function display_column( $column, $item_id ) {
96
+ echo parent::render_column( $column, $item_id );
97
+ }
98
+ }
99
+
100
 
101
+ class P2P_Column_User extends P2P_Column {
102
 
103
+ function __construct( $directed ) {
104
+ global $wp_list_table;
 
 
 
 
 
 
105
 
106
+ parent::__construct( $directed, $wp_list_table->items );
107
 
108
+ add_filter( 'manage_users_custom_column', array( $this, 'display_column' ), 10, 3 );
109
+ }
110
+
111
+ function get_admin_link( $item ) {
112
+ $args = array(
113
+ 'connected_type' => $this->ctype->name,
114
+ 'connected_direction' => $this->ctype->flip_direction()->get_direction(),
115
+ 'connected_items' => $item->get_id(),
116
+ );
117
+
118
+ return add_query_arg( $args, admin_url( 'users.php' ) );
119
+ }
120
+
121
+ function display_column( $content, $column, $item_id ) {
122
+ return parent::render_column( $column, $item_id );
123
  }
124
  }
125
 
admin/factory.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ abstract class P2P_Factory {
4
+
5
+ protected $queue = array();
6
+
7
+ function register( $p2p_type, $args ) {
8
+ if ( isset( $this->queue[$p2p_type] ) )
9
+ return false;
10
+
11
+ $args = (object) $args;
12
+
13
+ if ( !$args->show )
14
+ return false;
15
+
16
+ $this->queue[$p2p_type] = $args;
17
+
18
+ return true;
19
+ }
20
+
21
+ function filter( $object_type, $post_type ) {
22
+ foreach ( $this->queue as $p2p_type => $args ) {
23
+ $ctype = p2p_type( $p2p_type );
24
+
25
+ $directions = self::determine_directions( $ctype, $object_type, $post_type, $args->show );
26
+
27
+ $title = $ctype->title;
28
+
29
+ if ( count( $directions ) > 1 && $title['from'] == $title['to'] ) {
30
+ $title['from'] .= __( ' (from)', P2P_TEXTDOMAIN );
31
+ $title['to'] .= __( ' (to)', P2P_TEXTDOMAIN );
32
+ }
33
+
34
+ foreach ( $directions as $direction ) {
35
+ $key = ( 'to' == $direction ) ? 'to' : 'from';
36
+
37
+ $directed = $ctype->set_direction( $direction );
38
+
39
+ $this->add_item( $directed, $object_type, $post_type, $title[$key] );
40
+ }
41
+ }
42
+ }
43
+
44
+ protected static function determine_directions( $ctype, $object_type, $post_type, $show_ui ) {
45
+ $direction = $ctype->direction_from_types( $object_type, $post_type );
46
+ if ( !$direction )
47
+ return array();
48
+
49
+ if ( $ctype->indeterminate )
50
+ $direction = 'any';
51
+
52
+ if ( $ctype->reciprocal ) {
53
+ if ( $show_ui )
54
+ $directions = array( 'any' );
55
+ else
56
+ $directions = array();
57
+ } else {
58
+ $directions = array_intersect(
59
+ _p2p_expand_direction( $show_ui ),
60
+ _p2p_expand_direction( $direction )
61
+ );
62
+ }
63
+
64
+ return $directions;
65
+ }
66
+
67
+ abstract function add_item( $directed, $object_type, $post_type, $title );
68
+ }
69
+
admin/fields.php CHANGED
@@ -1,23 +1,5 @@
1
  <?php
2
 
3
- class P2P_Field_Create implements P2P_Field {
4
-
5
- function get_title() {
6
- // Not needed
7
- return '';
8
- }
9
-
10
- function render( $p2p_id, $post_id ) {
11
- $data = array(
12
- 'post_id' => $post_id,
13
- 'title' => __( 'Create connection', P2P_TEXTDOMAIN )
14
- );
15
-
16
- return P2P_Mustache::render( 'column-create', $data );
17
- }
18
- }
19
-
20
-
21
  class P2P_Field_Delete implements P2P_Field {
22
 
23
  function get_title() {
@@ -28,7 +10,7 @@ class P2P_Field_Delete implements P2P_Field {
28
  return P2P_Mustache::render( 'column-delete-all', $data );
29
  }
30
 
31
- function render( $p2p_id, $post_id ) {
32
  $data = array(
33
  'p2p_id' => $p2p_id,
34
  'title' => __( 'Delete connection', P2P_TEXTDOMAIN )
@@ -51,7 +33,7 @@ class P2P_Field_Order implements P2P_Field {
51
  return '';
52
  }
53
 
54
- function render( $p2p_id, $post_id ) {
55
  return html( 'input', array(
56
  'type' => 'hidden',
57
  'name' => "p2p_order[$this->sort_key][]",
@@ -60,6 +42,7 @@ class P2P_Field_Order implements P2P_Field {
60
  }
61
  }
62
 
 
63
  class P2P_Field_Generic implements P2P_Field {
64
 
65
  protected $key;
@@ -74,7 +57,7 @@ class P2P_Field_Generic implements P2P_Field {
74
  return $this->data['title'];
75
  }
76
 
77
- function render( $p2p_id, $post_id ) {
78
  $args = array(
79
  'name' => array( 'p2p_meta', $p2p_id, $this->key ),
80
  'type' => $this->data['type']
@@ -91,7 +74,31 @@ class P2P_Field_Generic implements P2P_Field {
91
  }
92
 
93
 
94
- class P2P_Field_Title_Post implements P2P_Field {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  protected $title;
97
 
@@ -103,63 +110,55 @@ class P2P_Field_Title_Post implements P2P_Field {
103
  return $this->title;
104
  }
105
 
106
- function render( $p2p_id, $post_id ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  $data = array(
108
- 'title-attr' => get_permalink( $post_id ),
109
- 'title' => get_post_field( 'post_title', $post_id ),
110
- 'url' => get_edit_post_link( $post_id ),
111
  );
112
 
113
- $post_status = get_post_status( $post_id );
114
 
115
- if ( 'publish' != $post_status ) {
116
- $status_obj = get_post_status_object( $post_status );
117
  if ( $status_obj ) {
118
  $data['status']['text'] = $status_obj->label;
119
  }
120
  }
121
 
122
- return P2P_Mustache::render( 'column-title', $data );
123
  }
124
  }
125
 
 
126
 
127
- class P2P_Field_Title_Attachment extends P2P_Field_Title_Post {
128
-
129
- function render( $p2p_id, $attachment_id ) {
130
- list( $src ) = wp_get_attachment_image_src( $attachment_id, 'thumbnail', true );
131
-
132
  $data = array(
133
- 'title-attr' => get_post_field( 'post_title', $attachment_id ),
134
- 'title' => html( 'img', compact( 'src' ) ),
135
- 'url' => get_edit_post_link( $attachment_id ),
136
  );
137
 
138
- return P2P_Mustache::render( 'column-title', $data );
139
  }
140
  }
141
 
 
142
 
143
- class P2P_Field_Title_User extends P2P_Field_Title_Post {
144
-
145
- function render( $p2p_id, $user_id ) {
146
- $data = array(
147
  'title-attr' => '',
148
- 'title' => get_user_by( 'id', $user_id )->display_name,
149
- 'url' => $this->get_edit_url( $user_id ),
150
  );
151
-
152
- return P2P_Mustache::render( 'column-title', $data );
153
- }
154
-
155
- private function get_edit_url( $user_id ) {
156
- if ( get_current_user_id() == $user_id ) {
157
- $edit_link = 'profile.php';
158
- } else {
159
- $edit_link = "user-edit.php?user_id=$user_id";
160
- }
161
-
162
- return admin_url( $edit_link );
163
  }
164
  }
165
 
1
  <?php
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  class P2P_Field_Delete implements P2P_Field {
4
 
5
  function get_title() {
10
  return P2P_Mustache::render( 'column-delete-all', $data );
11
  }
12
 
13
+ function render( $p2p_id, $_ ) {
14
  $data = array(
15
  'p2p_id' => $p2p_id,
16
  'title' => __( 'Delete connection', P2P_TEXTDOMAIN )
33
  return '';
34
  }
35
 
36
+ function render( $p2p_id, $_ ) {
37
  return html( 'input', array(
38
  'type' => 'hidden',
39
  'name' => "p2p_order[$this->sort_key][]",
42
  }
43
  }
44
 
45
+
46
  class P2P_Field_Generic implements P2P_Field {
47
 
48
  protected $key;
57
  return $this->data['title'];
58
  }
59
 
60
+ function render( $p2p_id, $_ ) {
61
  $args = array(
62
  'name' => array( 'p2p_meta', $p2p_id, $this->key ),
63
  'type' => $this->data['type']
74
  }
75
 
76
 
77
+ class P2P_Field_Create implements P2P_Field {
78
+
79
+ protected $title_field;
80
+
81
+ function __construct( $title_field ) {
82
+ $this->title_field = $title_field;
83
+ }
84
+
85
+ function get_title() {
86
+ // Not needed
87
+ return '';
88
+ }
89
+
90
+ function render( $p2p_id, $item ) {
91
+ $data = array_merge( $this->title_field->get_data( $item ), array(
92
+ 'title' => $item->get_title(),
93
+ 'item-id' => $item->get_id(),
94
+ ) );
95
+
96
+ return P2P_Mustache::render( 'column-create', $data );
97
+ }
98
+ }
99
+
100
+
101
+ abstract class P2P_Field_Title implements P2P_Field {
102
 
103
  protected $title;
104
 
110
  return $this->title;
111
  }
112
 
113
+ function render( $p2p_id, $item ) {
114
+ $data = array_merge( $this->get_data( $item ), array(
115
+ 'title' => $item->get_title(),
116
+ 'url' => $item->get_editlink(),
117
+ ) );
118
+
119
+ return P2P_Mustache::render( 'column-title', $data );
120
+ }
121
+
122
+ abstract function get_data( $item );
123
+ }
124
+
125
+ class P2P_Field_Title_Post extends P2P_Field_Title {
126
+
127
+ function get_data( $item ) {
128
  $data = array(
129
+ 'title-attr' => $item->get_permalink()
 
 
130
  );
131
 
132
+ $post = $item->get_object();
133
 
134
+ if ( 'publish' != $post->post_status ) {
135
+ $status_obj = get_post_status_object( $post->post_status );
136
  if ( $status_obj ) {
137
  $data['status']['text'] = $status_obj->label;
138
  }
139
  }
140
 
141
+ return $data;
142
  }
143
  }
144
 
145
+ class P2P_Field_Title_Attachment extends P2P_Field_Title {
146
 
147
+ function get_data( $item ) {
 
 
 
 
148
  $data = array(
149
+ 'title-attr' => $item->get_object()->post_title,
 
 
150
  );
151
 
152
+ return $data;
153
  }
154
  }
155
 
156
+ class P2P_Field_Title_User extends P2P_Field_Title {
157
 
158
+ function get_data( $user ) {
159
+ return array(
 
 
160
  'title-attr' => '',
 
 
161
  );
 
 
 
 
 
 
 
 
 
 
 
 
162
  }
163
  }
164
 
admin/templates/box.html CHANGED
@@ -19,20 +19,23 @@
19
 
20
  {{#create-connections}}
21
  <div class="p2p-create-connections" {{{hide}}}>
22
- <p><strong>{{{label}}}</strong></p>
23
- {{#show-tab-headers}}
24
- <ul class="wp-tab-bar clearfix">
 
 
 
 
 
 
 
 
25
  {{#tabs}}
26
- <li {{#is-active}}class="wp-tab-active"{{/is-active}} data-ref=".p2p-tab-{{tab-id}}"><a href="#">{{{tab-title}}}</a></li>
 
 
27
  {{/tabs}}
28
- </ul>
29
- {{/show-tab-headers}}
30
-
31
- {{#tabs}}
32
- <div class="p2p-tab-{{tab-id}} tabs-panel">
33
- {{{tab-content}}}
34
  </div>
35
- {{/tabs}}
36
  </div>
37
  {{/create-connections}}
38
  </div>
19
 
20
  {{#create-connections}}
21
  <div class="p2p-create-connections" {{{hide}}}>
22
+ <p class="p2p-toggle-tabs"><a href="#">+ {{{label}}}</a></p>
23
+
24
+ <div class="p2p-create-connections-tabs" style="display: none">
25
+ {{#show-tab-headers}}
26
+ <ul class="wp-tab-bar clearfix">
27
+ {{#tabs}}
28
+ <li {{#is-active}}class="wp-tab-active"{{/is-active}} data-ref=".p2p-tab-{{tab-id}}"><a href="#">{{{tab-title}}}</a></li>
29
+ {{/tabs}}
30
+ </ul>
31
+ {{/show-tab-headers}}
32
+
33
  {{#tabs}}
34
+ <div class="p2p-tab-{{tab-id}} tabs-panel">
35
+ {{{tab-content}}}
36
+ </div>
37
  {{/tabs}}
 
 
 
 
 
 
38
  </div>
 
39
  </div>
40
  {{/create-connections}}
41
  </div>
admin/templates/column-create.html CHANGED
@@ -1 +1,4 @@
1
- <a href="#" data-post_id="{{post_id}}" title="{{{title}}}"></a>
 
 
 
1
+ <div data-item-id="{{item-id}}" title="{{{title-attr}}}">
2
+ <span class="p2p-icon"></span>
3
+ {{{title}}}{{#status}} - <span class="post-state">{{text}}</span>{{/status}}
4
+ </div>
admin/templates/column-delete-all.html CHANGED
@@ -1 +1 @@
1
- <a href="#" title="{{{title}}}"></a>
1
+ <span class="p2p-icon" title="{{{title}}}"></span>
admin/templates/column-delete.html CHANGED
@@ -1,2 +1,2 @@
1
- <a href="#" title="{{{title}}}"></a>
2
  <input type="hidden" name="p2p_connections[]" value="{{p2p_id}}" />
1
+ <span class="p2p-icon" title="{{{title}}}"></span>
2
  <input type="hidden" name="p2p_connections[]" value="{{p2p_id}}" />
admin/templates/connection-types.html CHANGED
@@ -1,3 +1,4 @@
 
1
  <table class="widefat">
2
  <thead>
3
  <tr>
@@ -16,3 +17,8 @@
16
  {{/rows}}
17
  </tbody>
18
  </table>
 
 
 
 
 
1
+ {{#has-rows}}
2
  <table class="widefat">
3
  <thead>
4
  <tr>
17
  {{/rows}}
18
  </tbody>
19
  </table>
20
+ {{/has-rows}}
21
+ {{^has-rows}}
22
+ <p>{{{no-rows}}}</p>
23
+ <p>{{{no-rows2}}}</p>
24
+ {{/has-rows}}
admin/templates/tab-search.html CHANGED
@@ -1,5 +1,5 @@
1
  <div class="p2p-search">
2
  <input type="text" name="p2p_search" autocomplete="off" placeholder="{{{placeholder}}}" />
3
-
4
- <button class="button">{{{view-all}}}</button>
5
  </div>
 
 
1
  <div class="p2p-search">
2
  <input type="text" name="p2p_search" autocomplete="off" placeholder="{{{placeholder}}}" />
 
 
3
  </div>
4
+
5
+ {{{candidates}}}
admin/tools.php CHANGED
@@ -23,20 +23,7 @@ class P2P_Tools_Page extends scbAdminPage {
23
 
24
  P2P_Storage::install();
25
 
26
- if ( isset( $_GET['p2p-upgrade'] ) ) {
27
- $n = P2P_Storage::upgrade();
28
-
29
- update_option( 'p2p_storage', P2P_Storage::$version );
30
-
31
- echo scb_admin_notice( sprintf( __( 'Upgraded %d connections.', P2P_TEXTDOMAIN ), $n ) );
32
- } elseif ( $current_ver ) {
33
- echo scb_admin_notice( sprintf(
34
- __( 'The Posts 2 Posts connections need to be upgraded. <a href="%s">Proceed.</a>', P2P_TEXTDOMAIN ),
35
- admin_url( 'tools.php?page=connection-types&p2p-upgrade=1' )
36
- ) );
37
- } else {
38
- update_option( 'p2p_storage', P2P_Storage::$version );
39
- }
40
  }
41
 
42
  function form_handler() {
@@ -80,22 +67,35 @@ class P2P_Tools_Page extends scbAdminPage {
80
  )
81
  );
82
 
83
- foreach ( $this->get_connection_counts() as $p2p_type => $count ) {
84
- $row = array(
85
- 'p2p_type' => $p2p_type,
86
- 'count' => number_format_i18n( $count )
 
 
 
 
87
  );
 
 
88
 
89
- $ctype = p2p_type( $p2p_type );
 
 
 
 
90
 
91
- if ( $ctype ) {
92
- $row['desc'] = $ctype->get_desc();
93
- } else {
94
- $row['desc'] = __( 'Convert to registered connection type:', P2P_TEXTDOMAIN ) . scbForms::form_wrap( $this->get_dropdown( $p2p_type ), $this->nonce );
95
- $row['class'] = 'error';
96
- }
97
 
98
- $data['rows'][] = $row;
 
 
 
 
 
 
 
 
99
  }
100
 
101
  echo P2P_Mustache::render( 'connection-types', $data );
23
 
24
  P2P_Storage::install();
25
 
26
+ update_option( 'p2p_storage', P2P_Storage::$version );
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
28
 
29
  function form_handler() {
67
  )
68
  );
69
 
70
+ $connection_counts = $this->get_connection_counts();
71
+
72
+ if ( empty( $connection_counts ) ) {
73
+ $data['has-rows'] = false;
74
+ $data['no-rows'] = __( 'No connection types registered.', P2P_TEXTDOMAIN );
75
+ $data['no-rows2'] = sprintf(
76
+ __( 'To register a connection type, see <a href="%s">the wiki</a>.', P2P_TEXTDOMAIN ),
77
+ 'https://github.com/scribu/wp-posts-to-posts/wiki/'
78
  );
79
+ } else {
80
+ $data['has-rows'] = array(true);
81
 
82
+ foreach ( $connection_counts as $p2p_type => $count ) {
83
+ $row = array(
84
+ 'p2p_type' => $p2p_type,
85
+ 'count' => number_format_i18n( $count )
86
+ );
87
 
88
+ $ctype = p2p_type( $p2p_type );
 
 
 
 
 
89
 
90
+ if ( $ctype ) {
91
+ $row['desc'] = $ctype->get_desc();
92
+ } else {
93
+ $row['desc'] = __( 'Convert to registered connection type:', P2P_TEXTDOMAIN ) . scbForms::form_wrap( $this->get_dropdown( $p2p_type ), $this->nonce );
94
+ $row['class'] = 'error';
95
+ }
96
+
97
+ $data['rows'][] = $row;
98
+ }
99
  }
100
 
101
  echo P2P_Mustache::render( 'connection-types', $data );
core/api.php CHANGED
@@ -171,7 +171,7 @@ function _p2p_get_connections( $p2p_type, $args = array() ) {
171
  if ( empty( $$key ) )
172
  return array();
173
 
174
- $value = scbUtil::array_to_sql( (array) $$key );
175
 
176
  $where .= " AND p2p_$key IN ($value)";
177
  }
@@ -228,8 +228,8 @@ function p2p_create_connection( $p2p_type, $args ) {
228
  'meta' => array()
229
  ) ), EXTR_SKIP );
230
 
231
- $from = absint( $from );
232
- $to = absint( $to );
233
 
234
  if ( !$from || !$to )
235
  return false;
@@ -251,6 +251,8 @@ function p2p_create_connection( $p2p_type, $args ) {
251
  foreach ( $meta as $key => $value )
252
  p2p_add_meta( $p2p_id, $key, $value );
253
 
 
 
254
  return $p2p_id;
255
  }
256
 
@@ -283,6 +285,8 @@ function p2p_delete_connection( $p2p_id ) {
283
 
284
  $p2p_ids = array_map( 'absint', (array) $p2p_id );
285
 
 
 
286
  $where = "WHERE p2p_id IN (" . implode( ',', $p2p_ids ) . ")";
287
 
288
  $count = $wpdb->query( "DELETE FROM $wpdb->p2p $where" );
@@ -339,6 +343,16 @@ function p2p_distribute_connected( $items, $connected, $prop_name ) {
339
  $indexed_list[ $item->ID ] = $item;
340
  }
341
 
 
 
 
 
 
 
 
 
 
 
342
  foreach ( $connected as $inner_item ) {
343
  if ( $inner_item->ID == $inner_item->p2p_from ) {
344
  $outer_item_id = $inner_item->p2p_to;
@@ -349,7 +363,9 @@ function p2p_distribute_connected( $items, $connected, $prop_name ) {
349
  continue;
350
  }
351
 
352
- array_push( $indexed_list[ $outer_item_id ]->$prop_name, $inner_item );
353
  }
 
 
354
  }
355
 
171
  if ( empty( $$key ) )
172
  return array();
173
 
174
+ $value = scbUtil::array_to_sql( _p2p_normalize( $$key ) );
175
 
176
  $where .= " AND p2p_$key IN ($value)";
177
  }
228
  'meta' => array()
229
  ) ), EXTR_SKIP );
230
 
231
+ list( $from ) = _p2p_normalize( $from );
232
+ list( $to ) = _p2p_normalize( $to );
233
 
234
  if ( !$from || !$to )
235
  return false;
251
  foreach ( $meta as $key => $value )
252
  p2p_add_meta( $p2p_id, $key, $value );
253
 
254
+ do_action( 'p2p_created_connection', $p2p_id );
255
+
256
  return $p2p_id;
257
  }
258
 
285
 
286
  $p2p_ids = array_map( 'absint', (array) $p2p_id );
287
 
288
+ do_action( 'p2p_delete_connections', $p2p_ids );
289
+
290
  $where = "WHERE p2p_id IN (" . implode( ',', $p2p_ids ) . ")";
291
 
292
  $count = $wpdb->query( "DELETE FROM $wpdb->p2p $where" );
343
  $indexed_list[ $item->ID ] = $item;
344
  }
345
 
346
+ $groups = p2p_triage_connected( $connected );
347
+
348
+ foreach ( $groups as $outer_item_id => $connected_items ) {
349
+ $indexed_list[ $outer_item_id ]->$prop_name = $connected_items;
350
+ }
351
+ }
352
+
353
+ function p2p_triage_connected( $connected ) {
354
+ $groups = array();
355
+
356
  foreach ( $connected as $inner_item ) {
357
  if ( $inner_item->ID == $inner_item->p2p_from ) {
358
  $outer_item_id = $inner_item->p2p_to;
363
  continue;
364
  }
365
 
366
+ $groups[ $outer_item_id ][] = $inner_item;
367
  }
368
+
369
+ return $groups;
370
  }
371
 
core/directed-type.php CHANGED
@@ -31,12 +31,7 @@ class P2P_Directed_Connection_Type {
31
  }
32
 
33
  public function flip_direction() {
34
- if ( 'any' == $this->direction )
35
- return $this;
36
-
37
- $direction = ( 'to' == $this->direction ) ? 'from' : 'to';
38
-
39
- return $this->set_direction( $direction );
40
  }
41
 
42
  public function get_opposite( $key ) {
@@ -98,34 +93,12 @@ class P2P_Directed_Connection_Type {
98
  $side = $this->get_opposite( 'side' );
99
 
100
  $args = array_merge( $side->translate_qv( $extra_qv ), array(
 
 
101
  'connected_items' => $item
102
  ) );
103
 
104
- return $this->abstract_query( $this->get_connected_args( $args ), $side, $output );
105
- }
106
-
107
- public function get_connected_args( $q ) {
108
- $q = wp_parse_args( $q, array(
109
- 'p2p:context' => false
110
- ) );
111
-
112
- if ( $orderby_key = $this->get_orderby_key() ) {
113
- $q = wp_parse_args( $q, array(
114
- 'connected_orderby' => $orderby_key,
115
- 'connected_order' => 'ASC',
116
- 'connected_order_num' => true,
117
- ) );
118
- }
119
-
120
- $q = array_merge( $this->get_opposite( 'side' )->get_base_qv( $q ), array(
121
- 'p2p_type' => array( $this->name => $this->get_direction() ),
122
- ) );
123
-
124
- $q = array_merge_recursive( $q, array(
125
- 'connected_meta' => $this->data
126
- ) );
127
-
128
- return apply_filters( 'p2p_connected_args', $q, $this, $q['connected_items'] );
129
  }
130
 
131
  public function get_orderby_key() {
@@ -145,38 +118,40 @@ class P2P_Directed_Connection_Type {
145
  /**
146
  * Get a list of items that could be connected to a given item.
147
  *
148
- * @param int $post_id A post id.
149
  */
150
- public function get_connectable( $item_id, $extra_qv = array() ) {
151
  $side = $this->get_opposite( 'side' );
152
 
153
- $extra_qv['p2p:exclude'] = $this->get_non_connectable( $item_id );
 
 
154
 
155
  $extra_qv = $side->get_base_qv( $side->translate_qv( $extra_qv ) );
156
 
157
- $qv = apply_filters( 'p2p_connectable_args', $extra_qv, $this, $item_id );
158
 
159
  return $this->abstract_query( $qv, $side );
160
  }
161
 
162
- private function get_non_connectable( $item_id ) {
163
  $to_exclude = array();
164
 
165
  if ( $this->indeterminate && !$this->self_connections )
166
- $to_exclude[] = $item_id;
167
 
168
  if ( 'one' == $this->get_current( 'cardinality' ) ) {
169
- _p2p_append( $to_exclude, $this->get_connections( array(
170
- 'fields' => 'object_id'
171
- ) ) );
 
 
172
  }
173
 
174
- if ( !$this->duplicate_connections ) {
175
- _p2p_append( $to_exclude, $this->get_connections( array(
176
- 'from' => $item_id,
177
- 'fields' => 'object_id'
178
- ) ) );
179
- }
180
 
181
  return $to_exclude;
182
  }
@@ -191,25 +166,29 @@ class P2P_Directed_Connection_Type {
191
  * @return int|object p2p_id or WP_Error on failure
192
  */
193
  public function connect( $from, $to, $meta = array() ) {
194
- $from = $this->get_current( 'side' )->item_id( $from );
195
  if ( !$from )
196
  return new WP_Error( 'first_parameter', 'Invalid first parameter.' );
197
 
198
- $to = $this->get_opposite( 'side' )->item_id( $to );
199
  if ( !$to )
200
  return new WP_Error( 'second_parameter', 'Invalid second parameter.' );
201
 
202
- if ( !$this->self_connections && $from == $to )
203
  return new WP_Error( 'self_connection', 'Connection between an element and itself is not allowed.' );
204
 
205
  if ( !$this->duplicate_connections && $this->get_p2p_id( $from, $to ) )
206
  return new WP_Error( 'duplicate_connection', 'Duplicate connections are not allowed.' );
207
 
208
- if ( 'one' == $this->get_opposite( 'cardinality' ) && $this->connection_exists( compact( 'from' ) ) )
209
- return new WP_Error( 'cardinality_opposite', 'Cardinality problem.' );
 
 
210
 
211
- if ( 'one' == $this->get_current( 'cardinality' ) && $this->connection_exists( compact( 'to' ) ) )
212
- return new WP_Error( 'cardinality_current', 'Cardinality problem.' );
 
 
213
 
214
  $p2p_id = $this->create_connection( array(
215
  'from' => $from,
@@ -228,6 +207,14 @@ class P2P_Directed_Connection_Type {
228
  return $p2p_id;
229
  }
230
 
 
 
 
 
 
 
 
 
231
  protected function get_default( $args, $p2p_id ) {
232
  if ( isset( $args['default_cb'] ) )
233
  return call_user_func( $args['default_cb'], p2p_get_connection( $p2p_id ) );
@@ -247,12 +234,12 @@ class P2P_Directed_Connection_Type {
247
  * @return int|object count or WP_Error on failure
248
  */
249
  public function disconnect( $from, $to ) {
250
- $from = $this->get_current( 'side' )->item_id( $from );
251
  if ( !$from )
252
  return new WP_Error( 'first_parameter', 'Invalid first parameter.' );
253
 
254
  if ( 'any' != $to ) {
255
- $to = $this->get_opposite( 'side' )->item_id( $to );
256
  if ( !$to )
257
  return new WP_Error( 'second_parameter', 'Invalid second parameter.' );
258
  }
31
  }
32
 
33
  public function flip_direction() {
34
+ return $this->set_direction( _p2p_flip_direction( $this->direction ) );
 
 
 
 
 
35
  }
36
 
37
  public function get_opposite( $key ) {
93
  $side = $this->get_opposite( 'side' );
94
 
95
  $args = array_merge( $side->translate_qv( $extra_qv ), array(
96
+ 'connected_type' => $this->name,
97
+ 'connected_direction' => $this->direction,
98
  'connected_items' => $item
99
  ) );
100
 
101
+ return $this->abstract_query( $args, $side, $output );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  }
103
 
104
  public function get_orderby_key() {
118
  /**
119
  * Get a list of items that could be connected to a given item.
120
  *
121
+ * @param mixed $arg The item to find connection candidates for.
122
  */
123
+ public function get_connectable( $arg, $extra_qv = array() ) {
124
  $side = $this->get_opposite( 'side' );
125
 
126
+ $item = $this->get_current( 'side' )->item_recognize( $arg );
127
+
128
+ $extra_qv['p2p:exclude'] = $this->get_non_connectable( $item, $extra_qv );
129
 
130
  $extra_qv = $side->get_base_qv( $side->translate_qv( $extra_qv ) );
131
 
132
+ $qv = apply_filters( 'p2p_connectable_args', $extra_qv, $this, $item->get_object() );
133
 
134
  return $this->abstract_query( $qv, $side );
135
  }
136
 
137
+ private function get_non_connectable( $item, $extra_qv ) {
138
  $to_exclude = array();
139
 
140
  if ( $this->indeterminate && !$this->self_connections )
141
+ $to_exclude[] = $item->get_id();
142
 
143
  if ( 'one' == $this->get_current( 'cardinality' ) ) {
144
+ $to_check = 'any';
145
+ } elseif ( !$this->duplicate_connections ) {
146
+ $to_check = $item;
147
+ } else {
148
+ return $to_exclude;
149
  }
150
 
151
+ $extra_qv['fields'] = 'ids';
152
+ $already_connected = $this->get_connected( $to_check, $extra_qv, 'abstract' )->items;
153
+
154
+ _p2p_append( $to_exclude, $already_connected );
 
 
155
 
156
  return $to_exclude;
157
  }
166
  * @return int|object p2p_id or WP_Error on failure
167
  */
168
  public function connect( $from, $to, $meta = array() ) {
169
+ $from = $this->get_current( 'side' )->item_recognize( $from );
170
  if ( !$from )
171
  return new WP_Error( 'first_parameter', 'Invalid first parameter.' );
172
 
173
+ $to = $this->get_opposite( 'side' )->item_recognize( $to );
174
  if ( !$to )
175
  return new WP_Error( 'second_parameter', 'Invalid second parameter.' );
176
 
177
+ if ( !$this->self_connections && $from->get_id() == $to->get_id() )
178
  return new WP_Error( 'self_connection', 'Connection between an element and itself is not allowed.' );
179
 
180
  if ( !$this->duplicate_connections && $this->get_p2p_id( $from, $to ) )
181
  return new WP_Error( 'duplicate_connection', 'Duplicate connections are not allowed.' );
182
 
183
+ if ( 'one' == $this->get_opposite( 'cardinality' ) ) {
184
+ if ( $this->has_connections( $from ) )
185
+ return new WP_Error( 'cardinality_opposite', 'Cardinality problem (opposite).' );
186
+ }
187
 
188
+ if ( 'one' == $this->get_current( 'cardinality' ) ) {
189
+ if ( $this->has_connections( $to ) )
190
+ return new WP_Error( 'cardinality_current', 'Cardinality problem (current).' );
191
+ }
192
 
193
  $p2p_id = $this->create_connection( array(
194
  'from' => $from,
207
  return $p2p_id;
208
  }
209
 
210
+ protected function has_connections( $item ) {
211
+ $extra_qv = array( 'p2p:per_page' => 1 );
212
+
213
+ $connections = $this->lose_direction()->get_connected( $item, $extra_qv, 'abstract' );
214
+
215
+ return !empty( $connections->items );
216
+ }
217
+
218
  protected function get_default( $args, $p2p_id ) {
219
  if ( isset( $args['default_cb'] ) )
220
  return call_user_func( $args['default_cb'], p2p_get_connection( $p2p_id ) );
234
  * @return int|object count or WP_Error on failure
235
  */
236
  public function disconnect( $from, $to ) {
237
+ $from = $this->get_current( 'side' )->item_recognize( $from );
238
  if ( !$from )
239
  return new WP_Error( 'first_parameter', 'Invalid first parameter.' );
240
 
241
  if ( 'any' != $to ) {
242
+ $to = $this->get_opposite( 'side' )->item_recognize( $to );
243
  if ( !$to )
244
  return new WP_Error( 'second_parameter', 'Invalid second parameter.' );
245
  }
core/extra.php CHANGED
@@ -25,9 +25,6 @@ class P2P_Widget extends scbWidget {
25
  $ctypes = array();
26
 
27
  foreach ( P2P_Connection_Type_Factory::get_all_instances() as $p2p_type => $ctype ) {
28
- if ( ! $ctype instanceof P2P_Connection_Type )
29
- continue;
30
-
31
  $ctypes[ $p2p_type ] = $ctype->get_desc();
32
  }
33
 
@@ -41,7 +38,8 @@ class P2P_Widget extends scbWidget {
41
  'type' => 'select',
42
  'name' => 'ctype',
43
  'values' => $ctypes,
44
- 'desc' => __( 'Connection type:', P2P_TEXTDOMAIN )
 
45
  ), $instance ) );
46
 
47
  echo html( 'p',
25
  $ctypes = array();
26
 
27
  foreach ( P2P_Connection_Type_Factory::get_all_instances() as $p2p_type => $ctype ) {
 
 
 
28
  $ctypes[ $p2p_type ] = $ctype->get_desc();
29
  }
30
 
38
  'type' => 'select',
39
  'name' => 'ctype',
40
  'values' => $ctypes,
41
+ 'desc' => __( 'Connection type:', P2P_TEXTDOMAIN ),
42
+ 'extra' => "style='width: 100%'"
43
  ), $instance ) );
44
 
45
  echo html( 'p',
core/item.php ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ abstract class P2P_Item {
4
+
5
+ protected $item;
6
+
7
+ function __construct( $item ) {
8
+ $this->item = $item;
9
+ }
10
+
11
+ function __get( $key ) {
12
+ return $this->item->$key;
13
+ }
14
+
15
+ function get_object() {
16
+ return $this->item;
17
+ }
18
+
19
+ function get_id() {
20
+ return $this->item->ID;
21
+ }
22
+
23
+ abstract function get_permalink();
24
+ abstract function get_title();
25
+ }
26
+
27
+
28
+ class P2P_Item_Post extends P2P_Item {
29
+
30
+ function get_title() {
31
+ return get_the_title( $this->item );
32
+ }
33
+
34
+ function get_permalink() {
35
+ return get_permalink( $this->item );
36
+ }
37
+
38
+ function get_editlink() {
39
+ return get_edit_post_link( $this->item );
40
+ }
41
+ }
42
+
43
+
44
+ class P2P_Item_Attachment extends P2P_Item_Post {
45
+
46
+ function get_title() {
47
+ return wp_get_attachment_image( $this->item->ID, 'thumbnail', false );
48
+ }
49
+ }
50
+
51
+
52
+ class P2P_Item_User extends P2P_Item {
53
+
54
+ function get_title() {
55
+ return $this->item->display_name;
56
+ }
57
+
58
+ function get_permalink() {
59
+ return get_author_posts_url( $this->item->ID );
60
+ }
61
+
62
+ function get_editlink() {
63
+ return get_edit_user_link( $this->item->ID );
64
+ }
65
+ }
66
+
67
+
68
+ // WP < 3.5
69
+ if ( !function_exists( 'get_edit_user_link' ) ) :
70
+ function get_edit_user_link( $user_id = null ) {
71
+ if ( ! $user_id )
72
+ $user_id = get_current_user_id();
73
+
74
+ if ( empty( $user_id ) || ! current_user_can( 'edit_user', $user_id ) )
75
+ return '';
76
+
77
+ $user = new WP_User( $user_id );
78
+
79
+ if ( ! $user->exists() )
80
+ return '';
81
+
82
+ if ( get_current_user_id() == $user->ID )
83
+ $link = get_edit_profile_url( $user->ID );
84
+ else
85
+ $link = add_query_arg( 'user_id', $user->ID, self_admin_url( 'user-edit.php' ) );
86
+
87
+ return apply_filters( 'get_edit_user_link', $link, $user->ID );
88
+ }
89
+ endif;
90
+
core/list.php CHANGED
@@ -6,6 +6,16 @@ abstract class P2P_List {
6
  public $current_page = 1;
7
  public $total_pages = 0;
8
 
 
 
 
 
 
 
 
 
 
 
9
  function render( $args = array() ) {
10
  if ( empty( $this->items ) )
11
  return '';
@@ -33,7 +43,11 @@ abstract class P2P_List {
33
  echo $before_list;
34
 
35
  if ( $separator ) {
36
- echo implode( $separator, array_map( array( $this, 'render_item' ), $this->items ) );
 
 
 
 
37
  } else {
38
  foreach ( $this->items as $item ) {
39
  echo $before_item . $this->render_item( $item ) . $after_item;
@@ -46,7 +60,9 @@ abstract class P2P_List {
46
  return ob_get_clean();
47
  }
48
 
49
- abstract protected function render_item( $item );
 
 
50
  }
51
 
52
 
@@ -54,26 +70,19 @@ class P2P_List_Post extends P2P_List {
54
 
55
  function __construct( $wp_query ) {
56
  if ( is_array( $wp_query ) ) {
57
- $this->items = $wp_query;
58
  } else {
59
- $this->items = $wp_query->posts;
60
  $this->current_page = max( 1, $wp_query->get('paged') );
61
  $this->total_pages = $wp_query->max_num_pages;
62
  }
63
- }
64
 
65
- protected function render_item( $post ) {
66
- return html( 'a', array( 'href' => get_permalink( $post ) ), get_the_title( $post ) );
67
  }
68
  }
69
 
70
 
71
- class P2P_List_Attachment extends P2P_List_Post {
72
-
73
- protected function render_item( $post ) {
74
- return html( 'a', array( 'href' => get_permalink( $post ) ), wp_get_attachment_image( $post->ID, 'thumbnail', false ) );
75
- }
76
- }
77
 
78
 
79
  class P2P_List_User extends P2P_List {
@@ -81,16 +90,12 @@ class P2P_List_User extends P2P_List {
81
  function __construct( $query ) {
82
  $qv = $query->query_vars;
83
 
84
- $this->items = $query->get_results();
85
-
86
  if ( isset( $qv['p2p:page'] ) ) {
87
  $this->current_page = $qv['p2p:page'];
88
  $this->total_pages = ceil( $query->get_total() / $qv['p2p:per_page'] );
89
  }
90
- }
91
 
92
- protected function render_item( $user ) {
93
- return html( 'a', array( 'href' => get_author_posts_url( $user->ID ) ), $user->display_name );
94
  }
95
  }
96
 
6
  public $current_page = 1;
7
  public $total_pages = 0;
8
 
9
+ function __construct( $items ) {
10
+ if ( is_numeric( reset( $items ) ) ) {
11
+ // Don't wrap when we just have a list of ids
12
+ $this->items = $items;
13
+ } else {
14
+ $class = str_replace( 'P2P_List', 'P2P_Item', get_class( $this ) );
15
+ $this->items = _p2p_wrap( $items, $class );
16
+ }
17
+ }
18
+
19
  function render( $args = array() ) {
20
  if ( empty( $this->items ) )
21
  return '';
43
  echo $before_list;
44
 
45
  if ( $separator ) {
46
+ $list = array();
47
+ foreach ( $this->items as $item ) {
48
+ $list[] = $this->render_item( $item->get_object() );
49
+ }
50
+ echo implode( $separator, $list );
51
  } else {
52
  foreach ( $this->items as $item ) {
53
  echo $before_item . $this->render_item( $item ) . $after_item;
60
  return ob_get_clean();
61
  }
62
 
63
+ protected function render_item( $item ) {
64
+ return html_link( $item->get_permalink(), $item->get_title() );
65
+ }
66
  }
67
 
68
 
70
 
71
  function __construct( $wp_query ) {
72
  if ( is_array( $wp_query ) ) {
73
+ $items = $wp_query;
74
  } else {
75
+ $items = $wp_query->posts;
76
  $this->current_page = max( 1, $wp_query->get('paged') );
77
  $this->total_pages = $wp_query->max_num_pages;
78
  }
 
79
 
80
+ parent::__construct( $items );
 
81
  }
82
  }
83
 
84
 
85
+ class P2P_List_Attachment extends P2P_List_Post {}
 
 
 
 
 
86
 
87
 
88
  class P2P_List_User extends P2P_List {
90
  function __construct( $query ) {
91
  $qv = $query->query_vars;
92
 
 
 
93
  if ( isset( $qv['p2p:page'] ) ) {
94
  $this->current_page = $qv['p2p:page'];
95
  $this->total_pages = ceil( $query->get_total() / $qv['p2p:per_page'] );
96
  }
 
97
 
98
+ parent::__construct( $query->get_results() );
 
99
  }
100
  }
101
 
core/query-post.php CHANGED
@@ -1,64 +1,63 @@
1
  <?php
2
 
3
- /**
4
- * Handles connected{_to|_from} query vars
5
- */
6
- class P2P_WP_Query {
7
 
8
  static function init() {
9
  add_action( 'parse_query', array( __CLASS__, 'parse_query' ), 20 );
10
- add_filter( 'posts_clauses', array( __CLASS__, 'posts_clauses' ), 10, 2 );
 
11
  add_filter( 'the_posts', array( __CLASS__, 'cache_p2p_meta' ), 20, 2 );
12
  }
13
 
14
  static function parse_query( $wp_query ) {
15
- $q =& $wp_query->query_vars;
16
 
17
- P2P_Query::expand_shortcut_qv( $q );
 
18
 
19
- if ( isset( $q['connected_items'] ) ) {
20
- $item = $q['connected_items'];
21
- } else {
22
- $item = isset( $q['post_type'] ) ? $q['post_type'] : 'post';
23
  }
24
 
25
- $r = P2P_Query::expand_connected_type( $q, $item, 'post' );
 
26
 
27
- if ( false === $r ) {
28
- $q = array( 'year' => 2525 );
29
- } elseif ( $r ) {
30
- $wp_query->is_home = false;
31
- $wp_query->is_archive = true;
32
- }
33
  }
34
 
35
  static function posts_clauses( $clauses, $wp_query ) {
36
  global $wpdb;
37
 
38
- $qv = P2P_Query::get_qv( $wp_query->query_vars );
39
-
40
- if ( !$qv )
41
  return $clauses;
42
 
43
- $wp_query->_p2p_cache = true;
 
 
 
 
 
 
 
 
 
44
 
45
- return P2P_Query::alter_clauses( $clauses, $qv, "$wpdb->posts.ID" );
46
  }
47
 
48
  /**
49
  * Pre-populates the p2p meta cache to decrease the number of queries.
50
  */
51
  static function cache_p2p_meta( $the_posts, $wp_query ) {
52
- if ( empty( $the_posts ) )
53
- return $the_posts;
54
-
55
- if ( isset( $wp_query->_p2p_cache ) ) {
56
  update_meta_cache( 'p2p', wp_list_pluck( $the_posts, 'p2p_id' ) );
57
- }
58
 
59
  return $the_posts;
60
  }
61
  }
62
 
63
- P2P_WP_Query::init();
64
 
1
  <?php
2
 
3
+ class P2P_Post_Query {
 
 
 
4
 
5
  static function init() {
6
  add_action( 'parse_query', array( __CLASS__, 'parse_query' ), 20 );
7
+ add_filter( 'posts_clauses', array( __CLASS__, 'posts_clauses' ), 20, 2 );
8
+ add_filter( 'posts_request', array( __CLASS__, 'capture' ), 999, 2 );
9
  add_filter( 'the_posts', array( __CLASS__, 'cache_p2p_meta' ), 20, 2 );
10
  }
11
 
12
  static function parse_query( $wp_query ) {
13
+ $p2p_q = P2P_Query::create_from_qv( $wp_query->query_vars, 'post' );
14
 
15
+ if ( is_wp_error( $p2p_q ) ) {
16
+ trigger_error( $p2p_q->get_error_message(), E_USER_WARNING );
17
 
18
+ $wp_query->set( 'year', 2525 );
19
+ return;
 
 
20
  }
21
 
22
+ if ( null === $p2p_q )
23
+ return;
24
 
25
+ $wp_query->_p2p_query = $p2p_q;
26
+
27
+ $wp_query->is_home = false;
28
+ $wp_query->is_archive = true;
 
 
29
  }
30
 
31
  static function posts_clauses( $clauses, $wp_query ) {
32
  global $wpdb;
33
 
34
+ if ( !isset( $wp_query->_p2p_query ) )
 
 
35
  return $clauses;
36
 
37
+ return $wp_query->_p2p_query->alter_clauses( $clauses, "$wpdb->posts.ID" );
38
+ }
39
+
40
+ static function capture( $request, $wp_query ) {
41
+ global $wpdb;
42
+
43
+ if ( !isset( $wp_query->_p2p_capture ) )
44
+ return $request;
45
+
46
+ $wp_query->_p2p_sql = $request;
47
 
48
+ return '';
49
  }
50
 
51
  /**
52
  * Pre-populates the p2p meta cache to decrease the number of queries.
53
  */
54
  static function cache_p2p_meta( $the_posts, $wp_query ) {
55
+ if ( isset( $wp_query->_p2p_query ) && !empty( $the_posts ) )
 
 
 
56
  update_meta_cache( 'p2p', wp_list_pluck( $the_posts, 'p2p_id' ) );
 
57
 
58
  return $the_posts;
59
  }
60
  }
61
 
62
+ P2P_Post_Query::init();
63
 
core/query-user.php CHANGED
@@ -9,28 +9,16 @@ class P2P_User_Query {
9
  static function pre_user_query( $query ) {
10
  global $wpdb;
11
 
12
- $q =& $query->query_vars;
13
 
14
- P2P_Query::expand_shortcut_qv( $q );
 
15
 
16
- if ( isset( $q['connected_items'] ) ) {
17
- $item = $q['connected_items'];
18
- } else {
19
- $item = 'any';
20
- }
21
-
22
- $r = P2P_Query::expand_connected_type( $q, $item, 'user' );
23
-
24
- if ( false === $r ) {
25
  $query->query_where = " AND 1=0";
26
  return;
27
  }
28
 
29
- // alter query
30
-
31
- $qv = P2P_Query::get_qv( $q );
32
-
33
- if ( !$qv )
34
  return;
35
 
36
  $map = array(
@@ -45,13 +33,13 @@ class P2P_User_Query {
45
  foreach ( $map as $clause => $key )
46
  $clauses[$clause] = $query->$key;
47
 
48
- $clauses = P2P_Query::alter_clauses( $clauses, $qv, "$wpdb->users.ID" );
49
 
50
  if ( 0 !== strpos( $clauses['orderby'], 'ORDER BY ' ) )
51
  $clauses['orderby'] = 'ORDER BY ' . $clauses['orderby'];
52
 
53
  foreach ( $map as $clause => $key )
54
- $query->$key = $clauses[$clause];
55
  }
56
  }
57
 
9
  static function pre_user_query( $query ) {
10
  global $wpdb;
11
 
12
+ $p2p_q = P2P_Query::create_from_qv( $query->query_vars, 'user' );
13
 
14
+ if ( is_wp_error( $p2p_q ) ) {
15
+ trigger_error( $p2p_q->get_error_message(), E_USER_WARNING );
16
 
 
 
 
 
 
 
 
 
 
17
  $query->query_where = " AND 1=0";
18
  return;
19
  }
20
 
21
+ if ( null === $p2p_q )
 
 
 
 
22
  return;
23
 
24
  $map = array(
33
  foreach ( $map as $clause => $key )
34
  $clauses[$clause] = $query->$key;
35
 
36
+ $clauses = $p2p_q->alter_clauses( $clauses, "$wpdb->users.ID" );
37
 
38
  if ( 0 !== strpos( $clauses['orderby'], 'ORDER BY ' ) )
39
  $clauses['orderby'] = 'ORDER BY ' . $clauses['orderby'];
40
 
41
  foreach ( $map as $clause => $key )
42
+ $query->$key = $clauses[ $clause ];
43
  }
44
  }
45
 
core/query.php CHANGED
@@ -2,54 +2,39 @@
2
 
3
  class P2P_Query {
4
 
5
- static function expand_shortcut_qv( &$q ) {
6
- $qv_map = array(
 
 
 
 
 
 
 
 
 
 
 
 
7
  'connected' => 'any',
8
  'connected_to' => 'to',
9
  'connected_from' => 'from',
10
  );
11
 
12
- foreach ( $qv_map as $key => $direction ) {
13
  if ( !empty( $q[ $key ] ) ) {
14
  $q['connected_items'] = _p2p_pluck( $q, $key );
15
  $q['connected_direction'] = $direction;
16
  }
17
  }
18
- }
19
 
20
- static function get_qv( $q ) {
21
- if ( !isset( $q['p2p_type'] ) ) {
22
  if ( isset( $q['connected_items'] ) ) {
23
- trigger_error( "P2P queries without 'connected_type' are no longer supported." );
24
  }
25
- return false;
26
- }
27
-
28
- $qv['p2p_type'] = $q['p2p_type'];
29
 
30
- $qv_list = array(
31
- 'items', 'direction', 'meta',
32
- 'orderby', 'order_num', 'order'
33
- );
34
-
35
- foreach ( $qv_list as $key ) {
36
- $qv[$key] = isset( $q["connected_$key"] ) ? $q["connected_$key"] : false;
37
- }
38
-
39
- return $qv;
40
- }
41
-
42
- /**
43
- * Sets 'p2p_type' => array( connection_type => direction )
44
- *
45
- * @return:
46
- * null means ignore current query
47
- * false means trigger 404
48
- * true means proceed
49
- */
50
- static function expand_connected_type( &$q, $item, $object_type ) {
51
- if ( !isset( $q['connected_type'] ) )
52
  return;
 
53
 
54
  $ctypes = (array) _p2p_pluck( $q, 'connected_type' );
55
 
@@ -58,6 +43,8 @@ class P2P_Query {
58
  else
59
  $directions = array();
60
 
 
 
61
  $p2p_types = array();
62
 
63
  foreach ( $ctypes as $i => $p2p_type ) {
@@ -69,51 +56,91 @@ class P2P_Query {
69
  if ( isset( $directions[$i] ) ) {
70
  $directed = $ctype->set_direction( $directions[$i] );
71
  } else {
72
- $directed = self::find_direction( $ctype, $item, $object_type );
73
  }
74
 
75
  if ( !$directed )
76
  continue;
77
 
78
- $p2p_types[ $p2p_type ] = $directed->get_direction();
79
  }
80
 
81
  if ( empty( $p2p_types ) )
82
- return false;
83
 
84
- if ( 1 == count( $p2p_types ) )
85
- $q = $directed->get_connected_args( $q );
86
- else
87
- $q['p2p_type'] = $p2p_types;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
90
  }
91
 
92
- static function alter_clauses( $clauses, $q, $main_id_column ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  global $wpdb;
94
 
95
  $clauses['fields'] .= ", $wpdb->p2p.*";
96
 
97
  $clauses['join'] .= " INNER JOIN $wpdb->p2p";
98
 
99
- // Handle main query
100
- if ( 'any' == $q['items'] ) {
101
- $search = false;
102
- } else {
103
- $search = implode( ',', array_map( 'absint', _p2p_normalize( $q['items'] ) ) );
104
- }
105
-
106
  $where_parts = array();
107
 
108
- foreach ( $q['p2p_type'] as $p2p_type => $direction ) {
109
- if ( 0 === $p2p_type ) // used by migration script
110
- $part = "1 = 1";
111
- else
112
- $part = $wpdb->prepare( "$wpdb->p2p.p2p_type = %s", $p2p_type );
113
 
114
  $fields = array( 'p2p_from', 'p2p_to' );
115
 
116
- switch ( $direction ) {
117
 
118
  case 'from':
119
  $fields = array_reverse( $fields );
@@ -121,22 +148,20 @@ class P2P_Query {
121
  case 'to':
122
  list( $from, $to ) = $fields;
123
 
124
- $part .= " AND $main_id_column = $wpdb->p2p.$from";
125
 
126
- if ( $search ) {
127
- $part .= " AND $wpdb->p2p.$to IN ($search)";
128
- }
129
 
130
  break;
131
  default:
132
- if ( $search ) {
133
- $part .= " AND (
134
- ($main_id_column = $wpdb->p2p.p2p_to AND $wpdb->p2p.p2p_from IN ($search)) OR
135
- ($main_id_column = $wpdb->p2p.p2p_from AND $wpdb->p2p.p2p_to IN ($search))
136
- )";
137
- } else {
138
- $part .= " AND ($main_id_column = $wpdb->p2p.p2p_to OR $main_id_column = $wpdb->p2p.p2p_from)";
139
- }
140
  }
141
 
142
  $where_parts[] = '(' . $part . ')';
@@ -144,30 +169,30 @@ class P2P_Query {
144
 
145
  if ( 1 == count( $where_parts ) )
146
  $clauses['where'] .= " AND " . $where_parts[0];
147
- else
148
  $clauses['where'] .= " AND (" . implode( ' OR ', $where_parts ) . ")";
149
 
150
  // Handle custom fields
151
- if ( !empty( $q['meta'] ) ) {
152
- $meta_clauses = _p2p_meta_sql_helper( $q['meta'] );
153
  foreach ( $meta_clauses as $key => $value ) {
154
  $clauses[ $key ] .= $value;
155
  }
156
  }
157
 
158
  // Handle ordering
159
- if ( $q['orderby'] ) {
160
  $clauses['join'] .= $wpdb->prepare( "
161
  LEFT JOIN $wpdb->p2pmeta AS p2pm_order ON (
162
  $wpdb->p2p.p2p_id = p2pm_order.p2p_id AND p2pm_order.meta_key = %s
163
  )
164
- ", $q['orderby'] );
165
 
166
- $order = ( 'DESC' == strtoupper( $q['order'] ) ) ? 'DESC' : 'ASC';
167
 
168
  $field = 'meta_value';
169
 
170
- if ( $q['order_num'] )
171
  $field .= '+0';
172
 
173
  $clauses['orderby'] = "p2pm_order.$field $order";
@@ -175,30 +200,5 @@ class P2P_Query {
175
 
176
  return $clauses;
177
  }
178
-
179
- private function find_direction( $ctype, $arg, $object_type ) {
180
- $opposite_side = self::choose_side( $object_type,
181
- $ctype->object['from'],
182
- $ctype->object['to']
183
- );
184
-
185
- if ( in_array( $opposite_side, array( 'from', 'to' ) ) )
186
- return $ctype->set_direction( $opposite_side );
187
-
188
- return $ctype->find_direction( $arg );
189
- }
190
-
191
- private static function choose_side( $current, $from, $to ) {
192
- if ( $from == $to && $current == $from )
193
- return 'any';
194
-
195
- if ( $current == $from )
196
- return 'to';
197
-
198
- if ( $current == $to )
199
- return 'from';
200
-
201
- return false;
202
- }
203
  }
204
 
2
 
3
  class P2P_Query {
4
 
5
+ protected $ctypes, $items, $query, $meta;
6
+ protected $orderby, $order, $order_num;
7
+
8
+ /**
9
+ * Create instance from mixed query vars
10
+ *
11
+ * @param array Query vars to collect parameters from
12
+ * @return:
13
+ * - null means ignore current query
14
+ * - WP_Error instance if the query is invalid
15
+ * - P2P_Query instance on success
16
+ */
17
+ public static function create_from_qv( &$q, $object_type ) {
18
+ $shortcuts = array(
19
  'connected' => 'any',
20
  'connected_to' => 'to',
21
  'connected_from' => 'from',
22
  );
23
 
24
+ foreach ( $shortcuts as $key => $direction ) {
25
  if ( !empty( $q[ $key ] ) ) {
26
  $q['connected_items'] = _p2p_pluck( $q, $key );
27
  $q['connected_direction'] = $direction;
28
  }
29
  }
 
30
 
31
+ if ( !isset( $q['connected_type'] ) ) {
 
32
  if ( isset( $q['connected_items'] ) ) {
33
+ return new WP_Error( 'no_connection_type', "Queries without 'connected_type' are no longer supported." );
34
  }
 
 
 
 
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  return;
37
+ }
38
 
39
  $ctypes = (array) _p2p_pluck( $q, 'connected_type' );
40
 
43
  else
44
  $directions = array();
45
 
46
+ $item = isset( $q['connected_items'] ) ? $q['connected_items'] : 'any';
47
+
48
  $p2p_types = array();
49
 
50
  foreach ( $ctypes as $i => $p2p_type ) {
56
  if ( isset( $directions[$i] ) ) {
57
  $directed = $ctype->set_direction( $directions[$i] );
58
  } else {
59
+ $directed = $ctype->find_direction( $item, true, $object_type );
60
  }
61
 
62
  if ( !$directed )
63
  continue;
64
 
65
+ $p2p_types[] = $directed;
66
  }
67
 
68
  if ( empty( $p2p_types ) )
69
+ return new WP_Error( 'no_direction', "Could not find direction(s)." );
70
 
71
+ if ( 1 == count( $p2p_types ) ) {
72
+ $directed = $p2p_types[0];
73
+
74
+ if ( $orderby_key = $directed->get_orderby_key() ) {
75
+ $q = wp_parse_args( $q, array(
76
+ 'connected_orderby' => $orderby_key,
77
+ 'connected_order' => 'ASC',
78
+ 'connected_order_num' => true,
79
+ ) );
80
+ }
81
+
82
+ $q = array_merge_recursive( $q, array(
83
+ 'connected_meta' => $directed->data
84
+ ) );
85
+
86
+ $q = $directed->get_opposite( 'side' )->get_base_qv( $q );
87
+
88
+ $q = apply_filters( 'p2p_connected_args', $q, $directed, $item );
89
+ }
90
+
91
+ $p2p_q = new P2P_Query;
92
+
93
+ $p2p_q->ctypes = $p2p_types;
94
+ $p2p_q->items = $item;
95
 
96
+ foreach ( array( 'meta', 'orderby', 'order_num', 'order' ) as $key ) {
97
+ $p2p_q->$key = isset( $q["connected_$key"] ) ? $q["connected_$key"] : false;
98
+ }
99
+
100
+ $p2p_q->query = isset( $q['connected_query'] ) ? $q['connected_query'] : array();
101
+
102
+ return $p2p_q;
103
+ }
104
+
105
+ protected function __construct() {}
106
+
107
+ public function __get( $key ) {
108
+ return $this->$key;
109
  }
110
 
111
+ private function do_other_query( $side ) {
112
+ $qv = array_merge( $this->query, array(
113
+ 'fields' => 'ids',
114
+ 'p2p:per_page' => -1
115
+ ) );
116
+
117
+ if ( 'any' != $this->items )
118
+ $qv['p2p:include'] = _p2p_normalize( $this->items );
119
+
120
+ return $side->capture_query( $side->get_base_qv( $side->translate_qv( $qv ) ) );
121
+ }
122
+
123
+ /**
124
+ * For low-level query modifications
125
+ */
126
+ public function alter_clauses( &$clauses, $main_id_column ) {
127
  global $wpdb;
128
 
129
  $clauses['fields'] .= ", $wpdb->p2p.*";
130
 
131
  $clauses['join'] .= " INNER JOIN $wpdb->p2p";
132
 
 
 
 
 
 
 
 
133
  $where_parts = array();
134
 
135
+ foreach ( $this->ctypes as $directed ) {
136
+ if ( null === $directed ) // used by migration script
137
+ continue;
138
+
139
+ $part = $wpdb->prepare( "$wpdb->p2p.p2p_type = %s", $directed->name );
140
 
141
  $fields = array( 'p2p_from', 'p2p_to' );
142
 
143
+ switch ( $directed->get_direction() ) {
144
 
145
  case 'from':
146
  $fields = array_reverse( $fields );
148
  case 'to':
149
  list( $from, $to ) = $fields;
150
 
151
+ $search = $this->do_other_query( $directed->get_current( 'side' ) );
152
 
153
+ $part .= " AND $main_id_column = $wpdb->p2p.$from";
154
+ $part .= " AND $wpdb->p2p.$to IN ($search)";
 
155
 
156
  break;
157
  default:
158
+ $part .= sprintf ( " AND (
159
+ ($main_id_column = $wpdb->p2p.p2p_to AND $wpdb->p2p.p2p_from IN (%s)) OR
160
+ ($main_id_column = $wpdb->p2p.p2p_from AND $wpdb->p2p.p2p_to IN (%s))
161
+ )",
162
+ $this->do_other_query( $directed->get_current( 'side' ) ),
163
+ $this->do_other_query( $directed->get_opposite( 'side' ) )
164
+ );
 
165
  }
166
 
167
  $where_parts[] = '(' . $part . ')';
169
 
170
  if ( 1 == count( $where_parts ) )
171
  $clauses['where'] .= " AND " . $where_parts[0];
172
+ elseif ( !empty( $where_parts ) )
173
  $clauses['where'] .= " AND (" . implode( ' OR ', $where_parts ) . ")";
174
 
175
  // Handle custom fields
176
+ if ( !empty( $this->meta ) ) {
177
+ $meta_clauses = _p2p_meta_sql_helper( $this->meta );
178
  foreach ( $meta_clauses as $key => $value ) {
179
  $clauses[ $key ] .= $value;
180
  }
181
  }
182
 
183
  // Handle ordering
184
+ if ( $this->orderby ) {
185
  $clauses['join'] .= $wpdb->prepare( "
186
  LEFT JOIN $wpdb->p2pmeta AS p2pm_order ON (
187
  $wpdb->p2p.p2p_id = p2pm_order.p2p_id AND p2pm_order.meta_key = %s
188
  )
189
+ ", $this->orderby );
190
 
191
+ $order = ( 'DESC' == strtoupper( $this->order ) ) ? 'DESC' : 'ASC';
192
 
193
  $field = 'meta_value';
194
 
195
+ if ( $this->order_num )
196
  $field .= '+0';
197
 
198
  $clauses['orderby'] = "p2pm_order.$field $order";
200
 
201
  return $clauses;
202
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  }
204
 
core/side.php CHANGED
@@ -2,35 +2,59 @@
2
 
3
  abstract class P2P_Side {
4
 
5
- public $query_vars;
 
 
6
 
7
- function __construct( $query_vars ) {
8
- $this->query_vars = $query_vars;
9
- }
10
 
11
- function get_base_qv( $q ) {
12
- return array_merge( $this->query_vars, $q );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  }
14
  }
15
 
16
 
17
  class P2P_Side_Post extends P2P_Side {
18
 
19
- public $post_type = array();
20
 
21
  function __construct( $query_vars ) {
22
- parent::__construct( $query_vars );
 
23
 
24
- $this->post_type = $this->query_vars['post_type'];
 
25
  }
26
 
27
  private function get_ptype() {
28
- return get_post_type_object( $this->post_type[0] );
29
  }
30
 
31
  function get_base_qv( $q ) {
32
  if ( isset( $q['post_type'] ) && 'any' != $q['post_type'] ) {
33
- $common = array_intersect( $this->post_type, (array) $q['post_type'] );
34
 
35
  if ( !$common )
36
  unset( $q['post_type'] );
@@ -43,7 +67,7 @@ class P2P_Side_Post extends P2P_Side {
43
  }
44
 
45
  function get_desc() {
46
- return implode( ', ', array_map( array( $this, 'post_type_label' ), $this->post_type ) );
47
  }
48
 
49
  private function post_type_label( $post_type ) {
@@ -56,19 +80,39 @@ class P2P_Side_Post extends P2P_Side {
56
  }
57
 
58
  function get_labels() {
59
- return $this->get_ptype()->labels;
60
  }
61
 
62
- function check_capability() {
63
  return current_user_can( $this->get_ptype()->cap->edit_posts );
64
  }
65
 
 
 
 
 
 
 
 
 
 
 
66
  function do_query( $args ) {
67
  return new WP_Query( $args );
68
  }
69
 
 
 
 
 
 
 
 
 
 
70
  function translate_qv( $qv ) {
71
  $map = array(
 
72
  'exclude' => 'post__not_in',
73
  'search' => 's',
74
  'page' => 'paged',
@@ -82,43 +126,68 @@ class P2P_Side_Post extends P2P_Side {
82
  return $qv;
83
  }
84
 
 
 
 
 
 
 
 
 
 
85
  function item_recognize( $arg ) {
86
- if ( is_object( $arg ) ) {
87
- if ( !isset( $arg->post_type ) )
 
 
88
  return false;
89
- $post_type = $arg->post_type;
90
- } elseif ( $post_id = (int) $arg ) {
91
- $post_type = get_post_type( $post_id );
92
- } else {
93
- $post_type = $arg;
94
  }
95
 
96
- if ( !post_type_exists( $post_type ) )
 
 
 
 
97
  return false;
98
 
99
- return in_array( $post_type, $this->post_type );
100
  }
101
 
102
- function item_id( $arg ) {
103
  $post = get_post( $arg );
104
- if ( $post )
105
- return $post->ID;
106
 
107
- return false;
 
 
 
 
 
 
108
  }
109
 
110
- function item_title( $item ) {
111
- return $item->post_title;
 
 
 
112
  }
113
  }
114
 
115
 
116
  class P2P_Side_Attachment extends P2P_Side_Post {
117
 
 
 
118
  function __construct( $query_vars ) {
119
- P2P_Side::__construct( $query_vars );
120
 
121
- $this->post_type = array( 'attachment' );
 
 
 
 
122
  }
123
 
124
  function get_base_qv( $q ) {
@@ -131,6 +200,12 @@ class P2P_Side_Attachment extends P2P_Side_Post {
131
 
132
  class P2P_Side_User extends P2P_Side {
133
 
 
 
 
 
 
 
134
  function get_desc() {
135
  return __( 'Users', P2P_TEXTDOMAIN );
136
  }
@@ -140,22 +215,60 @@ class P2P_Side_User extends P2P_Side {
140
  }
141
 
142
  function get_labels() {
143
- return (object) array(
144
  'singular_name' => __( 'User', P2P_TEXTDOMAIN ),
145
  'search_items' => __( 'Search Users', P2P_TEXTDOMAIN ),
146
  'not_found' => __( 'No users found.', P2P_TEXTDOMAIN ),
147
  );
148
  }
149
 
150
- function check_capability() {
151
  return current_user_can( 'list_users' );
152
  }
153
 
 
 
 
 
154
  function do_query( $args ) {
155
  return new WP_User_Query( $args );
156
  }
157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  function translate_qv( $qv ) {
 
 
 
159
  if ( isset( $qv['p2p:exclude'] ) )
160
  $qv['exclude'] = _p2p_pluck( $qv, 'p2p:exclude' );
161
 
@@ -170,23 +283,19 @@ class P2P_Side_User extends P2P_Side {
170
  return $qv;
171
  }
172
 
173
- function item_recognize( $arg ) {
174
- return is_a( $arg, 'WP_User' );
175
  }
176
 
177
- function item_id( $arg ) {
178
- if ( $this->item_recognize( $arg ) )
179
- return $arg->ID;
180
-
181
- $user = get_user_by( 'id', $arg );
182
- if ( $user )
183
- return $user->ID;
184
-
185
- return false;
186
  }
187
 
188
- function item_title( $item ) {
189
- return $item->display_name;
 
 
 
190
  }
191
  }
192
 
2
 
3
  abstract class P2P_Side {
4
 
5
+ abstract function get_title();
6
+ abstract function get_desc();
7
+ abstract function get_labels();
8
 
9
+ abstract function can_edit_connections();
10
+ abstract function can_create_item();
 
11
 
12
+ abstract function get_base_qv( $q );
13
+ abstract function translate_qv( $qv );
14
+ abstract function do_query( $args );
15
+ abstract function capture_query( $args );
16
+
17
+ abstract function is_indeterminate( $side );
18
+
19
+ protected $item_type;
20
+
21
+ function item_recognize( $arg ) {
22
+ $class = $this->item_type;
23
+
24
+ if ( is_a( $arg, $class ) )
25
+ return $arg;
26
+
27
+ if ( is_a( $arg, 'P2P_Item' ) )
28
+ return false;
29
+
30
+ $raw_item = $this->recognize( $arg );
31
+ if ( !$raw_item )
32
+ return false;
33
+
34
+ return new $class( $raw_item );
35
  }
36
  }
37
 
38
 
39
  class P2P_Side_Post extends P2P_Side {
40
 
41
+ protected $item_type = 'P2P_Item_Post';
42
 
43
  function __construct( $query_vars ) {
44
+ $this->query_vars = $query_vars;
45
+ }
46
 
47
+ public function first_post_type() {
48
+ return $this->query_vars['post_type'][0];
49
  }
50
 
51
  private function get_ptype() {
52
+ return get_post_type_object( $this->first_post_type() );
53
  }
54
 
55
  function get_base_qv( $q ) {
56
  if ( isset( $q['post_type'] ) && 'any' != $q['post_type'] ) {
57
+ $common = array_intersect( $this->query_vars['post_type'], (array) $q['post_type'] );
58
 
59
  if ( !$common )
60
  unset( $q['post_type'] );
67
  }
68
 
69
  function get_desc() {
70
+ return implode( ', ', array_map( array( $this, 'post_type_label' ), $this->query_vars['post_type'] ) );
71
  }
72
 
73
  private function post_type_label( $post_type ) {
80
  }
81
 
82
  function get_labels() {
83
+ return get_object_vars( $this->get_ptype()->labels );
84
  }
85
 
86
+ function can_edit_connections() {
87
  return current_user_can( $this->get_ptype()->cap->edit_posts );
88
  }
89
 
90
+ function can_create_item() {
91
+ if ( count( $this->query_vars['post_type'] ) > 1 )
92
+ return false;
93
+
94
+ if ( count( $this->query_vars ) > 1 )
95
+ return false;
96
+
97
+ return true;
98
+ }
99
+
100
  function do_query( $args ) {
101
  return new WP_Query( $args );
102
  }
103
 
104
+ function capture_query( $args ) {
105
+ $q = new WP_Query;
106
+ $q->_p2p_capture = true;
107
+
108
+ $q->query( $args );
109
+
110
+ return $q->_p2p_sql;
111
+ }
112
+
113
  function translate_qv( $qv ) {
114
  $map = array(
115
+ 'include' => 'post__in',
116
  'exclude' => 'post__not_in',
117
  'search' => 's',
118
  'page' => 'paged',
126
  return $qv;
127
  }
128
 
129
+ function is_indeterminate( $side ) {
130
+ $common = array_intersect(
131
+ $this->query_vars['post_type'],
132
+ $side->query_vars['post_type']
133
+ );
134
+
135
+ return !empty( $common );
136
+ }
137
+
138
  function item_recognize( $arg ) {
139
+ $class = $this->item_type;
140
+
141
+ if ( is_a( $arg, $class ) ) {
142
+ if ( !$this->recognize_post_type( $arg->post_type ) )
143
  return false;
144
+
145
+ return $arg;
 
 
 
146
  }
147
 
148
+ if ( is_a( $arg, 'P2P_Item' ) )
149
+ return false;
150
+
151
+ $raw_item = $this->recognize( $arg );
152
+ if ( !$raw_item )
153
  return false;
154
 
155
+ return new $class( $raw_item );
156
  }
157
 
158
+ protected function recognize( $arg ) {
159
  $post = get_post( $arg );
 
 
160
 
161
+ if ( !is_object( $post ) )
162
+ return false;
163
+
164
+ if ( !$this->recognize_post_type( $post->post_type ) )
165
+ return false;
166
+
167
+ return $post;
168
  }
169
 
170
+ public function recognize_post_type( $post_type ) {
171
+ if ( !post_type_exists( $post_type ) )
172
+ return false;
173
+
174
+ return in_array( $post_type, $this->query_vars['post_type'] );
175
  }
176
  }
177
 
178
 
179
  class P2P_Side_Attachment extends P2P_Side_Post {
180
 
181
+ protected $item_type = 'P2P_Item_Attachment';
182
+
183
  function __construct( $query_vars ) {
184
+ $this->query_vars = $query_vars;
185
 
186
+ $this->query_vars['post_type'] = array( 'attachment' );
187
+ }
188
+
189
+ function can_create_item() {
190
+ return false;
191
  }
192
 
193
  function get_base_qv( $q ) {
200
 
201
  class P2P_Side_User extends P2P_Side {
202
 
203
+ protected $item_type = 'P2P_Item_User';
204
+
205
+ function __construct( $query_vars ) {
206
+ $this->query_vars = $query_vars;
207
+ }
208
+
209
  function get_desc() {
210
  return __( 'Users', P2P_TEXTDOMAIN );
211
  }
215
  }
216
 
217
  function get_labels() {
218
+ return array(
219
  'singular_name' => __( 'User', P2P_TEXTDOMAIN ),
220
  'search_items' => __( 'Search Users', P2P_TEXTDOMAIN ),
221
  'not_found' => __( 'No users found.', P2P_TEXTDOMAIN ),
222
  );
223
  }
224
 
225
+ function can_edit_connections() {
226
  return current_user_can( 'list_users' );
227
  }
228
 
229
+ function can_create_item() {
230
+ return false;
231
+ }
232
+
233
  function do_query( $args ) {
234
  return new WP_User_Query( $args );
235
  }
236
 
237
+ function capture_query( $args ) {
238
+ $args['count_total'] = false;
239
+
240
+ $uq = new WP_User_Query;
241
+ $uq->_p2p_capture = true; // needed by P2P_URL_Query
242
+
243
+ // see http://core.trac.wordpress.org/ticket/21119
244
+ $uq->query_vars = wp_parse_args( $args, array(
245
+ 'blog_id' => $GLOBALS['blog_id'],
246
+ 'role' => '',
247
+ 'meta_key' => '',
248
+ 'meta_value' => '',
249
+ 'meta_compare' => '',
250
+ 'include' => array(),
251
+ 'exclude' => array(),
252
+ 'search' => '',
253
+ 'search_columns' => array(),
254
+ 'orderby' => 'login',
255
+ 'order' => 'ASC',
256
+ 'offset' => '',
257
+ 'number' => '',
258
+ 'count_total' => true,
259
+ 'fields' => 'all',
260
+ 'who' => ''
261
+ ) );
262
+
263
+ $uq->prepare_query();
264
+
265
+ return "SELECT $uq->query_fields $uq->query_from $uq->query_where $uq->query_orderby $uq->query_limit";
266
+ }
267
+
268
  function translate_qv( $qv ) {
269
+ if ( isset( $qv['p2p:include'] ) )
270
+ $qv['include'] = _p2p_pluck( $qv, 'p2p:include' );
271
+
272
  if ( isset( $qv['p2p:exclude'] ) )
273
  $qv['exclude'] = _p2p_pluck( $qv, 'p2p:exclude' );
274
 
283
  return $qv;
284
  }
285
 
286
+ function is_indeterminate( $side ) {
287
+ return true;
288
  }
289
 
290
+ function get_base_qv( $q ) {
291
+ return array_merge( $this->query_vars, $q );
 
 
 
 
 
 
 
292
  }
293
 
294
+ protected function recognize( $arg ) {
295
+ if ( is_a( $arg, 'WP_User' ) )
296
+ return $arg;
297
+
298
+ return get_user_by( 'id', $arg );
299
  }
300
  }
301
 
core/storage.php CHANGED
@@ -38,36 +38,6 @@ class P2P_Storage {
38
  " );
39
  }
40
 
41
- static function upgrade() {
42
- global $wpdb;
43
-
44
- $n = 0;
45
-
46
- foreach ( P2P_Connection_Type_Factory::get_all_instances() as $p2p_type => $ctype ) {
47
- if ( ! $ctype instanceof P2P_Connection_Type )
48
- continue;
49
-
50
- $args = $ctype->set_direction( 'any' )->get_connected_args( array(
51
- 'connected_items' => 'any',
52
- 'cache_results' => false,
53
- 'post_status' => 'any',
54
- 'nopaging' => true
55
- ) );
56
-
57
- $args['p2p_type'] = array( 0 => 'any' );
58
-
59
- foreach ( get_posts( $args ) as $post ) {
60
- // some connections might be ambiguous, spanning multiple connection types; first one wins
61
- if ( $post->p2p_type )
62
- continue;
63
-
64
- $n += $wpdb->update( $wpdb->p2p, compact( 'p2p_type' ), array( 'p2p_id' => $post->p2p_id ) );
65
- }
66
- }
67
-
68
- return $n;
69
- }
70
-
71
  static function uninstall() {
72
  scb_uninstall_table( 'p2p' );
73
  scb_uninstall_table( 'p2pmeta' );
38
  " );
39
  }
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  static function uninstall() {
42
  scb_uninstall_table( 'p2p' );
43
  scb_uninstall_table( 'p2pmeta' );
core/type.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  class P2P_Connection_Type {
4
 
5
- public $indeterminate = false;
6
 
7
  public $object;
8
 
@@ -24,12 +24,7 @@ class P2P_Connection_Type {
24
  }
25
 
26
  if ( $this->object['from'] == $this->object['to'] ) {
27
- if ( 'post' == $this->object['to'] ) {
28
- $common = array_intersect( $this->side['from']->post_type, $this->side['to']->post_type );
29
-
30
- if ( !empty( $common ) )
31
- $this->indeterminate = true;
32
- }
33
  } else {
34
  $args['self_connections'] = true;
35
  }
@@ -80,14 +75,12 @@ class P2P_Connection_Type {
80
 
81
  private function set_labels( &$args ) {
82
  foreach ( array( 'from', 'to' ) as $key ) {
83
- $labels = _p2p_pluck( $args, $key . '_labels' );
 
84
 
85
- if ( empty( $labels ) )
86
- $labels = $this->side[ $key ]->get_labels();
87
- else
88
- $labels = (object) $labels;
89
 
90
- $this->labels[ $key ] = $labels;
91
  }
92
  }
93
 
@@ -115,6 +108,12 @@ class P2P_Connection_Type {
115
  }
116
 
117
  public function __call( $method, $args ) {
 
 
 
 
 
 
118
  $directed = $this->find_direction( $args[0] );
119
  if ( !$directed ) {
120
  trigger_error( sprintf( "Can't determine direction for '%s' type.", $this->name ), E_USER_WARNING );
@@ -146,48 +145,91 @@ class P2P_Connection_Type {
146
  *
147
  * @param mixed A post type, object or object id.
148
  * @param bool Whether to return an instance of P2P_Directed_Connection_Type or just the direction
 
149
  *
150
  * @return bool|object|string False on failure, P2P_Directed_Connection_Type instance or direction on success.
151
  */
152
- public function find_direction( $arg, $instantiate = true ) {
153
  if ( is_array( $arg ) )
154
  $arg = reset( $arg );
155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  foreach ( array( 'from', 'to' ) as $direction ) {
157
- if ( !$this->side[ $direction ]->item_recognize( $arg ) )
 
 
158
  continue;
159
 
160
  if ( $this->indeterminate )
161
  $direction = $this->reciprocal ? 'any' : 'from';
162
 
163
- return $this->set_direction( $direction, $instantiate );
164
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
 
166
  return false;
167
  }
168
 
169
- // Used in each_connected()
170
- private function find_direction_multiple( $post_types ) {
171
- $possible_directions = array();
172
-
173
  foreach ( array( 'from', 'to' ) as $direction ) {
174
- if ( 'post' == $this->object[$direction] ) {
175
- foreach ( $post_types as $post_type ) {
176
- if ( !$this->side[ $direction ]->item_recognize( $post_type ) ) {
177
- $possible_directions[] = $direction;
178
- break;
179
- }
180
- }
181
- }
182
  }
183
 
184
- if ( empty( $possible_directions ) )
 
 
 
 
185
  return false;
186
 
187
- if ( count( $possible_directions ) > 1 )
188
- return 'any';
 
 
 
 
 
 
 
 
189
 
190
- return reset( $possible_directions );
191
  }
192
 
193
  /** Alias for get_prev() */
@@ -277,12 +319,10 @@ class P2P_Connection_Type {
277
  $post_types = array_unique( wp_list_pluck( $items, 'post_type' ) );
278
 
279
  if ( count( $post_types ) > 1 ) {
280
- $direction = $this->find_direction_multiple( $post_types );
281
  $extra_qv['post_type'] = 'any';
282
- } else {
283
- $direction = $this->find_direction( $post_types[0], false );
284
  }
285
 
 
286
  if ( !$direction )
287
  return false;
288
 
@@ -301,6 +341,24 @@ class P2P_Connection_Type {
301
  p2p_distribute_connected( $items, $q->items, $prop_name );
302
  }
303
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  public function get_desc() {
305
  foreach ( array( 'from', 'to' ) as $key ) {
306
  $$key = $this->side[ $key ]->get_desc();
2
 
3
  class P2P_Connection_Type {
4
 
5
+ public $indeterminate;
6
 
7
  public $object;
8
 
24
  }
25
 
26
  if ( $this->object['from'] == $this->object['to'] ) {
27
+ $this->indeterminate = $this->side['from']->is_indeterminate( $this->side['to'] );
 
 
 
 
 
28
  } else {
29
  $args['self_connections'] = true;
30
  }
75
 
76
  private function set_labels( &$args ) {
77
  foreach ( array( 'from', 'to' ) as $key ) {
78
+ $labels = $this->side[ $key ]->get_labels();
79
+ $labels['create'] = __( 'Create connections', P2P_TEXTDOMAIN );
80
 
81
+ _p2p_append( $labels, (array) _p2p_pluck( $args, $key . '_labels' ) );
 
 
 
82
 
83
+ $this->labels[ $key ] = (object) $labels;
84
  }
85
  }
86
 
108
  }
109
 
110
  public function __call( $method, $args ) {
111
+ if ( ! method_exists( 'P2P_Directed_Connection_Type', $method ) ) {
112
+ trigger_error( "Method '$method' does not exist.", E_USER_ERROR );
113
+ return;
114
+ }
115
+
116
+ // TODO: make find_direction() return the normalized item and pass that along
117
  $directed = $this->find_direction( $args[0] );
118
  if ( !$directed ) {
119
  trigger_error( sprintf( "Can't determine direction for '%s' type.", $this->name ), E_USER_WARNING );
145
  *
146
  * @param mixed A post type, object or object id.
147
  * @param bool Whether to return an instance of P2P_Directed_Connection_Type or just the direction
148
+ * @param string An object type, such as 'post' or 'user'
149
  *
150
  * @return bool|object|string False on failure, P2P_Directed_Connection_Type instance or direction on success.
151
  */
152
+ public function find_direction( $arg, $instantiate = true, $object_type = null ) {
153
  if ( is_array( $arg ) )
154
  $arg = reset( $arg );
155
 
156
+ if ( $object_type ) {
157
+ $direction = $this->direction_from_object_type( $object_type );
158
+ if ( !$direction )
159
+ return false;
160
+
161
+ if ( in_array( $direction, array( 'from', 'to' ) ) )
162
+ return $this->set_direction( $direction, $instantiate );
163
+ }
164
+
165
+ $direction = $this->direction_from_item( $arg );
166
+
167
+ if ( $direction )
168
+ return $this->set_direction( $direction, $instantiate );
169
+
170
+ return false;
171
+ }
172
+
173
+ public function direction_from_item( $arg ) {
174
  foreach ( array( 'from', 'to' ) as $direction ) {
175
+ $item = $this->side[ $direction ]->item_recognize( $arg );
176
+
177
+ if ( !$item )
178
  continue;
179
 
180
  if ( $this->indeterminate )
181
  $direction = $this->reciprocal ? 'any' : 'from';
182
 
183
+ return $direction;
184
  }
185
+ }
186
+
187
+ public function direction_from_object_type( $current ) {
188
+ $from = $this->object['from'];
189
+ $to = $this->object['to'];
190
+
191
+ if ( $from == $to && $current == $from )
192
+ return 'any';
193
+
194
+ if ( $current == $from )
195
+ return 'to';
196
+
197
+ if ( $current == $to )
198
+ return 'from';
199
 
200
  return false;
201
  }
202
 
203
+ public function direction_from_types( $object_type, $post_types = null ) {
 
 
 
204
  foreach ( array( 'from', 'to' ) as $direction ) {
205
+ if ( !$this->_type_check( $direction, $object_type, $post_types ) )
206
+ continue;
207
+
208
+ if ( $this->indeterminate )
209
+ $direction = $this->reciprocal ? 'any' : 'from';
210
+
211
+ return $direction;
 
212
  }
213
 
214
+ return false;
215
+ }
216
+
217
+ private function _type_check( $direction, $object_type, $post_types ) {
218
+ if ( $object_type != $this->object[ $direction ] )
219
  return false;
220
 
221
+ $side = $this->side[ $direction ];
222
+
223
+ if ( !method_exists( $side, 'recognize_post_type' ) )
224
+ return true;
225
+
226
+ foreach ( (array) $post_types as $post_type ) {
227
+ if ( $side->recognize_post_type( $post_type ) ) {
228
+ return true;
229
+ }
230
+ }
231
 
232
+ return false;
233
  }
234
 
235
  /** Alias for get_prev() */
319
  $post_types = array_unique( wp_list_pluck( $items, 'post_type' ) );
320
 
321
  if ( count( $post_types ) > 1 ) {
 
322
  $extra_qv['post_type'] = 'any';
 
 
323
  }
324
 
325
+ $direction = $this->find_direction_multiple( $post_types );
326
  if ( !$direction )
327
  return false;
328
 
341
  p2p_distribute_connected( $items, $q->items, $prop_name );
342
  }
343
 
344
+ // Used in each_connected()
345
+ private function find_direction_multiple( $post_types ) {
346
+ $possible_directions = array();
347
+
348
+ foreach ( array( 'from', 'to' ) as $direction ) {
349
+ if ( 'post' == $this->object[$direction] ) {
350
+ foreach ( $post_types as $post_type ) {
351
+ if ( !$this->side[ $direction ]->item_recognize( $post_type ) ) {
352
+ $possible_directions[] = $direction;
353
+ break;
354
+ }
355
+ }
356
+ }
357
+ }
358
+
359
+ return _p2p_compress_direction( $possible_directions );
360
+ }
361
+
362
  public function get_desc() {
363
  foreach ( array( 'from', 'to' ) as $key ) {
364
  $$key = $this->side[ $key ]->get_desc();
core/url-query.php CHANGED
@@ -2,16 +2,41 @@
2
 
3
  class P2P_URL_Query {
4
 
5
- function init() {
 
 
 
 
6
  add_filter( 'query_vars', array( __CLASS__, 'query_vars' ) );
 
 
 
 
 
 
 
 
7
  }
8
 
9
- function query_vars( $public_qv ) {
10
- return array_merge( $public_qv, array(
11
- 'connected_type',
12
- 'connected_items',
13
- 'connected_direction',
14
- ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  }
16
  }
17
 
2
 
3
  class P2P_URL_Query {
4
 
5
+ static function get_custom_qv() {
6
+ return array( 'connected_type', 'connected_items', 'connected_direction' );
7
+ }
8
+
9
+ static function init() {
10
  add_filter( 'query_vars', array( __CLASS__, 'query_vars' ) );
11
+
12
+ if ( is_admin() )
13
+ add_action( 'pre_user_query', array( __CLASS__, 'user_query' ), 9 );
14
+ }
15
+
16
+ // Make the query vars public
17
+ static function query_vars( $public_qv ) {
18
+ return array_merge( $public_qv, self::get_custom_qv() );
19
  }
20
 
21
+ // Add the query vars to the global user query (on the user admin screen)
22
+ static function user_query( $query ) {
23
+ if ( defined( 'DOING_AJAX' ) )
24
+ return;
25
+
26
+ // Restrict to users screen
27
+ if ( 'users' != get_current_screen()->id )
28
+ return;
29
+
30
+ // Don't overwrite capturing query
31
+ if ( isset( $query->_p2p_capture ) )
32
+ return;
33
+
34
+ // Don't overwrite existing P2P query
35
+ if ( isset( $query->query_vars['connected_type'] ) )
36
+ return;
37
+
38
+ _p2p_append( $query->query_vars, wp_array_slice_assoc( $_GET,
39
+ P2P_URL_Query::get_custom_qv() ) );
40
  }
41
  }
42
 
core/util.php CHANGED
@@ -8,13 +8,48 @@ function _p2p_expand_direction( $direction ) {
8
  return array( $direction );
9
  }
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  /** @internal */
12
  function _p2p_normalize( $items ) {
13
  if ( !is_array( $items ) )
14
  $items = array( $items );
15
 
16
- if ( is_object( reset( $items ) ) )
17
- $items = wp_list_pluck( $items, 'ID' );
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  return $items;
20
  }
8
  return array( $direction );
9
  }
10
 
11
+ /** @internal */
12
+ function _p2p_compress_direction( $directions ) {
13
+ if ( empty( $directions ) )
14
+ return false;
15
+
16
+ if ( count( $directions ) > 1 )
17
+ return 'any';
18
+
19
+ return reset( $directions );
20
+ }
21
+
22
+ /** @internal */
23
+ function _p2p_flip_direction( $direction ) {
24
+ $map = array(
25
+ 'from' => 'to',
26
+ 'to' => 'from',
27
+ 'any' => 'any',
28
+ );
29
+
30
+ return $map[ $direction ];
31
+ }
32
+
33
  /** @internal */
34
  function _p2p_normalize( $items ) {
35
  if ( !is_array( $items ) )
36
  $items = array( $items );
37
 
38
+ foreach ( $items as &$item ) {
39
+ if ( is_a( $item, 'P2P_Item' ) )
40
+ $item = $item->get_id();
41
+ elseif ( is_object( $item ) )
42
+ $item = $item->ID;
43
+ }
44
+
45
+ return $items;
46
+ }
47
+
48
+ /** @internal */
49
+ function _p2p_wrap( $items, $class ) {
50
+ foreach ( $items as &$item ) {
51
+ $item = new $class( $item );
52
+ }
53
 
54
  return $items;
55
  }
lang/posts-to-posts-it_IT.mo ADDED
Binary file
lang/posts-to-posts-it_IT.po ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2011
2
+ # This file is distributed under the same license as the package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: Posts 2 Posts 1.1.4\n"
6
+ "Report-Msgid-Bugs-To: http://wordpress.org/tag/p2p\n"
7
+ "POT-Creation-Date: 2011-12-01 16:28:04+00:00\n"
8
+ "MIME-Version: 1.0\n"
9
+ "Content-Type: text/plain; charset=UTF-8\n"
10
+ "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2012-06-21 22:25+0200\n"
12
+ "Last-Translator: Andrea Bersi\n"
13
+ "Language-Team: \n"
14
+ "X-Poedit-Language: Italian\n"
15
+ "X-Poedit-Country: ITALY\n"
16
+
17
+ #: admin/box-factory.php:56
18
+ msgid " (from)"
19
+ msgstr " (da)"
20
+
21
+ #: admin/box-factory.php:57
22
+ msgid " (to)"
23
+ msgstr " (a)"
24
+
25
+ #: admin/box.php:42
26
+ msgid "Are you sure you want to delete all connections?"
27
+ msgstr "Confermi la rimozione di tutte le relazioni?"
28
+
29
+ #: admin/box.php:115
30
+ msgid "Create connections:"
31
+ msgstr "Crea relazioni:"
32
+
33
+ #: admin/box.php:128
34
+ msgid "Search"
35
+ msgstr "Cerca"
36
+
37
+ #: admin/box.php:136
38
+ msgid "View All"
39
+ msgstr "Vedi tutti"
40
+
41
+ #: admin/box.php:204
42
+ msgid "previous"
43
+ msgstr "precedente"
44
+
45
+ #: admin/box.php:205
46
+ msgid "next"
47
+ msgstr "successivo"
48
+
49
+ #: admin/box.php:206
50
+ msgid "of"
51
+ msgstr "di"
52
+
53
+ #: admin/fields.php:16
54
+ msgid "Create connection"
55
+ msgstr "Crea relazione"
56
+
57
+ #: admin/fields.php:30
58
+ msgid "Delete all connections"
59
+ msgstr "Cancella tutte le relazioni"
60
+
61
+ #: admin/fields.php:39
62
+ msgid "Delete connection"
63
+ msgstr "Cancella relazione"
64
+
65
+ #: scb/AdminPage.php:165
66
+ msgid "Settings <strong>saved</strong>."
67
+ msgstr "Impostazioni <strong>salvate</strong>"
68
+
69
+ #: scb/AdminPage.php:178
70
+ #: scb/AdminPage.php:189
71
+ msgid "Save Changes"
72
+ msgstr "Salva"
73
+
74
+ #: scb/AdminPage.php:350
75
+ msgid "Settings"
76
+ msgstr "Impostazioni"
77
+
78
+ #: core/type.php:65
79
+ msgid "Connected %s"
80
+ msgstr "Collegato %s"
81
+
82
+ #: core/widget.php:15
83
+ msgid "Posts 2 Posts"
84
+ msgstr "Posts 2 Posts"
85
+
86
+ #: core/widget.php:16
87
+ msgid "A list of posts connected to the current post"
88
+ msgstr "Elenco dei contenuti collegati"
89
+
90
+ #: core/widget.php:37
91
+ msgid "Connection type:"
92
+ msgstr "Tipo relazione:"
93
+
94
+ #: core/widget.php:41
95
+ msgid "Connection listing:"
96
+ msgstr "Elenco relazioni:"
97
+
98
+ #: core/widget.php:47
99
+ msgid "connected"
100
+ msgstr "connesso"
101
+
102
+ #: core/widget.php:48
103
+ msgid "related"
104
+ msgstr "in relazione"
105
+
106
+ #: core/widget.php:73
107
+ msgid "Related %s"
108
+ msgstr "In relazione %s"
109
+
110
+ #: core/storage.php:35
111
+ msgid "Upgraded %d connections."
112
+ msgstr "Aggiornato %d relazioni"
113
+
114
+ #: core/storage.php:38
115
+ msgid "The Posts 2 Posts connections need to be upgraded. <a href=\"%s\">Proceed.</a>"
116
+ msgstr "Posts 2 Posts ha un Upgrade. <a href=\"%s\">Procedi.</a>"
117
+
118
+ #: core/side.php:110
119
+ msgid "Users"
120
+ msgstr "Utenti"
121
+
122
+ #: core/side.php:115
123
+ msgid "User"
124
+ msgstr "Utente"
125
+
126
+ #: core/side.php:116
127
+ msgid "Search Users"
128
+ msgstr "Cerca utenti"
129
+
130
+ #: core/side.php:117
131
+ msgid "No users found."
132
+ msgstr "Nessun utente trovato."
133
+
lang/posts-to-posts-ro_RO.mo CHANGED
Binary file
lang/posts-to-posts-ro_RO.po CHANGED
@@ -7,8 +7,8 @@ msgid ""
7
  msgstr ""
8
  "Project-Id-Version: Posts 2 Posts\n"
9
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/p2p\n"
10
- "POT-Creation-Date: 2012-04-15 23:11:12+00:00\n"
11
- "PO-Revision-Date: 2012-04-16 02:15+0200\n"
12
  "Last-Translator: scribu <mail@scribu.net>\n"
13
  "Language-Team: ROMANIAN <LL@li.org>\n"
14
  "Language: \n"
@@ -16,55 +16,43 @@ msgstr ""
16
  "Content-Type: text/plain; charset=UTF-8\n"
17
  "Content-Transfer-Encoding: 8bit\n"
18
 
19
- #: admin/box-factory.php:75
20
- msgid " (from)"
21
- msgstr "(de la)"
22
-
23
- #: admin/box-factory.php:76
24
- msgid " (to)"
25
- msgstr "(la)"
26
-
27
- #: admin/box.php:40
28
  msgid "Are you sure you want to delete all connections?"
29
  msgstr "Sigur vreți să ștergeți toate conexiunile?"
30
 
31
- #: admin/box.php:129
32
- msgid "Create connections:"
33
- msgstr "Creează conexiuni:"
34
-
35
- #: admin/box.php:138
36
- msgid "View All"
37
- msgstr "Vezi toate"
38
-
39
- #: admin/box.php:143
40
  msgid "Search"
41
  msgstr "Căutare"
42
 
43
- #: admin/box.php:220
44
  msgid "previous"
45
  msgstr "anterioare"
46
 
47
- #: admin/box.php:221
48
  msgid "next"
49
  msgstr "următoare"
50
 
51
- #: admin/box.php:222
52
  msgid "of"
53
  msgstr "din"
54
 
55
- #: admin/box.php:261
56
  msgid "Can't create connection: %s"
57
  msgstr "Conexiunea nu poate fi creată: %s"
58
 
59
- #: admin/fields.php:13
60
- msgid "Create connection"
61
- msgstr "Crează conexiune"
 
 
 
 
62
 
63
- #: admin/fields.php:25
64
  msgid "Delete all connections"
65
  msgstr "Șterge toate conexiunile"
66
 
67
- #: admin/fields.php:34
68
  msgid "Delete connection"
69
  msgstr "Șterge conexiune"
70
 
@@ -72,35 +60,35 @@ msgstr "Șterge conexiune"
72
  msgid "Connection Types"
73
  msgstr "Tipuri de conexiuni"
74
 
75
- #: admin/tools.php:31
76
- msgid "Upgraded %d connections."
77
- msgstr "Am actualizat %d conexiuni."
78
-
79
- #: admin/tools.php:34
80
- msgid "The Posts 2 Posts connections need to be upgraded. <a href=\"%s\">Proceed.</a>"
81
- msgstr "Conexiunile Posts 2 Posts trebuie actualizate. <a href=\"%s\">Pornește.</a>"
82
-
83
- #: admin/tools.php:54
84
  msgid "<em>%s</em> is not a registered connection type."
85
  msgstr "<em>%s</em> nu e un tip de conexiune înregistrat."
86
 
87
- #: admin/tools.php:63
88
  msgid "Converted %1$s connections from <em>%2$s</em> to <em>%3$s</em>."
89
  msgstr "Am convertit %1$s conexiuni din <em>%2$s</em> în <em>%3$s</em>."
90
 
91
- #: admin/tools.php:77
92
  msgid "Name"
93
  msgstr "Nume"
94
 
95
- #: admin/tools.php:78
96
  msgid "Information"
97
  msgstr "Informații"
98
 
99
- #: admin/tools.php:79
100
  msgid "Connections"
101
  msgstr "Conexiuni"
102
 
103
- #: admin/tools.php:94
 
 
 
 
 
 
 
 
104
  msgid "Convert to registered connection type:"
105
  msgstr "Convertește într-un tip de conexiune înregistrat:"
106
 
@@ -116,43 +104,47 @@ msgstr "Posts 2 Posts"
116
  msgid "A list of posts connected to the current post"
117
  msgstr "O lista de postări conectate la postarea curentă"
118
 
119
- #: core/extra.php:37
120
  msgid "Title:"
121
  msgstr "Titlu:"
122
 
123
- #: core/extra.php:44
124
  msgid "Connection type:"
125
  msgstr "Tipul conexiunii:"
126
 
127
- #: core/extra.php:48
128
  msgid "Connection listing:"
129
  msgstr "Conexiuni de listat:"
130
 
131
- #: core/extra.php:54
132
  msgid "connected"
133
  msgstr "conectate"
134
 
135
- #: core/extra.php:55
136
  msgid "related"
137
  msgstr "înrudite"
138
 
139
- #: core/side.php:129
140
  msgid "Users"
141
  msgstr "Utilizatori"
142
 
143
- #: core/side.php:138
144
  msgid "User"
145
  msgstr "Utilizator"
146
 
147
- #: core/side.php:139
148
  msgid "Search Users"
149
  msgstr "Caută utilizatori"
150
 
151
- #: core/side.php:140
152
  msgid "No users found."
153
  msgstr "Niciun utilizator găsit."
154
 
155
- #: core/type.php:88
 
 
 
 
156
  msgid "Connected %s"
157
  msgstr "%s conectate"
158
 
@@ -165,10 +157,26 @@ msgstr "Setări <strong>salvate</strong>."
165
  msgid "Save Changes"
166
  msgstr "Salvează schimbările"
167
 
168
- #: scb/AdminPage.php:360
169
  msgid "Settings"
170
  msgstr "Setări"
171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  #~ msgid "Related %s"
173
  #~ msgstr "%s înrudite"
174
 
7
  msgstr ""
8
  "Project-Id-Version: Posts 2 Posts\n"
9
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/p2p\n"
10
+ "POT-Creation-Date: 2012-08-17 07:40:29+00:00\n"
11
+ "PO-Revision-Date: 2012-08-17 10:42+0200\n"
12
  "Last-Translator: scribu <mail@scribu.net>\n"
13
  "Language-Team: ROMANIAN <LL@li.org>\n"
14
  "Language: \n"
16
  "Content-Type: text/plain; charset=UTF-8\n"
17
  "Content-Transfer-Encoding: 8bit\n"
18
 
19
+ #: admin/box.php:38
 
 
 
 
 
 
 
 
20
  msgid "Are you sure you want to delete all connections?"
21
  msgstr "Sigur vreți să ștergeți toate conexiunile?"
22
 
23
+ #: admin/box.php:114
 
 
 
 
 
 
 
 
24
  msgid "Search"
25
  msgstr "Căutare"
26
 
27
+ #: admin/box.php:191
28
  msgid "previous"
29
  msgstr "anterioare"
30
 
31
+ #: admin/box.php:192
32
  msgid "next"
33
  msgstr "următoare"
34
 
35
+ #: admin/box.php:193
36
  msgid "of"
37
  msgstr "din"
38
 
39
+ #: admin/box.php:236
40
  msgid "Can't create connection: %s"
41
  msgstr "Conexiunea nu poate fi creată: %s"
42
 
43
+ #: admin/factory.php:30
44
+ msgid " (from)"
45
+ msgstr "(de la)"
46
+
47
+ #: admin/factory.php:31
48
+ msgid " (to)"
49
+ msgstr "(la)"
50
 
51
+ #: admin/fields.php:7
52
  msgid "Delete all connections"
53
  msgstr "Șterge toate conexiunile"
54
 
55
+ #: admin/fields.php:16
56
  msgid "Delete connection"
57
  msgstr "Șterge conexiune"
58
 
60
  msgid "Connection Types"
61
  msgstr "Tipuri de conexiuni"
62
 
63
+ #: admin/tools.php:41
 
 
 
 
 
 
 
 
64
  msgid "<em>%s</em> is not a registered connection type."
65
  msgstr "<em>%s</em> nu e un tip de conexiune înregistrat."
66
 
67
+ #: admin/tools.php:50
68
  msgid "Converted %1$s connections from <em>%2$s</em> to <em>%3$s</em>."
69
  msgstr "Am convertit %1$s conexiuni din <em>%2$s</em> în <em>%3$s</em>."
70
 
71
+ #: admin/tools.php:64
72
  msgid "Name"
73
  msgstr "Nume"
74
 
75
+ #: admin/tools.php:65
76
  msgid "Information"
77
  msgstr "Informații"
78
 
79
+ #: admin/tools.php:66
80
  msgid "Connections"
81
  msgstr "Conexiuni"
82
 
83
+ #: admin/tools.php:74
84
+ msgid "No connection types registered."
85
+ msgstr "Nu există tipuri de conexiuni înregistrate."
86
+
87
+ #: admin/tools.php:76
88
+ msgid "To register a connection type, see <a href=\"%s\">the wiki</a>."
89
+ msgstr "Pentru a înregistra un tip de conexiune, vezi <a href=\"%s\">wiki-ul</a>."
90
+
91
+ #: admin/tools.php:93
92
  msgid "Convert to registered connection type:"
93
  msgstr "Convertește într-un tip de conexiune înregistrat:"
94
 
104
  msgid "A list of posts connected to the current post"
105
  msgstr "O lista de postări conectate la postarea curentă"
106
 
107
+ #: core/extra.php:34
108
  msgid "Title:"
109
  msgstr "Titlu:"
110
 
111
+ #: core/extra.php:41
112
  msgid "Connection type:"
113
  msgstr "Tipul conexiunii:"
114
 
115
+ #: core/extra.php:46
116
  msgid "Connection listing:"
117
  msgstr "Conexiuni de listat:"
118
 
119
+ #: core/extra.php:52
120
  msgid "connected"
121
  msgstr "conectate"
122
 
123
+ #: core/extra.php:53
124
  msgid "related"
125
  msgstr "înrudite"
126
 
127
+ #: core/side.php:210
128
  msgid "Users"
129
  msgstr "Utilizatori"
130
 
131
+ #: core/side.php:219
132
  msgid "User"
133
  msgstr "Utilizator"
134
 
135
+ #: core/side.php:220
136
  msgid "Search Users"
137
  msgstr "Caută utilizatori"
138
 
139
+ #: core/side.php:221
140
  msgid "No users found."
141
  msgstr "Niciun utilizator găsit."
142
 
143
+ #: core/type.php:79
144
+ msgid "Create connections"
145
+ msgstr "Creează conexiuni"
146
+
147
+ #: core/type.php:102
148
  msgid "Connected %s"
149
  msgstr "%s conectate"
150
 
157
  msgid "Save Changes"
158
  msgstr "Salvează schimbările"
159
 
160
+ #: scb/AdminPage.php:346
161
  msgid "Settings"
162
  msgstr "Setări"
163
 
164
+ #~ msgid "View All"
165
+ #~ msgstr "Vezi toate"
166
+
167
+ #~ msgid "Create connection"
168
+ #~ msgstr "Crează conexiune"
169
+
170
+ #~ msgid "Upgraded %d connections."
171
+ #~ msgstr "Am actualizat %d conexiuni."
172
+
173
+ #~ msgid ""
174
+ #~ "The Posts 2 Posts connections need to be upgraded. <a href=\"%s\">Proceed."
175
+ #~ "</a>"
176
+ #~ msgstr ""
177
+ #~ "Conexiunile Posts 2 Posts trebuie actualizate. <a href=\"%s\">Pornește.</"
178
+ #~ "a>"
179
+
180
  #~ msgid "Related %s"
181
  #~ msgstr "%s înrudite"
182
 
lang/posts-to-posts.pot CHANGED
@@ -4,7 +4,7 @@ msgid ""
4
  msgstr ""
5
  "Project-Id-Version: \n"
6
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/p2p\n"
7
- "POT-Creation-Date: 2012-04-15 23:11:12+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
@@ -12,55 +12,43 @@ msgstr ""
12
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
14
 
15
- #: admin/box-factory.php:75
16
- msgid " (from)"
17
- msgstr ""
18
-
19
- #: admin/box-factory.php:76
20
- msgid " (to)"
21
- msgstr ""
22
-
23
- #: admin/box.php:40
24
  msgid "Are you sure you want to delete all connections?"
25
  msgstr ""
26
 
27
- #: admin/box.php:129
28
- msgid "Create connections:"
29
- msgstr ""
30
-
31
- #: admin/box.php:138
32
- msgid "View All"
33
- msgstr ""
34
-
35
- #: admin/box.php:143
36
  msgid "Search"
37
  msgstr ""
38
 
39
- #: admin/box.php:220
40
  msgid "previous"
41
  msgstr ""
42
 
43
- #: admin/box.php:221
44
  msgid "next"
45
  msgstr ""
46
 
47
- #: admin/box.php:222
48
  msgid "of"
49
  msgstr ""
50
 
51
- #: admin/box.php:261
52
  msgid "Can't create connection: %s"
53
  msgstr ""
54
 
55
- #: admin/fields.php:13
56
- msgid "Create connection"
57
  msgstr ""
58
 
59
- #: admin/fields.php:25
 
 
 
 
60
  msgid "Delete all connections"
61
  msgstr ""
62
 
63
- #: admin/fields.php:34
64
  msgid "Delete connection"
65
  msgstr ""
66
 
@@ -68,37 +56,35 @@ msgstr ""
68
  msgid "Connection Types"
69
  msgstr ""
70
 
71
- #: admin/tools.php:31
72
- msgid "Upgraded %d connections."
73
- msgstr ""
74
-
75
- #: admin/tools.php:34
76
- msgid ""
77
- "The Posts 2 Posts connections need to be upgraded. <a href=\"%s\">Proceed.</"
78
- "a>"
79
- msgstr ""
80
-
81
- #: admin/tools.php:54
82
  msgid "<em>%s</em> is not a registered connection type."
83
  msgstr ""
84
 
85
- #: admin/tools.php:63
86
  msgid "Converted %1$s connections from <em>%2$s</em> to <em>%3$s</em>."
87
  msgstr ""
88
 
89
- #: admin/tools.php:77
90
  msgid "Name"
91
  msgstr ""
92
 
93
- #: admin/tools.php:78
94
  msgid "Information"
95
  msgstr ""
96
 
97
- #: admin/tools.php:79
98
  msgid "Connections"
99
  msgstr ""
100
 
101
- #: admin/tools.php:94
 
 
 
 
 
 
 
 
102
  msgid "Convert to registered connection type:"
103
  msgstr ""
104
 
@@ -114,43 +100,47 @@ msgstr ""
114
  msgid "A list of posts connected to the current post"
115
  msgstr ""
116
 
117
- #: core/extra.php:37
118
  msgid "Title:"
119
  msgstr ""
120
 
121
- #: core/extra.php:44
122
  msgid "Connection type:"
123
  msgstr ""
124
 
125
- #: core/extra.php:48
126
  msgid "Connection listing:"
127
  msgstr ""
128
 
129
- #: core/extra.php:54
130
  msgid "connected"
131
  msgstr ""
132
 
133
- #: core/extra.php:55
134
  msgid "related"
135
  msgstr ""
136
 
137
- #: core/side.php:129
138
  msgid "Users"
139
  msgstr ""
140
 
141
- #: core/side.php:138
142
  msgid "User"
143
  msgstr ""
144
 
145
- #: core/side.php:139
146
  msgid "Search Users"
147
  msgstr ""
148
 
149
- #: core/side.php:140
150
  msgid "No users found."
151
  msgstr ""
152
 
153
- #: core/type.php:88
 
 
 
 
154
  msgid "Connected %s"
155
  msgstr ""
156
 
@@ -162,6 +152,6 @@ msgstr ""
162
  msgid "Save Changes"
163
  msgstr ""
164
 
165
- #: scb/AdminPage.php:360
166
  msgid "Settings"
167
  msgstr ""
4
  msgstr ""
5
  "Project-Id-Version: \n"
6
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/p2p\n"
7
+ "POT-Creation-Date: 2012-08-17 07:40:29+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
12
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
14
 
15
+ #: admin/box.php:38
 
 
 
 
 
 
 
 
16
  msgid "Are you sure you want to delete all connections?"
17
  msgstr ""
18
 
19
+ #: admin/box.php:114
 
 
 
 
 
 
 
 
20
  msgid "Search"
21
  msgstr ""
22
 
23
+ #: admin/box.php:191
24
  msgid "previous"
25
  msgstr ""
26
 
27
+ #: admin/box.php:192
28
  msgid "next"
29
  msgstr ""
30
 
31
+ #: admin/box.php:193
32
  msgid "of"
33
  msgstr ""
34
 
35
+ #: admin/box.php:236
36
  msgid "Can't create connection: %s"
37
  msgstr ""
38
 
39
+ #: admin/factory.php:30
40
+ msgid " (from)"
41
  msgstr ""
42
 
43
+ #: admin/factory.php:31
44
+ msgid " (to)"
45
+ msgstr ""
46
+
47
+ #: admin/fields.php:7
48
  msgid "Delete all connections"
49
  msgstr ""
50
 
51
+ #: admin/fields.php:16
52
  msgid "Delete connection"
53
  msgstr ""
54
 
56
  msgid "Connection Types"
57
  msgstr ""
58
 
59
+ #: admin/tools.php:41
 
 
 
 
 
 
 
 
 
 
60
  msgid "<em>%s</em> is not a registered connection type."
61
  msgstr ""
62
 
63
+ #: admin/tools.php:50
64
  msgid "Converted %1$s connections from <em>%2$s</em> to <em>%3$s</em>."
65
  msgstr ""
66
 
67
+ #: admin/tools.php:64
68
  msgid "Name"
69
  msgstr ""
70
 
71
+ #: admin/tools.php:65
72
  msgid "Information"
73
  msgstr ""
74
 
75
+ #: admin/tools.php:66
76
  msgid "Connections"
77
  msgstr ""
78
 
79
+ #: admin/tools.php:74
80
+ msgid "No connection types registered."
81
+ msgstr ""
82
+
83
+ #: admin/tools.php:76
84
+ msgid "To register a connection type, see <a href=\"%s\">the wiki</a>."
85
+ msgstr ""
86
+
87
+ #: admin/tools.php:93
88
  msgid "Convert to registered connection type:"
89
  msgstr ""
90
 
100
  msgid "A list of posts connected to the current post"
101
  msgstr ""
102
 
103
+ #: core/extra.php:34
104
  msgid "Title:"
105
  msgstr ""
106
 
107
+ #: core/extra.php:41
108
  msgid "Connection type:"
109
  msgstr ""
110
 
111
+ #: core/extra.php:46
112
  msgid "Connection listing:"
113
  msgstr ""
114
 
115
+ #: core/extra.php:52
116
  msgid "connected"
117
  msgstr ""
118
 
119
+ #: core/extra.php:53
120
  msgid "related"
121
  msgstr ""
122
 
123
+ #: core/side.php:210
124
  msgid "Users"
125
  msgstr ""
126
 
127
+ #: core/side.php:219
128
  msgid "User"
129
  msgstr ""
130
 
131
+ #: core/side.php:220
132
  msgid "Search Users"
133
  msgstr ""
134
 
135
+ #: core/side.php:221
136
  msgid "No users found."
137
  msgstr ""
138
 
139
+ #: core/type.php:79
140
+ msgid "Create connections"
141
+ msgstr ""
142
+
143
+ #: core/type.php:102
144
  msgid "Connected %s"
145
  msgstr ""
146
 
152
  msgid "Save Changes"
153
  msgstr ""
154
 
155
+ #: scb/AdminPage.php:346
156
  msgid "Settings"
157
  msgstr ""
posts-to-posts.php CHANGED
@@ -2,7 +2,7 @@
2
  /*
3
  Plugin Name: Posts 2 Posts
4
  Description: Create many-to-many relationships between all types of posts.
5
- Version: 1.3.1
6
  Author: scribu
7
  Author URI: http://scribu.net/
8
  Plugin URI: http://scribu.net/wordpress/posts-to-posts
@@ -10,20 +10,20 @@ Text Domain: posts-to-posts
10
  Domain Path: /lang
11
  */
12
 
13
- define( 'P2P_PLUGIN_VERSION', '1.3.1' );
14
 
15
  define( 'P2P_TEXTDOMAIN', 'posts-to-posts' );
16
 
17
  require dirname( __FILE__ ) . '/scb/load.php';
18
 
19
- function _p2p_init() {
20
  $base = dirname( __FILE__ );
21
 
22
  load_plugin_textdomain( P2P_TEXTDOMAIN, '', basename( $base ) . '/lang' );
23
 
24
  _p2p_load_files( "$base/core", array(
25
  'storage', 'query', 'query-post', 'query-user', 'url-query',
26
- 'util', 'side', 'list', 'type-factory', 'type', 'directed-type',
27
  'api', 'extra'
28
  ) );
29
 
@@ -32,7 +32,7 @@ function _p2p_init() {
32
 
33
  if ( is_admin() ) {
34
  _p2p_load_files( "$base/admin", array(
35
- 'mustache',
36
  'box-factory', 'box', 'fields',
37
  'column-factory', 'column',
38
  'tools'
@@ -41,8 +41,13 @@ function _p2p_init() {
41
 
42
  register_uninstall_hook( __FILE__, array( 'P2P_Storage', 'uninstall' ) );
43
  }
44
- scb_init( '_p2p_init' );
45
 
 
 
 
 
 
46
 
47
  function _p2p_load_files( $dir, $files ) {
48
  foreach ( $files as $file )
2
  /*
3
  Plugin Name: Posts 2 Posts
4
  Description: Create many-to-many relationships between all types of posts.
5
+ Version: 1.4
6
  Author: scribu
7
  Author URI: http://scribu.net/
8
  Plugin URI: http://scribu.net/wordpress/posts-to-posts
10
  Domain Path: /lang
11
  */
12
 
13
+ define( 'P2P_PLUGIN_VERSION', '1.4' );
14
 
15
  define( 'P2P_TEXTDOMAIN', 'posts-to-posts' );
16
 
17
  require dirname( __FILE__ ) . '/scb/load.php';
18
 
19
+ function _p2p_load() {
20
  $base = dirname( __FILE__ );
21
 
22
  load_plugin_textdomain( P2P_TEXTDOMAIN, '', basename( $base ) . '/lang' );
23
 
24
  _p2p_load_files( "$base/core", array(
25
  'storage', 'query', 'query-post', 'query-user', 'url-query',
26
+ 'util', 'item', 'list', 'side', 'type-factory', 'type', 'directed-type',
27
  'api', 'extra'
28
  ) );
29
 
32
 
33
  if ( is_admin() ) {
34
  _p2p_load_files( "$base/admin", array(
35
+ 'mustache', 'factory',
36
  'box-factory', 'box', 'fields',
37
  'column-factory', 'column',
38
  'tools'
41
 
42
  register_uninstall_hook( __FILE__, array( 'P2P_Storage', 'uninstall' ) );
43
  }
44
+ scb_init( '_p2p_load' );
45
 
46
+ function _p2p_init() {
47
+ // Safe hook for calling p2p_register_connection_type()
48
+ do_action( 'p2p_init' );
49
+ }
50
+ add_action( 'wp_loaded', '_p2p_init' );
51
 
52
  function _p2p_load_files( $dir, $files ) {
53
  foreach ( $files as $file )
readme.txt CHANGED
@@ -1,9 +1,9 @@
1
  === Posts 2 Posts ===
2
  Contributors: scribu, ciobi
3
  Tags: connections, custom post types, relationships, many-to-many, users
4
- Requires at least: 3.2
5
  Tested up to: 3.4
6
- Stable tag: 1.3.1
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -53,6 +53,13 @@ Make sure your host is running PHP 5. The only foolproof way to do this is to ad
53
 
54
  == Changelog ==
55
 
 
 
 
 
 
 
 
56
  = 1.3.1 =
57
  * sanitize connection fields values on save, preventing security exploits
58
  * improved connection field default value handling
1
  === Posts 2 Posts ===
2
  Contributors: scribu, ciobi
3
  Tags: connections, custom post types, relationships, many-to-many, users
4
+ Requires at least: 3.4
5
  Tested up to: 3.4
6
+ Stable tag: 1.4
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
53
 
54
  == Changelog ==
55
 
56
+ = 1.4 =
57
+ * added 'p2p_init' hook
58
+ * replaced 'View All' button with '+ Create connections' toggle
59
+ * improved usability of connection candidate UI
60
+ * fixed issues related to auto-drafts
61
+ * show columns on the admin user list screen
62
+
63
  = 1.3.1 =
64
  * sanitize connection fields values on save, preventing security exploits
65
  * improved connection field default value handling
scb/AdminPage.php CHANGED
@@ -127,7 +127,7 @@ abstract class scbAdminPage {
127
  function page_header() {
128
  echo "<div class='wrap'>\n";
129
  screen_icon( $this->args['screen_icon'] );
130
- echo "<h2>" . $this->args['page_title'] . "</h2>\n";
131
  }
132
 
133
  // This is where the page content goes
@@ -274,20 +274,6 @@ abstract class scbAdminPage {
274
  return $this->row_wrap( $args['title'], $this->input( $args, $formdata ) );
275
  }
276
 
277
- // Wraps the given content in a <table>
278
- function table_wrap( $content ) {
279
- return
280
- html( 'table class="form-table"', $content );
281
- }
282
-
283
- // Wraps the given content in a <tr><td>
284
- function row_wrap( $title, $content ) {
285
- return
286
- html( 'tr',
287
- html( 'th scope="row"', $title )
288
- .html( 'td', $content ) );
289
- }
290
-
291
  // Mimic scbForms inheritance
292
  function __call( $method, $args ) {
293
  if ( in_array( $method, array( 'input', 'form' ) ) ) {
@@ -303,12 +289,12 @@ abstract class scbAdminPage {
303
 
304
  // Wraps a string in a <script> tag
305
  function js_wrap( $string ) {
306
- return "\n<script type='text/javascript'>\n" . $string . "\n</script>\n";
307
  }
308
 
309
  // Wraps a string in a <style> tag
310
  function css_wrap( $string ) {
311
- return "\n<style type='text/css'>\n" . $string . "\n</style>\n";
312
  }
313
 
314
 
127
  function page_header() {
128
  echo "<div class='wrap'>\n";
129
  screen_icon( $this->args['screen_icon'] );
130
+ echo html( "h2", $this->args['page_title'] );
131
  }
132
 
133
  // This is where the page content goes
274
  return $this->row_wrap( $args['title'], $this->input( $args, $formdata ) );
275
  }
276
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  // Mimic scbForms inheritance
278
  function __call( $method, $args ) {
279
  if ( in_array( $method, array( 'input', 'form' ) ) ) {
289
 
290
  // Wraps a string in a <script> tag
291
  function js_wrap( $string ) {
292
+ return html( "script type='text/javascript'", $string );
293
  }
294
 
295
  // Wraps a string in a <style> tag
296
  function css_wrap( $string ) {
297
+ return html( "style type='text/css'", $string );
298
  }
299
 
300
 
scb/Forms.php CHANGED
@@ -60,34 +60,26 @@ class scbForms {
60
  // ____________WRAPPERS____________
61
 
62
 
63
- // Wraps the given content in a <form><table>
64
  static function form_table_wrap( $content, $nonce = 'update_options' ) {
65
- $output = self::table_wrap( $content );
66
- $output = self::form_wrap( $output, $nonce );
67
-
68
- return $output;
69
  }
70
 
71
- // Wraps the given content in a <form> tag
72
  static function form_wrap( $content, $nonce = 'update_options' ) {
73
- $output = "\n<form method='post' action=''>\n";
74
- $output .= $content;
75
- $output .= wp_nonce_field( $action = $nonce, $name = "_wpnonce", $referer = true , $echo = false );
76
- $output .= "\n</form>\n";
77
-
78
- return $output;
79
  }
80
 
81
- // Wraps the given content in a <table>
82
  static function table_wrap( $content ) {
83
- $output = "\n<table class='form-table'>\n" . $content . "\n</table>\n";
84
-
85
- return $output;
86
  }
87
 
88
- // Wraps the given content in a <tr><td>
89
  static function row_wrap( $title, $content ) {
90
- return "\n<tr>\n\t<th scope='row'>" . $title . "</th>\n\t<td>\n\t\t" . $content . "\t</td>\n\n</tr>";
 
 
 
91
  }
92
 
93
 
@@ -141,16 +133,15 @@ class scbForms {
141
  *
142
  * @param array $fields List of args that would be sent to scbForms::input()
143
  * @param array $data The data to validate. Defaults to $_POST
 
144
  *
145
  * @return array
146
  */
147
- static function validate_post_data( $fields, $data = null ) {
148
  if ( null === $data ) {
149
  $data = stripslashes_deep( $_POST );
150
  }
151
 
152
- $to_update = array();
153
-
154
  foreach ( $fields as $field ) {
155
  $value = scbForms::get_value( $field['name'], $data );
156
 
@@ -266,12 +257,34 @@ class scbForm {
266
  }
267
 
268
 
269
- abstract class scbFormField {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
 
271
  protected $args;
272
 
273
  public static function create( $args ) {
274
- if ( is_a( $args, __CLASS__ ) )
275
  return $args;
276
 
277
  if ( empty( $args['name'] ) ) {
@@ -279,10 +292,15 @@ abstract class scbFormField {
279
  }
280
 
281
  if ( isset( $args['value'] ) && is_array( $args['value'] ) ) {
282
- $args['values'] = $args['value'];
283
  unset( $args['value'] );
284
  }
285
 
 
 
 
 
 
286
  if ( isset( $args['extra'] ) && !is_array( $args['extra'] ) )
287
  $args['extra'] = shortcode_parse_atts( $args['extra'] );
288
 
@@ -293,8 +311,9 @@ abstract class scbFormField {
293
  'wrap_each' => scbForms::TOKEN,
294
  ) );
295
 
296
- if ( isset( $args['values'] ) )
297
- self::_expand_values( $args );
 
298
 
299
  switch ( $args['type'] ) {
300
  case 'radio':
@@ -302,10 +321,12 @@ abstract class scbFormField {
302
  case 'select':
303
  return new scbSelectField( $args );
304
  case 'checkbox':
305
- if ( isset( $args['values'] ) )
306
  return new scbMultipleChoiceField( $args );
307
  else
308
  return new scbSingleCheckboxField( $args );
 
 
309
  default:
310
  return new scbTextField( $args );
311
  }
@@ -323,13 +344,6 @@ abstract class scbFormField {
323
  return isset( $this->args[ $key ] );
324
  }
325
 
326
- /**
327
- * Generate the corresponding HTML for a field
328
- *
329
- * @param mixed $value The value to use
330
- *
331
- * @return string
332
- */
333
  public function render( $value = null ) {
334
  if ( null === $value && isset( $this->default ) )
335
  $value = $this->default;
@@ -350,15 +364,6 @@ abstract class scbFormField {
350
  // The actual rendering
351
  abstract protected function _render( $args );
352
 
353
- /**
354
- * Validates a value against a field.
355
- *
356
- * @param mixed $value The value to check
357
- *
358
- * @return mixed null if the validation failed, sanitized value otherwise.
359
- */
360
- abstract public function validate( $value );
361
-
362
  // Handle args for a single checkbox or radio input
363
  protected static function _checkbox( $args ) {
364
  $args = wp_parse_args( $args, array(
@@ -415,15 +420,15 @@ abstract class scbFormField {
415
  return $input . ' ' . $desc;
416
  }
417
 
418
- private static function _expand_values( &$args ) {
419
- $values =& $args['values'];
420
 
421
- if ( !empty( $values ) && !self::is_associative( $values ) ) {
422
  if ( is_array( $args['desc'] ) ) {
423
- $values = array_combine( $values, $args['desc'] ); // back-compat
424
  $args['desc'] = false;
425
  } elseif ( !isset( $args['numeric'] ) || !$args['numeric'] ) {
426
- $values = array_combine( $values, $values );
427
  }
428
  }
429
  }
@@ -469,7 +474,7 @@ class scbTextField extends scbFormField {
469
  abstract class scbSingleChoiceField extends scbFormField {
470
 
471
  public function validate( $value ) {
472
- if ( isset( $this->values[ $value ] ) )
473
  return $value;
474
 
475
  return null;
@@ -510,10 +515,10 @@ class scbSelectField extends scbSingleChoiceField {
510
  );
511
  }
512
 
513
- foreach ( $values as $value => $title ) {
514
  $options[] = array(
515
  'value' => $value,
516
- 'selected' => ( (string) $value == (string) $selected ),
517
  'title' => $title
518
  );
519
  }
@@ -541,16 +546,16 @@ class scbRadiosField extends scbSelectField {
541
 
542
  if ( array( 'foo' ) == $selected ) {
543
  // radio buttons should always have one option selected
544
- $selected = key( $values );
545
  }
546
 
547
  $opts = '';
548
- foreach ( $values as $value => $title ) {
549
  $single_input = scbFormField::_checkbox( array(
550
  'name' => $name,
551
  'type' => 'radio',
552
  'value' => $value,
553
- 'checked' => ( (string) $value == (string) $selected ),
554
  'desc' => $title,
555
  'desc_pos' => 'after'
556
  ) );
@@ -566,7 +571,7 @@ class scbRadiosField extends scbSelectField {
566
  class scbMultipleChoiceField extends scbFormField {
567
 
568
  public function validate( $value ) {
569
- return array_intersect( array_keys( $this->values ), (array) $value );
570
  }
571
 
572
  protected function _render( $args ) {
@@ -581,7 +586,7 @@ class scbMultipleChoiceField extends scbFormField {
581
  $checked = array();
582
 
583
  $opts = '';
584
- foreach ( $values as $value => $title ) {
585
  $single_input = scbFormField::_checkbox( array(
586
  'name' => $name . '[]',
587
  'type' => 'checkbox',
@@ -634,3 +639,32 @@ class scbSingleCheckboxField extends scbFormField {
634
  }
635
  }
636
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  // ____________WRAPPERS____________
61
 
62
 
 
63
  static function form_table_wrap( $content, $nonce = 'update_options' ) {
64
+ return self::form_wrap( self::table_wrap( $content ), $nonce );
 
 
 
65
  }
66
 
 
67
  static function form_wrap( $content, $nonce = 'update_options' ) {
68
+ return html( "form method='post' action=''",
69
+ $content,
70
+ wp_nonce_field( $nonce, '_wpnonce', $referer = true, $echo = false )
71
+ );
 
 
72
  }
73
 
 
74
  static function table_wrap( $content ) {
75
+ return html( "table class='form-table'", $content );
 
 
76
  }
77
 
 
78
  static function row_wrap( $title, $content ) {
79
+ return html( "tr",
80
+ html( "th scope='row'", $title ),
81
+ html( "td", $content )
82
+ );
83
  }
84
 
85
 
133
  *
134
  * @param array $fields List of args that would be sent to scbForms::input()
135
  * @param array $data The data to validate. Defaults to $_POST
136
+ * @param array $to_update Existing data to populate. Necessary for nested values
137
  *
138
  * @return array
139
  */
140
+ static function validate_post_data( $fields, $data = null, $to_update = array() ) {
141
  if ( null === $data ) {
142
  $data = stripslashes_deep( $_POST );
143
  }
144
 
 
 
145
  foreach ( $fields as $field ) {
146
  $value = scbForms::get_value( $field['name'], $data );
147
 
257
  }
258
 
259
 
260
+ interface scbFormField_I {
261
+
262
+ /**
263
+ * Generate the corresponding HTML for a field
264
+ *
265
+ * @param mixed $value The value to use
266
+ *
267
+ * @return string
268
+ */
269
+ function render( $value = null );
270
+
271
+ /**
272
+ * Validates a value against a field.
273
+ *
274
+ * @param mixed $value The value to check
275
+ *
276
+ * @return mixed null if the validation failed, sanitized value otherwise.
277
+ */
278
+ function validate( $value );
279
+ }
280
+
281
+
282
+ abstract class scbFormField implements scbFormField_I {
283
 
284
  protected $args;
285
 
286
  public static function create( $args ) {
287
+ if ( is_a( $args, 'scbFormField_I' ) )
288
  return $args;
289
 
290
  if ( empty( $args['name'] ) ) {
292
  }
293
 
294
  if ( isset( $args['value'] ) && is_array( $args['value'] ) ) {
295
+ $args['choices'] = $args['value'];
296
  unset( $args['value'] );
297
  }
298
 
299
+ if ( isset( $args['values'] ) ) {
300
+ $args['choices'] = $args['values'];
301
+ unset( $args['values'] );
302
+ }
303
+
304
  if ( isset( $args['extra'] ) && !is_array( $args['extra'] ) )
305
  $args['extra'] = shortcode_parse_atts( $args['extra'] );
306
 
311
  'wrap_each' => scbForms::TOKEN,
312
  ) );
313
 
314
+ // depends on $args['desc']
315
+ if ( isset( $args['choices'] ) )
316
+ self::_expand_choices( $args );
317
 
318
  switch ( $args['type'] ) {
319
  case 'radio':
321
  case 'select':
322
  return new scbSelectField( $args );
323
  case 'checkbox':
324
+ if ( isset( $args['choices'] ) )
325
  return new scbMultipleChoiceField( $args );
326
  else
327
  return new scbSingleCheckboxField( $args );
328
+ case 'custom':
329
+ return new scbCustomField( $args );
330
  default:
331
  return new scbTextField( $args );
332
  }
344
  return isset( $this->args[ $key ] );
345
  }
346
 
 
 
 
 
 
 
 
347
  public function render( $value = null ) {
348
  if ( null === $value && isset( $this->default ) )
349
  $value = $this->default;
364
  // The actual rendering
365
  abstract protected function _render( $args );
366
 
 
 
 
 
 
 
 
 
 
367
  // Handle args for a single checkbox or radio input
368
  protected static function _checkbox( $args ) {
369
  $args = wp_parse_args( $args, array(
420
  return $input . ' ' . $desc;
421
  }
422
 
423
+ private static function _expand_choices( &$args ) {
424
+ $choices =& $args['choices'];
425
 
426
+ if ( !empty( $choices ) && !self::is_associative( $choices ) ) {
427
  if ( is_array( $args['desc'] ) ) {
428
+ $choices = array_combine( $choices, $args['desc'] ); // back-compat
429
  $args['desc'] = false;
430
  } elseif ( !isset( $args['numeric'] ) || !$args['numeric'] ) {
431
+ $choices = array_combine( $choices, $choices );
432
  }
433
  }
434
  }
474
  abstract class scbSingleChoiceField extends scbFormField {
475
 
476
  public function validate( $value ) {
477
+ if ( isset( $this->choices[ $value ] ) )
478
  return $value;
479
 
480
  return null;
515
  );
516
  }
517
 
518
+ foreach ( $choices as $value => $title ) {
519
  $options[] = array(
520
  'value' => $value,
521
+ 'selected' => ( $value == $selected ),
522
  'title' => $title
523
  );
524
  }
546
 
547
  if ( array( 'foo' ) == $selected ) {
548
  // radio buttons should always have one option selected
549
+ $selected = key( $choices );
550
  }
551
 
552
  $opts = '';
553
+ foreach ( $choices as $value => $title ) {
554
  $single_input = scbFormField::_checkbox( array(
555
  'name' => $name,
556
  'type' => 'radio',
557
  'value' => $value,
558
+ 'checked' => ( $value == $selected ),
559
  'desc' => $title,
560
  'desc_pos' => 'after'
561
  ) );
571
  class scbMultipleChoiceField extends scbFormField {
572
 
573
  public function validate( $value ) {
574
+ return array_intersect( array_keys( $this->choices ), (array) $value );
575
  }
576
 
577
  protected function _render( $args ) {
586
  $checked = array();
587
 
588
  $opts = '';
589
+ foreach ( $choices as $value => $title ) {
590
  $single_input = scbFormField::_checkbox( array(
591
  'name' => $name . '[]',
592
  'type' => 'checkbox',
639
  }
640
  }
641
 
642
+
643
+ class scbCustomField implements scbFormField_I {
644
+
645
+ protected $args;
646
+
647
+ function __construct( $args ) {
648
+ $this->args = wp_parse_args( $args, array(
649
+ 'render' => 'var_dump',
650
+ 'sanitize' => 'wp_filter_kses',
651
+ ) );
652
+ }
653
+
654
+ public function __get( $key ) {
655
+ return $this->args[ $key ];
656
+ }
657
+
658
+ public function __isset( $key ) {
659
+ return isset( $this->args[ $key ] );
660
+ }
661
+
662
+ public function render( $value = null ) {
663
+ return call_user_func( $this->render, $value, $this );
664
+ }
665
+
666
+ public function validate( $value ) {
667
+ return call_user_func( $this->sanitize, $value, $this );
668
+ }
669
+ }
670
+
scb/Options.php CHANGED
@@ -35,27 +35,25 @@ class scbOptions {
35
  }
36
 
37
  /**
38
- * Get option values for one, many or all fields
39
  *
40
- * @param string|array $field The field(s) to get
41
  * @return mixed Whatever is in those fields
42
  */
43
- public function get( $field = '' ) {
44
- $data = get_option( $this->key, array() );
45
 
46
- $data = array_merge( $this->defaults, $data );
47
-
48
- return $this->_get( $field, $data );
49
  }
50
 
51
  /**
52
- * Get default values for one, many or all fields
53
  *
54
- * @param string|array $field The field( s ) to get
55
  * @return mixed Whatever is in those fields
56
  */
57
- public function get_defaults( $field = '' ) {
58
- return $this->_get( $field, $this->defaults );
59
  }
60
 
61
  /**
@@ -89,7 +87,7 @@ class scbOptions {
89
  * @return bool
90
  */
91
  public function cleanup() {
92
- $this->update( $this->_clean( $this->get() ) );
93
  }
94
 
95
  /**
@@ -129,19 +127,7 @@ class scbOptions {
129
  return wp_array_slice_assoc( $data, array_keys( $this->defaults ) );
130
  }
131
 
132
- // Get one, more or all fields from an array
133
  private function &_get( $field, $data ) {
134
- if ( empty( $field ) )
135
- return $data;
136
-
137
- if ( is_string( $field ) )
138
- return $data[$field];
139
-
140
- foreach ( $field as $key )
141
- if ( isset( $data[$key] ) )
142
- $result[] = $data[$key];
143
-
144
- return $result;
145
  }
146
 
147
  // Magic method: $options->field
35
  }
36
 
37
  /**
38
+ * Get option values for one or all fields
39
  *
40
+ * @param string|array $field The field to get
41
  * @return mixed Whatever is in those fields
42
  */
43
+ public function get( $field = null, $default = null ) {
44
+ $data = array_merge( $this->defaults, get_option( $this->key, array() ) );
45
 
46
+ return scbForms::get_value( $field, $data, $default );
 
 
47
  }
48
 
49
  /**
50
+ * Get default values for one or all fields
51
  *
52
+ * @param string|array $field The field to get
53
  * @return mixed Whatever is in those fields
54
  */
55
+ public function get_defaults( $field = null ) {
56
+ return scbForms::get_value( $field, $this->defaults );
57
  }
58
 
59
  /**
87
  * @return bool
88
  */
89
  public function cleanup() {
90
+ $this->update( $this->get(), true );
91
  }
92
 
93
  /**
127
  return wp_array_slice_assoc( $data, array_keys( $this->defaults ) );
128
  }
129
 
 
130
  private function &_get( $field, $data ) {
 
 
 
 
 
 
 
 
 
 
 
131
  }
132
 
133
  // Magic method: $options->field
scb/Util.php CHANGED
@@ -119,7 +119,7 @@ class scbUtil {
119
 
120
  // Return a standard admin notice
121
  function scb_admin_notice( $msg, $class = 'updated' ) {
122
- return "<div class='$class fade'><p>$msg</p></div>\n";
123
  }
124
 
125
  // Transform a list of objects into an associative array
@@ -145,6 +145,8 @@ function scb_list_fold( $list, $key, $value ) {
145
  */
146
  if ( ! function_exists( 'html' ) ):
147
  function html( $tag ) {
 
 
148
  $args = func_get_args();
149
 
150
  $tag = array_shift( $args );
@@ -165,7 +167,7 @@ function html( $tag ) {
165
  list( $closing ) = explode( ' ', $tag, 2 );
166
  }
167
 
168
- if ( in_array( $closing, array( 'area', 'base', 'basefont', 'br', 'hr', 'input', 'img', 'link', 'meta' ) ) ) {
169
  return "<{$tag} />";
170
  }
171
 
119
 
120
  // Return a standard admin notice
121
  function scb_admin_notice( $msg, $class = 'updated' ) {
122
+ return html( "div class='$class fade'", html( "p", $msg ) );
123
  }
124
 
125
  // Transform a list of objects into an associative array
145
  */
146
  if ( ! function_exists( 'html' ) ):
147
  function html( $tag ) {
148
+ static $SELF_CLOSING_TAGS = array( 'area', 'base', 'basefont', 'br', 'hr', 'input', 'img', 'link', 'meta' );
149
+
150
  $args = func_get_args();
151
 
152
  $tag = array_shift( $args );
167
  list( $closing ) = explode( ' ', $tag, 2 );
168
  }
169
 
170
+ if ( in_array( $closing, $SELF_CLOSING_TAGS ) ) {
171
  return "<{$tag} />";
172
  }
173
 
scb/load.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- $GLOBALS['_scb_data'] = array( 54, __FILE__, array(
4
  'scbUtil', 'scbOptions', 'scbForms', 'scbTable',
5
  'scbWidget', 'scbAdminPage', 'scbBoxesPage',
6
  'scbCron', 'scbHooks',
@@ -32,8 +32,10 @@ class scbLoad4 {
32
  add_action( 'activate_plugin', array( __CLASS__, 'delayed_activation' ) );
33
  }
34
 
35
- // TODO: don't load when activating a plugin ?
36
- add_action( 'plugins_loaded', array( __CLASS__, 'load' ), 9, 0 );
 
 
37
  }
38
 
39
  static function delayed_activation( $plugin ) {
1
  <?php
2
 
3
+ $GLOBALS['_scb_data'] = array( 56, __FILE__, array(
4
  'scbUtil', 'scbOptions', 'scbForms', 'scbTable',
5
  'scbWidget', 'scbAdminPage', 'scbBoxesPage',
6
  'scbCron', 'scbHooks',
32
  add_action( 'activate_plugin', array( __CLASS__, 'delayed_activation' ) );
33
  }
34
 
35
+ if ( did_action( 'plugins_loaded' ) )
36
+ self::load();
37
+ else
38
+ add_action( 'plugins_loaded', array( __CLASS__, 'load' ), 9, 0 );
39
  }
40
 
41
  static function delayed_activation( $plugin ) {
screenshot-1.png CHANGED
Binary file
screenshot-2.png CHANGED
Binary file
screenshot-4.png CHANGED
Binary file