<?php
/**
 * eXtreme Message Board
 * XMB 1.9.11
 *
 * Developed And Maintained By The XMB Group
 * Copyright (c) 2001-2011, The XMB Group
 * http://www.xmbforum.com
 *
 * Sponsored By iEntry, Inc.
 * http://www.ientry.com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 **/

if (!defined('IN_CODE')) {
    header('HTTP/1.0 403 Forbidden');
    exit("Not allowed to run this file directly.");
}

/**
 * Responsible for accepting credentials for new sessions.
 *
 * @param  string $xmbuserinput Must be html escaped & db escaped username input.
 * @param  string $xmbpwinput   Must be raw password hash input.
 * @param  bool   $invisible    Optional.
 * @param  bool   $tempcookie   Optional.
 * @return bool
 */
function loginUser($xmbuserinput, $xmbpwinput, $invisible=NULL, $tempcookie=FALSE) {
    global $server, $self, $onlineip, $onlinetime, $db, $cookiepath, $cookiedomain, $cookiesecure;

    if (elevateUser($xmbuserinput, $xmbpwinput, $invisible)) {
        $dbname = $db->escape($self['username']);

        if (!is_null($invisible)) {
            if ($invisible And $self['invisible'] == 0) {
                $db->query("UPDATE ".X_PREFIX."members SET invisible='1' WHERE username='$dbname'");
                $self['invisible'] = 1;
            } elseif (!$invisible And $self['invisible'] == 1) {
                $db->query("UPDATE ".X_PREFIX."members SET invisible='0' WHERE username='$dbname'");
                $self['invisible'] = 0;
            }
        }

        if ($tempcookie) {
            $currtime = 0;
        } else {
            $currtime = $onlinetime + (86400*30);
        }

        if ($server == 'Mic') {
            $setusing = X_SET_JS;
        } else {
            $setusing = X_SET_HEADER;
        }
        put_cookie("xmbuser", $self['username'], $currtime, $cookiepath, $cookiedomain, $cookiesecure, $setusing);
        put_cookie("xmbpw", $xmbpwinput, $currtime, $cookiepath, $cookiedomain, $cookiesecure, $setusing);
        return TRUE;
    } else {
        return FALSE;
    }
}

/**
 * Responsible for authenticating established sessions and setting up session variables.
 *
 * @param  string $xmbuserinput Must be html escaped & db escaped username input.
 * @param  string $xmbpwinput Must be raw password hash input.
 * @param  int    $force_inv Optional.
 * @param  string $serror Optional. Informs this function if any session errors occurred before authenticating.
 * @return bool
 */
function elevateUser($xmbuserinput, $xmbpwinput, $force_inv=FALSE, $serror = '') {
    global $xmbuser, $xmbpw, $self, $db, $SETTINGS, $status_enum;

    $xmbuser = '';
    $xmbpw = '';
    $self = array();
    $maxurl = 150; //Schema constant.

    //Usernames are historically html encoded in the XMB database, as well as in cookies.
    //$xmbuser is often used as a raw value in queries and should be sql escaped.
    //$self['username'] is a good alternative for future template use.
    //$xmbpw was historically abused and will no longer contain a value.

    if (strlen($xmbuserinput) >= 3) {
        $query = $db->query("SELECT * FROM ".X_PREFIX."members WHERE username='$xmbuserinput'");
        if ($db->num_rows($query) == 1) {
            $self = $db->fetch_array($query); //The self array will remain available, global.
            if ($serror == 'ip' And $self['status'] != 'Super Administrator' And $self['status'] != 'Administrator') {
                // User is IP-Banned
            } elseif ($self['password'] == $xmbpwinput) {
                $xmbuser = $db->escape($self['username']);
            }
            $self['password'] = '';
        }
        $db->free_result($query);
    }

    $xmbuserinput = '';
    $xmbpwinput = '';

    //Database routine complete.  Now set the user status constants.

    if ($xmbuser != '') {
        // Initialize the new translation system
        if (X_SCRIPT != 'upgrade.php') {
            if (!loadLang($self['langfile'])) {
                if (!loadLang($SETTINGS['langfile'])) {
                    require_once(ROOT.'include/translation.inc.php');
                    langPanic();
                }
            }
        }

        if ($self['status'] == 'Banned') {
            $xmbuser = '';
            $self = array();
            $self['status'] = 'Banned';
            if (!defined('X_GUEST')) {
                define('X_MEMBER', FALSE);
                define('X_GUEST', TRUE);
            }
        } else {
            if (!defined('X_GUEST')) {
                define('X_MEMBER', TRUE);
                define('X_GUEST', FALSE);
            }
            // Save some write locks by updating in 60-second intervals.
            if (abs(time() - (int)$self['lastvisit']) > 60) {
                $db->query("UPDATE ".X_PREFIX."members SET lastvisit=".$db->time(time())." WHERE username='$xmbuser'");
            }
        }
    } else {
        if (X_SCRIPT != 'upgrade.php') {
            if (!loadLang($SETTINGS['langfile'])) {
                require_once(ROOT.'include/translation.inc.php');
                langPanic();
            }
        }

        $self = array();
        $self['status'] = '';
        if (!defined('X_GUEST')) {
            define('X_MEMBER', FALSE);
            define('X_GUEST', TRUE);
        }
    }

    // Enumerate status
    if (isset($status_enum[$self['status']])) {
        $int_status = $status_enum[$self['status']];
    } else {
        $int_status = $status_enum['Member']; // If $self['status'] contains an unknown value, default to Member.
    }

    if (!defined('X_STAFF')) {
        define('X_SADMIN', ($self['status'] == 'Super Administrator'));
        define('X_ADMIN', ($int_status <= $status_enum['Administrator']));
        define('X_SMOD', ($int_status <= $status_enum['Super Moderator']));
        define('X_MOD', ($int_status <= $status_enum['Moderator']));
        define('X_STAFF', X_MOD);
    }

    // Set more globals
    global $timeoffset, $themeuser, $status, $tpp, $ppp, $memtime, $dateformat,
           $sig, $invisible, $timecode, $dformatorig, $onlineuser;

    if ($xmbuser != '') {
        $timeoffset = $self['timeoffset'];
        $themeuser = $self['theme'];
        $status = $self['status'];
        $tpp = $self['tpp'];
        $ppp = $self['ppp'];
        $memtime = $self['timeformat'];
        if ($self['dateformat'] != '') {
            $dateformat = $self['dateformat'];
        }
        $sig = $self['sig'];
        $invisible = $self['invisible'];
        $onlineuser = $xmbuser;
    } else {
        $timeoffset = $SETTINGS['def_tz'];
        $themeuser = '';
        $status = 'member';
        $tpp = $SETTINGS['topicperpage'];
        $ppp = $SETTINGS['postperpage'];
        $memtime = $SETTINGS['timeformat'];
        $sig = '';
        $invisible = 0;
        $onlineuser = 'xguest123';
        $self['ban'] = '';
        $self['sig'] = '';
        $self['uid'] = 0;
        $self['username'] = '';
    }

    if ($force_inv === TRUE) {
        $invisible = 1;
    }

    if ($memtime == 24) {
        $timecode = "H:i";
    } else {
        $timecode = "h:i A";
    }

    $dformatorig = $dateformat;
    $dateformat = str_replace(array('mm', 'MM', 'dd', 'DD', 'yyyy', 'YYYY', 'yy', 'YY'), array('n', 'n', 'j', 'j', 'Y', 'Y', 'y', 'y'), $dateformat);

    // Save This Session
    if (X_SCRIPT != 'upgrade.php' and (X_ADMIN or $serror == '' or $serror == 'guest' and X_MEMBER)) {
        global $onlineip, $onlinetime, $url;

        $wollocation = substr($url, 0, $maxurl);
        $db->escape_fast($wollocation);
        $newtime = $onlinetime - X_ONLINE_TIMER;
        $db->query("DELETE FROM ".X_PREFIX."whosonline WHERE ((ip='$onlineip' && username='xguest123') OR (username='$xmbuser') OR (time < '$newtime'))");
        $db->query("INSERT INTO ".X_PREFIX."whosonline (username, ip, time, location, invisible) VALUES ('$onlineuser', '$onlineip', ".$db->time($onlinetime).", '$wollocation', '$invisible')");
    }

    return ($xmbuser != '');
}

/**
 * Uses the new translation database to populate the old $lang and $langfile variables.
 *
 * @param string $devname Name specified by XMB for internal use (usually written in English).
 * @return bool
 */
function loadLang($devname = "English") {
    global $charset, $db, $lang, $langfile;

    $db->escape_fast($devname);

    // Query The Translation Database
    $sql = 'SELECT k.langkey, t.cdata '
         . 'FROM '.X_PREFIX.'lang_keys AS k'
         . ' LEFT JOIN '.X_PREFIX.'lang_text AS t USING (phraseid)'
         . ' INNER JOIN '.X_PREFIX.'lang_base AS b USING (langid)'
         . "WHERE b.devname = '$devname'";
    $result = $db->query($sql);

    // Load the $lang array.
    if ($db->num_rows($result) > 0) {
        $langfile = $devname;
        $lang = array();
        while($row = $db->fetch_array($result)) {
            $lang[$row['langkey']] = $row['cdata'];
        }
        $db->free_result($result);
        $charset = $lang['charset'];
        return TRUE;
    } else {
        return FALSE;
    }
}

/**
 * Uses the new translation database to retrieve a single phrase in all available languages.
 *
 * @param array $langkeys Of strings, used as the $lang array key.
 * @return array Associative indexes lang_base.devname and lang_keys.langkey.
 */
function loadPhrases($langkeys) {
    global $db;

    $csv = "'".implode("', '", $langkeys)."'";

    // Query The Translation Database
    $sql = 'SELECT b.devname, t.cdata, k.langkey '
         . 'FROM '.X_PREFIX.'lang_base AS b'
         . ' LEFT JOIN '.X_PREFIX.'lang_text AS t USING (langid)'
         . ' INNER JOIN '.X_PREFIX.'lang_keys AS k USING (phraseid)'
         . "WHERE k.langkey IN ($csv)";
    $result = $db->query($sql);

    // Load the $lang array.
    if ($db->num_rows($result) > 0) {
        $phrases = array();
        while($row = $db->fetch_array($result)) {
            $phrases[$row['devname']][$row['langkey']] = $row['cdata'];
        }
        $db->free_result($result);
        return $phrases;
    } else {
        return FALSE;
    }
}

function nav($add=false, $raquo=true) {
    global $navigation;

    if (!$add) {
        $navigation = '';
    } else {
        $navigation .= ($raquo ? ' &raquo; ' : ''). $add;
    }
}

