Posts 2 Posts - Version 1.6

Version Description

  • introduced p2p_candidate_title filter
  • introduced JavaScript API
  • added Japanese translation
  • various refactorings
Download this release

Release Info

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

Code changes from version 1.5.2 to 1.6

CONTRIBUTING.md CHANGED
@@ -32,7 +32,6 @@ coffee -c admin
32
 
33
  The plugin comes with a few unit tests.
34
 
35
- 1. Install [PHPUnit](https://github.com/sebastianbergmann/phpunit/).
36
- 2. Create `tests/wp-tests-config.php` file. ([sample](https://unit-tests.svn.wordpress.org/trunk/wp-tests-config-sample.php))
37
- 3. [Install the scbFramework](https://github.com/scribu/wp-scb-framework/wiki) in the mu-plugins dir.
38
- 4. Run `./bin/test`
32
 
33
  The plugin comes with a few unit tests.
34
 
35
+ 1. Install [Composer](https://getcomposer.org).
36
+ 2. Run `composer install --dev`.
37
+ 3. Run `vendor/bin/phpunit`.
 
admin/box.js CHANGED
@@ -1,15 +1,16 @@
1
- // Generated by CoffeeScript 1.6.1
2
  (function() {
3
- var Candidates, CandidatesView, Connections, ConnectionsView, CreatePostView, ENTER_KEY, MetaboxView, get_mustache_template, remove_row, row_wait;
4
 
5
  ENTER_KEY = 13;
6
 
7
  row_wait = function($td) {
8
- return $td.find('.p2p-icon').css('background-image', 'url(' + P2PAdmin.spinner + ')');
9
  };
10
 
11
  remove_row = function($td) {
12
  var $table;
 
13
  $table = $td.closest('table');
14
  $td.closest('tr').remove();
15
  if (!$table.find('tbody tr').length) {
@@ -21,21 +22,28 @@
21
  return jQuery('#p2p-template-' + name).html();
22
  };
23
 
 
 
 
 
24
  Candidates = Backbone.Model.extend({
25
  sync: function() {
26
  var params,
27
  _this = this;
28
- params = _.extend({}, this.attributes, {
 
29
  subaction: 'search'
30
- });
31
  return this.ajax_request(params, function(response) {
32
  var _ref;
 
33
  _this.total_pages = ((_ref = response.navigation) != null ? _ref['total-pages-raw'] : void 0) || 1;
34
  return _this.trigger('sync', response);
35
  });
36
  },
37
  validate: function(attrs) {
38
  var _ref;
 
39
  if ((0 < (_ref = attrs['paged']) && _ref <= this.total_pages)) {
40
  return null;
41
  }
@@ -43,43 +51,48 @@
43
  }
44
  });
45
 
46
- Connections = Backbone.Model.extend({
 
47
  createItemAndConnect: function(title) {
48
  var data,
49
  _this = this;
 
50
  data = {
51
  subaction: 'create_post',
52
  post_title: title
53
  };
54
  return this.ajax_request(data, function(response) {
55
- return _this.trigger('create:from_new_item', response);
56
  });
57
  },
58
- create: function($td) {
59
  var data,
60
  _this = this;
 
61
  data = {
62
  subaction: 'connect',
63
- to: $td.find('div').data('item-id')
64
  };
65
  return this.ajax_request(data, function(response) {
66
- return _this.trigger('create', response, $td);
67
  });
68
  },
69
- "delete": function($td) {
70
  var data,
71
  _this = this;
 
72
  data = {
73
  subaction: 'disconnect',
74
- p2p_id: $td.find('input').val()
75
  };
76
  return this.ajax_request(data, function(response) {
77
- return _this.trigger('delete', response, $td);
78
  });
79
  },
80
  clear: function() {
81
  var data,
82
  _this = this;
 
83
  data = {
84
  subaction: 'clear_connections'
85
  };
@@ -95,13 +108,9 @@
95
  'click td.p2p-col-delete .p2p-icon': 'delete'
96
  },
97
  initialize: function(options) {
98
- this.ajax_request = options.ajax_request;
99
  this.maybe_make_sortable();
100
  this.collection.on('create', this.afterCreate, this);
101
- this.collection.on('create:from_new_item', this.afterCreate, this);
102
- this.collection.on('delete', this.afterDelete, this);
103
- this.collection.on('clear', this.afterClear, this);
104
- return options.candidates.on('promote', this.create, this);
105
  },
106
  maybe_make_sortable: function() {
107
  if (this.$('th.p2p-col-order').length) {
@@ -110,6 +119,7 @@
110
  helper: function(e, ui) {
111
  ui.children().each(function() {
112
  var $this;
 
113
  $this = jQuery(this);
114
  return $this.width($this.width());
115
  });
@@ -120,8 +130,9 @@
120
  },
121
  clear: function(ev) {
122
  var $td;
 
123
  ev.preventDefault();
124
- if (!confirm(P2PAdmin.deleteConfirmMessage)) {
125
  return;
126
  }
127
  $td = jQuery(ev.target).closest('td');
@@ -132,18 +143,17 @@
132
  return this.$el.hide().find('tbody').html('');
133
  },
134
  "delete": function(ev) {
135
- var $td;
 
136
  ev.preventDefault();
137
  $td = jQuery(ev.target).closest('td');
138
  row_wait($td);
139
- this.collection["delete"]($td);
140
- return null;
141
- },
142
- afterDelete: function(response, $td) {
143
- return remove_row($td);
144
- },
145
- create: function($td) {
146
- this.collection.create($td);
147
  return null;
148
  },
149
  afterCreate: function(response) {
@@ -162,26 +172,29 @@
162
  },
163
  initialize: function(options) {
164
  this.spinner = options.spinner;
165
- options.connections.on('create', this.afterConnectionCreated, this);
166
- options.connections.on('delete', this.refreshCandidates, this);
167
- options.connections.on('clear', this.refreshCandidates, this);
168
- this.collection.on('sync', this.refreshCandidates, this);
169
  this.collection.on('error', this.afterInvalid, this);
170
  return this.collection.on('invalid', this.afterInvalid, this);
171
  },
172
- afterConnectionCreated: function(response, $td) {
173
- if (this.options.duplicate_connections) {
174
- return $td.find('.p2p-icon').css('background-image', '');
175
- } else {
176
- return remove_row($td);
177
- }
178
- },
179
  promote: function(ev) {
180
- var $td;
181
- $td = jQuery(ev.target).closest('td');
 
182
  ev.preventDefault();
 
183
  row_wait($td);
184
- this.collection.trigger('promote', $td);
 
 
 
 
 
 
 
 
 
185
  return null;
186
  },
187
  handleReturn: function(ev) {
@@ -193,12 +206,14 @@
193
  handleSearch: function(ev) {
194
  var $searchInput, delayed,
195
  _this = this;
 
196
  if (delayed !== void 0) {
197
  clearTimeout(delayed);
198
  }
199
  $searchInput = jQuery(ev.target);
200
  delayed = setTimeout(function() {
201
  var searchStr;
 
202
  searchStr = $searchInput.val();
203
  if (searchStr === _this.collection.get('s')) {
204
  return;
@@ -213,6 +228,7 @@
213
  },
214
  changePage: function(ev) {
215
  var $navButton, new_page;
 
216
  $navButton = jQuery(ev.currentTarget);
217
  new_page = this.collection.get('paged');
218
  if ($navButton.hasClass('p2p-prev')) {
@@ -223,10 +239,13 @@
223
  this.spinner.appendTo(this.$('.p2p-navigation'));
224
  return this.collection.save('paged', new_page);
225
  },
226
- refreshCandidates: function(response) {
227
  this.spinner.remove();
228
  this.$('button, .p2p-results, .p2p-navigation, .p2p-notice').remove();
229
- return this.$el.append(this.template(response));
 
 
 
230
  },
231
  afterInvalid: function() {
232
  return this.spinner.remove();
@@ -239,10 +258,8 @@
239
  'keypress :text': 'handleReturn'
240
  },
241
  initialize: function(options) {
242
- this.ajax_request = options.ajax_request;
243
  this.createButton = this.$('button');
244
- this.createInput = this.$(':text');
245
- return this.collection.on('create:from_new_item', this.afterItemCreated, this);
246
  },
247
  handleReturn: function(ev) {
248
  if (ev.keyCode === ENTER_KEY) {
@@ -252,7 +269,9 @@
252
  return null;
253
  },
254
  createItem: function(ev) {
255
- var title;
 
 
256
  ev.preventDefault();
257
  if (this.createButton.hasClass('inactive')) {
258
  return false;
@@ -263,12 +282,12 @@
263
  return;
264
  }
265
  this.createButton.addClass('inactive');
266
- this.collection.createItemAndConnect(title);
 
 
 
 
267
  return null;
268
- },
269
- afterItemCreated: function() {
270
- this.createInput.val('');
271
- return this.createButton.removeClass('inactive');
272
  }
273
  });
274
 
@@ -286,6 +305,7 @@
286
  },
287
  toggleTabs: function(ev) {
288
  var $tabs;
 
289
  ev.preventDefault();
290
  $tabs = this.$('.p2p-create-connections-tabs');
291
  $tabs.toggle();
@@ -297,6 +317,7 @@
297
  },
298
  setActiveTab: function(ev) {
299
  var $tab;
 
300
  ev.preventDefault();
301
  $tab = jQuery(ev.currentTarget);
302
  this.$('.wp-tab-bar li').removeClass('wp-tab-active');
@@ -315,11 +336,19 @@
315
  }
316
  });
317
 
 
 
 
 
 
 
318
  jQuery(function() {
319
  var clearVal, setVal;
 
320
  if (!jQuery('<input placeholder="1" />')[0].placeholder) {
321
  setVal = function() {
322
  var $this;
 
323
  $this = jQuery(this);
324
  if (!$this.val()) {
325
  $this.val($this.attr('placeholder'));
@@ -329,6 +358,7 @@
329
  };
330
  clearVal = function() {
331
  var $this;
 
332
  $this = jQuery(this);
333
  if ($this.hasClass('p2p-placeholder')) {
334
  $this.val('');
@@ -341,9 +371,10 @@
341
  Mustache.compilePartial('table-row', get_mustache_template('table-row'));
342
  return jQuery('.p2p-box').each(function() {
343
  var $metabox, $spinner, ajax_request, candidates, candidatesView, connections, connectionsView, createPostView, ctype, metaboxView;
 
344
  $metabox = jQuery(this);
345
  $spinner = jQuery('<img>', {
346
- 'src': P2PAdmin.spinner,
347
  'class': 'p2p-spinner'
348
  });
349
  candidates = new Candidates({
@@ -358,14 +389,18 @@
358
  };
359
  ajax_request = function(options, callback) {
360
  var params;
 
361
  params = _.extend({}, options, candidates.attributes, ctype, {
362
  action: 'p2p_box',
363
- nonce: P2PAdmin.nonce
364
  });
365
  return jQuery.post(ajaxurl, params, function(response) {
 
 
366
  try {
367
  response = jQuery.parseJSON(response);
368
- } catch (e) {
 
369
  if (typeof console !== "undefined" && console !== null) {
370
  console.error('Malformed response', response);
371
  }
@@ -397,13 +432,17 @@
397
  el: $metabox.find('.p2p-tab-create-post'),
398
  collection: connections
399
  });
400
- return metaboxView = new MetaboxView({
401
  el: $metabox,
402
  spinner: $spinner,
403
  cardinality: $metabox.data('cardinality'),
404
  candidates: candidates,
405
  connections: connections
406
  });
 
 
 
 
407
  });
408
  });
409
 
1
+ // Generated by CoffeeScript 1.6.2
2
  (function() {
3
+ var Candidate, Candidates, CandidatesView, Connection, Connections, ConnectionsView, CreatePostView, ENTER_KEY, MetaboxView, get_mustache_template, remove_row, row_wait;
4
 
5
  ENTER_KEY = 13;
6
 
7
  row_wait = function($td) {
8
+ return $td.find('.p2p-icon').css('background-image', 'url(' + P2PAdminL10n.spinner + ')');
9
  };
10
 
11
  remove_row = function($td) {
12
  var $table;
13
+
14
  $table = $td.closest('table');
15
  $td.closest('tr').remove();
16
  if (!$table.find('tbody tr').length) {
22
  return jQuery('#p2p-template-' + name).html();
23
  };
24
 
25
+ Candidate = Backbone.Model.extend({});
26
+
27
+ Connection = Backbone.Model.extend({});
28
+
29
  Candidates = Backbone.Model.extend({
30
  sync: function() {
31
  var params,
32
  _this = this;
33
+
34
+ params = {
35
  subaction: 'search'
36
+ };
37
  return this.ajax_request(params, function(response) {
38
  var _ref;
39
+
40
  _this.total_pages = ((_ref = response.navigation) != null ? _ref['total-pages-raw'] : void 0) || 1;
41
  return _this.trigger('sync', response);
42
  });
43
  },
44
  validate: function(attrs) {
45
  var _ref;
46
+
47
  if ((0 < (_ref = attrs['paged']) && _ref <= this.total_pages)) {
48
  return null;
49
  }
51
  }
52
  });
53
 
54
+ Connections = Backbone.Collection.extend({
55
+ model: Connection,
56
  createItemAndConnect: function(title) {
57
  var data,
58
  _this = this;
59
+
60
  data = {
61
  subaction: 'create_post',
62
  post_title: title
63
  };
64
  return this.ajax_request(data, function(response) {
65
+ return _this.trigger('create', response);
66
  });
67
  },
68
+ create: function(candidate) {
69
  var data,
70
  _this = this;
71
+
72
  data = {
73
  subaction: 'connect',
74
+ to: candidate.get('id')
75
  };
76
  return this.ajax_request(data, function(response) {
77
+ return _this.trigger('create', response);
78
  });
79
  },
80
+ "delete": function(connection) {
81
  var data,
82
  _this = this;
83
+
84
  data = {
85
  subaction: 'disconnect',
86
+ p2p_id: connection.get('id')
87
  };
88
  return this.ajax_request(data, function(response) {
89
+ return _this.trigger('delete', response, connection);
90
  });
91
  },
92
  clear: function() {
93
  var data,
94
  _this = this;
95
+
96
  data = {
97
  subaction: 'clear_connections'
98
  };
108
  'click td.p2p-col-delete .p2p-icon': 'delete'
109
  },
110
  initialize: function(options) {
 
111
  this.maybe_make_sortable();
112
  this.collection.on('create', this.afterCreate, this);
113
+ return this.collection.on('clear', this.afterClear, this);
 
 
 
114
  },
115
  maybe_make_sortable: function() {
116
  if (this.$('th.p2p-col-order').length) {
119
  helper: function(e, ui) {
120
  ui.children().each(function() {
121
  var $this;
122
+
123
  $this = jQuery(this);
124
  return $this.width($this.width());
125
  });
130
  },
131
  clear: function(ev) {
132
  var $td;
133
+
134
  ev.preventDefault();
135
+ if (!confirm(P2PAdminL10n.deleteConfirmMessage)) {
136
  return;
137
  }
138
  $td = jQuery(ev.target).closest('td');
143
  return this.$el.hide().find('tbody').html('');
144
  },
145
  "delete": function(ev) {
146
+ var $td, req;
147
+
148
  ev.preventDefault();
149
  $td = jQuery(ev.target).closest('td');
150
  row_wait($td);
151
+ req = this.collection["delete"](new Connection({
152
+ id: $td.find('input').val()
153
+ }));
154
+ req.done(function() {
155
+ return remove_row($td);
156
+ });
 
 
157
  return null;
158
  },
159
  afterCreate: function(response) {
172
  },
173
  initialize: function(options) {
174
  this.spinner = options.spinner;
175
+ options.connections.on('delete', this.afterCandidatesRefreshed, this);
176
+ options.connections.on('clear', this.afterCandidatesRefreshed, this);
177
+ this.collection.on('sync', this.afterCandidatesRefreshed, this);
 
178
  this.collection.on('error', this.afterInvalid, this);
179
  return this.collection.on('invalid', this.afterInvalid, this);
180
  },
 
 
 
 
 
 
 
181
  promote: function(ev) {
182
+ var $td, req,
183
+ _this = this;
184
+
185
  ev.preventDefault();
186
+ $td = jQuery(ev.target).closest('td');
187
  row_wait($td);
188
+ req = this.options.connections.create(new Candidate({
189
+ id: $td.find('div').data('item-id')
190
+ }));
191
+ req.done(function() {
192
+ if (_this.options.duplicate_connections) {
193
+ return $td.find('.p2p-icon').css('background-image', '');
194
+ } else {
195
+ return remove_row($td);
196
+ }
197
+ });
198
  return null;
199
  },
200
  handleReturn: function(ev) {
206
  handleSearch: function(ev) {
207
  var $searchInput, delayed,
208
  _this = this;
209
+
210
  if (delayed !== void 0) {
211
  clearTimeout(delayed);
212
  }
213
  $searchInput = jQuery(ev.target);
214
  delayed = setTimeout(function() {
215
  var searchStr;
216
+
217
  searchStr = $searchInput.val();
218
  if (searchStr === _this.collection.get('s')) {
219
  return;
228
  },
229
  changePage: function(ev) {
230
  var $navButton, new_page;
231
+
232
  $navButton = jQuery(ev.currentTarget);
233
  new_page = this.collection.get('paged');
234
  if ($navButton.hasClass('p2p-prev')) {
239
  this.spinner.appendTo(this.$('.p2p-navigation'));
240
  return this.collection.save('paged', new_page);
241
  },
242
+ afterCandidatesRefreshed: function(response) {
243
  this.spinner.remove();
244
  this.$('button, .p2p-results, .p2p-navigation, .p2p-notice').remove();
245
+ if ('string' !== typeof response) {
246
+ response = this.template(response);
247
+ }
248
+ return this.$el.append(response);
249
  },
250
  afterInvalid: function() {
251
  return this.spinner.remove();
258
  'keypress :text': 'handleReturn'
259
  },
260
  initialize: function(options) {
 
261
  this.createButton = this.$('button');
262
+ return this.createInput = this.$(':text');
 
263
  },
264
  handleReturn: function(ev) {
265
  if (ev.keyCode === ENTER_KEY) {
269
  return null;
270
  },
271
  createItem: function(ev) {
272
+ var req, title,
273
+ _this = this;
274
+
275
  ev.preventDefault();
276
  if (this.createButton.hasClass('inactive')) {
277
  return false;
282
  return;
283
  }
284
  this.createButton.addClass('inactive');
285
+ req = this.collection.createItemAndConnect(title);
286
+ req.done(function() {
287
+ _this.createInput.val('');
288
+ return _this.createButton.removeClass('inactive');
289
+ });
290
  return null;
 
 
 
 
291
  }
292
  });
293
 
305
  },
306
  toggleTabs: function(ev) {
307
  var $tabs;
308
+
309
  ev.preventDefault();
310
  $tabs = this.$('.p2p-create-connections-tabs');
311
  $tabs.toggle();
317
  },
318
  setActiveTab: function(ev) {
319
  var $tab;
320
+
321
  ev.preventDefault();
322
  $tab = jQuery(ev.currentTarget);
323
  this.$('.wp-tab-bar li').removeClass('wp-tab-active');
336
  }
337
  });
338
 
339
+ window.P2PAdmin = {
340
+ Candidate: Candidate,
341
+ Connection: Connection,
342
+ boxes: {}
343
+ };
344
+
345
  jQuery(function() {
346
  var clearVal, setVal;
347
+
348
  if (!jQuery('<input placeholder="1" />')[0].placeholder) {
349
  setVal = function() {
350
  var $this;
351
+
352
  $this = jQuery(this);
353
  if (!$this.val()) {
354
  $this.val($this.attr('placeholder'));
358
  };
359
  clearVal = function() {
360
  var $this;
361
+
362
  $this = jQuery(this);
363
  if ($this.hasClass('p2p-placeholder')) {
364
  $this.val('');
371
  Mustache.compilePartial('table-row', get_mustache_template('table-row'));
372
  return jQuery('.p2p-box').each(function() {
373
  var $metabox, $spinner, ajax_request, candidates, candidatesView, connections, connectionsView, createPostView, ctype, metaboxView;
374
+
375
  $metabox = jQuery(this);
376
  $spinner = jQuery('<img>', {
377
+ 'src': P2PAdminL10n.spinner,
378
  'class': 'p2p-spinner'
379
  });
380
  candidates = new Candidates({
389
  };
390
  ajax_request = function(options, callback) {
391
  var params;
392
+
393
  params = _.extend({}, options, candidates.attributes, ctype, {
394
  action: 'p2p_box',
395
+ nonce: P2PAdminL10n.nonce
396
  });
397
  return jQuery.post(ajaxurl, params, function(response) {
398
+ var e;
399
+
400
  try {
401
  response = jQuery.parseJSON(response);
402
+ } catch (_error) {
403
+ e = _error;
404
  if (typeof console !== "undefined" && console !== null) {
405
  console.error('Malformed response', response);
406
  }
432
  el: $metabox.find('.p2p-tab-create-post'),
433
  collection: connections
434
  });
435
+ metaboxView = new MetaboxView({
436
  el: $metabox,
437
  spinner: $spinner,
438
  cardinality: $metabox.data('cardinality'),
439
  candidates: candidates,
440
  connections: connections
441
  });
442
+ return P2PAdmin.boxes[ctype.p2p_type] = {
443
+ candidates: candidates,
444
+ connections: connections
445
+ };
446
  });
447
  });
448
 
admin/box.php CHANGED
@@ -39,7 +39,7 @@ class P2P_Box {
39
  wp_enqueue_script( 'p2p-box', plugins_url( 'box.js', __FILE__ ),
40
  array( 'backbone', 'mustache' ), P2P_PLUGIN_VERSION, true );
41
 
42
- wp_localize_script( 'p2p-box', 'P2PAdmin', array(
43
  'nonce' => wp_create_nonce( P2P_BOX_NONCE ),
44
  'spinner' => admin_url( 'images/wpspin_light.gif' ),
45
  'deleteConfirmMessage' => __( 'Are you sure you want to delete all connections?', P2P_TEXTDOMAIN ),
@@ -62,18 +62,18 @@ class P2P_Box {
62
  ), file_get_contents( dirname( __FILE__ ) . "/templates/$slug.html" ) );
63
  }
64
 
65
- function render( $post ) {
66
  $extra_qv = array_merge( self::$admin_box_qv, array(
67
  'p2p:context' => 'admin_box',
68
  'p2p:per_page' => -1
69
  ) );
70
 
71
- $this->connected_items = $this->ctype->get_connected( $post, $extra_qv, 'abstract' )->items;
72
 
73
  $data = array(
74
  'attributes' => $this->render_data_attributes(),
75
- 'connections' => $this->render_connections_table( $post ),
76
- 'create-connections' => $this->render_create_connections( $post ),
77
  'help' => isset( $this->labels->help ) ? $this->labels->help : ''
78
  );
79
 
@@ -95,7 +95,7 @@ class P2P_Box {
95
  return implode( ' ', $data_attr_str );
96
  }
97
 
98
- protected function render_connections_table( $post ) {
99
  $data = array();
100
 
101
  if ( empty( $this->connected_items ) )
@@ -117,7 +117,7 @@ class P2P_Box {
117
  return $data;
118
  }
119
 
120
- protected function render_create_connections( $post ) {
121
  $data = array(
122
  'label' => $this->labels->create,
123
  );
@@ -139,7 +139,7 @@ class P2P_Box {
139
  'tab-content' => $tab_content
140
  );
141
 
142
- // Create post tab
143
  if ( $this->can_create_post() ) {
144
  $tab_content = P2P_Mustache::render( 'tab-create-post', array(
145
  'title' => $this->labels->add_new_item
@@ -158,13 +158,9 @@ class P2P_Box {
158
  }
159
 
160
  protected function connection_row( $p2p_id, $item, $render = false ) {
161
- return $this->table_row( $this->columns, $p2p_id, $item, $render );
162
- }
163
-
164
- protected function table_row( $columns, $p2p_id, $item, $render = false ) {
165
  $data = array();
166
 
167
- foreach ( $columns as $key => $field ) {
168
  $data['columns'][] = array(
169
  'column' => $key,
170
  'content' => $field->render( $p2p_id, $item )
@@ -177,7 +173,25 @@ class P2P_Box {
177
  return P2P_Mustache::render( 'table-row', $data );
178
  }
179
 
180
- protected function post_rows( $current_post_id, $page = 1, $search = '' ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  $extra_qv = array_merge( self::$admin_box_qv, array(
182
  'p2p:context' => 'admin_box_candidates',
183
  'p2p:search' => $search,
@@ -193,12 +207,8 @@ class P2P_Box {
193
 
194
  $data = array();
195
 
196
- $columns = array(
197
- 'create' => new P2P_Field_Create( $this->columns['title'] ),
198
- );
199
-
200
  foreach ( $candidate->items as $item ) {
201
- $data['rows'][] = $this->table_row( $columns, 0, $item );
202
  }
203
 
204
  if ( $candidate->total_pages > 1 ) {
@@ -294,7 +304,7 @@ class P2P_Box {
294
  }
295
 
296
  private function refresh_candidates() {
297
- die( json_encode( $this->post_rows(
298
  $_REQUEST['from'], $_REQUEST['paged'], $_REQUEST['s'] ) ) );
299
  }
300
 
39
  wp_enqueue_script( 'p2p-box', plugins_url( 'box.js', __FILE__ ),
40
  array( 'backbone', 'mustache' ), P2P_PLUGIN_VERSION, true );
41
 
42
+ wp_localize_script( 'p2p-box', 'P2PAdminL10n', array(
43
  'nonce' => wp_create_nonce( P2P_BOX_NONCE ),
44
  'spinner' => admin_url( 'images/wpspin_light.gif' ),
45
  'deleteConfirmMessage' => __( 'Are you sure you want to delete all connections?', P2P_TEXTDOMAIN ),
62
  ), file_get_contents( dirname( __FILE__ ) . "/templates/$slug.html" ) );
63
  }
64
 
65
+ function render( $item ) {
66
  $extra_qv = array_merge( self::$admin_box_qv, array(
67
  'p2p:context' => 'admin_box',
68
  'p2p:per_page' => -1
69
  ) );
70
 
71
+ $this->connected_items = $this->ctype->get_connected( $item, $extra_qv, 'abstract' )->items;
72
 
73
  $data = array(
74
  'attributes' => $this->render_data_attributes(),
75
+ 'connections' => $this->render_connections_table( $item ),
76
+ 'create-connections' => $this->render_create_connections( $item ),
77
  'help' => isset( $this->labels->help ) ? $this->labels->help : ''
78
  );
79
 
95
  return implode( ' ', $data_attr_str );
96
  }
97
 
98
+ protected function render_connections_table( $item ) {
99
  $data = array();
100
 
101
  if ( empty( $this->connected_items ) )
117
  return $data;
118
  }
119
 
120
+ protected function render_create_connections( $item ) {
121
  $data = array(
122
  'label' => $this->labels->create,
123
  );
139
  'tab-content' => $tab_content
140
  );
141
 
142
+ // "Create post" tab
143
  if ( $this->can_create_post() ) {
144
  $tab_content = P2P_Mustache::render( 'tab-create-post', array(
145
  'title' => $this->labels->add_new_item
158
  }
159
 
160
  protected function connection_row( $p2p_id, $item, $render = false ) {
 
 
 
 
161
  $data = array();
162
 
163
+ foreach ( $this->columns as $key => $field ) {
164
  $data['columns'][] = array(
165
  'column' => $key,
166
  'content' => $field->render( $p2p_id, $item )
173
  return P2P_Mustache::render( 'table-row', $data );
174
  }
175
 
176
+ protected function candidate_row( $item ) {
177
+ $title = apply_filters( 'p2p_candidate_title', $item->get_title(), $item->get_object(), $this->ctype );
178
+
179
+ $title_data = array_merge( $this->columns['title']->get_data( $item ), array(
180
+ 'title' => $title,
181
+ 'item-id' => $item->get_id(),
182
+ ) );
183
+
184
+ $data = array();
185
+
186
+ $data['columns'][] = array(
187
+ 'column' => 'create',
188
+ 'content' => P2P_Mustache::render( 'column-create', $title_data )
189
+ );
190
+
191
+ return $data;
192
+ }
193
+
194
+ protected function candidate_rows( $current_post_id, $page = 1, $search = '' ) {
195
  $extra_qv = array_merge( self::$admin_box_qv, array(
196
  'p2p:context' => 'admin_box_candidates',
197
  'p2p:search' => $search,
207
 
208
  $data = array();
209
 
 
 
 
 
210
  foreach ( $candidate->items as $item ) {
211
+ $data['rows'][] = $this->candidate_row( $item );
212
  }
213
 
214
  if ( $candidate->total_pages > 1 ) {
304
  }
305
 
306
  private function refresh_candidates() {
307
+ die( json_encode( $this->candidate_rows(
308
  $_REQUEST['from'], $_REQUEST['paged'], $_REQUEST['s'] ) ) );
309
  }
310
 
admin/column.php CHANGED
@@ -43,7 +43,7 @@ abstract class P2P_Column {
43
 
44
  $connected = $this->ctype->get_connected( $items, $extra_qv, 'abstract' );
45
 
46
- $this->connected = p2p_list_cluster( $connected->items, '_p2p_get_other_id' );
47
  }
48
 
49
  function styles() {
43
 
44
  $connected = $this->ctype->get_connected( $items, $extra_qv, 'abstract' );
45
 
46
+ $this->connected = scb_list_group_by( $connected->items, '_p2p_get_other_id' );
47
  }
48
 
49
  function styles() {
admin/field-create.php DELETED
@@ -1,25 +0,0 @@
1
- <?php
2
-
3
- class P2P_Field_Create implements P2P_Field {
4
-
5
- protected $title_field;
6
-
7
- function __construct( $title_field ) {
8
- $this->title_field = $title_field;
9
- }
10
-
11
- function get_title() {
12
- // Not needed
13
- return '';
14
- }
15
-
16
- function render( $p2p_id, $item ) {
17
- $data = array_merge( $this->title_field->get_data( $item ), array(
18
- 'title' => $item->get_title(),
19
- 'item-id' => $item->get_id(),
20
- ) );
21
-
22
- return P2P_Mustache::render( 'column-create', $data );
23
- }
24
- }
25
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/api.php CHANGED
@@ -297,7 +297,7 @@ function p2p_list_posts( $posts, $args = array() ) {
297
  $list = new P2P_List( $posts, 'P2P_Item_Post' );
298
  }
299
 
300
- return $list->render( $args );
301
  }
302
 
303
  /**
@@ -316,7 +316,7 @@ function p2p_distribute_connected( $items, $connected, $prop_name ) {
316
  $indexed_list[ $item->ID ] = $item;
317
  }
318
 
319
- $groups = p2p_list_cluster( $connected, '_p2p_get_other_id' );
320
 
321
  foreach ( $groups as $outer_item_id => $connected_items ) {
322
  $indexed_list[ $outer_item_id ]->$prop_name = $connected_items;
297
  $list = new P2P_List( $posts, 'P2P_Item_Post' );
298
  }
299
 
300
+ return P2P_List_Renderer::render( $list, $args );
301
  }
302
 
303
  /**
316
  $indexed_list[ $item->ID ] = $item;
317
  }
318
 
319
+ $groups = scb_list_group_by( $connected, '_p2p_get_other_id' );
320
 
321
  foreach ( $groups as $outer_item_id => $connected_items ) {
322
  $indexed_list[ $outer_item_id ]->$prop_name = $connected_items;
core/exception.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php
2
+
3
+ class P2P_Exception extends RuntimeException {}
core/list-renderer.php ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class P2P_List_Renderer {
4
+
5
+ static function query_and_render( $args ) {
6
+ $ctype = p2p_type( $args['ctype'] );
7
+ if ( !$ctype ) {
8
+ trigger_error( sprintf( "Unregistered connection type '%s'.", $ctype ), E_USER_WARNING );
9
+ return '';
10
+ }
11
+
12
+ $directed = $ctype->find_direction( $args['item'] );
13
+ if ( !$directed )
14
+ return '';
15
+
16
+ $context = $args['context'];
17
+
18
+ $extra_qv = array(
19
+ 'p2p:per_page' => -1,
20
+ 'p2p:context' => $context
21
+ );
22
+
23
+ $connected = call_user_func( array( $directed, $args['method'] ), $args['item'], $extra_qv, 'abstract' );
24
+
25
+ switch ( $args['mode'] ) {
26
+ case 'inline':
27
+ $render_args = array(
28
+ 'separator' => ', '
29
+ );
30
+ break;
31
+
32
+ case 'ol':
33
+ $render_args = array(
34
+ 'before_list' => '<ol id="' . $ctype->name . '_list">',
35
+ 'after_list' => '</ol>',
36
+ );
37
+ break;
38
+
39
+ case 'ul':
40
+ default:
41
+ $render_args = array(
42
+ 'before_list' => '<ul id="' . $ctype->name . '_list">',
43
+ 'after_list' => '</ul>',
44
+ );
45
+ break;
46
+ }
47
+
48
+ $render_args['echo'] = false;
49
+
50
+ $html = self::render( $connected, $render_args );
51
+
52
+ return apply_filters( "p2p_{$context}_html", $html, $connected, $directed, $args['mode'] );
53
+ }
54
+
55
+ static function render( $list, $args = array() ) {
56
+ if ( empty( $list->items ) )
57
+ return '';
58
+
59
+ $args = wp_parse_args( $args, array(
60
+ 'before_list' => '<ul>', 'after_list' => '</ul>',
61
+ 'before_item' => '<li>', 'after_item' => '</li>',
62
+ 'separator' => false,
63
+ 'echo' => true
64
+ ) );
65
+
66
+ if ( $args['separator'] ) {
67
+ if ( '<ul>' == $args['before_list'] )
68
+ $args['before_list'] = '';
69
+
70
+ if ( '</ul>' == $args['after_list'] )
71
+ $args['after_list'] = '';
72
+ }
73
+
74
+ if ( !$args['echo'] )
75
+ ob_start();
76
+
77
+ echo $args['before_list'];
78
+
79
+ if ( $args['separator'] ) {
80
+ $rendered = array();
81
+ foreach ( $list->items as $item ) {
82
+ $rendered[] = self::render_item( $item );
83
+ }
84
+ echo implode( $args['separator'], $rendered );
85
+ } else {
86
+ foreach ( $list->items as $item ) {
87
+ echo $args['before_item'] . self::render_item( $item ) . $args['after_item'];
88
+ }
89
+ }
90
+
91
+ echo $args['after_list'];
92
+
93
+ if ( !$args['echo'] )
94
+ return ob_get_clean();
95
+ }
96
+
97
+ private static function render_item( $item ) {
98
+ return html_link( $item->get_permalink(), $item->get_title() );
99
+ }
100
+ }
101
+
core/list.php CHANGED
@@ -14,51 +14,5 @@ class P2P_List {
14
  $this->items = _p2p_wrap( $items, $item_type );
15
  }
16
  }
17
-
18
- function render( $args = array() ) {
19
- if ( empty( $this->items ) )
20
- return '';
21
-
22
- $args = wp_parse_args( $args, array(
23
- 'before_list' => '<ul>', 'after_list' => '</ul>',
24
- 'before_item' => '<li>', 'after_item' => '</li>',
25
- 'separator' => false,
26
- 'echo' => true
27
- ) );
28
-
29
- if ( $args['separator'] ) {
30
- if ( '<ul>' == $args['before_list'] )
31
- $args['before_list'] = '';
32
-
33
- if ( '</ul>' == $args['after_list'] )
34
- $args['after_list'] = '';
35
- }
36
-
37
- if ( !$args['echo'] )
38
- ob_start();
39
-
40
- echo $args['before_list'];
41
-
42
- if ( $args['separator'] ) {
43
- $list = array();
44
- foreach ( $this->items as $item ) {
45
- $list[] = $this->render_item( $item );
46
- }
47
- echo implode( $args['separator'], $list );
48
- } else {
49
- foreach ( $this->items as $item ) {
50
- echo $args['before_item'] . $this->render_item( $item ) . $args['after_item'];
51
- }
52
- }
53
-
54
- echo $args['after_list'];
55
-
56
- if ( !$args['echo'] )
57
- return ob_get_clean();
58
- }
59
-
60
- protected function render_item( $item ) {
61
- return html_link( $item->get_permalink(), $item->get_title() );
62
- }
63
  }
64
 
14
  $this->items = _p2p_wrap( $items, $item_type );
15
  }
16
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  }
18
 
core/query-post.php CHANGED
@@ -10,19 +10,19 @@ class P2P_Query_Post {
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;
10
  }
11
 
12
  static function parse_query( $wp_query ) {
13
+ $r = P2P_Query::create_from_qv( $wp_query->query_vars, 'post' );
14
 
15
+ if ( is_wp_error( $r ) ) {
16
+ trigger_error( $r->get_error_message(), E_USER_WARNING );
17
 
18
  $wp_query->set( 'year', 2525 );
19
  return;
20
  }
21
 
22
+ if ( null === $r )
23
  return;
24
 
25
+ list( $wp_query->_p2p_query, $wp_query->query_vars ) = $r;
26
 
27
  $wp_query->is_home = false;
28
  $wp_query->is_archive = true;
core/query-user.php CHANGED
@@ -9,18 +9,20 @@ class P2P_Query_User {
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(
25
  'fields' => 'query_fields',
26
  'join' => 'query_from',
9
  static function pre_user_query( $query ) {
10
  global $wpdb;
11
 
12
+ $r = P2P_Query::create_from_qv( $query->query_vars, 'user' );
13
 
14
+ if ( is_wp_error( $r ) ) {
15
+ trigger_error( $r->get_error_message(), E_USER_WARNING );
16
 
17
  $query->query_where = " AND 1=0";
18
  return;
19
  }
20
 
21
+ if ( null === $r )
22
  return;
23
 
24
+ list( $p2p_q, $query->query_vars ) = $r;
25
+
26
  $map = array(
27
  'fields' => 'query_fields',
28
  'join' => 'query_from',
core/query.php CHANGED
@@ -5,16 +5,7 @@ class P2P_Query {
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',
@@ -28,23 +19,10 @@ class P2P_Query {
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
-
41
- if ( isset( $q['connected_direction'] ) )
42
- $directions = (array) _p2p_pluck( $q, 'connected_direction' );
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 ) {
@@ -53,8 +31,8 @@ class P2P_Query {
53
  if ( !$ctype )
54
  continue;
55
 
56
- if ( isset( $directions[$i] ) ) {
57
- $directed = $ctype->set_direction( $directions[$i] );
58
  } else {
59
  $directed = $ctype->find_direction( $item, true, $object_type );
60
  }
@@ -65,27 +43,63 @@ class P2P_Query {
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_final_qv( $q, 'opposite' );
87
 
88
- $q = apply_filters( 'p2p_connected_args', $q, $directed, $item );
 
 
 
 
 
 
 
 
89
  }
90
 
91
  $p2p_q = new P2P_Query;
@@ -99,7 +113,7 @@ class P2P_Query {
99
 
100
  $p2p_q->query = isset( $q['connected_query'] ) ? $q['connected_query'] : array();
101
 
102
- return $p2p_q;
103
  }
104
 
105
  protected function __construct() {}
5
  protected $ctypes, $items, $query, $meta;
6
  protected $orderby, $order, $order_num;
7
 
8
+ private static function expand_shortcuts( $q ) {
 
 
 
 
 
 
 
 
 
9
  $shortcuts = array(
10
  'connected' => 'any',
11
  'connected_to' => 'to',
19
  }
20
  }
21
 
22
+ return $q;
23
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
+ private static function expand_ctypes( $item, $directions, $object_type, $ctypes ) {
26
  $p2p_types = array();
27
 
28
  foreach ( $ctypes as $i => $p2p_type ) {
31
  if ( !$ctype )
32
  continue;
33
 
34
+ if ( isset( $directions[ $i ] ) ) {
35
+ $directed = $ctype->set_direction( $directions[ $i ] );
36
  } else {
37
  $directed = $ctype->find_direction( $item, true, $object_type );
38
  }
43
  $p2p_types[] = $directed;
44
  }
45
 
46
+ return $p2p_types;
47
+ }
48
 
49
+ private static function finalize_query_vars( $q, $directed, $item ) {
50
+ if ( $orderby_key = $directed->get_orderby_key() ) {
51
+ $q = wp_parse_args( $q, array(
52
+ 'connected_orderby' => $orderby_key,
53
+ 'connected_order' => 'ASC',
54
+ 'connected_order_num' => true,
55
+ ) );
56
+ }
57
+
58
+ $q = array_merge_recursive( $q, array(
59
+ 'connected_meta' => $directed->data
60
+ ) );
61
+
62
+ $q = $directed->get_final_qv( $q, 'opposite' );
63
+
64
+ return apply_filters( 'p2p_connected_args', $q, $directed, $item );
65
+ }
66
+
67
+ /**
68
+ * Create instance from mixed query vars; also returns the modified query vars.
69
+ *
70
+ * @param array Query vars to collect parameters from
71
+ * @return:
72
+ * - null means ignore current query
73
+ * - WP_Error instance if the query is invalid
74
+ * - array( P2P_Query, array )
75
+ */
76
+ public static function create_from_qv( $q, $object_type ) {
77
+ $q = self::expand_shortcuts( $q );
78
+
79
+ if ( !isset( $q['connected_type'] ) ) {
80
+ if ( isset( $q['connected_items'] ) ) {
81
+ return new WP_Error( 'no_connection_type', "Queries without 'connected_type' are no longer supported." );
82
  }
83
 
84
+ return;
85
+ }
86
+
87
+ if ( isset( $q['connected_direction'] ) )
88
+ $directions = (array) _p2p_pluck( $q, 'connected_direction' );
89
+ else
90
+ $directions = array();
91
 
92
+ $item = isset( $q['connected_items'] ) ? $q['connected_items'] : 'any';
93
 
94
+ $ctypes = (array) _p2p_pluck( $q, 'connected_type' );
95
+
96
+ $p2p_types = self::expand_ctypes( $item, $directions, $object_type, $ctypes );
97
+
98
+ if ( empty( $p2p_types ) )
99
+ return new WP_Error( 'no_direction', "Could not find direction(s)." );
100
+
101
+ if ( 1 == count( $p2p_types ) ) {
102
+ $q = self::finalize_query_vars( $q, $p2p_types[0], $item );
103
  }
104
 
105
  $p2p_q = new P2P_Query;
113
 
114
  $p2p_q->query = isset( $q['connected_query'] ) ? $q['connected_query'] : array();
115
 
116
+ return array( $p2p_q, $q );
117
  }
118
 
119
  protected function __construct() {}
core/shortcodes.php CHANGED
@@ -23,7 +23,7 @@ class P2P_Shortcodes {
23
  'mode' => 'ul',
24
  ), $attr );
25
 
26
- return _p2p_get_list( array(
27
  'ctype' => $attr['type'],
28
  'method' => $method,
29
  'item' => $post,
23
  'mode' => 'ul',
24
  ), $attr );
25
 
26
+ return P2P_List_Renderer::query_and_render( array(
27
  'ctype' => $attr['type'],
28
  'method' => $method,
29
  'item' => $post,
core/side-post.php CHANGED
@@ -17,7 +17,15 @@ class P2P_Side_Post extends P2P_Side {
17
  }
18
 
19
  private function get_ptype() {
20
- return get_post_type_object( $this->first_post_type() );
 
 
 
 
 
 
 
 
21
  }
22
 
23
  function get_base_qv( $q ) {
@@ -44,15 +52,27 @@ class P2P_Side_Post extends P2P_Side {
44
  }
45
 
46
  function get_title() {
47
- return $this->get_ptype()->labels->name;
48
  }
49
 
50
  function get_labels() {
51
- return $this->get_ptype()->labels;
 
 
 
 
 
 
 
52
  }
53
 
54
  function can_edit_connections() {
55
- return current_user_can( $this->get_ptype()->cap->edit_posts );
 
 
 
 
 
56
  }
57
 
58
  function can_create_item() {
17
  }
18
 
19
  private function get_ptype() {
20
+ $ptype = $this->first_post_type();
21
+
22
+ $ptype_object = get_post_type_object( $ptype );
23
+
24
+ if ( !$ptype_object ) {
25
+ throw new P2P_Exception( "Can't find $ptype." );
26
+ }
27
+
28
+ return $ptype_object;
29
  }
30
 
31
  function get_base_qv( $q ) {
52
  }
53
 
54
  function get_title() {
55
+ return $this->get_labels()->name;
56
  }
57
 
58
  function get_labels() {
59
+ try {
60
+ $labels = $this->get_ptype()->labels;
61
+ } catch ( P2P_Exception $e ) {
62
+ trigger_error( $e->getMessage(), E_USER_WARNING );
63
+ $labels = new stdClass;
64
+ }
65
+
66
+ return $labels;
67
  }
68
 
69
  function can_edit_connections() {
70
+ try {
71
+ return current_user_can( $this->get_ptype()->cap->edit_posts );
72
+ } catch ( P2P_Exception $e ) {
73
+ trigger_error( $e->getMessage(), E_USER_WARNING );
74
+ return false;
75
+ }
76
  }
77
 
78
  function can_create_item() {
core/util.php CHANGED
@@ -1,18 +1,8 @@
1
  <?php
2
 
 
3
  function p2p_list_cluster( $items, $callback ) {
4
- $groups = array();
5
-
6
- foreach ( $items as $item ) {
7
- $key = $callback( $item );
8
-
9
- if ( null === $key )
10
- continue;
11
-
12
- $groups[ $key ][] = $item;
13
- }
14
-
15
- return $groups;
16
  }
17
 
18
  /** @internal */
@@ -133,52 +123,3 @@ function _p2p_get_other_id( $item ) {
133
  trigger_error( "Corrupted data for item $inner_item->ID", E_USER_WARNING );
134
  }
135
 
136
- /** @internal */
137
- function _p2p_get_list( $args ) {
138
- $ctype = p2p_type( $args['ctype'] );
139
- if ( !$ctype ) {
140
- trigger_error( sprintf( "Unregistered connection type '%s'.", $ctype ), E_USER_WARNING );
141
- return '';
142
- }
143
-
144
- $directed = $ctype->find_direction( $args['item'] );
145
- if ( !$directed )
146
- return '';
147
-
148
- $context = $args['context'];
149
-
150
- $extra_qv = array(
151
- 'p2p:per_page' => -1,
152
- 'p2p:context' => $context
153
- );
154
-
155
- $connected = call_user_func( array( $directed, $args['method'] ), $args['item'], $extra_qv, 'abstract' );
156
-
157
- switch ( $args['mode'] ) {
158
- case 'inline':
159
- $render_args = array(
160
- 'separator' => ', '
161
- );
162
- break;
163
-
164
- case 'ol':
165
- $render_args = array(
166
- 'before_list' => '<ol id="' . $ctype->name . '_list">',
167
- 'after_list' => '</ol>',
168
- );
169
- break;
170
-
171
- case 'ul':
172
- default:
173
- $render_args = array(
174
- 'before_list' => '<ul id="' . $ctype->name . '_list">',
175
- 'after_list' => '</ul>',
176
- );
177
- break;
178
- }
179
-
180
- $render_args['echo'] = false;
181
-
182
- return apply_filters( "p2p_{$context}_html", $connected->render( $render_args ), $connected, $directed, $args['mode'] );
183
- }
184
-
1
  <?php
2
 
3
+ /** @internal */
4
  function p2p_list_cluster( $items, $callback ) {
5
+ return scb_list_group_by( $items, $callback );
 
 
 
 
 
 
 
 
 
 
 
6
  }
7
 
8
  /** @internal */
123
  trigger_error( "Corrupted data for item $inner_item->ID", E_USER_WARNING );
124
  }
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/widget.php CHANGED
@@ -59,7 +59,7 @@ class P2P_Widget extends scbWidget {
59
  function widget( $args, $instance ) {
60
  $instance = array_merge( $this->defaults, $instance );
61
 
62
- $output = _p2p_get_list( array(
63
  'ctype' => $instance['ctype'],
64
  'method' => ( 'related' == $instance['listing'] ? 'get_related' : 'get_connected' ),
65
  'item' => get_queried_object(),
59
  function widget( $args, $instance ) {
60
  $instance = array_merge( $this->defaults, $instance );
61
 
62
+ $output = P2P_List_Renderer::query_and_render( array(
63
  'ctype' => $instance['ctype'],
64
  'method' => ( 'related' == $instance['listing'] ? 'get_related' : 'get_connected' ),
65
  'item' => get_queried_object(),
lang/posts-to-posts-ja.mo ADDED
Binary file
lang/posts-to-posts-ja.po ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2013
2
+ # This file is distributed under the same license as the package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: ja\n"
6
+ "Report-Msgid-Bugs-To: http://wordpress.org/tag/p2p\n"
7
+ "POT-Creation-Date: 2013-02-17 08:06:11+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: 2013-04-13 22:05+0900\n"
12
+ "Last-Translator: MIKI, Toru <toru@waviaei.com>\n"
13
+ "Language-Team: ja <waviaei@gmail.com>\n"
14
+ "X-Generator: Poedit 1.5.5\n"
15
+ "Language: ja\n"
16
+
17
+ #: admin/box.php:45
18
+ msgid "Are you sure you want to delete all connections?"
19
+ msgstr "全ての関連付けを削除してもよいですか?"
20
+
21
+ #: admin/box.php:136
22
+ msgid "Search"
23
+ msgstr "検索"
24
+
25
+ #: admin/box.php:213
26
+ msgid "previous"
27
+ msgstr "前へ"
28
+
29
+ #: admin/box.php:214
30
+ msgid "next"
31
+ msgstr "次へ"
32
+
33
+ #: admin/box.php:215
34
+ msgid "of"
35
+ msgstr " / "
36
+
37
+ #: admin/dropdown-user.php:32
38
+ msgid "Filter"
39
+ msgstr "フィルター"
40
+
41
+ #: admin/factory.php:88
42
+ msgid " (from)"
43
+ msgstr " (から)"
44
+
45
+ #: admin/factory.php:89
46
+ msgid " (to)"
47
+ msgstr " (へ)"
48
+
49
+ #: admin/field-delete.php:7
50
+ msgid "Delete all connections"
51
+ msgstr "全ての関連付けを削除"
52
+
53
+ #: admin/field-delete.php:16
54
+ msgid "Delete connection"
55
+ msgstr "関連付けを削除"
56
+
57
+ #: admin/tools-page.php:7
58
+ msgid "Connection Types"
59
+ msgstr "関連付け"
60
+
61
+ #: admin/tools-page.php:41
62
+ msgid "<em>%s</em> is not a registered connection type."
63
+ msgstr "<em>%s</em> は登録された関連付けではありません。"
64
+
65
+ #: admin/tools-page.php:50
66
+ msgid "Converted %1$s connections from <em>%2$s</em> to <em>%3$s</em>."
67
+ msgstr "<em>%2$s</em> から <em>%3$s</em> へ %1$s の関連付けを変換する。"
68
+
69
+ #: admin/tools-page.php:64
70
+ msgid "Name"
71
+ msgstr "関連付けの名称"
72
+
73
+ #: admin/tools-page.php:65
74
+ msgid "Information"
75
+ msgstr "内容"
76
+
77
+ #: admin/tools-page.php:66
78
+ msgid "Connections"
79
+ msgstr "数"
80
+
81
+ #: admin/tools-page.php:74
82
+ msgid "No connection types registered."
83
+ msgstr "関連付けは登録されていません。"
84
+
85
+ #: admin/tools-page.php:76
86
+ msgid "To register a connection type, see <a href=\"%s\">the wiki</a>."
87
+ msgstr "関連付けの登録方法は <a href=\"%s\">wiki</a> をご覧ください。"
88
+
89
+ #: admin/tools-page.php:93
90
+ msgid "Convert to registered connection type:"
91
+ msgstr "登録済みの関連付けに変換する:"
92
+
93
+ #: admin/tools-page.php:129
94
+ msgid "Go"
95
+ msgstr "Go"
96
+
97
+ #: core/connection-type.php:102
98
+ msgid "Create connections"
99
+ msgstr "関連付けを作成"
100
+
101
+ #: core/connection-type.php:120
102
+ msgid "Connected %s"
103
+ msgstr "%s への関連付け"
104
+
105
+ #: core/side-user.php:16
106
+ msgid "Users"
107
+ msgstr "ユーザー"
108
+
109
+ #: core/side-user.php:25
110
+ msgid "User"
111
+ msgstr "ユーザー"
112
+
113
+ #: core/side-user.php:26
114
+ msgid "Search Users"
115
+ msgstr "ユーザーを検索"
116
+
117
+ #: core/side-user.php:27
118
+ msgid "No users found."
119
+ msgstr "ユーザーは見つかりませんでした。"
120
+
121
+ #: core/widget.php:16
122
+ msgid "Posts 2 Posts"
123
+ msgstr "Posts 2 Posts"
124
+
125
+ #: core/widget.php:17
126
+ msgid "A list of posts connected to the current post"
127
+ msgstr "この投稿に関連付けられた投稿一覧"
128
+
129
+ #: core/widget.php:34
130
+ msgid "Title:"
131
+ msgstr "タイトル :"
132
+
133
+ #: core/widget.php:41
134
+ msgid "Connection type:"
135
+ msgstr "関連付け:"
136
+
137
+ #: core/widget.php:46
138
+ msgid "Connection listing:"
139
+ msgstr "表示するリスト:"
140
+
141
+ #: core/widget.php:52
142
+ msgid "connected"
143
+ msgstr "関連付けられた投稿"
144
+
145
+ #: core/widget.php:53
146
+ msgid "related"
147
+ msgstr "関係のある投稿"
148
+
149
+ #: scb/AdminPage.php:227
150
+ msgid "Settings <strong>saved</strong>."
151
+ msgstr "設定を<strong>保存</strong>しました。"
152
+
153
+ #: scb/AdminPage.php:466
154
+ msgid "Settings"
155
+ 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.5.2
6
  Author: scribu
7
  Author URI: http://scribu.net/
8
  Plugin URI: http://scribu.net/wordpress/posts-to-posts
@@ -10,7 +10,7 @@ Text Domain: posts-to-posts
10
  Domain Path: /lang
11
  */
12
 
13
- define( 'P2P_PLUGIN_VERSION', '1.5.2' );
14
 
15
  define( 'P2P_TEXTDOMAIN', 'posts-to-posts' );
16
 
2
  /*
3
  Plugin Name: Posts 2 Posts
4
  Description: Create many-to-many relationships between all types of posts.
5
+ Version: 1.6
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.6' );
14
 
15
  define( 'P2P_TEXTDOMAIN', 'posts-to-posts' );
16
 
readme.txt CHANGED
@@ -4,7 +4,7 @@ Contributors: scribu, ciobi
4
  Tags: connections, custom post types, relationships, many-to-many, users
5
  Requires at least: 3.5
6
  Tested up to: 3.5
7
- Stable tag: 1.5.2
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -25,6 +25,12 @@ Additionally, you can create many-to-many relationships between posts and users.
25
  * multiple authors per post
26
  * etc.
27
 
 
 
 
 
 
 
28
  Links: [**Documentation**](http://github.com/scribu/wp-posts-to-posts/wiki) | [Plugin News](http://scribu.net/wordpress/posts-to-posts) | [Author's Site](http://scribu.net)
29
 
30
  == Installation ==
@@ -51,6 +57,12 @@ Additional info can be found on the [wiki](http://github.com/scribu/wp-posts-to-
51
 
52
  == Changelog ==
53
 
 
 
 
 
 
 
54
  = 1.5.2 =
55
  * fixed get_prev() and get_next()
56
  * introduced get_adjacent_items()
4
  Tags: connections, custom post types, relationships, many-to-many, users
5
  Requires at least: 3.5
6
  Tested up to: 3.5
7
+ Stable tag: 1.6
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
25
  * multiple authors per post
26
  * etc.
27
 
28
+ = Support & Maintenance =
29
+
30
+ I, scribu, will not be offering support (either free or paid) for this plugin anymore.
31
+
32
+ If you want to help maintain the plugin, fork it [on github](https://github.com/scribu/wp-posts-to-posts) and open pull requests.
33
+
34
  Links: [**Documentation**](http://github.com/scribu/wp-posts-to-posts/wiki) | [Plugin News](http://scribu.net/wordpress/posts-to-posts) | [Author's Site](http://scribu.net)
35
 
36
  == Installation ==
57
 
58
  == Changelog ==
59
 
60
+ = 1.6 =
61
+ * introduced `p2p_candidate_title` filter
62
+ * introduced JavaScript API
63
+ * added Japanese translation
64
+ * various refactorings
65
+
66
  = 1.5.2 =
67
  * fixed get_prev() and get_next()
68
  * introduced get_adjacent_items()
scb/Table.php CHANGED
@@ -45,11 +45,27 @@ function scb_register_table( $key, $name = false ) {
45
  $wpdb->$key = $wpdb->prefix . $name;
46
  }
47
 
48
- function scb_install_table( $key, $columns, $upgrade_method = 'dbDelta' ) {
 
 
 
 
 
 
 
49
  global $wpdb;
50
 
51
  $full_table_name = $wpdb->$key;
52
 
 
 
 
 
 
 
 
 
 
53
  $charset_collate = '';
54
  if ( $wpdb->has_cap( 'collation' ) ) {
55
  if ( ! empty( $wpdb->charset ) )
@@ -58,16 +74,18 @@ function scb_install_table( $key, $columns, $upgrade_method = 'dbDelta' ) {
58
  $charset_collate .= " COLLATE $wpdb->collate";
59
  }
60
 
61
- if ( 'dbDelta' == $upgrade_method ) {
 
 
62
  require_once ABSPATH . 'wp-admin/includes/upgrade.php';
63
- dbDelta( "CREATE TABLE $full_table_name ( $columns ) $charset_collate" );
64
  return;
65
  }
66
 
67
- if ( 'delete_first' == $upgrade_method )
68
  $wpdb->query( "DROP TABLE IF EXISTS $full_table_name;" );
69
 
70
- $wpdb->query( "CREATE TABLE IF NOT EXISTS $full_table_name ( $columns ) $charset_collate;" );
71
  }
72
 
73
  function scb_uninstall_table( $key ) {
45
  $wpdb->$key = $wpdb->prefix . $name;
46
  }
47
 
48
+ /**
49
+ * Runs the SQL query for installing/upgrading a table
50
+ *
51
+ * @param string $key The key used in scb_register_table()
52
+ * @param string $columns The SQL columns for the CREATE TABLE statement
53
+ * @param array $opts Various other options
54
+ */
55
+ function scb_install_table( $key, $columns, $opts = array() ) {
56
  global $wpdb;
57
 
58
  $full_table_name = $wpdb->$key;
59
 
60
+ if ( is_string( $opts ) ) {
61
+ $opts = array( 'upgrade_method' => $opts );
62
+ }
63
+
64
+ $opts = wp_parse_args( $opts, array(
65
+ 'upgrade_method' => 'dbDelta',
66
+ 'table_options' => '',
67
+ ) );
68
+
69
  $charset_collate = '';
70
  if ( $wpdb->has_cap( 'collation' ) ) {
71
  if ( ! empty( $wpdb->charset ) )
74
  $charset_collate .= " COLLATE $wpdb->collate";
75
  }
76
 
77
+ $table_options = $charset_collate . ' ' . $opts['table_options'];
78
+
79
+ if ( 'dbDelta' == $opts['upgrade_method'] ) {
80
  require_once ABSPATH . 'wp-admin/includes/upgrade.php';
81
+ dbDelta( "CREATE TABLE $full_table_name ( $columns ) $table_options" );
82
  return;
83
  }
84
 
85
+ if ( 'delete_first' == $opts['upgrade_method'] )
86
  $wpdb->query( "DROP TABLE IF EXISTS $full_table_name;" );
87
 
88
+ $wpdb->query( "CREATE TABLE IF NOT EXISTS $full_table_name ( $columns ) $table_options;" );
89
  }
90
 
91
  function scb_uninstall_table( $key ) {
scb/Util.php CHANGED
@@ -137,6 +137,26 @@ function scb_list_fold( $list, $key, $value ) {
137
  return $r;
138
  }
139
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
  //_____Minimalist HTML framework_____
142
 
137
  return $r;
138
  }
139
 
140
+ /**
141
+ * Splits a list into sets, grouped by the result of running each value through $fn.
142
+ *
143
+ * @param array List of items to be partitioned
144
+ * @param callback Function that takes an element and returns a string key
145
+ */
146
+ function scb_list_group_by( $list, $fn ) {
147
+ $groups = array();
148
+
149
+ foreach ( $list as $item ) {
150
+ $key = call_user_func( $fn, $item );
151
+
152
+ if ( null === $key )
153
+ continue;
154
+
155
+ $groups[ $key ][] = $item;
156
+ }
157
+
158
+ return $groups;
159
+ }
160
 
161
  //_____Minimalist HTML framework_____
162
 
scb/load.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- $GLOBALS['_scb_data'] = array( 58, __FILE__, array(
4
  'scbUtil', 'scbOptions', 'scbForms', 'scbTable',
5
  'scbWidget', 'scbAdminPage', 'scbBoxesPage', 'scbPostMetabox',
6
  'scbCron', 'scbHooks',
1
  <?php
2
 
3
+ $GLOBALS['_scb_data'] = array( 59, __FILE__, array(
4
  'scbUtil', 'scbOptions', 'scbForms', 'scbTable',
5
  'scbWidget', 'scbAdminPage', 'scbBoxesPage', 'scbPostMetabox',
6
  'scbCron', 'scbHooks',