public function can(string $action, string $context_path = '', string $cabin = \CABIN_NAME, int $user_id = 0) : bool
{
$state = State::instance();
if (empty($cabin)) {
$cabin = \CABIN_NAME;
}
// If you don't specify the user ID to check, it will use the current
// user ID instead, by default.
if (empty($user_id)) {
if (!empty($_SESSION['userid'])) {
$user_id = (int) $_SESSION['userid'];
}
}
// If you're a super-user, the answer is "yes, yes you can".
if ($this->isSuperUser($user_id)) {
return true;
}
$allowed = false;
$failed_one = false;
// Get all applicable contexts
$contexts = self::getOverlap($context_path, $cabin);
if (empty($contexts)) {
// Sane default: In the absence of permissions, return false
return false;
}
if ($user_id > 0) {
// You need to be allowed in every relevant context.
foreach ($contexts as $c_id) {
if (self::checkUser($action, $c_id, $user_id) || self::checkUsersGroups($action, $c_id, $user_id)) {
$allowed = true;
} else {
$failed_one = true;
}
}
} else {
if (!$state->universal['guest_groups']) {
return false;
}
// Guests can be assigned to groups. This fails closed if they aren't given any.
foreach ($contexts as $c_id) {
$ctx_res = false;
foreach ($state->universal['guest_groups'] as $grp) {
if (self::checkGroup($action, $c_id, $grp)) {
$ctx_res = true;
}
}
if ($ctx_res) {
$allowed = true;
} else {
$failed_one = true;
}
}
}
// We return true if we were allowed at least once and we did not fail
// in one of the overlapping contexts
return $allowed && !$failed_one;
}