X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/abab46f983118e42e7780670812db54162c33e06..e6e8d906d13313a34320c3de20254077e4f25a97:/IkiWiki.pm
diff --git a/IkiWiki.pm b/IkiWiki.pm
index 6a72a0144..2ad2f792d 100644
--- a/IkiWiki.pm
+++ b/IkiWiki.pm
@@ -12,15 +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};
+ %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
@@ -40,6 +42,28 @@ sub getsetup () { #{{{
safe => 1,
rebuild => 1,
},
+ adminemail => {
+ type => "string",
+ default => undef,
+ example => 'me@example.com',
+ description => "contact email for wiki",
+ safe => 1,
+ rebuild => 0,
+ },
+ adminuser => {
+ type => "string",
+ default => [],
+ description => "users who are wiki admins",
+ safe => 1,
+ rebuild => 0,
+ },
+ banned_users => {
+ type => "string",
+ default => [],
+ description => "users who are banned from the wiki",
+ safe => 1,
+ rebuild => 0,
+ },
srcdir => {
type => "string",
default => undef,
@@ -56,21 +80,6 @@ sub getsetup () { #{{{
safe => 0, # path
rebuild => 1,
},
- adminuser => {
- type => "string",
- default => [],
- description => "user names of wiki admins",
- safe => 1,
- rebuild => 0,
- },
- adminemail => {
- type => "string",
- default => undef,
- example => 'me@example.com',
- description => "contact email for wiki",
- safe => 1,
- rebuild => 0,
- },
url => {
type => "string",
default => '',
@@ -82,11 +91,26 @@ sub getsetup () { #{{{
cgiurl => {
type => "string",
default => '',
- examples => "http://example.com/wiki/ikiwiki.cgi",
+ example => "http://example.com/wiki/ikiwiki.cgi",
description => "url to the ikiwiki.cgi",
safe => 1,
rebuild => 1,
},
+ cgi_wrapper => {
+ type => "string",
+ default => '',
+ example => "/var/www/wiki/ikiwiki.cgi",
+ description => "cgi wrapper to generate",
+ safe => 0, # file
+ rebuild => 0,
+ },
+ cgi_wrappermode => {
+ type => "string",
+ default => '06755',
+ description => "mode for cgi_wrapper (can safely be made suid)",
+ safe => 0,
+ rebuild => 0,
+ },
rcs => {
type => "string",
default => '',
@@ -94,58 +118,34 @@ sub getsetup () { #{{{
safe => 0, # don't allow overriding
rebuild => 0,
},
- historyurl => {
- type => "string",
- # TODO should be set per-rcs to allow different
- # examples and descriptions
- default => '',
- example => "XXX",
- description => "XXX",
- safe => 1,
+ default_plugins => {
+ type => "internal",
+ default => [qw{mdwn link inline meta htmlscrubber passwordauth
+ openid signinedit lockedit conditional
+ recentchanges parentlinks editpage}],
+ description => "plugins to enable by default",
+ safe => 0,
rebuild => 1,
},
- diffurl => {
+ add_plugins => {
type => "string",
- # TODO ditto above
- default => '',
- example => "XXX",
- description => "XXX",
+ default => [],
+ description => "plugins to add to the default configuration",
safe => 1,
rebuild => 1,
},
- discussion => {
- type => "boolean",
- default => 1,
- description => "enable Discussion pages",
+ disable_plugins => {
+ type => "string",
+ default => [],
+ description => "plugins to disable",
safe => 1,
rebuild => 1,
},
- svnpath => {
- # TODO move
- type => "string",
- default => "trunk",
- description => "path inside svn repo where wiki is located",
- safe => 0, # could expose/overwrite data
- rebuild => 0,
- },
- gitorigin_branch => {
- type => "string",
- default => "origin",
- description => "the git origin to pull from",
- safe => 0, # paranoia
- rebuild => 0,
- },
- gitmaster_branch => {
- type => "string",
- default => "master",
- description => "the git master branch",
- safe => 0, # paranoia
- rebuild => 0,
- },
templatedir => {
type => "string",
default => "$installdir/share/ikiwiki/templates",
description => "location of template files",
+ advanced => 1,
safe => 0, # path
rebuild => 1,
},
@@ -153,9 +153,17 @@ sub getsetup () { #{{{
type => "string",
default => "$installdir/share/ikiwiki/basewiki",
description => "base wiki source location",
+ advanced => 1,
safe => 0, # path
rebuild => 0,
},
+ wrappers => {
+ type => "internal",
+ default => [],
+ description => "wrappers to generate",
+ safe => 0,
+ rebuild => 0,
+ },
underlaydirs => {
type => "internal",
default => [],
@@ -165,15 +173,15 @@ sub getsetup () { #{{{
},
verbose => {
type => "boolean",
- default => 0,
- description => "display verbose messages when building",
+ example => 1,
+ description => "display verbose messages when building?",
safe => 1,
rebuild => 0,
},
syslog => {
type => "boolean",
- default => 0,
- description => "log to syslog",
+ example => 1,
+ description => "log to syslog?",
safe => 1,
rebuild => 0,
},
@@ -191,6 +199,28 @@ 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,
+ description => "enable Discussion pages?",
+ safe => 1,
+ rebuild => 1,
+ },
+ sslcookie => {
+ type => "boolean",
+ default => 0,
+ description => "only send cookies over SSL connections?",
+ advanced => 1,
+ safe => 1,
+ rebuild => 0,
+ },
default_pageext => {
type => "string",
default => "mdwn",
@@ -209,6 +239,7 @@ sub getsetup () { #{{{
type => "string",
default => '%c',
description => "strftime format string to display date",
+ advanced => 1,
safe => 1,
rebuild => 1,
},
@@ -217,16 +248,10 @@ sub getsetup () { #{{{
default => undef,
example => "en_US.UTF-8",
description => "UTF-8 locale to use",
+ advanced => 1,
safe => 0,
rebuild => 1,
},
- sslcookie => {
- type => "boolean",
- default => 0,
- description => "only send cookies over SSL connections?",
- safe => 1,
- rebuild => 0,
- },
userdir => {
type => "string",
default => "",
@@ -245,11 +270,52 @@ sub getsetup () { #{{{
hardlink => {
type => "boolean",
default => 0,
- description => "attempt to hardlink source files (optimisation for large files)",
+ description => "attempt to hardlink source files? (optimisation for large files)",
+ advanced => 1,
safe => 0, # paranoia
rebuild => 0,
},
-
+ umask => {
+ type => "integer",
+ example => "022",
+ description => "force ikiwiki to use a particular umask",
+ advanced => 1,
+ safe => 0, # paranoia
+ rebuild => 0,
+ },
+ wrappergroup => {
+ type => "string",
+ example => "ikiwiki",
+ description => "group for wrappers to run in",
+ advanced => 1,
+ safe => 0, # paranoia
+ rebuild => 0,
+ },
+ libdir => {
+ type => "string",
+ default => "",
+ example => "$ENV{HOME}/.ikiwiki/",
+ description => "extra library and plugin directory",
+ advanced => 1,
+ safe => 0, # directory
+ rebuild => 0,
+ },
+ ENV => {
+ type => "string",
+ default => {},
+ description => "environment variables",
+ safe => 0, # paranoia
+ rebuild => 0,
+ },
+ exclude => {
+ type => "string",
+ default => undef,
+ example => '\.wav$',
+ description => "regexp of source files to ignore",
+ advanced => 1,
+ safe => 0, # regexp
+ rebuild => 1,
+ },
wiki_file_prune_regexps => {
type => "internal",
default => [qr/(^|\/)\.\.(\/|$)/, qr/^\./, qr/\/\./,
@@ -261,9 +327,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,
@@ -303,6 +375,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,
@@ -310,55 +389,45 @@ sub getsetup () { #{{{
safe => 0,
rebuild => 0,
},
- getctime => {
+ test_receive => {
type => "internal",
default => 0,
- description => "running in getctime mode",
+ description => "running in receive test mode",
safe => 0,
rebuild => 0,
},
- w3mmode => {
+ getctime => {
type => "internal",
default => 0,
- description => "running in w3mmode",
+ description => "running in getctime mode",
safe => 0,
rebuild => 0,
},
- wrapper => {
+ w3mmode => {
type => "internal",
- default => undef,
- description => "wrapper file to generate",
+ default => 0,
+ description => "running in w3mmode",
safe => 0,
rebuild => 0,
},
- wrappermode => {
+ wikistatedir => {
type => "internal",
default => undef,
- description => "mode of wrapper file",
+ description => "path to the .ikiwiki directory holding ikiwiki state",
safe => 0,
rebuild => 0,
},
- setup => {
+ setupfile => {
type => "internal",
default => undef,
- description => "setup file to read",
+ description => "path to setup file",
safe => 0,
rebuild => 0,
},
- plugin => {
- type => "internal",
- default => [qw{mdwn link inline htmlscrubber passwordauth
- openid signinedit lockedit conditional
- recentchanges parentlinks}],
- description => "enabled plugins",
- safe => 0,
- rebuild => 1,
- },
- libdir => {
- type => "internal",
- default => undef,
- example => "$ENV{HOME}/.ikiwiki/",
- description => "extra library and plugin directory",
+ allow_symlinks_before_srcdir => {
+ type => "boolean",
+ default => 0,
+ description => "allow symlinks in the path leading to the srcdir (potentially insecure)",
safe => 0,
rebuild => 0,
},
@@ -386,6 +455,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}}) {
@@ -408,19 +481,9 @@ sub checkconfig () { #{{{
}
$config{wikistatedir}="$config{srcdir}/.ikiwiki"
- unless exists $config{wikistatedir};
-
- if ($config{rcs}) {
- eval qq{use IkiWiki::Rcs::$config{rcs}};
- if ($@) {
- error("Failed to load RCS module IkiWiki::Rcs::$config{rcs}: $@");
- }
- }
- else {
- require IkiWiki::Rcs::Stub;
- }
+ unless exists $config{wikistatedir} && defined $config{wikistatedir};
- if (exists $config{umask}) {
+ if (defined $config{umask}) {
umask(possibly_foolish_untaint($config{umask}));
}
@@ -429,12 +492,44 @@ sub checkconfig () { #{{{
return 1;
} #}}}
+sub listplugins () { #{{{
+ my %ret;
+
+ foreach my $dir (@INC, $config{libdir}) {
+ next unless defined $dir && length $dir;
+ foreach my $file (glob("$dir/IkiWiki/Plugin/*.pm")) {
+ my ($plugin)=$file=~/.*\/(.*)\.pm$/;
+ $ret{$plugin}=1;
+ }
+ }
+ foreach my $dir ($config{libdir}, "$installdir/lib/ikiwiki") {
+ next unless defined $dir && length $dir;
+ foreach my $file (glob("$dir/plugins/*")) {
+ $ret{basename($file)}=1 if -x $file;
+ }
+ }
+
+ return keys %ret;
+} #}}}
+
sub loadplugins () { #{{{
- if (defined $config{libdir}) {
+ if (defined $config{libdir} && length $config{libdir}) {
unshift @INC, possibly_foolish_untaint($config{libdir});
}
- loadplugin($_) foreach @{$config{plugin}};
+ foreach my $plugin (@{$config{default_plugins}}, @{$config{add_plugins}}) {
+ loadplugin($plugin);
+ }
+
+ if ($config{rcs}) {
+ if (exists $IkiWiki::hooks{rcs}) {
+ error(gettext("cannot use multiple rcs plugins"));
+ }
+ loadplugin($config{rcs});
+ }
+ if (! exists $IkiWiki::hooks{rcs}) {
+ loadplugin("norcs");
+ }
run_hooks(getopt => sub { shift->() });
if (grep /^-/, @ARGV) {
@@ -454,8 +549,13 @@ 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;
}
}
@@ -465,6 +565,7 @@ sub loadplugin ($) { #{{{
if ($@) {
error("Failed to load plugin $mod: $@");
}
+ $loaded_plugins{$plugin}=1;
return 1;
} #}}}
@@ -546,17 +647,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;
}
} #}}}
@@ -586,11 +703,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;
@@ -610,6 +728,10 @@ sub readfile ($;$$) { #{{{
binmode($in) if ($binary);
return \*$in if $wantfd;
my $ret=<$in>;
+ # check for invalid utf-8, and toss it back to avoid crashes
+ if (! utf8::valid($ret)) {
+ $ret=encode_utf8($ret);
+ }
close $in || error("failed to read $file: $!");
return $ret;
} #}}}
@@ -678,7 +800,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");
}
@@ -719,7 +841,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);
@@ -757,13 +879,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;
} #}}}
@@ -799,6 +924,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) {
@@ -817,9 +949,9 @@ sub beautify_urlpath ($) { #{{{
$url =~ s!/index.$config{htmlext}$!/!;
}
- # Ensure url is not an empty link, and
- # if it's relative, make that explicit to avoid colon confusion.
- if ($url !~ /^\//) {
+ # Ensure url is not an empty link, and if necessary,
+ # add ./ to avoid colon confusion.
+ if ($url !~ /^\// && $url !~ /^\.\.\//) {
$url="./$url";
}
@@ -1001,6 +1133,8 @@ sub preprocess ($$$;$$) { #{{{
my $prefix=shift;
my $command=shift;
my $params=shift;
+ $params="" if ! defined $params;
+
if (length $escape) {
return "[[$prefix$command $params]]";
}
@@ -1045,13 +1179,10 @@ sub preprocess ($$$;$$) { #{{{
if ($preprocessing{$page}++ > 3) {
# Avoid loops of preprocessed pages preprocessing
# other pages that preprocess them, etc.
- #translators: The first parameter is a
- #translators: preprocessor directive name,
- #translators: the second a page name, the
- #translators: third a number.
- return "[[".sprintf(gettext("%s preprocessing loop detected on %s at depth %i"),
- $command, $page, $preprocessing{$page}).
- "]]";
+ return "[[!$command ".
+ sprintf(gettext("preprocessing loop detected on %s at depth %i"),
+ $page, $preprocessing{$page}).
+ "]]";
}
my $ret;
if (! $scan) {
@@ -1160,8 +1291,7 @@ sub indexlink () { #{{{
my $wikilock;
-sub lockwiki (;$) { #{{{
- my $wait=@_ ? shift : 1;
+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}) {
@@ -1169,25 +1299,14 @@ sub lockwiki (;$) { #{{{
}
open($wikilock, '>', "$config{wikistatedir}/lockfile") ||
error ("cannot write to $config{wikistatedir}/lockfile: $!");
- if (! flock($wikilock, 2 | 4)) { # LOCK_EX | LOCK_NB
- if ($wait) {
- debug("wiki seems to be locked, waiting for lock");
- my $wait=600; # arbitrary, but don't hang forever to
- # prevent process pileup
- for (1..$wait) {
- 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?)");
- }
- else {
- return 0;
- }
+ if (! flock($wikilock, 2)) { # LOCK_EX
+ error("failed to get lock");
}
return 1;
} #}}}
sub unlockwiki () { #{{{
+ POSIX::close($ENV{IKIWIKI_CGILOCK_FD}) if exists $ENV{IKIWIKI_CGILOCK_FD};
return close($wikilock) if $wikilock;
return;
} #}}}
@@ -1235,31 +1354,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;
@@ -1285,12 +1415,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},
@@ -1298,17 +1429,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);
@@ -1408,6 +1548,50 @@ sub run_hooks ($$) { # {{{
return 1;
} #}}}
+sub rcs_update () { #{{{
+ $hooks{rcs}{rcs_update}{call}->(@_);
+} #}}}
+
+sub rcs_prepedit ($) { #{{{
+ $hooks{rcs}{rcs_prepedit}{call}->(@_);
+} #}}}
+
+sub rcs_commit ($$$;$$) { #{{{
+ $hooks{rcs}{rcs_commit}{call}->(@_);
+} #}}}
+
+sub rcs_commit_staged ($$$) { #{{{
+ $hooks{rcs}{rcs_commit_staged}{call}->(@_);
+} #}}}
+
+sub rcs_add ($) { #{{{
+ $hooks{rcs}{rcs_add}{call}->(@_);
+} #}}}
+
+sub rcs_remove ($) { #{{{
+ $hooks{rcs}{rcs_remove}{call}->(@_);
+} #}}}
+
+sub rcs_rename ($$) { #{{{
+ $hooks{rcs}{rcs_rename}{call}->(@_);
+} #}}}
+
+sub rcs_recentchanges ($) { #{{{
+ $hooks{rcs}{rcs_recentchanges}{call}->(@_);
+} #}}}
+
+sub rcs_diff ($) { #{{{
+ $hooks{rcs}{rcs_diff}{call}->(@_);
+} #}}}
+
+sub rcs_getctime ($) { #{{{
+ $hooks{rcs}{rcs_getctime}{call}->(@_);
+} #}}}
+
+sub rcs_receive () { #{{{
+ $hooks{rcs}{rcs_receive}{call}->();
+} #}}}
+
sub globlist_to_pagespec ($) { #{{{
my @globlist=split(' ', shift);
@@ -1500,6 +1684,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;
@@ -1594,7 +1803,7 @@ sub pagespec_valid ($) { #{{{
my $sub=pagespec_translate($spec);
return ! $@;
} #}}}
-
+
sub glob2re ($) { #{{{
my $re=quotemeta(shift);
$re=~s/\\\*/.*/g;
@@ -1691,6 +1900,10 @@ sub match_link ($$;@) { #{{{
else {
return IkiWiki::SuccessReason->new("$page links to page $p matching $link")
if match_glob($p, $link, %params);
+ $p=~s/^\///;
+ $link=~s/^\///;
+ return IkiWiki::SuccessReason->new("$page links to page $p matching $link")
+ if match_glob($p, $link, %params);
}
}
return IkiWiki::FailReason->new("$page does not link to $link");
@@ -1761,4 +1974,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