1131 lines
38 KiB
Perl
1131 lines
38 KiB
Perl
|
|
#!/usr/bin/perl
|
||
|
|
#
|
||
|
|
# DW::User::Edges::WatchTrust
|
||
|
|
#
|
||
|
|
# Implements the watch and trust edges between accounts. These are closely
|
||
|
|
# related edges that serve a lot of core functionality of the site. Without
|
||
|
|
# these edges, the site will probably not work.
|
||
|
|
#
|
||
|
|
# Authors:
|
||
|
|
# Mark Smith <mark@dreamwidth.org>
|
||
|
|
#
|
||
|
|
# Copyright (c) 2008-2019 by Dreamwidth Studios, LLC.
|
||
|
|
#
|
||
|
|
# This program is free software; you may redistribute it and/or modify it under
|
||
|
|
# the same terms as Perl itself. For a copy of the license, please reference
|
||
|
|
# 'perldoc perlartistic' or 'perldoc perlgpl'.
|
||
|
|
#
|
||
|
|
|
||
|
|
package DW::User::Edges::WatchTrust;
|
||
|
|
|
||
|
|
use strict;
|
||
|
|
use v5.10;
|
||
|
|
use Log::Log4perl;
|
||
|
|
my $log = Log::Log4perl->get_logger(__PACKAGE__);
|
||
|
|
|
||
|
|
use Carp qw/ confess /;
|
||
|
|
|
||
|
|
use DW::User::Edges;
|
||
|
|
use DW::User::Edges::WatchTrust::Loader;
|
||
|
|
use DW::User::Edges::WatchTrust::UserHelper;
|
||
|
|
use LJ::Global::Constants;
|
||
|
|
|
||
|
|
# the watch edge simply adds one user's content to another user's watch page
|
||
|
|
# and has no security implications whatsoever
|
||
|
|
DW::User::Edges::define_edge(
|
||
|
|
watch => {
|
||
|
|
type => 'hashref',
|
||
|
|
db_edge => 'W',
|
||
|
|
options => {
|
||
|
|
fgcolor => { required => 0, type => 'int' },
|
||
|
|
bgcolor => { required => 0, type => 'int' },
|
||
|
|
nonotify => { required => 0, type => 'bool', default => 0 },
|
||
|
|
},
|
||
|
|
add_sub => \&_add_wt_edge,
|
||
|
|
del_sub => \&_del_wt_edge,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
# the trust edge is what provides authorization for one user to see another
|
||
|
|
# user's protected posts
|
||
|
|
DW::User::Edges::define_edge(
|
||
|
|
trust => {
|
||
|
|
type => 'hashref',
|
||
|
|
db_edge => 'T',
|
||
|
|
options => {
|
||
|
|
mask => { required => 0, type => 'int' },
|
||
|
|
nonotify => { required => 0, type => 'bool', default => 0 },
|
||
|
|
},
|
||
|
|
add_sub => \&_add_wt_edge,
|
||
|
|
del_sub => \&_del_wt_edge,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
# internal method used to add a watch/trust edge to an account
|
||
|
|
sub _add_wt_edge {
|
||
|
|
my ( $from_u, $to_u, $edges ) = @_;
|
||
|
|
|
||
|
|
# bail unless there are watch/trust edges
|
||
|
|
my ( $trust_edge, $watch_edge ) = ( delete $edges->{trust}, delete $edges->{watch} );
|
||
|
|
return unless $trust_edge || $watch_edge;
|
||
|
|
|
||
|
|
# now setup some helper variables
|
||
|
|
my $do_watch = $watch_edge ? 1 : 0;
|
||
|
|
$watch_edge ||= {};
|
||
|
|
my $do_trust = $trust_edge ? 1 : 0;
|
||
|
|
$trust_edge ||= {};
|
||
|
|
|
||
|
|
# throw errors if we're not allowed
|
||
|
|
return 0 if $do_watch && !$from_u->can_watch($to_u);
|
||
|
|
return 0 if $do_trust && !$from_u->can_trust($to_u);
|
||
|
|
|
||
|
|
# get current record, so we know what to modify
|
||
|
|
my $dbh = LJ::get_db_writer();
|
||
|
|
my $row = $dbh->selectrow_hashref(
|
||
|
|
'SELECT fgcolor, bgcolor, groupmask FROM wt_edges WHERE from_userid = ? AND to_userid = ?',
|
||
|
|
undef, $from_u->id, $to_u->id
|
||
|
|
);
|
||
|
|
confess $dbh->errstr if $dbh->err;
|
||
|
|
$row ||= { groupmask => 0 };
|
||
|
|
|
||
|
|
# store some existing trust/watch values for use later
|
||
|
|
my $existing_watch = $row->{groupmask} & ( 1 << 61 );
|
||
|
|
my $existing_trust = $row->{groupmask} & ~( 7 << 61 );
|
||
|
|
|
||
|
|
# only matters in the read case, but ...
|
||
|
|
my ( $fgcol, $bgcol ) = (
|
||
|
|
$row->{fgcolor} || LJ::color_todb('#000000'),
|
||
|
|
exists $row->{bgcolor} ? $row->{bgcolor} : LJ::color_todb('#ffffff')
|
||
|
|
);
|
||
|
|
$fgcol = $watch_edge->{fgcolor} if exists $watch_edge->{fgcolor};
|
||
|
|
$bgcol = $watch_edge->{bgcolor} if exists $watch_edge->{bgcolor};
|
||
|
|
|
||
|
|
# calculate the mask we're going to apply to the user; this is somewhat complicated
|
||
|
|
# as we have to account for situations where we're updating only one of the edges, as
|
||
|
|
# well as the situation where we are just updating the trust edge without changing
|
||
|
|
# the user's group memberships. lots of comments. start with a mask of 0.
|
||
|
|
my $mask = 0;
|
||
|
|
|
||
|
|
# if they are adding a watch edge, simply turn that bit on
|
||
|
|
$mask |= ( 1 << 61 ) if $do_watch;
|
||
|
|
|
||
|
|
# if they are not adding a watch edge, import the existing watch edge
|
||
|
|
$mask |= $existing_watch unless $do_watch;
|
||
|
|
|
||
|
|
# if they are adding a trust edge, we need to turn bit 1 on
|
||
|
|
$mask |= 1 if $do_trust;
|
||
|
|
|
||
|
|
# now, we have to copy some trustmask, depending on some factors
|
||
|
|
if (
|
||
|
|
( $do_watch && !$do_trust ) || # 1) if we're only updating watch
|
||
|
|
( $do_trust && !exists $trust_edge->{mask} )
|
||
|
|
) # 2) they're adding a trust edge but don't set a mask
|
||
|
|
{
|
||
|
|
# in these two cases, we want to copy up the trustmask from the database
|
||
|
|
$mask |= $existing_trust;
|
||
|
|
}
|
||
|
|
|
||
|
|
# the final case, they are trusting and have specified a mask; but note we cannot allow
|
||
|
|
# them to set the high bits
|
||
|
|
if ( $do_trust && exists $trust_edge->{mask} ) {
|
||
|
|
$mask |= ( $trust_edge->{mask} + 0 & ~( 7 << 61 ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
# now add the row
|
||
|
|
$dbh->do(
|
||
|
|
'REPLACE INTO wt_edges (from_userid, to_userid, fgcolor, bgcolor, groupmask) VALUES (?, ?, ?, ?, ?)',
|
||
|
|
undef, $from_u->id, $to_u->id, $fgcol, $bgcol, $mask
|
||
|
|
);
|
||
|
|
confess $dbh->errstr if $dbh->err;
|
||
|
|
|
||
|
|
# delete friend-of memcache keys for anyone who was added
|
||
|
|
my ( $from_userid, $to_userid ) = ( $from_u->id, $to_u->id );
|
||
|
|
LJ::MemCache::delete( [ $from_userid, "trustmask:$from_userid:$to_userid" ] );
|
||
|
|
LJ::memcache_kill( $to_userid, 'wt_edges_rev' );
|
||
|
|
LJ::memcache_kill( $from_userid, 'wt_edges' );
|
||
|
|
LJ::memcache_kill( $from_userid, 'wt_list' );
|
||
|
|
LJ::memcache_kill( $from_userid, 'watched' );
|
||
|
|
LJ::memcache_kill( $from_userid, 'trusted' );
|
||
|
|
LJ::memcache_kill( $to_userid, 'watched_by' );
|
||
|
|
LJ::memcache_kill( $to_userid, 'trusted_by' );
|
||
|
|
|
||
|
|
# fire notifications if we have theschwartz
|
||
|
|
my $notify =
|
||
|
|
!$from_u->equals($to_u)
|
||
|
|
&& $from_u->is_visible
|
||
|
|
&& ( $from_u->is_personal || $from_u->is_identity )
|
||
|
|
&& ( $to_u->is_personal || $to_u->is_identity )
|
||
|
|
&& !$to_u->has_banned($from_u) ? 1 : 0;
|
||
|
|
my $trust_notify = $notify && !$trust_edge->{nonotify} ? 1 : 0;
|
||
|
|
my $watch_notify = $notify && !$watch_edge->{nonotify} ? 1 : 0;
|
||
|
|
|
||
|
|
my @jobs;
|
||
|
|
|
||
|
|
push @jobs, LJ::Event::AddedToCircle->new( $to_u, $from_u, 1 )
|
||
|
|
if $do_trust && $trust_notify;
|
||
|
|
push @jobs, LJ::Event::AddedToCircle->new( $to_u, $from_u, 2 )
|
||
|
|
if $do_watch && $watch_notify;
|
||
|
|
|
||
|
|
DW::TaskQueue->dispatch(@jobs) if @jobs;
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# internal method to delete an edge
|
||
|
|
sub _del_wt_edge {
|
||
|
|
my ( $from_u, $to_u, $edges ) = @_;
|
||
|
|
$from_u = LJ::want_user($from_u) or return 0;
|
||
|
|
$to_u = LJ::want_user($to_u) or return 0;
|
||
|
|
|
||
|
|
# determine if we're doing an update or a delete
|
||
|
|
my $de_watch = delete $edges->{watch};
|
||
|
|
my $de_trust = delete $edges->{trust};
|
||
|
|
return 1 unless $de_watch || $de_trust;
|
||
|
|
|
||
|
|
# now setup some helper variables
|
||
|
|
my $do_watch = $de_watch ? 1 : 0;
|
||
|
|
my $do_trust = $de_trust ? 1 : 0;
|
||
|
|
|
||
|
|
# get what we know
|
||
|
|
my $does_watch = $from_u->watches($to_u);
|
||
|
|
my $does_trust = $from_u->trusts($to_u);
|
||
|
|
return 1 unless $does_watch || $does_trust;
|
||
|
|
|
||
|
|
# make sure we have a valid edge to remove
|
||
|
|
return 1
|
||
|
|
unless ( $de_watch && $does_watch )
|
||
|
|
|| ( $de_trust && $does_trust );
|
||
|
|
|
||
|
|
my $dbh = LJ::get_db_writer()
|
||
|
|
or return 0;
|
||
|
|
|
||
|
|
# deletes are easy, these are cases where we're removing both edges,
|
||
|
|
# or removing the only remaining edge
|
||
|
|
if ( ( $de_watch && $de_trust )
|
||
|
|
|| ( $de_watch && $does_watch && !$does_trust )
|
||
|
|
|| ( $de_trust && $does_trust && !$does_watch ) )
|
||
|
|
{
|
||
|
|
|
||
|
|
$dbh->do( 'DELETE FROM wt_edges WHERE from_userid = ? AND to_userid = ?',
|
||
|
|
undef, $from_u->id, $to_u->id );
|
||
|
|
return 0 if $dbh->err;
|
||
|
|
|
||
|
|
# at this point, we're guaranteed to have only the other edge remaining
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
|
||
|
|
my $mask = $de_trust ? 1 << 61 : $from_u->trustmask($to_u);
|
||
|
|
|
||
|
|
$dbh->do( 'UPDATE wt_edges SET groupmask = ? WHERE from_userid = ? AND to_userid = ?',
|
||
|
|
undef, $mask, $from_u->id, $to_u->id );
|
||
|
|
return 0 if $dbh->err;
|
||
|
|
}
|
||
|
|
|
||
|
|
# kill memcaches
|
||
|
|
LJ::memcache_kill( $from_u, 'wt_edges' );
|
||
|
|
LJ::memcache_kill( $to_u, 'wt_edges_rev' );
|
||
|
|
LJ::memcache_kill( $from_u, 'wt_list' );
|
||
|
|
LJ::memcache_kill( $from_u, 'watched' );
|
||
|
|
LJ::memcache_kill( $from_u, 'trusted' );
|
||
|
|
LJ::memcache_kill( $to_u, 'watched_by' );
|
||
|
|
LJ::memcache_kill( $to_u, 'trusted_by' );
|
||
|
|
LJ::MemCache::delete( [ $from_u->id, "trustmask:" . $from_u->id . ":" . $to_u->id ] );
|
||
|
|
|
||
|
|
# fire notifications if we have theschwartz
|
||
|
|
my $notify =
|
||
|
|
!$from_u->equals($to_u)
|
||
|
|
&& $from_u->is_visible
|
||
|
|
&& ( $from_u->is_personal || $from_u->is_identity )
|
||
|
|
&& ( $to_u->is_personal || $to_u->is_identity )
|
||
|
|
&& !$to_u->has_banned($from_u) ? 1 : 0;
|
||
|
|
my $trust_notify = $notify && !$de_trust->{nonotify} ? 1 : 0;
|
||
|
|
my $watch_notify = $notify && !$de_watch->{nonotify} ? 1 : 0;
|
||
|
|
|
||
|
|
my @jobs;
|
||
|
|
push @jobs, LJ::Event::RemovedFromCircle->new( $to_u, $from_u, 1 )
|
||
|
|
if $do_trust && $trust_notify;
|
||
|
|
push @jobs, LJ::Event::RemovedFromCircle->new( $to_u, $from_u, 2 )
|
||
|
|
if $do_watch && $watch_notify;
|
||
|
|
DW::TaskQueue->dispatch(@jobs) if @jobs;
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# returns the valid version of a group name
|
||
|
|
sub valid_group_name {
|
||
|
|
my $name = $_[0];
|
||
|
|
|
||
|
|
# strip off trailing slash(es)
|
||
|
|
$name =~ s!/+\s*$!!;
|
||
|
|
|
||
|
|
# conform to maxes
|
||
|
|
$name = LJ::text_trim( $name, LJ::BMAX_GRPNAME, LJ::CMAX_GRPNAME );
|
||
|
|
|
||
|
|
return $name;
|
||
|
|
}
|
||
|
|
|
||
|
|
###############################################################################
|
||
|
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||
|
|
###############################################################################
|
||
|
|
|
||
|
|
# push methods up into the DW::User namespace
|
||
|
|
package DW::User;
|
||
|
|
use strict;
|
||
|
|
|
||
|
|
use Carp qw/ confess /;
|
||
|
|
|
||
|
|
# returns 1 if the given user watches the given account
|
||
|
|
# returns 0 otherwise
|
||
|
|
sub watches {
|
||
|
|
my ( $from_u, $to_u ) = @_;
|
||
|
|
$from_u = LJ::want_user($from_u) or return 0;
|
||
|
|
$to_u = LJ::want_user($to_u) or return 0;
|
||
|
|
|
||
|
|
# now get the mask; note we have to use the internal method so we
|
||
|
|
# can get the real mask - without the top-bit masking that $u->trustmask
|
||
|
|
# does...
|
||
|
|
my $mask = DW::User::Edges::WatchTrust::Loader::_trustmask( $from_u->id, $to_u->id );
|
||
|
|
return ( $mask & ( 1 << 61 ) ) ? 1 : 0;
|
||
|
|
}
|
||
|
|
*LJ::User::watches = \&watches;
|
||
|
|
|
||
|
|
# returns 1 if you generally trust the target user
|
||
|
|
# returns 0 otherwise
|
||
|
|
sub trusts {
|
||
|
|
my ( $from_u, $to_u ) = @_;
|
||
|
|
$from_u = LJ::want_user($from_u) or return 0;
|
||
|
|
$to_u = LJ::want_user($to_u) or return 0;
|
||
|
|
|
||
|
|
# you always trust yourself...
|
||
|
|
return 1 if $from_u->id == $to_u->id;
|
||
|
|
|
||
|
|
# now get the mask; again with the internal mask method
|
||
|
|
my $mask = DW::User::Edges::WatchTrust::Loader::_trustmask( $from_u->id, $to_u->id );
|
||
|
|
return ( $mask & 1 ) ? 1 : 0;
|
||
|
|
}
|
||
|
|
*LJ::User::trusts = \&trusts;
|
||
|
|
|
||
|
|
# return 1/0 if the given user is mutually trusted
|
||
|
|
sub mutually_trusts {
|
||
|
|
my ( $from_u, $to_u ) = @_;
|
||
|
|
$from_u = LJ::want_user($from_u) or return 0;
|
||
|
|
$to_u = LJ::want_user($to_u) or return 0;
|
||
|
|
|
||
|
|
return 1
|
||
|
|
if $from_u->trusts($to_u)
|
||
|
|
&& $to_u->trusts($from_u);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
*LJ::User::mutually_trusts = \&mutually_trusts;
|
||
|
|
|
||
|
|
# returns a numeric trustmask; can also be used as a setter if you specify a numeric
|
||
|
|
# as the third argument. in which case it returns the newly updated trustmask.
|
||
|
|
sub trustmask {
|
||
|
|
my ( $from_u, $to_u ) = @_;
|
||
|
|
$from_u = LJ::want_user($from_u) or return 0;
|
||
|
|
$to_u = LJ::want_user($to_u) or return 0;
|
||
|
|
|
||
|
|
# if we still have an argument, we need to set someone's mask
|
||
|
|
if ( scalar(@_) == 3 ) {
|
||
|
|
|
||
|
|
# make sure we trust them... we have to do this here because otherwise we could
|
||
|
|
# implicitly create a trust relationship when one doesn't exist
|
||
|
|
confess 'attempted to set trustmask on non-trusted edge'
|
||
|
|
unless $from_u->trusts($to_u);
|
||
|
|
|
||
|
|
# we update the mask by re-adding the trust edge; this is the simplest way
|
||
|
|
# that ensures we do everything "properly"
|
||
|
|
$from_u->add_edge( $to_u, trust => { mask => $_[2], nonotify => 1 } );
|
||
|
|
}
|
||
|
|
|
||
|
|
# note: we mask out the top three bits (i.e., the reserved bits and the watch bit)
|
||
|
|
# so external callers never see them.
|
||
|
|
return DW::User::Edges::WatchTrust::Loader::_trustmask( $from_u->id, $to_u->id ) & ~( 7 << 61 );
|
||
|
|
}
|
||
|
|
*LJ::User::trustmask = \&trustmask;
|
||
|
|
|
||
|
|
# name: LJ::User::get_birthdays
|
||
|
|
# des: get the upcoming birthdays for friends of a user. shows birthdays 3 months away by default
|
||
|
|
# pass in full => 1 to get all friends' birthdays.
|
||
|
|
# returns: arrayref of [ month, day, username ] arrayrefs
|
||
|
|
sub get_birthdays {
|
||
|
|
my ( $u, %opts ) = @_;
|
||
|
|
$u = LJ::want_user($u) or return undef;
|
||
|
|
|
||
|
|
my $months_ahead = $opts{months_ahead} || 3;
|
||
|
|
my $full = $opts{full};
|
||
|
|
|
||
|
|
# try to get the cached birthday
|
||
|
|
my $memkey = [ $u->userid, 'bdays:' . $u->userid . ':' . ( $full ? 'full' : $months_ahead ) ];
|
||
|
|
my $cached_bdays = LJ::MemCache::get($memkey);
|
||
|
|
return @$cached_bdays if $cached_bdays;
|
||
|
|
|
||
|
|
my @circle = $u->is_community ? $u->member_userids : $u->circle_userids;
|
||
|
|
my $nb = LJ::User->next_birthdays(@circle) or return undef;
|
||
|
|
|
||
|
|
# now map it to an array with DateTime objects
|
||
|
|
my @bdays = map { [ $_, DateTime->from_epoch( epoch => $nb->{$_} ) ] }
|
||
|
|
keys %$nb;
|
||
|
|
|
||
|
|
# now push a second set of objects, for each object, one year ago
|
||
|
|
push @bdays, [ $bdays[$_]->[0], $bdays[$_]->[1]->clone->subtract( years => 1 ) ]
|
||
|
|
for 0 .. $#bdays;
|
||
|
|
|
||
|
|
# sort the list... will end up nicely sorted by time
|
||
|
|
@bdays = sort { DateTime->compare( $a->[1], $b->[1] ) } @bdays;
|
||
|
|
|
||
|
|
# remove anything that is actually in the past
|
||
|
|
my $now = $u->time_now;
|
||
|
|
shift @bdays while @bdays && $bdays[0]->[1]->epoch < $now->epoch;
|
||
|
|
|
||
|
|
# remove anything that is too far in the future
|
||
|
|
my $months = $full ? 12 : $months_ahead;
|
||
|
|
my $compare = $now->add( months => $months )->epoch;
|
||
|
|
pop @bdays while @bdays && $bdays[-1]->[1]->epoch > $compare;
|
||
|
|
|
||
|
|
# now load the userids
|
||
|
|
my $uids = LJ::load_userids( map { $_->[0] } @bdays );
|
||
|
|
|
||
|
|
my @results;
|
||
|
|
foreach my $ub (@bdays) {
|
||
|
|
my $u = $uids->{ $ub->[0] };
|
||
|
|
next unless $u->is_personal && $u->can_notify_bday;
|
||
|
|
|
||
|
|
push @results, [ $ub->[1]->month, $ub->[1]->day, $u->user ];
|
||
|
|
}
|
||
|
|
|
||
|
|
# set birthdays in memcache for later
|
||
|
|
LJ::MemCache::set( $memkey, \@results, 86400 );
|
||
|
|
|
||
|
|
return @results;
|
||
|
|
}
|
||
|
|
*LJ::User::get_birthdays = \&get_birthdays;
|
||
|
|
|
||
|
|
# return users you trust
|
||
|
|
sub trusted_users {
|
||
|
|
my $u = shift;
|
||
|
|
my @trustids = $u->trusted_userids;
|
||
|
|
my $users = LJ::load_userids(@trustids);
|
||
|
|
return values %$users if wantarray;
|
||
|
|
return $users;
|
||
|
|
}
|
||
|
|
*LJ::User::trusted_users = \&trusted_users;
|
||
|
|
|
||
|
|
# return users you watch
|
||
|
|
sub watched_users {
|
||
|
|
my $u = shift;
|
||
|
|
my @watchids = $u->watched_userids;
|
||
|
|
my $users = LJ::load_userids(@watchids);
|
||
|
|
return values %$users if wantarray;
|
||
|
|
return $users;
|
||
|
|
}
|
||
|
|
*LJ::User::watched_users = \&watched_users;
|
||
|
|
|
||
|
|
# return users you watch and/or trust
|
||
|
|
sub circle_users {
|
||
|
|
my $u = shift;
|
||
|
|
my @circleids = $u->circle_userids;
|
||
|
|
my $users = LJ::load_userids(@circleids);
|
||
|
|
return values %$users if wantarray;
|
||
|
|
return $users;
|
||
|
|
}
|
||
|
|
*LJ::User::circle_users = \&circle_users;
|
||
|
|
|
||
|
|
# return users who trust you
|
||
|
|
sub trusted_by_users {
|
||
|
|
my $u = shift;
|
||
|
|
my @trustedbyids = $u->trusted_by_userids;
|
||
|
|
my $users = LJ::load_userids(@trustedbyids);
|
||
|
|
return values %$users if wantarray;
|
||
|
|
return $users;
|
||
|
|
}
|
||
|
|
*LJ::User::trusted_by_users = \&trusted_by_users;
|
||
|
|
|
||
|
|
# return users who watch you
|
||
|
|
sub watched_by_users {
|
||
|
|
my $u = shift;
|
||
|
|
my @watchedbyids = $u->watched_by_userids;
|
||
|
|
my $users = LJ::load_userids(@watchedbyids);
|
||
|
|
return values %$users if wantarray;
|
||
|
|
return $users;
|
||
|
|
}
|
||
|
|
*LJ::User::watched_by_users = \&watched_by_users;
|
||
|
|
|
||
|
|
# returns array of trusted by uids. by default, limited at 50,000 items.
|
||
|
|
sub trusted_by_userids {
|
||
|
|
my ( $u, %args ) = @_;
|
||
|
|
$u = LJ::want_user($u) or confess 'not a valid user object';
|
||
|
|
my $limit = delete $args{limit} || 0;
|
||
|
|
$limit = int($limit) || $LJ::MAX_WT_EDGES_LOAD;
|
||
|
|
confess 'unknown option' if %args;
|
||
|
|
|
||
|
|
return DW::User::Edges::WatchTrust::Loader::_wt_userids(
|
||
|
|
$u,
|
||
|
|
limit => $limit,
|
||
|
|
mode => 'trust',
|
||
|
|
reverse => 1
|
||
|
|
);
|
||
|
|
}
|
||
|
|
*LJ::User::trusted_by_userids = \&trusted_by_userids;
|
||
|
|
|
||
|
|
# returns array of trusted uids. by default, limited at 50,000 items.
|
||
|
|
sub trusted_userids {
|
||
|
|
my ( $u, %args ) = @_;
|
||
|
|
$u = LJ::want_user($u) or confess 'not a valid user object';
|
||
|
|
my $limit = delete $args{limit} || 0;
|
||
|
|
$limit = int($limit) || $LJ::MAX_WT_EDGES_LOAD;
|
||
|
|
confess 'unknown option' if %args;
|
||
|
|
|
||
|
|
return DW::User::Edges::WatchTrust::Loader::_wt_userids(
|
||
|
|
$u,
|
||
|
|
limit => $limit,
|
||
|
|
mode => 'trust'
|
||
|
|
);
|
||
|
|
}
|
||
|
|
*LJ::User::trusted_userids = \&trusted_userids;
|
||
|
|
|
||
|
|
# returns array of watched by uids. by default, limited at 50,000 items.
|
||
|
|
sub watched_by_userids {
|
||
|
|
my ( $u, %args ) = @_;
|
||
|
|
$u = LJ::want_user($u) or confess 'not a valid user object';
|
||
|
|
my $limit = delete $args{limit} || 0;
|
||
|
|
$limit = int($limit) || $LJ::MAX_WT_EDGES_LOAD;
|
||
|
|
confess 'unknown option' if %args;
|
||
|
|
|
||
|
|
return DW::User::Edges::WatchTrust::Loader::_wt_userids(
|
||
|
|
$u,
|
||
|
|
limit => $limit,
|
||
|
|
mode => 'watch',
|
||
|
|
reverse => 1
|
||
|
|
);
|
||
|
|
}
|
||
|
|
*LJ::User::watched_by_userids = \&watched_by_userids;
|
||
|
|
|
||
|
|
# returns array of watched uids. by default, limited at 50,000 items.
|
||
|
|
sub watched_userids {
|
||
|
|
my ( $u, %args ) = @_;
|
||
|
|
$u = LJ::want_user($u) or confess 'not a valid user object';
|
||
|
|
my $limit = delete $args{limit} || 0;
|
||
|
|
$limit = int($limit) || $LJ::MAX_WT_EDGES_LOAD;
|
||
|
|
confess 'unknown option' if %args;
|
||
|
|
|
||
|
|
return DW::User::Edges::WatchTrust::Loader::_wt_userids(
|
||
|
|
$u,
|
||
|
|
limit => $limit,
|
||
|
|
mode => 'watch'
|
||
|
|
);
|
||
|
|
}
|
||
|
|
*LJ::User::watched_userids = \&watched_userids;
|
||
|
|
|
||
|
|
# returns array of watched and/or trusted uids.
|
||
|
|
sub circle_userids {
|
||
|
|
my ( $u, %args ) = @_;
|
||
|
|
$u = LJ::want_user($u) or confess 'not a valid user object';
|
||
|
|
my %circle; # using a hash avoids duplicate elements
|
||
|
|
map { $circle{$_}++ } ( $u->watched_userids(%args), $u->trusted_userids(%args) );
|
||
|
|
return keys %circle;
|
||
|
|
}
|
||
|
|
*LJ::User::circle_userids = \&circle_userids;
|
||
|
|
|
||
|
|
# returns array of mutually watched userids. by default, limit at 50k.
|
||
|
|
# note that this function will be wildly inaccurate in any situation where
|
||
|
|
# an account actually has more than 50k of either direction. but we'll
|
||
|
|
# cross that bridge when we come to it...
|
||
|
|
sub mutually_watched_userids {
|
||
|
|
my ( $u, %args ) = @_;
|
||
|
|
$u = LJ::want_user($u) or confess 'not a valid user object';
|
||
|
|
|
||
|
|
my %mutual;
|
||
|
|
my %watched_fwd = map { $_ => 1 } $u->watched_userids(%args);
|
||
|
|
foreach my $uid ( $u->watched_by_userids(%args) ) {
|
||
|
|
$mutual{$uid} = 1
|
||
|
|
if exists $watched_fwd{$uid};
|
||
|
|
}
|
||
|
|
|
||
|
|
return keys %mutual;
|
||
|
|
}
|
||
|
|
*LJ::User::mutually_watched_userids = \&mutually_watched_userids;
|
||
|
|
|
||
|
|
# returns array of mutually trusted userids. by default, limit at 50k.
|
||
|
|
# same limitations as above.
|
||
|
|
sub mutually_trusted_userids {
|
||
|
|
my ( $u, %args ) = @_;
|
||
|
|
$u = LJ::want_user($u) or confess 'not a valid user object';
|
||
|
|
|
||
|
|
my %mutual;
|
||
|
|
my %trusted_fwd = map { $_ => 1 } $u->trusted_userids(%args);
|
||
|
|
foreach my $uid ( $u->trusted_by_userids(%args) ) {
|
||
|
|
$mutual{$uid} = 1
|
||
|
|
if exists $trusted_fwd{$uid};
|
||
|
|
}
|
||
|
|
|
||
|
|
return keys %mutual;
|
||
|
|
}
|
||
|
|
*LJ::User::mutually_trusted_userids = \&mutually_trusted_userids;
|
||
|
|
|
||
|
|
# returns hashref;
|
||
|
|
#
|
||
|
|
# { userid =>
|
||
|
|
# { groupmask => NNN, fgcolor => '#xxx', bgcolor => '#xxx', showbydefault => NNN }
|
||
|
|
# }
|
||
|
|
#
|
||
|
|
# one entry in the hashref for everything the given user trusts. note that fgcolor/bgcolor
|
||
|
|
# are only really useful for watched users, so these will be default/empty if the user
|
||
|
|
# is only trusted.
|
||
|
|
#
|
||
|
|
# arguments is a hash of options
|
||
|
|
# memcache_only => 1, if set, never hit database
|
||
|
|
# force_database => 1, if set, ALWAYS hit database (DANGER)
|
||
|
|
#
|
||
|
|
sub trust_list {
|
||
|
|
my ( $u, %args ) = @_;
|
||
|
|
$u = LJ::want_user($u) or confess 'invalid user object';
|
||
|
|
my $memc_only = delete $args{memcache_only} || 0;
|
||
|
|
my $db_only = delete $args{force_database} || 0;
|
||
|
|
confess 'extra/invalid arguments' if %args;
|
||
|
|
|
||
|
|
# special case, we can disable loading friends for a user if there is a site
|
||
|
|
# problem or some other issue with this codebranch
|
||
|
|
return undef if $LJ::FORCE_EMPTY_SUBSCRIPTIONS{ $u->id };
|
||
|
|
|
||
|
|
# attempt memcache if allowed
|
||
|
|
unless ($db_only) {
|
||
|
|
my $memc = DW::User::Edges::WatchTrust::Loader::_trust_list_memc($u);
|
||
|
|
return $memc if $memc;
|
||
|
|
}
|
||
|
|
|
||
|
|
# bail early if we are supposed to only hit memcache, this saves us from a
|
||
|
|
# potentially expensive database call in codepaths that are best-effort
|
||
|
|
return {} if $memc_only;
|
||
|
|
|
||
|
|
# damn you memcache for not having our data
|
||
|
|
return DW::User::Edges::WatchTrust::Loader::_trust_list_db( $u, force_database => $db_only );
|
||
|
|
}
|
||
|
|
*LJ::User::trust_list = \&trust_list;
|
||
|
|
|
||
|
|
# returns hashref;
|
||
|
|
#
|
||
|
|
# { userid =>
|
||
|
|
# { groupmask => NNN, fgcolor => '#xxx', bgcolor => '#xxx', showbydefault => NNN }
|
||
|
|
# }
|
||
|
|
#
|
||
|
|
# one entry in the hashref for everything the given user has in a particular trust
|
||
|
|
# group. you can specify the group by id or name.
|
||
|
|
#
|
||
|
|
# arguments is a hash of options
|
||
|
|
# id => 1, if set, get members of trust group id 1
|
||
|
|
# name => "Foo Group", if set, get members of trust group "Foo Group"
|
||
|
|
# memcache_only => 1, if set, never hit database
|
||
|
|
# force_database => 1, if set, ALWAYS hit database (DANGER)
|
||
|
|
#
|
||
|
|
sub trust_group_members {
|
||
|
|
my ( $u, %args ) = @_;
|
||
|
|
$u = LJ::want_user($u) or confess 'invalid user object';
|
||
|
|
my $memc_only = delete $args{memcache_only} || 0;
|
||
|
|
my $db_only = delete $args{force_database} || 0;
|
||
|
|
my $name = delete $args{name};
|
||
|
|
my $id = delete $args{id};
|
||
|
|
confess 'extra/invalid arguments' if %args;
|
||
|
|
confess 'need one of: id, name' unless $id || $name;
|
||
|
|
confess 'do not need both: id, name' if $id && $name;
|
||
|
|
|
||
|
|
# special case, we can disable loading friends for a user if there is a site
|
||
|
|
# problem or some other issue with this codebranch
|
||
|
|
return undef if $LJ::FORCE_EMPTY_SUBSCRIPTIONS{ $u->id };
|
||
|
|
|
||
|
|
# load the user's groups
|
||
|
|
my $grp = $u->trust_groups( id => $id, name => $name );
|
||
|
|
return {} unless $grp;
|
||
|
|
|
||
|
|
# calculate mask to use later
|
||
|
|
my $mask = 1 << $grp->{groupnum};
|
||
|
|
|
||
|
|
# attempt memcache if allowed
|
||
|
|
unless ($db_only) {
|
||
|
|
my $memc = DW::User::Edges::WatchTrust::Loader::_trust_group_members_memc( $mask, $u );
|
||
|
|
return $memc if $memc;
|
||
|
|
}
|
||
|
|
|
||
|
|
# bail early if we are supposed to only hit memcache, this saves us from a
|
||
|
|
# potentially expensive database call in codepaths that are best-effort
|
||
|
|
return {} if $memc_only;
|
||
|
|
|
||
|
|
# damn you memcache for not having our data
|
||
|
|
return DW::User::Edges::WatchTrust::Loader::_trust_group_members_db( $mask, $u,
|
||
|
|
force_database => $db_only );
|
||
|
|
}
|
||
|
|
*LJ::User::trust_group_members = \&trust_group_members;
|
||
|
|
|
||
|
|
# returns hashref;
|
||
|
|
#
|
||
|
|
# { userid =>
|
||
|
|
# { groupmask => NNN, fgcolor => '#xxx', bgcolor => '#xxx', showbydefault => NNN }
|
||
|
|
# }
|
||
|
|
#
|
||
|
|
# one entry in the hashref for everything the given user is watching.
|
||
|
|
#
|
||
|
|
# arguments is a hash of options
|
||
|
|
# memcache_only => 1, if set, never hit database
|
||
|
|
# force_database => 1, if set, ALWAYS hit database (DANGER)
|
||
|
|
#
|
||
|
|
sub watch_list {
|
||
|
|
my ( $u, %args ) = @_;
|
||
|
|
$u = LJ::want_user($u) or confess 'invalid user object';
|
||
|
|
my $memc_only = delete $args{memcache_only} || 0;
|
||
|
|
my $db_only = delete $args{force_database} || 0;
|
||
|
|
my $comm_okay = delete $args{community_okay} || 0;
|
||
|
|
confess 'extra/invalid arguments' if %args;
|
||
|
|
|
||
|
|
# special case, we can disable loading friends for a user if there is a site
|
||
|
|
# problem or some other issue with this codebranch
|
||
|
|
return undef if $LJ::FORCE_EMPTY_SUBSCRIPTIONS{ $u->id };
|
||
|
|
|
||
|
|
# attempt memcache if allowed
|
||
|
|
unless ($db_only) {
|
||
|
|
my $memc = DW::User::Edges::WatchTrust::Loader::_watch_list_memc( $u,
|
||
|
|
community_okay => $comm_okay );
|
||
|
|
return $memc if $memc;
|
||
|
|
}
|
||
|
|
|
||
|
|
# bail early if we are supposed to only hit memcache, this saves us from a
|
||
|
|
# potentially expensive database call in codepaths that are best-effort
|
||
|
|
return {} if $memc_only;
|
||
|
|
|
||
|
|
# damn you memcache for not having our data
|
||
|
|
return DW::User::Edges::WatchTrust::Loader::_watch_list_db(
|
||
|
|
$u,
|
||
|
|
force_database => $db_only,
|
||
|
|
community_okay => $comm_okay
|
||
|
|
);
|
||
|
|
}
|
||
|
|
*LJ::User::watch_list = \&watch_list;
|
||
|
|
|
||
|
|
# gets a hashref of trust group requested. arguments is a hash of options
|
||
|
|
# id => NNN, id of group to get
|
||
|
|
# name => "ZZZ", name of group to get
|
||
|
|
#
|
||
|
|
# returns undef if group not found
|
||
|
|
#
|
||
|
|
sub trust_groups {
|
||
|
|
my ( $u, %opts ) = @_;
|
||
|
|
$u = LJ::want_user($u)
|
||
|
|
or confess 'invalid user object';
|
||
|
|
my $id = delete $opts{id};
|
||
|
|
my $bit = defined $id ? $id + 0 : 0;
|
||
|
|
confess 'invalid bit number' if $bit < 0 || $bit > 60;
|
||
|
|
my $name = delete( $opts{name} );
|
||
|
|
$name = lc $name if defined $name;
|
||
|
|
confess 'invalid arguments' if %opts;
|
||
|
|
|
||
|
|
return DW::User::Edges::WatchTrust::Loader::_trust_groups( $u, $bit, $name );
|
||
|
|
}
|
||
|
|
*LJ::User::trust_groups = \&trust_groups;
|
||
|
|
|
||
|
|
# edits a new trust_group, arguments is a hash of options
|
||
|
|
# id => NNN, (optional) bit/ID of the group to edit (1..60)
|
||
|
|
# groupname => "ZZZ", name of this group
|
||
|
|
# sortorder => NNN, (optional) sort order (0..255)
|
||
|
|
# is_public => 1/0, (optional) defaults to 0
|
||
|
|
#
|
||
|
|
# arguments are used to create the group. if you don't specify an id then one
|
||
|
|
# will be automatically created for you.
|
||
|
|
#
|
||
|
|
# returns id of new group.
|
||
|
|
#
|
||
|
|
sub create_trust_group {
|
||
|
|
my ( $u, %opts ) = @_;
|
||
|
|
$u = LJ::want_user($u)
|
||
|
|
or confess 'invalid user object';
|
||
|
|
my $grp = $u->trust_groups;
|
||
|
|
|
||
|
|
# calculate an id to use
|
||
|
|
my $id = delete( $opts{id} ) + 0;
|
||
|
|
confess 'group with that id already exists'
|
||
|
|
if $id > 0 && exists $grp->{$id};
|
||
|
|
($id) ||= ( grep { !exists $grp->{$_} } 1 .. 60 )[0];
|
||
|
|
confess 'id invalid'
|
||
|
|
if $id < 1 || $id > 60;
|
||
|
|
|
||
|
|
# validate other parameters
|
||
|
|
confess 'invalid sortorder (not in range 0..255)'
|
||
|
|
if exists $opts{sortorder} && $opts{sortorder} !~ /^\d+$/;
|
||
|
|
confess 'invalid is_public (not 1/0)'
|
||
|
|
if exists $opts{is_public} && $opts{is_public} !~ /^(?:0|1)$/;
|
||
|
|
|
||
|
|
# need a name
|
||
|
|
$opts{groupname} = DW::User::Edges::WatchTrust::valid_group_name( $opts{groupname} );
|
||
|
|
confess 'name not provided'
|
||
|
|
unless $opts{groupname};
|
||
|
|
|
||
|
|
# now perform an edit with our chosen id
|
||
|
|
return $id
|
||
|
|
if $u->edit_trust_group( id => $id, _force_create => 1, %opts );
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
*LJ::User::create_trust_group = \&create_trust_group;
|
||
|
|
|
||
|
|
# edits a new trust_group, arguments is a hash of options
|
||
|
|
# id => NNN, bit/ID of the group to edit (1..60)
|
||
|
|
# groupname => "ZZZ", (optional) name of this group
|
||
|
|
# sortorder => NNN, (optional) sort order (0..255)
|
||
|
|
# is_public => 1/0, (optional) defaults to 0
|
||
|
|
#
|
||
|
|
# arguments are used to update the group, if you don't specify a particular
|
||
|
|
# parameter then we won't update that column.
|
||
|
|
#
|
||
|
|
# returns 1/0.
|
||
|
|
#
|
||
|
|
sub edit_trust_group {
|
||
|
|
my ( $u, %opts ) = @_;
|
||
|
|
$u = LJ::want_user($u)
|
||
|
|
or confess 'invalid user object';
|
||
|
|
my $id = delete( $opts{id} ) + 0;
|
||
|
|
confess 'invalid id number' if $id < 0 || $id > 60;
|
||
|
|
|
||
|
|
# and just in case they didn't tell us to change anything...
|
||
|
|
return 1 unless %opts;
|
||
|
|
|
||
|
|
# get current trust groups
|
||
|
|
my $grps = $u->trust_groups;
|
||
|
|
return 0 unless exists $grps->{$id} || $opts{_force_create};
|
||
|
|
|
||
|
|
# now calculate what to change
|
||
|
|
my %change = (
|
||
|
|
sortorder => $grps->{$id}->{sortorder},
|
||
|
|
groupname => $grps->{$id}->{groupname},
|
||
|
|
is_public => $grps->{$id}->{is_public},
|
||
|
|
);
|
||
|
|
$change{sortorder} = $opts{sortorder}
|
||
|
|
if exists $opts{sortorder} && $opts{sortorder} =~ /^\d+$/;
|
||
|
|
$change{groupname} = DW::User::Edges::WatchTrust::valid_group_name( $opts{groupname} )
|
||
|
|
if exists $opts{groupname};
|
||
|
|
$change{is_public} = $opts{is_public}
|
||
|
|
if exists $opts{is_public} && $opts{is_public} =~ /^(?:0|1)$/;
|
||
|
|
|
||
|
|
# update the database
|
||
|
|
$u->do(
|
||
|
|
'REPLACE INTO trust_groups (userid, groupnum, groupname, sortorder, is_public) VALUES (?, ?, ?, ?, ?)',
|
||
|
|
undef,
|
||
|
|
$u->id,
|
||
|
|
$id,
|
||
|
|
$change{groupname},
|
||
|
|
$change{sortorder} || 50,
|
||
|
|
$change{is_public} || 0
|
||
|
|
);
|
||
|
|
confess $u->errstr if $u->err;
|
||
|
|
|
||
|
|
# kill memcache and return
|
||
|
|
LJ::memcache_kill( $u, 'trust_group' );
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
*LJ::User::edit_trust_group = \&edit_trust_group;
|
||
|
|
|
||
|
|
# deletes a trust_group, arguments is a hash of options
|
||
|
|
# id => NNN, delete by id (1..60)
|
||
|
|
# name => "ZZZ", or delete by name
|
||
|
|
#
|
||
|
|
# specify either the id or the name. note that deletion is a rather permanent
|
||
|
|
# option that will remove this group from all entries that are secured to it
|
||
|
|
# as well as remove this bit from all trustmasks.
|
||
|
|
#
|
||
|
|
# returns 1/0.
|
||
|
|
#
|
||
|
|
sub delete_trust_group {
|
||
|
|
my ( $u, %opts ) = @_;
|
||
|
|
$u = LJ::want_user($u) or confess 'invalid user object';
|
||
|
|
|
||
|
|
# use existing accessor to figure out what group they mean
|
||
|
|
my $grp = $u->trust_groups( id => $opts{id}, name => $opts{name} );
|
||
|
|
return 0 unless $grp;
|
||
|
|
|
||
|
|
# set bit to remove
|
||
|
|
my $bit = $grp->{groupnum} + 0;
|
||
|
|
return 0 unless $bit >= 1 && $bit <= 60;
|
||
|
|
|
||
|
|
# remove all posts from allowing that group:
|
||
|
|
my @posts_to_clean = @{
|
||
|
|
$u->selectcol_arrayref(
|
||
|
|
q{SELECT jitemid FROM logsec2 WHERE journalid = ? AND allowmask & (1 << ?)},
|
||
|
|
undef, $u->id, $bit )
|
||
|
|
|| []
|
||
|
|
};
|
||
|
|
|
||
|
|
# now clean the posts while we can, this is a loop so we can do it in blocks of twenty
|
||
|
|
# as it's somewhat hard on the database to do this enmasse
|
||
|
|
my $userid = $u->id; # convenience
|
||
|
|
while (@posts_to_clean) {
|
||
|
|
my @batch = splice( @posts_to_clean, 0, 50 );
|
||
|
|
|
||
|
|
# actually updates the entries. note that we do not return an error here because
|
||
|
|
# it's not the end of the world if one of these fails...
|
||
|
|
my $in = join ',', @batch;
|
||
|
|
$u->do( "UPDATE log2 SET allowmask=allowmask & ~(1 << $bit) "
|
||
|
|
. "WHERE journalid=$userid AND jitemid IN ($in) AND security='usemask'" );
|
||
|
|
$u->do( "UPDATE logsec2 SET allowmask=allowmask & ~(1 << $bit) "
|
||
|
|
. "WHERE journalid=$userid AND jitemid IN ($in)" );
|
||
|
|
|
||
|
|
foreach my $id (@batch) {
|
||
|
|
LJ::MemCache::delete( [ $userid, "log2:$userid:$id" ] );
|
||
|
|
}
|
||
|
|
LJ::MemCache::delete( [ $userid, "log2lt:$userid" ] );
|
||
|
|
}
|
||
|
|
|
||
|
|
# notify the tags system so it can do its thing
|
||
|
|
LJ::Tags::deleted_trust_group( $u, $bit );
|
||
|
|
|
||
|
|
# used here down
|
||
|
|
my $dbh = LJ::get_db_writer()
|
||
|
|
or return 0;
|
||
|
|
|
||
|
|
# iterate over everybody in this group and remove the bit
|
||
|
|
my $tglist = $u->trust_group_members( id => $bit, force_database => 1 );
|
||
|
|
foreach my $tid ( keys %{ $tglist || {} } ) {
|
||
|
|
$dbh->do(
|
||
|
|
q{UPDATE wt_edges SET groupmask = groupmask & ~(1 << ?) WHERE from_userid = ? AND to_userid = ?},
|
||
|
|
undef, $bit, $u->id, $tid
|
||
|
|
);
|
||
|
|
|
||
|
|
# don't forget memcache
|
||
|
|
LJ::MemCache::delete( [ $userid, "trustmask:$userid:$tid" ] );
|
||
|
|
}
|
||
|
|
|
||
|
|
# finally remove the trust group, huzzah
|
||
|
|
$u->do( q{DELETE FROM trust_groups WHERE userid = ? AND groupnum = ?}, undef, $u->id, $bit );
|
||
|
|
return 0 if $u->err;
|
||
|
|
|
||
|
|
# invalidate memcache of friends/groups
|
||
|
|
LJ::memcache_kill( $u->id, "trust_group" );
|
||
|
|
LJ::memcache_kill( $u->id, "wt_list" );
|
||
|
|
|
||
|
|
# sister mary of the holy hand grenade says hi and apologies if any of the
|
||
|
|
# above failed. we think it worked by this point, though.
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
*LJ::User::delete_trust_group = \&delete_trust_group;
|
||
|
|
|
||
|
|
# alters a trustmask to munge someone's group membership
|
||
|
|
#
|
||
|
|
# $u->edit_trustmask( $otheru, ARGUMENTS )
|
||
|
|
#
|
||
|
|
# where ARGUMENTS can be one or more of:
|
||
|
|
#
|
||
|
|
# set => [ 1, 3 ] put $otheru in groups 1 and 3 only, remove from others
|
||
|
|
# add => [ 1, 3 ] add $otheru to groups 1 and 3
|
||
|
|
# remove => [ 1, 3 ] remove $otheru from groups 1 and 3
|
||
|
|
#
|
||
|
|
# if you are only adding/removing/setting a single group, you may pass the argument
|
||
|
|
# as a single number, not an arrayref. e.g.,
|
||
|
|
#
|
||
|
|
# $u->edit_trustmask( $otheru, add => 5 )
|
||
|
|
#
|
||
|
|
# adds $otheru to group 5.
|
||
|
|
#
|
||
|
|
# NOTE: passing the 'set' argument will override 'add' and 'remove' so they have no
|
||
|
|
# effect in the same call. (so use either set or add/remove. not both.)
|
||
|
|
#
|
||
|
|
# returns 1 on success, 0 on error.
|
||
|
|
#
|
||
|
|
sub edit_trustmask {
|
||
|
|
my ( $u, $tu, %opts ) = @_;
|
||
|
|
$u = LJ::want_user($u) or confess 'invalid user object';
|
||
|
|
$tu = LJ::want_user($tu) or confess 'invalid target user object';
|
||
|
|
return 0 unless $u->trusts($tu);
|
||
|
|
|
||
|
|
# there's got to be a better way of doing this... but we want our arrays to only
|
||
|
|
# contain valid group ids
|
||
|
|
my @add = grep { $_ >= 1 && $_ <= 60 }
|
||
|
|
map { $_ + 0 } @{ ref $opts{add} ? $opts{add} : [ $opts{add} ] };
|
||
|
|
my @del = grep { $_ >= 1 && $_ <= 60 }
|
||
|
|
map { $_ + 0 } @{ ref $opts{remove} ? $opts{remove} : [ $opts{remove} ] };
|
||
|
|
my @set = grep { $_ >= 1 && $_ <= 60 }
|
||
|
|
map { $_ + 0 } @{ ref $opts{set} ? $opts{set} : [ $opts{set} ] };
|
||
|
|
my $do_clear = ( ref $opts{set} eq 'ARRAY' && scalar(@set) == 0 ) ? 1 : 0;
|
||
|
|
return 1 unless @add || @del || @set || $do_clear;
|
||
|
|
|
||
|
|
# this is a special case, they said "set => []" with an empty arrayref,
|
||
|
|
# so we remove this person's membership from all groups
|
||
|
|
if ($do_clear) {
|
||
|
|
$u->trustmask( $tu, 0 );
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# if we're only doing a set, we can do that easily too
|
||
|
|
if (@set) {
|
||
|
|
my $mask = 0;
|
||
|
|
$mask += ( 1 << $_ ) foreach @set;
|
||
|
|
$u->trustmask( $tu, $mask );
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# hard path, we need to break down a user's mask and then update it
|
||
|
|
# and send out a new one
|
||
|
|
my $mask = $u->trustmask($tu);
|
||
|
|
my %groups = map { $_ => 1 } grep { $mask & ( 1 << $_ ) } 1 .. 60;
|
||
|
|
|
||
|
|
# now process adds/deletes
|
||
|
|
$groups{$_} = 1 foreach @add;
|
||
|
|
delete $groups{$_} foreach @del;
|
||
|
|
|
||
|
|
# now set it back and we're done
|
||
|
|
$mask = 0;
|
||
|
|
$mask += ( 1 << $_ ) foreach keys %groups;
|
||
|
|
$u->trustmask( $tu, $mask );
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
*LJ::User::edit_trustmask = \&edit_trustmask;
|
||
|
|
|
||
|
|
# give a user and a group, returns if they are in that group
|
||
|
|
sub trust_group_contains {
|
||
|
|
my ( $u, $tu, $gid ) = @_;
|
||
|
|
$u = LJ::want_user($u) or confess 'invalid user object';
|
||
|
|
$tu = LJ::want_user($tu) or confess 'invalid target user object';
|
||
|
|
$gid = $gid + 0;
|
||
|
|
|
||
|
|
return 0 unless $gid >= 1 && $gid <= 60;
|
||
|
|
return 1 if $u->trustmask($tu) & ( 1 << $gid );
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
*LJ::User::trust_group_contains = \&trust_group_contains;
|
||
|
|
|
||
|
|
# returns 1/0 depending on if the source is allowed to add a trust edge
|
||
|
|
# to the target. note: if you don't pass a target user, then we return
|
||
|
|
# a generic 1/0 meaning "this account is allowed to have a trust edge".
|
||
|
|
sub can_trust {
|
||
|
|
my ( $u, $tu, %opts ) = @_;
|
||
|
|
$u = LJ::want_user($u) or confess 'invalid user object';
|
||
|
|
$tu = LJ::want_user($tu);
|
||
|
|
|
||
|
|
my $errref = $opts{errref};
|
||
|
|
|
||
|
|
# the user must be an individual
|
||
|
|
unless ( $u->is_individual ) {
|
||
|
|
$$errref = LJ::Lang::ml('edges.trust.error.usernotindividual');
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
# the user must be visible
|
||
|
|
unless ( $u->is_visible ) {
|
||
|
|
$$errref = LJ::Lang::ml('edges.trust.error.usernotvisible');
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($tu) {
|
||
|
|
|
||
|
|
# the user cannot be the same as the target
|
||
|
|
if ( $u->equals($tu) ) {
|
||
|
|
$$errref = LJ::Lang::ml('edges.trust.error.userequalstarget');
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
# the target must be an individual
|
||
|
|
unless ( $tu->is_individual ) {
|
||
|
|
$$errref = LJ::Lang::ml('edges.trust.error.targetnotindividual');
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
# the target must not be purged/suspended/locked/deleted
|
||
|
|
if ( $tu->is_expunged || $tu->is_suspended || $tu->is_locked || $tu->is_deleted ) {
|
||
|
|
$$errref = LJ::Lang::ml('edges.trust.error.targetinvalidstatusvis');
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
# the target must not be banned by the user
|
||
|
|
if ( $u->has_banned($tu) ) {
|
||
|
|
$$errref = LJ::Lang::ml('edges.trust.error.userbannedtarget');
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# check limits
|
||
|
|
return 0 unless _can_add_wt_edge( $u, $errref, { target => $tu } );
|
||
|
|
|
||
|
|
# okay, good to go!
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
*LJ::User::can_trust = \&can_trust;
|
||
|
|
|
||
|
|
# returns 1/0 depending on if the source is allowed to add a watch edge
|
||
|
|
# to the target. note: if you don't pass a target user, then we return
|
||
|
|
# a generic 1/0 meaning "this account is allowed to have a watch edge".
|
||
|
|
sub can_watch {
|
||
|
|
my ( $u, $tu, %opts ) = @_;
|
||
|
|
$u = LJ::want_user($u) or confess 'invalid user object';
|
||
|
|
$tu = LJ::want_user($tu);
|
||
|
|
|
||
|
|
my $errref = $opts{errref};
|
||
|
|
|
||
|
|
# the user must be an individual
|
||
|
|
unless ( $u->is_individual ) {
|
||
|
|
$$errref = LJ::Lang::ml('edges.watch.error.usernotindividual');
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
# the user must be visible
|
||
|
|
unless ( $u->is_visible ) {
|
||
|
|
$$errref = LJ::Lang::ml('edges.watch.error.usernotvisible');
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($tu) {
|
||
|
|
|
||
|
|
# the target must not be purged/suspended/locked/deleted
|
||
|
|
if ( $tu->is_expunged || $tu->is_suspended || $tu->is_locked || $tu->is_deleted ) {
|
||
|
|
$$errref = LJ::Lang::ml('edges.watch.error.targetinvalidstatusvis');
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# check limits
|
||
|
|
return 0 unless _can_add_wt_edge( $u, $errref, { target => $tu } );
|
||
|
|
|
||
|
|
# okay, good to go!
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
*LJ::User::can_watch = \&can_watch;
|
||
|
|
|
||
|
|
# internal helper sub to determine if we're at the rate limit
|
||
|
|
sub _can_add_wt_edge {
|
||
|
|
my ( $u, $err, $opts ) = @_;
|
||
|
|
|
||
|
|
if ( $u->is_suspended ) {
|
||
|
|
$$err = LJ::Lang::ml("error.adduser.suspended");
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
# have they reached their friend limit?
|
||
|
|
my $fr_count = $opts->{'numfriends'} || $u->circle_userids;
|
||
|
|
my $maxfriends = $u->count_maxfriends;
|
||
|
|
if ( $fr_count >= $maxfriends ) {
|
||
|
|
$$err = LJ::Lang::ml( "error.adduser.limit", { maxnum => $maxfriends } );
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
# are they trying to add friends too quickly?
|
||
|
|
|
||
|
|
# don't count mutual friends
|
||
|
|
if ( defined $opts->{target} ) {
|
||
|
|
my $fr_user = $opts->{target};
|
||
|
|
|
||
|
|
# we needed LJ::User object, not just a hash.
|
||
|
|
if ( ref($fr_user) eq 'HASH' ) {
|
||
|
|
$fr_user = LJ::load_user( $fr_user->{username} );
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$fr_user = LJ::want_user($fr_user);
|
||
|
|
}
|
||
|
|
|
||
|
|
return 1
|
||
|
|
if $fr_user
|
||
|
|
&& ( $fr_user->watches($u) || $fr_user->trusts($u) );
|
||
|
|
}
|
||
|
|
|
||
|
|
unless ( $u->rate_log( 'addfriend', 1 ) ) {
|
||
|
|
$$err = LJ::Lang::ml("error.adduser.rate");
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
1;
|