From: Joey Hess Date: Wed, 23 Jul 2008 19:00:07 +0000 (-0400) Subject: Merge branch 'master' into tova X-Git-Url: https://sipb.mit.edu/gitweb.cgi/ikiwiki.git/commitdiff_plain/335a6a59e66ee7c2cf0c68c659259b885f7e8a07?hp=1d1767192c80548755655cfe1ce8e503b392c7c1 Merge branch 'master' into tova --- diff --git a/IkiWiki/CGI.pm b/IkiWiki/CGI.pm index d805506aa..4cb45895b 100644 --- a/IkiWiki/CGI.pm +++ b/IkiWiki/CGI.pm @@ -416,7 +416,6 @@ sub cgi_editpage ($$) { #{{{ elsif ($form->submitted eq "Save Page") { $form->tmpl_param("page_preview", ""); } - $form->tmpl_param("page_conflict", ""); if ($form->submitted ne "Save Page" || ! $form->validate) { if ($form->field("do") eq "create") { @@ -532,7 +531,7 @@ sub cgi_editpage ($$) { #{{{ if ($form->field("do") ne "create" && ! $exists && ! defined srcfile($file, 1)) { - $form->tmpl_param("page_gone", 1); + $form->tmpl_param("message", template("editpagegone.tmpl")->output); $form->field(name => "do", value => "create", force => 1); $form->tmpl_param("page_select", 0); $form->field(name => "page", type => 'hidden'); @@ -542,7 +541,7 @@ sub cgi_editpage ($$) { #{{{ return; } elsif ($form->field("do") eq "create" && $exists) { - $form->tmpl_param("creation_conflict", 1); + $form->tmpl_param("message", template("editcreationconflict.tmpl")->output); $form->field(name => "do", value => "edit", force => 1); $form->tmpl_param("page_select", 0); $form->field(name => "page", type => 'hidden'); @@ -575,8 +574,9 @@ sub cgi_editpage ($$) { #{{{ if ($@) { $form->field(name => "rcsinfo", value => rcs_prepedit($file), force => 1); - $form->tmpl_param("failed_save", 1); - $form->tmpl_param("error_message", $@); + my $mtemplate=template("editfailedsave.tmpl"); + $mtemplate->param(error_message => $@); + $form->tmpl_param("message", $mtemplate->output); $form->field("editcontent", value => $content, force => 1); $form->tmpl_param("page_select", 0); $form->field(name => "page", type => 'hidden'); @@ -620,7 +620,7 @@ sub cgi_editpage ($$) { #{{{ if (defined $conflict) { $form->field(name => "rcsinfo", value => rcs_prepedit($file), force => 1); - $form->tmpl_param("page_conflict", 1); + $form->tmpl_param("message", template("editconflict.tmpl")->output); $form->field("editcontent", value => $conflict, force => 1); $form->field("do", "edit", force => 1); $form->tmpl_param("page_select", 0); diff --git a/IkiWiki/Plugin/attachment.pm b/IkiWiki/Plugin/attachment.pm index 3982c4883..e08aa3677 100644 --- a/IkiWiki/Plugin/attachment.pm +++ b/IkiWiki/Plugin/attachment.pm @@ -11,6 +11,40 @@ sub import { #{{{ hook(type => "formbuilder", id => "attachment", call => \&formbuilder); } # }}} +sub check_canattach ($$;$) { + my $session=shift; + my $dest=shift; # where it's going to be put, under the srcdir + my $file=shift; # the path to the attachment currently + + # Don't allow an attachment to be uploaded with the same name as an + # existing page. + if (exists $pagesources{$dest} && $pagesources{$dest} ne $dest) { + error(sprintf(gettext("there is already a page named %s"), $dest)); + } + + # Use a special pagespec to test that the attachment is valid. + my $allowed=1; + foreach my $admin (@{$config{adminuser}}) { + my $allowed_attachments=IkiWiki::userinfo_get($admin, "allowed_attachments"); + if (defined $allowed_attachments && + length $allowed_attachments) { + $allowed=pagespec_match($dest, + $allowed_attachments, + file => $file, + user => $session->param("name"), + ip => $ENV{REMOTE_ADDR}, + ); + last if $allowed; + } + } + if (! $allowed) { + error(gettext("prohibited by allowed_attachments")." ($allowed)"); + } + else { + return 1; + } +} + sub checkconfig () { #{{{ $config{cgi_disable_uploads}=0; } #}}} @@ -113,25 +147,8 @@ sub formbuilder (@) { #{{{ # Check that the user is allowed to edit a page with the # name of the attachment. IkiWiki::check_canedit($filename, $q, $session, 1); - - # Use a special pagespec to test that the attachment is valid. - my $allowed=1; - foreach my $admin (@{$config{adminuser}}) { - my $allowed_attachments=IkiWiki::userinfo_get($admin, "allowed_attachments"); - if (defined $allowed_attachments && - length $allowed_attachments) { - $allowed=pagespec_match($filename, - $allowed_attachments, - file => $tempfile, - user => $session->param("name"), - ip => $ENV{REMOTE_ADDR}, - ); - last if $allowed; - } - } - if (! $allowed) { - error(gettext("attachment rejected")." ($allowed)"); - } + # And that the attachment itself is acceptable. + check_canattach($session, $filename, $tempfile); # Needed for fast_file_copy and for rendering below. require IkiWiki::Render; @@ -419,6 +436,9 @@ sub match_user ($$;@) { #{{{ 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"); } diff --git a/IkiWiki/Plugin/img.pm b/IkiWiki/Plugin/img.pm index 17a9367d3..748d28ace 100644 --- a/IkiWiki/Plugin/img.pm +++ b/IkiWiki/Plugin/img.pm @@ -41,6 +41,10 @@ sub preprocess (@) { #{{{ } my $file = bestlink($params{page}, $image); + my $srcfile = srcfile($file, 1); + if (! length $file || ! defined $srcfile) { + return htmllink($params{page}, $params{destpage}, $image); + } my $dir = $params{page}; my $base = IkiWiki::basename($file); @@ -61,12 +65,12 @@ sub preprocess (@) { #{{{ will_render($params{page}, $imglink); - if (-e $outfile && (-M srcfile($file) >= -M $outfile)) { + if (-e $outfile && (-M $srcfile >= -M $outfile)) { $r = $im->Read($outfile); error sprintf(gettext("failed to read %s: %s"), $outfile, $r) if $r; } else { - $r = $im->Read(srcfile($file)); + $r = $im->Read($srcfile); error sprintf(gettext("failed to read %s: %s"), $file, $r) if $r; $r = $im->Resize(geometry => "${w}x${h}"); @@ -83,7 +87,7 @@ sub preprocess (@) { #{{{ } } else { - $r = $im->Read(srcfile($file)); + $r = $im->Read($srcfile); error sprintf(gettext("failed to read %s: %s"), $file, $r) if $r; $imglink = $file; } diff --git a/IkiWiki/Plugin/remove.pm b/IkiWiki/Plugin/remove.pm new file mode 100644 index 000000000..4c73ed9e5 --- /dev/null +++ b/IkiWiki/Plugin/remove.pm @@ -0,0 +1,212 @@ +#!/usr/bin/perl +package IkiWiki::Plugin::remove; + +use warnings; +use strict; +use IkiWiki 2.00; + +sub import { #{{{ + hook(type => "formbuilder_setup", id => "remove", call => \&formbuilder_setup); + hook(type => "formbuilder", id => "remove", call => \&formbuilder); + hook(type => "sessioncgi", id => "remove", call => \&sessioncgi); + +} # }}} + +sub check_canremove ($$$$) { + my $page=shift; + my $q=shift; + my $session=shift; + my $attachment=shift; + + # Must be a known source file. + if (! exists $pagesources{$page}) { + error(sprintf(gettext("%s does not exist"), + htmllink("", "", $page, noimageinline => 1))); + } + + # Must exist on disk, and be a regular file. + my $file=$pagesources{$page}; + if (! -e "$config{srcdir}/$file") { + error(sprintf(gettext("%s is not in the srcdir, so it cannot be deleted"), $file)); + } + elsif (-l "$config{srcdir}/$file" && ! -f _) { + error(sprintf(gettext("%s is not a file"), $file)); + } + + # Must be editiable. + IkiWiki::check_canedit($page, $q, $session); + + # This is sorta overkill, but better safe + # than sorry. If a user can't upload an + # attachment, don't let them delete it. + if ($attachment) { + IkiWiki::Plugin::attachment::check_canattach($session, $page, $file); + } +} + +sub formbuilder_setup (@) { #{{{ + my %params=@_; + my $form=$params{form}; + my $q=$params{cgi}; + + if (defined $form->field("do") && $form->field("do") eq "edit") { + # Removal button for the page, and also for attachments. + push @{$params{buttons}}, "Remove"; + $form->tmpl_param("field-remove" => ''); + } +} #}}} + +sub confirmation_form ($$) { #{{{ + my $q=shift; + my $session=shift; + + eval q{use CGI::FormBuilder}; + error($@) if $@; + my $f = CGI::FormBuilder->new( + name => "remove", + header => 0, + charset => "utf-8", + method => 'POST', + javascript => 0, + params => $q, + action => $config{cgiurl}, + stylesheet => IkiWiki::baseurl()."style.css", + fields => [qw{do page}], + ); + + $f->field(name => "do", type => "hidden", value => "remove", force => 1); + + return $f, ["Remove", "Cancel"]; +} #}}} + +sub removal_confirm ($$@) { + my $q=shift; + my $session=shift; + my $attachment=shift; + my @pages=@_; + + check_canremove($_, $q, $session, $attachment) foreach @pages; + + # Save current form state to allow returning to it later + # without losing any edits. + # (But don't save what button was submitted, to avoid + # looping back to here.) + # Note: "_submit" is CGI::FormBuilder internals. + $q->param(-name => "_submit", -value => ""); + $session->param(postremove => scalar $q->Vars); + IkiWiki::cgi_savesession($session); + + my ($f, $buttons)=confirmation_form($q, $session); + $f->title(sprintf(gettext("confirm removal of %s"), + join(", ", map { IkiWiki::pagetitle($_) } @pages))); + $f->field(name => "page", type => "hidden", value => \@pages, force => 1); + if (defined $attachment) { + $f->field(name => "attachment", type => "hidden", + value => $attachment, force => 1); + } + + IkiWiki::showform($f, $buttons, $session, $q); + exit 0; +} + +sub postremove ($) { + my $session=shift; + + # Load saved form state and return to edit form. + my $postremove=CGI->new($session->param("postremove")); + $session->clear("postremove"); + IkiWiki::cgi_savesession($session); + IkiWiki::cgi($postremove, $session); +} + +sub formbuilder (@) { #{{{ + my %params=@_; + my $form=$params{form}; + + if (defined $form->field("do") && $form->field("do") eq "edit") { + my $q=$params{cgi}; + my $session=$params{session}; + + if ($form->submitted eq "Remove") { + removal_confirm($q, $session, 0, $form->field("page")); + } + elsif ($form->submitted eq "Remove Attachments") { + my @selected=$q->param("attachment_select"); + if (! @selected) { + error(gettext("Please select the attachments to remove.")); + } + removal_confirm($q, $session, 1, @selected); + } + } +} #}}} + +sub sessioncgi ($$) { #{{{ + my $q=shift; + + if ($q->param("do") eq 'remove') { + my $session=shift; + my ($form, $buttons)=confirmation_form($q, $session); + IkiWiki::decode_form_utf8($form); + + if ($form->submitted eq 'Cancel') { + postremove($session); + } + elsif ($form->submitted eq 'Remove' && $form->validate) { + my @pages=$q->param("page"); + + # Validate removal by checking that the page exists, + # and that the user is allowed to edit(/remove) it. + my @files; + foreach my $page (@pages) { + check_canremove($page, $q, $session, $q->param("attachment")); + + # This untaint is safe because of the + # checks performed above, which verify the + # page is a normal file, etc. + push @files, IkiWiki::possibly_foolish_untaint($pagesources{$page}); + } + + # Do removal, and update the wiki. + require IkiWiki::Render; + if ($config{rcs}) { + IkiWiki::disable_commit_hook(); + foreach my $file (@files) { + IkiWiki::rcs_remove($file); + } + IkiWiki::rcs_commit_staged(gettext("removed"), + $session->param("name"), $ENV{REMOTE_ADDR}); + IkiWiki::enable_commit_hook(); + IkiWiki::rcs_update(); + } + else { + foreach my $file (@files) { + IkiWiki::prune("$config{srcdir}/$file"); + } + } + IkiWiki::refresh(); + IkiWiki::saveindex(); + + if ($q->param("attachment")) { + # Attachments were deleted, so redirect + # back to the edit form. + postremove($session); + } + else { + # The page is gone, so redirect to parent + # of the page. + my $parent=IkiWiki::dirname($pages[0]); + if (! exists $pagesources{$parent}) { + $parent="index"; + } + IkiWiki::redirect($q, $config{url}."/".htmlpage($parent)); + } + } + else { + IkiWiki::showform($form, $buttons, $session, $q); + } + + exit 0; + } +} + +1 diff --git a/IkiWiki/Plugin/rename.pm b/IkiWiki/Plugin/rename.pm new file mode 100644 index 000000000..38f703ddd --- /dev/null +++ b/IkiWiki/Plugin/rename.pm @@ -0,0 +1,285 @@ +#!/usr/bin/perl +package IkiWiki::Plugin::rename; + +use warnings; +use strict; +use IkiWiki 2.00; + +sub import { #{{{ + hook(type => "formbuilder_setup", id => "rename", call => \&formbuilder_setup); + hook(type => "formbuilder", id => "rename", call => \&formbuilder); + hook(type => "sessioncgi", id => "rename", call => \&sessioncgi); + +} # }}} + +sub check_canrename ($$$$$$$) { #{{{ + my $src=shift; + my $srcfile=shift; + my $dest=shift; + my $destfile=shift; + my $q=shift; + my $session=shift; + my $attachment=shift; + + # Must be a known source file. + if (! exists $pagesources{$src}) { + error(sprintf(gettext("%s does not exist"), + htmllink("", "", $src, noimageinline => 1))); + } + + # Must exist on disk, and be a regular file. + if (! -e "$config{srcdir}/$srcfile") { + error(sprintf(gettext("%s is not in the srcdir, so it cannot be renamed"), $srcfile)); + } + elsif (-l "$config{srcdir}/$srcfile" && ! -f _) { + error(sprintf(gettext("%s is not a file"), $srcfile)); + } + + # Must be editable. + IkiWiki::check_canedit($src, $q, $session); + if ($attachment) { + IkiWiki::Plugin::attachment::check_canattach($session, $src, $srcfile); + } + + # Dest checks can be omitted by passing undef. + if (defined $dest) { + if ($src eq $dest || $srcfile eq $destfile) { + error(gettext("no change to the file name was specified")); + } + + # Must be a legal filename, and not absolute. + if (IkiWiki::file_pruned($destfile, $config{srcdir}) || + $destfile=~/^\//) { + error(sprintf(gettext("illegal name"))); + } + + # Must not be a known source file. + if (exists $pagesources{$dest}) { + error(sprintf(gettext("%s already exists"), + htmllink("", "", $dest, noimageinline => 1))); + } + + # Must not exist on disk already. + if (-l "$config{srcdir}/$destfile" || -e _) { + error(sprintf(gettext("%s already exists on disk"), $destfile)); + } + + # Must be editable. + IkiWiki::check_canedit($dest, $q, $session); + if ($attachment) { + # Note that $srcfile is used here, not $destfile, + # because it wants the current file, to check it. + IkiWiki::Plugin::attachment::check_canattach($session, $dest, $srcfile); + } + } +} #}}} + +sub rename_form ($$$) { #{{{ + my $q=shift; + my $session=shift; + my $page=shift; + + eval q{use CGI::FormBuilder}; + error($@) if $@; + my $f = CGI::FormBuilder->new( + name => "rename", + title => sprintf(gettext("rename %s"), IkiWiki::pagetitle($page)), + header => 0, + charset => "utf-8", + method => 'POST', + javascript => 0, + params => $q, + action => $config{cgiurl}, + stylesheet => IkiWiki::baseurl()."style.css", + fields => [qw{do page new_name attachment}], + ); + + $f->field(name => "do", type => "hidden", value => "rename", force => 1); + $f->field(name => "page", type => "hidden", value => $page, force => 1); + $f->field(name => "new_name", value => IkiWiki::pagetitle($page), size => 60); + $f->field(name => "attachment", type => "hidden"); + + return $f, ["Rename", "Cancel"]; +} #}}} + +sub rename_start ($$$$) { + my $q=shift; + my $session=shift; + my $attachment=shift; + my $page=shift; + + check_canrename($page, $pagesources{$page}, undef, undef, + $q, $session, $attachment); + + # Save current form state to allow returning to it later + # without losing any edits. + # (But don't save what button was submitted, to avoid + # looping back to here.) + # Note: "_submit" is CGI::FormBuilder internals. + $q->param(-name => "_submit", -value => ""); + $session->param(postrename => scalar $q->Vars); + IkiWiki::cgi_savesession($session); + + my ($f, $buttons)=rename_form($q, $session, $page); + if (defined $attachment) { + $f->field(name => "attachment", value => $attachment, force => 1); + } + + IkiWiki::showform($f, $buttons, $session, $q); + exit 0; +} + +sub postrename ($;$$) { + my $session=shift; + my $dest=shift; + my $attachment=shift; + + # Load saved form state and return to edit page. + my $postrename=CGI->new($session->param("postrename")); + $session->clear("postrename"); + IkiWiki::cgi_savesession($session); + + if (defined $dest && ! $attachment) { + # They renamed the page they were editing. This requires + # fixups to the edit form state. + # Tweak the edit form to be editing the new page. + $postrename->param("page", $dest); + # Get a new edit token; old one might not be valid for the + # renamed file. + $postrename->param("rcsinfo", IkiWiki::rcs_prepedit($pagesources{$dest})); + } + + IkiWiki::cgi_editpage($postrename, $session); +} + +sub formbuilder (@) { #{{{ + my %params=@_; + my $form=$params{form}; + + if (defined $form->field("do") && $form->field("do") eq "edit") { + my $q=$params{cgi}; + my $session=$params{session}; + + if ($form->submitted eq "Rename") { + rename_start($q, $session, 0, $form->field("page")); + } + elsif ($form->submitted eq "Rename Attachment") { + my @selected=$q->param("attachment_select"); + if (@selected > 1) { + error(gettext("Only one attachment can be renamed at a time.")); + } + elsif (! @selected) { + error(gettext("Please select the attachment to rename.")) + } + rename_start($q, $session, 1, $selected[0]); + } + } +} #}}} + +my $renamesummary; + +sub formbuilder_setup (@) { #{{{ + my %params=@_; + my $form=$params{form}; + my $q=$params{cgi}; + + if (defined $form->field("do") && $form->field("do") eq "edit") { + # Rename button for the page, and also for attachments. + push @{$params{buttons}}, "Rename"; + $form->tmpl_param("field-rename" => ''); + + if (defined $renamesummary) { + $form->tmpl_param(message => $renamesummary); + } + } +} #}}} + +sub sessioncgi ($$) { #{{{ + my $q=shift; + + if ($q->param("do") eq 'rename') { + my $session=shift; + my ($form, $buttons)=rename_form($q, $session, $q->param("page")); + IkiWiki::decode_form_utf8($form); + + if ($form->submitted eq 'Cancel') { + postrename($session); + } + elsif ($form->submitted eq 'Rename' && $form->validate) { + # These untaints are safe because of the checks + # performed in check_canrename below. + my $src=$q->param("page"); + my $srcfile=IkiWiki::possibly_foolish_untaint($pagesources{$src}); + my $dest=IkiWiki::possibly_foolish_untaint(IkiWiki::titlepage($q->param("new_name"))); + + # The extension of dest is the same as src if it's + # a page. If it's an extension, the extension is + # already included. + my $destfile=$dest; + if (! $q->param("attachment")) { + my ($ext)=$srcfile=~/(\.[^.]+)$/; + $destfile.=$ext; + } + + check_canrename($src, $srcfile, $dest, $destfile, + $q, $session, $q->param("attachment")); + + # Ensures that the dest directory exists and is ok. + IkiWiki::prep_writefile($destfile, $config{srcdir}); + + # Do rename, and update the wiki. + require IkiWiki::Render; + if ($config{rcs}) { + IkiWiki::disable_commit_hook(); + IkiWiki::rcs_rename($srcfile, $destfile); + IkiWiki::rcs_commit_staged( + sprintf(gettext("rename %s to %s"), $src, $dest), + $session->param("name"), $ENV{REMOTE_ADDR}); + IkiWiki::enable_commit_hook(); + IkiWiki::rcs_update(); + } + else { + if (! rename("$config{srcdir}/$srcfile", "$config{srcdir}/$destfile")) { + error("rename: $!"); + } + } + IkiWiki::refresh(); + IkiWiki::saveindex(); + + # scan for broken links to $src + my @brokenlinks; + foreach my $page (keys %links) { + foreach my $link (@{$links{$page}}) { + my $bestlink=bestlink($page, $link); + if ($bestlink eq $src) { + push @brokenlinks, $page; + } + } + } + + # Generate a rename summary, that will be shown at the top + # of the edit template. + my $template=template("renamesummary.tmpl"); + $template->param(src => $src); + $template->param(dest => $dest); + $template->param(linklist => [ + map { + { + page => htmllink($dest, $dest, $_, + noimageinline => 1) + } + } @brokenlinks + ]); + $renamesummary=$template->output; + + postrename($session, $dest, $q->param("attachment")); + } + else { + IkiWiki::showform($form, $buttons, $session, $q); + } + + exit 0; + } +} + +1 diff --git a/IkiWiki/Rcs/Stub.pm b/IkiWiki/Rcs/Stub.pm index 6b69e65dc..43a2f2029 100644 --- a/IkiWiki/Rcs/Stub.pm +++ b/IkiWiki/Rcs/Stub.pm @@ -24,6 +24,14 @@ sub rcs_commit ($$$;$$) { # Tries to commit the page; returns undef on _success_ and # a version of the page with the rcs's conflict markers on failure. # The file is relative to the srcdir. + my ($file, $message, $rcstoken, $user, $ipaddr) = @_; + return undef # success +} + +sub rcs_commit_staged ($$$) { + # Commits all staged changes. Changes can be staged using rcs_add, + # rcs_remove, and rcs_rename. + my ($message, $user, $ipaddr)=@_; return undef # success } @@ -31,6 +39,25 @@ sub rcs_add ($) { # Add a file. The filename is relative to the root of the srcdir. # Note that this should not check the new file in, it should only # prepare for it to be checked in when rcs_commit is called. + # Note that the file may be in a new subdir that is not yet added + # to version control; the subdir can be added if so. +} + +sub rcs_remove ($) { + # Remove a file. The filename is relative to the root of the srcdir. + # Note that this should not check the removal in, it should only + # prepare for it to be checked in when rcs_commit is called. + # Note that the new file may be in a new subdir that is not yet added + # to version control; the subdir can be added if so. +} + +sub rcs_rename ($$) { + # Rename a file. The filenames are relative to the root of the srcdir. + # Note that this should not commit the rename, it should only + # prepare it for when rcs_commit is called. + # The new filename may be in a new subdir, that is not yet added to + # version control. If so, the subdir will exist already, and should + # be added. } sub rcs_recentchanges ($) { diff --git a/IkiWiki/Rcs/bzr.pm b/IkiWiki/Rcs/bzr.pm index 0dc456de2..e414e85d2 100644 --- a/IkiWiki/Rcs/bzr.pm +++ b/IkiWiki/Rcs/bzr.pm @@ -80,6 +80,14 @@ sub rcs_commit ($$$;$$) { #{{{ return undef; # success } #}}} +sub rcs_commit_staged ($$$) { + # Commits all staged changes. Changes can be staged using rcs_add, + # rcs_remove, and rcs_rename. + my ($message, $user, $ipaddr)=@_; + + error("rcs_commit_staged not implemented for bzr"); # TODO +} + sub rcs_add ($) { # {{{ my ($file) = @_; @@ -89,6 +97,18 @@ sub rcs_add ($) { # {{{ } } #}}} +sub rcs_remove ($) { # {{{ + my ($file) = @_; + + error("rcs_remove not implemented for bzr"); # TODO +} #}}} + +sub rcs_rename ($$) { # {{{ + my ($src, $dest) = @_; + + error("rcs_rename not implemented for bzr"); # TODO +} #}}} + sub rcs_recentchanges ($) { #{{{ my ($num) = @_; diff --git a/IkiWiki/Rcs/git.pm b/IkiWiki/Rcs/git.pm index 7fb612a39..ecf560d0b 100644 --- a/IkiWiki/Rcs/git.pm +++ b/IkiWiki/Rcs/git.pm @@ -318,7 +318,16 @@ sub rcs_commit ($$$;$$) { #{{{ my $conflict = _merge_past($prev, $file, $dummy_commit_msg); return $conflict if defined $conflict; } - + + rcs_add($file); + return rcs_commit_staged($message, $user, $ipaddr); +} #}}} + +sub rcs_commit_staged ($$$) { + # Commits all staged changes. Changes can be staged using rcs_add, + # rcs_remove, and rcs_rename. + my ($message, $user, $ipaddr)=@_; + # Set the commit author and email to the web committer. my %env=%ENV; if (defined $user || defined $ipaddr) { @@ -330,7 +339,8 @@ sub rcs_commit ($$$;$$) { #{{{ # git commit returns non-zero if file has not been really changed. # so we should ignore its exit status (hence run_or_non). $message = possibly_foolish_untaint($message); - if (run_or_non('git', 'commit', '--cleanup=verbatim', '-q', '-m', $message, '-i', $file)) { + if (run_or_non('git', 'commit', '--cleanup=verbatim', + '-q', '-m', $message)) { if (length $config{gitorigin_branch}) { run_or_cry('git', 'push', $config{gitorigin_branch}); } @@ -338,7 +348,7 @@ sub rcs_commit ($$$;$$) { #{{{ %ENV=%env; return undef; # success -} #}}} +} sub rcs_add ($) { # {{{ # Add file to archive. @@ -348,6 +358,20 @@ sub rcs_add ($) { # {{{ run_or_cry('git', 'add', $file); } #}}} +sub rcs_remove ($) { # {{{ + # Remove file from archive. + + my ($file) = @_; + + run_or_cry('git', 'rm', '-f', $file); +} #}}} + +sub rcs_rename ($$) { # {{{ + my ($src, $dest) = @_; + + run_or_cry('git', 'mv', '-f', $src, $dest); +} #}}} + sub rcs_recentchanges ($) { #{{{ # List of recent changes. diff --git a/IkiWiki/Rcs/mercurial.pm b/IkiWiki/Rcs/mercurial.pm index bfe6ba49c..8c3f03e07 100644 --- a/IkiWiki/Rcs/mercurial.pm +++ b/IkiWiki/Rcs/mercurial.pm @@ -92,6 +92,14 @@ sub rcs_commit ($$$;$$) { #{{{ return undef; # success } #}}} +sub rcs_commit_staged ($$$) { + # Commits all staged changes. Changes can be staged using rcs_add, + # rcs_remove, and rcs_rename. + my ($message, $user, $ipaddr)=@_; + + error("rcs_commit_staged not implemented for mercurial"); # TODO +} + sub rcs_add ($) { # {{{ my ($file) = @_; @@ -101,6 +109,18 @@ sub rcs_add ($) { # {{{ } } #}}} +sub rcs_remove ($) { # {{{ + my ($file) = @_; + + error("rcs_remove not implemented for mercurial"); # TODO +} #}}} + +sub rcs_rename ($$) { # {{{ + my ($src, $dest) = @_; + + error("rcs_rename not implemented for mercurial"); # TODO +} #}}} + sub rcs_recentchanges ($) { #{{{ my ($num) = @_; diff --git a/IkiWiki/Rcs/monotone.pm b/IkiWiki/Rcs/monotone.pm index ce4a2a3ed..97d9c7a30 100644 --- a/IkiWiki/Rcs/monotone.pm +++ b/IkiWiki/Rcs/monotone.pm @@ -359,6 +359,14 @@ sub rcs_commit ($$$;$$) { #{{{ return undef # success } #}}} +sub rcs_commit_staged ($$$) { + # Commits all staged changes. Changes can be staged using rcs_add, + # rcs_remove, and rcs_rename. + my ($message, $user, $ipaddr)=@_; + + error("rcs_commit_staged not implemented for monotone"); # TODO +} + sub rcs_add ($) { #{{{ my $file=shift; @@ -370,6 +378,18 @@ sub rcs_add ($) { #{{{ } } #}}} +sub rcs_remove ($) { # {{{ + my $file = shift; + + error("rcs_remove not implemented for monotone"); # TODO +} #}}} + +sub rcs_rename ($$) { # {{{ + my ($src, $dest) = @_; + + error("rcs_rename not implemented for monotone"); # TODO +} #}}} + sub rcs_recentchanges ($) { #{{{ my $num=shift; my @ret; diff --git a/IkiWiki/Rcs/svn.pm b/IkiWiki/Rcs/svn.pm index 6a822e896..9081c3902 100644 --- a/IkiWiki/Rcs/svn.pm +++ b/IkiWiki/Rcs/svn.pm @@ -117,6 +117,28 @@ sub rcs_commit ($$$;$$) { #{{{ return undef # success } #}}} +sub rcs_commit_staged ($$$) { + # Commits all staged changes. Changes can be staged using rcs_add, + # rcs_remove, and rcs_rename. + my ($message, $user, $ipaddr)=@_; + + if (defined $user) { + $message="web commit by $user".(length $message ? ": $message" : ""); + } + elsif (defined $ipaddr) { + $message="web commit from $ipaddr".(length $message ? ": $message" : ""); + } + + if (system("svn", "commit", "--quiet", + "--encoding", "UTF-8", "-m", + possibly_foolish_untaint($message), + $config{srcdir}) != 0) { + warn("svn commit failed\n"); + return 1; # failure + } + return undef # success +} + sub rcs_add ($) { #{{{ # filename is relative to the root of the srcdir my $file=shift; @@ -134,6 +156,40 @@ sub rcs_add ($) { #{{{ } } #}}} +sub rcs_remove ($) { #{{{ + # filename is relative to the root of the srcdir + my $file=shift; + + if (-d "$config{srcdir}/.svn") { + if (system("svn", "rm", "--force", "--quiet", "$config{srcdir}/$file") != 0) { + warn("svn rm failed\n"); + } + } +} #}}} + +sub rcs_rename ($$) { #{{{ + # filenames relative to the root of the srcdir + my ($src, $dest)=@_; + + if (-d "$config{srcdir}/.svn") { + # Add parent directory for $dest + my $parent=dirname($dest); + if (! -d "$config{srcdir}/$parent/.svn") { + while (! -d "$config{srcdir}/$parent/.svn") { + $parent=dirname($dest); + } + if (system("svn", "add", "--quiet", "$config{srcdir}/$parent") != 0) { + warn("svn add $parent failed\n"); + } + } + + if (system("svn", "mv", "--force", "--quiet", + "$config{srcdir}/$src", "$config{srcdir}/$dest") != 0) { + warn("svn rename failed\n"); + } + } +} #}}} + sub rcs_recentchanges ($) { #{{{ my $num=shift; my @ret; diff --git a/IkiWiki/Rcs/tla.pm b/IkiWiki/Rcs/tla.pm index e7fed9ad8..4232e1fe8 100644 --- a/IkiWiki/Rcs/tla.pm +++ b/IkiWiki/Rcs/tla.pm @@ -78,6 +78,14 @@ sub rcs_commit ($$$;$$) { #{{{ return undef # success } #}}} +sub rcs_commit_staged ($$$) { + # Commits all staged changes. Changes can be staged using rcs_add, + # rcs_remove, and rcs_rename. + my ($message, $user, $ipaddr)=@_; + + error("rcs_commit_staged not implemented for tla"); # TODO +} + sub rcs_add ($) { #{{{ my $file=shift; @@ -88,6 +96,18 @@ sub rcs_add ($) { #{{{ } } #}}} +sub rcs_remove ($) { # {{{ + my $file = shift; + + error("rcs_remove not implemented for tla"); # TODO +} #}}} + +sub rcs_rename ($$) { # {{{a + my ($src, $dest) = @_; + + error("rcs_rename not implemented for tla"); # TODO +} #}}} + sub rcs_recentchanges ($) { my $num=shift; my @ret; diff --git a/debian/changelog b/debian/changelog index c699f698b..a0d526f88 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,11 @@ ikiwiki (2.55) UNRELEASED; urgency=low + * remove: New plugin that adds the ability to remove pages via the web. + (Sponsored by The TOVA Company.) + * rename: New plugin that adds the ability to rename pages via the web. + (Sponsored by The TOVA Company.) (This one's for you, Kyle.) + * All rcs backends need to implement rcs_remove, rcs_commitstaged, + and rcs_rename. (Done for svn, git). * prefix_directives enabled in doc wiki, all preprocessor directives converted. (Simon McVittie) * editpage: Don't show attachments link when attachments are disabled. @@ -11,6 +17,10 @@ ikiwiki (2.55) UNRELEASED; urgency=low * Add allow_symlinks_before_srcdir config setting that can be used to avoid a security check that is a good safe default, but problimatic overkill in some situations. + * Don't allow uploading an attachment with the same name as an existing + page, to avoid confusion. + * Split out error messages from editpage.tmpl into several separate + templates. * attachment: Do not escape _ when determining attachment filenames. -- Joey Hess Mon, 21 Jul 2008 11:35:46 -0400 diff --git a/doc/plugins/remove.mdwn b/doc/plugins/remove.mdwn new file mode 100644 index 000000000..be382e1d8 --- /dev/null +++ b/doc/plugins/remove.mdwn @@ -0,0 +1,7 @@ +[[!template id=plugin name=remove core=0 author="[[Joey]]"]] +[[!tag type/useful]] + +This plugin allows pages or other files to be removed using the web +interface. + +Users can only remove things that they are allowed to edit or upload. diff --git a/doc/plugins/rename.mdwn b/doc/plugins/rename.mdwn new file mode 100644 index 000000000..f5433ca65 --- /dev/null +++ b/doc/plugins/rename.mdwn @@ -0,0 +1,7 @@ +[[!template id=plugin name=rename core=0 author="[[Joey]]"]] +[[!tag type/useful]] + +This plugin allows pages or other files to be renamed using the web +interface. + +Users can only rename things that they are allowed to edit or upload. diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index 22bd5d114..12bd33662 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -635,4 +635,5 @@ when imported, populate `$IkiWiki::Setup::raw_setup` with a reference to a hash containing all the config items. By the way, to parse a ikiwiki setup file, a program just needs to -do something like `use IkiWiki::Setup; my %setup=IkiWiki::Setup::load($filename)` +do something like: +`use IkiWiki::Setup; my %setup=IkiWiki::Setup::load($filename)` diff --git a/doc/todo/Moving_Pages.mdwn b/doc/todo/Moving_Pages.mdwn index 7485f06fd..cd19d6b98 100644 --- a/doc/todo/Moving_Pages.mdwn +++ b/doc/todo/Moving_Pages.mdwn @@ -370,8 +370,14 @@ When renaming `foo`, it probably makes sense to also rename `foo/Discussion`. Should other SubPages in `foo/` also be renamed? I think it's probably simplest to rename all of its SubPages too. +(For values of "simplest" that don't include the pain of dealing with all +the changed links on subpages.. as well as issues like pagespecs that +continue to match the old subpages, and cannot reasonably be auto-converted +to use the new, etc, etc... So still undecided about this.) + When deleting `foo`, I don't think SubPages should be deleted. The -potential for mistakes and abuse is too large. +potential for mistakes and abuse is too large. Deleting Discussion page +might be a useful exception. ## link fixups @@ -379,6 +385,18 @@ When renaming a page, it's desirable to keep links that point to it working. Rather than use redirection pages, I think that all pages that link to it should be modified to fix their links. +The rename plugin can add a "rename" hook, which other plugins can use to +update links &etc. The hook would be passed page content, the old and new +link names, and would modify the content and return it. At least the link +plugin should have such a hook. + +After calling the "rename" hook, and rendering the wiki, the rename plugin +can check to see what links remain pointing to the old page. There could +still be some, for example, CamelCase links probably won't be changed; img +plugins and others contain logical links to the file, etc. The user can be +presented with a list of all the pages that still have links to the old +page, and can manually deal with them. + In some cases, a redirection page will be wanted, to keep long-lived urls working. Since the meta plugin supports creating such pages, and since they won't always be needed, I think it will be simplest to just leave it up to @@ -390,21 +408,16 @@ The source page must be editable by the user to be deleted/renamed. When renaming, the dest page must not already exist, and must be creatable by the user, too. -When deleting/renaming attachments, the `allowed_attachments` PageSpec +lWhen deleting/renaming attachments, the `allowed_attachments` PageSpec is checked too. ## RCS -Two new optional functions are added to the RCS interface: +Three new functions are added to the RCS interface: -* `rcs_delete(file, message, rcstoken, user, ipaddr)` -* `rcs_rename(old, new, message, rcstoken, user, ipaddr)` - -The page move/rename code will check if these are not available, and error -out. - -Similar to `rcs_commit` both of these take a rcstoken, which is generated -by an earlier `rcs_prepedit`. +* `rcs_remove(file)` +* `rcs_rename(old, new)` +* `rcs_commit_staged(message, user, ip)` ## conflicts @@ -413,37 +426,55 @@ Cases that have to be dealt with: * Alice clicks "delete" button for a page; Bob makes a modification; Alice confirms deletion. Ideally in this case, Alice should get an error message that there's a conflict. + Update: In my current code, alice's deletion will fail if the file was + moved or deleted in the meantime; if the file was modified since alice + clicked on the delete button, the modifications will be deleted too. I + think this is acceptable. * Alice opens edit UI for a page; Bob makes a modification; Alice clicks delete button and confirms deletion. Again here, Alice should get a conflict error. Note that this means that the rcstoken should be recorded when the edit UI is first opened, not when the delete button is hit. + Update: Again here, there's no conflict, but the delete succeeds. Again, + basically acceptible. * Alice and Bob both try to delete a page at the same time. It's fine for the second one to get a message that it no longer exists. Or just to silently fail to delete the deleted page.. + Update: It will display an error to the second one that the page doesn't + exist. * Alice deletes a page; Bob had edit window open for it, and saves it afterwards. I think that Bob should win in this case; Alice can always notice the page has been added back, and delete it again. + Update: Bob wins. * Alice clicks "rename" button for a page; Bob makes a modification; Alice confirms rename. This case seems easy, it should just rename the modified page. + Update: it does * Alice opens edit UI for a page; Bob makes a modification; Alice clicks rename button and confirms rename. Seems same as previous case. + Update: check * Alice and Bob both try to rename a page at the same time (to probably different names). Or one tries to delete, and the other to rename. I think it's acceptible for the second one to get an error message that the page no longer exists. + Update: check, that happens * Alice renames a page; Bob had edit window open for it, and saves it afterwards, under old name. I think it's acceptible for Bob to succeed in saving it under the old name in this case, though not ideal. -* Alice renames (or deletes) a page. In the meantime, Bob is uploading an - attachment to it, and finishes after the rename finishes. Is it - acceptible for the attachment to be saved under the old name? + Update: Behavior is the same as if Alice renamed the page and Bob created + a new page with the old name. Seems acceptable, though could be mildly + confusing to Bob (or Alice). * Alice starts creating a new page. In the meantime, Bob renames a different page to that name. Alice should get an error message when committing; and it should have conflict markers. Ie, this should work the same as if Bob had edited the new page at the same time as Alice did. + Update: That should happen. Haven't tested this case yet to make sure. * Bob starts renaming a page. In the meantime, Alice creates a new page with the name he's renaming it to. Here Bob should get a error message that he can't rename the page to an existing name. (A conflict resolution edit would also be ok.) + Update: Bob gets an error message. + +* Alice renames (or deletes) a page. In the meantime, Bob is uploading an + attachment to it, and finishes after the rename finishes. Is it + acceptible for the attachment to be saved under the old name? diff --git a/po/ikiwiki.pot b/po/ikiwiki.pot index c00242df7..621bfdd4d 100644 --- a/po/ikiwiki.pot +++ b/po/ikiwiki.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-07-21 10:11-0400\n" +"POT-Creation-Date: 2008-07-22 19:44-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -62,16 +62,16 @@ msgid "creating %s" msgstr "" #: ../IkiWiki/CGI.pm:512 ../IkiWiki/CGI.pm:540 ../IkiWiki/CGI.pm:550 -#: ../IkiWiki/CGI.pm:584 ../IkiWiki/CGI.pm:629 +#: ../IkiWiki/CGI.pm:586 ../IkiWiki/CGI.pm:631 #, perl-format msgid "editing %s" msgstr "" -#: ../IkiWiki/CGI.pm:729 +#: ../IkiWiki/CGI.pm:731 msgid "You are banned." msgstr "" -#: ../IkiWiki/CGI.pm:758 ../IkiWiki/CGI.pm:759 ../IkiWiki.pm:783 +#: ../IkiWiki/CGI.pm:760 ../IkiWiki/CGI.pm:761 ../IkiWiki.pm:783 msgid "Error" msgstr "" @@ -173,15 +173,20 @@ msgstr "" msgid "Failed to delete file from S3: " msgstr "" -#: ../IkiWiki/Plugin/attachment.pm:110 -msgid "bad attachment filename" +#: ../IkiWiki/Plugin/attachment.pm:22 +#, perl-format +msgid "there is already a page named %s" +msgstr "" + +#: ../IkiWiki/Plugin/attachment.pm:41 +msgid "prohibited by allowed_attachments" msgstr "" -#: ../IkiWiki/Plugin/attachment.pm:133 -msgid "attachment rejected" +#: ../IkiWiki/Plugin/attachment.pm:144 +msgid "bad attachment filename" msgstr "" -#: ../IkiWiki/Plugin/attachment.pm:169 +#: ../IkiWiki/Plugin/attachment.pm:186 msgid "attachment upload" msgstr "" @@ -232,27 +237,27 @@ msgstr "" msgid "prog not a valid graphviz program" msgstr "" -#: ../IkiWiki/Plugin/img.pm:49 +#: ../IkiWiki/Plugin/img.pm:53 msgid "Image::Magick is not installed" msgstr "" -#: ../IkiWiki/Plugin/img.pm:56 +#: ../IkiWiki/Plugin/img.pm:60 #, perl-format msgid "bad size \"%s\"" msgstr "" -#: ../IkiWiki/Plugin/img.pm:66 ../IkiWiki/Plugin/img.pm:70 -#: ../IkiWiki/Plugin/img.pm:87 +#: ../IkiWiki/Plugin/img.pm:70 ../IkiWiki/Plugin/img.pm:74 +#: ../IkiWiki/Plugin/img.pm:91 #, perl-format msgid "failed to read %s: %s" msgstr "" -#: ../IkiWiki/Plugin/img.pm:73 +#: ../IkiWiki/Plugin/img.pm:77 #, perl-format msgid "failed to resize: %s" msgstr "" -#: ../IkiWiki/Plugin/img.pm:104 +#: ../IkiWiki/Plugin/img.pm:108 #, perl-format msgid "failed to determine size of image %s" msgstr "" @@ -496,6 +501,76 @@ msgstr "" msgid "(Diff truncated)" msgstr "" +#: ../IkiWiki/Plugin/remove.pm:23 ../IkiWiki/Plugin/rename.pm:26 +#, perl-format +msgid "%s does not exist" +msgstr "" + +#: ../IkiWiki/Plugin/remove.pm:30 +#, perl-format +msgid "%s is not in the srcdir, so it cannot be deleted" +msgstr "" + +#: ../IkiWiki/Plugin/remove.pm:33 ../IkiWiki/Plugin/rename.pm:35 +#, perl-format +msgid "%s is not a file" +msgstr "" + +#: ../IkiWiki/Plugin/remove.pm:100 +#, perl-format +msgid "confirm removal of %s" +msgstr "" + +#: ../IkiWiki/Plugin/remove.pm:136 +msgid "Please select the attachments to remove." +msgstr "" + +#: ../IkiWiki/Plugin/remove.pm:176 +msgid "removed" +msgstr "" + +#: ../IkiWiki/Plugin/rename.pm:32 +#, perl-format +msgid "%s is not in the srcdir, so it cannot be renamed" +msgstr "" + +#: ../IkiWiki/Plugin/rename.pm:47 +msgid "no change to the file name was specified" +msgstr "" + +#: ../IkiWiki/Plugin/rename.pm:53 +#, perl-format +msgid "illegal name" +msgstr "" + +#: ../IkiWiki/Plugin/rename.pm:58 +#, perl-format +msgid "%s already exists" +msgstr "" + +#: ../IkiWiki/Plugin/rename.pm:64 +#, perl-format +msgid "%s already exists on disk" +msgstr "" + +#: ../IkiWiki/Plugin/rename.pm:98 +#, perl-format +msgid "rename %s" +msgstr "" + +#: ../IkiWiki/Plugin/rename.pm:181 +msgid "Only one attachment can be renamed at a time." +msgstr "" + +#: ../IkiWiki/Plugin/rename.pm:184 +msgid "Please select the attachment to rename." +msgstr "" + +#: ../IkiWiki/Plugin/rename.pm:229 +#, perl-format +msgid "rename %s to %s" +msgstr "" + #: ../IkiWiki/Plugin/search.pm:20 #, perl-format msgid "Must specify %s when using the search plugin" @@ -608,7 +683,7 @@ msgstr "" msgid "failed to generate image from code" msgstr "" -#: ../IkiWiki/Rcs/Stub.pm:69 +#: ../IkiWiki/Rcs/Stub.pm:96 msgid "getctime not implemented" msgstr "" diff --git a/templates/editconflict.tmpl b/templates/editconflict.tmpl new file mode 100644 index 000000000..125203395 --- /dev/null +++ b/templates/editconflict.tmpl @@ -0,0 +1,7 @@ +

