bestlink htmllink readfile writefile pagetype srcfile pagename
displaytime will_render gettext urlto targetpage
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
safe => 0,
rebuild => 0,
},
+ test_receive => {
+ type => "internal",
+ default => 0,
+ description => "running in receive test mode",
+ safe => 0,
+ rebuild => 0,
+ },
getctime => {
type => "internal",
default => 0,
rebuild => 0,
},
allow_symlinks_before_srcdir => {
- type => "string",
+ type => "boolean",
default => 0,
description => "allow symlinks in the path leading to the srcdir (potentially insecure)",
safe => 0,
sub targetpage ($$) { #{{{
my $page=shift;
my $ext=shift;
-
- if (! $config{usedirs} || $page eq 'index') {
+
+ my $targetpage='';
+ run_hooks(targetpage => sub {
+ $targetpage=shift->(
+ page => $page,
+ ext => $ext,
+ );
+ });
+
+ if (defined $targetpage && (length($targetpage) > 0)) {
+ return $targetpage;
+ }
+ elsif (! $config{usedirs} || $page eq 'index') {
return $page.".".$ext;
}
else {
sub bestlink ($$) { #{{{
my $page=shift;
my $link=shift;
+ my $res=undef;
my $cwd=$page;
if ($link=~s/^\/+//) {
$l.=$link;
if (exists $links{$l}) {
- return $l;
+ $res=$l;
}
elsif (exists $pagecase{lc $l}) {
- return $pagecase{lc $l};
+ $res=$pagecase{lc $l};
}
- } while $cwd=~s{/?[^/]+$}{};
+ } while ($cwd=~s{/?[^/]+$}{} && ! defined $res);
- if (length $config{userdir}) {
+ if (! defined $res && length $config{userdir}) {
my $l = "$config{userdir}/".lc($link);
if (exists $links{$l}) {
- return $l;
+ $res=$l;
}
elsif (exists $pagecase{lc $l}) {
- return $pagecase{lc $l};
+ $res=$pagecase{lc $l};
}
}
- #print STDERR "warning: page $page, broken link: $link\n";
- return "";
+ if (defined $res) {
+ run_hooks(tweakbestlink => sub {
+ $res=shift->(
+ page => $page,
+ link => $res);
+ });
+ return $res;
+ }
+ else {
+ #print STDERR "warning: page $page, broken link: $link\n";
+ return "";
+ }
} #}}}
sub isinlinableimage ($) { #{{{
} #}}}
sub displaytime ($;$) { #{{{
- my $time=shift;
- my $format=shift;
- if (exists $hooks{displaytime}) {
- my $ret;
- run_hooks(displaytime => sub {
- $ret=shift->($time, $format)
- });
- return $ret;
- }
- else {
- return formattime($time, $format);
- }
+ # Plugins can override this function to mark up the time to
+ # display.
+ return '<span class="date">'.formattime(@_).'</span>';
} #}}}
sub formattime ($;$) { #{{{
- # Plugins can override this function to mark up the time for
- # display.
+ # Plugins can override this function to format the time.
my $time=shift;
my $format=shift;
if (! defined $format) {
$url =~ s!/index.$config{htmlext}$!/!;
}
+ run_hooks(tweakurlpath => sub {
+ $url=shift->(url => $url);
+ });
+
# Ensure url is not an empty link, and
# if it's relative, make that explicit to avoid colon confusion.
if ($url !~ /^\//) {
$hooks{rcs}{rcs_getctime}{call}->(@_);
} #}}}
+ sub rcs_receive () { #{{{
+ $hooks{rcs}{rcs_receive}{call}->();
+ } #}}}
+
sub globlist_to_pagespec ($) { #{{{
my @globlist=split(' ', shift);
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;
my $sub=pagespec_translate($spec);
return ! $@;
} #}}}
-
+
sub glob2re ($) { #{{{
my $re=quotemeta(shift);
$re=~s/\\\*/.*/g;
the state is saved. The function can save other state, modify values before
they're saved, etc.
- ### displaytime
-
- hook(type => "displaytime", id => "foo", call => \&display);
-
- This hook can be registered to override the regular `displaytime` function.
- Only the last displaytime hook will be used.
-
### renamepage
hook(type => "renamepage", id => "foo", call => \&renamepage);
and undef if a rebuild could be needed in some circumstances, but is not
strictly required.
+### targetpage
+
+ hook(type => "targetpage", id => "foo", call => \&targetpage);
+
+This hook can be used to override the name of the file a page should
+be compiled into.
+
+It should return the target filename.
+
+### tweakurlpath
+
+ hook(type => "tweakurlpath", id => "foo", call => \&tweakurlpath);
+
+This hook can be used to modify the internal urls generated by
+ikiwiki; it is run just after ikiwiki has removed the trailing
+`index.html`, in case `usedirs` is enabled.
+
+It should return the modified url.
+
+### tweakbestlink
+
+ hook(type => "tweakbestlink", id => "foo", call => \&tweakbestlink);
+
+This hook can be used to modify the page returned by `bestlink`. It is
+passed named parameters `page` and `link`. These are, respectively,
+the page where the link will appear and the link ikiwiki would choose
+as the best one, if no `tweakbestlink` hook was in effect.
+
+It should return the modified link.
+
## Plugin interface
To import the ikiwiki plugin interface:
It's ok if this is not implemented, and throws an error.
+ #### `rcs_receive()`
+
+ This is called when ikiwiki is running as a pre-receive hook (or
+ equivalent), and is testing if changes pushed into the RCS from an
+ untrusted user should be accepted. This is optional, and doesn't make
+ sense to implement for all RCSs.
+
+ It should examine the incoming changes, and do any sanity
+ checks that are appropriate for the RCS to limit changes to safe file adds,
+ removes, and changes. If something bad is found, it should exit
+ nonzero, to abort the push. Otherwise, it should return a list of
+ files that were changed, in the form:
+
+ {
+ file => # name of file that was changed
+ action => # either "add", "change", or "remove"
+ path => # temp file containing the new file content, only
+ # needed for "add"/"change", and only if the file
+ # is an attachment, not a page
+ }
+
+ The list will then be checked to make sure that each change is one that
+ is allowed to be made via the web interface.
+
### PageSpec plugins
It's also possible to write plugins that add new functions to
program just needs to do something like:
`use IkiWiki::Setup; IkiWiki::Setup::load($filename)`
+ ### Function overriding
+
+ Sometimes using ikiwiki's pre-defined hooks is not enough. Your plugin
+ may need to replace one of ikiwiki's own functions with a modified version,
+ or wrap one of the functions.
+
+ For example, your plugin might want to override `displaytime`, to change
+ the html markup used when displaying a date. Or it might want to override
+ `IkiWiki::formattime`, to change how a date is formatted. Or perhaps you
+ want to override `bestlink` and change how ikiwiki deals with WikiLinks.
+
+ By venturing into this territory, your plugin is becoming tightly tied to
+ ikiwiki's internals. And it might break if those internals change. But
+ don't let that stop you, if you're brave.
+
+ Ikiwiki provides an `inject()` function, that is a powerful way to replace
+ any function with one of your own. This even allows you to inject a
+ replacement for an exported function, like `bestlink`. Everything that
+ imports that function will get your version instead. Pass it the name of
+ the function to replace, and a new function to call.
+
+ For example, here's how to replace `displaytime` with a version using HTML 5
+ markup:
+
+ inject(name => 'IkiWiki::displaytime', call => sub {
+ return "<time>".formattime(@_)."</time>";
+ });
+
+ Here's how to wrap `bestlink` with a version that tries to handle
+ plural words:
+
+ my $origbestlink=\&bestlink;
+ inject(name => 'IkiWiki::bestlink', call => \&mybestlink);
+
+ sub deplural ($) {
+ my $word=shift;
+ $word =~ s/e?s$//; # just an example :-)
+ return $word;
+ }
+
+ sub mybestlink ($$) {
+ my $page=shift;
+ my $link=shift;
+ my $ret=$origbestlink->($page, $link);
+ if (! length $ret) {
+ $ret=$origbestlink->($page, deplural($link));
+ }
+ return $ret;
+ }
+
### Javascript
Some plugins use javascript to make ikiwiki look a bit more web-2.0-ish.