time(), ); // #1 latest member ID, #2 the real name for a new registration. if (is_numeric($parameter1)) { $changes['latestMember'] = $parameter1; $changes['latestRealName'] = $parameter2; updateSettings(array('totalMembers' => true), true); } // We need to calculate the totals. else { // Update the latest activated member (highest id_member) and count. $result = $smcFunc['db_query']('', ' SELECT COUNT(*), MAX(id_member) FROM {db_prefix}members WHERE is_activated = {int:is_activated}', array( 'is_activated' => 1, ) ); list ($changes['totalMembers'], $changes['latestMember']) = $smcFunc['db_fetch_row']($result); $smcFunc['db_free_result']($result); // Get the latest activated member's display name. $result = $smcFunc['db_query']('', ' SELECT real_name FROM {db_prefix}members WHERE id_member = {int:id_member} LIMIT 1', array( 'id_member' => (int) $changes['latestMember'], ) ); list ($changes['latestRealName']) = $smcFunc['db_fetch_row']($result); $smcFunc['db_free_result']($result); // Are we using registration approval? if ((!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($modSettings['approveAccountDeletion'])) { // Update the amount of members awaiting approval - ignoring COPPA accounts, as you can't approve them until you get permission. $result = $smcFunc['db_query']('', ' SELECT COUNT(*) FROM {db_prefix}members WHERE is_activated IN ({array_int:activation_status})', array( 'activation_status' => array(3, 4), ) ); list ($changes['unapprovedMembers']) = $smcFunc['db_fetch_row']($result); $smcFunc['db_free_result']($result); } } updateSettings($changes); break; case 'message': if ($parameter1 === true && $parameter2 !== null) updateSettings(array('totalMessages' => true, 'maxMsgID' => $parameter2), true); else { // SUM and MAX on a smaller table is better for InnoDB tables. $result = $smcFunc['db_query']('', ' SELECT SUM(num_posts + unapproved_posts) AS total_messages, MAX(id_last_msg) AS max_msg_id FROM {db_prefix}boards WHERE redirect = {string:blank_redirect}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' AND id_board != {int:recycle_board}' : ''), array( 'recycle_board' => isset($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0, 'blank_redirect' => '', ) ); $row = $smcFunc['db_fetch_assoc']($result); $smcFunc['db_free_result']($result); updateSettings(array( 'totalMessages' => $row['total_messages'] === null ? 0 : $row['total_messages'], 'maxMsgID' => $row['max_msg_id'] === null ? 0 : $row['max_msg_id'] )); } break; case 'subject': // Remove the previous subject (if any). $smcFunc['db_query']('', ' DELETE FROM {db_prefix}log_search_subjects WHERE id_topic = {int:id_topic}', array( 'id_topic' => (int) $parameter1, ) ); // Insert the new subject. if ($parameter2 !== null) { $parameter1 = (int) $parameter1; $parameter2 = text2words($parameter2); $inserts = array(); foreach ($parameter2 as $word) $inserts[] = array($word, $parameter1); if (!empty($inserts)) $smcFunc['db_insert']('ignore', '{db_prefix}log_search_subjects', array('word' => 'string', 'id_topic' => 'int'), $inserts, array('word', 'id_topic') ); } break; case 'topic': if ($parameter1 === true) updateSettings(array('totalTopics' => true), true); else { // Get the number of topics - a SUM is better for InnoDB tables. // We also ignore the recycle bin here because there will probably be a bunch of one-post topics there. $result = $smcFunc['db_query']('', ' SELECT SUM(num_topics + unapproved_topics) AS total_topics FROM {db_prefix}boards' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' WHERE id_board != {int:recycle_board}' : ''), array( 'recycle_board' => !empty($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0, ) ); $row = $smcFunc['db_fetch_assoc']($result); $smcFunc['db_free_result']($result); updateSettings(array('totalTopics' => $row['total_topics'] === null ? 0 : $row['total_topics'])); } break; case 'postgroups': // Parameter two is the updated columns: we should check to see if we base groups off any of these. if ($parameter2 !== null && !in_array('posts', $parameter2)) return; if (($postgroups = cache_get_data('updateStats:postgroups', 360)) == null) { // Fetch the postgroups! $request = $smcFunc['db_query']('', ' SELECT id_group, min_posts FROM {db_prefix}membergroups WHERE min_posts != {int:min_posts}', array( 'min_posts' => -1, ) ); $postgroups = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) $postgroups[$row['id_group']] = $row['min_posts']; $smcFunc['db_free_result']($request); // Sort them this way because if it's done with MySQL it causes a filesort :(. arsort($postgroups); cache_put_data('updateStats:postgroups', $postgroups, 360); } // Oh great, they've screwed their post groups. if (empty($postgroups)) return; // Set all membergroups from most posts to least posts. $conditions = ''; foreach ($postgroups as $id => $min_posts) { $conditions .= ' WHEN posts >= ' . $min_posts . (!empty($lastMin) ? ' AND posts <= ' . $lastMin : '') . ' THEN ' . $id; $lastMin = $min_posts; } // A big fat CASE WHEN... END is faster than a zillion UPDATE's ;). $smcFunc['db_query']('', ' UPDATE {db_prefix}members SET id_post_group = CASE ' . $conditions . ' ELSE 0 END' . ($parameter1 != null ? ' WHERE ' . (is_array($parameter1) ? 'id_member IN ({array_int:members})' : 'id_member = {int:members}') : ''), array( 'members' => $parameter1, ) ); break; default: trigger_error('updateStats(): Invalid statistic type \'' . $type . '\'', E_USER_NOTICE); } } // Assumes the data has been htmlspecialchar'd. function updateMemberData($members, $data) { global $modSettings, $user_info, $smcFunc; $parameters = array(); if (is_array($members)) { $condition = 'id_member IN ({array_int:members})'; $parameters['members'] = $members; } elseif ($members === null) $condition = '1=1'; else { $condition = 'id_member = {int:member}'; $parameters['member'] = $members; } if (!empty($modSettings['integrate_change_member_data'])) { // Only a few member variables are really interesting for integration. $integration_vars = array( 'member_name', 'real_name', 'email_address', 'id_group', 'gender', 'birthdate', 'website_title', 'website_url', 'location', 'hide_email', 'time_format', 'time_offset', 'avatar', 'lngfile', ); $vars_to_integrate = array_intersect($integration_vars, array_keys($data)); // Only proceed if there are any variables left to call the integration function. if (count($vars_to_integrate) != 0) { // Fetch a list of member_names if necessary if ((!is_array($members) && $members === $user_info['id']) || (is_array($members) && count($members) == 1 && in_array($user_info['id'], $members))) $member_names = array($user_info['username']); else { $member_names = array(); $request = $smcFunc['db_query']('', ' SELECT member_name FROM {db_prefix}members WHERE ' . $condition, $parameters ); while ($row = $smcFunc['db_fetch_assoc']($request)) $member_names[] = $row['member_name']; $smcFunc['db_free_result']($request); } if (!empty($member_names)) foreach ($vars_to_integrate as $var) call_integration_hook('integrate_change_member_data', array($member_names, $var, $data[$var])); } } // Everything is assumed to be a string unless it's in the below. $knownInts = array( 'date_registered', 'posts', 'id_group', 'last_login', 'instant_messages', 'unread_messages', 'new_pm', 'pm_prefs', 'gender', 'hide_email', 'show_online', 'pm_email_notify', 'pm_receive_from', 'karma_good', 'karma_bad', 'notify_announcements', 'notify_send_body', 'notify_regularity', 'notify_types', 'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning', ); $knownFloats = array( 'time_offset', ); $setString = ''; foreach ($data as $var => $val) { $type = 'string'; if (in_array($var, $knownInts)) $type = 'int'; elseif (in_array($var, $knownFloats)) $type = 'float'; elseif ($var == 'birthdate') $type = 'date'; // Doing an increment? if ($type == 'int' && ($val === '+' || $val === '-')) { $val = $var . ' ' . $val . ' 1'; $type = 'raw'; } // Ensure posts, instant_messages, and unread_messages don't overflow or underflow. if (in_array($var, array('posts', 'instant_messages', 'unread_messages'))) { if (preg_match('~^' . $var . ' (\+ |- |\+ -)([\d]+)~', $val, $match)) { if ($match[1] != '+ ') $val = 'CASE WHEN ' . $var . ' <= ' . abs($match[2]) . ' THEN 0 ELSE ' . $val . ' END'; $type = 'raw'; } } $setString .= ' ' . $var . ' = {' . $type . ':p_' . $var . '},'; $parameters['p_' . $var] = $val; } $smcFunc['db_query']('', ' UPDATE {db_prefix}members SET' . substr($setString, 0, -1) . ' WHERE ' . $condition, $parameters ); updateStats('postgroups', $members, array_keys($data)); // Clear any caching? if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2 && !empty($members)) { if (!is_array($members)) $members = array($members); foreach ($members as $member) { if ($modSettings['cache_enable'] >= 3) { cache_put_data('member_data-profile-' . $member, null, 120); cache_put_data('member_data-normal-' . $member, null, 120); cache_put_data('member_data-minimal-' . $member, null, 120); } cache_put_data('user_settings-' . $member, null, 60); } } } // Updates the settings table as well as $modSettings... only does one at a time if $update is true. function updateSettings($changeArray, $update = false, $debug = false) { global $modSettings, $smcFunc; if (empty($changeArray) || !is_array($changeArray)) return; // In some cases, this may be better and faster, but for large sets we don't want so many UPDATEs. if ($update) { foreach ($changeArray as $variable => $value) { $smcFunc['db_query']('', ' UPDATE {db_prefix}settings SET value = {' . ($value === false || $value === true ? 'raw' : 'string') . ':value} WHERE variable = {string:variable}', array( 'value' => $value === true ? 'value + 1' : ($value === false ? 'value - 1' : $value), 'variable' => $variable, ) ); $modSettings[$variable] = $value === true ? $modSettings[$variable] + 1 : ($value === false ? $modSettings[$variable] - 1 : $value); } // Clean out the cache and make sure the cobwebs are gone too. cache_put_data('modSettings', null, 90); return; } $replaceArray = array(); foreach ($changeArray as $variable => $value) { // Don't bother if it's already like that ;). if (isset($modSettings[$variable]) && $modSettings[$variable] == $value) continue; // If the variable isn't set, but would only be set to nothing'ness, then don't bother setting it. elseif (!isset($modSettings[$variable]) && empty($value)) continue; $replaceArray[] = array($variable, $value); $modSettings[$variable] = $value; } if (empty($replaceArray)) return; $smcFunc['db_insert']('replace', '{db_prefix}settings', array('variable' => 'string-255', 'value' => 'string-65534'), $replaceArray, array('variable') ); // Kill the cache - it needs redoing now, but we won't bother ourselves with that here. cache_put_data('modSettings', null, 90); } // Constructs a page list. // $pageindex = constructPageIndex($scripturl . '?board=' . $board, $_REQUEST['start'], $num_messages, $maxindex, true); function constructPageIndex($base_url, &$start, $max_value, $num_per_page, $flexible_start = false) { global $modSettings; // Save whether $start was less than 0 or not. $start = (int) $start; $start_invalid = $start < 0; // Make sure $start is a proper variable - not less than 0. if ($start_invalid) $start = 0; // Not greater than the upper bound. elseif ($start >= $max_value) $start = max(0, (int) $max_value - (((int) $max_value % (int) $num_per_page) == 0 ? $num_per_page : ((int) $max_value % (int) $num_per_page))); // And it has to be a multiple of $num_per_page! else $start = max(0, (int) $start - ((int) $start % (int) $num_per_page)); // Wireless will need the protocol on the URL somewhere. if (WIRELESS) $base_url .= ';' . WIRELESS_PROTOCOL; $base_link = '%2$s '; // Compact pages is off or on? if (empty($modSettings['compactTopicPagesEnable'])) { // Show the left arrow. $pageindex = $start == 0 ? ' ' : sprintf($base_link, $start - $num_per_page, '«'); // Show all the pages. $display_page = 1; for ($counter = 0; $counter < $max_value; $counter += $num_per_page) $pageindex .= $start == $counter && !$start_invalid ? '' . $display_page++ . ' ' : sprintf($base_link, $counter, $display_page++); // Show the right arrow. $display_page = ($start + $num_per_page) > $max_value ? $max_value : ($start + $num_per_page); if ($start != $counter - $max_value && !$start_invalid) $pageindex .= $display_page > $counter - $num_per_page ? ' ' : sprintf($base_link, $display_page, '»'); } else { // If they didn't enter an odd value, pretend they did. $PageContiguous = (int) ($modSettings['compactTopicPagesContiguous'] - ($modSettings['compactTopicPagesContiguous'] % 2)) / 2; // Show the first page. (>1< ... 6 7 [8] 9 10 ... 15) if ($start > $num_per_page * $PageContiguous) $pageindex = sprintf($base_link, 0, '1'); else $pageindex = ''; // Show the ... after the first page. (1 >...< 6 7 [8] 9 10 ... 15) if ($start > $num_per_page * ($PageContiguous + 1)) $pageindex .= ' ... '; // Show the pages before the current one. (1 ... >6 7< [8] 9 10 ... 15) for ($nCont = $PageContiguous; $nCont >= 1; $nCont--) if ($start >= $num_per_page * $nCont) { $tmpStart = $start - $num_per_page * $nCont; $pageindex.= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1); } // Show the current page. (1 ... 6 7 >[8]< 9 10 ... 15) if (!$start_invalid) $pageindex .= '[' . ($start / $num_per_page + 1) . '] '; else $pageindex .= sprintf($base_link, $start, $start / $num_per_page + 1); // Show the pages after the current one... (1 ... 6 7 [8] >9 10< ... 15) $tmpMaxPages = (int) (($max_value - 1) / $num_per_page) * $num_per_page; for ($nCont = 1; $nCont <= $PageContiguous; $nCont++) if ($start + $num_per_page * $nCont <= $tmpMaxPages) { $tmpStart = $start + $num_per_page * $nCont; $pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1); } // Show the '...' part near the end. (1 ... 6 7 [8] 9 10 >...< 15) if ($start + $num_per_page * ($PageContiguous + 1) < $tmpMaxPages) $pageindex .= ' ... '; // Show the last number in the list. (1 ... 6 7 [8] 9 10 ... >15<) if ($start + $num_per_page * $PageContiguous < $tmpMaxPages) $pageindex .= sprintf($base_link, $tmpMaxPages, $tmpMaxPages / $num_per_page + 1); } return $pageindex; } // Formats a number to display in the style of the admin's choosing. function comma_format($number, $override_decimal_count = false) { global $txt; static $thousands_separator = null, $decimal_separator = null, $decimal_count = null; // !!! Should, perhaps, this just be handled in the language files, and not a mod setting? // (French uses 1 234,00 for example... what about a multilingual forum?) // Cache these values... if ($decimal_separator === null) { // Not set for whatever reason? if (empty($txt['number_format']) || preg_match('~^1([^\d]*)?234([^\d]*)(0*?)$~', $txt['number_format'], $matches) != 1) return $number; // Cache these each load... $thousands_separator = $matches[1]; $decimal_separator = $matches[2]; $decimal_count = strlen($matches[3]); } // Format the string with our friend, number_format. return number_format($number, is_float($number) ? ($override_decimal_count === false ? $decimal_count : $override_decimal_count) : 0, $decimal_separator, $thousands_separator); } // Format a time to make it look purdy. function timeformat($log_time, $show_today = true, $offset_type = false) { global $context, $user_info, $txt, $modSettings, $smcFunc; static $non_twelve_hour; // Offset the time. if (!$offset_type) $time = $log_time + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600; // Just the forum offset? elseif ($offset_type == 'forum') $time = $log_time + $modSettings['time_offset'] * 3600; else $time = $log_time; // We can't have a negative date (on Windows, at least.) if ($log_time < 0) $log_time = 0; // Today and Yesterday? if ($modSettings['todayMod'] >= 1 && $show_today === true) { // Get the current time. $nowtime = forum_time(); $then = @getdate($time); $now = @getdate($nowtime); // Try to make something of a time format string... $s = strpos($user_info['time_format'], '%S') === false ? '' : ':%S'; if (strpos($user_info['time_format'], '%H') === false && strpos($user_info['time_format'], '%T') === false) { $h = strpos($user_info['time_format'], '%l') === false ? '%I' : '%l'; $today_fmt = $h . ':%M' . $s . ' %p'; } else $today_fmt = '%H:%M' . $s; // Same day of the year, same year.... Today! if ($then['yday'] == $now['yday'] && $then['year'] == $now['year']) return $txt['today'] . timeformat($log_time, $today_fmt, $offset_type); // Day-of-year is one less and same year, or it's the first of the year and that's the last of the year... if ($modSettings['todayMod'] == '2' && (($then['yday'] == $now['yday'] - 1 && $then['year'] == $now['year']) || ($now['yday'] == 0 && $then['year'] == $now['year'] - 1) && $then['mon'] == 12 && $then['mday'] == 31)) return $txt['yesterday'] . timeformat($log_time, $today_fmt, $offset_type); } $str = !is_bool($show_today) ? $show_today : $user_info['time_format']; if (setlocale(LC_TIME, $txt['lang_locale'])) { if (!isset($non_twelve_hour)) $non_twelve_hour = trim(strftime('%p')) === ''; if ($non_twelve_hour && strpos($str, '%p') !== false) $str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str); foreach (array('%a', '%A', '%b', '%B') as $token) if (strpos($str, $token) !== false) $str = str_replace($token, !empty($txt['lang_capitalize_dates']) ? $smcFunc['ucwords'](strftime($token, $time)) : strftime($token, $time), $str); } else { // Do-it-yourself time localization. Fun. foreach (array('%a' => 'days_short', '%A' => 'days', '%b' => 'months_short', '%B' => 'months') as $token => $text_label) if (strpos($str, $token) !== false) $str = str_replace($token, $txt[$text_label][(int) strftime($token === '%a' || $token === '%A' ? '%w' : '%m', $time)], $str); if (strpos($str, '%p') !== false) $str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str); } // Windows doesn't support %e; on some versions, strftime fails altogether if used, so let's prevent that. if ($context['server']['is_windows'] && strpos($str, '%e') !== false) $str = str_replace('%e', ltrim(strftime('%d', $time), '0'), $str); // Format any other characters.. return strftime($str, $time); } // Removes special entities from strings. Compatibility... function un_htmlspecialchars($string) { static $translation; if (!isset($translation)) $translation = array_flip(get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES)) + array(''' => '\'', ' ' => ' '); return strtr($string, $translation); } // Shorten a subject + internationalization concerns. function shorten_subject($subject, $len) { global $smcFunc; // It was already short enough! if ($smcFunc['strlen']($subject) <= $len) return $subject; // Shorten it by the length it was too long, and strip off junk from the end. return $smcFunc['substr']($subject, 0, $len) . '...'; } // Spoiler choosing function function build_spoiler($position, $topic = '') { global $txt, $settings, $context, $modSettings; //Use global style if the theme-specific isn't set or is set to "follow global" (aka 0) $spoilerTagStyle = isset($settings['spoiler_style']) ? $settings['spoiler_style'] : 0; if($spoilerTagStyle == 0) { //Use "hover" if the global style isn't explicitly set $spoilerTagStyle = isset($modSettings['defaultSpoilerStyle']) ? $modSettings['defaultSpoilerStyle'] : 1; } /* Styles: 3: button 2: link 1: hover (default) */ $retval = ''; switch($position) { case 'before': switch($spoilerTagStyle) { case 3: $retval = ( '
'. ' '. $txt['spoiler_tag_click_info'].'