+Your changes conflict with other changes made to the page. +

+

+Conflict markers have been inserted into the page content. Reconcile the +conflict and commit again to save your changes. +

diff --git a/templates/editcreationconflict.tmpl b/templates/editcreationconflict.tmpl new file mode 100644 index 000000000..c99102f0d --- /dev/null +++ b/templates/editcreationconflict.tmpl @@ -0,0 +1,9 @@ +

+While you were creating this page, someone else independently created a page +with the same name. +

+

+The edit box below contains the page's current content, followed by the +content you entered previously, to allow you to merge the two +together before saving. +

diff --git a/templates/editfailedsave.tmpl b/templates/editfailedsave.tmpl new file mode 100644 index 000000000..5184f7d4d --- /dev/null +++ b/templates/editfailedsave.tmpl @@ -0,0 +1,10 @@ +

+Failed to save your changes. +

+

+Your changes were not able to be saved to disk. The system gave the error: +

+ +
+Your changes are preserved below, and you can try again to save them. +

diff --git a/templates/editpage.tmpl b/templates/editpage.tmpl index 987531803..4b54db2d1 100644 --- a/templates/editpage.tmpl +++ b/templates/editpage.tmpl @@ -1,46 +1,6 @@
- -

-Your changes conflict with other changes made to the page. -

