phpBB
Statistics
| Revision:

root / trunk / phpBB / includes / functions.php

History | View | Annotate | Download (140.3 kB)

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