mourningdove/cgi-bin/LJ/User/Message.pm

976 lines
29 KiB
Perl
Raw Normal View History

2026-05-24 01:03:05 +00:00
# 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 = \&notification_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) ) ? " &dagger;" : "";
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\" " : "" )
. "/>&nbsp;"
. "<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;