LLMS_User_Permissions
Filters and actions related to user permissions
Contents
Source Source
File: includes/class.llms.user.permissions.php
class LLMS_User_Permissions { /** * Constructor * * @since 3.13.0 * @since 3.34.0 Always add the `editable_roles` filter. * * @return void */ public function __construct() { add_filter( 'user_has_cap', array( $this, 'handle_caps' ), 10, 3 ); add_filter( 'editable_roles', array( $this, 'editable_roles' ) ); } /** * Determines what other user roles can be managed by a user role * * Allows LMS Managers to create instructors and other managers. * Allows instructors to create & manage assistants. * * @since 3.13.0 * @since 3.34.0 Moved the `llms_editable_roles` filter to the class method get_editable_roles(). * @since 3.37.14 Use strict comparison. * @since 4.10.0 Better handling of users with multiple roles. * * @link https://codex.wordpress.org/Plugin_API/Filter_Reference/editable_roles * * @param array $all_roles All roles array. * @return array */ public function editable_roles( $all_roles ) { /** * Prevent issues when other plugins call get_editable_roles() before `init`. * * @link https://github.com/gocodebox/lifterlms/issues/1727 */ if ( ! function_exists( 'wp_get_current_user' ) ) { return $all_roles; } if ( is_multisite() && is_super_admin() ) { return $all_roles; } $user = wp_get_current_user(); $user_roles = $user->roles; if ( in_array( 'administrator', $user_roles, true ) ) { return $all_roles; } $editable_roles = self::get_editable_roles(); if ( empty( array_intersect( $user_roles, array_keys( $editable_roles ) ) ) ) { return $all_roles; } $roles = array(); foreach ( $user_roles as $user_role ) { if ( isset( $editable_roles[ $user_role ] ) ) { $roles = array_merge( $roles, $editable_roles[ $user_role ] ); } } $roles = array_unique( $roles ); foreach ( array_keys( $all_roles ) as $role ) { if ( ! in_array( $role, $roles, true ) ) { unset( $all_roles[ $role ] ); } } return $all_roles; } /** * Handle capabilities checks for lms content to allow *editing* content based on course instructor * * @since 3.13.0 * * @param bool[] $allcaps Array of key/value pairs where keys represent a capability name and boolean values * represent whether the user has that capability. * @param string[] $cap Required primitive capabilities for the requested capability. * @param array $args { * Arguments that accompany the requested capability check. * * @type string $0 Requested capability. * @type int $1 Concerned user ID. * @type mixed ...$2 Optional second and further parameters, typically object ID. * } * @return array */ public function edit_others_lms_content( $allcaps, $cap, $args ) { /** * this might be a problem * this happens when in wp-admin/includes/post.php * when actually creating/updating a course * and no post_id is passed in $args[2]. */ if ( empty( $args[2] ) ) { $allcaps[ $cap[0] ] = true; return $allcaps; } $instructor = llms_get_instructor( $args[1] ); if ( $instructor && $instructor->is_instructor( $args[2] ) ) { $allcaps[ $cap[0] ] = true; } return $allcaps; } /** * Get a map of roles that can be managed by LifterLMS User Roles * * @since 3.34.0 * * @return array */ public static function get_editable_roles() { /** * Get a map of roles that can be managed by LifterLMS User Roles * * @since 3.13.0 * * @param array $roles Array of user roles. The array key is the role and the value is an array of roles that can be managed by that role. */ $roles = apply_filters( 'llms_editable_roles', array( 'lms_manager' => array( 'instructor', 'instructors_assistant', 'lms_manager', 'student' ), 'instructor' => array( 'instructors_assistant' ), ) ); return $roles; } /** * Modify a users ability to `view_grades` * * Users can view the grades (quiz results) if one of the following conditions is met: * + Users can view their own grades. * + Admins and LMS Managers can view anyone's grade. * + Any user who has been explicitly granted the `view_grades` cap can view anyone's grade (via custom code). * + Any instructor/assistant who can `edit_post` for the course the quiz belongs to can view grades of the students within that course. * * @since 4.21.2 * * @param bool[] $allcaps Array of key/value pairs where keys represent a capability name and boolean values * represent whether the user has that capability. * @param array $args { * Arguments that accompany the requested capability check. * * @type string $0 Requested capability: 'view_grades'. * @type int $1 Current User ID. * @type int $2 Requested User ID. * @type int $3 WP_Post ID of the quiz. * } * @return array */ private function handle_cap_view_grades( $allcaps, $args ) { // Logged out user or missing required args. if ( empty( $args[1] ) || empty( $args[2] ) || empty( $args[3] ) ) { return $allcaps; } list( $requested_cap, $current_user_id, $requested_user_id, $post_id ) = $args; // Administrators and LMS managers explicitly have the cap so we don't need to perform any further checks. if ( ! empty( $allcaps[ $requested_cap ] ) ) { return $allcaps; } // Users can view their own grades. if ( $current_user_id === $requested_user_id ) { $allcaps[ $requested_cap ] = true; } elseif ( current_user_can( 'edit_post', $post_id ) ) { $instructor = llms_get_instructor( $current_user_id ); if ( $instructor && $instructor->has_student( $requested_user_id ) ) { $allcaps[ $requested_cap ] = true; } } return $allcaps; } /** * Custom capability checks for LifterLMS things * * @since 3.13.0 * @since 3.34.0 Add logic for `edit_users` and `delete_users` capabilities with regards to LifterLMS user roles. * Add logic for `view_students`, `edit_students`, and `delete_students` capabilities. * @since 3.36.5 Add `llms_user_caps_edit_others_posts_post_types` filter. * @since 3.37.14 Use strict comparison. * @since 4.21.2 Add logic to handle the `view_grades` capability. * * @param bool[] $allcaps Array of key/value pairs where keys represent a capability name and boolean values * represent whether the user has that capability. * @param string[] $cap Required primitive capabilities for the requested capability. * @param array $args { * Arguments that accompany the requested capability check. * * @type string $0 Requested capability. * @type int $1 Concerned user ID. * @type mixed ...$2 Optional second and further parameters, typically object ID. * } * @return array */ public function handle_caps( $allcaps, $cap, $args ) { /** * Modify the list of post types that users may not own but can still edit based on instructor permissions on the course * * @since 3.36.5 * * @param string[] $post_types Array of unprefixed post type names. */ $post_types = apply_filters( 'llms_user_caps_edit_others_posts_post_types', array( 'courses', 'lessons', 'sections', 'quizzes', 'questions', 'memberships' ) ); foreach ( $post_types as $cpt ) { // Allow any instructor to edit courses they're attached to. if ( in_array( sprintf( 'edit_others_%s', $cpt ), $cap, true ) ) { $allcaps = $this->edit_others_lms_content( $allcaps, $cap, $args ); } } $required_cap = ! empty( $cap[0] ) ? $cap[0] : false; if ( 'view_grades' === $required_cap ) { return $this->handle_cap_view_grades( $allcaps, $args ); } // We don't have a cap or the user doesn't have the requested cap. if ( ! $required_cap || empty( $allcaps[ $required_cap ] ) ) { return $allcaps; } $user_id = ! empty( $args[1] ) ? $args[1] : false; $object_id = ! empty( $args[2] ) ? $args[2] : false; if ( in_array( $required_cap, array( 'edit_users', 'delete_users' ), true ) ) { if ( $user_id && $object_id && false === $this->user_can_manage_user( $user_id, $object_id ) ) { unset( $allcaps[ $required_cap ] ); } } if ( in_array( $required_cap, array( 'view_students', 'edit_students', 'delete_students' ), true ) ) { $others_cap = str_replace( '_', '_others_', $required_cap ); if ( $user_id && $object_id && ! user_can( $user_id, $others_cap ) ) { $instructor = llms_get_instructor( $user_id ); if ( ! $instructor || ! $instructor->has_student( $object_id ) ) { unset( $allcaps[ $required_cap ] ); } } } return $allcaps; } /** * Determines if the current user is an instructor. * * @since 3.34.0 * * @return bool */ public static function is_current_user_instructor() { return ( current_user_can( 'lifterlms_instructor' ) && current_user_can( 'list_users' ) && ! current_user_can( 'manage_lifterlms' ) ); } /** * Determine if a user can manage another user. * * Run on `user_has_cap` filters for the `edit_users` and `delete_users` capabilities. * * @since 3.34.0 * @since 3.41.0 Better handling of users with multiple roles. * * @param int $user_id WP User ID of the user requesting to perform the action. * @param int $edit_id WP User ID of the user the action will be performed on. * @return bool|null Returns true if the user performs the action, false if it can't, and null for core user roles which are skipped. */ protected function user_can_manage_user( $user_id, $edit_id ) { $user = get_user_by( 'id', $user_id ); /** * Filter the list of "ignored" user roles * * If a user has one of the roles specified in this list, LifterLMS * will not attempt to determine if the user can manage other users * and will instead allow the WordPress core (or another plugin) * to determine if they have the required permissions. * * @since 3.41.0 * * @param string[] $ignored Array of user roles. */ $ignored = apply_filters( 'llms_user_can_manage_user_ignored_roles', array( 'administrator' ) ); $lms_roles = array_keys( LLMS_Roles::get_roles() ); $user_roles = array_intersect( $user->roles, $lms_roles ); $user_ignored_roles = array_intersect( $user->roles, $ignored ); /** * Skip the user because: * * + User has no LMS roles, eg: Administrator, Editor, or Subscriber. * + User has an LMS role and a "protected" role, eg: Administrator and student. * * In both scenarios we will return `null` which signals that the WordPress core (or another plugin) * should take care of determining if the user can manage the user. */ if ( ! $user_roles || ! empty( $user_ignored_roles ) ) { return null; } $edit_id = absint( $edit_id ); $user_id = absint( $user_id ); // Users can edit themselves. if ( $user_id === $edit_id ) { return true; } $edit_user = get_user_by( 'id', $edit_id ); $edit_roles = array_intersect( $edit_user->roles, $lms_roles ); $editable_roles = self::get_editable_roles(); foreach ( $user_roles as $role ) { if ( 'instructor' === $role && in_array( 'instructors_assistant', $edit_roles, true ) ) { $instructor = llms_get_instructor( $user ); if ( in_array( $edit_id, array_map( 'absint', $instructor->get_assistants() ), true ) ) { return true; } } elseif ( ! empty( $editable_roles[ $role ] ) && array_intersect( $edit_roles, $editable_roles[ $role ] ) ) { return true; } } return false; } }
Expand full source code Collapse full source code View on GitHub
Methods Methods
- __construct — Constructor
- edit_others_lms_content — Handle capabilities checks for lms content to allow *editing* content based on course instructor
- editable_roles — Determines what other user roles can be managed by a user role
- get_editable_roles — Get a map of roles that can be managed by LifterLMS User Roles
- handle_cap_view_grades — Modify a users ability to `view_grades`
- handle_caps — Custom capability checks for LifterLMS things
- is_current_user_instructor — Determines if the current user is an instructor.
- user_can_manage_user — Determine if a user can manage another user.
Changelog Changelog
Version | Description |
---|---|
3.41.0 | Improve user management of other users when the managing user has multiple roles. |
3.37.14 | Use strict comparisons where needed. |
3.36.5 | Add llms_user_caps_edit_others_posts_post_types filter to allow 3rd parties to utilize core methods for modifying other users posts. |
3.34.0 | Added methods and logic for managing user management of other users. Add logic for view_students , edit_students , and delete_students capabilities. |
3.13.0 | Introduced. |