LJSUP-17669: Login.bml form refactoring
[livejournal.git] / htdocs / talkpost_do.bml
blob478c4725ebef09f75a586aa61fb4d1be8a859b0b
1 <?_code
3 #line 3
4     use strict;
6     use LJ::Antimat;
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 );
11     my $add = '';
12     my $need_captcha = 0;
14     my $restore_old = sub {
15         my $remote = LJ::get_remote();
17         # restore previous login
18         #if ( $remote ) {
19         #    $remote->restore();
20         #}
21     };
23     my $error = sub {
24         ( $status, $result ) = ( 'error', shift );
25         return $result;
26     };
28     my $redirect = sub {
29         ( $status, $result, $redirected ) = ( 'redirect', shift, 1 );
30         return $result;
31     };
33     my $error_bi = sub {
34         return LJ::bad_input( $error->(LJ::Lang::ml(shift)) )
35     };
37     my $ok = sub {
38         ( $status, $result ) = ( 'ok', shift );
39     };
41     my @errors;
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'};
52     my $init;
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');
63                 return "";
64             }
65         }
67         foreach my $re (@LJ::TALKSPAM) {
68             if ( $POST{'body'} =~ /$re/ ) {
69                 ( $status, $result ) = ('error', 'spam');
71                 return;
72             }
73         }
75         return error( LJ::server_down_html() )
76             if $LJ::SERVER_DOWN;
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'});
84         }
86         my $get_styleinfo = sub {
87             my $journal = shift;
89             ### Load necessary props
90             my @needed_props = ("stylesys", "s2_style");
91             LJ::load_user_props($journal, @needed_props);
93             my $forceflag = 0;
94             LJ::run_hooks("force_s1", $journal, \$forceflag);
95             if ( !$forceflag && $journal->{'stylesys'} == 2 ) {
96                 return (2, $journal->{'s2_style'});
97             }
98             # no special case and not s2, fall through to s1
99             return (1, 0);
100         };
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)) {
108             $style_u = $remote;
109         }
111         my ($stylesys, $styleid) = $get_styleinfo->($style_u);
113         my $use_s1 = 1;
114         my $ctx = undef;
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");
121         }
123         if ( LJ::is_enabled('new_comments') and $use_s1 ) {
124             LJ::need_res( LJ::Widget::Form->need_res() );
125         } else {
126             LJ::Talk::resources_for_talkform();
127         }
129         my $editid = $POST{editid};
131         # Set the title to be for an error, it will be changed later
132         # upon sucess
133         $title = $ML{'Error'};
135         # identities support
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')
151                 unless $pending;
153             my $penddata = eval { Storable::thaw($pending) };
155             # wouldn't have form auth at this point
156             $skip_form_auth = 1;
158             %POST = %$penddata;
160             if ( $GET{'failed'} ) {
161                 push @errors, "You chose to cancel your identity verification";
162                 return $error->($errors[-1]) if $ajax;
163             }
164         }
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');
170         }
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')
184             unless $journalu;
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;
191         }
193         $user_url = $journalu->journal_base;
195         ## preview
196         # ignore errors for previewing
197         if ($POST{'submitpreview'} || ($POST{'qr'} && $POST{'do_spellcheck'})) {
198             my $cookie_auth;
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);
204         }
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
220                 : 0;
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'});     
231         }
233         # special case here (LJINT-408), only applies to the partners case:
234         #
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
237         {
238             if ( $external_site_case && !$remote ) {
239                 return $redirect->($logcom_page) if $ajax;
241                 return BML::redirect( $logcom_page );
242             }
243         }
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
257         my $require_tos = 0;
258         my $commentu = $init ? $init->{comment}->{u} : undef;
260         if ($init && ! $POST{agree_tos} && $commentu && ! $commentu->tosagree_verify) {
261             $require_tos = 1;
262         }
264         if (! $init || $require_tos) {
265             # we will show form again with error message, so we need old user
266             $restore_old->();
267             $remote = LJ::get_remote();
269             my ($sth, $parpost);
270             my $dbcr = LJ::get_cluster_def_reader($journalu);
272             return $error_bi->('talkpostdo.bml.error.no.database.connection.present')
273                 unless $dbcr;
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 = {
288                 'remote'      => $remote,
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,
297                 'form'        => \%POST,
298                 'talkpost_do' => 1,
299             };
301             if ( $external_site_case ) {
302                 $talkform_opts->{'embedable_form'} = 1;
304                 my $remote_sessid;
306                 if ($remote) {
307                     $remote_sessid = $remote->session->id;
308                 }
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({
316                     'mode'           => 'logcom',
317                     'partner_domain' => $POST{'partner_domain'},
318                 });
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'};
324             }
326             # show mobile form
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(
332                     entry        => $entry,
333                     standalone   => 2,
334                     stylemine    => $GET{'style'} eq 'mine'? 1 : 0,
335                     replyto      => $talkform_opts->{'replyto'},
336                     editid       => $editid,
337                     form         => \%POST,
338                     parpost      => $parpost,
339                     errors       => [ map { error => $_ }, @errors ],
340                     parenttalkid => $talkform_opts->{'replyto'}, #ref $parpost eq 'HASH'? $init->{'ditemid'} : 0,
341                 );
342             } else {
343                 return LJ::Talk::talkform($talkform_opts);
344             }
345         }
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} ) {
350             my $err = "";
352             return LJ::bad_input($error->($err))
353                 unless $commentu->tosagree_set(\$err);
354         }
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)
364         unless ($editid) {
365             return $error_bi->('.error.maxcomments')
366                 if LJ::Talk::Post::over_maxcomments($journalu, $item->{'jitemid'});
367         }
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');
386         my $err;
388         if ($editid) {
389             return LJ::bad_input($error->($err))
390                 unless LJ::Talk::Post::edit_comment($entryu, $journalu, $comment, $parent, $item, \$err);
391         }
392         else {
393             return LJ::bad_input($error->($err))
394                 unless LJ::Talk::Post::post_comment($entryu, $journalu, $comment, $parent, $item, \$err);
395         }
397         # Yeah, we're done.
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.
402         if ($ajax) {
403             my %opts = (
404                 view        => $dtalkid,
405                 init_comobj => 0,
406                 strict_page_size => 1,
407             );
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}";
413             }
414         }
416         # Allow style=mine for QR redirects
417         my $stylemine = $POST{'stylemine'} ? 'style=mine' : '';
419         my $commentlink;
421         if ($external_site_case) {
422             my $partner
423                 = LJ::PartnerSite->find_by_journal_username($journalu->username);
425             my $uri
426                 = URI->new( $partner->xdreceiver_url( $POST{'partner_domain'} ) );
428             my $parentid = $parent->{'talkid'}
429                 ? $parent->{'talkid'} * 256 + $entry->anum
430                 : 0;
432             $uri->query_form( $uri->query_form,
433                 'mode'       => 'addcomment',
434                 'comment_id' => $dtalkid,
435                 'replyto'    => $parentid,
436             );
438             $commentlink = $uri->as_string;
440         # need to return on mobile version
441         }
442         elsif ($POST{'mobile_domain'} =~ qr/^\w\.$LJ::DOMAIN$/ && $POST{'ret'} =~ m!^/([\w\d\/]+)$!) {
443             $commentlink = "http://$POST{'mobile_domain'}$POST{'ret'}#comments";
444         }
445         elsif ($POST{'viewing_thread'} eq '') {
446             $commentlink = LJ::Talk::talkargs($talkurl, "view=$dtalkid", $stylemine) . "#t$dtalkid";
447         }
448         else {
449             $commentlink = LJ::Talk::talkargs($talkurl, "thread=$POST{viewing_thread}", $stylemine) . "#t$dtalkid";
450         }
452         my $ret = "";
453         $ret .= "<h1>$ML{'.success.title'}</h1>";
455         my $mlcode;
456         my $SC = '/talkpost_do.bml';
457         my ($curl, $cadr);
459         if ( $commentlink =~ /^(.+)(\#.+)$/ ) {
460             ($curl, $cadr) = ($1, $2);
461         }
462         else {
463             $curl = $commentlink;
464             $cadr = '';
465         }
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);
476             }
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;
487             }
488             else {
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;
494             }
495         } else {
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;
503             } else {
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;
509             }
510         }
513         $ok->($mlcode);
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>";
520         }
522         if ($init->{didlogin}) {
523             $ret .= "<p> $ML{'.success.loggedin'} </p>";
524         }
526         # Sucessful!
527         $title = $ML{'.title'};
529         $message = $ret;
530         return $redirect->($commentlink) if $ajax;
532         return $ret;
534     }; # end $render_body
536     my $body = $render_body->();
538     $restore_old->();
540     my $head = ! $LJ::REQ_HEAD_HAS{'chalresp_js'}++
541         ? $LJ::COMMON_CODE{'chalresp_js'}
542         : '';
544     my $talkurl;
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;
550     if ( $ajax ) {
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;
559         # LJSUP-10671
560         if ( $POST{'talkpost_do'} ) {
561             $user_url = $LJ::SITEROOT;
562         } else {
563             $user_url = $user? $user->journal_base : '';
564         }
565         return qq(
566 <!DOCTYPE html>
567 <html>
568 <head></head>
569 <iframe src="$user_url/xdreceiver.html?type=commentator%2Fsubmit&status=$status&result=$result$add" />
570 </body>
571 </html>
573     }
574     elsif ($site_scheme_wrap) {
575         if ( $status eq 'error' and not $result ) {
576             return BML::redirect($url);
577         }
579         return BML::render_page({
580             'body'  => $body,
581             'title' => $title,
582             'head'  => $head,
583         });
584     }
585     else {
587         return $body;
588     }
590 _code?><?_c <LJDEP>
591 lib: LJ::SpellCheck
592 link: htdocs/lostinfo.bml, htdocs/userinfo.bml, htdocs/talkread.bml
593 post: htdocs/talkpost_do.bml
594 </LJDEP> _c?>