# 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::Event::JournalNewComment; use strict; use Scalar::Util qw(blessed); use LJ::Comment; use LJ::JSON; use DW::EmailPost::Comment; use Carp qw(croak); use base 'LJ::Event'; # we don't allow subscriptions to comments on friends' journals, so # setting undef on this skips some nasty queries sub zero_journalid_subs_means { undef } sub new { my ( $class, $comment ) = @_; croak 'Not an LJ::Comment' unless blessed $comment && $comment->isa("LJ::Comment"); return $class->SUPER::new( $comment->journal, $comment->jtalkid ); } # Create an event for a comment that was just unscreened. # Uses arg2 as a flag so matches_filter can skip users # who were already notified when the comment was screened. sub new_for_unscreen { my ( $class, $comment ) = @_; croak 'Not an LJ::Comment' unless blessed $comment && $comment->isa("LJ::Comment"); return $class->SUPER::new( $comment->journal, $comment->jtalkid, 1 ); } # Returns true if this event was fired because a comment was unscreened # (as opposed to newly posted). sub is_unscreen_event { return $_[0]->{args}[1] ? 1 : 0; } sub arg_list { return ("Comment jtalkid"); } sub related_event_classes { return ( "LJ::Event::JournalNewComment", "LJ::Event::JournalNewComment::TopLevel", "LJ::Event::JournalNewComment::Edited", "LJ::Event::JournalNewComment::Reply" ); } sub is_common { 1 } my @_ml_strings_en = ( 'esn.mail_comments.fromname.user', # "[[user]] - [[sitenameabbrev]] Comment", 'esn.mail_comments.fromname.anonymous', # "[[sitenameshort]] Comment", 'esn.mail_comments.subject.edit_reply_to_your_comment', # "Edited reply to your comment...", 'esn.mail_comments.subject.reply_to_your_comment', # "Reply to your comment...", 'esn.mail_comments.subject.edit_reply_to_your_entry', # "Edited reply to your entry...", 'esn.mail_comments.subject.reply_to_your_entry', # "Reply to your entry...", 'esn.mail_comments.subject.edit_reply_to_an_entry', # "Edited reply to an entry...", 'esn.mail_comments.subject.reply_to_an_entry', # "Reply to an entry...", 'esn.mail_comments.subject.edit_reply_to_a_comment', # "Edited reply to a comment...", 'esn.mail_comments.subject.reply_to_a_comment', # "Reply to a comment...", 'esn.mail_comments.subject.comment_you_posted', # "Comment you posted...", 'esn.mail_comments.subject.comment_you_edited', # "Comment you edited...", ); sub as_email_from_name { my ( $self, $u ) = @_; my $vars = { user => $self->comment->poster ? $self->comment->poster->display_username : '', sitenameabbrev => $LJ::SITENAMEABBREV, sitenameshort => $LJ::SITENAMESHORT, }; my $key = 'esn.mail_comments.fromname.'; if ( $self->comment->poster ) { $key .= 'user'; } else { $key .= 'anonymous'; } return LJ::Lang::get_default_text( $key, $vars ); } sub as_email_headers { my ( $self, $u ) = @_; my $this_msgid = $self->comment->email_messageid; my $top_msgid = $self->comment->entry->email_messageid; my $par_msgid; if ( $self->comment->parent ) { # a reply to a comment $par_msgid = $self->comment->parent->email_messageid; } else { # reply to an entry $par_msgid = $top_msgid; $top_msgid = ""; # so it's not duplicated } my $journalu = $self->comment->entry->journal; my $headers = { 'Message-ID' => $this_msgid, 'In-Reply-To' => $par_msgid, 'References' => "$top_msgid $par_msgid", 'X-Journal-Name' => $journalu->user, 'Reply-To' => DW::EmailPost::Comment->replyto_address_header( $u, $journalu, $self->comment->entry->ditemid, $self->comment->dtalkid ), }; return $headers; } sub as_email_subject { my ( $self, $u ) = @_; my $edited = $self->comment->is_edited; my $entry_details = ''; if ( $self->comment->journal && $self->comment->entry ) { $entry_details = ' [ ' . $self->comment->journal->display_name . ' - ' . $self->comment->entry->ditemid . ' ]'; } my $key = 'esn.mail_comments.subject.'; if ( $self->comment->subject_orig ) { return LJ::strip_html( $self->comment->subject_orig . $entry_details ); } elsif ( $u && $u->equals( $self->comment->poster ) ) { $key .= $edited ? 'comment_you_edited' : 'comment_you_posted'; } elsif ( $self->comment->parent ) { if ( $u && $u->equals( $self->comment->parent->poster ) ) { $key .= $edited ? 'edit_reply_to_your_comment' : 'reply_to_your_comment'; } else { $key .= $edited ? 'edit_reply_to_a_comment' : 'reply_to_a_comment'; } } elsif ( $u && $u->equals( $self->comment->entry->poster ) ) { $key .= $edited ? 'edit_reply_to_your_entry' : 'reply_to_your_entry'; } else { $key .= $edited ? 'edit_reply_to_an_entry' : 'reply_to_an_entry'; } return LJ::Lang::get_default_text($key) . $entry_details; } sub as_email_string { my ( $self, $u ) = @_; my $comment = $self->comment or return "(Invalid comment)"; return $comment->format_text_mail($u); } sub as_email_html { my ( $self, $u ) = @_; my $comment = $self->comment or return "(Invalid comment)"; return $comment->format_html_mail($u); } sub as_string { my ( $self, $u ) = @_; my $comment = $self->comment; my $journal = $comment->entry->journal->user; return "There is a new anonymous comment in $journal at " . $comment->url unless $comment->poster; my $poster = $comment->poster->display_username; if ( $self->comment->is_edited ) { return "$poster has edited a comment in $journal at " . $comment->url; } else { return "$poster has posted a new comment in $journal at " . $comment->url; } } sub _can_view_content { my ( $self, $comment, $target ) = @_; return undef unless $comment && $comment->valid; return undef unless $comment->entry && $comment->entry->valid; return undef unless $comment->visible_to($target); return undef if $comment->is_deleted; return 1; } sub content { my ( $self, $target ) = @_; my $comment = $self->comment; return undef unless $self->_can_view_content( $comment, $target ); my $comment_body = $comment->body_html; my $buttons = $comment->manage_buttons; my $dtalkid = $comment->dtalkid; my $htmlid = LJ::Talk::comment_htmlid($dtalkid); if ( $comment->is_edited ) { my $reason = LJ::ehtml( $comment->edit_reason ); $comment_body .= "

