Chris@76: time(), Chris@76: ); Chris@76: Chris@76: // #1 latest member ID, #2 the real name for a new registration. Chris@76: if (is_numeric($parameter1)) Chris@76: { Chris@76: $changes['latestMember'] = $parameter1; Chris@76: $changes['latestRealName'] = $parameter2; Chris@76: Chris@76: updateSettings(array('totalMembers' => true), true); Chris@76: } Chris@76: Chris@76: // We need to calculate the totals. Chris@76: else Chris@76: { Chris@76: // Update the latest activated member (highest id_member) and count. Chris@76: $result = $smcFunc['db_query']('', ' Chris@76: SELECT COUNT(*), MAX(id_member) Chris@76: FROM {db_prefix}members Chris@76: WHERE is_activated = {int:is_activated}', Chris@76: array( Chris@76: 'is_activated' => 1, Chris@76: ) Chris@76: ); Chris@76: list ($changes['totalMembers'], $changes['latestMember']) = $smcFunc['db_fetch_row']($result); Chris@76: $smcFunc['db_free_result']($result); Chris@76: Chris@76: // Get the latest activated member's display name. Chris@76: $result = $smcFunc['db_query']('', ' Chris@76: SELECT real_name Chris@76: FROM {db_prefix}members Chris@76: WHERE id_member = {int:id_member} Chris@76: LIMIT 1', Chris@76: array( Chris@76: 'id_member' => (int) $changes['latestMember'], Chris@76: ) Chris@76: ); Chris@76: list ($changes['latestRealName']) = $smcFunc['db_fetch_row']($result); Chris@76: $smcFunc['db_free_result']($result); Chris@76: Chris@76: // Are we using registration approval? Chris@76: if ((!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($modSettings['approveAccountDeletion'])) Chris@76: { Chris@76: // Update the amount of members awaiting approval - ignoring COPPA accounts, as you can't approve them until you get permission. Chris@76: $result = $smcFunc['db_query']('', ' Chris@76: SELECT COUNT(*) Chris@76: FROM {db_prefix}members Chris@76: WHERE is_activated IN ({array_int:activation_status})', Chris@76: array( Chris@76: 'activation_status' => array(3, 4), Chris@76: ) Chris@76: ); Chris@76: list ($changes['unapprovedMembers']) = $smcFunc['db_fetch_row']($result); Chris@76: $smcFunc['db_free_result']($result); Chris@76: } Chris@76: } Chris@76: Chris@76: updateSettings($changes); Chris@76: break; Chris@76: Chris@76: case 'message': Chris@76: if ($parameter1 === true && $parameter2 !== null) Chris@76: updateSettings(array('totalMessages' => true, 'maxMsgID' => $parameter2), true); Chris@76: else Chris@76: { Chris@76: // SUM and MAX on a smaller table is better for InnoDB tables. Chris@76: $result = $smcFunc['db_query']('', ' Chris@76: SELECT SUM(num_posts + unapproved_posts) AS total_messages, MAX(id_last_msg) AS max_msg_id Chris@76: FROM {db_prefix}boards Chris@76: WHERE redirect = {string:blank_redirect}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' Chris@76: AND id_board != {int:recycle_board}' : ''), Chris@76: array( Chris@76: 'recycle_board' => isset($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0, Chris@76: 'blank_redirect' => '', Chris@76: ) Chris@76: ); Chris@76: $row = $smcFunc['db_fetch_assoc']($result); Chris@76: $smcFunc['db_free_result']($result); Chris@76: Chris@76: updateSettings(array( Chris@76: 'totalMessages' => $row['total_messages'] === null ? 0 : $row['total_messages'], Chris@76: 'maxMsgID' => $row['max_msg_id'] === null ? 0 : $row['max_msg_id'] Chris@76: )); Chris@76: } Chris@76: break; Chris@76: Chris@76: case 'subject': Chris@76: // Remove the previous subject (if any). Chris@76: $smcFunc['db_query']('', ' Chris@76: DELETE FROM {db_prefix}log_search_subjects Chris@76: WHERE id_topic = {int:id_topic}', Chris@76: array( Chris@76: 'id_topic' => (int) $parameter1, Chris@76: ) Chris@76: ); Chris@76: Chris@76: // Insert the new subject. Chris@76: if ($parameter2 !== null) Chris@76: { Chris@76: $parameter1 = (int) $parameter1; Chris@76: $parameter2 = text2words($parameter2); Chris@76: Chris@76: $inserts = array(); Chris@76: foreach ($parameter2 as $word) Chris@76: $inserts[] = array($word, $parameter1); Chris@76: Chris@76: if (!empty($inserts)) Chris@76: $smcFunc['db_insert']('ignore', Chris@76: '{db_prefix}log_search_subjects', Chris@76: array('word' => 'string', 'id_topic' => 'int'), Chris@76: $inserts, Chris@76: array('word', 'id_topic') Chris@76: ); Chris@76: } Chris@76: break; Chris@76: Chris@76: case 'topic': Chris@76: if ($parameter1 === true) Chris@76: updateSettings(array('totalTopics' => true), true); Chris@76: else Chris@76: { Chris@76: // Get the number of topics - a SUM is better for InnoDB tables. Chris@76: // We also ignore the recycle bin here because there will probably be a bunch of one-post topics there. Chris@76: $result = $smcFunc['db_query']('', ' Chris@76: SELECT SUM(num_topics + unapproved_topics) AS total_topics Chris@76: FROM {db_prefix}boards' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' Chris@76: WHERE id_board != {int:recycle_board}' : ''), Chris@76: array( Chris@76: 'recycle_board' => !empty($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0, Chris@76: ) Chris@76: ); Chris@76: $row = $smcFunc['db_fetch_assoc']($result); Chris@76: $smcFunc['db_free_result']($result); Chris@76: Chris@76: updateSettings(array('totalTopics' => $row['total_topics'] === null ? 0 : $row['total_topics'])); Chris@76: } Chris@76: break; Chris@76: Chris@76: case 'postgroups': Chris@76: // Parameter two is the updated columns: we should check to see if we base groups off any of these. Chris@76: if ($parameter2 !== null && !in_array('posts', $parameter2)) Chris@76: return; Chris@76: Chris@76: if (($postgroups = cache_get_data('updateStats:postgroups', 360)) == null) Chris@76: { Chris@76: // Fetch the postgroups! Chris@76: $request = $smcFunc['db_query']('', ' Chris@76: SELECT id_group, min_posts Chris@76: FROM {db_prefix}membergroups Chris@76: WHERE min_posts != {int:min_posts}', Chris@76: array( Chris@76: 'min_posts' => -1, Chris@76: ) Chris@76: ); Chris@76: $postgroups = array(); Chris@76: while ($row = $smcFunc['db_fetch_assoc']($request)) Chris@76: $postgroups[$row['id_group']] = $row['min_posts']; Chris@76: $smcFunc['db_free_result']($request); Chris@76: Chris@76: // Sort them this way because if it's done with MySQL it causes a filesort :(. Chris@76: arsort($postgroups); Chris@76: Chris@76: cache_put_data('updateStats:postgroups', $postgroups, 360); Chris@76: } Chris@76: Chris@76: // Oh great, they've screwed their post groups. Chris@76: if (empty($postgroups)) Chris@76: return; Chris@76: Chris@76: // Set all membergroups from most posts to least posts. Chris@76: $conditions = ''; Chris@76: foreach ($postgroups as $id => $min_posts) Chris@76: { Chris@76: $conditions .= ' Chris@76: WHEN posts >= ' . $min_posts . (!empty($lastMin) ? ' AND posts <= ' . $lastMin : '') . ' THEN ' . $id; Chris@76: $lastMin = $min_posts; Chris@76: } Chris@76: Chris@76: // A big fat CASE WHEN... END is faster than a zillion UPDATE's ;). Chris@76: $smcFunc['db_query']('', ' Chris@76: UPDATE {db_prefix}members Chris@76: SET id_post_group = CASE ' . $conditions . ' Chris@76: ELSE 0 Chris@76: END' . ($parameter1 != null ? ' Chris@76: WHERE ' . (is_array($parameter1) ? 'id_member IN ({array_int:members})' : 'id_member = {int:members}') : ''), Chris@76: array( Chris@76: 'members' => $parameter1, Chris@76: ) Chris@76: ); Chris@76: break; Chris@76: Chris@76: default: Chris@76: trigger_error('updateStats(): Invalid statistic type \'' . $type . '\'', E_USER_NOTICE); Chris@76: } Chris@76: } Chris@76: Chris@76: // Assumes the data has been htmlspecialchar'd. Chris@76: function updateMemberData($members, $data) Chris@76: { Chris@76: global $modSettings, $user_info, $smcFunc; Chris@76: Chris@76: $parameters = array(); Chris@76: if (is_array($members)) Chris@76: { Chris@76: $condition = 'id_member IN ({array_int:members})'; Chris@76: $parameters['members'] = $members; Chris@76: } Chris@76: elseif ($members === null) Chris@76: $condition = '1=1'; Chris@76: else Chris@76: { Chris@76: $condition = 'id_member = {int:member}'; Chris@76: $parameters['member'] = $members; Chris@76: } Chris@76: Chris@76: if (!empty($modSettings['integrate_change_member_data'])) Chris@76: { Chris@76: // Only a few member variables are really interesting for integration. Chris@76: $integration_vars = array( Chris@76: 'member_name', Chris@76: 'real_name', Chris@76: 'email_address', Chris@76: 'id_group', Chris@76: 'gender', Chris@76: 'birthdate', Chris@76: 'website_title', Chris@76: 'website_url', Chris@76: 'location', Chris@76: 'hide_email', Chris@76: 'time_format', Chris@76: 'time_offset', Chris@76: 'avatar', Chris@76: 'lngfile', Chris@76: ); Chris@76: $vars_to_integrate = array_intersect($integration_vars, array_keys($data)); Chris@76: Chris@76: // Only proceed if there are any variables left to call the integration function. Chris@76: if (count($vars_to_integrate) != 0) Chris@76: { Chris@76: // Fetch a list of member_names if necessary Chris@76: if ((!is_array($members) && $members === $user_info['id']) || (is_array($members) && count($members) == 1 && in_array($user_info['id'], $members))) Chris@76: $member_names = array($user_info['username']); Chris@76: else Chris@76: { Chris@76: $member_names = array(); Chris@76: $request = $smcFunc['db_query']('', ' Chris@76: SELECT member_name Chris@76: FROM {db_prefix}members Chris@76: WHERE ' . $condition, Chris@76: $parameters Chris@76: ); Chris@76: while ($row = $smcFunc['db_fetch_assoc']($request)) Chris@76: $member_names[] = $row['member_name']; Chris@76: $smcFunc['db_free_result']($request); Chris@76: } Chris@76: Chris@76: if (!empty($member_names)) Chris@76: foreach ($vars_to_integrate as $var) Chris@76: call_integration_hook('integrate_change_member_data', array($member_names, $var, $data[$var])); Chris@76: } Chris@76: } Chris@76: Chris@76: // Everything is assumed to be a string unless it's in the below. Chris@76: $knownInts = array( Chris@76: 'date_registered', 'posts', 'id_group', 'last_login', 'instant_messages', 'unread_messages', Chris@76: 'new_pm', 'pm_prefs', 'gender', 'hide_email', 'show_online', 'pm_email_notify', 'pm_receive_from', 'karma_good', 'karma_bad', Chris@76: 'notify_announcements', 'notify_send_body', 'notify_regularity', 'notify_types', Chris@76: 'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning', Chris@76: ); Chris@76: $knownFloats = array( Chris@76: 'time_offset', Chris@76: ); Chris@76: Chris@76: $setString = ''; Chris@76: foreach ($data as $var => $val) Chris@76: { Chris@76: $type = 'string'; Chris@76: if (in_array($var, $knownInts)) Chris@76: $type = 'int'; Chris@76: elseif (in_array($var, $knownFloats)) Chris@76: $type = 'float'; Chris@76: elseif ($var == 'birthdate') Chris@76: $type = 'date'; Chris@76: Chris@76: // Doing an increment? Chris@76: if ($type == 'int' && ($val === '+' || $val === '-')) Chris@76: { Chris@76: $val = $var . ' ' . $val . ' 1'; Chris@76: $type = 'raw'; Chris@76: } Chris@76: Chris@76: // Ensure posts, instant_messages, and unread_messages don't overflow or underflow. Chris@76: if (in_array($var, array('posts', 'instant_messages', 'unread_messages'))) Chris@76: { Chris@76: if (preg_match('~^' . $var . ' (\+ |- |\+ -)([\d]+)~', $val, $match)) Chris@76: { Chris@76: if ($match[1] != '+ ') Chris@76: $val = 'CASE WHEN ' . $var . ' <= ' . abs($match[2]) . ' THEN 0 ELSE ' . $val . ' END'; Chris@76: $type = 'raw'; Chris@76: } Chris@76: } Chris@76: Chris@76: $setString .= ' ' . $var . ' = {' . $type . ':p_' . $var . '},'; Chris@76: $parameters['p_' . $var] = $val; Chris@76: } Chris@76: Chris@76: $smcFunc['db_query']('', ' Chris@76: UPDATE {db_prefix}members Chris@76: SET' . substr($setString, 0, -1) . ' Chris@76: WHERE ' . $condition, Chris@76: $parameters Chris@76: ); Chris@76: Chris@76: updateStats('postgroups', $members, array_keys($data)); Chris@76: Chris@76: // Clear any caching? Chris@76: if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2 && !empty($members)) Chris@76: { Chris@76: if (!is_array($members)) Chris@76: $members = array($members); Chris@76: Chris@76: foreach ($members as $member) Chris@76: { Chris@76: if ($modSettings['cache_enable'] >= 3) Chris@76: { Chris@76: cache_put_data('member_data-profile-' . $member, null, 120); Chris@76: cache_put_data('member_data-normal-' . $member, null, 120); Chris@76: cache_put_data('member_data-minimal-' . $member, null, 120); Chris@76: } Chris@76: cache_put_data('user_settings-' . $member, null, 60); Chris@76: } Chris@76: } Chris@76: } Chris@76: Chris@76: // Updates the settings table as well as $modSettings... only does one at a time if $update is true. Chris@76: function updateSettings($changeArray, $update = false, $debug = false) Chris@76: { Chris@76: global $modSettings, $smcFunc; Chris@76: Chris@76: if (empty($changeArray) || !is_array($changeArray)) Chris@76: return; Chris@76: Chris@76: // In some cases, this may be better and faster, but for large sets we don't want so many UPDATEs. Chris@76: if ($update) Chris@76: { Chris@76: foreach ($changeArray as $variable => $value) Chris@76: { Chris@76: $smcFunc['db_query']('', ' Chris@76: UPDATE {db_prefix}settings Chris@76: SET value = {' . ($value === false || $value === true ? 'raw' : 'string') . ':value} Chris@76: WHERE variable = {string:variable}', Chris@76: array( Chris@76: 'value' => $value === true ? 'value + 1' : ($value === false ? 'value - 1' : $value), Chris@76: 'variable' => $variable, Chris@76: ) Chris@76: ); Chris@76: $modSettings[$variable] = $value === true ? $modSettings[$variable] + 1 : ($value === false ? $modSettings[$variable] - 1 : $value); Chris@76: } Chris@76: Chris@76: // Clean out the cache and make sure the cobwebs are gone too. Chris@76: cache_put_data('modSettings', null, 90); Chris@76: Chris@76: return; Chris@76: } Chris@76: Chris@76: $replaceArray = array(); Chris@76: foreach ($changeArray as $variable => $value) Chris@76: { Chris@76: // Don't bother if it's already like that ;). Chris@76: if (isset($modSettings[$variable]) && $modSettings[$variable] == $value) Chris@76: continue; Chris@76: // If the variable isn't set, but would only be set to nothing'ness, then don't bother setting it. Chris@76: elseif (!isset($modSettings[$variable]) && empty($value)) Chris@76: continue; Chris@76: Chris@76: $replaceArray[] = array($variable, $value); Chris@76: Chris@76: $modSettings[$variable] = $value; Chris@76: } Chris@76: Chris@76: if (empty($replaceArray)) Chris@76: return; Chris@76: Chris@76: $smcFunc['db_insert']('replace', Chris@76: '{db_prefix}settings', Chris@76: array('variable' => 'string-255', 'value' => 'string-65534'), Chris@76: $replaceArray, Chris@76: array('variable') Chris@76: ); Chris@76: Chris@76: // Kill the cache - it needs redoing now, but we won't bother ourselves with that here. Chris@76: cache_put_data('modSettings', null, 90); Chris@76: } Chris@76: Chris@76: // Constructs a page list. Chris@76: // $pageindex = constructPageIndex($scripturl . '?board=' . $board, $_REQUEST['start'], $num_messages, $maxindex, true); Chris@76: function constructPageIndex($base_url, &$start, $max_value, $num_per_page, $flexible_start = false) Chris@76: { Chris@76: global $modSettings; Chris@76: Chris@76: // Save whether $start was less than 0 or not. Chris@76: $start = (int) $start; Chris@76: $start_invalid = $start < 0; Chris@76: Chris@76: // Make sure $start is a proper variable - not less than 0. Chris@76: if ($start_invalid) Chris@76: $start = 0; Chris@76: // Not greater than the upper bound. Chris@76: elseif ($start >= $max_value) Chris@76: $start = max(0, (int) $max_value - (((int) $max_value % (int) $num_per_page) == 0 ? $num_per_page : ((int) $max_value % (int) $num_per_page))); Chris@76: // And it has to be a multiple of $num_per_page! Chris@76: else Chris@76: $start = max(0, (int) $start - ((int) $start % (int) $num_per_page)); Chris@76: Chris@76: // Wireless will need the protocol on the URL somewhere. Chris@76: if (WIRELESS) Chris@76: $base_url .= ';' . WIRELESS_PROTOCOL; Chris@76: Chris@76: $base_link = '%2$s '; Chris@76: Chris@76: // Compact pages is off or on? Chris@76: if (empty($modSettings['compactTopicPagesEnable'])) Chris@76: { Chris@76: // Show the left arrow. Chris@76: $pageindex = $start == 0 ? ' ' : sprintf($base_link, $start - $num_per_page, '«'); Chris@76: Chris@76: // Show all the pages. Chris@76: $display_page = 1; Chris@76: for ($counter = 0; $counter < $max_value; $counter += $num_per_page) Chris@76: $pageindex .= $start == $counter && !$start_invalid ? '' . $display_page++ . ' ' : sprintf($base_link, $counter, $display_page++); Chris@76: Chris@76: // Show the right arrow. Chris@76: $display_page = ($start + $num_per_page) > $max_value ? $max_value : ($start + $num_per_page); Chris@76: if ($start != $counter - $max_value && !$start_invalid) Chris@76: $pageindex .= $display_page > $counter - $num_per_page ? ' ' : sprintf($base_link, $display_page, '»'); Chris@76: } Chris@76: else Chris@76: { Chris@76: // If they didn't enter an odd value, pretend they did. Chris@76: $PageContiguous = (int) ($modSettings['compactTopicPagesContiguous'] - ($modSettings['compactTopicPagesContiguous'] % 2)) / 2; Chris@76: Chris@76: // Show the first page. (>1< ... 6 7 [8] 9 10 ... 15) Chris@76: if ($start > $num_per_page * $PageContiguous) Chris@76: $pageindex = sprintf($base_link, 0, '1'); Chris@76: else Chris@76: $pageindex = ''; Chris@76: Chris@76: // Show the ... after the first page. (1 >...< 6 7 [8] 9 10 ... 15) Chris@76: if ($start > $num_per_page * ($PageContiguous + 1)) Chris@76: $pageindex .= ' ... '; Chris@76: Chris@76: // Show the pages before the current one. (1 ... >6 7< [8] 9 10 ... 15) Chris@76: for ($nCont = $PageContiguous; $nCont >= 1; $nCont--) Chris@76: if ($start >= $num_per_page * $nCont) Chris@76: { Chris@76: $tmpStart = $start - $num_per_page * $nCont; Chris@76: $pageindex.= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1); Chris@76: } Chris@76: Chris@76: // Show the current page. (1 ... 6 7 >[8]< 9 10 ... 15) Chris@76: if (!$start_invalid) Chris@76: $pageindex .= '[' . ($start / $num_per_page + 1) . '] '; Chris@76: else Chris@76: $pageindex .= sprintf($base_link, $start, $start / $num_per_page + 1); Chris@76: Chris@76: // Show the pages after the current one... (1 ... 6 7 [8] >9 10< ... 15) Chris@76: $tmpMaxPages = (int) (($max_value - 1) / $num_per_page) * $num_per_page; Chris@76: for ($nCont = 1; $nCont <= $PageContiguous; $nCont++) Chris@76: if ($start + $num_per_page * $nCont <= $tmpMaxPages) Chris@76: { Chris@76: $tmpStart = $start + $num_per_page * $nCont; Chris@76: $pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1); Chris@76: } Chris@76: Chris@76: // Show the '...' part near the end. (1 ... 6 7 [8] 9 10 >...< 15) Chris@76: if ($start + $num_per_page * ($PageContiguous + 1) < $tmpMaxPages) Chris@76: $pageindex .= ' ... '; Chris@76: Chris@76: // Show the last number in the list. (1 ... 6 7 [8] 9 10 ... >15<) Chris@76: if ($start + $num_per_page * $PageContiguous < $tmpMaxPages) Chris@76: $pageindex .= sprintf($base_link, $tmpMaxPages, $tmpMaxPages / $num_per_page + 1); Chris@76: } Chris@76: Chris@76: return $pageindex; Chris@76: } Chris@76: Chris@76: // Formats a number to display in the style of the admin's choosing. Chris@76: function comma_format($number, $override_decimal_count = false) Chris@76: { Chris@76: global $txt; Chris@76: static $thousands_separator = null, $decimal_separator = null, $decimal_count = null; Chris@76: Chris@76: // !!! Should, perhaps, this just be handled in the language files, and not a mod setting? Chris@76: // (French uses 1 234,00 for example... what about a multilingual forum?) Chris@76: Chris@76: // Cache these values... Chris@76: if ($decimal_separator === null) Chris@76: { Chris@76: // Not set for whatever reason? Chris@76: if (empty($txt['number_format']) || preg_match('~^1([^\d]*)?234([^\d]*)(0*?)$~', $txt['number_format'], $matches) != 1) Chris@76: return $number; Chris@76: Chris@76: // Cache these each load... Chris@76: $thousands_separator = $matches[1]; Chris@76: $decimal_separator = $matches[2]; Chris@76: $decimal_count = strlen($matches[3]); Chris@76: } Chris@76: Chris@76: // Format the string with our friend, number_format. Chris@76: return number_format($number, is_float($number) ? ($override_decimal_count === false ? $decimal_count : $override_decimal_count) : 0, $decimal_separator, $thousands_separator); Chris@76: } Chris@76: Chris@76: // Format a time to make it look purdy. Chris@76: function timeformat($log_time, $show_today = true, $offset_type = false) Chris@76: { Chris@76: global $context, $user_info, $txt, $modSettings, $smcFunc; Chris@76: static $non_twelve_hour; Chris@76: Chris@76: // Offset the time. Chris@76: if (!$offset_type) Chris@76: $time = $log_time + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600; Chris@76: // Just the forum offset? Chris@76: elseif ($offset_type == 'forum') Chris@76: $time = $log_time + $modSettings['time_offset'] * 3600; Chris@76: else Chris@76: $time = $log_time; Chris@76: Chris@76: // We can't have a negative date (on Windows, at least.) Chris@76: if ($log_time < 0) Chris@76: $log_time = 0; Chris@76: Chris@76: // Today and Yesterday? Chris@76: if ($modSettings['todayMod'] >= 1 && $show_today === true) Chris@76: { Chris@76: // Get the current time. Chris@76: $nowtime = forum_time(); Chris@76: Chris@76: $then = @getdate($time); Chris@76: $now = @getdate($nowtime); Chris@76: Chris@76: // Try to make something of a time format string... Chris@76: $s = strpos($user_info['time_format'], '%S') === false ? '' : ':%S'; Chris@76: if (strpos($user_info['time_format'], '%H') === false && strpos($user_info['time_format'], '%T') === false) Chris@76: { Chris@76: $h = strpos($user_info['time_format'], '%l') === false ? '%I' : '%l'; Chris@76: $today_fmt = $h . ':%M' . $s . ' %p'; Chris@76: } Chris@76: else Chris@76: $today_fmt = '%H:%M' . $s; Chris@76: Chris@76: // Same day of the year, same year.... Today! Chris@76: if ($then['yday'] == $now['yday'] && $then['year'] == $now['year']) Chris@76: return $txt['today'] . timeformat($log_time, $today_fmt, $offset_type); Chris@76: Chris@76: // 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... Chris@76: 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)) Chris@76: return $txt['yesterday'] . timeformat($log_time, $today_fmt, $offset_type); Chris@76: } Chris@76: Chris@76: $str = !is_bool($show_today) ? $show_today : $user_info['time_format']; Chris@76: Chris@76: if (setlocale(LC_TIME, $txt['lang_locale'])) Chris@76: { Chris@76: if (!isset($non_twelve_hour)) Chris@76: $non_twelve_hour = trim(strftime('%p')) === ''; Chris@76: if ($non_twelve_hour && strpos($str, '%p') !== false) Chris@76: $str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str); Chris@76: Chris@76: foreach (array('%a', '%A', '%b', '%B') as $token) Chris@76: if (strpos($str, $token) !== false) Chris@76: $str = str_replace($token, !empty($txt['lang_capitalize_dates']) ? $smcFunc['ucwords'](strftime($token, $time)) : strftime($token, $time), $str); Chris@76: } Chris@76: else Chris@76: { Chris@76: // Do-it-yourself time localization. Fun. Chris@76: foreach (array('%a' => 'days_short', '%A' => 'days', '%b' => 'months_short', '%B' => 'months') as $token => $text_label) Chris@76: if (strpos($str, $token) !== false) Chris@76: $str = str_replace($token, $txt[$text_label][(int) strftime($token === '%a' || $token === '%A' ? '%w' : '%m', $time)], $str); Chris@76: Chris@76: if (strpos($str, '%p') !== false) Chris@76: $str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str); Chris@76: } Chris@76: Chris@76: // Windows doesn't support %e; on some versions, strftime fails altogether if used, so let's prevent that. Chris@76: if ($context['server']['is_windows'] && strpos($str, '%e') !== false) Chris@76: $str = str_replace('%e', ltrim(strftime('%d', $time), '0'), $str); Chris@76: Chris@76: // Format any other characters.. Chris@76: return strftime($str, $time); Chris@76: } Chris@76: Chris@76: // Removes special entities from strings. Compatibility... Chris@76: function un_htmlspecialchars($string) Chris@76: { Chris@76: static $translation; Chris@76: Chris@76: if (!isset($translation)) Chris@76: $translation = array_flip(get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES)) + array(''' => '\'', ' ' => ' '); Chris@76: Chris@76: return strtr($string, $translation); Chris@76: } Chris@76: Chris@76: // Shorten a subject + internationalization concerns. Chris@76: function shorten_subject($subject, $len) Chris@76: { Chris@76: global $smcFunc; Chris@76: Chris@76: // It was already short enough! Chris@76: if ($smcFunc['strlen']($subject) <= $len) Chris@76: return $subject; Chris@76: Chris@76: // Shorten it by the length it was too long, and strip off junk from the end. Chris@76: return $smcFunc['substr']($subject, 0, $len) . '...'; Chris@76: } Chris@76: Chris@76: // The current time with offset. Chris@76: function forum_time($use_user_offset = true, $timestamp = null) Chris@76: { Chris@76: global $user_info, $modSettings; Chris@76: Chris@76: if ($timestamp === null) Chris@76: $timestamp = time(); Chris@76: elseif ($timestamp == 0) Chris@76: return 0; Chris@76: Chris@76: return $timestamp + ($modSettings['time_offset'] + ($use_user_offset ? $user_info['time_offset'] : 0)) * 3600; Chris@76: } Chris@76: Chris@76: // This gets all possible permutations of an array. Chris@76: function permute($array) Chris@76: { Chris@76: $orders = array($array); Chris@76: Chris@76: $n = count($array); Chris@76: $p = range(0, $n); Chris@76: for ($i = 1; $i < $n; null) Chris@76: { Chris@76: $p[$i]--; Chris@76: $j = $i % 2 != 0 ? $p[$i] : 0; Chris@76: Chris@76: $temp = $array[$i]; Chris@76: $array[$i] = $array[$j]; Chris@76: $array[$j] = $temp; Chris@76: Chris@76: for ($i = 1; $p[$i] == 0; $i++) Chris@76: $p[$i] = 1; Chris@76: Chris@76: $orders[] = $array; Chris@76: } Chris@76: Chris@76: return $orders; Chris@76: } Chris@76: Chris@76: // Parse bulletin board code in a string, as well as smileys optionally. Chris@76: function parse_bbc($message, $smileys = true, $cache_id = '', $parse_tags = array()) Chris@76: { Chris@76: global $txt, $scripturl, $context, $modSettings, $user_info, $smcFunc; Chris@76: static $bbc_codes = array(), $itemcodes = array(), $no_autolink_tags = array(); Chris@76: static $disabled; Chris@76: Chris@76: // Don't waste cycles Chris@76: if ($message === '') Chris@76: return ''; Chris@76: Chris@76: // Never show smileys for wireless clients. More bytes, can't see it anyway :P. Chris@76: if (WIRELESS) Chris@76: $smileys = false; Chris@76: elseif ($smileys !== null && ($smileys == '1' || $smileys == '0')) Chris@76: $smileys = (bool) $smileys; Chris@76: Chris@76: if (empty($modSettings['enableBBC']) && $message !== false) Chris@76: { Chris@76: if ($smileys === true) Chris@76: parsesmileys($message); Chris@76: Chris@76: return $message; Chris@76: } Chris@76: Chris@76: // Just in case it wasn't determined yet whether UTF-8 is enabled. Chris@76: if (!isset($context['utf8'])) Chris@76: $context['utf8'] = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8'; Chris@76: Chris@76: // If we are not doing every tag then we don't cache this run. Chris@76: if (!empty($parse_tags) && !empty($bbc_codes)) Chris@76: { Chris@76: $temp_bbc = $bbc_codes; Chris@76: $bbc_codes = array(); Chris@76: } Chris@76: Chris@76: // Sift out the bbc for a performance improvement. Chris@76: if (empty($bbc_codes) || $message === false || !empty($parse_tags)) Chris@76: { Chris@76: if (!empty($modSettings['disabledBBC'])) Chris@76: { Chris@76: $temp = explode(',', strtolower($modSettings['disabledBBC'])); Chris@76: Chris@76: foreach ($temp as $tag) Chris@76: $disabled[trim($tag)] = true; Chris@76: } Chris@76: Chris@76: if (empty($modSettings['enableEmbeddedFlash'])) Chris@76: $disabled['flash'] = true; Chris@76: Chris@76: /* The following bbc are formatted as an array, with keys as follows: Chris@76: Chris@76: tag: the tag's name - should be lowercase! Chris@76: Chris@76: type: one of... Chris@76: - (missing): [tag]parsed content[/tag] Chris@76: - unparsed_equals: [tag=xyz]parsed content[/tag] Chris@76: - parsed_equals: [tag=parsed data]parsed content[/tag] Chris@76: - unparsed_content: [tag]unparsed content[/tag] Chris@76: - closed: [tag], [tag/], [tag /] Chris@76: - unparsed_commas: [tag=1,2,3]parsed content[/tag] Chris@76: - unparsed_commas_content: [tag=1,2,3]unparsed content[/tag] Chris@76: - unparsed_equals_content: [tag=...]unparsed content[/tag] Chris@76: Chris@76: parameters: an optional array of parameters, for the form Chris@76: [tag abc=123]content[/tag]. The array is an associative array Chris@76: where the keys are the parameter names, and the values are an Chris@76: array which may contain the following: Chris@76: - match: a regular expression to validate and match the value. Chris@76: - quoted: true if the value should be quoted. Chris@76: - validate: callback to evaluate on the data, which is $data. Chris@76: - value: a string in which to replace $1 with the data. Chris@76: either it or validate may be used, not both. Chris@76: - optional: true if the parameter is optional. Chris@76: Chris@76: test: a regular expression to test immediately after the tag's Chris@76: '=', ' ' or ']'. Typically, should have a \] at the end. Chris@76: Optional. Chris@76: Chris@76: content: only available for unparsed_content, closed, Chris@76: unparsed_commas_content, and unparsed_equals_content. Chris@76: $1 is replaced with the content of the tag. Parameters Chris@76: are replaced in the form {param}. For unparsed_commas_content, Chris@76: $2, $3, ..., $n are replaced. Chris@76: Chris@76: before: only when content is not used, to go before any Chris@76: content. For unparsed_equals, $1 is replaced with the value. Chris@76: For unparsed_commas, $1, $2, ..., $n are replaced. Chris@76: Chris@76: after: similar to before in every way, except that it is used Chris@76: when the tag is closed. Chris@76: Chris@76: disabled_content: used in place of content when the tag is Chris@76: disabled. For closed, default is '', otherwise it is '$1' if Chris@76: block_level is false, '
$1
' elsewise. Chris@76: Chris@76: disabled_before: used in place of before when disabled. Defaults Chris@76: to '
' if block_level, '' if not. Chris@76: Chris@76: disabled_after: used in place of after when disabled. Defaults Chris@76: to '
' if block_level, '' if not. Chris@76: Chris@76: block_level: set to true the tag is a "block level" tag, similar Chris@76: to HTML. Block level tags cannot be nested inside tags that are Chris@76: not block level, and will not be implicitly closed as easily. Chris@76: One break following a block level tag may also be removed. Chris@76: Chris@76: trim: if set, and 'inside' whitespace after the begin tag will be Chris@76: removed. If set to 'outside', whitespace after the end tag will Chris@76: meet the same fate. Chris@76: Chris@76: validate: except when type is missing or 'closed', a callback to Chris@76: validate the data as $data. Depending on the tag's type, $data Chris@76: may be a string or an array of strings (corresponding to the Chris@76: replacement.) Chris@76: Chris@76: quoted: when type is 'unparsed_equals' or 'parsed_equals' only, Chris@76: may be not set, 'optional', or 'required' corresponding to if Chris@76: the content may be quoted. This allows the parser to read Chris@76: [tag="abc]def[esdf]"] properly. Chris@76: Chris@76: require_parents: an array of tag names, or not set. If set, the Chris@76: enclosing tag *must* be one of the listed tags, or parsing won't Chris@76: occur. Chris@76: Chris@76: require_children: similar to require_parents, if set children Chris@76: won't be parsed if they are not in the list. Chris@76: Chris@76: disallow_children: similar to, but very different from, Chris@76: require_children, if it is set the listed tags will not be Chris@76: parsed inside the tag. Chris@76: Chris@76: parsed_tags_allowed: an array restricting what BBC can be in the Chris@76: parsed_equals parameter, if desired. Chris@76: */ Chris@76: Chris@76: $codes = array( Chris@76: array( Chris@76: 'tag' => 'abbr', Chris@76: 'type' => 'unparsed_equals', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: 'quoted' => 'optional', Chris@76: 'disabled_after' => ' ($1)', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'acronym', Chris@76: 'type' => 'unparsed_equals', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: 'quoted' => 'optional', Chris@76: 'disabled_after' => ' ($1)', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'anchor', Chris@76: 'type' => 'unparsed_equals', Chris@76: 'test' => '[#]?([A-Za-z][A-Za-z0-9_\-]*)\]', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'b', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'bdo', Chris@76: 'type' => 'unparsed_equals', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: 'test' => '(rtl|ltr)\]', Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'black', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'blue', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'br', Chris@76: 'type' => 'closed', Chris@76: 'content' => '
', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'center', Chris@76: 'before' => '
', Chris@76: 'after' => '
', Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'code', Chris@76: 'type' => 'unparsed_content', Chris@76: 'content' => '
' . $txt['code'] . ': ' . $txt['code_select'] . '
' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '
' : '') . '$1' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '
' : ''), Chris@76: // !!! Maybe this can be simplified? Chris@76: 'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', ' Chris@76: global $context; Chris@76: Chris@76: if (!isset($disabled[\'code\'])) Chris@76: { Chris@76: $php_parts = preg_split(\'~(<\?php|\?>)~\', $data, -1, PREG_SPLIT_DELIM_CAPTURE); Chris@76: Chris@76: for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++) Chris@76: { Chris@76: // Do PHP code coloring? Chris@76: if ($php_parts[$php_i] != \'<?php\') Chris@76: continue; Chris@76: Chris@76: $php_string = \'\'; Chris@76: while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?>\') Chris@76: { Chris@76: $php_string .= $php_parts[$php_i]; Chris@76: $php_parts[$php_i++] = \'\'; Chris@76: } Chris@76: $php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]); Chris@76: } Chris@76: Chris@76: // Fix the PHP code stuff... Chris@76: $data = str_replace("
\t
", "\t", implode(\'\', $php_parts)); Chris@76: Chris@76: // Older browsers are annoying, aren\'t they? Chris@76: if ($context[\'browser\'][\'is_ie4\'] || $context[\'browser\'][\'is_ie5\'] || $context[\'browser\'][\'is_ie5.5\']) Chris@76: $data = str_replace("\t", "
\t
", $data); Chris@76: else Chris@76: $data = str_replace("\t", "\t", $data); Chris@76: Chris@76: // Recent Opera bug requiring temporary fix. &nsbp; is needed before to avoid broken selection. Chris@76: if ($context[\'browser\'][\'is_opera\']) Chris@76: $data .= \' \'; Chris@76: }'), Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'code', Chris@76: 'type' => 'unparsed_equals_content', Chris@76: 'content' => '
' . $txt['code'] . ': ($2) ' . $txt['code_select'] . '
' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '
' : '') . '$1' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '
' : ''), Chris@76: // !!! Maybe this can be simplified? Chris@76: 'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', ' Chris@76: global $context; Chris@76: Chris@76: if (!isset($disabled[\'code\'])) Chris@76: { Chris@76: $php_parts = preg_split(\'~(<\?php|\?>)~\', $data[0], -1, PREG_SPLIT_DELIM_CAPTURE); Chris@76: Chris@76: for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++) Chris@76: { Chris@76: // Do PHP code coloring? Chris@76: if ($php_parts[$php_i] != \'<?php\') Chris@76: continue; Chris@76: Chris@76: $php_string = \'\'; Chris@76: while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?>\') Chris@76: { Chris@76: $php_string .= $php_parts[$php_i]; Chris@76: $php_parts[$php_i++] = \'\'; Chris@76: } Chris@76: $php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]); Chris@76: } Chris@76: Chris@76: // Fix the PHP code stuff... Chris@76: $data[0] = str_replace("
\t
", "\t", implode(\'\', $php_parts)); Chris@76: Chris@76: // Older browsers are annoying, aren\'t they? Chris@76: if ($context[\'browser\'][\'is_ie4\'] || $context[\'browser\'][\'is_ie5\'] || $context[\'browser\'][\'is_ie5.5\']) Chris@76: $data[0] = str_replace("\t", "
\t
", $data[0]); Chris@76: else Chris@76: $data[0] = str_replace("\t", "\t", $data[0]); Chris@76: Chris@76: // Recent Opera bug requiring temporary fix. &nsbp; is needed before to avoid broken selection. Chris@76: if ($context[\'browser\'][\'is_opera\']) Chris@76: $data[0] .= \' \'; Chris@76: }'), Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'color', Chris@76: 'type' => 'unparsed_equals', Chris@76: 'test' => '(#[\da-fA-F]{3}|#[\da-fA-F]{6}|[A-Za-z]{1,20}|rgb\(\d{1,3}, ?\d{1,3}, ?\d{1,3}\))\]', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'email', Chris@76: 'type' => 'unparsed_content', Chris@76: 'content' => '$1', Chris@76: // !!! Should this respect guest_hideContacts? Chris@76: 'validate' => create_function('&$tag, &$data, $disabled', '$data = strtr($data, array(\'
\' => \'\'));'), Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'email', Chris@76: 'type' => 'unparsed_equals', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: // !!! Should this respect guest_hideContacts? Chris@76: 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), Chris@76: 'disabled_after' => ' ($1)', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'flash', Chris@76: 'type' => 'unparsed_commas_content', Chris@76: 'test' => '\d+,\d+\]', Chris@76: 'content' => ($context['browser']['is_ie'] && !$context['browser']['is_mac_ie'] ? '<a href="$1" target="_blank" class="new_win">$1</a>' : '<a href="$1" target="_blank" class="new_win">$1</a>'), Chris@76: 'validate' => create_function('&$tag, &$data, $disabled', ' Chris@76: if (isset($disabled[\'url\'])) Chris@76: $tag[\'content\'] = \'$1\'; Chris@76: elseif (strpos($data[0], \'http://\') !== 0 && strpos($data[0], \'https://\') !== 0) Chris@76: $data[0] = \'http://\' . $data[0]; Chris@76: '), Chris@76: 'disabled_content' => '$1', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'font', Chris@76: 'type' => 'unparsed_equals', Chris@76: 'test' => '[A-Za-z0-9_,\-\s]+?\]', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'ftp', Chris@76: 'type' => 'unparsed_content', Chris@76: 'content' => '$1', Chris@76: 'validate' => create_function('&$tag, &$data, $disabled', ' Chris@76: $data = strtr($data, array(\'
\' => \'\')); Chris@76: if (strpos($data, \'ftp://\') !== 0 && strpos($data, \'ftps://\') !== 0) Chris@76: $data = \'ftp://\' . $data; Chris@76: '), Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'ftp', Chris@76: 'type' => 'unparsed_equals', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: 'validate' => create_function('&$tag, &$data, $disabled', ' Chris@76: if (strpos($data, \'ftp://\') !== 0 && strpos($data, \'ftps://\') !== 0) Chris@76: $data = \'ftp://\' . $data; Chris@76: '), Chris@76: 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), Chris@76: 'disabled_after' => ' ($1)', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'glow', Chris@76: 'type' => 'unparsed_commas', Chris@76: 'test' => '[#0-9a-zA-Z\-]{3,12},([012]\d{1,2}|\d{1,2})(,[^]]+)?\]', Chris@76: 'before' => $context['browser']['is_ie'] ? '
' : '', Chris@76: 'after' => $context['browser']['is_ie'] ? '
' : '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'green', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'html', Chris@76: 'type' => 'unparsed_content', Chris@76: 'content' => '$1', Chris@76: 'block_level' => true, Chris@76: 'disabled_content' => '$1', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'hr', Chris@76: 'type' => 'closed', Chris@76: 'content' => '
', Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'i', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'img', Chris@76: 'type' => 'unparsed_content', Chris@76: 'parameters' => array( Chris@76: 'alt' => array('optional' => true), Chris@76: 'width' => array('optional' => true, 'value' => ' width="$1"', 'match' => '(\d+)'), Chris@76: 'height' => array('optional' => true, 'value' => ' height="$1"', 'match' => '(\d+)'), Chris@76: ), Chris@76: 'content' => '{alt}', Chris@76: 'validate' => create_function('&$tag, &$data, $disabled', ' Chris@76: $data = strtr($data, array(\'
\' => \'\')); Chris@76: if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0) Chris@76: $data = \'http://\' . $data; Chris@76: '), Chris@76: 'disabled_content' => '($1)', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'img', Chris@76: 'type' => 'unparsed_content', Chris@76: 'content' => '', Chris@76: 'validate' => create_function('&$tag, &$data, $disabled', ' Chris@76: $data = strtr($data, array(\'
\' => \'\')); Chris@76: if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0) Chris@76: $data = \'http://\' . $data; Chris@76: '), Chris@76: 'disabled_content' => '($1)', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'iurl', Chris@76: 'type' => 'unparsed_content', Chris@76: 'content' => '$1', Chris@76: 'validate' => create_function('&$tag, &$data, $disabled', ' Chris@76: $data = strtr($data, array(\'
\' => \'\')); Chris@76: if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0) Chris@76: $data = \'http://\' . $data; Chris@76: '), Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'iurl', Chris@76: 'type' => 'unparsed_equals', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: 'validate' => create_function('&$tag, &$data, $disabled', ' Chris@76: if (substr($data, 0, 1) == \'#\') Chris@76: $data = \'#post_\' . substr($data, 1); Chris@76: elseif (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0) Chris@76: $data = \'http://\' . $data; Chris@76: '), Chris@76: 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), Chris@76: 'disabled_after' => ' ($1)', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'left', Chris@76: 'before' => '
', Chris@76: 'after' => '
', Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'li', Chris@76: 'before' => '
  • ', Chris@76: 'after' => '
  • ', Chris@76: 'trim' => 'outside', Chris@76: 'require_parents' => array('list'), Chris@76: 'block_level' => true, Chris@76: 'disabled_before' => '', Chris@76: 'disabled_after' => '
    ', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'list', Chris@76: 'before' => '', Chris@76: 'trim' => 'inside', Chris@76: 'require_children' => array('li', 'list'), Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'list', Chris@76: 'parameters' => array( Chris@76: 'type' => array('match' => '(none|disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-alpha|upper-alpha|lower-greek|lower-latin|upper-latin|hebrew|armenian|georgian|cjk-ideographic|hiragana|katakana|hiragana-iroha|katakana-iroha)'), Chris@76: ), Chris@76: 'before' => '', Chris@76: 'trim' => 'inside', Chris@76: 'require_children' => array('li'), Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'ltr', Chris@76: 'before' => '
    ', Chris@76: 'after' => '
    ', Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'me', Chris@76: 'type' => 'unparsed_equals', Chris@76: 'before' => '
    * $1 ', Chris@76: 'after' => '
    ', Chris@76: 'quoted' => 'optional', Chris@76: 'block_level' => true, Chris@76: 'disabled_before' => '/me ', Chris@76: 'disabled_after' => '
    ', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'move', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: 'block_level' => true, Chris@76: 'disallow_children' => array('move'), Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'nobbc', Chris@76: 'type' => 'unparsed_content', Chris@76: 'content' => '$1', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'php', Chris@76: 'type' => 'unparsed_content', Chris@76: 'content' => '$1', Chris@76: 'validate' => isset($disabled['php']) ? null : create_function('&$tag, &$data, $disabled', ' Chris@76: if (!isset($disabled[\'php\'])) Chris@76: { Chris@76: $add_begin = substr(trim($data), 0, 5) != \'<?\'; Chris@76: $data = highlight_php_code($add_begin ? \'<?php \' . $data . \'?>\' : $data); Chris@76: if ($add_begin) Chris@76: $data = preg_replace(array(\'~^(.+?)<\?.{0,40}?php(?: |\s)~\', \'~\?>((?:)*)$~\'), \'$1\', $data, 2); Chris@76: }'), Chris@76: 'block_level' => false, Chris@76: 'disabled_content' => '$1', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'pre', Chris@76: 'before' => '
    ',
    Chris@76: 				'after' => '
    ', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'quote', Chris@76: 'before' => '
    ' . $txt['quote'] . '
    ', Chris@76: 'after' => '
    ', Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'quote', Chris@76: 'parameters' => array( Chris@76: 'author' => array('match' => '(.{1,192}?)', 'quoted' => true), Chris@76: ), Chris@76: 'before' => '
    ' . $txt['quote_from'] . ': {author}
    ', Chris@76: 'after' => '
    ', Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'quote', Chris@76: 'type' => 'parsed_equals', Chris@76: 'before' => '
    ' . $txt['quote_from'] . ': $1
    ', Chris@76: 'after' => '
    ', Chris@76: 'quoted' => 'optional', Chris@76: // Don't allow everything to be embedded with the author name. Chris@76: 'parsed_tags_allowed' => array('url', 'iurl', 'ftp'), Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'quote', Chris@76: 'parameters' => array( Chris@76: 'author' => array('match' => '([^<>]{1,192}?)'), Chris@76: 'link' => array('match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|action=profile;u=\d+)'), Chris@76: 'date' => array('match' => '(\d+)', 'validate' => 'timeformat'), Chris@76: ), Chris@76: 'before' => '
    ' . $txt['quote_from'] . ': {author} ' . $txt['search_on'] . ' {date}
    ', Chris@76: 'after' => '
    ', Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'quote', Chris@76: 'parameters' => array( Chris@76: 'author' => array('match' => '(.{1,192}?)'), Chris@76: ), Chris@76: 'before' => '
    ' . $txt['quote_from'] . ': {author}
    ', Chris@76: 'after' => '
    ', Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'red', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'right', Chris@76: 'before' => '
    ', Chris@76: 'after' => '
    ', Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'rtl', Chris@76: 'before' => '
    ', Chris@76: 'after' => '
    ', Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 's', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'shadow', Chris@76: 'type' => 'unparsed_commas', Chris@76: 'test' => '[#0-9a-zA-Z\-]{3,12},(left|right|top|bottom|[0123]\d{0,2})\]', Chris@76: 'before' => $context['browser']['is_ie'] ? '' : '', Chris@76: 'after' => '', Chris@76: 'validate' => $context['browser']['is_ie'] ? create_function('&$tag, &$data, $disabled', ' Chris@76: if ($data[1] == \'left\') Chris@76: $data[1] = 270; Chris@76: elseif ($data[1] == \'right\') Chris@76: $data[1] = 90; Chris@76: elseif ($data[1] == \'top\') Chris@76: $data[1] = 0; Chris@76: elseif ($data[1] == \'bottom\') Chris@76: $data[1] = 180; Chris@76: else Chris@76: $data[1] = (int) $data[1];') : create_function('&$tag, &$data, $disabled', ' Chris@76: if ($data[1] == \'top\' || (is_numeric($data[1]) && $data[1] < 50)) Chris@76: $data[1] = \'0 -2px 1px\'; Chris@76: elseif ($data[1] == \'right\' || (is_numeric($data[1]) && $data[1] < 100)) Chris@76: $data[1] = \'2px 0 1px\'; Chris@76: elseif ($data[1] == \'bottom\' || (is_numeric($data[1]) && $data[1] < 190)) Chris@76: $data[1] = \'0 2px 1px\'; Chris@76: elseif ($data[1] == \'left\' || (is_numeric($data[1]) && $data[1] < 280)) Chris@76: $data[1] = \'-2px 0 1px\'; Chris@76: else Chris@76: $data[1] = \'1px 1px 1px\';'), Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'size', Chris@76: 'type' => 'unparsed_equals', Chris@76: 'test' => '([1-9][\d]?p[xt]|small(?:er)?|large[r]?|x[x]?-(?:small|large)|medium|(0\.[1-9]|[1-9](\.[\d][\d]?)?)?em)\]', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'size', Chris@76: 'type' => 'unparsed_equals', Chris@76: 'test' => '[1-7]\]', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: 'validate' => create_function('&$tag, &$data, $disabled', ' Chris@76: $sizes = array(1 => 0.7, 2 => 1.0, 3 => 1.35, 4 => 1.45, 5 => 2.0, 6 => 2.65, 7 => 3.95); Chris@76: $data = $sizes[$data] . \'em\';' Chris@76: ), Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'sub', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'sup', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'table', Chris@76: 'before' => '', Chris@76: 'after' => '
    ', Chris@76: 'trim' => 'inside', Chris@76: 'require_children' => array('tr'), Chris@76: 'block_level' => true, Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'td', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: 'require_parents' => array('tr'), Chris@76: 'trim' => 'outside', Chris@76: 'block_level' => true, Chris@76: 'disabled_before' => '', Chris@76: 'disabled_after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'time', Chris@76: 'type' => 'unparsed_content', Chris@76: 'content' => '$1', Chris@76: 'validate' => create_function('&$tag, &$data, $disabled', ' Chris@76: if (is_numeric($data)) Chris@76: $data = timeformat($data); Chris@76: else Chris@76: $tag[\'content\'] = \'[time]$1[/time]\';'), Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'tr', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: 'require_parents' => array('table'), Chris@76: 'require_children' => array('td'), Chris@76: 'trim' => 'both', Chris@76: 'block_level' => true, Chris@76: 'disabled_before' => '', Chris@76: 'disabled_after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'tt', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'u', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'url', Chris@76: 'type' => 'unparsed_content', Chris@76: 'content' => '$1', Chris@76: 'validate' => create_function('&$tag, &$data, $disabled', ' Chris@76: $data = strtr($data, array(\'
    \' => \'\')); Chris@76: if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0) Chris@76: $data = \'http://\' . $data; Chris@76: '), Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'url', Chris@76: 'type' => 'unparsed_equals', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: 'validate' => create_function('&$tag, &$data, $disabled', ' Chris@76: if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0) Chris@76: $data = \'http://\' . $data; Chris@76: '), Chris@76: 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), Chris@76: 'disabled_after' => ' ($1)', Chris@76: ), Chris@76: array( Chris@76: 'tag' => 'white', Chris@76: 'before' => '', Chris@76: 'after' => '', Chris@76: ), Chris@76: ); Chris@76: Chris@76: // Let mods add new BBC without hassle. Chris@76: call_integration_hook('integrate_bbc_codes', array(&$codes)); Chris@76: Chris@76: // This is mainly for the bbc manager, so it's easy to add tags above. Custom BBC should be added above this line. Chris@76: if ($message === false) Chris@76: { Chris@76: if (isset($temp_bbc)) Chris@76: $bbc_codes = $temp_bbc; Chris@76: return $codes; Chris@76: } Chris@76: Chris@76: // So the parser won't skip them. Chris@76: $itemcodes = array( Chris@76: '*' => 'disc', Chris@76: '@' => 'disc', Chris@76: '+' => 'square', Chris@76: 'x' => 'square', Chris@76: '#' => 'square', Chris@76: 'o' => 'circle', Chris@76: 'O' => 'circle', Chris@76: '0' => 'circle', Chris@76: ); Chris@76: if (!isset($disabled['li']) && !isset($disabled['list'])) Chris@76: { Chris@76: foreach ($itemcodes as $c => $dummy) Chris@76: $bbc_codes[$c] = array(); Chris@76: } Chris@76: Chris@76: // Inside these tags autolink is not recommendable. Chris@76: $no_autolink_tags = array( Chris@76: 'url', Chris@76: 'iurl', Chris@76: 'ftp', Chris@76: 'email', Chris@76: ); Chris@76: Chris@76: // Shhhh! Chris@76: if (!isset($disabled['color'])) Chris@76: { Chris@76: $codes[] = array( Chris@76: 'tag' => 'chrissy', Chris@76: 'before' => '', Chris@76: 'after' => ' :-*', Chris@76: ); Chris@76: $codes[] = array( Chris@76: 'tag' => 'kissy', Chris@76: 'before' => '', Chris@76: 'after' => ' :-*', Chris@76: ); Chris@76: } Chris@76: Chris@76: foreach ($codes as $code) Chris@76: { Chris@76: // If we are not doing every tag only do ones we are interested in. Chris@76: if (empty($parse_tags) || in_array($code['tag'], $parse_tags)) Chris@76: $bbc_codes[substr($code['tag'], 0, 1)][] = $code; Chris@76: } Chris@76: $codes = null; Chris@76: } Chris@76: Chris@76: // Shall we take the time to cache this? Chris@76: if ($cache_id != '' && !empty($modSettings['cache_enable']) && (($modSettings['cache_enable'] >= 2 && strlen($message) > 1000) || strlen($message) > 2400) && empty($parse_tags)) Chris@76: { Chris@76: // It's likely this will change if the message is modified. Chris@76: $cache_key = 'parse:' . $cache_id . '-' . md5(md5($message) . '-' . $smileys . (empty($disabled) ? '' : implode(',', array_keys($disabled))) . serialize($context['browser']) . $txt['lang_locale'] . $user_info['time_offset'] . $user_info['time_format']); Chris@76: Chris@76: if (($temp = cache_get_data($cache_key, 240)) != null) Chris@76: return $temp; Chris@76: Chris@76: $cache_t = microtime(); Chris@76: } Chris@76: Chris@76: if ($smileys === 'print') Chris@76: { Chris@76: // [glow], [shadow], and [move] can't really be printed. Chris@76: $disabled['glow'] = true; Chris@76: $disabled['shadow'] = true; Chris@76: $disabled['move'] = true; Chris@76: Chris@76: // Colors can't well be displayed... supposed to be black and white. Chris@76: $disabled['color'] = true; Chris@76: $disabled['black'] = true; Chris@76: $disabled['blue'] = true; Chris@76: $disabled['white'] = true; Chris@76: $disabled['red'] = true; Chris@76: $disabled['green'] = true; Chris@76: $disabled['me'] = true; Chris@76: Chris@76: // Color coding doesn't make sense. Chris@76: $disabled['php'] = true; Chris@76: Chris@76: // Links are useless on paper... just show the link. Chris@76: $disabled['ftp'] = true; Chris@76: $disabled['url'] = true; Chris@76: $disabled['iurl'] = true; Chris@76: $disabled['email'] = true; Chris@76: $disabled['flash'] = true; Chris@76: Chris@76: // !!! Change maybe? Chris@76: if (!isset($_GET['images'])) Chris@76: $disabled['img'] = true; Chris@76: Chris@76: // !!! Interface/setting to add more? Chris@76: } Chris@76: Chris@76: $open_tags = array(); Chris@76: $message = strtr($message, array("\n" => '
    ')); Chris@76: Chris@76: // The non-breaking-space looks a bit different each time. Chris@76: $non_breaking_space = $context['utf8'] ? ($context['server']['complex_preg_chars'] ? '\x{A0}' : "\xC2\xA0") : '\xA0'; Chris@76: Chris@76: // This saves time by doing our break long words checks here. Chris@76: if (!empty($modSettings['fixLongWords']) && $modSettings['fixLongWords'] > 5) Chris@76: { Chris@76: if ($context['browser']['is_gecko'] || $context['browser']['is_konqueror']) Chris@76: $breaker = ' '; Chris@76: // Opera... Chris@76: elseif ($context['browser']['is_opera']) Chris@76: $breaker = ' '; Chris@76: // Internet Explorer... Chris@76: else Chris@76: $breaker = ' '; Chris@76: Chris@76: // PCRE will not be happy if we don't give it a short. Chris@76: $modSettings['fixLongWords'] = (int) min(65535, $modSettings['fixLongWords']); Chris@76: } Chris@76: Chris@76: $pos = -1; Chris@76: while ($pos !== false) Chris@76: { Chris@76: $last_pos = isset($last_pos) ? max($pos, $last_pos) : $pos; Chris@76: $pos = strpos($message, '[', $pos + 1); Chris@76: Chris@76: // Failsafe. Chris@76: if ($pos === false || $last_pos > $pos) Chris@76: $pos = strlen($message) + 1; Chris@76: Chris@76: // Can't have a one letter smiley, URL, or email! (sorry.) Chris@76: if ($last_pos < $pos - 1) Chris@76: { Chris@76: // Make sure the $last_pos is not negative. Chris@76: $last_pos = max($last_pos, 0); Chris@76: Chris@76: // Pick a block of data to do some raw fixing on. Chris@76: $data = substr($message, $last_pos, $pos - $last_pos); Chris@76: Chris@76: // Take care of some HTML! Chris@76: if (!empty($modSettings['enablePostHTML']) && strpos($data, '<') !== false) Chris@76: { Chris@76: $data = preg_replace('~<a\s+href=((?:")?)((?:https?://|ftps?://|mailto:)\S+?)\\1>~i', '[url=$2]', $data); Chris@76: $data = preg_replace('~</a>~i', '[/url]', $data); Chris@76: Chris@76: //
    should be empty. Chris@76: $empty_tags = array('br', 'hr'); Chris@76: foreach ($empty_tags as $tag) Chris@76: $data = str_replace(array('<' . $tag . '>', '<' . $tag . '/>', '<' . $tag . ' />'), '[' . $tag . ' /]', $data); Chris@76: Chris@76: // b, u, i, s, pre... basic tags. Chris@76: $closable_tags = array('b', 'u', 'i', 's', 'em', 'ins', 'del', 'pre', 'blockquote'); Chris@76: foreach ($closable_tags as $tag) Chris@76: { Chris@76: $diff = substr_count($data, '<' . $tag . '>') - substr_count($data, '</' . $tag . '>'); Chris@76: $data = strtr($data, array('<' . $tag . '>' => '<' . $tag . '>', '</' . $tag . '>' => '')); Chris@76: Chris@76: if ($diff > 0) Chris@76: $data = substr($data, 0, -1) . str_repeat('', $diff) . substr($data, -1); Chris@76: } Chris@76: Chris@76: // Do - with security... action= -> action-. Chris@76: preg_match_all('~<img\s+src=((?:")?)((?:https?://|ftps?://)\S+?)\\1(?:\s+alt=(".*?"|\S*?))?(?:\s?/)?>~i', $data, $matches, PREG_PATTERN_ORDER); Chris@76: if (!empty($matches[0])) Chris@76: { Chris@76: $replaces = array(); Chris@76: foreach ($matches[2] as $match => $imgtag) Chris@76: { Chris@76: $alt = empty($matches[3][$match]) ? '' : ' alt=' . preg_replace('~^"|"$~', '', $matches[3][$match]); Chris@76: Chris@76: // Remove action= from the URL - no funny business, now. Chris@76: if (preg_match('~action(=|%3d)(?!dlattach)~i', $imgtag) != 0) Chris@76: $imgtag = preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $imgtag); Chris@76: Chris@76: // Check if the image is larger than allowed. Chris@76: if (!empty($modSettings['max_image_width']) && !empty($modSettings['max_image_height'])) Chris@76: { Chris@76: list ($width, $height) = url_image_size($imgtag); Chris@76: Chris@76: if (!empty($modSettings['max_image_width']) && $width > $modSettings['max_image_width']) Chris@76: { Chris@76: $height = (int) (($modSettings['max_image_width'] * $height) / $width); Chris@76: $width = $modSettings['max_image_width']; Chris@76: } Chris@76: Chris@76: if (!empty($modSettings['max_image_height']) && $height > $modSettings['max_image_height']) Chris@76: { Chris@76: $width = (int) (($modSettings['max_image_height'] * $width) / $height); Chris@76: $height = $modSettings['max_image_height']; Chris@76: } Chris@76: Chris@76: // Set the new image tag. Chris@76: $replaces[$matches[0][$match]] = '[img width=' . $width . ' height=' . $height . $alt . ']' . $imgtag . '[/img]'; Chris@76: } Chris@76: else Chris@76: $replaces[$matches[0][$match]] = '[img' . $alt . ']' . $imgtag . '[/img]'; Chris@76: } Chris@76: Chris@76: $data = strtr($data, $replaces); Chris@76: } Chris@76: } Chris@76: Chris@76: if (!empty($modSettings['autoLinkUrls'])) Chris@76: { Chris@76: // Are we inside tags that should be auto linked? Chris@76: $no_autolink_area = false; Chris@76: if (!empty($open_tags)) Chris@76: { Chris@76: foreach ($open_tags as $open_tag) Chris@76: if (in_array($open_tag['tag'], $no_autolink_tags)) Chris@76: $no_autolink_area = true; Chris@76: } Chris@76: Chris@76: // Don't go backwards. Chris@76: //!!! Don't think is the real solution.... Chris@76: $lastAutoPos = isset($lastAutoPos) ? $lastAutoPos : 0; Chris@76: if ($pos < $lastAutoPos) Chris@76: $no_autolink_area = true; Chris@76: $lastAutoPos = $pos; Chris@76: Chris@76: if (!$no_autolink_area) Chris@76: { Chris@76: // Parse any URLs.... have to get rid of the @ problems some things cause... stupid email addresses. Chris@76: if (!isset($disabled['url']) && (strpos($data, '://') !== false || strpos($data, 'www.') !== false) && strpos($data, '[url') === false) Chris@76: { Chris@76: // Switch out quotes really quick because they can cause problems. Chris@76: $data = strtr($data, array(''' => '\'', ' ' => $context['utf8'] ? "\xC2\xA0" : "\xA0", '"' => '>">', '"' => '<"<', '<' => '\.(;\'"]|^)((?:http|https)://[\w\-_%@:|]+(?:\.[\w\-_%]+)*(?::\d+)?(?:/[\w\-_\~%\.@!,\?&;=#(){}+:\'\\\\]*)*[/\w\-_\~%@\?;=#}\\\\])~i', Chris@76: '~(?<=[\s>\.(;\'"]|^)((?:ftp|ftps)://[\w\-_%@:|]+(?:\.[\w\-_%]+)*(?::\d+)?(?:/[\w\-_\~%\.@,\?&;=#(){}+:\'\\\\]*)*[/\w\-_\~%@\?;=#}\\\\])~i', Chris@76: '~(?<=[\s>(\'<]|^)(www(?:\.[\w\-_]+)+(?::\d+)?(?:/[\w\-_\~%\.@!,\?&;=#(){}+:\'\\\\]*)*[/\w\-_\~%@\?;=#}\\\\])~i' Chris@76: ), array( Chris@76: '[url]$1[/url]', Chris@76: '[ftp]$1[/ftp]', Chris@76: '[url=http://$1]$1[/url]' Chris@76: ), $data))) Chris@76: $data = $result; Chris@76: Chris@76: $data = strtr($data, array('\'' => ''', $context['utf8'] ? "\xC2\xA0" : "\xA0" => ' ', '>">' => '"', '<"<' => '"', ' '<')); Chris@76: } Chris@76: Chris@76: // Next, emails... Chris@76: if (!isset($disabled['email']) && strpos($data, '@') !== false && strpos($data, '[email') === false) Chris@76: { Chris@76: $data = preg_replace('~(?<=[\?\s' . $non_breaking_space . '\[\]()*\\\;>]|^)([\w\-\.]{1,80}@[\w\-]+\.[\w\-\.]+[\w\-])(?=[?,\s' . $non_breaking_space . '\[\]()*\\\]|$|
    | |>|<|"|'|\.(?:\.|;| |\s|$|
    ))~' . ($context['utf8'] ? 'u' : ''), '[email]$1[/email]', $data); Chris@76: $data = preg_replace('~(?<=
    )([\w\-\.]{1,80}@[\w\-]+\.[\w\-\.]+[\w\-])(?=[?\.,;\s' . $non_breaking_space . '\[\]()*\\\]|$|
    | |>|<|"|')~' . ($context['utf8'] ? 'u' : ''), '[email]$1[/email]', $data); Chris@76: } Chris@76: } Chris@76: } Chris@76: Chris@76: $data = strtr($data, array("\t" => '   ')); Chris@76: Chris@76: if (!empty($modSettings['fixLongWords']) && $modSettings['fixLongWords'] > 5) Chris@76: { Chris@76: // The idea is, find words xx long, and then replace them with xx + space + more. Chris@76: if ($smcFunc['strlen']($data) > $modSettings['fixLongWords']) Chris@76: { Chris@76: // This is done in a roundabout way because $breaker has "long words" :P. Chris@76: $data = strtr($data, array($breaker => '< >', ' ' => $context['utf8'] ? "\xC2\xA0" : "\xA0")); Chris@76: $data = preg_replace( Chris@76: '~(?<=[>;:!? ' . $non_breaking_space . '\]()]|^)([\w' . ($context['utf8'] ? '\pL' : '') . '\.]{' . $modSettings['fixLongWords'] . ',})~e' . ($context['utf8'] ? 'u' : ''), Chris@76: 'preg_replace(\'/(.{' . ($modSettings['fixLongWords'] - 1) . '})/' . ($context['utf8'] ? 'u' : '') . '\', \'\\$1< >\', \'$1\')', Chris@76: $data); Chris@76: $data = strtr($data, array('< >' => $breaker, $context['utf8'] ? "\xC2\xA0" : "\xA0" => ' ')); Chris@76: } Chris@76: } Chris@76: Chris@76: // If it wasn't changed, no copying or other boring stuff has to happen! Chris@76: if ($data != substr($message, $last_pos, $pos - $last_pos)) Chris@76: { Chris@76: $message = substr($message, 0, $last_pos) . $data . substr($message, $pos); Chris@76: Chris@76: // Since we changed it, look again in case we added or removed a tag. But we don't want to skip any. Chris@76: $old_pos = strlen($data) + $last_pos; Chris@76: $pos = strpos($message, '[', $last_pos); Chris@76: $pos = $pos === false ? $old_pos : min($pos, $old_pos); Chris@76: } Chris@76: } Chris@76: Chris@76: // Are we there yet? Are we there yet? Chris@76: if ($pos >= strlen($message) - 1) Chris@76: break; Chris@76: Chris@76: $tags = strtolower(substr($message, $pos + 1, 1)); Chris@76: Chris@76: if ($tags == '/' && !empty($open_tags)) Chris@76: { Chris@76: $pos2 = strpos($message, ']', $pos + 1); Chris@76: if ($pos2 == $pos + 2) Chris@76: continue; Chris@76: $look_for = strtolower(substr($message, $pos + 2, $pos2 - $pos - 2)); Chris@76: Chris@76: $to_close = array(); Chris@76: $block_level = null; Chris@76: do Chris@76: { Chris@76: $tag = array_pop($open_tags); Chris@76: if (!$tag) Chris@76: break; Chris@76: Chris@76: if (!empty($tag['block_level'])) Chris@76: { Chris@76: // Only find out if we need to. Chris@76: if ($block_level === false) Chris@76: { Chris@76: array_push($open_tags, $tag); Chris@76: break; Chris@76: } Chris@76: Chris@76: // The idea is, if we are LOOKING for a block level tag, we can close them on the way. Chris@76: if (strlen($look_for) > 0 && isset($bbc_codes[$look_for[0]])) Chris@76: { Chris@76: foreach ($bbc_codes[$look_for[0]] as $temp) Chris@76: if ($temp['tag'] == $look_for) Chris@76: { Chris@76: $block_level = !empty($temp['block_level']); Chris@76: break; Chris@76: } Chris@76: } Chris@76: Chris@76: if ($block_level !== true) Chris@76: { Chris@76: $block_level = false; Chris@76: array_push($open_tags, $tag); Chris@76: break; Chris@76: } Chris@76: } Chris@76: Chris@76: $to_close[] = $tag; Chris@76: } Chris@76: while ($tag['tag'] != $look_for); Chris@76: Chris@76: // Did we just eat through everything and not find it? Chris@76: if ((empty($open_tags) && (empty($tag) || $tag['tag'] != $look_for))) Chris@76: { Chris@76: $open_tags = $to_close; Chris@76: continue; Chris@76: } Chris@76: elseif (!empty($to_close) && $tag['tag'] != $look_for) Chris@76: { Chris@76: if ($block_level === null && isset($look_for[0], $bbc_codes[$look_for[0]])) Chris@76: { Chris@76: foreach ($bbc_codes[$look_for[0]] as $temp) Chris@76: if ($temp['tag'] == $look_for) Chris@76: { Chris@76: $block_level = !empty($temp['block_level']); Chris@76: break; Chris@76: } Chris@76: } Chris@76: Chris@76: // We're not looking for a block level tag (or maybe even a tag that exists...) Chris@76: if (!$block_level) Chris@76: { Chris@76: foreach ($to_close as $tag) Chris@76: array_push($open_tags, $tag); Chris@76: continue; Chris@76: } Chris@76: } Chris@76: Chris@76: foreach ($to_close as $tag) Chris@76: { Chris@76: $message = substr($message, 0, $pos) . "\n" . $tag['after'] . "\n" . substr($message, $pos2 + 1); Chris@76: $pos += strlen($tag['after']) + 2; Chris@76: $pos2 = $pos - 1; Chris@76: Chris@76: // See the comment at the end of the big loop - just eating whitespace ;). Chris@76: if (!empty($tag['block_level']) && substr($message, $pos, 6) == '
    ') Chris@76: $message = substr($message, 0, $pos) . substr($message, $pos + 6); Chris@76: if (!empty($tag['trim']) && $tag['trim'] != 'inside' && preg_match('~(
    | |\s)*~', substr($message, $pos), $matches) != 0) Chris@76: $message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0])); Chris@76: } Chris@76: Chris@76: if (!empty($to_close)) Chris@76: { Chris@76: $to_close = array(); Chris@76: $pos--; Chris@76: } Chris@76: Chris@76: continue; Chris@76: } Chris@76: Chris@76: // No tags for this character, so just keep going (fastest possible course.) Chris@76: if (!isset($bbc_codes[$tags])) Chris@76: continue; Chris@76: Chris@76: $inside = empty($open_tags) ? null : $open_tags[count($open_tags) - 1]; Chris@76: $tag = null; Chris@76: foreach ($bbc_codes[$tags] as $possible) Chris@76: { Chris@76: // Not a match? Chris@76: if (strtolower(substr($message, $pos + 1, strlen($possible['tag']))) != $possible['tag']) Chris@76: continue; Chris@76: Chris@76: $next_c = substr($message, $pos + 1 + strlen($possible['tag']), 1); Chris@76: Chris@76: // A test validation? Chris@76: if (isset($possible['test']) && preg_match('~^' . $possible['test'] . '~', substr($message, $pos + 1 + strlen($possible['tag']) + 1)) == 0) Chris@76: continue; Chris@76: // Do we want parameters? Chris@76: elseif (!empty($possible['parameters'])) Chris@76: { Chris@76: if ($next_c != ' ') Chris@76: continue; Chris@76: } Chris@76: elseif (isset($possible['type'])) Chris@76: { Chris@76: // Do we need an equal sign? Chris@76: if (in_array($possible['type'], array('unparsed_equals', 'unparsed_commas', 'unparsed_commas_content', 'unparsed_equals_content', 'parsed_equals')) && $next_c != '=') Chris@76: continue; Chris@76: // Maybe we just want a /... Chris@76: if ($possible['type'] == 'closed' && $next_c != ']' && substr($message, $pos + 1 + strlen($possible['tag']), 2) != '/]' && substr($message, $pos + 1 + strlen($possible['tag']), 3) != ' /]') Chris@76: continue; Chris@76: // An immediate ]? Chris@76: if ($possible['type'] == 'unparsed_content' && $next_c != ']') Chris@76: continue; Chris@76: } Chris@76: // No type means 'parsed_content', which demands an immediate ] without parameters! Chris@76: elseif ($next_c != ']') Chris@76: continue; Chris@76: Chris@76: // Check allowed tree? Chris@76: if (isset($possible['require_parents']) && ($inside === null || !in_array($inside['tag'], $possible['require_parents']))) Chris@76: continue; Chris@76: elseif (isset($inside['require_children']) && !in_array($possible['tag'], $inside['require_children'])) Chris@76: continue; Chris@76: // If this is in the list of disallowed child tags, don't parse it. Chris@76: elseif (isset($inside['disallow_children']) && in_array($possible['tag'], $inside['disallow_children'])) Chris@76: continue; Chris@76: Chris@76: $pos1 = $pos + 1 + strlen($possible['tag']) + 1; Chris@76: Chris@76: // Quotes can have alternate styling, we do this php-side due to all the permutations of quotes. Chris@76: if ($possible['tag'] == 'quote') Chris@76: { Chris@76: // Start with standard Chris@76: $quote_alt = false; Chris@76: foreach ($open_tags as $open_quote) Chris@76: { Chris@76: // Every parent quote this quote has flips the styling Chris@76: if ($open_quote['tag'] == 'quote') Chris@76: $quote_alt = !$quote_alt; Chris@76: } Chris@76: // Add a class to the quote to style alternating blockquotes Chris@76: $possible['before'] = strtr($possible['before'], array('
    ' => '
    ')); Chris@76: } Chris@76: Chris@76: // This is long, but it makes things much easier and cleaner. Chris@76: if (!empty($possible['parameters'])) Chris@76: { Chris@76: $preg = array(); Chris@76: foreach ($possible['parameters'] as $p => $info) Chris@76: $preg[] = '(\s+' . $p . '=' . (empty($info['quoted']) ? '' : '"') . (isset($info['match']) ? $info['match'] : '(.+?)') . (empty($info['quoted']) ? '' : '"') . ')' . (empty($info['optional']) ? '' : '?'); Chris@76: Chris@76: // 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. Chris@76: $match = false; Chris@76: $orders = permute($preg); Chris@76: foreach ($orders as $p) Chris@76: if (preg_match('~^' . implode('', $p) . '\]~i', substr($message, $pos1 - 1), $matches) != 0) Chris@76: { Chris@76: $match = true; Chris@76: break; Chris@76: } Chris@76: Chris@76: // Didn't match our parameter list, try the next possible. Chris@76: if (!$match) Chris@76: continue; Chris@76: Chris@76: $params = array(); Chris@76: for ($i = 1, $n = count($matches); $i < $n; $i += 2) Chris@76: { Chris@76: $key = strtok(ltrim($matches[$i]), '='); Chris@76: if (isset($possible['parameters'][$key]['value'])) Chris@76: $params['{' . $key . '}'] = strtr($possible['parameters'][$key]['value'], array('$1' => $matches[$i + 1])); Chris@76: elseif (isset($possible['parameters'][$key]['validate'])) Chris@76: $params['{' . $key . '}'] = $possible['parameters'][$key]['validate']($matches[$i + 1]); Chris@76: else Chris@76: $params['{' . $key . '}'] = $matches[$i + 1]; Chris@76: Chris@76: // Just to make sure: replace any $ or { so they can't interpolate wrongly. Chris@76: $params['{' . $key . '}'] = strtr($params['{' . $key . '}'], array('$' => '$', '{' => '{')); Chris@76: } Chris@76: Chris@76: foreach ($possible['parameters'] as $p => $info) Chris@76: { Chris@76: if (!isset($params['{' . $p . '}'])) Chris@76: $params['{' . $p . '}'] = ''; Chris@76: } Chris@76: Chris@76: $tag = $possible; Chris@76: Chris@76: // Put the parameters into the string. Chris@76: if (isset($tag['before'])) Chris@76: $tag['before'] = strtr($tag['before'], $params); Chris@76: if (isset($tag['after'])) Chris@76: $tag['after'] = strtr($tag['after'], $params); Chris@76: if (isset($tag['content'])) Chris@76: $tag['content'] = strtr($tag['content'], $params); Chris@76: Chris@76: $pos1 += strlen($matches[0]) - 1; Chris@76: } Chris@76: else Chris@76: $tag = $possible; Chris@76: break; Chris@76: } Chris@76: Chris@76: // Item codes are complicated buggers... they are implicit [li]s and can make [list]s! Chris@76: if ($smileys !== false && $tag === null && isset($itemcodes[substr($message, $pos + 1, 1)]) && substr($message, $pos + 2, 1) == ']' && !isset($disabled['list']) && !isset($disabled['li'])) Chris@76: { Chris@76: if (substr($message, $pos + 1, 1) == '0' && !in_array(substr($message, $pos - 1, 1), array(';', ' ', "\t", '>'))) Chris@76: continue; Chris@76: $tag = $itemcodes[substr($message, $pos + 1, 1)]; Chris@76: Chris@76: // First let's set up the tree: it needs to be in a list, or after an li. Chris@76: if ($inside === null || ($inside['tag'] != 'list' && $inside['tag'] != 'li')) Chris@76: { Chris@76: $open_tags[] = array( Chris@76: 'tag' => 'list', Chris@76: 'after' => '', Chris@76: 'block_level' => true, Chris@76: 'require_children' => array('li'), Chris@76: 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null, Chris@76: ); Chris@76: $code = '
      '; Chris@76: } Chris@76: // We're in a list item already: another itemcode? Close it first. Chris@76: elseif ($inside['tag'] == 'li') Chris@76: { Chris@76: array_pop($open_tags); Chris@76: $code = ''; Chris@76: } Chris@76: else Chris@76: $code = ''; Chris@76: Chris@76: // Now we open a new tag. Chris@76: $open_tags[] = array( Chris@76: 'tag' => 'li', Chris@76: 'after' => '', Chris@76: 'trim' => 'outside', Chris@76: 'block_level' => true, Chris@76: 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null, Chris@76: ); Chris@76: Chris@76: // First, open the tag... Chris@76: $code .= ''; Chris@76: $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos + 3); Chris@76: $pos += strlen($code) - 1 + 2; Chris@76: Chris@76: // Next, find the next break (if any.) If there's more itemcode after it, keep it going - otherwise close! Chris@76: $pos2 = strpos($message, '
      ', $pos); Chris@76: $pos3 = strpos($message, '[/', $pos); Chris@76: if ($pos2 !== false && ($pos2 <= $pos3 || $pos3 === false)) Chris@76: { Chris@76: preg_match('~^(
      | |\s|\[)+~', substr($message, $pos2 + 6), $matches); Chris@76: $message = substr($message, 0, $pos2) . "\n" . (!empty($matches[0]) && substr($matches[0], -1) == '[' ? '[/li]' : '[/li][/list]') . "\n" . substr($message, $pos2); Chris@76: Chris@76: $open_tags[count($open_tags) - 2]['after'] = '
    '; Chris@76: } Chris@76: // Tell the [list] that it needs to close specially. Chris@76: else Chris@76: { Chris@76: // Move the li over, because we're not sure what we'll hit. Chris@76: $open_tags[count($open_tags) - 1]['after'] = ''; Chris@76: $open_tags[count($open_tags) - 2]['after'] = ''; Chris@76: } Chris@76: Chris@76: continue; Chris@76: } Chris@76: Chris@76: // Implicitly close lists and tables if something other than what's required is in them. This is needed for itemcode. Chris@76: if ($tag === null && $inside !== null && !empty($inside['require_children'])) Chris@76: { Chris@76: array_pop($open_tags); Chris@76: Chris@76: $message = substr($message, 0, $pos) . "\n" . $inside['after'] . "\n" . substr($message, $pos); Chris@76: $pos += strlen($inside['after']) - 1 + 2; Chris@76: } Chris@76: Chris@76: // No tag? Keep looking, then. Silly people using brackets without actual tags. Chris@76: if ($tag === null) Chris@76: continue; Chris@76: Chris@76: // Propagate the list to the child (so wrapping the disallowed tag won't work either.) Chris@76: if (isset($inside['disallow_children'])) Chris@76: $tag['disallow_children'] = isset($tag['disallow_children']) ? array_unique(array_merge($tag['disallow_children'], $inside['disallow_children'])) : $inside['disallow_children']; Chris@76: Chris@76: // Is this tag disabled? Chris@76: if (isset($disabled[$tag['tag']])) Chris@76: { Chris@76: if (!isset($tag['disabled_before']) && !isset($tag['disabled_after']) && !isset($tag['disabled_content'])) Chris@76: { Chris@76: $tag['before'] = !empty($tag['block_level']) ? '
    ' : ''; Chris@76: $tag['after'] = !empty($tag['block_level']) ? '
    ' : ''; Chris@76: $tag['content'] = isset($tag['type']) && $tag['type'] == 'closed' ? '' : (!empty($tag['block_level']) ? '
    $1
    ' : '$1'); Chris@76: } Chris@76: elseif (isset($tag['disabled_before']) || isset($tag['disabled_after'])) Chris@76: { Chris@76: $tag['before'] = isset($tag['disabled_before']) ? $tag['disabled_before'] : (!empty($tag['block_level']) ? '
    ' : ''); Chris@76: $tag['after'] = isset($tag['disabled_after']) ? $tag['disabled_after'] : (!empty($tag['block_level']) ? '
    ' : ''); Chris@76: } Chris@76: else Chris@76: $tag['content'] = $tag['disabled_content']; Chris@76: } Chris@76: Chris@76: // The only special case is 'html', which doesn't need to close things. Chris@76: if (!empty($tag['block_level']) && $tag['tag'] != 'html' && empty($inside['block_level'])) Chris@76: { Chris@76: $n = count($open_tags) - 1; Chris@76: while (empty($open_tags[$n]['block_level']) && $n >= 0) Chris@76: $n--; Chris@76: Chris@76: // Close all the non block level tags so this tag isn't surrounded by them. Chris@76: for ($i = count($open_tags) - 1; $i > $n; $i--) Chris@76: { Chris@76: $message = substr($message, 0, $pos) . "\n" . $open_tags[$i]['after'] . "\n" . substr($message, $pos); Chris@76: $pos += strlen($open_tags[$i]['after']) + 2; Chris@76: $pos1 += strlen($open_tags[$i]['after']) + 2; Chris@76: Chris@76: // Trim or eat trailing stuff... see comment at the end of the big loop. Chris@76: if (!empty($open_tags[$i]['block_level']) && substr($message, $pos, 6) == '
    ') Chris@76: $message = substr($message, 0, $pos) . substr($message, $pos + 6); Chris@76: if (!empty($open_tags[$i]['trim']) && $tag['trim'] != 'inside' && preg_match('~(
    | |\s)*~', substr($message, $pos), $matches) != 0) Chris@76: $message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0])); Chris@76: Chris@76: array_pop($open_tags); Chris@76: } Chris@76: } Chris@76: Chris@76: // No type means 'parsed_content'. Chris@76: if (!isset($tag['type'])) Chris@76: { Chris@76: // !!! Check for end tag first, so people can say "I like that [i] tag"? Chris@76: $open_tags[] = $tag; Chris@76: $message = substr($message, 0, $pos) . "\n" . $tag['before'] . "\n" . substr($message, $pos1); Chris@76: $pos += strlen($tag['before']) - 1 + 2; Chris@76: } Chris@76: // Don't parse the content, just skip it. Chris@76: elseif ($tag['type'] == 'unparsed_content') Chris@76: { Chris@76: $pos2 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos1); Chris@76: if ($pos2 === false) Chris@76: continue; Chris@76: Chris@76: $data = substr($message, $pos1, $pos2 - $pos1); Chris@76: Chris@76: if (!empty($tag['block_level']) && substr($data, 0, 6) == '
    ') Chris@76: $data = substr($data, 6); Chris@76: Chris@76: if (isset($tag['validate'])) Chris@76: $tag['validate']($tag, $data, $disabled); Chris@76: Chris@76: $code = strtr($tag['content'], array('$1' => $data)); Chris@76: $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 3 + strlen($tag['tag'])); Chris@76: Chris@76: $pos += strlen($code) - 1 + 2; Chris@76: $last_pos = $pos + 1; Chris@76: Chris@76: } Chris@76: // Don't parse the content, just skip it. Chris@76: elseif ($tag['type'] == 'unparsed_equals_content') Chris@76: { Chris@76: // The value may be quoted for some tags - check. Chris@76: if (isset($tag['quoted'])) Chris@76: { Chris@76: $quoted = substr($message, $pos1, 6) == '"'; Chris@76: if ($tag['quoted'] != 'optional' && !$quoted) Chris@76: continue; Chris@76: Chris@76: if ($quoted) Chris@76: $pos1 += 6; Chris@76: } Chris@76: else Chris@76: $quoted = false; Chris@76: Chris@76: $pos2 = strpos($message, $quoted == false ? ']' : '"]', $pos1); Chris@76: if ($pos2 === false) Chris@76: continue; Chris@76: $pos3 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos2); Chris@76: if ($pos3 === false) Chris@76: continue; Chris@76: Chris@76: $data = array( Chris@76: substr($message, $pos2 + ($quoted == false ? 1 : 7), $pos3 - ($pos2 + ($quoted == false ? 1 : 7))), Chris@76: substr($message, $pos1, $pos2 - $pos1) Chris@76: ); Chris@76: Chris@76: if (!empty($tag['block_level']) && substr($data[0], 0, 6) == '
    ') Chris@76: $data[0] = substr($data[0], 6); Chris@76: Chris@76: // Validation for my parking, please! Chris@76: if (isset($tag['validate'])) Chris@76: $tag['validate']($tag, $data, $disabled); Chris@76: Chris@76: $code = strtr($tag['content'], array('$1' => $data[0], '$2' => $data[1])); Chris@76: $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + strlen($tag['tag'])); Chris@76: $pos += strlen($code) - 1 + 2; Chris@76: } Chris@76: // A closed tag, with no content or value. Chris@76: elseif ($tag['type'] == 'closed') Chris@76: { Chris@76: $pos2 = strpos($message, ']', $pos); Chris@76: $message = substr($message, 0, $pos) . "\n" . $tag['content'] . "\n" . substr($message, $pos2 + 1); Chris@76: $pos += strlen($tag['content']) - 1 + 2; Chris@76: } Chris@76: // This one is sorta ugly... :/. Unfortunately, it's needed for flash. Chris@76: elseif ($tag['type'] == 'unparsed_commas_content') Chris@76: { Chris@76: $pos2 = strpos($message, ']', $pos1); Chris@76: if ($pos2 === false) Chris@76: continue; Chris@76: $pos3 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos2); Chris@76: if ($pos3 === false) Chris@76: continue; Chris@76: Chris@76: // We want $1 to be the content, and the rest to be csv. Chris@76: $data = explode(',', ',' . substr($message, $pos1, $pos2 - $pos1)); Chris@76: $data[0] = substr($message, $pos2 + 1, $pos3 - $pos2 - 1); Chris@76: Chris@76: if (isset($tag['validate'])) Chris@76: $tag['validate']($tag, $data, $disabled); Chris@76: Chris@76: $code = $tag['content']; Chris@76: foreach ($data as $k => $d) Chris@76: $code = strtr($code, array('$' . ($k + 1) => trim($d))); Chris@76: $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + strlen($tag['tag'])); Chris@76: $pos += strlen($code) - 1 + 2; Chris@76: } Chris@76: // This has parsed content, and a csv value which is unparsed. Chris@76: elseif ($tag['type'] == 'unparsed_commas') Chris@76: { Chris@76: $pos2 = strpos($message, ']', $pos1); Chris@76: if ($pos2 === false) Chris@76: continue; Chris@76: Chris@76: $data = explode(',', substr($message, $pos1, $pos2 - $pos1)); Chris@76: Chris@76: if (isset($tag['validate'])) Chris@76: $tag['validate']($tag, $data, $disabled); Chris@76: Chris@76: // Fix after, for disabled code mainly. Chris@76: foreach ($data as $k => $d) Chris@76: $tag['after'] = strtr($tag['after'], array('$' . ($k + 1) => trim($d))); Chris@76: Chris@76: $open_tags[] = $tag; Chris@76: Chris@76: // Replace them out, $1, $2, $3, $4, etc. Chris@76: $code = $tag['before']; Chris@76: foreach ($data as $k => $d) Chris@76: $code = strtr($code, array('$' . ($k + 1) => trim($d))); Chris@76: $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 1); Chris@76: $pos += strlen($code) - 1 + 2; Chris@76: } Chris@76: // A tag set to a value, parsed or not. Chris@76: elseif ($tag['type'] == 'unparsed_equals' || $tag['type'] == 'parsed_equals') Chris@76: { Chris@76: // The value may be quoted for some tags - check. Chris@76: if (isset($tag['quoted'])) Chris@76: { Chris@76: $quoted = substr($message, $pos1, 6) == '"'; Chris@76: if ($tag['quoted'] != 'optional' && !$quoted) Chris@76: continue; Chris@76: Chris@76: if ($quoted) Chris@76: $pos1 += 6; Chris@76: } Chris@76: else Chris@76: $quoted = false; Chris@76: Chris@76: $pos2 = strpos($message, $quoted == false ? ']' : '"]', $pos1); Chris@76: if ($pos2 === false) Chris@76: continue; Chris@76: Chris@76: $data = substr($message, $pos1, $pos2 - $pos1); Chris@76: Chris@76: // Validation for my parking, please! Chris@76: if (isset($tag['validate'])) Chris@76: $tag['validate']($tag, $data, $disabled); Chris@76: Chris@76: // For parsed content, we must recurse to avoid security problems. Chris@76: if ($tag['type'] != 'unparsed_equals') Chris@76: $data = parse_bbc($data, !empty($tag['parsed_tags_allowed']) ? false : true, '', !empty($tag['parsed_tags_allowed']) ? $tag['parsed_tags_allowed'] : array()); Chris@76: Chris@76: $tag['after'] = strtr($tag['after'], array('$1' => $data)); Chris@76: Chris@76: $open_tags[] = $tag; Chris@76: Chris@76: $code = strtr($tag['before'], array('$1' => $data)); Chris@76: $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + ($quoted == false ? 1 : 7)); Chris@76: $pos += strlen($code) - 1 + 2; Chris@76: } Chris@76: Chris@76: // If this is block level, eat any breaks after it. Chris@76: if (!empty($tag['block_level']) && substr($message, $pos + 1, 6) == '
    ') Chris@76: $message = substr($message, 0, $pos + 1) . substr($message, $pos + 7); Chris@76: Chris@76: // Are we trimming outside this tag? Chris@76: if (!empty($tag['trim']) && $tag['trim'] != 'outside' && preg_match('~(
    | |\s)*~', substr($message, $pos + 1), $matches) != 0) Chris@76: $message = substr($message, 0, $pos + 1) . substr($message, $pos + 1 + strlen($matches[0])); Chris@76: } Chris@76: Chris@76: // Close any remaining tags. Chris@76: while ($tag = array_pop($open_tags)) Chris@76: $message .= "\n" . $tag['after'] . "\n"; Chris@76: Chris@76: // Parse the smileys within the parts where it can be done safely. Chris@76: if ($smileys === true) Chris@76: { Chris@76: $message_parts = explode("\n", $message); Chris@76: for ($i = 0, $n = count($message_parts); $i < $n; $i += 2) Chris@76: parsesmileys($message_parts[$i]); Chris@76: Chris@76: $message = implode('', $message_parts); Chris@76: } Chris@76: Chris@76: // No smileys, just get rid of the markers. Chris@76: else Chris@76: $message = strtr($message, array("\n" => '')); Chris@76: Chris@76: if (substr($message, 0, 1) == ' ') Chris@76: $message = ' ' . substr($message, 1); Chris@76: Chris@76: // Cleanup whitespace. Chris@76: $message = strtr($message, array(' ' => '  ', "\r" => '', "\n" => '
    ', '
    ' => '
     ', ' ' => "\n")); Chris@76: Chris@76: // Cache the output if it took some time... Chris@76: if (isset($cache_key, $cache_t) && array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.05) Chris@76: cache_put_data($cache_key, $message, 240); Chris@76: Chris@76: // If this was a force parse revert if needed. Chris@76: if (!empty($parse_tags)) Chris@76: { Chris@76: if (empty($temp_bbc)) Chris@76: $bbc_codes = array(); Chris@76: else Chris@76: { Chris@76: $bbc_codes = $temp_bbc; Chris@76: unset($temp_bbc); Chris@76: } Chris@76: } Chris@76: Chris@76: return $message; Chris@76: } Chris@76: Chris@76: // Parse smileys in the passed message. Chris@76: function parsesmileys(&$message) Chris@76: { Chris@76: global $modSettings, $txt, $user_info, $context, $smcFunc; Chris@76: static $smileyPregSearch = array(), $smileyPregReplacements = array(); Chris@76: Chris@76: // No smiley set at all?! Chris@76: if ($user_info['smiley_set'] == 'none') Chris@76: return; Chris@76: Chris@76: // If the smiley array hasn't been set, do it now. Chris@76: if (empty($smileyPregSearch)) Chris@76: { Chris@76: // Use the default smileys if it is disabled. (better for "portability" of smileys.) Chris@76: if (empty($modSettings['smiley_enable'])) Chris@76: { Chris@76: $smileysfrom = array('>:D', ':D', '::)', '>:(', ':))', ':)', ';)', ';D', ':(', ':o', '8)', ':P', '???', ':-[', ':-X', ':-*', ':\'(', ':-\\', '^-^', 'O0', 'C:-)', '0:)'); Chris@76: $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'); Chris@76: $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'], '', '', '', ''); Chris@76: } Chris@76: else Chris@76: { Chris@76: // Load the smileys in reverse order by length so they don't get parsed wrong. Chris@76: if (($temp = cache_get_data('parsing_smileys', 480)) == null) Chris@76: { Chris@76: $result = $smcFunc['db_query']('', ' Chris@76: SELECT code, filename, description Chris@76: FROM {db_prefix}smileys', Chris@76: array( Chris@76: ) Chris@76: ); Chris@76: $smileysfrom = array(); Chris@76: $smileysto = array(); Chris@76: $smileysdescs = array(); Chris@76: while ($row = $smcFunc['db_fetch_assoc']($result)) Chris@76: { Chris@76: $smileysfrom[] = $row['code']; Chris@76: $smileysto[] = $row['filename']; Chris@76: $smileysdescs[] = $row['description']; Chris@76: } Chris@76: $smcFunc['db_free_result']($result); Chris@76: Chris@76: cache_put_data('parsing_smileys', array($smileysfrom, $smileysto, $smileysdescs), 480); Chris@76: } Chris@76: else Chris@76: list ($smileysfrom, $smileysto, $smileysdescs) = $temp; Chris@76: } Chris@76: Chris@76: // The non-breaking-space is a complex thing... Chris@76: $non_breaking_space = $context['utf8'] ? ($context['server']['complex_preg_chars'] ? '\x{A0}' : "\xC2\xA0") : '\xA0'; Chris@76: Chris@76: // 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) Chris@76: $smileyPregReplacements = array(); Chris@76: $searchParts = array(); Chris@76: for ($i = 0, $n = count($smileysfrom); $i < $n; $i++) Chris@76: { Chris@76: $smileyCode = '' . strtr(htmlspecialchars($smileysfrom[$i], ENT_QUOTES), array(':' => ':', '(' => '(', ')' => ')', '$' => '$', '[' => '[')). ''; Chris@76: Chris@76: $smileyPregReplacements[$smileysfrom[$i]] = $smileyCode; Chris@76: $smileyPregReplacements[htmlspecialchars($smileysfrom[$i], ENT_QUOTES)] = $smileyCode; Chris@76: $searchParts[] = preg_quote($smileysfrom[$i], '~'); Chris@76: $searchParts[] = preg_quote(htmlspecialchars($smileysfrom[$i], ENT_QUOTES), '~'); Chris@76: } Chris@76: Chris@76: $smileyPregSearch = '~(?<=[>:\?\.\s' . $non_breaking_space . '[\]()*\\\;]|^)(' . implode('|', $searchParts) . ')(?=[^[:alpha:]0-9]|$)~e' . ($context['utf8'] ? 'u' : ''); Chris@76: } Chris@76: Chris@76: // Replace away! Chris@76: $message = preg_replace($smileyPregSearch, 'isset($smileyPregReplacements[\'$1\']) ? $smileyPregReplacements[\'$1\'] : \'\'', $message); Chris@76: } Chris@76: Chris@76: // Highlight any code... Chris@76: function highlight_php_code($code) Chris@76: { Chris@76: global $context; Chris@76: Chris@76: // Remove special characters. Chris@76: $code = un_htmlspecialchars(strtr($code, array('
    ' => "\n", "\t" => 'SMF_TAB();', '[' => '['))); Chris@76: Chris@76: $oldlevel = error_reporting(0); Chris@76: Chris@76: // It's easier in 4.2.x+. Chris@76: if (@version_compare(PHP_VERSION, '4.2.0') == -1) Chris@76: { Chris@76: ob_start(); Chris@76: @highlight_string($code); Chris@76: $buffer = str_replace(array("\n", "\r"), '', ob_get_contents()); Chris@76: ob_end_clean(); Chris@76: } Chris@76: else Chris@76: $buffer = str_replace(array("\n", "\r"), '', @highlight_string($code, true)); Chris@76: Chris@76: error_reporting($oldlevel); Chris@76: Chris@76: // Yes, I know this is kludging it, but this is the best way to preserve tabs from PHP :P. Chris@76: $buffer = preg_replace('~SMF_TAB(?:<(?:font color|span style)="[^"]*?">)?\\(\\);~', '
    ' . "\t" . '
    ', $buffer); Chris@76: Chris@76: return strtr($buffer, array('\'' => ''', '' => '', '' => '')); Chris@76: } Chris@76: Chris@76: // Put this user in the online log. Chris@76: function writeLog($force = false) Chris@76: { Chris@76: global $user_info, $user_settings, $context, $modSettings, $settings, $topic, $board, $smcFunc, $sourcedir; Chris@76: Chris@76: // 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. Chris@76: if (!empty($settings['display_who_viewing']) && ($topic || $board)) Chris@76: { Chris@76: // Take the opposite approach! Chris@76: $force = true; Chris@76: // Don't update for every page - this isn't wholly accurate but who cares. Chris@76: if ($topic) Chris@76: { Chris@76: if (isset($_SESSION['last_topic_id']) && $_SESSION['last_topic_id'] == $topic) Chris@76: $force = false; Chris@76: $_SESSION['last_topic_id'] = $topic; Chris@76: } Chris@76: } Chris@76: Chris@76: // Are they a spider we should be tracking? Mode = 1 gets tracked on its spider check... Chris@76: if (!empty($user_info['possibly_robot']) && !empty($modSettings['spider_mode']) && $modSettings['spider_mode'] > 1) Chris@76: { Chris@76: require_once($sourcedir . '/ManageSearchEngines.php'); Chris@76: logSpider(); Chris@76: } Chris@76: Chris@76: // Don't mark them as online more than every so often. Chris@76: if (!empty($_SESSION['log_time']) && $_SESSION['log_time'] >= (time() - 8) && !$force) Chris@76: return; Chris@76: Chris@76: if (!empty($modSettings['who_enabled'])) Chris@76: { Chris@76: $serialized = $_GET + array('USER_AGENT' => $_SERVER['HTTP_USER_AGENT']); Chris@76: Chris@76: // In the case of a dlattach action, session_var may not be set. Chris@76: if (!isset($context['session_var'])) Chris@76: $context['session_var'] = $_SESSION['session_var']; Chris@76: Chris@76: unset($serialized['sesc'], $serialized[$context['session_var']]); Chris@76: $serialized = serialize($serialized); Chris@76: } Chris@76: else Chris@76: $serialized = ''; Chris@76: Chris@76: // Guests use 0, members use their session ID. Chris@76: $session_id = $user_info['is_guest'] ? 'ip' . $user_info['ip'] : session_id(); Chris@76: Chris@76: // Grab the last all-of-SMF-specific log_online deletion time. Chris@76: $do_delete = cache_get_data('log_online-update', 30) < time() - 30; Chris@76: Chris@76: // If the last click wasn't a long time ago, and there was a last click... Chris@76: if (!empty($_SESSION['log_time']) && $_SESSION['log_time'] >= time() - $modSettings['lastActive'] * 20) Chris@76: { Chris@76: if ($do_delete) Chris@76: { Chris@76: $smcFunc['db_query']('delete_log_online_interval', ' Chris@76: DELETE FROM {db_prefix}log_online Chris@76: WHERE log_time < {int:log_time} Chris@76: AND session != {string:session}', Chris@76: array( Chris@76: 'log_time' => time() - $modSettings['lastActive'] * 60, Chris@76: 'session' => $session_id, Chris@76: ) Chris@76: ); Chris@76: Chris@76: // Cache when we did it last. Chris@76: cache_put_data('log_online-update', time(), 30); Chris@76: } Chris@76: Chris@76: $smcFunc['db_query']('', ' Chris@76: UPDATE {db_prefix}log_online Chris@76: SET log_time = {int:log_time}, ip = IFNULL(INET_ATON({string:ip}), 0), url = {string:url} Chris@76: WHERE session = {string:session}', Chris@76: array( Chris@76: 'log_time' => time(), Chris@76: 'ip' => $user_info['ip'], Chris@76: 'url' => $serialized, Chris@76: 'session' => $session_id, Chris@76: ) Chris@76: ); Chris@76: Chris@76: // Guess it got deleted. Chris@76: if ($smcFunc['db_affected_rows']() == 0) Chris@76: $_SESSION['log_time'] = 0; Chris@76: } Chris@76: else Chris@76: $_SESSION['log_time'] = 0; Chris@76: Chris@76: // Otherwise, we have to delete and insert. Chris@76: if (empty($_SESSION['log_time'])) Chris@76: { Chris@76: if ($do_delete || !empty($user_info['id'])) Chris@76: $smcFunc['db_query']('', ' Chris@76: DELETE FROM {db_prefix}log_online Chris@76: WHERE ' . ($do_delete ? 'log_time < {int:log_time}' : '') . ($do_delete && !empty($user_info['id']) ? ' OR ' : '') . (empty($user_info['id']) ? '' : 'id_member = {int:current_member}'), Chris@76: array( Chris@76: 'current_member' => $user_info['id'], Chris@76: 'log_time' => time() - $modSettings['lastActive'] * 60, Chris@76: ) Chris@76: ); Chris@76: Chris@76: $smcFunc['db_insert']($do_delete ? 'ignore' : 'replace', Chris@76: '{db_prefix}log_online', Chris@76: array('session' => 'string', 'id_member' => 'int', 'id_spider' => 'int', 'log_time' => 'int', 'ip' => 'raw', 'url' => 'string'), Chris@76: array($session_id, $user_info['id'], empty($_SESSION['id_robot']) ? 0 : $_SESSION['id_robot'], time(), 'IFNULL(INET_ATON(\'' . $user_info['ip'] . '\'), 0)', $serialized), Chris@76: array('session') Chris@76: ); Chris@76: } Chris@76: Chris@76: // Mark your session as being logged. Chris@76: $_SESSION['log_time'] = time(); Chris@76: Chris@76: // Well, they are online now. Chris@76: if (empty($_SESSION['timeOnlineUpdated'])) Chris@76: $_SESSION['timeOnlineUpdated'] = time(); Chris@76: Chris@76: // Set their login time, if not already done within the last minute. Chris@76: if (SMF != 'SSI' && !empty($user_info['last_login']) && $user_info['last_login'] < time() - 60) Chris@76: { Chris@76: // Don't count longer than 15 minutes. Chris@76: if (time() - $_SESSION['timeOnlineUpdated'] > 60 * 15) Chris@76: $_SESSION['timeOnlineUpdated'] = time(); Chris@76: Chris@76: $user_settings['total_time_logged_in'] += time() - $_SESSION['timeOnlineUpdated']; Chris@76: 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'])); Chris@76: Chris@76: if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2) Chris@76: cache_put_data('user_settings-' . $user_info['id'], $user_settings, 60); Chris@76: Chris@76: $user_info['total_time_logged_in'] += time() - $_SESSION['timeOnlineUpdated']; Chris@76: $_SESSION['timeOnlineUpdated'] = time(); Chris@76: } Chris@76: } Chris@76: Chris@76: // Make sure the browser doesn't come back and repost the form data. Should be used whenever anything is posted. Chris@76: function redirectexit($setLocation = '', $refresh = false) Chris@76: { Chris@76: global $scripturl, $context, $modSettings, $db_show_debug, $db_cache; Chris@76: Chris@76: // In case we have mail to send, better do that - as obExit doesn't always quite make it... Chris@76: if (!empty($context['flush_mail'])) Chris@76: AddMailQueue(true); Chris@76: Chris@76: $add = preg_match('~^(ftp|http)[s]?://~', $setLocation) == 0 && substr($setLocation, 0, 6) != 'about:'; Chris@76: Chris@76: if (WIRELESS) Chris@76: { Chris@76: // Add the scripturl on if needed. Chris@76: if ($add) Chris@76: $setLocation = $scripturl . '?' . $setLocation; Chris@76: Chris@76: $char = strpos($setLocation, '?') === false ? '?' : ';'; Chris@76: Chris@76: if (strpos($setLocation, '#') !== false) Chris@76: $setLocation = strtr($setLocation, array('#' => $char . WIRELESS_PROTOCOL . '#')); Chris@76: else Chris@76: $setLocation .= $char . WIRELESS_PROTOCOL; Chris@76: } Chris@76: elseif ($add) Chris@76: $setLocation = $scripturl . ($setLocation != '' ? '?' . $setLocation : ''); Chris@76: Chris@76: // Put the session ID in. Chris@76: if (defined('SID') && SID != '') Chris@76: $setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation); Chris@76: // Keep that debug in their for template debugging! Chris@76: elseif (isset($_GET['debug'])) Chris@76: $setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation); Chris@76: Chris@76: 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']))) Chris@76: { Chris@76: if (defined('SID') && SID != '') Chris@76: $setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&))((?:board|topic)=[^#]+?)(#[^"]*?)?$/e', "\$scripturl . '/' . strtr('\$1', '&;=', '//,') . '.html\$2?' . SID", $setLocation); Chris@76: else Chris@76: $setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$/e', "\$scripturl . '/' . strtr('\$1', '&;=', '//,') . '.html\$2'", $setLocation); Chris@76: } Chris@76: Chris@76: // Maybe integrations want to change where we are heading? Chris@76: call_integration_hook('integrate_redirect', array(&$setLocation, &$refresh)); Chris@76: Chris@76: // We send a Refresh header only in special cases because Location looks better. (and is quicker...) Chris@76: if ($refresh && !WIRELESS) Chris@76: header('Refresh: 0; URL=' . strtr($setLocation, array(' ' => '%20'))); Chris@76: else Chris@76: header('Location: ' . str_replace(' ', '%20', $setLocation)); Chris@76: Chris@76: // Debugging. Chris@76: if (isset($db_show_debug) && $db_show_debug === true) Chris@76: $_SESSION['debug_redirect'] = $db_cache; Chris@76: Chris@76: obExit(false); Chris@76: } Chris@76: Chris@76: // Ends execution. Takes care of template loading and remembering the previous URL. Chris@76: function obExit($header = null, $do_footer = null, $from_index = false, $from_fatal_error = false) Chris@76: { Chris@76: global $context, $settings, $modSettings, $txt, $smcFunc; Chris@76: static $header_done = false, $footer_done = false, $level = 0, $has_fatal_error = false; Chris@76: Chris@76: // Attempt to prevent a recursive loop. Chris@76: ++$level; Chris@76: if ($level > 1 && !$from_fatal_error && !$has_fatal_error) Chris@76: exit; Chris@76: if ($from_fatal_error) Chris@76: $has_fatal_error = true; Chris@76: Chris@76: // Clear out the stat cache. Chris@76: trackStats(); Chris@76: Chris@76: // If we have mail to send, send it. Chris@76: if (!empty($context['flush_mail'])) Chris@76: AddMailQueue(true); Chris@76: Chris@76: $do_header = $header === null ? !$header_done : $header; Chris@76: if ($do_footer === null) Chris@76: $do_footer = $do_header; Chris@76: Chris@76: // Has the template/header been done yet? Chris@76: if ($do_header) Chris@76: { Chris@76: // Was the page title set last minute? Also update the HTML safe one. Chris@76: if (!empty($context['page_title']) && empty($context['page_title_html_safe'])) Chris@76: $context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])); Chris@76: Chris@76: // Start up the session URL fixer. Chris@76: ob_start('ob_sessrewrite'); Chris@76: Chris@76: if (!empty($settings['output_buffers']) && is_string($settings['output_buffers'])) Chris@76: $buffers = explode(',', $settings['output_buffers']); Chris@76: elseif (!empty($settings['output_buffers'])) Chris@76: $buffers = $settings['output_buffers']; Chris@76: else Chris@76: $buffers = array(); Chris@76: Chris@76: if (isset($modSettings['integrate_buffer'])) Chris@76: $buffers = array_merge(explode(',', $modSettings['integrate_buffer']), $buffers); Chris@76: Chris@76: if (!empty($buffers)) Chris@76: foreach ($buffers as $function) Chris@76: { Chris@76: $function = trim($function); Chris@76: $call = strpos($function, '::') !== false ? explode('::', $function) : $function; Chris@76: Chris@76: // Is it valid? Chris@76: if (is_callable($call)) Chris@76: ob_start($call); Chris@76: } Chris@76: Chris@76: // Display the screen in the logical order. Chris@76: template_header(); Chris@76: $header_done = true; Chris@76: } Chris@76: if ($do_footer) Chris@76: { Chris@76: if (WIRELESS && !isset($context['sub_template'])) Chris@76: fatal_lang_error('wireless_error_notyet', false); Chris@76: Chris@76: // Just show the footer, then. Chris@76: loadSubTemplate(isset($context['sub_template']) ? $context['sub_template'] : 'main'); Chris@76: Chris@76: // Anything special to put out? Chris@76: if (!empty($context['insert_after_template']) && !isset($_REQUEST['xml'])) Chris@76: echo $context['insert_after_template']; Chris@76: Chris@76: // Just so we don't get caught in an endless loop of errors from the footer... Chris@76: if (!$footer_done) Chris@76: { Chris@76: $footer_done = true; Chris@76: template_footer(); Chris@76: Chris@76: // (since this is just debugging... it's okay that it's after .) Chris@76: if (!isset($_REQUEST['xml'])) Chris@76: db_debug_junk(); Chris@76: } Chris@76: } Chris@76: Chris@76: // Remember this URL in case someone doesn't like sending HTTP_REFERER. Chris@76: if (strpos($_SERVER['REQUEST_URL'], 'action=dlattach') === false && strpos($_SERVER['REQUEST_URL'], 'action=viewsmfile') === false) Chris@76: $_SESSION['old_url'] = $_SERVER['REQUEST_URL']; Chris@76: Chris@76: // For session check verfication.... don't switch browsers... Chris@76: $_SESSION['USER_AGENT'] = $_SERVER['HTTP_USER_AGENT']; Chris@76: Chris@76: if (!empty($settings['strict_doctype'])) Chris@76: { Chris@76: // The theme author wants to use the STRICT doctype (only God knows why). Chris@76: $temp = ob_get_contents(); Chris@76: if (function_exists('ob_clean')) Chris@76: ob_clean(); Chris@76: else Chris@76: { Chris@76: ob_end_clean(); Chris@76: ob_start('ob_sessrewrite'); Chris@76: } Chris@76: Chris@76: echo strtr($temp, array( Chris@76: 'var smf_iso_case_folding' => 'var target_blank = \'_blank\'; var smf_iso_case_folding', Chris@76: 'target="_blank"' => 'onclick="this.target=target_blank"')); Chris@76: } Chris@76: Chris@76: // Hand off the output to the portal, etc. we're integrated with. Chris@76: call_integration_hook('integrate_exit', array($do_footer && !WIRELESS)); Chris@76: Chris@76: // Don't exit if we're coming from index.php; that will pass through normally. Chris@76: if (!$from_index || WIRELESS) Chris@76: exit; Chris@76: } Chris@76: Chris@76: // Usage: logAction('remove', array('starter' => $id_member_started)); Chris@76: function logAction($action, $extra = array(), $log_type = 'moderate') Chris@76: { Chris@76: global $modSettings, $user_info, $smcFunc, $sourcedir; Chris@76: Chris@76: $log_types = array( Chris@76: 'moderate' => 1, Chris@76: 'user' => 2, Chris@76: 'admin' => 3, Chris@76: ); Chris@76: Chris@76: if (!is_array($extra)) Chris@76: trigger_error('logAction(): data is not an array with action \'' . $action . '\'', E_USER_NOTICE); Chris@76: Chris@76: // Pull out the parts we want to store separately, but also make sure that the data is proper Chris@76: if (isset($extra['topic'])) Chris@76: { Chris@76: if (!is_numeric($extra['topic'])) Chris@76: trigger_error('logAction(): data\'s topic is not a number', E_USER_NOTICE); Chris@76: $topic_id = empty($extra['topic']) ? '0' : (int)$extra['topic']; Chris@76: unset($extra['topic']); Chris@76: } Chris@76: else Chris@76: $topic_id = '0'; Chris@76: Chris@76: if (isset($extra['message'])) Chris@76: { Chris@76: if (!is_numeric($extra['message'])) Chris@76: trigger_error('logAction(): data\'s message is not a number', E_USER_NOTICE); Chris@76: $msg_id = empty($extra['message']) ? '0' : (int)$extra['message']; Chris@76: unset($extra['message']); Chris@76: } Chris@76: else Chris@76: $msg_id = '0'; Chris@76: Chris@76: // Is there an associated report on this? Chris@76: if (in_array($action, array('move', 'remove', 'split', 'merge'))) Chris@76: { Chris@76: $request = $smcFunc['db_query']('', ' Chris@76: SELECT id_report Chris@76: FROM {db_prefix}log_reported Chris@76: WHERE {raw:column_name} = {int:reported} Chris@76: LIMIT 1', Chris@76: array( Chris@76: 'column_name' => !empty($msg_id) ? 'id_msg' : 'id_topic', Chris@76: 'reported' => !empty($msg_id) ? $msg_id : $topic_id, Chris@76: )); Chris@76: Chris@76: // Alright, if we get any result back, update open reports. Chris@76: if ($smcFunc['db_num_rows']($request) > 0) Chris@76: { Chris@76: require_once($sourcedir . '/ModerationCenter.php'); Chris@76: updateSettings(array('last_mod_report_action' => time())); Chris@76: recountOpenReports(); Chris@76: } Chris@76: $smcFunc['db_free_result']($request); Chris@76: } Chris@76: Chris@76: // No point in doing anything else, if the log isn't even enabled. Chris@76: if (empty($modSettings['modlog_enabled']) || !isset($log_types[$log_type])) Chris@76: return false; Chris@76: Chris@76: if (isset($extra['member']) && !is_numeric($extra['member'])) Chris@76: trigger_error('logAction(): data\'s member is not a number', E_USER_NOTICE); Chris@76: Chris@76: if (isset($extra['board'])) Chris@76: { Chris@76: if (!is_numeric($extra['board'])) Chris@76: trigger_error('logAction(): data\'s board is not a number', E_USER_NOTICE); Chris@76: $board_id = empty($extra['board']) ? '0' : (int)$extra['board']; Chris@76: unset($extra['board']); Chris@76: } Chris@76: else Chris@76: $board_id = '0'; Chris@76: Chris@76: if (isset($extra['board_to'])) Chris@76: { Chris@76: if (!is_numeric($extra['board_to'])) Chris@76: trigger_error('logAction(): data\'s board_to is not a number', E_USER_NOTICE); Chris@76: if (empty($board_id)) Chris@76: { Chris@76: $board_id = empty($extra['board_to']) ? '0' : (int)$extra['board_to']; Chris@76: unset($extra['board_to']); Chris@76: } Chris@76: } Chris@76: Chris@76: $smcFunc['db_insert']('', Chris@76: '{db_prefix}log_actions', Chris@76: array( Chris@76: 'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'string-16', 'action' => 'string', Chris@76: 'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534', Chris@76: ), Chris@76: array( Chris@76: time(), $log_types[$log_type], $user_info['id'], $user_info['ip'], $action, Chris@76: $board_id, $topic_id, $msg_id, serialize($extra), Chris@76: ), Chris@76: array('id_action') Chris@76: ); Chris@76: Chris@76: return $smcFunc['db_insert_id']('{db_prefix}log_actions', 'id_action'); Chris@76: } Chris@76: Chris@76: // Track Statistics. Chris@76: function trackStats($stats = array()) Chris@76: { Chris@76: global $modSettings, $smcFunc; Chris@76: static $cache_stats = array(); Chris@76: Chris@76: if (empty($modSettings['trackStats'])) Chris@76: return false; Chris@76: if (!empty($stats)) Chris@76: return $cache_stats = array_merge($cache_stats, $stats); Chris@76: elseif (empty($cache_stats)) Chris@76: return false; Chris@76: Chris@76: $setStringUpdate = ''; Chris@76: $insert_keys = array(); Chris@76: $date = strftime('%Y-%m-%d', forum_time(false)); Chris@76: $update_parameters = array( Chris@76: 'current_date' => $date, Chris@76: ); Chris@76: foreach ($cache_stats as $field => $change) Chris@76: { Chris@76: $setStringUpdate .= ' Chris@76: ' . $field . ' = ' . ($change === '+' ? $field . ' + 1' : '{int:' . $field . '}') . ','; Chris@76: Chris@76: if ($change === '+') Chris@76: $cache_stats[$field] = 1; Chris@76: else Chris@76: $update_parameters[$field] = $change; Chris@76: $insert_keys[$field] = 'int'; Chris@76: } Chris@76: Chris@76: $smcFunc['db_query']('', ' Chris@76: UPDATE {db_prefix}log_activity Chris@76: SET' . substr($setStringUpdate, 0, -1) . ' Chris@76: WHERE date = {date:current_date}', Chris@76: $update_parameters Chris@76: ); Chris@76: if ($smcFunc['db_affected_rows']() == 0) Chris@76: { Chris@76: $smcFunc['db_insert']('ignore', Chris@76: '{db_prefix}log_activity', Chris@76: array_merge($insert_keys, array('date' => 'date')), Chris@76: array_merge($cache_stats, array($date)), Chris@76: array('date') Chris@76: ); Chris@76: } Chris@76: Chris@76: // Don't do this again. Chris@76: $cache_stats = array(); Chris@76: Chris@76: return true; Chris@76: } Chris@76: Chris@76: // Make sure the user isn't posting over and over again. Chris@76: function spamProtection($error_type) Chris@76: { Chris@76: global $modSettings, $txt, $user_info, $smcFunc; Chris@76: Chris@76: // Certain types take less/more time. Chris@76: $timeOverrides = array( Chris@76: 'login' => 2, Chris@76: 'register' => 2, Chris@76: 'sendtopc' => $modSettings['spamWaitTime'] * 4, Chris@76: 'sendmail' => $modSettings['spamWaitTime'] * 5, Chris@76: 'reporttm' => $modSettings['spamWaitTime'] * 4, Chris@76: 'search' => !empty($modSettings['search_floodcontrol_time']) ? $modSettings['search_floodcontrol_time'] : 1, Chris@76: ); Chris@76: Chris@76: // Moderators are free... Chris@76: if (!allowedTo('moderate_board')) Chris@76: $timeLimit = isset($timeOverrides[$error_type]) ? $timeOverrides[$error_type] : $modSettings['spamWaitTime']; Chris@76: else Chris@76: $timeLimit = 2; Chris@76: Chris@76: // Delete old entries... Chris@76: $smcFunc['db_query']('', ' Chris@76: DELETE FROM {db_prefix}log_floodcontrol Chris@76: WHERE log_time < {int:log_time} Chris@76: AND log_type = {string:log_type}', Chris@76: array( Chris@76: 'log_time' => time() - $timeLimit, Chris@76: 'log_type' => $error_type, Chris@76: ) Chris@76: ); Chris@76: Chris@76: // Add a new entry, deleting the old if necessary. Chris@76: $smcFunc['db_insert']('replace', Chris@76: '{db_prefix}log_floodcontrol', Chris@76: array('ip' => 'string-16', 'log_time' => 'int', 'log_type' => 'string'), Chris@76: array($user_info['ip'], time(), $error_type), Chris@76: array('ip', 'log_type') Chris@76: ); Chris@76: Chris@76: // If affected is 0 or 2, it was there already. Chris@76: if ($smcFunc['db_affected_rows']() != 1) Chris@76: { Chris@76: // Spammer! You only have to wait a *few* seconds! Chris@76: fatal_lang_error($error_type . 'WaitTime_broken', false, array($timeLimit)); Chris@76: return true; Chris@76: } Chris@76: Chris@76: // They haven't posted within the limit. Chris@76: return false; Chris@76: } Chris@76: Chris@76: // Get the size of a specified image with better error handling. Chris@76: function url_image_size($url) Chris@76: { Chris@76: global $sourcedir; Chris@76: Chris@76: // Make sure it is a proper URL. Chris@76: $url = str_replace(' ', '%20', $url); Chris@76: Chris@76: // Can we pull this from the cache... please please? Chris@76: if (($temp = cache_get_data('url_image_size-' . md5($url), 240)) !== null) Chris@76: return $temp; Chris@76: $t = microtime(); Chris@76: Chris@76: // Get the host to pester... Chris@76: preg_match('~^\w+://(.+?)/(.*)$~', $url, $match); Chris@76: Chris@76: // Can't figure it out, just try the image size. Chris@76: if ($url == '' || $url == 'http://' || $url == 'https://') Chris@76: { Chris@76: return false; Chris@76: } Chris@76: elseif (!isset($match[1])) Chris@76: { Chris@76: $size = @getimagesize($url); Chris@76: } Chris@76: else Chris@76: { Chris@76: // Try to connect to the server... give it half a second. Chris@76: $temp = 0; Chris@76: $fp = @fsockopen($match[1], 80, $temp, $temp, 0.5); Chris@76: Chris@76: // Successful? Continue... Chris@76: if ($fp != false) Chris@76: { Chris@76: // Send the HEAD request (since we don't have to worry about chunked, HTTP/1.1 is fine here.) Chris@76: fwrite($fp, 'HEAD /' . $match[2] . ' HTTP/1.1' . "\r\n" . 'Host: ' . $match[1] . "\r\n" . 'User-Agent: PHP/SMF' . "\r\n" . 'Connection: close' . "\r\n\r\n"); Chris@76: Chris@76: // Read in the HTTP/1.1 or whatever. Chris@76: $test = substr(fgets($fp, 11), -1); Chris@76: fclose($fp); Chris@76: Chris@76: // See if it returned a 404/403 or something. Chris@76: if ($test < 4) Chris@76: { Chris@76: $size = @getimagesize($url); Chris@76: Chris@76: // This probably means allow_url_fopen is off, let's try GD. Chris@76: if ($size === false && function_exists('imagecreatefromstring')) Chris@76: { Chris@76: include_once($sourcedir . '/Subs-Package.php'); Chris@76: Chris@76: // It's going to hate us for doing this, but another request... Chris@76: $image = @imagecreatefromstring(fetch_web_data($url)); Chris@76: if ($image !== false) Chris@76: { Chris@76: $size = array(imagesx($image), imagesy($image)); Chris@76: imagedestroy($image); Chris@76: } Chris@76: } Chris@76: } Chris@76: } Chris@76: } Chris@76: Chris@76: // If we didn't get it, we failed. Chris@76: if (!isset($size)) Chris@76: $size = false; Chris@76: Chris@76: // If this took a long time, we may never have to do it again, but then again we might... Chris@76: if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.8) Chris@76: cache_put_data('url_image_size-' . md5($url), $size, 240); Chris@76: Chris@76: // Didn't work. Chris@76: return $size; Chris@76: } Chris@76: Chris@76: function determineTopicClass(&$topic_context) Chris@76: { Chris@76: // Set topic class depending on locked status and number of replies. Chris@76: if ($topic_context['is_very_hot']) Chris@76: $topic_context['class'] = 'veryhot'; Chris@76: elseif ($topic_context['is_hot']) Chris@76: $topic_context['class'] = 'hot'; Chris@76: else Chris@76: $topic_context['class'] = 'normal'; Chris@76: Chris@76: $topic_context['class'] .= $topic_context['is_poll'] ? '_poll' : '_post'; Chris@76: Chris@76: if ($topic_context['is_locked']) Chris@76: $topic_context['class'] .= '_locked'; Chris@76: Chris@76: if ($topic_context['is_sticky']) Chris@76: $topic_context['class'] .= '_sticky'; Chris@76: Chris@76: // This is so old themes will still work. Chris@76: $topic_context['extended_class'] = &$topic_context['class']; Chris@76: } Chris@76: Chris@76: // Sets up the basic theme context stuff. Chris@76: function setupThemeContext($forceload = false) Chris@76: { Chris@76: global $modSettings, $user_info, $scripturl, $context, $settings, $options, $txt, $maintenance; Chris@76: global $user_settings, $smcFunc; Chris@76: static $loaded = false; Chris@76: Chris@76: // Under SSI this function can be called more then once. That can cause some problems. Chris@76: // So only run the function once unless we are forced to run it again. Chris@76: if ($loaded && !$forceload) Chris@76: return; Chris@76: Chris@76: $loaded = true; Chris@76: Chris@76: $context['in_maintenance'] = !empty($maintenance); Chris@76: $context['current_time'] = timeformat(time(), false); Chris@76: $context['current_action'] = isset($_GET['action']) ? $_GET['action'] : ''; Chris@76: $context['show_quick_login'] = !empty($modSettings['enableVBStyleLogin']) && $user_info['is_guest']; Chris@76: Chris@76: // Get some news... Chris@76: $context['news_lines'] = explode("\n", str_replace("\r", '', trim(addslashes($modSettings['news'])))); Chris@76: $context['fader_news_lines'] = array(); Chris@76: for ($i = 0, $n = count($context['news_lines']); $i < $n; $i++) Chris@76: { Chris@76: if (trim($context['news_lines'][$i]) == '') Chris@76: continue; Chris@76: Chris@76: // Clean it up for presentation ;). Chris@76: $context['news_lines'][$i] = parse_bbc(stripslashes(trim($context['news_lines'][$i])), true, 'news' . $i); Chris@76: Chris@76: // Gotta be special for the javascript. Chris@76: $context['fader_news_lines'][$i] = strtr(addslashes($context['news_lines'][$i]), array('/' => '\/', ' (isset($_SESSION['unread_messages']) ? $_SESSION['unread_messages'] : 0)) Chris@76: $context['user']['popup_messages'] = true; Chris@76: else Chris@76: $context['user']['popup_messages'] = false; Chris@76: $_SESSION['unread_messages'] = $user_info['unread_messages']; Chris@76: Chris@76: if (allowedTo('moderate_forum')) Chris@76: $context['unapproved_members'] = (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($modSettings['approveAccountDeletion']) ? $modSettings['unapprovedMembers'] : 0; Chris@76: $context['show_open_reports'] = empty($user_settings['mod_prefs']) || $user_settings['mod_prefs'][0] == 1; Chris@76: Chris@76: $context['user']['avatar'] = array(); Chris@76: Chris@76: // Figure out the avatar... uploaded? Chris@76: if ($user_info['avatar']['url'] == '' && !empty($user_info['avatar']['id_attach'])) Chris@76: $context['user']['avatar']['href'] = $user_info['avatar']['custom_dir'] ? $modSettings['custom_avatar_url'] . '/' . $user_info['avatar']['filename'] : $scripturl . '?action=dlattach;attach=' . $user_info['avatar']['id_attach'] . ';type=avatar'; Chris@76: // Full URL? Chris@76: elseif (substr($user_info['avatar']['url'], 0, 7) == 'http://') Chris@76: { Chris@76: $context['user']['avatar']['href'] = $user_info['avatar']['url']; Chris@76: Chris@76: if ($modSettings['avatar_action_too_large'] == 'option_html_resize' || $modSettings['avatar_action_too_large'] == 'option_js_resize') Chris@76: { Chris@76: if (!empty($modSettings['avatar_max_width_external'])) Chris@76: $context['user']['avatar']['width'] = $modSettings['avatar_max_width_external']; Chris@76: if (!empty($modSettings['avatar_max_height_external'])) Chris@76: $context['user']['avatar']['height'] = $modSettings['avatar_max_height_external']; Chris@76: } Chris@76: } Chris@76: // Otherwise we assume it's server stored? Chris@76: elseif ($user_info['avatar']['url'] != '') Chris@76: $context['user']['avatar']['href'] = $modSettings['avatar_url'] . '/' . htmlspecialchars($user_info['avatar']['url']); Chris@76: Chris@76: if (!empty($context['user']['avatar'])) Chris@76: $context['user']['avatar']['image'] = ''; Chris@76: Chris@76: // Figure out how long they've been logged in. Chris@76: $context['user']['total_time_logged_in'] = array( Chris@76: 'days' => floor($user_info['total_time_logged_in'] / 86400), Chris@76: 'hours' => floor(($user_info['total_time_logged_in'] % 86400) / 3600), Chris@76: 'minutes' => floor(($user_info['total_time_logged_in'] % 3600) / 60) Chris@76: ); Chris@76: } Chris@76: else Chris@76: { Chris@76: $context['user']['messages'] = 0; Chris@76: $context['user']['unread_messages'] = 0; Chris@76: $context['user']['avatar'] = array(); Chris@76: $context['user']['total_time_logged_in'] = array('days' => 0, 'hours' => 0, 'minutes' => 0); Chris@76: $context['user']['popup_messages'] = false; Chris@76: Chris@76: if (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 1) Chris@76: $txt['welcome_guest'] .= $txt['welcome_guest_activate']; Chris@76: Chris@76: // If we've upgraded recently, go easy on the passwords. Chris@76: if (!empty($modSettings['disableHashTime']) && ($modSettings['disableHashTime'] == 1 || time() < $modSettings['disableHashTime'])) Chris@76: $context['disable_login_hashing'] = true; Chris@76: elseif ($context['browser']['is_ie5'] || $context['browser']['is_ie5.5']) Chris@76: $context['disable_login_hashing'] = true; Chris@76: } Chris@76: Chris@76: // Setup the main menu items. Chris@76: setupMenuContext(); Chris@76: Chris@76: if (empty($settings['theme_version'])) Chris@76: $context['show_vBlogin'] = $context['show_quick_login']; Chris@76: Chris@76: // This is here because old index templates might still use it. Chris@76: $context['show_news'] = !empty($settings['enable_news']); Chris@76: Chris@76: // This is done to allow theme authors to customize it as they want. Chris@76: $context['show_pm_popup'] = $context['user']['popup_messages'] && !empty($options['popup_messages']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] != 'pm'); Chris@76: Chris@76: // Resize avatars the fancy, but non-GD requiring way. Chris@76: if ($modSettings['avatar_action_too_large'] == 'option_js_resize' && (!empty($modSettings['avatar_max_width_external']) || !empty($modSettings['avatar_max_height_external']))) Chris@76: { Chris@76: $context['html_headers'] .= ' Chris@76: '; Chris@76: } Chris@76: Chris@76: // This looks weird, but it's because BoardIndex.php references the variable. Chris@76: $context['common_stats']['latest_member'] = array( Chris@76: 'id' => $modSettings['latestMember'], Chris@76: 'name' => $modSettings['latestRealName'], Chris@76: 'href' => $scripturl . '?action=profile;u=' . $modSettings['latestMember'], Chris@76: 'link' => '' . $modSettings['latestRealName'] . '', Chris@76: ); Chris@76: $context['common_stats'] = array( Chris@76: 'total_posts' => comma_format($modSettings['totalMessages']), Chris@76: 'total_topics' => comma_format($modSettings['totalTopics']), Chris@76: 'total_members' => comma_format($modSettings['totalMembers']), Chris@76: 'latest_member' => $context['common_stats']['latest_member'], Chris@76: ); Chris@76: Chris@76: if (empty($settings['theme_version'])) Chris@76: $context['html_headers'] .= ' Chris@76: '; Chris@76: Chris@76: if (!isset($context['page_title'])) Chris@76: $context['page_title'] = ''; Chris@76: Chris@76: // Set some specific vars. Chris@76: $context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])); Chris@76: $context['meta_keywords'] = !empty($modSettings['meta_keywords']) ? $smcFunc['htmlspecialchars']($modSettings['meta_keywords']) : ''; Chris@76: } Chris@76: Chris@76: // This is the only template included in the sources... Chris@76: function template_rawdata() Chris@76: { Chris@76: global $context; Chris@76: Chris@76: echo $context['raw_data']; Chris@76: } Chris@76: Chris@76: function template_header() Chris@76: { Chris@76: global $txt, $modSettings, $context, $settings, $user_info, $boarddir, $cachedir; Chris@76: Chris@76: setupThemeContext(); Chris@76: Chris@76: // Print stuff to prevent caching of pages (except on attachment errors, etc.) Chris@76: if (empty($context['no_last_modified'])) Chris@76: { Chris@76: header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); Chris@76: header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); Chris@76: Chris@76: // Are we debugging the template/html content? Chris@76: if (!isset($_REQUEST['xml']) && isset($_GET['debug']) && !$context['browser']['is_ie'] && !WIRELESS) Chris@76: header('Content-Type: application/xhtml+xml'); Chris@76: elseif (!isset($_REQUEST['xml']) && !WIRELESS) Chris@76: header('Content-Type: text/html; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set'])); Chris@76: } Chris@76: Chris@76: header('Content-Type: text/' . (isset($_REQUEST['xml']) ? 'xml' : 'html') . '; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set'])); Chris@76: Chris@76: $checked_securityFiles = false; Chris@76: $showed_banned = false; Chris@76: foreach ($context['template_layers'] as $layer) Chris@76: { Chris@76: loadSubTemplate($layer . '_above', true); Chris@76: Chris@76: // May seem contrived, but this is done in case the body and main layer aren't there... Chris@76: if (in_array($layer, array('body', 'main')) && allowedTo('admin_forum') && !$user_info['is_guest'] && !$checked_securityFiles) Chris@76: { Chris@76: $checked_securityFiles = true; Chris@76: $securityFiles = array('install.php', 'webinstall.php', 'upgrade.php', 'convert.php', 'repair_paths.php', 'repair_settings.php', 'Settings.php~', 'Settings_bak.php~'); Chris@76: foreach ($securityFiles as $i => $securityFile) Chris@76: { Chris@76: if (!file_exists($boarddir . '/' . $securityFile)) Chris@76: unset($securityFiles[$i]); Chris@76: } Chris@76: Chris@76: if (!empty($securityFiles) || (!empty($modSettings['cache_enable']) && !is_writable($cachedir))) Chris@76: { Chris@76: echo ' Chris@76:
    Chris@76:

    !!

    Chris@76:

    ', empty($securityFiles) ? $txt['cache_writable_head'] : $txt['security_risk'], '

    Chris@76:

    '; Chris@76: Chris@76: foreach ($securityFiles as $securityFile) Chris@76: { Chris@76: echo ' Chris@76: ', $txt['not_removed'], '', $securityFile, '!
    '; Chris@76: Chris@76: if ($securityFile == 'Settings.php~' || $securityFile == 'Settings_bak.php~') Chris@76: echo ' Chris@76: ', sprintf($txt['not_removed_extra'], $securityFile, substr($securityFile, 0, -1)), '
    '; Chris@76: } Chris@76: Chris@76: if (!empty($modSettings['cache_enable']) && !is_writable($cachedir)) Chris@76: echo ' Chris@76: ', $txt['cache_writable'], '
    '; Chris@76: Chris@76: echo ' Chris@76:

    Chris@76:
    '; Chris@76: } Chris@76: } Chris@76: // If the user is banned from posting inform them of it. Chris@76: elseif (in_array($layer, array('main', 'body')) && isset($_SESSION['ban']['cannot_post']) && !$showed_banned) Chris@76: { Chris@76: $showed_banned = true; Chris@76: echo ' Chris@76:
    Chris@76: ', sprintf($txt['you_are_post_banned'], $user_info['is_guest'] ? $txt['guest_title'] : $user_info['name']); Chris@76: Chris@76: if (!empty($_SESSION['ban']['cannot_post']['reason'])) Chris@76: echo ' Chris@76:
    ', $_SESSION['ban']['cannot_post']['reason'], '
    '; Chris@76: Chris@76: if (!empty($_SESSION['ban']['expire_time'])) Chris@76: echo ' Chris@76:
    ', sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)), '
    '; Chris@76: else Chris@76: echo ' Chris@76:
    ', $txt['your_ban_expires_never'], '
    '; Chris@76: Chris@76: echo ' Chris@76:
    '; Chris@76: } Chris@76: } Chris@76: Chris@76: if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'defaults' && isset($settings['default_template'])) Chris@76: { Chris@76: $settings['theme_url'] = $settings['default_theme_url']; Chris@76: $settings['images_url'] = $settings['default_images_url']; Chris@76: $settings['theme_dir'] = $settings['default_theme_dir']; Chris@76: } Chris@76: } Chris@76: Chris@76: // Show the copyright... Chris@76: function theme_copyright($get_it = false) Chris@76: { Chris@76: global $forum_copyright, $context, $boardurl, $forum_version, $txt, $modSettings; Chris@76: Chris@76: // Don't display copyright for things like SSI. Chris@76: if (!isset($forum_version)) Chris@76: return; Chris@76: Chris@76: // Put in the version... Chris@76: $forum_copyright = sprintf($forum_copyright, $forum_version); Chris@76: Chris@76: echo ' Chris@76: ' . $forum_copyright . ' Chris@76: '; Chris@76: } Chris@76: Chris@76: function template_footer() Chris@76: { Chris@76: global $context, $settings, $modSettings, $time_start, $db_count; Chris@76: Chris@76: // Show the load time? (only makes sense for the footer.) Chris@76: $context['show_load_time'] = !empty($modSettings['timeLoadPageEnable']); Chris@76: $context['load_time'] = round(array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)), 3); Chris@76: $context['load_queries'] = $db_count; Chris@76: Chris@76: if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'defaults' && isset($settings['default_template'])) Chris@76: { Chris@76: $settings['theme_url'] = $settings['actual_theme_url']; Chris@76: $settings['images_url'] = $settings['actual_images_url']; Chris@76: $settings['theme_dir'] = $settings['actual_theme_dir']; Chris@76: } Chris@76: Chris@76: foreach (array_reverse($context['template_layers']) as $layer) Chris@76: loadSubTemplate($layer . '_below', true); Chris@76: Chris@76: } Chris@76: Chris@76: // Debugging. Chris@76: function db_debug_junk() Chris@76: { Chris@76: global $context, $scripturl, $boarddir, $modSettings, $boarddir; Chris@76: global $db_cache, $db_count, $db_show_debug, $cache_count, $cache_hits, $txt; Chris@76: Chris@76: // Add to Settings.php if you want to show the debugging information. Chris@76: if (!isset($db_show_debug) || $db_show_debug !== true || (isset($_GET['action']) && $_GET['action'] == 'viewquery') || WIRELESS) Chris@76: return; Chris@76: Chris@76: if (empty($_SESSION['view_queries'])) Chris@76: $_SESSION['view_queries'] = 0; Chris@76: if (empty($context['debug']['language_files'])) Chris@76: $context['debug']['language_files'] = array(); Chris@76: if (empty($context['debug']['sheets'])) Chris@76: $context['debug']['sheets'] = array(); Chris@76: Chris@76: $files = get_included_files(); Chris@76: $total_size = 0; Chris@76: for ($i = 0, $n = count($files); $i < $n; $i++) Chris@76: { Chris@76: if (file_exists($files[$i])) Chris@76: $total_size += filesize($files[$i]); Chris@76: $files[$i] = strtr($files[$i], array($boarddir => '.')); Chris@76: } Chris@76: Chris@76: $warnings = 0; Chris@76: if (!empty($db_cache)) Chris@76: { Chris@76: foreach ($db_cache as $q => $qq) Chris@76: { Chris@76: if (!empty($qq['w'])) Chris@76: $warnings += count($qq['w']); Chris@76: } Chris@76: Chris@76: $_SESSION['debug'] = &$db_cache; Chris@76: } Chris@76: Chris@76: // Gotta have valid HTML ;). Chris@76: $temp = ob_get_contents(); Chris@76: if (function_exists('ob_clean')) Chris@76: ob_clean(); Chris@76: else Chris@76: { Chris@76: ob_end_clean(); Chris@76: ob_start('ob_sessrewrite'); Chris@76: } Chris@76: Chris@76: echo preg_replace('~\s*~', '', $temp), ' Chris@76:
    Chris@76: ', $txt['debug_templates'], count($context['debug']['templates']), ': ', implode(', ', $context['debug']['templates']), '.
    Chris@76: ', $txt['debug_subtemplates'], count($context['debug']['sub_templates']), ': ', implode(', ', $context['debug']['sub_templates']), '.
    Chris@76: ', $txt['debug_language_files'], count($context['debug']['language_files']), ': ', implode(', ', $context['debug']['language_files']), '.
    Chris@76: ', $txt['debug_stylesheets'], count($context['debug']['sheets']), ': ', implode(', ', $context['debug']['sheets']), '.
    Chris@76: ', $txt['debug_files_included'], count($files), ' - ', round($total_size / 1024), $txt['debug_kb'], ' (', $txt['debug_show'], ')
    '; Chris@76: Chris@76: if (!empty($modSettings['cache_enable']) && !empty($cache_hits)) Chris@76: { Chris@76: $entries = array(); Chris@76: $total_t = 0; Chris@76: $total_s = 0; Chris@76: foreach ($cache_hits as $cache_hit) Chris@76: { Chris@76: $entries[] = $cache_hit['d'] . ' ' . $cache_hit['k'] . ': ' . sprintf($txt['debug_cache_seconds_bytes'], comma_format($cache_hit['t'], 5), $cache_hit['s']); Chris@76: $total_t += $cache_hit['t']; Chris@76: $total_s += $cache_hit['s']; Chris@76: } Chris@76: Chris@76: echo ' Chris@76: ', $txt['debug_cache_hits'], $cache_count, ': ', sprintf($txt['debug_cache_seconds_bytes_total'], comma_format($total_t, 5), comma_format($total_s)), ' (', $txt['debug_show'], ')
    '; Chris@76: } Chris@76: Chris@76: echo ' Chris@76: ', $warnings == 0 ? sprintf($txt['debug_queries_used'], (int) $db_count) : sprintf($txt['debug_queries_used_and_warnings'], (int) $db_count, $warnings), '
    Chris@76:
    '; Chris@76: Chris@76: if ($_SESSION['view_queries'] == 1 && !empty($db_cache)) Chris@76: foreach ($db_cache as $q => $qq) Chris@76: { Chris@76: $is_select = substr(trim($qq['q']), 0, 6) == 'SELECT' || preg_match('~^INSERT(?: IGNORE)? INTO \w+(?:\s+\([^)]+\))?\s+SELECT .+$~s', trim($qq['q'])) != 0; Chris@76: // Temporary tables created in earlier queries are not explainable. Chris@76: if ($is_select) Chris@76: { Chris@76: foreach (array('log_topics_unread', 'topics_posted_in', 'tmp_log_search_topics', 'tmp_log_search_messages') as $tmp) Chris@76: if (strpos(trim($qq['q']), $tmp) !== false) Chris@76: { Chris@76: $is_select = false; Chris@76: break; Chris@76: } Chris@76: } Chris@76: // But actual creation of the temporary tables are. Chris@76: elseif (preg_match('~^CREATE TEMPORARY TABLE .+?SELECT .+$~s', trim($qq['q'])) != 0) Chris@76: $is_select = true; Chris@76: Chris@76: // Make the filenames look a bit better. Chris@76: if (isset($qq['f'])) Chris@76: $qq['f'] = preg_replace('~^' . preg_quote($boarddir, '~') . '~', '...', $qq['f']); Chris@76: Chris@76: echo ' Chris@76: ', $is_select ? '' : '', nl2br(str_replace("\t", '   ', htmlspecialchars(ltrim($qq['q'], "\n\r")))) . ($is_select ? '' : '') . '
    Chris@76:    '; Chris@76: if (!empty($qq['f']) && !empty($qq['l'])) Chris@76: echo sprintf($txt['debug_query_in_line'], $qq['f'], $qq['l']); Chris@76: Chris@76: if (isset($qq['s'], $qq['t']) && isset($txt['debug_query_which_took_at'])) Chris@76: echo sprintf($txt['debug_query_which_took_at'], round($qq['t'], 8), round($qq['s'], 8)) . '
    '; Chris@76: elseif (isset($qq['t'])) Chris@76: echo sprintf($txt['debug_query_which_took'], round($qq['t'], 8)) . '
    '; Chris@76: echo ' Chris@76:
    '; Chris@76: } Chris@76: Chris@76: echo ' Chris@76: ', $txt['debug_' . (empty($_SESSION['view_queries']) ? 'show' : 'hide') . '_queries'], ' Chris@76:
    '; Chris@76: } Chris@76: Chris@76: // Get an attachment's encrypted filename. If $new is true, won't check for file existence. Chris@76: function getAttachmentFilename($filename, $attachment_id, $dir = null, $new = false, $file_hash = '') Chris@76: { Chris@76: global $modSettings, $smcFunc; Chris@76: Chris@76: // Just make up a nice hash... Chris@76: if ($new) Chris@76: return sha1(md5($filename . time()) . mt_rand()); Chris@76: Chris@76: // Grab the file hash if it wasn't added. Chris@76: if ($file_hash === '') Chris@76: { Chris@76: $request = $smcFunc['db_query']('', ' Chris@76: SELECT file_hash Chris@76: FROM {db_prefix}attachments Chris@76: WHERE id_attach = {int:id_attach}', Chris@76: array( Chris@76: 'id_attach' => $attachment_id, Chris@76: )); Chris@76: Chris@76: if ($smcFunc['db_num_rows']($request) === 0) Chris@76: return false; Chris@76: Chris@76: list ($file_hash) = $smcFunc['db_fetch_row']($request); Chris@76: $smcFunc['db_free_result']($request); Chris@76: } Chris@76: Chris@76: // In case of files from the old system, do a legacy call. Chris@76: if (empty($file_hash)) Chris@76: return getLegacyAttachmentFilename($filename, $attachment_id, $dir, $new); Chris@76: Chris@76: // Are we using multiple directories? Chris@76: if (!empty($modSettings['currentAttachmentUploadDir'])) Chris@76: { Chris@76: if (!is_array($modSettings['attachmentUploadDir'])) Chris@76: $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); Chris@76: $path = $modSettings['attachmentUploadDir'][$dir]; Chris@76: } Chris@76: else Chris@76: $path = $modSettings['attachmentUploadDir']; Chris@76: Chris@76: return $path . '/' . $attachment_id . '_' . $file_hash; Chris@76: } Chris@76: Chris@76: // Older attachments may still use this function. Chris@76: function getLegacyAttachmentFilename($filename, $attachment_id, $dir = null, $new = false) Chris@76: { Chris@76: global $modSettings, $db_character_set; Chris@76: Chris@76: $clean_name = $filename; Chris@76: // Remove international characters (windows-1252) Chris@76: // These lines should never be needed again. Still, behave. Chris@76: if (empty($db_character_set) || $db_character_set != 'utf8') Chris@76: { Chris@76: $clean_name = strtr($filename, Chris@76: "\x8a\x8e\x9a\x9e\x9f\xc0\xc1\xc2\xc3\xc4\xc5\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd1\xd2\xd3\xd4\xd5\xd6\xd8\xd9\xda\xdb\xdc\xdd\xe0\xe1\xe2\xe3\xe4\xe5\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xff", Chris@76: 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'); Chris@76: $clean_name = strtr($clean_name, array("\xde" => 'TH', "\xfe" => Chris@76: 'th', "\xd0" => 'DH', "\xf0" => 'dh', "\xdf" => 'ss', "\x8c" => 'OE', Chris@76: "\x9c" => 'oe', "\c6" => 'AE', "\xe6" => 'ae', "\xb5" => 'u')); Chris@76: } Chris@76: // Sorry, no spaces, dots, or anything else but letters allowed. Chris@76: $clean_name = preg_replace(array('/\s/', '/[^\w_\.\-]/'), array('_', ''), $clean_name); Chris@76: Chris@76: $enc_name = $attachment_id . '_' . strtr($clean_name, '.', '_') . md5($clean_name); Chris@76: $clean_name = preg_replace('~\.[\.]+~', '.', $clean_name); Chris@76: Chris@76: if ($attachment_id == false || ($new && empty($modSettings['attachmentEncryptFilenames']))) Chris@76: return $clean_name; Chris@76: elseif ($new) Chris@76: return $enc_name; Chris@76: Chris@76: // Are we using multiple directories? Chris@76: if (!empty($modSettings['currentAttachmentUploadDir'])) Chris@76: { Chris@76: if (!is_array($modSettings['attachmentUploadDir'])) Chris@76: $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); Chris@76: $path = $modSettings['attachmentUploadDir'][$dir]; Chris@76: } Chris@76: else Chris@76: $path = $modSettings['attachmentUploadDir']; Chris@76: Chris@76: if (file_exists($path . '/' . $enc_name)) Chris@76: $filename = $path . '/' . $enc_name; Chris@76: else Chris@76: $filename = $path . '/' . $clean_name; Chris@76: Chris@76: return $filename; Chris@76: } Chris@76: Chris@76: // Convert a single IP to a ranged IP. Chris@76: function ip2range($fullip) Chris@76: { Chris@76: // Pretend that 'unknown' is 255.255.255.255. (since that can't be an IP anyway.) Chris@76: if ($fullip == 'unknown') Chris@76: $fullip = '255.255.255.255'; Chris@76: Chris@76: $ip_parts = explode('.', $fullip); Chris@76: $ip_array = array(); Chris@76: Chris@76: if (count($ip_parts) != 4) Chris@76: return array(); Chris@76: Chris@76: for ($i = 0; $i < 4; $i++) Chris@76: { Chris@76: if ($ip_parts[$i] == '*') Chris@76: $ip_array[$i] = array('low' => '0', 'high' => '255'); Chris@76: elseif (preg_match('/^(\d{1,3})\-(\d{1,3})$/', $ip_parts[$i], $range) == 1) Chris@76: $ip_array[$i] = array('low' => $range[1], 'high' => $range[2]); Chris@76: elseif (is_numeric($ip_parts[$i])) Chris@76: $ip_array[$i] = array('low' => $ip_parts[$i], 'high' => $ip_parts[$i]); Chris@76: } Chris@76: Chris@76: return $ip_array; Chris@76: } Chris@76: Chris@76: // Lookup an IP; try shell_exec first because we can do a timeout on it. Chris@76: function host_from_ip($ip) Chris@76: { Chris@76: global $modSettings; Chris@76: Chris@76: if (($host = cache_get_data('hostlookup-' . $ip, 600)) !== null) Chris@76: return $host; Chris@76: $t = microtime(); Chris@76: Chris@76: // If we can't access nslookup/host, PHP 4.1.x might just crash. Chris@76: if (@version_compare(PHP_VERSION, '4.2.0') == -1) Chris@76: $host = false; Chris@76: Chris@76: // Try the Linux host command, perhaps? Chris@76: if (!isset($host) && (strpos(strtolower(PHP_OS), 'win') === false || strpos(strtolower(PHP_OS), 'darwin') !== false) && mt_rand(0, 1) == 1) Chris@76: { Chris@76: if (!isset($modSettings['host_to_dis'])) Chris@76: $test = @shell_exec('host -W 1 ' . @escapeshellarg($ip)); Chris@76: else Chris@76: $test = @shell_exec('host ' . @escapeshellarg($ip)); Chris@76: Chris@76: // Did host say it didn't find anything? Chris@76: if (strpos($test, 'not found') !== false) Chris@76: $host = ''; Chris@76: // Invalid server option? Chris@76: elseif ((strpos($test, 'invalid option') || strpos($test, 'Invalid query name 1')) && !isset($modSettings['host_to_dis'])) Chris@76: updateSettings(array('host_to_dis' => 1)); Chris@76: // Maybe it found something, after all? Chris@76: elseif (preg_match('~\s([^\s]+?)\.\s~', $test, $match) == 1) Chris@76: $host = $match[1]; Chris@76: } Chris@76: Chris@76: // This is nslookup; usually only Windows, but possibly some Unix? Chris@76: if (!isset($host) && strpos(strtolower(PHP_OS), 'win') !== false && strpos(strtolower(PHP_OS), 'darwin') === false && mt_rand(0, 1) == 1) Chris@76: { Chris@76: $test = @shell_exec('nslookup -timeout=1 ' . @escapeshellarg($ip)); Chris@76: if (strpos($test, 'Non-existent domain') !== false) Chris@76: $host = ''; Chris@76: elseif (preg_match('~Name:\s+([^\s]+)~', $test, $match) == 1) Chris@76: $host = $match[1]; Chris@76: } Chris@76: Chris@76: // This is the last try :/. Chris@76: if (!isset($host) || $host === false) Chris@76: $host = @gethostbyaddr($ip); Chris@76: Chris@76: // It took a long time, so let's cache it! Chris@76: if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.5) Chris@76: cache_put_data('hostlookup-' . $ip, $host, 600); Chris@76: Chris@76: return $host; Chris@76: } Chris@76: Chris@76: // Chops a string into words and prepares them to be inserted into (or searched from) the database. Chris@76: function text2words($text, $max_chars = 20, $encrypt = false) Chris@76: { Chris@76: global $smcFunc, $context; Chris@76: Chris@76: // Step 1: Remove entities/things we don't consider words: Chris@76: $words = preg_replace('~(?:[\x0B\0' . ($context['utf8'] ? ($context['server']['complex_preg_chars'] ? '\x{A0}' : "\xC2\xA0") : '\xA0') . '\t\r\s\n(){}\\[\\]<>!@$%^*.,:+=`\~\?/\\\\]+|&(?:amp|lt|gt|quot);)+~' . ($context['utf8'] ? 'u' : ''), ' ', strtr($text, array('
    ' => ' '))); Chris@76: Chris@76: // Step 2: Entities we left to letters, where applicable, lowercase. Chris@76: $words = un_htmlspecialchars($smcFunc['strtolower']($words)); Chris@76: Chris@76: // Step 3: Ready to split apart and index! Chris@76: $words = explode(' ', $words); Chris@76: Chris@76: if ($encrypt) Chris@76: { Chris@76: $possible_chars = array_flip(array_merge(range(46, 57), range(65, 90), range(97, 122))); Chris@76: $returned_ints = array(); Chris@76: foreach ($words as $word) Chris@76: { Chris@76: if (($word = trim($word, '-_\'')) !== '') Chris@76: { Chris@76: $encrypted = substr(crypt($word, 'uk'), 2, $max_chars); Chris@76: $total = 0; Chris@76: for ($i = 0; $i < $max_chars; $i++) Chris@76: $total += $possible_chars[ord($encrypted{$i})] * pow(63, $i); Chris@76: $returned_ints[] = $max_chars == 4 ? min($total, 16777215) : $total; Chris@76: } Chris@76: } Chris@76: return array_unique($returned_ints); Chris@76: } Chris@76: else Chris@76: { Chris@76: // Trim characters before and after and add slashes for database insertion. Chris@76: $returned_words = array(); Chris@76: foreach ($words as $word) Chris@76: if (($word = trim($word, '-_\'')) !== '') Chris@76: $returned_words[] = $max_chars === null ? $word : substr($word, 0, $max_chars); Chris@76: Chris@76: // Filter out all words that occur more than once. Chris@76: return array_unique($returned_words); Chris@76: } Chris@76: } Chris@76: Chris@76: // Creates an image/text button Chris@76: function create_button($name, $alt, $label = '', $custom = '', $force_use = false) Chris@76: { Chris@76: global $settings, $txt, $context; Chris@76: Chris@76: // Does the current loaded theme have this and we are not forcing the usage of this function? Chris@76: if (function_exists('template_create_button') && !$force_use) Chris@76: return template_create_button($name, $alt, $label = '', $custom = ''); Chris@76: Chris@76: if (!$settings['use_image_buttons']) Chris@76: return $txt[$alt]; Chris@76: elseif (!empty($settings['use_buttons'])) Chris@76: return '' . $txt[$alt] . '' . ($label != '' ? '' . $txt[$label] . '' : ''); Chris@76: else Chris@76: return '' . $txt[$alt] . ''; Chris@76: } Chris@76: Chris@76: // Empty out the cache folder. Chris@76: function clean_cache($type = '') Chris@76: { Chris@76: global $cachedir, $sourcedir; Chris@76: Chris@76: // No directory = no game. Chris@76: if (!is_dir($cachedir)) Chris@76: return; Chris@76: Chris@76: // Remove the files in SMF's own disk cache, if any Chris@76: $dh = opendir($cachedir); Chris@76: while ($file = readdir($dh)) Chris@76: { Chris@76: if ($file != '.' && $file != '..' && $file != 'index.php' && $file != '.htaccess' && (!$type || substr($file, 0, strlen($type)) == $type)) Chris@76: @unlink($cachedir . '/' . $file); Chris@76: } Chris@76: closedir($dh); Chris@76: Chris@76: // Invalidate cache, to be sure! Chris@76: // ... as long as Load.php can be modified, anyway. Chris@76: @touch($sourcedir . '/' . 'Load.php'); Chris@76: clearstatcache(); Chris@76: } Chris@76: Chris@76: // Load classes that are both (E_STRICT) PHP 4 and PHP 5 compatible. Chris@76: function loadClassFile($filename) Chris@76: { Chris@76: global $sourcedir; Chris@76: static $files_included = array(); Chris@76: Chris@76: if (!file_exists($sourcedir . '/' . $filename)) Chris@76: fatal_lang_error('error_bad_file', 'general', array($sourcedir . '/' . $filename)); Chris@76: Chris@76: // Using a version below PHP 5.0? Do a compatibility conversion. Chris@76: if (@version_compare(PHP_VERSION, '5.0.0') != 1) Chris@76: { Chris@76: // Check if it was included before. Chris@76: if (in_array($filename, $files_included)) Chris@76: return; Chris@76: Chris@76: // Make sure we don't include it again. Chris@76: $files_included[] = $filename; Chris@76: Chris@76: // Do some replacements to make it PHP 4 compatible. Chris@76: eval('?' . '>' . preg_replace(array( Chris@76: '~class\s+([\w-_]+)([^}]+)function\s+__construct\s*\(~', Chris@76: '~([\s\t]+)public\s+\$~', Chris@76: '~([\s\t]+)private\s+\$~', Chris@76: '~([\s\t]+)protected\s+\$~', Chris@76: '~([\s\t]+)public\s+function\s+~', Chris@76: '~([\s\t]+)private\s+function\s+~', Chris@76: '~([\s\t]+)protected\s+function\s+~', Chris@76: ), array( Chris@76: 'class $1$2function $1(', Chris@76: '$1var $', Chris@76: '$1var $', Chris@76: '$1var $', Chris@76: '$1function ', Chris@76: '$1function ', Chris@76: '$1function ', Chris@76: ), rtrim(file_get_contents($sourcedir . '/' . $filename)))); Chris@76: } Chris@76: else Chris@76: require_once($sourcedir . '/' . $filename); Chris@76: } Chris@76: Chris@76: function setupMenuContext() Chris@76: { Chris@76: global $context, $modSettings, $user_info, $txt, $scripturl; Chris@76: Chris@76: // Set up the menu privileges. Chris@76: $context['allow_search'] = allowedTo('search_posts'); Chris@76: $context['allow_admin'] = allowedTo(array('admin_forum', 'manage_boards', 'manage_permissions', 'moderate_forum', 'manage_membergroups', 'manage_bans', 'send_mail', 'edit_news', 'manage_attachments', 'manage_smileys')); Chris@76: $context['allow_edit_profile'] = !$user_info['is_guest'] && allowedTo(array('profile_view_own', 'profile_view_any', 'profile_identity_own', 'profile_identity_any', 'profile_extra_own', 'profile_extra_any', 'profile_remove_own', 'profile_remove_any', 'moderate_forum', 'manage_membergroups', 'profile_title_own', 'profile_title_any')); Chris@76: $context['allow_memberlist'] = allowedTo('view_mlist'); Chris@76: $context['allow_calendar'] = allowedTo('calendar_view') && !empty($modSettings['cal_enabled']); Chris@76: $context['allow_moderation_center'] = $context['user']['can_mod']; Chris@76: $context['allow_pm'] = allowedTo('pm_read'); Chris@76: Chris@76: $cacheTime = $modSettings['lastActive'] * 60; Chris@76: Chris@76: // All the buttons we can possible want and then some, try pulling the final list of buttons from cache first. Chris@76: if (($menu_buttons = cache_get_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $cacheTime)) === null || time() - $cacheTime <= $modSettings['settings_updated']) Chris@76: { Chris@76: $buttons = array( Chris@76: 'home' => array( Chris@76: 'title' => $txt['home'], Chris@76: 'href' => $scripturl, Chris@76: 'show' => true, Chris@76: 'sub_buttons' => array( Chris@76: ), Chris@76: 'is_last' => $context['right_to_left'], Chris@76: ), Chris@76: 'help' => array( Chris@76: 'title' => $txt['help'], Chris@76: 'href' => $scripturl . '?action=help', Chris@76: 'show' => true, Chris@76: 'sub_buttons' => array( Chris@76: ), Chris@76: ), Chris@76: 'search' => array( Chris@76: 'title' => $txt['search'], Chris@76: 'href' => $scripturl . '?action=search', Chris@76: 'show' => $context['allow_search'], Chris@76: 'sub_buttons' => array( Chris@76: ), Chris@76: ), Chris@76: 'admin' => array( Chris@76: 'title' => $txt['admin'], Chris@76: 'href' => $scripturl . '?action=admin', Chris@76: 'show' => $context['allow_admin'], Chris@76: 'sub_buttons' => array( Chris@76: 'featuresettings' => array( Chris@76: 'title' => $txt['modSettings_title'], Chris@76: 'href' => $scripturl . '?action=admin;area=featuresettings', Chris@76: 'show' => allowedTo('admin_forum'), Chris@76: ), Chris@76: 'packages' => array( Chris@76: 'title' => $txt['package'], Chris@76: 'href' => $scripturl . '?action=admin;area=packages', Chris@76: 'show' => allowedTo('admin_forum'), Chris@76: ), Chris@76: 'errorlog' => array( Chris@76: 'title' => $txt['errlog'], Chris@76: 'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc', Chris@76: 'show' => allowedTo('admin_forum') && !empty($modSettings['enableErrorLogging']), Chris@76: ), Chris@76: 'permissions' => array( Chris@76: 'title' => $txt['edit_permissions'], Chris@76: 'href' => $scripturl . '?action=admin;area=permissions', Chris@76: 'show' => allowedTo('manage_permissions'), Chris@76: 'is_last' => true, Chris@76: ), Chris@76: ), Chris@76: ), Chris@76: 'moderate' => array( Chris@76: 'title' => $txt['moderate'], Chris@76: 'href' => $scripturl . '?action=moderate', Chris@76: 'show' => $context['allow_moderation_center'], Chris@76: 'sub_buttons' => array( Chris@76: 'modlog' => array( Chris@76: 'title' => $txt['modlog_view'], Chris@76: 'href' => $scripturl . '?action=moderate;area=modlog', Chris@76: 'show' => !empty($modSettings['modlog_enabled']) && !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1', Chris@76: ), Chris@76: 'poststopics' => array( Chris@76: 'title' => $txt['mc_unapproved_poststopics'], Chris@76: 'href' => $scripturl . '?action=moderate;area=postmod;sa=posts', Chris@76: 'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']), Chris@76: ), Chris@76: 'attachments' => array( Chris@76: 'title' => $txt['mc_unapproved_attachments'], Chris@76: 'href' => $scripturl . '?action=moderate;area=attachmod;sa=attachments', Chris@76: 'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']), Chris@76: ), Chris@76: 'reports' => array( Chris@76: 'title' => $txt['mc_reported_posts'], Chris@76: 'href' => $scripturl . '?action=moderate;area=reports', Chris@76: 'show' => !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1', Chris@76: 'is_last' => true, Chris@76: ), Chris@76: ), Chris@76: ), Chris@76: 'profile' => array( Chris@76: 'title' => $txt['profile'], Chris@76: 'href' => $scripturl . '?action=profile', Chris@76: 'show' => $context['allow_edit_profile'], Chris@76: 'sub_buttons' => array( Chris@76: 'summary' => array( Chris@76: 'title' => $txt['summary'], Chris@76: 'href' => $scripturl . '?action=profile', Chris@76: 'show' => true, Chris@76: ), Chris@76: 'account' => array( Chris@76: 'title' => $txt['account'], Chris@76: 'href' => $scripturl . '?action=profile;area=account', Chris@76: 'show' => allowedTo(array('profile_identity_any', 'profile_identity_own', 'manage_membergroups')), Chris@76: ), Chris@76: 'profile' => array( Chris@76: 'title' => $txt['forumprofile'], Chris@76: 'href' => $scripturl . '?action=profile;area=forumprofile', Chris@76: 'show' => allowedTo(array('profile_extra_any', 'profile_extra_own')), Chris@76: 'is_last' => true, Chris@76: ), Chris@76: ), Chris@76: ), Chris@76: 'pm' => array( Chris@76: 'title' => $txt['pm_short'], Chris@76: 'href' => $scripturl . '?action=pm', Chris@76: 'show' => $context['allow_pm'], Chris@76: 'sub_buttons' => array( Chris@76: 'pm_read' => array( Chris@76: 'title' => $txt['pm_menu_read'], Chris@76: 'href' => $scripturl . '?action=pm', Chris@76: 'show' => allowedTo('pm_read'), Chris@76: ), Chris@76: 'pm_send' => array( Chris@76: 'title' => $txt['pm_menu_send'], Chris@76: 'href' => $scripturl . '?action=pm;sa=send', Chris@76: 'show' => allowedTo('pm_send'), Chris@76: 'is_last' => true, Chris@76: ), Chris@76: ), Chris@76: ), Chris@76: 'calendar' => array( Chris@76: 'title' => $txt['calendar'], Chris@76: 'href' => $scripturl . '?action=calendar', Chris@76: 'show' => $context['allow_calendar'], Chris@76: 'sub_buttons' => array( Chris@76: 'view' => array( Chris@76: 'title' => $txt['calendar_menu'], Chris@76: 'href' => $scripturl . '?action=calendar', Chris@76: 'show' => allowedTo('calendar_post'), Chris@76: ), Chris@76: 'post' => array( Chris@76: 'title' => $txt['calendar_post_event'], Chris@76: 'href' => $scripturl . '?action=calendar;sa=post', Chris@76: 'show' => allowedTo('calendar_post'), Chris@76: 'is_last' => true, Chris@76: ), Chris@76: ), Chris@76: ), Chris@76: 'mlist' => array( Chris@76: 'title' => $txt['members_title'], Chris@76: 'href' => $scripturl . '?action=mlist', Chris@76: 'show' => $context['allow_memberlist'], Chris@76: 'sub_buttons' => array( Chris@76: 'mlist_view' => array( Chris@76: 'title' => $txt['mlist_menu_view'], Chris@76: 'href' => $scripturl . '?action=mlist', Chris@76: 'show' => true, Chris@76: ), Chris@76: 'mlist_search' => array( Chris@76: 'title' => $txt['mlist_search'], Chris@76: 'href' => $scripturl . '?action=mlist;sa=search', Chris@76: 'show' => true, Chris@76: 'is_last' => true, Chris@76: ), Chris@76: ), Chris@76: ), Chris@76: 'login' => array( Chris@76: 'title' => $txt['login'], Chris@76: 'href' => $scripturl . '?action=login', Chris@76: 'show' => $user_info['is_guest'], Chris@76: 'sub_buttons' => array( Chris@76: ), Chris@76: ), Chris@76: 'register' => array( Chris@76: 'title' => $txt['register'], Chris@76: 'href' => $scripturl . '?action=register', Chris@76: 'show' => $user_info['is_guest'], Chris@76: 'sub_buttons' => array( Chris@76: ), Chris@76: 'is_last' => !$context['right_to_left'], Chris@76: ), Chris@76: 'logout' => array( Chris@76: 'title' => $txt['logout'], Chris@76: 'href' => $scripturl . '?action=logout;%1$s=%2$s', Chris@76: 'show' => !$user_info['is_guest'], Chris@76: 'sub_buttons' => array( Chris@76: ), Chris@76: 'is_last' => !$context['right_to_left'], Chris@76: ), Chris@76: ); Chris@76: Chris@76: // Allow editing menu buttons easily. Chris@76: call_integration_hook('integrate_menu_buttons', array(&$buttons)); Chris@76: Chris@76: // Now we put the buttons in the context so the theme can use them. Chris@76: $menu_buttons = array(); Chris@76: foreach ($buttons as $act => $button) Chris@76: if (!empty($button['show'])) Chris@76: { Chris@76: $button['active_button'] = false; Chris@76: Chris@76: // Make sure the last button truely is the last button. Chris@76: if (!empty($button['is_last'])) Chris@76: { Chris@76: if (isset($last_button)) Chris@76: unset($menu_buttons[$last_button]['is_last']); Chris@76: $last_button = $act; Chris@76: } Chris@76: Chris@76: // Go through the sub buttons if there are any. Chris@76: if (!empty($button['sub_buttons'])) Chris@76: foreach ($button['sub_buttons'] as $key => $subbutton) Chris@76: { Chris@76: if (empty($subbutton['show'])) Chris@76: unset($button['sub_buttons'][$key]); Chris@76: Chris@76: // 2nd level sub buttons next... Chris@76: if (!empty($subbutton['sub_buttons'])) Chris@76: { Chris@76: foreach ($subbutton['sub_buttons'] as $key2 => $sub_button2) Chris@76: { Chris@76: if (empty($sub_button2['show'])) Chris@76: unset($button['sub_buttons'][$key]['sub_buttons'][$key2]); Chris@76: } Chris@76: } Chris@76: } Chris@76: Chris@76: $menu_buttons[$act] = $button; Chris@76: } Chris@76: Chris@76: if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2) Chris@76: cache_put_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $menu_buttons, $cacheTime); Chris@76: } Chris@76: Chris@76: $context['menu_buttons'] = $menu_buttons; Chris@76: Chris@76: // Logging out requires the session id in the url. Chris@76: if (isset($context['menu_buttons']['logout'])) Chris@76: $context['menu_buttons']['logout']['href'] = sprintf($context['menu_buttons']['logout']['href'], $context['session_var'], $context['session_id']); Chris@76: Chris@76: // Figure out which action we are doing so we can set the active tab. Chris@76: // Default to home. Chris@76: $current_action = 'home'; Chris@76: Chris@76: if (isset($context['menu_buttons'][$context['current_action']])) Chris@76: $current_action = $context['current_action']; Chris@76: elseif ($context['current_action'] == 'search2') Chris@76: $current_action = 'search'; Chris@76: elseif ($context['current_action'] == 'theme') Chris@76: $current_action = isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'pick' ? 'profile' : 'admin'; Chris@76: elseif ($context['current_action'] == 'register2') Chris@76: $current_action = 'register'; Chris@76: elseif ($context['current_action'] == 'login2' || ($user_info['is_guest'] && $context['current_action'] == 'reminder')) Chris@76: $current_action = 'login'; Chris@76: elseif ($context['current_action'] == 'groups' && $context['allow_moderation_center']) Chris@76: $current_action = 'moderate'; Chris@76: Chris@76: $context['menu_buttons'][$current_action]['active_button'] = true; Chris@76: Chris@76: if (!$user_info['is_guest'] && $context['user']['unread_messages'] > 0 && isset($context['menu_buttons']['pm'])) Chris@76: { Chris@76: $context['menu_buttons']['pm']['alttitle'] = $context['menu_buttons']['pm']['title'] . ' [' . $context['user']['unread_messages'] . ']'; Chris@76: $context['menu_buttons']['pm']['title'] .= ' [' . $context['user']['unread_messages'] . ']'; Chris@76: } Chris@76: } Chris@76: Chris@76: // Generate a random seed and ensure it's stored in settings. Chris@76: function smf_seed_generator() Chris@76: { Chris@76: global $modSettings; Chris@76: Chris@76: // Never existed? Chris@76: if (empty($modSettings['rand_seed'])) Chris@76: { Chris@76: $modSettings['rand_seed'] = microtime() * 1000000; Chris@76: updateSettings(array('rand_seed' => $modSettings['rand_seed'])); Chris@76: } Chris@76: Chris@76: if (@version_compare(PHP_VERSION, '4.2.0') == -1) Chris@76: { Chris@76: $seed = ($modSettings['rand_seed'] + ((double) microtime() * 1000003)) & 0x7fffffff; Chris@76: mt_srand($seed); Chris@76: } Chris@76: Chris@76: // Change the seed. Chris@76: updateSettings(array('rand_seed' => mt_rand())); Chris@76: } Chris@76: Chris@76: // Process functions of an integration hook. Chris@76: function call_integration_hook($hook, $parameters = array()) Chris@76: { Chris@76: global $modSettings; Chris@76: Chris@76: $results = array(); Chris@76: if (empty($modSettings[$hook])) Chris@76: return $results; Chris@76: Chris@76: $functions = explode(',', $modSettings[$hook]); Chris@76: Chris@76: // Loop through each function. Chris@76: foreach ($functions as $function) Chris@76: { Chris@76: $function = trim($function); Chris@76: $call = strpos($function, '::') !== false ? explode('::', $function) : $function; Chris@76: Chris@76: // Is it valid? Chris@76: if (is_callable($call)) Chris@76: $results[$function] = call_user_func_array($call, $parameters); Chris@76: } Chris@76: Chris@76: return $results; Chris@76: } Chris@76: Chris@76: // Add a function for integration hook. Chris@76: function add_integration_function($hook, $function, $permanent = true) Chris@76: { Chris@76: global $smcFunc, $modSettings; Chris@76: Chris@76: // Is it going to be permanent? Chris@76: if ($permanent) Chris@76: { Chris@76: $request = $smcFunc['db_query']('', ' Chris@76: SELECT value Chris@76: FROM {db_prefix}settings Chris@76: WHERE variable = {string:variable}', Chris@76: array( Chris@76: 'variable' => $hook, Chris@76: ) Chris@76: ); Chris@76: list($current_functions) = $smcFunc['db_fetch_row']($request); Chris@76: $smcFunc['db_free_result']($request); Chris@76: Chris@76: if (!empty($current_functions)) Chris@76: { Chris@76: $current_functions = explode(',', $current_functions); Chris@76: if (in_array($function, $current_functions)) Chris@76: return; Chris@76: Chris@76: $permanent_functions = array_merge($current_functions, array($function)); Chris@76: } Chris@76: else Chris@76: $permanent_functions = array($function); Chris@76: Chris@76: updateSettings(array($hook => implode(',', $permanent_functions))); Chris@76: } Chris@76: Chris@76: // Make current function list usable. Chris@76: $functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]); Chris@76: Chris@76: // Do nothing, if it's already there. Chris@76: if (in_array($function, $functions)) Chris@76: return; Chris@76: Chris@76: $functions[] = $function; Chris@76: $modSettings[$hook] = implode(',', $functions); Chris@76: } Chris@76: Chris@76: // Remove an integration hook function. Chris@76: function remove_integration_function($hook, $function) Chris@76: { Chris@76: global $smcFunc, $modSettings; Chris@76: Chris@76: // Get the permanent functions. Chris@76: $request = $smcFunc['db_query']('', ' Chris@76: SELECT value Chris@76: FROM {db_prefix}settings Chris@76: WHERE variable = {string:variable}', Chris@76: array( Chris@76: 'variable' => $hook, Chris@76: ) Chris@76: ); Chris@76: list($current_functions) = $smcFunc['db_fetch_row']($request); Chris@76: $smcFunc['db_free_result']($request); Chris@76: Chris@76: if (!empty($current_functions)) Chris@76: { Chris@76: $current_functions = explode(',', $current_functions); Chris@76: Chris@76: if (in_array($function, $current_functions)) Chris@76: updateSettings(array($hook => implode(',', array_diff($current_functions, array($function))))); Chris@76: } Chris@76: Chris@76: // Turn the function list into something usable. Chris@76: $functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]); Chris@76: Chris@76: // You can only remove it if it's available. Chris@76: if (!in_array($function, $functions)) Chris@76: return; Chris@76: Chris@76: $functions = array_diff($functions, array($function)); Chris@76: $modSettings[$hook] = implode(',', $functions); Chris@76: } Chris@76: Chris@76: ?>