phpBB
Statistics
| Revision:

root / branches / phpBB-3_0_0 / phpBB / includes / functions.php

History | View | Annotate | Download (137.6 kB)

1
<?php
2
/**
3
*
4
* @package phpBB3
5
* @version $Id: functions.php 11682 2012-02-11 10:45:09Z git-gate $
6
* @copyright (c) 2005 phpBB Group
7
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
8
*
9
*/
10
11
/**
12
* @ignore
13
*/
14
if (!defined('IN_PHPBB'))
15
{
16
        exit;
17
}
18
19
// Common global functions
20
21
/**
22
* set_var
23
*
24
* Set variable, used by {@link request_var the request_var function}
25
*
26
* @access private
27
*/
28
function set_var(&$result, $var, $type, $multibyte = false)
29
{
30
        settype($var, $type);
31
        $result = $var;
32
33
        if ($type == 'string')
34
        {
35
                $result = trim(htmlspecialchars(str_replace(array("\r\n", "\r", "\0"), array("\n", "\n", ''), $result), ENT_COMPAT, 'UTF-8'));
36
37
                if (!empty($result))
38
                {
39
                        // Make sure multibyte characters are wellformed
40
                        if ($multibyte)
41
                        {
42
                                if (!preg_match('/^./u', $result))
43
                                {
44
                                        $result = '';
45
                                }
46
                        }
47
                        else
48
                        {
49
                                // no multibyte, allow only ASCII (0-127)
50
                                $result = preg_replace('/[\x80-\xFF]/', '?', $result);
51
                        }
52
                }
53
54
                $result = (STRIP) ? stripslashes($result) : $result;
55
        }
56
}
57
58
/**
59
* request_var
60
*
61
* Used to get passed variable
62
*/
63
function request_var($var_name, $default, $multibyte = false, $cookie = false)
64
{
65
        if (!$cookie && isset($_COOKIE[$var_name]))
66
        {
67
                if (!isset($_GET[$var_name]) && !isset($_POST[$var_name]))
68
                {
69
                        return (is_array($default)) ? array() : $default;
70
                }
71
                $_REQUEST[$var_name] = isset($_POST[$var_name]) ? $_POST[$var_name] : $_GET[$var_name];
72
        }
73
74
        $super_global = ($cookie) ? '_COOKIE' : '_REQUEST';
75
        if (!isset($GLOBALS[$super_global][$var_name]) || is_array($GLOBALS[$super_global][$var_name]) != is_array($default))
76
        {
77
                return (is_array($default)) ? array() : $default;
78
        }
79
80
        $var = $GLOBALS[$super_global][$var_name];
81
        if (!is_array($default))
82
        {
83
                $type = gettype($default);
84
        }
85
        else
86
        {
87
                list($key_type, $type) = each($default);
88
                $type = gettype($type);
89
                $key_type = gettype($key_type);
90
                if ($type == 'array')
91
                {
92
                        reset($default);
93
                        $default = current($default);
94
                        list($sub_key_type, $sub_type) = each($default);
95
                        $sub_type = gettype($sub_type);
96
                        $sub_type = ($sub_type == 'array') ? 'NULL' : $sub_type;
97
                        $sub_key_type = gettype($sub_key_type);
98
                }
99
        }
100
101
        if (is_array($var))
102
        {
103
                $_var = $var;
104
                $var = array();
105
106
                foreach ($_var as $k => $v)
107
                {
108
                        set_var($k, $k, $key_type);
109
                        if ($type == 'array' && is_array($v))
110
                        {
111
                                foreach ($v as $_k => $_v)
112
                                {
113
                                        if (is_array($_v))
114
                                        {
115
                                                $_v = null;
116
                                        }
117
                                        set_var($_k, $_k, $sub_key_type, $multibyte);
118
                                        set_var($var[$k][$_k], $_v, $sub_type, $multibyte);
119
                                }
120
                        }
121
                        else
122
                        {
123
                                if ($type == 'array' || is_array($v))
124
                                {
125
                                        $v = null;
126
                                }
127
                                set_var($var[$k], $v, $type, $multibyte);
128
                        }
129
                }
130
        }
131
        else
132
        {
133
                set_var($var, $var, $type, $multibyte);
134
        }
135
136
        return $var;
137
}
138
139
/**
140
* Set config value. Creates missing config entry.
141
*/
142
function set_config($config_name, $config_value, $is_dynamic = false)
143
{
144
        global $db, $cache, $config;
145
146
        $sql = 'UPDATE ' . CONFIG_TABLE . "
147
                SET config_value = '" . $db->sql_escape($config_value) . "'
148
                WHERE config_name = '" . $db->sql_escape($config_name) . "'";
149
        $db->sql_query($sql);
150
151
        if (!$db->sql_affectedrows() && !isset($config[$config_name]))
152
        {
153
                $sql = 'INSERT INTO ' . CONFIG_TABLE . ' ' . $db->sql_build_array('INSERT', array(
154
                        'config_name'        => $config_name,
155
                        'config_value'        => $config_value,
156
                        'is_dynamic'        => ($is_dynamic) ? 1 : 0));
157
                $db->sql_query($sql);
158
        }
159
160
        $config[$config_name] = $config_value;
161
162
        if (!$is_dynamic)
163
        {
164
                $cache->destroy('config');
165
        }
166
}
167
168
/**
169
* Set dynamic config value with arithmetic operation.
170
*/
171
function set_config_count($config_name, $increment, $is_dynamic = false)
172
{
173
        global $db, $cache;
174
175
        switch ($db->sql_layer)
176
        {
177
                case 'firebird':
178
                        // Precision must be from 1 to 18
179
                        $sql_update = 'CAST(CAST(config_value as DECIMAL(18, 0)) + ' . (int) $increment . ' as VARCHAR(255))';
180
                break;
181
182
                case 'postgres':
183
                        // Need to cast to text first for PostgreSQL 7.x
184
                        $sql_update = 'CAST(CAST(config_value::text as DECIMAL(255, 0)) + ' . (int) $increment . ' as VARCHAR(255))';
185
                break;
186
187
                // MySQL, SQlite, mssql, mssql_odbc, oracle
188
                default:
189
                        $sql_update = 'config_value + ' . (int) $increment;
190
                break;
191
        }
192
193
        $db->sql_query('UPDATE ' . CONFIG_TABLE . ' SET config_value = ' . $sql_update . " WHERE config_name = '" . $db->sql_escape($config_name) . "'");
194
195
        if (!$is_dynamic)
196
        {
197
                $cache->destroy('config');
198
        }
199
}
200
201
/**
202
* Generates an alphanumeric random string of given length
203
*
204
* @return string
205
*/
206
function gen_rand_string($num_chars = 8)
207
{
208
        // [a, z] + [0, 9] = 36
209
        return substr(strtoupper(base_convert(unique_id(), 16, 36)), 0, $num_chars);
210
}
211
212
/**
213
* Generates a user-friendly alphanumeric random string of given length
214
* We remove 0 and O so users cannot confuse those in passwords etc.
215
*
216
* @return string
217
*/
218
function gen_rand_string_friendly($num_chars = 8)
219
{
220
        $rand_str = unique_id();
221
222
        // Remove Z and Y from the base_convert(), replace 0 with Z and O with Y
223
        // [a, z] + [0, 9] - {z, y} = [a, z] + [0, 9] - {0, o} = 34
224
        $rand_str = str_replace(array('0', 'O'), array('Z', 'Y'), strtoupper(base_convert($rand_str, 16, 34)));
225
226
        return substr($rand_str, 0, $num_chars);
227
}
228
229
/**
230
* Return unique id
231
* @param string $extra additional entropy
232
*/
233
function unique_id($extra = 'c')
234
{
235
        static $dss_seeded = false;
236
        global $config;
237
238
        $val = $config['rand_seed'] . microtime();
239
        $val = md5($val);
240
        $config['rand_seed'] = md5($config['rand_seed'] . $val . $extra);
241
242
        if ($dss_seeded !== true && ($config['rand_seed_last_update'] < time() - rand(1,10)))
243
        {
244
                set_config('rand_seed_last_update', time(), true);
245
                set_config('rand_seed', $config['rand_seed'], true);
246
                $dss_seeded = true;
247
        }
248
249
        return substr($val, 4, 16);
250
}
251
252
/**
253
* Wrapper for mt_rand() which allows swapping $min and $max parameters.
254
*
255
* PHP does not allow us to swap the order of the arguments for mt_rand() anymore.
256
* (since PHP 5.3.4, see http://bugs.php.net/46587)
257
*
258
* @param int $min                Lowest value to be returned
259
* @param int $max                Highest value to be returned
260
*
261
* @return int                        Random integer between $min and $max (or $max and $min)
262
*/
263
function phpbb_mt_rand($min, $max)
264
{
265
        return ($min > $max) ? mt_rand($max, $min) : mt_rand($min, $max);
266
}
267
268
/**
269
* Wrapper for getdate() which returns the equivalent array for UTC timestamps.
270
*
271
* @param int $time                Unix timestamp (optional)
272
*
273
* @return array                        Returns an associative array of information related to the timestamp.
274
*                                                See http://www.php.net/manual/en/function.getdate.php
275
*/
276
function phpbb_gmgetdate($time = false)
277
{
278
        if ($time === false)
279
        {
280
                $time = time();
281
        }
282
283
        // getdate() interprets timestamps in local time.
284
        // What follows uses the fact that getdate() and
285
        // date('Z') balance each other out.
286
        return getdate($time - date('Z'));
287
}
288
289
/**
290
* Return formatted string for filesizes
291
*
292
* @param int        $value                        filesize in bytes
293
* @param bool        $string_only        true if language string should be returned
294
* @param array        $allowed_units        only allow these units (data array indexes)
295
*
296
* @return mixed                                        data array if $string_only is false
297
* @author bantu
298
*/
299
function get_formatted_filesize($value, $string_only = true, $allowed_units = false)
300
{
301
        global $user;
302
303
        $available_units = array(
304
                'gb' => array(
305
                        'min'                 => 1073741824, // pow(2, 30)
306
                        'index'                => 3,
307
                        'si_unit'        => 'GB',
308
                        'iec_unit'        => 'GIB',
309
                ),
310
                'mb' => array(
311
                        'min'                => 1048576, // pow(2, 20)
312
                        'index'                => 2,
313
                        'si_unit'        => 'MB',
314
                        'iec_unit'        => 'MIB',
315
                ),
316
                'kb' => array(
317
                        'min'                => 1024, // pow(2, 10)
318
                        'index'                => 1,
319
                        'si_unit'        => 'KB',
320
                        'iec_unit'        => 'KIB',
321
                ),
322
                'b' => array(
323
                        'min'                => 0,
324
                        'index'                => 0,
325
                        'si_unit'        => 'BYTES', // Language index
326
                        'iec_unit'        => 'BYTES',  // Language index
327
                ),
328
        );
329
330
        foreach ($available_units as $si_identifier => $unit_info)
331
        {
332
                if (!empty($allowed_units) && $si_identifier != 'b' && !in_array($si_identifier, $allowed_units))
333
                {
334
                        continue;
335
                }
336
337
                if ($value >= $unit_info['min'])
338
                {
339
                        $unit_info['si_identifier'] = $si_identifier;
340
341
                        break;
342
                }
343
        }
344
        unset($available_units);
345
346
        for ($i = 0; $i < $unit_info['index']; $i++)
347
        {
348
                $value /= 1024;
349
        }
350
        $value = round($value, 2);
351
352
        // Lookup units in language dictionary
353
        $unit_info['si_unit'] = (isset($user->lang[$unit_info['si_unit']])) ? $user->lang[$unit_info['si_unit']] : $unit_info['si_unit'];
354
        $unit_info['iec_unit'] = (isset($user->lang[$unit_info['iec_unit']])) ? $user->lang[$unit_info['iec_unit']] : $unit_info['iec_unit'];
355
356
        // Default to IEC
357
        $unit_info['unit'] = $unit_info['iec_unit'];
358
359
        if (!$string_only)
360
        {
361
                $unit_info['value'] = $value;
362
363
                return $unit_info;
364
        }
365
366
        return $value  . ' ' . $unit_info['unit'];
367
}
368
369
/**
370
* Determine whether we are approaching the maximum execution time. Should be called once
371
* at the beginning of the script in which it's used.
372
* @return        bool        Either true if the maximum execution time is nearly reached, or false
373
*                                        if some time is still left.
374
*/
375
function still_on_time($extra_time = 15)
376
{
377
        static $max_execution_time, $start_time;
378
379
        $time = explode(' ', microtime());
380
        $current_time = $time[0] + $time[1];
381
382
        if (empty($max_execution_time))
383
        {
384
                $max_execution_time = (function_exists('ini_get')) ? (int) @ini_get('max_execution_time') : (int) @get_cfg_var('max_execution_time');
385
386
                // If zero, then set to something higher to not let the user catch the ten seconds barrier.
387
                if ($max_execution_time === 0)
388
                {
389
                        $max_execution_time = 50 + $extra_time;
390
                }
391
392
                $max_execution_time = min(max(10, ($max_execution_time - $extra_time)), 50);
393
394
                // For debugging purposes
395
                // $max_execution_time = 10;
396
397
                global $starttime;
398
                $start_time = (empty($starttime)) ? $current_time : $starttime;
399
        }
400
401
        return (ceil($current_time - $start_time) < $max_execution_time) ? true : false;
402
}
403
404
/**
405
*
406
* @version Version 0.1 / slightly modified for phpBB 3.0.x (using $H$ as hash type identifier)
407
*
408
* Portable PHP password hashing framework.
409
*
410
* Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
411
* the public domain.
412
*
413
* There's absolutely no warranty.
414
*
415
* The homepage URL for this framework is:
416
*
417
*        http://www.openwall.com/phpass/
418
*
419
* Please be sure to update the Version line if you edit this file in any way.
420
* It is suggested that you leave the main version number intact, but indicate
421
* your project name (after the slash) and add your own revision information.
422
*
423
* Please do not change the "private" password hashing method implemented in
424
* here, thereby making your hashes incompatible.  However, if you must, please
425
* change the hash type identifier (the "$P$") to something different.
426
*
427
* Obviously, since this code is in the public domain, the above are not
428
* requirements (there can be none), but merely suggestions.
429
*
430
*
431
* Hash the password
432
*/
433
function phpbb_hash($password)
434
{
435
        $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
436
437
        $random_state = unique_id();
438
        $random = '';
439
        $count = 6;
440
441
        if (($fh = @fopen('/dev/urandom', 'rb')))
442
        {
443
                $random = fread($fh, $count);
444
                fclose($fh);
445
        }
446
447
        if (strlen($random) < $count)
448
        {
449
                $random = '';
450
451
                for ($i = 0; $i < $count; $i += 16)
452
                {
453
                        $random_state = md5(unique_id() . $random_state);
454
                        $random .= pack('H*', md5($random_state));
455
                }
456
                $random = substr($random, 0, $count);
457
        }
458
459
        $hash = _hash_crypt_private($password, _hash_gensalt_private($random, $itoa64), $itoa64);
460
461
        if (strlen($hash) == 34)
462
        {
463
                return $hash;
464
        }
465
466
        return md5($password);
467
}
468
469
/**
470
* Check for correct password
471
*
472
* @param string $password The password in plain text
473
* @param string $hash The stored password hash
474
*
475
* @return bool Returns true if the password is correct, false if not.
476
*/
477
function phpbb_check_hash($password, $hash)
478
{
479
        $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
480
        if (strlen($hash) == 34)
481
        {
482
                return (_hash_crypt_private($password, $hash, $itoa64) === $hash) ? true : false;
483
        }
484
485
        return (md5($password) === $hash) ? true : false;
486
}
487
488
/**
489
* Generate salt for hash generation
490
*/
491
function _hash_gensalt_private($input, &$itoa64, $iteration_count_log2 = 6)
492
{
493
        if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
494
        {
495
                $iteration_count_log2 = 8;
496
        }
497
498
        $output = '$H$';
499
        $output .= $itoa64[min($iteration_count_log2 + ((PHP_VERSION >= 5) ? 5 : 3), 30)];
500
        $output .= _hash_encode64($input, 6, $itoa64);
501
502
        return $output;
503
}
504
505
/**
506
* Encode hash
507
*/
508
function _hash_encode64($input, $count, &$itoa64)
509
{
510
        $output = '';
511
        $i = 0;
512
513
        do
514
        {
515
                $value = ord($input[$i++]);
516
                $output .= $itoa64[$value & 0x3f];
517
518
                if ($i < $count)
519
                {
520
                        $value |= ord($input[$i]) << 8;
521
                }
522
523
                $output .= $itoa64[($value >> 6) & 0x3f];
524
525
                if ($i++ >= $count)
526
                {
527
                        break;
528
                }
529
530
                if ($i < $count)
531
                {
532
                        $value |= ord($input[$i]) << 16;
533
                }
534
535
                $output .= $itoa64[($value >> 12) & 0x3f];
536
537
                if ($i++ >= $count)
538
                {
539
                        break;
540
                }
541
542
                $output .= $itoa64[($value >> 18) & 0x3f];
543
        }
544
        while ($i < $count);
545
546
        return $output;
547
}
548
549
/**
550
* The crypt function/replacement
551
*/
552
function _hash_crypt_private($password, $setting, &$itoa64)
553
{
554
        $output = '*';
555
556
        // Check for correct hash
557
        if (substr($setting, 0, 3) != '$H$' && substr($setting, 0, 3) != '$P$')
558
        {
559
                return $output;
560
        }
561
562
        $count_log2 = strpos($itoa64, $setting[3]);
563
564
        if ($count_log2 < 7 || $count_log2 > 30)
565
        {
566
                return $output;
567
        }
568
569
        $count = 1 << $count_log2;
570
        $salt = substr($setting, 4, 8);
571
572
        if (strlen($salt) != 8)
573
        {
574
                return $output;
575
        }
576
577
        /**
578
        * We're kind of forced to use MD5 here since it's the only
579
        * cryptographic primitive available in all versions of PHP
580
        * currently in use.  To implement our own low-level crypto
581
        * in PHP would result in much worse performance and
582
        * consequently in lower iteration counts and hashes that are
583
        * quicker to crack (by non-PHP code).
584
        */
585
        if (PHP_VERSION >= 5)
586
        {
587
                $hash = md5($salt . $password, true);
588
                do
589
                {
590
                        $hash = md5($hash . $password, true);
591
                }
592
                while (--$count);
593
        }
594
        else
595
        {
596
                $hash = pack('H*', md5($salt . $password));
597
                do
598
                {
599
                        $hash = pack('H*', md5($hash . $password));
600
                }
601
                while (--$count);
602
        }
603
604
        $output = substr($setting, 0, 12);
605
        $output .= _hash_encode64($hash, 16, $itoa64);
606
607
        return $output;
608
}
609
610
/**
611
* Hashes an email address to a big integer
612
*
613
* @param string $email                Email address
614
*
615
* @return string                        Unsigned Big Integer
616
*/
617
function phpbb_email_hash($email)
618
{
619
        return sprintf('%u', crc32(strtolower($email))) . strlen($email);
620
}
621
622
/**
623
* Wrapper for version_compare() that allows using uppercase A and B
624
* for alpha and beta releases.
625
*
626
* See http://www.php.net/manual/en/function.version-compare.php
627
*
628
* @param string $version1                First version number
629
* @param string $version2                Second version number
630
* @param string $operator                Comparison operator (optional)
631
*
632
* @return mixed                                        Boolean (true, false) if comparison operator is specified.
633
*                                                                Integer (-1, 0, 1) otherwise.
634
*/
635
function phpbb_version_compare($version1, $version2, $operator = null)
636
{
637
        $version1 = strtolower($version1);
638
        $version2 = strtolower($version2);
639
640
        if (is_null($operator))
641
        {
642
                return version_compare($version1, $version2);
643
        }
644
        else
645
        {
646
                return version_compare($version1, $version2, $operator);
647
        }
648
}
649
650
/**
651
* Global function for chmodding directories and files for internal use
652
*
653
* This function determines owner and group whom the file belongs to and user and group of PHP and then set safest possible file permissions.
654
* The function determines owner and group from common.php file and sets the same to the provided file.
655
* The function uses bit fields to build the permissions.
656
* The function sets the appropiate execute bit on directories.
657
*
658
* Supported constants representing bit fields are:
659
*
660
* CHMOD_ALL - all permissions (7)
661
* CHMOD_READ - read permission (4)
662
* CHMOD_WRITE - write permission (2)
663
* CHMOD_EXECUTE - execute permission (1)
664
*
665
* NOTE: The function uses POSIX extension and fileowner()/filegroup() functions. If any of them is disabled, this function tries to build proper permissions, by calling is_readable() and is_writable() functions.
666
*
667
* @param string        $filename        The file/directory to be chmodded
668
* @param int        $perms                Permissions to set
669
*
670
* @return bool        true on success, otherwise false
671
* @author faw, phpBB Group
672
*/
673
function phpbb_chmod($filename, $perms = CHMOD_READ)
674
{
675
        static $_chmod_info;
676
677
        // Return if the file no longer exists.
678
        if (!file_exists($filename))
679
        {
680
                return false;
681
        }
682
683
        // Determine some common vars
684
        if (empty($_chmod_info))
685
        {
686
                if (!function_exists('fileowner') || !function_exists('filegroup'))
687
                {
688
                        // No need to further determine owner/group - it is unknown
689
                        $_chmod_info['process'] = false;
690
                }
691
                else
692
                {
693
                        global $phpbb_root_path, $phpEx;
694
695
                        // Determine owner/group of common.php file and the filename we want to change here
696
                        $common_php_owner = @fileowner($phpbb_root_path . 'common.' . $phpEx);
697
                        $common_php_group = @filegroup($phpbb_root_path . 'common.' . $phpEx);
698
699
                        // And the owner and the groups PHP is running under.
700
                        $php_uid = (function_exists('posix_getuid')) ? @posix_getuid() : false;
701
                        $php_gids = (function_exists('posix_getgroups')) ? @posix_getgroups() : false;
702
703
                        // If we are unable to get owner/group, then do not try to set them by guessing
704
                        if (!$php_uid || empty($php_gids) || !$common_php_owner || !$common_php_group)
705
                        {
706
                                $_chmod_info['process'] = false;
707
                        }
708
                        else
709
                        {
710
                                $_chmod_info = array(
711
                                        'process'                => true,
712
                                        'common_owner'        => $common_php_owner,
713
                                        'common_group'        => $common_php_group,
714
                                        'php_uid'                => $php_uid,
715
                                        'php_gids'                => $php_gids,
716
                                );
717
                        }
718
                }
719
        }
720
721
        if ($_chmod_info['process'])
722
        {
723
                $file_uid = @fileowner($filename);
724
                $file_gid = @filegroup($filename);
725
726
                // Change owner
727
                if (@chown($filename, $_chmod_info['common_owner']))
728
                {
729
                        clearstatcache();
730
                        $file_uid = @fileowner($filename);
731
                }
732
733
                // Change group
734
                if (@chgrp($filename, $_chmod_info['common_group']))
735
                {
736
                        clearstatcache();
737
                        $file_gid = @filegroup($filename);
738
                }
739
740
                // If the file_uid/gid now match the one from common.php we can process further, else we are not able to change something
741
                if ($file_uid != $_chmod_info['common_owner'] || $file_gid != $_chmod_info['common_group'])
742
                {
743
                        $_chmod_info['process'] = false;
744
                }
745
        }
746
747
        // Still able to process?
748
        if ($_chmod_info['process'])
749
        {
750
                if ($file_uid == $_chmod_info['php_uid'])
751
                {
752
                        $php = 'owner';
753
                }
754
                else if (in_array($file_gid, $_chmod_info['php_gids']))
755
                {
756
                        $php = 'group';
757
                }
758
                else
759
                {
760
                        // Since we are setting the everyone bit anyway, no need to do expensive operations
761
                        $_chmod_info['process'] = false;
762
                }
763
        }
764
765
        // We are not able to determine or change something
766
        if (!$_chmod_info['process'])
767
        {
768
                $php = 'other';
769
        }
770
771
        // Owner always has read/write permission
772
        $owner = CHMOD_READ | CHMOD_WRITE;
773
        if (is_dir($filename))
774
        {
775
                $owner |= CHMOD_EXECUTE;
776
777
                // Only add execute bit to the permission if the dir needs to be readable
778
                if ($perms & CHMOD_READ)
779
                {
780
                        $perms |= CHMOD_EXECUTE;
781
                }
782
        }
783
784
        switch ($php)
785
        {
786
                case 'owner':
787
                        $result = @chmod($filename, ($owner << 6) + (0 << 3) + (0 << 0));
788
789
                        clearstatcache();
790
791
                        if (is_readable($filename) && phpbb_is_writable($filename))
792
                        {
793
                                break;
794
                        }
795
796
                case 'group':
797
                        $result = @chmod($filename, ($owner << 6) + ($perms << 3) + (0 << 0));
798
799
                        clearstatcache();
800
801
                        if ((!($perms & CHMOD_READ) || is_readable($filename)) && (!($perms & CHMOD_WRITE) || phpbb_is_writable($filename)))
802
                        {
803
                                break;
804
                        }
805
806
                case 'other':
807
                        $result = @chmod($filename, ($owner << 6) + ($perms << 3) + ($perms << 0));
808
809
                        clearstatcache();
810
811
                        if ((!($perms & CHMOD_READ) || is_readable($filename)) && (!($perms & CHMOD_WRITE) || phpbb_is_writable($filename)))
812
                        {
813
                                break;
814
                        }
815
816
                default:
817
                        return false;
818
                break;
819
        }
820
821
        return $result;
822
}
823
824
/**
825
* Test if a file/directory is writable
826
*
827
* This function calls the native is_writable() when not running under
828
* Windows and it is not disabled.
829
*
830
* @param string $file Path to perform write test on
831
* @return bool True when the path is writable, otherwise false.
832
*/
833
function phpbb_is_writable($file)
834
{
835
        if (strtolower(substr(PHP_OS, 0, 3)) === 'win' || !function_exists('is_writable'))
836
        {
837
                if (file_exists($file))
838
                {
839
                        // Canonicalise path to absolute path
840
                        $file = phpbb_realpath($file);
841
842
                        if (is_dir($file))
843
                        {
844
                                // Test directory by creating a file inside the directory
845
                                $result = @tempnam($file, 'i_w');
846
847
                                if (is_string($result) && file_exists($result))
848
                                {
849
                                        unlink($result);
850
851
                                        // Ensure the file is actually in the directory (returned realpathed)
852
                                        return (strpos($result, $file) === 0) ? true : false;
853
                                }
854
                        }
855
                        else
856
                        {
857
                                $handle = @fopen($file, 'r+');
858
859
                                if (is_resource($handle))
860
                                {
861
                                        fclose($handle);
862
                                        return true;
863
                                }
864
                        }
865
                }
866
                else
867
                {
868
                        // file does not exist test if we can write to the directory
869
                        $dir = dirname($file);
870
871
                        if (file_exists($dir) && is_dir($dir) && phpbb_is_writable($dir))
872
                        {
873
                                return true;
874
                        }
875
                }
876
877
                return false;
878
        }
879
        else
880
        {
881
                return is_writable($file);
882
        }
883
}
884
885
// Compatibility functions
886
887
if (!function_exists('array_combine'))
888
{
889
        /**
890
        * A wrapper for the PHP5 function array_combine()
891
        * @param array $keys contains keys for the resulting array
892
        * @param array $values contains values for the resulting array
893
        *
894
        * @return Returns an array by using the values from the keys array as keys and the
895
        *         values from the values array as the corresponding values. Returns false if the
896
        *         number of elements for each array isn't equal or if the arrays are empty.
897
        */
898
        function array_combine($keys, $values)
899
        {
900
                $keys = array_values($keys);
901
                $values = array_values($values);
902
903
                $n = sizeof($keys);
904
                $m = sizeof($values);
905
                if (!$n || !$m || ($n != $m))
906
                {
907
                        return false;
908
                }
909
910
                $combined = array();
911
                for ($i = 0; $i < $n; $i++)
912
                {
913
                        $combined[$keys[$i]] = $values[$i];
914
                }
915
                return $combined;
916
        }
917
}
918
919
if (!function_exists('str_split'))
920
{
921
        /**
922
        * A wrapper for the PHP5 function str_split()
923
        * @param array $string contains the string to be converted
924
        * @param array $split_length contains the length of each chunk
925
        *
926
        * @return  Converts a string to an array. If the optional split_length parameter is specified,
927
        *          the returned array will be broken down into chunks with each being split_length in length,
928
        *          otherwise each chunk will be one character in length. FALSE is returned if split_length is
929
        *          less than 1. If the split_length length exceeds the length of string, the entire string is
930
        *          returned as the first (and only) array element.
931
        */
932
        function str_split($string, $split_length = 1)
933
        {
934
                if ($split_length < 1)
935
                {
936
                        return false;
937
                }
938
                else if ($split_length >= strlen($string))
939
                {
940
                        return array($string);
941
                }
942
                else
943
                {
944
                        preg_match_all('#.{1,' . $split_length . '}#s', $string, $matches);
945
                        return $matches[0];
946
                }
947
        }
948
}
949
950
if (!function_exists('stripos'))
951
{
952
        /**
953
        * A wrapper for the PHP5 function stripos
954
        * Find position of first occurrence of a case-insensitive string
955
        *
956
        * @param string $haystack is the string to search in
957
        * @param string $needle is the string to search for
958
        *
959
        * @return mixed Returns the numeric position of the first occurrence of needle in the haystack string. Unlike strpos(), stripos() is case-insensitive.
960
        * Note that the needle may be a string of one or more characters.
961
        * If needle is not found, stripos() will return boolean FALSE.
962
        */
963
        function stripos($haystack, $needle)
964
        {
965
                if (preg_match('#' . preg_quote($needle, '#') . '#i', $haystack, $m))
966
                {
967
                        return strpos($haystack, $m[0]);
968
                }
969
970
                return false;
971
        }
972
}
973
974
/**
975
* Checks if a path ($path) is absolute or relative
976
*
977
* @param string $path Path to check absoluteness of
978
* @return boolean
979
*/
980
function is_absolute($path)
981
{
982
        return ($path[0] == '/' || (DIRECTORY_SEPARATOR == '\\' && preg_match('#^[a-z]:[/\\\]#i', $path))) ? true : false;
983
}
984
985
/**
986
* @author Chris Smith <chris@project-minerva.org>
987
* @copyright 2006 Project Minerva Team
988
* @param string $path The path which we should attempt to resolve.
989
* @return mixed
990
*/
991
function phpbb_own_realpath($path)
992
{
993
        // Now to perform funky shizzle
994
995
        // Switch to use UNIX slashes
996
        $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
997
        $path_prefix = '';
998
999
        // Determine what sort of path we have
1000
        if (is_absolute($path))
1001
        {
1002
                $absolute = true;
1003
1004
                if ($path[0] == '/')
1005
                {
1006
                        // Absolute path, *NIX style
1007
                        $path_prefix = '';
1008
                }
1009
                else
1010
                {
1011
                        // Absolute path, Windows style
1012
                        // Remove the drive letter and colon
1013
                        $path_prefix = $path[0] . ':';
1014
                        $path = substr($path, 2);
1015
                }
1016
        }
1017
        else
1018
        {
1019
                // Relative Path
1020
                // Prepend the current working directory
1021
                if (function_exists('getcwd'))
1022
                {
1023
                        // This is the best method, hopefully it is enabled!
1024
                        $path = str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . '/' . $path;
1025
                        $absolute = true;
1026
                        if (preg_match('#^[a-z]:#i', $path))
1027
                        {
1028
                                $path_prefix = $path[0] . ':';
1029
                                $path = substr($path, 2);
1030
                        }
1031
                        else
1032
                        {
1033
                                $path_prefix = '';
1034
                        }
1035
                }
1036
                else if (isset($_SERVER['SCRIPT_FILENAME']) && !empty($_SERVER['SCRIPT_FILENAME']))
1037
                {
1038
                        // Warning: If chdir() has been used this will lie!
1039
                        // Warning: This has some problems sometime (CLI can create them easily)
1040
                        $path = str_replace(DIRECTORY_SEPARATOR, '/', dirname($_SERVER['SCRIPT_FILENAME'])) . '/' . $path;
1041
                        $absolute = true;
1042
                        $path_prefix = '';
1043
                }
1044
                else
1045
                {
1046
                        // We have no way of getting the absolute path, just run on using relative ones.
1047
                        $absolute = false;
1048
                        $path_prefix = '.';
1049
                }
1050
        }
1051
1052
        // Remove any repeated slashes
1053
        $path = preg_replace('#/{2,}#', '/', $path);
1054
1055
        // Remove the slashes from the start and end of the path
1056
        $path = trim($path, '/');
1057
1058
        // Break the string into little bits for us to nibble on
1059
        $bits = explode('/', $path);
1060
1061
        // Remove any . in the path, renumber array for the loop below
1062
        $bits = array_values(array_diff($bits, array('.')));
1063
1064
        // Lets get looping, run over and resolve any .. (up directory)
1065
        for ($i = 0, $max = sizeof($bits); $i < $max; $i++)
1066
        {
1067
                // @todo Optimise
1068
                if ($bits[$i] == '..' )
1069
                {
1070
                        if (isset($bits[$i - 1]))
1071
                        {
1072
                                if ($bits[$i - 1] != '..')
1073
                                {
1074
                                        // We found a .. and we are able to traverse upwards, lets do it!
1075
                                        unset($bits[$i]);
1076
                                        unset($bits[$i - 1]);
1077
                                        $i -= 2;
1078
                                        $max -= 2;
1079
                                        $bits = array_values($bits);
1080
                                }
1081
                        }
1082
                        else if ($absolute) // ie. !isset($bits[$i - 1]) && $absolute
1083
                        {
1084
                                // We have an absolute path trying to descend above the root of the filesystem
1085
                                // ... Error!
1086
                                return false;
1087
                        }
1088
                }
1089
        }
1090
1091
        // Prepend the path prefix
1092
        array_unshift($bits, $path_prefix);
1093
1094
        $resolved = '';
1095
1096
        $max = sizeof($bits) - 1;
1097
1098
        // Check if we are able to resolve symlinks, Windows cannot.
1099
        $symlink_resolve = (function_exists('readlink')) ? true : false;
1100
1101
        foreach ($bits as $i => $bit)
1102
        {
1103
                if (@is_dir("$resolved/$bit") || ($i == $max && @is_file("$resolved/$bit")))
1104
                {
1105
                        // Path Exists
1106
                        if ($symlink_resolve && is_link("$resolved/$bit") && ($link = readlink("$resolved/$bit")))
1107
                        {
1108
                                // Resolved a symlink.
1109
                                $resolved = $link . (($i == $max) ? '' : '/');
1110
                                continue;
1111
                        }
1112
                }
1113
                else
1114
                {
1115
                        // Something doesn't exist here!
1116
                        // This is correct realpath() behaviour but sadly open_basedir and safe_mode make this problematic
1117
                        // return false;
1118
                }
1119
                $resolved .= $bit . (($i == $max) ? '' : '/');
1120
        }
1121
1122
        // @todo If the file exists fine and open_basedir only has one path we should be able to prepend it
1123
        // because we must be inside that basedir, the question is where...
1124
        // @internal The slash in is_dir() gets around an open_basedir restriction
1125
        if (!@file_exists($resolved) || (!@is_dir($resolved . '/') && !is_file($resolved)))
1126
        {
1127
                return false;
1128
        }
1129
1130
        // Put the slashes back to the native operating systems slashes
1131
        $resolved = str_replace('/', DIRECTORY_SEPARATOR, $resolved);
1132
1133
        // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
1134
        if (substr($resolved, -1) == DIRECTORY_SEPARATOR)
1135
        {
1136
                return substr($resolved, 0, -1);
1137
        }
1138
1139
        return $resolved; // We got here, in the end!
1140
}
1141
1142
if (!function_exists('realpath'))
1143
{
1144
        /**
1145
        * A wrapper for realpath
1146
        * @ignore
1147
        */
1148
        function phpbb_realpath($path)
1149
        {
1150
                return phpbb_own_realpath($path);
1151
        }
1152
}
1153
else
1154
{
1155
        /**
1156
        * A wrapper for realpath
1157
        */
1158
        function phpbb_realpath($path)
1159
        {
1160
                $realpath = realpath($path);
1161
1162
                // Strangely there are provider not disabling realpath but returning strange values. :o
1163
                // We at least try to cope with them.
1164
                if ($realpath === $path || $realpath === false)
1165
                {
1166
                        return phpbb_own_realpath($path);
1167
                }
1168
1169
                // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
1170
                if (substr($realpath, -1) == DIRECTORY_SEPARATOR)
1171
                {
1172
                        $realpath = substr($realpath, 0, -1);
1173
                }
1174
1175
                return $realpath;
1176
        }
1177
}
1178
1179
if (!function_exists('htmlspecialchars_decode'))
1180
{
1181
        /**
1182
        * A wrapper for htmlspecialchars_decode
1183
        * @ignore
1184
        */
1185
        function htmlspecialchars_decode($string, $quote_style = ENT_COMPAT)
1186
        {
1187
                return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style)));
