3713 lines
117 KiB
Perl
3713 lines
117 KiB
Perl
|
#
|
||
|
# LiveJournal user object
|
||
|
#
|
||
|
# 2004-07-21: we're transition from $u hashrefs to $u objects, currently
|
||
|
# backed by hashrefs, to ease migration. in the future,
|
||
|
# more methods from ljlib.pl and other places will move here,
|
||
|
# and the representation of a $u object will change to 'fields'.
|
||
|
# at present, the motivation to moving to $u objects is to do
|
||
|
# all database access for a given user through his/her $u object
|
||
|
# so the queries can be tagged for use by the star replication
|
||
|
# daemon.
|
||
|
|
||
|
use strict;
|
||
|
|
||
|
package LJ::User;
|
||
|
use Carp;
|
||
|
use lib "$ENV{'LJHOME'}/cgi-bin";
|
||
|
use LJ::MemCache;
|
||
|
|
||
|
|
||
|
# instance method: returns userprop for a user. currently from cache with no
|
||
|
# way yet to force master.
|
||
|
sub prop {
|
||
|
my ($u, $prop) = @_;
|
||
|
|
||
|
# some props have accessors which do crazy things, if so they need
|
||
|
# to be redirected from this method, which only loads raw values
|
||
|
if ({ map { $_ => 1 }
|
||
|
qw(opt_sharebday opt_showbday opt_showlocation opt_showmutualfriends
|
||
|
view_control_strip show_control_strip opt_ctxpopup
|
||
|
esn_inbox_default_expand opt_getting_started)
|
||
|
}->{$prop})
|
||
|
{
|
||
|
return $u->$prop;
|
||
|
}
|
||
|
|
||
|
return $u->raw_prop($prop);
|
||
|
}
|
||
|
|
||
|
sub raw_prop {
|
||
|
my ($u, $prop) = @_;
|
||
|
$u->preload_props($prop) unless exists $u->{$_};
|
||
|
return $u->{$prop};
|
||
|
}
|
||
|
|
||
|
sub preload_props {
|
||
|
my $u = shift;
|
||
|
LJ::load_user_props($u, @_);
|
||
|
}
|
||
|
|
||
|
sub readonly {
|
||
|
my $u = shift;
|
||
|
return LJ::get_cap($u, "readonly");
|
||
|
}
|
||
|
|
||
|
# returns self (the $u object which can be used for $u->do) if
|
||
|
# user is writable, else 0
|
||
|
sub writer {
|
||
|
my $u = shift;
|
||
|
return $u if $u->{'_dbcm'} ||= LJ::get_cluster_master($u);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
# returns a true value if the user is underage; or if you give it an argument,
|
||
|
# will turn on/off that user's underage status. can also take a second argument
|
||
|
# when you're setting the flag to also update the underage_status userprop
|
||
|
# which is used to record if a user was ever marked as underage.
|
||
|
sub underage {
|
||
|
# has no bearing if this isn't on
|
||
|
return undef unless $LJ::UNDERAGE_BIT;
|
||
|
|
||
|
# now get the args and continue
|
||
|
my $u = shift;
|
||
|
return LJ::get_cap($u, 'underage') unless @_;
|
||
|
|
||
|
# now set it on or off
|
||
|
my $on = shift() ? 1 : 0;
|
||
|
if ($on) {
|
||
|
LJ::modify_caps($u, [ $LJ::UNDERAGE_BIT ], []);
|
||
|
$u->{caps} |= 1 << $LJ::UNDERAGE_BIT;
|
||
|
} else {
|
||
|
LJ::modify_caps($u, [], [ $LJ::UNDERAGE_BIT ]);
|
||
|
$u->{caps} &= !(1 << $LJ::UNDERAGE_BIT);
|
||
|
}
|
||
|
|
||
|
# now set their status flag if one was sent
|
||
|
my $status = shift();
|
||
|
if ($status || $on) {
|
||
|
# by default, just records if user was ever underage ("Y")
|
||
|
$u->underage_status($status || 'Y');
|
||
|
}
|
||
|
|
||
|
# add to statushistory
|
||
|
if (my $shwhen = shift()) {
|
||
|
my $text = $on ? "marked" : "unmarked";
|
||
|
my $status = $u->underage_status;
|
||
|
LJ::statushistory_add($u, undef, "coppa", "$text; status=$status; when=$shwhen");
|
||
|
}
|
||
|
|
||
|
# now fire off any hooks that are available
|
||
|
LJ::run_hooks('set_underage', {
|
||
|
u => $u,
|
||
|
on => $on,
|
||
|
status => $u->underage_status,
|
||
|
});
|
||
|
|
||
|
# return what we set it to
|
||
|
return $on;
|
||
|
}
|
||
|
|
||
|
# log a line to our userlog
|
||
|
sub log_event {
|
||
|
my $u = shift;
|
||
|
|
||
|
my ($type, $info) = @_;
|
||
|
return undef unless $type;
|
||
|
$info ||= {};
|
||
|
|
||
|
# now get variables we need; we use delete to remove them from the hash so when we're
|
||
|
# done we can just encode what's left
|
||
|
my $ip = delete($info->{ip}) || LJ::get_remote_ip() || undef;
|
||
|
my $uniq = delete $info->{uniq};
|
||
|
unless ($uniq) {
|
||
|
eval {
|
||
|
$uniq = Apache->request->notes('uniq');
|
||
|
};
|
||
|
}
|
||
|
my $remote = delete($info->{remote}) || LJ::get_remote() || undef;
|
||
|
my $targetid = (delete($info->{actiontarget})+0) || undef;
|
||
|
my $extra = %$info ? join('&', map { LJ::eurl($_) . '=' . LJ::eurl($info->{$_}) } keys %$info) : undef;
|
||
|
|
||
|
# now insert the data we have
|
||
|
$u->do("INSERT INTO userlog (userid, logtime, action, actiontarget, remoteid, ip, uniq, extra) " .
|
||
|
"VALUES (?, UNIX_TIMESTAMP(), ?, ?, ?, ?, ?, ?)", undef, $u->{userid}, $type,
|
||
|
$targetid, $remote ? $remote->{userid} : undef, $ip, $uniq, $extra);
|
||
|
return undef if $u->err;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
# return or set the underage status userprop
|
||
|
sub underage_status {
|
||
|
return undef unless $LJ::UNDERAGE_BIT;
|
||
|
|
||
|
my $u = shift;
|
||
|
|
||
|
# return if they aren't setting it
|
||
|
unless (@_) {
|
||
|
LJ::load_user_props($u, 'underage_status');
|
||
|
return $u->{underage_status};
|
||
|
}
|
||
|
|
||
|
# set and return what it got set to
|
||
|
LJ::set_userprop($u, 'underage_status', shift());
|
||
|
return $u->{underage_status};
|
||
|
}
|
||
|
|
||
|
# returns a true value if user has a reserved 'ext' name.
|
||
|
sub external {
|
||
|
my $u = shift;
|
||
|
return $u->{user} =~ /^ext_/;
|
||
|
}
|
||
|
|
||
|
# this is for debugging/special uses where you need to instruct
|
||
|
# a user object on what database handle to use. returns the
|
||
|
# handle that you gave it.
|
||
|
sub set_dbcm {
|
||
|
my $u = shift;
|
||
|
return $u->{'_dbcm'} = shift;
|
||
|
}
|
||
|
|
||
|
sub begin_work {
|
||
|
my $u = shift;
|
||
|
return 1 unless $LJ::INNODB_DB{$u->{clusterid}};
|
||
|
|
||
|
my $dbcm = $u->{'_dbcm'} ||= LJ::get_cluster_master($u)
|
||
|
or croak "Database handle unavailable";
|
||
|
|
||
|
my $rv = $dbcm->begin_work;
|
||
|
if ($u->{_dberr} = $dbcm->err) {
|
||
|
$u->{_dberrstr} = $dbcm->errstr;
|
||
|
}
|
||
|
return $rv;
|
||
|
}
|
||
|
|
||
|
sub commit {
|
||
|
my $u = shift;
|
||
|
return 1 unless $LJ::INNODB_DB{$u->{clusterid}};
|
||
|
|
||
|
my $dbcm = $u->{'_dbcm'} ||= LJ::get_cluster_master($u)
|
||
|
or croak "Database handle unavailable";
|
||
|
|
||
|
my $rv = $dbcm->commit;
|
||
|
if ($u->{_dberr} = $dbcm->err) {
|
||
|
$u->{_dberrstr} = $dbcm->errstr;
|
||
|
}
|
||
|
return $rv;
|
||
|
}
|
||
|
|
||
|
sub rollback {
|
||
|
my $u = shift;
|
||
|
return 0 unless $LJ::INNODB_DB{$u->{clusterid}};
|
||
|
|
||
|
my $dbcm = $u->{'_dbcm'} ||= LJ::get_cluster_master($u)
|
||
|
or croak "Database handle unavailable";
|
||
|
|
||
|
my $rv = $dbcm->rollback;
|
||
|
if ($u->{_dberr} = $dbcm->err) {
|
||
|
$u->{_dberrstr} = $dbcm->errstr;
|
||
|
}
|
||
|
return $rv;
|
||
|
}
|
||
|
|
||
|
# get an $sth from the writer
|
||
|
sub prepare {
|
||
|
my $u = shift;
|
||
|
|
||
|
my $dbcm = $u->{'_dbcm'} ||= LJ::get_cluster_master($u)
|
||
|
or croak "Database handle unavailable";
|
||
|
|
||
|
my $rv = $dbcm->prepare(@_);
|
||
|
if ($u->{_dberr} = $dbcm->err) {
|
||
|
$u->{_dberrstr} = $dbcm->errstr;
|
||
|
}
|
||
|
return $rv;
|
||
|
}
|
||
|
|
||
|
# $u->do("UPDATE foo SET key=?", undef, $val);
|
||
|
sub do {
|
||
|
my $u = shift;
|
||
|
my $query = shift;
|
||
|
|
||
|
my $uid = $u->{userid}+0
|
||
|
or croak "Database update called on null user object";
|
||
|
|
||
|
my $dbcm = $u->{'_dbcm'} ||= LJ::get_cluster_master($u)
|
||
|
or croak "Database handle unavailable";
|
||
|
|
||
|
$query =~ s!^(\s*\w+\s+)!$1/* uid=$uid */ !;
|
||
|
|
||
|
my $rv = $dbcm->do($query, @_);
|
||
|
if ($u->{_dberr} = $dbcm->err) {
|
||
|
$u->{_dberrstr} = $dbcm->errstr;
|
||
|
}
|
||
|
|
||
|
$u->{_mysql_insertid} = $dbcm->{'mysql_insertid'} if $dbcm->{'mysql_insertid'};
|
||
|
|
||
|
return $rv;
|
||
|
}
|
||
|
|
||
|
sub selectrow_array {
|
||
|
my $u = shift;
|
||
|
my $dbcm = $u->{'_dbcm'} ||= LJ::get_cluster_master($u)
|
||
|
or croak "Database handle unavailable";
|
||
|
return $dbcm->selectrow_array(@_);
|
||
|
}
|
||
|
|
||
|
sub selectrow_hashref {
|
||
|
my $u = shift;
|
||
|
my $dbcm = $u->{'_dbcm'} ||= LJ::get_cluster_master($u)
|
||
|
or croak "Database handle unavailable";
|
||
|
return $dbcm->selectrow_hashref(@_);
|
||
|
}
|
||
|
|
||
|
sub err {
|
||
|
my $u = shift;
|
||
|
return $u->{_dberr};
|
||
|
}
|
||
|
|
||
|
sub errstr {
|
||
|
my $u = shift;
|
||
|
return $u->{_dberrstr};
|
||
|
}
|
||
|
|
||
|
sub quote {
|
||
|
my $u = shift;
|
||
|
my $text = shift;
|
||
|
|
||
|
my $dbcm = $u->{'_dbcm'} ||= LJ::get_cluster_master($u)
|
||
|
or croak "Database handle unavailable";
|
||
|
|
||
|
return $dbcm->quote($text);
|
||
|
}
|
||
|
|
||
|
sub mysql_insertid {
|
||
|
my $u = shift;
|
||
|
if ($u->isa("LJ::User")) {
|
||
|
return $u->{_mysql_insertid};
|
||
|
} elsif (LJ::isdb($u)) {
|
||
|
my $db = $u;
|
||
|
return $db->{'mysql_insertid'};
|
||
|
} else {
|
||
|
die "Unknown object '$u' being passed to LJ::User::mysql_insertid.";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::User::dudata_set
|
||
|
# class: logging
|
||
|
# des: Record or delete disk usage data for a journal
|
||
|
# args: u, area, areaid, bytes
|
||
|
# area: One character: "L" for log, "T" for talk, "B" for bio, "P" for pic.
|
||
|
# areaid: Unique ID within $area, or '0' if area has no ids (like bio)
|
||
|
# bytes: Number of bytes item takes up. Or 0 to delete record.
|
||
|
# returns: 1.
|
||
|
# </LJFUNC>
|
||
|
sub dudata_set {
|
||
|
my ($u, $area, $areaid, $bytes) = @_;
|
||
|
$bytes += 0; $areaid += 0;
|
||
|
if ($bytes) {
|
||
|
$u->do("REPLACE INTO dudata (userid, area, areaid, bytes) ".
|
||
|
"VALUES (?, ?, $areaid, $bytes)", undef,
|
||
|
$u->{userid}, $area);
|
||
|
} else {
|
||
|
$u->do("DELETE FROM dudata WHERE userid=? AND ".
|
||
|
"area=? AND areaid=$areaid", undef,
|
||
|
$u->{userid}, $area);
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
sub generate_session
|
||
|
{
|
||
|
my ($u, $opts) = @_;
|
||
|
my $udbh = LJ::get_cluster_master($u);
|
||
|
return undef unless $udbh;
|
||
|
|
||
|
# clean up any old, expired sessions they might have (lazy clean)
|
||
|
$u->do("DELETE FROM sessions WHERE userid=? AND timeexpire < UNIX_TIMESTAMP()",
|
||
|
undef, $u->{userid});
|
||
|
|
||
|
my $sess = {};
|
||
|
$opts->{'exptype'} = "short" unless $opts->{'exptype'} eq "long" ||
|
||
|
$opts->{'exptype'} eq "once";
|
||
|
$sess->{'auth'} = LJ::rand_chars(10);
|
||
|
my $expsec = $opts->{'expsec'}+0 || {
|
||
|
'short' => 60*60*24*1.5, # 36 hours
|
||
|
'long' => 60*60*24*60, # 60 days
|
||
|
'once' => 60*60*24*1.5, # same as short; just doesn't renew
|
||
|
}->{$opts->{'exptype'}};
|
||
|
my $id = LJ::alloc_user_counter($u, 'S');
|
||
|
return undef unless $id;
|
||
|
$u->do("REPLACE INTO sessions (userid, sessid, auth, exptype, ".
|
||
|
"timecreate, timeexpire, ipfixed) VALUES (?,?,?,?,UNIX_TIMESTAMP(),".
|
||
|
"UNIX_TIMESTAMP()+$expsec,?)", undef,
|
||
|
$u->{'userid'}, $id, $sess->{'auth'}, $opts->{'exptype'}, $opts->{'ipfixed'});
|
||
|
return undef if $u->err;
|
||
|
$sess->{'sessid'} = $id;
|
||
|
$sess->{'userid'} = $u->{'userid'};
|
||
|
$sess->{'ipfixed'} = $opts->{'ipfixed'};
|
||
|
$sess->{'exptype'} = $opts->{'exptype'};
|
||
|
|
||
|
# clean up old sessions
|
||
|
my $old = $udbh->selectcol_arrayref("SELECT sessid FROM sessions WHERE ".
|
||
|
"userid=$u->{'userid'} AND ".
|
||
|
"timeexpire < UNIX_TIMESTAMP()");
|
||
|
$u->kill_sessions(@$old) if $old;
|
||
|
|
||
|
# mark account as being used
|
||
|
LJ::mark_user_active($u, 'login');
|
||
|
|
||
|
return $sess;
|
||
|
}
|
||
|
|
||
|
sub make_login_session {
|
||
|
my ($u, $exptype, $ipfixed) = @_;
|
||
|
$exptype ||= 'short';
|
||
|
return 0 unless $u;
|
||
|
|
||
|
my $etime = 0;
|
||
|
eval { Apache->request->notes('ljuser' => $u->{'user'}); };
|
||
|
|
||
|
my $sess = $u->generate_session({
|
||
|
'exptype' => $exptype,
|
||
|
'ipfixed' => $ipfixed,
|
||
|
});
|
||
|
$BML::COOKIE{'ljsession'} = [ "ws:$u->{'user'}:$sess->{'sessid'}:$sess->{'auth'}", $etime, 1 ];
|
||
|
LJ::set_remote($u);
|
||
|
|
||
|
LJ::load_user_props($u, "browselang", "schemepref" );
|
||
|
my $bl = LJ::Lang::get_lang($u->{'browselang'});
|
||
|
if ($bl) {
|
||
|
BML::set_cookie("langpref", $bl->{'lncode'} . "/" . time(), 0, $LJ::COOKIE_PATH, $LJ::COOKIE_DOMAIN);
|
||
|
BML::set_language($bl->{'lncode'});
|
||
|
}
|
||
|
|
||
|
# restore default scheme
|
||
|
if ($u->{'schemepref'} ne "") {
|
||
|
BML::set_cookie("BMLschemepref", $u->{'schemepref'}, 0, $LJ::COOKIE_PATH, $LJ::COOKIE_DOMAIN);
|
||
|
BML::set_scheme($u->{'schemepref'});
|
||
|
}
|
||
|
|
||
|
LJ::run_hooks("post_login", {
|
||
|
"u" => $u,
|
||
|
"form" => {},
|
||
|
"expiretime" => $etime,
|
||
|
});
|
||
|
|
||
|
LJ::mark_user_active($u, 'login');
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
sub tosagree_set
|
||
|
{
|
||
|
my ($u, $err) = @_;
|
||
|
return undef unless $u;
|
||
|
|
||
|
unless (-f "$LJ::HOME/htdocs/inc/legal-tos") {
|
||
|
$$err = "TOS include file could not be found";
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
my $rev;
|
||
|
open (TOS, "$LJ::HOME/htdocs/inc/legal-tos");
|
||
|
while ((!$rev) && (my $line = <TOS>)) {
|
||
|
my $rcstag = "Revision";
|
||
|
if ($line =~ /\$$rcstag:\s*(\S+)\s*\$/) {
|
||
|
$rev = $1;
|
||
|
}
|
||
|
}
|
||
|
close TOS;
|
||
|
|
||
|
# if the required version of the tos is not available, error!
|
||
|
my $rev_req = $LJ::REQUIRED_TOS{rev};
|
||
|
if ($rev_req > 0 && $rev ne $rev_req) {
|
||
|
$$err = "Required Terms of Service revision is $rev_req, but system version is $rev.";
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
my $newval = join(', ', time(), $rev);
|
||
|
my $rv = LJ::set_userprop($u, "legal_tosagree", $newval);
|
||
|
|
||
|
# set in $u object for callers later
|
||
|
$u->{legal_tosagree} = $newval if $rv;
|
||
|
|
||
|
return $rv;
|
||
|
}
|
||
|
|
||
|
sub tosagree_verify {
|
||
|
my $u = shift;
|
||
|
|
||
|
return 1 unless $LJ::TOS_CHECK;
|
||
|
|
||
|
my $rev_req = $LJ::REQUIRED_TOS{rev};
|
||
|
return 1 unless $rev_req > 0;
|
||
|
|
||
|
LJ::load_user_props($u, 'legal_tosagree')
|
||
|
unless $u->{legal_tosagree};
|
||
|
|
||
|
my $rev_cur = (split(/\s*,\s*/, $u->{legal_tosagree}))[1];
|
||
|
|
||
|
return $rev_cur eq $rev_req;
|
||
|
}
|
||
|
|
||
|
sub kill_sessions {
|
||
|
my $u = shift;
|
||
|
my (@sessids) = @_;
|
||
|
my $in = join(',', map { $_+0 } @sessids);
|
||
|
return 1 unless $in;
|
||
|
my $userid = $u->{'userid'};
|
||
|
foreach (qw(sessions sessions_data)) {
|
||
|
$u->do("DELETE FROM $_ WHERE userid=? AND ".
|
||
|
"sessid IN ($in)", undef, $userid);
|
||
|
}
|
||
|
foreach my $id (@sessids) {
|
||
|
$id += 0;
|
||
|
my $memkey = [$userid,"sess:$userid:$id"];
|
||
|
LJ::MemCache::delete($memkey);
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
sub kill_all_sessions {
|
||
|
my $u = shift;
|
||
|
return 0 unless $u;
|
||
|
my $udbh = LJ::get_cluster_master($u);
|
||
|
my $sessions = $udbh->selectcol_arrayref("SELECT sessid FROM sessions WHERE ".
|
||
|
"userid=$u->{'userid'}");
|
||
|
$u->kill_sessions(@$sessions) if @$sessions;
|
||
|
|
||
|
# forget this user, if we knew they were logged in
|
||
|
delete $BML::COOKIE{'ljsession'};
|
||
|
LJ::set_remote(undef) if
|
||
|
$LJ::CACHE_REMOTE &&
|
||
|
$LJ::CACHE_REMOTE->{userid} == $u->{userid};
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
sub kill_session {
|
||
|
my $u = shift;
|
||
|
return 0 unless $u;
|
||
|
return 0 unless exists $u->{'_session'};
|
||
|
$u->kill_sessions($u->{'_session'}->{'sessid'});
|
||
|
|
||
|
# forget this user, if we knew they were logged in
|
||
|
delete $BML::COOKIE{'ljsession'};
|
||
|
LJ::set_remote(undef) if
|
||
|
$LJ::CACHE_REMOTE &&
|
||
|
$LJ::CACHE_REMOTE->{userid} == $u->{userid};
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::User::mogfs_userpic_key
|
||
|
# class: mogilefs
|
||
|
# des: Make a mogilefs key for the given pic for the user
|
||
|
# args: pic
|
||
|
# pic: Either the userpic hash or the picid of the userpic.
|
||
|
# returns: 1.
|
||
|
# </LJFUNC>
|
||
|
sub mogfs_userpic_key {
|
||
|
my $self = shift or return undef;
|
||
|
my $pic = shift or croak "missing required arg: userpic";
|
||
|
|
||
|
my $picid = ref $pic ? $pic->{picid} : $pic+0;
|
||
|
return "up:$self->{userid}:$picid";
|
||
|
}
|
||
|
|
||
|
# all reads/writes to talk2 must be done inside a lock, so there's
|
||
|
# no race conditions between reading from db and putting in memcache.
|
||
|
# can't do a db write in between those 2 steps. the talk2 -> memcache
|
||
|
# is elsewhere (talklib.pl), but this $dbh->do wrapper is provided
|
||
|
# here because non-talklib things modify the talk2 table, and it's
|
||
|
# nice to centralize the locking rules.
|
||
|
#
|
||
|
# return value is return of $dbh->do. $errref scalar ref is optional, and
|
||
|
# if set, gets value of $dbh->errstr
|
||
|
#
|
||
|
# write: (LJ::talk2_do)
|
||
|
# GET_LOCK
|
||
|
# update/insert into talk2
|
||
|
# RELEASE_LOCK
|
||
|
# delete memcache
|
||
|
#
|
||
|
# read: (LJ::Talk::get_talk_data)
|
||
|
# try memcache
|
||
|
# GET_LOCk
|
||
|
# read db
|
||
|
# update memcache
|
||
|
# RELEASE_LOCK
|
||
|
|
||
|
sub talk2_do {
|
||
|
my ($u, $nodetype, $nodeid, $errref, $sql, @args) = @_;
|
||
|
return undef unless $nodetype =~ /^\w$/;
|
||
|
return undef unless $nodeid =~ /^\d+$/;
|
||
|
return undef unless $u->writer;
|
||
|
|
||
|
my $dbcm = $u->{_dbcm};
|
||
|
|
||
|
my $memkey = [$u->{'userid'}, "talk2:$u->{'userid'}:$nodetype:$nodeid"];
|
||
|
my $lockkey = $memkey->[1];
|
||
|
|
||
|
$dbcm->selectrow_array("SELECT GET_LOCK(?,10)", undef, $lockkey);
|
||
|
my $ret = $u->do($sql, undef, @args);
|
||
|
$$errref = $u->errstr if ref $errref && $u->err;
|
||
|
$dbcm->selectrow_array("SELECT RELEASE_LOCK(?)", undef, $lockkey);
|
||
|
|
||
|
LJ::MemCache::delete($memkey, 0) if int($ret);
|
||
|
return $ret;
|
||
|
}
|
||
|
|
||
|
# log2_do
|
||
|
# see comments for talk2_do
|
||
|
|
||
|
sub log2_do {
|
||
|
my ($u, $errref, $sql, @args) = @_;
|
||
|
return undef unless $u->writer;
|
||
|
|
||
|
my $dbcm = $u->{_dbcm};
|
||
|
|
||
|
my $memkey = [$u->{'userid'}, "log2lt:$u->{'userid'}"];
|
||
|
my $lockkey = $memkey->[1];
|
||
|
|
||
|
$dbcm->selectrow_array("SELECT GET_LOCK(?,10)", undef, $lockkey);
|
||
|
my $ret = $u->do($sql, undef, @args);
|
||
|
$$errref = $u->errstr if ref $errref && $u->err;
|
||
|
$dbcm->selectrow_array("SELECT RELEASE_LOCK(?)", undef, $lockkey);
|
||
|
|
||
|
LJ::MemCache::delete($memkey, 0) if int($ret);
|
||
|
return $ret;
|
||
|
}
|
||
|
|
||
|
sub url {
|
||
|
my $u = shift;
|
||
|
LJ::load_user_props($u, "url");
|
||
|
if ($u->{'journaltype'} eq "I" && ! $u->{url}) {
|
||
|
my $id = $u->identity;
|
||
|
if ($id && $id->[0] eq "O") {
|
||
|
LJ::set_userprop($u, "url", $id->[1]) if $id->[1];
|
||
|
return $id->[1];
|
||
|
}
|
||
|
}
|
||
|
return $u->{url};
|
||
|
}
|
||
|
|
||
|
# returns arrayref of [idtype, identity]
|
||
|
sub identity {
|
||
|
my $u = shift;
|
||
|
return $u->{_identity} if $u->{_identity};
|
||
|
return undef unless $u->{'journaltype'} eq "I";
|
||
|
my $memkey = [$u->{userid}, "ident:$u->{userid}"];
|
||
|
my $ident = LJ::MemCache::get($memkey);
|
||
|
if ($ident) {
|
||
|
return $u->{_identity} = $ident;
|
||
|
}
|
||
|
|
||
|
my $dbh = LJ::get_db_writer();
|
||
|
$ident = $dbh->selectrow_arrayref("SELECT idtype, identity FROM identitymap ".
|
||
|
"WHERE userid=? LIMIT 1", undef, $u->{userid});
|
||
|
if ($ident) {
|
||
|
LJ::MemCache::set($memkey, $ident);
|
||
|
return $ident;
|
||
|
}
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
# returns a URL iff account is an OpenID identity. undef otherwise.
|
||
|
sub openid_identity {
|
||
|
my $u = shift;
|
||
|
my $ident = $u->identity;
|
||
|
return undef unless $ident && $ident->[0] == 0;
|
||
|
return $ident->[1];
|
||
|
}
|
||
|
|
||
|
# returns username or identity display name, not escaped
|
||
|
sub display_name {
|
||
|
my $u = shift;
|
||
|
return $u->{'user'} unless $u->{'journaltype'} eq "I";
|
||
|
|
||
|
my $id = $u->identity;
|
||
|
return "[ERR:unknown_identity]" unless $id;
|
||
|
|
||
|
my ($url, $name);
|
||
|
if ($id->[0] eq "O") {
|
||
|
require Net::OpenID::Consumer;
|
||
|
$url = $id->[1];
|
||
|
$name = Net::OpenID::VerifiedIdentity::DisplayOfURL($url, $LJ::IS_DEV_SERVER);
|
||
|
# FIXME: make a good out of this
|
||
|
$name =~ s/\[(live|dead)journal\.com/\[${1}journal/;
|
||
|
}
|
||
|
elsif ($id->[0] eq "G") {
|
||
|
if ($id->[1] =~ /^http\:\/\/.*\.(.*)journal.*\/(.*)$/) {
|
||
|
$name = $2 . "\@" . substr($1, 0, 1) . "j";
|
||
|
}
|
||
|
else {
|
||
|
$name = $id->[1];
|
||
|
}
|
||
|
}
|
||
|
return $name;
|
||
|
}
|
||
|
|
||
|
sub ljuser_display {
|
||
|
my $u = shift;
|
||
|
my $opts = shift;
|
||
|
|
||
|
return LJ::ljuser($u, $opts) unless $u->{'journaltype'} eq "I";
|
||
|
|
||
|
my $id = $u->identity;
|
||
|
return "<b>????</b>" unless $id;
|
||
|
|
||
|
my $andfull = $opts->{'full'} ? "&mode=full" : "";
|
||
|
my $img = $opts->{'imgroot'} || $LJ::IMGPREFIX;
|
||
|
my $strike = $opts->{'del'} ? ' text-decoration: line-through;' : '';
|
||
|
|
||
|
my ($url, $name);
|
||
|
|
||
|
if ($id->[0] eq "O") {
|
||
|
$url = $id->[1];
|
||
|
$name = $u->display_name;
|
||
|
|
||
|
my $openid_icon;
|
||
|
if ($url =~ /http:\/\/.+\.livejournal.com\/$/ ||
|
||
|
$url =~ /http:\/\/users.livejournal.com\/.+\/$/) {
|
||
|
$openid_icon = "userinfo-lj.gif";
|
||
|
}
|
||
|
else {
|
||
|
$openid_icon = "openid-profile.gif";
|
||
|
}
|
||
|
|
||
|
|
||
|
$url ||= "about:blank";
|
||
|
$name ||= "[no_name]";
|
||
|
|
||
|
$url = LJ::ehtml($url);
|
||
|
$name = LJ::ehtml($name);
|
||
|
|
||
|
return "<span class='ljuser' style='white-space: nowrap;'><a href='$LJ::SITEROOT/userinfo.bml?userid=$u->{userid}&t=I$andfull'><img src='$img/$openid_icon' alt='[info]' width='16' height='16' style='vertical-align: bottom; border: 0;' /></a><a href='$url' rel='nofollow'><b>$name</b></a></span>";
|
||
|
|
||
|
}
|
||
|
elsif ($id->[0] eq "G") {
|
||
|
$url = $id->[1];
|
||
|
$name = $u->display_name;
|
||
|
|
||
|
$url ||= "about:blank";
|
||
|
$name ||= "[no_name]";
|
||
|
|
||
|
$url = LJ::ehtml($url);
|
||
|
$name = LJ::ehtml($name);
|
||
|
|
||
|
return "<span class='ljuser' style='white-space: nowrap;'><a href='$LJ::SITEROOT/userinfo.bml?user=$u->{user}$andfull'><img src='$img/imported-profile.gif' alt='[info]' width='16' height='16' style='vertical-align: bottom; border: 0;' /></a><a href='$url' rel='nofollow'><b>$name</b></a></span>";
|
||
|
}
|
||
|
else {
|
||
|
return "<b>????</b>";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub load_identity_user {
|
||
|
my ($type, $ident, $vident) = @_;
|
||
|
|
||
|
my $dbh = LJ::get_db_writer();
|
||
|
my $uid = $dbh->selectrow_array("SELECT userid FROM identitymap WHERE idtype=? AND identity=?",
|
||
|
undef, $type, $ident);
|
||
|
return LJ::load_userid($uid) if $uid;
|
||
|
|
||
|
# increment ext_ counter until we successfully create an LJ
|
||
|
# account. hard cap it at 10 tries. (arbitrary, but we really
|
||
|
# shouldn't have *any* failures here, let alone 10 in a row)
|
||
|
|
||
|
for (1..10) {
|
||
|
my $extuser = 'ext_' . LJ::alloc_global_counter('E');
|
||
|
|
||
|
my $name = $extuser;
|
||
|
if ($type eq "O" && ref $vident) {
|
||
|
$name = $vident->display;
|
||
|
}
|
||
|
|
||
|
$uid = LJ::create_account({
|
||
|
caps => undef,
|
||
|
user => $extuser,
|
||
|
name => $name,
|
||
|
journaltype => 'I',
|
||
|
});
|
||
|
last if $uid;
|
||
|
select undef, undef, undef, .10; # lets not thrash over this
|
||
|
}
|
||
|
return undef unless $uid &&
|
||
|
$dbh->do("INSERT INTO identitymap (idtype, identity, userid) VALUES (?,?,?)",
|
||
|
undef, $type, $ident, $uid);
|
||
|
return LJ::load_userid($uid);
|
||
|
}
|
||
|
|
||
|
package LJ;
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::get_authas_list
|
||
|
# des: Get a list of usernames a given user can authenticate as
|
||
|
# returns: an array of usernames
|
||
|
# args: u, opts?
|
||
|
# des-opts: Optional hashref. keys are:
|
||
|
# - type: 'P' to only return users of journaltype 'P'
|
||
|
# - cap: cap to filter users on
|
||
|
# </LJFUNC>
|
||
|
sub get_authas_list {
|
||
|
my ($u, $opts) = @_;
|
||
|
|
||
|
# used to accept a user type, now accept an opts hash
|
||
|
$opts = { 'type' => $opts } unless ref $opts;
|
||
|
|
||
|
# only one valid type right now
|
||
|
$opts->{'type'} = 'P' if $opts->{'type'};
|
||
|
|
||
|
my $ids = LJ::load_rel_target($u, 'A');
|
||
|
return undef unless $ids;
|
||
|
|
||
|
# load_userids_multiple
|
||
|
my %users;
|
||
|
LJ::load_userids_multiple([ map { $_, \$users{$_} } @$ids ], [$u]);
|
||
|
|
||
|
return $u->{'user'}, sort map { $_->{'user'} }
|
||
|
grep { ! $opts->{'cap'} || LJ::get_cap($_, $opts->{'cap'}) }
|
||
|
grep { ! $opts->{'type'} || $opts->{'type'} eq $_->{'journaltype'} }
|
||
|
grep { $_->{clusterid} > 0 }
|
||
|
grep { $_->{statusvis} !~ /[XS]/ }
|
||
|
values %users;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::can_view
|
||
|
# des: Checks to see if the remote user can view a given journal entry.
|
||
|
# <b>Note:</b> This is meant for use on single entries at a time,
|
||
|
# not for calling many times on every entry in a journal.
|
||
|
# returns: boolean; 1 if remote user can see item
|
||
|
# args: remote, item
|
||
|
# des-item: Hashref from the 'log' table.
|
||
|
# </LJFUNC>
|
||
|
sub can_view
|
||
|
{
|
||
|
&nodb;
|
||
|
my $remote = shift;
|
||
|
my $item = shift;
|
||
|
|
||
|
# public is okay
|
||
|
return 1 if $item->{'security'} eq "public";
|
||
|
|
||
|
# must be logged in otherwise
|
||
|
return 0 unless $remote;
|
||
|
|
||
|
my $userid = int($item->{'ownerid'} || $item->{'journalid'});
|
||
|
my $remoteid = int($remote->{'userid'});
|
||
|
|
||
|
# owners can always see their own.
|
||
|
return 1 if ($userid == $remoteid);
|
||
|
|
||
|
# other people can't read private
|
||
|
return 0 if ($item->{'security'} eq "private");
|
||
|
|
||
|
# should be 'usemask' security from here out, otherwise
|
||
|
# assume it's something new and return 0
|
||
|
return 0 unless ($item->{'security'} eq "usemask");
|
||
|
|
||
|
# if it's usemask, we have to refuse non-personal journals,
|
||
|
# so we have to load the user
|
||
|
return 0 unless
|
||
|
$remote->{'journaltype'} eq 'P' ||
|
||
|
$remote->{'journaltype'} eq 'I';
|
||
|
|
||
|
# TAG:FR:ljlib:can_view (turn off bit 0 for just watching? hmm.)
|
||
|
my $gmask = LJ::get_groupmask($userid, $remoteid);
|
||
|
my $allowed = (int($gmask) & int($item->{'allowmask'}));
|
||
|
return $allowed ? 1 : 0; # no need to return matching mask
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::wipe_major_memcache
|
||
|
# des: invalidate all major memcache items associated with a given user
|
||
|
# args: u
|
||
|
# returns: nothing
|
||
|
# </LJFUNC>
|
||
|
sub wipe_major_memcache
|
||
|
{
|
||
|
my $u = shift;
|
||
|
my $userid = LJ::want_userid($u);
|
||
|
foreach my $key ("userid","bio","talk2ct","talkleftct","log2ct",
|
||
|
"log2lt","memkwid","dayct","s1overr","s1uc","fgrp",
|
||
|
"friends","friendofs","tu","upicinf","upiccom",
|
||
|
"upicurl", "intids", "memct", "lastcomm")
|
||
|
{
|
||
|
LJ::memcache_kill($userid, $key);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::load_user_props
|
||
|
# des: Given a user hashref, loads the values of the given named properties
|
||
|
# into that user hashref.
|
||
|
# args: dbarg?, u, opts?, propname*
|
||
|
# des-opts: hashref of opts. set key 'cache' to use memcache.
|
||
|
# des-propname: the name of a property from the userproplist table.
|
||
|
# </LJFUNC>
|
||
|
sub load_user_props
|
||
|
{
|
||
|
&nodb;
|
||
|
|
||
|
my $u = shift;
|
||
|
return unless isu($u);
|
||
|
return if $u->{'statusvis'} eq "X";
|
||
|
|
||
|
my $opts = ref $_[0] ? shift : {};
|
||
|
my (@props) = @_;
|
||
|
|
||
|
my ($sql, $sth);
|
||
|
LJ::load_props("user");
|
||
|
|
||
|
## user reference
|
||
|
my $uid = $u->{'userid'}+0;
|
||
|
$uid = LJ::get_userid($u->{'user'}) unless $uid;
|
||
|
|
||
|
my $mem = {};
|
||
|
my $use_master = 0;
|
||
|
my $used_slave = 0; # set later if we ended up using a slave
|
||
|
|
||
|
if (@LJ::MEMCACHE_SERVERS) {
|
||
|
my @keys;
|
||
|
foreach (@props) {
|
||
|
next if exists $u->{$_};
|
||
|
my $p = LJ::get_prop("user", $_);
|
||
|
die "Invalid userprop $_ passed to LJ::load_user_props." unless $p;
|
||
|
push @keys, [$uid,"uprop:$uid:$p->{'id'}"];
|
||
|
}
|
||
|
$mem = LJ::MemCache::get_multi(@keys) || {};
|
||
|
$use_master = 1;
|
||
|
}
|
||
|
|
||
|
$use_master = 1 if $opts->{'use_master'};
|
||
|
|
||
|
my @needwrite; # [propid, propname] entries we need to save to memcache later
|
||
|
|
||
|
my %loadfrom;
|
||
|
my %multihomed; # ( $propid => 0/1 ) # 0 if we haven't loaded it, 1 if we have
|
||
|
unless (@props) {
|
||
|
# case 1: load all props for a given user.
|
||
|
# multihomed props are stored on userprop and userproplite2, but since they
|
||
|
# should always be in sync, it doesn't matter which gets loaded first, the
|
||
|
# net results should be the same. see doc/designnotes/multihomed_props.txt
|
||
|
# for more information.
|
||
|
$loadfrom{'userprop'} = 1;
|
||
|
$loadfrom{'userproplite'} = 1;
|
||
|
$loadfrom{'userproplite2'} = 1;
|
||
|
$loadfrom{'userpropblob'} = 1;
|
||
|
} else {
|
||
|
# case 2: load only certain things
|
||
|
foreach (@props) {
|
||
|
next if exists $u->{$_};
|
||
|
my $p = LJ::get_prop("user", $_);
|
||
|
die "Invalid userprop $_ passed to LJ::load_user_props." unless $p;
|
||
|
if (defined $mem->{"uprop:$uid:$p->{'id'}"}) {
|
||
|
$u->{$_} = $mem->{"uprop:$uid:$p->{'id'}"};
|
||
|
next;
|
||
|
}
|
||
|
push @needwrite, [ $p->{'id'}, $_ ];
|
||
|
my $source = $p->{'indexed'} ? "userprop" : "userproplite";
|
||
|
if ($p->{datatype} eq 'blobchar') {
|
||
|
$source = "userpropblob"; # clustered blob
|
||
|
}
|
||
|
elsif ($p->{'cldversion'} && $u->{'dversion'} >= $p->{'cldversion'}) {
|
||
|
$source = "userproplite2"; # clustered
|
||
|
}
|
||
|
elsif ($p->{multihomed}) {
|
||
|
$multihomed{$p->{id}} = 0;
|
||
|
$source = "userproplite2";
|
||
|
}
|
||
|
push @{$loadfrom{$source}}, $p->{'id'};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach my $table (qw{userproplite userproplite2 userpropblob userprop}) {
|
||
|
next unless exists $loadfrom{$table};
|
||
|
my $db;
|
||
|
if ($use_master) {
|
||
|
$db = ($table =~ m{userprop(lite2|blob)}) ?
|
||
|
LJ::get_cluster_master($u) :
|
||
|
LJ::get_db_writer();
|
||
|
}
|
||
|
unless ($db) {
|
||
|
$db = ($table =~ m{userprop(lite2|blob)}) ?
|
||
|
LJ::get_cluster_reader($u) :
|
||
|
LJ::get_db_reader();
|
||
|
$used_slave = 1;
|
||
|
}
|
||
|
$sql = "SELECT upropid, value FROM $table WHERE userid=$uid";
|
||
|
if (ref $loadfrom{$table}) {
|
||
|
$sql .= " AND upropid IN (" . join(",", @{$loadfrom{$table}}) . ")";
|
||
|
}
|
||
|
$sth = $db->prepare($sql);
|
||
|
$sth->execute;
|
||
|
while (my ($id, $v) = $sth->fetchrow_array) {
|
||
|
delete $multihomed{$id} if $table eq 'userproplite2';
|
||
|
$u->{$LJ::CACHE_PROPID{'user'}->{$id}->{'name'}} = $v;
|
||
|
}
|
||
|
|
||
|
# push back multihomed if necessary
|
||
|
if ($table eq 'userproplite2') {
|
||
|
push @{$loadfrom{userprop}}, $_ foreach keys %multihomed;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# see if we failed to get anything above and need to hit the master.
|
||
|
# this usually happens the first time a multihomed prop is hit. this
|
||
|
# code will propogate that prop down to the cluster.
|
||
|
if (%multihomed) {
|
||
|
|
||
|
# verify that we got the database handle before we try propogating data
|
||
|
if ($u->writer) {
|
||
|
my @values;
|
||
|
foreach my $id (keys %multihomed) {
|
||
|
my $pname = $LJ::CACHE_PROPID{user}{$id}{name};
|
||
|
if (defined $u->{$pname} && $u->{$pname}) {
|
||
|
push @values, "($uid, $id, " . $u->quote($u->{$pname}) . ")";
|
||
|
} else {
|
||
|
push @values, "($uid, $id, '')";
|
||
|
}
|
||
|
}
|
||
|
$u->do("REPLACE INTO userproplite2 VALUES " . join ',', @values);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Add defaults to user object.
|
||
|
|
||
|
# defaults for S1 style IDs in config file are magic: really
|
||
|
# uniq strings representing style IDs, so on first use, we need
|
||
|
# to map them
|
||
|
unless ($LJ::CACHED_S1IDMAP) {
|
||
|
|
||
|
my $pubsty = LJ::S1::get_public_styles();
|
||
|
foreach (values %$pubsty) {
|
||
|
my $k = "s1_$_->{'type'}_style";
|
||
|
next unless $LJ::USERPROP_DEF{$k} eq "$_->{'type'}/$_->{'styledes'}";
|
||
|
|
||
|
$LJ::USERPROP_DEF{$k} = $_->{'styleid'};
|
||
|
}
|
||
|
|
||
|
$LJ::CACHED_S1IDMAP = 1;
|
||
|
}
|
||
|
|
||
|
# If this was called with no @props, then the function tried
|
||
|
# to load all metadata. but we don't know what's missing, so
|
||
|
# try to apply all defaults.
|
||
|
unless (@props) { @props = keys %LJ::USERPROP_DEF; }
|
||
|
|
||
|
foreach my $prop (@props) {
|
||
|
next if (defined $u->{$prop});
|
||
|
$u->{$prop} = $LJ::USERPROP_DEF{$prop};
|
||
|
}
|
||
|
|
||
|
unless ($used_slave) {
|
||
|
foreach my $wr (@needwrite) {
|
||
|
my ($id, $name) = ($wr->[0], $wr->[1]);
|
||
|
LJ::MemCache::set([$uid,"uprop:$uid:$id"], $u->{$name} || "");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# simple interface to LJ::load_userids_multiple. takes userids,
|
||
|
# returns hashref with keys ids, values $u refs.
|
||
|
sub load_userids
|
||
|
{
|
||
|
my %u;
|
||
|
LJ::load_userids_multiple([ map { $_ => \$u{$_} } @_ ]);
|
||
|
return \%u;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::load_userids_multiple
|
||
|
# des: Loads a number of users at once, efficiently.
|
||
|
# info: loads a few users at once, their userids given in the keys of $map
|
||
|
# listref (not hashref: can't have dups). values of $map listref are
|
||
|
# scalar refs to put result in. $have is an optional listref of user
|
||
|
# object caller already has, but is too lazy to sort by themselves.
|
||
|
# args: dbarg?, map, have, memcache_only?
|
||
|
# des-map: Arrayref of pairs (userid, destination scalarref)
|
||
|
# des-have: Arrayref of user objects caller already has
|
||
|
# des-memcache_only: Flag to only retrieve data from memcache
|
||
|
# returns: Nothing.
|
||
|
# </LJFUNC>
|
||
|
sub load_userids_multiple
|
||
|
{
|
||
|
&nodb;
|
||
|
my ($map, $have, $memcache_only) = @_;
|
||
|
|
||
|
my $sth;
|
||
|
|
||
|
my %need;
|
||
|
while (@$map) {
|
||
|
my $id = shift @$map;
|
||
|
my $ref = shift @$map;
|
||
|
next unless int($id);
|
||
|
push @{$need{$id}}, $ref;
|
||
|
|
||
|
if ($LJ::REQ_CACHE_USER_ID{$id}) {
|
||
|
push @{$have}, $LJ::REQ_CACHE_USER_ID{$id};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
my $satisfy = sub {
|
||
|
my $u = shift;
|
||
|
next unless ref $u eq "LJ::User";
|
||
|
foreach (@{$need{$u->{'userid'}}}) {
|
||
|
$$_ = $u;
|
||
|
}
|
||
|
$LJ::REQ_CACHE_USER_NAME{$u->{'user'}} = $u;
|
||
|
$LJ::REQ_CACHE_USER_ID{$u->{'userid'}} = $u;
|
||
|
delete $need{$u->{'userid'}};
|
||
|
};
|
||
|
|
||
|
if ($have) {
|
||
|
foreach my $u (@$have) {
|
||
|
$satisfy->($u);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (keys %need) {
|
||
|
foreach (LJ::memcache_get_u(map { [$_,"userid:$_"] } keys %need)) {
|
||
|
$satisfy->($_);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (keys %need && ! $memcache_only) {
|
||
|
my $db = @LJ::MEMCACHE_SERVERS ? LJ::get_db_writer() : LJ::get_db_reader();
|
||
|
_load_user_raw($db, "userid", [ keys %need ], sub {
|
||
|
my $u = shift;
|
||
|
LJ::memcache_set_u($u);
|
||
|
$satisfy->($u);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# des-db: $dbh/$dbr
|
||
|
# des-key: either "userid" or "user" (the WHERE part)
|
||
|
# des-vals: value or arrayref of values for key to match on
|
||
|
# des-hoook: optional code ref to run for each $u
|
||
|
# returns: last $u found
|
||
|
sub _load_user_raw
|
||
|
{
|
||
|
my ($db, $key, $vals, $hook) = @_;
|
||
|
$hook ||= sub {};
|
||
|
$vals = [ $vals ] unless ref $vals eq "ARRAY";
|
||
|
|
||
|
if (!$db) {
|
||
|
warn("_load_user_raw: database is not available");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
my $use_isam;
|
||
|
unless ($LJ::CACHE_NO_ISAM{user} || scalar(@$vals) > 10) {
|
||
|
eval { $db->do("HANDLER user OPEN"); };
|
||
|
if ($@ || $db->err) {
|
||
|
$LJ::CACHE_NO_ISAM{user} = 1;
|
||
|
} else {
|
||
|
$use_isam = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
my $last;
|
||
|
|
||
|
if ($use_isam) {
|
||
|
$key = "PRIMARY" if $key eq "userid";
|
||
|
foreach my $v (@$vals) {
|
||
|
my $sth = $db->prepare("HANDLER user READ `$key` = (?) LIMIT 1");
|
||
|
$sth->execute($v);
|
||
|
my $u = $sth->fetchrow_hashref;
|
||
|
if ($u) {
|
||
|
bless $u, 'LJ::User';
|
||
|
$hook->($u);
|
||
|
$last = $u;
|
||
|
}
|
||
|
}
|
||
|
$db->do("HANDLER user close");
|
||
|
} else {
|
||
|
my $in = join(", ", map { $db->quote($_) } @$vals);
|
||
|
my $sth = $db->prepare("SELECT * FROM user WHERE $key IN ($in)");
|
||
|
$sth->execute;
|
||
|
while (my $u = $sth->fetchrow_hashref) {
|
||
|
bless $u, 'LJ::User';
|
||
|
$hook->($u);
|
||
|
$last = $u;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $last;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::load_user
|
||
|
# des: Loads a user record given a username.
|
||
|
# info: From the [dbarg[user]] table.
|
||
|
# args: dbarg?, user, force?
|
||
|
# des-user: Username of user to load.
|
||
|
# des-force: if set to true, won't return cached user object and will
|
||
|
# query a dbh
|
||
|
# returns: Hashref with keys being columns of [dbtable[user]] table.
|
||
|
# </LJFUNC>
|
||
|
sub load_user
|
||
|
{
|
||
|
&nodb;
|
||
|
my ($user, $force) = @_;
|
||
|
|
||
|
$user = LJ::canonical_username($user);
|
||
|
return undef unless length $user;
|
||
|
|
||
|
my $set_req_cache = sub {
|
||
|
my $u = shift;
|
||
|
$LJ::REQ_CACHE_USER_NAME{$u->{'user'}} = $u;
|
||
|
$LJ::REQ_CACHE_USER_ID{$u->{'userid'}} = $u;
|
||
|
return $u;
|
||
|
};
|
||
|
|
||
|
my $get_user = sub {
|
||
|
my $use_dbh = shift;
|
||
|
my $db = $use_dbh ? LJ::get_db_writer() : LJ::get_db_reader();
|
||
|
my $u = _load_user_raw($db, "user", $user);
|
||
|
return $u unless $u && $use_dbh;
|
||
|
|
||
|
# set caches since we got a u from the master
|
||
|
LJ::memcache_set_u($u);
|
||
|
return $set_req_cache->($u);
|
||
|
};
|
||
|
|
||
|
# caller is forcing a master, return now
|
||
|
return $get_user->("master") if $force;
|
||
|
|
||
|
my $u;
|
||
|
|
||
|
# return process cache if we have one
|
||
|
$u = $LJ::REQ_CACHE_USER_NAME{$user};
|
||
|
return $u if $u;
|
||
|
|
||
|
# check memcache
|
||
|
{
|
||
|
my $uid = LJ::MemCache::get("uidof:$user");
|
||
|
$u = LJ::memcache_get_u([$uid, "userid:$uid"]) if $uid;
|
||
|
return $set_req_cache->($u) if $u;
|
||
|
}
|
||
|
|
||
|
# try to load from master if using memcache, otherwise from slave
|
||
|
$u = $get_user->(scalar @LJ::MEMCACHE_SERVERS);
|
||
|
return $u if $u;
|
||
|
|
||
|
# setup LDAP handler if this is the first time
|
||
|
if ($LJ::LDAP_HOST && ! $LJ::AUTH_EXISTS) {
|
||
|
require LJ::LDAP;
|
||
|
$LJ::AUTH_EXISTS = sub {
|
||
|
my $user = shift;
|
||
|
my $rec = LJ::LDAP::load_ldap_user($user);
|
||
|
return $rec ? $rec : undef;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
# if user doesn't exist in the LJ database, it's possible we're using
|
||
|
# an external authentication source and we should create the account
|
||
|
# implicitly.
|
||
|
my $lu;
|
||
|
if (ref $LJ::AUTH_EXISTS eq "CODE" && ($lu = $LJ::AUTH_EXISTS->($user)))
|
||
|
{
|
||
|
my $name = ref $lu eq "HASH" ? ($lu->{'nick'} || $lu->{name} || $user) : $user;
|
||
|
if (LJ::create_account({
|
||
|
'user' => $user,
|
||
|
'name' => $name,
|
||
|
'email' => ref $lu eq "HASH" ? $lu->{email} : "",
|
||
|
'password' => "",
|
||
|
}))
|
||
|
{
|
||
|
# this should pull from the master, since it was _just_ created
|
||
|
return $get_user->("master");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::u_equals
|
||
|
# des: Compares two user objects to see if they're the same user.
|
||
|
# args: userobj1, userobj2
|
||
|
# des-userobj1: First user to compare.
|
||
|
# des-userobj2: Second user to compare.
|
||
|
# returns: Boolean, true if userobj1 and userobj2 are defined and have equal userids.
|
||
|
# </LJFUNC>
|
||
|
sub u_equals {
|
||
|
my ($u1, $u2) = @_;
|
||
|
return $u1 && $u2 && $u1->{'userid'} == $u2->{'userid'};
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::load_userid
|
||
|
# des: Loads a user record given a userid.
|
||
|
# info: From the [dbarg[user]] table.
|
||
|
# args: dbarg?, userid, force?
|
||
|
# des-userid: Userid of user to load.
|
||
|
# des-force: if set to true, won't return cached user object and will
|
||
|
# query a dbh
|
||
|
# returns: Hashref with keys being columns of [dbtable[user]] table.
|
||
|
# </LJFUNC>
|
||
|
sub load_userid
|
||
|
{
|
||
|
&nodb;
|
||
|
my ($userid, $force) = @_;
|
||
|
return undef unless $userid;
|
||
|
|
||
|
my $set_req_cache = sub {
|
||
|
my $u = shift;
|
||
|
$LJ::REQ_CACHE_USER_NAME{$u->{'user'}} = $u;
|
||
|
$LJ::REQ_CACHE_USER_ID{$u->{'userid'}} = $u;
|
||
|
return $u;
|
||
|
};
|
||
|
|
||
|
my $get_user = sub {
|
||
|
my $use_dbh = shift;
|
||
|
my $db = $use_dbh ? LJ::get_db_writer() : LJ::get_db_reader();
|
||
|
my $u = _load_user_raw($db, "userid", $userid);
|
||
|
return $u unless $u && $use_dbh;
|
||
|
|
||
|
# set caches since we got a u from the master
|
||
|
LJ::memcache_set_u($u);
|
||
|
return $set_req_cache->($u);
|
||
|
};
|
||
|
|
||
|
# user is forcing master, return now
|
||
|
return $get_user->("master") if $force;
|
||
|
|
||
|
my $u;
|
||
|
|
||
|
# check process cache
|
||
|
$u = $LJ::REQ_CACHE_USER_ID{$userid};
|
||
|
return $u if $u;
|
||
|
|
||
|
# check memcache
|
||
|
$u = LJ::memcache_get_u([$userid,"userid:$userid"]);
|
||
|
return $set_req_cache->($u) if $u;
|
||
|
|
||
|
# get from master if using memcache
|
||
|
return $get_user->("master") if @LJ::MEMCACHE_SERVERS;
|
||
|
|
||
|
# check slave
|
||
|
$u = $get_user->();
|
||
|
return $u if $u;
|
||
|
|
||
|
# if we didn't get a u from the reader, fall back to master
|
||
|
return $get_user->("master");
|
||
|
}
|
||
|
|
||
|
sub memcache_get_u
|
||
|
{
|
||
|
my @keys = @_;
|
||
|
my @ret;
|
||
|
foreach my $ar (values %{LJ::MemCache::get_multi(@keys) || {}}) {
|
||
|
my $u = LJ::MemCache::array_to_hash("user", $ar);
|
||
|
if ($u) {
|
||
|
bless $u, 'LJ::User';
|
||
|
push @ret, $u;
|
||
|
}
|
||
|
}
|
||
|
return wantarray ? @ret : $ret[0];
|
||
|
}
|
||
|
|
||
|
sub memcache_set_u
|
||
|
{
|
||
|
my $u = shift;
|
||
|
return unless $u;
|
||
|
my $ar = LJ::MemCache::hash_to_array("user", $u);
|
||
|
return unless $ar;
|
||
|
LJ::MemCache::set([$u->{'userid'}, "userid:$u->{'userid'}"], $ar);
|
||
|
LJ::MemCache::set("uidof:$u->{user}", $u->{userid});
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::get_bio
|
||
|
# des: gets a user bio, from db or memcache
|
||
|
# args: u, force
|
||
|
# des-force: true to get data from cluster master
|
||
|
# returns: string
|
||
|
# </LJFUNC>
|
||
|
sub get_bio {
|
||
|
my ($u, $force) = @_;
|
||
|
return unless $u && $u->{'has_bio'} eq "Y";
|
||
|
|
||
|
my $bio;
|
||
|
|
||
|
my $memkey = [$u->{'userid'}, "bio:$u->{'userid'}"];
|
||
|
unless ($force) {
|
||
|
my $bio = LJ::MemCache::get($memkey);
|
||
|
return $bio if defined $bio;
|
||
|
}
|
||
|
|
||
|
# not in memcache, fall back to disk
|
||
|
my $db = @LJ::MEMCACHE_SERVERS || $force ?
|
||
|
LJ::get_cluster_def_reader($u) : LJ::get_cluster_reader($u);
|
||
|
$bio = $db->selectrow_array("SELECT bio FROM userbio WHERE userid=?",
|
||
|
undef, $u->{'userid'});
|
||
|
|
||
|
# set in memcache
|
||
|
LJ::MemCache::add($memkey, $bio);
|
||
|
|
||
|
return $bio;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::journal_base
|
||
|
# des: Returns URL of a user's journal.
|
||
|
# info: The tricky thing is that users with underscores in their usernames
|
||
|
# can't have some_user.site.com as a hostname, so that's changed into
|
||
|
# some-user.site.com.
|
||
|
# args: uuser, vhost?
|
||
|
# des-uuser: User hashref or username of user whose URL to make.
|
||
|
# des-vhost: What type of URL. Acceptable options are "users", to make a
|
||
|
# http://user.site.com/ URL; "tilde" to make http://site.com/~user/;
|
||
|
# "community" for http://site.com/community/user; or the default
|
||
|
# will be http://site.com/users/user. If unspecifed and uuser
|
||
|
# is a user hashref, then the best/preferred vhost will be chosen.
|
||
|
# returns: scalar; a URL.
|
||
|
# </LJFUNC>
|
||
|
sub journal_base
|
||
|
{
|
||
|
my ($user, $vhost) = @_;
|
||
|
if (isu($user)) {
|
||
|
my $u = $user;
|
||
|
$user = $u->{'user'};
|
||
|
unless (defined $vhost) {
|
||
|
if (($LJ::FRONTPAGE_JOURNAL) && $LJ::FRONTPAGE_JOURNAL eq $user) {
|
||
|
$vhost = "front";
|
||
|
} elsif ($u->{'journaltype'} eq "P") {
|
||
|
$vhost = "";
|
||
|
} elsif ($u->{'journaltype'} eq "C") {
|
||
|
$vhost = "community";
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
if ($vhost eq "users") {
|
||
|
my $he_user = $user;
|
||
|
$he_user =~ s/_/-/g;
|
||
|
return "http://$he_user.$LJ::USER_DOMAIN";
|
||
|
} elsif ($vhost eq "tilde") {
|
||
|
return "$LJ::SITEROOT/~$user";
|
||
|
} elsif ($vhost eq "community") {
|
||
|
return "$LJ::SITEROOT/community/$user";
|
||
|
} elsif ($vhost eq "front") {
|
||
|
return $LJ::SITEROOT;
|
||
|
} elsif ($vhost =~ /^other:(.+)/) {
|
||
|
return "http://$1";
|
||
|
} else {
|
||
|
return "$LJ::SITEROOT/users/$user";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
# loads all of the given privs for a given user into a hashref
|
||
|
# inside the user record ($u->{_privs}->{$priv}->{$arg} = 1)
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::load_user_privs
|
||
|
# class:
|
||
|
# des:
|
||
|
# info:
|
||
|
# args:
|
||
|
# des-:
|
||
|
# returns:
|
||
|
# </LJFUNC>
|
||
|
sub load_user_privs
|
||
|
{
|
||
|
&nodb;
|
||
|
my $remote = shift;
|
||
|
my @privs = @_;
|
||
|
return unless $remote and @privs;
|
||
|
|
||
|
# return if we've already loaded these privs for this user.
|
||
|
@privs = grep { ! $remote->{'_privloaded'}->{$_} } @privs;
|
||
|
return unless @privs;
|
||
|
|
||
|
my $dbr = LJ::get_db_reader();
|
||
|
return unless $dbr;
|
||
|
foreach (@privs) { $remote->{'_privloaded'}->{$_}++; }
|
||
|
@privs = map { $dbr->quote($_) } @privs;
|
||
|
my $sth = $dbr->prepare("SELECT pl.privcode, pm.arg ".
|
||
|
"FROM priv_map pm, priv_list pl ".
|
||
|
"WHERE pm.prlid=pl.prlid AND ".
|
||
|
"pl.privcode IN (" . join(',',@privs) . ") ".
|
||
|
"AND pm.userid=$remote->{'userid'}");
|
||
|
$sth->execute;
|
||
|
while (my ($priv, $arg) = $sth->fetchrow_array) {
|
||
|
unless (defined $arg) { $arg = ""; } # NULL -> ""
|
||
|
$remote->{'_priv'}->{$priv}->{$arg} = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::check_priv
|
||
|
# des: Check to see if a user has a certain privilege.
|
||
|
# info: Usually this is used to check the privs of a $remote user.
|
||
|
# See [func[LJ::get_remote]]. As such, a $u argument of undef
|
||
|
# is okay to pass: 0 will be returned, as an unknown user can't
|
||
|
# have any rights.
|
||
|
# args: dbarg?, u, priv, arg?
|
||
|
# des-priv: Priv name to check for (see [dbtable[priv_list]])
|
||
|
# des-arg: Optional argument. If defined, function only returns true
|
||
|
# when $remote has a priv of type $priv also with arg $arg, not
|
||
|
# just any priv of type $priv, which is the behavior without
|
||
|
# an $arg
|
||
|
# returns: boolean; true if user has privilege
|
||
|
# </LJFUNC>
|
||
|
sub check_priv
|
||
|
{
|
||
|
&nodb;
|
||
|
my ($u, $priv, $arg) = @_;
|
||
|
return 0 unless $u;
|
||
|
|
||
|
if (! $u->{'_privloaded'}->{$priv}) {
|
||
|
LJ::load_user_privs($u, $priv);
|
||
|
}
|
||
|
|
||
|
if (defined $arg) {
|
||
|
return (defined $u->{'_priv'}->{$priv} &&
|
||
|
defined $u->{'_priv'}->{$priv}->{$arg});
|
||
|
} else {
|
||
|
return (defined $u->{'_priv'}->{$priv});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#
|
||
|
#
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::remote_has_priv
|
||
|
# class:
|
||
|
# des: Check to see if the given remote user has a certain priviledge
|
||
|
# info: DEPRECATED. should use load_user_privs + check_priv
|
||
|
# args:
|
||
|
# des-:
|
||
|
# returns:
|
||
|
# </LJFUNC>
|
||
|
sub remote_has_priv
|
||
|
{
|
||
|
&nodb;
|
||
|
my $remote = shift;
|
||
|
my $privcode = shift; # required. priv code to check for.
|
||
|
my $ref = shift; # optional, arrayref or hashref to populate
|
||
|
return 0 unless ($remote);
|
||
|
|
||
|
### authentication done. time to authorize...
|
||
|
|
||
|
my $dbr = LJ::get_db_reader();
|
||
|
my $sth = $dbr->prepare("SELECT pm.arg FROM priv_map pm, priv_list pl WHERE pm.prlid=pl.prlid AND pl.privcode=? AND pm.userid=?");
|
||
|
$sth->execute($privcode, $remote->{'userid'});
|
||
|
|
||
|
my $match = 0;
|
||
|
if (ref $ref eq "ARRAY") { @$ref = (); }
|
||
|
if (ref $ref eq "HASH") { %$ref = (); }
|
||
|
while (my ($arg) = $sth->fetchrow_array) {
|
||
|
$match++;
|
||
|
if (ref $ref eq "ARRAY") { push @$ref, $arg; }
|
||
|
if (ref $ref eq "HASH") { $ref->{$arg} = 1; }
|
||
|
}
|
||
|
return $match;
|
||
|
}
|
||
|
|
||
|
# $dom: 'L' == log, 'T' == talk, 'M' == modlog, 'S' == session,
|
||
|
# 'R' == memory (remembrance), 'K' == keyword id,
|
||
|
# 'P' == phone post, 'C' == pending comment
|
||
|
sub alloc_user_counter
|
||
|
{
|
||
|
my ($u, $dom, $recurse) = @_;
|
||
|
|
||
|
##################################################################
|
||
|
# IF YOU UPDATE THIS MAKE SURE YOU ADD INITIALIZATION CODE BELOW #
|
||
|
return undef unless $dom =~ /^[LTMPSRKCD]$/; #
|
||
|
##################################################################
|
||
|
|
||
|
my $dbh = LJ::get_db_writer();
|
||
|
return undef unless $dbh;
|
||
|
|
||
|
my $newmax;
|
||
|
my $uid = $u->{'userid'}+0;
|
||
|
return undef unless $uid;
|
||
|
my $memkey = [$uid, "auc:$uid:$dom"];
|
||
|
|
||
|
# in a master-master DB cluster we need to be careful that in
|
||
|
# an automatic failover case where one cluster is slightly behind
|
||
|
# that the same counter ID isn't handed out twice. use memcache
|
||
|
# as a sanity check to record/check latest number handed out.
|
||
|
my $memmax = int(LJ::MemCache::get($memkey) || 0);
|
||
|
|
||
|
my $rs = $dbh->do("UPDATE usercounter SET max=LAST_INSERT_ID(GREATEST(max,$memmax)+1) ".
|
||
|
"WHERE journalid=? AND area=?", undef, $uid, $dom);
|
||
|
if ($rs > 0) {
|
||
|
$newmax = $dbh->selectrow_array("SELECT LAST_INSERT_ID()");
|
||
|
LJ::MemCache::set($memkey, $newmax);
|
||
|
return $newmax;
|
||
|
}
|
||
|
|
||
|
if ($recurse) {
|
||
|
# We shouldn't ever get here if all is right with the world.
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
my $qry_map = {
|
||
|
# for entries:
|
||
|
'log' => "SELECT MAX(jitemid) FROM log2 WHERE journalid=?",
|
||
|
'logtext' => "SELECT MAX(jitemid) FROM logtext2 WHERE journalid=?",
|
||
|
'talk_nodeid' => "SELECT MAX(nodeid) FROM talk2 WHERE nodetype='L' AND journalid=?",
|
||
|
# for comments:
|
||
|
'talk' => "SELECT MAX(jtalkid) FROM talk2 WHERE journalid=?",
|
||
|
'talktext' => "SELECT MAX(jtalkid) FROM talktext2 WHERE journalid=?",
|
||
|
};
|
||
|
|
||
|
my $consider = sub {
|
||
|
my @tables = @_;
|
||
|
foreach my $t (@tables) {
|
||
|
my $res = $u->selectrow_array($qry_map->{$t}, undef, $uid);
|
||
|
$newmax = $res if ($res) && $res > $newmax;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
# Make sure the counter table is populated for this uid/dom.
|
||
|
if ($dom eq "L") {
|
||
|
# back in the ol' days IDs were reused (because of MyISAM)
|
||
|
# so now we're extra careful not to reuse a number that has
|
||
|
# foreign junk "attached". turns out people like to delete
|
||
|
# each entry by hand, but we do lazy deletes that are often
|
||
|
# too lazy and a user can see old stuff come back alive
|
||
|
$consider->("log", "logtext", "talk_nodeid");
|
||
|
} elsif ($dom eq "T") {
|
||
|
# just paranoia, not as bad as above. don't think we've ever
|
||
|
# run into cases of talktext without a talk, but who knows.
|
||
|
# can't hurt.
|
||
|
$consider->("talk", "talktext");
|
||
|
} elsif ($dom eq "M") {
|
||
|
$newmax = $u->selectrow_array("SELECT MAX(modid) FROM modlog WHERE journalid=?",
|
||
|
undef, $uid);
|
||
|
} elsif ($dom eq "S") {
|
||
|
$newmax = $u->selectrow_array("SELECT MAX(sessid) FROM sessions WHERE userid=?",
|
||
|
undef, $uid);
|
||
|
} elsif ($dom eq "R") {
|
||
|
$newmax = $u->selectrow_array("SELECT MAX(memid) FROM memorable2 WHERE userid=?",
|
||
|
undef, $uid);
|
||
|
} elsif ($dom eq "K") {
|
||
|
$newmax = $u->selectrow_array("SELECT MAX(kwid) FROM userkeywords WHERE userid=?",
|
||
|
undef, $uid);
|
||
|
} elsif ($dom eq "P") {
|
||
|
my $userblobmax = $u->selectrow_array("SELECT MAX(blobid) FROM userblob WHERE journalid=? AND domain=?",
|
||
|
undef, $uid, LJ::get_blob_domainid("phonepost"));
|
||
|
my $ppemax = $u->selectrow_array("SELECT MAX(blobid) FROM phonepostentry WHERE userid=?",
|
||
|
undef, $uid);
|
||
|
$newmax = ($ppemax > $userblobmax) ? $ppemax : $userblobmax;
|
||
|
} elsif ($dom eq "C") {
|
||
|
my $commentmax = $u->selectrow_array("SELECT MAX(pendid) FROM pendcomments WHERE jid=?",
|
||
|
undef, $uid);
|
||
|
} elsif ($dom eq "D") {
|
||
|
$newmax = $u->selectrow_array("SELECT MAX(moduleid) FROM embedcontent WHERE userid=?",
|
||
|
undef, $uid);
|
||
|
} else {
|
||
|
die "No user counter initializer defined for area '$dom'.\n";
|
||
|
}
|
||
|
$newmax += 0;
|
||
|
$dbh->do("INSERT IGNORE INTO usercounter (journalid, area, max) VALUES (?,?,?)",
|
||
|
undef, $uid, $dom, $newmax) or return undef;
|
||
|
|
||
|
# The 2nd invocation of the alloc_user_counter sub should do the
|
||
|
# intended incrementing.
|
||
|
return LJ::alloc_user_counter($u, $dom, 1);
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::make_user_active
|
||
|
# des: Record user activity per cluster to
|
||
|
# make per-activity cluster stats easier.
|
||
|
# args: userobj, type
|
||
|
# arg-userid: source userobj ref
|
||
|
# arg-type: currently unused
|
||
|
# </LJFUNC>
|
||
|
sub mark_user_active {
|
||
|
my ($u, $type) = @_; # not currently using type
|
||
|
return 0 unless $u; # do not auto-vivify $u
|
||
|
my $uid = $u->{userid};
|
||
|
return 0 unless $uid && $u->{clusterid};
|
||
|
|
||
|
# Update the clustertrack table, but not if we've done it for this
|
||
|
# user in the last hour. if no memcache servers are configured
|
||
|
# we don't do the optimization and just always log the activity info
|
||
|
if (@LJ::MEMCACHE_SERVERS == 0 ||
|
||
|
LJ::MemCache::add("rate:tracked:$uid", 1, 3600)) {
|
||
|
|
||
|
return 0 unless $u->writer;
|
||
|
$u->do("REPLACE INTO clustertrack2 SET ".
|
||
|
"userid=?, timeactive=?, clusterid=?", undef,
|
||
|
$uid, time(), $u->{clusterid}) or return 0;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::infohistory_add
|
||
|
# des: Add a line of text to the infohistory table for an account.
|
||
|
# args: uuid, what, value, other?
|
||
|
# des-uuid: User id or user object to insert infohistory for.
|
||
|
# des-what: What type of history being inserted (15 chars max).
|
||
|
# des-value: Value for the item (255 chars max).
|
||
|
# des-other: Extra information (30 chars max).
|
||
|
# returns: 1 on success, 0 on error.
|
||
|
# </LJFUNC>
|
||
|
sub infohistory_add {
|
||
|
my ($uuid, $what, $value, $other) = @_;
|
||
|
$uuid = LJ::want_userid($uuid);
|
||
|
return unless $uuid && $what && $value;
|
||
|
|
||
|
# get writer and insert
|
||
|
my $dbh = LJ::get_db_writer();
|
||
|
$dbh->do("INSERT INTO infohistory (userid, what, timechange, oldvalue, other) VALUES (?, ?, NOW(), ?, ?)",
|
||
|
undef, $uuid, $what, $value, $other);
|
||
|
return $dbh->err ? 0 : 1;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::set_userprop
|
||
|
# des: Sets/deletes a userprop by name for a user.
|
||
|
# info: This adds or deletes from the
|
||
|
# [dbtable[userprop]]/[dbtable[userproplite]] tables. One
|
||
|
# crappy thing about this interface is that it doesn't allow
|
||
|
# a batch of userprops to be updated at once, which is the
|
||
|
# common thing to do.
|
||
|
# args: dbarg?, uuserid, propname, value, memonly?
|
||
|
# des-uuserid: The userid of the user or a user hashref.
|
||
|
# des-propname: The name of the property. Or a hashref of propname keys and corresponding values.
|
||
|
# des-value: The value to set to the property. If undefined or the
|
||
|
# empty string, then property is deleted.
|
||
|
# des-memonly: if true, only writes to memcache, and not to database.
|
||
|
# </LJFUNC>
|
||
|
sub set_userprop
|
||
|
{
|
||
|
&nodb;
|
||
|
|
||
|
my ($u, $propname, $value, $memonly) = @_;
|
||
|
$u = ref $u ? $u : LJ::load_userid($u);
|
||
|
my $userid = $u->{'userid'}+0;
|
||
|
|
||
|
my $hash = ref $propname eq "HASH" ? $propname : { $propname => $value };
|
||
|
|
||
|
my %action; # $table -> {"replace"|"delete"} -> [ "($userid, $propid, $qvalue)" | propid ]
|
||
|
my %multihomed; # { $propid => $value }
|
||
|
|
||
|
foreach $propname (keys %$hash) {
|
||
|
my $p = LJ::get_prop("user", $propname) or
|
||
|
die "Invalid userprop $propname passed to LJ::set_userprop.";
|
||
|
if ($p->{multihomed}) {
|
||
|
# collect into array for later handling
|
||
|
$multihomed{$p->{id}} = $hash->{$propname};
|
||
|
next;
|
||
|
}
|
||
|
my $table = $p->{'indexed'} ? "userprop" : "userproplite";
|
||
|
if ($p->{datatype} eq 'blobchar') {
|
||
|
$table = 'userpropblob';
|
||
|
}
|
||
|
elsif ($p->{'cldversion'} && $u->{'dversion'} >= $p->{'cldversion'}) {
|
||
|
$table = "userproplite2";
|
||
|
}
|
||
|
unless ($memonly) {
|
||
|
my $db = $action{$table}->{'db'} ||= (
|
||
|
$table !~ m{userprop(lite2|blob)}
|
||
|
? LJ::get_db_writer()
|
||
|
: $u->writer );
|
||
|
return 0 unless $db;
|
||
|
}
|
||
|
$value = $hash->{$propname};
|
||
|
if (defined $value && $value) {
|
||
|
push @{$action{$table}->{"replace"}}, [ $p->{'id'}, $value ];
|
||
|
} else {
|
||
|
push @{$action{$table}->{"delete"}}, $p->{'id'};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach my $table (keys %action) {
|
||
|
my $db = $action{$table}->{'db'};
|
||
|
if (my $list = $action{$table}->{"replace"}) {
|
||
|
if ($db) {
|
||
|
my $vals = join(',', map { "($userid,$_->[0]," . $db->quote($_->[1]) . ")" } @$list);
|
||
|
$db->do("REPLACE INTO $table (userid, upropid, value) VALUES $vals");
|
||
|
}
|
||
|
LJ::MemCache::set([$userid,"uprop:$userid:$_->[0]"], $_->[1]) foreach (@$list);
|
||
|
}
|
||
|
if (my $list = $action{$table}->{"delete"}) {
|
||
|
if ($db) {
|
||
|
my $in = join(',', @$list);
|
||
|
$db->do("DELETE FROM $table WHERE userid=$userid AND upropid IN ($in)");
|
||
|
}
|
||
|
LJ::MemCache::set([$userid,"uprop:$userid:$_"], "") foreach (@$list);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# if we had any multihomed props, set them here
|
||
|
if (%multihomed) {
|
||
|
my $dbh = LJ::get_db_writer();
|
||
|
return 0 unless $dbh && $u->writer;
|
||
|
while (my ($propid, $pvalue) = each %multihomed) {
|
||
|
if (defined $pvalue && $pvalue) {
|
||
|
# replace data into master
|
||
|
$dbh->do("REPLACE INTO userprop VALUES (?, ?, ?)",
|
||
|
undef, $userid, $propid, $pvalue);
|
||
|
} else {
|
||
|
# delete data from master, but keep in cluster
|
||
|
$dbh->do("DELETE FROM userprop WHERE userid = ? AND upropid = ?",
|
||
|
undef, $userid, $propid);
|
||
|
}
|
||
|
|
||
|
# fail out?
|
||
|
return 0 if $dbh->err;
|
||
|
|
||
|
# put data in cluster
|
||
|
$pvalue ||= '';
|
||
|
$u->do("REPLACE INTO userproplite2 VALUES (?, ?, ?)",
|
||
|
undef, $userid, $propid, $pvalue);
|
||
|
return 0 if $u->err;
|
||
|
|
||
|
# set memcache
|
||
|
LJ::MemCache::set([$userid,"uprop:$userid:$propid"], $pvalue);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::get_shared_journals
|
||
|
# des: Gets an array of shared journals a user has access to.
|
||
|
# returns: An array of shared journals.
|
||
|
# args: u
|
||
|
# </LJFUNC>
|
||
|
sub get_shared_journals
|
||
|
{
|
||
|
my $u = shift;
|
||
|
my $ids = LJ::load_rel_target($u, 'A') || [];
|
||
|
|
||
|
# have to get usernames;
|
||
|
my %users;
|
||
|
LJ::load_userids_multiple([ map { $_, \$users{$_} } @$ids ], [$u]);
|
||
|
return sort map { $_->{'user'} } values %users;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# class: component
|
||
|
# name: LJ::ljuser
|
||
|
# des: Make link to userinfo/journal of user.
|
||
|
# info: Returns the HTML for a userinfo/journal link pair for a given user
|
||
|
# name, just like LJUSER does in BML. This is for files like cleanhtml.pl
|
||
|
# and ljpoll.pl which need this functionality too, but they aren't run as BML.
|
||
|
# args: user, opts?
|
||
|
# des-user: Username to link to, or user hashref.
|
||
|
# des-opts: Optional hashref to control output. Key 'full' when true causes
|
||
|
# a link to the mode=full userinfo. Key 'type' when 'C' makes
|
||
|
# a community link, when 'Y' makes a syndicated account link,
|
||
|
# when 'N' makes a news account link, otherwise makes a user account
|
||
|
# link. If user parameter is a hashref, its 'journaltype' overrides
|
||
|
# this 'type'. Key 'del', when true, makes a tag for a deleted user.
|
||
|
# If user parameter is a hashref, its 'statusvis' overrides 'del'.
|
||
|
# Key 'no_follow', when true, disables traversal of renamed users.
|
||
|
# Key 'side' for LJ-based server control
|
||
|
# returns: HTML with a little head image & bold text link.
|
||
|
# </LJFUNC>
|
||
|
sub ljuser
|
||
|
{
|
||
|
my $user = shift;
|
||
|
my $opts = shift;
|
||
|
my $u;
|
||
|
my $native=1;
|
||
|
|
||
|
if ($opts->{'side'}){
|
||
|
if ($opts->{'side'} eq 'LJR') {$native=1; $opts->{'side'}="lj.rossia.org"}
|
||
|
elsif ($opts->{'side'} eq 'lj.rossia.org') {$native=1;}
|
||
|
elsif ($opts->{'side'} eq 'LJCOM') { $opts->{'side'}="www.livejournal.com"; $native=0;}
|
||
|
elsif ($opts->{'side'} eq 'LJ') { $opts->{'side'}="www.livejournal.com"; $native=0;}
|
||
|
else {$native=0;}
|
||
|
}
|
||
|
if ($native){
|
||
|
my $do_dynamic = $LJ::DYNAMIC_LJUSER || ($user =~ /^ext_/);
|
||
|
if ($do_dynamic && ! isu($user) && ! $opts->{'type'}) {
|
||
|
# Try to automatically pick the user type, but still
|
||
|
# make something if we can't (user doesn't exist?)
|
||
|
$user = LJ::load_user($user) || $user;
|
||
|
|
||
|
my $hops = 0;
|
||
|
|
||
|
# Traverse the renames to the final journal
|
||
|
while (ref $user and $user->{'journaltype'} eq 'R'
|
||
|
and ! $opts->{'no_follow'} && $hops++ < 5) {
|
||
|
|
||
|
LJ::load_user_props($user, 'renamedto');
|
||
|
last unless length $user->{'renamedto'};
|
||
|
$user = LJ::load_user($user->{'renamedto'});
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (isu($user)) {
|
||
|
$u = $user;
|
||
|
$opts->{'type'} = $user->{'journaltype'};
|
||
|
# Mark accounts as deleted that aren't visible, memorial, or locked
|
||
|
$opts->{'del'} = $user->{'statusvis'} ne 'V' &&
|
||
|
$user->{'statusvis'} ne 'M' &&
|
||
|
$user->{'statusvis'} ne 'L';
|
||
|
$user = $user->{'user'};
|
||
|
}
|
||
|
my $andfull = $opts->{'full'} ? "&mode=full" : "";
|
||
|
my $img = $opts->{'imgroot'} || $LJ::IMGPREFIX;
|
||
|
my $strike = $opts->{'del'} ? ' text-decoration: line-through;' : '';
|
||
|
my $make_tag = sub {
|
||
|
my ($fil, $dir, $x, $y) = @_;
|
||
|
$y ||= $x; # make square if only one dimension given
|
||
|
|
||
|
return "<span class='ljuser' style='white-space: nowrap;$strike'><a href='$LJ::SITEROOT/userinfo.bml?user=$user$andfull'><img src='$img/$fil' alt='[info]' width='$x' height='$y' style='vertical-align: bottom; border: 0;' /></a><a href='$LJ::SITEROOT/$dir/$user/'><b>$user</b></a></span>";
|
||
|
};
|
||
|
$u = LJ::load_user($user);
|
||
|
$opts->{'type'} = $u->{'journaltype'};
|
||
|
|
||
|
if ($opts->{'type'} eq 'C') {
|
||
|
return $make_tag->('community.gif', 'community', 16);
|
||
|
} elsif ($opts->{'type'} eq 'Y') {
|
||
|
return $make_tag->('syndicated.gif', 'users', 16);
|
||
|
} elsif ($opts->{'type'} eq 'N') {
|
||
|
return $make_tag->('newsinfo.gif', 'users', 16);
|
||
|
} elsif ($opts->{'type'} eq 'I') {
|
||
|
return $u->ljuser_display($opts);
|
||
|
} else {
|
||
|
return $make_tag->('userinfo.gif', 'users', 17);
|
||
|
}
|
||
|
} else {
|
||
|
#we have non-native side
|
||
|
my $andfull = $opts->{'full'} ? "&mode=full" : "";
|
||
|
my $img = $opts->{'imgroot'} || $LJ::SITEROOT."/".$LJ::IMGPREFIX;
|
||
|
if ($opts->{'side'} eq "www.livejournal.com") {$img="http://stat.livejournal.com/img/"}
|
||
|
my $strike = $opts->{'del'} ? ' text-decoration: line-through;' : '';
|
||
|
my $make_tag = sub {
|
||
|
my ($s, $fil, $dir, $x, $y) = @_;
|
||
|
$y ||= $x; # make square if only one dimension given
|
||
|
|
||
|
return "<span class='ljuser' style='white-space: nowrap;$strike'><a href='http://$s/userinfo.bml?user=$user$andfull'><img src='$img/$fil' alt='[info]' width='$x' height='$y' style='vertical-align: bottom; border: 0;' /></a><a href='http://$s/$dir/$user/'><b>$user</b></a></span>";
|
||
|
};
|
||
|
|
||
|
if ($opts->{'type'} eq 'C') {
|
||
|
return $make_tag->( $opts->{'side'}, 'community.gif', 'community', 16);
|
||
|
} elsif ($opts->{'type'} eq 'Y') {
|
||
|
return $make_tag->( $opts->{'side'}, 'syndicated.gif', 'users', 16);
|
||
|
} elsif ($opts->{'type'} eq 'N') {
|
||
|
return $make_tag->( $opts->{'side'}, 'newsinfo.gif', 'users', 16);
|
||
|
} elsif ($opts->{'type'} eq 'I') {
|
||
|
return $u->ljuser_display($opts);
|
||
|
} else {
|
||
|
return $make_tag->( $opts->{'side'}, 'userinfo.gif', 'users', 17);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub update_user
|
||
|
{
|
||
|
my ($arg, $ref) = @_;
|
||
|
my @uid;
|
||
|
|
||
|
if (ref $arg eq "ARRAY") {
|
||
|
@uid = @$arg;
|
||
|
} else {
|
||
|
@uid = want_userid($arg);
|
||
|
}
|
||
|
@uid = grep { $_ } map { $_ + 0 } @uid;
|
||
|
return 0 unless @uid;
|
||
|
|
||
|
my @sets;
|
||
|
my @bindparams;
|
||
|
while (my ($k, $v) = each %$ref) {
|
||
|
if ($k eq "raw") {
|
||
|
push @sets, $v;
|
||
|
} else {
|
||
|
push @sets, "$k=?";
|
||
|
push @bindparams, $v;
|
||
|
}
|
||
|
}
|
||
|
return 1 unless @sets;
|
||
|
my $dbh = LJ::get_db_writer();
|
||
|
return 0 unless $dbh;
|
||
|
{
|
||
|
local $" = ",";
|
||
|
my $where = @uid == 1 ? "userid=$uid[0]" : "userid IN (@uid)";
|
||
|
$dbh->do("UPDATE user SET @sets WHERE $where", undef,
|
||
|
@bindparams);
|
||
|
return 0 if $dbh->err;
|
||
|
}
|
||
|
if (@LJ::MEMCACHE_SERVERS) {
|
||
|
LJ::memcache_kill($_, "userid") foreach @uid;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::get_timezone
|
||
|
# des: Gets the timezone offset for the user.
|
||
|
# args: u, offsetref, fakedref
|
||
|
# des-u: user object.
|
||
|
# des-offsetref: reference to scalar to hold timezone offset;
|
||
|
# des-fakedref: reference to scalar to hold whether this timezone was
|
||
|
# faked. 0 if it is the timezone specified by the user (not supported yet).
|
||
|
# returns: nonzero if successful.
|
||
|
# </LJFUNC>
|
||
|
sub get_timezone {
|
||
|
my ($u, $offsetref, $fakedref) = @_;
|
||
|
|
||
|
# we currently don't support timezones,
|
||
|
# but when we do this will be the function to modify.
|
||
|
|
||
|
my $offset;
|
||
|
|
||
|
my $dbcr = LJ::get_cluster_def_reader($u);
|
||
|
return 0 unless $dbcr;
|
||
|
|
||
|
# we guess their current timezone's offset
|
||
|
# by comparing the gmtime of their last post
|
||
|
# with the time they specified on that post.
|
||
|
|
||
|
# grab the times on the last post.
|
||
|
if (my $last_row = $dbcr->selectrow_hashref(
|
||
|
"SELECT rlogtime, eventtime ".
|
||
|
"FROM log2 WHERE journalid=? ".
|
||
|
"ORDER BY rlogtime LIMIT 1",
|
||
|
undef, $u->{userid})) {
|
||
|
my $logtime = $LJ::EndOfTime - $last_row->{'rlogtime'};
|
||
|
my $eventtime = LJ::mysqldate_to_time($last_row->{'eventtime'}, 1);
|
||
|
my $hourdiff = ($eventtime - $logtime) / 3600;
|
||
|
|
||
|
# if they're up to a quarter hour behind, round up.
|
||
|
$$offsetref = $hourdiff > 0 ? int($hourdiff + 0.25) : int($hourdiff - 0.25);
|
||
|
}
|
||
|
|
||
|
# until we store real timezones, the timezone is always faked.
|
||
|
$$fakedref = 1 if $fakedref;
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
# returns undef on error, or otherwise arrayref of arrayrefs,
|
||
|
# each of format [ year, month, day, count ] for all days with
|
||
|
# non-zero count. examples:
|
||
|
# [ [ 2003, 6, 5, 3 ], [ 2003, 6, 8, 4 ], ... ]
|
||
|
#
|
||
|
sub get_daycounts
|
||
|
{
|
||
|
my ($u, $remote, $not_memcache) = @_;
|
||
|
# NOTE: $remote not yet used. one of the oldest LJ shortcomings is that
|
||
|
# it's public how many entries users have per-day, even if the entries
|
||
|
# are protected. we'll be fixing that with a new table, but first
|
||
|
# we're moving everything to this API.
|
||
|
|
||
|
my $uid = LJ::want_userid($u) or return undef;
|
||
|
|
||
|
my @days;
|
||
|
my $memkey = [$uid,"dayct:$uid"];
|
||
|
unless ($not_memcache) {
|
||
|
my $list = LJ::MemCache::get($memkey);
|
||
|
return $list if $list;
|
||
|
}
|
||
|
|
||
|
my $dbcr = LJ::get_cluster_def_reader($u) or return undef;
|
||
|
my $sth = $dbcr->prepare("SELECT year, month, day, COUNT(*) ".
|
||
|
"FROM log2 WHERE journalid=? GROUP BY 1, 2, 3");
|
||
|
$sth->execute($uid);
|
||
|
while (my ($y, $m, $d, $c) = $sth->fetchrow_array) {
|
||
|
# we force each number from string scalars (from DBI) to int scalars,
|
||
|
# so they store smaller in memcache
|
||
|
push @days, [ int($y), int($m), int($d), int($c) ];
|
||
|
}
|
||
|
LJ::MemCache::add($memkey, \@days);
|
||
|
return \@days;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::modify_caps
|
||
|
# des: Given a list of caps to add and caps to remove, updates a user's caps
|
||
|
# args: uuid, cap_add, cap_del, res
|
||
|
# arg-cap_add: arrayref of bit numbers to turn on
|
||
|
# arg-cap_del: arrayref of bit numbers to turn off
|
||
|
# arg-res: hashref returned from 'modify_caps' hook
|
||
|
# returns: updated u object, retrieved from $dbh, then 'caps' key modified
|
||
|
# otherwise, returns 0 unless all hooks run properly
|
||
|
# </LJFUNC>
|
||
|
sub modify_caps {
|
||
|
my ($argu, $cap_add, $cap_del, $res) = @_;
|
||
|
my $userid = LJ::want_userid($argu);
|
||
|
return undef unless $userid;
|
||
|
|
||
|
$cap_add ||= [];
|
||
|
$cap_del ||= [];
|
||
|
my %cap_add_mod = ();
|
||
|
my %cap_del_mod = ();
|
||
|
|
||
|
# convert capnames to bit numbers
|
||
|
if (LJ::are_hooks("get_cap_bit")) {
|
||
|
foreach my $bit (@$cap_add, @$cap_del) {
|
||
|
next if $bit =~ /^\d+$/;
|
||
|
|
||
|
# bit is a magical reference into the array
|
||
|
$bit = LJ::run_hook("get_cap_bit", $bit);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# get a u object directly from the db
|
||
|
my $u = LJ::load_userid($userid, "force");
|
||
|
|
||
|
# add new caps
|
||
|
my $newcaps = int($u->{'caps'});
|
||
|
foreach (@$cap_add) {
|
||
|
my $cap = 1 << $_;
|
||
|
|
||
|
# about to turn bit on, is currently off?
|
||
|
$cap_add_mod{$_} = 1 unless $newcaps & $cap;
|
||
|
$newcaps |= $cap;
|
||
|
}
|
||
|
|
||
|
# remove deleted caps
|
||
|
foreach (@$cap_del) {
|
||
|
my $cap = 1 << $_;
|
||
|
|
||
|
# about to turn bit off, is it currently on?
|
||
|
$cap_del_mod{$_} = 1 if $newcaps & $cap;
|
||
|
$newcaps &= ~$cap;
|
||
|
}
|
||
|
|
||
|
# run hooks for modified bits
|
||
|
if (LJ::are_hooks("modify_caps")) {
|
||
|
$res = LJ::run_hook("modify_caps",
|
||
|
{ 'u' => $u,
|
||
|
'newcaps' => $newcaps,
|
||
|
'oldcaps' => $u->{'caps'},
|
||
|
'cap_on_req' => { map { $_ => 1 } @$cap_add },
|
||
|
'cap_off_req' => { map { $_ => 1 } @$cap_del },
|
||
|
'cap_on_mod' => \%cap_add_mod,
|
||
|
'cap_off_mod' => \%cap_del_mod,
|
||
|
});
|
||
|
|
||
|
# hook should return a status code
|
||
|
return undef unless defined $res;
|
||
|
}
|
||
|
|
||
|
# update user row
|
||
|
LJ::update_user($u, { 'caps' => $newcaps });
|
||
|
|
||
|
return $u;
|
||
|
}
|
||
|
|
||
|
# returns 1 if action is permitted. 0 if above rate or fail.
|
||
|
# action isn't logged on fail.
|
||
|
#
|
||
|
# opts keys:
|
||
|
# -- "limit_by_ip" => "1.2.3.4" (when used for checking rate)
|
||
|
# --
|
||
|
sub rate_log
|
||
|
{
|
||
|
my ($u, $ratename, $count, $opts) = @_;
|
||
|
my $rateperiod = LJ::get_cap($u, "rateperiod-$ratename");
|
||
|
return 1 unless $rateperiod;
|
||
|
|
||
|
return 0 unless $u->writer;
|
||
|
|
||
|
my $rp = LJ::get_prop("rate", $ratename);
|
||
|
return 0 unless $rp;
|
||
|
|
||
|
my $now = time();
|
||
|
my $beforeperiod = $now - $rateperiod;
|
||
|
|
||
|
# delete inapplicable stuff (or some of it)
|
||
|
$u->do("DELETE FROM ratelog WHERE userid=$u->{'userid'} AND rlid=$rp->{'id'} ".
|
||
|
"AND evttime < $beforeperiod LIMIT 1000");
|
||
|
|
||
|
# check rate. (okay per period)
|
||
|
my $opp = LJ::get_cap($u, "rateallowed-$ratename");
|
||
|
return 1 unless $opp;
|
||
|
my $udbr = LJ::get_cluster_reader($u);
|
||
|
my $ip = $udbr->quote($opts->{'limit_by_ip'} || "0.0.0.0");
|
||
|
my $sum = $udbr->selectrow_array("SELECT COUNT(quantity) FROM ratelog WHERE ".
|
||
|
"userid=$u->{'userid'} AND rlid=$rp->{'id'} ".
|
||
|
"AND ip=INET_ATON($ip) ".
|
||
|
"AND evttime > $beforeperiod");
|
||
|
|
||
|
# would this transaction go over the limit?
|
||
|
if ($sum + $count > $opp) {
|
||
|
# TODO: optionally log to rateabuse, unless caller is doing it themselves
|
||
|
# somehow, like with the "loginstall" table.
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
# log current
|
||
|
$count = $count + 0;
|
||
|
$u->do("INSERT INTO ratelog (userid, rlid, evttime, ip, quantity) VALUES ".
|
||
|
"($u->{'userid'}, $rp->{'id'}, $now, INET_ATON($ip), $count)");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
sub login_ip_banned
|
||
|
{
|
||
|
my $u = shift;
|
||
|
return 0 unless $u;
|
||
|
|
||
|
my $ip;
|
||
|
return 0 unless ($ip = LJ::get_remote_ip());
|
||
|
|
||
|
my $udbr;
|
||
|
my $rateperiod = LJ::get_cap($u, "rateperiod-failed_login");
|
||
|
if ($rateperiod && ($udbr = LJ::get_cluster_reader($u))) {
|
||
|
my $bantime = $udbr->selectrow_array("SELECT time FROM loginstall WHERE ".
|
||
|
"userid=$u->{'userid'} AND ip=INET_ATON(?)",
|
||
|
undef, $ip);
|
||
|
if ($bantime && $bantime > time() - $rateperiod) {
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
sub handle_bad_login
|
||
|
{
|
||
|
my $u = shift;
|
||
|
return 1 unless $u;
|
||
|
|
||
|
my $ip;
|
||
|
return 1 unless ($ip = LJ::get_remote_ip());
|
||
|
# an IP address is permitted such a rate of failures
|
||
|
# until it's banned for a period of time.
|
||
|
my $udbh;
|
||
|
if (! LJ::rate_log($u, "failed_login", 1, { 'limit_by_ip' => $ip }) &&
|
||
|
($udbh = LJ::get_cluster_master($u)))
|
||
|
{
|
||
|
$udbh->do("REPLACE INTO loginstall (userid, ip, time) VALUES ".
|
||
|
"(?,INET_ATON(?),UNIX_TIMESTAMP())", undef, $u->{'userid'}, $ip);
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::_friends_do
|
||
|
# des: Runs given sql, then deletes the given userid's friends from memcache
|
||
|
# args: uuserid, sql, args
|
||
|
# des-uuserid: a userid or u object
|
||
|
# des-sql: sql to run via $dbh->do()
|
||
|
# des-args: a list of arguments to pass use via: $dbh->do($sql, undef, @args)
|
||
|
# returns: return false on error
|
||
|
# </LJFUNC>
|
||
|
sub _friends_do {
|
||
|
my ($uuid, $sql, @args) = @_;
|
||
|
my $uid = want_userid($uuid);
|
||
|
return undef unless $uid && $sql;
|
||
|
|
||
|
my $dbh = LJ::get_db_writer() or return 0;
|
||
|
|
||
|
my $ret = $dbh->do($sql, undef, @args);
|
||
|
return 0 if $dbh->err;
|
||
|
|
||
|
LJ::memcache_kill($uid, "friends");
|
||
|
|
||
|
# pass $uuid in case it's a $u object which mark_dirty wants
|
||
|
LJ::mark_dirty($uuid, "friends");
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::add_friend
|
||
|
# des: Simple interface to add a friend edge.
|
||
|
# args: uuid, to_add, opts?
|
||
|
# des-to_add: a single uuid or an arrayref of uuids to add (befriendees)
|
||
|
# des-opts: hashref; 'defaultview' key means add target uuids to $uuid's Default View friends group
|
||
|
# returns: boolean; 1 on success (or already friend), 0 on failure (bogus args)
|
||
|
# </LJFUNC>
|
||
|
sub add_friend
|
||
|
{
|
||
|
&nodb;
|
||
|
my ($userid, $to_add, $opts) = @_;
|
||
|
|
||
|
$userid = LJ::want_userid($userid);
|
||
|
return 0 unless $userid;
|
||
|
|
||
|
my @add_ids = ref $to_add eq 'ARRAY' ? map { LJ::want_userid($_) } @$to_add : ( LJ::want_userid($to_add) );
|
||
|
return 0 unless @add_ids;
|
||
|
|
||
|
my $dbh = LJ::get_db_writer();
|
||
|
|
||
|
my $black = LJ::color_todb("#000000");
|
||
|
my $white = LJ::color_todb("#ffffff");
|
||
|
|
||
|
my $groupmask = 1;
|
||
|
if ($opts->{'defaultview'}) {
|
||
|
# TAG:FR:ljlib:add_friend_getdefviewmask
|
||
|
my $group = LJ::get_friend_group($userid, { name => 'Default View' });
|
||
|
my $grp = $group ? $group->{groupnum}+0 : 0;
|
||
|
$groupmask |= (1 << $grp) if $grp;
|
||
|
}
|
||
|
|
||
|
# TAG:FR:ljlib:add_friend
|
||
|
my $bind = join(",", map { "(?,?,?,?,?)" } @add_ids);
|
||
|
my @vals = map { $userid, $_, $black, $white, $groupmask } @add_ids;
|
||
|
|
||
|
my $res = LJ::_friends_do
|
||
|
($userid, "INSERT IGNORE INTO friends (userid, friendid, fgcolor, bgcolor, groupmask) VALUES $bind", @vals);
|
||
|
|
||
|
# delete friend-of memcache keys for anyone who was added
|
||
|
foreach (@add_ids) {
|
||
|
LJ::MemCache::delete([ $userid, "frgmask:$userid:$_" ]);
|
||
|
LJ::memcache_kill($_, 'friendofs');
|
||
|
}
|
||
|
|
||
|
return $res;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::remove_friend
|
||
|
# args: uuid, to_del
|
||
|
# des-to_del: a single uuid or an arrayref of uuids to remove
|
||
|
# </LJFUNC>
|
||
|
sub remove_friend
|
||
|
{
|
||
|
my ($userid, $to_del) = @_;
|
||
|
|
||
|
$userid = LJ::want_userid($userid);
|
||
|
return undef unless $userid;
|
||
|
|
||
|
my @del_ids = ref $to_del eq 'ARRAY' ? map { LJ::want_userid($_) } @$to_del : ( LJ::want_userid($to_del) );
|
||
|
return 0 unless @del_ids;
|
||
|
|
||
|
my $bind = join(",", map { "?" } @del_ids);
|
||
|
my $res = LJ::_friends_do($userid, "DELETE FROM friends WHERE userid=? AND friendid IN ($bind)",
|
||
|
$userid, @del_ids);
|
||
|
|
||
|
# delete friend-of memcache keys for anyone who was removed
|
||
|
foreach my $fid (@del_ids) {
|
||
|
LJ::MemCache::delete([ $userid, "frgmask:$userid:$fid" ]);
|
||
|
LJ::memcache_kill($fid, 'friendofs');
|
||
|
}
|
||
|
|
||
|
return $res;
|
||
|
}
|
||
|
*delete_friend_edge = \&LJ::remove_friend;
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::get_friends
|
||
|
# des: Returns friends rows for a given user.
|
||
|
# args: uuserid, mask?, memcache_only?, force?, hint_superset?
|
||
|
# des-uuserid: a userid or u object
|
||
|
# des-mask: a security mask to filter on
|
||
|
# des-memcache_only: flag, set to only return data from memcache
|
||
|
# des-force: flag, set to ignore memcache and always hit db
|
||
|
# des-hint_superset: if defined, friends should be within this hash of ids
|
||
|
# returns: hashref; keys = friend userids
|
||
|
# values = hashrefs of 'friends' columns and their values
|
||
|
# </LJFUNC>
|
||
|
sub get_friends {
|
||
|
# TAG:FR:ljlib:get_friends
|
||
|
my ($uuid, $mask, $memcache_only, $force, $hint) = @_;
|
||
|
my $userid = LJ::want_userid($uuid);
|
||
|
return undef unless $userid;
|
||
|
return undef if $LJ::FORCE_EMPTY_FRIENDS{$userid};
|
||
|
|
||
|
# memcache data version
|
||
|
my $ver = 1;
|
||
|
|
||
|
my $packfmt = "NH6H6NC";
|
||
|
my $packlen = 15; # bytes
|
||
|
|
||
|
my @cols = qw(friendid fgcolor bgcolor groupmask showbydefault);
|
||
|
|
||
|
# first, check memcache
|
||
|
my $memkey = [$userid, "friends:$userid"];
|
||
|
|
||
|
unless ($force) {
|
||
|
my $memfriends = LJ::MemCache::get($memkey);
|
||
|
if ($memfriends) {
|
||
|
my %friends; # rows to be returned
|
||
|
|
||
|
# first byte of object is data version
|
||
|
# only version 1 is meaningful right now
|
||
|
my $memver = substr($memfriends, 0, 1, '');
|
||
|
return undef unless $memver == $ver;
|
||
|
|
||
|
# get each $packlen-byte row
|
||
|
while (length($memfriends) >= $packlen) {
|
||
|
my @row = unpack($packfmt, substr($memfriends, 0, $packlen, ''));
|
||
|
|
||
|
# don't add into %friends hash if groupmask doesn't match
|
||
|
next if $mask && ! ($row[3]+0 & $mask+0);
|
||
|
next if $hint && not exists $hint->{$row[0]};
|
||
|
|
||
|
# add "#" to beginning of colors
|
||
|
$row[$_] = "\#$row[$_]" foreach 1..2;
|
||
|
|
||
|
# turn unpacked row into hashref
|
||
|
my $fid = $row[0];
|
||
|
my $idx = 1;
|
||
|
foreach my $col (@cols[1..$#cols]) {
|
||
|
$friends{$fid}->{$col} = $row[$idx];
|
||
|
$idx++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# got from memcache, return
|
||
|
return \%friends;
|
||
|
}
|
||
|
}
|
||
|
return {} if $memcache_only; # no friends
|
||
|
|
||
|
my %colors;
|
||
|
my $color_fmt = sub {
|
||
|
my ($c) = @_;
|
||
|
my $res = $colors{$c};
|
||
|
if (!defined $res) { $res = sprintf("%06x", $c); $colors{$c} = $res; };
|
||
|
return $res;
|
||
|
};
|
||
|
|
||
|
# nothing from memcache, select all rows from the
|
||
|
# database and insert those into memcache
|
||
|
# then return rows that matched the given groupmask
|
||
|
|
||
|
my $mempack = $ver; # full packed string to insert into memcache, byte 1 is dversion
|
||
|
my %friends; # friends object to be returned, all groupmasks match
|
||
|
my $dbh = LJ::get_db_writer();
|
||
|
my $sth = $dbh->prepare("SELECT friendid, fgcolor, bgcolor, groupmask, showbydefault " .
|
||
|
"FROM friends WHERE userid=?");
|
||
|
$sth->execute($userid);
|
||
|
die $dbh->errstr if $dbh->err;
|
||
|
while (my @row = $sth->fetchrow_array) {
|
||
|
|
||
|
# convert color columns to hex
|
||
|
$row[$_] = $color_fmt->($row[$_]) foreach 1..2;
|
||
|
#$row[$_] = sprintf("%06x", $row[$_]) foreach 1..2;
|
||
|
|
||
|
$mempack .= pack($packfmt, @row);
|
||
|
|
||
|
# unless groupmask matches, skip adding to %friends
|
||
|
next if $mask && ! ($row[3]+0 & $mask+0);
|
||
|
next if $hint && not exists $hint->{$row[0]};
|
||
|
|
||
|
# add "#" to beginning of colors
|
||
|
$row[$_] = "\#$row[$_]" foreach 1..2;
|
||
|
|
||
|
my $fid = $row[0];
|
||
|
my $idx = 1;
|
||
|
foreach my $col (@cols[1..$#cols]) {
|
||
|
$friends{$fid}->{$col} = $row[$idx];
|
||
|
$idx++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LJ::MemCache::set($memkey, $mempack);
|
||
|
|
||
|
return \%friends;
|
||
|
}
|
||
|
|
||
|
|
||
|
# Get friendsfriends list using MemCache::get_multi()
|
||
|
# Skip self references, apply mask, but ignore colors.
|
||
|
# (code from get_friends(), slightly improved)
|
||
|
#
|
||
|
sub get_friends_multi {
|
||
|
# TAG:FR:ljlib:get_friends_multi
|
||
|
my ($uids, $mask) = @_;
|
||
|
|
||
|
# memcache data version
|
||
|
my $ver = 1;
|
||
|
|
||
|
my $packfmt = "NH6H6NC";
|
||
|
my $packlen = 15; # bytes
|
||
|
|
||
|
my @cols = qw(friendid fgcolor bgcolor groupmask showbydefault);
|
||
|
my %friends; # rows to be returned
|
||
|
|
||
|
my @memkeys = map { [$_, "friends:$_"] } keys %$uids;
|
||
|
my $mem = LJ::MemCache::get_multi(@memkeys) || {};
|
||
|
foreach (values %$mem) {
|
||
|
my $memfriends = $_;
|
||
|
if ($memfriends) {
|
||
|
|
||
|
# first byte of object is data version
|
||
|
# only version 1 is meaningful right now
|
||
|
my $memver = substr($memfriends, 0, 1, '');
|
||
|
return undef unless $memver == $ver;
|
||
|
|
||
|
# get each $packlen-byte row
|
||
|
while (length($memfriends) >= $packlen) {
|
||
|
my @row = unpack($packfmt, substr($memfriends, 0, $packlen, ''));
|
||
|
|
||
|
my $fid = $row[0];
|
||
|
next if exists $uids->{$fid};
|
||
|
next if exists $friends{$fid};
|
||
|
next if $mask && ! ($row[3]+0 & $mask+0);
|
||
|
|
||
|
# turn unpacked row into hashref
|
||
|
my $idx = 1;
|
||
|
foreach my $col (@cols[1..$#cols]) {
|
||
|
$friends{$fid}->{$col} = $row[$idx];
|
||
|
$idx++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return \%friends;
|
||
|
}
|
||
|
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::get_friendofs
|
||
|
# des: Returns userids of friendofs for a given user.
|
||
|
# args: uuserid, opts?
|
||
|
# des-opts: options hash, keys: 'force' => don't check memcache
|
||
|
# returns: userid for friendofs
|
||
|
# </LJFUNC>
|
||
|
sub get_friendofs {
|
||
|
# TAG:FR:ljlib:get_friends
|
||
|
my ($uuid, $opts) = @_;
|
||
|
my $userid = LJ::want_userid($uuid);
|
||
|
return undef unless $userid;
|
||
|
|
||
|
# first, check memcache
|
||
|
my $memkey = [$userid, "friendofs:$userid"];
|
||
|
|
||
|
unless ($opts->{force}) {
|
||
|
my $memfriendofs = LJ::MemCache::get($memkey);
|
||
|
return @$memfriendofs if $memfriendofs;
|
||
|
}
|
||
|
|
||
|
# nothing from memcache, select all rows from the
|
||
|
# database and insert those into memcache
|
||
|
|
||
|
my $dbh = LJ::get_db_writer();
|
||
|
my $limit = $opts->{force} ? '' : " LIMIT " . ($LJ::MAX_FRIENDOF_LOAD+1);
|
||
|
my $friendofs = $dbh->selectcol_arrayref
|
||
|
("SELECT userid FROM friends WHERE friendid=? $limit",
|
||
|
undef, $userid) || [];
|
||
|
die $dbh->errstr if $dbh->err;
|
||
|
|
||
|
LJ::MemCache::add($memkey, $friendofs);
|
||
|
|
||
|
return @$friendofs;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::get_friend_group
|
||
|
# des: Returns friendgroup row(s) for a given user.
|
||
|
# args: uuserid, opt?
|
||
|
# des-uuserid: a userid or u object
|
||
|
# des-opt: a hashref with keys: 'bit' => bit number of group to return
|
||
|
# 'name' => name of group to return
|
||
|
# returns: hashref; if bit/name are specified, returns hashref with keys being
|
||
|
# friendgroup rows, or undef if the group wasn't found.
|
||
|
#
|
||
|
# otherwise, returns hashref of all group rows with keys being
|
||
|
# group bit numbers and values being row col => val hashrefs
|
||
|
# </LJFUNC>
|
||
|
sub get_friend_group {
|
||
|
my ($uuid, $opt) = @_;
|
||
|
my $u = LJ::want_user($uuid);
|
||
|
return undef unless $u;
|
||
|
my $uid = $u->{userid};
|
||
|
|
||
|
# data version number
|
||
|
my $ver = 1;
|
||
|
|
||
|
# sanity check bitnum
|
||
|
delete $opt->{'bit'} if
|
||
|
(exists $opt->{'bit'}) &&
|
||
|
($opt->{'bit'} > 31 || $opt->{'bit'} < 0);
|
||
|
|
||
|
my $fg;
|
||
|
my $find_grp = sub {
|
||
|
|
||
|
# $fg format:
|
||
|
# [ version, [userid, bitnum, name, sortorder, public], [...], ... ]
|
||
|
|
||
|
my $memver = shift @$fg;
|
||
|
return undef unless $memver == $ver;
|
||
|
|
||
|
# bit number was specified
|
||
|
if ($opt->{'bit'}) {
|
||
|
foreach (@$fg) {
|
||
|
return LJ::MemCache::array_to_hash("fgrp", [$memver, @$_])
|
||
|
if $_->[1] == $opt->{'bit'};
|
||
|
}
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
# group name was specified
|
||
|
if ($opt->{'name'}) {
|
||
|
foreach (@$fg) {
|
||
|
return LJ::MemCache::array_to_hash("fgrp", [$memver, @$_])
|
||
|
if lc($_->[2]) eq lc($opt->{'name'});
|
||
|
}
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
# no arg, return entire object
|
||
|
return { map { $_->[1] => LJ::MemCache::array_to_hash("fgrp", [$memver, @$_]) } @$fg };
|
||
|
};
|
||
|
|
||
|
# check memcache
|
||
|
my $memkey = [$uid, "fgrp:$uid"];
|
||
|
$fg = LJ::MemCache::get($memkey);
|
||
|
return $find_grp->() if $fg;
|
||
|
|
||
|
# check database
|
||
|
$fg = [$ver];
|
||
|
my ($db, $fgtable) = $u->{dversion} > 5 ?
|
||
|
(LJ::get_cluster_def_reader($u), 'friendgroup2') : # if dversion is 6+, use definitive reader
|
||
|
(LJ::get_db_writer(), 'friendgroup'); # else, use regular db writer
|
||
|
return undef unless $db;
|
||
|
|
||
|
my $sth = $db->prepare("SELECT userid, groupnum, groupname, sortorder, is_public " .
|
||
|
"FROM $fgtable WHERE userid=?");
|
||
|
$sth->execute($uid);
|
||
|
return LJ::error($db) if $db->err;
|
||
|
|
||
|
my @row;
|
||
|
push @$fg, [ @row ] while @row = $sth->fetchrow_array;
|
||
|
|
||
|
# set in memcache
|
||
|
LJ::MemCache::set($memkey, $fg);
|
||
|
|
||
|
return $find_grp->();
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::fill_groups_xmlrpc
|
||
|
# des: Fills a hashref (presumably to be sent to an XMLRPC client, EG fotobilder)
|
||
|
# with user friend group information
|
||
|
# args: u, ret
|
||
|
# des-ret: a response hashref to fill with friend group data
|
||
|
# returns: undef if called incorrectly, 1 otherwise
|
||
|
# </LJFUNC>
|
||
|
sub fill_groups_xmlrpc {
|
||
|
my ($u, $ret) = @_;
|
||
|
return undef unless ref $u && ref $ret;
|
||
|
|
||
|
# layer on friend group information in the following format:
|
||
|
#
|
||
|
# grp:1 => 'mygroup',
|
||
|
# ...
|
||
|
# grp:30 => 'anothergroup',
|
||
|
#
|
||
|
# grpu:whitaker => '0,1,2,3,4',
|
||
|
# grpu:test => '0',
|
||
|
|
||
|
my $grp = LJ::get_friend_group($u) || {};
|
||
|
|
||
|
$ret->{"grp:0"} = "_all_";
|
||
|
foreach my $bit (1..30) {
|
||
|
next unless my $g = $grp->{$bit};
|
||
|
$ret->{"grp:$bit"} = $g->{groupname};
|
||
|
}
|
||
|
|
||
|
my $fr = LJ::get_friends($u) || {};
|
||
|
my $users = LJ::load_userids(keys %$fr);
|
||
|
while (my ($fid, $f) = each %$fr) {
|
||
|
my $u = $users->{$fid};
|
||
|
next unless $u->{journaltype} =~ /[PS]/;
|
||
|
|
||
|
my $fname = $u->{user};
|
||
|
$ret->{"grpu:$fid:$fname"} =
|
||
|
join(",", 0, grep { $grp->{$_} && $f->{groupmask} & 1 << $_ } 1..30);
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::mark_dirty
|
||
|
# des: Marks a given user as being $what type of dirty
|
||
|
# args: u, what
|
||
|
# des-what: type of dirty being marked (EG 'friends')
|
||
|
# returns: 1
|
||
|
# </LJFUNC>
|
||
|
sub mark_dirty {
|
||
|
my ($uuserid, $what) = @_;
|
||
|
|
||
|
my $userid = LJ::want_userid($uuserid);
|
||
|
return 1 if $LJ::REQ_CACHE_DIRTY{$what}->{$userid};
|
||
|
|
||
|
my $u = LJ::want_user($userid);
|
||
|
|
||
|
# friends dirtiness is only necessary to track
|
||
|
# if we're exchange XMLRPC with fotobilder
|
||
|
if ($what eq 'friends' && $LJ::FB_SITEROOT) {
|
||
|
push @LJ::CLEANUP_HANDLERS, sub {
|
||
|
my $res = LJ::cmd_buffer_add($u->{clusterid}, $u->{userid}, 'dirty', { what => 'friends' });
|
||
|
};
|
||
|
} else {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
$LJ::REQ_CACHE_DIRTY{$what}->{$userid}++;
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::memcache_kill
|
||
|
# des: Kills a memcache entry, given a userid and type
|
||
|
# args: uuserid, type
|
||
|
# des-uuserid: a userid or u object
|
||
|
# des-args: memcache key type, will be used as "$type:$userid"
|
||
|
# returns: results of LJ::MemCache::delete
|
||
|
# </LJFUNC>
|
||
|
sub memcache_kill {
|
||
|
my ($uuid, $type) = @_;
|
||
|
my $userid = want_userid($uuid);
|
||
|
return undef unless $userid && $type;
|
||
|
|
||
|
return LJ::MemCache::delete([$userid, "$type:$userid"]);
|
||
|
}
|
||
|
|
||
|
# is a user object (at least a hashref)
|
||
|
sub isu { return ref $_[0] && (ref $_[0] eq "LJ::User" ||
|
||
|
ref $_[0] eq "HASH" && $_[0]->{userid}); }
|
||
|
|
||
|
# create externally mapped user.
|
||
|
# return uid of LJ user on success, undef on error.
|
||
|
# opts = {
|
||
|
# extuser or extuserid (or both, but one is required.),
|
||
|
# caps
|
||
|
# }
|
||
|
# opts also can contain any additional options that create_account takes. (caps?)
|
||
|
sub create_extuser
|
||
|
{
|
||
|
my ($type, $opts) = @_;
|
||
|
return undef unless $type && $LJ::EXTERNAL_NAMESPACE{$type}->{id};
|
||
|
return undef unless ref $opts &&
|
||
|
($opts->{extuser} || defined $opts->{extuserid});
|
||
|
|
||
|
my $uid;
|
||
|
my $dbh = LJ::get_db_writer();
|
||
|
return undef unless $dbh;
|
||
|
|
||
|
# make sure a mapping for this user doesn't already exist.
|
||
|
$uid = LJ::get_extuser_uid( $type, $opts, 'force' );
|
||
|
return $uid if $uid;
|
||
|
|
||
|
# increment ext_ counter until we successfully create an LJ account.
|
||
|
# hard cap it at 10 tries. (arbitrary, but we really shouldn't have *any*
|
||
|
# failures here, let alone 10 in a row.)
|
||
|
for (1..10) {
|
||
|
my $extuser = 'ext_' . LJ::alloc_global_counter( 'E' );
|
||
|
$uid =
|
||
|
LJ::create_account(
|
||
|
{ caps => $opts->{caps}, user => $extuser, name => $extuser } );
|
||
|
last if $uid;
|
||
|
select undef, undef, undef, .10; # lets not thrash over this.
|
||
|
}
|
||
|
return undef unless $uid;
|
||
|
|
||
|
# add extuser mapping.
|
||
|
my $sql = "INSERT INTO extuser SET userid=?, siteid=?";
|
||
|
my @bind = ($uid, $LJ::EXTERNAL_NAMESPACE{$type}->{id});
|
||
|
|
||
|
if ($opts->{extuser}) {
|
||
|
$sql .= ", extuser=?";
|
||
|
push @bind, $opts->{extuser};
|
||
|
}
|
||
|
|
||
|
if ($opts->{extuserid}) {
|
||
|
$sql .= ", extuserid=? ";
|
||
|
push @bind, $opts->{extuserid}+0;
|
||
|
}
|
||
|
|
||
|
$dbh->do($sql, undef, @bind) or return undef;
|
||
|
return $uid;
|
||
|
}
|
||
|
|
||
|
# given an extuserid or extuser, return the LJ uid.
|
||
|
# return undef if there is no mapping.
|
||
|
sub get_extuser_uid
|
||
|
{
|
||
|
my ($type, $opts, $force) = @_;
|
||
|
return undef unless $type && $LJ::EXTERNAL_NAMESPACE{$type}->{id};
|
||
|
return undef unless ref $opts &&
|
||
|
($opts->{extuser} || defined $opts->{extuserid});
|
||
|
|
||
|
my $dbh = $force ? LJ::get_db_writer() : LJ::get_db_reader();
|
||
|
return undef unless $dbh;
|
||
|
|
||
|
my $sql = "SELECT userid FROM extuser WHERE siteid=?";
|
||
|
my @bind = ($LJ::EXTERNAL_NAMESPACE{$type}->{id});
|
||
|
|
||
|
if ($opts->{extuser}) {
|
||
|
$sql .= " AND extuser=?";
|
||
|
push @bind, $opts->{extuser};
|
||
|
}
|
||
|
|
||
|
if ($opts->{extuserid}) {
|
||
|
$sql .= $opts->{extuser} ? ' OR ' : ' AND ';
|
||
|
$sql .= "extuserid=?";
|
||
|
push @bind, $opts->{extuserid}+0;
|
||
|
}
|
||
|
|
||
|
return $dbh->selectrow_array($sql, undef, @bind);
|
||
|
}
|
||
|
|
||
|
# given a LJ userid/u, return a hashref of:
|
||
|
# type, extuser, extuserid
|
||
|
# returns undef if user isn't an externally mapped account.
|
||
|
sub get_extuser_map
|
||
|
{
|
||
|
my $uid = LJ::want_userid(shift);
|
||
|
return undef unless $uid;
|
||
|
|
||
|
my $dbr = LJ::get_db_reader();
|
||
|
return undef unless $dbr;
|
||
|
|
||
|
my $sql = "SELECT * FROM extuser WHERE userid=?";
|
||
|
my $ret = $dbr->selectrow_hashref($sql, undef, $uid);
|
||
|
return undef unless $ret;
|
||
|
|
||
|
my $type = 'unknown';
|
||
|
foreach ( keys %LJ::EXTERNAL_NAMESPACE ) {
|
||
|
$type = $_ if $LJ::EXTERNAL_NAMESPACE{$_}->{id} == $ret->{siteid};
|
||
|
}
|
||
|
|
||
|
$ret->{type} = $type;
|
||
|
return $ret;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::create_account
|
||
|
# des: Creates a new basic account. <b>Note:</b> This function is
|
||
|
# not really too useful but should be extended to be useful so
|
||
|
# htdocs/create.bml can use it, rather than doing the work itself.
|
||
|
# returns: integer of userid created, or 0 on failure.
|
||
|
# args: dbarg?, opts
|
||
|
# des-opts: hashref containing keys 'user', 'name', 'password', 'email', 'caps', 'journaltype'
|
||
|
# </LJFUNC>
|
||
|
sub create_account
|
||
|
{
|
||
|
&nodb;
|
||
|
my $o = shift;
|
||
|
|
||
|
my $user = LJ::canonical_username($o->{'user'});
|
||
|
unless ($user) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
my $dbh = LJ::get_db_writer();
|
||
|
my $quser = $dbh->quote($user);
|
||
|
my $cluster = defined $o->{'cluster'} ? $o->{'cluster'} : LJ::new_account_cluster();
|
||
|
my $caps = $o->{'caps'} || $LJ::NEWUSER_CAPS;
|
||
|
my $journaltype = $o->{'journaltype'} || "P";
|
||
|
|
||
|
# new non-clustered accounts aren't supported anymore
|
||
|
return 0 unless $cluster;
|
||
|
|
||
|
my $userid = 0; # scope it higher
|
||
|
my $errorcounter = 0;
|
||
|
my $old_print_error = $dbh->{PrintError}; # save PrintError mode
|
||
|
$dbh->{PrintError} = 0; # will check for errors manually
|
||
|
|
||
|
while (1) {
|
||
|
my $ruserid;
|
||
|
if ($o->{imported}) {
|
||
|
$ruserid = LJ::get_new_importedid();
|
||
|
}
|
||
|
else {
|
||
|
$ruserid = LJ::get_new_userid();
|
||
|
}
|
||
|
|
||
|
return 0 unless $ruserid;
|
||
|
$dbh->do("set insert_id = $ruserid");
|
||
|
|
||
|
# try to create new account
|
||
|
$dbh->do("INSERT INTO user (user, name, password, clusterid, dversion, caps, email, journaltype) ".
|
||
|
"VALUES ($quser, ?, ?, ?, $LJ::MAX_DVERSION, ?, ?, ?)", undef,
|
||
|
$o->{'name'}, $o->{'password'}, $cluster, $caps, $o->{'email'}, $journaltype);
|
||
|
|
||
|
if ($dbh->err) {
|
||
|
# who wants to try forever
|
||
|
if ($errorcounter > 10) {
|
||
|
print $dbh->{err} . ": " . $dbh->{errstr} . "\n";
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
$errorcounter++;
|
||
|
|
||
|
sleep 1; # let it breathe
|
||
|
next; # try again
|
||
|
}
|
||
|
else {
|
||
|
$userid = $dbh->{'mysql_insertid'}; # smells like success
|
||
|
$dbh->{PrintError} = $old_print_error; # restore error reporting
|
||
|
|
||
|
return 0 unless $userid; # but?
|
||
|
|
||
|
last; # finally
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$dbh->do("INSERT INTO useridmap (userid, user) VALUES ($userid, $quser)");
|
||
|
$dbh->do("INSERT INTO userusage (userid, timecreate) VALUES ($userid, NOW())");
|
||
|
|
||
|
LJ::run_hooks("post_create", {
|
||
|
'userid' => $userid,
|
||
|
'user' => $user,
|
||
|
'code' => undef,
|
||
|
});
|
||
|
return $userid;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::new_account_cluster
|
||
|
# des: Which cluster to put a new account on. $DEFAULT_CLUSTER if it's
|
||
|
# a scalar, random element from @$DEFAULT_CLUSTER if it's arrayref.
|
||
|
# also verifies that the database seems to be available.
|
||
|
# returns: clusterid where the new account should be created; 0 on error
|
||
|
# (such as no clusters available)
|
||
|
# </LJFUNC>
|
||
|
sub new_account_cluster
|
||
|
{
|
||
|
# if it's not an arrayref, put it in an array ref so we can use it below
|
||
|
my $clusters = ref $LJ::DEFAULT_CLUSTER ? $LJ::DEFAULT_CLUSTER : [ $LJ::DEFAULT_CLUSTER+0 ];
|
||
|
|
||
|
# iterate through the new clusters from a random point
|
||
|
my $size = @$clusters;
|
||
|
my $start = int(rand() * $size);
|
||
|
foreach (1..$size) {
|
||
|
my $cid = $clusters->[$start++ % $size];
|
||
|
|
||
|
# verify that this cluster is in @LJ::CLUSTERS
|
||
|
my @check = grep { $_ == $cid } @LJ::CLUSTERS;
|
||
|
next unless scalar(@check) >= 1 && $check[0] == $cid;
|
||
|
|
||
|
# try this cluster to see if we can use it, return if so
|
||
|
my $dbcm = LJ::get_cluster_master($cid);
|
||
|
return $cid if $dbcm;
|
||
|
}
|
||
|
|
||
|
# if we get here, we found no clusters that were up...
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::make_journal
|
||
|
# class:
|
||
|
# des:
|
||
|
# info:
|
||
|
# args: dbarg, user, view, remote, opts
|
||
|
# des-:
|
||
|
# returns:
|
||
|
# </LJFUNC>
|
||
|
sub make_journal
|
||
|
{
|
||
|
&nodb;
|
||
|
my ($user, $view, $remote, $opts) = @_;
|
||
|
|
||
|
my $r = $opts->{'r'}; # mod_perl $r, or undef
|
||
|
my $geta = $opts->{'getargs'};
|
||
|
|
||
|
if ($LJ::SERVER_DOWN) {
|
||
|
if ($opts->{'vhost'} eq "customview") {
|
||
|
return "<!-- LJ down for maintenance -->";
|
||
|
}
|
||
|
return LJ::server_down_html();
|
||
|
}
|
||
|
|
||
|
my $u = LJ::load_user($user);
|
||
|
unless ($u) {
|
||
|
$opts->{'baduser'} = 1;
|
||
|
return "<h1>Error</h1>No such user <b>$user</b>";
|
||
|
}
|
||
|
|
||
|
if (($view eq 'entry' || $view eq 'reply') && $u->{'statusvis'} !~ /[DXS]/) {
|
||
|
# both for s1 and s2: fast check for 304, 403, 404 statuses without a body
|
||
|
LJ::S2::EntryPage_fast_check($u, $view, $remote, $opts);
|
||
|
return if $opts->{'notmodified'} || $opts->{'suspendeduser'} || $opts->{'handler_return'};
|
||
|
}
|
||
|
|
||
|
# what charset we put in the HTML
|
||
|
$opts->{'saycharset'} ||= "utf-8";
|
||
|
|
||
|
if ($view eq 'data' && $u->{'statusvis'} !~ /[DXS]/) {
|
||
|
# rss, atom, foaf
|
||
|
return LJ::Feed::make_feed($r, $u, $remote, $opts);
|
||
|
}
|
||
|
|
||
|
|
||
|
# if we're using the 'tag' view, then convert to lastn and set option, this
|
||
|
# is done early so it can propogate to S1 and S2 as appropriate
|
||
|
if ($view eq 'tag') {
|
||
|
# FIXME: would be nice to have boolean logic "foo AND bar" or "+foo -bar" etc
|
||
|
# "foo,bar" is interpreted as "foo or bar" for now, common case
|
||
|
my $tags = LJ::durl($opts->{pathextra});
|
||
|
$tags =~ s/^\///; # clear leading /
|
||
|
$opts->{getargs}->{tag} = $tags;
|
||
|
|
||
|
# use a lastn view to render
|
||
|
delete $opts->{pathextra};
|
||
|
$view = 'lastn';
|
||
|
}
|
||
|
|
||
|
# S1 style hashref. won't be loaded now necessarily,
|
||
|
# only if via customview.
|
||
|
my $style;
|
||
|
|
||
|
my $styleid;
|
||
|
if ($opts->{'styleid'}) { # s1 styleid
|
||
|
$styleid = $opts->{'styleid'}+0;
|
||
|
|
||
|
# if we have an explicit styleid, we have to load
|
||
|
# it early so we can learn its type, so we can
|
||
|
# know which uprops to load for its owner
|
||
|
$style = LJ::S1::load_style($styleid, \$view);
|
||
|
} else {
|
||
|
$view ||= "lastn"; # default view when none specified explicitly in URLs
|
||
|
if ($LJ::viewinfo{$view} || $view eq "month" ||
|
||
|
$view eq "entry" || $view eq "reply") {
|
||
|
$styleid = -1; # to get past the return, then checked later for -1 and fixed, once user is loaded.
|
||
|
} else {
|
||
|
$opts->{'badargs'} = 1;
|
||
|
}
|
||
|
}
|
||
|
return unless $styleid;
|
||
|
|
||
|
my $eff_view = $LJ::viewinfo{$view}->{'styleof'} || $view;
|
||
|
my $s1prop = "s1_${eff_view}_style";
|
||
|
|
||
|
my @needed_props = ("stylesys", "s2_style", "url", "urlname", "opt_nctalklinks",
|
||
|
"renamedto", "opt_blockrobots", "opt_usesharedpic",
|
||
|
"journaltitle", "journalsubtitle", "external_foaf_url");
|
||
|
|
||
|
# S2 is more fully featured than S1, so sometimes we get here and $eff_view
|
||
|
# is reply/month/entry/res and that means it *has* to be S2--S1 defaults to a
|
||
|
# BML page to handle those, but we don't want to attempt to load a userprop
|
||
|
# because now load_user_props dies if you try to load something invalid
|
||
|
push @needed_props, $s1prop if $eff_view =~ /^(?:calendar|day|friends|lastn)$/;
|
||
|
|
||
|
# preload props the view creation code will need later (combine two selects)
|
||
|
if (ref $LJ::viewinfo{$eff_view}->{'owner_props'} eq "ARRAY") {
|
||
|
push @needed_props, @{$LJ::viewinfo{$eff_view}->{'owner_props'}};
|
||
|
}
|
||
|
|
||
|
if ($eff_view eq "reply") {
|
||
|
push @needed_props, "opt_logcommentips";
|
||
|
}
|
||
|
|
||
|
LJ::load_user_props($u, @needed_props);
|
||
|
|
||
|
# if the remote is the user to be viewed, make sure the $remote
|
||
|
# hashref has the value of $u's opt_nctalklinks (though with
|
||
|
# LJ::load_user caching, this may be assigning between the same
|
||
|
# underlying hashref)
|
||
|
$remote->{'opt_nctalklinks'} = $u->{'opt_nctalklinks'} if
|
||
|
($remote && $remote->{'userid'} == $u->{'userid'});
|
||
|
|
||
|
my $stylesys = 1;
|
||
|
if ($styleid == -1) {
|
||
|
|
||
|
my $get_styleinfo = sub {
|
||
|
|
||
|
my $get_s1_styleid = sub {
|
||
|
my $id = $u->{$s1prop};
|
||
|
LJ::run_hooks("s1_style_select", {
|
||
|
'styleid' => \$id,
|
||
|
'u' => $u,
|
||
|
'view' => $view,
|
||
|
});
|
||
|
return $id;
|
||
|
};
|
||
|
|
||
|
# forced s2 style id
|
||
|
if ($geta->{'s2id'} && LJ::get_cap($u, "s2styles")) {
|
||
|
|
||
|
# see if they own the requested style
|
||
|
my $dbr = LJ::get_db_reader();
|
||
|
my $style_userid = $dbr->selectrow_array("SELECT userid FROM s2styles WHERE styleid=?",
|
||
|
undef, $geta->{'s2id'});
|
||
|
|
||
|
# if remote owns the style or the journal owns the style, it's okay
|
||
|
if ($u->{'userid'} == $style_userid ||
|
||
|
$remote->{'userid'} == $style_userid ) {
|
||
|
return (2, $geta->{'s2id'});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# style=mine passed in GET?
|
||
|
if ($remote && $geta->{'style'} eq 'mine') {
|
||
|
|
||
|
# get remote props and decide what style remote uses
|
||
|
LJ::load_user_props($remote, "stylesys", "s2_style");
|
||
|
|
||
|
# remote using s2; make sure we pass down the $remote object as the style_u to
|
||
|
# indicate that they should use $remote to load the style instead of the regular $u
|
||
|
if ($remote->{'stylesys'} == 2 && $remote->{'s2_style'}) {
|
||
|
$opts->{'checkremote'} = 1;
|
||
|
$opts->{'style_u'} = $remote;
|
||
|
return (2, $remote->{'s2_style'});
|
||
|
}
|
||
|
|
||
|
# remote using s1
|
||
|
return (1, $get_s1_styleid->());
|
||
|
}
|
||
|
|
||
|
# resource URLs have the styleid in it
|
||
|
if ($view eq "res" && $opts->{'pathextra'} =~ m!^/(\d+)/!) {
|
||
|
return (2, $1);
|
||
|
}
|
||
|
|
||
|
my $forceflag = 0;
|
||
|
LJ::run_hooks("force_s1", $u, \$forceflag);
|
||
|
|
||
|
# if none of the above match, they fall through to here
|
||
|
if ( !$forceflag && $u->{'stylesys'} == 2 ) {
|
||
|
return (2, $u->{'s2_style'});
|
||
|
}
|
||
|
|
||
|
# no special case and not s2, fall through to s1
|
||
|
return (1, $get_s1_styleid->());
|
||
|
};
|
||
|
|
||
|
($stylesys, $styleid) = $get_styleinfo->();
|
||
|
}
|
||
|
|
||
|
if ($r) {
|
||
|
$r->notes('journalid' => $u->{'userid'});
|
||
|
}
|
||
|
|
||
|
my $notice = sub {
|
||
|
my $msg = shift;
|
||
|
my $status = shift;
|
||
|
|
||
|
my $url = "$LJ::SITEROOT/users/$user/";
|
||
|
$opts->{'status'} = $status if $status;
|
||
|
|
||
|
my $head;
|
||
|
$head .= qq{<link rel="openid.server" href="$LJ::OPENID_SERVER" />\n}
|
||
|
if LJ::OpenID::server_enabled();
|
||
|
|
||
|
return qq{
|
||
|
<html>
|
||
|
<head>
|
||
|
$head
|
||
|
</head>
|
||
|
<body>
|
||
|
<h1>Notice</h1>
|
||
|
<p>$msg</p>
|
||
|
<p>Instead, please use <nobr><a href=\"$url\">$url</a></nobr></p>
|
||
|
</body>
|
||
|
</html>
|
||
|
}.("<!-- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -->\n" x 50);
|
||
|
};
|
||
|
my $error = sub {
|
||
|
my $msg = shift;
|
||
|
my $status = shift;
|
||
|
$opts->{'status'} = $status if $status;
|
||
|
|
||
|
return qq{
|
||
|
<h1>Error</h1>
|
||
|
<p>$msg</p>
|
||
|
}.("<!-- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -->\n" x 50);
|
||
|
};
|
||
|
if ($LJ::USER_VHOSTS && $opts->{'vhost'} eq "users" && $u->{'journaltype'} ne 'R' &&
|
||
|
! LJ::get_cap($u, "userdomain")) {
|
||
|
return $notice->("URLs like <nobr><b>http://<i>username</i>.$LJ::USER_DOMAIN/" .
|
||
|
"</b></nobr> are not available for this user's account type.");
|
||
|
}
|
||
|
if ($opts->{'vhost'} =~ /^other:/ && ! LJ::get_cap($u, "userdomain")) {
|
||
|
return $notice->("This user's account type doesn't permit domain aliasing.");
|
||
|
}
|
||
|
if ($opts->{'vhost'} eq "customview" && ! LJ::get_cap($u, "styles")) {
|
||
|
return $notice->("This user's account type is not permitted to create and embed styles.");
|
||
|
}
|
||
|
if ($opts->{'vhost'} eq "community" && $u->{'journaltype'} !~ /[CR]/) {
|
||
|
$opts->{'badargs'} = 1; # Output a generic 'bad URL' message if available
|
||
|
return "<h1>Notice</h1><p>This account isn't a community journal.</p>";
|
||
|
}
|
||
|
if ($view eq "friendsfriends" && ! LJ::get_cap($u, "friendsfriendsview")) {
|
||
|
return "<b>Sorry</b><br />This user's account type doesn't permit showing friends of friends.";
|
||
|
}
|
||
|
|
||
|
# if there's a GET argument for tags, split those out
|
||
|
if (exists $opts->{getargs}->{tag}) {
|
||
|
my $tagfilter = $opts->{getargs}->{tag};
|
||
|
|
||
|
my $print_tag_list = sub {
|
||
|
# show all tags and exit
|
||
|
my $tags = LJ::Tags::get_usertags($u, { remote => $remote });
|
||
|
my @list = ();
|
||
|
foreach (values %{$tags}) {
|
||
|
push @list, [$_->{'name'}, $_->{'uses'}];
|
||
|
}
|
||
|
@list = sort {$a->[0] cmp $b->[0]} @list; #sort by alphabet
|
||
|
#@list = sort {$b->[1] <=> $a->[1]} @list; #sort by popularity
|
||
|
my $text = "<html><head>Visible Tags</head><body><ul>\n";
|
||
|
foreach (@list) {
|
||
|
my $num = "$_->[1] uses";
|
||
|
$num = "1 use" if ($_->[1] == 1);
|
||
|
$text .= "<li><a href=\"$LJ::SITEROOT/users/$user/tag/$_->[0]\">$_->[0]</a> - $num</li>\n";
|
||
|
}
|
||
|
$text .= "</ul></body></html>";
|
||
|
return $text;
|
||
|
};
|
||
|
|
||
|
# if empty tags argument - show all tags and exit.
|
||
|
return $print_tag_list->() unless $tagfilter;
|
||
|
|
||
|
$opts->{tags} = [];
|
||
|
|
||
|
return $print_tag_list->()
|
||
|
#return $error->("Sorry, the tag list specified is invalid.", "404 Not Found")
|
||
|
unless LJ::Tags::is_valid_tagstring($tagfilter, $opts->{tags});
|
||
|
}
|
||
|
|
||
|
|
||
|
unless ($geta->{'viewall'} && LJ::check_priv($remote, "canview") ||
|
||
|
$opts->{'pathextra'} =~ m#/(\d+)/stylesheet$#
|
||
|
) { # don't check style sheets
|
||
|
return $error->("Journal has been deleted. If you are <b>$user</b>, you have a period of 30 days to decide to undelete your journal.", "404 Not Found") if ($u->{'statusvis'} eq "D");
|
||
|
return $error->("This journal has been suspended.", "403 Forbidden") if ($u->{'statusvis'} eq "S");
|
||
|
}
|
||
|
|
||
|
return $error->("This journal has been deleted and purged.", "410 Gone") if ($u->{'statusvis'} eq "X");
|
||
|
|
||
|
return $error->("This user has no journal here.", "404 Not here") if $u->{'journaltype'} eq "I" && $view ne "friends";
|
||
|
|
||
|
$opts->{'view'} = $view;
|
||
|
|
||
|
if ($stylesys == 2) {
|
||
|
$r->notes('codepath' => "s2.$view") if $r;
|
||
|
return LJ::S2::make_journal($u, $styleid, $view, $remote, $opts);
|
||
|
}
|
||
|
|
||
|
# Everything from here on down is S1. FIXME: this should be moved to LJ::S1::make_journal
|
||
|
# to be more like LJ::S2::make_journal.
|
||
|
|
||
|
return LJ::S1_make_journal($r, $u, $style, $styleid, $view, $remote, $opts);
|
||
|
}
|
||
|
|
||
|
# S1
|
||
|
sub S1_make_journal {
|
||
|
my ($r, $u, $style, $styleid, $view, $remote, $opts) = @_;
|
||
|
|
||
|
$r->notes('codepath' => "s1.$view") if $r;
|
||
|
|
||
|
# signal to LiveJournal.pm that we can't handle this - is it ever needed?
|
||
|
if ($view eq "entry" || $view eq "reply" || $view eq "month") {
|
||
|
${$opts->{'handle_with_bml_ref'}} = 1;
|
||
|
}
|
||
|
|
||
|
# For embedded polls
|
||
|
BML::set_language($LJ::LANGS[0] || 'en', \&LJ::Lang::get_text);
|
||
|
|
||
|
# load the user-related S1 data (overrides and colors)
|
||
|
my $s1uc = {};
|
||
|
my $s1uc_memkey = [$u->{'userid'}, "s1uc:$u->{'userid'}"];
|
||
|
if ($u->{'useoverrides'} eq "Y" || $u->{'themeid'} == 0) {
|
||
|
$s1uc = LJ::MemCache::get($s1uc_memkey);
|
||
|
unless ($s1uc) {
|
||
|
my $db;
|
||
|
my $setmem = 1;
|
||
|
if (@LJ::MEMCACHE_SERVERS) {
|
||
|
$db = LJ::get_cluster_def_reader($u);
|
||
|
} else {
|
||
|
$db = LJ::get_cluster_reader($u);
|
||
|
$setmem = 0;
|
||
|
}
|
||
|
$s1uc = $db->selectrow_hashref("SELECT * FROM s1usercache WHERE userid=?",
|
||
|
undef, $u->{'userid'});
|
||
|
LJ::MemCache::set($s1uc_memkey, $s1uc) if $s1uc && $setmem;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# we should have our cache row! we'll update it in a second.
|
||
|
my $dbcm;
|
||
|
if (! $s1uc) {
|
||
|
$u->do("INSERT IGNORE INTO s1usercache (userid) VALUES (?)", undef, $u->{'userid'});
|
||
|
$s1uc = {};
|
||
|
}
|
||
|
|
||
|
# conditionally rebuild parts of our cache that are missing
|
||
|
my %update;
|
||
|
|
||
|
# is the overrides cache old or missing?
|
||
|
my $dbh;
|
||
|
if ($u->{'useoverrides'} eq "Y" && (! $s1uc->{'override_stor'} ||
|
||
|
$s1uc->{'override_cleanver'} < $LJ::S1::CLEANER_VERSION)) {
|
||
|
|
||
|
my $overrides = LJ::S1::get_overrides($u);
|
||
|
$update{'override_stor'} = LJ::CleanHTML::clean_s1_style($overrides);
|
||
|
$update{'override_cleanver'} = $LJ::S1::CLEANER_VERSION;
|
||
|
}
|
||
|
|
||
|
# is the color cache here if it's a custom user theme?
|
||
|
if ($u->{'themeid'} == 0 && ! $s1uc->{'color_stor'}) {
|
||
|
my $col = {};
|
||
|
$dbh ||= LJ::get_db_writer();
|
||
|
my $sth = $dbh->prepare("SELECT coltype, color FROM themecustom WHERE user=?");
|
||
|
$sth->execute($u->{'user'});
|
||
|
$col->{$_->{'coltype'}} = $_->{'color'} while $_ = $sth->fetchrow_hashref;
|
||
|
$update{'color_stor'} = Storable::nfreeze($col);
|
||
|
}
|
||
|
|
||
|
# save the updates
|
||
|
if (%update) {
|
||
|
my $set;
|
||
|
foreach my $k (keys %update) {
|
||
|
$s1uc->{$k} = $update{$k};
|
||
|
$set .= ", " if $set;
|
||
|
$set .= "$k=" . $u->quote($update{$k});
|
||
|
}
|
||
|
my $rv = $u->do("UPDATE s1usercache SET $set WHERE userid=?", undef, $u->{'userid'});
|
||
|
if ($rv && $update{'color_stor'}) {
|
||
|
$dbh ||= LJ::get_db_writer();
|
||
|
$dbh->do("DELETE FROM themecustom WHERE user=?", undef, $u->{'user'});
|
||
|
}
|
||
|
LJ::MemCache::set($s1uc_memkey, $s1uc);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
# load the style
|
||
|
my $viewref = $view eq "" ? \$view : undef;
|
||
|
$style ||= $LJ::viewinfo{$view}->{'nostyle'} ? {} :
|
||
|
LJ::S1::load_style($styleid, $viewref);
|
||
|
|
||
|
my %vars = ();
|
||
|
|
||
|
# apply the style
|
||
|
foreach (keys %$style) {
|
||
|
$vars{$_} = $style->{$_};
|
||
|
}
|
||
|
|
||
|
# apply the overrides
|
||
|
if ($opts->{'nooverride'}==0 && $u->{'useoverrides'} eq "Y") {
|
||
|
my $tw = Storable::thaw($s1uc->{'override_stor'});
|
||
|
foreach (keys %$tw) {
|
||
|
$vars{$_} = $tw->{$_};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# apply the color theme
|
||
|
my $cols = $u->{'themeid'} ? LJ::S1::get_themeid($u->{'themeid'}) :
|
||
|
Storable::thaw($s1uc->{'color_stor'});
|
||
|
foreach (keys %$cols) {
|
||
|
$vars{"color-$_"} = $cols->{$_};
|
||
|
}
|
||
|
|
||
|
# instruct some function to make this specific view type
|
||
|
return unless defined $LJ::viewinfo{$view}->{'creator'};
|
||
|
my $ret = "";
|
||
|
|
||
|
# call the view creator w/ the buffer to fill and the construction variables
|
||
|
my $res = $LJ::viewinfo{$view}->{'creator'}->(\$ret, $u, \%vars, $remote, $opts);
|
||
|
unless ($res) {
|
||
|
my $errcode = $opts->{'errcode'};
|
||
|
my $errmsg = {
|
||
|
'nodb' => 'Database temporarily unavailable during maintenance.',
|
||
|
'nosyn' => 'No syndication URL available.',
|
||
|
}->{$errcode};
|
||
|
return "<!-- $errmsg -->" if ($opts->{'vhost'} eq "customview");
|
||
|
|
||
|
print STDERR "error running LJ::viewinfo [$errcode]; user [$u->{'name'}] \n";
|
||
|
|
||
|
# If not customview, set the error response code.
|
||
|
$opts->{'status'} = {
|
||
|
'nodb' => '503 Maintenance',
|
||
|
'nosyn' => '404 Not Found',
|
||
|
}->{$errcode} || '500 Server Error';
|
||
|
return "3".$errmsg;
|
||
|
}
|
||
|
|
||
|
if ($opts->{'redir'}) {
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
# clean up attributes which we weren't able to quickly verify
|
||
|
# as safe in the Storable-stored clean copy of the style.
|
||
|
$ret =~ s/\%\%\[attr\[(.+?)\]\]\%\%/LJ::CleanHTML::s1_attribute_clean($1)/eg;
|
||
|
|
||
|
# return it...
|
||
|
return $ret;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::canonical_username
|
||
|
# des:
|
||
|
# info:
|
||
|
# args: user
|
||
|
# returns: the canonical username given, or blank if the username is not well-formed
|
||
|
# </LJFUNC>
|
||
|
sub canonical_username
|
||
|
{
|
||
|
my $user = shift;
|
||
|
if ($user =~ /^\s*([\w\-]{1,15})\s*$/) {
|
||
|
# perl 5.8 bug: $user = lc($1) sometimes causes corruption when $1 points into $user.
|
||
|
$user = $1;
|
||
|
$user = lc($user);
|
||
|
$user =~ s/-/_/g;
|
||
|
return $user;
|
||
|
}
|
||
|
return ""; # not a good username.
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::get_userid
|
||
|
# des: Returns a userid given a username.
|
||
|
# info: Results cached in memory. On miss, does DB call. Not advised
|
||
|
# to use this many times in a row... only once or twice perhaps
|
||
|
# per request. Tons of serialized db requests, even when small,
|
||
|
# are slow. Opposite of [func[LJ::get_username]].
|
||
|
# args: dbarg?, user
|
||
|
# des-user: Username whose userid to look up.
|
||
|
# returns: Userid, or 0 if invalid user.
|
||
|
# </LJFUNC>
|
||
|
sub get_userid
|
||
|
{
|
||
|
&nodb;
|
||
|
my $user = shift;
|
||
|
|
||
|
$user = LJ::canonical_username($user);
|
||
|
|
||
|
if ($LJ::CACHE_USERID{$user}) { return $LJ::CACHE_USERID{$user}; }
|
||
|
|
||
|
my $userid = LJ::MemCache::get("uidof:$user");
|
||
|
return $LJ::CACHE_USERID{$user} = $userid if $userid;
|
||
|
|
||
|
my $dbr = LJ::get_db_reader();
|
||
|
$userid = $dbr->selectrow_array("SELECT userid FROM useridmap WHERE user=?", undef, $user);
|
||
|
|
||
|
# implictly create an account if we're using an external
|
||
|
# auth mechanism
|
||
|
if (! $userid && ref $LJ::AUTH_EXISTS eq "CODE")
|
||
|
{
|
||
|
$userid = LJ::create_account({ 'user' => $user,
|
||
|
'name' => $user,
|
||
|
'password' => '', });
|
||
|
}
|
||
|
|
||
|
if ($userid) {
|
||
|
$LJ::CACHE_USERID{$user} = $userid;
|
||
|
LJ::MemCache::set("uidof:$user", $userid);
|
||
|
}
|
||
|
|
||
|
return ($userid+0);
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::want_userid
|
||
|
# des: Returns userid when passed either userid or the user hash. Useful to functions that
|
||
|
# want to accept either. Forces its return value to be a number (for safety).
|
||
|
# args: userid
|
||
|
# des-userid: Either a userid, or a user hash with the userid in its 'userid' key.
|
||
|
# returns: The userid, guaranteed to be a numeric value.
|
||
|
# </LJFUNC>
|
||
|
sub want_userid
|
||
|
{
|
||
|
my $uuserid = shift;
|
||
|
return ($uuserid->{'userid'} + 0) if ref $uuserid;
|
||
|
return ($uuserid + 0);
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::want_user
|
||
|
# des: Returns user object when passed either userid or the user hash. Useful to functions that
|
||
|
# want to accept either.
|
||
|
# args: user
|
||
|
# des-user: Either a userid, or a user hash with the userid in its 'userid' key.
|
||
|
# returns: The user hash represented by said userid.
|
||
|
# </LJFUNC>
|
||
|
sub want_user
|
||
|
{
|
||
|
my $uuser = shift;
|
||
|
return $uuser if ref $uuser;
|
||
|
return $uuser unless $uuser;
|
||
|
return LJ::load_userid($uuser+0);
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::get_username
|
||
|
# des: Returns a username given a userid.
|
||
|
# info: Results cached in memory. On miss, does DB call. Not advised
|
||
|
# to use this many times in a row... only once or twice perhaps
|
||
|
# per request. Tons of serialized db requests, even when small,
|
||
|
# are slow. Opposite of [func[LJ::get_userid]].
|
||
|
# args: dbarg?, user
|
||
|
# des-user: Username whose userid to look up.
|
||
|
# returns: Userid, or 0 if invalid user.
|
||
|
# </LJFUNC>
|
||
|
sub get_username
|
||
|
{
|
||
|
&nodb;
|
||
|
my $userid = shift;
|
||
|
$userid += 0;
|
||
|
|
||
|
# Checked the cache first.
|
||
|
if ($LJ::CACHE_USERNAME{$userid}) { return $LJ::CACHE_USERNAME{$userid}; }
|
||
|
|
||
|
# if we're using memcache, it's faster to just query memcache for
|
||
|
# an entire $u object and just return the username. otherwise, we'll
|
||
|
# go ahead and query useridmap
|
||
|
if (@LJ::MEMCACHE_SERVERS) {
|
||
|
my $u = LJ::load_userid($userid);
|
||
|
return undef unless $u;
|
||
|
|
||
|
$LJ::CACHE_USERNAME{$userid} = $u->{'user'};
|
||
|
return $u->{'user'};
|
||
|
}
|
||
|
|
||
|
my $dbr = LJ::get_db_reader();
|
||
|
my $user = $dbr->selectrow_array("SELECT user FROM useridmap WHERE userid=?", undef, $userid);
|
||
|
|
||
|
# Fall back to master if it doesn't exist.
|
||
|
unless (defined $user) {
|
||
|
my $dbh = LJ::get_db_writer();
|
||
|
$user = $dbh->selectrow_array("SELECT user FROM useridmap WHERE userid=?", undef, $userid);
|
||
|
}
|
||
|
|
||
|
return undef unless defined $user;
|
||
|
|
||
|
$LJ::CACHE_USERNAME{$userid} = $user;
|
||
|
return $user;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::can_manage
|
||
|
# des: Given a user and a target user, will determine if the first user is an
|
||
|
# admin for the target user.
|
||
|
# returns: bool: true if authorized, otherwise fail
|
||
|
# args: remote, u
|
||
|
# des-remote: user object or userid of user to try and authenticate
|
||
|
# des-u: user object or userid of target user
|
||
|
# </LJFUNC>
|
||
|
sub can_manage {
|
||
|
my $remote = LJ::want_user(shift);
|
||
|
my $u = LJ::want_user(shift);
|
||
|
return undef unless $remote && $u;
|
||
|
|
||
|
# is same user?
|
||
|
return 1 if LJ::u_equals($u, $remote);
|
||
|
|
||
|
# people/syn/rename accounts can only be managed by the one account
|
||
|
return undef if $u->{journaltype} =~ /^[PYR]$/;
|
||
|
|
||
|
# check for admin access
|
||
|
return undef unless LJ::check_rel($u, $remote, 'A');
|
||
|
|
||
|
# passed checks, return true
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::can_manage_other
|
||
|
# des: Given a user and a target user, will determine if the first user is an
|
||
|
# admin for the target user, but not if the two are the same.
|
||
|
# returns: bool: true if authorized, otherwise fail
|
||
|
# args: remote, u
|
||
|
# des-remote: user object or userid of user to try and authenticate
|
||
|
# des-u: user object or userid of target user
|
||
|
# </LJFUNC>
|
||
|
sub can_manage_other {
|
||
|
my ($remote, $u) = @_;
|
||
|
return 0 if LJ::want_userid($remote) == LJ::want_userid($u);
|
||
|
return LJ::can_manage($remote, $u);
|
||
|
}
|
||
|
|
||
|
sub can_delete_journal_item {
|
||
|
return LJ::can_manage(@_);
|
||
|
}
|
||
|
|
||
|
# <LJFUNC>
|
||
|
# name: LJ::get_remote
|
||
|
# des: authenticates the user at the remote end based on their cookies
|
||
|
# and returns a hashref representing them
|
||
|
# returns: hashref containing 'user' and 'userid' if valid user, else
|
||
|
# undef.
|
||
|
# args: opts?
|
||
|
# des-opts: 'criterr': scalar ref to set critical error flag. if set, caller
|
||
|
# should stop processing whatever it's doing and complain
|
||
|
# about an invalid login with a link to the logout page..
|
||
|
# 'ignore_ip': ignore IP address of remote for IP-bound sessions
|
||
|
# </LJFUNC>
|
||
|
sub get_remote
|
||
|
{
|
||
|
my $opts = ref $_[0] eq "HASH" ? shift : {};
|
||
|
my $dummy;
|
||
|
|
||
|
return $LJ::CACHE_REMOTE if $LJ::CACHED_REMOTE && ! $opts->{'ignore_ip'};
|
||
|
|
||
|
my $criterr = $opts->{criterr} || \$dummy;
|
||
|
$$criterr = 0;
|
||
|
|
||
|
my $cookie = sub { return $BML::COOKIE{$_[0]}; };
|
||
|
|
||
|
my $no_remote = sub {
|
||
|
LJ::set_remote(undef);
|
||
|
return undef;
|
||
|
};
|
||
|
|
||
|
# set this flag if any of their ljsession cookies contained the ".FS"
|
||
|
# opt to use the fast server. if we later find they're not logged
|
||
|
# in and set it, or set it with a free account, then we give them
|
||
|
# the invalid cookies error.
|
||
|
my $tried_fast = 0;
|
||
|
my @errs = ();
|
||
|
my $now = time();
|
||
|
|
||
|
my ($u, $sess); # what we eventually care about
|
||
|
my $memkey;
|
||
|
|
||
|
foreach my $sessdata (@{ $cookie->('ljsession[]'); }) {
|
||
|
my ($authtype, $user, $sessid, $auth, $sopts) = split(/:/, $sessdata);
|
||
|
$tried_fast = 1 if $sopts =~ /\.FS\b/;
|
||
|
my $err = sub {
|
||
|
$sess = undef;
|
||
|
push @errs, "$sessdata: $_[0]";
|
||
|
};
|
||
|
|
||
|
# fail unless authtype is 'ws' (more might be added in future)
|
||
|
unless ($authtype eq "ws") {
|
||
|
$err->("no ws auth");
|
||
|
next;
|
||
|
}
|
||
|
|
||
|
$u = LJ::load_user($user);
|
||
|
unless ($u) {
|
||
|
$err->("user doesn't exist");
|
||
|
next;
|
||
|
}
|
||
|
|
||
|
# locked accounts can't be logged in
|
||
|
if ($u->{statusvis} eq 'L') {
|
||
|
$err->("User account is locked.");
|
||
|
next;
|
||
|
}
|
||
|
|
||
|
my $sess_db;
|
||
|
my $get_sess = sub {
|
||
|
return undef unless $sess_db;
|
||
|
$sess = $sess_db->selectrow_hashref("SELECT * FROM sessions ".
|
||
|
"WHERE userid=? AND sessid=? AND auth=?",
|
||
|
undef, $u->{'userid'}, $sessid, $auth);
|
||
|
};
|
||
|
$memkey = [$u->{'userid'},"sess:$u->{'userid'}:$sessid"];
|
||
|
# try memory
|
||
|
$sess = LJ::MemCache::get($memkey);
|
||
|
# try master
|
||
|
unless ($sess) {
|
||
|
$sess_db = LJ::get_cluster_def_reader($u);
|
||
|
$get_sess->();
|
||
|
LJ::MemCache::set($memkey, $sess) if $sess;
|
||
|
}
|
||
|
# try slave
|
||
|
unless ($sess) {
|
||
|
$sess_db = LJ::get_cluster_reader($u);
|
||
|
$get_sess->();
|
||
|
}
|
||
|
|
||
|
unless ($sess) {
|
||
|
$err->("Couldn't find session");
|
||
|
next;
|
||
|
}
|
||
|
|
||
|
unless ($sess->{auth} eq $auth) {
|
||
|
$err->("Invald auth");
|
||
|
next;
|
||
|
}
|
||
|
|
||
|
if ($sess->{'timeexpire'} < $now) {
|
||
|
$err->("Invalid auth");
|
||
|
next;
|
||
|
}
|
||
|
|
||
|
if ($sess->{'ipfixed'} && ! $opts->{'ignore_ip'}) {
|
||
|
my $remote_ip = $LJ::_XFER_REMOTE_IP || LJ::get_remote_ip();
|
||
|
if ($sess->{'ipfixed'} ne $remote_ip) {
|
||
|
$err->("Session wrong IP");
|
||
|
next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
last;
|
||
|
}
|
||
|
|
||
|
# inform the caller that this user is faking their fast-server cookie
|
||
|
# attribute.
|
||
|
if ($tried_fast && ! LJ::get_cap($u, "fastserver")) {
|
||
|
$$criterr = 1;
|
||
|
}
|
||
|
|
||
|
if (! $sess) {
|
||
|
LJ::set_remote(undef);
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
# renew short session
|
||
|
my $sess_length = {
|
||
|
'short' => 60*60*24*1.5,
|
||
|
'long' => 60*60*24*60,
|
||
|
'once' => 0, # do not renew these
|
||
|
}->{$sess->{exptype}};
|
||
|
|
||
|
# only long cookies should be given an expiration time
|
||
|
# other should get 0 (when browser closes)
|
||
|
my $cookie_length = $sess->{exptype} eq 'long' ? $sess_length : 0;
|
||
|
|
||
|
# if there is a new session length to be set and the user's db writer is available,
|
||
|
# go ahead and set the new session expiration in the database. then only update the
|
||
|
# cookies if the database operation is successful
|
||
|
if ($sess_length && $sess->{'timeexpire'} - $now < $sess_length/2 &&
|
||
|
$u->writer && $u->do("UPDATE sessions SET timeexpire=? WHERE userid=? AND sessid=?",
|
||
|
undef, $now + $sess_length, $u->{userid}, $sess->{sessid}))
|
||
|
{
|
||
|
|
||
|
# delete old, now-bogus memcache data
|
||
|
LJ::MemCache::delete($memkey);
|
||
|
|
||
|
# update their ljsession cookie unless it's a session-length cookie
|
||
|
if ($cookie_length) {
|
||
|
|
||
|
eval {
|
||
|
my @domains = ref $LJ::COOKIE_DOMAIN ? @$LJ::COOKIE_DOMAIN : ($LJ::COOKIE_DOMAIN);
|
||
|
foreach my $dom (@domains) {
|
||
|
my $cookiestr = 'ljsession=' . $cookie->('ljsession');
|
||
|
$cookiestr .= '; expires=' . LJ::time_to_cookie($now + $cookie_length);
|
||
|
$cookiestr .= $dom ? "; domain=$dom" : '';
|
||
|
$cookiestr .= '; path=/; HttpOnly';
|
||
|
|
||
|
Apache->request->err_headers_out->add('Set-Cookie' => $cookiestr);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# augment hash with session data;
|
||
|
$u->{'_session'} = $sess;
|
||
|
|
||
|
LJ::set_remote($u);
|
||
|
|
||
|
eval {
|
||
|
Apache->request->notes("ljuser" => $u->{'user'});
|
||
|
};
|
||
|
|
||
|
return $u;
|
||
|
}
|
||
|
|
||
|
sub set_remote
|
||
|
{
|
||
|
my $remote = shift;
|
||
|
$LJ::CACHED_REMOTE = 1;
|
||
|
$LJ::CACHE_REMOTE = $remote;
|
||
|
1;
|
||
|
}
|
||
|
|
||
|
sub unset_remote
|
||
|
{
|
||
|
$LJ::CACHED_REMOTE = 0;
|
||
|
$LJ::CACHE_REMOTE = undef;
|
||
|
1;
|
||
|
}
|
||
|
|
||
|
|
||
|
1;
|