function template($name) {
    global $db, $comment_output;

    $db->escape_fast($name);

    if (($template = templatecache(X_CACHE_GET, $name)) === false) {
        $query = $db->query("SELECT template FROM ".X_PREFIX."templates WHERE name='$name'");
        if ($db->num_rows($query) == 1) {
            if (X_SADMIN && DEBUG) {
                trigger_error('Efficiency Notice: The template "'.$name.'" was not preloaded.', E_USER_NOTICE);
            }
            $gettemplate = $db->fetch_array($query);
            templatecache(X_CACHE_PUT, $name, $gettemplate['template']);
            $template = $gettemplate['template'];
        } else {
            if (X_SADMIN && DEBUG) {
                trigger_error('Efficiency Warning: The template "'.$name.'" could not be found.', E_USER_WARNING);
            }
        }
        $db->free_result($query);
    }

    $template = str_replace("\\'","'", $template);

    if ($name != 'phpinclude' && $comment_output === true) {
        return "<!--Begin Template: $name -->\n$template\n<!-- End Template: $name -->";
    } else {
        return $template;
    }
}

function templatecache($type=X_CACHE_GET, $name, $data='') {
    static $cache;

    switch($type) {
        case X_CACHE_GET:
            if (!isset($cache[$name])) {
                return false;
            } else {
                return $cache[$name];
            }
            break;
        case X_CACHE_PUT:
            $cache[$name] = $data;
            return true;
            break;
    }
}

function loadtemplates() {
    global $db;

    $num = func_num_args();
    if ($num < 1) {
        echo 'Not enough arguments given to loadtemplates() on line: '.__LINE__;
        return false;
    } else {
        $namesarray = array_unique(array_merge(func_get_args(), array('header','css','error','message','footer','footer_querynum','footer_phpsql','footer_totaltime','footer_load')));
        $sql = "'".implode("', '", $namesarray)."'";
        $query = $db->query("SELECT name, template FROM ".X_PREFIX."templates WHERE name IN ($sql)");
        while($template = $db->fetch_array($query)) {
            templatecache(X_CACHE_PUT, $template['name'], $template['template']);
        }
        $db->free_result($query);
    }
}

/**
 * Get a template with the token filled in.
 *
 * @param string $name The template name.
 * @param string $action The action for which the token is valid.
 * @param string $id The object for which the token is valid.
 * @return string
 */
function template_secure($name, $action, $id) {
    $key = template_key($action, $id);
    $nonce = nonce_create($key);
    $placeholder = '<input type="hidden" name="token" value="" />';
    $replace = '<input type="hidden" name="token" value="'.$nonce.'" />';
    return str_replace(addslashes($placeholder), addslashes($replace), template($name));
}

/**
 * Assert token validity for a user request.
 *
 * @param string $action The action for which the token is valid.
 * @param string $id The object for which the token is valid.
 * @param int    $expire Number of seconds for which the token was valid.
 * @param bool   $error_header Display header template on errors?
 */
function request_secure($action, $id, $expire, $error_header = TRUE) {
    global $lang;

    $key = template_key($action, $id);
    $nonce = postedVar('token');
    if (!nonce_use($key, $nonce, $expire)) {
        error($lang['noadminsession'], $error_header);
    }
}

/**
 * Make a key for the nonce/key pair.
 *
 * @param string $action The action for which the token is valid.
 * @param string $id The object for which the token is valid.
 * @return string
 */
function template_key($action, $id) {
    $id_len = X_NONCE_KEY_LEN - strlen($action);
    if (strlen($id) > $id_len) {
        $id = substr($id, -$id_len);
    } else {
        $id = str_pad($id, $id_len, '0', STR_PAD_LEFT);
    }
    return $action . $id;
}

function censor($txt) {
    global $censorcache;

    $ignorespaces = TRUE;
    if (is_array($censorcache)) {
        if (count($censorcache) > 0) {
            $prevfind = '';
            foreach($censorcache as $find=>$replace) {
                if ($ignorespaces === true) {
                    $txt = str_ireplace($find, $replace, $txt);
                } else {
                    if ($prevfind == '') {
                        $prevfind = $find;
                    }
                    $txt = preg_replace("#(^|[^a-z])(".preg_quote($find)."|".preg_quote($prevfind).")($|[^a-z])#si", '\1'.$replace.'\3', $txt);
                    $prevfind = $find;
                }
            }
            if ($ignorespaces !== true) {
                $txt = preg_replace("#(^|[^a-z])(".preg_quote($find).")($|[^a-z])#si", '\1'.$replace.'\3', $txt);
            }
        }
    }

    return $txt;
}

function smile(&$txt) {
    global $smiliesnum, $smiliecache, $smdir;

    if ($smiliesnum > 0) {
        reset($smiliecache);
        foreach($smiliecache as $code=>$url) {
            $txt = str_replace($code, '<img src="./'.$smdir.'/'.$url.'" style="border:none" alt="'.$code.'" />', $txt);
        }
    }

    return TRUE;
}

function postify($message, $smileyoff='no', $bbcodeoff='no', $allowsmilies='yes', $allowhtml='yes', $allowbbcode='yes', $allowimgcode='yes', $ignorespaces=false, $ismood="no", $wrap="yes") {

    $bballow = ($allowbbcode == 'yes' || $allowbbcode == 'on') ? (($bbcodeoff != 'off' && $bbcodeoff != 'yes') ? true : false) : false;
    $smiliesallow = ($allowsmilies == 'yes' || $allowsmilies == 'on') ? (($smileyoff != 'off' && $smileyoff != 'yes') ? true : false) : false;
    $allowurlcode = ($ismood != 'yes');

    if ($bballow) {
        if ($ismood == 'yes') {
            $message = str_replace(array('[rquote=', '[quote]', '[/quote]', '[code]', '[/code]', '[list]', '[/list]', '[list=1]', '[list=a]', '[list=A]', '[/list=1]', '[/list=a]', '[/list=A]', '[*]'), '_', $message);
        }

        //Remove the code block contents from $message.
        $messagearray = bbcodeCode($message);
        $message = array();
        for($i = 0; $i < count($messagearray); $i += 2) {
            $message[$i] = $messagearray[$i];
        }
        $message = implode("<!-- code -->", $message);

        // Do BBCode
        $message = rawHTMLmessage($message, $allowhtml);
        if ($smiliesallow) {
            smile($message);
        }
        bbcode($message, $allowimgcode, $allowurlcode);
        $message = nl2br($message);

        // Replace the code block contents in $message.
        if (count($messagearray) > 1) {
            $message = explode("<!-- code -->", $message);
            for($i = 0; $i < count($message) - 1; $i++) {
                $message[$i] .= censor($messagearray[$i*2+1]);
            }
            $message = implode("", $message);
        }

        if ('yes' == $wrap) {
            xmb_wordwrap($message);
        } else {
            $message = str_replace(array('<!-- nobr -->', '<!-- /nobr -->'), array('', ''), $message);
        }
    } else {
        $message = rawHTMLmessage($message, $allowhtml);
        if ($smiliesallow) {
            smile($message);
        }
        $message = nl2br($message);
        if ('yes' == $wrap) {
            xmb_wordwrap($message);
        }
    }

    $message = preg_replace('#(script|about|applet|activex|chrome):#Sis',"\\1 &#058;",$message);

    return $message;
}

