package LJ;
use strict;
sub class_bit {
my ($class) = @_;
foreach my $bit (0..15) {
my $def = $LJ::CAP{$bit};
next unless $def->{_key} && $def->{_key} eq $class;
return $bit;
}
return undef;
}
# what class name does a given bit number represent?
sub class_of_bit {
my $bit = shift;
return $LJ::CAP{$bit}->{_key};
}
sub classes_from_mask {
my $caps = shift;
my @classes = ();
foreach my $bit (0..15) {
my $class = LJ::class_of_bit($bit);
next unless $class && LJ::caps_in_group($caps, $class);
push @classes, $class;
}
return @classes;
}
sub mask_from_classes {
my @classes = @_;
my $mask = 0;
foreach my $class (@classes) {
my $bit = LJ::class_bit($class);
$mask |= (1 << $bit);
}
return $mask;
}
sub mask_from_bits {
my @bits = @_;
my $mask = 0;
foreach my $bit (@bits) {
$mask |= (1 << $bit);
}
return $mask;
}
sub caps_in_group {
my ($caps, $class) = @_;
my $bit = LJ::class_bit($class);
unless (defined $bit) {
# this site has no underage class? 'underage' is the only
# general class.
return 0 if $class eq "underage";
# all other classes are site-defined, so we die on those not existing.
die "unknown class '$class'";
}
return ($caps+0 & (1 << $bit)) ? 1 : 0;
}
#
# name: LJ::name_caps
# des: Given a user's capability class bit mask, returns a
# site-specific string representing the capability class name.
# args: caps
# des-caps: 16 bit capability bitmask
#
sub name_caps
{
return undef unless LJ::are_hooks("name_caps");
my $caps = shift;
return LJ::run_hook("name_caps", $caps);
}
#
# name: LJ::name_caps_short
# des: Given a user's capability class bit mask, returns a
# site-specific short string code.
# args: caps
# des-caps: 16 bit capability bitmask
#
sub name_caps_short
{
return undef unless LJ::are_hooks("name_caps_short");
my $caps = shift;
return LJ::run_hook("name_caps_short", $caps);
}
#
# name: LJ::user_caps_icon
# des: Given a user's capability class bit mask, returns
# site-specific HTML with the capability class icon.
# args: caps
# des-caps: 16 bit capability bitmask
#
sub user_caps_icon
{
return undef unless LJ::are_hooks("user_caps_icon");
my $caps = shift;
return LJ::run_hook("user_caps_icon", $caps);
}
#
# name: LJ::get_cap
# des: Given a user object, capability class key or capability class bit mask
# and a capability/limit name,
# returns the maximum value allowed for given user or class, considering
# all the limits in each class the user is a part of.
# args: u_cap, capname
# des-u_cap: 16 bit capability bitmask or a user object from which the
# bitmask could be obtained
# des-capname: the name of a limit, defined in [special[caps]].
#
sub get_cap
{
my $caps = shift; # capability bitmask (16 bits), cap key or user object
my $cname = shift; # capability limit name
my $opts = shift; # { no_hook => 1/0 }
$opts ||= {};
# If caps is a reference
my $u = ref $caps ? $caps : undef;
# If caps is a reference get caps from User object
if ($u) {
$caps = $u->{'caps'};
# If it is not all digits assume it is a key
} elsif ($caps && $caps !~ /^\d+$/) {
$caps = 1 << LJ::class_bit($caps);
}
# The caps is the cap mask already or undef, force it to be a number
$caps += 0;
my $max = undef;
# allow a way for admins to force-set the read-only cap
# to lower writes on a cluster.
if ($cname eq "readonly" && $u &&
($LJ::READONLY_CLUSTER{$u->{clusterid}} ||
$LJ::READONLY_CLUSTER_ADVISORY{$u->{clusterid}} &&
! LJ::get_cap($u, "avoid_readonly"))) {
# HACK for desperate moments. in when_needed mode, see if
# database is locky first
my $cid = $u->{clusterid};
if ($LJ::READONLY_CLUSTER_ADVISORY{$cid} eq "when_needed") {
my $now = time();
return 1 if $LJ::LOCKY_CACHE{$cid} > $now - 15;
my $dbcm = LJ::get_cluster_master($u->{clusterid});
return 1 unless $dbcm;
my $sth = $dbcm->prepare("SHOW PROCESSLIST");
$sth->execute;
return 1 if $dbcm->err;
my $busy = 0;
my $too_busy = $LJ::WHEN_NEEDED_THRES || 300;
while (my $r = $sth->fetchrow_hashref) {
$busy++ if $r->{Command} ne "Sleep";
}
if ($busy > $too_busy) {
$LJ::LOCKY_CACHE{$cid} = $now;
return 1;
}
} else {
return 1;
}
}
# underage/coppa check etc
if ($cname eq "underage" && $u && $u->in_class("underage")) {
return 1;
}
# is there a hook for this cap name?
if (! $opts->{no_hook} && LJ::are_hooks("check_cap_$cname")) {
die "Hook 'check_cap_$cname' requires full user object"
unless LJ::isu($u);
my $val = LJ::run_hook("check_cap_$cname", $u);
return $val if defined $val;
# otherwise fall back to standard means
}
# otherwise check via other means
foreach my $bit (keys %LJ::CAP) {
next unless ($caps & (1 << $bit));
my $v = $LJ::CAP{$bit}->{$cname};
next unless (defined $v);
next if (defined $max && $max > $v);
$max = $v;
}
return defined $max ? $max : $LJ::CAP_DEF{$cname};
}
#
# name: LJ::get_cap_min
# des: Just like [func[LJ::get_cap]], but returns the minimum value.
# Although it might not make sense at first, some things are
# better when they're low, like the minimum amount of time
# a user might have to wait between getting updates or being
# allowed to refresh a page.
# args: u_cap, capname
# des-u_cap: 16 bit capability bitmask or a user object from which the
# bitmask could be obtained
# des-capname: the name of a limit, defined in [special[caps]].
#
sub get_cap_min
{
my $caps = shift; # capability bitmask (16 bits), or user object
my $cname = shift; # capability name
if (! defined $caps) { $caps = 0; }
elsif (isu($caps)) { $caps = $caps->{'caps'}; }
my $min = undef;
foreach my $bit (keys %LJ::CAP) {
next unless ($caps & (1 << $bit));
my $v = $LJ::CAP{$bit}->{$cname};
next unless (defined $v);
next if (defined $min && $min < $v);
$min = $v;
}
return defined $min ? $min : $LJ::CAP_DEF{$cname};
}
1;