3 no warnings
'uninitialized';
5 use lib
"$ENV{LJHOME}/cgi-bin";
8 LJ
::User
::Relations
::Friends
9 LJ
::User
::Relations
::Subscribers
13 use HTTP
::Date
qw( str2time );
21 use LJ
::MemCacheProxy
;
22 use LJ
::RateLimit
qw();
23 use LJ
::RelationService
;
27 use LJ
::User
::InfoHistory
;
28 use LJ
::User
::PropStorage
;
29 use LJ
::User
::Userlog
;
30 use LJ
::Response
::CachedTemplate
;
31 use LJ
::PersonalStats
::DB
;
34 # TODO: get rid of Class::Autouse, maybe? it's pretty useless
35 # in web context and leads to some nasty bugs otherwise, so probably
36 # the benefit is not worth it
37 use Class
::Autouse
qw(
52 LJ::Subscription::GroupSet
55 # class method to create a new account.
57 my ($class, %opts) = @_;
59 my $username = LJ
::canonical_username
($opts{user
}) or return;
61 my $cluster = $opts{cluster
} || LJ
::new_account_cluster
();
62 my $caps = $opts{caps
} || $LJ::NEWUSER_CAPS
;
63 my $journaltype = $opts{journaltype
} || "P";
65 # non-clustered accounts aren't supported anymore
66 return unless $cluster;
68 my $dbh = LJ
::get_db_writer
();
70 $dbh->do("INSERT INTO user (user, clusterid, dversion, caps, journaltype) " .
71 "VALUES (?, ?, ?, ?, ?)", undef,
72 $username, $cluster, $LJ::MAX_DVERSION
, $caps, $journaltype);
75 my $userid = $dbh->{'mysql_insertid'};
76 return unless $userid;
78 $dbh->do("INSERT INTO useridmap (userid, user) VALUES (?, ?)",
79 undef, $userid, $username);
80 $dbh->do("INSERT INTO userusage (userid, timecreate) VALUES (?, NOW())",
83 my $u = LJ
::load_userid
($userid, "force");
85 my $status = $opts{status
} || ($LJ::EVERYONE_VALID ?
'A' : 'N');
86 my $name = $opts{name
} || $username;
87 my $bdate = $opts{bdate
} || "0000-00-00";
88 my $email = $opts{email
} || "";
89 my $password = $opts{password
} || "";
91 LJ
::update_user
($u, { 'status' => $status, 'name' => $name, 'bdate' => $bdate,
92 'email' => $email, 'password' => $password, %LJ::USER_INIT
});
94 my $remote = LJ
::get_remote
();
95 LJ
::User
::UserlogRecord
::AccountCreate
->create( $u, 'remote' => $remote );
97 while (my ($name, $val) = each %LJ::USERPROP_INIT
) {
98 $u->set_prop($name, $val);
101 if ($opts{extra_props
}) {
102 while (my ($key, $value) = each( %{$opts{extra_props
}} )) {
103 $u->set_prop( $key => $value );
107 if ($opts{status_history
}) {
108 my $system = LJ
::load_user
("system");
110 while (my ($key, $value) = each( %{$opts{status_history
}} )) {
111 LJ
::statushistory_add
($u, $system, $key, $value);
116 LJ
::run_hooks
("post_create", {
120 'news' => $opts{'get_ljnews'},
121 'email' => $opts{'email'},
127 sub create_personal
{
128 my ($class, %opts) = @_;
130 my $u = LJ
::User
->create(%opts) or return;
132 $u->set_prop("init_bdate", $opts{bdate
});
133 while (my ($name, $val) = each %LJ::USERPROP_INIT_PERSONAL
) {
134 $u->set_prop($name, $val);
137 # so birthday notifications get sent
138 $u->set_next_birthday;
140 # Set the default style
141 LJ
::run_hook
('set_default_style', $u);
143 if (length $opts{inviter
}) {
144 if ($opts{inviter
} =~ /^partner:/) {
145 LJ
::run_hook
('partners_registration_done', $u, $opts{inviter
});
147 # store inviter, if there was one
148 my $inviter = LJ
::load_user
($opts{inviter
});
150 LJ
::set_rel
($u, $inviter, "I");
151 LJ
::statushistory_add
($u, $inviter, 'create_from_invite', "Created new account.");
154 $u->add_friend($inviter);
155 LJ
::Event
::InvitedFriendJoins
->new($inviter, $u)->fire;
159 # if we have initial friends for new accounts, add them.
160 my @initial_friends = LJ
::SUP
->is_sup_enabled($u)
161 ?
(LJ
::GeoLocation
->ip_country eq 'UA' ?
@LJ::UA_INITIAL_FRIENDS
: @LJ::SUP_INITIAL_FRIENDS
)
162 : @LJ::INITIAL_FRIENDS
;
163 foreach my $friend (@initial_friends) {
164 my $friendid = LJ
::get_userid
($friend);
165 LJ
::add_friend
($u->id, $friendid) if $friendid;
168 # populate some default friends groups
172 'mode' => 'editfriendgroups',
174 'ver' => $LJ::PROTOCOL_VER
,
175 'efg_set_1_name' => 'Family',
176 'efg_set_2_name' => 'Local Friends',
177 'efg_set_3_name' => 'Online Friends',
178 'efg_set_4_name' => 'School',
179 'efg_set_5_name' => 'Work',
180 'efg_set_6_name' => 'Mobile View',
181 }, \
%res, { 'u' => $u, 'noauth' => 1, }
184 $u->set_prop("newpost_minsecurity", "friends") if $u->is_child;
186 # now flag as underage (and set O to mean was old or Y to mean was young)
187 $u->underage(1, $opts{ofage
} ?
'O' : 'Y', 'account creation') if $opts{underage
};
189 # For settings that are to be set explicitly
190 # on create, with more private settings for non-adults
191 if ($u->underage || $u->is_child) {
192 $u->set_prop("opt_findbyemail", 'N');
194 $u->set_prop("opt_findbyemail", 'H');
200 sub create_community
{
201 my ($class, %opts) = @_;
203 $opts{journaltype
} = "C";
204 my $u = LJ
::User
->create(%opts) or return;
206 $u->set_prop("spamprotection" => 'Y');
207 $u->set_prop("nonmember_posting", $opts{nonmember_posting
}+0);
208 $u->set_prop("moderated", $opts{moderated
});
209 $u->set_prop("adult_content", $opts{journal_adult_settings
}) if LJ
::is_enabled
("content_flag");
211 my $remote = $opts{'owner'} || LJ
::get_remote
();
213 die "No remote user!\n" unless $remote;
215 LJ
::set_rel
($u, $remote, "A"); # maintainer
217 LJ
::set_rel
($u, $remote, "S"); # supermaintainer
219 LJ
::User
::UserlogRecord
::SetOwner
->create( $u,
220 'ownerid' => $remote->userid, 'remote' => $remote );
222 LJ
::statushistory_add
($u, $remote, 'set_owner', "Set supermaintainer on created time as " . $remote->{user
});
224 LJ
::set_rel
($u, $remote, "M") if $opts{moderated
} =~ /^[AF]$/; # moderator if moderated
225 LJ
::join_community
($remote, $u, 1, 1); # member
227 LJ
::set_comm_settings
($u, $remote, { membership
=> $opts{membership
},
228 postlevel
=> $opts{postlevel
} });
230 my $theme = LJ
::S2Theme
->load_by_uniq($LJ::DEFAULT_THEME_COMMUNITY
);
231 LJ
::Customize
->apply_theme($u, $theme) if $theme;
233 $remote->clear_cache_friends($u);
238 sub create_syndicated
{
239 my ($class, %opts) = @_;
241 return unless $opts{feedurl
};
243 $opts{caps
} = $LJ::SYND_CAPS
;
244 $opts{cluster
} = $LJ::SYND_CLUSTER
;
245 $opts{journaltype
} = "Y";
247 my $u = LJ
::User
->create(%opts) or return;
249 my $dbh = LJ
::get_db_writer
();
250 $dbh->do("INSERT INTO syndicated (userid, synurl, checknext) VALUES (?, ?, NOW())",
251 undef, $u->id, $opts{feedurl
});
252 die $dbh->errstr if $dbh->err;
254 my $remote = $opts{'creator'} || LJ
::get_remote
();
255 LJ
::statushistory_add
($remote, $u, "synd_create", "acct: " . $u->user);
260 # retrieve hash of basic syndicated info
264 return unless $u->is_syndicated;
265 my $memkey = [$u->{'userid'}, "synd:$u->{'userid'}"];
268 $synd = LJ
::MemCache
::get
($memkey);
270 my $dbr = LJ
::get_db_reader
();
272 $synd = $dbr->selectrow_hashref("SELECT * FROM syndicated WHERE userid=$u->{'userid'}");
273 LJ
::MemCache
::set
($memkey, $synd, 60 * 120) if $synd;
279 sub is_protected_username
{
280 my ($class, $username) = @_;
281 foreach my $re (@LJ::PROTECTED_USERNAMES
) {
282 return 1 if $username =~ /$re/;
288 my ($class, $row) = @_;
289 my $u = bless $row, $class;
295 my ($class, $url) = @_;
297 my $username = $class->username_from_url($url);
299 return LJ
::load_user
($username);
302 # domains like 'http://news.independent.livejournal.com' or 'http://some.site.domain.com'
303 if ($url =~ m!^http://([\w.-]+)/?$!) {
304 return $class->new_from_external_domain($1);
310 ## Input: domain (e.g. 'news.independent.livejournal.com' or 'some.site.domain.com')
311 ## Output: LJ::User object or undef
312 sub new_from_external_domain
{
313 my( $class, $host ) = @_;
317 $host =~ s/_/-/g if ($host =~ /(.*?)\.?(?:xn--80adlbbiisqhy9a|xn--f1aa)\.xn--p1ai/);
319 if (my $user = $LJ::DOMAIN_JOURNALS_REVERSE
{$host}) {
320 return LJ
::load_user
($user);
323 my $key = "domain:$host";
324 my $userid = LJ
::MemCache
::get
($key);
326 unless (defined $userid) {
327 my $db = LJ
::get_db_reader
();
328 ($userid) = $db->selectrow_array(qq{SELECT userid FROM domains WHERE domain
=?
}, undef, $host);
329 $userid ||= 0; ## we do cache negative results - if no user for such domain, set userid=0
330 my $expire = time() + 1800;
331 LJ
::MemCache
::set
($key, $userid, $expire);
334 my $u = LJ
::load_userid
($userid);
339 sub username_from_url
{
340 my ($class, $url) = @_;
342 # /users, /community, or /~
343 if ($url =~ m!^\Q$LJ::SITEROOT\E/(?:users/|community/|~)([\w-]+)/?!) {
347 # subdomains that hold a bunch of users (eg, users.siteroot.com/username/)
348 if ($url =~ m!^http://(\w+)\.\Q$LJ::USER_DOMAIN\E/([\w_-]+)/?!) {
349 if ( $LJ::IS_USER_DOMAIN
->{$1} ) {
355 my $user_uri_regex = qr{
356 # it all starts with a protocol:
362 # literal dot separating it from our domain space:
368 # either it ends right there, or there is a forward slash character
369 # followed by something (we don't care what):
372 }xo
; # $LJ::USER_DOMAIN is basically a constant, let Perl know that
374 if ( $LJ::USER_DOMAIN
&& $url =~ $user_uri_regex ) {
380 # returns LJ::User class of a random user, undef if we couldn't get one
381 # my $random_u = LJ::User->load_random_user();
382 sub load_random_user
{
385 # get a random database, but make sure to try them all if one is down or not
386 # responding or similar
388 foreach (List
::Util
::shuffle
(@LJ::CLUSTERS
)) {
389 $dbcr = LJ
::get_cluster_reader
($_);
392 die "Unable to get database cluster reader handle\n" unless $dbcr;
394 # get a selection of users around a random time
395 my $when = time() - int(rand($LJ::RANDOM_USER_PERIOD
* 24 * 60 * 60)); # days -> seconds
396 my $uids = $dbcr->selectcol_arrayref(qq{
397 SELECT userid FROM random_user_set
398 WHERE posttime
> $when
402 die "Failed to execute query: " . $dbcr->errstr . "\n" if $dbcr->err;
403 return undef unless $uids && @
$uids;
405 # try the users we got
406 foreach my $uid (@
$uids) {
407 my $u = LJ
::load_userid
($uid)
410 # situational checks to ensure this user is a good one to show
411 next unless $u->is_person; # people accounts only
412 next unless $u->is_visible; # no suspended/deleted/etc users
413 next if $u->prop('latest_optout'); # they have chosen to be excluded
415 # they've passed the checks, return this user
423 # class method. returns remote (logged in) user object. or undef if
424 # no session is active.
426 my ($class, $opts) = @_;
427 return LJ
::get_remote
($opts);
430 # class method. set the remote user ($u or undef) for the duration of this request.
431 # once set, it'll never be reloaded, unless "unset_remote" is called to forget it.
434 my ($class, $remote) = @_;
435 $LJ::CACHED_REMOTE
= 1;
436 $LJ::CACHE_REMOTE
= $remote;
440 # class method. forgets the cached remote user.
444 $LJ::CACHED_REMOTE
= 0;
445 $LJ::CACHE_REMOTE
= undef;
451 LJ
::load_user_props
($u, @_);
454 sub prefetch_subscriptions
{
456 my @subs = LJ
::Subscription
->find($u, prefetch
=> 1);
457 $u->{__subscriptions
} = \
@subs;
462 return LJ
::get_cap
($u, "readonly");
465 # returns self (the $u object which can be used for $u->do) if
466 # user is writable, else 0
469 my $dbcm = $u->{'_dbcm'} || LJ
::get_cluster_master
($u);
473 sub database_cluster_up
{
476 my $master = eval { LJ
::get_cluster_master
($u) };
479 my $username = $u->username;
480 warn "error getting a cluster handle for $username: $@";
483 return $master ?
1 : 0;
488 return undef unless $u->{defaultpicid
};
489 return LJ
::Userpic
->new($u, $u->{defaultpicid
});
492 # returns a true value if the user is underage; or if you give it an argument,
493 # will turn on/off that user's underage status. can also take a second argument
494 # when you're setting the flag to also update the underage_status userprop
495 # which is used to record if a user was ever marked as underage.
497 # has no bearing if this isn't on
498 return undef unless LJ
::class_bit
("underage");
501 my $ret_zero = 0; # no need to return zero
503 # now get the args and continue
504 my $u = shift(@args);
505 unless (@args) { # we are getter
506 my $young = LJ
::get_cap
($u, 'underage');
507 return unless $young; # cap is clear -> return false
509 # here cap is set -> may be we will return it, may be we will update
510 return 1 unless $u->underage_status eq 'Y'; # only "provided birthdate" may be updated, "manual" and "cookie" must be preserved
511 return 1 if $u->init_age < 14; # yes, user is young -> return true
513 # here cap is set and user is not young now -> will update
514 @args = (0, undef, 'auto clear based on init_age()');
515 # fall to setter code
519 # now set it on or off
520 my $on = shift(@args) ?
1 : 0;
522 $u->add_to_class("underage");
524 $u->remove_from_class("underage");
527 # now set their status flag if one was sent
528 my $status = shift(@args);
529 if ($status || $on) {
530 # by default, just records if user was ever underage ("Y")
531 $u->underage_status($status || 'Y');
534 # add to statushistory
535 if (my $shwhen = shift(@args)) {
536 my $text = $on ?
"marked" : "unmarked";
537 my $status = $u->underage_status;
538 LJ
::statushistory_add
($u, undef, "coppa", "$text; status=$status; when=$shwhen");
541 # now fire off any hooks that are available
542 LJ
::run_hooks
('set_underage', {
545 status
=> $u->underage_status,
548 # return true if no failures
549 return $ret_zero ?
0 : 1;
551 *is_underage
= \
&underage
;
553 # return true if we know user is a minor (< 18)
556 my $age = $self->best_guess_age;
557 return 0 unless $age;
558 return 1 if ($age < 18);
562 # return true if we know user is a child (< 14)
565 my $age = $self->best_guess_age;
567 return 0 unless $age;
568 return 1 if ($age < 14);
572 # get/set the gizmo account of a user
576 # parse out their account information
577 my $acct = $u->prop( 'gizmo' );
578 my ($validated, $gizmo);
579 if ($acct && $acct =~ /^([01]);(.+)$/) {
580 ($validated, $gizmo) = ($1, $2);
583 # setting the account
584 # all account sets are initially unvalidated
586 my $newgizmo = shift;
587 $u->set_prop( 'gizmo' => "0;$newgizmo" );
589 # purge old memcache keys
590 LJ
::MemCache
::delete( "gizmo-ljmap:$gizmo" );
593 # return the information (either account + validation or just account)
594 return wantarray ?
($gizmo, $validated) : $gizmo unless @_;
597 # get/set the validated status of a user's gizmo account
598 sub gizmo_account_validated
{
601 my ($gizmo, $validated) = $u->gizmo_account;
603 if ( defined $_[0] && $_[0] =~ /[01]/) {
604 $u->set_prop( 'gizmo' => "$_[0];$gizmo" );
611 # return or set the underage status userprop
612 sub underage_status
{
613 return undef unless LJ
::class_bit
("underage");
617 # return if they aren't setting it
619 return $u->prop("underage_status");
622 # set and return what it got set to
623 $u->set_prop('underage_status', shift());
624 return $u->{underage_status
};
627 # returns a true value if user has a reserved 'ext' name.
630 return $u->{user
} =~ /^ext_/;
633 # this is for debugging/special uses where you need to instruct
634 # a user object on what database handle to use. returns the
635 # handle that you gave it.
638 return $u->{'_dbcm'} = shift;
643 return "Database handle unavailable [user: " . $u->user . "; cluster: " . $u->clusterid . ", errstr: $DBI::errstr]";
648 return $LJ::CACHE_CLUSTER_IS_INNO
{$u->{clusterid
}}
649 if defined $LJ::CACHE_CLUSTER_IS_INNO
{$u->{clusterid
}};
651 my $dbcm = $u->{'_dbcm'} || LJ
::get_cluster_master
($u)
652 or croak
$u->nodb_err;
653 my (undef, $ctable) = $dbcm->selectrow_array("SHOW CREATE TABLE log2");
654 die "Failed to auto-discover database type for cluster \#$u->{clusterid}: [$ctable]"
655 unless $ctable =~ /^CREATE TABLE/;
657 my $is_inno = ($ctable =~ /=InnoDB/i ?
1 : 0);
658 return $LJ::CACHE_CLUSTER_IS_INNO
{$u->{clusterid
}} = $is_inno;
663 return 1 unless $u->is_innodb;
665 my $dbcm = $u->{'_dbcm'} || LJ
::get_cluster_master
($u)
666 or croak
$u->nodb_err;
668 my $rv = $dbcm->begin_work;
669 if ($u->{_dberr
} = $dbcm->err) {
670 $u->{_dberrstr
} = $dbcm->errstr;
677 return 1 unless $u->is_innodb;
679 my $dbcm = $u->{'_dbcm'} || LJ
::get_cluster_master
($u)
680 or croak
$u->nodb_err;
682 my $rv = $dbcm->commit;
683 if ($u->{_dberr
} = $dbcm->err) {
684 $u->{_dberrstr
} = $dbcm->errstr;
691 return 1 unless $u->is_innodb;
693 my $dbcm = $u->{'_dbcm'} || LJ
::get_cluster_master
($u)
694 or croak
$u->nodb_err;
696 my $rv = $dbcm->rollback;
697 if ($u->{_dberr
} = $dbcm->err) {
698 $u->{_dberrstr
} = $dbcm->errstr;
703 # get an $sth from the writer
707 my $dbcm = $u->{'_dbcm'} || LJ
::get_cluster_master
($u)
708 or croak
$u->nodb_err;
710 my $rv = $dbcm->prepare(@_);
711 if ($u->{_dberr
} = $dbcm->err) {
712 $u->{_dberrstr
} = $dbcm->errstr;
717 # $u->do("UPDATE foo SET key=?", undef, $val);
722 my $uid = $u->{userid
}+0
723 or croak
"Database update called on null user object";
725 my $dbcm = $u->{'_dbcm'} || LJ
::get_cluster_master
($u)
726 or croak
$u->nodb_err;
728 $query =~ s!^(\s*\w+\s+)!$1/* uid=$uid */ !;
730 my $rv = $dbcm->do($query, @_);
731 if ($u->{_dberr
} = $dbcm->err) {
732 $u->{_dberrstr
} = $dbcm->errstr;
735 $u->{_mysql_insertid
} = $dbcm->{'mysql_insertid'} if $dbcm->{'mysql_insertid'};
740 sub selectrow_array
{
742 my $dbcm = $u->{'_dbcm'} || LJ
::get_cluster_master
($u)
743 or croak
$u->nodb_err;
746 if ($u->{_dberr
} = $dbcm->err) {
747 $u->{_dberrstr
} = $dbcm->errstr;
752 my @rv = $dbcm->selectrow_array(@_);
757 my $rv = $dbcm->selectrow_array(@_);
762 sub selectcol_arrayref
{
764 my $dbcm = $u->{'_dbcm'} || LJ
::get_cluster_master
($u)
765 or croak
$u->nodb_err;
767 my $rv = $dbcm->selectcol_arrayref(@_);
769 if ($u->{_dberr
} = $dbcm->err) {
770 $u->{_dberrstr
} = $dbcm->errstr;
777 sub selectall_hashref
{
779 my $dbcm = $u->{'_dbcm'} || LJ
::get_cluster_master
($u)
780 or croak
$u->nodb_err;
782 my $rv = $dbcm->selectall_hashref(@_);
784 if ($u->{_dberr
} = $dbcm->err) {
785 $u->{_dberrstr
} = $dbcm->errstr;
791 sub selectall_arrayref
{
793 my $dbcm = $u->{'_dbcm'} || LJ
::get_cluster_master
($u)
794 or croak
$u->nodb_err;
796 my $rv = $dbcm->selectall_arrayref(@_);
798 if ($u->{_dberr
} = $dbcm->err) {
799 $u->{_dberrstr
} = $dbcm->errstr;
805 sub selectrow_hashref
{
807 my $dbcm = $u->{'_dbcm'} || LJ
::get_cluster_master
($u)
808 or croak
$u->nodb_err;
810 my $rv = $dbcm->selectrow_hashref(@_);
812 if ($u->{_dberr
} = $dbcm->err) {
813 $u->{_dberrstr
} = $dbcm->errstr;
826 return $u->{_dberrstr
};
833 my $dbcm = $u->{'_dbcm'} || LJ
::get_cluster_master
($u)
834 or croak
$u->nodb_err;
836 return $dbcm->quote($text);
841 if ($u->isa("LJ::User")) {
842 return $u->{_mysql_insertid
};
843 } elsif (LJ
::isdb
($u)) {
845 return $db->{'mysql_insertid'};
847 die "Unknown object '$u' being passed to LJ::User::mysql_insertid.";
852 # name: LJ::User::dudata_set
854 # des: Record or delete disk usage data for a journal.
855 # args: u, area, areaid, bytes
856 # des-area: One character: "L" for log, "T" for talk, "B" for bio, "P" for pic.
857 # des-areaid: Unique ID within $area, or '0' if area has no ids (like bio)
858 # des-bytes: Number of bytes item takes up. Or 0 to delete record.
862 my ($u, $area, $areaid, $bytes) = @_;
863 $bytes += 0; $areaid += 0;
865 $u->do("REPLACE INTO dudata (userid, area, areaid, bytes) ".
866 "VALUES (?, ?, $areaid, $bytes)", undef,
867 $u->{userid
}, $area);
869 $u->do("DELETE FROM dudata WHERE userid=? AND ".
870 "area=? AND areaid=$areaid", undef,
871 $u->{userid
}, $area);
876 sub make_login_session
{
877 my ($u, $exptype, $ipfixed) = @_;
878 $exptype ||= 'short';
881 eval { LJ
::Request
->notes('ljuser' => $u->{'user'}); };
883 # create session and log user in
885 'exptype' => $exptype,
886 'ipfixed' => $ipfixed,
889 my $sess = LJ
::Session
->create($u, %$sess_opts);
890 $sess->update_master_cookie;
892 LJ
::User
->set_remote($u);
894 # add a uniqmap row if we don't have one already
895 my $uniq = LJ
::UniqCookie
->current_uniq;
896 LJ
::UniqCookie
->save_mapping($uniq => $u);
898 # restore scheme and language
899 my $bl = LJ
::Lang
::get_lang
($u->prop('browselang'));
900 BML
::set_language
($bl->{'lncode'}) if $bl;
902 # don't set/force the scheme for this page if we're on SSL.
903 # we'll pick it up from cookies on subsequent pageloads
904 # but if their scheme doesn't have an SSL equivalent,
905 # then the post-login page throws security errors
906 BML
::set_scheme
($u->prop('schemepref'))
911 LJ
::run_hooks
("login_add_opts", {
916 my $sopts = @sopts ?
":" . join('', map { ".$_" } @sopts) : "";
917 $sess->flags($sopts);
919 my $etime = $sess->expiration_time;
920 LJ
::run_hooks
("post_login", {
923 "expiretime" => $etime,
926 # activity for cluster usage tracking
927 LJ
::mark_user_active
($u, 'login');
929 # activity for global account number tracking
930 $u->note_activity('A');
935 # We have about 10 million different forms of activity tracking.
936 # This one is for tracking types of user activity on a per-hour basis
938 # Example: $u had login activity during this out
941 my ($u, $atype) = @_;
942 croak
("invalid user") unless ref $u;
943 croak
("invalid activity type") unless $atype;
945 # If we have no memcache servers, this function would trigger
946 # an insert for every logged-in pageview. Probably not a problem
947 # load-wise if the site isn't using memcache anyway, but if the
948 # site is that small active user tracking probably doesn't matter
950 return undef unless @LJ::MEMCACHE_SERVERS
;
952 # Also disable via config flag
953 return undef if $LJ::DISABLED
{active_user_tracking
};
956 my $uid = $u->{userid
}; # yep, lazy typist w/ rsi
957 my $explen = 1800; # 30 min, same for all types now
959 my $memkey = [ $uid, "uactive:$atype:$uid" ];
961 # get activity key from memcache
962 my $atime = LJ
::MemCacheProxy
::get
($memkey);
964 # nothing to do if we got an $atime within the last hour
965 return 1 if $atime && $atime > $now - $explen;
967 # key didn't exist due to expiration, or was too old,
968 # means we need to make an activity entry for the user
969 my ($hr, $dy, $mo, $yr) = (gmtime($now))[2..5];
970 $yr += 1900; # offset from 1900
973 # delayed insert in case the table is currently locked due to an analysis
974 # running. this way the apache won't be tied up waiting
975 $u->do("INSERT IGNORE INTO active_user " .
976 "SET year=?, month=?, day=?, hour=?, userid=?, type=?",
977 undef, $yr, $mo, $dy, $hr, $uid, $atype);
979 # set a new memcache key good for $explen
980 LJ
::MemCacheProxy
::set
($memkey, $now, $explen);
985 sub note_transition
{
986 my ($u, $what, $from, $to) = @_;
987 croak
"invalid user object" unless LJ
::isu
($u);
989 return 1 if $LJ::DISABLED
{user_transitions
};
991 # we don't want to insert if the requested transition is already
992 # the last noted one for this user... in that case there has been
993 # no transition at all
994 my $last = $u->last_transition($what);
996 $last->{before
} eq $from &&
997 $last->{after
} eq $to;
999 my $dbh = LJ
::get_db_writer
()
1000 or die "unable to contact global db master";
1002 # bleh, need backticks on the 'before' and 'after' columns since those
1003 # are MySQL reserved words
1004 $dbh->do("INSERT INTO usertrans " .
1005 "SET userid=?, time=UNIX_TIMESTAMP(), what=?, " .
1006 "`before`=?, `after`=?",
1007 undef, $u->{userid
}, $what, $from, $to);
1008 die $dbh->errstr if $dbh->err;
1010 # also log account changes to statushistory
1011 my $remote = LJ
::get_remote
();
1012 LJ
::statushistory_add
($u, $remote, "account_level_change", "$from -> $to")
1013 if $what eq "account";
1018 sub transition_list
{
1019 my ($u, $what) = @_;
1020 croak
"invalid user object" unless LJ
::isu
($u);
1022 my $dbh = LJ
::get_db_writer
()
1023 or die "unable to contact global db master";
1025 # FIXME: return list of transition object singleton instances?
1027 my $sth = $dbh->prepare("SELECT time, `before`, `after` " .
1028 "FROM usertrans WHERE userid=? AND what=?");
1029 $sth->execute($u->{userid
}, $what);
1030 die $dbh->errstr if $dbh->err;
1032 while (my $trans = $sth->fetchrow_hashref) {
1034 # fill in a couple of properties here rather than
1035 # sending over the network from db
1036 $trans->{userid
} = $u->{userid
};
1037 $trans->{what
} = $what;
1042 return wantarray() ?
@list : \
@list;
1045 sub last_transition
{
1046 my ($u, $what) = @_;
1047 croak
"invalid user object" unless LJ
::isu
($u);
1049 $u->transition_list($what)->[-1];
1055 return undef unless $u;
1057 unless (-f
"$LJ::HOME/htdocs/inc/legal-tos") {
1058 $$err = "TOS include file could not be found";
1063 open (TOS
, "$LJ::HOME/htdocs/inc/legal-tos");
1064 while ((!$rev) && (my $line = <TOS
>)) {
1065 my $rcstag = "Revision";
1066 if ($line =~ /\$$rcstag:\s*(\S+)\s*\$/) {
1072 # if the required version of the tos is not available, error!
1073 my $rev_req = $LJ::REQUIRED_TOS
{rev
};
1074 if ($rev_req > 0 && $rev ne $rev_req) {
1075 $$err = "Required Terms of Service revision is $rev_req, but system version is $rev.";
1079 my $newval = join(', ', time(), $rev);
1080 my $rv = $u->set_prop("legal_tosagree", $newval);
1082 # set in $u object for callers later
1083 ## hm, doesn't "set_prop" do it?
1084 $u->{legal_tosagree
} = $newval;
1087 $$err = "Internal error: can't set prop legal_tosagree";
1092 sub tosagree_verify
{
1094 return 1 unless $LJ::TOS_CHECK
;
1096 my $rev_req = $LJ::REQUIRED_TOS
{rev
};
1097 return 1 unless $rev_req > 0;
1099 my $rev_cur = (split(/\s*,\s*/, $u->prop("legal_tosagree")))[1];
1100 return $rev_cur eq $rev_req;
1103 # my $sess = $u->session (returns current session)
1104 # my $sess = $u->session($sessid) (returns given session id for user)
1107 my ($u, $sessid) = @_;
1108 $sessid = $sessid + 0;
1109 return $u->{_session
} unless $sessid; # should be undef, or LJ::Session hashref
1110 return LJ
::Session
->instance($u, $sessid);
1113 # in list context, returns an array of LJ::Session objects which are active.
1114 # in scalar context, returns hashref of sessid -> LJ::Session, which are active
1117 my @sessions = LJ
::Session
->active_sessions($u);
1118 return @sessions if wantarray;
1120 foreach my $s (@sessions) {
1121 $ret->{$s->id} = $s;
1128 if (my $sess = $u->session) {
1136 LJ
::Session
->destroy_all_sessions($u)
1137 or die "Failed to logout all";
1141 sub _logout_common
{
1143 LJ
::Session
->clear_master_cookie;
1144 LJ
::User
->set_remote(undef);
1145 delete $BML::COOKIE
{'BMLschemepref'};
1146 delete $BML::COOKIE
{'cart'};
1147 eval { BML
::set_scheme
(undef); };
1148 LJ
::run_hooks
("user_logout");
1151 # returns a new LJ::Session object, or undef on failure
1154 my ($u, %opts) = @_;
1155 return LJ
::Session
->create($u, %opts);
1158 # $u->kill_session(@sessids)
1161 return LJ
::Session
->destroy_sessions($u, @_);
1164 sub kill_all_sessions
{
1168 LJ
::Session
->destroy_all_sessions($u)
1171 # forget this user, if we knew they were logged in
1172 if ($LJ::CACHE_REMOTE
&& $LJ::CACHE_REMOTE
->{userid
} == $u->{userid
}) {
1173 LJ
::Session
->clear_master_cookie;
1174 LJ
::User
->set_remote(undef);
1183 my $sess = $u->session
1188 if ($LJ::CACHE_REMOTE
&& $LJ::CACHE_REMOTE
->{userid
} == $u->{userid
}) {
1189 LJ
::Session
->clear_master_cookie;
1190 LJ
::User
->set_remote(undef);
1197 # name: LJ::User::mogfs_userpic_key
1199 # des: Make a mogilefs key for the given pic for the user.
1201 # des-pic: Either the userpic hash or the picid of the userpic.
1204 sub mogfs_userpic_key
{
1205 my $self = shift or return undef;
1206 my $pic = shift or croak
"missing required arg: userpic";
1208 my $picid = ref $pic ?
$pic->{picid
} : $pic+0;
1209 return "up:$self->{userid}:$picid";
1212 # all reads/writes to talk2 must be done inside a lock, so there's
1213 # no race conditions between reading from db and putting in memcache.
1214 # can't do a db write in between those 2 steps. the talk2 -> memcache
1215 # is elsewhere (talklib.pl), but this $dbh->do wrapper is provided
1216 # here because non-talklib things modify the talk2 table, and it's
1217 # nice to centralize the locking rules.
1219 # return value is return of $dbh->do. $errref scalar ref is optional, and
1220 # if set, gets value of $dbh->errstr
1222 # write: (LJ::talk2_do)
1224 # update/insert into talk2
1228 # read: (LJ::Talk::get_talk_data)
1236 #my ($u, $nodetype, $nodeid, $errref, $sql, @args) = @_;
1239 my $nodetype = $args{nodetype
};
1240 my $nodeid = $args{nodeid
};
1241 my $errref = $args{errref
};
1242 my $sql = $args{sql
};
1243 my @bindings = ref $args{bindings
} eq 'ARRAY' ? @
{$args{bindings
}} : ();
1244 my $flush_cache = exists $args{flush_cache
} ?
$args{flush_cache
} : 1;
1247 return undef unless $nodetype =~ /^\w$/;
1248 return undef unless $nodeid =~ /^\d+$/;
1249 return undef unless $u->writer;
1251 my $dbcm = $u->writer;
1253 my $memkey = [$u->{'userid'}, "talk2:$u->{'userid'}:$nodetype:$nodeid"];
1254 my $lockkey = $memkey->[1];
1256 $dbcm->selectrow_array("SELECT GET_LOCK(?,10)", undef, $lockkey);
1257 my $ret = $u->do($sql, undef, @bindings);
1258 $$errref = $u->errstr if ref $errref && $u->err;
1259 $dbcm->selectrow_array("SELECT RELEASE_LOCK(?)", undef, $lockkey);
1262 LJ
::MemCache
::delete($memkey, 0) if int($ret) and $flush_cache;
1271 # see comments for talk2_do
1274 my ($u, $errref, $sql, @args) = @_;
1275 return undef unless $u->writer;
1277 my $dbcm = $u->writer;
1279 my $memkey = [$u->{'userid'}, "log2lt:$u->{'userid'}"];
1280 my $lockkey = $memkey->[1];
1282 $dbcm->selectrow_array("SELECT GET_LOCK(?,10)", undef, $lockkey);
1283 my $ret = $u->do($sql, undef, @args);
1284 $$errref = $u->errstr if ref $errref && $u->err;
1285 $dbcm->selectrow_array("SELECT RELEASE_LOCK(?)", undef, $lockkey);
1287 LJ
::MemCache
::delete($memkey, 0) if int($ret);
1296 if ($u->is_identity && !$u->prop('url')) {
1297 $u->set_prop( 'url' => $u->identity->url($u) );
1300 $url ||= $u->prop('url');
1303 $url = "http://$url" unless $url =~ m!^https?://!;
1308 # there are two procedures for finding an LJ::Identity object for the given
1309 # user, the difference being that identity() checks for journaltype eq 'I'
1310 # while find_identity() does not. this is done this way for backwards
1311 # compatibility: some parts of LJ code use identity() to check what
1312 # is_identity() checks (suboptimal, yes, but it works that way)
1317 return $u->{'_identity'} if $u->{'_identity'};
1319 my $memkey = [$u->{userid
}, "ident:$u->{userid}"];
1320 my $ident = LJ
::MemCache
::get
($memkey);
1322 my $i = LJ
::Identity
->new(
1323 typeid
=> $ident->[0],
1324 value
=> $ident->[1],
1327 return $u->{_identity
} = $i;
1330 my $dbh = LJ
::get_db_writer
();
1331 $ident = $dbh->selectrow_arrayref("SELECT idtype, identity FROM identitymap ".
1332 "WHERE userid=? LIMIT 1", undef, $u->{userid
});
1334 LJ
::MemCache
::set
($memkey, $ident);
1335 my $i = LJ
::Identity
->new(
1336 typeid
=> $ident->[0],
1337 value
=> $ident->[1],
1348 return unless $u->is_identity;
1349 return $u->find_identity;
1352 # returns a URL if account is an OpenID identity. undef otherwise.
1353 sub openid_identity
{
1355 my $ident = $u->identity;
1356 return undef unless $ident && $ident->typeid == 0;
1357 return $ident->value;
1360 # returns username or identity display name
1364 my $display_name_override;
1365 LJ
::run_hooks
( 'override_display_name', $u, \
$display_name_override );
1366 return $display_name_override if $display_name_override;
1368 return $u->username unless $u->is_identity;
1370 my $id = $u->identity;
1371 return "[ERR:unknown_identity]" unless $id;
1372 return LJ
::ehtml
( $id->display_name($u) );
1375 sub ljuser_display
{
1376 my ($u, $opts) = @_;
1377 return LJ
::ljuser
($u, $opts);
1380 # class function - load an identity user, but only if they're already known to us
1381 sub load_existing_identity_user
{
1382 my ($type, $ident) = @_;
1384 my $dbh = LJ
::get_db_reader
();
1385 my $uid = $dbh->selectrow_array("SELECT userid FROM identitymap WHERE idtype=? AND identity=?",
1386 undef, $type, $ident);
1387 return $uid ? LJ
::load_userid
($uid) : undef;
1390 # class function - load an identity user, and if we've never seen them before create a user account for them
1391 sub load_identity_user
{
1392 my ($type, $ident, $extra, $created_ref) = @_;
1394 my $u = load_existing_identity_user
($type, $ident);
1396 # If the user is marked as expunged, move identity mapping aside
1397 # and continue to create new account.
1398 # Otherwise return user if it exists.
1400 if ($u->is_expunged) {
1401 return undef unless ($u->rename_identity);
1407 # increment ext_ counter until we successfully create an LJ
1408 # account. hard cap it at 10 tries. (arbitrary, but we really
1409 # shouldn't have *any* failures here, let alone 10 in a row)
1410 my $dbh = LJ
::get_db_writer
();
1414 my $extuser = 'ext_' . LJ
::alloc_global_counter
('E');
1416 $uid = LJ
::create_account
({
1424 select undef, undef, undef, .10; # lets not thrash over this
1426 return undef unless $uid &&
1427 $dbh->do("INSERT INTO identitymap (idtype, identity, userid) VALUES (?,?,?)",
1428 undef, $type, $ident, $uid);
1430 $u = LJ
::load_userid
($uid);
1432 $u->identity->initialize_user($u, $extra);
1433 $$created_ref = 1 if $created_ref;
1435 # record create information
1436 my $remote = LJ
::get_remote
();
1437 LJ
::User
::UserlogRecord
::AccountCreate
->create( $u, 'remote' => $remote );
1442 sub remove_identity
{
1445 my $dbh = LJ
::get_db_writer
();
1446 $dbh->do( 'DELETE FROM identitymap WHERE userid=?', undef, $u->id );
1448 delete $u->{'_identity'};
1450 my $memkey = [$u->{userid
}, "ident:$u->{userid}"];
1451 LJ
::MemCache
::delete($memkey);
1454 # instance method: returns userprop for a user. currently from cache with no
1455 # way yet to force master.
1457 my ($u, $prop) = @_;
1459 # some props have accessors which do crazy things, if so they need
1460 # to be redirected from this method, which only loads raw values
1461 if ({ map { $_ => 1 }
1462 qw(opt_sharebday opt_showbday opt_showlocation opt_showmutualfriends
1463 view_control_strip show_control_strip opt_ctxpopup opt_embedplaceholders
1464 esn_inbox_default_expand opt_getting_started)
1470 return $u->raw_prop($prop);
1474 my ($u, $prop) = @_;
1475 $u->preload_props($prop) unless exists $u->{$prop};
1479 sub _lazy_migrate_infoshow
{
1481 return 1 if $LJ::DISABLED
{infoshow_migrate
};
1483 # 1) column exists, but value is migrated
1484 # 2) column has died from 'user')
1485 if ($u->{allow_infoshow
} eq ' ' || ! $u->{allow_infoshow
}) {
1486 return 1; # nothing to do
1489 my $infoval = $u->{allow_infoshow
} eq 'Y' ?
undef : 'N';
1491 # need to migrate allow_infoshow => opt_showbday
1493 foreach my $prop (qw(opt_showbday opt_showlocation)) {
1494 $u->set_prop($prop => $infoval);
1498 # setting allow_infoshow to ' ' means we've migrated it
1499 LJ
::update_user
($u, { allow_infoshow
=> ' ' })
1500 or die "unable to update user after infoshow migration";
1501 $u->{allow_infoshow
} = ' ';
1506 # opt_showbday options
1507 # F - Full Display of Birthday
1508 # D - Only Show Month/Day
1509 # Y - Only Show Year
1510 # N - Do not display
1513 # option not set = "yes", set to N = "no"
1514 $u->_lazy_migrate_infoshow;
1516 # migrate above did nothing
1517 # -- if user was already migrated in the past, we'll
1518 # fall through and show their prop value
1519 # -- if user not migrated yet, we'll synthesize a prop
1520 # value from infoshow without writing it
1521 if ($LJ::DISABLED
{infoshow_migrate
} && $u->{allow_infoshow
} ne ' ') {
1522 return $u->{allow_infoshow
} eq 'Y' ?
undef : 'N';
1524 if ($u->raw_prop('opt_showbday') =~ /^(D|F|N|Y)$/) {
1525 return $u->raw_prop('opt_showbday');
1531 # opt_sharebday options
1533 # R - Registered Users
1539 if ($u->raw_prop('opt_sharebday') =~ /^(A|F|N|R)$/) {
1540 return $u->raw_prop('opt_sharebday');
1542 return 'N' if ($u->underage || $u->is_child);
1543 return 'F' if ($u->is_minor);
1548 # opt_showljtalk options based on user setting
1549 # Y = Show the LJ Talk field on profile (default)
1550 # N = Don't show the LJ Talk field on profile
1551 sub opt_showljtalk
{
1554 # Check for valid value, or just return default of 'Y'.
1555 if ($u->raw_prop('opt_showljtalk') =~ /^(Y|N)$/) {
1556 return $u->raw_prop('opt_showljtalk');
1562 # Show LJ Talk field on profile? opt_showljtalk needs a value of 'Y'.
1565 croak
"Invalid user object passed" unless LJ
::isu
($u);
1567 # Fail if the user wants to hide the LJ Talk field on their profile,
1568 # or doesn't even have the ability to show it.
1569 return 0 if $u->opt_showljtalk eq 'N' || $LJ::DISABLED
{'ljtalk'} || !$u->is_person;
1571 # User either decided to show LJ Talk field or has left it at the default.
1572 return 1 if $u->opt_showljtalk eq 'Y';
1575 # Hide the LJ Talk field on profile? opt_showljtalk needs a value of 'N'.
1578 croak
"Invalid user object passed" unless LJ
::isu
($u);
1580 # ... The opposite of showing the field. :)
1581 return $u->show_ljtalk ?
0 : 1;
1586 croak
"Invalid user object passed" unless LJ
::isu
($u);
1588 return $u->{'user'}.'@'.$LJ::USER_DOMAIN
;
1591 sub opt_showlocation
{
1593 # option not set = "yes", set to N = "no"
1594 $u->_lazy_migrate_infoshow;
1596 # see comments for opt_showbday
1597 if ($LJ::DISABLED
{infoshow_migrate
} && $u->{allow_infoshow
} ne ' ') {
1598 return $u->{allow_infoshow
} eq 'Y' ?
undef : 'N';
1600 if ($u->raw_prop('opt_showlocation') =~ /^(N|Y|R|F)$/) {
1601 return $u->raw_prop('opt_showlocation');
1603 return 'N' if ($u->underage || $u->is_child);
1604 return 'F' if ($u->is_minor);
1609 sub opt_showcontact
{
1612 if ($u->{'allow_contactshow'} =~ /^(N|Y|R|F)$/) {
1613 return $u->{'allow_contactshow'};
1615 return 'N' if ($u->underage || $u->is_child);
1616 return 'F' if ($u->is_minor);
1621 # opt_showonlinestatus options
1622 # F = Mutual Friends
1625 sub opt_showonlinestatus
{
1628 if ($u->raw_prop('opt_showonlinestatus') =~ /^(F|N|Y)$/) {
1629 return $u->raw_prop('opt_showonlinestatus');
1635 sub can_show_location
{
1637 croak
"invalid user object passed" unless LJ
::isu
($u);
1640 my $remote = $opts{remote
} || LJ
::get_remote
();
1642 return 0 if $u->underage;
1643 return 0 if ($u->opt_showlocation eq 'N');
1644 return 0 if ($u->opt_showlocation eq 'R' && !$remote);
1645 return 0 if ($u->opt_showlocation eq 'F' && !$u->is_friend($remote));
1649 sub can_show_onlinestatus
{
1652 croak
"invalid user object passed"
1655 # Nobody can see online status of u
1656 return 0 if $u->opt_showonlinestatus eq 'N';
1657 # Everybody can see online status of u
1658 return 1 if $u->opt_showonlinestatus eq 'Y';
1659 # Only mutual friends of u can see online status
1660 if ($u->opt_showonlinestatus eq 'F') {
1661 return 0 unless $remote;
1662 return 1 if $u->is_mutual_friend($remote);
1668 # return the setting indicating how a user can be found by their email address
1669 # Y - Findable, N - Not findable, H - Findable but identity hidden
1670 sub opt_findbyemail
{
1673 if ($u->raw_prop('opt_findbyemail') =~ /^(N|Y|H)$/) {
1674 return $u->raw_prop('opt_findbyemail');
1680 # return user selected mail encoding or undef
1683 my $enc = $u->prop('mailencoding');
1685 return undef unless $enc;
1687 LJ
::load_codes
({ "encoding" => \
%LJ::CACHE_ENCODINGS
} )
1688 unless %LJ::CACHE_ENCODINGS
;
1689 return $LJ::CACHE_ENCODINGS
{$enc}
1692 # Birthday logic -- show appropriate string based on opt_showbday
1693 # This will return true if the actual birthday can be shown
1695 my ($u, %opts) = @_;
1696 croak
"invalid user object passed" unless LJ
::isu
($u);
1698 my $to_u = $opts{to
} || LJ
::get_remote
();
1700 return 0 unless $u->can_share_bday( with
=> $to_u );
1701 return 0 unless $u->opt_showbday eq 'D' || $u->opt_showbday eq 'F';
1705 # Birthday logic -- can any of the birthday info be shown
1706 # This will return true if any birthday info can be shown
1707 sub can_share_bday
{
1709 croak
"invalid user object passed" unless LJ
::isu
($u);
1712 my $with_u = $opts{with
} || LJ
::get_remote
();
1714 return 0 if ($u->opt_sharebday eq 'N');
1715 return 0 if ($u->opt_sharebday eq 'R' && !$with_u);
1716 return 0 if ($u->opt_sharebday eq 'F' && !$u->is_friend($with_u));
1721 # This will return true if the actual birth year can be shown
1722 sub can_show_bday_year
{
1724 croak
"invalid user object passed" unless LJ
::isu
($u);
1727 my $to_u = $opts{to
} || LJ
::get_remote
();
1729 return 0 unless $u->can_share_bday( with
=> $to_u );
1730 return 0 unless $u->opt_showbday eq 'Y' || $u->opt_showbday eq 'F';
1734 # This will return true if month, day, and year can be shown
1735 sub can_show_full_bday
{
1737 croak
"invalid user object passed" unless LJ
::isu
($u);
1740 my $to_u = $opts{to
} || LJ
::get_remote
();
1742 return 0 unless $u->can_share_bday( with
=> $to_u );
1743 return 0 unless $u->opt_showbday eq 'F';
1747 # This will format the birthdate based on the user prop
1749 my ($u, %opts) = @_;
1750 croak
"invalid user object passed" unless LJ
::isu
($u);
1751 return 0 if $u->underage;
1753 my $bdate = $u->{'bdate'};
1754 my ($year,$mon,$day) = split(/-/, $bdate);
1755 my $bday_string = '';
1757 if ($opts{'format'}) {
1758 if ($u->can_show_full_bday && $day > 0 && $mon > 0 && $year > 0) {
1759 $mon = LJ
::Lang
::ml
(LJ
::Lang
::month_long_genitive_langcode
($mon));
1760 $bday_string = sprintf("%2d %s %04d", $day, $mon, $year);
1761 } elsif ($u->can_show_bday && $day > 0 && $mon > 0) {
1762 $mon = LJ
::Lang
::ml
(LJ
::Lang
::month_long_genitive_langcode
($mon));
1763 $bday_string = sprintf("%2d %s", $day, $mon);
1764 } elsif ($u->can_show_bday_year && $year > 0) {
1765 $bday_string = $year;
1768 if ($u->can_show_full_bday && $day > 0 && $mon > 0 && $year > 0) {
1769 $bday_string = $bdate; #sprintf("%02d %s %04d", $day, $mon, $year);
1770 } elsif ($u->can_show_bday && $day > 0 && $mon > 0) {
1771 $bday_string = "$mon-$day"; #sprintf("%02d %s, $day, $mon");
1772 } elsif ($u->can_show_bday_year && $year > 0) {
1773 $bday_string = $year;
1776 $bday_string =~ s/^0000-//;
1777 return $bday_string;
1780 # Users age based off their profile birthdate
1783 croak
"Invalid user object" unless LJ
::isu
($u);
1784 my $age = $u->{__age
} || 0;
1785 return $age if $age;
1787 my $bdate = $u->{bdate
};
1788 return unless length $bdate;
1790 my ($year, $mon, $day) = $bdate =~ m/^(\d\d\d\d)-(\d\d)-(\d\d)/;
1791 $age = LJ
::TimeUtil
->calc_age($year, $mon, $day);
1799 sub age_for_adcall
{
1801 croak
"Invalid user object" unless LJ
::isu
($u);
1803 return undef if $u->underage;
1804 return eval {$u->age || $u->init_age};
1807 # This returns the users age based on the init_bdate (users coppa validation birthdate)
1810 croak
"Invalid user object" unless LJ
::isu
($u);
1812 my $init_bdate = $u->prop('init_bdate');
1813 return unless $init_bdate;
1815 my ($year, $mon, $day) = $init_bdate =~ m/^(\d\d\d\d)-(\d\d)-(\d\d)/;
1816 my $age = LJ
::TimeUtil
->calc_age($year, $mon, $day);
1817 return $age if $age > 0;
1821 # Returns the best guess age of the user, which is init_age if it exists, otherwise age
1822 sub best_guess_age
{
1824 return 0 unless $u->is_person || $u->is_identity;
1825 return $u->init_age || $u->age;
1830 return $u->{gender
} if exists $u->{gender
};
1831 return $u->prop('gender');
1834 sub gender_for_adcall
{
1836 my $format = shift || '6a';
1837 croak
"Invalid user object" unless LJ
::isu
($u);
1839 my $gender = $u->prop('gender') || '';
1840 $gender = uc(substr($gender, 0, 1));
1841 if ($format eq '6a') {
1842 if ($gender && $gender !~ /^U/i) {
1843 return $gender; # M|F
1845 return "unspecified";
1847 } elsif ($format eq 'dc') {
1848 return ($gender eq 'M') ?
1 :
1849 ($gender eq 'F') ?
2 : 0;
1850 } elsif ($format eq 'ga') {
1851 return ($gender eq 'M') ?
'male' :
1852 ($gender eq 'F') ?
'female' : 'all';
1858 sub should_fire_birthday_notif
{
1861 return 0 unless $u->is_person;
1862 return 0 unless $u->is_visible;
1864 # if the month/day can't be shown
1865 return 0 if $u->opt_showbday =~ /^[YN]$/;
1867 # if the birthday isn't shown to anyone
1868 return 0 if $u->opt_sharebday eq "N";
1870 # note: this isn't intended to capture all cases where birthday
1871 # info is restricted. we want to pare out as much as possible;
1872 # individual "can user X see this birthday" is handled in
1873 # LJ::Event::Birthday->matches_filter
1880 return if $u->is_expunged;
1882 return $u->selectrow_array("SELECT nextbirthday FROM birthdays " .
1883 "WHERE userid = ?", undef, $u->id)+0;
1886 # class method, loads next birthdays for a bunch of users
1887 sub next_birthdays
{
1890 # load the users we need, so we can get their clusters
1891 my $clusters = LJ
::User
->split_by_cluster(@_);
1894 foreach my $cid (keys %$clusters) {
1897 my @users = @
{$clusters->{$cid} || []};
1898 my $dbcr = LJ
::get_cluster_def_reader
($cid)
1899 or die "Unable to load reader for cluster: $cid";
1901 my $bind = join(",", map { "?" } @users);
1902 my $sth = $dbcr->prepare("SELECT * FROM birthdays WHERE userid IN ($bind)");
1903 $sth->execute(@users);
1904 while (my $row = $sth->fetchrow_hashref) {
1905 $bdays{$row->{userid
}} = $row->{nextbirthday
};
1913 # this sets the unix time of their next birthday for notifications
1914 sub set_next_birthday
{
1916 return if $u->is_expunged;
1918 my ($year, $mon, $day) = split(/-/, $u->{bdate
});
1919 unless ($mon > 0 && $day > 0) {
1920 $u->do("DELETE FROM birthdays WHERE userid = ?", undef, $u->id);
1925 return LJ
::TimeUtil
->mysqldate_to_time(sprintf("%04d-%02d-%02d", @_));
1928 my $curyear = (gmtime(time))[5]+1900;
1930 # Calculate the time of their next birthday.
1932 # Assumption is that birthday-notify jobs won't be backed up.
1933 # therefore, if a user's birthday is 1 day from now, but
1934 # we process notifications for 2 days in advance, their next
1935 # birthday is really a year from tomorrow.
1937 # We need to do calculate three possible "next birthdays":
1938 # Current Year + 0: For the case where we it for the first
1939 # time, which could happen later this year.
1940 # Current Year + 1: For the case where we're setting their next
1941 # birthday on (approximately) their birthday. Gotta set it for
1942 # next year. This works in all cases but...
1943 # Current Year + 2: For the case where we're processing notifs
1944 # for next year already (eg, 2 days in advance, and we do
1945 # 1/1 birthdays on 12/30). Year + 1 gives us the date two days
1946 # from now! So, add another year on top of that.
1948 # We take whichever one is earliest, yet still later than the
1949 # window of dates where we're processing notifications.
1952 for my $inc (0..2) {
1953 $bday = $as_unix->($curyear + $inc, $mon, $day);
1954 last if $bday > time() + $LJ::BIRTHDAY_NOTIFS_ADVANCE
;
1957 # up to twelve hours drift so we don't get waves
1958 $bday += int(rand(12*3600));
1960 $u->do("REPLACE INTO birthdays VALUES (?, ?)", undef, $u->id, $bday);
1961 die $u->errstr if $u->err;
1967 sub include_in_age_search
{
1970 # if they don't display the year
1971 return 0 if $u->opt_showbday =~ /^[DN]$/;
1973 # if it's not visible to registered users
1974 return 0 if $u->opt_sharebday =~ /^[NF]$/;
1980 # data for generating packed directory records
1981 sub usersearch_age_with_expire
{
1983 croak
"Invalid user object" unless LJ
::isu
($u);
1985 # don't include their age in directory searches
1986 # if it's not publicly visible in their profile
1987 my $age = $u->include_in_age_search ?
$u->age : 0;
1990 # no need to expire due to age if we don't have a birthday
1991 my $expire = $u->next_birthday || undef;
1993 return ($age, $expire);
1996 # returns the country specified by the user
1999 return $u->prop('country');
2002 # sets prop, and also updates $u's cached version
2004 my ($u, $prop, $value) = @_;
2006 my $propmap = ref $prop ?
$prop : { $prop => $value };
2008 # filter out props that do not change
2009 foreach my $propname (keys %$propmap) {
2010 # it's not loaded, so let's not check it
2011 next unless exists $u->{$propname};
2013 if ( (!$propmap->{$propname} && !$u->{$propname})
2014 || $propmap->{$propname} eq $u->{$propname} )
2016 delete $propmap->{$propname};
2020 my @props_affected = keys %$propmap;
2021 my $groups = LJ
::User
::PropStorage
->get_handler_multi(\
@props_affected);
2022 my $memcache_available = @LJ::MEMCACHE_SERVERS
;
2024 my $memc_expire = time + 3600 * 24;
2026 foreach my $handler (keys %$groups) {
2027 my $propnames_handled = $groups->{$handler};
2028 my %propmap_handled = map { $_ => $propmap->{$_} }
2029 @
$propnames_handled;
2031 # first, actually save stuff to the database;
2032 # then, delete it from memcache, depending on the memcache
2033 # policy of the handler
2034 $handler->set_props( $u, \
%propmap_handled );
2036 # if there is no memcache, or if the handler doesn't wish to use
2037 # memcache, we don't need to deal with it, yay
2038 if ( !$memcache_available || !defined $handler->use_memcache )
2043 # now let's find out what we're going to do with memcache
2044 my $memcache_policy = $handler->use_memcache;
2046 if ( $memcache_policy eq 'lite' ) {
2047 # the handler loads everything from the corresponding
2048 # table and uses only one memcache key to cache that
2050 my $memkey = $handler->memcache_key($u);
2051 LJ
::MemCacheProxy
::delete([ $u->userid, $memkey ]);
2052 } elsif ( $memcache_policy eq 'blob' ) {
2053 # the handler uses one memcache key for each prop,
2054 # so let's delete them all
2056 foreach my $propname (@
$propnames_handled) {
2057 my $memkey = $handler->memcache_key( $u, $propname );
2058 LJ
::MemCacheProxy
::delete([ $u->userid, $memkey ]);
2063 # now, actually reflect that we've changed the props in the
2065 foreach my $propname (keys %$propmap) {
2066 $u->{$propname} = $propmap->{$propname};
2069 # and run the hooks, too
2070 LJ
::run_hooks
( 'props_changed', $u, $propmap );
2076 my ($u, $prop) = @_;
2077 $u->set_prop($prop, undef);
2083 return $u->{'journal_base'} if $u->{'journal_base'};
2084 return LJ
::journal_base
($u);
2089 return "$LJ::SITEROOT/allpics.bml?user=" . $u->user;
2092 sub get_userpic_count
{
2093 my $u = shift or return undef;
2094 my $count = scalar LJ
::Userpic
->load_user_userpics($u);
2100 my $u = shift or return undef;
2101 my $quota = $u->get_cap('userpics');
2108 croak
"invalid user object passed" unless LJ
::isu
($u);
2110 return $u->journal_base . "/wishlist";
2114 my ($u, %opts) = @_;
2116 my $remote = LJ
::get_remote
();
2119 if ($u->{journaltype
} eq "I") {
2120 if ($LJ::DISABLED
{profile_controller
}) {
2121 $url = "$LJ::SITEROOT/userinfo.bml?userid=$u->{'userid'}&t=I";
2122 $url .= "&mode=full" if $opts{full
};
2124 $url = "$LJ::SITEROOT/profile";
2125 $url .= "/".$opts{'friends_page'} if $opts{'friends_page'};
2126 $url .= "?userid=$u->{'userid'}&t=I";
2127 $url .= "&mode=full" if $opts{full
};
2130 $url = $u->journal_base . "/profile";
2131 $url .= "/".$opts{'friends_page'} if $opts{'friends_page'};
2132 $url .= "?mode=full" if $opts{full
};
2137 # returns the gift shop URL to buy a gift for that user
2139 my ($u, $opts) = @_;
2140 croak
"invalid user object passed" unless LJ
::isu
($u);
2141 my $item = $opts->{item
} ?
delete $opts->{item
} : '';
2143 return "$LJ::SITEROOT/shop/vgift.bml?to=$u->{'user'}";
2146 # return the URL to the send message page
2149 croak
"invalid user object passed" unless LJ
::isu
($u);
2151 return undef if $LJ::DISABLED
{user_messaging
};
2152 return "$LJ::SITEROOT/inbox/compose.bml?user=$u->{'user'}";
2156 # name: LJ::User::large_journal_icon
2157 # des: get the large icon by journal type.
2158 # returns: HTML to display large journal icon.
2160 sub large_journal_icon
{
2162 croak
"invalid user object"
2165 my $wrap_img = sub {
2166 return "<img src='$LJ::IMGPREFIX/$_[0]' border='0' height='24' " .
2167 "width='24' style='padding: 0px 2px 0px 0px' />";
2170 # hook will return image to use if it cares about
2171 # the $u it's been passed
2172 my $hook_img = LJ
::run_hook
("large_journal_icon", $u);
2173 return $wrap_img->($hook_img) if $hook_img;
2176 return $wrap_img->("community24x24.gif?v=6283");
2179 if ($u->is_syndicated) {
2180 return $wrap_img->("syndicated24x24.gif?v=6283");
2183 if ($u->is_identity) {
2184 return $wrap_img->("openid24x24.gif?v=6034");
2187 # personal, news, or unknown fallthrough
2188 return $wrap_img->("userinfo24x24.gif?v=6283");
2192 # name: LJ::User::caps_icon
2193 # des: get the icon for a user's cap.
2194 # returns: HTML with site-specific cap icon.
2198 return LJ
::user_caps_icon
($u->{caps
});
2201 # tests to see if a user is in a specific named class. class
2202 # names are site-specific.
2204 my ($u, @classes) = @_;
2206 foreach my $class (@classes) {
2207 return 1 if LJ
::caps_in_group
($u->{caps
}, $class);
2214 # get recent talkitems posted to this user
2215 # args: maximum number of comments to retrieve
2216 # returns: array of hashrefs with jtalkid, nodetype, nodeid, parenttalkid, posterid, state
2217 sub get_recent_talkitems
{
2218 my ($u, $maxshow, %opts) = @_;
2221 my $max_fetch = int($LJ::TOOLS_RECENT_COMMENTS_MAX
*1.5) || 150;
2222 # We fetch more items because some may be screened
2223 # or from suspended users, and we weed those out later
2225 my $remote = $opts{remote
} || LJ
::get_remote
();
2226 return undef unless LJ
::isu
($u);
2228 ## $raw_talkitems - contains DB rows that are not filtered
2229 ## to match remote user's permissions to see
2231 my $memkey = [$u->userid, 'rcntalk:' . $u->userid ];
2232 $raw_talkitems = LJ
::MemCache
::get
($memkey);
2233 if (!$raw_talkitems) {
2234 my $sth = $u->prepare(
2235 "SELECT jtalkid, nodetype, nodeid, parenttalkid, ".
2236 " posterid, UNIX_TIMESTAMP(datepost) as 'datepostunix', state ".
2238 "WHERE journalid=? AND (state <> 'D' AND state <> 'B') " .
2239 "ORDER BY jtalkid DESC ".
2242 $sth->execute($u->{'userid'});
2243 $raw_talkitems = $sth->fetchall_arrayref({});
2244 LJ
::MemCache
::set
($memkey, $raw_talkitems, 60*5);
2247 ## Check remote's permission to see the comment, and create singletons
2249 foreach my $r (@
$raw_talkitems) {
2250 last if @recv >= $maxshow;
2252 # construct an LJ::Comment singleton
2253 my $comment = LJ
::Comment
->new($u, jtalkid
=> $r->{jtalkid
});
2254 $comment->absorb_row($r);
2255 next unless $comment->visible_to($remote);
2259 # need to put the comments in order, with "oldest first"
2260 # they are fetched from DB in "recent first" order
2261 return reverse @recv;
2264 sub last_login_time
{
2267 my $userid = $u->userid;
2268 my $cache = ( $LJ::REQ_GLOBAL
{'user_last_login_time'} ||= {} );
2270 return $cache->{$userid} if exists $cache->{$userid};
2272 my $dbr = LJ
::get_cluster_reader
($u);
2273 my ($time) = $dbr->selectrow_array(
2274 'SELECT MAX(logintime) FROM loginlog WHERE userid=?',
2279 $cache->{$userid} = $time;
2283 sub last_password_change_time
{
2286 my $userid = $u->userid;
2287 my $cache = ( $LJ::REQ_GLOBAL
{'user_last_password_change_time'} ||= {} );
2289 return $cache->{$userid} if exists $cache->{$userid};
2291 my $infohistory = LJ
::User
::InfoHistory
->get( $u, 'password' );
2292 my @password_change_timestamps = map { $_->timechange_unix } @
$infohistory;
2295 if (@password_change_timestamps) {
2296 $time = List
::Util
::max
( @password_change_timestamps );
2301 $cache->{$userid} = $time;
2305 # THIS IS DEPRECATED DO NOT USE
2307 my ($u, $remote) = @_;
2308 return $u->emails_visible($remote);
2313 $u->{_email
} ||= LJ
::MemCache
::get_or_set
([$u->{userid
}, "email:$u->{userid}"], sub {
2314 my $dbh = LJ
::get_db_writer
() or die "Couldn't get db master";
2315 return $dbh->selectrow_array("SELECT email FROM email WHERE userid=?",
2318 return $u->{_email
};
2321 sub validated_mbox_sha1sum
{
2325 return undef unless $u->is_validated;
2327 # must have one on file
2328 my $email = $u->email_raw;
2329 return undef unless $email;
2331 # return SHA1, which does not disclose the actual value
2332 return Digest
::SHA1
::sha1_hex
('mailto:' . $email);
2335 # in scalar context, returns user's email address. given a remote user,
2336 # bases decision based on whether $remote user can see it. in list context,
2337 # returns all emails that can be shown
2339 my ($u, $remote) = @_;
2341 return scalar $u->emails_visible($remote);
2344 sub emails_visible
{
2345 my ($u, $remote) = @_;
2347 return () if $u->{journaltype
} =~ /[YI]/;
2350 return () unless $u->share_contactinfo($remote);
2352 my $whatemail = $u->prop("opt_whatemailshow");
2353 my $useremail_cap = LJ
::get_cap
($u, 'useremail');
2355 # some classes of users we want to have their contact info hidden
2356 # after so much time of activity, to prevent people from bugging
2357 # them for their account or trying to brute force it.
2358 my $hide_contactinfo = sub {
2359 my $hide_after = LJ
::get_cap
($u, "hide_email_after");
2360 return 0 unless $hide_after;
2361 my $memkey = [$u->{userid
}, "timeactive:$u->{userid}"];
2363 unless (defined($active = LJ
::MemCache
::get
($memkey))) {
2364 my $dbcr = LJ
::get_cluster_def_reader
($u) or return 0;
2365 $active = $dbcr->selectrow_array("SELECT timeactive FROM clustertrack2 ".
2366 "WHERE userid=?", undef, $u->{userid
});
2367 LJ
::MemCache
::set
($memkey, $active, 86400);
2369 return $active && (time() - $active) > $hide_after * 86400;
2372 return () if $u->{'opt_whatemailshow'} eq "N" ||
2373 $u->{'opt_whatemailshow'} eq "L" && ($u->prop("no_mail_alias") || ! $useremail_cap || ! $LJ::USER_EMAIL
) ||
2374 $hide_contactinfo->();
2376 my @emails = ($u->email_raw);
2377 if ($u->{'opt_whatemailshow'} eq "L") {
2380 if ($LJ::USER_EMAIL
&& $useremail_cap) {
2381 unless ($u->{'opt_whatemailshow'} eq "A" || $u->prop('no_mail_alias')) {
2382 push @emails, "$u->{'user'}\@$LJ::USER_DOMAIN";
2385 return wantarray ?
@emails : $emails[0];
2388 sub email_for_feeds
{
2391 # don't display if it's mangled
2392 return if $u->prop("opt_mangleemail") eq "Y";
2394 my $remote = LJ
::get_remote
();
2395 return $u->email_visible($remote);
2400 return $u->{status
};
2405 return $u->email_status eq "A";
2408 sub receives_html_emails
{
2410 return $u->{opt_htmlemail
} eq 'Y';
2413 sub update_email_alias
{
2416 return unless $u && $u->get_cap("useremail");
2417 return if exists $LJ::FIXED_ALIAS
{$u->{'user'}};
2418 return if $u->prop("no_mail_alias");
2419 return unless $u->is_validated;
2421 my $dbh = LJ
::get_db_writer
();
2422 $dbh->do("REPLACE INTO email_aliases (alias, rcpt) VALUES (?,?)",
2423 undef, "$u->{'user'}\@$LJ::USER_DOMAIN", $u->email_raw);
2425 return 0 if $dbh->err;
2429 # my $u = LJ::want_user(12);
2430 # my $data = $u->get_email_data('test@test.ru');
2431 # print $data->{'email_state'}; # email status if test@test.ru is the
2432 # # current email; "P" otherwise
2433 # print $data->{'time'}; # time when that email was added to the account
2434 sub get_email_data
{
2435 my ($u, $addr) = @_;
2437 return undef unless $u && $addr;
2439 my $emails = $u->emails_info;
2440 my $is_current = lc($addr) eq lc($u->email_raw);
2442 foreach my $email (@
$emails) {
2443 next unless lc($email->{'email'}) eq lc($addr);
2444 next if $email->{'deleted'};
2445 next unless $email->{'status'} eq 'A';
2446 next if $is_current && !$email->{'current'};
2449 $ret->{'email_state'} = $is_current ?
$email->{'status'} : 'P';
2450 $ret->{'time'} = $email->{'set'};
2458 # get information about which emails the user has used previously or uses now
2461 # { email => 'test@test.com', current => 1, set => 123142345234, status => 'A' },
2462 # { email => 'test2@test.com', set => $timestamp, changed => 123142345234, status => 'A' },
2463 # { email => 'test3@test.com', set => $timestamp2, changed => $timestamp, status => 'T', deleted => $timestamp3 },
2468 return $u->{'_emails'} if defined $u->{'_emails'};
2473 LJ
::User
::InfoHistory
->get( $u, [ 'email', 'emaildeleted' ] );
2475 my $infohistory_records =
2476 [ sort { $a->timechange_unix <=> $b->timechange_unix } @
$infohistory ];
2478 # my $dbr = LJ::get_db_reader();
2479 # my $infohistory_rows = $dbr->selectall_arrayref(
2480 # 'SELECT what, UNIX_TIMESTAMP(timechange) AS timechange, '.
2481 # 'oldvalue, other FROM infohistory WHERE userid=? AND '.
2482 # 'what IN ("email", "emaildeleted") ORDER BY timechange',
2483 # { Slice => {} }, $u->id
2485 # my @infohistory_rows = @$infohistory_rows;
2487 # this actually finds the greatest timechange in rows before $recordnum;
2488 # if it fails to find it, it returns $u->timecreate
2489 my $find_timeset = sub {
2490 my ($recordnum) = @_;
2492 for ( my $recordnum2 = $recordnum - 1;
2493 $recordnum2 >= 0; $recordnum2-- )
2495 my $record2 = $infohistory_records->[$recordnum2];
2496 return $record2->timechange_unix if $record2->what eq 'email';
2499 # in case we found nothing, the address was set when the account
2501 return $u->timecreate;
2504 foreach my $recordnum ( 0 .. $#$infohistory_records ) {
2505 my $record = $infohistory_records->[$recordnum];
2506 if ( $record->what eq 'email' ) {
2507 # new email has been added to the list, but now, we're going to
2508 # record the old address
2510 'email' => $record->oldvalue,
2511 'changed' => $record->timechange_unix,
2512 'status' => $record->other,
2513 'set' => $find_timeset->($recordnum),
2515 } elsif ( $record->what eq 'emaildeleted' ) {
2516 # there may be two cases here: 1) it was something like an admin
2517 # deletion or 2) it was deletion through /tools/emailmanage.bml,
2518 # which previously did 'UPDATE infohistory SET what="emaildelete"'
2519 # (oh weird) and also changed `other` to "A; $timeset".
2520 # /tools/emailmanage.bml has since been changed to record that
2521 # change as a new entry, which returns us to the first case
2523 unless ( $record->other =~ /;/ ) {
2524 # first case: find all other occurences of that email
2525 # and mark them with the date of deletion
2527 foreach my $email (@ret) {
2528 next unless $email->{'email'} eq $record->oldvalue;
2529 next unless $email->{'set'} <= $record->timechange_unix;
2531 $email->{'deleted'} = $record->timechange_unix
2532 unless $email->{'deleted'};
2535 # second case: parse the timestamp, create an email hashref,
2536 # find the record with the next address to set "set", and
2537 # finally, mark it as deleted. ugh.
2539 my ( $status, $time ) = split /;/, $record->other;
2541 # there is no joke here. in infohistory, time is stored as
2542 # MySQL DATETIME. emailmanage.bml used to just append it to
2543 # previous status, so now, we need to parse.
2544 $time = str2time
($time);
2546 # we need to find the first record which has timestamp
2547 # greater or equal to $time so that we can call $find_timeset
2549 foreach my $recordnum2 ( 0 .. $#$infohistory_records ) {
2550 my $record2 = $infohistory_records->[$recordnum2];
2551 next unless $record2->what eq 'email';
2552 next if $record2->timechange_unix < $time;
2554 $nextrecord = $recordnum2;
2559 'email' => $record->oldvalue,
2561 'status' => $status,
2562 'deleted' => $record->timechange_unix,
2563 'set' => $find_timeset->($nextrecord),
2569 # finally, the current address
2571 'email' => $u->email_raw,
2573 'status' => $u->email_status,
2574 'set' => $find_timeset->( $#$infohistory_records + 1 ),
2577 $u->{'_emails'} = \
@ret;
2581 # returns array (not arrayref) of emails that the user has ever used, including
2586 my $emails = $u->emails_info;
2589 foreach my $email (@
$emails) {
2590 $ret{lc($email->{'email'})} = 1;
2593 return sort keys %ret;
2596 # read emails and calculate primitives:
2597 # date of last leaving
2598 # date of chain start
2599 # returns data of emails_info function with additional keys ('leaving', may be undef, and 'starting')
2600 # skips internal steps of chains
2601 # skips deleted emails
2602 # cleans out all chains, which are unusable for password restoring, i.e. 'leaving' is newer than 6 month old
2603 # must return array for printing on tools/emailmanage.bml
2604 sub emails_chained_info
{
2607 return $u->{'_emails_chained'} if defined $u->{'_emails_chained'};
2609 my $emails = $u->emails_info;
2610 my @email_addresses = $u->emails_unique;
2614 # process all elements
2615 foreach my $addr (@email_addresses) {
2616 # find all information about this element
2617 my ($starting, $leaving);
2618 my $lc_addr = lc $addr;
2620 my @relevant = grep { lc($_->{email
}) eq $lc_addr } @
$emails;
2621 # already sorted by MySQL
2624 foreach my $step (@relevant) {
2626 $written_addr = $step->{email
};
2628 next unless $step->{status
} eq 'A'; # restoring can be done only by validated addressed
2630 if ($step->{deleted
}) {
2636 if (defined $leaving and $step->{set
} - $leaving > $LJ::EMAIL_FORGET_AGE
) {
2637 # forget old chain - because it is unusable for password restoring
2638 undef $starting; # start new chain
2642 # most early starting
2643 $starting = $step->{set
} unless defined $starting and $starting < $step->{set
};
2646 $leaving = $step->{changed
} unless defined $leaving and defined $step->{changed
} and $step->{changed
} < $leaving;
2649 if ($starting and time - $leaving < $LJ::EMAIL_FORGET_AGE
or not defined $leaving) {
2650 push @chains, { email
=> $written_addr, leaving
=> $leaving, starting
=> $starting }; # fix this chain
2651 # we store address with upper case letters possibly,
2652 # to make it more comfort for user when he/she reads address
2656 $u->{'_emails_chained'} = \
@chains;
2660 # returns time when the user has last stopped using the given email
2661 # (that is, switched their current address to a different one)
2662 # this ASSUMES that the address is not a current one, but that it was
2663 # validated previously.
2664 sub email_lastchange
{
2665 my ($u, $addr) = @_;
2668 my $emails = $u->emails_info;
2672 foreach my $email (@
$emails) {
2673 next unless lc($email->{'email'}) eq lc($addr);
2674 next unless $email->{'status'} eq 'A';
2675 next if $email->{'current'};
2676 next if $email->{'deleted'};
2679 $lastchange = $email->{'changed'} if $email->{'changed'} > $lastchange;
2682 return undef unless $found;
2686 # checks whether user is allowed to remove the given email from their history
2687 # and this way, disable themselves from sending a password reset to that address
2688 sub can_delete_email
{
2689 my ($u, $email) = @_;
2691 my $chains = $u->emails_chained_info;
2692 my $addr = ref $email ?
$email->{email
} : $email;
2695 # reformat as email => parameters hash
2696 my %chains = map { lc($_->{email
}) => $_ } @
$chains;
2698 my $current = lc $u->email_raw;
2699 my $edge_age = $chains{$current}->{starting
};
2701 my $aim_value = $chains{$addr}->{starting
};
2703 return 0 unless defined $edge_age and $aim_value;
2704 return $aim_value > $edge_age;
2707 # delete the given email from user's history, disabling the user from sending
2708 # a password reset to that address
2709 # this performs the necessary checks by calling can_delete_email() as defined
2712 my ($u, $addr) = @_;
2714 return unless $u->can_delete_email($addr);
2716 LJ
::User
::InfoHistory
->add( $u, 'emaildeleted', $addr );
2719 my $emails = $u->emails_info;
2720 foreach my $email (@
$emails) {
2721 next if $email->{'deleted'};
2723 next unless lc($email->{'email'}) eq lc($addr);
2724 $email->{'deleted'} = time;
2728 # checks whether the given email has been validated, regardless of whether it is
2729 # set to current right now. despite the name, it specifically omits deleted
2731 sub is_email_validated
{
2732 my ($u, $addr) = @_;
2734 my $emails = $u->emails_info;
2735 foreach my $email (@
$emails) {
2736 next unless lc($email->{'email'}) eq lc($addr);
2737 next if $email->{'deleted'};
2738 next unless $email->{'status'} eq 'A';
2746 # checks whether the user can send a password reset to the given email
2747 # the current logic is:
2748 # case 1: NOT $LJ::DISABLED{'limit_password_reset'}
2749 # yes if the email is set to current OR
2750 # (has been previously validated AND has not been deleted after that AND
2751 # the user has stopped using it no more than 6 months ago);
2753 # case 2: $LJ::DISABLED{'limit_password_reset'}
2754 # yes if and only if the email is set to current
2755 sub can_reset_password_using_email
{
2756 my ($u, $addr) = @_;
2758 return 0 unless $LJ::DISABLED
{'limit_password_reset'};
2760 my $current = lc $u->email_raw;
2761 return 1 if lc($addr) eq $current;
2763 return 0 unless $u->is_email_validated($addr);
2765 my $chains = $u->emails_chained_info;
2767 # reformat as email => parameters hash
2768 my %chains = map { lc($_->{email
}) => $_ } @
$chains;
2770 my $aim_value = $chains{lc $addr}->{leaving
};
2772 return 0 unless defined $aim_value;
2773 return time - $aim_value < $LJ::EMAIL_FORGET_AGE
;
2776 # returns date when the user has last changed their email
2777 sub get_current_email_set_date
{
2780 my $emails = $u->emails_info;
2782 foreach my $email (@
$emails) {
2783 next unless $email->{'current'};
2784 return $email->{'set'};
2790 sub previous_usernames
{
2793 # the memcache is set to expire automatically on an account rename;
2794 # the value there contains a username, and if that changes, we have
2795 # to recalculate stuff
2796 my $memkey = [ $u->userid, 'previous_usernames:' . $u->userid ];
2797 if ( my $value = LJ
::MemCache
::get
($memkey) ) {
2798 if ( $value->{'current'} eq $u->username ) {
2799 return $value->{'previous'};
2803 my $infohistory = LJ
::User
::InfoHistory
->get( $u, 'username' );
2804 my @usernames = map { $_->oldvalue } @
$infohistory;
2806 my $value = { 'current' => $u->username, 'previous' => \
@usernames };
2808 # the auto-expiration here may fail us in case user is renamed back
2809 # before this function is called with that user in the other username,
2810 # so let's expire it after a day passes to somehow handle that
2811 LJ
::MemCache
::set
( $memkey, $value, 86400 );
2816 sub share_contactinfo
{
2817 my ($u, $remote) = @_;
2819 return 0 if ($u->underage || $u->{journaltype
} eq "Y");
2820 return 0 if ($u->opt_showcontact eq 'N');
2821 return 0 if ($u->opt_showcontact eq 'R' && !$remote);
2822 return 0 if ($u->opt_showcontact eq 'F' && !$u->is_friend($remote));
2826 # return social capital by user
2827 sub get_social_capital
{
2830 if ( $LJ::IS_DEV_SERVER
&& ( my $getter = $LJ::FAKE_SOCIAL_CAPITAL
) ) {
2831 return $getter->($u);
2834 my $key = $u->userid . ":sccap";
2835 my $attr = '_social_capital';
2837 return $u->{$attr} if defined $u->{$attr};
2839 my $soc_capital = LJ
::MemCache
::get
( $key );
2841 unless (defined $soc_capital || $LJ::IS_DEV_SERVER
|| !LJ
::is_enabled
('authority_redis_storage')) {
2842 # TODO: Check the date of social capital (if the data is wrong that try to get actual version of social cap from service
2843 my $redis = LJ
::Redis
->get_connection;
2845 my $authority = $redis->get('authority.'.$u->userid) || 0;
2846 $soc_capital = int($authority / 1000);
2850 unless (defined $soc_capital) {
2851 my $response = LJ
::PersonalStats
::DB
->fetch_raw('ratings', {func
=> 'get_authority', journal_id
=> $u->userid});
2853 $soc_capital = int($response->{result
}->{authority
}/1000);
2854 LJ
::MemCache
::set
( $key, $soc_capital, 5*60);
2858 $u->{$attr} = $soc_capital if defined $soc_capital;
2860 return $soc_capital;
2863 sub get_authority_multi
{
2864 my ($class, $uids) = @_;
2866 return unless $uids && @
$uids;
2868 if ( $LJ::IS_DEV_SERVER
&& ( my $getter = $LJ::FAKE_SOCIAL_CAPITAL
) ) {
2869 my $users = LJ
::load_userids
(@
$uids);
2870 return { map {$_ => ($getter->($users->{$_})*1000)} @
$uids };
2873 my $redis = LJ
::Redis
->get_connection || return;
2875 my @keys = map {"authority.$_"} @
$uids;
2877 my @res = $redis->mget(@keys);
2879 my $res = { map { $uids->[$_] => ($res[$_]) } (0..$#res) };
2885 # name: LJ::display_soccap
2887 # des: Encode social capital into nice look text
2889 # returns: nice string
2893 my $soc_capital = shift;
2894 if ( $soc_capital =~ /^\d+$/ ) {
2895 $soc_capital = $soc_capital < 10 ? LJ
::Lang
::ml
('/tools/endpoints/ctxpopup.bml.social_capital_less_that') : LJ
::commafy
($soc_capital);
2897 $soc_capital = LJ
::Lang
::ml
('social_capital_undef');
2899 return $soc_capital;
2903 # name: LJ::User::get_reader_weight
2904 # des: returns reader_weight of user
2907 sub get_reader_weight
{
2910 my $memkey = join ':', $u->userid, 'reader_weight';
2912 my $reader_weight = LJ
::MemCache
::get
( $memkey );
2914 return $reader_weight if defined $reader_weight;
2916 my $resp = LJ
::PersonalStats
::DB
->fetch_raw('ratings', {
2917 func
=> 'get_reader_weight',
2918 journal_id
=> $u->userid,
2921 return undef unless $resp;
2923 $reader_weight = $resp->{reader_weight
};
2925 LJ
::MemCache
::set
( $memkey, $reader_weight, 60);
2927 return $reader_weight || -1;
2931 # name: LJ::User::activate_userpics
2932 # des: Sets/unsets userpics as inactive based on account caps.
2935 sub activate_userpics
{
2938 # this behavior is optional, but enabled by default
2939 return 1 if $LJ::ALLOW_PICS_OVER_QUOTA
;
2941 return undef unless LJ
::isu
($u);
2943 # can't get a cluster read for expunged users since they are clusterid 0,
2944 # so just return 1 to the caller from here and act like everything went fine
2945 return 1 if $u->is_expunged;
2947 my $userid = $u->{'userid'};
2949 # active / inactive lists
2952 my $allow = LJ
::get_cap
($u, "userpics");
2954 # get a database handle for reading/writing
2955 my $dbh = LJ
::get_db_writer
();
2956 my $dbcr = LJ
::get_cluster_def_reader
($u);
2958 # select all userpics and build active / inactive lists
2960 if ($u->{'dversion'} > 6) {
2961 return undef unless $dbcr;
2962 $sth = $dbcr->prepare("SELECT picid, state FROM userpic2 WHERE userid=?");
2964 return undef unless $dbh;
2965 $sth = $dbh->prepare("SELECT picid, state FROM userpic WHERE userid=?");
2967 $sth->execute($userid);
2968 while (my ($picid, $state) = $sth->fetchrow_array) {
2969 next if $state eq 'X'; # expunged, means userpic has been removed from site by admins
2970 if ($state eq 'I') {
2971 push @inactive, $picid;
2973 push @active, $picid;
2977 # inactivate previously activated userpics
2978 if (@active > $allow) {
2980 my @ban = sort { $a <=> $b } @active;
2981 splice(@ban, 0, $allow);
2983 my $ban_in = join(",", map { $dbh->quote($_) } @ban);
2984 if ($u->{'dversion'} > 6) {
2985 $u->do("UPDATE userpic2 SET state='I' WHERE userid=? AND picid IN ($ban_in)",
2986 undef, $userid) if $ban_in;
2988 $dbh->do("UPDATE userpic SET state='I' WHERE userid=? AND picid IN ($ban_in)",
2989 undef, $userid) if $ban_in;
2993 # activate previously inactivated userpics
2994 if (@inactive && @active < $allow) {
2995 my $to_activate = $allow - @active;
2996 $to_activate = @inactive if $to_activate > @inactive;
2998 # take the $to_activate newest (highest numbered) pictures
3000 @inactive = sort @inactive;
3001 my @activate_picids = splice(@inactive, -$to_activate);
3003 my $activate_in = join(",", map { $dbh->quote($_) } @activate_picids);
3005 if ($u->{'dversion'} > 6) {
3006 $u->do("UPDATE userpic2 SET state='N' WHERE userid=? AND picid IN ($activate_in)",
3009 $dbh->do("UPDATE userpic SET state='N' WHERE userid=? AND picid IN ($activate_in)",
3015 # delete userpic info object from memcache
3016 LJ
::Userpic
->delete_cache($u);
3022 # revert S2 style to the default if the user is using a layout/theme layer that they don't have permission to use
3026 # FIXME: this solution sucks
3027 # - ensure that these packages are loaded via Class::Autouse by calling a method on them
3028 LJ
::S2
->can("dostuff");
3029 LJ
::S2Theme
->can("dostuff");
3030 LJ
::Customize
->can("dostuff");
3032 my $current_theme = LJ
::Customize
->get_current_theme($u);
3033 return unless $current_theme;
3034 my $default_theme_of_current_layout = LJ
::S2Theme
->load_default_of($current_theme->layoutid, user
=> $u);
3035 return unless $default_theme_of_current_layout;
3037 my $default_style = LJ
::run_hook
('get_default_style', $u) || $LJ::DEFAULT_STYLE
;
3038 my $default_layout_uniq = exists $default_style->{layout
} ?
$default_style->{layout
} : '';
3039 my $default_theme_uniq = exists $default_style->{theme
} ?
$default_style->{theme
} : '';
3041 my %style = LJ
::S2
::get_style
($u, "verify");
3042 my $public = LJ
::S2
::get_public_layers
();
3043 my $userlay = LJ
::S2
::get_layers_of_user
($u);
3045 # check to see if the user is using a custom layout or theme
3046 # if so, we want to let them keep using it
3047 foreach my $layerid (keys %$userlay) {
3048 return if $current_theme->layoutid == $layerid;
3049 return if $current_theme->themeid == $layerid;
3052 # if the user cannot use the layout or the default theme of that layout, switch to the default style (if it's defined)
3053 if (($default_layout_uniq || $default_theme_uniq) && (!LJ
::S2
::can_use_layer
($u, $current_theme->layout_uniq) || !$default_theme_of_current_layout->available_to($u))) {
3055 if ($default_theme_uniq) {
3056 $new_theme = LJ
::S2Theme
->load_by_uniq($default_theme_uniq);
3059 $layoutid = $public->{$default_layout_uniq}->{s2lid
}
3060 if $public->{$default_layout_uniq} && $public->{$default_layout_uniq}->{type
} eq "layout";
3061 $new_theme = LJ
::S2Theme
->load_default_of($layoutid, user
=> $u) if $layoutid;
3064 return unless $new_theme;
3066 # look for a style that uses the default layout/theme, and use it if it exists
3067 my $styleid = $new_theme->get_styleid_for_theme($u);
3068 my $style_exists = 0;
3071 $u->set_prop("s2_style", $styleid);
3073 my $stylelayers = LJ
::S2
::get_style_layers
($u, $u->prop('s2_style'));
3074 foreach my $layer (qw(user i18nc i18n core)) {
3075 $style{$layer} = exists $stylelayers->{$layer} ?
$stylelayers->{$layer} : 0;
3079 # set the layers that are defined by $default_style
3080 while (my ($layer, $name) = each %$default_style) {
3081 next if $name eq "";
3082 next unless $public->{$name};
3083 my $id = $public->{$name}->{s2lid
};
3084 $style{$layer} = $id if $id;
3087 # make sure core was set
3088 $style{core
} = $new_theme->coreid
3089 if $style{core
} == 0;
3091 # make sure the other layers were set
3092 foreach my $layer (qw(user i18nc i18n)) {
3093 $style{$layer} = 0 unless $style{$layer} || $style_exists;
3097 if ($style_exists) {
3098 LJ
::Customize
->implicit_style_create($u, %style);
3100 LJ
::Customize
->implicit_style_create({ 'force' => 1 }, $u, %style);
3103 # if the user can use the layout but not the theme, switch to the default theme of that layout
3104 # we know they can use this theme at this point because if they couldn't, the above block would have caught it
3105 } elsif (LJ
::S2
::can_use_layer
($u, $current_theme->layout_uniq) && !LJ
::S2
::can_use_layer
($u, $current_theme->uniq)) {
3106 $style{theme
} = $default_theme_of_current_layout->themeid;
3107 LJ
::Customize
->implicit_style_create($u, %style);
3114 my ($u, $name) = @_;
3116 my $handler = LJ
::User
::PropStorage
->get_handler ($name);
3117 $handler->delete_prop_memcache ($u, $name);
3123 sub set_draft_text
{
3124 my ($u, $draft, $prop_name) = @_;
3126 $prop_name ||= 'entry_draft';
3128 my $old = $u->draft_text($prop_name);
3130 $LJ::_T_DRAFT_RACE
->() if $LJ::_T_DRAFT_RACE
;
3132 # try to find a shortcut that makes the SQL shorter
3133 my @methods; # list of [ $subref, $cost ]
3135 # one method is just setting it all at once. which incurs about
3136 # 75 bytes of SQL overhead on top of the length of the draft,
3137 # not counting the escaping
3138 push @methods, [ "set", sub { $u->set_prop($prop_name, $draft); 1 },
3139 75 + length $draft ];
3141 # stupid case, setting the same thing:
3142 push @methods, [ "noop", sub { 1 }, 0 ] if $draft eq $old;
3144 # simple case: appending
3145 if (length $old && $draft =~ /^\Q$old\E(.+)/s) {
3147 my $appending = sub {
3148 my $prop = LJ
::get_prop
("user", $prop_name) or die; # FIXME: use exceptions
3149 my $rv = $u->do("UPDATE userpropblob SET value = CONCAT(value, ?) WHERE userid=? AND upropid=? AND LENGTH(value)=?",
3150 undef, $new, $u->{userid
}, $prop->{id
}, length $old);
3151 return 0 unless $rv > 0;
3152 $u->uncache_prop($prop_name);
3155 push @methods, [ "append", $appending, 40 + length $new ];
3158 # TODO: prepending/middle insertion (the former being just the latter), as well
3159 # appending, wihch we could then get rid of
3161 # try the methods in increasing order
3162 foreach my $m (sort { $a->[2] <=> $b->[2] } @methods) {
3165 $LJ::_T_METHOD_USED
->($m->[0]) if $LJ::_T_METHOD_USED
; # for testing
3173 my ($u, $prop_name) = @_;
3174 $prop_name ||= 'entry_draft';
3175 return $u->prop($prop_name);
3178 sub notable_interests
{
3182 # arrayref of arrayrefs of format [intid, intname, intcount];
3183 my $ints = LJ
::get_interests
($u)
3186 my @ints = map { $_->[1] } @
$ints;
3188 # sorta arrayref inline
3189 LJ
::AdTargetedInterests
->sort_interests(\
@ints);
3191 return @ints[0..$n-1] if @ints > $n;
3195 # returns $n number of communities that $u is a member of, sorted by update time (most recent to least recent)
3196 sub notable_communities
{
3200 my $friends = $u->friends;
3202 my $fro_m = LJ
::M
::FriendsOf
->new(
3204 sloppy
=> 1, # approximate if no summary info
3205 friends
=> { map {$_ => 1} keys %$friends },
3208 my $update_times = LJ
::get_timeupdate_multi
( map { $_->id } $fro_m->member_of );
3212 foreach my $commid (sort {$update_times->{$b} <=> $update_times->{$a}} keys %$update_times) {
3213 last if $count > $n;
3214 push @ret_commids, $commid;
3218 my $us = LJ
::load_userids
(@ret_commids);
3220 return map { $us->{$_} } @ret_commids;
3223 # returns the max capability ($cname) for all the classes
3224 # the user is a member of
3226 my ($u, $cname) = @_;
3227 return 1 if $LJ::T_HAS_ALL_CAPS
;
3228 return LJ
::get_cap
($u, $cname);
3231 # tests to see if a user is in a specific named class. class
3232 # names are site-specific.
3234 my ($u, $class) = @_;
3235 return LJ
::caps_in_group
($u->{caps
}, $class);
3239 my ($u, $class) = @_;
3240 my $bit = LJ
::class_bit
($class);
3241 die "unknown class '$class'" unless defined $bit;
3243 # call add_to_class hook before we modify the
3244 # current $u, so it can make inferences from the
3245 # old $u caps vs the new we say we'll be adding
3246 if (LJ
::are_hooks
('add_to_class')) {
3247 LJ
::run_hooks
('add_to_class', $u, $class);
3250 return LJ
::modify_caps
($u, [$bit], []);
3253 sub remove_from_class
{
3254 my ($u, $class) = @_;
3255 my $bit = LJ
::class_bit
($class);
3256 die "unknown class '$class'" unless defined $bit;
3258 # call remove_from_class hook before we modify the
3259 # current $u, so it can make inferences from the
3260 # old $u caps vs what we'll be removing
3261 if (LJ
::are_hooks
('remove_from_class')) {
3262 LJ
::run_hooks
('remove_from_class', $u, $class);
3265 return LJ
::modify_caps
($u, [], [$bit]);
3270 my $val = $u->selectrow_array("SELECT value FROM userblobcache WHERE userid=? AND bckey=?",
3271 undef, $u->{userid
}, $key);
3272 return undef unless defined $val;
3273 if (my $thaw = eval { Storable
::thaw
($val); }) {
3280 my ($u, $key, $value, $expr) = @_;
3282 $expr ||= $now + 86400;
3283 $expr += $now if $expr < 315532800; # relative to absolute time
3284 $value = Storable
::nfreeze
($value) if ref $value;
3285 $u->do("REPLACE INTO userblobcache (userid, bckey, value, timeexpire) VALUES (?,?,?,?)",
3286 undef, $u->{userid
}, $key, $value, $expr);
3289 # returns array of LJ::Entry objects, ignoring security
3290 sub recent_entries
{
3291 my ($u, %opts) = @_;
3292 my $remote = delete $opts{'filtered_for'} || LJ
::get_remote
();
3293 my $count = delete $opts{'count'} || 50;
3294 my $order = delete $opts{'order'} || "";
3295 die "unknown options" if %opts;
3298 my @recent = LJ
::get_recent_items
({
3301 userid
=> $u->{userid
},
3302 clusterid
=> $u->{clusterid
},
3306 die "Error loading recent items: $err" if $err;
3309 foreach my $ri (@recent) {
3310 my $entry = LJ
::Entry
->new($u, jitemid
=> $ri->{itemid
});
3312 # FIXME: populate the $entry with security/posterid/alldatepart/ownerid/rlogtime
3317 # front-end to recent_entries, which forces the remote user to be
3318 # the owner, so we get everything.
3319 sub all_recent_entries
{
3322 $opts{filtered_for
} = $u;
3323 return $u->recent_entries(%opts);
3326 sub sms_active_number
{
3328 return LJ
::SMS
->uid_to_num($u, verified_only
=> 1);
3331 sub sms_pending_number
{
3333 my $num = LJ
::SMS
->uid_to_num($u, verified_only
=> 0);
3334 return undef unless $num;
3335 return $num if LJ
::SMS
->num_is_pending($num);
3339 # this method returns any mapped number for the user,
3340 # regardless of its verification status
3341 sub sms_mapped_number
{
3343 return LJ
::SMS
->uid_to_num($u, verified_only
=> 0);
3349 # active if the user has a verified sms number
3350 return LJ
::SMS
->configured_for_user($u);
3356 # pending if user has an unverified number
3357 return LJ
::SMS
->pending_for_user($u);
3360 sub sms_register_time_remaining
{
3363 return LJ
::SMS
->num_register_time_remaining($u);
3366 sub sms_num_instime
{
3369 return LJ
::SMS
->num_instime($u->sms_mapped_number);
3372 sub set_sms_number
{
3373 my ($u, $num, %opts) = @_;
3374 my $verified = delete $opts{verified
};
3376 # these two are only checked if $num, because it's possible
3377 # to just pass ($u, undef, undef) to delete the mapping
3379 croak
"invalid number" unless $num =~ /^\+\d+$/;
3380 croak
"invalid verified flag" unless $verified =~ /^[YN]$/;
3383 return LJ
::SMS
->replace_mapping($u, $num, $verified);
3386 sub set_sms_number_verified
{
3387 my ($u, $verified) = @_;
3389 return LJ
::SMS
->set_number_verified($u, $verified);
3392 sub sms_message_count
{
3394 return LJ
::SMS
->message_count($u, @_);
3397 sub sms_sent_message_count
{
3399 return LJ
::SMS
->sent_message_count($u, @_);
3402 sub delete_sms_number
{
3404 return LJ
::SMS
->replace_mapping($u, undef);
3408 # no_quota = don't check user quota or deduct from their quota for sending a message
3410 my ($u, $msg, %opts) = @_;
3414 croak
"invalid user object for object method"
3416 croak
"invalid LJ::SMS::Message object to send"
3417 unless $msg && $msg->isa("LJ::SMS::Message");
3419 my $ret = $msg->send(%opts);
3425 my ($u, $msgtext, %opts) = @_;
3427 my $msg = LJ
::SMS
::Message
->new(
3431 body_text
=> $msgtext,
3434 # if user specified a class_key for send, set it on
3436 if ($opts{class_key
}) {
3437 $msg->class_key($opts{class_key
});
3443 sub sms_quota_remaining
{
3444 my ($u, $type) = @_;
3446 return LJ
::SMS
->sms_quota_remaining($u, $type);
3450 my ($u, $qty, $type) = @_;
3452 return LJ
::SMS
->add_sms_quota($u, $qty, $type);
3456 my ($u, $qty, $type) = @_;
3458 return LJ
::SMS
->set_sms_quota($u, $qty, $type);
3463 return LJ
::SMS
->max_sms_bytes($u);
3466 sub max_sms_substr
{
3467 my ($u, $text, %opts) = @_;
3468 return LJ
::SMS
->max_sms_substr($u, $text, %opts);
3471 sub subtract_sms_quota
{
3472 my ($u, $qty, $type) = @_;
3474 return LJ
::SMS
->subtract_sms_quota($u, $qty, $type);
3479 return $u->{journaltype
} eq "Y";
3484 return $u->{journaltype
} eq "C";
3486 *is_comm
= \
&is_community
;
3490 return $u->{journaltype
} eq "S";
3495 return $u->{journaltype
} eq "N";
3500 return $u->{journaltype
} eq "P";
3502 *is_personal
= \
&is_person
;
3506 return $u->{journaltype
} eq "I";
3511 return $u->{journaltype
} eq "R";
3514 ## We trust OpenID users if they are either from trusted OpenID provider or
3515 ## have e-mail validated. During e-mail validation, they answer CAPTCHA test.
3516 ## Trusted OpenID users are like registered user, untrusted are like anonymous
3517 sub is_trusted_identity
{
3519 return unless $u->is_identity;
3521 return 1 if $u->is_validated;
3523 my $id = $u->identity;
3525 if ($id->short_code eq 'openid') {
3526 ## Check top-to-down domain names in list of trusted providers:
3527 ## asdf.openid.somewhere.com -> openid.somewhere.com -> somewhere.com
3529 if ($url and my $uri = URI
->new($url)) {
3530 return unless $uri->can('host');
3531 my $host = $uri->host;
3532 while ($host =~ /\./) {
3533 return 1 if $LJ::TRUSTED_OPENID_PROVIDERS
{$host};
3534 # remove first domain name (or whatever) with dot
3535 $host =~ s/^.*?\.//;
3545 # return the journal type as a name
3546 sub journaltype_readable
{
3557 }->{$u->{journaltype
}};
3562 my $inviterid = LJ
::load_rel_user
($u, 'I');
3564 return LJ
::load_userid
($inviterid);
3569 return LJ
::Subscription
->subscriptions_of_user($u);
3572 sub subscription_count
{
3574 return scalar LJ
::Subscription
->subscriptions_of_user($u);
3577 # this is the count used to check the maximum subscription count
3578 sub active_inbox_subscription_count
{
3580 return $u->subscriptions_count;
3583 sub max_subscriptions
{
3585 return $u->get_cap('subscriptions');
3588 sub can_add_inbox_subscription
{
3590 return $u->active_inbox_subscription_count >= $u->max_subscriptions ?
0 : 1;
3593 # subscribe to an event
3595 my ($u, %opts) = @_;
3596 croak
"No subscription options" unless %opts;
3598 return LJ
::Subscription
->create($u, %opts);
3601 # unsubscribe from an event(s)
3603 my ($u, %opts) = @_;
3604 croak
"No subscription options" unless %opts;
3606 # find all matching subscriptions
3607 my @subs = LJ
::Subscription
->find($u, %opts);
3613 # run delete method on each subscription
3622 sub subscribe_entry_comments_via_sms
{
3623 my ($u, $entry) = @_;
3624 croak
"Invalid LJ::Entry passed"
3625 unless $entry && $entry->isa("LJ::Entry");
3627 # don't subscribe if user is over subscription limit
3628 return unless $u->can_add_inbox_subscription;
3631 ( event
=> "LJ::Event::JournalNewComment",
3633 arg1
=> $entry->ditemid, );
3636 ( method
=> "LJ::NotificationMethod::SMS",
3640 ( method
=> "LJ::NotificationMethod::Inbox",
3646 # search for a subscription
3647 *find_subscriptions
= \
&has_subscription
;
3648 sub has_subscription
{
3649 my ($u, %params) = @_;
3650 croak
"No parameters" unless %params;
3652 $params{postprocess
} = $u->{__subscriptions
}
3653 unless $params{postprocess
};
3655 return LJ
::Subscription
->find($u, %params);
3658 # interim solution while legacy/ESN notifications are both happening:
3659 # checks possible subscriptions to see if user will get an ESN notification
3660 # THIS IS TEMPORARY. should only be called by talklib.
3661 # params: journal, arg1 (entry ditemid), arg2 (comment talkid)
3663 my ($u, %params) = @_;
3665 $params{event
} = "LJ::Event::JournalNewComment";
3666 $params{method
} = "LJ::NotificationMethod::Email";
3670 # did they subscribe to the parent comment?
3671 $has_sub = LJ
::Subscription
->find($u, %params);
3672 return $has_sub if $has_sub;
3674 # remove the comment-specific parameter, then check for an entry subscription
3676 $has_sub = LJ
::Subscription
->find($u, %params);
3677 return $has_sub if $has_sub;
3679 # remove the entry-specific parameter, then check if they're subscribed to the entire journal
3681 $has_sub = LJ
::Subscription
->find($u, %params);
3685 # delete all of a user's subscriptions
3686 sub delete_all_subscriptions
{
3689 ## Logging for delete all subscriptions
3690 my $remote = LJ
::get_remote
();
3691 my $admin = $remote || LJ
::load_user
('system');
3692 my $subs_number = scalar $u->subscriptions;
3693 LJ
::statushistory_add
( $u, $admin, 'remove_subs', $subs_number )
3696 return LJ
::Subscription
->delete_all_subs($u);
3699 # delete all of a user's subscriptions
3700 sub delete_all_inactive_subscriptions
{
3704 ## Logging for delete all subscriptions
3705 my $remote = LJ
::get_remote
();
3706 my $admin = $remote || LJ
::load_user
('system');
3707 my $set = LJ
::Subscription
::GroupSet
->fetch_for_user($u);
3708 my @inactive_groups = grep { !$_->active } $set->groups;
3709 my $subs_number = scalar @inactive_groups;
3710 LJ
::statushistory_add
( $u, $admin, 'remove_subs', $subs_number )
3713 return LJ
::Subscription
->delete_all_inactive_subs($u, $dryrun);
3716 # What journals can this user post to?
3717 sub posting_access_list
{
3722 my $ids = LJ
::load_rel_target
($u, 'P');
3723 my $us = LJ
::load_userids
(@
$ids);
3724 foreach my $u ( values %$us ) {
3725 next unless $u && $u->is_visible && $u->database_cluster_up;
3729 return sort { $a->username cmp $b->username } @res;
3732 # can $u post to $targetu?
3734 my ($u, $targetu) = @_;
3735 return unless $u && $targetu;
3736 return LJ
::can_use_journal
($u->id, $targetu->user);
3739 sub delete_and_purge_completely
{
3741 # TODO: delete from user tables
3742 # TODO: delete from global tables
3743 my $dbh = LJ
::get_db_writer
();
3745 my @tables = qw(user useridmap priv_map infohistory email password);
3746 foreach my $table (@tables) {
3747 $dbh->do("DELETE FROM $table WHERE userid=?", undef, $u->id);
3750 LJ
::RelationService
->delete_and_purge_completely($u);
3752 $dbh->do("DELETE FROM email_aliases WHERE alias=?", undef, $u->user . "\@$LJ::USER_DOMAIN");
3754 $dbh->do("DELETE FROM community WHERE userid=?", undef, $u->id)
3755 if $u->is_community;
3756 $dbh->do("DELETE FROM syndicated WHERE userid=?", undef, $u->id)
3757 if $u->is_syndicated;
3758 $dbh->do("DELETE FROM content_flag WHERE journalid=? OR reporterid=?", undef, $u->id, $u->id);
3763 # Returns 'rich' or 'plain' depending on user's
3764 # setting of which editor they would like to use
3765 # and what they last used
3766 sub new_entry_editor
{
3769 my $editor = $u->prop('entry_editor');
3770 return 'plain' if $editor eq 'always_plain'; # They said they always want plain
3771 return 'rich' if $editor eq 'always_rich'; # They said they always want rich
3772 return $editor if $editor =~ /(rich|plain)/; # What did they last use?
3773 return $LJ::DEFAULT_EDITOR
; # Use config default
3776 # Returns the NotificationInbox for this user
3777 *inbox
= \
¬ification_inbox
;
3778 sub notification_inbox
{
3780 return LJ
::NotificationInbox
->new($u);
3783 sub new_message_count
{
3785 my $inbox = $u->notification_inbox;
3786 my $count = $inbox->unread_count;
3791 sub notification_archive
{
3793 return LJ
::NotificationArchive
->new($u);
3797 sub can_receive_message
{
3798 my ($u, $sender) = @_;
3800 my $opt_usermsg = $u->opt_usermsg;
3801 return 0 if ($opt_usermsg eq 'N' || !$sender);
3802 return 0 if ($u->has_banned($sender));
3803 return 0 if ($opt_usermsg eq 'M' && !$u->is_mutual_friend($sender));
3804 return 0 if ($opt_usermsg eq 'F' && !$u->is_friend($sender));
3809 # opt_usermsg options
3810 # Y - Registered Users
3812 # M - Mutual Friends
3817 if ($u->raw_prop('opt_usermsg') =~ /^(Y|F|M|N)$/) {
3818 return $u->raw_prop('opt_usermsg');
3820 return 'N' if ($u->underage || $u->is_child);
3821 return 'M' if ($u->is_minor);
3826 sub view_control_strip
{
3829 LJ
::run_hook
('control_strip_propcheck', $u, 'view_control_strip') unless $LJ::DISABLED
{control_strip_propcheck
};
3831 my $prop = $u->raw_prop('view_control_strip');
3832 return 0 if $prop =~ /^off/;
3834 return 'dark' if $prop eq 'forced';
3839 sub show_control_strip
{
3842 LJ
::run_hook
('control_strip_propcheck', $u, 'show_control_strip') unless $LJ::DISABLED
{control_strip_propcheck
};
3844 my $prop = $u->raw_prop('show_control_strip');
3845 return 0 if $prop =~ /^off/;
3847 return 'dark' if $prop eq 'forced';
3852 # when was this account created?
3857 return $u->{_cache_timecreate
} if $u->{_cache_timecreate
};
3859 my $memkey = [$u->id, "tc:" . $u->id];
3860 my $timecreate = LJ
::MemCache
::get
($memkey);
3862 $u->{_cache_timecreate
} = $timecreate;
3866 my $dbr = LJ
::get_db_reader
() or die "No db";
3867 my $when = $dbr->selectrow_array("SELECT timecreate FROM userusage WHERE userid=?", undef, $u->id);
3869 $timecreate = LJ
::TimeUtil
->mysqldate_to_time($when);
3870 $u->{_cache_timecreate
} = $timecreate;
3871 LJ
::MemCache
::set
($memkey, $timecreate, 60*60*24);
3876 # when was last time this account updated?
3880 my $timeupdate = LJ
::get_timeupdate_multi
($u->id);
3881 return $timeupdate->{$u->id};
3884 # when was last time new public entry was created
3885 # (fast reposts are excluded)
3886 sub last_public_entry_time
{
3887 my ($u, %opts) = @_;
3889 my $key = "lpt.".$u->id;
3890 my $attr = '_cache_last_public_time';
3892 return $u->{$attr} if defined $u->{$attr};
3894 my $redis = LJ
::Redis
->get_connection;
3896 my $lastpublic = $redis && !$opts{nocache
} ?
$redis->get($key) : undef;
3898 if (defined $lastpublic) {
3899 $u->{$attr} = $lastpublic;
3905 my $is_person = $u->is_person;
3908 'userid' => $u->userid,
3909 'clusterid' => $u->clusterid,
3913 'security' => "public",
3914 'load_props' => $is_person ?
1 : 0,
3920 my ($skip, $itemshow) = (0, 10);
3922 until ( $lastpublic ) {
3925 $req->{'entry_objects'} = \
@entries;
3927 $req->{'skip'} = $skip;
3928 $req->{'itemshow'} = $is_person ?
$itemshow : 1;
3930 LJ
::get_recent_items
($req);
3933 warn "Error loading recent_entries: $err";
3938 last unless @entries;
3940 foreach my $entry (@entries) {
3941 unless( $is_person && $entry->prop('repost') && $entry->prop('repost') eq 'e' ) {
3942 $lastpublic = LJ
::TimeUtil
->mysqldate_to_time($entry->{logtime
}, 0);
3950 if (defined $lastpublic) {
3951 $u->{$attr} = $lastpublic;
3952 $redis->set($key, $lastpublic) if $redis;
3959 sub get_last_public_entry_time_multi
{
3960 my ($class, $uids) = @_;
3962 return unless $uids && @
$uids;
3964 my $redis = LJ
::Redis
->get_connection || return;
3966 my @keys = map {"lpt.$_"} @
$uids;
3968 my @res = $redis->mget(@keys);
3970 my $res = { map { $uids->[$_] => $res[$_] } (0..$#res) };
3975 # set the last public entry time
3976 # do it only if key already exists, i.e. if somebody has already request it
3977 sub set_last_public_entry_time
{
3978 my ($u, $lastpublic) = @_;
3980 my $key = "lpt.".$u->id;
3981 my $attr = '_cache_last_public_time';
3983 $u->{$attr} = $lastpublic;
3985 my $redis = LJ
::Redis
->get_connection || return;
3987 return unless $redis->exists($key);
3988 $redis->set($key, $lastpublic);
3992 # delete last public entry time
3993 sub del_last_public_entry_time
{
3996 my $key = "lpt.".$u->id;
3997 my $attr = '_cache_last_public_time';
4001 my $redis = LJ
::Redis
->get_connection || return;
4005 # can this user use ESN?
4008 return 0 if $LJ::DISABLED
{esn
};
4009 my $disable = $LJ::DISABLED
{esn_ui
};
4010 return 1 unless $disable;
4012 if (ref $disable eq 'CODE') {
4013 return $disable->($u) ?
0 : 1;
4016 return $disable ?
0 : 1;
4021 return LJ
::SMS
->can_use_sms($u);
4024 sub can_use_ljphoto
{
4027 return $u->is_personal ?
1 : 0;
4030 sub can_upload_photo
{
4033 return 0 unless $u->can_use_ljphoto();
4034 return $u->get_cap('disk_quota') ?
1 : 0;
4037 sub ajax_auth_token
{
4039 return LJ
::Auth
->ajax_auth_token($u, @_);
4042 sub check_ajax_auth_token
{
4044 return LJ
::Auth
->check_ajax_auth_token($u, @_);
4056 return "I,$u->{userid}" if $u->{journaltype
} eq "I";
4060 # returns username for display
4061 sub display_username
{
4063 my $need_cut = shift || 0;
4065 my $username = $u->{user
};
4066 if ($u->is_identity){
4067 $username = $u->display_name;
4069 my $short_name = substr ($username, 0, 16);
4070 if ($username eq $short_name) {
4071 $username = $short_name;
4073 $username = $short_name . "...";
4078 return LJ
::ehtml
($username);
4081 # returns the user-specified name of a journal exactly as entered
4087 # returns the user-specified name of a journal in valid UTF-8
4090 LJ
::text_out
(\
$u->{name
});
4094 # returns the user-specified name of a journal in valid UTF-8
4095 # and with HTML escaped
4098 return LJ
::ehtml
($u->name_raw);
4105 return int($u->{userid
});
4110 return $u->{clusterid
};
4113 # class method, returns { clusterid => [ uid, uid ], ... }
4114 sub split_by_cluster
{
4118 my $us = LJ
::load_userids
(@uids);
4121 foreach my $u (values %$us) {
4123 push @
{$clusters{$u->clusterid}}, $u->id;
4129 ## Returns current userhead for user.
4132 my $opts = +shift || {};
4134 my $userhead_override;
4135 LJ
::run_hooks
( 'override_userhead', $u, \
$userhead_override );
4136 return ( $userhead_override, 16, 16 ) if $userhead_override;
4138 my $head_size = $opts->{head_size
};
4140 my $userhead = 'userinfo.gif?v=17080';
4141 my $userhead_w = 16;
4142 my $userhead_h = undef;
4145 my ($icon, $size) = LJ
::run_hook
("head_icon",
4146 $u, head_size
=> $head_size);
4150 $userhead_w = $size || 16;
4151 $userhead_h = $userhead_w;
4152 return $userhead, $userhead_w, $userhead_h;
4156 if (!$LJ::IS_SSL
&& ($icon = $u->custom_usericon)) {
4159 } elsif ($u->is_community) {
4161 $userhead = "comm_${head_size}.gif";
4162 $userhead_w = $head_size;
4164 $userhead = "community.gif?v=556";
4167 } elsif ($u->is_syndicated) {
4169 $userhead = "syn_${head_size}.gif";
4170 $userhead_w = $head_size;
4172 $userhead = "syndicated.gif?v=6283";
4175 } elsif ($u->is_news) {
4177 $userhead = "news_${head_size}.gif";
4178 $userhead_w = $head_size;
4180 $userhead = "newsinfo.gif?v=2990";
4183 } elsif ($u->is_identity) {
4184 my $ident = $u->identity;
4185 my $params = $ident ?
$ident->ljuser_display_params($u, $opts) : {};
4186 $userhead = $params->{'userhead'} || $userhead;
4187 $userhead_w = $params->{'userhead_w'} || $userhead_w;
4188 $userhead_h = $params->{'userhead_h'} || $userhead_h;
4191 $userhead = "user_${head_size}.gif";
4192 $userhead_w = $head_size;
4194 $userhead = "userinfo.gif?v=17080";
4198 $userhead_h ||= $userhead_w;
4199 return $userhead, $userhead_w, $userhead_h;
4204 my ($userhead) = $u->userhead;
4205 return undef unless $userhead;
4206 return $userhead if $userhead =~ m
|^https?
://|;
4207 return join '', $LJ::IMGPREFIX
, '/', $userhead, '?v=', $LJ::CURRENT_VERSION
;
4212 return LJ
::get_bio
($u);
4215 # if bio_absent is set to "yes", bio won't be updated
4217 my ($u, $text, $bio_absent) = @_;
4218 $bio_absent = "" unless $bio_absent;
4220 my $oldbio = $u->bio;
4221 my $newbio = $bio_absent eq "yes" ?
$oldbio : $text;
4222 my $has_bio = ($newbio =~ /\S/) ?
"Y" : "N";
4225 'has_bio' => $has_bio,
4227 LJ
::update_user
($u, \
%update);
4229 # update their bio text
4230 if (($oldbio ne $text) && $bio_absent ne "yes") {
4231 if ($has_bio eq "N") {
4232 $u->do("DELETE FROM userbio WHERE userid=?", undef, $u->id);
4233 $u->dudata_set('B', 0, 0);
4235 $u->do("REPLACE INTO userbio (userid, bio) VALUES (?, ?)",
4236 undef, $u->id, $text);
4237 $u->dudata_set('B', 0, length($text));
4239 LJ
::MemCache
::set
([$u->id, "bio:" . $u->id], $text);
4246 # if unset, default to on
4247 my $prop = $u->raw_prop('opt_ctxpopup') || 'Y';
4249 return $prop eq 'Y';
4252 # opt_imagelinks format:
4253 # 0|1 - replace images with placeholders at friends page
4255 # 0|1 - replace images with placeholders in comments at entry page
4256 sub get_opt_imagelinks
{
4258 my $opt = $u->prop("opt_imagelinks") || "0:0";
4259 $opt = "0:0" unless $opt;
4260 $opt = "1:0" unless $opt =~ /^\d\:\d$/;
4264 sub opt_placeholders_comments
{
4266 my $opt = $u->get_opt_imagelinks;
4268 if ( $opt =~ /^\d\:(\d)$/ ) {
4275 sub get_opt_videolinks
{
4277 my $opt = $u->raw_prop("opt_embedplaceholders") || "0:0";
4278 $opt = "0:0" if ! $opt || $opt eq 'N';
4279 $opt = "1:0" unless $opt =~ /^\d\:\d$/;
4283 sub opt_embedplaceholders
{
4285 my $opt = $u->get_opt_videolinks;
4287 if ( $opt =~ /^(\d)\:\d$/ ) {
4294 sub opt_videoplaceholders_comments
{
4296 my $opt = $u->get_opt_videolinks;
4298 if ( $opt =~ /^\d\:(\d)$/ ) {
4305 sub opt_getting_started
{
4308 # if unset, default to on
4309 my $prop = $u->raw_prop('opt_getting_started') || 'Y';
4314 sub opt_stylealwaysmine
{
4317 return 0 unless $u->can_use_stylealwaysmine;
4318 return $u->raw_prop('opt_stylealwaysmine') eq 'Y' ?
1 : 0;
4321 sub can_use_stylealwaysmine
{
4325 return 0 if $LJ::DISABLED
{stylealwaysmine
};
4326 $ret = LJ
::run_hook
("can_use_stylealwaysmine", $u);
4330 sub opt_commentsstylemine
{
4333 return 0 unless $u->can_use_commentsstylemine;
4335 if ( $u->raw_prop('opt_stylemine') ) {
4336 $u->set_prop( opt_stylemine
=> 0 );
4337 $u->set_prop( opt_commentsstylemine
=> 'Y' );
4340 return $u->raw_prop('opt_commentsstylemine') eq 'Y'?
1 : 0;
4343 sub can_use_commentsstylemine
{
4344 return 0 unless LJ
::is_enabled
('comments_style_mine');
4348 sub has_enabled_getting_started
{
4351 return $u->opt_getting_started eq 'Y' ?
1 : 0;
4356 # ***************************** OBSOLETE ************************************* #
4358 # This method sends messages using djabberd servers
4359 # which have been changed with Ejabberd. So method is obsolete.
4360 # Code to send messages to Ejabberd is in cgi-bin/LJ/NotificationMethod/IM.pm
4363 # find what servers a user is logged in to, and send them an IM
4364 # returns true if sent, false if failure or user not logged on
4365 # Please do not call from web context
4367 my ($self, %opts) = @_;
4369 croak
"Can't call in web context" if LJ
::is_web_context
();
4371 my $from = delete $opts{from
};
4372 my $msg = delete $opts{message
} or croak
"No message specified";
4374 croak
"No from or bot jid defined" unless $from || $LJ::JABBER_BOT_JID
;
4376 my @resources = keys %{LJ
::Jabber
::Presence
->get_resources($self)} or return 0;
4378 my $res = $resources[0] or return 0; # FIXME: pick correct server based on priority?
4379 my $pres = LJ
::Jabber
::Presence
->new($self, $res) or return 0;
4380 my $ip = $LJ::JABBER_SERVER_IP
|| '127.0.0.1';
4382 my $sock = IO
::Socket
::INET
->new(PeerAddr
=> "${ip}:5200")
4385 my $vhost = $LJ::DOMAIN
;
4387 my $to_jid = $self->user . '@' . $LJ::DOMAIN
;
4388 my $from_jid = $from ?
$from->user . '@' . $LJ::DOMAIN
: $LJ::JABBER_BOT_JID
;
4390 my $emsg = LJ
::exml
($msg);
4391 my $stanza = LJ
::eurl
(qq{<message to
="$to_jid" from
="$from_jid"><body
>$emsg</body></message
>});
4393 print $sock "send_stanza $vhost $to_jid $stanza\n";
4395 my $start_time = time();
4399 vec($rin, fileno($sock), 1) = 1;
4400 select(my $rout=$rin, undef, undef, 1);
4401 if (vec($rout, fileno($sock), 1)) {
4403 return 1 if $ln =~ /^OK/;
4406 last if time() > $start_time + 5;
4412 # returns whether or not the user is online on jabber
4413 sub jabber_is_online
{
4416 return keys %{LJ
::Jabber
::Presence
->get_resources($u)} ?
1 : 0;
4419 sub esn_inbox_default_expand
{
4422 my $prop = $u->raw_prop('esn_inbox_default_expand');
4423 return $prop ne 'N';
4427 my ($u, $ratename, $count, $opts) = @_;
4428 LJ
::rate_log
($u, $ratename, $count, $opts);
4432 my ($u, $ratename, $count, $opts) = @_;
4433 LJ
::rate_check
($u, $ratename, $count, $opts);
4438 return $u->{statusvis
};
4443 return $u->{statusvisdate
};
4446 sub statusvisdate_unix
{
4448 return LJ
::TimeUtil
->mysqldate_to_time($u->{statusvisdate
});
4451 # returns list of all previous statuses of the journal
4452 # in order from newest to oldest
4453 sub get_previous_statusvis
{
4456 my $records = LJ
::User
::Userlog
->get_records( $u,
4457 'action' => 'accountstatus' );
4460 foreach my $record (@
$records) {
4461 push @statusvis, $record->extra_unpacked->{'old'};
4467 # set_statusvis only change statusvis parameter, all accompanied actions are done in set_* methods
4469 my ($u, $statusvis) = @_;
4471 LJ
::MemCache
::delete('u:s:' . $u->userid);
4473 croak
"Invalid statusvis: $statusvis"
4474 unless $statusvis =~ /^(?
:
4485 # log the change to userlog, but only in case we have a valid clusterid;
4486 # this check addresses the case when an expunged user gets suspended
4487 if ( $u->clusterid ) {
4488 # remote looked up by create()
4489 LJ
::User
::UserlogRecord
::AccountStatus
->create( $u,
4490 'old' => $u->statusvis, 'new' => $statusvis );
4494 my $ret = LJ
::update_user
($u, { statusvis
=> $statusvis,
4495 raw
=> 'statusvisdate=NOW()' });
4497 LJ
::run_hooks
("props_changed", $u, {statusvis
=> $statusvis});
4507 LJ
::run_hooks
("account_will_be_visible", $u);
4508 return $u->set_statusvis('V');
4513 my $res = $u->set_statusvis('D');
4515 # run any account cancellation hooks
4516 LJ
::run_hooks
("account_delete", $u);
4522 return $u->set_statusvis('X');
4526 my ($u, $who, $reason, $errref, $public_reason) = @_;
4527 die "Not enough parameters for LJ::User::set_suspended call" unless $who and $reason;
4529 my $res = $u->set_statusvis('S');
4531 $$errref = "DB error while setting statusvis to 'S'" if ref $errref;
4535 LJ
::statushistory_add
($u, $who, "suspend", $reason);
4537 # close all spamreports on this user
4538 my $dbh = LJ
::get_db_writer
();
4539 $dbh->do("UPDATE spamreports SET state='closed' WHERE posterid = ? AND state='open'", undef, $u->userid);
4541 # close all botreports on this user
4542 require LJ
::BotReport
;
4543 LJ
::BotReport
->close_requests($u->userid);
4546 LJ
::run_hooks
("account_cancel", $u);
4547 LJ
::run_hooks
("account_suspend", $u);
4549 if ($public_reason) {
4550 LJ
::statushistory_add
($u, $who, "suspend_reason", $public_reason);
4551 $u->set_prop('suspend_reason' => $public_reason);
4554 if (my $err = LJ
::run_hook
("cdn_purge_userpics", $u)) {
4555 $$errref = $err if ref $errref and $err;
4559 return $res; # success
4562 # sets a user to visible, but also does all of the stuff necessary when a suspended account is unsuspended
4563 # this can only be run on a suspended account
4564 sub set_unsuspended
{
4565 my ($u, $who, $reason, $errref) = @_;
4566 die "Not enough parameters for LJ::User::set_unsuspended call" unless $who and $reason;
4568 unless ($u->is_suspended) {
4569 $$errref = "User isn't suspended" if ref $errref;
4573 my $res = $u->set_statusvis('V');
4575 $$errref = "DB error while setting statusvis to 'V'" if ref $errref;
4579 LJ
::statushistory_add
($u, $who, "unsuspend", $reason);
4580 LJ
::run_hooks
("account_unsuspend", $u);
4582 return $res; # success
4587 return $u->set_statusvis('L');
4592 return $u->set_statusvis('M');
4597 return $u->set_statusvis('O');
4602 return $u->set_statusvis('R');
4605 # returns if this user is considered visible
4608 return ($u->statusvis eq 'V' && $u->clusterid != 0);
4613 return $u->statusvis eq 'D';
4618 return $u->statusvis eq 'X' || $u->clusterid == 0;
4623 return $u->statusvis eq 'S';
4628 return $u->statusvis eq 'L';
4633 return $u->statusvis eq 'M';
4638 return $u->statusvis eq 'O';
4643 return $u->statusvis eq 'R';
4651 *get_post_count
= \
&number_of_posts
;
4652 sub number_of_posts
{
4653 my ($u, %opts) = @_;
4655 # to count only a subset of all posts
4657 $opts{return} = 'count';
4658 return $u->get_post_ids(%opts);
4661 my $memkey = [$u->{userid
}, "log2ct:$u->{userid}"];
4662 my $expire = time() + 3600*24*2; # 2 days
4663 return LJ
::MemCache
::get_or_set
($memkey, sub {
4664 return $u->selectrow_array("SELECT COUNT(*) FROM log2 WHERE journalid=?",
4665 undef, $u->{userid
});
4669 # return the number if public posts
4670 sub number_of_public_posts
{
4672 my $memkey = [$u->{userid
}, "log2publicct:$u->{userid}"];
4673 my $expire = time() + 300; # 5 min
4674 return LJ
::MemCache
::get_or_set
($memkey, sub {
4675 return $u->get_post_ids(return => 'count', security
=> 'public');
4680 # return the number of posts that the user actually posted themselves
4681 sub number_of_posted_posts
{
4684 my $num = $u->number_of_posts;
4685 $num-- if LJ
::run_hook
('user_has_auto_post', $u);
4691 # name: LJ::get_post_ids
4692 # des: Given a user object and some options, return the number of posts or the
4693 # posts'' IDs (jitemids) that match.
4694 # returns: number of matching posts, <strong>or</strong> IDs of
4695 # matching posts (default).
4697 # des-opts: 'security' - [public|private|usemask]
4698 # 'allowmask' - integer for friends-only or custom groups
4699 # 'start_date' - UTC date after which to look for match
4700 # 'end_date' - UTC date before which to look for match
4701 # 'return' - if 'count' just return the count
4702 # TODO: Add caching?
4705 my ($u, %opts) = @_;
4707 my $query = 'SELECT';
4708 my @vals; # parameters to query
4710 if ($opts{'start_date'} || $opts{'end_date'}) {
4711 croak
"start or end date not defined"
4712 if (!$opts{'start_date'} || !$opts{'end_date'});
4714 if (!($opts{'start_date'} >= 0) || !($opts{'end_date'} >= 0) ||
4715 !($opts{'start_date'} <= $LJ::EndOfTime
) ||
4716 !($opts{'end_date'} <= $LJ::EndOfTime
) ) {
4721 # return count or jitemids
4722 if ($opts{'return'} eq 'count') {
4723 $query .= " COUNT(*)";
4725 $query .= " jitemid";
4728 # from the journal entries table for this user
4729 $query .= " FROM log2 WHERE journalid=?";
4730 push(@vals, $u->{userid
});
4732 # filter by security
4733 if ($opts{'security'}) {
4734 $query .= " AND security=?";
4735 push(@vals, $opts{'security'});
4736 # If friends-only or custom
4737 if ($opts{'security'} eq 'usemask' && $opts{'allowmask'}) {
4738 $query .= " AND allowmask=?";
4739 push(@vals, $opts{'allowmask'});
4743 if ($opts{posterid
}){
4744 $query .= " AND posterid = ? ";
4745 push @vals => $opts{posterid
};
4747 if ($opts{afterid
}){
4748 $query .= " AND jitemid > ? ";
4749 push @vals => $opts{afterid
};
4752 # filter by date, use revttime as it is indexed
4753 if ($opts{'start_date'} && $opts{'end_date'}) {
4754 # revttime is reverse event time
4755 my $s_date = $LJ::EndOfTime
- $opts{'start_date'};
4756 my $e_date = $LJ::EndOfTime
- $opts{'end_date'};
4757 $query .= " AND revttime<?";
4758 push(@vals, $s_date);
4759 $query .= " AND revttime>?";
4760 push(@vals, $e_date);
4763 # return count or jitemids
4764 if ($opts{'return'} eq 'count') {
4765 return $u->selectrow_array($query, undef, @vals);
4767 my $jitemids = $u->selectcol_arrayref($query, undef, @vals) || [];
4768 die $u->errstr if $u->err;
4775 return unless $u->is_person;
4776 $u->{_password
} ||= LJ
::MemCache
::get_or_set
([$u->{userid
}, "pw:$u->{userid}"], sub {
4777 my $dbh = LJ
::get_db_writer
() or die "Couldn't get db master";
4778 return $dbh->selectrow_array("SELECT password FROM password WHERE userid=?",
4781 return $u->{_password
};
4786 return $u->{journaltype
};
4790 my ($u, $password) = @_;
4791 return LJ
::set_password
($u->id, $password);
4795 my ($u, $email) = @_;
4796 return LJ
::set_email
($u->id, $email);
4803 require LJ
::FBInterface
;
4804 LJ
::FBInterface
->push_user_info( $u->id );
4807 warn "Error running fb_push: $@\n" if $@
&& $LJ::IS_DEV_SERVER
;
4811 my ($u, $priv, $arg) = @_;
4813 my $dbh = LJ
::get_db_writer
();
4815 return 1 if LJ
::check_priv
($u, $priv, $arg);
4817 my $privid = $dbh->selectrow_array("SELECT prlid FROM priv_list".
4818 " WHERE privcode = ?", undef, $priv);
4819 return 0 unless $privid;
4821 $dbh->do("INSERT INTO priv_map (userid, prlid, arg) VALUES (?, ?, ?)",
4822 undef, $u->id, $privid, $arg);
4823 return 0 if $dbh->err;
4825 undef $u->{'_privloaded'}; # to force reloading of privs later
4830 my ($u, $priv, $arg) = @_;
4832 my $dbh = LJ
::get_db_writer
();
4834 return 1 unless LJ
::check_priv
($u, $priv, $arg);
4836 my $privid = $dbh->selectrow_array("SELECT prlid FROM priv_list".
4837 " WHERE privcode = ?", undef, $priv);
4838 return 0 unless $privid;
4840 $dbh->do("DELETE FROM priv_map WHERE userid = ? AND prlid = ? AND arg = ?",
4841 undef, $u->id, $privid, $arg);
4842 return 0 if $dbh->err;
4844 undef $u->{'_privloaded'}; # to force reloading of privs later
4845 undef $u->{'_priv'};
4849 sub revoke_priv_all
{
4850 my ($u, $priv) = @_;
4851 my $dbh = LJ
::get_db_writer
();
4853 my $privid = $dbh->selectrow_array("SELECT prlid FROM priv_list".
4854 " WHERE privcode = ?", undef, $priv);
4855 return 0 unless $privid;
4857 $dbh->do("DELETE FROM priv_map WHERE userid = ? AND prlid = ?",
4858 undef, $u->id, $privid);
4859 return 0 if $dbh->err;
4861 undef $u->{'_privloaded'}; # to force reloading of privs later
4862 undef $u->{'_priv'};
4866 # must be called whenever birthday, location, journal modtime, journaltype, etc.
4867 # changes. see LJ/Directory/PackedUserRecord.pm
4868 sub invalidate_directory_record
{
4872 # LJ::try_our_best_to("invalidate_directory_record", $u->id);
4873 # then elsewhere, map that key to subref. if primary run fails,
4874 # put in schwartz, then have one worker (misc-deferred) to
4877 my $dbs = defined $LJ::USERSEARCH_DB_WRITER ? LJ
::get_dbh
($LJ::USERSEARCH_DB_WRITER
) : LJ
::get_db_writer
();
4878 $dbs->do("UPDATE usersearch_packdata SET good_until=0 WHERE userid=?",
4882 # Used to promote communities in interest search results
4883 sub render_promo_of_community
{
4884 my ($comm, $style) = @_;
4886 return undef unless $comm;
4888 $style ||= 'Vertical';
4890 # get the ljuser link
4891 my $commljuser = $comm->ljuser_display;
4894 my $journal_base = $comm->journal_base;
4896 # get default userpic if any
4897 my $userpic = $comm->userpic;
4898 my $userpic_html = '';
4900 my $userpic_url = $userpic->url;
4901 $userpic_html = qq { <a href
="$journal_base"><img src
="$userpic_url" /></a> };
4904 my $blurb = $comm->prop('comm_promo_blurb') || '';
4906 my $join_link = "$LJ::SITEROOT/community/join.bml?comm=$comm->{user}";
4907 my $watch_link = "$LJ::SITEROOT/friends/add.bml?user=$comm->{user}";
4908 my $read_link = $comm->journal_base;
4910 LJ
::need_res
("stc/lj_base.css");
4912 # if horizontal, userpic needs to come before everything
4916 if (lc $style eq 'horizontal') {
4917 $box_class = 'Horizontal';
4918 $comm_display = qq {
4919 <div
class="Userpic">$userpic_html</div
>
4920 <div
class="Title">Community Promo
</div
>
4921 <div
class="CommLink">$commljuser</div
>
4924 $box_class = 'Vertical';
4925 $comm_display = qq {
4926 <div
class="Title">Community Promo
</div
>
4927 <div
class="CommLink">$commljuser</div
>
4928 <div
class="Userpic">$userpic_html</div
>
4934 <div
class="CommunityPromoBox">
4935 <div
class="$box_class">
4937 <div
class="Blurb">$blurb</div
>
4938 <div
class="Links"><a href
="$join_link">Join
</a> | <a href="$watch_link">Watch</a> |
4939 <a href
="$read_link">Read
</a></div
>
4941 <div
class='ljclear'> 
;</div
>
4952 my $statusvisdate = $u->statusvisdate_unix;
4954 # check admin flag "this journal must not be expunged for abuse team
4955 # investigation". hack: if flag is on, then set statusvisdate to now,
4956 # so that the next time worker bin/worker/expunge-users won't check
4959 # optimization concern: isn't it too much strain checking this prop
4960 # for every user? well, we've got to check this prop for every user
4961 # that seems eligible anyway, and moveucluster isn't supposed to send
4962 # us users who got too recent statusvisdate or something.
4963 if ($u->prop('dont_expunge_journal')) {
4964 LJ
::update_user
($u, { raw
=> 'statusvisdate=NOW()' });
4968 if ($u->is_deleted) {
4970 LJ
::conf_test
($LJ::DAYS_BEFORE_EXPUNGE
) || 30;
4972 return 0 unless $statusvisdate < time() - 86400 * $expunge_days;
4977 if ($u->is_suspended) {
4978 return 0 if $LJ::DISABLED
{'expunge_suspended'};
4981 LJ
::conf_test
($LJ::DAYS_BEFORE_EXPUNGE_SUSPENDED
) || 30;
4983 return 0 unless $statusvisdate < time() - 86400 * $expunge_days;
4991 # Check to see if the user can use eboxes at all
4995 return ref $LJ::DISABLED
{ebox
} ?
!$LJ::DISABLED
{ebox
}->($u) : !$LJ::DISABLED
{ebox
};
4998 # Allow users to choose eboxes if:
4999 # 1. The entire ebox feature isn't disabled AND
5000 # 2. The option to choose eboxes isn't disabled OR
5001 # 3. The option to choose eboxes is disabled AND
5002 # 4. The user already has eboxes turned on
5003 sub can_use_ebox_ui
{
5007 if ($LJ::DISABLED
{ebox_option
}) {
5008 $allow_ebox = $u->prop('journal_box_entries');
5011 return $u->can_use_ebox && $allow_ebox;
5014 # return hashref with intname => intid
5017 my $uints = LJ
::get_interests
($u);
5020 foreach my $int (@
$uints) {
5021 $interests{$int->[1]} = $int->[0]; # $interests{name} = intid
5030 return map { $_->[1] } @
{ LJ
::get_interests
($u) };
5033 sub interest_count
{
5036 # FIXME: fall back to SELECT COUNT(*) if not cached already?
5037 return scalar @
{LJ
::get_interests
($u, { justids
=> 1 })};
5042 LJ
::set_interests
($u, @_);
5045 sub lazy_interests_cleanup
{
5048 my $dbh = LJ
::get_db_writer
();
5050 if ($u->is_community) {
5051 $dbh->do("INSERT IGNORE INTO comminterests SELECT * FROM userinterests WHERE userid=?", undef, $u->id);
5052 $dbh->do("DELETE FROM userinterests WHERE userid=?", undef, $u->id);
5054 $dbh->do("INSERT IGNORE INTO userinterests SELECT * FROM comminterests WHERE userid=?", undef, $u->id);
5055 $dbh->do("DELETE FROM comminterests WHERE userid=?", undef, $u->id);
5058 LJ
::memcache_kill
($u, "intids");
5062 # this will return a hash of information about this user.
5063 # this is useful for JavaScript endpoints which need to dump
5064 # JSON data about users.
5069 username
=> $u->user,
5070 display_username
=> $u->display_username,
5071 display_name
=> $u->display_name,
5072 userid
=> $u->userid,
5073 url_journal
=> $u->journal_base,
5074 url_profile
=> $u->profile_url,
5075 url_allpics
=> $u->allpics_base,
5076 is_comm
=> $u->is_comm,
5077 is_person
=> $u->is_person,
5078 is_syndicated
=> $u->is_syndicated,
5079 is_identity
=> $u->is_identity,
5080 is_shared
=> $u->is_shared,
5082 # Without url_message "Send Message" link should not display
5083 $ret{url_message
} = $u->message_url unless ($u->opt_usermsg eq 'N');
5085 LJ
::run_hook
("extra_info_for_js", $u, \
%ret);
5087 my $up = $u->userpic;
5090 $ret{url_userpic
} = $up->url;
5091 $ret{userpic_w
} = $up->width;
5092 $ret{userpic_h
} = $up->height;
5098 sub postreg_completed
{
5101 return 0 unless $u->bio;
5102 return 0 unless $u->interest_count;
5106 # return if $target is banned from $u's journal
5107 *has_banned
= \
&is_banned
;
5109 my ($u, $target) = @_;
5110 return LJ
::is_banned
($target->userid, $u->userid);
5114 my ($u, $ban_u) = @_;
5116 my $remote = LJ
::get_remote
();
5117 LJ
::User
::UserlogRecord
::BanSet
->create( $u,
5118 'bannedid' => $ban_u->userid, 'remote' => $remote );
5120 LJ
::run_hooks
('ban_set', $u, $ban_u);
5122 $ban_u->clear_cache_friends($u);
5124 return LJ
::set_rel
($u->id, $ban_u->id, 'B');
5127 sub ban_user_multi
{
5128 my ($u, @banlist) = @_;
5130 my $us = LJ
::load_userids
(@banlist);
5131 my $remote = LJ
::get_remote
();
5133 foreach my $banuid (@banlist) {
5135 next unless $us->{$banuid};
5137 LJ
::User
::UserlogRecord
::BanSet
->create( $u,
5138 'bannedid' => $banuid, 'remote' => $remote );
5140 LJ
::run_hooks
('ban_set', $u, $us->{$banuid}) if $us->{$banuid};
5142 $us->{$banuid}->clear_cache_friends($u);
5145 LJ
::set_rel_multi
(map { [$u->id, $_, 'B'] } @banlist);
5150 sub unban_user_multi
{
5151 my ($u, @unbanlist) = @_;
5153 my $us = LJ
::load_userids
(@unbanlist);
5154 my $remote = LJ
::get_remote
();
5156 foreach my $banuid (@unbanlist) {
5158 next unless $us->{$banuid};
5160 LJ
::User
::UserlogRecord
::BanUnset
->create( $u,
5161 'bannedid' => $banuid, 'remote' => $remote );
5163 LJ
::run_hooks
('ban_unset', $u, $us->{$banuid}) if $us->{$banuid};
5165 $us->{$banuid}->clear_cache_friends($u);
5169 LJ
::clear_rel_multi
(map { [$u->id, $_, 'B'] } @unbanlist);
5174 # returns if this user's polls are clustered
5175 sub polls_clustered
{
5177 return $u->dversion >= 8;
5182 return $u->{dversion
};
5185 # take a user on dversion 7 and upgrade them to dversion 8 (clustered polls)
5186 sub upgrade_to_dversion_8
{
5192 # If user has been purged, go ahead and update version
5193 # Otherwise move their polls
5194 my $ok = $u->is_expunged ?
1 : LJ
::Poll
->make_polls_clustered($u, $dbh, $dbhslo, $dbcm);
5196 LJ
::update_user
($u, { 'dversion' => 8 }) if $ok;
5201 # returns if this user can join an adult community or not
5202 # adultref will hold the value of the community's adult content flag
5203 sub can_join_adult_comm
{
5204 my ($u, %opts) = @_;
5206 return 1 unless LJ
::is_enabled
('content_flag');
5208 my $adultref = $opts{adultref
};
5209 my $comm = $opts{comm
} or croak
"No community passed";
5211 my $adult_content = $comm->adult_content_calculated;
5212 $$adultref = $adult_content;
5214 if ($adult_content eq "concepts" && ($u->is_child || !$u->best_guess_age) && $LJ::DISABLED
{'remove_adult_concepts'}) {
5216 } elsif ($adult_content eq "explicit" && ($u->is_minor || !$u->best_guess_age)) {
5226 return LJ
::BetaFeatures
->user_in_beta( $u => $key );
5229 # return the user's timezone based on the prop if it's defined, otherwise best guess
5233 return $u->{'__timezone_offset'}
5234 if exists $u->{'__timezone_offset'};
5237 LJ
::get_timezone
($u, \
$offset);
5239 $u->{'__timezone_offset'} = $offset;
5243 # returns a DateTime object corresponding to a user's "now"
5247 my $now = DateTime
->now;
5249 # if user has timezone, use it!
5250 my $tz = $u->prop("timezone");
5251 return $now unless $tz;
5253 $now = eval { DateTime
->from_epoch(
5262 sub can_admin_content_flagging
{
5265 return 0 unless LJ
::is_enabled
("content_flag");
5266 return 1 if $LJ::IS_DEV_SERVER
;
5267 return LJ
::check_priv
($u, "siteadmin", "contentflag");
5270 sub can_see_content_flag_button
{
5274 return 0 unless LJ
::is_enabled
("content_flag");
5276 my $content = $opts{content
};
5278 # user can't flag any journal they manage nor any entry they posted
5279 # user also can't flag non-public entries
5280 if (LJ
::isu
($content)) {
5281 return 0 if $u->can_manage($content);
5282 } elsif ($content->isa("LJ::Entry")) {
5283 return 0 if $u->equals($content->poster);
5284 return 0 unless $content->security eq "public";
5287 # user can't flag anything if their account isn't at least one month old
5288 my $one_month = 60*60*24*30;
5289 return 0 unless time() - $u->timecreate >= $one_month;
5294 sub can_flag_content
{
5298 return 0 unless $u->can_see_content_flag_button(%opts);
5299 return 0 if LJ
::sysban_check
("contentflag", $u->user);
5300 return 0 unless $u->rate_check("ctflag", 1);
5304 # sometimes when the app throws errors, we want to display "nice"
5305 # text to end-users, while allowing admins to view the actual error message
5306 sub show_raw_errors
{
5309 return 1 if $LJ::IS_DEV_SERVER
;
5311 return 1 if LJ
::check_priv
($u, "supporthelp");
5312 return 1 if LJ
::check_priv
($u, "supportviewscreened");
5313 return 1 if LJ
::check_priv
($u, "siteadmin");
5318 # defined by the user
5319 # returns 'none', 'concepts' or 'explicit'
5323 my $prop_value = $u->prop('adult_content');
5325 return $prop_value ?
$prop_value : "none";
5328 # defined by an admin
5329 sub admin_content_flag
{
5332 return $u->prop('admin_content_flag');
5335 # uses both user- and admin-defined props to figure out the adult content level
5336 sub adult_content_calculated
{
5339 $u->preload_props(qw
/admin_content_flag adult_content/);
5340 return "explicit" if $u->admin_content_flag eq "explicit_adult";
5341 return $u->adult_content;
5344 sub show_graphic_previews
{
5347 my $prop_value = $u->prop('show_graphic_previews');
5349 my $hook_rv = LJ
::run_hook
("override_show_graphic_previews", $u, $prop_value);
5350 return $hook_rv if defined $hook_rv;
5354 } elsif ($prop_value eq "explicit_on") {
5356 } elsif ($prop_value eq "explicit_off") {
5363 sub should_show_graphic_previews
{
5366 return $u->show_graphic_previews eq "on" ?
1 : 0;
5369 # name: can_super_manage
5370 # des: Given a target user and determines that the user is an supermaintainer of community
5371 # returns: bool: true if supermaitainer, otherwise fail
5373 # des-u: user object or userid of community
5374 sub can_super_manage
{
5376 my $u = LJ
::want_user
(shift);
5378 return undef unless $remote && $u;
5381 return 1 if LJ
::u_equals
($u, $remote);
5383 # do not allow suspended users manage other accounts
5384 return 0 if $remote->is_suspended;
5386 # people/syn/rename accounts can only be managed by the one account
5387 return undef if $u->{journaltype
} =~ /^[PYR]$/;
5389 # check for supermaintainer access
5390 return 1 if LJ
::RelationService
->is_relation_to($u, $remote, 'S');
5392 # not passed checks, return false
5396 # name: can_moderate
5397 # des: Given a target user and determines that the user is an moderator for the target user
5398 # returns: bool: true if authorized, otherwise fail
5400 # des-u: user object or userid of target user
5403 my $u = LJ
::want_user
(shift);
5405 return undef unless $remote && $u;
5407 # can moderate only community
5408 return undef unless $u->is_community;
5410 # do not allow suspended users manage other accounts
5411 return 0 if $remote->is_suspended;
5413 # people/syn/rename accounts can only be managed by the one account
5414 return undef if $u->{journaltype
} =~ /^[PYR]$/;
5416 # check for moderate access
5417 return 1 if LJ
::RelationService
->is_relation_to($u, $remote, 'M');
5419 # passed not checks, return false
5424 # des: Given a target user and determines that the user is an admin for the taget user
5425 # returns: bool: true if authorized, otherwise fail
5427 # des-u: user object or userid of target user
5430 my $u = LJ
::want_user
(shift);
5432 return undef unless $remote && $u;
5435 return 1 if LJ
::u_equals
($u, $remote);
5437 # people/syn/rename accounts can only be managed by the one account
5438 return undef if $u->{journaltype
} =~ /^[PYR]$/;
5440 # do not allow suspended users manage other accounts
5441 return 0 if $remote->is_suspended;
5443 # check for supermaintainer
5444 return 1 if $remote->can_super_manage($u);
5446 return 0 unless LJ
::RelationService
->is_relation_to($u, $remote, 'A');
5448 # passed checks, return true
5454 my $u = LJ
::want_user
(shift);
5456 return undef unless $remote && $u;
5459 return 1 if LJ
::u_equals
($u, $remote);
5461 # do not allow suspended users to be watchers of other accounts.
5462 return 0 if $remote->is_suspended;
5464 # only personal journals can have watchers
5465 return undef unless $u->journaltype eq 'P';
5467 # check for admin access
5468 return undef unless LJ
::check_rel
($u, $remote, 'W');
5473 sub hide_adult_content
{
5476 my $prop_value = $u->prop('hide_adult_content');
5478 if (($u->is_child || !$u->best_guess_age) && $LJ::DISABLED
{'remove_adult_concepts'}) {
5482 if ($u->is_minor && $prop_value ne "concepts") {
5486 return $prop_value ?
$prop_value : "none";
5489 # returns a number that represents the user's chosen search filtering level
5491 # 1-10 = moderate filtering
5492 # >10 = strict filtering
5496 my $prop_value = $u->prop('safe_search');
5498 # current user 18+ default is 0
5499 # current user <18 default is 10
5500 # new user default (prop value is "nu_default") is 10
5501 return 0 if $prop_value eq "none";
5502 return $prop_value if $prop_value && $prop_value =~ /^\d+$/;
5503 return 0 if $prop_value ne "nu_default" && $u->best_guess_age && !$u->is_minor;
5507 # determine if the user in "for_u" should see $u in a search result
5508 sub should_show_in_search_results
{
5512 return 1 unless LJ
::is_enabled
("content_flag") && LJ
::is_enabled
("safe_search");
5514 my $adult_content = $u->adult_content_calculated;
5515 my $admin_flag = $u->admin_content_flag;
5517 my $for_u = $opts{for};
5518 unless (LJ
::isu
($for_u)) {
5519 return $adult_content ne "none" || $admin_flag ?
0 : 1;
5522 my $safe_search = $for_u->safe_search;
5523 return 1 if $safe_search == 0;
5525 my $adult_content_flag_level = $LJ::CONTENT_FLAGS
{$adult_content} ?
$LJ::CONTENT_FLAGS
{$adult_content}->{safe_search_level
} : 0;
5526 my $admin_flag_level = $LJ::CONTENT_FLAGS
{$admin_flag} ?
$LJ::CONTENT_FLAGS
{$admin_flag}->{safe_search_level
} : 0;
5528 return 0 if $adult_content_flag_level && ($safe_search >= $adult_content_flag_level);
5529 return 0 if $admin_flag_level && ($safe_search >= $admin_flag_level);
5534 my ($u, $target) = @_;
5536 return LJ
::u_equals
($u, $target);
5542 return LJ
::Tags
::get_usertags
($u);
5545 sub newpost_minsecurity
{
5548 my $val = $u->raw_prop('newpost_minsecurity') || 'public';
5551 if ($u->journaltype ne 'P' && $val eq 'private');
5556 sub third_party_notify_list
{
5559 my $val = $u->prop('third_party_notify_list');
5560 my @services = split(',', $val);
5565 # Check if the user's notify list contains a particular service
5566 sub third_party_notify_list_contains
{
5570 return 1 if grep { $_ eq $val } $u->third_party_notify_list;
5575 # Add a service to a user's notify list
5576 sub third_party_notify_list_add
{
5579 return 0 unless $svc;
5581 # Is it already there?
5582 return 1 if $u->third_party_notify_list_contains($svc);
5584 # Create the new list of services
5585 my @cur_services = $u->third_party_notify_list;
5586 push @cur_services, $svc;
5587 my $svc_list = join(',', @cur_services);
5589 # Trim a service from the list if it is too long
5590 if (length $svc_list > 255) {
5591 shift @cur_services;
5592 $svc_list = join(',', @cur_services)
5596 $u->set_prop('third_party_notify_list', $svc_list);
5600 # Remove a service to a user's notify list
5601 sub third_party_notify_list_remove
{
5604 return 0 unless $svc;
5607 return 1 unless $u->third_party_notify_list_contains($svc);
5610 $u->set_prop('third_party_notify_list',
5612 grep { $_ ne $svc } $u->third_party_notify_list
5618 # can $u add existing tags to $targetu's entries?
5619 sub can_add_tags_to
{
5620 my ($u, $targetu) = @_;
5622 return LJ
::Tags
::can_add_tags
($targetu, $u);
5625 sub qct_value_for_ads
{
5628 return 0 unless LJ
::is_enabled
("content_flag");
5630 my $adult_content = $u->adult_content_calculated;
5631 my $admin_flag = $u->admin_content_flag;
5633 if ($LJ::CONTENT_FLAGS
{$adult_content} && $LJ::CONTENT_FLAGS
{$adult_content}->{qct_value_for_ads
}) {
5634 return $LJ::CONTENT_FLAGS
{$adult_content}->{qct_value_for_ads
};
5636 if ($LJ::CONTENT_FLAGS
{$admin_flag} && $LJ::CONTENT_FLAGS
{$admin_flag}->{qct_value_for_ads
}) {
5637 return $LJ::CONTENT_FLAGS
{$admin_flag}->{qct_value_for_ads
};
5643 sub should_block_robots
{
5646 return 1 if $u->prop('opt_blockrobots');
5648 return 0 unless LJ
::is_enabled
("content_flag");
5650 my $adult_content = $u->adult_content_calculated;
5651 my $admin_flag = $u->admin_content_flag;
5653 return 1 if $LJ::CONTENT_FLAGS
{$adult_content} && $LJ::CONTENT_FLAGS
{$adult_content}->{block_robots
};
5654 return 1 if $LJ::CONTENT_FLAGS
{$admin_flag} && $LJ::CONTENT_FLAGS
{$admin_flag}->{block_robots
};
5658 # memcache key that holds the number of times a user performed one of the rate-limited actions
5662 return [$u->id, "rate:" . $u->id . ":$rp->{id}"];
5665 sub opt_exclude_from_verticals
{
5668 my $prop_val = $u->prop('opt_exclude_from_verticals');
5670 return $prop_val if $prop_val =~ /^(?:entries)$/;
5674 sub set_opt_exclude_from_verticals
{
5678 # only set the "none" value if the prop is currently set to something (explicit off)
5679 my $prop_val = $val ?
"entries" : undef;
5680 $prop_val = "none" if !$val && $u->prop('opt_exclude_from_verticals');
5682 $u->set_prop( opt_exclude_from_verticals
=> $prop_val );
5687 # prepare OpenId part of html-page, if needed
5693 # OpenID Server and Yadis
5694 if (LJ
::OpenID
->server_enabled and defined $u) {
5695 my $journalbase = $u->journal_base;
5696 $head .= qq{<link rel
="openid2.provider" href
="$LJ::OPENID_SERVER" />\n};
5697 $head .= qq{<link rel
="openid.server" href
="$LJ::OPENID_SERVER" />\n};
5698 $head .= qq{<meta http
-equiv
="X-XRDS-Location" content
="$journalbase/data/yadis" />\n};
5704 # return the number of comments a user has posted
5705 sub num_comments_posted
{
5708 my $ret = $u->prop('talkleftct2');
5710 unless (defined $ret) {
5711 my $dbr = LJ
::get_cluster_reader
($u);
5712 $ret = $dbr->selectrow_array(qq{
5713 SELECT COUNT
(*) FROM talkleft WHERE userid
=?
5716 $u->set_prop('talkleftct2' => $ret);
5722 # increase the number of comments a user has posted by 1
5723 sub incr_num_comments_posted
{
5726 $u->set_prop('talkleftct2' => $u->num_comments_posted + 1);
5729 # return the number of comments a user has received
5730 sub num_comments_received
{
5734 my $userid = $u->id;
5735 my $memkey = [$userid, "talk2ct:$userid"];
5736 my $count = LJ
::MemCache
::get
($memkey);
5738 my $dbcr = $opts{dbh
} || LJ
::get_cluster_reader
($u);
5739 my $expire = time() + 3600*24*2; # 2 days;
5740 $count = $dbcr->selectrow_array("SELECT COUNT(*) FROM talk2 ".
5741 "WHERE journalid=?", undef, $userid);
5742 LJ
::MemCache
::set
($memkey, $count, $expire) if defined $count;
5748 # returns undef if there shouldn't be an option for this user
5749 # B = show ads [B]oth to logged-out traffic on the user's journal and on the user's app pages
5750 # J = show ads only to logged-out traffic on the user's [J]ournal
5751 # A = show ads only on the user's [A]pp pages
5755 return undef unless LJ
::is_enabled
("basic_ads") && LJ
::run_hook
("user_is_basic", $u);
5756 return 'J' unless LJ
::is_enabled
("basic_ad_options") && $u->is_personal;
5758 my $prop_val = $u->prop("ad_visibility");
5759 return $prop_val =~ /^[BJA]$/ ?
$prop_val : 'B';
5762 sub wants_ads_on_app
{
5765 my $ad_visibility = $u->ad_visibility;
5766 return $ad_visibility eq "B" || $ad_visibility eq "A" ?
1 : 0;
5769 sub wants_ads_in_journal
{
5772 my $ad_visibility = $u->ad_visibility;
5773 return $ad_visibility eq "B" || $ad_visibility eq "J" ?
1 : 0;
5776 # format unixtimestamp according to the user's timezone setting
5781 return undef unless $time;
5783 return eval { DateTime
->from_epoch(epoch
=>$time, time_zone
=>$u->prop("timezone"))->ymd('-') } ||
5784 DateTime
->from_epoch(epoch
=> $time)->ymd('-');
5787 sub support_points_count
{
5790 my $dbr = LJ
::get_db_reader
();
5791 my $userid = $u->id;
5794 $count = $u->{_supportpointsum
};
5795 return $count if defined $count;
5797 my $memkey = [$userid, "supportpointsum:$userid"];
5798 $count = LJ
::MemCache
::get
($memkey);
5799 if (defined $count) {
5800 $u->{_supportpointsum
} = $count;
5804 $count = $dbr->selectrow_array("SELECT totpoints FROM supportpointsum WHERE userid=?", undef, $userid) || 0;
5805 $u->{_supportpointsum
} = $count;
5806 LJ
::MemCache
::set
($memkey, $count, 60*5);
5811 sub can_be_nudged_by
{
5812 my ($u, $nudger) = @_;
5814 return 0 unless LJ
::is_enabled
("nudge");
5815 return 0 if $u->equals($nudger);
5816 return 0 unless $u->is_personal;
5817 return 0 unless $u->is_visible;
5818 return 0 if $u->prop("opt_no_nudge");
5819 return 0 unless $u->is_mutual_friend($nudger);
5820 return 0 unless time() - $u->timeupdate >= 604800; # updated in the past week
5825 sub should_show_schools_to
{
5826 my ($u, $targetu) = @_;
5828 return 0 unless LJ
::is_enabled
("schools");
5829 return 1 if $u->prop('opt_showschools') eq '' || $u->prop('opt_showschools') eq 'Y';
5830 return 1 if $u->prop('opt_showschools') eq 'F' && $u->has_friend($targetu);
5835 sub can_be_text_messaged_by
{
5836 my ($u, $sender) = @_;
5838 return 0 unless $u->get_cap("textmessaging");
5840 my $tminfo = LJ
::TextMessage
->tm_info($u);
5842 ## messaging is disabled for some providers
5843 my $provider = $tminfo ?
$tminfo->{provider
} : '';
5844 return 0 if $provider eq 'beeline';
5845 return 0 if $provider eq 'megafon';
5848 my $security = $tminfo && $tminfo->{security
} ?
$tminfo->{security
} : "none";
5849 return 0 if $security eq "none";
5850 return 1 if $security eq "all";
5853 return 1 if $security eq "reg";
5854 return 1 if $security eq "friends" && $u->has_friend($sender);
5861 # name: LJ::User::rename_identity
5862 # des: Change an identity user's 'identity', update DB,
5863 # clear memcache and log change.
5865 # returns: Success or failure.
5867 sub rename_identity
{
5869 return 0 unless ($u && $u->is_identity && $u->is_expunged);
5871 my $id = $u->identity;
5872 return 0 unless $id;
5874 my $dbh = LJ
::get_db_writer
();
5876 # generate a new identity value that looks like ex_oldidvalue555
5880 my $temp = (length($ident) > 249) ?
substr($ident, 0, 249) : $ident;
5884 $exid = "ex_$temp" . int(rand(999));
5886 # check to see if this identity already exists
5887 unless ($dbh->selectrow_array("SELECT COUNT(*) FROM identitymap WHERE identity=? AND idtype=? LIMIT 1", undef, $exid, $idtype)) {
5888 # name doesn't already exist, use this one
5891 # name existed, try and get another
5900 my $from = $id->value;
5901 my $to = $tempid->($id->value, $id->typeid);
5903 return 0 unless $to;
5905 $dbh->do("UPDATE identitymap SET identity=? WHERE identity=? AND idtype=?",
5906 undef, $to, $from, $id->typeid);
5908 LJ
::memcache_kill
($u, "userid");
5910 LJ
::User
::InfoHistory
->add( $u, 'identity', $from );
5916 # name: LJ::User::get_renamed_user
5917 # des: Get the actual user of a renamed user
5921 sub get_renamed_user
{
5924 my $hops = $opts{hops
} || 5;
5926 # Traverse the renames to the final journal
5928 while ($u and $u->journaltype eq 'R' and $hops-- > 0) {
5929 my $rt = $u->prop("renamedto");
5930 last unless length $rt;
5931 if ($rt =~ /^https?:\/\
//){
5932 if ( my $newu = LJ
::User
->new_from_url($rt) ) {
5935 warn $u->username . " links to non-existent user at $rt";
5939 if ( my $newu = LJ
::load_user
($rt) ) {
5942 warn $u->username . " links to non-existent user at $rt";
5952 sub dismissed_page_notices
{
5955 my $val = $u->prop("dismissed_page_notices");
5956 my @notices = split(",", $val);
5961 sub has_dismissed_page_notice
{
5963 my $notice_string = shift;
5965 return 1 if grep { $_ eq $notice_string } $u->dismissed_page_notices;
5969 # add a page notice to a user's dismissed page notices list
5970 sub dismissed_page_notices_add
{
5972 my $notice_string = shift;
5973 return 0 unless $notice_string && $LJ::VALID_PAGE_NOTICES
{$notice_string};
5975 # is it already there?
5976 return 1 if $u->has_dismissed_page_notice($notice_string);
5978 # create the new list of dismissed page notices
5979 my @cur_notices = $u->dismissed_page_notices;
5980 push @cur_notices, $notice_string;
5981 my $cur_notices_string = join(",", @cur_notices);
5983 # remove the oldest notice if the list is too long
5984 if (length $cur_notices_string > 255) {
5986 $cur_notices_string = join(",", @cur_notices);
5990 $u->set_prop("dismissed_page_notices", $cur_notices_string);
5995 # remove a page notice from a user's dismissed page notices list
5996 sub dismissed_page_notices_remove
{
5998 my $notice_string = shift;
5999 return 0 unless $notice_string && $LJ::VALID_PAGE_NOTICES
{$notice_string};
6002 return 0 unless $u->has_dismissed_page_notice($notice_string);
6005 $u->set_prop("dismissed_page_notices", join(",", grep { $_ ne $notice_string } $u->dismissed_page_notices));
6010 sub custom_usericon
{
6013 ## Get user's selected userhead
6014 my $selected_uh_id = 0;
6015 my $url = $u->prop('custom_usericon') || '';
6019 && (my ($uh_id) = ($selected_uh_id) = $url =~ m/\/userhead\
/(\d+)$/)
6021 my $uh = LJ
::UserHead
->get_userhead ($uh_id);
6023 my $uh_fs = LJ
::FileStore
->get_path_info ( path
=> "/userhead/".$uh->get_uh_id );
6024 $url .= "?v=".$uh_fs->{'change_time'} if $uh_fs->{'change_time'};
6028 ## Check for individual userhead
6029 my $indiv_uh_id = 0;
6030 my $propval = $u->prop ('custom_usericon_individual');
6032 ## If it buyed we need to check exp date
6033 my $individual_uh_info = LJ
::JSON
->from_json ($propval);
6034 if ($individual_uh_info->{'date_exp'} > time) {
6035 my ($uh_id) = ($indiv_uh_id) = $individual_uh_info->{'uh_id'} =~ m
#uh-(\d+)#;
6036 my $uh = LJ
::UserHead
->get_userhead ($uh_id);
6037 if ($uh && $selected_uh_id == $indiv_uh_id) {
6038 my $uh_fs = LJ
::FileStore
->get_path_info ( path
=> "/userhead/".$uh_id );
6039 $url = $LJ::FILEPREFIX
."/userhead/".$uh_id;
6040 $url .= "?v=".$uh_fs->{'change_time'} if $uh_fs->{'change_time'};
6043 ## If indiv userhead was selected and date is expired, set userhead to default
6044 if ($selected_uh_id == $indiv_uh_id) {
6045 $u->set_custom_usericon (undef);
6050 $url =~ s
#^http://files\.livejournal\.com#$LJ::FILEPREFIX#;
6055 sub custom_usericon_appid
{
6057 return $u->prop('custom_usericon_appid') || 0;
6060 sub set_custom_usericon
{
6061 my ($u, $url, %opts) = @_;
6063 $u->set_prop( 'custom_usericon' => $url );
6065 if ($opts{application_id
}) {
6066 $u->set_prop( 'custom_usericon_appid' => $opts{application_id
});
6068 $u->clear_prop( 'custom_usericon_appid' );
6072 sub _subscriptions_count
{
6075 my $set = LJ
::Subscription
::GroupSet
->fetch_for_user($u, sub { 0 });
6077 return $set->{'active_count'};
6080 sub subscriptions_count
{
6083 my $cached = LJ
::MemCache
::get
('subscriptions_count:'.$u->id);
6084 return $cached if defined $cached;
6086 my $count = $u->_subscriptions_count;
6087 LJ
::MemCache
::set
('subscriptions_count:'.$u->id, $count);
6093 return $u->{'packed_props'};
6096 sub set_packed_props
{
6097 my ($u, $newprops) = @_;
6099 LJ
::update_user
($u, { 'packed_props' => $newprops });
6100 $u->{'packed_props'} = 1;
6103 sub init_userprop_def
{
6106 # defaults for S1 style IDs in config file are magic: really
6107 # uniq strings representing style IDs, so on first use, we need
6109 unless ($LJ::CACHED_S1IDMAP
) {
6110 my $pubsty = LJ
::S1
::get_public_styles
();
6111 foreach (values %$pubsty) {
6112 my $k = "s1_$_->{'type'}_style";
6113 my $needval = "$_->{'type'}/$_->{'styledes'}";
6114 next unless $LJ::USERPROP_DEF
{$k} eq $needval;
6116 $LJ::USERPROP_DEF
{$k} = $_->{'styleid'};
6119 $LJ::CACHED_S1IDMAP
= 1;
6126 my $dbcm = LJ
::get_cluster_master
($u);
6127 return 0 unless $dbcm;
6155 msn:mutual_friends_wlids:uid=*
6163 subscriptions_count:*
6182 foreach my $key (@keys) {
6183 $key =~ s/\*/$u->{userid}/g;
6184 LJ
::MemCacheProxy
::delete([ $u->{userid
}, $key ]);
6187 my $bio = $dbcm->selectrow_array('SELECT bio FROM userbio WHERE userid = ?', undef, $u->{userid
});
6188 if ($bio =~ /\S/ && $u->{has_bio
} ne 'Y') {
6189 LJ
::update_user
($u, { has_bio
=> 'Y' });
6192 $u->do("UPDATE s1usercache SET override_stor = NULL WHERE userid = ?", undef, $u->{userid
});
6194 my $dbh = LJ
::get_db_writer
();
6195 my $themeids = $dbh->selectcol_arrayref('SELECT moodthemeid FROM moodthemes WHERE ownerid = ?', undef, $u->{userid
});
6196 if ($themeids && @
$themeids) {
6197 foreach my $themeid (@
$themeids) {
6198 LJ
::MemCache
::delete([ $themeid, "moodthemedata:$themeid" ]);
6202 my $picids = $dbcm->selectcol_arrayref('SELECT picid FROM userpic2 WHERE userid = ?', undef, $u->{userid
});
6203 if ($picids && @
$picids) {
6204 foreach my $picid (@
$picids) {
6205 LJ
::MemCache
::delete([ $picid, "mogp.up.$picid" ]);
6206 LJ
::MemCache
::delete([ $picid, "mogp.up.$picid.alt" ]); # alt-zone (only zone at this time)
6210 my $s2ids = $dbh->selectcol_arrayref('SELECT styleid FROM s2styles WHERE userid = ?', undef, $u->{userid
});
6211 if ($s2ids && @
$s2ids) {
6212 foreach my $s2id (@
$s2ids) {
6213 LJ
::MemCache
::delete([ $s2id, "s2s:$s2id" ]);
6214 LJ
::MemCache
::delete([ $s2id, "s2sl:$s2id" ]);
6218 my $s2lids = $dbcm->selectcol_arrayref('SELECT s2lid FROM s2stylelayers2 WHERE userid = ?', undef, $u->{userid
});
6220 # put it in a hash to remove duplicates so we don't purge one layer twice
6221 my %s2lids = ( map { $_ => 1 } grep { $_ } @
$s2lids );
6223 foreach my $s2lid (keys %s2lids) {
6224 LJ
::MemCache
::delete([ $s2lid, "s2lo:$s2lid" ]);
6225 LJ
::MemCache
::delete([ $s2lid, "s2c:$s2lid" ]);
6233 ## Check for activity user at last N days
6234 ## args: days - how many days to check
6236 ## 1 - user logs in the last 'days' days
6237 ## 0 - user NOT logs in the last 'days' days
6238 sub check_activity
{
6242 return 0 unless $days;
6244 my $sth = $u->prepare ("SELECT logintime FROM loginlog WHERE userid=? ORDER BY logintime DESC");
6245 $sth->execute ($u->userid);
6247 if (my @row = $sth->fetchrow_array) {
6248 my $logintime = $row[0];
6249 return 1 if time - $logintime < $days * 86400;
6255 sub is_in_whitelist_for_spam
{
6257 return $u->prop('in_whitelist_for_spam');
6260 sub is_spamprotection_enabled
{
6262 return 0 if $LJ::DISABLED
{'spam_button'};
6263 my $spamprotection = $u->prop('spamprotection');
6264 return 0 if $spamprotection eq 'N';
6268 sub check_non_whitelist_enabled
{
6270 return 0 if $LJ::DISABLED
{'spam_button'};
6271 return 0 unless $u->is_community;
6272 return 0 if $u->prop("moderated") eq 'N';
6273 my $check_non_whitelist = $u->prop('check_non_whitelist');
6274 return 1 if defined($check_non_whitelist) && $check_non_whitelist eq 'Y';
6278 # return sticky entries existing
6279 sub has_sticky_entry
{
6281 my $sticky_id = $self->prop("sticky_entry_id");
6288 # returns sticky entry jitemid
6289 sub get_sticky_entry_id
{
6291 return $self->prop("sticky_entry_id") || '';
6294 # returns sticky entry jitemid
6295 sub remove_sticky_entry_id
{
6297 my $ownerid = $self->userid;
6298 LJ
::MemCache
::delete([$ownerid, "log2lt:$ownerid"]);
6299 $self->clear_prop("sticky_entry_id");
6304 my ($self, $itemid) = @_;
6305 die "itemid is not set" unless ($itemid);
6307 my $ownerid = $self->userid;
6308 LJ
::MemCache
::delete([$ownerid, "log2lt:$ownerid"]);
6309 $self->set_prop( sticky_entry_id
=> $itemid );
6312 # set socical influence information
6313 sub set_social_influence
{
6314 my ($self, $social_influence_infornation) = @_;
6316 # update user cached 'social_influence_info'
6317 $self->{'__social_influence_info'} = $social_influence_infornation;
6319 my $new_prop_value = LJ
::JSON
->to_json($social_influence_infornation) ;
6320 $self->set_prop( 'social_influence_info' => $new_prop_value);
6323 # get socical influence information
6324 sub get_social_influence
{
6327 # Does user contains cache?
6328 if ( !$self->{'__social_influence_info'} ) {
6329 my $prop_value = $self->prop("social_influence_info");
6334 $self->{'__social_influence_info'} = LJ
::JSON
->from_json($prop_value);
6336 return $self->{'__social_influence_info'};
6339 sub push_subscriptions
{
6343 $u->{push_subscriptions
} = LJ
::PushNotification
::Storage
->get_all($u)
6344 if !$u->{push_subscriptions
} || $opts{flush
};
6346 return keys %{$u->{push_subscriptions
}};
6349 sub push_subscription
{
6352 return $u->{push_subscriptions
}{$key} || {};
6355 sub disable_promo_announce
{
6357 $u->set_prop('promo_announce_disabled', 1);
6360 sub promo_announce_disabled
{
6362 return $u->prop('promo_announce_disabled') || 0;
6367 return $u->prop('spam_counter') || 0;
6370 sub clear_spam_counter
{
6372 $u->set_prop('spam_counter', 0);
6375 # If true, user migrated old friends to friends and subscriptions
6376 sub is_migrated_to_friends_and_subscriptions
{
6378 return $u->prop('migrated_to_friends_and_subscriptions');
6386 # name: LJ::get_authas_list
6387 # des: Get a list of usernames a given user can authenticate as.
6388 # returns: an array of usernames.
6390 # des-opts: Optional hashref. keys are:
6391 # - type: 'P' to only return users of journaltype 'P'.
6392 # 'S' return users of Supermaintainer type instead Maintainer type.
6393 # - cap: cap to filter users on.
6395 sub get_authas_list
{
6396 my ($u, $opts) = @_;
6400 # used to accept a user type, now accept an opts hash
6401 $opts = { 'type' => $opts } unless ref $opts;
6403 # Two valid types, Personal or Community
6404 $opts->{'type'} = undef unless $opts->{'type'} =~ m/^(P|C|S)$/;
6406 my $ids = LJ
::load_rel_target
($u, 'S') || [];
6407 if ($opts->{'type'} ne 'S') {
6408 my $a_ids = LJ
::load_rel_target
($u, 'A') || [];
6409 push @
$ids, @
$a_ids;
6411 return $u->{'user'} unless $ids && @
$ids;
6413 $opts->{'type'} = '' if $opts->{'type'} eq 'S';
6415 # load_userids_multiple
6417 LJ
::load_userids_multiple
([ map { $_, \
$users{$_} } @
$ids ], [$u]);
6419 return map { $_->{'user'} }
6420 grep { ! $opts->{'cap'} || LJ
::get_cap
($_, $opts->{'cap'}) }
6421 grep { ! $opts->{'type'} || $opts->{'type'} eq $_->{'journaltype'} }
6423 # unless overridden, hide non-visible/non-read-only journals. always display the user's acct
6424 grep { $opts->{'showall'} || $_->is_visible || $_->is_readonly || LJ
::u_equals
($_, $u) }
6426 # can't work as an expunged account
6427 grep { $_ && ref $_ eq 'LJ::User' && %$_ && !$_->is_expunged && $_->{clusterid
} > 0 }
6428 $u, sort { $a->{'user'} cmp $b->{'user'} } values %users;
6432 # name: LJ::get_postto_list
6433 # des: Get the list of usernames a given user can post to.
6434 # returns: an array of usernames
6436 # des-opts: Optional hashref. keys are:
6437 # - type: 'P' to only return users of journaltype 'P'.
6438 # - cap: cap to filter users on.
6440 sub get_postto_list
{
6441 my ($u, $opts) = @_;
6443 # used to accept a user type, now accept an opts hash
6444 $opts = { 'type' => $opts } unless ref $opts;
6446 # only one valid type right now
6447 $opts->{'type'} = 'P' if $opts->{'type'};
6449 my $ids = LJ
::load_rel_target
($u, 'P');
6450 return undef unless $ids;
6452 # load_userids_multiple
6454 LJ
::load_userids_multiple
([ map { $_, \
$users{$_} } @
$ids ], [$u]);
6456 return $u->{'user'}, sort map { $_->{'user'} }
6457 grep { ! $opts->{'cap'} || LJ
::get_cap
($_, $opts->{'cap'}) }
6458 grep { ! $opts->{'type'} || $opts->{'type'} eq $_->{'journaltype'} }
6459 grep { $_->clusterid > 0 }
6460 grep { $_->is_visible }
6466 # des: Checks to see if the remote user can use javascript in S2 layers.
6467 # returns: boolean; 1 if remote user can use javascript
6469 # des-userid: id of user to check
6474 my $u = LJ
::load_userid
($userid);
6477 return $u->prop('javascript');
6481 # name: LJ::can_view
6482 # des: Checks to see if the remote user can view a given journal entry.
6483 # <b>Note:</b> This is meant for use on single entries at a time,
6484 # not for calling many times on every entry in a journal.
6485 # returns: boolean; 1 if remote user can see item
6486 # args: remote, item
6487 # des-item: Hashref from the 'log' table.
6494 return 1 if $item->{'security'} eq "public";
6496 # must be logged in otherwise
6497 return 0 unless $remote;
6499 my $userid = int($item->{'ownerid'} || $item->{'journalid'});
6500 my $u = LJ
::load_userid
($userid);
6501 my $journal_name = $u ?
$u->user : '';
6502 my $remoteid = int($remote->{'userid'});
6504 # owners can always see their own.
6505 return 1 if $remote->can_manage($userid);
6507 # author in community can always see their post
6508 return 1 if $remoteid == $item->{'posterid'} and not $LJ::JOURNALS_WITH_PROTECTED_CONTENT
{ $journal_name };;
6510 # other people can't read private
6511 return 0 if ($item->{'security'} eq "private");
6513 # should be 'usemask' security from here out, otherwise
6514 # assume it's something new and return 0
6515 return 0 unless ($item->{'security'} eq "usemask");
6517 # if it's usemask, we have to refuse non-personal journals,
6518 # so we have to load the user
6519 return 0 unless $remote->{'journaltype'} eq 'P' || $remote->{'journaltype'} eq 'I';
6521 # TAG:FR:ljlib:can_view (turn off bit 0 for just watching? hmm.)
6522 my $gmask = LJ
::get_groupmask
($userid, $remoteid);
6523 my $allowed = (int($gmask) & int($item->{'allowmask'}));
6524 return $allowed ?
1 : 0; # no need to return matching mask
6528 # name: LJ::wipe_major_memcache
6529 # des: invalidate all major memcache items associated with a given user.
6533 sub wipe_major_memcache
6536 my $userid = LJ
::want_userid
($u);
6537 foreach my $key ("userid","bio","talk2ct","log2ct",
6538 "log2lt","memkwid","s1overr","s1uc","fgrp",
6539 "friends","friendofs","tu","upicinf","upiccom",
6540 "upicurl", "intids", "memct", "lastcomm")
6542 LJ
::memcache_kill
($userid, $key);
6547 # name: LJ::load_user_props
6548 # des: Given a user hashref, loads the values of the given named properties
6549 # into that user hashref.
6550 # args: u, opts?, propname*
6551 # des-opts: hashref of opts. set key 'cache' to use memcache.
6552 # des-propname: the name of a property from the [dbtable[userproplist]] table.
6554 sub load_user_props
{
6555 my ($u, @props) = @_;
6556 return unless ref $u;
6558 my $opts = ref $props[0]?
shift @props : {};
6559 unless ( delete $opts->{'reload'} ) {
6560 @props = grep { not exists $u->{$_} } @props;
6563 LJ
::load_user_props_multi
([$u], \
@props, $opts);
6566 sub load_user_props_multi
{
6567 my ($users, $props, $opts) = @_;
6568 my $use_master = $opts->{'use_master'};
6570 $props = [grep { defined and not ref } @
$props];
6571 return unless @
$props;
6573 $users = { map { $_->{'userid'} => $_ } grep { $_->{'statusvis'} ne 'X' and $_->{'clusterid'} } grep { ref } @
$users };
6574 return unless %$users;
6576 $LJ::COUNT_LOAD_PROPS_MULTI
++;
6578 my $groups = LJ
::User
::PropStorage
->get_handler_multi(\@
$props);
6579 my $memcache_available = @LJ::MEMCACHE_SERVERS
;
6580 $use_master = $memcache_available || $use_master;
6581 my $memc_expire = time() + 3600 * 24;
6583 LJ
::User
->init_userprop_def;
6585 foreach my $handler (keys %$groups) {
6586 my %propkeys = map { $_ => $LJ::USERPROP_DEF
{$_} || '' } @
{ $groups->{$handler} };
6588 # if there is no memcache, or if the handler doesn't wish to use
6589 # memcache, hit the storage directly, update the user object,
6590 # and get straight to the next handler
6591 if ( not $memcache_available or not defined $handler->use_memcache ) {
6592 foreach my $u (values %$users) {
6595 %{ $handler->get_props($u, $groups->{$handler},
6597 use_master
=> $use_master
6603 _extend_user_object
->($u, $propmap);
6609 # now let's find out what we're going to do with memcache
6610 my $memcache_policy = $handler->use_memcache;
6612 if ( $memcache_policy eq 'lite' ) {
6614 my $propmaps = LJ
::MemCacheProxy
::get_multi
(map {
6616 ($_ => ($memkeys{$_} = $handler->memcache_key($users->{$_})))
6621 my $rmemkeys = { map { $memkeys{$_} => $_ } keys %memkeys };
6623 while (($userid, $v) = each %$propmaps) {
6625 $userid = $rmemkeys->{$userid};
6627 delete $memkeys{$userid}; # Loading is successfull
6629 # Hack to init keys for empty props
6632 %{ LJ
::User
::PropStorage
->unpack_from_memcache($v) },
6635 _extend_user_object
($users->{$userid}, $packed);
6638 while (($userid, $v) = each %memkeys) {
6639 my $propmap = $handler->get_props(
6640 $users->{$userid}, [],
6641 { 'use_master' => $use_master }
6644 _extend_user_object
($users->{$userid}, { %propkeys, %$propmap });
6646 my $packed = LJ
::User
::PropStorage
->pack_for_memcache($propmap);
6647 LJ
::MemCache
::set
([$userid, $v], $packed, $memc_expire);
6649 } elsif ( $memcache_policy eq 'blob' ) {
6650 my $handled_props = $groups->{$handler};
6652 foreach my $u (values %$users) {
6653 my $propmap_memc = $handler->fetch_props_memcache($u, $handled_props);
6655 _extend_user_object
($u, { %propkeys, %$propmap_memc });
6657 my @load_from_db = grep { !exists $propmap_memc->{$_} }
6660 # if we can avoid hitting the db, avoid it
6661 next unless @load_from_db;
6663 my $propmap_db = $handler->get_props(
6665 { 'use_master' => $use_master }
6668 _extend_user_object
($u, $propmap_db);
6670 # now, update memcache
6671 $handler->store_props_memcache( $u, $propmap_db );
6677 sub _extend_user_object
{
6678 my ($u, $propmap) = @_;
6679 return unless ref $u;
6680 return unless ref $propmap eq 'HASH';
6683 $u->{$k} = $v while ($k, $v) = each %$propmap;
6688 # name: LJ::load_userids
6689 # des: Simple interface to [func[LJ::load_userids_multiple]].
6691 # returns: hashref with keys ids, values $u refs.
6695 LJ
::load_userids_multiple
([ map { $_ => \
$u{$_} } @_ ]);
6700 # name: LJ::load_userids_multiple
6701 # des: Loads a number of users at once, efficiently.
6702 # info: loads a few users at once, their userids given in the keys of $map
6703 # listref (not hashref: can't have dups). values of $map listref are
6704 # scalar refs to put result in. $have is an optional listref of user
6705 # object caller already has, but is too lazy to sort by themselves.
6706 # <strong>Note</strong>: The $have parameter is deprecated,
6707 # as is $memcache_only; but it is still preserved for now.
6708 # Really, this whole API (i.e. LJ::load_userids_multiple) is clumsy.
6709 # Use [func[LJ::load_userids]] instead.
6710 # args: map, have, memcache_only?
6711 # des-map: Arrayref of pairs (userid, destination scalarref).
6712 # des-have: Arrayref of user objects caller already has.
6713 # des-memcache_only: Flag to only retrieve data from memcache.
6716 sub load_userids_multiple
{
6717 # the $have parameter is deprecated, as is $memcache_only, but it's still preserved for now.
6718 # actually this whole API is crap. use LJ::load_userids() instead.
6719 my ($map, undef, $memcache_only) = @_;
6725 my $id = shift @
$map;
6726 my $ref = shift @
$map;
6727 next unless int($id);
6728 push @
{$need{$id}}, $ref;
6730 if ($LJ::REQ_CACHE_USER_ID
{$id}) {
6731 push @have, $LJ::REQ_CACHE_USER_ID
{$id};
6737 return unless ref $u eq "LJ::User";
6739 # this could change the $u returned to an
6740 # existing one we already have loaded in memory,
6741 # once it's been upgraded. then everybody points
6743 $u = _set_u_req_cache
($u);
6745 foreach (@
{$need{$u->{'userid'}}}) {
6746 # check if existing target is defined and not what we already have.
6748 LJ
::assert_is
($u->{userid
}, $eu->{userid
});
6753 delete $need{$u->{'userid'}};
6756 unless ($LJ::_PRAGMA_FORCE_MASTER
) {
6757 foreach my $u (@have) {
6762 foreach (LJ
::memcache_get_u
(map { [$_,"userid:$_"] } keys %need)) {
6768 if (%need && ! $memcache_only) {
6769 my $db = @LJ::MEMCACHE_SERVERS
|| $LJ::_PRAGMA_FORCE_MASTER ?
6770 LJ
::get_db_writer
() : LJ
::get_db_reader
();
6772 _load_user_raw
($db, "userid", [ keys %need ], sub {
6774 LJ
::memcache_set_u
($u);
6781 # des-key: either "userid" or "user" (the WHERE part)
6782 # des-vals: value or arrayref of values for key to match on
6783 # des-hook: optional code ref to run for each $u
6784 # returns: last $u found
6787 my ($db, $key, $vals, $hook) = @_;
6789 $vals = [ $vals ] unless ref $vals eq "ARRAY";
6792 unless ($LJ::CACHE_NO_ISAM
{user
} || scalar(@
$vals) > 10) {
6793 eval { $db->do("HANDLER user OPEN"); };
6794 if ($@
|| $db->err) {
6795 $LJ::CACHE_NO_ISAM
{user
} = 1;
6804 $key = "PRIMARY" if $key eq "userid";
6805 foreach my $v (@
$vals) {
6806 my $sth = $db->prepare("HANDLER user READ `$key` = (?) LIMIT 1");
6808 my $row = $sth->fetchrow_hashref;
6810 my $u = LJ
::User
->new_from_row($row);
6815 $db->do("HANDLER user close");
6817 my $in = join(", ", map { $db->quote($_) } @
$vals);
6818 my $sth = $db->prepare("SELECT * FROM user WHERE $key IN ($in)");
6820 while (my $row = $sth->fetchrow_hashref) {
6821 my $u = LJ
::User
->new_from_row($row);
6830 sub _set_u_req_cache
{
6831 my $u = shift or die "no u to set";
6833 # if we have an existing user singleton, upgrade it with
6834 # the latested data, but keep using its address
6835 if (my $eu = $LJ::REQ_CACHE_USER_ID
{$u->{'userid'}}) {
6836 LJ
::assert_is
($eu->{userid
}, $u->{userid
});
6838 $eu->{$_} = $u->{$_} foreach keys %$u;
6841 $LJ::REQ_CACHE_USER_NAME
{$u->{'user'}} = $u;
6842 $LJ::REQ_CACHE_USER_ID
{$u->{'userid'}} = $u;
6846 sub load_user_or_identity
{
6849 my $user = LJ
::canonical_username
($arg);
6850 return LJ
::load_user
($user) if $user;
6852 # return undef if not dot in arg (can't be a URL)
6853 return undef unless $arg =~ /\./;
6855 my $dbh = LJ
::get_db_writer
();
6857 $url = "http://$url" unless $url =~ m!^http://!;
6858 $url .= "/" unless $url =~ m
!/$!;
6859 my $uid = $dbh->selectrow_array("SELECT userid FROM identitymap WHERE idtype=? AND identity=?",
6861 return LJ
::load_userid
($uid) if $uid;
6865 # load either a username, or a "I,<userid>" parameter.
6868 my $user = LJ
::canonical_username
($arg);
6869 return LJ
::load_user
($user) if length $user;
6870 if ($arg =~ /^I,(\d+)$/) {
6871 my $u = LJ
::load_userid
($1);
6872 return $u if $u->is_identity;
6878 # name: LJ::load_user
6879 # des: Loads a user record, from the [dbtable[user]] table, given a username.
6880 # args: user, force?
6881 # des-user: Username of user to load.
6882 # des-force: if set to true, won't return cached user object and will
6884 # returns: Hashref, with keys being columns of [dbtable[user]] table.
6887 my ($user, $force) = @_;
6889 $user = LJ
::canonical_username
($user);
6890 return undef unless length $user;
6892 my $get_user = sub {
6893 my $use_dbh = shift;
6894 my $db = $use_dbh ? LJ
::get_db_writer
() : LJ
::get_db_reader
();
6895 my $u = _load_user_raw
($db, "user", $user)
6898 # set caches since we got a u from the master
6899 LJ
::memcache_set_u
($u) if $use_dbh;
6901 return _set_u_req_cache
($u);
6904 # caller is forcing a master, return now
6905 return $get_user->("master") if $force || $LJ::_PRAGMA_FORCE_MASTER
;
6909 # return process cache if we have one
6910 if ($u = $LJ::REQ_CACHE_USER_NAME
{$user}) {
6917 if (exists $LJ::PRELOADED_USER_IDS
{$user} && !$LJ::IS_DEV_SERVER
) {
6918 $uid = $LJ::PRELOADED_USER_IDS
{$user};
6920 $uid = LJ
::MemCacheProxy
::get
("uidof:$user");
6923 $u = LJ
::memcache_get_u
([$uid, "userid:$uid"]) if $uid;
6924 return _set_u_req_cache
($u) if $u;
6927 # try to load from master if using memcache, otherwise from slave
6928 $u = $get_user->(scalar @LJ::MEMCACHE_SERVERS
);
6931 # setup LDAP handler if this is the first time
6932 if ($LJ::LDAP_HOST
&& ! $LJ::AUTH_EXISTS
) {
6934 $LJ::AUTH_EXISTS
= sub {
6936 my $rec = LJ
::LDAP
::load_ldap_user
($user);
6937 return $rec ?
$rec : undef;
6941 # if user doesn't exist in the LJ database, it's possible we're using
6942 # an external authentication source and we should create the account
6945 if (ref $LJ::AUTH_EXISTS
eq "CODE" && ($lu = $LJ::AUTH_EXISTS
->($user)))
6947 my $name = ref $lu eq "HASH" ?
($lu->{'nick'} || $lu->{name
} || $user) : $user;
6948 if (LJ
::create_account
({
6951 'email' => ref $lu eq "HASH" ?
$lu->email_raw : "",
6955 # this should pull from the master, since it was _just_ created
6956 return $get_user->("master");
6966 my %need = map {$_ => 1} @users;
6971 foreach my $user ( @users ) {
6972 if (my $u = $LJ::REQ_CACHE_USER_NAME
{$user}) {
6973 $loaded{$u->userid} = $u;
6974 delete $need{$u->userid};
6978 ## username to userid and load
6979 my $us = LJ
::load_userids
( LJ
::get_userid_multi
( [keys %need] ) );
6981 while ( my ($k, $v) = each %loaded ) {
6989 # name: LJ::u_equals
6990 # des: Compares two user objects to see if they are the same user.
6991 # args: userobj1, userobj2
6992 # des-userobj1: First user to compare.
6993 # des-userobj2: Second user to compare.
6994 # returns: Boolean, true if userobj1 and userobj2 are defined and have equal userids.
6998 return $u1 && $u2 && $u1->{'userid'} == $u2->{'userid'};
7002 # name: LJ::load_userid
7003 # des: Loads a user record, from the [dbtable[user]] table, given a userid.
7004 # args: userid, force?
7005 # des-userid: Userid of user to load.
7006 # des-force: if set to true, won't return cached user object and will
7008 # returns: Hashref with keys being columns of [dbtable[user]] table.
7011 my ($userid, $force) = @_;
7012 return undef unless $userid;
7014 my $get_user = sub {
7015 my $use_dbh = shift;
7016 my $db = $use_dbh ? LJ
::get_db_writer
() : LJ
::get_db_reader
();
7017 my $u = _load_user_raw
($db, "userid", $userid)
7020 LJ
::memcache_set_u
($u) if $use_dbh;
7021 return _set_u_req_cache
($u);
7024 # user is forcing master, return now
7025 return $get_user->("master") if $force || $LJ::_PRAGMA_FORCE_MASTER
;
7029 # check process cache
7030 $u = $LJ::REQ_CACHE_USER_ID
{$userid};
7036 $u = LJ
::memcache_get_u
([$userid,"userid:$userid"]);
7037 return _set_u_req_cache
($u) if $u;
7039 # get from master if using memcache
7040 return $get_user->("master") if @LJ::MEMCACHE_SERVERS
;
7046 # if we didn't get a u from the reader, fall back to master
7047 return $get_user->("master");
7054 my $users = LJ
::MemCacheProxy
::get_multi
(@keys) || {};
7055 while (my ($key, $ar) = each %$users) {
7056 my $row = LJ
::MemCache
::array_to_hash
("user", $ar, $key)
7058 my $u = LJ
::User
->new_from_row($row);
7061 return wantarray ?
@ret : $ret[0];
7068 my $expire = time() + 1800;
7069 my $ar = LJ
::MemCache
::hash_to_array
("user", $u);
7071 LJ
::MemCacheProxy
::set
([$u->{'userid'}, "userid:$u->{'userid'}"], $ar, $expire);
7072 LJ
::MemCacheProxy
::set
("uidof:$u->{user}", $u->{userid
});
7077 # des: gets a user bio, from DB or memcache.
7079 # des-force: true to get data from cluster master.
7083 my ($u, $force) = @_;
7084 return unless $u && $u->{'has_bio'} eq "Y";
7088 my $memkey = [$u->{'userid'}, "bio:$u->{'userid'}"];
7090 my $bio = LJ
::MemCache
::get
($memkey);
7091 return $bio if defined $bio;
7094 # not in memcache, fall back to disk
7095 my $db = @LJ::MEMCACHE_SERVERS
|| $force ?
7096 LJ
::get_cluster_def_reader
($u) : LJ
::get_cluster_reader
($u);
7097 $bio = $db->selectrow_array("SELECT bio FROM userbio WHERE userid=?",
7098 undef, $u->{'userid'});
7101 LJ
::MemCache
::add
($memkey, $bio);
7107 # name: LJ::journal_base
7108 # des: Returns URL of a user's journal.
7109 # info: The tricky thing is that users with underscores in their usernames
7110 # can't have some_user.example.com as a hostname, so that's changed into
7111 # some-user.example.com.
7112 # args: uuser, vhost?
7113 # des-uuser: LJ::User object, user hashref or username of user whose URL to make.
7114 # des-vhost: What type of URL. Acceptable options: "users", to make a
7115 # http://user.example.com/ URL; "tilde" for http://example.com/~user/;
7116 # "community" for http://example.com/community/user; or the default
7117 # will be http://example.com/users/user. If unspecified and uuser
7118 # is a user hashref, then the best/preferred vhost will be chosen.
7119 # returns: scalar; a URL.
7123 my ($user, $vhost) = @_;
7125 return unless $user;
7127 if (LJ
::are_hooks
("journal_base")) {
7128 ## We must pass a real LJ::User object into hook
7130 ## $user is either LJ::User object or plain hash with 'userid' field
7131 if (!UNIVERSAL
::isa
($user, "LJ::User")) {
7132 $user = LJ
::load_userid
($user->{userid
});
7135 ## $user is plain username
7136 $user = LJ
::load_user
($user);
7139 return $user->{'journal_base'}
7140 if $user->{'journal_base'};
7141 my $hookurl = LJ
::run_hook
("journal_base", $user, $vhost);
7142 $user->{'journal_base'} = $hookurl if (isu
($user) && $hookurl);
7143 return $hookurl if $hookurl;
7148 $user = $u->{'user'};
7149 unless (defined $vhost) {
7150 if ($LJ::FRONTPAGE_JOURNAL
eq $user) {
7152 } elsif ($u->{'journaltype'} eq "P") {
7159 if ($vhost eq "users") {
7160 my $he_user = $user;
7161 $he_user =~ s/_/-/g;
7162 return "http://$he_user.$LJ::USER_DOMAIN";
7163 } elsif ($vhost eq "tilde") {
7164 return "$LJ::SITEROOT/~$user";
7165 } elsif ($vhost eq "community") {
7166 return "$LJ::SITEROOT/community/$user";
7167 } elsif ($vhost eq "front") {
7168 return $LJ::SITEROOT
;
7169 } elsif ($vhost =~ /^other:(.+)/) {
7172 return "$LJ::SITEROOT/users/$user";
7178 # name: LJ::load_user_privs
7180 # des: loads all of the given privs for a given user into a hashref, inside
7181 # the user record. See also [func[LJ::check_priv]].
7182 # args: u, priv, arg?
7183 # des-priv: Priv names to load (see [dbtable[priv_list]]).
7184 # des-arg: Optional argument. See also [func[LJ::check_priv]].
7187 sub load_user_privs
{
7190 return unless $remote and @privs;
7192 # return if we've already loaded these privs for this user.
7193 @privs = grep { ! $remote->{'_privloaded'}->{$_} } @privs;
7194 return unless @privs;
7196 my $dbr = LJ
::get_db_reader
();
7198 foreach (@privs) { $remote->{'_privloaded'}->{$_}++; }
7199 @privs = map { $dbr->quote($_) } @privs;
7200 my $sth = $dbr->prepare("SELECT pl.privcode, pm.arg ".
7201 "FROM priv_map pm, priv_list pl ".
7202 "WHERE pm.prlid=pl.prlid AND ".
7203 "pl.privcode IN (" . join(',',@privs) . ") ".
7204 "AND pm.userid=$remote->{'userid'}");
7206 while (my ($priv, $arg) = $sth->fetchrow_array) {
7207 unless (defined $arg) { $arg = ""; } # NULL -> ""
7208 $remote->{'_priv'}->{$priv}->{$arg} = 1;
7213 # name: LJ::check_priv
7214 # des: Check to see if a user has a certain privilege.
7215 # info: Usually this is used to check the privs of a $remote user.
7216 # See [func[LJ::get_remote]]. As such, a $u argument of undef
7217 # is okay to pass: 0 will be returned, as an unknown user can't
7219 # args: u, priv, arg?
7220 # des-priv: Priv name to check for (see [dbtable[priv_list]])
7221 # des-arg: Optional argument. If defined, function only returns true
7222 # when $remote has a priv of type $priv also with arg $arg, not
7223 # just any priv of type $priv, which is the behavior without
7224 # an $arg. Arg can be "*", for all args.
7225 # returns: boolean; true if user has privilege
7228 my ($u, $priv, $arg) = @_;
7231 LJ
::run_hook
("update_counter", {
7232 counter
=> "check_priv",
7235 LJ
::load_user_privs
($u, $priv)
7236 unless $u->{'_privloaded'}->{$priv};
7238 # no access if they don't have the priv
7239 return 0 unless defined $u->{'_priv'}->{$priv};
7241 # at this point we know they have the priv
7242 return 1 unless defined $arg;
7244 # check if they have the right arguments
7245 return 1 if defined $u->{'_priv'}->{$priv}->{$arg};
7246 return 1 if defined $u->{'_priv'}->{$priv}->{"*"};
7248 # don't have the right argument
7255 # name: LJ::users_by_priv
7257 # des: Return users with a certain privilege.
7259 # des-args: user privilege to searching. arg can be "*" for all args.
7260 # return: Userids or empty list.
7261 # TODO Add store to MemCache
7263 my ($priv, $arg) = @_;
7265 my $dbr = LJ
::get_db_reader
();
7268 return unless $priv;
7270 my $users = $dbr->selectcol_arrayref ("SELECT userid FROM priv_list pl, priv_map pm
7271 WHERE pl.prlid = pm.prlid
7274 ", undef, $priv, $arg);
7276 return unless ref $users eq 'ARRAY';
7283 # name: LJ::remote_has_priv
7284 # des: Check to see if the given remote user has a certain privilege.
7286 sub remote_has_priv
{
7288 my $privcode = shift; # required. priv code to check for.
7289 my $ref = shift; # optional, arrayref or hashref to populate
7290 return 0 unless ($remote);
7292 ### authentication done. time to authorize...
7294 my $dbr = LJ
::get_db_reader
();
7295 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=?");
7296 $sth->execute($privcode, $remote->{'userid'});
7299 if (ref $ref eq "ARRAY") { @
$ref = (); }
7300 if (ref $ref eq "HASH") { %$ref = (); }
7301 while (my ($arg) = $sth->fetchrow_array) {
7303 if (ref $ref eq "ARRAY") { push @
$ref, $arg; }
7304 if (ref $ref eq "HASH") { $ref->{$arg} = 1; }
7309 # $dom: 'L' == log, 'T' == talk, 'M' == modlog, 'S' == session,
7310 # 'R' == memory (remembrance), 'K' == keyword id,
7311 # 'P' == phone post, 'C' == pending comment
7312 # 'O' == pOrtal box id, 'V' == 'vgift', 'E' == ESN subscription id
7313 # 'Q' == Notification Inbox, 'G' == 'SMS messaGe'
7314 # 'D' == 'moDule embed contents', 'W' == 'Wish-list element'
7315 # 'F' == Photo ID, 'A' == Album ID, 'Y' == delaYed entries
7316 # 'I' == Fotki migration log ID, 'H' == pics tag id
7318 # FIXME: both phonepost and vgift are ljcom. need hooks. but then also
7319 # need a separate namespace. perhaps a separate function/table?
7320 sub alloc_user_counter
7322 my ($u, $dom, $opts) = @_;
7325 ##################################################################
7326 # IF YOU UPDATE THIS MAKE SURE YOU ADD INITIALIZATION CODE BELOW #
7327 return undef unless $dom =~ /^[LTMPSRKCOVEQGDWFAYIH]$/; #
7328 ##################################################################
7330 my $dbh = LJ
::get_db_writer
();
7331 return undef unless $dbh;
7334 my $uid = $u->{'userid'}+0;
7335 return undef unless $uid;
7336 my $memkey = [$uid, "auc:$uid:$dom"];
7338 # in a master-master DB cluster we need to be careful that in
7339 # an automatic failover case where one cluster is slightly behind
7340 # that the same counter ID isn't handed out twice. use memcache
7341 # as a sanity check to record/check latest number handed out.
7342 my $memmax = int(LJ
::MemCache
::get
($memkey) || 0);
7344 my $rs = $dbh->do("UPDATE usercounter SET max=LAST_INSERT_ID(GREATEST(max,$memmax)+1) ".
7345 "WHERE journalid=? AND area=?", undef, $uid, $dom);
7347 $newmax = $dbh->selectrow_array("SELECT LAST_INSERT_ID()");
7349 # if we've got a supplied callback, lets check the counter
7350 # number for consistency. If it fails our test, wipe
7351 # the counter row and start over, initializing a new one.
7352 # callbacks should return true to signal 'all is well.'
7353 if ($opts->{callback
} && ref $opts->{callback
} eq 'CODE') {
7355 eval { $rv = $opts->{callback
}->($u, $newmax) };
7357 $dbh->do("DELETE FROM usercounter WHERE " .
7358 "journalid=? AND area=?", undef, $uid, $dom);
7359 return LJ
::alloc_user_counter
($u, $dom);
7363 LJ
::MemCache
::set
($memkey, $newmax);
7367 if ($opts->{recurse
}) {
7368 # We shouldn't ever get here if all is right with the world.
7374 'log' => "SELECT MAX(jitemid) FROM log2 WHERE journalid=?",
7375 'logtext' => "SELECT MAX(jitemid) FROM logtext2 WHERE journalid=?",
7376 'talk_nodeid' => "SELECT MAX(nodeid) FROM talk2 WHERE nodetype='L' AND journalid=?",
7378 'talk' => "SELECT MAX(jtalkid) FROM talk2 WHERE journalid=?",
7379 'talktext' => "SELECT MAX(jtalkid) FROM talktext2 WHERE journalid=?",
7382 my $consider = sub {
7384 foreach my $t (@tables) {
7385 my $res = $u->selectrow_array($qry_map->{$t}, undef, $uid);
7386 $newmax = $res if $res > $newmax;
7390 # Make sure the counter table is populated for this uid/dom.
7392 # back in the ol' days IDs were reused (because of MyISAM)
7393 # so now we're extra careful not to reuse a number that has
7394 # foreign junk "attached". turns out people like to delete
7395 # each entry by hand, but we do lazy deletes that are often
7396 # too lazy and a user can see old stuff come back alive
7397 $consider->("log", "logtext", "talk_nodeid");
7398 } elsif ($dom eq "T") {
7399 # just paranoia, not as bad as above. don't think we've ever
7400 # run into cases of talktext without a talk, but who knows.
7402 $consider->("talk", "talktext");
7403 } elsif ($dom eq "M") {
7404 $newmax = $u->selectrow_array("SELECT MAX(modid) FROM modlog WHERE journalid=?",
7406 } elsif ($dom eq "S") {
7407 $newmax = $u->selectrow_array("SELECT MAX(sessid) FROM sessions WHERE userid=?",
7409 } elsif ($dom eq "R") {
7410 $newmax = $u->selectrow_array("SELECT MAX(memid) FROM memorable2 WHERE userid=?",
7412 } elsif ($dom eq "K") {
7413 $newmax = $u->selectrow_array("SELECT MAX(kwid) FROM userkeywords WHERE userid=?",
7415 } elsif ($dom eq "P") {
7416 my $userblobmax = $u->selectrow_array("SELECT MAX(blobid) FROM userblob WHERE journalid=? AND domain=?",
7417 undef, $uid, LJ
::get_blob_domainid
("phonepost"));
7418 my $ppemax = $u->selectrow_array("SELECT MAX(blobid) FROM phonepostentry WHERE userid=?",
7420 $newmax = ($ppemax > $userblobmax) ?
$ppemax : $userblobmax;
7421 } elsif ($dom eq "C") {
7422 $newmax = $u->selectrow_array("SELECT MAX(pendcid) FROM pendcomments WHERE jid=?",
7424 } elsif ($dom eq "O") {
7425 $newmax = $u->selectrow_array("SELECT MAX(pboxid) FROM portal_config WHERE userid=?",
7427 } elsif ($dom eq "V") {
7428 $newmax = $u->selectrow_array("SELECT MAX(giftid) FROM vgifts WHERE userid=?",
7430 } elsif ($dom eq "E") {
7431 $newmax = $u->selectrow_array("SELECT MAX(subid) FROM subs WHERE userid=?",
7433 } elsif ($dom eq "Q") {
7434 $newmax = $u->selectrow_array("SELECT MAX(qid) FROM notifyqueue WHERE userid=?",
7436 } elsif ($dom eq "G") {
7437 $newmax = $u->selectrow_array("SELECT MAX(msgid) FROM sms_msg WHERE userid=?",
7439 } elsif ($dom eq "D") {
7440 $newmax = $u->selectrow_array("SELECT MAX(moduleid) FROM embedcontent WHERE userid=?",
7442 } elsif ($dom eq "W") {
7443 $newmax = $u->selectrow_array("SELECT MAX(wishid) FROM wishlist2 WHERE userid=?",
7445 } elsif ($dom eq "F") {
7446 $newmax = $u->selectrow_array("SELECT MAX(photo_id) FROM fotki_photos WHERE userid=?",
7448 } elsif ($dom eq "A") {
7449 $newmax = $u->selectrow_array("SELECT MAX(album_id) FROM fotki_albums WHERE userid=?",
7451 } elsif ($dom eq "Y") {
7452 $newmax = $u->selectrow_array("SELECT MAX(delayedid) FROM delayedlog2 WHERE journalid=?",
7454 } elsif ( $dom eq 'I' ) {
7455 $newmax = $u->selectrow_array("SELECT MAX(logid) FROM fotki_migration_log WHERE userid=?",
7457 } elsif ( $dom eq 'H' ) {
7458 $newmax = $u->selectrow_array("SELECT MAX(tag_id) FROM fotki_tags WHERE userid=?",
7461 die "No user counter initializer defined for area '$dom'.\n";
7464 $dbh->do("INSERT IGNORE INTO usercounter (journalid, area, max) VALUES (?,?,?)",
7465 undef, $uid, $dom, $newmax) or return undef;
7467 # The 2nd invocation of the alloc_user_counter sub should do the
7468 # intended incrementing.
7469 return LJ
::alloc_user_counter
($u, $dom, { recurse
=> 1 });
7473 # name: LJ::make_user_active
7474 # des: Record user activity per cluster, on [dbtable[clustertrack2]], to
7475 # make per-activity cluster stats easier.
7476 # args: userid, type
7477 # des-userid: source userobj ref
7478 # des-type: currently unused
7480 sub mark_user_active
{
7481 my ($u, $type) = @_; # not currently using type
7482 return 0 unless $u; # do not auto-vivify $u
7483 my $uid = $u->{userid
};
7484 return 0 unless $uid && $u->{clusterid
};
7486 # Update the clustertrack2 table, but not if we've done it for this
7487 # user in the last hour. if no memcache servers are configured
7488 # we don't do the optimization and just always log the activity info
7489 if (@LJ::MEMCACHE_SERVERS
== 0 ||
7490 LJ
::MemCache
::add
("rate:tracked:$uid", 1, 3600)) {
7492 return 0 unless $u->writer;
7493 $u->do("REPLACE INTO clustertrack2 SET ".
7494 "userid=?, timeactive=?, clusterid=?", undef,
7495 $uid, time(), $u->{clusterid
}) or return 0;
7501 # name: LJ::get_shared_journals
7502 # des: Gets an array of shared journals a user has access to.
7503 # returns: An array of shared journals.
7506 sub get_shared_journals
7509 my $ids = LJ
::load_rel_target
($u, 'A') || [];
7511 # have to get usernames;
7513 LJ
::load_userids_multiple
([ map { $_, \
$users{$_} } @
$ids ], [$u]);
7514 return sort map { $_->{'user'} } values %users;
7517 ## my $text = LJ::ljuser_alias($u)
7518 ## returns note text (former 'alias') for current remote user
7522 return if $LJ::DISABLED
{'aliases'};
7524 my $remote = LJ
::get_remote
();
7525 return unless $remote;
7526 return unless $remote->get_cap('aliases');
7528 my $u = LJ
::load_user
($user);
7531 if (!$remote->{_aliases
}) {
7532 my $prop_aliases = LJ
::text_uncompress
( $remote->prop('aliases') );
7533 $remote->{_aliases
} = ($prop_aliases) ? LJ
::JSON
->from_json($prop_aliases) : {};
7535 return $remote->{_aliases
}->{ $u->{userid
} };
7539 ## LJ::set_alias($u, $text, \$error)
7540 ## LJ::set_alias([ $u1, $text1, $u2, $text2], \$error);
7542 ## Sets notes (alias) text for user $u to the current $remote user
7543 ## $u is either user object or userid (number)
7544 ## If aliases cannot be updated, undef value is returned and optional \$error reference is set
7545 ## Use empty text for deleting alias
7548 my $list = (ref $_[0] eq 'ARRAY') ?
shift : [shift, shift];
7551 if ($LJ::DISABLED
{'aliases'}) {
7552 $$err = "Notes (aliases) are disabled" if $err;
7556 my $remote = LJ
::get_remote
();
7558 $$err = "No remote user" if $err;
7561 unless ($remote->get_cap('aliases')) {
7562 $$err = "Remote user can't manage notes (aliases)" if $err;
7567 if (!$remote->{_aliases
}) {
7568 my $prop_aliases = LJ
::text_uncompress
( $remote->prop('aliases') );
7569 $remote->{_aliases
} = $prop_aliases ? LJ
::JSON
->from_json($prop_aliases) : {};
7572 ## modify (edit, add or delete)
7573 for (my $i = 0; $i < @
$list / 2; ++$i) {
7574 my $userid = $list->[$i * 2];
7575 my $alias = $list->[$i * 2 + 1];
7576 $alias = substr($alias, 0, 400);
7577 $userid = $userid->{userid
} if ref $userid;
7578 die "Numeric id is expected, not $userid" unless $userid =~ /^\d+$/;
7581 $remote->{_aliases
}->{$userid} = $alias;
7583 delete $remote->{_aliases
}->{$userid};
7588 my $serialized_text = LJ
::JSON
->to_json($remote->{_aliases
});
7589 $serialized_text = LJ
::text_compress
( $serialized_text ) unless $LJ::DISABLED
{'aliases_compress'};
7590 if (length $serialized_text < 65536) {
7591 return $remote->set_prop( aliases
=> $serialized_text );
7593 delete $remote->{_aliases
}; ## drop unsuccessfully modified data
7594 $$err = BML
::ml
('widget.addalias.too.long') if $err;
7599 ## my %all_aliases = LJ::get_all_aliases();
7600 ## Returns all aliases for current remote user as hash userid => alias
7601 sub get_all_aliases
{
7603 return if $LJ::DISABLED
{'aliases'};
7605 my $remote = shift || LJ
::get_remote
();
7606 return unless $remote and $remote->get_cap('aliases');
7608 if (!$remote->{_aliases
}) {
7609 my $prop_aliases = LJ
::text_uncompress
($remote->prop('aliases'));
7610 $remote->{_aliases
} = ($prop_aliases) ? LJ
::JSON
->from_json($prop_aliases) : {};
7613 return %{$remote->{_aliases
}};
7619 # des: Make link to userinfo/journal of user.
7620 # info: Returns the HTML for a userinfo/journal link pair for a given user
7621 # name, just like LJUSER does in BML. This is for files like cleanhtml.pl
7622 # and ljpoll.pl which need this functionality too, but they aren't run as BML.
7624 # des-user: Username to link to, or user hashref.
7625 # des-opts: Optional hashref to control output. Key 'full' when true causes
7626 # a link to the mode=full userinfo. Key 'type' when 'C' makes
7627 # a community link, when 'Y' makes a syndicated account link,
7628 # when 'I' makes an identity account link (e.g. OpenID),
7629 # when 'N' makes a news account link, otherwise makes a user account
7630 # link. If user parameter is a hashref, its 'journaltype' overrides
7631 # this 'type'. Key 'del', when true, makes a tag for a deleted user.
7632 # If user parameter is a hashref, its 'statusvis' overrides 'del'.
7633 # Key 'no_follow', when true, disables traversal of renamed users.
7634 # returns: HTML with a little head image & bold text link.
7637 my $ljuser_tmpl_path = join('/', $ENV{'LJHOME'}, 'templates', 'User');
7638 my $ljuser_cache = {};
7641 my ($user, $opts) = @_;
7642 my ($u, $username, $journal_url, $striked);
7643 my ($journal_name, $journal, $userhead);
7644 my ($attrs, $color, $user_alias, %user);
7650 $username = $u->username;
7652 $u = LJ
::load_user
($user);
7657 if ( $u and LJ
::isu
($u) ) {
7658 # Traverse the renames to the final journal
7659 unless ( $opts->{'no_follow'} ) {
7660 $u = $u->get_renamed_user;
7661 $username = $u->username;
7664 last if $ljuser_cache->{$username};
7666 # Mark accounts as deleted that aren't visible, memorial, locked, or
7668 if ( $u->statusvis !~ m![VMLO]! ) {
7672 $journal_name = $username;
7673 $journal_url = $u->journal_base . "/";
7674 ($userhead) = $u->userhead($opts);
7677 if ( $u->is_identity ) {
7678 $identity = $u->identity;
7679 my $params = $identity ?
$identity->ljuser_display_params($u, $opts) : {};
7680 $profile_url = $params->{'profile_url'} || '';
7681 $journal_url = $params->{'journal_url'} || $journal_url;
7682 $journal_name = $params->{'journal_name'} || $journal_name;
7685 $profile_url = $u->profile_url();
7687 $username = LJ
::canonical_username
($username);
7689 last if $ljuser_cache->{$username};
7691 $journal_url = join('', $LJ::SITEROOT
, '/userinfo.bml?user=', $username);
7692 $profile_url ||= $journal_url;
7693 $userhead = 'userinfo.gif?v=17080';
7696 LJ
::run_hooks
( 'override_display_name', $u, \
$journal_name );
7697 LJ
::run_hooks
( 'override_profile_url', $u, \
$profile_url );
7698 LJ
::run_hooks
( 'override_journal_url', $u, \
$journal_url );
7701 if ( $color = $opts->{'link_color'} ) {
7702 unless ( $color =~ /^#(?:[a-f0-9]{3}|[a-f0-9]{6})$/i ) {
7707 %user = %{ $ljuser_cache->{$username} ||= {
7712 username
=> $username,
7713 journal
=> $journal_name,
7714 striked
=> $striked,
7715 journal_url
=> $journal_url,
7716 profile_url
=> $profile_url,
7717 userhead_url
=> $userhead,
7719 is_identity
=> $identity?
1 : 0,
7722 $user{'noctxpopup'} = 1 if $opts->{'noctxpopup'};
7723 $user{'bold'} = 1 if $opts->{'bold'} or not exists $opts->{'bold'};
7724 $user{'inline_css'} = 1 if $opts->{'inline_css'};
7725 $user{'journal'} = $opts->{'title'} if $opts->{'title'};
7726 $user{'target'} = $opts->{'target'} if $opts->{'target'};
7727 $user{'profile_url'} .= '?mode=full' if $opts->{'full'};
7728 $user{'profile_url'} = $opts->{'profile_url'} if $opts->{'profile_url'};
7729 $user{'user_alias'} = LJ
::ehtml
(LJ
::ljuser_alias
($username));
7730 $user{'alias'} = $user{'user_alias'}?
1 : 0;
7731 $user{'color'} = $color;
7733 if ( $opts->{'side_alias'} and $user{'alias'} ) {
7734 $user{'side_alias'} = 1;
7739 unless ( $user{'userhead_url'} =~ m!^https?:\/\/! ) {
7740 $user{'userhead_url'} = join('',
7741 $opts->{'imgroot'} || $LJ::IMGPREFIX
,
7742 '/', $user{'userhead_url'},
7743 '?v=', $LJ::CURRENT_VERSION
7747 # FIXME: try to remove this
7748 if ( $opts->{'in_journal'} ) {
7749 my $cu = LJ
::load_user
($opts->{'in_journal'});
7751 $user{'attrs'} = join('"', 'data-journal=', $cu->journal_base, '');
7755 if ( $opts->{'raw'} ) {
7758 return LJ
::Response
::CachedTemplate
->new(
7759 path
=> $ljuser_tmpl_path,
7760 file
=> 'Display.tmpl',
7767 my ($userid, $email) = @_;
7769 my $dbh = LJ
::get_db_writer
();
7770 if ($LJ::DEBUG
{'write_emails_to_user_table'}) {
7771 $dbh->do("UPDATE user SET email=? WHERE userid=?", undef,
7774 $dbh->do("REPLACE INTO email (userid, email) VALUES (?, ?)",
7775 undef, $userid, $email);
7778 LJ
::memcache_kill
($userid, "userid");
7779 LJ
::MemCache
::delete([$userid, "email:$userid"]);
7780 my $cache = $LJ::REQ_CACHE_USER_ID
{$userid} or return;
7781 $cache->{'_email'} = $email;
7785 my @friends_names = @_;
7787 push @ret, grep { $_ } map { LJ
::load_user
($_) } @friends_names;
7792 my ($userid, $password) = @_;
7794 my $dbh = LJ
::get_db_writer
();
7795 if ($LJ::DEBUG
{'write_passwords_to_user_table'}) {
7796 $dbh->do("UPDATE user SET password=? WHERE userid=?", undef,
7797 $password, $userid);
7799 $dbh->do("REPLACE INTO password (userid, password) VALUES (?, ?)",
7800 undef, $userid, $password);
7803 LJ
::memcache_kill
($userid, "userid");
7804 LJ
::MemCache
::delete([$userid, "pw:$userid"]);
7805 my $cache = $LJ::REQ_CACHE_USER_ID
{$userid} or return;
7806 $cache->{'_password'} = $password;
7811 my ($arg, $ref) = @_;
7814 if (ref $arg eq "ARRAY") {
7817 @uid = want_userid
($arg);
7819 @uid = grep { $_ } map { $_ + 0 } @uid;
7820 return 0 unless @uid;
7825 while (my ($k, $v) = each %$ref) {
7829 } elsif ($k eq 'email') {
7830 set_email
($_, $v) foreach @uid;
7831 } elsif ($k eq 'password') {
7832 set_password
($_, $v) foreach @uid;
7835 push @bindparams, $v;
7838 return 1 unless @sets;
7839 my $dbh = LJ
::get_db_writer
();
7840 return 0 unless $dbh;
7843 my $where = @uid == 1 ? "userid
=$uid[0]" : "userid IN
(@uid)";
7844 $dbh->do("UPDATE user SET
@sets WHERE
$where", undef,
7846 return 0 if $dbh->err;
7848 if (@LJ::MEMCACHE_SERVERS) {
7849 LJ::memcache_kill($_, "userid
") foreach @uid;
7853 # for a load of userids from the master after update
7854 # so we pick up the values set via the 'raw' option
7855 require_master(sub { LJ::load_userids(@uid) });
7857 foreach my $uid (@uid) {
7858 while (my ($k, $v) = each %$ref) {
7859 my $cache = $LJ::REQ_CACHE_USER_ID{$uid} or next;
7866 LJ::run_hooks("update_user
", userid => $_, fields => $ref)
7873 # name: LJ::get_timezone
7874 # des: Gets the timezone offset for the user.
7875 # args: u, offsetref, fakedref
7876 # des-u: user object.
7877 # des-offsetref: reference to scalar to hold timezone offset;
7878 # des-fakedref: reference to scalar to hold whether this timezone was
7879 # faked. 0 if it is the timezone specified by the user.
7880 # returns: nonzero if successful.
7883 my ($u, $offsetref, $fakedref) = @_;
7885 # See if the user specified their timezone
7886 if (my $tz = $u->prop('timezone')) {
7887 # If the eval fails, we'll fall through to guessing instead
7889 DateTime->from_epoch(
7896 $$offsetref = $dt->offset() / (60 * 60); # Convert from seconds to hours
7897 $$fakedref = 0 if $fakedref;
7903 # Either the user hasn't set a timezone or we failed at
7904 # loading it. We guess their current timezone's offset
7905 # by comparing the gmtime of their last post with the time
7906 # they specified on that post.
7908 # first, check request cache
7909 my $timezone = $u->{_timezone_guess};
7911 $$offsetref = $timezone;
7915 # next, check memcache
7916 my $memkey = [$u->userid, 'timezone_guess:' . $u->userid];
7917 my $memcache_data = LJ::MemCacheProxy::get($memkey);
7918 if ($memcache_data) {
7919 # fill the request cache since it was empty
7920 $u->{_timezone_guess} = $memcache_data;
7921 $$offsetref = $memcache_data;
7925 # nothing in cache; check db
7926 my $dbcr = LJ::get_cluster_def_reader($u);
7927 return 0 unless $dbcr;
7929 $$fakedref = 1 if $fakedref;
7931 # grab the times on the last post that wasn't backdated.
7932 # (backdated is rlogtime == $LJ::EndOfTime)
7933 if (my $last_row = $dbcr->selectrow_hashref(
7935 SELECT rlogtime, eventtime
7937 WHERE journalid = ? AND rlogtime <> ?
7938 ORDER BY rlogtime LIMIT 1
7939 }, undef, $u->{userid}, $LJ::EndOfTime)) {
7940 my $logtime = $LJ::EndOfTime - $last_row->{'rlogtime'};
7941 my $eventtime = LJ::TimeUtil->mysqldate_to_time($last_row->{'eventtime'}, 1);
7942 my $hourdiff = ($eventtime - $logtime) / 3600;
7944 # if they're up to a quarter hour behind, round up.
7945 $hourdiff = $hourdiff > 0 ? int($hourdiff + 0.25) : int($hourdiff - 0.25);
7947 # if the offset is more than 24h in either direction, then the last
7948 # entry is probably unreliable. don't use any offset at all.
7949 $$offsetref = (-24 < $hourdiff && $hourdiff < 24) ? $hourdiff : 0;
7952 $u->{_timezone_guess} = $$offsetref;
7953 my $expire = 60*60*24; # 24 hours
7954 LJ::MemCacheProxy::set($memkey, $$offsetref, $expire);
7960 # returns undef on error, or otherwise arrayref of arrayrefs,
7961 # each of format [ year, month, day, count ] for all days with
7962 # non-zero count. examples:
7963 # [ [ 2003, 6, 5, 3 ], [ 2003, 6, 8, 4 ], ... ]
7966 my ( $u, $remote ) = @_;
7968 # ['public'], ['all'], or [ 'gmask', $gmask ]
7972 if ( LJ::is_web_context() && LJ::Request->get_param('viewall') &&
7973 LJ::check_priv( $remote, 'canview', '*' ) )
7976 LJ::statushistory_add($u->{'userid'}, $remote->{'userid'},
7977 "viewall
", "calendar
");
7980 if ( $remote->can_manage($u) ) {
7983 if ( my $gmask = LJ::get_groupmask($u, $remote) ) {
7984 # friends case: allowmask == gmask == 1
7985 $kind = [ 'gmask', $gmask ];
7995 ## the first element of the array stored in memcache
7996 ## is the time of the creation of the list. The memcache is
7997 ## invalid if there are new entries in journal since that time.
7999 my $memkey = [ $u->userid, join( ':', 'dayct3', $u->userid, @$kind ) ];
8000 my $list = LJ::MemCache::get($memkey);
8002 my $list_create_time = shift @$list;
8003 return $list if $list_create_time >= $u->timeupdate;
8006 my $dbcr = LJ::get_cluster_def_reader($u) or return;
8008 ## get lock to prevent multiple apache processes to execute the sql below.
8009 ## one process runs, the other wait for results
8010 my $release_lock = sub {
8011 $dbcr->do( 'SELECT RELEASE_LOCK(?)', undef, $memkey->[1] );
8014 my ($locked) = $dbcr->selectrow_array(
8015 'SELECT GET_LOCK(?,2)', undef, $memkey->[1] );
8017 return [] unless $locked; ## 2 seconds expired
8019 $list = LJ::MemCache::get($memkey);
8021 ## other process may have filled the data while we waited for the lock
8022 my $list_create_time = shift @$list;
8023 if ($list_create_time >= $u->timeupdate) {
8029 if ( LJ::is_enabled( 'dayct_month', $u ) ) {
8031 my ( $min_year, $max_year ) = $dbcr->selectrow_array(
8032 'SELECT MIN(year), MAX(year) FROM log2 WHERE journalid=?',
8033 undef, $u->userid );
8037 foreach my $year ( $min_year .. $max_year ) {
8038 foreach my $month ( 1 .. 12 ) {
8039 my $month_daycounts =
8040 get_month_daycounts( $u, $kind, $year, $month );
8041 push @$days, @$month_daycounts;
8045 LJ::MemCache::set( $memkey, [ time, @$days ] );
8049 my ( $selecttype, $gmask ) = @$kind;
8051 if ( $selecttype eq 'all' ) {
8053 } elsif ( $selecttype eq 'public' ) {
8054 $secwhere = 'AND security="public
"';
8055 } elsif ( $selecttype eq 'gmask' ) {
8056 $secwhere = "AND
( security
='public' OR
" .
8057 "(security
='usemask' AND allowmask
& $gmask) )";
8060 my $sth = $dbcr->prepare("SELECT year
, month
, day
, COUNT
(*) ".
8061 "FROM log2 WHERE journalid
=?
$secwhere " .
8062 "GROUP BY year
, month
, day
");
8063 $sth->execute( $u->userid );
8065 while ( my ( $y, $m, $d, $c ) = $sth->fetchrow_array ) {
8066 # we force each number from string scalars (from DBI) to int scalars,
8067 # so they store smaller in memcache
8068 push @$days, [ int($y), int($m), int($d), int($c) ];
8071 LJ::MemCache::set( $memkey, [time, @$days] );
8077 sub get_month_daycounts {
8078 my ( $u, $kind, $year, $month ) = @_;
8080 my $memkind = join( ':', @$kind );
8083 ## the first element of the array stored in memcache
8084 ## is the time of the creation of the list. The memcache is
8085 ## invalid if there are new entries in journal since that time.
8088 join( ':', 'dayct3', 'month', $year, $month, $u->userid, @$kind );
8089 my $memkey = [ $u->userid, $memkey_base ];
8090 my $memlockkey = [ $u->userid, $memkey_base . ':lock' ];
8092 my $lock_acquired = 0;
8095 return if $lock_acquired;
8098 return LJ::MemCache::add( $memlockkey, 1, 2 );
8102 LJ::MemCache::delete($memlockkey);
8106 my $list = LJ::MemCache::get($memkey);
8108 my $list_create_time = shift @$list;
8109 my $list_exptime = shift @$list;
8111 my $need_recalculate = 0;
8113 my $timeupdate = $u->timeupdate;
8114 my $timeupdate_year = ( gmtime $timeupdate )[5] + 1900;
8115 my $timeupdate_month = ( gmtime $timeupdate )[4] + 1;
8117 $need_recalculate = 1
8118 if $timeupdate_year == $year &&
8119 $timeupdate_month == $month &&
8120 $u->timeupdate > $list_create_time;
8122 if ($need_recalculate) {
8123 $need_recalculate = 0 unless $lock->();
8126 return $list unless $need_recalculate;
8129 return [] unless $lock->();
8131 my ( $selecttype, $gmask ) = @$kind;
8133 if ( $selecttype eq 'all' ) {
8135 } elsif ( $selecttype eq 'public' ) {
8136 $secwhere = 'AND security="public
"';
8137 } elsif ( $selecttype eq 'gmask' ) {
8138 $secwhere = "AND
( security
='public' OR
" .
8139 "(security
='usemask' AND allowmask
& $gmask) )";
8142 my $dbcr = LJ::get_cluster_def_reader($u);
8144 my $sth = $dbcr->prepare("SELECT day
, COUNT
(*) ".
8145 "FROM log2 WHERE journalid
=?
$secwhere AND
" .
8146 "year
=? AND month
=?
" .
8148 $sth->execute( $u->userid, $year, $month );
8150 while ( my ( $d, $c ) = $sth->fetchrow_array ) {
8151 # we force each number from string scalars (from DBI) to int scalars,
8152 # so they store smaller in memcache
8153 push @$days, [ int($year), int($month), int($d), int($c) ];
8156 my $exptime = time + 3600 + int( rand(3600) );
8157 LJ::MemCache::set( $memkey, [ time, $exptime, @$days ] );
8163 ## input: $u, $remote, $year, $month
8164 ## output: hashref with data for rendering calendar for given month,
8165 ## days: arrayref [ count of entries for each day]
8166 ## days[1] = count of entries for the 1st day, days[0] is always null
8167 ## prev_month: arrayref [year, month] - previous month that has entries
8168 ## next_month, prev_year, next_year - arrayref of the same format
8170 sub get_calendar_data_for_month {
8171 my ($u, $remote, $year, $month) = @_;
8173 $remote ||= LJ::get_remote();
8174 unless ($year || $month) {
8175 ($month, $year) = (localtime)[4, 5];
8180 my %ret = (journal => $u->user, year => $year, month => $month);
8181 my $days = LJ::get_daycounts($u, $remote);
8182 foreach my $d (@$days) {
8183 ## @$d = ($y, $m, $d, $count)
8184 if ($d->[0]==$year && $d->[1]==$month) {
8185 $ret{days}->[ $d->[2] ] = $d->[3]+0;
8188 ## $prev_month = max( grep { $day < Date($year, $month) } @$days );
8189 ## max @list = List::Util::reduce { ($a < $b) ? $b : $a } @list
8190 ## min @list = List::Util::reduce { !($a < $b) ? $b : $a } @list
8191 my $current_month = [$year, $month];
8192 my $less_year = sub { my ($a, $b) = @_; return $a->[0]<$b->[0]; };
8193 my $less = sub { my ($a, $b) = @_; return $a->[0]<$b->[0] || $a->[0]==$b->[0] && $a->[1]<$b->[1] };
8194 $ret{'prev_month'} = List::Util::reduce { $less->($a, $b) ? $b : $a } grep { $less->($_, $current_month) } @$days;
8195 $ret{'next_month'} = List::Util::reduce { !$less->($a, $b) ? $b : $a } grep { $less->($current_month, $_) } @$days;
8196 $ret{'prev_year'} = List::Util::reduce { $less->($a, $b) ? $b : $a } grep { $less_year->($_, $current_month) } @$days;
8197 $ret{'next_year'} = List::Util::reduce { !$less->($a, $b) ? $b : $a } grep { $less_year->($current_month, $_) } @$days;
8198 foreach my $k (qw/prev_month next_month prev_year next_year/) {
8200 $ret{$k} = [ $ret{$k}->[0]+0, $ret{$k}->[1]+0];
8209 # name: LJ::set_interests
8210 # des: Change a user's interests.
8212 # des-old: hashref of old interests (hashing being interest => intid)
8213 # des-new: listref of new interests
8214 # returns: 1 on success, undef on failure
8217 my ($u, $old, $new) = @_;
8219 $u = LJ::want_user($u);
8220 my $userid = $u->{'userid'};
8221 return undef unless $userid;
8223 return undef unless ref $old eq 'HASH';
8224 return undef unless ref $new eq 'ARRAY';
8226 my $dbh = LJ::get_db_writer();
8228 my %int_del = %$old; # assume deleting everything, unless in @$new
8230 # user interests go in a different table than user interests,
8231 # though the schemas are the same so we can run the same queries on them
8232 my $uitable = $u->{'journaltype'} eq 'C' ? 'comminterests' : 'userinterests';
8234 # track if we made changes to refresh memcache later.
8237 my @valid_ints = LJ::validate_interest_list(@$new);
8238 foreach my $int (@valid_ints)
8240 $int_new{$int} = 1 unless $old->{$int};
8241 delete $int_del{$int};
8244 ### were interests removed?
8247 ## easy, we know their IDs, so delete them en masse
8248 my $intid_in = join(", ", values %int_del);
8249 $dbh->do("DELETE FROM
$uitable WHERE userid
=$userid AND intid IN
($intid_in)");
8250 $dbh->do("UPDATE interests SET intcount
=intcount
-1 WHERE intid IN
($intid_in) AND intcount
> 0");
8254 ### do we have new interests to add?
8255 my @new_intids = (); ## existing IDs we'll add for this user
8260 ## difficult, have to find intids of interests, and create new ints for interests
8261 ## that nobody has ever entered before
8262 my $int_in = join(", ", map { $dbh->quote($_); } keys %int_new);
8265 ## find existing IDs
8266 my $sth = $dbh->prepare("SELECT interest
, intid FROM interests WHERE interest IN
($int_in)");
8268 while (my ($intr, $intid) = $sth->fetchrow_array) {
8269 push @new_intids, $intid; # - we'll add this later.
8270 delete $int_new{$intr}; # - so we don't have to make a new intid for
8276 foreach my $newid (@new_intids) {
8277 if ($sql) { $sql .= ", "; }
8278 else { $sql = "REPLACE INTO
$uitable (userid
, intid
) VALUES
"; }
8279 $sql .= "($userid, $newid)";
8283 my $intid_in = join(", ", @new_intids);
8284 $dbh->do("UPDATE interests SET intcount
=intcount
+1 WHERE intid IN
($intid_in)");
8288 ### do we STILL have interests to add? (must make new intids)
8291 foreach my $int (keys %int_new)
8294 my $qint = $dbh->quote($int);
8296 $dbh->do("INSERT INTO interests
(intid
, intcount
, interest
) ".
8297 "VALUES
(NULL
, 1, $qint)");
8299 # somebody beat us to creating it. find its id.
8300 $intid = $dbh->selectrow_array("SELECT intid FROM interests WHERE interest
=$qint");
8301 $dbh->do("UPDATE interests SET intcount
=intcount
+1 WHERE intid
=$intid");
8304 $intid = $dbh->{'mysql_insertid'};
8307 ## now we can actually insert it into the userinterests table:
8308 $dbh->do("INSERT INTO
$uitable (userid
, intid
) ".
8309 "VALUES
($userid, $intid)");
8310 push @new_intids, $intid;
8314 LJ::run_hooks("set_interests
", $u, \%int_del, \@new_intids); # interest => intid
8316 # do migrations to clean up userinterests vs comminterests conflicts
8317 $u->lazy_interests_cleanup;
8319 LJ::memcache_kill($u, "intids
") if $did_mod;
8320 $u->{_cache_interests} = undef if $did_mod;
8325 sub validate_interest_list {
8326 my $interrors = ref $_[0] eq "ARRAY
" ? shift : [];
8329 my @valid_ints = ();
8330 foreach my $int (@ints) {
8331 $int = lc($int); # FIXME: use utf8?
8332 $int =~ s/^i like //; # *sigh*
8335 # Specific interest failures
8336 my ($bytes,$chars) = LJ::text_length($int);
8337 my @words = split(/\s+/, $int);
8338 my $word_ct = scalar @words;
8340 my $error_string = '';
8341 if ($int =~ /[\<\>]/) {
8342 $int = LJ::ehtml($int);
8343 $error_string .= '.invalid';
8345 $error_string .= '.bytes' if $bytes > LJ::BMAX_INTEREST;
8346 $error_string .= '.chars' if $chars > LJ::CMAX_INTEREST;
8347 $error_string .= '.words' if $word_ct > 4;
8350 if ($error_string) {
8351 $error_string = "error
.interest
$error_string";
8352 push @$interrors, [ $error_string,
8355 bytes_max => LJ::BMAX_INTEREST,
8357 chars_max => LJ::CMAX_INTEREST,
8364 push @valid_ints, $int;
8368 sub interest_string_to_list {
8371 $intstr =~ s/^\s+//; # strip leading space
8372 $intstr =~ s/\s+$//; # strip trailing space
8373 $intstr =~ s/\n/,/g; # newlines become commas
8374 $intstr =~ s/\s+/ /g; # strip duplicate spaces from the interest
8376 # final list is ,-sep
8377 return grep { length } split (/\s*,\s*/, $intstr);
8380 # $opts is optional, with keys:
8381 # forceids => 1 : don't use memcache for loading the intids
8382 # forceints => 1 : don't use memcache for loading the interest rows
8383 # justids => 1 : return arrayref of intids only, not names/counts
8384 # returns otherwise an arrayref of interest rows, sorted by interest name
8387 my ($u, $opts) = @_;
8389 return undef unless $u;
8391 # first check request cache inside $u
8392 if (my $ints = $u->{_cache_interests}) {
8393 if ($opts->{justids}) {
8394 return [ map { $_->[0] } @$ints ];
8399 my $uid = $u->{userid};
8400 my $uitable = $u->{'journaltype'} eq 'C' ? 'comminterests' : 'userinterests';
8404 my $mk_ids = [$uid, "intids
:$uid"];
8405 $ids = LJ::MemCache::get($mk_ids) unless $opts->{'forceids'};
8406 unless ($ids && ref $ids eq "ARRAY
") {
8408 my $dbh = LJ::get_db_writer();
8409 my $sth = $dbh->prepare("SELECT intid FROM
$uitable WHERE userid
=?
");
8410 $sth->execute($uid);
8411 push @$ids, $_ while ($_) = $sth->fetchrow_array;
8412 LJ::MemCache::add($mk_ids, $ids);
8415 # FIXME: set a 'justids' $u cache key in this case, then only return that
8416 # later if 'justids' is requested? probably not worth it.
8417 return $ids if $opts->{'justids'};
8419 # load interest rows
8421 $need{$_} = 1 foreach @$ids;
8424 unless ($opts->{'forceints'}) {
8425 if (my $mc = LJ::MemCache::get_multi(map { [$_, "introw
:$_"] } @$ids)) {
8426 while (my ($k, $v) = each %$mc) {
8427 next unless $k =~ /^introw:(\d+)/;
8435 my $ids = join(",", map { $_+0 } keys %need);
8436 my $dbr = LJ::get_db_reader();
8437 my $sth = $dbr->prepare("SELECT intid
, interest
, intcount FROM interests
".
8438 "WHERE intid IN
($ids)");
8441 while (my ($intid, $int, $count) = $sth->fetchrow_array) {
8442 # minimize latency... only store 25 into memcache at a time
8443 # (too bad we don't have set_multi.... hmmmm)
8444 my $aref = [$intid, $int, $count];
8445 if ($memc_store++ < 25) {
8446 # if the count is fairly high, keep item in memcache longer,
8447 # since count's not so important.
8448 my $expire = $count < 10 ? 3600*12 : 3600*48;
8449 LJ::MemCache::add([$intid, "introw
:$intid"], $aref, $expire);
8455 @ret = sort { $a->[1] cmp $b->[1] } @ret;
8456 return $u->{_cache_interests} = \@ret;
8460 # name: LJ::modify_caps
8461 # des: Given a list of caps to add and caps to remove, updates a user's caps.
8462 # args: uuid, cap_add, cap_del, res
8463 # des-cap_add: arrayref of bit numbers to turn on
8464 # des-cap_del: arrayref of bit numbers to turn off
8465 # des-res: hashref returned from 'modify_caps' hook
8466 # returns: updated u object, retrieved from $dbh, then 'caps' key modified
8467 # otherwise, returns 0 unless all hooks run properly.
8470 my ($argu, $cap_add, $cap_del, $res) = @_;
8471 my $userid = LJ::want_userid($argu);
8472 return undef unless $userid;
8476 my %cap_add_mod = ();
8477 my %cap_del_mod = ();
8479 # convert capnames to bit numbers
8480 if (LJ::are_hooks("get_cap_bit
")) {
8481 foreach my $bit (@$cap_add, @$cap_del) {
8482 next if $bit =~ /^\d+$/;
8484 # bit is a magical reference into the array
8485 $bit = LJ::run_hook("get_cap_bit
", $bit);
8489 # get a u object directly from the db
8490 my $u = LJ::load_userid($userid, "force
");
8492 delete $u->{sup_enabled} if $u;
8495 my $newcaps = int($u->{'caps'});
8496 foreach (@$cap_add) {
8499 # about to turn bit on, is currently off?
8500 $cap_add_mod{$_} = 1 unless $newcaps & $cap;
8504 # remove deleted caps
8505 foreach (@$cap_del) {
8508 # about to turn bit off, is it currently on?
8509 $cap_del_mod{$_} = 1 if $newcaps & $cap;
8513 # run hooks for modified bits
8514 if (LJ::are_hooks("modify_caps
")) {
8515 my @res = LJ::run_hooks("modify_caps
",
8517 'newcaps' => $newcaps,
8518 'oldcaps' => $u->{'caps'},
8519 'cap_on_req' => { map { $_ => 1 } @$cap_add },
8520 'cap_off_req' => { map { $_ => 1 } @$cap_del },
8521 'cap_on_mod' => \%cap_add_mod,
8522 'cap_off_mod' => \%cap_del_mod,
8525 # hook should return a status code
8526 foreach my $status (@res) {
8527 return undef unless ref $status and defined $status->[0];
8532 return 0 unless LJ::update_user($u, { 'caps' => $newcaps });
8534 $u->{caps} = $newcaps;
8535 $argu->{caps} = $newcaps if ref $argu; # temp hack
8537 LJ::run_hooks("props_changed
", $u, {caps => $newcaps});
8542 # returns 1 if action is permitted. 0 if above rate or fail.
8543 # action isn't logged on fail.
8546 # -- "limit_by_ip
" => "1.2.3.4" (when used for checking rate)
8550 my ($u, $ratename, $count, $opts) = @_;
8551 my $rateperiod = LJ::get_cap($u, "rateperiod
-$ratename");
8552 return 1 unless $rateperiod;
8554 return 0 unless $u->writer;
8556 my $rp = LJ::get_prop("rate
", $ratename);
8557 return 0 unless $rp;
8558 $opts->{'rp'} = $rp;
8561 $opts->{'now'} = $now;
8562 my $udbr = LJ::get_cluster_reader($u);
8563 my $ip = $udbr->quote($opts->{'limit_by_ip'} || "0.0.0.0");
8564 $opts->{'ip'} = $ip;
8565 return 0 unless LJ::rate_check($u, $ratename, $count, $opts);
8568 $count = $count + 0;
8569 $u->do("INSERT INTO ratelog
(userid
, rlid
, evttime
, ip
, quantity
) VALUES
".
8570 "($u->{'userid'}, $rp->{'id'}, $now, INET_ATON
($ip), $count)");
8572 # delete memcache, except in the case of rate limiting by ip
8573 unless ($opts->{limit_by_ip}) {
8574 LJ::MemCache::delete($u->rate_memkey($rp));
8580 # returns 1 if action is permitted. 0 if above rate or fail.
8582 my ($u, $ratename, $count, $opts) = @_;
8584 return 1 if grep { $_ eq $u->username } @LJ::NO_RATE_CHECK_USERS;
8586 my $rateperiod = LJ::get_cap($u, "rateperiod
-$ratename");
8587 return 1 unless $rateperiod;
8589 my $rp = defined $opts->{'rp'} ? $opts->{'rp'}
8590 : LJ::get_prop("rate
", $ratename);
8591 return 0 unless $rp;
8593 my $now = defined $opts->{'now'} ? $opts->{'now'} : time();
8594 my $beforeperiod = $now - $rateperiod;
8596 # check rate. (okay per period)
8597 my $opp = LJ::get_cap($u, "rateallowed
-$ratename");
8598 return 1 unless $opp;
8600 # check memcache, except in the case of rate limiting by ip
8601 my $memkey = $u->rate_memkey($rp);
8602 unless ($opts->{limit_by_ip}) {
8603 my $attempts = LJ::MemCache::get($memkey);
8605 my $num_attempts = 0;
8606 foreach my $attempt (@$attempts) {
8607 next if $attempt->{evttime} < $beforeperiod;
8608 $num_attempts += $attempt->{quantity};
8611 return $num_attempts + $count > $opp ? 0 : 1;
8615 return 0 unless $u->writer;
8617 # delete inapplicable stuff (or some of it)
8618 $u->do("DELETE FROM ratelog WHERE userid
=$u->{'userid'} AND rlid
=$rp->{'id'} ".
8619 "AND evttime
< $beforeperiod LIMIT
1000");
8621 my $udbr = LJ::get_cluster_reader($u);
8622 my $ip = defined $opts->{'ip'}
8624 : $udbr->quote($opts->{'limit_by_ip'} || "0.0.0.0");
8625 my $sth = $udbr->prepare("SELECT evttime
, quantity FROM ratelog WHERE
".
8626 "userid
=$u->{'userid'} AND rlid
=$rp->{'id'} ".
8627 "AND ip
=INET_ATON
($ip) ".
8628 "AND evttime
> $beforeperiod");
8633 while (my $data = $sth->fetchrow_hashref) {
8634 push @memdata, $data;
8635 $sum += $data->{quantity};
8638 # set memcache, except in the case of rate limiting by ip
8639 unless ($opts->{limit_by_ip}) {
8640 LJ::MemCache::set( $memkey => \@memdata || [] );
8643 # would this transaction go over the limit?
8644 if ($sum + $count > $opp) {
8645 # TODO: optionally log to rateabuse, unless caller is doing it themselves
8646 # somehow, like with the "loginstall
" table.
8659 $ip ||= LJ::get_remote_ip();
8660 return 0 unless $ip;
8663 my $rateperiod = LJ::get_cap($u, "rateperiod
-failed_login
");
8664 if ($rateperiod && ($udbr = LJ::get_cluster_reader($u))) {
8665 my $bantime = $udbr->selectrow_array("SELECT
time FROM loginstall WHERE
".
8666 "userid
=$u->{'userid'} AND ip
=INET_ATON
(?
)",
8668 if ($bantime && $bantime > time() - $rateperiod) {
8675 sub handle_bad_login
8680 $ip ||= LJ::get_remote_ip();
8681 return 1 unless $ip;
8683 # an IP address is permitted such a rate of failures
8684 # until it's banned for a period of time.
8686 if (! LJ::rate_log($u, "failed_login
", 1, { 'limit_by_ip' => $ip }) &&
8687 ($udbh = LJ::get_cluster_master($u)))
8689 $udbh->do("REPLACE INTO loginstall
(userid
, ip
, time) VALUES
".
8690 "(?
,INET_ATON
(?
),UNIX_TIMESTAMP
())", undef, $u->{'userid'}, $ip);
8696 # name: LJ::userpic_count
8697 # des: Gets a count of userpics for a given user.
8698 # args: upics, idlist
8699 # des-upics: hashref to load pictures into, keys being the picids
8700 # des-idlist: [$u, $picid] or [[$u, $picid], [$u, $picid], +] objects
8701 # also supports deprecated old method, of an array ref of picids.
8704 my $u = shift or return undef;
8706 if ($u->{'dversion'} > 6) {
8707 my $dbcr = LJ::get_cluster_def_reader($u) or return undef;
8708 return $dbcr->selectrow_array("SELECT COUNT
(*) FROM userpic2
" .
8709 "WHERE userid
=? AND
state <> 'X'", undef, $u->{'userid'});
8712 my $dbh = LJ::get_db_writer() or return undef;
8713 return $dbh->selectrow_array("SELECT COUNT
(*) FROM userpic
" .
8714 "WHERE userid
=? AND
state <> 'X'", undef, $u->{'userid'});
8718 # name: LJ::_friends_do
8719 # des: Runs given SQL, then deletes the given userid's friends from memcache.
8720 # args: uuserid, sql, args
8721 # des-uuserid: a userid or u object
8722 # des-sql: SQL to run via $dbh->do()
8723 # des-args: a list of arguments to pass use via: $dbh->do($sql, undef, @args)
8724 # returns: return false on error
8727 my ($uuid, $sql, @args) = @_;
8728 my $uid = want_userid($uuid);
8729 return undef unless $uid && $sql;
8731 my $dbh = LJ::get_db_writer() or return 0;
8733 my $ret = $dbh->do($sql, undef, @args);
8734 return 0 if $dbh->err;
8736 LJ::memcache_kill($uid, "friends
");
8738 # pass $uuid in case it's a $u object which mark_dirty wants
8739 LJ::mark_dirty($uuid, "friends
");
8745 # name: LJ::add_friend
8746 # des: Simple interface to add a friend edge.
8747 # args: uuid, to_add, opts?
8748 # des-to_add: a single uuid or an arrayref of uuids to add (befriendees)
8749 # des-opts: hashref; 'defaultview' key means add target uuids to $uuid's Default View friends group,
8750 # 'groupmask' key means use this group mask
8751 # returns: boolean; 1 on success (or already friend), 0 on failure (bogus args)
8754 my ($userid, $to_add, $opts) = @_;
8756 $userid = LJ::want_userid($userid);
8757 return 0 unless $userid;
8759 my @add_ids = ref $to_add eq 'ARRAY' ? map { LJ::want_userid($_) } @$to_add : ( LJ::want_userid($to_add) );
8760 return 0 unless @add_ids;
8762 # clean widget cache
8763 my $widget_key = "friend_birthdays
:" . $userid;
8764 LJ::MemCache::delete($widget_key);
8766 my $friender = LJ::load_userid($userid);
8769 ## TODO: rate check of adding friends needs PM elaboration
8770 ## Remove '1 ||' when specification is complete
8771 unless (1 || $opts->{no_rate_check}){
8772 my $cond = ["ratecheck
:add_friend
:$userid",
8773 [ $LJ::ADD_FRIEND_RATE_LIMIT || [ 1, 3600 ] ]
8775 return 0 unless LJ::RateLimit->check($friender, [ $cond ]);
8778 my $sclient = LJ::theschwartz();
8780 my $fgcol = LJ::color_todb($opts->{'fgcolor'}) || LJ::color_todb("#000000");
8781 my $bgcol = LJ
::color_todb
($opts->{'bgcolor'});
8782 # in case the background color is #000000, in which case the || falls through
8783 # so only overwrite what we got if what we got was undef (invalid input)
8784 $bgcol = LJ
::color_todb
("#ffffff") unless defined $bgcol;
8789 if (defined $opts->{groupmask
}) {
8790 $groupmask = $opts->{groupmask
};
8791 } elsif ($opts->{'defaultview'}) {
8792 # TAG:FR:ljlib:add_friend_getdefviewmask
8793 my $group = LJ
::get_friend_group
($userid, { name
=> 'Default View' });
8794 my $grp = $group ?
$group->{groupnum
}+0 : 0;
8795 $groupmask |= (1 << $grp) if $grp;
8798 # part of the criteria for whether to fire befriended event
8799 my $notify = !$LJ::DISABLED
{esn
} && !$opts->{nonotify
}
8800 && $friender->is_visible && $friender->is_person;
8803 # load all users at once
8804 LJ
::load_userids
(@add_ids);
8805 foreach my $add_id (@add_ids) {
8806 LJ
::RelationService
->create_relation_to(
8807 $friender, $add_id, 'F',
8808 groupmask
=> $groupmask,
8813 my $friendee = LJ
::load_userid
($add_id);
8814 LJ
::add_to_friend_list
($friender, $friendee);
8815 __drop_short_lifetime_cache
($friender, $friendee);
8820 # only fire event if the friender is a person and not banned and visible
8821 if ($notify && !$friendee->is_banned($friender)) {
8822 require LJ
::Event
::BefriendedDelayed
;
8823 LJ
::Event
::BefriendedDelayed
->send($friendee, $friender);
8826 push @jobs, TheSchwartz
::Job
->new(
8827 funcname
=> "LJ::NewWorker::TheSchwartz::FriendChange",
8828 arg
=> [$userid, 'add', $add_id],
8829 ) unless $LJ::DISABLED
{'friendchange-schwartz'};
8831 $sclient->insert_jobs(@jobs) if @jobs;
8836 # WARNING: always returns "true". Check result of executing "REPLACE INTO friends ..." statement above.
8841 # name: LJ::remove_friend
8842 # des: delete existing friends.
8843 # args: uuid, to_del
8844 # des-to_del: a single uuid or an arrayref of uuids to remove.
8848 my ($userid, $to_del, $opts) = @_;
8850 $userid = LJ
::want_userid
($userid);
8851 return undef unless $userid;
8853 my @del_ids = ref $to_del eq 'ARRAY' ?
map { LJ
::want_userid
($_) } @
$to_del : ( LJ
::want_userid
($to_del) );
8854 return 0 unless @del_ids;
8856 my $u = LJ
::load_userid
($userid);
8858 my $dbh = LJ
::get_db_writer
() or return 0;
8860 my $sclient = LJ
::theschwartz
();
8861 # part of the criteria for whether to fire defriended event
8862 my $notify = !$LJ::DISABLED
{esn
} && !$opts->{nonotify
} && $u->is_visible && $u->is_person;
8864 foreach my $del_id (@del_ids) {
8865 LJ
::RelationService
->remove_relation_to( $u, $del_id, 'F' );
8868 LJ
::load_userids
(@del_ids);
8869 # delete friend-of memcache keys for anyone who was removed
8870 foreach my $fid (@del_ids) {
8871 my $friendee = LJ
::load_userid
($fid);
8873 LJ
::remove_from_friend_list
($u, $friendee);
8874 __drop_short_lifetime_cache
($u, $friendee);
8879 # only fire event if the friender is a person and not banned and visible
8880 if ($notify && !$friendee->has_banned($u)) {
8881 require LJ
::Event
::DefriendedDelayed
;
8882 LJ
::Event
::DefriendedDelayed
->send($friendee, $u);
8885 push @jobs, TheSchwartz
::Job
->new(
8886 funcname
=> "LJ::NewWorker::TheSchwartz::FriendChange",
8887 arg
=> [$userid, 'del', $fid],
8888 ) unless $LJ::DISABLED
{'friendchange-schwartz'};
8890 $sclient->insert_jobs(@jobs);
8897 *delete_friend_edge
= \
&LJ
::remove_friend
;
8899 sub __drop_short_lifetime_cache
{
8900 my ($u, $friend) = @_;
8903 return unless $friend;
8905 my @clean_clist = ('cfriends', 'member', 'mutual_cfriends');
8906 my @clean_flist = ('friends', 'mutual_friends', 'pfriends', 'friendof', 'mutual', 'yfriends');
8908 my $remote = LJ
::get_remote
();
8910 my $sub_drop = sub {
8911 my ($userid, $list_name) = @_;
8913 my $cached = $list_name !~ /mutual/;
8917 $uid = $remote ?
$remote->userid : 'n';
8920 LJ
::MemCache
::delete("u:profile:l:$userid:$uid:$list_name:");
8921 LJ
::MemCache
::delete("u:profile:l:$userid:$uid:$list_name:150");
8923 LJ
::MemCache
::delete("u:profile:l:$userid:n:$list_name:");
8924 LJ
::MemCache
::delete("u:profile:l:$userid:n:$list_name:150");
8928 my $userid = $u->userid;
8929 my $friendid = $friend->userid;
8931 foreach my $list_name (@clean_flist) {
8932 $sub_drop->($userid, $list_name);
8935 foreach my $list_name (@clean_clist) {
8936 $sub_drop->($userid, $list_name);
8939 foreach my $list_name (@clean_flist) {
8940 $sub_drop->($friendid, $list_name);
8943 foreach my $list_name (@clean_clist) {
8944 $sub_drop->($friendid, $list_name);
8950 # name: LJ::get_friends
8951 # des: Returns friends rows for a given user.
8952 # args: uuserid, mask?, memcache_only?, force?
8953 # des-uuserid: a userid or u object.
8954 # des-mask: a security mask to filter on.
8955 # des-memcache_only: flag, set to only return data from memcache
8956 # des-force: flag, set to ignore memcache and always hit DB.
8957 # returns: hashref; keys = friend userids
8958 # values = hashrefs of 'friends' columns and their values
8961 # TAG:FR:ljlib:get_friends
8962 my ($uuid, $mask, $memcache_only, $force) = @_;
8963 my $userid = LJ
::want_userid
($uuid);
8964 return undef unless $userid;
8965 return undef if $LJ::FORCE_EMPTY_FRIENDS
{$userid};
8967 my $u = LJ
::load_userid
($userid);
8969 return LJ
::RelationService
->load_relation_destinations(
8973 memcache_only
=> $memcache_only,
8979 # name: LJ::get_friendofs
8980 # des: Returns userids of friendofs for a given user.
8981 # args: uuserid, opts?
8982 # des-opts: options hash, keys: 'force' => don't check memcache
8983 # returns: userid for friendofs
8986 # TAG:FR:ljlib:get_friends
8987 my ($uuid, $opts) = @_;
8988 my $userid = LJ
::want_userid
($uuid);
8989 return undef unless $userid;
8991 my $u = LJ
::load_userid
($userid);
8992 return LJ
::RelationService
->find_relation_sources($u, 'F',
8993 nolimit
=> $opts->{force
} || 0,
8994 skip_memcached
=> $opts->{force
},
8999 # name: LJ::fill_groups_xmlrpc
9000 # des: Fills a hashref (presumably to be sent to an XML-RPC client, e.g. FotoBilder)
9001 # with user friend group information
9003 # des-ret: a response hashref to fill with friend group data
9004 # returns: undef if called incorrectly, 1 otherwise
9006 sub fill_groups_xmlrpc
{
9008 return undef unless ref $u && ref $ret;
9010 # best interface ever...
9011 $RPC::XML
::ENCODING
= "utf-8";
9013 # layer on friend group information in the following format:
9015 # grp:1 => 'mygroup',
9017 # grp:30 => 'anothergroup',
9019 # grpu:whitaker => '0,1,2,3,4',
9022 my $grp = LJ
::get_friend_group
($u) || {};
9024 # we don't always have RPC::XML loaded (in web context), and it doesn't really
9025 # matter much anyway, since our only consumer is also perl which will take
9026 # the occasional ints back to strings.
9029 my $val = eval { RPC
::XML
::string
->new($str); };
9030 return $val unless $@
;
9034 $ret->{"grp:0"} = $str->("_all_");
9035 foreach my $bit (1..30) {
9036 next unless my $g = $grp->{$bit};
9037 $ret->{"grp:$bit"} = $str->($g->{groupname
});
9040 my $fr = LJ
::get_friends
($u) || {};
9041 my $users = LJ
::load_userids
(keys %$fr);
9042 while (my ($fid, $f) = each %$fr) {
9043 my $u = $users->{$fid};
9044 next unless $u->{journaltype
} =~ /[PSI]/;
9046 my $fname = $u->{user
};
9047 $ret->{"grpu:$fid:$fname"} =
9048 $str->(join(",", 0, grep { $grp->{$_} && $f->{groupmask
} & 1 << $_ } 1..30));
9055 # name: LJ::mark_dirty
9056 # des: Marks a given user as being $what type of dirty.
9058 # des-what: type of dirty being marked (e.g. 'friends')
9062 my ($uuserid, $what) = @_;
9064 my $userid = LJ
::want_userid
($uuserid);
9065 return 1 if $LJ::REQ_CACHE_DIRTY
{$what}->{$userid};
9067 my $u = LJ
::want_user
($userid);
9069 # friends dirtiness is only necessary to track
9070 # if we're exchange XMLRPC with fotobilder
9071 if ($what eq 'friends') {
9072 return 1 unless $LJ::FB_SITEROOT
;
9073 my $sclient = LJ
::theschwartz
();
9075 push @LJ::CLEANUP_HANDLERS
, sub {
9077 my $job = TheSchwartz
::Job
->new(
9078 funcname
=> "LJ::Worker::UpdateFotobilderFriends",
9079 coalesce
=> "uid:$u->{userid}",
9080 arg
=> $u->{userid
},
9082 $sclient->insert($job);
9084 die "No schwartz client found";
9089 $LJ::REQ_CACHE_DIRTY
{$what}->{$userid}++;
9095 # name: LJ::delete_all_comments
9096 # des: deletes all comments from a post, permanently, for when a post is deleted
9097 # info: The tables [dbtable[talk2]], [dbtable[talkprop2]], [dbtable[talktext2]],
9098 # are deleted from, immediately.
9099 # args: u, nodetype, nodeid
9100 # des-nodetype: The thread nodetype (probably 'L' for log items).
9101 # des-nodeid: The thread nodeid for the given nodetype (probably the jitemid
9102 # from the [dbtable[log2]] row).
9103 # returns: boolean; success value
9105 sub delete_all_comments
{
9106 my ($u, $nodetype, $nodeid) = @_;
9108 my $dbcm = LJ
::get_cluster_master
($u);
9109 return 0 unless $dbcm && $u->writer;
9112 my ($t, $loop) = (undef, 1);
9113 my $chunk_size = 200;
9115 ($t = $dbcm->selectcol_arrayref("SELECT jtalkid FROM talk2 WHERE ".
9116 "nodetype=? AND journalid=? ".
9117 "AND nodeid=? LIMIT $chunk_size", undef,
9118 $nodetype, $u->{'userid'}, $nodeid))
9121 my @batch = map { int $_ } @
$t;
9122 my $in = join(',', @batch);
9123 return 1 unless $in;
9125 LJ
::run_hooks
('report_cmt_delete', $u->{'userid'}, \
@batch);
9126 LJ
::run_hooks
('report_cmt_text_delete', $u->{'userid'}, \
@batch);
9127 foreach my $table (qw(talkprop2 talktext2 talk2)) {
9128 $u->do("DELETE FROM $table WHERE journalid=? AND jtalkid IN ($in)",
9129 undef, $u->{'userid'});
9131 # decrement memcache
9132 LJ
::MemCache
::decr
([$u->{'userid'}, "talk2ct:$u->{'userid'}"], scalar(@
$t));
9133 $loop = 0 unless @
$t == $chunk_size;
9139 # is a user object (at least a hashref)
9141 return unless ref $_[0];
9142 return 1 if UNIVERSAL
::isa
($_[0], "LJ::User");
9144 if (ref $_[0] eq "HASH" && $_[0]->{userid
}) {
9145 carp
"User HASH objects are deprecated from use." if $LJ::IS_DEV_SERVER
;
9150 # create externally mapped user.
9151 # return uid of LJ user on success, undef on error.
9153 # extuser or extuserid (or both, but one is required.),
9156 # opts also can contain any additional options that create_account takes. (caps?)
9159 my ($type, $opts) = @_;
9160 return undef unless $type && $LJ::EXTERNAL_NAMESPACE
{$type}->{id
};
9161 return undef unless ref $opts &&
9162 ($opts->{extuser
} || defined $opts->{extuserid
});
9165 my $dbh = LJ
::get_db_writer
();
9166 return undef unless $dbh;
9168 # make sure a mapping for this user doesn't already exist.
9169 $uid = LJ
::get_extuser_uid
( $type, $opts, 'force' );
9170 return $uid if $uid;
9172 # increment ext_ counter until we successfully create an LJ account.
9173 # hard cap it at 10 tries. (arbitrary, but we really shouldn't have *any*
9174 # failures here, let alone 10 in a row.)
9176 my $extuser = 'ext_' . LJ
::alloc_global_counter
( 'E' );
9179 { caps
=> $opts->{caps
}, user
=> $extuser, name
=> $extuser } );
9181 select undef, undef, undef, .10; # lets not thrash over this.
9183 return undef unless $uid;
9185 # add extuser mapping.
9186 my $sql = "INSERT INTO extuser SET userid=?, siteid=?";
9187 my @bind = ($uid, $LJ::EXTERNAL_NAMESPACE
{$type}->{id
});
9189 if ($opts->{extuser
}) {
9190 $sql .= ", extuser=?";
9191 push @bind, $opts->{extuser
};
9194 if ($opts->{extuserid
}) {
9195 $sql .= ", extuserid=? ";
9196 push @bind, $opts->{extuserid
}+0;
9199 $dbh->do($sql, undef, @bind) or return undef;
9204 my ($url, $remote) = @_;
9206 my $privilege = $LJ::PAGE_PRIVILEGES
{$url} || $LJ::PAGE_PRIVILEGES
{"$url/"};
9208 return 0 unless $privilege;
9210 my $priv = $privilege->{'priv'};
9211 my $arg = $privilege->{'arg'};
9212 if ( LJ
::check_priv
($remote, $priv, $arg) ) {
9213 my $uri = LJ
::Request
->uri;
9214 my $args = LJ
::Request
->args;
9215 my $current_url = "$uri?$args";
9216 my $authas = LJ
::Request
->get_param('authas') || LJ
::Request
->post_param('authas');
9217 my $u = LJ
::load_user
($authas);
9218 LJ
::statushistory_add
($u, $remote, "view_settings", "$current_url" );
9224 # given an extuserid or extuser, return the LJ uid.
9225 # return undef if there is no mapping.
9228 my ($type, $opts, $force) = @_;
9229 return undef unless $type && $LJ::EXTERNAL_NAMESPACE
{$type}->{id
};
9230 return undef unless ref $opts &&
9231 ($opts->{extuser
} || defined $opts->{extuserid
});
9233 my $dbh = $force ? LJ
::get_db_writer
() : LJ
::get_db_reader
();
9234 return undef unless $dbh;
9236 my $sql = "SELECT userid FROM extuser WHERE siteid=?";
9237 my @bind = ($LJ::EXTERNAL_NAMESPACE
{$type}->{id
});
9239 if ($opts->{extuser
}) {
9240 $sql .= " AND extuser=?";
9241 push @bind, $opts->{extuser
};
9244 if ($opts->{extuserid
}) {
9245 $sql .= $opts->{extuser
} ?
' OR ' : ' AND ';
9246 $sql .= "extuserid=?";
9247 push @bind, $opts->{extuserid
}+0;
9250 return $dbh->selectrow_array($sql, undef, @bind);
9253 # given a LJ userid/u, return a hashref of:
9254 # type, extuser, extuserid
9255 # returns undef if user isn't an externally mapped account.
9258 my $uid = LJ
::want_userid
(shift);
9259 return undef unless $uid;
9261 my $dbr = LJ
::get_db_reader
();
9262 return undef unless $dbr;
9264 my $sql = "SELECT * FROM extuser WHERE userid=?";
9265 my $ret = $dbr->selectrow_hashref($sql, undef, $uid);
9266 return undef unless $ret;
9268 my $type = 'unknown';
9269 foreach ( keys %LJ::EXTERNAL_NAMESPACE
) {
9270 $type = $_ if $LJ::EXTERNAL_NAMESPACE
{$_}->{id
} == $ret->{siteid
};
9273 $ret->{type
} = $type;
9278 # name: LJ::create_account
9279 # des: Creates a new basic account. <strong>Note:</strong> This function is
9280 # not really too useful but should be extended to be useful so
9281 # htdocs/create.bml can use it, rather than doing the work itself.
9282 # returns: integer of userid created, or 0 on failure.
9284 # des-opts: hashref containing keys 'user', 'name', 'password', 'email', 'caps', 'journaltype'.
9286 sub create_account
{
9288 my $u = LJ
::User
->create(%$opts)
9295 # name: LJ::new_account_cluster
9296 # des: Which cluster to put a new account on. $DEFAULT_CLUSTER if it's
9297 # a scalar, random element from [ljconfig[default_cluster]] if it's arrayref.
9298 # also verifies that the database seems to be available.
9299 # returns: clusterid where the new account should be created; 0 on error
9300 # (such as no clusters available).
9302 sub new_account_cluster
9304 # if it's not an arrayref, put it in an array ref so we can use it below
9305 my $clusters = ref $LJ::DEFAULT_CLUSTER ?
$LJ::DEFAULT_CLUSTER
: [ $LJ::DEFAULT_CLUSTER
+0 ];
9307 # select a random cluster from the set we've chosen in $LJ::DEFAULT_CLUSTER
9308 return LJ
::random_cluster
(@
$clusters);
9311 # returns the clusterid of a random cluster which is up
9312 # -- accepts @clusters as an arg to enforce a subset, otherwise
9313 # uses @LJ::CLUSTERS
9314 sub random_cluster
{
9315 my @clusters = @_ ?
@_ : @LJ::CLUSTERS
;
9317 # iterate through the new clusters from a random point
9318 my $size = @clusters;
9319 my $start = int(rand() * $size);
9320 foreach (1..$size) {
9321 my $cid = $clusters[$start++ % $size];
9323 # verify that this cluster is in @LJ::CLUSTERS
9324 my @check = grep { $_ == $cid } @LJ::CLUSTERS
;
9325 next unless scalar(@check) >= 1 && $check[0] == $cid;
9327 # try this cluster to see if we can use it, return if so
9328 my $dbcm = LJ
::get_cluster_master
($cid);
9329 return $cid if $dbcm;
9332 # if we get here, we found no clusters that were up...
9337 # name: LJ::make_journal
9338 # args: user, view, remote, opts
9341 my ($user, $view, $remote, $opts) = @_;
9343 my $geta = $opts->{'getargs'};
9345 if ($LJ::SERVER_DOWN
) {
9346 if ($opts->{'vhost'} eq "customview") {
9347 return "<!-- LJ down for maintenance -->";
9349 return LJ
::server_down_html
();
9352 my $u = $opts->{'u'} || LJ
::load_user
($user);
9354 $opts->{'baduser'} = 1;
9355 return "<h1>Error</h1>No such user <b>$user</b>";
9357 LJ
::set_active_journal
($u);
9358 LJ
::Request
->notes('ljentry' => $opts->{'ljentry'}->url) if $opts->{'ljentry'};
9360 # S1 style hashref. won't be loaded now necessarily,
9361 # only if via customview.
9365 if ($opts->{'styleid'}) { # s1 styleid
9366 $styleid = $opts->{'styleid'}+0;
9368 # if we have an explicit styleid, we have to load
9369 # it early so we can learn its type, so we can
9370 # know which uprops to load for its owner
9371 if ($LJ::ONLY_USER_VHOSTS
&& $opts->{vhost
} eq "customview") {
9372 # reject this style if it's not trusted by the user, and we're showing
9373 # stuff on user domains
9374 my $ownerid = LJ
::S1
::get_style_userid_always
($styleid);
9375 my $is_trusted = sub {
9376 return 1 if $ownerid == $u->{userid
};
9377 return 1 if $ownerid == LJ
::system_userid
();
9378 return 1 if $LJ::S1_CUSTOMVIEW_WHITELIST
{"styleid-$styleid"};
9379 return 1 if $LJ::S1_CUSTOMVIEW_WHITELIST
{"userid-$ownerid"};
9380 my $trust_list = eval { $u->prop("trusted_s1") };
9381 return 1 if $trust_list =~ /\b$styleid\b/;
9384 unless ($is_trusted->()) {
9391 $view ||= "lastn"; # default view when none specified explicitly in URLs
9392 if ($LJ::viewinfo
{$view} || $view eq "month" ||
9393 $view eq "entry" || $view eq "reply") {
9394 $styleid = -1; # to get past the return, then checked later for -1 and fixed, once user is loaded.
9396 $opts->{'badargs'} = 1;
9399 return unless $styleid;
9402 $u->{'_journalbase'} = LJ
::journal_base
($u->{'user'}, $opts->{'vhost'});
9404 my $eff_view = $LJ::viewinfo
{$view}->{'styleof'} || $view;
9405 my $s1prop = "s1_${eff_view}_style";
9407 my @needed_props = ("stylesys", "s2_style", "url", "urlname", "opt_nctalklinks",
9408 "renamedto", "opt_blockrobots", "opt_usesharedpic", "icbm",
9409 "journaltitle", "journalsubtitle", "external_foaf_url",
9410 "adult_content", "admin_content_flag", "community_reader_ids");
9412 # S2 is more fully featured than S1, so sometimes we get here and $eff_view
9413 # is reply/month/entry/res and that means it *has* to be S2--S1 defaults to a
9414 # BML page to handle those, but we don't want to attempt to load a userprop
9415 # because now load_user_props dies if you try to load something invalid
9416 push @needed_props, $s1prop if $eff_view =~ /^(?:calendar|day|friends|lastn)$/;
9418 # preload props the view creation code will need later (combine two selects)
9419 if (ref $LJ::viewinfo
{$eff_view}->{'owner_props'} eq "ARRAY") {
9420 push @needed_props, @
{$LJ::viewinfo
{$eff_view}->{'owner_props'}};
9423 if ($eff_view eq "reply") {
9424 push @needed_props, "opt_logcommentips";
9427 $u->preload_props(@needed_props);
9429 # FIXME: remove this after all affected accounts have been fixed
9430 # see http://zilla.livejournal.org/1443 for details
9431 if ($u->{$s1prop} =~ /^\D/) {
9432 $u->{$s1prop} = $LJ::USERPROP_DEF
{$s1prop};
9433 $u->set_prop($s1prop, $u->{$s1prop});
9436 # if the remote is the user to be viewed, make sure the $remote
9437 # hashref has the value of $u's opt_nctalklinks (though with
9438 # LJ::load_user caching, this may be assigning between the same
9439 # underlying hashref)
9440 $remote->{'opt_nctalklinks'} = $u->{'opt_nctalklinks'} if
9441 ($remote && $remote->{'userid'} == $u->{'userid'});
9444 if ($styleid == -1) {
9446 my $get_styleinfo = sub {
9448 my $get_s1_styleid = sub {
9449 my $id = $u->{$s1prop};
9450 LJ
::run_hooks
("s1_style_select", {
9458 # forced s2 style id
9459 if ($geta->{'s2id'}) {
9461 # get the owner of the requested style
9462 my $style = LJ
::S2
::load_style
( $geta->{s2id
} );
9463 my $owner = $style && $style->{userid
} ?
$style->{userid
} : 0;
9465 # remote can use s2id on this journal if:
9466 # owner of the style is remote or managed by remote OR
9467 # owner of the style has s2styles cap and remote is viewing owner's journal
9469 if ($u->id == $owner && $u->get_cap("s2styles")) {
9470 $opts->{'style_u'} = LJ
::load_userid
($owner);
9471 return (2, $geta->{'s2id'});
9474 if ($remote && $remote->can_manage($owner)) {
9475 # check is owned style still available: paid user possible became plus...
9476 my $lay_id = $style->{layer
}->{layout
};
9477 my $theme_id = $style->{layer
}->{theme
};
9479 LJ
::S2
::load_layer_info
(\
%lay_info, [$style->{layer
}->{layout
}, $style->{layer
}->{theme
}]);
9481 if (LJ
::S2
::can_use_layer
($remote, $lay_info{$lay_id}->{redist_uniq
})
9482 and LJ
::S2
::can_use_layer
($remote, $lay_info{$theme_id}->{redist_uniq
})) {
9483 $opts->{'style_u'} = LJ
::load_userid
($owner);
9484 return (2, $geta->{'s2id'});
9485 } # else this style not allowed by policy
9489 # style=mine passed in GET?
9490 if ($remote && ( $geta->{'style'} eq 'mine' ||
9491 $remote->opt_stylealwaysmine ) ) {
9493 # get remote props and decide what style remote uses
9494 $remote->preload_props("stylesys", "s2_style");
9496 # remote using s2; make sure we pass down the $remote object as the style_u to
9497 # indicate that they should use $remote to load the style instead of the regular $u
9498 if ($remote->{'stylesys'} == 2 && $remote->{'s2_style'}) {
9499 $opts->{'checkremote'} = 1;
9500 $opts->{'style_u'} = $remote;
9501 return (2, $remote->{'s2_style'});
9505 return (1, $get_s1_styleid->());
9508 # resource URLs have the styleid in it
9509 if ($view eq "res" && $opts->{'pathextra'} =~ m!^/(\d+)/!) {
9514 LJ
::run_hooks
("force_s1", $u, \
$forceflag);
9516 # if none of the above match, they fall through to here
9517 if ( !$forceflag && $u->{'stylesys'} == 2 ) {
9518 return (2, $u->{'s2_style'});
9521 # no special case and not s2, fall through to s1
9522 return (1, $get_s1_styleid->());
9525 if ($LJ::JOURNALS_WITH_FIXED_STYLE
{$u->user}) {
9526 ($stylesys, $styleid) = (2, $u->{'s2_style'});
9528 ($stylesys, $styleid) = $get_styleinfo->();
9532 # transcode the tag filtering information into the tag getarg; this has to
9533 # be done above the s1shortcomings section so that we can fall through to that
9534 # style for lastn filtered by tags view
9535 if ($view eq 'lastn' && $opts->{pathextra
} && $opts->{pathextra
} =~ /^\/tag\
/(.+)$/) {
9536 $opts->{getargs
}->{tag
} = $1;
9537 $opts->{pathextra
} = undef;
9540 # do the same for security filtering
9541 elsif ($view eq 'lastn' && $opts->{pathextra
} && $opts->{pathextra
} =~ /^\/security\
/(.+)$/) {
9542 $opts->{getargs
}->{security
} = $1;
9543 $opts->{pathextra
} = undef;
9546 if (LJ
::Request
->is_inited) {
9547 LJ
::Request
->notes('journalid' => $u->{'userid'});
9554 my $url = "$LJ::SITEROOT/users/$user/";
9555 $opts->{'status'} = $status if $status;
9558 my $journalbase = LJ
::journal_base
($user);
9560 # Automatic Discovery of RSS/Atom
9561 $head .= qq{<link rel
="alternate" type
="application/rss+xml" title
="RSS" href
="$journalbase/data/rss" />\n};
9562 $head .= qq{<link rel
="alternate" type
="application/atom+xml" title
="Atom" href
="$journalbase/data/atom" />\n};
9563 $head .= qq{<link rel
="service.feed" type
="application/atom+xml" title
="AtomAPI-enabled feed" href
="$LJ::SITEROOT/interface/atom/feed" />\n};
9564 $head .= qq{<link rel
="service.post" type
="application/atom+xml" title
="Create a new post" href
="$LJ::SITEROOT/interface/atom/post" />\n};
9566 # OpenID Server and Yadis
9567 $head .= $u->openid_tags;
9569 # FOAF autodiscovery
9570 my $foafurl = $u->{external_foaf_url
} ? LJ
::eurl
($u->{external_foaf_url
}) : "$journalbase/data/foaf";
9571 $head .= qq{<link rel
="meta" type
="application/rdf+xml" title
="FOAF" href
="$foafurl" />\n};
9573 if ($u->email_visible($remote)) {
9574 my $digest = Digest
::SHA1
::sha1_hex
('mailto:' . $u->email_raw);
9575 $head .= qq{<meta name
="foaf:maker" content
="foaf:mbox_sha1sum '$digest'" />\n};
9586 <p
>Instead
, please
use <nobr
><a href
=\"$url\">$url</a></nobr
></p
>
9589 }.("<!-- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -->\n" x
50);
9594 $opts->{'status'} = $status if $status;
9599 }.("<!-- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -->\n" x
50);
9601 if ($LJ::USER_VHOSTS
&& $opts->{'vhost'} eq "users" && $u->{'journaltype'} ne 'R' &&
9602 ! LJ
::get_cap
($u, "userdomain")) {
9603 return $notice->("URLs like <nobr><b>http://<i>username</i>.$LJ::USER_DOMAIN/" .
9604 "</b></nobr> are not available for this user's account type.");
9606 if ($opts->{'vhost'} eq "customview" && ! LJ
::get_cap
($u, "styles")) {
9607 return $notice->("This user's account type is not permitted to create and embed styles.");
9609 if ($opts->{'vhost'} eq "community" && $u->{'journaltype'} !~ /[CR]/) {
9610 $opts->{'badargs'} = 1; # Output a generic 'bad URL' message if available
9611 return "<h1>Notice</h1><p>This account isn't a community journal.</p>";
9613 if ($view eq "friendsfriends" && ! LJ
::get_cap
($u, "friendsfriendsview")) {
9615 if ($inline .= LJ
::run_hook
("cprod_inline", $u, 'FriendsFriendsInline')) {
9618 return BML
::ml
('cprod.friendsfriendsinline.text.v1');
9622 # signal to LiveJournal.pm that we can't handle this
9623 if (($stylesys == 1 || $geta->{'format'} eq 'light') &&
9624 ({ entry
=>1, reply
=>1, month
=>1, tag
=>1 }->{$view} || ($view eq 'lastn' && ($geta->{tag
} || $geta->{security
})))) {
9626 # pick which fallback method (s2 or bml) we'll use by default, as configured with
9628 my $fallback = $LJ::S1_SHORTCOMINGS ?
"s2" : "bml";
9630 # but if the user specifies which they want, override the fallback we picked
9631 if ($geta->{'fallback'} && $geta->{'fallback'} =~ /^s2|bml$/) {
9632 $fallback = $geta->{'fallback'};
9635 # if we are in this path, and they have style=mine set, it means
9636 # they either think they can get a S2 styled page but their account
9637 # type won't let them, or they really want this to fallback to bml
9638 if ($remote && ( $geta->{'style'} eq 'mine' ||
9639 $remote->opt_stylealwaysmine ) ) {
9643 # If they specified ?format=light, it means they want a page easy
9644 # to deal with text-only or on a mobile device. For now that means
9645 # render it in the lynx site scheme.
9646 if ($geta->{'format'} eq 'light') {
9648 LJ
::Request
->notes('bml_use_scheme' => 'lynx');
9651 # there are no BML handlers for these views, so force s2
9652 if ($view eq 'tag' || $view eq 'lastn') {
9656 # fall back to BML unless we're using S2
9657 # fallback (the "s1shortcomings/layout")
9658 if ($fallback eq "bml") {
9659 ${$opts->{'handle_with_bml_ref'}} = 1;
9663 # S1 can't handle these views, so we fall back to a
9664 # system-owned S2 style (magic value "s1short") that renders
9667 $styleid = "s1short";
9670 # now, if there's a GET argument for tags, split those out
9671 if (exists $opts->{getargs
}->{tag
}) {
9672 my $tagfilter = $opts->{getargs
}->{tag
};
9673 return $error->("You must provide tags to filter by.", "404 Not Found")
9677 return $error->("Sorry, the tag system is currently disabled.", "404 Not Found")
9678 if $LJ::DISABLED
{tags
};
9680 # throw an error if we're rendering in S1, but not for renamed accounts
9681 return $error->("Sorry, tag filtering is not supported within S1 styles.", "404 Not Found")
9682 if $stylesys == 1 && $view ne 'data' && $u->{journaltype
} ne 'R';
9684 # overwrite any tags that exist
9686 return $error->("Sorry, the tag list specified is invalid.", "404 Not Found")
9687 unless LJ
::Tags
::is_valid_tagstring
($tagfilter, $opts->{tags
}, { omit_underscore_check
=> 1 });
9689 # get user's tags so we know what remote can see, and setup an inverse mapping
9690 # from keyword to tag
9691 $opts->{tagids
} = [];
9692 $opts->{'tagmap'} = {};
9693 my $tags = LJ
::Tags
::get_usertags
($u, { remote
=> $remote });
9696 foreach my $tagid (keys %$tags) {
9697 push @
{$kwref{LJ
::Text
->normalize_tag_name ($tags->{$tagid}->{'name'})}}, $tagid;
9700 foreach my $tagname (@
{$opts->{tags
}}) {
9701 unless ($kwref{LJ
::Text
->normalize_tag_name ($tagname)}) {
9702 LJ
::Request
->pnotes ('error' => 'e404');
9703 LJ
::Request
->pnotes ('remote' => LJ
::get_remote
());
9704 $opts->{'handler_return'} = "404 Not Found";
9707 #return $error->("Sorry, one or more specified tags do not exist.", "404 Not Found")
9708 # unless $kwref{$tagname};
9709 push @
{$opts->{'tagids'}}, @
{$kwref{LJ
::Text
->normalize_tag_name ($tagname)}};
9710 $opts->{'tagmap'}->{$tagname} = $kwref{LJ
::Text
->normalize_tag_name ($tagname)};
9713 $opts->{tagmode
} = $opts->{getargs
}->{mode
} eq 'and' ?
'and' : 'or';
9716 # validate the security filter
9717 if (exists $opts->{getargs
}->{security
}) {
9718 my $securityfilter = $opts->{getargs
}->{security
};
9719 return $error->("You must provide a security level to filter by.", "404 Not Found")
9720 unless $securityfilter;
9722 return $error->("This feature is not available for your account level.", "403 Forbidden")
9723 unless LJ
::get_cap
($remote, "security_filter") || LJ
::get_cap
($u, "security_filter");
9726 return $error->("Sorry, the security-filtering system is currently disabled.", "404 Not Found")
9727 unless LJ
::is_enabled
("security_filter");
9729 # throw an error if we're rendering in S1, but not for renamed accounts
9730 return $error->("Sorry, security filtering is not supported within S1 styles.", "404 Not Found")
9731 if $stylesys == 1 && $view ne 'data' && !$u->is_redirect;
9733 # check the filter itself
9734 if ($securityfilter =~ /^(?:public|friends|private)$/i) {
9735 $opts->{'securityfilter'} = lc($securityfilter);
9737 } elsif ($securityfilter =~ /^group:(.+)$/i) {
9738 my $groupres = LJ
::get_friend_group
($u, { 'name' => $1});
9740 if ($groupres && (LJ
::u_equals
($u, $remote)
9741 || LJ
::get_groupmask
($u, $remote) & (1 << $groupres->{groupnum
}))) {
9742 $opts->{securityfilter
} = $groupres->{groupnum
};
9746 return $error->("You have specified an invalid security setting, the friends group you specified does not exist, or you are not a member of that group.", "404 Not Found")
9747 unless defined $opts->{securityfilter
};
9751 unless ($geta->{'viewall'} && LJ
::check_priv
($remote, "canview", "suspended") ||
9752 $opts->{'pathextra'} =~ m
#/(\d+)/stylesheet$#) { ## don't check style sheets
9753 if ($u->is_deleted){
9754 my $warning = LJ
::Lang
::get_text
(LJ
::Lang
::get_effective_lang
(),
9755 'journal.deleted', undef, {username
=> $u->username})
9756 || LJ
::Lang
::get_text
($LJ::DEFAULT_LANG
,
9757 'journal.deleted', undef, {username
=> $u->username});
9758 LJ
::Request
->pnotes ('error' => 'deleted');
9759 LJ
::Request
->pnotes ('remote' => LJ
::get_remote
());
9760 $opts->{'handler_return'} = "404 Not Found";
9762 #return $error->($warning, "404 Not Found");
9765 if ($u->is_suspended) {
9766 LJ
::Request
->pnotes ('error' => 'suspended');
9767 LJ
::Request
->pnotes ('remote' => LJ
::get_remote
());
9768 $opts->{'handler_return'} = "403 Forbidden";
9771 #return $error->("This journal has been suspended.", "403 Forbidden") if ($u->is_suspended);
9773 my $entry = $opts->{ljentry
};
9775 if ($entry && $entry->is_suspended_for($remote)) {
9776 LJ
::Request
->pnotes ('error' => 'suspended_post');
9777 LJ
::Request
->pnotes ('remote' => LJ
::get_remote
());
9778 $opts->{'handler_return'} = "403 Forbidden";
9782 return $error->("This entry has been suspended. You can visit the journal <a href='" . $u->journal_base . "/'>here</a>.", "403 Forbidden")
9783 if $entry && $entry->is_suspended_for($remote);
9785 if ($u->is_expunged) {
9786 LJ
::Request
->pnotes ('error' => 'expunged');
9787 LJ
::Request
->pnotes ('remote' => LJ
::get_remote
());
9788 $opts->{'handler_return'} = "410 Gone";
9792 return $error->("This user has no journal here.", "404 Not here") if $u->{'journaltype'} eq "I" && $view ne "friends";
9794 $opts->{'view'} = $view;
9796 # what charset we put in the HTML
9797 $opts->{'saycharset'} ||= "utf-8";
9799 if ($view eq 'data') {
9800 return LJ
::Feed
::make_feed
($u, $remote, $opts);
9803 if ($stylesys == 2) {
9804 LJ
::Request
->notes('codepath' => "s2.$view") if LJ
::Request
->is_inited;
9806 eval { LJ
::S2
->can("dostuff") }; # force Class::Autouse
9807 my $mj = LJ
::S2
::make_journal
($u, $styleid, $view, $remote, $opts);
9809 # intercept flag to handle_with_bml_ref and instead use S1 shortcomings
9810 # if BML is disabled
9811 if ($opts->{'handle_with_bml_ref'} && ${$opts->{'handle_with_bml_ref'}} &&
9812 ($LJ::S1_SHORTCOMINGS
|| $geta->{fallback
} eq "s2"))
9815 ${$opts->{'handle_with_bml_ref'}} = 0;
9817 # and proceed with s1shortcomings (which looks like BML) instead of BML
9818 $mj = LJ
::S2
::make_journal
($u, "s1short", $view, $remote, $opts);
9824 # Everything from here on down is S1. FIXME: this should be moved to LJ::S1::make_journal
9825 # to be more like LJ::S2::make_journal.
9826 LJ
::Request
->notes('codepath' => "s1.$view") if LJ
::Request
->is_inited;
9827 $u->{'_s1styleid'} = $styleid + 0;
9829 # For embedded polls
9830 BML
::set_language
($LJ::LANGS
[0] || 'en', \
&LJ
::Lang
::get_text
);
9832 # load the user-related S1 data (overrides and colors)
9834 my $is_s1uc_valid = sub {
9835 ## Storable::thaw takes valid date, undef or empty string;
9836 ## dies on invalid data
9839 Storable
::thaw
($_[0]->{'color_stor'});
9840 Storable
::thaw
($_[0]->{'override_stor'});
9844 my $s1uc_memkey = [$u->{'userid'}, "s1uc:$u->{'userid'}"];
9845 if ($u->{'useoverrides'} eq "Y" || $u->{'themeid'} == 0) {
9846 $s1uc = LJ
::MemCache
::get
($s1uc_memkey);
9847 undef($s1uc) if $s1uc && !$is_s1uc_valid->($s1uc);
9852 if (@LJ::MEMCACHE_SERVERS
) {
9853 $db = LJ
::get_cluster_def_reader
($u);
9855 $db = LJ
::get_cluster_reader
($u);
9858 $s1uc = $db->selectrow_hashref("SELECT * FROM s1usercache WHERE userid=?",
9859 undef, $u->{'userid'});
9860 undef($s1uc) if $s1uc && !$is_s1uc_valid->($s1uc);
9861 LJ
::MemCache
::set
($s1uc_memkey, $s1uc) if $s1uc && $setmem;
9865 # we should have our cache row! we'll update it in a second.
9868 $u->do("INSERT IGNORE INTO s1usercache (userid) VALUES (?)", undef, $u->{'userid'});
9872 # conditionally rebuild parts of our cache that are missing
9875 # is the overrides cache old or missing?
9877 if ($u->{'useoverrides'} eq "Y" && (! $s1uc->{'override_stor'} ||
9878 $s1uc->{'override_cleanver'} < $LJ::S1
::CLEANER_VERSION
)) {
9880 my $overrides = LJ
::S1
::get_overrides
($u);
9881 $update{'override_stor'} = LJ
::CleanHTML
::clean_s1_style
($overrides);
9882 $update{'override_cleanver'} = $LJ::S1
::CLEANER_VERSION
;
9885 # is the color cache here if it's a custom user theme?
9886 if ($u->{'themeid'} == 0 && ! $s1uc->{'color_stor'}) {
9888 $dbh ||= LJ
::get_db_writer
();
9889 my $sth = $dbh->prepare("SELECT coltype, color FROM themecustom WHERE user=?");
9890 $sth->execute($u->{'user'});
9891 $col->{$_->{'coltype'}} = $_->{'color'} while $_ = $sth->fetchrow_hashref;
9892 $update{'color_stor'} = Storable
::nfreeze
($col);
9898 foreach my $k (keys %update) {
9899 $s1uc->{$k} = $update{$k};
9900 $set .= ", " if $set;
9901 $set .= "$k=" . $u->quote($update{$k});
9903 my $rv = $u->do("UPDATE s1usercache SET $set WHERE userid=?", undef, $u->{'userid'});
9904 LJ
::MemCache
::set
($s1uc_memkey, $s1uc);
9908 my $viewref = $view eq "" ? \
$view : undef;
9909 $style ||= $LJ::viewinfo
{$view}->{'nostyle'} ?
{} :
9910 LJ
::S1
::load_style
($styleid, $viewref);
9915 foreach (keys %$style) {
9916 $vars{$_} = $style->{$_};
9919 # apply the overrides
9920 if ($opts->{'nooverride'}==0 && $u->{'useoverrides'} eq "Y") {
9921 my $tw = Storable
::thaw
($s1uc->{'override_stor'});
9922 foreach (keys %$tw) {
9923 $vars{$_} = $tw->{$_};
9927 # apply the color theme
9929 my $cols = $u->{'themeid'}
9930 ? LJ
::S1
::get_themeid
($u->{'themeid'})
9931 : Storable
::thaw
($s1uc->{'color_stor'});
9932 foreach (keys %$cols) {
9933 $vars{"color-$_"} = $cols->{$_};
9936 # instruct some function to make this specific view type
9937 return unless defined $LJ::viewinfo
{$view}->{'creator'};
9940 # call the view creator w/ the buffer to fill and the construction variables
9941 my $res = $LJ::viewinfo
{$view}->{'creator'}->(\
$ret, $u, \
%vars, $remote, $opts);
9943 if ($LJ::USE_S1w2
&& $LJ::USE_S1w2
->($view, $u, $remote)) {
9944 # S1w2 is an experimental version of S1 that acts as if it were an S2 style,
9945 # getting all of its necessary data from the S2 data structures rather than
9946 # fetching the data itself and duplicating all of that logic.
9947 # It should ideally generate exactly the same output as traditional S1 with
9948 # the same input data, but until this has been tested thoroughly it's
9949 # disabled by default.
9951 # We render S1w2 in addition to traditional S1 so that we can see if there
9952 # is any difference.
9953 my $s1result = $ret;
9956 require "ljviews-s1-using-s2.pl"; # Load on demand
9957 $LJ::S1w2
::viewcreator
{$view}->(\
$ret, $u, \
%vars, $remote, $opts);
9959 if ($s1result ne $ret) {
9960 warn "S1w2 differed from S1 when rendering a $view page for $u->{user} with ".($remote ?
$remote->{user
} : "an anonymous user")." watching";
9962 # Optionally produce a diff between S1 and S1w2
9963 # NOTE: This _make_diff function hits the filesystem and forks a diff process.
9964 # It's only useful/sensible on a low-load development server.
9965 if ($LJ::SHOW_S1w2_DIFFS
) {
9966 $ret .= "<plaintext>".LJ
::S1w2
::_make_diff
($s1result, $ret);
9973 my $errcode = $opts->{'errcode'};
9975 'nodb' => 'Database temporarily unavailable during maintenance.',
9976 'nosyn' => 'No syndication URL available.',
9978 return "<!-- $errmsg -->" if ($opts->{'vhost'} eq "customview");
9980 # If not customview, set the error response code.
9981 $opts->{'status'} = {
9982 'nodb' => '503 Maintenance',
9983 'nosyn' => '404 Not Found',
9984 }->{$errcode} || '500 Server Error';
9988 if ($opts->{'redir'}) {
9992 # clean up attributes which we weren't able to quickly verify
9993 # as safe in the Storable-stored clean copy of the style.
9994 $ret =~ s/\%\%\[attr\[(.+?)\]\]\%\%/LJ::CleanHTML::s1_attribute_clean($1)/eg;
10001 # name: LJ::canonical_username
10002 # des: normalizes username.
10005 # returns: the canonical username given, or blank if the username is not well-formed
10007 sub canonical_username
10010 if ($user =~ /^\s*([A-Za-z0-9_\-]{1,15})\s*$/) {
10011 # perl 5.8 bug: $user = lc($1) sometimes causes corruption when $1 points into $user.
10017 return ""; # not a good username.
10021 # name: LJ::get_userid
10022 # des: Returns a userid given a username.
10023 # info: Results cached in memory. On miss, does DB call. Not advised
10024 # to use this many times in a row... only once or twice perhaps
10025 # per request. Tons of serialized db requests, even when small,
10026 # are slow. Opposite of [func[LJ::get_username]].
10028 # des-user: Username whose userid to look up.
10029 # returns: Userid, or 0 if invalid user.
10034 $user = LJ
::canonical_username
($user);
10036 if (exists $LJ::PRELOADED_USER_IDS
{$user} && !$LJ::IS_DEV_SERVER
) { return $LJ::PRELOADED_USER_IDS
{$user}; }
10037 if ($LJ::CACHE_USERID
{$user}) { return $LJ::CACHE_USERID
{$user}; }
10039 my $userid = LJ
::MemCacheProxy
::get
("uidof:$user");
10040 return $LJ::CACHE_USERID
{$user} = $userid if $userid;
10042 my $dbr = LJ
::get_db_reader
();
10043 $userid = $dbr->selectrow_array("SELECT userid FROM useridmap WHERE user=?", undef, $user);
10045 # implicitly create an account if we're using an external
10047 if (! $userid && ref $LJ::AUTH_EXISTS
eq "CODE")
10049 $userid = LJ
::create_account
({ 'user' => $user,
10051 'password' => '', });
10055 $LJ::CACHE_USERID
{$user} = $userid;
10056 LJ
::MemCacheProxy
::set
("uidof:$user", $userid);
10059 return ($userid+0);
10062 # TODO: Rewrite that function in more optimal way!
10063 sub get_userid_multi
{
10067 for my $user ( @
$users ) {
10068 my $userid = LJ
::get_userid
( $user );
10069 push @res, $userid if $userid;
10076 # name: LJ::want_user
10077 # des: Returns user object when passed either userid or user object. Useful to functions that
10078 # want to accept either.
10080 # des-user: Either a userid or a user hash with the userid in its 'userid' key.
10081 # returns: The user object represented by said userid or username.
10086 return undef unless $uuid;
10087 return $uuid if ref $uuid;
10088 return LJ
::load_userid
($uuid) if $uuid =~ /^\d+$/;
10089 Carp
::croak
("Bogus caller of LJ::want_user with non-ref/non-numeric parameter: $uuid");
10093 # name: LJ::get_username
10094 # des: Returns a username given a userid.
10095 # info: Results cached in memory. On miss, does DB call. Not advised
10096 # to use this many times in a row... only once or twice perhaps
10097 # per request. Tons of serialized db requests, even when small,
10098 # are slow. Opposite of [func[LJ::get_userid]].
10100 # des-user: Username whose userid to look up.
10101 # returns: Userid, or 0 if invalid user.
10104 my $userid = shift;
10107 # Checked the cache first.
10108 if ($LJ::CACHE_USERNAME
{$userid}) { return $LJ::CACHE_USERNAME
{$userid}; }
10110 # if we're using memcache, it's faster to just query memcache for
10111 # an entire $u object and just return the username. otherwise, we'll
10112 # go ahead and query useridmap
10113 if (@LJ::MEMCACHE_SERVERS
) {
10114 my $u = LJ
::load_userid
($userid);
10115 return undef unless $u;
10117 $LJ::CACHE_USERNAME
{$userid} = $u->{'user'};
10118 return $u->{'user'};
10121 my $dbr = LJ
::get_db_reader
();
10122 my $user = $dbr->selectrow_array("SELECT user FROM useridmap WHERE userid=?", undef, $userid);
10124 # Fall back to master if it doesn't exist.
10125 unless (defined $user) {
10126 my $dbh = LJ
::get_db_writer
();
10127 $user = $dbh->selectrow_array("SELECT user FROM useridmap WHERE userid=?", undef, $userid);
10130 return undef unless defined $user;
10132 $LJ::CACHE_USERNAME
{$userid} = $user;
10137 # name: LJ::can_manage_other
10138 # des: Given a user and a target user, will determine if the first user is an
10139 # admin for the target user, but not if the two are the same.
10141 # des-remote: user object or userid of user to try and authenticate
10142 # des-u: user object or userid of target user
10143 # returns: bool: true if authorized, otherwise fail
10145 sub can_manage_other
{
10146 my ($remote, $u) = @_;
10147 return 0 if LJ
::want_userid
($remote) == LJ
::want_userid
($u);
10148 $remote = LJ
::want_user
($remote);
10149 return $remote && $remote->can_manage($u);
10152 sub can_delete_journal_item
{
10153 my ($remote, $u, $itemid) = @_;
10154 $remote = LJ
::want_user
($remote);
10156 return 0 unless $remote;
10158 return 0 unless $remote->can_manage($u);
10159 # here admin or supermaintainer
10161 return 0 if $LJ::JOURNALS_WITH_PROTECTED_CONTENT
{ $u->{user
} } and !LJ
::is_friend
($u, $remote);
10168 # name: LJ::get_remote
10169 # des: authenticates the user at the remote end based on their cookies
10170 # and returns a hashref representing them.
10172 # des-opts: 'criterr': scalar ref to set critical error flag. if set, caller
10173 # should stop processing whatever it's doing and complain
10174 # about an invalid login with a link to the logout page.
10175 # 'ignore_ip': ignore IP address of remote for IP-bound sessions
10176 # returns: hashref containing 'user' and 'userid' if valid user, else
10180 my $opts = ref $_[0] eq "HASH" ?
shift : {};
10182 return $LJ::CACHE_REMOTE
if $LJ::CACHED_REMOTE
&& ! $opts->{'ignore_ip'};
10184 my $no_remote = sub {
10185 LJ
::User
->set_remote(undef);
10189 # can't have a remote user outside of web context
10190 return $no_remote->() unless LJ
::Request
->is_inited;
10192 my $get_as = LJ
::Request
->get_param('as');
10193 if ( $LJ::IS_DEV_SERVER
&& $get_as =~ /^\w{1,15}$/ ) {
10194 my $ru = LJ
::load_user
($get_as);
10196 # might be undef, to allow for "view as logged out":
10197 LJ
::set_remote
($ru);
10201 my $criterr = $opts->{criterr
} || do { my $d; \
$d; };
10204 $LJ::CACHE_REMOTE_BOUNCE_URL
= "";
10206 # set this flag if any of their ljsession cookies contained the ".FS"
10207 # opt to use the fast server. if we later find they're not logged
10208 # in and set it, or set it with a free account, then we give them
10209 # the invalid cookies error.
10210 my $tried_fast = 0;
10211 my $sessobj = LJ
::Session
->session_from_cookies(
10212 tried_fast
=> \
$tried_fast,
10213 redirect_ref
=> \
$LJ::CACHE_REMOTE_BOUNCE_URL
,
10214 ignore_ip
=> $opts->{ignore_ip
},
10217 my $u = $sessobj ?
$sessobj->owner : undef;
10219 # inform the caller that this user is faking their fast-server cookie
10221 if ($tried_fast && ! LJ
::get_cap
($u, "fastserver")) {
10225 return $no_remote->() unless $sessobj;
10227 # renew soon-to-expire sessions
10228 $sessobj->try_renew;
10230 # augment hash with session data;
10231 $u->{'_session'} = $sessobj;
10233 # keep track of activity for the user we just loaded from db/memcache
10234 # - if necessary, this code will actually run in Apache's cleanup handler
10235 # so latency won't affect the user
10236 if (@LJ::MEMCACHE_SERVERS
&& ! $LJ::DISABLED
{active_user_tracking
}) {
10237 push @LJ::CLEANUP_HANDLERS
, sub { $u->note_activity('A') };
10240 LJ
::User
->set_remote($u);
10241 LJ
::Request
->notes("ljuser" => $u->{'user'});
10245 # returns either $remote or the authenticated user that $remote is working with
10246 sub get_effective_remote
{
10247 my $authas_arg = shift || "authas";
10249 return undef unless LJ
::is_web_context
();
10251 my $remote = LJ
::get_remote
();
10252 return undef unless $remote;
10254 my $authas = $BMLCodeBlock::GET
{authas
} || $BMLCodeBlock::POST
{authas
} || $remote->user;
10255 return $remote if $authas eq $remote->user;
10257 return LJ
::get_authas_user
($authas);
10260 # returns URL we have to bounce the remote user to in order to
10261 # get their domain cookie
10262 sub remote_bounce_url
{
10263 return $LJ::CACHE_REMOTE_BOUNCE_URL
;
10267 my $remote = shift;
10268 LJ
::User
->set_remote($remote);
10274 LJ
::User
->unset_remote;
10278 sub get_active_journal
10280 return $LJ::ACTIVE_JOURNAL
;
10283 sub set_active_journal
10285 $LJ::ACTIVE_JOURNAL
= shift;
10288 # Checks if they are flagged as having a bad password and redirects
10289 # to changepassword.bml. If returl is on it returns the URL to
10290 # redirect to vs doing the redirect itself. Useful in non-BML context
10291 # and for QuickReply links
10292 sub bad_password_redirect
{
10295 my $remote = LJ
::get_remote
();
10296 return undef unless $remote;
10298 return undef if $LJ::DISABLED
{'force_pass_change'};
10300 return undef unless $remote->prop('badpassword');
10302 my $redir = "$LJ::SITEROOT/changepassword.bml";
10303 unless (defined $opts->{'returl'}) {
10304 return BML
::redirect
($redir);
10310 # Returns HTML to display user search results
10313 # users => hash ref of userid => u object like LJ::load userids
10314 # returns or array ref of user objects
10315 # userids => array ref of userids to include in results, ignored
10316 # if users is defined
10317 # timesort => set to 1 to sort by last updated instead
10319 # perpage => Enable pagination and how many users to display on
10321 # curpage => What page of results to display
10322 # navbar => Scalar reference for paging bar
10323 # pickwd => userpic keyword to display instead of default if it
10324 # exists for the user
10325 # self_link => Sub ref to generate link to use for pagination
10326 sub user_search_display
{
10330 unless (defined $args{users
}) {
10331 $loaded_users = LJ
::load_userids
(@
{$args{userids
}});
10333 if (ref $args{users
} eq 'HASH') { # Assume this is direct from LJ::load_userids
10334 $loaded_users = $args{users
};
10335 } elsif (ref $args{users
} eq 'ARRAY') { # They did a grep on it or something
10336 foreach (@
{$args{users
}}) {
10337 $loaded_users->{$_->{userid
}} = $_;
10344 # If we're sorting by last updated, we need to load that
10345 # info for all users before the sort. If sorting by
10346 # username we can load it for a subset of users later,
10351 if ($args{timesort
}) {
10352 $updated = LJ
::get_timeupdate_multi
(keys %$loaded_users);
10353 @display = sort { $updated->{$b->{userid
}} <=> $updated->{$a->{userid
}} } values %$loaded_users;
10355 @display = sort { $a->{user
} cmp $b->{user
} } values %$loaded_users;
10358 if (defined $args{perpage
}) {
10359 my %items = BML
::paging
(\
@display, $args{curpage
}, $args{perpage
});
10363 $opts->{self_link
} = $args{self_link
} if $args{self_link
};
10364 ${$args{navbar
}} = LJ
::paging_bar
($items{'page'}, $items{'pages'}, $opts);
10366 # Now pull out the set of users to display
10367 @display = @
{$items{'items'}};
10370 # If we aren't sorting by time updated, load last updated time for the
10371 # set of users we are displaying.
10372 $updated = LJ
::get_timeupdate_multi
(map { $_->{userid
} } @display)
10373 unless $args{timesort
};
10375 # Allow caller to specify a custom userpic to use instead
10376 # of the user's default all userpics
10377 my $get_picid = sub {
10379 return $u->{'defaultpicid'} unless $args{'pickwd'};
10380 return LJ
::get_picid_from_keyword
($u, $args{'pickwd'});
10384 foreach my $u (@display) {
10385 # We should always have loaded user objects, but it seems
10386 # when the site is overloaded we don't always load the users
10388 next unless LJ
::isu
($u);
10390 $ret .= "<div style='width: 300px; height: 105px; overflow: hidden; float: left; ";
10391 $ret .= "border-bottom: 1px solid <?altcolor2?>; margin-bottom: 10px; padding-bottom: 5px; margin-right: 10px'>";
10392 $ret .= "<table style='height: 105px'><tr>";
10394 $ret .= "<td style='width: 100px; text-align: center;'>";
10395 $ret .= "<a href='/allpics.bml?user=$u->{user}'>";
10396 if (my $picid = $get_picid->($u)) {
10397 $ret .= "<img src='$LJ::USERPIC_ROOT/$picid/$u->{userid}' alt='$u->{user} userpic' style='border: 1px solid #000;' />";
10399 $ret .= "<img src='$LJ::STATPREFIX/horizon/nouserpic.png?v=2621' alt='no default userpic' style='border: 1px solid #000;' width='100' height='100' />";
10403 $ret .= "</td><td style='padding-left: 5px;' valign='top'><table>";
10405 $ret .= "<tr><td class='searchusername' colspan='2' style='text-align: left;'>";
10406 $ret .= $u->ljuser_display({ head_size
=> $args{head_size
} });
10407 $ret .= "</td></tr><tr>";
10410 $ret .= "<td width='1%' style='font-size: smaller' valign='top'>Name:</td><td style='font-size: smaller'><a href='" . $u->profile_url . "'>";
10411 $ret .= LJ
::ehtml
($u->{name
});
10413 $ret .= "</td></tr><tr>";
10416 if (my $jtitle = $u->prop('journaltitle')) {
10417 $ret .= "<td width='1%' style='font-size: smaller' valign='top'>Journal:</td><td style='font-size: smaller'><a href='" . $u->journal_base . "'>";
10418 $ret .= LJ
::ehtml
($jtitle) . "</a>";
10419 $ret .= "</td></tr>";
10422 $ret .= "<tr><td colspan='2' style='text-align: left; font-size: smaller' class='lastupdated'>";
10424 if ($updated->{$u->{'userid'}} > 0) {
10425 $ret .= "Updated ";
10426 $ret .= LJ
::TimeUtil
->ago_text(time() - $updated->{$u->{'userid'}});
10428 $ret .= "Never updated";
10431 $ret .= "</td></tr>";
10433 $ret .= "</table>";
10434 $ret .= "</td></tr>";
10435 $ret .= "</table></div>";
10441 # returns the country that the remote IP address comes from
10442 # undef is returned if the country cannot be determined from the IP
10443 sub country_of_remote_ip
{
10444 my $ip = LJ
::get_remote_ip
();
10445 return undef unless $ip;
10447 if (LJ
::GeoLocation
->can('get_country_info_by_ip')) {
10448 ## use module LJ::GeoLocation if it's installed
10449 return LJ
::GeoLocation
->get_country_info_by_ip($ip)
10450 } elsif (eval "use IP::Country::Fast; 1;") {
10451 my $reg = IP
::Country
::Fast
->new();
10452 my $country = $reg->inet_atocc($ip);
10454 # "**" is returned if the IP is private
10455 return undef if $country eq "**";
10462 sub get_aggregated_user
{
10463 my ($row, $opts) = @_;
10465 my $user = eval { LJ
::load_userid
($row->{userid
}) };
10467 return unless $user;
10469 return unless $opts->{attrs
} && ref $opts->{attrs
};
10471 my @identity_methods;
10473 foreach my $method (@
{$opts->{attrs
}}) {
10474 if($method =~ /^identity_(.+)/) {
10475 push @identity_methods, $1;
10479 my @result = eval {$user->$method};
10480 ($row->{$method}) = @result > 1 ? \
@result : @result;
10483 return unless (@identity_methods && $user->is_identity);
10485 my $i = $user->identity;
10487 foreach my $method (@identity_methods) {
10488 my @result = eval {$i->$method};
10489 ($row->{'identity_'.$method}) = @result > 1 ? \
@result : @result;
10493 # Return friends with type
10501 sub get_friends_with_type
{
10502 my ($u, $options) = @_;
10504 my $types = $options->{types
};
10505 my $limit = $options->{limit
};
10507 die "no user" unless $u;
10508 die "no type" unless $types;
10510 my %allow_list = map { $_ => 1 } @
$types;
10513 # Exclude some friends types to type P.
10515 if ($allow_list{'P'}) {
10516 my %types_data = map { $_ => 1 } @
$types;
10518 my @types_list = ('I', 'Y', 'N', 'C');
10519 my @types_to_load = ();
10522 # May do not need to exclude all friends
10524 foreach my $type (@types_list) {
10525 push @types_to_load, $type
10526 unless $types_data{$type};
10529 my @exclude = get_friends_with_type
($u, { types
=> \
@types_to_load,
10530 limit
=> $limit });
10532 my %exclude_list = map { $_ => 1 } @exclude;
10533 my @friends = $u->friend_uids(limit
=> $limit);
10534 my @list = grep { !$exclude_list{$_} } @friends;
10539 #mnenonic User:FriendsList:
10540 my @keys = map { "u:fl:" . $u->userid . ":$_"} @
$types ;
10542 my $redis = LJ
::Redis
->get_connection();
10545 foreach my $key (@keys) {
10546 my @result = $redis->smembers($key);
10547 push @list, @result if @result;
10550 return @list if @list;
10553 # get and set a list
10555 my @friends = $u->friend_uids();
10556 my $friends_data = LJ
::get_journal_short_info_multi
(@friends);
10558 my @typed_journals = ();
10559 my %put_in_cache = ();
10560 foreach my $friend (@friends) {
10561 my $friend_info = $friends_data->{$friend};
10562 next if $friend_info->{statusvis
} eq 'X' ||
10563 $friend_info->{clusterid
} == 0;
10565 my $type = $friend_info->{journaltype
};
10566 next unless $allow_list{$type};
10568 push @
{$put_in_cache{$type}}, $friend if $redis;
10569 push @typed_journals, $friend;
10573 foreach my $type (keys %put_in_cache) {
10574 my $key = "u:fl:" . $u->userid . ":$type";
10575 $redis->sadd($key, @
{$put_in_cache{$type}});
10576 $redis->expire($key, 60 * 60);
10580 return @typed_journals;
10583 sub remove_from_friend_list
{
10584 my ($u, $friend) = @_;
10586 my $type = $friend->journaltype;
10587 my $key = "u:fl:" . $u->userid . ":$type";
10588 my $redis = LJ
::Redis
->get_connection();
10590 $redis->srem($key, $friend);
10594 sub add_to_friend_list
{
10595 my ($u, $friend) = @_;
10597 my $type = $friend->journaltype;
10598 my $key = "u:fl:" . $u->userid . ":$type";
10599 my $redis = LJ
::Redis
->get_connection();
10601 if ($redis && $redis->exists($key)) {
10602 $redis->sadd($key, $friend);
10606 sub get_journal_short_info_multi
{
10610 foreach my $userid (@userids) {
10611 push @keys, "u:s:$userid";
10614 my %final_result = ();
10616 my $result = LJ
::MemCache
::get_multi
(@keys);
10618 my @users_to_load = ();
10619 foreach my $userid (@userids) {
10620 my $data = delete $result->{"u:s:$userid"};
10622 push @users_to_load, $userid;
10624 my %user_result = ();
10626 my ($status, $cid, $type) = split(/:/, $data);
10627 $user_result{statusvis
} = $status;
10628 $user_result{clusterid
} = $cid;
10629 $user_result{journaltype
} = $type;
10631 $final_result{$userid} = \
%user_result;
10635 my $users = LJ
::load_userids
(@users_to_load);
10637 foreach my $userid (@users_to_load) {
10638 my $user = $users->{$userid};
10641 my $status = $user->{statusvis
};
10642 my $cid = $user->{clusterid
};
10643 my $type = $user->{journaltype
};
10645 my %user_result = ();
10647 $user_result{statusvis
} = $status;
10648 $user_result{clusterid
} = $cid;
10649 $user_result{journaltype
} = $type;
10651 $final_result{$userid} = \
%user_result;
10653 my $cache = join(':', $status, $cid, $type);
10654 my $expire_time = time + 60*60*24*30;
10655 LJ
::MemCache
::set
("u:s:$userid", $cache, $expire_time);
10659 return \
%final_result;