Ignore:
Timestamp:
Nov 14, 2011, 11:17:15 PM (13 years ago)
Author:
dj3c1t
Message:

passage a Fluxbb 1.4.7

File:
1 edited

Legend:

Unmodified
Added
Removed
  • branches/rsr.v5.1.dev/web/punbb/include/functions.php

    r1 r3  
    11<?php
    2 /***********************************************************************
    3 
    4   Copyright (C) 2002-2005  Rickard Andersson (rickard@punbb.org)
    5 
    6   This file is part of PunBB.
    7 
    8   PunBB is free software; you can redistribute it and/or modify it
    9   under the terms of the GNU General Public License as published
    10   by the Free Software Foundation; either version 2 of the License,
    11   or (at your option) any later version.
    12 
    13   PunBB is distributed in the hope that it will be useful, but
    14   WITHOUT ANY WARRANTY; without even the implied warranty of
    15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    16   GNU General Public License for more details.
    17 
    18   You should have received a copy of the GNU General Public License
    19   along with this program; if not, write to the Free Software
    20   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
    21   MA  02111-1307  USA
    22 
    23 ************************************************************************/
     2
     3/**
     4 * Copyright (C) 2008-2011 FluxBB
     5 * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
     6 * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
     7 */
     8
     9
     10//
     11// Return current timestamp (with microseconds) as a float
     12//
     13function get_microtime()
     14{
     15        list($usec, $sec) = explode(' ', microtime());
     16        return ((float)$usec + (float)$sec);
     17}
    2418
    2519//
     
    2822function check_cookie(&$pun_user)
    2923{
    30         global $db, $pun_config, $cookie_name, $cookie_seed;
     24        global $db, $db_type, $pun_config, $cookie_name, $cookie_seed;
    3125
    3226        $now = time();
    33         $expire = $now + 31536000;      // The cookie expires after a year
    34 
    35         // We assume it's a guest
    36         $cookie = array('user_id' => 1, 'password_hash' => 'Invité');
    37 
    38         // If a cookie is set, we get the user_id and password hash from it
    39         if (isset($_COOKIE[$cookie_name]))
    40                 list($cookie['user_id'], $cookie['password_hash']) = @unserialize($_COOKIE[$cookie_name]);
    41 
    42         if ($cookie['user_id'] > 1)
    43         {
     27
     28        // If the cookie is set and it matches the correct pattern, then read the values from it
     29        if (isset($_COOKIE[$cookie_name]) && preg_match('%^(\d+)\|([0-9a-fA-F]+)\|(\d+)\|([0-9a-fA-F]+)$%', $_COOKIE[$cookie_name], $matches))
     30        {
     31                $cookie = array(
     32                        'user_id'                       => intval($matches[1]),
     33                        'password_hash'         => $matches[2],
     34                        'expiration_time'       => intval($matches[3]),
     35                        'cookie_hash'           => $matches[4],
     36                );
     37        }
     38
     39        // If it has a non-guest user, and hasn't expired
     40        if (isset($cookie) && $cookie['user_id'] > 1 && $cookie['expiration_time'] > $now)
     41        {
     42                // If the cookie has been tampered with
     43                if (forum_hmac($cookie['user_id'].'|'.$cookie['expiration_time'], $cookie_seed.'_cookie_hash') != $cookie['cookie_hash'])
     44                {
     45                        $expire = $now + 31536000; // The cookie expires after a year
     46                        pun_setcookie(1, pun_hash(uniqid(rand(), true)), $expire);
     47                        set_default_user();
     48
     49                        return;
     50                }
     51
    4452                // Check if there's a user with the user ID and password hash from the cookie
    45         $_query = 'SELECT u.*, g.*, o.logged, o.idle FROM '.$db->prefix.'users AS u INNER JOIN '.$db->prefix.'groups AS g ON u.group_id=g.g_id LEFT JOIN '.$db->prefix.'online AS o ON o.user_id=u.id WHERE u.id='.intval($cookie['user_id']);
    46                 $result = $db->query($_query) or error('Impossible de retrouver les informations utilisateur', __FILE__, __LINE__, $db->error());
     53                $result = $db->query('SELECT u.*, g.*, o.logged, o.idle FROM '.$db->prefix.'users AS u INNER JOIN '.$db->prefix.'groups AS g ON u.group_id=g.g_id LEFT JOIN '.$db->prefix.'online AS o ON o.user_id=u.id WHERE u.id='.intval($cookie['user_id'])) or error('Unable to fetch user information', __FILE__, __LINE__, $db->error());
    4754                $pun_user = $db->fetch_assoc($result);
    4855
    4956                // If user authorisation failed
    50                 if (!isset($pun_user['id']) || md5($cookie_seed.$pun_user['password']) !== $cookie['password_hash'])
    51                 {
    52                         pun_setcookie(0, random_pass(8), $expire);
     57                if (!isset($pun_user['id']) || forum_hmac($pun_user['password'], $cookie_seed.'_password_hash') !== $cookie['password_hash'])
     58                {
     59                        $expire = $now + 31536000; // The cookie expires after a year
     60                        pun_setcookie(1, pun_hash(uniqid(rand(), true)), $expire);
    5361                        set_default_user();
    5462
     
    5664                }
    5765
     66                // Send a new, updated cookie with a new expiration timestamp
     67                $expire = ($cookie['expiration_time'] > $now + $pun_config['o_timeout_visit']) ? $now + 1209600 : $now + $pun_config['o_timeout_visit'];
     68                pun_setcookie($pun_user['id'], $pun_user['password'], $expire);
     69
    5870                // Set a default language if the user selected language no longer exists
    59                 if (!$pun_user['language'] || !@file_exists(PUN_ROOT.'lang/'.$pun_user['language']))
     71                if (!file_exists(PUN_ROOT.'lang/'.$pun_user['language']))
    6072                        $pun_user['language'] = $pun_config['o_default_lang'];
    6173
    6274                // Set a default style if the user selected style no longer exists
    63                 if (!@file_exists(PUN_ROOT.'style/'.$pun_user['style'].'.css'))
     75                if (!file_exists(PUN_ROOT.'style/'.$pun_user['style'].'.css'))
    6476                        $pun_user['style'] = $pun_config['o_default_style'];
    6577
     
    6981                        $pun_user['disp_posts'] = $pun_config['o_disp_posts_default'];
    7082
    71                 if ($pun_user['save_pass'] == '0')
    72                         $expire = 0;
    73 
    7483                // Define this if you want this visit to affect the online list and the users last visit data
    7584                if (!defined('PUN_QUIET_VISIT'))
     
    7786                        // Update the online list
    7887                        if (!$pun_user['logged'])
    79                                 $db->query('INSERT INTO '.$db->prefix.'online (user_id, ident, logged) VALUES('.$pun_user['id'].', \''.$db->escape($pun_user['username']).'\', '.$now.')') or error('Impossbile d\'insérer dans la liste des utilisateurs en ligne', __FILE__, __LINE__, $db->error());
     88                        {
     89                                $pun_user['logged'] = $now;
     90
     91                                // With MySQL/MySQLi/SQLite, REPLACE INTO avoids a user having two rows in the online table
     92                                switch ($db_type)
     93                                {
     94                                        case 'mysql':
     95                                        case 'mysqli':
     96                                        case 'mysql_innodb':
     97                                        case 'mysqli_innodb':
     98                                        case 'sqlite':
     99                                                $db->query('REPLACE INTO '.$db->prefix.'online (user_id, ident, logged) VALUES('.$pun_user['id'].', \''.$db->escape($pun_user['username']).'\', '.$pun_user['logged'].')') or error('Unable to insert into online list', __FILE__, __LINE__, $db->error());
     100                                                break;
     101
     102                                        default:
     103                                                $db->query('INSERT INTO '.$db->prefix.'online (user_id, ident, logged) SELECT '.$pun_user['id'].', \''.$db->escape($pun_user['username']).'\', '.$pun_user['logged'].' WHERE NOT EXISTS (SELECT 1 FROM '.$db->prefix.'online WHERE user_id='.$pun_user['id'].')') or error('Unable to insert into online list', __FILE__, __LINE__, $db->error());
     104                                                break;
     105                                }
     106
     107                                // Reset tracked topics
     108                                set_tracked_topics(null);
     109                        }
    80110                        else
    81111                        {
     
    83113                                if ($pun_user['logged'] < ($now-$pun_config['o_timeout_visit']))
    84114                                {
    85                                         $db->query('UPDATE '.$db->prefix.'users SET last_visit='.$pun_user['logged'].' WHERE id='.$pun_user['id']) or error('Impossible de mettre à jour les données de visite de l\'utilisateur', __FILE__, __LINE__, $db->error());
     115                                        $db->query('UPDATE '.$db->prefix.'users SET last_visit='.$pun_user['logged'].' WHERE id='.$pun_user['id']) or error('Unable to update user visit data', __FILE__, __LINE__, $db->error());
    86116                                        $pun_user['last_visit'] = $pun_user['logged'];
    87117                                }
    88118
    89119                                $idle_sql = ($pun_user['idle'] == '1') ? ', idle=0' : '';
    90                                 $db->query('UPDATE '.$db->prefix.'online SET logged='.$now.$idle_sql.' WHERE user_id='.$pun_user['id']) or error('Impossible de mettre à jour la liste des utilisateurs en ligne', __FILE__, __LINE__, $db->error());
     120                                $db->query('UPDATE '.$db->prefix.'online SET logged='.$now.$idle_sql.' WHERE user_id='.$pun_user['id']) or error('Unable to update online list', __FILE__, __LINE__, $db->error());
     121
     122                                // Update tracked topics with the current expire time
     123                                if (isset($_COOKIE[$cookie_name.'_track']))
     124                                        forum_setcookie($cookie_name.'_track', $_COOKIE[$cookie_name.'_track'], $now + $pun_config['o_timeout_visit']);
    91125                        }
    92126                }
     127                else
     128                {
     129                        if (!$pun_user['logged'])
     130                                $pun_user['logged'] = $pun_user['last_visit'];
     131                }
    93132
    94133                $pun_user['is_guest'] = false;
     134                $pun_user['is_admmod'] = $pun_user['g_id'] == PUN_ADMIN || $pun_user['g_moderator'] == '1';
    95135        }
    96136        else
     
    100140
    101141//
     142// Converts the CDATA end sequence ]]> into ]]&gt;
     143//
     144function escape_cdata($str)
     145{
     146        return str_replace(']]>', ']]&gt;', $str);
     147}
     148
     149
     150//
     151// Authenticates the provided username and password against the user database
     152// $user can be either a user ID (integer) or a username (string)
     153// $password can be either a plaintext password or a password hash including salt ($password_is_hash must be set accordingly)
     154//
     155function authenticate_user($user, $password, $password_is_hash = false)
     156{
     157        global $db, $pun_user;
     158
     159        // Check if there's a user matching $user and $password
     160        $result = $db->query('SELECT u.*, g.*, o.logged, o.idle FROM '.$db->prefix.'users AS u INNER JOIN '.$db->prefix.'groups AS g ON g.g_id=u.group_id LEFT JOIN '.$db->prefix.'online AS o ON o.user_id=u.id WHERE '.(is_int($user) ? 'u.id='.intval($user) : 'u.username=\''.$db->escape($user).'\'')) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
     161        $pun_user = $db->fetch_assoc($result);
     162
     163        if (!isset($pun_user['id']) ||
     164                ($password_is_hash && $password != $pun_user['password']) ||
     165                (!$password_is_hash && pun_hash($password) != $pun_user['password']))
     166                set_default_user();
     167        else
     168                $pun_user['is_guest'] = false;
     169}
     170
     171
     172//
     173// Try to determine the current URL
     174//
     175function get_current_url($max_length = 0)
     176{
     177        $protocol = get_current_protocol();
     178        $port = (isset($_SERVER['SERVER_PORT']) && (($_SERVER['SERVER_PORT'] != '80' && $protocol == 'http') || ($_SERVER['SERVER_PORT'] != '443' && $protocol == 'https')) && strpos($_SERVER['HTTP_HOST'], ':') === false) ? ':'.$_SERVER['SERVER_PORT'] : '';
     179
     180        $url = urldecode($protocol.'://'.$_SERVER['HTTP_HOST'].$port.$_SERVER['REQUEST_URI']);
     181
     182        if (strlen($url) <= $max_length || $max_length == 0)
     183                return $url;
     184
     185        // We can't find a short enough url
     186        return null;
     187}
     188
     189
     190//
     191// Fetch the current protocol in use - http or https
     192//
     193function get_current_protocol()
     194{
     195        $protocol = 'http';
     196
     197        // Check if the server is claiming to using HTTPS
     198        if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
     199                $protocol = 'https';
     200
     201        // If we are behind a reverse proxy try to decide which protocol it is using
     202        if (defined('FORUM_BEHIND_REVERSE_PROXY'))
     203        {
     204                // Check if we are behind a Microsoft based reverse proxy
     205                if (!empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) != 'off')
     206                        $protocol = 'https';
     207
     208                // Check if we're behind a "proper" reverse proxy, and what protocol it's using
     209                if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']))
     210                        $protocol = strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']);
     211        }
     212
     213        return $protocol;
     214}
     215
     216//
     217// Fetch the base_url, optionally support HTTPS and HTTP
     218//
     219function get_base_url($support_https = false)
     220{
     221        global $pun_config;
     222        static $base_url;
     223
     224        if (!$support_https)
     225                return $pun_config['o_base_url'];
     226
     227        if (!isset($base_url))
     228        {
     229                // Make sure we are using the correct protocol
     230                $base_url = str_replace(array('http://', 'https://'), get_current_protocol().'://', $pun_config['o_base_url']);
     231        }
     232
     233        return $base_url;
     234}
     235
     236
     237//
    102238// Fill $pun_user with default values (for guests)
    103239//
    104240function set_default_user()
    105241{
    106         global $db, $pun_user, $pun_config;
     242        global $db, $db_type, $pun_user, $pun_config;
    107243
    108244        $remote_addr = get_remote_address();
    109245
    110246        // Fetch guest user
    111         $result = $db->query('SELECT u.*, g.*, o.logged FROM '.$db->prefix.'users AS u INNER JOIN '.$db->prefix.'groups AS g ON u.group_id=g.g_id LEFT JOIN '.$db->prefix.'online AS o ON o.ident=\''.$remote_addr.'\' WHERE u.id=1') or error('Impossible de retrouver les informations d\'invité', __FILE__, __LINE__, $db->error());
     247        $result = $db->query('SELECT u.*, g.*, o.logged, o.last_post, o.last_search FROM '.$db->prefix.'users AS u INNER JOIN '.$db->prefix.'groups AS g ON u.group_id=g.g_id LEFT JOIN '.$db->prefix.'online AS o ON o.ident=\''.$remote_addr.'\' WHERE u.id=1') or error('Unable to fetch guest information', __FILE__, __LINE__, $db->error());
    112248        if (!$db->num_rows($result))
    113                 exit('Impossible de retrouver les informations invité. La table \''.$db->prefix.'users\' doit contenir une entrée avec un id = 1 qui représente les utilisateurs anonymes.');
     249                exit('Unable to fetch guest information. Your database must contain both a guest user and a guest user group.');
    114250
    115251        $pun_user = $db->fetch_assoc($result);
     
    117253        // Update online list
    118254        if (!$pun_user['logged'])
    119                 $db->query('INSERT INTO '.$db->prefix.'online (user_id, ident, logged) VALUES(1, \''.$db->escape($remote_addr).'\', '.time().')') or error('Impossbile d\'insérer dans la liste des utilisateurs en ligne', __FILE__, __LINE__, $db->error());
     255        {
     256                $pun_user['logged'] = time();
     257
     258                // With MySQL/MySQLi/SQLite, REPLACE INTO avoids a user having two rows in the online table
     259                switch ($db_type)
     260                {
     261                        case 'mysql':
     262                        case 'mysqli':
     263                        case 'mysql_innodb':
     264                        case 'mysqli_innodb':
     265                        case 'sqlite':
     266                                $db->query('REPLACE INTO '.$db->prefix.'online (user_id, ident, logged) VALUES(1, \''.$db->escape($remote_addr).'\', '.$pun_user['logged'].')') or error('Unable to insert into online list', __FILE__, __LINE__, $db->error());
     267                                break;
     268
     269                        default:
     270                                $db->query('INSERT INTO '.$db->prefix.'online (user_id, ident, logged) SELECT 1, \''.$db->escape($remote_addr).'\', '.$pun_user['logged'].' WHERE NOT EXISTS (SELECT 1 FROM '.$db->prefix.'online WHERE ident=\''.$db->escape($remote_addr).'\')') or error('Unable to insert into online list', __FILE__, __LINE__, $db->error());
     271                                break;
     272                }
     273        }
    120274        else
    121                 $db->query('UPDATE '.$db->prefix.'online SET logged='.time().' WHERE ident=\''.$db->escape($remote_addr).'\'') or error('Impossible de mettre à jour la liste des utilisateurs en ligne', __FILE__, __LINE__, $db->error());
     275                $db->query('UPDATE '.$db->prefix.'online SET logged='.time().' WHERE ident=\''.$db->escape($remote_addr).'\'') or error('Unable to update online list', __FILE__, __LINE__, $db->error());
    122276
    123277        $pun_user['disp_topics'] = $pun_config['o_disp_topics_default'];
    124278        $pun_user['disp_posts'] = $pun_config['o_disp_posts_default'];
    125         $pun_user['timezone'] = $pun_config['o_server_timezone'];
     279        $pun_user['timezone'] = $pun_config['o_default_timezone'];
     280        $pun_user['dst'] = $pun_config['o_default_dst'];
    126281        $pun_user['language'] = $pun_config['o_default_lang'];
    127282        $pun_user['style'] = $pun_config['o_default_style'];
    128283        $pun_user['is_guest'] = true;
    129 
    130 }
    131 
    132 
    133 //
    134 // Set a cookie, PunBB style!
     284        $pun_user['is_admmod'] = false;
     285}
     286
     287
     288//
     289// SHA1 HMAC with PHP 4 fallback
     290//
     291function forum_hmac($data, $key, $raw_output = false)
     292{
     293        if (function_exists('hash_hmac'))
     294                return hash_hmac('sha1', $data, $key, $raw_output);
     295
     296        // If key size more than blocksize then we hash it once
     297        if (strlen($key) > 64)
     298                $key = pack('H*', sha1($key)); // we have to use raw output here to match the standard
     299
     300        // Ensure we're padded to exactly one block boundary
     301        $key = str_pad($key, 64, chr(0x00));
     302
     303        $hmac_opad = str_repeat(chr(0x5C), 64);
     304        $hmac_ipad = str_repeat(chr(0x36), 64);
     305
     306        // Do inner and outer padding
     307        for ($i = 0;$i < 64;$i++) {
     308                $hmac_opad[$i] = $hmac_opad[$i] ^ $key[$i];
     309                $hmac_ipad[$i] = $hmac_ipad[$i] ^ $key[$i];
     310        }
     311
     312        // Finally, calculate the HMAC
     313        $hash = sha1($hmac_opad.pack('H*', sha1($hmac_ipad.$data)));
     314
     315        // If we want raw output then we need to pack the final result
     316        if ($raw_output)
     317                $hash = pack('H*', $hash);
     318
     319        return $hash;
     320}
     321
     322
     323//
     324// Set a cookie, FluxBB style!
     325// Wrapper for forum_setcookie
    135326//
    136327function pun_setcookie($user_id, $password_hash, $expire)
    137328{
    138         global $cookie_name, $cookie_path, $cookie_domain, $cookie_secure, $cookie_seed;
    139 
    140         // Enable sending of a P3P header by removing // from the following line (try this if login is failing in IE6)
    141 //      @header('P3P: CP="CUR ADM"');
    142 
    143         setcookie($cookie_name, serialize(array($user_id, md5($cookie_seed.$password_hash))), $expire, $cookie_path, $cookie_domain, $cookie_secure);
     329        global $cookie_name, $cookie_seed;
     330
     331        forum_setcookie($cookie_name, $user_id.'|'.forum_hmac($password_hash, $cookie_seed.'_password_hash').'|'.$expire.'|'.forum_hmac($user_id.'|'.$expire, $cookie_seed.'_cookie_hash'), $expire);
     332}
     333
     334
     335//
     336// Set a cookie, FluxBB style!
     337//
     338function forum_setcookie($name, $value, $expire)
     339{
     340        global $cookie_path, $cookie_domain, $cookie_secure;
     341
     342        // Enable sending of a P3P header
     343        header('P3P: CP="CUR ADM"');
     344
     345        if (version_compare(PHP_VERSION, '5.2.0', '>='))
     346                setcookie($name, $value, $expire, $cookie_path, $cookie_domain, $cookie_secure, true);
     347        else
     348                setcookie($name, $value, $expire, $cookie_path.'; HttpOnly', $cookie_domain, $cookie_secure);
    144349}
    145350
     
    152357        global $db, $pun_config, $lang_common, $pun_user, $pun_bans;
    153358
    154         // Admins aren't affected
    155         if ($pun_user['g_id'] == PUN_ADMIN || !$pun_bans)
     359        // Admins and moderators aren't affected
     360        if ($pun_user['is_admmod'] || !$pun_bans)
    156361                return;
    157362
    158         // Add a dot at the end of the IP address to prevent banned address 192.168.0.5 from matching e.g. 192.168.0.50
    159         $user_ip = get_remote_address().'.';
     363        // Add a dot or a colon (depending on IPv4/IPv6) at the end of the IP address to prevent banned address
     364        // 192.168.0.5 from matching e.g. 192.168.0.50
     365        $user_ip = get_remote_address();
     366        $user_ip .= (strpos($user_ip, '.') !== false) ? '.' : ':';
     367
    160368        $bans_altered = false;
     369        $is_banned = false;
    161370
    162371        foreach ($pun_bans as $cur_ban)
     
    165374                if ($cur_ban['expire'] != '' && $cur_ban['expire'] <= time())
    166375                {
    167                         $db->query('DELETE FROM '.$db->prefix.'bans WHERE id='.$cur_ban['id']) or error('Impossible de supprimé le bannissement expiré', __FILE__, __LINE__, $db->error());
     376                        $db->query('DELETE FROM '.$db->prefix.'bans WHERE id='.$cur_ban['id']) or error('Unable to delete expired ban', __FILE__, __LINE__, $db->error());
    168377                        $bans_altered = true;
    169378                        continue;
    170379                }
    171380
    172                 if ($cur_ban['username'] != '' && !strcasecmp($pun_user['username'], $cur_ban['username']))
    173                 {
    174                         $db->query('DELETE FROM '.$db->prefix.'online WHERE ident=\''.$db->escape($pun_user['username']).'\'') or error('Impossible de supprimer de la liste des utilisateur en ligne', __FILE__, __LINE__, $db->error());
    175                         message($lang_common['Ban message'].' '.(($cur_ban['expire'] != '') ? $lang_common['Ban message 2'].' '.strtolower(format_time($cur_ban['expire'], true)).'. ' : '').(($cur_ban['message'] != '') ? $lang_common['Ban message 3'].'<br /><br /><strong>'.pun_htmlspecialchars($cur_ban['message']).'</strong><br /><br />' : '<br /><br />').$lang_common['Ban message 4'].' <a href="mailto:'.$pun_config['o_admin_email'].'">'.$pun_config['o_admin_email'].'</a>.', true);
    176                 }
     381                if ($cur_ban['username'] != '' && utf8_strtolower($pun_user['username']) == utf8_strtolower($cur_ban['username']))
     382                        $is_banned = true;
    177383
    178384                if ($cur_ban['ip'] != '')
     
    180386                        $cur_ban_ips = explode(' ', $cur_ban['ip']);
    181387
    182                         for ($i = 0; $i < count($cur_ban_ips); ++$i)
     388                        $num_ips = count($cur_ban_ips);
     389                        for ($i = 0; $i < $num_ips; ++$i)
    183390                        {
    184                                 $cur_ban_ips[$i] = $cur_ban_ips[$i].'.';
     391                                // Add the proper ending to the ban
     392                                if (strpos($user_ip, '.') !== false)
     393                                        $cur_ban_ips[$i] = $cur_ban_ips[$i].'.';
     394                                else
     395                                        $cur_ban_ips[$i] = $cur_ban_ips[$i].':';
    185396
    186397                                if (substr($user_ip, 0, strlen($cur_ban_ips[$i])) == $cur_ban_ips[$i])
    187398                                {
    188                                         $db->query('DELETE FROM '.$db->prefix.'online WHERE ident=\''.$db->escape($pun_user['username']).'\'') or error('Impossible de supprimer de la liste des utilisateur en ligne', __FILE__, __LINE__, $db->error());
    189                                         message($lang_common['Ban message'].' '.(($cur_ban['expire'] != '') ? $lang_common['Ban message 2'].' '.strtolower(format_time($cur_ban['expire'], true)).'. ' : '').(($cur_ban['message'] != '') ? $lang_common['Ban message 3'].'<br /><br /><strong>'.pun_htmlspecialchars($cur_ban['message']).'</strong><br /><br />' : '<br /><br />').$lang_common['Ban message 4'].' <a href="mailto:'.$pun_config['o_admin_email'].'">'.$pun_config['o_admin_email'].'</a>.', true);
     399                                        $is_banned = true;
     400                                        break;
    190401                                }
    191402                        }
    192403                }
     404
     405                if ($is_banned)
     406                {
     407                        $db->query('DELETE FROM '.$db->prefix.'online WHERE ident=\''.$db->escape($pun_user['username']).'\'') or error('Unable to delete from online list', __FILE__, __LINE__, $db->error());
     408                        message($lang_common['Ban message'].' '.(($cur_ban['expire'] != '') ? $lang_common['Ban message 2'].' '.strtolower(format_time($cur_ban['expire'], true)).'. ' : '').(($cur_ban['message'] != '') ? $lang_common['Ban message 3'].'<br /><br /><strong>'.pun_htmlspecialchars($cur_ban['message']).'</strong><br /><br />' : '<br /><br />').$lang_common['Ban message 4'].' <a href="mailto:'.$pun_config['o_admin_email'].'">'.$pun_config['o_admin_email'].'</a>.', true);
     409                }
    193410        }
    194411
     
    196413        if ($bans_altered)
    197414        {
    198                 require_once PUN_ROOT.'include/cache.php';
     415                if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
     416                        require PUN_ROOT.'include/cache.php';
     417
    199418                generate_bans_cache();
    200419        }
     
    203422
    204423//
     424// Check username
     425//
     426function check_username($username, $exclude_id = null)
     427{
     428        global $db, $pun_config, $errors, $lang_prof_reg, $lang_register, $lang_common, $pun_bans;
     429
     430        // Convert multiple whitespace characters into one (to prevent people from registering with indistinguishable usernames)
     431        $username = preg_replace('%\s+%s', ' ', $username);
     432
     433        // Validate username
     434        if (pun_strlen($username) < 2)
     435                $errors[] = $lang_prof_reg['Username too short'];
     436        else if (pun_strlen($username) > 25) // This usually doesn't happen since the form element only accepts 25 characters
     437                $errors[] = $lang_prof_reg['Username too long'];
     438        else if (!strcasecmp($username, 'Guest') || !strcasecmp($username, $lang_common['Guest']))
     439                $errors[] = $lang_prof_reg['Username guest'];
     440        else if (preg_match('%[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}%', $username) || preg_match('%((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))%', $username))
     441                $errors[] = $lang_prof_reg['Username IP'];
     442        else if ((strpos($username, '[') !== false || strpos($username, ']') !== false) && strpos($username, '\'') !== false && strpos($username, '"') !== false)
     443                $errors[] = $lang_prof_reg['Username reserved chars'];
     444        else if (preg_match('%(?:\[/?(?:b|u|s|ins|del|em|i|h|colou?r|quote|code|img|url|email|list|\*|topic|post|forum|user)\]|\[(?:img|url|quote|list)=)%i', $username))
     445                $errors[] = $lang_prof_reg['Username BBCode'];
     446
     447        // Check username for any censored words
     448        if ($pun_config['o_censoring'] == '1' && censor_words($username) != $username)
     449                $errors[] = $lang_register['Username censor'];
     450
     451        // Check that the username (or a too similar username) is not already registered
     452        $query = ($exclude_id) ? ' AND id!='.$exclude_id : '';
     453
     454        $result = $db->query('SELECT username FROM '.$db->prefix.'users WHERE (UPPER(username)=UPPER(\''.$db->escape($username).'\') OR UPPER(username)=UPPER(\''.$db->escape(ucp_preg_replace('%[^\p{L}\p{N}]%u', '', $username)).'\')) AND id>1'.$query) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
     455
     456        if ($db->num_rows($result))
     457        {
     458                $busy = $db->result($result);
     459                $errors[] = $lang_register['Username dupe 1'].' '.pun_htmlspecialchars($busy).'. '.$lang_register['Username dupe 2'];
     460        }
     461
     462        // Check username for any banned usernames
     463        foreach ($pun_bans as $cur_ban)
     464        {
     465                if ($cur_ban['username'] != '' && utf8_strtolower($username) == utf8_strtolower($cur_ban['username']))
     466                {
     467                        $errors[] = $lang_prof_reg['Banned username'];
     468                        break;
     469                }
     470        }
     471}
     472
     473
     474//
    205475// Update "Users online"
    206476//
    207477function update_users_online()
    208478{
    209         global $db, $pun_config, $pun_user;
     479        global $db, $pun_config;
    210480
    211481        $now = time();
    212482
    213483        // Fetch all online list entries that are older than "o_timeout_online"
    214         $result = $db->query('SELECT * FROM '.$db->prefix.'online WHERE logged<'.($now-$pun_config['o_timeout_online'])) or error('Impossible de retrouver les anciennes entrées de la liste des utilisateurs', __FILE__, __LINE__, $db->error());
     484        $result = $db->query('SELECT user_id, ident, logged, idle FROM '.$db->prefix.'online WHERE logged<'.($now-$pun_config['o_timeout_online'])) or error('Unable to fetch old entries from online list', __FILE__, __LINE__, $db->error());
    215485        while ($cur_user = $db->fetch_assoc($result))
    216486        {
    217487                // If the entry is a guest, delete it
    218488                if ($cur_user['user_id'] == '1')
    219                         $db->query('DELETE FROM '.$db->prefix.'online WHERE ident=\''.$db->escape($cur_user['ident']).'\'') or error('Impossible de supprimer de la liste des utilisateurs en ligne', __FILE__, __LINE__, $db->error());
     489                        $db->query('DELETE FROM '.$db->prefix.'online WHERE ident=\''.$db->escape($cur_user['ident']).'\'') or error('Unable to delete from online list', __FILE__, __LINE__, $db->error());
    220490                else
    221491                {
     
    223493                        if ($cur_user['logged'] < ($now-$pun_config['o_timeout_visit']))
    224494                        {
    225                                 $db->query('UPDATE '.$db->prefix.'users SET last_visit='.$cur_user['logged'].' WHERE id='.$cur_user['user_id']) or error('Impossible de mettre à jour les données de visite de l\'utilisateur', __FILE__, __LINE__, $db->error());
    226                                 $db->query('DELETE FROM '.$db->prefix.'online WHERE user_id='.$cur_user['user_id']) or error('Impossible de supprimer de la liste des utilisateurs en ligne', __FILE__, __LINE__, $db->error());
     495                                $db->query('UPDATE '.$db->prefix.'users SET last_visit='.$cur_user['logged'].' WHERE id='.$cur_user['user_id']) or error('Unable to update user visit data', __FILE__, __LINE__, $db->error());
     496                                $db->query('DELETE FROM '.$db->prefix.'online WHERE user_id='.$cur_user['user_id']) or error('Unable to delete from online list', __FILE__, __LINE__, $db->error());
    227497                        }
    228498                        else if ($cur_user['idle'] == '0')
    229                                 $db->query('UPDATE '.$db->prefix.'online SET idle=1 WHERE user_id='.$cur_user['user_id']) or error('Impossible d\'insérer à la liste des utilisateurs en ligne', __FILE__, __LINE__, $db->error());
    230                 }
    231         }
    232 }
    233 
    234 
    235 //
    236 // Generate the "navigator" that appears at the top of every page
    237 //
    238 function generate_navlinks()
    239 {
    240         global $pun_config, $lang_common, $pun_user;
    241 
    242         // Index and Userlist should always be displayed
    243         $links[] = '<li id="navindex"><a href="index.php">'.$lang_common['Index'].'</a>';
    244         $links[] = '<li id="navuserlist"><a href="userlist.php">'.$lang_common['User list'].'</a>';
    245 
    246         if ($pun_config['o_rules'] == '1')
    247                 $links[] = '<li id="navrules"><a href="misc.php?action=rules">'.$lang_common['Rules'].'</a>';
    248 
    249         if ($pun_user['is_guest'])
    250         {
    251                 if ($pun_user['g_search'] == '1')
    252                         $links[] = '<li id="navsearch"><a href="search.php">'.$lang_common['Search'].'</a>';
    253 
    254                 $links[] = '<li id="navregister"><a href="register.php">'.$lang_common['Register'].'</a>';
    255                 $links[] = '<li id="navlogin"><a href="login.php">'.$lang_common['Login'].'</a>';
    256 
    257                 $info = $lang_common['Not logged in'];
    258         }
    259         else
    260         {
    261                 if ($pun_user['g_id'] > PUN_MOD)
    262                 {
    263                         if ($pun_user['g_search'] == '1')
    264                                 $links[] = '<li id="navsearch"><a href="search.php">'.$lang_common['Search'].'</a>';
    265 
    266                         $links[] = '<li id="navprofile"><a href="profile.php?id='.$pun_user['id'].'">'.$lang_common['Profile'].'</a>';
    267                         $links[] = '<li id="navlogout"><a href="login.php?action=out&amp;id='.$pun_user['id'].'">'.$lang_common['Logout'].'</a>';
    268                 }
    269                 else
    270                 {
    271                         $links[] = '<li id="navsearch"><a href="search.php">'.$lang_common['Search'].'</a>';
    272                         $links[] = '<li id="navprofile"><a href="profile.php?id='.$pun_user['id'].'">'.$lang_common['Profile'].'</a>';
    273                         $links[] = '<li id="navadmin"><a href="admin_index.php">'.$lang_common['Admin'].'</a>';
    274                         $links[] = '<li id="navlogout"><a href="login.php?action=out&amp;id='.$pun_user['id'].'">'.$lang_common['Logout'].'</a>';
    275                 }
    276         }
    277 
    278         // Are there any additional navlinks we should insert into the array before imploding it?
    279         if ($pun_config['o_additional_navlinks'] != '')
    280         {
    281                 if (preg_match_all('#([0-9]+)\s*=\s*(.*?)\n#s', $pun_config['o_additional_navlinks']."\n", $extra_links))
    282                 {
    283                         // Insert any additional links into the $links array (at the correct index)
    284                         for ($i = 0; $i < count($extra_links[1]); ++$i)
    285                                 array_splice($links, $extra_links[1][$i], 0, array('<li id="navextra'.($i + 1).'">'.$extra_links[2][$i]));
    286                 }
    287         }
    288 
    289         return '<ul>'."\n\t\t\t\t".implode($lang_common['Link separator'].'</li>'."\n\t\t\t\t", $links).'</li>'."\n\t\t\t".'</ul>';
     499                                $db->query('UPDATE '.$db->prefix.'online SET idle=1 WHERE user_id='.$cur_user['user_id']) or error('Unable to insert into online list', __FILE__, __LINE__, $db->error());
     500                }
     501        }
    290502}
    291503
     
    308520                                        <li<?php if ($page == 'personal') echo ' class="isactive"'; ?>><a href="profile.php?section=personal&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section personal'] ?></a></li>
    309521                                        <li<?php if ($page == 'messaging') echo ' class="isactive"'; ?>><a href="profile.php?section=messaging&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section messaging'] ?></a></li>
    310                                         <li<?php if ($page == 'personality') echo ' class="isactive"'; ?>><a href="profile.php?section=personality&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section personality'] ?></a></li>
    311                                         <li<?php if ($page == 'display') echo ' class="isactive"'; ?>><a href="profile.php?section=display&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section display'] ?></a></li>
     522<?php if ($pun_config['o_avatars'] == '1' || $pun_config['o_signatures'] == '1'): ?>                                    <li<?php if ($page == 'personality') echo ' class="isactive"'; ?>><a href="profile.php?section=personality&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section personality'] ?></a></li>
     523<?php endif; ?>                                 <li<?php if ($page == 'display') echo ' class="isactive"'; ?>><a href="profile.php?section=display&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section display'] ?></a></li>
    312524                                        <li<?php if ($page == 'privacy') echo ' class="isactive"'; ?>><a href="profile.php?section=privacy&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section privacy'] ?></a></li>
    313 <?php if ($pun_user['g_id'] == PUN_ADMIN || ($pun_user['g_id'] == PUN_MOD && $pun_config['p_mod_ban_users'] == '1')): ?>                                        <li<?php if ($page == 'admin') echo ' class="isactive"'; ?>><a href="profile.php?section=admin&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section admin'] ?></a></li>
     525<?php if ($pun_user['g_id'] == PUN_ADMIN || ($pun_user['g_moderator'] == '1' && $pun_user['g_mod_ban_users'] == '1')): ?>                                       <li<?php if ($page == 'admin') echo ' class="isactive"'; ?>><a href="profile.php?section=admin&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section admin'] ?></a></li>
    314526<?php endif; ?>                         </ul>
    315527                        </div>
     
    322534
    323535//
    324 // Update posts, topics, last_post, last_post_id and last_poster for a forum (redirect topics are not included)
     536// Outputs markup to display a user's avatar
     537//
     538function generate_avatar_markup($user_id)
     539{
     540        global $pun_config;
     541
     542        $filetypes = array('jpg', 'gif', 'png');
     543        $avatar_markup = '';
     544
     545        foreach ($filetypes as $cur_type)
     546        {
     547                $path = $pun_config['o_avatars_dir'].'/'.$user_id.'.'.$cur_type;
     548
     549                if (file_exists(PUN_ROOT.$path) && $img_size = getimagesize(PUN_ROOT.$path))
     550                {
     551                        $avatar_markup = '<img src="'.pun_htmlspecialchars(get_base_url(true).'/'.$path.'?m='.filemtime(PUN_ROOT.$path)).'" '.$img_size[3].' alt="" />';
     552                        break;
     553                }
     554        }
     555
     556        return $avatar_markup;
     557}
     558
     559
     560//
     561// Generate browser's title
     562//
     563function generate_page_title($page_title, $p = null)
     564{
     565        global $pun_config, $lang_common;
     566
     567        $page_title = array_reverse($page_title);
     568
     569        if ($p != null)
     570                $page_title[0] .= ' ('.sprintf($lang_common['Page'], forum_number_format($p)).')';
     571
     572        $crumbs = implode($lang_common['Title separator'], $page_title);
     573
     574        return $crumbs;
     575}
     576
     577
     578//
     579// Save array of tracked topics in cookie
     580//
     581function set_tracked_topics($tracked_topics)
     582{
     583        global $cookie_name, $cookie_path, $cookie_domain, $cookie_secure, $pun_config;
     584
     585        $cookie_data = '';
     586        if (!empty($tracked_topics))
     587        {
     588                // Sort the arrays (latest read first)
     589                arsort($tracked_topics['topics'], SORT_NUMERIC);
     590                arsort($tracked_topics['forums'], SORT_NUMERIC);
     591
     592                // Homebrew serialization (to avoid having to run unserialize() on cookie data)
     593                foreach ($tracked_topics['topics'] as $id => $timestamp)
     594                        $cookie_data .= 't'.$id.'='.$timestamp.';';
     595                foreach ($tracked_topics['forums'] as $id => $timestamp)
     596                        $cookie_data .= 'f'.$id.'='.$timestamp.';';
     597
     598                // Enforce a byte size limit (4096 minus some space for the cookie name - defaults to 4048)
     599                if (strlen($cookie_data) > FORUM_MAX_COOKIE_SIZE)
     600                {
     601                        $cookie_data = substr($cookie_data, 0, FORUM_MAX_COOKIE_SIZE);
     602                        $cookie_data = substr($cookie_data, 0, strrpos($cookie_data, ';')).';';
     603                }
     604        }
     605
     606        forum_setcookie($cookie_name.'_track', $cookie_data, time() + $pun_config['o_timeout_visit']);
     607        $_COOKIE[$cookie_name.'_track'] = $cookie_data; // Set it directly in $_COOKIE as well
     608}
     609
     610
     611//
     612// Extract array of tracked topics from cookie
     613//
     614function get_tracked_topics()
     615{
     616        global $cookie_name;
     617
     618        $cookie_data = isset($_COOKIE[$cookie_name.'_track']) ? $_COOKIE[$cookie_name.'_track'] : false;
     619        if (!$cookie_data)
     620                return array('topics' => array(), 'forums' => array());
     621
     622        if (strlen($cookie_data) > 4048)
     623                return array('topics' => array(), 'forums' => array());
     624
     625        // Unserialize data from cookie
     626        $tracked_topics = array('topics' => array(), 'forums' => array());
     627        $temp = explode(';', $cookie_data);
     628        foreach ($temp as $t)
     629        {
     630                $type = substr($t, 0, 1) == 'f' ? 'forums' : 'topics';
     631                $id = intval(substr($t, 1));
     632                $timestamp = intval(substr($t, strpos($t, '=') + 1));
     633                if ($id > 0 && $timestamp > 0)
     634                        $tracked_topics[$type][$id] = $timestamp;
     635        }
     636
     637        return $tracked_topics;
     638}
     639
     640
     641//
     642// Update posts, topics, last_post, last_post_id and last_poster for a forum
    325643//
    326644function update_forum($forum_id)
     
    328646        global $db;
    329647
    330         $result = $db->query('SELECT COUNT(id), SUM(num_replies) FROM '.$db->prefix.'topics WHERE moved_to IS NULL AND forum_id='.$forum_id) or error('Impossible de retrouver le total de discussions du forum', __FILE__, __LINE__, $db->error());
     648        $result = $db->query('SELECT COUNT(id), SUM(num_replies) FROM '.$db->prefix.'topics WHERE forum_id='.$forum_id) or error('Unable to fetch forum topic count', __FILE__, __LINE__, $db->error());
    331649        list($num_topics, $num_posts) = $db->fetch_row($result);
    332650
    333         $num_posts = $num_posts + $num_topics;          // $num_posts is only the sum of all replies (we have to add the topic posts)
    334 
    335         $result = $db->query('SELECT last_post, last_post_id, last_poster FROM '.$db->prefix.'topics WHERE forum_id='.$forum_id.' AND moved_to IS NULL ORDER BY last_post DESC LIMIT 1') or error('Impossible de retrouver last_post/last_post_id/last_poster', __FILE__, __LINE__, $db->error());
    336         if ($db->num_rows($result))             // There are topics in the forum
     651        $num_posts = $num_posts + $num_topics; // $num_posts is only the sum of all replies (we have to add the topic posts)
     652
     653        $result = $db->query('SELECT last_post, last_post_id, last_poster FROM '.$db->prefix.'topics WHERE forum_id='.$forum_id.' AND moved_to IS NULL ORDER BY last_post DESC LIMIT 1') or error('Unable to fetch last_post/last_post_id/last_poster', __FILE__, __LINE__, $db->error());
     654        if ($db->num_rows($result)) // There are topics in the forum
    337655        {
    338656                list($last_post, $last_post_id, $last_poster) = $db->fetch_row($result);
    339657
    340                 $db->query('UPDATE '.$db->prefix.'forums SET num_topics='.$num_topics.', num_posts='.$num_posts.', last_post='.$last_post.', last_post_id='.$last_post_id.', last_poster=\''.$db->escape($last_poster).'\' WHERE id='.$forum_id) or error('Impossible de mettre à jour last_post/last_post_id/last_poster', __FILE__, __LINE__, $db->error());
    341         }
    342         else    // There are no topics
    343                 $db->query('UPDATE '.$db->prefix.'forums SET num_topics=0, num_posts=0, last_post=NULL, last_post_id=NULL, last_poster=NULL WHERE id='.$forum_id) or error('Impossible de mettre à jour last_post/last_post_id/last_poster', __FILE__, __LINE__, $db->error());
     658                $db->query('UPDATE '.$db->prefix.'forums SET num_topics='.$num_topics.', num_posts='.$num_posts.', last_post='.$last_post.', last_post_id='.$last_post_id.', last_poster=\''.$db->escape($last_poster).'\' WHERE id='.$forum_id) or error('Unable to update last_post/last_post_id/last_poster', __FILE__, __LINE__, $db->error());
     659        }
     660        else // There are no topics
     661                $db->query('UPDATE '.$db->prefix.'forums SET num_topics='.$num_topics.', num_posts='.$num_posts.', last_post=NULL, last_post_id=NULL, last_poster=NULL WHERE id='.$forum_id) or error('Unable to update last_post/last_post_id/last_poster', __FILE__, __LINE__, $db->error());
     662}
     663
     664
     665//
     666// Deletes any avatars owned by the specified user ID
     667//
     668function delete_avatar($user_id)
     669{
     670        global $pun_config;
     671
     672        $filetypes = array('jpg', 'gif', 'png');
     673
     674        // Delete user avatar
     675        foreach ($filetypes as $cur_type)
     676        {
     677                if (file_exists(PUN_ROOT.$pun_config['o_avatars_dir'].'/'.$user_id.'.'.$cur_type))
     678                        @unlink(PUN_ROOT.$pun_config['o_avatars_dir'].'/'.$user_id.'.'.$cur_type);
     679        }
    344680}
    345681
     
    353689
    354690        // Delete the topic and any redirect topics
    355         $db->query('DELETE FROM '.$db->prefix.'topics WHERE id='.$topic_id.' OR moved_to='.$topic_id) or error('Impossible de supprimer le sujet', __FILE__, __LINE__, $db->error());
    356 
    357         // Create a list of the post ID's in this topic
     691        $db->query('DELETE FROM '.$db->prefix.'topics WHERE id='.$topic_id.' OR moved_to='.$topic_id) or error('Unable to delete topic', __FILE__, __LINE__, $db->error());
     692
     693        // Create a list of the post IDs in this topic
    358694        $post_ids = '';
    359         $result = $db->query('SELECT id FROM '.$db->prefix.'posts WHERE topic_id='.$topic_id) or error('Impossible de retrouver les messages', __FILE__, __LINE__, $db->error());
     695        $result = $db->query('SELECT id FROM '.$db->prefix.'posts WHERE topic_id='.$topic_id) or error('Unable to fetch posts', __FILE__, __LINE__, $db->error());
    360696        while ($row = $db->fetch_row($result))
    361697                $post_ids .= ($post_ids != '') ? ','.$row[0] : $row[0];
    362698
    363         // Make sure we have a list of post ID's
     699        // Make sure we have a list of post IDs
    364700        if ($post_ids != '')
    365701        {
     
    367703
    368704                // Delete posts in topic
    369                 $db->query('DELETE FROM '.$db->prefix.'posts WHERE topic_id='.$topic_id) or error('Impossible de supprimer les messages', __FILE__, __LINE__, $db->error());
     705                $db->query('DELETE FROM '.$db->prefix.'posts WHERE topic_id='.$topic_id) or error('Unable to delete posts', __FILE__, __LINE__, $db->error());
    370706        }
    371707
    372708        // Delete any subscriptions for this topic
    373         $db->query('DELETE FROM '.$db->prefix.'subscriptions WHERE topic_id='.$topic_id) or error('Impossible de supprimer l\'abonnement', __FILE__, __LINE__, $db->error());
     709        $db->query('DELETE FROM '.$db->prefix.'topic_subscriptions WHERE topic_id='.$topic_id) or error('Unable to delete subscriptions', __FILE__, __LINE__, $db->error());
    374710}
    375711
     
    382718        global $db;
    383719
    384         $result = $db->query('SELECT id, poster, posted FROM '.$db->prefix.'posts WHERE topic_id='.$topic_id.' ORDER BY id DESC LIMIT 2') or error('Impossible de retrouver les informations du message', __FILE__, __LINE__, $db->error());
     720        $result = $db->query('SELECT id, poster, posted FROM '.$db->prefix.'posts WHERE topic_id='.$topic_id.' ORDER BY id DESC LIMIT 2') or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
    385721        list($last_id, ,) = $db->fetch_row($result);
    386722        list($second_last_id, $second_poster, $second_posted) = $db->fetch_row($result);
    387723
    388724        // Delete the post
    389         $db->query('DELETE FROM '.$db->prefix.'posts WHERE id='.$post_id) or error('Impossible de supprimer le message', __FILE__, __LINE__, $db->error());
     725        $db->query('DELETE FROM '.$db->prefix.'posts WHERE id='.$post_id) or error('Unable to delete post', __FILE__, __LINE__, $db->error());
    390726
    391727        strip_search_index($post_id);
    392728
    393729        // Count number of replies in the topic
    394         $result = $db->query('SELECT COUNT(id) FROM '.$db->prefix.'posts WHERE topic_id='.$topic_id) or error('Impossible de retrouver le total de messages pour la discussion', __FILE__, __LINE__, $db->error());
     730        $result = $db->query('SELECT COUNT(id) FROM '.$db->prefix.'posts WHERE topic_id='.$topic_id) or error('Unable to fetch post count for topic', __FILE__, __LINE__, $db->error());
    395731        $num_replies = $db->result($result, 0) - 1;
    396732
     
    400736                // If there is a $second_last_id there is more than 1 reply to the topic
    401737                if (!empty($second_last_id))
    402                         $db->query('UPDATE '.$db->prefix.'topics SET last_post='.$second_posted.', last_post_id='.$second_last_id.', last_poster=\''.$db->escape($second_poster).'\', num_replies='.$num_replies.' WHERE id='.$topic_id) or error('Impossible de mettre à jour la discussion', __FILE__, __LINE__, $db->error());
     738                        $db->query('UPDATE '.$db->prefix.'topics SET last_post='.$second_posted.', last_post_id='.$second_last_id.', last_poster=\''.$db->escape($second_poster).'\', num_replies='.$num_replies.' WHERE id='.$topic_id) or error('Unable to update topic', __FILE__, __LINE__, $db->error());
    403739                else
    404740                        // We deleted the only reply, so now last_post/last_post_id/last_poster is posted/id/poster from the topic itself
    405                         $db->query('UPDATE '.$db->prefix.'topics SET last_post=posted, last_post_id=id, last_poster=poster, num_replies='.$num_replies.' WHERE id='.$topic_id) or error('Impossible de mettre à jour la discussion', __FILE__, __LINE__, $db->error());
     741                        $db->query('UPDATE '.$db->prefix.'topics SET last_post=posted, last_post_id=id, last_poster=poster, num_replies='.$num_replies.' WHERE id='.$topic_id) or error('Unable to update topic', __FILE__, __LINE__, $db->error());
    406742        }
    407743        else
    408744                // Otherwise we just decrement the reply counter
    409                 $db->query('UPDATE '.$db->prefix.'topics SET num_replies='.$num_replies.' WHERE id='.$topic_id) or error('Impossible de mettre à jour la discussion', __FILE__, __LINE__, $db->error());
     745                $db->query('UPDATE '.$db->prefix.'topics SET num_replies='.$num_replies.' WHERE id='.$topic_id) or error('Unable to update topic', __FILE__, __LINE__, $db->error());
     746}
     747
     748
     749//
     750// Delete every .php file in the forum's cache directory
     751//
     752function forum_clear_cache()
     753{
     754        $d = dir(FORUM_CACHE_DIR);
     755        while (($entry = $d->read()) !== false)
     756        {
     757                if (substr($entry, -4) == '.php')
     758                        @unlink(FORUM_CACHE_DIR.$entry);
     759        }
     760        $d->close();
    410761}
    411762
     
    422773        if (!isset($search_for))
    423774        {
    424                 $result = $db->query('SELECT search_for, replace_with FROM '.$db->prefix.'censoring') or error('Impossible de retrouver la liste des mots à censurer', __FILE__, __LINE__, $db->error());
    425                 $num_words = $db->num_rows($result);
    426 
    427                 $search_for = array();
    428                 for ($i = 0; $i < $num_words; ++$i)
    429                 {
    430                         list($search_for[$i], $replace_with[$i]) = $db->fetch_row($result);
    431                         $search_for[$i] = '/\b('.str_replace('\*', '\w*?', preg_quote($search_for[$i], '/')).')\b/i';
     775                if (file_exists(FORUM_CACHE_DIR.'cache_censoring.php'))
     776                        include FORUM_CACHE_DIR.'cache_censoring.php';
     777
     778                if (!defined('PUN_CENSOR_LOADED'))
     779                {
     780                        if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
     781                                require PUN_ROOT.'include/cache.php';
     782
     783                        generate_censoring_cache();
     784                        require FORUM_CACHE_DIR.'cache_censoring.php';
    432785                }
    433786        }
    434787
    435788        if (!empty($search_for))
    436                 $text = substr(preg_replace($search_for, $replace_with, ' '.$text.' '), 1, -1);
     789                $text = substr(ucp_preg_replace($search_for, $replace_with, ' '.$text.' '), 1, -1);
    437790
    438791        return $text;
     
    459812
    460813        // If not already loaded in a previous call, load the cached ranks
    461         if ($pun_config['o_ranks'] == '1' && empty($pun_ranks))
    462         {
    463                 @include PUN_ROOT.'cache/cache_ranks.php';
     814        if ($pun_config['o_ranks'] == '1' && !defined('PUN_RANKS_LOADED'))
     815        {
     816                if (file_exists(FORUM_CACHE_DIR.'cache_ranks.php'))
     817                        include FORUM_CACHE_DIR.'cache_ranks.php';
     818
    464819                if (!defined('PUN_RANKS_LOADED'))
    465820                {
    466                         require_once PUN_ROOT.'include/cache.php';
     821                        if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
     822                                require PUN_ROOT.'include/cache.php';
     823
    467824                        generate_ranks_cache();
    468                         require PUN_ROOT.'cache/cache_ranks.php';
     825                        require FORUM_CACHE_DIR.'cache_ranks.php';
    469826                }
    470827        }
     
    487844                if ($pun_config['o_ranks'] == '1' && !empty($pun_ranks))
    488845                {
    489                         @reset($pun_ranks);
    490                         while (list(, $cur_rank) = @each($pun_ranks))
     846                        foreach ($pun_ranks as $cur_rank)
    491847                        {
    492                                 if (intval($user['num_posts']) >= $cur_rank['min_posts'])
     848                                if ($user['num_posts'] >= $cur_rank['min_posts'])
    493849                                        $user_title = pun_htmlspecialchars($cur_rank['rank']);
    494850                        }
     
    507863// Generate a string with numbered links (for multipage scripts)
    508864//
    509 function paginate($num_pages, $cur_page, $link_to)
    510 {
     865function paginate($num_pages, $cur_page, $link)
     866{
     867        global $lang_common;
     868
    511869        $pages = array();
    512870        $link_to_all = false;
     
    520878
    521879        if ($num_pages <= 1)
    522                 $pages = array('<strong>1</strong>');
     880                $pages = array('<strong class="item1">1</strong>');
    523881        else
    524882        {
     883                // Add a previous page link
     884                if ($num_pages > 1 && $cur_page > 1)
     885                        $pages[] = '<a'.(empty($pages) ? ' class="item1"' : '').' href="'.$link.'&amp;p='.($cur_page - 1).'">'.$lang_common['Previous'].'</a>';
     886
    525887                if ($cur_page > 3)
    526888                {
    527                         $pages[] = '<a href="'.$link_to.'&amp;p=1">1</a>';
    528 
    529                         if ($cur_page != 4)
    530                                 $pages[] = '&hellip;';
     889                        $pages[] = '<a'.(empty($pages) ? ' class="item1"' : '').' href="'.$link.'&amp;p=1">1</a>';
     890
     891                        if ($cur_page > 5)
     892                                $pages[] = '<span class="spacer">'.$lang_common['Spacer'].'</span>';
    531893                }
    532894
    533895                // Don't ask me how the following works. It just does, OK? :-)
    534                 for ($current = $cur_page - 2, $stop = $cur_page + 3; $current < $stop; ++$current)
     896                for ($current = ($cur_page == 5) ? $cur_page - 3 : $cur_page - 2, $stop = ($cur_page + 4 == $num_pages) ? $cur_page + 4 : $cur_page + 3; $current < $stop; ++$current)
    535897                {
    536898                        if ($current < 1 || $current > $num_pages)
    537899                                continue;
    538900                        else if ($current != $cur_page || $link_to_all)
    539                                 $pages[] = '<a href="'.$link_to.'&amp;p='.$current.'">'.$current.'</a>';
     901                                $pages[] = '<a'.(empty($pages) ? ' class="item1"' : '').' href="'.$link.'&amp;p='.$current.'">'.forum_number_format($current).'</a>';
    540902                        else
    541                                 $pages[] = '<strong>'.$current.'</strong>';
     903                                $pages[] = '<strong'.(empty($pages) ? ' class="item1"' : '').'>'.forum_number_format($current).'</strong>';
    542904                }
    543905
    544906                if ($cur_page <= ($num_pages-3))
    545907                {
    546                         if ($cur_page != ($num_pages-3))
    547                                 $pages[] = '&hellip;';
    548 
    549                         $pages[] = '<a href="'.$link_to.'&amp;p='.$num_pages.'">'.$num_pages.'</a>';
    550                 }
    551         }
    552 
    553         return implode('&#160;', $pages);
     908                        if ($cur_page != ($num_pages-3) && $cur_page != ($num_pages-4))
     909                                $pages[] = '<span class="spacer">'.$lang_common['Spacer'].'</span>';
     910
     911                        $pages[] = '<a'.(empty($pages) ? ' class="item1"' : '').' href="'.$link.'&amp;p='.$num_pages.'">'.forum_number_format($num_pages).'</a>';
     912                }
     913
     914                // Add a next page link
     915                if ($num_pages > 1 && !$link_to_all && $cur_page < $num_pages)
     916                        $pages[] = '<a'.(empty($pages) ? ' class="item1"' : '').' href="'.$link.'&amp;p='.($cur_page +1).'">'.$lang_common['Next'].'</a>';
     917        }
     918
     919        return implode(' ', $pages);
    554920}
    555921
     
    560926function message($message, $no_back_link = false)
    561927{
    562 
    563         global $db, $lang_common, $pun_config, $pun_start, $tpl_main, $env;
     928        global $db, $lang_common, $pun_config, $pun_start, $tpl_main, $pun_user;
    564929
    565930        if (!defined('PUN_HEADER'))
    566931        {
    567                 global $pun_user;
    568 
    569                 $page_title = pun_htmlspecialchars($pun_config['o_board_title']).' / '.$lang_common['Info'];
     932                $page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_common['Info']);
     933                define('PUN_ACTIVE_PAGE', 'index');
    570934                require PUN_ROOT.'header.php';
    571935        }
     
    577941        <div class="box">
    578942                <div class="inbox">
    579                 <p><?php echo $message ?></p>
    580 <?php if (!$no_back_link): ?>           <p><a href="javascript: history.go(-1)"><?php echo $lang_common['Go back'] ?></a></p>
     943                        <p><?php echo $message ?></p>
     944<?php if (!$no_back_link): ?>                   <p><a href="javascript: history.go(-1)"><?php echo $lang_common['Go back'] ?></a></p>
    581945<?php endif; ?>         </div>
    582946        </div>
     
    589953
    590954//
    591 // Format a time string according to $time_format and timezones
    592 //
    593 function format_time($timestamp, $date_only = false)
    594 {
    595         global $pun_config, $lang_common, $pun_user;
     955// Format a time string according to $time_format and time zones
     956//
     957function format_time($timestamp, $date_only = false, $date_format = null, $time_format = null, $time_only = false, $no_text = false)
     958{
     959        global $pun_config, $lang_common, $pun_user, $forum_date_formats, $forum_time_formats;
    596960
    597961        if ($timestamp == '')
    598962                return $lang_common['Never'];
    599963
    600         $diff = ($pun_user['timezone'] - $pun_config['o_server_timezone']) * 3600;
     964        $diff = ($pun_user['timezone'] + $pun_user['dst']) * 3600;
    601965        $timestamp += $diff;
    602966        $now = time();
    603967
    604         $date = date($pun_config['o_date_format'], $timestamp);
    605         $today = date($pun_config['o_date_format'], $now+$diff);
    606         $yesterday = date($pun_config['o_date_format'], $now+$diff-86400);
    607 
    608         if ($date == $today)
    609                 $date = $lang_common['Today'];
    610         else if ($date == $yesterday)
    611                 $date = $lang_common['Yesterday'];
    612 
    613         if (!$date_only)
    614                 return $date.' '.date($pun_config['o_time_format'], $timestamp);
     968        if($date_format == null)
     969                $date_format = $forum_date_formats[$pun_user['date_format']];
     970
     971        if($time_format == null)
     972                $time_format = $forum_time_formats[$pun_user['time_format']];
     973
     974        $date = gmdate($date_format, $timestamp);
     975        $today = gmdate($date_format, $now+$diff);
     976        $yesterday = gmdate($date_format, $now+$diff-86400);
     977
     978        if(!$no_text)
     979        {
     980                if ($date == $today)
     981                        $date = $lang_common['Today'];
     982                else if ($date == $yesterday)
     983                        $date = $lang_common['Yesterday'];
     984        }
     985
     986        if ($date_only)
     987                return $date;
     988        else if ($time_only)
     989                return gmdate($time_format, $timestamp);
    615990        else
    616                 return $date;
    617 }
    618 
    619 
    620 //
    621 // If we are running pre PHP 4.3.0, we add our own implementation of file_get_contents
    622 //
    623 if (!function_exists('file_get_contents'))
    624 {
    625         function file_get_contents($filename, $use_include_path = 0)
    626         {
    627                 $data = '';
    628 
    629                 if ($fh = fopen($filename, 'rb', $use_include_path))
    630                 {
    631                         $data = fread($fh, filesize($filename));
    632                         fclose($fh);
    633                 }
    634 
    635                 return $data;
    636         }
    637 }
    638 
    639 
    640 //
    641 // Make sure that HTTP_REFERER matches $pun_config['o_base_url']/$script
    642 //
    643 function confirm_referrer($script)
     991                return $date.' '.gmdate($time_format, $timestamp);
     992}
     993
     994
     995//
     996// A wrapper for PHP's number_format function
     997//
     998function forum_number_format($number, $decimals = 0)
     999{
     1000        global $lang_common;
     1001
     1002        return is_numeric($number) ? number_format($number, $decimals, $lang_common['lang_decimal_point'], $lang_common['lang_thousands_sep']) : $number;
     1003}
     1004
     1005
     1006//
     1007// Generate a random key of length $len
     1008//
     1009function random_key($len, $readable = false, $hash = false)
     1010{
     1011        $key = '';
     1012
     1013        if ($hash)
     1014                $key = substr(pun_hash(uniqid(rand(), true)), 0, $len);
     1015        else if ($readable)
     1016        {
     1017                $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
     1018
     1019                for ($i = 0; $i < $len; ++$i)
     1020                        $key .= substr($chars, (mt_rand() % strlen($chars)), 1);
     1021        }
     1022        else
     1023        {
     1024                for ($i = 0; $i < $len; ++$i)
     1025                        $key .= chr(mt_rand(33, 126));
     1026        }
     1027
     1028        return $key;
     1029}
     1030
     1031
     1032//
     1033// Make sure that HTTP_REFERER matches base_url/script
     1034//
     1035function confirm_referrer($script, $error_msg = false)
    6441036{
    6451037        global $pun_config, $lang_common;
    6461038
    647         if (!preg_match('#^'.preg_quote(str_replace('www.', '', $pun_config['o_base_url']).'/'.$script, '#').'#i', str_replace('www.', '', (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''))))
    648                 message($lang_common['Bad referrer']);
     1039        // There is no referrer
     1040        if (empty($_SERVER['HTTP_REFERER']))
     1041                message($error_msg ? $error_msg : $lang_common['Bad referrer']);
     1042
     1043        $referrer = parse_url(strtolower($_SERVER['HTTP_REFERER']));
     1044        // Remove www subdomain if it exists
     1045        if (strpos($referrer['host'], 'www.') === 0)
     1046                $referrer['host'] = substr($referrer['host'], 4);
     1047
     1048        $valid = parse_url(strtolower(get_base_url().'/'.$script));
     1049        // Remove www subdomain if it exists
     1050        if (strpos($valid['host'], 'www.') === 0)
     1051                $valid['host'] = substr($valid['host'], 4);
     1052
     1053        // Check the host and path match. Ignore the scheme, port, etc.
     1054        if ($referrer['host'] != $valid['host'] || $referrer['path'] != $valid['path'])
     1055                message($error_msg ? $error_msg : $lang_common['Bad referrer']);
    6491056}
    6501057
     
    6521059//
    6531060// Generate a random password of length $len
     1061// Compatibility wrapper for random_key
    6541062//
    6551063function random_pass($len)
    6561064{
    657         $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    658 
    659         $password = '';
    660         for ($i = 0; $i < $len; ++$i)
    661                 $password .= substr($chars, (mt_rand() % strlen($chars)), 1);
    662 
    663         return $password;
     1065        return random_key($len, true);
    6641066}
    6651067
     
    6671069//
    6681070// Compute a hash of $str
    669 // Uses sha1() if available. If not, SHA1 through mhash() if available. If not, fall back on md5().
    6701071//
    6711072function pun_hash($str)
    6721073{
    673         if (function_exists('sha1'))    // Only in PHP 4.3.0+
    674                 return sha1($str);
    675         else if (function_exists('mhash'))      // Only if Mhash library is loaded
    676                 return bin2hex(mhash(MHASH_SHA1, $str));
    677         else
    678                 return md5($str);
     1074        return sha1($str);
    6791075}
    6801076
     
    6851081function get_remote_address()
    6861082{
    687         return $_SERVER['REMOTE_ADDR'];
    688 }
    689 
    690 
    691 //
    692 // Equivalent to htmlspecialchars(), but allows &#[0-9]+ (for unicode)
     1083        $remote_addr = $_SERVER['REMOTE_ADDR'];
     1084
     1085        // If we are behind a reverse proxy try to find the real users IP
     1086        if (defined('FORUM_BEHIND_REVERSE_PROXY'))
     1087        {
     1088                if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
     1089                {
     1090                        // The general format of the field is:
     1091                        // X-Forwarded-For: client1, proxy1, proxy2
     1092                        // where the value is a comma+space separated list of IP addresses, the left-most being the farthest downstream client,
     1093                        // and each successive proxy that passed the request adding the IP address where it received the request from.
     1094                        $forwarded_for = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
     1095                        $forwarded_for = trim($forwarded_for[0]);
     1096
     1097                        if (@preg_match('%^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$%', $forwarded_for) || @preg_match('%^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$%', $forwarded_for))
     1098                                $remote_addr = $forwarded_for;
     1099                }
     1100        }
     1101
     1102        return $remote_addr;
     1103}
     1104
     1105
     1106//
     1107// Calls htmlspecialchars with a few options already set
    6931108//
    6941109function pun_htmlspecialchars($str)
    6951110{
    696         $str = preg_replace('/&(?!#[0-9]+;)/s', '&amp;', $str);
    697         $str = str_replace(array('<', '>', '"'), array('&lt;', '&gt;', '&quot;'), $str);
    698 
    699         return $str;
    700 }
    701 
    702 
    703 //
    704 // Equivalent to strlen(), but counts &#[0-9]+ as one character (for unicode)
     1111        return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
     1112}
     1113
     1114
     1115//
     1116// Calls htmlspecialchars_decode with a few options already set
     1117//
     1118function pun_htmlspecialchars_decode($str)
     1119{
     1120        if (function_exists('htmlspecialchars_decode'))
     1121                return htmlspecialchars_decode($str, ENT_QUOTES);
     1122
     1123        static $translations;
     1124        if (!isset($translations))
     1125        {
     1126                $translations = get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES);
     1127                $translations['&#039;'] = '\''; // get_html_translation_table doesn't include &#039; which is what htmlspecialchars translates ' to, but apparently that is okay?! http://bugs.php.net/bug.php?id=25927
     1128                $translations = array_flip($translations);
     1129        }
     1130
     1131        return strtr($str, $translations);
     1132}
     1133
     1134
     1135//
     1136// A wrapper for utf8_strlen for compatibility
    7051137//
    7061138function pun_strlen($str)
    7071139{
    708         return strlen(preg_replace('/&#([0-9]+);/', '!', $str));
     1140        return utf8_strlen($str);
    7091141}
    7101142
     
    7201152
    7211153//
    722 // A more aggressive version of trim()
    723 //
    724 function pun_trim($str)
    725 {
    726         global $lang_common;
    727 
    728         if (strpos($lang_common['lang_encoding'], '8859') !== false)
    729         {
    730                 $fishy_chars = array(chr(0x81), chr(0x8D), chr(0x8F), chr(0x90), chr(0x9D), chr(0xA0));
    731                 return trim(str_replace($fishy_chars, ' ', $str));
    732         }
    733         else
    734                 return trim($str);
     1154// A wrapper for utf8_trim for compatibility
     1155//
     1156function pun_trim($str, $charlist = false)
     1157{
     1158        return utf8_trim($str, $charlist);
     1159}
     1160
     1161//
     1162// Checks if a string is in all uppercase
     1163//
     1164function is_all_uppercase($string)
     1165{
     1166        return utf8_strtoupper($string) == $string && utf8_strtolower($string) != $string;
     1167}
     1168
     1169
     1170//
     1171// Inserts $element into $input at $offset
     1172// $offset can be either a numerical offset to insert at (eg: 0 inserts at the beginning of the array)
     1173// or a string, which is the key that the new element should be inserted before
     1174// $key is optional: it's used when inserting a new key/value pair into an associative array
     1175//
     1176function array_insert(&$input, $offset, $element, $key = null)
     1177{
     1178        if ($key == null)
     1179                $key = $offset;
     1180
     1181        // Determine the proper offset if we're using a string
     1182        if (!is_int($offset))
     1183                $offset = array_search($offset, array_keys($input), true);
     1184
     1185        // Out of bounds checks
     1186        if ($offset > count($input))
     1187                $offset = count($input);
     1188        else if ($offset < 0)
     1189                $offset = 0;
     1190
     1191        $input = array_merge(array_slice($input, 0, $offset), array($key => $element), array_slice($input, $offset));
    7351192}
    7361193
     
    7421199{
    7431200        global $db, $pun_config, $lang_common, $pun_user;
     1201
     1202        // Send no-cache headers
     1203        header('Expires: Thu, 21 Jul 1977 07:30:00 GMT'); // When yours truly first set eyes on this world! :)
     1204        header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
     1205        header('Cache-Control: post-check=0, pre-check=0', false);
     1206        header('Pragma: no-cache'); // For HTTP/1.0 compatibility
     1207
     1208        // Send the Content-type header in case the web server is setup to send something else
     1209        header('Content-type: text/html; charset=utf-8');
    7441210
    7451211        // Deal with newlines, tabs and multiple spaces
     
    7481214        $message = str_replace($pattern, $replace, $pun_config['o_maintenance_message']);
    7491215
    750 
    751         // Load the maintenance template
    752         $tpl_maint = trim(file_get_contents(PUN_ROOT.'include/template/maintenance.tpl'));
     1216        if (file_exists(PUN_ROOT.'style/'.$pun_user['style'].'/maintenance.tpl'))
     1217        {
     1218                $tpl_file = PUN_ROOT.'style/'.$pun_user['style'].'/maintenance.tpl';
     1219                $tpl_inc_dir = PUN_ROOT.'style/'.$pun_user['style'].'/';
     1220        }
     1221        else
     1222        {
     1223                $tpl_file = PUN_ROOT.'include/template/maintenance.tpl';
     1224                $tpl_inc_dir = PUN_ROOT.'include/user/';
     1225        }
     1226
     1227        $tpl_maint = file_get_contents($tpl_file);
     1228
     1229        // START SUBST - <pun_include "*">
     1230        preg_match_all('%<pun_include "([^/\\\\]*?)\.(php[45]?|inc|html?|txt)">%i', $tpl_maint, $pun_includes, PREG_SET_ORDER);
     1231
     1232        foreach ($pun_includes as $cur_include)
     1233        {
     1234                ob_start();
     1235
     1236                // Allow for overriding user includes, too.
     1237                if (file_exists($tpl_inc_dir.$cur_include[1].'.'.$cur_include[2]))
     1238                        require $tpl_inc_dir.$cur_include[1].'.'.$cur_include[2];
     1239                else if (file_exists(PUN_ROOT.'include/user/'.$cur_include[1].'.'.$cur_include[2]))
     1240                        require PUN_ROOT.'include/user/'.$cur_include[1].'.'.$cur_include[2];
     1241                else
     1242                        error(sprintf($lang_common['Pun include error'], htmlspecialchars($cur_include[0]), basename($tpl_file)));
     1243
     1244                $tpl_temp = ob_get_contents();
     1245                $tpl_maint = str_replace($cur_include[0], $tpl_temp, $tpl_maint);
     1246                ob_end_clean();
     1247        }
     1248        // END SUBST - <pun_include "*">
     1249
     1250
     1251        // START SUBST - <pun_language>
     1252        $tpl_maint = str_replace('<pun_language>', $lang_common['lang_identifier'], $tpl_maint);
     1253        // END SUBST - <pun_language>
    7531254
    7541255
     
    7581259
    7591260
    760         // START SUBST - <pun_char_encoding>
    761         $tpl_maint = str_replace('<pun_char_encoding>', $lang_common['lang_encoding'], $tpl_maint);
    762         // END SUBST - <pun_char_encoding>
    763 
    764 
    7651261        // START SUBST - <pun_head>
    7661262        ob_start();
    7671263
     1264        $page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_common['Maintenance']);
     1265
    7681266?>
    769 <title><?php echo pun_htmlspecialchars($pun_config['o_board_title']).' / '.$lang_common['Maintenance'] ?></title>
     1267<title><?php echo generate_page_title($page_title) ?></title>
    7701268<link rel="stylesheet" type="text/css" href="style/<?php echo $pun_user['style'].'.css' ?>" />
    7711269<?php
     
    7771275
    7781276
    779         // START SUBST - <pun_maint_heading>
    780         $tpl_maint = str_replace('<pun_maint_heading>', $lang_common['Maintenance'], $tpl_maint);
    781         // END SUBST - <pun_maint_heading>
    782 
    783 
    784         // START SUBST - <pun_maint_message>
    785         $tpl_maint = str_replace('<pun_maint_message>', $message, $tpl_maint);
    786         // END SUBST - <pun_maint_message>
     1277        // START SUBST - <pun_maint_main>
     1278        ob_start();
     1279
     1280?>
     1281<div class="block">
     1282        <h2><?php echo $lang_common['Maintenance'] ?></h2>
     1283        <div class="box">
     1284                <div class="inbox">
     1285                        <p><?php echo $message ?></p>
     1286                </div>
     1287        </div>
     1288</div>
     1289<?php
     1290
     1291        $tpl_temp = trim(ob_get_contents());
     1292        $tpl_maint = str_replace('<pun_maint_main>', $tpl_temp, $tpl_maint);
     1293        ob_end_clean();
     1294        // END SUBST - <pun_maint_main>
    7871295
    7881296
     
    7911299
    7921300
    793         // START SUBST - <pun_include "*">
    794         while (preg_match('#<pun_include "([^/\\\\]*?)">#', $tpl_maint, $cur_include))
    795         {
    796                 if (!file_exists(PUN_ROOT.'include/user/'.$cur_include[1]))
    797                    error('Impossible de procéder à l\'inclusion utilisateur &lt;pun_include "'.htmlspecialchars($cur_include[1]).'"&gt; depuis le template main.tpl. Il n\'y a pas de fichier dans le répertoire /include/user/');
    798 
    799                 ob_start();
    800                 include PUN_ROOT.'include/user/'.$cur_include[1];
    801                 $tpl_temp = ob_get_contents();
    802                 $tpl_redir = str_replace($cur_include[0], $tpl_temp, $tpl_redir);
    803             ob_end_clean();
    804         }
    805         // END SUBST - <pun_include "*">
    806 
    807 
    8081301        // Close the db connection (and free up any result data)
    8091302        $db->close();
     
    8201313        global $db, $pun_config, $lang_common, $pun_user;
    8211314
    822         if ($destination_url == '')
    823                 $destination_url = 'index.php';
     1315        // Prefix with base_url (unless there's already a valid URI)
     1316        if (strpos($destination_url, 'http://') !== 0 && strpos($destination_url, 'https://') !== 0 && strpos($destination_url, '/') !== 0)
     1317                $destination_url = get_base_url(true).'/'.$destination_url;
     1318
     1319        // Do a little spring cleaning
     1320        $destination_url = preg_replace('%([\r\n])|(\%0[ad])|(;\s*data\s*:)%i', '', $destination_url);
    8241321
    8251322        // If the delay is 0 seconds, we might as well skip the redirect all together
     
    8271324                header('Location: '.str_replace('&amp;', '&', $destination_url));
    8281325
    829 
    830         // Load the redirect template
    831         $tpl_redir = trim(file_get_contents(PUN_ROOT.'include/template/redirect.tpl'));
     1326        // Send no-cache headers
     1327        header('Expires: Thu, 21 Jul 1977 07:30:00 GMT'); // When yours truly first set eyes on this world! :)
     1328        header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
     1329        header('Cache-Control: post-check=0, pre-check=0', false);
     1330        header('Pragma: no-cache'); // For HTTP/1.0 compatibility
     1331
     1332        // Send the Content-type header in case the web server is setup to send something else
     1333        header('Content-type: text/html; charset=utf-8');
     1334
     1335        if (file_exists(PUN_ROOT.'style/'.$pun_user['style'].'/redirect.tpl'))
     1336        {
     1337                $tpl_file = PUN_ROOT.'style/'.$pun_user['style'].'/redirect.tpl';
     1338                $tpl_inc_dir = PUN_ROOT.'style/'.$pun_user['style'].'/';
     1339        }
     1340        else
     1341        {
     1342                $tpl_file = PUN_ROOT.'include/template/redirect.tpl';
     1343                $tpl_inc_dir = PUN_ROOT.'include/user/';
     1344        }
     1345
     1346        $tpl_redir = file_get_contents($tpl_file);
     1347
     1348        // START SUBST - <pun_include "*">
     1349        preg_match_all('%<pun_include "([^/\\\\]*?)\.(php[45]?|inc|html?|txt)">%i', $tpl_redir, $pun_includes, PREG_SET_ORDER);
     1350
     1351        foreach ($pun_includes as $cur_include)
     1352        {
     1353                ob_start();
     1354
     1355                // Allow for overriding user includes, too.
     1356                if (file_exists($tpl_inc_dir.$cur_include[1].'.'.$cur_include[2]))
     1357                        require $tpl_inc_dir.$cur_include[1].'.'.$cur_include[2];
     1358                else if (file_exists(PUN_ROOT.'include/user/'.$cur_include[1].'.'.$cur_include[2]))
     1359                        require PUN_ROOT.'include/user/'.$cur_include[1].'.'.$cur_include[2];
     1360                else
     1361                        error(sprintf($lang_common['Pun include error'], htmlspecialchars($cur_include[0]), basename($tpl_file)));
     1362
     1363                $tpl_temp = ob_get_contents();
     1364                $tpl_redir = str_replace($cur_include[0], $tpl_temp, $tpl_redir);
     1365                ob_end_clean();
     1366        }
     1367        // END SUBST - <pun_include "*">
     1368
     1369
     1370        // START SUBST - <pun_language>
     1371        $tpl_redir = str_replace('<pun_language>', $lang_common['lang_identifier'], $tpl_redir);
     1372        // END SUBST - <pun_language>
    8321373
    8331374
     
    8371378
    8381379
    839         // START SUBST - <pun_char_encoding>
    840         $tpl_redir = str_replace('<pun_char_encoding>', $lang_common['lang_encoding'], $tpl_redir);
    841         // END SUBST - <pun_char_encoding>
    842 
    843 
    8441380        // START SUBST - <pun_head>
    8451381        ob_start();
    8461382
     1383        $page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_common['Redirecting']);
     1384
    8471385?>
    8481386<meta http-equiv="refresh" content="<?php echo $pun_config['o_redirect_delay'] ?>;URL=<?php echo str_replace(array('<', '>', '"'), array('&lt;', '&gt;', '&quot;'), $destination_url) ?>" />
    849 <title><?php echo pun_htmlspecialchars($pun_config['o_board_title']).' / '.$lang_common['Redirecting'] ?></title>
     1387<title><?php echo generate_page_title($page_title) ?></title>
    8501388<link rel="stylesheet" type="text/css" href="style/<?php echo $pun_user['style'].'.css' ?>" />
    8511389<?php
     
    8571395
    8581396
    859         // START SUBST - <pun_redir_heading>
    860         $tpl_redir = str_replace('<pun_redir_heading>', $lang_common['Redirecting'], $tpl_redir);
    861         // END SUBST - <pun_redir_heading>
    862 
    863 
    864         // START SUBST - <pun_redir_text>
    865         $tpl_temp = $message.'<br /><br />'.'<a href="'.$destination_url.'">'.$lang_common['Click redirect'].'</a>';
    866         $tpl_redir = str_replace('<pun_redir_text>', $tpl_temp, $tpl_redir);
    867         // END SUBST - <pun_redir_text>
     1397        // START SUBST - <pun_redir_main>
     1398        ob_start();
     1399
     1400?>
     1401<div class="block">
     1402        <h2><?php echo $lang_common['Redirecting'] ?></h2>
     1403        <div class="box">
     1404                <div class="inbox">
     1405                        <p><?php echo $message.'<br /><br /><a href="'.$destination_url.'">'.$lang_common['Click redirect'].'</a>' ?></p>
     1406                </div>
     1407        </div>
     1408</div>
     1409<?php
     1410
     1411        $tpl_temp = trim(ob_get_contents());
     1412        $tpl_redir = str_replace('<pun_redir_main>', $tpl_temp, $tpl_redir);
     1413        ob_end_clean();
     1414        // END SUBST - <pun_redir_main>
    8681415
    8691416
     
    8841431
    8851432
    886         // START SUBST - <pun_include "*">
    887         while (preg_match('<pun_include "(.*?)">', $tpl_redir, $cur_include))
    888         {
    889                 ob_start();
    890                 include PUN_ROOT.$cur_include[1];
    891                 $tpl_temp = ob_get_contents();
    892                 $tpl_redir = str_replace('<'.$cur_include[0].'>', $tpl_temp, $tpl_redir);
    893             ob_end_clean();
    894         }
    895         // END SUBST - <pun_include "*">
    896 
    897 
    8981433        // Close the db connection (and free up any result data)
    8991434        $db->close();
     
    9061441// Display a simple error message
    9071442//
    908 function error($message, $file, $line, $db_error = false)
    909 {
    910         global $pun_config;
    911 
    912         // Set a default title if the script failed before $pun_config could be populated
     1443function error($message, $file = null, $line = null, $db_error = false)
     1444{
     1445        global $pun_config, $lang_common;
     1446
     1447        // Set some default settings if the script failed before $pun_config could be populated
    9131448        if (empty($pun_config))
    914                 $pun_config['o_board_title'] = 'PunBB';
    915 
    916         // Empty output buffer and stop buffering
    917         @ob_end_clean();
     1449        {
     1450                $pun_config = array(
     1451                        'o_board_title' => 'FluxBB',
     1452                        'o_gzip'                => '0'
     1453                );
     1454        }
     1455
     1456        // Set some default translations if the script failed before $lang_common could be populated
     1457        if (empty($lang_common))
     1458        {
     1459                $lang_common = array(
     1460                        'Title separator'       => ' / ',
     1461                        'Page'                          => 'Page %s'
     1462                );
     1463        }
     1464
     1465        // Empty all output buffers and stop buffering
     1466        while (@ob_end_clean());
    9181467
    9191468        // "Restart" output buffering if we are using ob_gzhandler (since the gzip header is already sent)
    920         if (!empty($pun_config['o_gzip']) && extension_loaded('zlib') && (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false || strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') !== false))
     1469        if ($pun_config['o_gzip'] && extension_loaded('zlib'))
    9211470                ob_start('ob_gzhandler');
     1471
     1472        // Send no-cache headers
     1473        header('Expires: Thu, 21 Jul 1977 07:30:00 GMT'); // When yours truly first set eyes on this world! :)
     1474        header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
     1475        header('Cache-Control: post-check=0, pre-check=0', false);
     1476        header('Pragma: no-cache'); // For HTTP/1.0 compatibility
     1477
     1478        // Send the Content-type header in case the web server is setup to send something else
     1479        header('Content-type: text/html; charset=utf-8');
    9221480
    9231481?>
    9241482<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    925 <html dir="ltr">
     1483<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">
    9261484<head>
    927 <meta http-equiv="Content-Type" content="text/html; charset=<?php echo $lang_common["lang_encoding"] ?>" />
    928 <title><?php echo pun_htmlspecialchars($pun_config['o_board_title']) ?> / Error</title>
     1485<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
     1486<?php $page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), 'Error') ?>
     1487<title><?php echo generate_page_title($page_title) ?></title>
    9291488<style type="text/css">
    9301489<!--
     
    9391498
    9401499<div id="errorbox">
    941         <h2>Une erreur s'est produite</h2>
     1500        <h2>An error was encountered</h2>
    9421501        <div>
    9431502<?php
    9441503
    945         if (defined('PUN_DEBUG'))
    946         {
    947                 echo "\t\t".'<strong>Fichier :</strong> '.$file.'<br />'."\n\t\t".'<strong>Ligne :</strong> '.$line.'<br /><br />'."\n\t\t".'<strong>PunBB a rapporté :</strong> '.$message."\n";
     1504        if (defined('PUN_DEBUG') && $file !== null && $line !== null)
     1505        {
     1506                echo "\t\t".'<strong>File:</strong> '.$file.'<br />'."\n\t\t".'<strong>Line:</strong> '.$line.'<br /><br />'."\n\t\t".'<strong>FluxBB reported</strong>: '.$message."\n";
    9481507
    9491508                if ($db_error)
    9501509                {
    951                         echo "\t\t".'<br /><br /><strong>La base de données a rapporté :</strong> '.pun_htmlspecialchars($db_error['error_msg']).(($db_error['error_no']) ? ' (Errno: '.$db_error['error_no'].')' : '')."\n";
     1510                        echo "\t\t".'<br /><br /><strong>Database reported:</strong> '.pun_htmlspecialchars($db_error['error_msg']).(($db_error['error_no']) ? ' (Errno: '.$db_error['error_no'].')' : '')."\n";
    9521511
    9531512                        if ($db_error['error_sql'] != '')
    954                                 echo "\t\t".'<br /><br /><strong>Requête échouée :</strong> '.pun_htmlspecialchars($db_error['error_sql'])."\n";
     1513                                echo "\t\t".'<br /><br /><strong>Failed query:</strong> '.pun_htmlspecialchars($db_error['error_sql'])."\n";
    9551514                }
    9561515        }
    9571516        else
    958                 echo "\t\t".'Erreur : <strong>'.$message.'.</strong>'."\n";
     1517                echo "\t\t".'Error: <strong>'.$message.'.</strong>'."\n";
    9591518
    9601519?>
     
    9711530
    9721531        exit;
     1532}
     1533
     1534
     1535//
     1536// Unset any variables instantiated as a result of register_globals being enabled
     1537//
     1538function forum_unregister_globals()
     1539{
     1540        $register_globals = ini_get('register_globals');
     1541        if ($register_globals === '' || $register_globals === '0' || strtolower($register_globals) === 'off')
     1542                return;
     1543
     1544        // Prevent script.php?GLOBALS[foo]=bar
     1545        if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS']))
     1546                exit('I\'ll have a steak sandwich and... a steak sandwich.');
     1547
     1548        // Variables that shouldn't be unset
     1549        $no_unset = array('GLOBALS', '_GET', '_POST', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES');
     1550
     1551        // Remove elements in $GLOBALS that are present in any of the superglobals
     1552        $input = array_merge($_GET, $_POST, $_COOKIE, $_SERVER, $_ENV, $_FILES, isset($_SESSION) && is_array($_SESSION) ? $_SESSION : array());
     1553        foreach ($input as $k => $v)
     1554        {
     1555                if (!in_array($k, $no_unset) && isset($GLOBALS[$k]))
     1556                {
     1557                        unset($GLOBALS[$k]);
     1558                        unset($GLOBALS[$k]); // Double unset to circumvent the zend_hash_del_key_or_index hole in PHP <4.4.3 and <5.1.4
     1559                }
     1560        }
     1561}
     1562
     1563
     1564//
     1565// Removes any "bad" characters (characters which mess with the display of a page, are invisible, etc) from user input
     1566//
     1567function forum_remove_bad_characters()
     1568{
     1569        $_GET = remove_bad_characters($_GET);
     1570        $_POST = remove_bad_characters($_POST);
     1571        $_COOKIE = remove_bad_characters($_COOKIE);
     1572        $_REQUEST = remove_bad_characters($_REQUEST);
     1573}
     1574
     1575//
     1576// Removes any "bad" characters (characters which mess with the display of a page, are invisible, etc) from the given string
     1577// See: http://kb.mozillazine.org/Network.IDN.blacklist_chars
     1578//
     1579function remove_bad_characters($array)
     1580{
     1581        static $bad_utf8_chars;
     1582
     1583        if (!isset($bad_utf8_chars))
     1584        {
     1585                $bad_utf8_chars = array(
     1586                        "\xcc\xb7"              => '',          // COMBINING SHORT SOLIDUS OVERLAY              0337    *
     1587                        "\xcc\xb8"              => '',          // COMBINING LONG SOLIDUS OVERLAY               0338    *
     1588                        "\xe1\x85\x9F"  => '',          // HANGUL CHOSEONG FILLER                               115F    *
     1589                        "\xe1\x85\xA0"  => '',          // HANGUL JUNGSEONG FILLER                              1160    *
     1590                        "\xe2\x80\x8b"  => '',          // ZERO WIDTH SPACE                                             200B    *
     1591                        "\xe2\x80\x8c"  => '',          // ZERO WIDTH NON-JOINER                                200C
     1592                        "\xe2\x80\x8d"  => '',          // ZERO WIDTH JOINER                                    200D
     1593                        "\xe2\x80\x8e"  => '',          // LEFT-TO-RIGHT MARK                                   200E
     1594                        "\xe2\x80\x8f"  => '',          // RIGHT-TO-LEFT MARK                                   200F
     1595                        "\xe2\x80\xaa"  => '',          // LEFT-TO-RIGHT EMBEDDING                              202A
     1596                        "\xe2\x80\xab"  => '',          // RIGHT-TO-LEFT EMBEDDING                              202B
     1597                        "\xe2\x80\xac"  => '',          // POP DIRECTIONAL FORMATTING                   202C
     1598                        "\xe2\x80\xad"  => '',          // LEFT-TO-RIGHT OVERRIDE                               202D
     1599                        "\xe2\x80\xae"  => '',          // RIGHT-TO-LEFT OVERRIDE                               202E
     1600                        "\xe2\x80\xaf"  => '',          // NARROW NO-BREAK SPACE                                202F    *
     1601                        "\xe2\x81\x9f"  => '',          // MEDIUM MATHEMATICAL SPACE                    205F    *
     1602                        "\xe2\x81\xa0"  => '',          // WORD JOINER                                                  2060
     1603                        "\xe3\x85\xa4"  => '',          // HANGUL FILLER                                                3164    *
     1604                        "\xef\xbb\xbf"  => '',          // ZERO WIDTH NO-BREAK SPACE                    FEFF
     1605                        "\xef\xbe\xa0"  => '',          // HALFWIDTH HANGUL FILLER                              FFA0    *
     1606                        "\xef\xbf\xb9"  => '',          // INTERLINEAR ANNOTATION ANCHOR                FFF9    *
     1607                        "\xef\xbf\xba"  => '',          // INTERLINEAR ANNOTATION SEPARATOR             FFFA    *
     1608                        "\xef\xbf\xbb"  => '',          // INTERLINEAR ANNOTATION TERMINATOR    FFFB    *
     1609                        "\xef\xbf\xbc"  => '',          // OBJECT REPLACEMENT CHARACTER                 FFFC    *
     1610                        "\xef\xbf\xbd"  => '',          // REPLACEMENT CHARACTER                                FFFD    *
     1611                        "\xe2\x80\x80"  => ' ',         // EN QUAD                                                              2000    *
     1612                        "\xe2\x80\x81"  => ' ',         // EM QUAD                                                              2001    *
     1613                        "\xe2\x80\x82"  => ' ',         // EN SPACE                                                             2002    *
     1614                        "\xe2\x80\x83"  => ' ',         // EM SPACE                                                             2003    *
     1615                        "\xe2\x80\x84"  => ' ',         // THREE-PER-EM SPACE                                   2004    *
     1616                        "\xe2\x80\x85"  => ' ',         // FOUR-PER-EM SPACE                                    2005    *
     1617                        "\xe2\x80\x86"  => ' ',         // SIX-PER-EM SPACE                                             2006    *
     1618                        "\xe2\x80\x87"  => ' ',         // FIGURE SPACE                                                 2007    *
     1619                        "\xe2\x80\x88"  => ' ',         // PUNCTUATION SPACE                                    2008    *
     1620                        "\xe2\x80\x89"  => ' ',         // THIN SPACE                                                   2009    *
     1621                        "\xe2\x80\x8a"  => ' ',         // HAIR SPACE                                                   200A    *
     1622                        "\xE3\x80\x80"  => ' ',         // IDEOGRAPHIC SPACE                                    3000    *
     1623                );
     1624        }
     1625
     1626        if (is_array($array))
     1627                return array_map('remove_bad_characters', $array);
     1628
     1629        // Strip out any invalid characters
     1630        $array = utf8_bad_strip($array);
     1631
     1632        // Remove control characters
     1633        $array = preg_replace('%[\x{00}-\x{08}\x{0b}-\x{0c}\x{0e}-\x{1f}]%', '', $array);
     1634
     1635        // Replace some "bad" characters
     1636        $array = str_replace(array_keys($bad_utf8_chars), array_values($bad_utf8_chars), $array);
     1637
     1638        return $array;
     1639}
     1640
     1641
     1642//
     1643// Converts the file size in bytes to a human readable file size
     1644//
     1645function file_size($size)
     1646{
     1647        global $lang_common;
     1648
     1649        $units = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB');
     1650
     1651        for ($i = 0; $size > 1024; $i++)
     1652                $size /= 1024;
     1653
     1654        return sprintf($lang_common['Size unit '.$units[$i]], round($size, 2));;
     1655}
     1656
     1657
     1658//
     1659// Fetch a list of available styles
     1660//
     1661function forum_list_styles()
     1662{
     1663        $styles = array();
     1664
     1665        $d = dir(PUN_ROOT.'style');
     1666        while (($entry = $d->read()) !== false)
     1667        {
     1668                if ($entry{0} == '.')
     1669                        continue;
     1670
     1671                if (substr($entry, -4) == '.css')
     1672                        $styles[] = substr($entry, 0, -4);
     1673        }
     1674        $d->close();
     1675
     1676        natcasesort($styles);
     1677
     1678        return $styles;
     1679}
     1680
     1681
     1682//
     1683// Fetch a list of available language packs
     1684//
     1685function forum_list_langs()
     1686{
     1687        $languages = array();
     1688
     1689        $d = dir(PUN_ROOT.'lang');
     1690        while (($entry = $d->read()) !== false)
     1691        {
     1692                if ($entry{0} == '.')
     1693                        continue;
     1694
     1695                if (is_dir(PUN_ROOT.'lang/'.$entry) && file_exists(PUN_ROOT.'lang/'.$entry.'/common.php'))
     1696                        $languages[] = $entry;
     1697        }
     1698        $d->close();
     1699
     1700        natcasesort($languages);
     1701
     1702        return $languages;
     1703}
     1704
     1705
     1706//
     1707// Generate a cache ID based on the last modification time for all stopwords files
     1708//
     1709function generate_stopwords_cache_id()
     1710{
     1711        $files = glob(PUN_ROOT.'lang/*/stopwords.txt');
     1712        if ($files === false)
     1713                return 'cache_id_error';
     1714
     1715        $hash = array();
     1716
     1717        foreach ($files as $file)
     1718        {
     1719                $hash[] = $file;
     1720                $hash[] = filemtime($file);
     1721        }
     1722
     1723        return sha1(implode('|', $hash));
     1724}
     1725
     1726
     1727//
     1728// Fetch a list of available admin plugins
     1729//
     1730function forum_list_plugins($is_admin)
     1731{
     1732        $plugins = array();
     1733
     1734        $d = dir(PUN_ROOT.'plugins');
     1735        while (($entry = $d->read()) !== false)
     1736        {
     1737                if ($entry{0} == '.')
     1738                        continue;
     1739
     1740                $prefix = substr($entry, 0, strpos($entry, '_'));
     1741                $suffix = substr($entry, strlen($entry) - 4);
     1742
     1743                if ($suffix == '.php' && ((!$is_admin && $prefix == 'AMP') || ($is_admin && ($prefix == 'AP' || $prefix == 'AMP'))))
     1744                        $plugins[$entry] = substr($entry, strpos($entry, '_') + 1, -4);
     1745        }
     1746        $d->close();
     1747
     1748        natcasesort($plugins);
     1749
     1750        return $plugins;
     1751}
     1752
     1753
     1754//
     1755// Split text into chunks ($inside contains all text inside $start and $end, and $outside contains all text outside)
     1756//
     1757function split_text($text, $start, $end, &$errors, $retab = true)
     1758{
     1759        global $pun_config, $lang_common;
     1760
     1761        $result = array(0 => array(), 1 => array()); // 0 = inside, 1 = outside
     1762
     1763        // split the text into parts
     1764        $parts = preg_split('%'.preg_quote($start, '%').'(.*)'.preg_quote($end, '%').'%Us', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
     1765        $num_parts = count($parts);
     1766
     1767        // preg_split results in outside parts having even indices, inside parts having odd
     1768        for ($i = 0;$i < $num_parts;$i++)
     1769                $result[1 - ($i % 2)][] = $parts[$i];
     1770
     1771        if ($pun_config['o_indent_num_spaces'] != 8 && $retab)
     1772        {
     1773                $spaces = str_repeat(' ', $pun_config['o_indent_num_spaces']);
     1774                $result[1] = str_replace("\t", $spaces, $result[1]);
     1775        }
     1776
     1777        return $result;
     1778}
     1779
     1780
     1781//
     1782// Extract blocks from a text with a starting and ending string
     1783// This function always matches the most outer block so nesting is possible
     1784//
     1785function extract_blocks($text, $start, $end, &$errors = array(), $retab = true)
     1786{
     1787        global $pun_config;
     1788
     1789        $code = array();
     1790        $start_len = strlen($start);
     1791        $end_len = strlen($end);
     1792        $regex = '%(?:'.preg_quote($start, '%').'|'.preg_quote($end, '%').')%';
     1793        $matches = array();
     1794
     1795        if (preg_match_all($regex, $text, $matches))
     1796        {
     1797                $counter = $offset = 0;
     1798                $start_pos = $end_pos = false;
     1799
     1800                foreach ($matches[0] as $match)
     1801                {
     1802                        if ($match == $start)
     1803                        {
     1804                                if ($counter == 0)
     1805                                        $start_pos = strpos($text, $start);
     1806                                $counter++;
     1807                        }
     1808                        elseif ($match == $end)
     1809                        {
     1810                                $counter--;
     1811                                if ($counter == 0)
     1812                                        $end_pos = strpos($text, $end, $offset + 1);
     1813                                $offset = strpos($text, $end, $offset + 1);
     1814                        }
     1815
     1816                        if ($start_pos !== false && $end_pos !== false)
     1817                        {
     1818                                $code[] = substr($text, $start_pos + $start_len,
     1819                                        $end_pos - $start_pos - $start_len);
     1820                                $text = substr_replace($text, "\1", $start_pos,
     1821                                        $end_pos - $start_pos + $end_len);
     1822                                $start_pos = $end_pos = false;
     1823                                $offset = 0;
     1824                        }
     1825                }
     1826        }
     1827
     1828        if ($pun_config['o_indent_num_spaces'] != 8 && $retab)
     1829        {
     1830                $spaces = str_repeat(' ', $pun_config['o_indent_num_spaces']);
     1831                $text = str_replace("\t", $spaces, $text);
     1832        }
     1833
     1834        return array($code, $text);
     1835}
     1836
     1837
     1838//
     1839// function url_valid($url) {
     1840//
     1841// Return associative array of valid URI components, or FALSE if $url is not
     1842// RFC-3986 compliant. If the passed URL begins with: "www." or "ftp.", then
     1843// "http://" or "ftp://" is prepended and the corrected full-url is stored in
     1844// the return array with a key name "url". This value should be used by the caller.
     1845//
     1846// Return value: FALSE if $url is not valid, otherwise array of URI components:
     1847// e.g.
     1848// Given: "http://www.jmrware.com:80/articles?height=10&width=75#fragone"
     1849// Array(
     1850//        [scheme] => http
     1851//        [authority] => www.jmrware.com:80
     1852//        [userinfo] =>
     1853//        [host] => www.jmrware.com
     1854//        [IP_literal] =>
     1855//        [IPV6address] =>
     1856//        [ls32] =>
     1857//        [IPvFuture] =>
     1858//        [IPv4address] =>
     1859//        [regname] => www.jmrware.com
     1860//        [port] => 80
     1861//        [path_abempty] => /articles
     1862//        [query] => height=10&width=75
     1863//        [fragment] => fragone
     1864//        [url] => http://www.jmrware.com:80/articles?height=10&width=75#fragone
     1865// )
     1866function url_valid($url)
     1867{
     1868        if (strpos($url, 'www.') === 0) $url = 'http://'. $url;
     1869        if (strpos($url, 'ftp.') === 0) $url = 'ftp://'. $url;
     1870        if (!preg_match('/# Valid absolute URI having a non-empty, valid DNS host.
     1871                ^
     1872                (?P<scheme>[A-Za-z][A-Za-z0-9+\-.]*):\/\/
     1873                (?P<authority>
     1874                  (?:(?P<userinfo>(?:[A-Za-z0-9\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*)@)?
     1875                  (?P<host>
     1876                        (?P<IP_literal>
     1877                          \[
     1878                          (?:
     1879                                (?P<IPV6address>
     1880                                  (?:                                                                                            (?:[0-9A-Fa-f]{1,4}:){6}
     1881                                  |                                                                                                ::(?:[0-9A-Fa-f]{1,4}:){5}
     1882                                  | (?:                                                  [0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}
     1883                                  | (?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}
     1884                                  | (?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}
     1885                                  | (?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::   [0-9A-Fa-f]{1,4}:
     1886                                  | (?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::
     1887                                  )
     1888                                  (?P<ls32>[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}
     1889                                  | (?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}
     1890                                           (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
     1891                                  )
     1892                                |       (?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::       [0-9A-Fa-f]{1,4}
     1893                                |       (?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::
     1894                                )
     1895                          | (?P<IPvFuture>[Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&\'()*+,;=:]+)
     1896                          )
     1897                          \]
     1898                        )
     1899                  | (?P<IPv4address>(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}
     1900                                                           (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))
     1901                  | (?P<regname>(?:[A-Za-z0-9\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})+)
     1902                  )
     1903                  (?::(?P<port>[0-9]*))?
     1904                )
     1905                (?P<path_abempty>(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)
     1906                (?:\?(?P<query>           (?:[A-Za-z0-9\-._~!$&\'()*+,;=:@\\/?]|%[0-9A-Fa-f]{2})*))?
     1907                (?:\#(?P<fragment>        (?:[A-Za-z0-9\-._~!$&\'()*+,;=:@\\/?]|%[0-9A-Fa-f]{2})*))?
     1908                $
     1909                /mx', $url, $m)) return FALSE;
     1910        switch ($m['scheme'])
     1911        {
     1912        case 'https':
     1913        case 'http':
     1914                if ($m['userinfo']) return FALSE; // HTTP scheme does not allow userinfo.
     1915                break;
     1916        case 'ftps':
     1917        case 'ftp':
     1918                break;
     1919        default:
     1920                return FALSE;   // Unrecognised URI scheme. Default to FALSE.
     1921        }
     1922        // Validate host name conforms to DNS "dot-separated-parts".
     1923        if ($m{'regname'}) // If host regname specified, check for DNS conformance.
     1924        {
     1925                if (!preg_match('/# HTTP DNS host name.
     1926                        ^                                          # Anchor to beginning of string.
     1927                        (?!.{256})                         # Overall host length is less than 256 chars.
     1928                        (?:                                        # Group dot separated host part alternatives.
     1929                          [0-9A-Za-z]\.            # Either a single alphanum followed by dot
     1930                        |                                          # or... part has more than one char (63 chars max).
     1931                          [0-9A-Za-z]              # Part first char is alphanum (no dash).
     1932                          [\-0-9A-Za-z]{0,61}  # Internal chars are alphanum plus dash.
     1933                          [0-9A-Za-z]              # Part last char is alphanum (no dash).
     1934                          \.                               # Each part followed by literal dot.
     1935                        )*                                         # One or more parts before top level domain.
     1936                        (?:                                        # Explicitly specify top level domains.
     1937                          com|edu|gov|int|mil|net|org|biz|
     1938                          info|name|pro|aero|coop|museum|
     1939                          asia|cat|jobs|mobi|tel|travel|
     1940                          [A-Za-z]{2})             # Country codes are exqactly two alpha chars.
     1941                        $                                          # Anchor to end of string.
     1942                        /ix', $m['host'])) return FALSE;
     1943        }
     1944        $m['url'] = $url;
     1945        for ($i = 0; isset($m[$i]); ++$i) unset($m[$i]);
     1946        return $m; // return TRUE == array of useful named $matches plus the valid $url.
     1947}
     1948
     1949//
     1950// Replace string matching regular expression
     1951//
     1952// This function takes care of possibly disabled unicode properties in PCRE builds
     1953//
     1954function ucp_preg_replace($pattern, $replace, $subject)
     1955{
     1956        $replaced = preg_replace($pattern, $replace, $subject);
     1957
     1958        // If preg_replace() returns false, this probably means unicode support is not built-in, so we need to modify the pattern a little
     1959        if ($replaced === false)
     1960        {
     1961                if (is_array($pattern))
     1962                {
     1963                        foreach ($pattern as $cur_key => $cur_pattern)
     1964                                $pattern[$cur_key] = str_replace('\p{L}\p{N}', '\w', $cur_pattern);
     1965
     1966                        $replaced = preg_replace($pattern, $replace, $subject);
     1967                }
     1968                else
     1969                        $replaced = preg_replace(str_replace('\p{L}\p{N}', '\w', $pattern), $replace, $subject);
     1970        }
     1971
     1972        return $replaced;
    9731973}
    9741974
     
    9941994                        <thead>
    9951995                                <tr>
    996                                         <th class="tcl" scope="col">Temps (s)</th>
    997                                         <th class="tcr" scope="col">Requête</th>
     1996                                        <th class="tcl" scope="col"><?php echo $lang_common['Query times'] ?></th>
     1997                                        <th class="tcr" scope="col"><?php echo $lang_common['Query'] ?></th>
    9981998                                </tr>
    9991999                        </thead>
     
    10022002
    10032003        $query_time_total = 0.0;
    1004         while (list(, $cur_query) = @each($saved_queries))
     2004        foreach ($saved_queries as $cur_query)
    10052005        {
    10062006                $query_time_total += $cur_query[1];
     
    10172017?>
    10182018                                <tr>
    1019                                         <td class="tcl" colspan="2">Temps total requête : <?php echo $query_time_total ?> s</td>
     2019                                        <td class="tcl" colspan="2"><?php printf($lang_common['Total query time'], $query_time_total.' s') ?></td>
    10202020                                </tr>
    10212021                        </tbody>
     
    10302030
    10312031//
    1032 // Unset any variables instantiated as a result of register_globals being enabled
    1033 //
    1034 function unregister_globals()
    1035 {
    1036         // Prevent script.php?GLOBALS[foo]=bar
    1037         if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS']))
    1038                 exit('I\'ll have a steak sandwich and... a steak sandwich.');
    1039        
    1040         // Variables that shouldn't be unset
    1041         $no_unset = array('GLOBALS', '_GET', '_POST', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES');
    1042        
    1043         // Remove elements in $GLOBALS that are present in any of the superglobals
    1044         $input = array_merge($_GET, $_POST, $_COOKIE, $_SERVER, $_ENV, $_FILES, isset($_SESSION) && is_array($_SESSION) ? $_SESSION : array());
    1045         foreach ($input as $k => $v)
    1046         {
    1047                 if (!in_array($k, $no_unset) && isset($GLOBALS[$k]))
    1048                         unset($GLOBALS[$k]);
    1049         }
    1050 }
    1051 
    1052 
    1053 //
    10542032// Dump contents of variable(s)
    10552033//
Note: See TracChangeset for help on using the changeset viewer.