1188
        }
1189
}
1190
1191
// functions used for building option fields
1192
1193
/**
1194
* Pick a language, any language ...
1195
*/
1196
function language_select($default = '')
1197
{
1198
        global $db;
1199
1200
        $sql = 'SELECT lang_iso, lang_local_name
1201
                FROM ' . LANG_TABLE . '
1202
                ORDER BY lang_english_name';
1203
        $result = $db->sql_query($sql);
1204
1205
        $lang_options = '';
1206
        while ($row = $db->sql_fetchrow($result))
1207
        {
1208
                $selected = ($row['lang_iso'] == $default) ? ' selected="selected"' : '';
1209
                $lang_options .= '<option value="' . $row['lang_iso'] . '"' . $selected . '>' . $row['lang_local_name'] . '</option>';
1210
        }
1211
        $db->sql_freeresult($result);
1212
1213
        return $lang_options;
1214
}
1215
1216
/**
1217
* Pick a template/theme combo,
1218
*/
1219
function style_select($default = '', $all = false)
1220
{
1221
        global $db;
1222
1223
        $sql_where = (!$all) ? 'WHERE style_active = 1 ' : '';
1224
        $sql = 'SELECT style_id, style_name
1225
                FROM ' . STYLES_TABLE . "
1226
                $sql_where
1227
                ORDER BY style_name";
1228
        $result = $db->sql_query($sql);
1229
1230
        $style_options = '';
1231
        while ($row = $db->sql_fetchrow($result))
1232
        {
1233
                $selected = ($row['style_id'] == $default) ? ' selected="selected"' : '';
1234
                $style_options .= '<option value="' . $row['style_id'] . '"' . $selected . '>' . $row['style_name'] . '</option>';
1235
        }
1236
        $db->sql_freeresult($result);
1237
1238
        return $style_options;
1239
}
1240
1241
/**
1242
* Pick a timezone
1243
*/
1244
function tz_select($default = '', $truncate = false)
1245
{
1246
        global $user;
1247
1248
        $tz_select = '';
1249
        foreach ($user->lang['tz_zones'] as $offset => $zone)
1250
        {
1251
                if ($truncate)
1252
                {
1253
                        $zone_trunc = truncate_string($zone, 50, 255, false, '...');
1254
                }
1255
                else
1256
                {
1257
                        $zone_trunc = $zone;
1258
                }
1259
1260
                if (is_numeric($offset))
1261
                {
1262
                        $selected = ($offset == $default) ? ' selected="selected"' : '';
1263
                        $tz_select .= '<option title="' . $zone . '" value="' . $offset . '"' . $selected . '>' . $zone_trunc . '</option>';
1264
                }
1265
        }
1266
1267
        return $tz_select;
1268
}
1269
1270
// Functions handling topic/post tracking/marking
1271
1272
/**
1273
* Marks a topic/forum as read
1274
* Marks a topic as posted to
1275
*
1276
* @param int $user_id can only be used with $mode == 'post'
1277
*/
1278
function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0)
1279
{
1280
        global $db, $user, $config;
1281
1282
        if ($mode == 'all')
1283
        {
1284
                if ($forum_id === false || !sizeof($forum_id))
1285
                {
1286
                        if ($config['load_db_lastread'] && $user->data['is_registered'])
1287
                        {
1288
                                // Mark all forums read (index page)
1289
                                $db->sql_query('DELETE FROM ' . TOPICS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
1290
                                $db->sql_query('DELETE FROM ' . FORUMS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
1291
                                $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
1292
                        }
1293
                        else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1294
                        {
1295
                                $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1296
                                $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1297
1298
                                unset($tracking_topics['tf']);
1299
                                unset($tracking_topics['t']);
1300
                                unset($tracking_topics['f']);
1301
                                $tracking_topics['l'] = base_convert(time() - $config['board_startdate'], 10, 36);
1302
1303
                                $user->set_cookie('track', tracking_serialize($tracking_topics), time() + 31536000);
1304
                                $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking_topics)) : tracking_serialize($tracking_topics);
1305
1306
                                unset($tracking_topics);
1307
1308
                                if ($user->data['is_registered'])
1309
                                {
1310
                                        $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
1311
                                }
1312
                        }
1313
                }
1314
1315
                return;
1316
        }
1317
        else if ($mode == 'topics')
1318
        {
1319
                // Mark all topics in forums read
1320
                if (!is_array($forum_id))
1321
                {
1322
                        $forum_id = array($forum_id);
1323
                }
1324
1325
                // Add 0 to forums array to mark global announcements correctly
1326
                // $forum_id[] = 0;
1327
1328
                if ($config['load_db_lastread'] && $user->data['is_registered'])
1329
                {
1330
                        $sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . "
1331
                                WHERE user_id = {$user->data['user_id']}
1332
                                        AND " . $db->sql_in_set('forum_id', $forum_id);
1333
                        $db->sql_query($sql);
1334
1335
                        $sql = 'SELECT forum_id
1336
                                FROM ' . FORUMS_TRACK_TABLE . "
1337
                                WHERE user_id = {$user->data['user_id']}
1338
                                        AND " . $db->sql_in_set('forum_id', $forum_id);
1339
                        $result = $db->sql_query($sql);
1340
1341
                        $sql_update = array();
1342
                        while ($row = $db->sql_fetchrow($result))
1343
                        {
1344
                                $sql_update[] = (int) $row['forum_id'];
1345
                        }
1346
                        $db->sql_freeresult($result);
1347
1348
                        if (sizeof($sql_update))
1349
                        {
1350
                                $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . '
1351
                                        SET mark_time = ' . time() . "
1352
                                        WHERE user_id = {$user->data['user_id']}
1353
                                                AND " . $db->sql_in_set('forum_id', $sql_update);
1354
                                $db->sql_query($sql);
1355
                        }
1356
1357
                        if ($sql_insert = array_diff($forum_id, $sql_update))
1358
                        {
1359
                                $sql_ary = array();
1360
                                foreach ($sql_insert as $f_id)
1361
                                {
1362
                                        $sql_ary[] = array(
1363
                                                'user_id'        => (int) $user->data['user_id'],
1364
                                                'forum_id'        => (int) $f_id,
1365
                                                'mark_time'        => time()
1366
                                        );
1367
                                }
1368
1369
                                $db->sql_multi_insert(FORUMS_TRACK_TABLE, $sql_ary);
1370
                        }
1371
                }
1372
                else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1373
                {
1374
                        $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1375
                        $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
1376
1377
                        foreach ($forum_id as $f_id)
1378
                        {
1379
                                $topic_ids36 = (isset($tracking['tf'][$f_id])) ? $tracking['tf'][$f_id] : array();
1380
1381
                                if (isset($tracking['tf'][$f_id]))
1382
                                {
1383
                                        unset($tracking['tf'][$f_id]);
1384
                                }
1385
1386
                                foreach ($topic_ids36 as $topic_id36)
1387
                                {
1388
                                        unset($tracking['t'][$topic_id36]);
1389
                                }
1390
1391
                                if (isset($tracking['f'][$f_id]))
1392
                                {
1393
                                        unset($tracking['f'][$f_id]);
1394
                                }
1395
1396
                                $tracking['f'][$f_id] = base_convert(time() - $config['board_startdate'], 10, 36);
1397
                        }
1398
1399
                        if (isset($tracking['tf']) && empty($tracking['tf']))
1400
                        {
1401
                                unset($tracking['tf']);
1402
                        }
1403
1404
                        $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
1405
                        $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking);
1406
1407
                        unset($tracking);
1408
                }
1409
1410
                return;
1411
        }
1412
        else if ($mode == 'topic')