" . LJ::Lang::get_default_text( "esn.journal_new_comment.edit_reason", { reason => $reason } ) . "
" if $reason; } my $admin_post = ""; if ( $comment->admin_post ) { $admin_post = '
' . LJ::Lang::get_default_text( "esn.journal_new_comment.admin_post", { img => LJ::img('admin-post') } ) . '
'; } my $ret = qq {
$buttons
$admin_post
$comment_body
}; my $cmt_info = $comment->info; $cmt_info->{form_auth} = LJ::form_auth(1); my $cmt_info_js = to_json($cmt_info) || '{}'; my $posterusername = $self->comment->poster ? $self->comment->poster->{user} : ""; $ret .= qq { }; $ret = "
" . $self->as_html_actions . "
" . $ret if LJ::has_too_many( $comment_body, linebreaks => 10, chars => 2000 ); $ret .= $self->as_html_actions; return $ret; } sub content_summary { my ( $self, $target ) = @_; my $comment = $self->comment; return undef unless $self->_can_view_content( $comment, $target ); my $body_summary = $comment->body_html_summary(300); LJ::warn_for_perl_utf8($body_summary); my $ret = $body_summary; $ret .= "..." if $comment->body_html ne $body_summary; if ( $comment->is_edited ) { my $reason = LJ::ehtml( $comment->edit_reason ); $ret .= "

