Main Page | Directories | File List | File Members

utils.php

Go to the documentation of this file.
00001 <?php
00002 # This file is part of the Savane project
00003 # <http://gna.org/projects/savane/>
00004 #
00005 # $Id: utils.php 5500 2006-02-26 12:11:12Z toddy $
00006 #
00007 #  Copyright 1999-2000 (c) The SourceForge Crew
00008 #  Copyright 2000-2003 (c) Free Software Foundation
00009 #
00010 #  Copyright 2002-2006 (c) Mathieu Roy <yeupou--gnu.org>,
00011 #                          Tobias Toedter <t.toedter--gmx.net>
00012 #
00013 # The Savane project is free software; you can redistribute it and/or
00014 # modify it under the terms of the GNU General Public License
00015 # as published by the Free Software Foundation; either version 2
00016 # of the License, or (at your option) any later version.
00017 #
00018 # The Savane project is distributed in the hope that it will be useful,
00019 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00020 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00021 # GNU General Public License for more details.
00022 #
00023 # You should have received a copy of the GNU General Public License
00024 # along with the Savane project; if not, write to the Free Software
00025 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00026 
00027 function utils_safeinput ($string)
00028 {
00029   return safeinput($string);
00030 }
00031 
00032 
00033 # This function permit including site specific content with ease
00034 function utils_get_content ($file)
00035 {
00036   if (is_file($GLOBALS['sys_incdir'].'/'.$file.'.'.$GLOBALS['locale']))
00037     {
00038       # there is localized version of the file :
00039       include($GLOBALS['sys_incdir'].'/'.$file.'.'.$GLOBALS['locale']);
00040     }
00041   elseif (is_file($GLOBALS['sys_incdir'].'/'.$file.'.txt'))
00042     {
00043       include($GLOBALS['sys_incdir'].'/'.$file.'.txt');
00044     }
00045   else
00046     {
00047       fb(sprintf(_("Warning: Savane was not able to read \"%s\" site-specific information, please contact administrators"), $file), 1);
00048     }
00049 }
00050 
00051 # Make sure that to avoid malicious file paths
00052 function utils_check_path ($path)
00053 {
00054   if (eregi(".*\.\.\/.*", $path))
00055     {
00056       exit_error('Error','Malformed url');
00057     }
00058 }
00059 
00060 # In a string, replace %PROJECT by the group_name
00061 # (useful for group type configuration)
00062 function utils_makereal ($data, $string="%PROJECT", $replacement=0)
00063 {
00064   if (!$replacement)
00065     { $replacement = $GLOBALS[group_name]; }
00066   return ereg_replace($string, $replacement, $data);
00067 }
00068 
00069 # Add unavailable css class to a link if required
00070 function utils_link ($url, $title, $defaultclass=0, $available=1, $help=0)
00071 {
00072   if (!$available)
00073     { $defaultclass = 'unavailable'; }
00074 
00075   $return = '<a href="'.$url.'"';
00076 
00077   if ($defaultclass)
00078     { $return .= ' class="'.$defaultclass.'"'; }
00079   if ($help)
00080     { $return .= ' title="'.$help.'"'; }
00081   $return .= '>'.$title.'</a>';
00082   return $return;
00083 }
00084 
00085 # make an clean email link depending on the authentification level of the user
00086 function utils_email ($address, $nohtml=0)
00087 {
00088   if (user_isloggedin())
00089     {
00090       if ($nohtml)
00091         { return $address; }
00092 
00093       return '<a href="mailto:'.$address.'">'.$address.'</a>';
00094 
00095     }
00096   else
00097     {
00098       if ($nohtml)
00099         { return _("-unavailable-"); }
00100 
00101       return utils_help(_("-unavailable-"),
00102                         _("This information is not provided to anonymous users"),
00103                         1);
00104     }
00105 
00106 }
00107 
00108 # Found out if a string is pure ASCII or not
00109 function utils_is_ascii ($string)
00110 {
00111   return preg_match('%^(?: [\x09\x0A\x0D\x20-\x7E] )*$%xs', $string);
00112 }
00113 
00114 # Alias function
00115 function utils_altrow ($i)
00116 {
00117   return html_get_alt_row_color ($i);
00118 }
00119 
00120 function utils_cutstring ($string, $lenght=35)
00121 {
00122   $string = rtrim($string);
00123   if (strlen($string) > $lenght)
00124     {
00125       $string = substr($string, 0, $lenght);
00126       $string = substr($string, 0, strrpos($string, ' '));
00127       $string .= "...";
00128     }
00129   return $string;
00130 }
00131 
00138 function format_date($format="default", $timestamp, $default_value='-')
00139 {
00140   return utils_format_date($timestamp, $format);
00141 }
00142 
00158 function utils_format_date($timestamp, $format="default")
00159 {
00160   global $sys_datefmt;
00161   if ($timestamp == 0)
00162     {
00163       return '-';
00164     }
00165 
00166   # The installation configured a specific date format. This is not nice
00167   # this will prevent locales from being used
00168   if ($sys_datefmt)
00169     {
00170       return strftime($sys_datefmt, $timestamp);
00171     }
00172 
00173   ## Go at task #2614 to discuss about this
00174   # Used by default
00175   switch ($format)
00176     {
00177     case 'short':
00178       {
00179         # To be used in tables, nowhere else
00180         return strftime('%a %x, %R', $timestamp);
00181       }
00182     case 'minimal':
00183       {
00184         # To be used where place is really lacking, like in feature boxes.
00185         # (Nowhere else, it is too uninformative)
00186         return strftime('%x', $timestamp);
00187       }
00188     default:
00189       {
00190         # Used by default
00191         # Mention timezone to non-logged in users or in printer mode.
00192         # Logged-in users have this as account setting, so we can assume they
00193         # now and dont want time wasted by that
00194         if (user_isloggedin() && !defined(PRINTER))
00195           {
00196             return strftime('%A %x '._("at").' %R', $timestamp);
00197           }
00198         else
00199           {
00200             return strftime('%A %x '._("at").' %R %Z', $timestamp);
00201           }
00202       }
00203     }
00204 
00205   return false;
00206 }
00207 
00208 
00209 # Convert a date as used in the bug tracking system and other services (YYYY-MM-DD)
00210 # into a Unix time
00211 # Returns a list with two values: the unix time and a boolean saying whether the conversion
00212 # went well (true) or bad (false)
00213 function utils_date_to_unixtime ($date)
00214 {
00215   $res = preg_match("/\s*(\d+)-(\d+)-(\d+)/",$date,$match);
00216   if ($res == 0)
00217     { return array(0,false); }
00218   list(,$year,$month,$day) = $match;
00219   $time = mktime(0, 0, 0, $month, $day, $year);
00220   dbg("DBG Matching date $date -> year $year, month $month,day $day -> time = $time<br />");
00221   return array($time,true);
00222 }
00223 
00224 function utils_read_file($filename)
00225 {
00226   @$fp = fopen($filename, "r");
00227   if ($fp)
00228     {
00229       $val = fread($fp, filesize($filename));
00230       fclose ($fp);
00231       return $val;
00232     }
00233   return false;
00234 }
00235 
00236 function utils_filesize($filename, $file_size=0)
00237 {
00238   # If file size is defined, assume that we just want an unit conversion.
00239   if (!$file_size)
00240     {   $file_size = filesize($filename); }
00241 
00242   if ($file_size >= 1048576)
00243     {
00244       $file_size = round($file_size / 1048576 * 100) / 100 . _("MB");
00245     }
00246   elseif ($file_size >= 1024)
00247     {
00248       $file_size = round($file_size / 1024 * 100) / 100 . _("KB");
00249     }
00250   else
00251     {
00252       $file_size = $file_size . _("B");
00253     }
00254 
00255   return $file_size;
00256 }
00257 
00258 function utils_fileextension($filename)
00259 {
00260 
00261   $ext = substr(basename($filename), strrpos(basename($filename),".") + 1);
00262   if ($ext==gz || $ext==bz2)
00263     {
00264       $ext = substr(basename($filename), strrpos(basename($filename),".") - 3);
00265     }
00266   if ($ext==rpm)
00267     {
00268       $long_ext = _("rpm package");
00269     }
00270   if ($ext==deb)
00271     {
00272       $long_ext = _("debian package");
00273     }
00274   if ($ext==deb || $ext==rpm)
00275     {
00276       $arch_type = substr(basename($filename), strrpos(basename($filename),".") - 3);
00277       if ($arch_type == "src.".$ext)
00278         {
00279           $long_ext = sprintf(_("source %s"), $long_ext);
00280         }
00281       if ($arch_type == "rch.".$ext)
00282         {
00283           $long_ext = sprintf(_("arch independant %s"), $long_ext);
00284         }
00285       if ($arch_type == "386.".$ext)
00286         {
00287           $long_ext = sprintf(_("%s for i386 (ix86)"), $long_ext);
00288         }
00289       if ($arch_type == "586.".$ext)
00290         {
00291           $long_ext = sprintf(_("%s for i586"), $long_ext);
00292         }
00293       if ($arch_type == "686.".$ext)
00294         {
00295           $long_ext = sprintf(_("%s for i686"), $long_ext);
00296         }
00297       if ($arch_type == "a64.".$ext)
00298         {
00299           $long_ext = sprintf(_("%s for Itanium 64"), $long_ext);
00300         }
00301       if ($arch_type == "arc.".$ext)
00302         {
00303           $long_ext = sprintf(_("%s for Sparc"), $long_ext);
00304         }
00305       if ($arch_type == "pha.".$ext)
00306         {
00307           $long_ext = sprintf(_("%s for Alpha"), $long_ext);
00308         }
00309       if ($arch_type == "ppc.".$ext)
00310         {
00311           $long_ext = sprintf(_("%s for PowerPC"), $long_ext);
00312         }
00313       if ($arch_type == "390.".$ext)
00314         {
00315           $long_ext = sprintf(_("%s for s390"), $long_ext);
00316         }
00317       $ext = $long_ext;
00318     }
00319   return $ext;
00320 }
00321 
00322 function utils_prep_string_for_sendmail($body)
00323 {
00324   $body=str_replace("\\","\\\\",$body);
00325   $body=str_replace("\"","\\\"",$body);
00326   $body=str_replace("\$","\\\$",$body);
00327   $body=str_replace("`","\\`",$body);
00328   return $body;
00329 }
00330 
00331 function utils_unconvert_htmlspecialchars($string)
00332 {
00333   if (strlen($string) < 1)
00334     {
00335       return '';
00336     }
00337   else
00338     {
00339       $string=str_replace('&nbsp;',' ',$string);
00340       $string=str_replace('&quot;','"',$string);
00341       $string=str_replace('&gt;','>',$string);
00342       $string=str_replace('&lt;','<',$string);
00343       $string=str_replace('&amp;','&',$string);
00344       return $string;
00345     }
00346 }
00347 
00348 function utils_remove_htmlheader($string)
00349 {
00350   $string = eregi_replace('(^.*<html[^>]*>.*<body[^>]*>)|(</body[^>]*>.*</html[^>]*>.*$)', '', $string);
00351   return $string;
00352 }
00353 
00354 function utils_result_column_to_array($result, $col=0)
00355 {
00356   /*
00357                 Takes a result set and turns the optional column into
00358                 an array
00359   */
00360   $rows=db_numrows($result);
00361 
00362   if ($rows > 0)
00363     {
00364       $arr=array();
00365       for ($i=0; $i<$rows; $i++)
00366         {
00367           $arr[$i]=db_result($result,$i,$col);
00368         }
00369     }
00370   else
00371     {
00372       $arr=array();
00373     }
00374   return $arr;
00375 }
00376 
00377 function result_column_to_array($result, $col=0)
00378 {
00379   /*
00380                 backwards compatibility
00381   */
00382   return utils_result_column_to_array($result, $col);
00383 }
00384 
00385 function utils_wrap_find_space($string,$wrap)
00386 {
00387   $start=$wrap-5;
00388   $try=1;
00389   $found=false;
00390 
00391   while (!$found)
00392     {
00393       #find the first space starting at $start
00394       $pos=@strpos($string,' ',$start);
00395 
00396       #if that space is too far over, go back and start more to the left
00397       if (($pos > ($wrap+5)) || !$pos)
00398         {
00399           $try++;
00400           $start=($wrap-($try*5));
00401           #if we've gotten so far left , just truncate the line
00402           if ($start<=10)
00403             {
00404               return $wrap;
00405             }
00406           $found=false;
00407         }
00408       else
00409         {
00410           $found=true;
00411         }
00412     }
00413 
00414   return $pos;
00415 }
00416 
00417 function utils_line_wrap ($text, $wrap = 78, $break = "\n")
00418 {
00419   $paras = explode("\n", $text);
00420 
00421   $result = array();
00422   $i = 0;
00423   while ($i < count($paras))
00424     {
00425       if (strlen($paras[$i]) <= $wrap)
00426         {
00427           $result[] = $paras[$i];
00428           $i++;
00429         }
00430       else
00431         {
00432           $pos=utils_wrap_find_space($paras[$i],$wrap);
00433 
00434           $result[] = substr($paras[$i], 0, $pos);
00435 
00436           $new = trim(substr($paras[$i], $pos, strlen($paras[$i]) - $pos));
00437           if ($new != '')
00438             {
00439               $paras[$i] = $new;
00440               $pos=utils_wrap_find_space($paras[$i],$wrap);
00441             }
00442           else
00443             {
00444               $i++;
00445             }
00446         }
00447     }
00448   return implode($break, $result);
00449 }
00450 
00451 
00452 
00464 function utils_basic_markup($text)
00465 {
00466   $lines = explode("\n", $text);
00467   $result = array();
00468 
00469   foreach ($lines as $line)
00470     {
00471       $result[] = _markup_inline($line);
00472     }
00473 
00474   return join("\n", $result);
00475 }
00476 
00477 
00478 
00490 function utils_rich_markup($text)
00491 {
00492   return utils_full_markup($text, false);
00493 }
00494 
00495 
00496 
00504 function utils_full_markup($text, $allow_headings=true)
00505 {
00506   $lines = explode("\n", $text);
00507   $result = array();
00508 
00509   # we use a stack (last in, first out) to track the current
00510   # context (paragraph, lists) so we can correctly close tags
00511   $context_stack = array();
00512 
00513   $quoted_text = false;
00514   $verbatim = false;
00515   foreach ($lines as $index => $line)
00516     {
00517       # the verbatim tags are not allowed to be nested, because
00518       # they are translated to HTML <pre>, which in turn is also
00519       # not allowed to be nested.
00520       # therefore, we don't need a counter of the level, but only
00521       # a simple bool flag
00522       if ($line == '+verbatim+' and !$verbatim)
00523         {
00524           $verbatim = true;
00525           # empty the context stack
00526           $line = join("\n", $context_stack).'<pre>';
00527           $context_stack = array('</pre>');
00528         }
00529 
00530       # if we're in the verbatim markup, don't apply the markup
00531       if ($verbatim)
00532         {
00533           # disable the +nomarkup+ tags by inserting a unique string.
00534           # this has to be done in the original string, because that
00535           # is the one which will be split upon the +nomarkup+ tags,
00536           # see below
00537           $escaped_line = str_replace('nomarkup',
00538             'no-1a4f67a7-4eae-4aa1-a2ef-eecd8af6a997-markup', $line);
00539           $lines[$index] = $escaped_line;
00540           $result[] = $escaped_line;
00541         }
00542       else
00543         {
00544           $result[] = _full_markup($line, $allow_headings, &$context_stack, &$quoted_text);
00545         }
00546 
00547       if ($line == '-verbatim-' and $verbatim)
00548         {
00549           $verbatim = false;
00550           # empty the context stack
00551           $line = join("\n", $context_stack);
00552           $context_stack = array();
00553           array_pop($result);
00554           $result[] = '</pre>';
00555         }
00556     }
00557 
00558   # make sure that all previously used contexts get their
00559   # proper closing tag by merging in the last closing tags
00560   $markup_text = join("\n", array_merge($result, $context_stack));
00561 
00562   # it's easiest to markup everything, without supporting the nomarkup
00563   # tag. afterwards, we replace every nomarkup tag pair with the content
00564   # between those tags in the original string
00565   $original = preg_split('/([+-]nomarkup[+-])/', join("\n", $lines), -1,
00566     PREG_SPLIT_DELIM_CAPTURE);
00567   $markup = preg_split('/([+-]nomarkup[+-])/', $markup_text, -1,
00568     PREG_SPLIT_DELIM_CAPTURE);
00569   # save the HTML tags from the last element in the markup array, see below
00570   $last_tags = $markup[count($markup)-1];
00571   $nomarkup_level = 0;
00572 
00573   foreach ($original as $index => $original_text)
00574     {
00575       # keep track of nomarkup tags
00576       if ($original_text == '+nomarkup+') $nomarkup_level++;
00577       if ($original_text == '-nomarkup-') $nomarkup_level--;
00578 
00579       # if the current match is the nomarkup tag, we don't want it to
00580       # show up in the markup text -> set it to an empty string
00581       if (preg_match('/([+-]nomarkup[+-])/', $original_text))
00582         {
00583           $markup[$index] = '';
00584           $original_text = '';
00585         }
00586       # while we're in a nomarkup environment, the already marked up text
00587       # needs to be replaced with the original content. Also, we need
00588       # to add <br />  tags for newlines.
00589       if ($nomarkup_level > 0)
00590         {
00591           $markup[$index] = nl2br($original_text);
00592         }
00593     }
00594 
00595   # normally, $nomarkup_level must be zero at this point. however, if
00596   # the user submits wrong markup and forgets to close the -nomarkup-
00597   # tag, we need to take care of that.
00598   # To do this, we need to look for closing tags which have been deleted.
00599   if ($nomarkup_level > 0)
00600     {
00601       $trailing_markup = array_reverse(split("\n", $last_tags));
00602       $restored_tags = '';
00603       foreach ($trailing_markup as $tag)
00604         {
00605           if (preg_match('/^\s*<\/[a-z]+>$/', $tag))
00606             {
00607               $restored_tags = "\n$tag$restored_tags";
00608             }
00609           else
00610             {
00611               $markup[] = $restored_tags;
00612               break;
00613             }
00614         }
00615     }
00616 
00617   # lastly, revert the escaping of +nomarkup+ tags done above
00618   # for verbatim environments
00619   return str_replace('no-1a4f67a7-4eae-4aa1-a2ef-eecd8af6a997-markup',
00620     'nomarkup', join('', $markup));
00621 }
00622 
00623 
00624 
00633 function _full_markup($line, $allow_headings, &$context_stack, &$quoted_text)
00634 {
00635   #############################################################
00636   # context formatting
00637   #
00638   # the code below marks up recognized special characters,
00639   # by starting a new context (e.g. headings and lists)
00640   #############################################################
00641 
00642   # generally, we want to start a new paragraph. this will be set
00643   # to false, if a new paragraph is no longer appropriate, like
00644   # for headings or lists
00645   $start_paragraph = true;
00646 
00647   # Match the headings, e.g. === heading ===
00648   if ($allow_headings)
00649     {
00650       $line = _markup_headings($line, &$context_stack, &$start_paragraph);
00651     }
00652 
00653   # Match list items
00654   $line = _markup_lists($line, &$context_stack, &$start_paragraph);
00655 
00656   # replace at least four '-' sign with a horizontal ruler
00657   if (preg_match('/^----+\s*$/', $line))
00658     {
00659       $line = join("\n", $context_stack).'<hr />';
00660       $context_stack = array();
00661       $start_paragraph = false;
00662     }
00663 
00664   #############################################################
00665   # inline formatting
00666   #
00667   # the code below marks up recognized special characters,
00668   # without starting a new context (e.g. <strong> and <em>)
00669   #############################################################
00670 
00671   $line = _markup_inline($line);
00672 
00673   #############################################################
00674   # paragraph formatting
00675   #
00676   # the code below is responsible for doing the Right Thing(tm)
00677   # by either starting a new paragraph and closing any previous
00678   # context or continuing an existing paragraph
00679   #############################################################
00680 
00681   # change the quoteing mode when the line start with '>'
00682   if (substr($line, 0, 4) == '&gt;')
00683     {
00684       # if the previous line was not quoted, start a new quote paragraph
00685       if (!$quoted_text)
00686         {
00687           $line = join("\n", $context_stack)."<p class=\"quote\">$line";
00688           # empty the stack
00689           $context_stack = array('</p>');
00690           $start_paragraph = false;
00691         }
00692       $quoted_text = true;
00693     }
00694   else
00695     {
00696       # if the previous line was quoted, end the quote paragraph
00697       if ($quoted_text and $start_paragraph and $line != '')
00698         {
00699           $line = join("\n", $context_stack)."\n<p>$line";
00700           # empty the stack
00701           $context_stack = array('</p>');
00702         }
00703       $quoted_text = false;
00704     }
00705 
00706   # don't start a new paragraph again, if we already did that
00707   if ($context_stack[0] == '</p>')
00708     {
00709       $start_paragraph = false;
00710     }
00711 
00712   # add proper closing tags when we encounter an empty line.
00713   # note that there might be no closing tags, in this case
00714   # the line will remain emtpy.
00715   if ($line == '')
00716     {
00717       $line = join("\n", $context_stack)."$line";
00718       # empty the stack
00719       $context_stack = array();
00720       $start_paragraph = false;
00721     }
00722 
00723   # Finally start a new paragraph if appropriate
00724   if ($start_paragraph)
00725     {
00726       # make sure that all previously used contexts get their
00727       # proper closing tag
00728       $line = join("\n", $context_stack)."<p>$line";
00729       # empty the stack
00730       $context_stack = array('</p>');
00731     }
00732 
00733   # append a linebreak while in paragraph mode
00734   if ($context_stack[0] == '</p>')
00735     {
00736       $line .= '<br />';
00737     }
00738 
00739   return $line;
00740 }
00741 
00742 
00743 
00751 function _markup_headings($line, &$context_stack, &$start_paragraph)
00752 {
00753   if (preg_match(
00754     # find one to four '=' signs at the start of a line
00755     '/^(={1,4})'
00756     # followed by exactly one space
00757     .' '
00758     # followed by any character
00759     .'(.+)'
00760     # followed by exactly one space
00761     .' '
00762     # followed by one to four '=' signs at the end of a line (whitespace allowed)
00763     .'(={1,4})\s*$/', $line, $matches))
00764     {
00765       $header_level_start = max(min(strlen($matches[1]), 4), 1);
00766       $header_level_end = strlen($matches[3]);
00767       if ($header_level_start == $header_level_end)
00768         {
00769           # if the user types '= heading =' (one '=' sign), it will
00770           # actually be rendered as a level 3 heading <h3>
00771           $header_level_start += 2;
00772           $header_level_end += 2;
00773 
00774           $line = "<h$header_level_start>$matches[2]</h$header_level_end>";
00775           # make sure that all previously used contexts get their
00776           # proper closing tag
00777           $line = join("\n", $context_stack).$line;
00778           # empty the stack
00779           $context_stack = array();
00780           $start_paragraph = false;
00781         }
00782     }
00783   return $line;
00784 }
00785 
00786 
00787 
00795 function _markup_lists($line, &$context_stack, &$start_paragraph)
00796 {
00797   if (preg_match('/^([*0]+) (.+)$/', $line, $matches))
00798     {
00799       # determine the list level currently in use
00800       $current_list_level = 0;
00801       foreach ($context_stack as $context)
00802         {
00803           if ($context == '</ul>' or $context == '</ol>')
00804             {
00805               $current_list_level++;
00806             }
00807         }
00808 
00809       # determine whether the user list levels match the list
00810       # level we have in our context stack
00811       #
00812       # this will catch (potential) errors of the following form:
00813       # * list start
00814       # 0 maybe wrong list character
00815       # * list end
00816       $markup_position = 0;
00817       foreach (array_reverse($context_stack) as $context)
00818         {
00819           # we only care for the list types
00820           if ($context != '</ul>' and $context != '</ol>')
00821             {
00822               continue;
00823             }
00824 
00825           $markup_character = substr($matches[1], $markup_position, 1);
00826 
00827           if (($markup_character === '*' and $context != '</ul>')
00828             or ($markup_character === '0' and $context != '</ol>'))
00829             {
00830               # force a new and clean list start
00831               $current_list_level = 0;
00832               break;
00833             }
00834           else
00835             {
00836               $markup_position++;
00837             }
00838         }
00839 
00840       # if we're not in a list, close the previous context
00841       $line = '';
00842       if ($current_list_level == 0)
00843         {
00844           $line = join("\n", $context_stack);
00845           $context_stack = array();
00846         }
00847 
00848       # determine the list level the user wanted
00849       $wanted_list_level = strlen($matches[1]);
00850 
00851       # here we start a new list and make sure that the markup
00852       # is valid, even if the user did skip one or more list levels
00853       $list_level_counter = $current_list_level;
00854       while ($list_level_counter < $wanted_list_level)
00855         {
00856           switch (substr($matches[1], $list_level_counter, 1))
00857             {
00858               case '*':
00859                 $tag = 'ul';
00860                 break;
00861               case '0':
00862                 $tag = 'ol';
00863                 break;
00864             }
00865           $line .= "<$tag>\n<li>";
00866           array_unshift($context_stack, "</$tag>");
00867           array_unshift($context_stack, "</li>");
00868           $list_level_counter++;
00869         }
00870 
00871       # here we end a previous list and make sure that the markup
00872       # is valid, even if the user did skip one or more list levels
00873       $list_level_counter = $current_list_level;
00874       while ($list_level_counter > $wanted_list_level)
00875         {
00876           $line .= array_shift($context_stack)."\n"
00877             .array_shift($context_stack)."\n";
00878           $list_level_counter--;
00879         }
00880 
00881       # prepare the next item of the same list level
00882       if ($current_list_level >= $wanted_list_level)
00883         {
00884           $line .= "</li>\n<li>";
00885         }
00886 
00887       # finally, append the list item
00888       $line .= $matches[2];
00889       $start_paragraph = false;
00890     }
00891   return $line;
00892 }
00893 
00894 
00895 
00903 function _markup_inline($line)
00904 {
00905   # Group_id may be necessary for recipe #nnn links
00906   global $group_id;
00907 
00908   if ($group_id)
00909     {
00910       $comingfrom = "&amp;comingfrom=$group_id";
00911     }
00912 
00913   if (strlen($line) == 0)
00914     {
00915       return;
00916     }
00917 
00918   # 1 and 2 could maybe be replaced by a better regexp, as they may in
00919   # some very rare case, break content. But we havent found such case.
00920   # Feel free to propose better regexp.
00921 
00922   # 1. Don't mess with HTML links already there by escaping
00923   # the protocol separator with ":##"
00924   $line = eregi_replace("(<a href=\"[a-z]+)://([^<[:space:]]+)://([^<[:space:]]+)</a>",
00925     '\1:##\2:##\3</a>', $line);
00926   $line = eregi_replace("(<a href=\"[a-z]+)://([^<\"[:space:]]+)\"",
00927     '\1:##\2"', $line);
00928 
00929   # 2. Dont mess with links surrounded by < >
00930   # (Which are in fact html special chars)
00931   $line = str_replace('&gt;', ' ##>', $line);
00932   $line = str_replace('&lt;', '<## ', $line);
00933 
00934   # Prepare usual links: prefix every "www." with "http://"
00935   $line = preg_replace('/(^|\s+)(www\.)/i', '$1http://$2', $line);
00936 
00937   # replace the @ sign with an HTML entity, if it is used within
00938   # an url (e.g. for pointers to mailing lists). This way, the
00939   # @ sign doesn't get mangled in the e-mail markup code
00940   # below. See bug #2689 on http://gna.org/ for reference.
00941   $line = eregi_replace("([a-z]+://[^<>[:space:]]+)@", "\\1&#64;", $line);
00942 
00943   # Prepare the markup for normal links, e.g. http://test.org, by
00944   # surrounding them with braces []
00945   $line = preg_replace('/(^|[^\[a-z])([a-z]+:\/\/[^<>\s]+[a-z0-9\/]+)/i',
00946     '$1[$2]', $line);
00947 
00948   # do a markup for mail links, e.g. info@support.org
00949   $line = eregi_replace("([a-z0-9_+-.]+@([a-z0-9_+-]+\.)+[a-z]+)",
00950     utils_email('\1'), $line);
00951 
00952   # Revert the escaping of already provided HTML links, done above
00953   $line = str_replace(":##", "://", $line);
00954   $line = str_replace(' ##>', '&gt;', $line);
00955   $line = str_replace('<## ', '&lt;', $line);
00956 
00957   # Links between items
00958   # FIXME: it should be i18n, but in a clever way, meaning that everytime
00959   # a form is submitted with such string, the string get converted in
00960   # english so we always get the links found without having a regexp
00961   # including every possible language.
00962   $trackers = array (
00963       "bugs?" => "bugs/?",
00964       "support|sr" => "support/?",
00965       "tasks?" => "task/?",
00966       "recipes?|rcp" => "cookbook/?func=detailitem$comingfrom&amp;item_id=",
00967       "patch" => "patch/?",
00968       # In this case, we make the link pointing to support, it wont matter,
00969       # the download page is in every tracker and does not check if the tracker
00970       # is actually used
00971       "files?" => "support/download.php?file_id=",
00972   );
00973   foreach ($trackers as $regexp => $link)
00974     {
00975       $line = preg_replace("/(^|\s+)($regexp)\s*#([0-9]+)/i",
00976         '$1<em><a href="'.$GLOBALS['sys_home']
00977         .$link.'$3">$2&nbsp;#$3</a></em>', $line);
00978     }
00979 
00980   # add an internal link for comments
00981   $line = preg_replace('/(comments?)\s*#([0-9]+)/i',
00982     '<em><a href="#comment$2">$1&nbsp;#$2</a></em>', $line);
00983 
00984   # Add support for named hyperlinks, e.g.
00985   # [http://gna.org/ Text] -> <a href="http://gna.org/">Text</a>
00986   $line = preg_replace(
00987     # find the opening brace '['
00988     '/\['
00989     # followed by the protocol, either http:// or https://
00990     .'(https?:\/\/'
00991     # match any character except whitespace or the closing
00992     # brace ']' for the actual link
00993     .'[^\s\]]+)'
00994     # followed by at least one whitespace
00995     .'\s+'
00996     # followed by any character (non-greedy) and the
00997     # next closing brace ']'
00998     .'(.+?)\]/', '<a href="$1">$2</a>', $line);
00999 
01000   # Add support for unnamed hyperlinks, e.g.
01001   # [http://gna.org/] -> <a href="http://gna.org/">http://gna.org/</a>
01002   $line = preg_replace(
01003     # find the opening brace '['
01004     '/\['
01005     # followed by the protocol, either http:// or https://
01006     .'(https?:\/\/'
01007     # match any character except whitespace (non-greedy) for
01008     # the actual link, followed by the closing brace ']'
01009     .'[^\s]+?)\]/', '<a href="$1">$1</a>', $line);
01010 
01011   # *word* -> <strong>word</strong>
01012   $line = preg_replace(
01013     # find an asterisk
01014     '/\*'
01015     # then one character (except a space or asterisk)
01016     .'([^* ]'
01017     # then (optionally) any character except asterisk
01018     .'[^*]*?)'
01019     # then an asterisk
01020     .'\*/', '<strong>$1</strong>', $line);
01021 
01022   # _word_ -> <em>word</em>
01023   $line = preg_replace(
01024     # allow for the pattern to start at the beginning of a line.
01025     # if it doesn't start there, the character before the slash
01026     # must be either whitespace or the closing brace '>', to
01027     # allow for nested html tags (e.g. <p>_markup_</p>).
01028     '/(^|\s+|>)'
01029     # match the underscore
01030     .'_'
01031     # match any character (non-greedy)
01032     .'(.+?)'
01033     # match the ending underscore and either end of line or
01034     # a non-word character
01035     .'_(\W|$)/', '$1<em>$2</em>$3', $line);
01036 
01037   return $line;
01038 }
01039 
01040 
01041 
01042 function utils_user_link ($username, $realname=0)
01043 {
01044   if ( $username == 'None' || empty($username))
01045     {
01046       return $username;
01047     }
01048   else
01049     {
01050       $re = '<a href="'.$GLOBALS['sys_home'].'users/'.$username.'">';
01051       if ($realname)
01052         {
01053           $re .= $realname." &lt;".$username."&gt;";
01054         }
01055       else
01056         {
01057           $re .= $username;
01058         }
01059       $re .= '</a>';
01060 
01061       return $re;
01062     }
01063 }
01064 
01065 function utils_double_diff_array($arr1, $arr2)
01066 {
01067   # first transform both arrays in hashes
01068   reset($arr1); reset($arr2);
01069   while ( list(,$v) = each($arr1))
01070     { $h1[$v] = $v; }
01071   while ( list(,$v) = each($arr2))
01072     { $h2[$v] = $v; }
01073 
01074   $deleted = array();
01075   while ( list($k,) = each($h1))
01076     {
01077       if (!isset($h2[$k]))
01078         { $deleted[] = $k; }
01079     }
01080 
01081   $added = array();
01082   while ( list($k,) = each($h2))
01083     {
01084       if (!isset($h1[$k]))
01085         { $added[] = $k; }
01086     }
01087 
01088   return array($deleted, $added);
01089 }
01090 
01091 function utils_registration_history ($unix_group_name)
01092 {
01093   # Meaningless with chrooted system; all www system should be chrooted.
01094 }
01095 
01096 function show_priority_colors_key()
01097 {
01098   print '<p class="smaller">';
01099   print _("Open Items Priority Colors:")."<br />&nbsp;&nbsp;&nbsp;\n";
01100 
01101   for ($i=1; $i<10; $i++)
01102     {
01103       print '<span class="'.utils_get_priority_color($i).'">&nbsp;'.$i.'&nbsp;</span>'."\n";
01104     }
01105 
01106   print '<br />';
01107   print _("Closed Items Priority Colors:")."<br />&nbsp;&nbsp;&nbsp;\n";
01108 
01109   for ($i=11; $i<20; $i++)
01110     {
01111       print '<span class="'.utils_get_priority_color($i).'">&nbsp;'.($i-10).'&nbsp;</span>'."\n";
01112     }
01113 
01114   print  "</p>";
01115 }
01116 
01117 function get_priority_color ($index, $closed="")
01118 { return utils_get_priority_color($index, $closed); }
01119 
01120 
01121 function utils_get_tracker_icon ($tracker)
01122 {
01123   if ($tracker == "bugs")
01124     { return "bug"; }
01125   if ($tracker == "support")
01126     { return "help"; }
01127   if ($tracker == "cookbook")
01128     { return "man"; }
01129   return $tracker;
01130 }
01131 
01132 function utils_get_tracker_prefix ($tracker)
01133 {
01134   if ($tracker == "bugs")
01135     { return "bug"; }
01136   if ($tracker == "support")
01137     { return "sr"; }
01138   if ($tracker == "cookbook")
01139     { return "recipe"; }
01140   return $tracker;
01141 }
01142 
01143 
01144 function utils_get_priority_color ($index, $closed="")
01145 {
01146   global $bgpri;
01147   # If the item is closed, add ten to the index number to get closed colors
01148   if ($closed == 3)
01149     { $index = $index + 10; }
01150 
01151   return $bgpri[$index];
01152 }
01153 
01154 function build_priority_select_box ($name="priority", $checked_val="5")
01155 {
01156   /*
01157                 Return a select box of standard priorities.
01158                 The name of this select box is optional and so is the default checked value
01159   */
01160 
01161   print "<select name=\"$name\">\n";
01162   print '<option value="1"'.($checked_val=="1" ?" selected":"").'>1 - '._("Lowest").'</option>';
01163   print '<option value="2"'.($checked_val=="2" ?" selected":"").'>2</option>';
01164   print '<option value="3"'.($checked_val=="3" ?" selected":"").'>3</option>';
01165   print '<option value="4"'.($checked_val=="4" ?" selected":"").'>4</option>';
01166   print '<option value="5"'.($checked_val=="5" ?" selected":"").'>5 - '._("Medium").'</option>';
01167   print '<option value="6"'.($checked_val=="6" ?" selected":"").'>6</option>';
01168   print '<option value="7"'.($checked_val=="7" ?" selected":"").'>7</option>';
01169   print '<option value="8"'.($checked_val=="8" ?" selected":"").'>8</option>';
01170   print '<option value="9"'.($checked_val=="9" ?" selected":"").'>9 - '._("Highest").'</option>';
01171   print '</select>';
01172 
01173 
01174 }
01175 
01176 # ########################################### checkbox array
01177 # ################# mostly for group languages and environments
01178 
01179 function utils_buildcheckboxarray($options,$name,$checked_array)
01180 {
01181   $option_count=count($options);
01182   $checked_count=count($checked_array);
01183 
01184   for ($i=1; $i<=$option_count; $i++)
01185     {
01186       print '
01187                         <BR><INPUT type="checkbox" name="'.$name.'" value="'.$i.'"';
01188       for ($j=0; $j<$checked_count; $j++)
01189         {
01190           if ($i == $checked_array[$j])
01191             {
01192               print ' CHECKED';
01193             }
01194         }
01195       print '> '.$options[$i];
01196     }
01197 }
01198 
01199 # deprecated name
01200 function GraphResult($result,$title)
01201 {
01202   utils_graph_result($result, $title);
01203 }
01204 
01205 # DEPRECATED:
01206 function utils_graph_result ($result,$title)
01207 {
01208 
01209   /*
01210         GraphResult by Tim Perdue, PHPBuilder.com
01211 
01212         Takes a database result set.
01213         The first column should be the name,
01214         and the second column should be the values
01215 
01216         ####
01217         ####   Be sure to include (HTML_Graphs.php) before hitting these graphing functions
01218         ####
01219   */
01220 
01221   /*
01222                 db_ should be replaced with your database, aka mysql_ or pg_
01223   */
01224   $rows=db_numrows($result);
01225 
01226   if ((!$result) || ($rows < 1))
01227     {
01228       print 'None Found.';
01229     }
01230   else
01231     {
01232       $names=array();
01233       $values=array();
01234 
01235       for ($j=0; $j<db_numrows($result); $j++)
01236         {
01237           if (db_result($result, $j, 0) != '' && db_result($result, $j, 1) != '' )
01238             {
01239               $names[$j]= db_result($result, $j, 0);
01240               $values[$j]= db_result($result, $j, 1);
01241             }
01242         }
01243 
01244       /*
01245                 This is another function detailed below
01246       */
01247       GraphIt($names,$values,$title);
01248     }
01249 }
01250 
01251 
01252 # DEPRECATED: deprecated name
01253 function GraphIt ($name_string,$value_string,$title)
01254 {
01255   utils_graph_it($name_string,$value_string,$title);
01256 }
01257 
01258 
01259 # DEPRECATED:
01260 function utils_graph_it ($name_string,$value_string,$title)
01261 {
01262 
01263   /*
01264                 GraphIt by Tim Perdue, PHPBuilder.com
01265   */
01266   $counter=count($name_string);
01267 
01268   /*
01269                 Can choose any color you wish
01270   */
01271   $bars=array();
01272 
01273   for ($i = 0; $i < $counter; $i++)
01274     {
01275       $bars[$i]=$GLOBALS['COLOR_LTBACK1'];
01276     }
01277 
01278   $counter=count($value_string);
01279 
01280   /*
01281                 Figure the max_value passed in, so scale can be determined
01282   */
01283 
01284   $max_value=0;
01285 
01286   for ($i = 0; $i < $counter; $i++)
01287     {
01288       if ($value_string[$i] > $max_value)
01289         {
01290           $max_value=$value_string[$i];
01291         }
01292     }
01293 
01294   if ($max_value < 1)
01295     {
01296       $max_value=1;
01297     }
01298 
01299   /*
01300                 I want my graphs all to be 800 pixels wide, so that is my divisor
01301   */
01302 
01303   $scale=(400/$max_value);
01304 
01305   /*
01306                 I create a wrapper table around the graph that holds the title
01307   */
01308 
01309   $title_arr=array();
01310   $title_arr[]=$title;
01311 
01312   print html_build_list_table_top ($title_arr);
01313   print '<TR><TD>';
01314   /*
01315                 Create an associate array to pass in. I leave most of it blank
01316   */
01317 
01318   $vals =  array(
01319                  'vlabel'=>'',
01320                  'hlabel'=>'',
01321                  'type'=>'',
01322                  'cellpadding'=>'',
01323                  'cellspacing'=>'0',
01324                  'border'=>'',
01325                  'width'=>'',
01326                  'background'=>'',
01327                  'vfcolor'=>'',
01328                  'hfcolor'=>'',
01329                  'vbgcolor'=>'',
01330                  'hbgcolor'=>'',
01331                  'vfstyle'=>'',
01332                  'hfstyle'=>'',
01333                  'noshowvals'=>'',
01334                  'scale'=>$scale,
01335                  'namebgcolor'=>'',
01336                  'valuebgcolor'=>'',
01337                  'namefcolor'=>'',
01338                  'valuefcolor'=>'',
01339                  'namefstyle'=>'',
01340                  'valuefstyle'=>'',
01341                  'doublefcolor'=>'');
01342 
01343   /*
01344                 This is the actual call to the HTML_Graphs class
01345   */
01346 
01347   html_graph($name_string,$value_string,$bars,$vals);
01348 
01349   print '
01350                 </TD></TR></TABLE>
01351                 <!-- end outer graph table -->';
01352 }
01353 
01354 
01355 
01356 function utils_show_result_set ($result,$title="Untitled",$linkify=false)
01357 {
01358   global $group_id,$HTML;
01359   /*
01360                 Very simple, plain way to show a generic result set
01361                 Accepts a result set and title
01362                 Makes certain items into HTML links
01363   */
01364 
01365   if  ($result)  {
01366     $rows  =  db_numrows($result);
01367     $cols  =  db_numfields($result);
01368 
01369     # Show title
01370     print "<h4>$title</h4>\n";
01371     print '<table border="0" width="100%" summary="'.$title.'">'."\n";
01372 
01373     /*  Create  the  headers  */
01374     print "<tr>\n";
01375     for ($i=0; $i < $cols; $i++)
01376       {
01377         print '<th>'.db_fieldname($result,  $i)."</th>\n";
01378       }
01379     print "</tr>\n";
01380 
01381     /*  Create the rows  */
01382     for ($j = 0; $j < $rows; $j++)
01383       {
01384         print '<tr class="'. html_get_alt_row_color($j) .'">';
01385         for ($i = 0; $i < $cols; $i++)
01386           {
01387             if ($linkify && $i == 0)
01388               {
01389                 $link = '<a href="'.$PHP_SELF.'?';
01390                 $linkend = '</a>';
01391                 if ($linkify == "bug_cat")
01392                   {
01393                     $link .= 'group_id='.$group_id.'&bug_cat_mod=y&bug_cat_id='.db_result($result, $j, 'bug_category_id').'">';
01394                   } else if($linkify == "bug_group")
01395                     {
01396                       $link .= 'group_id='.$group_id.'&bug_group_mod=y&bug_group_id='.db_result($result, $j, 'bug_group_id').'">';
01397                     } else if($linkify == "patch_cat")
01398                       {
01399                         $link .= 'group_id='.$group_id.'&patch_cat_mod=y&patch_cat_id='.db_result($result, $j, 'patch_category_id').'">';
01400                       } else if($linkify == "support_cat")
01401                         {
01402                           $link .= 'group_id='.$group_id.'&support_cat_mod=y&support_cat_id='.db_result($result, $j, 'support_category_id').'">';
01403                         } else if($linkify == "pm_project")
01404                           {
01405                             $link .= 'group_id='.$group_id.'&project_cat_mod=y&project_cat_id='.db_result($result, $j, 'group_project_id').'">';
01406                           }
01407                 else
01408                   {
01409                     $link = $linkend = '';
01410                   }
01411               }
01412             else
01413               {
01414                 $link = $linkend = '';
01415               }
01416             print '<td>'.$link . db_result($result,  $j,  $i) . $linkend.'</td>';
01417 
01418           }
01419         print '</tr>';
01420       }
01421     print "</table>\n";
01422   }
01423   else
01424     {
01425       print db_error();
01426     }
01427 }
01428 
01429 
01430 # Clean up email address (remove spaces...) and put to lower case
01431 function utils_cleanup_emails ($addresses)
01432 {
01433   return strtolower(preg_replace("/\s/","", $addresses));
01434 }
01435 
01436 # Clean up email address (remove spaces...) and add @... if it is a simple
01437 # login name
01438 function utils_normalize_email ($address)
01439 {
01440   $address = utils_cleanup_emails($address);
01441   if (validate_email($address))
01442     return $address;
01443   else
01444     return $address."@".$GLOBALS['sys_default_domain'];
01445 }
01446 
01447 
01448 # Clean up email address (remove spaces...) and split comma separated emails
01449 function utils_split_emails($addresses)
01450 {
01451   $addresses = utils_cleanup_emails($addresses);
01452   $addresses = ereg_replace(";", ",", $addresses);
01453   return split(',',$addresses);
01454 }
01455 
01456 # Email Verification
01457 function validate_email ($address)
01458 {
01459   return (ereg('^[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+'. '@'. '[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.' . '[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$', $address));
01460 }
01461 
01462 # Verification of comma separated list of email addresses
01463 function validate_emails ($addresses)
01464 {
01465   $arr = utils_split_emails($addresses);
01466   while (list(, $addr) = each ($arr))
01467     {
01468       if (!validate_email($addr))
01469         { return false;}
01470     }
01471   return true;
01472 }
01473 
01474 function utils_is_valid_filename ($file)
01475 {
01476   if (ereg("[]~`! ~@#\"$%^,&*();=|[{}<>?/]",$file))
01477     {
01478       return false;
01479     }
01480   else
01481     {
01482       if (strstr($file,'..'))
01483         {
01484           return false;
01485         }
01486       else
01487         {
01488           return true;
01489         }
01490     }
01491 }
01492 
01493 # alias
01494 function util_debug ($msg)
01495 {
01496   dbg($msg);
01497 }
01498 
01499 # alias
01500 function debug ($msg)
01501 {
01502   dbg($msg);
01503 }
01504 
01505 # add debugging information
01506 function dbg ($msg)
01507 {
01508   if ($GLOBALS['sys_debug_on'])
01509     {
01510       $GLOBALS['debug'] .= "<br /><br />latest func called: ".ereg_replace($_SERVER["DOCUMENT_ROOT"].$GLOBALS['sys_home'], "",$GLOBALS['sys_debug_where']);
01511       $GLOBALS['debug'] .= "<br />msg: $msg";
01512     }
01513 }
01514 
01515 
01516 # alias
01517 function util_feedback ($msg, $error=0)
01518 {
01519   fb($msg, $error);
01520 }
01521 
01522 # alias
01523 function feedback ($msg, $error=0)
01524 {
01525   fb($msg, $error);
01526 }
01527 
01528 # add feedback information
01529 function fb ($msg, $error=0, $enablehtml=0)
01530 {
01531   # If ware in enablehtml mode, it means that the feedback may contain real
01532   # html.
01533   # It means that we need to remove previous feedback to avoid malicious usage
01534   # of the feedback form.
01535   # This should be used on rare occasions.
01536   # We will also set feedback_html.
01537   if ($enablehtml)
01538     {
01539       unset($GLOBALS['feedback'], $GLOBALS['ffeedback']);
01540       $GLOBALS['feedback_html'] = 1;
01541     }
01542 
01543   # Increment feedback count
01544   $GLOBALS['feedback_count']++;
01545 
01546   # Remove the dot at the end, if existing
01547   if (substr($msg, -1) == ".")
01548     {
01549       $msg = substr($msg, 0, (strlen($msg)-1));
01550     }
01551 
01552 
01553   # First letter capitalized, that's all
01554   $msg = strtoupper(substr($msg, 0, 1)).strtolower(substr($msg, 1, strlen($msg)));
01555 
01556   if ($GLOBALS['sys_debug_on'])
01557     {
01558       $msg .= ' [#'.$GLOBALS['feedback_count'].']';
01559       dbg("Add feedback #".$GLOBALS['feedback_count']);
01560     }
01561 
01562   $msg .= _(";").' ';
01563 
01564   # feed
01565   if (!$error)
01566     {
01567       $GLOBALS['feedback'] .= $msg;
01568     }
01569   else
01570     {
01571       $GLOBALS['ffeedback'] .= $msg;
01572     }
01573 }
01574 
01575 # fb function to be used about database error when context of error is obvious
01576 function fb_dberror()
01577 {
01578   fb(_("Error updating database"),1);
01579 }
01580 
01581 # fb function to be used about database error when context of error is obvious
01582 function fb_dbsuccess()
01583 {
01584   fb(_("Database successfully updated"));
01585 }
01586 
01587 
01588 # alias
01589 function utils_help ($text, $explanation_array, $noarray=0)
01590 {
01591   return help($text, $explanation_array, $noarray);
01592 }
01593 
01594 # print help about a word
01595 #   $text  is the sentence where ballons are
01596 #   $explanation_array is the table word->explanation, must be in the
01597 #   array syntax.
01598 function help ($text, $explanation_array, $noarray=0)
01599 {
01600   if (!$noarray)
01601     {
01602       while (list($word,$explanation) = each($explanation_array))
01603         {
01604           $text = str_replace($word,
01605                               '<span class="help" title="'.$explanation.'">'.$word.'</span>',
01606                               $text);
01607         }
01608       return $text;
01609     }
01610 
01611 
01612   return '<span class="help"