Posts 2 Posts - Version 1.0.1

Version Description

  • don't show metabox at all if user doesn't have the required capability
  • fix checkbox handling when there are no other input fields
  • improve metabox styling
  • rename 'show_ui' to 'admin_box'
  • add 'admin_column' parameter
Download this release

Release Info

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

Code changes from version 0.9.5 to 1.0.1

admin/base.php DELETED
@@ -1,94 +0,0 @@
1
- <?php
2
-
3
- define( 'P2P_BOX_NONCE', 'p2p-box' );
4
-
5
- /**
6
- * @package Administration
7
- */
8
- class P2P_Box_Factory {
9
-
10
- function init() {
11
- add_action( 'add_meta_boxes', array( __CLASS__, 'add_meta_boxes' ) );
12
- add_action( 'save_post', array( __CLASS__, 'save_post' ), 10, 2 );
13
- add_action( 'wp_ajax_p2p_box', array( __CLASS__, 'wp_ajax_p2p_box' ) );
14
- }
15
-
16
- /**
17
- * Add all the metaboxes.
18
- */
19
- static function add_meta_boxes( $from ) {
20
- foreach ( P2P_Connection_Type::get() as $ctype_id => $args ) {
21
- $box = self::make_box( $ctype_id, $from );
22
- if ( !$box )
23
- continue;
24
-
25
- $box->register();
26
- }
27
- }
28
-
29
- /**
30
- * Collect metadata from all boxes.
31
- */
32
- function save_post( $post_id, $post ) {
33
- if ( 'revision' == $post->post_type || defined( 'DOING_AJAX' ) )
34
- return;
35
-
36
- // Custom fields
37
- if ( isset( $_POST['p2p_meta'] ) ) {
38
- foreach ( $_POST['p2p_meta'] as $p2p_id => $data ) {
39
- foreach ( $data as $key => $value ) {
40
- p2p_update_meta( $p2p_id, $key, $value );
41
- }
42
- }
43
- }
44
-
45
- // Ordering
46
- if ( isset( $_POST['p2p_order'] ) ) {
47
- foreach ( $_POST['p2p_order'] as $key => $list ) {
48
- foreach ( $list as $i => $p2p_id ) {
49
- p2p_update_meta( $p2p_id, $key, $i );
50
- }
51
- }
52
- }
53
- }
54
-
55
- /**
56
- * Controller for all box ajax requests.
57
- */
58
- function wp_ajax_p2p_box() {
59
- check_ajax_referer( P2P_BOX_NONCE, 'nonce' );
60
-
61
- $box = self::make_box( $_REQUEST['ctype_id'], $_REQUEST['post_type'] );
62
- if ( !$box )
63
- die(0);
64
-
65
- if ( !current_user_can( $box->ptype->cap->edit_posts ) )
66
- die(-1);
67
-
68
- $method = 'ajax_' . $_REQUEST['subaction'];
69
-
70
- $box->$method();
71
- }
72
-
73
- private static function make_box( $ctype_id, $post_type ) {
74
- $ctype = p2p_type( $ctype_id );
75
-
76
- if ( !$ctype )
77
- return false;
78
-
79
- if ( !$ctype->show_ui )
80
- return false;
81
-
82
- $directed = $ctype->find_direction( $post_type );
83
- if ( !$directed )
84
- return false;
85
-
86
- if ( !$ctype->reciprocal && 'to' == $directed->direction )
87
- return false;
88
-
89
- return new P2P_Box( $directed, $post_type );
90
- }
91
- }
92
-
93
- P2P_Box_Factory::init();
94
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
admin/box-factory.php ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ define( 'P2P_BOX_NONCE', 'p2p-box' );
4
+
5
+ /**
6
+ * @package Administration
7
+ */
8
+ 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(
17
+ 'show' => 'any',
18
+ 'context' => 'side',
19
+ 'fields' => array(),
20
+ 'can_create_post' => true
21
+ ) );
22
+
23
+ if ( !$box_args->show )
24
+ return false;
25
+
26
+ foreach ( $box_args->fields as &$field_args ) {
27
+ if ( !is_array( $field_args ) )
28
+ $field_args = array( 'title' => $field_args );
29
+
30
+ $field_args['type'] = _p2p_get_field_type( $field_args );
31
+
32
+ if ( 'checkbox' == $field_args['type'] && !isset( $field_args['values'] ) )
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
+
53
+ $title = $ctype->title;
54
+
55
+ if ( count( $dir ) > 1 && $title['from'] == $title['to'] ) {
56
+ $title['from'] .= __( ' (from)', P2P_TEXTDOMAIN );
57
+ $title['to'] .= __( ' (to)', P2P_TEXTDOMAIN );
58
+ }
59
+
60
+ foreach ( $dir as $direction ) {
61
+ $key = ( 'to' == $direction ) ? 'to' : 'from';
62
+
63
+ $directed = $ctype->set_direction( $direction );
64
+
65
+ $box = new P2P_Box( $box_args, $directed );
66
+
67
+ if ( !$box->check_capability() )
68
+ continue;
69
+
70
+ add_meta_box(
71
+ "p2p-{$direction}-{$ctype->id}",
72
+ $title[$key],
73
+ array( $box, 'render' ),
74
+ $post_type,
75
+ $box_args->context,
76
+ 'default'
77
+ );
78
+
79
+ $box->init_scripts();
80
+ }
81
+ }
82
+ }
83
+
84
+ private static function get_visible_directions( $post_type, $ctype, $show_ui ) {
85
+ $direction = $ctype->find_direction( $post_type, false );
86
+ if ( !$direction )
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 )
97
+ return array( $direction );
98
+
99
+ return array();
100
+ }
101
+
102
+ /**
103
+ * Collect metadata from all boxes.
104
+ */
105
+ static function save_post( $post_id, $post ) {
106
+ if ( 'revision' == $post->post_type || defined( 'DOING_AJAX' ) )
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
+ }
138
+
139
+ // Ordering
140
+ if ( isset( $_POST['p2p_order'] ) ) {
141
+ foreach ( $_POST['p2p_order'] as $key => $list ) {
142
+ foreach ( $list as $i => $p2p_id ) {
143
+ p2p_update_meta( $p2p_id, $key, $i );
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Controller for all box ajax requests.
151
+ */
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'] );
160
+ if ( !$post_type )
161
+ die(0);
162
+
163
+ $directed = $ctype->set_direction( $_REQUEST['direction'] );
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);
171
+
172
+ $method = 'ajax_' . $_REQUEST['subaction'];
173
+
174
+ $box->$method();
175
+ }
176
+ }
177
+
178
+ P2P_Box_Factory::init();
179
+
admin/box.css CHANGED
@@ -6,6 +6,10 @@
6
  margin-bottom: 8px;
7
  }
8
 
 
 
 
 
9
  /* Tabs */
10
 
