plugin safe/rebuild controls
[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 $plugin_forced=defined $plugin && (! $plugininfo{safe} ||
86                 (exists $config{websetup_force_plugins} && grep { $_ eq $plugin } @{$config{websetup_force_plugins}}));
87         if ($plugin_forced && ! $enabled) {
88                 # plugin is disabled and cannot be turned on,
89                 # so skip its configuration
90                 return;
91         }
92
93         my %shownfields;
94         my $section=defined $plugin ? $plugin." ".gettext("plugin") : "main";
95         
96         while (@show) {
97                 my $key=shift @show;
98                 my %info=%{shift @show};
99
100                 my $description=$info{description};
101                 if (exists $info{link} && length $info{link}) {
102                         if ($info{link} =~ /^\w+:\/\//) {
103                                 $description="<a href=\"$info{link}\">$description</a>";
104                         }
105                         else {
106                                 $description=htmllink("", "", $info{link}, noimageinline => 1, linktext => $description);
107                         }
108                 }
109
110                 # multiple plugins can have the same field
111                 my $name=defined $plugin ? $plugin.".".$key : $key;
112
113                 my $value=$config{$key};
114
115                 if ($info{safe} && (ref $config{$key} eq 'ARRAY' || ref $info{example} eq 'ARRAY')) {
116                         push @{$value}, "", ""; # blank items for expansion
117                 }
118
119                 if ($info{type} eq "string") {
120                         $form->field(
121                                 name => $name,
122                                 label => $description,
123                                 comment => formatexample($info{example}, $value),
124                                 type => "text",
125                                 value => $value,
126                                 size => 60,
127                                 fieldset => $section,
128                         );
129                 }
130                 elsif ($info{type} eq "pagespec") {
131                         $form->field(
132                                 name => $name,
133                                 label => $description,
134                                 comment => formatexample($info{example}, $value),
135                                 type => "text",
136                                 value => $value,
137                                 size => 60,
138                                 validate => \&IkiWiki::pagespec_valid,
139                                 fieldset => $section,
140                         );
141                 }
142                 elsif ($info{type} eq "integer") {
143                         $form->field(
144                                 name => $name,
145                                 label => $description,
146                                 comment => formatexample($info{example}, $value),
147                                 type => "text",
148                                 value => $value,
149                                 size => 5,
150                                 validate => '/^[0-9]+$/',
151                                 fieldset => $section,
152                         );
153                 }
154                 elsif ($info{type} eq "boolean") {
155                         $form->field(
156                                 name => $name,
157                                 label => "",
158                                 type => "checkbox",
159                                 value => $value,
160                                 options => [ [ 1 => $description ] ],
161                                 fieldset => $section,
162                         );
163                 }
164                 
165                 if (! $info{safe}) {
166                         $form->field(name => $name, disabled => 1);
167                 }
168                 else {
169                         $shownfields{$name}=[$key, \%info];
170                 }
171         }
172
173         if (defined $plugin && (! $plugin_forced || $config{websetup_advanced})) {
174                 my $name="enable.$plugin";
175                 $section="plugins" unless %shownfields;
176                 $form->field(
177                         name => $name,
178                         label => "",
179                         type => "checkbox",
180                         options => [ [ 1 => sprintf(gettext("enable %s?"), $plugin) ] ],
181                         value => $enabled,
182                         fieldset => $section,
183                 );
184                 if ($plugin_forced) {
185                         $form->field(name => $name, disabled => 1);
186                 }
187                 else {
188                         $shownfields{$name}=[$name, \%plugininfo];
189                 }
190         }
191
192         return %shownfields;
193 } #}}}
194
195 sub showform ($$) { #{{{
196         my $cgi=shift;
197         my $session=shift;
198
199         if (! defined $session->param("name") || 
200             ! IkiWiki::is_admin($session->param("name"))) {
201                 error(gettext("you are not logged in as an admin"));
202         }
203
204         eval q{use CGI::FormBuilder};
205         error($@) if $@;
206
207         my $form = CGI::FormBuilder->new(
208                 title => "setup",
209                 name => "setup",
210                 header => 0,
211                 charset => "utf-8",
212                 method => 'POST',
213                 javascript => 0,
214                 reset => 1,
215                 params => $cgi,
216                 fieldsets => [
217                         [main => gettext("main")], 
218                         [plugins => gettext("plugins")]
219                 ],
220                 action => $config{cgiurl},
221                 template => {type => 'div'},
222                 stylesheet => IkiWiki::baseurl()."style.css",
223         );
224
225         if ($form->submitted eq 'Basic') {
226                 $form->field(name => "showadvanced", type => "hidden", 
227                         value => 0, force => 1);
228         }
229         elsif ($form->submitted eq 'Advanced') {
230                 $form->field(name => "showadvanced", type => "hidden", 
231                         value => 1, force => 1);
232         }
233         my $advancedtoggle;
234         if ($form->field("showadvanced")) {
235                 $config{websetup_advanced}=1;
236                 $advancedtoggle="Basic";
237         }
238         else {
239                 $config{websetup_advanced}=0;
240                 $advancedtoggle="Advanced";
241         }
242
243         my $buttons=["Save Setup", $advancedtoggle, "Cancel"];
244
245         IkiWiki::decode_form_utf8($form);
246         IkiWiki::run_hooks(formbuilder_setup => sub {
247                 shift->(form => $form, cgi => $cgi, session => $session,
248                         buttons => $buttons);
249         });
250         IkiWiki::decode_form_utf8($form);
251
252         $form->field(name => "do", type => "hidden", value => "setup",
253                 force => 1);
254         my %fields=showfields($form, undef, undef, IkiWiki::getsetup());
255         
256         # record all currently enabled plugins before all are loaded
257         my %enabled_plugins=%IkiWiki::loaded_plugins;
258
259         # per-plugin setup
260         require IkiWiki::Setup;
261         my %plugins=map { $_ => 1 } IkiWiki::listplugins();
262         foreach my $pair (IkiWiki::Setup::getsetup()) {
263                 my $plugin=$pair->[0];
264                 my $setup=$pair->[1];
265
266                 my %shown=showfields($form, $plugin, $enabled_plugins{$plugin}, @{$setup});
267                 if (%shown) {
268                         delete $plugins{$plugin};
269                         $fields{$_}=$shown{$_} foreach keys %shown;
270                 }
271         }
272         
273         if ($form->submitted eq "Cancel") {
274                 IkiWiki::redirect($cgi, $config{url});
275                 return;
276         }
277         elsif (($form->submitted eq 'Save Setup' || $form->submitted eq 'Rebuild Wiki') && $form->validate) {
278                 my %rebuild;
279                 foreach my $field (keys %fields) {
280                         if ($field=~/^enable\./) {
281                                 # rebuild is overkill for many plugins,
282                                 # but no good way to tell which
283                                 $rebuild{$field}=1; # TODO only if state changed tho
284                                 # TODO plugin enable/disable
285                                 next;
286                         }
287                         
288                         my %info=%{$fields{$field}->[1]};
289                         my $key=$fields{$field}->[0];
290                         my @value=$form->field($field);
291                         
292                         if (! $info{safe}) {
293                                 error("unsafe field $key"); # should never happen
294                         }
295
296                         next unless @value;
297                         # Avoid setting fields to empty strings,
298                         # if they were not set before.
299                         next if ! defined $config{$key} && ! grep { length $_ } @value;
300
301                         if (ref $config{$key} eq "ARRAY" || ref $info{example} eq "ARRAY") {
302                                 if ($info{rebuild} && (! defined $config{$key} || (@{$config{$key}}) != (@value))) {
303                                         $rebuild{$field}=1;
304                                 }
305                                 $config{$key}=\@value;
306                         }
307                         elsif (ref $config{$key} || ref $info{example}) {
308                                 error("complex field $key"); # should never happen
309                         }
310                         else {
311                                 if ($info{rebuild} && (! defined $config{$key} || $config{$key} ne $value[0])) {
312                                         $rebuild{$field}=1;
313                                 }
314                                 $config{$key}=$value[0];
315                         }               
316                 }
317
318                 if (%rebuild && $form->submitted eq 'Save Setup') {
319                         $form->text(gettext("The configuration changes shown below require a wiki rebuild to take effect."));
320                         foreach my $field ($form->field) {
321                                 next if $rebuild{$field};
322                                 $form->field(name => $field, type => "hidden",
323                                         force => 1);
324                         }
325                         $form->reset(0); # doesn't really make sense here
326                         $buttons=["Rebuild Wiki", "Cancel"];
327                 }
328                 else {
329                         # TODO save to real path
330                         IkiWiki::Setup::dump("/tmp/s");
331                         $form->text(gettext("Setup saved."));
332
333                         if (%rebuild) {
334                                 # TODO rebuild
335                         }
336                 }
337         }
338
339         IkiWiki::showform($form, $buttons, $session, $cgi);
340 } #}}}
341
342 sub sessioncgi ($$) { #{{{
343         my $cgi=shift;
344         my $session=shift;
345
346         if ($cgi->param("do") eq "setup") {
347                 showform($cgi, $session);
348                 exit;
349         }
350 } #}}}
351
352 sub formbuilder_setup (@) { #{{{
353         my %params=@_;
354
355         my $form=$params{form};
356         if ($form->title eq "preferences") {
357                 push @{$params{buttons}}, "Wiki Setup";
358                 if ($form->submitted && $form->submitted eq "Wiki Setup") {
359                         showform($params{cgi}, $params{session});
360                         exit;
361                 }
362         }
363 } #}}}
364
365 1