<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;"># Functions for searching the webmin docs and UI

=head2 search_webmin(phrase, [callback-function], [&amp;modules])

Searches all Webmin help pages, UI text, module names and config.info files
for entries matching the given phrase or word. Returns them sorted by relevance
order, each as a hash ref with the following keys :

=item mod - A module hash reference for the module the search result was in

=item rank - A result ranking, higher being better

=item type - One of mod (for module name), dir (for module directory), config (configuration setting), help (help page) or text (UI text)

=item text - The text that matched

=items cgis - An array ref of pages on which the text appears, each formatted like module/script.cgi

=cut
sub search_webmin
{
my ($re, $cbfunc, $onlymods) = @_;

# Work out this Webmin's URL base
my $urlhost = $ENV{'HTTP_HOST'};
if ($urlhost !~ /:/) {
	$urlhost .= ":".$ENV{'SERVER_PORT'};
	}
my $urlbase = ($ENV{'HTTPS'} eq 'ON' ? 'https://' : 'http://').$urlhost;

# Search module names and add to results list
my @rv = ( );
my $pn = &amp;get_product_name();
my @mods;
if ($onlymods) {
	# Modules specified by caller
	@mods = grep { &amp;foreign_available($_-&gt;{'dir'}) } @$onlymods;
	}
else {
	# All reasonable modules
	@mods = &amp;get_available_module_infos();
	}
@mods = grep { !$_-&gt;{'clone'} &amp;&amp; !$_-&gt;{'hidden'} }
	  grep { !$_-&gt;{'noui'} &amp;&amp; !$_-&gt;{$pn.'_noui'} } @mods;
@mods = sort { $b-&gt;{'longdesc'} cmp $a-&gt;{'longdesc'} } @mods;
foreach my $m (@mods) {
	if ($m-&gt;{'desc'} =~ /\Q$re\E/i) {
		# Module description match
		push(@rv, { 'mod' =&gt; $m,
			    'rank' =&gt; 10,
			    'type' =&gt; 'mod',
			    'link' =&gt; $m-&gt;{'dir'}.'/',
			    'text' =&gt; $m-&gt;{'desc'} });
		}
	elsif ($m-&gt;{'longdesc'} =~ /\Q$re\E/i) {
		# Module long description match
		push(@rv, { 'mod' =&gt; $m,
			    'rank' =&gt; 9.5,
			    'type' =&gt; 'mod',
			    'link' =&gt; $m-&gt;{'dir'}.'/',
			    'text' =&gt; $m-&gt;{'longdesc'} });
		}
	elsif ($m-&gt;{'dir'} =~ /\Q$re\E/i) {
		# Module directory match
		push(@rv, { 'mod' =&gt; $m,
			    'rank' =&gt; 9,
			    'type' =&gt; 'dir',
			    'link' =&gt; $m-&gt;{'dir'}.'/',
			    'text' =&gt; $urlbase."/".$m-&gt;{'dir'}."/" });
		}
	&amp;$cbfunc() if ($cbfunc);
	}

# Search module configs and their help pages
foreach my $m (@mods) {
	my %access = &amp;get_module_acl(undef, $m);
	next if ($access{'noconfig'});
	my $file = $prod eq 'webmin' ? "$m-&gt;{'dir'}/config.info"
				     : "$m-&gt;{'dir'}/uconfig.info";
	my %info = ( );
	my @info_order = ( );
	&amp;read_file($file, \%info, \@info_order);
	foreach my $o (@lang_order_list) {
		&amp;read_file("$file.$o", \%info);
		}
	my $section = undef;
	foreach my $c (@info_order) {
		my @p = split(/,/, $info{$c});
		if ($p[1] == 11) {
			$section = $c;
			}
		if ($p[0] =~ /\Q$re\E/i) {
			# Config description matches
			push(@rv, { 'mod' =&gt; $m,
				    'rank' =&gt; 8,
				    'type' =&gt; 'config',
				    'link' =&gt; "/config.cgi?module=$m-&gt;{'dir'}&amp;".
					     "section=".&amp;urlize($section)."#$c",
				    'text' =&gt; $p[0],
				  });
			}
		my $hfl = &amp;help_file($mod-&gt;{'dir'}, "config_".$c);
		my ($title, $help) = &amp;help_file_match($hfl);
		if ($help) {
			# Config help matches
			push(@rv, { 'mod' =&gt; $m,
                                    'rank' =&gt; 6,
				    'type' =&gt; 'help',
				    'link' =&gt; "/help.cgi/$m-&gt;{'dir'}/config_".$c,
				    'desc' =&gt; &amp;text('wsearch_helpfor', $p[0]),
				    'text' =&gt; $help,
				    'cgis' =&gt; [ "/config.cgi?".
					        "module=$m-&gt;{'dir'}&amp;section=".
						&amp;urlize($section)."#$c" ],
				   });
			}
		}
	&amp;$cbfunc() if ($cbfunc);
	}

# Search other help pages
my %lang_order_list = map { $_, 1 } @lang_order_list;
foreach my $m (@mods) {
	my $helpdir = &amp;module_root_directory($m-&gt;{'dir'})."/help";
	my %donepage = ( );
	opendir(DIR, $helpdir);
	foreach my $f (sort { length($b) &lt;=&gt; length($a) } readdir(DIR)) {
		next if ($f =~ /^config_/);	# For config help, already done

		# Work out if we should grep this help page - don't do the same
		# page twice for different languages
		my $grep = 0;
		my ($page, $lang);
		if ($f =~ /^(\S+)\.([^\.]+)\.html$/) {
			($page, $lang) = ($1, $2);
			if ($lang_order_list{$lang} &amp;&amp; !$donepage{$page}++) {
				$grep = 1;
				}
			}
		elsif ($f =~ /^(\S+)\.html$/) {
			$page = $1;
			if (!$donepage{$page}++) {
				$grep = 1;
				}
			}

		# If yes, search it
		if ($grep) {
			my ($title, $help) = &amp;help_file_match("$helpdir/$f");
			if ($title) {
				my @cgis = &amp;find_cgi_text(
					[ "hlink\\(.*'$page'",
					  "hlink\\(.*\"$page\"",
					  "header\\([^,]+,[^,]+,[^,]+,\\s*\"$page\"",
					  "header\\([^,]+,[^,]+,[^,]+,\\s*'$page'",
					], $m, 1);
				push(@rv, { 'mod' =&gt; $m,
					    'rank' =&gt; 6,
					    'type' =&gt; 'help',
					    'link' =&gt; "/help.cgi/$m-&gt;{'dir'}/$page",
					    'desc' =&gt; $title,
					    'text' =&gt; $help,
					    'cgis' =&gt; \@cgis });
				}
			}
		&amp;$cbfunc() if ($cbfunc);
		}
	closedir(DIR);
	}

# Then do text strings
my %gtext = &amp;load_language("");
MODULE: foreach my $m (@mods) {
	my %mtext = &amp;load_language($m-&gt;{'dir'});
	foreach my $k (keys %mtext) {
		next if ($gtext{$k});	# Skip repeated global strings
		$mtext{$k} =~ s/\$[0-9]//g;
		if ($mtext{$k} =~ /\Q$re\E/i) {
			# Find CGIs that use this text
			my @cgis = &amp;find_cgi_text(
				[ "\$text{'$k'}",
				  "\$text{\"$k\"}",
				  "\$text{$k}",
				  "&amp;text('$k'",
				  "&amp;text(\"$k\"" ], $m);
			if (@cgis) {
				push(@rv, { 'mod' =&gt; $m,
					    'rank' =&gt; 4,
					    'type' =&gt; 'text',
					    'text' =&gt; $mtext{$k},
					    'cgis' =&gt; \@cgis });
				}
			}
		}
	&amp;$cbfunc() if ($cbfunc);
	}

# Sort results by relevancy
# XXX can do better?
@rv = sort { $b-&gt;{'rank'} &lt;=&gt; $a-&gt;{'rank'} ||
	     lc($a-&gt;{'mod'}-&gt;{'desc'}) cmp lc($b-&gt;{'mod'}-&gt;{'desc'}) } @rv;
return @rv;
}

