Completed
Pull Request — master (#904)
by Zack
10:41 queued 06:56
created

GravityView_Roles_Capabilities::has_cap()   D

Complexity

Conditions 9
Paths 10

Size

Total Lines 45
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
eloc 14
nc 10
nop 3
dl 0
loc 45
ccs 13
cts 13
cp 1
crap 9
rs 4.909
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 23 and the first side effect is on line 14.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * Roles and Capabilities
4
 *
5
 * @package     GravityView
6
 * @license     GPL2+
7
 * @since       1.14
8
 * @author      Katz Web Services, Inc.
9
 * @link        http://gravityview.co
10
 * @copyright   Copyright 2015, Katz Web Services, Inc.
11
 */
12
13
// Exit if accessed directly
14
defined( 'ABSPATH' ) || exit;
15
16
/**
17
 * GravityView Roles Class
18
 *
19
 * This class handles the role creation and assignment of capabilities for those roles.
20
 *
21
 * @since 1.15
22
 */
23
class GravityView_Roles_Capabilities {
24
25
	/**
26
	 * @var GravityView_Roles_Capabilities|null
27
	 */
28
	static $instance = null;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $instance.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
29
30
	/**
31
	 * @since 1.15
32
	 * @return GravityView_Roles_Capabilities
33
	 */
34 1
	public static function get_instance() {
35
36 1
		if( ! self::$instance ) {
37
			self::$instance = new self;
38
		}
39
40 1
		return self::$instance;
41
	}
42
43
	/**
44
	 * Get things going
45
	 *
46
	 * @since 1.15
47
	 */
48
	public function __construct() {
49
		$this->add_hooks();
50
	}
51
52
	/**
53
	 * @since 1.15
54
	 */
55
	private function add_hooks() {
56
		add_filter( 'members_get_capabilities', array( 'GravityView_Roles_Capabilities', 'merge_with_all_caps' ) );
57
		add_action( 'members_register_cap_groups', array( $this, 'members_register_cap_group' ), 20 );
58
		add_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 10, 4 );
59
        add_action( 'admin_init', array( $this, 'add_caps') );
0 ignored issues
show
introduced by
No space before closing parenthesis of array is bad style
Loading history...
60
	}
61
62
63
	/**
64
	 * Add support for `gravityview_full_access` capability, and
65
	 *
66
	 * @see map_meta_cap()
67
	 *
68
	 * @since 1.15
69
	 *
70
	 * @param array   $allcaps An array of all the user's capabilities.
0 ignored issues
show
Bug introduced by
There is no parameter named $allcaps. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
71
	 * @param array   $caps    Actual capabilities for meta capability.
72
	 * @param array   $args    Optional parameters passed to has_cap(), typically object ID.
73
	 * @param WP_User|null $user    The user object, in WordPress 3.7.0 or higher
74
	 *
75
	 * @return mixed
76
	 */
77 7
	public function filter_user_has_cap( $usercaps = array(), $caps = array(), $args = array(), $user = NULL ) {
0 ignored issues
show
Unused Code introduced by
The parameter $args is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $user is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Coding Style introduced by
TRUE, FALSE and NULL must be lowercase; expected null, but found NULL.
Loading history...
78
79
		// Empty caps_to_check array
80 7
		if( ! $usercaps || ! $caps ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $usercaps of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $caps of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
81 4
			return $usercaps;
82
		}
83
84
		/**
85
		 * Enable all GravityView caps_to_check if `gravityview_full_access` is enabled
86
		 */
87 7
		if( ! empty( $usercaps['gravityview_full_access'] ) ) {
88
89 3
			$all_gravityview_caps = self::all_caps();
90
91 3
			foreach( $all_gravityview_caps as $gv_cap ) {
92 3
				$usercaps[ $gv_cap ] = true;
93
			}
94
95 3
			unset( $all_gravityview_caps );
96
		}
97
98 7
		$usercaps = $this->add_gravity_forms_usercaps_to_gravityview_caps( $usercaps );
99
100 7
		return $usercaps;
101
	}
102
103
	/**
104
	 * If a user has been assigned custom capabilities for Gravity Forms, but they haven't been assigned similar abilities
105
	 * in GravityView yet, we give temporary access to the permissions, until they're set.
106
	 *
107
	 * This is for custom roles that GravityView_Roles_Capabilities::add_caps() doesn't modify. If you have a
108
	 * custom role with the ability to edit any Gravity Forms entries (`gravityforms_edit_entries`), you would
109
	 * expect GravityView to match that capability, until the role has been updated with GravityView caps.
110
	 *
111
	 * @since 1.15
112
	 *
113
	 * @param array $usercaps User's allcaps array
114
	 *
115
	 * @return array
116
	 */
117 7
	private function add_gravity_forms_usercaps_to_gravityview_caps( $usercaps ) {
118
119
		$gf_to_gv_caps = array(
120 7
			'gravityforms_edit_entries'     => 'gravityview_edit_others_entries',
121
			'gravityforms_delete_entries'   => 'gravityview_delete_others_entries',
122
			'gravityforms_view_entry_notes' => 'gravityview_view_entry_notes',
123
			'gravityforms_edit_entry_notes' => 'gravityview_delete_entry_notes',
124
		);
125
126 7
		foreach ( $gf_to_gv_caps as $gf_cap => $gv_cap ) {
127 7
			if ( isset( $usercaps[ $gf_cap ] ) && ! isset( $usercaps[ $gv_cap ] ) ) {
128 7
				$usercaps[ $gv_cap ] = $usercaps[ $gf_cap ];
129
			}
130
		}
131
132 7
		return $usercaps;
133
	}
134
135
	/**
136
	 * Add GravityView group to Members 1.x plugin management screen
137
	 * @see members_register_cap_group()
138
	 * @since 1.15
139
	 * @return void
140
	 */
141
	function members_register_cap_group() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
142
		if ( function_exists( 'members_register_cap_group' ) ) {
143
144
			$args = array(
145
				'label'         => __( 'GravityView', 'gravityview' ),
146
				'icon'          => 'gv-icon-astronaut-head',
147
				'caps'          => self::all_caps(),
148
				'merge_added'   => true,
149
				'diff_added'    => false,
150
			);
151
152
			members_register_cap_group( 'gravityview', $args );
153
		}
154
	}
