More work on help search.. still not done though
[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                         }
64                 $hfl = &help_file($mod->{'dir'}, "config_".$c);
65                 ($title, $help) = &help_file_match($hfl);
66                 if ($help) {
67                         # Config help matches
68                         push(@rv, { 'mod' => $m,
69                                     'rank' => 6,
70                                     'type' => 'help',
71                                     'link' => "help.cgi/$m->{'dir'}/config_".$c,
72                                     'desc' => &text('wsearch_helpfor', $p[0]),
73                                     'text' => $help,
74                                     'cgis' => [ "/config.cgi?".
75                                                 "module=$m->{'dir'}&section=".
76                                                 &urlize($section)."#$c" ],
77                                    });
78                         }
79                 }
80         }
81
82 # Search other help pages
83 %lang_order_list = map { $_, 1 } @lang_order_list;
84 foreach $m (@mods) {
85         $helpdir = &module_root_directory($m->{'dir'})."/help";
86         %donepage = ( );
87         opendir(DIR, $helpdir);
88         foreach $f (sort { length($b) <=> length($a) } readdir(DIR)) {
89                 next if ($f =~ /^config_/);     # For config help, already done
90
91                 # Work out if we should grep this help page - don't do the same
92                 # page twice for different languages
93                 $grep = 0;
94                 if ($f =~ /^(\S+)\.([^\.]+)\.html$/) {
95                         ($page, $lang) = ($1, $2);
96                         if ($lang_order_list{$lang} && !$donepage{$page}++) {
97                                 $grep = 1;
98                                 }
99                         }
100                 elsif ($f =~ /^(\S+)\.html$/) {
101                         $page = $1;
102                         if (!$donepage{$page}++) {
103                                 $grep = 1;
104                                 }
105                         }
106
107                 # If yes, search it
108                 if ($grep) {
109                         ($title, $help) = &help_file_match("$helpdir/$f");
110                         if ($title) {
111                                 my @cgis = &find_cgi_text(
112                                         [ "hlink\\(.*'$page'",
113                                           "hlink\\(.*\"$page\"",
114                                         ], $m, 1);
115                                 push(@rv, { 'mod' => $m,
116                                             'rank' => 6,
117                                             'type' => 'help',
118                                             'link' => "help.cgi/$m->{'dir'}/$page",
119                                             'desc' => $title,
120                                             'text' => $help,
121                                             'cgis' => \@cgis });
122                                 }
123                         }
124                 }
125         closedir(DIR);
126         }
127
128 # Then do text strings
129 %gtext = &load_language("");
130 MODULE: foreach $m (@mods) {
131         %mtext = &load_language($m->{'dir'});
132         foreach $k (keys %mtext) {
133                 next if ($gtext{$k});   # Skip repeated global strings
134                 if ($mtext{$k} =~ /\Q$re\E/i) {
135                         my @cgis = &find_cgi_text(
136                                 [ "\$text{'$k'}",
137                                   "\$text{\"$k\"}",
138                                   "\$text{$k}" ], $m);
139                         push(@rv, { 'mod' => $m,
140                                     'rank' => 4,
141                                     'type' => 'text',
142                                     'text' => $mtext{$k},
143                                     'cgis' => \@cgis });
144                         }
145                 }
146         }
147
148 # Sort results by relevancy
149 # XXX
150 @rv = sort { $b->{'rank'} <=> $a->{'rank'} } @rv;
151
152 # Show in table
153 if (@rv) {
154         # XXX next page link?
155         print &ui_columns_start(
156                 [ $text{'wsearch_htext'}, $text{'wsearch_htype'},
157                   $text{'wsearch_hmod'}, $text{'wsearch_hcgis'} ], 100);
158         foreach my $r (@rv) {
159                 $hi = &highlight_text($r->{'text'});
160                 if ($r->{'link'}) {
161                         $hi = "<a href='$r->{'link'}'>$hi</a>";
162                         }
163                 @links = ( );
164                 foreach my $c (@{$r->{'cgis'}}) {
165                         ($cmod, $cpage) = split(/\//, $c);
166                         ($cpage, $cargs) = split(/\?/, $cpage);
167                         $ctitle = &cgi_page_title($cmod, $cpage) || $cpage;
168                         push(@links, "<a href='$c'>$ctitle</a>");
169                         }
170                 if (@links > 2) {
171                         @links = ( @links[0..1], "..." );
172                         }
173                 print &ui_columns_row([
174                         $hi,
175                         $text{'wsearch_type_'.$r->{'type'}},
176                         "<a href='$r->{'mod'}->{'dir'}/'>$r->{'mod'}->{'desc'}</a>",
177                         &ui_links_row(\@links),
178                         ]);
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 # highlight_text(text, [length])
190 # Returns text with the search term bolded, and truncated to 60 characters
191 sub highlight_text
192 {
193 local ($str, $len) = @_;
194 $len ||= 50;
195 local $hlen = $len / 2;
196 $str =~ s/<[^>]*>//g;
197 if ($str =~ /(.*)(\Q$re\E)(.*)/i) {
198         local ($before, $match, $after) = ($1, $2, $3);
199         if (length($before) > $hlen) {
200                 $before = "...".substr($before, length($before)-$hlen);
201                 }
202         if (length($after) > $hlen) {
203                 $after = substr($after, 0, $hlen)."...";
204                 }
205         $str = $before."<b>".&html_escape($match)."</b>".$after;
206         }
207 return $str;
208 }
209
210 # find_cgi_text(&regexps, module, re-mode)
211 # Returns the relative URLs of CGIs that matches some regexps, in the given
212 # module.
213 sub find_cgi_text
214 {
215 local ($res, $m, $remode) = @_;
216 local $mdir = &module_root_directory($m);
217 local @rv;
218 foreach my $f (glob("$mdir/*.cgi")) {
219         local $found = 0;
220         open(CGI, $f);
221         LINE: while(my $line = <CGI>) {
222                 foreach my $r (@$res) {
223                         if (!$remode && index($line, $r) >= 0 ||
224                             $remode && $line =~ /$r/) {
225                                 $found++;
226                                 last LINE;
227                                 }
228                         }
229                 }
230         close(CGI);
231         if ($found) {
232                 local $url = $f;
233                 $url =~ s/^\Q$root_directory\E\///;
234                 push(@rv, $url);
235                 }
236         }
237 return @rv;
238 }
239
240 # help_file_match(file)
241 # Returns the title if some help file matches the current search
242 sub help_file_match
243 {
244 local ($f) = @_;
245 local $data = &read_file_contents($f);
246 local $title;
247 if ($data =~ /<header>([^<]*)<\/header>/) {
248         $title = $1;
249         }
250 else {
251         $title = $f;
252         }
253 $data =~ s/\s+/ /g;
254 $data =~ s/<p>/\n\n/gi;
255 $data =~ s/<br>/\n/gi;
256 $data =~ s/<[^>]+>//g;
257 if ($data =~ /\Q$re\E/i) {
258         return ($title, $data);
259         }
260 return ( );
261 }
262
263 # cgi_page_title(module, cgi)
264 # Given a CGI, return the text for its page title, if possible
265 sub cgi_page_title
266 {
267 local ($m, $cgi) = @_;
268 local $data = &read_file_contents(&module_root_directory($m)."/".$cgi);
269 local $rv;
270 if ($data =~ /(header|ui_print_header|ui_print_unbuffered_header)\([^,]+,\s*(\$text{'([^']+)'|\$text{"([^"]+)"|\&text\('([^']+)'|\&text\("([^"]+)")/) {
271         local $msg = $3 || $4 || $5 || $6;
272         local %mtext = &load_language($m);
273         $rv = $mtext{$msg};
274         }
275 if ($cgi eq "index.cgi" && !$rv) {
276         # If no title was found for an index.cgi, use module title
277         local %minfo = &get_module_info($m);
278         $rv = $minfo{'desc'};
279         }
280 return $rv;
281 }