From: joey Date: Wed, 21 Mar 2007 06:55:41 +0000 (+0000) Subject: tagging version 1.33.2 X-Git-Tag: etch X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/commitdiff_plain/91274e86f18b47d90d99bc92a243da093b9f621e tagging version 1.33.2 --- diff --git a/1.33.2/CHANGELOG b/1.33.2/CHANGELOG new file mode 120000 index 000000000..d526672ce --- /dev/null +++ b/1.33.2/CHANGELOG @@ -0,0 +1 @@ +debian/changelog \ No newline at end of file diff --git a/1.33.2/IkiWiki.pm b/1.33.2/IkiWiki.pm new file mode 100644 index 000000000..efacb20ed --- /dev/null +++ b/1.33.2/IkiWiki.pm @@ -0,0 +1,841 @@ +#!/usr/bin/perl + +package IkiWiki; +use warnings; +use strict; +use Encode; +use HTML::Entities; +use open qw{:utf8 :std}; + +use vars qw{%config %links %oldlinks %oldpagemtime %pagectime %pagecase + %renderedfiles %oldrenderedfiles %pagesources %depends %hooks + %forcerebuild}; + +use Exporter q{import}; +our @EXPORT = qw(hook debug error template htmlpage add_depends pagespec_match + bestlink htmllink readfile writefile pagetype srcfile pagename + displaytime will_render + %config %links %renderedfiles %pagesources); +our $VERSION = 1.01; # plugin interface version + +# Optimisation. +use Memoize; +memoize("abs2rel"); +memoize("pagespec_translate"); + +my $installdir=''; # INSTALLDIR_AUTOREPLACE done by Makefile, DNE +our $version='unknown'; # VERSION_AUTOREPLACE done by Makefile, DNE + +sub defaultconfig () { #{{{ + wiki_file_prune_regexp => qr{((^|/).svn/|\.\.|^\.|\/\.|\.x?html?$|\.rss$|\.atom$|.arch-ids/|{arch}/)}, + wiki_link_regexp => qr/\[\[(?:([^\]\|]+)\|)?([^\s\]]+)\]\]/, + wiki_file_regexp => qr/(^[-[:alnum:]_.:\/+]+$)/, + verbose => 0, + syslog => 0, + wikiname => "wiki", + default_pageext => "mdwn", + cgi => 0, + rcs => 'svn', + notify => 0, + url => '', + cgiurl => '', + historyurl => '', + diffurl => '', + anonok => 0, + rss => 0, + atom => 0, + discussion => 1, + rebuild => 0, + refresh => 0, + getctime => 0, + w3mmode => 0, + wrapper => undef, + wrappermode => undef, + svnrepo => undef, + svnpath => "trunk", + srcdir => undef, + destdir => undef, + pingurl => [], + templatedir => "$installdir/share/ikiwiki/templates", + underlaydir => "$installdir/share/ikiwiki/basewiki", + setup => undef, + adminuser => undef, + adminemail => undef, + plugin => [qw{mdwn inline htmlscrubber}], + timeformat => '%c', + locale => undef, + sslcookie => 0, + httpauth => 0, +} #}}} + +sub checkconfig () { #{{{ + # locale stuff; avoid LC_ALL since it overrides everything + if (defined $ENV{LC_ALL}) { + $ENV{LANG} = $ENV{LC_ALL}; + delete $ENV{LC_ALL}; + } + if (defined $config{locale}) { + eval q{use POSIX}; + error($@) if $@; + $ENV{LANG} = $config{locale} + if POSIX::setlocale(&POSIX::LC_TIME, $config{locale}); + } + + if ($config{w3mmode}) { + eval q{use Cwd q{abs_path}}; + error($@) if $@; + $config{srcdir}=possibly_foolish_untaint(abs_path($config{srcdir})); + $config{destdir}=possibly_foolish_untaint(abs_path($config{destdir})); + $config{cgiurl}="file:///\$LIB/ikiwiki-w3m.cgi/".$config{cgiurl} + unless $config{cgiurl} =~ m!file:///!; + $config{url}="file://".$config{destdir}; + } + + if ($config{cgi} && ! length $config{url}) { + error("Must specify url to wiki with --url when using --cgi\n"); + } + if (($config{rss} || $config{atom}) && ! length $config{url}) { + error("Must specify url to wiki with --url when using --rss or --atom\n"); + } + + $config{wikistatedir}="$config{srcdir}/.ikiwiki" + unless exists $config{wikistatedir}; + + if ($config{rcs}) { + eval qq{require IkiWiki::Rcs::$config{rcs}}; + if ($@) { + error("Failed to load RCS module IkiWiki::Rcs::$config{rcs}: $@"); + } + } + else { + require IkiWiki::Rcs::Stub; + } + + run_hooks(checkconfig => sub { shift->() }); +} #}}} + +sub loadplugins () { #{{{ + foreach my $plugin (@{$config{plugin}}) { + my $mod="IkiWiki::Plugin::".possibly_foolish_untaint($plugin); + eval qq{use $mod}; + if ($@) { + error("Failed to load plugin $mod: $@"); + } + } + run_hooks(getopt => sub { shift->() }); + if (grep /^-/, @ARGV) { + print STDERR "Unknown option: $_\n" + foreach grep /^-/, @ARGV; + usage(); + } +} #}}} + +sub error ($) { #{{{ + if ($config{cgi}) { + print "Content-type: text/html\n\n"; + print misctemplate("Error", "

Error: @_

