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)
{
// 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) 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'],
'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) AS total_topics
FROM {db_prefix}boards' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
WHERE id_board != {int:recycle_board}' : ''),
array(
'recycle_board' => $modSettings['recycle_board'],
)
);
$row = $smcFunc['db_fetch_assoc']($result);
$smcFunc['db_free_result']($result);
updateSettings(array('totalTopics' => $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';
else
{
$condition = 'id_member = {int:member}';
$parameters['member'] = $members;
}
if (isset($modSettings['integrate_change_member_data']) && function_exists($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_user_func($modSettings['integrate_change_member_data'], $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', 'karma_good', 'karma_bad',
'notify_announcements', 'notify_send_body', 'notify_regularity', 'notify_types',
'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_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 = '%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 $user_info, $txt, $modSettings, $smcFunc;
// 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']))
{
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'))
$str = str_replace('%p', (strftime('%H', $time) < 12 ? 'am' : 'pm'), $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);
}
if (!function_exists('stripos'))
{
function stripos($haystack, $needle, $offset = 0)
{
return strpos(strtolower($haystack), strtolower($needle), $offset);
}
}
// 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;
static $bbc_codes = array(), $itemcodes = array(), $no_autolink_tags = array();
static $disabled;
// 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
' elsewise.
disabled_before: used in place of before when disabled. Defaults
to '
' if block_level, '' if not.
disabled_after: used in place of after when disabled. Defaults
to '
' if block_level, '' if not.
block_level: set to true the tag is a "block level" tag, similar
to HTML. Block level tags cannot be nested inside tags that are
not block level, and will not be implicitly closed as easily.
One break following a block level tag may also be removed.
trim: if set, and 'inside' whitespace after the begin tag will be
removed. If set to 'outside', whitespace after the end tag will
meet the same fate.
validate: except when type is missing or 'closed', a callback to
validate the data as $data. Depending on the tag's type, $data
may be a string or an array of strings (corresponding to the
replacement.)
quoted: when type is 'unparsed_equals' or 'parsed_equals' only,
may be not set, 'optional', or 'required' corresponding to if
the content may be quoted. This allows the parser to read
[tag="abc]def[esdf]"] properly.
require_parents: an array of tag names, or not set. If set, the
enclosing tag *must* be one of the listed tags, or parsing won't
occur.
require_children: similar to require_parents, if set children
won't be parsed if they are not in the list.
disallow_children: similar to, but very different from,
require_children, if it is set the listed tags will not be
parsed inside the tag.
*/
$codes = array(
array(
'tag' => 'abbr',
'type' => 'unparsed_equals',
'before' => '',
'after' => '',
'quoted' => 'optional',
'disabled_after' => ' ($1)',
),
array(
'tag' => 'acronym',
'type' => 'unparsed_equals',
'before' => '',
'after' => '',
'quoted' => 'optional',
'disabled_after' => ' ($1)',
),
array(
'tag' => 'anchor',
'type' => 'unparsed_equals',
'test' => '[#]?([A-Za-z][A-Za-z0-9_\-]*)\]',
'before' => '',
'after' => '',
),
array(
'tag' => 'b',
'before' => '',
'after' => '',
),
array(
'tag' => 'bdo',
'type' => 'unparsed_equals',
'before' => '',
'after' => '',
'test' => '(rtl|ltr)\]',
'block_level' => true,
),
array(
'tag' => 'black',
'before' => '',
'after' => '',
),
array(
'tag' => 'blue',
'before' => '',
'after' => '',
),
array(
'tag' => 'br',
'type' => 'closed',
'content' => ' ',
),
array(
'tag' => 'code',
'type' => 'unparsed_content',
'content' => '
';
}
// 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) . $inside['after'] . substr($message, $pos);
$pos += strlen($inside['after']) - 1;
}
// 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']) ? '
' : '');
}
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) . $open_tags[$i]['after'] . substr($message, $pos);
$pos += strlen($open_tags[$i]['after']);
$pos1 += strlen($open_tags[$i]['after']);
// 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) . $tag['before'] . substr($message, $pos1);
$pos += strlen($tag['before']) - 1;
}
// 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) . $code . substr($message, $pos2 + 3 + strlen($tag['tag']));
$pos += strlen($code) - 1;
$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) . $code . substr($message, $pos3 + 3 + strlen($tag['tag']));
$pos += strlen($code) - 1;
}
// A closed tag, with no content or value.
elseif ($tag['type'] == 'closed')
{
$pos2 = strpos($message, ']', $pos);
$message = substr($message, 0, $pos) . $tag['content'] . substr($message, $pos2 + 1);
$pos += strlen($tag['content']) - 1;
}
// This one is sorta ugly... :/. Unforunately, 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) . $code . substr($message, $pos3 + 3 + strlen($tag['tag']));
$pos += strlen($code) - 1;
}
// 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) . $code . substr($message, $pos2 + 1);
$pos += strlen($code) - 1;
}
// 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);
$tag['after'] = strtr($tag['after'], array('$1' => $data));
$open_tags[] = $tag;
$code = strtr($tag['before'], array('$1' => $data));
$message = substr($message, 0, $pos) . $code . substr($message, $pos2 + ($quoted == false ? 1 : 7));
$pos += strlen($code) - 1;
}
// 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 .= $tag['after'];
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]|$)~e' . ($context['utf8'] ? 'u' : '');
}
// Replace away!
$message = preg_replace($smileyPregSearch, 'isset($smileyPregReplacements[\'$1\']) ? $smileyPregReplacements[\'$1\'] : \'\'', $message);
}
// 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, $sc, $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']);
unset($serialized['sesc']);
$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) && !empty($context['server']['is_apache']))
{
if (defined('SID') && SID != '')
$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\?(?:' . SID . ';)((?:board|topic)=[^#]+?)(#[^"]*?)?$/e', "\$scripturl . '/' . strtr('\$1', '&;=', '//,') . '.html\$2?' . SID", $setLocation);
else
$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$/e', "\$scripturl . '/' . strtr('\$1', '&;=', '//,') . '.html\$2'", $setLocation);
}
if (isset($modSettings['integrate_redirect']) && function_exists($modSettings['integrate_redirect']))
$modSettings['integrate_redirect']($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)
{
global $context, $settings, $modSettings, $txt;
static $header_done = false, $footer_done = false;
// 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)
{
// Start up the session URL fixer.
ob_start('ob_sessrewrite');
// Just in case we have anything bad already in there...
if ((isset($_REQUEST['debug']) || isset($_REQUEST['xml']) || (WIRELESS && WIRELESS_PROTOCOL == 'wap')) && in_array($txt['lang_locale'], array('UTF-8', 'ISO-8859-1')))
ob_start('validate_unicode__recursive');
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 $buffer_function)
{
if (function_exists(trim($buffer_function)))
ob_start(trim($buffer_function));
}
// 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