Groups - Version 2.17.0

Version Description

Download this release

Release Info

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

Code changes from version 2.16.2 to 2.17.0

changelog.txt CHANGED
@@ -1,5 +1,12 @@
1
  == Groups by itthinx - changelog.txt
2
 
 
 
 
 
 
 
 
3
  = 2.16.2 =
4
  * Fixes a PHP 8.1 warning related to the passing of a null parameter to version_compare().
5
 
1
  == Groups by itthinx - changelog.txt
2
 
3
+ = 2.17.0 =
4
+ * WordPress 6.1 compatible.
5
+ * Fixes potential errors with inconsistent postmeta values obtained for groups protecting posts, using own API method which has been further guarded against inconsistent values.
6
+ * Revised code to use updated API method to obtain read access group IDs instead of accessing via get_post_meta().
7
+ * Fixed several instances of code formatting issues.
8
+ * Improves the performance on filtering out adjacent posts with sets of eligible post IDs cached by post type and avoids redundant queries.
9
+
10
  = 2.16.2 =
11
  * Fixes a PHP 8.1 warning related to the passing of a null parameter to version_compare().
12
 
groups.php CHANGED
@@ -21,7 +21,7 @@
21
  * Plugin Name: Groups
22
  * Plugin URI: https://www.itthinx.com/plugins/groups
23
  * Description: Groups provides group-based user membership management, group-based capabilities and content access control.
24
- * Version: 2.16.2
25
  * Author: itthinx
26
  * Author URI: https://www.itthinx.com
27
  * Donate-Link: https://www.itthinx.com/shop/
@@ -32,7 +32,7 @@
32
  if ( !defined( 'ABSPATH' ) ) {
33
  exit;
34
  }
35
- define( 'GROUPS_CORE_VERSION', '2.16.2' );
36
  define( 'GROUPS_FILE', __FILE__ );