"); + } + log_message(error => @_); + exit(1); +} #}}} + +sub debug ($) { #{{{ + return unless $config{verbose}; + log_message(debug => @_); +} #}}} + +my $log_open=0; +sub log_message ($$) { #{{{ + my $type=shift; + + if ($config{syslog}) { + require Sys::Syslog; + unless ($log_open) { + Sys::Syslog::setlogsock('unix'); + Sys::Syslog::openlog('ikiwiki', '', 'user'); + $log_open=1; + } + eval { + Sys::Syslog::syslog($type, join(" ", @_)); + } + } + elsif (! $config{cgi}) { + print "@_\n"; + } + else { + print STDERR "@_\n"; + } +} #}}} + +sub possibly_foolish_untaint ($) { #{{{ + my $tainted=shift; + my ($untainted)=$tainted=~/(.*)/; + return $untainted; +} #}}} + +sub basename ($) { #{{{ + my $file=shift; + + $file=~s!.*/+!!; + return $file; +} #}}} + +sub dirname ($) { #{{{ + my $file=shift; + + $file=~s!/*[^/]+$!!; + return $file; +} #}}} + +sub pagetype ($) { #{{{ + my $page=shift; + + if ($page =~ /\.([^.]+)$/) { + return $1 if exists $hooks{htmlize}{$1}; + } + return undef; +} #}}} + +sub pagename ($) { #{{{ + my $file=shift; + + my $type=pagetype($file); + my $page=$file; + $page=~s/\Q.$type\E*$// if defined $type; + return $page; +} #}}} + +sub htmlpage ($) { #{{{ + my $page=shift; + + return $page.".html"; +} #}}} + +sub srcfile ($) { #{{{ + my $file=shift; + + return "$config{srcdir}/$file" if -e "$config{srcdir}/$file"; + return "$config{underlaydir}/$file" if -e "$config{underlaydir}/$file"; + error("internal error: $file cannot be found"); +} #}}} + +sub readfile ($;$) { #{{{ + my $file=shift; + my $binary=shift; + + if (-l $file) { + error("cannot read a symlink ($file)"); + } + + local $/=undef; + open (IN, $file) || error("failed to read $file: $!"); + binmode(IN) if ($binary); + my $ret=; + close IN; + return $ret; +} #}}} + +sub writefile ($$$;$) { #{{{ + my $file=shift; # can include subdirs + my $destdir=shift; # directory to put file in + my $content=shift; + my $binary=shift; + + my $test=$file; + while (length $test) { + if (-l "$destdir/$test") { + error("cannot write to a symlink ($test)"); + } + $test=dirname($test); + } + + my $dir=dirname("$destdir/$file"); + if (! -d $dir) { + my $d=""; + foreach my $s (split(m!/+!, $dir)) { + $d.="$s/"; + if (! -d $d) { + mkdir($d) || error("failed to create directory $d: $!"); + } + } + } + + open (OUT, ">$destdir/$file") || error("failed to write $destdir/$file: $!"); + binmode(OUT) if ($binary); + print OUT $content; + close OUT; +} #}}} + +my %cleared; +sub will_render ($$;$) { #{{{ + my $page=shift; + my $dest=shift; + my $clear=shift; + + # Important security check. + if (-e "$config{destdir}/$dest" && ! $config{rebuild} && + ! grep { $_ eq $dest } (@{$renderedfiles{$page}}, @{$oldrenderedfiles{$page}})) { + error("$config{destdir}/$dest independently created, not overwriting with version from $page"); + } + + if (! $clear || $cleared{$page}) { + $renderedfiles{$page}=[$dest, grep { $_ ne $dest } @{$renderedfiles{$page}}]; + } + else { + $renderedfiles{$page}=[$dest]; + $cleared{$page}=1; + } +} #}}} + +sub bestlink ($$) { #{{{ + my $page=shift; + my $link=shift; + + my $cwd=$page; + do { + my $l=$cwd; + $l.="/" if length $l; + $l.=$link; + + if (exists $links{$l}) { + return $l; + } + elsif (exists $pagecase{lc $l}) { + return $pagecase{lc $l}; + } + } while $cwd=~s!/?[^/]+$!!; + + #print STDERR "warning: page $page, broken link: $link\n"; + return ""; +} #}}} + +sub isinlinableimage ($) { #{{{ + my $file=shift; + + $file=~/\.(png|gif|jpg|jpeg)$/i; +} #}}} + +sub pagetitle ($) { #{{{ + my $page=shift; + $page=~s/__(\d+)__/&#$1;/g; + $page=~y/_/ /; + return $page; +} #}}} + +sub titlepage ($) { #{{{ + my $title=shift; + $title=~y/ /_/; + $title=~s/([^-[:alnum:]_:+\/.])/"__".ord($1)."__"/eg; + return $title; +} #}}} + +sub cgiurl (@) { #{{{ + my %params=@_; + + return $config{cgiurl}."?".join("&", map "$_=$params{$_}", keys %params); +} #}}} + +sub baseurl (;$) { #{{{ + my $page=shift; + + return "$config{url}/" if ! defined $page; + + $page=~s/[^\/]+$//; + $page=~s/[^\/]+\//..\//g; + return $page; +} #}}} + +sub abs2rel ($$) { #{{{ + # Work around very innefficient behavior in File::Spec if abs2rel + # is passed two relative paths. It's much faster if paths are + # absolute! (Debian bug #376658) + my $path="/".shift; + my $base="/".shift; + + require File::Spec; + my $ret=File::Spec->abs2rel($path, $base); + $ret=~s/^// if defined $ret; + return $ret; +} #}}} + +sub displaytime ($) { #{{{ + my $time=shift; + + eval q{use POSIX}; + error($@) if $@; + # strftime doesn't know about encodings, so make sure + # its output is properly treated as utf8 + return decode_utf8(POSIX::strftime( + $config{timeformat}, localtime($time))); +} #}}} + +sub htmllink ($$$;$$$) { #{{{ + my $lpage=shift; # the page doing the linking + my $page=shift; # the page that will contain the link (different for inline) + my $link=shift; + my $noimageinline=shift; # don't turn links into inline html images + my $forcesubpage=shift; # force a link to a subpage + my $linktext=shift; # set to force the link text to something + + my $bestlink; + if (! $forcesubpage) { + $bestlink=bestlink($lpage, $link); + } + else { + $bestlink="$lpage/".lc($link); + } + + $linktext=pagetitle(basename($link)) unless defined $linktext; + + return "$linktext" + if length $bestlink && $page eq $bestlink; + + # TODO BUG: %renderedfiles may not have it, if the linked to page + # was also added and isn't yet rendered! Note that this bug is + # masked by the bug that makes all new files be rendered twice. + if (! grep { $_ eq $bestlink } map { @{$_} } values %renderedfiles) { + $bestlink=htmlpage($bestlink); + } + if (! grep { $_ eq $bestlink } map { @{$_} } values %renderedfiles) { + return " "create", page => lc($link), from => $page). + "\">?$linktext" + } + + $bestlink=abs2rel($bestlink, dirname($page)); + + if (! $noimageinline && isinlinableimage($bestlink)) { + return "\"$linktext\""; + } + return "$linktext"; +} #}}} + +sub htmlize ($$$) { #{{{ + my $page=shift; + my $type=shift; + my $content=shift; + + if (exists $hooks{htmlize}{$type}) { + $content=$hooks{htmlize}{$type}{call}->( + page => $page, + content => $content, + ); + } + else { + error("htmlization of $type not supported"); + } + + run_hooks(sanitize => sub { + $content=shift->( + page => $page, + content => $content, + ); + }); + + return $content; +} #}}} + +sub linkify ($$$) { #{{{ + my $lpage=shift; # the page containing the links + my $page=shift; # the page the link will end up on (different for inline) + my $content=shift; + + $content =~ s{(\\?)$config{wiki_link_regexp}}{ + $2 ? ( $1 ? "[[$2|$3]]" : htmllink($lpage, $page, titlepage($3), 0, 0, pagetitle($2))) + : ( $1 ? "[[$3]]" : htmllink($lpage, $page, titlepage($3))) + }eg; + + return $content; +} #}}} + +my %preprocessing; +sub preprocess ($$$;$) { #{{{ + my $page=shift; # the page the data comes from + my $destpage=shift; # the page the data will appear in (different for inline) + my $content=shift; + my $scan=shift; + + my $handle=sub { + my $escape=shift; + my $command=shift; + my $params=shift; + if (length $escape) { + return "[[$command $params]]"; + } + elsif (exists $hooks{preprocess}{$command}) { + return "" if $scan && ! $hooks{preprocess}{$command}{scan}; + # Note: preserve order of params, some plugins may + # consider it significant. + my @params; + while ($params =~ /(?:(\w+)=)?(?:"""(.*?)"""|"([^"]+)"|(\S+))(?:\s+|$)/sg) { + my $key=$1; + my $val; + if (defined $2) { + $val=$2; + $val=~s/\r\n/\n/mg; + $val=~s/^\n+//g; + $val=~s/\n+$//g; + } + elsif (defined $3) { + $val=$3; + } + elsif (defined $4) { + $val=$4; + } + + if (defined $key) { + push @params, $key, $val; + } + else { + push @params, $val, ''; + } + } + if ($preprocessing{$page}++ > 3) { + # Avoid loops of preprocessed pages preprocessing + # other pages that preprocess them, etc. + return "[[$command preprocessing loop detected on $page at depth $preprocessing{$page}]]"; + } + my $ret=$hooks{preprocess}{$command}{call}->( + @params, + page => $page, + destpage => $destpage, + ); + $preprocessing{$page}--; + return $ret; + } + else { + return "[[$command $params]]"; + } + }; + + $content =~ s{(\\?)\[\[(\w+)\s+((?:(?:\w+=)?(?:""".*?"""|"[^"]+"|[^\s\]]+)\s*)*)\]\]}{$handle->($1, $2, $3)}seg; + return $content; +} #}}} + +sub filter ($$) { #{{{ + my $page=shift; + my $content=shift; + + run_hooks(filter => sub { + $content=shift->(page => $page, content => $content); + }); + + return $content; +} #}}} + +sub indexlink () { #{{{ + return "$config{wikiname}"; +} #}}} + +sub lockwiki () { #{{{ + # Take an exclusive lock on the wiki to prevent multiple concurrent + # run issues. The lock will be dropped on program exit. + if (! -d $config{wikistatedir}) { + mkdir($config{wikistatedir}); + } + open(WIKILOCK, ">$config{wikistatedir}/lockfile") || + error ("cannot write to $config{wikistatedir}/lockfile: $!"); + if (! flock(WIKILOCK, 2 | 4)) { + debug("wiki seems to be locked, waiting for lock"); + my $wait=600; # arbitrary, but don't hang forever to + # prevent process pileup + for (1..600) { + return if flock(WIKILOCK, 2 | 4); + sleep 1; + } + error("wiki is locked; waited $wait seconds without lock being freed (possible stuck process or stale lock?)"); + } +} #}}} + +sub unlockwiki () { #{{{ + close WIKILOCK; +} #}}} + +sub loadindex () { #{{{ + open (IN, "$config{wikistatedir}/index") || return; + while () { + $_=possibly_foolish_untaint($_); + chomp; + my %items; + $items{link}=[]; + $items{dest}=[]; + foreach my $i (split(/ /, $_)) { + my ($item, $val)=split(/=/, $i, 2); + push @{$items{$item}}, decode_entities($val); + } + + next unless exists $items{src}; # skip bad lines for now + + my $page=pagename($items{src}[0]); + if (! $config{rebuild}) { + $pagesources{$page}=$items{src}[0]; + $oldpagemtime{$page}=$items{mtime}[0]; + $oldlinks{$page}=[@{$items{link}}]; + $links{$page}=[@{$items{link}}]; + $depends{$page}=$items{depends}[0] if exists $items{depends}; + $renderedfiles{$page}=[@{$items{dest}}]; + $oldrenderedfiles{$page}=[@{$items{dest}}]; + $pagecase{lc $page}=$page; + } + $pagectime{$page}=$items{ctime}[0]; + } + close IN; +} #}}} + +sub saveindex () { #{{{ + run_hooks(savestate => sub { shift->() }); + + if (! -d $config{wikistatedir}) { + mkdir($config{wikistatedir}); + } + open (OUT, ">$config{wikistatedir}/index") || + error("cannot write to $config{wikistatedir}/index: $!"); + foreach my $page (keys %oldpagemtime) { + next unless $oldpagemtime{$page}; + my $line="mtime=$oldpagemtime{$page} ". + "ctime=$pagectime{$page} ". + "src=$pagesources{$page}"; + $line.=" dest=$_" foreach @{$renderedfiles{$page}}; + my %count; + $line.=" link=$_" foreach grep { ++$count{$_} == 1 } @{$links{$page}}; + if (exists $depends{$page}) { + $line.=" depends=".encode_entities($depends{$page}, " \t\n"); + } + print OUT $line."\n"; + } + close OUT; +} #}}} + +sub template_params (@) { #{{{ + my $filename=shift; + + require HTML::Template; + return filter => sub { + my $text_ref = shift; + $$text_ref=&Encode::decode_utf8($$text_ref); + }, + filename => "$config{templatedir}/$filename", + loop_context_vars => 1, + die_on_bad_params => 0, + @_; +} #}}} + +sub template ($;@) { #{{{ + HTML::Template->new(template_params(@_)); +} #}}} + +sub misctemplate ($$;@) { #{{{ + my $title=shift; + my $pagebody=shift; + + my $template=template("misc.tmpl"); + $template->param( + title => $title, + indexlink => indexlink(), + wikiname => $config{wikiname}, + pagebody => $pagebody, + baseurl => baseurl(), + @_, + ); + run_hooks(pagetemplate => sub { + shift->(page => "", destpage => "", template => $template); + }); + return $template->output; +}#}}} + +sub hook (@) { # {{{ + my %param=@_; + + if (! exists $param{type} || ! ref $param{call} || ! exists $param{id}) { + error "hook requires type, call, and id parameters"; + } + + return if $param{no_override} && exists $hooks{$param{type}}{$param{id}}; + + $hooks{$param{type}}{$param{id}}=\%param; +} # }}} + +sub run_hooks ($$) { # {{{ + # Calls the given sub for each hook of the given type, + # passing it the hook function to call. + my $type=shift; + my $sub=shift; + + if (exists $hooks{$type}) { + foreach my $id (keys %{$hooks{$type}}) { + $sub->($hooks{$type}{$id}{call}); + } + } +} #}}} + +sub globlist_to_pagespec ($) { #{{{ + my @globlist=split(' ', shift); + + my (@spec, @skip); + foreach my $glob (@globlist) { + if ($glob=~/^!(.*)/) { + push @skip, $glob; + } + else { + push @spec, $glob; + } + } + + my $spec=join(" or ", @spec); + if (@skip) { + my $skip=join(" and ", @skip); + if (length $spec) { + $spec="$skip and ($spec)"; + } + else { + $spec=$skip; + } + } + return $spec; +} #}}} + +sub is_globlist ($) { #{{{ + my $s=shift; + $s=~/[^\s]+\s+([^\s]+)/ && $1 ne "and" && $1 ne "or"; +} #}}} + +sub safequote ($) { #{{{ + my $s=shift; + $s=~s/[{}]//g; + return "q{$s}"; +} #}}} + +sub pagespec_merge ($$) { #{{{ + my $a=shift; + my $b=shift; + + return $a if $a eq $b; + + # Support for old-style GlobLists. + if (is_globlist($a)) { + $a=globlist_to_pagespec($a); + } + if (is_globlist($b)) { + $b=globlist_to_pagespec($b); + } + + return "($a) or ($b)"; +} #}}} + +sub pagespec_translate ($) { #{{{ + # This assumes that $page is in scope in the function + # that evalulates the translated pagespec code. + my $spec=shift; + + # Support for old-style GlobLists. + if (is_globlist($spec)) { + $spec=globlist_to_pagespec($spec); + } + + # Convert spec to perl code. + my $code=""; + while ($spec=~m/\s*(\!|\(|\)|\w+\([^\)]+\)|[^\s()]+)\s*/ig) { + my $word=$1; + if (lc $word eq "and") { + $code.=" &&"; + } + elsif (lc $word eq "or") { + $code.=" ||"; + } + elsif ($word eq "(" || $word eq ")" || $word eq "!") { + $code.=" ".$word; + } + elsif ($word =~ /^(link|backlink|created_before|created_after|creation_month|creation_year|creation_day)\((.+)\)$/) { + $code.=" match_$1(\$page, ".safequote($2).")"; + } + else { + $code.=" match_glob(\$page, ".safequote($word).")"; + } + } + + return $code; +} #}}} + +sub add_depends ($$) { #{{{ + my $page=shift; + my $pagespec=shift; + + if (! exists $depends{$page}) { + $depends{$page}=$pagespec; + } + else { + $depends{$page}=pagespec_merge($depends{$page}, $pagespec); + } +} # }}} + +sub pagespec_match ($$) { #{{{ + my $page=shift; + my $spec=shift; + + return eval pagespec_translate($spec); +} #}}} + +sub match_glob ($$) { #{{{ + my $page=shift; + my $glob=shift; + + # turn glob into safe regexp + $glob=quotemeta($glob); + $glob=~s/\\\*/.*/g; + $glob=~s/\\\?/./g; + + return $page=~/^$glob$/i; +} #}}} + +sub match_link ($$) { #{{{ + my $page=shift; + my $link=lc(shift); + + my $links = $links{$page} or return undef; + foreach my $p (@$links) { + return 1 if lc $p eq $link; + } + return 0; +} #}}} + +sub match_backlink ($$) { #{{{ + match_link(pop, pop); +} #}}} + +sub match_created_before ($$) { #{{{ + my $page=shift; + my $testpage=shift; + + if (exists $pagectime{$testpage}) { + return $pagectime{$page} < $pagectime{$testpage}; + } + else { + return 0; + } +} #}}} + +sub match_created_after ($$) { #{{{ + my $page=shift; + my $testpage=shift; + + if (exists $pagectime{$testpage}) { + return $pagectime{$page} > $pagectime{$testpage}; + } + else { + return 0; + } +} #}}} + +sub match_creation_day ($$) { #{{{ + return ((gmtime($pagectime{shift()}))[3] == shift); +} #}}} + +sub match_creation_month ($$) { #{{{ + return ((gmtime($pagectime{shift()}))[4] + 1 == shift); +} #}}} + +sub match_creation_year ($$) { #{{{ + return ((gmtime($pagectime{shift()}))[5] + 1900 == shift); +} #}}} + +1 diff --git a/1.33.2/IkiWiki/CGI.pm b/1.33.2/IkiWiki/CGI.pm new file mode 100644 index 000000000..1e0ee01eb --- /dev/null +++ b/1.33.2/IkiWiki/CGI.pm @@ -0,0 +1,749 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use IkiWiki; +use IkiWiki::UserInfo; +use open qw{:utf8 :std}; +use Encode; + +package IkiWiki; + +sub printheader ($) { #{{{ + my $session=shift; + + if ($config{sslcookie}) { + print $session->header(-charset => 'utf-8', + -cookie => $session->cookie(-secure => 1)); + } else { + print $session->header(-charset => 'utf-8'); + } + +} #}}} + +sub redirect ($$) { #{{{ + my $q=shift; + my $url=shift; + if (! $config{w3mmode}) { + print $q->redirect($url); + } + else { + print "Content-type: text/plain\n"; + print "W3m-control: GOTO $url\n\n"; + } +} #}}} + +sub page_locked ($$;$) { #{{{ + my $page=shift; + my $session=shift; + my $nonfatal=shift; + + my $user=$session->param("name"); + return if defined $user && is_admin($user); + + foreach my $admin (@{$config{adminuser}}) { + my $locked_pages=userinfo_get($admin, "locked_pages"); + if (pagespec_match($page, userinfo_get($admin, "locked_pages"))) { + return 1 if $nonfatal; + error(htmllink("", "", $page, 1)." is locked by ". + htmllink("", "", $admin, 1)." and cannot be edited."); + } + } + + return 0; +} #}}} + +sub decode_form_utf8 ($) { #{{{ + my $form = shift; + foreach my $f ($form->field) { + next if Encode::is_utf8(scalar $form->field($f)); + $form->field(name => $f, + value => decode_utf8($form->field($f)), + force => 1, + ); + } +} #}}} + +sub cgi_recentchanges ($) { #{{{ + my $q=shift; + + unlockwiki(); + + # Optimisation: building recentchanges means calculating lots of + # links. Memoizing htmllink speeds it up a lot (can't be memoized + # during page builds as the return values may change, but they + # won't here.) + eval q{use Memoize}; + error($@) if $@; + memoize("htmllink"); + + eval q{use Time::Duration}; + error($@) if $@; + eval q{use CGI 'escapeHTML'}; + error($@) if $@; + + my $changelog=[rcs_recentchanges(100)]; + foreach my $change (@$changelog) { + $change->{when} = concise(ago($change->{when})); + $change->{user} = htmllink("", "", escapeHTML($change->{user}), 1); + + my $is_excess = exists $change->{pages}[10]; # limit pages to first 10 + delete @{$change->{pages}}[10 .. @{$change->{pages}}] if $is_excess; + $change->{pages} = [ + map { + $_->{link} = htmllink("", "", $_->{page}, 1); + $_; + } @{$change->{pages}} + ]; + push @{$change->{pages}}, { link => '...' } if $is_excess; + } + + my $template=template("recentchanges.tmpl"); + $template->param( + title => "RecentChanges", + indexlink => indexlink(), + wikiname => $config{wikiname}, + changelog => $changelog, + baseurl => baseurl(), + ); + run_hooks(pagetemplate => sub { + shift->(page => "", destpage => "", template => $template); + }); + print $q->header(-charset => 'utf-8'), $template->output; +} #}}} + +sub cgi_signin ($$) { #{{{ + my $q=shift; + my $session=shift; + + eval q{use CGI::FormBuilder}; + error($@) if $@; + my $form = CGI::FormBuilder->new( + title => "signin", + fields => [qw(do title page subpage from name password)], + header => 1, + charset => "utf-8", + method => 'POST', + validate => { + confirm_password => { + perl => q{eq $form->field("password")}, + }, + email => 'EMAIL', + }, + required => 'NONE', + javascript => 0, + params => $q, + action => $config{cgiurl}, + header => 0, + template => (-e "$config{templatedir}/signin.tmpl" ? + {template_params("signin.tmpl")} : ""), + stylesheet => baseurl()."style.css", + ); + + decode_form_utf8($form); + + $form->field(name => "name", required => 0); + $form->field(name => "do", type => "hidden"); + $form->field(name => "page", type => "hidden"); + $form->field(name => "title", type => "hidden"); + $form->field(name => "from", type => "hidden"); + $form->field(name => "subpage", type => "hidden"); + $form->field(name => "password", type => "password", required => 0); + if ($form->submitted eq "Register" || $form->submitted eq "Create Account") { + $form->title("register"); + $form->text(""); + $form->fields(qw(do title page subpage from name password confirm_password email)); + $form->field(name => "confirm_password", type => "password"); + $form->field(name => "email", type => "text"); + } + if ($q->param("do") ne "signin" && !$form->submitted) { + $form->text("You need to log in first."); + } + + if ($form->submitted) { + # Set required fields based on how form was submitted. + my %required=( + "Login" => [qw(name password)], + "Register" => [], + "Create Account" => [qw(name password confirm_password email)], + "Mail Password" => [qw(name)], + ); + foreach my $opt (@{$required{$form->submitted}}) { + $form->field(name => $opt, required => 1); + } + + # Validate password differently depending on how + # form was submitted. + if ($form->submitted eq 'Login') { + $form->field( + name => "password", + validate => sub { + length $form->field("name") && + shift eq userinfo_get($form->field("name"), 'password'); + }, + ); + $form->field(name => "name", validate => '/^\w+$/'); + } + else { + $form->field(name => "password", validate => 'VALUE'); + } + # And make sure the entered name exists when logging + # in or sending email, and does not when registering. + if ($form->submitted eq 'Create Account' || + $form->submitted eq 'Register') { + $form->field( + name => "name", + validate => sub { + my $name=shift; + length $name && + $name=~/$config{wiki_file_regexp}/ && + ! userinfo_get($name, "regdate"); + }, + ); + } + else { + $form->field( + name => "name", + validate => sub { + my $name=shift; + length $name && + userinfo_get($name, "regdate"); + }, + ); + } + } + else { + # First time settings. + $form->field(name => "name", comment => "use FirstnameLastName"); + if ($session->param("name")) { + $form->field(name => "name", value => $session->param("name")); + } + } + + if ($form->submitted && $form->validate) { + if ($form->submitted eq 'Login') { + $session->param("name", $form->field("name")); + if (defined $form->field("do") && + $form->field("do") ne 'signin') { + redirect($q, cgiurl( + do => $form->field("do"), + page => $form->field("page"), + title => $form->field("title"), + subpage => $form->field("subpage"), + from => $form->field("from"), + )); + } + else { + redirect($q, $config{url}); + } + } + elsif ($form->submitted eq 'Create Account') { + my $user_name=$form->field('name'); + if (userinfo_setall($user_name, { + 'email' => $form->field('email'), + 'password' => $form->field('password'), + 'regdate' => time + })) { + $form->field(name => "confirm_password", type => "hidden"); + $form->field(name => "email", type => "hidden"); + $form->text("Account creation successful. Now you can Login."); + printheader($session); + print misctemplate($form->title, $form->render(submit => ["Login"])); + } + else { + error("Error creating account."); + } + } + elsif ($form->submitted eq 'Mail Password') { + my $user_name=$form->field("name"); + my $template=template("passwordmail.tmpl"); + $template->param( + user_name => $user_name, + user_password => userinfo_get($user_name, "password"), + wikiurl => $config{url}, + wikiname => $config{wikiname}, + REMOTE_ADDR => $ENV{REMOTE_ADDR}, + ); + + eval q{use Mail::Sendmail}; + error($@) if $@; + sendmail( + To => userinfo_get($user_name, "email"), + From => "$config{wikiname} admin <$config{adminemail}>", + Subject => "$config{wikiname} information", + Message => $template->output, + ) or error("Failed to send mail"); + + $form->text("Your password has been emailed to you."); + $form->field(name => "name", required => 0); + printheader($session); + print misctemplate($form->title, $form->render(submit => ["Login", "Mail Password"])); + } + elsif ($form->submitted eq "Register") { + printheader($session); + print misctemplate($form->title, $form->render(submit => ["Create Account"])); + } + } + elsif ($form->submitted eq "Create Account") { + printheader($session); + print misctemplate($form->title, $form->render(submit => ["Create Account"])); + } + else { + printheader($session); + print misctemplate($form->title, $form->render(submit => ["Login", "Register", "Mail Password"])); + } +} #}}} + +sub cgi_prefs ($$) { #{{{ + my $q=shift; + my $session=shift; + + eval q{use CGI::FormBuilder}; + error($@) if $@; + my $form = CGI::FormBuilder->new( + title => "preferences", + fields => [qw(do name password confirm_password email + subscriptions locked_pages)], + header => 0, + charset => "utf-8", + method => 'POST', + validate => { + confirm_password => { + perl => q{eq $form->field("password")}, + }, + email => 'EMAIL', + }, + required => 'NONE', + javascript => 0, + params => $q, + action => $config{cgiurl}, + template => (-e "$config{templatedir}/prefs.tmpl" ? + {template_params("prefs.tmpl")} : ""), + stylesheet => baseurl()."style.css", + ); + my @buttons=("Save Preferences", "Logout", "Cancel"); + + my $user_name=$session->param("name"); + $form->field(name => "do", type => "hidden"); + $form->field(name => "name", disabled => 1, + value => $user_name, force => 1); + $form->field(name => "password", type => "password"); + $form->field(name => "confirm_password", type => "password"); + $form->field(name => "subscriptions", size => 50, + comment => "(".htmllink("", "", "PageSpec", 1).")"); + $form->field(name => "locked_pages", size => 50, + comment => "(".htmllink("", "", "PageSpec", 1).")"); + $form->field(name => "banned_users", size => 50); + + if (! is_admin($user_name)) { + $form->field(name => "locked_pages", type => "hidden"); + $form->field(name => "banned_users", type => "hidden"); + } + + if ($config{httpauth}) { + $form->field(name => "password", type => "hidden"); + $form->field(name => "confirm_password", type => "hidden"); + } + + if (! $form->submitted) { + $form->field(name => "email", force => 1, + value => userinfo_get($user_name, "email")); + $form->field(name => "subscriptions", force => 1, + value => userinfo_get($user_name, "subscriptions")); + $form->field(name => "locked_pages", force => 1, + value => userinfo_get($user_name, "locked_pages")); + if (is_admin($user_name)) { + $form->field(name => "banned_users", force => 1, + value => join(" ", get_banned_users())); + } + } + + decode_form_utf8($form); + + if ($form->submitted eq 'Logout') { + $session->delete(); + redirect($q, $config{url}); + return; + } + elsif ($form->submitted eq 'Cancel') { + redirect($q, $config{url}); + return; + } + elsif ($form->submitted eq "Save Preferences" && $form->validate) { + foreach my $field (qw(password email subscriptions locked_pages)) { + if (length $form->field($field)) { + userinfo_set($user_name, $field, $form->field($field)) || error("failed to set $field"); + } + } + if (is_admin($user_name)) { + set_banned_users(grep { ! is_admin($_) } + split(' ', $form->field("banned_users"))); + } + $form->text("Preferences saved."); + } + + printheader($session); + print misctemplate($form->title, $form->render(submit => \@buttons)); +} #}}} + +sub cgi_editpage ($$) { #{{{ + my $q=shift; + my $session=shift; + + my @fields=qw(do rcsinfo subpage from page type editcontent comments); + my @buttons=("Save Page", "Preview", "Cancel"); + + eval q{use CGI::FormBuilder; use CGI::FormBuilder::Template::HTML}; + error($@) if $@; + my $renderer=CGI::FormBuilder::Template::HTML->new( + fields => \@fields, + template_params("editpage.tmpl"), + ); + run_hooks(pagetemplate => sub { + shift->(page => "", destpage => "", template => $renderer->engine); + }); + my $form = CGI::FormBuilder->new( + fields => \@fields, + header => 1, + charset => "utf-8", + method => 'POST', + validate => { + editcontent => '/.+/', + }, + required => [qw{editcontent}], + javascript => 0, + params => $q, + action => $config{cgiurl}, + table => 0, + template => $renderer, + ); + + decode_form_utf8($form); + + # This untaint is safe because titlepage removes any problematic + # characters. + my ($page)=$form->field('page'); + $page=titlepage(possibly_foolish_untaint($page)); + if (! defined $page || ! length $page || + $page=~/$config{wiki_file_prune_regexp}/ || $page=~/^\//) { + error("bad page name"); + } + + my $from; + if (defined $form->field('from')) { + ($from)=$form->field('from')=~/$config{wiki_file_regexp}/; + } + + my $file; + my $type; + if (exists $pagesources{$page}) { + $file=$pagesources{$page}; + $type=pagetype($file); + if (! defined $type) { + error("$page is not an editable page"); + } + } + else { + $type=$form->param('type'); + if (defined $type && length $type && $hooks{htmlize}{$type}) { + $type=possibly_foolish_untaint($type); + } + elsif (defined $from) { + # favor the type of linking page + $type=pagetype($pagesources{$from}); + } + $type=$config{default_pageext} unless defined $type; + $file=$page.".".$type; + } + + my $newfile=0; + if (! -e "$config{srcdir}/$file") { + $newfile=1; + } + + $form->field(name => "do", type => 'hidden'); + $form->field(name => "from", type => 'hidden'); + $form->field(name => "rcsinfo", type => 'hidden'); + $form->field(name => "subpage", type => 'hidden'); + $form->field(name => "page", value => $page, force => 1); + $form->field(name => "type", value => $type, force => 1); + $form->field(name => "comments", type => "text", size => 80); + $form->field(name => "editcontent", type => "textarea", rows => 20, + cols => 80); + $form->tmpl_param("can_commit", $config{rcs}); + $form->tmpl_param("indexlink", indexlink()); + $form->tmpl_param("helponformattinglink", + htmllink("", "", "HelpOnFormatting", 1)); + $form->tmpl_param("baseurl", baseurl()); + if (! $form->submitted) { + $form->field(name => "rcsinfo", value => rcs_prepedit($file), + force => 1); + } + + if ($form->submitted eq "Cancel") { + if ($newfile && defined $from) { + redirect($q, "$config{url}/".htmlpage($from)); + } + elsif ($newfile) { + redirect($q, $config{url}); + } + else { + redirect($q, "$config{url}/".htmlpage($page)); + } + return; + } + elsif ($form->submitted eq "Preview") { + my $content=$form->field('editcontent'); + my $comments=$form->field('comments'); + $form->field(name => "editcontent", + value => $content, force => 1); + $form->field(name => "comments", + value => $comments, force => 1); + $config{rss}=$config{atom}=0; # avoid preview writing a feed! + $form->tmpl_param("page_preview", + htmlize($page, $type, + linkify($page, "", + preprocess($page, $page, + filter($page, $content))))); + } + else { + $form->tmpl_param("page_preview", ""); + } + $form->tmpl_param("page_conflict", ""); + + if (! $form->submitted || $form->submitted eq "Preview" || + ! $form->validate) { + if ($form->field("do") eq "create") { + my @page_locs; + my $best_loc; + if (! defined $from || ! length $from || + $from ne $form->field('from') || + $from=~/$config{wiki_file_prune_regexp}/ || + $from=~/^\// || + $form->submitted eq "Preview") { + @page_locs=$best_loc=$page; + } + else { + my $dir=$from."/"; + $dir=~s![^/]+/+$!!; + + if ((defined $form->field('subpage') && length $form->field('subpage')) || + $page eq 'discussion') { + $best_loc="$from/$page"; + } + else { + $best_loc=$dir.$page; + } + + push @page_locs, $dir.$page; + push @page_locs, "$from/$page"; + while (length $dir) { + $dir=~s![^/]+/+$!!; + push @page_locs, $dir.$page; + } + } + + @page_locs = grep { + ! exists $pagecase{lc $_} && + ! page_locked($_, $session, 1) + } @page_locs; + + if (! @page_locs) { + # hmm, someone else made the page in the + # meantime? + redirect($q, "$config{url}/".htmlpage($page)); + return; + } + + my @page_types; + if (exists $hooks{htmlize}) { + @page_types=keys %{$hooks{htmlize}}; + } + + $form->tmpl_param("page_select", 1); + $form->field(name => "page", type => 'select', + options => \@page_locs, value => $best_loc); + $form->field(name => "type", type => 'select', + options => \@page_types); + $form->title("creating ".pagetitle($page)); + } + elsif ($form->field("do") eq "edit") { + page_locked($page, $session); + if (! defined $form->field('editcontent') || + ! length $form->field('editcontent')) { + my $content=""; + if (exists $pagesources{$page}) { + $content=readfile(srcfile($pagesources{$page})); + $content=~s/\n/\r\n/g; + } + $form->field(name => "editcontent", value => $content, + force => 1); + } + $form->tmpl_param("page_select", 0); + $form->field(name => "page", type => 'hidden'); + $form->field(name => "type", type => 'hidden'); + $form->title("editing ".pagetitle($page)); + } + + print $form->render(submit => \@buttons); + } + else { + # save page + page_locked($page, $session); + + my $content=$form->field('editcontent'); + + $content=~s/\r\n/\n/g; + $content=~s/\r/\n/g; + writefile($file, $config{srcdir}, $content); + + my $message="web commit "; + if (defined $session->param("name") && + length $session->param("name")) { + $message.="by ".$session->param("name"); + } + else { + $message.="from $ENV{REMOTE_ADDR}"; + } + if (defined $form->field('comments') && + length $form->field('comments')) { + $message.=": ".$form->field('comments'); + } + + if ($config{rcs}) { + if ($newfile) { + rcs_add($file); + } + # prevent deadlock with post-commit hook + unlockwiki(); + # presumably the commit will trigger an update + # of the wiki + my $conflict=rcs_commit($file, $message, + $form->field("rcsinfo")); + + if (defined $conflict) { + $form->field(name => "rcsinfo", value => rcs_prepedit($file), + force => 1); + $form->tmpl_param("page_conflict", 1); + $form->field("editcontent", value => $conflict, force => 1); + $form->field(name => "comments", value => $form->field('comments'), force => 1); + $form->field("do", "edit)"); + $form->tmpl_param("page_select", 0); + $form->field(name => "page", type => 'hidden'); + $form->field(name => "type", type => 'hidden'); + $form->title("editing $page"); + print $form->render(submit => \@buttons); + return; + } + } + else { + require IkiWiki::Render; + refresh(); + saveindex(); + } + + # The trailing question mark tries to avoid broken + # caches and get the most recent version of the page. + redirect($q, "$config{url}/".htmlpage($page)."?updated"); + } +} #}}} + +sub cgi () { #{{{ + eval q{use CGI; use CGI::Session}; + error($@) if $@; + + my $q=CGI->new; + + run_hooks(cgi => sub { shift->($q) }); + + my $do=$q->param('do'); + if (! defined $do || ! length $do) { + my $error = $q->cgi_error; + if ($error) { + error("Request not processed: $error"); + } + else { + error("\"do\" parameter missing"); + } + } + + # Things that do not need a session. + if ($do eq 'recentchanges') { + cgi_recentchanges($q); + return; + } + elsif ($do eq 'hyperestraier') { + cgi_hyperestraier(); + } + + CGI::Session->name("ikiwiki_session_".encode_utf8($config{wikiname})); + + my $oldmask=umask(077); + my $session = CGI::Session->new("driver:DB_File", $q, + { FileName => "$config{wikistatedir}/sessions.db" }); + umask($oldmask); + + # Everything below this point needs the user to be signed in. + if (((! $config{anonok} || $do eq 'prefs') && + (! $config{httpauth}) && + (! defined $session->param("name") || + ! userinfo_get($session->param("name"), "regdate"))) || $do eq 'signin') { + cgi_signin($q, $session); + + # Force session flush with safe umask. + my $oldmask=umask(077); + $session->flush; + umask($oldmask); + + return; + } + + if ($config{httpauth} && (! defined $session->param("name"))) { + if (! defined $q->remote_user()) { + error("Could not determine authenticated username."); + } + else { + $session->param("name", $q->remote_user()); + if (! userinfo_get($session->param("name"), "regdate")) { + userinfo_setall($session->param("name"), { + email => "", + password => "", + regdate=>time, + }); + } + } + } + + if (defined $session->param("name") && userinfo_get($session->param("name"), "banned")) { + print $q->header(-status => "403 Forbidden"); + $session->delete(); + print "You are banned."; + exit; + } + + if ($do eq 'create' || $do eq 'edit') { + cgi_editpage($q, $session); + } + elsif ($do eq 'prefs') { + cgi_prefs($q, $session); + } + elsif ($do eq 'blog') { + my $page=titlepage(decode_utf8($q->param('title'))); + # if the page already exists, munge it to be unique + my $from=$q->param('from'); + my $add=""; + while (exists $pagecase{lc "$from/$page$add"}) { + $add=1 unless length $add; + $add++; + } + $q->param('page', $page.$add); + # now run same as create + $q->param('do', 'create'); + cgi_editpage($q, $session); + } + else { + error("unknown do parameter"); + } +} #}}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/aggregate.pm b/1.33.2/IkiWiki/Plugin/aggregate.pm new file mode 100644 index 000000000..2a3f36fce --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/aggregate.pm @@ -0,0 +1,416 @@ +#!/usr/bin/perl +# Blog aggregation plugin. +package IkiWiki::Plugin::aggregate; + +use warnings; +use strict; +use IkiWiki; +use HTML::Entities; +use HTML::Parser; +use HTML::Tagset; +use URI; +use open qw{:utf8 :std}; + +my %feeds; +my %guids; + +sub import { #{{{ + hook(type => "getopt", id => "aggregate", call => \&getopt); + hook(type => "checkconfig", id => "aggregate", call => \&checkconfig); + hook(type => "filter", id => "aggregate", call => \&filter); + hook(type => "preprocess", id => "aggregate", call => \&preprocess); + hook(type => "delete", id => "aggregate", call => \&delete); + hook(type => "savestate", id => "aggregate", call => \&savestate); +} # }}} + +sub getopt () { #{{{ + eval q{use Getopt::Long}; + error($@) if $@; + Getopt::Long::Configure('pass_through'); + GetOptions("aggregate" => \$config{aggregate}); +} #}}} + +sub checkconfig () { #{{{ + IkiWiki::lockwiki(); + loadstate(); + if ($config{aggregate}) { + IkiWiki::loadindex(); + aggregate(); + expire(); + savestate(); + } + IkiWiki::unlockwiki(); +} #}}} + +sub filter (@) { #{{{ + my %params=@_; + my $page=$params{page}; + + # Mark all feeds originating on this page as removable; + # preprocess will unmark those that still exist. + remove_feeds($page); + + return $params{content}; +} # }}} + +sub preprocess (@) { #{{{ + my %params=@_; + + foreach my $required (qw{name url}) { + if (! exists $params{$required}) { + return "[[aggregate plugin missing $required parameter]]"; + } + } + + my $feed={}; + my $name=$params{name}; + if (exists $feeds{$name}) { + $feed=$feeds{$name}; + } + else { + $feeds{$name}=$feed; + } + $feed->{name}=$name; + $feed->{sourcepage}=$params{page}; + $feed->{url}=$params{url}; + my $dir=exists $params{dir} ? $params{dir} : $params{page}."/".IkiWiki::titlepage($params{name}); + $dir=~s/^\/+//; + ($dir)=$dir=~/$config{wiki_file_regexp}/; + $feed->{dir}=$dir; + $feed->{feedurl}=defined $params{feedurl} ? $params{feedurl} : ""; + $feed->{updateinterval}=defined $params{updateinterval} ? $params{updateinterval} * 60 : 15 * 60; + $feed->{expireage}=defined $params{expireage} ? $params{expireage} : 0; + $feed->{expirecount}=defined $params{expirecount} ? $params{expirecount} : 0; + delete $feed->{remove}; + delete $feed->{expired}; + $feed->{lastupdate}=0 unless defined $feed->{lastupdate}; + $feed->{numposts}=0 unless defined $feed->{numposts}; + $feed->{newposts}=0 unless defined $feed->{newposts}; + $feed->{message}="new feed" unless defined $feed->{message}; + $feed->{error}=0 unless defined $feed->{error}; + $feed->{tags}=[]; + while (@_) { + my $key=shift; + my $value=shift; + if ($key eq 'tag') { + push @{$feed->{tags}}, $value; + } + } + + return "{url}."\">".$feed->{name}.": ". + ($feed->{error} ? "" : "").$feed->{message}. + ($feed->{error} ? "" : ""). + " (".$feed->{numposts}." posts". + ($feed->{newposts} ? "; ".$feed->{newposts}." new" : ""). + ")"; +} # }}} + +sub delete (@) { #{{{ + my @files=@_; + + # Remove feed data for removed pages. + foreach my $file (@files) { + my $page=pagename($file); + remove_feeds($page); + } +} #}}} + +sub loadstate () { #{{{ + if (-e "$config{wikistatedir}/aggregate") { + open (IN, "$config{wikistatedir}/aggregate" || + die "$config{wikistatedir}/aggregate: $!"); + while () { + $_=IkiWiki::possibly_foolish_untaint($_); + chomp; + my $data={}; + foreach my $i (split(/ /, $_)) { + my ($field, $val)=split(/=/, $i, 2); + if ($field eq "name" || $field eq "feed" || + $field eq "guid" || $field eq "message") { + $data->{$field}=decode_entities($val, " \t\n"); + } + elsif ($field eq "tag") { + push @{$data->{tags}}, $val; + } + else { + $data->{$field}=$val; + } + } + + if (exists $data->{name}) { + $feeds{$data->{name}}=$data; + } + elsif (exists $data->{guid}) { + $guids{$data->{guid}}=$data; + } + } + + close IN; + } +} #}}} + +sub savestate () { #{{{ + eval q{use HTML::Entities}; + error($@) if $@; + open (OUT, ">$config{wikistatedir}/aggregate" || + die "$config{wikistatedir}/aggregate: $!"); + foreach my $data (values %feeds, values %guids) { + if ($data->{remove}) { + if ($data->{name}) { + foreach my $guid (values %guids) { + if ($guid->{feed} eq $data->{name}) { + $guid->{remove}=1; + } + } + } + else { + unlink pagefile($data->{page}); + } + next; + } + elsif ($data->{expired} && exists $data->{page}) { + unlink pagefile($data->{page}); + delete $data->{page}; + delete $data->{md5}; + } + + my @line; + foreach my $field (keys %$data) { + if ($field eq "name" || $field eq "feed" || + $field eq "guid" || $field eq "message") { + push @line, "$field=".encode_entities($data->{$field}, " \t\n"); + } + elsif ($field eq "tags") { + push @line, "tag=$_" foreach @{$data->{tags}}; + } + else { + push @line, "$field=".$data->{$field}; + } + } + print OUT join(" ", @line)."\n"; + } + close OUT; +} #}}} + +sub expire () { #{{{ + foreach my $feed (values %feeds) { + next unless $feed->{expireage} || $feed->{expirecount}; + my $count=0; + foreach my $item (sort { $IkiWiki::pagectime{$b->{page}} <=> $IkiWiki::pagectime{$a->{page}} } + grep { exists $_->{page} && $_->{feed} eq $feed->{name} && $IkiWiki::pagectime{$_->{page}} } + values %guids) { + if ($feed->{expireage}) { + my $days_old = (time - $IkiWiki::pagectime{$item->{page}}) / 60 / 60 / 24; + if ($days_old > $feed->{expireage}) { + debug("expiring ".$item->{page}." ($days_old days old)"); + $item->{expired}=1; + } + } + elsif ($feed->{expirecount} && + $count >= $feed->{expirecount}) { + debug("expiring ".$item->{page}); + $item->{expired}=1; + } + else { + $count++; + } + } + } +} #}}} + +sub aggregate () { #{{{ + eval q{use XML::Feed}; + error($@) if $@; + eval q{use HTML::Entities}; + error($@) if $@; + + foreach my $feed (values %feeds) { + next unless $config{rebuild} || + time - $feed->{lastupdate} >= $feed->{updateinterval}; + $feed->{lastupdate}=time; + $feed->{newposts}=0; + $IkiWiki::forcerebuild{$feed->{sourcepage}}=1; + + debug("checking feed ".$feed->{name}." ..."); + + if (! length $feed->{feedurl}) { + my @urls=XML::Feed->find_feeds($feed->{url}); + if (! @urls) { + $feed->{message}="could not find feed at ".$feed->{feedurl}; + $feed->{error}=1; + debug($feed->{message}); + next; + } + $feed->{feedurl}=pop @urls; + } + my $f=eval{XML::Feed->parse(URI->new($feed->{feedurl}))}; + if ($@) { + $feed->{message}="feed crashed XML::Feed! $@"; + $feed->{error}=1; + debug($feed->{message}); + next; + } + if (! $f) { + $feed->{message}=XML::Feed->errstr; + $feed->{error}=1; + debug($feed->{message}); + next; + } + + foreach my $entry ($f->entries) { + add_page( + feed => $feed, + title => defined $entry->title ? decode_entities($entry->title) : "untitled", + link => $entry->link, + content => $entry->content->body, + guid => defined $entry->id ? $entry->id : time."_".$feed->name, + ctime => $entry->issued ? ($entry->issued->epoch || time) : time, + ); + } + + $feed->{message}="processed ok at ". + displaytime($feed->{lastupdate}); + $feed->{error}=0; + } +} #}}} + +sub add_page (@) { #{{{ + my %params=@_; + + my $feed=$params{feed}; + my $guid={}; + my $mtime; + if (exists $guids{$params{guid}}) { + # updating an existing post + $guid=$guids{$params{guid}}; + return if $guid->{expired}; + } + else { + # new post + $guid->{guid}=$params{guid}; + $guids{$params{guid}}=$guid; + $mtime=$params{ctime}; + $feed->{numposts}++; + $feed->{newposts}++; + + # assign it an unused page + my $page=IkiWiki::titlepage($params{title}); + # escape slashes and periods in title so it doesn't specify + # directory name or trigger ".." disallowing code. + $page=~s!([/.])!"__".ord($1)."__"!eg; + $page=$feed->{dir}."/".$page; + ($page)=$page=~/$config{wiki_file_regexp}/; + if (! defined $page || ! length $page) { + $page=$feed->{dir}."/item"; + } + my $c=""; + while (exists $IkiWiki::pagecase{lc $page.$c} || + -e pagefile($page.$c)) { + $c++ + } + $guid->{page}=$page; + debug("creating new page $page"); + } + $guid->{feed}=$feed->{name}; + + # To write or not to write? Need to avoid writing unchanged pages + # to avoid unneccessary rebuilding. The mtime from rss cannot be + # trusted; let's use a digest. + eval q{use Digest::MD5 'md5_hex'}; + error($@) if $@; + require Encode; + my $digest=md5_hex(Encode::encode_utf8($params{content})); + return unless ! exists $guid->{md5} || $guid->{md5} ne $digest || $config{rebuild}; + $guid->{md5}=$digest; + + # Create the page. + my $template=template("aggregatepost.tmpl", blind_cache => 1); + $template->param(title => $params{title}) + if defined $params{title} && length($params{title}); + $template->param(content => htmlescape(htmlabs($params{content}, $feed->{feedurl}))); + $template->param(name => $feed->{name}); + $template->param(url => $feed->{url}); + $template->param(permalink => urlabs($params{link}, $feed->{feedurl})) + if defined $params{link}; + if (ref $feed->{tags}) { + $template->param(tags => [map { tag => $_ }, @{$feed->{tags}}]); + } + writefile(htmlpage($guid->{page}), $config{srcdir}, + $template->output); + + # Set the mtime, this lets the build process get the right creation + # time on record for the new page. + utime $mtime, $mtime, pagefile($guid->{page}) if defined $mtime; +} #}}} + +sub htmlescape ($) { #{{{ + # escape accidental wikilinks and preprocessor stuff + my $html=shift; + $html=~s/(?new_abs($url, $urlbase)->as_string; +} #}}} + +sub htmlabs ($$) { #{{{ + # Convert links in html from relative to absolute. + # Note that this is a heuristic, which is not specified by the rss + # spec and may not be right for all feeds. Also, see Debian + # bug #381359. + my $html=shift; + my $urlbase=shift; + + my $ret=""; + my $p = HTML::Parser->new(api_version => 3); + $p->handler(default => sub { $ret.=join("", @_) }, "text"); + $p->handler(start => sub { + my ($tagname, $pos, $text) = @_; + if (ref $HTML::Tagset::linkElements{$tagname}) { + while (4 <= @$pos) { + # use attribute sets from right to left + # to avoid invalidating the offsets + # when replacing the values + my($k_offset, $k_len, $v_offset, $v_len) = + splice(@$pos, -4); + my $attrname = lc(substr($text, $k_offset, $k_len)); + next unless grep { $_ eq $attrname } @{$HTML::Tagset::linkElements{$tagname}}; + next unless $v_offset; # 0 v_offset means no value + my $v = substr($text, $v_offset, $v_len); + $v =~ s/^([\'\"])(.*)\1$/$2/; + my $new_v=urlabs($v, $urlbase); + $new_v =~ s/\"/"/g; # since we quote with "" + substr($text, $v_offset, $v_len) = qq("$new_v"); + } + } + $ret.=$text; + }, "tagname, tokenpos, text"); + $p->parse($html); + $p->eof; + + return $ret; +} #}}} + +sub remove_feeds () { #{{{ + my $page=shift; + + my %removed; + foreach my $id (keys %feeds) { + if ($feeds{$id}->{sourcepage} eq $page) { + $feeds{$id}->{remove}=1; + $removed{$id}=1; + } + } +} #}}} + +sub pagefile ($) { #{{{ + my $page=shift; + + return "$config{srcdir}/".htmlpage($page); +} #}}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/brokenlinks.pm b/1.33.2/IkiWiki/Plugin/brokenlinks.pm new file mode 100644 index 000000000..6741f9081 --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/brokenlinks.pm @@ -0,0 +1,41 @@ +#!/usr/bin/perl +# Provides a list of broken links. +package IkiWiki::Plugin::brokenlinks; + +use warnings; +use strict; +use IkiWiki; + +sub import { #{{{ + hook(type => "preprocess", id => "brokenlinks", call => \&preprocess); +} # }}} + +sub preprocess (@) { #{{{ + my %params=@_; + $params{pages}="*" unless defined $params{pages}; + + # Needs to update whenever a page is added or removed, so + # register a dependency. + add_depends($params{page}, $params{pages}); + + my @broken; + foreach my $page (keys %links) { + if (pagespec_match($page, $params{pages})) { + foreach my $link (@{$links{$page}}) { + next if $link =~ /.*\/discussion/i && $config{discussion}; + my $bestlink=bestlink($page, $link); + next if length $bestlink; + push @broken, + htmllink($page, $params{destpage}, $link, 1). + " from ". + htmllink($params{page}, $params{destpage}, $page, 1); + } + } + } + + return "There are no broken links!" unless @broken; + my %seen; + return "
    \n".join("\n", map { "
  • $_
  • " } grep { ! $seen{$_}++ } sort @broken)."