function bbcode(&$message, $allowimgcode, $allowurlcode) {
    global $lang, $imgdir;

    //Balance simple tags.
    $begin = array(
        0 => '[b]',
        1 => '[i]',
        2 => '[u]',
        3 => '[marquee]',
        4 => '[blink]',
        5 => '[strike]',
        6 => '[quote]',
        8 => '[list]',
        9 => '[list=1]',
        10 => '[list=a]',
        11 => '[list=A]',
    );

    $end = array(
        0 => '[/b]',
        1 => '[/i]',
        2 => '[/u]',
        3 => '[/marquee]',
        4 => '[/blink]',
        5 => '[/strike]',
        6 => '[/quote]',
        8 => '[/list]',
        9 => '[/list=1]',
        10 => '[/list=a]',
        11 => '[/list=A]',
    );

    foreach($begin as $key=>$value) {
        $check = substr_count($message, $value) - substr_count($message, $end[$key]);
        if ($check > 0) {
            $message .= str_repeat($end[$key], $check);
        } else if ($check < 0) {
            $message = str_repeat($value, abs($check)).$message;
        }
    }

    // Balance regex tags.
    $regex = array();
    $regex['align']  = "@\\[align=(left|center|right|justify)\\]@i";
    $regex['font']   = "@\\[font=([a-z\\r\\n\\t 0-9]+)\\]@i";
    $regex['rquote'] = "@\\[rquote=(\\d+)&(?:amp;)?tid=(\\d+)&(?:amp;)?author=([^\\[\\]<>]+)\\]@s";
    $regex['size']   = "@\\[size=([+-]?[0-9]{1,2})\\]@";
    $regex['color'] = array();
    $regex['color']['named'] = "@\\[color=(White|Black|Red|Yellow|Pink|Green|Orange|Purple|Blue|Beige|Brown|Teal|Navy|Maroon|LimeGreen|aqua|fuchsia|gray|silver|lime|olive)\\]@i";
    $regex['color']['hex']   = "@\\[color=#([\\da-f]{3,6})\\]@i";
    $regex['color']['rgb']   = "@\\[color=rgb\\(([\\s]*[\\d]{1,3}%?[\\s]*,[\\s]*[\\d]{1,3}%?[\\s]*,[\\s]*[\\d]{1,3}%?[\\s]*)\\)\\]@i";

    bbcodeBalanceTags($message, $regex);

    // Replace simple tags.
    $find = array(
        0 => '[b]',
        1 => '[/b]',
        2 => '[i]',
        3 => '[/i]',
        4 => '[u]',
        5 => '[/u]',
        6 => '[marquee]',
        7 => '[/marquee]',
        8 => '[blink]',
        9 => '[/blink]',
        10 => '[strike]',
        11 => '[/strike]',
        12 => '[quote]',
        13 => '[/quote]',
        14 => '[code]',
        15 => '[/code]',
        16 => '[list]',
        17 => '[/list]',
        18 => '[list=1]',
        19 => '[list=a]',
        20 => '[list=A]',
        21 => '[/list=1]',
        22 => '[/list=a]',
        23 => '[/list=A]',
        24 => '[*]',
        25 => '[/color]',
        26 => '[/font]',
        27 => '[/size]',
        28 => '[/align]',
        29 => '[/rquote]'
    );

    $replace = array(
        0 => '<strong>',
        1 => '</strong>',
        2 => '<em>',
        3 => '</em>',
        4 => '<u>',
        5 => '</u>',
        6 => '<marquee>',
        7 => '</marquee>',
        8 => '<blink>',
        9 => '</blink>',
        10 => '<strike>',
        11 => '</strike>',
        12 => '</font> <!-- nobr --><table align="center" class="quote" cellspacing="0" cellpadding="0"><tr><td class="quote">'.$lang['textquote'].'</td></tr><tr><td class="quotemessage"><!-- /nobr -->',
        13 => ' </td></tr></table><font class="mediumtxt">',
        14 => '</font> <!-- nobr --><table align="center" class="code" cellspacing="0" cellpadding="0"><tr><td class="code">'.$lang['textcode'].'</td></tr><tr><td class="codemessage"><code>',
        15 => '</code></td></tr></table><font class="mediumtxt"><!-- /nobr -->',
        16 => '<ul type="square">',
        17 => '</ul>',
        18 => '<ol type="1">',
        19 => '<ol type="A">',
        20 => '<ol type="A">',
        21 => '</ol>',
        22 => '</ol>',
        23 => '</ol>',
        24 => '<li />',
        25 => '</span>',
        26 => '</span>',
        27 => '</span>',
        28 => '</div>',
        29 => ' </td></tr></table><font class="mediumtxt">'
    );

    $message = str_replace($find, $replace, $message);

    // Replace regex tags.
    $patterns = array();
    $replacements = array();

    $patterns[] = $regex['rquote'];
    $replacements[] = '</font> <!-- nobr --><table align="center" class="quote" cellspacing="0" cellpadding="0"><tr><td class="quote">'.$lang['textquote'].' <a href="viewthread.php?tid=$2&amp;goto=search&amp;pid=$1" rel="nofollow">'.$lang['origpostedby'].' $3 &nbsp;<img src="'.$imgdir.'/lastpost.gif" border="0" alt="" style="vertical-align: middle;" /></a></td></tr><tr><td class="quotemessage"><!-- /nobr -->';
    $patterns[] = $regex['color']['named'];
    $replacements[] = '<span style="color: $1;">';
    $patterns[] = $regex['color']['hex'];
    $replacements[] = '<span style="color: #$1;">';
    $patterns[] = $regex['color']['rgb'];
    $replacements[] = '<span style="color: rgb($1);">';
    $patterns[] = $regex['font'];
    $replacements[] = '<span style="font-family: $1;">';
    $patterns[] = $regex['align'];
    $replacements[] = '<div style="text-align: $1;">';

    $patterns[] = "@\\[pid=(\\d+)&amp;tid=(\\d+)](.*?)\\[/pid]@si";
    $replacements[] = '<a <!-- nobr -->href="viewthread.php?tid=$2&amp;goto=search&amp;pid=$1"><strong><!-- /nobr -->$3</strong> &nbsp;<img src="'.$imgdir.'/lastpost.gif" border="0" alt="" style="vertical-align: middle;" /></a>';

    if ($allowimgcode != 'no' && $allowimgcode != 'off') {
        if (false == stripos($message, 'javascript:')) {
            $patterns[] = '#\[img\](http[s]?|ftp[s]?){1}://([:a-z\\./_\-0-9%~]+){1}\[/img\]#Smi';
            $replacements[] = '<img <!-- nobr -->src="\1://\2\3"<!-- /nobr --> border="0" alt="" />';
            $patterns[] = "#\[img=([0-9]*?){1}x([0-9]*?)\](http[s]?|ftp[s]?){1}://([:~a-z\\./0-9_\-%]+){1}(\?[a-z=0-9&_\-;~]*)?\[/img\]#Smi";
            $replacements[] = '<img width="\1" height="\2" <!-- nobr -->src="\3://\4\5"<!-- /nobr --> alt="" border="0" />';
        }
    }

    $patterns[] = "#\\[email\\]([^\"'<>]+?)\\[/email\\]#mi";
    $replacements[] = '<a href="mailto:\1">\1</a>';
    $patterns[] = "#\\[email=([^\"'<>\\[\\]]+)\\](.+?)\\[/email\\]#mi";
    $replacements[] = '<a href="mailto:\1">\2</a>';

    $message = preg_replace($patterns, $replacements, $message);

    $message = preg_replace_callback($regex['size'], 'bbcodeSizeTags', $message);

    if ($allowurlcode) {
        /*
          This block positioned last so that bare URLs may appear adjacent to BBCodes without matching on square braces.
          Regexp explanation: match strings surrounded by whitespace or () or ><.  Do not include the surrounding chars.
            Group 1 will be identical to the full match so that the callback function can be reused for [url] codes.
        */
        $regexp = '(?<=^|\s|>|\()'
                . '('
                . '(?:(?:http|ftp)s?://|www)'
                . '[-a-z0-9.]+\.[a-z]{2,4}'
                . '[^\s()"\'<>\[\]]*'
                . ')'
                . '(?=$|\s|<|\))';
        $message = preg_replace_callback("#$regexp#Smi", 'bbcodeLongURLs', $message);

        //[url]http://www.example.com/[/url]
        //[url]www.example.com[/url]
        $message = preg_replace_callback("#\[url\]([^\"'<>]+?)\[/url\]#i", 'bbcodeLongURLs', $message);

        //[url=http://www.example.com/]Lorem Ipsum[/url]
        //[url=www.example.com]Lorem Ipsum[/url]
        $message = preg_replace_callback("#\[url=([^\"'<>\[\]]+)\](.*?)\[/url\]#i", 'bbcodeLongURLs', $message);
    }

    return TRUE;
}

/**
 * Full parsing of [code] tags.
 *
 * @param string $message
 * @return array Odd number indexes contain the code block contents.
 */
function bbcodeCode($message){
    $counter = 0;
    $offset = 0;
    $done = FALSE;
    $messagearray = array();
    while(!$done){
        $pos = strpos($message, '[code]', $offset);
        if (FALSE === $pos) {
            $messagearray[$counter] = substr($message, $offset);
            $messagearray[$counter] = str_replace('[/code]', '&#091;/code]', $messagearray[$counter]);
            if ($counter > 1) {
                $messagearray[$counter] = '[/code]'.$messagearray[$counter];
            }
            $done = TRUE;
        } else {
            $pos += strlen('[code]');
            $messagearray[$counter] = substr($message, $offset, $pos - $offset);
            $messagearray[$counter] = str_replace('[/code]', '&#091;/code]', $messagearray[$counter]);
            if ($counter > 1) {
                $messagearray[$counter] = '[/code]'.$messagearray[$counter];
            }
            $counter++;
            $offset = $pos;
            $pos = strpos($message, '[/code]', $offset);
            if (FALSE === $pos) {
                $messagearray[$counter] = substr($message, $offset);
                $counter++;
                $messagearray[$counter] = '[/code]';
                $done = TRUE;
            } else {
                $messagearray[$counter] = substr($message, $offset, $pos - $offset);
                $counter++;
                $offset = $pos + strlen('[/code]');
            }
        }
    }
    return $messagearray;
}

/**
 * Wraps long lines but avoids certain elements.
 *
 * @since 1.9.11.12
 * @param string $input Read/Write Variable
 */
function xmb_wordwrap(&$input) {
    $br = trim(nl2br("\n"));
    $messagearray = preg_split("#<!-- nobr -->|<!-- /nobr -->#", $input);
    for($i = 0; $i < sizeof($messagearray); $i++) {
        if ($i % 2 == 0) {
            $messagearray[$i] = explode($br, $messagearray[$i]);
            foreach($messagearray[$i] as $key => $val) {
                $messagearray[$i][$key] = wordwrap($val, 150, "\n", TRUE);
            }
            $messagearray[$i] = implode($br, $messagearray[$i]);
        } // else inside nobr block
    }
    $input = implode('', $messagearray);
}

/**
 * Guarantees each BBCode has an equal number of open and close tags.
 *
 * @since 1.9.11.12
 * @param string $message Read/Write Variable
 * @param array $regex Indexed by code name
 */
function bbcodeBalanceTags(&$message, $regex){
    foreach($regex as $code => $pattern) {
        if (is_array($pattern)) {
            $open = 0;
            foreach($pattern as $subpattern) {
                $open += preg_match_all($subpattern, $message, $matches);
            }
        } else {
            $open = preg_match_all($pattern, $message, $matches);
        }
        $close = substr_count($message, "[/$code]");
        $open -= $close;
        if ($open > 0) {
            $message .= str_repeat("[/$code]", $open);
        } elseif ($open < 0) {
            $message = preg_replace("@\\[/$code]@", "&#091;/$code]", $message, -$open);
        }
    }
}

/**
 * DEPRECATED by XMB 1.9.11.12
 */
function fixUrl($matches) {
    $fullurl = '';
    if (!empty($matches[2])) {
        if ($matches[3] != 'www') {
            $fullurl = $matches[2];
        } else {
            $fullurl = 'http://'.$matches[2];
        }
    }

    $fullurl = strip_tags($fullurl);

    return $matches[1].'[url]'.$fullurl.'[/url]';
}

/**
 * Handles the [url] BBCode.
 *
 * This helper function is algorithmically required in order to fully support
 * unencoded square braces in BBCode URLs.  Encoding of the RFC 1738 Unsafe
 * character set thus remains optional at the BBCode and HTML layers.
 *
 * Credit for the value used in $scheme_whitelist goes to the WordPress project.
 *
 * @since 1.9.11.12
 * @param array $url Expects $url[0] to be the raw BBCode, $url[1] to be the URL only, and optionally $url[2] to be the display text.
 * @return string The HTML replacement for $url[0] if the code was valid, else the code is unchaged.
 */
function bbcodeLongURLs($url) {
    $url_max_display_len = 60;
    $scheme_whitelist = array('http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet', 'mms', 'rtsp', 'svn');

    $colon = strpos($url[1], ':');
    if (FALSE !== $colon) {
        $scheme = substr($url[1], 0, $colon);
	if (in_array($scheme, $scheme_whitelist)) {
            $href = $url[1];
        } else {
            return $url[0];
        }
    } else {
        $href = 'http://'.$url[1];
    }
    if (!empty($url[2])) {
        $text = $url[2];
    } elseif (strlen($url[1]) <= $url_max_display_len) {
        $text = $url[1];
    } else {
        $text = substr($url[1], 0, $url_max_display_len).'...';
    }
    return '<a <!-- nobr -->href="'.$href.'" onclick="window.open(this.href); return false;"><!-- /nobr -->'.$text.'</a>';
}

/**
 * Replaces createAbsFSizeFromRel() to eliminate the /e in size bbcode regex.
 *
 * @since 1.9.11 Alpha Three
 */
function bbcodeSizeTags($matches) {
    global $fontsize;
    static $cachedFs;

    if (!is_array($cachedFs) || count($cachedFs) != 2) {
        preg_match('#([0-9]+)([a-z]+)?#i', $fontsize, $res);
        $cachedFs[0] = $res[1];
        $cachedFs[1] = $res[2];

        if (empty($cachedFs[1])) {
            $cachedFs[1] = 'px';
        }
    }

    $o = ($matches[1]+$cachedFs[0]).$cachedFs[1];

    $html = "<span style=\"font-size: $o;\">";

    return $html;
}

/**
 * Processes tags like [file]1234[/file]
 *
 * Caller must include attach.inc.php, query the attachments table,
 * and load the needed templates.
 *
 * @param string $message Read/Write Variable.  Returns the processed HTML.
 * @param array  $files   Read-Only Variable.  Contains the result rows from an attachment query.
 * @param int    $pid     Pass zero when in newthread or reply preview.
 * @param bool   $bBBcodeOnForThisPost
 */
