<?PHP

/**
 * @file StdLib.php
 * Standard library functions for CWIS.
 */

/**
 * Determine if the user is logged in.
 * @return bool TRUE if the user is logged in and FALSE otherwise
 */
function UserIsLoggedIn()
{
    return $GLOBALS["G_User"]->IsLoggedIn();
}

/**
 * Determine if a local function exists as defined in an SPT--Local.php file.
 * @param string $FunctionBaseName
 * @return bool TRUE if the local function exists or FALSE otherwise
 */
function LocalFunctionExists($FunctionBaseName)
{
    # try to load SPT--Local.php from its current location
    if (file_exists("local/misc/SPT--Local.php"))
    {
        require_once "local/misc/SPT--Local.php";
    }

    # try to load SPT--Local.php from its old location
    else if (file_exists("include/SPT--Local.php"))
    {
        require_once "include/SPT--Local.php";
    }

    return function_exists("Local_".$FunctionBaseName);
}

/**
 * Get the name of the directory of the current theme.
 * @return string the name of the directory of the current theme
 */
function GetCurrentThemeDir()
{
    # if accessibility wizard is enabled and user has accessibility
    # attribute selected that requires accessible theme
    if ($GLOBALS["SysConfig"]->AccessibilityWizardEnabled()
        && (($GLOBALS["User"]->PrefFontSize() > 0)
            || (strlen($GLOBALS["User"]->PrefFontTypeFace()))
            || (strlen($GLOBALS["User"]->PrefFontColor()))
            || (strlen($GLOBALS["User"]->PrefBackgroundColor()))
            || ($GLOBALS["User"]->PrefColorAvoidanceFlags() > 0)))
    {
        # return accessible theme
        return "Theme--Accessible";
    }

    else
    {
        # return configured theme
        return $GLOBALS["SysConfig"]->CurrentTheme();
    }
}

/**
 * Check whether the user is authorized to view the current page. If the current
 * user does not have at least one of the specified privileges, then a hook is
 * set to cause the "Unauthorized Access" HTML file to display instead of the
 * normal HTML file.
 * @param
 * @param AuthFlag Privilege required (or array of possible privileges).
 * @return TRUE if user has one of the specified privileges, otherwise FALSE.
 * @see CheckAuthorization_SignalHook()
*/
function CheckAuthorization($AuthFlag = NULL)
{
    $Privileges = is_array($AuthFlag) ? $AuthFlag : func_get_args();

    # if the user is logged in and no privileges were given or the user has at
    # least one of the specified privileges
    if ($GLOBALS["G_User"]->IsLoggedIn()
        && ((is_null($AuthFlag)) || $GLOBALS["G_User"]->HasPriv($Privileges)))
    {
        return TRUE;
    }

    # the user is not logged in or doesn't have at least one of the specified
    # privileges
    else
    {
        # display the "Unauthorized Access" page
        $GLOBALS["AF"]->HookEvent(
            "EVENT_HTML_FILE_LOAD",
            "CheckAuthorization_SignalHook");

        return FALSE;
    }
}

/**
 * Hook used to display the "Unauthorized Access" page in conjunction with the
 * CheckAuthorization() function.
 * @param string $PageName page name
 * @return array modified hook parameters
 * @see CheckAuthorization()
 */
function CheckAuthorization_SignalHook($PageName)
{
    return array("PageName" => "UnauthorizedAccess");
}

/**
 * Print all error messages within a <ul>, if any.
 * @return void
 */
function PrintErrorMessageList()
{
    # make sure error messages are set before attempting to access them
    if (isset($GLOBALS["ErrorMessages"]))
    {
        $Errors = $GLOBALS["ErrorMessages"];

        # if dealing with an error list and it has error messages
        if ($Errors instanceof ErrorList && $Errors->HasErrors())
        {
            print $Errors->GetMessagesAsUList();
        }
    }
}

/**
 * Get or set the title of page as displayed in the title bar of a user's
 * web browser.
 * @param string|null $NewTitle the new page title or NULL to leave it as-is
 * @param bool $IncludePortalName TRUE to automatically prepend the portal name
 * @return string the new page title, including the portal name if applicable
 */
function PageTitle($NewTitle = NULL, $IncludePortalName = TRUE)
{
    static $Title;

    # save a new page title if given one
    if (!is_null($NewTitle))
    {
        $Title = $NewTitle;
    }

    # the portal name should be prepended before returning the title...
    if ($IncludePortalName && strlen($GLOBALS["SysConfig"]->PortalName()))
    {
        return $GLOBALS["SysConfig"]->PortalName() . " - " . $Title;
    }

    # ... otherwise just return the page title
    return $Title;
}

/**
 * Get the path to the interface directory that contains the fast user rating
 * images/icons, if any.
 * @return string the path to the interface containing the rating stars
 */
