let's see if i can break something. :o
[phpbb.git] / phpBB / includes / functions.php
blobcaf26bf37969797f2b3bd895fcfd34d10056f6d9
1 <?php
2 /**
4 * @package phpBB3
5 * @version $Id$
6 * @copyright (c) 2005 phpBB Group
7 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
9 */
11 // Common global functions
13 /**
14 * set_var
16 * Set variable, used by {@link request_var the request_var function}
18 * @access private
20 function set_var(&$result, $var, $type, $multibyte = false)
22 settype($var, $type);
23 $result = $var;
25 if ($type == 'string')
27 $result = trim(htmlspecialchars(str_replace(array("\r\n", "\r"), array("\n", "\n"), $result), ENT_COMPAT, 'UTF-8'));
29 if (!empty($result))
31 // Make sure multibyte characters are wellformed
32 if ($multibyte)
34 if (!preg_match('/^./u', $result))
36 $result = '';
39 else
41 // no multibyte, allow only ASCII (0-127)
42 $result = preg_replace('/[\x80-\xFF]/', '?', $result);
46 $result = (STRIP) ? stripslashes($result) : $result;
50 /**
51 * request_var
53 * Used to get passed variable
55 function request_var($var_name, $default, $multibyte = false, $cookie = false)
57 if (!$cookie && isset($_COOKIE[$var_name]))
59 if (!isset($_GET[$var_name]) && !isset($_POST[$var_name]))
61 return (is_array($default)) ? array() : $default;
63 $_REQUEST[$var_name] = isset($_POST[$var_name]) ? $_POST[$var_name] : $_GET[$var_name];
66 if (!isset($_REQUEST[$var_name]) || (is_array($_REQUEST[$var_name]) && !is_array($default)) || (is_array($default) && !is_array($_REQUEST[$var_name])))
68 return (is_array($default)) ? array() : $default;
71 $var = $_REQUEST[$var_name];
72 if (!is_array($default))
74 $type = gettype($default);
76 else
78 list($key_type, $type) = each($default);
79 $type = gettype($type);
80 $key_type = gettype($key_type);
81 if ($type == 'array')
83 reset($default);
84 list($sub_key_type, $sub_type) = each(current($default));
85 $sub_type = gettype($sub_type);
86 $sub_type = ($sub_type == 'array') ? 'NULL' : $sub_type;
87 $sub_key_type = gettype($sub_key_type);
91 if (is_array($var))
93 $_var = $var;
94 $var = array();
96 foreach ($_var as $k => $v)
98 set_var($k, $k, $key_type);
99 if ($type == 'array' && is_array($v))
101 foreach ($v as $_k => $_v)
103 if (is_array($_v))
105 $_v = null;
107 set_var($_k, $_k, $sub_key_type);
108 set_var($var[$k][$_k], $_v, $sub_type, $multibyte);
111 else
113 if ($type == 'array' || is_array($v))
115 $v = null;
117 set_var($var[$k], $v, $type, $multibyte);
121 else
123 set_var($var, $var, $type, $multibyte);
126 return $var;
130 * Set config value. Creates missing config entry.
132 function set_config($config_name, $config_value, $is_dynamic = false)
134 global $db, $cache, $config;
136 $sql = 'UPDATE ' . CONFIG_TABLE . "
137 SET config_value = '" . $db->sql_escape($config_value) . "'
138 WHERE config_name = '" . $db->sql_escape($config_name) . "'";
139 $db->sql_query($sql);
141 if (!$db->sql_affectedrows() && !isset($config[$config_name]))
143 $sql = 'INSERT INTO ' . CONFIG_TABLE . ' ' . $db->sql_build_array('INSERT', array(
144 'config_name' => $config_name,
145 'config_value' => $config_value,
146 'is_dynamic' => ($is_dynamic) ? 1 : 0));
147 $db->sql_query($sql);
150 $config[$config_name] = $config_value;
152 if (!$is_dynamic)
154 $cache->destroy('config');
159 * Generates an alphanumeric random string of given length
161 function gen_rand_string($num_chars = 8)
163 $rand_str = unique_id();
164 $rand_str = str_replace('0', 'Z', strtoupper(base_convert($rand_str, 16, 35)));
166 return substr($rand_str, 0, $num_chars);
170 * Return unique id
171 * @param string $extra additional entropy
173 function unique_id($extra = 'c')
175 static $dss_seeded = false;
176 global $config;
178 $val = $config['rand_seed'] . microtime();
179 $val = md5($val);
180 $config['rand_seed'] = md5($config['rand_seed'] . $val . $extra);
182 if ($dss_seeded !== true && ($config['rand_seed_last_update'] < time() - rand(1,10)))
184 set_config('rand_seed', $config['rand_seed'], true);
185 set_config('rand_seed_last_update', time(), true);
186 $dss_seeded = true;
189 return substr($val, 4, 16);
193 * Determine whether we are approaching the maximum execution time. Should be called once
194 * at the beginning of the script in which it's used.
195 * @return bool Either true if the maximum execution time is nearly reached, or false
196 * if some time is still left.
198 function still_on_time($extra_time = 15)
200 static $max_execution_time, $start_time;
202 $time = explode(' ', microtime());
203 $current_time = $time[0] + $time[1];
205 if (empty($max_execution_time))
207 $max_execution_time = (function_exists('ini_get')) ? (int) ini_get('max_execution_time') : (int) get_cfg_var('max_execution_time');
209 // If zero, then set to something higher to not let the user catch the ten seconds barrier.
210 if ($max_execution_time === 0)
212 $max_execution_time = 50 + $extra_time;
215 $max_execution_time = min(max(10, ($max_execution_time - $extra_time)), 50);
217 // For debugging purposes
218 // $max_execution_time = 10;
220 global $starttime;
221 $start_time = (empty($starttime)) ? $current_time : $starttime;
224 return (ceil($current_time - $start_time) < $max_execution_time) ? true : false;
228 * Generate sort selection fields
230 function gen_sort_selects(&$limit_days, &$sort_by_text, &$sort_days, &$sort_key, &$sort_dir, &$s_limit_days, &$s_sort_key, &$s_sort_dir, &$u_sort_param)
232 global $user;
234 $sort_dir_text = array('a' => $user->lang['ASCENDING'], 'd' => $user->lang['DESCENDING']);
236 // Check if the key is selectable. If not, we reset to the first key found.
237 // This ensures the values are always valid.
238 if (!isset($limit_days[$sort_days]))
240 @reset($limit_days);
241 $sort_days = key($limit_days);
244 if (!isset($sort_by_text[$sort_key]))
246 @reset($sort_by_text);
247 $sort_key = key($sort_by_text);
250 if (!isset($sort_dir_text[$sort_dir]))
252 @reset($sort_dir_text);
253 $sort_dir = key($sort_dir_text);
256 $s_limit_days = '<select name="st">';
257 foreach ($limit_days as $day => $text)
259 $selected = ($sort_days == $day) ? ' selected="selected"' : '';
260 $s_limit_days .= '<option value="' . $day . '"' . $selected . '>' . $text . '</option>';
262 $s_limit_days .= '</select>';
264 $s_sort_key = '<select name="sk">';
265 foreach ($sort_by_text as $key => $text)
267 $selected = ($sort_key == $key) ? ' selected="selected"' : '';
268 $s_sort_key .= '<option value="' . $key . '"' . $selected . '>' . $text . '</option>';
270 $s_sort_key .= '</select>';
272 $s_sort_dir = '<select name="sd">';
273 foreach ($sort_dir_text as $key => $value)
275 $selected = ($sort_dir == $key) ? ' selected="selected"' : '';
276 $s_sort_dir .= '<option value="' . $key . '"' . $selected . '>' . $value . '</option>';
278 $s_sort_dir .= '</select>';
280 $u_sort_param = "st=$sort_days&amp;sk=$sort_key&amp;sd=$sort_dir";
282 return;
286 * Generate Jumpbox
288 function make_jumpbox($action, $forum_id = false, $select_all = false, $acl_list = false)
290 global $config, $auth, $template, $user, $db;
292 if (!$config['load_jumpbox'])
294 return;
297 $sql = 'SELECT forum_id, forum_name, parent_id, forum_type, left_id, right_id
298 FROM ' . FORUMS_TABLE . '
299 ORDER BY left_id ASC';
300 $result = $db->sql_query($sql, 600);
302 $right = $padding = 0;
303 $padding_store = array('0' => 0);
304 $display_jumpbox = false;
305 $iteration = 0;
307 // Sometimes it could happen that forums will be displayed here not be displayed within the index page
308 // This is the result of forums not displayed at index, having list permissions and a parent of a forum with no permissions.
309 // If this happens, the padding could be "broken"
311 while ($row = $db->sql_fetchrow($result))
313 if ($row['left_id'] < $right)
315 $padding++;
316 $padding_store[$row['parent_id']] = $padding;
318 else if ($row['left_id'] > $right + 1)
320 // Ok, if the $padding_store for this parent is empty there is something wrong. For now we will skip over it.
321 // @todo digging deep to find out "how" this can happen.
322 $padding = (isset($padding_store[$row['parent_id']])) ? $padding_store[$row['parent_id']] : $padding;
325 $right = $row['right_id'];
327 if ($row['forum_type'] == FORUM_CAT && ($row['left_id'] + 1 == $row['right_id']))
329 // Non-postable forum with no subforums, don't display
330 continue;
333 if (!$auth->acl_get('f_list', $row['forum_id']))
335 // if the user does not have permissions to list this forum skip
336 continue;
339 if ($acl_list && !$auth->acl_gets($acl_list, $row['forum_id']))
341 continue;
344 if (!$display_jumpbox)
346 $template->assign_block_vars('jumpbox_forums', array(
347 'FORUM_ID' => ($select_all) ? 0 : -1,
348 'FORUM_NAME' => ($select_all) ? $user->lang['ALL_FORUMS'] : $user->lang['SELECT_FORUM'],
349 'S_FORUM_COUNT' => $iteration)
352 $iteration++;
353 $display_jumpbox = true;
356 $template->assign_block_vars('jumpbox_forums', array(
357 'FORUM_ID' => $row['forum_id'],
358 'FORUM_NAME' => $row['forum_name'],
359 'SELECTED' => ($row['forum_id'] == $forum_id) ? ' selected="selected"' : '',
360 'S_FORUM_COUNT' => $iteration,
361 'S_IS_CAT' => ($row['forum_type'] == FORUM_CAT) ? true : false,
362 'S_IS_LINK' => ($row['forum_type'] == FORUM_LINK) ? true : false,
363 'S_IS_POST' => ($row['forum_type'] == FORUM_POST) ? true : false)
366 for ($i = 0; $i < $padding; $i++)
368 $template->assign_block_vars('jumpbox_forums.level', array());
370 $iteration++;
372 $db->sql_freeresult($result);
373 unset($padding_store);
375 $template->assign_vars(array(
376 'S_DISPLAY_JUMPBOX' => $display_jumpbox,
377 'S_JUMPBOX_ACTION' => $action)
380 return;
384 // Compatibility functions
386 if (!function_exists('array_combine'))
389 * A wrapper for the PHP5 function array_combine()
390 * @param array $keys contains keys for the resulting array
391 * @param array $values contains values for the resulting array
393 * @return Returns an array by using the values from the keys array as keys and the
394 * values from the values array as the corresponding values. Returns false if the
395 * number of elements for each array isn't equal or if the arrays are empty.
397 function array_combine($keys, $values)
399 $keys = array_values($keys);
400 $values = array_values($values);
402 $n = sizeof($keys);
403 $m = sizeof($values);
404 if (!$n || !$m || ($n != $m))
406 return false;
409 $combined = array();
410 for ($i = 0; $i < $n; $i++)
412 $combined[$keys[$i]] = $values[$i];
414 return $combined;
418 if (!function_exists('str_split'))
421 * A wrapper for the PHP5 function str_split()
422 * @param array $string contains the string to be converted
423 * @param array $split_length contains the length of each chunk
425 * @return Converts a string to an array. If the optional split_length parameter is specified,
426 * the returned array will be broken down into chunks with each being split_length in length,
427 * otherwise each chunk will be one character in length. FALSE is returned if split_length is
428 * less than 1. If the split_length length exceeds the length of string, the entire string is
429 * returned as the first (and only) array element.
431 function str_split($string, $split_length = 1)
433 if ($split_length < 1)
435 return false;
437 else if ($split_length >= strlen($string))
439 return array($string);
441 else
443 preg_match_all('#.{1,' . $split_length . '}#s', $string, $matches);
444 return $matches[0];
449 if (!function_exists('stripos'))
452 * A wrapper for the PHP5 function stripos
453 * Find position of first occurrence of a case-insensitive string
455 * @param string $haystack is the string to search in
456 * @param string $needle is the string to search for
458 * @return mixed Returns the numeric position of the first occurrence of needle in the haystack string. Unlike strpos(), stripos() is case-insensitive.
459 * Note that the needle may be a string of one or more characters.
460 * If needle is not found, stripos() will return boolean FALSE.
462 function stripos($haystack, $needle)
464 if (preg_match('#' . preg_quote($needle, '#') . '#i', $haystack, $m))
466 return strpos($haystack, $m[0]);
469 return false;
473 if (!function_exists('realpath'))
475 if (DIRECTORY_SEPARATOR != '\\' && !(bool) ini_get('safe_mode') && function_exists('shell_exec') && trim(`realpath .`))
478 * @author Chris Smith <chris@project-minerva.org>
479 * @copyright 2006 Project Minerva Team
480 * @param string $path The path which we should attempt to resolve.
481 * @return mixed
482 * @ignore
484 function phpbb_realpath($path)
486 $arg = escapeshellarg($path);
487 return trim(`realpath '$arg'`);
490 else
493 * Checks if a path ($path) is absolute or relative
495 * @param string $path Path to check absoluteness of
496 * @return boolean
498 function is_absolute($path)
500 return ($path[0] == '/' || (DIRECTORY_SEPARATOR == '\\' && preg_match('#^[a-z]:/#i', $path))) ? true : false;
504 * @author Chris Smith <chris@project-minerva.org>
505 * @copyright 2006 Project Minerva Team
506 * @param string $path The path which we should attempt to resolve.
507 * @return mixed
509 function phpbb_realpath($path)
511 // Now to perform funky shizzle
513 // Switch to use UNIX slashes
514 $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
515 $path_prefix = '';
517 // Determine what sort of path we have
518 if (is_absolute($path))
520 $absolute = true;
522 if ($path[0] == '/')
524 // Absolute path, *NIX style
525 $path_prefix = '';
527 else
529 // Absolute path, Windows style
530 // Remove the drive letter and colon
531 $path_prefix = $path[0] . ':';
532 $path = substr($path, 2);
535 else
537 // Relative Path
538 // Prepend the current working directory
539 if (function_exists('getcwd'))
541 // This is the best method, hopefully it is enabled!
542 $path = str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . '/' . $path;
543 $absolute = true;
544 if (preg_match('#^[a-z]:#i', $path))
546 $path_prefix = $path[0] . ':';
547 $path = substr($path, 2);
549 else
551 $path_prefix = '';
554 else if (isset($_SERVER['SCRIPT_FILENAME']) && !empty($_SERVER['SCRIPT_FILENAME']))
556 // Warning: If chdir() has been used this will lie!
557 // Warning: This has some problems sometime (CLI can create them easily)
558 $path = str_replace(DIRECTORY_SEPARATOR, '/', dirname($_SERVER['SCRIPT_FILENAME'])) . '/' . $path;
559 $absolute = true;
560 $path_prefix = '';
562 else
564 // We have no way of getting the absolute path, just run on using relative ones.
565 $absolute = false;
566 $path_prefix = '.';
570 // Remove any repeated slashes
571 $path = preg_replace('#/{2,}#', '/', $path);
573 // Remove the slashes from the start and end of the path
574 $path = trim($path, '/');
576 // Break the string into little bits for us to nibble on
577 $bits = explode('/', $path);
579 // Remove any . in the path, renumber array for the loop below
580 $bits = array_keys(array_diff($bits, array('.')));
582 // Lets get looping, run over and resolve any .. (up directory)
583 for ($i = 0, $max = sizeof($bits); $i < $max; $i++)
585 // @todo Optimise
586 if ($bits[$i] == '..' )
588 if (isset($bits[$i - 1]))
590 if ($bits[$i - 1] != '..')
592 // We found a .. and we are able to traverse upwards, lets do it!
593 unset($bits[$i]);
594 unset($bits[$i - 1]);
595 $i -= 2;
596 $max -= 2;
597 $bits = array_values($bits);
600 else if ($absolute) // ie. !isset($bits[$i - 1]) && $absolute
602 // We have an absolute path trying to descend above the root of the filesystem
603 // ... Error!
604 return false;
609 // Prepend the path prefix
610 array_unshift($bits, $path_prefix);
612 $resolved = '';
614 $max = sizeof($bits) - 1;
616 // Check if we are able to resolve symlinks, Windows cannot.
617 $symlink_resolve = (function_exists('readlink')) ? true : false;
619 foreach ($bits as $i => $bit)
621 if (@is_dir("$resolved/$bit") || ($i == $max && @is_file("$resolved/$bit")))
623 // Path Exists
624 if ($symlink_resolve && is_link("$resolved/$bit") && ($link = readlink("$resolved/$bit")))
626 // Resolved a symlink.
627 $resolved = $link . (($i == $max) ? '' : '/');
628 continue;
631 else
633 // Something doesn't exist here!
634 // This is correct realpath() behaviour but sadly open_basedir and safe_mode make this problematic
635 // return false;
637 $resolved .= $bit . (($i == $max) ? '' : '/');
640 // @todo If the file exists fine and open_basedir only has one path we should be able to prepend it
641 // because we must be inside that basedir, the question is where...
642 // @internal The slash in is_dir() gets around an open_basedir restriction
643 if (!@file_exists($resolved) || (!is_dir($resolved . '/') && !is_file($resolved)))
645 return false;
648 // Put the slashes back to the native operating systems slashes
649 $resolved = str_replace('/', DIRECTORY_SEPARATOR, $resolved);
651 return $resolved; // We got here, in the end!
655 else
658 * A wrapper for realpath
659 * @ignore
661 function phpbb_realpath($path)
663 return realpath($path);
667 if (!function_exists('htmlspecialchars_decode'))
670 * A wrapper for htmlspecialchars_decode
671 * @ignore
673 function htmlspecialchars_decode($string, $quote_style = ENT_COMPAT)
675 return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style)));
679 // functions used for building option fields
682 * Pick a language, any language ...
684 function language_select($default = '')
686 global $db;
688 $sql = 'SELECT lang_iso, lang_local_name
689 FROM ' . LANG_TABLE . '
690 ORDER BY lang_english_name';
691 $result = $db->sql_query($sql);
693 $lang_options = '';
694 while ($row = $db->sql_fetchrow($result))
696 $selected = ($row['lang_iso'] == $default) ? ' selected="selected"' : '';
697 $lang_options .= '<option value="' . $row['lang_iso'] . '"' . $selected . '>' . $row['lang_local_name'] . '</option>';
699 $db->sql_freeresult($result);
701 return $lang_options;
704 /**
705 * Pick a template/theme combo,
707 function style_select($default = '', $all = false)
709 global $db;
711 $sql_where = (!$all) ? 'WHERE style_active = 1 ' : '';
712 $sql = 'SELECT style_id, style_name
713 FROM ' . STYLES_TABLE . "
714 $sql_where
715 ORDER BY style_name";
716 $result = $db->sql_query($sql);
718 $style_options = '';
719 while ($row = $db->sql_fetchrow($result))
721 $selected = ($row['style_id'] == $default) ? ' selected="selected"' : '';
722 $style_options .= '<option value="' . $row['style_id'] . '"' . $selected . '>' . $row['style_name'] . '</option>';
724 $db->sql_freeresult($result);
726 return $style_options;
730 * Pick a timezone
732 function tz_select($default = '', $truncate = false)
734 global $user;
736 $tz_select = '';
737 foreach ($user->lang['tz_zones'] as $offset => $zone)
739 if ($truncate)
741 $zone_trunc = truncate_string($zone, 50, false, '...');
743 else
745 $zone_trunc = $zone;
748 if (is_numeric($offset))
750 $selected = ($offset == $default) ? ' selected="selected"' : '';
751 $tz_select .= '<option title="'.$zone.'" value="' . $offset . '"' . $selected . '>' . $zone_trunc . '</option>';
755 return $tz_select;
758 // Functions handling topic/post tracking/marking
761 * Marks a topic/forum as read
762 * Marks a topic as posted to
764 * @param int $user_id can only be used with $mode == 'post'
766 function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0)
768 global $db, $user, $config;
770 if ($mode == 'all')
772 if ($forum_id === false || !sizeof($forum_id))
774 if ($config['load_db_lastread'] && $user->data['is_registered'])
776 // Mark all forums read (index page)
777 $db->sql_query('DELETE FROM ' . TOPICS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
778 $db->sql_query('DELETE FROM ' . FORUMS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
779 $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
781 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
783 $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
784 $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
786 unset($tracking_topics['tf']);
787 unset($tracking_topics['t']);
788 unset($tracking_topics['f']);
789 $tracking_topics['l'] = base_convert(time() - $config['board_startdate'], 10, 36);
791 $user->set_cookie('track', tracking_serialize($tracking_topics), time() + 31536000);
792 unset($tracking_topics);
794 if ($user->data['is_registered'])
796 $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
801 return;
803 else if ($mode == 'topics')
805 // Mark all topics in forums read
806 if (!is_array($forum_id))
808 $forum_id = array($forum_id);
811 // Add 0 to forums array to mark global announcements correctly
812 $forum_id[] = 0;
814 if ($config['load_db_lastread'] && $user->data['is_registered'])
816 $sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . "
817 WHERE user_id = {$user->data['user_id']}
818 AND " . $db->sql_in_set('forum_id', $forum_id);
819 $db->sql_query($sql);
821 $sql = 'SELECT forum_id
822 FROM ' . FORUMS_TRACK_TABLE . "
823 WHERE user_id = {$user->data['user_id']}
824 AND " . $db->sql_in_set('forum_id', $forum_id);
825 $result = $db->sql_query($sql);
827 $sql_update = array();
828 while ($row = $db->sql_fetchrow($result))
830 $sql_update[] = $row['forum_id'];
832 $db->sql_freeresult($result);
834 if (sizeof($sql_update))
836 $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . '
837 SET mark_time = ' . time() . "
838 WHERE user_id = {$user->data['user_id']}
839 AND " . $db->sql_in_set('forum_id', $sql_update);
840 $db->sql_query($sql);
843 if ($sql_insert = array_diff($forum_id, $sql_update))
845 $sql_ary = array();
846 foreach ($sql_insert as $f_id)
848 $sql_ary[] = array(
849 'user_id' => $user->data['user_id'],
850 'forum_id' => $f_id,
851 'mark_time' => time()
855 $db->sql_multi_insert(FORUMS_TRACK_TABLE, $sql_ary);
858 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
860 $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
861 $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
863 foreach ($forum_id as $f_id)
865 $topic_ids36 = (isset($tracking['tf'][$f_id])) ? $tracking['tf'][$f_id] : array();
867 if (isset($tracking['tf'][$f_id]))
869 unset($tracking['tf'][$f_id]);
872 foreach ($topic_ids36 as $topic_id36)
874 unset($tracking['t'][$topic_id36]);
877 if (isset($tracking['f'][$f_id]))
879 unset($tracking['f'][$f_id]);
882 $tracking['f'][$f_id] = base_convert(time() - $config['board_startdate'], 10, 36);
885 $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
886 unset($tracking);
889 return;
891 else if ($mode == 'topic')
893 if ($topic_id === false || $forum_id === false)
895 return;
898 if ($config['load_db_lastread'] && $user->data['is_registered'])
900 $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . '
901 SET mark_time = ' . (($post_time) ? $post_time : time()) . "
902 WHERE user_id = {$user->data['user_id']}
903 AND topic_id = $topic_id";
904 $db->sql_query($sql);
906 // insert row
907 if (!$db->sql_affectedrows())
909 $db->sql_return_on_error(true);
911 $sql_ary = array(
912 'user_id' => $user->data['user_id'],
913 'topic_id' => $topic_id,
914 'forum_id' => (int) $forum_id,
915 'mark_time' => ($post_time) ? $post_time : time(),
918 $db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
920 $db->sql_return_on_error(false);
923 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
925 $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
926 $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
928 $topic_id36 = base_convert($topic_id, 10, 36);
930 if (!isset($tracking['t'][$topic_id36]))
932 $tracking['tf'][$forum_id][$topic_id36] = true;
935 $post_time = ($post_time) ? $post_time : time();
936 $tracking['t'][$topic_id36] = base_convert($post_time - $config['board_startdate'], 10, 36);
938 // If the cookie grows larger than 10000 characters we will remove the smallest value
939 // This can result in old topics being unread - but most of the time it should be accurate...
940 if (isset($_COOKIE[$config['cookie_name'] . '_track']) && strlen($_COOKIE[$config['cookie_name'] . '_track']) > 10000)
942 //echo 'Cookie grown too large' . print_r($tracking, true);
944 // We get the ten most minimum stored time offsets and its associated topic ids
945 $time_keys = array();
946 for ($i = 0; $i < 10 && sizeof($tracking['t']); $i++)
948 $min_value = min($tracking['t']);
949 $m_tkey = array_search($min_value, $tracking['t']);
950 unset($tracking['t'][$m_tkey]);
952 $time_keys[$m_tkey] = $min_value;
955 // Now remove the topic ids from the array...
956 foreach ($tracking['tf'] as $f_id => $topic_id_ary)
958 foreach ($time_keys as $m_tkey => $min_value)
960 if (isset($topic_id_ary[$m_tkey]))
962 $tracking['f'][$f_id] = $min_value;
963 unset($tracking['tf'][$f_id][$m_tkey]);
968 if ($user->data['is_registered'])
970 $user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10));
971 $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . $user->data['user_lastmark'] . " WHERE user_id = {$user->data['user_id']}");
973 else
975 $tracking['l'] = max($time_keys);
979 $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
982 return;
984 else if ($mode == 'post')
986 if ($topic_id === false)
988 return;
991 $use_user_id = (!$user_id) ? $user->data['user_id'] : $user_id;
993 if ($config['load_db_track'] && $use_user_id != ANONYMOUS)
995 $db->sql_return_on_error(true);
997 $sql_ary = array(
998 'user_id' => $use_user_id,
999 'topic_id' => $topic_id,
1000 'topic_posted' => 1
1003 $db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1005 $db->sql_return_on_error(false);
1008 return;
1013 * Get topic tracking info by using already fetched info
1015 function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $global_announce_list = false)
1017 global $config, $user;
1019 $last_read = array();
1021 if (!is_array($topic_ids))
1023 $topic_ids = array($topic_ids);
1026 foreach ($topic_ids as $topic_id)
1028 if (!empty($rowset[$topic_id]['mark_time']))
1030 $last_read[$topic_id] = $rowset[$topic_id]['mark_time'];
1034 $topic_ids = array_diff($topic_ids, array_keys($last_read));
1036 if (sizeof($topic_ids))
1038 $mark_time = array();
1040 // Get global announcement info
1041 if ($global_announce_list && sizeof($global_announce_list))
1043 if (!isset($forum_mark_time[0]))
1045 global $db;
1047 $sql = 'SELECT mark_time
1048 FROM ' . FORUMS_TRACK_TABLE . "
1049 WHERE user_id = {$user->data['user_id']}
1050 AND forum_id = 0";
1051 $result = $db->sql_query($sql);
1052 $row = $db->sql_fetchrow($result);
1053 $db->sql_freeresult($result);
1055 if ($row)
1057 $mark_time[0] = $row['mark_time'];
1060 else
1062 if ($forum_mark_time[0] !== false)
1064 $mark_time[0] = $forum_mark_time[0];
1069 if (!empty($forum_mark_time[$forum_id]) && $forum_mark_time[$forum_id] !== false)
1071 $mark_time[$forum_id] = $forum_mark_time[$forum_id];
1074 $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1076 foreach ($topic_ids as $topic_id)
1078 if ($global_announce_list && isset($global_announce_list[$topic_id]))
1080 $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1082 else
1084 $last_read[$topic_id] = $user_lastmark;
1089 return $last_read;
1093 * Get topic tracking info from db (for cookie based tracking only this function is used)
1095 function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_list = false)
1097 global $config, $user;
1099 $last_read = array();
1101 if (!is_array($topic_ids))
1103 $topic_ids = array($topic_ids);
1106 if ($config['load_db_lastread'] && $user->data['is_registered'])
1108 global $db;
1110 $sql = 'SELECT topic_id, mark_time
1111 FROM ' . TOPICS_TRACK_TABLE . "
1112 WHERE user_id = {$user->data['user_id']}
1113 AND " . $db->sql_in_set('topic_id', $topic_ids);
1114 $result = $db->sql_query($sql);
1116 while ($row = $db->sql_fetchrow($result))
1118 $last_read[$row['topic_id']] = $row['mark_time'];
1120 $db->sql_freeresult($result);
1122 $topic_ids = array_diff($topic_ids, array_keys($last_read));
1124 if (sizeof($topic_ids))
1126 $sql = 'SELECT forum_id, mark_time
1127 FROM ' . FORUMS_TRACK_TABLE . "
1128 WHERE user_id = {$user->data['user_id']}
1129 AND forum_id " .
1130 (($global_announce_list && sizeof($global_announce_list)) ? "IN (0, $forum_id)" : "= $forum_id");
1131 $result = $db->sql_query($sql);
1133 $mark_time = array();
1134 while ($row = $db->sql_fetchrow($result))
1136 $mark_time[$row['forum_id']] = $row['mark_time'];
1138 $db->sql_freeresult($result);
1140 $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1142 foreach ($topic_ids as $topic_id)
1144 if ($global_announce_list && isset($global_announce_list[$topic_id]))
1146 $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1148 else
1150 $last_read[$topic_id] = $user_lastmark;
1155 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1157 global $tracking_topics;
1159 if (!isset($tracking_topics) || !sizeof($tracking_topics))
1161 $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1162 $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1165 if (!$user->data['is_registered'])
1167 $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
1169 else
1171 $user_lastmark = $user->data['user_lastmark'];
1174 foreach ($topic_ids as $topic_id)
1176 $topic_id36 = base_convert($topic_id, 10, 36);
1178 if (isset($tracking_topics['t'][$topic_id36]))
1180 $last_read[$topic_id] = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
1184 $topic_ids = array_diff($topic_ids, array_keys($last_read));
1186 if (sizeof($topic_ids))
1188 $mark_time = array();
1189 if ($global_announce_list && sizeof($global_announce_list))
1191 if (isset($tracking_topics['f'][0]))
1193 $mark_time[0] = base_convert($tracking_topics['f'][0], 36, 10) + $config['board_startdate'];
1197 if (isset($tracking_topics['f'][$forum_id]))
1199 $mark_time[$forum_id] = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
1202 $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user_lastmark;
1204 foreach ($topic_ids as $topic_id)
1206 if ($global_announce_list && isset($global_announce_list[$topic_id]))
1208 $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1210 else
1212 $last_read[$topic_id] = $user_lastmark;
1218 return $last_read;
1222 * Check for read forums and update topic tracking info accordingly
1224 * @param int $forum_id the forum id to check
1225 * @param int $forum_last_post_time the forums last post time
1226 * @param int $f_mark_time the forums last mark time if user is registered and load_db_lastread enabled
1227 * @param int $mark_time_forum false if the mark time needs to be obtained, else the last users forum mark time
1229 * @return true if complete forum got marked read, else false.
1231 function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time = false, $mark_time_forum = false)
1233 global $db, $tracking_topics, $user, $config;
1235 // Determine the users last forum mark time if not given.
1236 if ($mark_time_forum === false)
1238 if ($config['load_db_lastread'] && $user->data['is_registered'])
1240 $mark_time_forum = (!empty($f_mark_time)) ? $f_mark_time : $user->data['user_lastmark'];
1242 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1244 if (!isset($tracking_topics) || !sizeof($tracking_topics))
1246 $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1247 $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1250 if (!$user->data['is_registered'])
1252 $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0;
1255 $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'];
1259 // Check the forum for any left unread topics.
1260 // If there are none, we mark the forum as read.
1261 if ($config['load_db_lastread'] && $user->data['is_registered'])
1263 if ($mark_time_forum >= $forum_last_post_time)
1265 // We do not need to mark read, this happened before. Therefore setting this to true
1266 $row = true;
1268 else
1270 $sql = 'SELECT t.forum_id FROM ' . TOPICS_TABLE . ' t
1271 LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt ON (tt.topic_id = t.topic_id AND tt.user_id = ' . $user->data['user_id'] . ')
1272 WHERE t.forum_id = ' . $forum_id . '
1273 AND t.topic_last_post_time > ' . $mark_time_forum . '
1274 AND t.topic_moved_id = 0
1275 AND (tt.topic_id IS NULL OR tt.mark_time < t.topic_last_post_time)
1276 GROUP BY t.forum_id';
1277 $result = $db->sql_query_limit($sql, 1);
1278 $row = $db->sql_fetchrow($result);
1279 $db->sql_freeresult($result);
1282 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1284 // Get information from cookie
1285 $row = false;
1287 if (!isset($tracking_topics['tf'][$forum_id]))
1289 // We do not need to mark read, this happened before. Therefore setting this to true
1290 $row = true;
1292 else
1294 $sql = 'SELECT topic_id
1295 FROM ' . TOPICS_TABLE . '
1296 WHERE forum_id = ' . $forum_id . '
1297 AND topic_last_post_time > ' . $mark_time_forum . '
1298 AND topic_moved_id = 0';
1299 $result = $db->sql_query($sql);
1301 $check_forum = $tracking_topics['tf'][$forum_id];
1302 $unread = false;
1303 while ($row = $db->sql_fetchrow($result))
1305 if (!in_array(base_convert($row['topic_id'], 10, 36), array_keys($check_forum)))
1307 $unread = true;
1308 break;
1311 $db->sql_freeresult($result);
1313 $row = $unread;
1316 else
1318 $row = true;
1321 if (!$row)
1323 markread('topics', $forum_id);
1324 return true;
1327 return false;
1331 * Transform an array into a serialized format
1333 function tracking_serialize($input)
1335 $out = '';
1336 foreach ($input as $key => $value)
1338 if (is_array($value))
1340 $out .= $key . ':(' . tracking_serialize($value) . ');';
1342 else
1344 $out .= $key . ':' . $value . ';';
1347 return $out;
1351 * Transform a serialized array into an actual array
1353 function tracking_unserialize($string, $max_depth = 3)
1355 $n = strlen($string);
1356 if ($n > 10010)
1358 die('Invalid data supplied');
1360 $data = $stack = array();
1361 $key = '';
1362 $mode = 0;
1363 $level = &$data;
1364 for ($i = 0; $i < $n; ++$i)
1366 switch ($mode)
1368 case 0:
1369 switch ($string[$i])
1371 case ':':
1372 $level[$key] = 0;
1373 $mode = 1;
1374 break;
1375 case ')':
1376 unset($level);
1377 $level = array_pop($stack);
1378 $mode = 3;
1379 break;
1380 default:
1381 $key .= $string[$i];
1383 break;
1385 case 1:
1386 switch ($string[$i])
1388 case '(':
1389 if (sizeof($stack) >= $max_depth)
1391 die('Invalid data supplied');
1393 $stack[] = &$level;
1394 $level[$key] = array();
1395 $level = &$level[$key];
1396 $key = '';
1397 $mode = 0;
1398 break;
1399 default:
1400 $level[$key] = $string[$i];
1401 $mode = 2;
1402 break;
1404 break;
1406 case 2:
1407 switch ($string[$i])
1409 case ')':
1410 unset($level);
1411 $level = array_pop($stack);
1412 $mode = 3;
1413 break;
1414 case ';':
1415 $key = '';
1416 $mode = 0;
1417 break;
1418 default:
1419 $level[$key] .= $string[$i];
1420 break;
1422 break;
1424 case 3:
1425 switch ($string[$i])
1427 case ')':
1428 unset($level);
1429 $level = array_pop($stack);
1430 break;
1431 case ';':
1432 $key = '';
1433 $mode = 0;
1434 break;
1435 default:
1436 die('Invalid data supplied');
1437 break;
1439 break;
1443 if (sizeof($stack) != 0 || ($mode != 0 && $mode != 3))
1445 die('Invalid data supplied');
1448 return $level;
1451 // Pagination functions
1454 * Pagination routine, generates page number sequence
1455 * tpl_prefix is for using different pagination blocks at one page
1457 function generate_pagination($base_url, $num_items, $per_page, $start_item, $add_prevnext_text = false, $tpl_prefix = '')
1459 global $template, $user;
1461 // Make sure $per_page is a valid value
1462 $per_page = ($per_page <= 0) ? 1 : $per_page;
1464 $seperator = '<span class="page-sep">' . $user->lang['COMMA_SEPARATOR'] . '</span>';
1465 $total_pages = ceil($num_items / $per_page);
1467 if ($total_pages == 1 || !$num_items)
1469 return false;
1472 $on_page = floor($start_item / $per_page) + 1;
1473 $url_delim = (strpos($base_url, '?') === false) ? '?' : '&amp;';
1475 $page_string = ($on_page == 1) ? '<strong>1</strong>' : '<a href="' . $base_url . '">1</a>';
1477 if ($total_pages > 5)
1479 $start_cnt = min(max(1, $on_page - 4), $total_pages - 5);
1480 $end_cnt = max(min($total_pages, $on_page + 4), 6);
1482 $page_string .= ($start_cnt > 1) ? ' ... ' : $seperator;
1484 for ($i = $start_cnt + 1; $i < $end_cnt; $i++)
1486 $page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>';
1487 if ($i < $end_cnt - 1)
1489 $page_string .= $seperator;
1493 $page_string .= ($end_cnt < $total_pages) ? ' ... ' : $seperator;
1495 else
1497 $page_string .= $seperator;
1499 for ($i = 2; $i < $total_pages; $i++)
1501 $page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>';
1502 if ($i < $total_pages)
1504 $page_string .= $seperator;
1509 $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>';
1511 if ($add_prevnext_text)
1513 if ($on_page != 1)
1515 $page_string = '<a href="' . $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page) . '">' . $user->lang['PREVIOUS'] . '</a>&nbsp;&nbsp;' . $page_string;
1518 if ($on_page != $total_pages)
1520 $page_string .= '&nbsp;&nbsp;<a href="' . $base_url . "{$url_delim}start=" . ($on_page * $per_page) . '">' . $user->lang['NEXT'] . '</a>';
1524 $template->assign_vars(array(
1525 $tpl_prefix . 'BASE_URL' => $base_url,
1526 $tpl_prefix . 'PER_PAGE' => $per_page,
1528 $tpl_prefix . 'PREVIOUS_PAGE' => ($on_page == 1) ? '' : $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page),
1529 $tpl_prefix . 'NEXT_PAGE' => ($on_page == $total_pages) ? '' : $base_url . "{$url_delim}start=" . ($on_page * $per_page),
1530 $tpl_prefix . 'TOTAL_PAGES' => $total_pages)
1533 return $page_string;
1537 * Return current page (pagination)
1539 function on_page($num_items, $per_page, $start)
1541 global $template, $user;
1543 // Make sure $per_page is a valid value
1544 $per_page = ($per_page <= 0) ? 1 : $per_page;
1546 $on_page = floor($start / $per_page) + 1;
1548 $template->assign_vars(array(
1549 'ON_PAGE' => $on_page)
1552 return sprintf($user->lang['PAGE_OF'], $on_page, max(ceil($num_items / $per_page), 1));
1555 // Server functions (building urls, redirecting...)
1558 * Append session id to url
1560 * @param string $url The url the session id needs to be appended to (can have params)
1561 * @param mixed $params String or array of additional url parameters
1562 * @param bool $is_amp Is url using &amp; (true) or & (false)
1563 * @param string $session_id Possibility to use a custom session id instead of the global one
1565 * Examples:
1566 * <code>
1567 * append_sid("{$phpbb_root_path}viewtopic.$phpEx?t=1&amp;f=2");
1568 * append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&amp;f=2');
1569 * append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&f=2', false);
1570 * append_sid("{$phpbb_root_path}viewtopic.$phpEx", array('t' => 1, 'f' => 2));
1571 * </code>
1573 function append_sid($url, $params = false, $is_amp = true, $session_id = false)
1575 global $_SID, $_EXTRA_URL;
1577 // Assign sid if session id is not specified
1578 if ($session_id === false)
1580 $session_id = $_SID;
1583 $amp_delim = ($is_amp) ? '&amp;' : '&';
1584 $url_delim = (strpos($url, '?') === false) ? '?' : $amp_delim;
1586 // Appending custom url parameter?
1587 $append_url = (!empty($_EXTRA_URL)) ? implode($amp_delim, $_EXTRA_URL) : '';
1589 $anchor = '';
1590 if (strpos($url, '#') !== false)
1592 list($url, $anchor) = explode('#', $url, 2);
1593 $anchor = '#' . $anchor;
1595 else if (!is_array($params) && strpos($params, '#') !== false)
1597 list($params, $anchor) = explode('#', $params, 2);
1598 $anchor = '#' . $anchor;
1601 // Use the short variant if possible ;)
1602 if ($params === false)
1604 // Append session id
1605 if (!$session_id)
1607 return $url . (($append_url) ? $url_delim . $append_url : '') . $anchor;
1609 else
1611 return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . 'sid=' . $session_id . $anchor;
1615 // Build string if parameters are specified as array
1616 if (is_array($params))
1618 $output = array();
1620 foreach ($params as $key => $item)
1622 if ($item === NULL)
1624 continue;
1627 if ($key == '#')
1629 $anchor = '#' . $item;
1630 continue;
1633 $output[] = $key . '=' . $item;
1636 $params = implode($amp_delim, $output);
1639 // Append session id and parameters (even if they are empty)
1640 // If parameters are empty, the developer can still append his/her parameters without caring about the delimiter
1641 return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . $params . ((!$session_id) ? '' : $amp_delim . 'sid=' . $session_id) . $anchor;
1645 * Generate board url (example: http://www.foo.bar/phpBB)
1646 * @param bool $without_script_path if set to true the script path gets not appended (example: http://www.foo.bar)
1648 function generate_board_url($without_script_path = false)
1650 global $config, $user;
1652 $server_name = (!empty($_SERVER['SERVER_NAME'])) ? $_SERVER['SERVER_NAME'] : getenv('SERVER_NAME');
1653 $server_port = (!empty($_SERVER['SERVER_PORT'])) ? (int) $_SERVER['SERVER_PORT'] : (int) getenv('SERVER_PORT');
1655 // Forcing server vars is the only way to specify/override the protocol
1656 if ($config['force_server_vars'] || !$server_name)
1658 $server_protocol = ($config['server_protocol']) ? $config['server_protocol'] : (($config['cookie_secure']) ? 'https://' : 'http://');
1659 $server_name = $config['server_name'];
1660 $server_port = (int) $config['server_port'];
1661 $script_path = $config['script_path'];
1663 $url = $server_protocol . $server_name;
1665 else
1667 // Do not rely on cookie_secure, users seem to think that it means a secured cookie instead of an encrypted connection
1668 $cookie_secure = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 1 : 0;
1669 $url = (($cookie_secure) ? 'https://' : 'http://') . $server_name;
1671 $script_path = $user->page['root_script_path'];
1674 if ($server_port && (($config['cookie_secure'] && $server_port <> 443) || (!$config['cookie_secure'] && $server_port <> 80)))
1676 $url .= ':' . $server_port;
1679 if (!$without_script_path)
1681 $url .= $script_path;
1684 // Strip / from the end
1685 if (substr($url, -1, 1) == '/')
1687 $url = substr($url, 0, -1);
1690 return $url;
1694 * Redirects the user to another page then exits the script nicely
1696 function redirect($url, $return = false)
1698 global $db, $cache, $config, $user, $phpbb_root_path;
1700 if (empty($user->lang))
1702 $user->add_lang('common');
1705 if (!$return)
1707 garbage_collection();
1710 // Make sure no &amp;'s are in, this will break the redirect
1711 $url = str_replace('&amp;', '&', $url);
1713 // Determine which type of redirect we need to handle...
1714 $url_parts = parse_url($url);
1716 if ($url_parts === false)
1718 // Malformed url, redirect to current page...
1719 $url = generate_board_url() . '/' . $user->page['page'];
1721 else if (!empty($url_parts['scheme']) && !empty($url_parts['host']))
1723 // Full URL
1725 else if ($url[0] == '/')
1727 // Absolute uri, prepend direct url...
1728 $url = generate_board_url(true) . $url;
1730 else
1732 // Relative uri
1733 $pathinfo = pathinfo($url);
1735 // Is the uri pointing to the current directory?
1736 if ($pathinfo['dirname'] == '.')
1738 $url = str_replace('./', '', $url);
1740 // Strip / from the beginning
1741 if ($url && substr($url, 0, 1) == '/')
1743 $url = substr($url, 1);
1746 if ($user->page['page_dir'])
1748 $url = generate_board_url() . '/' . $user->page['page_dir'] . '/' . $url;
1750 else
1752 $url = generate_board_url() . '/' . $url;
1755 else
1757 // Used ./ before, but $phpbb_root_path is working better with urls within another root path
1758 $root_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($phpbb_root_path)));
1759 $page_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($pathinfo['dirname'])));
1760 $intersection = array_intersect_assoc($root_dirs, $page_dirs);
1762 $root_dirs = array_diff_assoc($root_dirs, $intersection);
1763 $page_dirs = array_diff_assoc($page_dirs, $intersection);
1765 $dir = str_repeat('../', sizeof($root_dirs)) . implode('/', $page_dirs);
1767 // Strip / from the end
1768 if ($dir && substr($dir, -1, 1) == '/')
1770 $dir = substr($dir, 0, -1);
1773 // Strip / from the beginning
1774 if ($dir && substr($dir, 0, 1) == '/')
1776 $dir = substr($dir, 1);
1779 $url = str_replace($pathinfo['dirname'] . '/', '', $url);
1781 // Strip / from the beginning
1782 if (substr($url, 0, 1) == '/')
1784 $url = substr($url, 1);
1787 $url = $dir . '/' . $url;
1788 $url = generate_board_url() . '/' . $url;
1792 // Make sure no linebreaks are there... to prevent http response splitting for PHP < 4.4.2
1793 if (strpos(urldecode($url), "\n") !== false || strpos(urldecode($url), "\r") !== false || strpos($url, ';') !== false)
1795 trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
1798 if ($return)
1800 return $url;
1803 // Redirect via an HTML form for PITA webservers
1804 if (@preg_match('#Microsoft|WebSTAR|Xitami#', getenv('SERVER_SOFTWARE')))
1806 header('Refresh: 0; URL=' . $url);
1808 echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1809 echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="' . $user->lang['DIRECTION'] . '" lang="' . $user->lang['USER_LANG'] . '" xml:lang="' . $user->lang['USER_LANG'] . '">';
1810 echo '<head>';
1811 echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
1812 echo '<meta http-equiv="refresh" content="0; url=' . str_replace('&', '&amp;', $url) . '" />';
1813 echo '<title>' . $user->lang['REDIRECT'] . '</title>';
1814 echo '</head>';
1815 echo '<body>';
1816 echo '<div style="text-align: center;">' . sprintf($user->lang['URL_REDIRECT'], '<a href="' . str_replace('&', '&amp;', $url) . '">', '</a>') . '</div>';
1817 echo '</body>';
1818 echo '</html>';
1820 exit;
1823 // Behave as per HTTP/1.1 spec for others
1824 header('Location: ' . $url);
1825 exit;
1829 * Re-Apply session id after page reloads
1831 function reapply_sid($url)
1833 global $phpEx, $phpbb_root_path;
1835 if ($url === "index.$phpEx")
1837 return append_sid("index.$phpEx");
1839 else if ($url === "{$phpbb_root_path}index.$phpEx")
1841 return append_sid("{$phpbb_root_path}index.$phpEx");
1844 // Remove previously added sid
1845 if (strpos($url, '?sid=') !== false)
1847 $url = preg_replace('/(\?)sid=[a-z0-9]+(&amp;|&)?/', '\1', $url);
1849 else if (strpos($url, '&sid=') !== false)
1851 $url = preg_replace('/&sid=[a-z0-9]+(&)?/', '\1', $url);
1853 else if (strpos($url, '&amp;sid=') !== false)
1855 $url = preg_replace('/&amp;sid=[a-z0-9]+(&amp;)?/', '\1', $url);
1858 return append_sid($url);
1862 * Returns url from the session/current page with an re-appended SID with optionally stripping vars from the url
1864 function build_url($strip_vars = false)
1866 global $user, $phpbb_root_path;
1868 // Append SID
1869 $redirect = (($user->page['page_dir']) ? $user->page['page_dir'] . '/' : '') . $user->page['page_name'] . (($user->page['query_string']) ? "?{$user->page['query_string']}" : '');
1870 $redirect = append_sid($redirect, false, false);
1872 // Add delimiter if not there...
1873 if (strpos($redirect, '?') === false)
1875 $redirect .= '?';
1878 // Strip vars...
1879 if ($strip_vars !== false && strpos($redirect, '?') !== false)
1881 if (!is_array($strip_vars))
1883 $strip_vars = array($strip_vars);
1886 $query = $_query = array();
1888 $args = substr($redirect, strpos($redirect, '?') + 1);
1889 $args = ($args) ? explode('&', $args) : array();
1890 $redirect = substr($redirect, 0, strpos($redirect, '?'));
1892 foreach ($args as $argument)
1894 $arguments = explode('=', $argument);
1895 $key = $arguments[0];
1896 unset($arguments[0]);
1898 $query[$key] = implode('=', $arguments);
1901 // Strip the vars off
1902 foreach ($strip_vars as $strip)
1904 if (isset($query[$strip]))
1906 unset($query[$strip]);
1910 // Glue the remaining parts together... already urlencoded
1911 foreach ($query as $key => $value)
1913 $_query[] = $key . '=' . $value;
1915 $query = implode('&', $_query);
1917 $redirect .= ($query) ? '?' . $query : '';
1920 return $phpbb_root_path . str_replace('&', '&amp;', $redirect);
1924 * Meta refresh assignment
1926 function meta_refresh($time, $url)
1928 global $template;
1930 $url = redirect($url, true);
1932 // For XHTML compatibility we change back & to &amp;
1933 $template->assign_vars(array(
1934 'META' => '<meta http-equiv="refresh" content="' . $time . ';url=' . str_replace('&', '&amp;', $url) . '" />')
1938 // Message/Login boxes
1941 * Build Confirm box
1942 * @param boolean $check True for checking if confirmed (without any additional parameters) and false for displaying the confirm box
1943 * @param string $title Title/Message used for confirm box.
1944 * message text is _CONFIRM appended to title.
1945 * If title can not be found in user->lang a default one is displayed
1946 * If title_CONFIRM can not be found in user->lang the text given is used.
1947 * @param string $hidden Hidden variables
1948 * @param string $html_body Template used for confirm box
1949 * @param string $u_action Custom form action
1951 function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '')
1953 global $user, $template, $db;
1954 global $phpEx, $phpbb_root_path;
1956 if (isset($_POST['cancel']))
1958 return false;
1961 $confirm = false;
1962 if (isset($_POST['confirm']))
1964 // language frontier
1965 if ($_POST['confirm'] == $user->lang['YES'])
1967 $confirm = true;
1971 if ($check && $confirm)
1973 $user_id = request_var('user_id', 0);
1974 $session_id = request_var('sess', '');
1975 $confirm_key = request_var('confirm_key', '');
1977 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'])
1979 return false;
1982 // Reset user_last_confirm_key
1983 $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = ''
1984 WHERE user_id = " . $user->data['user_id'];
1985 $db->sql_query($sql);
1987 return true;
1989 else if ($check)
1991 return false;
1994 $s_hidden_fields = build_hidden_fields(array(
1995 'user_id' => $user->data['user_id'],
1996 'sess' => $user->session_id,
1997 'sid' => $user->session_id)
2000 // generate activation key
2001 $confirm_key = gen_rand_string(10);
2003 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2005 adm_page_header((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]);
2007 else
2009 page_header((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]);
2012 $template->set_filenames(array(
2013 'body' => $html_body)
2016 // If activation key already exist, we better do not re-use the key (something very strange is going on...)
2017 if (request_var('confirm_key', ''))
2019 // This should not occur, therefore we cancel the operation to safe the user
2020 return false;
2023 // re-add sid / transform & to &amp; for user->page (user->page is always using &)
2024 $use_page = ($u_action) ? $phpbb_root_path . $u_action : $phpbb_root_path . str_replace('&', '&amp;', $user->page['page']);
2025 $u_action = reapply_sid($use_page);
2026 $u_action .= ((strpos($u_action, '?') === false) ? '?' : '&amp;') . 'confirm_key=' . $confirm_key;
2028 $template->assign_vars(array(
2029 'MESSAGE_TITLE' => (!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title],
2030 'MESSAGE_TEXT' => (!isset($user->lang[$title . '_CONFIRM'])) ? $title : $user->lang[$title . '_CONFIRM'],
2032 'YES_VALUE' => $user->lang['YES'],
2033 'S_CONFIRM_ACTION' => $u_action,
2034 'S_HIDDEN_FIELDS' => $hidden . $s_hidden_fields)
2037 $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '" . $db->sql_escape($confirm_key) . "'
2038 WHERE user_id = " . $user->data['user_id'];
2039 $db->sql_query($sql);
2041 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2043 adm_page_footer();
2045 else
2047 page_footer();
2052 * Generate login box or verify password
2054 function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true)
2056 global $db, $user, $template, $auth, $phpEx, $phpbb_root_path, $config;
2058 $err = '';
2060 // Make sure user->setup() has been called
2061 if (empty($user->lang))
2063 $user->setup();
2066 // Print out error if user tries to authenticate as an administrator without having the privileges...
2067 if ($admin && !$auth->acl_get('a_'))
2069 // Not authd
2070 // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
2071 if ($user->data['is_registered'])
2073 add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2075 trigger_error('NO_AUTH_ADMIN');
2078 if (isset($_POST['login']))
2080 $username = request_var('username', '', true);
2081 $password = request_var('password', '', true);
2082 $autologin = (!empty($_POST['autologin'])) ? true : false;
2083 $viewonline = (!empty($_POST['viewonline'])) ? 0 : 1;
2084 $admin = ($admin) ? 1 : 0;
2085 $viewonline = ($admin) ? $user->data['session_viewonline'] : $viewonline;
2087 // Check if the supplied username is equal to the one stored within the database if re-authenticating
2088 if ($admin && utf8_clean_string($username) != utf8_clean_string($user->data['username']))
2090 // We log the attempt to use a different username...
2091 add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2092 trigger_error('NO_AUTH_ADMIN_USER_DIFFER');
2095 // do not allow empty password
2096 if (!$password)
2098 trigger_error('NO_PASSWORD_SUPPLIED');
2101 // If authentication is successful we redirect user to previous page
2102 $result = $auth->login($username, $password, $autologin, $viewonline, $admin);
2104 // If admin authentication and login, we will log if it was a success or not...
2105 // We also break the operation on the first non-success login - it could be argued that the user already knows
2106 if ($admin)
2108 if ($result['status'] == LOGIN_SUCCESS)
2110 add_log('admin', 'LOG_ADMIN_AUTH_SUCCESS');
2112 else
2114 // Only log the failed attempt if a real user tried to.
2115 // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
2116 if ($user->data['is_registered'])
2118 add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2123 // The result parameter is always an array, holding the relevant information...
2124 if ($result['status'] == LOGIN_SUCCESS)
2126 $redirect = request_var('redirect', "{$phpbb_root_path}index.$phpEx");
2127 $message = ($l_success) ? $l_success : $user->lang['LOGIN_REDIRECT'];
2128 $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']);
2130 // append/replace SID (may change during the session for AOL users)
2131 $redirect = reapply_sid($redirect);
2133 // Special case... the user is effectively banned, but we allow founders to login
2134 if (defined('IN_CHECK_BAN') && $result['user_row']['user_type'] != USER_FOUNDER)
2136 return;
2139 meta_refresh(3, $redirect);
2140 trigger_error($message . '<br /><br />' . sprintf($l_redirect, '<a href="' . $redirect . '">', '</a>'));
2143 // Something failed, determine what...
2144 if ($result['status'] == LOGIN_BREAK)
2146 trigger_error($result['error_msg'], E_USER_ERROR);
2149 // Special cases... determine
2150 switch ($result['status'])
2152 case LOGIN_ERROR_ATTEMPTS:
2154 // Show confirm image
2155 $sql = 'DELETE FROM ' . CONFIRM_TABLE . "
2156 WHERE session_id = '" . $db->sql_escape($user->session_id) . "'
2157 AND confirm_type = " . CONFIRM_LOGIN;
2158 $db->sql_query($sql);
2160 // Generate code
2161 $code = gen_rand_string(mt_rand(5, 8));
2162 $confirm_id = md5(unique_id($user->ip));
2163 $seed = hexdec(substr(unique_id(), 4, 10));
2165 // compute $seed % 0x7fffffff
2166 $seed -= 0x7fffffff * floor($seed / 0x7fffffff);
2168 $sql = 'INSERT INTO ' . CONFIRM_TABLE . ' ' . $db->sql_build_array('INSERT', array(
2169 'confirm_id' => (string) $confirm_id,
2170 'session_id' => (string) $user->session_id,
2171 'confirm_type' => (int) CONFIRM_LOGIN,
2172 'code' => (string) $code,
2173 'seed' => (int) $seed)
2175 $db->sql_query($sql);
2177 $template->assign_vars(array(
2178 'S_CONFIRM_CODE' => true,
2179 'CONFIRM_ID' => $confirm_id,
2180 'CONFIRM_IMAGE' => '<img src="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=confirm&amp;id=' . $confirm_id . '&amp;type=' . CONFIRM_LOGIN) . '" alt="" title="" />',
2181 'L_LOGIN_CONFIRM_EXPLAIN' => sprintf($user->lang['LOGIN_CONFIRM_EXPLAIN'], '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">', '</a>'),
2184 $err = $user->lang[$result['error_msg']];
2186 break;
2188 case LOGIN_ERROR_PASSWORD_CONVERT:
2189 $err = sprintf(
2190 $user->lang[$result['error_msg']],
2191 ($config['email_enable']) ? '<a href="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') . '">' : '',
2192 ($config['email_enable']) ? '</a>' : '',
2193 ($config['board_contact']) ? '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">' : '',
2194 ($config['board_contact']) ? '</a>' : ''
2196 break;
2198 // Username, password, etc...
2199 default:
2200 $err = $user->lang[$result['error_msg']];
2202 // Assign admin contact to some error messages
2203 if ($result['error_msg'] == 'LOGIN_ERROR_USERNAME' || $result['error_msg'] == 'LOGIN_ERROR_PASSWORD')
2205 $err = (!$config['board_contact']) ? sprintf($user->lang[$result['error_msg']], '', '') : sprintf($user->lang[$result['error_msg']], '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">', '</a>');
2208 break;
2212 if (!$redirect)
2214 // We just use what the session code determined...
2215 // If we are not within the admin directory we use the page dir...
2216 $redirect = '';
2218 if (!$admin)
2220 $redirect .= ($user->page['page_dir']) ? $user->page['page_dir'] . '/' : '';
2223 $redirect .= $user->page['page_name'] . (($user->page['query_string']) ? '?' . htmlspecialchars($user->page['query_string']) : '');
2226 $s_hidden_fields = build_hidden_fields(array('redirect' => $redirect, 'sid' => $user->session_id));
2228 $template->assign_vars(array(
2229 'LOGIN_ERROR' => $err,
2230 'LOGIN_EXPLAIN' => $l_explain,
2232 'U_SEND_PASSWORD' => ($config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') : '',
2233 'U_RESEND_ACTIVATION' => ($config['require_activation'] != USER_ACTIVATION_NONE && $config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=resend_act') : '',
2234 'U_TERMS_USE' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
2235 'U_PRIVACY' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
2237 'S_DISPLAY_FULL_LOGIN' => ($s_display) ? true : false,
2238 'S_AUTOLOGIN_ENABLED' => ($config['allow_autologin']) ? true : false,
2239 'S_LOGIN_ACTION' => (!$admin) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login') : append_sid("index.$phpEx", false, true, $user->session_id), // Needs to stay index.$phpEx because we are within the admin directory
2240 'S_HIDDEN_FIELDS' => $s_hidden_fields,
2242 'S_ADMIN_AUTH' => $admin,
2243 'USERNAME' => ($admin) ? $user->data['username'] : '')
2246 page_header($user->lang['LOGIN']);
2248 $template->set_filenames(array(
2249 'body' => 'login_body.html')
2251 make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"));
2253 page_footer();
2257 * Generate forum login box
2259 function login_forum_box($forum_data)
2261 global $db, $config, $user, $template, $phpEx;
2263 $password = request_var('password', '', true);
2265 $sql = 'SELECT forum_id
2266 FROM ' . FORUMS_ACCESS_TABLE . '
2267 WHERE forum_id = ' . $forum_data['forum_id'] . '
2268 AND user_id = ' . $user->data['user_id'] . "
2269 AND session_id = '" . $db->sql_escape($user->session_id) . "'";
2270 $result = $db->sql_query($sql);
2271 $row = $db->sql_fetchrow($result);
2272 $db->sql_freeresult($result);
2274 if ($row)
2276 return true;
2279 if ($password)
2281 // Remove expired authorised sessions
2282 $sql = 'SELECT session_id
2283 FROM ' . SESSIONS_TABLE;
2284 $result = $db->sql_query($sql);
2286 if ($row = $db->sql_fetchrow($result))
2288 $sql_in = array();
2291 $sql_in[] = (string) $row['session_id'];
2293 while ($row = $db->sql_fetchrow($result));
2295 // Remove expired sessions
2296 $sql = 'DELETE FROM ' . FORUMS_ACCESS_TABLE . '
2297 WHERE ' . $db->sql_in_set('session_id', $sql_in, true);
2298 $db->sql_query($sql);
2300 $db->sql_freeresult($result);
2302 if ($password == $forum_data['forum_password'])
2304 $sql_ary = array(
2305 'forum_id' => (int) $forum_data['forum_id'],
2306 'user_id' => (int) $user->data['user_id'],
2307 'session_id' => (string) $user->session_id,
2310 $db->sql_query('INSERT INTO ' . FORUMS_ACCESS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
2312 return true;
2315 $template->assign_var('LOGIN_ERROR', $user->lang['WRONG_PASSWORD']);
2318 page_header($user->lang['LOGIN']);
2320 $template->assign_vars(array(
2321 'S_HIDDEN_FIELDS' => build_hidden_fields(array('f' => $forum_data['forum_id'])))
2324 $template->set_filenames(array(
2325 'body' => 'login_forum.html')
2328 page_footer();
2331 // Content related functions
2334 * Bump Topic Check - used by posting and viewtopic
2336 function bump_topic_allowed($forum_id, $topic_bumped, $last_post_time, $topic_poster, $last_topic_poster)
2338 global $config, $auth, $user;
2340 // Check permission and make sure the last post was not already bumped
2341 if (!$auth->acl_get('f_bump', $forum_id) || $topic_bumped)
2343 return false;
2346 // Check bump time range, is the user really allowed to bump the topic at this time?
2347 $bump_time = ($config['bump_type'] == 'm') ? $config['bump_interval'] * 60 : (($config['bump_type'] == 'h') ? $config['bump_interval'] * 3600 : $config['bump_interval'] * 86400);
2349 // Check bump time
2350 if ($last_post_time + $bump_time > time())
2352 return false;
2355 // Check bumper, only topic poster and last poster are allowed to bump
2356 if ($topic_poster != $user->data['user_id'] && $last_topic_poster != $user->data['user_id'])
2358 return false;
2361 // A bump time of 0 will completely disable the bump feature... not intended but might be useful.
2362 return $bump_time;
2366 * Generates a text with approx. the specified length which contains the specified words and their context
2368 * @param string $text The full text from which context shall be extracted
2369 * @param string $words An array of words which should be contained in the result, has to be a valid part of a PCRE pattern (escape with preg_quote!)
2370 * @param int $length The desired length of the resulting text, however the result might be shorter or longer than this value
2372 * @return string Context of the specified words separated by "..."
2374 function get_context($text, $words, $length = 400)
2376 // first replace all whitespaces with single spaces
2377 $text = preg_replace('/ +/', ' ', strtr($text, "\t\n\r\x0C ", ' '), $text);
2379 $word_indizes = array();
2380 if (sizeof($words))
2382 $match = '';
2383 // find the starting indizes of all words
2384 foreach ($words as $word)
2386 if ($word)
2388 if (preg_match('#(?:[^\w]|^)(' . $word . ')(?:[^\w]|$)#i', $text, $match))
2390 $pos = strpos($text, $match[1]);
2391 if ($pos !== false)
2393 $word_indizes[] = $pos;
2398 unset($match);
2400 if (sizeof($word_indizes))
2402 $word_indizes = array_unique($word_indizes);
2403 sort($word_indizes);
2405 $wordnum = sizeof($word_indizes);
2406 // number of characters on the right and left side of each word
2407 $sequence_length = (int) ($length / (2 * $wordnum)) - 2;
2408 $final_text = '';
2409 $word = $j = 0;
2410 $final_text_index = -1;
2412 // cycle through every character in the original text
2413 for ($i = $word_indizes[$word], $n = strlen($text); $i < $n; $i++)
2415 // if the current position is the start of one of the words then append $sequence_length characters to the final text
2416 if (isset($word_indizes[$word]) && ($i == $word_indizes[$word]))
2418 if ($final_text_index < $i - $sequence_length - 1)
2420 $final_text .= '... ' . preg_replace('#^([^ ]*)#', '', substr($text, $i - $sequence_length, $sequence_length));
2422 else
2424 // if the final text is already nearer to the current word than $sequence_length we only append the text
2425 // from its current index on and distribute the unused length to all other sequenes
2426 $sequence_length += (int) (($final_text_index - $i + $sequence_length + 1) / (2 * $wordnum));
2427 $final_text .= substr($text, $final_text_index + 1, $i - $final_text_index - 1);
2429 $final_text_index = $i - 1;
2431 // add the following characters to the final text (see below)
2432 $word++;
2433 $j = 1;
2436 if ($j > 0)
2438 // add the character to the final text and increment the sequence counter
2439 $final_text .= $text[$i];
2440 $final_text_index++;
2441 $j++;
2443 // if this is a whitespace then check whether we are done with this sequence
2444 if ($text[$i] == ' ')
2446 // only check whether we have to exit the context generation completely if we haven't already reached the end anyway
2447 if ($i + 4 < $n)
2449 if (($j > $sequence_length && $word >= $wordnum) || strlen($final_text) > $length)
2451 $final_text .= ' ...';
2452 break;
2455 else
2457 // make sure the text really reaches the end
2458 $j -= 4;
2461 // stop context generation and wait for the next word
2462 if ($j > $sequence_length)
2464 $j = 0;
2469 return $final_text;
2473 if (!sizeof($words) || !sizeof($word_indizes))
2475 return (strlen($text) >= $length + 3) ? substr($text, 0, $length) . '...' : $text;
2480 * Decode text whereby text is coming from the db and expected to be pre-parsed content
2481 * We are placing this outside of the message parser because we are often in need of it...
2483 function decode_message(&$message, $bbcode_uid = '')
2485 global $config;
2487 if ($bbcode_uid)
2489 $match = array('<br />', "[/*:m:$bbcode_uid]", ":u:$bbcode_uid", ":o:$bbcode_uid", ":$bbcode_uid");
2490 $replace = array("\n", '', '', '', '');
2492 else
2494 $match = array('<br />');
2495 $replace = array("\n");
2498 $message = str_replace($match, $replace, $message);
2500 $match = get_preg_expression('bbcode_htm');
2501 $replace = array('\1', '\1', '\2', '\1', '', '');
2503 $message = preg_replace($match, $replace, $message);
2507 * Strips all bbcode from a text and returns the plain content
2509 function strip_bbcode(&$text, $uid = '')
2511 if (!$uid)
2513 $uid = '[0-9a-z]{5,}';
2516 $text = preg_replace("#\[\/?[a-z0-9\*\+\-]+(?:=.*?)?(?::[a-z])?(\:?$uid)\]#", ' ', $text);
2518 $match = get_preg_expression('bbcode_htm');
2519 $replace = array('\1', '\1', '\2', '\1', '', '');
2521 $text = preg_replace($match, $replace, $text);
2525 * For display of custom parsed text on user-facing pages
2526 * Expects $text to be the value directly from the database (stored value)
2528 function generate_text_for_display($text, $uid, $bitfield, $flags)
2530 static $bbcode;
2532 if (!$text)
2534 return '';
2537 $text = censor_text($text);
2539 // Parse bbcode if bbcode uid stored and bbcode enabled
2540 if ($uid && ($flags & OPTION_FLAG_BBCODE))
2542 if (!class_exists('bbcode'))
2544 global $phpbb_root_path, $phpEx;
2545 include($phpbb_root_path . 'includes/bbcode.' . $phpEx);
2548 if (empty($bbcode))
2550 $bbcode = new bbcode($bitfield);
2552 else
2554 $bbcode->bbcode($bitfield);
2557 $bbcode->bbcode_second_pass($text, $uid);
2560 $text = str_replace("\n", '<br />', $text);
2562 $text = smiley_text($text, !($flags & OPTION_FLAG_SMILIES));
2564 return $text;
2568 * For parsing custom parsed text to be stored within the database.
2569 * This function additionally returns the uid and bitfield that needs to be stored.
2570 * Expects $text to be the value directly from request_var() and in it's non-parsed form
2572 function generate_text_for_storage(&$text, &$uid, &$bitfield, &$flags, $allow_bbcode = false, $allow_urls = false, $allow_smilies = false)
2574 global $phpbb_root_path, $phpEx;
2576 $uid = $bitfield = '';
2578 if (!$text)
2580 return;
2583 if (!class_exists('parse_message'))
2585 include($phpbb_root_path . 'includes/message_parser.' . $phpEx);
2588 $message_parser = new parse_message($text);
2589 $message_parser->parse($allow_bbcode, $allow_urls, $allow_smilies);
2591 $text = $message_parser->message;
2592 $uid = $message_parser->bbcode_uid;
2594 // If the bbcode_bitfield is empty, there is no need for the uid to be stored.
2595 if (!$message_parser->bbcode_bitfield)
2597 $uid = '';
2600 $flags = (($allow_bbcode) ? OPTION_FLAG_BBCODE : 0) + (($allow_smilies) ? OPTION_FLAG_SMILIES : 0) + (($allow_urls) ? OPTION_FLAG_LINKS : 0);
2601 $bitfield = $message_parser->bbcode_bitfield;
2603 return;
2607 * For decoding custom parsed text for edits as well as extracting the flags
2608 * Expects $text to be the value directly from the database (pre-parsed content)
2610 function generate_text_for_edit($text, $uid, $flags)
2612 global $phpbb_root_path, $phpEx;
2614 decode_message($text, $uid);
2616 return array(
2617 'allow_bbcode' => ($flags & OPTION_FLAG_BBCODE) ? 1 : 0,
2618 'allow_smilies' => ($flags & OPTION_FLAG_SMILIES) ? 1 : 0,
2619 'allow_urls' => ($flags & OPTION_FLAG_LINKS) ? 1 : 0,
2620 'text' => $text
2625 * A subroutine of make_clickable used with preg_replace
2626 * It places correct HTML around an url, shortens the displayed text
2627 * and makes sure no entities are inside URLs
2629 function make_clickable_callback($type, $whitespace, $url, $relative_url, $class)
2631 $append = '';
2632 $url = htmlspecialchars_decode($url);
2633 $relative_url = htmlspecialchars_decode($relative_url);
2635 // make sure no HTML entities were matched
2636 $chars = array('<', '>', '"');
2637 $split = false;
2639 foreach ($chars as $char)
2641 $next_split = strpos($url, $char);
2642 if ($next_split !== false)
2644 $split = ($split !== false) ? min($split, $next_split) : $next_split;
2648 if ($split !== false)
2650 // an HTML entity was found, so the URL has to end before it
2651 $append = substr($url, $split) . $relative_url;
2652 $url = substr($url, 0, $split);
2653 $relative_url = '';
2655 else if ($relative_url)
2657 // same for $relative_url
2658 $split = false;
2659 foreach ($chars as $char)
2661 $next_split = strpos($relative_url, $char);
2662 if ($next_split !== false)
2664 $split = ($split !== false) ? min($split, $next_split) : $next_split;
2668 if ($split !== false)
2670 $append = substr($relative_url, $split);
2671 $relative_url = substr($relative_url, 0, $split);
2675 // if the last character of the url is a punctuation mark, exclude it from the url
2676 $last_char = ($relative_url) ? $relative_url[strlen($relative_url) - 1] : $url[strlen($url) - 1];
2678 switch ($last_char)
2680 case '.':
2681 case '?':
2682 case '!':
2683 case ':':
2684 case ',':
2685 $append = $last_char;
2686 if ($relative_url)
2688 $relative_url = substr($relative_url, 0, -1);
2690 else
2692 $url = substr($url, 0, -1);
2694 break;
2697 switch ($type)
2699 case MAGIC_URL_LOCAL:
2700 $tag = 'l';
2701 $relative_url = preg_replace('/[&?]sid=[0-9a-f]{32}$/', '', preg_replace('/([&?])sid=[0-9a-f]{32}&/', '$1', $relative_url));
2702 $url = $url . '/' . $relative_url;
2703 $text = ($relative_url) ? $relative_url : $url . '/';
2704 break;
2706 case MAGIC_URL_FULL:
2707 $tag = 'm';
2708 $text = (strlen($url) > 55) ? substr($url, 0, 39) . ' ... ' . substr($url, -10) : $url;
2709 break;
2711 case MAGIC_URL_WWW:
2712 $tag = 'w';
2713 $url = 'http://' . $url;
2714 $text = (strlen($url) > 55) ? substr($url, 0, 39) . ' ... ' . substr($url, -10) : $url;
2715 break;
2717 case MAGIC_URL_EMAIL:
2718 $tag = 'e';
2719 $text = (strlen($url) > 55) ? substr($url, 0, 39) . ' ... ' . substr($url, -10) : $url;
2720 $url = 'mailto:' . $url;
2721 break;
2724 $url = htmlspecialchars($url);
2725 $text = htmlspecialchars($text);
2726 $append = htmlspecialchars($append);
2728 $html = "$whitespace<!-- $tag --><a$class href=\"$url\">$text</a><!-- $tag -->$append";
2730 return $html;
2734 * make_clickable function
2736 * Replace magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx.
2737 * Cuts down displayed size of link if over 50 chars, turns absolute links
2738 * into relative versions when the server/script path matches the link
2740 function make_clickable($text, $server_url = false, $class = 'postlink')
2742 if ($server_url === false)
2744 $server_url = generate_board_url();
2747 static $magic_url_match;
2748 static $magic_url_replace;
2749 static $static_class;
2751 if (!is_array($magic_url_match) || $static_class != $class)
2753 $static_class = $class;
2754 $class = ($static_class) ? ' class="' . $static_class . '"' : '';
2755 $local_class = ($static_class) ? ' class="' . $static_class . '-local"' : '';
2757 $magic_url_match = $magic_url_replace = array();
2758 // Be sure to not let the matches cross over. ;)
2760 // relative urls for this board
2761 $magic_url_match[] = '#(^|[\n\t (>])(' . preg_quote($server_url, '#') . ')/(' . get_preg_expression('relative_url_inline') . ')#ie';
2762 $magic_url_replace[] = "make_clickable_callback(MAGIC_URL_LOCAL, '\$1', '\$2', '\$3', '$local_class')";
2764 // matches a xxxx://aaaaa.bbb.cccc. ...
2765 $magic_url_match[] = '#(^|[\n\t (>])(' . get_preg_expression('url_inline') . ')#ie';
2766 $magic_url_replace[] = "make_clickable_callback(MAGIC_URL_FULL, '\$1', '\$2', '', '$class')";
2768 // matches a "www.xxxx.yyyy[/zzzz]" kinda lazy URL thing
2769 $magic_url_match[] = '#(^|[\n\t (>])(' . get_preg_expression('www_url_inline') . ')#ie';
2770 $magic_url_replace[] = "make_clickable_callback(MAGIC_URL_WWW, '\$1', '\$2', '', '$class')";
2772 // matches an email@domain type address at the start of a line, or after a space or after what might be a BBCode.
2773 $magic_url_match[] = '/(^|[\n\t (>])(' . get_preg_expression('email') . ')/ie';
2774 $magic_url_replace[] = "make_clickable_callback(MAGIC_URL_EMAIL, '\$1', '\$2', '', '')";
2777 return preg_replace($magic_url_match, $magic_url_replace, $text);
2781 * Censoring
2783 function censor_text($text)
2785 static $censors;
2786 global $cache;
2788 if (!isset($censors) || !is_array($censors))
2790 // obtain_word_list is taking care of the users censor option and the board-wide option
2791 $censors = $cache->obtain_word_list();
2794 if (sizeof($censors))
2796 return preg_replace($censors['match'], $censors['replace'], $text);
2799 return $text;
2803 * Smiley processing
2805 function smiley_text($text, $force_option = false)
2807 global $config, $user, $phpbb_root_path;
2809 if ($force_option || !$config['allow_smilies'] || !$user->optionget('viewsmilies'))
2811 return preg_replace('#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#', '\1', $text);
2813 else
2815 return preg_replace('#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/(.*?) \/><!\-\- s\1 \-\->#', '<img src="' . $phpbb_root_path . $config['smilies_path'] . '/\2 />', $text);
2820 * General attachment parsing
2822 * @param mixed $forum_id The forum id the attachments are displayed in (false if in private message)
2823 * @param string &$message The post/private message
2824 * @param array &$attachments The attachments to parse for (inline) display. The attachments array will hold templated data after parsing.
2825 * @param array &$update_count The attachment counts to be updated - will be filled
2826 * @param bool $preview If set to true the attachments are parsed for preview. Within preview mode the comments are fetched from the given $attachments array and not fetched from the database.
2828 function parse_attachments($forum_id, &$message, &$attachments, &$update_count, $preview = false)
2830 if (!sizeof($attachments))
2832 return;
2835 global $template, $cache, $user;
2836 global $extensions, $config, $phpbb_root_path, $phpEx;
2839 $compiled_attachments = array();
2841 if (!isset($template->filename['attachment_tpl']))
2843 $template->set_filenames(array(
2844 'attachment_tpl' => 'attachment.html')
2848 if (empty($extensions) || !is_array($extensions))
2850 $extensions = $cache->obtain_attach_extensions($forum_id);
2853 // Look for missing attachment information...
2854 $attach_ids = array();
2855 foreach ($attachments as $pos => $attachment)
2857 // If is_orphan is set, we need to retrieve the attachments again...
2858 if (!isset($attachment['extension']) && !isset($attachment['physical_filename']))
2860 $attach_ids[(int) $attachment['attach_id']] = $pos;
2864 // Grab attachments (security precaution)
2865 if (sizeof($attach_ids))
2867 global $db;
2869 $new_attachment_data = array();
2871 $sql = 'SELECT *
2872 FROM ' . ATTACHMENTS_TABLE . '
2873 WHERE ' . $db->sql_in_set('attach_id', array_keys($attach_ids));
2874 $result = $db->sql_query($sql);
2876 while ($row = $db->sql_fetchrow($result))
2878 if (!isset($attach_ids[$row['attach_id']]))
2880 continue;
2883 // If we preview attachments we will set some retrieved values here
2884 if ($preview)
2886 $row['attach_comment'] = $attachments[$attach_ids[$row['attach_id']]]['attach_comment'];
2889 $new_attachment_data[$attach_ids[$row['attach_id']]] = $row;
2891 $db->sql_freeresult($result);
2893 $attachments = $new_attachment_data;
2894 unset($new_attachment_data);
2897 // Sort correctly
2898 if ($config['display_order'])
2900 // Ascending sort
2901 krsort($attachments);
2903 else
2905 // Descending sort
2906 ksort($attachments);
2909 foreach ($attachments as $attachment)
2911 if (!sizeof($attachment))
2913 continue;
2916 // We need to reset/empty the _file block var, because this function might be called more than once
2917 $template->destroy_block_vars('_file');
2919 $block_array = array();
2921 // Some basics...
2922 $attachment['extension'] = strtolower(trim($attachment['extension']));
2923 $filename = $phpbb_root_path . $config['upload_path'] . '/' . basename($attachment['physical_filename']);
2924 $thumbnail_filename = $phpbb_root_path . $config['upload_path'] . '/thumb_' . basename($attachment['physical_filename']);
2926 $upload_icon = '';
2928 if (isset($extensions[$attachment['extension']]))
2930 if ($user->img('icon_topic_attach', '') && !$extensions[$attachment['extension']]['upload_icon'])
2932 $upload_icon = $user->img('icon_topic_attach', '');
2934 else if ($extensions[$attachment['extension']]['upload_icon'])
2936 $upload_icon = '<img src="' . $phpbb_root_path . $config['upload_icons_path'] . '/' . trim($extensions[$attachment['extension']]['upload_icon']) . '" alt="" />';
2940 $filesize = $attachment['filesize'];
2941 $size_lang = ($filesize >= 1048576) ? $user->lang['MB'] : ( ($filesize >= 1024) ? $user->lang['KB'] : $user->lang['BYTES'] );
2942 $filesize = ($filesize >= 1048576) ? round((round($filesize / 1048576 * 100) / 100), 2) : (($filesize >= 1024) ? round((round($filesize / 1024 * 100) / 100), 2) : $filesize);
2944 $comment = str_replace("\n", '<br />', censor_text($attachment['attach_comment']));
2946 $block_array += array(
2947 'UPLOAD_ICON' => $upload_icon,
2948 'FILESIZE' => $filesize,
2949 'SIZE_LANG' => $size_lang,
2950 'DOWNLOAD_NAME' => basename($attachment['real_filename']),
2951 'COMMENT' => $comment,
2954 $denied = false;
2956 if (!extension_allowed($forum_id, $attachment['extension'], $extensions))
2958 $denied = true;
2960 $block_array += array(
2961 'S_DENIED' => true,
2962 'DENIED_MESSAGE' => sprintf($user->lang['EXTENSION_DISABLED_AFTER_POSTING'], $attachment['extension'])
2966 if (!$denied)
2968 $l_downloaded_viewed = $download_link = '';
2969 $display_cat = $extensions[$attachment['extension']]['display_cat'];
2971 if ($display_cat == ATTACHMENT_CATEGORY_IMAGE)
2973 if ($attachment['thumbnail'])
2975 $display_cat = ATTACHMENT_CATEGORY_THUMB;
2977 else
2979 if ($config['img_display_inlined'])
2981 if ($config['img_link_width'] || $config['img_link_height'])
2983 $dimension = @getimagesize($filename);
2985 // If the dimensions could not be determined or the image being 0x0 we display it as a link for safety purposes
2986 if ($dimension === false || empty($dimension[0]) || empty($dimension[1]))
2988 $display_cat = ATTACHMENT_CATEGORY_NONE;
2990 else
2992 $display_cat = ($dimension[0] <= $config['img_link_width'] && $dimension[1] <= $config['img_link_height']) ? ATTACHMENT_CATEGORY_IMAGE : ATTACHMENT_CATEGORY_NONE;
2996 else
2998 $display_cat = ATTACHMENT_CATEGORY_NONE;
3003 // Make some descisions based on user options being set.
3004 if (($display_cat == ATTACHMENT_CATEGORY_IMAGE || $display_cat == ATTACHMENT_CATEGORY_THUMB) && !$user->optionget('viewimg'))
3006 $display_cat = ATTACHMENT_CATEGORY_NONE;
3009 if ($display_cat == ATTACHMENT_CATEGORY_FLASH && !$user->optionget('viewflash'))
3011 $display_cat = ATTACHMENT_CATEGORY_NONE;
3014 $download_link = append_sid("{$phpbb_root_path}download.$phpEx", 'id=' . $attachment['attach_id']);
3016 switch ($display_cat)
3018 // Images
3019 case ATTACHMENT_CATEGORY_IMAGE:
3020 $l_downloaded_viewed = 'VIEWED_COUNT';
3021 $inline_link = append_sid("{$phpbb_root_path}download.$phpEx", 'id=' . $attachment['attach_id']);
3022 $download_link .= '&amp;mode=view';
3024 $block_array += array(
3025 'S_IMAGE' => true,
3026 'U_INLINE_LINK' => $inline_link,
3029 $update_count[] = $attachment['attach_id'];
3030 break;
3032 // Images, but display Thumbnail
3033 case ATTACHMENT_CATEGORY_THUMB:
3034 $l_downloaded_viewed = 'VIEWED_COUNT';
3035 $thumbnail_link = append_sid("{$phpbb_root_path}download.$phpEx", 'id=' . $attachment['attach_id'] . '&amp;t=1');
3036 $download_link .= '&amp;mode=view';
3038 $block_array += array(
3039 'S_THUMBNAIL' => true,
3040 'THUMB_IMAGE' => $thumbnail_link,
3042 break;
3044 // Windows Media Streams
3045 case ATTACHMENT_CATEGORY_WM:
3046 $l_downloaded_viewed = 'VIEWED_COUNT';
3048 // Giving the filename directly because within the wm object all variables are in local context making it impossible
3049 // to validate against a valid session (all params can differ)
3050 // $download_link = $filename;
3052 $block_array += array(
3053 'U_FORUM' => generate_board_url(),
3054 'ATTACH_ID' => $attachment['attach_id'],
3055 'S_WM_FILE' => true,
3058 // Viewed/Heared File ... update the download count
3059 $update_count[] = $attachment['attach_id'];
3060 break;
3062 // Real Media Streams
3063 case ATTACHMENT_CATEGORY_RM:
3064 case ATTACHMENT_CATEGORY_QUICKTIME:
3065 $l_downloaded_viewed = 'VIEWED_COUNT';
3067 $block_array += array(
3068 'S_RM_FILE' => ($display_cat == ATTACHMENT_CATEGORY_RM) ? true : false,
3069 'S_QUICKTIME_FILE' => ($display_cat == ATTACHMENT_CATEGORY_QUICKTIME) ? true : false,
3070 'U_FORUM' => generate_board_url(),
3071 'ATTACH_ID' => $attachment['attach_id'],
3074 // Viewed/Heared File ... update the download count
3075 $update_count[] = $attachment['attach_id'];
3076 break;
3078 // Macromedia Flash Files
3079 case ATTACHMENT_CATEGORY_FLASH:
3080 list($width, $height) = @getimagesize($filename);
3082 $l_downloaded_viewed = 'VIEWED_COUNT';
3084 $block_array += array(
3085 'S_FLASH_FILE' => true,
3086 'WIDTH' => $width,
3087 'HEIGHT' => $height,
3090 // Viewed/Heared File ... update the download count
3091 $update_count[] = $attachment['attach_id'];
3092 break;
3094 default:
3095 $l_downloaded_viewed = 'DOWNLOAD_COUNT';
3097 $block_array += array(
3098 'S_FILE' => true,
3100 break;
3103 $l_download_count = (!isset($attachment['download_count']) || $attachment['download_count'] == 0) ? $user->lang[$l_downloaded_viewed . '_NONE'] : (($attachment['download_count'] == 1) ? sprintf($user->lang[$l_downloaded_viewed], $attachment['download_count']) : sprintf($user->lang[$l_downloaded_viewed . 'S'], $attachment['download_count']));
3105 $block_array += array(
3106 'U_DOWNLOAD_LINK' => $download_link,
3107 'L_DOWNLOAD_COUNT' => $l_download_count
3111 $template->assign_block_vars('_file', $block_array);
3113 $compiled_attachments[] = $template->assign_display('attachment_tpl');
3116 $attachments = $compiled_attachments;
3117 unset($compiled_attachments);
3119 $tpl_size = sizeof($attachments);
3121 $unset_tpl = array();
3123 preg_match_all('#<!\-\- ia([0-9]+) \-\->(.*?)<!\-\- ia\1 \-\->#', $message, $matches, PREG_PATTERN_ORDER);
3125 $replace = array();
3126 foreach ($matches[0] as $num => $capture)
3128 // Flip index if we are displaying the reverse way
3129 $index = ($config['display_order']) ? ($tpl_size-($matches[1][$num] + 1)) : $matches[1][$num];
3131 $replace['from'][] = $matches[0][$num];
3132 $replace['to'][] = (isset($attachments[$index])) ? $attachments[$index] : sprintf($user->lang['MISSING_INLINE_ATTACHMENT'], $matches[2][array_search($index, $matches[1])]);
3134 $unset_tpl[] = $index;
3137 if (isset($replace['from']))
3139 $message = str_replace($replace['from'], $replace['to'], $message);
3142 $unset_tpl = array_unique($unset_tpl);
3144 // Needed to let not display the inlined attachments at the end of the post again
3145 foreach ($unset_tpl as $index)
3147 unset($attachments[$index]);
3152 * Check if extension is allowed to be posted.
3154 * @param mixed $forum_id The forum id to check or false if private message
3155 * @param string $extension The extension to check, for example zip.
3156 * @param array &$extensions The extension array holding the information from the cache (will be obtained if empty)
3158 * @return bool False if the extension is not allowed to be posted, else true.
3160 function extension_allowed($forum_id, $extension, &$extensions)
3162 if (empty($extensions))
3164 global $cache;
3165 $extensions = $cache->obtain_attach_extensions($forum_id);
3168 return (!isset($extensions['_allowed_'][$extension])) ? false : true;
3171 // Little helpers
3174 * Little helper for the build_hidden_fields function
3176 function _build_hidden_fields($key, $value, $specialchar, $stripslashes)
3178 $hidden_fields = '';
3180 if (!is_array($value))
3182 $value = ($stripslashes) ? stripslashes($value) : $value;
3183 $value = ($specialchar) ? htmlspecialchars($value, ENT_COMPAT, 'UTF-8') : $value;
3185 $hidden_fields .= '<input type="hidden" name="' . $key . '" value="' . $value . '" />' . "\n";
3187 else
3189 foreach ($value as $_key => $_value)
3191 $_key = ($stripslashes) ? stripslashes($_key) : $_key;
3192 $_key = ($specialchar) ? htmlspecialchars($_key, ENT_COMPAT, 'UTF-8') : $_key;
3194 $hidden_fields .= _build_hidden_fields($key . '[' . $_key . ']', $_value, $specialchar, $stripslashes);
3198 return $hidden_fields;
3202 * Build simple hidden fields from array
3204 * @param array $field_ary an array of values to build the hidden field from
3205 * @param bool $specialchar if true, keys and values get specialchared
3206 * @param bool $stripslashes if true, keys and values get stripslashed
3208 * @return string the hidden fields
3210 function build_hidden_fields($field_ary, $specialchar = false, $stripslashes = false)
3212 $s_hidden_fields = '';
3214 foreach ($field_ary as $name => $vars)
3216 $name = ($stripslashes) ? stripslashes($name) : $name;
3217 $name = ($specialchar) ? htmlspecialchars($name, ENT_COMPAT, 'UTF-8') : $name;
3219 $s_hidden_fields .= _build_hidden_fields($name, $vars, $specialchar, $stripslashes);
3222 return $s_hidden_fields;
3226 * Parse cfg file
3228 function parse_cfg_file($filename, $lines = false)
3230 $parsed_items = array();
3232 if ($lines === false)
3234 $lines = file($filename);
3237 foreach ($lines as $line)
3239 $line = trim($line);
3241 if (!$line || $line[0] == '#' || ($delim_pos = strpos($line, '=')) === false)
3243 continue;
3246 // Determine first occurrence, since in values the equal sign is allowed
3247 $key = strtolower(trim(substr($line, 0, $delim_pos)));
3248 $value = trim(substr($line, $delim_pos + 1));
3250 if (in_array($value, array('off', 'false', '0')))
3252 $value = false;
3254 else if (in_array($value, array('on', 'true', '1')))
3256 $value = true;
3258 else if (!trim($value))
3260 $value = '';
3262 else if (($value[0] == "'" && $value[sizeof($value) - 1] == "'") || ($value[0] == '"' && $value[sizeof($value) - 1] == '"'))
3264 $value = substr($value, 1, sizeof($value)-2);
3267 $parsed_items[$key] = $value;
3270 return $parsed_items;
3274 * Add log event
3276 function add_log()
3278 global $db, $user;
3280 $args = func_get_args();
3282 $mode = array_shift($args);
3283 $reportee_id = ($mode == 'user') ? intval(array_shift($args)) : '';
3284 $forum_id = ($mode == 'mod') ? intval(array_shift($args)) : '';
3285 $topic_id = ($mode == 'mod') ? intval(array_shift($args)) : '';
3286 $action = array_shift($args);
3287 $data = (!sizeof($args)) ? '' : serialize($args);
3289 $sql_ary = array(
3290 'user_id' => (empty($user->data)) ? ANONYMOUS : $user->data['user_id'],
3291 'log_ip' => $user->ip,
3292 'log_time' => time(),
3293 'log_operation' => $action,
3294 'log_data' => $data,
3297 switch ($mode)
3299 case 'admin':
3300 $sql_ary['log_type'] = LOG_ADMIN;
3301 break;
3303 case 'mod':
3304 $sql_ary += array(
3305 'log_type' => LOG_MOD,
3306 'forum_id' => $forum_id,
3307 'topic_id' => $topic_id
3309 break;
3311 case 'user':
3312 $sql_ary += array(
3313 'log_type' => LOG_USERS,
3314 'reportee_id' => $reportee_id
3316 break;
3318 case 'critical':
3319 $sql_ary['log_type'] = LOG_CRITICAL;
3320 break;
3322 default:
3323 return false;
3326 $db->sql_query('INSERT INTO ' . LOG_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
3328 return $db->sql_nextid();
3332 * Return a nicely formatted backtrace (parts from the php manual by diz at ysagoon dot com)
3334 function get_backtrace()
3336 global $phpbb_root_path;
3338 $output = '<div style="font-family: monospace;">';
3339 $backtrace = debug_backtrace();
3340 $path = phpbb_realpath($phpbb_root_path);
3342 foreach ($backtrace as $number => $trace)
3344 // We skip the first one, because it only shows this file/function
3345 if ($number == 0)
3347 continue;
3350 // Strip the current directory from path
3351 if (empty($trace['file']))
3353 $trace['file'] = '';
3355 else
3357 $trace['file'] = str_replace(array($path, '\\'), array('', '/'), $trace['file']);
3358 $trace['file'] = substr($trace['file'], 1);
3360 $args = array();
3362 // If include/require/include_once is not called, do not show arguments - they may contain sensible information
3363 if (!in_array($trace['function'], array('include', 'require', 'include_once')))
3365 unset($trace['args']);
3367 else
3369 // Path...
3370 if (!empty($trace['args'][0]))
3372 $argument = htmlspecialchars($trace['args'][0]);
3373 $argument = str_replace(array($path, '\\'), array('', '/'), $argument);
3374 $argument = substr($argument, 1);
3375 $args[] = "'{$argument}'";
3379 $trace['class'] = (!isset($trace['class'])) ? '' : $trace['class'];
3380 $trace['type'] = (!isset($trace['type'])) ? '' : $trace['type'];
3382 $output .= '<br />';
3383 $output .= '<b>FILE:</b> ' . htmlspecialchars($trace['file']) . '<br />';
3384 $output .= '<b>LINE:</b> ' . ((!empty($trace['line'])) ? $trace['line'] : '') . '<br />';
3386 $output .= '<b>CALL:</b> ' . htmlspecialchars($trace['class'] . $trace['type'] . $trace['function']) . '(' . ((sizeof($args)) ? implode(', ', $args) : '') . ')<br />';
3388 $output .= '</div>';
3389 return $output;
3393 * This function returns a regular expression pattern for commonly used expressions
3394 * Use with / as delimiter for email mode and # for url modes
3395 * mode can be: email|bbcode_htm|url|url_inline|www_url|www_url_inline|relative_url|relative_url_inline
3397 function get_preg_expression($mode)
3399 switch ($mode)
3401 case 'email':
3402 return '[a-z0-9&\'\.\-_\+]+@[a-z0-9\-]+\.([a-z0-9\-]+\.)*[a-z]+';
3403 break;
3405 case 'bbcode_htm':
3406 return array(
3407 '#<!\-\- e \-\-><a href="mailto:(.*?)">.*?</a><!\-\- e \-\->#',
3408 '#<!\-\- l \-\-><a (?:class="[\w-]+" )?href="(.*?)(?:(&amp;|\?)sid=[0-9a-f]{32})?">.*?</a><!\-\- l \-\->#',
3409 '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="(.*?)">.*?</a><!\-\- \1 \-\->#',
3410 '#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#',
3411 '#<!\-\- .*? \-\->#s',
3412 '#<.*?>#s',
3414 break;
3416 case 'url':
3417 case 'url_inline':
3418 $inline = ($mode == 'url') ? ')' : '';
3419 // generated with regex generation file in the develop folder
3420 return "[a-z][a-z\d+\-.]*:/{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})*)?";
3421 break;
3423 case 'www_url':
3424 case 'www_url_inline':
3425 $inline = ($mode == 'www_url') ? ')' : '';
3426 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})*)?";
3427 break;
3429 case 'relative_url':
3430 case 'relative_url_inline':
3431 $inline = ($mode == 'relative_url') ? ')' : '';
3432 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})*)?";
3433 break;
3436 return '';
3440 * Returns the first block of the specified IPv6 address and as many additional
3441 * ones as specified in the length paramater.
3442 * If length is zero, then an empty string is returned.
3443 * If length is greater than 3 the complete IP will be returned
3445 function short_ipv6($ip, $length)
3447 if ($length < 1)
3449 return '';
3452 // extend IPv6 addresses
3453 $blocks = substr_count($ip, ':') + 1;
3454 if ($blocks < 9)
3456 $ip = str_replace('::', ':' . str_repeat('0000:', 9 - $blocks), $ip);
3458 if ($ip[0] == ':')
3460 $ip = '0000' . $ip;
3462 if ($length < 4)
3464 $ip = implode(':', array_slice(explode(':', $ip), 0, 1 + $length));
3467 return $ip;
3471 * Truncates string while retaining special characters if going over the max length
3472 * The default max length is 60 at the moment
3474 function truncate_string($string, $max_length = 60, $allow_reply = true, $append = '')
3476 $chars = array();
3478 $strip_reply = false;
3479 $stripped = false;
3480 if ($allow_reply && strpos($string, 'Re: ') === 0)
3482 $strip_reply = true;
3483 $string = substr($string, 4);
3486 $_chars = utf8_str_split(htmlspecialchars_decode($string));
3487 $chars = array_map('htmlspecialchars', $_chars);
3489 // Now check the length ;)
3490 if (sizeof($chars) > $max_length)
3492 // Cut off the last elements from the array
3493 $string = implode('', array_slice($chars, 0, $max_length));
3494 $stripped = true;
3497 if ($strip_reply)
3499 $string = 'Re: ' . $string;
3502 if ($append != '' && $stripped)
3504 $string = $string . $append;
3507 return $string;
3511 * Get username details for placing into templates.
3513 * @param string $mode Can be profile (for getting an url to the profile), username (for obtaining the username), colour (for obtaining the user colour) or full (for obtaining a html string representing a coloured link to the users profile).
3514 * @param int $user_id The users id
3515 * @param string $username The users name
3516 * @param string $username_colour The users colour
3517 * @param string $guest_username optional parameter to specify the guest username. It will be used in favor of the GUEST language variable then.
3518 * @param string $custom_profile_url optional parameter to specify a profile url. The user id get appended to this url as &amp;u={user_id}
3520 * @return string A string consisting of what is wanted based on $mode.
3522 function get_username_string($mode, $user_id, $username, $username_colour = '', $guest_username = false, $custom_profile_url = false)
3524 global $phpbb_root_path, $phpEx, $user, $auth;
3526 $profile_url = '';
3527 $username_colour = ($username_colour) ? '#' . $username_colour : '';
3529 if ($guest_username === false)
3531 $username = ($username) ? $username : $user->lang['GUEST'];
3533 else
3535 $username = ($user_id && $user_id != ANONYMOUS) ? $username : ((!empty($guest_username)) ? $guest_username : $user->lang['GUEST']);
3538 // Only show the link if not anonymous
3539 if ($user_id && $user_id != ANONYMOUS)
3541 // Do not show the link if the user is already logged in but do not have u_viewprofile permissions (relevant for bots mostly).
3542 // For all others the link leads to a login page or the profile.
3543 if ($user->data['user_id'] != ANONYMOUS && !$auth->acl_get('u_viewprofile'))
3545 $profile_url = '';
3547 else
3549 $profile_url = ($custom_profile_url !== false) ? $custom_profile_url : append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=viewprofile');
3550 $profile_url .= '&amp;u=' . (int) $user_id;
3553 else
3555 $profile_url = '';
3558 switch ($mode)
3560 case 'profile':
3561 return $profile_url;
3562 break;
3564 case 'username':
3565 return $username;
3566 break;
3568 case 'colour':
3569 return $username_colour;
3570 break;
3572 case 'full':
3573 default:
3575 $tpl = '';
3576 if (!$profile_url && !$username_colour)
3578 $tpl = '{USERNAME}';
3580 else if (!$profile_url && $username_colour)
3582 $tpl = '<span style="color: {USERNAME_COLOUR};" class="username-coloured">{USERNAME}</span>';
3584 else if ($profile_url && !$username_colour)
3586 $tpl = '<a href="{PROFILE_URL}">{USERNAME}</a>';
3588 else if ($profile_url && $username_colour)
3590 $tpl = '<a href="{PROFILE_URL}" style="color: {USERNAME_COLOUR};" class="username-coloured">{USERNAME}</a>';
3593 return str_replace(array('{PROFILE_URL}', '{USERNAME_COLOUR}', '{USERNAME}'), array($profile_url, $username_colour, $username), $tpl);
3594 break;
3599 * Wrapper for php's checkdnsrr function.
3601 * The windows failover is from the php manual
3602 * Please make sure to check the return value for === true and === false, since NULL could
3603 * be returned too.
3605 * @return true if entry found, false if not, NULL if this function is not supported by this environment
3607 function phpbb_checkdnsrr($host, $type = '')
3609 $type = (!$type) ? 'MX' : $type;
3611 if (DIRECTORY_SEPARATOR == '\\')
3613 if (!function_exists('exec'))
3615 return NULL;
3618 @exec('nslookup -type=' . escapeshellarg($type) . ' ' . escapeshellarg($host), $output);
3620 // If output is empty, the nslookup failed
3621 if (empty($output))
3623 return NULL;
3626 foreach ($output as $line)
3628 if (!trim($line))
3630 continue;
3633 // Valid records begin with host name:
3634 if (strpos($line, $host) === 0)
3636 return true;
3640 return false;
3642 else if (function_exists('checkdnsrr'))
3644 return (checkdnsrr($host, $type)) ? true : false;
3647 return NULL;
3650 // Handler, header and footer
3653 * Error and message handler, call with trigger_error if reqd
3655 function msg_handler($errno, $msg_text, $errfile, $errline)
3657 global $cache, $db, $auth, $template, $config, $user;
3658 global $phpEx, $phpbb_root_path, $msg_title, $msg_long_text;
3660 // Message handler is stripping text. In case we need it, we are possible to define long text...
3661 if (isset($msg_long_text) && $msg_long_text && !$msg_text)
3663 $msg_text = $msg_long_text;
3666 switch ($errno)
3668 case E_NOTICE:
3669 case E_WARNING:
3671 // Check the error reporting level and return if the error level does not match
3672 // Additionally do not display notices if we suppress them via @
3673 // If DEBUG is defined the default level is E_ALL
3674 if (($errno & ((defined('DEBUG') && error_reporting()) ? E_ALL : error_reporting())) == 0)
3676 return;
3679 if (strpos($errfile, 'cache') === false && strpos($errfile, 'template.') === false)
3681 // remove complete path to installation, with the risk of changing backslashes meant to be there
3682 $errfile = str_replace(array(phpbb_realpath($phpbb_root_path), '\\'), array('', '/'), $errfile);
3683 $msg_text = str_replace(array(phpbb_realpath($phpbb_root_path), '\\'), array('', '/'), $msg_text);
3685 echo '<b>[phpBB Debug] PHP Notice</b>: in file <b>' . $errfile . '</b> on line <b>' . $errline . '</b>: <b>' . $msg_text . '</b><br />' . "\n";
3688 return;
3690 break;
3692 case E_USER_ERROR:
3694 if (!empty($user) && !empty($user->lang))
3696 $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
3697 $msg_title = (!isset($msg_title)) ? $user->lang['GENERAL_ERROR'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
3699 $l_return_index = sprintf($user->lang['RETURN_INDEX'], '<a href="' . $phpbb_root_path . '">', '</a>');
3700 $l_notify = '';
3702 if (!empty($config['board_contact']))
3704 $l_notify = '<p>' . sprintf($user->lang['NOTIFY_ADMIN_EMAIL'], $config['board_contact']) . '</p>';
3707 else
3709 $msg_title = 'General Error';
3710 $l_return_index = '<a href="' . $phpbb_root_path . '">Return to index page</a>';
3711 $l_notify = '';
3713 if (!empty($config['board_contact']))
3715 $l_notify = '<p>Please notify the board administrator or webmaster: <a href="mailto:' . $config['board_contact'] . '">' . $config['board_contact'] . '</a></p>';
3719 garbage_collection();
3721 // Try to not call the adm page data...
3723 echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
3724 echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">';
3725 echo '<head>';
3726 echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
3727 echo '<title>' . $msg_title . '</title>';
3728 echo '<style type="text/css">' . "\n" . '<!--' . "\n";
3729 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; } ';
3730 echo 'a:link, a:active, a:visited { color: #006699; text-decoration: none; } a:hover { color: #DD6900; text-decoration: underline; } ';
3731 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; } ';
3732 echo '.panel { margin: 4px 0; background-color: #FFFFFF; border: solid 1px #A9B8C2; } ';
3733 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; } ';
3734 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; } ';
3735 echo "\n" . '//-->' . "\n";
3736 echo '</style>';
3737 echo '</head>';
3738 echo '<body id="errorpage">';
3739 echo '<div id="wrap">';
3740 echo ' <div id="page-header">';
3741 echo ' ' . $l_return_index;
3742 echo ' </div>';
3743 echo ' <div id="acp">';
3744 echo ' <div class="panel">';
3745 echo ' <div id="content">';
3746 echo ' <h1>' . $msg_title . '</h1>';
3748 echo ' <div>' . $msg_text . '</div>';
3750 echo $l_notify;
3752 echo ' </div>';
3753 echo ' </div>';
3754 echo ' </div>';
3755 echo ' <div id="page-footer">';
3756 echo ' Powered by phpBB &copy; 2000, 2002, 2005, 2007 <a href="http://www.phpbb.com/">phpBB Group</a>';
3757 echo ' </div>';
3758 echo '</div>';
3759 echo '</body>';
3760 echo '</html>';
3762 exit;
3763 break;
3765 case E_USER_WARNING:
3766 case E_USER_NOTICE:
3768 define('IN_ERROR_HANDLER', true);
3770 if (empty($user->data))
3772 $user->session_begin();
3775 // We re-init the auth array to get correct results on login/logout
3776 $auth->acl($user->data);
3778 if (empty($user->lang))
3780 $user->setup();
3783 $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
3784 $msg_title = (!isset($msg_title)) ? $user->lang['INFORMATION'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
3786 if (!defined('HEADER_INC'))
3788 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
3790 adm_page_header($msg_title);
3792 else
3794 page_header($msg_title);
3798 $template->set_filenames(array(
3799 'body' => 'message_body.html')
3802 $template->assign_vars(array(
3803 'MESSAGE_TITLE' => $msg_title,
3804 'MESSAGE_TEXT' => $msg_text,
3805 'S_USER_WARNING' => ($errno == E_USER_WARNING) ? true : false,
3806 'S_USER_NOTICE' => ($errno == E_USER_NOTICE) ? true : false)
3809 // We do not want the cron script to be called on error messages
3810 define('IN_CRON', true);
3812 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
3814 adm_page_footer();
3816 else
3818 page_footer();
3821 exit;
3822 break;
3825 // If we notice an error not handled here we pass this back to PHP by returning false
3826 // This may not work for all php versions
3827 return false;
3831 * Generate page header
3833 function page_header($page_title = '', $display_online_list = true)
3835 global $db, $config, $template, $SID, $_SID, $user, $auth, $phpEx, $phpbb_root_path;
3837 if (defined('HEADER_INC'))
3839 return;
3842 define('HEADER_INC', true);
3844 // gzip_compression
3845 if ($config['gzip_compress'])
3847 if (@extension_loaded('zlib') && !headers_sent())
3849 ob_start('ob_gzhandler');
3853 // Generate logged in/logged out status
3854 if ($user->data['user_id'] != ANONYMOUS)
3856 $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=logout', true, $user->session_id);
3857 $l_login_logout = sprintf($user->lang['LOGOUT_USER'], $user->data['username']);
3859 else
3861 $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login');
3862 $l_login_logout = $user->lang['LOGIN'];
3865 // Last visit date/time
3866 $s_last_visit = ($user->data['user_id'] != ANONYMOUS) ? $user->format_date($user->data['session_last_visit']) : '';
3868 // Get users online list ... if required
3869 $l_online_users = $online_userlist = $l_online_record = '';
3871 if ($config['load_online'] && $config['load_online_time'] && $display_online_list)
3873 $logged_visible_online = $logged_hidden_online = $guests_online = $prev_user_id = 0;
3874 $prev_session_ip = $reading_sql = '';
3876 if (!empty($_REQUEST['f']))
3878 $f = request_var('f', 0);
3880 $reading_sql = ' AND s.session_page ' . $db->sql_like_expression("{$db->any_char}_f_={$f}x{$db->any_char}");
3883 // Get number of online guests
3884 if (!$config['load_online_guests'])
3886 if ($db->sql_layer === 'sqlite')
3888 $sql = 'SELECT COUNT(session_ip) as num_guests
3889 FROM (
3890 SELECT DISTINCT s.session_ip
3891 FROM ' . SESSIONS_TABLE . ' s
3892 WHERE s.session_user_id = ' . ANONYMOUS . '
3893 AND s.session_time >= ' . (time() - ($config['load_online_time'] * 60)) .
3894 $reading_sql .
3895 ')';
3897 else
3899 $sql = 'SELECT COUNT(DISTINCT s.session_ip) as num_guests
3900 FROM ' . SESSIONS_TABLE . ' s
3901 WHERE s.session_user_id = ' . ANONYMOUS . '
3902 AND s.session_time >= ' . (time() - ($config['load_online_time'] * 60)) .
3903 $reading_sql;
3905 $result = $db->sql_query($sql);
3906 $guests_online = (int) $db->sql_fetchfield('num_guests');
3907 $db->sql_freeresult($result);
3910 $sql = 'SELECT u.username, u.username_clean, u.user_id, u.user_type, u.user_allow_viewonline, u.user_colour, s.session_ip, s.session_viewonline
3911 FROM ' . USERS_TABLE . ' u, ' . SESSIONS_TABLE . ' s
3912 WHERE s.session_time >= ' . (time() - (intval($config['load_online_time']) * 60)) .
3913 $reading_sql .
3914 ((!$config['load_online_guests']) ? ' AND s.session_user_id <> ' . ANONYMOUS : '') . '
3915 AND u.user_id = s.session_user_id
3916 ORDER BY u.username_clean ASC, s.session_ip ASC';
3917 $result = $db->sql_query($sql);
3919 while ($row = $db->sql_fetchrow($result))
3921 // User is logged in and therefore not a guest
3922 if ($row['user_id'] != ANONYMOUS)
3924 // Skip multiple sessions for one user
3925 if ($row['user_id'] != $prev_user_id)
3927 if ($row['user_colour'])
3929 $user_colour = ' style="color:#' . $row['user_colour'] . '"';
3930 $row['username'] = '<strong>' . $row['username'] . '</strong>';
3932 else
3934 $user_colour = '';
3937 if ($row['session_viewonline'])
3939 $user_online_link = $row['username'];
3940 $logged_visible_online++;
3942 else
3944 $user_online_link = '<em>' . $row['username'] . '</em>';
3945 $logged_hidden_online++;
3948 if (($row['session_viewonline']) || $auth->acl_get('u_viewonline'))
3950 if ($row['user_type'] <> USER_IGNORE)
3952 $user_online_link = '<a href="' . append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=viewprofile&amp;u=' . $row['user_id']) . '"' . $user_colour . '>' . $user_online_link . '</a>';
3954 else
3956 $user_online_link = ($user_colour) ? '<span' . $user_colour . '>' . $user_online_link . '</span>' : $user_online_link;
3959 $online_userlist .= ($online_userlist != '') ? ', ' . $user_online_link : $user_online_link;
3963 $prev_user_id = $row['user_id'];
3965 else
3967 // Skip multiple sessions for one user
3968 if ($row['session_ip'] != $prev_session_ip)
3970 $guests_online++;
3974 $prev_session_ip = $row['session_ip'];
3976 $db->sql_freeresult($result);
3978 if (!$online_userlist)
3980 $online_userlist = $user->lang['NO_ONLINE_USERS'];
3983 if (empty($_REQUEST['f']))
3985 $online_userlist = $user->lang['REGISTERED_USERS'] . ' ' . $online_userlist;
3987 else
3989 $l_online = ($guests_online == 1) ? $user->lang['BROWSING_FORUM_GUEST'] : $user->lang['BROWSING_FORUM_GUESTS'];
3990 $online_userlist = sprintf($l_online, $online_userlist, $guests_online);
3993 $total_online_users = $logged_visible_online + $logged_hidden_online + $guests_online;
3995 if ($total_online_users > $config['record_online_users'])
3997 set_config('record_online_users', $total_online_users, true);
3998 set_config('record_online_date', time(), true);
4001 // Build online listing
4002 $vars_online = array(
4003 'ONLINE' => array('total_online_users', 'l_t_user_s'),
4004 'REG' => array('logged_visible_online', 'l_r_user_s'),
4005 'HIDDEN' => array('logged_hidden_online', 'l_h_user_s'),
4006 'GUEST' => array('guests_online', 'l_g_user_s')
4009 foreach ($vars_online as $l_prefix => $var_ary)
4011 switch (${$var_ary[0]})
4013 case 0:
4014 ${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_ZERO_TOTAL'];
4015 break;
4017 case 1:
4018 ${$var_ary[1]} = $user->lang[$l_prefix . '_USER_TOTAL'];
4019 break;
4021 default:
4022 ${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_TOTAL'];
4023 break;
4026 unset($vars_online);
4028 $l_online_users = sprintf($l_t_user_s, $total_online_users);
4029 $l_online_users .= sprintf($l_r_user_s, $logged_visible_online);
4030 $l_online_users .= sprintf($l_h_user_s, $logged_hidden_online);
4031 $l_online_users .= sprintf($l_g_user_s, $guests_online);
4033 $l_online_record = sprintf($user->lang['RECORD_ONLINE_USERS'], $config['record_online_users'], $user->format_date($config['record_online_date']));
4035 $l_online_time = ($config['load_online_time'] == 1) ? 'VIEW_ONLINE_TIME' : 'VIEW_ONLINE_TIMES';
4036 $l_online_time = sprintf($user->lang[$l_online_time], $config['load_online_time']);
4038 else
4040 $l_online_time = '';
4043 $l_privmsgs_text = $l_privmsgs_text_unread = '';
4044 $s_privmsg_new = false;
4046 // Obtain number of new private messages if user is logged in
4047 if (isset($user->data['is_registered']) && $user->data['is_registered'])
4049 if ($user->data['user_new_privmsg'])
4051 $l_message_new = ($user->data['user_new_privmsg'] == 1) ? $user->lang['NEW_PM'] : $user->lang['NEW_PMS'];
4052 $l_privmsgs_text = sprintf($l_message_new, $user->data['user_new_privmsg']);
4054 if (!$user->data['user_last_privmsg'] || $user->data['user_last_privmsg'] > $user->data['session_last_visit'])
4056 $sql = 'UPDATE ' . USERS_TABLE . '
4057 SET user_last_privmsg = ' . $user->data['session_last_visit'] . '
4058 WHERE user_id = ' . $user->data['user_id'];
4059 $db->sql_query($sql);
4061 $s_privmsg_new = true;
4063 else
4065 $s_privmsg_new = false;
4068 else
4070 $l_privmsgs_text = $user->lang['NO_NEW_PM'];
4071 $s_privmsg_new = false;
4074 $l_privmsgs_text_unread = '';
4076 if ($user->data['user_unread_privmsg'] && $user->data['user_unread_privmsg'] != $user->data['user_new_privmsg'])
4078 $l_message_unread = ($user->data['user_unread_privmsg'] == 1) ? $user->lang['UNREAD_PM'] : $user->lang['UNREAD_PMS'];
4079 $l_privmsgs_text_unread = sprintf($l_message_unread, $user->data['user_unread_privmsg']);
4083 // Which timezone?
4084 $tz = ($user->data['user_id'] != ANONYMOUS) ? strval(doubleval($user->data['user_timezone'])) : strval(doubleval($config['board_timezone']));
4086 // The following assigns all _common_ variables that may be used at any point in a template.
4087 $template->assign_vars(array(
4088 'SITENAME' => $config['sitename'],
4089 'SITE_DESCRIPTION' => $config['site_desc'],
4090 'PAGE_TITLE' => $page_title,
4091 'SCRIPT_NAME' => str_replace('.' . $phpEx, '', $user->page['page_name']),
4092 'LAST_VISIT_DATE' => sprintf($user->lang['YOU_LAST_VISIT'], $s_last_visit),
4093 'LAST_VISIT_YOU' => $s_last_visit,
4094 'CURRENT_TIME' => sprintf($user->lang['CURRENT_TIME'], $user->format_date(time(), false, true)),
4095 'TOTAL_USERS_ONLINE' => $l_online_users,
4096 'LOGGED_IN_USER_LIST' => $online_userlist,
4097 'RECORD_USERS' => $l_online_record,
4098 'PRIVATE_MESSAGE_INFO' => $l_privmsgs_text,
4099 'PRIVATE_MESSAGE_INFO_UNREAD' => $l_privmsgs_text_unread,
4101 'S_USER_NEW_PRIVMSG' => $user->data['user_new_privmsg'],
4102 'S_USER_UNREAD_PRIVMSG' => $user->data['user_unread_privmsg'],
4104 'SID' => $SID,
4105 '_SID' => $_SID,
4106 'SESSION_ID' => $user->session_id,
4107 'ROOT_PATH' => $phpbb_root_path,
4109 'L_LOGIN_LOGOUT' => $l_login_logout,
4110 'L_INDEX' => $user->lang['FORUM_INDEX'],
4111 'L_ONLINE_EXPLAIN' => $l_online_time,
4113 'U_PRIVATEMSGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
4114 'U_RETURN_INBOX' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
4115 'UA_RETURN_INBOX' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox', false),
4116 'U_POPUP_PM' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;mode=popup'),
4117 'UA_POPUP_PM' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=popup', false),
4118 'U_MEMBERLIST' => append_sid("{$phpbb_root_path}memberlist.$phpEx"),
4119 'U_MEMBERSLIST' => append_sid("{$phpbb_root_path}memberlist.$phpEx"),
4120 'U_VIEWONLINE' => append_sid("{$phpbb_root_path}viewonline.$phpEx"),
4121 'U_LOGIN_LOGOUT' => $u_login_logout,
4122 'U_INDEX' => append_sid("{$phpbb_root_path}index.$phpEx"),
4123 'U_SEARCH' => append_sid("{$phpbb_root_path}search.$phpEx"),
4124 'U_REGISTER' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'),
4125 'U_PROFILE' => append_sid("{$phpbb_root_path}ucp.$phpEx"),
4126 'U_MODCP' => append_sid("{$phpbb_root_path}mcp.$phpEx", false, true, $user->session_id),
4127 'U_FAQ' => append_sid("{$phpbb_root_path}faq.$phpEx"),
4128 'U_SEARCH_SELF' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=egosearch'),
4129 'U_SEARCH_NEW' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=newposts'),
4130 'U_SEARCH_UNANSWERED' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unanswered'),
4131 'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'),
4132 'U_DELETE_COOKIES' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=delete_cookies'),
4133 'U_TEAM' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=leaders'),
4134 'U_RESTORE_PERMISSIONS' => ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm') : '',
4136 'S_USER_LOGGED_IN' => ($user->data['user_id'] != ANONYMOUS) ? true : false,
4137 'S_BOARD_DISABLED' => ($config['board_disable']) ? true : false,
4138 'S_REGISTERED_USER' => $user->data['is_registered'],
4139 'S_IS_BOT' => $user->data['is_bot'],
4140 'S_USER_PM_POPUP' => $user->optionget('popuppm'),
4141 'S_USER_LANG' => $user->lang['USER_LANG'],
4142 'S_USER_BROWSER' => (isset($user->data['session_browser'])) ? $user->data['session_browser'] : $user->lang['UNKNOWN_BROWSER'],
4143 'S_USERNAME' => $user->data['username'],
4144 'S_CONTENT_DIRECTION' => $user->lang['DIRECTION'],
4145 'S_CONTENT_FLOW_BEGIN' => ($user->lang['DIRECTION'] == 'ltr') ? 'left' : 'right',
4146 'S_CONTENT_FLOW_END' => ($user->lang['DIRECTION'] == 'ltr') ? 'right' : 'left',
4147 'S_CONTENT_ENCODING' => 'UTF-8',
4148 '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], ''),
4149 'S_DISPLAY_ONLINE_LIST' => ($l_online_time) ? 1 : 0,
4150 'S_DISPLAY_SEARCH' => (!$config['load_search']) ? 0 : (isset($auth) ? ($auth->acl_get('u_search') && $auth->acl_getf_global('f_search')) : 1),
4151 'S_DISPLAY_PM' => ($config['allow_privmsg'] && $user->data['is_registered'] && ($auth->acl_get('u_readpm') || $auth->acl_get('u_sendpm'))) ? true : false,
4152 'S_DISPLAY_MEMBERLIST' => (isset($auth)) ? $auth->acl_get('u_viewprofile') : 0,
4153 'S_NEW_PM' => ($s_privmsg_new) ? 1 : 0,
4155 'T_THEME_PATH' => "{$phpbb_root_path}styles/" . $user->theme['theme_path'] . '/theme',
4156 'T_TEMPLATE_PATH' => "{$phpbb_root_path}styles/" . $user->theme['template_path'] . '/template',
4157 'T_IMAGESET_PATH' => "{$phpbb_root_path}styles/" . $user->theme['imageset_path'] . '/imageset',
4158 'T_IMAGESET_LANG_PATH' => "{$phpbb_root_path}styles/" . $user->theme['imageset_path'] . '/imageset/' . $user->data['user_lang'],
4159 'T_IMAGES_PATH' => "{$phpbb_root_path}images/",
4160 'T_SMILIES_PATH' => "{$phpbb_root_path}{$config['smilies_path']}/",
4161 'T_AVATAR_PATH' => "{$phpbb_root_path}{$config['avatar_path']}/",
4162 'T_AVATAR_GALLERY_PATH' => "{$phpbb_root_path}{$config['avatar_gallery_path']}/",
4163 'T_ICONS_PATH' => "{$phpbb_root_path}{$config['icons_path']}/",
4164 'T_RANKS_PATH' => "{$phpbb_root_path}{$config['ranks_path']}/",
4165 'T_UPLOAD_PATH' => "{$phpbb_root_path}{$config['upload_path']}/",
4166 'T_STYLESHEET_LINK' => (!$user->theme['theme_storedb']) ? "{$phpbb_root_path}styles/" . $user->theme['theme_path'] . '/theme/stylesheet.css' : "{$phpbb_root_path}style.$phpEx?sid=$user->session_id&amp;id=" . $user->theme['style_id'] . '&amp;lang=' . $user->data['user_lang'],
4167 'T_STYLESHEET_NAME' => $user->theme['theme_name'],
4169 'SITE_LOGO_IMG' => $user->img('site_logo'))
4172 // Once used, we do not want to have the whole theme data twice in memory...
4173 if ($user->theme['theme_storedb'])
4175 // Parse Theme Data
4176 $replace = array(
4177 '{T_THEME_PATH}' => "{$phpbb_root_path}styles/" . $user->theme['theme_path'] . '/theme',
4178 '{T_TEMPLATE_PATH}' => "{$phpbb_root_path}styles/" . $user->theme['template_path'] . '/template',
4179 '{T_IMAGESET_PATH}' => "{$phpbb_root_path}styles/" . $user->theme['imageset_path'] . '/imageset',
4180 '{T_IMAGESET_LANG_PATH}' => "{$phpbb_root_path}styles/" . $user->theme['imageset_path'] . '/imageset/' . $user->data['user_lang'],
4181 '{T_STYLESHEET_NAME}' => $user->theme['theme_name'],
4182 '{S_USER_LANG}' => $user->data['user_lang']
4185 $user->theme['theme_data'] = str_replace(array_keys($replace), array_values($replace), $user->theme['theme_data']);
4187 $matches = array();
4188 if (strpos($user->theme['theme_data'], '{IMG_') !== false)
4190 preg_match_all('#\{IMG_([A-Za-z0-9_]*?)_(WIDTH|HEIGHT|SRC)\}#', $user->theme['theme_data'], $matches);
4192 $imgs = $find = $replace = array();
4193 if (isset($matches[0]) && sizeof($matches[0]))
4195 foreach ($matches[1] as $i => $img)
4197 $img = strtolower($img);
4198 if (!isset($img_array[$img]))
4200 continue;
4203 if (!isset($imgs[$img]))
4205 $img_data = &$img_array[$img];
4206 $imgsrc = ($img_data['image_lang'] ? $img_data['image_lang'] . '/' : '') . $img_data['image_filename'];
4207 $imgs[$img] = array(
4208 'src' => $phpbb_root_path . 'styles/' . $user->theme['imageset_path'] . '/imageset/' . $imgsrc,
4209 'width' => $img_data['image_width'],
4210 'height' => $img_data['image_height'],
4214 switch ($matches[2][$i])
4216 case 'SRC':
4217 $replace[] = $imgs[$img]['src'];
4218 break;
4220 case 'WIDTH':
4221 $replace[] = $imgs[$img]['width'];
4222 break;
4224 case 'HEIGHT':
4225 $replace[] = $imgs[$img]['height'];
4226 break;
4228 default:
4229 continue;
4231 $find[] = $matches[0][$i];
4234 if (sizeof($find))
4236 $user->theme['theme_data'] = str_replace($find, $replace, $user->theme['theme_data']);
4241 $template->assign_var('T_THEME_DATA', $user->theme['theme_data']);
4242 $user->theme['theme_data'] = '';
4245 // application/xhtml+xml not used because of IE
4246 header('Content-type: text/html; charset=UTF-8');
4248 header('Cache-Control: private, no-cache="set-cookie"');
4249 header('Expires: 0');
4250 header('Pragma: no-cache');
4252 return;
4256 * Generate page footer
4258 function page_footer($run_cron = true)
4260 global $db, $config, $template, $user, $auth, $cache, $starttime, $phpbb_root_path, $phpEx;
4262 // Output page creation time
4263 if (defined('DEBUG'))
4265 $mtime = explode(' ', microtime());
4266 $totaltime = $mtime[0] + $mtime[1] - $starttime;
4268 if (!empty($_REQUEST['explain']) && $auth->acl_get('a_') && defined('DEBUG_EXTRA') && method_exists($db, 'sql_report'))
4270 $db->sql_report('display');
4273 $debug_output = sprintf('Time : %.3fs | ' . $db->sql_num_queries() . ' Queries | GZIP : ' . (($config['gzip_compress']) ? 'On' : 'Off') . (($user->load) ? ' | Load : ' . $user->load : ''), $totaltime);
4275 if ($auth->acl_get('a_') && defined('DEBUG_EXTRA'))
4277 if (function_exists('memory_get_usage'))
4279 if ($memory_usage = memory_get_usage())
4281 global $base_memory_usage;
4282 $memory_usage -= $base_memory_usage;
4283 $memory_usage = ($memory_usage >= 1048576) ? round((round($memory_usage / 1048576 * 100) / 100), 2) . ' ' . $user->lang['MB'] : (($memory_usage >= 1024) ? round((round($memory_usage / 1024 * 100) / 100), 2) . ' ' . $user->lang['KB'] : $memory_usage . ' ' . $user->lang['BYTES']);
4285 $debug_output .= ' | Memory Usage: ' . $memory_usage;
4289 $debug_output .= ' | <a href="' . build_url() . '&amp;explain=1">Explain</a>';
4293 $template->assign_vars(array(
4294 'DEBUG_OUTPUT' => (defined('DEBUG')) ? $debug_output : '',
4295 'TRANSLATION_INFO' => (!empty($user->lang['TRANSLATION_INFO'])) ? $user->lang['TRANSLATION_INFO'] : '',
4297 'U_ACP' => ($auth->acl_get('a_') && $user->data['is_registered']) ? append_sid("{$phpbb_root_path}adm/index.$phpEx", '', true, $user->session_id) : '')
4300 // Call cron-type script
4301 if (!defined('IN_CRON') && $run_cron)
4303 $cron_type = '';
4305 if (time() - $config['queue_interval'] > $config['last_queue_run'] && !defined('IN_ADMIN') && file_exists($phpbb_root_path . 'cache/queue.' . $phpEx))
4307 // Process email queue
4308 $cron_type = 'queue';
4310 else if (method_exists($cache, 'tidy') && time() - $config['cache_gc'] > $config['cache_last_gc'])
4312 // Tidy the cache
4313 $cron_type = 'tidy_cache';
4315 else if (time() - $config['warnings_gc'] > $config['warnings_last_gc'])
4317 $cron_type = 'tidy_warnings';
4319 else if (time() - $config['database_gc'] > $config['database_last_gc'])
4321 // Tidy the database
4322 $cron_type = 'tidy_database';
4324 else if (time() - $config['search_gc'] > $config['search_last_gc'])
4326 // Tidy the search
4327 $cron_type = 'tidy_search';
4329 else if (time() - $config['session_gc'] > $config['session_last_gc'])
4331 $cron_type = 'tidy_sessions';
4334 if ($cron_type)
4336 $template->assign_var('RUN_CRON_TASK', '<img src="' . append_sid($phpbb_root_path . 'cron.' . $phpEx, 'cron_type=' . $cron_type) . '" width="1" height="1" alt="cron" />');
4340 $template->display('body');
4342 garbage_collection();
4344 exit;
4348 * Closing the cache object and the database
4349 * Cool function name, eh? We might want to add operations to it later
4351 function garbage_collection()
4353 global $cache, $db;
4355 // Unload cache, must be done before the DB connection if closed
4356 if (!empty($cache))
4358 $cache->unload();
4361 // Close our DB connection.
4362 if (!empty($db))
4364 $db->sql_close();
4369 * @package phpBB3
4371 class bitfield
4373 var $data;
4375 function bitfield($bitfield = '')
4377 $this->data = base64_decode($bitfield);
4382 function get($n)
4384 // Get the ($n / 8)th char
4385 $byte = $n >> 3;
4387 if (strlen($this->data) >= $byte + 1)
4389 $c = $this->data[$byte];
4391 // Lookup the ($n % 8)th bit of the byte
4392 $bit = 7 - ($n & 7);
4393 return (bool) (ord($c) & (1 << $bit));
4395 else
4397 return false;
4401 function set($n)
4403 $byte = $n >> 3;
4404 $bit = 7 - ($n & 7);
4406 if (strlen($this->data) >= $byte + 1)
4408 $this->data[$byte] = $this->data[$byte] | chr(1 << $bit);
4410 else
4412 $this->data .= str_repeat("\0", $byte - strlen($this->data));
4413 $this->data .= chr(1 << $bit);
4417 function clear($n)
4419 $byte = $n >> 3;
4421 if (strlen($this->data) >= $byte + 1)
4423 $bit = 7 - ($n & 7);
4424 $this->data[$byte] = $this->data[$byte] &~ chr(1 << $bit);
4428 function get_blob()
4430 return $this->data;
4433 function get_base64()
4435 return base64_encode($this->data);
4438 function get_bin()
4440 $bin = '';
4441 $len = strlen($this->data);
4443 for ($i = 0; $i < $len; ++$i)
4445 $bin .= str_pad(decbin(ord($this->data[$i])), 8, '0', STR_PAD_LEFT);
4448 return $bin;
4451 function get_all_set()
4453 return array_keys(array_filter(str_split($this->get_bin())));
4456 function merge($bitfield)
4458 $this->data = $this->data | $bitfield->get_blob();