#!/usr/bin/perl -w

use strict;
use warnings;
use LWP::UserAgent;
#use LWP::DebugFile qw(+trace +cons +debug);
use HTTP::Cookies;
use Syntax::Highlight::Universal;
use Text::Diff; #I store the full text of each change anyway
use Digest::MD5 qw(md5_hex);
use DBI;
use URI::Escape;
use CGI;
use CGI::Cookie;
use Carp::Datum qw(:all off);
use Class::Date;

#TODO (important):
#make cookie code nicer, now that you've got it working (also, figure out how to break it)
#be sure people can't PM spam via Alt OS FAQWiki
#make return values not stupid
#do conflict resolution when someone submits an edit of a page that's been changed while he was editing it


#TODO: (less important)
#return to previous page after logging in
#add real WikiLinks
#make all links relative (should double-check on this)
#ability to make and save a draft of a post for later editing and completion
#add ability to view all questions from the main page in one big page
#think about how to find out and minimize the number of db queries
#add timezone-awareness to timestamps
#view diffs between versions
#let people download a password-scrubbed database
#add a page that shows all stored pages
#make the site look good
#add a swear filter
#add ability to ban members
#make it easy to tell what's recently been added to a page (ie what's the most recent change)

sub loginToForums($); #$commonArgs
sub logoutFromForums($); #$commonArgs
sub getCanonicalUsername($); #$userName
sub sendPM($$$$); #$commonArgs, $toUsernames, $msgTitle, $msgBody
sub makeMemberConfString($$); #$commonArgs, $memberName
sub confirmConfirmationString($$); #$commonArgs, $confirmationString
sub randomString($); #$length
sub isMember($$); #$commonArgs, $username
sub addSearchPageToDB($$); #$commonArgs, $pageContents
sub getMemberForumID($$); #$commonArgs, $username
sub pageExists($$);  #$commonArgs, $version
sub bbWikiConvert($$); #$commonArgs, $wikiCode
sub isSHULang($); #$langName
sub getHTMLHeader($); #$commonArgs
sub getViewPage($$$$); #$commonArgs, $version, $historyCount, $text
sub addNewVersion($$$$$); #commonArgs, authorComment, comment, text, title
sub getPreviewPage($$$$$); #commonArgs, authorComment, comment, text, pageFullName
sub getEditPage($$); #$commonArgs, $version
sub getNewPagePage($$); #$commonArgs, $name
sub getRawPostBody($$); #$commonArgs, $version
sub getErrorPage($$); #$commonArgs, $errorName
sub getLoginPage($); #$commonArgs
sub getRegisterPage($); #$commonArgs
sub getSignupPage($$); #$commonArgs, $confirmationString
sub getSearchPage($); #$commonArgs
sub getSearchResultsPage($$$$$); #$commonArgs, $searchFor, $searchType, $findVersion
sub getMaxVersion($); #$commonArgs
sub getLoginHash($); #$commonArgs
sub getHeaderLinks($$); #commonArgs,$fakeLogout
sub getRecentChanges($$); #commonArgs, $howMany

#login to the OC Forums.  This is mostly for sending PMs.
sub loginToForums($)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $dbh = $commonArgs->{'dbh'};
    my $ua = $commonArgs->{'ua'};
    my $fromUsername = "Alt OS FAQWiki";
    my $dbRes = $dbh->selectrow_arrayref("SELECT password_hash FROM members WHERE username='$fromUsername'");
    my $fromPasswordHash = $dbRes->[0];

    #login
    my $loginUrl = "http://www.ocforums.com/login.php";
    my $loginResult = $ua->post( $loginUrl,
                      [ "vb_login_username"    => "$fromUsername",
                        "vb_login_password"    => "", #um, what's the md5 field for?
                        "submit"               => "Log in",
                        "cookieuser"           => 1,
                        "s"                    => "",
                        "do"                   => "login",
                        "forceredirect"        => 1,
                        "vb_login_md5password" => "$fromPasswordHash"
                      ]);

    #check that login was successful
    if ($loginResult->is_error()) {
        return DVAL "error logging Alt OS FAQWiki into forums:".$loginResult->status_line;
    } elsif ($loginResult->content =~ /Thank you for logging in, Alt OS FAQWiki./) {
        return DVAL "success";
    } else {
        return DVAL "failed";
    }
}

#just for politeness
sub logoutFromForums ($)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $ua = $commonArgs->{'ua'};
    #logout (don't care about error checking here)
    $ua->get("http://www.ocforums.com/login.php",
             ["do" => "logout",
              "u"  => 0] );
    return DVOID;
}


sub getCanonicalUsername($)
{
    DFEATURE my $f_;
    #vb3 automatically html-escpaes names and doesn't care about case
    return CGI::escapeHTML(lc(shift));
}

sub sendPM($$$$)
{
    DFEATURE my $f_;
    #special-purpose user for sending PMs
    my $fromUsername = "Alt OS FAQWiki";
    my $commonArgs = shift;
    my $ua = $commonArgs->{'ua'};
    my $toUsernames = shift;
    my $msgTitle = shift;
    my $msgBody = shift;

    my $loginResult = loginToForums($commonArgs);
    return DVAL $loginResult unless $loginResult eq "success";
  
    #send PM
    my $pmUrl = "http://www.ocforums.com/private.php";
    my $pmResult = $ua->post($pmUrl,
                   [ "recipients"       => $toUsernames,
                     "title"            => $msgTitle,
                     "message"          => $msgBody,
                     "iconid"           => 10, #smiley face
                     "s"                => "", #who knows?
                     "do"               => "insertpm",
                     "pmid"             => "", #um, shouldn't vbulletin be using this?
                     "forward"          => "", #or this?
                     "sbutton"          => "Submit Message",
                     "savecopy"         => 1,
                     "receipt"          => 0,
                     "signature"        => 0,
                     "parseurl"         => 0,
                   ]);
    logoutFromForums($commonArgs);

    #check that PM was sent successfully
    if ($pmResult->is_error()) {
        return DVAL "error sending pm:",$pmResult->status_line;
    } elsif ($pmResult =~ /Sorry! The administrator has specified/) {
        return DVAL "error: retry in 30 second";
    } else {
        return DVAL "success";
    }

}

sub makeMemberConfString($$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $dbh = $commonArgs->{'dbh'};
    my $memberName = shift;
    my $canonicalUsername = getCanonicalUsername($memberName);
    $canonicalUsername =~ s/'/''/g;
    #check if the guy's in the db
    my $dbRes = $dbh->selectall_arrayref("select * from members where canonical_username = '$canonicalUsername'");
    #if not, return error
    if (scalar(@$dbRes) != 0) {
        return DVAL "error: already a member";
    } elsif (isMember($commonArgs,$memberName) eq "success") {
        #make a conf string
        my $confirmationString = randomString(16);
        #add his username, conf string and a NULL password to the db
        $dbh->do("update members set confstring = '$confirmationString' where canonical_username = '$canonicalUsername'");
        #return the conf string
        return DVAL $confirmationString;
    } else {
        return DVAL "error: couldn't confirm member";
    }
}

sub confirmConfirmationString($$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $dbh = $commonArgs->{'dbh'};
    my $username = $commonArgs->{'loggedInUser'};
    my $confirmationString = shift;
    $confirmationString =~ s/'/''/g;
    my $dbRes = $dbh->selectrow_arrayref("select username from members where confirmation_string='$confirmationString'");
    #hopefully, each random string only maps to one member
    if (! defined $dbRes || scalar(@$dbRes) == 0) {
        return DVAL "error: invalid confirmation string";
    } elsif ($confirmationString eq "confirmed") {
        return DVAL "error: not gonna happen"; 
    } 
    $username = $dbRes->[0];
    return DVAL "success:$username";
}