11
  .p2p-box .tabs-panel {
@@ -50,8 +54,7 @@
50
  .p2p-connections th,
51
  .p2p-connections td,
52
  .p2p-results td {
53
- padding-left: 6px;
54
- padding-right: 6px;
55
  }
56
 
57
  th.p2p-col-delete,
@@ -68,10 +71,6 @@ td.p2p-col-create {
68
  vertical-align: middle;
69
  }
70
 
71
- th.p2p-col-delete a {
72
- padding-bottom: 8px;
73
- }
74
-
75
  .p2p-results tr:first-child td {
76
  border-top: 0;
77
  }
@@ -94,17 +93,28 @@ th.p2p-col-delete a {
94
  .p2p-col-delete a {
95
  display: block;
96
  height: 16px;
97
- margin-top: 1px;
98
- opacity: 0.5;
99
  width: 16px;
100
  }
101
 
102
- .p2p-col-create a:hover,
103
- .p2p-col-delete a:hover {
 
 
 
 
 
104
  background-color: #f5f5f5;
105
- border-right: 1px solid #ddd;
 
 
 
 
 
 
 
 
 
106
  opacity: 1;
107
- width: 15px;
108
  }
109
 
110
  .p2p-col-create a {
@@ -115,6 +125,10 @@ th.p2p-col-delete a {
115
  background: url("images/delete.png") no-repeat scroll 50% 50% transparent;
116
  }
117
 
 
 
 
 
118
  tr:hover td.p2p-col-order {
119
  cursor: move;
120
  background: url("images/sort.png") no-repeat scroll 50% 50% transparent;
@@ -170,6 +184,7 @@ tr:hover td.p2p-col-order {
170
  margin-top: 3px;
171
  }
172
 
 
173
  /* RTL support */
174
 
175
  body.rtl .p2p-navigation div {
@@ -186,12 +201,17 @@ body.rtl .p2p-tab-search input {
186
  padding-right: 0;
187
  }
188
 
189
- body.rtl .p2p-col-create a:hover,
190
- body.rtl .p2p-col-delete a:hover {
191
- border-left: 1px solid #ddd;
192
  border-right: 0;
193
  }
194
 
 
 
 
 
 
195
  body.rtl .p2p-navigation div {
196
  margin-right: 0;
197
  margin-left: 5px;
6
  margin-bottom: 8px;
7
  }
8
 
9
+ .p2p-connections a {
10
+ text-decoration: none;
11
+ }
12
+
13
  /* Tabs */
14
 
15
  .p2p-box .tabs-panel {
54
  .p2p-connections th,
55
  .p2p-connections td,
56
  .p2p-results td {
57
+ padding: 3px 6px;
 
58
  }
59
 
60
  th.p2p-col-delete,
71
  vertical-align: middle;
72
  }
73
 
 
 
 
 
74
  .p2p-results tr:first-child td {
75
  border-top: 0;
76
  }
93
  .p2p-col-delete a {
94
  display: block;
95
  height: 16px;
 
 
96
  width: 16px;
97
  }
98
 
99
+ .p2p-col-create,
100
+ .p2p-col-delete {
101
+ border-right: 1px solid transparent;
102
+ }
103
+
104
+ .p2p-col-create:hover,
105
+ .p2p-col-delete:hover {
106
  background-color: #f5f5f5;
107
+ border-right-color: #ddd;
108
+ }
109
+
110
+ .p2p-col-create a,
111
+ .p2p-col-delete a {
112
+ opacity: 0.5;
113
+ }
114
+
115
+ .p2p-col-create:hover a,
116
+ .p2p-col-delete:hover a {
117
  opacity: 1;
 
118
  }
119
 
120
  .p2p-col-create a {
125
  background: url("images/delete.png") no-repeat scroll 50% 50% transparent;
126
  }
127
 
128
+ td.p2p-col-order {
129
+ width: 13px;
130
+ }
131
+
132
  tr:hover td.p2p-col-order {
133
  cursor: move;
134
  background: url("images/sort.png") no-repeat scroll 50% 50% transparent;
184
  margin-top: 3px;
185
  }
186
 
187
+
188
  /* RTL support */
189
 
190
  body.rtl .p2p-navigation div {
201
  padding-right: 0;
202
  }
203
 
204
+ body.rtl .p2p-col-create,
205
+ body.rtl .p2p-col-delete {
206
+ border-left: 1px solid transparent;
207
  border-right: 0;
208
  }
209
 
210
+ body.rtl .p2p-col-create:hover,
211
+ body.rtl .p2p-col-delete:hover {
212
+ border-left-color: #ddd;
213
+ }
214
+
215
  body.rtl .p2p-navigation div {
216
  margin-right: 0;
217
  margin-left: 5px;
admin/box.js CHANGED
@@ -28,13 +28,19 @@
28
  'src': P2PAdmin.spinner,
29
  'class': 'p2p-spinner'
30
  });
31
- ajax_request = function(data, callback, method){
32
- method == null && (method = 'post');
33
  data.action = 'p2p_box';
34
  data.nonce = P2PAdmin.nonce;
35
  data.ctype_id = $metabox.data('ctype_id');
36
- data.post_type = jQuery('#post_type').val();
37
- return jQuery[method](ajaxurl, data, callback);
 
 
 
 
 
 
38
  };
39
  row_ajax_request = function($td, data, callback){
40
  $td.html($spinner.show());
@@ -54,8 +60,7 @@
54
  $self = jQuery(ev.target);
55
  $td = $self.closest('td');
56
  data = {
57
- subaction: 'clear_connections',
58
- post_id: jQuery('#post_ID').val()
59
  };
60
  row_ajax_request($td, data, function(response){
61
  $connections.hide().find('tbody').html('');
@@ -87,7 +92,6 @@
87
  $td = $self.closest('td');
88
  data = {
89
  subaction: 'connect',
90
- from: jQuery('#post_ID').val(),
91
  to: $self.data('post_id')
92
  };
93
  row_ajax_request($td, data, function(response){
@@ -137,7 +141,6 @@
137
  this.tab.delegate('.p2p-prev, .p2p-next', 'click', __bind(this, this.change_page));
138
  this.data = {
139
  subaction: 'search',
140
- post_id: jQuery('#post_ID').val(),
141
  s: ''
142
  };
143
  }
@@ -165,10 +168,18 @@
165
  ? new_page > this.total_pages ? this.current_page : new_page
166
  : this.current_page;
167
  $spinner.appendTo(this.tab.find('.p2p-navigation'));
168
- return ajax_request(this.data, __bind(this, this.update_rows), 'getJSON');
169
  };
170
  prototype.update_rows = function(response){
171
  $spinner.remove();
 
 
 
 
 
 
 
 
172
  this.tab.find('.p2p-results, .p2p-navigation, .p2p-notice').remove();
173
  if (!response.rows) {
174
  return this.tab.append(jQuery('<div class="p2p-notice">').html(response.msg));
@@ -218,7 +229,6 @@
218
  $button.addClass('inactive');
219
  data = {
220
  subaction: 'create_post',
221
- from: jQuery('#post_ID').val(),
222
  post_title: title
223
  };
224
  ajax_request(data, function(response){
28
  'src': P2PAdmin.spinner,
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());
60
  $self = jQuery(ev.target);
61
  $td = $self.closest('td');
62
  data = {
63
+ subaction: 'clear_connections'
 
64
  };
65
  row_ajax_request($td, data, function(response){
66
  $connections.hide().find('tbody').html('');
92
  $td = $self.closest('td');
93
  data = {
94
  subaction: 'connect',
 
95
  to: $self.data('post_id')
96
  };
97
  row_ajax_request($td, data, function(response){
141
  this.tab.delegate('.p2p-prev, .p2p-next', 'click', __bind(this, this.change_page));
142
  this.data = {
143
  subaction: 'search',
 
144
  s: ''
145
  };
146
  }
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));
229
  $button.addClass('inactive');
230
  data = {
231
  subaction: 'create_post',
 
232
  post_title: title
233
  };
234
  ajax_request(data, function(response){
admin/box.php CHANGED
@@ -14,9 +14,9 @@ interface P2P_Field {
14
  class P2P_Box {
15
  private $ctype;
16
 
17
- private $current_ptype;
18
 
19
- public $ptype;
20
 
21
  private $columns;
22
 
@@ -26,42 +26,19 @@ class P2P_Box {
26
  'post_status' => 'any',
27
  );
28
 
29
- function __construct( $ctype, $current_ptype ) {
30
- $this->ctype = $ctype;
31
-
32
- $this->current_ptype = $current_ptype;
33
 
34
- $other_ptype = $this->ctype->get_other_post_type();
35
- $this->ptype = get_post_type_object( $other_ptype[0] );
36
 
37
- if ( !class_exists( 'Mustache' ) )
38
- require dirname(__FILE__) . '/../mustache/Mustache.php';
39
 
40
  add_filter( 'posts_search', array( __CLASS__, '_search_by_title' ), 10, 2 );
41
 
42
  $this->init_columns();
43
  }
44
 
45
- public function register() {
46
- $title = $this->ctype->get_title();
47
-
48
- if ( empty( $title ) ) {
49
- $title = sprintf( __( 'Connected %s', P2P_TEXTDOMAIN ), $this->ptype->labels->name );
50
- }
51
-
52
- add_meta_box(
53
- 'p2p-connections-' . $this->ctype->id,
54
- $title,
55
- array( $this, 'render' ),
56
- $this->current_ptype,
57
- $this->ctype->context,
58
- 'default'
59
- );
60
-
61
- $this->init_scripts();
62
- }
63
-
64
- protected function init_scripts() {
65
  wp_enqueue_style( 'p2p-admin', plugins_url( 'box.css', __FILE__ ), array(), P2P_PLUGIN_VERSION );
66
 
67
  wp_enqueue_script( 'p2p-admin', plugins_url( 'box.js', __FILE__ ), array( 'jquery' ), P2P_PLUGIN_VERSION, true );
@@ -78,12 +55,12 @@ class P2P_Box {
78
  'title' => new P2P_Field_Title( $this->ptype->labels->singular_name ),
79
  );
80
 
81
- foreach ( $this->ctype->fields as $key => $data ) {
82
  $this->columns[ $key ] = new P2P_Field_Generic( $data );
83
  }
84
 
85
- if ( $this->ctype->is_sortable() ) {
86
- $this->columns['order'] = new P2P_Field_Order( $this->ctype->sortable );
87
  }
88
  }
89
 
@@ -94,23 +71,28 @@ class P2P_Box {
94
  $this->connected_posts = $this->ctype->get_connected( $post->ID, $qv )->posts;
95
 
96
  $data = array(
 
 
97
  'connections' => $this->render_connections_table( $post ),
98
- 'create-connections' => $this->render_create_connections( $post )
99
  );
100
 
 
 
 
 
101
  $data_attr = array(
102
  'ctype_id' => $this->ctype->id,
103
  'prevent_duplicates' => $this->ctype->prevent_duplicates,
104
- 'cardinality' => $this->ctype->cardinality,
 
105
  );
106
 
107
  $data_attr_str = array();
108
  foreach ( $data_attr as $key => $value )
109
  $data_attr_str[] = "data-$key='" . $value . "'";
110
 
111
- $data['attributes'] = implode( ' ', $data_attr_str );
112
-
113
- echo _p2p_mustache_render( 'box.html', $data );
114
  }
115
 
116
  protected function render_connections_table( $post ) {
@@ -119,9 +101,9 @@ class P2P_Box {
119
  if ( empty( $this->connected_posts ) )
120
  $data['hide'] = 'style="display:none"';
121
 
122
- $tbody = '';
123
  foreach ( $this->connected_posts as $connected ) {
124
- $tbody .= $this->connection_row( $connected->p2p_id, $connected->ID );
125
  }
126
  $data['tbody'] = $tbody;
127
 
@@ -132,19 +114,19 @@ class P2P_Box {
132
  );
133
  }
134
 
135
- return _p2p_mustache_render( 'table.html', $data );
136
  }
137
 
138
  protected function render_create_connections( $post ) {
139
  $data = array(
140
- 'create-label' => __( 'Create connections:', P2P_TEXTDOMAIN )
141
  );
142
 
143
- if ( 'one' == $this->ctype->cardinality && !empty( $this->connected_posts ) )
144
  $data['hide'] = 'style="display:none"';
145
 
146
  // Search tab
147
- $tab_content = _p2p_mustache_render( 'tab-search.html', array(
148
  'placeholder' => $this->ptype->labels->search_items,
149
  ) );
150
 
@@ -164,7 +146,7 @@ class P2P_Box {
164
 
165
  // Create post tab
166
  if ( $this->can_create_post() ) {
167
- $tab_content = _p2p_mustache_render( 'tab-create-post.html', array(
168
  'title' => $this->ptype->labels->add_new_item
169
  ) );
170
 
@@ -175,20 +157,27 @@ class P2P_Box {
175
  );
176
  }
177
 
178
- return _p2p_mustache_render( 'create-connections.html', $data );
 
 
 
 
179
  }
180
 
181
- protected function connection_row( $p2p_id, $post_id ) {
182
  $data = array();
183
 
184
- foreach ( $this->columns as $key => $field ) {
185
  $data['columns'][] = array(
186
  'column' => $key,
187
  'content' => $field->render( $key, $p2p_id, $post_id )
188
  );
189
  }
190
 
191
- return _p2p_mustache_render( 'table-row.html', $data );
 
 
 
192
  }
193
 
194
  protected function post_rows( $current_post_id, $page = 1, $search = '' ) {
@@ -221,16 +210,7 @@ class P2P_Box {
221
  );
222
 
223
  foreach ( $candidate->posts as $post ) {
224
- $row = array();
225
-
226
- foreach ( $columns as $key => $field ) {
227
- $row['columns'][] = array(
228
- 'column' => $key,
229
- 'content' => $field->render( $key, 0, $post->ID )
230
- );
231
- }
232
-
233
- $data['rows'][] = $row;
234
  }
235
 
236
  if ( $candidate->total_pages > 1 ) {
@@ -250,7 +230,7 @@ class P2P_Box {
250
  );
251
  }
252
 
253
- return _p2p_mustache_render( 'tab-list.html', $data, array( 'table-row' ) );
254
  }
255
 
256
 
@@ -282,10 +262,10 @@ class P2P_Box {
282
  if ( !$from || !$to )
283
  die(-1);
284
 
285
- $p2p_id = $this->ctype->lose_direction()->connect( $from, $to );
286
 
287
  if ( $p2p_id )
288
- echo $this->connection_row( $p2p_id, $to );
289
 
290
  die;
291
  }
@@ -297,13 +277,13 @@ class P2P_Box {
297
  }
298
 
299
  public function ajax_clear_connections() {
300
- $this->ctype->lose_direction()->disconnect_all( $_POST['post_id'] );
301
 
302
  die(1);
303
  }
304
 
305
  public function ajax_search() {
306
- $rows = $this->post_rows( $_GET['post_id'], $_GET['paged'], $_GET['s'] );
307
 
308
  if ( $rows ) {
309
  $results = compact( 'rows' );
@@ -317,11 +297,21 @@ class P2P_Box {
317
  }
318
 
319
  protected function can_create_post() {
320
- $ptype = $this->ctype->get_other_post_type();
 
 
 
321
 
322
- if ( count( $ptype ) > 1 )
323
  return false;
324
 
 
 
 
 
 
 
 
325
  return current_user_can( $this->ptype->cap->edit_posts );
326
  }
327
 
@@ -335,27 +325,3 @@ class P2P_Box {
335
  }
336
  }
337
 
338
-
339
- // Helpers
340
-
341
- /**
342
- * @internal
343
- */
344
- function _p2p_mustache_render( $file, $data, $partials = array() ) {
345
- $partial_data = array();
346
- foreach ( $partials as $partial ) {
347
- $partial_data[$partial] = _p2p_load_template( $partial . '.html' );
348
- }
349
-
350
- $m = new Mustache;
351
-
352
- return $m->render( _p2p_load_template( $file ), $data, $partial_data );
353
- }
354
-
355
- /**
356
- * @internal
357
- */
358
- function _p2p_load_template( $file ) {
359
- return file_get_contents( dirname(__FILE__) . '/templates/' . $file );
360
- }
361
-
14
  class P2P_Box {
15
  private $ctype;
16
 
17
+ private $args;
18
 
19
+ private $ptype;
20
 
21
  private $columns;
22
 
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
  }
40
 
41
+ public function init_scripts() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  wp_enqueue_style( 'p2p-admin', plugins_url( 'box.css', __FILE__ ), array(), P2P_PLUGIN_VERSION );
43
 
44
  wp_enqueue_script( 'p2p-admin', plugins_url( 'box.js', __FILE__ ), array( 'jquery' ), P2P_PLUGIN_VERSION, true );
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
 
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 ),
78
  );
79
 
80
+ echo P2P_Mustache::render( 'box', $data );
81
+ }
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()
89
  );
90
 
91
  $data_attr_str = array();
92
  foreach ( $data_attr as $key => $value )
93
  $data_attr_str[] = "data-$key='" . $value . "'";
94
 
95
+ return implode( ' ', $data_attr_str );
 
 
96
  }
97
 
98
  protected function render_connections_table( $post ) {
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
 
114
  );
115
  }
116
 
117
+ return $data;
118
  }
119
 
120
  protected function render_create_connections( $post ) {
121
  $data = array(
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
 
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
 
157
  );
158
  }
159
 
160
+ return $data;
161
+ }
162
+
163
+ protected function connection_row( $p2p_id, $post_id, $render = false ) {
164
+ return $this->table_row( $this->columns, $p2p_id, $post_id, $render );
165
  }
166
 
167
+ protected function table_row( $columns, $p2p_id, $post_id, $render = false ) {
168
  $data = array();
169
 
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
 
177
+ if ( !$render )
178
+ return $data;
179
+
180
+ return P2P_Mustache::render( 'table-row', $data );
181
  }
182
 
183
  protected function post_rows( $current_post_id, $page = 1, $search = '' ) {
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 ) {
230
  );
231
  }
232
 
233
+ return P2P_Mustache::render( 'tab-list', $data );
234
  }
235
 
236
 
262
  if ( !$from || !$to )
263
  die(-1);
264
 
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
  }
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' );
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
 
325
  }
326
  }
327
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
admin/column-factory.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ 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
+ }
18
+
19
+ function add_columns() {
20
+ $screen = get_current_screen();
21
+
22
+ if ( 'edit' != $screen->base )
23
+ return;
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 )
32
+ continue;
33
+
34
+ if ( !( 'any' == $column_args || $directed->get_direction() == $column_args ) )
35
+ continue;
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
+
admin/column.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class P2P_Column {
4
+
5
+ protected $ctype;
6
+
7
+ protected $connected = array();
8
+
9
+ function __construct( $directed ) {
10
+ $this->ctype = $directed;
11
+
12
+ $this->ctype->lose_direction()->each_connected( $GLOBALS['wp_query'] );
13
+
14
+ $this->connected = scb_list_fold( $GLOBALS['wp_query']->posts, 'ID', 'connected' );
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
+
admin/fields.php CHANGED
@@ -16,7 +16,7 @@ class P2P_Field_Create implements P2P_Field {
16
  'title' => __( 'Create connection', P2P_TEXTDOMAIN )
17
  );
18
 
19
- return _p2p_mustache_render( 'column-create.html', $data );
20
  }
21
  }
22
 
@@ -30,7 +30,7 @@ class P2P_Field_Delete implements P2P_Field {
30
  'title' => __( 'Delete all connections', P2P_TEXTDOMAIN )
31
  );
32
 
33
- return _p2p_mustache_render( 'column-delete-all.html', $data );
34
  }
35
 
36
  function render( $key, $p2p_id, $post_id ) {
@@ -39,7 +39,7 @@ class P2P_Field_Delete implements P2P_Field {
39
  'title' => __( 'Delete connection', P2P_TEXTDOMAIN )
40
  );
41
 
42
- return _p2p_mustache_render( 'column-delete.html', $data );
43
  }
44
  }
45
 
@@ -84,7 +84,7 @@ class P2P_Field_Title implements P2P_Field {
84
 
85
  function render( $key, $p2p_id, $post_id ) {
86
  $data = array(
87
- 'title-attr' => get_post_type_object( get_post_type( $post_id ) )->labels->edit_item,
88
  'title' => get_post_field( 'post_title', $post_id ),
89
  'url' => get_edit_post_link( $post_id ),
90
  );
@@ -98,7 +98,7 @@ class P2P_Field_Title implements P2P_Field {
98
  }
99
  }
100
 
101
- return _p2p_mustache_render( 'column-title.html', $data );
102
  }
103
  }