# highlight_text(text, [length])
# Returns text with the search term bolded, and truncated to 50 characters
sub highlight_text
{
local ($str, $len) = @_;
$len ||= 50;
local $hlen = $len / 2;
$str =~ s/&lt;[^&gt;]*&gt;//g;
if ($str =~ /(.*)(\Q$re\E)(.*)/i) {
	local ($before, $match, $after) = ($1, $2, $3);
	if (length($before) &gt; $hlen) {
		$before = "...".substr($before, length($before)-$hlen);
		}
	if (length($after) &gt; $hlen) {
		$after = substr($after, 0, $hlen)."...";
		}
	$str = $before."&lt;b&gt;".&amp;html_escape($match)."&lt;/b&gt;".$after;
	}
return $str;
}

# find_cgi_text(&amp;regexps, module, re-mode)
# Returns the relative URLs of CGIs that matches some regexps, in the given
# module. Does not include those that don't call some header function, as
# they cannot be linked to normally
sub find_cgi_text
{
local ($res, $m, $remode) = @_;
local $mdir = &amp;module_root_directory($m);
local @rv;
foreach my $f (glob("$mdir/*.cgi")) {
	local $found = 0;
	local $header = 0;
	open(CGI, $f);
	LINE: while(my $line = &lt;CGI&gt;) {
		if ($line =~ /(header|ui_print_header|ui_print_unbuffered_header)\(/) {
			$header++;
			}
		foreach my $r (@$res) {
			if (!$remode &amp;&amp; index($line, $r) &gt;= 0 ||
			    $remode &amp;&amp; $line =~ /$r/) {
				$found++;
				last LINE;
				}
			}
		}
	close(CGI);
	if ($found &amp;&amp; $header) {
		local $url = $f;
		$url =~ s/^\Q$root_directory\E\///;
		push(@rv, $url);
		}
	}
return @rv;
}

# help_file_match(file)
# Returns the title if some help file matches the current search
sub help_file_match
{
local ($f) = @_;
local $data = &amp;read_file_contents($f);
local $title;
if ($data =~ /&lt;header&gt;([^&lt;]*)&lt;\/header&gt;/) {
	$title = $1;
	}
$data =~ s/\s+/ /g;
$data =~ s/&lt;p&gt;/\n\n/gi;
$data =~ s/&lt;br&gt;/\n/gi;
$data =~ s/&lt;[^&gt;]+&gt;//g;
if ($data =~ /\Q$re\E/i) {
	return ($title, $data);
	}
return ( );
}

# cgi_page_title(module, cgi)
# Given a CGI, return the text for its page title, if possible
sub cgi_page_title
{
local ($m, $cgi) = @_;
local $data = &amp;read_file_contents(&amp;module_root_directory($m)."/".$cgi);
local $rv;
if ($data =~ /(ui_print_header|ui_print_unbuffered_header)\([^,]+,[^,]*(\$text{'([^']+)'|\$text{"([^"]+)"|\&amp;text\('([^']+)'|\&amp;text\("([^"]+)")/) {
	# New header function, with arg before title
	local $msg = $3 || $4 || $5 || $6;
	local %mtext = &amp;load_language($m);
	$rv = $mtext{$msg};
	}
elsif ($data =~ /(^|\s|mail_page_)header\(\s*(\$text{'([^']+)'|\$text{"([^"]+)"|\&amp;text\('([^']+)'|\&amp;text\("([^"]+)")/) {
	# Old header function
	local $msg = $3 || $4 || $5 || $6;
	local %mtext = &amp;load_language($m);
	$rv = $mtext{$msg};
	}
if ($cgi eq "index.cgi" &amp;&amp; !$rv) {
	# If no title was found for an index.cgi, use module title
	local %minfo = &amp;get_module_info($m);
	$rv = $minfo{'desc'};
	}
return $rv;
}

# cgi_page_args(module, cgi)
# Given a module and CGI name, returns a string of URL parameters that can be
# used for linking to it. Returns "none" if parameters are needed, but cannot
# be determined.
sub cgi_page_args
{
local ($m, $cgi) = @_;
local $mroot = &amp;module_root_directory($m);
if (-r "$mroot/cgi_args.pl") {
	# Module can tell us what args to use
	&amp;foreign_require($m, "cgi_args.pl");
	$args = &amp;foreign_call($m, "cgi_args", $cgi);
	if (defined($args)) {
		return $args;
		}
	}
if ($cgi eq "index.cgi") {
	# Index page is always safe to link to
	return undef;
	}
# Otherwise check if it appears to parse any args
local $data = &amp;read_file_contents($mroot."/".$cgi);
if ($data =~ /(ReadParse|ReadParseMime)\(/) {
	return "none";
	}
return undef;
}

1;

</pre></body></html>