sub randomString($)
{
    DFEATURE my $f_;
    #return a random 32-char string
    my @alphabet = (qw(q w e r t y u i o p a s d f g h j k l z x c v b n m 
                       Q W E R T Y U I O P A S D F G H J K L Z X C V B N M
                       1 2 3 4 5 6 7 8 9 0));

    my $len = shift;
    my $confString = "";

    while($len--) {
        $confString .= @alphabet[int rand(@alphabet)];
    }

    return DVAL $confString;
}

sub isMember($$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $dbh = $commonArgs->{'dbh'};
    my $ua = $commonArgs->{'ua'};
    my $usernameInQuestion = shift;
    my $canonicalUsername = getCanonicalUsername($usernameInQuestion);
    $canonicalUsername =~ s/'/''/g;
    $usernameInQuestion =~ s/'/''/g;
    return DVAL "failed" unless defined $usernameInQuestion;

    #check the db first
    #XXX: sql injection anyone?
    my $dbRes = $dbh->selectrow_arrayref("SELECT * FROM members WHERE canonical_username='$canonicalUsername';");
    if (defined $dbRes->[0]) {
        return DVAL "success";
    }

    my $searchURL = "http://www.ocforums.com/search.php";
    
    my $searchResult = $ua->post($searchURL,
             ["s"             => "", #for "sexy", perhaps?
              "do"            => "process",
              "query"         => "",
              "titleonly"     => 0,
              "searchuser"    => $usernameInQuestion, #need to use unescaped name
              "starteronly"   => 0,
              "exactname"     => 1,
              "replyless"     => 0,
              "replylimit"    => 0,
              "searchdate"    => 0,
              "beforeafter"   => "after",
              "sortby"        => "lastpost",
              "order"         => "descending",
              "showposts"     => 0,
              "forumchoice[]" => 0,
              "childforums"   => 1,
              "dosearch"      => "Search Now", #as opposed to next week?
              "saveprefs"     => 1,
             ]);

    if ($searchResult->is_error) {
        return DVAL "error finding member: ".$searchResult->status_line;
    #if the resulting page does not have "<blockquote><p>Invalid User specified.", he's a real user
    } elsif ($searchResult->content !~ "<blockquote><p>Invalid User specified.") {
        #make him show up if we need to check on him again
        $dbh->do("INSERT INTO members VALUES('$usernameInQuestion','$canonicalUsername','','','')");
        return DVAL "success";
    } else {
        return DVAL "failed";
    }
}

sub addSearchPageToDB($$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $searchContent = shift;
    my $dbh = $commonArgs->{'dbh'};
    my $dbRes;
    my $username;
    my $canonicalUsername;
    my $user_id;
    for (split /\n/,$searchContent) {
        if (/\s*<a href="member\.php\?u=(\d*)">.*?<\/a>/) {
            chomp();
            $user_id = $1;
            s/\s*<a href="member\.php\?u=\d*">//;
            s/<\/a>//;
            s/<[^>]*>//g; #knock out any extra html
            s/'/''/g; #escape quotes for the db
            s/\r//g; #stupid dos newlines
            chomp();
            $username = $_;
            #forums already return html-escaped names
            $canonicalUsername = lc($username);
            $canonicalUsername =~ s/'/''/g;
            $username =~ s/'/''/g;
            
            #check if he's in the db
            #print "adding forum id for $username\n";
            $dbRes = $dbh->selectrow_arrayref("SELECT * FROM members WHERE canonical_username='$canonicalUsername'");

            #if not, add him
            if (defined @$dbRes && scalar(@$dbRes) > 0) {
                #print "added $username\'s id to db, id = $user_id\n";
                $dbh->do(qq(UPDATE members SET forum_id=$user_id WHERE canonical_username='$canonicalUsername'));
            } else {
                #print "INSERTED $username\'s id to db, id = $user_id\n";
                $dbh->do(qq(INSERT INTO members VALUES('$username','$canonicalUsername','','',$user_id)));
            }
        }
    }
    return DVOID;
}


sub getMemberForumID($$)
{
    DFEATURE my $f_;
    #there are 4 cases: in db w/ id, in db w/o id, not in db, not a member
    my $commonArgs = shift;
    my $dbh = $commonArgs->{'dbh'};
    my $ua = $commonArgs->{'ua'};
    my $username = shift;

    return 0 if $username eq "";
    my $canonicalUsername = getCanonicalUsername($username);
    $canonicalUsername =~ s/'/''/g;
    #print "getting forum id for $username...\n";
    #check if the username is in the db
    my $dbRes = $dbh->selectrow_arrayref("SELECT username, forum_id FROM members WHERE canonical_username='$canonicalUsername'");
    #check if he's in the db w/id
    if (defined $dbRes->[0] && $dbRes->[1] ne "") {
        return DVAL $dbRes->[1];
    #if he's not already in the db...
    } else { 
        #check if he's not a member
        return DVAL "error: not a member" unless isMember($commonArgs,$username) eq "success";

        #at this point, he's a member in the db w/o an id or he's not in the db

        #login
        my $loginResult = loginToForums($commonArgs);
        return DVAL "error logging in" unless $loginResult eq "success";
        #use the member search to search for the username
        my $searchURL = "http://www.ocforums.com/memberlist.php";
        my $searchOptions = {
                  "do"            => "getall",
                  "page"          => 1,
                  "ltr"           => "",
                  "pp"            => 100,
                  "order"         => "desc",
                  "postslower"    => 0,
                  "postsupper"    => 0,
                  "sort"          => "lastvisit",
                  "ausername"     => "$username",
                  "homepage"      => "",
                  "icq"           => "",
                  "aim"           => "",
                  "yahoo"         => "",
                  "msn"           => "",
                  "joindateafter" => "",
                  "joindatebefore"=> "",
                  "lastpostafter" => "",
                  "lastpostbefore"=> "",
        };

        my $searchResult = $ua->post($searchURL,$searchOptions);
        return DVAL "error: ".$searchResult->status_line if $searchResult->is_error;

        #add everyone on this search page to the db
        addSearchPageToDB($commonArgs,$searchResult->content);

        my $remainingPages = $searchResult->content;
        my $currentPage = 1;
        if ($remainingPages =~ /.*>Page 1 of (\d+)<.*/) {
            $remainingPages = $1;
        } else {
            $remainingPages = 0;
        }
        #print "there will be $remainingPages more pages\n";
        
        $dbRes = $dbh->selectrow_arrayref("SELECT forum_id from members where canonical_username='$canonicalUsername'");
        #while there are more results and the guy isn't in the db...
        while ($remainingPages > $currentPage && $dbRes->[0] eq "") {
            #get the next page
            #print "searching page $currentPage\n";
            $currentPage++;
            $searchOptions->{'page'} = $currentPage;
            $searchResult = $ua->post($searchURL,$searchOptions);
            #add to db
            addSearchPageToDB($commonArgs,$searchResult->content);
            $dbRes = $dbh->selectrow_arrayref("SELECT forum_id from members where canonical_username='$canonicalUsername'");
        }
        #if we have the id
        if (scalar(@$dbRes)) {
            return DVAL $dbRes->[0];
        }
        return DVAL "error: not in db";
    }
}

sub pageExists($$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $dbh = $commonArgs->{'dbh'};
    my $pageName = $commonArgs->{'pageName'};
    $pageName =~ s/'/''/g;
    my $version = shift;

    my $dbRes = $dbh->selectrow_arrayref("SELECT name FROM posts WHERE name='$pageName' AND version=$version");
    if (defined @$dbRes) {
        return DVAL "true";
    }
    return DVAL "false";
}