function bbcodeFileTags(&$message, &$files, $pid, $bBBcodeOnForThisPost) {
    global $lang, $SETTINGS;

    $pid = intval($pid);
    $count = 0;
    $separator = '';
    foreach($files as $attach) {
        $post = array();
        $post['filename'] = attrOut($attach['filename']);
        $post['filetype'] = attrOut($attach['filetype']);
        $post['fileurl'] = getAttachmentURL($attach['aid'], $pid, $attach['filename']);
        $attachsize = getSizeFormatted($attach['filesize']);

        $post['filedims'] = '';
        $output = '';
        $prefix = '';
        $extension = strtolower(get_extension($post['filename']));
        $img_extensions = array('jpg', 'jpeg', 'jpe', 'gif', 'png', 'wbmp', 'wbm', 'bmp');
        if ($SETTINGS['attachimgpost'] == 'on' and in_array($extension, $img_extensions)) {
            if (intval($attach['thumbid'] > 0)) {
                $post['thumburl'] = getAttachmentURL($attach['thumbid'], $pid, $attach['thumbname']);
                $result = explode('x', $attach['thumbsize']);
                $post['filedims'] = 'width="'.$result[0].'px" height="'.$result[1].'px"';
                eval('$output = "'.template('viewthread_post_attachmentthumb').'";');
            } else {
                if ($attach['img_size'] != '') {
                    $result = explode('x', $attach['img_size']);
                    $post['filedims'] = 'width="'.$result[0].'px" height="'.$result[1].'px"';
                }
                eval('$output = "'.template('viewthread_post_attachmentimage').'";');
            }
            $separator = '';
        } else {
            $downloadcount = $attach['downloads'];
            if ($downloadcount == '') {
                $downloadcount = 0;
            }
            eval('$output = "'.template('viewthread_post_attachment').'";');
            if ($separator == '') {
                $prefix = "<br /><br />";
            }
            $separator = "<br /><br />";
        }
        $output = '<!-- nobr -->'.trim(str_replace(array("\n","\r"), array('',''), $output)).'<!-- /nobr -->'; // Avoid nl2br, trailing space, wordwrap.
        if ($count == 0) {
            $prefix = "<br /><br />";
        }
        $matches = 0;
        if ($bBBcodeOnForThisPost) {
            $find = "[file]{$attach['aid']}[/file]";
            $pos = strpos($message, $find);
            if ($pos !== FALSE) {
                $matches = 1;
                $message = substr($message, 0, $pos).$output.substr($message, $pos + strlen($find));
            }
        }
        if ($matches == 0) {
            $message .= $prefix.$output.$separator; // Do we need some sort of a separator template here?
            $count++;
        }
    }
}

function modcheck($username, $mods, $override=X_SMOD) {

    $retval = '';
    if ($override) {
        $retval = 'Moderator';
    } else if (X_MOD) {
        $username = strtoupper($username);
        $mods = explode(',', $mods);
        foreach($mods as $key=>$moderator) {
            if (strtoupper(trim($moderator)) == $username) {
                $retval = 'Moderator';
                break;
            }
        }
    }

    return $retval;
}

function modcheckPost(&$username, &$mods, &$origstatus) {
    global $SETTINGS;
    $retval = modcheck($username, $mods);

    if ($retval != '' And $SETTINGS['allowrankedit'] != 'off') {
        switch($origstatus) {
            case 'Super Administrator':
                if (!X_SADMIN) {
                    $retval = '';
                }
                break;
            case 'Administrator':
                if (!X_ADMIN) {
                    $retval = '';
                }
                break;
            case 'Super Moderator':
                if (!X_SMOD) {
                    $retval = '';
                }
                break;
            //If member does not have X_MOD then modcheck() returned a null string.  No reason to continue testing.
        }
    }

    return $retval;
}

// As of version 1.9.11, function forum() is not responsible for any permissions checking.
// Caller should use permittedForums() or getStructuredForums() instead of querying for the parameters.
function forum($forum, $template, $index_subforums) {
    global $timecode, $dateformat, $lang, $timeoffset, $oldtopics, $lastvisit;
    global $altbg1, $altbg2, $imgdir, $THEME, $SETTINGS;

    $forum['name'] = fnameOut($forum['name']);
    $forum['description'] = html_entity_decode($forum['description']);

    if (isset($forum['moderator']) && $forum['lastpost'] != '') {
        $lastpost = explode('|', $forum['lastpost']);
        $dalast = $lastpost[0];
        if ($lastpost[1] != 'Anonymous' && $lastpost[1] != '') {
            $lastpost[1] = '<a href="member.php?action=viewpro&amp;member='.recodeOut($lastpost[1]).'">'.$lastpost[1].'</a>';
        } else {
            $lastpost[1] = $lang['textanonymous'];
        }

        $lastPid = isset($lastpost[2]) ? $lastpost[2] : 0;

        $lastpostdate = gmdate($dateformat, $lastpost[0] + ($timeoffset * 3600) + ($SETTINGS['addtime'] * 3600));
        $lastposttime = gmdate($timecode, $lastpost[0] + ($timeoffset * 3600) + ($SETTINGS['addtime'] * 3600));
        $lastpost = $lastpostdate.' '.$lang['textat'].' '.$lastposttime.'<br />'.$lang['textby'].' '.$lastpost[1];
        eval('$lastpostrow = "'.template($template.'_lastpost').'";');
    } else {
        $dalast = 0;
        $lastpost = $lang['textnever'];
        eval('$lastpostrow = "'.template($template.'_nolastpost').'";');
    }

    if ($lastvisit < $dalast && (strpos($oldtopics, '|'.$lastPid.'|') === false)) {
        $folder = '<img src="'.$THEME['imgdir'].'/red_folder.gif" alt="'.$lang['altredfolder'].'" border="0" />';
    } else {
        $folder = '<img src="'.$THEME['imgdir'].'/folder.gif" alt="'.$lang['altfolder'].'" border="0" />';
    }

    if ($dalast == '') {
        $folder = '<img src="'.$THEME['imgdir'].'/folder.gif" alt="'.$lang['altfolder'].'" border="0" />';
    }

    $foruminfo = '';

    if (isset($forum['moderator']) && $forum['moderator'] != '') {
        $moderators = explode(', ', $forum['moderator']);
        $forum['moderator'] = array();
        for($num = 0; $num < count($moderators); $num++) {
            $forum['moderator'][] = '<a href="member.php?action=viewpro&amp;member='.recodeOut($moderators[$num]).'">'.$moderators[$num].'</a>';
        }
        $forum['moderator'] = implode(', ', $forum['moderator']);
        $forum['moderator'] = '('.$lang['textmodby'].' '.$forum['moderator'].')';
    }

    $subforums = array();
    if (count($index_subforums) > 0) {
        for($i=0; $i < count($index_subforums); $i++) {
            $sub = $index_subforums[$i];
            if ($sub['fup'] == $forum['fid']) {
                $subforums[] = '<a href="forumdisplay.php?fid='.intval($sub['fid']).'">'.fnameOut($sub['name']).'</a>';
            }
        }
    }

    if (!empty($subforums)) {
        $subforums = implode(', ', $subforums);
        $subforums = '<br /><strong>'.$lang['textsubforums'].'</strong> '.$subforums;
    } else {
        $subforums = '';
    }
    eval('$foruminfo = "'.template($template).'";');

    $dalast = '';

    return $foruminfo;
}

/**
 * Handles most of the I/O tasks to create a collection of numbered pages
 * from an ordered collection of items.
 *
 * Caller must echo the returned html directly or in a template variable.
 *
 * @param int $num Total number of items in the collection.
 * @param int $perpage Number of items to display on each page.
 * @param string $baseurl Relative URL of the first page in the collection.
 * @param mixed $canonical Optional. Specify FALSE if the $baseurl param is not a canonical URL. Specify a Relative URL string to override $baseurl.
 * @return array Associative indexes: 'html' the link bar string, 'start' the LIMIT int used in queries.
 */
function multipage($num, $perpage, $baseurl, $canonical = TRUE) {
    global $cookiepath, $full_url, $lang, $url;

    // Initialize
    $return = array();
    $page = getInt('page');
    $max_page = quickpage(intval($num), intval($perpage));
    if ($canonical === TRUE) $canonical =& $baseurl;

    // Calculate the LIMIT start number for queries
    if ($page > 1 && $page <= $max_page) {
        $return['start'] = ($page-1) * $perpage;
        if ($canonical !== FALSE) setCanonicalLink($canonical.((strpos($baseurl, '?') !== FALSE) ? '&amp;' : '?').'page='.$page);
    } elseif ($page == 0 And !isset($_GET['page'])) {
        $return['start'] = 0;
        $page = 1;
        if ($canonical !== FALSE) setCanonicalLink($canonical);
    } elseif ($page == 1) {
        $newurl = preg_replace('/[^\x20-\x7e]/', '', $url);
        $newurl = str_replace('&page=1', '', $newurl);
        $newurl = substr($full_url, 0, -strlen($cookiepath)).$newurl;
        header('HTTP/1.0 301 Moved Permanently');
        header('Location: '.$newurl);
        exit;
    } else {
        header('HTTP/1.0 404 Not Found');
        error($lang['generic_missing']);
    }

    // Generate the multipage link bar.
    $return['html'] = multi($page, $max_page, $baseurl);

    return $return;
}

/**
 * Generates an HTML page-selection bar for any collection of numbered pages.
 *
 * The link to each page in the collection will have the "page" variable added
 * to its query string, except for page number one.
 *
 * @param int $page Current page number, must be >= 1.
 * @param int $lastpage Total number of pages in the collection.
 * @param string $mpurl Read-Only Variable. Relative URL of the first page in the collection.
 * @param bool $isself FALSE indicates the page bar will be displayed on a page that is not part of the collection.
 * @return string Null string if the $lastpage parameter was <= 1 or $page was invalid.
 */