1413
        {
1414
                if ($topic_id === false || $forum_id === false)
1415
                {
1416
                        return;
1417
                }
1418
1419
                if ($config['load_db_lastread'] && $user->data['is_registered'])
1420
                {
1421
                        $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . '
1422
                                SET mark_time = ' . (($post_time) ? $post_time : time()) . "
1423
                                WHERE user_id = {$user->data['user_id']}
1424
                                        AND topic_id = $topic_id";
1425
                        $db->sql_query($sql);
1426
1427
                        // insert row
1428
                        if (!$db->sql_affectedrows())
1429
                        {
1430
                                $db->sql_return_on_error(true);
1431
1432
                                $sql_ary = array(
1433
                                        'user_id'                => (int) $user->data['user_id'],
1434
                                        'topic_id'                => (int) $topic_id,
1435
                                        'forum_id'                => (int) $forum_id,
1436
                                        'mark_time'                => ($post_time) ? (int) $post_time : time(),
1437
                                );
1438
1439
                                $db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1440
1441
                                $db->sql_return_on_error(false);
1442
                        }
1443
                }
1444
                else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1445
                {
1446
                        $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1447
                        $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
1448
1449
                        $topic_id36 = base_convert($topic_id, 10, 36);
1450
1451
                        if (!isset($tracking['t'][$topic_id36]))
1452
                        {
1453
                                $tracking['tf'][$forum_id][$topic_id36] = true;
1454
                        }
1455
1456
                        $post_time = ($post_time) ? $post_time : time();
1457
                        $tracking['t'][$topic_id36] = base_convert($post_time - $config['board_startdate'], 10, 36);
1458
1459
                        // If the cookie grows larger than 10000 characters we will remove the smallest value
1460
                        // This can result in old topics being unread - but most of the time it should be accurate...
1461
                        if (isset($_COOKIE[$config['cookie_name'] . '_track']) && strlen($_COOKIE[$config['cookie_name'] . '_track']) > 10000)
1462
                        {
1463
                                //echo 'Cookie grown too large' . print_r($tracking, true);
1464
1465
                                // We get the ten most minimum stored time offsets and its associated topic ids
1466
                                $time_keys = array();
1467
                                for ($i = 0; $i < 10 && sizeof($tracking['t']); $i++)
1468
                                {
1469
                                        $min_value = min($tracking['t']);
1470
                                        $m_tkey = array_search($min_value, $tracking['t']);
1471
                                        unset($tracking['t'][$m_tkey]);
1472
1473
                                        $time_keys[$m_tkey] = $min_value;
1474
                                }
1475
1476
                                // Now remove the topic ids from the array...
1477
                                foreach ($tracking['tf'] as $f_id => $topic_id_ary)
1478
                                {
1479
                                        foreach ($time_keys as $m_tkey => $min_value)
1480
                                        {
1481
                                                if (isset($topic_id_ary[$m_tkey]))
1482
                                                {
1483
                                                        $tracking['f'][$f_id] = $min_value;
1484
                                                        unset($tracking['tf'][$f_id][$m_tkey]);
1485
                                                }
1486
                                        }
1487
                                }
1488
1489
                                if ($user->data['is_registered'])
1490
                                {
1491
                                        $user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10));
1492
                                        $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . $user->data['user_lastmark'] . " WHERE user_id = {$user->data['user_id']}");
1493
                                }
1494
                                else
1495
                                {
1496
                                        $tracking['l'] = max($time_keys);
1497
                                }
1498
                        }
1499
1500
                        $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
1501
                        $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking);
1502
                }
1503
1504
                return;
1505
        }
1506
        else if ($mode == 'post')
1507
        {
1508
                if ($topic_id === false)
1509
                {
1510
                        return;
1511
                }
1512
1513
                $use_user_id = (!$user_id) ? $user->data['user_id'] : $user_id;
1514
1515
                if ($config['load_db_track'] && $use_user_id != ANONYMOUS)
1516
                {
1517
                        $db->sql_return_on_error(true);
1518
1519
                        $sql_ary = array(
1520
                                'user_id'                => (int) $use_user_id,
1521
                                'topic_id'                => (int) $topic_id,
1522
                                'topic_posted'        => 1
1523
                        );
1524
1525
                        $db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1526
1527
                        $db->sql_return_on_error(false);
1528
                }
1529
1530
                return;
1531
        }
1532
}
1533
1534
/**
1535
* Get topic tracking info by using already fetched info
1536
*/
1537
function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $global_announce_list = false)
1538
{
1539
        global $config, $user;
1540
1541
        $last_read = array();
1542
1543
        if (!is_array($topic_ids))
1544
        {
1545
                $topic_ids = array($topic_ids);
1546
        }
1547
1548
        foreach ($topic_ids as $topic_id)
1549
        {
1550
                if (!empty($rowset[$topic_id]['mark_time']))
1551
                {
1552
                        $last_read[$topic_id] = $rowset[$topic_id]['mark_time'];
1553
                }
1554
        }
1555
1556
        $topic_ids = array_diff($topic_ids, array_keys($last_read));
1557
1558
        if (sizeof($topic_ids))
1559
        {
1560
                $mark_time = array();
1561
1562
                // Get global announcement info
1563
                if ($global_announce_list && sizeof($global_announce_list))
1564
                {
1565
                        if (!isset($forum_mark_time[0]))
1566
                        {
1567
                                global $db;
1568
1569
                                $sql = 'SELECT mark_time
1570
                                        FROM ' . FORUMS_TRACK_TABLE . "
1571
                                        WHERE user_id = {$user->data['user_id']}
1572
                                                AND forum_id = 0";
1573
                                $result = $db->sql_query($sql);
1574
                                $row = $db->sql_fetchrow($result);
1575
                                $db->sql_freeresult($result);
1576
1577
                                if ($row)
1578
                                {
1579
                                        $mark_time[0] = $row['mark_time'];
1580
                                }
1581
                        }
1582
                        else
1583
                        {
1584
                                if ($forum_mark_time[0] !== false)
1585
                                {
1586
                                        $mark_time[0] = $forum_mark_time[0];
1587
                                }
1588
                        }
1589
                }
1590
1591
                if (!empty($forum_mark_time[$forum_id]) && $forum_mark_time[$forum_id] !== false)
1592
                {
1593
                        $mark_time[$forum_id] = $forum_mark_time[$forum_id];
1594
                }
1595
1596
                $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1597
1598
                foreach ($topic_ids as $topic_id)
1599
                {
1600
                        if ($global_announce_list && isset($global_announce_list[$topic_id]))
1601
                        {
1602
                                $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1603
                        }
1604
                        else
1605
                        {
1606
                                $last_read[$topic_id] = $user_lastmark;
1607
                        }
1608
                }
1609
        }
1610
1611
        return $last_read;
1612
}
1613
1614
/**
1615
* Get topic tracking info from db (for cookie based tracking only this function is used)
1616
*/
1617
function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_list = false)
1618
{
1619
        global $config, $user;
1620
1621
        $last_read = array();
1622
1623
        if (!is_array($topic_ids))
1624
        {
1625
                $topic_ids = array($topic_ids);
1626
        }
1627
1628
        if ($config['load_db_lastread'] && $user->data['is_registered'])
1629
        {
1630
                global $db;
1631
1632
                $sql = 'SELECT topic_id, mark_time
1633
                        FROM ' . TOPICS_TRACK_TABLE . "
1634
                        WHERE user_id = {$user->data['user_id']}
1635
                                AND " . $db->sql_in_set('topic_id', $topic_ids);
1636
                $result = $db->sql_query($sql);
1637
1638
                while ($row = $db->sql_fetchrow($result))
1639
                {
1640
                        $last_read[$row['topic_id']] = $row['mark_time'];
1641
                }
1642
                $db->sql_freeresult($result);
1643
1644
                $topic_ids = array_diff($topic_ids, array_keys($last_read));
1645
1646
                if (sizeof($topic_ids))
1647
                {
1648
                        $sql = 'SELECT forum_id, mark_time
1649
                                FROM ' . FORUMS_TRACK_TABLE . "
1650
                                WHERE user_id = {$user->data['user_id']}
1651
                                        AND forum_id " .
1652
                                        (($global_announce_list && sizeof($global_announce_list)) ? "IN (0, $forum_id)" : "= $forum_id");
1653
                        $result = $db->sql_query($sql);
1654
1655
                        $mark_time = array();
1656
                        while ($row = $db->sql_fetchrow($result))
1657
                        {
1658
                                $mark_time[$row['forum_id']] = $row['mark_time'];
1659
                        }
1660
                        $db->sql_freeresult($result);
1661
1662
                        $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1663
1664
                        foreach ($topic_ids as $topic_id)
1665
                        {
1666
                                if ($global_announce_list && isset($global_announce_list[$topic_id]))
1667
                                {
1668
                                        $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1669
                                }
1670
                                else
1671
                                {
1672
                                        $last_read[$topic_id] = $user_lastmark;
1673
                                }
1674
                        }
1675
                }
1676
        }
1677
        else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1678
        {
1679
                global $tracking_topics;
1680
1681
                if (!isset($tracking_topics) || !sizeof($tracking_topics))
1682
                {
1683
                        $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1684
                        $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1685
                }
1686
1687
                if (!$user->data['is_registered'])
1688
                {
1689
                        $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
1690
                }
1691
                else
1692
                {
1693
                        $user_lastmark = $user->data['user_lastmark'];
1694
                }
1695
1696
                foreach ($topic_ids as $topic_id)
1697
                {
1698
                        $topic_id36 = base_convert($topic_id, 10, 36);
1699
1700
                        if (isset($tracking_topics['t'][$topic_id36]))
1701
                        {
1702
                                $last_read[$topic_id] = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
1703
                        }
1704
                }
1705
1706
                $topic_ids = array_diff($topic_ids, array_keys($last_read));
1707
1708
                if (sizeof($topic_ids))
1709
                {
1710
                        $mark_time = array();
1711
                        if ($global_announce_list && sizeof($global_announce_list))
1712
                        {
1713
                                if (isset($tracking_topics['f'][0]))
1714
                                {
1715
                                        $mark_time[0] = base_convert($tracking_topics['f'][0], 36, 10) + $config['board_startdate'];
1716
                                }
1717
                        }
1718
1719
                        if (isset($tracking_topics['f'][$forum_id]))
1720
                        {
1721
                                $mark_time[$forum_id] = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
1722
                        }
1723
1724
                        $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user_lastmark;
1725
1726
                        foreach ($topic_ids as $topic_id)
1727
                        {
1728
                                if ($global_announce_list && isset($global_announce_list[$topic_id]))
1729
                                {
1730
                                        $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1731
                                }
1732
                                else
1733
                                {
1734
                                        $last_read[$topic_id] = $user_lastmark;
1735
                                }
1736
                        }
1737
                }
1738
        }
1739
1740
        return $last_read;
1741
}
1742
1743
/**
1744
* Get list of unread topics
1745
*
1746
* @param int $user_id                        User ID (or false for current user)
1747
* @param string $sql_extra                Extra WHERE SQL statement
1748
* @param string $sql_sort                ORDER BY SQL sorting statement
1749
* @param string $sql_limit                Limits the size of unread topics list, 0 for unlimited query
1750
* @param string $sql_limit_offset  Sets the offset of the first row to search, 0 to search from the start
1751
*
1752
* @return array[int][int]                Topic ids as keys, mark_time of topic as value
1753
*/
1754
function get_unread_topics($user_id = false, $sql_extra = '', $sql_sort = '', $sql_limit = 1001, $sql_limit_offset = 0)
1755
{
1756
        global $config, $db, $user;
1757
1758
        $user_id = ($user_id === false) ? (int) $user->data['user_id'] : (int) $user_id;
1759
1760
        // Data array we're going to return
1761
        $unread_topics = array();
1762
1763
        if (empty($sql_sort))
1764
        {
1765
                $sql_sort = 'ORDER BY t.topic_last_post_time DESC';
1766
        }
1767
1768
        if ($config['load_db_lastread'] && $user->data['is_registered'])
1769
        {
1770
                // Get list of the unread topics
1771
                $last_mark = (int) $user->data['user_lastmark'];
1772
1773
                $sql_array = array(
1774
                        'SELECT'                => 't.topic_id, t.topic_last_post_time, tt.mark_time as topic_mark_time, ft.mark_time as forum_mark_time',
1775
1776
                        'FROM'                        => array(TOPICS_TABLE => 't'),
1777
1778
                        'LEFT_JOIN'                => array(
1779
                                array(
1780
                                        'FROM'        => array(TOPICS_TRACK_TABLE => 'tt'),
1781
                                        'ON'        => "tt.user_id = $user_id AND t.topic_id = tt.topic_id",
1782
                                ),
1783
                                array(
1784
                                        'FROM'        => array(FORUMS_TRACK_TABLE => 'ft'),
1785
                                        'ON'        => "ft.user_id = $user_id AND t.forum_id = ft.forum_id",
1786
                                ),
1787
                        ),
1788
1789
                        'WHERE'                        => "
1790
                                 t.topic_last_post_time > $last_mark AND
1791
                                (
1792
                                (tt.mark_time IS NOT NULL AND t.topic_last_post_time > tt.mark_time) OR
1793
                                (tt.mark_time IS NULL AND ft.mark_time IS NOT NULL AND t.topic_last_post_time > ft.mark_time) OR
1794
                                (tt.mark_time IS NULL AND ft.mark_time IS NULL)
1795
                                )
1796
                                $sql_extra
1797
                                $sql_sort",
1798
                );
1799
1800
                $sql = $db->sql_build_query('SELECT', $sql_array);
1801
                $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
1802
1803
                while ($row = $db->sql_fetchrow($result))
1804
                {
1805
                        $topic_id = (int) $row['topic_id'];
1806
                        $unread_topics[$topic_id] = ($row['topic_mark_time']) ? (int) $row['topic_mark_time'] : (($row['forum_mark_time']) ? (int) $row['forum_mark_time'] : $last_mark);
1807
                }
1808
                $db->sql_freeresult($result);
1809
        }
1810
        else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1811
        {
1812
                global $tracking_topics;
1813
1814
                if (empty($tracking_topics))
1815
                {
1816
                        $tracking_topics = request_var($config['cookie_name'] . '_track', '', false, true);
1817
                        $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1818
                }
1819
1820
                if (!$user->data['is_registered'])
1821
                {
1822
                        $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
1823
                }
1824
                else
1825
                {
1826
                        $user_lastmark = (int) $user->data['user_lastmark'];
1827
                }
1828
1829
                $sql = 'SELECT t.topic_id, t.forum_id, t.topic_last_post_time
1830
                        FROM ' . TOPICS_TABLE . ' t
1831
                        WHERE t.topic_last_post_time > ' . $user_lastmark . "
1832
                        $sql_extra
1833
                        $sql_sort";
1834
                $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
1835
1836
                while ($row = $db->sql_fetchrow($result))
1837
                {
1838
                        $forum_id = (int) $row['forum_id'];
1839
                        $topic_id = (int) $row['topic_id'];
1840
                        $topic_id36 = base_convert($topic_id, 10, 36);
1841
1842
                        if (isset($tracking_topics['t'][$topic_id36]))
1843
                        {
1844
                                $last_read = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
1845
1846
                                if ($row['topic_last_post_time'] > $last_read)
1847
                                {
1848
                                        $unread_topics[$topic_id] = $last_read;
1849
                                }
1850
                        }
1851
                        else if (isset($tracking_topics['f'][$forum_id]))
1852
                        {
1853
                                $mark_time = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
1854
1855
                                if ($row['topic_last_post_time'] > $mark_time)
1856
                                {
1857
                                        $unread_topics[$topic_id] = $mark_time;
1858
                                }
1859
                        }
1860
                        else
1861
                        {
1862
                                $unread_topics[$topic_id] = $user_lastmark;
1863
                        }
1864
                }
1865
                $db->sql_freeresult($result);
1866
        }
1867
1868
        return $unread_topics;
1869
}
1870
1871
/**
1872
* Check for read forums and update topic tracking info accordingly
1873
*
1874
* @param int $forum_id the forum id to check
1875
* @param int $forum_last_post_time the forums last post time
1876
* @param int $f_mark_time the forums last mark time if user is registered and load_db_lastread enabled
1877
* @param int $mark_time_forum false if the mark time needs to be obtained, else the last users forum mark time
1878
*
1879
* @return true if complete forum got marked read, else false.
1880
*/
1881
function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time = false, $mark_time_forum = false)
1882
{
1883
        global $db, $tracking_topics, $user, $config, $auth;
1884
1885
        // Determine the users last forum mark time if not given.
1886
        if ($mark_time_forum === false)
1887
        {
1888
                if ($config['load_db_lastread'] && $user->data['is_registered'])
1889
                {
1890
                        $mark_time_forum = (!empty($f_mark_time)) ? $f_mark_time : $user->data['user_lastmark'];
1891
                }
1892
                else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1893
                {
1894
                        $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1895
                        $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1896
1897
                        if (!$user->data['is_registered'])
1898
                        {
1899
                                $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0;
1900
                        }
1901
1902
                        $mark_time_forum = (isset($tracking_topics['f'][$forum_id])) ? (int) (base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate']) : $user->data['user_lastmark'];
1903
                }
1904
        }
1905
1906
        // Handle update of unapproved topics info.
1907
        // Only update for moderators having m_approve permission for the forum.
1908
        $sql_update_unapproved = ($auth->acl_get('m_approve', $forum_id)) ? '': 'AND t.topic_approved = 1';
1909
1910
        // Check the forum for any left unread topics.
1911
        // If there are none, we mark the forum as read.
1912
        if ($config['load_db_lastread'] && $user->data['is_registered'])
1913
        {
1914
                if ($mark_time_forum >= $forum_last_post_time)
1915
                {
1916
                        // We do not need to mark read, this happened before. Therefore setting this to true
1917
                        $row = true;
1918
                }
1919
                else
1920
                {
1921
                        $sql = 'SELECT t.forum_id FROM ' . TOPICS_TABLE . ' t
1922
                                LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt ON (tt.topic_id = t.topic_id AND tt.user_id = ' . $user->data['user_id'] . ')
1923
                                WHERE t.forum_id = ' . $forum_id . '
1924
                                        AND t.topic_last_post_time > ' . $mark_time_forum . '
1925
                                        AND t.topic_moved_id = 0 ' .
1926
                                        $sql_update_unapproved . '
1927
                                        AND (tt.topic_id IS NULL OR tt.mark_time < t.topic_last_post_time)
1928
                                GROUP BY t.forum_id';
1929
                        $result = $db->sql_query_limit($sql, 1);
1930
                        $row = $db->sql_fetchrow($result);
1931
                        $db->sql_freeresult($result);
1932
                }
1933
        }
1934
        else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1935
        {
1936
                // Get information from cookie
1937
                $row = false;
1938
1939
                if (!isset($tracking_topics['tf'][$forum_id]))
1940
                {
1941
                        // We do not need to mark read, this happened before. Therefore setting this to true
1942
                        $row = true;
1943
                }
1944
                else
1945
                {
1946
                        $sql = 'SELECT t.topic_id
1947
                                FROM ' . TOPICS_TABLE . ' t
1948
                                WHERE t.forum_id = ' . $forum_id . '
1949
                                        AND t.topic_last_post_time > ' . $mark_time_forum . '
1950
                                        AND t.topic_moved_id = 0 ' .
1951
                                        $sql_update_unapproved;
1952
                        $result = $db->sql_query($sql);
1953
1954
                        $check_forum = $tracking_topics['tf'][$forum_id];
1955
                        $unread = false;
1956
1957
                        while ($row = $db->sql_fetchrow($result))
1958
                        {
1959
                                if (!isset($check_forum[base_convert($row['topic_id'], 10, 36)]))
1960
                                {
1961
                                        $unread = true;
1962
                                        break;
1963
                                }
1964
                        }
1965
                        $db->sql_freeresult($result);
1966
1967
                        $row = $unread;
1968
                }
1969
        }
1970
        else
1971
        {
1972
                $row = true;
1973
        }
1974
1975
        if (!$row)
1976
        {
1977
                markread('topics', $forum_id);
1978
                return true;
1979
        }
1980
1981
        return false;
1982
}
1983
1984
/**
1985
* Transform an array into a serialized format
1986
*/
1987
function tracking_serialize($input)
1988
{
1989
        $out = '';
1990
        foreach ($input as $key => $value)
1991
        {
1992
                if (is_array($value))
1993
                {
1994
                        $out .= $key . ':(' . tracking_serialize($value) . ');';
1995
                }
1996
                else
1997
                {
1998
                        $out .= $key . ':' . $value . ';';
1999
                }
2000
        }
2001
        return $out;
2002
}
2003
2004
/**
2005
* Transform a serialized array into an actual array
2006
*/
2007
function tracking_unserialize($string, $max_depth = 3)
2008
{
2009
        $n = strlen($string);
2010
        if ($n > 10010)
2011
        {
2012
                die('Invalid data supplied');
2013
        }
2014
        $data = $stack = array();
2015
        $key = '';
2016
        $mode = 0;
2017
        $level = &$data;
2018
        for ($i = 0; $i < $n; ++$i)
2019
        {
2020
                switch ($mode)
2021
                {
2022
                        case 0:
2023
                                switch ($string[$i])
2024
                                {
2025
                                        case ':':
2026
                                                $level[$key] = 0;
2027
                                                $mode = 1;
2028
                                        break;
2029
                                        case ')':
2030
                                                unset($level);
2031
                                                $level = array_pop($stack);
2032
                                                $mode = 3;
2033
                                        break;
2034
                                        default:
2035
                                                $key .= $string[$i];
2036
                                }
2037
                        break;
2038
2039
                        case 1:
2040
                                switch ($string[$i])
2041
                                {
2042
                                        case '(':
2043
                                                if (sizeof($stack) >= $max_depth)
2044
                                                {
2045
                                                        die('Invalid data supplied');
2046
                                                }
2047
                                                $stack[] = &$level;
2048
                                                $level[$key] = array();
2049
                                                $level = &$level[$key];
2050
                                                $key = '';
2051
                                                $mode = 0;
2052
                                        break;
2053
                                        default:
2054
                                                $level[$key] = $string[$i];
2055
                                                $mode = 2;
2056
                                        break;
2057
                                }
2058
                        break;
2059
2060
                        case 2:
2061
                                switch ($string[$i])
2062
                                {
2063
                                        case ')':
2064
                                                unset($level);
2065
                                                $level = array_pop($stack);
2066
                                                $mode = 3;
2067
                                        break;
2068
                                        case ';':
2069
                                                $key = '';
2070
                                                $mode = 0;
2071
                                        break;
2072
                                        default:
2073
                                                $level[$key] .= $string[$i];
2074
                                        break;
2075
                                }
2076
                        break;
2077
2078
                        case 3:
2079
                                switch ($string[$i])
2080
                                {
2081
                                        case ')':
2082
                                                unset($level);
2083
                                                $level = array_pop($stack);
2084
                                        break;
2085
                                        case ';':
2086
                                                $key = '';
2087
                                                $mode = 0;
2088
                                        break;
2089
                                        default:
2090
                                                die('Invalid data supplied');
2091
                                        break;
2092
                                }
2093
                        break;
2094
                }
2095
        }
2096
2097
        if (sizeof($stack) != 0 || ($mode != 0 && $mode != 3))
2098
        {
2099
                die('Invalid data supplied');
2100
        }
2101
2102
        return $level;
2103
}
2104
2105
// Pagination functions
2106
2107
/**
2108
* Pagination routine, generates page number sequence
2109
* tpl_prefix is for using different pagination blocks at one page
2110
*/
2111
function generate_pagination($base_url, $num_items, $per_page, $start_item, $add_prevnext_text = false, $tpl_prefix = '')
2112
{
2113
        global $template, $user;
2114
2115
        // Make sure $per_page is a valid value
2116
        $per_page = ($per_page <= 0) ? 1 : $per_page;
2117
2118
        $seperator = '<span class="page-sep">' . $user->lang['COMMA_SEPARATOR'] . '</span>';
2119
        $total_pages = ceil($num_items / $per_page);
2120
2121
        if ($total_pages == 1 || !$num_items)
2122
        {
2123
                return false;
2124
        }
2125
2126
        $on_page = floor($start_item / $per_page) + 1;
2127
        $url_delim = (strpos($base_url, '?') === false) ? '?' : ((strpos($base_url, '?') === strlen($base_url) - 1) ? '' : '&amp;');
2128
2129
        $page_string = ($on_page == 1) ? '<strong>1</strong>' : '<a href="' . $base_url . '">1</a>';
2130
2131
        if ($total_pages > 5)
2132
        {
2133
                $start_cnt = min(max(1, $on_page - 4), $total_pages - 5);
2134
                $end_cnt = max(min($total_pages, $on_page + 4), 6);
2135
2136
                $page_string .= ($start_cnt > 1) ? '<span class="page-dots"> ... </span>' : $seperator;
2137
2138
                for ($i = $start_cnt + 1; $i < $end_cnt; $i++)
2139
                {
2140
                        $page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>';
2141
                        if ($i < $end_cnt - 1)
2142
                        {
2143
                                $page_string .= $seperator;
2144
                        }
2145
                }
2146
2147
                $page_string .= ($end_cnt < $total_pages) ? '<span class="page-dots"> ... </span>' : $seperator;
2148
        }
2149
        else
2150
        {
2151
                $page_string .= $seperator;
2152
2153
                for ($i = 2; $i < $total_pages; $i++)
2154
                {
2155
                        $page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>';
2156
                        if ($i < $total_pages)
2157
                        {
2158
                                $page_string .= $seperator;
2159
                        }
2160
                }
2161
        }
2162
2163
        $page_string .= ($on_page == $total_pages) ? '<strong>' . $total_pages . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($total_pages - 1) * $per_page) . '">' . $total_pages . '</a>';
2164
2165
        if ($add_prevnext_text)
2166
        {
2167
                if ($on_page != 1)
2168
                {
2169
                        $page_string = '<a href="' . $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page) . '">' . $user->lang['PREVIOUS'] . '</a>&nbsp;&nbsp;' . $page_string;
2170
                }
2171
2172
                if ($on_page != $total_pages)
2173
                {
2174
                        $page_string .= '&nbsp;&nbsp;<a href="' . $base_url . "{$url_delim}start=" . ($on_page * $per_page) . '">' . $user->lang['NEXT'] . '</a>';
2175
                }
2176
        }