155
156
	/**
157
	 * Merge capabilities array with GravityView capabilities
158
	 *
159
	 * @since 1.15 Used to add GravityView caps to the Members plugin
160
	 * @param array $caps Existing capabilities
161
	 * @return array Modified capabilities array
162
	 */
163 1
	public static function merge_with_all_caps( $caps ) {
164
165 1
		$return_caps = array_merge( $caps, self::all_caps() );
166
167 1
		return array_unique( $return_caps );
168
	}
169
170
	/**
171
	 * Retrieves the global WP_Roles instance and instantiates it if necessary.
172
	 *
173
	 * @see wp_roles() This method uses the exact same code as wp_roles(), here for backward compatibility
174
	 *
175
	 * @global WP_Roles $wp_roles WP_Roles global instance.
176
	 *
177
	 * @return WP_Roles WP_Roles global instance if not already instantiated.
178
	 */
179
	private function wp_roles() {
180
		global $wp_roles;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
181
182
		if ( ! isset( $wp_roles ) ) {
183
			$wp_roles = new WP_Roles();
0 ignored issues
show
introduced by
Overridding WordPress globals is prohibited
Loading history...
184
		}
185
186
		return $wp_roles;
187
	}
188
189
	/**
190
	 * Add capabilities to their respective roles if they don't already exist
191
	 * This could be simpler, but the goal is speed.
192
	 *
193
	 * @since 1.15
194
	 * @return void
195
	 */
196
	public function add_caps() {
197
198
		$wp_roles = $this->wp_roles();
0 ignored issues
show
introduced by
Overridding WordPress globals is prohibited
Loading history...
199
200
		if ( is_object( $wp_roles ) ) {
201
202
			$_use_db_backup = $wp_roles->use_db;
203
204
			/**
205
			 * When $use_db is true, add_cap() performs update_option() every time.
206
			 * We disable updating the database here, then re-enable it below.
207
			 */
208
			$wp_roles->use_db = false;
209
210
			$capabilities = self::all_caps( false, false );
211
212
			foreach ( $capabilities as $role_slug => $role_caps ) {
213
				foreach ( $role_caps as $cap ) {
214
					$wp_roles->add_cap( $role_slug, $cap );
215
				}
216
			}
217
218
			/**
219
			 * Update the option, as it does in add_cap when $use_db is true
220
			 *
221
			 * @see WP_Roles::add_cap() Original code
222
			 */
223
			update_option( $wp_roles->role_key, $wp_roles->roles );
224
225
			/**
226
			 * Restore previous $use_db setting
227
			 */
228
			$wp_roles->use_db = $_use_db_backup;
229
		}
230
	}
