can_view_all_reports() ) { return array( 'rooster_lms_view_all_reports' ); } if ( $scope->is_site_wide() ) { return array( 'rooster_lms_manage_orgs' ); } $allowed = match ( $cap ) { 'rooster_lms_manage_org' => $scope->can_manage( $org_id ), 'rooster_lms_manage_org_members' => $scope->can_manage_members( $org_id ), 'rooster_lms_view_org_reports' => $scope->can_view_reports( $org_id ), 'rooster_lms_grade_org' => $scope->can_grade( $org_id ), 'rooster_lms_manage_org_member' => self::member_cap( $scope, $user_id, $org_id, self::positive_int( $args[1] ?? null ), false ), 'rooster_lms_assign_org_member' => self::member_cap( $scope, $user_id, $org_id, self::positive_int( $args[1] ?? null ), true ), default => false, }; return $allowed ? array( 'exist' ) : array( 'do_not_allow' ); } private static function member_cap( OrgScope $scope, int $user_id, int $org_id, int $target_id, bool $assign ): bool { if ( $scope->can_manage( $org_id ) ) { return self::manageable_member( $user_id, $org_id, $target_id, $assign ); } if ( $target_id <= 0 || ! $scope->can_manage_members( $org_id ) ) { return false; } $target = OrgScope::target( $org_id, $target_id ); if ( ! $target['member'] || 0 === $target['roles'] ) { return false; } $units = $scope->multi_unit( $org_id ) ? \RoosterLMS\Domain\Services::placement_repo()->units_of( $org_id, $target_id ) : ( $target['unit'] > 0 ? array( $target['unit'] ) : array() ); if ( array() === $units ) { return false; } $scope->prime_units( array_merge( $units, array_keys( $target['units'] ) ) ); $covered = 0; foreach ( $units as $unit ) { $covered += ( $scope->roles_at( $org_id, $unit ) & MemberRole::MANAGER->value ) !== 0 ? 1 : 0; } if ( $assign ? 0 === $covered : count( $units ) !== $covered ) { return false; } if ( ! $assign && ! $scope->outranks( $org_id, $target['roles'], $target['units'] ) ) { return false; } return self::manageable_member( $user_id, $org_id, $target_id, $assign ); } private static function map_course_cap( $user_id, $args ): array { $user_id = is_numeric( $user_id ) ? (int) $user_id : 0; $args = is_array( $args ) ? array_values( $args ) : array(); $course_id = self::positive_int( $args[0] ?? null ); if ( $user_id <= 0 || $course_id <= 0 ) { return array( 'do_not_allow' ); } if ( OrgScope::for_user( $user_id )->is_site_wide() ) { return array( 'rooster_lms_manage_orgs' ); } $course = \RoosterLMS\Domain\Services::course_repo()->get( $course_id ); if ( null === $course ) { return array( 'do_not_allow' ); } if ( $course->home_post_id > 0 && Users::can( $user_id, 'edit_post', $course->home_post_id ) ) { return array( 'exist' ); } return in_array( $course_id, \RoosterLMS\Domain\Services::course_staff()->course_ids_of( $user_id ), true ) ? array( 'exist' ) : array( 'do_not_allow' ); } private static function map_unit_cap( string $cap, $user_id, $args ): array { $user_id = is_numeric( $user_id ) ? (int) $user_id : 0; $args = is_array( $args ) ? array_values( $args ) : array(); $org_id = self::positive_int( $args[0] ?? null ); $unit_id = self::positive_int( $args[1] ?? null ); if ( $user_id <= 0 || $org_id <= 0 || $unit_id <= 0 ) { return array( 'do_not_allow' ); } $scope = OrgScope::for_user( $user_id ); if ( ! $scope->unit_active_in( $org_id, $unit_id ) ) { return array( 'do_not_allow' ); } if ( 'rooster_lms_view_unit_reports' === $cap && $scope->can_view_all_reports() ) { return array( 'rooster_lms_view_all_reports' ); } if ( $scope->is_site_wide() ) { return array( 'rooster_lms_manage_orgs' ); } $allowed = match ( $cap ) { 'rooster_lms_manage_unit', 'rooster_lms_manage_unit_members' => $scope->can_manage_unit( $org_id, $unit_id ), 'rooster_lms_view_unit_reports' => $scope->can_view_unit_reports( $org_id, $unit_id ), 'rooster_lms_grade_unit' => $scope->can_grade_unit( $org_id, $unit_id ), default => false, }; return $allowed ? array( 'exist' ) : array( 'do_not_allow' ); } private static function map_place_cap( $user_id, $args ): array { $user_id = is_numeric( $user_id ) ? (int) $user_id : 0; $args = is_array( $args ) ? array_values( $args ) : array(); $org_id = self::positive_int( $args[0] ?? null ); $target_id = self::positive_int( $args[1] ?? null ); $unit_id = self::positive_int( $args[2] ?? null ); if ( $user_id <= 0 || $org_id <= 0 || $target_id <= 0 || $unit_id <= 0 || $target_id === $user_id ) { return array( 'do_not_allow' ); } $scope = OrgScope::for_user( $user_id ); if ( ! $scope->is_site_wide() && ! $scope->can_manage( $org_id ) ) { if ( ! $scope->can_manage_unit( $org_id, $unit_id ) || count( $scope->chain( $unit_id ) ) <= 1 ) { return array( 'do_not_allow' ); } return self::map_place_unit_manager( $scope, $user_id, $org_id, $target_id, $unit_id ); } $unit = \RoosterLMS\Domain\Services::org_unit_repo()->get( $unit_id ); if ( null === $unit || $unit->org_id !== $org_id || ! $unit->is_active() || $unit->is_root() ) { return array( 'do_not_allow' ); } if ( $scope->is_site_wide() ) { $target = OrgScope::target( $org_id, $target_id ); return ! $target['member'] || 0 === $target['roles'] || null === Users::get( $target_id ) ? array( 'do_not_allow' ) : array( 'rooster_lms_manage_orgs' ); } return self::manageable_member( $user_id, $org_id, $target_id, false ) ? array( 'exist' ) : array( 'do_not_allow' ); } private static function map_place_unit_manager( OrgScope $scope, int $user_id, int $org_id, int $target_id, int $unit_id ): array { if ( ! self::manageable_member( $user_id, $org_id, $target_id, false ) ) { return array( 'do_not_allow' ); } $target = OrgScope::target( $org_id, $target_id ); $roles = $target['roles']; $units = \RoosterLMS\Domain\Services::placement_repo()->units_of( $org_id, $target_id ); if ( in_array( $unit_id, $units, true ) ) { return $scope->outranks( $org_id, $roles, $target['units'] ) ? array( 'exist' ) : array( 'do_not_allow' ); } $wide = MemberRole::MANAGER->value | MemberRole::INSTRUCTOR->value | MemberRole::REPORTER->value; return $scope->multi_unit( $org_id ) && ( $roles & $wide ) === 0 && array() !== $units ? array( 'exist' ) : array( 'do_not_allow' ); } public static function can_edit_member( OrgScope $scope, int $org, MemberRow $row, array $assigned ): bool { $viewer = $scope->user_id(); $user = $row->membership->user_id; if ( $scope->is_site_wide() ) { return ! ProtectedAccounts::is_protected( $user ) || Users::can( $viewer, 'manage_options' ); } if ( $scope->can_manage( $org ) ) { return $user !== $viewer && ! $row->membership->has( MemberRole::MANAGER ) && ! ProtectedAccounts::is_protected( $user ); } $wide = MemberRole::MANAGER->value | MemberRole::INSTRUCTOR->value | MemberRole::REPORTER->value; if ( $user === $viewer || $row->membership->org_id !== $org || null !== $row->membership->left_at || ( $row->membership->roles & $wide ) !== 0 || ProtectedAccounts::is_protected( $user ) ) { return false; } if ( $scope->multi_unit( $org ) ? ( ! $row->units_covered || array() === $row->units ) : $row->location_id <= 0 ) { return false; } return array() === $assigned || $scope->outranks( $org, $row->membership->roles, $assigned ); } private static function manageable_member( int $user_id, int $org_id, int $target_id, bool $assign ): bool { if ( $target_id <= 0 ) { return false; } $self = $target_id === $user_id; if ( ! $assign && $self ) { return false; } if ( $self ) { $target_roles = OrgScope::for_user( $user_id )->roles_in( $org_id ); } else { $target = OrgScope::target( $org_id, $target_id ); $target_roles = $target['member'] ? $target['roles'] : 0; } if ( 0 === $target_roles || null === Users::get( $target_id ) ) { return false; } if ( ! $assign && ( $target_roles & MemberRole::MANAGER->value ) !== 0 ) { return false; } return $self || ! ProtectedAccounts::is_protected( $target_id ); } private static function positive_int( $value ): int { if ( is_int( $value ) ) { return max( 0, $value ); } if ( is_string( $value ) && 1 === preg_match( '/^\d+$/', $value ) ) { return (int) $value; } return 0; } public static function post_type_capabilities(): array { $caps = array(); foreach ( self::POST_CAPABILITY_TYPES as $type ) { $plural = $type[1]; foreach ( array( 'edit_', 'edit_others_', 'publish_', 'read_private_', 'delete_', 'delete_private_', 'delete_published_', 'delete_others_', 'edit_private_', 'edit_published_' ) as $verb ) { $caps[] = $verb . $plural; } } return $caps; } public static function grant_to_administrators(): void { $role = get_role( 'administrator' ); if ( ! $role ) { return; } foreach ( array_merge( self::SITE, self::post_type_capabilities() ) as $cap ) { if ( ! $role->has_cap( $cap ) ) { $role->add_cap( $cap ); } } } public static function remove_everywhere(): void { $caps = array_merge( self::SITE, self::post_type_capabilities() ); foreach ( wp_roles()->role_objects as $role ) { foreach ( $caps as $cap ) { if ( $role->has_cap( $cap ) ) { $role->remove_cap( $cap ); } } } } }