2177
2178
        $template->assign_vars(array(
2179
                $tpl_prefix . 'BASE_URL'                => $base_url,
2180
                'A_' . $tpl_prefix . 'BASE_URL'        => addslashes($base_url),
2181
                $tpl_prefix . 'PER_PAGE'                => $per_page,
2182
2183
                $tpl_prefix . 'PREVIOUS_PAGE'        => ($on_page == 1) ? '' : $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page),
2184
                $tpl_prefix . 'NEXT_PAGE'                => ($on_page == $total_pages) ? '' : $base_url . "{$url_delim}start=" . ($on_page * $per_page),
2185
                $tpl_prefix . 'TOTAL_PAGES'                => $total_pages,
2186
        ));
2187
2188
        return $page_string;
2189
}
2190
2191
/**
2192
* Return current page (pagination)
2193
*/
2194
function on_page($num_items, $per_page, $start)
2195
{
2196
        global $template, $user;
2197
2198
        // Make sure $per_page is a valid value
2199
        $per_page = ($per_page <= 0) ? 1 : $per_page;
2200
2201
        $on_page = floor($start / $per_page) + 1;
2202
2203
        $template->assign_vars(array(
2204
                'ON_PAGE'                => $on_page)
2205
        );
2206
2207
        return sprintf($user->lang['PAGE_OF'], $on_page, max(ceil($num_items / $per_page), 1));
2208
}
2209
2210
// Server functions (building urls, redirecting...)
2211
2212
/**
2213
* Append session id to url.
2214
* This function supports hooks.
2215
*
2216
* @param string $url The url the session id needs to be appended to (can have params)
2217
* @param mixed $params String or array of additional url parameters
2218
* @param bool $is_amp Is url using &amp; (true) or & (false)
2219
* @param string $session_id Possibility to use a custom session id instead of the global one
2220
*
2221
* Examples:
2222
* <code>
2223
* append_sid("{$phpbb_root_path}viewtopic.$phpEx?t=1&amp;f=2");
2224
* append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&amp;f=2');
2225
* append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&f=2', false);
2226
* append_sid("{$phpbb_root_path}viewtopic.$phpEx", array('t' => 1, 'f' => 2));
2227
* </code>
2228
*
2229
*/
2230
function append_sid($url, $params = false, $is_amp = true, $session_id = false)
2231
{
2232
        global $_SID, $_EXTRA_URL, $phpbb_hook;
2233
2234
        if ($params === '' || (is_array($params) && empty($params)))
2235
        {
2236
                // Do not append the ? if the param-list is empty anyway.
2237
                $params = false;
2238
        }
2239
2240
        // Developers using the hook function need to globalise the $_SID and $_EXTRA_URL on their own and also handle it appropriately.
2241
        // They could mimic most of what is within this function
2242
        if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__, $url, $params, $is_amp, $session_id))
2243
        {
2244
                if ($phpbb_hook->hook_return(__FUNCTION__))
2245
                {
2246
                        return $phpbb_hook->hook_return_result(__FUNCTION__);
2247
                }
2248
        }
2249
2250
        $params_is_array = is_array($params);
2251
2252
        // Get anchor
2253
        $anchor = '';
2254
        if (strpos($url, '#') !== false)
2255
        {
2256
                list($url, $anchor) = explode('#', $url, 2);
2257
                $anchor = '#' . $anchor;
2258
        }
2259
        else if (!$params_is_array && strpos($params, '#') !== false)
2260
        {
2261
                list($params, $anchor) = explode('#', $params, 2);
2262
                $anchor = '#' . $anchor;
2263
        }
2264
2265
        // Handle really simple cases quickly
2266
        if ($_SID == '' && $session_id === false && empty($_EXTRA_URL) && !$params_is_array && !$anchor)
2267
        {
2268
                if ($params === false)
2269
                {
2270
                        return $url;
2271
                }
2272
2273
                $url_delim = (strpos($url, '?') === false) ? '?' : (($is_amp) ? '&amp;' : '&');
2274
                return $url . ($params !== false ? $url_delim. $params : '');
2275
        }
2276
2277
        // Assign sid if session id is not specified
2278
        if ($session_id === false)
2279
        {
2280
                $session_id = $_SID;
2281
        }
2282
2283
        $amp_delim = ($is_amp) ? '&amp;' : '&';
2284
        $url_delim = (strpos($url, '?') === false) ? '?' : $amp_delim;
2285
2286
        // Appending custom url parameter?
2287
        $append_url = (!empty($_EXTRA_URL)) ? implode($amp_delim, $_EXTRA_URL) : '';
2288
2289
        // Use the short variant if possible ;)
2290
        if ($params === false)
2291
        {
2292
                // Append session id
2293
                if (!$session_id)
2294
                {
2295
                        return $url . (($append_url) ? $url_delim . $append_url : '') . $anchor;
2296
                }
2297
                else
2298
                {
2299
                        return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . 'sid=' . $session_id . $anchor;
2300
                }
2301
        }
2302
2303
        // Build string if parameters are specified as array
2304
        if (is_array($params))
2305
        {
2306
                $output = array();
2307
2308
                foreach ($params as $key => $item)
2309
                {
2310
                        if ($item === NULL)
2311
                        {
2312
                                continue;
2313
                        }
2314
2315
                        if ($key == '#')
2316
                        {
2317
                                $anchor = '#' . $item;
2318
                                continue;
2319
                        }
2320
2321
                        $output[] = $key . '=' . $item;
2322
                }
2323
2324
                $params = implode($amp_delim, $output);
2325
        }
2326
2327
        // Append session id and parameters (even if they are empty)
2328
        // If parameters are empty, the developer can still append his/her parameters without caring about the delimiter
2329
        return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . $params . ((!$session_id) ? '' : $amp_delim . 'sid=' . $session_id) . $anchor;
2330
}
2331
2332
/**
2333
* Generate board url (example: http://www.example.com/phpBB)
2334
*
2335
* @param bool $without_script_path if set to true the script path gets not appended (example: http://www.example.com)
2336
*
2337
* @return string the generated board url
2338
*/
2339
function generate_board_url($without_script_path = false)
2340
{
2341
        global $config, $user;
2342
2343
        $server_name = $user->host;
2344
        $server_port = (!empty($_SERVER['SERVER_PORT'])) ? (int) $_SERVER['SERVER_PORT'] : (int) getenv('SERVER_PORT');
2345
2346
        // Forcing server vars is the only way to specify/override the protocol
2347
        if ($config['force_server_vars'] || !$server_name)
2348
        {
2349
                $server_protocol = ($config['server_protocol']) ? $config['server_protocol'] : (($config['cookie_secure']) ? 'https://' : 'http://');
2350
                $server_name = $config['server_name'];
2351
                $server_port = (int) $config['server_port'];
2352
                $script_path = $config['script_path'];
2353
2354
                $url = $server_protocol . $server_name;
2355
                $cookie_secure = $config['cookie_secure'];
2356
        }
2357
        else
2358
        {
2359
                // Do not rely on cookie_secure, users seem to think that it means a secured cookie instead of an encrypted connection
2360
                $cookie_secure = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 1 : 0;
2361
                $url = (($cookie_secure) ? 'https://' : 'http://') . $server_name;
2362
2363
                $script_path = $user->page['root_script_path'];
2364
        }
2365
2366
        if ($server_port && (($cookie_secure && $server_port <> 443) || (!$cookie_secure && $server_port <> 80)))
2367
        {
2368
                // HTTP HOST can carry a port number (we fetch $user->host, but for old versions this may be true)
2369
                if (strpos($server_name, ':') === false)
2370
                {
2371
                        $url .= ':' . $server_port;
2372
                }
2373
        }
2374
2375
        if (!$without_script_path)
2376
        {
2377
                $url .= $script_path;
2378
        }
2379
2380
        // Strip / from the end
2381
        if (substr($url, -1, 1) == '/')
2382
        {
2383
                $url = substr($url, 0, -1);
2384
        }
2385
2386
        return $url;
2387
}
2388
2389
/**
2390
* Redirects the user to another page then exits the script nicely
2391
* This function is intended for urls within the board. It's not meant to redirect to cross-domains.
2392
*
2393
* @param string $url The url to redirect to
2394
* @param bool $return If true, do not redirect but return the sanitized URL. Default is no return.
2395
* @param bool $disable_cd_check If true, redirect() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain. Default is false.
2396
*/
2397
function redirect($url, $return = false, $disable_cd_check = false)
2398
{
2399
        global $db, $cache, $config, $user, $phpbb_root_path;
2400
2401
        $failover_flag = false;
2402
2403
        if (empty($user->lang))
2404
        {
2405
                $user->add_lang('common');
2406
        }
2407
2408
        if (!$return)
2409
        {
2410
                garbage_collection();
2411
        }
2412
2413
        // Make sure no &amp;'s are in, this will break the redirect
2414
        $url = str_replace('&amp;', '&', $url);
2415
2416
        // Determine which type of redirect we need to handle...
2417
        $url_parts = @parse_url($url);
2418
2419
        if ($url_parts === false)
2420
        {
2421
                // Malformed url, redirect to current page...
2422
                $url = generate_board_url() . '/' . $user->page['page'];
2423
        }
2424
        else if (!empty($url_parts['scheme']) && !empty($url_parts['host']))
2425
        {
2426
                // Attention: only able to redirect within the same domain if $disable_cd_check is false (yourdomain.com -> www.yourdomain.com will not work)
2427
                if (!$disable_cd_check && $url_parts['host'] !== $user->host)
2428
                {
2429
                        $url = generate_board_url();
2430
                }
2431
        }
2432
        else if ($url[0] == '/')
2433
        {
2434
                // Absolute uri, prepend direct url...
2435
                $url = generate_board_url(true) . $url;
2436
        }
2437
        else
2438
        {
2439
                // Relative uri
2440
                $pathinfo = pathinfo($url);
2441
2442
                if (!$disable_cd_check && !file_exists($pathinfo['dirname'] . '/'))
2443
                {
2444
                        $url = str_replace('../', '', $url);
2445
                        $pathinfo = pathinfo($url);
2446
2447
                        if (!file_exists($pathinfo['dirname'] . '/'))
2448
                        {
2449
                                // fallback to "last known user page"
2450
                                // at least this way we know the user does not leave the phpBB root
2451
                                $url = generate_board_url() . '/' . $user->page['page'];
2452
                                $failover_flag = true;
2453
                        }
2454
                }
2455
2456
                if (!$failover_flag)
2457
                {
2458
                        // Is the uri pointing to the current directory?
2459
                        if ($pathinfo['dirname'] == '.')
2460
                        {
2461
                                $url = str_replace('./', '', $url);
2462
2463
                                // Strip / from the beginning
2464
                                if ($url && substr($url, 0, 1) == '/')
2465
                                {
2466
                                        $url = substr($url, 1);
2467
                                }
2468
2469
                                if ($user->page['page_dir'])
2470
                                {
2471
                                        $url = generate_board_url() . '/' . $user->page['page_dir'] . '/' . $url;
2472
                                }
2473
                                else
2474
                                {
2475
                                        $url = generate_board_url() . '/' . $url;
2476
                                }
2477
                        }
2478
                        else
2479
                        {
2480
                                // Used ./ before, but $phpbb_root_path is working better with urls within another root path
2481
                                $root_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($phpbb_root_path)));
2482
                                $page_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($pathinfo['dirname'])));
2483
                                $intersection = array_intersect_assoc($root_dirs, $page_dirs);
2484
2485
                                $root_dirs = array_diff_assoc($root_dirs, $intersection);
2486
                                $page_dirs = array_diff_assoc($page_dirs, $intersection);
2487
2488
                                $dir = str_repeat('../', sizeof($root_dirs)) . implode('/', $page_dirs);
2489
2490
                                // Strip / from the end
2491
                                if ($dir && substr($dir, -1, 1) == '/')
2492
                                {
2493
                                        $dir = substr($dir, 0, -1);
2494
                                }
2495
2496
                                // Strip / from the beginning
2497
                                if ($dir && substr($dir, 0, 1) == '/')
2498
                                {
2499
                                        $dir = substr($dir, 1);
2500
                                }
2501
2502
                                $url = str_replace($pathinfo['dirname'] . '/', '', $url);
2503
2504
                                // Strip / from the beginning
2505
                                if (substr($url, 0, 1) == '/')
2506
                                {
2507
                                        $url = substr($url, 1);
2508
                                }
2509
2510
                                $url = (!empty($dir) ? $dir . '/' : '') . $url;
2511
                                $url = generate_board_url() . '/' . $url;
2512
                        }
2513
                }
2514
        }
2515
2516
        // Make sure no linebreaks are there... to prevent http response splitting for PHP < 4.4.2
2517
        if (strpos(urldecode($url), "\n") !== false || strpos(urldecode($url), "\r") !== false || strpos($url, ';') !== false)
2518
        {
2519
                trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
2520
        }
2521
2522
        // Now, also check the protocol and for a valid url the last time...
2523
        $allowed_protocols = array('http', 'https', 'ftp', 'ftps');
2524
        $url_parts = parse_url($url);
2525
2526
        if ($url_parts === false || empty($url_parts['scheme']) || !in_array($url_parts['scheme'], $allowed_protocols))
2527
        {
2528
                trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
2529
        }
2530
2531
        if ($return)
2532
        {
2533
                return $url;
2534
        }
2535
2536
        // Redirect via an HTML form for PITA webservers
2537
        if (@preg_match('#Microsoft|WebSTAR|Xitami#', getenv('SERVER_SOFTWARE')))
2538
        {
2539
                header('Refresh: 0; URL=' . $url);
2540
2541
                echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
2542
                echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="' . $user->lang['DIRECTION'] . '" lang="' . $user->lang['USER_LANG'] . '" xml:lang="' . $user->lang['USER_LANG'] . '">';
2543
                echo '<head>';
2544
                echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
2545
                echo '<meta http-equiv="refresh" content="0; url=' . str_replace('&', '&amp;', $url) . '" />';
2546
                echo '<title>' . $user->lang['REDIRECT'] . '</title>';
2547
                echo '</head>';
2548
                echo '<body>';
2549
                echo '<div style="text-align: center;">' . sprintf($user->lang['URL_REDIRECT'], '<a href="' . str_replace('&', '&amp;', $url) . '">', '</a>') . '</div>';
2550
                echo '</body>';
2551
                echo '</html>';
2552
2553
                exit;
2554
        }
2555
2556
        // Behave as per HTTP/1.1 spec for others
2557
        header('Location: ' . $url);
2558
        exit;
2559
}
2560
2561
/**
2562
* Re-Apply session id after page reloads
2563
*/
2564
function reapply_sid($url)
2565
{
2566
        global $phpEx, $phpbb_root_path;
2567
2568
        if ($url === "index.$phpEx")
2569
        {
2570
                return append_sid("index.$phpEx");
2571
        }
2572
        else if ($url === "{$phpbb_root_path}index.$phpEx")
2573
        {
2574
                return append_sid("{$phpbb_root_path}index.$phpEx");
2575
        }
2576
2577
        // Remove previously added sid
2578
        if (strpos($url, 'sid=') !== false)
2579
        {
2580
                // All kind of links
2581
                $url = preg_replace('/(\?)?(&amp;|&)?sid=[a-z0-9]+/', '', $url);
2582
                // if the sid was the first param, make the old second as first ones
2583
                $url = preg_replace("/$phpEx(&amp;|&)+?/", "$phpEx?", $url);
2584
        }
2585
2586
        return append_sid($url);
2587
}
2588
2589
/**
2590
* Returns url from the session/current page with an re-appended SID with optionally stripping vars from the url
2591
*/
2592
function build_url($strip_vars = false)
2593
{
2594
        global $user, $phpbb_root_path;
2595
2596
        // Append SID
2597
        $redirect = append_sid($user->page['page'], false, false);
2598
2599
        // Add delimiter if not there...
2600
        if (strpos($redirect, '?') === false)
2601
        {
2602
                $redirect .= '?';
2603
        }
2604
2605
        // Strip vars...
2606
        if ($strip_vars !== false && strpos($redirect, '?') !== false)
2607
        {
2608
                if (!is_array($strip_vars))
2609
                {
2610
                        $strip_vars = array($strip_vars);
2611
                }
2612
2613
                $query = $_query = array();
2614
2615
                $args = substr($redirect, strpos($redirect, '?') + 1);
2616
                $args = ($args) ? explode('&', $args) : array();
2617
                $redirect = substr($redirect, 0, strpos($redirect, '?'));
2618
2619
                foreach ($args as $argument)
2620
                {
2621
                        $arguments = explode('=', $argument);
2622
                        $key = $arguments[0];
2623
                        unset($arguments[0]);
2624
2625
                        if ($key === '')
2626
                        {
2627
                                continue;
2628
                        }
2629
2630
                        $query[$key] = implode('=', $arguments);
2631
                }
2632
2633
                // Strip the vars off
2634
                foreach ($strip_vars as $strip)
2635
                {
2636
                        if (isset($query[$strip]))
2637
                        {
2638
                                unset($query[$strip]);
2639
                        }
2640
                }
2641
2642
                // Glue the remaining parts together... already urlencoded
2643
                foreach ($query as $key => $value)
2644
                {
2645
                        $_query[] = $key . '=' . $value;
2646
                }
2647
                $query = implode('&', $_query);
2648
2649
                $redirect .= ($query) ? '?' . $query : '';
2650
        }
2651
2652
        // We need to be cautious here.
2653
        // On some situations, the redirect path is an absolute URL, sometimes a relative path
2654
        // For a relative path, let's prefix it with $phpbb_root_path to point to the correct location,
2655
        // else we use the URL directly.
2656
        $url_parts = @parse_url($redirect);
2657
2658
        // URL
2659
        if ($url_parts !== false && !empty($url_parts['scheme']) && !empty($url_parts['host']))
2660
        {
2661
                return str_replace('&', '&amp;', $redirect);
2662
        }
2663
2664
        return $phpbb_root_path . str_replace('&', '&amp;', $redirect);
2665
}
2666
2667
/**
2668
* Meta refresh assignment
2669
* Adds META template variable with meta http tag.
2670
*
2671
* @param int $time Time in seconds for meta refresh tag
2672
* @param string $url URL to redirect to. The url will go through redirect() first before the template variable is assigned
2673
* @param bool $disable_cd_check If true, meta_refresh() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain. Default is false.
2674
*/
2675
function meta_refresh($time, $url, $disable_cd_check = false)
2676
{
2677
        global $template;
2678
2679
        $url = redirect($url, true, $disable_cd_check);
2680
        $url = str_replace('&', '&amp;', $url);
2681
2682
        // For XHTML compatibility we change back & to &amp;
2683
        $template->assign_vars(array(
2684
                'META' => '<meta http-equiv="refresh" content="' . $time . ';url=' . $url . '" />')
2685
        );
2686
2687
        return $url;
2688
}
2689
2690
/**
2691
* Outputs correct status line header.
2692
*
2693
* Depending on php sapi one of the two following forms is used:
2694
*
2695
* Status: 404 Not Found
2696
*
2697
* HTTP/1.x 404 Not Found
2698
*
2699
* HTTP version is taken from HTTP_VERSION environment variable,
2700
* and defaults to 1.0.
2701
*
2702
* Sample usage:
2703
*
2704
* send_status_line(404, 'Not Found');
2705
*
2706
* @param int $code HTTP status code
2707
* @param string $message Message for the status code
2708
* @return void
2709
*/
2710
function send_status_line($code, $message)
2711
{
2712
        if (substr(strtolower(@php_sapi_name()), 0, 3) === 'cgi')
2713
        {
2714
                // in theory, we shouldn't need that due to php doing it. Reality offers a differing opinion, though
2715
                header("Status: $code $message", true, $code);
2716
        }
2717
        else
2718
        {
2719
                if (!empty($_SERVER['SERVER_PROTOCOL']))
2720
                {
2721
                        $version = $_SERVER['SERVER_PROTOCOL'];
2722
                }
2723
                else
2724
                {
2725
                        $version = 'HTTP/1.0';
2726
                }
2727
                header("$version $code $message", true, $code);
2728
        }
2729
}
2730
2731
//Form validation
2732
2733
2734
/**
2735
* Add a secret hash   for use in links/GET requests
2736
* @param string  $link_name The name of the link; has to match the name used in check_link_hash, otherwise no restrictions apply
2737
* @return string the hash
2738
2739
*/
2740
function generate_link_hash($link_name)
2741
{
2742
        global $user;
2743
2744
        if (!isset($user->data["hash_$link_name"]))
2745
        {
2746
                $user->data["hash_$link_name"] = substr(sha1($user->data['user_form_salt'] . $link_name), 0, 8);
2747
        }
2748
2749
        return $user->data["hash_$link_name"];
2750
}
2751
2752
2753
/**
2754
* checks a link hash - for GET requests
2755
* @param string $token the submitted token
2756
* @param string $link_name The name of the link
2757
* @return boolean true if all is fine
2758
*/
2759
function check_link_hash($token, $link_name)
2760
{
2761
        return $token === generate_link_hash($link_name);
2762
}
2763
2764
/**
2765
* Add a secret token to the form (requires the S_FORM_TOKEN template variable)
2766
* @param string  $form_name The name of the form; has to match the name used in check_form_key, otherwise no restrictions apply
2767
*/
2768
function add_form_key($form_name)
2769
{
2770
        global $config, $template, $user;
2771
2772
        $now = time();
2773
        $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2774
        $token = sha1($now . $user->data['user_form_salt'] . $form_name . $token_sid);
2775
2776
        $s_fields = build_hidden_fields(array(
2777
                'creation_time' => $now,
2778
                'form_token'        => $token,
2779
        ));
2780
2781
        $template->assign_vars(array(
2782
                'S_FORM_TOKEN'        => $s_fields,
2783
        ));
2784
}
2785
2786
/**
2787
* Check the form key. Required for all altering actions not secured by confirm_box
2788
* @param string  $form_name The name of the form; has to match the name used in add_form_key, otherwise no restrictions apply
2789
* @param int $timespan The maximum acceptable age for a submitted form in seconds. Defaults to the config setting.
2790
* @param string $return_page The address for the return link
2791
* @param bool $trigger If true, the function will triger an error when encountering an invalid form
2792
*/
2793
function check_form_key($form_name, $timespan = false, $return_page = '', $trigger = false)
2794
{
2795
        global $config, $user;
2796
2797
        if ($timespan === false)
2798
        {
2799
                // we enforce a minimum value of half a minute here.
2800
                $timespan = ($config['form_token_lifetime'] == -1) ? -1 : max(30, $config['form_token_lifetime']);
2801
        }
2802
2803
        if (isset($_POST['creation_time']) && isset($_POST['form_token']))
2804
        {
2805
                $creation_time        = abs(request_var('creation_time', 0));
2806
                $token = request_var('form_token', '');
2807
2808
                $diff = time() - $creation_time;
2809
2810
                // If creation_time and the time() now is zero we can assume it was not a human doing this (the check for if ($diff)...
2811
                if ($diff && ($diff <= $timespan || $timespan === -1))
2812
                {
2813
                        $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2814
                        $key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid);
2815
2816
                        if ($key === $token)
2817
                        {
2818
                                return true;
2819
                        }
2820
                }
2821
        }
