#!/usr/bin/perl # # DW::Controller::Tools # # # Authors: # RSH register_string( '/tools/comment_crosslinks', \&crosslinks_handler, app => 1 ); DW::Routing->register_string( '/tools/recent_emailposts', \&recent_emailposts_handler, app => 1 ); DW::Routing->register_string( '/tools/opml', \&opml_handler, app => 1 ); DW::Routing->register_string( '/tools/emailmanage', \&emailmanage_handler, app => 1 ); DW::Routing->register_string( '/tools/tellafriend', \&tellafriend_handler, app => 1 ); sub crosslinks_handler { my ( $ok, $rv ) = controller( authas => 1 ); return $rv unless $ok; my $u = $rv->{u}; my $dbcr = LJ::get_cluster_reader($u) or die; my $props = $dbcr->selectall_arrayref( q{ select l.jitemid * 256 + l.anum as 'ditemid', t.jtalkid * 256 + l.anum as 'dtalkid', tp.value from log2 l inner join talk2 t on (t.journalid = l.journalid and l.jitemid = t.nodeid and t.nodetype = 'L') inner join talkprop2 tp on (tp.journalid = t.journalid and t.jtalkid = tp.jtalkid) where tp.tpropid = 13 and tp.journalid = ? }, undef, $u->id ); my $base = $u->journal_base; my $vars = { 'base' => $base, 'props' => $props, 'authas_html' => $rv->{authas_html} }; return DW::Template->render_template( 'tools/comment_crosslinks.tt', $vars ); } sub recent_emailposts_handler { my ( $ok, $rv ) = controller( authas => 1 ); return $rv unless $ok; my $remote = $rv->{remote}; my $r = DW::Request->get; my $vars = {}; unless ($LJ::EMAIL_POST_DOMAIN) { $r->add_msg( "Sorry, this site is not configured to use the emailgateway.", $r->WARNING ); return $r->redirect($LJ::SITEROOT); } my $admin = $remote->has_priv('supporthelp'); $vars->{admin} = $admin; my $u; if ($admin) { my $user = $r->post_args->{user} || $r->get_args->{user}; $u = LJ::load_user($user); $vars->{user} = $user; } $u ||= $remote; if ($u) { my @clean_rows; my $dbcr = LJ::get_cluster_reader($u); my $sql = qq{ SELECT logtime, extra, DATE_FORMAT(FROM_UNIXTIME(logtime), "%M %D %Y, %l:%i%p") AS ftime FROM userlog WHERE userid=? AND action='emailpost' ORDER BY logtime DESC LIMIT 50 }; my $data = $dbcr->selectall_hashref( $sql, 'logtime', undef, $u->{userid} ); foreach ( reverse sort keys %$data ) { my $row = {}; LJ::decode_url_string( $data->{$_}->{extra}, $row ); my $err; if ( $row->{e} ) { $err = 'Yes'; $err .= ' (will retry)' if $row->{retry}; } else { $err = 'None'; } my $temp = { when => $data->{$_}->{ftime}, type => ( $row->{t} || "entry" ), subj => $row->{s}, err => $err, msg => ( $row->{m} ? LJ::ehtml( $row->{m} ) : 'Post success.' ) }; push @clean_rows, $temp; } $vars->{data} = \@clean_rows; } else { $r->add_msg( "No such user.", $r->WARNING ); } return DW::Template->render_template( 'tools/recent_emailposts.tt', $vars ); } sub opml_handler { my ( $ok, $rv ) = controller( authas => 1 ); return $rv unless $ok; my $remote = $rv->{remote}; my $r = DW::Request->get; my $get_args = $r->get_args; my $user = $get_args->{user}; # if we don't have a current user but somebody is logged in, redirect # them to their own OPML page if ( $remote && !$user ) { return BML::redirect("$LJ::SITEROOT/tools/opml?user=$remote->{user}"); } return "No 'user' argument" unless $user; my $u = LJ::load_user_or_identity($user) or return "Invalid user."; my @uids; # different accounts need different userid loads # will use watched accounts for personal accounts # and identity accounts # and members for communities if ( $u->is_person || $u->is_identity ) { @uids = $u->watched_userids; } elsif ( $u->is_community ) { @uids = $u->member_userids; } else { return "Invalid account type."; } my $us = LJ::load_userids(@uids); DW::Stats::increment( 'dw.opml.used', 1 ); my @cleaned; # currently ordered by user ID; there might be a better way to order this # but unless somebody has a strong preference about it there's no point foreach my $uid ( sort { $a <=> $b } @uids ) { my $w = $us->{$uid} or next; # identity accounts do not have feeds next if $w->is_identity; # filter by account type next if $get_args->{show} && !( $get_args->{show} =~ /[P]/ && $w->is_person || $get_args->{show} =~ /[C]/ && $w->is_community || $get_args->{show} =~ /[YF]/ && $w->is_syndicated ); my $title; # use the username + site abbreviation for each feed's title if we have that # option set if ( $get_args->{title} eq "username" ) { $title = $w->display_username . " (" . $LJ::SITENAMEABBREV . ")"; } else { # FIXXME: Should be using a function call instead! But $w->prop( "journaltitle" ) # returns empty here, though it's used in profile.bml $title = $w->{name}; } my $feed; if ( $w->is_syndicated ) { my $synd = $w->get_syndicated; $feed = $synd->{synurl}; } else { $feed = $w->journal_base; if ( $get_args->{feed} eq "atom" ) { $feed .= "/data/atom"; } else { $feed .= "/data/rss"; } if ( $get_args->{auth} eq "digest" ) { $feed .= "?auth=digest"; } } push @cleaned, { title => $title, feed => $feed }; } my $vars = { 'u' => $u, 'uids' => \@cleaned, 'email_visible' => $u->email_visible($remote) }; my $opml = DW::Template->template_string( 'tools/opml.tt', $vars, { no_sitescheme => 1 } ); $r->content_type("text/plain"); $r->print($opml); return $r->OK; } sub emailmanage_handler { my ( $ok, $rv ) = controller( authas => 1 ); return $rv unless $ok; my $remote = $rv->{remote}; my $r = DW::Request->get; my $dbh = LJ::get_db_writer(); my $authas = $r->get_args->{'authas'} || $remote->{'user'}; my $u = LJ::get_authas_user($authas); return error_ml('error.invalidauth') unless $u; # at the point that the latest email has been on the account for 6 months, *all* previous emails should become removable. # They should be able to remove all other email addresses at that point if multiple ones are listed, # so that they can remove anything that might potentially be compromised, leaving only their current/secured email on the account. # - Anne Zell, 11 december 2008 in LJSUP-3193, based on discussions in lj_core my $firstdate = $dbh->selectrow_array( qq{ SELECT MIN(timechange) FROM infohistory WHERE userid=? AND what='email' AND oldvalue=? }, undef, $u->{'userid'}, $u->email_raw ); my $lastdate_email = $dbh->selectrow_array( qq{ SELECT MAX(timechange) FROM infohistory WHERE userid=? AND (what='email' OR what='emaildeleted' AND length(other)<=2) }, undef, $u->{'userid'} ); my $lastdate_deleted = $dbh->selectrow_array( qq{ SELECT MAX(SUBSTRING(other FROM 3)) FROM infohistory WHERE userid=? AND what='emaildeleted' }, undef, $u->{'userid'} ); my $lastdate = defined $lastdate_deleted ? ( $lastdate_email gt $lastdate_deleted ? $lastdate_email : $lastdate_deleted ) : $lastdate_email; # current address was set more, than 6 months ago? my $six_month_case = time() - ( str2time($lastdate) // 0 ) > 182 * 24 * 3600; # half year my @deleted; if ( $r->did_post && $u->{'status'} eq 'A' ) { my $sth = $dbh->prepare( "SELECT timechange, oldvalue " . "FROM infohistory WHERE userid=? " . "AND what='email' ORDER BY timechange" ); $sth->execute( $u->{'userid'} ); while ( my ( $time, $email ) = $sth->fetchrow_array ) { my $can_del = defined $firstdate && $time gt $firstdate || $six_month_case; if ( $can_del && $r->post_args->{"$email-$time"} ) { push @deleted, LJ::Lang::ml( '/tools/emailmanage.tt.log.deleted', { 'email' => $email, 'time' => $time } ); $dbh->do( "UPDATE infohistory SET what='emaildeleted', other=CONCAT(other, ';', timechange), timechange = NOW() " . "WHERE what='email' AND userid=? AND timechange=? AND oldvalue=?", undef, $u->{'userid'}, $time, $email ); } } } my $sth = $dbh->prepare( "SELECT timechange, oldvalue FROM infohistory " . "WHERE userid=? AND what='email' " . "ORDER BY timechange" ); $sth->execute( $u->{'userid'} ); my @rows; while ( my ( $time, $email ) = $sth->fetchrow_array ) { my $can_del = defined $firstdate && $time gt $firstdate || $six_month_case; push @rows, { email => $email, time => $time, can_del => $can_del }; } my $vars = { 'u' => $u, 'lastdate' => $lastdate, 'authas_html' => $rv->{authas_html}, 'deleted' => \@deleted, 'rows' => \@rows, 'getextra' => ( $authas ne $remote->{'user'} ? "?authas=$authas" : '' ) }; return DW::Template->render_template( 'tools/emailmanage.tt', $vars ); } sub tellafriend_handler { my ( $ok, $rv ) = controller( authas => 1 ); return $rv unless $ok; my $remote = $rv->{remote}; my $r = DW::Request->get; my $get_args = $r->get_args; my $post_args = $r->post_args; my $scope = "/tools/tellafriend.tt"; my $user = $post_args->{'user'} || $get_args->{'user'}; my $journal = $post_args->{'journal'} || $get_args->{'journal'}; my $itemid = $post_args->{'itemid'} || $get_args->{'itemid'}; my $toemail = $post_args->{'toemail'}; return error_ml('/tools/tellafriend.tt.error.disabled') unless LJ::is_enabled('tellafriend'); # Get sender's email address my $u = LJ::load_userid( $remote->{'userid'} ); $u->{'emailpref'} = $u->email_raw; if ( $u->can_have_email_alias ) { $u->{'emailpref'} = $u->site_email_alias; } my $news_journal = LJ::load_user($LJ::NEWS_JOURNAL); my $news_url = LJ::isu($news_journal) ? $news_journal->journal_base : ''; my $footer_ml = LJ::isu($news_journal) ? 'footer.news' : 'footer'; my $msg_footer = LJ::Lang::ml( "$scope.email.body.$footer_ml", { user => $u->{user}, sitename => $LJ::SITENAME, sitenameshort => $LJ::SITENAMESHORT, news_url => $news_url } ); my $custom_msg = "\n\n" . LJ::Lang::ml( "$scope.email.body.custom", { user => $u->{user} } ); my $email_checkbox; my $errors = DW::FormErrors->new; # Tell a friend form submitted if ( $r->did_post && $post_args->{'mode'} eq "mail" ) { my @emails = split /,/, $toemail; $errors->add( 'toemail', ".error.noemail" ) unless ( scalar @emails ); my @errors; foreach my $em (@emails) { LJ::check_email( $em, \@errors, $post_args, \$email_checkbox ); } foreach my $err (@errors) { $errors->add( 'toemail', $err ); } # Check for images my $custom_body = $post_args->{'body'} // ''; if ( $custom_body =~ /<(img|image)\s+src/i ) { $errors->add( 'body', ".error.forbiddenimages" ); } # Check for external URLs foreach ( LJ::get_urls($custom_body) ) { if ( $_ !~ m!^https?://([\w-]+\.)?$LJ::DOMAIN(/.*)?$!i ) { $errors->add( 'body', ".error.forbiddenurl", { sitename => $LJ::SITENAME } ); } } $errors->add( undef, ".error.maximumemails" ) unless ( $u->rate_log( 'tellafriend', scalar @emails ) ); $errors->add( 'toemail', ".error.characterlimit" ) if ( length($toemail) > 150 ); unless ( $errors->exist ) { # All valid, go ahead and send my $msg_body = $post_args->{'body_start'}; if ( $custom_body ne '' ) { $msg_body .= $custom_msg . "\n-----\n" . $custom_body . "\n-----"; } $msg_body .= $msg_footer; LJ::send_mail( { 'to' => $toemail, 'from' => $LJ::BOGUS_EMAIL, 'fromname' => $u->user . LJ::Lang::ml("$scope.via") . " $LJ::SITENAMESHORT", 'charset' => 'utf-8', 'subject' => $post_args->{'subject'}, 'body' => $msg_body, 'headers' => { 'Reply-To' => qq{"$u->{user}" <$u->{emailpref}>}, } } ); my $tolist = $toemail; $tolist =~ s/(,\s*)/
/g; $r->add_msg( LJ::Lang::ml("$scope.sentpage.body.mailedlist") . "
" . $tolist, $r->SUCCESS ); } } my ( $subject, $msg ); $subject = LJ::Lang::ml("$scope.email.subject.noentry"); $msg = ''; if ( defined $itemid && $itemid =~ /^\d+$/ ) { my $uj = LJ::load_user($journal); return error_ml("$scope.error.unknownjournal") unless $uj; my $jitemid = $itemid + 0; $jitemid = int( $jitemid / 256 ); my $entry = LJ::Entry->new( $uj->{'userid'}, jitemid => $jitemid ); my $entry_subject = $entry->subject_text; my $pu = $entry->poster; my $url = $entry->url; my $uisjowner = ( $pu->{'user'} eq $u->{'user'} ); if ( !$uisjowner && $entry->security ne 'public' ) { return error_ml("$scope.email.warning.public"); } if ( $uisjowner && $entry->security eq 'private' ) { return error_ml("$scope.email.warning.private"); } if ( $uisjowner && $entry->security eq 'usemask' ) { $r->add_msg( LJ::Lang::ml("$scope.email.warning.usemask"), $r->WARNING ); $msg_footer = "\n\n" . LJ::Lang::ml( "$scope.email.usemask.footer", { user => $u->{'user'}, siteroot => $LJ::SITEROOT } ) . "$msg_footer"; } $msg .= LJ::Lang::ml( "$scope.email.body", { user => $u->{'user'}, sitenameshort => $LJ::SITENAMESHORT } ); $msg .= LJ::Lang::ml("$scope.email.sharedentry.title") . " $entry_subject\n" if $entry_subject; $msg .= LJ::Lang::ml("$scope.email.sharedentry.url") . " $url "; # email subject $subject = $entry_subject ? LJ::Lang::ml( "$scope.email.subject.entryhassubject", { sitenameshort => $LJ::SITENAMESHORT, subject => $entry_subject } ) : LJ::Lang::ml( "$scope.email.subject.entrynosubject", { sitenameshort => $LJ::SITENAMESHORT } ); } if ( defined $get_args->{'user'} && $get_args->{'user'} =~ /^\w{1,$LJ::USERNAME_MAXLENGTH}$/ ) { my $user = $get_args->{'user'}; my $uj = LJ::load_user($user); my $url = $uj->journal_base; $subject = LJ::Lang::ml("$scope.email.subject.journal"); if ( $user eq $u->{'user'} ) { $msg .= LJ::Lang::ml("$scope.email.body.yourjournal"); $msg .= " $url "; } else { $msg .= LJ::Lang::ml("$scope.email.body.otherjournal"); $msg .= " $url "; } } my $display_msg = $msg . $custom_msg; my $display_msg_footer = $msg_footer; $display_msg =~ s/\n/
/gm; $display_msg_footer =~ s/\n/
/gm; my $default_formdata = { body_start => $msg, subject => $subject, user => $user, journal => $journal, itemid => $itemid }; my $vars = { 'u' => $u, 'errors' => $errors, 'formdata' => $r->did_post ? $r->post_args : $default_formdata, 'display_msg' => $display_msg, 'display_msg_footer' => $display_msg_footer, 'email_checkbox' => $email_checkbox }; return DW::Template->render_template( 'tools/tellafriend.tt', $vars ); } 1;