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;
3903 return $lastpublic if $opts{from_redis
};
3907 my $is_person = $u->is_person;
3910 'userid' => $u->userid,
3911 'clusterid' => $u->clusterid,
3915 'security' => "public",
3916 'load_props' => $is_person ?
1 : 0,
3922 my ($skip, $itemshow) = (0, 10);
3924 until ( $lastpublic ) {
3927 $req->{'entry_objects'} = \
@entries;
3929 $req->{'skip'} = $skip;
3930 $req->{'itemshow'} = $is_person ?
$itemshow : 1;
3932 LJ
::get_recent_items
($req);
3935 warn "Error loading recent_entries: $err";
3940 last unless @entries;
3942 foreach my $entry (@entries) {
3943 unless( $is_person && $entry->prop('repost') && $entry->prop('repost') eq 'e' ) {
3944 $lastpublic = LJ
::TimeUtil
->mysqldate_to_time($entry->{logtime
}, 0);
3952 if (defined $lastpublic) {
3953 $u->{$attr} = $lastpublic;
3954 $redis->set($key, $lastpublic) if $redis;
3961 sub get_last_public_entry_time_multi
{
3962 my ($class, $uids) = @_;
3964 return unless $uids && @
$uids;
3966 my $redis = LJ
::Redis
->get_connection || return;
3968 my @keys = map {"lpt.$_"} @
$uids;
3970 my @res = $redis->mget(@keys);
3972 my $res = { map { $uids->[$_] => $res[$_] } (0..$#res) };
3977 # set the last public entry time
3978 # do it only if key already exists, i.e. if somebody has already request it
3979 sub set_last_public_entry_time
{
3980 my ($u, $lastpublic) = @_;
3982 my $key = "lpt.".$u->id;
3983 my $attr = '_cache_last_public_time';
3985 $u->{$attr} = $lastpublic;
3987 my $redis = LJ
::Redis
->get_connection || return;
3989 return unless $redis->exists($key);
3990 $redis->set($key, $lastpublic);
3994 # delete last public entry time
3995 sub del_last_public_entry_time
{
3998 my $key = "lpt.".$u->id;
3999 my $attr = '_cache_last_public_time';
4003 my $redis = LJ
::Redis
->get_connection || return;
4007 # can this user use ESN?
4010 return 0 if $LJ::DISABLED
{esn
};
4011 my $disable = $LJ::DISABLED
{esn_ui
};
4012 return 1 unless $disable;
4014 if (ref $disable eq 'CODE') {
4015 return $disable->($u) ?
0 : 1;
4018 return $disable ?
0 : 1;
4023 return LJ
::SMS
->can_use_sms($u);
4026 sub can_use_ljphoto
{
4029 return $u->is_personal ?
1 : 0;
4032 sub can_upload_photo
{
4035 return 0 unless $u->can_use_ljphoto();
4036 return $u->get_cap('disk_quota') ?
1 : 0;
4039 sub ajax_auth_token
{
4041 return LJ
::Auth
->ajax_auth_token($u, @_);
4044 sub check_ajax_auth_token
{
4046 return LJ
::Auth
->check_ajax_auth_token($u, @_);
4058 return "I,$u->{userid}" if $u->{journaltype
} eq "I";
4062 # returns username for display
4063 sub display_username
{
4065 my $need_cut = shift || 0;
4067 my $username = $u->{user
};
4068 if ($u->is_identity){
4069 $username = $u->display_name;
4071 my $short_name = substr ($username, 0, 16);
4072 if ($username eq $short_name) {
4073 $username = $short_name;
4075 $username = $short_name . "...";
4080 return LJ
::ehtml
($username);
4083 # returns the user-specified name of a journal exactly as entered
4089 # returns the user-specified name of a journal in valid UTF-8
4092 LJ
::text_out
(\
$u->{name
});
4096 # returns the user-specified name of a journal in valid UTF-8
4097 # and with HTML escaped
4100 return LJ
::ehtml
($u->name_raw);
4107 return int($u->{userid
});
4112 return $u->{clusterid
};
4115 # class method, returns { clusterid => [ uid, uid ], ... }
4116 sub split_by_cluster
{
4120 my $us = LJ
::load_userids
(@uids);
4123 foreach my $u (values %$us) {
4125 push @
{$clusters{$u->clusterid}}, $u->id;
4131 ## Returns current userhead for user.
4134 my $opts = +shift || {};
4136 my $userhead_override;
4137 LJ
::run_hooks
( 'override_userhead', $u, \
$userhead_override );
4138 return ( $userhead_override, 16, 16 ) if $userhead_override;
4140 my $head_size = $opts->{head_size
};
4142 my $userhead = 'userinfo.gif?v=17080';
4143 my $userhead_w = 16;
4144 my $userhead_h = undef;
4147 my ($icon, $size) = LJ
::run_hook
("head_icon",
4148 $u, head_size
=> $head_size);
4152 $userhead_w = $size || 16;
4153 $userhead_h = $userhead_w;
4154 return $userhead, $userhead_w, $userhead_h;
4158 if (!$LJ::IS_SSL
&& ($icon = $u->custom_usericon)) {
4161 } elsif ($u->is_community) {
4163 $userhead = "comm_${head_size}.gif";
4164 $userhead_w = $head_size;
4166 $userhead = "community.gif?v=556";
4169 } elsif ($u->is_syndicated) {
4171 $userhead = "syn_${head_size}.gif";
4172 $userhead_w = $head_size;
4174 $userhead = "syndicated.gif?v=6283";
4177 } elsif ($u->is_news) {
4179 $userhead = "news_${head_size}.gif";
4180 $userhead_w = $head_size;
4182 $userhead = "newsinfo.gif?v=2990";
4185 } elsif ($u->is_identity) {
4186 my $ident = $u->identity;
4187 my $params = $ident ?
$ident->ljuser_display_params($u, $opts) : {};
4188 $userhead = $params->{'userhead'} || $userhead;
4189 $userhead_w = $params->{'userhead_w'} || $userhead_w;
4190 $userhead_h = $params->{'userhead_h'} || $userhead_h;
4193 $userhead = "user_${head_size}.gif";
4194 $userhead_w = $head_size;
4196 $userhead = "userinfo.gif?v=17080";
4200 $userhead_h ||= $userhead_w;
4201 return $userhead, $userhead_w, $userhead_h;
4206 my ($userhead) = $u->userhead;
4207 return undef unless $userhead;
4208 return $userhead if $userhead =~ m
|^https?
://|;
4209 return join '', $LJ::IMGPREFIX
, '/', $userhead, '?v=', $LJ::CURRENT_VERSION
;
4214 return LJ
::get_bio
($u);
4217 # if bio_absent is set to "yes", bio won't be updated
4219 my ($u, $text, $bio_absent) = @_;
4220 $bio_absent = "" unless $bio_absent;
4222 my $oldbio = $u->bio;
4223 my $newbio = $bio_absent eq "yes" ?
$oldbio : $text;
4224 my $has_bio = ($newbio =~ /\S/) ?
"Y" : "N";
4227 'has_bio' => $has_bio,
4229 LJ
::update_user
($u, \
%update);
4231 # update their bio text
4232 if (($oldbio ne $text) && $bio_absent ne "yes") {
4233 if ($has_bio eq "N") {
4234 $u->do("DELETE FROM userbio WHERE userid=?", undef, $u->id);
4235 $u->dudata_set('B', 0, 0);
4237 $u->do("REPLACE INTO userbio (userid, bio) VALUES (?, ?)",
4238 undef, $u->id, $text);
4239 $u->dudata_set('B', 0, length($text));
4241 LJ
::MemCache
::set
([$u->id, "bio:" . $u->id], $text);
4248 # if unset, default to on
4249 my $prop = $u->raw_prop('opt_ctxpopup') || 'Y';
4251 return $prop eq 'Y';
4254 # opt_imagelinks format:
4255 # 0|1 - replace images with placeholders at friends page
4257 # 0|1 - replace images with placeholders in comments at entry page
4258 sub get_opt_imagelinks
{
4260 my $opt = $u->prop("opt_imagelinks") || "0:0";
4261 $opt = "0:0" unless $opt;
4262 $opt = "1:0" unless $opt =~ /^\d\:\d$/;
4266 sub opt_placeholders_comments
{
4268 my $opt = $u->get_opt_imagelinks;
4270 if ( $opt =~ /^\d\:(\d)$/ ) {
4277 sub get_opt_videolinks
{
4279 my $opt = $u->raw_prop("opt_embedplaceholders") || "0:0";
4280 $opt = "0:0" if ! $opt || $opt eq 'N';
4281 $opt = "1:0" unless $opt =~ /^\d\:\d$/;
4285 sub opt_embedplaceholders
{
4287 my $opt = $u->get_opt_videolinks;
4289 if ( $opt =~ /^(\d)\:\d$/ ) {
4296 sub opt_videoplaceholders_comments
{
4298 my $opt = $u->get_opt_videolinks;
4300 if ( $opt =~ /^\d\:(\d)$/ ) {
4307 sub opt_getting_started
{
4310 # if unset, default to on
4311 my $prop = $u->raw_prop('opt_getting_started') || 'Y';
4316 sub opt_stylealwaysmine
{
4319 return 0 unless $u->can_use_stylealwaysmine;
4320 return $u->raw_prop('opt_stylealwaysmine') eq 'Y' ?
1 : 0;
4323 sub can_use_stylealwaysmine
{
4327 return 0 if $LJ::DISABLED
{stylealwaysmine
};
4328 $ret = LJ
::run_hook
("can_use_stylealwaysmine", $u);
4332 sub opt_commentsstylemine
{
4335 return 0 unless $u->can_use_commentsstylemine;
4337 if ( $u->raw_prop('opt_stylemine') ) {
4338 $u->set_prop( opt_stylemine
=> 0 );
4339 $u->set_prop( opt_commentsstylemine
=> 'Y' );
4342 return $u->raw_prop('opt_commentsstylemine') eq 'Y'?
1 : 0;
4345 sub can_use_commentsstylemine
{
4346 return 0 unless LJ
::is_enabled
('comments_style_mine');
4350 sub has_enabled_getting_started
{
4353 return $u->opt_getting_started eq 'Y' ?
1 : 0;
4358 # ***************************** OBSOLETE ************************************* #
4360 # This method sends messages using djabberd servers
4361 # which have been changed with Ejabberd. So method is obsolete.
4362 # Code to send messages to Ejabberd is in cgi-bin/LJ/NotificationMethod/IM.pm
4365 # find what servers a user is logged in to, and send them an IM
4366 # returns true if sent, false if failure or user not logged on
4367 # Please do not call from web context
4369 my ($self, %opts) = @_;
4371 croak
"Can't call in web context" if LJ
::is_web_context
();
4373 my $from = delete $opts{from
};
4374 my $msg = delete $opts{message
} or croak
"No message specified";
4376 croak
"No from or bot jid defined" unless $from || $LJ::JABBER_BOT_JID
;
4378 my @resources = keys %{LJ
::Jabber
::Presence
->get_resources($self)} or return 0;
4380 my $res = $resources[0] or return 0; # FIXME: pick correct server based on priority?
4381 my $pres = LJ
::Jabber
::Presence
->new($self, $res) or return 0;
4382 my $ip = $LJ::JABBER_SERVER_IP
|| '127.0.0.1';
4384 my $sock = IO
::Socket
::INET
->new(PeerAddr
=> "${ip}:5200")
4387 my $vhost = $LJ::DOMAIN
;
4389 my $to_jid = $self->user . '@' . $LJ::DOMAIN
;
4390 my $from_jid = $from ?
$from->user . '@' . $LJ::DOMAIN
: $LJ::JABBER_BOT_JID
;
4392 my $emsg = LJ
::exml
($msg);
4393 my $stanza = LJ
::eurl
(qq{<message to
="$to_jid" from
="$from_jid"><body
>$emsg</body></message
>});
4395 print $sock "send_stanza $vhost $to_jid $stanza\n";
4397 my $start_time = time();
4401 vec($rin, fileno($sock), 1) = 1;
4402 select(my $rout=$rin, undef, undef, 1);
4403 if (vec($rout, fileno($sock), 1)) {
4405 return 1 if $ln =~ /^OK/;
4408 last if time() > $start_time + 5;
4414 # returns whether or not the user is online on jabber
4415 sub jabber_is_online
{
4418 return keys %{LJ
::Jabber
::Presence
->get_resources($u)} ?
1 : 0;
4421 sub esn_inbox_default_expand
{
4424 my $prop = $u->raw_prop('esn_inbox_default_expand');
4425 return $prop ne 'N';
4429 my ($u, $ratename, $count, $opts) = @_;
4430 LJ
::rate_log
($u, $ratename, $count, $opts);
4434 my ($u, $ratename, $count, $opts) = @_;
4435 LJ
::rate_check
($u, $ratename, $count, $opts);
4440 return $u->{statusvis
};
4445 return $u->{statusvisdate
};
4448 sub statusvisdate_unix
{
4450 return LJ
::TimeUtil
->mysqldate_to_time($u->{statusvisdate
});
4453 # returns list of all previous statuses of the journal
4454 # in order from newest to oldest
4455 sub get_previous_statusvis
{
4458 my $records = LJ
::User
::Userlog
->get_records( $u,
4459 'action' => 'accountstatus' );
4462 foreach my $record (@
$records) {
4463 push @statusvis, $record->extra_unpacked->{'old'};
4469 # set_statusvis only change statusvis parameter, all accompanied actions are done in set_* methods
4471 my ($u, $statusvis) = @_;
4473 LJ
::MemCache
::delete('u:s:' . $u->userid);
4475 croak
"Invalid statusvis: $statusvis"
4476 unless $statusvis =~ /^(?
:
4487 # log the change to userlog, but only in case we have a valid clusterid;
4488 # this check addresses the case when an expunged user gets suspended
4489 if ( $u->clusterid ) {
4490 # remote looked up by create()
4491 LJ
::User
::UserlogRecord
::AccountStatus
->create( $u,
4492 'old' => $u->statusvis, 'new' => $statusvis );
4496 my $ret = LJ
::update_user
($u, { statusvis
=> $statusvis,
4497 raw
=> 'statusvisdate=NOW()' });
4499 LJ
::run_hooks
("props_changed", $u, {statusvis
=> $statusvis});
4509 LJ
::run_hooks
("account_will_be_visible", $u);
4510 return $u->set_statusvis('V');
4515 my $res = $u->set_statusvis('D');
4517 # run any account cancellation hooks
4518 LJ
::run_hooks
("account_delete", $u);
4524 return $u->set_statusvis('X');
4528 my ($u, $who, $reason, $errref, $public_reason) = @_;
4529 die "Not enough parameters for LJ::User::set_suspended call" unless $who and $reason;
4531 my $res = $u->set_statusvis('S');
4533 $$errref = "DB error while setting statusvis to 'S'" if ref $errref;
4537 LJ
::statushistory_add
($u, $who, "suspend", $reason);
4539 # close all spamreports on this user
4540 my $dbh = LJ
::get_db_writer
();
4541 $dbh->do("UPDATE spamreports SET state='closed' WHERE posterid = ? AND state='open'", undef, $u->userid);
4543 # close all botreports on this user
4544 require LJ
::BotReport
;
4545 LJ
::BotReport
->close_requests($u->userid);
4548 LJ
::run_hooks
("account_cancel", $u);
4549 LJ
::run_hooks
("account_suspend", $u);
4551 if ($public_reason) {
4552 LJ
::statushistory_add
($u, $who, "suspend_reason", $public_reason);
4553 $u->set_prop('suspend_reason' => $public_reason);
4556 if (my $err = LJ
::run_hook
("cdn_purge_userpics", $u)) {
4557 $$errref = $err if ref $errref and $err;
4561 return $res; # success
4564 # sets a user to visible, but also does all of the stuff necessary when a suspended account is unsuspended
4565 # this can only be run on a suspended account
4566 sub set_unsuspended
{
4567 my ($u, $who, $reason, $errref) = @_;
4568 die "Not enough parameters for LJ::User::set_unsuspended call" unless $who and $reason;
4570 unless ($u->is_suspended) {
4571 $$errref = "User isn't suspended" if ref $errref;
4575 my $res = $u->set_statusvis('V');
4577 $$errref = "DB error while setting statusvis to 'V'" if ref $errref;
4581 LJ
::statushistory_add
($u, $who, "unsuspend", $reason);
4582 LJ
::run_hooks
("account_unsuspend", $u);
4584 return $res; # success
4589 return $u->set_statusvis('L');
4594 return $u->set_statusvis('M');
4599 return $u->set_statusvis('O');
4604 return $u->set_statusvis('R');
4607 # returns if this user is considered visible
4610 return ($u->statusvis eq 'V' && $u->clusterid != 0);
4615 return $u->statusvis eq 'D';
4620 return $u->statusvis eq 'X' || $u->clusterid == 0;
4625 return $u->statusvis eq 'S';
4630 return $u->statusvis eq 'L';
4635 return $u->statusvis eq 'M';
4640 return $u->statusvis eq 'O';
4645 return $u->statusvis eq 'R';
4655 return $u->in_class('sup_user') && !$u->in_class('sup_optout');
4658 *get_post_count
= \
&number_of_posts
;
4659 sub number_of_posts
{
4660 my ($u, %opts) = @_;
4662 # to count only a subset of all posts
4664 $opts{return} = 'count';
4665 return $u->get_post_ids(%opts);
4668 my $memkey = [$u->{userid
}, "log2ct:$u->{userid}"];
4669 my $expire = time() + 3600*24*2; # 2 days
4670 return LJ
::MemCache
::get_or_set
($memkey, sub {
4671 return $u->selectrow_array("SELECT COUNT(*) FROM log2 WHERE journalid=?",
4672 undef, $u->{userid
});
4676 # return the number if public posts
4677 sub number_of_public_posts
{
4679 my $memkey = [$u->{userid
}, "log2publicct:$u->{userid}"];
4680 my $expire = time() + 300; # 5 min
4681 return LJ
::MemCache
::get_or_set
($memkey, sub {
4682 return $u->get_post_ids(return => 'count', security
=> 'public');
4687 # return the number of posts that the user actually posted themselves
4688 sub number_of_posted_posts
{
4691 my $num = $u->number_of_posts;
4692 $num-- if LJ
::run_hook
('user_has_auto_post', $u);
4698 # name: LJ::get_post_ids
4699 # des: Given a user object and some options, return the number of posts or the
4700 # posts'' IDs (jitemids) that match.
4701 # returns: number of matching posts, <strong>or</strong> IDs of
4702 # matching posts (default).
4704 # des-opts: 'security' - [public|private|usemask]
4705 # 'allowmask' - integer for friends-only or custom groups
4706 # 'start_date' - UTC date after which to look for match
4707 # 'end_date' - UTC date before which to look for match
4708 # 'return' - if 'count' just return the count
4709 # TODO: Add caching?
4712 my ($u, %opts) = @_;
4714 my $query = 'SELECT';
4715 my @vals; # parameters to query
4717 if ($opts{'start_date'} || $opts{'end_date'}) {
4718 croak
"start or end date not defined"
4719 if (!$opts{'start_date'} || !$opts{'end_date'});
4721 if (!($opts{'start_date'} >= 0) || !($opts{'end_date'} >= 0) ||
4722 !($opts{'start_date'} <= $LJ::EndOfTime
) ||
4723 !($opts{'end_date'} <= $LJ::EndOfTime
) ) {
4728 # return count or jitemids
4729 if ($opts{'return'} eq 'count') {
4730 $query .= " COUNT(*)";
4732 $query .= " jitemid";
4735 # from the journal entries table for this user
4736 $query .= " FROM log2 WHERE journalid=?";
4737 push(@vals, $u->{userid
});
4739 # filter by security
4740 if ($opts{'security'}) {
4741 $query .= " AND security=?";
4742 push(@vals, $opts{'security'});
4743 # If friends-only or custom
4744 if ($opts{'security'} eq 'usemask' && $opts{'allowmask'}) {
4745 $query .= " AND allowmask=?";
4746 push(@vals, $opts{'allowmask'});
4750 if ($opts{posterid
}){
4751 $query .= " AND posterid = ? ";
4752 push @vals => $opts{posterid
};
4754 if ($opts{afterid
}){
4755 $query .= " AND jitemid > ? ";
4756 push @vals => $opts{afterid
};
4759 # filter by date, use revttime as it is indexed
4760 if ($opts{'start_date'} && $opts{'end_date'}) {
4761 # revttime is reverse event time
4762 my $s_date = $LJ::EndOfTime
- $opts{'start_date'};
4763 my $e_date = $LJ::EndOfTime
- $opts{'end_date'};
4764 $query .= " AND revttime<?";
4765 push(@vals, $s_date);
4766 $query .= " AND revttime>?";
4767 push(@vals, $e_date);
4770 # return count or jitemids
4771 if ($opts{'return'} eq 'count') {
4772 return $u->selectrow_array($query, undef, @vals);
4774 my $jitemids = $u->selectcol_arrayref($query, undef, @vals) || [];
4775 die $u->errstr if $u->err;
4782 return unless $u->is_person;
4783 $u->{_password
} ||= LJ
::MemCache
::get_or_set
([$u->{userid
}, "pw:$u->{userid}"], sub {
4784 my $dbh = LJ
::get_db_writer
() or die "Couldn't get db master";
4785 return $dbh->selectrow_array("SELECT password FROM password WHERE userid=?",
4788 return $u->{_password
};
4793 return $u->{journaltype
};
4797 my ($u, $password) = @_;
4798 return LJ
::set_password
($u->id, $password);
4802 my ($u, $email) = @_;
4803 return LJ
::set_email
($u->id, $email);
4810 require LJ
::FBInterface
;
4811 LJ
::FBInterface
->push_user_info( $u->id );
4814 warn "Error running fb_push: $@\n" if $@
&& $LJ::IS_DEV_SERVER
;
4818 my ($u, $priv, $arg) = @_;
4820 my $dbh = LJ
::get_db_writer
();
4822 return 1 if LJ
::check_priv
($u, $priv, $arg);
4824 my $privid = $dbh->selectrow_array("SELECT prlid FROM priv_list".
4825 " WHERE privcode = ?", undef, $priv);
4826 return 0 unless $privid;
4828 $dbh->do("INSERT INTO priv_map (userid, prlid, arg) VALUES (?, ?, ?)",
4829 undef, $u->id, $privid, $arg);
4830 return 0 if $dbh->err;
4832 undef $u->{'_privloaded'}; # to force reloading of privs later
4837 my ($u, $priv, $arg) = @_;
4839 my $dbh = LJ
::get_db_writer
();
4841 return 1 unless LJ
::check_priv
($u, $priv, $arg);
4843 my $privid = $dbh->selectrow_array("SELECT prlid FROM priv_list".
4844 " WHERE privcode = ?", undef, $priv);
4845 return 0 unless $privid;
4847 $dbh->do("DELETE FROM priv_map WHERE userid = ? AND prlid = ? AND arg = ?",
4848 undef, $u->id, $privid, $arg);
4849 return 0 if $dbh->err;
4851 undef $u->{'_privloaded'}; # to force reloading of privs later
4852 undef $u->{'_priv'};
4856 sub revoke_priv_all
{
4857 my ($u, $priv) = @_;
4858 my $dbh = LJ
::get_db_writer
();
4860 my $privid = $dbh->selectrow_array("SELECT prlid FROM priv_list".
4861 " WHERE privcode = ?", undef, $priv);
4862 return 0 unless $privid;
4864 $dbh->do("DELETE FROM priv_map WHERE userid = ? AND prlid = ?",
4865 undef, $u->id, $privid);
4866 return 0 if $dbh->err;
4868 undef $u->{'_privloaded'}; # to force reloading of privs later
4869 undef $u->{'_priv'};
4873 # must be called whenever birthday, location, journal modtime, journaltype, etc.
4874 # changes. see LJ/Directory/PackedUserRecord.pm
4875 sub invalidate_directory_record
{
4879 # LJ::try_our_best_to("invalidate_directory_record", $u->id);
4880 # then elsewhere, map that key to subref. if primary run fails,
4881 # put in schwartz, then have one worker (misc-deferred) to
4884 my $dbs = defined $LJ::USERSEARCH_DB_WRITER ? LJ
::get_dbh
($LJ::USERSEARCH_DB_WRITER
) : LJ
::get_db_writer
();
4885 $dbs->do("UPDATE usersearch_packdata SET good_until=0 WHERE userid=?",
4889 # Used to promote communities in interest search results
4890 sub render_promo_of_community
{
4891 my ($comm, $style) = @_;
4893 return undef unless $comm;
4895 $style ||= 'Vertical';
4897 # get the ljuser link
4898 my $commljuser = $comm->ljuser_display;
4901 my $journal_base = $comm->journal_base;
4903 # get default userpic if any
4904 my $userpic = $comm->userpic;
4905 my $userpic_html = '';
4907 my $userpic_url = $userpic->url;
4908 $userpic_html = qq { <a href
="$journal_base"><img src
="$userpic_url" /></a> };
4911 my $blurb = $comm->prop('comm_promo_blurb') || '';
4913 my $join_link = "$LJ::SITEROOT/community/join.bml?comm=$comm->{user}";
4914 my $watch_link = "$LJ::SITEROOT/friends/add.bml?user=$comm->{user}";
4915 my $read_link = $comm->journal_base;
4917 LJ
::need_res
("stc/lj_base.css");
4919 # if horizontal, userpic needs to come before everything
4923 if (lc $style eq 'horizontal') {
4924 $box_class = 'Horizontal';
4925 $comm_display = qq {
4926 <div
class="Userpic">$userpic_html</div
>
4927 <div
class="Title">Community Promo
</div
>
4928 <div
class="CommLink">$commljuser</div
>
4931 $box_class = 'Vertical';
4932 $comm_display = qq {
4933 <div
class="Title">Community Promo
</div
>
4934 <div
class="CommLink">$commljuser</div
>
4935 <div
class="Userpic">$userpic_html</div
>
4941 <div
class="CommunityPromoBox">
4942 <div
class="$box_class">
4944 <div
class="Blurb">$blurb</div
>
4945 <div
class="Links"><a href
="$join_link">Join
</a> | <a href="$watch_link">Watch</a> |
4946 <a href
="$read_link">Read
</a></div
>
4948 <div
class='ljclear'> 
;</div
>
4959 my $statusvisdate = $u->statusvisdate_unix;
4961 # check admin flag "this journal must not be expunged for abuse team
4962 # investigation". hack: if flag is on, then set statusvisdate to now,
4963 # so that the next time worker bin/worker/expunge-users won't check
4966 # optimization concern: isn't it too much strain checking this prop
4967 # for every user? well, we've got to check this prop for every user
4968 # that seems eligible anyway, and moveucluster isn't supposed to send
4969 # us users who got too recent statusvisdate or something.
4970 if ($u->prop('dont_expunge_journal')) {
4971 LJ
::update_user
($u, { raw
=> 'statusvisdate=NOW()' });
4975 if ($u->is_deleted) {
4977 LJ
::conf_test
($LJ::DAYS_BEFORE_EXPUNGE
) || 30;
4979 return 0 unless $statusvisdate < time() - 86400 * $expunge_days;
4984 if ($u->is_suspended) {
4985 return 0 if $LJ::DISABLED
{'expunge_suspended'};
4988 LJ
::conf_test
($LJ::DAYS_BEFORE_EXPUNGE_SUSPENDED
) || 30;
4990 return 0 unless $statusvisdate < time() - 86400 * $expunge_days;
4998 # Check to see if the user can use eboxes at all
5002 return ref $LJ::DISABLED
{ebox
} ?
!$LJ::DISABLED
{ebox
}->($u) : !$LJ::DISABLED
{ebox
};
5005 # Allow users to choose eboxes if:
5006 # 1. The entire ebox feature isn't disabled AND
5007 # 2. The option to choose eboxes isn't disabled OR
5008 # 3. The option to choose eboxes is disabled AND
5009 # 4. The user already has eboxes turned on
5010 sub can_use_ebox_ui
{
5014 if ($LJ::DISABLED
{ebox_option
}) {
5015 $allow_ebox = $u->prop('journal_box_entries');
5018 return $u->can_use_ebox && $allow_ebox;
5021 # return hashref with intname => intid
5024 my $uints = LJ
::get_interests
($u);
5027 foreach my $int (@
$uints) {
5028 $interests{$int->[1]} = $int->[0]; # $interests{name} = intid
5037 return map { $_->[1] } @
{ LJ
::get_interests
($u) };
5040 sub interest_count
{
5043 # FIXME: fall back to SELECT COUNT(*) if not cached already?
5044 return scalar @
{LJ
::get_interests
($u, { justids
=> 1 })};
5049 LJ
::set_interests
($u, @_);
5052 sub lazy_interests_cleanup
{
5055 my $dbh = LJ
::get_db_writer
();
5057 if ($u->is_community) {
5058 $dbh->do("INSERT IGNORE INTO comminterests SELECT * FROM userinterests WHERE userid=?", undef, $u->id);
5059 $dbh->do("DELETE FROM userinterests WHERE userid=?", undef, $u->id);
5061 $dbh->do("INSERT IGNORE INTO userinterests SELECT * FROM comminterests WHERE userid=?", undef, $u->id);
5062 $dbh->do("DELETE FROM comminterests WHERE userid=?", undef, $u->id);
5065 LJ
::memcache_kill
($u, "intids");
5069 # this will return a hash of information about this user.
5070 # this is useful for JavaScript endpoints which need to dump
5071 # JSON data about users.
5076 username
=> $u->user,
5077 display_username
=> $u->display_username,
5078 display_name
=> $u->display_name,
5079 userid
=> $u->userid,
5080 url_journal
=> $u->journal_base,
5081 url_profile
=> $u->profile_url,
5082 url_allpics
=> $u->allpics_base,
5083 is_comm
=> $u->is_comm,
5084 is_person
=> $u->is_person,
5085 is_syndicated
=> $u->is_syndicated,
5086 is_identity
=> $u->is_identity,
5087 is_shared
=> $u->is_shared,
5089 # Without url_message "Send Message" link should not display
5090 $ret{url_message
} = $u->message_url unless ($u->opt_usermsg eq 'N');
5092 LJ
::run_hook
("extra_info_for_js", $u, \
%ret);
5094 my $up = $u->userpic;
5097 $ret{url_userpic
} = $up->url;
5098 $ret{userpic_w
} = $up->width;
5099 $ret{userpic_h
} = $up->height;
5105 sub postreg_completed
{
5108 return 0 unless $u->bio;
5109 return 0 unless $u->interest_count;
5113 # return if $target is banned from $u's journal
5114 *has_banned
= \
&is_banned
;
5116 my ($u, $target) = @_;
5117 return LJ
::is_banned
($target->userid, $u->userid);
5121 my ($u, $ban_u) = @_;
5123 my $remote = LJ
::get_remote
();
5124 LJ
::User
::UserlogRecord
::BanSet
->create( $u,
5125 'bannedid' => $ban_u->userid, 'remote' => $remote );
5127 LJ
::run_hooks
('ban_set', $u, $ban_u);
5129 $ban_u->clear_cache_friends($u);
5131 return LJ
::set_rel
($u->id, $ban_u->id, 'B');
5134 sub ban_user_multi
{
5135 my ($u, @banlist) = @_;
5137 my $us = LJ
::load_userids
(@banlist);
5138 my $remote = LJ
::get_remote
();
5140 foreach my $banuid (@banlist) {
5142 next unless $us->{$banuid};
5144 LJ
::User
::UserlogRecord
::BanSet
->create( $u,
5145 'bannedid' => $banuid, 'remote' => $remote );
5147 LJ
::run_hooks
('ban_set', $u, $us->{$banuid}) if $us->{$banuid};
5149 $us->{$banuid}->clear_cache_friends($u);
5152 LJ
::set_rel_multi
(map { [$u->id, $_, 'B'] } @banlist);
5157 sub unban_user_multi
{
5158 my ($u, @unbanlist) = @_;
5160 my $us = LJ
::load_userids
(@unbanlist);
5161 my $remote = LJ
::get_remote
();
5163 foreach my $banuid (@unbanlist) {
5165 next unless $us->{$banuid};
5167 LJ
::User
::UserlogRecord
::BanUnset
->create( $u,
5168 'bannedid' => $banuid, 'remote' => $remote );
5170 LJ
::run_hooks
('ban_unset', $u, $us->{$banuid}) if $us->{$banuid};
5172 $us->{$banuid}->clear_cache_friends($u);
5176 LJ
::clear_rel_multi
(map { [$u->id, $_, 'B'] } @unbanlist);
5181 # returns if this user's polls are clustered
5182 sub polls_clustered
{
5184 return $u->dversion >= 8;
5189 return $u->{dversion
};
5192 # take a user on dversion 7 and upgrade them to dversion 8 (clustered polls)
5193 sub upgrade_to_dversion_8
{
5199 # If user has been purged, go ahead and update version
5200 # Otherwise move their polls
5201 my $ok = $u->is_expunged ?
1 : LJ
::Poll
->make_polls_clustered($u, $dbh, $dbhslo, $dbcm);
5203 LJ
::update_user
($u, { 'dversion' => 8 }) if $ok;
5208 # returns if this user can join an adult community or not
5209 # adultref will hold the value of the community's adult content flag
5210 sub can_join_adult_comm
{
5211 my ($u, %opts) = @_;
5213 return 1 unless LJ
::is_enabled
('content_flag');
5215 my $adultref = $opts{adultref
};
5216 my $comm = $opts{comm
} or croak
"No community passed";
5218 my $adult_content = $comm->adult_content_calculated;
5219 $$adultref = $adult_content;
5221 if ($adult_content eq "concepts" && ($u->is_child || !$u->best_guess_age) && $LJ::DISABLED
{'remove_adult_concepts'}) {
5223 } elsif ($adult_content eq "explicit" && ($u->is_minor || !$u->best_guess_age)) {
5233 return LJ
::BetaFeatures
->user_in_beta( $u => $key );
5236 # return the user's timezone based on the prop if it's defined, otherwise best guess
5240 return $u->{'__timezone_offset'}
5241 if exists $u->{'__timezone_offset'};
5244 LJ
::get_timezone
($u, \
$offset);
5246 $u->{'__timezone_offset'} = $offset;
5250 # returns a DateTime object corresponding to a user's "now"
5254 my $now = DateTime
->now;
5256 # if user has timezone, use it!
5257 my $tz = $u->prop("timezone");
5258 return $now unless $tz;
5260 $now = eval { DateTime
->from_epoch(
5269 sub can_admin_content_flagging
{
5272 return 0 unless LJ
::is_enabled
("content_flag");
5273 return 1 if $LJ::IS_DEV_SERVER
;
5274 return LJ
::check_priv
($u, "siteadmin", "contentflag");
5277 sub can_see_content_flag_button
{
5281 return 0 unless LJ
::is_enabled
("content_flag");
5283 my $content = $opts{content
};
5285 # user can't flag any journal they manage nor any entry they posted
5286 # user also can't flag non-public entries
5287 if (LJ
::isu
($content)) {
5288 return 0 if $u->can_manage($content);
5289 } elsif ($content->isa("LJ::Entry")) {
5290 return 0 if $u->equals($content->poster);
5291 return 0 unless $content->security eq "public";
5294 # user can't flag anything if their account isn't at least one month old
5295 my $one_month = 60*60*24*30;
5296 return 0 unless time() - $u->timecreate >= $one_month;
5301 sub can_flag_content
{
5305 return 0 unless $u->can_see_content_flag_button(%opts);
5306 return 0 if LJ
::sysban_check
("contentflag", $u->user);
5307 return 0 unless $u->rate_check("ctflag", 1);
5311 # sometimes when the app throws errors, we want to display "nice"
5312 # text to end-users, while allowing admins to view the actual error message
5313 sub show_raw_errors
{
5316 return 1 if $LJ::IS_DEV_SERVER
;
5318 return 1 if LJ
::check_priv
($u, "supporthelp");
5319 return 1 if LJ
::check_priv
($u, "supportviewscreened");
5320 return 1 if LJ
::check_priv
($u, "siteadmin");
5325 # defined by the user
5326 # returns 'none', 'concepts' or 'explicit'
5330 my $prop_value = $u->prop('adult_content');
5332 return $prop_value ?
$prop_value : "none";
5335 # defined by an admin
5336 sub admin_content_flag
{
5339 return $u->prop('admin_content_flag');
5342 # uses both user- and admin-defined props to figure out the adult content level
5343 sub adult_content_calculated
{
5346 $u->preload_props(qw
/admin_content_flag adult_content/);
5347 return "explicit" if $u->admin_content_flag eq "explicit_adult";
5348 return $u->adult_content;
5351 sub show_graphic_previews
{
5354 my $prop_value = $u->prop('show_graphic_previews');
5356 my $hook_rv = LJ
::run_hook
("override_show_graphic_previews", $u, $prop_value);
5357 return $hook_rv if defined $hook_rv;
5361 } elsif ($prop_value eq "explicit_on") {
5363 } elsif ($prop_value eq "explicit_off") {
5370 sub should_show_graphic_previews
{
5373 return $u->show_graphic_previews eq "on" ?
1 : 0;
5376 # name: can_super_manage
5377 # des: Given a target user and determines that the user is an supermaintainer of community
5378 # returns: bool: true if supermaitainer, otherwise fail
5380 # des-u: user object or userid of community
5381 sub can_super_manage
{
5383 my $u = LJ
::want_user
(shift);
5385 return undef unless $remote && $u;
5388 return 1 if LJ
::u_equals
($u, $remote);
5390 # do not allow suspended users manage other accounts
5391 return 0 if $remote->is_suspended;
5393 # people/syn/rename accounts can only be managed by the one account
5394 return undef if $u->{journaltype
} =~ /^[PYR]$/;
5396 # check for supermaintainer access
5397 return 1 if LJ
::RelationService
->is_relation_to($u, $remote, 'S');
5399 # not passed checks, return false
5403 # name: can_moderate
5404 # des: Given a target user and determines that the user is an moderator for the target user
5405 # returns: bool: true if authorized, otherwise fail
5407 # des-u: user object or userid of target user
5410 my $u = LJ
::want_user
(shift);
5412 return undef unless $remote && $u;
5414 # can moderate only community
5415 return undef unless $u->is_community;
5417 # do not allow suspended users manage other accounts
5418 return 0 if $remote->is_suspended;
5420 # people/syn/rename accounts can only be managed by the one account
5421 return undef if $u->{journaltype
} =~ /^[PYR]$/;
5423 # check for moderate access
5424 return 1 if LJ
::RelationService
->is_relation_to($u, $remote, 'M');
5426 # passed not checks, return false
5431 # des: Given a target user and determines that the user is an admin for the taget user
5432 # returns: bool: true if authorized, otherwise fail
5434 # des-u: user object or userid of target user
5437 my $u = LJ
::want_user
(shift);
5439 return undef unless $remote && $u;
5442 return 1 if LJ
::u_equals
($u, $remote);
5444 # people/syn/rename accounts can only be managed by the one account
5445 return undef if $u->{journaltype
} =~ /^[PYR]$/;
5447 # do not allow suspended users manage other accounts
5448 return 0 if $remote->is_suspended;
5450 # check for supermaintainer
5451 return 1 if $remote->can_super_manage($u);
5453 return 0 unless LJ
::RelationService
->is_relation_to($u, $remote, 'A');
5455 # passed checks, return true
5461 my $u = LJ
::want_user
(shift);
5463 return undef unless $remote && $u;
5466 return 1 if LJ
::u_equals
($u, $remote);
5468 # do not allow suspended users to be watchers of other accounts.
5469 return 0 if $remote->is_suspended;
5471 # only personal journals can have watchers
5472 return undef unless $u->journaltype eq 'P';
5474 # check for admin access
5475 return undef unless LJ
::check_rel
($u, $remote, 'W');
5480 sub hide_adult_content
{
5483 my $prop_value = $u->prop('hide_adult_content');
5485 if (($u->is_child || !$u->best_guess_age) && $LJ::DISABLED
{'remove_adult_concepts'}) {
5489 if ($u->is_minor && $prop_value ne "concepts") {
5493 return $prop_value ?
$prop_value : "none";
5496 # returns a number that represents the user's chosen search filtering level
5498 # 1-10 = moderate filtering
5499 # >10 = strict filtering
5503 my $prop_value = $u->prop('safe_search');
5505 # current user 18+ default is 0
5506 # current user <18 default is 10
5507 # new user default (prop value is "nu_default") is 10
5508 return 0 if $prop_value eq "none";
5509 return $prop_value if $prop_value && $prop_value =~ /^\d+$/;
5510 return 0 if $prop_value ne "nu_default" && $u->best_guess_age && !$u->is_minor;
5514 # determine if the user in "for_u" should see $u in a search result
5515 sub should_show_in_search_results
{
5519 return 1 unless LJ
::is_enabled
("content_flag") && LJ
::is_enabled
("safe_search");
5521 my $adult_content = $u->adult_content_calculated;
5522 my $admin_flag = $u->admin_content_flag;
5524 my $for_u = $opts{for};
5525 unless (LJ
::isu
($for_u)) {
5526 return $adult_content ne "none" || $admin_flag ?
0 : 1;
5529 my $safe_search = $for_u->safe_search;
5530 return 1 if $safe_search == 0;
5532 my $adult_content_flag_level = $LJ::CONTENT_FLAGS
{$adult_content} ?
$LJ::CONTENT_FLAGS
{$adult_content}->{safe_search_level
} : 0;
5533 my $admin_flag_level = $LJ::CONTENT_FLAGS
{$admin_flag} ?
$LJ::CONTENT_FLAGS
{$admin_flag}->{safe_search_level
} : 0;
5535 return 0 if $adult_content_flag_level && ($safe_search >= $adult_content_flag_level);
5536 return 0 if $admin_flag_level && ($safe_search >= $admin_flag_level);
5541 my ($u, $target) = @_;
5543 return LJ
::u_equals
($u, $target);
5549 return LJ
::Tags
::get_usertags
($u);
5552 sub newpost_minsecurity
{
5555 my $val = $u->raw_prop('newpost_minsecurity') || 'public';
5558 if ($u->journaltype ne 'P' && $val eq 'private');
5563 sub third_party_notify_list
{
5566 my $val = $u->prop('third_party_notify_list');
5567 my @services = split(',', $val);
5572 # Check if the user's notify list contains a particular service
5573 sub third_party_notify_list_contains
{
5577 return 1 if grep { $_ eq $val } $u->third_party_notify_list;
5582 # Add a service to a user's notify list
5583 sub third_party_notify_list_add
{
5586 return 0 unless $svc;
5588 # Is it already there?
5589 return 1 if $u->third_party_notify_list_contains($svc);
5591 # Create the new list of services
5592 my @cur_services = $u->third_party_notify_list;
5593 push @cur_services, $svc;
5594 my $svc_list = join(',', @cur_services);
5596 # Trim a service from the list if it is too long
5597 if (length $svc_list > 255) {
5598 shift @cur_services;
5599 $svc_list = join(',', @cur_services)
5603 $u->set_prop('third_party_notify_list', $svc_list);
5607 # Remove a service to a user's notify list
5608 sub third_party_notify_list_remove
{
5611 return 0 unless $svc;
5614 return 1 unless $u->third_party_notify_list_contains($svc);
5617 $u->set_prop('third_party_notify_list',
5619 grep { $_ ne $svc } $u->third_party_notify_list
5625 # can $u add existing tags to $targetu's entries?
5626 sub can_add_tags_to
{
5627 my ($u, $targetu) = @_;
5629 return LJ
::Tags
::can_add_tags
($targetu, $u);
5632 sub qct_value_for_ads
{
5635 return 0 unless LJ
::is_enabled
("content_flag");
5637 my $adult_content = $u->adult_content_calculated;
5638 my $admin_flag = $u->admin_content_flag;
5640 if ($LJ::CONTENT_FLAGS
{$adult_content} && $LJ::CONTENT_FLAGS
{$adult_content}->{qct_value_for_ads
}) {
5641 return $LJ::CONTENT_FLAGS
{$adult_content}->{qct_value_for_ads
};
5643 if ($LJ::CONTENT_FLAGS
{$admin_flag} && $LJ::CONTENT_FLAGS
{$admin_flag}->{qct_value_for_ads
}) {
5644 return $LJ::CONTENT_FLAGS
{$admin_flag}->{qct_value_for_ads
};
5650 sub should_block_robots
{
5653 return 1 if $u->prop('opt_blockrobots');
5655 return 0 unless LJ
::is_enabled
("content_flag");
5657 my $adult_content = $u->adult_content_calculated;
5658 my $admin_flag = $u->admin_content_flag;
5660 return 1 if $LJ::CONTENT_FLAGS
{$adult_content} && $LJ::CONTENT_FLAGS
{$adult_content}->{block_robots
};
5661 return 1 if $LJ::CONTENT_FLAGS
{$admin_flag} && $LJ::CONTENT_FLAGS
{$admin_flag}->{block_robots
};
5665 # memcache key that holds the number of times a user performed one of the rate-limited actions
5669 return [$u->id, "rate:" . $u->id . ":$rp->{id}"];
5672 sub opt_exclude_from_verticals
{
5675 my $prop_val = $u->prop('opt_exclude_from_verticals');
5677 return $prop_val if $prop_val =~ /^(?:entries)$/;
5681 sub set_opt_exclude_from_verticals
{
5685 # only set the "none" value if the prop is currently set to something (explicit off)
5686 my $prop_val = $val ?
"entries" : undef;
5687 $prop_val = "none" if !$val && $u->prop('opt_exclude_from_verticals');
5689 $u->set_prop( opt_exclude_from_verticals
=> $prop_val );
5694 # prepare OpenId part of html-page, if needed
5700 # OpenID Server and Yadis
5701 if (LJ
::OpenID
->server_enabled and defined $u) {
5702 my $journalbase = $u->journal_base;
5703 $head .= qq{<link rel
="openid2.provider" href
="$LJ::OPENID_SERVER" />\n};
5704 $head .= qq{<link rel
="openid.server" href
="$LJ::OPENID_SERVER" />\n};
5705 $head .= qq{<meta http
-equiv
="X-XRDS-Location" content
="$journalbase/data/yadis" />\n};
5711 # return the number of comments a user has posted
5712 sub num_comments_posted
{
5715 my $ret = $u->prop('talkleftct2');
5717 unless (defined $ret) {
5718 my $dbr = LJ
::get_cluster_reader
($u);
5719 $ret = $dbr->selectrow_array(qq{
5720 SELECT COUNT
(*) FROM talkleft WHERE userid
=?
5723 $u->set_prop('talkleftct2' => $ret);
5729 # increase the number of comments a user has posted by 1
5730 sub incr_num_comments_posted
{
5733 $u->set_prop('talkleftct2' => $u->num_comments_posted + 1);
5736 # return the number of comments a user has received
5737 sub num_comments_received
{
5741 my $userid = $u->id;
5742 my $memkey = [$userid, "talk2ct:$userid"];
5743 my $count = LJ
::MemCache
::get
($memkey);
5745 my $dbcr = $opts{dbh
} || LJ
::get_cluster_reader
($u);
5746 my $expire = time() + 3600*24*2; # 2 days;
5747 $count = $dbcr->selectrow_array("SELECT COUNT(*) FROM talk2 ".
5748 "WHERE journalid=?", undef, $userid);
5749 LJ
::MemCache
::set
($memkey, $count, $expire) if defined $count;
5755 # returns undef if there shouldn't be an option for this user
5756 # B = show ads [B]oth to logged-out traffic on the user's journal and on the user's app pages
5757 # J = show ads only to logged-out traffic on the user's [J]ournal
5758 # A = show ads only on the user's [A]pp pages
5762 return undef unless LJ
::is_enabled
("basic_ads") && LJ
::run_hook
("user_is_basic", $u);
5763 return 'J' unless LJ
::is_enabled
("basic_ad_options") && $u->is_personal;
5765 my $prop_val = $u->prop("ad_visibility");
5766 return $prop_val =~ /^[BJA]$/ ?
$prop_val : 'B';
5769 sub wants_ads_on_app
{
5772 my $ad_visibility = $u->ad_visibility;
5773 return $ad_visibility eq "B" || $ad_visibility eq "A" ?
1 : 0;
5776 sub wants_ads_in_journal
{
5779 my $ad_visibility = $u->ad_visibility;
5780 return $ad_visibility eq "B" || $ad_visibility eq "J" ?
1 : 0;
5783 # format unixtimestamp according to the user's timezone setting
5788 return undef unless $time;
5790 return eval { DateTime
->from_epoch(epoch
=>$time, time_zone
=>$u->prop("timezone"))->ymd('-') } ||
5791 DateTime
->from_epoch(epoch
=> $time)->ymd('-');
5794 sub support_points_count
{
5797 my $dbr = LJ
::get_db_reader
();
5798 my $userid = $u->id;
5801 $count = $u->{_supportpointsum
};
5802 return $count if defined $count;
5804 my $memkey = [$userid, "supportpointsum:$userid"];
5805 $count = LJ
::MemCache
::get
($memkey);
5806 if (defined $count) {
5807 $u->{_supportpointsum
} = $count;
5811 $count = $dbr->selectrow_array("SELECT totpoints FROM supportpointsum WHERE userid=?", undef, $userid) || 0;
5812 $u->{_supportpointsum
} = $count;
5813 LJ
::MemCache
::set
($memkey, $count, 60*5);
5818 sub can_be_nudged_by
{
5819 my ($u, $nudger) = @_;
5821 return 0 unless LJ
::is_enabled
("nudge");
5822 return 0 if $u->equals($nudger);
5823 return 0 unless $u->is_personal;
5824 return 0 unless $u->is_visible;
5825 return 0 if $u->prop("opt_no_nudge");
5826 return 0 unless $u->is_mutual_friend($nudger);
5827 return 0 unless time() - $u->timeupdate >= 604800; # updated in the past week
5832 sub should_show_schools_to
{
5833 my ($u, $targetu) = @_;
5835 return 0 unless LJ
::is_enabled
("schools");
5836 return 1 if $u->prop('opt_showschools') eq '' || $u->prop('opt_showschools') eq 'Y';
5837 return 1 if $u->prop('opt_showschools') eq 'F' && $u->has_friend($targetu);
5842 sub can_be_text_messaged_by
{
5843 my ($u, $sender) = @_;
5845 return 0 unless $u->get_cap("textmessaging");
5847 my $tminfo = LJ
::TextMessage
->tm_info($u);
5849 ## messaging is disabled for some providers
5850 my $provider = $tminfo ?
$tminfo->{provider
} : '';
5851 return 0 if $provider eq 'beeline';
5852 return 0 if $provider eq 'megafon';
5855 my $security = $tminfo && $tminfo->{security
} ?
$tminfo->{security
} : "none";
5856 return 0 if $security eq "none";
5857 return 1 if $security eq "all";
5860 return 1 if $security eq "reg";
5861 return 1 if $security eq "friends" && $u->has_friend($sender);
5868 # name: LJ::User::rename_identity
5869 # des: Change an identity user's 'identity', update DB,
5870 # clear memcache and log change.
5872 # returns: Success or failure.
5874 sub rename_identity
{
5876 return 0 unless ($u && $u->is_identity && $u->is_expunged);
5878 my $id = $u->identity;
5879 return 0 unless $id;
5881 my $dbh = LJ
::get_db_writer
();
5883 # generate a new identity value that looks like ex_oldidvalue555
5887 my $temp = (length($ident) > 249) ?
substr($ident, 0, 249) : $ident;
5891 $exid = "ex_$temp" . int(rand(999));
5893 # check to see if this identity already exists
5894 unless ($dbh->selectrow_array("SELECT COUNT(*) FROM identitymap WHERE identity=? AND idtype=? LIMIT 1", undef, $exid, $idtype)) {
5895 # name doesn't already exist, use this one
5898 # name existed, try and get another
5907 my $from = $id->value;
5908 my $to = $tempid->($id->value, $id->typeid);
5910 return 0 unless $to;
5912 $dbh->do("UPDATE identitymap SET identity=? WHERE identity=? AND idtype=?",
5913 undef, $to, $from, $id->typeid);
5915 LJ
::memcache_kill
($u, "userid");
5917 LJ
::User
::InfoHistory
->add( $u, 'identity', $from );
5923 # name: LJ::User::get_renamed_user
5924 # des: Get the actual user of a renamed user
5928 sub get_renamed_user
{
5931 my $hops = $opts{hops
} || 5;
5933 # Traverse the renames to the final journal
5935 while ($u and $u->journaltype eq 'R' and $hops-- > 0) {
5936 my $rt = $u->prop("renamedto");
5937 last unless length $rt;
5938 if ($rt =~ /^https?:\/\
//){
5939 if ( my $newu = LJ
::User
->new_from_url($rt) ) {
5942 warn $u->username . " links to non-existent user at $rt";
5946 if ( my $newu = LJ
::load_user
($rt) ) {
5949 warn $u->username . " links to non-existent user at $rt";
5959 sub dismissed_page_notices
{
5962 my $val = $u->prop("dismissed_page_notices");
5963 my @notices = split(",", $val);
5968 sub has_dismissed_page_notice
{
5970 my $notice_string = shift;
5972 return 1 if grep { $_ eq $notice_string } $u->dismissed_page_notices;
5976 # add a page notice to a user's dismissed page notices list
5977 sub dismissed_page_notices_add
{
5979 my $notice_string = shift;
5980 return 0 unless $notice_string && $LJ::VALID_PAGE_NOTICES
{$notice_string};
5982 # is it already there?
5983 return 1 if $u->has_dismissed_page_notice($notice_string);
5985 # create the new list of dismissed page notices
5986 my @cur_notices = $u->dismissed_page_notices;
5987 push @cur_notices, $notice_string;
5988 my $cur_notices_string = join(",", @cur_notices);
5990 # remove the oldest notice if the list is too long
5991 if (length $cur_notices_string > 255) {
5993 $cur_notices_string = join(",", @cur_notices);
5997 $u->set_prop("dismissed_page_notices", $cur_notices_string);
6002 # remove a page notice from a user's dismissed page notices list
6003 sub dismissed_page_notices_remove
{
6005 my $notice_string = shift;
6006 return 0 unless $notice_string && $LJ::VALID_PAGE_NOTICES
{$notice_string};
6009 return 0 unless $u->has_dismissed_page_notice($notice_string);
6012 $u->set_prop("dismissed_page_notices", join(",", grep { $_ ne $notice_string } $u->dismissed_page_notices));
6017 sub custom_usericon
{
6020 ## Get user's selected userhead
6021 my $selected_uh_id = 0;
6022 my $url = $u->prop('custom_usericon') || '';
6026 && (my ($uh_id) = ($selected_uh_id) = $url =~ m/\/userhead\
/(\d+)$/)
6028 my $uh = LJ
::UserHead
->get_userhead ($uh_id);
6030 my $uh_fs = LJ
::FileStore
->get_path_info ( path
=> "/userhead/".$uh->get_uh_id );
6031 $url .= "?v=".$uh_fs->{'change_time'} if $uh_fs->{'change_time'};
6035 ## Check for individual userhead
6036 my $indiv_uh_id = 0;
6037 my $propval = $u->prop ('custom_usericon_individual');
6039 ## If it buyed we need to check exp date
6040 my $individual_uh_info = LJ
::JSON
->from_json ($propval);
6041 if ($individual_uh_info->{'date_exp'} > time) {
6042 my ($uh_id) = ($indiv_uh_id) = $individual_uh_info->{'uh_id'} =~ m
#uh-(\d+)#;
6043 my $uh = LJ
::UserHead
->get_userhead ($uh_id);
6044 if ($uh && $selected_uh_id == $indiv_uh_id) {
6045 my $uh_fs = LJ
::FileStore
->get_path_info ( path
=> "/userhead/".$uh_id );
6046 $url = $LJ::FILEPREFIX
."/userhead/".$uh_id;
6047 $url .= "?v=".$uh_fs->{'change_time'} if $uh_fs->{'change_time'};
6050 ## If indiv userhead was selected and date is expired, set userhead to default
6051 if ($selected_uh_id == $indiv_uh_id) {
6052 $u->set_custom_usericon (undef);
6057 $url =~ s
#^http://files\.livejournal\.com#$LJ::FILEPREFIX#;
6062 sub custom_usericon_appid
{
6064 return $u->prop('custom_usericon_appid') || 0;
6067 sub set_custom_usericon
{
6068 my ($u, $url, %opts) = @_;
6070 $u->set_prop( 'custom_usericon' => $url );
6072 if ($opts{application_id
}) {
6073 $u->set_prop( 'custom_usericon_appid' => $opts{application_id
});
6075 $u->clear_prop( 'custom_usericon_appid' );
6079 sub _subscriptions_count
{
6082 my $set = LJ
::Subscription
::GroupSet
->fetch_for_user($u, sub { 0 });
6084 return $set->{'active_count'};
6087 sub subscriptions_count
{
6090 my $cached = LJ
::MemCache
::get
('subscriptions_count:'.$u->id);
6091 return $cached if defined $cached;
6093 my $count = $u->_subscriptions_count;
6094 LJ
::MemCache
::set
('subscriptions_count:'.$u->id, $count);
6100 return $u->{'packed_props'};
6103 sub set_packed_props
{
6104 my ($u, $newprops) = @_;
6106 LJ
::update_user
($u, { 'packed_props' => $newprops });
6107 $u->{'packed_props'} = 1;
6110 sub init_userprop_def
{
6113 # defaults for S1 style IDs in config file are magic: really
6114 # uniq strings representing style IDs, so on first use, we need
6116 unless ($LJ::CACHED_S1IDMAP
) {
6117 my $pubsty = LJ
::S1
::get_public_styles
();
6118 foreach (values %$pubsty) {
6119 my $k = "s1_$_->{'type'}_style";
6120 my $needval = "$_->{'type'}/$_->{'styledes'}";
6121 next unless $LJ::USERPROP_DEF
{$k} eq $needval;
6123 $LJ::USERPROP_DEF
{$k} = $_->{'styleid'};
6126 $LJ::CACHED_S1IDMAP
= 1;
6133 my $dbcm = LJ
::get_cluster_master
($u);
6134 return 0 unless $dbcm;
6162 msn:mutual_friends_wlids:uid=*
6170 subscriptions_count:*
6189 foreach my $key (@keys) {
6190 $key =~ s/\*/$u->{userid}/g;
6191 LJ
::MemCacheProxy
::delete([ $u->{userid
}, $key ]);
6194 my $bio = $dbcm->selectrow_array('SELECT bio FROM userbio WHERE userid = ?', undef, $u->{userid
});
6195 if ($bio =~ /\S/ && $u->{has_bio
} ne 'Y') {
6196 LJ
::update_user
($u, { has_bio
=> 'Y' });
6199 $u->do("UPDATE s1usercache SET override_stor = NULL WHERE userid = ?", undef, $u->{userid
});
6201 my $dbh = LJ
::get_db_writer
();
6202 my $themeids = $dbh->selectcol_arrayref('SELECT moodthemeid FROM moodthemes WHERE ownerid = ?', undef, $u->{userid
});
6203 if ($themeids && @
$themeids) {
6204 foreach my $themeid (@
$themeids) {
6205 LJ
::MemCache
::delete([ $themeid, "moodthemedata:$themeid" ]);
6209 my $picids = $dbcm->selectcol_arrayref('SELECT picid FROM userpic2 WHERE userid = ?', undef, $u->{userid
});
6210 if ($picids && @
$picids) {
6211 foreach my $picid (@
$picids) {
6212 LJ
::MemCache
::delete([ $picid, "mogp.up.$picid" ]);
6213 LJ
::MemCache
::delete([ $picid, "mogp.up.$picid.alt" ]); # alt-zone (only zone at this time)
6217 my $s2ids = $dbh->selectcol_arrayref('SELECT styleid FROM s2styles WHERE userid = ?', undef, $u->{userid
});
6218 if ($s2ids && @
$s2ids) {
6219 foreach my $s2id (@
$s2ids) {
6220 LJ
::MemCache
::delete([ $s2id, "s2s:$s2id" ]);
6221 LJ
::MemCache
::delete([ $s2id, "s2sl:$s2id" ]);
6225 my $s2lids = $dbcm->selectcol_arrayref('SELECT s2lid FROM s2stylelayers2 WHERE userid = ?', undef, $u->{userid
});
6227 # put it in a hash to remove duplicates so we don't purge one layer twice
6228 my %s2lids = ( map { $_ => 1 } grep { $_ } @
$s2lids );
6230 foreach my $s2lid (keys %s2lids) {
6231 LJ
::MemCache
::delete([ $s2lid, "s2lo:$s2lid" ]);
6232 LJ
::MemCache
::delete([ $s2lid, "s2c:$s2lid" ]);
6240 ## Check for activity user at last N days
6241 ## args: days - how many days to check
6243 ## 1 - user logs in the last 'days' days
6244 ## 0 - user NOT logs in the last 'days' days
6245 sub check_activity
{
6249 return 0 unless $days;
6251 my $sth = $u->prepare ("SELECT logintime FROM loginlog WHERE userid=? ORDER BY logintime DESC");
6252 $sth->execute ($u->userid);
6254 if (my @row = $sth->fetchrow_array) {
6255 my $logintime = $row[0];
6256 return 1 if time - $logintime < $days * 86400;
6262 sub is_in_whitelist_for_spam
{
6264 return $u->prop('in_whitelist_for_spam');
6267 sub is_spamprotection_enabled
{
6269 return 0 if $LJ::DISABLED
{'spam_button'};
6270 my $spamprotection = $u->prop('spamprotection');
6271 return 0 if $spamprotection eq 'N';
6275 sub check_non_whitelist_enabled
{
6277 return 0 if $LJ::DISABLED
{'spam_button'};
6278 return 0 unless $u->is_community;
6279 return 0 if $u->prop("moderated") eq 'N';
6280 my $check_non_whitelist = $u->prop('check_non_whitelist');
6281 return 1 if defined($check_non_whitelist) && $check_non_whitelist eq 'Y';
6285 # return sticky entries existing
6286 sub has_sticky_entry
{
6288 my $sticky_id = $self->prop("sticky_entry_id");
6295 # returns sticky entry jitemid
6296 sub get_sticky_entry_id
{
6298 return $self->prop("sticky_entry_id") || '';
6301 # returns sticky entry jitemid
6302 sub remove_sticky_entry_id
{
6304 my $ownerid = $self->userid;
6305 LJ
::MemCache
::delete([$ownerid, "log2lt:$ownerid"]);
6306 $self->clear_prop("sticky_entry_id");
6311 my ($self, $itemid) = @_;
6312 die "itemid is not set" unless ($itemid);
6314 my $ownerid = $self->userid;
6315 LJ
::MemCache
::delete([$ownerid, "log2lt:$ownerid"]);
6316 $self->set_prop( sticky_entry_id
=> $itemid );
6319 # set socical influence information
6320 sub set_social_influence
{
6321 my ($self, $social_influence_infornation) = @_;
6323 # update user cached 'social_influence_info'
6324 $self->{'__social_influence_info'} = $social_influence_infornation;
6326 my $new_prop_value = LJ
::JSON
->to_json($social_influence_infornation) ;
6327 $self->set_prop( 'social_influence_info' => $new_prop_value);
6330 # get socical influence information
6331 sub get_social_influence
{
6334 # Does user contains cache?
6335 if ( !$self->{'__social_influence_info'} ) {
6336 my $prop_value = $self->prop("social_influence_info");
6341 $self->{'__social_influence_info'} = LJ
::JSON
->from_json($prop_value);
6343 return $self->{'__social_influence_info'};
6346 sub push_subscriptions
{
6350 $u->{push_subscriptions
} = LJ
::PushNotification
::Storage
->get_all($u)
6351 if !$u->{push_subscriptions
} || $opts{flush
};
6353 return keys %{$u->{push_subscriptions
}};
6356 sub push_subscription
{
6359 return $u->{push_subscriptions
}{$key} || {};
6362 sub disable_promo_announce
{
6364 $u->set_prop('promo_announce_disabled', 1);
6367 sub promo_announce_disabled
{
6369 return $u->prop('promo_announce_disabled') || 0;
6374 return $u->prop('spam_counter') || 0;
6377 sub clear_spam_counter
{
6379 $u->set_prop('spam_counter', 0);
6382 # If true, user migrated old friends to friends and subscriptions
6383 sub is_migrated_to_friends_and_subscriptions
{
6385 return $u->prop('migrated_to_friends_and_subscriptions');
6388 sub admin_api_access
{
6389 my ($u, $method) = @_;
6391 return unless LJ
::isu
($u) && $method;
6393 my $privilege = $LJ::API_PRIVILEGES
{$method};
6395 my $priv = $privilege->{'priv'};
6396 my $arg = $privilege->{'arg'};
6398 if ( LJ
::check_priv
($u, $priv, $arg) ) {
6410 # name: LJ::get_authas_list
6411 # des: Get a list of usernames a given user can authenticate as.
6412 # returns: an array of usernames.
6414 # des-opts: Optional hashref. keys are:
6415 # - type: 'P' to only return users of journaltype 'P'.
6416 # 'S' return users of Supermaintainer type instead Maintainer type.
6417 # - cap: cap to filter users on.
6419 sub get_authas_list
{
6420 my ($u, $opts) = @_;
6424 # used to accept a user type, now accept an opts hash
6425 $opts = { 'type' => $opts } unless ref $opts;
6427 # Two valid types, Personal or Community
6428 $opts->{'type'} = undef unless $opts->{'type'} =~ m/^(P|C|S)$/;
6430 my $ids = LJ
::load_rel_target
($u, 'S') || [];
6431 if ($opts->{'type'} ne 'S') {
6432 my $a_ids = LJ
::load_rel_target
($u, 'A') || [];
6433 push @
$ids, @
$a_ids;
6435 return $u->{'user'} unless $ids && @
$ids;
6437 $opts->{'type'} = '' if $opts->{'type'} eq 'S';
6439 # load_userids_multiple
6441 LJ
::load_userids_multiple
([ map { $_, \
$users{$_} } @
$ids ], [$u]);
6443 return map { $_->{'user'} }
6444 grep { ! $opts->{'cap'} || LJ
::get_cap
($_, $opts->{'cap'}) }
6445 grep { ! $opts->{'type'} || $opts->{'type'} eq $_->{'journaltype'} }
6447 # unless overridden, hide non-visible/non-read-only journals. always display the user's acct
6448 grep { $opts->{'showall'} || $_->is_visible || $_->is_readonly || LJ
::u_equals
($_, $u) }
6450 # can't work as an expunged account
6451 grep { $_ && ref $_ eq 'LJ::User' && %$_ && !$_->is_expunged && $_->{clusterid
} > 0 }
6452 $u, sort { $a->{'user'} cmp $b->{'user'} } values %users;
6456 # name: LJ::get_postto_list
6457 # des: Get the list of usernames a given user can post to.
6458 # returns: an array of usernames
6460 # des-opts: Optional hashref. keys are:
6461 # - type: 'P' to only return users of journaltype 'P'.
6462 # - cap: cap to filter users on.
6464 sub get_postto_list
{
6465 my ($u, $opts) = @_;
6467 # used to accept a user type, now accept an opts hash
6468 $opts = { 'type' => $opts } unless ref $opts;
6470 # only one valid type right now
6471 $opts->{'type'} = 'P' if $opts->{'type'};
6473 my $ids = LJ
::load_rel_target
($u, 'P');
6474 return undef unless $ids;
6476 # load_userids_multiple
6478 LJ
::load_userids_multiple
([ map { $_, \
$users{$_} } @
$ids ], [$u]);
6480 return $u->{'user'}, sort map { $_->{'user'} }
6481 grep { ! $opts->{'cap'} || LJ
::get_cap
($_, $opts->{'cap'}) }
6482 grep { ! $opts->{'type'} || $opts->{'type'} eq $_->{'journaltype'} }
6483 grep { $_->clusterid > 0 }
6484 grep { $_->is_visible }
6490 # des: Checks to see if the remote user can use javascript in S2 layers.
6491 # returns: boolean; 1 if remote user can use javascript
6493 # des-userid: id of user to check
6498 my $u = LJ
::load_userid
($userid);
6501 return $u->prop('javascript');
6505 # name: LJ::can_view
6506 # des: Checks to see if the remote user can view a given journal entry.
6507 # <b>Note:</b> This is meant for use on single entries at a time,
6508 # not for calling many times on every entry in a journal.
6509 # returns: boolean; 1 if remote user can see item
6510 # args: remote, item
6511 # des-item: Hashref from the 'log' table.
6518 return 1 if $item->{'security'} eq "public";
6520 # must be logged in otherwise
6521 return 0 unless $remote;
6523 my $userid = int($item->{'ownerid'} || $item->{'journalid'});
6524 my $u = LJ
::load_userid
($userid);
6525 my $journal_name = $u ?
$u->user : '';
6526 my $remoteid = int($remote->{'userid'});
6528 # owners can always see their own.
6529 return 1 if $remote->can_manage($userid);
6531 # author in community can always see their post
6532 return 1 if $remoteid == $item->{'posterid'} and not $LJ::JOURNALS_WITH_PROTECTED_CONTENT
{ $journal_name };;
6534 # other people can't read private
6535 return 0 if ($item->{'security'} eq "private");
6537 # should be 'usemask' security from here out, otherwise
6538 # assume it's something new and return 0
6539 return 0 unless ($item->{'security'} eq "usemask");
6541 # if it's usemask, we have to refuse non-personal journals,
6542 # so we have to load the user
6543 return 0 unless $remote->{'journaltype'} eq 'P' || $remote->{'journaltype'} eq 'I';
6545 # TAG:FR:ljlib:can_view (turn off bit 0 for just watching? hmm.)
6546 my $gmask = LJ
::get_groupmask
($userid, $remoteid);
6547 my $allowed = (int($gmask) & int($item->{'allowmask'}));
6548 return $allowed ?
1 : 0; # no need to return matching mask
6552 # name: LJ::wipe_major_memcache
6553 # des: invalidate all major memcache items associated with a given user.
6557 sub wipe_major_memcache
6560 my $userid = LJ
::want_userid
($u);
6561 foreach my $key ("userid","bio","talk2ct","log2ct",
6562 "log2lt","memkwid","s1overr","s1uc","fgrp",
6563 "friends","friendofs","tu","upicinf","upiccom",
6564 "upicurl", "intids", "memct", "lastcomm")
6566 LJ
::memcache_kill
($userid, $key);
6571 # name: LJ::load_user_props
6572 # des: Given a user hashref, loads the values of the given named properties
6573 # into that user hashref.
6574 # args: u, opts?, propname*
6575 # des-opts: hashref of opts. set key 'cache' to use memcache.
6576 # des-propname: the name of a property from the [dbtable[userproplist]] table.
6578 sub load_user_props
{
6579 my ($u, @props) = @_;
6580 return unless ref $u;
6582 my $opts = ref $props[0]?
shift @props : {};
6583 unless ( delete $opts->{'reload'} ) {
6584 @props = grep { not exists $u->{$_} } @props;
6587 LJ
::load_user_props_multi
([$u], \
@props, $opts);
6590 sub load_user_props_multi
{
6591 my ($users, $props, $opts) = @_;
6592 my $use_master = $opts->{'use_master'};
6594 $props = [grep { defined and not ref } @
$props];
6595 return unless @
$props;
6597 $users = { map { $_->{'userid'} => $_ } grep { $_->{'statusvis'} ne 'X' and $_->{'clusterid'} } grep { ref } @
$users };
6598 return unless %$users;
6600 $LJ::COUNT_LOAD_PROPS_MULTI
++;
6602 my $groups = LJ
::User
::PropStorage
->get_handler_multi(\@
$props);
6603 my $memcache_available = @LJ::MEMCACHE_SERVERS
;
6604 $use_master = $memcache_available || $use_master;
6605 my $memc_expire = time() + 3600 * 24;
6607 LJ
::User
->init_userprop_def;
6609 foreach my $handler (keys %$groups) {
6610 my %propkeys = map { $_ => $LJ::USERPROP_DEF
{$_} || '' } @
{ $groups->{$handler} };
6612 # if there is no memcache, or if the handler doesn't wish to use
6613 # memcache, hit the storage directly, update the user object,
6614 # and get straight to the next handler
6615 if ( not $memcache_available or not defined $handler->use_memcache ) {
6616 foreach my $u (values %$users) {
6619 %{ $handler->get_props($u, $groups->{$handler},
6621 use_master
=> $use_master
6627 _extend_user_object
->($u, $propmap);
6633 # now let's find out what we're going to do with memcache
6634 my $memcache_policy = $handler->use_memcache;
6636 if ( $memcache_policy eq 'lite' ) {
6638 my $propmaps = LJ
::MemCacheProxy
::get_multi
(map {
6640 ($_ => ($memkeys{$_} = $handler->memcache_key($users->{$_})))
6645 my $rmemkeys = { map { $memkeys{$_} => $_ } keys %memkeys };
6647 while (($userid, $v) = each %$propmaps) {
6649 $userid = $rmemkeys->{$userid};
6651 delete $memkeys{$userid}; # Loading is successfull
6653 # Hack to init keys for empty props
6656 %{ LJ
::User
::PropStorage
->unpack_from_memcache($v) },
6659 _extend_user_object
($users->{$userid}, $packed);
6662 while (($userid, $v) = each %memkeys) {
6663 my $propmap = $handler->get_props(
6664 $users->{$userid}, [],
6665 { 'use_master' => $use_master }
6668 _extend_user_object
($users->{$userid}, { %propkeys, %$propmap });
6670 my $packed = LJ
::User
::PropStorage
->pack_for_memcache($propmap);
6671 LJ
::MemCache
::set
([$userid, $v], $packed, $memc_expire);
6673 } elsif ( $memcache_policy eq 'blob' ) {
6674 my $handled_props = $groups->{$handler};
6676 foreach my $u (values %$users) {
6677 my $propmap_memc = $handler->fetch_props_memcache($u, $handled_props);
6679 _extend_user_object
($u, { %propkeys, %$propmap_memc });
6681 my @load_from_db = grep { !exists $propmap_memc->{$_} }
6684 # if we can avoid hitting the db, avoid it
6685 next unless @load_from_db;
6687 my $propmap_db = $handler->get_props(
6689 { 'use_master' => $use_master }
6692 _extend_user_object
($u, $propmap_db);
6694 # now, update memcache
6695 $handler->store_props_memcache( $u, $propmap_db );
6701 sub _extend_user_object
{
6702 my ($u, $propmap) = @_;
6703 return unless ref $u;
6704 return unless ref $propmap eq 'HASH';
6707 $u->{$k} = $v while ($k, $v) = each %$propmap;
6712 # name: LJ::load_userids
6713 # des: Simple interface to [func[LJ::load_userids_multiple]].
6715 # returns: hashref with keys ids, values $u refs.
6719 LJ
::load_userids_multiple
([ map { $_ => \
$u{$_} } @_ ]);
6724 # name: LJ::load_userids_multiple
6725 # des: Loads a number of users at once, efficiently.
6726 # info: loads a few users at once, their userids given in the keys of $map
6727 # listref (not hashref: can't have dups). values of $map listref are
6728 # scalar refs to put result in. $have is an optional listref of user
6729 # object caller already has, but is too lazy to sort by themselves.
6730 # <strong>Note</strong>: The $have parameter is deprecated,
6731 # as is $memcache_only; but it is still preserved for now.
6732 # Really, this whole API (i.e. LJ::load_userids_multiple) is clumsy.
6733 # Use [func[LJ::load_userids]] instead.
6734 # args: map, have, memcache_only?
6735 # des-map: Arrayref of pairs (userid, destination scalarref).
6736 # des-have: Arrayref of user objects caller already has.
6737 # des-memcache_only: Flag to only retrieve data from memcache.
6740 sub load_userids_multiple
{
6741 # the $have parameter is deprecated, as is $memcache_only, but it's still preserved for now.
6742 # actually this whole API is crap. use LJ::load_userids() instead.
6743 my ($map, undef, $memcache_only) = @_;
6749 my $id = shift @
$map;
6750 my $ref = shift @
$map;
6751 next unless int($id);
6752 push @
{$need{$id}}, $ref;
6754 if ($LJ::REQ_CACHE_USER_ID
{$id}) {
6755 push @have, $LJ::REQ_CACHE_USER_ID
{$id};
6761 return unless ref $u eq "LJ::User";
6763 # this could change the $u returned to an
6764 # existing one we already have loaded in memory,
6765 # once it's been upgraded. then everybody points
6767 $u = _set_u_req_cache
($u);
6769 foreach (@
{$need{$u->{'userid'}}}) {
6770 # check if existing target is defined and not what we already have.
6772 LJ
::assert_is
($u->{userid
}, $eu->{userid
});
6777 delete $need{$u->{'userid'}};
6780 unless ($LJ::_PRAGMA_FORCE_MASTER
) {
6781 foreach my $u (@have) {
6786 foreach (LJ
::memcache_get_u
(map { [$_,"userid:$_"] } keys %need)) {
6792 if (%need && ! $memcache_only) {
6793 my $db = @LJ::MEMCACHE_SERVERS
|| $LJ::_PRAGMA_FORCE_MASTER ?
6794 LJ
::get_db_writer
() : LJ
::get_db_reader
();
6796 _load_user_raw
($db, "userid", [ keys %need ], sub {
6798 LJ
::memcache_set_u
($u);
6805 # des-key: either "userid" or "user" (the WHERE part)
6806 # des-vals: value or arrayref of values for key to match on
6807 # des-hook: optional code ref to run for each $u
6808 # returns: last $u found
6811 my ($db, $key, $vals, $hook) = @_;
6813 $vals = [ $vals ] unless ref $vals eq "ARRAY";
6816 unless ($LJ::CACHE_NO_ISAM
{user
} || scalar(@
$vals) > 10) {
6817 eval { $db->do("HANDLER user OPEN"); };
6818 if ($@
|| $db->err) {
6819 $LJ::CACHE_NO_ISAM
{user
} = 1;
6828 $key = "PRIMARY" if $key eq "userid";
6829 foreach my $v (@
$vals) {
6830 my $sth = $db->prepare("HANDLER user READ `$key` = (?) LIMIT 1");
6832 my $row = $sth->fetchrow_hashref;
6834 my $u = LJ
::User
->new_from_row($row);
6839 $db->do("HANDLER user close");
6841 my $in = join(", ", map { $db->quote($_) } @
$vals);
6842 my $sth = $db->prepare("SELECT * FROM user WHERE $key IN ($in)");
6844 while (my $row = $sth->fetchrow_hashref) {
6845 my $u = LJ
::User
->new_from_row($row);
6854 sub _set_u_req_cache
{
6855 my $u = shift or die "no u to set";
6857 # if we have an existing user singleton, upgrade it with
6858 # the latested data, but keep using its address
6859 if (my $eu = $LJ::REQ_CACHE_USER_ID
{$u->{'userid'}}) {
6860 LJ
::assert_is
($eu->{userid
}, $u->{userid
});
6862 $eu->{$_} = $u->{$_} foreach keys %$u;
6865 $LJ::REQ_CACHE_USER_NAME
{$u->{'user'}} = $u;
6866 $LJ::REQ_CACHE_USER_ID
{$u->{'userid'}} = $u;
6870 sub load_user_or_identity
{
6873 my $user = LJ
::canonical_username
($arg);
6874 return LJ
::load_user
($user) if $user;
6876 # return undef if not dot in arg (can't be a URL)
6877 return undef unless $arg =~ /\./;
6879 my $dbh = LJ
::get_db_writer
();
6881 $url = "http://$url" unless $url =~ m!^http://!;
6882 $url .= "/" unless $url =~ m
!/$!;
6883 my $uid = $dbh->selectrow_array("SELECT userid FROM identitymap WHERE idtype=? AND identity=?",
6885 return LJ
::load_userid
($uid) if $uid;
6889 # load either a username, or a "I,<userid>" parameter.
6892 my $user = LJ
::canonical_username
($arg);
6893 return LJ
::load_user
($user) if length $user;
6894 if ($arg =~ /^I,(\d+)$/) {
6895 my $u = LJ
::load_userid
($1);
6896 return $u if $u->is_identity;
6902 # name: LJ::load_user
6903 # des: Loads a user record, from the [dbtable[user]] table, given a username.
6904 # args: user, force?
6905 # des-user: Username of user to load.
6906 # des-force: if set to true, won't return cached user object and will
6908 # returns: Hashref, with keys being columns of [dbtable[user]] table.
6911 my ($user, $force) = @_;
6913 $user = LJ
::canonical_username
($user);
6914 return undef unless length $user;
6916 my $get_user = sub {
6917 my $use_dbh = shift;
6918 my $db = $use_dbh ? LJ
::get_db_writer
() : LJ
::get_db_reader
();
6919 my $u = _load_user_raw
($db, "user", $user)
6922 # set caches since we got a u from the master
6923 LJ
::memcache_set_u
($u) if $use_dbh;
6925 return _set_u_req_cache
($u);
6928 # caller is forcing a master, return now
6929 return $get_user->("master") if $force || $LJ::_PRAGMA_FORCE_MASTER
;
6933 # return process cache if we have one
6934 if ($u = $LJ::REQ_CACHE_USER_NAME
{$user}) {
6941 if (exists $LJ::PRELOADED_USER_IDS
{$user} && !$LJ::IS_DEV_SERVER
) {
6942 $uid = $LJ::PRELOADED_USER_IDS
{$user};
6944 $uid = LJ
::MemCacheProxy
::get
("uidof:$user");
6947 $u = LJ
::memcache_get_u
([$uid, "userid:$uid"]) if $uid;
6948 return _set_u_req_cache
($u) if $u;
6951 # try to load from master if using memcache, otherwise from slave
6952 $u = $get_user->(scalar @LJ::MEMCACHE_SERVERS
);
6955 # setup LDAP handler if this is the first time
6956 if ($LJ::LDAP_HOST
&& ! $LJ::AUTH_EXISTS
) {
6958 $LJ::AUTH_EXISTS
= sub {
6960 my $rec = LJ
::LDAP
::load_ldap_user
($user);
6961 return $rec ?
$rec : undef;
6965 # if user doesn't exist in the LJ database, it's possible we're using
6966 # an external authentication source and we should create the account
6969 if (ref $LJ::AUTH_EXISTS
eq "CODE" && ($lu = $LJ::AUTH_EXISTS
->($user)))
6971 my $name = ref $lu eq "HASH" ?
($lu->{'nick'} || $lu->{name
} || $user) : $user;
6972 if (LJ
::create_account
({
6975 'email' => ref $lu eq "HASH" ?
$lu->email_raw : "",
6979 # this should pull from the master, since it was _just_ created
6980 return $get_user->("master");
6990 my %need = map {$_ => 1} @users;
6995 foreach my $user ( @users ) {
6996 if (my $u = $LJ::REQ_CACHE_USER_NAME
{$user}) {
6997 $loaded{$u->userid} = $u;
6998 delete $need{$u->userid};
7002 ## username to userid and load
7003 my $us = LJ
::load_userids
( LJ
::get_userid_multi
( [keys %need] ) );
7005 while ( my ($k, $v) = each %loaded ) {
7013 # name: LJ::u_equals
7014 # des: Compares two user objects to see if they are the same user.
7015 # args: userobj1, userobj2
7016 # des-userobj1: First user to compare.
7017 # des-userobj2: Second user to compare.
7018 # returns: Boolean, true if userobj1 and userobj2 are defined and have equal userids.
7022 return $u1 && $u2 && $u1->{'userid'} == $u2->{'userid'};
7026 # name: LJ::load_userid
7027 # des: Loads a user record, from the [dbtable[user]] table, given a userid.
7028 # args: userid, force?
7029 # des-userid: Userid of user to load.
7030 # des-force: if set to true, won't return cached user object and will
7032 # returns: Hashref with keys being columns of [dbtable[user]] table.
7035 my ($userid, $force) = @_;
7036 return undef unless $userid;
7038 my $get_user = sub {
7039 my $use_dbh = shift;
7040 my $db = $use_dbh ? LJ
::get_db_writer
() : LJ
::get_db_reader
();
7041 my $u = _load_user_raw
($db, "userid", $userid)
7044 LJ
::memcache_set_u
($u) if $use_dbh;
7045 return _set_u_req_cache
($u);
7048 # user is forcing master, return now
7049 return $get_user->("master") if $force || $LJ::_PRAGMA_FORCE_MASTER
;
7053 # check process cache
7054 $u = $LJ::REQ_CACHE_USER_ID
{$userid};
7060 $u = LJ
::memcache_get_u
([$userid,"userid:$userid"]);
7061 return _set_u_req_cache
($u) if $u;
7063 # get from master if using memcache
7064 return $get_user->("master") if @LJ::MEMCACHE_SERVERS
;
7070 # if we didn't get a u from the reader, fall back to master
7071 return $get_user->("master");
7078 my $users = LJ
::MemCacheProxy
::get_multi
(@keys) || {};
7079 while (my ($key, $ar) = each %$users) {
7080 my $row = LJ
::MemCache
::array_to_hash
("user", $ar, $key)
7082 my $u = LJ
::User
->new_from_row($row);
7085 return wantarray ?
@ret : $ret[0];
7092 my $expire = time() + 1800;
7093 my $ar = LJ
::MemCache
::hash_to_array
("user", $u);
7095 LJ
::MemCacheProxy
::set
([$u->{'userid'}, "userid:$u->{'userid'}"], $ar, $expire);
7096 LJ
::MemCacheProxy
::set
("uidof:$u->{user}", $u->{userid
});
7101 # des: gets a user bio, from DB or memcache.
7103 # des-force: true to get data from cluster master.
7107 my ($u, $force) = @_;
7108 return unless $u && $u->{'has_bio'} eq "Y";
7112 my $memkey = [$u->{'userid'}, "bio:$u->{'userid'}"];
7114 my $bio = LJ
::MemCache
::get
($memkey);
7115 return $bio if defined $bio;
7118 # not in memcache, fall back to disk
7119 my $db = @LJ::MEMCACHE_SERVERS
|| $force ?
7120 LJ
::get_cluster_def_reader
($u) : LJ
::get_cluster_reader
($u);
7121 $bio = $db->selectrow_array("SELECT bio FROM userbio WHERE userid=?",
7122 undef, $u->{'userid'});
7125 LJ
::MemCache
::add
($memkey, $bio);
7131 # name: LJ::journal_base
7132 # des: Returns URL of a user's journal.
7133 # info: The tricky thing is that users with underscores in their usernames
7134 # can't have some_user.example.com as a hostname, so that's changed into
7135 # some-user.example.com.
7136 # args: uuser, vhost?
7137 # des-uuser: LJ::User object, user hashref or username of user whose URL to make.
7138 # des-vhost: What type of URL. Acceptable options: "users", to make a
7139 # http://user.example.com/ URL; "tilde" for http://example.com/~user/;
7140 # "community" for http://example.com/community/user; or the default
7141 # will be http://example.com/users/user. If unspecified and uuser
7142 # is a user hashref, then the best/preferred vhost will be chosen.
7143 # returns: scalar; a URL.
7147 my ($user, $vhost) = @_;
7149 return unless $user;
7151 if (LJ
::are_hooks
("journal_base")) {
7152 ## We must pass a real LJ::User object into hook
7154 ## $user is either LJ::User object or plain hash with 'userid' field
7155 if (!UNIVERSAL
::isa
($user, "LJ::User")) {
7156 $user = LJ
::load_userid
($user->{userid
});
7159 ## $user is plain username
7160 $user = LJ
::load_user
($user);
7163 return $user->{'journal_base'}
7164 if $user->{'journal_base'};
7165 my $hookurl = LJ
::run_hook
("journal_base", $user, $vhost);
7166 $user->{'journal_base'} = $hookurl if (isu
($user) && $hookurl);
7167 return $hookurl if $hookurl;
7172 $user = $u->{'user'};
7173 unless (defined $vhost) {
7174 if ($LJ::FRONTPAGE_JOURNAL
eq $user) {
7176 } elsif ($u->{'journaltype'} eq "P") {
7183 if ($vhost eq "users") {
7184 my $he_user = $user;
7185 $he_user =~ s/_/-/g;
7186 return "http://$he_user.$LJ::USER_DOMAIN";
7187 } elsif ($vhost eq "tilde") {
7188 return "$LJ::SITEROOT/~$user";
7189 } elsif ($vhost eq "community") {
7190 return "$LJ::SITEROOT/community/$user";
7191 } elsif ($vhost eq "front") {
7192 return $LJ::SITEROOT
;
7193 } elsif ($vhost =~ /^other:(.+)/) {
7196 return "$LJ::SITEROOT/users/$user";
7202 # name: LJ::load_user_privs
7204 # des: loads all of the given privs for a given user into a hashref, inside
7205 # the user record. See also [func[LJ::check_priv]].
7206 # args: u, priv, arg?
7207 # des-priv: Priv names to load (see [dbtable[priv_list]]).
7208 # des-arg: Optional argument. See also [func[LJ::check_priv]].
7211 sub load_user_privs
{
7214 return unless $remote and @privs;
7216 # return if we've already loaded these privs for this user.
7217 @privs = grep { ! $remote->{'_privloaded'}->{$_} } @privs;
7218 return unless @privs;
7220 my $dbr = LJ
::get_db_reader
();
7222 foreach (@privs) { $remote->{'_privloaded'}->{$_}++; }
7223 @privs = map { $dbr->quote($_) } @privs;
7224 my $sth = $dbr->prepare("SELECT pl.privcode, pm.arg ".
7225 "FROM priv_map pm, priv_list pl ".
7226 "WHERE pm.prlid=pl.prlid AND ".
7227 "pl.privcode IN (" . join(',',@privs) . ") ".
7228 "AND pm.userid=$remote->{'userid'}");
7230 while (my ($priv, $arg) = $sth->fetchrow_array) {
7231 unless (defined $arg) { $arg = ""; } # NULL -> ""
7232 $remote->{'_priv'}->{$priv}->{$arg} = 1;
7237 # name: LJ::check_priv
7238 # des: Check to see if a user has a certain privilege.
7239 # info: Usually this is used to check the privs of a $remote user.
7240 # See [func[LJ::get_remote]]. As such, a $u argument of undef
7241 # is okay to pass: 0 will be returned, as an unknown user can't
7243 # args: u, priv, arg?
7244 # des-priv: Priv name to check for (see [dbtable[priv_list]])
7245 # des-arg: Optional argument. If defined, function only returns true
7246 # when $remote has a priv of type $priv also with arg $arg, not
7247 # just any priv of type $priv, which is the behavior without
7248 # an $arg. Arg can be "*", for all args.
7249 # returns: boolean; true if user has privilege
7252 my ($u, $priv, $arg) = @_;
7255 LJ
::run_hook
("update_counter", {
7256 counter
=> "check_priv",
7259 LJ
::load_user_privs
($u, $priv)
7260 unless $u->{'_privloaded'}->{$priv};
7262 # no access if they don't have the priv
7263 return 0 unless defined $u->{'_priv'}->{$priv};
7265 # at this point we know they have the priv
7266 return 1 unless defined $arg;
7268 # check if they have the right arguments
7269 return 1 if defined $u->{'_priv'}->{$priv}->{$arg};
7270 return 1 if defined $u->{'_priv'}->{$priv}->{"*"};
7272 # don't have the right argument
7279 # name: LJ::users_by_priv
7281 # des: Return users with a certain privilege.
7283 # des-args: user privilege to searching. arg can be "*" for all args.
7284 # return: Userids or empty list.
7285 # TODO Add store to MemCache
7287 my ($priv, $arg) = @_;
7289 my $dbr = LJ
::get_db_reader
();
7292 return unless $priv;
7294 my $users = $dbr->selectcol_arrayref ("SELECT userid FROM priv_list pl, priv_map pm
7295 WHERE pl.prlid = pm.prlid
7298 ", undef, $priv, $arg);
7300 return unless ref $users eq 'ARRAY';
7307 # name: LJ::remote_has_priv
7308 # des: Check to see if the given remote user has a certain privilege.
7310 sub remote_has_priv
{
7312 my $privcode = shift; # required. priv code to check for.
7313 my $ref = shift; # optional, arrayref or hashref to populate
7314 return 0 unless ($remote);
7316 ### authentication done. time to authorize...
7318 my $dbr = LJ
::get_db_reader
();
7319 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=?");
7320 $sth->execute($privcode, $remote->{'userid'});
7323 if (ref $ref eq "ARRAY") { @
$ref = (); }
7324 if (ref $ref eq "HASH") { %$ref = (); }
7325 while (my ($arg) = $sth->fetchrow_array) {
7327 if (ref $ref eq "ARRAY") { push @
$ref, $arg; }
7328 if (ref $ref eq "HASH") { $ref->{$arg} = 1; }
7333 # $dom: 'L' == log, 'T' == talk, 'M' == modlog, 'S' == session,
7334 # 'R' == memory (remembrance), 'K' == keyword id,
7335 # 'P' == phone post, 'C' == pending comment
7336 # 'O' == pOrtal box id, 'V' == 'vgift', 'E' == ESN subscription id
7337 # 'Q' == Notification Inbox, 'G' == 'SMS messaGe'
7338 # 'D' == 'moDule embed contents', 'W' == 'Wish-list element'
7339 # 'F' == Photo ID, 'A' == Album ID, 'Y' == delaYed entries
7340 # 'I' == Fotki migration log ID, 'H' == pics tag id
7342 # FIXME: both phonepost and vgift are ljcom. need hooks. but then also
7343 # need a separate namespace. perhaps a separate function/table?
7344 sub alloc_user_counter
7346 my ($u, $dom, $opts) = @_;
7349 ##################################################################
7350 # IF YOU UPDATE THIS MAKE SURE YOU ADD INITIALIZATION CODE BELOW #
7351 return undef unless $dom =~ /^[LTMPSRKCOVEQGDWFAYIH]$/; #
7352 ##################################################################
7354 my $dbh = LJ
::get_db_writer
();
7355 return undef unless $dbh;
7358 my $uid = $u->{'userid'}+0;
7359 return undef unless $uid;
7360 my $memkey = [$uid, "auc:$uid:$dom"];
7362 # in a master-master DB cluster we need to be careful that in
7363 # an automatic failover case where one cluster is slightly behind
7364 # that the same counter ID isn't handed out twice. use memcache
7365 # as a sanity check to record/check latest number handed out.
7366 my $memmax = int(LJ
::MemCache
::get
($memkey) || 0);
7368 my $rs = $dbh->do("UPDATE usercounter SET max=LAST_INSERT_ID(GREATEST(max,$memmax)+1) ".
7369 "WHERE journalid=? AND area=?", undef, $uid, $dom);
7371 $newmax = $dbh->selectrow_array("SELECT LAST_INSERT_ID()");
7373 # if we've got a supplied callback, lets check the counter
7374 # number for consistency. If it fails our test, wipe
7375 # the counter row and start over, initializing a new one.
7376 # callbacks should return true to signal 'all is well.'
7377 if ($opts->{callback
} && ref $opts->{callback
} eq 'CODE') {
7379 eval { $rv = $opts->{callback
}->($u, $newmax) };
7381 $dbh->do("DELETE FROM usercounter WHERE " .
7382 "journalid=? AND area=?", undef, $uid, $dom);
7383 return LJ
::alloc_user_counter
($u, $dom);
7387 LJ
::MemCache
::set
($memkey, $newmax);
7391 if ($opts->{recurse
}) {
7392 # We shouldn't ever get here if all is right with the world.
7398 'log' => "SELECT MAX(jitemid) FROM log2 WHERE journalid=?",
7399 'logtext' => "SELECT MAX(jitemid) FROM logtext2 WHERE journalid=?",
7400 'talk_nodeid' => "SELECT MAX(nodeid) FROM talk2 WHERE nodetype='L' AND journalid=?",
7402 'talk' => "SELECT MAX(jtalkid) FROM talk2 WHERE journalid=?",
7403 'talktext' => "SELECT MAX(jtalkid) FROM talktext2 WHERE journalid=?",
7406 my $consider = sub {
7408 foreach my $t (@tables) {
7409 my $res = $u->selectrow_array($qry_map->{$t}, undef, $uid);
7410 $newmax = $res if $res > $newmax;
7414 # Make sure the counter table is populated for this uid/dom.
7416 # back in the ol' days IDs were reused (because of MyISAM)
7417 # so now we're extra careful not to reuse a number that has
7418 # foreign junk "attached". turns out people like to delete
7419 # each entry by hand, but we do lazy deletes that are often
7420 # too lazy and a user can see old stuff come back alive
7421 $consider->("log", "logtext", "talk_nodeid");
7422 } elsif ($dom eq "T") {
7423 # just paranoia, not as bad as above. don't think we've ever
7424 # run into cases of talktext without a talk, but who knows.
7426 $consider->("talk", "talktext");
7427 } elsif ($dom eq "M") {
7428 $newmax = $u->selectrow_array("SELECT MAX(modid) FROM modlog WHERE journalid=?",
7430 } elsif ($dom eq "S") {
7431 $newmax = $u->selectrow_array("SELECT MAX(sessid) FROM sessions WHERE userid=?",
7433 } elsif ($dom eq "R") {
7434 $newmax = $u->selectrow_array("SELECT MAX(memid) FROM memorable2 WHERE userid=?",
7436 } elsif ($dom eq "K") {
7437 $newmax = $u->selectrow_array("SELECT MAX(kwid) FROM userkeywords WHERE userid=?",
7439 } elsif ($dom eq "P") {
7440 my $userblobmax = $u->selectrow_array("SELECT MAX(blobid) FROM userblob WHERE journalid=? AND domain=?",
7441 undef, $uid, LJ
::get_blob_domainid
("phonepost"));
7442 my $ppemax = $u->selectrow_array("SELECT MAX(blobid) FROM phonepostentry WHERE userid=?",
7444 $newmax = ($ppemax > $userblobmax) ?
$ppemax : $userblobmax;
7445 } elsif ($dom eq "C") {
7446 $newmax = $u->selectrow_array("SELECT MAX(pendcid) FROM pendcomments WHERE jid=?",
7448 } elsif ($dom eq "O") {
7449 $newmax = $u->selectrow_array("SELECT MAX(pboxid) FROM portal_config WHERE userid=?",
7451 } elsif ($dom eq "V") {
7452 $newmax = $u->selectrow_array("SELECT MAX(giftid) FROM vgifts WHERE userid=?",
7454 } elsif ($dom eq "E") {
7455 $newmax = $u->selectrow_array("SELECT MAX(subid) FROM subs WHERE userid=?",
7457 } elsif ($dom eq "Q") {
7458 $newmax = $u->selectrow_array("SELECT MAX(qid) FROM notifyqueue WHERE userid=?",
7460 } elsif ($dom eq "G") {
7461 $newmax = $u->selectrow_array("SELECT MAX(msgid) FROM sms_msg WHERE userid=?",
7463 } elsif ($dom eq "D") {
7464 $newmax = $u->selectrow_array("SELECT MAX(moduleid) FROM embedcontent WHERE userid=?",
7466 } elsif ($dom eq "W") {
7467 $newmax = $u->selectrow_array("SELECT MAX(wishid) FROM wishlist2 WHERE userid=?",
7469 } elsif ($dom eq "F") {
7470 $newmax = $u->selectrow_array("SELECT MAX(photo_id) FROM fotki_photos WHERE userid=?",
7472 } elsif ($dom eq "A") {
7473 $newmax = $u->selectrow_array("SELECT MAX(album_id) FROM fotki_albums WHERE userid=?",
7475 } elsif ($dom eq "Y") {
7476 $newmax = $u->selectrow_array("SELECT MAX(delayedid) FROM delayedlog2 WHERE journalid=?",
7478 } elsif ( $dom eq 'I' ) {
7479 $newmax = $u->selectrow_array("SELECT MAX(logid) FROM fotki_migration_log WHERE userid=?",
7481 } elsif ( $dom eq 'H' ) {
7482 $newmax = $u->selectrow_array("SELECT MAX(tag_id) FROM fotki_tags WHERE userid=?",
7485 die "No user counter initializer defined for area '$dom'.\n";
7488 $dbh->do("INSERT IGNORE INTO usercounter (journalid, area, max) VALUES (?,?,?)",
7489 undef, $uid, $dom, $newmax) or return undef;
7491 # The 2nd invocation of the alloc_user_counter sub should do the
7492 # intended incrementing.
7493 return LJ
::alloc_user_counter
($u, $dom, { recurse
=> 1 });
7497 # name: LJ::make_user_active
7498 # des: Record user activity per cluster, on [dbtable[clustertrack2]], to
7499 # make per-activity cluster stats easier.
7500 # args: userid, type
7501 # des-userid: source userobj ref
7502 # des-type: currently unused
7504 sub mark_user_active
{
7505 my ($u, $type) = @_; # not currently using type
7506 return 0 unless $u; # do not auto-vivify $u
7507 my $uid = $u->{userid
};
7508 return 0 unless $uid && $u->{clusterid
};
7510 # Update the clustertrack2 table, but not if we've done it for this
7511 # user in the last hour. if no memcache servers are configured
7512 # we don't do the optimization and just always log the activity info
7513 if (@LJ::MEMCACHE_SERVERS
== 0 ||
7514 LJ
::MemCache
::add
("rate:tracked:$uid", 1, 3600)) {
7516 return 0 unless $u->writer;
7517 $u->do("REPLACE INTO clustertrack2 SET ".
7518 "userid=?, timeactive=?, clusterid=?", undef,
7519 $uid, time(), $u->{clusterid
}) or return 0;
7525 # name: LJ::get_shared_journals
7526 # des: Gets an array of shared journals a user has access to.
7527 # returns: An array of shared journals.
7530 sub get_shared_journals
7533 my $ids = LJ
::load_rel_target
($u, 'A') || [];
7535 # have to get usernames;
7537 LJ
::load_userids_multiple
([ map { $_, \
$users{$_} } @
$ids ], [$u]);
7538 return sort map { $_->{'user'} } values %users;
7541 ## my $text = LJ::ljuser_alias($u)
7542 ## returns note text (former 'alias') for current remote user
7545 return if $LJ::DISABLED
{'aliases'};
7549 $remote = LJ
::get_remote
() unless isu
($remote);
7551 return unless $remote;
7552 return unless $remote->get_cap('aliases');
7554 my $u = LJ
::load_user
($user);
7557 if (!$remote->{_aliases
}) {
7558 my $prop_aliases = LJ
::text_uncompress
( $remote->prop('aliases') );
7559 $remote->{_aliases
} = ($prop_aliases) ? LJ
::JSON
->from_json($prop_aliases) : {};
7561 return $remote->{_aliases
}->{ $u->{userid
} };
7565 ## LJ::set_alias($u, $text, \$error)
7566 ## LJ::set_alias([ $u1, $text1, $u2, $text2], \$error);
7568 ## Sets notes (alias) text for user $u to the current $remote user
7569 ## $u is either user object or userid (number)
7570 ## If aliases cannot be updated, undef value is returned and optional \$error reference is set
7571 ## Use empty text for deleting alias
7574 my $list = (ref $_[0] eq 'ARRAY') ?
shift : [shift, shift];
7577 if ($LJ::DISABLED
{'aliases'}) {
7578 $$err = "Notes (aliases) are disabled" if $err;
7582 my $remote = LJ
::get_remote
();
7584 $$err = "No remote user" if $err;
7587 unless ($remote->get_cap('aliases')) {
7588 $$err = "Remote user can't manage notes (aliases)" if $err;
7593 if (!$remote->{_aliases
}) {
7594 my $prop_aliases = LJ
::text_uncompress
( $remote->prop('aliases') );
7595 $remote->{_aliases
} = $prop_aliases ? LJ
::JSON
->from_json($prop_aliases) : {};
7598 ## modify (edit, add or delete)
7599 for (my $i = 0; $i < @
$list / 2; ++$i) {
7600 my $userid = $list->[$i * 2];
7601 my $alias = $list->[$i * 2 + 1];
7602 $alias = substr($alias, 0, 400);
7603 $userid = $userid->{userid
} if ref $userid;
7604 die "Numeric id is expected, not $userid" unless $userid =~ /^\d+$/;
7607 $remote->{_aliases
}->{$userid} = $alias;
7609 delete $remote->{_aliases
}->{$userid};
7614 my $serialized_text = LJ
::JSON
->to_json($remote->{_aliases
});
7615 $serialized_text = LJ
::text_compress
( $serialized_text ) unless $LJ::DISABLED
{'aliases_compress'};
7616 if (length $serialized_text < 65536) {
7617 return $remote->set_prop( aliases
=> $serialized_text );
7619 delete $remote->{_aliases
}; ## drop unsuccessfully modified data
7620 $$err = BML
::ml
('widget.addalias.too.long') if $err;
7625 ## my %all_aliases = LJ::get_all_aliases();
7626 ## Returns all aliases for current remote user as hash userid => alias
7627 sub get_all_aliases
{
7629 return if $LJ::DISABLED
{'aliases'};
7632 $remote = LJ
::get_remote
() unless isu
($remote);
7633 return unless $remote and $remote->get_cap('aliases');
7635 if (!$remote->{_aliases
}) {
7636 my $prop_aliases = LJ
::text_uncompress
($remote->prop('aliases'));
7637 $remote->{_aliases
} = ($prop_aliases) ? LJ
::JSON
->from_json($prop_aliases) : {};
7640 return %{$remote->{_aliases
}};
7646 # des: Make link to userinfo/journal of user.
7647 # info: Returns the HTML for a userinfo/journal link pair for a given user
7648 # name, just like LJUSER does in BML. This is for files like cleanhtml.pl
7649 # and ljpoll.pl which need this functionality too, but they aren't run as BML.
7651 # des-user: Username to link to, or user hashref.
7652 # des-opts: Optional hashref to control output. Key 'full' when true causes
7653 # a link to the mode=full userinfo. Key 'type' when 'C' makes
7654 # a community link, when 'Y' makes a syndicated account link,
7655 # when 'I' makes an identity account link (e.g. OpenID),
7656 # when 'N' makes a news account link, otherwise makes a user account
7657 # link. If user parameter is a hashref, its 'journaltype' overrides
7658 # this 'type'. Key 'del', when true, makes a tag for a deleted user.
7659 # If user parameter is a hashref, its 'statusvis' overrides 'del'.
7660 # Key 'no_follow', when true, disables traversal of renamed users.
7661 # returns: HTML with a little head image & bold text link.
7664 my $ljuser_tmpl_path = join('/', $ENV{'LJHOME'}, 'templates', 'User');
7665 my $ljuser_cache = {};
7668 my ($user, $opts) = @_;
7669 my ($u, $username, $journal_url, $striked);
7670 my ($journal_name, $journal, $userhead);
7671 my ($attrs, $color, $user_alias, %user);
7677 $username = $u->username;
7679 $u = LJ
::load_user
($user);
7684 if ( $u and LJ
::isu
($u) ) {
7685 # Traverse the renames to the final journal
7686 unless ( $opts->{'no_follow'} ) {
7687 $u = $u->get_renamed_user;
7688 $username = $u->username;
7691 last if $ljuser_cache->{$username};
7693 # Mark accounts as deleted that aren't visible, memorial, locked, or
7695 if ( $u->statusvis !~ m![VMLO]! ) {
7699 $journal_name = $username;
7700 $journal_url = $u->journal_base . "/";
7701 ($userhead) = $u->userhead($opts);
7704 if ( $u->is_identity ) {
7705 $identity = $u->identity;
7706 my $params = $identity ?
$identity->ljuser_display_params($u, $opts) : {};
7707 $profile_url = $params->{'profile_url'} || '';
7708 $journal_url = $params->{'journal_url'} || $journal_url;
7709 $journal_name = $params->{'journal_name'} || $journal_name;
7712 $profile_url = $u->profile_url();
7714 $username = LJ
::canonical_username
($username);
7716 last if $ljuser_cache->{$username};
7718 $journal_url = join('', $LJ::SITEROOT
, '/userinfo.bml?user=', $username);
7719 $profile_url ||= $journal_url;
7720 $userhead = 'userinfo.gif?v=17080';
7723 LJ
::run_hooks
( 'override_display_name', $u, \
$journal_name );
7724 LJ
::run_hooks
( 'override_profile_url', $u, \
$profile_url );
7725 LJ
::run_hooks
( 'override_journal_url', $u, \
$journal_url );
7728 if ( $color = $opts->{'link_color'} ) {
7729 unless ( $color =~ /^#(?:[a-f0-9]{3}|[a-f0-9]{6})$/i ) {
7734 %user = %{ $ljuser_cache->{$username} ||= {
7739 username
=> $username,
7740 journal
=> $journal_name,
7741 striked
=> $striked,
7742 journal_url
=> $journal_url,
7743 profile_url
=> $profile_url,
7744 userhead_url
=> $userhead,
7746 is_identity
=> $identity?
1 : 0,
7749 $user{'noctxpopup'} = 1 if $opts->{'noctxpopup'};
7750 $user{'bold'} = 1 if $opts->{'bold'} or not exists $opts->{'bold'};
7751 $user{'inline_css'} = 1 if $opts->{'inline_css'};
7752 $user{'journal'} = $opts->{'title'} if $opts->{'title'};
7753 $user{'target'} = $opts->{'target'} if $opts->{'target'};
7754 $user{'profile_url'} .= '?mode=full' if $opts->{'full'};
7755 $user{'profile_url'} = $opts->{'profile_url'} if $opts->{'profile_url'};
7756 $user{'user_alias'} = LJ
::ehtml
(LJ
::ljuser_alias
($username));
7757 $user{'alias'} = $user{'user_alias'}?
1 : 0;
7758 $user{'color'} = $color;
7760 if ( $opts->{'side_alias'} and $user{'alias'} ) {
7761 $user{'side_alias'} = 1;
7766 unless ( $user{'userhead_url'} =~ m!^https?:\/\/! ) {
7767 $user{'userhead_url'} = join('',
7768 $opts->{'imgroot'} || $LJ::IMGPREFIX
,
7769 '/', $user{'userhead_url'},
7770 '?v=', $LJ::CURRENT_VERSION
7774 # FIXME: try to remove this
7775 if ( $opts->{'in_journal'} ) {
7776 my $cu = LJ
::load_user
($opts->{'in_journal'});
7778 $user{'attrs'} = join('"', 'data-journal=', $cu->journal_base, '');
7782 if ( $opts->{'raw'} ) {
7785 return LJ
::Response
::CachedTemplate
->new(
7786 path
=> $ljuser_tmpl_path,
7787 file
=> 'Display.tmpl',
7794 my ($userid, $email) = @_;
7796 my $dbh = LJ
::get_db_writer
();
7797 if ($LJ::DEBUG
{'write_emails_to_user_table'}) {
7798 $dbh->do("UPDATE user SET email=? WHERE userid=?", undef,
7801 $dbh->do("REPLACE INTO email (userid, email) VALUES (?, ?)",
7802 undef, $userid, $email);
7805 LJ
::memcache_kill
($userid, "userid");
7806 LJ
::MemCache
::delete([$userid, "email:$userid"]);
7807 my $cache = $LJ::REQ_CACHE_USER_ID
{$userid} or return;
7808 $cache->{'_email'} = $email;
7812 my @friends_names = @_;
7814 push @ret, grep { $_ } map { LJ
::load_user
($_) } @friends_names;
7819 my ($userid, $password) = @_;
7821 my $dbh = LJ
::get_db_writer
();
7822 if ($LJ::DEBUG
{'write_passwords_to_user_table'}) {
7823 $dbh->do("UPDATE user SET password=? WHERE userid=?", undef,
7824 $password, $userid);
7826 $dbh->do("REPLACE INTO password (userid, password) VALUES (?, ?)",
7827 undef, $userid, $password);
7830 LJ
::memcache_kill
($userid, "userid");
7831 LJ
::MemCache
::delete([$userid, "pw:$userid"]);
7832 my $cache = $LJ::REQ_CACHE_USER_ID
{$userid} or return;
7833 $cache->{'_password'} = $password;
7838 my ($arg, $ref) = @_;
7841 if (ref $arg eq "ARRAY") {
7844 @uid = want_userid
($arg);
7846 @uid = grep { $_ } map { $_ + 0 } @uid;
7847 return 0 unless @uid;
7852 while (my ($k, $v) = each %$ref) {
7856 } elsif ($k eq 'email') {
7857 set_email
($_, $v) foreach @uid;
7858 } elsif ($k eq 'password') {
7859 set_password
($_, $v) foreach @uid;
7862 push @bindparams, $v;
7865 return 1 unless @sets;
7866 my $dbh = LJ
::get_db_writer
();
7867 return 0 unless $dbh;
7870 my $where = @uid == 1 ? "userid
=$uid[0]" : "userid IN
(@uid)";
7871 $dbh->do("UPDATE user SET
@sets WHERE
$where", undef,
7873 return 0 if $dbh->err;
7875 if (@LJ::MEMCACHE_SERVERS) {
7876 LJ::memcache_kill($_, "userid
") foreach @uid;
7880 # for a load of userids from the master after update
7881 # so we pick up the values set via the 'raw' option
7882 require_master(sub { LJ::load_userids(@uid) });
7884 foreach my $uid (@uid) {
7885 while (my ($k, $v) = each %$ref) {
7886 my $cache = $LJ::REQ_CACHE_USER_ID{$uid} or next;
7893 LJ::run_hooks("update_user
", userid => $_, fields => $ref)
7900 # name: LJ::get_timezone
7901 # des: Gets the timezone offset for the user.
7902 # args: u, offsetref, fakedref
7903 # des-u: user object.
7904 # des-offsetref: reference to scalar to hold timezone offset;
7905 # des-fakedref: reference to scalar to hold whether this timezone was
7906 # faked. 0 if it is the timezone specified by the user.
7907 # returns: nonzero if successful.
7910 my ($u, $offsetref, $fakedref) = @_;
7912 # See if the user specified their timezone
7913 if (my $tz = $u->prop('timezone')) {
7914 # If the eval fails, we'll fall through to guessing instead
7916 DateTime->from_epoch(
7923 $$offsetref = $dt->offset() / (60 * 60); # Convert from seconds to hours
7924 $$fakedref = 0 if $fakedref;
7930 # Either the user hasn't set a timezone or we failed at
7931 # loading it. We guess their current timezone's offset
7932 # by comparing the gmtime of their last post with the time
7933 # they specified on that post.
7935 # first, check request cache
7936 my $timezone = $u->{_timezone_guess};
7938 $$offsetref = $timezone;
7942 # next, check memcache
7943 my $memkey = [$u->userid, 'timezone_guess:' . $u->userid];
7944 my $memcache_data = LJ::MemCacheProxy::get($memkey);
7945 if ($memcache_data) {
7946 # fill the request cache since it was empty
7947 $u->{_timezone_guess} = $memcache_data;
7948 $$offsetref = $memcache_data;
7952 # nothing in cache; check db
7953 my $dbcr = LJ::get_cluster_def_reader($u);
7954 return 0 unless $dbcr;
7956 $$fakedref = 1 if $fakedref;
7958 # grab the times on the last post that wasn't backdated.
7959 # (backdated is rlogtime == $LJ::EndOfTime)
7960 if (my $last_row = $dbcr->selectrow_hashref(
7962 SELECT rlogtime, eventtime
7964 WHERE journalid = ? AND rlogtime <> ?
7965 ORDER BY rlogtime LIMIT 1
7966 }, undef, $u->{userid}, $LJ::EndOfTime)) {
7967 my $logtime = $LJ::EndOfTime - $last_row->{'rlogtime'};
7968 my $eventtime = LJ::TimeUtil->mysqldate_to_time($last_row->{'eventtime'}, 1);
7969 my $hourdiff = ($eventtime - $logtime) / 3600;
7971 # if they're up to a quarter hour behind, round up.
7972 $hourdiff = $hourdiff > 0 ? int($hourdiff + 0.25) : int($hourdiff - 0.25);
7974 # if the offset is more than 24h in either direction, then the last
7975 # entry is probably unreliable. don't use any offset at all.
7976 $$offsetref = (-24 < $hourdiff && $hourdiff < 24) ? $hourdiff : 0;
7979 $u->{_timezone_guess} = $$offsetref;
7980 my $expire = 60*60*24; # 24 hours
7981 LJ::MemCacheProxy::set($memkey, $$offsetref, $expire);
7987 # returns undef on error, or otherwise arrayref of arrayrefs,
7988 # each of format [ year, month, day, count ] for all days with
7989 # non-zero count. examples:
7990 # [ [ 2003, 6, 5, 3 ], [ 2003, 6, 8, 4 ], ... ]
7993 my ( $u, $remote ) = @_;
7995 # ['public'], ['all'], or [ 'gmask', $gmask ]
7999 if ( LJ::is_web_context() && LJ::Request->get_param('viewall') &&
8000 LJ::check_priv( $remote, 'canview', '*' ) )
8003 LJ::statushistory_add($u->{'userid'}, $remote->{'userid'},
8004 "viewall
", "calendar
");
8007 if ( $remote->can_manage($u) ) {
8010 if ( my $gmask = LJ::get_groupmask($u, $remote) ) {
8011 # friends case: allowmask == gmask == 1
8012 $kind = [ 'gmask', $gmask ];
8022 ## the first element of the array stored in memcache
8023 ## is the time of the creation of the list. The memcache is
8024 ## invalid if there are new entries in journal since that time.
8026 my $memkey = [ $u->userid, join( ':', 'dayct3', $u->userid, @$kind ) ];
8027 my $list = LJ::MemCache::get($memkey);
8029 my $list_create_time = shift @$list;
8030 return $list if $list_create_time >= $u->timeupdate;
8033 my $dbcr = LJ::get_cluster_def_reader($u) or return;
8035 ## get lock to prevent multiple apache processes to execute the sql below.
8036 ## one process runs, the other wait for results
8037 my $release_lock = sub {
8038 $dbcr->do( 'SELECT RELEASE_LOCK(?)', undef, $memkey->[1] );
8041 my ($locked) = $dbcr->selectrow_array(
8042 'SELECT GET_LOCK(?,2)', undef, $memkey->[1] );
8044 return [] unless $locked; ## 2 seconds expired
8046 $list = LJ::MemCache::get($memkey);
8048 ## other process may have filled the data while we waited for the lock
8049 my $list_create_time = shift @$list;
8050 if ($list_create_time >= $u->timeupdate) {
8056 if ( LJ::is_enabled( 'dayct_month', $u ) ) {
8058 my ( $min_year, $max_year ) = $dbcr->selectrow_array(
8059 'SELECT MIN(year), MAX(year) FROM log2 WHERE journalid=?',
8060 undef, $u->userid );
8064 foreach my $year ( $min_year .. $max_year ) {
8065 foreach my $month ( 1 .. 12 ) {
8066 my $month_daycounts =
8067 get_month_daycounts( $u, $kind, $year, $month );
8068 push @$days, @$month_daycounts;
8072 LJ::MemCache::set( $memkey, [ time, @$days ] );
8076 my ( $selecttype, $gmask ) = @$kind;
8078 if ( $selecttype eq 'all' ) {
8080 } elsif ( $selecttype eq 'public' ) {
8081 $secwhere = 'AND security="public
"';
8082 } elsif ( $selecttype eq 'gmask' ) {
8083 $secwhere = "AND
( security
='public' OR
" .
8084 "(security
='usemask' AND allowmask
& $gmask) )";
8087 my $sth = $dbcr->prepare("SELECT year
, month
, day
, COUNT
(*) ".
8088 "FROM log2 WHERE journalid
=?
$secwhere " .
8089 "GROUP BY year
, month
, day
");
8090 $sth->execute( $u->userid );
8092 while ( my ( $y, $m, $d, $c ) = $sth->fetchrow_array ) {
8093 # we force each number from string scalars (from DBI) to int scalars,
8094 # so they store smaller in memcache
8095 push @$days, [ int($y), int($m), int($d), int($c) ];
8098 LJ::MemCache::set( $memkey, [time, @$days] );
8104 sub get_month_daycounts {
8105 my ( $u, $kind, $year, $month ) = @_;
8107 my $memkind = join( ':', @$kind );
8110 ## the first element of the array stored in memcache
8111 ## is the time of the creation of the list. The memcache is
8112 ## invalid if there are new entries in journal since that time.
8115 join( ':', 'dayct3', 'month', $year, $month, $u->userid, @$kind );
8116 my $memkey = [ $u->userid, $memkey_base ];
8117 my $memlockkey = [ $u->userid, $memkey_base . ':lock' ];
8119 my $lock_acquired = 0;
8122 return if $lock_acquired;
8125 return LJ::MemCache::add( $memlockkey, 1, 2 );
8129 LJ::MemCache::delete($memlockkey);
8133 my $list = LJ::MemCache::get($memkey);
8135 my $list_create_time = shift @$list;
8136 my $list_exptime = shift @$list;
8138 my $need_recalculate = 0;
8140 my $timeupdate = $u->timeupdate;
8141 my $timeupdate_year = ( gmtime $timeupdate )[5] + 1900;
8142 my $timeupdate_month = ( gmtime $timeupdate )[4] + 1;
8144 $need_recalculate = 1
8145 if $timeupdate_year == $year &&
8146 $timeupdate_month == $month &&
8147 $u->timeupdate > $list_create_time;
8149 if ($need_recalculate) {
8150 $need_recalculate = 0 unless $lock->();
8153 return $list unless $need_recalculate;
8156 return [] unless $lock->();
8158 my ( $selecttype, $gmask ) = @$kind;
8160 if ( $selecttype eq 'all' ) {
8162 } elsif ( $selecttype eq 'public' ) {
8163 $secwhere = 'AND security="public
"';
8164 } elsif ( $selecttype eq 'gmask' ) {
8165 $secwhere = "AND
( security
='public' OR
" .
8166 "(security
='usemask' AND allowmask
& $gmask) )";
8169 my $dbcr = LJ::get_cluster_def_reader($u);
8171 my $sth = $dbcr->prepare("SELECT day
, COUNT
(*) ".
8172 "FROM log2 WHERE journalid
=?
$secwhere AND
" .
8173 "year
=? AND month
=?
" .
8175 $sth->execute( $u->userid, $year, $month );
8177 while ( my ( $d, $c ) = $sth->fetchrow_array ) {
8178 # we force each number from string scalars (from DBI) to int scalars,
8179 # so they store smaller in memcache
8180 push @$days, [ int($year), int($month), int($d), int($c) ];
8183 my $exptime = time + 3600 + int( rand(3600) );
8184 LJ::MemCache::set( $memkey, [ time, $exptime, @$days ] );
8190 ## input: $u, $remote, $year, $month
8191 ## output: hashref with data for rendering calendar for given month,
8192 ## days: arrayref [ count of entries for each day]
8193 ## days[1] = count of entries for the 1st day, days[0] is always null
8194 ## prev_month: arrayref [year, month] - previous month that has entries
8195 ## next_month, prev_year, next_year - arrayref of the same format
8197 sub get_calendar_data_for_month {
8198 my ($u, $remote, $year, $month) = @_;
8200 $remote ||= LJ::get_remote();
8201 unless ($year || $month) {
8202 ($month, $year) = (localtime)[4, 5];
8207 my %ret = (journal => $u->user, year => $year, month => $month);
8208 my $days = LJ::get_daycounts($u, $remote);
8209 foreach my $d (@$days) {
8210 ## @$d = ($y, $m, $d, $count)
8211 if ($d->[0]==$year && $d->[1]==$month) {
8212 $ret{days}->[ $d->[2] ] = $d->[3]+0;
8215 ## $prev_month = max( grep { $day < Date($year, $month) } @$days );
8216 ## max @list = List::Util::reduce { ($a < $b) ? $b : $a } @list
8217 ## min @list = List::Util::reduce { !($a < $b) ? $b : $a } @list
8218 my $current_month = [$year, $month];
8219 my $less_year = sub { my ($a, $b) = @_; return $a->[0]<$b->[0]; };
8220 my $less = sub { my ($a, $b) = @_; return $a->[0]<$b->[0] || $a->[0]==$b->[0] && $a->[1]<$b->[1] };
8221 $ret{'prev_month'} = List::Util::reduce { $less->($a, $b) ? $b : $a } grep { $less->($_, $current_month) } @$days;
8222 $ret{'next_month'} = List::Util::reduce { !$less->($a, $b) ? $b : $a } grep { $less->($current_month, $_) } @$days;
8223 $ret{'prev_year'} = List::Util::reduce { $less->($a, $b) ? $b : $a } grep { $less_year->($_, $current_month) } @$days;
8224 $ret{'next_year'} = List::Util::reduce { !$less->($a, $b) ? $b : $a } grep { $less_year->($current_month, $_) } @$days;
8225 foreach my $k (qw/prev_month next_month prev_year next_year/) {
8227 $ret{$k} = [ $ret{$k}->[0]+0, $ret{$k}->[1]+0];
8236 # name: LJ::set_interests
8237 # des: Change a user's interests.
8239 # des-old: hashref of old interests (hashing being interest => intid)
8240 # des-new: listref of new interests
8241 # returns: 1 on success, undef on failure
8244 my ($u, $old, $new) = @_;
8246 $u = LJ::want_user($u);
8247 my $userid = $u->{'userid'};
8248 return undef unless $userid;
8250 return undef unless ref $old eq 'HASH';
8251 return undef unless ref $new eq 'ARRAY';
8253 my $dbh = LJ::get_db_writer();
8255 my %int_del = %$old; # assume deleting everything, unless in @$new
8257 # user interests go in a different table than user interests,
8258 # though the schemas are the same so we can run the same queries on them
8259 my $uitable = $u->{'journaltype'} eq 'C' ? 'comminterests' : 'userinterests';
8261 # track if we made changes to refresh memcache later.
8264 my @valid_ints = LJ::validate_interest_list(@$new);
8265 foreach my $int (@valid_ints)
8267 $int_new{$int} = 1 unless $old->{$int};
8268 delete $int_del{$int};
8271 ### were interests removed?
8274 ## easy, we know their IDs, so delete them en masse
8275 my $intid_in = join(", ", values %int_del);
8276 $dbh->do("DELETE FROM
$uitable WHERE userid
=$userid AND intid IN
($intid_in)");
8277 $dbh->do("UPDATE interests SET intcount
=intcount
-1 WHERE intid IN
($intid_in) AND intcount
> 0");
8281 ### do we have new interests to add?
8282 my @new_intids = (); ## existing IDs we'll add for this user
8287 ## difficult, have to find intids of interests, and create new ints for interests
8288 ## that nobody has ever entered before
8289 my $int_in = join(", ", map { $dbh->quote($_); } keys %int_new);
8292 ## find existing IDs
8293 my $sth = $dbh->prepare("SELECT interest
, intid FROM interests WHERE interest IN
($int_in)");
8295 while (my ($intr, $intid) = $sth->fetchrow_array) {
8296 push @new_intids, $intid; # - we'll add this later.
8297 delete $int_new{$intr}; # - so we don't have to make a new intid for
8303 foreach my $newid (@new_intids) {
8304 if ($sql) { $sql .= ", "; }
8305 else { $sql = "REPLACE INTO
$uitable (userid
, intid
) VALUES
"; }
8306 $sql .= "($userid, $newid)";
8310 my $intid_in = join(", ", @new_intids);
8311 $dbh->do("UPDATE interests SET intcount
=intcount
+1 WHERE intid IN
($intid_in)");
8315 ### do we STILL have interests to add? (must make new intids)
8318 foreach my $int (keys %int_new)
8321 my $qint = $dbh->quote($int);
8323 $dbh->do("INSERT INTO interests
(intid
, intcount
, interest
) ".
8324 "VALUES
(NULL
, 1, $qint)");
8326 # somebody beat us to creating it. find its id.
8327 $intid = $dbh->selectrow_array("SELECT intid FROM interests WHERE interest
=$qint");
8328 $dbh->do("UPDATE interests SET intcount
=intcount
+1 WHERE intid
=$intid");
8331 $intid = $dbh->{'mysql_insertid'};
8334 ## now we can actually insert it into the userinterests table:
8335 $dbh->do("INSERT INTO
$uitable (userid
, intid
) ".
8336 "VALUES
($userid, $intid)");
8337 push @new_intids, $intid;
8341 LJ::run_hooks("set_interests
", $u, \%int_del, \@new_intids); # interest => intid
8343 # do migrations to clean up userinterests vs comminterests conflicts
8344 $u->lazy_interests_cleanup;
8346 LJ::memcache_kill($u, "intids
") if $did_mod;
8347 $u->{_cache_interests} = undef if $did_mod;
8352 sub validate_interest_list {
8353 my $interrors = ref $_[0] eq "ARRAY
" ? shift : [];
8356 my @valid_ints = ();
8357 foreach my $int (@ints) {
8358 $int = lc($int); # FIXME: use utf8?
8359 $int =~ s/^i like //; # *sigh*
8362 # Specific interest failures
8363 my ($bytes,$chars) = LJ::text_length($int);
8364 my @words = split(/\s+/, $int);
8365 my $word_ct = scalar @words;
8367 my $error_string = '';
8368 if ($int =~ /[\<\>]/) {
8369 $int = LJ::ehtml($int);
8370 $error_string .= '.invalid';
8372 $error_string .= '.bytes' if $bytes > LJ::BMAX_INTEREST;
8373 $error_string .= '.chars' if $chars > LJ::CMAX_INTEREST;
8374 $error_string .= '.words' if $word_ct > 4;
8377 if ($error_string) {
8378 $error_string = "error
.interest
$error_string";
8379 push @$interrors, [ $error_string,
8382 bytes_max => LJ::BMAX_INTEREST,
8384 chars_max => LJ::CMAX_INTEREST,
8391 push @valid_ints, $int;
8395 sub interest_string_to_list {
8398 $intstr =~ s/^\s+//; # strip leading space
8399 $intstr =~ s/\s+$//; # strip trailing space
8400 $intstr =~ s/\n/,/g; # newlines become commas
8401 $intstr =~ s/\s+/ /g; # strip duplicate spaces from the interest
8403 # final list is ,-sep
8404 return grep { length } split (/\s*,\s*/, $intstr);
8407 # $opts is optional, with keys:
8408 # forceids => 1 : don't use memcache for loading the intids
8409 # forceints => 1 : don't use memcache for loading the interest rows
8410 # justids => 1 : return arrayref of intids only, not names/counts
8411 # returns otherwise an arrayref of interest rows, sorted by interest name
8414 my ($u, $opts) = @_;
8416 return undef unless $u;
8418 # first check request cache inside $u
8419 if (my $ints = $u->{_cache_interests}) {
8420 if ($opts->{justids}) {
8421 return [ map { $_->[0] } @$ints ];
8426 my $uid = $u->{userid};
8427 my $uitable = $u->{'journaltype'} eq 'C' ? 'comminterests' : 'userinterests';
8431 my $mk_ids = [$uid, "intids
:$uid"];
8432 $ids = LJ::MemCache::get($mk_ids) unless $opts->{'forceids'};
8433 unless ($ids && ref $ids eq "ARRAY
") {
8435 my $dbh = LJ::get_db_writer();
8436 my $sth = $dbh->prepare("SELECT intid FROM
$uitable WHERE userid
=?
");
8437 $sth->execute($uid);
8438 push @$ids, $_ while ($_) = $sth->fetchrow_array;
8439 LJ::MemCache::add($mk_ids, $ids);
8442 # FIXME: set a 'justids' $u cache key in this case, then only return that
8443 # later if 'justids' is requested? probably not worth it.
8444 return $ids if $opts->{'justids'};
8446 # load interest rows
8448 $need{$_} = 1 foreach @$ids;
8451 unless ($opts->{'forceints'}) {
8452 if (my $mc = LJ::MemCache::get_multi(map { [$_, "introw
:$_"] } @$ids)) {
8453 while (my ($k, $v) = each %$mc) {
8454 next unless $k =~ /^introw:(\d+)/;
8462 my $ids = join(",", map { $_+0 } keys %need);
8463 my $dbr = LJ::get_db_reader();
8464 my $sth = $dbr->prepare("SELECT intid
, interest
, intcount FROM interests
".
8465 "WHERE intid IN
($ids)");
8468 while (my ($intid, $int, $count) = $sth->fetchrow_array) {
8469 # minimize latency... only store 25 into memcache at a time
8470 # (too bad we don't have set_multi.... hmmmm)
8471 my $aref = [$intid, $int, $count];
8472 if ($memc_store++ < 25) {
8473 # if the count is fairly high, keep item in memcache longer,
8474 # since count's not so important.
8475 my $expire = $count < 10 ? 3600*12 : 3600*48;
8476 LJ::MemCache::add([$intid, "introw
:$intid"], $aref, $expire);
8482 @ret = sort { $a->[1] cmp $b->[1] } @ret;
8483 return $u->{_cache_interests} = \@ret;
8487 # name: LJ::modify_caps
8488 # des: Given a list of caps to add and caps to remove, updates a user's caps.
8489 # args: uuid, cap_add, cap_del, res
8490 # des-cap_add: arrayref of bit numbers to turn on
8491 # des-cap_del: arrayref of bit numbers to turn off
8492 # des-res: hashref returned from 'modify_caps' hook
8493 # returns: updated u object, retrieved from $dbh, then 'caps' key modified
8494 # otherwise, returns 0 unless all hooks run properly.
8497 my ($argu, $cap_add, $cap_del, $res) = @_;
8498 my $userid = LJ::want_userid($argu);
8499 return undef unless $userid;
8503 my %cap_add_mod = ();
8504 my %cap_del_mod = ();
8506 # convert capnames to bit numbers
8507 if (LJ::are_hooks("get_cap_bit
")) {
8508 foreach my $bit (@$cap_add, @$cap_del) {
8509 next if $bit =~ /^\d+$/;
8511 # bit is a magical reference into the array
8512 $bit = LJ::run_hook("get_cap_bit
", $bit);
8516 # get a u object directly from the db
8517 my $u = LJ::load_userid($userid, "force
");
8519 delete $u->{sup_enabled} if $u;
8522 my $newcaps = int($u->{'caps'});
8523 foreach (@$cap_add) {
8526 # about to turn bit on, is currently off?
8527 $cap_add_mod{$_} = 1 unless $newcaps & $cap;
8531 # remove deleted caps
8532 foreach (@$cap_del) {
8535 # about to turn bit off, is it currently on?
8536 $cap_del_mod{$_} = 1 if $newcaps & $cap;
8540 # run hooks for modified bits
8541 if (LJ::are_hooks("modify_caps
")) {
8542 my @res = LJ::run_hooks("modify_caps
",
8544 'newcaps' => $newcaps,
8545 'oldcaps' => $u->{'caps'},
8546 'cap_on_req' => { map { $_ => 1 } @$cap_add },
8547 'cap_off_req' => { map { $_ => 1 } @$cap_del },
8548 'cap_on_mod' => \%cap_add_mod,
8549 'cap_off_mod' => \%cap_del_mod,
8552 # hook should return a status code
8553 foreach my $status (@res) {
8554 return undef unless ref $status and defined $status->[0];
8559 return 0 unless LJ::update_user($u, { 'caps' => $newcaps });
8561 $u->{caps} = $newcaps;
8562 $argu->{caps} = $newcaps if ref $argu; # temp hack
8564 LJ::run_hooks("props_changed
", $u, {caps => $newcaps});
8569 # returns 1 if action is permitted. 0 if above rate or fail.
8570 # action isn't logged on fail.
8573 # -- "limit_by_ip
" => "1.2.3.4" (when used for checking rate)
8577 my ($u, $ratename, $count, $opts) = @_;
8578 my $rateperiod = LJ::get_cap($u, "rateperiod
-$ratename");
8579 return 1 unless $rateperiod;
8581 return 0 unless $u->writer;
8583 my $rp = LJ::get_prop("rate
", $ratename);
8584 return 0 unless $rp;
8585 $opts->{'rp'} = $rp;
8588 $opts->{'now'} = $now;
8589 my $udbr = LJ::get_cluster_reader($u);
8590 my $ip = $udbr->quote($opts->{'limit_by_ip'} || "0.0.0.0");
8591 $opts->{'ip'} = $ip;
8592 return 0 unless LJ::rate_check($u, $ratename, $count, $opts);
8595 $count = $count + 0;
8596 $u->do("INSERT INTO ratelog
(userid
, rlid
, evttime
, ip
, quantity
) VALUES
".
8597 "($u->{'userid'}, $rp->{'id'}, $now, INET_ATON
($ip), $count)");
8599 # delete memcache, except in the case of rate limiting by ip
8600 unless ($opts->{limit_by_ip}) {
8601 LJ::MemCache::delete($u->rate_memkey($rp));
8607 # returns 1 if action is permitted. 0 if above rate or fail.
8609 my ($u, $ratename, $count, $opts) = @_;
8611 return 1 if grep { $_ eq $u->username } @LJ::NO_RATE_CHECK_USERS;
8613 my $rateperiod = LJ::get_cap($u, "rateperiod
-$ratename");
8614 return 1 unless $rateperiod;
8616 my $rp = defined $opts->{'rp'} ? $opts->{'rp'}
8617 : LJ::get_prop("rate
", $ratename);
8618 return 0 unless $rp;
8620 my $now = defined $opts->{'now'} ? $opts->{'now'} : time();
8621 my $beforeperiod = $now - $rateperiod;
8623 # check rate. (okay per period)
8624 my $opp = LJ::get_cap($u, "rateallowed
-$ratename");
8625 return 1 unless $opp;
8627 # check memcache, except in the case of rate limiting by ip
8628 my $memkey = $u->rate_memkey($rp);
8629 unless ($opts->{limit_by_ip}) {
8630 my $attempts = LJ::MemCache::get($memkey);
8632 my $num_attempts = 0;
8633 foreach my $attempt (@$attempts) {
8634 next if $attempt->{evttime} < $beforeperiod;
8635 $num_attempts += $attempt->{quantity};
8638 return $num_attempts + $count > $opp ? 0 : 1;
8642 return 0 unless $u->writer;
8644 # delete inapplicable stuff (or some of it)
8645 $u->do("DELETE FROM ratelog WHERE userid
=$u->{'userid'} AND rlid
=$rp->{'id'} ".
8646 "AND evttime
< $beforeperiod LIMIT
1000");
8648 my $udbr = LJ::get_cluster_reader($u);
8649 my $ip = defined $opts->{'ip'}
8651 : $udbr->quote($opts->{'limit_by_ip'} || "0.0.0.0");
8652 my $sth = $udbr->prepare("SELECT evttime
, quantity FROM ratelog WHERE
".
8653 "userid
=$u->{'userid'} AND rlid
=$rp->{'id'} ".
8654 "AND ip
=INET_ATON
($ip) ".
8655 "AND evttime
> $beforeperiod");
8660 while (my $data = $sth->fetchrow_hashref) {
8661 push @memdata, $data;
8662 $sum += $data->{quantity};
8665 # set memcache, except in the case of rate limiting by ip
8666 unless ($opts->{limit_by_ip}) {
8667 LJ::MemCache::set( $memkey => \@memdata || [] );
8670 # would this transaction go over the limit?
8671 if ($sum + $count > $opp) {
8672 # TODO: optionally log to rateabuse, unless caller is doing it themselves
8673 # somehow, like with the "loginstall
" table.
8686 $ip ||= LJ::get_remote_ip();
8687 return 0 unless $ip;
8690 my $rateperiod = LJ::get_cap($u, "rateperiod
-failed_login
");
8691 if ($rateperiod && ($udbr = LJ::get_cluster_reader($u))) {
8692 my $bantime = $udbr->selectrow_array("SELECT
time FROM loginstall WHERE
".
8693 "userid
=$u->{'userid'} AND ip
=INET_ATON
(?
)",
8695 if ($bantime && $bantime > time() - $rateperiod) {
8702 sub handle_bad_login
8707 $ip ||= LJ::get_remote_ip();
8708 return 1 unless $ip;
8710 # an IP address is permitted such a rate of failures
8711 # until it's banned for a period of time.
8713 if (! LJ::rate_log($u, "failed_login
", 1, { 'limit_by_ip' => $ip }) &&
8714 ($udbh = LJ::get_cluster_master($u)))
8716 $udbh->do("REPLACE INTO loginstall
(userid
, ip
, time) VALUES
".
8717 "(?
,INET_ATON
(?
),UNIX_TIMESTAMP
())", undef, $u->{'userid'}, $ip);
8723 # name: LJ::userpic_count
8724 # des: Gets a count of userpics for a given user.
8725 # args: upics, idlist
8726 # des-upics: hashref to load pictures into, keys being the picids
8727 # des-idlist: [$u, $picid] or [[$u, $picid], [$u, $picid], +] objects
8728 # also supports deprecated old method, of an array ref of picids.
8731 my $u = shift or return undef;
8733 if ($u->{'dversion'} > 6) {
8734 my $dbcr = LJ::get_cluster_def_reader($u) or return undef;
8735 return $dbcr->selectrow_array("SELECT COUNT
(*) FROM userpic2
" .
8736 "WHERE userid
=? AND
state <> 'X'", undef, $u->{'userid'});
8739 my $dbh = LJ::get_db_writer() or return undef;
8740 return $dbh->selectrow_array("SELECT COUNT
(*) FROM userpic
" .
8741 "WHERE userid
=? AND
state <> 'X'", undef, $u->{'userid'});
8745 # name: LJ::_friends_do
8746 # des: Runs given SQL, then deletes the given userid's friends from memcache.
8747 # args: uuserid, sql, args
8748 # des-uuserid: a userid or u object
8749 # des-sql: SQL to run via $dbh->do()
8750 # des-args: a list of arguments to pass use via: $dbh->do($sql, undef, @args)
8751 # returns: return false on error
8754 my ($uuid, $sql, @args) = @_;
8755 my $uid = want_userid($uuid);
8756 return undef unless $uid && $sql;
8758 my $dbh = LJ::get_db_writer() or return 0;
8760 my $ret = $dbh->do($sql, undef, @args);
8761 return 0 if $dbh->err;
8763 LJ::memcache_kill($uid, "friends
");
8769 # name: LJ::add_friend
8770 # des: Simple interface to add a friend edge.
8771 # args: uuid, to_add, opts?
8772 # des-to_add: a single uuid or an arrayref of uuids to add (befriendees)
8773 # des-opts: hashref; 'defaultview' key means add target uuids to $uuid's Default View friends group,
8774 # 'groupmask' key means use this group mask
8775 # returns: boolean; 1 on success (or already friend), 0 on failure (bogus args)
8778 my ($userid, $to_add, $opts) = @_;
8780 $userid = LJ::want_userid($userid);
8781 return 0 unless $userid;
8783 my @add_ids = ref $to_add eq 'ARRAY' ? map { LJ::want_userid($_) } @$to_add : ( LJ::want_userid($to_add) );
8784 return 0 unless @add_ids;
8786 # clean widget cache
8787 my $widget_key = "friend_birthdays
:" . $userid;
8788 LJ::MemCache::delete($widget_key);
8790 my $friender = LJ::load_userid($userid);
8793 ## TODO: rate check of adding friends needs PM elaboration
8794 ## Remove '1 ||' when specification is complete
8795 unless (1 || $opts->{no_rate_check}){
8796 my $cond = ["ratecheck
:add_friend
:$userid",
8797 [ $LJ::ADD_FRIEND_RATE_LIMIT || [ 1, 3600 ] ]
8799 return 0 unless LJ::RateLimit->check($friender, [ $cond ]);
8802 my $sclient = LJ::theschwartz();
8804 my $fgcol = LJ::color_todb($opts->{'fgcolor'}) || LJ::color_todb("#000000");
8805 my $bgcol = LJ
::color_todb
($opts->{'bgcolor'});
8806 # in case the background color is #000000, in which case the || falls through
8807 # so only overwrite what we got if what we got was undef (invalid input)
8808 $bgcol = LJ
::color_todb
("#ffffff") unless defined $bgcol;
8813 if (defined $opts->{groupmask
}) {
8814 $groupmask = $opts->{groupmask
};
8815 } elsif ($opts->{'defaultview'}) {
8816 # TAG:FR:ljlib:add_friend_getdefviewmask
8817 my $group = LJ
::get_friend_group
($userid, { name
=> 'Default View' });
8818 my $grp = $group ?
$group->{groupnum
}+0 : 0;
8819 $groupmask |= (1 << $grp) if $grp;
8822 # part of the criteria for whether to fire befriended event
8823 my $notify = !$LJ::DISABLED
{esn
} && !$opts->{nonotify
}
8824 && $friender->is_visible && $friender->is_person;
8827 # load all users at once
8828 LJ
::load_userids
(@add_ids);
8829 foreach my $add_id (@add_ids) {
8830 LJ
::RelationService
->create_relation_to(
8831 $friender, $add_id, 'F',
8832 groupmask
=> $groupmask,
8837 my $friendee = LJ
::load_userid
($add_id);
8838 LJ
::add_to_friend_list
($friender, $friendee);
8839 __drop_short_lifetime_cache
($friender, $friendee);
8844 # only fire event if the friender is a person and not banned and visible
8845 if ($notify && !$friendee->is_banned($friender)) {
8846 require LJ
::Event
::BefriendedDelayed
;
8847 LJ
::Event
::BefriendedDelayed
->send($friendee, $friender);
8850 push @jobs, TheSchwartz
::Job
->new(
8851 funcname
=> "LJ::NewWorker::TheSchwartz::FriendChange",
8852 arg
=> [$userid, 'add', $add_id],
8853 ) unless $LJ::DISABLED
{'friendchange-schwartz'};
8855 $sclient->insert_jobs(@jobs) if @jobs;
8860 # WARNING: always returns "true". Check result of executing "REPLACE INTO friends ..." statement above.
8865 # name: LJ::remove_friend
8866 # des: delete existing friends.
8867 # args: uuid, to_del
8868 # des-to_del: a single uuid or an arrayref of uuids to remove.
8872 my ($userid, $to_del, $opts) = @_;
8874 $userid = LJ
::want_userid
($userid);
8875 return undef unless $userid;
8877 my @del_ids = ref $to_del eq 'ARRAY' ?
map { LJ
::want_userid
($_) } @
$to_del : ( LJ
::want_userid
($to_del) );
8878 return 0 unless @del_ids;
8880 my $u = LJ
::load_userid
($userid);
8882 my $dbh = LJ
::get_db_writer
() or return 0;
8884 my $sclient = LJ
::theschwartz
();
8885 # part of the criteria for whether to fire defriended event
8886 my $notify = !$LJ::DISABLED
{esn
} && !$opts->{nonotify
} && $u->is_visible && $u->is_person;
8888 foreach my $del_id (@del_ids) {
8889 LJ
::RelationService
->remove_relation_to( $u, $del_id, 'F' );
8892 LJ
::load_userids
(@del_ids);
8893 # delete friend-of memcache keys for anyone who was removed
8894 foreach my $fid (@del_ids) {
8895 my $friendee = LJ
::load_userid
($fid);
8897 LJ
::remove_from_friend_list
($u, $friendee);
8898 __drop_short_lifetime_cache
($u, $friendee);
8903 # only fire event if the friender is a person and not banned and visible
8904 if ($notify && !$friendee->has_banned($u)) {
8905 require LJ
::Event
::DefriendedDelayed
;
8906 LJ
::Event
::DefriendedDelayed
->send($friendee, $u);
8909 push @jobs, TheSchwartz
::Job
->new(
8910 funcname
=> "LJ::NewWorker::TheSchwartz::FriendChange",
8911 arg
=> [$userid, 'del', $fid],
8912 ) unless $LJ::DISABLED
{'friendchange-schwartz'};
8914 $sclient->insert_jobs(@jobs);
8921 *delete_friend_edge
= \
&LJ
::remove_friend
;
8923 sub __drop_short_lifetime_cache
{
8924 my ($u, $friend) = @_;
8927 return unless $friend;
8929 my @clean_clist = ('cfriends', 'member', 'mutual_cfriends');
8930 my @clean_flist = ('friends', 'mutual_friends', 'pfriends', 'friendof', 'mutual', 'yfriends');
8932 my $remote = LJ
::get_remote
();
8934 my $sub_drop = sub {
8935 my ($userid, $list_name) = @_;
8937 my $cached = $list_name !~ /mutual/;
8941 $uid = $remote ?
$remote->userid : 'n';
8944 LJ
::MemCache
::delete("u:profile:l:$userid:$uid:$list_name:");
8945 LJ
::MemCache
::delete("u:profile:l:$userid:$uid:$list_name:150");
8947 LJ
::MemCache
::delete("u:profile:l:$userid:n:$list_name:");
8948 LJ
::MemCache
::delete("u:profile:l:$userid:n:$list_name:150");
8952 my $userid = $u->userid;
8953 my $friendid = $friend->userid;
8955 foreach my $list_name (@clean_flist) {
8956 $sub_drop->($userid, $list_name);
8959 foreach my $list_name (@clean_clist) {
8960 $sub_drop->($userid, $list_name);
8963 foreach my $list_name (@clean_flist) {
8964 $sub_drop->($friendid, $list_name);
8967 foreach my $list_name (@clean_clist) {
8968 $sub_drop->($friendid, $list_name);
8974 # name: LJ::get_friends
8975 # des: Returns friends rows for a given user.
8976 # args: uuserid, mask?, memcache_only?, force?
8977 # des-uuserid: a userid or u object.
8978 # des-mask: a security mask to filter on.
8979 # des-memcache_only: flag, set to only return data from memcache
8980 # des-force: flag, set to ignore memcache and always hit DB.
8981 # returns: hashref; keys = friend userids
8982 # values = hashrefs of 'friends' columns and their values
8985 # TAG:FR:ljlib:get_friends
8986 my ($uuid, $mask, $memcache_only, $force) = @_;
8987 my $userid = LJ
::want_userid
($uuid);
8988 return undef unless $userid;
8989 return undef if $LJ::FORCE_EMPTY_FRIENDS
{$userid};
8991 my $u = LJ
::load_userid
($userid);
8993 return LJ
::RelationService
->load_relation_destinations(
8997 memcache_only
=> $memcache_only,
9003 # name: LJ::get_friendofs
9004 # des: Returns userids of friendofs for a given user.
9005 # args: uuserid, opts?
9006 # des-opts: options hash, keys: 'force' => don't check memcache
9007 # returns: userid for friendofs
9010 # TAG:FR:ljlib:get_friends
9011 my ($uuid, $opts) = @_;
9012 my $userid = LJ
::want_userid
($uuid);
9013 return undef unless $userid;
9015 my $u = LJ
::load_userid
($userid);
9016 return LJ
::RelationService
->find_relation_sources($u, 'F',
9017 nolimit
=> $opts->{force
} || 0,
9018 skip_memcached
=> $opts->{force
},
9023 # name: LJ::fill_groups_xmlrpc
9024 # des: Fills a hashref (presumably to be sent to an XML-RPC client, e.g. FotoBilder)
9025 # with user friend group information
9027 # des-ret: a response hashref to fill with friend group data
9028 # returns: undef if called incorrectly, 1 otherwise
9030 sub fill_groups_xmlrpc
{
9032 return undef unless ref $u && ref $ret;
9034 # best interface ever...
9035 $RPC::XML
::ENCODING
= "utf-8";
9037 # layer on friend group information in the following format:
9039 # grp:1 => 'mygroup',
9041 # grp:30 => 'anothergroup',
9043 # grpu:whitaker => '0,1,2,3,4',
9046 my $grp = LJ
::get_friend_group
($u) || {};
9048 # we don't always have RPC::XML loaded (in web context), and it doesn't really
9049 # matter much anyway, since our only consumer is also perl which will take
9050 # the occasional ints back to strings.
9053 my $val = eval { RPC
::XML
::string
->new($str); };
9054 return $val unless $@
;
9058 $ret->{"grp:0"} = $str->("_all_");
9059 foreach my $bit (1..30) {
9060 next unless my $g = $grp->{$bit};
9061 $ret->{"grp:$bit"} = $str->($g->{groupname
});
9064 my $fr = LJ
::get_friends
($u) || {};
9065 my $users = LJ
::load_userids
(keys %$fr);
9066 while (my ($fid, $f) = each %$fr) {
9067 my $u = $users->{$fid};
9068 next unless $u->{journaltype
} =~ /[PSI]/;
9070 my $fname = $u->{user
};
9071 $ret->{"grpu:$fid:$fname"} =
9072 $str->(join(",", 0, grep { $grp->{$_} && $f->{groupmask
} & 1 << $_ } 1..30));
9079 # name: LJ::delete_all_comments
9080 # des: deletes all comments from a post, permanently, for when a post is deleted
9081 # info: The tables [dbtable[talk2]], [dbtable[talkprop2]], [dbtable[talktext2]],
9082 # are deleted from, immediately.
9083 # args: u, nodetype, nodeid
9084 # des-nodetype: The thread nodetype (probably 'L' for log items).
9085 # des-nodeid: The thread nodeid for the given nodetype (probably the jitemid
9086 # from the [dbtable[log2]] row).
9087 # returns: boolean; success value
9089 sub delete_all_comments
{
9090 my ($u, $nodetype, $nodeid) = @_;
9092 my $dbcm = LJ
::get_cluster_master
($u);
9093 return 0 unless $dbcm && $u->writer;
9096 my ($t, $loop) = (undef, 1);
9097 my $chunk_size = 200;
9099 ($t = $dbcm->selectcol_arrayref("SELECT jtalkid FROM talk2 WHERE ".
9100 "nodetype=? AND journalid=? ".
9101 "AND nodeid=? LIMIT $chunk_size", undef,
9102 $nodetype, $u->{'userid'}, $nodeid))
9105 my @batch = map { int $_ } @
$t;
9106 my $in = join(',', @batch);
9107 return 1 unless $in;
9109 LJ
::run_hooks
('report_cmt_delete', $u->{'userid'}, \
@batch);
9110 LJ
::run_hooks
('report_cmt_text_delete', $u->{'userid'}, \
@batch);
9111 foreach my $table (qw(talkprop2 talktext2 talk2)) {
9112 $u->do("DELETE FROM $table WHERE journalid=? AND jtalkid IN ($in)",
9113 undef, $u->{'userid'});
9115 # decrement memcache
9116 LJ
::MemCache
::decr
([$u->{'userid'}, "talk2ct:$u->{'userid'}"], scalar(@
$t));
9117 $loop = 0 unless @
$t == $chunk_size;
9123 # is a user object (at least a hashref)
9125 return unless ref $_[0];
9126 return 1 if UNIVERSAL
::isa
($_[0], "LJ::User");
9128 if (ref $_[0] eq "HASH" && $_[0]->{userid
}) {
9129 carp
"User HASH objects are deprecated from use." if $LJ::IS_DEV_SERVER
;
9134 # create externally mapped user.
9135 # return uid of LJ user on success, undef on error.
9137 # extuser or extuserid (or both, but one is required.),
9140 # opts also can contain any additional options that create_account takes. (caps?)
9143 my ($type, $opts) = @_;
9144 return undef unless $type && $LJ::EXTERNAL_NAMESPACE
{$type}->{id
};
9145 return undef unless ref $opts &&
9146 ($opts->{extuser
} || defined $opts->{extuserid
});
9149 my $dbh = LJ
::get_db_writer
();
9150 return undef unless $dbh;
9152 # make sure a mapping for this user doesn't already exist.
9153 $uid = LJ
::get_extuser_uid
( $type, $opts, 'force' );
9154 return $uid if $uid;
9156 # increment ext_ counter until we successfully create an LJ account.
9157 # hard cap it at 10 tries. (arbitrary, but we really shouldn't have *any*
9158 # failures here, let alone 10 in a row.)
9160 my $extuser = 'ext_' . LJ
::alloc_global_counter
( 'E' );
9163 { caps
=> $opts->{caps
}, user
=> $extuser, name
=> $extuser } );
9165 select undef, undef, undef, .10; # lets not thrash over this.
9167 return undef unless $uid;
9169 # add extuser mapping.
9170 my $sql = "INSERT INTO extuser SET userid=?, siteid=?";
9171 my @bind = ($uid, $LJ::EXTERNAL_NAMESPACE
{$type}->{id
});
9173 if ($opts->{extuser
}) {
9174 $sql .= ", extuser=?";
9175 push @bind, $opts->{extuser
};
9178 if ($opts->{extuserid
}) {
9179 $sql .= ", extuserid=? ";
9180 push @bind, $opts->{extuserid
}+0;
9183 $dbh->do($sql, undef, @bind) or return undef;
9188 my ($url, $remote) = @_;
9190 my $privilege = $LJ::PAGE_PRIVILEGES
{$url} || $LJ::PAGE_PRIVILEGES
{"$url/"};
9192 return 0 unless $privilege;
9194 my $priv = $privilege->{'priv'};
9195 my $arg = $privilege->{'arg'};
9196 if ( LJ
::check_priv
($remote, $priv, $arg) ) {
9197 my $uri = LJ
::Request
->uri;
9198 my $args = LJ
::Request
->args;
9199 my $current_url = "$uri?$args";
9200 my $authas = LJ
::Request
->get_param('authas') || LJ
::Request
->post_param('authas');
9201 my $u = LJ
::load_user
($authas);
9202 LJ
::statushistory_add
($u, $remote, "view_settings", "$current_url" );
9207 # given an extuserid or extuser, return the LJ uid.
9208 # return undef if there is no mapping.
9211 my ($type, $opts, $force) = @_;
9212 return undef unless $type && $LJ::EXTERNAL_NAMESPACE
{$type}->{id
};
9213 return undef unless ref $opts &&
9214 ($opts->{extuser
} || defined $opts->{extuserid
});
9216 my $dbh = $force ? LJ
::get_db_writer
() : LJ
::get_db_reader
();
9217 return undef unless $dbh;
9219 my $sql = "SELECT userid FROM extuser WHERE siteid=?";
9220 my @bind = ($LJ::EXTERNAL_NAMESPACE
{$type}->{id
});
9222 if ($opts->{extuser
}) {
9223 $sql .= " AND extuser=?";
9224 push @bind, $opts->{extuser
};
9227 if ($opts->{extuserid
}) {
9228 $sql .= $opts->{extuser
} ?
' OR ' : ' AND ';
9229 $sql .= "extuserid=?";
9230 push @bind, $opts->{extuserid
}+0;
9233 return $dbh->selectrow_array($sql, undef, @bind);
9236 # given a LJ userid/u, return a hashref of:
9237 # type, extuser, extuserid
9238 # returns undef if user isn't an externally mapped account.
9241 my $uid = LJ
::want_userid
(shift);
9242 return undef unless $uid;
9244 my $dbr = LJ
::get_db_reader
();
9245 return undef unless $dbr;
9247 my $sql = "SELECT * FROM extuser WHERE userid=?";
9248 my $ret = $dbr->selectrow_hashref($sql, undef, $uid);
9249 return undef unless $ret;
9251 my $type = 'unknown';
9252 foreach ( keys %LJ::EXTERNAL_NAMESPACE
) {
9253 $type = $_ if $LJ::EXTERNAL_NAMESPACE
{$_}->{id
} == $ret->{siteid
};
9256 $ret->{type
} = $type;
9261 # name: LJ::create_account
9262 # des: Creates a new basic account. <strong>Note:</strong> This function is
9263 # not really too useful but should be extended to be useful so
9264 # htdocs/create.bml can use it, rather than doing the work itself.
9265 # returns: integer of userid created, or 0 on failure.
9267 # des-opts: hashref containing keys 'user', 'name', 'password', 'email', 'caps', 'journaltype'.
9269 sub create_account
{
9271 my $u = LJ
::User
->create(%$opts)
9278 # name: LJ::new_account_cluster
9279 # des: Which cluster to put a new account on. $DEFAULT_CLUSTER if it's
9280 # a scalar, random element from [ljconfig[default_cluster]] if it's arrayref.
9281 # also verifies that the database seems to be available.
9282 # returns: clusterid where the new account should be created; 0 on error
9283 # (such as no clusters available).
9285 sub new_account_cluster
9287 # if it's not an arrayref, put it in an array ref so we can use it below
9288 my $clusters = ref $LJ::DEFAULT_CLUSTER ?
$LJ::DEFAULT_CLUSTER
: [ $LJ::DEFAULT_CLUSTER
+0 ];
9290 # select a random cluster from the set we've chosen in $LJ::DEFAULT_CLUSTER
9291 return LJ
::random_cluster
(@
$clusters);
9294 # returns the clusterid of a random cluster which is up
9295 # -- accepts @clusters as an arg to enforce a subset, otherwise
9296 # uses @LJ::CLUSTERS
9297 sub random_cluster
{
9298 my @clusters = @_ ?
@_ : @LJ::CLUSTERS
;
9300 # iterate through the new clusters from a random point
9301 my $size = @clusters;
9302 my $start = int(rand() * $size);
9303 foreach (1..$size) {
9304 my $cid = $clusters[$start++ % $size];
9306 # verify that this cluster is in @LJ::CLUSTERS
9307 my @check = grep { $_ == $cid } @LJ::CLUSTERS
;
9308 next unless scalar(@check) >= 1 && $check[0] == $cid;
9310 # try this cluster to see if we can use it, return if so
9311 my $dbcm = LJ
::get_cluster_master
($cid);
9312 return $cid if $dbcm;
9315 # if we get here, we found no clusters that were up...
9320 # name: LJ::make_journal
9321 # args: user, view, remote, opts
9324 my ($user, $view, $remote, $opts) = @_;
9326 my $geta = $opts->{'getargs'};
9328 if ($LJ::SERVER_DOWN
) {
9329 if ($opts->{'vhost'} eq "customview") {
9330 return "<!-- LJ down for maintenance -->";
9332 return LJ
::server_down_html
();
9335 my $u = $opts->{'u'} || LJ
::load_user
($user);
9337 $opts->{'baduser'} = 1;
9338 return "<h1>Error</h1>No such user <b>$user</b>";
9340 LJ
::set_active_journal
($u);
9341 LJ
::Request
->notes('ljentry' => $opts->{'ljentry'}->url) if $opts->{'ljentry'};
9343 # S1 style hashref. won't be loaded now necessarily,
9344 # only if via customview.
9348 if ($opts->{'styleid'}) { # s1 styleid
9349 $styleid = $opts->{'styleid'}+0;
9351 # if we have an explicit styleid, we have to load
9352 # it early so we can learn its type, so we can
9353 # know which uprops to load for its owner
9354 if ($LJ::ONLY_USER_VHOSTS
&& $opts->{vhost
} eq "customview") {
9355 # reject this style if it's not trusted by the user, and we're showing
9356 # stuff on user domains
9357 my $ownerid = LJ
::S1
::get_style_userid_always
($styleid);
9358 my $is_trusted = sub {
9359 return 1 if $ownerid == $u->{userid
};
9360 return 1 if $ownerid == LJ
::system_userid
();
9361 return 1 if $LJ::S1_CUSTOMVIEW_WHITELIST
{"styleid-$styleid"};
9362 return 1 if $LJ::S1_CUSTOMVIEW_WHITELIST
{"userid-$ownerid"};
9363 my $trust_list = eval { $u->prop("trusted_s1") };
9364 return 1 if $trust_list =~ /\b$styleid\b/;
9367 unless ($is_trusted->()) {
9374 $view ||= "lastn"; # default view when none specified explicitly in URLs
9375 if ($LJ::viewinfo
{$view} || $view eq "month" ||
9376 $view eq "entry" || $view eq "reply") {
9377 $styleid = -1; # to get past the return, then checked later for -1 and fixed, once user is loaded.
9379 $opts->{'badargs'} = 1;
9382 return unless $styleid;
9385 $u->{'_journalbase'} = LJ
::journal_base
($u->{'user'}, $opts->{'vhost'});
9387 my $eff_view = $LJ::viewinfo
{$view}->{'styleof'} || $view;
9388 my $s1prop = "s1_${eff_view}_style";
9390 my @needed_props = ("stylesys", "s2_style", "url", "urlname", "opt_nctalklinks",
9391 "renamedto", "opt_blockrobots", "opt_usesharedpic", "icbm",
9392 "journaltitle", "journalsubtitle", "external_foaf_url",
9393 "adult_content", "admin_content_flag", "community_reader_ids");
9395 # S2 is more fully featured than S1, so sometimes we get here and $eff_view
9396 # is reply/month/entry/res and that means it *has* to be S2--S1 defaults to a
9397 # BML page to handle those, but we don't want to attempt to load a userprop
9398 # because now load_user_props dies if you try to load something invalid
9399 push @needed_props, $s1prop if $eff_view =~ /^(?:calendar|day|friends|lastn)$/;
9401 # preload props the view creation code will need later (combine two selects)
9402 if (ref $LJ::viewinfo
{$eff_view}->{'owner_props'} eq "ARRAY") {
9403 push @needed_props, @
{$LJ::viewinfo
{$eff_view}->{'owner_props'}};
9406 if ($eff_view eq "reply") {
9407 push @needed_props, "opt_logcommentips";
9410 $u->preload_props(@needed_props);
9412 # FIXME: remove this after all affected accounts have been fixed
9413 # see http://zilla.livejournal.org/1443 for details
9414 if ($u->{$s1prop} =~ /^\D/) {
9415 $u->{$s1prop} = $LJ::USERPROP_DEF
{$s1prop};
9416 $u->set_prop($s1prop, $u->{$s1prop});
9419 # if the remote is the user to be viewed, make sure the $remote
9420 # hashref has the value of $u's opt_nctalklinks (though with
9421 # LJ::load_user caching, this may be assigning between the same
9422 # underlying hashref)
9423 $remote->{'opt_nctalklinks'} = $u->{'opt_nctalklinks'} if
9424 ($remote && $remote->{'userid'} == $u->{'userid'});
9427 if ($styleid == -1) {
9429 my $get_styleinfo = sub {
9431 my $get_s1_styleid = sub {
9432 my $id = $u->{$s1prop};
9433 LJ
::run_hooks
("s1_style_select", {
9441 # forced s2 style id
9442 if ($geta->{'s2id'}) {
9444 # get the owner of the requested style
9445 my $style = LJ
::S2
::load_style
( $geta->{s2id
} );
9446 my $owner = $style && $style->{userid
} ?
$style->{userid
} : 0;
9448 # remote can use s2id on this journal if:
9449 # owner of the style is remote or managed by remote OR
9450 # owner of the style has s2styles cap and remote is viewing owner's journal
9452 if ($u->id == $owner && $u->get_cap("s2styles")) {
9453 $opts->{'style_u'} = LJ
::load_userid
($owner);
9454 return (2, $geta->{'s2id'});
9457 if ($remote && $remote->can_manage($owner)) {
9458 # check is owned style still available: paid user possible became plus...
9459 my $lay_id = $style->{layer
}->{layout
};
9460 my $theme_id = $style->{layer
}->{theme
};
9462 LJ
::S2
::load_layer_info
(\
%lay_info, [$style->{layer
}->{layout
}, $style->{layer
}->{theme
}]);
9464 if (LJ
::S2
::can_use_layer
($remote, $lay_info{$lay_id}->{redist_uniq
})
9465 and LJ
::S2
::can_use_layer
($remote, $lay_info{$theme_id}->{redist_uniq
})) {
9466 $opts->{'style_u'} = LJ
::load_userid
($owner);
9467 return (2, $geta->{'s2id'});
9468 } # else this style not allowed by policy
9472 # style=mine passed in GET?
9473 if ($remote && ( $geta->{'style'} eq 'mine' ||
9474 $remote->opt_stylealwaysmine ) ) {
9476 # get remote props and decide what style remote uses
9477 $remote->preload_props("stylesys", "s2_style");
9479 # remote using s2; make sure we pass down the $remote object as the style_u to
9480 # indicate that they should use $remote to load the style instead of the regular $u
9481 if ($remote->{'stylesys'} == 2 && $remote->{'s2_style'}) {
9482 $opts->{'checkremote'} = 1;
9483 $opts->{'style_u'} = $remote;
9484 return (2, $remote->{'s2_style'});
9488 return (1, $get_s1_styleid->());
9491 # resource URLs have the styleid in it
9492 if ($view eq "res" && $opts->{'pathextra'} =~ m!^/(\d+)/!) {
9497 LJ
::run_hooks
("force_s1", $u, \
$forceflag);
9499 # if none of the above match, they fall through to here
9500 if ( !$forceflag && $u->{'stylesys'} == 2 ) {
9501 return (2, $u->{'s2_style'});
9504 # no special case and not s2, fall through to s1
9505 return (1, $get_s1_styleid->());
9508 if ($LJ::JOURNALS_WITH_FIXED_STYLE
{$u->user}) {
9509 ($stylesys, $styleid) = (2, $u->{'s2_style'});
9511 ($stylesys, $styleid) = $get_styleinfo->();
9515 # transcode the tag filtering information into the tag getarg; this has to
9516 # be done above the s1shortcomings section so that we can fall through to that
9517 # style for lastn filtered by tags view
9518 if ($view eq 'lastn' && $opts->{pathextra
} && $opts->{pathextra
} =~ /^\/tag\
/(.+)$/) {
9519 $opts->{getargs
}->{tag
} = $1;
9520 $opts->{pathextra
} = undef;
9523 # do the same for security filtering
9524 elsif ($view eq 'lastn' && $opts->{pathextra
} && $opts->{pathextra
} =~ /^\/security\
/(.+)$/) {
9525 $opts->{getargs
}->{security
} = $1;
9526 $opts->{pathextra
} = undef;
9529 if (LJ
::Request
->is_inited) {
9530 LJ
::Request
->notes('journalid' => $u->{'userid'});
9537 my $url = "$LJ::SITEROOT/users/$user/";
9538 $opts->{'status'} = $status if $status;
9541 my $journalbase = LJ
::journal_base
($user);
9543 # Automatic Discovery of RSS/Atom
9544 $head .= qq{<link rel
="alternate" type
="application/rss+xml" title
="RSS" href
="$journalbase/data/rss" />\n};
9545 $head .= qq{<link rel
="alternate" type
="application/atom+xml" title
="Atom" href
="$journalbase/data/atom" />\n};
9546 $head .= qq{<link rel
="service.feed" type
="application/atom+xml" title
="AtomAPI-enabled feed" href
="$LJ::SITEROOT/interface/atom/feed" />\n};
9547 $head .= qq{<link rel
="service.post" type
="application/atom+xml" title
="Create a new post" href
="$LJ::SITEROOT/interface/atom/post" />\n};
9549 # OpenID Server and Yadis
9550 $head .= $u->openid_tags;
9552 # FOAF autodiscovery
9553 my $foafurl = $u->{external_foaf_url
} ? LJ
::eurl
($u->{external_foaf_url
}) : "$journalbase/data/foaf";
9554 $head .= qq{<link rel
="meta" type
="application/rdf+xml" title
="FOAF" href
="$foafurl" />\n};
9556 if ($u->email_visible($remote)) {
9557 my $digest = Digest
::SHA1
::sha1_hex
('mailto:' . $u->email_raw);
9558 $head .= qq{<meta name
="foaf:maker" content
="foaf:mbox_sha1sum '$digest'" />\n};
9569 <p
>Instead
, please
use <nobr
><a href
=\"$url\">$url</a></nobr
></p
>
9572 }.("<!-- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -->\n" x
50);
9577 $opts->{'status'} = $status if $status;
9582 }.("<!-- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -->\n" x
50);
9584 if ($LJ::USER_VHOSTS
&& $opts->{'vhost'} eq "users" && $u->{'journaltype'} ne 'R' &&
9585 ! LJ
::get_cap
($u, "userdomain")) {
9586 return $notice->("URLs like <nobr><b>http://<i>username</i>.$LJ::USER_DOMAIN/" .
9587 "</b></nobr> are not available for this user's account type.");
9589 if ($opts->{'vhost'} eq "customview" && ! LJ
::get_cap
($u, "styles")) {
9590 return $notice->("This user's account type is not permitted to create and embed styles.");
9592 if ($opts->{'vhost'} eq "community" && $u->{'journaltype'} !~ /[CR]/) {
9593 $opts->{'badargs'} = 1; # Output a generic 'bad URL' message if available
9594 return "<h1>Notice</h1><p>This account isn't a community journal.</p>";
9596 if ($view eq "friendsfriends" && ! LJ
::get_cap
($u, "friendsfriendsview")) {
9598 if ($inline .= LJ
::run_hook
("cprod_inline", $u, 'FriendsFriendsInline')) {
9601 return BML
::ml
('cprod.friendsfriendsinline.text.v1');
9605 # signal to LiveJournal.pm that we can't handle this
9606 if (($stylesys == 1 || $geta->{'format'} eq 'light') &&
9607 ({ entry
=>1, reply
=>1, month
=>1, tag
=>1 }->{$view} || ($view eq 'lastn' && ($geta->{tag
} || $geta->{security
})))) {
9609 # pick which fallback method (s2 or bml) we'll use by default, as configured with
9611 my $fallback = $LJ::S1_SHORTCOMINGS ?
"s2" : "bml";
9613 # but if the user specifies which they want, override the fallback we picked
9614 if ($geta->{'fallback'} && $geta->{'fallback'} =~ /^s2|bml$/) {
9615 $fallback = $geta->{'fallback'};
9618 # if we are in this path, and they have style=mine set, it means
9619 # they either think they can get a S2 styled page but their account
9620 # type won't let them, or they really want this to fallback to bml
9621 if ($remote && ( $geta->{'style'} eq 'mine' ||
9622 $remote->opt_stylealwaysmine ) ) {
9626 # If they specified ?format=light, it means they want a page easy
9627 # to deal with text-only or on a mobile device. For now that means
9628 # render it in the lynx site scheme.
9629 if ($geta->{'format'} eq 'light') {
9631 LJ
::Request
->notes('bml_use_scheme' => 'lynx');
9634 # there are no BML handlers for these views, so force s2
9635 if ($view eq 'tag' || $view eq 'lastn') {
9639 # fall back to BML unless we're using S2
9640 # fallback (the "s1shortcomings/layout")
9641 if ($fallback eq "bml") {
9642 ${$opts->{'handle_with_bml_ref'}} = 1;
9646 # S1 can't handle these views, so we fall back to a
9647 # system-owned S2 style (magic value "s1short") that renders
9650 $styleid = "s1short";
9653 # now, if there's a GET argument for tags, split those out
9654 if (exists $opts->{getargs
}->{tag
}) {
9655 my $tagfilter = $opts->{getargs
}->{tag
};
9656 return $error->("You must provide tags to filter by.", "404 Not Found")
9657 unless defined $tagfilter;
9660 return $error->("Sorry, the tag system is currently disabled.", "404 Not Found")
9661 if $LJ::DISABLED
{tags
};
9663 # throw an error if we're rendering in S1, but not for renamed accounts
9664 return $error->("Sorry, tag filtering is not supported within S1 styles.", "404 Not Found")
9665 if $stylesys == 1 && $view ne 'data' && $u->{journaltype
} ne 'R';
9667 # overwrite any tags that exist
9669 return $error->("Sorry, the tag list specified is invalid.", "404 Not Found")
9670 unless LJ
::Tags
::is_valid_tagstring
($tagfilter, $opts->{tags
}, { omit_underscore_check
=> 1 });
9672 # get user's tags so we know what remote can see, and setup an inverse mapping
9673 # from keyword to tag
9674 $opts->{tagids
} = [];
9675 $opts->{'tagmap'} = {};
9676 my $tags = LJ
::Tags
::get_usertags
($u, { remote
=> $remote });
9679 foreach my $tagid (keys %$tags) {
9680 push @
{$kwref{LJ
::Text
->normalize_tag_name ($tags->{$tagid}->{'name'})}}, $tagid;
9683 foreach my $tagname (@
{$opts->{tags
}}) {
9684 unless ($kwref{LJ
::Text
->normalize_tag_name ($tagname)}) {
9685 LJ
::Request
->pnotes ('error' => 'e404');
9686 LJ
::Request
->pnotes ('remote' => LJ
::get_remote
());
9687 $opts->{'handler_return'} = "404 Not Found";
9690 #return $error->("Sorry, one or more specified tags do not exist.", "404 Not Found")
9691 # unless $kwref{$tagname};
9692 push @
{$opts->{'tagids'}}, @
{$kwref{LJ
::Text
->normalize_tag_name ($tagname)}};
9693 $opts->{'tagmap'}->{$tagname} = $kwref{LJ
::Text
->normalize_tag_name ($tagname)};
9696 $opts->{tagmode
} = $opts->{getargs
}->{mode
} eq 'and' ?
'and' : 'or';
9699 # validate the security filter
9700 if (exists $opts->{getargs
}->{security
}) {
9701 my $securityfilter = $opts->{getargs
}->{security
};
9702 return $error->("You must provide a security level to filter by.", "404 Not Found")
9703 unless $securityfilter;
9705 return $error->("This feature is not available for your account level.", "403 Forbidden")
9706 unless LJ
::get_cap
($remote, "security_filter") || LJ
::get_cap
($u, "security_filter");
9709 return $error->("Sorry, the security-filtering system is currently disabled.", "404 Not Found")
9710 unless LJ
::is_enabled
("security_filter");
9712 # throw an error if we're rendering in S1, but not for renamed accounts
9713 return $error->("Sorry, security filtering is not supported within S1 styles.", "404 Not Found")
9714 if $stylesys == 1 && $view ne 'data' && !$u->is_redirect;
9716 # check the filter itself
9717 if ($securityfilter =~ /^(?:public|friends|private)$/i) {
9718 $opts->{'securityfilter'} = lc($securityfilter);
9720 } elsif ($securityfilter =~ /^group:(.+)$/i) {
9721 my $groupres = LJ
::get_friend_group
($u, { 'name' => $1});
9723 if ($groupres && (LJ
::u_equals
($u, $remote)
9724 || LJ
::get_groupmask
($u, $remote) & (1 << $groupres->{groupnum
}))) {
9725 $opts->{securityfilter
} = $groupres->{groupnum
};
9729 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")
9730 unless defined $opts->{securityfilter
};
9734 unless ($geta->{'viewall'} && LJ
::check_priv
($remote, "canview", "suspended") ||
9735 $opts->{'pathextra'} =~ m
#/(\d+)/stylesheet$#) { ## don't check style sheets
9736 if ($u->is_deleted){
9737 my $warning = LJ
::Lang
::get_text
(LJ
::Lang
::get_effective_lang
(),
9738 'journal.deleted', undef, {username
=> $u->username})
9739 || LJ
::Lang
::get_text
($LJ::DEFAULT_LANG
,
9740 'journal.deleted', undef, {username
=> $u->username});
9741 LJ
::Request
->pnotes ('error' => 'deleted');
9742 LJ
::Request
->pnotes ('remote' => LJ
::get_remote
());
9743 $opts->{'handler_return'} = "404 Not Found";
9745 #return $error->($warning, "404 Not Found");
9748 if ($u->is_suspended) {
9749 LJ
::Request
->pnotes ('error' => 'suspended');
9750 LJ
::Request
->pnotes ('remote' => LJ
::get_remote
());
9751 $opts->{'handler_return'} = "403 Forbidden";
9754 #return $error->("This journal has been suspended.", "403 Forbidden") if ($u->is_suspended);
9756 my $entry = $opts->{ljentry
};
9758 if ($entry && $entry->is_suspended_for($remote)) {
9759 LJ
::Request
->pnotes ('error' => 'suspended_post');
9760 LJ
::Request
->pnotes ('remote' => LJ
::get_remote
());
9761 $opts->{'handler_return'} = "403 Forbidden";
9765 return $error->("This entry has been suspended. You can visit the journal <a href='" . $u->journal_base . "/'>here</a>.", "403 Forbidden")
9766 if $entry && $entry->is_suspended_for($remote);
9768 if ($u->is_expunged) {
9769 LJ
::Request
->pnotes ('error' => 'expunged');
9770 LJ
::Request
->pnotes ('remote' => LJ
::get_remote
());
9771 $opts->{'handler_return'} = "410 Gone";
9775 return $error->("This user has no journal here.", "404 Not here") if $u->{'journaltype'} eq "I" && $view ne "friends";
9777 $opts->{'view'} = $view;
9779 # what charset we put in the HTML
9780 $opts->{'saycharset'} ||= "utf-8";
9782 if ($view eq 'data') {
9783 return LJ
::Feed
::make_feed
($u, $remote, $opts);
9786 if ($stylesys == 2) {
9787 LJ
::Request
->notes('codepath' => "s2.$view") if LJ
::Request
->is_inited;
9789 eval { LJ
::S2
->can("dostuff") }; # force Class::Autouse
9790 my $mj = LJ
::S2
::make_journal
($u, $styleid, $view, $remote, $opts);
9792 # intercept flag to handle_with_bml_ref and instead use S1 shortcomings
9793 # if BML is disabled
9794 if ($opts->{'handle_with_bml_ref'} && ${$opts->{'handle_with_bml_ref'}} &&
9795 ($LJ::S1_SHORTCOMINGS
|| $geta->{fallback
} eq "s2"))
9798 ${$opts->{'handle_with_bml_ref'}} = 0;
9800 # and proceed with s1shortcomings (which looks like BML) instead of BML
9801 $mj = LJ
::S2
::make_journal
($u, "s1short", $view, $remote, $opts);
9807 # Everything from here on down is S1. FIXME: this should be moved to LJ::S1::make_journal
9808 # to be more like LJ::S2::make_journal.
9809 LJ
::Request
->notes('codepath' => "s1.$view") if LJ
::Request
->is_inited;
9810 $u->{'_s1styleid'} = $styleid + 0;
9812 # For embedded polls
9813 BML
::set_language
($LJ::LANGS
[0] || 'en', \
&LJ
::Lang
::get_text
);
9815 # load the user-related S1 data (overrides and colors)
9817 my $is_s1uc_valid = sub {
9818 ## Storable::thaw takes valid date, undef or empty string;
9819 ## dies on invalid data
9822 Storable
::thaw
($_[0]->{'color_stor'});
9823 Storable
::thaw
($_[0]->{'override_stor'});
9827 my $s1uc_memkey = [$u->{'userid'}, "s1uc:$u->{'userid'}"];
9828 if ($u->{'useoverrides'} eq "Y" || $u->{'themeid'} == 0) {
9829 $s1uc = LJ
::MemCache
::get
($s1uc_memkey);
9830 undef($s1uc) if $s1uc && !$is_s1uc_valid->($s1uc);
9835 if (@LJ::MEMCACHE_SERVERS
) {
9836 $db = LJ
::get_cluster_def_reader
($u);
9838 $db = LJ
::get_cluster_reader
($u);
9841 $s1uc = $db->selectrow_hashref("SELECT * FROM s1usercache WHERE userid=?",
9842 undef, $u->{'userid'});
9843 undef($s1uc) if $s1uc && !$is_s1uc_valid->($s1uc);
9844 LJ
::MemCache
::set
($s1uc_memkey, $s1uc) if $s1uc && $setmem;
9848 # we should have our cache row! we'll update it in a second.
9851 $u->do("INSERT IGNORE INTO s1usercache (userid) VALUES (?)", undef, $u->{'userid'});
9855 # conditionally rebuild parts of our cache that are missing
9858 # is the overrides cache old or missing?
9860 if ($u->{'useoverrides'} eq "Y" && (! $s1uc->{'override_stor'} ||
9861 $s1uc->{'override_cleanver'} < $LJ::S1
::CLEANER_VERSION
)) {
9863 my $overrides = LJ
::S1
::get_overrides
($u);
9864 $update{'override_stor'} = LJ
::CleanHTML
::clean_s1_style
($overrides);
9865 $update{'override_cleanver'} = $LJ::S1
::CLEANER_VERSION
;
9868 # is the color cache here if it's a custom user theme?
9869 if ($u->{'themeid'} == 0 && ! $s1uc->{'color_stor'}) {
9871 $dbh ||= LJ
::get_db_writer
();
9872 my $sth = $dbh->prepare("SELECT coltype, color FROM themecustom WHERE user=?");
9873 $sth->execute($u->{'user'});
9874 $col->{$_->{'coltype'}} = $_->{'color'} while $_ = $sth->fetchrow_hashref;
9875 $update{'color_stor'} = Storable
::nfreeze
($col);
9881 foreach my $k (keys %update) {
9882 $s1uc->{$k} = $update{$k};
9883 $set .= ", " if $set;
9884 $set .= "$k=" . $u->quote($update{$k});
9886 my $rv = $u->do("UPDATE s1usercache SET $set WHERE userid=?", undef, $u->{'userid'});
9887 LJ
::MemCache
::set
($s1uc_memkey, $s1uc);
9891 my $viewref = $view eq "" ? \
$view : undef;
9892 $style ||= $LJ::viewinfo
{$view}->{'nostyle'} ?
{} :
9893 LJ
::S1
::load_style
($styleid, $viewref);
9898 foreach (keys %$style) {
9899 $vars{$_} = $style->{$_};
9902 # apply the overrides
9903 if ($opts->{'nooverride'}==0 && $u->{'useoverrides'} eq "Y") {
9904 my $tw = Storable
::thaw
($s1uc->{'override_stor'});
9905 foreach (keys %$tw) {
9906 $vars{$_} = $tw->{$_};
9910 # apply the color theme
9912 my $cols = $u->{'themeid'}
9913 ? LJ
::S1
::get_themeid
($u->{'themeid'})
9914 : Storable
::thaw
($s1uc->{'color_stor'});
9915 foreach (keys %$cols) {
9916 $vars{"color-$_"} = $cols->{$_};
9919 # instruct some function to make this specific view type
9920 return unless defined $LJ::viewinfo
{$view}->{'creator'};
9923 # call the view creator w/ the buffer to fill and the construction variables
9924 my $res = $LJ::viewinfo
{$view}->{'creator'}->(\
$ret, $u, \
%vars, $remote, $opts);
9926 if ($LJ::USE_S1w2
&& $LJ::USE_S1w2
->($view, $u, $remote)) {
9927 # S1w2 is an experimental version of S1 that acts as if it were an S2 style,
9928 # getting all of its necessary data from the S2 data structures rather than
9929 # fetching the data itself and duplicating all of that logic.
9930 # It should ideally generate exactly the same output as traditional S1 with
9931 # the same input data, but until this has been tested thoroughly it's
9932 # disabled by default.
9934 # We render S1w2 in addition to traditional S1 so that we can see if there
9935 # is any difference.
9936 my $s1result = $ret;
9939 require "ljviews-s1-using-s2.pl"; # Load on demand
9940 $LJ::S1w2
::viewcreator
{$view}->(\
$ret, $u, \
%vars, $remote, $opts);
9942 if ($s1result ne $ret) {
9943 warn "S1w2 differed from S1 when rendering a $view page for $u->{user} with ".($remote ?
$remote->{user
} : "an anonymous user")." watching";
9945 # Optionally produce a diff between S1 and S1w2
9946 # NOTE: This _make_diff function hits the filesystem and forks a diff process.
9947 # It's only useful/sensible on a low-load development server.
9948 if ($LJ::SHOW_S1w2_DIFFS
) {
9949 $ret .= "<plaintext>".LJ
::S1w2
::_make_diff
($s1result, $ret);
9956 my $errcode = $opts->{'errcode'};
9958 'nodb' => 'Database temporarily unavailable during maintenance.',
9959 'nosyn' => 'No syndication URL available.',
9961 return "<!-- $errmsg -->" if ($opts->{'vhost'} eq "customview");
9963 # If not customview, set the error response code.
9964 $opts->{'status'} = {
9965 'nodb' => '503 Maintenance',
9966 'nosyn' => '404 Not Found',
9967 }->{$errcode} || '500 Server Error';
9971 if ($opts->{'redir'}) {
9975 # clean up attributes which we weren't able to quickly verify
9976 # as safe in the Storable-stored clean copy of the style.
9977 $ret =~ s/\%\%\[attr\[(.+?)\]\]\%\%/LJ::CleanHTML::s1_attribute_clean($1)/eg;
9984 # name: LJ::canonical_username
9985 # des: normalizes username.
9988 # returns: the canonical username given, or blank if the username is not well-formed
9990 sub canonical_username
9993 if ($user =~ /^\s*([A-Za-z0-9_\-]{1,15})\s*$/) {
9994 # perl 5.8 bug: $user = lc($1) sometimes causes corruption when $1 points into $user.
10000 return ""; # not a good username.
10004 # name: LJ::get_userid
10005 # des: Returns a userid given a username.
10006 # info: Results cached in memory. On miss, does DB call. Not advised
10007 # to use this many times in a row... only once or twice perhaps
10008 # per request. Tons of serialized db requests, even when small,
10009 # are slow. Opposite of [func[LJ::get_username]].
10011 # des-user: Username whose userid to look up.
10012 # returns: Userid, or 0 if invalid user.
10017 $user = LJ
::canonical_username
($user);
10019 if (exists $LJ::PRELOADED_USER_IDS
{$user} && !$LJ::IS_DEV_SERVER
) { return $LJ::PRELOADED_USER_IDS
{$user}; }
10020 if ($LJ::CACHE_USERID
{$user}) { return $LJ::CACHE_USERID
{$user}; }
10022 my $userid = LJ
::MemCacheProxy
::get
("uidof:$user");
10023 return $LJ::CACHE_USERID
{$user} = $userid if $userid;
10025 my $dbr = LJ
::get_db_reader
();
10026 $userid = $dbr->selectrow_array("SELECT userid FROM useridmap WHERE user=?", undef, $user);
10028 # implicitly create an account if we're using an external
10030 if (! $userid && ref $LJ::AUTH_EXISTS
eq "CODE")
10032 $userid = LJ
::create_account
({ 'user' => $user,
10034 'password' => '', });
10038 $LJ::CACHE_USERID
{$user} = $userid;
10039 LJ
::MemCacheProxy
::set
("uidof:$user", $userid);
10042 return ($userid+0);
10045 # TODO: Rewrite that function in more optimal way!
10046 sub get_userid_multi
{
10050 for my $user ( @
$users ) {
10051 my $userid = LJ
::get_userid
( $user );
10052 push @res, $userid if $userid;
10059 # name: LJ::want_user
10060 # des: Returns user object when passed either userid or user object. Useful to functions that
10061 # want to accept either.
10063 # des-user: Either a userid or a user hash with the userid in its 'userid' key.
10064 # returns: The user object represented by said userid or username.
10069 return undef unless $uuid;
10070 return $uuid if ref $uuid;
10071 return LJ
::load_userid
($uuid) if $uuid =~ /^\d+$/;
10072 Carp
::croak
("Bogus caller of LJ::want_user with non-ref/non-numeric parameter: $uuid");
10076 # name: LJ::get_username
10077 # des: Returns a username given a userid.
10078 # info: Results cached in memory. On miss, does DB call. Not advised
10079 # to use this many times in a row... only once or twice perhaps
10080 # per request. Tons of serialized db requests, even when small,
10081 # are slow. Opposite of [func[LJ::get_userid]].
10083 # des-user: Username whose userid to look up.
10084 # returns: Userid, or 0 if invalid user.
10087 my $userid = shift;
10090 # Checked the cache first.
10091 if ($LJ::CACHE_USERNAME
{$userid}) { return $LJ::CACHE_USERNAME
{$userid}; }
10093 # if we're using memcache, it's faster to just query memcache for
10094 # an entire $u object and just return the username. otherwise, we'll
10095 # go ahead and query useridmap
10096 if (@LJ::MEMCACHE_SERVERS
) {
10097 my $u = LJ
::load_userid
($userid);
10098 return undef unless $u;
10100 $LJ::CACHE_USERNAME
{$userid} = $u->{'user'};
10101 return $u->{'user'};
10104 my $dbr = LJ
::get_db_reader
();
10105 my $user = $dbr->selectrow_array("SELECT user FROM useridmap WHERE userid=?", undef, $userid);
10107 # Fall back to master if it doesn't exist.
10108 unless (defined $user) {
10109 my $dbh = LJ
::get_db_writer
();
10110 $user = $dbh->selectrow_array("SELECT user FROM useridmap WHERE userid=?", undef, $userid);
10113 return undef unless defined $user;
10115 $LJ::CACHE_USERNAME
{$userid} = $user;
10120 # name: LJ::can_manage_other
10121 # des: Given a user and a target user, will determine if the first user is an
10122 # admin for the target user, but not if the two are the same.
10124 # des-remote: user object or userid of user to try and authenticate
10125 # des-u: user object or userid of target user
10126 # returns: bool: true if authorized, otherwise fail
10128 sub can_manage_other
{
10129 my ($remote, $u) = @_;
10130 return 0 if LJ
::want_userid
($remote) == LJ
::want_userid
($u);
10131 $remote = LJ
::want_user
($remote);
10132 return $remote && $remote->can_manage($u);
10135 sub can_delete_journal_item
{
10136 my ($remote, $u, $itemid) = @_;
10137 $remote = LJ
::want_user
($remote);
10139 return 0 unless $remote;
10141 return 0 unless $remote->can_manage($u);
10142 # here admin or supermaintainer
10144 return 0 if $LJ::JOURNALS_WITH_PROTECTED_CONTENT
{ $u->{user
} } and !LJ
::is_friend
($u, $remote);
10151 # name: LJ::get_remote
10152 # des: authenticates the user at the remote end based on their cookies
10153 # and returns a hashref representing them.
10155 # des-opts: 'criterr': scalar ref to set critical error flag. if set, caller
10156 # should stop processing whatever it's doing and complain
10157 # about an invalid login with a link to the logout page.
10158 # 'ignore_ip': ignore IP address of remote for IP-bound sessions
10159 # returns: hashref containing 'user' and 'userid' if valid user, else
10163 my $opts = ref $_[0] eq "HASH" ?
shift : {};
10165 return $LJ::CACHE_REMOTE
if $LJ::CACHED_REMOTE
&& ! $opts->{'ignore_ip'};
10167 my $no_remote = sub {
10168 LJ
::User
->set_remote(undef);
10172 # can't have a remote user outside of web context
10173 return $no_remote->() unless LJ
::Request
->is_inited;
10175 my $get_as = LJ
::Request
->get_param('as');
10176 if ( $LJ::IS_DEV_SERVER
&& $get_as =~ /^\w{1,15}$/ ) {
10177 my $ru = LJ
::load_user
($get_as);
10179 # might be undef, to allow for "view as logged out":
10180 LJ
::set_remote
($ru);
10184 my $criterr = $opts->{criterr
} || do { my $d; \
$d; };
10187 $LJ::CACHE_REMOTE_BOUNCE_URL
= "";
10189 # set this flag if any of their ljsession cookies contained the ".FS"
10190 # opt to use the fast server. if we later find they're not logged
10191 # in and set it, or set it with a free account, then we give them
10192 # the invalid cookies error.
10193 my $tried_fast = 0;
10194 my $sessobj = LJ
::Session
->session_from_cookies(
10195 tried_fast
=> \
$tried_fast,
10196 redirect_ref
=> \
$LJ::CACHE_REMOTE_BOUNCE_URL
,
10197 ignore_ip
=> $opts->{ignore_ip
},
10200 my $u = $sessobj ?
$sessobj->owner : undef;
10202 # inform the caller that this user is faking their fast-server cookie
10204 if ($tried_fast && ! LJ
::get_cap
($u, "fastserver")) {
10208 return $no_remote->() unless $sessobj;
10210 # renew soon-to-expire sessions
10211 $sessobj->try_renew;
10213 # augment hash with session data;
10214 $u->{'_session'} = $sessobj;
10216 # keep track of activity for the user we just loaded from db/memcache
10217 # - if necessary, this code will actually run in Apache's cleanup handler
10218 # so latency won't affect the user
10219 if (@LJ::MEMCACHE_SERVERS
&& ! $LJ::DISABLED
{active_user_tracking
}) {
10220 push @LJ::CLEANUP_HANDLERS
, sub { $u->note_activity('A') };
10223 LJ
::User
->set_remote($u);
10224 LJ
::Request
->notes("ljuser" => $u->{'user'});
10228 # returns either $remote or the authenticated user that $remote is working with
10229 sub get_effective_remote
{
10230 my $authas_arg = shift || "authas";
10232 return undef unless LJ
::is_web_context
();
10234 my $remote = LJ
::get_remote
();
10235 return undef unless $remote;
10237 my $authas = $BMLCodeBlock::GET
{authas
} || $BMLCodeBlock::POST
{authas
} || $remote->user;
10238 return $remote if $authas eq $remote->user;
10240 return LJ
::get_authas_user
($authas);
10243 # returns URL we have to bounce the remote user to in order to
10244 # get their domain cookie
10245 sub remote_bounce_url
{
10246 return $LJ::CACHE_REMOTE_BOUNCE_URL
;
10250 my $remote = shift;
10251 LJ
::User
->set_remote($remote);
10257 LJ
::User
->unset_remote;
10261 sub get_active_journal
10263 return $LJ::ACTIVE_JOURNAL
;
10266 sub set_active_journal
10268 $LJ::ACTIVE_JOURNAL
= shift;
10271 # Checks if they are flagged as having a bad password and redirects
10272 # to changepassword.bml. If returl is on it returns the URL to
10273 # redirect to vs doing the redirect itself. Useful in non-BML context
10274 # and for QuickReply links
10275 sub bad_password_redirect
{
10278 my $remote = LJ
::get_remote
();
10279 return undef unless $remote;
10281 return undef if $LJ::DISABLED
{'force_pass_change'};
10283 return undef unless $remote->prop('badpassword');
10285 my $redir = "$LJ::SITEROOT/changepassword.bml";
10286 unless (defined $opts->{'returl'}) {
10287 return BML
::redirect
($redir);
10293 # Returns HTML to display user search results
10296 # users => hash ref of userid => u object like LJ::load userids
10297 # returns or array ref of user objects
10298 # userids => array ref of userids to include in results, ignored
10299 # if users is defined
10300 # timesort => set to 1 to sort by last updated instead
10302 # perpage => Enable pagination and how many users to display on
10304 # curpage => What page of results to display
10305 # navbar => Scalar reference for paging bar
10306 # pickwd => userpic keyword to display instead of default if it
10307 # exists for the user
10308 # self_link => Sub ref to generate link to use for pagination
10309 sub user_search_display
{
10313 unless (defined $args{users
}) {
10314 $loaded_users = LJ
::load_userids
(@
{$args{userids
}});
10316 if (ref $args{users
} eq 'HASH') { # Assume this is direct from LJ::load_userids
10317 $loaded_users = $args{users
};
10318 } elsif (ref $args{users
} eq 'ARRAY') { # They did a grep on it or something
10319 foreach (@
{$args{users
}}) {
10320 $loaded_users->{$_->{userid
}} = $_;
10327 # If we're sorting by last updated, we need to load that
10328 # info for all users before the sort. If sorting by
10329 # username we can load it for a subset of users later,
10334 if ($args{timesort
}) {
10335 $updated = LJ
::get_timeupdate_multi
(keys %$loaded_users);
10336 @display = sort { $updated->{$b->{userid
}} <=> $updated->{$a->{userid
}} } values %$loaded_users;
10338 @display = sort { $a->{user
} cmp $b->{user
} } values %$loaded_users;
10341 if (defined $args{perpage
}) {
10342 my %items = BML
::paging
(\
@display, $args{curpage
}, $args{perpage
});
10346 $opts->{self_link
} = $args{self_link
} if $args{self_link
};
10347 ${$args{navbar
}} = LJ
::paging_bar
($items{'page'}, $items{'pages'}, $opts);
10349 # Now pull out the set of users to display
10350 @display = @
{$items{'items'}};
10353 # If we aren't sorting by time updated, load last updated time for the
10354 # set of users we are displaying.
10355 $updated = LJ
::get_timeupdate_multi
(map { $_->{userid
} } @display)
10356 unless $args{timesort
};
10358 # Allow caller to specify a custom userpic to use instead
10359 # of the user's default all userpics
10360 my $get_picid = sub {
10362 return $u->{'defaultpicid'} unless $args{'pickwd'};
10363 return LJ
::get_picid_from_keyword
($u, $args{'pickwd'});
10367 foreach my $u (@display) {
10368 # We should always have loaded user objects, but it seems
10369 # when the site is overloaded we don't always load the users
10371 next unless LJ
::isu
($u);
10373 $ret .= "<div style='width: 300px; height: 105px; overflow: hidden; float: left; ";
10374 $ret .= "border-bottom: 1px solid <?altcolor2?>; margin-bottom: 10px; padding-bottom: 5px; margin-right: 10px'>";
10375 $ret .= "<table style='height: 105px'><tr>";
10377 $ret .= "<td style='width: 100px; text-align: center;'>";
10378 $ret .= "<a href='/allpics.bml?user=$u->{user}'>";
10379 if (my $picid = $get_picid->($u)) {
10380 $ret .= "<img src='$LJ::USERPIC_ROOT/$picid/$u->{userid}' alt='$u->{user} userpic' style='border: 1px solid #000;' />";
10382 $ret .= "<img src='$LJ::STATPREFIX/horizon/nouserpic.png?v=2621' alt='no default userpic' style='border: 1px solid #000;' width='100' height='100' />";
10386 $ret .= "</td><td style='padding-left: 5px;' valign='top'><table>";
10388 $ret .= "<tr><td class='searchusername' colspan='2' style='text-align: left;'>";
10389 $ret .= $u->ljuser_display({ head_size
=> $args{head_size
} });
10390 $ret .= "</td></tr><tr>";
10393 $ret .= "<td width='1%' style='font-size: smaller' valign='top'>Name:</td><td style='font-size: smaller'><a href='" . $u->profile_url . "'>";
10394 $ret .= LJ
::ehtml
($u->{name
});
10396 $ret .= "</td></tr><tr>";
10399 if (my $jtitle = $u->prop('journaltitle')) {
10400 $ret .= "<td width='1%' style='font-size: smaller' valign='top'>Journal:</td><td style='font-size: smaller'><a href='" . $u->journal_base . "'>";
10401 $ret .= LJ
::ehtml
($jtitle) . "</a>";
10402 $ret .= "</td></tr>";
10405 $ret .= "<tr><td colspan='2' style='text-align: left; font-size: smaller' class='lastupdated'>";
10407 if ($updated->{$u->{'userid'}} > 0) {
10408 $ret .= "Updated ";
10409 $ret .= LJ
::TimeUtil
->ago_text(time() - $updated->{$u->{'userid'}});
10411 $ret .= "Never updated";
10414 $ret .= "</td></tr>";
10416 $ret .= "</table>";
10417 $ret .= "</td></tr>";
10418 $ret .= "</table></div>";
10424 # returns the country that the remote IP address comes from
10425 # undef is returned if the country cannot be determined from the IP
10426 sub country_of_remote_ip
{
10427 my $ip = LJ
::get_remote_ip
();
10428 return undef unless $ip;
10430 if (LJ
::GeoLocation
->can('get_country_info_by_ip')) {
10431 ## use module LJ::GeoLocation if it's installed
10432 return LJ
::GeoLocation
->get_country_info_by_ip($ip)
10433 } elsif (eval "use IP::Country::Fast; 1;") {
10434 my $reg = IP
::Country
::Fast
->new();
10435 my $country = $reg->inet_atocc($ip);
10437 # "**" is returned if the IP is private
10438 return undef if $country eq "**";
10445 sub get_aggregated_user
{
10446 my ($row, $opts) = @_;
10448 my $user = eval { LJ
::load_userid
($row->{userid
}) };
10450 return unless $user;
10452 return unless $opts->{attrs
} && ref $opts->{attrs
};
10454 my @identity_methods;
10456 foreach my $method (@
{$opts->{attrs
}}) {
10457 if($method =~ /^identity_(.+)/) {
10458 push @identity_methods, $1;
10462 my @result = eval {$user->$method};
10463 ($row->{$method}) = @result > 1 ? \
@result : @result;
10466 return unless (@identity_methods && $user->is_identity);
10468 my $i = $user->identity;
10470 foreach my $method (@identity_methods) {
10471 my @result = eval {$i->$method};
10472 ($row->{'identity_'.$method}) = @result > 1 ? \
@result : @result;
10476 # Return friends with type
10484 sub get_friends_with_type
{
10485 my ($u, $options) = @_;
10487 my $types = $options->{types
};
10488 my $limit = $options->{limit
};
10490 die "no user" unless $u;
10491 die "no type" unless $types;
10493 my %allow_list = map { $_ => 1 } @
$types;
10496 # Exclude some friends types to type P.
10498 if ($allow_list{'P'}) {
10499 my %types_data = map { $_ => 1 } @
$types;
10501 my @types_list = ('I', 'Y', 'N', 'C');
10502 my @types_to_load = ();
10505 # May do not need to exclude all friends
10507 foreach my $type (@types_list) {
10508 push @types_to_load, $type
10509 unless $types_data{$type};
10512 my @exclude = get_friends_with_type
($u, { types
=> \
@types_to_load,
10513 limit
=> $limit });
10515 my %exclude_list = map { $_ => 1 } @exclude;
10516 my @friends = $u->friend_uids(limit
=> $limit);
10517 my @list = grep { !$exclude_list{$_} } @friends;
10522 #mnenonic User:FriendsList:
10523 my @keys = map { "u:fl:" . $u->userid . ":$_"} @
$types ;
10525 my $redis = LJ
::Redis
->get_connection();
10528 foreach my $key (@keys) {
10529 my @result = $redis->smembers($key);
10530 push @list, @result if @result;
10533 return @list if @list;
10536 # get and set a list
10538 my @friends = $u->friend_uids();
10539 my $friends_data = LJ
::get_journal_short_info_multi
(@friends);
10541 my @typed_journals = ();
10542 my %put_in_cache = ();
10543 foreach my $friend (@friends) {
10544 my $friend_info = $friends_data->{$friend};
10545 next if $friend_info->{statusvis
} eq 'X' ||
10546 $friend_info->{clusterid
} == 0;
10548 my $type = $friend_info->{journaltype
};
10549 next unless $allow_list{$type};
10551 push @
{$put_in_cache{$type}}, $friend if $redis;
10552 push @typed_journals, $friend;
10556 foreach my $type (keys %put_in_cache) {
10557 my $key = "u:fl:" . $u->userid . ":$type";
10558 $redis->sadd($key, @
{$put_in_cache{$type}});
10559 $redis->expire($key, 60 * 60);
10563 return @typed_journals;
10566 sub remove_from_friend_list
{
10567 my ($u, $friend) = @_;
10569 my $type = $friend->journaltype;
10570 my $key = "u:fl:" . $u->userid . ":$type";
10571 my $redis = LJ
::Redis
->get_connection();
10573 $redis->srem($key, $friend);
10577 sub add_to_friend_list
{
10578 my ($u, $friend) = @_;
10580 my $type = $friend->journaltype;
10581 my $key = "u:fl:" . $u->userid . ":$type";
10582 my $redis = LJ
::Redis
->get_connection();
10584 if ($redis && $redis->exists($key)) {
10585 $redis->sadd($key, $friend);
10589 sub get_journal_short_info_multi
{
10593 foreach my $userid (@userids) {
10594 push @keys, "u:s:$userid";
10597 my %final_result = ();
10599 my $result = LJ
::MemCache
::get_multi
(@keys);
10601 my @users_to_load = ();
10602 foreach my $userid (@userids) {
10603 my $data = delete $result->{"u:s:$userid"};
10605 push @users_to_load, $userid;
10607 my %user_result = ();
10609 my ($status, $cid, $type) = split(/:/, $data);
10610 $user_result{statusvis
} = $status;
10611 $user_result{clusterid
} = $cid;
10612 $user_result{journaltype
} = $type;
10614 $final_result{$userid} = \
%user_result;
10618 my $users = LJ
::load_userids
(@users_to_load);
10620 foreach my $userid (@users_to_load) {
10621 my $user = $users->{$userid};
10624 my $status = $user->{statusvis
};
10625 my $cid = $user->{clusterid
};
10626 my $type = $user->{journaltype
};
10628 my %user_result = ();
10630 $user_result{statusvis
} = $status;
10631 $user_result{clusterid
} = $cid;
10632 $user_result{journaltype
} = $type;
10634 $final_result{$userid} = \
%user_result;
10636 my $cache = join(':', $status, $cid, $type);
10637 my $expire_time = time + 60*60*24*30;
10638 LJ
::MemCache
::set
("u:s:$userid", $cache, $expire_time);
10642 return \
%final_result;