2822
2823
        if ($trigger)
2824
        {
2825
                trigger_error($user->lang['FORM_INVALID'] . $return_page);
2826
        }
2827
2828
        return false;
2829
}
2830
2831
// Message/Login boxes
2832
2833
/**
2834
* Build Confirm box
2835
* @param boolean $check True for checking if confirmed (without any additional parameters) and false for displaying the confirm box
2836
* @param string $title Title/Message used for confirm box.
2837
*                message text is _CONFIRM appended to title.
2838
*                If title cannot be found in user->lang a default one is displayed
2839
*                If title_CONFIRM cannot be found in user->lang the text given is used.
2840
* @param string $hidden Hidden variables
2841
* @param string $html_body Template used for confirm box
2842
* @param string $u_action Custom form action
2843
*/
2844
function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '')
2845
{
2846
        global $user, $template, $db;
2847
        global $phpEx, $phpbb_root_path;
2848
2849
        if (isset($_POST['cancel']))
2850
        {
2851
                return false;
2852
        }
2853
2854
        $confirm = false;
2855
        if (isset($_POST['confirm']))
2856
        {
2857
                // language frontier
2858
                if ($_POST['confirm'] === $user->lang['YES'])
2859
                {
2860
                        $confirm = true;
2861
                }
2862
        }
2863
2864
        if ($check && $confirm)
2865
        {
2866
                $user_id = request_var('confirm_uid', 0);
2867
                $session_id = request_var('sess', '');
2868
                $confirm_key = request_var('confirm_key', '');
2869
2870
                if ($user_id != $user->data['user_id'] || $session_id != $user->session_id || !$confirm_key || !$user->data['user_last_confirm_key'] || $confirm_key != $user->data['user_last_confirm_key'])
2871
                {
2872
                        return false;
2873
                }
2874
2875
                // Reset user_last_confirm_key
2876
                $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = ''
2877
                        WHERE user_id = " . $user->data['user_id'];
2878
                $db->sql_query($sql);
2879
2880
                return true;
2881
        }
2882
        else if ($check)
2883
        {
2884
                return false;
2885
        }
2886
2887
        $s_hidden_fields = build_hidden_fields(array(
2888
                'confirm_uid'        => $user->data['user_id'],
2889
                'sess'                        => $user->session_id,
2890
                'sid'                        => $user->session_id,
2891
        ));
2892
2893
        // generate activation key
2894
        $confirm_key = gen_rand_string(10);
2895
2896
        if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2897
        {
2898
                adm_page_header((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]);
2899
        }
2900
        else
2901
        {
2902
                page_header(((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]), false);
2903
        }
2904
2905
        $template->set_filenames(array(
2906
                'body' => $html_body)
2907
        );
2908
2909
        // If activation key already exist, we better do not re-use the key (something very strange is going on...)
2910
        if (request_var('confirm_key', ''))
2911
        {
2912
                // This should not occur, therefore we cancel the operation to safe the user
2913
                return false;
2914
        }
2915
2916
        // re-add sid / transform & to &amp; for user->page (user->page is always using &)
2917
        $use_page = ($u_action) ? $phpbb_root_path . $u_action : $phpbb_root_path . str_replace('&', '&amp;', $user->page['page']);
2918
        $u_action = reapply_sid($use_page);
2919
        $u_action .= ((strpos($u_action, '?') === false) ? '?' : '&amp;') . 'confirm_key=' . $confirm_key;
2920
2921
        $template->assign_vars(array(
2922
                'MESSAGE_TITLE'                => (!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title],
2923
                'MESSAGE_TEXT'                => (!isset($user->lang[$title . '_CONFIRM'])) ? $title : $user->lang[$title . '_CONFIRM'],
2924
2925
                'YES_VALUE'                        => $user->lang['YES'],
2926
                'S_CONFIRM_ACTION'        => $u_action,
2927
                'S_HIDDEN_FIELDS'        => $hidden . $s_hidden_fields)
2928
        );
2929
2930
        $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '" . $db->sql_escape($confirm_key) . "'
2931
                WHERE user_id = " . $user->data['user_id'];
2932
        $db->sql_query($sql);
2933
2934
        if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2935
        {
2936
                adm_page_footer();
2937
        }
2938
        else
2939
        {
2940
                page_footer();
2941
        }
2942
}
2943
2944
/**
2945
* Generate login box or verify password
2946
*/
2947
function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true)
2948
{
2949
        global $db, $user, $template, $auth, $phpEx, $phpbb_root_path, $config;
2950
2951
        if (!class_exists('phpbb_captcha_factory'))
2952
        {
2953
                include($phpbb_root_path . 'includes/captcha/captcha_factory.' . $phpEx);
2954
        }
2955
2956
        $err = '';
2957
2958
        // Make sure user->setup() has been called
2959
        if (empty($user->lang))
2960
        {
2961
                $user->setup();
2962
        }
2963
2964
        // Print out error if user tries to authenticate as an administrator without having the privileges...
2965
        if ($admin && !$auth->acl_get('a_'))
2966
        {
2967
                // Not authd
2968
                // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
2969
                if ($user->data['is_registered'])
2970
                {
2971
                        add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2972
                }
2973
                trigger_error('NO_AUTH_ADMIN');
2974
        }
2975
2976
        if (isset($_POST['login']))
2977
        {
2978
                // Get credential
2979
                if ($admin)
2980
                {
2981
                        $credential = request_var('credential', '');
2982
2983
                        if (strspn($credential, 'abcdef0123456789') !== strlen($credential) || strlen($credential) != 32)
2984
                        {
2985
                                if ($user->data['is_registered'])
2986
                                {
2987
                                        add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2988
                                }
2989
                                trigger_error('NO_AUTH_ADMIN');
2990
                        }
2991
2992
                        $password        = request_var('password_' . $credential, '', true);
2993
                }
2994
                else
2995
                {
2996
                        $password        = request_var('password', '', true);
2997
                }
2998
2999
                $username        = request_var('username', '', true);
3000
                $autologin        = (!empty($_POST['autologin'])) ? true : false;
3001
                $viewonline = (!empty($_POST['viewonline'])) ? 0 : 1;
3002
                $admin                 = ($admin) ? 1 : 0;
3003
                $viewonline = ($admin) ? $user->data['session_viewonline'] : $viewonline;
3004
3005
                // Check if the supplied username is equal to the one stored within the database if re-authenticating
3006
                if ($admin && utf8_clean_string($username) != utf8_clean_string($user->data['username']))
3007
                {
3008
                        // We log the attempt to use a different username...
3009
                        add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
3010
                        trigger_error('NO_AUTH_ADMIN_USER_DIFFER');
3011
                }
3012
3013
                // If authentication is successful we redirect user to previous page
3014
                $result = $auth->login($username, $password, $autologin, $viewonline, $admin);
3015
3016
                // If admin authentication and login, we will log if it was a success or not...
3017
                // We also break the operation on the first non-success login - it could be argued that the user already knows
3018
                if ($admin)
3019
                {
3020
                        if ($result['status'] == LOGIN_SUCCESS)
3021
                        {
3022
                                add_log('admin', 'LOG_ADMIN_AUTH_SUCCESS');
3023
                        }
3024
                        else
3025
                        {
3026
                                // Only log the failed attempt if a real user tried to.
3027
                                // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
3028
                                if ($user->data['is_registered'])
3029
                                {
3030
                                        add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
3031
                                }
3032
                        }
3033
                }
3034
3035
                // The result parameter is always an array, holding the relevant information...
3036
                if ($result['status'] == LOGIN_SUCCESS)
3037
                {
3038
                        $redirect = request_var('redirect', "{$phpbb_root_path}index.$phpEx");
3039
                        $message = ($l_success) ? $l_success : $user->lang['LOGIN_REDIRECT'];
3040
                        $l_redirect = ($admin) ? $user->lang['PROCEED_TO_ACP'] : (($redirect === "{$phpbb_root_path}index.$phpEx" || $redirect === "index.$phpEx") ? $user->lang['RETURN_INDEX'] : $user->lang['RETURN_PAGE']);
3041
3042
                        // append/replace SID (may change during the session for AOL users)
3043
                        $redirect = reapply_sid($redirect);
3044
3045
                        // Special case... the user is effectively banned, but we allow founders to login
3046
                        if (defined('IN_CHECK_BAN') && $result['user_row']['user_type'] != USER_FOUNDER)
3047
                        {
3048
                                return;
3049
                        }
3050
3051
                        $redirect = meta_refresh(3, $redirect);
3052
                        trigger_error($message . '<br /><br />' . sprintf($l_redirect, '<a href="' . $redirect . '">', '</a>'));
3053
                }
3054
3055
                // Something failed, determine what...
3056
                if ($result['status'] == LOGIN_BREAK)
3057
                {
3058
                        trigger_error($result['error_msg']);
3059
                }
3060
3061
                // Special cases... determine
3062
                switch ($result['status'])
3063
                {
3064
                        case LOGIN_ERROR_ATTEMPTS:
3065
3066
                                $captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']);
3067
                                $captcha->init(CONFIRM_LOGIN);
3068
                                // $captcha->reset();
3069
3070
                                $template->assign_vars(array(
3071
                                        'CAPTCHA_TEMPLATE'                        => $captcha->get_template(),
3072
                                ));
3073
3074
                                $err = $user->lang[$result['error_msg']];
3075
                        break;
3076
3077
                        case LOGIN_ERROR_PASSWORD_CONVERT:
3078
                                $err = sprintf(
3079
                                        $user->lang[$result['error_msg']],
3080
                                        ($config['email_enable']) ? '<a href="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') . '">' : '',
3081
                                        ($config['email_enable']) ? '</a>' : '',
3082
                                        ($config['board_contact']) ? '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">' : '',
3083
                                        ($config['board_contact']) ? '</a>' : ''
3084
                                );
3085
                        break;
3086
3087
                        // Username, password, etc...
3088
                        default:
3089
                                $err = $user->lang[$result['error_msg']];
3090
3091
                                // Assign admin contact to some error messages
3092
                                if ($result['error_msg'] == 'LOGIN_ERROR_USERNAME' || $result['error_msg'] == 'LOGIN_ERROR_PASSWORD')
3093
                                {
3094
                                        $err = (!$config['board_contact']) ? sprintf($user->lang[$result['error_msg']], '', '') : sprintf($user->lang[$result['error_msg']], '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">', '</a>');
3095
                                }
3096
3097
                        break;
3098
                }
3099
        }
3100
3101
        // Assign credential for username/password pair
3102
        $credential = ($admin) ? md5(unique_id()) : false;
3103
3104
        $s_hidden_fields = array(
3105
                'sid'                => $user->session_id,
3106
        );
3107
3108
        if ($redirect)
3109
        {
3110
                $s_hidden_fields['redirect'] = $redirect;
3111
        }
3112
3113
        if ($admin)
3114
        {
3115
                $s_hidden_fields['credential'] = $credential;
3116
        }
3117
3118
        $s_hidden_fields = build_hidden_fields($s_hidden_fields);
