X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/0eabe6f79498e3339205aeff2c79ae1e85498ab4..c03d91727ffa37c7fee508242d775ac6096781e4:/IkiWiki.pm?ds=inline diff --git a/IkiWiki.pm b/IkiWiki.pm index 701f7137d..87ddb1b56 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -20,9 +20,9 @@ use Exporter q{import}; our @EXPORT = qw(hook debug error htmlpage template template_depends deptype add_depends pagespec_match pagespec_match_list bestlink htmllink readfile writefile pagetype srcfile pagename - displaytime will_render gettext ngettext urlto targetpage + displaytime strftime_utf8 will_render gettext ngettext urlto targetpage add_underlay pagetitle titlepage linkpage newpagefile - inject add_link add_autofile + inject add_link add_autofile useragent %config %links %pagestate %wikistate %renderedfiles %pagesources %destsources %typedlinks); our $VERSION = 3.00; # plugin interface version, next is ikiwiki version @@ -118,6 +118,22 @@ sub getsetup () { safe => 0, rebuild => 0, }, + cgi_overload_delay => { + type => "string", + default => '', + example => "10", + description => "number of seconds to delay CGI requests when overloaded", + safe => 1, + rebuild => 0, + }, + cgi_overload_message => { + type => "string", + default => '', + example => "Please wait", + description => "message to display when overloaded (may contain html)", + safe => 1, + rebuild => 0, + }, rcs => { type => "string", default => '', @@ -237,8 +253,8 @@ sub getsetup () { html5 => { type => "boolean", default => 0, - description => "generate HTML5? (experimental)", - advanced => 1, + description => "generate HTML5?", + advanced => 0, safe => 1, rebuild => 1, }, @@ -305,9 +321,9 @@ sub getsetup () { rebuild => 0, }, umask => { - type => "integer", - example => "022", - description => "force ikiwiki to use a particular umask", + type => "string", + example => "public", + description => "force ikiwiki to use a particular umask (keywords public, group or private, or a number)", advanced => 1, safe => 0, # paranoia rebuild => 0, @@ -336,6 +352,14 @@ sub getsetup () { safe => 0, # paranoia rebuild => 0, }, + timezone => { + type => "string", + default => "", + example => "US/Eastern", + description => "time zone name", + safe => 1, + rebuild => 1, + }, include => { type => "string", default => undef, @@ -477,7 +501,7 @@ sub getsetup () { }, setuptype => { type => "internal", - default => "Standard", + default => "Yaml", description => "perl class to use to dump setup file", safe => 0, rebuild => 0, @@ -489,6 +513,13 @@ sub getsetup () { safe => 0, rebuild => 0, }, + cookiejar => { + type => "string", + default => { file => "$ENV{HOME}/.ikiwiki/cookies" }, + description => "cookie control", + safe => 0, # hooks into perl module internals + rebuild => 0, + }, } sub defaultconfig () { @@ -497,10 +528,15 @@ sub defaultconfig () { foreach my $key (keys %s) { push @ret, $key, $s{$key}->{default}; } - use Data::Dumper; return @ret; } +# URL to top of wiki as a path starting with /, valid from any wiki page or +# the CGI; if that's not possible, an absolute URL. Either way, it ends with / +my $local_url; +# URL to CGI script, similar to $local_url +my $local_cgiurl; + sub checkconfig () { # locale stuff; avoid LC_ALL since it overrides everything if (defined $ENV{LC_ALL}) { @@ -523,6 +559,12 @@ sub checkconfig () { $ENV{$val}=$config{ENV}{$val}; } } + if (defined $config{timezone} && length $config{timezone}) { + $ENV{TZ}=$config{timezone}; + } + else { + $config{timezone}=$ENV{TZ}; + } if ($config{w3mmode}) { eval q{use Cwd q{abs_path}}; @@ -537,12 +579,54 @@ sub checkconfig () { if ($config{cgi} && ! length $config{url}) { error(gettext("Must specify url to wiki with --url when using --cgi")); } - + + if (defined $config{url} && length $config{url}) { + eval q{use URI}; + my $baseurl = URI->new($config{url}); + + $local_url = $baseurl->path . "/"; + $local_cgiurl = undef; + + if (length $config{cgiurl}) { + my $cgiurl = URI->new($config{cgiurl}); + + $local_cgiurl = $cgiurl->path; + + if ($cgiurl->scheme ne $baseurl->scheme or + $cgiurl->authority ne $baseurl->authority) { + # too far apart, fall back to absolute URLs + $local_url = "$config{url}/"; + $local_cgiurl = $config{cgiurl}; + } + } + + $local_url =~ s{//$}{/}; + } + else { + $local_cgiurl = $config{cgiurl}; + } + $config{wikistatedir}="$config{srcdir}/.ikiwiki" unless exists $config{wikistatedir} && defined $config{wikistatedir}; if (defined $config{umask}) { - umask(possibly_foolish_untaint($config{umask})); + my $u = possibly_foolish_untaint($config{umask}); + + if ($u =~ m/^\d+$/) { + umask($u); + } + elsif ($u eq 'private') { + umask(077); + } + elsif ($u eq 'group') { + umask(027); + } + elsif ($u eq 'public') { + umask(022); + } + else { + error(sprintf(gettext("unsupported umask setting %s"), $u)); + } } run_hooks(checkconfig => sub { shift->() }); @@ -717,7 +801,7 @@ sub pagename ($) { my $type=pagetype($file); my $page=$file; - $page=~s/\Q.$type\E*$// + $page=~s/\Q.$type\E*$// if defined $type && !$hooks{htmlize}{$type}{keepextension} && !$hooks{htmlize}{$type}{noextension}; if ($config{indexpages} && $page=~/(.*)\/index$/) { @@ -778,17 +862,23 @@ sub srcfile ($;$) { return (srcfile_stat(@_))[0]; } -sub add_underlay ($) { +sub add_literal_underlay ($) { my $dir=shift; - if ($dir !~ /^\//) { - $dir="$config{underlaydirbase}/$dir"; - } - if (! grep { $_ eq $dir } @{$config{underlaydirs}}) { unshift @{$config{underlaydirs}}, $dir; } +} + +sub add_underlay ($) { + my $dir = shift; + if ($dir !~ /^\//) { + $dir="$config{underlaydirbase}/$dir"; + } + + add_literal_underlay($dir); + # why does it return 1? we just don't know return 1; } @@ -823,6 +913,17 @@ sub prep_writefile ($$) { if (-l "$destdir/$test") { error("cannot write to a symlink ($test)"); } + if (-f _ && $test ne $file) { + # Remove conflicting file. + foreach my $p (keys %renderedfiles, keys %oldrenderedfiles) { + foreach my $f (@{$renderedfiles{$p}}, @{$oldrenderedfiles{$p}}) { + if ($f eq $test) { + unlink("$destdir/$test"); + last; + } + } + } + } $test=dirname($test); } @@ -876,10 +977,36 @@ sub will_render ($$;$) { my $dest=shift; my $clear=shift; - # Important security check. + # Important security check for independently created files. if (-e "$config{destdir}/$dest" && ! $config{rebuild} && ! grep { $_ eq $dest } (@{$renderedfiles{$page}}, @{$oldrenderedfiles{$page}}, @{$wikistate{editpage}{previews}})) { - error("$config{destdir}/$dest independently created, not overwriting with version from $page"); + my $from_other_page=0; + # Expensive, but rarely runs. + foreach my $p (keys %renderedfiles, keys %oldrenderedfiles) { + if (grep { + $_ eq $dest || + dirname($_) eq $dest + } @{$renderedfiles{$p}}, @{$oldrenderedfiles{$p}}) { + $from_other_page=1; + last; + } + } + + error("$config{destdir}/$dest independently created, not overwriting with version from $page") + unless $from_other_page; + } + + # If $dest exists as a directory, remove conflicting files in it + # rendered from other pages. + if (-d _) { + foreach my $p (keys %renderedfiles, keys %oldrenderedfiles) { + foreach my $f (@{$renderedfiles{$p}}, @{$oldrenderedfiles{$p}}) { + if (dirname($f) eq $dest) { + unlink("$config{destdir}/$f"); + rmdir(dirname("$config{destdir}/$f")); + } + } + } } if (! $clear || $cleared{$page}) { @@ -938,7 +1065,7 @@ sub bestlink ($$) { sub isinlinableimage ($) { my $file=shift; - return $file =~ /\.(png|gif|jpg|jpeg)$/i; + return $file =~ /\.(png|gif|jpg|jpeg|svg)$/i; } sub pagetitle ($;$) { @@ -973,19 +1100,30 @@ sub linkpage ($) { sub cgiurl (@) { my %params=@_; - my $cgiurl=$config{cgiurl}; + my $cgiurl=$local_cgiurl; + if (exists $params{cgiurl}) { $cgiurl=$params{cgiurl}; delete $params{cgiurl}; } + + unless (%params) { + return $cgiurl; + } + return $cgiurl."?". join("&", map $_."=".uri_escape_utf8($params{$_}), keys %params); } +sub cgiurl_abs (@) { + eval q{use URI}; + URI->new_abs(cgiurl(@_), $config{cgiurl}); +} + sub baseurl (;$) { my $page=shift; - return "$config{url}/" if ! defined $page; + return $local_url if ! defined $page; $page=htmlpage($page); $page=~s/[^\/]+$//; @@ -993,6 +1131,16 @@ sub baseurl (;$) { return $page; } +sub urlabs ($$) { + my $url=shift; + my $urlbase=shift; + + return $url unless defined $urlbase && length $urlbase; + + eval q{use URI}; + URI->new_abs($url, $urlbase)->as_string; +} + sub abs2rel ($$) { # Work around very innefficient behavior in File::Spec if abs2rel # is passed two relative paths. It's much faster if paths are @@ -1028,9 +1176,19 @@ sub formattime ($;$) { $format=$config{timeformat}; } + return strftime_utf8($format, localtime($time)); +} + +my $strftime_encoding; +sub strftime_utf8 { # strftime doesn't know about encodings, so make sure - # its output is properly treated as utf8 - return decode_utf8(POSIX::strftime($format, localtime($time))); + # its output is properly treated as utf8. + # Note that this does not handle utf-8 in the format string. + ($strftime_encoding) = POSIX::setlocale(&POSIX::LC_TIME) =~ m#\.([^@]+)# + unless defined $strftime_encoding; + $strftime_encoding + ? Encode::decode($strftime_encoding, POSIX::strftime(@_)) + : POSIX::strftime(@_); } sub date_3339 ($) { @@ -1059,13 +1217,13 @@ sub beautify_urlpath ($) { return $url; } -sub urlto ($$;$) { +sub urlto ($;$$) { my $to=shift; my $from=shift; my $absolute=shift; if (! length $to) { - return beautify_urlpath(baseurl($from)."index.$config{htmlext}"); + $to = 'index'; } if (! $destsources{$to}) { @@ -1076,11 +1234,26 @@ sub urlto ($$;$) { return $config{url}.beautify_urlpath("/".$to); } + if (! defined $from) { + my $u = $local_url || ''; + $u =~ s{/$}{}; + return $u.beautify_urlpath("/".$to); + } + my $link = abs2rel($to, dirname(htmlpage($from))); return beautify_urlpath($link); } +sub isselflink ($$) { + # Plugins can override this function to support special types + # of selflinks. + my $page=shift; + my $link=shift; + + return $page eq $link; +} + sub htmllink ($$$;@) { my $lpage=shift; # the page doing the linking my $page=shift; # the page that will contain the link (different for inline) @@ -1106,7 +1279,7 @@ sub htmllink ($$$;@) { } return "$linktext" - if length $bestlink && $page eq $bestlink && + if length $bestlink && isselflink($page, $bestlink) && ! defined $opts{anchor}; if (! $destsources{$bestlink}) { @@ -1118,7 +1291,7 @@ sub htmllink ($$$;@) { $cgilink = " "create", - page => lc($link), + page => $link, from => $lpage )."\" rel=\"nofollow\">?"; } @@ -1155,7 +1328,7 @@ sub userpage ($) { sub openiduser ($) { my $user=shift; - if ($user =~ m!^https?://! && + if (defined $user && $user =~ m!^https?://! && eval q{use Net::OpenID::VerifiedIdentity; 1} && !$@) { my $display; @@ -1267,16 +1440,21 @@ sub preprocess ($$$;$$) { # consider it significant. my @params; while ($params =~ m{ - (?:([-\w]+)=)? # 1: named parameter key? + (?:([-.\w]+)=)? # 1: named parameter key? (?: """(.*?)""" # 2: triple-quoted value | "([^"]*?)" # 3: single-quoted value | - (\S+) # 4: unquoted value + '''(.*?)''' # 4: triple-single-quote + | + <<([a-zA-Z]+)\n # 5: heredoc start + (.*?)\n\5 # 6: heredoc value + | + (\S+) # 7: unquoted value ) (?:\s+|$) # delimiter to next param - }sgx) { + }msgx) { my $key=$1; my $val; if (defined $2) { @@ -1291,6 +1469,12 @@ sub preprocess ($$$;$$) { elsif (defined $4) { $val=$4; } + elsif (defined $7) { + $val=$7; + } + elsif (defined $6) { + $val=$6; + } if (defined $key) { push @params, $key, $val; @@ -1353,12 +1537,17 @@ sub preprocess ($$$;$$) { ( # 4: the parameters.. \s+ # Must have space if parameters present (?: - (?:[-\w]+=)? # named parameter key? + (?:[-.\w]+=)? # named parameter key? (?: """.*?""" # triple-quoted value | "[^"]*?" # single-quoted value | + '''.*?''' # triple-single-quote + | + <<([a-zA-Z]+)\n # 5: heredoc start + (?:.*?)\n\5 # heredoc value + | [^"\s\]]+ # unquoted value ) \s* # whitespace or end @@ -1376,12 +1565,17 @@ sub preprocess ($$$;$$) { \s+ ( # 4: the parameters.. (?: - (?:[-\w]+=)? # named parameter key? + (?:[-.\w]+=)? # named parameter key? (?: """.*?""" # triple-quoted value | "[^"]*?" # single-quoted value | + '''.*?''' # triple-single-quote + | + <<([a-zA-Z]+)\n # 5: heredoc start + (?:.*?)\n\5 # heredoc value + | [^"\s\]]+ # unquoted value ) \s* # whitespace or end @@ -1473,6 +1667,69 @@ sub check_content (@) { return defined $ok ? $ok : 1; } +sub check_canchange (@) { + my %params = @_; + my $cgi = $params{cgi}; + my $session = $params{session}; + my @changes = @{$params{changes}}; + + my %newfiles; + foreach my $change (@changes) { + # This untaint is safe because we check file_pruned and + # wiki_file_regexp. + my ($file)=$change->{file}=~/$config{wiki_file_regexp}/; + $file=possibly_foolish_untaint($file); + if (! defined $file || ! length $file || + file_pruned($file)) { + error(gettext("bad file name %s"), $file); + } + + my $type=pagetype($file); + my $page=pagename($file) if defined $type; + + if ($change->{action} eq 'add') { + $newfiles{$file}=1; + } + + if ($change->{action} eq 'change' || + $change->{action} eq 'add') { + if (defined $page) { + check_canedit($page, $cgi, $session); + next; + } + else { + if (IkiWiki::Plugin::attachment->can("check_canattach")) { + IkiWiki::Plugin::attachment::check_canattach($session, $file, $change->{path}); + check_canedit($file, $cgi, $session); + next; + } + } + } + elsif ($change->{action} eq 'remove') { + # check_canremove tests to see if the file is present + # on disk. This will fail when a single commit adds a + # file and then removes it again. Avoid the problem + # by not testing the removal in such pairs of changes. + # (The add is still tested, just to make sure that + # no data is added to the repo that a web edit + # could not add.) + next if $newfiles{$file}; + + if (IkiWiki::Plugin::remove->can("check_canremove")) { + IkiWiki::Plugin::remove::check_canremove(defined $page ? $page : $file, $cgi, $session); + check_canedit(defined $page ? $page : $file, $cgi, $session); + next; + } + } + else { + error "unknown action ".$change->{action}; + } + + error sprintf(gettext("you are not allowed to change %s"), $file); + } +} + + my $wikilock; sub lockwiki () { @@ -1550,6 +1807,12 @@ sub loadindex () { if (exists $index->{version} && ! ref $index->{version}) { $pages=$index->{page}; %wikistate=%{$index->{state}}; + # Handle plugins that got disabled by loading a new setup. + if (exists $config{setupfile}) { + require IkiWiki::Setup; + IkiWiki::Setup::disabled_plugins( + grep { ! $loaded_plugins{$_} } keys %wikistate); + } } else { $pages=$index; @@ -1617,11 +1880,7 @@ sub loadindex () { sub saveindex () { run_hooks(savestate => sub { shift->() }); - my %hookids; - foreach my $type (keys %hooks) { - $hookids{$_}=1 foreach keys %{$hooks{$type}}; - } - my @hookids=keys %hookids; + my @plugins=keys %loaded_plugins; if (! -d $config{wikistatedir}) { mkdir($config{wikistatedir}); @@ -1655,7 +1914,7 @@ sub saveindex () { } if (exists $pagestate{$page}) { - foreach my $id (@hookids) { + foreach my $id (@plugins) { foreach my $key (keys %{$pagestate{$page}{$id}}) { $index{page}{$src}{state}{$id}{$key}=$pagestate{$page}{$id}{$key}; } @@ -1664,7 +1923,8 @@ sub saveindex () { } $index{state}={}; - foreach my $id (@hookids) { + foreach my $id (@plugins) { + $index{state}{$id}={}; # used to detect disabled plugins foreach my $key (keys %{$wikistate{$id}}) { $index{state}{$id}{$key}=$wikistate{$id}{$key}; } @@ -1684,12 +1944,15 @@ sub template_file ($) { my $name=shift; my $tpage=($name =~ s/^\///) ? $name : "templates/$name"; + my $template; if ($name !~ /\.tmpl$/ && exists $pagesources{$tpage}) { - $tpage=$pagesources{$tpage}; + $template=srcfile($pagesources{$tpage}, 1); $name.=".tmpl"; } + else { + $template=srcfile($tpage, 1); + } - my $template=srcfile($tpage, 1); if (defined $template) { return $template, $tpage, 1 if wantarray; return $template; @@ -1717,12 +1980,14 @@ sub template_depends ($$;@) { my $page=shift; my ($filename, $tpage, $untrusted)=template_file($name); + if (! defined $filename) { + error(sprintf(gettext("template %s not found"), $name)) + } + if (defined $page && defined $tpage) { add_depends($page, $tpage); } - - return unless defined $filename; - + my @opts=( filter => sub { my $text_ref = shift; @@ -1730,6 +1995,7 @@ sub template_depends ($$;@) { }, loop_context_vars => 1, die_on_bad_params => 0, + parent_global_vars => 1, filename => $filename, @_, ($untrusted ? (no_includes => 1) : ()), @@ -1744,39 +2010,6 @@ sub template ($;@) { template_depends(shift, undef, @_); } -sub misctemplate ($$;@) { - my $title=shift; - my $content=shift; - my %params=@_; - - my $template=template("page.tmpl"); - - my $page=""; - if (exists $params{page}) { - $page=delete $params{page}; - } - run_hooks(pagetemplate => sub { - shift->( - page => $page, - destpage => $page, - template => $template, - ); - }); - templateactions($template, ""); - - $template->param( - dynamic => 1, - title => $title, - wikiname => $config{wikiname}, - content => $content, - baseurl => baseurl(), - html5 => $config{html5}, - %params, - ); - - return $template->output; -} - sub templateactions ($$) { my $template=shift; my $page=shift; @@ -1871,7 +2104,7 @@ sub rcs_recentchanges ($) { $hooks{rcs}{rcs_recentchanges}{call}->(@_); } -sub rcs_diff ($) { +sub rcs_diff ($;$) { $hooks{rcs}{rcs_diff}{call}->(@_); } @@ -2048,6 +2281,13 @@ sub add_autofile ($$$) { $autofiles{$file}{generator}=$generator; } +sub useragent () { + return LWP::UserAgent->new( + cookie_jar => $config{cookiejar}, + env_proxy => 1, # respect proxy env vars + ); +} + sub sortspec_translate ($$) { my $spec = shift; my $reverse = shift; @@ -2271,7 +2511,7 @@ sub glob2re ($) { my $re=quotemeta(shift); $re=~s/\\\*/.*/g; $re=~s/\\\?/./g; - return $re; + return qr/^$re$/i; } package IkiWiki::FailReason; @@ -2350,15 +2590,23 @@ sub derel ($$) { my $path=shift; my $from=shift; - if ($path =~ m!^\./!) { - $from=~s#/?[^/]+$## if defined $from; - $path=~s#^\./##; - $path="$from/$path" if defined $from && length $from; + if ($path =~ m!^\.(/|$)!) { + if ($1) { + $from=~s#/?[^/]+$## if defined $from; + $path=~s#^\./##; + $path="$from/$path" if defined $from && length $from; + } + else { + $path = $from; + $path = "" unless defined $path; + } } return $path; } +my %glob_cache; + sub match_glob ($$;@) { my $page=shift; my $glob=shift; @@ -2366,8 +2614,13 @@ sub match_glob ($$;@) { $glob=derel($glob, $params{location}); - my $regexp=IkiWiki::glob2re($glob); - if ($page=~/^$regexp$/i) { + # Instead of converting the glob to a regex every time, + # cache the compiled regex to save time. + my $re=$glob_cache{$glob}; + unless (defined $re) { + $glob_cache{$glob} = $re = IkiWiki::glob2re($glob); + } + if ($page =~ $re) { if (! IkiWiki::isinternal($page) || $params{internal}) { return IkiWiki::SuccessReason->new("$glob matches $page"); } @@ -2387,13 +2640,16 @@ sub match_internal ($$;@) { sub match_page ($$;@) { my $page=shift; my $match=match_glob($page, shift, @_); - if ($match && ! (exists $IkiWiki::pagesources{$page} - && defined IkiWiki::pagetype($IkiWiki::pagesources{$page}))) { - return IkiWiki::FailReason->new("$page is not a page"); - } - else { - return $match; + if ($match) { + my $source=exists $IkiWiki::pagesources{$page} ? + $IkiWiki::pagesources{$page} : + $IkiWiki::delpagesources{$page}; + my $type=defined $source ? IkiWiki::pagetype($source) : undef; + if (! defined $type) { + return IkiWiki::FailReason->new("$page is not a page"); + } } + return $match; } sub match_link ($$;@) { @@ -2436,8 +2692,14 @@ sub match_link ($$;@) { } sub match_backlink ($$;@) { - my $ret=match_link($_[1], $_[0], @_); - $ret->influences($_[1] => $IkiWiki::DEPEND_LINKS); + my $page=shift; + my $testpage=shift; + my %params=@_; + if ($testpage eq '.') { + $testpage = $params{'location'} + } + my $ret=match_link($testpage, $page, @_); + $ret->influences($testpage => $IkiWiki::DEPEND_LINKS); return $ret; } @@ -2482,7 +2744,12 @@ sub match_created_after ($$;@) { } sub match_creation_day ($$;@) { - if ((localtime($IkiWiki::pagectime{shift()}))[3] == shift) { + my $page=shift; + my $d=shift; + if ($d !~ /^\d+$/) { + return IkiWiki::ErrorReason->new("invalid day $d"); + } + if ((localtime($IkiWiki::pagectime{$page}))[3] == $d) { return IkiWiki::SuccessReason->new('creation_day matched'); } else { @@ -2491,7 +2758,12 @@ sub match_creation_day ($$;@) { } sub match_creation_month ($$;@) { - if ((localtime($IkiWiki::pagectime{shift()}))[4] + 1 == shift) { + my $page=shift; + my $m=shift; + if ($m !~ /^\d+$/) { + return IkiWiki::ErrorReason->new("invalid month $m"); + } + if ((localtime($IkiWiki::pagectime{$page}))[4] + 1 == $m) { return IkiWiki::SuccessReason->new('creation_month matched'); } else { @@ -2500,7 +2772,12 @@ sub match_creation_month ($$;@) { } sub match_creation_year ($$;@) { - if ((localtime($IkiWiki::pagectime{shift()}))[5] + 1900 == shift) { + my $page=shift; + my $y=shift; + if ($y !~ /^\d+$/) { + return IkiWiki::ErrorReason->new("invalid year $y"); + } + if ((localtime($IkiWiki::pagectime{$page}))[5] + 1900 == $y) { return IkiWiki::SuccessReason->new('creation_year matched'); } else { @@ -2513,13 +2790,13 @@ sub match_user ($$;@) { my $user=shift; my %params=@_; - my $regexp=IkiWiki::glob2re($user); - if (! exists $params{user}) { return IkiWiki::ErrorReason->new("no user specified"); } - if (defined $params{user} && $params{user}=~/^$regexp$/i) { + my $regexp=IkiWiki::glob2re($user); + + if (defined $params{user} && $params{user}=~$regexp) { return IkiWiki::SuccessReason->new("user is $user"); } elsif (! defined $params{user}) { @@ -2558,8 +2835,10 @@ sub match_ip ($$;@) { if (! exists $params{ip}) { return IkiWiki::ErrorReason->new("no IP specified"); } + + my $regexp=IkiWiki::glob2re(lc $ip); - if (defined $params{ip} && lc $params{ip} eq lc $ip) { + if (defined $params{ip} && lc $params{ip}=~$regexp) { return IkiWiki::SuccessReason->new("IP is $ip"); } else { @@ -2582,6 +2861,7 @@ sub cmp_title { IkiWiki::pagetitle(IkiWiki::basename($b)) } +sub cmp_path { IkiWiki::pagetitle($a) cmp IkiWiki::pagetitle($b) } sub cmp_mtime { $IkiWiki::pagemtime{$b} <=> $IkiWiki::pagemtime{$a} } sub cmp_age { $IkiWiki::pagectime{$b} <=> $IkiWiki::pagectime{$a} }