support rebuild tristate
[ikiwiki.git] / IkiWiki / Plugin / websetup.pm
1 #!/usr/bin/perl
2 package IkiWiki::Plugin::websetup;
3
4 use warnings;
5 use strict;
6 use IkiWiki 2.00;
7
8 sub import { #{{{
9         hook(type => "getsetup", id => "websetup", call => \&getsetup);
10         hook(type => "checkconfig", id => "websetup", call => \&checkconfig);
11         hook(type => "sessioncgi", id => "websetup", call => \&sessioncgi);
12         hook(type => "formbuilder_setup", id => "websetup", 
13              call => \&formbuilder_setup);
14 } # }}}
15
16 sub getsetup () { #{{{
17         return
18                 websetup_force_plugins => {
19                         type => "string",
20                         example => [],
21                         description => "list of plugins that cannot be enabled/disabled via the web interface",
22                         safe => 0,
23                         rebuild => 0,
24                 },
25                 websetup_show_unsafe => {
26                         type => "boolean",
27                         example => 1,
28                         description => "show unsafe settings, read-only, in web interface?",
29                         safe => 0,
30                         rebuild => 0,
31                 },
32 } #}}}
33
34 sub checkconfig () { #{{{
35         if (! exists $config{websetup_show_unsafe}) {
36                 $config{websetup_show_unsafe}=1;
37         }
38 } #}}}
39
40 sub formatexample ($$) { #{{{
41         my $example=shift;
42         my $value=shift;
43
44         if (defined $value && length $value) {
45                 return "";
46         }
47         elsif (defined $example && ! ref $example && length $example) {
48                 return "<br/ ><small>Example: <tt>$example</tt></small>";
49         }
50         else {
51                 return "";
52         }
53 } #}}}
54
55 sub showfields ($$$@) { #{{{
56         my $form=shift;
57         my $plugin=shift;
58         my $enabled=shift;
59
60         my @show;
61         my %plugininfo;
62         while (@_) {
63                 my $key=shift;
64                 my %info=%{shift()};
65
66                 # skip internal settings
67                 next if defined $info{type} && $info{type} eq "internal";
68                 # XXX hashes not handled yet
69                 next if ref $config{$key} && ref $config{$key} eq 'HASH' || ref $info{example} eq 'HASH';
70                 # maybe skip unsafe settings
71                 next if ! $info{safe} && ! ($config{websetup_show_unsafe} && $config{websetup_advanced});
72                 # maybe skip advanced settings
73                 next if $info{advanced} && ! $config{websetup_advanced};
74                 # these are handled specially, so don't show
75                 next if $key eq 'add_plugins' || $key eq 'disable_plugins';
76
77                 if ($key eq 'plugin') {
78                         %plugininfo=%info;
79                         next;
80                 }
81                 
82                 push @show, $key, \%info;
83         }
84
85         my $section=defined $plugin ? $plugin." ".gettext("plugin") : "main";
86         my %enabledfields;
87         my $shownfields=0;
88         
89         my $plugin_forced=defined $plugin && (! $plugininfo{safe} ||
90                 (exists $config{websetup_force_plugins} && grep { $_ eq $plugin } @{$config{websetup_force_plugins}}));
91         if ($plugin_forced && ! $enabled) {
92                 # plugin is forced disabled, so skip its settings
93                 @show=();
94         }
95
96         # show plugin toggle
97         if (defined $plugin && (! $plugin_forced || $config{websetup_advanced})) {
98                 my $name="enable.$plugin";
99                 $form->field(
100                         name => $name,
101                         label => "",
102                         type => "checkbox",
103                         options => [ [ 1 => sprintf(gettext("enable %s?"), $plugin) ] ],
104                         value => $enabled,
105                         fieldset => $section,
106                 );
107                 if ($plugin_forced) {
108                         $form->field(name => $name, disabled => 1);
109                 }
110                 else {
111                         $enabledfields{$name}=[$name, \%plugininfo];
112                 }
113         }
114
115         # show plugin settings
116         while (@show) {
117                 my $key=shift @show;
118                 my %info=%{shift @show};
119
120                 my $description=$info{description};
121                 if (exists $info{link} && length $info{link}) {
122                         if ($info{link} =~ /^\w+:\/\//) {
123                                 $description="<a href=\"$info{link}\">$description</a>";
124                         }
125                         else {
126                                 $description=htmllink("", "", $info{link}, noimageinline => 1, linktext => $description);
127                         }
128                 }
129
130                 # multiple plugins can have the same field
131                 my $name=defined $plugin ? $plugin.".".$key : $key;
132
133                 my $value=$config{$key};
134
135                 if ($info{safe} && (ref $config{$key} eq 'ARRAY' || ref $info{example} eq 'ARRAY')) {
136                         push @{$value}, "", ""; # blank items for expansion
137                 }
138
139                 if ($info{type} eq "string") {
140                         $form->field(
141                                 name => $name,
142                                 label => $description,
143                                 comment => formatexample($info{example}, $value),
144                                 type => "text",
145                                 value => $value,
146                                 size => 60,
147                                 fieldset => $section,
148                         );
149                 }
150                 elsif ($info{type} eq "pagespec") {
151                         $form->field(
152                                 name => $name,
153                                 label => $description,
154                                 comment => formatexample($info{example}, $value),
155                                 type => "text",
156                                 value => $value,
157                                 size => 60,
158                                 validate => \&IkiWiki::pagespec_valid,
159                                 fieldset => $section,
160                         );
161                 }
162                 elsif ($info{type} eq "integer") {
163                         $form->field(
164                                 name => $name,
165                                 label => $description,
166                                 comment => formatexample($info{example}, $value),
167                                 type => "text",
168                                 value => $value,
169                                 size => 5,
170                                 validate => '/^[0-9]+$/',
171                                 fieldset => $section,
172                         );
173                 }
174                 elsif ($info{type} eq "boolean") {
175                         $form->field(
176                                 name => $name,
177                                 label => "",
178                                 type => "checkbox",
179                                 value => $value,
180                                 options => [ [ 1 => $description ] ],
181                                 fieldset => $section,
182                         );
183                 }
184                 
185                 if (! $info{safe}) {
186                         $form->field(name => $name, disabled => 1);
187                 }
188                 else {
189                         $enabledfields{$name}=[$key, \%info];
190                 }
191                 $shownfields++;
192         }
193         
194         # if no fields were shown for the plugin, drop it into the
195         # plugins fieldset
196         if (defined $plugin && (! $plugin_forced || $config{websetup_advanced}) &&
197             ! $shownfields) {
198                 $form->field(name => "enable.$plugin", fieldset => "plugins");
199         }
200
201         return %enabledfields;
202 } #}}}
203
204 sub showform ($$) { #{{{
205         my $cgi=shift;
206         my $session=shift;
207
208         if (! defined $session->param("name") || 
209             ! IkiWiki::is_admin($session->param("name"))) {
210                 error(gettext("you are not logged in as an admin"));
211         }
212
213         eval q{use CGI::FormBuilder};
214         error($@) if $@;
215
216         my $form = CGI::FormBuilder->new(
217                 title => "setup",
218                 name => "setup",
219                 header => 0,
220                 charset => "utf-8",
221                 method => 'POST',
222                 javascript => 0,
223                 reset => 1,
224                 params => $cgi,
225                 fieldsets => [
226                         [main => gettext("main")], 
227                         [plugins => gettext("plugins")]
228                 ],
229                 action => $config{cgiurl},
230                 template => {type => 'div'},
231                 stylesheet => IkiWiki::baseurl()."style.css",
232         );
233         
234         $form->field(name => "do", type => "hidden", value => "setup",
235                 force => 1);
236
237         if ($form->submitted eq 'Basic Mode') {
238                 $form->field(name => "showadvanced", type => "hidden", 
239                         value => 0, force => 1);
240         }
241         elsif ($form->submitted eq 'Advanced Mode') {
242                 $form->field(name => "showadvanced", type => "hidden", 
243                         value => 1, force => 1);
244         }
245         my $advancedtoggle;
246         if ($form->field("showadvanced")) {
247                 $config{websetup_advanced}=1;
248                 $advancedtoggle="Basic Mode";
249         }
250         else {
251                 $config{websetup_advanced}=0;
252                 $advancedtoggle="Advanced Mode";
253         }
254
255         my $buttons=["Save Setup", $advancedtoggle, "Cancel"];
256
257         IkiWiki::decode_form_utf8($form);
258         IkiWiki::run_hooks(formbuilder_setup => sub {
259                 shift->(form => $form, cgi => $cgi, session => $session,
260                         buttons => $buttons);
261         });
262         IkiWiki::decode_form_utf8($form);
263
264         my %fields=showfields($form, undef, undef, IkiWiki::getsetup());
265         
266         # record all currently enabled plugins before all are loaded
267         my %enabled_plugins=%IkiWiki::loaded_plugins;
268
269         # per-plugin setup
270         require IkiWiki::Setup;
271         foreach my $pair (IkiWiki::Setup::getsetup()) {
272                 my $plugin=$pair->[0];
273                 my $setup=$pair->[1];
274
275                 my %shown=showfields($form, $plugin, $enabled_plugins{$plugin}, @{$setup});
276                 if (%shown) {
277                         $fields{$_}=$shown{$_} foreach keys %shown;
278                 }
279         }
280         
281         if ($form->submitted eq "Cancel") {
282                 IkiWiki::redirect($cgi, $config{url});
283                 return;
284         }
285         elsif (($form->submitted eq 'Save Setup' || $form->submitted eq 'Rebuild Wiki') && $form->validate) {
286                 # Push values from form into %config, avoiding unnecessary
287                 # changes, and keeping track of which changes need a
288                 # rebuild.
289                 my %rebuild;
290                 foreach my $field (keys %fields) {
291                         my %info=%{$fields{$field}->[1]};
292                         my $key=$fields{$field}->[0];
293                         my @value=$form->field($field);
294                         if (! @value) {
295                                 @value=0;
296                         }
297                 
298                         if (! $info{safe}) {
299                                 error("unsafe field $key"); # should never happen
300                         }
301                 
302                         if (exists $info{rebuild} &&
303                             ($info{rebuild} || ! defined $info{rebuild})) {
304                                 $rebuild{$field}=$info{rebuild};
305                         }
306                                         
307                         if ($field=~/^enable\.(.*)/) {
308                                 my $plugin=$1;
309                                 if ($value[0] != exists $enabled_plugins{$plugin}) {
310                                         # TODO plugin enable/disable
311                                 }
312                                 else {
313                                         delete $rebuild{$field};
314                                 }
315                                 next;
316                         }
317
318                         if (ref $config{$key} eq "ARRAY" || ref $info{example} eq "ARRAY") {
319                                 @value=sort grep { length $_ } @value;
320                                 my @oldvalue=sort grep { length $_ }
321                                         (defined $config{$key} ? @{$config{$key}} : ());
322                                 if ((@oldvalue) == (@value)) {
323                                         delete $rebuild{$field};
324                                 }
325                                 else {
326                                         $config{$key}=\@value;
327                                 }
328                         }
329                         elsif (ref $config{$key} || ref $info{example}) {
330                                 error("complex field $key"); # should never happen
331                         }
332                         else {
333                                 if (defined $config{$key} && $config{$key} eq $value[0]) {
334                                         delete $rebuild{$field};
335                                 }
336                                 elsif (! defined $config{$key} && ! length $value[0]) {
337                                         delete $rebuild{$field};
338                                 }
339                                 elsif (! defined $config{$key} && ! $value[0] &&
340                                        $info{type} eq "boolean") {
341                                         delete $rebuild{$field};
342                                 }
343                                 else {
344                                         print STDERR ">>$key (@value) ($config{$key})\n";
345                                         $config{$key}=$value[0];
346                                 }
347                         }
348                 }
349                 
350                 if (%rebuild && ! $form->field("rebuild_asked")) {
351                         print STDERR ">>".(join "," , keys %rebuild)."\n";
352                         my $required=0;
353                         foreach my $field ($form->field) {
354                                 $required=1 if $rebuild{$field};
355                                 next if exists $rebuild{$field};
356                                 $form->field(name => $field, type => "hidden",
357                                         force => 1);
358                         }
359                         if ($required) {
360                                 $form->text(gettext("The configuration changes shown below require a wiki rebuild to take effect."));
361                                 $buttons=["Rebuild Wiki", "Cancel"];
362                         }
363                         else {
364                                 $form->text(gettext("For the configuration changes shown below to fully take effect, you may need to rebuild the wiki."));
365                                 $buttons=["Rebuild Wiki", "Save Setup", "Cancel"];
366                         }
367                         $form->field(name => "rebuild_asked", type => "hidden",
368                                 value => 1, force => 1);
369                         $form->reset(0); # doesn't really make sense here
370                 }
371                 else {
372                         $form->field(name => "rebuild_asked", type => "hidden",
373                                 value => 0, force => 1);
374                         # TODO save to real path
375                         IkiWiki::Setup::dump("/tmp/s");
376                         $form->text(gettext("Setup saved."));
377         
378                         if ($form->submitted eq 'Rebuild Wiki') {
379                                 # TODO rebuild
380                         }
381                 }
382         }
383
384         IkiWiki::showform($form, $buttons, $session, $cgi);
385 } #}}}
386
387 sub sessioncgi ($$) { #{{{
388         my $cgi=shift;
389         my $session=shift;
390
391         if ($cgi->param("do") eq "setup") {
392                 showform($cgi, $session);
393                 exit;
394         }
395 } #}}}
396
397 sub formbuilder_setup (@) { #{{{
398         my %params=@_;
399
400         my $form=$params{form};
401         if ($form->title eq "preferences") {
402                 push @{$params{buttons}}, "Wiki Setup";
403                 if ($form->submitted && $form->submitted eq "Wiki Setup") {
404                         showform($params{cgi}, $params{session});
405                         exit;
406                 }
407         }
408 } #}}}
409
410 1