\n"; +} # }}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/camelcase.pm b/1.33.2/IkiWiki/Plugin/camelcase.pm new file mode 100644 index 000000000..0bb35b6e2 --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/camelcase.pm @@ -0,0 +1,23 @@ +#!/usr/bin/perl +# CamelCase links +package IkiWiki::Plugin::camelcase; + +use IkiWiki; +use warnings; +use strict; + +sub import { #{{{ + hook(type => "filter", id => "camelcase", call => \&filter); +} # }}} + +sub filter (@) { #{{{ + my %params=@_; + + # Make CamelCase links work by promoting them to fullfledged + # WikiLinks. This regexp is based on the one in Text::WikiFormat. + $params{content}=~s#(?=])\b((?:[A-Z][a-z0-9]\w*){2,})#[[$1]]#g; + + return $params{content}; +} #}}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/ddate.pm b/1.33.2/IkiWiki/Plugin/ddate.pm new file mode 100644 index 000000000..862d4da5b --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/ddate.pm @@ -0,0 +1,32 @@ +#!/usr/bin/perl +# Discordian date support fnord ikiwiki. +package IkiWiki::Plugin::ddate; +use IkiWiki; +no warnings; + +sub import { #{{{ + hook(type => "checkconfig", id => "skeleton", call => \&checkconfig); +} # }}} + +sub checkconfig () { #{{{ + if (! defined $config{timeformat} || + $config{timeformat} eq '%c') { + $config{timeformat}='on %A, the %e of %B, %Y. %N%nCelebrate %H'; + } +} #}}} + +sub IkiWiki::displaytime ($) { #{{{ + my $time=shift; + eval q{ + use DateTime; + use DateTime::Calendar::Discordian; + }; + if ($@) { + return "some time or other ($@ -- hail Eris!)"; + } + my $dt = DateTime->from_epoch(epoch => $time); + my $dd = DateTime::Calendar::Discordian->from_object(object => $dt); + return $dd->strftime($IkiWiki::config{timeformat}); +} #}}} + +5 diff --git a/1.33.2/IkiWiki/Plugin/favicon.pm b/1.33.2/IkiWiki/Plugin/favicon.pm new file mode 100644 index 000000000..864131d1e --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/favicon.pm @@ -0,0 +1,24 @@ +#!/usr/bin/perl +# favicon plugin. + +package IkiWiki::Plugin::favicon; + +use warnings; +use strict; +use IkiWiki; + +sub import { #{{{ + hook(type => "pagetemplate", id => "favicon", call => \&pagetemplate); +} # }}} + +sub pagetemplate (@) { #{{{ + my %params=@_; + + my $template=$params{template}; + + if ($template->query(name => "favicon")) { + $template->param(favicon => "favicon.ico"); + } +} # }}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/fortune.pm b/1.33.2/IkiWiki/Plugin/fortune.pm new file mode 100644 index 000000000..43f4526af --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/fortune.pm @@ -0,0 +1,25 @@ +#!/usr/bin/perl +# Include a fortune in a page +package IkiWiki::Plugin::fortune; + +use IkiWiki; +use warnings; +use strict; + +sub import { #{{{ + hook(type => "preprocess", id => "fortune", call => \&preprocess); +} # }}} + +sub preprocess (@) { #{{{ + $ENV{PATH}="$ENV{PATH}:/usr/games:/usr/local/games"; + my $f = `fortune`; + + if ($?) { + return "[[fortune failed]]"; + } + else { + return "
$f
\n"; + } +} # }}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/googlecalendar.pm b/1.33.2/IkiWiki/Plugin/googlecalendar.pm new file mode 100644 index 000000000..dc0df0ad3 --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/googlecalendar.pm @@ -0,0 +1,45 @@ +#!/usr/bin/perl +package IkiWiki::Plugin::googlecalendar; + +use warnings; +use strict; +use IkiWiki; + +sub import { #{{{ + hook(type => "preprocess", id => "googlecalendar", + call => \&preprocess); + hook(type => "format", id => "googlecalendar", + call => \&format); +} # }}} + +sub preprocess (@) { #{{{ + my %params=@_; + + # Parse the html, looking for the url to embed for the calendar. + # Avoid XSS attacks.. + my ($url)=$params{html}=~m#iframe\s+src="http://www\.google\.com/calendar/embed\?([^"<>]+)"#; + if (! defined $url || ! length $url) { + return "[[googlecalendar failed to find url in html]]"; + } + my ($height)=$params{html}=~m#height="(\d+)"#; + my ($width)=$params{html}=~m#width="(\d+)"#; + + return "
"; +} # }}} + +sub format (@) { #{{{ + my %params=@_; + + $params{content}=~s/
<\/div>/gencal($1,$2,$3)/eg; + + return $params{content}; +} # }}} + +sub gencal ($$$) { #{{{ + my $url=shift; + my $height=shift; + my $width=shift; + return qq{}; +} #}}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/haiku.pm b/1.33.2/IkiWiki/Plugin/haiku.pm new file mode 100644 index 000000000..fe8a782fa --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/haiku.pm @@ -0,0 +1,50 @@ +#!/usr/bin/perl +# haiku generator plugin +package IkiWiki::Plugin::haiku; + +use warnings; +use strict; +use IkiWiki; + +sub import { #{{{ + hook(type => "preprocess", id => "haiku", call => \&preprocess); +} # }}} + +sub preprocess (@) { #{{{ + my %params=@_; + + my $haiku; + eval q{use Coy}; + if ($@ || ! Coy->can("Coy::with_haiku")) { + my @canned=( + "The lack of a Coy: + No darting, subtle haiku. + Instead, canned tuna. + ", + "apt-get install Coy + no, wait, that's not quite it + instead: libcoy-perl + ", + "Coyly I'll do it, + no code, count Five-Seven-Five + to make a haiku. + ", + ); + + $haiku=$canned[rand @canned]; + } + else { + $haiku=Coy::with_haiku($params{hint} ? $params{hint} : $params{page}); + + # trim off other text + $haiku=~s/\s+-----\n//s; + $haiku=~s/\s+-----.*//s; + } + + $haiku=~s/^\s+//mg; + $haiku=~s/\n/
\n/mg; + + return "\n\n
$haiku
\n\n"; +} # }}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/html.pm b/1.33.2/IkiWiki/Plugin/html.pm new file mode 100644 index 000000000..4270a7eb6 --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/html.pm @@ -0,0 +1,23 @@ +#!/usr/bin/perl +# Raw html as a wiki page type. +package IkiWiki::Plugin::html; + +use warnings; +use strict; +use IkiWiki; + +sub import { #{{{ + hook(type => "htmlize", id => "html", call => \&htmlize); + hook(type => "htmlize", id => "htm", call => \&htmlize); + + # ikiwiki defaults to skipping .html files as a security measure; + # make it process them so this plugin can take effect + $config{wiki_file_prune_regexp} =~ s/\|\\\.x\?html\?\$//; +} # }}} + +sub htmlize (@) { #{{{ + my %params=@_; + return $params{content}; +} #}}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/htmlscrubber.pm b/1.33.2/IkiWiki/Plugin/htmlscrubber.pm new file mode 100644 index 000000000..ae3ec4456 --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/htmlscrubber.pm @@ -0,0 +1,53 @@ +#!/usr/bin/perl +package IkiWiki::Plugin::htmlscrubber; + +use warnings; +use strict; +use IkiWiki; + +sub import { #{{{ + hook(type => "sanitize", id => "htmlscrubber", call => \&sanitize); +} # }}} + +sub sanitize (@) { #{{{ + my %params=@_; + return scrubber()->scrub($params{content}); +} # }}} + +my $_scrubber; +sub scrubber { #{{{ + return $_scrubber if defined $_scrubber; + + eval q{use HTML::Scrubber}; + error($@) if $@; + # Lists based on http://feedparser.org/docs/html-sanitization.html + $_scrubber = HTML::Scrubber->new( + allow => [qw{ + a abbr acronym address area b big blockquote br + button caption center cite code col colgroup dd del + dfn dir div dl dt em fieldset font form h1 h2 h3 h4 + h5 h6 hr i img input ins kbd label legend li map + menu ol optgroup option p pre q s samp select small + span strike strong sub sup table tbody td textarea + tfoot th thead tr tt u ul var + }], + default => [undef, { map { $_ => 1 } qw{ + abbr accept accept-charset accesskey action + align alt axis border cellpadding cellspacing + char charoff charset checked cite class + clear cols colspan color compact coords + datetime dir disabled enctype for frame + headers height href hreflang hspace id ismap + label lang longdesc maxlength media method + multiple name nohref noshade nowrap prompt + readonly rel rev rows rowspan rules scope + selected shape size span src start summary + tabindex target title type usemap valign + value vspace width + }, "/" => 1, # emit proper
XHTML + }], + ); + return $_scrubber; +} # }}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/htmltidy.pm b/1.33.2/IkiWiki/Plugin/htmltidy.pm new file mode 100644 index 000000000..0609e72c3 --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/htmltidy.pm @@ -0,0 +1,45 @@ +#!/usr/bin/perl +# HTML Tidy plugin +# requires 'tidy' binary, found in Debian or http://tidy.sf.net/ +# mostly a proof-of-concept on how to use external filters. +# It is particularly useful when the html plugin is used. +# +# by Faidon Liambotis +package IkiWiki::Plugin::htmltidy; + +use warnings; +use strict; +use IkiWiki; +use IPC::Open2; + +sub import { #{{{ + hook(type => "sanitize", id => "tidy", call => \&sanitize); +} # }}} + +sub sanitize (@) { #{{{ + my %params=@_; + + my $pid; + my $sigpipe=0; + $SIG{PIPE}=sub { $sigpipe=1 }; + $pid=open2(*IN, *OUT, 'tidy -quiet -asxhtml -utf8 --show-body-only yes --show-warnings no --tidy-mark no'); + + # open2 doesn't respect "use open ':utf8'" + binmode (IN, ':utf8'); + binmode (OUT, ':utf8'); + + print OUT $params{content}; + close OUT; + + local $/ = undef; + my $ret=; + close IN; + waitpid $pid, 0; + + return $params{content} if $sigpipe; + $SIG{PIPE}="DEFAULT"; + + return $ret; +} # }}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/img.pm b/1.33.2/IkiWiki/Plugin/img.pm new file mode 100644 index 000000000..20893f56c --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/img.pm @@ -0,0 +1,85 @@ +#!/usr/bin/perl +# Ikiwiki enhanced image handling plugin +# Christian Mock cm@tahina.priv.at 20061002 +package IkiWiki::Plugin::img; + +use warnings; +use strict; +use IkiWiki; +use Image::Magick; + +my $convert = 'convert'; + +my %imgdefaults; + +sub import { #{{{ + hook(type => "preprocess", id => "img", call => \&preprocess); +} #}}} + +sub preprocess (@) { #{{{ + my ($image) = $_[0] =~ /$config{wiki_file_regexp}/; # untaint + my %params=@_; + + if (! exists $imgdefaults{$params{page}}) { + $imgdefaults{$params{page}} = {}; + } + my $size = $params{size} || $imgdefaults{$params{page}}->{size} || 'full'; + my $alt = $params{alt} || $imgdefaults{$params{page}}->{alt} || ''; + + if ($image eq 'defaults') { + $imgdefaults{$params{page}} = { + size => $size, + alt => $alt, + }; + return ''; + } + + add_depends($params{page}, $image); + my $file = bestlink($params{page}, $image) || return "[[img $image not found]]"; + + my $dir = IkiWiki::dirname($file); + my $base = IkiWiki::basename($file); + my $im = Image::Magick->new; + my $imglink; + my $r; + + if ($size ne 'full') { + my ($w, $h) = ($size =~ /^(\d+)x(\d+)$/); + return "[[img bad size \"$size\"]]" unless (defined $w && defined $h); + + my $outfile = "$config{destdir}/$dir/${w}x${h}-$base"; + $imglink = "$dir/${w}x${h}-$base"; + will_render($params{page}, $imglink); + + if (-e $outfile && (-M srcfile($file) >= -M $outfile)) { + $r = $im->Read($outfile); + return "[[img failed to read $outfile: $r]]" if $r; + } + else { + $r = $im->Read(srcfile($file)); + return "[[img failed to read $file: $r]]" if $r; + + $r = $im->Resize(geometry => "${w}x${h}"); + return "[[img failed to resize: $r]]" if $r; + + my @blob = $im->ImageToBlob(); + writefile($imglink, $config{destdir}, $blob[0], 1); + } + } + else { + $r = $im->Read(srcfile($file)); + return "[[img failed to read $file: $r]]" if $r; + $imglink = $file; + } + + add_depends($imglink, $params{page}); + + return ''.$alt.''; +} #}}} + +1; diff --git a/1.33.2/IkiWiki/Plugin/inline.pm b/1.33.2/IkiWiki/Plugin/inline.pm new file mode 100644 index 000000000..c6c6c6a1e --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/inline.pm @@ -0,0 +1,372 @@ +#!/usr/bin/perl +# Page inlining and blogging. +package IkiWiki::Plugin::inline; + +use warnings; +use strict; +use IkiWiki 1.00; +use IkiWiki::Render; # for displaytime +use URI; + +sub import { #{{{ + hook(type => "preprocess", id => "inline", + call => \&IkiWiki::preprocess_inline); + hook(type => "pagetemplate", id => "inline", + call => \&IkiWiki::pagetemplate_inline); + # Hook to change to do pinging since it's called late. + # This ensures each page only pings once and prevents slow + # pings interrupting page builds. + hook(type => "change", id => "inline", + call => \&IkiWiki::pingurl); +} # }}} + +# Back to ikiwiki namespace for the rest, this code is very much +# internal to ikiwiki even though it's separated into a plugin. +package IkiWiki; + +my %toping; +my %feedlinks; + +sub yesno ($) { #{{{ + my $val=shift; + return (defined $val && lc($val) eq "yes"); +} #}}} + +sub preprocess_inline (@) { #{{{ + my %params=@_; + + if (! exists $params{pages}) { + return ""; + } + my $raw=yesno($params{raw}); + my $archive=yesno($params{archive}); + my $rss=($config{rss} && exists $params{rss}) ? yesno($params{rss}) : $config{rss}; + my $atom=($config{atom} && exists $params{atom}) ? yesno($params{atom}) : $config{atom}; + my $feeds=exists $params{feeds} ? yesno($params{feeds}) : 1; + if (! exists $params{show} && ! $archive) { + $params{show}=10; + } + my $desc; + if (exists $params{description}) { + $desc = $params{description} + } else { + $desc = $config{wikiname}; + } + my $actions=yesno($params{actions}); + + my @list; + foreach my $page (keys %pagesources) { + next if $page eq $params{page}; + if (pagespec_match($page, $params{pages})) { + push @list, $page; + } + } + + if (exists $params{sort} && $params{sort} eq 'title') { + @list=sort @list; + } + elsif (! exists $params{sort} || $params{sort} eq 'age') { + @list=sort { $pagectime{$b} <=> $pagectime{$a} } @list; + } + else { + return "unknown sort type $params{sort}"; + } + + if (exists $params{skip}) { + @list=@list[$params{skip} .. scalar @list - 1]; + } + + if ($params{show} && @list > $params{show}) { + @list=@list[0..$params{show} - 1]; + } + + add_depends($params{page}, $params{pages}); + + my $rssurl=rsspage(basename($params{page})); + my $atomurl=atompage(basename($params{page})); + my $ret=""; + + if (exists $params{rootpage} && $config{cgiurl}) { + # Add a blog post form, with feed buttons. + my $formtemplate=template("blogpost.tmpl", blind_cache => 1); + $formtemplate->param(cgiurl => $config{cgiurl}); + $formtemplate->param(rootpage => $params{rootpage}); + $formtemplate->param(rssurl => $rssurl) if $feeds && $rss; + $formtemplate->param(atomurl => $atomurl) if $feeds && $atom; + $ret.=$formtemplate->output; + } + elsif ($feeds) { + # Add feed buttons. + my $linktemplate=template("feedlink.tmpl", blind_cache => 1); + $linktemplate->param(rssurl => $rssurl) if $rss; + $linktemplate->param(atomurl => $atomurl) if $atom; + $ret.=$linktemplate->output; + } + + my $template=template( + ($archive ? "inlinepagetitle.tmpl" : "inlinepage.tmpl"), + blind_cache => 1, + ) unless $raw; + + foreach my $page (@list) { + my $file = $pagesources{$page}; + my $type = pagetype($file); + if (! $raw || ($raw && ! defined $type)) { + # Get the content before populating the template, + # since getting the content uses the same template + # if inlines are nested. + # TODO: if $archive=1, the only reason to do this + # is to let the meta plugin get page title info; so stop + # calling this next line then once the meta plugin can + # store that accross runs (also tags plugin). + my $content=get_inline_content($page, $params{destpage}); + # Don't use htmllink because this way the title is separate + # and can be overridden by other plugins. + my $link=bestlink($params{page}, $page); + $link=htmlpage($link) if defined $type; + $link=abs2rel($link, dirname($params{destpage})); + $template->param(pageurl => $link); + $template->param(title => pagetitle(basename($page))); + $template->param(content => $content); + $template->param(ctime => displaytime($pagectime{$page})); + + if ($actions) { + my $file = $pagesources{$page}; + my $type = pagetype($file); + if ($config{discussion}) { + $template->param(have_actions => 1); + $template->param(discussionlink => htmllink($page, $page, "Discussion", 1, 1)); + } + if (length $config{cgiurl} && defined $type) { + $template->param(have_actions => 1); + $template->param(editurl => cgiurl(do => "edit", page => $page)); + } + } + + run_hooks(pagetemplate => sub { + shift->(page => $page, destpage => $params{page}, + template => $template,); + }); + + $ret.=$template->output; + $template->clear_params; + } + else { + if (defined $type) { + $ret.="\n". + linkify($page, $params{page}, + preprocess($page, $params{page}, + filter($page, + readfile(srcfile($file))))); + } + } + } + + if ($feeds && $rss) { + will_render($params{page}, rsspage($params{page})); + writefile(rsspage($params{page}), $config{destdir}, + genfeed("rss", $rssurl, $desc, $params{page}, @list)); + $toping{$params{page}}=1 unless $config{rebuild}; + $feedlinks{$params{destpage}}=qq{}; + } + if ($feeds && $atom) { + will_render($params{page}, atompage($params{page})); + writefile(atompage($params{page}), $config{destdir}, + genfeed("atom", $atomurl, $desc, $params{page}, @list)); + $toping{$params{page}}=1 unless $config{rebuild}; + $feedlinks{$params{destpage}}=qq{}; + } + + return $ret; +} #}}} + +sub pagetemplate_inline (@) { #{{{ + my %params=@_; + my $page=$params{page}; + my $template=$params{template}; + + $template->param(feedlinks => $feedlinks{$page}) + if exists $feedlinks{$page} && $template->query(name => "feedlinks"); +} #}}} + +sub get_inline_content ($$) { #{{{ + my $page=shift; + my $destpage=shift; + + my $file=$pagesources{$page}; + my $type=pagetype($file); + if (defined $type) { + return htmlize($page, $type, + linkify($page, $destpage, + preprocess($page, $destpage, + filter($page, + readfile(srcfile($file)))))); + } + else { + return ""; + } +} #}}} + +sub date_822 ($) { #{{{ + my $time=shift; + + eval q{use POSIX}; + error($@) if $@; + my $lc_time= POSIX::setlocale(&POSIX::LC_TIME); + POSIX::setlocale(&POSIX::LC_TIME, "C"); + my $ret=POSIX::strftime("%a, %d %b %Y %H:%M:%S %z", localtime($time)); + POSIX::setlocale(&POSIX::LC_TIME, $lc_time); + return $ret; +} #}}} + +sub date_3339 ($) { #{{{ + my $time=shift; + + eval q{use POSIX}; + error($@) if $@; + my $lc_time= POSIX::setlocale(&POSIX::LC_TIME); + POSIX::setlocale(&POSIX::LC_TIME, "C"); + my $ret=POSIX::strftime("%Y-%m-%dT%H:%M:%SZ", localtime($time)); + POSIX::setlocale(&POSIX::LC_TIME, $lc_time); + return $ret; +} #}}} + +sub absolute_urls ($$) { #{{{ + # sucky sub because rss sucks + my $content=shift; + my $url=shift; + + $url=~s/[^\/]+$//; + + $content=~s/new(encode_utf8($config{url}."/".htmlpage($page))); + + my $itemtemplate=template($feedtype."item.tmpl", blind_cache => 1); + my $content=""; + my $lasttime = 0; + foreach my $p (@pages) { + my $u=URI->new(encode_utf8($config{url}."/".htmlpage($p))); + + $itemtemplate->param( + title => pagetitle(basename($p)), + url => $u, + permalink => $u, + date_822 => date_822($pagectime{$p}), + date_3339 => date_3339($pagectime{$p}), + ); + + my $pcontent = absolute_urls(get_inline_content($p, $page), $url); + if ($itemtemplate->query(name => "enclosure")) { + my $file=$pagesources{$p}; + my $type=pagetype($file); + if (defined $type) { + $itemtemplate->param(content => $pcontent); + } + else { + my ($a, $b, $c, $d, $e, $f, $g, $size) = stat(srcfile($file)); + my $mime="unknown"; + eval q{use File::MimeInfo}; + if (! $@) { + $mime = mimetype($file); + } + $itemtemplate->param( + enclosure => $u, + type => $mime, + length => $size, + ); + } + } + else { + $itemtemplate->param(content => $pcontent); + } + + run_hooks(pagetemplate => sub { + shift->(page => $p, destpage => $page, + template => $itemtemplate); + }); + + $content.=$itemtemplate->output; + $itemtemplate->clear_params; + + $lasttime = $pagectime{$p} if $pagectime{$p} > $lasttime; + } + + my $template=template($feedtype."page.tmpl", blind_cache => 1); + $template->param( + title => $page ne "index" ? pagetitle($page) : $config{wikiname}, + wikiname => $config{wikiname}, + pageurl => $url, + content => $content, + feeddesc => $feeddesc, + feeddate => date_3339($lasttime), + feedurl => $feedurl, + version => $IkiWiki::version, + ); + run_hooks(pagetemplate => sub { + shift->(page => $page, destpage => $page, + template => $template); + }); + + return $template->output; +} #}}} + +sub pingurl (@) { #{{{ + return unless $config{pingurl} && %toping; + + eval q{require RPC::XML::Client}; + if ($@) { + debug("RPC::XML::Client not found, not pinging"); + return; + } + + # TODO: daemonize here so slow pings don't slow down wiki updates + + foreach my $page (keys %toping) { + my $title=pagetitle(basename($page)); + my $url="$config{url}/".htmlpage($page); + foreach my $pingurl (@{$config{pingurl}}) { + debug("Pinging $pingurl for $page"); + eval { + my $client = RPC::XML::Client->new($pingurl); + my $req = RPC::XML::request->new('weblogUpdates.ping', + $title, $url); + my $res = $client->send_request($req); + if (! ref $res) { + debug("Did not receive response to ping"); + } + my $r=$res->value; + if (! exists $r->{flerror} || $r->{flerror}) { + debug("Ping rejected: ".(exists $r->{message} ? $r->{message} : "[unknown reason]")); + } + }; + if ($@) { + debug "Ping failed: $@"; + } + } + } +} #}}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/linkmap.pm b/1.33.2/IkiWiki/Plugin/linkmap.pm new file mode 100644 index 000000000..d7dffc941 --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/linkmap.pm @@ -0,0 +1,105 @@ +#!/usr/bin/perl +package IkiWiki::Plugin::linkmap; + +use warnings; +use strict; +use IkiWiki; +use IPC::Open2; + +sub import { #{{{ + hook(type => "preprocess", id => "linkmap", call => \&preprocess); + hook(type => "format", id => "linkmap", call => \&format); +} # }}} + +my $mapnum=0; +my %maps; + +sub preprocess (@) { #{{{ + my %params=@_; + + $params{pages}="*" unless defined $params{pages}; + + # Needs to update whenever a page is added or removed, so + # register a dependency. + add_depends($params{page}, $params{pages}); + + # Can't just return the linkmap here, since the htmlscrubber + # scrubs out all tags (with good reason!) + # Instead, insert a placeholder tag, which will be expanded during + # formatting. + $mapnum++; + $maps{$mapnum}=\%params; + return "
"; +} # }}} + +sub format (@) { #{{{ + my %params=@_; + + $params{content}=~s/
<\/div>/genmap($1)/eg; + + return $params{content}; +} # }}} + +sub genmap ($) { #{{{ + my $mapnum=shift; + return "" unless exists $maps{$mapnum}; + my %params=%{$maps{$mapnum}}; + + # Get all the items to map. + my %mapitems = (); + foreach my $item (keys %links) { + if (pagespec_match($item, $params{pages})) { + my $link=htmlpage($item); + $link=IkiWiki::abs2rel($link, IkiWiki::dirname($params{page})); + $mapitems{$item}=$link; + } + } + + # Use ikiwiki's function to create the file, this makes sure needed + # subdirs are there and does some sanity checking. + will_render($params{page}, $params{page}.".png"); + writefile($params{page}.".png", $config{destdir}, ""); + + # Run dot to create the graphic and get the map data. + my $pid; + my $sigpipe=0;; + $SIG{PIPE}=sub { $sigpipe=1 }; + $pid=open2(*IN, *OUT, "dot -Tpng -o '$config{destdir}/$params{page}.png' -Tcmapx"); + + # open2 doesn't respect "use open ':utf8'" + binmode (IN, ':utf8'); + binmode (OUT, ':utf8'); + + print OUT "digraph linkmap$mapnum {\n"; + print OUT "concentrate=true;\n"; + print OUT "charset=\"utf-8\";\n"; + print OUT "ratio=compress;\nsize=\"".($params{width}+0).", ".($params{height}+0)."\";\n" + if defined $params{width} and defined $params{height}; + foreach my $item (keys %mapitems) { + print OUT "\"$item\" [shape=box,href=\"$mapitems{$item}\"];\n"; + foreach my $link (map { bestlink($item, $_) } @{$links{$item}}) { + print OUT "\"$item\" -> \"$link\";\n" + if $mapitems{$link}; + } + } + print OUT "}\n"; + close OUT; + + local $/=undef; + my $ret="\n". + . + ""; + close IN; + + waitpid $pid, 0; + $SIG{PIPE}="DEFAULT"; + if ($sigpipe) { + return "[[linkmap failed to run dot]]"; + } + + return $ret; +} #}}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/map.pm b/1.33.2/IkiWiki/Plugin/map.pm new file mode 100644 index 000000000..96daf39fc --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/map.pm @@ -0,0 +1,64 @@ +#!/usr/bin/perl +# +# Produce a hierarchical map of links. +# +# by Alessandro Dotti Contra +# +# Revision: 0.2 +package IkiWiki::Plugin::map; + +use warnings; +use strict; +use IkiWiki; + +sub import { #{{{ + hook(type => "preprocess", id => "map", call => \&preprocess); +} # }}} + +sub preprocess (@) { #{{{ + my %params=@_; + $params{pages}="*" unless defined $params{pages}; + + # Needs to update whenever a page is added or removed, so + # register a dependency. + add_depends($params{page}, $params{pages}); + + # Get all the items to map. + my @mapitems = (); + foreach my $page (keys %links) { + if (pagespec_match($page, $params{pages})) { + push @mapitems, $page; + } + } + + # Create the map. + my $indent=0; + my $openli=0; + my $map = "
\n"; + $map .= "
    \n"; + foreach my $item (sort @mapitems) { + my $depth = ($item =~ tr/\//\//); + while ($depth < $indent) { + $indent--; + $map.="
