$pos1)
{
$pos = $pos1;
break;
}
$pos = $pos2 + 1;
}
$clean .= ' %s ';
$old_pos = $pos + 1;
}
$clean .= substr($db_string, $old_pos);
$clean = trim(strtolower(preg_replace(array('~\s+~s', '~/\*!40001 SQL_NO_CACHE \*/~', '~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~'), array(' ', '', ''), $clean)));
// We don't use UNION in SMF, at least so far. But it's useful for injections.
if (strpos($clean, 'union') !== false && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0)
$fail = true;
// Comments? We don't use comments in our queries, we leave 'em outside!
elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false)
$fail = true;
// Trying to change passwords, slow us down, or something?
elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0)
$fail = true;
elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
$fail = true;
// Sub selects? We don't use those either.
elseif (preg_match('~\([^)]*?select~s', $clean) != 0)
$fail = true;
if (!empty($fail))
{
log_error('Hacking attempt...' . "\n" . $db_string, $file, $line);
fatal_error('Hacking attempt...', false);
}
}
$ret = mysql_query($db_string, $db_connection);
if ($ret === false && $file !== false)
$ret = db_error($db_string, $file, $line);
// Debugging.
if (isset($db_show_debug) && $db_show_debug === true)
$db_cache[$db_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st));
return $ret;
}
function db_affected_rows()
{
global $db_connection;
return mysql_affected_rows($db_connection);
}
function db_insert_id()
{
global $db_connection;
return mysql_insert_id($db_connection);
}
// Update some basic statistics...
function updateStats($type, $parameter1 = null, $parameter2 = null)
{
global $db_prefix, $sourcedir, $modSettings;
switch ($type)
{
case 'member':
$changes = array(
'memberlist_updated' => time(),
);
// Are we using registration approval?
if (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2)
{
// Update the latest activated member (highest ID_MEMBER) and count.
$result = db_query("
SELECT COUNT(*), MAX(ID_MEMBER)
FROM {$db_prefix}members
WHERE is_activated = 1", __FILE__, __LINE__);
list ($changes['totalMembers'], $changes['latestMember']) = mysql_fetch_row($result);
mysql_free_result($result);
// Get the latest activated member's display name.
$result = db_query("
SELECT realName
FROM {$db_prefix}members
WHERE ID_MEMBER = " . (int) $changes['latestMember'] . "
LIMIT 1", __FILE__, __LINE__);
list ($changes['latestRealName']) = mysql_fetch_row($result);
mysql_free_result($result);
// Update the amount of members awaiting approval - ignoring COPPA accounts, as you can't approve them until you get permission.
$result = db_query("
SELECT COUNT(*)
FROM {$db_prefix}members
WHERE is_activated IN (3, 4)", __FILE__, __LINE__);
list ($changes['unapprovedMembers']) = mysql_fetch_row($result);
mysql_free_result($result);
}
// If $parameter1 is a number, it's the new ID_MEMBER and #2 is the real name for a new registration.
elseif ($parameter1 !== null && $parameter1 !== false)
{
$changes['latestMember'] = $parameter1;
$changes['latestRealName'] = $parameter2;
updateSettings(array('totalMembers' => true), true);
}
// If $parameter1 is false, and approval is off, we need change nothing.
elseif ($parameter1 !== false)
{
// Update the latest member (highest ID_MEMBER) and count.
$result = db_query("
SELECT COUNT(*), MAX(ID_MEMBER)
FROM {$db_prefix}members", __FILE__, __LINE__);
list ($changes['totalMembers'], $changes['latestMember']) = mysql_fetch_row($result);
mysql_free_result($result);
// Get the latest member's display name.
$result = db_query("
SELECT realName
FROM {$db_prefix}members
WHERE ID_MEMBER = " . (int) $changes['latestMember'] . "
LIMIT 1", __FILE__, __LINE__);
list ($changes['latestRealName']) = mysql_fetch_row($result);
mysql_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 = db_query("
SELECT SUM(numPosts) AS totalMessages, MAX(ID_LAST_MSG) AS maxMsgID
FROM {$db_prefix}boards", __FILE__, __LINE__);
$row = mysql_fetch_assoc($result);
mysql_free_result($result);
updateSettings(array(
'totalMessages' => $row['totalMessages'],
'maxMsgID' => $row['maxMsgID'] === null ? 0 : $row['maxMsgID']
));
}
break;
case 'subject':
// Remove the previous subject (if any).
db_query("
DELETE FROM {$db_prefix}log_search_subjects
WHERE ID_TOPIC = " . (int) $parameter1, __FILE__, __LINE__);
// Insert the new subject.
if ($parameter2 !== null)
{
$parameter1 = (int) $parameter1;
$parameter2 = text2words($parameter2);
$inserts = array();
foreach ($parameter2 as $word)
$inserts[] = "'$word', $parameter1";
if (!empty($inserts))
db_query("
INSERT IGNORE INTO {$db_prefix}log_search_subjects
(word, ID_TOPIC)
VALUES (" . implode('),
(', array_unique($inserts)) . ")", __FILE__, __LINE__);
}
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 = db_query("
SELECT SUM(numTopics) AS totalTopics
FROM {$db_prefix}boards" . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? "
WHERE ID_BOARD != $modSettings[recycle_board]" : ''), __FILE__, __LINE__);
$row = mysql_fetch_assoc($result);
mysql_free_result($result);
updateSettings(array('totalTopics' => $row['totalTopics']));
}
break;
case 'calendar':
require_once($sourcedir . '/Calendar.php');
// Calculate the YYYY-MM-DD of the lowest and highest days.
$low_date = strftime('%Y-%m-%d', forum_time(false) - 24 * 3600);
$high_date = strftime('%Y-%m-%d', forum_time(false) + $modSettings['cal_days_for_index'] * 24 * 3600);
$holidays = calendarHolidayArray($low_date, $high_date);
$bday = calendarBirthdayArray($low_date, $high_date);
$events = calendarEventArray($low_date, $high_date, false);
// Cache the results in the settings.
updateSettings(array(
'cal_today_updated' => strftime('%Y%m%d', forum_time(false)),
'cal_today_holiday' => addslashes(serialize($holidays)),
'cal_today_birthday' => addslashes(serialize($bday)),
'cal_today_event' => addslashes(serialize($events))
));
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 = db_query("
SELECT ID_GROUP, minPosts
FROM {$db_prefix}membergroups
WHERE minPosts != -1", __FILE__, __LINE__);
$postgroups = array();
while ($row = mysql_fetch_assoc($request))
$postgroups[$row['ID_GROUP']] = $row['minPosts'];
mysql_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 => $minPosts)
{
$conditions .= '
WHEN posts >= ' . $minPosts . (!empty($lastMin) ? ' AND posts <= ' . $lastMin : '') . ' THEN ' . $id;
$lastMin = $minPosts;
}
// A big fat CASE WHEN... END is faster than a zillion UPDATE's ;).
db_query("
UPDATE {$db_prefix}members
SET ID_POST_GROUP = CASE$conditions
ELSE 0
END" . ($parameter1 != null ? "
WHERE $parameter1" : ''), __FILE__, __LINE__);
break;
default:
trigger_error('updateStats(): Invalid statistic type \'' . $type . '\'', E_USER_NOTICE);
}
}
// Assumes the data has been slashed.
function updateMemberData($members, $data)
{
global $db_prefix, $modSettings, $ID_MEMBER, $user_info;
if (is_array($members))
$condition = 'ID_MEMBER IN (' . implode(', ', $members) . ')
LIMIT ' . count($members);
elseif ($members === null)
$condition = '1';
else
$condition = 'ID_MEMBER = ' . $members . '
LIMIT 1';
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(
'memberName',
'realName',
'emailAddress',
'ID_GROUP',
'gender',
'birthdate',
'websiteTitle',
'websiteUrl',
'location',
'hideEmail',
'timeFormat',
'timeOffset',
'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 memberNames if necessary
if ((!is_array($members) && $members === $ID_MEMBER) || (is_array($members) && count($members) == 1 && in_array($ID_MEMBER, $members)))
$memberNames = array($user_info['username']);
else
{
$memberNames = array();
$request = db_query("
SELECT memberName
FROM {$db_prefix}members
WHERE $condition", __FILE__, __LINE__);
while ($row = mysql_fetch_assoc($request))
$memberNames[] = $row['memberName'];
mysql_free_result($request);
}
if (!empty($memberNames))
foreach ($vars_to_integrate as $var)
call_user_func($modSettings['integrate_change_member_data'], $memberNames, $var, stripslashes($data[$var]));
}
}
foreach ($data as $var => $val)
{
if ($val === '+')
$data[$var] = $var . ' + 1';
elseif ($val === '-')
$data[$var] = $var . ' - 1';
}
// Ensure posts, instantMessages, and unreadMessages never go below 0.
foreach(array('posts', 'instantMessages', 'unreadMessages') as $type)
if (isset($data[$type]) && preg_match('~^' . $type . ' - ([\d]+)~', $data[$type], $match) === 1)
$data[$type] = 'CASE WHEN ' . $type . ' <= ' . $match[1] . ' THEN 0 ELSE ' . $data[$type] . ' END';
$setString = '';
foreach ($data as $var => $val)
{
$setString .= "
$var = $val,";
}
db_query("
UPDATE {$db_prefix}members
SET" . substr($setString, 0, -1) . '
WHERE ' . $condition, __FILE__, __LINE__);
updateStats('postgroups', $condition, 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.
// All input variables and values are assumed to have escaped apostrophes(')!
function updateSettings($changeArray, $update = false)
{
global $db_prefix, $modSettings;
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)
{
db_query("
UPDATE {$db_prefix}settings
SET value = " . ($value === true ? 'value + 1' : ($value === false ? 'value - 1' : "'$value'")) . "
WHERE variable = '$variable'
LIMIT 1", __FILE__, __LINE__);
$modSettings[$variable] = $value === true ? $modSettings[$variable] + 1 : ($value === false ? $modSettings[$variable] - 1 : stripslashes($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] == stripslashes($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[] = "(SUBSTRING('$variable', 1, 255), SUBSTRING('$value', 1, 65534))";
$modSettings[$variable] = stripslashes($value);
}
if (empty($replaceArray))
return;
db_query("
REPLACE INTO {$db_prefix}settings
(variable, value)
VALUES " . implode(',
', $replaceArray), __FILE__, __LINE__);
// 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 $modSettings;
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($modSettings['number_format']) || preg_match('~^1([^\d]*)?234([^\d]*)(0*?)$~', $modSettings['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($logTime, $show_today = true)
{
global $user_info, $txt, $db_prefix, $modSettings, $func;
// Offset the time.
$time = $logTime + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600;
// We can't have a negative date (on Windows, at least.)
if ($time < 0)
$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)
$today_fmt = '%I:%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['smf10'] . timeformat($logTime, $today_fmt);
// 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['smf10b'] . timeformat($logTime, $today_fmt);
}
$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, $func['ucwords'](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)
{
return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES)) + array(''' => '\'', ' ' => ' '));
}
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 $func;
// It was already short enough!
if ($func['strlen']($subject) <= $len)
return $subject;
// Shorten it by the length it was too long, and strip off junk from the end.
return $func['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;
}
// For old stuff still using doUBBC()...
function doUBBC($message, $enableSmileys = true)
{
return parse_bbc($message, $enableSmileys);
}
// Parse bulletin board code in a string, as well as smileys optionally.
function parse_bbc($message, $smileys = true, $cache_id = '')
{
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';
// Sift out the bbc for a performance improvement.
if (empty($bbc_codes) || $message === false)
{
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 repalced 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' => 'black',
'before' => '',
'after' => '',
),
array(
'tag' => 'blue',
'before' => '',
'after' => '',
),
array(
'tag' => 'br',
'type' => 'closed',
'content' => ' ',
),
array(
'tag' => 'code',
'type' => 'unparsed_content',
'content' => '
' . $txt['smf238'] . ':
' . ($context['browser']['is_gecko'] ? '
$1
' : '$1') . '
',
// !!! 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("
';
}
// 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']) ? '