" . LJ::Lang::get_default_text( "esn.journal_new_comment.edit_reason", { reason => $reason } ) . "
" if $reason; LJ::warn_for_perl_utf8($ret); } $ret .= $self->as_html_actions; LJ::warn_for_perl_utf8($ret); return $ret; } sub as_html { my ( $self, $target ) = @_; my $comment = $self->comment; my $journal = $self->u; my $entry = $comment->entry; return sprintf( "(Comment on a deleted entry in %s)", $journal->ljuser_display ) unless $entry && $entry->valid; my $entry_subject = $entry->subject_text || "an entry"; return sprintf( qq{(Deleted comment to post from %s in %s: comment by %s in "%s")}, LJ::diff_ago_text( $entry->logtime_unix ), $journal->ljuser_display, $comment->poster ? $comment->poster->ljuser_display : "(Anonymous)", $entry_subject ) unless $comment && $comment->valid && !$comment->is_deleted; return "(You are not authorized to view this comment)" unless $comment->visible_to($target); my $ju = LJ::ljuser($journal); my $pu = LJ::ljuser( $comment->poster ); my $url = $comment->url; my $in_text = '$entry_subject"; LJ::warn_for_perl_utf8($in_text); my $subject = $comment->subject_text ? ' "' . $comment->subject_text . '"' : ''; LJ::warn_for_perl_utf8($subject); my $poster = $comment->poster ? "by $pu" : ''; LJ::warn_for_perl_utf8($poster); my $ret; if ( $comment->is_edited ) { $ret = "Edited comment $subject $poster on $in_text in $ju."; } else { $ret = "New comment $subject $poster on $in_text in $ju."; } $ret .= ' (filter to this entry)'; } sub as_html_actions { my ($self) = @_; my $comment = $self->comment; my $url = $comment->url; my $reply_url = $comment->reply_url; my $parent_url = $comment->parent_url; my $ret .= "
"; $ret .= " Reply | "; $ret .= " Link "; $ret .= " | Parent" if $parent_url; $ret .= "
"; return $ret; } # ML-keys and contents of all items used in this subroutine: # 01 event.journal_new_comment.friend=Someone comments in any journal on my friends page # 02 event.journal_new_comment.my_journal=Someone comments in my journal, on any entry # 03 event.journal_new_comment.user_journal=Someone comments in [[user]], on any entry # 04 event.journal_new_comment.user_journal.deleted=Someone comments on a deleted entry in [[user]] # 05 event.journal_new_comment.my_journal.deleted=Someone comments on a deleted entry in my journal # 06 event.journal_new_comment.user_journal.titled_entry=Someone comments on [[entrydesc]] in [[user]] # 07 event.journal_new_comment.user_journal.untitled_entry=Someone comments on en entry in [[user]] # 08 event.journal_new_comment.my_journal.titled_entry=Someone comments on [[entrydesc]] my journal # 09 event.journal_new_comment.my_journal.untitled_entry=Someone comments on en entry my journal # 10 event.journal_new_comment.my_journal.titled_entry.titled_thread.user=Someone comments under [[thread_desc]] by [[posteruser]] in [[entrydesc]] on my journal # 11 event.journal_new_comment.my_journal.titled_entry.untitled_thread.user=Someone comments under the thread by [[posteruser]] in [[entrydesc]] on my journal # 12 event.journal_new_comment.my_journal.titled_entry.titled_thread.me=Someone comments under [[thread_desc]] by me in [[entrydesc]] on my journal # 13 event.journal_new_comment.my_journal.titled_entry.untitled_thread.me=Someone comments under the thread by me in [[entrydesc]] on my journal # 14 event.journal_new_comment.my_journal.titled_entry.titled_thread.anonymous=Someone comments under [[thread_desc]] by (Anonymous) in [[entrydesc]] on my journal # 15 event.journal_new_comment.my_journal.titled_entry.untitled_thread.anonymous=Someone comments under the thread by (Anonymous) in [[entrydesc]] on my journal # 16 event.journal_new_comment.my_journal.untitled_entry.titled_thread.user=Someone comments under [[thread_desc]] by [[posteruser]] in en entry on my journal # 17 event.journal_new_comment.my_journal.untitled_entry.untitled_thread.user=Someone comments under the thread by [[posteruser]] in en entry on my journal # 18 event.journal_new_comment.my_journal.untitled_entry.titled_thread.me=Someone comments under [[thread_desc]] by me in en entry on my journal # 19 event.journal_new_comment.my_journal.untitled_entry.untitled_thread.me=Someone comments under the thread by me in en entry on my journal # 20 event.journal_new_comment.my_journal.untitled_entry.titled_thread.anonymous=Someone comments under [[thread_desc]] by (Anonymous) in en entry on my journal # 21 event.journal_new_comment.my_journal.untitled_entry.untitled_thread.anonymous=Someone comments under the thread by (Anonymous) in en entry on my journal # 22 event.journal_new_comment.user_journal.titled_entry.titled_thread.user=Someone comments under [[thread_desc]] by [[posteruser]] in [[entrydesc]] in [[user]] # 23 event.journal_new_comment.user_journal.titled_entry.untitled_thread.user=Someone comments under the thread by [[posteruser]] in [[entrydesc]] in [[user]] # 24 event.journal_new_comment.user_journal.titled_entry.titled_thread.me=Someone comments under [[thread_desc]] by me in [[entrydesc]] in [[user]] # 25 event.journal_new_comment.user_journal.titled_entry.untitled_thread.me=Someone comments under the thread by me in [[entrydesc]] in [[user]] # 26 event.journal_new_comment.user_journal.titled_entry.titled_thread.anonymous=Someone comments under [[thread_desc]] by (Anonymous) in [[entrydesc]] in [[user]] # 27 event.journal_new_comment.user_journal.titled_entry.untitled_thread.anonymous=Someone comments under the thread by (Anonymous) in [[entrydesc]] in [[user]] # 28 event.journal_new_comment.user_journal.untitled_entry.titled_thread.user=Someone comments under [[thread_desc]] by [[posteruser]] in en entry in [[user]] # 29 event.journal_new_comment.user_journal.untitled_entry.untitled_thread.user=Someone comments under the thread by [[posteruser]] in en entry in [[user]] # 30 event.journal_new_comment.user_journal.untitled_entry.titled_thread.me=Someone comments under [[thread_desc]] by me in en entry in [[user]] # 31 event.journal_new_comment.user_journal.untitled_entry.untitled_thread.me=Someone comments under the thread by me in en entry in [[user]] # 32 event.journal_new_comment.user_journal.untitled_entry.titled_thread.anonymous=Someone comments under [[thread_desc]] by (Anonymous) in en entry in [[user]] # 33 event.journal_new_comment.user_journal.untitled_entry.untitled_thread.anonymous=Someone comments under the thread by (Anonymous) in en entry in [[user]] # -- now, let's begin. sub subscription_as_html { my ( $class, $subscr, $key_prefix ) = @_; my $arg1 = $subscr->arg1; my $arg2 = $subscr->arg2; my $journal = $subscr->journal; my $key = $key_prefix || 'event.journal_new_comment'; if ( !$journal ) { ### 01 event.journal_new_comment.friend=Someone comments in any journal on my friends page return BML::ml( $key . '.friend' ); } my ( $user, $journal_is_owner ); if ( $journal->equals( $subscr->owner ) ) { $user = 'my journal'; $key .= '.my_journal'; my $journal_is_owner = 1; } else { $user = LJ::ljuser($journal); $key .= '.user_journal'; my $journal_is_owner = 0; } return if $journal->is_identity; if ( $arg1 == 0 && $arg2 == 0 ) { ### 02 event.journal_new_comment.my_journal=Someone comments in my journal, on any entry ### 03 event.journal_new_comment.user_journal=Someone comments in [[user]], on any entry return BML::ml( $key, { user => $user } ); } # load ditemid from jtalkid if no ditemid my $comment; if ($arg2) { $comment = LJ::Comment->new( $journal, jtalkid => $arg2 ); return "(Invalid comment)" unless $comment && $comment->valid; $arg1 = $comment->entry->ditemid unless $arg1; } my $entry = LJ::Entry->new( $journal, ditemid => $arg1 ); ### 04 event.journal_new_comment.user_journal.deleted=Someone comments on a deleted entry in [[user]] ### 05 event.journal_new_comment.my_journal.deleted=Someone comments on a deleted entry in my journal return BML::ml( $key . '.deleted', { user => $user } ) unless $entry && $entry->valid; my $entrydesc = $entry->subject_text; if ($entrydesc) { $entrydesc = "\"$entrydesc\""; $key .= '.titled_entry'; } else { $entrydesc = "an entry"; $key .= '.untitled_entry'; } my $entryurl = $entry->url; ### 06 event.journal_new_comment.user_journal.titled_entry=Someone comments on [[entrydesc]] in [[user]] ### 07 event.journal_new_comment.user_journal.untitled_entry=Someone comments on en entry in [[user]] ### 08 event.journal_new_comment.my_journal.titled_entry=Someone comments on [[entrydesc]] my journal ### 09 event.journal_new_comment.my_journal.untitled_entry=Someone comments on en entry my journal return BML::ml( $key, { user => $user, entryurl => $entryurl, entrydesc => $entrydesc, } ) if $arg2 == 0; my $posteru = $comment->poster; my $posteruser; my $threadurl = $comment->url; my $thread_desc = $comment->subject_text; if ($thread_desc) { $thread_desc = "\"$thread_desc\""; $key .= '.titled_thread'; } else { $thread_desc = "the thread"; $key .= '.untitled_thread'; } if ($posteru) { if ($journal_is_owner) { $posteruser = LJ::ljuser($posteru); $key .= '.me'; } else { $posteruser = LJ::ljuser($posteru); $key .= '.user'; } } else { $posteruser = "(Anonymous)"; $key .= '.anonymous'; } ### 10 ... 33 return BML::ml( $key, { user => $user, threadurl => $threadurl, thread_desc => $thread_desc, posteruser => $posteruser, entryurl => $entryurl, entrydesc => $entrydesc, } ); } sub matches_filter { my ( $self, $subscr ) = @_; return 0 unless $subscr->available_for_user; my $sjid = $subscr->journalid; my $ejid = $self->event_journal->userid; # if subscription is for a specific journal (not a wildcard like 0 # for all friends) then it must match the event's journal exactly. return 0 if $sjid && $sjid != $ejid; my ( $earg1, $earg2 ) = ( $self->arg1, $self->arg2 ); my ( $sarg1, $sarg2 ) = ( $subscr->arg1, $subscr->arg2 ); my $comment = $self->comment; my $entry = $comment->entry; my $watcher = $subscr->owner; return 0 unless $comment->visible_to($watcher); if ($watcher) { # not a match if this user posted the comment return 0 if $watcher->equals( $comment->poster ); # For unscreen events, skip users who were already notified # when the comment was screened (they could already see it). if ( $self->is_unscreen_event ) { return 0 if $watcher->can_manage( $comment->journal ); return 0 if $entry->poster && $watcher->equals( $entry->poster ); } # not a match if opt_noemail applies return 0 if $self->apply_noemail( $watcher, $comment, $subscr->method ); } # watching a specific journal if ( $sarg1 == 0 && $sarg2 == 0 ) { # TODO: friend group filtering in case of $sjid == 0 when # a subprop is filtering on a friend group return 1; } my $wanted_ditemid = $sarg1; # a (journal, dtalkid) pair identifies a comment uniquely, as does # a (journal, ditemid, dtalkid pair). So ditemid is optional. If we have # it, though, it needs to be correct. return 0 if $wanted_ditemid && $entry->ditemid != $wanted_ditemid; # watching a post return 1 if $sarg2 == 0; # watching a thread my $wanted_jtalkid = $sarg2; while ($comment) { return 1 if $comment->jtalkid == $wanted_jtalkid; $comment = $comment->parent; } return 0; } sub apply_noemail { my ( $self, $watcher, $comment, $method ) = @_; my $entry = $comment->entry; # not a match if this user posted the entry and they don't want comments emailed, # unless it is a reply to one of their comments or they posted the comment my $reply_to_own_comment = $comment->parent ? $watcher->equals( $comment->parent->poster ) : 0; my $receive_own_comment = $comment->posterid == $watcher->id # we posted && $watcher->prop('opt_getselfemail') && $watcher->can_get_self_email; if ( $watcher->equals( $entry->poster ) && !( $reply_to_own_comment || $receive_own_comment ) # special-cased ) { return 1 if $entry->prop('opt_noemail') && $method =~ /Email$/; } } sub jtalkid { my $self = shift; return $self->arg1; } # when was this comment posted or edited? sub eventtime_unix { my $self = shift; my $cmt = $self->comment; my $time = $cmt->is_edited ? $cmt->edit_time : $cmt->unixtime; return $cmt ? $time : $self->SUPER::eventtime_unix; } sub comment { my $self = shift; return LJ::Comment->new( $self->event_journal, jtalkid => $self->jtalkid ); } sub available_for_user { my ( $class, $u, $subscr ) = @_; my $journal = $subscr->journal; my ( $sarg1, $sarg2 ) = ( $subscr->arg1, $subscr->arg2 ); # not allowed to track replies to comments return 0 if !$u->can_track_thread && $sarg2; return 0 if ( $sarg1 == 0 && $sarg2 == 0 ) && $journal && $journal->is_community && !$u->can_track_all_community_comments($journal); return 1; } # return detailed data for XMLRPC::getinbox sub raw_info { my ( $self, $target, $flags ) = @_; my $extended = ( $flags and $flags->{extended} ) ? 1 : 0; # add comments body my $res = $self->SUPER::raw_info; my $comment = $self->comment; my $journal = $self->u; $res->{journal} = $journal->user; return { %$res, action => 'deleted' } unless $comment && $comment->valid && !$comment->is_deleted; my $entry = $comment->entry; return { %$res, action => 'comment_deleted' } unless $entry && $entry->valid; return { %$res, visibility => 'no' } unless $comment->visible_to($target); $res->{entry} = $entry->url; $res->{comment} = $comment->url; $res->{poster} = $comment->poster->user if $comment->poster; $res->{subject} = $comment->subject_text; if ($extended) { $res->{extended}->{subject_raw} = $comment->subject_raw; $res->{extended}->{body} = $comment->body_raw; $res->{extended}->{dtalkid} = $comment->dtalkid; } if ( $comment->is_edited ) { return { %$res, action => 'edited' }; } else { return { %$res, action => 'new' }; } } 1;