104
 
@@ -110,9 +110,6 @@ class P2P_Field_Generic implements P2P_Field {
110
  protected $data;
111
 
112
  function __construct( $data ) {
113
- if ( !is_array( $data ) )
114
- $data = array( 'title' => $data );
115
-
116
  $this->data = $data;
117
  }
118
 
@@ -121,20 +118,20 @@ class P2P_Field_Generic implements P2P_Field {
121
  }
122
 
123
  function render( $key, $p2p_id, $post_id ) {
124
- $form = new scbForm(
125
- array( $key => p2p_get_meta( $p2p_id, $key, true ) ),
126
- array( 'p2p_meta', $p2p_id )
127
- );
128
-
129
  $args = array(
130
- 'type' => 'text',
131
- 'name' => $key
132
  );
133
 
134
- if ( isset( $this->data['values'] ) ) {
135
- $args['type'] = 'select';
136
  $args['value'] = $this->data['values'];
137
- }
 
 
 
 
 
 
138
 
139
  return $form->input( $args );
140
  }
16
  'title' => __( 'Create connection', P2P_TEXTDOMAIN )
17
  );
18
 
19
+ return P2P_Mustache::render( 'column-create', $data );
20
  }
21
  }
22
 
30
  'title' => __( 'Delete all connections', P2P_TEXTDOMAIN )
