Posts 2 Posts - Version 1.3

Version Description

  • allow passing entire objects to get_connected(), connect() etc.
  • made get_related() work with posts-to-users connections
  • made each_connected() work with simple array of posts
  • introduced [p2p_connected] and [p2p_related] shortcodes
  • allow 'default' parameter in 'fields' array
  • more info
Download this release

Release Info

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

Code changes from version 1.2 to 1.3

admin/box.js CHANGED
@@ -1,49 +1,57 @@
1
- (function(){
2
- jQuery(function(){
3
- var setVal, clearVal;
 
 
4
  if (!jQuery('<input placeholder="1" />')[0].placeholder) {
5
- setVal = function(){
6
  var $this;
7
  $this = jQuery(this);
8
  if (!$this.val()) {
9
  $this.val($this.attr('placeholder'));
10
  $this.addClass('p2p-placeholder');
11
  }
 
12
  };
13
- clearVal = function(){
14
  var $this;
15
  $this = jQuery(this);
16
  if ($this.hasClass('p2p-placeholder')) {
17
  $this.val('');
18
  $this.removeClass('p2p-placeholder');
19
  }
 
20
  };
21
  jQuery('.p2p-search input[placeholder]').each(setVal).focus(clearVal).blur(setVal);
22
  }
23
- return jQuery('.p2p-box').each(function(){
24
- var $metabox, $connections, $spinner, ajax_request, PostsTab, searchTab, row_ajax_request, maybe_hide_table, append_connection, refresh_candidates, clear_connections, delete_connection, create_connection, switch_to_tab, $viewAll, $searchInput, $createButton, $createInput;
25
  $metabox = jQuery(this);
26
  $connections = $metabox.find('.p2p-connections');
27
  $spinner = jQuery('<img>', {
28
  'src': P2PAdmin.spinner,
29
  'class': 'p2p-spinner'
30
  });
31
- ajax_request = function(data, callback, type){
32
  var handler;
33
- type == null && (type = 'POST');
34
- data.action = 'p2p_box';
35
- data.nonce = P2PAdmin.nonce;
36
- data.p2p_type = $metabox.data('p2p_type');
37
- data.direction = $metabox.data('direction');
38
- data.from = jQuery('#post_ID').val();
39
- data.s = searchTab.params.s;
40
- data.paged = searchTab.params.paged;
41
- handler = function(response){
 
 
 
 
42
  try {
43
  response = jQuery.parseJSON(response);
44
  return callback(response);
45
  } catch (e) {
46
- return typeof console != 'undefined' && console !== null ? console.error('Malformed response', response) : void 8;
47
  }
48
  };
49
  return jQuery.ajax({
@@ -53,28 +61,34 @@
53
  success: handler
54
  });
55
  };
56
- PostsTab = (function(){
57
- PostsTab.displayName = 'PostsTab';
58
- var prototype = PostsTab.prototype, constructor = PostsTab;
59
- function PostsTab(selector){
 
 
60
  this.tab = $metabox.find(selector);
61
  this.params = {
62
  subaction: 'search',
63
  s: ''
64
  };
65
  this.init_pagination_data();
66
- this.tab.delegate('.p2p-prev, .p2p-next', 'click', __bind(this, this.change_page));
 
 
67
  }
68
- prototype.init_pagination_data = function(){
 
69
  this.params.paged = this.tab.find('.p2p-current').data('num') || 1;
70
  return this.total_pages = this.tab.find('.p2p-total').data('num') || 1;
71
  };
72
- prototype.change_page = function(ev){
 
73
  var $navButton, new_page;
74
- $navButton = jQuery(ev.target);
75
  new_page = this.params.paged;
76
  if ($navButton.hasClass('inactive')) {
77
- return false;
78
  }
79
  if ($navButton.hasClass('p2p-prev')) {
80
  new_page--;
@@ -82,16 +96,20 @@
82
  new_page++;
83
  }
84
  $spinner.appendTo(this.tab.find('.p2p-navigation'));
85
- this.find_posts(new_page);
86
- return false;
87
  };
88
- prototype.find_posts = function(new_page){
89
- if (0 < new_page && new_page <= this.total_pages) {
 
 
90
  this.params.paged = new_page;
91
  }
92
- return ajax_request(this.params, __bind(this, this.update_rows), 'GET');
 
 
93
  };
94
- prototype.update_rows = function(response){
 
95
  $spinner.remove();
96
  this.tab.find('button, .p2p-results, .p2p-navigation, .p2p-notice').remove();
97
  if (!response.rows) {
@@ -101,30 +119,33 @@
101
  return this.init_pagination_data();
102
  }
103
  };
 
104
  return PostsTab;
105
- }());
 
106
  searchTab = new PostsTab('.p2p-tab-search');
107
- row_ajax_request = function($td, data, callback){
108
  $td.html($spinner.show());
109
  return ajax_request(data, callback);
110
  };
111
- maybe_hide_table = function($table){
112
  if (!$table.find('tbody tr').length) {
113
  return $table.hide();
114
  }
115
  };
116
- append_connection = function(response){
117
  $connections.show().find('tbody').append(response.row);
118
- if ('one' == $metabox.data('cardinality')) {
119
  return $metabox.find('.p2p-create-connections').hide();
120
  }
121
  };
122
- refresh_candidates = function(results){
123
  $metabox.find('.p2p-create-connections').show();
124
  return searchTab.update_rows(results);
125
  };
126
- clear_connections = function(ev){
127
- var $self, $td, data, _this = this;
 
128
  if (!confirm(P2PAdmin.deleteConfirmMessage)) {
129
  return false;
130
  }
@@ -133,37 +154,39 @@
133
  data = {
134
  subaction: 'clear_connections'
135
  };
136
- row_ajax_request($td, data, function(response){
137
  $connections.hide().find('tbody').html('');
138
  $td.html($self);
139
  return refresh_candidates(response);
140
  });
141
  return false;
142
  };
143
- delete_connection = function(ev){
144
- var $self, $td, data, _this = this;
 
145
  $self = jQuery(ev.target);
146
  $td = $self.closest('td');
147
  data = {
148
  subaction: 'disconnect',
149
  p2p_id: $self.data('p2p_id')
150
  };
151
- row_ajax_request($td, data, function(response){
152
  $td.closest('tr').remove();
153
  maybe_hide_table($connections);
154
  return refresh_candidates(response);
155
  });
156
  return false;
157
  };
158
- create_connection = function(ev){
159
- var $self, $td, data, _this = this;
 
160
  $self = jQuery(ev.target);
161
  $td = $self.closest('td');
162
  data = {
163
  subaction: 'connect',
164
  to: $self.data('post_id')
165
  };
166
- row_ajax_request($td, data, function(response){
167
  append_connection(response);
168
  if ($metabox.data('prevent_duplicates')) {
169
  $td.closest('tr').remove();
@@ -174,7 +197,7 @@
174
  });
175
  return false;
176
  };
177
- switch_to_tab = function(){
178
  var $tab;
179
  $tab = jQuery(this);
180
  $metabox.find('.wp-tab-bar li').removeClass('wp-tab-active');
@@ -186,8 +209,8 @@
186
  if ($connections.find('th.p2p-col-order').length) {
187
  $connections.find('tbody').sortable({
188
  handle: 'td.p2p-col-order',
189
- helper: function(e, ui){
190
- ui.children().each(function(){
191
  var $this;
192
  $this = jQuery(this);
193
  return $this.width($this.width());
@@ -197,21 +220,21 @@
197
  });
198
  }
199
  $viewAll = $metabox.find('.p2p-tab-search button');
200
- $viewAll.click(function(){
201
  searchTab.find_posts(1);
202
  return false;
203
  });
204
  $searchInput = $metabox.find('.p2p-tab-search :text');
205
- $searchInput.keypress(function(ev){
206
- if (13 === ev.keyCode) {
207
  return false;
208
  }
209
- }).keyup(function(ev){
210
  var delayed;
211
- if (undefined !== delayed) {
212
  clearTimeout(delayed);
213
  }
214
- return delayed = setTimeout(function(){
215
  var searchStr;
216
  searchStr = $searchInput.val();
217
  if (searchStr === searchTab.params.s) {
@@ -224,14 +247,14 @@
224
  });
225
  $createButton = $metabox.find('.p2p-tab-create-post button');
226
  $createInput = $metabox.find('.p2p-tab-create-post :text');
227
- $createButton.click(function(){
228
- var $button, title, data;
229
  $button = jQuery(this);
230
  if ($button.hasClass('inactive')) {
231
  return false;
232
  }
233
  title = $createInput.val();
234
- if ('' === title) {
235
  $createInput.focus();
236
  return false;
237
  }
@@ -240,14 +263,14 @@
240
  subaction: 'create_post',
241
  post_title: title
242
  };
243
- ajax_request(data, function(response){
244
  append_connection(response);
245
  $createInput.val('');
246
  return $button.removeClass('inactive');
247
  });
248
  return false;
249
  });
250
- return $createInput.keypress(function(ev){
251
  if (13 === ev.keyCode) {
252
  $createButton.click();
253
  return false;
@@ -255,5 +278,5 @@
255
  });
256
  });
257
  });
258
- function __bind(me, fn){ return function(){ return fn.apply(me, arguments) } }
259
  }).call(this);
1
+ // Generated by CoffeeScript 1.3.1
2
+ (function() {
3
+
4
+ jQuery(function() {
5
+ var clearVal, setVal;
6
  if (!jQuery('<input placeholder="1" />')[0].placeholder) {
7
+ setVal = function() {
8
  var $this;
9
  $this = jQuery(this);
10
  if (!$this.val()) {
11
  $this.val($this.attr('placeholder'));
12
  $this.addClass('p2p-placeholder');
13
  }
14
+ return;
15
  };
16
+ clearVal = function() {
17
  var $this;
18
  $this = jQuery(this);
19
  if ($this.hasClass('p2p-placeholder')) {
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, maybe_hide_table, refresh_candidates, row_ajax_request, searchTab, switch_to_tab;
29
  $metabox = jQuery(this);
30
  $connections = $metabox.find('.p2p-connections');
31
  $spinner = jQuery('<img>', {
32
  'src': P2PAdmin.spinner,
33
  'class': 'p2p-spinner'
34
  });
35
+ ajax_request = function(data, callback, type) {
36
  var handler;
37
+ if (type == null) {
38
+ type = 'POST';
39
+ }
40
+ jQuery.extend(data, {
41
+ action: 'p2p_box',
42
+ nonce: P2PAdmin.nonce,
43
+ p2p_type: $metabox.data('p2p_type'),
44
+ direction: $metabox.data('direction'),
45
+ from: jQuery('#post_ID').val(),
46
+ s: searchTab.params.s,
47
+ paged: searchTab.params.paged
48
+ });
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
  }
56
  };
57
  return jQuery.ajax({
61
  success: handler
62
  });
63
  };
64
+ PostsTab = (function() {
65
+
66
+ PostsTab.name = 'PostsTab';
67
+
68
+ function PostsTab(selector) {
69
+ var _this = this;
70
  this.tab = $metabox.find(selector);
71
  this.params = {
72
  subaction: 'search',
73
  s: ''
74
  };
75
  this.init_pagination_data();
76
+ this.tab.delegate('.p2p-prev, .p2p-next', 'click', function(ev) {
77
+ return _this.change_page(ev.target);
78
+ });
79
  }
80
+
81
+ PostsTab.prototype.init_pagination_data = function() {
82
  this.params.paged = this.tab.find('.p2p-current').data('num') || 1;
83
  return this.total_pages = this.tab.find('.p2p-total').data('num') || 1;
84
  };
85
+
86
+ PostsTab.prototype.change_page = function(button) {
87
  var $navButton, new_page;
88
+ $navButton = jQuery(button);
89
  new_page = this.params.paged;
90
  if ($navButton.hasClass('inactive')) {
91
+ return;
92
  }
93
  if ($navButton.hasClass('p2p-prev')) {
94
  new_page--;
96
  new_page++;
97
  }
98
  $spinner.appendTo(this.tab.find('.p2p-navigation'));
99
+ return this.find_posts(new_page);
 
100
  };
101
+
102
+ PostsTab.prototype.find_posts = function(new_page) {
103
+ var _this = this;
104
+ if ((0 < new_page && new_page <= this.total_pages)) {
105
  this.params.paged = new_page;
106
  }
107
+ return ajax_request(this.params, function(response) {
108
+ return _this.update_rows(response);
109
+ }, 'GET');
110
  };
111
+
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) {
119
  return this.init_pagination_data();
120
  }
121
  };
122
+
123
  return PostsTab;
124
+
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
+ maybe_hide_table = function($table) {
132
  if (!$table.find('tbody tr').length) {
133
  return $table.hide();
134
  }
135
  };
136
+ append_connection = function(response) {
137
  $connections.show().find('tbody').append(response.row);
138
+ if ('one' === $metabox.data('cardinality')) {
139
  return $metabox.find('.p2p-create-connections').hide();
140
  }
141
  };
142
+ refresh_candidates = function(results) {
143
  $metabox.find('.p2p-create-connections').show();
144
  return searchTab.update_rows(results);
145
  };
146
+ clear_connections = function(ev) {
147
+ var $self, $td, data,
148
+ _this = this;
149
  if (!confirm(P2PAdmin.deleteConfirmMessage)) {
150
  return false;
151
  }
154
  data = {
155
  subaction: 'clear_connections'
156
  };
157
+ row_ajax_request($td, data, function(response) {
158
  $connections.hide().find('tbody').html('');
159
  $td.html($self);
160
  return refresh_candidates(response);
161
  });
162
  return false;
163
  };
164
+ delete_connection = function(ev) {
165
+ var $self, $td, data,
166
+ _this = this;
167
  $self = jQuery(ev.target);
168
  $td = $self.closest('td');
169
  data = {
170
  subaction: 'disconnect',
171
  p2p_id: $self.data('p2p_id')
172
  };
173
+ row_ajax_request($td, data, function(response) {
174
  $td.closest('tr').remove();
175
  maybe_hide_table($connections);
176
  return refresh_candidates(response);
177
  });
178
  return false;
179
  };
180
+ create_connection = function(ev) {
181
+ var $self, $td, data,
182
+ _this = this;
183
  $self = jQuery(ev.target);
184
  $td = $self.closest('td');
185
  data = {
186
  subaction: 'connect',
187
  to: $self.data('post_id')
188
  };
189
+ row_ajax_request($td, data, function(response) {
190
  append_connection(response);
191
  if ($metabox.data('prevent_duplicates')) {
192
  $td.closest('tr').remove();
197
  });
198
  return false;
199
  };
200
+ switch_to_tab = function() {
201
  var $tab;
202
  $tab = jQuery(this);
203
  $metabox.find('.wp-tab-bar li').removeClass('wp-tab-active');
209
  if ($connections.find('th.p2p-col-order').length) {
210
  $connections.find('tbody').sortable({
211
  handle: 'td.p2p-col-order',
212
+ helper: function(e, ui) {
213
+ ui.children().each(function() {
214
  var $this;
215
  $this = jQuery(this);
216
  return $this.width($this.width());
220
  });
221
  }
222
  $viewAll = $metabox.find('.p2p-tab-search button');
223
+ $viewAll.click(function() {
224
  searchTab.find_posts(1);
225
  return false;
226
  });
227
  $searchInput = $metabox.find('.p2p-tab-search :text');
228
+ $searchInput.keypress(function(ev) {
229
+ if (ev.keyCode === 13) {
230
  return false;
231
  }
232
+ }).keyup(function(ev) {
233
  var delayed;
234
+ if (delayed !== void 0) {
235
  clearTimeout(delayed);
236
  }
237
+ return delayed = setTimeout(function() {
238
  var searchStr;
239
  searchStr = $searchInput.val();
240
  if (searchStr === searchTab.params.s) {
247
  });
248
  $createButton = $metabox.find('.p2p-tab-create-post button');
249
  $createInput = $metabox.find('.p2p-tab-create-post :text');
250
+ $createButton.click(function() {
251
+ var $button, data, title;
252
  $button = jQuery(this);
253
  if ($button.hasClass('inactive')) {
254
  return false;
255
  }
256
  title = $createInput.val();
257
+ if (title === '') {
258
  $createInput.focus();
259
  return false;
260
  }
263
  subaction: 'create_post',
264
  post_title: title
265
  };
266
+ ajax_request(data, function(response) {
267
  append_connection(response);
268
  $createInput.val('');
269
  return $button.removeClass('inactive');
270
  });
271
  return false;
272
  });
273
+ return $createInput.keypress(function(ev) {
274
  if (13 === ev.keyCode) {
275
  $createButton.click();
276
  return false;
278
  });
279
  });
280
  });
281
+
282
  }).call(this);
admin/box.php CHANGED
@@ -14,6 +14,12 @@ class P2P_Box {
14
 
15
  private $columns;
16
 
 
 
 
 
 
 
17
  function __construct( $args, $ctype ) {
18
  $this->args = $args;
19
 
@@ -70,12 +76,15 @@ class P2P_Box {
70
  echo P2P_Mustache::render( 'box', $data );
71
  }
72
 
73
- protected function get_folded_connections( $post_id ) {
74
- $side = $this->ctype->get_opposite( 'side' );
 
 
 
75
 
76
- $query = $this->ctype->get_connected( $post_id, $side->get_connections_qv() );
77
 
78
- return scb_list_fold( $side->abstract_query( $query )->items, 'p2p_id', 'ID' );
79
  }
80
 
81
  protected function render_data_attributes() {
@@ -175,7 +184,13 @@ class P2P_Box {
175
  }
176
 
177
  protected function post_rows( $current_post_id, $page = 1, $search = '' ) {
178
- $candidate = $this->ctype->get_connectable( $current_post_id, $page, $search );
 
 
 
 
 
 
179
 
180
  if ( empty( $candidate->items ) )
181
  return false;
@@ -242,10 +257,10 @@ class P2P_Box {
242
 
243
  $p2p_id = $this->ctype->connect( $from, $to );
244
 
245
- if ( $p2p_id )
246
- $r = array( 'row' => $this->connection_row( $p2p_id, $to, true ) );
247
  else
248
- $r = array( 'error' => __( "Can't create connection.", P2P_TEXTDOMAIN ) );
249
 
250
  die( json_encode( $r ) );
251
  }
@@ -257,7 +272,7 @@ class P2P_Box {
257
  }
258
 
259
  public function ajax_clear_connections() {
260
- $this->ctype->disconnect_all( $_POST['from'] );
261
 
262
  $this->refresh_candidates();
263
  }
@@ -305,7 +320,9 @@ class P2P_Box {
305
  }
306
 
307
  public function check_capability() {
308
- return $this->ctype->get_opposite( 'side' )->check_capability();
 
 
309
  }
310
  }
311
 
14
 
15
  private $columns;
16
 
17
+ private static $admin_box_qv = array(
18
+ 'update_post_term_cache' => false,
19
+ 'update_post_meta_cache' => false,
20
+ 'post_status' => 'any',
21
+ );
22
+
23
  function __construct( $args, $ctype ) {
24
  $this->args = $args;
25
 
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() {
184
  }
185
 
186
  protected function post_rows( $current_post_id, $page = 1, $search = '' ) {
187
+ $extra_qv = array_merge( self::$admin_box_qv, array(
188
+ 'p2p:search' => $search,
189
+ 'p2p:page' => $page,
190
+ 'p2p:per_page' => 5
191
+ ) );
192
+
193
+ $candidate = $this->ctype->get_connectable( $current_post_id, $extra_qv );
194
 
195
  if ( empty( $candidate->items ) )
196
  return false;
257
 
258
  $p2p_id = $this->ctype->connect( $from, $to );
259
 
260
+ if ( is_wp_error( $p2p_id ) )
261
+ $r = array( 'error' => sprintf( __( "Can't create connection: %s", P2P_TEXTDOMAIN ), $p2p_id->get_error_message() ) );
262
  else
263
+ $r = array( 'row' => $this->connection_row( $p2p_id, $to, true ) );
264
 
265
  die( json_encode( $r ) );
266
  }
272
  }
273
 
274
  public function ajax_clear_connections() {
275
+ $this->ctype->disconnect( $_POST['from'], 'any' );
276
 
277
  $this->refresh_candidates();
278
  }
320
  }
321
 
322
  public function check_capability() {
323
+ $show = $this->ctype->get_opposite( 'side' )->check_capability();
324
+
325
+ return apply_filters( 'p2p_admin_box_show', $show, $GLOBALS['post'], $this->ctype );
326
  }
327
  }
328
 
admin/column.php CHANGED
@@ -9,7 +9,9 @@ class P2P_Column {
9
  function __construct( $directed ) {
10
  $this->ctype = $directed;
11
 
12
- $this->ctype->lose_direction()->each_connected( $GLOBALS['wp_query'] );
 
 
13
 
14
  $this->connected = scb_list_fold( $GLOBALS['wp_query']->posts, 'ID', 'connected' );
15
  }
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
  }
admin/fields.php CHANGED
@@ -83,8 +83,13 @@ class P2P_Field_Generic implements P2P_Field {
83
  if ( isset( $this->data['values'] ) )
84
  $args['value'] = $this->data['values'];
85
 
86
- if ( 'select' == $args['type'] )
 
 
 
 
87
  $args['text'] = '';
 
88
 
89
  return scbForms::input_from_meta( $args, $p2p_id, 'p2p' );
90
  }
83
  if ( isset( $this->data['values'] ) )
84
  $args['value'] = $this->data['values'];
85
 
86
+ if ( isset( $this->data['default'] ) ) {
87
+ $args['default'] = $this->data['default'];
88
+ if ( 'checkbox' == $args['type'] )
89
+ $args['default'] = (array) $args['default'];
90
+ } elseif ( 'select' == $args['type'] ) {
91
  $args['text'] = '';
92
+ }
93
 
94
  return scbForms::input_from_meta( $args, $p2p_id, 'p2p' );
95
  }
core/api.php CHANGED
@@ -304,52 +304,48 @@ function p2p_delete_meta( $p2p_id, $key, $value = '' ) {
304
  }
305
 
306
  /**
307
- * List some posts.
308
  *
309
- * @param object|array A WP_Query instance, or a list of post objects
310
  * @param array $args (optional)
311
  */
312
  function p2p_list_posts( $posts, $args = array() ) {
313
- if ( is_object( $posts ) )
314
- $posts = $posts->posts;
315
-
316
- $args = wp_parse_args( $args, array(
317
- 'before_list' => '<ul>', 'after_list' => '</ul>',
318
- 'before_item' => '<li>', 'after_item' => '</li>',
319
- 'separator' => false,
320
- 'template' => false
321
- ) );
322
-
323
- extract( $args, EXTR_SKIP );
324
-
325
- if ( empty( $posts ) )
326
- return;
327
-
328
- echo $before_list;
329
-
330
- $i = 0;
331
-
332
- foreach ( $posts as $post ) {
333
- $GLOBALS['post'] = $post;
334
-
335
- setup_postdata( $post );
336
-
337
- if ( !$separator ) echo $before_item;
338
-
339
- if ( $template )
340
- locate_template( $template, true, false );
341
- else
342
- if ( 0 < $i && $separator ) echo $separator;
343
 
344
- echo html( 'a', array( 'href' => get_permalink() ), get_the_title() );
 
345
 
346
- if ( !$separator ) echo $after_item;
 
 
 
 
 
 
 
 
 
347
 
348
- $i++;
 
 
349
  }
350
 
351
- echo $after_list;
 
 
 
 
 
 
 
 
352
 
353
- wp_reset_postdata();
 
354
  }
355
 
304
  }
305
 
306
  /**
307
+ * List some items.
308
  *
309
+ * @param object|array A P2P_List instance, a WP_Query instance, or a list of post objects
310
  * @param array $args (optional)
311
  */
312
  function p2p_list_posts( $posts, $args = array() ) {
313
+ if ( is_a( $posts, 'P2P_List' ) ) {
314
+ $list = $posts;
315
+ } else {
316
+ $list = new P2P_List_Post( $posts );
317
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
 
319
+ return $list->render( $args );
320
+ }
321
 
322
+ /**
323
+ * Given a list of objects and another list of connected items,
324
+ * distribute each connected item to it's respective counterpart.
325
+ *
326
+ * @param array List of objects
327
+ * @param array List of connected objects
328
+ * @param string Name of connected array property
329
+ */
330
+ function p2p_distribute_connected( $items, $connected, $prop_name ) {
331
+ $indexed_list = array();
332
 
333
+ foreach ( $items as $item ) {
334
+ $item->$prop_name = array();
335
+ $indexed_list[ $item->ID ] = $item;
336
  }
337
 
338
+ foreach ( $connected as $inner_item ) {
339
+ if ( $inner_item->ID == $inner_item->p2p_from ) {
340
+ $outer_item_id = $inner_item->p2p_to;
341
+ } elseif ( $inner_item->ID == $inner_item->p2p_to ) {
342
+ $outer_item_id = $inner_item->p2p_from;
343
+ } else {
344
+ trigger_error( "Corrupted data for item $inner_item->ID", E_USER_WARNING );
345
+ continue;
346
+ }
347
 
348
+ array_push( $indexed_list[ $outer_item_id ]->$prop_name, $inner_item );
349
+ }
350
  }
351
 
core/directed-type.php CHANGED
@@ -30,6 +30,15 @@ class P2P_Directed_Connection_Type {
30
  return $this->ctype;
31
  }
32
 
 
 
 
 
 
 
 
 
 
33
  public function get_opposite( $key ) {
34
  $direction = ( 'to' == $this->direction ) ? 'from' : 'to';
35
 
@@ -48,30 +57,58 @@ class P2P_Directed_Connection_Type {
48
  return $arg[$direction];
49
  }
50
 
 
 
 
 
 
 
 
 
 
 
 
51
  /**
52
- * Get a list of posts that are connected to a given post.
53
  *
54
- * @param int|array $post_id A post id or an array of post ids.
55
  * @param array $extra_qv Additional query variables to use.
56
  *
57
- * @return object
58
  */
59
- public function get_connected( $post_id, $extra_qv = array(), $output = 'raw' ) {
60
- $args = array_merge( $extra_qv, array(
61
- 'connected_items' => $post_id
62
- ) );
63
 
64
- $side = $this->get_opposite( 'side' );
65
 
66
- $query = $side->do_query( $this->get_connected_args( $args ) );
67
 
68
- if ( 'abstract' == $output )
69
- $query = $side->abstract_query( $query );
70
 
71
- return $query;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  }
73
 
74
  public function get_connected_args( $q ) {
 
 
 
 
75
  if ( $orderby_key = $this->get_orderby_key() ) {
76
  $q = wp_parse_args( $q, array(
77
  'connected_orderby' => $orderby_key,
@@ -80,7 +117,7 @@ class P2P_Directed_Connection_Type {
80
  ) );
81
  }
82
 
83
- $q = array_merge( $this->get_opposite( 'side' )->get_base_qv(), $q, array(
84
  'p2p_type' => array( $this->name => $this->get_direction() ),
85
  ) );
86
 
@@ -106,18 +143,20 @@ class P2P_Directed_Connection_Type {
106
  }
107
 
108
  /**
109
- * Get a list of posts that could be connected to a given post.
110
  *
111
  * @param int $post_id A post id.
112
  */
113
- public function get_connectable( $item_id, $page, $search ) {
114
  $side = $this->get_opposite( 'side' );
115
 
116
- $qv = $side->get_connectable_qv( $item_id, $page, $search, $this->get_non_connectable( $item_id ) );
117
 
118
- $qv = apply_filters( 'p2p_connectable_args', $qv, $this, $item_id );
119
 
120
- return $side->abstract_query( $side->do_query( $qv ) );
 
 
121
  }
122
 
123
  private function get_non_connectable( $item_id ) {
@@ -145,17 +184,19 @@ class P2P_Directed_Connection_Type {
145
  /**
146
  * Connect two items.
147
  *
148
- * @param int The first end of the connection.
149
- * @param int The second end of the connection.
150
  * @param array Additional information about the connection.
151
  *
152
  * @return int|object p2p_id or WP_Error on failure
153
  */
154
  public function connect( $from, $to, $meta = array() ) {
155
- if ( !$this->get_current( 'side' )->item_exists( $from ) )
 
156
  return new WP_Error( 'first_parameter', 'Invalid first parameter.' );
157
 
158
- if ( !$this->get_opposite( 'side' )->item_exists( $to ) )
 
159
  return new WP_Error( 'second_parameter', 'Invalid second parameter.' );
160
 
161
  if ( !$this->self_connections && $from == $to )
@@ -178,22 +219,25 @@ class P2P_Directed_Connection_Type {
178
  }
179
 
180
  /**
181
- * Disconnect two posts.
 
 
 
182
  *
183
- * @param int The first end of the connection.
184
- * @param int The second end of the connection.
185
  */
186
  public function disconnect( $from, $to ) {
187
- return $this->delete_connections( compact( 'from', 'to' ) );
188
- }
 
189
 
190
- /**
191
- * Delete all connections for a certain post.
192
- *
193
- * @param int The post id.
194
- */
195
- public function disconnect_all( $from ) {
196
- return $this->delete_connections( compact( 'from' ) );
197
  }
198
 
199
  public function get_p2p_id( $from, $to ) {
30
  return $this->ctype;
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 ) {
43
  $direction = ( 'to' == $this->direction ) ? 'from' : 'to';
44
 
57
  return $arg[$direction];
58
  }
59
 
60
+ private function abstract_query( $qv, $side, $output = 'abstract' ) {
61
+ $query = $side->do_query( $qv );
62
+
63
+ if ( 'raw' == $output )
64
+ return $query;
65
+
66
+ $class = str_replace( 'P2P_Side_', 'P2P_List_', get_class( $side ) );
67
+
68
+ return new $class( $query );
69
+ }
70
+
71
  /**
72
+ * Get a list of posts connected to other posts connected to a post.
73
  *
74
+ * @param mixed $item An object, an object id or an array of such.
75
  * @param array $extra_qv Additional query variables to use.
76
  *
77
+ * @return bool|object False on failure; A WP_Query instance on success.
78
  */
79
+ public function get_related( $item, $extra_qv = array(), $output = 'raw' ) {
80
+ $extra_qv['fields'] = 'ids';
 
 
81
 
82
+ $connected = $this->get_connected( $item, $extra_qv, 'abstract' );
83
 
84
+ $additional_qv = array( 'p2p:exclude' => _p2p_normalize( $item ) );
85
 
86
+ return $this->flip_direction()->get_connected( $connected->items, $additional_qv, $output );
87
+ }
88
 
89
+ /**
90
+ * Get a list of items that are connected to a given item.
91
+ *
92
+ * @param mixed $item An object, an object id or an array of such.
93
+ * @param array $extra_qv Additional query variables to use.
94
+ *
95
+ * @return object
96
+ */
97
+ public function get_connected( $item, $extra_qv = array(), $output = 'raw' ) {
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,
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
 
143
  }
144
 
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 ) {
184
  /**
185
  * Connect two items.
186
  *
187
+ * @param mixed The first end of the connection.
188
+ * @param mixed The second end of the connection.
189
  * @param array Additional information about the connection.
190
  *
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 )
219
  }
220
 
221
  /**
222
+ * Disconnect two items.
223
+ *
224
+ * @param mixed The first end of the connection.
225
+ * @param mixed The second end of the connection or 'any'.
226
  *
227
+ * @return int|object count or WP_Error on failure
 
228
  */
229
  public function disconnect( $from, $to ) {
230
+ $from = $this->get_current( 'side' )->item_id( $from );
231
+ if ( !$from )
232
+ return new WP_Error( 'first_parameter', 'Invalid first parameter.' );
233
 
234
+ if ( 'any' != $to ) {
235
+ $to = $this->get_opposite( 'side' )->item_id( $to );
236
+ if ( !$to )
237
+ return new WP_Error( 'second_parameter', 'Invalid second parameter.' );
238
+ }
239
+
240
+ return $this->delete_connections( compact( 'from', 'to' ) );
241
  }
242
 
243
  public function get_p2p_id( $from, $to ) {
core/extra.php ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class P2P_Widget extends scbWidget {
4
+
5
+ protected $defaults = array(
6
+ 'ctype' => false,
7
+ 'listing' => 'connected',
8
+ 'title' => ''
9
+ );
10
+
11
+ static function init() {
12
+ parent::init( __CLASS__, false, 'p2p' );
13
+ }
14
+
15
+ function __construct() {
16
+ parent::__construct( 'p2p', __( 'Posts 2 Posts', P2P_TEXTDOMAIN ), array(
17
+ 'description' => __( 'A list of posts connected to the current post', P2P_TEXTDOMAIN )
18
+ ) );
19
+ }
20
+
21
+ function form( $instance ) {
22
+ if ( empty( $instance ) )
23
+ $instance = $this->defaults;
24
+
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
+
34
+ echo html( 'p', $this->input( array(
35
+ 'type' => 'text',
36
+ 'name' => 'title',
37
+ 'desc' => __( 'Title:', P2P_TEXTDOMAIN )
38
+ ), $instance ) );
39
+
40
+ echo html( 'p', $this->input( array(
41
+ 'type' => 'select',
42
+ 'name' => 'ctype',
43
+ 'values' => $ctypes,
44
+ 'desc' => __( 'Connection type:', P2P_TEXTDOMAIN )
45
+ ), $instance ) );
46
+
47
+ echo html( 'p',
48
+ __( 'Connection listing:', P2P_TEXTDOMAIN ),
49
+ '<br>',
50
+ $this->input( array(
51
+ 'type' => 'radio',
52
+ 'name' => 'listing',
53
+ 'values' => array(
54
+ 'connected' => __( 'connected', P2P_TEXTDOMAIN ),
55
+ 'related' => __( 'related', P2P_TEXTDOMAIN )
56
+ ),
57
+ ), $instance )
58
+ );
59
+ }
60
+
61
+ function widget( $args, $instance ) {
62
+ $instance = array_merge( $this->defaults, $instance );
63
+
64
+ $output = _p2p_get_list( array(
65
+ 'ctype' => $instance['ctype'],
66
+ 'method' => ( 'related' == $instance['listing'] ? 'get_related' : 'get_connected' ),
67
+ 'item' => get_queried_object(),
68
+ 'mode' => 'ul',
69
+ 'context' => 'widget'
70
+ ) );
71
+
72
+ if ( !$output )
73
+ return;
74
+
75
+ $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
76
+
77
+ extract( $args );
78
+
79
+ echo $before_widget;
80
+
81
+ if ( ! empty( $title ) )
82
+ echo $before_title . $title . $after_title;
83
+
84
+ echo $output;
85
+
86
+ echo $after_widget;
87
+ }
88
+ }
89
+
90
+
91
+ class P2P_Shortcodes {
92
+
93
+ static function init() {
94
+ add_shortcode( 'p2p_connected', array( __CLASS__, 'connected' ) );
95
+ add_shortcode( 'p2p_related', array( __CLASS__, 'related' ) );
96
+ }
97
+
98
+ static function connected( $attr ) {
99
+ return self::get_list( $attr, 'get_connected' );
100
+ }
101
+
102
+ static function related( $attr ) {
103
+ return self::get_list( $attr, 'get_related' );
104
+ }
105
+
106
+ private static function get_list( $attr, $method ) {
107
+ global $post;
108
+
109
+ $attr = shortcode_atts( array(
110
+ 'type' => '',
111
+ 'mode' => 'ul',
112
+ ), $attr );
113
+
114
+ return _p2p_get_list( array(
115
+ 'ctype' => $attr['type'],
116
+ 'method' => $method,
117
+ 'item' => $post,
118
+ 'mode' => $attr['mode'],
119
+ 'context' => 'shortcode'
120
+ ) );
121
+ }
122
+ }
123
+
124
+
125
+ /** @internal */
126
+ function _p2p_get_list( $args ) {
127
+ extract( $args );
128
+
129
+ $ctype = p2p_type( $ctype );
130
+ if ( !$ctype ) {
131
+ trigger_error( sprintf( "Unregistered connection type '%s'.", $ctype ), E_USER_WARNING );
132
+ return '';
133
+ }
134
+
135
+ $directed = $ctype->find_direction( $item );
136
+ if ( !$directed )
137
+ return '';
138
+
139
+ $extra_qv = array(
140
+ 'p2p:per_page' => -1,
141
+ 'p2p:context' => $context
142
+ );
143
+
144
+ $connected = $directed->$method( $item, $extra_qv, 'abstract' );
145
+
146
+ $args = array(
147
+ 'before_list' => '<ul id="' . $ctype->name . '_list">',
148
+ 'echo' => false
149
+ );
150
+
151
+ if ( 'ol' == $mode ) {
152
+ _p2p_append( $args, array(
153
+ 'before_list' => '<ol id="' . $ctype->name . '_list">',
154
+ 'after_list' => '</ol>',
155
+ ) );
156
+ }
157
+
158
+ return apply_filters( "p2p_{$context}_html", $connected->render( $args ), $connected, $directed, $mode );
159
+ }
160
+
core/list.php ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ abstract class P2P_List {
4
+
5
+ public $items;
6
+ public $current_page = 1;
7
+ public $total_pages = 0;
8
+
9
+ function render( $args = array() ) {
10
+ if ( empty( $this->items ) )
11
+ return '';
12
+
13
+ $args = wp_parse_args( $args, array(
14
+ 'before_list' => '<ul>', 'after_list' => '</ul>',
15
+ 'before_item' => '<li>', 'after_item' => '</li>',
16
+ 'separator' => false,
17
+ 'echo' => true
18
+ ) );
19
+
20
+ extract( $args, EXTR_SKIP );
21
+
22
+ if ( $separator ) {
23
+ if ( '<ul>' == $before_list )
24
+ $before_list = '';
25
+
26
+ if ( '</ul>' == $after_list )
27
+ $after_list = '';
28
+ }
29
+
30
+ if ( !$echo )
31
+ ob_start();
32
+
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;
40
+ }
41
+ }
42
+
43
+ echo $after_list;
44
+
45
+ if ( !$echo )
46
+ return ob_get_clean();
47
+ }
48
+
49
+ abstract protected function render_item( $item );
50
+ }
51
+
52
+
53
+ 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 {
80
+
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
+
core/query.php CHANGED
@@ -100,7 +100,7 @@ class P2P_Query {
100
  if ( 'any' == $q['items'] ) {
101
  $search = false;
102
  } else {
103
- $search = implode( ',', array_map( 'absint', (array) $q['items'] ) );
104
  }
105
 
106
  $where_parts = array();
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();
core/side.php CHANGED
@@ -1,7 +1,5 @@
1
  <?php
2
 
3
- define( 'ADMIN_BOX_PER_PAGE', 5 );
4
-
5
  abstract class P2P_Side {
6
 
7
  public $query_vars;
@@ -10,8 +8,8 @@ abstract class P2P_Side {
10
  $this->query_vars = $query_vars;
11
  }
12
 
13
- function get_base_qv() {
14
- return $this->query_vars;
15
  }
16
  }
17
 
@@ -26,9 +24,19 @@ class P2P_Side_Post extends P2P_Side {
26
  $this->post_type = $this->query_vars['post_type'];
27
  }
28
 
29
- function get_base_qv() {
30
- return array_merge( $this->query_vars, array(
31
- 'post_type' => $this->post_type,
 
 
 
 
 
 
 
 
 
 
32
  'suppress_filters' => false,
33
  'ignore_sticky_posts' => true,
34
  ) );
@@ -59,56 +67,28 @@ class P2P_Side_Post extends P2P_Side {
59
  return new WP_Query( $args );
60
  }
61
 
62
- function abstract_query( $query ) {
63
- return (object) array(
64
- 'items' => $query->posts,
65
- 'current_page' => max( 1, $query->get('paged') ),
66
- 'total_pages' => $query->max_num_pages
 
67
  );
68
- }
69
 
70
- private static $admin_box_qv = array(
71
- 'update_post_term_cache' => false,
72
- 'update_post_meta_cache' => false,
73
- 'post_status' => 'any',
74
- );
75
-
76
- function get_connections_qv() {
77
- return array_merge( self::$admin_box_qv, array(
78
- 'nopaging' => true
79
- ) );
80
- }
81
-
82
- function get_connectable_qv( $item_id, $page, $search, $to_exclude ) {
83
- $qv = array_merge( $this->get_base_qv(), self::$admin_box_qv, array(
84
- 'posts_per_page' => ADMIN_BOX_PER_PAGE,
85
- 'paged' => $page,
86
- ) );
87
-
88
- if ( $search ) {
89
- $qv['s'] = $search;
90
- }
91
-
92
- $qv['post__not_in'] = $to_exclude;
93
 
94
  return $qv;
95
  }
96
 
97
- /**
98
- * @param mixed A post type, a post id, a post object, an array of post ids or of objects.
99
- */
100
  function item_recognize( $arg ) {
101
- if ( is_array( $arg ) ) {
102
- $arg = reset( $arg );
103
- }
104
-
105
  if ( is_object( $arg ) ) {
 
 
106
  $post_type = $arg->post_type;
107
  } elseif ( $post_id = (int) $arg ) {
108
- $post = get_post( $post_id );
109
- if ( !$post )
110
- return false;
111
- $post_type = $post->post_type;
112
  } else {
113
  $post_type = $arg;
114
  }
@@ -119,12 +99,12 @@ class P2P_Side_Post extends P2P_Side {
119
  return in_array( $post_type, $this->post_type );
120
  }
121
 
122
- protected function get_ptype() {
123
- return get_post_type_object( $this->post_type[0] );
124
- }
 
125
 
126
- function item_exists( $item_id ) {
127
- return (bool) get_post( $item_id );
128
  }
129
 
130
  function item_title( $item ) {
@@ -140,6 +120,12 @@ class P2P_Side_Attachment extends P2P_Side_Post {
140
 
141
  $this->post_type = array( 'attachment' );
142
  }
 
 
 
 
 
 
143
  }
144
 
145
 
@@ -169,40 +155,34 @@ class P2P_Side_User extends P2P_Side {
169
  return new WP_User_Query( $args );
170
  }
171
 
172
- function abstract_query( $query ) {
173
- return (object) array(
174
- 'items' => $query->get_results(),
175
- 'current_page' => isset( $query->query_vars['_p2p_page'] ) ? $query->query_vars['_p2p_page'] : 1,
176
- 'total_pages' => ceil( $query->get_total() / ADMIN_BOX_PER_PAGE )
177
- );
178
- }
179
 
180
- function get_connections_qv() {
181
- return array();
182
- }
183
 
184
- function get_connectable_qv( $item_id, $page, $search, $to_exclude ) {
185
- $qv = array_merge( $this->get_base_qv(), array(
186
- 'number' => ADMIN_BOX_PER_PAGE,
187
- 'offset' => ADMIN_BOX_PER_PAGE * ( $page - 1 ),
188
- '_p2p_page' => $page
189
- ) );
190
-
191
- if ( $search ) {
192
- $qv['search'] = '*' . $search . '*';
193
  }
194
 
195
- $qv['exclude'] = $to_exclude;
196
-
197
  return $qv;
198
  }
199
 
200
  function item_recognize( $arg ) {
201
- return false;
202
  }
203
 
204
- function item_exists( $item_id ) {
205
- return (bool) get_user_by( 'id', $item_id );
 
 
 
 
 
 
 
206
  }
207
 
208
  function item_title( $item ) {
1
  <?php
2
 
 
 
3
  abstract class P2P_Side {
4
 
5
  public $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
 
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'] );
37
+ }
38
+
39
+ return array_merge( $this->query_vars, $q, array(
40
  'suppress_filters' => false,
41
  'ignore_sticky_posts' => true,
42
  ) );
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',
75
+ 'per_page' => 'posts_per_page'
76
  );
 
77
 
78
+ foreach ( $map as $old => $new )
79
+ if ( isset( $qv["p2p:$old"] ) )
80
+ $qv[$new] = _p2p_pluck( $qv, "p2p:$old" );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
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
  }
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 ) {
120
 
121
  $this->post_type = array( 'attachment' );
122
  }
123
+
124
+ function get_base_qv( $q ) {
125
+ return array_merge( parent::get_base_qv( $q ), array(
126
+ 'post_status' => 'inherit'
127
+ ) );
128
+ }
129
  }
130
 
131
 
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
 
162
+ if ( isset( $qv['p2p:search'] ) && $qv['p2p:search'] )
163
+ $qv['search'] = '*' . _p2p_pluck( $qv, 'p2p:search' ) . '*';
 
164
 
165
+ if ( isset( $qv['p2p:page'] ) && $qv['p2p:page'] > 0 ) {
166
+ $qv['number'] = $qv['p2p:per_page'];
167
+ $qv['offset'] = $qv['p2p:per_page'] * ( $qv['p2p:page'] - 1 );
 
 
 
 
 
 
168
  }
169
 
 
 
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 ) {
core/type-factory.php CHANGED
@@ -1,6 +1,7 @@
1
  <?php
2
 
3
  class P2P_Connection_Type_Factory {
 
4
  private static $instances = array();
5
 
6
  public static function register( $args ) {
@@ -68,14 +69,9 @@ class P2P_Connection_Type_Factory {
68
  ) ) ) ) );
69
  }
70
 
71
- if ( $args['from_object'] == $args['to_object'] && 'post' == $args['from_object'] )
72
- $class = 'P2P_Connection_Type';
73
- else
74
- $class = 'Generic_Connection_Type';
75
-
76
  $args = apply_filters( 'p2p_connection_type_args', $args );
77
 
78
- $ctype = new $class( $args );
79
 
80
  if ( isset( self::$instances[ $ctype->name ] ) ) {
81
  trigger_error( "Connection type '$ctype->name' is already defined.", E_USER_NOTICE );
1
  <?php
2
 
3
  class P2P_Connection_Type_Factory {
4
+
5
  private static $instances = array();
6
 
7
  public static function register( $args ) {
69
  ) ) ) ) );
70
  }
71
 
 
 
 
 
 
72
  $args = apply_filters( 'p2p_connection_type_args', $args );
73
 
74
+ $ctype = new P2P_Connection_Type( $args );
75
 
76
  if ( isset( self::$instances[ $ctype->name ] ) ) {
77
  trigger_error( "Connection type '$ctype->name' is already defined.", E_USER_NOTICE );
core/type.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- class Generic_Connection_Type {
4
 
5
  public $indeterminate = false;
6
 
@@ -23,6 +23,17 @@ class Generic_Connection_Type {
23
  $this->side[ $direction ] = new $class( _p2p_pluck( $args, $direction . '_query_vars' ) );
24
  }
25
 
 
 
 
 
 
 
 
 
 
 
 
26
  $this->set_cardinality( _p2p_pluck( $args, 'cardinality' ) );
27
 
28
  $this->set_labels( $args );
@@ -110,14 +121,17 @@ class Generic_Connection_Type {
110
  }
111
 
112
  /**
113
- * Attempt to guess direction based on a post id or post type.
114
  *
115
- * @param int|string $arg A post id or a post type.
116
  * @param bool Whether to return an instance of P2P_Directed_Connection_Type or just the direction
117
  *
118
  * @return bool|object|string False on failure, P2P_Directed_Connection_Type instance or direction on success.
119
  */
120
  public function find_direction( $arg, $instantiate = true ) {
 
 
 
121
  foreach ( array( 'from', 'to' ) as $direction ) {
122
  if ( !$this->side[ $direction ]->item_recognize( $arg ) )
123
  continue;
@@ -131,119 +145,28 @@ class Generic_Connection_Type {
131
  return false;
132
  }
133
 
134
- /**
135
- * Optimized inner query, after the outer query was executed.
136
- *
137
- * Populates each of the outer querie's $post objects with a 'connected' property, containing a list of connected posts
138
- *
139
- * @param object $query WP_Query instance.
140
- * @param string|array $extra_qv Additional query vars for the inner query.
141
- * @param string $prop_name The name of the property used to store the list of connected items on each post object.
142
- */
143
- public function each_connected( $query, $extra_qv = array(), $prop_name = 'connected' ) {
144
- if ( empty( $query->posts ) || !is_object( $query->posts[0] ) )
145
- return;
146
-
147
- $post_type = $query->get( 'post_type' );
148
- if ( empty( $post_type ) )
149
- $post_type = 'post';
150
-
151
- $directed = $this->find_direction( $post_type );
152
- if ( !$directed )
153
- return false;
154
-
155
- $posts = array();
156
-
157
- foreach ( $query->posts as $post ) {
158
- $post->$prop_name = array();
159
- $posts[ $post->ID ] = $post;
160
- }
161
-
162
- // ignore pagination
163
- foreach ( array( 'showposts', 'posts_per_page', 'posts_per_archive_page' ) as $disabled_qv ) {
164
- if ( isset( $extra_qv[ $disabled_qv ] ) ) {
165
- trigger_error( "Can't use '$disabled_qv' in an inner query", E_USER_WARNING );
166
- }
167
- }
168
- $extra_qv['nopaging'] = true;
169
-
170
- $q = $directed->get_connected( array_keys( $posts ), $extra_qv, 'abstract' );
171
 
172
- foreach ( $q->items as $inner_item ) {
173
- if ( $inner_item->ID == $inner_item->p2p_from ) {
174
- $outer_item_id = $inner_item->p2p_to;
175
- } elseif ( $inner_item->ID == $inner_item->p2p_to ) {
176
- $outer_item_id = $inner_item->p2p_from;
177
- } else {
178
- trigger_error( "Corrupted data for item $inner_item->ID", E_USER_WARNING );
179
- continue;
180
  }
181
-
182
- array_push( $posts[ $outer_item_id ]->$prop_name, $inner_item );
183
  }
184
- }
185
-
186
- public function get_desc() {
187
- foreach ( array( 'from', 'to' ) as $key ) {
188
- $$key = $this->side[ $key ]->get_desc();
189
- }
190
-
191
- if ( $this->indeterminate )
192
- $arrow = '&harr;';
193
- else
194
- $arrow = '&rarr;';
195
-
196
- $label = "$from $arrow $to";
197
-
198
- $title = $this->title[ 'from' ];
199
-
200
- if ( $title )
201
- $label .= " ($title)";
202
-
203
- return $label;
204
- }
205
- }
206
-
207
-
208
- class P2P_Connection_Type extends Generic_Connection_Type {
209
-
210
- public function __construct( $args ) {
211
- parent::__construct( $args );
212
 
213
- $common = array_intersect( $this->from, $this->to );
214
-
215
- if ( !empty( $common ) )
216
- $this->indeterminate = true;
217
- }
218
-
219
- public function __get( $key ) {
220
- if ( 'from' == $key || 'to' == $key )
221
- return $this->side[ $key ]->post_type;
222
- }
223
-
224
- /**
225
- * Get a list of posts connected to other posts connected to a post.
226
- *
227
- * @param int|array $post_id A post id or array of post ids
228
- * @param array $extra_qv Additional query variables to use.
229
- *
230
- * @return bool|object False on failure; A WP_Query instance on success.
231
- */
232
- public function get_related( $post_id, $extra_qv = array() ) {
233
- $post_id = (array) $post_id;
234
-
235
- $connected = $this->get_connected( $post_id, $extra_qv );
236
- if ( !$connected )
237
  return false;
238
 
239
- if ( !$connected->have_posts() )
240
- return $connected;
241
 
242
- $connected_ids = wp_list_pluck( $connected->posts, 'ID' );
243
-
244
- return $this->get_connected( $connected_ids, array(
245
- 'post__not_in' => $post_id,
246
- ) );
247
  }
248
 
249
  /** Alias for get_prev() */
@@ -285,18 +208,22 @@ class P2P_Connection_Type extends Generic_Connection_Type {
285
  * @return bool|object False on failure, post object on success
286
  */
287
  public function get_adjacent( $from, $to, $which ) {
 
 
 
288
  $directed = $this->find_direction( $to );
289
  if ( !$directed )
290
  return false;
291
 
292
- if ( !method_exists( $directed, 'get_orderby_key' ) )
 
293
  return false;
294
 
295
  $p2p_id = $directed->get_p2p_id( $to, $from );
296
  if ( !$p2p_id )
297
  return false;
298
 
299
- $order = (int) p2p_get_meta( $p2p_id, $directed->get_orderby_key(), true );
300
 
301
  $adjacent = $directed->get_connected( $to, array(
302
  'connected_meta' => array(
@@ -305,12 +232,72 @@ class P2P_Connection_Type extends Generic_Connection_Type {
305
  'value' => $order + $which
306
  )
307
  )
308
- ) )->posts;
309
 
310
- if ( empty( $adjacent ) )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  return false;
312
 
313
- return $adjacent[0];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  }
315
  }
316
 
1
  <?php
2
 
3
+ class P2P_Connection_Type {
4
 
5
  public $indeterminate = false;
6
 
23
  $this->side[ $direction ] = new $class( _p2p_pluck( $args, $direction . '_query_vars' ) );
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
+ $this->self_connections = true;
35
+ }
36
+
37
  $this->set_cardinality( _p2p_pluck( $args, 'cardinality' ) );
38
 
39
  $this->set_labels( $args );
121
  }
122
 
123
  /**
124
+ * Attempt to guess direction based on a parameter.
125
  *
126
+ * @param mixed A post type, object or object id.
127
  * @param bool Whether to return an instance of P2P_Directed_Connection_Type or just the direction
128
  *
129
  * @return bool|object|string False on failure, P2P_Directed_Connection_Type instance or direction on success.
130
  */
131
  public function find_direction( $arg, $instantiate = true ) {
132
+ if ( is_array( $arg ) )
133
+ $arg = reset( $arg );
134
+
135
  foreach ( array( 'from', 'to' ) as $direction ) {
136
  if ( !$this->side[ $direction ]->item_recognize( $arg ) )
137
  continue;
145
  return false;
146
  }
147
 
148
+ // Used in each_connected()
149
+ private function find_direction_multiple( $post_types ) {
150
+ $possible_directions = array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
+ foreach ( array( 'from', 'to' ) as $direction ) {
153
+ if ( 'post' == $this->object[$direction] ) {
154
+ foreach ( $post_types as $post_type ) {
155
+ if ( !$this->side[ $direction ]->item_recognize( $post_type ) ) {
156
+ $possible_directions[] = $direction;
157
+ break;
158
+ }
159
+ }
160
  }
 
 
161
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
+ if ( empty( $possible_directions ) )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  return false;
165
 
166
+ if ( count( $possible_directions ) > 1 )
167
+ return 'any';
168
 
169
+ return reset( $possible_directions );
 
 
 
 
170
  }
171
 
172
  /** Alias for get_prev() */
208
  * @return bool|object False on failure, post object on success
209
  */
210
  public function get_adjacent( $from, $to, $which ) {
211
+
212
+ // The direction needs to be based on the second parameter,
213
+ // so that it's consistent with $this->connect( $from, $to ) etc.
214
  $directed = $this->find_direction( $to );
215
  if ( !$directed )
216
  return false;
217
 
218
+ $key = $directed->get_orderby_key();
219
+ if ( !$key )
220
  return false;
221
 
222
  $p2p_id = $directed->get_p2p_id( $to, $from );
223
  if ( !$p2p_id )
224
  return false;
225
 
226
+ $order = (int) p2p_get_meta( $p2p_id, $key, true );
227
 
228
  $adjacent = $directed->get_connected( $to, array(
229
  'connected_meta' => array(
232
  'value' => $order + $which
233
  )
234
  )
235
+ ), 'abstract' );
236
 
237
+ return _p2p_first( $adjacent->items );
238
+ }
239
+
240
+ /**
241
+ * Optimized inner query, after the outer query was executed.
242
+ *
243
+ * Populates each of the outer querie's $post objects with a 'connected' property, containing a list of connected posts
244
+ *
245
+ * @param object|array $items WP_Query instance or list of post objects
246
+ * @param string|array $extra_qv Additional query vars for the inner query.
247
+ * @param string $prop_name The name of the property used to store the list of connected items on each post object.
248
+ */
249
+ public function each_connected( $items, $extra_qv = array(), $prop_name = 'connected' ) {
250
+ if ( is_a( $items, 'WP_Query' ) )
251
+ $items =& $items->posts;
252
+
253
+ if ( empty( $items ) || !is_object( $items[0] ) )
254
+ return;
255
+
256
+ $post_types = array_unique( wp_list_pluck( $items, 'post_type' ) );
257
+
258
+ if ( count( $post_types ) > 1 ) {
259
+ $direction = $this->find_direction_multiple( $post_types );
260
+ $extra_qv['post_type'] = 'any';
261
+ } else {
262
+ $direction = $this->find_direction( $post_types[0], false );
263
+ }
264
+
265
+ if ( !$direction )
266
  return false;
267
 
268
+ $directed = $this->set_direction( $direction );
269
+
270
+ // ignore pagination
271
+ foreach ( array( 'showposts', 'posts_per_page', 'posts_per_archive_page' ) as $disabled_qv ) {
272
+ if ( isset( $extra_qv[ $disabled_qv ] ) ) {
273
+ trigger_error( "Can't use '$disabled_qv' in an inner query", E_USER_WARNING );
274
+ }
275
+ }
276
+ $extra_qv['nopaging'] = true;
277
+
278
+ $q = $directed->get_connected( $items, $extra_qv, 'abstract' );
279
+
280
+ p2p_distribute_connected( $items, $q->items, $prop_name );
281
+ }
282
+
283
+ public function get_desc() {
284
+ foreach ( array( 'from', 'to' ) as $key ) {
285
+ $$key = $this->side[ $key ]->get_desc();
286
+ }
287
+
288
+ if ( $this->indeterminate )
289
+ $arrow = '&harr;';
290
+ else
291
+ $arrow = '&rarr;';
292
+
293
+ $label = "$from $arrow $to";
294
+
295
+ $title = $this->title[ 'from' ];
296
+
297
+ if ( $title )
298
+ $label .= " ($title)";
299
+
300
+ return $label;
301
  }
302
  }
303
 
core/util.php CHANGED
@@ -9,16 +9,27 @@ function _p2p_expand_direction( $direction ) {
9
  }
10
 
11
  /** @internal */
12
- function _p2p_meta_sql_helper( $data ) {
 
 
 
 
 
 
 
 
 
 
 
13
  global $wpdb;
14
 
15
- if ( isset( $data[0] ) ) {
16
- $meta_query = $data;
17
  }
18
  else {
19
  $meta_query = array();
20
 
21
- foreach ( $data as $key => $value ) {
22
  $meta_query[] = compact( 'key', 'value' );
23
  }
24
  }
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
+ }
21
+
22
+ /** @internal */
23
+ function _p2p_meta_sql_helper( $query ) {
24
  global $wpdb;
25
 
26
+ if ( isset( $query[0] ) ) {
27
+ $meta_query = $query;
28
  }
29
  else {
30
  $meta_query = array();
31
 
32
+ foreach ( $query as $key => $value ) {
33
  $meta_query[] = compact( 'key', 'value' );
34
  }
35
  }
core/widget.php DELETED
@@ -1,98 +0,0 @@
1
- <?php
2
-
3
- class P2P_Widget extends scbWidget {
4
-
5
- protected $defaults = array(
6
- 'ctype' => false,
7
- 'listing' => 'connected'
8
- );
9
-
10
- static function init() {
11
- parent::init( __CLASS__, false, 'p2p' );
12
- }
13
-
14
- function __construct() {
15
- parent::__construct( 'p2p', __( 'Posts 2 Posts', P2P_TEXTDOMAIN ), array(
16
- 'description' => __( 'A list of posts connected to the current post', P2P_TEXTDOMAIN )
17
- ) );
18
- }
19
-
20
- function form( $instance ) {
21
- if ( empty( $instance ) )
22
- $instance = $this->defaults;
23
-
24
- $ctypes = array();
25
-
26
- foreach ( P2P_Connection_Type_Factory::get_all_instances() as $p2p_type => $ctype ) {
27
- if ( ! $ctype instanceof P2P_Connection_Type )
28
- continue;
29
-
30
- $ctypes[ $p2p_type ] = $ctype->get_desc();
31
- }
32
-
33
- echo html( 'p', $this->input( array(
34
- 'type' => 'select',
35
- 'name' => 'ctype',
36
- 'values' => $ctypes,
37
- 'desc' => __( 'Connection type:', P2P_TEXTDOMAIN )
38
- ), $instance ) );
39
-
40
- echo html( 'p',
41
- __( 'Connection listing:', P2P_TEXTDOMAIN ),
42
- '<br>',
43
- $this->input( array(
44
- 'type' => 'radio',
45
- 'name' => 'listing',
46
- 'values' => array(
47
- 'connected' => __( 'connected', P2P_TEXTDOMAIN ),
48
- 'related' => __( 'related', P2P_TEXTDOMAIN )
49
- ),
50
- ), $instance )
51
- );
52
- }
53
-
54
- function widget( $args, $instance ) {
55
- if ( !is_singular() )
56
- return;
57
-
58
- $instance = array_merge( $this->defaults, $instance );
59
-
60
- $post_id = get_queried_object_id();
61
-
62
- $ctype = p2p_type( $instance['ctype'] );
63
- if ( !$ctype )
64
- return;
65
-
66
- $directed = $ctype->find_direction( $post_id );
67
- if ( !$directed )
68
- return;
69
-
70
- if ( 'related' == $instance['listing'] ) {
71
- $connected = $ctype->get_related( $post_id );
72
- $title = sprintf(
73
- __( 'Related %s', P2P_TEXTDOMAIN ),
74
- $directed->get_current( 'side' )->get_title()
75
- );
76
- } else {
77
- $connected = $directed->get_connected( $post_id );
78
- $title = $directed->get_current( 'title' );
79
- }
80
-
81
- if ( !$connected->have_posts() )
82
- return;
83
-
84
- $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
85
-
86
- extract( $args );
87
-
88
- echo $before_widget;
89
-
90
- if ( ! empty( $title ) )
91
- echo $before_title . $title . $after_title;
92
-
93
- p2p_list_posts( $connected );
94
-
95
- echo $after_widget;
96
- }
97
- }
98
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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-06 20:57:17+00:00\n"
11
- "PO-Revision-Date: 2012-04-06 23:59+0200\n"
12
  "Last-Translator: scribu <mail@scribu.net>\n"
13
  "Language-Team: ROMANIAN <LL@li.org>\n"
14
  "Language: \n"
@@ -24,37 +24,37 @@ msgstr "(de la)"
24
  msgid " (to)"
25
  msgstr "(la)"
26
 
27
- #: admin/box.php:34
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:120
32
  msgid "Create connections:"
33
  msgstr "Creează conexiuni:"
34
 
35
- #: admin/box.php:129
36
  msgid "View All"
37
  msgstr "Vezi toate"
38
 
39
- #: admin/box.php:134
40
  msgid "Search"
41
  msgstr "Căutare"
42
 
43
- #: admin/box.php:205
44
  msgid "previous"
45
  msgstr "anterioare"
46
 
47
- #: admin/box.php:206
48
  msgid "next"
49
  msgstr "următoare"
50
 
51
- #: admin/box.php:207
52
  msgid "of"
53
  msgstr "din"
54
 
55
- #: admin/box.php:248
56
- msgid "Can't create connection."
57
- msgstr "Conexiunea nu poate fi creată."
58
 
59
  #: admin/fields.php:13
60
  msgid "Create connection"
@@ -70,7 +70,7 @@ msgstr "Șterge conexiune"
70
 
71
  #: admin/tools.php:7
72
  msgid "Connection Types"
73
- msgstr "Tipuri de conexiuni:"
74
 
75
  #: admin/tools.php:31
76
  msgid "Upgraded %d connections."
@@ -108,53 +108,53 @@ msgstr "Convertește într-un tip de conexiune înregistrat:"
108
  msgid "Go"
109
  msgstr "Go"
110
 
111
- #: core/side.php:149
112
- msgid "Users"
113
- msgstr "Utilizatori"
114
-
115
- #: core/side.php:158
116
- msgid "User"
117
- msgstr "Utilizator"
118
-
119
- #: core/side.php:159
120
- msgid "Search Users"
121
- msgstr "Caută utilizatori"
122
-
123
- #: core/side.php:160
124
- msgid "No users found."
125
- msgstr "Niciun utilizator găsit."
126
-
127
- #: core/type.php:77
128
- msgid "Connected %s"
129
- msgstr "%s conectate"
130
-
131
- #: core/widget.php:15
132
  msgid "Posts 2 Posts"
133
  msgstr "Posts 2 Posts"
134
 
135
- #: core/widget.php:16
136
  msgid "A list of posts connected to the current post"
137
  msgstr "O lista de postări conectate la postarea curentă"
138
 
139
- #: core/widget.php:37
 
 
 
 
140
  msgid "Connection type:"
141
  msgstr "Tipul conexiunii:"
142
 
143
- #: core/widget.php:41
144
  msgid "Connection listing:"
145
- msgstr "Tipul conexiunii:"
146
 
147
- #: core/widget.php:47
148
  msgid "connected"
149
  msgstr "conectate"
150
 
151
- #: core/widget.php:48
152
  msgid "related"
153
  msgstr "înrudite"
154
 
155
- #: core/widget.php:73
156
- msgid "Related %s"
157
- msgstr "%s înrudite"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
  #: scb/AdminPage.php:172
160
  msgid "Settings <strong>saved</strong>."
@@ -169,6 +169,9 @@ msgstr "Salvează schimbările"
169
  msgid "Settings"
170
  msgstr "Setări"
171
 
 
 
 
172
  #~ msgid "Recent"
173
  #~ msgstr "Recente"
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-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"
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"
70
 
71
  #: admin/tools.php:7
72
  msgid "Connection Types"
73
+ msgstr "Tipuri de conexiuni"
74
 
75
  #: admin/tools.php:31
76
  msgid "Upgraded %d connections."
108
  msgid "Go"
109
  msgstr "Go"
110
 
111
+ #: core/extra.php:16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  msgid "Posts 2 Posts"
113
  msgstr "Posts 2 Posts"
114
 
115
+ #: core/extra.php:17
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
 
159
  #: scb/AdminPage.php:172
160
  msgid "Settings <strong>saved</strong>."
169
  msgid "Settings"
170
  msgstr "Setări"
171
 
172
+ #~ msgid "Related %s"
173
+ #~ msgstr "%s înrudite"
174
+
175
  #~ msgid "Recent"
176
  #~ msgstr "Recente"
177
 
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-06 20:57:17+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
@@ -20,36 +20,36 @@ msgstr ""
20
  msgid " (to)"
21
  msgstr ""
22
 
23
- #: admin/box.php:34
24
  msgid "Are you sure you want to delete all connections?"
25
  msgstr ""
26
 
27
- #: admin/box.php:120
28
  msgid "Create connections:"
29
  msgstr ""
30
 
31
- #: admin/box.php:129
32
  msgid "View All"
33
  msgstr ""
34
 
35
- #: admin/box.php:134
36
  msgid "Search"
37
  msgstr ""
38
 
39
- #: admin/box.php:205
40
  msgid "previous"
41
  msgstr ""
42
 
43
- #: admin/box.php:206
44
  msgid "next"
45
  msgstr ""
46
 
47
- #: admin/box.php:207
48
  msgid "of"
49
  msgstr ""
50
 
51
- #: admin/box.php:248
52
- msgid "Can't create connection."
53
  msgstr ""
54
 
55
  #: admin/fields.php:13
@@ -106,52 +106,52 @@ msgstr ""
106
  msgid "Go"
107
  msgstr ""
108
 
109
- #: core/side.php:149
110
- msgid "Users"
111
  msgstr ""
112
 
113
- #: core/side.php:158
114
- msgid "User"
115
  msgstr ""
116
 
117
- #: core/side.php:159
118
- msgid "Search Users"
119
  msgstr ""
120
 
121
- #: core/side.php:160
122
- msgid "No users found."
123
  msgstr ""
124
 
125
- #: core/type.php:77
126
- msgid "Connected %s"
127
  msgstr ""
128
 
129
- #: core/widget.php:15
130
- msgid "Posts 2 Posts"
131
  msgstr ""
132
 
133
- #: core/widget.php:16
134
- msgid "A list of posts connected to the current post"
135
  msgstr ""
136
 
137
- #: core/widget.php:37
138
- msgid "Connection type:"
139
  msgstr ""
140
 
141
- #: core/widget.php:41
142
- msgid "Connection listing:"
143
  msgstr ""
144
 
145
- #: core/widget.php:47
146
- msgid "connected"
147
  msgstr ""
148
 
149
- #: core/widget.php:48
150
- msgid "related"
151
  msgstr ""
152
 
153
- #: core/widget.php:73
154
- msgid "Related %s"
155
  msgstr ""
156
 
157
  #: scb/AdminPage.php:172
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"
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
106
  msgid "Go"
107
  msgstr ""
108
 
109
+ #: core/extra.php:16
110
+ msgid "Posts 2 Posts"
111
  msgstr ""
112
 
113
+ #: core/extra.php:17
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
 
157
  #: scb/AdminPage.php:172
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.2
6
  Author: scribu
7
  Author URI: http://scribu.net/
8
  Plugin URI: http://scribu.net/wordpress/posts-to-posts
@@ -26,7 +26,7 @@ You should have received a copy of the GNU General Public License
26
  along with this program. If not, see <http://www.gnu.org/licenses/>.
27
  */
28
 
29
- define( 'P2P_PLUGIN_VERSION', '1.2' );
30
 
31
  define( 'P2P_TEXTDOMAIN', 'posts-to-posts' );
32
 
@@ -39,11 +39,12 @@ function _p2p_init() {
39
 
40
  _p2p_load_files( "$base/core", array(
41
  'storage', 'query', 'query-post', 'query-user', 'url-query',
42
- 'util', 'side', 'type-factory', 'type', 'directed-type',
43
- 'api', 'widget'
44
  ) );
45
 
46
  P2P_Widget::init();
 
47
 
48
  if ( is_admin() ) {
49
  _p2p_load_files( "$base/admin", array(
2
  /*
3
  Plugin Name: Posts 2 Posts
4
  Description: Create many-to-many relationships between all types of posts.
5
+ Version: 1.3
6
  Author: scribu
7
  Author URI: http://scribu.net/
8
  Plugin URI: http://scribu.net/wordpress/posts-to-posts
26
  along with this program. If not, see <http://www.gnu.org/licenses/>.
27
  */
28
 
29
+ define( 'P2P_PLUGIN_VERSION', '1.3-beta' );
30
 
31
  define( 'P2P_TEXTDOMAIN', 'posts-to-posts' );
32
 
39
 
40
  _p2p_load_files( "$base/core", array(
41
  'storage', 'query', 'query-post', 'query-user', 'url-query',
42
+ 'util', 'side', 'list', 'type-factory', 'type', 'directed-type',
43
+ 'api', 'extra'
44
  ) );
45
 
46
  P2P_Widget::init();
47
+ P2P_Shortcodes::init();
48
 
49
  if ( is_admin() ) {
50
  _p2p_load_files( "$base/admin", array(
readme.txt CHANGED
@@ -2,8 +2,8 @@
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.3
6
- Stable tag: 1.2
7
 
8
  Efficient many-to-many connections between posts, pages, custom post types, users.
9
 
@@ -11,9 +11,10 @@ Efficient many-to-many connections between posts, pages, custom post types, user
11
 
12
  This plugin allows you to create many-to-many relationships between posts of any type: post, page, custom etc. A few example use cases:
13
 
14
- * post series
15
  * manually curated lists of related posts
16
- * 'actor' posts connected to 'movie' posts
 
 
17
 
18
  Additionally, you can create many-to-many relationships between posts and users. So, you could also implement:
19
 
@@ -50,6 +51,14 @@ Make sure your host is running PHP 5. The only foolproof way to do this is to ad
50
 
51
  == Changelog ==
52
 
 
 
 
 
 
 
 
 
53
  = 1.2 =
54
  * added Tools -> Connection Types admin screen
55
  * fixed migration script
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
7
 
8
  Efficient many-to-many connections between posts, pages, custom post types, users.
9
 
11
 
12
  This plugin allows you to create many-to-many relationships between posts of any type: post, page, custom etc. A few example use cases:
13
 
 
14
  * manually curated lists of related posts
15
+ * post series
16
+ * products connected to retailers
17
+ * etc.
18
 
19
  Additionally, you can create many-to-many relationships between posts and users. So, you could also implement:
20
 
51
 
52
  == Changelog ==
53
 
54
+ = 1.3 =
55
+ * allow passing entire objects to get_connected(), connect() etc.
56
+ * made get_related() work with posts-to-users connections
57
+ * made each_connected() work with simple array of posts
58
+ * introduced [p2p_connected] and [p2p_related] shortcodes
59
+ * allow 'default' parameter in 'fields' array
60
+ * [more info](http://scribu.net/wordpress/posts-to-posts/p2p-1-3.html)
61
+
62
  = 1.2 =
63
  * added Tools -> Connection Types admin screen
64
  * fixed migration script
scb/Forms.php CHANGED
@@ -9,6 +9,9 @@ class scbForms {
9
  protected static $cur_name;
10
 
11
  function input_with_value( $args, $value ) {
 
 
 
12
  if ( !is_null( $value ) ) {
13
  switch ( $args['type'] ) {
14
  case 'select':
@@ -433,7 +436,12 @@ class scbForms {
433
  $key = (array) $args['name'];
434
  $key = end( $key );
435
 
436
- $value = get_metadata( $meta_type, $object_id, $key, $single );
 
 
 
 
 
437
 
438
  return self::input_with_value( $args, $value );
439
  }
@@ -511,9 +519,7 @@ class scbForm {
511
  }
512
 
513
  function input( $args ) {
514
- $default = isset( $args['default'] ) ? $args['default'] : null;
515
-
516
- $value = scbForms::get_value( $args['name'], $this->data, $default );
517
 
518
  if ( !empty( $this->prefix ) ) {
519
  $args['name'] = array_merge( $this->prefix, (array) $args['name'] );
9
  protected static $cur_name;
10
 
11
  function input_with_value( $args, $value ) {
12
+ if ( is_null( $value ) && isset( $args['default'] ) )
13
+ $value = $args['default'];
14
+
15
  if ( !is_null( $value ) ) {
16
  switch ( $args['type'] ) {
17
  case 'select':
436
  $key = (array) $args['name'];
437
  $key = end( $key );
438
 
439
+ $value = get_metadata( $meta_type, $object_id, $key );
440
+
441
+ if ( empty( $value ) )
442
+ $value = null;
443
+ elseif( $single )
444
+ $value = reset( $value );
445
 
446
  return self::input_with_value( $args, $value );
447
  }
519
  }
520
 
521
  function input( $args ) {
522
+ $value = scbForms::get_value( $args['name'], $this->data );
 
 
523
 
524
  if ( !empty( $this->prefix ) ) {
525
  $args['name'] = array_merge( $this->prefix, (array) $args['name'] );
scb/load.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- $GLOBALS['_scb_data'] = array( 52, __FILE__, array(
4
  'scbUtil', 'scbOptions', 'scbForms', 'scbTable',
5
  'scbWidget', 'scbAdminPage', 'scbBoxesPage',
6
  'scbCron', 'scbHooks',
1
  <?php
2
 
3
+ $GLOBALS['_scb_data'] = array( 53, __FILE__, array(
4
  'scbUtil', 'scbOptions', 'scbForms', 'scbTable',
5
  'scbWidget', 'scbAdminPage', 'scbBoxesPage',
6
  'scbCron', 'scbHooks',
screenshot-1.png CHANGED
Binary file
screenshot-2.png CHANGED
Binary file