function GetFastRatingInterfaceDirectory()
{
    if (preg_match('/(.*)\/images\/BigStars--0_0\.gif$/',
        $GLOBALS["AF"]->GUIFile("BigStars--0_0.gif"), $Matches))
    {
        return $Matches[1];
    }

    return "";
}

/**
 * Get a string of options for the possible user interfaces.
 * @param string $Selected the user interface that's currently selected
 * @param string options (HTML) for the possible user interfaces
 */
function GetActiveUIOptionList($Selected)
{
    # possible UI directories
    $InterfaceDirs = array(
            "interface",
            "local/interface",
            );

    # start out with empty list
    $Text = "";

    # for each possible UI directory
    foreach ($InterfaceDirs as $InterfaceDir)
    {
        # for each file in current directory
        $Dir = dir($InterfaceDir);
        while (($DirEntry = $Dir->read()) !== FALSE)
        {
            # if file is UI directory
            if ($DirEntry{0} != "." && is_dir($InterfaceDir."/".$DirEntry)
                && preg_match("/[a-zA-Z]+/", $DirEntry))
            {
                # read UI name (if available)
                $UIName = "";
                $UINameFile = $InterfaceDir."/".$DirEntry."/NAME";
                if (is_readable($UINameFile))
                {
                    $FHandle = fopen($UINameFile, "r");
                    $UIName = fgets($FHandle, 64);
                    fclose($FHandle);
                }
                if (strlen($UIName) < 1) {  $UIName = $DirEntry;  }

                # print option tag for UI
                if ($DirEntry == $Selected) {  $Select = " selected";  } else {  $Select = "";  }
                $Text .= "<option value=\"${DirEntry}\"${Select}>${UIName}</option>\n";
            }
        }
    }

    # return list to caller
    return $Text;
}

/**
 * Determine whether the current page is part of the metadata tool.
 * @param bool TRUE if the page is part of the metadata tool or FALSE otherwise
 */