\n"; + } + while ($depth > $indent) { + $indent++; + $map.="
    \n"; + $openli=0; + } + $map .= "\n" if $openli; + $map .= "
  • " + .htmllink($params{page}, $params{destpage}, $item) ."\n"; + $openli=1; + } + while ($indent > 0) { + $indent--; + $map.="
\n"; + } + $map .= "\n"; + $map .= "
\n"; + return $map; +} # }}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/mdwn.pm b/1.33.2/IkiWiki/Plugin/mdwn.pm new file mode 100644 index 000000000..625f77f21 --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/mdwn.pm @@ -0,0 +1,54 @@ +#!/usr/bin/perl +# Markdown markup language +package IkiWiki::Plugin::mdwn; + +use warnings; +use strict; +use IkiWiki; + +sub import { #{{{ + hook(type => "htmlize", id => "mdwn", call => \&htmlize); +} # }}} + +my $markdown_sub; +sub htmlize (@) { #{{{ + my %params=@_; + my $content = $params{content}; + + if (! defined $markdown_sub) { + # Markdown is forked and splintered upstream and can be + # available in a variety of incompatible forms. Support + # them all. + no warnings 'once'; + $blosxom::version="is a proper perl module too much to ask?"; + use warnings 'all'; + + eval q{use Markdown}; + if (! $@) { + $markdown_sub=\&Markdown::Markdown; + } + else { + eval q{use Text::Markdown}; + if (! $@) { + $markdown_sub=\&Text::Markdown::Markdown; + } + else { + do "/usr/bin/markdown" || + error("failed to load Markdown.pm perl module ($@) or /usr/bin/markdown ($!)"); + $markdown_sub=\&Markdown::Markdown; + } + } + require Encode; + } + + # Workaround for perl bug (#376329) + $content=Encode::encode_utf8($content); + $content=Encode::encode_utf8($content); + $content=&$markdown_sub($content); + $content=Encode::decode_utf8($content); + $content=Encode::decode_utf8($content); + + return $content; +} # }}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/meta.pm b/1.33.2/IkiWiki/Plugin/meta.pm new file mode 100644 index 000000000..f53fdb6d8 --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/meta.pm @@ -0,0 +1,98 @@ +#!/usr/bin/perl +# Ikiwiki metadata plugin. +package IkiWiki::Plugin::meta; + +use warnings; +use strict; +use IkiWiki; + +my %meta; +my %title; +my %permalink; +my %author; +my %authorurl; + +sub import { #{{{ + hook(type => "preprocess", id => "meta", call => \&preprocess, scan => 1); + hook(type => "filter", id => "meta", call => \&filter); + hook(type => "pagetemplate", id => "meta", call => \&pagetemplate); +} # }}} + +sub filter (@) { #{{{ + my %params=@_; + + $meta{$params{page}}=''; + + return $params{content}; +} # }}} + +sub preprocess (@) { #{{{ + if (! @_) { + return ""; + } + my %params=@_; + my $key=shift; + my $value=$params{$key}; + delete $params{$key}; + my $page=$params{page}; + delete $params{page}; + delete $params{destpage}; + + eval q{use HTML::Entities}; + # Always dencode, even if encoding later, since it might not be + # fully encoded. + $value=decode_entities($value); + + if ($key eq 'link') { + if (%params) { + $meta{$page}.="\n"; + } + else { + # hidden WikiLink + push @{$links{$page}}, $value; + } + } + elsif ($key eq 'title') { + $title{$page}=encode_entities($value); + } + elsif ($key eq 'permalink') { + $permalink{$page}=$value; + $meta{$page}.="\n"; + } + else { + $meta{$page}.="\n"; + if ($key eq 'author') { + $author{$page}=$value; + } + elsif ($key eq 'authorurl') { + $authorurl{$page}=$value; + } + } + + return ""; +} # }}} + +sub pagetemplate (@) { #{{{ + my %params=@_; + my $page=$params{page}; + my $template=$params{template}; + + $template->param(meta => $meta{$page}) + if exists $meta{$page} && $template->query(name => "meta"); + if (exists $title{$page} && $template->query(name => "title")) { + $template->param(title => $title{$page}); + $template->param(title_overridden => 1); + } + $template->param(permalink => $permalink{$page}) + if exists $permalink{$page} && $template->query(name => "permalink"); + $template->param(author => $author{$page}) + if exists $author{$page} && $template->query(name => "author"); + $template->param(authorurl => $authorurl{$page}) + if exists $authorurl{$page} && $template->query(name => "authorurl"); + +} # }}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/orphans.pm b/1.33.2/IkiWiki/Plugin/orphans.pm new file mode 100644 index 000000000..3a8a7954f --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/orphans.pm @@ -0,0 +1,45 @@ +#!/usr/bin/perl +# Provides a list of pages no other page links to. +package IkiWiki::Plugin::orphans; + +use warnings; +use strict; +use IkiWiki; + +sub import { #{{{ + hook(type => "preprocess", id => "orphans", call => \&preprocess); +} # }}} + +sub preprocess (@) { #{{{ + my %params=@_; + $params{pages}="*" unless defined $params{pages}; + + # Needs to update whenever a page is added or removed, so + # register a dependency. + add_depends($params{page}, $params{pages}); + + my %linkedto; + foreach my $p (keys %links) { + map { $linkedto{bestlink($p, $_)}=1 if length $_ } + @{$links{$p}}; + } + + my @orphans; + foreach my $page (keys %renderedfiles) { + next if $linkedto{$page}; + next unless pagespec_match($page, $params{pages}); + # If the page has a link to some other page, it's + # indirectly linked to a page via that page's backlinks. + next if grep { + length $_ && + ($_ !~ /\/Discussion$/i || ! $config{discussion}) && + bestlink($page, $_) !~ /^($page|)$/ + } @{$links{$page}}; + push @orphans, $page; + } + + return "All pages are linked to by other pages." unless @orphans; + return "
    \n".join("\n", map { "
  • ".htmllink($params{page}, $params{destpage}, $_, 1)."
  • " } sort @orphans)."