function multi($page, $lastpage, &$mpurl, $isself = TRUE) {
    global $lang;

    $multipage = $lang['textpages'];

    if ($page >= 1 And $lastpage > 1 And $page <= $lastpage) {
        if ($page >= $lastpage - 3) {
            $to = $lastpage;
        } else {
            $to = $page + 3;
        }

        if ($page <= 4) {
            $from = 1;
        } else {
            $from = $page - 3;
        }

        $to--;
        $from++;

        $string = (strpos($mpurl, '?') !== false) ? '&amp;' : '?';

        // Link to first page
        $multipage .= "\n";
        if ($page != 1 Or !$isself) {
            $extra = '';
            if ($isself) {
                $extra = ' rel="start"';
            }
            $multipage .= '&nbsp;<u><a href="'.$mpurl.'"'.$extra.'>1</a></u>';
            if ($from > 2) {
                $multipage .= "\n&nbsp;..";
            }
        } else {
            $multipage .= '&nbsp;<strong>1</strong>';
        }

        // Link to current page and up to 2 prev and 2 next pages.
        $multipage .= "\n";
        for($i = $from; $i <= $to; $i++) {
            if ($i != $page) {
                $extra = '';
                if ($isself) {
                    if ($i == $page - 1) {
                        $extra = ' rel="prev"';
                    } else if ($i == $page + 1) {
                        $extra = ' rel="next"';
                    }
                    if ($page == 1) {
                        $extra .= ' rev="start"';
                    }
                }
                $multipage .= '&nbsp;<u><a href="'.$mpurl.$string.'page='.$i.'"'.$extra.'>'.$i.'</a></u>';
            } else {
                $multipage .= '&nbsp;<strong>'.$i.'</strong>';
            }
            $multipage .= "\n";
        }

        // Link to last page
        if ($lastpage != $page) {
            if (($lastpage - 1) > $to) {
                $multipage .= "&nbsp;..\n";
            }
            $extra = '';
            if ($isself And $page == 1) {
                $extra = ' rev="start"';
            }
            $multipage .= '&nbsp;<u><a href="'.$mpurl.$string.'page='.$lastpage.'"'.$extra.'>'.$lastpage.'</a></u>';
        } else {
            $multipage .= '&nbsp;<strong>'.$lastpage.'</strong>';
        }
    } else {
        $multipage = '';
    }

    return $multipage;
}

function quickpage($things, $thingsperpage) {
    return ((($things > 0) && ($thingsperpage > 0) && ($things > $thingsperpage)) ? ceil($things / $thingsperpage) : 1);
}

function smilieinsert($type='normal') {
    global $imgdir, $smdir, $db, $SETTINGS, $smiliesnum, $smiliecache;

    $counter = 0;
    $sms = array();
    $smilies = '';
    $smilieinsert = '';

    if ($type == 'normal') {
        $smcols = intval($SETTINGS['smcols']);
        $smtotal = intval($SETTINGS['smtotal']);
    } elseif ($type == 'quick') {
        $smcols = 4;
        $smtotal = 16;
    } elseif ($type == 'full') {
        $smcols = intval($SETTINGS['smcols']);
        $smtotal = 0;
    }

    if ($SETTINGS['smileyinsert'] == 'on' And $smcols > 0 And $smiliesnum > 0) {
        foreach($smiliecache as $key=>$val) {
            $smilie['code'] = $key;
            $smilie['url'] = $val;
            eval('$sms[] = "'.template('functions_smilieinsert_smilie').'";');
            if ($smtotal > 0) {
                $counter++;
                if ($counter >= $smtotal) {
                    break;
                }
            }
        }

        $smilies = '<tr>';
        for($i=0;$i<count($sms);$i++) {
            $smilies .= $sms[$i];
            if (($i+1)%$smcols == 0) {
                $smilies .= '</tr>';
                if (($i+1) < $smtotal) {
                    $smilies .= '<tr>';
                }
            }
        }

        if ($smtotal%$smcols > 0) {
            $left = $smcols-($smtotal%$smcols);
            for($i=0;$i<$left;$i++) {
                $smilies .= '<td />';
            }
            $smilies .= '</tr>';
        }
        eval('$smilieinsert = "'.template('functions_smilieinsert').'";');
    }

    return $smilieinsert;
}

function updateforumcount($fid) {
    global $db;
    $fid = intval($fid);

    $query = $db->query("SELECT COUNT(pid) FROM ".X_PREFIX."forums AS f INNER JOIN ".X_PREFIX."posts USING(fid) WHERE f.fid=$fid OR f.fup=$fid");
    $postcount = $db->result($query, 0);
    $db->free_result($query);

    $query = $db->query("SELECT COUNT(tid) FROM ".X_PREFIX."forums AS f INNER JOIN ".X_PREFIX."threads USING(fid) WHERE f.fid=$fid OR f.fup=$fid");
    $threadcount = $db->result($query, 0);
    $db->free_result($query);

    $query = $db->query("SELECT t.lastpost FROM ".X_PREFIX."forums AS f LEFT JOIN ".X_PREFIX."threads AS t USING(fid) WHERE f.fid=$fid OR f.fup=$fid ORDER BY t.lastpost DESC LIMIT 0, 1");
    $lp = $db->fetch_array($query);
    $db->escape_fast($lp['lastpost']);
    $db->query("UPDATE ".X_PREFIX."forums SET posts='$postcount', threads='$threadcount', lastpost='{$lp['lastpost']}' WHERE fid='$fid'");
    $db->free_result($query);
}

function updatethreadcount($tid) {
    global $db;
    $tid = intval($tid);

    $query = $db->query("SELECT tid FROM ".X_PREFIX."posts WHERE tid='$tid'");
    $replycount = $db->num_rows($query);
    $db->free_result($query);
    $replycount--;
    $query = $db->query("SELECT dateline, author, pid FROM ".X_PREFIX."posts WHERE tid='$tid' ORDER BY dateline DESC, pid DESC LIMIT 1");
    $lp = $db->fetch_array($query);
    $db->free_result($query);
    $query = $db->query("SELECT date, username FROM ".X_PREFIX."logs WHERE tid='$tid' AND action='bump' ORDER BY date DESC LIMIT 1");
    if ($db->num_rows($query) == 1) {
        $lb = $db->fetch_array($query);
        $lp['dateline'] = $lb['date'];
        $lp['author'] = $lb['username'];
    }
    $db->free_result($query);
    $lastpost = $lp['dateline'].'|'.$lp['author'].'|'.$lp['pid'];
    $db->escape_fast($lastpost);

    $db->query("UPDATE ".X_PREFIX."threads SET replies='$replycount', lastpost='$lastpost' WHERE tid='$tid'");
}

function smcwcache() {
    global $db, $smiliecache, $censorcache, $smiliesnum, $wordsnum;
    static $cached;

    if (!$cached) {
        $smiliecache = array();
        $censorcache = array();

        $query = $db->query("SELECT code, url FROM ".X_PREFIX."smilies WHERE type='smiley'");
        $smiliesnum = $db->num_rows($query);

        if ($smiliesnum > 0) {
            while($smilie = $db->fetch_array($query)) {
                $code = $smilie['code'];
                $smiliecache[$code] = $smilie['url'];
            }
        }
        $db->free_result($query);

        $query = $db->query("SELECT find, replace1 FROM ".X_PREFIX."words");
        $wordsnum = $db->num_rows($query);
        if ($wordsnum > 0) {
            while($word = $db->fetch_array($query)) {
                $find = $word['find'];
                $censorcache[$find] = $word['replace1'];
            }
        }
        $db->free_result($query);

        $cached = true;
        return true;
    }

    return false;
}

if (!function_exists('htmlspecialchars_decode')) {
    function htmlspecialchars_decode($string, $type=ENT_QUOTES) {
        $array = array_flip(get_html_translation_table(HTML_SPECIALCHARS, $type));
        return strtr($string, $array);
    }
}

/**
 * Generates sub-templates in the $footerstuff global array.
 */
function end_time() {
    global $db, $footerstuff, $lang, $starttime, $SETTINGS;

    $mtime2 = explode(' ', microtime());
    $endtime = $mtime2[1] + $mtime2[0];

    $totaltime = ($endtime - $starttime);

    $footer_options = explode('-', $SETTINGS['footer_options']);

    if (X_ADMIN && in_array('serverload', $footer_options)) {
        $load = ServerLoad();
        if (!empty($load)) {
            eval('$footerstuff["load"] = "'.template('footer_load').'";');
        } else {
            $footerstuff['load'] = '';
        }
    } else {
        $footerstuff['load'] = '';
    }

    if (in_array('queries', $footer_options)) {
        $querynum = $db->querynum;
        eval('$footerstuff["querynum"] = "'.template('footer_querynum').'";');
    } else {
        $footerstuff['querynum'] = '';
    }

    if (in_array('phpsql', $footer_options)) {
        $db_duration = number_format(($db->duration/$totaltime)*100, 1);
        $php_duration = number_format((1-($db->duration/$totaltime))*100, 1);
        eval('$footerstuff["phpsql"] = "'.template('footer_phpsql').'";');
    } else {
        $footerstuff['phpsql'] = '';
    }

    if (in_array('loadtimes', $footer_options) && X_ADMIN) {
        $totaltime = number_format($totaltime, 7);
        eval('$footerstuff["totaltime"] = "'.template('footer_totaltime').'";');
    } else {
        $footerstuff['totaltime'] = '';
    }

    if (X_SADMIN && DEBUG) {
        $footerstuff['querydump'] = printAllQueries();
    } else {
        $footerstuff['querydump'] = '';
    }
}

function redirect($path, $timeout=2, $type=X_REDIRECT_HEADER) {
    if (strpos(urldecode($path), "\n") !== false || strpos(urldecode($path), "\r") !== false) {
        error('Tried to redirect to potentially insecure url.');
    }

    if (headers_sent() Or $type == X_REDIRECT_JS) {
        ?>
        <script language="javascript" type="text/javascript">
        function redirect() {
            window.location.replace("<?php echo $path?>");
        }
        setTimeout("redirect();", <?php echo ($timeout*1000)?>);
        </script>
        <?php
    } else {
        if ($timeout == 0) {
            header('HTTP/1.0 302 Found');
            header("Location: $path");
            exit;
        } else {
            header("Refresh: $timeout; URL=$path");
        }
    }

    return true;
}

function get_extension(&$filename) {
    $a = explode('.', $filename);
    $count = count($a);
    if ($count == 1) {
        return '';
    } else {
        return $a[$count-1];
    }
}

function ServerLoad() {
    if ($stats = @exec('uptime')) {
        $parts = explode(',', $stats);
        $count = count($parts);
        $first = explode(' ', $parts[$count-3]);
        $c = count($first);
        $first = $first[$c-1];
        return array($first, $parts[$count-2], $parts[$count-1]);
    } else {
        return array();
    }
}

function error($msg, $showheader=true, $prepend='', $append='', $redirect=false, $die=true, $return_as_string=false, $showfooter=true) {
    global $footerstuff, $navigation; // Used by nav() and end_time()

    if (isset($GLOBALS)) {
        extract($GLOBALS, EXTR_SKIP);
    }

    $args = func_get_args();

    $message = (isset($args[0]) ? $args[0] : '');
    $showheader = (isset($args[1]) ? $args[1] : true);
    $prepend = (isset($args[2]) ? $args[2] : '');
    $append = (isset($args[3]) ? $args[3] : '');
    $redirect = (isset($args[4]) ? $args[4] : false);
    $die = (isset($args[5]) ? $args[5] : true);
    $return_str = (isset($args[6]) ? $args[6] : false);
    $showfooter = (isset($args[7]) ? $args[7] : true);

    $header = $footer = $return = '';

    if ($showheader) {
        nav($lang['error']);
    }

    end_time();

    if ($redirect !== false) {
        redirect($redirect, 3);
    }

    if ($showheader === false) {
        $header = '';
    } else {
        if (!isset($css) || strlen($css) ==0) {
            eval('$css = "'.template('css').'";');
        }
        eval('$header = "'.template('header').'";');
    }

    $error = '';
    eval('$error = "'.template('error').'";');

    if ($showfooter === true) {
        eval('$footer = "'.template('footer').'";');
    } else {
        $footer = '';
    }

    if ($return_str !== false) {
        $return = $prepend . $error . $append . $footer;
    } else {
        echo $prepend . $error . $append . $footer;
        $return = '';
    }

    if ($die) {
        exit();
    }

    return $return;
}