3119
3120
        $template->assign_vars(array(
3121
                'LOGIN_ERROR'                => $err,
3122
                'LOGIN_EXPLAIN'                => $l_explain,
3123
3124
                'U_SEND_PASSWORD'                 => ($config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') : '',
3125
                'U_RESEND_ACTIVATION'        => ($config['require_activation'] == USER_ACTIVATION_SELF && $config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=resend_act') : '',
3126
                'U_TERMS_USE'                        => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
3127
                'U_PRIVACY'                                => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
3128
3129
                'S_DISPLAY_FULL_LOGIN'        => ($s_display) ? true : false,
3130
                'S_HIDDEN_FIELDS'                 => $s_hidden_fields,
3131
3132
                'S_ADMIN_AUTH'                        => $admin,
3133
                'USERNAME'                                => ($admin) ? $user->data['username'] : '',
3134
3135
                'USERNAME_CREDENTIAL'        => 'username',
3136
                'PASSWORD_CREDENTIAL'        => ($admin) ? 'password_' . $credential : 'password',
3137
        ));
3138
3139
        page_header($user->lang['LOGIN'], false);
3140
3141
        $template->set_filenames(array(
3142
                'body' => 'login_body.html')
3143
        );
3144
        make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"));
3145
3146
        page_footer();
3147
}
3148
3149
/**
3150
* Generate forum login box
3151
*/
3152
function login_forum_box($forum_data)
3153
{
3154
        global $db, $config, $user, $template, $phpEx;
3155
3156
        $password = request_var('password', '', true);
3157
3158
        $sql = 'SELECT forum_id
3159
                FROM ' . FORUMS_ACCESS_TABLE . '
3160
                WHERE forum_id = ' . $forum_data['forum_id'] . '
3161
                        AND user_id = ' . $user->data['user_id'] . "
3162
                        AND session_id = '" . $db->sql_escape($user->session_id) . "'";
3163
        $result = $db->sql_query($sql);
3164
        $row = $db->sql_fetchrow($result);
3165
        $db->sql_freeresult($result);
3166
3167
        if ($row)
3168
        {
3169
                return true;
3170
        }
3171
3172
        if ($password)
3173
        {
3174
                // Remove expired authorised sessions
3175
                $sql = 'SELECT f.session_id
3176
                        FROM ' . FORUMS_ACCESS_TABLE . ' f
3177
                        LEFT JOIN ' . SESSIONS_TABLE . ' s ON (f.session_id = s.session_id)
3178
                        WHERE s.session_id IS NULL';
3179
                $result = $db->sql_query($sql);
3180
3181
                if ($row = $db->sql_fetchrow($result))
3182
                {
3183
                        $sql_in = array();
3184
                        do
3185
                        {
3186
                                $sql_in[] = (string) $row['session_id'];
3187
                        }
3188
                        while ($row = $db->sql_fetchrow($result));
3189
3190
                        // Remove expired sessions
3191
                        $sql = 'DELETE FROM ' . FORUMS_ACCESS_TABLE . '
3192
                                WHERE ' . $db->sql_in_set('session_id', $sql_in);
3193
                        $db->sql_query($sql);
3194
                }
3195
                $db->sql_freeresult($result);
3196
3197
                if (phpbb_check_hash($password, $forum_data['forum_password']))
3198
                {
3199
                        $sql_ary = array(
3200
                                'forum_id'                => (int) $forum_data['forum_id'],
3201
                                'user_id'                => (int) $user->data['user_id'],
3202
                                'session_id'        => (string) $user->session_id,
3203
                        );
3204
3205
                        $db->sql_query('INSERT INTO ' . FORUMS_ACCESS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
3206
3207
                        return true;
3208
                }
3209
3210
                $template->assign_var('LOGIN_ERROR', $user->lang['WRONG_PASSWORD']);
3211
        }
3212
3213
        page_header($user->lang['LOGIN'], false);
3214
3215
        $template->assign_vars(array(
3216
                'S_LOGIN_ACTION'                => build_url(array('f')),
3217
                'S_HIDDEN_FIELDS'                => build_hidden_fields(array('f' => $forum_data['forum_id'])))
3218
        );
3219
3220
        $template->set_filenames(array(
3221
                'body' => 'login_forum.html')
3222
        );
3223
3224
        page_footer();
3225
}
3226
3227
// Little helpers
3228
3229
/**
3230
* Little helper for the build_hidden_fields function
3231
*/
3232
function _build_hidden_fields($key, $value, $specialchar, $stripslashes)
3233
{
3234
        $hidden_fields = '';
3235
3236
        if (!is_array($value))
3237
        {
3238
                $value = ($stripslashes) ? stripslashes($value) : $value;
3239
                $value = ($specialchar) ? htmlspecialchars($value, ENT_COMPAT, 'UTF-8') : $value;
3240
3241
                $hidden_fields .= '<input type="hidden" name="' . $key . '" value="' . $value . '" />' . "\n";
3242
        }
3243
        else
3244
        {
3245
                foreach ($value as $_key => $_value)
3246
                {
3247
                        $_key = ($stripslashes) ? stripslashes($_key) : $_key;
3248
                        $_key = ($specialchar) ? htmlspecialchars($_key, ENT_COMPAT, 'UTF-8') : $_key;
3249
3250
                        $hidden_fields .= _build_hidden_fields($key . '[' . $_key . ']', $_value, $specialchar, $stripslashes);
3251
                }
3252
        }
3253
3254
        return $hidden_fields;
3255
}
3256
3257
/**
3258
* Build simple hidden fields from array
3259
*
3260
* @param array $field_ary an array of values to build the hidden field from
3261
* @param bool $specialchar if true, keys and values get specialchared
3262
* @param bool $stripslashes if true, keys and values get stripslashed
3263
*
3264
* @return string the hidden fields
3265
*/
3266
function build_hidden_fields($field_ary, $specialchar = false, $stripslashes = false)
3267
{
3268
        $s_hidden_fields = '';
3269
3270
        foreach ($field_ary as $name => $vars)
3271
        {
3272
                $name = ($stripslashes) ? stripslashes($name) : $name;
3273
                $name = ($specialchar) ? htmlspecialchars($name, ENT_COMPAT, 'UTF-8') : $name;
3274
3275
                $s_hidden_fields .= _build_hidden_fields($name, $vars, $specialchar, $stripslashes);
3276
        }
3277
3278
        return $s_hidden_fields;
3279
}
3280
3281
/**
3282
* Parse cfg file
3283
*/
3284
function parse_cfg_file($filename, $lines = false)
3285
{
3286
        $parsed_items = array();
3287
3288
        if ($lines === false)
3289
        {
3290
                $lines = file($filename);
3291
        }
3292
3293
        foreach ($lines as $line)
3294
        {
3295
                $line = trim($line);
3296
3297
                if (!$line || $line[0] == '#' || ($delim_pos = strpos($line, '=')) === false)
3298
                {
3299
                        continue;
3300
                }
3301
3302
                // Determine first occurrence, since in values the equal sign is allowed
3303
                $key = strtolower(trim(substr($line, 0, $delim_pos)));
3304
                $value = trim(substr($line, $delim_pos + 1));
3305
3306
                if (in_array($value, array('off', 'false', '0')))
3307
                {
3308
                        $value = false;
3309
                }
3310
                else if (in_array($value, array('on', 'true', '1')))
3311
                {
3312
                        $value = true;
3313
                }
3314
                else if (!trim($value))
3315
                {
3316
                        $value = '';
3317
                }
3318
                else if (($value[0] == "'" && $value[sizeof($value) - 1] == "'") || ($value[0] == '"' && $value[sizeof($value) - 1] == '"'))
3319
                {
3320
                        $value = substr($value, 1, sizeof($value)-2);
3321
                }
3322
3323
                $parsed_items[$key] = $value;
3324
        }
3325
        
3326
        if (isset($parsed_items['inherit_from']) && isset($parsed_items['name']) && $parsed_items['inherit_from'] == $parsed_items['name'])
3327
        {
3328
                unset($parsed_items['inherit_from']);
3329
        }
3330
3331
        return $parsed_items;
3332
}
3333
3334
/**
3335
* Add log event
3336
*/
3337
function add_log()
3338
{
3339
        global $db, $user;
3340
3341
        // In phpBB 3.1.x i want to have logging in a class to be able to control it
3342
        // For now, we need a quite hakish approach to circumvent logging for some actions
3343
        // @todo implement cleanly
3344
        if (!empty($GLOBALS['skip_add_log']))
3345
        {
3346
                return false;
3347
        }
3348
3349
        $args = func_get_args();
3350
3351
        $mode                        = array_shift($args);
3352
        $reportee_id        = ($mode == 'user') ? intval(array_shift($args)) : '';
3353
        $forum_id                = ($mode == 'mod') ? intval(array_shift($args)) : '';
3354
        $topic_id                = ($mode == 'mod') ? intval(array_shift($args)) : '';
3355
        $action                        = array_shift($args);
3356
        $data                        = (!sizeof($args)) ? '' : serialize($args);
3357
3358
        $sql_ary = array(
3359
                'user_id'                => (empty($user->data)) ? ANONYMOUS : $user->data['user_id'],
3360
                'log_ip'                => $user->ip,
3361
                'log_time'                => time(),
3362
                'log_operation'        => $action,
3363
                'log_data'                => $data,
3364
        );
3365
3366
        switch ($mode)
3367
        {
3368
                case 'admin':
3369
                        $sql_ary['log_type'] = LOG_ADMIN;
3370
                break;
3371
3372
                case 'mod':
3373
                        $sql_ary += array(
3374
                                'log_type'        => LOG_MOD,
3375
                                'forum_id'        => $forum_id,
3376
                                'topic_id'        => $topic_id
3377
                        );
3378
                break;
3379
3380
                case 'user':
3381
                        $sql_ary += array(
3382
                                'log_type'                => LOG_USERS,
3383
                                'reportee_id'        => $reportee_id
3384
                        );
3385
                break;
3386
3387
                case 'critical':
3388
                        $sql_ary['log_type'] = LOG_CRITICAL;
3389
                break;
3390
3391
                default:
3392
                        return false;
3393
        }
3394
3395
        $db->sql_query('INSERT INTO ' . LOG_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
3396
3397
        return $db->sql_nextid();
3398
}
3399
3400
/**
3401
* Return a nicely formatted backtrace.
3402
*
3403
* Turns the array returned by debug_backtrace() into HTML markup.
3404
* Also filters out absolute paths to phpBB root.
3405
*
3406
* @return string        HTML markup
3407
*/
3408
function get_backtrace()
3409
{
3410
        $output = '<div style="font-family: monospace;">';
3411
        $backtrace = debug_backtrace();
3412
3413
        // We skip the first one, because it only shows this file/function
3414
        unset($backtrace[0]);
3415
3416
        foreach ($backtrace as $trace)
3417
        {
3418
                // Strip the current directory from path
3419
                $trace['file'] = (empty($trace['file'])) ? '(not given by php)' : htmlspecialchars(phpbb_filter_root_path($trace['file']));
3420
                $trace['line'] = (empty($trace['line'])) ? '(not given by php)' : $trace['line'];
3421
3422
                // Only show function arguments for include etc.
3423
                // Other parameters may contain sensible information
3424
                $argument = '';
3425
                if (!empty($trace['args'][0]) && in_array($trace['function'], array('include', 'require', 'include_once', 'require_once')))
3426
                {
3427
                        $argument = htmlspecialchars(phpbb_filter_root_path($trace['args'][0]));
3428
                }
3429
3430
                $trace['class'] = (!isset($trace['class'])) ? '' : $trace['class'];
3431
                $trace['type'] = (!isset($trace['type'])) ? '' : $trace['type'];
3432
3433
                $output .= '<br />';
3434
                $output .= '<b>FILE:</b> ' . $trace['file'] . '<br />';
3435
                $output .= '<b>LINE:</b> ' . ((!empty($trace['line'])) ? $trace['line'] : '') . '<br />';
3436
3437
                $output .= '<b>CALL:</b> ' . htmlspecialchars($trace['class'] . $trace['type'] . $trace['function']);
3438
                $output .= '(' . (($argument !== '') ? "'$argument'" : '') . ')<br />';
3439
        }
3440
        $output .= '</div>';
3441
        return $output;
3442
}
3443
3444
/**
3445
* This function returns a regular expression pattern for commonly used expressions
3446
* Use with / as delimiter for email mode and # for url modes
3447
* mode can be: email|bbcode_htm|url|url_inline|www_url|www_url_inline|relative_url|relative_url_inline|ipv4|ipv6
3448
*/
3449
function get_preg_expression($mode)
3450
{
3451
        switch ($mode)
3452
        {
3453
                case 'email':
3454
                        // Regex written by James Watts and Francisco Jose Martin Moreno
3455
                        // http://fightingforalostcause.net/misc/2006/compare-email-regex.php
3456
                        return '([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*(?:[\w\!\#$\%\'\*\+\-\/\=\?\^\`{\|\}\~]|&amp;)+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)';
3457
                break;
3458
3459
                case 'bbcode_htm':
3460
                        return array(
3461
                                '#<!\-\- e \-\-><a href="mailto:(.*?)">.*?</a><!\-\- e \-\->#',
3462
                                '#<!\-\- l \-\-><a (?:class="[\w-]+" )?href="(.*?)(?:(&amp;|\?)sid=[0-9a-f]{32})?">.*?</a><!\-\- l \-\->#',
3463
                                '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="(.*?)">.*?</a><!\-\- \1 \-\->#',
3464
                                '#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#',
3465
                                '#<!\-\- .*? \-\->#s',
3466
                                '#<.*?>#s',
3467
                        );
3468
                break;
3469
3470
                // Whoa these look impressive!
3471
                // The code to generate the following two regular expressions which match valid IPv4/IPv6 addresses
3472
                // can be found in the develop directory
3473
                case 'ipv4':
3474
                        return '#^(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$#';
3475
                break;
3476
3477
                case 'ipv6':
3478
                        return '#^(?:(?:(?:[\dA-F]{1,4}:){6}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:::(?:[\dA-F]{1,4}:){0,5}(?:[\dA-F]{1,4}(?::[\dA-F]{1,4})?|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:):(?:[\dA-F]{1,4}:){4}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,2}:(?:[\dA-F]{1,4}:){3}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,3}:(?:[\dA-F]{1,4}:){2}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,4}:(?:[\dA-F]{1,4}:)(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,5}:(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,6}:[\dA-F]{1,4})|(?:(?:[\dA-F]{1,4}:){1,7}:)|(?:::))$#i';
3479
                break;
3480
3481
                case 'url':
3482
                case 'url_inline':
3483
                        $inline = ($mode == 'url') ? ')' : '';
3484
                        $scheme = ($mode == 'url') ? '[a-z\d+\-.]' : '[a-z\d+]'; // avoid automatic parsing of "word" in "last word.http://..."
3485
                        // generated with regex generation file in the develop folder
3486
                        return "[a-z]$scheme*:/{2}(?:(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})+|[0-9.]+|\[[a-z0-9.]+:[a-z0-9.]+:[a-z0-9.:]+\])(?::\d*)?(?:/(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?";
3487
                break;
3488
3489
                case 'www_url':
3490
                case 'www_url_inline':
3491
                        $inline = ($mode == 'www_url') ? ')' : '';
3492
                        return "www\.(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})+(?::\d*)?(?:/(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?";
3493
                break;
3494
3495
                case 'relative_url':
3496
                case 'relative_url_inline':
3497
                        $inline = ($mode == 'relative_url') ? ')' : '';
3498
                        return "(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*(?:/(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?";
3499
                break;
3500
3501
                case 'table_prefix':
3502
                        return '#^[a-zA-Z][a-zA-Z0-9_]*$#';
3503
                break;
3504
        }
3505
3506
        return '';
3507
}
3508
3509
/**
3510
* Generate regexp for naughty words censoring
3511
* Depends on whether installed PHP version supports unicode properties
3512
*
3513
* @param string        $word                        word template to be replaced
3514
* @param bool        $use_unicode        whether or not to take advantage of PCRE supporting unicode
3515
*
3516
* @return string $preg_expr                regex to use with word censor
3517
*/
3518
function get_censor_preg_expression($word, $use_unicode = true)
3519
{
3520
        static $unicode_support = null;
3521
3522
        // Check whether PHP version supports unicode properties
3523
        if (is_null($unicode_support))
3524
        {
3525
                $unicode_support = ((version_compare(PHP_VERSION, '5.1.0', '>=') || (version_compare(PHP_VERSION, '5.0.0-dev', '<=') && version_compare(PHP_VERSION, '4.4.0', '>='))) && @preg_match('/\p{L}/u', 'a') !== false) ? true : false;
3526
        }
3527
3528
        // Unescape the asterisk to simplify further conversions
3529
        $word = str_replace('\*', '*', preg_quote($word, '#'));
3530
3531
        if ($use_unicode && $unicode_support)
3532
        {
3533
                // Replace asterisk(s) inside the pattern, at the start and at the end of it with regexes
3534
                $word = preg_replace(array('#(?<=[\p{Nd}\p{L}_])\*+(?=[\p{Nd}\p{L}_])#iu', '#^\*+#', '#\*+$#'), array('([\x20]*?|[\p{Nd}\p{L}_-]*?)', '[\p{Nd}\p{L}_-]*?', '[\p{Nd}\p{L}_-]*?'), $word);
3535
3536
                // Generate the final substitution
3537
                $preg_expr = '#(?<![\p{Nd}\p{L}_-])(' . $word . ')(?![\p{Nd}\p{L}_-])#iu';
3538
        }
3539
        else
3540
        {
3541
                // Replace the asterisk inside the pattern, at the start and at the end of it with regexes
3542
                $word = preg_replace(array('#(?<=\S)\*+(?=\S)#iu', '#^\*+#', '#\*+$#'), array('(\x20*?\S*?)', '\S*?', '\S*?'), $word);
3543
3544
                // Generate the final substitution
3545
                $preg_expr = '#(?<!\S)(' . $word . ')(?!\S)#iu';
3546
        }
3547
3548
        return $preg_expr;
3549
}
3550
3551
/**
3552
* Returns the first block of the specified IPv6 address and as many additional
3553
* ones as specified in the length paramater.
3554
* If length is zero, then an empty string is returned.
3555
* If length is greater than 3 the complete IP will be returned
3556
*/
3557
function short_ipv6($ip, $length)
3558
{
3559
        if ($length < 1)
3560
        {
3561
                return '';
3562
        }
3563
3564
        // extend IPv6 addresses
3565
        $blocks = substr_count($ip, ':') + 1;
3566
        if ($blocks < 9)
3567
        {
3568
                $ip = str_replace('::', ':' . str_repeat('0000:', 9 - $blocks), $ip);
3569
        }
3570
        if ($ip[0] == ':')
3571
        {
3572
                $ip = '0000' . $ip;
3573
        }
3574
        if ($length < 4)
3575
        {
3576
                $ip = implode(':', array_slice(explode(':', $ip), 0, 1 + $length));
3577
        }
3578
3579
        return $ip;
3580
}
3581
3582
/**
3583
* Wrapper for php's checkdnsrr function.
3584
*
3585
* @param string $host        Fully-Qualified Domain Name
3586
* @param string $type        Resource record type to lookup
3587
*                                                Supported types are: MX (default), A, AAAA, NS, TXT, CNAME
3588
*                                                Other types may work or may not work
3589
*
3590
* @return mixed                true if entry found,
3591
*                                        false if entry not found,
3592
*                                        null if this function is not supported by this environment
3593
*
3594
* Since null can also be returned, you probably want to compare the result
3595
* with === true or === false,
3596
*
3597
* @author bantu
3598
*/
3599
function phpbb_checkdnsrr($host, $type = 'MX')
3600
{
3601
        // The dot indicates to search the DNS root (helps those having DNS prefixes on the same domain)
3602
        if (substr($host, -1) == '.')
3603
        {
3604
                $host_fqdn = $host;
3605
                $host = substr($host, 0, -1);
3606
        }
3607
        else
3608
        {
3609
                $host_fqdn = $host . '.';
3610
        }
3611
        // $host                has format        some.host.example.com
3612
        // $host_fqdn        has format        some.host.example.com.
3613
3614
        // If we're looking for an A record we can use gethostbyname()
3615
        if ($type == 'A' && function_exists('gethostbyname'))
3616
        {
3617
                return (@gethostbyname($host_fqdn) == $host_fqdn) ? false : true;
3618
        }
3619
3620
        // checkdnsrr() is available on Windows since PHP 5.3,
3621
        // but until 5.3.3 it only works for MX records
3622
        // See: http://bugs.php.net/bug.php?id=51844
3623
3624
        // Call checkdnsrr() if
3625
        // we're looking for an MX record or
3626
        // we're not on Windows or
3627
        // we're running a PHP version where #51844 has been fixed
3628
3629
        // checkdnsrr() supports AAAA since 5.0.0
3630
        // checkdnsrr() supports TXT since 5.2.4
3631
        if (
3632
                ($type == 'MX' || DIRECTORY_SEPARATOR != '\\' || version_compare(PHP_VERSION, '5.3.3', '>=')) &&
3633
                ($type != 'AAAA' || version_compare(PHP_VERSION, '5.0.0', '>=')) &&
3634
                ($type != 'TXT' || version_compare(PHP_VERSION, '5.2.4', '>=')) &&
3635
                function_exists('checkdnsrr')
3636
        )
3637
        {
3638
                return checkdnsrr($host_fqdn, $type);
3639
        }
3640
3641
        // dns_get_record() is available since PHP 5; since PHP 5.3 also on Windows,
3642
        // but on Windows it does not work reliable for AAAA records before PHP 5.3.1
3643
3644
        // Call dns_get_record() if
3645
        // we're not looking for an AAAA record or
3646
        // we're not on Windows or
3647
        // we're running a PHP version where AAAA lookups work reliable
3648
        if (
3649
                ($type != 'AAAA' || DIRECTORY_SEPARATOR != '\\' || version_compare(PHP_VERSION, '5.3.1', '>=')) &&
3650
                function_exists('dns_get_record')
3651
        )
3652
        {
3653
                // dns_get_record() expects an integer as second parameter
3654
                // We have to convert the string $type to the corresponding integer constant.
3655
                $type_constant = 'DNS_' . $type;
3656
                $type_param = (defined($type_constant)) ? constant($type_constant) : DNS_ANY;
3657
3658
                // dns_get_record() might throw E_WARNING and return false for records that do not exist
3659
                $resultset = @dns_get_record($host_fqdn, $type_param);
3660
3661
                if (empty($resultset) || !is_array($resultset))
3662
                {
3663
                        return false;
3664
                }
3665
                else if ($type_param == DNS_ANY)
3666
                {
3667
                        // $resultset is a non-empty array
3668
                        return true;
3669
                }
3670
3671
                foreach ($resultset as $result)
3672
                {
3673
                        if (
3674
                                isset($result['host']) && $result['host'] == $host &&
3675
                                isset($result['type']) && $result['type'] == $type
3676
                        )
3677
                        {
3678
                                return true;
3679
                        }
3680
                }
3681
3682
                return false;
3683
        }
3684
3685
        // If we're on Windows we can still try to call nslookup via exec() as a last resort
3686
        if (DIRECTORY_SEPARATOR == '\\' && function_exists('exec'))
3687
        {
3688
                @exec('nslookup -type=' . escapeshellarg($type) . ' ' . escapeshellarg($host_fqdn), $output);
3689
3690
                // If output is empty, the nslookup failed
3691
                if (empty($output))
3692
                {
3693
                        return NULL;
3694
                }
3695
3696
                foreach ($output as $line)
3697
                {
3698
                        $line = trim($line);
3699
3700
                        if (empty($line))
3701
                        {
3702
                                continue;
3703
                        }
3704
3705
                        // Squash tabs and multiple whitespaces to a single whitespace.
3706
                        $line = preg_replace('/\s+/', ' ', $line);
3707
3708
                        switch ($type)
3709
                        {
3710
                                case 'MX':
3711
                                        if (stripos($line, "$host MX") === 0)
3712
                                        {
3713
                                                return true;
3714
                                        }
3715
                                break;
3716
3717
                                case 'NS':
3718
                                        if (stripos($line, "$host nameserver") === 0)
3719
                                        {
3720
                                                return true;
3721
                                        }
3722
                                break;
3723
3724
                                case 'TXT':
3725
                                        if (stripos($line, "$host text") === 0)
3726
                                        {
3727
                                                return true;
3728
                                        }
3729
                                break;
3730
3731
                                case 'CNAME':
3732
                                        if (stripos($line, "$host canonical name") === 0)
3733
                                        {
3734
                                                return true;
3735
                                        }
3736
                                break;
3737
3738
                                default:
3739
                                case 'AAAA':
3740
                                        // AAAA records returned by nslookup on Windows XP/2003 have this format.
3741
                                        // Later Windows versions use the A record format below for AAAA records.
3742
                                        if (stripos($line, "$host AAAA IPv6 address") === 0)
3743
                                        {
3744
                                                return true;
3745
                                        }
3746
                                // No break
3747
3748
                                case 'A':
3749
                                        if (!empty($host_matches))
3750
                                        {
3751
                                                // Second line
3752
                                                if (stripos($line, "Address: ") === 0)
3753
                                                {
3754
                                                        return true;
3755
                                                }
3756
                                                else
3757
                                                {
3758
                                                        $host_matches = false;
3759
                                                }
3760
                                        }
3761
                                        else if (stripos($line, "Name: $host") === 0)
3762
                                        {
3763
                                                // First line
3764
                                                $host_matches = true;
3765
                                        }
3766
                                break;
3767
                        }
3768
                }
3769
3770
                return false;
3771
        }
3772
3773
        return NULL;
3774
}
3775
3776
// Handler, header and footer
3777
3778
/**
3779
* Error and message handler, call with trigger_error if reqd
3780
*/
3781
function msg_handler($errno, $msg_text, $errfile, $errline)
3782
{
3783
        global $cache, $db, $auth, $template, $config, $user;
3784
        global $phpEx, $phpbb_root_path, $msg_title, $msg_long_text;
3785
3786
        // Do not display notices if we suppress them via @
3787
        if (error_reporting() == 0 && $errno != E_USER_ERROR && $errno != E_USER_WARNING && $errno != E_USER_NOTICE)
3788
        {
3789
                return;
3790
        }
3791
3792
        // Message handler is stripping text. In case we need it, we are possible to define long text...
3793
        if (isset($msg_long_text) && $msg_long_text && !$msg_text)
3794
        {
3795
                $msg_text = $msg_long_text;
3796
        }
3797
3798
        if (!defined('E_DEPRECATED'))
3799
        {
3800
                define('E_DEPRECATED', 8192);
3801
        }
3802
3803
        switch ($errno)
3804
        {
3805
                case E_NOTICE:
3806
                case E_WARNING:
3807
3808
                        // Check the error reporting level and return if the error level does not match
3809
                        // If DEBUG is defined the default level is E_ALL
3810
                        if (($errno & ((defined('DEBUG')) ? E_ALL : error_reporting())) == 0)
3811
                        {
3812
                                return;
3813
                        }
3814
3815
                        if (strpos($errfile, 'cache') === false && strpos($errfile, 'template.') === false)
3816
                        {
3817
                                $errfile = phpbb_filter_root_path($errfile);
3818
                                $msg_text = phpbb_filter_root_path($msg_text);
3819
                                $error_name = ($errno === E_WARNING) ? 'PHP Warning' : 'PHP Notice';
3820
                                echo '<b>[phpBB Debug] ' . $error_name . '</b>: in file <b>' . $errfile . '</b> on line <b>' . $errline . '</b>: <b>' . $msg_text . '</b><br />' . "\n";
3821
3822
                                // we are writing an image - the user won't see the debug, so let's place it in the log
3823
                                if (defined('IMAGE_OUTPUT') || defined('IN_CRON'))
3824
                                {
3825
                                        add_log('critical', 'LOG_IMAGE_GENERATION_ERROR', $errfile, $errline, $msg_text);
3826
                                }
3827
                                // echo '<br /><br />BACKTRACE<br />' . get_backtrace() . '<br />' . "\n";
3828
                        }
3829
3830
                        return;
3831
3832
                break;
3833
3834
                case E_USER_ERROR:
3835
3836
                        if (!empty($user) && !empty($user->lang))
3837
                        {
3838
                                $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
3839
                                $msg_title = (!isset($msg_title)) ? $user->lang['GENERAL_ERROR'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
3840
3841
                                $l_return_index = sprintf($user->lang['RETURN_INDEX'], '<a href="' . $phpbb_root_path . '">', '</a>');
3842
                                $l_notify = '';
3843
3844
                                if (!empty($config['board_contact']))
3845
                                {
3846
                                        $l_notify = '<p>' . sprintf($user->lang['NOTIFY_ADMIN_EMAIL'], $config['board_contact']) . '</p>';
3847
                                }
3848
                        }
3849
                        else
3850
                        {
3851
                                $msg_title = 'General Error';
3852
                                $l_return_index = '<a href="' . $phpbb_root_path . '">Return to index page</a>';
3853
                                $l_notify = '';
3854
3855
                                if (!empty($config['board_contact']))
3856
                                {
3857
                                        $l_notify = '<p>Please notify the board administrator or webmaster: <a href="mailto:' . $config['board_contact'] . '">' . $config['board_contact'] . '</a></p>';
3858
                                }
3859
                        }
3860
3861
                        $log_text = $msg_text;
3862
                        $backtrace = get_backtrace();
3863
                        if ($backtrace)
3864
                        {
3865
                                $log_text .= '<br /><br />BACKTRACE<br />' . $backtrace;
3866
                        }
3867
3868
                        if (defined('IN_INSTALL') || defined('DEBUG_EXTRA') || isset($auth) && $auth->acl_get('a_'))
3869
                        {
3870
                                $msg_text = $log_text;
3871
                        }
3872
3873
                        if ((defined('DEBUG') || defined('IN_CRON') || defined('IMAGE_OUTPUT')) && isset($db))
3874
                        {
3875
                                // let's avoid loops
3876
                                $db->sql_return_on_error(true);
3877
                                add_log('critical', 'LOG_GENERAL_ERROR', $msg_title, $log_text);
3878
                                $db->sql_return_on_error(false);
3879
                        }
3880
3881
                        // Do not send 200 OK, but service unavailable on errors
3882
                        send_status_line(503, 'Service Unavailable');
3883
3884
                        garbage_collection();
3885
3886
                        // Try to not call the adm page data...
3887
3888
                        echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
3889
                        echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">';
3890
                        echo '<head>';
3891
                        echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
3892
                        echo '<title>' . $msg_title . '</title>';
3893
                        echo '<style type="text/css">' . "\n" . '/* <![CDATA[ */' . "\n";
3894
                        echo '* { margin: 0; padding: 0; } html { font-size: 100%; height: 100%; margin-bottom: 1px; background-color: #E4EDF0; } body { font-family: "Lucida Grande", Verdana, Helvetica, Arial, sans-serif; color: #536482; background: #E4EDF0; font-size: 62.5%; margin: 0; } ';
3895
                        echo 'a:link, a:active, a:visited { color: #006699; text-decoration: none; } a:hover { color: #DD6900; text-decoration: underline; } ';
3896
                        echo '#wrap { padding: 0 20px 15px 20px; min-width: 615px; } #page-header { text-align: right; height: 40px; } #page-footer { clear: both; font-size: 1em; text-align: center; } ';
3897
                        echo '.panel { margin: 4px 0; background-color: #FFFFFF; border: solid 1px  #A9B8C2; } ';
3898
                        echo '#errorpage #page-header a { font-weight: bold; line-height: 6em; } #errorpage #content { padding: 10px; } #errorpage #content h1 { line-height: 1.2em; margin-bottom: 0; color: #DF075C; } ';
3899
                        echo '#errorpage #content div { margin-top: 20px; margin-bottom: 5px; border-bottom: 1px solid #CCCCCC; padding-bottom: 5px; color: #333333; font: bold 1.2em "Lucida Grande", Arial, Helvetica, sans-serif; text-decoration: none; line-height: 120%; text-align: left; } ';
3900
                        echo "\n" . '/* ]]> */' . "\n";
3901
                        echo '</style>';
3902
                        echo '</head>';
3903
                        echo '<body id="errorpage">';
3904
                        echo '<div id="wrap">';
3905
                        echo '        <div id="page-header">';
3906
                        echo '                ' . $l_return_index;
3907
                        echo '        </div>';
3908
                        echo '        <div id="acp">';
3909
                        echo '        <div class="panel">';
3910
                        echo '                <div id="content">';
3911
                        echo '                        <h1>' . $msg_title . '</h1>';
3912
3913
                        echo '                        <div>' . $msg_text . '</div>';
3914
3915
                        echo $l_notify;
3916
3917
                        echo '                </div>';
3918
                        echo '        </div>';
3919
                        echo '        </div>';
3920
                        echo '        <div id="page-footer">';
3921
                        echo '                Powered by <a href="http://www.phpbb.com/">phpBB</a>&reg; Forum Software &copy; phpBB Group';
3922
                        echo '        </div>';
3923
                        echo '</div>';
3924
                        echo '</body>';
3925
                        echo '</html>';
3926
3927
                        exit_handler();
3928
3929
                        // On a fatal error (and E_USER_ERROR *is* fatal) we never want other scripts to continue and force an exit here.
3930
                        exit;
3931
                break;
3932
3933
                case E_USER_WARNING:
3934
                case E_USER_NOTICE:
3935
3936
                        define('IN_ERROR_HANDLER', true);
3937
3938
                        if (empty($user->data))
3939
                        {
3940
                                $user->session_begin();
3941
                        }
3942
3943
                        // We re-init the auth array to get correct results on login/logout
3944
                        $auth->acl($user->data);
3945
3946
                        if (empty($user->lang))
3947
                        {
3948
                                $user->setup();
3949
                        }
3950
3951
                        if ($msg_text == 'ERROR_NO_ATTACHMENT' || $msg_text == 'NO_FORUM' || $msg_text == 'NO_TOPIC' || $msg_text == 'NO_USER')
3952
                        {
3953
                                send_status_line(404, 'Not Found');
3954
                        }
3955
3956
                        $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
3957
                        $msg_title = (!isset($msg_title)) ? $user->lang['INFORMATION'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
3958
3959
                        if (!defined('HEADER_INC'))
3960
                        {
3961
                                if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
3962
                                {
3963
                                        adm_page_header($msg_title);
3964
                                }
3965
                                else
3966
                                {
3967
                                        page_header($msg_title, false);
3968
                                }
3969
                        }
3970
3971
                        $template->set_filenames(array(
3972
                                'body' => 'message_body.html')
3973
                        );
3974
3975
                        $template->assign_vars(array(
3976
                                'MESSAGE_TITLE'                => $msg_title,
3977
                                'MESSAGE_TEXT'                => $msg_text,
3978
                                'S_USER_WARNING'        => ($errno == E_USER_WARNING) ? true : false,
3979
                                'S_USER_NOTICE'                => ($errno == E_USER_NOTICE) ? true : false)
3980
                        );
3981
3982
                        // We do not want the cron script to be called on error messages
3983
                        define('IN_CRON', true);
3984
3985
                        if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
3986
                        {
3987
                                adm_page_footer();
3988
                        }
3989
                        else
3990
                        {
3991
                                page_footer();
3992
                        }
3993
3994
                        exit_handler();
3995
                break;
3996
3997
                // PHP4 compatibility
3998
                case E_DEPRECATED:
3999
                        return true;
4000
                break;
4001
        }
4002
4003
        // If we notice an error not handled here we pass this back to PHP by returning false
4004
        // This may not work for all php versions
4005
        return false;
4006
}
4007
4008
/**
4009
* Removes absolute path to phpBB root directory from error messages
4010
* and converts backslashes to forward slashes.
4011
*
4012
* @param string $errfile        Absolute file path
4013
*                                                        (e.g. /var/www/phpbb3/phpBB/includes/functions.php)
4014
*                                                        Please note that if $errfile is outside of the phpBB root,
4015
*                                                        the root path will not be found and can not be filtered.
4016
* @return string                        Relative file path
4017
*                                                        (e.g. /includes/functions.php)
4018
*/
4019
function phpbb_filter_root_path($errfile)
4020
{
4021
        static $root_path;
4022
4023
        if (empty($root_path))
4024
        {
4025
                $root_path = phpbb_realpath(dirname(__FILE__) . '/../');
4026
        }
4027
4028
        return str_replace(array($root_path, '\\'), array('[ROOT]', '/'), $errfile);
4029
}
4030
4031
/**
4032
* Queries the session table to get information about online guests
4033
* @param int $item_id Limits the search to the item with this id
4034
* @param string $item The name of the item which is stored in the session table as session_{$item}_id
4035
* @return int The number of active distinct guest sessions
4036
*/
4037
function obtain_guest_count($item_id = 0, $item = 'forum')
4038
{
4039
        global $db, $config;
4040
4041
        if ($item_id)
4042
        {
4043
                $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
4044
        }
4045
        else
4046
        {
4047
                $reading_sql = '';
4048
        }
4049
        $time = (time() - (intval($config['load_online_time']) * 60));
4050
4051
        // Get number of online guests
4052
4053
        if ($db->sql_layer === 'sqlite')
4054
        {
4055
                $sql = 'SELECT COUNT(session_ip) as num_guests
4056
                        FROM (
4057
                                SELECT DISTINCT s.session_ip
4058
                                FROM ' . SESSIONS_TABLE . ' s
4059
                                WHERE s.session_user_id = ' . ANONYMOUS . '
4060
                                        AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
4061
                                $reading_sql .
4062
                        ')';
4063
        }
4064
        else
4065
        {
4066
                $sql = 'SELECT COUNT(DISTINCT s.session_ip) as num_guests
4067
                        FROM ' . SESSIONS_TABLE . ' s
4068
                        WHERE s.session_user_id = ' . ANONYMOUS . '
4069
                                AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
4070
                        $reading_sql;
4071
        }
4072
        $result = $db->sql_query($sql);
4073
        $guests_online = (int) $db->sql_fetchfield('num_guests');
4074
        $db->sql_freeresult($result);
4075
4076
        return $guests_online;
4077
}
4078
4079
/**
4080
* Queries the session table to get information about online users
4081
* @param int $item_id Limits the search to the item with this id
4082
* @param string $item The name of the item which is stored in the session table as session_{$item}_id
4083
* @return array An array containing the ids of online, hidden and visible users, as well as statistical info
4084
*/
4085
function obtain_users_online($item_id = 0, $item = 'forum')
4086
{
4087
        global $db, $config, $user;
4088
4089
        $reading_sql = '';
4090
        if ($item_id !== 0)
4091
        {
4092
                $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
4093
        }
4094
4095
        $online_users = array(
4096
                'online_users'                        => array(),
4097
                'hidden_users'                        => array(),
4098
                'total_online'                        => 0,
4099
                'visible_online'                => 0,
4100
                'hidden_online'                        => 0,
4101
                'guests_online'                        => 0,
4102
        );
4103
4104
        if ($config['load_online_guests'])
4105
        {
4106
                $online_users['guests_online'] = obtain_guest_count($item_id, $item);
4107
        }
4108
4109
        // a little discrete magic to cache this for 30 seconds
4110
        $time = (time() - (intval($config['load_online_time']) * 60));
4111
4112
        $sql = 'SELECT s.session_user_id, s.session_ip, s.session_viewonline
4113
                FROM ' . SESSIONS_TABLE . ' s
4114
                WHERE s.session_time >= ' . ($time - ((int) ($time % 30))) .
4115
                        $reading_sql .
4116
                ' AND s.session_user_id <> ' . ANONYMOUS;
4117
        $result = $db->sql_query($sql);
4118
4119
        while ($row = $db->sql_fetchrow($result))
4120
        {
4121
                // Skip multiple sessions for one user
4122
                if (!isset($online_users['online_users'][$row['session_user_id']]))
4123
                {
4124
                        $online_users['online_users'][$row['session_user_id']] = (int) $row['session_user_id'];
4125
                        if ($row['session_viewonline'])
4126
                        {
4127
                                $online_users['visible_online']++;
4128
                        }
4129
                        else
4130
                        {
4131
                                $online_users['hidden_users'][$row['session_user_id']] = (int) $row['session_user_id'];
4132
                                $online_users['hidden_online']++;
4133
                        }
4134
                }
4135
        }
4136
        $online_users['total_online'] = $online_users['guests_online'] + $online_users['visible_online'] + $online_users['hidden_online'];
4137
        $db->sql_freeresult($result);
4138
4139
        return $online_users;
4140
}
4141
4142
/**
4143
* Uses the result of obtain_users_online to generate a localized, readable representation.
4144
* @param mixed $online_users result of obtain_users_online - array with user_id lists for total, hidden and visible users, and statistics
4145
* @param int $item_id Indicate that the data is limited to one item and not global
4146
* @param string $item The name of the item which is stored in the session table as session_{$item}_id
4147
* @return array An array containing the string for output to the template
4148
*/
4149
function obtain_users_online_string($online_users, $item_id = 0, $item = 'forum')
4150
{
4151
        global $config, $db, $user, $auth;
4152
4153
        $user_online_link = $online_userlist = '';
4154
        // Need caps version of $item for language-strings
4155
        $item_caps = strtoupper($item);
4156
4157
        if (sizeof($online_users['online_users']))
4158
        {
4159
                $sql = 'SELECT username, username_clean, user_id, user_type, user_allow_viewonline, user_colour
4160
                                FROM ' . USERS_TABLE . '
4161
                                WHERE ' . $db->sql_in_set('user_id', $online_users['online_users']) . '
4162
                                ORDER BY username_clean ASC';
4163
                $result = $db->sql_query($sql);
4164
4165
                while ($row = $db->sql_fetchrow($result))
4166
                {
4167
                        // User is logged in and therefore not a guest
4168
                        if ($row['user_id'] != ANONYMOUS)
4169
                        {
4170
                                if (isset($online_users['hidden_users'][$row['user_id']]))
4171
                                {
4172
                                        $row['username'] = '<em>' . $row['username'] . '</em>';
4173
                                }
4174
4175
                                if (!isset($online_users['hidden_users'][$row['user_id']]) || $auth->acl_get('u_viewonline'))
4176
                                {
4177
                                        $user_online_link = get_username_string(($row['user_type'] <> USER_IGNORE) ? 'full' : 'no_profile', $row['user_id'], $row['username'], $row['user_colour']);
4178
                                        $online_userlist .= ($online_userlist != '') ? ', ' . $user_online_link : $user_online_link;
4179
                                }
4180
                        }
4181
                }
4182
                $db->sql_freeresult($result);
4183
        }
4184
4185
        if (!$online_userlist)
4186
        {
4187
                $online_userlist = $user->lang['NO_ONLINE_USERS'];
4188
        }
4189
4190
        if ($item_id === 0)
4191
        {
4192
                $online_userlist = $user->lang['REGISTERED_USERS'] . ' ' . $online_userlist;
4193
        }
4194
        else if ($config['load_online_guests'])
4195
        {
4196
                $l_online = ($online_users['guests_online'] === 1) ? $user->lang['BROWSING_' . $item_caps . '_GUEST'] : $user->lang['BROWSING_' . $item_caps . '_GUESTS'];
4197
                $online_userlist = sprintf($l_online, $online_userlist, $online_users['guests_online']);
4198
        }
4199
        else
4200
        {
4201
                $online_userlist = sprintf($user->lang['BROWSING_' . $item_caps], $online_userlist);
4202
        }
4203
        // Build online listing
4204
        $vars_online = array(
4205
                'ONLINE'        => array('total_online', 'l_t_user_s', 0),
4206
                'REG'                => array('visible_online', 'l_r_user_s', !$config['load_online_guests']),
4207
                'HIDDEN'        => array('hidden_online', 'l_h_user_s', $config['load_online_guests']),
4208
                'GUEST'                => array('guests_online', 'l_g_user_s', 0)
4209
        );
4210
4211
        foreach ($vars_online as $l_prefix => $var_ary)
4212
        {
4213
                if ($var_ary[2])
4214
                {
4215
                        $l_suffix = '_AND';
4216
                }
4217
                else
4218
                {
4219
                        $l_suffix = '';
4220
                }
4221
                switch ($online_users[$var_ary[0]])
4222
                {
4223
                        case 0:
4224
                                ${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_ZERO_TOTAL' . $l_suffix];
4225
                        break;
4226
4227
                        case 1:
4228
                                ${$var_ary[1]} = $user->lang[$l_prefix . '_USER_TOTAL' . $l_suffix];
4229
                        break;
4230
4231
                        default:
4232
                                ${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_TOTAL' . $l_suffix];
4233
                        break;
4234
                }
4235
        }
4236
        unset($vars_online);
4237
4238
        $l_online_users = sprintf($l_t_user_s, $online_users['total_online']);
4239
        $l_online_users .= sprintf($l_r_user_s, $online_users['visible_online']);
4240
        $l_online_users .= sprintf($l_h_user_s, $online_users['hidden_online']);
4241
4242
        if ($config['load_online_guests'])
4243
        {
4244
                $l_online_users .= sprintf($l_g_user_s, $online_users['guests_online']);
4245
        }
4246
4247
4248
4249
        return array(
4250
                'online_userlist'        => $online_userlist,
4251
                'l_online_users'        => $l_online_users,
4252
        );
4253
}
4254
4255
/**
4256
* Get option bitfield from custom data
4257
*
4258
* @param int        $bit                The bit/value to get
4259
* @param int        $data                Current bitfield to check
4260
* @return bool        Returns true if value of constant is set in bitfield, else false
4261
*/
4262
function phpbb_optionget($bit, $data)
4263
{
4264
        return ($data & 1 << (int) $bit) ? true : false;
4265
}
4266
4267
/**
4268
* Set option bitfield
4269
*
4270
* @param int        $bit                The bit/value to set/unset
4271
* @param bool        $set                True if option should be set, false if option should be unset.
4272
* @param int        $data                Current bitfield to change
4273
*
4274
* @return int        The new bitfield
4275
*/
4276
function phpbb_optionset($bit, $set, $data)
4277
{
4278
        if ($set && !($data & 1 << $bit))
4279
        {
4280
                $data += 1 << $bit;
4281
        }
4282
        else if (!$set && ($data & 1 << $bit))
4283
        {
4284
                $data -= 1 << $bit;
4285
        }
4286
4287
        return $data;
4288
}
4289
4290
/**
4291
* Login using http authenticate.
4292
*
4293
* @param array        $param                Parameter array, see $param_defaults array.
4294
*
4295
* @return void
4296
*/
4297
function phpbb_http_login($param)
4298
{
4299
        global $auth, $user;
4300
        global $config;
4301
4302
        $param_defaults = array(
4303
                'auth_message'        => '',
4304
4305
                'autologin'                => false,
4306
                'viewonline'        => true,
4307
                'admin'                        => false,
4308
        );
4309
4310
        // Overwrite default values with passed values
4311
        $param = array_merge($param_defaults, $param);
4312
4313
        // User is already logged in
4314
        // We will not overwrite his session
4315
        if (!empty($user->data['is_registered']))
4316
        {
4317
                return;
4318
        }
4319
4320
        // $_SERVER keys to check
4321
        $username_keys = array(
4322
                'PHP_AUTH_USER',
4323
                'Authorization',
4324
                'REMOTE_USER', 'REDIRECT_REMOTE_USER',
4325
                'HTTP_AUTHORIZATION', 'REDIRECT_HTTP_AUTHORIZATION',
4326
                'REMOTE_AUTHORIZATION', 'REDIRECT_REMOTE_AUTHORIZATION',
4327
                'AUTH_USER',
4328
        );
4329
4330
        $password_keys = array(
4331
                'PHP_AUTH_PW',
4332
                'REMOTE_PASSWORD',
4333
                'AUTH_PASSWORD',
4334
        );
4335
4336
        $username = null;
4337
        foreach ($username_keys as $k)
4338
        {
4339
                if (isset($_SERVER[$k]))
4340
                {
4341
                        $username = $_SERVER[$k];
4342
                        break;
4343
                }
4344
        }
4345
4346
        $password = null;
4347
        foreach ($password_keys as $k)
4348
        {
4349
                if (isset($_SERVER[$k]))
4350
                {
4351
                        $password = $_SERVER[$k];
4352
                        break;
4353
                }
4354
        }
4355
4356
        // Decode encoded information (IIS, CGI, FastCGI etc.)
4357
        if (!is_null($username) && is_null($password) && strpos($username, 'Basic ') === 0)
4358
        {
4359
                list($username, $password) = explode(':', base64_decode(substr($username, 6)), 2);
4360
        }
4361
4362
        if (!is_null($username) && !is_null($password))
4363
        {
4364
                set_var($username, $username, 'string', true);
4365
                set_var($password, $password, 'string', true);
4366
4367
                $auth_result = $auth->login($username, $password, $param['autologin'], $param['viewonline'], $param['admin']);
4368
4369
                if ($auth_result['status'] == LOGIN_SUCCESS)
4370
                {
4371
                        return;
4372
                }
4373
                else if ($auth_result['status'] == LOGIN_ERROR_ATTEMPTS)
4374
                {
4375
                        send_status_line(401, 'Unauthorized');
4376
4377
                        trigger_error('NOT_AUTHORISED');
4378
                }
4379
        }
4380
4381
        // Prepend sitename to auth_message
4382
        $param['auth_message'] = ($param['auth_message'] === '') ? $config['sitename'] : $config['sitename'] . ' - ' . $param['auth_message'];
4383
4384
        // We should probably filter out non-ASCII characters - RFC2616
4385
        $param['auth_message'] = preg_replace('/[\x80-\xFF]/', '?', $param['auth_message']);
4386
4387
        header('WWW-Authenticate: Basic realm="' . $param['auth_message'] . '"');
4388
        send_status_line(401, 'Unauthorized');
4389
4390
        trigger_error('NOT_AUTHORISED');
4391
}
4392
4393
/**
4394
* Generate page header
4395
*/
4396
function page_header($page_title = '', $display_online_list = true, $item_id = 0, $item = 'forum')
4397
{
4398
        global $db, $config, $template, $SID, $_SID, $_EXTRA_URL, $user, $auth, $phpEx, $phpbb_root_path;
4399
4400
        if (defined('HEADER_INC'))
4401
        {
4402
                return;
4403
        }
4404
4405
        define('HEADER_INC', true);
4406
4407
        // gzip_compression
4408
        if ($config['gzip_compress'])
4409
        {
4410
                // to avoid partially compressed output resulting in blank pages in
4411
                // the browser or error messages, compression is disabled in a few cases:
4412
                //
4413
                // 1) if headers have already been sent, this indicates plaintext output
4414
                //    has been started so further content must not be compressed
4415
                // 2) the length of the current output buffer is non-zero. This means
4416
                //    there is already some uncompressed content in this output buffer
4417
                //    so further output must not be compressed
4418
                // 3) if more than one level of output buffering is used because we
4419
                //    cannot test all output buffer level content lengths. One level
4420
                //    could be caused by php.ini output_buffering. Anything
4421
                //    beyond that is manual, so the code wrapping phpBB in output buffering
4422
                //    can easily compress the output itself.
4423
                //
4424
                if (@extension_loaded('zlib') && !headers_sent() && ob_get_level() <= 1 && ob_get_length() == 0)
4425
                {
4426
                        ob_start('ob_gzhandler');
4427
                }
4428
        }
4429
4430
        // Generate logged in/logged out status
4431
        if ($user->data['user_id'] != ANONYMOUS)
4432
        {
4433
                $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=logout', true, $user->session_id);
4434
                $l_login_logout = sprintf($user->lang['LOGOUT_USER'], $user->data['username']);
4435
        }
4436
        else
4437
        {
4438
                $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login');
4439
                $l_login_logout = $user->lang['LOGIN'];
4440
        }
4441
4442
        // Last visit date/time
4443
        $s_last_visit = ($user->data['user_id'] != ANONYMOUS) ? $user->format_date($user->data['session_last_visit']) : '';
4444
4445
        // Get users online list ... if required
4446
        $l_online_users = $online_userlist = $l_online_record = $l_online_time = '';
4447
4448
        if ($config['load_online'] && $config['load_online_time'] && $display_online_list)
4449
        {
4450
                /**
4451
                * Load online data:
4452
                * For obtaining another session column use $item and $item_id in the function-parameter, whereby the column is session_{$item}_id.
4453
                */
4454
                $item_id = max($item_id, 0);
4455
4456
                $online_users = obtain_users_online($item_id, $item);
4457
                $user_online_strings = obtain_users_online_string($online_users, $item_id, $item);
4458
4459
                $l_online_users = $user_online_strings['l_online_users'];
4460
                $online_userlist = $user_online_strings['online_userlist'];
4461
                $total_online_users = $online_users['total_online'];
4462
4463
                if ($total_online_users > $config['record_online_users'])
4464
                {
4465
                        set_config('record_online_users', $total_online_users, true);
4466
                        set_config('record_online_date', time(), true);
4467
                }
4468
4469
                $l_online_record = sprintf($user->lang['RECORD_ONLINE_USERS'], $config['record_online_users'], $user->format_date($config['record_online_date'], false, true));
4470
4471
                $l_online_time = ($config['load_online_time'] == 1) ? 'VIEW_ONLINE_TIME' : 'VIEW_ONLINE_TIMES';
4472
                $l_online_time = sprintf($user->lang[$l_online_time], $config['load_online_time']);
4473
        }
4474
4475
        $l_privmsgs_text = $l_privmsgs_text_unread = '';
4476
        $s_privmsg_new = false;
4477
4478
        // Obtain number of new private messages if user is logged in
4479
        if (!empty($user->data['is_registered']))
4480
        {
4481
                if ($user->data['user_new_privmsg'])
4482
                {
4483
                        $l_message_new = ($user->data['user_new_privmsg'] == 1) ? $user->lang['NEW_PM'] : $user->lang['NEW_PMS'];
4484
                        $l_privmsgs_text = sprintf($l_message_new, $user->data['user_new_privmsg']);
4485
4486
                        if (!$user->data['user_last_privmsg'] || $user->data['user_last_privmsg'] > $user->data['session_last_visit'])
4487
                        {
4488
                                $sql = 'UPDATE ' . USERS_TABLE . '
4489
                                        SET user_last_privmsg = ' . $user->data['session_last_visit'] . '
4490
                                        WHERE user_id = ' . $user->data['user_id'];
4491
                                $db->sql_query($sql);
4492
4493
                                $s_privmsg_new = true;
4494
                        }
4495
                        else
4496
                        {
4497
                                $s_privmsg_new = false;
4498
                        }
4499
                }
4500
                else
4501
                {
4502
                        $l_privmsgs_text = $user->lang['NO_NEW_PM'];
4503
                        $s_privmsg_new = false;
4504
                }
4505
4506
                $l_privmsgs_text_unread = '';
4507
4508
                if ($user->data['user_unread_privmsg'] && $user->data['user_unread_privmsg'] != $user->data['user_new_privmsg'])
4509
                {
4510
                        $l_message_unread = ($user->data['user_unread_privmsg'] == 1) ? $user->lang['UNREAD_PM'] : $user->lang['UNREAD_PMS'];
4511
                        $l_privmsgs_text_unread = sprintf($l_message_unread, $user->data['user_unread_privmsg']);
4512
                }
4513
        }
4514
4515
        $forum_id = request_var('f', 0);
4516
        $topic_id = request_var('t', 0);
4517
4518
        $s_feed_news = false;
4519
4520
        // Get option for news
4521
        if ($config['feed_enable'])
4522
        {
4523
                $sql = 'SELECT forum_id
4524
                        FROM ' . FORUMS_TABLE . '
4525
                        WHERE ' . $db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0');
4526
                $result = $db->sql_query_limit($sql, 1, 0, 600);
4527
                $s_feed_news = (int) $db->sql_fetchfield('forum_id');
4528
                $db->sql_freeresult($result);
4529
        }
4530
4531
        // Determine board url - we may need it later
4532
        $board_url = generate_board_url() . '/';
4533
        $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $phpbb_root_path;
4534
4535
        // Which timezone?
4536
        $tz = ($user->data['user_id'] != ANONYMOUS) ? strval(doubleval($user->data['user_timezone'])) : strval(doubleval($config['board_timezone']));
4537
4538
        // Send a proper content-language to the output
4539
        $user_lang = $user->lang['USER_LANG'];
4540
        if (strpos($user_lang, '-x-') !== false)
4541
        {
4542
                $user_lang = substr($user_lang, 0, strpos($user_lang, '-x-'));
4543
        }
4544
4545
        $s_search_hidden_fields = array();
4546
        if ($_SID)
4547
        {
4548
                $s_search_hidden_fields['sid'] = $_SID;
4549
        }
4550
4551
        if (!empty($_EXTRA_URL))
4552
        {
4553
                foreach ($_EXTRA_URL as $url_param)
4554
                {
4555
                        $url_param = explode('=', $url_param, 2);
4556
                        $s_search_hidden_fields[$url_param[0]] = $url_param[1];
4557
                }
4558
        }
4559
4560
        // The following assigns all _common_ variables that may be used at any point in a template.
4561
        $template->assign_vars(array(
4562
                'SITENAME'                                                => $config['sitename'],
4563
                'SITE_DESCRIPTION'                                => $config['site_desc'],
4564
                'PAGE_TITLE'                                        => $page_title,
4565
                'SCRIPT_NAME'                                        => str_replace('.' . $phpEx, '', $user->page['page_name']),
4566
                'LAST_VISIT_DATE'                                => sprintf($user->lang['YOU_LAST_VISIT'], $s_last_visit),
4567
                'LAST_VISIT_YOU'                                => $s_last_visit,
4568
                'CURRENT_TIME'                                        => sprintf($user->lang['CURRENT_TIME'], $user->format_date(time(), false, true)),
4569
                'TOTAL_USERS_ONLINE'                        => $l_online_users,
4570
                'LOGGED_IN_USER_LIST'                        => $online_userlist,
4571
                'RECORD_USERS'                                        => $l_online_record,
4572
                'PRIVATE_MESSAGE_INFO'                        => $l_privmsgs_text,
4573
                'PRIVATE_MESSAGE_INFO_UNREAD'        => $l_privmsgs_text_unread,
4574
4575
                'S_USER_NEW_PRIVMSG'                        => $user->data['user_new_privmsg'],
4576
                'S_USER_UNREAD_PRIVMSG'                        => $user->data['user_unread_privmsg'],
4577
                'S_USER_NEW'                                        => $user->data['user_new'],
4578
4579
                'SID'                                => $SID,
4580
                '_SID'                                => $_SID,
4581
                'SESSION_ID'                => $user->session_id,
4582
                'ROOT_PATH'                        => $phpbb_root_path,
4583
                'BOARD_URL'                        => $board_url,
4584
4585
                'L_LOGIN_LOGOUT'        => $l_login_logout,
4586
                'L_INDEX'                        => $user->lang['FORUM_INDEX'],
4587
                'L_ONLINE_EXPLAIN'        => $l_online_time,
4588
4589
                'U_PRIVATEMSGS'                        => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
4590
                'U_RETURN_INBOX'                => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
4591
                'U_POPUP_PM'                        => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;mode=popup'),
4592
                'UA_POPUP_PM'                        => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;mode=popup')),
4593
                'U_MEMBERLIST'                        => append_sid("{$phpbb_root_path}memberlist.$phpEx"),
4594
                'U_VIEWONLINE'                        => ($auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) ? append_sid("{$phpbb_root_path}viewonline.$phpEx") : '',
4595
                'U_LOGIN_LOGOUT'                => $u_login_logout,
4596
                'U_INDEX'                                => append_sid("{$phpbb_root_path}index.$phpEx"),
4597
                'U_SEARCH'                                => append_sid("{$phpbb_root_path}search.$phpEx"),
4598
                'U_REGISTER'                        => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'),
4599
                'U_PROFILE'                                => append_sid("{$phpbb_root_path}ucp.$phpEx"),
4600
                'U_MODCP'                                => append_sid("{$phpbb_root_path}mcp.$phpEx", false, true, $user->session_id),
4601
                'U_FAQ'                                        => append_sid("{$phpbb_root_path}faq.$phpEx"),
4602
                'U_SEARCH_SELF'                        => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=egosearch'),
4603
                'U_SEARCH_NEW'                        => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=newposts'),
4604
                'U_SEARCH_UNANSWERED'        => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unanswered'),
4605
                'U_SEARCH_UNREAD'                => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unreadposts'),
4606
                'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'),
4607
                'U_DELETE_COOKIES'                => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=delete_cookies'),
4608
                'U_TEAM'                                => ($user->data['user_id'] != ANONYMOUS && !$auth->acl_get('u_viewprofile')) ? '' : append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=leaders'),
4609
                'U_TERMS_USE'                        => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
4610
                'U_PRIVACY'                                => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
4611
                'U_RESTORE_PERMISSIONS'        => ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm') : '',
4612
                'U_FEED'                                => generate_board_url() . "/feed.$phpEx",
4613
4614
                'S_USER_LOGGED_IN'                => ($user->data['user_id'] != ANONYMOUS) ? true : false,
4615
                'S_AUTOLOGIN_ENABLED'        => ($config['allow_autologin']) ? true : false,
4616
                'S_BOARD_DISABLED'                => ($config['board_disable']) ? true : false,
4617
                'S_REGISTERED_USER'                => (!empty($user->data['is_registered'])) ? true : false,
4618
                'S_IS_BOT'                                => (!empty($user->data['is_bot'])) ? true : false,
4619
                'S_USER_PM_POPUP'                => $user->optionget('popuppm'),
4620
                'S_USER_LANG'                        => $user_lang,
4621
                'S_USER_BROWSER'                => (isset($user->data['session_browser'])) ? $user->data['session_browser'] : $user->lang['UNKNOWN_BROWSER'],
4622
                'S_USERNAME'                        => $user->data['username'],
4623
                'S_CONTENT_DIRECTION'        => $user->lang['DIRECTION'],
4624
                'S_CONTENT_FLOW_BEGIN'        => ($user->lang['DIRECTION'] == 'ltr') ? 'left' : 'right',
4625
                'S_CONTENT_FLOW_END'        => ($user->lang['DIRECTION'] == 'ltr') ? 'right' : 'left',
4626
                'S_CONTENT_ENCODING'        => 'UTF-8',
4627
                'S_TIMEZONE'                        => ($user->data['user_dst'] || ($user->data['user_id'] == ANONYMOUS && $config['board_dst'])) ? sprintf($user->lang['ALL_TIMES'], $user->lang['tz'][$tz], $user->lang['tz']['dst']) : sprintf($user->lang['ALL_TIMES'], $user->lang['tz'][$tz], ''),
4628
                'S_DISPLAY_ONLINE_LIST'        => ($l_online_time) ? 1 : 0,
4629
                'S_DISPLAY_SEARCH'                => (!$config['load_search']) ? 0 : (isset($auth) ? ($auth->acl_get('u_search') && $auth->acl_getf_global('f_search')) : 1),
4630
                'S_DISPLAY_PM'                        => ($config['allow_privmsg'] && !empty($user->data['is_registered']) && ($auth->acl_get('u_readpm') || $auth->acl_get('u_sendpm'))) ? true : false,
4631
                'S_DISPLAY_MEMBERLIST'        => (isset($auth)) ? $auth->acl_get('u_viewprofile') : 0,
4632
                'S_NEW_PM'                                => ($s_privmsg_new) ? 1 : 0,
4633
                'S_REGISTER_ENABLED'        => ($config['require_activation'] != USER_ACTIVATION_DISABLE) ? true : false,
4634
                'S_FORUM_ID'                        => $forum_id,
4635
                'S_TOPIC_ID'                        => $topic_id,
4636
4637
                'S_LOGIN_ACTION'                => ((!defined('ADMIN_START')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login') : append_sid("index.$phpEx", false, true, $user->session_id)),
4638
                'S_LOGIN_REDIRECT'                => build_hidden_fields(array('redirect' => build_url())),
4639
4640
                'S_ENABLE_FEEDS'                        => ($config['feed_enable']) ? true : false,
4641
                'S_ENABLE_FEEDS_OVERALL'        => ($config['feed_overall']) ? true : false,
4642
                'S_ENABLE_FEEDS_FORUMS'                => ($config['feed_overall_forums']) ? true : false,
4643
                'S_ENABLE_FEEDS_TOPICS'                => ($config['feed_topics_new']) ? true : false,
4644
                'S_ENABLE_FEEDS_TOPICS_ACTIVE'        => ($config['feed_topics_active']) ? true : false,
4645
                'S_ENABLE_FEEDS_NEWS'                => ($s_feed_news) ? true : false,
4646
4647
                'S_LOAD_UNREADS'                        => ($config['load_unreads_search'] && ($config['load_anon_lastread'] || $user->data['is_registered'])) ? true : false,
4648
4649
                'S_SEARCH_HIDDEN_FIELDS'        => build_hidden_fields($s_search_hidden_fields),
4650
4651
                'T_THEME_PATH'                        => "{$web_path}styles/" . rawurlencode($user->theme['theme_path']) . '/theme',
4652
                'T_TEMPLATE_PATH'                => "{$web_path}styles/" . rawurlencode($user->theme['template_path']) . '/template',
4653
                'T_SUPER_TEMPLATE_PATH'        => (isset($user->theme['template_inherit_path']) && $user->theme['template_inherit_path']) ? "{$web_path}styles/" . rawurlencode($user->theme['template_inherit_path']) . '/template' : "{$web_path}styles/" . rawurlencode($user->theme['template_path']) . '/template',
4654
                'T_IMAGESET_PATH'                => "{$web_path}styles/" . rawurlencode($user->theme['imageset_path']) . '/imageset',
4655
                'T_IMAGESET_LANG_PATH'        => "{$web_path}styles/" . rawurlencode($user->theme['imageset_path']) . '/imageset/' . $user->lang_name,
4656
                'T_IMAGES_PATH'                        => "{$web_path}images/",
4657
                'T_SMILIES_PATH'                => "{$web_path}{$config['smilies_path']}/",
4658
                'T_AVATAR_PATH'                        => "{$web_path}{$config['avatar_path']}/",
4659
                'T_AVATAR_GALLERY_PATH'        => "{$web_path}{$config['avatar_gallery_path']}/",
4660
                'T_ICONS_PATH'                        => "{$web_path}{$config['icons_path']}/",
4661
                'T_RANKS_PATH'                        => "{$web_path}{$config['ranks_path']}/",
4662
                'T_UPLOAD_PATH'                        => "{$web_path}{$config['upload_path']}/",
4663
                'T_STYLESHEET_LINK'                => (!$user->theme['theme_storedb']) ? "{$web_path}styles/" . rawurlencode($user->theme['theme_path']) . '/theme/stylesheet.css' : append_sid("{$phpbb_root_path}style.$phpEx", 'id=' . $user->theme['style_id'] . '&amp;lang=' . $user->lang_name),
4664
                'T_STYLESHEET_NAME'                => $user->theme['theme_name'],
4665
4666
                'T_THEME_NAME'                        => rawurlencode($user->theme['theme_path']),
4667
                'T_TEMPLATE_NAME'                => rawurlencode($user->theme['template_path']),
4668
                'T_SUPER_TEMPLATE_NAME'        => rawurlencode((isset($user->theme['template_inherit_path']) && $user->theme['template_inherit_path']) ? $user->theme['template_inherit_path'] : $user->theme['template_path']),
4669
                'T_IMAGESET_NAME'                => rawurlencode($user->theme['imageset_path']),
4670
                'T_IMAGESET_LANG_NAME'        => $user->data['user_lang'],
4671
                'T_IMAGES'                                => 'images',
4672
                'T_SMILIES'                                => $config['smilies_path'],
4673
                'T_AVATAR'                                => $config['avatar_path'],
4674
                'T_AVATAR_GALLERY'                => $config['avatar_gallery_path'],
4675
                'T_ICONS'                                => $config['icons_path'],
4676
                'T_RANKS'                                => $config['ranks_path'],
4677
                'T_UPLOAD'                                => $config['upload_path'],
4678
4679
                'SITE_LOGO_IMG'                        => $user->img('site_logo'),
4680
4681
                'A_COOKIE_SETTINGS'                => addslashes('; path=' . $config['cookie_path'] . ((!$config['cookie_domain'] || $config['cookie_domain'] == 'localhost' || $config['cookie_domain'] == '127.0.0.1') ? '' : '; domain=' . $config['cookie_domain']) . ((!$config['cookie_secure']) ? '' : '; secure')),
4682
        ));
4683
4684
        // application/xhtml+xml not used because of IE
4685
        header('Content-type: text/html; charset=UTF-8');
4686
4687
        header('Cache-Control: private, no-cache="set-cookie"');
4688
        header('Expires: 0');
4689
        header('Pragma: no-cache');
4690
4691
        if (!empty($user->data['is_bot']))
4692
        {
4693
                // Let reverse proxies know we detected a bot.
4694
                header('X-PHPBB-IS-BOT: yes');
4695
        }
4696
4697
        return;
4698
}
4699
4700
/**
4701
* Generate page footer
4702
*/
4703
function page_footer($run_cron = true)
4704
{
4705
        global $db, $config, $template, $user, $auth, $cache, $starttime, $phpbb_root_path, $phpEx;
4706
4707
        // Output page creation time
4708
        if (defined('DEBUG'))
4709
        {
4710
                $mtime = explode(' ', microtime());
4711
                $totaltime = $mtime[0] + $mtime[1] - $starttime;
4712
4713
                if (!empty($_REQUEST['explain']) && $auth->acl_get('a_') && defined('DEBUG_EXTRA') && method_exists($db, 'sql_report'))
4714
                {
4715
                        $db->sql_report('display');
4716
                }
4717
4718
                $debug_output = sprintf('Time : %.3fs | ' . $db->sql_num_queries() . ' Queries | GZIP : ' . (($config['gzip_compress'] && @extension_loaded('zlib')) ? 'On' : 'Off') . (($user->load) ? ' | Load : ' . $user->load : ''), $totaltime);
4719
4720
                if ($auth->acl_get('a_') && defined('DEBUG_EXTRA'))
4721
                {
4722
                        if (function_exists('memory_get_usage'))
4723
                        {
4724
                                if ($memory_usage = memory_get_usage())
4725
                                {
4726
                                        global $base_memory_usage;
4727
                                        $memory_usage -= $base_memory_usage;
4728
                                        $memory_usage = get_formatted_filesize($memory_usage);
4729
4730
                                        $debug_output .= ' | Memory Usage: ' . $memory_usage;
4731
                                }
4732
                        }
4733
4734
                        $debug_output .= ' | <a href="' . build_url() . '&amp;explain=1">Explain</a>';
4735
                }
4736
        }
4737
4738
        $template->assign_vars(array(
4739
                'DEBUG_OUTPUT'                        => (defined('DEBUG')) ? $debug_output : '',
4740
                'TRANSLATION_INFO'                => (!empty($user->lang['TRANSLATION_INFO'])) ? $user->lang['TRANSLATION_INFO'] : '',
4741
4742
                'U_ACP' => ($auth->acl_get('a_') && !empty($user->data['is_registered'])) ? append_sid("{$phpbb_root_path}adm/index.$phpEx", false, true, $user->session_id) : '')
4743
        );
4744
4745
        // Call cron-type script
4746
        $call_cron = false;
4747
        if (!defined('IN_CRON') && $run_cron && !$config['board_disable'] && !$user->data['is_bot'])
4748
        {
4749
                $call_cron = true;
4750
                $time_now = (!empty($user->time_now) && is_int($user->time_now)) ? $user->time_now : time();
4751
4752
                // Any old lock present?
4753
                if (!empty($config['cron_lock']))
4754
                {
4755
                        $cron_time = explode(' ', $config['cron_lock']);
4756
4757
                        // If 1 hour lock is present we do not call cron.php
4758
                        if ($cron_time[0] + 3600 >= $time_now)
4759
                        {
4760
                                $call_cron = false;
4761
                        }
4762
                }
4763
        }
4764
4765
        // Call cron job?
4766
        if ($call_cron)
4767
        {
4768
                $cron_type = '';
4769
4770
                if ($time_now - $config['queue_interval'] > $config['last_queue_run'] && !defined('IN_ADMIN') && file_exists($phpbb_root_path . 'cache/queue.' . $phpEx))
4771
                {
4772
                        // Process email queue
4773
                        $cron_type = 'queue';
4774
                }
4775
                else if (method_exists($cache, 'tidy') && $time_now - $config['cache_gc'] > $config['cache_last_gc'])
4776
                {
4777
                        // Tidy the cache
4778
                        $cron_type = 'tidy_cache';
4779
                }
4780
                else if ($config['warnings_expire_days'] && ($time_now - $config['warnings_gc'] > $config['warnings_last_gc']))
4781
                {
4782
                        $cron_type = 'tidy_warnings';
4783
                }
4784
                else if ($time_now - $config['database_gc'] > $config['database_last_gc'])
4785
                {
4786
                        // Tidy the database
4787
                        $cron_type = 'tidy_database';
4788
                }
4789
                else if ($time_now - $config['search_gc'] > $config['search_last_gc'])
4790
                {
4791
                        // Tidy the search
4792
                        $cron_type = 'tidy_search';
4793
                }
4794
                else if ($time_now - $config['session_gc'] > $config['session_last_gc'])
4795
                {
4796
                        $cron_type = 'tidy_sessions';
4797
                }
4798
4799
                if ($cron_type)
4800
                {
4801
                        $template->assign_var('RUN_CRON_TASK', '<img src="' . append_sid($phpbb_root_path . 'cron.' . $phpEx, 'cron_type=' . $cron_type) . '" width="1" height="1" alt="cron" />');
4802
                }
4803
        }
4804
4805
        $template->display('body');
4806
4807
        garbage_collection();
4808
        exit_handler();
4809
}
4810
4811
/**
4812
* Closing the cache object and the database
4813
* Cool function name, eh? We might want to add operations to it later
4814
*/
4815
function garbage_collection()
4816
{
4817
        global $cache, $db;
4818
4819
        // Unload cache, must be done before the DB connection if closed
4820
        if (!empty($cache))
4821
        {
4822
                $cache->unload();
4823
        }
4824
4825
        // Close our DB connection.
4826
        if (!empty($db))
4827
        {
4828
                $db->sql_close();
4829
        }
4830
}
4831
4832
/**
4833
* Handler for exit calls in phpBB.
4834
* This function supports hooks.
4835
*
4836
* Note: This function is called after the template has been outputted.
4837
*/
4838
function exit_handler()
4839
{
4840
        global $phpbb_hook, $config;
4841
4842
        if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
4843
        {
4844
                if ($phpbb_hook->hook_return(__FUNCTION__))
4845
                {
4846
                        return $phpbb_hook->hook_return_result(__FUNCTION__);
4847
                }
4848
        }
4849
4850
        // As a pre-caution... some setups display a blank page if the flush() is not there.
4851
        (ob_get_level() > 0) ? @ob_flush() : @flush();
4852
4853
        exit;
4854
}
4855
4856
/**
4857
* Handler for init calls in phpBB. This function is called in user::setup();
4858
* This function supports hooks.
4859
*/
4860
function phpbb_user_session_handler()
4861
{
4862
        global $phpbb_hook;
4863
4864
        if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
4865
        {
4866
                if ($phpbb_hook->hook_return(__FUNCTION__))
4867
                {
4868
                        return $phpbb_hook->hook_return_result(__FUNCTION__);
4869
                }
4870
        }
4871
4872
        return;
4873
}
4874
4875
?>