Work on conversion of help search to new format
[webmin.git] / webmin_search.cgi
1 #!/usr/local/bin/perl
2 # Search Webmin modules and help pages and text and config.info
3
4 BEGIN { push(@INC, ".."); };
5 use WebminCore;
6
7 &init_config();
8 &ReadParse();
9
10 $prod = &get_product_name();
11 $ucprod = ucfirst($prod);
12 &ui_print_header(undef, &text('wsearch_title', $ucprod), "", undef, 0, 1);
13
14 # Validate search text
15 $re = $in{'search'};
16 if ($re !~ /\S/) {
17         &error($text{'wsearch_esearch'});
18         }
19 $re =~ s/^\s+//;
20 $re =~ s/\s+$//;
21
22 # Search module names and add to results list
23 @rv = ( );
24 @mods = sort { $b->{'longdesc'} cmp $a->{'longdesc'} }
25              grep { !$_->{'clone'} } &get_available_module_infos();
26 foreach $m (@mods) {
27         if ($m->{'desc'} =~ /\Q$re\E/i) {
28                 push(@rv, { 'mod' => $m,
29                             'rank' => 10,
30                             'type' => 'mod',
31                             'link' => $m->{'dir'}.'/',
32                             'text' => $m->{'desc'} });
33                 }
34         }
35
36 # Search module configs and their help pages
37 foreach $m (@mods) {
38         %access = &get_module_acl(undef, $m);
39         next if ($access{'noconfig'});
40         $file = $prod eq 'webmin' ? "$m->{'dir'}/config.info"
41                                   : "$m->{'dir'}/uconfig.info";
42         %info = ( );
43         @info_order = ( );
44         &read_file($file, \%info, \@info_order);
45         foreach $o (@lang_order_list) {
46                 &read_file("$file.$o", \%info);
47                 }
48         $section = undef;
49         foreach $c (@info_order) {
50                 @p = split(/,/, $info{$c});
51                 if ($p[1] == 11) {
52                         $section = $c;
53                         }
54                 if ($p[0] =~ /\Q$re\E/i) {
55                         # Config description matches
56                         push(@rv, { 'mod' => $m,
57                                     'rank' => 8,
58                                     'type' => 'config',
59                                     'link' => "config.cgi?module=$m->{'dir'}&".
60                                              "section=".&urlize($section)."#$c",
61                                     'text' => $p[0] });
62                         }
63                 $hfl = &help_file($mod->{'dir'}, "config_".$c);
64                 ($title, $help) = &help_file_match($hfl);
65                 if ($help) {
66                         # Config help matches
67                         push(@rv, { 'mod' => $m,
68                                     'rank' => 6,
69                                     'type' => 'help',
70                                     'link' => "help.cgi/$m->{'dir'}/config_$c",
71                                     'desc' => &text('wsearch_helpfor', $p[0]),
72                                     'text' => $help,
73                                    });
74                         }
75                 }
76         }
77
78 # Search other help pages
79 %lang_order_list = map { $_, 1 } @lang_order_list;
80 foreach $m (@mods) {
81         $helpdir = &module_root_directory($m->{'dir'})."/help";
82         %donepage = ( );
83         opendir(DIR, $helpdir);
84         foreach $f (sort { length($b) <=> length($a) } readdir(DIR)) {
85                 next if ($f =~ /^config_/);     # For config help, already done
86
87                 # Work out if we should grep this help page - don't do the same
88                 # page twice for different languages
89                 $grep = 0;
90                 if ($f =~ /^(\S+)\.([^\.]+)\.html$/) {
91                         ($page, $lang) = ($1, $2);
92                         if ($lang_order_list{$lang} && !$donepage{$page}++) {
93                                 $grep = 1;
94                                 }
95                         }
96                 elsif ($f =~ /^(\S+)\.html$/) {
97                         $page = $1;
98                         if (!$donepage{$page}++) {
99                                 $grep = 1;
100                                 }
101                         }
102
103                 # If yes, search it
104                 if ($grep) {
105                         ($title, $help) = &help_file_match("$helpdir/$f");
106                         if ($title) {
107                                 @cgis = &find_cgi_text(
108                                         [ "hlink\\(.*'$page'",
109                                           "hlink\\(.*\"$page\"",
110                                         ], $m, 1);
111                                 # XXX delete this block
112                                 if (@cgis == 0) {
113                                         $link = "";
114                                         }
115                                 else {
116                                         $link = &ui_links_row([
117                                             map { my $s = $_;
118                                                   $s =~ s/^\Q$m->{'dir'}\E\///;
119                                                   "<a href='$_'>$s</a>" } @cgis
120                                             ]);
121                                         $link =~ s/<br>//;
122                                         $link = &text('wsearch_on', $link);
123                                         }
124                                 push(@rv, { 'mod' => $m,
125                                             'rank' => 6,
126                                             'type' => 'help',
127                                             'link' => "help.cgi/$m->{'dir'}/config_$c",
128                                             'desc' => $title,
129                                             'text' => $help,
130                                             'cgis' => \@cgis });
131                                 }
132                         }
133                 }
134         closedir(DIR);
135         }
136
137 # Then do text strings
138 MODULE: foreach $m (@mods) {
139         %mtext = &load_language($m->{'dir'});
140         foreach $k (keys %mtext) {
141                 if ($mtext{$k} =~ /\Q$re\E/i) {
142                         @cgis = &find_cgi_text(
143                                 [ "\$text{'$k'}",
144                                   "\$text{\"$k\"}",
145                                   "\$text{$k}" ], $m);
146                         # XXX delete this block
147                         if (@cgis == 0) {
148                                 $link = "<a href='$m->{'dir'}/'>$m->{'desc'}</a>";
149                                 }
150                         else {
151                                 $link = &ui_links_row([
152                                     map { my $s = $_;
153                                           $s =~ s/^\Q$m->{'dir'}\E\///;
154                                           "<a href='$_'>$s</a>" } @cgis ]);
155                                 $link =~ s/<br>//;
156                                 }
157                         push(@rv, { 'mod' => $m,
158                                     'rank' => 4,
159                                     'type' => 'text',
160                                     'desc' => $pagetitle,       # XXX
161                                     'text' => $mtext{$k},
162                                     'cgis' => \@cgis });
163                         }
164                 }
165         }
166
167 # Sort results by relevancy
168 # XXX
169 @rv = sort { $a->{'rank'} <=> $b->{'rank'} } @rv;
170
171 # Show in table
172 if (@rv) {
173         # XXX next page link?
174         print &ui_columns_start(
175                 [ $text{'wsearch_htext'}, $text{'wsearch_htype'},
176                   $text{'wsearch_hcgis'} ], 100);
177         foreach my $r (@rv) {
178                 # XXX
179                 }
180         print &ui_columns_end();
181         }
182 else {
183         print "<b>",&text('wsearch_enone',
184                 "<tt>".&html_escape($re)."</tt>"),"</b><p>\n";
185         }
186
187 &ui_print_footer();
188
189 # Returns text with the search term bolded, and truncated to 60 characters
190 sub highlight_text
191 {
192 local ($str, $len) = @_;
193 $len ||= 90;
194 local $hlen = $len / 2;
195 $str =~ s/<[^>]*>//g;
196 if ($str =~ /(.*)(\Q$re\E)(.*)/i) {
197         local ($before, $match, $after) = ($1, $2, $3);
198         if (length($before) > $hlen) {
199                 $before = "...".substr($before, length($before)-$hlen);
200                 }
201         if (length($after) > $hlen) {
202                 $after = substr($after, 0, $hlen)."...";
203                 }
204         $str = $before."<b>".&html_escape($match)."</b>".$after;
205         }
206 return $str;
207 }
208
209 sub match_row
210 {
211 local ($m, $link, $what, $text, $module_link) = @_;
212 print "<font size=+1>$link</font>\n";
213 if ($module_link) {
214         print " (".&text('wsearch_inmod',
215                          "<a href='$m->{'dir'}/'>$m->{'desc'}</a>").")";
216         }
217 print "<br>\n";
218 if ($text) {
219         print &highlight_text($text),"<br>\n";
220         }
221 print "<font color=#4EBF37>$m->{'desc'} - $what</font><br>&nbsp;<br>\n";
222 $count++;
223 }
224
225 # find_cgi_text(&regexps, module, re-mode)
226 # Returns the relative URLs of CGIs that matches some regexps, in the given
227 # module.
228 sub find_cgi_text
229 {
230 local ($res, $m, $remode) = @_;
231 local $mdir = &module_root_directory($m);
232 local @rv;
233 foreach my $f (glob("$mdir/*.cgi")) {
234         local $found = 0;
235         open(CGI, $f);
236         LINE: while(my $line = <CGI>) {
237                 foreach my $r (@$res) {
238                         if (!$remode && index($line, $r) >= 0 ||
239                             $remode && $line =~ /$r/) {
240                                 $found++;
241                                 last LINE;
242                                 }
243                         }
244                 }
245         close(CGI);
246         if ($found) {
247                 local $url = $f;
248                 $url =~ s/^\Q$root_directory\E\///;
249                 push(@rv, $url);
250                 }
251         }
252 return @rv;
253 }
254
255 # help_file_match(file)
256 # Returns the title if some help file matches the current search
257 sub help_file_match
258 {
259 local ($f) = @_;
260 local $data = &read_file_contents($f);
261 local $title;
262 if ($data =~ /<header>([^<]*)<\/header>/) {
263         $title = $1;
264         }
265 else {
266         $title = $f;
267         }
268 $data =~ s/\s+/ /g;
269 $data =~ s/<p>/\n\n/gi;
270 $data =~ s/<br>/\n/gi;
271 $data =~ s/<[^>]+>//g;
272 if ($data =~ /\Q$re\E/i) {
273         return $title;
274         }
275 return undef;
276 }