function message($msg, $showheader=true, $prepend='', $append='', $redirect=false, $die=true, $return_as_string=false, $showfooter=true) {
    global $footerstuff, $navigation; // Used by nav() and end_time()

    if (isset($GLOBALS)) {
        extract($GLOBALS, EXTR_SKIP);
    }

    $args = func_get_args();

    $message = (isset($args[0]) ? $args[0] : '');
    $showheader = (isset($args[1]) ? $args[1] : true);
    $prepend = (isset($args[2]) ? $args[2] : '');
    $append = (isset($args[3]) ? $args[3] : '');
    $redirect = (isset($args[4]) ? $args[4] : false);
    $die = (isset($args[5]) ? $args[5] : true);
    $return_str = (isset($args[6]) ? $args[6] : false);
    $showfooter = (isset($args[7]) ? $args[7] : true);

    $header = $footer = $return = '';

    if ($showheader) {
        nav($lang['message']);
    }

    end_time();

    if ($redirect !== false) {
        redirect($redirect, 3);
    }

    if ($showheader === false) {
        $header = '';
    } else {
        if (!isset($css) || strlen($css) ==0) {
            eval('$css = "'.template('css').'";');
        }
        eval('$header = "'.template('header').'";');
    }

    $success = '';
    eval('$success = "'.template('message').'";');

    if ($showfooter === true) {
        eval('$footer = "'.template('footer').'";');
    } else {
        $footer = '';
    }

    if ($return_str !== false) {
        $return = $prepend . $success . $append . $footer;
    } else {
        echo $prepend . $success . $append . $footer;
        $return = '';
    }

    if ($die) {
        exit();
    }

    return $return;
}

function put_cookie($name, $value=null, $expire=0, $path=null, $domain=null, $secure=FALSE, $setVia=X_SET_HEADER) {
    if (!headers_sent() && $setVia != X_SET_JS) {
        return setcookie($name, $value, $expire, $path, $domain, $secure);
    } else {
        if ($expire > 0) {
            $expire = gmdate('r', $expire);
        } else {
            $expire = '';
        }
        ?>
        <script type="text/javascript">
            function put_cookie(name, value, expires, path, domain, secure) {
                var curCookie = name + "=" + escape(value) +
                ((expires) ? "; expires=" + expires : "") +
                ((path) ? "; path=" + path : "") +
                ((domain) ? "; domain=" + domain : "") +
                ((secure) ? "; secure" : "");
                document.cookie = curCookie;
            }
            put_cookie('<?php echo $name?>', '<?php echo $value?>', '<?php echo $expire?>', '<?php echo $path?>', '<?php echo $domain?>', '<?php echo $secure?>');
        </script>
        <?php
        return true;
    }
}

function audit($user='', $action, $fid, $tid, $reason='') {
    global $xmbuser, $db;

    if ($user == '') {
        $user = $xmbuser;
    }

    $fid = (int) $fid;
    $tid = (int) $tid;
    $action = cdataOut($action);
    $user = cdataOut($user);
    $reason = cdataOut($reason);

    $db->query("INSERT ".X_PREFIX."logs (tid, username, action, fid, date) VALUES ('$tid', '$user', '$action', '$fid', " . $db->time() . ")");
    return true;
}

function validatePpp() {
    global $ppp, $postperpage;

    if (!isset($ppp) || $ppp == '') {
        $ppp = $postperpage;
    } else {
        $ppp = is_numeric($ppp) ? (int) $ppp : $postperpage;
    }

    if ($ppp < 5) {
        $ppp = 30;
    }
}

function validateTpp() {
    global $tpp, $topicperpage;

    if (!isset($tpp) || $tpp == '') {
        $tpp = $topicperpage;
    } else {
        $tpp = is_numeric($tpp) ? (int) $tpp : $topicperpage;
    }

    if ($tpp < 5) {
        $tpp = 30;
    }
}

function altMail($to, $subject, $message, $additional_headers='', $additional_parameters=null) {
    global $mailer, $SETTINGS;
    static $handlers;

    $message = str_replace(array("\r\n", "\r", "\n"), array("\n", "\n", "\r\n"), $message);
    $subject = str_replace(array("\r", "\n"), array('', ''), $subject);

    if ($mailer['type'] == 'socket_SMTP') {
        require_once(ROOT.'include/smtp.inc.php');

        if (!isset($handlers['socket_SMTP'])) {
            if (DEBUG) {
                $mail = new socket_SMTP(true, './smtp-log.txt');
            } else {
                $mail = new socket_SMTP;
            }
            $handlers['socket_SMTP'] = &$mail;
            if (!$mail->connect($mailer['host'], $mailer['port'], $mailer['username'], $mailer['password'])) {
                return FALSE;
            }
            register_shutdown_function(array(&$mail, 'disconnect'));
        } else {
            $mail = &$handlers['socket_SMTP'];
            if (FALSE === $mail->connection) {
                return FALSE;
            }
        }

        $subjectInHeader = false;
        $toInHeader = false;
        $additional_headers = explode("\r\n", $additional_headers);
        foreach($additional_headers as $k=>$h) {
            if (strpos(trim($h), 'ubject:') === 1) {
                $additional_headers[$k] = 'Subject: '.$subject."\r\n";
                $subjectInHeader = true;
                continue;
            }

            if (strpos(trim(strtolower($h)), 'to:') === 0) {
                $toInHeader = true;
            }
        }

        if (!$subjectInHeader) {
            $additional_headers[] = 'Subject: '.$subject;
        }

        if (!$toInHeader) {
            $additional_headers[] = 'To: '.$to;
        }

        $additional_headers = implode("\r\n", $additional_headers);

        return $mail->sendMessage($SETTINGS['adminemail'], $to, $message, $additional_headers);
    } else {
        if (PHP_OS == 'WINNT' Or PHP_OS == 'WIN32') {  // Official XMB hack for PHP bug #45305 a.k.a. #28038
            ini_set('sendmail_from', ini_get('sendmail_from'));
        }
        if (ini_get('safe_mode') == "1") {
            $return = mail($to, $subject, $message, $additional_headers);
        } else {
            $return = mail($to, $subject, $message, $additional_headers, $additional_parameters);
        }
        if (!$return) {
            $msg = 'XMB failed to send an e-mail because the PHP mail() function returned FALSE!  This might be caused by using an invalid address in XMB\'s Administrator E-Mail setting.';
            trigger_error($msg, E_USER_WARNING);
        }
        return $return;
    }
}

function shortenString($string, $len=100, $shortType=X_SHORTEN_SOFT, $ps='...') {
    if (strlen($string) > $len) {
        if (($shortType & X_SHORTEN_SOFT) === X_SHORTEN_SOFT) {
            $string = preg_replace('#^(.{0,'.$len.'})([\W].*)#', '\1'.$ps, $string);
        }

        if ((strlen($string) > $len+strlen($ps)) && (($shortType & X_SHORTEN_HARD) === X_SHORTEN_HARD)) {
            $string = substr($string, 0, $len).$ps;
        }
        return $string;
    } else {
        return $string;
    }
}

function printGmDate($timestamp=null, $altFormat=null, $altOffset=0) {
    global $dateformat, $SETTINGS, $timeoffset, $addtime;

    if ($timestamp === null) {
        $timestamp = time();
    }

    if ($altFormat === null) {
        $altFormat = $dateformat;
    }

    $f = false;
    if ((($pos = strpos($altFormat, 'F')) !== false && $f = true) || ($pos2 = strpos($altFormat, 'M')) !== false) {
        $startStr = substr($altFormat, 0, $pos);
        $endStr = substr($altFormat, $pos+1);
        $month = gmdate('m', $timestamp + ($timeoffset*3600)+(($altOffset+$addtime)*3600));
        $textM = month2text($month);
        return printGmDate($timestamp, $startStr, $altOffset).substr($textM,0, ($f ? strlen($textM) : 3)).printGmDate($timestamp, $endStr, $altOffset);
    } else {
        return gmdate($altFormat, $timestamp + ($timeoffset * 3600) + (($altOffset+$addtime) * 3600));
    }
}

function printGmTime($timestamp=null, $altFormat=null, $altOffset=0) {
    global $self, $SETTINGS, $timeoffset, $addtime, $timecode;

    if ($timestamp === null) {
        $timestamp = time();
    }

    if ($altFormat !== null) {
        return gmdate($altFormat, $timestamp + ($timeoffset * 3600) + (($altOffset+$addtime) * 3600));
    } else {
        return gmdate($timecode, $timestamp + ($timeoffset * 3600) + (($altOffset+$addtime) * 3600));
    }
}

function MakeTime() {
   $objArgs = func_get_args();
   $nCount = count($objArgs);
   if ($nCount < 7) {
       if ($nCount < 1) {
           $objArgs[] = intval(gmdate('H'));
       } else if ($nCount < 2) {
           $objArgs[] = intval(gmdate('i'));
       } else if ($nCount < 3) {
           $objArgs[] = intval(gmdate('s'));
       } else if ($nCount < 4) {
           $objArgs[] = intval(gmdate('n'));
       } else if ($nCount < 5) {
           $objArgs[] = intval(gmdate('j'));
       } else if ($nCount < 6) {
           $objArgs[] = intval(gmdate('Y'));
       }
   }

   $nYear = $objArgs[5];
   $nOffset = 0;
   if ($nYear < 1970) {
       if ($nYear < 1902) {
           return 0;
       } else if ($nYear < 1952) {
           $nOffset = -2650838400;
           $objArgs[5] += 84;
       } else {
           $nOffset = -883612800;
           $objArgs[5] += 28;
       }
   }

   return call_user_func_array("gmmktime", $objArgs) + $nOffset;
}

function iso8601_date($year=0, $month=0, $day=0) {
    $year = (int) $year;
    $month = (int) $month;
    $day = (int) $day;

    if ($year < 1 || $month < 1 || $day < 1) {
        return '0000-00-00';
    }

    if ($year < 100) {
        $year += 1900;
    }

    if ($month > 12 || $month < 1) {
        $month = 1;
    }

    if ($day > 31 || $day < 1) {
        $day = 1;
    }

    return $year.'-'.str_pad($month, 2, 0, STR_PAD_LEFT).'-'.str_pad($day, 2, 0, STR_PAD_LEFT);
}

function month2text($num) {
    global $lang;

    $num = (int) $num;
    if ($num < 1 || $num > 12) {
        $num = 1;
    }

    $months = array(
        $lang['textjan'],
        $lang['textfeb'],
        $lang['textmar'],
        $lang['textapr'],
        $lang['textmay'],
        $lang['textjun'],
        $lang['textjul'],
        $lang['textaug'],
        $lang['textsep'],
        $lang['textoct'],
        $lang['textnov'],
        $lang['textdec']
    );

    return $months[$num-1];
}