-

-Conflict markers have been inserted into the page content. Reconcile the -conflict and commit again to save your changes. -

-
- -

-Failed to save your changes. -

-

-Your changes were not able to be saved to disk. The system gave the error: -

- -
-Your changes are preserved below, and you can try again to save them. -

-
- -

-The page you were editing has disappeared. -

-

-Perhaps someone else has deleted it or moved it. If you want to recreate -this page with your text, click "Save Page" again. -

-
- -

-While you were creating this page, someone else independently created a page -with the same name. -

-

-The edit box below contains the page's current content, followed by the -content you entered previously, to allow you to merge the two -together before saving. -

-
+ @@ -71,7 +31,7 @@ Optional comment about this change:
- + diff --git a/templates/editpagegone.tmpl b/templates/editpagegone.tmpl new file mode 100644 index 000000000..2eed03af4 --- /dev/null +++ b/templates/editpagegone.tmpl @@ -0,0 +1,7 @@ +

+The page you were editing has disappeared. +

+

+Perhaps someone else has deleted it or moved it. If you want to recreate +this page with your text, click "Save Page" again. +

diff --git a/templates/renamesummary.tmpl b/templates/renamesummary.tmpl new file mode 100644 index 000000000..3d6866310 --- /dev/null +++ b/templates/renamesummary.tmpl @@ -0,0 +1,13 @@ +

+Successfully renamed to . +

+

+ +The following pages still link to : +

    +
  • +
+ +No pages have broken links to . + +