web commit by http://ethan.betacantrips.com/
[ikiwiki] / doc / patchqueue / move_page.mdwn
1 This is my first cut at a feature like that requested in [[todo/Moving_Pages]]. In case it gets mangled you can find it on [my site](http://www.betacantrips.com//attic/move.patch).
2
3 A bunch of obvious shortcomings exist: 
4
5 * I'm not sure all the untaints are safe.
6 * No precautions whatsoever are made to protect against race conditions or failures
7   in the rcs\_move function.
8 * movepage.tmpl doesn't exist yet.
9 * Some code is duplicated between cgi\_movepage and cgi\_editpage, as well
10   as rcs\_commit and rcs\_move.
11 * The user interface is pretty lame -- there's no handy select list full 
12   of possible places to move it or anything.
13 * I don't think I implemented cancelling.
14 * from is redundant with page.
15 * I don't think I called the right hook functions.
16 * No redirect pages like those mentioned on [[todo/Moving_Pages]] exist yet, 
17   so none are created.
18 * It's not possible to get there through the actions listed on the wiki page.
19   Instead you can select "Edit" and then change "edit" to "move" in the 
20   location bar.
21
22 Anyhow, here's the patch, for whatever good it does.
23
24 > Looks like a good start, although I agree about many of the points above,
25 > and also feel that something needs to be done about rcses that don't
26 > implement a move operation -- falling back to an add and delete.
27 > --[[Joey]]
28
29 Hmm. Shouldn't that be done on a by-RCS basis, though? (i.e. implemented
30 by backends in the `rcs_move` function)
31
32 Also, how should ikiwiki react if a page is edited (say, by another user)
33 before it is moved? Bail, or shrug and proceed?
34
35 Could you elaborate on [[commit-internals]]? Can I assume that ikiwiki's 
36 working copy W will always reflect a revision of the master copy M? 
37 (That is, nobody changes W and leaves it uncommitted.) I would guess 
38 probably not; a user probably expects that if he starts editing W it 
39 won't get randomly committed by web user actions. But then looking at
40 the svn backend, it looks like if I edit foo.mdwn, don't commit, and then
41 a web user makes different changes, my changes get wiped out. So does
42 W "belong" to ikiwiki? --Ethan
43
44
45     diff -urx .svn ikiwiki/IkiWiki/CGI.pm ikiwiki-new/IkiWiki/CGI.pm
46     --- ikiwiki/IkiWiki/CGI.pm  2007-01-04 03:52:47.000000000 -0800
47     +++ ikiwiki-new/IkiWiki/CGI.pm      2007-01-11 18:49:37.000000000 -0800
48     @@ -523,6 +523,97 @@
49         }
50      } #}}}
51      
52     +sub cgi_movepage($$) {
53     +   my $q = shift;
54     +   my $session = shift;
55     +   eval q{use CGI::FormBuilder};
56     +   error($@) if $@;
57     +   my @fields=qw(do from rcsinfo subpage page newname message); # subpage ignored so far
58     +   my @buttons=("Rename Page", "Cancel");
59     +
60     +   my $form = CGI::FormBuilder->new(
61     +           fields => \@fields,
62     +                header => 1,
63     +                charset => "utf-8",
64     +                method => 'POST',
65     +           action => $config{cgiurl},
66     +                template => (-e "$config{templatedir}/movepage.tmpl" ?
67     +                        {template_params("movepage.tpml")} : ""),
68     +   );
69     +   run_hooks(formbuilder_setup => sub {
70     +           shift->(form => $form, cgi => $q, session => $session);
71     +   });
72     +
73     +   decode_form_utf8($form);
74     +   
75     +   # This untaint is safe because if the page doesn't exist, bail.
76     +   my $page = $form->field('page');
77     +   $page = possibly_foolish_untaint($page);
78     +   if (! exists $pagesources{$page}) {
79     +           error("page does not exist");
80     +   }
81     +   my $file=$pagesources{$page};
82     +   my $type=pagetype($file);
83     +
84     +   my $from;
85     +   if (defined $form->field('from')) {
86     +           ($from)=$form->field('from')=~/$config{wiki_file_regexp}/;
87     +   }
88     +   
89     +   $form->field(name => "do", type => 'hidden');
90     +   $form->field(name => "from", type => 'hidden');
91     +   $form->field(name => "rcsinfo", type => 'hidden');
92     +   $form->field(name => "subpage", type => 'hidden');
93     +   $form->field(name => "page", value => $page, force => 1);
94     +   $form->field(name => "newname", type => "text", size => 80);
95     +   $form->field(name => "message", type => "text", size => 80);
96     +
97     +   if (! $form->submitted) {
98     +           $form->field(name => "rcsinfo", value => rcs_prepedit($file),
99     +                        force => 1);
100     +   }
101     +
102     +   if ($form->submitted eq "Cancel") {
103     +           redirect($q, "$config{url}/".htmlpage($from));
104     +           return;
105     +   }
106     +           
107     +   if (! $form->submitted || $form->submitted eq "Preview" || 
108     +       ! $form->validate) {
109     +           if ($form->field("do") eq "move"){
110     +                   page_locked($page, $session);
111     +                   $form->tmpl_param("page_select", 0);
112     +                   $form->field(name => "page", type => 'hidden');
113     +                   $form->field(name => "type", type => 'hidden');
114     +                   $form->title(sprintf(gettext("moving %s"), pagetitle($page)));
115     +                   if (! defined $form->field('newname') ||
116     +                       ! length $form->field('newname')) {
117     +                           $form->field(name => "newname", 
118     +                                        value => pagetitle($page), force => 1);
119     +                   }
120     +
121     +           }
122     +           print $form->render(submit => \@buttons);
123     +   }
124     +   else{
125     +           # This untaint is safe because titlepage removes any problematic
126     +           # characters.
127     +           my ($newname)=$form->field('newname');
128     +           $newname=titlepage(possibly_foolish_untaint($newname));
129     +           if (! defined $newname || ! length $newname || file_pruned($newname, $config{srcdir}) || $newname=~/^\//) {
130     +                   error("bad page name");
131     +           }
132     +           page_locked($page, $session);
133     +
134     +           my $newfile = $newname . ".$type";
135     +           my $message = $form->field('message');
136     +           unlockwiki();
137     +           rcs_move($file, $newfile, $message, $form->field("rcsinfo"), 
138     +                    $session->param("name"), $ENV{REMOTE_ADDR});
139     +           redirect($q, "$config{url}/".htmlpage($newname));
140     +   }
141     +}
142     +
143      sub cgi_getsession ($) { #{{{
144         my $q=shift;
145      
146     @@ -631,6 +722,9 @@
147         if ($do eq 'create' || $do eq 'edit') {
148                 cgi_editpage($q, $session);
149         }
150     +   elsif ($do eq 'move') {
151     +           cgi_movepage($q, $session);
152     +   }
153         elsif ($do eq 'prefs') {
154                 cgi_prefs($q, $session);
155         }
156     diff -urx .svn ikiwiki/IkiWiki/Rcs/svn.pm ikiwiki-new/IkiWiki/Rcs/svn.pm
157     --- ikiwiki/IkiWiki/Rcs/svn.pm      2006-12-28 17:50:46.000000000 -0800
158     +++ ikiwiki-new/IkiWiki/Rcs/svn.pm  2007-01-11 18:14:30.000000000 -0800
159     @@ -60,6 +60,34 @@
160         }
161      } #}}}
162      
163     +sub rcs_move ($$$$;$$) {
164     +   my $file=shift;
165     +   my $newname=shift;
166     +   my $message=shift;
167     +   my $rcstoken=shift;
168     +   my $user=shift;
169     +   my $ipaddr=shift;
170     +   if (defined $user) {
171     +           $message="web commit by $user".(length $message ? ": $message" : "");
172     +   }
173     +   elsif (defined $ipaddr) {
174     +           $message="web commit from $ipaddr".(length $message ? ": $message" : "");
175     +   }
176     +
177     +   chdir($config{srcdir}); # svn merge wants to be here
178     +
179     +   if (system("svn", "move", "--quiet", 
180     +              "$file", "$newname") != 0) {
181     +           return 1;
182     +   }
183     +   if (system("svn", "commit", "--quiet", 
184     +              "--encoding", "UTF-8", "-m",
185     +              possibly_foolish_untaint($message)) != 0) {
186     +           return 1;
187     +   }
188     +   return undef # success
189     +}
190     +
191      sub rcs_commit ($$$;$$) { #{{{
192         # Tries to commit the page; returns undef on _success_ and
193         # a version of the page with the rcs's conflict markers on failure.