time(), ); // #1 latest member ID, #2 the real name for a new registration. if (is_numeric($parameter1)) { $changes['latestMember'] = $parameter1; $changes['latestRealName'] = $parameter2; updateSettings(array('totalMembers' => true), true); } // We need to calculate the totals. else { // Update the latest activated member (highest id_member) and count. $result = $smcFunc['db_query']('', ' SELECT COUNT(*), MAX(id_member) FROM {db_prefix}members WHERE is_activated = {int:is_activated}', array( 'is_activated' => 1, ) ); list ($changes['totalMembers'], $changes['latestMember']) = $smcFunc['db_fetch_row']($result); $smcFunc['db_free_result']($result); // Get the latest activated member's display name. $result = $smcFunc['db_query']('', ' SELECT real_name FROM {db_prefix}members WHERE id_member = {int:id_member} LIMIT 1', array( 'id_member' => (int) $changes['latestMember'], ) ); list ($changes['latestRealName']) = $smcFunc['db_fetch_row']($result); $smcFunc['db_free_result']($result); // Are we using registration approval? if ((!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($modSettings['approveAccountDeletion'])) { // Update the amount of members awaiting approval - ignoring COPPA accounts, as you can't approve them until you get permission. $result = $smcFunc['db_query']('', ' SELECT COUNT(*) FROM {db_prefix}members WHERE is_activated IN ({array_int:activation_status})', array( 'activation_status' => array(3, 4), ) ); list ($changes['unapprovedMembers']) = $smcFunc['db_fetch_row']($result); $smcFunc['db_free_result']($result); } } updateSettings($changes); break; case 'message': if ($parameter1 === true && $parameter2 !== null) updateSettings(array('totalMessages' => true, 'maxMsgID' => $parameter2), true); else { // SUM and MAX on a smaller table is better for InnoDB tables. $result = $smcFunc['db_query']('', ' SELECT SUM(num_posts + unapproved_posts) AS total_messages, MAX(id_last_msg) AS max_msg_id FROM {db_prefix}boards WHERE redirect = {string:blank_redirect}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' AND id_board != {int:recycle_board}' : ''), array( 'recycle_board' => isset($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0, 'blank_redirect' => '', ) ); $row = $smcFunc['db_fetch_assoc']($result); $smcFunc['db_free_result']($result); updateSettings(array( 'totalMessages' => $row['total_messages'] === null ? 0 : $row['total_messages'], 'maxMsgID' => $row['max_msg_id'] === null ? 0 : $row['max_msg_id'] )); } break; case 'subject': // Remove the previous subject (if any). $smcFunc['db_query']('', ' DELETE FROM {db_prefix}log_search_subjects WHERE id_topic = {int:id_topic}', array( 'id_topic' => (int) $parameter1, ) ); // Insert the new subject. if ($parameter2 !== null) { $parameter1 = (int) $parameter1; $parameter2 = text2words($parameter2); $inserts = array(); foreach ($parameter2 as $word) $inserts[] = array($word, $parameter1); if (!empty($inserts)) $smcFunc['db_insert']('ignore', '{db_prefix}log_search_subjects', array('word' => 'string', 'id_topic' => 'int'), $inserts, array('word', 'id_topic') ); } break; case 'topic': if ($parameter1 === true) updateSettings(array('totalTopics' => true), true); else { // Get the number of topics - a SUM is better for InnoDB tables. // We also ignore the recycle bin here because there will probably be a bunch of one-post topics there. $result = $smcFunc['db_query']('', ' SELECT SUM(num_topics + unapproved_topics) AS total_topics FROM {db_prefix}boards' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' WHERE id_board != {int:recycle_board}' : ''), array( 'recycle_board' => !empty($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0, ) ); $row = $smcFunc['db_fetch_assoc']($result); $smcFunc['db_free_result']($result); updateSettings(array('totalTopics' => $row['total_topics'] === null ? 0 : $row['total_topics'])); } break; case 'postgroups': // Parameter two is the updated columns: we should check to see if we base groups off any of these. if ($parameter2 !== null && !in_array('posts', $parameter2)) return; if (($postgroups = cache_get_data('updateStats:postgroups', 360)) == null) { // Fetch the postgroups! $request = $smcFunc['db_query']('', ' SELECT id_group, min_posts FROM {db_prefix}membergroups WHERE min_posts != {int:min_posts}', array( 'min_posts' => -1, ) ); $postgroups = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) $postgroups[$row['id_group']] = $row['min_posts']; $smcFunc['db_free_result']($request); // Sort them this way because if it's done with MySQL it causes a filesort :(. arsort($postgroups); cache_put_data('updateStats:postgroups', $postgroups, 360); } // Oh great, they've screwed their post groups. if (empty($postgroups)) return; // Set all membergroups from most posts to least posts. $conditions = ''; foreach ($postgroups as $id => $min_posts) { $conditions .= ' WHEN posts >= ' . $min_posts . (!empty($lastMin) ? ' AND posts <= ' . $lastMin : '') . ' THEN ' . $id; $lastMin = $min_posts; } // A big fat CASE WHEN... END is faster than a zillion UPDATE's ;). $smcFunc['db_query']('', ' UPDATE {db_prefix}members SET id_post_group = CASE ' . $conditions . ' ELSE 0 END' . ($parameter1 != null ? ' WHERE ' . (is_array($parameter1) ? 'id_member IN ({array_int:members})' : 'id_member = {int:members}') : ''), array( 'members' => $parameter1, ) ); break; default: trigger_error('updateStats(): Invalid statistic type \'' . $type . '\'', E_USER_NOTICE); } } // Assumes the data has been htmlspecialchar'd. function updateMemberData($members, $data) { global $modSettings, $user_info, $smcFunc; $parameters = array(); if (is_array($members)) { $condition = 'id_member IN ({array_int:members})'; $parameters['members'] = $members; } elseif ($members === null) $condition = '1=1'; else { $condition = 'id_member = {int:member}'; $parameters['member'] = $members; } if (!empty($modSettings['integrate_change_member_data'])) { // Only a few member variables are really interesting for integration. $integration_vars = array( 'member_name', 'real_name', 'email_address', 'id_group', 'gender', 'birthdate', 'website_title', 'website_url', 'location', 'hide_email', 'time_format', 'time_offset', 'avatar', 'lngfile', ); $vars_to_integrate = array_intersect($integration_vars, array_keys($data)); // Only proceed if there are any variables left to call the integration function. if (count($vars_to_integrate) != 0) { // Fetch a list of member_names if necessary if ((!is_array($members) && $members === $user_info['id']) || (is_array($members) && count($members) == 1 && in_array($user_info['id'], $members))) $member_names = array($user_info['username']); else { $member_names = array(); $request = $smcFunc['db_query']('', ' SELECT member_name FROM {db_prefix}members WHERE ' . $condition, $parameters ); while ($row = $smcFunc['db_fetch_assoc']($request)) $member_names[] = $row['member_name']; $smcFunc['db_free_result']($request); } if (!empty($member_names)) foreach ($vars_to_integrate as $var) call_integration_hook('integrate_change_member_data', array($member_names, $var, $data[$var])); } } // Everything is assumed to be a string unless it's in the below. $knownInts = array( 'date_registered', 'posts', 'id_group', 'last_login', 'instant_messages', 'unread_messages', 'new_pm', 'pm_prefs', 'gender', 'hide_email', 'show_online', 'pm_email_notify', 'pm_receive_from', 'karma_good', 'karma_bad', 'notify_announcements', 'notify_send_body', 'notify_regularity', 'notify_types', 'is_shareable', 'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning', ); $knownFloats = array( 'time_offset', ); $setString = ''; foreach ($data as $var => $val) { $type = 'string'; if (in_array($var, $knownInts)) $type = 'int'; elseif (in_array($var, $knownFloats)) $type = 'float'; elseif ($var == 'birthdate') $type = 'date'; // Doing an increment? if ($type == 'int' && ($val === '+' || $val === '-')) { $val = $var . ' ' . $val . ' 1'; $type = 'raw'; } // Ensure posts, instant_messages, and unread_messages don't overflow or underflow. if (in_array($var, array('posts', 'instant_messages', 'unread_messages'))) { if (preg_match('~^' . $var . ' (\+ |- |\+ -)([\d]+)~', $val, $match)) { if ($match[1] != '+ ') $val = 'CASE WHEN ' . $var . ' <= ' . abs($match[2]) . ' THEN 0 ELSE ' . $val . ' END'; $type = 'raw'; } } $setString .= ' ' . $var . ' = {' . $type . ':p_' . $var . '},'; $parameters['p_' . $var] = $val; } $smcFunc['db_query']('', ' UPDATE {db_prefix}members SET' . substr($setString, 0, -1) . ' WHERE ' . $condition, $parameters ); updateStats('postgroups', $members, array_keys($data)); // Clear any caching? if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2 && !empty($members)) { if (!is_array($members)) $members = array($members); foreach ($members as $member) { if ($modSettings['cache_enable'] >= 3) { cache_put_data('member_data-profile-' . $member, null, 120); cache_put_data('member_data-normal-' . $member, null, 120); cache_put_data('member_data-minimal-' . $member, null, 120); } cache_put_data('user_settings-' . $member, null, 60); } } } // Updates the settings table as well as $modSettings... only does one at a time if $update is true. function updateSettings($changeArray, $update = false, $debug = false) { global $modSettings, $smcFunc; if (empty($changeArray) || !is_array($changeArray)) return; // In some cases, this may be better and faster, but for large sets we don't want so many UPDATEs. if ($update) { foreach ($changeArray as $variable => $value) { $smcFunc['db_query']('', ' UPDATE {db_prefix}settings SET value = {' . ($value === false || $value === true ? 'raw' : 'string') . ':value} WHERE variable = {string:variable}', array( 'value' => $value === true ? 'value + 1' : ($value === false ? 'value - 1' : $value), 'variable' => $variable, ) ); $modSettings[$variable] = $value === true ? $modSettings[$variable] + 1 : ($value === false ? $modSettings[$variable] - 1 : $value); } // Clean out the cache and make sure the cobwebs are gone too. cache_put_data('modSettings', null, 90); return; } $replaceArray = array(); foreach ($changeArray as $variable => $value) { // Don't bother if it's already like that ;). if (isset($modSettings[$variable]) && $modSettings[$variable] == $value) continue; // If the variable isn't set, but would only be set to nothing'ness, then don't bother setting it. elseif (!isset($modSettings[$variable]) && empty($value)) continue; $replaceArray[] = array($variable, $value); $modSettings[$variable] = $value; } if (empty($replaceArray)) return; $smcFunc['db_insert']('replace', '{db_prefix}settings', array('variable' => 'string-255', 'value' => 'string-65534'), $replaceArray, array('variable') ); // Kill the cache - it needs redoing now, but we won't bother ourselves with that here. cache_put_data('modSettings', null, 90); } // Constructs a page list. // $pageindex = constructPageIndex($scripturl . '?board=' . $board, $_REQUEST['start'], $num_messages, $maxindex, true); function constructPageIndex($base_url, &$start, $max_value, $num_per_page, $flexible_start = false) { global $modSettings; // Save whether $start was less than 0 or not. $start = (int) $start; $start_invalid = $start < 0; // Make sure $start is a proper variable - not less than 0. if ($start_invalid) $start = 0; // Not greater than the upper bound. elseif ($start >= $max_value) $start = max(0, (int) $max_value - (((int) $max_value % (int) $num_per_page) == 0 ? $num_per_page : ((int) $max_value % (int) $num_per_page))); // And it has to be a multiple of $num_per_page! else $start = max(0, (int) $start - ((int) $start % (int) $num_per_page)); // Wireless will need the protocol on the URL somewhere. if (WIRELESS) $base_url .= ';' . WIRELESS_PROTOCOL; $base_link = '%2$s '; // Compact pages is off or on? if (empty($modSettings['compactTopicPagesEnable'])) { // Show the left arrow. $pageindex = $start == 0 ? ' ' : sprintf($base_link, $start - $num_per_page, '«'); // Show all the pages. $display_page = 1; for ($counter = 0; $counter < $max_value; $counter += $num_per_page) $pageindex .= $start == $counter && !$start_invalid ? '' . $display_page++ . ' ' : sprintf($base_link, $counter, $display_page++); // Show the right arrow. $display_page = ($start + $num_per_page) > $max_value ? $max_value : ($start + $num_per_page); if ($start != $counter - $max_value && !$start_invalid) $pageindex .= $display_page > $counter - $num_per_page ? ' ' : sprintf($base_link, $display_page, '»'); } else { // If they didn't enter an odd value, pretend they did. $PageContiguous = (int) ($modSettings['compactTopicPagesContiguous'] - ($modSettings['compactTopicPagesContiguous'] % 2)) / 2; // Show the first page. (>1< ... 6 7 [8] 9 10 ... 15) if ($start > $num_per_page * $PageContiguous) $pageindex = sprintf($base_link, 0, '1'); else $pageindex = ''; // Show the ... after the first page. (1 >...< 6 7 [8] 9 10 ... 15) if ($start > $num_per_page * ($PageContiguous + 1)) $pageindex .= ' ... '; // Show the pages before the current one. (1 ... >6 7< [8] 9 10 ... 15) for ($nCont = $PageContiguous; $nCont >= 1; $nCont--) if ($start >= $num_per_page * $nCont) { $tmpStart = $start - $num_per_page * $nCont; $pageindex.= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1); } // Show the current page. (1 ... 6 7 >[8]< 9 10 ... 15) if (!$start_invalid) $pageindex .= '[' . ($start / $num_per_page + 1) . '] '; else $pageindex .= sprintf($base_link, $start, $start / $num_per_page + 1); // Show the pages after the current one... (1 ... 6 7 [8] >9 10< ... 15) $tmpMaxPages = (int) (($max_value - 1) / $num_per_page) * $num_per_page; for ($nCont = 1; $nCont <= $PageContiguous; $nCont++) if ($start + $num_per_page * $nCont <= $tmpMaxPages) { $tmpStart = $start + $num_per_page * $nCont; $pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1); } // Show the '...' part near the end. (1 ... 6 7 [8] 9 10 >...< 15) if ($start + $num_per_page * ($PageContiguous + 1) < $tmpMaxPages) $pageindex .= ' ... '; // Show the last number in the list. (1 ... 6 7 [8] 9 10 ... >15<) if ($start + $num_per_page * $PageContiguous < $tmpMaxPages) $pageindex .= sprintf($base_link, $tmpMaxPages, $tmpMaxPages / $num_per_page + 1); } return $pageindex; } // Formats a number to display in the style of the admin's choosing. function comma_format($number, $override_decimal_count = false) { global $txt; static $thousands_separator = null, $decimal_separator = null, $decimal_count = null; // !!! Should, perhaps, this just be handled in the language files, and not a mod setting? // (French uses 1 234,00 for example... what about a multilingual forum?) // Cache these values... if ($decimal_separator === null) { // Not set for whatever reason? if (empty($txt['number_format']) || preg_match('~^1([^\d]*)?234([^\d]*)(0*?)$~', $txt['number_format'], $matches) != 1) return $number; // Cache these each load... $thousands_separator = $matches[1]; $decimal_separator = $matches[2]; $decimal_count = strlen($matches[3]); } // Format the string with our friend, number_format. return number_format($number, is_float($number) ? ($override_decimal_count === false ? $decimal_count : $override_decimal_count) : 0, $decimal_separator, $thousands_separator); } // Format a time to make it look purdy. function timeformat($log_time, $show_today = true, $offset_type = false) { global $context, $user_info, $txt, $modSettings, $smcFunc; static $non_twelve_hour; // Offset the time. if (!$offset_type) $time = $log_time + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600; // Just the forum offset? elseif ($offset_type == 'forum') $time = $log_time + $modSettings['time_offset'] * 3600; else $time = $log_time; // We can't have a negative date (on Windows, at least.) if ($log_time < 0) $log_time = 0; // Today and Yesterday? if ($modSettings['todayMod'] >= 1 && $show_today === true) { // Get the current time. $nowtime = forum_time(); $then = @getdate($time); $now = @getdate($nowtime); // Try to make something of a time format string... $s = strpos($user_info['time_format'], '%S') === false ? '' : ':%S'; if (strpos($user_info['time_format'], '%H') === false && strpos($user_info['time_format'], '%T') === false) { $h = strpos($user_info['time_format'], '%l') === false ? '%I' : '%l'; $today_fmt = $h . ':%M' . $s . ' %p'; } else $today_fmt = '%H:%M' . $s; // Same day of the year, same year.... Today! if ($then['yday'] == $now['yday'] && $then['year'] == $now['year']) return $txt['today'] . timeformat($log_time, $today_fmt, $offset_type); // Day-of-year is one less and same year, or it's the first of the year and that's the last of the year... if ($modSettings['todayMod'] == '2' && (($then['yday'] == $now['yday'] - 1 && $then['year'] == $now['year']) || ($now['yday'] == 0 && $then['year'] == $now['year'] - 1) && $then['mon'] == 12 && $then['mday'] == 31)) return $txt['yesterday'] . timeformat($log_time, $today_fmt, $offset_type); } $str = !is_bool($show_today) ? $show_today : $user_info['time_format']; if (setlocale(LC_TIME, $txt['lang_locale'])) { if (!isset($non_twelve_hour)) $non_twelve_hour = trim(strftime('%p')) === ''; if ($non_twelve_hour && strpos($str, '%p') !== false) $str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str); foreach (array('%a', '%A', '%b', '%B') as $token) if (strpos($str, $token) !== false) $str = str_replace($token, !empty($txt['lang_capitalize_dates']) ? $smcFunc['ucwords'](strftime($token, $time)) : strftime($token, $time), $str); } else { // Do-it-yourself time localization. Fun. foreach (array('%a' => 'days_short', '%A' => 'days', '%b' => 'months_short', '%B' => 'months') as $token => $text_label) if (strpos($str, $token) !== false) $str = str_replace($token, $txt[$text_label][(int) strftime($token === '%a' || $token === '%A' ? '%w' : '%m', $time)], $str); if (strpos($str, '%p') !== false) $str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str); } // Windows doesn't support %e; on some versions, strftime fails altogether if used, so let's prevent that. if ($context['server']['is_windows'] && strpos($str, '%e') !== false) $str = str_replace('%e', ltrim(strftime('%d', $time), '0'), $str); // Format any other characters.. return strftime($str, $time); } // Removes special entities from strings. Compatibility... function un_htmlspecialchars($string) { static $translation; if (!isset($translation)) $translation = array_flip(get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES)) + array(''' => '\'', ' ' => ' '); return strtr($string, $translation); } // Shorten a subject + internationalization concerns. function shorten_subject($subject, $len) { global $smcFunc; // It was already short enough! if ($smcFunc['strlen']($subject) <= $len) return $subject; // Shorten it by the length it was too long, and strip off junk from the end. return $smcFunc['substr']($subject, 0, $len) . '...'; } // The current time with offset. function forum_time($use_user_offset = true, $timestamp = null) { global $user_info, $modSettings; if ($timestamp === null) $timestamp = time(); elseif ($timestamp == 0) return 0; return $timestamp + ($modSettings['time_offset'] + ($use_user_offset ? $user_info['time_offset'] : 0)) * 3600; } // This gets all possible permutations of an array. function permute($array) { $orders = array($array); $n = count($array); $p = range(0, $n); for ($i = 1; $i < $n; null) { $p[$i]--; $j = $i % 2 != 0 ? $p[$i] : 0; $temp = $array[$i]; $array[$i] = $array[$j]; $array[$j] = $temp; for ($i = 1; $p[$i] == 0; $i++) $p[$i] = 1; $orders[] = $array; } return $orders; } // Parse bulletin board code in a string, as well as smileys optionally. function parse_bbc($message, $smileys = true, $cache_id = '', $parse_tags = array()) { global $txt, $scripturl, $context, $modSettings, $user_info, $smcFunc; static $bbc_codes = array(), $itemcodes = array(), $no_autolink_tags = array(); static $disabled; // Don't waste cycles if ($message === '') return ''; // Never show smileys for wireless clients. More bytes, can't see it anyway :P. if (WIRELESS) $smileys = false; elseif ($smileys !== null && ($smileys == '1' || $smileys == '0')) $smileys = (bool) $smileys; if (empty($modSettings['enableBBC']) && $message !== false) { if ($smileys === true) parsesmileys($message); return $message; } // Just in case it wasn't determined yet whether UTF-8 is enabled. if (!isset($context['utf8'])) $context['utf8'] = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8'; // If we are not doing every tag then we don't cache this run. if (!empty($parse_tags) && !empty($bbc_codes)) { $temp_bbc = $bbc_codes; $bbc_codes = array(); } // Sift out the bbc for a performance improvement. if (empty($bbc_codes) || $message === false || !empty($parse_tags)) { if (!empty($modSettings['disabledBBC'])) { $temp = explode(',', strtolower($modSettings['disabledBBC'])); foreach ($temp as $tag) $disabled[trim($tag)] = true; } if (empty($modSettings['enableEmbeddedFlash'])) $disabled['flash'] = true; /* The following bbc are formatted as an array, with keys as follows: tag: the tag's name - should be lowercase! type: one of... - (missing): [tag]parsed content[/tag] - unparsed_equals: [tag=xyz]parsed content[/tag] - parsed_equals: [tag=parsed data]parsed content[/tag] - unparsed_content: [tag]unparsed content[/tag] - closed: [tag], [tag/], [tag /] - unparsed_commas: [tag=1,2,3]parsed content[/tag] - unparsed_commas_content: [tag=1,2,3]unparsed content[/tag] - unparsed_equals_content: [tag=...]unparsed content[/tag] parameters: an optional array of parameters, for the form [tag abc=123]content[/tag]. The array is an associative array where the keys are the parameter names, and the values are an array which may contain the following: - match: a regular expression to validate and match the value. - quoted: true if the value should be quoted. - validate: callback to evaluate on the data, which is $data. - value: a string in which to replace $1 with the data. either it or validate may be used, not both. - optional: true if the parameter is optional. test: a regular expression to test immediately after the tag's '=', ' ' or ']'. Typically, should have a \] at the end. Optional. content: only available for unparsed_content, closed, unparsed_commas_content, and unparsed_equals_content. $1 is replaced with the content of the tag. Parameters are replaced in the form {param}. For unparsed_commas_content, $2, $3, ..., $n are replaced. before: only when content is not used, to go before any content. For unparsed_equals, $1 is replaced with the value. For unparsed_commas, $1, $2, ..., $n are replaced. after: similar to before in every way, except that it is used when the tag is closed. disabled_content: used in place of content when the tag is disabled. For closed, default is '', otherwise it is '$1' if block_level is false, '
' : '') . '$1
' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '
' : ''),
// !!! Maybe this can be simplified?
'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', '
global $context;
if (!isset($disabled[\'code\']))
{
$php_parts = preg_split(\'~(<\?php|\?>)~\', $data, -1, PREG_SPLIT_DELIM_CAPTURE);
for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
{
// Do PHP code coloring?
if ($php_parts[$php_i] != \'<?php\')
continue;
$php_string = \'\';
while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?>\')
{
$php_string .= $php_parts[$php_i];
$php_parts[$php_i++] = \'\';
}
$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
}
// Fix the PHP code stuff...
$data = str_replace("\t", "\t", implode(\'\', $php_parts)); // Older browsers are annoying, aren\'t they? if ($context[\'browser\'][\'is_ie4\'] || $context[\'browser\'][\'is_ie5\'] || $context[\'browser\'][\'is_ie5.5\']) $data = str_replace("\t", "
\t", $data); else $data = str_replace("\t", "\t", $data); // Recent Opera bug requiring temporary fix. &nsbp; is needed before to avoid broken selection. if ($context[\'browser\'][\'is_opera\']) $data .= \' \'; }'), 'block_level' => true, ), array( 'tag' => 'code', 'type' => 'unparsed_equals_content', 'content' => '
' : '') . '$1
' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '
' : ''),
// !!! Maybe this can be simplified?
'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', '
global $context;
if (!isset($disabled[\'code\']))
{
$php_parts = preg_split(\'~(<\?php|\?>)~\', $data[0], -1, PREG_SPLIT_DELIM_CAPTURE);
for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
{
// Do PHP code coloring?
if ($php_parts[$php_i] != \'<?php\')
continue;
$php_string = \'\';
while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?>\')
{
$php_string .= $php_parts[$php_i];
$php_parts[$php_i++] = \'\';
}
$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
}
// Fix the PHP code stuff...
$data[0] = str_replace("\t", "\t", implode(\'\', $php_parts)); // Older browsers are annoying, aren\'t they? if ($context[\'browser\'][\'is_ie4\'] || $context[\'browser\'][\'is_ie5\'] || $context[\'browser\'][\'is_ie5.5\']) $data[0] = str_replace("\t", "
\t", $data[0]); else $data[0] = str_replace("\t", "\t", $data[0]); // Recent Opera bug requiring temporary fix. &nsbp; is needed before to avoid broken selection. if ($context[\'browser\'][\'is_opera\']) $data[0] .= \' \'; }'), 'block_level' => true, ), array( 'tag' => 'color', 'type' => 'unparsed_equals', 'test' => '(#[\da-fA-F]{3}|#[\da-fA-F]{6}|[A-Za-z]{1,20}|rgb\(\d{1,3}, ?\d{1,3}, ?\d{1,3}\))\]', 'before' => '', 'after' => '', ), array( 'tag' => 'email', 'type' => 'unparsed_content', 'content' => '$1', // !!! Should this respect guest_hideContacts? 'validate' => create_function('&$tag, &$data, $disabled', '$data = strtr($data, array(\'
' : '', 'after' => $context['browser']['is_ie'] ? ' |
', 'after' => '', ), array( 'tag' => 'quote', 'before' => '
', 'after' => '', 'block_level' => true, ), array( 'tag' => 'quote', 'parameters' => array( 'author' => array('match' => '(.{1,192}?)', 'quoted' => true), ), 'before' => '
', 'after' => '', 'block_level' => true, ), array( 'tag' => 'quote', 'type' => 'parsed_equals', 'before' => '
', 'after' => '', 'quoted' => 'optional', // Don't allow everything to be embedded with the author name. 'parsed_tags_allowed' => array('url', 'iurl', 'ftp'), 'block_level' => true, ), array( 'tag' => 'quote', 'parameters' => array( 'author' => array('match' => '([^<>]{1,192}?)'), 'link' => array('match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|action=profile;u=\d+)'), 'date' => array('match' => '(\d+)', 'validate' => 'timeformat'), ), 'before' => '
', 'after' => '', 'block_level' => true, ), array( 'tag' => 'quote', 'parameters' => array( 'author' => array('match' => '(.{1,192}?)'), ), 'before' => '
', 'after' => '', 'block_level' => true, ), array( 'tag' => 'red', 'before' => '', 'after' => '', ), array( 'tag' => 'right', 'before' => '
' => '')); } // This is long, but it makes things much easier and cleaner. if (!empty($possible['parameters'])) { $preg = array(); foreach ($possible['parameters'] as $p => $info) $preg[] = '(\s+' . $p . '=' . (empty($info['quoted']) ? '' : '"') . (isset($info['match']) ? $info['match'] : '(.+?)') . (empty($info['quoted']) ? '' : '"') . ')' . (empty($info['optional']) ? '' : '?'); // Okay, this may look ugly and it is, but it's not going to happen much and it is the best way of allowing any order of parameters but still parsing them right. $match = false; $orders = permute($preg); foreach ($orders as $p) if (preg_match('~^' . implode('', $p) . '\]~i', substr($message, $pos1 - 1), $matches) != 0) { $match = true; break; } // Didn't match our parameter list, try the next possible. if (!$match) continue; $params = array(); for ($i = 1, $n = count($matches); $i < $n; $i += 2) { $key = strtok(ltrim($matches[$i]), '='); if (isset($possible['parameters'][$key]['value'])) $params['{' . $key . '}'] = strtr($possible['parameters'][$key]['value'], array('$1' => $matches[$i + 1])); elseif (isset($possible['parameters'][$key]['validate'])) $params['{' . $key . '}'] = $possible['parameters'][$key]['validate']($matches[$i + 1]); else $params['{' . $key . '}'] = $matches[$i + 1]; // Just to make sure: replace any $ or { so they can't interpolate wrongly. $params['{' . $key . '}'] = strtr($params['{' . $key . '}'], array('$' => '$', '{' => '{')); } foreach ($possible['parameters'] as $p => $info) { if (!isset($params['{' . $p . '}'])) $params['{' . $p . '}'] = ''; } $tag = $possible; // Put the parameters into the string. if (isset($tag['before'])) $tag['before'] = strtr($tag['before'], $params); if (isset($tag['after'])) $tag['after'] = strtr($tag['after'], $params); if (isset($tag['content'])) $tag['content'] = strtr($tag['content'], $params); $pos1 += strlen($matches[0]) - 1; } else $tag = $possible; break; } // Item codes are complicated buggers... they are implicit [li]s and can make [list]s! if ($smileys !== false && $tag === null && isset($itemcodes[substr($message, $pos + 1, 1)]) && substr($message, $pos + 2, 1) == ']' && !isset($disabled['list']) && !isset($disabled['li'])) { if (substr($message, $pos + 1, 1) == '0' && !in_array(substr($message, $pos - 1, 1), array(';', ' ', "\t", '>'))) continue; $tag = $itemcodes[substr($message, $pos + 1, 1)]; // First let's set up the tree: it needs to be in a list, or after an li. if ($inside === null || ($inside['tag'] != 'list' && $inside['tag'] != 'li')) { $open_tags[] = array( 'tag' => 'list', 'after' => '', 'block_level' => true, 'require_children' => array('li'), 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null, ); $code = ''; } // We're in a list item already: another itemcode? Close it first. elseif ($inside['tag'] == 'li') { array_pop($open_tags); $code = ''; } else $code = ''; // Now we open a new tag. $open_tags[] = array( 'tag' => 'li', 'after' => '', 'trim' => 'outside', 'block_level' => true, 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null, ); // First, open the tag... $code .= '
'; } // Tell the [list] that it needs to close specially. else { // Move the li over, because we're not sure what we'll hit. $open_tags[count($open_tags) - 1]['after'] = ''; $open_tags[count($open_tags) - 2]['after'] = ''; } continue; } // Implicitly close lists and tables if something other than what's required is in them. This is needed for itemcode. if ($tag === null && $inside !== null && !empty($inside['require_children'])) { array_pop($open_tags); $message = substr($message, 0, $pos) . "\n" . $inside['after'] . "\n" . substr($message, $pos); $pos += strlen($inside['after']) - 1 + 2; } // No tag? Keep looking, then. Silly people using brackets without actual tags. if ($tag === null) continue; // Propagate the list to the child (so wrapping the disallowed tag won't work either.) if (isset($inside['disallow_children'])) $tag['disallow_children'] = isset($tag['disallow_children']) ? array_unique(array_merge($tag['disallow_children'], $inside['disallow_children'])) : $inside['disallow_children']; // Is this tag disabled? if (isset($disabled[$tag['tag']])) { if (!isset($tag['disabled_before']) && !isset($tag['disabled_after']) && !isset($tag['disabled_content'])) { $tag['before'] = !empty($tag['block_level']) ? '- '; $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos + 3); $pos += strlen($code) - 1 + 2; // Next, find the next break (if any.) If there's more itemcode after it, keep it going - otherwise close! $pos2 = strpos($message, '
', $pos); $pos3 = strpos($message, '[/', $pos); if ($pos2 !== false && ($pos2 <= $pos3 || $pos3 === false)) { preg_match('~^(
| |\s|\[)+~', substr($message, $pos2 + 6), $matches); $message = substr($message, 0, $pos2) . "\n" . (!empty($matches[0]) && substr($matches[0], -1) == '[' ? '[/li]' : '[/li][/list]') . "\n" . substr($message, $pos2); $open_tags[count($open_tags) - 2]['after'] = '' : ''; $tag['after'] = !empty($tag['block_level']) ? '' : ''; $tag['content'] = isset($tag['type']) && $tag['type'] == 'closed' ? '' : (!empty($tag['block_level']) ? '$1' : '$1'); } elseif (isset($tag['disabled_before']) || isset($tag['disabled_after'])) { $tag['before'] = isset($tag['disabled_before']) ? $tag['disabled_before'] : (!empty($tag['block_level']) ? '' : ''); $tag['after'] = isset($tag['disabled_after']) ? $tag['disabled_after'] : (!empty($tag['block_level']) ? '' : ''); } else $tag['content'] = $tag['disabled_content']; } // The only special case is 'html', which doesn't need to close things. if (!empty($tag['block_level']) && $tag['tag'] != 'html' && empty($inside['block_level'])) { $n = count($open_tags) - 1; while (empty($open_tags[$n]['block_level']) && $n >= 0) $n--; // Close all the non block level tags so this tag isn't surrounded by them. for ($i = count($open_tags) - 1; $i > $n; $i--) { $message = substr($message, 0, $pos) . "\n" . $open_tags[$i]['after'] . "\n" . substr($message, $pos); $pos += strlen($open_tags[$i]['after']) + 2; $pos1 += strlen($open_tags[$i]['after']) + 2; // Trim or eat trailing stuff... see comment at the end of the big loop. if (!empty($open_tags[$i]['block_level']) && substr($message, $pos, 6) == '
') $message = substr($message, 0, $pos) . substr($message, $pos + 6); if (!empty($open_tags[$i]['trim']) && $tag['trim'] != 'inside' && preg_match('~(
| |\s)*~', substr($message, $pos), $matches) != 0) $message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0])); array_pop($open_tags); } } // No type means 'parsed_content'. if (!isset($tag['type'])) { // !!! Check for end tag first, so people can say "I like that [i] tag"? $open_tags[] = $tag; $message = substr($message, 0, $pos) . "\n" . $tag['before'] . "\n" . substr($message, $pos1); $pos += strlen($tag['before']) - 1 + 2; } // Don't parse the content, just skip it. elseif ($tag['type'] == 'unparsed_content') { $pos2 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos1); if ($pos2 === false) continue; $data = substr($message, $pos1, $pos2 - $pos1); if (!empty($tag['block_level']) && substr($data, 0, 6) == '
') $data = substr($data, 6); if (isset($tag['validate'])) $tag['validate']($tag, $data, $disabled); $code = strtr($tag['content'], array('$1' => $data)); $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 3 + strlen($tag['tag'])); $pos += strlen($code) - 1 + 2; $last_pos = $pos + 1; } // Don't parse the content, just skip it. elseif ($tag['type'] == 'unparsed_equals_content') { // The value may be quoted for some tags - check. if (isset($tag['quoted'])) { $quoted = substr($message, $pos1, 6) == '"'; if ($tag['quoted'] != 'optional' && !$quoted) continue; if ($quoted) $pos1 += 6; } else $quoted = false; $pos2 = strpos($message, $quoted == false ? ']' : '"]', $pos1); if ($pos2 === false) continue; $pos3 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos2); if ($pos3 === false) continue; $data = array( substr($message, $pos2 + ($quoted == false ? 1 : 7), $pos3 - ($pos2 + ($quoted == false ? 1 : 7))), substr($message, $pos1, $pos2 - $pos1) ); if (!empty($tag['block_level']) && substr($data[0], 0, 6) == '
') $data[0] = substr($data[0], 6); // Validation for my parking, please! if (isset($tag['validate'])) $tag['validate']($tag, $data, $disabled); $code = strtr($tag['content'], array('$1' => $data[0], '$2' => $data[1])); $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + strlen($tag['tag'])); $pos += strlen($code) - 1 + 2; } // A closed tag, with no content or value. elseif ($tag['type'] == 'closed') { $pos2 = strpos($message, ']', $pos); $message = substr($message, 0, $pos) . "\n" . $tag['content'] . "\n" . substr($message, $pos2 + 1); $pos += strlen($tag['content']) - 1 + 2; } // This one is sorta ugly... :/. Unfortunately, it's needed for flash. elseif ($tag['type'] == 'unparsed_commas_content') { $pos2 = strpos($message, ']', $pos1); if ($pos2 === false) continue; $pos3 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos2); if ($pos3 === false) continue; // We want $1 to be the content, and the rest to be csv. $data = explode(',', ',' . substr($message, $pos1, $pos2 - $pos1)); $data[0] = substr($message, $pos2 + 1, $pos3 - $pos2 - 1); if (isset($tag['validate'])) $tag['validate']($tag, $data, $disabled); $code = $tag['content']; foreach ($data as $k => $d) $code = strtr($code, array('$' . ($k + 1) => trim($d))); $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + strlen($tag['tag'])); $pos += strlen($code) - 1 + 2; } // This has parsed content, and a csv value which is unparsed. elseif ($tag['type'] == 'unparsed_commas') { $pos2 = strpos($message, ']', $pos1); if ($pos2 === false) continue; $data = explode(',', substr($message, $pos1, $pos2 - $pos1)); if (isset($tag['validate'])) $tag['validate']($tag, $data, $disabled); // Fix after, for disabled code mainly. foreach ($data as $k => $d) $tag['after'] = strtr($tag['after'], array('$' . ($k + 1) => trim($d))); $open_tags[] = $tag; // Replace them out, $1, $2, $3, $4, etc. $code = $tag['before']; foreach ($data as $k => $d) $code = strtr($code, array('$' . ($k + 1) => trim($d))); $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 1); $pos += strlen($code) - 1 + 2; } // A tag set to a value, parsed or not. elseif ($tag['type'] == 'unparsed_equals' || $tag['type'] == 'parsed_equals') { // The value may be quoted for some tags - check. if (isset($tag['quoted'])) { $quoted = substr($message, $pos1, 6) == '"'; if ($tag['quoted'] != 'optional' && !$quoted) continue; if ($quoted) $pos1 += 6; } else $quoted = false; $pos2 = strpos($message, $quoted == false ? ']' : '"]', $pos1); if ($pos2 === false) continue; $data = substr($message, $pos1, $pos2 - $pos1); // Validation for my parking, please! if (isset($tag['validate'])) $tag['validate']($tag, $data, $disabled); // For parsed content, we must recurse to avoid security problems. if ($tag['type'] != 'unparsed_equals') $data = parse_bbc($data, !empty($tag['parsed_tags_allowed']) ? false : true, '', !empty($tag['parsed_tags_allowed']) ? $tag['parsed_tags_allowed'] : array()); $tag['after'] = strtr($tag['after'], array('$1' => $data)); $open_tags[] = $tag; $code = strtr($tag['before'], array('$1' => $data)); $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + ($quoted == false ? 1 : 7)); $pos += strlen($code) - 1 + 2; } // If this is block level, eat any breaks after it. if (!empty($tag['block_level']) && substr($message, $pos + 1, 6) == '
') $message = substr($message, 0, $pos + 1) . substr($message, $pos + 7); // Are we trimming outside this tag? if (!empty($tag['trim']) && $tag['trim'] != 'outside' && preg_match('~(
| |\s)*~', substr($message, $pos + 1), $matches) != 0) $message = substr($message, 0, $pos + 1) . substr($message, $pos + 1 + strlen($matches[0])); } // Close any remaining tags. while ($tag = array_pop($open_tags)) $message .= "\n" . $tag['after'] . "\n"; // Parse the smileys within the parts where it can be done safely. if ($smileys === true) { $message_parts = explode("\n", $message); for ($i = 0, $n = count($message_parts); $i < $n; $i += 2) parsesmileys($message_parts[$i]); $message = implode('', $message_parts); } // No smileys, just get rid of the markers. else $message = strtr($message, array("\n" => '')); if (substr($message, 0, 1) == ' ') $message = ' ' . substr($message, 1); // Cleanup whitespace. $message = strtr($message, array(' ' => ' ', "\r" => '', "\n" => '
', '
' => '
', ' ' => "\n")); // Cache the output if it took some time... if (isset($cache_key, $cache_t) && array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.05) cache_put_data($cache_key, $message, 240); // If this was a force parse revert if needed. if (!empty($parse_tags)) { if (empty($temp_bbc)) $bbc_codes = array(); else { $bbc_codes = $temp_bbc; unset($temp_bbc); } } return $message; } // Parse smileys in the passed message. function parsesmileys(&$message) { global $modSettings, $txt, $user_info, $context, $smcFunc; static $smileyPregSearch = array(), $smileyPregReplacements = array(); // No smiley set at all?! if ($user_info['smiley_set'] == 'none') return; // If the smiley array hasn't been set, do it now. if (empty($smileyPregSearch)) { // Use the default smileys if it is disabled. (better for "portability" of smileys.) if (empty($modSettings['smiley_enable'])) { $smileysfrom = array('>:D', ':D', '::)', '>:(', ':))', ':)', ';)', ';D', ':(', ':o', '8)', ':P', '???', ':-[', ':-X', ':-*', ':\'(', ':-\\', '^-^', 'O0', 'C:-)', '0:)'); $smileysto = array('evil.gif', 'cheesy.gif', 'rolleyes.gif', 'angry.gif', 'laugh.gif', 'smiley.gif', 'wink.gif', 'grin.gif', 'sad.gif', 'shocked.gif', 'cool.gif', 'tongue.gif', 'huh.gif', 'embarrassed.gif', 'lipsrsealed.gif', 'kiss.gif', 'cry.gif', 'undecided.gif', 'azn.gif', 'afro.gif', 'police.gif', 'angel.gif'); $smileysdescs = array('', $txt['icon_cheesy'], $txt['icon_rolleyes'], $txt['icon_angry'], '', $txt['icon_smiley'], $txt['icon_wink'], $txt['icon_grin'], $txt['icon_sad'], $txt['icon_shocked'], $txt['icon_cool'], $txt['icon_tongue'], $txt['icon_huh'], $txt['icon_embarrassed'], $txt['icon_lips'], $txt['icon_kiss'], $txt['icon_cry'], $txt['icon_undecided'], '', '', '', ''); } else { // Load the smileys in reverse order by length so they don't get parsed wrong. if (($temp = cache_get_data('parsing_smileys', 480)) == null) { $result = $smcFunc['db_query']('', ' SELECT code, filename, description FROM {db_prefix}smileys', array( ) ); $smileysfrom = array(); $smileysto = array(); $smileysdescs = array(); while ($row = $smcFunc['db_fetch_assoc']($result)) { $smileysfrom[] = $row['code']; $smileysto[] = $row['filename']; $smileysdescs[] = $row['description']; } $smcFunc['db_free_result']($result); cache_put_data('parsing_smileys', array($smileysfrom, $smileysto, $smileysdescs), 480); } else list ($smileysfrom, $smileysto, $smileysdescs) = $temp; } // The non-breaking-space is a complex thing... $non_breaking_space = $context['utf8'] ? ($context['server']['complex_preg_chars'] ? '\x{A0}' : "\xC2\xA0") : '\xA0'; // This smiley regex makes sure it doesn't parse smileys within code tags (so [url=mailto:David@bla.com] doesn't parse the :D smiley) $smileyPregReplacements = array(); $searchParts = array(); for ($i = 0, $n = count($smileysfrom); $i < $n; $i++) { $smileyCode = ''; $smileyPregReplacements[$smileysfrom[$i]] = $smileyCode; $smileyPregReplacements[htmlspecialchars($smileysfrom[$i], ENT_QUOTES)] = $smileyCode; $searchParts[] = preg_quote($smileysfrom[$i], '~'); $searchParts[] = preg_quote(htmlspecialchars($smileysfrom[$i], ENT_QUOTES), '~'); } $smileyPregSearch = '~(?<=[>:\?\.\s' . $non_breaking_space . '[\]()*\\\;]|^)(' . implode('|', $searchParts) . ')(?=[^[:alpha:]0-9]|$)~' . ($context['utf8'] ? 'u' : ''); } // Replace away! // TODO: When SMF supports only PHP 5.3+, we can change this to "uses" keyword and simplify this. $context['smiley_replacements'] = $smileyPregReplacements; $message = preg_replace_callback($smileyPregSearch, 'smileyPregReplaceCallback', $message); } // This allows use to do delayed argument binding and bring in the replacement variables for some preg replacements. function pregReplaceCurry($func, $arity) { return create_function('', " \$args = func_get_args(); if(count(\$args) >= $arity) return call_user_func_array('$func', \$args); \$args = var_export(\$args, 1); return create_function('',' \$a = func_get_args(); \$z = ' . \$args . '; \$a = array_merge(\$z,\$a); return call_user_func_array(\'$func\', \$a); '); "); } // Our callback that does the actual smiley replacements. function smileyPregReplaceCallback($matches) { global $context; return $context['smiley_replacements'][$matches[1]]; } // Highlight any code... function highlight_php_code($code) { global $context; // Remove special characters. $code = un_htmlspecialchars(strtr($code, array('
' => "\n", "\t" => 'SMF_TAB();', '[' => '['))); $oldlevel = error_reporting(0); // It's easier in 4.2.x+. if (@version_compare(PHP_VERSION, '4.2.0') == -1) { ob_start(); @highlight_string($code); $buffer = str_replace(array("\n", "\r"), '', ob_get_contents()); ob_end_clean(); } else $buffer = str_replace(array("\n", "\r"), '', @highlight_string($code, true)); error_reporting($oldlevel); // Yes, I know this is kludging it, but this is the best way to preserve tabs from PHP :P. $buffer = preg_replace('~SMF_TAB(?:(?:font|span)><(?:font color|span style)="[^"]*?">)?\\(\\);~', '' . "\t" . '', $buffer); return strtr($buffer, array('\'' => ''', '' => '', '
' => '')); } // Put this user in the online log. function writeLog($force = false) { global $user_info, $user_settings, $context, $modSettings, $settings, $topic, $board, $smcFunc, $sourcedir; // If we are showing who is viewing a topic, let's see if we are, and force an update if so - to make it accurate. if (!empty($settings['display_who_viewing']) && ($topic || $board)) { // Take the opposite approach! $force = true; // Don't update for every page - this isn't wholly accurate but who cares. if ($topic) { if (isset($_SESSION['last_topic_id']) && $_SESSION['last_topic_id'] == $topic) $force = false; $_SESSION['last_topic_id'] = $topic; } } // Are they a spider we should be tracking? Mode = 1 gets tracked on its spider check... if (!empty($user_info['possibly_robot']) && !empty($modSettings['spider_mode']) && $modSettings['spider_mode'] > 1) { require_once($sourcedir . '/ManageSearchEngines.php'); logSpider(); } // Don't mark them as online more than every so often. if (!empty($_SESSION['log_time']) && $_SESSION['log_time'] >= (time() - 8) && !$force) return; if (!empty($modSettings['who_enabled'])) { $serialized = $_GET + array('USER_AGENT' => $_SERVER['HTTP_USER_AGENT']); // In the case of a dlattach action, session_var may not be set. if (!isset($context['session_var'])) $context['session_var'] = $_SESSION['session_var']; unset($serialized['sesc'], $serialized[$context['session_var']]); $serialized = serialize($serialized); } else $serialized = ''; // Guests use 0, members use their session ID. $session_id = $user_info['is_guest'] ? 'ip' . $user_info['ip'] : session_id(); // Grab the last all-of-SMF-specific log_online deletion time. $do_delete = cache_get_data('log_online-update', 30) < time() - 30; // If the last click wasn't a long time ago, and there was a last click... if (!empty($_SESSION['log_time']) && $_SESSION['log_time'] >= time() - $modSettings['lastActive'] * 20) { if ($do_delete) { $smcFunc['db_query']('delete_log_online_interval', ' DELETE FROM {db_prefix}log_online WHERE log_time < {int:log_time} AND session != {string:session}', array( 'log_time' => time() - $modSettings['lastActive'] * 60, 'session' => $session_id, ) ); // Cache when we did it last. cache_put_data('log_online-update', time(), 30); } $smcFunc['db_query']('', ' UPDATE {db_prefix}log_online SET log_time = {int:log_time}, ip = IFNULL(INET_ATON({string:ip}), 0), url = {string:url} WHERE session = {string:session}', array( 'log_time' => time(), 'ip' => $user_info['ip'], 'url' => $serialized, 'session' => $session_id, ) ); // Guess it got deleted. if ($smcFunc['db_affected_rows']() == 0) $_SESSION['log_time'] = 0; } else $_SESSION['log_time'] = 0; // Otherwise, we have to delete and insert. if (empty($_SESSION['log_time'])) { if ($do_delete || !empty($user_info['id'])) $smcFunc['db_query']('', ' DELETE FROM {db_prefix}log_online WHERE ' . ($do_delete ? 'log_time < {int:log_time}' : '') . ($do_delete && !empty($user_info['id']) ? ' OR ' : '') . (empty($user_info['id']) ? '' : 'id_member = {int:current_member}'), array( 'current_member' => $user_info['id'], 'log_time' => time() - $modSettings['lastActive'] * 60, ) ); $smcFunc['db_insert']($do_delete ? 'ignore' : 'replace', '{db_prefix}log_online', array('session' => 'string', 'id_member' => 'int', 'id_spider' => 'int', 'log_time' => 'int', 'ip' => 'raw', 'url' => 'string'), array($session_id, $user_info['id'], empty($_SESSION['id_robot']) ? 0 : $_SESSION['id_robot'], time(), 'IFNULL(INET_ATON(\'' . $user_info['ip'] . '\'), 0)', $serialized), array('session') ); } // Mark your session as being logged. $_SESSION['log_time'] = time(); // Well, they are online now. if (empty($_SESSION['timeOnlineUpdated'])) $_SESSION['timeOnlineUpdated'] = time(); // Set their login time, if not already done within the last minute. if (SMF != 'SSI' && !empty($user_info['last_login']) && $user_info['last_login'] < time() - 60) { // Don't count longer than 15 minutes. if (time() - $_SESSION['timeOnlineUpdated'] > 60 * 15) $_SESSION['timeOnlineUpdated'] = time(); $user_settings['total_time_logged_in'] += time() - $_SESSION['timeOnlineUpdated']; updateMemberData($user_info['id'], array('last_login' => time(), 'member_ip' => $user_info['ip'], 'member_ip2' => $_SERVER['BAN_CHECK_IP'], 'total_time_logged_in' => $user_settings['total_time_logged_in'])); if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2) cache_put_data('user_settings-' . $user_info['id'], $user_settings, 60); $user_info['total_time_logged_in'] += time() - $_SESSION['timeOnlineUpdated']; $_SESSION['timeOnlineUpdated'] = time(); } } // Make sure the browser doesn't come back and repost the form data. Should be used whenever anything is posted. function redirectexit($setLocation = '', $refresh = false) { global $scripturl, $context, $modSettings, $db_show_debug, $db_cache; // In case we have mail to send, better do that - as obExit doesn't always quite make it... if (!empty($context['flush_mail'])) AddMailQueue(true); $add = preg_match('~^(ftp|http)[s]?://~', $setLocation) == 0 && substr($setLocation, 0, 6) != 'about:'; if (WIRELESS) { // Add the scripturl on if needed. if ($add) $setLocation = $scripturl . '?' . $setLocation; $char = strpos($setLocation, '?') === false ? '?' : ';'; if (strpos($setLocation, '#') !== false) $setLocation = strtr($setLocation, array('#' => $char . WIRELESS_PROTOCOL . '#')); else $setLocation .= $char . WIRELESS_PROTOCOL; } elseif ($add) $setLocation = $scripturl . ($setLocation != '' ? '?' . $setLocation : ''); // Put the session ID in. if (defined('SID') && SID != '') $setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation); // Keep that debug in their for template debugging! elseif (isset($_GET['debug'])) $setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation); if (!empty($modSettings['queryless_urls']) && (empty($context['server']['is_cgi']) || @ini_get('cgi.fix_pathinfo') == 1 || @get_cfg_var('cgi.fix_pathinfo') == 1) && (!empty($context['server']['is_apache']) || !empty($context['server']['is_lighttpd']))) { if (defined('SID') && SID != '') $setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&))((?:board|topic)=[^#]+?)(#[^"]*?)?$~', 'fix_redirect_sid__preg_callback', $setLocation); else $setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$~', 'fix_redirect_path__preg_callback', $setLocation); } // Maybe integrations want to change where we are heading? call_integration_hook('integrate_redirect', array(&$setLocation, &$refresh)); // We send a Refresh header only in special cases because Location looks better. (and is quicker...) if ($refresh && !WIRELESS) header('Refresh: 0; URL=' . strtr($setLocation, array(' ' => '%20'))); else header('Location: ' . str_replace(' ', '%20', $setLocation)); // Debugging. if (isset($db_show_debug) && $db_show_debug === true) $_SESSION['debug_redirect'] = $db_cache; obExit(false); } // Ends execution. Takes care of template loading and remembering the previous URL. function obExit($header = null, $do_footer = null, $from_index = false, $from_fatal_error = false) { global $context, $settings, $modSettings, $txt, $smcFunc; static $header_done = false, $footer_done = false, $level = 0, $has_fatal_error = false; // Attempt to prevent a recursive loop. ++$level; if ($level > 1 && !$from_fatal_error && !$has_fatal_error) exit; if ($from_fatal_error) $has_fatal_error = true; // Clear out the stat cache. trackStats(); // If we have mail to send, send it. if (!empty($context['flush_mail'])) AddMailQueue(true); $do_header = $header === null ? !$header_done : $header; if ($do_footer === null) $do_footer = $do_header; // Has the template/header been done yet? if ($do_header) { // Was the page title set last minute? Also update the HTML safe one. if (!empty($context['page_title']) && empty($context['page_title_html_safe'])) $context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])); // Start up the session URL fixer. ob_start('ob_sessrewrite'); if (!empty($settings['output_buffers']) && is_string($settings['output_buffers'])) $buffers = explode(',', $settings['output_buffers']); elseif (!empty($settings['output_buffers'])) $buffers = $settings['output_buffers']; else $buffers = array(); if (isset($modSettings['integrate_buffer'])) $buffers = array_merge(explode(',', $modSettings['integrate_buffer']), $buffers); if (!empty($buffers)) foreach ($buffers as $function) { $function = trim($function); $call = strpos($function, '::') !== false ? explode('::', $function) : $function; // Is it valid? if (is_callable($call)) ob_start($call); } // Display the screen in the logical order. template_header(); $header_done = true; } if ($do_footer) { if (WIRELESS && !isset($context['sub_template'])) fatal_lang_error('wireless_error_notyet', false); // Just show the footer, then. loadSubTemplate(isset($context['sub_template']) ? $context['sub_template'] : 'main'); // Anything special to put out? if (!empty($context['insert_after_template']) && !isset($_REQUEST['xml'])) echo $context['insert_after_template']; // Just so we don't get caught in an endless loop of errors from the footer... if (!$footer_done) { $footer_done = true; template_footer(); // (since this is just debugging... it's okay that it's after