317 lines
12 KiB
Perl
317 lines
12 KiB
Perl
|
|
#!/usr/bin/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.
|
||
|
|
|
||
|
|
use strict;
|
||
|
|
|
||
|
|
package LJ::S2;
|
||
|
|
|
||
|
|
sub ReplyPage {
|
||
|
|
my ( $u, $remote, $opts ) = @_;
|
||
|
|
|
||
|
|
my $p = Page( $u, $opts );
|
||
|
|
$p->{'_type'} = "ReplyPage";
|
||
|
|
$p->{'view'} = "reply";
|
||
|
|
|
||
|
|
my $get = $opts->{'getargs'};
|
||
|
|
|
||
|
|
my ( $entry, $s2entry ) = EntryPage_entry( $u, $remote, $opts );
|
||
|
|
return if $opts->{'suspendeduser'};
|
||
|
|
|
||
|
|
# reply page of suspended entry cannot be accessed by anyone, even entry poster
|
||
|
|
if ( $entry && $entry->is_suspended ) {
|
||
|
|
$opts->{suspendedentry} = 1;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
# read-only users can't comment anywhere
|
||
|
|
if ( $remote && $remote->is_readonly ) {
|
||
|
|
$opts->{readonlyremote} = 1;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
# no one can comment in a read-only journal
|
||
|
|
if ( $u->is_readonly ) {
|
||
|
|
$opts->{readonlyjournal} = 1;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
return if $opts->{'handler_return'};
|
||
|
|
return if $opts->{'redir'};
|
||
|
|
return if $opts->{internal_redir};
|
||
|
|
|
||
|
|
my $ditemid = $entry->ditemid;
|
||
|
|
my $replytoid = $get->{replyto} ? $get->{replyto} : 0;
|
||
|
|
|
||
|
|
# canonical link to the entry or comment thread
|
||
|
|
$p->{head_content} .= LJ::canonical_link( $entry->url, $replytoid );
|
||
|
|
|
||
|
|
$p->{'head_content'} .= qq{
|
||
|
|
<script type="text/javascript" src="$LJ::JSPREFIX/md5.js"></script>
|
||
|
|
};
|
||
|
|
|
||
|
|
LJ::need_res( LJ::S2::tracking_popup_js() );
|
||
|
|
|
||
|
|
# include JS for quick reply, icon browser, and ajax cut tag
|
||
|
|
my $handle_with_siteviews = $opts->{handle_with_siteviews_ref}
|
||
|
|
&& ${ $opts->{handle_with_siteviews_ref} };
|
||
|
|
LJ::Talk::init_s2journal_js(
|
||
|
|
iconbrowser => $remote && $remote->can_use_userpic_select,
|
||
|
|
siteskin => $handle_with_siteviews,
|
||
|
|
noqr => 1
|
||
|
|
);
|
||
|
|
|
||
|
|
if ( $u->should_block_robots || $entry->should_block_robots ) {
|
||
|
|
$p->{'head_content'} .= LJ::robot_meta_tags();
|
||
|
|
}
|
||
|
|
|
||
|
|
$p->{'entry'} = $s2entry;
|
||
|
|
LJ::Hooks::run_hook( 'notify_event_displayed', $entry );
|
||
|
|
|
||
|
|
# setup the replying item
|
||
|
|
my $replyto = $s2entry;
|
||
|
|
my $editid = $get->{edit} ? $get->{edit} : 0;
|
||
|
|
my $parpost;
|
||
|
|
my $parentcomment;
|
||
|
|
|
||
|
|
my $comment;
|
||
|
|
my %comment_values;
|
||
|
|
if ($editid) {
|
||
|
|
my $errref;
|
||
|
|
$comment = LJ::Comment->new( $u, dtalkid => $editid );
|
||
|
|
unless ($remote) {
|
||
|
|
my $redir = LJ::eurl( LJ::create_url( undef, keep_args => 1 ) );
|
||
|
|
$opts->{'redir'} = "$LJ::SITEROOT/?returnto=$redir&errmsg=notloggedin";
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
unless ( $comment->remote_can_edit( \$errref ) ) {
|
||
|
|
if ($errref) {
|
||
|
|
$opts->{status} = "403 Forbidden";
|
||
|
|
return "<p>$errref</p>";
|
||
|
|
}
|
||
|
|
$opts->{'handler_return'} = 403;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$parpost = $comment->parent;
|
||
|
|
|
||
|
|
# for comments where the parent comment is deleted, we pretend the parent comment doesn't exist so the user can edit
|
||
|
|
$parpost = undef if $parpost && $parpost->is_deleted;
|
||
|
|
$replytoid = $parpost ? $comment->parent->dtalkid : 0;
|
||
|
|
|
||
|
|
$comment_values{editid} = $editid;
|
||
|
|
$comment_values{replyto} = $replytoid;
|
||
|
|
$comment_values{subject} = $comment->subject_orig;
|
||
|
|
$comment_values{body} = $comment->body_orig;
|
||
|
|
$comment_values{subjecticon} = $comment->prop('subjecticon');
|
||
|
|
$comment_values{prop_opt_preformatted} = $comment->prop('opt_preformatted');
|
||
|
|
$comment_values{prop_picture_keyword} = $comment->userpic_kw;
|
||
|
|
$comment_values{prop_editor} = $comment->prop('editor');
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($replytoid) {
|
||
|
|
my $re_talkid = int( $replytoid >> 8 );
|
||
|
|
my $re_anum = $replytoid % 256;
|
||
|
|
unless ( $re_anum == $entry->anum ) {
|
||
|
|
$opts->{'handler_return'} = 404;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $dtalkid = $re_talkid * 256 + $entry->anum;
|
||
|
|
|
||
|
|
# FIXME: Why are we loading the comment manually when we do LJ::Comment->new below
|
||
|
|
# and could do everything through there.
|
||
|
|
my $sql =
|
||
|
|
"SELECT jtalkid, posterid, state, datepost FROM talk2 "
|
||
|
|
. "WHERE journalid=$u->{'userid'} AND jtalkid=$re_talkid "
|
||
|
|
. "AND nodetype='L' AND nodeid="
|
||
|
|
. $entry->jitemid;
|
||
|
|
foreach my $pass ( 1, 2 ) {
|
||
|
|
my $db = $pass == 1 ? LJ::get_cluster_reader($u) : LJ::get_cluster_def_reader($u);
|
||
|
|
$parpost = $db->selectrow_hashref($sql);
|
||
|
|
last if $parpost;
|
||
|
|
}
|
||
|
|
my $parentcomment = LJ::Comment->new( $u, jtalkid => $re_talkid );
|
||
|
|
unless ( $parpost and $parpost->{'state'} ne 'D' ) {
|
||
|
|
|
||
|
|
# FIXME: This is a hack. See below...
|
||
|
|
|
||
|
|
$opts->{status} = "404 Not Found";
|
||
|
|
return "<p>This comment has been deleted; you cannot reply to it.</p>";
|
||
|
|
}
|
||
|
|
if ( $parpost->{state} eq 'S' && !$parentcomment->visible_to($remote) ) {
|
||
|
|
$opts->{'handler_return'} = 403;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if ( $parpost->{'state'} eq 'F' ) {
|
||
|
|
|
||
|
|
# frozen comment, no replies allowed
|
||
|
|
|
||
|
|
# FIXME: eventually have S2 ErrorPage to handle this and similar
|
||
|
|
# For now, this hack will work; this error is pretty uncommon anyway.
|
||
|
|
$opts->{status} = "403 Forbidden";
|
||
|
|
return "<p>This thread has been frozen; no more replies are allowed.</p>";
|
||
|
|
}
|
||
|
|
if ( $entry->is_suspended ) {
|
||
|
|
$opts->{status} = "403 Forbidden";
|
||
|
|
return "<p>This entry has been suspended; you cannot reply to it.</p>";
|
||
|
|
}
|
||
|
|
if ( $remote && $remote->is_readonly ) {
|
||
|
|
$opts->{status} = "403 Forbidden";
|
||
|
|
return "<p>You are read-only. You cannot reply to this entry.</p>";
|
||
|
|
}
|
||
|
|
|
||
|
|
my $tt = LJ::get_talktext2( $u, $re_talkid );
|
||
|
|
$parpost->{'subject'} = $tt->{$re_talkid}->[0];
|
||
|
|
$parpost->{'body'} = $tt->{$re_talkid}->[1];
|
||
|
|
$parpost->{'props'} =
|
||
|
|
LJ::load_talk_props2( $u, [$re_talkid] )->{$re_talkid} || {};
|
||
|
|
|
||
|
|
if ( $parpost->{'props'}->{'unknown8bit'} ) {
|
||
|
|
LJ::item_toutf8( $u, \$parpost->{'subject'}, \$parpost->{'body'}, {} );
|
||
|
|
}
|
||
|
|
|
||
|
|
my $datetime = DateTime_unix( LJ::mysqldate_to_time( $parpost->{'datepost'} ) );
|
||
|
|
|
||
|
|
my $comment_userpic;
|
||
|
|
my $s2poster;
|
||
|
|
|
||
|
|
my $pu = $parentcomment->poster;
|
||
|
|
if ($pu) {
|
||
|
|
return $opts->{handler_return} = 403
|
||
|
|
if $pu->is_suspended; # do not show comments by suspended users
|
||
|
|
$s2poster = UserLite($pu);
|
||
|
|
|
||
|
|
my ( $pic, $pickw ) = $parentcomment->userpic;
|
||
|
|
$comment_userpic = Image_userpic( $pu, $pic ? $pic->picid : 0, $pickw );
|
||
|
|
}
|
||
|
|
|
||
|
|
my $anon_comment = LJ::Talk::treat_as_anon( $pu, $u );
|
||
|
|
LJ::CleanHTML::clean_comment(
|
||
|
|
\$parpost->{'body'},
|
||
|
|
{
|
||
|
|
preformatted => $parentcomment->prop('opt_preformatted'),
|
||
|
|
anon_comment => $anon_comment,
|
||
|
|
nocss => $anon_comment,
|
||
|
|
editor => $parentcomment->prop('editor'),
|
||
|
|
datepost => $parentcomment->{datepost}, # for format guessing
|
||
|
|
is_imported => defined $parentcomment->prop('import_source') ? 1 : 0,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
my $cmtobj = LJ::Comment->new( $u, dtalkid => $dtalkid );
|
||
|
|
|
||
|
|
my $tz_remote;
|
||
|
|
my $datetime_remote;
|
||
|
|
my $datetime_poster;
|
||
|
|
if ($remote) {
|
||
|
|
my $tz = $remote->prop("timezone");
|
||
|
|
$tz_remote = $tz ? eval { DateTime::TimeZone->new( name => $tz ); } : undef;
|
||
|
|
}
|
||
|
|
|
||
|
|
$datetime_remote = $tz_remote ? DateTime_tz( $cmtobj->unixtime, $tz_remote ) : undef;
|
||
|
|
$datetime_poster =
|
||
|
|
$parentcomment ? DateTime_tz( $cmtobj->unixtime, $parentcomment->poster ) : undef;
|
||
|
|
|
||
|
|
my $replyargs = LJ::viewing_style_args(%$get);
|
||
|
|
$replyto = {
|
||
|
|
'_type' => 'Comment',
|
||
|
|
'subject' => LJ::ehtml( $parpost->{'subject'} ),
|
||
|
|
'text' => $parpost->{'body'},
|
||
|
|
'userpic' => $comment_userpic,
|
||
|
|
'poster' => $s2poster,
|
||
|
|
'journal' => $s2entry->{'journal'},
|
||
|
|
'metadata' => {},
|
||
|
|
'permalink_url' => $cmtobj->url($replyargs),
|
||
|
|
'depth' => 1,
|
||
|
|
'parent_url' => $cmtobj->parent_url($replyargs),
|
||
|
|
'threadroot_url' => $cmtobj->threadroot_url($replyargs),
|
||
|
|
'time' => $datetime,
|
||
|
|
'system_time' => $datetime,
|
||
|
|
'time_remote' => $datetime_remote,
|
||
|
|
'time_poster' => $datetime_poster,
|
||
|
|
'tags' => [],
|
||
|
|
'talkid' => $dtalkid,
|
||
|
|
'link_keyseq' => ['delete_comment'],
|
||
|
|
'screened' => $parpost->{'state'} eq "S" ? 1 : 0,
|
||
|
|
'frozen' => $parpost->{'state'} eq "F" ? 1 : 0,
|
||
|
|
'deleted' => $parpost->{'state'} eq "D" ? 1 : 0,
|
||
|
|
'full' => 1,
|
||
|
|
timeformat24 => $remote && $remote->use_24hour_time,
|
||
|
|
admin_post => $cmtobj->admin_post ? 1 : 0,
|
||
|
|
};
|
||
|
|
|
||
|
|
# Conditionally add more links to the keyseq
|
||
|
|
my $link_keyseq = $replyto->{'link_keyseq'};
|
||
|
|
push @$link_keyseq, $replyto->{'screened'} ? 'unscreen_comment' : 'screen_comment';
|
||
|
|
push @$link_keyseq, $replyto->{'frozen'} ? 'unfreeze_thread' : 'freeze_thread';
|
||
|
|
push @$link_keyseq, "watch_thread" if LJ::is_enabled('esn');
|
||
|
|
push @$link_keyseq, "unwatch_thread" if LJ::is_enabled('esn');
|
||
|
|
push @$link_keyseq, "watching_parent" if LJ::is_enabled('esn');
|
||
|
|
unshift @$link_keyseq, "edit_comment" if LJ::is_enabled('edit_comments');
|
||
|
|
}
|
||
|
|
|
||
|
|
$p->{'replyto'} = $replyto;
|
||
|
|
|
||
|
|
$p->{'form'} = {
|
||
|
|
'_type' => "ReplyForm",
|
||
|
|
'_remote' => $remote,
|
||
|
|
'_u' => $u,
|
||
|
|
'_ditemid' => $ditemid,
|
||
|
|
'_entry' => $entry,
|
||
|
|
'_parpost' => $parpost,
|
||
|
|
'_values' => \%comment_values,
|
||
|
|
'_styleopts' => $p->{_styleopts},
|
||
|
|
'_thread' => $get->{thread} || 0,
|
||
|
|
};
|
||
|
|
|
||
|
|
$p->{'isedit'} = $editid ? 1 : 0;
|
||
|
|
|
||
|
|
return $p;
|
||
|
|
}
|
||
|
|
|
||
|
|
package S2::Builtin::LJ;
|
||
|
|
|
||
|
|
sub ReplyForm__print {
|
||
|
|
my ( $ctx, $form ) = @_;
|
||
|
|
my $remote = $form->{'_remote'};
|
||
|
|
my $u = $form->{'_u'};
|
||
|
|
my $parpost = $form->{'_parpost'};
|
||
|
|
my $parent = $parpost ? $parpost->{'jtalkid'} : 0;
|
||
|
|
|
||
|
|
my $post_vars = DW::Request->get->post_args;
|
||
|
|
$post_vars = $form->{_values}
|
||
|
|
unless keys %$post_vars; # _values only gets populated when editing comment. -NF
|
||
|
|
|
||
|
|
$S2::pout->(
|
||
|
|
LJ::Talk::talkform(
|
||
|
|
{
|
||
|
|
'journalu' => $u,
|
||
|
|
'parpost' => $parpost,
|
||
|
|
'replyto' => $parent,
|
||
|
|
'ditemid' => $form->{'_ditemid'},
|
||
|
|
'styleopts' => $form->{_styleopts},
|
||
|
|
'thread' => $form->{_thread},
|
||
|
|
'form' => $post_vars,
|
||
|
|
'do_captcha' => LJ::Talk::Post::require_captcha_test(
|
||
|
|
$remote, $u, $post_vars->{body}, $form->{'_entry'}
|
||
|
|
)
|
||
|
|
}
|
||
|
|
)
|
||
|
|
);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
1;
|