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