Groups - Version 1.3.9

Version Description

  • Fix: added filter hooked on posts_where motivated by pagination issues - the posts must be filtered before the totals are calculated in WP_Query::get_posts().
  • Improvement: modified the signature of the the_posts filter method in Groups_Post_Access to receive the $query by reference
  • Improvement: a substantial improvement on overall performance is achieved by caching user capabilities and groups
  • Fix: access restriction boxes showing capabilities that the user should not be allowed to set to restrict posts
  • Fix: resolve user-capability when a capability is deleted
Download this release

Release Info

Developer itthinx
Plugin Icon 128x128 Groups
Version 1.3.9
Comparing to
See all releases

Code changes from version 1.3.8 to 1.3.9

groups.php CHANGED
@@ -21,13 +21,13 @@
21
  * Plugin Name: Groups
22
  * Plugin URI: http://www.itthinx.com/plugins/groups
23
  * Description: Groups provides group-based user membership management, group-based capabilities and content access control.
24
- * Version: 1.3.8
25
  * Author: itthinx
26
  * Author URI: http://www.itthinx.com
27
  * Donate-Link: http://www.itthinx.com
28
  * License: GPLv3
29
  */
30
- define( 'GROUPS_CORE_VERSION', '1.3.8' );
31
  define( 'GROUPS_FILE', __FILE__ );