sub bbWikiConvert($$)
{
    DFEATURE my $f_;
    my $highlighter = Syntax::Highlight::Universal->new;
    #this is a huge speedup and will be fine, since the code won't change often
    $highlighter->setCacheDir('/var/shu_cache');
    my $commonArgs = shift;
    my $wikiCode = shift;
    my $wikiRoot = "ocwiki.pl";
    my $randString = randomString(64); #placeholder for text substitution
    my $randStringNoEsc = randomString(64); #placeholder for code tags
    my @noEscList;
    my $noEscCounter = 0;
    for ($wikiCode) {

        #do this first so other stuff can ignore <pre> tags
        #code, php, html 
        while ($wikiCode =~ m#\[code([^\]])*\].*?\[\/code\]#s) {
            #if there's no language specified...
            if (! defined $1) {
                #do whatever vb does for [code] tags
                $wikiCode =~ s#\[code\](.*?)\[/code\]\n?#NOESC${noEscCounter}:${randStringNoEsc}NOESC#s;
                my $escapedCode = $1;
                #escape spaces too
                $escapedCode =~ s/ /&#32;/g;
                push @noEscList, $escapedCode;
                $noEscCounter++;
                #$wikiCode =~ s#$randString#$escapedCode#;
            } else {
                #if there's a language, use Syntax::Highlight::Universal
                $wikiCode =~ m#\[code=([a-zA-Z0-9.-]*?)\]#;
                if (isSHULang($1)) {
                    $wikiCode =~ s#\[code=([a-zA-Z0-9.-]*?)\](.*?)\[/code\]\n?#NOESC${noEscCounter}:${randStringNoEsc}NOESC#s;
                    #XXX: validate language
                    my $highlightCode = $highlighter->highlight($1,$2);
                    push @noEscList, $highlightCode;
                    $noEscCounter++;
                    #$wikiCode =~ s#$randString#$highlightCode#;
                } else {
                    #since we need opening and closing tags, invalidating the
                    #opening tags means the closing ones will be ignored
                    $wikiCode =~ s/\[code=([a-zA-Z0-9.-]*?)\]/\[BROKEN_CODE=$1\]/;
                }
            }
        }
        
        $wikiCode =~ s/BROKEN_CODE/code/g;

        
        $wikiCode = CGI::escapeHTML($wikiCode);

        #custom stuff:
       
        #topic
        $wikiCode =~ s#\[topic\](.*?)\[/topic\]#\[b\]\[u\]\[size=+1]$1\[/size\]\[/u\]\[/b\]#sg;

        #subtopic
        $wikiCode =~ s#\[subtopic\](.*?)\[/subtopic\]#\[b\]\[u\]$1\[/u\]\[/b\]#sg;
        $wikiCode =~ s#\[subtopic=([^]]*)\]#\[b\]\[u\]$1\[/u\]\[/b\]#sg;

        #cmd
        $wikiCode =~ s#\[cmd\](.*?)\[/cmd\]#<b>$1</b>#g;

        #info (better name?)
        $wikiCode =~ s#\[info\](.*?)\[/info\]#<i>$1</i>#g;

        #name (for forum members)
        #XXX: this can take a long time when forum ids aren't in the db
        while ($wikiCode =~ /\[name=[^]]*\]/) {
            $wikiCode =~ s#\[name=([^]]*)\]#$randString#s;
             my $nameTag = "<a href=\"http://www.ocforums.com/member.php?u=".getMemberForumID($commonArgs,$1)."\">$1</a>";
             s/$randString/$nameTag/;
         }

        #hr
        $wikiCode =~ s#\[hr\]\n?#<hr>NEWLINE#g;

        #p
        $wikiCode =~ s#\n\n#<p>NEWLINENEWLINE#g;

        #title
        $wikiCode =~ s#\[title=([^]]*)]#\[b\]\[u\]\[size=+1\]\[color=blue\]$1\[/color\]\[/size\]\[/u\]\[/b\]#g;
        
        #wiki links
        $wikiCode =~ s#\[wiki=([a-zA-Z0-9_+-]*)\](.*?)\[/wiki\]#\<a href="$wikiRoot?do=view&page=$1">$2\</a>#g;
        while ($wikiCode =~ /\[wiki=[a-zA-Z0-9_+-]*\]/) {
            s/\[wiki=([a-zA-Z0-9_+-]*)\]/$randString/;
            my $wikiName = $1;
            my $dbh = $commonArgs->{'dbh'};
            my $dbRes = $dbh->selectrow_arrayref("SELECT full_name FROM posts WHERE name='$wikiName' AND version=(SELECT MAX(version) FROM posts WHERE name='$wikiName')");
            my $wikiTitle = "unknown wiki page";
            if (ref $dbRes && scalar(@$dbRes)) {
                $wikiTitle = $dbRes->[0];
            }
            s/$randString/<a href="$wikiRoot?do=view&page=$wikiName">$wikiTitle<\/a>/;
        }


        #br
        $wikiCode =~ s#\[br\]#<br>#g;
        $wikiCode =~ s#\n#<br>NEWLINE#g;


        #standard bbcode stuff:
        
        #[b] => bold
        $wikiCode =~ s#\[b\](.*?)\[/b\]#<b>$1</b>#sg;
        #[i] => italic
        $wikiCode =~ s#\[i\](.*?)\[/i\]#<i>$1</i>#sg;
        #[u] => underline
        $wikiCode =~ s#\[u\](.*?)\[/u\]#<u>$1</u>#sg;

        #color
        $wikiCode =~ s#\[color=(\#?[a-zA-Z0-9]*)\](.*?)\[/color\]#<font color="$1">$2</font>#sg;
        
        #size
        $wikiCode =~ s#\[size=([+-]?[0-9]+)\](.*?)\[/size\]#<font size="$1">$2</font>#sg;
        #font
        $wikiCode =~ s#\[font=([a-zA-Z]*)\](.*?)\[/font\]#<font face="$1">$2</font>#sg;
        
        #alignment
        $wikiCode =~ s#\[left\](.*?)\[/left\](?:<br>)?#<div align="left">$1</div>#sg;
        $wikiCode =~ s#\[center\](.*?)\[/center\](?:<br>)?#<div align="center">$1</div>#sg;
        $wikiCode =~ s#\[right\](.*?)\[/right\](?:<br>)?#<div align="right">$1</div>#sg;

        #blockquote
        $wikiCode =~ s#\[indent\](.*?)\[/indent\](?:<br>)?#<blockquote><div>$1</div></blockquote>#sg;

        #email
        $wikiCode =~ s#\[email\](.+\@\w+\..+)\[/email]#<a href="mailto:$1">$1</a>#sg;
        $wikiCode =~ s#\[email=(.+\@\w+\..+)\](.*?)\[/email]#<a href="mailto:$1">$2</a>#sg;

        #urls
        #XXX: add extra stuff in case you end up with [url=google.com]
        $wikiCode =~ s#\[url\](.*?)\[/url\]#<a href="$1">$1</a>#sg;
        $wikiCode =~ s#\[url=((?:http|ftp|https)[^]]*)\](.*?)\[/url\]#<a href="$1">$2</a>#sg;
        $wikiCode =~ s#\[url=(\w+\.\w+)\]([^\]]*)\[/url\]#<a href="http://$1">$2</a>#sg;
        
        #list
        while ($wikiCode =~ /\[list\].*?\[\/list\]/s) {
            $wikiCode =~ s#\[list\](?:<br>NEWLINE)*(.*?)\[/list\](?:<br>NEWLINE)*#$randString#sm;
            my $listStuff = $1;
            $listStuff =~ s#\[\*\](.*?)<br>#<li>$1</li>#sg;
            $wikiCode =~ s#$randString#<ul>$listStuff</ul>#;
        }

        #images
        $wikiCode =~ s#\[img\](.*://.+\..+.*)\[/img\]#<img src="$1" alt="" border="0" />#sg;
        
        #strikethrough
        $wikiCode =~ s#\[s\](.*?)\[/s\]#<s>$1</s>#sg;
        #subscript
        $wikiCode =~ s#\[sub\](.*?)\[/sub\]#<sub>$1</sub>#sg;
        #superscript
        $wikiCode =~ s#\[sup\](.*?)\[/sup\]#<sup>$1</sup>#sg;

        #now that all the html processing is done, insert back the code stuff
        $noEscCounter = 0;
        for my $noEscCode (@noEscList) {
            s/NOESC${noEscCounter}:${randStringNoEsc}NOESC/<pre>$noEscCode<\/pre>/;
            $noEscCounter++;
        }
        $wikiCode =~ s/NEWLINE/\n/g;
    }
    return DVAL $wikiCode;    
}