31
  );
32
 
33
+ return P2P_Mustache::render( 'column-delete-all', $data );
34
  }
35
 
36
  function render( $key, $p2p_id, $post_id ) {
39
  'title' => __( 'Delete connection', P2P_TEXTDOMAIN )
40
  );
41
 
42
+ return P2P_Mustache::render( 'column-delete', $data );
43
  }
44
  }
45
 
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 ),
89
  'url' => get_edit_post_link( $post_id ),
90
  );
98
  }
99
  }
100
 
101
+ return P2P_Mustache::render( 'column-title', $data );
102
  }
103
  }
104
 
110
  protected $data;
111
 
112
  function __construct( $data ) {
 
 
 
113
  $this->data = $data;
114
  }
115
 
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
  }
admin/templates/box.html CHANGED
@@ -1,4 +1,38 @@
 
 
1
  <div class="p2p-box" {{attributes}}>
2
- {{{connections}}}
3
- {{{create-connections}}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  </div>
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>
7
+ <tr>
8
+ {{#thead}}
9
+ <th class="p2p-col-{{column}}">{{{title}}}</th>
10
+ {{/thead}}
11
+ </tr>
12
+ </thead>
13
+
14
+ <tbody>
15
+ {{#tbody}}
16
+ {{>table-row}}
17
+ {{/tbody}}
18
+ </tbody>
19
+ </table>
20
+ {{/connections}}
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">
33
+ {{{tab-content}}}
34
+ </div>
35
+ {{/tabs}}
36
+ </div>
37
+ {{/create-connections}}
38
  </div>
admin/templates/create-connections.html DELETED
@@ -1,15 +0,0 @@
1
- <div class="p2p-create-connections" {{{hide}}}>
2
- <p><strong>{{create-label}}</strong></p>
3
-
4
- <ul class="wp-tab-bar clearfix">
5
- {{#tabs}}
6
- <li {{#is-active}}class="wp-tab-active"{{/is-active}} data-ref=".p2p-tab-{{tab-id}}"><a href="#">{{tab-title}}</a></li>
7
- {{/tabs}}
8
- </ul>
9
-
10
- {{#tabs}}
11
- <div class="p2p-tab-{{tab-id}} tabs-panel">
12
- {{{tab-content}}}
13
- </div>
14
- {{/tabs}}
15
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
admin/templates/table-row.html CHANGED
@@ -1,7 +1,5 @@
1
  <tr>
2
  {{#columns}}
3
- <td class="p2p-col-{{column}}">
4
- {{{content}}}
5
- </td>
6
  {{/columns}}
7
  </tr>
1
  <tr>
2
  {{#columns}}
3
+ <td class="p2p-col-{{column}}">{{{content}}}</td>
 
 
4
  {{/columns}}
5
  </tr>
admin/templates/table.html DELETED
@@ -1,13 +0,0 @@
1
- <table class="p2p-connections" {{{hide}}}>
2
- <thead>
3
- <tr>
4
- {{#thead}}
5
- <th class="p2p-col-{{column}}">{{{title}}}</th>
6
- {{/thead}}
7
- </tr>
8
- </thead>
9
-
10
- <tbody>
11
- {{{tbody}}}
12
- </tbody>
13
- </table>
 
 
 
 
 
 
 
 
 
 
 
 
 
admin/utils.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @internal
5
+ */
6
+ abstract class P2P_Mustache {
7
+
8
+ private static $loader;
9
+ private static $mustache;
10
+
11
+ public static function init() {
12
+ if ( !class_exists( 'Mustache' ) )
13
+ require dirname(__FILE__) . '/../mustache/Mustache.php';
14
+
15
+ if ( !class_exists( 'MustacheLoader' ) )
16
+ require dirname(__FILE__) . '/../mustache/MustacheLoader.php';
17
+
18
+ self::$loader = new MustacheLoader( dirname(__FILE__) . '/templates', 'html' );
19
+
20
+ self::$mustache = new Mustache( null, null, self::$loader );
21
+ }
22
+
23
+ public static function render( $template, $data ) {
24
+ return self::$mustache->render( self::$loader[$template], $data );
25
+ }
26
+ }
27
+
28
+ P2P_Mustache::init();
29
+
core/api.php CHANGED
@@ -11,23 +11,29 @@
11
  *
12
  * - 'from' - string|array The first end of the connection.
13
  *
 
 
14
  * - 'to' - string|array The second end of the connection.
15
  *
16
- * - 'fields' - array( key => Title ) Metadata fields editable by the user (optional).
 
 
17
  *
18
- * - 'data' - array( key => value ) Metadata fields not editable by the user (optional).
19
  *
20
- * - 'sortable' - string A custom field key used to add a special column that allows manual connection ordering. Default: false.
21
  *
22
  * - 'prevent_duplicates' - bool Whether to disallow duplicate connections between the same two posts. Default: true.
23
  *
24
- * - 'title' - string The box's title. Default: 'Connected {$post_type}s'
 
 
25
  *
26
- * - 'reciprocal' - bool Whether to show the box on both sides of the connection. Default: false.
27
  *
28
- * - 'show_ui' - bool Whether to show the admin connections box. Default: true.
29
  *
30
- * - 'context' - string Where should the box show up by default. Possible values: 'advanced' or 'side'
31
  *
32
  * @param array $args
33
  *
@@ -35,23 +41,72 @@
35
  */
36
  function p2p_register_connection_type( $args ) {
37
  if ( !did_action('init') ) {
38
- trigger_error( "Connection types should not be registered before the 'init' hook.", E_USER_NOTICE );
39
  }
40
 
41
  $argv = func_get_args();
42
 
 
43
  if ( count( $argv ) > 1 ) {
44
  $args = array();
45
- @list( $args['from'], $args['to'], $args['reciprocal'] ) = $argv;
 
 
 
46
  }
47
 
48
- $args = wp_parse_args( $args, array(
49
- 'show_ui' => true,
50
- 'fields' => array(),
51
- 'context' => 'side',
52
- ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- return P2P_Connection_Type::register( $args );
55
  }
56
 
57
  /**
@@ -62,7 +117,7 @@ function p2p_register_connection_type( $args ) {
62
  * @return bool|object False if connection type not found, P2P_Connection_Type instance on success.
63
  */
64
  function p2p_type( $id ) {
65
- return P2P_Connection_Type::get( $id );
66
  }
67
 
68
  /**
@@ -89,7 +144,8 @@ function p2p_split_posts( $posts, $key ) {
89
  $buckets = array();
90
 
91
  foreach ( $posts as $post ) {
92
- $buckets[ p2p_get_meta( $post->p2p_id, $key, true ) ][] = $post;
 
93
  }
94
 
95
  return $buckets;
11
  *
12
  * - 'from' - string|array The first end of the connection.
13
  *
14
+ * - 'from_query_vars' - array Additional query vars to pass to WP_Query. Default: none.
15
+ *
16
  * - 'to' - string|array The second end of the connection.
17
  *
18
+ * - 'to_query_vars' - array Additional query vars to pass to WP_Query. Default: none.
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.
35
  *
36
+ * - 'can_create_post' - bool Whether to allow post creation via the connection box. Default: true.
37
  *
38
  * @param array $args
39
  *
41
  */
42
  function p2p_register_connection_type( $args ) {
43
  if ( !did_action('init') ) {
44
+ trigger_error( "Connection types should not be registered before the 'init' hook." );
45
  }
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
  /**
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
  /**
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;
core/directed-type.php CHANGED
@@ -26,88 +26,108 @@ class P2P_Directed_Connection_Type {
26
  }
27
 
28
  function __get( $key ) {
29
- if ( 'direction' == $key )
30
- return $this->direction;
 
 
 
 
31
 
32
- if ( 'cardinality' == $key )
33
- return $this->cardinality;
 
34
 
35
- return $this->ctype->$key;
 
36
  }
37
 
38
  public function lose_direction() {
39
  return $this->ctype;
40
  }
41
 
42
- public function get_title() {
43
- $title = $this->title;
44
-
45
- if ( is_array( $title ) ) {
46
- $key = ( 'to' == $this->direction ) ? 'to' : 'from';
47
 
48
- if ( isset( $title[ $key ] ) )
49
- $title = $title[ $key ];
50
- else
51
- $title = '';
52
- }
53
 
54
- return $title;
55
  }
56
 
57
- public function get_other_post_type() {
58
- return 'from' == $this->direction ? $this->to : $this->from;
59
  }
60
 
61
- public function is_sortable() {
62
- return $this->sortable && 'from' == $this->direction;
63
  }
64
 
65
- private function get_base_args( $extra_qv ) {
66
  $base_qv = ( 'from' == $this->direction ) ? $this->to_query_vars : $this->from_query_vars;
67
 
68
- return array_merge( $extra_qv, $base_qv, array(
69
  'suppress_filters' => false,
70
- 'ignore_sticky_posts' => true
71
  ) );
72
  }
73
 
 
 
 
 
 
 
 
 
74
  public function get_connected( $post_id, $extra_qv = array() ) {
75
- $args = $this->get_base_args( $extra_qv );
 
 
 
 
 
 
 
 
 
76
 
77
  $args['connected_query'] = array(
78
  'posts' => $post_id,
79
  'direction' => $this->direction
80
  );
81
 
82
- $args['connected_meta'] = $this->data;
83
-
84
- if ( $this->is_sortable() ) {
85
- _p2p_append( $args, array(
86
- 'connected_orderby' => $this->sortable,
87
- 'connected_order' => 'ASC',
88
- 'connected_order_num' => true,
89
- ) );
90
- }
91
-
92
- $args = apply_filters( 'p2p_connected_args', $args, $this );
93
-
94
- return new WP_Query( $args );
95
  }
96
 
 
 
 
 
 
 
 
 
97
  public function get_connectable( $post_id, $extra_qv = array() ) {
98
- $args = $this->get_base_args( $extra_qv );
99
 
100
  if ( 'one' == $this->other_cardinality ) {
101
- $connected = $this->get_connected( 'any', array( 'fields' => 'ids' ) )->posts;
102
- } else if ( $this->prevent_duplicates ) {
103
- $connected = $this->get_connected( $post_id, array( 'fields' => 'ids' ) )->posts;
104
  }
105
 
106
- if ( !empty( $connected ) ) {
107
- _p2p_append( $args['post__not_in'], $connected );
 
 
 
 
 
 
108
  }
109
 
110
- $args = apply_filters( 'p2p_connectable_args', $args, $this );
111
 
112
  return new WP_Query( $args );
113
  }
@@ -116,11 +136,19 @@ class P2P_Directed_Connection_Type {
116
  $connected = $this->get_connected( $from, array( 'post__in' => array( $to ) ) );
117
 
118
  if ( !empty( $connected->posts ) )
119
- return $connected->posts[0]->p2p_id;
120
 
121
  return false;
122
  }
123
 
 
 
 
 
 
 
 
 
124
  public function connect( $from, $to ) {
125
  if ( !get_post( $from ) || !get_post( $to ) )
126
  return false;
@@ -149,15 +177,26 @@ class P2P_Directed_Connection_Type {
149
  return $p2p_id;
150
  }
151
 
 
 
 
 
 
 
152
  public function disconnect( $from, $to ) {
153
  return P2P_Storage::delete( $this->get_p2p_id( $from, $to ) );
154
  }
155
 
 
 
 
 
 
156
  public function disconnect_all( $from ) {
157
  $connected = $this->get_connected( $from );
158
 
159
- foreach ( wp_list_pluck( $connected->posts, 'p2p_id' ) as $p2p_id )
160
- P2P_Storage::delete( $p2p_id );
161
  }
162
  }
163
 
26
  }
27
 
28
  function __get( $key ) {
29
+ return $this->ctype->$key;
30
+ }
31
+
32
+ function __isset( $key ) {
33
+ return isset( $this->ctype->$key );
34
+ }
35
 
36
+ public function get_direction() {
37
+ return $this->direction;
38
+ }
39
 
40
+ public function set_direction( $direction ) {
41
+ return $this->ctype->set_direction( $direction );
42
  }
43
 
44
  public function lose_direction() {
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
+ /**
76
+ * Get a list of posts that are connected to a given post.
77
+ *
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
  }
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;
177
  return $p2p_id;
178
  }
179
 
180
+ /**
181
+ * Disconnect two posts.
182
+ *
183
+ * @param int The first end of the connection.
184
+ * @param int The second end of the connection.
185
+ */
186
  public function disconnect( $from, $to ) {
187
  return P2P_Storage::delete( $this->get_p2p_id( $from, $to ) );
188
  }
189
 
190
+ /**
191
+ * Delete all connections for a certain post.
192
+ *
193
+ * @param int The post id.
194
+ */
195
  public function disconnect_all( $from ) {
196
  $connected = $this->get_connected( $from );
197
 
198
+ foreach ( $connected->posts as $post )
199
+ P2P_Storage::delete( $post->p2p_id );
200
  }
201
  }
202
 
core/ordered-type.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.php CHANGED
@@ -6,9 +6,9 @@
6
  class P2P_Query {
7
 
8
  function init() {
9
- add_action( 'parse_query', array( 'P2P_Query', 'parse_legacy_qv' ) );
10
- add_filter( 'posts_clauses', array( 'P2P_Query', 'posts_clauses' ), 10, 2 );
11
- add_filter( 'the_posts', array( 'P2P_Query', 'the_posts' ), 11, 2 );
12
  }
13
 
14
  function parse_legacy_qv( $wp_query ) {
@@ -118,7 +118,7 @@ class P2P_Query {
118
  /**
119
  * Pre-populates the p2p meta cache to decrease the number of queries.
120
  */
121
- function the_posts( $the_posts, $wp_query ) {
122
  if ( empty( $the_posts ) )
123
  return $the_posts;
124
 
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 ) {
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
 
core/storage.php CHANGED
@@ -2,6 +2,9 @@
2
 
3
  class P2P_Storage {
4
 
 
 
 
5
  private static $version = 3;
6
 
7
  function init() {
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() {
core/type.php CHANGED
@@ -12,7 +12,7 @@ class P2P_Connection_Type {
12
  'from_query_vars' => array(),
13
  'to_query_vars' => array(),
14
  'data' => array(),
15
- 'reciprocal' => null,
16
  'cardinality' => 'many-to-many',
17
  'prevent_duplicates' => true,
18
  'sortable' => false,
@@ -42,10 +42,11 @@ class P2P_Connection_Type {
42
  return self::$instances[ $id ] = new P2P_Connection_Type( $args );
43
  }
44
 
45
- public function get( $hash = false ) {
46
- if ( !$hash )
47
- return self::$instances;
48
 
 
49
  if ( isset( self::$instances[ $hash ] ) )
50
  return self::$instances[ $hash ];
51
 
@@ -54,55 +55,82 @@ class P2P_Connection_Type {
54
 
55
 
56
  protected $args;
 
57
 
58
  protected function __construct( $args ) {
59
  $this->args = $args;
60
 
61
  $common = array_intersect( $this->from, $this->to );
62
 
63
- if ( !empty( $common ) && count( $this->from ) + count( $this->to ) > 2 )
64
- $this->args['reciprocal'] = false;
65
 
66
- if ( is_null( $args['reciprocal'] ) )
67
- $this->args['reciprocal'] = ( $this->from == $this->to );
68
  }
69
 
70
  public function __get( $key ) {
71
- if ( in_array( $key, array( 'from', 'to' ) ) )
72
  return $this->args[ "{$key}_query_vars" ]['post_type'];
73
 
 
 
 
74
  return $this->args[$key];
75
  }
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  /**
78
- * Get connection direction.
79
  *
80
  * @param int|string $arg A post id or a post type.
 
81
  *
82
- * @return bool|string False on failure, 'any', 'to' or 'from' on success.
83
  */
84
- public function find_direction( $arg ) {
85
- if ( $post_id = (int) $arg ) {
86
- $post = get_post( $post_id );
87
- if ( !$post )
88
- return false;
89
- $post_type = $post->post_type;
90
- } else {
91
- $post_type = $arg;
92
- }
93
 
94
- if ( in_array( $post_type, $this->from ) ) {
95
- if ( $this->reciprocal && in_array( $post_type, $this->to ) )
96
- $direction = 'any';
97
- else
98
- $direction = 'from';
99
- } elseif ( in_array( $post_type, $this->to ) ) {
100
- $direction = 'to';
101
- } else {
102
  return false;
103
- }
104
 
105
- return new P2P_Directed_Connection_Type( $this, $direction );
 
 
 
 
 
 
106
  }
107
 
108
  /**
@@ -164,78 +192,90 @@ class P2P_Connection_Type {
164
  }
165
 
166
  /**
167
- * Get a list of posts that are connected to a given post.
168
  *
169
- * @param int $post_id A post id.
170
  * @param array $extra_qv Additional query variables to use.
171
  *
172
  * @return bool|object False on failure; A WP_Query instance on success.
173
  */
174
- public function get_connected( $post_id, $extra_qv = array() ) {
175
- $directed = $this->find_direction( $post_id );
176
- if ( !$directed )
 
 
177
  return false;
178
 
179
- return $directed->get_connected( $post_id, $extra_qv );
 
 
 
 
 
 
 
180
  }
181
 
182
  /**
183
- * Get a list of posts that could be connected to a given post.
184
  *
185
- * @param int $post_id A post id.
186
- * @param array $extra_qv Additional query variables to use.
187
  *
188
- * @return bool|object False on failure; A WP_Query instance on success.
189
  */
190
- public function get_connectable( $post_id, $extra_qv = array() ) {
191
- $directed = $this->find_direction( $post_id );
192
- if ( !$directed )
193
- return false;
194
-
195
- return $directed->get_connectable( $post_id, $extra_qv );
196
  }
197
 
198
  /**
199
- * Connect two posts.
200
  *
201
  * @param int The first end of the connection.
202
  * @param int The second end of the connection.
203
  *
204
- * @return int p2p_id
205
  */
206
- public function connect( $from, $to ) {
207
- $directed = $this->find_direction( $from );
208
- if ( !$directed )
209
- return false;
210
-
211
- return $directed->connect( $from, $to );
212
  }
213
 
214
  /**
215
- * Disconnect two posts.
216
  *
217
  * @param int The first end of the connection.
218
  * @param int The second end of the connection.
 
 
 
219
  */
220
- public function disconnect( $from, $to ) {
221
- $directed = $this->find_direction( $from );
222
  if ( !$directed )
223
  return false;
224
 
225
- return $directed->disconnect( $from, $to );
226
- }
227
 
228
- /**
229
- * Delete all connections for a certain post.
230
- *
231
- * @param int The post id.
232
- */
233
- public function disconnect_all( $from ) {
234
- $directed = $this->find_direction( $from );
235
- if ( !$directed )
 
 
 
 
 
 
 
 
236
  return false;
237
 
238
- return $directed->disconnect_all( $from );
239
  }
240
 
241
  /**
@@ -246,13 +286,5 @@ class P2P_Connection_Type {
246
  public function delete_connection( $p2p_id ) {
247
  return P2P_Storage::delete( $p2p_id );
248
  }
249
-
250
- public function get_p2p_id( $from, $to ) {
251
- $directed = $this->find_direction( $from );
252
- if ( !$directed )
253
- return false;
254
-
255
- return $directed->get_p2p_id( $from, $to );
256
- }
257
  }
258
 
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,
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
 
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
+ }
92
+
93
+ /**
94
+ * Set the direction.
95
+ *
96
+ * @param string $direction Can be 'from', 'to' or 'any'.
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
  /**
111
+ * Attempt to guess direction based on a post id or post type.
112
  *
113
  * @param int|string $arg A post id or a post type.
114
+ * @param bool Whether to return an instance of P2P_Directed_Connection_Type or just the direction
115
  *
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
  /**
192
  }
193
 
194
  /**
195
+ * Get a list of posts connected to other posts connected to a post.
196
  *
197
+ * @param int|array $post_id A post id or array of post ids
198
  * @param array $extra_qv Additional query variables to use.
199
  *
200
  * @return bool|object False on failure; A WP_Query instance on success.
201
  */
202
+ public function get_related( $post_id, $extra_qv = array() ) {
203
+ $post_id = (array) $post_id;
204
+
205
+ $connected = $this->get_connected( $post_id, $extra_qv );
206
+ if ( !$connected )
207
  return false;
208
 
209
+ if ( !$connected->have_posts() )
210
+ return $connected;
211
+
212
+ $connected_ids = wp_list_pluck( $connected->posts, 'ID' );
213
+
214
+ return $this->get_connected( $connected_ids, array(
215
+ 'post__not_in' => $post_id,
216
+ ) );
217
  }
218
 
219
  /**
220
+ * Get the previous post in an ordered connection.
221
  *
222
+ * @param int The first end of the connection.
223
+ * @param int The second end of the connection.
224
  *
225
+ * @return bool|object False on failure, post object on success
226
  */
227
+ public function get_previous( $from, $to ) {
228
+ return $this->get_adjacent( $from, $to, -1 );
 
 
 
 
229
  }
230
 
231
  /**
232
+ * Get the next post in an ordered connection.
233
  *
234
  * @param int The first end of the connection.
235
  * @param int The second end of the connection.
236
  *
237
+ * @return bool|object False on failure, post object on success
238
  */
239
+ public function get_next( $from, $to ) {
240
+ return $this->get_adjacent( $from, $to, +1 );
 
 
 
 
241
  }
242
 
243
  /**
244
+ * Get another post in an ordered connection.
245
  *
246
  * @param int The first end of the connection.
247
  * @param int The second end of the connection.
248
+ * @param int The position relative to the first parameter
249
+ *
250
+ * @return bool|object False on failure, post object on success
251
  */
252
+ public function get_adjacent( $from, $to, $which ) {
253
+ $directed = $this->find_direction( $to );
254
  if ( !$directed )
255
  return false;
256
 
257
+ if ( !method_exists( $directed, 'get_orderby_key' ) )
258
+ return false;
259
 
260
+ $p2p_id = $directed->get_p2p_id( $to, $from );
261
+ if ( !$p2p_id )
262
+ return false;
263
+
264
+ $order = (int) p2p_get_meta( $p2p_id, $directed->get_orderby_key(), true );
265
+
266
+ $adjacent = $directed->get_connected( $to, array(
267
+ 'connected_meta' => array(
268
+ array(
269
+ 'key' => $directed->get_orderby_key(),
270
+ 'value' => $order + $which
271
+ )
272
+ )
273
+ ) )->posts;
274
+
275
+ if ( empty( $adjacent ) )
276
  return false;
277
 
278
+ return $adjacent[0];
279
  }
280
 
281
  /**
286
  public function delete_connection( $p2p_id ) {
287
  return P2P_Storage::delete( $p2p_id );
288
  }
 
 
 
 
 
 
 
 
289
  }
290
 
core/url-query.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ 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
+
35
+ P2P_URL_Query::init();
36
+
core/util.php ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
core/widget.php CHANGED
@@ -4,6 +4,7 @@ class P2P_Widget extends scbWidget {
4
 
5
  protected $defaults = array(
6
  'ctype' => false,
 
7
  );
8
 
9
  static function init( $file ) {
@@ -20,39 +21,59 @@ class P2P_Widget extends scbWidget {
20
  if ( empty( $instance ) )
21
  $instance = $this->defaults;
22
 
23
- $ctypes = array_map( array( __CLASS__, 'ctype_label' ), P2P_Connection_Type::get() );
24
 
25
- echo $this->input( array(
26
  'type' => 'select',
27
  'name' => 'ctype',
28
  'values' => $ctypes,
29
  'desc' => __( 'Connection type:', P2P_TEXTDOMAIN )
30
- ), $instance );
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  }
32
 
33
  function widget( $args, $instance ) {
34
  if ( !is_singular() )
35
  return;
36
 
37
- $ctype = P2P_Connection_Type::get_instance( $instance['ctype'] );
 
 
 
 
38
  if ( !$ctype )
39
  return;
40
 
41
- $directed = $ctype->find_direction( get_queried_object_id() );
42
  if ( !$directed )
43
  return;
44
 
45
- $connected = $directed->get_connected( get_queried_object_id() );
 
 
 
 
 
 
 
 
 
 
46
  if ( !$connected->have_posts() )
47
  return;
48
 
49
- $title = $directed->get_title();
50
-
51
- if ( empty( $title ) ) {
52
- $ptype = get_post_type_object( $directed->get_other_post_type() );
53
- $title = sprintf( __( 'Related %s', P2P_TEXTDOMAIN ), $ptype->label );
54
- }
55
-
56
  $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
57
 
58
  extract( $args );
@@ -72,14 +93,14 @@ class P2P_Widget extends scbWidget {
72
  $$key = implode( ', ', array_map( array( __CLASS__, 'post_type_label' ), $ctype->$key ) );
73
  }
74
 
75
- if ( $ctype->reciprocal || $ctype->to == $ctype->from )
76
  $arrow = '&harr;';
77
  else
78
  $arrow = '&rarr;';
79
 
80
  $label = "$from $arrow $to";
81
 
82
- $title = $ctype->find_direction( $ctype->from[0] )->get_title();
83
 
84
  if ( $title )
85
  $label .= " ($title)";
4
 
5
  protected $defaults = array(
6
  'ctype' => false,
7
+ 'listing' => 'connected'
8
  );
9
 
10
  static function init( $file ) {
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',
28
  'name' => 'ctype',
29
  'values' => $ctypes,
30
  'desc' => __( 'Connection type:', P2P_TEXTDOMAIN )
31
+ ), $instance ) );
32
+
33
+ echo html( 'p',
34
+ __( 'Connection listing:', P2P_TEXTDOMAIN ),
35
+ '<br>',
36
+ $this->input( array(
37
+ 'type' => 'radio',
38
+ 'name' => 'listing',
39
+ 'values' => array(
40
+ 'connected' => __( 'connected', P2P_TEXTDOMAIN ),
41
+ 'related' => __( 'related', P2P_TEXTDOMAIN )
42
+ ),
43
+ ), $instance )
44
+ );
45
  }
46
 
47
  function widget( $args, $instance ) {
48
  if ( !is_singular() )
49
  return;
50
 
51
+ $instance = array_merge( $this->defaults, $instance );
52
+
53
+ $post_id = get_queried_object_id();
54
+
55
+ $ctype = p2p_type( $instance['ctype'] );
56
  if ( !$ctype )
57
  return;
58
 
59
+ $directed = $ctype->find_direction( $post_id );
60
  if ( !$directed )
61
  return;
62
 
63
+ if ( 'related' == $instance['listing'] ) {
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() )
75
  return;
76
 
 
 
 
 
 
 
 
77
  $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
78
 
79
  extract( $args );
93
  $$key = implode( ', ', array_map( array( __CLASS__, 'post_type_label' ), $ctype->$key ) );
94
  }
95
 
96
+ if ( $ctype->indeterminate )
97
  $arrow = '&harr;';
98
  else
99
  $arrow = '&rarr;';
100
 
101
  $label = "$from $arrow $to";
102
 
103
+ $title = $ctype->set_direction( 'from' )->get_title();
104
 
105
  if ( $title )
106
  $label .= " ($title)";
lang/posts-to-posts-ro_RO.mo CHANGED
Binary file
lang/posts-to-posts-ro_RO.po CHANGED
@@ -7,85 +7,105 @@ 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-09-24 13:12:56+00:00\n"
11
- "PO-Revision-Date: 2011-09-24 16:15+0200\n"
12
- "Last-Translator: scribu <scribu@gmail.com>\n"
13
  "Language-Team: ROMANIAN <LL@li.org>\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
- #: scb/AdminPage.php:163
20
- msgid "Settings <strong>saved</strong>."
21
- msgstr "Setări <strong>salvate</strong>."
22
-
23
- #: scb/AdminPage.php:176
24
- #: scb/AdminPage.php:187
25
- msgid "Save Changes"
26
- msgstr "Salvează schimbările"
27
-
28
- #: scb/AdminPage.php:348
29
- msgid "Settings"
30
- msgstr "Setări"
31
-
32
- #: admin/fields.php:13
33
- msgid "Create connection"
34
- msgstr "Crează conexiune"
35
-
36
- #: admin/fields.php:25
37
- msgid "Delete all connections"
38
- msgstr "Șterge toate conexiunile"
39
-
40
- #: admin/fields.php:34
41
- msgid "Delete connection"
42
- msgstr "Șterge conexiune"
43
-
44
- #: admin/box.php:59
45
  msgid "Are you sure you want to delete all connections?"
46
  msgstr "Sigur vreți să ștergeți toate conexiunile?"
47
 
48
- #: admin/box.php:69
49
- msgid "Connected %s"
50
- msgstr "%s conectate"
51
-
52
- #: admin/box.php:116
53
  msgid "Create connections:"
54
  msgstr "Creează conexiuni:"
55
 
56
- #: admin/box.php:125
57
  msgid "Search"
58
  msgstr "Căutare"
59
 
60
- #: admin/box.php:133
61
  msgid "View All"
62
  msgstr "Vezi toate"
63
 
64
- #: admin/box.php:216
65
  msgid "previous"
66
  msgstr "anterioare"
67
 
68
- #: admin/box.php:217
69
  msgid "next"
70
  msgstr "următoare"
71
 
72
- #: admin/box.php:218
73
  msgid "of"
74
  msgstr "din"
75
 
76
- #: core/widget.php:14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  msgid "Posts 2 Posts"
78
  msgstr "Posts 2 Posts"
79
 
80
- #: core/widget.php:15
81
  msgid "A list of posts connected to the current post"
82
  msgstr "O lista de postări conectate la postarea curentă"
83
 
84
- #: core/widget.php:29
85
  msgid "Connection type:"
86
  msgstr "Tipul conexiunii:"
87
 
88
- #: core/widget.php:53
 
 
 
 
 
 
 
 
 
 
 
 
89
  msgid "Related %s"
90
  msgstr "%s înrudite"
91
 
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"
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: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
+
84
+ #: core/widget.php:15
85
  msgid "Posts 2 Posts"
86
  msgstr "Posts 2 Posts"
87
 
88
+ #: core/widget.php:16
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
 
lang/posts-to-posts.pot CHANGED
@@ -4,7 +4,7 @@ msgid ""
4
  msgstr ""
5
  "Project-Id-Version: \n"
6
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/p2p\n"
7
- "POT-Creation-Date: 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"
@@ -12,74 +12,94 @@ msgstr ""
12
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
14
 
15
- #: scb/AdminPage.php:163
16
- msgid "Settings <strong>saved</strong>."
17
  msgstr ""
18
 
19
- #: scb/AdminPage.php:176 scb/AdminPage.php:187
20
- msgid "Save Changes"
21
  msgstr ""
22
 
23
- #: scb/AdminPage.php:348
24
- msgid "Settings"
25
  msgstr ""
26
 
27
- #: admin/fields.php:13
28
- msgid "Create connection"
29
  msgstr ""
30
 
31
- #: admin/fields.php:25
32
- msgid "Delete all connections"
33
  msgstr ""
34
 
35
- #: admin/fields.php:34
36
- msgid "Delete connection"
37
  msgstr ""
38
 
39
- #: admin/box.php:59
40
- msgid "Are you sure you want to delete all connections?"
41
  msgstr ""
42
 
43
- #: admin/box.php:69
44
- msgid "Connected %s"
45
  msgstr ""
46
 
47
- #: admin/box.php:116
48
- msgid "Create connections:"
49
  msgstr ""
50
 
51
- #: admin/box.php:125
52
- msgid "Search"
53
  msgstr ""
54
 
55
- #: admin/box.php:133
56
- msgid "View All"
57
  msgstr ""
58
 
59
- #: admin/box.php:216
60
- msgid "previous"
61
  msgstr ""
62
 
63
- #: admin/box.php:217
64
- msgid "next"
65
  msgstr ""
66
 
67
- #: admin/box.php:218
68
- msgid "of"
69
  msgstr ""
70
 
71
- #: core/widget.php:14
72
- msgid "Posts 2 Posts"
 
 
 
 
73
  msgstr ""
74
 
75
  #: core/widget.php:15
 
 
 
 
76
  msgid "A list of posts connected to the current post"
77
  msgstr ""
78
 
79
- #: core/widget.php:29
80
  msgid "Connection type:"
81
  msgstr ""
82
 
83
- #: core/widget.php:53
 
 
 
 
 
 
 
 
 
 
 
 
84
  msgid "Related %s"
85
  msgstr ""
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"
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
 
79
  #: core/widget.php:15
80
+ msgid "Posts 2 Posts"
81
+ msgstr ""
82
+
83
+ #: core/widget.php:16
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 ""
mustache/Mustache.php CHANGED
@@ -14,7 +14,8 @@
14
  */
15
  class Mustache {
16
 
17
- const VERSION = '0.7.1';
 
18
 
19
  /**
20
  * Should this Mustache throw exceptions when it finds unexpected tags?
@@ -238,7 +239,10 @@ class Mustache {
238
 
239
  // regular section
240
  case '#':
241
- if ($this->_varIsIterable($val)) {
 
 
 
242
  foreach ($val as $local_context) {
243
  $this->_pushContext($local_context);
244
  $rendered_content .= $this->_renderTemplate($content);
@@ -615,7 +619,8 @@ class Mustache {
615
  * @return string
616
  */
617
  protected function _renderEscaped($tag_name, $leading, $trailing) {
618
- return $leading . htmlentities($this->_getVariable($tag_name), ENT_COMPAT, $this->_charset) . $trailing;
 
619
  }
620
 
621
  /**
@@ -647,7 +652,13 @@ class Mustache {
647
  * @return string
648
  */
649
  protected function _renderUnescaped($tag_name, $leading, $trailing) {
650
- return $leading . $this->_getVariable($tag_name) . $trailing;
 
 
 
 
 
 
651
  }
652
 
653
  /**
@@ -808,7 +819,10 @@ class Mustache {
808
  * @return string
809
  */
810
  protected function _getPartial($tag_name) {
811
- if (is_array($this->_partials) && isset($this->_partials[$tag_name])) {
 
 
 
812
  return $this->_partials[$tag_name];
813
  }
814
 
@@ -829,6 +843,23 @@ class Mustache {
829
  protected function _varIsIterable($var) {
830
  return $var instanceof Traversable || (is_array($var) && !array_diff_key($var, array_keys(array_keys($var))));
831
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
832
  }
833
 
834
 
@@ -859,4 +890,3 @@ class MustacheException extends Exception {
859
  const UNKNOWN_PRAGMA = 4;
860
 
861
  }
862
-
14
  */
15
  class Mustache {
16
 
17
+ const VERSION = '0.8.0';
18
+ const SPEC_VERSION = '1.1.2';
19
 
20
  /**
21
  * Should this Mustache throw exceptions when it finds unexpected tags?
239
 
240
  // regular section
241
  case '#':
242
+ // higher order sections
243
+ if ($this->_varIsCallable($val)) {
244
+ $rendered_content = $this->_renderTemplate(call_user_func($val, $content));
245
+ } else if ($this->_varIsIterable($val)) {
246
  foreach ($val as $local_context) {
247
  $this->_pushContext($local_context);
248
  $rendered_content .= $this->_renderTemplate($content);
619
  * @return string
620
  */
621
  protected function _renderEscaped($tag_name, $leading, $trailing) {
622
+ $rendered = htmlentities($this->_renderUnescaped($tag_name, '', ''), ENT_COMPAT, $this->_charset);
623
+ return $leading . $rendered . $trailing;
624
  }
625
 
626
  /**
652
  * @return string
653
  */
654
  protected function _renderUnescaped($tag_name, $leading, $trailing) {
655
+ $val = $this->_getVariable($tag_name);
656
+
657
+ if ($this->_varIsCallable($val)) {
658
+ $val = $this->_renderTemplate(call_user_func($val));
659
+ }
660
+
661
+ return $leading . $val . $trailing;
662
  }
663
 
664
  /**
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
 
843
  protected function _varIsIterable($var) {
844
  return $var instanceof Traversable || (is_array($var) && !array_diff_key($var, array_keys(array_keys($var))));
845
  }
846
+
847
+ /**
848
+ * Higher order sections helper: tests whether the section $var is a valid callback.
849
+ *
850
+ * In Mustache.php, a variable is considered 'callable' if the variable is:
851
+ *
852
+ * 1. an anonymous function.
853
+ * 2. an object and the name of a public function, i.e. `array($SomeObject, 'methodName')`
854
+ * 3. a class name and the name of a public static function, i.e. `array('SomeClass', 'methodName')`
855
+ *
856
+ * @access protected
857
+ * @param mixed $var
858
+ * @return bool
859
+ */
860
+ protected function _varIsCallable($var) {
861
+ return !is_string($var) && is_callable($var);
862
+ }
863
  }
864
 
865
 
890
  const UNKNOWN_PRAGMA = 4;
891
 
892
  }
 
mustache/MustacheLoader.php ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * A Mustache Partial filesystem loader.
5
+ *
6
+ * @author Justin Hileman {@link http://justinhileman.com}
7
+ */
8
+ class MustacheLoader implements ArrayAccess {
9
+
10
+ protected $baseDir;
11
+ protected $partialsCache = array();
12
+ protected $extension;
13
+
14
+ /**
15
+ * MustacheLoader constructor.
16
+ *
17
+ * @access public
18
+ * @param string $baseDir Base template directory.
19
+ * @param string $extension File extension for Mustache files (default: 'mustache')
20
+ * @return void
21
+ */
22
+ public function __construct($baseDir, $extension = 'mustache') {
23
+ if (!is_dir($baseDir)) {
24
+ throw new InvalidArgumentException('$baseDir must be a valid directory, ' . $baseDir . ' given.');
25
+ }
26
+
27
+ $this->baseDir = $baseDir;
28
+ $this->extension = $extension;
29
+ }
30
+
31
+ /**
32
+ * @param string $offset Name of partial
33
+ * @return boolean
34
+ */
35
+ public function offsetExists($offset) {
36
+ return (isset($this->partialsCache[$offset]) || file_exists($this->pathName($offset)));
37
+ }
38
+
39
+ /**
40
+ * @throws InvalidArgumentException if the given partial doesn't exist
41
+ * @param string $offset Name of partial
42
+ * @return string Partial template contents
43
+ */
44
+ public function offsetGet($offset) {
45
+ if (!$this->offsetExists($offset)) {
46
+ throw new InvalidArgumentException('Partial does not exist: ' . $offset);
47
+ }
48
+
49
+ if (!isset($this->partialsCache[$offset])) {
50
+ $this->partialsCache[$offset] = file_get_contents($this->pathName($offset));
51
+ }
52
+
53
+ return $this->partialsCache[$offset];
54
+ }
55
+
56
+ /**
57
+ * MustacheLoader is an immutable filesystem loader. offsetSet throws a LogicException if called.
58
+ *
59
+ * @throws LogicException
60
+ * @return void
61
+ */
62
+ public function offsetSet($offset, $value) {
63
+ throw new LogicException('Unable to set offset: MustacheLoader is an immutable ArrayAccess object.');
64
+ }
65
+
66
+ /**
67
+ * MustacheLoader is an immutable filesystem loader. offsetUnset throws a LogicException if called.
68
+ *
69
+ * @throws LogicException
70
+ * @return void
71
+ */
72
+ public function offsetUnset($offset) {
73
+ throw new LogicException('Unable to unset offset: MustacheLoader is an immutable ArrayAccess object.');
74
+ }
75
+
76
+ /**
77
+ * An internal helper for generating path names.
78
+ *
79
+ * @param string $file Partial name
80
+ * @return string File path
81
+ */
82
+ protected function pathName($file) {
83
+ return $this->baseDir . '/' . $file . '.' . $this->extension;
84
+ }
85
+ }
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: 0.9.5
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ă (scribu@gmail.com)
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', '0.9.5' );
30
 
31
  define( 'P2P_TEXTDOMAIN', 'posts-to-posts' );
32
 
@@ -37,22 +37,27 @@ function _p2p_init() {
37
 
38
  load_plugin_textdomain( P2P_TEXTDOMAIN, '', basename( $base ) . '/lang' );
39
 
40
- foreach ( array( 'storage', 'query', 'directed-type', 'type', 'api', 'widget' ) as $file )
41
- require_once "$base/core/$file.php";
 
 
 
42
 
43
  P2P_Widget::init( __FILE__ );
44
 
45
  if ( is_admin() ) {
46
- foreach ( array( 'base', 'box', 'fields' ) as $file )
47
- require_once "$base/admin/$file.php";
 
 
 
48
  }
49
  }
50
  scb_init( '_p2p_init' );
51
 
52
- function _p2p_append( &$arr, $values ) {
53
- if ( !is_array( $arr ) )
54
- $arr = array();
55
 
56
- $arr = array_merge( $arr, $values );
 
 
57
  }
58
 
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
  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
  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
 
37
 
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(
50
+ 'utils',
51
+ 'box-factory', 'box', 'fields',
52
+ 'column-factory', 'column'
53
+ ) );
54
  }
55
  }
56
  scb_init( '_p2p_init' );
57
 
 
 
 
58
 
59
+ function _p2p_load_files( $dir, $files ) {
60
+ foreach ( $files as $file )
61
+ require_once "$dir/$file.php";
62
  }
63
 
readme.txt CHANGED
@@ -3,7 +3,7 @@ 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: 0.9.5
7
 
8
  Create connections between posts
9
 
@@ -13,8 +13,9 @@ This plugin allows you to create many-to-many relationships between posts of any
13
 
14
  A few example use cases:
15
 
16
- * 'review' posts connected to 'product' posts
17
  * manually curated lists of related posts
 
18
 
19
  etc.
20
 
@@ -41,9 +42,29 @@ Make sure your host is running PHP 5. The only foolproof way to do this is to ad
41
 
42
  1. Basic connection metabox
43
  2. Advanced connection metabox
 
 
44
 
45
  == Changelog ==
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  = 0.9.5 =
48
  * add '{from|to}_query_vars' args to p2p_register_connection_type()
49
  * add 'cardinality' arg to p2p_register_connection_type()
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
 
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
 
42
 
43
  1. Basic connection metabox
44
  2. Advanced connection metabox
45
+ 3. Admin column
46
+ 4. Widget
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
53
+ * improve metabox styling
54
+ * rename 'show_ui' to 'admin_box'
55
+ * add 'admin_column' parameter
56
+
57
+ = 1.0 =
58
+ * widget can now list related posts
59
+ * add P2P_Connection_Type::get_related() method
60
+ * add 'can_create_post' arg to p2p_register_connection_type()
61
+ * two-box mode for `'reciprocal' => false`
62
+ * more options for 'show_ui'
63
+ * allow checkboxes, radio buttons and textareas as connection fields
64
+ * allow drag & drop ordering in both directions
65
+ * added get_previous(), get_next() and get_adjacent() methods to P2P_Connection_Type
66
+ * [more info](http://scribu.net/wordpress/posts-to-posts/p2p-1-0.html)
67
+
68
  = 0.9.5 =
69
  * add '{from|to}_query_vars' args to p2p_register_connection_type()
70
  * add 'cardinality' arg to p2p_register_connection_type()
scb/BoxesPage.php CHANGED
@@ -25,8 +25,6 @@ abstract class scbBoxesPage extends scbAdminPage {
25
  parent::page_init();
26
 
27
  add_action( 'load-' . $this->pagehook, array( $this, 'boxes_init' ) );
28
-
29
- add_screen_option( 'layout_columns', array( 'max' => $this->args['columns'], 'default' => $this->args['columns'] ) );
30
  }
31
 
32
  function default_css() {
@@ -163,6 +161,11 @@ abstract class scbBoxesPage extends scbAdminPage {
163
  function boxes_init() {
164
  wp_enqueue_script( 'postbox' );
165
 
 
 
 
 
 
166
  $registered = array();
167
  foreach( $this->boxes as $box_args ) {
168
  @list( $name, $title, $context, $priority, $args ) = $box_args;
25
  parent::page_init();
26
 
27
  add_action( 'load-' . $this->pagehook, array( $this, 'boxes_init' ) );
 
 
28
  }
29
 
30
  function default_css() {
161
  function boxes_init() {
162
  wp_enqueue_script( 'postbox' );
163
 
164
+ add_screen_option( 'layout_columns', array(
165
+ 'max' => $this->args['columns'],
166
+ 'default' => $this->args['columns']
167
+ ) );
168
+
169
  $registered = array();
170
  foreach( $this->boxes as $box_args ) {
171
  @list( $name, $title, $context, $priority, $args ) = $box_args;
scb/Forms.php CHANGED
@@ -373,13 +373,14 @@ class scbForms {
373
  *
374
  * @param array|string $name The name of the value
375
  * @param array $value The data that will be traversed
 
376
  *
377
  * @return mixed
378
  */
379
- static function get_value( $name, $value ) {
380
  foreach ( (array) $name as $key ) {
381
  if ( !isset( $value[ $key ] ) )
382
- return null;
383
 
384
  $value = $value[$key];
385
  }
373
  *
374
  * @param array|string $name The name of the value
375
  * @param array $value The data that will be traversed
376
+ * @param mixed $fallback The value returned when the key is not found
377
  *
378
  * @return mixed
379
  */
380
+ static function get_value( $name, $value, $fallback = null ) {
381
  foreach ( (array) $name as $key ) {
382
  if ( !isset( $value[ $key ] ) )
383
+ return $fallback;
384
 
385
  $value = $value[$key];
386
  }
scb/load.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- $GLOBALS['_scb_data'] = array( 41, __FILE__, array(
4
  'scbUtil', 'scbOptions', 'scbForms', 'scbTable',
5
  'scbWidget', 'scbAdminPage', 'scbBoxesPage',
6
  'scbCron', 'scbHooks',
1
  <?php
2
 
3
+ $GLOBALS['_scb_data'] = array( 42, __FILE__, array(
4
  'scbUtil', 'scbOptions', 'scbForms', 'scbTable',
5
  'scbWidget', 'scbAdminPage', 'scbBoxesPage',
6
  'scbCron', 'scbHooks',
screenshot-1.png CHANGED
Binary file
screenshot-2.png CHANGED
Binary file
screenshot-3.png ADDED
Binary file
screenshot-4.png ADDED
Binary file