X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/f398ad035b973608d380c9939ea845d8e2a0cdc2..1226d9f144310876080c077a9c45bd2257829a32:/IkiWiki/Plugin/attachment.pm?ds=sidebyside

diff --git a/IkiWiki/Plugin/attachment.pm b/IkiWiki/Plugin/attachment.pm
index ee105a170..ab1929e36 100644
--- a/IkiWiki/Plugin/attachment.pm
+++ b/IkiWiki/Plugin/attachment.pm
@@ -6,11 +6,13 @@ use strict;
 use IkiWiki 3.00;
 
 sub import {
+	add_underlay("attachment");
 	add_underlay("javascript");
+	add_underlay("jquery");
 	hook(type => "getsetup", id => "attachment", call => \&getsetup);
 	hook(type => "checkconfig", id => "attachment", call => \&checkconfig);
 	hook(type => "formbuilder_setup", id => "attachment", call => \&formbuilder_setup);
-	hook(type => "formbuilder", id => "attachment", call => \&formbuilder);
+	hook(type => "formbuilder", id => "attachment", call => \&formbuilder, last => 1);
 	IkiWiki::loadplugin("filecheck");
 }
 
@@ -89,10 +91,20 @@ sub formbuilder_setup (@) {
 		$form->tmpl_param("field-upload" => '<input name="_submit" type="submit" value="Upload Attachment" />');
 		$form->tmpl_param("field-link" => '<input name="_submit" type="submit" value="Insert Links" />');
 
-		# Add the toggle javascript; the attachments interface uses
-		# it to toggle visibility.
+		# Add all the javascript used by the attachments interface.
 		require IkiWiki::Plugin::toggle;
-		$form->tmpl_param("javascript" => IkiWiki::Plugin::toggle::include_javascript($params{page}, 1));
+		my $js=IkiWiki::Plugin::toggle::include_javascript($params{page});
+		$js.='<link rel="stylesheet" href="'.urlto("ikiwiki/jquery-ui.min.css", $params{page}).'" id="theme">'."\n";
+		my @jsfiles=qw{jquery.min jquery-ui.min
+			jquery.tmpl.min jquery.iframe-transport
+			jquery.fileupload jquery.fileupload-ui
+		};
+		foreach my $file (@jsfiles) {
+			$js.='<script src="'.urlto("ikiwiki/$file.js", $params{page}).
+			     '" type="text/javascript" charset="utf-8"></script>'."\n";
+		}
+		$form->tmpl_param("javascript" => $js);
+
 		# Start with the attachments interface toggled invisible,
 		# but if it was used, keep it open.
 		if ($form->submitted ne "Upload Attachment" &&
@@ -103,6 +115,13 @@ sub formbuilder_setup (@) {
 		else {
 			$form->tmpl_param("attachments-class" => "toggleable-open");
 		}
+		
+		# Save attachments in holding area before previewing and
+		# saving.
+		if ($form->submitted eq "Preview" ||
+		    $form->submitted eq "Save Page") {
+			attachments_save($form, $params{session});
+		}
 	}
 }
 
@@ -114,95 +133,22 @@ sub formbuilder (@) {
 	return if ! defined $form->field("do") || ($form->field("do") ne "edit" && $form->field("do") ne "create") ;
 
 	my $filename=Encode::decode_utf8($q->param('attachment'));
-	if (defined $filename && length $filename &&
-            ($form->submitted eq "Upload Attachment" || $form->submitted eq "Save Page")) {
-		my $session=$params{session};
-		
-		# This is an (apparently undocumented) way to get the name
-		# of the temp file that CGI writes the upload to.
-		my $tempfile=$q->tmpFileName($filename);
-		if (! defined $tempfile || ! length $tempfile) {
-			# perl 5.8 needs an alternative, awful method
-			if ($q =~ /HASH/ && exists $q->{'.tmpfiles'}) {
-				foreach my $key (keys(%{$q->{'.tmpfiles'}})) {
-					$tempfile=$q->tmpFileName(\$key);
-					last if defined $tempfile && length $tempfile;
-				}
-			}
-			if (! defined $tempfile || ! length $tempfile) {
-				error("CGI::tmpFileName failed to return the uploaded file name");
-			}
-		}
-
-		$filename=IkiWiki::basename($filename);
-		$filename=~s/.*\\+(.+)/$1/; # hello, windows
-
-		$filename=linkpage(IkiWiki::possibly_foolish_untaint(
-				attachment_location($form->field('page')).
-				$filename));
-		if (IkiWiki::file_pruned($filename)) {
-			error(gettext("bad attachment filename"));
-		}
-		
-		# Check that the user is allowed to edit a page with the
-		# name of the attachment.
-		IkiWiki::check_canedit($filename, $q, $session, 1);
-		# And that the attachment itself is acceptable.
-		check_canattach($session, $filename, $tempfile);
-
-		# Needed for fast_file_copy and for rendering below.
-		require IkiWiki::Render;
-
-		# Move the attachment into place.
-		# Try to use a fast rename; fall back to copying.
-		IkiWiki::prep_writefile($filename, $config{srcdir});
-		unlink($config{srcdir}."/".$filename);
-		if (rename($tempfile, $config{srcdir}."/".$filename)) {
-			# The temp file has tight permissions; loosen up.
-			chmod(0666 & ~umask, $config{srcdir}."/".$filename);
-		}
-		else {
-			my $fh=$q->upload('attachment');
-			if (! defined $fh || ! ref $fh) {
-				# needed by old CGI versions
-				$fh=$q->param('attachment');
-				if (! defined $fh || ! ref $fh) {
-					# even that doesn't always work,
-					# fall back to opening the tempfile
-					$fh=undef;
-					open($fh, "<", $tempfile) || error("failed to open \"$tempfile\": $!");
-				}
-			}
-			binmode($fh);
-			writefile($filename, $config{srcdir}, undef, 1, sub {
-				IkiWiki::fast_file_copy($tempfile, $filename, $fh, @_);
-			});
-		}
+	if (defined $filename && length $filename) {
+		attachment_store($filename, $form, $q, $params{session});
+	}
 
-		# Check the attachment in and trigger a wiki refresh.
-		if ($config{rcs}) {
-			IkiWiki::rcs_add($filename);
-			IkiWiki::disable_commit_hook();
-			IkiWiki::rcs_commit(
-				file => $filename,
-				message => gettext("attachment upload"),
-				token => IkiWiki::rcs_prepedit($filename),
-				session => $session,
-			);
-			IkiWiki::enable_commit_hook();
-			IkiWiki::rcs_update();
-		}
-		IkiWiki::refresh();
-		IkiWiki::saveindex();
+	if ($form->submitted eq "Save Page") {
+		attachments_save($form, $params{session});
 	}
-	elsif ($form->submitted eq "Insert Links") {
+
+	if ($form->submitted eq "Insert Links") {
 		my $page=quotemeta(Encode::decode_utf8($q->param("page")));
 		my $add="";
 		foreach my $f ($q->param("attachment_select")) {
 			$f=Encode::decode_utf8($f);
 			$f=~s/^$page\///;
 			if (IkiWiki::isinlinableimage($f) &&
-			    UNIVERSAL::can("IkiWiki::Plugin::img", "import")) {
+			    IkiWiki::Plugin::img->can("import")) {
 				$add.='[[!img '.$f.' align="right" size="" alt=""]]';
 			}
 			else {
@@ -210,22 +156,160 @@ sub formbuilder (@) {
 			}
 			$add.="\n";
 		}
+		my $content = $form->field('editcontent');
 		$form->field(name => 'editcontent',
-			value => $form->field('editcontent')."\n\n".$add,
+			value => $content."\n\n".$add,
 			force => 1) if length $add;
 	}
 	
 	# Generate the attachment list only after having added any new
 	# attachments.
-	$form->tmpl_param("attachment_list" => [attachment_list($form->field('page'))]);
+	$form->tmpl_param("attachment_list" => [attachment_list(scalar $form->field('page'))]);
+}
+
+sub attachment_holding_location {
+	my $page=attachment_location(shift);
+
+	my $dir=$config{wikistatedir}."/attachments/".
+		IkiWiki::possibly_foolish_untaint(linkpage($page));
+	$dir=~s/\/$//;
+	return $dir;
+}
+
+sub is_held_attachment {
+	my $attachment=shift;
+
+	my $f=attachment_holding_location($attachment);
+	if (-f $f) {
+		return $f
+	}
+	else {
+		return undef;
+	}
+}
+
+# Stores the attachment in a holding area, not yet in the wiki proper.
+sub attachment_store {
+	my $filename=shift;
+	my $form=shift;
+	my $q=shift;
+	my $session=shift;
+	
+	# This is an (apparently undocumented) way to get the name
+	# of the temp file that CGI writes the upload to.
+	my $tempfile=$q->tmpFileName($filename);
+	if (! defined $tempfile || ! length $tempfile) {
+		# perl 5.8 needs an alternative, awful method
+		if ($q =~ /HASH/ && exists $q->{'.tmpfiles'}) {
+			foreach my $key (keys(%{$q->{'.tmpfiles'}})) {
+				$tempfile=$q->tmpFileName(\$key);
+				last if defined $tempfile && length $tempfile;
+			}
+		}
+		if (! defined $tempfile || ! length $tempfile) {
+			error("CGI::tmpFileName failed to return the uploaded file name");
+		}
+	}
+
+	$filename=IkiWiki::basename($filename);
+	$filename=~s/.*\\+(.+)/$1/; # hello, windows
+	$filename=IkiWiki::possibly_foolish_untaint(linkpage($filename));
+	my $dest=attachment_holding_location(scalar $form->field('page'));
+	
+	# Check that the user is allowed to edit the attachment.
+	my $final_filename=
+		linkpage(IkiWiki::possibly_foolish_untaint(
+			attachment_location(scalar $form->field('page')))).
+		$filename;
+	eval {
+		if (IkiWiki::file_pruned($final_filename)) {
+			error(gettext("bad attachment filename"));
+		}
+		IkiWiki::check_canedit($final_filename, $q, $session);
+		# And that the attachment itself is acceptable.
+		check_canattach($session, $final_filename, $tempfile);
+	};
+	if ($@) {
+		json_response($q, $form, $dest."/".$filename, $@);
+		error $@;
+	}
+
+	# Move the attachment into holding directory.
+	# Try to use a fast rename; fall back to copying.
+	IkiWiki::prep_writefile($filename, $dest);
+	unlink($dest."/".$filename);
+	if (rename($tempfile, $dest."/".$filename)) {
+		# The temp file has tight permissions; loosen up.
+		chmod(0666 & ~umask, $dest."/".$filename);
+	}
+	else {
+		my $fh=$q->upload('attachment');
+		if (! defined $fh || ! ref $fh) {
+			# needed by old CGI versions
+			$fh=$q->param('attachment');
+			if (! defined $fh || ! ref $fh) {
+				# even that doesn't always work,
+				# fall back to opening the tempfile
+				$fh=undef;
+				open($fh, "<", $tempfile) || error("failed to open \"$tempfile\": $!");
+			}
+		}
+		binmode($fh);
+		require IkiWiki::Render; 
+		writefile($filename, $dest, undef, 1, sub {
+			IkiWiki::fast_file_copy($tempfile, $filename, $fh, @_);
+		});
+	}
+
+	json_response($q, $form, $dest."/".$filename, stored_msg());
+}
+
+# Save all stored attachments for a page.
+sub attachments_save {
+	my $form=shift;
+	my $session=shift;
+
+	# Move attachments out of holding directory.
+	my @attachments;
+	my $dir=attachment_holding_location(scalar $form->field('page'));
+	foreach my $filename (glob("$dir/*")) {
+		$filename=Encode::decode_utf8($filename);
+		next unless -f $filename;
+		my $destdir=$config{srcdir}."/".
+			linkpage(IkiWiki::possibly_foolish_untaint(
+				attachment_location(scalar $form->field('page'))));
+		my $destfile=IkiWiki::basename($filename);
+		my $dest=$destdir.$destfile;
+		unlink($dest);
+		IkiWiki::prep_writefile($destfile, $destdir);
+		rename($filename, $dest);
+		push @attachments, $dest;
+	}
+	return unless @attachments;
+	require IkiWiki::Render;
+	IkiWiki::prune($dir, $config{wikistatedir}."/attachments");
+
+	# Check the attachments in and trigger a wiki refresh.
+	if ($config{rcs}) {
+		IkiWiki::rcs_add($_) foreach @attachments;
+		IkiWiki::disable_commit_hook();
+		IkiWiki::rcs_commit_staged(
+			message => gettext("attachment upload"),
+			session => $session,
+		);
+		IkiWiki::enable_commit_hook();
+		IkiWiki::rcs_update();
+	}
+	IkiWiki::refresh();
+	IkiWiki::saveindex();
 }
 
 sub attachment_location ($) {
 	my $page=shift;
 	
 	# Put the attachment in a subdir of the page it's attached
-	# to, unless that page is an "index" page.
-	$page=~s/(^|\/)index//;
+	# to, unless that page is the "index" page.
+	return "" if $page eq 'index';
 	$page.="/" if length $page;
 	
 	return $page;
@@ -235,23 +319,76 @@ sub attachment_list ($) {
 	my $page=shift;
 	my $loc=attachment_location($page);
 
-	my @ret;
+	my $std=sub {
+		my $file=shift;
+		my $mtime=shift;
+		my $date=shift;
+		my $size=shift;
+
+		name => $file,
+		size => IkiWiki::Plugin::filecheck::humansize($size),
+		mtime => $date,
+		mtime_raw => $mtime,
+	};
+
+	# attachments already in the wiki
+	my %attachments;
 	foreach my $f (values %pagesources) {
 		if (! defined pagetype($f) &&
 		    $f=~m/^\Q$loc\E[^\/]+$/) {
-			push @ret, {
-				"field-select" => '<input type="checkbox" name="attachment_select" value="'.$f.'" />',
+			$attachments{$f}={
+				$std->($f, $IkiWiki::pagemtime{$f}, displaytime($IkiWiki::pagemtime{$f}), (stat($f))[7]),
 				link => htmllink($page, $page, $f, noimageinline => 1),
-				size => IkiWiki::Plugin::filecheck::humansize((stat(_))[7]),
-				mtime => displaytime($IkiWiki::pagemtime{$f}),
-				mtime_raw => $IkiWiki::pagemtime{$f},
 			};
 		}
 	}
+	
+	# attachments in holding directory
+	my $dir=attachment_holding_location($page);
+	my $heldmsg=gettext("this attachment is not yet saved");
+	foreach my $file (glob("$dir/*")) {
+		$file=Encode::decode_utf8($file);
+		next unless -f $file;
+		my $base=IkiWiki::basename($file);
+		my $f=$loc.$base;
+		$attachments{$f}={
+			$std->($f, (stat($file))[9]*2, stored_msg(), (stat(_))[7]),
+			link => $base,
+		}
+	}
 
-	# Sort newer attachments to the top of the list, so a newly-added
-	# attachment appears just before the form used to add it.
-	return sort { $b->{mtime_raw} <=> $a->{mtime_raw} || $a->{link} cmp $b->{link} } @ret;
+	# Sort newer attachments to the end of the list.
+	return sort { $a->{mtime_raw} <=> $b->{mtime_raw} || $a->{link} cmp $b->{link} }
+		values %attachments;
+}
+
+sub stored_msg {
+	gettext("just uploaded");
+}
+
+sub json_response ($$$$) {
+	my $q=shift;
+	my $form=shift;
+	my $filename=shift;
+	my $stored_msg=shift;
+
+	if (! defined $form->submitted ||
+	    $form->submitted ne "Upload Attachment") {
+		eval q{use JSON};
+		error $@ if $@;
+		print "Content-type: text/html\n\n";
+		my $size=-s $filename;
+		print to_json([
+			{
+				name => IkiWiki::basename($filename),
+				size => $size,
+				humansize => IkiWiki::Plugin::filecheck::humansize($size),
+				stored_msg => $stored_msg,
+				
+			}
+		]);
+		exit 0;
+	}
 }
 
 1