#simple helper for Syntax::Highlighter::Universal, since it freaks 
#out if you give it an unsupported language
sub isSHULang($)
{
    DFEATURE my $f_;
    my $langName = lc shift;
    my @langs = (qw(abap4 actionscript ada adsp apache asm asm80 asp.js asp.ps asp.vb AutoIt awk Baan Batch c c1c cache clarion Clipper cobol coldfusion config cpp csharp css dcl default delphiform diff dssp dtd eiffel filesbbs forth fortran foxpro hrc hrd html html-css icon idl iss isScripts java javacc javaProperties jScript jsnet jsp lex lisp makefile matlab messages modula2 mysql nsi ocaml paradox pascal perl php php-body picasm python rarscript regedit relaxng resources rexx ruby shell sicstusProlog sml sql sqlj svg svg-css tcltk TeX text turboProlog vbasic vbnet vbScript verilog vhdl vrml xhtml-frameset xhtml-strict xhtml-trans xlink xml xmlschema xslt yacc z80));
    for my $lang (@langs) {
        return DVAL 1 if $lang eq $langName;
    }
    return DVOID;
}

sub getHTMLHeader($)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $headers;

    #Apache freaks out if I wait too long between sending
    #Set-Cookie and Content-Type so I have to be sure to send everything
    #in one swell foop.
    $headers = $commonArgs->{'cookies'} if defined $commonArgs->{'cookies'};
    $headers .= "Content-Type: text/html; charset=ISO-8859-1\n\n";
    return DVAL $headers;
}

sub getHeaderLinks($$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $fakeLogout = shift;
    my $loggedInUser = $commonArgs->{'loggedInUser'};
    my $links =  qq(<div id="wiki">);
    if ($loggedInUser ne "" && $fakeLogout == 0) {
        $links .= qq(Hello, $loggedInUser! (<a href="ocwiki.pl?do=logout">logout</a>)<br>);
    } else {
        $links .= qq(<a href="ocwiki.pl?do=login">Login</a> | 
<a href="ocwiki.pl?do=register">Register</a> | ); 
    }
    $links .= qq(<a href="ocwiki.pl">MainPage</a> | 
<a href="ocwiki.pl?do=search">Search</a> | 
<a href="ocwiki.pl?do=changes&count=5d">RecentChanges</a> |
<a href="ocwiki.pl?do=view&page=WikiCode">WikiCode</a> |
<a href="ocwiki.pl?do=view&page=WikiGuidelines">WikiGuidelines</a> |
<a href="ocwiki.pl?do=view&page=WikiRequests">WikiRequests</a> |
<a href="ocwiki.pl?do=newpage">NewPage</a> 
<hr>);
    return DVAL $links;
}

