PluginProbe
Groups – Memberships and Access Control / trunk
Groups – Memberships and Access Control vtrunk
4.6.0 4.5.0 4.4.0 4.3.0 trunk 1.0.0-beta-1 1.0.0-beta-2 1.0.0-beta-3 1.0.0-beta-3b 1.0.0-beta-3c 1.0.0-beta-3d 1.1.4 1.1.5 1.10.0 1.10.1 1.10.2 1.10.3 1.11.0 1.11.1 1.11.2 1.11.3 1.12.0 1.13.0 1.13.1 1.2.0 All 129 releases
groups / lib / wp / class-groups-wordpress.php

class-groups-wordpress.php in Groups – Memberships and Access Control trunk, at lib/wp/class-groups-wordpress.php

308 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * class-groups-wordpress.php
4 *
5 * Copyright (c) "kento" Karim Rahimpur www.itthinx.com
6 *
7 * This code is released under the GNU General Public License.
8 * See COPYRIGHT.txt and LICENSE.txt.
9 *
10 * This code is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * This header and all notices must be kept intact.
16 *
17 * @author Karim Rahimpur
18 * @package groups
19 * @since groups 1.0.0
20 */
21
22 if ( !defined( 'ABSPATH' ) ) {
23 exit;
24 }
25
26 /**
27 * WordPress capabilities integration.
28 */
29 class Groups_WordPress {
30
31 /**
32 * Cache group
33 *
34 * @var string
35 */
36 const CACHE_GROUP = 'groups';
37
38 /**
39 * Cache key prefix
40 *
41 * @var string
42 */
43 const HAS_CAP = 'has_cap';
44
45 /**
46 * Filter priority: groups_user_can
47 *
48 * @var integer
49 *
50 * @since 2.11.0
51 */
52 const GROUPS_USER_CAN_FILTER_PRIORITY = 10;
53
54 /**
55 * Filter priority: user_has_cap
56 *
57 * @var int
58 *
59 * @since 2.11.0
60 */
61 const USER_HAS_CAP_FILTER_PRIORITY = PHP_INT_MAX;
62
63 /**
64 * Hook into actions to extend user capabilities.
65 *
66 * @todo We might want to keep up with new capabilities when added, so
67 * that others don't have to add these explicitly to Groups when they
68 * add them to WordPress. Currently there's no hook for when a capability
69 * is added and checking this in any other way is too costly.
70 */
71 public static function init() {
72 // args: boolean $result, Groups_User $groups_user, string $capability
73 add_filter( 'groups_user_can', array( __CLASS__, 'groups_user_can' ), self::GROUPS_USER_CAN_FILTER_PRIORITY, 5 );
74 add_filter( 'user_has_cap', array( __CLASS__, 'user_has_cap' ), self::USER_HAS_CAP_FILTER_PRIORITY, 4 );
75 }
76
77 /**
78 * Whether the given user has the capability.
79 *
80 * Calls Groups_User::user_can() with user_has_cap and groups_user_can filters temporarily removed.
81 *
82 * @since 3.1.0
83 *
84 * @param int|WP_User $user user ID or object
85 * @param string $capability capability name
86 * @param mixed ...$args optional parameters, typically an object ID
87 *
88 * @return boolean
89 */
90 private static function unfiltered_user_can( $user, $capability, ...$args ) {
91 // We want to check without the capabilities granted to the user via groups.
92 // Groups_User::user_can() relies on user_can() or WP_User->has_cap() in the absense of the former,
93 // which will cause our user_has_cap filter to act so we have to remove it temporarily.
94 // To avoid potential internal conflicts or cicular dependencies, we also remove the groups_user_can filter which
95 // relies on this method.
96 $result = false;
97 $filter_user_has_cap = remove_filter( 'user_has_cap', array( __CLASS__, 'user_has_cap' ), self::USER_HAS_CAP_FILTER_PRIORITY ); // @since 3.1.0
98 $filter_groups_user_can = remove_filter( 'groups_user_can', array( __CLASS__, 'groups_user_can' ), self::GROUPS_USER_CAN_FILTER_PRIORITY ); // @since 3.1.0
99 $result = Groups_User::user_can( $user, $capability, ...$args );
100 if ( $filter_user_has_cap ) {
101 add_filter( 'user_has_cap', array( __CLASS__, 'user_has_cap' ), self::USER_HAS_CAP_FILTER_PRIORITY, 4 );
102 }
103 if ( $filter_groups_user_can ) {
104 add_filter( 'groups_user_can', array( __CLASS__, 'groups_user_can' ), self::GROUPS_USER_CAN_FILTER_PRIORITY, 5 );
105 }
106 return $result;
107 }
108
109 /**
110 * Extends Groups user capability with its WP_User capability.
111 *
112 * @param boolean $result
113 * @param Groups_User $groups_user
114 * @param string $capability
115 * @param mixed $object
116 * @param mixed $args
117 *
118 * @return boolean
119 */
120 public static function groups_user_can( $result, $groups_user, $capability, $object, $args ) {
121 // The intention here is to complement capabilities from groups with those of the user.
122 // Our user_has_cap filter extends user capabilities with those from groups, i.e. the reverse complementary.
123 // Thus, our user_has_cap filter should not be involved here. Also, we want to avoid any potential circular
124 // dependency which could arise from having our groups_user_can fired again while we attend to that action here.
125 // To avoid any potential hickups, we also remove the filter while we handle it.
126 $filter_groups_user_can = remove_filter( 'groups_user_can', array( __CLASS__, 'groups_user_can' ), self::GROUPS_USER_CAN_FILTER_PRIORITY ); // @since 3.1.0
127 if ( !$result ) {
128 // Check if the capability exists, otherwise this will
129 // produce a deprecation warning "Usage of user levels by plugins
130 // and themes is deprecated", not because we actually use a
131 // deprecated user level, but because it doesn't exist.
132 if (
133 !is_numeric( $capability ) ||
134 Groups_Capability::read_by_capability( $capability )
135 ) {
136 if ( $groups_user instanceof Groups_User ) {
137 $user_id = $groups_user->get_user_id();
138 if ( $user_id !== null ) {
139
140 // reduce to remnant args; we have $args[0] as $capability, $args[1] as $user_id and $args[2] as $object and pass them along explicitly
141 if ( is_array( $args ) ) {
142 array_shift( $args ); // $capability <-> user_has_cap filter $args[0] -> requested capability
143 array_shift( $args ); // $user_id <-> user_has_cap filter $args[1] -> concerned user ID
144 array_shift( $args ); // $object <-> user_has_cap filter $args[2] -> typically object ID
145 } else if ( $args instanceof Traversable ) {
146 $pass_args = array();
147 $i = 0;
148 foreach ( $args as $arg ) {
149 if ( $i > 2 ) {
150 $pass_args[] = $arg;
151 }
152 }
153 $args = $pass_args;
154 }
155
156 if ( $object === null ) {
157 $result = self::unfiltered_user_can( $user_id, $capability );
158 } else {
159 // @since 3.0.0
160 $object_id = null;
161 if ( is_numeric( $object ) ) {
162 $object_id = Groups_Utility::id( $object );
163 if ( $object_id === false ) {
164 $object_id = null;
165 }
166 } else if ( is_object( $object ) && method_exists( $object, 'get_id' ) ) {
167 $object_id = $object->get_id();
168 }
169 // @since 4.0.0 make sure that $args can be unpacked
170 if ( !( is_array( $args ) || is_object( $args ) && $args instanceof Traversable ) ) {
171 $args = array();
172 }
173 // PHP >= 8.1.0 named arguments can be used after unpacking ...$args
174 // With prior PHP versions, the order in which values appear in an $args array
175 // determines the order of the parameters passed.
176 if ( $object_id !== null ) {
177 $result = self::unfiltered_user_can( $user_id, $capability, $object_id, ...$args );
178 } else {
179 $result = self::unfiltered_user_can( $user_id, $capability, $object, ...$args );
180 }
181 }
182 }
183 }
184 }
185 }
186 if ( $filter_groups_user_can ) {
187 add_filter( 'groups_user_can', array( __CLASS__, 'groups_user_can' ), self::GROUPS_USER_CAN_FILTER_PRIORITY, 5 ); // @since 3.1.0
188 }
189 return $result;
190 }
191
192 /**
193 * Extend user capabilities with Groups user capabilities.
194 *
195 * Hooked on the user_has_cap filter. See WP_User::has_cap().
196 *
197 * @see user_can()
198 * @see WP_User::has_cap()
199 *
200 * @param array $allcaps capability names mapped to boolean values representing whether the user has the capability
201 * @param array $caps required primitive capabilities for the requested capability
202 * @param array $args the requested capability in $args[0], the user ID in $args[1] and optionally the related object's ID in $args[2] and potentially further parameters in $args[3] and so on ...
203 * @param WP_User $user the user object
204 *
205 * @return array
206 */
207 public static function user_has_cap( $allcaps, $caps, $args, $user ) {
208 if ( is_array( $caps ) ) {
209 $user_id = 0;
210 if ( isset( $user->ID ) ) {
211 $user_id = intval( $user->ID );
212 } else if ( isset( $args[1] ) ) {
213 $user_id = intval( $args[1] );
214 }
215 $hash = md5( json_encode( $caps ) . json_encode( $args ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
216 $cached = Groups_Cache::get( self::HAS_CAP . '_' . $user_id . '_' . $hash, self::CACHE_GROUP );
217
218 if ( $cached !== null ) {
219 $_allcaps = $cached->get_value();
220 unset( $cached );
221 // Capabilities that were added after our value was cached must be added and
222 // those entries that provide different values must be adopted. This is necessary
223 // because other filters which hook into user_has_cap might have added or modified
224 // things after we had already cached the values.
225 foreach ( $allcaps as $cap => $value ) {
226 if (
227 !key_exists( $cap, $_allcaps ) ||
228 $_allcaps[$cap] !== $value
229 ) {
230 $_allcaps[$cap] = $value;
231 }
232 }
233 $allcaps = $_allcaps;
234 } else {
235 $requested_cap = $args[0] ?? ''; // will always be supplied, just in case
236 $object_id = $args[2] ?? null;
237 $groups_user = new Groups_User( $user_id );
238 // we need to deactivate this because invoking $groups_user->can()
239 // would trigger this same function and we would end up
240 // in an infinite loop
241 remove_filter( 'user_has_cap', array( __CLASS__, 'user_has_cap' ), self::USER_HAS_CAP_FILTER_PRIORITY );
242 foreach ( $caps as $cap ) {
243 // Check for known primitive capability, if user has it. This requires no parameters from $args.
244 if (
245 $cap === $requested_cap && $object_id === null || // COND A
246 $cap !== $requested_cap // COND B
247 ) {
248 if ( Groups_Capability::read_by_capability( $cap ) ) {
249 if ( $groups_user->can( $cap ) ) {
250 $allcaps[$cap] = true;
251 }
252 }
253 }
254 }
255 add_filter( 'user_has_cap', array( __CLASS__, 'user_has_cap' ), self::USER_HAS_CAP_FILTER_PRIORITY, 4 );
256 Groups_Cache::set( self::HAS_CAP . '_' . $user_id . '_' . $hash, $allcaps, self::CACHE_GROUP );
257 }
258 }
259 $allcaps = apply_filters( 'groups_user_has_cap', $allcaps, $caps, $args, $user );
260 return $allcaps;
261 }
262
263 /**
264 * Adds WordPress capabilities to Groups capabilities.
265 * Must be called explicitly.
266 *
267 * @see Groups_Controller::activate()
268 */
269 public static function activate() {
270 self::refresh_capabilities();
271 }
272
273 /**
274 * Refreshes Groups capabilities based on WordPress capabilities.
275 *
276 * @return int number of capabilities added
277 */
278 public static function refresh_capabilities() {
279 global $wp_roles;
280 $capabilities = array();
281 $count = 0;
282 if ( !isset( $wp_roles ) ) {
283 // just trigger initialization
284 get_role( 'administrator' );
285 }
286 $roles = $wp_roles->roles;
287 if ( is_array( $roles ) ) {
288 foreach ( $roles as $rolename => $atts ) {
289 if ( isset( $atts['capabilities'] ) && is_array( $atts['capabilities'] ) ) {
290 foreach ( $atts['capabilities'] as $capability => $value ) {
291 if ( !in_array( $capability, $capabilities ) ) {
292 $capabilities[] = $capability;
293 }
294 }
295 }
296 }
297 }
298 foreach ( $capabilities as $capability ) {
299 if ( !Groups_Capability::read_by_capability( $capability ) ) {
300 Groups_Capability::create( array( 'capability' => $capability ) );
301 $count++;
302 }
303 }
304 return $count;
305 }
306 }
307 Groups_WordPress::init();
308