]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/blobdiff - IkiWiki.pm
response
[git.ikiwiki.info.git] / IkiWiki.pm
index d114c9a69d760194e05c788e36a74d4c8ab3c144..8136a7c4b67cff3ead2bae127b8020559111e132 100644 (file)
@@ -336,6 +336,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 +485,7 @@ sub getsetup () {
        },
        setuptype => {
                type => "internal",
-               default => "Standard",
+               default => "Yaml",
                description => "perl class to use to dump setup file",
                safe => 0,
                rebuild => 0,
@@ -497,10 +505,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 +536,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,7 +556,33 @@ 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};
 
@@ -717,7 +762,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 +823,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;
 }
 
@@ -1010,11 +1061,17 @@ 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);
 }
@@ -1022,7 +1079,7 @@ sub cgiurl (@) {
 sub baseurl (;$) {
        my $page=shift;
 
-       return "$config{url}/" if ! defined $page;
+       return $local_url if ! defined $page;
        
        $page=htmlpage($page);
        $page=~s/[^\/]+$//;
@@ -1030,6 +1087,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
@@ -1096,13 +1163,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}) {
@@ -1113,6 +1180,12 @@ 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);
@@ -1124,7 +1197,7 @@ sub isselflink ($$) {
        my $page=shift;
        my $link=shift;
 
-        return $page eq $link;
+       return $page eq $link;
 }
 
 sub htmllink ($$$;@) {
@@ -1201,7 +1274,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;
 
@@ -1319,10 +1392,14 @@ sub preprocess ($$$;$$) {
                                |
                                        "([^"]*?)"      # 3: single-quoted value
                                |
-                                       (\S+)           # 4: unquoted value
+                                       '''(.*?)'''     # 4: triple-single-quote
+                               |
+                                       <<(?<start>[a-zA-Z]+)\n(?<heredoc>.*?)\n\k<start> # 5, 6: heredoc'd value.
+                               |
+                                       (\S+)           # 7: unquoted value
                                )
                                (?:\s+|$)               # delimiter to next param
-                       }sgx) {
+                       }msgx) {
                                my $key=$1;
                                my $val;
                                if (defined $2) {
@@ -1337,6 +1414,12 @@ sub preprocess ($$$;$$) {
                                elsif (defined $4) {
                                        $val=$4;
                                }
+                               elsif (defined $7) {
+                                       $val=$7;
+                               }
+                               elsif (defined $+{heredoc}) {
+                                       $val=$+{heredoc};
+                               }
 
                                if (defined $key) {
                                        push @params, $key, $val;
@@ -1405,6 +1488,10 @@ sub preprocess ($$$;$$) {
                                                |
                                                "[^"]*?"        # single-quoted value
                                                |
+                                               <<(?<start>[a-zA-Z]+)\n(?<heredoc>.*?)\n\k<start> # heredoc'd value.
+                                               |
+                                               '''.*?''' # triple-single-quoted value
+                                               |
                                                [^"\s\]]+       # unquoted value
                                        )
                                        \s*                     # whitespace or end
@@ -1428,6 +1515,10 @@ sub preprocess ($$$;$$) {
                                                |
                                                "[^"]*?"        # single-quoted value
                                                |
+                                               '''.*?'''       # triple-single-quoted value
+                                               |
+                                               <<(?<start>[a-zA-Z]+)\n(?<heredoc>.*?)\n\k<start> # heredoc'd value.
+                                               |
                                                [^"\s\]]+       # unquoted value
                                        )
                                        \s*                     # whitespace or end
@@ -1455,12 +1546,11 @@ sub filter ($$$) {
        return $content;
 }
 
-sub check_canedit ($$$;$$) {
+sub check_canedit ($$$;$) {
        my $page=shift;
        my $q=shift;
        my $session=shift;
        my $nonfatal=shift;
-       my $nosubs=shift;
        
        my $canedit;
        run_hooks(canedit => sub {
@@ -1471,7 +1561,6 @@ sub check_canedit ($$$;$$) {
                                $canedit=1;
                        }
                        elsif (ref $ret eq 'CODE') {
-                               error(sprintf(gettext("you are not allowed to change %s"), $page)) if $nosubs && ! $nonfatal;
                                $ret->() unless $nonfatal;
                                $canedit=0;
                        }
@@ -1521,6 +1610,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 () {
@@ -1771,12 +1923,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;
@@ -1784,6 +1938,7 @@ sub template_depends ($$;@) {
                },
                loop_context_vars => 1,
                die_on_bad_params => 0,
+               parent_global_vars => 1,
                filename => $filename,
                @_,
                ($untrusted ? (no_includes => 1) : ()),
@@ -1798,39 +1953,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;
@@ -1925,7 +2047,7 @@ sub rcs_recentchanges ($) {
        $hooks{rcs}{rcs_recentchanges}{call}->(@_);
 }
 
-sub rcs_diff ($) {
+sub rcs_diff ($;$) {
        $hooks{rcs}{rcs_diff}{call}->(@_);
 }
 
@@ -2325,7 +2447,7 @@ sub glob2re ($) {
        my $re=quotemeta(shift);
        $re=~s/\\\*/.*/g;
        $re=~s/\\\?/./g;
-       return $re;
+       return qr/^$re$/i;
 }
 
 package IkiWiki::FailReason;
@@ -2419,6 +2541,8 @@ sub derel ($$) {
        return $path;
 }
 
+my %glob_cache;
+
 sub match_glob ($$;@) {
        my $page=shift;
        my $glob=shift;
@@ -2426,8 +2550,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");
                }
@@ -2545,7 +2674,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 {
@@ -2554,7 +2688,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 {
@@ -2563,7 +2702,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 {
@@ -2582,7 +2726,7 @@ sub match_user ($$;@) {
                return IkiWiki::ErrorReason->new("no user specified");
        }
 
-       if (defined $params{user} && $params{user}=~/^$regexp$/i) {
+       if (defined $params{user} && $params{user}=~$regexp) {
                return IkiWiki::SuccessReason->new("user is $user");
        }
        elsif (! defined $params{user}) {