sub getRecentChanges($$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $changes = shift;
    my $dbh = $commonArgs->{'dbh'};
    $changes = -1 if $changes eq 'all';
    $changes =~ s/d//;

    my $changesPage = getHTMLHeader($commonArgs).
    qq(<title>RecentChanges</title>
<link rel="stylesheet" href="/hs.css" type="text/css">
<body><META NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW">
</head>
<body text=black bgcolor=white><h1><img src="/img/crackspider.jpg">
<a href="ocwiki.pl?do=changes">RecentChanges</a></h1>).
    getHeaderLinks($commonArgs,0);

    #XXX: this could probably be much more efficient
    my $dbRes = $dbh->selectall_arrayref("SELECT name,full_name,version,date,author,author_comment FROM posts ORDER BY date DESC");

    my $day = $dbRes->[0][3];
    $day =~ s#(\d\d)(\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)#$3/$4/$2#;
    $changesPage .= qq(<font color="green"><b>$day</b></font><br>\n);
    for my $info (@$dbRes) {
        my $name = $info->[0];
        my $fullName = $info->[1];
        my $version = $info->[2];
        my $date = "hot";
        $date = $info->[3];        
        my $currDay = $date;
        $currDay =~ s#(\d\d)(\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)#$3/$4/$2#;
        if ($day ne $currDay) {
            $changes-- if $changes =~ /^-?\d+$/;
            last if $changes == 0;
            $changesPage .= qq(<hr><font color="green"><b>$currDay</b></font><br>\n);
            $day = $currDay;
        }
        my $author = $info->[4];
        my $authorComment = $info->[5];
        my $authorID = getMemberForumID($commonArgs,$author);
        $authorComment =~ s#/me#<a href="http://www.ocforums.com/member.php?u=$authorID">$author</a>#g;
        $date =~ s#(\d\d)(\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)#$5:$6#;
        $changesPage .= qq($date: <a href="ocwiki.pl?do=view&page=$name&version=$version">v$version</a> of <a href="ocwiki.pl?do=view&page=$name">\"$name\"</a> - <i>$authorComment</i><br>);
    }

    $changesPage .= qq(<hr></body></html>);
    return DVAL $changesPage;

}

sub getViewPage($$$$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $version = shift;
    my $versionLimit = shift;
    my $htmlText = shift;
    my $pageName = $commonArgs->{'pageName'};
    my $dbh = $commonArgs->{'dbh'};
    my $loggedInUser = $commonArgs->{'loggedInUser'};
    $pageName =~ s/'/''/g;

    my $dbRes = $dbh->selectrow_arrayref("SELECT full_name FROM posts WHERE name='$pageName' AND version=$version;");
    my $pageFullName = $dbRes->[0];
    my $viewPage = getHTMLHeader($commonArgs);
    $viewPage .= qq(<head>
    <title>$pageFullName</title>
    <link rel="stylesheet" href="/hs.css" type="text/css">
    </head>
    <body text=black bgcolor=white><h1><img src="/img/crackspider.jpg">
    <a href="ocwiki.pl?do=view&page=$pageName">$pageFullName</a></h1>);
    $viewPage .= getHeaderLinks($commonArgs,0);
    $viewPage .= $htmlText;
    #XXX end of header,beginning of footer
    
    #get version history
    $viewPage .= qq(</div><hr>
    <a href="ocwiki.pl?do=edit&page=$pageName&version=$version">Edit</a> this page.<br>);
    $dbRes = $dbh->selectrow_arrayref("SELECT MAX(version) FROM posts WHERE name='$pageName'");
    my $maxVersion = $dbRes->[0];
    $versionLimit = $versionLimit eq 'all' ? $maxVersion : $versionLimit;
    $versionLimit = $versionLimit > $maxVersion ? $maxVersion : $versionLimit;

    #add version history info...
    $viewPage .= qq(last $versionLimit changes (<a href="ocwiki.pl?do=view&page=$pageName&edits=all">view all</a>):<br>);
    my ($author, $authorComment, $authorID, $date);
    for my $currVersion (reverse 1 .. $maxVersion) {
        $dbRes = $dbh->selectrow_arrayref("SELECT author, author_comment, date FROM posts WHERE name='$pageName' AND version=$currVersion;");
        $author        = $dbRes->[0];
        $authorComment = $dbRes->[1];
        $date          = $dbRes->[2];
        $date =~ s#(\d\d)(\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)#$3/$4/$2 $5:$6#;
        $authorID = getMemberForumID($commonArgs,$author);
        $authorComment =~ s#/me#<a href="http://www.ocforums.com/member.php?u=$authorID">$author</a>#g;

        #color the history for the current version
        if ($version == $currVersion) {
            $viewPage .= qq(<a href="http://mksig.org/wiki/ocwiki.pl?do=view&page=$pageName&version=$currVersion">v$currVersion</a> <font color="green">($date)</font>: $authorComment<br>);
        } elsif ($versionLimit > 0) {
        #} else {
            $viewPage .= qq(<a href="http://mksig.org/wiki/ocwiki.pl?do=view&page=$pageName&version=$currVersion">v$currVersion</a> ($date): $authorComment<br>);
        }
        $versionLimit--;
    }
    
    $viewPage .= qq(</body></html>);
    return DVAL $viewPage;
}

sub addNewVersion($$$$$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $dbh = $commonArgs->{'dbh'};
    my $loggedInUser = $commonArgs->{'loggedInUser'};
    my $pageName = $commonArgs->{'pageName'};
    my $authorComment = shift;
    my $comment = shift;
    my $text = shift;
    my $title = shift;

    $pageName =~ s/'/''/g;
    $loggedInUser =~ s/'/''/g;
    $authorComment =~ s/'/''/g;
    $comment =~ s/'/''/g;
    $text =~ s/'/''/g;
    $title =~ s/'/''/g;

    my $dbRes = $dbh->selectrow_arrayref("SELECT max(version) FROM posts WHERE name='$pageName'");
    my $version = $dbRes->[0];
    $version++;
    my $date = Class::Date::date(time);
    
    #XXX: do auth stuff...

    #name, full_name, version, text, author, comment, author_comment, date
    $dbh->do("INSERT INTO posts VALUES('$pageName','$title',$version,'$text','$loggedInUser','$comment','$authorComment','$date')");
    #print "did \"INSERT INTO posts VALUES('$pageName','$title',$version,'$text','$loggedInUser','$comment','$authorComment','$date')\"";
    #print "db says: ".$dbh->errstr."\n";
    return DVOID;
}
    
sub getPreviewPage($$$$$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $dbh = $commonArgs->{'dbh'};
    my $loggedInUser = $commonArgs->{'loggedInUser'};
    my $pageName = $commonArgs->{'pageName'};
    my $authorComment = shift;
    my $comment = shift;
    my $text = shift;
    my $pageFullName = shift;

    $pageName =~ s/'/''/g;
    $loggedInUser =~ s/'/''/g;
    $authorComment =~ s/'/''/g;
    $comment =~ s/'/''/g;

    my $junk = int rand 10000; #hack so form contents aren't cached
    my $previewPage;
    
    #print preview page header
    $previewPage .= getHTMLHeader($commonArgs).
    qq(<title>Previewing $pageName</title>
<link rel="stylesheet" href="/hs.css" type="text/css">
<body><form method="POST" action="/wiki/ocwiki.pl">
<META NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW">
</head>
<body text=black bgcolor=white><h1><img src="/img/crackspider.jpg">
Previewing "$pageFullName"</h1>).
    getHeaderLinks($commonArgs,0);

    #print formatted text
    $previewPage .= bbWikiConvert($commonArgs,$text);

    #print all the stuff on the edit page
    $previewPage .= qq(<hr>);
    $previewPage .= getEditForm($commonArgs,{
            pageFullName=>$pageFullName,                            
            text=>$text,
            authorComment=>$authorComment
        });
    return DVAL $previewPage;
}

sub getEditPage($$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $version = shift;
    my $dbh = $commonArgs->{'dbh'};
    my $pageName = $commonArgs->{'pageName'};
    $pageName =~ s/'/''/g;

    my $dbRes = $dbh->selectrow_arrayref(qq(SELECT full_name,text FROM posts WHERE name='$pageName' AND version=$version));
    my $pageFullName = $dbRes->[0];
    my $text = $dbRes->[1];
    my $junk = int rand 10000; #hack so form contents aren't cached
    my $editPage = getHTMLHeader($commonArgs);
    $editPage .= qq(<title>Editing $pageName</title>
    <link rel="stylesheet" href="/hs.css" type="text/css">
    <body>
    <META NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW">
    </head>
    <body text=black bgcolor=white><h1><img src="/img/crackspider.jpg">
    Editing "$pageFullName"</h1>);
    $editPage .= getHeaderLinks($commonArgs,0);
    $editPage .= getEditForm($commonArgs,{
            pageFullName => $pageFullName,
            text => $text,
        });
    return DVAL $editPage;
}

sub getEditForm($$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $pageName = $commonArgs->{'pageName'};
    my $loggedInUser = $commonArgs->{'loggedInUser'};
    my $editArgs = shift;
    my $forumUserID = getMemberForumID($commonArgs,$loggedInUser);

    my $junk = rand; #keep firefox from caching form contents
    my $editForm = qq(<form method="post" action="/wiki/ocwiki.pl">
If you don't know how to format text, see <a href="/wiki/ocwiki.pl?do=view&page=WikiCode">WikiCode</a> for code examples.<p>);
    
    if (defined $editArgs->{'useWikiName'} ) {
        $editForm .= qq(WikiName:\n<input type="text" size=25 name="page" value="$pageName"><br>);
    }
    $editForm .= qq(Title:
    <input type="text" size=75 name="full_name" );

    if (defined $editArgs->{'pageFullName'} ) {
        my $pageFullName = $editArgs->{'pageFullName'};
        $editForm .= qq(value="$pageFullName");
    } 
    $editForm .= qq(>
    <textarea name="text_$junk" style="width:100%" rows=25 cols=80 wrap=virtual>);
    
    if (defined $editArgs->{'text'}) {
        $editForm .= $editArgs->{'text'};
    }
    $editForm .= qq(</textarea><br>
    <p>);
    if ($loggedInUser eq "") {
        $editForm .= qq(<font color="red">You must log in before editing this page.</font>);
    } else {
        $editForm .= qq(Author's comment: 
        <input type="text" name="author_comment" size=60 value=");

        if (defined $editArgs->{'authorComment'}) {
            $editForm .= $editArgs->{'authorComment'};
        } else {
            $editForm .= qq(/me made some unspecified changes.);
        }
        $editForm .= qq("><br>
        Example: "/me made some unspecified changes." will turn into "<a href=\"http://www.ocforums.com/member.php?u=$forumUserID">$loggedInUser</a> made some unspecified changes."<br>
        <input type="hidden" name="do" value="submit">
        <input type="submit" name="post" value=" Post it! ">
        <input type="submit" name="preview" value=" Preview ">
        <input type="hidden" name="page" value="$pageName"></form>);
    }
    return DVAL $editForm;
}

#XXX: You were in the process of changing all edit forms to use the above method.
#You may have to make some more stuff generic, but this will result in much nicer code
#and far less duplication.

sub getNewPagePage($$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $pageName = shift;
    my $junk = int rand 10000; #hack so form contents aren't cached
    my $newPagePage = getHTMLHeader($commonArgs);
    $newPagePage .= qq(<title>Making new page: $pageName</title>
<link rel="stylesheet" href="/hs.css" type="text/css">
<body>
<META NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW">
</head>
<body text=black bgcolor=white><h1><img src="/img/crackspider.jpg">
Making new page: $pageName</h1>);
    $newPagePage .= getHeaderLinks($commonArgs,0);
    $newPagePage .= getEditForm($commonArgs,{useWikiName=>'true'});
    return DVAL $newPagePage;
}

sub getRawPostBody($$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $dbh = $commonArgs->{'dbh'};
    my $pageName = $commonArgs->{'pageName'};
    my $version = shift;
    $pageName =~ s/'/''/g;
    $version =~ s/'/''/g;

    my $dbResult = $dbh->selectrow_arrayref(qq(SELECT text FROM posts WHERE name='$pageName' and version=$version;));
    return DVAL $dbResult->[0];
}

sub getErrorPage($$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $errorName = shift;

    return DVAL getHTMLHeader($commonArgs).
    qq(<head>
    <title>Error</title>
    <link rel="stylesheet" href="/hs.css" type="text/css">
    </head>
    <body text=black bgcolor=white><h1><img src="/img/crackspider.jpg">
    Error</h1>).
    getHeaderLinks($commonArgs,0).
    qq($errorName<hr></html>);
    
}

sub getSuccessPage($$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $msgName = shift;

    return DVAL getHTMLHeader($commonArgs).
    qq(<head>
    <title>Success!</title>
    <link rel="stylesheet" href="/hs.css" type="text/css">
    </head>
    <body text=black bgcolor=white><h1><img src="/img/crackspider.jpg">
    It worked!</h1>).
    getHeaderLinks($commonArgs,0).
    qq($msgName<hr></html>);
    
}

sub getLoginPage($)
{
    my $commonArgs = shift;
    my $loginPage = getHTMLHeader($commonArgs);
    $loginPage .= qq(<html>
    <head>
    <title>WikiLogin</title>
    <link rel="stylesheet" href="/hs.css" type="text/css">
    </head>
    <body text=black bgcolor=white><h1><img src="/img/crackspider.jpg">
    WikiLogin</h1>);
    $loginPage .= getHeaderLinks($commonArgs,1);
    $loginPage .= qq(<script type="text/javascript" src="/js/md5.js"></script><form action="ocwiki.pl" method="post" onsubmit="md5hash(password,passwordHash)">
    <code>username:</code> <input type="text" name="username" size="10" /><br> 
    <code>password:</code> <input type="password" name="password" size="10" /><br> 
    <input name="login" type="submit" value="Log in" />
    <input type="hidden" name="do" value="login" />
    <input type="hidden" name="passwordHash" />
    </form></html>);
    return DVAL $loginPage;
}

sub getRegisterPage($)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $randomStr = randomString(16);
    my $dbh = $commonArgs->{'dbh'};
    #this is to make PM-spamming impossible without the web interface
    $dbh->do("INSERT INTO rstr VALUES('$randomStr')");
    my $registerPage = getHTMLHeader($commonArgs);
    $registerPage .= qq(<html>
    <head>
    <title>WikiRegister</title>
    <link rel="stylesheet" href="/hs.css" type="text/css">
    </head>
    <body text=black bgcolor=white><h1><img src="/img/crackspider.jpg">
    WikiRegister</h1>);
    $registerPage .= getHeaderLinks($commonArgs,1);
    $registerPage .= qq(Please enter your OC Forums username.  You will be sent a PM with a confirmation link.
    <META NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW">
    <form method="post" action="/wiki/ocwiki.pl">
    <input type="hidden" name="rstr" value="$randomStr">
    <input type="hidden" name="do" value="register">
    <code>username:</code><input type="text" name="username"><br>
    <input type="submit" name="login" value="Submit">
    </form><hr>);
    return DVAL $registerPage;
}

sub getSignupPage($$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $confirmationString = shift;
    my $username = $commonArgs->{'loggedInUser'};
    my $signupPage = getHTMLHeader($commonArgs);
    $signupPage .= qq(<html>
    <head>
    <title>WikiSignup</title>
    <link rel="stylesheet" href="/hs.css" type="text/css">
    </head>
    <body text=black bgcolor=white><h1><img src="/img/crackspider.jpg">
    WikiSignup</h1>);
    $signupPage .= getHeaderLinks($commonArgs,1);
    $signupPage .= qq(Please choose your password.<script type="text/javascript" src="/js/md5.js"></script><form action="ocwiki.pl" method="post" onsubmit="md5hash(password1,passwordHash1);md5hash(password2,passwordHash2)">);
    $signupPage .= qq(<code>username: $username</code><br>
    <code>password:</code> <input type="password" name="password1" size="10" /><br> 
    <code>confirm: </code> <input type="password" name="password2" size="10" /><br> 
    <input name="login" type="submit" value="Sign up" />
    <input type="hidden" name="passwordHash1" />
    <input type="hidden" name="passwordHash2" />
    <input type="hidden" name="conf_str" value="$confirmationString" />
    <input type="hidden" name="do" value="signup" />
    </form></html>);
    return DVAL $signupPage;
}

sub getSearchPage($)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    return getHTMLHeader($commonArgs).
    qq(<html><head>
    <title>WikiSearch</title>
    <link rel="stylesheet" href="/hs.css" type="text/css">
    </head>
    <body text=black bgcolor=white><h1><img src="/img/crackspider.jpg">
    <a href="ocwiki.pl?do=search">WikiSearch</a></h1>).
    getHeaderLinks($commonArgs,0).qq(<META NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW">
    <form method="post" action="/wiki/ocwiki.pl">
    <input type="hidden" name="do" value="search">
    search string: <input type="text" name="search_for"><br>
    <input type="radio" name="search_type" value="any" checked="checked">any words
    <input type="radio" name="search_type" value="all">all words
    <input type="radio" name="search_type" value="exact">exact match<br>
    <input type="checkbox" name="whole_words" value="true">match whole words only<br>
    <input type="radio" name="find_version" value="latest" checked="checked">latest version
    <input type="radio" name="find_version" value="any">any version<br>
    <input type="submit" name="search" value="Search">
    </form><hr>);
}   

sub getSearchResultsPage($$$$$)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $dbh = $commonArgs->{'dbh'};
    my $searchFor = shift;
    my $searchType = shift; #should be 'all','any' or 'exact'
    my $wholeWords = shift;
    my $findVersion = shift;
    $searchFor =~ s/'/''/g;
    my $resultsPage = getHTMLHeader($commonArgs);
    $resultsPage .= qq(<html><head>
    <title>WikiSearch results</title>
    <link rel="stylesheet" href="/hs.css" type="text/css">
    </head>
    <body text=black bgcolor=white><h1><img src="/img/crackspider.jpg">
    <a href="ocwiki.pl?do=search">WikiSearch results</a></h1>).
    getHeaderLinks($commonArgs,0);

    my $wb = ""; #wb = word boundary, non-empty if we only want whole words (hack)
    $wb = " " if $wholeWords ne 'false';
    
    my $sqlQuery = "SELECT DISTINCT name, full_name FROM posts WHERE ";
    if ($searchType eq 'exact') {
        $sqlQuery .= "text LIKE '\%$searchFor\%' ";
    } elsif ($searchType eq 'all' ) {
        for my $word (split / /,$searchFor) {
            $sqlQuery .= "text LIKE '\%$wb$word$wb\%' AND ";
        }
        $sqlQuery =~ s/AND $/ /;
    } elsif ($searchType eq 'any' ) {
        for my $word (split / /,$searchFor) {
            $sqlQuery .= "text LIKE '\%$wb$word$wb\%' OR ";
        }
        $sqlQuery =~ s/OR $/ /;
    }

    if ($findVersion eq 'latest') {
        my $latestVersion = getMaxVersion($commonArgs);
        $sqlQuery .= " AND version=$latestVersion";
    }
    my $dbRes = $dbh->selectall_arrayref($sqlQuery);

    my $resultNum = 1;
    foreach my $n (@$dbRes) {
        $searchFor =~ s/ /,/g;
        $resultsPage .= qq($resultNum: <a href="ocwiki.pl?do=view&page=$n->[0]&highlight=$searchFor">$n->[1]</a><br>\n);
        $resultNum++;
    }
    if ($resultNum == 1) {
        $resultsPage .= qq(I didn't find any results for \"$searchFor\".);
    }
    $resultsPage .= qq(<hr></body></html>);

    return DVAL $resultsPage;
}

sub getMaxVersion($)
{
    DFEATURE my $f_;
    my $commonArgs = shift;
    my $dbh = $commonArgs->{'dbh'};
    my $pageName = $commonArgs->{'pageName'};
    $pageName =~ s/'/''/g;
    my $dbRes = $dbh->selectrow_arrayref("SELECT MAX(version) FROM posts WHERE name='$pageName'");
    if (scalar(@$dbRes)) {
        return DVAL $dbRes->[0];
    }
    return DVAL 0;
}

sub getLoginHash($)
{
    DFEATURE my $f_;
    #print "getting login hash...\n";
    my $commonArgs = shift;
    my $dbh = $commonArgs->{'dbh'};
    my $username = $commonArgs->{'loggedInUser'};
    $username = lc(uri_unescape($username));
    $username =~ s/'/''/g;
    #get password hash, confirmation string, salt
    my $dbRes = $dbh->selectrow_arrayref("SELECT password_hash, confirmation_string FROM members WHERE canonical_username='$username'");
    #print "SELECT password_hash, confirmation_string FROM members WHERE canonical_username='$username'\n";
    #print "playing with db...\n";
    if (defined $dbRes && scalar(@$dbRes)) {
        #print "found some stuff in db...\n";
        my $passwordHash = $dbRes->[0];
        my $confString = $dbRes->[1];
        return DVAL md5_hex("trogdor".$username.$passwordHash.$confString);
    }
    return DVOID;
}
    
DFEATURE my $f_;

my $q = new CGI;
my %cookie = fetch CGI::Cookie;
my ($arg, $param);
my $args = $q->Vars; ;

my $ua = LWP::UserAgent->new(agent => "Christoph's Alt OS FAQWiki user agent");
$ua->cookie_jar( HTTP::Cookies->new());
$ua->timeout(20);

my $dbh = DBI->connect("dbi:SQLite:dbname=/var/db/db","","");
my $loggedInUser = "";

#If the user has logged in, $commonArgs->{'loggedInUser'}
my $commonArgs = {
    pageName => "",
    dbh => $dbh,
    ua => $ua,
    q => $q,
    loggedInUser => $loggedInUser
};

#do cookie authentication
if (defined $cookie{'loginhash'} && defined $cookie{'username'}) {

    my $username  = $cookie{'username'};
    my $loginHash = $cookie{'loginhash'}; #md5sum unique to this user/password
    $username  =~ s/[^=]*=//;
    $loginHash =~ s/[^=]*=//;
    $username  =~ s/;.*//; #mmm!  Processed cookie!
    $loginHash =~ s/;.*//;

    $commonArgs->{'loggedInUser'} = $username;
    my $expectedLoginHash = getLoginHash($commonArgs);

    #compare to cookie val
    if ($expectedLoginHash eq $loginHash) {
        #if they match, set the loggedInUser as appropriate
        $commonArgs->{'loggedInUser'} = uri_unescape($username);
    } else {
        #otherwise, nobody is logged in
        $commonArgs->{'loggedInUser'} = "";
    }
}

#add a useful default
if (!defined $args->{'do'} ) {
    $args->{'do'} = 'view';
    $args->{'page'} = 'FaqUse';
} 

#XXX: if we get something like ocwiki.pl?FaqUse
#turn it indo do=view&page=FaqUse

if ($args->{'do'} eq "view") {

    my $page;
    my $version;
    my $versionLimit = 5;
    if (defined $args->{'page'}) {
        $commonArgs->{'pageName'} = $args->{'page'};
    } else {
        print getErrorPage($commonArgs,"no page specified");
        return;
    }
    
    if (defined $args->{'version'}) {
        $version = $args->{'version'};
    } else {
        $version = getMaxVersion($commonArgs);
    }

    if (defined $args->{'edits'}) {
        $versionLimit = $args->{'edits'};
    }

    if (pageExists($commonArgs,$version) eq "false") {
        print getNewPagePage($commonArgs,$args->{'page'});
        return;
    }

    my $rawBBCode = getRawPostBody($commonArgs,$version);
    if (defined $args->{'highlight'}) {
        for my $word (split /,/,$args->{'highlight'}) {
            #XXX: this isn't the best way to avoid highlighting stuff inside urls
            $rawBBCode =~ s#(\s)($word)(\s)#$1\[b\]\[color=red\]$2\[/color\]\[/b\]$3#ig;
        }
    }
    my $htmlText = bbWikiConvert($commonArgs,$rawBBCode);
    print getViewPage($commonArgs,$version,$versionLimit,$htmlText);

} elsif ($args->{'do'} eq 'edit' ) {

    my $page;
    my $version;
    if (defined $args->{'page'}) {
        $commonArgs->{'pageName'} = $args->{'page'};
    } else {
        print getErrorPage($commonArgs,"no page specified");
        return;
    }

    if (pageExists($commonArgs,1) eq "false") {
        print getErrorPage($commonArgs,"page '$page' doesn't exist");
        return;
    }

    if (defined $args->{'version'}) {
        $version = $args->{'version'}; 
        if (pageExists($commonArgs,$version) eq "false") {
           print getErrorPage($commonArgs,"specified version doesn't exist");
           return;
        }
    } else {
        $version = getMaxVersion($commonArgs);
    }

   print getEditPage($commonArgs,$version);
    
} elsif ($args->{'do'} eq 'submit') {

    my $authorComment;
    my $comment = "";

    if (defined $args->{'author_comment'}) {
        $authorComment = $args->{'author_comment'};
    } else {
        print getErrorPage($commonArgs,"No author comment was specified.");
        return;
    }
    if ($authorComment !~ /\/me/) {
        print getErrorPage($commonArgs,q(Please add "/me" to your author's comment.<br>Example: "/me made a few clarifications."));
        return;
    }

    if (defined $args->{'page'}) {
        $commonArgs->{'pageName'} = $args->{'page'};
    } else {
        print getErrorPage($commonArgs,"No page was specified.");
        return;
    }

    if ( $commonArgs->{'loggedInUser'} eq "") {
        print getErrorPage($commonArgs,"Please log in before posting.");
        return;
    }

    #goofy hack so I can submit forms by the name of text_2432
    my $text;
    for my $x (keys %$args) {
        #print "key is $x<br>\n";
        if ($x =~ /^text_/) {
            $text = $args->{$x};
        }
    }
    if ($text eq "") {
        print getErrorPage($commonArgs,"no text");
        return;
    }
    $text =~ s/\r\n/\n/g;

    if (defined $args->{'preview'}) {
        #check that title, text, loggedInUser, author_comment
        if (! defined $args->{'full_name'} ) {
            print getErrorPage($commonArgs,"You didn't specify a title");
            return;
        }
        my $title = $args->{'full_name'};
        print getPreviewPage($commonArgs,$authorComment,$comment,$text,$title);
    } elsif (defined $args->{'post'}) {
    
        if (! defined $args->{'full_name'} ) {
            print getErrorPage($commonArgs,"You didn't specify a title");
            return;
        }
        my $title = $args->{'full_name'};
        addNewVersion($commonArgs,$authorComment,$comment,$text,$title);
        my $version = getMaxVersion($commonArgs);
        my $htmlText = bbWikiConvert($commonArgs,getRawPostBody($commonArgs,$version));
        print getViewPage($commonArgs,$version,5,$htmlText);
    }

} elsif ($args->{'do'} eq 'login' ) {
    #if username and passwordHash are defined
    if (defined $args->{'username'} && defined $args->{'passwordHash'}) {
        #check that user exists
        #check that password hash matches
        my $username = $args->{'username'};
        $commonArgs->{'loggedInUser'} = $username;
        $username =~ s/'/''/g;
        my $dbRes = $dbh->selectrow_arrayref("SELECT password_hash FROM members WHERE username='$username'");

        if (defined $dbRes && scalar(@$dbRes) && $dbRes->[0] eq $args->{'passwordHash'}) {
            my $cookies ="Set-Cookie: ".
            new CGI::Cookie(-name  => 'loginhash',
                            -value => getLoginHash($commonArgs),
                            -expires => "+7d")."\n";
            $cookies .= "Set-Cookie: ".
            new CGI::Cookie(-name  => 'username',
                            -value => $username,
                            -expires => "+7d")."\n";
            $commonArgs->{'pageName'} = "FaqUse";
            $commonArgs->{'cookies'} = $cookies;
            my $version = getMaxVersion($commonArgs);
            my $htmlText = bbWikiConvert($commonArgs,getRawPostBody($commonArgs,$version));
            print getViewPage($commonArgs,$version,5,$htmlText);
        } else {
            $commonArgs->{'loggedInUser'} = ""; #force logout
            print getErrorPage($commonArgs,"You entered an invalid username or password.");
        }
        #check stuff
    #elsif nothing else is defined
    } else {
        #give the login page
        print getLoginPage($commonArgs);
    }
} elsif ($args->{'do'} eq 'logout' ) {
    my $cookies = "Set-Cookie: ".
            new CGI::Cookie(-name  => 'loginhash',
                            -value => 'deleted',
                            -expires => "-1d")."\n";
    $cookies .= "Set-Cookie: ".
            new CGI::Cookie(-name  => 'username',
                            -value => 'deleted',
                            -expires => "-1d")."\n";
    $commonArgs->{'cookies'} = $cookies;                                        
    $commonArgs->{'pageName'} = "FaqUse";
    $commonArgs->{'loggedInUser'} = ""; #force logout
    my $version = getMaxVersion($commonArgs);
    my $htmlText = bbWikiConvert($commonArgs,getRawPostBody($commonArgs,$version));
    print getViewPage($commonArgs,$version,5,$htmlText);

    
} elsif ($args->{'do'} eq 'register' ) {
    #if username and maagic cookie are defined
    if (defined $args->{'username'} && defined $args->{'rstr'}) {
        my $rstr = $args->{'rstr'};
        my $username = $args->{'username'};
        my $canonicalUsername = getCanonicalUsername($username);
        $canonicalUsername =~ s/'/''/g;
        my $dbh = $commonArgs->{'dbh'};
        my $dbRstr = $dbh->selectrow_arrayref("SELECT rstr FROM rstr WHERE rstr='$rstr'");
        my $dbUser = $dbh->selectrow_arrayref("SELECT confirmation_string FROM members WHERE canonical_username='$canonicalUsername'");

        #check if the guy's on the forums...
        if (isMember($commonArgs,$username) ne "success") {
            print getErrorPage($commonArgs,"$username doesn't seem to be a member on the forums.");

        #check if the rstr from the first page was put into the db...    
        } elsif (! defined $dbRstr || $dbRstr->[0] ne $rstr) {
            print getErrorPage($commonArgs,"It looks like you're trying to do something suspicious.  Cut it out.");

        #check if this user is already registered on the wiki
        } elsif (defined $dbUser && $dbUser->[0] ne "") {
            print getErrorPage($commonArgs,"It looks like $username user has already signed up.");

        #he's good to go!
        } else {
            #generate a confirmation string
            my $confirmationString = randomString(16);
            my $dbh = $commonArgs->{'dbh'};
            $dbh->do("UPDATE members SET confirmation_string='$confirmationString' WHERE canonical_username='$canonicalUsername'");
            my $pmBody = qq(Hello $username,
I'm the Alt OS FAQWiki.  You (or someone pretending to be you) told me that you wanted to register.  If it was you, please click [url=http://mksig.org/wiki/ocwiki.pl?do=confirm&conf_str=$confirmationString]here[/url] to confirm your registration.  You can also use the above link if you ever need to reset your password.
Before you start editing, please take a minute to read through the [url=http://mksig.org/wiki/ocwiki.pl?do=view&page=WikiGuidelines]guidelines[/url].
This is an automated PM.  If you have any thoughts, please PM [url=http://www.ocforums.com/private.php?do=newpm&u=9681]Christoph[/url] or leave a comment [url=http://www.ocforums.com/showthread.php?t=329177]here[/url].

Thanks,
The Alt OS FAQWiki);
            #send a PM to username
            my $pmResult = sendPM($commonArgs,$username,"Hello from the Alt OS FAQWiki!",$pmBody);
            if ($pmResult eq "success") {
                print getSuccessPage($commonArgs,"A confirmation PM has been sent to $username.  Please check your PM inbox to complete your registration.\n");
            } elsif ($pmResult eq "error: retry in 30 seconds") {
                print getErrorPage($commonArgs,"Too many users are trying to register right now.  Plesae try back in 30 seconds.");
            } else {
                print getErrorPage($commonArgs,"There was an error sending the PM.  Please try again.\n");
            #show that the PM has been sent
            }
        }
    #else
    } else {
        #show the register page
        print getRegisterPage($commonArgs);
    }
} elsif ($args->{'do'} eq 'signup' ) {
    #check that conf_str is defined and valid
    if (! defined $args->{'conf_str'}) {
        print getErrorPage($commonArgs,"Your confirmation string wasn't defined.");
    } elsif ( ! defined $args->{'passwordHash1'} || ! defined $args->{'passwordHash2'}) {
        print getErrorPage($commonArgs,"You didn't supply or confirm your password");
    #check that password hashes match
    } elsif ($args->{'passwordHash1'} ne $args->{'passwordHash2'}) {
        print getErrorPage($commonArgs,"passwords didn't match");
    
    #this is the md5sum of the empty string
    } elsif ($args->{'passwordHash1'} eq "d41d8cd98f00b204e9800998ecf8427e") {
        print getErrorPage($commonArgs,"Empty passwords aren't allowed.");
    } else {
        #insert password into db
        #change confirmationg string to "confirmed" in db
        my $passwordHash = $args->{'passwordHash1'};
        $passwordHash =~ s/'/''/g; #paranoid people are harder to kill
        my $dbh = $commonArgs->{'dbh'};
        my $confStr = $args->{'conf_str'};
        $confStr =~ s/'/''/g;
        my $dbRes = $dbh->selectrow_arrayref("SELECT username FROM members WHERE confirmation_string='$confStr'");
        my $username = $dbRes->[0];
        $dbh->do("UPDATE members SET password_hash='$passwordHash' where username='$username'");
        $dbh->do("UPDATE members SET confirmation_string='confirmed' where name='$username'");
        $username =~ s/''/'/g;
        print getSuccessPage($commonArgs,"You can now login as $username with the password you supplied.");
    }

} elsif ($args->{'do'} eq 'confirm' ) {
    if (!defined $args->{'conf_str'}) {
        print getErrorPage($commonArgs,"no confirmation string given");
    } else {
        my $confstrStatus = confirmConfirmationString($commonArgs,$args->{'conf_str'});
        if ($confstrStatus !~ /^success:/) {
            print getErrorPage($commonArgs,"invalid confirmation string given: $confstrStatus\nperhaps you've already registered using this link");
        } else {
            my $username = $confstrStatus;
            $username =~ s/^success://;
            $commonArgs->{'loggedInUser'} = $username;
            print getSignupPage($commonArgs,$args->{'conf_str'});
        }
    }
} elsif ($args->{'do'} eq 'search' ) {
    #if nothing is defined, present the search page
    if (! defined $args->{'search_for'} ) {
        #present the search page
        print getSearchPage($commonArgs);
    } else {
        if (! defined $args->{'search_type'} ) {
            print getErrorPage($commonArgs,"You didn't specify a search type.");
            return;
        } elsif (! defined $args->{'find_version'} ) {
            print getErrorPage($commonArgs,"You didn't specify which version to search.");
            return;
        }
        my $findMe = $args->{'search_for'};
        my $searchType = $args->{'search_type'};
        my $wholeWords = "false";
        my $findVersion = $args->{'find_version'};
        $wholeWords = $args->{'whole_words'} if defined $args->{'whole_words'};
        print getSearchResultsPage($commonArgs,$findMe,$searchType,$wholeWords,$findVersion);
    }
} elsif ($args->{'do'} eq 'changes' ) {
    #get a sorted list of posts (and versions, etc)
    my $changeCount = "all";
    if (defined $args->{'count'}) {
        $changeCount = $args->{'count'};
    }
    print getRecentChanges($commonArgs,$changeCount);

} elsif ($args->{'do'} eq 'newpage' ) {
    print getNewPagePage($commonArgs,"");

} elsif ($args->{'do'} eq 'debug' ) {
    $commonArgs->{'pageName'} = "FaqUse";
    $commonArgs->{'loggedInUser'} = ""; #force logout
    my $version = getMaxVersion($commonArgs);
    my $htmlText = bbWikiConvert($commonArgs,getRawPostBody($commonArgs,$version));
    print getViewPage($commonArgs,$version,5,$htmlText);
    print "\n";

} else {
    print getErrorPage($commonArgs,"I don't know what to do for '$args->{'do'}.'");
}

#sqlite connections are cheap, so there's no need to recycle them
$dbh->disconnect();

    