function InMetadataTool()
{
    if ((strstr($_SERVER["SCRIPT_NAME"], "/MetadataTool/")
                    && (strstr($_SERVER["SCRIPT_NAME"], "SPT--ImportData") == FALSE)
                    && (strstr($_SERVER["SCRIPT_NAME"], "SPT--EditControlledNameTypes") == FALSE))
            || (strstr($_SERVER["SCRIPT_NAME"],
                    "SPT--BrowseResources.php") && $GLOBALS["Editing"])
            || (strstr($_SERVER["SCRIPT_NAME"],
                    "SPT--Advanced.php") && $GLOBALS["Editing"])
            || (strstr($_SERVER["SCRIPT_NAME"],
                    "SPT--AdvancedSearch.php") && $GLOBALS["Editing"]))
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

/**
 * Add a closing tag to the given text if at least one is missing.
 * @param string $Tag the HTML tag to check for
 * @param string $Text the HTML to inspect
 * @return string the text with the tag closed if it was necessary
 */
function CloseTags($Tag, $Text)
{
    # if there are a greater number of opening tags than closing tags
    if (preg_match_all("/<".$Tag.">/i", $Text, $Dummy) > preg_match_all("/<\\/".$Tag.">/i", $Text, $Dummy))
    {
        # add closing tag to text
        $Text .= "</".$Tag.">";
    }

    # return (possibly) revised text to caller
    return $Text;
}

/**
 * Translate special characters within the value to HTML entities.
 * @param string $Value value to translate
 * @return string the value with special characters translated to HTML entities
 */
function GetHtmlTranslatedString($Value)
{
    return defaulthtmlspecialchars(stripslashes($Value));
}

/**
 * Debugging output utility function. This should not be used in production
 * code.
 * @param string $VarName the variable name
 * @param mixed $VarValue the value of the variable
 * @see var_dump()
 */
function PrintForDebug($VarName, $VarValue)
{
    # print the variable name
    print "<pre>" . $VarName . ": ";

    # print a newline if not printing a scalar value
    if (!is_scalar($VarValue))
    {
        print "\n";
    }

    # use PHP's built-in variable dumping function if available
    if (function_exists("var_dump"))
    {
        var_dump($VarValue);
    }

    # otherwise use the next best thing
    else
    {
        print_r($VarValue);
    }

    # print the closing tag
    print "</pre>\n";
}

/**
 * Return the HTTP protocol that is currently in use.
 * @return string the HTTP protocol currently in use ("http:" or "https:")
 */
function OurHttpProtocol()
{
    return isset($_SERVER["HTTPS"]) ? "https:" : "http:";
}

/**
 * Get the URL to the current file being executed.
 * @return string the URL to the current file being executed
 */
function OurUrl()
{
    $Protocol = OurHttpProtocol() . "//";
    $Url = $Protocol.(($_SERVER["SERVER_NAME"] != "127.0.0.1")
                ? $_SERVER["SERVER_NAME"]
                : $_SERVER["HTTP_HOST"])
            .$_SERVER["SCRIPT_NAME"];
    return $Url;
}

/**
 * Get the URL to the root of the site with a trailing slash.
 * @return string the URL to the root of the site with a trailing slash
 */
function OurBaseUrl()
{
    $Protocol = OurHttpProtocol() . "//";
    $BaseUrl = $Protocol.(($_SERVER["SERVER_NAME"] != "127.0.0.1")
                ? $_SERVER["SERVER_NAME"]
                : $_SERVER["HTTP_HOST"])
            .dirname($_SERVER["SCRIPT_NAME"]);
    if (substr($BaseUrl, -1) != "/")
    {
        $BaseUrl .= "/";
    }
    return $BaseUrl;
}

/**
 * Get the path part of the URL to the root of the site with a trailing slash.
 * @return string the path part of the URL to the root of the site
 */
function OurBasePath()
{
    $BasePath = dirname($_SERVER["SCRIPT_NAME"]);

    if (substr($BasePath, -1) != "/")
    {
        $BasePath .= "/";
    }

    return $BasePath;
}

/**
 * Determine whether the given URL is safe to redirect to, i.e., if a protocol
 * and host are specified, make sure the host is the same as the server's host.
 * This is meant to protect from malicious redirects
 * @param string $Url URL to check
 * @return bool TRUE if the URL is safe and FALSE otherwise
 */
function IsSafeRedirectUrl($Url)
{
    $ParsedUrl = parse_url($Url);
    $Protocol = GetArrayValue($ParsedUrl, "scheme");
    $Host = GetArrayValue($ParsedUrl, "host");

    # if a protocol and host are specified, make sure the host is equal to the
    # server's host to protect from malicious redirects
    return !$Protocol || $Host == $_SERVER["SERVER_NAME"];
}

/**
 * Get the correct length of a UTF-8 string that contains multi-byte characters.
 * @param string $string UTF-8 string
 * @return int the length of the string
 */
function utf8_strlen($string)
{
    return strlen(utf8_decode($string));
}

/**
 * Get a portion of a UTF-8 string that contains multi-byte characters.
 * @param string string UTF-8 string
 * @param int $start the position at which to start extracting the segment
 * @param int|null $length the maximum length of the extracted segment
 * @return string the portion of the string
 */
function utf8_substr($string, $start, $length=NULL)
{
    $Parameters = array(utf8_decode($string), $start);

    if (!is_null($length))
    {
        $Parameters[] = $length;
    }

    return utf8_encode(call_user_func_array("substr", $Parameters));
}

/**
 * Attempt to truncate a string as neatly as possible with respect to word
 * breaks, punctuation, and HTML tags.
 * @param string $String string to truncate
 * @param int $MaxLength the maximum length of the truncated string
 * @param bool $BreakAnywhere TRUE to break exactly at the maximum length
 * @return the (possibly) truncated string
 */
function NeatlyTruncateString($String, $MaxLength, $BreakAnywhere=FALSE)
{
    $strlen = (strcasecmp($GLOBALS["SysConfig"]->DefaultCharacterSet(), "UTF-8") === 0)
        ? "utf8_strlen" : "strlen";
    $substr = (strcasecmp($GLOBALS["SysConfig"]->DefaultCharacterSet(), "UTF-8") === 0)
        ? "utf8_substr" : "substr";

    # If the string is too long
    if ($MaxLength < $strlen($String))
    {
        # We are going to iterate through the string one char at a time
        # As we go, we will :
        #    * count printing characters (eg, the ones not inside HTML tags)
        #    * mark location of the last space or punct character where
        #        the string could be neatly broken
        #    * maintain a stack of opened tags
        #    * pop tags off of this stack when they close

        $CurLen=0;            # Number of printing characters seen so far
        $Cur=0;               # Current position in the string
        $LastBreakpoint=NULL; # Most recent seen breaking place
        $TagStack = array();  # Stack of open tags
        $InTag = FALSE;
        $BeginsWithUrl = substr(strip_tags($String), 0, 7) == "http://";
        $BeginsWithUrl = $BeginsWithUrl || substr(strip_tags($String), 0, 8) == "https://";

        while ($CurLen < $MaxLength && $Cur < $strlen($String))
        {
            if ($InTag)
            {
                # If we are in a tag, just look for the
                # end of the tag, eating characters as we go
                if (strstr(">", $String[$Cur] ) !== FALSE)
                {
                    $InTag = FALSE;
                }
                $Cur++;
                $LastBreakpoint=$Cur;
            }
            else
            {
                # If we were not in a tag, but one starts here:
                if ($String[$Cur]=="<")
                {
                    $InTag = TRUE; # Toggle the flag
                    $ThisTag = "";

                    # Eat the start char and leading spaces
                    while (strstr("< ", $String[$Cur]) !== FALSE)
                    {
                        $Cur++;
                    }
                    # Eat the tag up to the first space or tag-end
                    while (strstr(" >",$String[$Cur] ) === FALSE )
                    {
                        $ThisTag .= $String[$Cur];
                        $Cur++;
                    }

                    $ThisTag = strtolower($ThisTag);

                    if ( $ThisTag[0] == "/" )
                    {
                        # If it was an end-tag, find the last
                        # matching start tag on the tag stack and
                        # pop it off
                        $ThisTag = $substr($ThisTag,1,$strlen($ThisTag));
                        if (count($TagStack)>0)
                        {
                            unset($TagStack[max(array_keys($TagStack,$ThisTag))]);
                        }
                    }
                    else
                    {
                        # Otherwise, it is a start tag for an element
                        # which has an end tag, push it to the stack
                        if($substr( $ThisTag, 0, 2 ) != "br" &&
                           $substr( $ThisTag, 0, 2 ) != "hr" )
                        {
                            array_push($TagStack, $ThisTag);
                        }
                    }
                }
                else
                {
                    # Otherwise, we are just out in printed text land
                    # Look for point where we can break the string
                    # And store the last occurrence of one
                    if (strstr(",;: ",$String[$Cur]) !== FALSE ||
                        $BreakAnywhere || $BeginsWithUrl)
                    {
                        $LastBreakpoint = $Cur;
                    }
                    $CurLen++;
                    $Cur++;
                }
            }
        }

        # We have now parsed the whole string and need to decide
        # how we are going to chop it

        if ($LastBreakpoint === NULL)
        {
            # If there were no breakpoints,
            # just chop the string at maxlength
            $String = $substr($String, 0, $MaxLength);
        }
        else
        {
            # Otherwise, chop it at the last breakpoint
            $String = $substr($String, 0, $LastBreakpoint);

            # Close all the open tags
            while (count($TagStack)>0)
            {
                $String .= "</".array_pop($TagStack).">";
            }

            # And tack on the ellipsis
            $String .= "...";
        }
    }

    return $String;
}

/**
 * Get the value from an array with the given index or a default value if it
 * does not exist.
 * @param array $Array array
 * @param mixed $Key index of the value to retrieve
 * @param mixed $Default value to return if the key does not exist
 * @return the value at the given index or the default value if it doesn't exist
 */
function GetArrayValue(array $Array, $Key, $Default=NULL)
{
    return array_key_exists($Key, $Array) ? $Array[$Key] : $Default;
}

/**
 * Extract all the values with the given keys from the given array, setting the
 * values to a default one if they don't exist and a default is given or NULL if
 * one is not given.
 * @param array $Array array to extract values from
 * @param array $Keys array of keys of the values to extract
 * @param array $Defaults array of default values used if a value doesn't exist
 * @return array extracted values
 */
function GetArrayValues(array $Array, array $Keys, array $Defaults=array())
{
    $Values = array();

    foreach ($Keys as $Key)
    {
        $Default = GetArrayValue($Defaults, $Key);
        $Values[$Key] = GetArrayValue($Array, $Key, $Default);
    }

    return $Values;
}

/**
 * Retrieve the host name for the given IP address. Returns the IP address if
 * no host name can be found.
 * @param string $IpAddress IP address as a string
 * @return the host name for the IP address or the address if one doesn't exist
 */
function GetHostNameForAddr($IpAddress)
{
    static $HostNames;
    if (!isset($HostNames[$IpAddress]))
    {
        $HostNames[$IpAddress] = preg_match("/\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/",
                $IpAddress) ? gethostbyaddr($IpAddress)
                : (($IpAddress == "::1") ? "localhost" : $IpAddress);
    }
    return $HostNames[$IpAddress];
}

/**
 * Strips a string of any tags and attributes that are not provided as
 * exceptions. Stripping of tags or attributes can be disabled by options.
 * Uses the \f (form feed) character as a token, so this will fail in the
 * unlikely event that someone manages to get a \f character into the input
 * string to this function.
 *
 * Options are:
 *     "StripTags" => set to FALSE to disable tag stripping
 *     "StripAttributes" => set to FALSE to disable attribute stripping
 *     "Tags" => string of allowed tags, whitespace delimited (e.g., "a b i")
 *     "Attributes" => string of allowed attributes, whitespace delimited (e.g., "href target")
 *
 * @param string $String string to parse
 * @param array $Options (see above)
 * @return string the parsed string
 * @see StripXSSThreats()
 * @see StripUnsafeProtocols()
 */
function StripTagsAttributes($String, $Options=array())
{
    # make sure have values for the predicate options
    if (!is_array($Options)) {  $Options = array();  }

    $Options["StripTags"] = GetArrayValue($Options, "StripTags", TRUE);
    $Options["StripAttributes"] = GetArrayValue($Options, "StripAttributes", TRUE);

    # phase 1: strip invalid tags if necessary
    if ($Options["StripTags"])
    {
        $Tags = trim(GetArrayValue($Options, "Tags"));

        # escape allowed tags if any were given
        if (strlen($Tags))
        {
            # strip invalid characters and ready the names for the subpattern
            $Tags = preg_replace('/[^a-z0-9 ]/i', '', $Options["Tags"]);
            $Tags = preg_replace('/\s+/', "|", $Tags);

            # and finally escape the tags
            $String = preg_replace(
                  '/<('.$Tags.')(\s[^>]*)?(\/?)>|<(\/)('.$Tags.')(\s[^>]*)?>/i',
                  sprintf('%c$1$2$3$4$5$6%c', 12, 12),
                  $String);
        }

        # remove all other tags and then unescape allowed tags
        $String = preg_replace('/<[^>]*>/', '', $String);
        $String = preg_replace(sprintf('/%c([^%c]*)%c/', 12, 12, 12),
                '<$1>', $String);
    }

    # phase 2: strip attributes if necessary
    if ($Options["StripAttributes"])
    {
        $Attributes = trim(GetArrayValue($Options, "Attributes"));

        # move all of the attributes into separate contexts for validation
        $String = preg_replace('/<([a-z0-9]+)\s+([^>]*[^>\/])(\/)?>/i',
            sprintf('<$1$3>%c$2%c', 12, 12), $String);

        if (strlen($Attributes))
        {
            # remove bad chars and split by whitespace
            $Attributes = preg_replace('/[^a-zA-z0-9 ]/i', '', $Options["Attributes"]);
            $Attributes = preg_split('/\s+/', $Attributes);

            # extract each allowed attribute from its context
            for ($i = 0; $i < count($Attributes) && strlen($Attributes[$i]); $i++)
            {
                $String = preg_replace(sprintf('/<([A-Za-z0-9]+)(\s[^>]*[^>\/])?(\/)?>'.
                        '%c([^%c]*\s*)'.$Attributes[$i].'=("[^"]*"|\'[^\']*\')'.
                        '([^%c]*\s*)%c/i', 12, 12, 12, 12),
                        sprintf("<$1$2 ".$Attributes[$i]."=$5$3>%c$4$6%c", 12, 12),
                        $String);
            }
        }

        # destroy all the contexts created, deleting any attributes that aren't
        # allowed, and make well-formed singleton tags
        $String = preg_replace(
            array(sprintf('/%c[^%c]*%c/', 12, 12, 12), '/<([^>]+)\/>/'),
            array('', '<$1 />'),
            $String);
    }

    # return the string
    return $String;
}

/**
 * Strip potentially unsafe tags, attributes, and protocols from the given
 * string to remove any XSS threats.
 * @param string $String the string to strip
 * @return string the string stripped of potentially unsafe text
 * @see StripTagsAttributes()
 * @see StripUnsafeProtocols()
 */
function StripXSSThreats($String)
{
    $Options["Tags"] = "
        a abbr b blockquote br caption cite code dd dl dt del div em h1 h2 h3 h4
        h5 h6 hr i ins kbd ol ul li mark p pre q s samp small span strike strong
        sub sup table tbody td tfoot th thead time tr u var";
    $Options["Attributes"] = "href id name summary";
    return StripUnsafeProtocols(StripTagsAttributes($String, $Options));
}

/**
 * Strip any attributes with potentially unsafe protocols and data in the given
 * HTML. This does not strip out unsafe data in attributes where potentially
 * unsafe data is expected, e.g., the onclick and onhover attributes. Those
 * attributes should be removed entirely if their data cannot be trusted.
 * @param string $Html HTML to strip
 * @return string stripped HTML
 * @see StripTagsAttributes()
 */
function StripUnsafeProtocols($Html)
{
    # the attributes that potentially allow unsafe protocols, depending on the
    # user agent
    $CheckedAttributes = array(
        "action", "cite", "data", "for", "formaction", "formtarget", "href",
        "poster", "src", "srcdoc", "target");

    # the protocols that are considered unsafe
    $UnsafeProtocols = join("|", array("data", "javascript", "vbscript"));

    # remove unsafe protocols from each checked attribute
    foreach ($CheckedAttributes as $CheckedAttribute)
    {
        $Html = preg_replace(
            '/<([a-z0-9]+)\s+([^>]*)'.$CheckedAttribute
                .'\s*=\s*(["\'])\s*('.$UnsafeProtocols
                .')\s*:.*?[^\\\]\3\s?(.*?)>/i',
            '<\1 \2\5>',
            $Html);
    }

    # return the stripped HTML
    return $Html;
}

/**
 * Version-agnostic htmlentities() function.
 * See: http://www.php.net/manual/en/function.htmlentities.php
 * @param string $string the input string
 * @param int $quote_style defines what is done with quotes
 * @param strin $charset defines the character set used in conversion
 * @param bool $double_encode when off, PHP won't encode existing HTML entities (>= 5.2.3)
 * @return string the encoded string
 * @see htmlentities()
 * @see htmlspecialchars()
 * @see safehtmlspecialchars()
 * @see defaulthtmlentities()
 * @see defaulthtmlspecialchars()
 */
function safehtmlentities($string, $quote_style=ENT_COMPAT, $charset="ISO-8859-1", $double_encode=TRUE)
{
    # ignore $double_encode param if php version < 5.2.3
    if (version_compare(phpversion(), "5.2.3", "<"))
    {
        return htmlentities($string, $quote_style, $charset);
    }

    return htmlentities($string, $quote_style, $charset, $double_encode);
}

/**
 * Uses the default character set from the system configuration along with the
 * version-agnostic HTML special characters translation function to escape HTML
 * special characters in the given string in the same manner as htmlentities().
 * @param string $String string to translate
 * @return string the translated string
 * @see htmlentities()
 * @see htmlspecialchars()
 * @see safehtmlentities()
 * @see safehtmlspecialchars()
 * @see defaulthtmlspecialchars()
 */
function defaulthtmlentities($String)
{
    $CharacterSet = $GLOBALS["SysConfig"]->DefaultCharacterSet();

    return safehtmlentities($String, ENT_QUOTES, $CharacterSet, FALSE);
}

/**
 * Version-agnostic htmlspecialchars() function.
 * See: http://www.php.net/manual/en/function.htmlspecialchars.php
 * @param string $string the input string
 * @param int $quote_style defines what is done with quotes
 * @param string $charset defines the character set used in conversion
 * @param bool $double_encode when off, PHP won't encode existing HTML entities (>= 5.2.3)
 * @return string the encoded string
 * @see htmlspecialchars()
 * @see htmlentities()
 * @see safehtmlentities()
 * @see defaulthtmlspecialchars()
 * @see defaulthtmlentities()
 */
function safehtmlspecialchars($string, $quote_style=ENT_COMPAT, $charset="ISO-8859-1", $double_encode=TRUE)
{
    # ignore $double_encode param if php version < 5.2.3
    if (version_compare(phpversion(), "5.2.3", "<"))
    {
        return htmlspecialchars($string, $quote_style, $charset);
    }

    return htmlspecialchars($string, $quote_style, $charset, $double_encode);
}

/**
 * Uses the default character set from the system configuration along with the
 * version-agnostic HTML special characters translation function to escape HTML
 * special characters in the given string in the same manner as
 * htmlspecialchars().
 * @param string $String string to translate
 * @return string the translated string
 * @see htmlspecialchars()
 * @see htmlentities()
 * @see safehtmlspecialchars()
 * @see safehtmlentities()
 * @see defaulthtmlentities()
 */
function defaulthtmlspecialchars($String)
{
    $CharacterSet = $GLOBALS["SysConfig"]->DefaultCharacterSet();

    return safehtmlspecialchars($String, ENT_QUOTES, $CharacterSet, FALSE);
}

/**
 * Retreive PHP configuration settings via a call to phpinfo().
 * @return array an array of PHP configuration settings
 * @see phpinfo()
 */
function GetPhpInfo()
{
    # grab PHP info page
    ob_start();
    phpinfo();
    $InfoPage = ob_get_contents();
    ob_end_clean();

    # start by assuming that no info is available
    $Info = array();

    # for each section on page
    $PageChunks = explode("<h2", $InfoPage);
    foreach ($PageChunks as $PageChunk)
    {
        # look for module/section name
        preg_match("/<a name=\"module_([^<>]*)\">/", $PageChunk, $Piece);

        # if we found module/section
        if (count($Piece) > 1)
        {
            # save module/section name
            $ModuleName = trim($Piece[1]);
        }
        else
        {
            # assume no module/section name
            $ModuleName = "";
        }

        # pull out info values from HTML tables
        preg_match_all("/<tr[^>]*><td[^>]*>(.*)<\/td><td[^>]*>(.*)<\/td>/Ux",
                       $PageChunk, $LocalValue);
        preg_match_all("/<tr[^>]*><td[^>]*>(.*)<\/td><td[^>]*>(.*)<\/td><td[^>]*>(.*)<\/td>/Ux",
                       $PageChunk, $MasterValue);

        # store "local" info values
        foreach ($LocalValue[0] as $MatchString => $Dummy)
        {
            $MatchString = trim($MatchString);
            $Info[$ModuleName][trim(strip_tags($LocalValue[1][$MatchString]))] =
                    array(trim(strip_tags($LocalValue[2][$MatchString])));
        }

        # store "master" info values
        foreach ($MasterValue[0] as $MatchString => $Dummy)
        {
            $MatchString = trim($MatchString);
            $Info[$ModuleName][trim(strip_tags($MasterValue[1][$MatchString]))] =
                    array(trim(strip_tags($MasterValue[2][$MatchString])), trim(strip_tags($MasterValue[3][$MatchString])));
        }
    }

    # return info to caller
    return $Info;
}

/**
 * Munges an email address to try to fool web-scraping robots. Inserts two
 * strings of random characters into the email address, wrapped in spans which
 * will hide them from users whose browsers properly implement CSS.  Also wraps
 * the whole thing in a span.EMungeAddr so that some javascript in
 * SPT--EmailMunge.js can convert these fuzzed addresses back into clickable
 * mailtos.
 * @param string $String an email address to obfuscate
 * @return string the obfuscated email address
 */
function MungeEmailAddress($String)
{
    $FuzzOne = substr(md5(mt_rand()), 0, rand(8,32));
    $FuzzTwo = substr(md5(mt_rand()), 0, rand(8,32));
    return '<span class="EMungeAddr">'.preg_replace(
        '/@/',
        '<span style="display:none;"> '.htmlentities($FuzzOne).' </span>'
        .'&#64;'
        .'<span style="display:none;"> '.htmlentities($FuzzTwo).' </span>',
        $String).'</span>';
}

/**
 * Converts a simpleXML element into an array. Preserves attributes and
 * everything. You can choose to get your elements either flattened, or stored
 * in a custom index that you define.
 * For example, for a given element
 * <field name="someName" type="someType"/>
 * if you choose to flatten attributes, you would get:
 * $array['field']['name'] = 'someName';
 * $array['field']['type'] = 'someType';
 * If you choose not to flatten, you get:
 * $array['field']['@attributes']['name'] = 'someName';
 * _____________________________________
 * Repeating fields are stored in indexed arrays. so for a markup such as:
 * <parent>
 * <child>a</child>
 * <child>b</child>
 * <child>c</child>
 * </parent>
 * you array would be:
 * $array['parent']['child'][0] = 'a';
 * $array['parent']['child'][1] = 'b';
 * ...And so on.
 * _____________________________________
 * @param SimpleXMLElement $Xml XML to convert
 * @param boolean $FlattenValues Whether to flatten values
 *       or to set them under a particular index.  Defaults to TRUE;
 * @param boolean $FlattenAttributes Whether to flatten attributes
 *       or to set them under a particular index. Defaults to TRUE;
 * @param boolean $FlattenChildren Whether to flatten children
 *       or to set them under a particular index. Defaults to TRUE;
 * @param string $ValueKey Index for values, in case $FlattenValues was
 *       set to FALSE. Defaults to "@value"
 * @param string $AttributesKey Index for attributes, in case
 *       $FlattenAttributes was set to FALSE. Defaults to "@attributes"
 * @param string $ChildrenKey Index for children, in case $FlattenChildren
 *       was set to FALSE. Defaults to "@children"
 * @return array The resulting array.
 */
function SimpleXMLToArray($Xml,
        $FlattenValues = TRUE,
        $FlattenAttributes = TRUE,
        $FlattenChildren = TRUE,
        $ValueKey = "@values",
        $AttributesKey = "@attributes",
        $ChildrenKey = "@children")
{
    $Array = array();
    $XMLClassName = "SimpleXMLElement";
    if (!($Xml instanceof $XMLClassName)) {  return $Array;  }

    $Name = $Xml->getName();
    $Value = trim((string)$Xml);
    if (!strlen($Value)) {  $Value = NULL;  }

    if ($Value !== NULL)
    {
        if ($FlattenValues) {  $Array = $Value;  }
        else {  $Array[$ValueKey] = $Value;  }
    }

    $Children = array();
    foreach ($Xml->children() as $ElementName => $Child)
    {
        $Value = SimpleXMLToArray($Child, $FlattenValues, $FlattenAttributes,
                $FlattenChildren, $ValueKey, $AttributesKey, $ChildrenKey);

        if (isset($Children[$ElementName]))
        {
            if (!isset($MultipleMembers[$ElementName]))
            {
                $Temp = $Children[$ElementName];
                unset($Children[$ElementName]);
                $Children[$ElementName][] = $Temp;
                $MultipleMembers[$ElementName] = TRUE;
            }
            $Children[$ElementName][] = $Value;
        }
        else
        {
            $Children[$ElementName] = $Value;
        }
    }
    if (count($Children))
    {
        if ($FlattenChildren) {  $Array = array_merge($Array, $Children);  }
        else {  $Array[$ChildrenKey] = $Children;  }
    }

    $Attribs = array();
    foreach ($Xml->attributes() as $Name => $Value)
    {
        $Attribs[$Name] = trim($Value);
    }
    if (count($Attribs))
    {
        if (!$FlattenAttributes) {  $Array[$AttributesKey] = $Attribs;  }
        else {  $Array = array_merge($Array, $Attribs);  }
    }

    return $Array;
}

/**
 * Checks an SQL statement for potentially destructive keywords.
 * @param string $SqlStatement an SQL statement
 * @return bool FALSE for safe-looking statements and TRUE otherwise
 */
function ContainsDangerousSQL($SqlStatement)
{
    $EvilKeywords = array(
        "ALTER","RENAME","TRUNCATE","CREATE","START","COMMIT",
        "ROLLBACK", "SET", "BACKUP", "OPTIMIZE", "REPAIR", "RESTORE",
        "SET", "GRANT", "USE", "UPDATE", "INSERT", "DELETE", "DROP");

    return preg_match("/(^|\() *(".implode("|",$EvilKeywords).") /i",
                           $SqlStatement) === 1;
}

/**
 * Retrieve a form value from $_POST or $_GET, if available, otherwise returns a
 * default value
 * @param mixed $Key key into $_POST or $_GET to check for value
 * @param mixed $Default value to return if nothing is found in $_POST or $_GET
 *       (OPTIONAL, defaults to NULL)
 * @return mixed the form value or default if nothing is found
 * @see GetArrayValue()
 */
function GetFormValue($Key, $Default = NULL)
{
    return GetArrayValue($_POST, $Key, GetArrayValue($_GET, $Key, $Default));
}

/**
 * Remove the file or directory with the given path. Recursively removes
 * directories.
 * @param string $Path path of the file or directory to remove
 * @return bool TRUE if successful or FALSE otherwise
 */
function RemoveFromFilesystem($Path)
{
    # the path is a directory
    if (is_dir($Path))
    {
        # recursively delete directories
        foreach (glob($Path."/*") as $Item)
        {
            if (!RemoveFromFilesystem($Item))
            {
                return FALSE;
            }
        }

        # and then remove this directory
        return @rmdir($Path);
    }

    # the path is a file
    else
    {
        return @unlink($Path);
    }
}

/**
 * Converts a timestamp into a user-friendly format.
 * @param mixed $Timestamp Date/time value to print, in any format parseable by strtotime()..
 * @param bool $Verbose Whether to be verbose about date.  (OPTIONAL, defaults to FALSE)
 * @param string $BadTimeString String to display if date/time appears invalid.  (OPTIONAL,
 *       defaults to "-")
 * @param bool $IncludeOldTimes Whether to include time when date is more than
 *       a week in the past.  (OPTIONAL, defaults to TRUE)
 * @return string a string containing a nicely-formatted timestamp value
 */
function GetPrettyTimestamp($Timestamp, $Verbose = FALSE,
        $BadTimeString = "-", $IncludeOldTimes = TRUE)
{
    # convert timestamp to seconds
    $TStamp = strtotime($Timestamp);

    # if time was invalid
    if (($TStamp === FALSE) || ($TStamp < 0))
    {
        $Pretty = $BadTimeString;
    }
    # else if timestamp was today use format "1:23pm"
    elseif (date("z Y", $TStamp) == date("z Y"))
    {
        $Pretty = date("g:ia", $TStamp);
    }
    # else if timestamp was yesterday use format "Yesterday 1:23pm"
    elseif (date("n/j/Y", ($TStamp - (24 * 60 * 60))) == date("n/j/Y"))
    {
        $Pretty = "Yesterday "
                .($Verbose ? "at " : "")
                .date("g:ia", $TStamp);
    }
    # else if timestamp was this week use format "Monday 1:23pm"
    # (adjust timestamp by a day because "W" begins the week on monday)
    elseif (date("W/o/Y", ($TStamp + (24*60*60))) == date("W/o/Y"))
    {
        $Pretty = $Verbose ? date('l \a\t g:ia', $TStamp)
                : date("D g:ia", $TStamp);
    }
    # else if timestamp was this year use format "1/31 1:23pm"
    elseif (date("Y", $TStamp) == date("Y"))
    {
        $Pretty = date(($Verbose
                ? ($IncludeOldTimes ? 'F jS \a\t g:ia' : 'F jS')
                : ($IncludeOldTimes ? "n/j g:ia" : "n/j")
                ), $TStamp);
    }
    # else use format "1/31/99 1:23pm"
    else
    {
        $Pretty = date(($Verbose
                ? ($IncludeOldTimes ? 'F jS, Y \a\t g:ia' : 'F jS, Y')
                : ($IncludeOldTimes ? "n/j/y g:ia" : "n/j/y")
                ), $TStamp);
    }

    # return nicely-formatted timestamp to caller
    return $Pretty;
}
