#!/usr/bin/perl # # DW::Controller::Recent_comments # # This controller is for the Recent Comments pages. # # Authors: # hotlevel4 # # Copyright (c) 2013 by Dreamwidth Studios, LLC. # # This is based on code originally implemented on LiveJournal. # # This program is free software; you may redistribute it and/or modify it under # the same terms as Perl itself. For a copy of the license, please reference # 'perldoc perlartistic' or 'perldoc perlgpl'. # package DW::Controller::Comments; use strict; use warnings; use LJ::JSON; use DW::Controller; use DW::Routing; use DW::Template; DW::Routing->register_string( '/comments/recent', \&received_handler, app => 1 ); DW::Routing->register_string( '/comments/posted', \&posted_handler, app => 1 ); # redirect /tools/recent_comments, /tools/recent_comments.bml DW::Routing->register_redirect( '/tools/recent_comments', '/comments/recent', app => 1, formats => [ 'html', 'bml' ] ); sub received_handler { my ( $ok, $rv ) = controller( authas => 1 ); return $rv unless $ok; my $r = $rv->{r}; my $u = $rv->{u}; my $remote = $rv->{remote}; my $dbcr = LJ::get_cluster_reader($u); die "Error: can't get DB for user" unless $dbcr; my $vars; $vars->{u} = $u; $vars->{authas_html} = $rv->{authas_html}; $vars->{getextra} = ( $u ne $remote ) ? { authas => $u->user } : {}; my %LJ_cmtinfo = %{ LJ::Comment->info($u) }; $LJ_cmtinfo{form_auth} = LJ::form_auth(1); $vars = initialize_count( $u, $r, $vars ); my ( @recv, %talkids ); my %need_userid; $need_userid{ $u->{userid} } = 1 if $u->is_community; # Need to load the community for logtext my %logrow; # "jid nodeid" -> $logrow my %need_logids; # hash of "journalid jitemid" => [journalid, jitemid] my $now = time(); # Retrieve received if ( $u->is_person || $u->is_community ) { @recv = $u->get_recent_talkitems( $vars->{count} ); foreach my $post (@recv) { $need_userid{ $post->{posterid} } = 1 if $post->{posterid}; $talkids{ $post->{jtalkid} } = 1; $need_logids{"$u->{userid} $post->{nodeid}"} = [ $u->{userid}, $post->{nodeid} ] if $post->{nodetype} eq "L"; } @recv = sort { $b->{datepostunix} <=> $a->{datepostunix} } @recv; my @recv_talkids = map { $_->{'jtalkid'} } @recv; my %props; LJ::load_talk_props2( $u->{'userid'}, \@recv_talkids, \%props ); my $us = LJ::load_userids( keys %need_userid ); # setup the parameter to get_posts_raw my @need_logtext; foreach my $need ( values %need_logids ) { my ( $ownerid, $itemid ) = @$need; my $ju = $us->{$ownerid} or next; push @need_logtext, [ $ju->{clusterid}, $ownerid, $itemid ]; } my $comment_text = LJ::get_talktext2( $u, keys %talkids ); my $log_data = LJ::get_posts_raw( { text_only => 1 }, @need_logtext ); my $log_text = $log_data->{text}; my $root = $u->journal_base; # Cycle through comments and skip deleted ones foreach my $r (@recv) { next unless $r->{nodetype} eq "L"; next if $r->{state} eq "D"; my $pu = $us->{ $r->{posterid} }; next if $pu && ( $pu->is_expunged || $pu->is_suspended ); $r->{'props'} = $props{ $r->{'jtalkid'} }; my $lrow = $logrow{"$u->{userid} $r->{nodeid}"} ||= LJ::get_log2_row( $u, $r->{'nodeid'} ); my $talkid = ( $r->{'jtalkid'} << 8 ) + $lrow->{'anum'}; my $ditemid = "$root/$lrow->{ditemid}.html"; my $commentanchor = LJ::Talk::comment_anchor($talkid); my $talkurl = "$root/$lrow->{ditemid}.html?thread=$talkid$commentanchor"; my $state = ""; my $tdclass = ""; if ( $r->{state} eq "S" ) { $state = "Screened"; $tdclass = "screened"; } elsif ( $r->{state} eq "D" ) { $state = "Deleted"; } elsif ( $r->{state} eq "F" ) { $state = "Frozen"; } my $ljcmt = $LJ_cmtinfo{$talkid} = {}; $ljcmt->{u} = $pu ? $pu->{user} : ""; my $isanonymous = LJ::isu($pu) ? 0 : 1; my $hr_ago = LJ::diff_ago_text( $r->{datepostunix}, $now ); my $del_link = LJ::create_url( "/delcomment", args => { journal => $u->{'user'}, id => $talkid } ); my $del_img = LJ::img( "btn_del", "", { align => 'absmiddle', hspace => 2 } ); my $freeze_link; my $freeze_img; if ( $r->{'state'} ne 'F' ) { $freeze_link = LJ::create_url( "/talkscreen", args => { mode => "freeze", journal => $u->{'user'}, talkid => $talkid } ); $freeze_img = LJ::img( "btn_freeze", "", { align => 'absmiddle', hspace => 2 } ); } elsif ( $r->{'state'} eq 'F' ) { $freeze_link = LJ::create_url( "/talkscreen", args => { mode => "unfreeze", journal => $u->{'user'}, talkid => $talkid } ); $freeze_img = LJ::img( "btn_unfreeze", "", { align => 'absmiddle', hspace => 2 } ); } my $screen_link; my $screen_img; if ( $r->{'state'} ne 'S' ) { $screen_link = LJ::create_url( "/talkscreen", args => { mode => "screen", journal => $u->{'user'}, talkid => $talkid } ); $screen_img = LJ::img( "btn_scr", "", { align => 'absmiddle', hspace => 2 } ); } elsif ( $r->{'state'} eq 'S' ) { $screen_link = LJ::create_url( "/talkscreen", args => { mode => "unscreen", journal => $u->{'user'}, talkid => $talkid } ); $screen_img = LJ::img( "btn_unscr", "", { align => 'absmiddle', hspace => 2 } ); } # FIXME: (David?) We'll have to make talk_multi.bml understand jtalkids in multiple posts #$ret .= " "; #$ret .= " "; my $comment_htmlid = LJ::Talk::comment_htmlid($talkid); my $esubject = $log_text->{"$u->{userid}:$r->{nodeid}"}[0] // ""; LJ::CleanHTML::clean_subject( \$esubject ) if $esubject ne ""; my $ditemid_undef = defined $lrow->{ditemid} ? 0 : 1; my $csubject = LJ::ehtml( $comment_text->{ $r->{jtalkid} }[0] ); if ( !$csubject || $csubject =~ /^Re:\s*$/ ) { $csubject = ''; } my $comment = $comment_text->{ $r->{jtalkid} }[1]; LJ::CleanHTML::clean_comment( \$comment, { preformatted => $r->{props}->{opt_preformatted}, editor => $r->{props}->{editor}, anon_comment => LJ::Talk::treat_as_anon( $pu, $u ), nocss => 1, } ); my $stylemine = 0; my $replyurl = LJ::Talk::talkargs( $ditemid, "replyto=$talkid", $stylemine ); push @{ $vars->{comments} }, { isanonymous => $isanonymous, # 1 if posted by anonymous user pu => $pu, # user that posted the comment hr_ago => $hr_ago, # text of time posted state => $state, # Screened, Deleted, or Frozen del_link => $del_link, del_img => $del_img, screen_link => $screen_link, screen_img => $screen_img, freeze_link => $freeze_link, freeze_img => $freeze_img, comment_htmlid => $comment_htmlid, esubject => $esubject, ditemid_undef => $ditemid_undef, ditemid => $ditemid, csubject => $csubject, comment => $comment, talkurl => $talkurl, # direct link to comment replyurl => $replyurl, talkid => $talkid, # comment number tdclass => $tdclass }; } } $vars->{LJ_cmtinfo} = to_json( \%LJ_cmtinfo ); return DW::Template->render_template( 'comments/recent.tt', $vars ); } sub posted_handler { my ( $ok, $rv ) = controller( authas => 1 ); return $rv unless $ok; my $r = $rv->{r}; my $u = $rv->{u}; my $remote = $rv->{remote}; my $vars; $vars->{u} = $u; $vars->{authas_html} = $rv->{authas_html}; $vars->{getextra} = ( $u ne $remote ) ? { authas => $u->user } : {}; my %LJ_cmtinfo = %{ LJ::Comment->info($u) }; $LJ_cmtinfo{form_auth} = LJ::form_auth(1); my $dbcr = LJ::get_cluster_reader($u); die "Error: can't get DB for user" unless $dbcr; $vars = initialize_count( $u, $r, $vars ); my ( @posted, %talkids ); my %need_userid; $need_userid{ $u->{userid} } = 1 if $u->is_community; # Need to load the community for logtext my %logrow; # "jid nodeid" -> $logrow my %need_logids; # hash of "journalid jitemid" => [journalid, jitemid] my $now = time(); my $sth; $vars->{canedit} = $remote->can_edit_comments; # Retrieve posted if ( $u->is_individual ) { $sth = $dbcr->prepare( "SELECT posttime, journalid, nodetype, nodeid, jtalkid, publicitem " . "FROM talkleft " . "WHERE userid=? ORDER BY posttime DESC LIMIT $vars->{count}" ); $sth->execute( $u->{'userid'} ); my %jcount; # jid -> ct while ( my $r = $sth->fetchrow_hashref ) { push @posted, $r; $need_logids{"$r->{journalid} $r->{nodeid}"} = [ $r->{journalid}, $r->{nodeid} ] if $r->{nodetype} eq "L"; $need_userid{ $r->{journalid} } = 1; } my $us = LJ::load_userids( keys %need_userid ); # setup the parameter to get_posts_raw my @need_logtext; foreach my $need ( values %need_logids ) { my ( $ownerid, $itemid ) = @$need; my $ju = $us->{$ownerid} or next; push @need_logtext, [ $ju->{clusterid}, $ownerid, $itemid ]; } my $log_data = LJ::get_posts_raw( { text_only => 1 }, @need_logtext ); my $log_text = $log_data->{text}; my $root = $u->journal_base; # Cycle through each comment to extract necessary data foreach my $r (@posted) { $jcount{ $r->{'journalid'} }++; next unless $r->{'nodetype'} eq "L"; # log2 comment my $ju = $us->{ $r->{journalid} }; my $lrow = $logrow{"$ju->{userid} $r->{nodeid}"} ||= LJ::get_log2_row( $ju, $r->{'nodeid'} ); my $hr_ago = LJ::diff_ago_text( $r->{posttime}, $now ); # if entry exists if ( defined $lrow->{ditemid} ) { my $talkid = ( $r->{'jtalkid'} << 8 ) + $lrow->{'anum'}; my $ljcmt = $LJ_cmtinfo{$talkid} = {}; $ljcmt->{u} = $u->{user}; $ljcmt->{postedin} = $ju ? $ju->{user} : ""; my $comment = LJ::Comment->new( $ju, dtalkid => $talkid ); my $logurl = $ju->journal_base . "/$lrow->{ditemid}.html"; my $commentanchor = LJ::Talk::comment_anchor($talkid); my $talkurl = "$logurl?thread=$talkid$commentanchor"; my $subject = $log_text->{"$r->{journalid}:$r->{nodeid}"}[0] || "$lrow->{ditemid}.html"; LJ::CleanHTML::clean_subject( \$subject ); # add a sign if the comment has replies my $hasreplies = $comment->has_nondeleted_children ? "*" : ''; # delete link, very helpful for when the user does not have access to that entry anymore my $delete = $comment->is_deleted ? '' : LJ::create_url( "/delcomment", args => { journal => $ju->{'user'}, id => $talkid } ); # edit link, if comment can be edited my $editlink = $comment->remote_can_edit ? LJ::Talk::talkargs( $comment->edit_url ) : ''; push @{ $vars->{comments} }, { ju => $ju, # journal comment was posted in talkurl => $talkurl, # direct link to comment logurl => $logurl, # link to entry holding comment subject => $subject, # subject of entry candelete => $hasreplies, # '*' if comment has replies and cannot be deleted hr_ago => $hr_ago, # text of time posted deletelink => $delete, # link to delete comment (if available, otherwise blank) editlink => $editlink, # link to edit comment (if available, otherwise blank) talkid => $talkid # comment number }; } # entry has been deleted else { push @{ $vars->{comments} }, { postdeleted => 1, # entry deleted hr_ago => $hr_ago, ju => $ju }; } } } $vars->{LJ_cmtinfo} = to_json( \%LJ_cmtinfo ); return DW::Template->render_template( 'comments/posted.tt', $vars ); } # Ascertain number of comments to show sub initialize_count { my ( $u, $r, $vars ) = @_; my $max = $u->count_recent_comments_display; my $show = $r->get_args->{show} // 25; # how many comments to display by default $show = $max if $show > $max; $show = 0 if $show < 1; my $count = $show || ( $max > 25 ? 25 : $max ); $show = $max > 25 ? 25 : $max; $vars->{count} = $count; $vars->{show} = $show; $vars->{max} = $max; my @values = qw( 10 25 50 100 ); push @values, $count unless grep { $count == $_ } @values; push @values, $max unless grep { $max == $_ } @values; @values = sort { $a <=> $b } @values; $vars->{values} = \@values; $vars->{sitemax} = $LJ::TOOLS_RECENT_COMMENTS_MAX; return $vars; } 1;