\n"; +} # }}} + +1 diff --git a/1.33.2/IkiWiki/Plugin/otl.pm b/1.33.2/IkiWiki/Plugin/otl.pm new file mode 100644 index 000000000..3f3413390 --- /dev/null +++ b/1.33.2/IkiWiki/Plugin/otl.pm @@ -0,0 +1,90 @@ +#!/usr/bin/perl +# outline markup +package IkiWiki::Plugin::otl; + +use warnings; +use strict; +use IkiWiki; + +sub import { #{{{ + hook(type => "filter", id => "otl", call => \&filter); + hook(type => "htmlize", id => "otl", call => \&htmlize); + +} # }}} + +sub filter (@) { #{{{ + my %params=@_; + + # Munge up check boxes to look a little bit better. This is a hack. + my $checked=htmllink($params{page}, $params{page}, + "smileys/star_on.png", 0, 0, "[X]"); + my $unchecked=htmllink($params{page}, $params{page}, + "smileys/star_off.png", 0, 0, "[_]"); + $params{content}=~s/^(\s*)\[X\]\s/${1}$checked /mg; + $params{content}=~s/^(\s*)\[_\]\s/${1}$unchecked /mg; + + return $params{content}; +} # }}} + +sub htmlize (@) { #{{{ + my %params=@_; + + # Can't use open2 since otl2html doesn't play nice with buffering. + # Instead, fork off a child process that will run otl2html and feed + # it the content. Then read otl2html's response. + + my $tries=10; + my $pid; + do { + $pid = open(KID_TO_READ, "-|"); + unless (defined $pid) { + $tries--; + if ($tries < 1) { + debug("failed to fork: $@"); + return $params{content}; + } + } + } until defined $pid; + + if (! $pid) { + $tries=10; + $pid=undef; + + do { + $pid = open(KID_TO_WRITE, "|-"); + unless (defined $pid) { + $tries--; + if ($tries < 1) { + debug("failed to fork: $@"); + print $params{content}; + exit; + } + } + } until defined $pid; + + if (! $pid) { + if (! exec 'otl2html', '-S', '/dev/null', '-T', '/dev/stdin') { + debug("failed to run otl2html: $@"); + print $params{content}; + exit; + } + } + + print KID_TO_WRITE $params{content}; + close KID_TO_WRITE; + waitpid $pid, 0; + exit; + } + + local $/ = undef; + my $ret=; + close KID_TO_READ; + waitpid $pid, 0; + + $ret=~s/.*//s; + $ret=~s/.*//s; + $ret=~s/