32
  if ( !defined( 'GROUPS_CORE_DIR' ) ) {
33
  define( 'GROUPS_CORE_DIR', WP_PLUGIN_DIR . '/groups' );
21
  * Plugin Name: Groups
22
  * Plugin URI: http://www.itthinx.com/plugins/groups
23
  * Description: Groups provides group-based user membership management, group-based capabilities and content access control.
24
+ * Version: 1.3.9
25
  * Author: itthinx
26
  * Author URI: http://www.itthinx.com
27
  * Donate-Link: http://www.itthinx.com
28
  * License: GPLv3
29
  */
30
+ define( 'GROUPS_CORE_VERSION', '1.3.9' );
31
  define( 'GROUPS_FILE', __FILE__ );
32
  if ( !defined( 'GROUPS_CORE_DIR' ) ) {
33
  define( 'GROUPS_CORE_DIR', WP_PLUGIN_DIR . '/groups' );
lib/access/class-groups-access-meta-boxes.php CHANGED
@@ -103,6 +103,7 @@ class Groups_Access_Meta_Boxes {
103
  }
104
 
105
  if ( self::user_can_restrict() ) {
 
106
  $output .= __( "Enforce read access", GROUPS_PLUGIN_DOMAIN );
107
  $read_caps = get_post_meta( $post_id, Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ_POST_CAPABILITY );
108
  $valid_read_caps = Groups_Options::get_option( Groups_Post_Access::READ_POST_CAPABILITIES, array( Groups_Post_Access::READ_POST_CAPABILITY ) );
@@ -110,13 +111,15 @@ class Groups_Access_Meta_Boxes {
110
  $output .= '<ul>';
111
  foreach( $valid_read_caps as $valid_read_cap ) {
112
  if ( $capability = Groups_Capability::read_by_capability( $valid_read_cap ) ) {
113
- $checked = in_array( $capability->capability, $read_caps ) ? ' checked="checked" ' : '';
114
- $output .= '<li>';
115
- $output .= '<label>';
116
- $output .= '<input name="' . self::CAPABILITY . '[]" ' . $checked . ' type="checkbox" value="' . esc_attr( $capability->capability_id ) . '" />';
117
- $output .= wp_filter_nohtml_kses( $capability->capability );
118
- $output .= '</label>';
119
- $output .= '</li>';
 
 
120
  }
121
  }
122
  $output .= '</ul>';
@@ -158,19 +161,18 @@ class Groups_Access_Meta_Boxes {
158
  // PHP Notice: Undefined offset: 0 in /var/www/groups-forums/wp-includes/capabilities.php on line 1067
159
  if ( current_user_can( 'edit_'.$post_type, $post_id ) ) {
160
  if ( self::user_can_restrict() ) {
161
- Groups_Post_Access::delete( $post_id, null );
162
- $user = new Groups_User( get_current_user_id() );
163
- if ( !empty( $_POST[self::CAPABILITY] ) ) {
164
- foreach ( $_POST[self::CAPABILITY] as $capability_id ) {
165
- if ( $capability = Groups_Capability::read( $capability_id ) ) {
166
- if ( $user->can( $capability->capability_id ) ) {
167
- Groups_Post_Access::create( array(
168
- 'post_id' => $post_id,
169
- 'capability' => $capability->capability
170
- ) );
171
- }
172
- }
173
- }
174
  }
175
  }
176
  }
@@ -191,12 +193,13 @@ class Groups_Access_Meta_Boxes {
191
  $post_types_option = Groups_Options::get_option( Groups_Post_Access::POST_TYPES, array() );
192
  if ( !isset( $post_types_option['attachment']['add_meta_box'] ) || $post_types_option['attachment']['add_meta_box'] ) {
193
  if ( self::user_can_restrict() ) {
 
194
  $output = "";
195
  $post_singular_name = __( 'Media', GROUPS_PLUGIN_DOMAIN );
196
 
197
  $output .= __( "Enforce read access", GROUPS_PLUGIN_DOMAIN );
198
- $read_caps = get_post_meta( $post->ID, Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ_POST_CAPABILITY );
199
- $valid_read_caps = Groups_Options::get_option( Groups_Post_Access::READ_POST_CAPABILITIES, array( Groups_Post_Access::READ_POST_CAPABILITY ) );
200
  $output .= '<div style="padding:0 1em;margin:1em 0;border:1px solid #ccc;border-radius:4px;">';
201
  $output .= '<ul>';
202
  foreach( $valid_read_caps as $valid_read_cap ) {
@@ -237,20 +240,30 @@ class Groups_Access_Meta_Boxes {
237
  public static function attachment_fields_to_save( $post, $attachment ) {
238
  $post_types_option = Groups_Options::get_option( Groups_Post_Access::POST_TYPES, array() );
239
  if ( !isset( $post_types_option['attachment']['add_meta_box'] ) || $post_types_option['attachment']['add_meta_box'] ) {
240
- if ( current_user_can( 'edit_attachment' ) ) {
241
- if ( self::user_can_restrict() ) {
242
- Groups_Post_Access::delete( $post['ID'], null );
243
- if ( !empty( $attachment[self::CAPABILITY] ) ) {
244
- foreach ( $attachment[self::CAPABILITY] as $capability_id ) {
245
- if ( $capability = Groups_Capability::read( $capability_id ) ) {
 
 
 
 
 
 
 
 
246
  Groups_Post_Access::create( array(
247
- 'post_id' => $post['ID'],
248
  'capability' => $capability->capability
249
- ) );
250
- }
251
- }
 
 
252
  }
253
- }
254
  }
255
  }
256
  return $post;
@@ -276,5 +289,21 @@ class Groups_Access_Meta_Boxes {
276
  return $has_read_cap;
277
  }
278
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  }
280
  Groups_Access_Meta_Boxes::init();
103
  }
104
 
105
  if ( self::user_can_restrict() ) {
106
+ $user = new Groups_User( get_current_user_id() );
107
  $output .= __( "Enforce read access", GROUPS_PLUGIN_DOMAIN );
108
  $read_caps = get_post_meta( $post_id, Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ_POST_CAPABILITY );
109
  $valid_read_caps = Groups_Options::get_option( Groups_Post_Access::READ_POST_CAPABILITIES, array( Groups_Post_Access::READ_POST_CAPABILITY ) );
111
  $output .= '<ul>';
112
  foreach( $valid_read_caps as $valid_read_cap ) {
113
  if ( $capability = Groups_Capability::read_by_capability( $valid_read_cap ) ) {
114
+ if ( $user->can( $capability->capability ) ) {
115
+ $checked = in_array( $capability->capability, $read_caps ) ? ' checked="checked" ' : '';
116
+ $output .= '<li>';
117
+ $output .= '<label>';
118
+ $output .= '<input name="' . self::CAPABILITY . '[]" ' . $checked . ' type="checkbox" value="' . esc_attr( $capability->capability_id ) . '" />';
119
+ $output .= wp_filter_nohtml_kses( $capability->capability );
120
+ $output .= '</label>';
121
+ $output .= '</li>';
122
+ }
123
  }
124
  }
125
  $output .= '</ul>';
161
  // PHP Notice: Undefined offset: 0 in /var/www/groups-forums/wp-includes/capabilities.php on line 1067
162
  if ( current_user_can( 'edit_'.$post_type, $post_id ) ) {
163
  if ( self::user_can_restrict() ) {
164
+ $valid_read_caps = self::get_valid_read_caps_for_user();
165
+ foreach( $valid_read_caps as $valid_read_cap ) {
166
+ if ( $capability = Groups_Capability::read_by_capability( $valid_read_cap ) ) {
167
+ if ( !empty( $_POST[self::CAPABILITY] ) && is_array( $_POST[self::CAPABILITY] ) && in_array( $capability->capability_id, $_POST[self::CAPABILITY] ) ) {
168
+ Groups_Post_Access::create( array(
169
+ 'post_id' => $post_id,
170
+ 'capability' => $capability->capability
171
+ ) );
172
+ } else {
173
+ Groups_Post_Access::delete( $post_id, $capability->capability );
174
+ }
175
+ }
 
176
  }
177
  }
178
  }
193
  $post_types_option = Groups_Options::get_option( Groups_Post_Access::POST_TYPES, array() );
194
  if ( !isset( $post_types_option['attachment']['add_meta_box'] ) || $post_types_option['attachment']['add_meta_box'] ) {
195
  if ( self::user_can_restrict() ) {
196
+ $user = new Groups_User( get_current_user_id() );
197
  $output = "";
198
  $post_singular_name = __( 'Media', GROUPS_PLUGIN_DOMAIN );
199
 
200
  $output .= __( "Enforce read access", GROUPS_PLUGIN_DOMAIN );
201
+ $read_caps = get_post_meta( $post->ID, Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ_POST_CAPABILITY );
202
+ $valid_read_caps = self::get_valid_read_caps_for_user();
203
  $output .= '<div style="padding:0 1em;margin:1em 0;border:1px solid #ccc;border-radius:4px;">';
204
  $output .= '<ul>';
205
  foreach( $valid_read_caps as $valid_read_cap ) {
240
  public static function attachment_fields_to_save( $post, $attachment ) {
241
  $post_types_option = Groups_Options::get_option( Groups_Post_Access::POST_TYPES, array() );
242
  if ( !isset( $post_types_option['attachment']['add_meta_box'] ) || $post_types_option['attachment']['add_meta_box'] ) {
243
+ // if we're here, we assume the user is allowed to edit attachments,
244
+ // but we still need to check if the user can restrict access
245
+ if ( self::user_can_restrict() ) {
246
+ $post_id = null;
247
+ if ( isset( $post['ID'] ) ) {
248
+ $post_id = $post['ID'];
249
+ } else if ( isset( $post['post_ID'] ) ) {
250
+ $post_id = $post['post_ID'];
251
+ }
252
+ if ( $post_id !== null ) {
253
+ $valid_read_caps = self::get_valid_read_caps_for_user();
254
+ foreach( $valid_read_caps as $valid_read_cap ) {
255
+ if ( $capability = Groups_Capability::read_by_capability( $valid_read_cap ) ) {
256
+ if ( !empty( $attachment[self::CAPABILITY] ) && is_array( $attachment[self::CAPABILITY] ) && in_array( $capability->capability_id, $attachment[self::CAPABILITY] ) ) {
257
  Groups_Post_Access::create( array(
258
+ 'post_id' => $post_id,
259
  'capability' => $capability->capability
260
+ ) );
261
+ } else {
262
+ Groups_Post_Access::delete( $post_id, $capability->capability );
263
+ }
264
+ }
265
  }
266
+ }
267
  }
268
  }
269
  return $post;
289
  return $has_read_cap;
290
  }
291
 
292
+ /**
293
+ * @return array of valid read capabilities for the current or given user
294
+ */
295
+ private static function get_valid_read_caps_for_user( $user_id = null ) {
296
+ $result = array();
297
+ $user = new Groups_User( $user_id === null ? get_current_user_id() : $user_id );
298
+ $valid_read_caps = Groups_Options::get_option( Groups_Post_Access::READ_POST_CAPABILITIES, array( Groups_Post_Access::READ_POST_CAPABILITY ) );
299
+ foreach( $valid_read_caps as $valid_read_cap ) {
300
+ if ( $capability = Groups_Capability::read_by_capability( $valid_read_cap ) ) {
301
+ if ( $user->can( $capability->capability ) ) {
302
+ $result[] = $valid_read_cap;
303
+ }
304
+ }
305
+ }
306
+ return $result;
307
+ }
308
  }
309
  Groups_Access_Meta_Boxes::init();
lib/access/class-groups-post-access.php CHANGED
@@ -21,6 +21,12 @@
21
 
22
  /**
23
  * Post access restrictions.
 
 
 
 
 
 
24
  */
25
  class Groups_Post_Access {
26
 
@@ -51,6 +57,7 @@ class Groups_Post_Access {
51
  */
52
  public static function init() {
53
  // post access
 
54
  add_filter( 'get_pages', array( __CLASS__, "get_pages" ), 1 );
55
  add_filter( 'the_posts', array( __CLASS__, "the_posts" ), 1, 2 );
56
  add_filter( 'wp_get_nav_menu_items', array( __CLASS__, "wp_get_nav_menu_items" ), 1, 3 );
@@ -89,6 +96,57 @@ class Groups_Post_Access {
89
  return $caps;
90
  }
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  /**
93
  * Filter pages by access capability.
94
  *
@@ -111,7 +169,7 @@ class Groups_Post_Access {
111
  * @param array $posts list of posts
112
  * @param WP_Query $query
113
  */
114
- public static function the_posts( $posts, $query ) {
115
  $result = array();
116
  $user_id = get_current_user_id();
117
  foreach ( $posts as $post ) {
@@ -293,5 +351,6 @@ class Groups_Post_Access {
293
  }
294
  return $result;
295
  }
 
296
  }
297
  Groups_Post_Access::init();
21
 
22
  /**
23
  * Post access restrictions.
24
+ *
25
+ * @todo when wp_count_posts() provides reasonable filters, use them so that
26
+ * the post counts displayed on top are in line with the actual posts that
27
+ * are displayed in the table; same for wp_count_attachments()
28
+ * @see http://core.trac.wordpress.org/ticket/16603
29
+ *
30
  */
31
  class Groups_Post_Access {
32
 
57
  */
58
  public static function init() {
59
  // post access
60
+ add_filter( 'posts_where', array( __CLASS__, 'posts_where' ), 10, 2 );
61
  add_filter( 'get_pages', array( __CLASS__, "get_pages" ), 1 );
62
  add_filter( 'the_posts', array( __CLASS__, "the_posts" ), 1, 2 );
63
  add_filter( 'wp_get_nav_menu_items', array( __CLASS__, "wp_get_nav_menu_items" ), 1, 3 );
96
  return $caps;
97
  }
98
 
99
+ /**
100
+ * Filters out posts that the user should not be able to access.
101
+ *
102
+ * @param string $where current where conditions
103
+ * @param WP_Query $query current query
104
+ * @return string modified $where
105
+ */
106
+ public static function posts_where( $where, &$query ) {
107
+
108
+ global $wpdb;
109
+
110
+ $user_id = get_current_user_id();
111
+
112
+ // this only applies to logged in users
113
+ if ( $user_id ) {
114
+ // if administrators can override access, don't filter
115
+ if ( get_option( GROUPS_ADMINISTRATOR_ACCESS_OVERRIDE, GROUPS_ADMINISTRATOR_ACCESS_OVERRIDE_DEFAULT ) ) {
116
+ if ( user_can( $user_id, 'administrator' ) ) {
117
+ return $where;
118
+ }
119
+ }
120
+ }
121
+
122
+ // 1. Get all the capabilities that the user has, including those that are inherited:
123
+ $caps = array();
124
+ if ( $user = new Groups_User( $user_id ) ) {
125
+ $capabilities = $user->capabilities_deep;
126
+ if ( is_array( $capabilities ) ) {
127
+ foreach ( $capabilities as $capability ) {
128
+ $caps[] = "'". $capability . "'";
129
+ }
130
+ }
131
+ }
132
+
133
+ if ( count( $caps ) > 0 ) {
134
+ $caps = implode( ',', $caps );
135
+ } else {
136
+ $caps = '\'\'';
137
+ }
138
+
139
+ // 2. Filter the posts that require a capability that the user doesn't
140
+ // have, or in other words: exclude posts that the user must NOT access:
141
+ $where .= sprintf(
142
+ " AND {$wpdb->posts}.ID NOT IN (SELECT DISTINCT ID FROM $wpdb->posts LEFT JOIN $wpdb->postmeta on {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id WHERE {$wpdb->postmeta}.meta_key = '%s' AND {$wpdb->postmeta}.meta_value NOT IN (%s) ) ",
143
+ self::POSTMETA_PREFIX . self::READ_POST_CAPABILITY,
144
+ $caps
145
+ );
146
+
147
+ return $where;
148
+ }
149
+
150
  /**
151
  * Filter pages by access capability.
152
  *
169
  * @param array $posts list of posts
170
  * @param WP_Query $query
171
  */
172
+ public static function the_posts( $posts, &$query ) {
173
  $result = array();
174
  $user_id = get_current_user_id();
175
  foreach ( $posts as $post ) {
351
  }
352
  return $result;
353
  }
354
+
355
  }
356
  Groups_Post_Access::init();
lib/core/class-groups-user-capability.php CHANGED
@@ -35,6 +35,8 @@ class Groups_User_Capability {
35
  // when a user is deleted, user-capabilities must be removed
36
  // triggered by wp_delete_user()
37
  add_action( "deleted_user", array( __CLASS__, "deleted_user" ) );
 
 
38
  }
39
 
40
  /**
@@ -177,5 +179,26 @@ class Groups_User_Capability {
177
  }
178
  }
179
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  }
181
  Groups_User_Capability::init();
35
  // when a user is deleted, user-capabilities must be removed
36
  // triggered by wp_delete_user()
37
  add_action( "deleted_user", array( __CLASS__, "deleted_user" ) );
38
+ // when a capability is deleted the relationship must also be resolved
39
+ add_action( 'groups_deleted_capability', array( __CLASS__, 'groups_deleted_capability' ) );
40
  }
41
 
42
  /**
179
  }
180
  }
181
  }
182
+
183
+ /**
184
+ * Hooks into groups_deleted_capability to resolve all existing relations
185
+ * between users and the deleted capability.
186
+ * @param int $capability_id
187
+ */
188
+ public static function groups_deleted_capability( $capability_id ) {
189
+ global $wpdb;
190
+
191
+ $user_capability_table = _groups_get_tablename( "user_capability" );
192
+ $rows = $wpdb->get_results( $wpdb->prepare(
193
+ "SELECT * FROM $user_capability_table WHERE capability_id = %d",
194
+ Groups_Utility::id( $capability_id )
195
+ ) );
196
+ if ( $rows ) {
197
+ foreach( $rows as $row ) {
198
+ // do NOT 'optimize' (must trigger actions ... same as above)
199
+ self::delete( $row->user_id, $row->capability_id );
200
+ }
201
+ }
202
+ }
203
  }
204
  Groups_User_Capability::init();
lib/core/class-groups-user.php CHANGED
@@ -26,14 +26,66 @@ require_once( GROUPS_CORE_LIB . "/class-groups-capability.php" );
26
  * User OPM.
27
  */
28
  class Groups_User implements I_Capable {
29
-
 
 
 
 
 
30
  /**
31
  * User object.
32
  *
33
  * @var WP_User
34
  */
35
  var $user = null;
36
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  /**
38
  * Create, if $user_id = 0 an anonymous user is assumed.
39
  *
@@ -46,20 +98,73 @@ class Groups_User implements I_Capable {
46
  $this->user = new WP_User( 0 );
47
  }
48
  }
49
-
50
  /**
51
  * Retrieve a user property.
52
- * Must be "capabilities" or a property of the WP_User class.
53
  * @param string $name property's name
54
  */
55
  public function __get( $name ) {
56
-
57
  global $wpdb;
58
-
59
  $result = null;
60
-
 
 
61
  if ( $this->user !== null ) {
 
62
  switch ( $name ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  case "capabilities" :
64
  $user_capability_table = _groups_get_tablename( "user_capability" );
65
  $rows = $wpdb->get_results( $wpdb->prepare(
@@ -73,6 +178,18 @@ class Groups_User implements I_Capable {
73
  }
74
  }
75
  break;
 
 
 
 
 
 
 
 
 
 
 
 
76
  case "groups" :
77
  $user_group_table = _groups_get_tablename( "user_group" );
78
  $rows = $wpdb->get_results( $wpdb->prepare(
@@ -86,117 +203,138 @@ class Groups_User implements I_Capable {
86
  }
87
  }
88
  break;
 
89
  default:
90
  $result = $this->user->$name;
91
  }
92
  }
93
  return $result;
94
  }
95
-
96
  /**
97
  * (non-PHPdoc)
98
  * @see I_Capable::can()
99
  */
100
  public function can( $capability ) {
101
-
102
  global $wpdb;
103
  $result = false;
104
-
105
-
106
-
107
  if ( $this->user !== null ) {
108
-
109
- // if administrators can override access, let them
110
- if ( get_option( GROUPS_ADMINISTRATOR_ACCESS_OVERRIDE, GROUPS_ADMINISTRATOR_ACCESS_OVERRIDE_DEFAULT ) ) {
111
- if ( user_can( $this->user->ID, 'administrator' ) ) { // just using $this->user would raise a warning on 3.2.1
112
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  }
114
  }
115
-
116
- $group_table = _groups_get_tablename( "group" );
117
- $capability_table = _groups_get_tablename( "capability" );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  $group_capability_table = _groups_get_tablename( "group_capability" );
119
- $user_group_table = _groups_get_tablename( "user_group" );
120
-
121
- // determine capability id
122
- $capability_id = null;
123
- if ( is_numeric( $capability ) ) {
124
- $capability_id = Groups_Utility::id( $capability );
125
- } else if ( is_string( $capability ) ) {
126
- $capability_id = $wpdb->get_var( $wpdb->prepare(
127
- "SELECT capability_id FROM $capability_table WHERE capability = %s",
128
- $capability
129
  ) );
130
- }
131
-
132
- if ( $capability_id !== null ) {
133
- // either the user can ...
134
- $result = ( Groups_User_Capability::read( $this->user->ID, $capability_id ) !== false );
135
- // ... or the user can because a group the user belongs to can
136
- if ( !$result ) {
137
- // Important: before making any changes check
138
- // INDEX usage on modified query!
139
- $rows = $wpdb->get_results( $wpdb->prepare(
140
- "SELECT capability_id FROM $group_capability_table WHERE capability_id = %d AND group_id IN (SELECT group_id FROM $user_group_table WHERE user_id = %d)",
141
- Groups_Utility::id( $capability_id ),
142
- Groups_Utility::id( $this->user->ID )
143
- ) );
144
- if ( count( $rows ) > 0 ) {
145
- $result = true;
146
  }
147
  }
148
- // ... or because any of the parent groups can
149
- if ( !$result ) {
150
- // search in parent groups
151
- $limit = $wpdb->get_var( "SELECT COUNT(*) FROM $group_table" );
152
- if ( $limit !== null ) {
153
-
154
- // note that limits by blog_id for multisite are
155
- // enforced when a user is added to a blog
156
- $user_groups = $wpdb->get_results( $wpdb->prepare(
157
- "SELECT group_id FROM $user_group_table WHERE user_id = %d",
158
- Groups_Utility::id( $this->user->ID )
159
- ) );
160
-
161
- if ( $user_groups ) {
162
- $group_ids = array();
163
- foreach( $user_groups as $user_group ) {
164
- $group_ids[] = Groups_Utility::id( $user_group->group_id );
165
- }
166
- if ( count( $group_ids ) > 0 ) {
167
- $iterations = 0;
168
- $old_group_ids_count = 0;
169
- while( ( $iterations < $limit ) && ( count( $group_ids ) !== $old_group_ids_count ) ) {
170
- $iterations++;
171
- $old_group_ids_count = count( $group_ids );
172
- $id_list = implode( ",", $group_ids );
173
- $parent_group_ids = $wpdb->get_results(
174
- "SELECT parent_id FROM $group_table WHERE parent_id IS NOT NULL AND group_id IN ($id_list)"
175
- );
176
- if ( $parent_group_ids ) {
177
- foreach( $parent_group_ids as $parent_group_id ) {
178
- $parent_group_id = Groups_Utility::id( $parent_group_id->parent_id );
179
- if ( !in_array( $parent_group_id, $group_ids ) ) {
180
- $group_ids[] = $parent_group_id;
181
- }
182
- }
183
  }
184
  }
185
- $id_list = implode( ",", $group_ids );
186
- $rows = $wpdb->get_results( $wpdb->prepare(
187
- "SELECT capability_id FROM $group_capability_table WHERE capability_id = %d AND group_id IN ($id_list)",
188
- Groups_Utility::id( $capability_id )
189
- ) );
190
- if ( count( $rows ) > 0 ) {
191
- $result = true;
 
 
 
 
192
  }
193
  }
194
  }
 
195
  }
196
  }
 
 
 
197
  }
198
  }
199
- $result = apply_filters_ref_array( "groups_user_can", array( $result, &$this, $capability ) );
200
- return $result;
201
  }
202
- }
 
 
26
  * User OPM.
27
  */
28
  class Groups_User implements I_Capable {
29
+
30
+ const CACHE_GROUP = 'groups';
31
+ const CAPABILITIES = 'capabilities';
32
+ const CAPABILITY_IDS = 'capability_ids';
33
+ const GROUP_IDS = 'group_ids';
34
+
35
  /**
36
  * User object.
37
  *
38
  * @var WP_User
39
  */
40
  var $user = null;
41
+
42
+ /**
43
+ * Hook cache clearers to actions that can modify the capabilities.
44
+ */
45
+ public static function init() {
46
+ add_action( 'groups_created_user_group', array( __CLASS__, 'clear_cache' ) );
47
+ add_action( 'groups_updated_user_group', array( __CLASS__, 'clear_cache' ) );
48
+ add_action( 'groups_deleted_user_group', array( __CLASS__, 'clear_cache' ) );
49
+ add_action( 'groups_created_user_capability', array( __CLASS__, 'clear_cache' ) );
50
+ add_action( 'groups_updated_user_capability', array( __CLASS__, 'clear_cache' ) );
51
+ add_action( 'groups_deleted_user_capability', array( __CLASS__, 'clear_cache' ) );
52
+ add_action( 'groups_created_group_capability', array( __CLASS__, 'clear_cache_for_group' ) );
53
+ add_action( 'groups_updated_group_capability', array( __CLASS__, 'clear_cache_for_group' ) );
54
+ add_action( 'groups_deleted_group_capability', array( __CLASS__, 'clear_cache_for_group' ) );
55
+ }
56
+
57
+ /**
58
+ * Clear cache objects for the user.
59
+ * @param int $user_id
60
+ */
61
+ public static function clear_cache( $user_id ) {
62
+ // be lazy, clear the entries so they are rebuilt when requested
63
+ wp_cache_delete( self::CAPABILITIES . $user_id, self::CACHE_GROUP );
64
+ wp_cache_delete( self::CAPABILITY_IDS . $user_id, self::CACHE_GROUP );
65
+ wp_cache_delete( self::GROUP_IDS . $user_id, self::CACHE_GROUP );
66
+ }
67
+
68
+ /**
69
+ * Clear cache objects for all users in the group.
70
+ * @param unknown_type $group_id
71
+ */
72
+ public static function clear_cache_for_group( $group_id ) {
73
+ global $wpdb;
74
+ if ( $group = Groups_Group::read( $group_id ) ) {
75
+ // not using $group->users, as we don't need a lot of user objects created here
76
+ $user_group_table = _groups_get_tablename( "user_group" );
77
+ $users = $wpdb->get_results( $wpdb->prepare(
78
+ "SELECT ID FROM $wpdb->users LEFT JOIN $user_group_table ON $wpdb->users.ID = $user_group_table.user_id WHERE $user_group_table.group_id = %d",
79
+ Groups_Utility::id( $group_id )
80
+ ) );
81
+ if ( $users ) {
82
+ foreach( $users as $user ) {
83
+ self::clear_cache( $user->ID );
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
  /**
90
  * Create, if $user_id = 0 an anonymous user is assumed.
91
  *
98
  $this->user = new WP_User( 0 );
99
  }
100
  }
101
+
102
  /**
103
  * Retrieve a user property.
104
+ * Must be "capabilities", "groups" or a property of the WP_User class.
105
  * @param string $name property's name
106
  */
107
  public function __get( $name ) {
108
+
109
  global $wpdb;
 
110
  $result = null;
111
+
112
+ // @todo Do we need to maintain the current semantics of "capabilities" and "groups" as direct properties of the object?
113
+
114
  if ( $this->user !== null ) {
115
+
116
  switch ( $name ) {
117
+
118
+ case 'capability_ids' :
119
+ $user_capability_table = _groups_get_tablename( "user_capability" );
120
+ $rows = $wpdb->get_results( $wpdb->prepare(
121
+ "SELECT capability_id FROM $user_capability_table WHERE user_id = %d",
122
+ Groups_Utility::id( $this->user->ID )
123
+ ) );
124
+ if ( $rows ) {
125
+ $result = array();
126
+ foreach ( $rows as $row ) {
127
+ $result[] = $row->capability_id;
128
+ }
129
+ }
130
+ break;
131
+
132
+ case 'capability_ids_deep' :
133
+ if ( $this->user !== null ) {
134
+ $capability_ids = wp_cache_get( self::CAPABILITY_IDS . $this->user->ID, self::CACHE_GROUP );
135
+ if ( $capability_ids === false ) {
136
+ $this->init_cache();
137
+ $capability_ids = wp_cache_get( self::CAPABILITY_IDS . $this->user->ID, self::CACHE_GROUP );
138
+ }
139
+ $result = $capability_ids;
140
+ }
141
+ break;
142
+
143
+ case 'group_ids' :
144
+ $user_group_table = _groups_get_tablename( "user_group" );
145
+ $rows = $wpdb->get_results( $wpdb->prepare(
146
+ "SELECT group_id FROM $user_group_table WHERE user_id = %d",
147
+ Groups_Utility::id( $this->user->ID )
148
+ ) );
149
+ if ( $rows ) {
150
+ $result = array();
151
+ foreach( $rows as $row ) {
152
+ $result[] = $row->group_id;
153
+ }
154
+ }
155
+ break;
156
+
157
+ case 'group_ids_deep' :
158
+ if ( $this->user !== null ) {
159
+ $groups_ids = wp_cache_get( self::GROUP_IDS . $this->user->ID, self::CACHE_GROUP );
160
+ if ( $group_ids === false ) {
161
+ $this->init_cache();
162
+ $group_ids = wp_cache_get( self::GROUP_IDS . $this->user->ID, self::CACHE_GROUP );
163
+ }
164
+ $result = $group_ids;
165
+ }
166
+ break;
167
+
168
  case "capabilities" :
169
  $user_capability_table = _groups_get_tablename( "user_capability" );
170
  $rows = $wpdb->get_results( $wpdb->prepare(
178
  }
179
  }
180
  break;
181
+
182
+ case 'capabilities_deep' :
183
+ if ( $this->user !== null ) {
184
+ $capabilities = wp_cache_get( self::CAPABILITIES . $this->user->ID, self::CACHE_GROUP );
185
+ if ( $capabilities === false ) {
186
+ $this->init_cache();
187
+ $capabilities = wp_cache_get( self::CAPABILITIES . $this->user->ID, self::CACHE_GROUP );
188
+ }
189
+ $result = $capabilities;
190
+ }
191
+ break;
192
+
193
  case "groups" :
194
  $user_group_table = _groups_get_tablename( "user_group" );
195
  $rows = $wpdb->get_results( $wpdb->prepare(
203
  }
204
  }
205
  break;
206
+
207
  default:
208
  $result = $this->user->$name;
209
  }
210
  }
211
  return $result;
212
  }
213
+
214
  /**
215
  * (non-PHPdoc)
216
  * @see I_Capable::can()
217
  */
218
  public function can( $capability ) {
219
+
220
  global $wpdb;
221
  $result = false;
222
+
 
 
223
  if ( $this->user !== null ) {
224
+ if (
225
+ get_option( GROUPS_ADMINISTRATOR_ACCESS_OVERRIDE, GROUPS_ADMINISTRATOR_ACCESS_OVERRIDE_DEFAULT ) &&
226
+ user_can( $this->user->ID, 'administrator' ) // just using $this->user would raise a warning on 3.2.1
227
+ ) {
228
+ $result = true;
229
+ } else {
230
+ // determine capability id
231
+ $capability_id = null;
232
+ if ( is_numeric( $capability ) ) {
233
+ $capability_id = Groups_Utility::id( $capability );
234
+ $capability_ids = wp_cache_get( self::CAPABILITY_IDS . $this->user->ID, self::CACHE_GROUP );
235
+ if ( $capability_ids === false ) {
236
+ $this->init_cache();
237
+ $capability_ids = wp_cache_get( self::CAPABILITY_IDS . $this->user->ID, self::CACHE_GROUP );
238
+ }
239
+ $result = in_array( $capability_id, $capability_ids );
240
+ } else if ( is_string( $capability ) ) {
241
+ $capabilities = wp_cache_get( self::CAPABILITIES . $this->user->ID, self::CACHE_GROUP );
242
+ if ( $capabilities === false ) {
243
+ $this->init_cache();
244
+ $capabilities = wp_cache_get( self::CAPABILITIES . $this->user->ID, self::CACHE_GROUP );
245
+ }
246
+ $result = in_array( $capability, $capabilities );
247
  }
248
  }
249
+ }
250
+ $result = apply_filters_ref_array( "groups_user_can", array( $result, &$this, $capability ) );
251
+ return $result;
252
+ }
253
+
254
+ /**
255
+ * Builds the cache entries for user groups and capabilities if needed.
256
+ * The cache entries are built only if they do not already exist.
257
+ * If you want them rebuilt, delete them before calling.
258
+ */
259
+ private function init_cache() {
260
+
261
+ global $wpdb;
262
+
263
+ if ( ( $this->user !== null ) && ( wp_cache_get( self::GROUP_IDS . $this->user->ID, self::CACHE_GROUP ) === false ) ) {
264
+ $group_table = _groups_get_tablename( "group" );
265
+ $capability_table = _groups_get_tablename( "capability" );
266
  $group_capability_table = _groups_get_tablename( "group_capability" );
267
+ $user_group_table = _groups_get_tablename( "user_group" );
268
+ $user_capability_table = _groups_get_tablename( "user_capability" );
269
+ $limit = $wpdb->get_var( "SELECT COUNT(*) FROM $group_table" );
270
+ if ( $limit !== null ) {
271
+ // note that limits by blog_id for multisite are
272
+ // enforced when a user is added to a blog
273
+ $user_groups = $wpdb->get_results( $wpdb->prepare(
274
+ "SELECT group_id FROM $user_group_table WHERE user_id = %d",
275
+ Groups_Utility::id( $this->user->ID )
 
276
  ) );
277
+ // get all capabilities directly assigned (those granted through
278
+ // groups are added below
279
+ $capabilities = array();
280
+ $capability_ids = array();
281
+ $user_capabilities = $wpdb->get_results( $wpdb->prepare(
282
+ "SELECT c.capability_id, c.capability FROM $user_capability_table uc LEFT JOIN $capability_table c ON c.capability_id = uc.capability_id WHERE user_id = %d",
283
+ Groups_Utility::id( $this->user->ID )
284
+ ) );
285
+ if ( $user_capabilities ) {
286
+ foreach( $user_capabilities as $user_capability ) {
287
+ $capabilities[] = $user_capability->capability;
288
+ $capability_ids[] = $user_capability->capability_id;
 
 
 
 
289
  }
290
  }
291
+ // Get all groups the user belongs to directly or through
292
+ // inheritance along with their capabilities.
293
+ $group_ids = array();
294
+ if ( $user_groups ) {
295
+ foreach( $user_groups as $user_group ) {
296
+ $group_ids[] = Groups_Utility::id( $user_group->group_id );
297
+ }
298
+ if ( count( $group_ids ) > 0 ) {
299
+ $iterations = 0;
300
+ $old_group_ids_count = 0;
301
+ while( ( $iterations < $limit ) && ( count( $group_ids ) !== $old_group_ids_count ) ) {
302
+ $iterations++;
303
+ $old_group_ids_count = count( $group_ids );
304
+ $id_list = implode( ",", $group_ids );
305
+ $parent_group_ids = $wpdb->get_results(
306
+ "SELECT parent_id FROM $group_table WHERE parent_id IS NOT NULL AND group_id IN ($id_list)"
307
+ );
308
+ if ( $parent_group_ids ) {
309
+ foreach( $parent_group_ids as $parent_group_id ) {
310
+ $parent_group_id = Groups_Utility::id( $parent_group_id->parent_id );
311
+ if ( !in_array( $parent_group_id, $group_ids ) ) {
312
+ $group_ids[] = $parent_group_id;
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  }
314
  }
315
+ }
316
+ }
317
+ $id_list = implode( ",", $group_ids );
318
+ $rows = $wpdb->get_results(
319
+ "SELECT $group_capability_table.capability_id, $capability_table.capability FROM $group_capability_table LEFT JOIN $capability_table ON $group_capability_table.capability_id = $capability_table.capability_id WHERE group_id IN ($id_list)"
320
+ );
321
+ if ( count( $rows ) > 0 ) {
322
+ foreach ( $rows as $row ) {
323
+ if ( !in_array( $row->capability_id, $capability_ids ) ) {
324
+ $capabilities[] = $row->capability;
325
+ $capability_ids[] = $row->capability_id;
326
  }
327
  }
328
  }
329
+
330
  }
331
  }
332
+ wp_cache_set( self::CAPABILITIES . $this->user->ID, $capabilities, self::CACHE_GROUP );
333
+ wp_cache_set( self::CAPABILITY_IDS . $this->user->ID, $capability_ids, self::CACHE_GROUP );
334
+ wp_cache_set( self::GROUP_IDS . $this->user->ID, $group_ids, self::CACHE_GROUP );
335
  }
336
  }
 
 
337
  }
338
+
339
+ }
340
+ Groups_User::init();
readme.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: http://www.itthinx.com/plugins/groups
4
  Tags: access, access control, capability, capabilities, content, download, downloads, file, file access, files, group, groups, member, members, membership, memberships, paypal, permission, permissions, subscription, subscriptions, woocommerce
5
  Requires at least: 3.3
6
  Tested up to: 3.5.1
7
- Stable tag: 1.3.8
8
  License: GPLv3
9
 
10
  Groups provides group-based user membership management, group-based capabilities and content access control.
@@ -23,6 +23,7 @@ It integrates standard WordPress capabilities and application-specific capabilit
23
  - [Groups Jigoshop](http://jigoshop.com/product/subscriptions/) Groups integration for Jigoshop that supports memberships and subscriptions.
24
  - [Groups Notifications](http://www.itthinx.com/plugins/groups-notifications/) Adds customizable notifications for events related to Groups.
25
  - [Groups PayPal](http://www.itthinx.com/plugins/groups-paypal/) Groups for PayPal allows to sell memberships and subscriptions with Groups.
 
26
  - [Groups WooCommerce](http://www.woothemes.com/extension/groups-woocommerce/) Groups for WooCommerce is a WordPress plugin that allows you to sell memberships.
27
  - [Groups 404 Redirect](http://wordpress.org/extend/plugins/groups-404-redirect/) Redirects 404's caused by hits on pages that are protected by Groups.
28
 
@@ -326,6 +327,13 @@ See also [Groups](http://www.itthinx.com/plugins/groups/)
326
 
327
  == Changelog ==
328
 
 
 
 
 
 
 
 
329
  = 1.3.8 =
330
  * Fix: using substitute wp_cache_switch_to_blog instead of deprecated function wp_cache_reset when available (from 3.5.0)
331
  * Fix: don't show access restriction meta box on attachments, the option is added with the attachment fields (3.5 uses common post edit screen but save_post isn't triggered on attachments)
@@ -440,6 +448,9 @@ Some installations wouldn't work correctly, showing no capabilities and making i
440
 
441
  == Upgrade Notice ==
442
 
 
 
 
443
  = 1.3.8 =
444
  * This release includes several fixes and improvements, including more limiting features for access restrictions.
445
 
4
  Tags: access, access control, capability, capabilities, content, download, downloads, file, file access, files, group, groups, member, members, membership, memberships, paypal, permission, permissions, subscription, subscriptions, woocommerce
5
  Requires at least: 3.3
6
  Tested up to: 3.5.1
7
+ Stable tag: 1.3.9
8
  License: GPLv3
9
 
10
  Groups provides group-based user membership management, group-based capabilities and content access control.
23
  - [Groups Jigoshop](http://jigoshop.com/product/subscriptions/) Groups integration for Jigoshop that supports memberships and subscriptions.
24
  - [Groups Notifications](http://www.itthinx.com/plugins/groups-notifications/) Adds customizable notifications for events related to Groups.
25
  - [Groups PayPal](http://www.itthinx.com/plugins/groups-paypal/) Groups for PayPal allows to sell memberships and subscriptions with Groups.
26
+ - [Groups Subscriptions](http://www.itthinx.com/plugins/groups-subscriptions/) A subscription framework for Groups used by other extensions.
27
  - [Groups WooCommerce](http://www.woothemes.com/extension/groups-woocommerce/) Groups for WooCommerce is a WordPress plugin that allows you to sell memberships.
28
  - [Groups 404 Redirect](http://wordpress.org/extend/plugins/groups-404-redirect/) Redirects 404's caused by hits on pages that are protected by Groups.
29
 
327
 
328
  == Changelog ==
329
 
330
+ = 1.3.9 =
331
+ * Fix: added filter hooked on posts_where motivated by pagination issues - the posts must be filtered before the totals are calculated in WP_Query::get_posts().
332
+ * Improvement: modified the signature of the the_posts filter method in Groups_Post_Access to receive the $query by reference
333
+ * Improvement: a substantial improvement on overall performance is achieved by caching user capabilities and groups
334
+ * Fix: access restriction boxes showing capabilities that the user should not be allowed to set to restrict posts
335
+ * Fix: resolve user-capability when a capability is deleted
336
+
337
  = 1.3.8 =
338
  * Fix: using substitute wp_cache_switch_to_blog instead of deprecated function wp_cache_reset when available (from 3.5.0)
339
  * Fix: don't show access restriction meta box on attachments, the option is added with the attachment fields (3.5 uses common post edit screen but save_post isn't triggered on attachments)
448
 
449
  == Upgrade Notice ==
450
 
451
+ = 1.3.9 =
452
+ * Brings a substantial performance improvement and solves pagination issues due to post filters among other fixes.
453
+
454
  = 1.3.8 =
455
  * This release includes several fixes and improvements, including more limiting features for access restrictions.
456