231
232
	/**
233
	 * Get an array of GravityView capabilities
234
	 *
235
	 * @see get_post_type_capabilities()
236
	 *
237
	 * @since 1.15
238
	 *
239
	 * @param string $single_role If set, get the caps_to_check for a specific role. Pass 'all' to get all caps_to_check in a flat array. Default: `all`
240
	 * @param boolean $flat_array True: return all caps in a one-dimensional array. False: a multi-dimensional array with `$single_role` as keys and the caps as the values
241
	 *
242
	 * @return array If $role is set, flat array of caps_to_check. Otherwise, a multi-dimensional array of roles and their caps_to_check with the following keys: 'administrator', 'editor', 'author', 'contributor', 'subscriber'
243
	 */
244 8
	public static function all_caps( $single_role = false, $flat_array = true ) {
245
246
		// Change settings
247
		$administrator_caps = array(
248 8
			'gravityview_full_access', // Grant access to all caps_to_check
249
			'gravityview_view_settings',
250
			'gravityview_edit_settings',
251
			'gravityview_uninstall', // Ability to trigger the Uninstall @todo
252
			'gravityview_contact_support', // Whether able to send a message to support via the Support Port
253
		);
254
255
		// Edit, publish, delete own and others' stuff
256
		$editor_caps = array(
257 8
			'edit_others_gravityviews',
258
			'read_private_gravityviews',
259
			'delete_private_gravityviews',
260
			'delete_others_gravityviews',
261
			'edit_private_gravityviews',
262
			'publish_gravityviews',
263
			'delete_published_gravityviews',
264
			'edit_published_gravityviews',
265
			'copy_gravityviews', // For duplicate/clone View functionality
266
267
			// GF caps_to_check
268
			'gravityview_edit_others_entries',
269
			'gravityview_moderate_entries', // Approve or reject entries from the Admin; show/hide approval column in Entries screen
270
			'gravityview_delete_others_entries',
271
			'gravityview_add_entry_notes',
272
			'gravityview_view_entry_notes',
273
			'gravityview_delete_entry_notes',
274
			'gravityview_email_entry_notes',
275
		);
276
277
		// Edit, delete own stuff
278
		$author_caps = array(
279
			// GF caps_to_check
280 8
			'gravityview_edit_entries',
281
			'gravityview_edit_entry',
282
			'gravityview_edit_form_entries', // This is similar to `gravityview_edit_entries`, but checks against a Form ID $object_id
283
			'gravityview_delete_entries',
284
			'gravityview_delete_entry',
285
		);
286
287
		// Edit and delete drafts but not publish
288
		$contributor_caps = array(
289 8
			'edit_gravityviews', // Affects if you're able to see the Views menu in the Admin, and also if you're able to override cache using ?nocache
290
			'delete_gravityviews',
291
			'gravityview_getting_started', // Getting Started page access
292
			'gravityview_support_port', // Display GravityView Support Port
293
		);
294
295
		// Read only
296
		$subscriber_caps = array(
297 8
			'gravityview_view_entries',
298
			'gravityview_view_others_entries',
299
		);
300
301 8
		$subscriber = $subscriber_caps;
302 8
		$contributor = array_merge( $contributor_caps, $subscriber_caps );
303 8
		$author = array_merge( $author_caps, $contributor_caps, $subscriber_caps );
304 8
		$editor = array_merge( $editor_caps, $author_caps, $contributor_caps, $subscriber_caps );
305 8
		$administrator = array_merge( $administrator_caps, $editor_caps, $author_caps, $contributor_caps, $subscriber_caps );
306 8
		$all = $administrator;
307
308
		// If role is set, return caps_to_check for just that role.
309 8
		if( $single_role ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $single_role of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
310 8
			$caps = isset( ${$single_role} ) ? ${$single_role} : false;
311 8
			return $flat_array ? $caps : array( $single_role => $caps );
312
		}
313
314
		// Otherwise, return multi-dimensional array of all caps_to_check
315 5
		return $flat_array ? $all : compact( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
316
	}
317
318
	/**
319
	 * Check whether the current user has a capability
320
	 *
321
	 * @since 1.15
322
	 *
323
	 * @see WP_User::user_has_cap()
324
	 * @see https://codex.wordpress.org/Plugin_API/Filter_Reference/user_has_cap  You can filter permissions based on entry/View/form ID using `user_has_cap` filter
325
	 *
326
	 * @see  GFCommon::current_user_can_any
327
	 * @uses GFCommon::current_user_can_any
328
	 *
329
	 * @param string|array $caps_to_check Single capability or array of capabilities
330
	 * @param int|null $object_id (optional) Parameter can be used to check for capabilities against a specific object, such as a post or us
331
	 * @param int|null $user_id (optional) Check the capabilities for a user who is not necessarily the currently logged-in user
332
	 *
333
	 * @return bool True: user has at least one passed capability; False: user does not have any defined capabilities
334
	 */
335 8
	public static function has_cap( $caps_to_check = '', $object_id = null, $user_id = null ) {
336
337
		/**
338
		 * @filter `gravityview/capabilities/allow_logged_out` Shall we allow a cap check for non-logged in users?
339
		 *
340
		 * There are use-cases, albeit strange ones, where we'd like to check and override capabilities for
341
		 *  for a non-logged in user.
342
		 *
343
		 * Examples, you ask? https://github.com/gravityview/GravityView/issues/826
344
		 *
345
		 * @param boolean $allow_logged_out Allow the capability check or bail without even checking. Default: false. Do not allow. Do not pass Go. Do not collect $200.
346
		 * @param int|null $object_id (optional) Parameter can be used to check for capabilities against a specific object, such as a post or us.
347
		 * @param int|null $user_id (optional) Check the capabilities for a user who is not necessarily the currently logged-in user.
348
		 */
349 8
		$allow_logged_out = apply_filters( 'gravityview/capabilities/allow_logged_out', false, $caps_to_check, $object_id, $user_id );
350
351
		/**
352
		 * We bail with a negative response without even checking if:
353
		 *
354
		 * 1. The current user is not logged in and non-logged in users are considered unprivileged (@see `gravityview/capabilities/allow_logged_out` filter).
355
		 * 2. If the caps to check are empty.
356
		 */
357 8
		if ( ( ! is_user_logged_in() && ! $allow_logged_out ) || empty( $caps_to_check ) ) {
358 1
			return false;
359
		}
360
361 8
		$has_cap = false;
362
363
		// Add full access caps for GV & GF
364 8
		$caps_to_check = self::maybe_add_full_access_caps( $caps_to_check );
365
366 8
		foreach ( $caps_to_check as $cap ) {
367 8
			if( ! is_null( $object_id ) ) {
368 4
				$has_cap = $user_id ? user_can( $user_id, $cap, $object_id ) : current_user_can( $cap, $object_id );
369
			} else {
370 5
				$has_cap = $user_id ? user_can( $user_id, $cap ) : current_user_can( $cap );
371
			}
372
			// At the first successful response, stop checking
373 8
			if( $has_cap ) {
374 8
				break;
375
			}
376
		}
377
378 8
		return $has_cap;
379
	}
380
381
	/**
382
	 * Add Gravity Forms and GravityView's "full access" caps when any other caps are checked against.
383
	 *
384
	 * @since 1.15
385
386
	 * @param array $caps_to_check
387
	 *
388
	 * @return array
389
	 */
390 5
	public static function maybe_add_full_access_caps( $caps_to_check = array() ) {
391
392 5
		$caps_to_check = (array)$caps_to_check;
0 ignored issues
show
introduced by
No space after closing casting parenthesis is prohibited
Loading history...
393
394 5
		$all_gravityview_caps = self::all_caps();
395
396
		// Are there any $caps_to_check that are from GravityView?
397 5
		if( $has_gravityview_caps = array_intersect( $caps_to_check, $all_gravityview_caps ) ) {
398 4
			$caps_to_check[] = 'gravityview_full_access';
399
		}
400
401 5
		$all_gravity_forms_caps = class_exists( 'GFCommon' ) ? GFCommon::all_caps() : array();
402
403
		// Are there any $caps_to_check that are from Gravity Forms?
404 5
		if( $all_gravity_forms_caps = array_intersect( $caps_to_check, $all_gravity_forms_caps ) ) {
405 4
			$caps_to_check[] = 'gform_full_access';
406
		}
407
408 5
		return array_unique( $caps_to_check );
409
	}
410
411
	/**
412
	 * Remove all GravityView caps_to_check from all roles
413
	 *
414
	 * @since 1.15
415
	 * @return void
416
	 */
417
	public function remove_caps() {
418
419
		$wp_roles = $this->wp_roles();
0 ignored issues
show
introduced by
Overridding WordPress globals is prohibited
Loading history...
420
421
		if ( is_object( $wp_roles ) ) {
422
423
			/** Remove all GravityView caps_to_check from all roles */
424
			$capabilities = self::all_caps();
425
426
			// Loop through each role and remove GV caps_to_check
427
			foreach( $wp_roles->get_names() as $role_slug => $role_name ) {
428
				foreach ( $capabilities as $cap ) {
429
					$wp_roles->remove_cap( $role_slug, $cap );
430
				}
431
			}
432
		}
433
	}
434
}
435
436
add_action( 'init', array( 'GravityView_Roles_Capabilities', 'get_instance' ), 1 );