37
  if ( !defined( 'GROUPS_CORE_DIR' ) ) {
38
  define( 'GROUPS_CORE_DIR', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
21
  * Plugin Name: Groups
22
  * Plugin URI: https://www.itthinx.com/plugins/groups
23
  * Description: Groups provides group-based user membership management, group-based capabilities and content access control.
24
+ * Version: 2.17.0
25
  * Author: itthinx
26
  * Author URI: https://www.itthinx.com
27
  * Donate-Link: https://www.itthinx.com/shop/
32
  if ( !defined( 'ABSPATH' ) ) {
33
  exit;
34
  }
35
+ define( 'GROUPS_CORE_VERSION', '2.17.0' );
36
  define( 'GROUPS_FILE', __FILE__ );
37
  if ( !defined( 'GROUPS_CORE_DIR' ) ) {
38
  define( 'GROUPS_CORE_DIR', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
lib/access/class-groups-access-meta-boxes.php CHANGED
@@ -209,7 +209,7 @@ class Groups_Access_Meta_Boxes {
209
 
210
  $include = self::get_user_can_restrict_group_ids();
211
  $groups = Groups_Group::get_groups( array( 'order_by' => 'name', 'order' => 'ASC', 'include' => $include ) );
212
- $groups_read = get_post_meta( $post_id, Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ );
213
 
214
  $read_help = sprintf(
215
  __( 'You can restrict the visibility of this %1$s to group members. Choose one or more groups that are allowed to read this %2$s. If no groups are chosen, the %3$s is visible to anyone.', 'groups' ),
@@ -421,6 +421,7 @@ class Groups_Access_Meta_Boxes {
421
  *
422
  * @param array $form_fields
423
  * @param object $post
 
424
  * @return array
425
  */
426
  public static function attachment_fields_to_edit( $form_fields, $post ) {
@@ -435,7 +436,7 @@ class Groups_Access_Meta_Boxes {
435
 
436
  $include = self::get_user_can_restrict_group_ids();
437
  $groups = Groups_Group::get_groups( array( 'order_by' => 'name', 'order' => 'ASC', 'include' => $include ) );
438
- $groups_read = get_post_meta( $post->ID, Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ );
439
 
440
  $output = '';
441
  $post_singular_name = __( 'Media', 'groups' );
209
 
210
  $include = self::get_user_can_restrict_group_ids();
211
  $groups = Groups_Group::get_groups( array( 'order_by' => 'name', 'order' => 'ASC', 'include' => $include ) );
212
+ $groups_read = Groups_Post_Access::get_read_group_ids( $post_id );
213
 
214
  $read_help = sprintf(
215
  __( 'You can restrict the visibility of this %1$s to group members. Choose one or more groups that are allowed to read this %2$s. If no groups are chosen, the %3$s is visible to anyone.', 'groups' ),
421
  *
422
  * @param array $form_fields
423
  * @param object $post
424
+ *
425
  * @return array
426
  */
427
  public static function attachment_fields_to_edit( $form_fields, $post ) {
436
 
437
  $include = self::get_user_can_restrict_group_ids();
438
  $groups = Groups_Group::get_groups( array( 'order_by' => 'name', 'order' => 'ASC', 'include' => $include ) );
439
+ $groups_read = Groups_Post_Access::get_read_group_ids( $post->ID );
440
 
441
  $output = '';
442
  $post_singular_name = __( 'Media', 'groups' );
lib/access/class-groups-post-access.php CHANGED
@@ -125,6 +125,8 @@ class Groups_Post_Access {
125
  // adjacent posts
126
  add_filter( 'get_previous_post_where', array( __CLASS__, 'get_previous_post_where' ), 10, 5 );
127
  add_filter( 'get_next_post_where', array( __CLASS__, 'get_next_post_where' ), 10, 5 );
 
 
128
  }
129
 
130
  /**
@@ -492,11 +494,48 @@ class Groups_Post_Access {
492
  * @return string $where modified if appropriate
493
  */
494
  public static function get_next_post_where( $where, $in_same_term, $excluded_terms, $taxonomy, $post ) {
495
- if ( !empty( $post ) ) {
496
- // run it through get_posts with suppress_filters set to false so that our posts_where filter is applied and assures only accessible posts are seen
497
- $post_ids = get_posts( array( 'post_type' => $post->post_type, 'numberposts' => -1, 'suppress_filters' => false, 'fields' => 'ids' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
498
  if ( is_array( $post_ids ) && count( $post_ids ) > 0 ) {
499
- $post_ids = array_map( 'intval', $post_ids );
500
  $condition = ' p.ID IN (' . implode( ',', $post_ids ) . ') ';
501
  if ( !empty( $where ) ) {
502
  $where .= ' AND ' . $condition;
@@ -508,6 +547,74 @@ class Groups_Post_Access {
508
  return $where;
509
  }
510
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  /**
512
  * Adds an access requirement based on post_id and group_id.
513
  *
@@ -519,7 +626,8 @@ class Groups_Post_Access {
519
  * versions).
520
  *
521
  * @param array $map must contain 'post_id' (*) and 'group_id'
522
- * @return true if the capability could be added to the post, otherwise false
 
523
  */
524
  public static function create( $map ) {
525
  extract( $map );
@@ -540,7 +648,8 @@ class Groups_Post_Access {
540
  if ( $revision_parent_id = wp_is_post_revision( $post_id ) ) {
541
  $post_id = $revision_parent_id;
542
  }
543
- if ( !in_array( $group_id, get_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ ) ) ) {
 
544
  $result = add_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ, $group_id );
545
  }
546
  }
@@ -566,7 +675,7 @@ class Groups_Post_Access {
566
  } else if ( !is_array( $groups_read ) ) {
567
  $groups_read = array( $groups_read );
568
  }
569
- $group_ids = get_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ );
570
  if ( $group_ids ) {
571
  foreach( $groups_read as $group_id ) {
572
  $result = in_array( $group_id, $group_ids );
@@ -598,7 +707,7 @@ class Groups_Post_Access {
598
  $groups_read = array( $groups_read );
599
  }
600
  $groups_read = array_map( array( 'Groups_Utility', 'id' ), $groups_read );
601
- $current_groups_read = get_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ );
602
  $current_groups_read = array_map( array( 'Groups_Utility', 'id' ), $current_groups_read );
603
  foreach( $groups_read as $group_id ) {
604
  if ( !in_array( $group_id, $current_groups_read ) ) {
@@ -610,7 +719,8 @@ class Groups_Post_Access {
610
  delete_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ, $group_id );
611
  }
612
  }
613
- $result = array_map( array( 'Groups_Utility', 'id' ), get_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ ) );
 
614
  }
615
  return $result;
616
  }
@@ -665,10 +775,18 @@ class Groups_Post_Access {
665
  * Returns a list of group IDs that grant read access to the post.
666
  *
667
  * @param int $post_id
 
668
  * @return array of int, group IDs
669
  */
670
  public static function get_read_group_ids( $post_id ) {
671
- return get_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ );
 
 
 
 
 
 
 
672
  }
673
 
674
  /**
125
  // adjacent posts
126
  add_filter( 'get_previous_post_where', array( __CLASS__, 'get_previous_post_where' ), 10, 5 );
127
  add_filter( 'get_next_post_where', array( __CLASS__, 'get_next_post_where' ), 10, 5 );
128
+ add_action( 'save_post', array( __CLASS__, 'save_post' ), PHP_INT_MAX );
129
+ add_filter( 'attachment_fields_to_save', array( __CLASS__, 'attachment_fields_to_save' ), PHP_INT_MAX, 2 );
130
  }
131
 
132
  /**
494
  * @return string $where modified if appropriate
495
  */
496
  public static function get_next_post_where( $where, $in_same_term, $excluded_terms, $taxonomy, $post ) {
497
+ if (
498
+ !empty( $post ) &&
499
+ self::handles_post_type( $post->post_type )
500
+ ) {
501
+ $cache_group = self::CACHE_GROUP . '_' . $post->post_type;
502
+
503
+ $group_ids = array();
504
+ $user_id = get_current_user_id();
505
+ if ( $user_id ) {
506
+ $groups_user = new Groups_User( $user_id );
507
+ $group_ids = $groups_user->group_ids_deep;
508
+ if ( is_array( $group_ids ) ) {
509
+ sort( $group_ids );
510
+ $cache_group .= '_' . implode( '_', $group_ids );
511
+ }
512
+ }
513
+
514
+ // remember the cache group for purging
515
+ $stored_cache_groups = Groups_Options::get_option( 'eligible_post_ids_cache_groups', array() );
516
+ if ( !in_array( $cache_group, $stored_cache_groups ) ) {
517
+ $stored_cache_groups[] = $cache_group;
518
+ Groups_Options::update_option( 'eligible_post_ids_cache_groups', $stored_cache_groups );
519
+ }
520
+
521
+ $post_ids = array( -1 );
522
+ $cached = Groups_Cache::get( 'eligible_post_ids', $cache_group );
523
+ if ( $cached === null ) {
524
+ // run it through get_posts with suppress_filters set to false so that our posts_where filter is applied and assures only accessible posts are seen
525
+ $post_ids = get_posts( array( 'post_type' => $post->post_type, 'numberposts' => -1, 'suppress_filters' => false, 'fields' => 'ids' ) );
526
+ if ( is_array( $post_ids ) && count( $post_ids ) > 0 ) {
527
+ foreach ( $post_ids as $i => $post_id ) {
528
+ $post_ids[$i] = intval( $post_id );
529
+ }
530
+ } else {
531
+ $post_ids = array( -1 );
532
+ }
533
+ Groups_Cache::set( 'eligible_post_ids', $post_ids, $cache_group );
534
+ } else {
535
+ $post_ids = $cached->value;
536
+ }
537
+
538
  if ( is_array( $post_ids ) && count( $post_ids ) > 0 ) {
 
539
  $condition = ' p.ID IN (' . implode( ',', $post_ids ) . ') ';
540
  if ( !empty( $where ) ) {
541
  $where .= ' AND ' . $condition;
547
  return $where;
548
  }
549
 
550
+ /**
551
+ * Clears cached eligible post IDs.
552
+ *
553
+ * @since 2.17.0
554
+ *
555
+ * @param int $post_id
556
+ */
557
+ public static function save_post( $post_id ) {
558
+ if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE || wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) ) {
559
+ } else {
560
+ $post_type = get_post_type( $post_id );
561
+ if ( self::handles_post_type( $post_type ) ) {
562
+ self::purge_eligible_post_ids_cached( $post_type );
563
+ }
564
+ }
565
+ }
566
+
567
+ /**
568
+ * Clear cached eligible post IDs for the 'attachment' post type (the save_post action is not triggered for those).
569
+ *
570
+ * @since 2.17.0
571
+ *
572
+ * @param array $post
573
+ * @param array $attachment
574
+ *
575
+ * @return array
576
+ */
577
+ public static function attachment_fields_to_save( $post, $attachment ) {
578
+ if ( self::handles_post_type( 'attachment' ) ) {
579
+ $post_id = null;
580
+ if ( isset( $post['ID'] ) ) {
581
+ $post_id = $post['ID'];
582
+ } else if ( isset( $post['post_ID'] ) ) {
583
+ $post_id = $post['post_ID'];
584
+ }
585
+ if ( $post_id !== null ) {
586
+ self::save_post( $post_id );
587
+ }
588
+ }
589
+ return $post;
590
+ }
591
+
592
+ /**
593
+ * Deletes all stored eligible post IDs cached for the given post type, or all post types (by default).
594
+ *
595
+ * @since 2.17.0
596
+ *
597
+ * @param string|null $post_type
598
+ */
599
+ public static function purge_eligible_post_ids_cached( $post_type = null ) {
600
+ $changed = false;
601
+ $stored_cache_groups = Groups_Options::get_option( 'eligible_post_ids_cache_groups', array() );
602
+ foreach ( $stored_cache_groups as $cache_group ) {
603
+ if ( $post_type === null || strpos( $cache_group, $post_type ) !== false ) {
604
+ Groups_Cache::delete( 'eligible_post_ids' , $cache_group );
605
+ $stored_cache_groups = array_diff( $stored_cache_groups, array( $cache_group ) );
606
+ $changed = true;
607
+ }
608
+ }
609
+ if ( $changed ) {
610
+ if ( count( $stored_cache_groups ) > 0 ) {
611
+ Groups_Options::update_option( 'eligible_post_ids_cache_groups', $stored_cache_groups );
612
+ } else {
613
+ Groups_Options::delete_option( 'eligible_post_ids_cache_groups' );
614
+ }
615
+ }
616
+ }
617
+
618
  /**
619
  * Adds an access requirement based on post_id and group_id.
620
  *
626
  * versions).
627
  *
628
  * @param array $map must contain 'post_id' (*) and 'group_id'
629
+ *
630
+ * @return true if the access requirement could be added to the post, otherwise false
631
  */
632
  public static function create( $map ) {
633
  extract( $map );
648
  if ( $revision_parent_id = wp_is_post_revision( $post_id ) ) {
649
  $post_id = $revision_parent_id;
650
  }
651
+ $stored_group_ids = self::get_read_group_ids( $post_id );
652
+ if ( !in_array( $group_id, $stored_group_ids ) ) {
653
  $result = add_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ, $group_id );
654
  }
655
  }
675
  } else if ( !is_array( $groups_read ) ) {
676
  $groups_read = array( $groups_read );
677
  }
678
+ $group_ids = self::get_read_group_ids( $post_id );
679
  if ( $group_ids ) {
680
  foreach( $groups_read as $group_id ) {
681
  $result = in_array( $group_id, $group_ids );
707
  $groups_read = array( $groups_read );
708
  }
709
  $groups_read = array_map( array( 'Groups_Utility', 'id' ), $groups_read );
710
+ $current_groups_read = self::get_read_group_ids( $post_id );
711
  $current_groups_read = array_map( array( 'Groups_Utility', 'id' ), $current_groups_read );
712
  foreach( $groups_read as $group_id ) {
713
  if ( !in_array( $group_id, $current_groups_read ) ) {
719
  delete_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ, $group_id );
720
  }
721
  }
722
+ $stored_group_ids = self::get_read_group_ids( $post_id );
723
+ $result = array_map( array( 'Groups_Utility', 'id' ), $stored_group_ids );
724
  }
725
  return $result;
726
  }
775
  * Returns a list of group IDs that grant read access to the post.
776
  *
777
  * @param int $post_id
778
+ *
779
  * @return array of int, group IDs
780
  */
781
  public static function get_read_group_ids( $post_id ) {
782
+ $result = array();
783
+ $group_ids = get_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ );
784
+ if ( is_array( $group_ids ) ) {
785
+ foreach ( $group_ids as $group_id ) {
786
+ $result[] = intval( $group_id );
787
+ }
788
+ }
789
+ return $result;
790
  }
791
 
792
  /**
lib/admin/class-groups-admin-post-columns.php CHANGED
@@ -103,6 +103,7 @@ class Groups_Admin_Post_Columns {
103
  *
104
  * @param string $column_name
105
  * @param int $post_id
 
106
  * @return string custom column content
107
  */
108
  public static function custom_column( $column_name, $post_id ) {
@@ -110,7 +111,8 @@ class Groups_Admin_Post_Columns {
110
  switch ( $column_name ) {
111
  case self::GROUPS :
112
  $entries = array();
113
- $groups_read = get_post_meta( $post_id, Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ );
 
114
  if ( count( $groups_read ) > 0 ) {
115
  $groups = Groups_Group::get_groups( array( 'order_by' => 'name', 'order' => 'ASC', 'include' => $groups_read ) );
116
  if ( ( count( $groups ) > 0 ) ) {
103
  *
104
  * @param string $column_name
105
  * @param int $post_id
106
+ *
107
  * @return string custom column content
108
  */
109
  public static function custom_column( $column_name, $post_id ) {
111
  switch ( $column_name ) {
112
  case self::GROUPS :
113
  $entries = array();
114
+ $groups_read = Groups_Post_Access::get_read_group_ids( $post_id );
115
+
116
  if ( count( $groups_read ) > 0 ) {
117
  $groups = Groups_Group::get_groups( array( 'order_by' => 'name', 'order' => 'ASC', 'include' => $groups_read ) );
118
  if ( ( count( $groups ) > 0 ) ) {
lib/core/class-groups-controller.php CHANGED
@@ -287,7 +287,7 @@ class Groups_Controller {
287
  $user_capability_table = _groups_get_tablename( 'user_capability' );
288
  if ( $wpdb->get_var( "SHOW TABLES LIKE '$user_capability_table'" ) != $user_capability_table ) {
289
  $queries[] = "CREATE TABLE IF NOT EXISTS $user_capability_table (
290
- user_id bigint(20) unsigned NOT NULL,
291
  capability_id bigint(20) unsigned NOT NULL,
292
  PRIMARY KEY (user_id, capability_id),
293
  INDEX user_capability_cu (capability_id,user_id)
287
  $user_capability_table = _groups_get_tablename( 'user_capability' );
288
  if ( $wpdb->get_var( "SHOW TABLES LIKE '$user_capability_table'" ) != $user_capability_table ) {
289
  $queries[] = "CREATE TABLE IF NOT EXISTS $user_capability_table (
290
+ user_id bigint(20) unsigned NOT NULL,
291
  capability_id bigint(20) unsigned NOT NULL,
292
  PRIMARY KEY (user_id, capability_id),
293
  INDEX user_capability_cu (capability_id,user_id)
lib/core/class-groups-group.php CHANGED
@@ -200,8 +200,8 @@ class Groups_Group implements I_Capable {
200
  if ( !$result ) {
201
  // find all parent groups and include in the group's
202
  // upward hierarchy to see if any of these can
203
- $group_ids = array( $this->group->group_id );
204
- $iterations = 0;
205
  $old_group_ids_count = 0;
206
  $all_groups = $wpdb->get_var( "SELECT COUNT(*) FROM $group_table" );
207
  while( ( $iterations < $all_groups ) && ( count( $group_ids ) !== $old_group_ids_count ) ) {
@@ -432,7 +432,7 @@ class Groups_Group implements I_Capable {
432
  $iterations++;
433
  $old_group_ids_count = count( $group_ids );
434
 
435
- $id_list = implode( ',', $group_ids );
436
  // We can trust ourselves here, no need to use prepare()
437
  // but careful if this query is modified!
438
  $successor_group_ids = $wpdb->get_results(
200
  if ( !$result ) {
201
  // find all parent groups and include in the group's
202
  // upward hierarchy to see if any of these can
203
+ $group_ids = array( $this->group->group_id );
204
+ $iterations = 0;
205
  $old_group_ids_count = 0;
206
  $all_groups = $wpdb->get_var( "SELECT COUNT(*) FROM $group_table" );
207
  while( ( $iterations < $all_groups ) && ( count( $group_ids ) !== $old_group_ids_count ) ) {
432
  $iterations++;
433
  $old_group_ids_count = count( $group_ids );
434
 
435
+ $id_list = implode( ',', $group_ids );
436
  // We can trust ourselves here, no need to use prepare()
437
  // but careful if this query is modified!
438
  $successor_group_ids = $wpdb->get_results(
readme.txt CHANGED
@@ -3,9 +3,9 @@ Contributors: itthinx, proaktion
3
  Donate link: https://www.itthinx.com/shop/
4
  Tags: groups, access, access control, membership, memberships, member, members, capability, capabilities, content, download, downloads, file, file access, files, paypal, permission, permissions, subscription, subscriptions, woocommerce
5
  Requires at least: 5.5
6
- Tested up to: 6.0
7
  Requires PHP: 5.6.0
8
- Stable tag: 2.16.2
9
  License: GPLv3
10
 
11
  Groups is an efficient and powerful solution, providing group-based user membership management, group-based capabilities and content access control.
3
  Donate link: https://www.itthinx.com/shop/
4
  Tags: groups, access, access control, membership, memberships, member, members, capability, capabilities, content, download, downloads, file, file access, files, paypal, permission, permissions, subscription, subscriptions, woocommerce
5
  Requires at least: 5.5
6
+ Tested up to: 6.1
7
  Requires PHP: 5.6.0
8
+ Stable tag: 2.17.0
9
  License: GPLv3
10
 
11
  Groups is an efficient and powerful solution, providing group-based user membership management, group-based capabilities and content access control.