/**
 * Creates a db query result containing all active forums and forum categories.
 *
 * Important: The return value is passed by reference.  There is only one query object.  This cannot be used in nested functions.
 *
 * @return object
 */
function forumCache() {
    global $db;
    static $cache = FALSE;

    if ($cache === FALSE) {
        $cache = $db->query("SELECT f.* FROM ".X_PREFIX."forums f WHERE f.status='on' ORDER BY f.displayorder ASC");
    }

    if ($cache !== FALSE) {
        if ($db->num_rows($cache) > 0) {
            $db->data_seek($cache, 0);  // Restores the pointer for fetch_array().
        }
    }

    return $cache;
}

/**
 * Creates an associative array for the specified forum.
 */
function getForum($fid) {
    global $db;

    $forums = forumCache();
    while($forum = $db->fetch_array($forums)) {
        if (intval($forum['fid']) == intval($fid)) {
            return $forum;
        }
    }
    return FALSE;
}

/**
 * Creates a multi-dimensional array of forums.
 *
 * The array uses the following associative subscripts:
 *  0:forums.type
 *  1:forums.fup (always '0' for groups)
 *  2:forums.fid
 *  3:forums.*
 * Usage example:
 *  $forums = getStructuredForums();
 *  echo fnameOut($forums['forum']['9']['14']['name']);
 *
 * @param bool $usePerms If TRUE then not all forums are returned, only visible forums.
 * @return array
 */
function getStructuredForums($usePerms=FALSE) {
    global $db;

    if ($usePerms) {
        $forums = permittedForums(forumCache(), 'forum');
    } else {
        $forums = array();
        $query = forumCache();
        while($forum = $db->fetch_array($query)) {
            $forums[] = $forum;
        }
    }

    // This function guarantees the following subscripts exist, regardless of forum count.
    $structured['group'] = array();
    $structured['forum'] = array();
    $structured['sub'] = array();
    $structured['group']['0'] = array();
    $structured['forum']['0'] = array();

    foreach($forums as $forum) {
        $structured[$forum['type']][$forum['fup']][$forum['fid']] = $forum;
    }

    return $structured;
}

/**
 * Creates an array of permitted forum arrays.
 *
 * @param object $forums DB query result, preferably from forumCache().
 * @param string $mode Whether to check for 'forum' listing permissions or 'thread' listing permissions.
 * @param string $output If set to 'csv' causes the return value to be a CSV string of permitted forum IDs instead of an 'array' of arrays.
 * @param bool $check_parents Indicates whether each forum's permissions depend on the parent forum also being permitted.
 * @param bool $user_status Optional masquerade value passed to checkForumPermissions().
 * @return array
 */
function permittedForums($forums, $mode='thread', $output='array', $check_parents=TRUE, $user_status=FALSE) {
    global $db, $SETTINGS;

    $permitted = array();
    $fids['group'] = array();
    $fids['forum'] = array();
    $fids['sub'] = array();

    while($forum = $db->fetch_array($forums)) {
        $perms = checkForumPermissions($forum, $user_status);
        if ($mode == 'thread') {
            if ($forum['type'] == 'group' || ($perms[X_PERMS_VIEW] && $perms[X_PERMS_PASSWORD])) {
                $permitted[] = $forum;
                $fids[$forum['type']][] = $forum['fid'];
            }
        } elseif ($mode == 'forum') {
            if ($SETTINGS['hideprivate'] == 'off' || $forum['type'] == 'group' || $perms[X_PERMS_VIEW]) {
                $permitted[] = $forum;
                $fids[$forum['type']][] = $forum['fid'];
            }
        }
    }

    if ($check_parents) { // Use the $fids array to see if each forum's parent is permitted.
        $filtered = array();
        $fids['forum'] = array();
        $fids['sub'] = array();
        foreach($permitted as $forum) {
            if ($forum['type'] == 'group') {
                $filtered[] = $forum;
            } elseif ($forum['type'] == 'forum') {
                if (intval($forum['fup']) == 0) {
                    $filtered[] = $forum;
                    $fids['forum'][] = $forum['fid'];
                } elseif (array_search($forum['fup'], $fids['group']) !== FALSE) {
                    $filtered[] = $forum;
                    $fids['forum'][] = $forum['fid'];
                }
            }
        }

        foreach($permitted as $forum) {
            if ($forum['type'] == 'sub') {
                if (intval($forum['fup']) == 0) {
                    $filtered[] = $forum;
                    $fids['sub'][] = $forum['fid'];
                } elseif (array_search($forum['fup'], $fids['forum']) !== FALSE) {
                    $filtered[] = $forum;
                    $fids['sub'][] = $forum['fid'];
                }
            }
        }

        $permitted = $filtered;
    }

    if ($output == 'csv') {
        $permitted = implode(', ', array_merge($fids['group'], $fids['forum'], $fids['sub']));
    }

    return $permitted;
}

function forumList($selectname='srchfid', $multiple=false, $allowall=true, $currentfid=0) {
    global $lang;

    // Initialize $forumselect
    $forumselect = array();
    if (!$multiple) {
        $forumselect[] = '<select name="'.$selectname.'">';
    } else {
        $forumselect[] = '<select name="'.$selectname.'[]" size="10" multiple="multiple">';
    }

    if ($allowall) {
        if ($currentfid == 0) {
            $forumselect[] = '<option value="all" selected="selected">'.$lang['textallforumsandsubs'].'</option>';
        } else {
            $forumselect[] = '<option value="all">'.$lang['textallforumsandsubs'].'</option>';
        }
    } else if (!$allowall && !$multiple) {
        $forumselect[] = '<option value="" disabled="disabled" selected="selected">'.$lang['textforum'].'</option>';
    }

    // Populate $forumselect
    $permitted = getStructuredForums(TRUE);

    foreach($permitted['forum']['0'] as $forum) {
        $forumselect[] = '<option value="'.intval($forum['fid']).'"'.($forum['fid'] == $currentfid ? ' selected="selected"' : '').'> &nbsp; &raquo; '.fnameOut($forum['name']).'</option>';
        if (isset($permitted['sub'][$forum['fid']])) {
            foreach($permitted['sub'][$forum['fid']] as $sub) {
                $forumselect[] = '<option value="'.intval($sub['fid']).'"'.($sub['fid'] == $currentfid ? ' selected="selected"' : '').'>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &raquo; '.fnameOut($sub['name']).'</option>';
            }
        }
    }

    $forumselect[] = '<option value="0" disabled="disabled">&nbsp;</option>';
    foreach($permitted['group']['0'] as $group) {
        if (isset($permitted['forum'][$group['fid']]) && count($permitted['forum'][$group['fid']]) > 0) {
            $forumselect[] = '<option value="'.intval($group['fid']).'" disabled="disabled">'.fnameOut($group['name']).'</option>';
            foreach($permitted['forum'][$group['fid']] as $forum) {
                $forumselect[] = '<option value="'.intval($forum['fid']).'"'.($forum['fid'] == $currentfid ? ' selected="selected"' : '').'> &nbsp; &raquo; '.fnameOut($forum['name']).'</option>';
                if (isset($permitted['sub'][$forum['fid']])) {
                    foreach($permitted['sub'][$forum['fid']] as $sub) {
                        $forumselect[] = '<option value="'.intval($sub['fid']).'"'.($sub['fid'] == $currentfid ? ' selected="selected"' : '').'>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &raquo; '.fnameOut($sub['name']).'</option>';
                    }
                }
            }
        }
        $forumselect[] = '<option value="" disabled="disabled">&nbsp;</option>';
    }
    $forumselect[] = '</select>';
    return implode("\n", $forumselect);
}

function forumJump() {
    global $fid, $lang, $selHTML;

    // Initialize $forumselect
    $forumselect = array();
    $checkid = max($fid, getInt('gid', 'r'));

    $forumselect[] = "<select onchange=\"if (this.options[this.selectedIndex].value) {window.location=(''+this.options[this.selectedIndex].value)}\">";
    $forumselect[] = '<option value="">'.$lang['forumjumpselect'].'</option>';

    // Populate $forumselect
    $permitted = getStructuredForums(TRUE);

    if (0 == count($permitted['group']['0']) and 0 == count($permitted['forum']['0'])) {
        return '';
    }

    foreach($permitted['forum']['0'] as $forum) {
        $dropselc1 = ( $checkid == $forum['fid'] ) ? $selHTML : '';
        $forumselect[] = '<option value="forumdisplay.php?fid='.intval($forum['fid']).'" '.$dropselc1.'> &nbsp; &raquo; '.fnameOut($forum['name']).'</option>';
        if (isset($permitted['sub'][$forum['fid']])) {
            foreach($permitted['sub'][$forum['fid']] as $sub) {
                $dropselc2 = ( $checkid == $sub['fid'] ) ? $selHTML : '';
                $forumselect[] = '<option value="forumdisplay.php?fid='.intval($sub['fid']).'" '.$dropselc2.'>&nbsp; &nbsp; &raquo; '.fnameOut($sub['name']).'</option>';
            }
        }
    }

    foreach($permitted['group']['0'] as $group) {
        if (isset($permitted['forum'][$group['fid']])) {
            $dropselc3 = ( $checkid == $group['fid'] ) ? $selHTML : '';
            $forumselect[] = '<option value=""></option>';
            $forumselect[] = '<option value="index.php?gid='.intval($group['fid']).'" '.$dropselc3.'>'.fnameOut($group['name']).'</option>';
            foreach($permitted['forum'][$group['fid']] as $forum) {
                $dropselc4 = ( $checkid == $forum['fid'] ) ? $selHTML : '';
                $forumselect[] = '<option value="forumdisplay.php?fid='.intval($forum['fid']).'" '.$dropselc4.'> &nbsp; &raquo; '.fnameOut($forum['name']).'</option>';
                if (isset($permitted['sub'][$forum['fid']])) {
                    foreach($permitted['sub'][$forum['fid']] as $sub) {
                        $dropselc5 = ( $checkid == $sub['fid'] ) ? $selHTML : '';
                        $forumselect[] = '<option value="forumdisplay.php?fid='.intval($sub['fid']).'" '.$dropselc5.'>&nbsp; &nbsp; &raquo; '.fnameOut($sub['name']).'</option>';
                    }
                }
            }
        }
    }
    $forumselect[] = '</select>';
    return implode("\n", $forumselect);
}

