#!/usr/bin/perl # # Wala.pm # version 1.1.8 # # This script is in the public domain, and was originally written by # Brent P. Newhall . # If you re-distribute this code, modified or otherwise, please give credit # where due. # # This is a Wala, which is a derivation of a wiki that incorporates appending # text directly to pages, turning a wiki into something more like a forum while # retaining all the wonderful full-page editing features of a wiki. # # INSTALLATION: # This script is a self-contained package, which makes the code easy to test. # To actually use it as a wala, create a script named "wala.pl" in the same # directory, containing the following three lines of code (changed appropriately # for your setup of Perl): # # #!/usr/bin/perl # use Wala; # Wala::run; # # You can experiment with the wala by use'ing it and calling its functions # without calling "run". Any files and/or directories required by this script # are created as needed. Make sure that the web server has write access to the # directory. # # Modify the OPTIONS section about fifteen lines down to set up specific # options for this installation. # # FEATURES: # ''italics'' and '''bold''', ----, ":" for indentation, " " for # code, "#" # and "*" for lists, "|" for tables, [[Bareword]] pagenames, # [[Page||alternate link text]], linkifying of http:// and related protocols, # linkifying of mailto:, inline images, logged diffs, and cookie preferences # Setup stuff... use strict; package Wala; use UNIVERSAL qw(isa); ############### # OPTIONS # ############### my $RecentChangesMaxLines = 50; # Max lines to display in RecentChanges my $DefaultUserName = 'Anonymous'; # Default user name for "Add to page" my $StyleSheet = '/style.css'; # URL of style sheet my $PrinterStyleSheet = ''; # URL of style sheet for printing my $DefaultPageText = "Enter text here.\n"; my $CookieSurvivalDays = 90; # Number of days for cookies to remain # To set up and run the script, you should not have to edit anything below # this line. # "Global" variables, used by multiple functions my $ScriptName = substr( $0, rindex( $0, "/" ) + 1 ); my $page; my %cookies; my %parameters; my $last_wiki_line_type = ''; my $current_list_level = 0; my $last_recent_changes_date = ''; sub run # This is the "main()" function of the script. Call this function to run the # wala. { my( $query, $result ); &setup; &parse_cookies( $ENV{'HTTP_COOKIE'} ); $page = 'HomePage'; if( length( $ENV{'QUERY_STRING'} ) > 0 && index( $ENV{'QUERY_STRING'}, '=' ) < 0 ) { # We got a plain WikiWord as the only parameter, so that's the page $page = $ENV{'QUERY_STRING'}; # Strip out ; to prevent security breaches if( index( $page, ';' ) >= 0 ) { $page = substr( $page, 0, index( $page, ';' ) ); } } elsif( length($ENV{'QUERY_STRING'}) > 0 || $ENV{'CONTENT_LENGTH'} > 0 ) { # We have one or more parameters; read and parse them if( $ENV{'CONTENT_LENGTH'} > 0 ) { read( STDIN, $query, $ENV{'CONTENT_LENGTH'} ); } else { $query = $ENV{'QUERY_STRING'}; } $result = &parse_parameters( $query, $page ); } print &get_header( $page ); if( $result ne '' ) { print( $result ); print( "\n\n\n" ); } else { print( &print_page( $page ) . &print_standard_footer( $page ) ); } } ############################ # Wiki Word Processing # ############################ sub make_links_of_wiki_words { my( $text ) = shift; $text =~ s/\[\[([A-Za-z0-9| ]*)\]\]/&get_wiki_word_link($1)/geos; $text =~ s/(? 0 ) { $label = substr( $wikiword, $bar_posit + 1 ); $wikiword = substr( $wikiword, 0, $bar_posit ); } if( -e "pages/$wikiword" ) { return "" . "$label"; } else { return "$label"; } } ####################### # Link Processing # ####################### sub convert_links { my( $text ) = shift; $text =~ s/(?= 0 ) { return "" . substr( $text, $posit + 1 ) . ""; } else { return "$text"; } } sub get_link { my( $URL ) = shift; my $URL_stub = $URL; $URL_stub =~ s|^.*?://(.*)$|$1|; my $protocol = substr( $URL, 0, index( $URL, ':' ) ); if( $URL =~ /^(http:|https:|ftp:).+\.(gif|jpg|jpeg|png$)/ ) { return "\"[IMAGE]\""; } else { return "$protocol://$URL_stub"; } } sub get_email_link { my( $email_with_mailto ) = shift; return "" . substr( $email_with_mailto, 7 ) . ""; } ################### # Wiki Markup # ################### sub wiki_page_to_html { my( $page ) = shift; my( @lines, $line, $result ); $last_wiki_line_type = ''; @lines = split( "\n", $page ); foreach $line (@lines) { if( ord( substr( $line, length($line) - 1 ) ) < 20 ) # Remove EOL { chop( $line ); } $result .= &wiki_line_to_html( $line ) . "\n"; } $result .= &finish_wiki_tags( $result ); return $result; } sub wiki_line_to_html { my( $text ) = shift; $text =~ s/&/&/g; $text =~ s//>/g; $text = &convert_links( $text ); $text = &replace_wiki_markup_matched_tags( $text, "'''", '', '' ); $text = &replace_wiki_markup_matched_tags( $text, "''", '', '' ); $text =~ s/----*/
/; if( substr( $text, 0, 1 ) eq ' ' ) { $text = &setup_wiki_line_type( $text, ' ', '
' );
		}
	elsif( substr( $text, 0, 1 ) eq ':' )
		{
		$text = &setup_wiki_line_type( $text, ':', '
' ); } elsif( substr( $text, 0, 1 ) eq '*' ) { $text = &setup_wiki_line_type( $text, '*', '
    ' ); } elsif( substr( $text, 0, 1 ) eq '#' ) { $text = &setup_wiki_line_type( $text, '#', '
      ' ); } elsif( substr( $text, 0, 1 ) eq '|' ) { $text = &setup_wiki_line_type( $text, '|', '' ); } else { $text = &make_paragraph( $text ); $text = &finish_wiki_tags( $text ) . $text; } $text = &make_links_of_wiki_words( $text ); $text =~ s/<recentchanges +(\d+)>/&print_recent_changes($1)/geosi; return $text; } sub setup_wiki_line_type # Processes a Wiki line that starts with a special character like : or # { my( $text, $first_char, $tag ) = @_; my( $num_levels, $index ); if( $first_char eq '*' || $first_char eq '#' ) { while( substr( $text, 0, 1 ) eq $first_char ) { $text = substr( $text, 1 ); $num_levels = $num_levels + 1; } $text = '
    1. ' . $text . '
    2. '; if( $num_levels < $current_list_level && $last_wiki_line_type eq $first_char ) { $text = &repeat( ' $current_list_level && $last_wiki_line_type eq $first_char ) { $text = &repeat( $tag, $num_levels - $current_list_level ) . $text; } $current_list_level = $num_levels; } elsif( $first_char eq '|' ) { $text = ""; } elsif( $first_char eq ':' ) { $text = substr( $text, 1 ); } if( $last_wiki_line_type ne $first_char ) { $text = $tag . $text; } $last_wiki_line_type = $first_char; return $text; } sub finish_wiki_tags { my( $result, $index ); if( $last_wiki_line_type ne '' ) { if( $last_wiki_line_type eq ' ' ) { $result = ''; } elsif( $last_wiki_line_type eq ':' ) { $result = ''; } elsif( $last_wiki_line_type eq '*' ) { $result = &repeat( '', $current_list_level ); } elsif( $last_wiki_line_type eq '#' ) { $result = &repeat( '', $current_list_level ); } elsif( $last_wiki_line_type eq '|' ) { $result = '
      " . substr( $text, 1 ); $text =~ s/\|/<\/td>/g; $text .= "
      '; } } $last_wiki_line_type = ''; return $result; } sub replace_wiki_markup_matched_tags # Takes text, a type of Wiki markup (like ''), and start and end HTML tags. # Replaces two occurrences of the markup with the start and end tags. { my( $text, $markup, $start_tag, $end_tag ) = @_; $text =~ s/($markup)([\s\S]*?)($markup)/$start_tag$2$end_tag/g; return $text; } sub make_paragraph { my( $text ) = @_; if( $text ne "" && $text ne "
      " && $last_wiki_line_type eq '' ) { $text = '

      ' . $text . '

      '; } return $text; } ########################## # Webpage Processing # ########################## sub write_page { my( $pagename, $file_text, $summary ) = @_; &write_diff( $pagename, $file_text ); open( FILE, ">pages/$pagename" ); print( FILE $file_text ); close( FILE ); &log_page_edit( $pagename, $summary, &get_username ); return print_page( $pagename ) . print_standard_footer( $pagename ); } sub add_to_page { my( $pagename, $new_text ) = @_; my( $file_text, $summary, $posit ); $file_text = &get_file_text( "pages/$pagename" ); $file_text .= "\n<[[" . &get_username . "]]> $new_text\n"; $posit = index( $new_text, "\n" ); if( $posit > 0 ) { $summary = substr( $new_text, 0, $posit ); } else { $summary = $new_text; } return &write_page( $pagename, $file_text, $summary ); } sub get_header { my( $pagename ) = shift; my( $result ); $result = "Content-type: text/html\n\n" . "\n" . "\n" . "Wala - $pagename\n"; if( $StyleSheet ne '' ) { $result .= "\n"; } if( $PrinterStyleSheet ne '' ) { $result .= "\n"; } $result .= "\n" . "\n" . "

      $pagename

      \n" . "\n" . "
      \n\n"; return $result; } sub print_standard_footer { my( $pagename ) = shift; my( $text, $datestamp ); $text = "\n\n\n
      \n" . "\n" . " \n" . " \n". " \n" . " " . "\n" . " \n" . " \n" . " \n" . " \n" . " \n" . " " . "\n" . " \n" . "
      Add your " . "response:
      <" . &get_username . ">"; if( &get_username eq $DefaultUserName ) { $text .= "
      Change name"; } $text .= "
      " . "
      " . "
      \n" . " To format your text, see the Text Formatting Rules\n" . "
      \n" . "
      \n"; if( defined( $cookies{'username'} ) ) { $text .= "Logged in as " . &get_username . " "; } else { $text .= "Log in "; } $text .= "| Edit " . "this page"; my $diff_date = &get_latest_diff_date( $pagename, $datestamp ); if( $diff_date ne '' ) { $text .= " (last edited $diff_date)"; } $text .= "\n\n" . "\n"; return $text; } sub print_page { my( $pagename ) = shift; my( $pagetext,$result ); $pagetext = &get_file_text( "pages/$pagename" ); if( $pagetext eq '' ) { return( $DefaultPageText ); } $pagetext = &wiki_page_to_html( $pagetext ); if( $pagename eq 'RecentChanges' ) { if( ! -e "pages/RecentChanges" ) { open( FILE, ">pages/RecentChanges" ); print( FILE $DefaultPageText ); close( FILE ); } $pagetext .= &print_recent_changes( $RecentChangesMaxLines ); } return( $pagetext ); } sub get_wiki_edit_form { my( $pagename ) = shift; my( $result, $file_text ); $file_text = &get_file_text( "pages/$pagename" ); if( $file_text eq '' ) { $file_text = $DefaultPageText; } $file_text =~ s//>/g; $result = "
      " . "" . "" . "
      " . "\n" . "" . "\n
      Summary:
      ("; if( &get_username eq $DefaultUserName ) { $result .= "You are not logged in."; } else { $result .= "You are logged in as " . &get_username . "."; } $result .= ")
      \n"; return $result; } sub log_page_edit { my( $pagename, $summary, $username ) = ( shift, shift, shift ); my $currenttime = time; my $result = open( LOGFILE, ">>log" ); if( ! $result ) { return; } print( LOGFILE "$currenttime $pagename $username $summary\n" ); close( LOGFILE ); } sub get_list_of_all_pages { my( $filename, $text, @pages ); opendir( DIR, "pages/" ); $filename = readdir( DIR ); $filename = readdir( DIR ); $filename = readdir( DIR ); while( $filename ne '' ) { if( $filename ne 'temp' ) { push( @pages, $filename ); } $filename = readdir( DIR ); } @pages = sort( @pages ); foreach $filename (@pages) { $text .= "$filename
      \n"; } closedir( DIR ); return $text; } ###################### # Recent Changes # ###################### sub print_recent_changes { my( $max_lines ) = shift; my( @lines, $line, $result ); my $result = open( LOGFILE, "log" ); if( ! $result ) { return ''; } $result = ''; @lines = ; close( LOGFILE ); @lines = reverse( @lines ); while( $#lines >= $max_lines ) { pop( @lines ); } foreach $line (@lines) { chop( $line ); $result .= &get_recent_changes_line( $line ); } return $result . "
\n"; } sub get_recent_changes_line { my( $line ) = shift; my( $posit, @t, $timestamp, $thetime, $hour, $min, $ampm, $datestamp, $pagename, $author, @months, $year, $result ); @months = ( 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ); $timestamp = substr( $line, 0, 10 ); @t = localtime( $timestamp ); $year = $t[5] + 1900; $datestamp = $months[$t[4]] . " $t[3], $year"; if( $datestamp ne $last_recent_changes_date ) { if( $last_recent_changes_date ne '' ) { $result .= "\n"; } $result .= "$datestamp
\n
    \n"; $last_recent_changes_date = $datestamp; } $hour = $t[2]; if( $hour == 0 ) { $ampm = 'am'; $hour = 12; } elsif( $hour < 12 ) { $ampm = 'am'; } elsif( $hour == 12 ) { $ampm = 'pm'; } else { $ampm = 'pm'; $hour -= 12; } $min = $t[1]; if( length($min) == 1 ) { $min = '0' . $min; } $thetime = "$hour:$min $ampm"; $line = substr( $line, 11 ); $posit = index( $line, ' ' ); $pagename = substr( $line, 0, $posit ); $line = substr( $line, $posit + 1 ); if( substr( $line, 0, 1 ) ne ' ' ) { $posit = index( $line, ' ' ); $author = substr( $line, 0, $posit ); $line = substr( $line, $posit + 1 ); } else { $author = 'Anonymous'; $line = substr( $line, 1 ); } $line =~ s//>/g; $line =~ s/&/&/g; $result .= "
  • $pagename - " . $thetime . " - [" . $line . '] . . . . . ' . $author . "
  • \n"; } ############################### # Preferences and Cookies # ############################### sub get_preferences_form { my( $originalpage ) = shift; my( $text ); $text = "\n" . " \n" . " \n" . " \n" . " " . "\n" . " \n" . " \n" . " \n" . "
    Your name:
    (Letters and/or numbers only, no " . "spaces)
    \n"; return $text; } sub parse_cookies { my( $cookie_string ) = ( shift, shift ); my( @values, $query, $name, $value ); @values = split(/&/, $cookie_string); foreach $query (@values) { ($name, $value) = split(/=/, $query); $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; $cookies{$name} = $value; } } sub write_cookies_to_browser { my( $username, $newpage ) = ( shift, shift ); my $expiration_time = mygmtime( time + 86400 * $CookieSurvivalDays ); if( $username =~ /^[A-Za-z0-9]+$/ ) { print( "Set-Cookie: username=$username; expires=$expiration_time\n" ); } print( "Location: $ScriptName?$newpage\n\n" ); exit( 0 ); } ############# # Setup # ############# sub setup { if( ! -d "pages" ) { mkdir( "pages", 0777 ); mkdir( "diffs", 0777 ); } if( ! -e "pages/HomePage" ) { open( FILE, ">pages/HomePage" ); print( FILE "Welcome to the Wala.\n\n" . "See TextFormattingRules.\n" ); close( FILE ); } if( ! -e "pages/TextFormattingRules" ) { open( FILE, ">pages/TextFormattingRules" ); print( FILE "Wala has the following '''text formatting rules''':\n\n" . "* Start a line with * to create a ''bulleted list''.\n\n" . "# Start a line with # to create a ''numbered list''.\n\n" . ":Start a line with a : to indent it.\n\n" . " Start a line with a space to display it like source " . "code.\n\n" . "Start a line with a | to create a table:\n" . "|This is|a table\n" . "|specified with|formatting rules.\n\n" . "SmashWordsTogetherLikeSo to create a link to a new " . "page, or use brackets around a [[word]]. Click on " . "the link to create the page.\n\n" . "For links, just type the URL: " . "http://www.stun-software.com/\n\n" . "Alternatively, surround the URL with brackets and put a " . "description after it, separated by a space: " . "[http://www.stun-software.com/ STUN Software]\n\n" . "To include an image, just type the URL of the image.\n" ); close( FILE ); } } sub parse_parameters { my( $query_string ) = shift; my( @values, $query, $name, $value ); @values = split( /&/, $query_string ); foreach $query (@values) { ($name, $value) = split(/=/, $query); $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack( "C", hex($1) )/eg; $parameters{$name} = $value; } $_[0] = $parameters{'id'}; if( $parameters{'action'} eq 'edit' ) { return &get_wiki_edit_form( $parameters{'id'} ); } elsif( $parameters{'action'} eq 'post' ) { return &write_page( $parameters{'id'}, $parameters{'filetext'}, $parameters{'summary'} ); } elsif( $parameters{'action'} eq 'addtext' ) { return &add_to_page( $parameters{'id'}, $parameters{'text'} ); } elsif( $parameters{'action'} eq 'diff' ) { $_[0] .= ' Diff'; return &get_diff( $parameters{'id'}, $parameters{'datestamp'} ) . "
    \n" . &print_page( $parameters{'id'} ); } elsif( $parameters{'action'} eq 'setprefs' ) { &write_cookies_to_browser( $parameters{'username'}, $parameters{'id'} ); } elsif( $parameters{'action'} eq 'editprefs' ) { $_[0] = 'Preferences'; return &get_preferences_form( $parameters{'originalpage'} ); } elsif( $parameters{'action'} eq 'index' ) { $_[0] = 'Index of all pages'; return &get_list_of_all_pages; } } ############ # Diff # ############ sub get_diff { my( $pagename, $datestamp ) = ( shift,shift ); my( $text, @lines, $line, $last_type, $added_text, $removed_text ); $text = &get_file_text( "diffs/$pagename/$datestamp" ); @lines = split( "\n", $text ); $added_text = "Added:
    \n" . "
    \n"; $removed_text = "Removed:
    \n" . "
    \n"; $text = ''; foreach $line (@lines) { if( substr( $line, 0, 1 ) eq '<' || substr( $line, 0, 1 ) eq '>' ) { $text .= substr( $line, 1 ) . "
    \n"; } elsif( substr( $line, 0, 3 ) eq '---' ) { $text .= "
    \n$removed_text"; } elsif( index( $line, 'a' ) >= 0 && substr( $line, 0, 1 ) ne '\\' ) { $text .= "
    \n$removed_text"; } elsif( index( $line, 'c' ) >= 0 && substr( $line, 0, 1 ) ne '\\' ) { $text .= "\n$added_text"; } } return $text . "\n"; } sub write_diff { my( $pagename, $new_text ) = @_; my $currtime = time; my( $old_text, $the_diff ); open( FILE, ">pages/temp" ); print( FILE $new_text ); close( FILE ); $the_diff = `diff pages/temp pages/$pagename`; mkdir( "diffs/$pagename", 0777 ); open( FILE, ">diffs/$pagename/$currtime" ); print( FILE $the_diff ); close( FILE ); } sub merge_diff { my( $pagename, $datestamp ) = @_; my( $new_text ); $new_text = `patch -R --dry-run pages/$pagename diffs/$pagename/$datestamp 2> /dev/null`; print $new_text; } sub get_latest_diff_date { my( $pagename ) = shift; my @months = ( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ); my( $filename, @filenames, @t ); if( ! -d "diffs/$pagename" ) { return ''; } opendir( DIR, "diffs/$pagename" ); $filename = readdir( DIR ); $filename = readdir( DIR ); $filename = readdir( DIR ); while( $filename ne '' ) { push( @filenames, $filename ); $filename = readdir( DIR ); } closedir( DIR ); sort( @filenames ); @filenames = reverse( @filenames ); @t = localtime( $filenames[0] ); $_[0] = $filenames[0]; return $months[$t[4]] . ' ' . $t[3] . ', ' . substr( $t[5] + 1900, 2 ); } ################# # Utilities # ################# sub get_username { if( ( ! defined( $cookies{'username'} ) ) || $cookies{'username'} eq '' ) { return $DefaultUserName; } else { my $name = $cookies{'username'}; if( index( $name, ";" ) >= 0 ) { my @values = split( ";", $name ); return $values[0]; } return $name; } } sub mygmtime { my( $etime ) = shift; my @months = ( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ); my @days = ( 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($etime); my $timestr = sprintf( "%3s, %02d-%3s-%4d %02d:%02d:%02d GMT", $days[$wday],$mday,$months[$mon],$year+1900,$hour,$min,$sec); return $timestr; } sub repeat # Returns parameter 1, repeated parameter 2 times { my( $text, $num_iterations ) = (shift,shift); my( $index, $result ); for( $index = 0; $index < $num_iterations; $index++ ) { $result .= $text; } return $result; } sub get_file_text { my( $filename ) = (shift); my( $result, $pagetext ); $result = open( FILE, $filename ); if( ! $result ) { return( '' ); } while( ! eof( FILE ) ) { $pagetext .= ; } close( FILE ); return $pagetext; } 1;