976 lines
29 KiB
Perl
976 lines
29 KiB
Perl
|
|
# This code was forked from the LiveJournal project owned and operated
|
||
|
|
# by Live Journal, Inc. The code has been modified and expanded by
|
||
|
|
# Dreamwidth Studios, LLC. These files were originally licensed under
|
||
|
|
# the terms of the license supplied by Live Journal, Inc, which can
|
||
|
|
# currently be found at:
|
||
|
|
#
|
||
|
|
# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
|
||
|
|
#
|
||
|
|
# In accordance with the original license, this code and all its
|
||
|
|
# modifications are provided under the GNU General Public License.
|
||
|
|
# A copy of that license can be found in the LICENSE file included as
|
||
|
|
# part of this distribution.
|
||
|
|
|
||
|
|
package LJ::User;
|
||
|
|
use strict;
|
||
|
|
no warnings 'uninitialized';
|
||
|
|
|
||
|
|
use Carp;
|
||
|
|
use Digest::SHA1;
|
||
|
|
use Text::Fuzzy;
|
||
|
|
use LJ::Subscription;
|
||
|
|
|
||
|
|
########################################################################
|
||
|
|
### 16. Email-Related Functions
|
||
|
|
|
||
|
|
=head2 Email-Related Functions
|
||
|
|
=cut
|
||
|
|
|
||
|
|
sub accounts_by_email {
|
||
|
|
my ( $u, $email ) = @_;
|
||
|
|
$email ||= $u->email_raw if LJ::isu($u);
|
||
|
|
return undef unless $email;
|
||
|
|
|
||
|
|
my $dbr = LJ::get_db_reader() or die "Couldn't get db reader";
|
||
|
|
my $userids =
|
||
|
|
$dbr->selectcol_arrayref( "SELECT userid FROM email WHERE email=?", undef, $email );
|
||
|
|
die $dbr->errstr if $dbr->err;
|
||
|
|
return $userids ? @$userids : ();
|
||
|
|
}
|
||
|
|
|
||
|
|
sub delete_email_alias {
|
||
|
|
my $u = $_[0];
|
||
|
|
|
||
|
|
my $dbh = LJ::get_db_writer();
|
||
|
|
$dbh->do( "DELETE FROM email_aliases WHERE alias=?", undef, $u->site_email_alias );
|
||
|
|
|
||
|
|
return 0 if $dbh->err;
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub email_for_feeds {
|
||
|
|
my $u = shift;
|
||
|
|
|
||
|
|
# don't display if it's mangled
|
||
|
|
return if $u->prop("opt_mangleemail") eq "Y";
|
||
|
|
|
||
|
|
my $remote = LJ::get_remote();
|
||
|
|
return $u->email_visible($remote);
|
||
|
|
}
|
||
|
|
|
||
|
|
sub email_raw {
|
||
|
|
my $u = shift;
|
||
|
|
my $userid = $u->userid;
|
||
|
|
$u->{_email} ||= LJ::MemCache::get_or_set(
|
||
|
|
[ $userid, "email:$userid" ],
|
||
|
|
sub {
|
||
|
|
my $dbh = LJ::get_db_writer() or die "Couldn't get db master";
|
||
|
|
return $dbh->selectrow_array( "SELECT email FROM email WHERE userid=?",
|
||
|
|
undef, $userid );
|
||
|
|
}
|
||
|
|
);
|
||
|
|
return $u->{_email};
|
||
|
|
}
|
||
|
|
|
||
|
|
sub has_same_email_as {
|
||
|
|
my ( $u, $other ) = @_;
|
||
|
|
croak "invalid user object passed" unless LJ::isu($u) && LJ::isu($other);
|
||
|
|
|
||
|
|
my $email_1 = lc( $u->email_raw );
|
||
|
|
my $email_2 = lc( $other->email_raw );
|
||
|
|
return 1 if $email_1 eq $email_2;
|
||
|
|
|
||
|
|
# if unequal, try stripping any +mailbox addressing
|
||
|
|
$email_1 =~ s/\+[^@]+@/@/;
|
||
|
|
$email_2 =~ s/\+[^@]+@/@/;
|
||
|
|
return $email_1 eq $email_2;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub email_status {
|
||
|
|
my $u = shift;
|
||
|
|
return $u->{status};
|
||
|
|
}
|
||
|
|
|
||
|
|
# in scalar context, returns user's email address. given a remote user,
|
||
|
|
# bases decision based on whether $remote user can see it. in list context,
|
||
|
|
# returns all emails that can be shown
|
||
|
|
sub email_visible {
|
||
|
|
my ( $u, $remote ) = @_;
|
||
|
|
|
||
|
|
return scalar $u->emails_visible($remote);
|
||
|
|
}
|
||
|
|
|
||
|
|
# returns an array of emails based on the user's display prefs
|
||
|
|
# A: actual email address
|
||
|
|
# D: display email address
|
||
|
|
# L: local email address
|
||
|
|
# B: both actual + local email address
|
||
|
|
# V: both display + local email address
|
||
|
|
|
||
|
|
sub emails_visible {
|
||
|
|
my ( $u, $remote ) = @_;
|
||
|
|
|
||
|
|
return () if $u->is_identity || $u->is_syndicated;
|
||
|
|
|
||
|
|
# security controls
|
||
|
|
return () unless $u->share_contactinfo($remote);
|
||
|
|
|
||
|
|
my $whatemail = $u->opt_whatemailshow;
|
||
|
|
|
||
|
|
# some classes of users we want to have their contact info hidden
|
||
|
|
# after so much time of activity, to prevent people from bugging
|
||
|
|
# them for their account or trying to brute force it.
|
||
|
|
my $hide_contactinfo = sub {
|
||
|
|
return 0 if $LJ::IS_DEV_SERVER;
|
||
|
|
my $hide_after = $u->get_cap("hide_email_after");
|
||
|
|
return 0 unless $hide_after;
|
||
|
|
my $active = $u->get_timeactive;
|
||
|
|
return $active && ( time() - $active ) > $hide_after * 86400;
|
||
|
|
};
|
||
|
|
|
||
|
|
return () if $whatemail eq "N" || $hide_contactinfo->();
|
||
|
|
|
||
|
|
my @emails = ();
|
||
|
|
|
||
|
|
if ( $whatemail eq "A" || $whatemail eq "B" ) {
|
||
|
|
push @emails, $u->email_raw if $u->email_raw;
|
||
|
|
}
|
||
|
|
elsif ( $whatemail eq "D" || $whatemail eq "V" ) {
|
||
|
|
my $profile_email = $u->prop('opt_profileemail');
|
||
|
|
push @emails, $profile_email if $profile_email;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $whatemail eq "B" || $whatemail eq "V" || $whatemail eq "L" ) {
|
||
|
|
push @emails, $u->site_email_alias
|
||
|
|
unless $u->prop('no_mail_alias');
|
||
|
|
}
|
||
|
|
return wantarray ? @emails : $emails[0];
|
||
|
|
}
|
||
|
|
|
||
|
|
sub is_validated {
|
||
|
|
my $u = shift;
|
||
|
|
return $u->email_status eq "A";
|
||
|
|
}
|
||
|
|
|
||
|
|
# return the setting indicating how a user can be found by their email address
|
||
|
|
# Y - Findable, N - Not findable, H - Findable but identity hidden
|
||
|
|
sub opt_findbyemail {
|
||
|
|
my $u = shift;
|
||
|
|
|
||
|
|
if ( $u->raw_prop('opt_findbyemail') =~ /^(N|Y|H)$/ ) {
|
||
|
|
return $u->raw_prop('opt_findbyemail');
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
return undef;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# initiate reset of user's email
|
||
|
|
# newemail: the new address provided (not validated?)
|
||
|
|
# err: reference for error messages
|
||
|
|
# emailsucc: send email if defined, report success if reference
|
||
|
|
# update_opts: additional options for the update_user call
|
||
|
|
sub reset_email {
|
||
|
|
my ( $u, $newemail, $err, $emailsucc, $update_opts ) = @_;
|
||
|
|
my $errsub = sub { $$err = $_[0] if ref $err; return undef };
|
||
|
|
|
||
|
|
my $dbh = LJ::get_db_writer();
|
||
|
|
$dbh->do( "UPDATE infohistory SET what='emailreset'" . " WHERE userid=? AND what='email'",
|
||
|
|
undef, $u->id )
|
||
|
|
or return $errsub->( LJ::Lang::ml("error.dberror") . $dbh->errstr );
|
||
|
|
|
||
|
|
$u->infohistory_add( 'emailreset', $u->email_raw, $u->email_status )
|
||
|
|
if $u->email_raw ne $newemail; # record only if it changed
|
||
|
|
|
||
|
|
$update_opts ||= { status => 'T' };
|
||
|
|
$update_opts->{email} = $newemail;
|
||
|
|
|
||
|
|
# this is no longer done using update_self
|
||
|
|
my $changepass = delete $update_opts->{password};
|
||
|
|
if ( defined $changepass ) {
|
||
|
|
$u->set_password($changepass);
|
||
|
|
}
|
||
|
|
|
||
|
|
$u->update_self($update_opts)
|
||
|
|
or return $errsub->( LJ::Lang::ml( "email.emailreset.error", { user => $u->user } ) );
|
||
|
|
|
||
|
|
if ($LJ::T_SUPPRESS_EMAIL) {
|
||
|
|
$$emailsucc = 1 if ref $emailsucc; # pretend we sent it
|
||
|
|
}
|
||
|
|
elsif ( defined $emailsucc ) {
|
||
|
|
my $aa = LJ::register_authaction( $u->id, "validateemail", $newemail );
|
||
|
|
my $auth = "$aa->{aaid}.$aa->{authcode}";
|
||
|
|
my $sent = LJ::send_mail(
|
||
|
|
{
|
||
|
|
to => $newemail,
|
||
|
|
from => $LJ::ADMIN_EMAIL,
|
||
|
|
subject => LJ::Lang::ml("email.emailreset.subject"),
|
||
|
|
body => LJ::Lang::ml(
|
||
|
|
$changepass
|
||
|
|
? (
|
||
|
|
"email.emailreset.body_withpasswd",
|
||
|
|
{
|
||
|
|
user => $u->user,
|
||
|
|
newpass => $changepass,
|
||
|
|
sitename => $LJ::SITENAME,
|
||
|
|
siteroot => "$LJ::SITEROOT/",
|
||
|
|
auth => $auth
|
||
|
|
}
|
||
|
|
)
|
||
|
|
: (
|
||
|
|
"email.emailreset.body",
|
||
|
|
{
|
||
|
|
user => $u->user,
|
||
|
|
sitename => $LJ::SITENAME,
|
||
|
|
siteroot => "$LJ::SITEROOT/",
|
||
|
|
auth => $auth
|
||
|
|
}
|
||
|
|
)
|
||
|
|
),
|
||
|
|
}
|
||
|
|
);
|
||
|
|
$$emailsucc = $sent if ref $emailsucc;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub set_email {
|
||
|
|
my ( $u, $email ) = @_;
|
||
|
|
return LJ::set_email( $u->id, $email );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub site_email_alias {
|
||
|
|
my $u = $_[0];
|
||
|
|
my $alias = $u->user . "\@$LJ::USER_DOMAIN";
|
||
|
|
return $alias;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub update_email_alias {
|
||
|
|
my $u = $_[0];
|
||
|
|
|
||
|
|
return unless $u && $u->can_have_email_alias;
|
||
|
|
return if $u->prop("no_mail_alias");
|
||
|
|
return unless $u->is_validated;
|
||
|
|
|
||
|
|
my $dbh = LJ::get_db_writer();
|
||
|
|
$dbh->do( "REPLACE INTO email_aliases (alias, rcpt) VALUES (?,?)",
|
||
|
|
undef, $u->site_email_alias, $u->email_raw );
|
||
|
|
|
||
|
|
return 0 if $dbh->err;
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub validated_mbox_sha1sum {
|
||
|
|
my $u = shift;
|
||
|
|
|
||
|
|
# must be validated
|
||
|
|
return undef unless $u->is_validated;
|
||
|
|
|
||
|
|
# must have one on file
|
||
|
|
my $email = $u->email_raw;
|
||
|
|
return undef unless $email;
|
||
|
|
|
||
|
|
# return SHA1, which does not disclose the actual value
|
||
|
|
return Digest::SHA1::sha1_hex( 'mailto:' . $email );
|
||
|
|
}
|
||
|
|
|
||
|
|
########################################################################
|
||
|
|
### 25. Subscription, Notifiction, and Messaging Functions
|
||
|
|
|
||
|
|
=head2 Subscription, Notifiction, and Messaging Functions
|
||
|
|
=cut
|
||
|
|
|
||
|
|
# this is the count used to check the maximum subscription count
|
||
|
|
sub active_inbox_subscription_count {
|
||
|
|
my $u = shift;
|
||
|
|
return scalar( grep { $_->active && $_->enabled } $u->find_subscriptions( method => 'Inbox' ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub can_add_inbox_subscription {
|
||
|
|
my $u = shift;
|
||
|
|
return $u->active_inbox_subscription_count >= $u->max_subscriptions ? 0 : 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# can this user use ESN?
|
||
|
|
sub can_use_esn {
|
||
|
|
my $u = $_[0];
|
||
|
|
return 0 if $u->is_community || $u->is_syndicated;
|
||
|
|
return LJ::is_enabled('esn');
|
||
|
|
}
|
||
|
|
|
||
|
|
# 1/0 if someone can send a message to $u
|
||
|
|
sub can_receive_message {
|
||
|
|
my ( $u, $sender ) = @_;
|
||
|
|
|
||
|
|
my $opt_usermsg = $u->opt_usermsg;
|
||
|
|
return 0 if $opt_usermsg eq 'N' || !$sender;
|
||
|
|
return 0 if $u->has_banned($sender);
|
||
|
|
return 0 if $opt_usermsg eq 'M' && !$u->mutually_trusts($sender);
|
||
|
|
return 0 if $opt_usermsg eq 'F' && !$u->trusts($sender);
|
||
|
|
|
||
|
|
my $u_age = $u->init_age;
|
||
|
|
my $s_age = $sender->init_age;
|
||
|
|
|
||
|
|
# init_age returns undef for init_bdate year 0000.
|
||
|
|
return 0
|
||
|
|
if defined($u_age)
|
||
|
|
&& $u_age < 18
|
||
|
|
&& ( !defined($s_age) || $s_age >= 18 );
|
||
|
|
return 0
|
||
|
|
if ( !defined($u_age) || $u_age >= 18 )
|
||
|
|
&& defined($s_age)
|
||
|
|
&& $s_age < 18;
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# delete all of a user's subscriptions
|
||
|
|
sub delete_all_subscriptions {
|
||
|
|
return LJ::Subscription->delete_all_subs(@_);
|
||
|
|
}
|
||
|
|
|
||
|
|
# delete all of a user's subscriptions
|
||
|
|
sub delete_all_inactive_subscriptions {
|
||
|
|
return LJ::Subscription->delete_all_inactive_subs(@_);
|
||
|
|
}
|
||
|
|
|
||
|
|
# ensure that this user does not have more than the maximum number of subscriptions
|
||
|
|
# allowed by their cap, and enable subscriptions up to their current limit
|
||
|
|
sub enable_subscriptions {
|
||
|
|
my $u = shift;
|
||
|
|
|
||
|
|
# first thing, disable everything they don't have caps for
|
||
|
|
# and make sure everything is enabled that should be enabled
|
||
|
|
map { $_->available_for_user($u) ? $_->enable : $_->disable }
|
||
|
|
$u->find_subscriptions( method => 'Inbox' );
|
||
|
|
|
||
|
|
my $max_subs = $u->get_cap('subscriptions');
|
||
|
|
my @inbox_subs = grep { $_->active && $_->enabled } $u->find_subscriptions( method => 'Inbox' );
|
||
|
|
|
||
|
|
if ( ( scalar @inbox_subs ) > $max_subs ) {
|
||
|
|
|
||
|
|
# oh no, too many subs.
|
||
|
|
# disable the oldest subscriptions that are "tracking" subscriptions
|
||
|
|
my @tracking = grep { $_->is_tracking_category } @inbox_subs;
|
||
|
|
|
||
|
|
# oldest subs first
|
||
|
|
@tracking = sort { return $a->createtime <=> $b->createtime; } @tracking;
|
||
|
|
|
||
|
|
my $need_to_deactivate = ( scalar @inbox_subs ) - $max_subs;
|
||
|
|
|
||
|
|
for ( 1 .. $need_to_deactivate ) {
|
||
|
|
my $sub_to_deactivate = shift @tracking;
|
||
|
|
$sub_to_deactivate->deactivate if $sub_to_deactivate;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
# make sure all subscriptions are activated
|
||
|
|
my $need_to_activate = $max_subs - ( scalar @inbox_subs );
|
||
|
|
|
||
|
|
# get deactivated subs
|
||
|
|
@inbox_subs = grep { $_->active && $_->available_for_user }
|
||
|
|
$u->find_subscriptions( method => 'Inbox' );
|
||
|
|
|
||
|
|
for ( 1 .. $need_to_activate ) {
|
||
|
|
my $sub_to_activate = shift @inbox_subs;
|
||
|
|
$sub_to_activate->activate if $sub_to_activate;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub esn_inbox_default_expand {
|
||
|
|
my $u = shift;
|
||
|
|
|
||
|
|
my $prop = $u->raw_prop('esn_inbox_default_expand');
|
||
|
|
return $prop ne 'N';
|
||
|
|
}
|
||
|
|
|
||
|
|
# search for a subscription
|
||
|
|
*find_subscriptions = \&has_subscription;
|
||
|
|
|
||
|
|
sub has_subscription {
|
||
|
|
my ( $u, %params ) = @_;
|
||
|
|
croak "No parameters" unless %params;
|
||
|
|
|
||
|
|
return LJ::Subscription->find( $u, %params );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub max_subscriptions {
|
||
|
|
my $u = shift;
|
||
|
|
return $u->get_cap('subscriptions');
|
||
|
|
}
|
||
|
|
|
||
|
|
# return the URL to the send message page
|
||
|
|
# respects $remote's beta inbox selection
|
||
|
|
sub message_url {
|
||
|
|
my $u = shift;
|
||
|
|
croak "invalid user object passed" unless LJ::isu($u);
|
||
|
|
|
||
|
|
return undef unless LJ::is_enabled('user_messaging');
|
||
|
|
|
||
|
|
my $remote = LJ::get_remote();
|
||
|
|
my $path =
|
||
|
|
( $remote && LJ::BetaFeatures->user_in_beta( $remote => "inbox" ) )
|
||
|
|
? "inbox/new/compose"
|
||
|
|
: "inbox/compose";
|
||
|
|
return "$LJ::SITEROOT/$path?user=" . $u->user;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub new_message_count {
|
||
|
|
my $u = shift;
|
||
|
|
my $inbox = $u->notification_inbox;
|
||
|
|
my $count = $inbox->unread_count;
|
||
|
|
|
||
|
|
return $count || 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub notification_archive {
|
||
|
|
my $u = shift;
|
||
|
|
return LJ::NotificationArchive->new($u);
|
||
|
|
}
|
||
|
|
|
||
|
|
# Returns the NotificationInbox for this user
|
||
|
|
*inbox = \¬ification_inbox;
|
||
|
|
|
||
|
|
sub notification_inbox {
|
||
|
|
my $u = shift;
|
||
|
|
return LJ::NotificationInbox->new($u);
|
||
|
|
}
|
||
|
|
|
||
|
|
# opt_usermsg options
|
||
|
|
# Y - Registered Users
|
||
|
|
# F - Trusted Users
|
||
|
|
# M - Mutually Trusted Users
|
||
|
|
# N - Nobody
|
||
|
|
sub opt_usermsg {
|
||
|
|
my $u = shift;
|
||
|
|
my $prop = $u->raw_prop('opt_usermsg');
|
||
|
|
|
||
|
|
if ( defined $prop && $prop =~ /^(Y|F|M|N)$/ ) {
|
||
|
|
return $prop;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
return 'Y';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# extracted from LJ::subscribe_interface
|
||
|
|
sub pending_sub_data {
|
||
|
|
my ( $u, $pending_sub ) = @_;
|
||
|
|
my %ret;
|
||
|
|
|
||
|
|
$ret{upgrade_notice} = ( !$u->is_paid && $pending_sub->disabled($u) ) ? " †" : "";
|
||
|
|
|
||
|
|
if ( !ref $pending_sub ) {
|
||
|
|
$ret{special_sub} = 1;
|
||
|
|
|
||
|
|
return if $u->is_identity && $pending_sub->disabled($u);
|
||
|
|
|
||
|
|
$ret{inactive} = $pending_sub->disabled($u);
|
||
|
|
$ret{hidden} = !$pending_sub->selected($u);
|
||
|
|
|
||
|
|
return \%ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
return if $u->is_identity && !$pending_sub->enabled;
|
||
|
|
return unless $ret{input_name} = $pending_sub->freeze;
|
||
|
|
|
||
|
|
my $title = $pending_sub->as_html;
|
||
|
|
return unless $title;
|
||
|
|
$title .= $ret{upgrade_notice} unless $pending_sub->enabled;
|
||
|
|
|
||
|
|
$ret{title} = $title;
|
||
|
|
$ret{subscribed} = !$pending_sub->pending;
|
||
|
|
$ret{disabled} = !$pending_sub->enabled;
|
||
|
|
$ret{inactive} = !$pending_sub->active;
|
||
|
|
$ret{selected} = $pending_sub->default_selected;
|
||
|
|
|
||
|
|
# notification options for this subscription are hidden if not subscribed
|
||
|
|
$ret{hidden} = !$ret{selected} && ( !$ret{subscribed} || $ret{inactive} );
|
||
|
|
|
||
|
|
return \%ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
# extracted from /manage/settings
|
||
|
|
sub save_subscriptions {
|
||
|
|
my ( $u, $post_args ) = @_;
|
||
|
|
|
||
|
|
my @notif_errors;
|
||
|
|
my @sub_edit;
|
||
|
|
my @to_consider;
|
||
|
|
my @to_activate;
|
||
|
|
|
||
|
|
foreach my $postkey ( keys %$post_args ) {
|
||
|
|
my $subscr;
|
||
|
|
my $is_old = $postkey =~ /-old$/;
|
||
|
|
|
||
|
|
# are there other options for this pending subscription?
|
||
|
|
# if so, process those not this one
|
||
|
|
next if $postkey =~ /\.arg\d/;
|
||
|
|
|
||
|
|
$subscr = LJ::Subscription->thaw( $postkey, $u, $post_args ) or next;
|
||
|
|
|
||
|
|
if ( $subscr->pending ) {
|
||
|
|
push @to_consider, $subscr;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
push @to_activate, $subscr if !$is_old && !$subscr->active;
|
||
|
|
}
|
||
|
|
|
||
|
|
next unless $is_old;
|
||
|
|
my $old_postkey = $postkey;
|
||
|
|
|
||
|
|
# remove old string
|
||
|
|
$postkey =~ s/-old$//;
|
||
|
|
|
||
|
|
my $oldvalue = $post_args->{$old_postkey};
|
||
|
|
my $checked = $post_args->{$postkey};
|
||
|
|
|
||
|
|
push @sub_edit, [ $subscr, $checked, $oldvalue ];
|
||
|
|
}
|
||
|
|
|
||
|
|
# first process deletions
|
||
|
|
foreach my $edit_info (@sub_edit) {
|
||
|
|
my ( $subscr, $checked, $oldvalue ) = @$edit_info;
|
||
|
|
|
||
|
|
if ( !$checked && $oldvalue && $subscr->method->configured_for_user($u) ) {
|
||
|
|
|
||
|
|
# if it's not checked and is currently a real subscription, deactivate it
|
||
|
|
# unless we disabled it for them (disabled checkboxes don't POST)
|
||
|
|
$subscr->deactivate;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# then process new subs and activations
|
||
|
|
foreach my $subscr (@to_activate) {
|
||
|
|
my @inbox_subs =
|
||
|
|
grep { $_->active && $_->enabled } $u->find_subscriptions( method => 'Inbox' );
|
||
|
|
|
||
|
|
if ( @inbox_subs >= $u->max_subscriptions ) {
|
||
|
|
|
||
|
|
# too many, sorry
|
||
|
|
push @notif_errors, LJ::errobj( "Subscription::TooMany", subscr => $subscr, u => $u );
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
# all is good, reactivate it
|
||
|
|
$subscr->activate;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Define limits
|
||
|
|
my $paid_max = LJ::get_cap( 'paid', 'subscriptions' );
|
||
|
|
my $u_max = $u->max_subscriptions;
|
||
|
|
|
||
|
|
# max for total number of subscriptions (generally it is $paid_max)
|
||
|
|
my $system_max = $u_max > $paid_max ? $u_max : $paid_max;
|
||
|
|
|
||
|
|
my $inbox_ntypeid = LJ::NotificationMethod::Inbox->ntypeid;
|
||
|
|
my @other_ntypeid_to_consider;
|
||
|
|
|
||
|
|
my $process_pending = sub {
|
||
|
|
my ( $subscr, $method ) = @_;
|
||
|
|
|
||
|
|
my @all_subs = $u->find_subscriptions( method => $method );
|
||
|
|
my @active_subs = grep { $_->active && $_->enabled } @all_subs;
|
||
|
|
|
||
|
|
if ( @active_subs >= $u_max ) {
|
||
|
|
push @notif_errors, LJ::errobj( "Subscription::TooMany", subscr => $subscr, u => $u );
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
if ( @all_subs >= $system_max ) {
|
||
|
|
push @notif_errors,
|
||
|
|
LJ::errobj(
|
||
|
|
"Subscription::TooManySystemMax",
|
||
|
|
subscr => $subscr,
|
||
|
|
u => $u,
|
||
|
|
max => $system_max
|
||
|
|
);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
return 1;
|
||
|
|
};
|
||
|
|
|
||
|
|
# process new inbox subs
|
||
|
|
foreach my $subscr (@to_consider) {
|
||
|
|
if ( $subscr->ntypeid != $inbox_ntypeid ) {
|
||
|
|
|
||
|
|
# save this for consideration after we've processed all inbox subscriptions first
|
||
|
|
push @other_ntypeid_to_consider, $subscr;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
|
||
|
|
# this is an inbox subscription, save it
|
||
|
|
$subscr->commit if $process_pending->( $subscr, 'Inbox' );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# process all other new subs
|
||
|
|
foreach my $subscr (@other_ntypeid_to_consider) {
|
||
|
|
my %inbox_sub_params = $subscr->sub_info;
|
||
|
|
|
||
|
|
# don't save a subscription if there is no corresponding inbox sub for it
|
||
|
|
$inbox_sub_params{ntypeid} = $inbox_ntypeid;
|
||
|
|
delete $inbox_sub_params{flags};
|
||
|
|
|
||
|
|
my ($inbox_sub) = $u->has_subscription(%inbox_sub_params);
|
||
|
|
|
||
|
|
# If Inbox is always on, then act like an Inbox sub exists
|
||
|
|
my $always_checked = $subscr->event_class->always_checked ? 1 : 0;
|
||
|
|
next if !$always_checked && !( $inbox_sub && $inbox_sub->active && $inbox_sub->enabled );
|
||
|
|
|
||
|
|
$subscr->commit if $process_pending->( $subscr, $subscr->method );
|
||
|
|
}
|
||
|
|
|
||
|
|
return @notif_errors;
|
||
|
|
}
|
||
|
|
|
||
|
|
# subscribe to an event
|
||
|
|
sub subscribe {
|
||
|
|
my ( $u, %opts ) = @_;
|
||
|
|
croak "No subscription options" unless %opts;
|
||
|
|
|
||
|
|
return LJ::Subscription->create( $u, %opts );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub subscription_default_setup {
|
||
|
|
my ($u) = @_;
|
||
|
|
|
||
|
|
# set up default subscriptions for users that have not managed ESN stuff
|
||
|
|
if ( !$u->prop('esn_has_managed') && !$u->subscription_count ) {
|
||
|
|
$u->set_prop( esn_has_managed => 1 );
|
||
|
|
|
||
|
|
my @default_subscriptions =
|
||
|
|
( LJ::Subscription::Pending->new( $u, event => 'OfficialPost', ), );
|
||
|
|
|
||
|
|
if ( $u->prop('opt_gettalkemail') ne 'N' ) {
|
||
|
|
push @default_subscriptions, (
|
||
|
|
LJ::Subscription::Pending->new(
|
||
|
|
$u,
|
||
|
|
event => 'JournalNewComment',
|
||
|
|
journal => $u,
|
||
|
|
method => 'Inbox',
|
||
|
|
),
|
||
|
|
|
||
|
|
LJ::Subscription::Pending->new(
|
||
|
|
$u,
|
||
|
|
event => 'JournalNewComment',
|
||
|
|
journal => $u,
|
||
|
|
method => 'Email',
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
$_->commit foreach @default_subscriptions;
|
||
|
|
}
|
||
|
|
|
||
|
|
# Translate legacy pre-ESN notifs to ESN notifs
|
||
|
|
LJ::Event::JournalNewComment::Reply->migrate_user($u);
|
||
|
|
|
||
|
|
return $u;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub subscription_categories_for_settings_page {
|
||
|
|
my ($u) = @_;
|
||
|
|
|
||
|
|
my @cats = (
|
||
|
|
{
|
||
|
|
"My Account" => [
|
||
|
|
LJ::Subscription::Pending->new( $u, event => 'OfficialPost', ),
|
||
|
|
LJ::Subscription::Pending->new(
|
||
|
|
$u,
|
||
|
|
event => 'JournalNewComment',
|
||
|
|
journal => $u,
|
||
|
|
),
|
||
|
|
|
||
|
|
LJ::Subscription::Pending->new(
|
||
|
|
$u,
|
||
|
|
event => 'JournalNewComment::Reply',
|
||
|
|
arg2 => 0,
|
||
|
|
),
|
||
|
|
LJ::Subscription::Pending->new(
|
||
|
|
$u,
|
||
|
|
event => 'JournalNewComment::Reply',
|
||
|
|
arg2 => 1,
|
||
|
|
),
|
||
|
|
|
||
|
|
LJ::Subscription::Pending->new(
|
||
|
|
$u,
|
||
|
|
event => 'JournalNewComment::Reply',
|
||
|
|
arg2 => 2,
|
||
|
|
),
|
||
|
|
|
||
|
|
LJ::Subscription::Pending->new(
|
||
|
|
$u,
|
||
|
|
event => 'PollVote',
|
||
|
|
journal => $u,
|
||
|
|
),
|
||
|
|
'AddedToCircle',
|
||
|
|
'RemovedFromCircle',
|
||
|
|
LJ::Subscription::Pending->new(
|
||
|
|
$u,
|
||
|
|
event => 'XPostSuccess',
|
||
|
|
journal => $u,
|
||
|
|
),
|
||
|
|
LJ::Subscription::Pending->new(
|
||
|
|
$u,
|
||
|
|
event => 'XPostFailure',
|
||
|
|
journal => $u,
|
||
|
|
default_selected => 1,
|
||
|
|
),
|
||
|
|
LJ::Subscription::Pending->new(
|
||
|
|
$u,
|
||
|
|
event => 'VgiftDelivered',
|
||
|
|
journal => $u,
|
||
|
|
default_selected => 1,
|
||
|
|
),
|
||
|
|
LJ::Subscription::Pending->new(
|
||
|
|
$u,
|
||
|
|
event => 'UserMessageRecvd',
|
||
|
|
journal => $u,
|
||
|
|
default_selected => 1,
|
||
|
|
),
|
||
|
|
],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"Friends and Communities" => [
|
||
|
|
'InvitedFriendJoins',
|
||
|
|
'CommunityInvite',
|
||
|
|
'CommunityJoinRequest',
|
||
|
|
'CommunityModeratedEntryNew',
|
||
|
|
LJ::Subscription::Pending->new( $u, event => 'NewUserpic', ),
|
||
|
|
LJ::Subscription::Pending->new( $u, event => 'Birthday', ),
|
||
|
|
],
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
# add things the user is tracking
|
||
|
|
my @tracking;
|
||
|
|
my @subscriptions = $u->find_subscriptions( method => 'Inbox' );
|
||
|
|
|
||
|
|
foreach my $subsc ( sort { $a->id <=> $b->id } @subscriptions ) {
|
||
|
|
|
||
|
|
# if this event class is already being displayed above, skip over it
|
||
|
|
my $etypeid = $subsc->etypeid or next;
|
||
|
|
my ($evt_class) = ( LJ::Event->class($etypeid) =~ /LJ::Event::(.+)/i );
|
||
|
|
next unless $evt_class;
|
||
|
|
|
||
|
|
# search for this class in categories
|
||
|
|
next if grep { $_ eq $evt_class } map { @$_ } map { values %$_ } @cats;
|
||
|
|
|
||
|
|
push @tracking, $subsc;
|
||
|
|
}
|
||
|
|
|
||
|
|
return [ @cats, { "Subscription Tracking" => \@tracking } ];
|
||
|
|
}
|
||
|
|
|
||
|
|
sub subscription_count {
|
||
|
|
my $u = shift;
|
||
|
|
return scalar LJ::Subscription->subscriptions_of_user($u);
|
||
|
|
}
|
||
|
|
|
||
|
|
# extracted from LJ::subscribe_interface
|
||
|
|
sub subscription_event_filter {
|
||
|
|
my ( $u, $cat_events, $journalu, $include_settings ) = @_;
|
||
|
|
my @pending;
|
||
|
|
my @ret;
|
||
|
|
|
||
|
|
# build table of events that can be subscribed to
|
||
|
|
foreach my $cat_event (@$cat_events) {
|
||
|
|
if ( ( ref $cat_event ) =~ /Subscription/ ) {
|
||
|
|
push @pending, $cat_event;
|
||
|
|
}
|
||
|
|
elsif ( $cat_event =~ /^LJ::Setting/ ) {
|
||
|
|
|
||
|
|
# special subscription that's an LJ::Setting instead of an LJ::Subscription
|
||
|
|
push @pending, $cat_event if $include_settings;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
my $pending_sub = LJ::Subscription::Pending->new(
|
||
|
|
$u,
|
||
|
|
event => $cat_event,
|
||
|
|
journal => $journalu
|
||
|
|
);
|
||
|
|
push @pending, $pending_sub;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# check for existing subscriptions
|
||
|
|
foreach my $pending_sub (@pending) {
|
||
|
|
if ( !ref $pending_sub ) {
|
||
|
|
push @ret, $pending_sub;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
my %sub_args = $pending_sub->sub_info;
|
||
|
|
delete $sub_args{ntypeid};
|
||
|
|
$sub_args{method} = 'Inbox';
|
||
|
|
|
||
|
|
my @existing_subs = $u->has_subscription(%sub_args);
|
||
|
|
push @ret, ( @existing_subs ? @existing_subs : $pending_sub );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return @ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub subscriptions {
|
||
|
|
my $u = shift;
|
||
|
|
return LJ::Subscription->subscriptions_of_user($u);
|
||
|
|
}
|
||
|
|
|
||
|
|
# extracted from LJ::subscribe_interface
|
||
|
|
sub tracked_event_exclude {
|
||
|
|
my ( $u, $pending_sub, $cats ) = @_;
|
||
|
|
|
||
|
|
# return 1 if $pending_sub is already shown in another displayed category
|
||
|
|
foreach (@$cats) {
|
||
|
|
foreach my $cat_events ( values %$_ ) {
|
||
|
|
foreach my $event (@$cat_events) {
|
||
|
|
next if $event =~ /^LJ::Setting/;
|
||
|
|
$event = LJ::Subscription::Pending->new( $u, event => $event )
|
||
|
|
unless ref $event;
|
||
|
|
return 1 if $pending_sub->equals($event);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
########################################################################
|
||
|
|
### End LJ::User functions
|
||
|
|
|
||
|
|
########################################################################
|
||
|
|
### Begin LJ functions
|
||
|
|
|
||
|
|
package LJ;
|
||
|
|
|
||
|
|
use Carp;
|
||
|
|
|
||
|
|
########################################################################
|
||
|
|
### 16. Email-Related Functions
|
||
|
|
|
||
|
|
=head2 Email-Related Functions (LJ)
|
||
|
|
=cut
|
||
|
|
|
||
|
|
# loads the valid tlds as a hashref
|
||
|
|
sub load_valid_tlds {
|
||
|
|
return $LJ::VALID_EMAIL_DOMAINS
|
||
|
|
if $LJ::VALID_EMAIL_DOMAINS;
|
||
|
|
|
||
|
|
my %domains = map { lc $_ => 1 }
|
||
|
|
grep { $_ && $_ !~ /^#/ }
|
||
|
|
split( /\r?\n/, LJ::load_include('tlds') );
|
||
|
|
|
||
|
|
return $LJ::VALID_EMAIL_DOMAINS = \%domains;
|
||
|
|
}
|
||
|
|
|
||
|
|
# <LJFUNC>
|
||
|
|
# name: LJ::check_email
|
||
|
|
# des: checks for and rejects bogus e-mail addresses.
|
||
|
|
# info: Checks that the address is of the form username@some.domain,
|
||
|
|
# does not contain invalid characters. in the username, is a valid domain.
|
||
|
|
# Also checks for mis-spellings of common webmail providers,
|
||
|
|
# and web addresses instead of an e-mail address.
|
||
|
|
# args:
|
||
|
|
# returns: nothing on success, or error with error message if invalid/bogus e-mail address
|
||
|
|
# </LJFUNC>
|
||
|
|
sub check_email {
|
||
|
|
my ( $email, $errors, $post, $checkbox, $errorcodes ) = @_;
|
||
|
|
|
||
|
|
my $force_spelling = ref($post) && $post->{force_spelling};
|
||
|
|
|
||
|
|
# Trim off whitespace and force to lowercase.
|
||
|
|
$email =~ s/^\s+//;
|
||
|
|
$email =~ s/\s+$//;
|
||
|
|
$email = lc $email;
|
||
|
|
|
||
|
|
my $reject = sub {
|
||
|
|
my $errcode = shift;
|
||
|
|
my $errmsg = shift;
|
||
|
|
push @$errors, $errmsg if ref($errors);
|
||
|
|
push @$errorcodes, $errcode if ref($errorcodes);
|
||
|
|
return;
|
||
|
|
};
|
||
|
|
|
||
|
|
# Empty email addresses are not good.
|
||
|
|
unless ($email) {
|
||
|
|
return $reject->( "empty", "The email address cannot be blank." );
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check that the address is of the form username@some.domain.
|
||
|
|
my ( $username, $domain );
|
||
|
|
if ( $email =~ /^([^@]+)@([^@]+)/ ) {
|
||
|
|
$username = $1;
|
||
|
|
$domain = $2;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
return $reject->(
|
||
|
|
"bad_form",
|
||
|
|
"You did not give a valid email address. An email address looks like username\@some.domain"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check the username for invalid characters.
|
||
|
|
unless ( $username =~ /^[^\s\",;\(\)\[\]\{\}\<\>]+$/ ) {
|
||
|
|
return $reject->(
|
||
|
|
"bad_username", "You have invalid characters in the email address username."
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check the domain name.
|
||
|
|
my $valid_tlds = LJ::load_valid_tlds();
|
||
|
|
unless ( $domain =~ /^[\w-]+(?:\.[\w-]+)*\.(\w+)$/ && $valid_tlds->{$1} ) {
|
||
|
|
return $reject->( "bad_domain", "The email address domain is invalid." );
|
||
|
|
}
|
||
|
|
|
||
|
|
# Catch misspellings of gmail.com, yahoo.com, hotmail.com, outlook.com,
|
||
|
|
# aol.com, live.com.
|
||
|
|
# https://github.com/dreamwidth/dreamwidth/issues/993#issuecomment-357466645
|
||
|
|
# explains where 3 comes from.
|
||
|
|
my $tf_domain = Text::Fuzzy->new( $domain, max => 3, trans => 1 );
|
||
|
|
my @common_domains = (
|
||
|
|
'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com',
|
||
|
|
'aol.com', 'live.com', 'mail.com', 'fastmail.com',
|
||
|
|
'ymail.com', 'me.com'
|
||
|
|
);
|
||
|
|
my $nearest = $tf_domain->nearest( \@common_domains );
|
||
|
|
my $bad_spelling = defined $nearest && $tf_domain->last_distance > 0;
|
||
|
|
|
||
|
|
# Keep the checkbox if it was checked before, to stop it alternating
|
||
|
|
# between present/absent on successive submissions with other errors
|
||
|
|
if ( ref($checkbox) && ( $bad_spelling || $force_spelling ) ) {
|
||
|
|
$$checkbox =
|
||
|
|
"<input type=\"checkbox\" name=\"force_spelling\" id=\"force_spelling\" "
|
||
|
|
. ( $force_spelling ? "checked=\"checked\" " : "" )
|
||
|
|
. "/> "
|
||
|
|
. "<label for=\"force_spelling\">Yes I'm sure this is correct</label>";
|
||
|
|
}
|
||
|
|
if ( $bad_spelling && !$force_spelling ) {
|
||
|
|
return $reject->(
|
||
|
|
"bad_spelling",
|
||
|
|
"You gave $email as the email address. Are you sure you didn't mean $common_domains[$nearest]?"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
# Catch web addresses (two or more w's followed by a dot)
|
||
|
|
if ( $username =~ /^www*\./ ) {
|
||
|
|
return $reject->(
|
||
|
|
"web_address",
|
||
|
|
"You gave $email as the email address, but it looks more like a web address to me."
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub set_email {
|
||
|
|
my ( $userid, $email ) = @_;
|
||
|
|
|
||
|
|
my $dbh = LJ::get_db_writer();
|
||
|
|
$dbh->do( "REPLACE INTO email (userid, email) VALUES (?, ?)", undef, $userid, $email );
|
||
|
|
|
||
|
|
# update caches
|
||
|
|
LJ::memcache_kill( $userid, "userid" );
|
||
|
|
LJ::MemCache::delete( [ $userid, "email:$userid" ] );
|
||
|
|
my $cache = $LJ::REQ_CACHE_USER_ID{$userid} or return;
|
||
|
|
$cache->{'_email'} = $email;
|
||
|
|
}
|
||
|
|
|
||
|
|
1;
|