/**
 * Creates a set of boolean permissions for a specific forum.
 *
 * Normal Usage Example
 *  $fid = 1;
 *  $forum = getForum($fid);
 *  $perms = checkForumPermissions($forum);
 *  if ($perms[X_PERMS_VIEW]) { //$self is allowed to view $forum }
 * Masquerade Example
 *  $result = $db->query('SELECT * FROM '.X_PREFIX.'members WHERE uid=1');
 *  $user = $db->fetch_array($result);
 *  $perms = checkForumPermissions($forum, $user['status']);
 *  if ($perms[X_PERMS_VIEW]) { //$user is allowed to view $forum }
 * Masquerade Example 2
 *  $perms = checkForumPermissions($forum, 'Moderator');
 *  if ($perms[X_PERMS_VIEW]) { //Moderators are allowed to view $forum }
 *
 * @param array $forum One query row from the forums table, preferably provided by getForum().
 * @param string $user_status_in Optional. Masquerade as this user status, e.g. 'Guest'
 * @return array Of bools, indexed by X_PERMS_* constants.
 */
function checkForumPermissions($forum, $user_status_in=FALSE) {
    global $self, $status_enum;

    if (is_string($user_status_in)) {
        $user_status = $status_enum[$user_status_in];
    } else {
        $user_status = $status_enum[$self['status']];
    }

    // 1. Initialize $ret with zero permissions
    $ret = array_fill(0, X_PERMS_COUNT, FALSE);
    $ret[X_PERMS_POLL] = FALSE;
    $ret[X_PERMS_THREAD] = FALSE;
    $ret[X_PERMS_REPLY] = FALSE;
    $ret[X_PERMS_VIEW] = FALSE;
    $ret[X_PERMS_USERLIST] = FALSE;
    $ret[X_PERMS_PASSWORD] = FALSE;

    // 2. Check Forum Postperm
    $pp = explode(',', $forum['postperm']);
    foreach($pp as $key=>$val) {
        if ((intval($val) & $user_status) != 0) {
            $ret[$key] = TRUE;
        }
    }

    // 3. Check Forum Userlist
    if ($user_status_in === FALSE) {
        $userlist = $forum['userlist'];

        if (modcheck($self['username'], $forum['moderator'], FALSE) == "Moderator") {
            $ret[X_PERMS_USERLIST] = TRUE;
            $ret[X_PERMS_VIEW] = TRUE;
        } elseif (!X_GUEST) {
            $users = explode(',', $userlist);
            foreach($users as $user) {
                if (strtolower(trim($user)) == strtolower($self['username'])) {
                    $ret[X_PERMS_USERLIST] = TRUE;
                    $ret[X_PERMS_VIEW] = TRUE;
                    break;
                }
            }
        }
    }

    // 4. Set Effective Permissions
    $ret[X_PERMS_POLL]   = $ret[X_PERMS_RAWPOLL];
    $ret[X_PERMS_THREAD] = $ret[X_PERMS_RAWTHREAD];
    $ret[X_PERMS_REPLY]  = $ret[X_PERMS_RAWREPLY];
    $ret[X_PERMS_VIEW]   = $ret[X_PERMS_RAWVIEW] || $ret[X_PERMS_USERLIST];

    // 5. Check Forum Password
    $pwinput = postedVar('fidpw'.$forum['fid'], '', FALSE, FALSE, FALSE, 'c');
    if ($forum['password'] == '' Or $pwinput == $forum['password']) {
        $ret[X_PERMS_PASSWORD] = TRUE;
    }

    return $ret;
}

/**
 * Enables you to do complex comparisons without string parsing.
 *
 * Normal Usage Example
 *  $fid = 1;
 *  $forum = getForum($fid);
 *  $viewperms = getOneForumPerm($forum, X_PERMS_RAWVIEW);
 *  if ($viewperms >= $status_enum['Member']) { //Some non-staff status has perms to view $forum }
 *  if ($viewperms == $status_enum['Guest']) { //$forum is guest-only }
 *  if ($viewperms == $status_enum['Member'] - 1) { //$forum is staff-only }
 *
 * @param array $forum
 * @param int $bitfield Enumerated by X_PERMS_RAW* constants.  Other X_PERMS_* values will not work!
 * @return bool
 */
function getOneForumPerm($forum, $bitfield) {
    $pp = explode(',', $forum['postperm']);
    return $pp[$bitfield];
}

function handlePasswordDialog($fid) {
    global $db, $full_url, $url, $cookiepath, $cookiedomain;  // function vars
    global $THEME, $lang, $oToken, $altbg1, $altbg2, $tablewidth, $tablespace, $bordercolor;  // template vars

    $fid = intval($fid);
    $pwinput = postedVar('pw', '', FALSE, FALSE);

    $forum = getForum($fid);
    if (strlen($pwinput) != 0 And $forum !== FALSE) {
        if ($pwinput == $forum['password']) {
            put_cookie('fidpw'.$fid, $forum['password'], (time() + (86400*30)), $cookiepath, $cookiedomain);
            $newurl = preg_replace('/[^\x20-\x7e]/', '', $url);
            redirect($full_url.substr($newurl, strlen($cookiepath)), 0);
        } else {
            eval('$pwform = "'.template('forumdisplay_password').'";');
            error($lang['invalidforumpw'], true, '', $pwform, false, true, false, true);
        }
    } else {
        eval('$pwform = "'.template('forumdisplay_password').'";');
        error($lang['forumpwinfo'], true, '', $pwform, false, true, false, true);
    }
}

function createLangFileSelect($currentLangFile) {
    global $db;

    $lfs = array();

    $query = $db->query("SELECT b.devname, t.cdata "
                      . "FROM ".X_PREFIX."lang_base AS b "
                      . "LEFT JOIN ".X_PREFIX."lang_text AS t USING (langid) "
                      . "INNER JOIN ".X_PREFIX."lang_keys AS k USING (phraseid) "
                      . "WHERE k.langkey='language' "
                      . "ORDER BY t.cdata ASC");
    while ($row = $db->fetch_array($query)) {
        if ($row['devname'] == $currentLangFile) {
            $lfs[] = '<option value="'.$row['devname'].'" selected="selected">'.$row['cdata'].'</option>';
        } else {
            $lfs[] = '<option value="'.$row['devname'].'">'.$row['cdata'].'</option>';
        }
    }
    return '<select name="langfilenew">'.implode("\n", $lfs).'</select>';
}

/**
 * Creates an XHTML link to the forum search page.
 *
 * @param int $fid Optional. Current FID number used to create a context-sensitive search.
 * @return string Empty string if the forum search page is disabled.
 */
function makeSearchLink($fid=0) {
    global $imgdir, $lang, $SETTINGS;

    $fid = intval($fid);

    if ($SETTINGS['searchstatus'] == 'on') {
        $fid = intval($fid);
        if ($fid == 0) {
            $fid = '';
        } else {
            $fid = "?fid=$fid";
        }
        return '<img src="'.$imgdir.'/top_search.gif" alt="'.$lang['altsearch'].'" border="0" /> <a href="search.php'.$fid.'"><font class="navtd">'.$lang['textsearch'].'</font></a> &nbsp; ';
    } else {
        return '';
    }
}

/**
 * Sets an SEO variable used in the header template to indicate the proper current relative URI.
 *
 * @param string $relURI Path to the current page, relative to the base href (see header.php).
 */
function setCanonicalLink($relURI) {
    global $canonical_link, $cookiepath, $url;

    $testurl = $cookiepath;
    if ($relURI != './') {
        $testurl .= str_replace('&amp;', '&', $relURI);
    }
    if ($url != $testurl) {
        $canonical_link = '<link rel="canonical" href="'.$relURI.'" />';
    }
}

function phpShorthandValue($ininame) {
    $rawstring = trim(ini_get($ininame));
    $rchr = strtoupper(substr($rawstring, -1));
    switch ($rchr) {
    case 'G':
        $rawstring *= 1073741824;
        break;
    case 'M':
        $rawstring *= 1048576;
        break;
    case 'K':
        $rawstring *= 1024;
        break;
    default:
        $rawstring = intval($rawstring);
        break;
    }
    return $rawstring;
}

/**
 * Simple SMTP message From header formation.
 *
 * @since 1.9.11.08
 * @param string $fromname Will be converted to an SMTP quoted string.
 * @param string $fromaddress Must be a fully validated e-mail address.
 * @return string
 */
function smtpHeaderFrom($fromname, $fromaddress) {
    $fromname = preg_replace('@([^\\t !\\x23-\\x5b\\x5d-\\x7e])@', '\\\\$1', $fromname);
    return 'From: "'.$fromname.'" <'.$fromaddress.'>';
}

/**
 * Generate a nonce.
 *
 * The XMB schema is currently limited to a 12-byte key length, and as such
 * does not offer user uniqueness beyond simple randomization.
 *
 * @param string $key The known value, such as what the nonce may be used for.
 * @param string $salt A semi-secret value such as the id of the current user.
 * @return string
 */
function nonce_create($key) {
    global $db, $self;

    $key = substr($key, 0, X_NONCE_KEY_LEN);
    $db->escape_fast($key);
    $salt = isset($self['email']) ? $self['email'] : $key;
    $nonce = md5( $salt . mt_rand() );
    $time = time();
    $db->query("INSERT INTO ".X_PREFIX."captchaimages (imagehash, imagestring, dateline) VALUES ('$nonce', '$key', '$time')");

    return $nonce;
}

/**
 * Reveal the nonce/key pair to the user, as in CAPTCHA.
 *
 * @param  string $nonce The user input.
 * @param  int    $key_length The known length of the key.
 * @return string The key value.
 */
function nonce_peek($nonce, $key_length) {
    global $db;

    $key_length = (int) $key_length;
    if ($key_length >= X_NONCE_KEY_LEN) return '';  //Since the schema is so constrained, keep all the 12-byte keys secure.

    $db->escape_fast($nonce);
    $time = time() - X_NONCE_MAX_AGE;
    $result = $db->query(
        "SELECT imagestring
         FROM ".X_PREFIX."captchaimages
         WHERE imagehash='$nonce' AND dateline >= $time AND LENGTH(imagestring) = $key_length"
    );
    if ($db->num_rows($result) === 1) {
        return $db->result($result, 0);
    }
    return '';
}

/**
 * Test a nonce.
 *
 * @param string $key The same value used in nonce_create().
 * @param string $nonce The user input.
 * @param int    $expire Optional. Number of seconds for which any nonce having the same $key will be valid.
 * @return bool True only if the user provided a unique nonce for the key/nonce pair.
 */
function nonce_use($key, $nonce, $expire = 0) {
    global $db;

    $key = substr($key, 0, X_NONCE_KEY_LEN);
    $db->escape_fast($key);
    $db->escape_fast($nonce);
    $time = time() - X_NONCE_MAX_AGE;
    $sql_expire = "dateline < $time";
    if ($expire > 0 and $expire < X_NONCE_MAX_AGE) {
        $time = time() - $expire;
        $sql_expire .= " OR imagestring='$key' AND dateline < $time";
    }
    $db->query("DELETE FROM ".X_PREFIX."captchaimages WHERE $sql_expire");
    $db->query("DELETE FROM ".X_PREFIX."captchaimages WHERE imagehash='$nonce' AND imagestring='$key'");

    return ($db->affected_rows() === 1);
}

return;
?>
