X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/4c2f46e3fa34238bbdd093f0b3d211103a3b94a3..7fe2744d48a75a447fbe124839c3db2756c24795:/IkiWiki.pm diff --git a/IkiWiki.pm b/IkiWiki.pm index 051c11b43..e0454963d 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -12,16 +12,17 @@ use Storable; use open qw{:utf8 :std}; use vars qw{%config %links %oldlinks %pagemtime %pagectime %pagecase - %pagestate %renderedfiles %oldrenderedfiles %pagesources - %destsources %depends %hooks %forcerebuild $gettext_obj - %loaded_plugins}; + %pagestate %wikistate %renderedfiles %oldrenderedfiles + %pagesources %destsources %depends %hooks %forcerebuild + $gettext_obj %loaded_plugins}; 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 gettext urlto targetpage - add_underlay - %config %links %pagestate %renderedfiles + add_underlay pagetitle titlepage linkpage newpagefile + inject + %config %links %pagestate %wikistate %renderedfiles %pagesources %destsources); our $VERSION = 2.00; # plugin interface version, next is ikiwiki version our $version='unknown'; # VERSION_AUTOREPLACE done by Makefile, DNE @@ -121,7 +122,7 @@ sub getsetup () { #{{{ type => "internal", default => [qw{mdwn link inline htmlscrubber passwordauth openid signinedit lockedit conditional - recentchanges parentlinks}], + recentchanges parentlinks editpage}], description => "plugins to enable by default", safe => 0, rebuild => 1, @@ -172,14 +173,14 @@ sub getsetup () { #{{{ }, verbose => { type => "boolean", - default => 0, + example => 1, description => "display verbose messages when building?", safe => 1, rebuild => 0, }, syslog => { type => "boolean", - default => 0, + example => 1, description => "log to syslog?", safe => 1, rebuild => 0, @@ -198,6 +199,13 @@ sub getsetup () { #{{{ safe => 0, # changing requires manual transition rebuild => 1, }, + indexpages => { + type => "boolean", + default => 0, + description => "use page/index.mdwn source files", + safe => 1, + rebuild => 1, + }, discussion => { type => "boolean", default => 1, @@ -312,9 +320,15 @@ sub getsetup () { #{{{ safe => 0, rebuild => 1, }, + wiki_file_chars => { + type => "string", + description => "specifies the characters that are allowed in source filenames", + default => "-[:alnum:]+/.:_", + safe => 0, + rebuild => 1, + }, wiki_file_regexp => { type => "internal", - default => qr/(^[-[:alnum:]_.:\/+]+$)/, description => "regexp of legal source files", safe => 0, rebuild => 1, @@ -354,6 +368,13 @@ sub getsetup () { #{{{ safe => 0, rebuild => 0, }, + setup => { + type => "internal", + default => undef, + description => "running in setup mode", + safe => 0, + rebuild => 0, + }, refresh => { type => "internal", default => 0, @@ -375,10 +396,17 @@ sub getsetup () { #{{{ safe => 0, rebuild => 0, }, - setup => { + setupfile => { type => "internal", default => undef, - description => "setup file to read", + description => "path to setup file", + safe => 0, + rebuild => 0, + }, + allow_symlinks_before_srcdir => { + type => "string", + default => 0, + description => "allow symlinks in the path leading to the srcdir (potentially insecure)", safe => 0, rebuild => 0, }, @@ -406,6 +434,10 @@ sub checkconfig () { #{{{ $gettext_obj=undef; } } + + if (! defined $config{wiki_file_regexp}) { + $config{wiki_file_regexp}=qr/(^[$config{wiki_file_chars}]+$)/; + } if (ref $config{ENV} eq 'HASH') { foreach my $val (keys %{$config{ENV}}) { @@ -464,7 +496,9 @@ sub loadplugins () { #{{{ unshift @INC, possibly_foolish_untaint($config{libdir}); } - loadplugin($_) foreach @{$config{default_plugins}}, @{$config{add_plugins}}; + foreach my $plugin (@{$config{default_plugins}}, @{$config{add_plugins}}) { + loadplugin($plugin); + } if ($config{rcs}) { if (exists $IkiWiki::hooks{rcs}) { @@ -494,7 +528,11 @@ sub loadplugin ($) { #{{{ foreach my $dir (defined $config{libdir} ? possibly_foolish_untaint($config{libdir}) : undef, "$installdir/lib/ikiwiki") { if (defined $dir && -x "$dir/plugins/$plugin") { - require IkiWiki::Plugin::external; + eval { require IkiWiki::Plugin::external }; + if ($@) { + my $reason=$@; + error(sprintf(gettext("failed to load external plugin needed for %s plugin: %s"), $plugin, $reason)); + } import IkiWiki::Plugin::external "$dir/plugins/$plugin"; $loaded_plugins{$plugin}=1; return 1; @@ -588,17 +626,33 @@ sub pagename ($) { #{{{ my $type=pagetype($file); my $page=$file; - $page=~s/\Q.$type\E*$// if defined $type; + $page=~s/\Q.$type\E*$// if defined $type && !$hooks{htmlize}{$type}{keepextension}; + if ($config{indexpages} && $page=~/(.*)\/index$/) { + $page=$1; + } return $page; } #}}} +sub newpagefile ($$) { #{{{ + my $page=shift; + my $type=shift; + + if (! $config{indexpages} || $page eq 'index') { + return $page.".".$type; + } + else { + return $page."/index.".$type; + } +} #}}} + sub targetpage ($$) { #{{{ my $page=shift; my $ext=shift; - if (! $config{usedirs} || $page =~ /^index$/ ) { + if (! $config{usedirs} || $page eq 'index') { return $page.".".$ext; - } else { + } + else { return $page."/index.".$ext; } } #}}} @@ -628,11 +682,12 @@ sub srcfile ($;$) { #{{{ sub add_underlay ($) { #{{{ my $dir=shift; - if ($dir=~/^\//) { - unshift @{$config{underlaydirs}}, $dir; + if ($dir !~ /^\//) { + $dir="$config{underlaydir}/../$dir"; } - else { - unshift @{$config{underlaydirs}}, "$config{underlaydir}/../$dir"; + + if (! grep { $_ eq $dir } @{$config{underlaydirs}}) { + unshift @{$config{underlaydirs}}, $dir; } return 1; @@ -720,7 +775,7 @@ sub will_render ($$;$) { #{{{ # Important security check. if (-e "$config{destdir}/$dest" && ! $config{rebuild} && - ! grep { $_ eq $dest } (@{$renderedfiles{$page}}, @{$oldrenderedfiles{$page}})) { + ! grep { $_ eq $dest } (@{$renderedfiles{$page}}, @{$oldrenderedfiles{$page}}, @{$wikistate{editpage}{previews}})) { error("$config{destdir}/$dest independently created, not overwriting with version from $page"); } @@ -761,7 +816,7 @@ sub bestlink ($$) { #{{{ elsif (exists $pagecase{lc $l}) { return $pagecase{lc $l}; } - } while $cwd=~s!/?[^/]+$!!; + } while $cwd=~s{/?[^/]+$}{}; if (length $config{userdir}) { my $l = "$config{userdir}/".lc($link); @@ -799,13 +854,16 @@ sub pagetitle ($;$) { #{{{ sub titlepage ($) { #{{{ my $title=shift; - $title=~s/([^-[:alnum:]:+\/.])/$1 eq ' ' ? '_' : "__".ord($1)."__"/eg; + # support use w/o %config set + my $chars = defined $config{wiki_file_chars} ? $config{wiki_file_chars} : "-[:alnum:]+/.:_"; + $title=~s/([^$chars]|_)/$1 eq ' ' ? '_' : "__".ord($1)."__"/eg; return $title; } #}}} sub linkpage ($) { #{{{ my $link=shift; - $link=~s/([^-[:alnum:]:+\/._])/$1 eq ' ' ? '_' : "__".ord($1)."__"/eg; + my $chars = defined $config{wiki_file_chars} ? $config{wiki_file_chars} : "-[:alnum:]+/.:_"; + $link=~s/([^$chars])/$1 eq ' ' ? '_' : "__".ord($1)."__"/eg; return $link; } #}}} @@ -841,6 +899,13 @@ sub abs2rel ($$) { #{{{ } #}}} sub displaytime ($;$) { #{{{ + # Plugins can override this function to mark up the time to + # display. + return ''.formattime(@_).''; +} #}}} + +sub formattime ($;$) { #{{{ + # Plugins can override this function to format the time. my $time=shift; my $format=shift; if (! defined $format) { @@ -1043,6 +1108,8 @@ sub preprocess ($$$;$$) { #{{{ my $prefix=shift; my $command=shift; my $params=shift; + $params="" if ! defined $params; + if (length $escape) { return "[[$prefix$command $params]]"; } @@ -1176,8 +1243,7 @@ sub preprocess ($$$;$$) { #{{{ }sx; } - # $4 can be undef if the directive was [[!foo]] - $content =~ s{$regex}{$handle->($1, $2, $3, ($4 or ""))}eg; + $content =~ s{$regex}{$handle->($1, $2, $3, $4)}eg; return $content; } #}}} @@ -1275,31 +1341,42 @@ sub loadindex () { #{{{ return; } } - my $ret=Storable::fd_retrieve($in); - if (! defined $ret) { + + my $index=Storable::fd_retrieve($in); + if (! defined $index) { return 0; } - my %index=%$ret; - foreach my $src (keys %index) { - my %d=%{$index{$src}}; + + my $pages; + if (exists $index->{version} && ! ref $index->{version}) { + $pages=$index->{page}; + %wikistate=%{$index->{state}}; + } + else { + $pages=$index; + %wikistate=(); + } + + foreach my $src (keys %$pages) { + my $d=$pages->{$src}; my $page=pagename($src); - $pagectime{$page}=$d{ctime}; + $pagectime{$page}=$d->{ctime}; if (! $config{rebuild}) { $pagesources{$page}=$src; - $pagemtime{$page}=$d{mtime}; - $renderedfiles{$page}=$d{dest}; - if (exists $d{links} && ref $d{links}) { - $links{$page}=$d{links}; - $oldlinks{$page}=[@{$d{links}}]; + $pagemtime{$page}=$d->{mtime}; + $renderedfiles{$page}=$d->{dest}; + if (exists $d->{links} && ref $d->{links}) { + $links{$page}=$d->{links}; + $oldlinks{$page}=[@{$d->{links}}]; } - if (exists $d{depends}) { - $depends{$page}=$d{depends}; + if (exists $d->{depends}) { + $depends{$page}=$d->{depends}; } - if (exists $d{state}) { - $pagestate{$page}=$d{state}; + if (exists $d->{state}) { + $pagestate{$page}=$d->{state}; } } - $oldrenderedfiles{$page}=[@{$d{dest}}]; + $oldrenderedfiles{$page}=[@{$d->{dest}}]; } foreach my $page (keys %pagesources) { $pagecase{lc $page}=$page; @@ -1325,12 +1402,13 @@ sub saveindex () { #{{{ my $newfile="$config{wikistatedir}/indexdb.new"; my $cleanup = sub { unlink($newfile) }; open (my $out, '>', $newfile) || error("cannot write to $newfile: $!", $cleanup); + my %index; foreach my $page (keys %pagemtime) { next unless $pagemtime{$page}; my $src=$pagesources{$page}; - $index{$src}={ + $index{page}{$src}={ ctime => $pagectime{$page}, mtime => $pagemtime{$page}, dest => $renderedfiles{$page}, @@ -1338,17 +1416,26 @@ sub saveindex () { #{{{ }; if (exists $depends{$page}) { - $index{$src}{depends} = $depends{$page}; + $index{page}{$src}{depends} = $depends{$page}; } if (exists $pagestate{$page}) { foreach my $id (@hookids) { foreach my $key (keys %{$pagestate{$page}{$id}}) { - $index{$src}{state}{$id}{$key}=$pagestate{$page}{$id}{$key}; + $index{page}{$src}{state}{$id}{$key}=$pagestate{$page}{$id}{$key}; } } } } + + $index{state}={}; + foreach my $id (@hookids) { + foreach my $key (keys %{$wikistate{$id}}) { + $index{state}{$id}{$key}=$wikistate{$id}{$key}; + } + } + + $index{version}="3"; my $ret=Storable::nstore_fd(\%index, $out); return if ! defined $ret || ! $ret; close $out || error("failed saving to $newfile: $!", $cleanup); @@ -1580,6 +1667,31 @@ sub yesno ($) { #{{{ return (defined $val && lc($val) eq gettext("yes")); } #}}} +sub inject { #{{{ + # Injects a new function into the symbol table to replace an + # exported function. + my %params=@_; + + # This is deep ugly perl foo, beware. + no strict; + no warnings; + if (! defined $params{parent}) { + $params{parent}='::'; + $params{old}=\&{$params{name}}; + $params{name}=~s/.*:://; + } + my $parent=$params{parent}; + foreach my $ns (grep /^\w+::/, keys %{$parent}) { + $ns = $params{parent} . $ns; + inject(%params, parent => $ns) unless $ns eq '::main::'; + *{$ns . $params{name}} = $params{call} + if exists ${$ns}{$params{name}} && + \&{${$ns}{$params{name}}} == $params{old}; + } + use strict; + use warnings; +} #}}} + sub pagespec_merge ($$) { #{{{ my $a=shift; my $b=shift; @@ -1674,7 +1786,7 @@ sub pagespec_valid ($) { #{{{ my $sub=pagespec_translate($spec); return ! $@; } #}}} - + sub glob2re ($) { #{{{ my $re=quotemeta(shift); $re=~s/\\\*/.*/g; @@ -1841,4 +1953,61 @@ sub match_creation_year ($$;@) { #{{{ } } #}}} +sub match_user ($$;@) { #{{{ + shift; + my $user=shift; + my %params=@_; + + if (! exists $params{user}) { + return IkiWiki::FailReason->new("no user specified"); + } + + if (defined $params{user} && lc $params{user} eq lc $user) { + return IkiWiki::SuccessReason->new("user is $user"); + } + elsif (! defined $params{user}) { + return IkiWiki::FailReason->new("not logged in"); + } + else { + return IkiWiki::FailReason->new("user is $params{user}, not $user"); + } +} #}}} + +sub match_admin ($$;@) { #{{{ + shift; + shift; + my %params=@_; + + if (! exists $params{user}) { + return IkiWiki::FailReason->new("no user specified"); + } + + if (defined $params{user} && IkiWiki::is_admin($params{user})) { + return IkiWiki::SuccessReason->new("user is an admin"); + } + elsif (! defined $params{user}) { + return IkiWiki::FailReason->new("not logged in"); + } + else { + return IkiWiki::FailReason->new("user is not an admin"); + } +} #}}} + +sub match_ip ($$;@) { #{{{ + shift; + my $ip=shift; + my %params=@_; + + if (! exists $params{ip}) { + return IkiWiki::FailReason->new("no IP specified"); + } + + if (defined $params{ip} && lc $params{ip} eq lc $ip) { + return IkiWiki::SuccessReason->new("IP is $ip"); + } + else { + return IkiWiki::FailReason->new("IP is $params{ip}, not $ip"); + } +} #}}} + 1