Posts 2 Posts - Version 1.1.6

Version Description

  • convert "View All" tab into button
  • refresh candidate list after deleting a connection
  • fix cardinality check
  • introduce 'p2p_connection_type_args' filter
  • make 'connected_type' accept an array of connection type names
  • inadvertently remove support for queries without 'connected_type' parameter
Download this release

Release Info

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

Code changes from version 1.0.1 to 1.1.6

admin/box-factory.php CHANGED
@@ -9,8 +9,36 @@ class P2P_Box_Factory {
9
 
10
  private static $box_args = array();
11
 
12
- static function register( $ctype_id, $box_args ) {
13
- if ( isset( self::$box_args[$ctype_id] ) )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  return false;
15
 
16
  $box_args = (object) wp_parse_args( $box_args, array(
@@ -33,20 +61,14 @@ class P2P_Box_Factory {
33
  $field_args['values'] = array( true => ' ' );
34
  }
35
 
36
- self::$box_args[$ctype_id] = $box_args;
37
 
38
  return true;
39
  }
40
 
41
- static function init() {
42
- add_action( 'add_meta_boxes', array( __CLASS__, 'add_meta_boxes' ) );
43
- add_action( 'save_post', array( __CLASS__, 'save_post' ), 10, 2 );
44
- add_action( 'wp_ajax_p2p_box', array( __CLASS__, 'wp_ajax_p2p_box' ) );
45
- }
46
-
47
  static function add_meta_boxes( $post_type ) {
48
- foreach ( self::$box_args as $ctype_id => $box_args ) {
49
- $ctype = p2p_type( $ctype_id );
50
 
51
  $dir = self::get_visible_directions( $post_type, $ctype, $box_args->show );
52
 
@@ -68,7 +90,7 @@ class P2P_Box_Factory {
68
  continue;
69
 
70
  add_meta_box(
71
- "p2p-{$direction}-{$ctype->id}",
72
  $title[$key],
73
  array( $box, 'render' ),
74
  $post_type,
@@ -87,10 +109,7 @@ class P2P_Box_Factory {
87
  return array();
88
 
89
  if ( $ctype->indeterminate && !$ctype->reciprocal ) {
90
- if ( 'any' == $show_ui )
91
- return array( 'from', 'to' );
92
- else
93
- return array( $show_ui );
94
  }
95
 
96
  if ( 'any' == $show_ui || $direction == $show_ui )
@@ -107,31 +126,22 @@ class P2P_Box_Factory {
107
  return;
108
 
109
  // Custom fields
110
- if ( isset( $_POST['p2p_ctypes'] ) ) {
111
- foreach ( $_POST['p2p_ctypes'] as $ctype_id ) {
112
- $ctype = p2p_type( $ctype_id );
113
  if ( !$ctype )
114
  continue;
115
 
116
- foreach ( $ctype->get_connected( $post_id )->posts as $post ) {
117
- $p2p_id = $post->p2p_id;
118
- $data = scbForms::get_value( array( 'p2p_meta', $p2p_id ), $_POST, array() );
119
-
120
- foreach ( self::$box_args[$ctype_id]->fields as $key => $field_args ) {
121
- if ( 'checkbox' == $field_args['type'] ) {
122
- $new_values = scbForms::get_value( $key, $data, array() );
123
 
124
- $old_values = p2p_get_meta( $p2p_id, $key );
 
 
125
 
126
- foreach ( array_diff( $new_values, $old_values ) as $value )
127
- p2p_add_meta( $p2p_id, $key, $value );
128
 
129
- foreach ( array_diff( $old_values, $new_values ) as $value )
130
- p2p_delete_meta( $p2p_id, $key, $value );
131
- } else {
132
- p2p_update_meta( $p2p_id, $key, $data[$key] );
133
- }
134
- }
135
  }
136
  }
137
  }
@@ -152,8 +162,8 @@ class P2P_Box_Factory {
152
  static function wp_ajax_p2p_box() {
153
  check_ajax_referer( P2P_BOX_NONCE, 'nonce' );
154
 
155
- $ctype = p2p_type( $_REQUEST['ctype_id'] );
156
- if ( !$ctype || !isset( self::$box_args[$ctype->id] ) )
157
  die(0);
158
 
159
  $post_type = get_post_type( $_REQUEST['from'] );
@@ -164,7 +174,7 @@ class P2P_Box_Factory {
164
  if ( !$directed )
165
  die(0);
166
 
167
- $box = new P2P_Box( self::$box_args[$ctype->id], $directed, $post_type );
168
 
169
  if ( !$box->check_capability() )
170
  die(-1);
9
 
10
  private static $box_args = array();
11
 
12
+ static function init() {
13
+ add_filter( 'p2p_connection_type_args', array( __CLASS__, 'filter_args' ) );
14
+
15
+ add_action( 'add_meta_boxes', array( __CLASS__, 'add_meta_boxes' ) );
16
+ add_action( 'save_post', array( __CLASS__, 'save_post' ), 10, 2 );
17
+ add_action( 'wp_ajax_p2p_box', array( __CLASS__, 'wp_ajax_p2p_box' ) );
18
+ }
19
+
20
+ static function filter_args( $args ) {
21
+ if ( isset( $args['admin_box'] ) ) {
22
+ $box_args = _p2p_pluck( $args, 'admin_box' );
23
+ if ( !is_array( $box_args ) )
24
+ $box_args = array( 'show' => $box_args );
25
+ } else {
26
+ $box_args = array();
27
+ }
28
+
29
+ foreach ( array( 'fields', 'can_create_post' ) as $key ) {
30
+ if ( isset( $args[ $key ] ) ) {
31
+ $box_args[ $key ] = _p2p_pluck( $args, $key );
32
+ }
33
+ }
34
+
35
+ self::register( $args['name'], $box_args );
36
+
37
+ return $args;
38
+ }
39
+
40
+ static function register( $p2p_type, $box_args ) {
41
+ if ( isset( self::$box_args[$p2p_type] ) )
42
  return false;
43
 
44
  $box_args = (object) wp_parse_args( $box_args, array(
61
  $field_args['values'] = array( true => ' ' );
62
  }
63
 
64
+ self::$box_args[$p2p_type] = $box_args;
65
 
66
  return true;
67
  }
68
 
 
 
 
 
 
 
69
  static function add_meta_boxes( $post_type ) {
70
+ foreach ( self::$box_args as $p2p_type => $box_args ) {
71
+ $ctype = p2p_type( $p2p_type );
72
 
73
  $dir = self::get_visible_directions( $post_type, $ctype, $box_args->show );
74
 
90
  continue;
91
 
92
  add_meta_box(
93
+ "p2p-{$direction}-{$ctype->name}",
94
  $title[$key],
95
  array( $box, 'render' ),
96
  $post_type,
109
  return array();
110
 
111
  if ( $ctype->indeterminate && !$ctype->reciprocal ) {
112
+ return _p2p_expand_direction( $show_ui );
 
 
 
113
  }
114
 
115
  if ( 'any' == $show_ui || $direction == $show_ui )
126
  return;
127
 
128
  // Custom fields
129
+ if ( isset( $_POST['p2p_types'] ) ) {
130
+ foreach ( $_POST['p2p_types'] as $p2p_type ) {
131
+ $ctype = p2p_type( $p2p_type );
132
  if ( !$ctype )
133
  continue;
134
 
135
+ foreach ( $ctype->get_connections( $post_id ) as $p2p_id => $item_id ) {
136
+ $fields = self::$box_args[$p2p_type]->fields;
 
 
 
 
 
137
 
138
+ foreach ( $fields as $key => &$field ) {
139
+ $field['name'] = $key;
140
+ }
141
 
142
+ $data = scbForms::get_value( array( 'p2p_meta', $p2p_id ), $_POST, array() );
 
143
 
144
+ scbForms::update_meta( $fields, $data, $p2p_id, 'p2p' );
 
 
 
 
 
145
  }
146
  }
147
  }
162
  static function wp_ajax_p2p_box() {
163
  check_ajax_referer( P2P_BOX_NONCE, 'nonce' );
164
 
165
+ $ctype = p2p_type( $_REQUEST['p2p_type'] );
166
+ if ( !$ctype || !isset( self::$box_args[$ctype->name] ) )
167
  die(0);
168
 
169
  $post_type = get_post_type( $_REQUEST['from'] );
174
  if ( !$directed )
175
  die(0);
176
 
177
+ $box = new P2P_Box( self::$box_args[$ctype->name], $directed, $post_type );
178
 
179
  if ( !$box->check_capability() )
180
  die(-1);
admin/box.css CHANGED
@@ -2,12 +2,12 @@
2
  color: #AAA;
3
  }
4
 
5
- .p2p-connections {
6
- margin-bottom: 8px;
7
  }
8
 
9
- .p2p-connections a {
10
- text-decoration: none;
11
  }
12
 
13
  /* Tabs */
@@ -23,12 +23,12 @@
23
  display: block;
24
  }
25
 
26
- /* Connections */
27
-
28
- .p2p-connections {
29
- margin-top: 4px;
30
  }
31
 
 
 
32
  .p2p-results,
33
  .p2p-connections {
34
  background-color: #F9F9F9;
@@ -40,9 +40,14 @@
40
  width: 100%;
41
  }
42
 
 
 
 
 
 
 
43
  .p2p-search + .p2p-results,
44
- .p2p-results + .p2p-navigation,
45
- .p2p-tab-create-post .button {
46
  margin-top: 8px;
47
  }
48
 
@@ -125,6 +130,13 @@ td.p2p-col-create {
125
  background: url("images/delete.png") no-repeat scroll 50% 50% transparent;
126
  }
127
 
 
 
 
 
 
 
 
128
  td.p2p-col-order {
129
  width: 13px;
130
  }
@@ -147,7 +159,8 @@ tr:hover td.p2p-col-order {
147
  }
148
 
149
  .p2p-tab-create-post input[type="text"] {
150
- width: 94%;
 
151
  }
152
 
153
  #side-sortables .p2p-notice {
2
  color: #AAA;
3
  }
4
 
5
+ .p2p-box a {
6
+ text-decoration: none;
7
  }
8
 
9
+ .p2p-create-connections p {
10
+ margin-bottom: .5em;
11
  }
12
 
13
  /* Tabs */
23
  display: block;
24
  }
25
 
26
+ .tabs-panel {
27
+ border-radius: 0 3px 3px 3px;
 
 
28
  }
29
 
30
+ /* Connections */
31
+
32
  .p2p-results,
33
  .p2p-connections {
34
  background-color: #F9F9F9;
40
  width: 100%;
41
  }
42
 
43
+ .p2p-box .tabs-panel button {
44
+ clear: both;
45
+ display: block;
46
+ margin-top: 8px;
47
+ }
48
+
49
  .p2p-search + .p2p-results,
50
+ .p2p-results + .p2p-navigation {
 
51
  margin-top: 8px;
52
  }
53
 
130
  background: url("images/delete.png") no-repeat scroll 50% 50% transparent;
131
  }
132
 
133
+ .p2p-col-title img {
134
+ display: block;
135
+ margin: 2px 0;
136
+ max-height: 32px;
137
+ max-width: 40px;
138
+ }
139
+
140
  td.p2p-col-order {
141
  width: 13px;
142
  }
159
  }
160
 
161
  .p2p-tab-create-post input[type="text"] {
162
+ width: 100%;
163
+ display: block;
164
  }
165
 
166
  #side-sortables .p2p-notice {
admin/box.js CHANGED
@@ -21,7 +21,7 @@
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, row_ajax_request, append_connection, clear_connections, delete_connection, create_connection, switch_to_tab, PostsTab, searchTab, listTab, $searchInput, $createButton, $createInput;
25
  $metabox = jQuery(this);
26
  $connections = $metabox.find('.p2p-connections');
27
  $spinner = jQuery('<img>', {
@@ -29,29 +29,100 @@
29
  'class': 'p2p-spinner'
30
  });
31
  ajax_request = function(data, callback, type){
 
32
  type == null && (type = 'POST');
33
  data.action = 'p2p_box';
34
  data.nonce = P2PAdmin.nonce;
35
- data.ctype_id = $metabox.data('ctype_id');
36
  data.direction = $metabox.data('direction');
37
  data.from = jQuery('#post_ID').val();
 
 
 
 
 
 
 
 
 
 
38
  return jQuery.ajax({
39
  type: type,
40
  url: ajaxurl,
41
  data: data,
42
- success: callback
43
  });
44
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  row_ajax_request = function($td, data, callback){
46
  $td.html($spinner.show());
47
  return ajax_request(data, callback);
48
  };
49
- append_connection = function(html){
50
- $connections.show().find('tbody').append(html);
 
 
 
 
 
51
  if ('one' == $metabox.data('cardinality')) {
52
  return $metabox.find('.p2p-create-connections').hide();
53
  }
54
  };
 
 
 
 
55
  clear_connections = function(ev){
56
  var $self, $td, data, _this = this;
57
  if (!confirm(P2PAdmin.deleteConfirmMessage)) {
@@ -65,7 +136,7 @@
65
  row_ajax_request($td, data, function(response){
66
  $connections.hide().find('tbody').html('');
67
  $td.html($self);
68
- return $metabox.find('.p2p-create-connections').show();
69
  });
70
  return false;
71
  };
@@ -79,10 +150,8 @@
79
  };
80
  row_ajax_request($td, data, function(response){
81
  $td.closest('tr').remove();
82
- if (!$connections.find('tbody tr').length) {
83
- $connections.hide();
84
- }
85
- return $metabox.find('.p2p-create-connections').show();
86
  });
87
  return false;
88
  };
@@ -95,15 +164,10 @@
95
  to: $self.data('post_id')
96
  };
97
  row_ajax_request($td, data, function(response){
98
- var $table;
99
  append_connection(response);
100
  if ($metabox.data('prevent_duplicates')) {
101
- $table = $td.closest('table');
102
- if (1 == $table.find('tbody tr').length) {
103
- return $table.remove();
104
- } else {
105
- return $td.closest('tr').remove();
106
- }
107
  } else {
108
  return $td.html($self);
109
  }
@@ -132,66 +196,11 @@
132
  }
133
  });
134
  }
135
- PostsTab = (function(){
136
- PostsTab.displayName = 'PostsTab';
137
- var prototype = PostsTab.prototype, constructor = PostsTab;
138
- function PostsTab(selector){
139
- this.tab = $metabox.find(selector);
140
- this.init_pagination_data();
141
- this.tab.delegate('.p2p-prev, .p2p-next', 'click', __bind(this, this.change_page));
142
- this.data = {
143
- subaction: 'search',
144
- s: ''
145
- };
146
- }
147
- prototype.init_pagination_data = function(){
148
- this.current_page = this.tab.find('.p2p-current').data('num') || 1;
149
- return this.total_pages = this.tab.find('.p2p-total').data('num') || 1;
150
- };
151
- prototype.change_page = function(ev){
152
- var $navButton, new_page;
153
- $navButton = jQuery(ev.target);
154
- new_page = this.current_page;
155
- if ($navButton.hasClass('inactive')) {
156
- return false;
157
- }
158
- if ($navButton.hasClass('p2p-prev')) {
159
- new_page--;
160
- } else {
161
- new_page++;
162
- }
163
- this.find_posts(new_page);
164
- return false;
165
- };
166
- prototype.find_posts = function(new_page){
167
- this.data.paged = new_page
168
- ? new_page > this.total_pages ? this.current_page : new_page
169
- : this.current_page;
170
- $spinner.appendTo(this.tab.find('.p2p-navigation'));
171
- return ajax_request(this.data, __bind(this, this.update_rows), 'GET');
172
- };
173
- prototype.update_rows = function(response){
174
- $spinner.remove();
175
- try {
176
- response = jQuery.parseJSON(response);
177
- } catch (e) {
178
- if (typeof console != 'undefined' && console !== null) {
179
- console.error('Malformed response', response);
180
- }
181
- return;
182
- }
183
- this.tab.find('.p2p-results, .p2p-navigation, .p2p-notice').remove();
184
- if (!response.rows) {
185
- return this.tab.append(jQuery('<div class="p2p-notice">').html(response.msg));
186
- } else {
187
- this.tab.append(response.rows);
188
- return this.init_pagination_data();
189
- }
190
- };
191
- return PostsTab;
192
- }());
193
- searchTab = new PostsTab('.p2p-tab-search');
194
- listTab = new PostsTab('.p2p-tab-list');
195
  $searchInput = $metabox.find('.p2p-tab-search :text');
196
  $searchInput.keypress(function(ev){
197
  if (13 === ev.keyCode) {
@@ -205,15 +214,15 @@
205
  return delayed = setTimeout(function(){
206
  var searchStr;
207
  searchStr = $searchInput.val();
208
- if ('' == searchStr || searchStr === searchTab.data.s) {
209
  return;
210
  }
211
- searchTab.data.s = searchStr;
212
  $spinner.insertAfter($searchInput).show();
213
  return searchTab.find_posts(1);
214
  }, 400);
215
  });
216
- $createButton = $metabox.find('.p2p-tab-create-post .button');
217
  $createInput = $metabox.find('.p2p-tab-create-post :text');
218
  $createButton.click(function(){
219
  var $button, title, data;
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>', {
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.find('input[name^="p2p_types"]').val();
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({
50
  type: type,
51
  url: ajaxurl,
52
  data: data,
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--;
81
+ } else {
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) {
98
+ return this.tab.append(jQuery('<div class="p2p-notice">').html(response.msg));
99
+ } else {
100
+ this.tab.append(response.rows);
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)) {
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
  };
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
  };
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();
170
+ return maybe_hide_table($td.closest('table'));
 
 
 
 
171
  } else {
172
  return $td.html($self);
173
  }
196
  }
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) {
214
  return delayed = setTimeout(function(){
215
  var searchStr;
216
  searchStr = $searchInput.val();
217
+ if (searchStr === searchTab.params.s) {
218
  return;
219
  }
220
+ searchTab.params.s = searchStr;
221
  $spinner.insertAfter($searchInput).show();
222
  return searchTab.find_posts(1);
223
  }, 400);
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;
admin/box.php CHANGED
@@ -5,7 +5,7 @@
5
  */
6
  interface P2P_Field {
7
  function get_title();
8
- function render( $key, $p2p_id, $post_id );
9
  }
10
 
11
  /**
@@ -20,20 +20,12 @@ class P2P_Box {
20
 
21
  private $columns;
22
 
23
- private static $extra_qv = array(
24
- 'update_post_term_cache' => false,
25
- 'update_post_meta_cache' => false,
26
- 'post_status' => 'any',
27
- );
28
-
29
  function __construct( $args, $ctype ) {
30
  $this->args = $args;
31
 
32
  $this->ctype = $ctype;
33
 
34
- $this->ptype = P2P_Util::get_first_valid_ptype( $this->ctype->get_other_post_type() );
35
-
36
- add_filter( 'posts_search', array( __CLASS__, '_search_by_title' ), 10, 2 );
37
 
38
  $this->init_columns();
39
  }
@@ -50,28 +42,33 @@ class P2P_Box {
50
  }
51
 
52
  protected function init_columns() {
 
 
53
  $this->columns = array(
54
  'delete' => new P2P_Field_Delete,
55
- 'title' => new P2P_Field_Title( $this->ptype->labels->singular_name ),
56
  );
57
 
58
  foreach ( $this->args->fields as $key => $data ) {
59
- $this->columns[ $key ] = new P2P_Field_Generic( $data );
60
  }
61
 
62
- if ( method_exists( $this->ctype, 'get_orderby_key' ) ) {
63
- $this->columns['order'] = new P2P_Field_Order( $this->ctype->get_orderby_key() );
64
  }
65
  }
66
 
67
- function render( $post ) {
68
- $qv = self::$extra_qv;
69
- $qv['nopaging'] = true;
70
 
71
- $this->connected_posts = $this->ctype->get_connected( $post->ID, $qv )->posts;
 
 
 
 
72
 
73
  $data = array(
74
- 'ctype-id' => $this->ctype->id,
75
  'attributes' => $this->render_data_attributes(),
76
  'connections' => $this->render_connections_table( $post ),
77
  'create-connections' => $this->render_create_connections( $post ),
@@ -82,7 +79,6 @@ class P2P_Box {
82
 
83
  protected function render_data_attributes() {
84
  $data_attr = array(
85
- 'ctype_id' => $this->ctype->id,
86
  'prevent_duplicates' => $this->ctype->prevent_duplicates,
87
  'cardinality' => $this->ctype->accepts_single_connection() ? 'one' : 'many',
88
  'direction' => $this->ctype->get_direction()
@@ -98,12 +94,12 @@ class P2P_Box {
98
  protected function render_connections_table( $post ) {
99
  $data = array();
100
 
101
- if ( empty( $this->connected_posts ) )
102
  $data['hide'] = 'style="display:none"';
103
 
104
  $tbody = array();
105
- foreach ( $this->connected_posts as $connected ) {
106
- $tbody[] = $this->connection_row( $connected->p2p_id, $connected->ID );
107
  }
108
  $data['tbody'] = $tbody;
109
 
@@ -122,12 +118,13 @@ class P2P_Box {
122
  'label' => __( 'Create connections:', P2P_TEXTDOMAIN )
123
  );
124
 
125
- if ( $this->ctype->accepts_single_connection() && !empty( $this->connected_posts ) )
126
  $data['hide'] = 'style="display:none"';
127
 
128
  // Search tab
129
  $tab_content = P2P_Mustache::render( 'tab-search', array(
130
- 'placeholder' => $this->ptype->labels->search_items,
 
131
  ) );
132
 
133
  $data['tabs'][] = array(
@@ -137,26 +134,21 @@ class P2P_Box {
137
  'tab-content' => $tab_content
138
  );
139
 
140
- // List tab
141
- $data['tabs'][] = array(
142
- 'tab-id' => 'list',
143
- 'tab-title' => __( 'View All', P2P_TEXTDOMAIN ),
144
- 'tab-content' => $this->post_rows( $post->ID )
145
- );
146
-
147
  // Create post tab
148
  if ( $this->can_create_post() ) {
149
  $tab_content = P2P_Mustache::render( 'tab-create-post', array(
150
- 'title' => $this->ptype->labels->add_new_item
151
  ) );
152
 
153
  $data['tabs'][] = array(
154
  'tab-id' => 'create-post',
155
- 'tab-title' => $this->ptype->labels->new_item,
156
  'tab-content' => $tab_content
157
  );
158
  }
159
 
 
 
160
  return $data;
161
  }
162
 
@@ -170,7 +162,7 @@ class P2P_Box {
170
  foreach ( $columns as $key => $field ) {
171
  $data['columns'][] = array(
172
  'column' => $key,
173
- 'content' => $field->render( $key, $p2p_id, $post_id )
174
  );
175
  }
176
 
@@ -181,36 +173,20 @@ class P2P_Box {
181
  }
182
 
183
  protected function post_rows( $current_post_id, $page = 1, $search = '' ) {
184
- $args = array_merge( self::$extra_qv, array(
185
- 'paged' => $page,
186
- 'posts_per_page' => 5,
187
- ) );
188
-
189
- if ( $search ) {
190
- $args['_p2p_box'] = true;
191
- $args['s'] = $search;
192
- }
193
-
194
- $query = $this->ctype->get_connectable( $current_post_id, $args );
195
 
196
- if ( empty( $query->posts ) )
197
  return false;
198
 
199
- $candidate = (object) array(
200
- 'posts' => $query->posts,
201
- 'current_page' => max( 1, $query->get('paged') ),
202
- 'total_pages' => $query->max_num_pages
203
- );
204
-
205
  $data = array();
206
 
207
  $columns = array(
208
  'create' => new P2P_Field_Create,
209
- 'title' => new P2P_Field_Title,
210
  );
211
 
212
- foreach ( $candidate->posts as $post ) {
213
- $data['rows'][] = $this->table_row( $columns, 0, $post->ID );
214
  }
215
 
216
  if ( $candidate->total_pages > 1 ) {
@@ -243,7 +219,7 @@ class P2P_Box {
243
  $args = array(
244
  'post_title' => $_POST['post_title'],
245
  'post_author' => get_current_user_id(),
246
- 'post_type' => $this->ptype->name
247
  );
248
 
249
  $args = apply_filters( 'p2p_new_post_args', $args, $this->ctype );
@@ -265,63 +241,69 @@ class P2P_Box {
265
  $p2p_id = $this->ctype->connect( $from, $to );
266
 
267
  if ( $p2p_id )
268
- echo $this->connection_row( $p2p_id, $to, true );
 
 
269
 
270
- die;
271
  }
272
 
273
  public function ajax_disconnect() {
274
- P2P_Storage::delete( $_POST['p2p_id'] );
275
 
276
- die(1);
277
  }
278
 
279
  public function ajax_clear_connections() {
280
  $this->ctype->disconnect_all( $_POST['from'] );
281
 
282
- die(1);
283
  }
284
 
285
  public function ajax_search() {
286
- $rows = $this->post_rows( $_GET['from'], $_GET['paged'], $_GET['s'] );
 
 
 
 
 
 
 
 
 
 
287
 
288
  if ( $rows ) {
289
  $results = compact( 'rows' );
290
  } else {
291
  $results = array(
292
- 'msg' => $this->ptype->labels->not_found,
293
  );
294
  }
295
 
296
- die( json_encode( $results ) );
297
  }
298
 
299
  protected function can_create_post() {
300
  if ( !$this->args->can_create_post )
301
  return false;
302
 
303
- $base_qv = ( 'from' == $this->ctype->get_direction() ) ? $this->ctype->to_query_vars : $this->ctype->from_query_vars;
 
 
 
304
 
305
- if ( count( $base_qv ) > 1 )
306
  return false;
307
 
308
- if ( count( $this->ctype->get_other_post_type() ) > 1 )
309
  return false;
310
 
311
- return $this->check_capability();
312
  }
313
 
314
  public function check_capability() {
315
- return current_user_can( $this->ptype->cap->edit_posts );
316
- }
317
-
318
- function _search_by_title( $sql, $wp_query ) {
319
- if ( $wp_query->is_search && $wp_query->get( '_p2p_box' ) ) {
320
- list( $sql ) = explode( ' OR ', $sql, 2 );
321
- return $sql . '))';
322
- }
323
-
324
- return $sql;
325
  }
326
  }
327
 
5
  */
6
  interface P2P_Field {
7
  function get_title();
8
+ function render( $p2p_id, $post_id );
9
  }
10
 
11
  /**
20
 
21
  private $columns;
22
 
 
 
 
 
 
 
23
  function __construct( $args, $ctype ) {
24
  $this->args = $args;
25
 
26
  $this->ctype = $ctype;
27
 
28
+ $this->labels = $this->ctype->get_opposite( 'labels' );
 
 
29
 
30
  $this->init_columns();
31
  }
42
  }
43
 
44
  protected function init_columns() {
45
+ $title_class = $this->get_column_title_class();
46
+
47
  $this->columns = array(
48
  'delete' => new P2P_Field_Delete,
49
+ 'title' => new $title_class( $this->labels->singular_name ),
50
  );
51
 
52
  foreach ( $this->args->fields as $key => $data ) {
53
+ $this->columns[ 'meta-' . $key ] = new P2P_Field_Generic( $key, $data );
54
  }
55
 
56
+ if ( $orderby_key = $this->ctype->get_orderby_key() ) {
57
+ $this->columns['order'] = new P2P_Field_Order( $orderby_key );
58
  }
59
  }
60
 
61
+ protected function get_column_title_class() {
62
+ $object_type = $this->ctype->get_opposite( 'object' );
 
63
 
64
+ return 'P2P_Field_Title_' . ucfirst( $object_type );
65
+ }
66
+
67
+ function render( $post ) {
68
+ $this->connected_items = $this->ctype->get_connections( $post->ID );
69
 
70
  $data = array(
71
+ 'p2p-type' => $this->ctype->name,
72
  'attributes' => $this->render_data_attributes(),
73
  'connections' => $this->render_connections_table( $post ),
74
  'create-connections' => $this->render_create_connections( $post ),
79
 
80
  protected function render_data_attributes() {
81
  $data_attr = array(
 
82
  'prevent_duplicates' => $this->ctype->prevent_duplicates,
83
  'cardinality' => $this->ctype->accepts_single_connection() ? 'one' : 'many',
84
  'direction' => $this->ctype->get_direction()
94
  protected function render_connections_table( $post ) {
95
  $data = array();
96
 
97
+ if ( empty( $this->connected_items ) )
98
  $data['hide'] = 'style="display:none"';
99
 
100
  $tbody = array();
101
+ foreach ( $this->connected_items as $p2p_id => $item_id ) {
102
+ $tbody[] = $this->connection_row( $p2p_id, $item_id );
103
  }
104
  $data['tbody'] = $tbody;
105
 
118
  'label' => __( 'Create connections:', P2P_TEXTDOMAIN )
119
  );
120
 
121
+ if ( $this->ctype->accepts_single_connection() && !empty( $this->connected_items ) )
122
  $data['hide'] = 'style="display:none"';
123
 
124
  // Search tab
125
  $tab_content = P2P_Mustache::render( 'tab-search', array(
126
+ 'placeholder' => $this->labels->search_items,
127
+ 'view-all' => __( 'View All', P2P_TEXTDOMAIN ),
128
  ) );
129
 
130
  $data['tabs'][] = array(
134
  'tab-content' => $tab_content
135
  );
136
 
 
 
 
 
 
 
 
137
  // Create post tab
138
  if ( $this->can_create_post() ) {
139
  $tab_content = P2P_Mustache::render( 'tab-create-post', array(
140
+ 'title' => $this->labels->add_new_item
141
  ) );
142
 
143
  $data['tabs'][] = array(
144
  'tab-id' => 'create-post',
145
+ 'tab-title' => $this->labels->new_item,
146
  'tab-content' => $tab_content
147
  );
148
  }
149
 
150
+ $data['show-tab-headers'] = count( $data['tabs'] ) > 1 ? array(true) : false;
151
+
152
  return $data;
153
  }
154
 
162
  foreach ( $columns as $key => $field ) {
163
  $data['columns'][] = array(
164
  'column' => $key,
165
+ 'content' => $field->render( $p2p_id, $post_id )
166
  );
167
  }
168
 
173
  }
174
 
175
  protected function post_rows( $current_post_id, $page = 1, $search = '' ) {
176
+ $candidate = $this->ctype->get_connectable( $current_post_id, $page, $search );
 
 
 
 
 
 
 
 
 
 
177
 
178
+ if ( empty( $candidate->items ) )
179
  return false;
180
 
 
 
 
 
 
 
181
  $data = array();
182
 
183
  $columns = array(
184
  'create' => new P2P_Field_Create,
185
+ 'title' => $this->columns['title']
186
  );
187
 
188
+ foreach ( $candidate->items as $item ) {
189
+ $data['rows'][] = $this->table_row( $columns, 0, $item->ID );
190
  }
191
 
192
  if ( $candidate->total_pages > 1 ) {
219
  $args = array(
220
  'post_title' => $_POST['post_title'],
221
  'post_author' => get_current_user_id(),
222
+ 'post_type' => $this->ctype->get_opposite( 'side' )->post_type[0]
223
  );
224
 
225
  $args = apply_filters( 'p2p_new_post_args', $args, $this->ctype );
241
  $p2p_id = $this->ctype->connect( $from, $to );
242
 
243
  if ( $p2p_id )
244
+ $r = array( 'row' => $this->connection_row( $p2p_id, $to, true ) );
245
+ else
246
+ $r = array( 'error' => __( "Can't create connection.", P2P_TEXTDOMAIN ) );
247
 
248
+ die( json_encode( $r ) );
249
  }
250
 
251
  public function ajax_disconnect() {
252
+ p2p_delete_connection( $_POST['p2p_id'] );
253
 
254
+ $this->refresh_candidates();
255
  }
256
 
257
  public function ajax_clear_connections() {
258
  $this->ctype->disconnect_all( $_POST['from'] );
259
 
260
+ $this->refresh_candidates();
261
  }
262
 
263
  public function ajax_search() {
264
+ die( json_encode( $this->_ajax_search( $_GET ) ) );
265
+ }
266
+
267
+ private function refresh_candidates() {
268
+ $results = $this->_ajax_search( $_POST );
269
+
270
+ die( json_encode( $results ) );
271
+ }
272
+
273
+ private function _ajax_search( $args ) {
274
+ $rows = $this->post_rows( $args['from'], $args['paged'], $args['s'] );
275
 
276
  if ( $rows ) {
277
  $results = compact( 'rows' );
278
  } else {
279
  $results = array(
280
+ 'msg' => $this->labels->not_found,
281
  );
282
  }
283
 
284
+ return $results;
285
  }
286
 
287
  protected function can_create_post() {
288
  if ( !$this->args->can_create_post )
289
  return false;
290
 
291
+ if ( 'post' != $this->ctype->get_opposite( 'object' ) )
292
+ return false;
293
+
294
+ $side = $this->ctype->get_opposite( 'side' );
295
 
296
+ if ( count( $side->post_type ) > 1 )
297
  return false;
298
 
299
+ if ( count( $side->query_vars ) > 1 )
300
  return false;
301
 
302
+ return true;
303
  }
304
 
305
  public function check_capability() {
306
+ return $this->ctype->get_opposite( 'side' )->check_capability();
 
 
 
 
 
 
 
 
 
307
  }
308
  }
309
 
admin/column-factory.php CHANGED
@@ -4,14 +4,32 @@ class P2P_Column_Factory {
4
 
5
  private static $column_args = array();
6
 
7
- static function register( $ctype_id, $column_args ) {
8
- if ( isset( self::$column_args[$ctype_id] ) )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  return false;
10
 
11
  if ( !$column_args )
12
  return false;
13
 
14
- self::$column_args[$ctype_id] = $column_args;
15
 
16
  return true;
17
  }
@@ -24,8 +42,8 @@ class P2P_Column_Factory {
24
 
25
  $post_type = $screen->post_type;
26
 
27
- foreach ( self::$column_args as $ctype_id => $column_args ) {
28
- $ctype = p2p_type( $ctype_id );
29
 
30
  $directed = $ctype->find_direction( $post_type );
31
  if ( !$directed )
@@ -36,11 +54,13 @@ class P2P_Column_Factory {
36
 
37
  $column = new P2P_Column( $directed );
38
 
 
 
39
  add_filter( "manage_{$screen->id}_columns", array( $column, 'add_column' ) );
40
  add_action( "manage_{$post_type}_posts_custom_column", array( $column, 'display_column' ), 10, 2 );
41
  }
42
  }
43
  }
44
 
45
- add_action( 'admin_head', array( 'P2P_Column_Factory', 'add_columns' ) );
46
 
4
 
5
  private static $column_args = array();
6
 
7
+ static function init() {
8
+ add_filter( 'p2p_connection_type_args', array( __CLASS__, 'filter_args' ) );
9
+
10
+ add_action( 'admin_print_styles', array( __CLASS__, 'add_columns' ) );
11
+ }
12
+
13
+ static function filter_args( $args ) {
14
+ if ( isset( $args['admin_column'] ) ) {
15
+ $column_args = _p2p_pluck( $args, 'admin_column' );
16
+ } else {
17
+ $column_args = false;
18
+ }
19
+
20
+ self::register( $args['name'], $column_args );
21
+
22
+ return $args;
23
+ }
24
+
25
+ static function register( $p2p_type, $column_args ) {
26
+ if ( isset( self::$column_args[$p2p_type] ) )
27
  return false;
28
 
29
  if ( !$column_args )
30
  return false;
31
 
32
+ self::$column_args[$p2p_type] = $column_args;
33
 
34
  return true;
35
  }
42
 
43
  $post_type = $screen->post_type;
44
 
45
+ foreach ( self::$column_args as $p2p_type => $column_args ) {
46
+ $ctype = p2p_type( $p2p_type );
47
 
48
  $directed = $ctype->find_direction( $post_type );
49
  if ( !$directed )
54
 
55
  $column = new P2P_Column( $directed );
56
 
57
+ $column->styles();
58
+
59
  add_filter( "manage_{$screen->id}_columns", array( $column, 'add_column' ) );
60
  add_action( "manage_{$post_type}_posts_custom_column", array( $column, 'display_column' ), 10, 2 );
61
  }
62
  }
63
  }
64
 
65
+ P2P_Column_Factory::init();
66
 
admin/column.php CHANGED
@@ -15,26 +15,50 @@ class P2P_Column {
15
  }
16
 
17
  function add_column( $columns ) {
18
- $columns['connected'] = $this->ctype->get_title();
19
 
20
  return $columns;
21
  }
22
 
23
- function display_column( $column, $post_id ) {
24
- if ( 'connected' != $column )
 
 
 
 
 
 
 
 
 
 
 
25
  return;
26
 
27
- foreach ( $this->connected[ $post_id ] as $post ) {
 
 
 
 
 
 
 
 
 
 
 
28
  $args = array(
29
- 'post_type' => get_post_type( $post_id ),
30
- 'connected_type' => $this->ctype->id,
31
- 'connected' => $post->ID,
 
32
  );
33
 
34
  $url = add_query_arg( $args, admin_url( 'edit.php' ) );
35
 
36
- echo html( 'li', html_link( $url, $post->post_title ) );
37
  }
 
38
  }
39
  }
40
 
15
  }
16
 
17
  function add_column( $columns ) {
18
+ $columns[ $this->ctype->name ] = $this->ctype->get_current( 'title' );
19
 
20
  return $columns;
21
  }
22
 
23
+ function styles() {
24
+ ?>
25
+ <style type="text/css">
26
+ .column-<?php echo $this->ctype->name; ?> ul {
27
+ margin-top: 0;
28
+ margin-bottom: 0;
29
+ }
30
+ </style>
31
+ <?php
32
+ }
33
+
34
+ function display_column( $column, $item_id ) {
35
+ if ( $this->ctype->name != $column )
36
  return;
37
 
38
+ $opposite_direction = array(
39
+ 'from' => 'to',
40
+ 'to' => 'from',
41
+ 'any' => 'any'
42
+ );
43
+
44
+ $direction = $opposite_direction[ $this->ctype->get_direction() ];
45
+
46
+ $side = $this->ctype->side[ $direction ];
47
+
48
+ echo '<ul>';
49
+ foreach ( $this->connected[ $item_id ] as $item ) {
50
  $args = array(
51
+ 'connected_type' => $this->ctype->name,
52
+ 'connected_direction' => $direction,
53
+ 'connected_items' => $item->ID,
54
+ 'post_type' => get_current_screen()->post_type
55
  );
56
 
57
  $url = add_query_arg( $args, admin_url( 'edit.php' ) );
58
 
59
+ echo html( 'li', html_link( $url, $side->item_title( $item ) ) );
60
  }
61
+ echo '</ul>';
62
  }
63
  }
64
 
admin/fields.php CHANGED
@@ -1,8 +1,6 @@
1
  <?php
2
 
3
- /**
4
- * @package Administration
5
- */
6
  class P2P_Field_Create implements P2P_Field {
7
 
8
  function get_title() {
@@ -10,7 +8,7 @@ class P2P_Field_Create implements P2P_Field {
10
  return '';
11
  }
12
 
13
- function render( $key, $p2p_id, $post_id ) {
14
  $data = array(
15
  'post_id' => $post_id,
16
  'title' => __( 'Create connection', P2P_TEXTDOMAIN )
@@ -20,9 +18,8 @@ class P2P_Field_Create implements P2P_Field {
20
  }
21
  }
22
 
23
- /**
24
- * @package Administration
25
- */
26
  class P2P_Field_Delete implements P2P_Field {
27
 
28
  function get_title() {
@@ -33,7 +30,7 @@ class P2P_Field_Delete implements P2P_Field {
33
  return P2P_Mustache::render( 'column-delete-all', $data );
34
  }
35
 
36
- function render( $key, $p2p_id, $post_id ) {
37
  $data = array(
38
  'p2p_id' => $p2p_id,
39
  'title' => __( 'Delete connection', P2P_TEXTDOMAIN )
@@ -43,9 +40,8 @@ class P2P_Field_Delete implements P2P_Field {
43
  }
44
  }
45
 
46
- /**
47
- * @package Administration
48
- */
49
  class P2P_Field_Order implements P2P_Field {
50
 
51
  protected $sort_key;
@@ -58,7 +54,7 @@ class P2P_Field_Order implements P2P_Field {
58
  return '';
59
  }
60
 
61
- function render( $key, $p2p_id, $post_id ) {
62
  return html( 'input', array(
63
  'type' => 'hidden',
64
  'name' => "p2p_order[$this->sort_key][]",
@@ -67,10 +63,37 @@ class P2P_Field_Order implements P2P_Field {
67
  }
68
  }
69
 
70
- /**
71
- * @package Administration
72
- */
73
- class P2P_Field_Title implements P2P_Field {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
  protected $title;
76
 
@@ -82,7 +105,7 @@ class P2P_Field_Title implements P2P_Field {
82
  return $this->title;
83
  }
84
 
85
- function render( $key, $p2p_id, $post_id ) {
86
  $data = array(
87
  'title-attr' => get_permalink( $post_id ),
88
  'title' => get_post_field( 'post_title', $post_id ),
@@ -102,38 +125,45 @@ class P2P_Field_Title implements P2P_Field {
102
  }
103
  }
104
 
105
- /**
106
- * @package Administration
107
- */
108
- class P2P_Field_Generic implements P2P_Field {
109
-
110
- protected $data;
111
 
112
- function __construct( $data ) {
113
- $this->data = $data;
114
- }
115
 
116
- function get_title() {
117
- return $this->data['title'];
118
- }
119
 
120
- function render( $key, $p2p_id, $post_id ) {
121
- $args = array(
122
- 'name' => $key,
123
- 'type' => $this->data['type']
124
  );
125
 
126
- if ( isset( $this->data['values'] ) )
127
- $args['value'] = $this->data['values'];
 
 
128
 
129
- $single_value = ( 'checkbox' != $args['type'] );
 
130
 
131
- $form = new scbForm(
132
- array( $key => p2p_get_meta( $p2p_id, $key, $single_value ) ),
133
- array( 'p2p_meta', $p2p_id )
 
 
134
  );
135
 
136
- return $form->input( $args );
 
 
 
 
 
 
 
 
 
 
137
  }
138
  }
139
 
1
  <?php
2
 
3
+ /** @package Administration */
 
 
4
  class P2P_Field_Create implements P2P_Field {
5
 
6
  function get_title() {
8
  return '';
9
  }
10
 
11
+ function render( $p2p_id, $post_id ) {
12
  $data = array(
13
  'post_id' => $post_id,
14
  'title' => __( 'Create connection', P2P_TEXTDOMAIN )
18
  }
19
  }
20
 
21
+
22
+ /** @package Administration */
 
23
  class P2P_Field_Delete implements P2P_Field {
24
 
25
  function get_title() {
30
  return P2P_Mustache::render( 'column-delete-all', $data );
31
  }
32
 
33
+ function render( $p2p_id, $post_id ) {
34
  $data = array(
35
  'p2p_id' => $p2p_id,
36
  'title' => __( 'Delete connection', P2P_TEXTDOMAIN )
40
  }
41
  }
42
 
43
+
44
+ /** @package Administration */
 
45
  class P2P_Field_Order implements P2P_Field {
46
 
47
  protected $sort_key;
54
  return '';
55
  }
56
 
57
+ function render( $p2p_id, $post_id ) {
58
  return html( 'input', array(
59
  'type' => 'hidden',
60
  'name' => "p2p_order[$this->sort_key][]",
63
  }
64
  }
65
 
66
+ /** @package Administration */
67
+ class P2P_Field_Generic implements P2P_Field {
68
+
69
+ protected $key;
70
+ protected $data;
71
+
72
+ function __construct( $key, $data ) {
73
+ $this->key = $key;
74
+ $this->data = $data;
75
+ }
76
+
77
+ function get_title() {
78
+ return $this->data['title'];
79
+ }
80
+
81
+ function render( $p2p_id, $post_id ) {
82
+ $args = array(
83
+ 'name' => array( 'p2p_meta', $p2p_id, $this->key ),
84
+ 'type' => $this->data['type']
85
+ );
86
+
87
+ if ( isset( $this->data['values'] ) )
88
+ $args['value'] = $this->data['values'];
89
+
90
+ return scbForms::input_from_meta( $args, $p2p_id, 'p2p' );
91
+ }
92
+ }
93
+
94
+
95
+ /** @package Administration */
96
+ class P2P_Field_Title_Post implements P2P_Field {
97
 
98
  protected $title;
99
 
105
  return $this->title;
106
  }
107
 
108
+ function render( $p2p_id, $post_id ) {
109
  $data = array(
110
  'title-attr' => get_permalink( $post_id ),
111
  'title' => get_post_field( 'post_title', $post_id ),
125
  }
126
  }
127
 
 
 
 
 
 
 
128
 
129
+ /** @package Administration */
130
+ class P2P_Field_Title_Attachment extends P2P_Field_Title_Post {
 
131
 
132
+ function render( $p2p_id, $attachment_id ) {
133
+ list( $src ) = wp_get_attachment_image_src( $attachment_id, 'thumbnail', true );
 
134
 
135
+ $data = array(
136
+ 'title-attr' => get_post_field( 'post_title', $attachment_id ),
137
+ 'title' => html( 'img', compact( 'src' ) ),
138
+ 'url' => get_edit_post_link( $attachment_id ),
139
  );
140
 
141
+ return P2P_Mustache::render( 'column-title', $data );
142
+ }
143
+ }
144
+
145
 
146
+ /** @package Administration */
147
+ class P2P_Field_Title_User extends P2P_Field_Title_Post {
148
 
149
+ function render( $p2p_id, $user_id ) {
150
+ $data = array(
151
+ 'title-attr' => '',
152
+ 'title' => get_user_by( 'id', $user_id )->display_name,
153
+ 'url' => $this->get_edit_url( $user_id ),
154
  );
155
 
156
+ return P2P_Mustache::render( 'column-title', $data );
157
+ }
158
+
159
+ private function get_edit_url( $user_id ) {
160
+ if ( get_current_user_id() == $user_id ) {
161
+ $edit_link = 'profile.php';
162
+ } else {
163
+ $edit_link = "user-edit.php?user_id=$user_id";
164
+ }
165
+
166
+ return admin_url( $edit_link );
167
  }
168
  }
169
 
admin/templates/box.html CHANGED
@@ -1,6 +1,6 @@
1
- <input type="hidden" name="p2p_ctypes[]" value="{{ctype-id}}" />
2
-
3
  <div class="p2p-box" {{attributes}}>
 
 
4
  {{#connections}}
5
  <table class="p2p-connections" {{{hide}}}>
6
  <thead>
@@ -21,12 +21,14 @@
21
 
22
  {{#create-connections}}
23
  <div class="p2p-create-connections" {{{hide}}}>
24
- <p><strong>{{label}}</strong></p>
 
25
  <ul class="wp-tab-bar clearfix">
26
  {{#tabs}}
27
- <li {{#is-active}}class="wp-tab-active"{{/is-active}} data-ref=".p2p-tab-{{tab-id}}"><a href="#">{{tab-title}}</a></li>
28
  {{/tabs}}
29
  </ul>
 
30
 
31
  {{#tabs}}
32
  <div class="p2p-tab-{{tab-id}} tabs-panel">
 
 
1
  <div class="p2p-box" {{attributes}}>
2
+ <input type="hidden" name="p2p_types[]" value="{{p2p-type}}" />
3
+
4
  {{#connections}}
5
  <table class="p2p-connections" {{{hide}}}>
6
  <thead>
21
 
22
  {{#create-connections}}
23
  <div class="p2p-create-connections" {{{hide}}}>
24
+ <p><strong>{{{label}}}</strong></p>
25
+ {{#show-tab-headers}}
26
  <ul class="wp-tab-bar clearfix">
27
  {{#tabs}}
28
+ <li {{#is-active}}class="wp-tab-active"{{/is-active}} data-ref=".p2p-tab-{{tab-id}}"><a href="#">{{{tab-title}}}</a></li>
29
  {{/tabs}}
30
  </ul>
31
+ {{/show-tab-headers}}
32
 
33
  {{#tabs}}
34
  <div class="p2p-tab-{{tab-id}} tabs-panel">
admin/templates/column-create.html CHANGED
@@ -1 +1 @@
1
- <a href="#" data-post_id="{{post_id}}" title="{{title}}"></a>
1
+ <a href="#" data-post_id="{{post_id}}" title="{{{title}}}"></a>
admin/templates/column-delete-all.html CHANGED
@@ -1 +1 @@
1
- <a href="#" title="{{title}}"></a>
1
+ <a href="#" title="{{{title}}}"></a>
admin/templates/column-delete.html CHANGED
@@ -1 +1 @@
1
- <a href="#" data-p2p_id="{{p2p_id}}" title="{{title}}"></a>
1
+ <a href="#" data-p2p_id="{{p2p_id}}" title="{{{title}}}"></a>
admin/templates/column-title.html CHANGED
@@ -1 +1 @@
1
- <a href="{{{url}}}" title="{{title-attr}}">{{title}}</a>{{#status}} - <span class="post-state">{{text}}</span>{{/status}}
1
+ <a href="{{{url}}}" title="{{title-attr}}">{{{title}}}</a>{{#status}} - <span class="post-state">{{text}}</span>{{/status}}
admin/templates/tab-create-post.html CHANGED
@@ -1,2 +1,2 @@
1
  <input type="text" name="p2p_new_title" autocomplete="off" />
2
- <input type="button" class="button" value="{{title}}" />
1
  <input type="text" name="p2p_new_title" autocomplete="off" />
2
+ <button class="button">{{{title}}}</button>
admin/templates/tab-search.html CHANGED
@@ -1,3 +1,5 @@
1
  <div class="p2p-search">
2
- <input type="text" name="p2p_search" autocomplete="off" placeholder="{{placeholder}}" />
 
 
3
  </div>
1
  <div class="p2p-search">
2
+ <input type="text" name="p2p_search" autocomplete="off" placeholder="{{{placeholder}}}" />
3
+
4
+ <button class="button">{{{view-all}}}</button>
5
  </div>
core/api.php CHANGED
@@ -7,7 +7,7 @@
7
  *
8
  * Takes the following parameters, as an associative array:
9
  *
10
- * - 'id' - string A unique identifier for this connection type (optional).
11
  *
12
  * - 'from' - string|array The first end of the connection.
13
  *
@@ -19,16 +19,20 @@
19
  *
20
  * - 'fields' - array( key => Title ) Metadata fields editable by the user. Default: none.
21
  *
22
- * - 'data' - array( key => value ) Metadata fields not editable by the user. Dfault: none.
23
- *
24
  * - 'cardinality' - string How many connection can each post have: 'one-to-many', 'many-to-one' or 'many-to-many'. Default: 'many-to-many'
25
  *
26
  * - 'prevent_duplicates' - bool Whether to disallow duplicate connections between the same two posts. Default: true.
27
  *
 
 
28
  * - 'sortable' - bool|string Whether to allow connections to be ordered via drag-and-drop. Can be 'from', 'to', 'any' or false. Default: false.
29
  *
30
  * - 'title' - string|array The box's title. Default: 'Connected {$post_type}s'
31
  *
 
 
 
 
32
  * - 'reciprocal' - bool For indeterminate connections: True means all connections are displayed in a single box. False means 'from' connections are shown in one box and 'to' connections are shown in another box. Default: false.
33
  *
34
  * - 'admin_box' - bool|string|array Whether and where to show the admin connections box.
@@ -46,109 +50,252 @@ function p2p_register_connection_type( $args ) {
46
 
47
  $argv = func_get_args();
48
 
49
- // Back-compat begin
50
  if ( count( $argv ) > 1 ) {
51
  $args = array();
52
  foreach ( array( 'from', 'to', 'reciprocal' ) as $i => $key ) {
53
  if ( isset( $argv[ $i ] ) )
54
  $args[ $key ] = $argv[ $i ];
55
  }
 
 
 
 
 
 
56
  }
57
 
58
  if ( isset( $args['show_ui'] ) ) {
59
  $args['admin_box'] = array(
60
  'show' => _p2p_pluck( $args, 'show_ui' )
61
  );
 
62
  if ( isset( $args['context'] ) )
63
  $args['admin_box']['context'] = _p2p_pluck( $args, 'context' );
64
  }
65
- // Back-compat end
66
 
67
- // Box args
68
- if ( isset( $args['admin_box'] ) ) {
69
- $metabox_args = _p2p_pluck( $args, 'admin_box' );
70
- if ( !is_array( $metabox_args ) )
71
- $metabox_args = array( 'show' => $metabox_args );
72
- } else {
73
- $metabox_args = array();
74
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- foreach ( array( 'fields', 'can_create_post' ) as $key ) {
77
- if ( isset( $args[ $key ] ) ) {
78
- $metabox_args[ $key ] = _p2p_pluck( $args, $key );
 
 
 
 
79
  }
80
- }
81
 
82
- // Column args
83
- if ( isset( $args['admin_column'] ) ) {
84
- $column_args = _p2p_pluck( $args, 'admin_column' );
85
- } else {
86
- $column_args = false;
 
 
 
 
 
87
  }
88
 
89
- $ctype = P2P_Connection_Type::register( $args );
 
 
 
 
 
 
 
 
 
 
90
 
91
- if ( is_admin() ) {
92
- P2P_Box_Factory::register( $ctype->id, $metabox_args );
93
- P2P_Column_Factory::register( $ctype->id, $column_args );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  }
95
 
96
- return $ctype;
 
 
 
 
 
97
  }
98
 
99
  /**
100
- * @internal
 
 
 
 
101
  */
102
- function _p2p_get_field_type( $args ) {
103
- if ( isset( $args['type'] ) )
104
- return $args['type'];
105
-
106
- if ( isset( $args['values'] ) && is_array( $args['values'] ) )
107
- return 'select';
108
 
109
- return 'text';
110
  }
111
 
112
  /**
113
- * Get a connection type.
114
  *
115
- * @param string $id Connection type id
 
116
  *
117
- * @return bool|object False if connection type not found, P2P_Connection_Type instance on success.
118
  */
119
- function p2p_type( $id ) {
120
- return P2P_Connection_Type::get_instance( $id );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  }
122
 
123
  /**
124
  * Delete one or more connections.
125
  *
126
- * @param int|array $p2p_id Connection ids
 
127
  *
128
  * @return int Number of connections deleted
129
  */
130
- function p2p_delete_connection( $p2p_id ) {
131
- return P2P_Storage::delete( $p2p_id );
 
 
132
  }
133
 
134
  /**
135
- * Split some posts based on a certain connection field.
136
  *
137
- * @param object|array A WP_Query instance, or a list of post objects
138
- * @param string $key p2pmeta key
 
139
  */
140
- function p2p_split_posts( $posts, $key ) {
141
- if ( is_object( $posts ) )
142
- $posts = $posts->posts;
143
 
144
- $buckets = array();
 
145
 
146
- foreach ( $posts as $post ) {
147
- $value = p2p_get_meta( $post->p2p_id, $key, true );
148
- $buckets[ $value ][] = $post;
149
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
- return $buckets;
 
152
  }
153
 
154
  /**
7
  *
8
  * Takes the following parameters, as an associative array:
9
  *
10
+ * - 'name' - string A unique identifier for this connection type.
11
  *
12
  * - 'from' - string|array The first end of the connection.
13
  *
19
  *
20
  * - 'fields' - array( key => Title ) Metadata fields editable by the user. Default: none.
21
  *
 
 
22
  * - 'cardinality' - string How many connection can each post have: 'one-to-many', 'many-to-one' or 'many-to-many'. Default: 'many-to-many'
23
  *
24
  * - 'prevent_duplicates' - bool Whether to disallow duplicate connections between the same two posts. Default: true.
25
  *
26
+ * - 'self_connections' - bool Whether to allow a post to connect to itself. Default: false.
27
+ *
28
  * - 'sortable' - bool|string Whether to allow connections to be ordered via drag-and-drop. Can be 'from', 'to', 'any' or false. Default: false.
29
  *
30
  * - 'title' - string|array The box's title. Default: 'Connected {$post_type}s'
31
  *
32
+ * - 'from_labels' - array Additional labels for the admin box (optional)
33
+ *
34
+ * - 'to_labels' - array Additional labels for the admin box (optional)
35
+ *
36
  * - 'reciprocal' - bool For indeterminate connections: True means all connections are displayed in a single box. False means 'from' connections are shown in one box and 'to' connections are shown in another box. Default: false.
37
  *
38
  * - 'admin_box' - bool|string|array Whether and where to show the admin connections box.
50
 
51
  $argv = func_get_args();
52
 
 
53
  if ( count( $argv ) > 1 ) {
54
  $args = array();
55
  foreach ( array( 'from', 'to', 'reciprocal' ) as $i => $key ) {
56
  if ( isset( $argv[ $i ] ) )
57
  $args[ $key ] = $argv[ $i ];
58
  }
59
+ } else {
60
+ $args = $argv[0];
61
+ }
62
+
63
+ if ( isset( $args['id'] ) ) {
64
+ $args['name'] = _p2p_pluck( $args, 'id' );
65
  }
66
 
67
  if ( isset( $args['show_ui'] ) ) {
68
  $args['admin_box'] = array(
69
  'show' => _p2p_pluck( $args, 'show_ui' )
70
  );
71
+
72
  if ( isset( $args['context'] ) )
73
  $args['admin_box']['context'] = _p2p_pluck( $args, 'context' );
74
  }
 
75
 
76
+ return P2P_Connection_Type_Factory::register( $args );
77
+ }
78
+
79
+ /**
80
+ * Get a connection type.
81
+ *
82
+ * @param string $p2p_type
83
+ *
84
+ * @return bool|object False if connection type not found, P2P_Connection_Type instance on success.
85
+ */
86
+ function p2p_type( $p2p_type ) {
87
+ return P2P_Connection_Type_Factory::get_instance( $p2p_type );
88
+ }
89
+
90
+ /**
91
+ * Check if a certain connection exists.
92
+ *
93
+ * @param string $p2p_type A valid connection type.
94
+ * @param array $args Query args.
95
+ *
96
+ * @return bool
97
+ */
98
+ function p2p_connection_exists( $p2p_type, $args = array() ) {
99
+ $args['fields'] = 'count';
100
+
101
+ $r = p2p_get_connections( $p2p_type, $args );
102
+
103
+ return (bool) $r;
104
+ }
105
+
106
+ /**
107
+ * Retrieve connections.
108
+ *
109
+ * @param string $p2p_type A valid connection type.
110
+ * @param array $args Query args:
111
+ *
112
+ * - 'direction': Can be 'from', 'to' or 'any'
113
+ * - 'from': Object id. The first end of the connection. (optional)
114
+ * - 'to': Object id. The second end of the connection. (optional)
115
+ * - 'fields': Which field of the connection to return. Can be:
116
+ * 'all', 'object_id', 'p2p_from', 'p2p_to', 'p2p_id' or 'count'
117
+ *
118
+ * @return array
119
+ */
120
+ function p2p_get_connections( $p2p_type, $args = array() ) {
121
+ extract( wp_parse_args( $args, array(
122
+ 'direction' => 'from',
123
+ 'from' => 'any',
124
+ 'to' => 'any',
125
+ 'fields' => 'all',
126
+ ) ), EXTR_SKIP );
127
 
128
+ $r = array();
129
+
130
+ foreach ( _p2p_expand_direction( $direction ) as $direction ) {
131
+ $args = array( $from, $to );
132
+
133
+ if ( 'to' == $direction ) {
134
+ $args = array_reverse( $args );
135
  }
 
136
 
137
+ if ( 'object_id' == $fields )
138
+ $field = ( 'to' == $direction ) ? 'p2p_from' : 'p2p_to';
139
+ else
140
+ $field = $fields;
141
+
142
+ $r = array_merge( $r, _p2p_get_connections( $p2p_type, array(
143
+ 'from' => $args[0],
144
+ 'to' => $args[1],
145
+ 'fields' => $field
146
+ ) ) );
147
  }
148
 
149
+ if ( 'count' == $fields )
150
+ return array_sum( $r );
151
+
152
+ return $r;
153
+ }
154
+
155
+ /** @internal */
156
+ function _p2p_get_connections( $p2p_type, $args = array() ) {
157
+ global $wpdb;
158
+
159
+ extract( $args, EXTR_SKIP );
160
 
161
+ $where = $wpdb->prepare( 'WHERE p2p_type = %s', $p2p_type );
162
+
163
+ foreach ( array( 'from', 'to' ) as $key ) {
164
+ if ( 'any' == $$key )
165
+ continue;
166
+
167
+ $where .= $wpdb->prepare( " AND p2p_$key = %d", $$key );
168
+ }
169
+
170
+ switch ( $fields ) {
171
+ case 'p2p_id':
172
+ case 'p2p_from':
173
+ case 'p2p_to':
174
+ $sql_field = $fields;
175
+ break;
176
+ case 'count':
177
+ $sql_field = 'COUNT(*)';
178
+ break;
179
+ default:
180
+ $sql_field = '*';
181
  }
182
 
183
+ $query = "SELECT $sql_field FROM $wpdb->p2p $where";
184
+
185
+ if ( '*' == $sql_field )
186
+ return $wpdb->get_results( $query );
187
+ else
188
+ return $wpdb->get_col( $query );
189
  }
190
 
191
  /**
192
+ * Retrieve a single connection.
193
+ *
194
+ * @param int $p2p_id The connection id.
195
+ *
196
+ * @return object
197
  */
198
+ function p2p_get_connection( $p2p_id ) {
199
+ global $wpdb;
 
 
 
 
200
 
201
+ return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->p2p WHERE p2p_id = %d", $p2p_id ) );
202
  }
203
 
204
  /**
205
+ * Create a connection.
206
  *
207
+ * @param int $p2p_type A valid connection type.
208
+ * @param array $args Connection information.
209
  *
210
+ * @return bool|int False on failure, p2p_id on success.
211
  */
212
+ function p2p_create_connection( $p2p_type, $args ) {
213
+ global $wpdb;
214
+
215
+ extract( wp_parse_args( $args, array(
216
+ 'direction' => 'from',
217
+ 'from' => false,
218
+ 'to' => false,
219
+ 'meta' => array()
220
+ ) ), EXTR_SKIP );
221
+
222
+ $from = absint( $from );
223
+ $to = absint( $to );
224
+
225
+ if ( !$from || !$to )
226
+ return false;
227
+
228
+ $args = array( $from, $to );
229
+
230
+ if ( 'to' == $direction ) {
231
+ $args = array_reverse( $args );
232
+ }
233
+
234
+ $wpdb->insert( $wpdb->p2p, array(
235
+ 'p2p_type' => $p2p_type,
236
+ 'p2p_from' => $args[0],
237
+ 'p2p_to' => $args[1]
238
+ ) );
239
+
240
+ $p2p_id = $wpdb->insert_id;
241
+
242
+ foreach ( $meta as $key => $value )
243
+ p2p_add_meta( $p2p_id, $key, $value );
244
+
245
+ return $p2p_id;
246
  }
247
 
248
  /**
249
  * Delete one or more connections.
250
  *
251
+ * @param int $p2p_type A valid connection type.
252
+ * @param array $args Connection information.
253
  *
254
  * @return int Number of connections deleted
255
  */
256
+ function p2p_delete_connections( $p2p_type, $args = array() ) {
257
+ $args['fields'] = 'p2p_id';
258
+
259
+ return p2p_delete_connection( p2p_get_connections( $p2p_type, $args ) );
260
  }
261
 
262
  /**
263
+ * Delete connections using p2p_ids.
264
  *
265
+ * @param int|array $p2p_id Connection ids
266
+ *
267
+ * @return int Number of connections deleted
268
  */
269
+ function p2p_delete_connection( $p2p_id ) {
270
+ global $wpdb;
 
271
 
272
+ if ( empty( $p2p_id ) )
273
+ return 0;
274
 
275
+ $p2p_ids = array_map( 'absint', (array) $p2p_id );
276
+
277
+ $where = "WHERE p2p_id IN (" . implode( ',', $p2p_ids ) . ")";
278
+
279
+ $count = $wpdb->query( "DELETE FROM $wpdb->p2p $where" );
280
+ $wpdb->query( "DELETE FROM $wpdb->p2pmeta $where" );
281
+
282
+ return $count;
283
+ }
284
+
285
+ function p2p_get_meta( $p2p_id, $key = '', $single = false ) {
286
+ return get_metadata( 'p2p', $p2p_id, $key, $single );
287
+ }
288
+
289
+ function p2p_update_meta( $p2p_id, $key, $value, $prev_value = '' ) {
290
+ return update_metadata( 'p2p', $p2p_id, $key, $value, $prev_value );
291
+ }
292
+
293
+ function p2p_add_meta( $p2p_id, $key, $value, $unique = false ) {
294
+ return add_metadata( 'p2p', $p2p_id, $key, $value, $unique );
295
+ }
296
 
297
+ function p2p_delete_meta( $p2p_id, $key, $value = '' ) {
298
+ return delete_metadata( 'p2p', $p2p_id, $key, $value );
299
  }
300
 
301
  /**
core/directed-type.php CHANGED
@@ -5,24 +5,9 @@ class P2P_Directed_Connection_Type {
5
  protected $ctype;
6
  protected $direction;
7
 
8
- protected $cardinality;
9
- protected $other_cardinality;
10
-
11
  function __construct( $ctype, $direction ) {
12
  $this->ctype = $ctype;
13
  $this->direction = $direction;
14
-
15
- $this->set_cardinality();
16
- }
17
-
18
- protected function set_cardinality() {
19
- $parts = explode( '-', $this->ctype->cardinality );
20
-
21
- if ( 'to' == $this->direction )
22
- $parts = array_reverse( $parts );
23
-
24
- $this->cardinality = ( 'one' == $parts[2] ) ? 'one' : 'many';
25
- $this->other_cardinality = ( 'one' == $parts[0] ) ? 'one' : 'many';
26
  }
27
 
28
  function __get( $key ) {
@@ -45,31 +30,26 @@ class P2P_Directed_Connection_Type {
45
  return $this->ctype;
46
  }
47
 
48
- public function accepts_single_connection() {
49
- return 'one' == $this->cardinality;
 
 
50
  }
51
 
52
- public function get_title() {
53
- $key = ( 'to' == $this->direction ) ? 'to' : 'from';
54
 
55
- return $this->title[ $key ];
56
  }
57
 
58
- public function get_current_post_type() {
59
- return 'to' == $this->direction ? $this->to : $this->from;
60
- }
61
 
62
- public function get_other_post_type() {
63
- return 'to' == $this->direction ? $this->from : $this->to;
64
  }
65
 
66
- private function get_base_qv() {
67
- $base_qv = ( 'from' == $this->direction ) ? $this->to_query_vars : $this->from_query_vars;
68
-
69
- return array_merge( $base_qv, array(
70
- 'suppress_filters' => false,
71
- 'ignore_sticky_posts' => true,
72
- ) );
73
  }
74
 
75
  /**
@@ -78,103 +58,137 @@ class P2P_Directed_Connection_Type {
78
  * @param int|array $post_id A post id or an array of post ids.
79
  * @param array $extra_qv Additional query variables to use.
80
  *
81
- * @return A WP_Query instance on success.
82
  */
83
- public function get_connected( $post_id, $extra_qv = array() ) {
84
- return new WP_Query( $this->get_connected_args( $post_id, $extra_qv ) );
 
 
 
 
 
 
 
 
 
 
 
85
  }
86
 
87
- public function get_connected_args( $post_id, $extra_qv = array() ) {
88
- $args = array_merge( $extra_qv, $this->get_base_qv() );
 
 
 
 
 
 
89
 
90
- // don't completely overwrite 'connected_meta', but ensure that $this->data is added
91
- $args = array_merge_recursive( $args, array(
 
 
 
92
  'connected_meta' => $this->data
93
  ) );
94
 
95
- $args['connected_query'] = array(
96
- 'posts' => $post_id,
97
- 'direction' => $this->direction
98
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
- return apply_filters( 'p2p_connected_args', $args, $this, $post_id );
101
  }
102
 
103
  /**
104
  * Get a list of posts that could be connected to a given post.
105
  *
106
  * @param int $post_id A post id.
107
- * @param array $extra_qv Additional query variables to use.
108
- *
109
- * @return bool|object False on failure; A WP_Query instance on success.
110
  */
111
- public function get_connectable( $post_id, $extra_qv = array() ) {
112
- $args = array_merge( $this->get_base_qv(), $extra_qv );
113
 
114
- if ( 'one' == $this->other_cardinality ) {
115
- $to_check = 'any';
116
- } elseif ( $this->prevent_duplicates ) {
117
- $to_check = $post_id;
118
- }
119
 
120
- if ( isset( $to_check ) ) {
121
- $connected = $this->get_connected( $to_check, array( 'fields' => 'ids' ) )->posts;
 
 
 
 
122
 
123
- if ( !empty( $connected ) ) {
124
- $args = array_merge_recursive( $args, array(
125
- 'post__not_in' => $connected
126
- ) );
127
- }
 
128
  }
129
 
130
- $args = apply_filters( 'p2p_connectable_args', $args, $this, $post_id );
131
 
132
- return new WP_Query( $args );
133
- }
134
 
135
- public function get_p2p_id( $from, $to ) {
136
- $connected = $this->get_connected( $from, array( 'post__in' => array( $to ) ) );
137
-
138
- if ( !empty( $connected->posts ) )
139
- return (int) $connected->posts[0]->p2p_id;
140
 
141
- return false;
142
  }
143
 
144
  /**
145
- * Connect two posts.
146
  *
147
  * @param int The first end of the connection.
148
  * @param int The second end of the connection.
 
149
  *
150
  * @return int p2p_id
151
  */
152
- public function connect( $from, $to ) {
153
- if ( !get_post( $from ) || !get_post( $to ) )
154
  return false;
155
 
156
- $p2p_id = false;
157
-
158
- if ( 'one' == $this->cardinality ) {
159
- $connected = $this->get_connected( $from, array( 'fields' => 'ids' ) );
160
- if ( !empty( $connected->posts ) )
161
- return false;
162
- }
163
-
164
- if ( $this->prevent_duplicates ) {
165
- $p2p_id = $this->get_p2p_id( $from, $to );
166
- }
167
-
168
- if ( !$p2p_id ) {
169
- $args = array( $from, $to );
170
-
171
- if ( 'to' == $this->direction )
172
- $args = array_reverse( $args );
173
 
174
- $p2p_id = P2P_Storage::connect( $args[0], $args[1], $this->data );
175
- }
 
 
 
 
 
 
 
 
 
 
 
176
 
177
- return $p2p_id;
 
 
 
 
 
178
  }
179
 
180
  /**
@@ -184,7 +198,11 @@ class P2P_Directed_Connection_Type {
184
  * @param int The second end of the connection.
185
  */
186
  public function disconnect( $from, $to ) {
187
- return P2P_Storage::delete( $this->get_p2p_id( $from, $to ) );
 
 
 
 
188
  }
189
 
190
  /**
@@ -193,10 +211,47 @@ class P2P_Directed_Connection_Type {
193
  * @param int The post id.
194
  */
195
  public function disconnect_all( $from ) {
196
- $connected = $this->get_connected( $from );
 
 
 
 
197
 
198
- foreach ( $connected->posts as $post )
199
- P2P_Storage::delete( $post->p2p_id );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  }
201
  }
202
 
5
  protected $ctype;
6
  protected $direction;
7
 
 
 
 
8
  function __construct( $ctype, $direction ) {
9
  $this->ctype = $ctype;
10
  $this->direction = $direction;
 
 
 
 
 
 
 
 
 
 
 
 
11
  }
12
 
13
  function __get( $key ) {
30
  return $this->ctype;
31
  }
32
 
33
+ public function get_opposite( $key ) {
34
+ $direction = ( 'to' == $this->direction ) ? 'from' : 'to';
35
+
36
+ return $this->get_arg( $key, $direction );
37
  }
38
 
39
+ public function get_current( $key ) {
40
+ $direction = ( 'to' == $this->direction ) ? 'to' : 'from';
41
 
42
+ return $this->get_arg( $key, $direction );
43
  }
44
 
45
+ private function get_arg( $key, $direction ) {
46
+ $arg = $this->ctype->$key;
 
47
 
48
+ return $arg[$direction];
 
49
  }
50
 
51
+ public function accepts_single_connection() {
52
+ return 'one' == $this->get_opposite( 'cardinality' );
 
 
 
 
 
53
  }
54
 
55
  /**
58
  * @param int|array $post_id A post id or an array of post ids.
59
  * @param array $extra_qv Additional query variables to use.
60
  *
61
+ * @return object
62
  */
63
+ public function get_connected( $post_id, $extra_qv = array(), $output = 'raw' ) {
64
+ $args = array_merge( $extra_qv, array(
65
+ 'connected_items' => $post_id
66
+ ) );
67
+
68
+ $side = $this->get_opposite( 'side' );
69
+
70
+ $query = $side->do_query( $this->get_connected_args( $args ) );
71
+
72
+ if ( 'abstract' == $output )
73
+ $query = $side->abstract_query( $query );
74
+
75
+ return $query;
76
  }
77
 
78
+ public function get_connected_args( $q ) {
79
+ if ( $orderby_key = $this->get_orderby_key() ) {
80
+ $q = wp_parse_args( $q, array(
81
+ 'connected_orderby' => $orderby_key,
82
+ 'connected_order' => 'ASC',
83
+ 'connected_order_num' => true,
84
+ ) );
85
+ }
86
 
87
+ $q = array_merge( $this->get_opposite( 'side' )->get_base_qv(), $q, array(
88
+ 'p2p_type' => array( $this->name => $this->get_direction() ),
89
+ ) );
90
+
91
+ $q = array_merge_recursive( $q, array(
92
  'connected_meta' => $this->data
93
  ) );
94
 
95
+ return apply_filters( 'p2p_connected_args', $q, $this, $q['connected_items'] );
96
+ }
97
+
98
+ public function get_orderby_key() {
99
+ if ( !$this->sortable || 'any' == $this->direction )
100
+ return false;
101
+
102
+ if ( 'any' == $this->sortable || $this->direction == $this->sortable )
103
+ return '_order_' . $this->direction;
104
+
105
+ // Back-compat
106
+ if ( 'from' == $this->direction )
107
+ return $this->sortable;
108
+
109
+ return false;
110
+ }
111
+
112
+ /** @internal */
113
+ public function get_connections( $post_id ) {
114
+ $side = $this->get_opposite( 'side' );
115
+
116
+ $query = $this->get_connected( $post_id, $side->get_connections_qv() );
117
 
118
+ return scb_list_fold( $side->abstract_query( $query )->items, 'p2p_id', 'ID' );
119
  }
120
 
121
  /**
122
  * Get a list of posts that could be connected to a given post.
123
  *
124
  * @param int $post_id A post id.
 
 
 
125
  */
126
+ public function get_connectable( $item_id, $page, $search ) {
127
+ $to_exclude = array();
128
 
129
+ if ( $this->indeterminate && !$this->self_connections )
130
+ $to_exclude[] = $item_id;
 
 
 
131
 
132
+ if ( 'one' == $this->get_current( 'cardinality' ) ) {
133
+ _p2p_append( $to_exclude, p2p_get_connections( $this->name, array(
134
+ 'direction' => $this->direction,
135
+ 'fields' => 'object_id'
136
+ ) ) );
137
+ }
138
 
139
+ if ( $this->prevent_duplicates ) {
140
+ _p2p_append( $to_exclude, p2p_get_connections( $this->name, array(
141
+ 'direction' => $this->direction,
142
+ 'from' => $item_id,
143
+ 'fields' => 'object_id'
144
+ ) ) );
145
  }
146
 
147
+ $side = $this->get_opposite( 'side' );
148
 
149
+ $qv = $side->get_connectable_qv( $item_id, $page, $search, $to_exclude );
 
150
 
151
+ $qv = apply_filters( 'p2p_connectable_args', $qv, $this, $item_id );
 
 
 
 
152
 
153
+ return $side->abstract_query( $side->do_query( $qv ) );
154
  }
155
 
156
  /**
157
+ * Connect two items.
158
  *
159
  * @param int The first end of the connection.
160
  * @param int The second end of the connection.
161
+ * @param array Additional information about the connection.
162
  *
163
  * @return int p2p_id
164
  */
165
+ public function connect( $from, $to, $meta = array() ) {
166
+ if ( !$this->get_current( 'side' )->item_exists( $from ) )
167
  return false;
168
 
169
+ if ( !$this->get_opposite( 'side' )->item_exists( $to ) )
170
+ return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
+ if ( $this->accepts_single_connection() )
173
+ $to_check = 'any';
174
+ elseif ( $this->prevent_duplicates )
175
+ $to_check = $to;
176
+ else
177
+ $to_check = false;
178
+
179
+ if ( $to_check && p2p_connection_exists( $this->name, array(
180
+ 'direction' => $this->direction,
181
+ 'from' => $from,
182
+ 'to' => $to_check
183
+ ) ) )
184
+ return false;
185
 
186
+ return p2p_create_connection( $this->name, array(
187
+ 'direction' => $this->direction,
188
+ 'from' => $from,
189
+ 'to' => $to,
190
+ 'meta' => array_merge( $meta, $this->data )
191
+ ) );
192
  }
193
 
194
  /**
198
  * @param int The second end of the connection.
199
  */
200
  public function disconnect( $from, $to ) {
201
+ return p2p_delete_connections( $this->name, array(
202
+ 'direction' => $this->direction,
203
+ 'from' => $from,
204
+ 'to' => $to,
205
+ ) );
206
  }
207
 
208
  /**
211
  * @param int The post id.
212
  */
213
  public function disconnect_all( $from ) {
214
+ return p2p_delete_connections( $this->name, array(
215
+ 'direction' => $this->direction,
216
+ 'from' => $from,
217
+ ) );
218
+ }
219
 
220
+ /**
221
+ * For one-to-many connections: replaces existing item with a new one.
222
+ *
223
+ * @param int The first end of the connection.
224
+ * @param int The second end of the connection.
225
+ * @param array Additional information about the connection.
226
+ *
227
+ * @return int p2p_id
228
+ */
229
+ public function replace( $from, $to, $meta = array() ) {
230
+ if ( !$this->get_current( 'side' )->item_exists( $from ) )
231
+ return false;
232
+
233
+ if ( ! $this->accepts_single_connection() )
234
+ return false;
235
+
236
+ $existing = _p2p_first( $this->get_connections( $from ) );
237
+
238
+ if ( $existing ) {
239
+ if ( $existing == $to )
240
+ return;
241
+
242
+ $this->disconnect_all( $from );
243
+ }
244
+
245
+ return $this->connect( $from, $to, $meta );
246
+ }
247
+
248
+ public function get_p2p_id( $from, $to ) {
249
+ return _p2p_first( p2p_get_connections( $this->name, array(
250
+ 'direction' => $this->direction,
251
+ 'from' => $from,
252
+ 'to' => $to,
253
+ 'fields' => 'p2p_id'
254
+ ) ) );
255
  }
256
  }
257
 
core/ordered-type.php DELETED
@@ -1,27 +0,0 @@
1
- <?php
2
-
3
- class P2P_Ordered_Connection_Type extends P2P_Directed_Connection_Type {
4
-
5
- protected $orderby_key;
6
-
7
- function __construct( $ctype, $direction, $orderby_key ) {
8
- $this->orderby_key = $orderby_key;
9
-
10
- parent::__construct( $ctype, $direction );
11
- }
12
-
13
- public function get_orderby_key() {
14
- return $this->orderby_key;
15
- }
16
-
17
- public function get_connected( $post_id, $extra_qv = array() ) {
18
- $extra_qv = wp_parse_args( $extra_qv, array(
19
- 'connected_orderby' => $this->get_orderby_key(),
20
- 'connected_order' => 'ASC',
21
- 'connected_order_num' => true,
22
- ) );
23
-
24
- return parent::get_connected( $post_id, $extra_qv );
25
- }
26
- }
27
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/query-post.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Handles connected{_to|_from} query vars
5
+ */
6
+ class P2P_WP_Query {
7
+
8
+ function init() {
9
+ add_action( 'parse_query', array( __CLASS__, 'parse_query' ) );
10
+ add_filter( 'posts_clauses', array( __CLASS__, 'posts_clauses' ), 10, 2 );
11
+ add_filter( 'the_posts', array( __CLASS__, 'cache_p2p_meta' ), 11, 2 );
12
+ }
13
+
14
+ function parse_query( $wp_query ) {
15
+ $q =& $wp_query->query_vars;
16
+
17
+ P2P_Query::expand_shortcut_qv( $q );
18
+
19
+ if ( !isset( $q['connected_items'] ) )
20
+ return;
21
+
22
+ if ( !isset( $q['connected_type'] ) )
23
+ return;
24
+
25
+ if ( 'any' == $q['connected_items'] ) {
26
+ $item = isset( $q['post_type'] ) ? $q['post_type'] : 'post';
27
+ } else {
28
+ $item = $q['connected_items'];
29
+ }
30
+
31
+ $r = P2P_Query::expand_connected_type( $q, $item, 'post' );
32
+
33
+ if ( false === $r ) {
34
+ $q = array( 'year' => 2525 );
35
+ } elseif ( $r ) {
36
+ $wp_query->is_home = false;
37
+ $wp_query->is_archive = true;
38
+ }
39
+ }
40
+
41
+ function posts_clauses( $clauses, $wp_query ) {
42
+ global $wpdb;
43
+
44
+ $qv = P2P_Query::get_qv( $wp_query->query_vars );
45
+
46
+ if ( empty( $qv['items'] ) )
47
+ return $clauses;
48
+
49
+ $wp_query->_p2p_cache = true;
50
+
51
+ return P2P_Query::alter_clauses( $clauses, $qv, "$wpdb->posts.ID" );
52
+ }
53
+
54
+ /**
55
+ * Pre-populates the p2p meta cache to decrease the number of queries.
56
+ */
57
+ function cache_p2p_meta( $the_posts, $wp_query ) {
58
+ if ( empty( $the_posts ) )
59
+ return $the_posts;
60
+
61
+ if ( isset( $wp_query->_p2p_cache ) ) {
62
+ update_meta_cache( 'p2p', wp_list_pluck( $the_posts, 'p2p_id' ) );
63
+ }
64
+
65
+ return $the_posts;
66
+ }
67
+ }
68
+
69
+ P2P_WP_Query::init();
70
+
core/query-user.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class P2P_User_Query {
4
+
5
+ function init() {
6
+ add_action( 'pre_user_query', array( __CLASS__, 'pre_user_query' ) );
7
+ }
8
+
9
+ function pre_user_query( $query ) {
10
+ global $wpdb;
11
+
12
+ $q =& $query->query_vars;
13
+
14
+ P2P_Query::expand_shortcut_qv( $q );
15
+
16
+ if ( !isset( $q['connected_items'] ) )
17
+ return;
18
+
19
+ $r = P2P_Query::expand_connected_type( $q, $q['connected_items'], 'user' );
20
+
21
+ if ( false === $r ) {
22
+ $query->query_where = " AND 1=0";
23
+ return;
24
+ }
25
+
26
+ // alter query
27
+
28
+ $qv = P2P_Query::get_qv( $q );
29
+
30
+ if ( empty( $qv['items'] ) )
31
+ return;
32
+
33
+ $map = array(
34
+ 'fields' => 'query_fields',
35
+ 'join' => 'query_from',
36
+ 'where' => 'query_where',
37
+ 'orderby' => 'query_orderby',
38
+ );
39
+
40
+ $clauses = array();
41
+
42
+ foreach ( $map as $clause => $key )
43
+ $clauses[$clause] = $query->$key;
44
+
45
+ $clauses = P2P_Query::alter_clauses( $clauses, $qv, "$wpdb->users.ID" );
46
+
47
+ if ( 0 !== strpos( $clauses['orderby'], 'ORDER BY ' ) )
48
+ $clauses['orderby'] = 'ORDER BY ' . $clauses['orderby'];
49
+
50
+ foreach ( $map as $clause => $key )
51
+ $query->$key = $clauses[$clause];
52
+ }
53
+ }
54
+
55
+ P2P_User_Query::init();
56
+
core/query.php CHANGED
@@ -1,17 +1,8 @@
1
  <?php
2
 
3
- /**
4
- * Handles connected{_to|_from} query vars
5
- */
6
  class P2P_Query {
7
 
8
- function init() {
9
- add_action( 'parse_query', array( __CLASS__, 'parse_legacy_qv' ) );
10
- add_filter( 'posts_clauses', array( __CLASS__, 'posts_clauses' ), 10, 2 );
11
- add_filter( 'the_posts', array( __CLASS__, 'cache_p2p_meta' ), 11, 2 );
12
- }
13
-
14
- function parse_legacy_qv( $wp_query ) {
15
  $qv_map = array(
16
  'connected' => 'any',
17
  'connected_to' => 'to',
@@ -19,116 +10,182 @@ class P2P_Query {
19
  );
20
 
21
  foreach ( $qv_map as $key => $direction ) {
22
- $search = $wp_query->get( $key );
23
- if ( !empty( $search ) ) {
24
- $wp_query->set( 'connected_query', array(
25
- 'posts' => $search,
26
- 'direction' => $direction,
27
- ) );
28
-
29
- $wp_query->set( $key, false );
30
  }
31
  }
32
  }
33
 
34
- function posts_clauses( $clauses, $wp_query ) {
35
- global $wpdb;
 
 
 
36
 
37
- $connected_query = $wp_query->get( 'connected_query' );
38
- if ( !is_array( $connected_query ) ) {
39
- return $clauses;
40
  }
41
 
42
- $defaults = array(
43
- 'posts' => 'any',
44
- 'direction' => 'any',
45
- 'operator' => 'in'
46
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
- $connected_query = array_merge( $defaults, $connected_query );
49
 
50
- $wp_query->_p2p_cache = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  $clauses['fields'] .= ", $wpdb->p2p.*";
53
 
54
  $clauses['join'] .= " INNER JOIN $wpdb->p2p";
55
 
56
- if ( 'any' == $connected_query['posts'] ) {
 
57
  $search = false;
58
  } else {
59
- $search = implode( ',', array_map( 'absint', (array) $connected_query['posts'] ) );
60
  }
61
 
62
- $direction = $connected_query['direction'];
63
- if ( !in_array( $direction, array( 'from', 'to', 'any' ) ) )
64
- $direction = 'any';
 
 
 
 
65
 
66
- if ( 'any' == $direction ) {
67
- if ( $search ) {
68
- $clauses['where'] .= " AND (
69
- ($wpdb->posts.ID = $wpdb->p2p.p2p_to AND $wpdb->p2p.p2p_from IN ($search)) OR
70
- ($wpdb->posts.ID = $wpdb->p2p.p2p_from AND $wpdb->p2p.p2p_to IN ($search))
71
- )";
72
- } else {
73
- $clauses['where'] .= " AND ($wpdb->posts.ID = $wpdb->p2p.p2p_to OR $wpdb->posts.ID = $wpdb->p2p.p2p_from)";
74
- }
75
- } else {
76
  $fields = array( 'p2p_from', 'p2p_to' );
77
- if ( 'from' == $direction )
78
- $fields = array_reverse( $fields );
79
 
80
- list( $from, $to ) = $fields;
81
 
82
- $clauses['where'] .= " AND $wpdb->posts.ID = $wpdb->p2p.$from";
83
- if ( $search ) {
84
- $clauses['where'] .= " AND $wpdb->p2p.$to IN ($search)";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  }
 
 
86
  }
87
 
88
- $connected_meta = $wp_query->get( 'connected_meta' );
89
- if ( !empty( $connected_meta ) ) {
90
- $meta_clauses = _p2p_meta_sql_helper( $connected_meta );
 
 
 
 
 
91
  foreach ( $meta_clauses as $key => $value ) {
92
  $clauses[ $key ] .= $value;
93
  }
94
  }
95
 
96
  // Handle ordering
97
- $connected_orderby = $wp_query->get( 'connected_orderby' );
98
- if ( $connected_orderby ) {
99
  $clauses['join'] .= $wpdb->prepare( "
100
  LEFT JOIN $wpdb->p2pmeta AS p2pm_order ON (
101
  $wpdb->p2p.p2p_id = p2pm_order.p2p_id AND p2pm_order.meta_key = %s
102
  )
103
- ", $connected_orderby );
104
 
105
- $connected_order = ( 'DESC' == strtoupper( $wp_query->get('connected_order') ) ) ? 'DESC' : 'ASC';
106
 
107
  $field = 'meta_value';
108
 
109
- if ( $wp_query->get('connected_order_num') )
110
  $field .= '+0';
111
 
112
- $clauses['orderby'] = "p2pm_order.$field $connected_order";
113
  }
114
 
115
  return $clauses;
116
  }
117
 
118
- /**
119
- * Pre-populates the p2p meta cache to decrease the number of queries.
120
- */
121
- function cache_p2p_meta( $the_posts, $wp_query ) {
122
- if ( empty( $the_posts ) )
123
- return $the_posts;
124
 
125
- if ( isset( $wp_query->_p2p_cache ) ) {
126
- update_meta_cache( 'p2p', wp_list_pluck( $the_posts, 'p2p_id' ) );
127
- }
128
 
129
- return $the_posts;
130
  }
131
- }
132
 
133
- P2P_Query::init();
 
 
 
 
 
 
 
 
 
 
 
 
134
 
1
  <?php
2
 
 
 
 
3
  class P2P_Query {
4
 
5
+ function expand_shortcut_qv( &$q ) {
 
 
 
 
 
 
6
  $qv_map = array(
7
  'connected' => 'any',
8
  'connected_to' => 'to',
10
  );
11
 
12
  foreach ( $qv_map as $key => $direction ) {
13
+ if ( !empty( $q[ $key ] ) ) {
14
+ $q['connected_items'] = _p2p_pluck( $q, $key );
15
+ $q['connected_direction'] = $direction;
 
 
 
 
 
16
  }
17
  }
18
  }
19
 
20
+ function get_qv( $q ) {
21
+ $qv_list = array(
22
+ 'items', 'direction', 'meta',
23
+ 'orderby', 'order_num', 'order'
24
+ );
25
 
26
+ foreach ( $qv_list as $key ) {
27
+ $qv[$key] = isset( $q["connected_$key"] ) ? $q["connected_$key"] : false;
 
28
  }
29
 
30
+ $qv['p2p_type'] = isset( $q['p2p_type'] ) ? $q['p2p_type'] : false;
31
+
32
+ return $qv;
33
+ }
34
+
35
+ // null means do nothing
36
+ // false means trigger 404
37
+ // true means found valid p2p query vars
38
+ function expand_connected_type( &$q, $item, $object_type ) {
39
+ if ( !isset( $q['connected_type'] ) )
40
+ return;
41
+
42
+ $ctypes = (array) _p2p_pluck( $q, 'connected_type' );
43
+
44
+ if ( isset( $q['connected_direction'] ) )
45
+ $directions = (array) _p2p_pluck( $q, 'connected_direction' );
46
+ else
47
+ $directions = array();
48
 
49
+ $p2p_types = array();
50
 
51
+ foreach ( $ctypes as $i => $p2p_type ) {
52
+ $ctype = p2p_type( $p2p_type );
53
+
54
+ if ( !$ctype )
55
+ continue;
56
+
57
+ if ( isset( $directions[$i] ) ) {
58
+ $directed = $ctype->set_direction( $directions[$i] );
59
+ } else {
60
+ $directed = self::find_direction( $ctype, $item, $object_type );
61
+ }
62
+
63
+ if ( !$directed ) {
64
+ trigger_error( "Can't determine direction", E_USER_WARNING );
65
+ continue;
66
+ }
67
+
68
+ $p2p_types[ $p2p_type ] = $directed->get_direction();
69
+ }
70
+
71
+ if ( 1 == count( $p2p_types ) )
72
+ $q = $directed->get_connected_args( $q );
73
+ else
74
+ $q['p2p_type'] = $p2p_types;
75
+
76
+ return true;
77
+ }
78
+
79
+ function alter_clauses( $clauses, $q, $main_id_column ) {
80
+ global $wpdb;
81
 
82
  $clauses['fields'] .= ", $wpdb->p2p.*";
83
 
84
  $clauses['join'] .= " INNER JOIN $wpdb->p2p";
85
 
86
+ // Handle main query
87
+ if ( 'any' == $q['items'] ) {
88
  $search = false;
89
  } else {
90
+ $search = implode( ',', array_map( 'absint', (array) $q['items'] ) );
91
  }
92
 
93
+ $where_parts = array();
94
+
95
+ foreach ( $q['p2p_type'] as $p2p_type => $direction ) {
96
+ if ( false === $p2p_type ) // used by migration script
97
+ $part = "1 = 1";
98
+ else
99
+ $part = $wpdb->prepare( "$wpdb->p2p.p2p_type = %s", $p2p_type );
100
 
 
 
 
 
 
 
 
 
 
 
101
  $fields = array( 'p2p_from', 'p2p_to' );
 
 
102
 
103
+ switch ( $direction ) {
104
 
105
+ case 'from':
106
+ $fields = array_reverse( $fields );
107
+ // fallthrough
108
+ case 'to':
109
+ list( $from, $to ) = $fields;
110
+
111
+ $part .= " AND $main_id_column = $wpdb->p2p.$from";
112
+
113
+ if ( $search ) {
114
+ $part .= " AND $wpdb->p2p.$to IN ($search)";
115
+ }
116
+
117
+ break;
118
+ default:
119
+ if ( $search ) {
120
+ $part .= " AND (
121
+ ($main_id_column = $wpdb->p2p.p2p_to AND $wpdb->p2p.p2p_from IN ($search)) OR
122
+ ($main_id_column = $wpdb->p2p.p2p_from AND $wpdb->p2p.p2p_to IN ($search))
123
+ )";
124
+ } else {
125
+ $part .= " AND ($main_id_column = $wpdb->p2p.p2p_to OR $main_id_column = $wpdb->p2p.p2p_from)";
126
+ }
127
  }
128
+
129
+ $where_parts[] = '(' . $part . ')';
130
  }
131
 
132
+ if ( 1 == count( $where_parts ) )
133
+ $clauses['where'] .= " AND " . $where_parts[0];
134
+ else
135
+ $clauses['where'] .= " AND (" . implode( ' OR ', $where_parts ) . ")";
136
+
137
+ // Handle custom fields
138
+ if ( !empty( $q['meta'] ) ) {
139
+ $meta_clauses = _p2p_meta_sql_helper( $q['meta'] );
140
  foreach ( $meta_clauses as $key => $value ) {
141
  $clauses[ $key ] .= $value;
142
  }
143
  }
144
 
145
  // Handle ordering
146
+ if ( $q['orderby'] ) {
 
147
  $clauses['join'] .= $wpdb->prepare( "
148
  LEFT JOIN $wpdb->p2pmeta AS p2pm_order ON (
149
  $wpdb->p2p.p2p_id = p2pm_order.p2p_id AND p2pm_order.meta_key = %s
150
  )
151
+ ", $q['orderby'] );
152
 
153
+ $order = ( 'DESC' == strtoupper( $q['order'] ) ) ? 'DESC' : 'ASC';
154
 
155
  $field = 'meta_value';
156
 
157
+ if ( $q['order_num'] )
158
  $field .= '+0';
159
 
160
+ $clauses['orderby'] = "p2pm_order.$field $order";
161
  }
162
 
163
  return $clauses;
164
  }
165
 
166
+ private function find_direction( $ctype, $arg, $object_type ) {
167
+ $opposite_side = self::choose_side( $object_type,
168
+ $ctype->object['from'],
169
+ $ctype->object['to']
170
+ );
 
171
 
172
+ if ( in_array( $opposite_side, array( 'from', 'to' ) ) )
173
+ return $ctype->set_direction( $opposite_side );
 
174
 
175
+ return $ctype->find_direction( $arg );
176
  }
 
177
 
178
+ private static function choose_side( $current, $from, $to ) {
179
+ if ( $from == $to && $current == $from )
180
+ return 'any';
181
+
182
+ if ( $current == $from )
183
+ return 'to';
184
+
185
+ if ( $current == $to )
186
+ return 'from';
187
+
188
+ return false;
189
+ }
190
+ }
191
 
core/side.php ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ define( 'ADMIN_BOX_PER_PAGE', 5 );
4
+
5
+ abstract class P2P_Side {
6
+
7
+ public $query_vars;
8
+
9
+ function __construct( $query_vars ) {
10
+ $this->query_vars = $query_vars;
11
+ }
12
+
13
+ function get_base_qv() {
14
+ return $this->query_vars;
15
+ }
16
+ }
17
+
18
+
19
+ class P2P_Side_Post extends P2P_Side {
20
+
21
+ public $post_type = array();
22
+
23
+ function __construct( $query_vars ) {
24
+ parent::__construct( $query_vars );
25
+
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
+ ) );
35
+ }
36
+
37
+ function get_title() {
38
+ return $this->get_ptype()->labels->name;
39
+ }
40
+
41
+ function get_labels() {
42
+ return $this->get_ptype()->labels;
43
+ }
44
+
45
+ function check_capability() {
46
+ return current_user_can( $this->get_ptype()->cap->edit_posts );
47
+ }
48
+
49
+ function do_query( $args ) {
50
+ return new WP_Query( $args );
51
+ }
52
+
53
+ function abstract_query( $query ) {
54
+ return (object) array(
55
+ 'items' => $query->posts,
56
+ 'current_page' => max( 1, $query->get('paged') ),
57
+ 'total_pages' => $query->max_num_pages
58
+ );
59
+ }
60
+
61
+ private static $admin_box_qv = array(
62
+ 'update_post_term_cache' => false,
63
+ 'update_post_meta_cache' => false,
64
+ 'post_status' => 'any',
65
+ );
66
+
67
+ function get_connections_qv() {
68
+ return array_merge( self::$admin_box_qv, array(
69
+ 'nopaging' => true
70
+ ) );
71
+ }
72
+
73
+ function get_connectable_qv( $item_id, $page, $search, $to_exclude ) {
74
+ $qv = array_merge( $this->get_base_qv(), self::$admin_box_qv, array(
75
+ 'posts_per_page' => ADMIN_BOX_PER_PAGE,
76
+ 'paged' => $page,
77
+ ) );
78
+
79
+ if ( $search ) {
80
+ $qv['s'] = $search;
81
+ }
82
+
83
+ $qv['post__not_in'] = $to_exclude;
84
+
85
+ return $qv;
86
+ }
87
+
88
+ /**
89
+ * @param mixed A post type, a post id, a post object, an array of post ids or of objects.
90
+ */
91
+ function item_recognize( $arg ) {
92
+ if ( is_array( $arg ) ) {
93
+ $arg = reset( $arg );
94
+ }
95
+
96
+ if ( is_object( $arg ) ) {
97
+ $post_type = $arg->post_type;
98
+ } elseif ( $post_id = (int) $arg ) {
99
+ $post = get_post( $post_id );
100
+ if ( !$post )
101
+ return false;
102
+ $post_type = $post->post_type;
103
+ } else {
104
+ $post_type = $arg;
105
+ }
106
+
107
+ if ( !post_type_exists( $post_type ) )
108
+ return false;
109
+
110
+ return in_array( $post_type, $this->post_type );
111
+ }
112
+
113
+ protected function get_ptype() {
114
+ return get_post_type_object( $this->post_type[0] );
115
+ }
116
+
117
+ function item_exists( $item_id ) {
118
+ return (bool) get_post( $item_id );
119
+ }
120
+
121
+ function item_title( $item ) {
122
+ return $item->post_title;
123
+ }
124
+ }
125
+
126
+
127
+ class P2P_Side_Attachment extends P2P_Side_Post {
128
+
129
+ function __construct( $query_vars ) {
130
+ P2P_Side::__construct( $query_vars );
131
+
132
+ $this->post_type = array( 'attachment' );
133
+ }
134
+ }
135
+
136
+
137
+ class P2P_Side_User extends P2P_Side {
138
+
139
+ function get_title() {
140
+ return __( 'Users', P2P_TEXTDOMAIN );
141
+ }
142
+
143
+ function get_labels() {
144
+ return (object) array(
145
+ 'singular_name' => __( 'User', P2P_TEXTDOMAIN ),
146
+ 'search_items' => __( 'Search Users', P2P_TEXTDOMAIN ),
147
+ 'not_found' => __( 'No users found.', P2P_TEXTDOMAIN ),
148
+ );
149
+ }
150
+
151
+ function check_capability() {
152
+ return current_user_can( 'list_users' );
153
+ }
154
+
155
+ function do_query( $args ) {
156
+ return new WP_User_Query( $args );
157
+ }
158
+
159
+ function abstract_query( $query ) {
160
+ return (object) array(
161
+ 'items' => $query->get_results(),
162
+ 'current_page' => isset( $query->query_vars['_p2p_page'] ) ? $query->query_vars['_p2p_page'] : 1,
163
+ 'total_pages' => ceil( $query->get_total() / ADMIN_BOX_PER_PAGE )
164
+ );
165
+ }
166
+
167
+ function get_connections_qv() {
168
+ return array();
169
+ }
170
+
171
+ function get_connectable_qv( $item_id, $page, $search, $to_exclude ) {
172
+ $qv = array_merge( $this->get_base_qv(), array(
173
+ 'number' => ADMIN_BOX_PER_PAGE,
174
+ 'offset' => ADMIN_BOX_PER_PAGE * ( $page - 1 ),
175
+ '_p2p_page' => $page
176
+ ) );
177
+
178
+ if ( $search ) {
179
+ $qv['search'] = '*' . $search . '*';
180
+ }
181
+
182
+ $qv['exclude'] = $to_exclude;
183
+
184
+ return $qv;
185
+ }
186
+
187
+ function item_recognize( $arg ) {
188
+ return false;
189
+ }
190
+
191
+ function item_exists( $item_id ) {
192
+ return (bool) get_user_by( 'id', $item_id );
193
+ }
194
+
195
+ function item_title( $item ) {
196
+ return $item->display_name;
197
+ }
198
+ }
199
+
core/storage.php CHANGED
@@ -1,23 +1,61 @@
1
  <?php
2
 
 
 
 
3
  class P2P_Storage {
4
 
5
- // Use P2P_Connection_Type
6
- private function __construct() {}
7
-
8
- private static $version = 3;
9
 
10
  function init() {
11
- $table = new scbTable( 'p2p', false, "
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  p2p_id bigint(20) unsigned NOT NULL auto_increment,
13
  p2p_from bigint(20) unsigned NOT NULL,
14
  p2p_to bigint(20) unsigned NOT NULL,
 
15
  PRIMARY KEY (p2p_id),
16
  KEY p2p_from (p2p_from),
17
- KEY p2p_to (p2p_to)
 
18
  " );
19
 
20
- $table2 = new scbTable( 'p2pmeta', false, "
21
  meta_id bigint(20) unsigned NOT NULL auto_increment,
22
  p2p_id bigint(20) unsigned NOT NULL default '0',
23
  meta_key varchar(255) default NULL,
@@ -26,190 +64,57 @@ class P2P_Storage {
26
  KEY p2p_id (p2p_id),
27
  KEY meta_key (meta_key)
28
  " );
29
-
30
- if ( is_admin() && self::$version != get_option( 'p2p_storage' ) ) {
31
- $table->install();
32
- $table2->install();
33
-
34
- update_option( 'p2p_storage', self::$version );
35
- }
36
-
37
- add_action( 'deleted_post', array( __CLASS__, 'disconnect' ) );
38
- }
39
-
40
- /**
41
- * Get a list of connections, given a certain post id
42
- *
43
- * @param int $from post id
44
- * @param int|string $to post id or direction: 'from' or 'to'
45
- * @param array $data additional data about the connection to filter against
46
- *
47
- * @return array( p2p_id => post_id ) if $to is string
48
- * @return array( p2p_id ) if $to is int
49
- */
50
- function get( $post_id, $direction, $data = array() ) {
51
- if ( 'any' == $direction ) {
52
- $from = self::_get( $post_id, 'from', $data );
53
- $to = self::_get( $post_id, 'to', $data );
54
-
55
- // array_merge() doesn't preserve numeric keys
56
- return $from + $to;
57
- }
58
-
59
- return self::_get( $post_id, $direction, $data );
60
  }
61
 
62
- private function _get( $from, $to, $data = array() ) {
63
  global $wpdb;
64
 
65
- $fields = "$wpdb->p2p.p2p_id";
66
- $where = '';
67
- $join = '';
68
-
69
- $_return_p2p_ids = false;
70
- switch ( $to ) {
71
- case 'from':
72
- $fields .= ', p2p_to AS post_id';
73
- $where .= $wpdb->prepare( "p2p_from = %d", $from );
74
- break;
75
-
76
- case 'to':
77
- $fields .= ', p2p_from AS post_id';
78
- $where .= $wpdb->prepare( "p2p_to = %d", $from );
79
- break;
80
-
81
- default:
82
- $where .= $wpdb->prepare( "p2p_from = %d AND p2p_to = %d", $from, $to );
83
- $_return_p2p_ids = true;
84
- }
85
 
86
- if ( !empty( $data ) ) {
87
- $clauses = _p2p_meta_sql_helper( $data );
88
- $join .= $clauses['join'];
89
- $where .= $clauses['where'];
90
- }
91
 
92
- $query = "SELECT $fields FROM $wpdb->p2p $join WHERE $where";
 
 
 
 
 
93
 
94
- if ( $_return_p2p_ids )
95
- return $wpdb->get_col( $query );
96
 
97
- $results = $wpdb->get_results( $query );
 
 
 
98
 
99
- $r = array();
100
- foreach ( $results as $row )
101
- $r[ $row->p2p_id ] = $row->post_id;
102
 
103
- return $r;
104
  }
105
 
106
- /**
107
- * Connect two posts
108
- *
109
- * @param int $from post id
110
- * @param int $to post id
111
- * @param array $data additional data about the connection
112
- *
113
- * @return int|bool connection id or False on failure
114
- */
115
- function connect( $from, $to, $data = array() ) {
116
- global $wpdb;
117
-
118
- $from = absint( $from );
119
- $to = absint( $to );
120
-
121
- if ( !$from || !$to )
122
- return false;
123
-
124
- $wpdb->insert( $wpdb->p2p, array( 'p2p_from' => $from, 'p2p_to' => $to ), '%d' );
125
 
126
- $p2p_id = $wpdb->insert_id;
127
-
128
- foreach ( $data as $key => $value )
129
- p2p_add_meta( $p2p_id, $key, $value );
130
-
131
- return $p2p_id;
132
- }
133
-
134
- /**
135
- * Disconnect two posts
136
- *
137
- * @param int $from post id
138
- * @param int|string $to post id or direction: 'from' or 'to'
139
- * @param array $data additional data about the connection to filter against
140
- *
141
- * @return int Number of connections deleted
142
- */
143
- function disconnect( $from, $to = 'any', $data = array() ) {
144
- $connections = self::get( $from, $to, $data );
145
-
146
- // We're interested in the p2p_ids
147
- if ( !(int) $to )
148
- $connections = array_keys( $connections );
149
-
150
- return self::delete( $connections );
151
  }
152
 
153
- /**
154
- * Delete one or more connections
155
- *
156
- * @param int|array $p2p_id Connection ids
157
- *
158
- * @return int Number of connections deleted
159
- */
160
- function delete( $p2p_id ) {
161
- global $wpdb;
162
-
163
- if ( empty( $p2p_id ) )
164
- return 0;
165
-
166
- $p2p_ids = array_map( 'absint', (array) $p2p_id );
167
-
168
- $where = "WHERE p2p_id IN (" . implode( ',', $p2p_ids ) . ")";
169
-
170
- $wpdb->query( "DELETE FROM $wpdb->p2p $where" );
171
- $wpdb->query( "DELETE FROM $wpdb->p2pmeta $where" );
172
-
173
- return count( $p2p_ids );
174
  }
175
  }
176
 
177
  P2P_Storage::init();
178
 
179
-
180
- function p2p_get_meta($p2p_id, $key, $single = false) {
181
- return get_metadata('p2p', $p2p_id, $key, $single);
182
- }
183
-
184
- function p2p_update_meta($p2p_id, $meta_key, $meta_value, $prev_value = '') {
185
- return update_metadata('p2p', $p2p_id, $meta_key, $meta_value, $prev_value);
186
- }
187
-
188
- function p2p_add_meta($p2p_id, $meta_key, $meta_value, $unique = false) {
189
- return add_metadata('p2p', $p2p_id, $meta_key, $meta_value, $unique);
190
- }
191
-
192
- function p2p_delete_meta($p2p_id, $meta_key, $meta_value = '') {
193
- return delete_metadata('p2p', $p2p_id, $meta_key, $meta_value);
194
- }
195
-
196
- /**
197
- * @internal
198
- */
199
- function _p2p_meta_sql_helper( $data ) {
200
- global $wpdb;
201
-
202
- if ( isset( $data[0] ) ) {
203
- $meta_query = $data;
204
- }
205
- else {
206
- $meta_query = array();
207
-
208
- foreach ( $data as $key => $value ) {
209
- $meta_query[] = compact( 'key', 'value' );
210
- }
211
- }
212
-
213
- return get_meta_sql( $meta_query, 'p2p', $wpdb->p2p, 'p2p_id' );
214
- }
215
-
1
  <?php
2
 
3
+ /**
4
+ * Handles various db-related tasks
5
+ */
6
  class P2P_Storage {
7
 
8
+ static $version = 4;
 
 
 
9
 
10
  function init() {
11
+ scb_register_table( 'p2p' );
12
+ scb_register_table( 'p2pmeta' );
13
+
14
+ add_action( 'admin_notices', array( __CLASS__, 'maybe_install' ) );
15
+
16
+ add_action( 'deleted_post', array( __CLASS__, 'deleted_post' ) );
17
+ }
18
+
19
+ function maybe_install() {
20
+ if ( !current_user_can( 'manage_options' ) )
21
+ return;
22
+
23
+ $current_ver = get_option( 'p2p_storage' );
24
+
25
+ if ( $current_ver == self::$version )
26
+ return;
27
+
28
+ self::install();
29
+
30
+ if ( isset( $_GET['p2p-upgrade'] ) ) {
31
+ $n = self::upgrade();
32
+
33
+ update_option( 'p2p_storage', P2P_Storage::$version );
34
+
35
+ echo scb_admin_notice( sprintf( __( 'Upgraded %d connections.', P2P_TEXTDOMAIN ), $n ) );
36
+ } elseif ( $current_ver ) {
37
+ echo scb_admin_notice( sprintf(
38
+ __( 'The Posts 2 Posts connections need to be upgraded. <a href="%s">Proceed.</a>', P2P_TEXTDOMAIN ),
39
+ admin_url( 'tools.php?p2p-upgrade' )
40
+ ) );
41
+ } else {
42
+ update_option( 'p2p_storage', P2P_Storage::$version );
43
+ }
44
+ }
45
+
46
+ function install() {
47
+ scb_install_table( 'p2p', "
48
  p2p_id bigint(20) unsigned NOT NULL auto_increment,
49
  p2p_from bigint(20) unsigned NOT NULL,
50
  p2p_to bigint(20) unsigned NOT NULL,
51
+ p2p_type varchar(44) NOT NULL default '',
52
  PRIMARY KEY (p2p_id),
53
  KEY p2p_from (p2p_from),
54
+ KEY p2p_to (p2p_to),
55
+ KEY p2p_type (p2p_type)
56
  " );
57
 
58
+ scb_install_table( 'p2pmeta', "
59
  meta_id bigint(20) unsigned NOT NULL auto_increment,
60
  p2p_id bigint(20) unsigned NOT NULL default '0',
61
  meta_key varchar(255) default NULL,
64
  KEY p2p_id (p2p_id),
65
  KEY meta_key (meta_key)
66
  " );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  }
68
 
69
+ function upgrade() {
70
  global $wpdb;
71
 
72
+ $n = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
+ foreach ( P2P_Connection_Type_Factory::get_all_instances() as $p2p_type => $ctype ) {
75
+ if ( ! $ctype instanceof P2P_Connection_Type )
76
+ continue;
 
 
77
 
78
+ $args = $ctype->set_direction( 'any' )->get_connected_args( array(
79
+ 'connected_items' => 'any',
80
+ 'cache_results' => false,
81
+ 'post_status' => 'any',
82
+ 'nopaging' => true
83
+ ) );
84
 
85
+ $args['p2p_type'] = array( false => $args['direction'] );
 
86
 
87
+ foreach ( get_posts( $args ) as $post ) {
88
+ // some connections might be ambiguous, spanning multiple connection types; first one wins
89
+ if ( $post->p2p_type )
90
+ continue;
91
 
92
+ $n += $wpdb->update( $wpdb->p2p, compact( 'p2p_type' ), array( 'p2p_id' => $post->p2p_id ) );
93
+ }
94
+ }
95
 
96
+ return $n;
97
  }
98
 
99
+ function uninstall() {
100
+ scb_uninstall_table( 'p2p' );
101
+ scb_uninstall_table( 'p2pmeta' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
+ delete_option( 'p2p_storage' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  }
105
 
106
+ function deleted_post( $post_id ) {
107
+ foreach ( P2P_Connection_Type_Factory::get_all_instances() as $p2p_type => $ctype ) {
108
+ foreach ( array( 'from', 'to' ) as $direction ) {
109
+ if ( 'post' == $ctype->object[ $direction ] ) {
110
+ p2p_delete_connections( $p2p_type, array(
111
+ $direction => $post_id,
112
+ ) );
113
+ }
114
+ }
115
+ }
 
 
 
 
 
 
 
 
 
 
 
116
  }
117
  }
118
 
119
  P2P_Storage::init();
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/type-factory.php ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class P2P_Connection_Type_Factory {
4
+ private static $instances = array();
5
+
6
+ public static function register( $args ) {
7
+ if ( isset( $args['name'] ) ) {
8
+ if ( strlen( $args['name'] ) > 44 ) {
9
+ trigger_error( sprintf( "Connection name '%s' is longer than 44 characters.", $args['name'] ), E_USER_WARNING );
10
+ return false;
11
+ }
12
+ } else {
13
+ trigger_error( "Connection types without a 'name' parameter are deprecated.", E_USER_WARNING );
14
+ }
15
+
16
+ $args = wp_parse_args( $args, array(
17
+ 'name' => false,
18
+ 'from_object' => 'post',
19
+ 'to_object' => 'post',
20
+ 'from' => 'post',
21
+ 'to' => 'post',
22
+ 'from_query_vars' => array(),
23
+ 'to_query_vars' => array(),
24
+ 'data' => array(),
25
+ 'cardinality' => 'many-to-many',
26
+ 'prevent_duplicates' => true,
27
+ 'self_connections' => false,
28
+ 'sortable' => false,
29
+ 'title' => array(),
30
+ 'from_labels' => '',
31
+ 'to_labels' => '',
32
+ 'reciprocal' => false,
33
+ ) );
34
+
35
+ $sides = array();
36
+
37
+ foreach ( array( 'from', 'to' ) as $direction ) {
38
+ $object = _p2p_pluck( $args, $direction );
39
+
40
+ if ( 'user' == $object )
41
+ $args[ $direction . '_object' ] = 'user';
42
+ elseif ( 'attachment' == $object )
43
+ $args[ $direction . '_object' ] = 'attachment';
44
+
45
+ if ( 'post' == $args[ $direction . '_object' ] ) {
46
+ $validated = array();
47
+
48
+ foreach ( (array) $object as $ptype ) {
49
+ if ( !post_type_exists( $ptype ) ) {
50
+ trigger_error( "Post type '$ptype' is not defined." );
51
+ } else {
52
+ $validated[] = $ptype;
53
+ }
54
+ }
55
+
56
+ if ( empty( $validated ) )
57
+ $validated = array( 'post' );
58
+
59
+ $args[ $direction . '_query_vars' ]['post_type'] = $validated;
60
+ }
61
+ }
62
+
63
+ if ( !$args['name'] ) {
64
+ $args['name'] = md5( serialize( array_values( wp_array_slice_assoc( $args, array(
65
+ 'from_object', 'to_object',
66
+ 'from_query_vars', 'to_query_vars',
67
+ 'data'
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 );
82
+ }
83
+
84
+ self::$instances[ $ctype->name ] = $ctype;
85
+
86
+ return $ctype;
87
+ }
88
+
89
+ public static function get_all_instances() {
90
+ return self::$instances;
91
+ }
92
+
93
+ public static function get_instance( $hash ) {
94
+ if ( isset( self::$instances[ $hash ] ) )
95
+ return self::$instances[ $hash ];
96
+
97
+ return false;
98
+ }
99
+ }
100
+
core/type.php CHANGED
@@ -1,91 +1,93 @@
1
  <?php
2
 
3
- class P2P_Connection_Type {
4
-
5
- private static $instances = array();
6
-
7
- public function register( $args ) {
8
- $args = wp_parse_args( $args, array(
9
- 'id' => false,
10
- 'from' => '',
11
- 'to' => '',
12
- 'from_query_vars' => array(),
13
- 'to_query_vars' => array(),
14
- 'data' => array(),
15
- 'reciprocal' => false,
16
- 'cardinality' => 'many-to-many',
17
- 'prevent_duplicates' => true,
18
- 'sortable' => false,
19
- 'title' => '',
20
- ) );
21
 
22
- foreach ( array( 'from', 'to' ) as $key ) {
23
- if ( isset( $args[ $key ] ) ) {
24
- $args["{$key}_query_vars"]['post_type'] = (array) $args[ $key ];
25
- unset( $args[ $key ] );
26
- }
27
 
28
- if ( empty( $args["{$key}_query_vars"]['post_type'] ) )
29
- $args["{$key}_query_vars"]['post_type'] = array( 'post' );
30
- }
31
 
32
- $id =& $args['id'];
33
 
34
- if ( !$id ) {
35
- $id = md5( serialize( wp_array_slice_assoc( $args, array( 'from_query_vars', 'to_query_vars', 'data' ) ) ) );
36
- }
 
 
 
 
 
 
37
 
38
- if ( isset( self::$instances[ $id ] ) ) {
39
- trigger_error( 'Connection type is already defined.', E_USER_NOTICE );
 
40
  }
41
 
42
- return self::$instances[ $id ] = new P2P_Connection_Type( $args );
43
- }
44
 
45
- public function get_all_instances() {
46
- return self::$instances;
47
- }
48
 
49
- public function get_instance( $hash ) {
50
- if ( isset( self::$instances[ $hash ] ) )
51
- return self::$instances[ $hash ];
52
 
53
- return false;
 
 
54
  }
55
 
 
 
56
 
57
- protected $args;
58
- protected $indeterminate;
59
 
60
- protected function __construct( $args ) {
61
- $this->args = $args;
 
 
 
62
 
63
- $common = array_intersect( $this->from, $this->to );
 
 
64
 
65
- if ( !empty( $common ) )
66
- $this->indeterminate = true;
 
 
67
 
68
- $this->args['title'] = P2P_Util::expand_title( $this->args['title'], $this->from, $this->to );
 
69
  }
70
 
71
- public function __get( $key ) {
72
- if ( 'from' == $key || 'to' == $key )
73
- return $this->args[ "{$key}_query_vars" ]['post_type'];
 
 
 
 
 
 
 
 
74
 
75
- if ( 'indeterminate' == $key )
76
- return $this->indeterminate;
77
 
78
- return $this->args[$key];
79
- }
 
 
 
80
 
81
- public function __isset( $key ) {
82
- return isset( $this->args[$key] );
83
  }
84
 
85
  public function __call( $method, $args ) {
86
  $directed = $this->find_direction( $args[0] );
87
- if ( !$directed )
 
88
  return false;
 
89
 
90
  return call_user_func_array( array( $directed, $method ), $args );
91
  }
@@ -97,14 +99,14 @@ class P2P_Connection_Type {
97
  *
98
  * @return object P2P_Directed_Connection_Type instance
99
  */
100
- public function set_direction( $direction ) {
101
  if ( !in_array( $direction, array( 'from', 'to', 'any' ) ) )
102
  return false;
103
 
104
- if ( $orderby_key = P2P_Util::get_orderby_key( $this->sortable, $direction ) )
105
- return new P2P_Ordered_Connection_Type( $this, $direction, $orderby_key );
106
 
107
- return new P2P_Directed_Connection_Type( $this, $direction );
108
  }
109
 
110
  /**
@@ -116,21 +118,17 @@ class P2P_Connection_Type {
116
  * @return bool|object|string False on failure, P2P_Directed_Connection_Type instance or direction on success.
117
  */
118
  public function find_direction( $arg, $instantiate = true ) {
119
- $post_type = P2P_Util::find_post_type( $arg );
120
- if ( !$post_type )
121
- return false;
122
-
123
- $direction = P2P_Util::get_direction( $post_type, $this->from, $this->to );
124
- if ( !$direction )
125
- return false;
126
 
127
- if ( $this->indeterminate )
128
- $direction = $this->reciprocal ? 'any' : 'from';
129
 
130
- if ( $instantiate )
131
- return $this->set_direction( $direction );
132
 
133
- return $direction;
134
  }
135
 
136
  /**
@@ -140,8 +138,9 @@ class P2P_Connection_Type {
140
  *
141
  * @param object $query WP_Query instance.
142
  * @param string|array $extra_qv Additional query vars for the inner query.
 
143
  */
144
- public function each_connected( $query, $extra_qv = array() ) {
145
  if ( empty( $query->posts ) || !is_object( $query->posts[0] ) )
146
  return;
147
 
@@ -153,8 +152,6 @@ class P2P_Connection_Type {
153
  if ( !$directed )
154
  return false;
155
 
156
- $prop_name = 'connected';
157
-
158
  $posts = array();
159
 
160
  foreach ( $query->posts as $post ) {
@@ -170,26 +167,39 @@ class P2P_Connection_Type {
170
  }
171
  $extra_qv['nopaging'] = true;
172
 
173
- $q = $directed->get_connected( array_keys( $posts ), $extra_qv );
174
-
175
- foreach ( $q->posts as $inner_post ) {
176
- if ( $inner_post->ID == $inner_post->p2p_from )
177
- $outer_post_id = $inner_post->p2p_to;
178
- elseif ( $inner_post->ID == $inner_post->p2p_to )
179
- $outer_post_id = $inner_post->p2p_from;
180
- else {
181
- trigger_error( "Corrupted data for post $inner_post->ID", E_USER_WARNING );
182
- continue;
183
- }
184
 
185
- if ( $outer_post_id == $inner_post->ID ) {
186
- trigger_error( 'Post connected to itself.', E_USER_WARNING );
 
 
 
 
 
187
  continue;
188
  }
189
 
190
- array_push( $posts[ $outer_post_id ]->$prop_name, $inner_post );
191
  }
192
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
 
194
  /**
195
  * Get a list of posts connected to other posts connected to a post.
@@ -277,14 +287,5 @@ class P2P_Connection_Type {
277
 
278
  return $adjacent[0];
279
  }
280
-
281
- /**
282
- * Delete a connection.
283
- *
284
- * @param int p2p_id
285
- */
286
- public function delete_connection( $p2p_id ) {
287
- return P2P_Storage::delete( $p2p_id );
288
- }
289
  }
290
 
1
  <?php
2
 
3
+ class Generic_Connection_Type {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
+ public $indeterminate = false;
 
 
 
 
6
 
7
+ public $object;
 
 
8
 
9
+ public $side;
10
 
11
+ public $cardinality;
12
+
13
+ public $title;
14
+
15
+ public $labels;
16
+
17
+ public function __construct( $args ) {
18
+ foreach ( array( 'from', 'to' ) as $direction ) {
19
+ $this->object[ $direction ] = _p2p_pluck( $args, $direction . '_object' );
20
 
21
+ $class = 'P2P_Side_' . ucfirst( $this->object[ $direction ] );
22
+
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 );
 
 
29
 
30
+ $this->title = $this->expand_title( _p2p_pluck( $args, 'title' ) );
 
 
31
 
32
+ foreach ( $args as $key => $value ) {
33
+ $this->$key = $value;
34
+ }
35
  }
36
 
37
+ private function set_cardinality( $cardinality ) {
38
+ $parts = explode( '-', $cardinality );
39
 
40
+ $this->cardinality['from'] = $parts[0];
41
+ $this->cardinality['to'] = $parts[2];
42
 
43
+ foreach ( $this->cardinality as $key => &$value ) {
44
+ if ( 'one' != $value )
45
+ $value = 'many';
46
+ }
47
+ }
48
 
49
+ private function set_labels( &$args ) {
50
+ foreach ( array( 'from', 'to' ) as $key ) {
51
+ $labels = _p2p_pluck( $args, $key . '_labels' );
52
 
53
+ if ( empty( $labels ) )
54
+ $labels = $this->side[ $key ]->get_labels();
55
+ else
56
+ $labels = (object) $labels;
57
 
58
+ $this->labels[ $key ] = $labels;
59
+ }
60
  }
61
 
62
+ private function expand_title( $title ) {
63
+ if ( $title && !is_array( $title ) ) {
64
+ return array(
65
+ 'from' => $title,
66
+ 'to' => $title,
67
+ );
68
+ }
69
+
70
+ foreach ( array( 'from', 'to' ) as $key ) {
71
+ if ( isset( $title[$key] ) )
72
+ continue;
73
 
74
+ $other_key = ( 'from' == $key ) ? 'to' : 'from';
 
75
 
76
+ $title[$key] = sprintf(
77
+ __( 'Connected %s', P2P_TEXTDOMAIN ),
78
+ $this->side[ $other_key ]->get_title()
79
+ );
80
+ }
81
 
82
+ return $title;
 
83
  }
84
 
85
  public function __call( $method, $args ) {
86
  $directed = $this->find_direction( $args[0] );
87
+ if ( !$directed ) {
88
+ trigger_error( "Can't determine direction", E_USER_WARNING );
89
  return false;
90
+ }
91
 
92
  return call_user_func_array( array( $directed, $method ), $args );
93
  }
99
  *
100
  * @return object P2P_Directed_Connection_Type instance
101
  */
102
+ public function set_direction( $direction, $instantiate = true ) {
103
  if ( !in_array( $direction, array( 'from', 'to', 'any' ) ) )
104
  return false;
105
 
106
+ if ( $instantiate )
107
+ return new P2P_Directed_Connection_Type( $this, $direction );
108
 
109
+ return $direction;
110
  }
111
 
112
  /**
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;
 
 
 
 
124
 
125
+ if ( $this->indeterminate )
126
+ $direction = $this->reciprocal ? 'any' : 'from';
127
 
128
+ return $this->set_direction( $direction, $instantiate );
129
+ }
130
 
131
+ return false;
132
  }
133
 
134
  /**
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
 
152
  if ( !$directed )
153
  return false;
154
 
 
 
155
  $posts = array();
156
 
157
  foreach ( $query->posts as $post ) {
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
+
187
+
188
+ class P2P_Connection_Type extends Generic_Connection_Type {
189
+
190
+ public function __construct( $args ) {
191
+ parent::__construct( $args );
192
+
193
+ $common = array_intersect( $this->from, $this->to );
194
+
195
+ if ( !empty( $common ) )
196
+ $this->indeterminate = true;
197
+ }
198
+
199
+ public function __get( $key ) {
200
+ if ( 'from' == $key || 'to' == $key )
201
+ return $this->side[ $key ]->post_type;
202
+ }
203
 
204
  /**
205
  * Get a list of posts connected to other posts connected to a post.
287
 
288
  return $adjacent[0];
289
  }
 
 
 
 
 
 
 
 
 
290
  }
291
 
core/url-query.php CHANGED
@@ -4,31 +4,14 @@ class P2P_URL_Query {
4
 
5
  function init() {
6
  add_filter( 'query_vars', array( __CLASS__, 'query_vars' ) );
7
- add_filter( 'request', array( __CLASS__, 'request' ) );
8
  }
9
 
10
  function query_vars( $public_qv ) {
11
- $public_qv[] = 'connected_type';
12
- $public_qv[] = 'connected';
13
-
14
- return $public_qv;
15
- }
16
-
17
- function request( $request ) {
18
- if ( !isset( $request['connected_type'] ) || !isset( $request['connected'] ) )
19
- return $request;
20
-
21
- $connected_arg = _p2p_pluck( $request, 'connected' );
22
-
23
- $ctype = p2p_type( _p2p_pluck( $request, 'connected_type' ) );
24
- if ( !$ctype )
25
- return array( 'year' => 2525 );
26
-
27
- $directed = $ctype->find_direction( $connected_arg );
28
- if ( !$directed )
29
- return array( 'year' => 2525 );
30
-
31
- return $directed->get_connected_args( $connected_arg, $request );
32
  }
33
  }
34
 
4
 
5
  function init() {
6
  add_filter( 'query_vars', array( __CLASS__, 'query_vars' ) );
 
7
  }
8
 
9
  function query_vars( $public_qv ) {
10
+ return array_merge( $public_qv, array(
11
+ 'connected_type',
12
+ 'connected_items',
13
+ 'connected_direction',
14
+ ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  }
16
  }
17
 
core/util.php CHANGED
@@ -1,123 +1,59 @@
1
  <?php
2
 
3
- /**
4
- * Various utilities for working with connection types.
5
- *
6
- * They come wit no backwards-compatibility warantee.
7
- */
8
- abstract class P2P_Util {
9
-
10
- /**
11
- * Attempt to find a post type.
12
- *
13
- * @param mixed A post type, a post id, a post object, an array of post ids or of objects.
14
- *
15
- * @return bool|string False on failure, post type on success.
16
- */
17
- static function find_post_type( $arg ) {
18
- if ( is_array( $arg ) ) {
19
- $arg = reset( $arg );
20
- }
21
-
22
- if ( is_object( $arg ) ) {
23
- $post_type = $arg->post_type;
24
- } elseif ( $post_id = (int) $arg ) {
25
- $post = get_post( $post_id );
26
- if ( !$post )
27
- return false;
28
- $post_type = $post->post_type;
29
- } else {
30
- $post_type = $arg;
31
- }
32
-
33
- if ( !post_type_exists( $post_type ) )
34
- return false;
35
-
36
- return $post_type;
37
- }
38
 
39
- static function get_first_valid_ptype( $post_types ) {
40
- do {
41
- $ptype = get_post_type_object( array_shift( $post_types ) );
42
- } while ( !$ptype && !empty( $post_types ) );
43
 
44
- return $ptype;
 
45
  }
 
 
46
 
47
- /**
48
- * Check if a certain post or post type could have connections of this type.
49
- *
50
- * @param string A post type to check against.
51
- * @param array List of post types (from).
52
- * @param array List of post types (to).
53
- *
54
- * @return bool|string False on failure, direction on success.
55
- */
56
- static function get_direction( $post_type, $from, $to ) {
57
- if ( in_array( $post_type, $from ) ) {
58
- $direction = 'from';
59
- } elseif ( in_array( $post_type, $to ) ) {
60
- $direction = 'to';
61
- } else {
62
- $direction = false;
63
  }
64
-
65
- return $direction;
66
- }
67
-
68
- /**
69
- * @param string The direction in which ordering is allowed
70
- * @param string The current direction
71
- *
72
- * @return bool|string False on failure, the connection field key otherwise
73
- */
74
- static function get_orderby_key( $ordering_direction, $connection_direction ) {
75
- if ( !$ordering_direction || 'any' == $connection_direction )
76
- return false;
77
-
78
- if ( 'any' == $ordering_direction || $connection_direction == $ordering_direction )
79
- return '_order_' . $connection_direction;
80
-
81
- if ( 'from' == $connection_direction )
82
- return $ordering_direction;
83
-
84
- return false;
85
- }
86
-
87
- static function get_ptype_label( $ptypes ) {
88
- return get_post_type_object( $ptypes[0] )->labels->name;
89
  }
90
 
91
- static function expand_title( $title, $from, $to ) {
92
- if ( !$title )
93
- $title = array();
94
 
95
- if ( $title && !is_array( $title ) ) {
96
- return array(
97
- 'from' => $title,
98
- 'to' => $title,
99
- );
100
- }
101
 
102
- foreach ( array( 'from', 'to' ) as $key ) {
103
- if ( isset( $title[$key] ) )
104
- continue;
 
105
 
106
- $other_key = ( 'from' == $key ) ? 'to' : 'from';
 
 
 
107
 
108
- $title[$key] = sprintf(
109
- __( 'Connected %s', P2P_TEXTDOMAIN ),
110
- P2P_Util::get_ptype_label( $$other_key )
111
- );
112
- }
113
 
114
- return $title;
115
- }
116
  }
117
 
118
- function _p2p_pluck( &$arr, $key ) {
119
- $value = $arr[ $key ];
120
- unset( $arr[ $key ] );
121
- return $value;
 
 
122
  }
123
 
1
  <?php
2
 
3
+ /** @internal */
4
+ function _p2p_expand_direction( $direction ) {
5
+ if ( 'any' == $direction )
6
+ return array( 'from', 'to' );
7
+ else
8
+ return array( $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
  }
25
 
26
+ return get_meta_sql( $meta_query, 'p2p', $wpdb->p2p, 'p2p_id' );
27
+ }
 
28
 
29
+ /** @internal */
30
+ function _p2p_pluck( &$arr, $key ) {
31
+ $value = $arr[ $key ];
32
+ unset( $arr[ $key ] );
33
+ return $value;
34
+ }
35
 
36
+ /** @internal */
37
+ function _p2p_append( &$arr, $values ) {
38
+ $arr = array_merge( $arr, $values );
39
+ }
40
 
41
+ /** @internal */
42
+ function _p2p_get_field_type( $args ) {
43
+ if ( isset( $args['type'] ) )
44
+ return $args['type'];
45
 
46
+ if ( isset( $args['values'] ) && is_array( $args['values'] ) )
47
+ return 'select';
 
 
 
48
 
49
+ return 'text';
 
50
  }
51
 
52
+ /** @internal */
53
+ function _p2p_first( $args ) {
54
+ if ( empty( $args ) )
55
+ return false;
56
+
57
+ return reset( $args );
58
  }
59
 
core/widget.php CHANGED
@@ -7,8 +7,8 @@ class P2P_Widget extends scbWidget {
7
  'listing' => 'connected'
8
  );
9
 
10
- static function init( $file ) {
11
- parent::init( __CLASS__, $file, 'p2p' );
12
  }
13
 
14
  function __construct() {
@@ -21,7 +21,14 @@ class P2P_Widget extends scbWidget {
21
  if ( empty( $instance ) )
22
  $instance = $this->defaults;
23
 
24
- $ctypes = array_map( array( __CLASS__, 'ctype_label' ), P2P_Connection_Type::get_all_instances() );
 
 
 
 
 
 
 
25
 
26
  echo html( 'p', $this->input( array(
27
  'type' => 'select',
@@ -64,11 +71,11 @@ class P2P_Widget extends scbWidget {
64
  $connected = $ctype->get_related( $post_id );
65
  $title = sprintf(
66
  __( 'Related %s', P2P_TEXTDOMAIN ),
67
- P2P_Util::get_ptype_label( $directed->get_current_post_type() )
68
  );
69
  } else {
70
  $connected = $directed->get_connected( $post_id );
71
- $title = $directed->get_title();
72
  }
73
 
74
  if ( !$connected->have_posts() )
@@ -100,7 +107,7 @@ class P2P_Widget extends scbWidget {
100
 
101
  $label = "$from $arrow $to";
102
 
103
- $title = $ctype->set_direction( 'from' )->get_title();
104
 
105
  if ( $title )
106
  $label .= " ($title)";
7
  'listing' => 'connected'
8
  );
9
 
10
+ static function init() {
11
+ parent::init( __CLASS__, false, 'p2p' );
12
  }
13
 
14
  function __construct() {
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 ] = self::ctype_label( $ctype );
31
+ }
32
 
33
  echo html( 'p', $this->input( array(
34
  'type' => 'select',
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() )
107
 
108
  $label = "$from $arrow $to";
109
 
110
+ $title = $ctype->title[ 'from' ];
111
 
112
  if ( $title )
113
  $label .= " ($title)";
lang/posts-to-posts-de_DE.mo ADDED
Binary file
lang/posts-to-posts-de_DE.po ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2011
2
+ # This file is distributed under the same license as the package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: Posts 2 Posts 1.1.4\n"
6
+ "Report-Msgid-Bugs-To: http://wordpress.org/tag/p2p\n"
7
+ "POT-Creation-Date: 2011-12-01 16:28:04+00:00\n"
8
+ "MIME-Version: 1.0\n"
9
+ "Content-Type: text/plain; charset=UTF-8\n"
10
+ "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2012-01-24 13:03+0100\n"
12
+ "Last-Translator: ms-studio <code@ms-studio.net>\n"
13
+ "Language-Team: \n"
14
+ "X-Poedit-Language: German\n"
15
+ "X-Poedit-Country: GERMANY\n"
16
+
17
+ #: admin/box-factory.php:56
18
+ msgid " (from)"
19
+ msgstr " (von)"
20
+
21
+ #: admin/box-factory.php:57
22
+ msgid " (to)"
23
+ msgstr " (zu)"
24
+
25
+ #: admin/box.php:42
26
+ msgid "Are you sure you want to delete all connections?"
27
+ msgstr "Wollen Sie wirklich alle Verbindungen löschen?"
28
+
29
+ #: admin/box.php:115
30
+ msgid "Create connections:"
31
+ msgstr "Verbindungen erstellen:"
32
+
33
+ #: admin/box.php:128
34
+ msgid "Search"
35
+ msgstr "Suchen"
36
+
37
+ #: admin/box.php:136
38
+ msgid "View All"
39
+ msgstr "Alle Verbindungen"
40
+
41
+ #: admin/box.php:204
42
+ msgid "previous"
43
+ msgstr "vorherige"
44
+
45
+ #: admin/box.php:205
46
+ msgid "next"
47
+ msgstr "folgende"
48
+
49
+ #: admin/box.php:206
50
+ msgid "of"
51
+ msgstr "von"
52
+
53
+ #: admin/fields.php:16
54
+ msgid "Create connection"
55
+ msgstr "Verbindung erstellen"
56
+
57
+ #: admin/fields.php:30
58
+ msgid "Delete all connections"
59
+ msgstr "Alle Verbindungen löschen"
60
+
61
+ #: admin/fields.php:39
62
+ msgid "Delete connection"
63
+ msgstr "Verbindung löschen"
64
+
65
+ #: scb/AdminPage.php:165
66
+ msgid "Settings <strong>saved</strong>."
67
+ msgstr "Einstellungen <strong>gespeichert</strong>"
68
+
69
+ #: scb/AdminPage.php:178
70
+ #: scb/AdminPage.php:189
71
+ msgid "Save Changes"
72
+ msgstr "Änderungen speichern"
73
+
74
+ #: scb/AdminPage.php:350
75
+ msgid "Settings"
76
+ msgstr "Einstellungen"
77
+
78
+ #: core/type.php:65
79
+ msgid "Connected %s"
80
+ msgstr "Verbundene %s"
81
+
82
+ #: core/widget.php:15
83
+ msgid "Posts 2 Posts"
84
+ msgstr "Posts 2 Posts"
85
+
86
+ #: core/widget.php:16
87
+ msgid "A list of posts connected to the current post"
88
+ msgstr "Eine Liste von Artikeln, die mit diesem Artikel verbunden sind"
89
+
90
+ #: core/widget.php:37
91
+ msgid "Connection type:"
92
+ msgstr "Verbindungs-Typ:"
93
+
94
+ #: core/widget.php:41
95
+ msgid "Connection listing:"
96
+ msgstr "Verbindungs-Liste:"
97
+
98
+ #: core/widget.php:47
99
+ msgid "connected"
100
+ msgstr "verbunden"
101
+
102
+ #: core/widget.php:48
103
+ msgid "related"
104
+ msgstr "verwandt"
105
+
106
+ #: core/widget.php:73
107
+ msgid "Related %s"
108
+ msgstr "Verwandte %s"
109
+
110
+ #: core/storage.php:35
111
+ msgid "Upgraded %d connections."
112
+ msgstr "%d Verbindungen wurden ge-upgradet"
113
+
114
+ #: core/storage.php:38
115
+ msgid "The Posts 2 Posts connections need to be upgraded. <a href=\"%s\">Proceed.</a>"
116
+ msgstr "Die Posts 2 Posts Verbindungen benötigen ein Upgrade. <a href=\"%s\">Ausführen.</a>"
117
+
118
+ #: core/side.php:110
119
+ msgid "Users"
120
+ msgstr "Benutzer"
121
+
122
+ #: core/side.php:115
123
+ msgid "User"
124
+ msgstr "Benutzer"
125
+
126
+ #: core/side.php:116
127
+ msgid "Search Users"
128
+ msgstr "Benutzer suchen"
129
+
130
+ #: core/side.php:117
131
+ msgid "No users found."
132
+ msgstr "Keine Benutzer gefunden."
133
+
lang/posts-to-posts-ro_RO.mo CHANGED
Binary file
lang/posts-to-posts-ro_RO.po CHANGED
@@ -7,8 +7,8 @@ msgid ""
7
  msgstr ""
8
  "Project-Id-Version: Posts 2 Posts\n"
9
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/p2p\n"
10
- "POT-Creation-Date: 2011-11-13 13:52:29+00:00\n"
11
- "PO-Revision-Date: 2011-11-13 15:54+0200\n"
12
  "Last-Translator: scribu <mail@scribu.net>\n"
13
  "Language-Team: ROMANIAN <LL@li.org>\n"
14
  "Language: \n"
@@ -16,68 +16,83 @@ msgstr ""
16
  "Content-Type: text/plain; charset=UTF-8\n"
17
  "Content-Transfer-Encoding: 8bit\n"
18
 
19
- #: admin/box.php:74
 
 
 
 
 
 
 
 
20
  msgid "Are you sure you want to delete all connections?"
21
  msgstr "Sigur vreți să ștergeți toate conexiunile?"
22
 
23
- #: admin/box.php:147
24
  msgid "Create connections:"
25
  msgstr "Creează conexiuni:"
26
 
27
- #: admin/box.php:160
28
- msgid "Search"
29
- msgstr "Căutare"
30
-
31
- #: admin/box.php:168
32
  msgid "View All"
33
  msgstr "Vezi toate"
34
 
35
- #: admin/box.php:252
 
 
 
 
36
  msgid "previous"
37
  msgstr "anterioare"
38
 
39
- #: admin/box.php:253
40
  msgid "next"
41
  msgstr "următoare"
42
 
43
- #: admin/box.php:254
44
  msgid "of"
45
  msgstr "din"
46
 
47
- #: admin/fields.php:16
 
 
 
 
48
  msgid "Create connection"
49
  msgstr "Crează conexiune"
50
 
51
- #: admin/fields.php:30
52
  msgid "Delete all connections"
53
  msgstr "Șterge toate conexiunile"
54
 
55
- #: admin/fields.php:39
56
  msgid "Delete connection"
57
  msgstr "Șterge conexiune"
58
 
59
- #: scb/AdminPage.php:163
60
- msgid "Settings <strong>saved</strong>."
61
- msgstr "Setări <strong>salvate</strong>."
62
 
63
- #: scb/AdminPage.php:176
64
- #: scb/AdminPage.php:187
65
- msgid "Save Changes"
66
- msgstr "Salvează schimbările"
67
 
68
- #: scb/AdminPage.php:348
69
- msgid "Settings"
70
- msgstr "Setări"
71
 
72
- #: core/directed-type.php:59
73
- msgid " (from)"
74
- msgstr "(de la)"
75
 
76
- #: core/directed-type.php:60
77
- msgid " (to)"
78
- msgstr "(la)"
79
 
80
- #: core/type.php:86
 
 
 
 
81
  msgid "Connected %s"
82
  msgstr "%s conectate"
83
 
@@ -89,26 +104,39 @@ msgstr "Posts 2 Posts"
89
  msgid "A list of posts connected to the current post"
90
  msgstr "O lista de postări conectate la postarea curentă"
91
 
92
- #: core/widget.php:30
93
  msgid "Connection type:"
94
  msgstr "Tipul conexiunii:"
95
 
96
- #: core/widget.php:34
97
  msgid "Connection listing:"
98
  msgstr "Tipul conexiunii:"
99
 
100
- #: core/widget.php:40
101
  msgid "connected"
102
  msgstr "conectate"
103
 
104
- #: core/widget.php:41
105
  msgid "related"
106
  msgstr "înrudite"
107
 
108
- #: core/widget.php:66
109
  msgid "Related %s"
110
  msgstr "%s înrudite"
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  #~ msgid "Recent"
113
  #~ msgstr "Recente"
114
 
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-03-07 20:34:30+00:00\n"
11
+ "PO-Revision-Date: 2012-03-07 22:35+0200\n"
12
  "Last-Translator: scribu <mail@scribu.net>\n"
13
  "Language-Team: ROMANIAN <LL@li.org>\n"
14
  "Language: \n"
16
  "Content-Type: text/plain; charset=UTF-8\n"
17
  "Content-Transfer-Encoding: 8bit\n"
18
 
19
+ #: admin/box-factory.php:78
20
+ msgid " (from)"
21
+ msgstr "(de la)"
22
+
23
+ #: admin/box-factory.php:79
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:118
32
  msgid "Create connections:"
33
  msgstr "Creează conexiuni:"
34
 
35
+ #: admin/box.php:127
 
 
 
 
36
  msgid "View All"
37
  msgstr "Vezi toate"
38
 
39
+ #: admin/box.php:132
40
+ msgid "Search"
41
+ msgstr "Căutare"
42
+
43
+ #: admin/box.php:203
44
  msgid "previous"
45
  msgstr "anterioare"
46
 
47
+ #: admin/box.php:204
48
  msgid "next"
49
  msgstr "următoare"
50
 
51
+ #: admin/box.php:205
52
  msgid "of"
53
  msgstr "din"
54
 
55
+ #: admin/box.php:246
56
+ msgid "Can't create connection."
57
+ msgstr "Conexiunea nu poate fi creată."
58
+
59
+ #: admin/fields.php:14
60
  msgid "Create connection"
61
  msgstr "Crează conexiune"
62
 
63
+ #: admin/fields.php:27
64
  msgid "Delete all connections"
65
  msgstr "Șterge toate conexiunile"
66
 
67
+ #: admin/fields.php:36
68
  msgid "Delete connection"
69
  msgstr "Șterge conexiune"
70
 
71
+ #: core/side.php:140
72
+ msgid "Users"
73
+ msgstr "Utilizatori"
74
 
75
+ #: core/side.php:145
76
+ msgid "User"
77
+ msgstr "Utilizator"
 
78
 
79
+ #: core/side.php:146
80
+ msgid "Search Users"
81
+ msgstr "Caută utilizatori"
82
 
83
+ #: core/side.php:147
84
+ msgid "No users found."
85
+ msgstr "Niciun utilizator găsit."
86
 
87
+ #: core/storage.php:35
88
+ msgid "Upgraded %d connections."
89
+ msgstr "Am actualizat %d conexiuni."
90
 
91
+ #: core/storage.php:38
92
+ msgid "The Posts 2 Posts connections need to be upgraded. <a href=\"%s\">Proceed.</a>"
93
+ msgstr "Conexiunile Posts 2 Posts trebuie actualizate. <a href=\"%s\">Pornește.</a>"
94
+
95
+ #: core/type.php:77
96
  msgid "Connected %s"
97
  msgstr "%s conectate"
98
 
104
  msgid "A list of posts connected to the current post"
105
  msgstr "O lista de postări conectate la postarea curentă"
106
 
107
+ #: core/widget.php:37
108
  msgid "Connection type:"
109
  msgstr "Tipul conexiunii:"
110
 
111
+ #: core/widget.php:41
112
  msgid "Connection listing:"
113
  msgstr "Tipul conexiunii:"
114
 
115
+ #: core/widget.php:47
116
  msgid "connected"
117
  msgstr "conectate"
118
 
119
+ #: core/widget.php:48
120
  msgid "related"
121
  msgstr "înrudite"
122
 
123
+ #: core/widget.php:73
124
  msgid "Related %s"
125
  msgstr "%s înrudite"
126
 
127
+ #: scb/AdminPage.php:172
128
+ msgid "Settings <strong>saved</strong>."
129
+ msgstr "Setări <strong>salvate</strong>."
130
+
131
+ #: scb/AdminPage.php:185
132
+ #: scb/AdminPage.php:196
133
+ msgid "Save Changes"
134
+ msgstr "Salvează schimbările"
135
+
136
+ #: scb/AdminPage.php:360
137
+ msgid "Settings"
138
+ msgstr "Setări"
139
+
140
  #~ msgid "Recent"
141
  #~ msgstr "Recente"
142
 
lang/posts-to-posts-sv_SE.mo ADDED
Binary file
lang/posts-to-posts-sv_SE.po ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2010
2
+ # This file is distributed under the same license as the package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: Posts 2 Posts\n"
6
+ "Report-Msgid-Bugs-To: http://wordpress.org/tag/p2p\n"
7
+ "POT-Creation-Date: 2011-09-24 13:12:56+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: 2011-10-17 12:17+0100\n"
12
+ "Last-Translator: Joel Arvidsson <joel@oblador.se>\n"
13
+ "Language-Team: \n"
14
+ "X-Poedit-Language: Swedish\n"
15
+ "X-Poedit-Country: SWEDEN\n"
16
+ "X-Poedit-SourceCharset: utf-8\n"
17
+
18
+ #: scb/AdminPage.php:163
19
+ msgid "Settings <strong>saved</strong>."
20
+ msgstr "Inställningar <strong>sparade</strong>."
21
+
22
+ #: scb/AdminPage.php:176
23
+ #: scb/AdminPage.php:187
24
+ msgid "Save Changes"
25
+ msgstr "Spara ändringar"
26
+
27
+ #: scb/AdminPage.php:348
28
+ msgid "Settings"
29
+ msgstr "Inställningar"
30
+
31
+ #: admin/fields.php:13
32
+ msgid "Create connection"
33
+ msgstr "Skapa koppling"
34
+
35
+ #: admin/fields.php:25
36
+ msgid "Delete all connections"
37
+ msgstr "Radera alla kopplingar"
38
+
39
+ #: admin/fields.php:34
40
+ msgid "Delete connection"
41
+ msgstr "Radera koppling"
42
+
43
+ #: admin/box.php:59
44
+ msgid "Are you sure you want to delete all connections?"
45
+ msgstr "Är du säker på att du vill radera alla kopplingar?"
46
+
47
+ #: admin/box.php:69
48
+ msgid "Connected %s"
49
+ msgstr "Kopplad %s"
50
+
51
+ #: admin/box.php:116
52
+ msgid "Create connections:"
53
+ msgstr "Skapa kopplingar: "
54
+
55
+ #: admin/box.php:125
56
+ msgid "Search"
57
+ msgstr "Sök"
58
+
59
+ #: admin/box.php:133
60
+ msgid "View All"
61
+ msgstr "Visa alla"
62
+
63
+ #: admin/box.php:216
64
+ msgid "previous"
65
+ msgstr "föregående"
66
+
67
+ #: admin/box.php:217
68
+ msgid "next"
69
+ msgstr "nästa"
70
+
71
+ #: admin/box.php:218
72
+ msgid "of"
73
+ msgstr "av"
74
+
75
+ #: core/widget.php:14
76
+ msgid "Posts 2 Posts"
77
+ msgstr ""
78
+
79
+ #: core/widget.php:15
80
+ msgid "A list of posts connected to the current post"
81
+ msgstr "En lista över inlägg kopplade till aktuellt inlägg"
82
+
83
+ #: core/widget.php:29
84
+ msgid "Connection type:"
85
+ msgstr "Kopplingstyp: "
86
+
87
+ #: core/widget.php:53
88
+ msgid "Related %s"
89
+ msgstr "Relaterade %s"
90
+
lang/posts-to-posts-tr_TR.mo ADDED
Binary file
lang/posts-to-posts-tr_TR.po ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Translation of the WordPress plugin Posts 2 Posts 1.1.4 by scribu.
2
+ # Copyright (C) 2011 scribu
3
+ # This file is distributed under the same license as the Posts 2 Posts package.
4
+ # ÖNDER CEYLAN <ondercey@yahoo.com>, 2011.
5
+ #
6
+ msgid ""
7
+ msgstr ""
8
+ "Project-Id-Version: Posts 2 Posts 1.1.4\n"
9
+ "Report-Msgid-Bugs-To: http://wordpress.org/tag/posts-to-posts\n"
10
+ "POT-Creation-Date: 2011-03-30 21:56+0300\n"
11
+ "PO-Revision-Date: 2012-01-23 00:54+0200\n"
12
+ "Last-Translator: \n"
13
+ "Language-Team: \n"
14
+ "Language: \n"
15
+ "MIME-Version: 1.0\n"
16
+ "Content-Type: text/plain; charset=UTF-8\n"
17
+ "Content-Transfer-Encoding: 8bit\n"
18
+
19
+ #: admin/box.php:42
20
+ msgid "Are you sure you want to delete all connections?"
21
+ msgstr "Tüm bağlantıları kaldırmak istediğinizden emin misiniz?"
22
+
23
+ #: admin/box.php:120
24
+ msgid "Create connections:"
25
+ msgstr "Bağlantı yarat:"
26
+
27
+ #: admin/box.php:133
28
+ msgid "Search"
29
+ msgstr "Ara"
30
+
31
+ #: admin/box.php:141
32
+ msgid "View All"
33
+ msgstr "Tümünü Listele"
34
+
35
+ #: admin/box.php:209
36
+ msgid "previous"
37
+ msgstr "önceki"
38
+
39
+ #: admin/box.php:210
40
+ msgid "next"
41
+ msgstr "sonraki"
42
+
43
+ #: admin/box.php:211
44
+ msgid "of"
45
+ msgstr "arasından"
46
+
47
+ #: admin/fields.php:14
48
+ msgid "Create connection"
49
+ msgstr "Bağlantı ekle"
50
+
51
+ #: admin/fields.php:27
52
+ msgid "Delete all connections"
53
+ msgstr "Tüm bağlantıları kaldır"
54
+
55
+ #: admin/fields.php:36
56
+ msgid "Delete connection"
57
+ msgstr "Bağlantıyı kaldır"
58
+
59
+ #: core/side.php:141
60
+ msgid "Users"
61
+ msgstr "Kullanıcılar"
62
+
63
+ #: core/side.php:146
64
+ msgid "User"
65
+ msgstr "Kullanıcı"
66
+
67
+ #: core/side.php:147
68
+ msgid "Search Users"
69
+ msgstr "Kullanıcı Ara"
70
+
71
+ #: core/side.php:148
72
+ msgid "No users found."
73
+ msgstr "Kullanıcı bulunamadı."
74
+
75
+ #: core/storage.php:35
76
+ msgid "Upgraded %d connections."
77
+ msgstr "%d bağlantı güncellendi."
78
+
79
+ #: core/storage.php:38
80
+ msgid "The Posts 2 Posts connections need to be upgraded. <a href='%s'>Proceed.</a>"
81
+ msgstr "Posts 2 Posts bağlantılarının güncellenmesi gerekiyor. <a href='%s'>Devam et.</a>"
82
+
83
+ #: core/type.php:73
84
+ msgid "Connected %s"
85
+ msgstr "%s Bağlandı"
86
+
87
+ #: core/widget.php:15
88
+ msgid "Posts 2 Posts"
89
+ msgstr "Posts 2 Posts"
90
+
91
+ #: core/widget.php:16
92
+ msgid "A list of posts connected to the current post"
93
+ msgstr "Mevcut sayfayla bağlantılı sayfalar"
94
+
95
+ #: core/widget.php:37
96
+ msgid "Connection type:"
97
+ msgstr "Bağlantı türü:"
98
+
99
+ #: core/widget.php:41
100
+ msgid "Connection listing:"
101
+ msgstr "Bağlantı listesi:"
102
+
103
+ #: core/widget.php:47
104
+ msgid "connected"
105
+ msgstr "bağlı"
106
+
107
+ #: core/widget.php:48
108
+ msgid "related"
109
+ msgstr "benzer"
110
+
111
+ #: core/widget.php:73
112
+ msgid "Related %s"
113
+ msgstr "Benzer %s"
114
+
lang/posts-to-posts.pot CHANGED
@@ -1,78 +1,96 @@
1
- # Copyright (C) 2010
2
  # This file is distributed under the same license as the package.
3
  msgid ""
4
  msgstr ""
5
  "Project-Id-Version: \n"
6
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/p2p\n"
7
- "POT-Creation-Date: 2011-11-13 13:52:29+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
11
- "PO-Revision-Date: 2010-MO-DA HO:MI+ZONE\n"
12
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
14
 
15
- #: admin/box.php:74
 
 
 
 
 
 
 
 
16
  msgid "Are you sure you want to delete all connections?"
17
  msgstr ""
18
 
19
- #: admin/box.php:147
20
  msgid "Create connections:"
21
  msgstr ""
22
 
23
- #: admin/box.php:160
24
- msgid "Search"
25
  msgstr ""
26
 
27
- #: admin/box.php:168
28
- msgid "View All"
29
  msgstr ""
30
 
31
- #: admin/box.php:252
32
  msgid "previous"
33
  msgstr ""
34
 
35
- #: admin/box.php:253
36
  msgid "next"
37
  msgstr ""
38
 
39
- #: admin/box.php:254
40
  msgid "of"
41
  msgstr ""
42
 
43
- #: admin/fields.php:16
 
 
 
 
44
  msgid "Create connection"
45
  msgstr ""
46
 
47
- #: admin/fields.php:30
48
  msgid "Delete all connections"
49
  msgstr ""
50
 
51
- #: admin/fields.php:39
52
  msgid "Delete connection"
53
  msgstr ""
54
 
55
- #: scb/AdminPage.php:163
56
- msgid "Settings <strong>saved</strong>."
57
  msgstr ""
58
 
59
- #: scb/AdminPage.php:176 scb/AdminPage.php:187
60
- msgid "Save Changes"
61
  msgstr ""
62
 
63
- #: scb/AdminPage.php:348
64
- msgid "Settings"
65
  msgstr ""
66
 
67
- #: core/directed-type.php:59
68
- msgid " (from)"
69
  msgstr ""
70
 
71
- #: core/directed-type.php:60
72
- msgid " (to)"
 
 
 
 
 
 
73
  msgstr ""
74
 
75
- #: core/type.php:86
76
  msgid "Connected %s"
77
  msgstr ""
78
 
@@ -84,22 +102,34 @@ msgstr ""
84
  msgid "A list of posts connected to the current post"
85
  msgstr ""
86
 
87
- #: core/widget.php:30
88
  msgid "Connection type:"
89
  msgstr ""
90
 
91
- #: core/widget.php:34
92
  msgid "Connection listing:"
93
  msgstr ""
94
 
95
- #: core/widget.php:40
96
  msgid "connected"
97
  msgstr ""
98
 
99
- #: core/widget.php:41
100
  msgid "related"
101
  msgstr ""
102
 
103
- #: core/widget.php:66
104
  msgid "Related %s"
105
  msgstr ""
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2012
2
  # This file is distributed under the same license as the package.
3
  msgid ""
4
  msgstr ""
5
  "Project-Id-Version: \n"
6
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/p2p\n"
7
+ "POT-Creation-Date: 2012-03-07 20:34:30+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2012-MO-DA HO:MI+ZONE\n"
12
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
14
 
15
+ #: admin/box-factory.php:78
16
+ msgid " (from)"
17
+ msgstr ""
18
+
19
+ #: admin/box-factory.php:79
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:118
28
  msgid "Create connections:"
29
  msgstr ""
30
 
31
+ #: admin/box.php:127
32
+ msgid "View All"
33
  msgstr ""
34
 
35
+ #: admin/box.php:132
36
+ msgid "Search"
37
  msgstr ""
38
 
39
+ #: admin/box.php:203
40
  msgid "previous"
41
  msgstr ""
42
 
43
+ #: admin/box.php:204
44
  msgid "next"
45
  msgstr ""
46
 
47
+ #: admin/box.php:205
48
  msgid "of"
49
  msgstr ""
50
 
51
+ #: admin/box.php:246
52
+ msgid "Can't create connection."
53
+ msgstr ""
54
+
55
+ #: admin/fields.php:14
56
  msgid "Create connection"
57
  msgstr ""
58
 
59
+ #: admin/fields.php:27
60
  msgid "Delete all connections"
61
  msgstr ""
62
 
63
+ #: admin/fields.php:36
64
  msgid "Delete connection"
65
  msgstr ""
66
 
67
+ #: core/side.php:140
68
+ msgid "Users"
69
  msgstr ""
70
 
71
+ #: core/side.php:145
72
+ msgid "User"
73
  msgstr ""
74
 
75
+ #: core/side.php:146
76
+ msgid "Search Users"
77
  msgstr ""
78
 
79
+ #: core/side.php:147
80
+ msgid "No users found."
81
  msgstr ""
82
 
83
+ #: core/storage.php:35
84
+ msgid "Upgraded %d connections."
85
+ msgstr ""
86
+
87
+ #: core/storage.php:38
88
+ msgid ""
89
+ "The Posts 2 Posts connections need to be upgraded. <a href=\"%s\">Proceed.</"
90
+ "a>"
91
  msgstr ""
92
 
93
+ #: core/type.php:77
94
  msgid "Connected %s"
95
  msgstr ""
96
 
102
  msgid "A list of posts connected to the current post"
103
  msgstr ""
104
 
105
+ #: core/widget.php:37
106
  msgid "Connection type:"
107
  msgstr ""
108
 
109
+ #: core/widget.php:41
110
  msgid "Connection listing:"
111
  msgstr ""
112
 
113
+ #: core/widget.php:47
114
  msgid "connected"
115
  msgstr ""
116
 
117
+ #: core/widget.php:48
118
  msgid "related"
119
  msgstr ""
120
 
121
+ #: core/widget.php:73
122
  msgid "Related %s"
123
  msgstr ""
124
+
125
+ #: scb/AdminPage.php:172
126
+ msgid "Settings <strong>saved</strong>."
127
+ msgstr ""
128
+
129
+ #: scb/AdminPage.php:185 scb/AdminPage.php:196
130
+ msgid "Save Changes"
131
+ msgstr ""
132
+
133
+ #: scb/AdminPage.php:360
134
+ msgid "Settings"
135
+ msgstr ""
mustache/Mustache.php CHANGED
@@ -14,7 +14,7 @@
14
  */
15
  class Mustache {
16
 
17
- const VERSION = '0.8.0';
18
  const SPEC_VERSION = '1.1.2';
19
 
20
  /**
@@ -90,9 +90,18 @@ class Mustache {
90
  * // opening and closing delimiters, as an array or a space-separated string
91
  * 'delimiters' => '<% %>',
92
  *
93
- * // an array of pragmas to enable
94
  * 'pragmas' => array(
95
- * Mustache::PRAGMA_UNESCAPED
 
 
 
 
 
 
 
 
 
96
  * ),
97
  * );
98
  *
@@ -132,13 +141,19 @@ class Mustache {
132
  }
133
 
134
  if (isset($options['pragmas'])) {
135
- foreach ($options['pragmas'] as $pragma_name) {
136
- if (!in_array($pragma_name, $this->_pragmasImplemented)) {
137
  throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
138
  }
139
  }
140
  $this->_pragmas = $options['pragmas'];
141
  }
 
 
 
 
 
 
142
  }
143
 
144
  /**
@@ -189,7 +204,7 @@ class Mustache {
189
  }
190
 
191
  $template = $this->_renderPragmas($template);
192
- $template = $this->_renderTemplate($template, $this->_context);
193
 
194
  $this->_otag = $otag_orig;
195
  $this->_ctag = $ctag_orig;
@@ -403,7 +418,11 @@ class Mustache {
403
  $options_string = $matches['options_string'];
404
 
405
  if (!in_array($pragma_name, $this->_pragmasImplemented)) {
406
- throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
 
 
 
 
407
  }
408
 
409
  $options = array();
@@ -448,7 +467,9 @@ class Mustache {
448
  */
449
  protected function _getPragmaOptions($pragma_name) {
450
  if (!$this->_hasPragma($pragma_name)) {
451
- throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
 
 
452
  }
453
 
454
  return (is_array($this->_localPragmas[$pragma_name])) ? $this->_localPragmas[$pragma_name] : array();
@@ -767,7 +788,7 @@ class Mustache {
767
  $first = array_shift($chunks);
768
 
769
  $ret = $this->_findVariableInContext($first, $this->_context);
770
- while ($next = array_shift($chunks)) {
771
  // Slice off a chunk of context for dot notation traversal.
772
  $c = array($ret);
773
  $ret = $this->_findVariableInContext($next, $c);
@@ -819,10 +840,7 @@ class Mustache {
819
  * @return string
820
  */
821
  protected function _getPartial($tag_name) {
822
- if (
823
- (is_array($this->_partials) || $this->_partials instanceof ArrayAccess)
824
- && isset($this->_partials[$tag_name])
825
- ) {
826
  return $this->_partials[$tag_name];
827
  }
828
 
14
  */
15
  class Mustache {
16
 
17
+ const VERSION = '1.0.0';
18
  const SPEC_VERSION = '1.1.2';
19
 
20
  /**
90
  * // opening and closing delimiters, as an array or a space-separated string
91
  * 'delimiters' => '<% %>',
92
  *
93
+ * // an array of pragmas to enable/disable
94
  * 'pragmas' => array(
95
+ * Mustache::PRAGMA_UNESCAPED => true
96
+ * ),
97
+ *
98
+ * // an array of thrown exceptions to enable/disable
99
+ * 'throws_exceptions' => array(
100
+ * MustacheException::UNKNOWN_VARIABLE => false,
101
+ * MustacheException::UNCLOSED_SECTION => true,
102
+ * MustacheException::UNEXPECTED_CLOSE_SECTION => true,
103
+ * MustacheException::UNKNOWN_PARTIAL => false,
104
+ * MustacheException::UNKNOWN_PRAGMA => true,
105
  * ),
106
  * );
107
  *
141
  }
142
 
143
  if (isset($options['pragmas'])) {
144
+ foreach ($options['pragmas'] as $pragma_name => $pragma_value) {
145
+ if (!in_array($pragma_name, $this->_pragmasImplemented, true)) {
146
  throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
147
  }
148
  }
149
  $this->_pragmas = $options['pragmas'];
150
  }
151
+
152
+ if (isset($options['throws_exceptions'])) {
153
+ foreach ($options['throws_exceptions'] as $exception => $value) {
154
+ $this->_throwsExceptions[$exception] = $value;
155
+ }
156
+ }
157
  }
158
 
159
  /**
204
  }
205
 
206
  $template = $this->_renderPragmas($template);
207
+ $template = $this->_renderTemplate($template);
208
 
209
  $this->_otag = $otag_orig;
210
  $this->_ctag = $ctag_orig;
418
  $options_string = $matches['options_string'];
419
 
420
  if (!in_array($pragma_name, $this->_pragmasImplemented)) {
421
+ if ($this->_throwsException(MustacheException::UNKNOWN_PRAGMA)) {
422
+ throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
423
+ } else {
424
+ return '';
425
+ }
426
  }
427
 
428
  $options = array();
467
  */
468
  protected function _getPragmaOptions($pragma_name) {
469
  if (!$this->_hasPragma($pragma_name)) {
470
+ if ($this->_throwsException(MustacheException::UNKNOWN_PRAGMA)) {
471
+ throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
472
+ }
473
  }
474
 
475
  return (is_array($this->_localPragmas[$pragma_name])) ? $this->_localPragmas[$pragma_name] : array();
788
  $first = array_shift($chunks);
789
 
790
  $ret = $this->_findVariableInContext($first, $this->_context);
791
+ foreach ($chunks as $next) {
792
  // Slice off a chunk of context for dot notation traversal.
793
  $c = array($ret);
794
  $ret = $this->_findVariableInContext($next, $c);
840
  * @return string
841
  */
842
  protected function _getPartial($tag_name) {
843
+ if ((is_array($this->_partials) || $this->_partials instanceof ArrayAccess) && isset($this->_partials[$tag_name])) {
 
 
 
844
  return $this->_partials[$tag_name];
845
  }
846
 
mustache/README.markdown CHANGED
@@ -9,73 +9,79 @@ Usage
9
 
10
  A quick example:
11
 
12
- <?php
13
- include('Mustache.php');
14
- $m = new Mustache;
15
- echo $m->render('Hello {{planet}}', array('planet' => 'World!'));
16
- // "Hello World!"
17
- ?>
 
18
 
19
 
20
  And a more in-depth example--this is the canonical Mustache template:
21
 
22
- Hello {{name}}
23
- You have just won ${{value}}!
24
- {{#in_ca}}
25
- Well, ${{taxed_value}}, after taxes.
26
- {{/in_ca}}
 
 
27
 
28
 
29
  Along with the associated Mustache class:
30
 
31
- <?php
32
- class Chris extends Mustache {
33
- public $name = "Chris";
34
- public $value = 10000;
 
35
 
36
- public function taxed_value() {
37
- return $this->value - ($this->value * 0.4);
38
- }
39
-
40
- public $in_ca = true;
41
  }
42
 
 
 
 
 
43
 
44
  Render it like so:
45
 
46
- <?php
47
- $c = new Chris;
48
- echo $chris->render($template);
49
- ?>
 
50
 
51
 
52
  Here's the same thing, a different way:
53
 
54
  Create a view object--which could also be an associative array, but those don't do functions quite as well:
55
 
56
- <?php
57
- class Chris {
58
- public $name = "Chris";
59
- public $value = 10000;
60
-
61
- public function taxed_value() {
62
- return $this->value - ($this->value * 0.4);
63
- }
64
-
65
- public $in_ca = true;
66
- }
67
- ?>
68
 
 
 
 
69
 
70
- And render it:
 
 
71
 
72
- <?php
73
- $chris = new Chris;
74
- $m = new Mustache;
75
- echo $m->render($template, $chris);
76
- ?>
77
 
 
78
 
 
 
 
 
 
 
79
 
80
 
81
  Known Issues
9
 
10
  A quick example:
11
 
12
+ ```php
13
+ <?php
14
+ include('Mustache.php');
15
+ $m = new Mustache;
16
+ echo $m->render('Hello {{planet}}', array('planet' => 'World!'));
17
+ // "Hello World!"
18
+ ```
19
 
20
 
21
  And a more in-depth example--this is the canonical Mustache template:
22
 
23
+ ```
24
+ Hello {{name}}
25
+ You have just won ${{value}}!
26
+ {{#in_ca}}
27
+ Well, ${{taxed_value}}, after taxes.
28
+ {{/in_ca}}
29
+ ```
30
 
31
 
32
  Along with the associated Mustache class:
33
 
34
+ ```php
35
+ <?php
36
+ class Chris extends Mustache {
37
+ public $name = "Chris";
38
+ public $value = 10000;
39
 
40
+ public function taxed_value() {
41
+ return $this->value - ($this->value * 0.4);
 
 
 
42
  }
43
 
44
+ public $in_ca = true;
45
+ }
46
+ ```
47
+
48
 
49
  Render it like so:
50
 
51
+ ```php
52
+ <?php
53
+ $chris = new Chris;
54
+ echo $chris->render($template);
55
+ ```
56
 
57
 
58
  Here's the same thing, a different way:
59
 
60
  Create a view object--which could also be an associative array, but those don't do functions quite as well:
61
 
62
+ ```php
63
+ <?php
64
+ class Chris {
65
+ public $name = "Chris";
66
+ public $value = 10000;
 
 
 
 
 
 
 
67
 
68
+ public function taxed_value() {
69
+ return $this->value - ($this->value * 0.4);
70
+ }
71
 
72
+ public $in_ca = true;
73
+ }
74
+ ```
75
 
 
 
 
 
 
76
 
77
+ And render it:
78
 
79
+ ```php
80
+ <?php
81
+ $chris = new Chris;
82
+ $m = new Mustache;
83
+ echo $m->render($template, $chris);
84
+ ```
85
 
86
 
87
  Known Issues
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.0.1
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
- Copyright (C) 2010-2011 Cristi Burcă (mail@scribu.net)
14
 
15
  This program is free software; you can redistribute it and/or modify
16
  it under the terms of the GNU General Public License as published by
@@ -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.0.1' );
30
 
31
  define( 'P2P_TEXTDOMAIN', 'posts-to-posts' );
32
 
@@ -38,12 +38,12 @@ function _p2p_init() {
38
  load_plugin_textdomain( P2P_TEXTDOMAIN, '', basename( $base ) . '/lang' );
39
 
40
  _p2p_load_files( "$base/core", array(
41
- 'storage', 'query', 'url-query',
42
- 'util', 'type', 'directed-type', 'ordered-type',
43
  'api', 'widget'
44
  ) );
45
 
46
- P2P_Widget::init( __FILE__ );
47
 
48
  if ( is_admin() ) {
49
  _p2p_load_files( "$base/admin", array(
@@ -52,6 +52,8 @@ function _p2p_init() {
52
  'column-factory', 'column'
53
  ) );
54
  }
 
 
55
  }
56
  scb_init( '_p2p_init' );
57
 
2
  /*
3
  Plugin Name: Posts 2 Posts
4
  Description: Create many-to-many relationships between all types of posts.
5
+ Version: 1.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
+ Copyright (C) 2010-2012 Cristi Burcă (mail@scribu.net)
14
 
15
  This program is free software; you can redistribute it and/or modify
16
  it under the terms of the GNU General Public License as published by
26
  along with this program. If not, see <http://www.gnu.org/licenses/>.
27
  */
28
 
29
+ define( 'P2P_PLUGIN_VERSION', '1.1.6' );
30
 
31
  define( 'P2P_TEXTDOMAIN', 'posts-to-posts' );
32
 
38
  load_plugin_textdomain( P2P_TEXTDOMAIN, '', basename( $base ) . '/lang' );
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(
52
  'column-factory', 'column'
53
  ) );
54
  }
55
+
56
+ register_uninstall_hook( __FILE__, array( 'P2P_Storage', 'uninstall' ) );
57
  }
58
  scb_init( '_p2p_init' );
59
 
readme.txt CHANGED
@@ -1,22 +1,25 @@
1
  === Posts 2 Posts ===
2
  Contributors: scribu, ciobi
3
- Tags: cms, custom post types, relationships, many-to-many
4
  Requires at least: 3.2
5
  Tested up to: 3.3
6
- Stable tag: 1.0.1
7
 
8
- Create connections between posts
9
 
10
  == Description ==
11
 
12
- This plugin allows you to create many-to-many relationships between posts of any type: post, page, custom etc.
13
-
14
- A few example use cases:
15
 
16
  * post series
17
  * manually curated lists of related posts
18
  * 'actor' posts connected to 'movie' posts
19
 
 
 
 
 
 
20
  etc.
21
 
22
  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)
@@ -47,6 +50,51 @@ Make sure your host is running PHP 5. The only foolproof way to do this is to ad
47
 
48
  == Changelog ==
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  = 1.0.1 =
51
  * don't show metabox at all if user doesn't have the required capability
52
  * fix checkbox handling when there are no other input fields
1
  === Posts 2 Posts ===
2
  Contributors: scribu, ciobi
3
+ Tags: connections, custom post types, relationships, many-to-many, users
4
  Requires at least: 3.2
5
  Tested up to: 3.3
6
+ Stable tag: 1.1.6
7
 
8
+ Efficient many-to-many connections between posts, pages, custom post types, users.
9
 
10
  == Description ==
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
+
20
+ * favorite posts of users
21
+ * multiple authors per post
22
+
23
  etc.
24
 
25
  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)
50
 
51
  == Changelog ==
52
 
53
+ = 1.1.6 =
54
+ * convert "View All" tab into button
55
+ * refresh candidate list after deleting a connection
56
+ * fix cardinality check
57
+ * introduce 'p2p_connection_type_args' filter
58
+ * make 'connected_type' accept an array of connection type names
59
+ * inadvertently remove support for queries without 'connected_type' parameter
60
+
61
+ = 1.1.5 =
62
+ * added P2P_Connection_Type->replace() method
63
+ * added 'self_connections' flag to p2p_register_connection_type()
64
+ * made P2P_Connection_Type->each_connected() work for posts-to-users connections
65
+ * made admin list table columns work for posts-to-users connections
66
+ * fixed 'from_labels' and 'to_labels' parameters
67
+ * fixed search being limited only to post titles
68
+
69
+ = 1.1.4 =
70
+ * show attachment thumbnail instead of title
71
+ * merged 'from_object' into 'from' and 'to_object' into 'to'
72
+ * made posts-to-users queries respect 'to_query_vars' args
73
+ * added $prop_name parameter to P2P_Type::each_connected()
74
+ * fixed connection field name conflict
75
+
76
+ = 1.1.3 =
77
+ * fixed regression related to posts-to-users direction
78
+ * fixed admin columns overwriting each other
79
+ * fixed incorrect direction in admin column links
80
+ * added notices when connection type is not properly defined
81
+
82
+ = 1.1.2 =
83
+ * fixed fields not being saved for posts-to-users connections
84
+ * fixed missing "New Post" tab in admin box
85
+ * fixed notice when deleting post
86
+
87
+ = 1.1.1 =
88
+ * fixed faulty scbFramework loading
89
+ * simplified syntax for defining posts-to-users connection types
90
+
91
+ = 1.1 =
92
+ * add p2p_type column to the wp_p2p table
93
+ * new low-level api: p2p_create_connection(), p2p_get_connections(), p2p_delete_connections(), p2p_connection_exists()
94
+ * support posts-to-users and users-to-posts connection types in the admin
95
+ * add 'from_labels' and 'to_labels' args to p2p_register_connection_type()
96
+ * [more info](http://scribu.net/wordpress/posts-to-posts/p2p-1-1.html)
97
+
98
  = 1.0.1 =
99
  * don't show metabox at all if user doesn't have the required capability
100
  * fix checkbox handling when there are no other input fields
scb/AdminPage.php CHANGED
@@ -80,13 +80,10 @@ abstract class scbAdminPage {
80
 
81
 
82
  // Constructor
83
- function __construct( $file, $options = NULL ) {
84
  if ( is_a( $options, 'scbOptions' ) )
85
  $this->options = $options;
86
 
87
- $this->file = $file;
88
- $this->plugin_url = plugin_dir_url( $file );
89
-
90
  $this->setup();
91
  $this->check_args();
92
 
@@ -99,13 +96,25 @@ abstract class scbAdminPage {
99
  add_action( 'admin_menu', array( $this, 'page_init' ), $this->args['admin_action_priority'] );
100
  add_filter( 'contextual_help', array( $this, '_contextual_help' ), 10, 2 );
101
 
102
- if ( $this->args['action_link'] )
103
- add_filter( 'plugin_action_links_' . plugin_basename( $file ), array( $this, '_action_link' ) );
 
 
 
 
 
104
  }
105
 
106
  // This is where all the page args can be set
107
  function setup(){}
108
 
 
 
 
 
 
 
 
109
  // This is where the css and js go
110
  // Both wp_enqueue_*() and inline code can be added
111
  function page_head(){}
@@ -162,7 +171,7 @@ abstract class scbAdminPage {
162
  if ( empty( $msg ) )
163
  $msg = __( 'Settings <strong>saved</strong>.', $this->textdomain );
164
 
165
- echo "<div class='$class fade'><p>$msg</p></div>\n";
166
  }
167
 
168
 
@@ -187,18 +196,19 @@ abstract class scbAdminPage {
187
  $value = __( 'Save Changes', $this->textdomain );
188
  }
189
 
190
- $input_args = array( 'type' => 'submit',
191
- 'names' => $action,
192
- 'values' => $value,
 
193
  'extra' => '',
194
- 'desc' => false );
 
 
195
 
196
  if ( ! empty( $class ) )
197
- $input_args['extra'] = "class='{$class}'";
198
-
199
- $output = "<p class='submit'>\n" . scbForms::input( $input_args ) . "</p>\n";
200
 
201
- return $output;
202
  }
203
 
204
  /*
@@ -319,6 +329,8 @@ abstract class scbAdminPage {
319
  if ( ! $this->pagehook )
320
  return;
321
 
 
 
322
  if ( $ajax_submit ) {
323
  $this->ajax_response();
324
  add_action( 'admin_footer', array( $this, 'ajax_submit' ), 20 );
80
 
81
 
82
  // Constructor
83
+ function __construct( $file = false, $options = null ) {
84
  if ( is_a( $options, 'scbOptions' ) )
85
  $this->options = $options;
86
 
 
 
 
87
  $this->setup();
88
  $this->check_args();
89
 
96
  add_action( 'admin_menu', array( $this, 'page_init' ), $this->args['admin_action_priority'] );
97
  add_filter( 'contextual_help', array( $this, '_contextual_help' ), 10, 2 );
98
 
99
+ if ( $file ) {
100
+ $this->file = $file;
101
+ $this->plugin_url = plugin_dir_url( $file );
102
+
103
+ if ( $this->args['action_link'] )
104
+ add_filter( 'plugin_action_links_' . plugin_basename( $file ), array( $this, '_action_link' ) );
105
+ }
106
  }
107
 
108
  // This is where all the page args can be set
109
  function setup(){}
110
 
111
+ /**
112
+ * Called when the page is loaded, but before any rendering.
113
+ *
114
+ * Useful for calling $screen->add_help_tab() etc.
115
+ */
116
+ function page_loaded() {}
117
+
118
  // This is where the css and js go
119
  // Both wp_enqueue_*() and inline code can be added
120
  function page_head(){}
171
  if ( empty( $msg ) )
172
  $msg = __( 'Settings <strong>saved</strong>.', $this->textdomain );
173
 
174
+ echo scb_admin_notice( $msg, $class );
175
  }
176
 
177
 
196
  $value = __( 'Save Changes', $this->textdomain );
197
  }
198
 
199
+ $input_args = array(
200
+ 'type' => 'submit',
201
+ 'name' => $action,
202
+ 'value' => $value,
203
  'extra' => '',
204
+ 'desc' => false,
205
+ 'wrap' => html( 'p class="submit"', scbForms::TOKEN )
206
+ );
207
 
208
  if ( ! empty( $class ) )
209
+ $input_args['extra'] = compact( 'class' );
 
 
210
 
211
+ return scbForms::input( $input_args );
212
  }
213
 
214
  /*
329
  if ( ! $this->pagehook )
330
  return;
331
 
332
+ add_action( 'load-' . $this->pagehook, array( $this, 'page_loaded' ) );
333
+
334
  if ( $ajax_submit ) {
335
  $this->ajax_response();
336
  add_action( 'admin_footer', array( $this, 'ajax_submit' ), 20 );
scb/BoxesPage.php CHANGED
@@ -11,10 +11,9 @@ abstract class scbBoxesPage extends scbAdminPage {
11
  */
12
  protected $boxes = array();
13
 
14
- function __construct( $file, $options = null ) {
15
  parent::__construct( $file, $options );
16
 
17
- // too late
18
  scbUtil::add_uninstall_hook( $this->file, array( $this, 'uninstall' ) );
19
  }
20
 
@@ -167,8 +166,11 @@ abstract class scbBoxesPage extends scbAdminPage {
167
  ) );
168
 
169
  $registered = array();
170
- foreach( $this->boxes as $box_args ) {
171
- @list( $name, $title, $context, $priority, $args ) = $box_args;
 
 
 
172
 
173
  if ( empty( $title ) )
174
  $title = ucfirst( $name );
11
  */
12
  protected $boxes = array();
13
 
14
+ function __construct( $file = false, $options = null ) {
15
  parent::__construct( $file, $options );
16
 
 
17
  scbUtil::add_uninstall_hook( $this->file, array( $this, 'uninstall' ) );
18
  }
19
 
166
  ) );
167
 
168
  $registered = array();
169
+ foreach ( $this->boxes as $box_args ) {
170
+ foreach ( array( 'name', 'title', 'context', 'priority', 'args' ) as $i => $arg ) {
171
+ if ( isset( $box_args[$i] ) )
172
+ $$arg = $box_args[$i];
173
+ }
174
 
175
  if ( empty( $title ) )
176
  $title = ucfirst( $name );
scb/Cron.php CHANGED
@@ -19,7 +19,7 @@ class scbCron {
19
  string $schedule OR number $interval
20
  array $callback_args (optional)
21
  */
22
- function __construct( $file, $args ) {
23
  extract( $args, EXTR_SKIP );
24
 
25
  // Set time & schedule
19
  string $schedule OR number $interval
20
  array $callback_args (optional)
21
  */
22
+ function __construct( $file = false, $args ) {
23
  extract( $args, EXTR_SKIP );
24
 
25
  // Set time & schedule
scb/Forms.php CHANGED
@@ -4,35 +4,51 @@
4
 
5
  class scbForms {
6
 
7
- const token = '%input%';
8
 
9
  protected static $cur_name;
10
 
11
- static function input( $args, $formdata = false ) {
12
- // setle on singular keys
13
- foreach ( array( 'name', 'value' ) as $key ) {
14
- $old = $key . 's';
15
-
16
- if ( isset( $args[$old] ) ) {
17
- $args[$key] = $args[$old];
18
- unset( $args[$old] );
 
 
 
 
 
 
 
19
  }
20
  }
21
 
22
- if ( !empty( $formdata ) ) {
 
 
 
 
23
  $form = new scbForm( $formdata );
24
  return $form->input( $args );
25
  }
26
 
27
- if ( empty( $args['name'] ) )
28
  return trigger_error( 'Empty name', E_USER_WARNING );
 
29
 
30
  $args = wp_parse_args( $args, array(
31
  'desc' => '',
32
- 'desc_pos' => '',
 
33
  ) );
34
 
35
- $val_is_array = isset( $args['value'] ) && is_array( $args['value'] );
 
 
 
36
 
37
  if ( isset( $args['extra'] ) && !is_array( $args['extra'] ) )
38
  $args['extra'] = shortcode_parse_atts( $args['extra'] );
@@ -42,20 +58,19 @@ class scbForms {
42
  switch ( $args['type'] ) {
43
  case 'select':
44
  case 'radio':
45
- if ( ! $val_is_array )
46
- return trigger_error( "'value' argument is expected to be an array", E_USER_WARNING );
47
-
48
- return self::_single_choice( $args );
49
  break;
50
  case 'checkbox':
51
- if ( $val_is_array )
52
- return self::_multiple_choice( $args );
53
  else
54
- return self::_checkbox( $args );
55
  break;
56
  default:
57
- return self::_input( $args );
58
  }
 
 
59
  }
60
 
61
 
@@ -168,7 +183,7 @@ class scbForms {
168
  $checked = array();
169
 
170
  $opts = '';
171
- foreach ( $value as $value => $title ) {
172
  if ( empty( $value ) || empty( $title ) )
173
  continue;
174
 
@@ -177,21 +192,22 @@ class scbForms {
177
  'value' => $value,
178
  'checked' => in_array( $value, $checked ),
179
  'desc' => $title,
180
- 'desc_pos' => $desc_pos
181
  ) );
182
  }
183
 
184
- return $opts;
185
  }
186
 
187
  private static function _expand_values( &$args ) {
188
- $value =& $args['value'];
189
 
190
- if ( !empty( $value ) && !self::is_associative( $value ) ) {
191
  if ( is_array( $args['desc'] ) ) {
192
- $value = array_combine( $value, $args['desc'] ); // back-compat
193
- } elseif ( !$args['numeric'] ) {
194
- $value = array_combine( $value, $value );
 
195
  }
196
  }
197
  }
@@ -200,11 +216,12 @@ class scbForms {
200
  extract( $args );
201
 
202
  if ( array( 'foo' ) == $selected ) {
203
- $selected = key( $value ); // radio buttons should always have one option selected
 
204
  }
205
 
206
  $opts = '';
207
- foreach ( $value as $value => $title ) {
208
  if ( empty( $value ) || empty( $title ) )
209
  continue;
210
 
@@ -213,11 +230,11 @@ class scbForms {
213
  'value' => $value,
214
  'checked' => ( (string) $value == (string) $selected ),
215
  'desc' => $title,
216
- 'desc_pos' => $desc_pos
217
  ) );
218
  }
219
 
220
- return $opts;
221
  }
222
 
223
  private static function _select( $args ) {
@@ -236,7 +253,7 @@ class scbForms {
236
  );
237
  }
238
 
239
- foreach ( $value as $value => $title ) {
240
  if ( empty( $value ) || empty( $title ) )
241
  continue;
242
 
@@ -322,27 +339,17 @@ class scbForms {
322
  }
323
 
324
  private static function add_label( $input, $desc, $desc_pos ) {
325
- if ( empty( $desc_pos ) )
326
- $desc_pos = 'after';
327
-
328
- $label = '';
329
- if ( false === strpos( $desc, self::token ) ) {
330
- switch ( $desc_pos ) {
331
- case 'before': $label = $desc . ' ' . self::token; break;
332
- case 'after': $label = self::token . ' ' . $desc;
333
- }
334
- } else {
335
- $label = $desc;
336
- }
337
-
338
- $label = trim( str_replace( self::token, $input, $label ) );
339
 
 
340
  if ( empty( $desc ) )
341
- $output = $input;
342
- else
343
- $output = html( 'label', $label );
344
 
345
- return $output . "\n";
 
 
 
346
  }
347
 
348
 
@@ -388,12 +395,102 @@ class scbForms {
388
  return $value;
389
  }
390
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  private static function is_associative( $array ) {
392
  $keys = array_keys( $array );
393
  return array_keys( $keys ) !== $keys;
394
  }
395
  }
396
 
 
397
  /**
398
  * A wrapper for scbForms, containing the formdata
399
  */
@@ -418,30 +515,15 @@ class scbForm {
418
  }
419
 
420
  function input( $args ) {
421
- $value = scbForms::get_value( $args['name'], $this->data );
422
 
423
- if ( !is_null( $value ) ) {
424
- switch ( $args['type'] ) {
425
- case 'select':
426
- case 'radio':
427
- $args['selected'] = $value;
428
- break;
429
- case 'checkbox':
430
- if ( is_array( $value ) )
431
- $args['checked'] = $value;
432
- else
433
- $args['checked'] = ( $value || ( isset( $args['value'] ) && $value == $args['value'] ) );
434
- break;
435
- default:
436
- $args['value'] = $value;
437
- }
438
- }
439
 
440
  if ( !empty( $this->prefix ) ) {
441
  $args['name'] = array_merge( $this->prefix, (array) $args['name'] );
442
  }
443
 
444
- return scbForms::input( $args );
445
  }
446
  }
447
 
4
 
5
  class scbForms {
6
 
7
+ const TOKEN = '%input%';
8
 
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':
15
+ case 'radio':
16
+ $args['selected'] = $value;
17
+ break;
18
+ case 'checkbox':
19
+ if ( is_array( $value ) )
20
+ $args['checked'] = $value;
21
+ else
22
+ $args['checked'] = ( $value || ( isset( $args['value'] ) && $value == $args['value'] ) );
23
+ break;
24
+ default:
25
+ $args['value'] = $value;
26
  }
27
  }
28
 
29
+ return self::input( $args );
30
+ }
31
+
32
+ static function input( $args, $formdata = false ) {
33
+ if ( false !== $formdata ) {
34
  $form = new scbForm( $formdata );
35
  return $form->input( $args );
36
  }
37
 
38
+ if ( empty( $args['name'] ) ) {
39
  return trigger_error( 'Empty name', E_USER_WARNING );
40
+ }
41
 
42
  $args = wp_parse_args( $args, array(
43
  'desc' => '',
44
+ 'desc_pos' => 'after',
45
+ 'wrap' => self::TOKEN,
46
  ) );
47
 
48
+ if ( isset( $args['value'] ) && is_array( $args['value'] ) ) {
49
+ $args['values'] = $args['value'];
50
+ unset( $args['value'] );
51
+ }
52
 
53
  if ( isset( $args['extra'] ) && !is_array( $args['extra'] ) )
54
  $args['extra'] = shortcode_parse_atts( $args['extra'] );
58
  switch ( $args['type'] ) {
59
  case 'select':
60
  case 'radio':
61
+ $input = self::_single_choice( $args );
 
 
 
62
  break;
63
  case 'checkbox':
64
+ if ( isset( $args['values'] ) )
65
+ $input = self::_multiple_choice( $args );
66
  else
67
+ $input = self::_checkbox( $args );
68
  break;
69
  default:
70
+ $input = self::_input( $args );
71
  }
72
+
73
+ return str_replace( self::TOKEN, $input, $args['wrap'] );
74
  }
75
 
76
 
183
  $checked = array();
184
 
185
  $opts = '';
186
+ foreach ( $values as $value => $title ) {
187
  if ( empty( $value ) || empty( $title ) )
188
  continue;
189
 
192
  'value' => $value,
193
  'checked' => in_array( $value, $checked ),
194
  'desc' => $title,
195
+ 'desc_pos' => 'after'
196
  ) );
197
  }
198
 
199
+ return self::add_desc( $opts, $desc, $desc_pos );
200
  }
201
 
202
  private static function _expand_values( &$args ) {
203
+ $values =& $args['values'];
204
 
205
+ if ( !empty( $values ) && !self::is_associative( $values ) ) {
206
  if ( is_array( $args['desc'] ) ) {
207
+ $values = array_combine( $values, $args['desc'] ); // back-compat
208
+ $args['desc'] = false;
209
+ } elseif ( !isset( $args['numeric'] ) || !$args['numeric'] ) {
210
+ $values = array_combine( $values, $values );
211
  }
212
  }
213
  }
216
  extract( $args );
217
 
218
  if ( array( 'foo' ) == $selected ) {
219
+ // radio buttons should always have one option selected
220
+ $selected = key( $values );
221
  }
222
 
223
  $opts = '';
224
+ foreach ( $values as $value => $title ) {
225
  if ( empty( $value ) || empty( $title ) )
226
  continue;
227
 
230
  'value' => $value,
231
  'checked' => ( (string) $value == (string) $selected ),
232
  'desc' => $title,
233
+ 'desc_pos' => 'after'
234
  ) );
235
  }
236
 
237
+ return self::add_desc( $opts, $desc, $desc_pos );
238
  }
239
 
240
  private static function _select( $args ) {
253
  );
254
  }
255
 
256
+ foreach ( $values as $value => $title ) {
257
  if ( empty( $value ) || empty( $title ) )
258
  continue;
259
 
339
  }
340
 
341
  private static function add_label( $input, $desc, $desc_pos ) {
342
+ return html( 'label', self::add_desc( $input, $desc, $desc_pos ) ) . "\n";
343
+ }
 
 
 
 
 
 
 
 
 
 
 
 
344
 
345
+ private static function add_desc( $input, $desc, $desc_pos ) {
346
  if ( empty( $desc ) )
347
+ return $input;
 
 
348
 
349
+ if ( 'before' == $desc_pos )
350
+ return $desc . ' ' . $input;
351
+ else
352
+ return $input . ' ' . $desc;
353
  }
354
 
355
 
395
  return $value;
396
  }
397
 
398
+ /**
399
+ * Given a list of fields, extract the appropriate POST data and return it.
400
+ *
401
+ * @param array $fields List of args that would be sent to scbForms::input()
402
+ * @param array $to_update Existing data to update
403
+ *
404
+ * @return array
405
+ */
406
+ static function validate_post_data( $fields, $to_update = array() ) {
407
+ foreach ( $fields as $field ) {
408
+ $value = scbForms::get_value( $field['name'], $_POST );
409
+
410
+ $value = stripslashes_deep( $value );
411
+
412
+ switch ( $field['type'] ) {
413
+ case 'checkbox':
414
+ if ( isset( $field['values'] ) && is_array( $field['values'] ) )
415
+ $value = array_intersect( $field['values'], (array) $value );
416
+ else
417
+ $value = (bool) $value;
418
+
419
+ break;
420
+ case 'radio':
421
+ case 'select':
422
+ self::_expand_values( $field );
423
+
424
+ if ( !isset( $field['values'][ $value ] ) )
425
+ continue 2;
426
+ }
427
+
428
+ self::set_value( $to_update, $field['name'], $value );
429
+ }
430
+
431
+ return $to_update;
432
+ }
433
+
434
+ static function input_from_meta( $args, $object_id, $meta_type = 'post' ) {
435
+ $single = ( 'checkbox' != $args['type'] );
436
+
437
+ $key = (array) $args['name'];
438
+ $key = end( $key );
439
+
440
+ $value = get_metadata( $meta_type, $object_id, $key, $single );
441
+
442
+ return self::input_with_value( $args, $value );
443
+ }
444
+
445
+ static function update_meta( $fields, $data, $object_id, $meta_type = 'post' ) {
446
+ foreach ( $fields as $field_args ) {
447
+ $key = $field_args['name'];
448
+
449
+ if ( 'checkbox' == $field_args['type'] ) {
450
+ $new_values = isset( $data[$key] ) ? $data[$key] : array();
451
+
452
+ $old_values = get_metadata( $meta_type, $object_id, $key );
453
+
454
+ foreach ( array_diff( $new_values, $old_values ) as $value )
455
+ add_metadata( $meta_type, $object_id, $key, $value );
456
+
457
+ foreach ( array_diff( $old_values, $new_values ) as $value )
458
+ delete_metadata( $meta_type, $object_id, $key, $value );
459
+ } else {
460
+ $value = $data[$key];
461
+
462
+ if ( '' === $value )
463
+ delete_metadata( $meta_type, $object_id, $key );
464
+ else
465
+ update_metadata( $meta_type, $object_id, $key, $value );
466
+ }
467
+ }
468
+ }
469
+
470
+ private static function set_value( &$arr, $name, $value ) {
471
+ $name = (array) $name;
472
+
473
+ $final_key = array_pop( $name );
474
+
475
+ while ( !empty( $name ) ) {
476
+ $key = array_shift( $name );
477
+
478
+ if ( !isset( $arr[ $key ] ) )
479
+ $arr[ $key ] = array();
480
+
481
+ $arr =& $arr[ $key ];
482
+ }
483
+
484
+ $arr[ $final_key ] = $value;
485
+ }
486
+
487
  private static function is_associative( $array ) {
488
  $keys = array_keys( $array );
489
  return array_keys( $keys ) !== $keys;
490
  }
491
  }
492
 
493
+
494
  /**
495
  * A wrapper for scbForms, containing the formdata
496
  */
515
  }
516
 
517
  function input( $args ) {
518
+ $default = isset( $args['default'] ) ? $args['default'] : null;
519
 
520
+ $value = scbForms::get_value( $args['name'], $this->data, $default );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
521
 
522
  if ( !empty( $this->prefix ) ) {
523
  $args['name'] = array_merge( $this->prefix, (array) $args['name'] );
524
  }
525
 
526
+ return scbForms::input_with_value( $args, $value );
527
  }
528
  }
529
 
scb/Options.php CHANGED
@@ -103,7 +103,7 @@ class scbOptions {
103
  if ( $clean )
104
  $newdata = $this->_clean( $newdata );
105
 
106
- update_option( $this->key, $newdata );
107
  }
108
 
109
  /**
@@ -126,12 +126,7 @@ class scbOptions {
126
 
127
  // Keep only the keys defined in $this->defaults
128
  private function _clean( $data ) {
129
- $r = array();
130
- foreach ( array_keys( $this->defaults ) as $key )
131
- if ( isset( $data[$key] ) )
132
- $r[$key] = $data[$key];
133
-
134
- return $r;
135
  }
136
 
137
  // Get one, more or all fields from an array
103
  if ( $clean )
104
  $newdata = $this->_clean( $newdata );
105
 
106
+ update_option( $this->key, array_merge( $this->get(), $newdata ) );
107
  }
108
 
109
  /**
126
 
127
  // Keep only the keys defined in $this->defaults
128
  private function _clean( $data ) {
129
+ return wp_array_slice_assoc( $data, array_keys( $this->defaults ) );
 
 
 
 
 
130
  }
131
 
132
  // Get one, more or all fields from an array
scb/Table.php CHANGED
@@ -8,14 +8,11 @@ class scbTable {
8
  protected $upgrade_method;
9
 
10
  function __construct( $name, $file, $columns, $upgrade_method = 'dbDelta' ) {
11
- global $wpdb;
12
-
13
  $this->name = $name;
14
  $this->columns = $columns;
15
  $this->upgrade_method = $upgrade_method;
16
 
17
- $wpdb->tables[] = $name;
18
- $wpdb->$name = $wpdb->prefix . $name;
19
 
20
  if ( $file ) {
21
  scbUtil::add_activation_hook( $file, array( $this, 'install' ) );
@@ -24,36 +21,58 @@ class scbTable {
24
  }
25
 
26
  function install() {
27
- global $wpdb;
 
 
 
 
 
 
28
 
29
- $full_table_name = $wpdb->prefix . $this->name;
 
 
 
 
 
 
 
30
 
31
- $charset_collate = '';
32
- if ( $wpdb->has_cap( 'collation' ) ) {
33
- if ( ! empty( $wpdb->charset ) )
34
- $charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
35
- if ( ! empty( $wpdb->collate ) )
36
- $charset_collate .= " COLLATE $wpdb->collate";
37
- }
38
 
39
- if ( 'dbDelta' == $this->upgrade_method ) {
40
- require_once ABSPATH . 'wp-admin/includes/upgrade.php';
41
- dbDelta( "CREATE TABLE $full_table_name ( $this->columns ) $charset_collate" );
42
- return;
43
- }
 
44
 
45
- if ( 'delete_first' == $this->upgrade_method )
46
- $wpdb->query( "DROP TABLE IF EXISTS $full_table_name;" );
47
 
48
- $wpdb->query( "CREATE TABLE IF NOT EXISTS $full_table_name ( $this->columns ) $charset_collate;" );
 
 
 
 
 
49
  }
50
 
51
- function uninstall() {
52
- global $wpdb;
 
 
 
53
 
54
- $full_table_name = $wpdb->prefix . $this->name;
 
55
 
56
- $wpdb->query( "DROP TABLE IF EXISTS $full_table_name" );
57
- }
 
 
 
 
 
58
  }
59
 
8
  protected $upgrade_method;
9
 
10
  function __construct( $name, $file, $columns, $upgrade_method = 'dbDelta' ) {
 
 
11
  $this->name = $name;
12
  $this->columns = $columns;
13
  $this->upgrade_method = $upgrade_method;
14
 
15
+ scb_register_table( $name );
 
16
 
17
  if ( $file ) {
18
  scbUtil::add_activation_hook( $file, array( $this, 'install' ) );
21
  }
22
 
23
  function install() {
24
+ scb_install_table( $this->name, $this->columns, $this->upgrade_method );
25
+ }
26
+
27
+ function uninstall() {
28
+ scb_uninstall_table( $this->name );
29
+ }
30
+ }
31
 
32
+ /**
33
+ * Register a table with $wpdb
34
+ *
35
+ * @param string $key The key to be used on the $wpdb object
36
+ * @param string $name The actual name of the table, without $wpdb->prefix
37
+ */
38
+ function scb_register_table( $key, $name = false ) {
39
+ global $wpdb;
40
 
41
+ if ( !$name )
42
+ $name = $key;
 
 
 
 
 
43
 
44
+ $wpdb->tables[] = $name;
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 ) )
56
+ $charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
57
+ if ( ! empty( $wpdb->collate ) )
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 ) {
74
+ global $wpdb;
75
+
76
+ $wpdb->query( "DROP TABLE IF EXISTS " . $wpdb->$key );
77
  }
78
 
scb/Util.php CHANGED
@@ -32,7 +32,7 @@ class scbUtil {
32
  echo "</script>";
33
  }
34
 
35
- // Enable delayed activation ( to be used with scb_init() )
36
  static function add_activation_hook( $plugin, $callback ) {
37
  if ( defined( 'SCB_LOAD_MU' ) )
38
  register_activation_hook( $plugin, $callback );
@@ -40,13 +40,27 @@ class scbUtil {
40
  add_action( 'scb_activation_' . plugin_basename( $plugin ), $callback );
41
  }
42
 
43
- // Have more than one uninstall hooks; also prevents an UPDATE query on each page load
 
 
 
 
 
 
44
  static function add_uninstall_hook( $plugin, $callback ) {
 
 
 
45
  register_uninstall_hook( $plugin, '__return_false' ); // dummy
46
 
47
  add_action( 'uninstall_' . plugin_basename( $plugin ), $callback );
48
  }
49
 
 
 
 
 
 
50
  // Get the current, full URL
51
  static function get_current_url() {
52
  return ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
@@ -103,6 +117,10 @@ class scbUtil {
103
  }
104
  }
105
 
 
 
 
 
106
 
107
  // Transform a list of objects into an associative array
108
  function scb_list_fold( $list, $key, $value ) {
32
  echo "</script>";
33
  }
34
 
35
+ // Enable delayed activation; to be used with scb_init()
36
  static function add_activation_hook( $plugin, $callback ) {
37
  if ( defined( 'SCB_LOAD_MU' ) )
38
  register_activation_hook( $plugin, $callback );
40
  add_action( 'scb_activation_' . plugin_basename( $plugin ), $callback );
41
  }
42
 
43
+ // For debugging
44
+ static function do_activation( $plugin ) {
45
+ do_action( 'scb_activation_' . plugin_basename( $plugin ) );
46
+ }
47
+
48
+ // Allows more than one uninstall hooks.
49
+ // Also prevents an UPDATE query on each page load.
50
  static function add_uninstall_hook( $plugin, $callback ) {
51
+ if ( !is_admin() )
52
+ return;
53
+
54
  register_uninstall_hook( $plugin, '__return_false' ); // dummy
55
 
56
  add_action( 'uninstall_' . plugin_basename( $plugin ), $callback );
57
  }
58
 
59
+ // For debugging
60
+ static function do_uninstall( $plugin ) {
61
+ do_action( 'uninstall_' . plugin_basename( $plugin ) );
62
+ }
63
+
64
  // Get the current, full URL
65
  static function get_current_url() {
66
  return ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
117
  }
118
  }
119
 
120
+ // Return a standard admin notice
121
+ function scb_admin_notice( $msg, $class = 'updated' ) {
122
+ return "<div class='$class fade'><p>$msg</p></div>\n";
123
+ }
124
 
125
  // Transform a list of objects into an associative array
126
  function scb_list_fold( $list, $key, $value ) {
scb/load.php CHANGED
@@ -1,15 +1,20 @@
1
  <?php
2
 
3
- $GLOBALS['_scb_data'] = array( 42, __FILE__, array(
4
  'scbUtil', 'scbOptions', 'scbForms', 'scbTable',
5
  'scbWidget', 'scbAdminPage', 'scbBoxesPage',
6
  'scbCron', 'scbHooks',
7
  ) );
8
 
9
  if ( !class_exists( 'scbLoad4' ) ) :
 
 
 
 
 
10
  class scbLoad4 {
11
 
12
- private static $candidates;
13
  private static $classes;
14
  private static $callbacks = array();
15
 
1
  <?php
2
 
3
+ $GLOBALS['_scb_data'] = array( 51, __FILE__, array(
4
  'scbUtil', 'scbOptions', 'scbForms', 'scbTable',
5
  'scbWidget', 'scbAdminPage', 'scbBoxesPage',
6
  'scbCron', 'scbHooks',
7
  ) );
8
 
9
  if ( !class_exists( 'scbLoad4' ) ) :
10
+ /**
11
+ * The main idea behind this class is to load the most recent version of the scb classes available.
12
+ *
13
+ * It waits until all plugins are loaded and then does some crazy hacks to make activation hooks work.
14
+ */
15
  class scbLoad4 {
16
 
17
+ private static $candidates = array();
18
  private static $classes;
19
  private static $callbacks = array();
20
 
screenshot-1.png CHANGED
Binary file