7 use Encode qw/decode_utf8 encode_utf8/;
9 our ( %ML, %GET, %POST, %RQ, $title, $body );
10 my ( $status, $result, $answer, $redirected, $user_url, $message );
14 my $restore_old = sub {
15 my $remote = LJ::get_remote();
17 # restore previous login
24 ( $status, $result ) = ( 'error', shift );
29 ( $status, $result, $redirected ) = ( 'redirect', shift, 1 );
34 return LJ::bad_input( $error->(LJ::Lang::ml(shift)) )
38 ( $status, $result ) = ( 'ok', shift );
42 my $skip_form_auth = 0;
44 my $external_site_case = $POST{'is_form_embedded'};
45 my $site_scheme_wrap = ! $external_site_case;
47 # stupid hack to allow hotmail people to post, since hotmail changes
48 # POST forms to GET. this isn't a security problem (GET -> POST escalation)
49 # since talklib.pl's LJ::Talk::Post::init checks for $POST{'ecphash'}
50 # and requires it to be correct. if it's not, the page fails.
51 %POST = %GET if $GET{'ecphash'};
54 my $ajax = $POST{'json'} || 0;
55 my $render_body = sub {
56 if ($LJ::TALK_ABORT_REGEXP) {
57 my $tempbody = $POST{'body'};
58 LJ::CleanHTML::clean_comment(\$tempbody);
60 if ( $tempbody =~ /$LJ::TALK_ABORT_REGEXP/ || $POST{'body'} =~ /$LJ::TALK_ABORT_REGEXP/ ) {
61 ( $status, $result ) = ('error', 'stopword');
67 foreach my $re (@LJ::TALKSPAM) {
68 if ( $POST{'body'} =~ /$re/ ) {
69 ( $status, $result ) = ('error', 'spam');
75 return error( LJ::server_down_html() )
78 my $journal = LJ::load_user($POST{journal});
80 # Indetities are supported using hacks below
81 # and POST hash is empty at this moment
82 if ( $GET{'jid'} && $GET{'pendcid'} ) {
83 $journal = LJ::load_userid($GET{'jid'});
86 my $get_styleinfo = sub {
89 ### Load necessary props
90 my @needed_props = ("stylesys", "s2_style");
91 LJ::load_user_props($journal, @needed_props);
94 LJ::run_hooks("force_s1", $journal, \$forceflag);
95 if ( !$forceflag && $journal->{'stylesys'} == 2 ) {
96 return (2, $journal->{'s2_style'});
98 # no special case and not s2, fall through to s1
102 my $style_u = $journal;
103 my $stylemine = $GET{'style'} eq "mine" ? "style=mine" : "";
105 my $remote = LJ::get_remote();
107 if ($remote && ($stylemine || $remote->opt_stylealwaysmine)) {
111 my ($stylesys, $styleid) = $get_styleinfo->($style_u);
115 if ($stylesys == 2) {
116 $ctx = LJ::S2::s2_context('UNUSED', $styleid);
117 $LJ::S2::CURR_CTX = $ctx;
119 $use_s1 = 0 if !$ctx->[S2::PROPS]->{'view_entry_disabled'} &&
120 LJ::get_cap($style_u, "s2viewentry");
123 if ( LJ::is_enabled('new_comments') and $use_s1 ) {
124 LJ::need_res( LJ::Widget::Form->need_res() );
126 LJ::Talk::resources_for_talkform();
129 my $editid = $POST{editid};
131 # Set the title to be for an error, it will be changed later
133 $title = $ML{'Error'};
136 # identities are a bit of hackery but we'll check to make sure they're
137 # coming back from the identity server and then recreate their
138 # POST hash as if they never left. Watch and see
139 if ($GET{'jid'} && $GET{'pendcid'}) {
140 # Restore their data to reset state where they were
141 my $pendcid = $GET{'pendcid'} + 0;
142 my $journalu = LJ::load_userid($GET{'jid'});
144 return $error_bi->('talkpostdo.bml.error.unable.to.load.user.or.get.database.handle')
145 unless $journalu && $journalu->writer;
147 my $pending = $journalu->selectrow_array("SELECT data FROM pendcomments WHERE jid=? AND pendcid=?",
148 undef, $journalu->{'userid'}, $pendcid);
150 return $error_bi->('talkpostdo.bml.error.unable.to.load.pending.comment')
153 my $penddata = eval { Storable::thaw($pending) };
155 # wouldn't have form auth at this point
160 if ( $GET{'failed'} ) {
161 push @errors, "You chose to cancel your identity verification";
162 return $error->($errors[-1]) if $ajax;
165 # normally require POST. if an ecphash is specified, we'll let
166 # them through since they're coming from a comment page and
167 # validate the hash later.
168 elsif (! LJ::did_post() && !$POST{'ecphash'}) {
169 return $error_bi->('comment.not.posted.POST.required.or.missing.parameter');
172 # as an exception, we do NOT call LJ::text_in() to check for bad
173 # input, since it may be not in UTF-8 in replies coming from mail
174 # clients. We call it later.
175 my $remote_ip = LJ::get_remote_ip();
177 return $error_bi->('talkpostdo.bml.error.your.ip.address.is.detected.as.an.open.proxy')
178 if ($POST{'usertype'} eq "anonymous" || $POST{'usertype'} eq "openid") && LJ::is_open_proxy($remote_ip);
180 my $remote = LJ::get_remote();
181 my $journalu = LJ::load_user($POST{journal});
183 return $error_bi->('talkpostdo.bml.error.unknown.journal')
186 # FIXME: this isn't entirely correct, if ecphash is present but ignored/incorrect
187 # that fix would need to be done in talklib.pl
188 if ( $remote && ! ($skip_form_auth || $POST{'ecphash'} || LJ::check_form_auth()) ) {
189 push @errors, $ML{'.error.invalidform'};
190 return $error->($errors[-1]) if $ajax;
193 $user_url = $journalu->journal_base;
196 # ignore errors for previewing
197 if ($POST{'submitpreview'} || ($POST{'qr'} && $POST{'do_spellcheck'})) {
199 $cookie_auth = 1 if $POST{usertype} eq "cookieuser";
200 my $talkurl = $user_url . "/$POST{itemid}.html";
201 $title = $ML{'.title.preview'};
203 return LJ::Talk::Post::make_preview($talkurl, $cookie_auth, \%POST);
206 my $entry = LJ::Entry->new( $journalu, ditemid => $POST{'itemid'} );
208 # various variables for the external site case
209 my ( $partner, $docid, $logcom_page );
211 if ( $external_site_case ) {
212 my $partner_journalname = $journalu->username;
213 $partner = LJ::PartnerSite->find_by_journal_username( $partner_journalname )
214 or return "[API error: no partner for $partner_journalname]";
216 $docid = $partner->docid_from_entry($entry);
218 my $replyto = $POST{'replyto'}
219 ? int ( $POST{'replyto'} ) * 256 + $entry->anum
222 $logcom_page = "$LJ::SITEROOT/gadgets/logcom.bml?"
223 . 'rsk=' . LJ::eurl($partner->api_key) . '&'
224 . 'docid=' . LJ::eurl($docid) . '&'
225 . 'domain=' . LJ::eurl( $POST{'partner_domain'} ) . '&'
226 . 'replyto=' . $replyto;
228 $POST{'body'} = decode_utf8($POST{'body'});
229 $POST{'body'} = LJ::Antimat->process($POST{'body'});
230 $POST{'body'} = encode_utf8($POST{'body'});
233 # special case here (LJINT-408), only applies to the partners case:
235 # if the user got logged out after they opened the form, disabling them
236 # from commenting, we are going to redirect them back to the login form
238 if ( $external_site_case && !$remote ) {
239 return $redirect->($logcom_page) if $ajax;
241 return BML::redirect( $logcom_page );
245 ## init. this handles all the error-checking, as well.
246 $init = LJ::Talk::Post::init(\%POST, $remote, \$need_captcha, \@errors);
248 return $error->($errors[-1]) if $ajax && int(@errors) or defined @errors[0] and not @errors[0];
249 return if LJ::Request->redirected;
251 # Report errors in a friendly manner by regenerating the field.
252 # Required for challenge/response login, since we also need to regenerate an auth token.
253 # We repopulate what we can via hidden fields - however the objects (journalu & parpost) must be recreated here.
255 # if the user leaving the comment hasn't agreed to the current TOS, and they
256 # didn't click the agreement checkbox, return the form back to them
258 my $commentu = $init ? $init->{comment}->{u} : undef;
260 if ($init && ! $POST{agree_tos} && $commentu && ! $commentu->tosagree_verify) {
264 if (! $init || $require_tos) {
265 # we will show form again with error message, so we need old user
267 $remote = LJ::get_remote();
270 my $dbcr = LJ::get_cluster_def_reader($journalu);
272 return $error_bi->('talkpostdo.bml.error.no.database.connection.present')
275 $sth = $dbcr->prepare("SELECT posterid, state FROM talk2 ".
276 "WHERE journalid=? AND jtalkid=?");
277 $sth->execute($journalu->{userid}, int(($POST{itemid}+0)/256));
278 $parpost = $sth->fetchrow_hashref;
280 $title = $ML{'.title.error'} unless $need_captcha;
282 $POST{replyto} = $POST{parenttalkid}+0 unless exists $POST{replyto};
284 # talkform expects the editid to be in "edit"
285 $POST{edit} = $POST{editid};
287 my $talkform_opts = {
289 'journalu' => $journalu,
290 'parpost' => $parpost,
291 'replyto' => $POST{replyto},
292 'ditemid' => $POST{itemid},
293 'require_tos' => $require_tos,
294 'do_captcha' => $need_captcha,
295 'stylemine' => $GET{'style'} eq "mine",
296 'errors' => \@errors,
301 if ( $external_site_case ) {
302 $talkform_opts->{'embedable_form'} = 1;
307 $remote_sessid = $remote->session->id;
310 my $logout_url = "$LJ::SITEROOT/gadgets/logout.bml?"
311 . 'returnto=' . LJ::eurl($logcom_page) . '&'
312 . 'sessid=' . $remote_sessid;
314 $talkform_opts->{'logout_url'} = $logout_url;
315 $talkform_opts->{'js_check_domain'} = $partner->domain_check_js({
317 'partner_domain' => $POST{'partner_domain'},
320 $talkform_opts->{'partner_remote_ljuser'} = $partner->ljuser_html($remote);
322 $talkform_opts->{'resources_html'} = $partner->resources_html({only_needed => 1});
323 $talkform_opts->{'partner_domain'} = $POST{'partner_domain'};
327 return LJ::Talk::talkform_mobile($talkform_opts)
328 if $POST{'mobile_domain'} =~ qr/^\w\.$LJ::DOMAIN$/;
330 if ( $use_s1 and not $external_site_case and LJ::is_enabled('new_comments') ) {
331 return LJ::Widget::Form->render_body(
334 stylemine => $GET{'style'} eq 'mine'? 1 : 0,
335 replyto => $talkform_opts->{'replyto'},
339 errors => [ map { error => $_ }, @errors ],
340 parenttalkid => $talkform_opts->{'replyto'}, #ref $parpost eq 'HASH'? $init->{'ditemid'} : 0,
343 return LJ::Talk::talkform($talkform_opts);
347 # checked $POST{agree_tos} was checked above if it was necessary,
348 # now we just need to save the userprop
349 if ( $commentu && ! $commentu->tosagree_verify && $POST{agree_tos} ) {
352 return LJ::bad_input($error->($err))
353 unless $commentu->tosagree_set(\$err);
356 my $talkurl = $init->{talkurl};
358 my $entryu = $init->{entryu};
359 my $parent = $init->{parent};
360 my $comment = $init->{comment};
361 my $item = $init->{item};
363 # check max comments only if posting a new comment (not when editing)
365 return $error_bi->('.error.maxcomments')
366 if LJ::Talk::Post::over_maxcomments($journalu, $item->{'jitemid'});
369 # no replying to frozen comments
370 return $error_bi->('/talkpost.bml.error.noreply_frozen')
371 if $parent->{state} eq 'F';
373 # no replying to suspended entries, even by entry poster
374 return $error_bi->('/talkpost.bml.error.noreply_suspended')
375 if $entry && $entry->is_suspended;
377 # no replying to entries/comments in an entry where the remote user or journal are read-only
378 return $error_bi->('/talkpost.bml.error.noreply_readonly_remote')
379 if $remote && $remote->is_readonly;
381 return $error_bi->('/talkpost.bml.error.noreply_readonly_journal')
382 if $journalu && $journalu->is_readonly;
384 ## insertion or editing
385 my $wasscreened = ($parent->{state} eq 'S');
389 return LJ::bad_input($error->($err))
390 unless LJ::Talk::Post::edit_comment($entryu, $journalu, $comment, $parent, $item, \$err);
393 return LJ::bad_input($error->($err))
394 unless LJ::Talk::Post::post_comment($entryu, $journalu, $comment, $parent, $item, \$err);
398 my $dtalkid = $comment->{talkid}*256 + $item->{anum};
399 $add .= "&dtalkid=$dtalkid";
401 # Return page number of created comment only if $POST{json} is true.
406 strict_page_size => 1,
408 # Skip results of sub, need only 'out_page'.
409 LJ::Talk::load_comments_tree($journalu, $remote, "L", $item->{itemid}, \%opts);
411 unless ($opts{'out_error'}) {
412 $add .= "&page=$opts{out_page}";
416 # Allow style=mine for QR redirects
417 my $stylemine = $POST{'stylemine'} ? 'style=mine' : '';
421 if ($external_site_case) {
423 = LJ::PartnerSite->find_by_journal_username($journalu->username);
426 = URI->new( $partner->xdreceiver_url( $POST{'partner_domain'} ) );
428 my $parentid = $parent->{'talkid'}
429 ? $parent->{'talkid'} * 256 + $entry->anum
432 $uri->query_form( $uri->query_form,
433 'mode' => 'addcomment',
434 'comment_id' => $dtalkid,
435 'replyto' => $parentid,
438 $commentlink = $uri->as_string;
440 # need to return on mobile version
442 elsif ($POST{'mobile_domain'} =~ qr/^\w\.$LJ::DOMAIN$/ && $POST{'ret'} =~ m!^/([\w\d\/]+)$!) {
443 $commentlink = "http://$POST{'mobile_domain'}$POST{'ret'}#comments";
445 elsif ($POST{'viewing_thread'} eq '') {
446 $commentlink = LJ::Talk::talkargs($talkurl, "view=$dtalkid", $stylemine) . "#t$dtalkid";
449 $commentlink = LJ::Talk::talkargs($talkurl, "thread=$POST{viewing_thread}", $stylemine) . "#t$dtalkid";
453 $ret .= "<h1>$ML{'.success.title'}</h1>";
456 my $SC = '/talkpost_do.bml';
459 if ( $commentlink =~ /^(.+)(\#.+)$/ ) {
460 ($curl, $cadr) = ($1, $2);
463 $curl = $commentlink;
467 if ($comment->{state} eq 'A') {
468 # Redirect the user back to their post as long as it didn't unscreen its parent,
469 # is screened itself, or they logged in
470 if (!($wasscreened and $parent->{state} ne 'S') && !$init->{didlogin}) {
471 LJ::set_lastcomment($journalu->{'userid'}, $remote, $dtalkid);
473 return $redirect->($commentlink) if $ajax;
475 return BML::redirect($commentlink);
478 $mlcode = '.success.message2';
479 } elsif ($comment->{state} eq 'S') {
480 # otherwise, it's a screened comment.
481 if ($journalu->{'journaltype'} eq 'C') {
482 $mlcode = $POST{'usertype'} eq 'anonymous'
483 ? '.success.screened.comm.anon3'
484 : '.success.screened.comm3';
485 $message = $ML{$mlcode};
486 return $redirect->($commentlink) if $ajax;
489 $mlcode = $POST{'usertype'} eq 'anonymous'
490 ? '.success.screened.user.anon3'
491 : '.success.screened.user3';
492 $message = $ML{$mlcode};
493 return $redirect->($commentlink) if $ajax;
496 # otherwise, it's a spammed comment.
497 if ($journalu->{'journaltype'} eq 'C') {
498 $mlcode = $POST{'usertype'} eq 'anonymous'
499 ? '.success.spammed.comm.anon3'
500 : '.success.spammed.comm3';
501 $message = $ML{$mlcode};
502 return $redirect->($commentlink) if $ajax;
504 $mlcode = $POST{'usertype'} eq 'anonymous'
505 ? '.success.spammed.user.anon3'
506 : '.success.spammed.user3';
507 $message = $ML{$mlcode};
508 return $redirect->($commentlink) if $ajax;
515 $ret .= "<p> " . BML::ml($mlcode, {'aopts' => "href='$commentlink'"}) . " </p>";
517 # did this comment unscreen its parent?
518 if ($wasscreened and $parent->{state} ne 'S') {
519 $ret .= "<p> $ML{'.success.unscreened'} </p>";
522 if ($init->{didlogin}) {
523 $ret .= "<p> $ML{'.success.loggedin'} </p>";
527 $title = $ML{'.title'};
530 return $redirect->($commentlink) if $ajax;
534 }; # end $render_body
536 my $body = $render_body->();
540 my $head = ! $LJ::REQ_HEAD_HAS{'chalresp_js'}++
541 ? $LJ::COMMON_CODE{'chalresp_js'}
545 my $user = LJ::load_user($POST{'journal'});
546 my $entry = $user? LJ::Entry->new( $user, ditemid => $POST{'itemid'} ) : undef;
547 my $args = $POST{'replyto'}? '?replyto='. ($POST{'replyto'} * 256 + ($entry? $entry->anum : 0)) : '?mode=reply';
548 my $url = $entry? $entry->url. $args : $LJ::SITEROOT;
551 my $parent = $init->{'parent'};
552 my $comment = $init->{'comment'};
553 $result = LJ::eurl($result);
554 $add .= '&need_captcha=1' if $need_captcha;
555 $add .= '&hidden=screened' if $comment->{'state'} eq 'S';
556 $add .= '&hidden=spammed' if $comment->{'state'} eq 'B';
557 $add .= '&redirect='. LJ::eurl($url) if $status eq 'error' and not $result;
558 $add .= '&message='. $message if $message;
560 if ( $POST{'talkpost_do'} ) {
561 $user_url = $LJ::SITEROOT;
563 $user_url = $user? $user->journal_base : '';
569 <iframe src="$user_url/xdreceiver.html?type=commentator%2Fsubmit&status=$status&result=$result$add" />
574 elsif ($site_scheme_wrap) {
575 if ( $status eq 'error' and not $result ) {
576 return BML::redirect($url);
579 return BML::render_page({
592 link: htdocs/lostinfo.bml, htdocs/userinfo.bml, htdocs/talkread.bml
593 post: htdocs/talkpost_do.bml