#!/usr/bin/perl -T
+
+eval 'exec /usr/bin/perl -T -S $0 ${1+"$@"}'
+ if 0; # not running under some shell
$ENV{PATH}="/usr/local/bin:/usr/bin:/bin";
use warnings;
# Holds global config settings, also used by some modules.
our %config=( #{{{
wiki_file_prune_regexp => qr{((^|/).svn/|\.\.|^\.|\/\.|\.html?$)},
- wiki_link_regexp => qr/\[\[([^\s]+)\]\]/,
+ wiki_link_regexp => qr/\[\[([^\s\]]+)\]\]/,
wiki_file_regexp => qr/(^[-A-Za-z0-9_.:\/+]+$)/,
verbose => 0,
wikiname => "wiki",
url => '',
cgiurl => '',
historyurl => '',
+ diffurl => '',
anonok => 0,
rebuild => 0,
wrapper => undef,
wrappermode => undef,
srcdir => undef,
destdir => undef,
- templatedir => undef,
+ templatedir => "/usr/share/ikiwiki/templates",
setup => undef,
+ adminuser => undef,
); #}}}
GetOptions( #{{{
"url=s" => \$config{url},
"cgiurl=s" => \$config{cgiurl},
"historyurl=s" => \$config{historyurl},
+ "diffurl=s" => \$config{diffurl},
"exclude=s@" => sub {
$config{wiki_file_prune_regexp}=qr/$config{wiki_file_prune_regexp}|$_[1]/;
},
+ "adminuser=s@" => sub { push @{$config{adminuser}}, $_[1] },
+ "templatedir=s" => sub { $config{templatedir}=possibly_foolish_untaint($_[1]) },
) || usage();
if (! $config{setup}) {
- usage() unless @ARGV == 3;
+ usage() unless @ARGV == 2;
$config{srcdir} = possibly_foolish_untaint(shift);
- $config{templatedir} = possibly_foolish_untaint(shift);
$config{destdir} = possibly_foolish_untaint(shift);
if ($config{cgi} && ! length $config{url}) {
error("Must specify url to wiki with --url when using --cgi");
#}}}
sub usage { #{{{
- die "usage: ikiwiki [options] source templates dest\n";
+ die "usage: ikiwiki [options] source dest\n";
} #}}}
sub error { #{{{
return "<a href=\"$config{url}\">$config{wikiname}</a>";
} #}}}
-sub finalize ($$) { #{{{
+sub finalize ($$$) { #{{{
my $content=shift;
my $page=shift;
+ my $mtime=shift;
my $title=basename($page);
$title=~s/_/ /g;
if (length $config{cgiurl}) {
$template->param(editurl => "$config{cgiurl}?do=edit&page=$page");
+ $template->param(prefsurl => "$config{cgiurl}?do=prefs");
if ($config{svn}) {
$template->param(recentchangesurl => "$config{cgiurl}?do=recentchanges");
}
if (length $config{historyurl}) {
my $u=$config{historyurl};
- $u=~s/\[\[\]\]/$pagesources{$page}/g;
+ $u=~s/\[\[file\]\]/$pagesources{$page}/g;
$template->param(historyurl => $u);
}
content => $content,
backlinks => [backlinks($page)],
discussionlink => htmllink($page, "Discussion", 1, 1),
+ mtime => scalar(gmtime($mtime)),
);
return $template->output;
$content=linkify($content, $page);
$content=htmlize($type, $content);
- $content=finalize($content, $page);
+ $content=finalize($content, $page,
+ mtime("$config{srcdir}/$file"));
check_overwrite("$config{destdir}/".htmlpage($page), $page);
writefile("$config{destdir}/".htmlpage($page), $content);
# else since rcs_prepedit was called.
my ($oldrev)=$rcstoken=~/^([0-9]+)$/; # untaint
my $rev=svn_info("Revision", "$config{srcdir}/$file");
- if ($rev != $oldrev) {
+ if (defined $rev && defined $oldrev && $rev != $oldrev) {
# Merge their changes into the file that we've
# changed.
chdir($config{srcdir}); # svn merge wants to be here
if (system("svn", "commit", "--quiet", "-m",
possibly_foolish_untaint($message),
- "$config{srcdir}/$file") != 0) {
+ "$config{srcdir}") != 0) {
my $conflict=readfile("$config{srcdir}/$file");
if (system("svn", "revert", "--quiet", "$config{srcdir}/$file") != 0) {
warn("svn revert failed\n");
$user=$2;
$when=concise(ago(time - str2time($3)));
}
- elsif ($state eq 'header' && /^\s+[A-Z]\s+\Q$svn_base\E\/(.+)$/) {
- push @pages, { link => htmllink("", pagename($1), 1) }
- if length $1;
+ elsif ($state eq 'header' && /^\s+[A-Z]\s+\Q$svn_base\E\/([^ ]+)(?:$|\s)/) {
+ my $file=$1;
+ my $diffurl=$config{diffurl};
+ $diffurl=~s/\[\[file\]\]/$file/g;
+ $diffurl=~s/\[\[r1\]\]/$rev - 1/eg;
+ $diffurl=~s/\[\[r2\]\]/$rev/g;
+ push @pages, {
+ link => htmllink("", pagename($file), 1),
+ diffurl => $diffurl,
+ } if length $file;
}
elsif ($state eq 'header' && /^$/) {
$state='body';
user => htmllink("", $user, 1),
committype => $committype,
when => $when, message => [@message],
- pages => [@pages] } if @pages;
+ pages => [@pages],
+ } if @pages;
return @ret if @ret >= $num;
$state='header';
error("cannot create a wrapper that uses a setup file");
}
- my @params=($config{srcdir}, $config{templatedir}, $config{destdir},
- "--wikiname=$config{wikiname}");
+ my @params=($config{srcdir}, $config{destdir},
+ "--wikiname=$config{wikiname}",
+ "--templatedir=$config{templatedir}");
push @params, "--verbose" if $config{verbose};
push @params, "--rebuild" if $config{rebuild};
push @params, "--nosvn" if !$config{svn};
push @params, "--url=$config{url}" if length $config{url};
push @params, "--cgiurl=$config{cgiurl}" if length $config{cgiurl};
push @params, "--historyurl=$config{historyurl}" if length $config{historyurl};
+ push @params, "--diffurl=$config{diffurl}" if length $config{diffurl};
push @params, "--anonok" if $config{anonok};
+ push @params, "--adminuser=$_" foreach @{$config{adminuser}};
my $params=join(" ", @params);
my $call='';
foreach my $p ($this, $this, @params) {
eval q{use Storable};
my $userdata=eval{ Storable::lock_retrieve("$config{srcdir}/.ikiwiki/userdb") };
if (! defined $userdata || ! ref $userdata ||
- ! exists $userdata->{$user} || ! ref $userdata->{$user}) {
+ ! exists $userdata->{$user} || ! ref $userdata->{$user} ||
+ ! exists $userdata->{$user}->{$field}) {
return "";
}
return $userdata->{$user}->{$field};
} #}}}
-sub userinfo_set ($$) { #{{{
+sub userinfo_set ($$$) { #{{{
+ my $user=shift;
+ my $field=shift;
+ my $value=shift;
+
+ eval q{use Storable};
+ my $userdata=eval{ Storable::lock_retrieve("$config{srcdir}/.ikiwiki/userdb") };
+ if (! defined $userdata || ! ref $userdata ||
+ ! exists $userdata->{$user} || ! ref $userdata->{$user}) {
+ return "";
+ }
+
+ $userdata->{$user}->{$field}=$value;
+ my $oldmask=umask(077);
+ my $ret=Storable::lock_store($userdata, "$config{srcdir}/.ikiwiki/userdb");
+ umask($oldmask);
+ return $ret;
+} #}}}
+
+sub userinfo_setall ($$) { #{{{
my $user=shift;
my $info=shift;
eval q{use CGI::FormBuilder};
my $form = CGI::FormBuilder->new(
- title => "$config{wikiname} signin",
+ title => "signin",
fields => [qw(do page from name password confirm_password email)],
header => 1,
method => 'POST',
$form->field(name => "confirm_password", type => "password", required => 0);
$form->field(name => "email", required => 0);
if ($q->param("do") ne "signin") {
- $form->text("You need to log in before you can edit pages.");
+ $form->text("You need to log in first.");
}
if ($form->submitted) {
}
elsif ($form->submitted eq 'Register') {
my $user_name=$form->field('name');
- if (userinfo_set($user_name, {
+ if (userinfo_setall($user_name, {
'email' => $form->field('email'),
'password' => $form->field('password'),
'regdate' => time
}
} #}}}
+sub is_admin ($) { #{{{
+ my $user_name=shift;
+
+ return grep { $_ eq $user_name } @{$config{adminuser}};
+} #}}}
+
+sub glob_match ($$) { #{{{
+ my $page=shift;
+ my $glob=shift;
+
+ # turn glob into safe regexp
+ $glob=quotemeta($glob);
+ $glob=~s/\\\*/.*/g;
+ $glob=~s/\\\?/./g;
+ $glob=~s!\\/!/!g;
+
+ $page=~/^$glob$/i;
+} #}}}
+
+sub globlist_match ($$) { #{{{
+ my $page=shift;
+ my @globlist=split(" ", shift);
+
+ # check any negated globs first
+ foreach my $glob (@globlist) {
+ return 0 if $glob=~/^!(.*)/ && glob_match($page, $1);
+ }
+
+ foreach my $glob (@globlist) {
+ return 1 if glob_match($page, $glob);
+ }
+
+ return 0;
+} #}}}
+
+sub page_locked ($$;$) { #{{{
+ my $page=shift;
+ my $session=shift;
+ my $nonfatal=shift;
+
+ my $user=$session->param("name");
+ return if length $user && is_admin($user);
+
+ foreach my $admin (@{$config{adminuser}}) {
+ my $locked_pages=userinfo_get($admin, "locked_pages");
+ if (globlist_match($page, userinfo_get($admin, "locked_pages"))) {
+ return 1 if $nonfatal;
+ error(htmllink("", $page, 1)." is locked by ".
+ htmllink("", $admin, 1)." and cannot be edited.");
+ }
+ }
+
+ return 0;
+} #}}}
+
+sub cgi_prefs ($$) { #{{{
+ my $q=shift;
+ my $session=shift;
+
+ eval q{use CGI::FormBuilder};
+ my $form = CGI::FormBuilder->new(
+ title => "preferences",
+ fields => [qw(do name password confirm_password email locked_pages)],
+ header => 0,
+ method => 'POST',
+ validate => {
+ confirm_password => {
+ perl => q{eq $form->field("password")},
+ },
+ email => 'EMAIL',
+ },
+ required => 'NONE',
+ javascript => 0,
+ params => $q,
+ action => $q->request_uri,
+ template => (-e "$config{templatedir}/prefs.tmpl" ?
+ "$config{templatedir}/prefs.tmpl" : "")
+ );
+ my @buttons=("Save Preferences", "Logout", "Cancel");
+
+ my $user_name=$session->param("name");
+ $form->field(name => "do", type => "hidden");
+ $form->field(name => "name", disabled => 1,
+ value => $user_name, force => 1);
+ $form->field(name => "password", type => "password");
+ $form->field(name => "confirm_password", type => "password");
+ $form->field(name => "locked_pages", size => 50,
+ comment => "(".htmllink("", "GlobList", 1).")");
+
+ if (! is_admin($user_name)) {
+ $form->field(name => "locked_pages", type => "hidden");
+ }
+
+ if (! $form->submitted) {
+ $form->field(name => "email", force => 1,
+ value => userinfo_get($user_name, "email"));
+ $form->field(name => "locked_pages", force => 1,
+ value => userinfo_get($user_name, "locked_pages"));
+ }
+
+ if ($form->submitted eq 'Logout') {
+ $session->delete();
+ print $q->redirect($config{url});
+ return;
+ }
+ elsif ($form->submitted eq 'Cancel') {
+ print $q->redirect($config{url});
+ return;
+ }
+ elsif ($form->submitted eq "Save Preferences" && $form->validate) {
+ foreach my $field (qw(password email locked_pages)) {
+ if (length $form->field($field)) {
+ userinfo_set($user_name, $field, $form->field($field)) || error("failed to set $field");
+ }
+ }
+ $form->text("Preferences saved.");
+ }
+
+ print $session->header();
+ print misctemplate($form->title, $form->render(submit => \@buttons));
+} #}}}
+
sub cgi_editpage ($$) { #{{{
my $q=shift;
my $session=shift;
else {
my $dir=$from."/";
$dir=~s![^/]+/$!!;
+
+ if ($page eq 'discussion') {
+ $best_loc="$from/$page";
+ }
+ else {
+ $best_loc=$dir.$page;
+ }
+
push @page_locs, $dir.$page;
push @page_locs, "$from/$page";
- $best_loc="$from/$page";
while (length $dir) {
$dir=~s![^/]+/$!!;
push @page_locs, $dir.$page;
}
- @page_locs = grep { ! exists
- $pagesources{lc($_)} } @page_locs;
+ @page_locs = grep {
+ ! exists $pagesources{lc($_)} &&
+ ! page_locked($_, $session, 1)
+ } @page_locs;
}
$form->tmpl_param("page_select", 1);
$form->title("creating $page");
}
elsif ($form->field("do") eq "edit") {
+ page_locked($page, $session);
if (! defined $form->field('content') ||
! length $form->field('content')) {
my $content="";
}
else {
# save page
+ page_locked($page, $session);
+
my $content=$form->field('content');
$content=~s/\r\n/\n/g;
$content=~s/\r/\n/g;
writefile("$config{srcdir}/$file", $content);
my $message="web commit ";
- if ($session->param("name")) {
+ if (length $session->param("name")) {
$message.="by ".$session->param("name");
}
else {
# Everything below this point needs the user to be signed in.
if ((! $config{anonok} && ! defined $session->param("name") ||
- ! userinfo_get($session->param("name"), "regdate")) || $do eq 'signin') {
+ ! defined $session->param("name") ||
+ ! userinfo_get($session->param("name"), "regdate")) || $do eq 'signin') {
cgi_signin($q, $session);
# Force session flush with safe umask.
if ($do eq 'create' || $do eq 'edit') {
cgi_editpage($q, $session);
}
+ elsif ($do eq 'prefs') {
+ cgi_prefs($q, $session);
+ }
else {
error("unknown do parameter");
}
} #}}}
# main {{{
-lockwiki();
setup() if $config{setup};
+lockwiki();
if ($config{wrapper}) {
gen_wrapper(%config);
exit;