2 # Search Webmin modules and help pages and text and config.info
4 BEGIN { push(@INC, ".."); };
10 $prod = &get_product_name();
11 $ucprod = ucfirst($prod);
12 &ui_print_unbuffered_header(
13 undef, &text('wsearch_title', $ucprod), "", undef, 0, 1);
15 # Validate search text
18 &error($text{'wsearch_esearch'});
23 # Work out this Webmin's URL base
24 $urlhost = $ENV{'HTTP_HOST'};
25 if ($urlhost !~ /:/) {
26 $urlhost .= ":".$ENV{'SERVER_PORT'};
28 $urlbase = ($ENV{'HTTPS'} eq 'ON' ? 'https://' : 'http://').$urlhost;
31 print &text('wsearch_searching', "<i>".&html_escape($re)."</i>"),"\n";
33 # Search module names and add to results list
35 @mods = sort { $b->{'longdesc'} cmp $a->{'longdesc'} }
36 grep { !$_->{'clone'} } &get_available_module_infos();
38 if ($m->{'desc'} =~ /\Q$re\E/i) {
39 # Module description match
40 push(@rv, { 'mod' => $m,
43 'link' => $m->{'dir'}.'/',
44 'text' => $m->{'desc'} });
46 elsif ($m->{'dir'} =~ /\Q$re\E/i) {
47 # Module directory match
48 push(@rv, { 'mod' => $m,
51 'link' => $m->{'dir'}.'/',
52 'text' => $urlbase."/".$m->{'dir'}."/" });
57 # Search module configs and their help pages
59 %access = &get_module_acl(undef, $m);
60 next if ($access{'noconfig'});
61 $file = $prod eq 'webmin' ? "$m->{'dir'}/config.info"
62 : "$m->{'dir'}/uconfig.info";
65 &read_file($file, \%info, \@info_order);
66 foreach $o (@lang_order_list) {
67 &read_file("$file.$o", \%info);
70 foreach $c (@info_order) {
71 @p = split(/,/, $info{$c});
75 if ($p[0] =~ /\Q$re\E/i) {
76 # Config description matches
77 push(@rv, { 'mod' => $m,
80 'link' => "config.cgi?module=$m->{'dir'}&".
81 "section=".&urlize($section)."#$c",
85 $hfl = &help_file($mod->{'dir'}, "config_".$c);
86 ($title, $help) = &help_file_match($hfl);
89 push(@rv, { 'mod' => $m,
92 'link' => "help.cgi/$m->{'dir'}/config_".$c,
93 'desc' => &text('wsearch_helpfor', $p[0]),
95 'cgis' => [ "/config.cgi?".
96 "module=$m->{'dir'}§ion=".
97 &urlize($section)."#$c" ],
104 # Search other help pages
105 %lang_order_list = map { $_, 1 } @lang_order_list;
107 $helpdir = &module_root_directory($m->{'dir'})."/help";
109 opendir(DIR, $helpdir);
110 foreach $f (sort { length($b) <=> length($a) } readdir(DIR)) {
111 next if ($f =~ /^config_/); # For config help, already done
113 # Work out if we should grep this help page - don't do the same
114 # page twice for different languages
116 if ($f =~ /^(\S+)\.([^\.]+)\.html$/) {
117 ($page, $lang) = ($1, $2);
118 if ($lang_order_list{$lang} && !$donepage{$page}++) {
122 elsif ($f =~ /^(\S+)\.html$/) {
124 if (!$donepage{$page}++) {
131 ($title, $help) = &help_file_match("$helpdir/$f");
133 my @cgis = &find_cgi_text(
134 [ "hlink\\(.*'$page'",
135 "hlink\\(.*\"$page\"",
136 "header\\([^,]+,[^,]+,[^,]+,\\s*\"$page\"",
137 "header\\([^,]+,[^,]+,[^,]+,\\s*'$page'",
139 push(@rv, { 'mod' => $m,
142 'link' => "help.cgi/$m->{'dir'}/$page",
153 # Then do text strings
154 %gtext = &load_language("");
155 MODULE: foreach $m (@mods) {
156 %mtext = &load_language($m->{'dir'});
157 foreach $k (keys %mtext) {
158 next if ($gtext{$k}); # Skip repeated global strings
159 $mtext{$k} =~ s/\$[0-9]//g;
160 if ($mtext{$k} =~ /\Q$re\E/i) {
161 # Find CGIs that use this text
162 my @cgis = &find_cgi_text(
167 "&text(\"$k\"" ], $m);
169 push(@rv, { 'mod' => $m,
172 'text' => $mtext{$k},
180 print &text('wsearch_found', scalar(@rv)),"<p>\n";
182 # Sort results by relevancy
184 @rv = sort { $b->{'rank'} <=> $a->{'rank'} ||
185 lc($a->{'mod'}->{'desc'}) cmp lc($b->{'mod'}->{'desc'}) } @rv;
189 print &ui_columns_start(
190 [ $text{'wsearch_htext'}, $text{'wsearch_htype'},
191 $text{'wsearch_hmod'}, $text{'wsearch_hcgis'} ], 100);
192 foreach my $r (@rv) {
193 $hi = &highlight_text($r->{'text'});
195 $hi = "<a href='$r->{'link'}'>$hi</a>";
198 foreach my $c (@{$r->{'cgis'}}) {
199 ($cmod, $cpage) = split(/\//, $c);
200 ($cpage, $cargs) = split(/\?/, $cpage);
201 $ctitle = &cgi_page_title($cmod, $cpage) || $cpage;
202 if ($r->{'mod'}->{'installed'}) {
203 $cargs ||= &cgi_page_args($cmod, $cpage);
206 # For modules that aren't installed, linking
207 # to a CGI is likely useless
210 if ($cargs eq "none") {
211 push(@links, $ctitle);
214 $cargs = "?".$cargs if ($cargs ne '' &&
215 $cargs !~ /^(\/|%2F)/);
217 "<a href='$cmod/$cpage$cargs'>$ctitle</a>");
221 @links = ( @links[0..1], "..." );
223 print &ui_columns_row([
225 $text{'wsearch_type_'.$r->{'type'}},
226 "<a href='$r->{'mod'}->{'dir'}/'>$r->{'mod'}->{'desc'}</a>",
227 &ui_links_row(\@links),
230 print &ui_columns_end();
233 print "<b>",&text('wsearch_enone',
234 "<tt>".&html_escape($re)."</tt>"),"</b><p>\n";
239 # highlight_text(text, [length])
240 # Returns text with the search term bolded, and truncated to 60 characters
243 local ($str, $len) = @_;
245 local $hlen = $len / 2;
246 $str =~ s/<[^>]*>//g;
247 if ($str =~ /(.*)(\Q$re\E)(.*)/i) {
248 local ($before, $match, $after) = ($1, $2, $3);
249 if (length($before) > $hlen) {
250 $before = "...".substr($before, length($before)-$hlen);
252 if (length($after) > $hlen) {
253 $after = substr($after, 0, $hlen)."...";
255 $str = $before."<b>".&html_escape($match)."</b>".$after;
260 # find_cgi_text(®exps, module, re-mode)
261 # Returns the relative URLs of CGIs that matches some regexps, in the given
262 # module. Does not include those that don't call some header function, as
263 # they cannot be linked to normally
266 local ($res, $m, $remode) = @_;
267 local $mdir = &module_root_directory($m);
269 foreach my $f (glob("$mdir/*.cgi")) {
273 LINE: while(my $line = <CGI>) {
274 if ($line =~ /(header|ui_print_header|ui_print_unbuffered_header)\(/) {
277 foreach my $r (@$res) {
278 if (!$remode && index($line, $r) >= 0 ||
279 $remode && $line =~ /$r/) {
286 if ($found && $header) {
288 $url =~ s/^\Q$root_directory\E\///;
295 # help_file_match(file)
296 # Returns the title if some help file matches the current search
300 local $data = &read_file_contents($f);
302 if ($data =~ /<header>([^<]*)<\/header>/) {
306 $data =~ s/<p>/\n\n/gi;
307 $data =~ s/<br>/\n/gi;
308 $data =~ s/<[^>]+>//g;
309 if ($data =~ /\Q$re\E/i) {
310 return ($title, $data);
315 # cgi_page_title(module, cgi)
316 # Given a CGI, return the text for its page title, if possible
319 local ($m, $cgi) = @_;
320 local $data = &read_file_contents(&module_root_directory($m)."/".$cgi);
322 if ($data =~ /(ui_print_header|ui_print_unbuffered_header)\([^,]+,[^,]*(\$text{'([^']+)'|\$text{"([^"]+)"|\&text\('([^']+)'|\&text\("([^"]+)")/) {
323 # New header function, with arg before title
324 local $msg = $3 || $4 || $5 || $6;
325 local %mtext = &load_language($m);
328 elsif ($data =~ /(^|\s)header\(\s*(\$text{'([^']+)'|\$text{"([^"]+)"|\&text\('([^']+)'|\&text\("([^"]+)")/) {
329 # Old header function
330 local $msg = $3 || $4 || $5 || $6;
331 local %mtext = &load_language($m);
334 if ($cgi eq "index.cgi" && !$rv) {
335 # If no title was found for an index.cgi, use module title
336 local %minfo = &get_module_info($m);
337 $rv = $minfo{'desc'};
342 # cgi_page_args(module, cgi)
343 # Given a module and CGI name, returns a string of URL parameters that can be
344 # used for linking to it. Returns "none" if parameters are needed, but cannot
348 local ($m, $cgi) = @_;
349 local $mroot = &module_root_directory($m);
350 if (-r "$mroot/cgi_args.pl") {
351 # Module can tell us what args to use
352 &foreign_require($m, "cgi_args.pl");
353 $args = &foreign_call($m, "cgi_args", $cgi);
354 if (defined($args)) {
358 if ($cgi eq "index.cgi") {
359 # Index page is always safe to link to
362 # Otherwise check if it appears to parse any args
363 local $data = &read_file_contents($mroot."/".$cgi);
364 if ($data =~ /(ReadParse|ReadParseMime)\(/) {
371 # Print one dot per second
375 if ($now > $last_print_search_dot) {
377 $last_print_search_dot = $now;