#!/usr/bin/perl # # DW::Controller::Support::Faq # # This controller is for the Support FAQ page. # # Authors: # hotlevel4 # # Copyright (c) 2015 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::Support::Faq; use strict; use warnings; use DW::Controller; use DW::Routing; use DW::Template; DW::Routing->register_string( '/support/faq', \&faq_handler, app => 1 ); DW::Routing->register_string( '/support/faqpop', \&faqpop_handler, app => 1 ); DW::Routing->register_string( '/support/faqbrowse', \&faqbrowse_handler, app => 1 ); DW::Routing->register_string( '/support/faqsearch', \&faqsearch_handler, app => 1 ); sub faq_handler { my $r = DW::Request->get; my ( $ok, $rv ) = controller( anonymous => 1, form_auth => 1 ); return $rv unless $ok; my $remote = $rv->{remote}; my $user; my $user_url; my $vars = {}; my $dbr = LJ::get_db_reader(); my $sth; my %faqcat; my %faqq; my $ret = ""; $sth = $dbr->prepare( "SELECT faqcat, faqcatname, catorder FROM faqcat " . "WHERE faqcat<>'int-abuse'" ); $sth->execute; while ( $_ = $sth->fetchrow_hashref ) { $faqcat{ $_->{faqcat} } = $_; } # Get remote username and journal URL, or example user's username and journal URL if ($remote) { $user = $remote->user; $user_url = $remote->journal_base; } else { my $u = LJ::load_user($LJ::EXAMPLE_USER_ACCOUNT); $user = $u ? $u->user : "[Unknown or undefined example username]"; $user_url = $u ? $u->journal_base : "[Unknown or undefined example username]"; } foreach my $f ( LJ::Faq->load_all ) { $f->render_in_place( { user => $user, url => $user_url } ); $faqq{ $f->faqid } = $f; } foreach my $faqcat ( sort { $faqcat{$a}->{catorder} <=> $faqcat{$b}->{catorder} } keys %faqcat ) { my $countfaqs = 0; foreach ( grep { $faqq{$_}->faqcat eq $faqcat } keys %faqq ) { $countfaqs++; } next unless $countfaqs; push @{ $vars->{faqcats} }, { faqcat => $faqcat, faqcatname => $faqcat{$faqcat}->{faqcatname}, }; foreach my $faqid ( sort { $faqq{$a}->sortorder <=> $faqq{$b}->sortorder } grep { $faqq{$_}->faqcat eq $faqcat } keys %faqq ) { my $q = $faqq{$faqid}->question_html; next unless $q; $q =~ s/^\s+//; $q =~ s/\s+$//; $q =~ s!\n!
!g; push @{ $vars->{questions}->{$faqcat}->{faqqs} }, { q => $q, faqid => $faqid }; } } return DW::Template->render_template( 'support/faq.tt', $vars ); } sub faqpop_handler { my $r = DW::Request->get; my $get = $r->get_args; my ( $ok, $rv ) = controller( anonymous => 1, form_auth => 1 ); return $rv unless $ok; my $vars = {}; my $remote = $rv->{remote}; my $user; my $user_url; # Get remote username and journal URL, or example user's username and journal URL if ($remote) { $user = $remote->user; $user_url = $remote->journal_base; } else { my $u = LJ::load_user($LJ::EXAMPLE_USER_ACCOUNT); $user = $u ? $u->user : "[Unknown or undefined example username]"; $user_url = $u ? $u->journal_base : "[Unknown or undefined example username]"; } my $dbr = LJ::get_db_reader(); my $sth = $dbr->prepare( "SELECT statkey, statval FROM stats WHERE statcat='pop_faq' ORDER BY statval DESC LIMIT 50" ); $sth->execute; while ( my $s = $sth->fetchrow_hashref ) { my $f = LJ::Faq->load( $s->{statkey} ); $f->render_in_place( { user => $user, url => $user_url } ); my $q = $f->question_html; $q =~ s/^\s+//; $q =~ s/\s+$//; $q =~ s!\n!
!g; push @{ $vars->{faqs} }, { question => $q, statval => $s->{statval}, faqid => $f->faqid }; } return DW::Template->render_template( 'support/faqpop.tt', $vars ); } sub faqbrowse_handler { my $r = DW::Request->get; my ( $ok, $rv ) = controller( anonymous => 1, form_auth => 1 ); return $rv unless $ok; my $remote = $rv->{remote}; my $GET = $r->get_args; my $user; my $user_url; my $vars = {}; if ($remote) { $vars->{remote} = $remote; $user = $remote->user; $user_url = $remote->journal_base; } else { my $u = LJ::load_user($LJ::EXAMPLE_USER_ACCOUNT); $user = $u ? $u->user : "[Unknown or undefined example username]"; $user_url = $u ? $u->journal_base : "[Unknown or undefined example username]"; } # get faqid and redirect to faq.bml if none my $faqidarg = $GET->{'faqid'} ? $GET->{'faqid'} + 0 : 0; # FIXME: disallow both faqid and faqcat (or ignore one) my $faqcatarg = $GET->{'faqcat'}; unless ( $faqidarg || $faqcatarg ) { return $r->redirect("faq"); } # get language settings my $curlang = $GET->{'lang'} || LJ::Lang::get_effective_lang(); my $deflang = BML::get_language_default(); my $altlang = $curlang ne $deflang; my $mll = LJ::Lang::get_lang($curlang); my $mld = LJ::Lang::get_dom("faq"); $altlang = 0 unless $mll && $mld; my $lang = $altlang ? $curlang : $deflang; $vars->{altlang} = $altlang; $vars->{curlang} = $curlang; my $view = $GET->{'view'} // ''; my $mode = ( $view eq 'full' || $faqidarg ) ? 'answer' : 'summary'; my @faqs; my $title; my $body; my $dbr = LJ::get_db_reader(); if ($faqidarg) { # loading single faqid @faqs = ( LJ::Faq->load( $faqidarg, lang => $lang ) ); unless ( $faqs[0] ) { $title = LJ::Lang::ml( '/support/faqbrowse.tt.error.title_nofaq', { faqid => $faqidarg } ); $vars->{title} = $title; return DW::Template->render_template( 'support/faqbrowse.tt', $vars ); } $faqs[0]->render_in_place( { user => $user, url => $user_url } ); $title = $faqs[0]->question_html; } elsif ($faqcatarg) { # loading entire faqcat my $catname; if ($altlang) { $catname = LJ::Lang::get_text( $curlang, "cat.$faqcatarg", $mld->{dmid} ); } else { $catname = $dbr->selectrow_array( "SELECT faqcatname FROM faqcat WHERE faqcat=?", undef, $faqcatarg ); die $dbr->errstr if $dbr->err; } $title = LJ::Lang::ml( '/support/faqbrowse.tt.title_cat', { catname => LJ::ehtml($catname) } ); @faqs = sort { $a->sortorder <=> $b->sortorder } LJ::Faq->load_all( lang => $lang, cat => $faqcatarg ); LJ::Faq->render_in_place( { lang => $lang, user => $user, url => $user_url }, @faqs ); $vars->{faqcatarg} = 1; } my $dbh; my $categoryname; my @cleanfaqs; my $qterm = $GET->{'q'}; $vars->{q} = $qterm ? "&q=" . LJ::eurl($qterm) : ""; foreach my $f (@faqs) { my $cleanf; my $faqid = $f->faqid; # Used throughout, including in interpolations $dbh ||= LJ::get_db_writer(); # log this faq view if ( $remote && LJ::is_enabled('faquses') ) { $dbh->do( "REPLACE INTO faquses (faqid, userid, dateview) " . "VALUES (?, ?, NOW())", undef, $faqid, $remote->{'userid'} ); } BML::note_mod_time( $f->unixmodtime ); my $summary = $f->summary_raw; my $answer = $f->answer_raw; # What to display? my $display_summary; my $display_answer; if ( $mode eq 'answer' ) { # answer, summary if present $display_answer = 1; $display_summary = $f->has_summary; } else { # summary if there's one, answer if there's no summary $display_summary = $f->has_summary; $display_answer = !$display_summary; } # If summaries are disabled, pretend the FAQ doesn't have one. unless ( LJ::is_enabled('faq_summaries') ) { $display_answer ||= $display_summary; $display_summary = 0; } # escape question my $question = $f->question_html; $question =~ s/^\s+//; $question =~ s/\s+$//; $question =~ s/\n/
/g; # Clean this as if it were an entry, but don't allow lj-cuts LJ::CleanHTML::clean_event( \$summary, { 'ljcut_disable' => 1 } ) if $display_summary; LJ::CleanHTML::clean_event( \$answer, { 'ljcut_disable' => 1 } ) if $display_answer; # Highlight search terms my $term = sub { my $xterm = shift; return $xterm if $xterm =~ m!^https?://!; return "" . LJ::ehtml($xterm) . ""; }; if ($qterm) { $question =~ s/(\Q$qterm\E)/$term->($1)/ige; $summary //= ''; # no undefined string warnings # don't highlight terms in URLs or HTML tags # FIXME: if the search term is present in a tag, should still # highlight occurences outside tags. $summary =~ s!((?:https?://[^>]+)?\Q$qterm\E)!$term->($1)!ige unless $summary =~ m!<[^>]*\Q$qterm\E[^>]*>!; $answer =~ s!((?:https?://[^>]+)?\Q$qterm\E)!$term->($1)!ige unless $answer =~ m!<[^>]*\Q$qterm\E[^>]*>!; } my $lastmodwho = LJ::get_username( $f->lastmoduserid ); $cleanf = { 'faqid' => $faqid, 'question' => $question, 'answer' => $answer, 'summary' => $summary, 'display_summary' => $display_summary, 'display_answer' => $display_answer, 'lastmodwho' => $lastmodwho, 'lastmodtime' => $f->lastmodtime }; # this is incredibly ugly. i'm sorry. if ( $altlang && $remote && $remote->has_priv( "translate", $curlang ) ) { my @itids; push @itids, LJ::Lang::get_itemid( $mld->{'dmid'}, "$faqid.$_" ) foreach qw(1question 3summary 2answer); my $items = join( ",", map { $mld->{'dmid'} . ":" . $_ } @itids ); $cleanf->{t_items} = $items; } my $backfaqcat = $f->faqcat // ''; # get the name of this faq's category, if loading a single faqid if ($faqidarg) { if ($altlang) { $categoryname = LJ::Lang::get_text( $curlang, "cat.$backfaqcat", $mld->{'dmid'} ); } else { $categoryname = $dbr->selectrow_array( "SELECT faqcatname FROM faqcat WHERE faqcat=?", undef, $backfaqcat ); } $cleanf->{categoryname} = $categoryname; } push @cleanfaqs, $cleanf; } $vars->{title} = $title; $vars->{faqs} = \@cleanfaqs; return DW::Template->render_template( 'support/faqbrowse.tt', $vars ); } sub faqsearch_handler { my $r = DW::Request->get; my ( $ok, $rv ) = controller( anonymous => 1, form_auth => 1 ); return $rv unless $ok; my $remote = $rv->{remote}; my $GET = $r->get_args; my $vars; my @langs; foreach my $code (@LJ::LANGS) { my $l = LJ::Lang::get_lang($code); next unless $l; my $item = "langname.$code"; my $namethislang = BML::ml($item); my $namenative = LJ::Lang::get_text( $l->{'lncode'}, $item ); push @langs, $code; my $s = $namenative; $s .= " ($namethislang)" if $namethislang ne $namenative; push @langs, $s; } my $curr = BML::get_language(); my $sel = $GET->{'lang'} || $curr; my $q = $GET->{'q'}; $vars->{langs} = \@langs; $vars->{sel} = $sel; $vars->{q} = $q; if ( $q && length($q) > 2 ) { my $lang = $GET->{lang} || $curr || $LJ::DEFAULT_LANG; my $user; my $user_url; # Get remote username and journal URL, or example user's username and journal URL if ($remote) { $user = $remote->user; $user_url = $remote->journal_base; } else { my $u = LJ::load_user($LJ::EXAMPLE_USER_ACCOUNT); $user = $u ? $u->user : "[Unknown or undefined example username]"; $user_url = $u ? $u->journal_base : "[Unknown or undefined example username]"; } my @results = LJ::Faq->load_matching( $q, lang => $lang, user => $user, url => $user_url ); if ( @results > 25 ) { @results = @results[ 0 .. 24 ]; } my $term = sub { my $term = shift; return "" . LJ::ehtml($term) . ""; }; my @clean_results; foreach my $f (@results) { my $dq = $f->question_html; $dq =~ s/(\Q$q\E)/$term->($1) /ige; my $ueq = LJ::eurl($q); my $ul = $GET->{'lang'} ne $curr ? "&lang=" . $GET->{'lang'} : ''; my $clean = { dq => $dq, ueq => $ueq, ul => $ul, id => $f->faqid }; push @clean_results, $clean; } $vars->{results} = \@clean_results; } return DW::Template->render_template( 'support/faqsearch.tt', $vars ); } 1;