commit be85b7ae06857ecdb1d707ba9caea15449de5436 Author: moparisthebest Date: Mon Feb 3 21:58:48 2014 -0500 First commit of smf_2-0-7_upgrade.tar.bz2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a27692 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# backup files +**~ +**.bak + +# settings files +Settings.php +Settings_bak.php + +# things that change +attachments/ +avatars/ +cache/ diff --git a/Packages/.htaccess b/Packages/.htaccess new file mode 100644 index 0000000..91386d1 --- /dev/null +++ b/Packages/.htaccess @@ -0,0 +1,5 @@ + + Order Deny,Allow + Deny from all + Allow from localhost + \ No newline at end of file diff --git a/Packages/backups/.htaccess b/Packages/backups/.htaccess new file mode 100644 index 0000000..91386d1 --- /dev/null +++ b/Packages/backups/.htaccess @@ -0,0 +1,5 @@ + + Order Deny,Allow + Deny from all + Allow from localhost + \ No newline at end of file diff --git a/Packages/backups/index.php b/Packages/backups/index.php new file mode 100644 index 0000000..be9895a --- /dev/null +++ b/Packages/backups/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Packages/index.php b/Packages/index.php new file mode 100644 index 0000000..fc83b4e --- /dev/null +++ b/Packages/index.php @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/Packages/installed.list b/Packages/installed.list new file mode 100644 index 0000000..e69de29 diff --git a/SSI.php b/SSI.php new file mode 100644 index 0000000..8988fe3 --- /dev/null +++ b/SSI.php @@ -0,0 +1,1999 @@ +', $context['user']['name'], '', allowedTo('pm_read') ? ', ' . $txt['msg_alert_you_have'] . ' ' . $context['user']['messages'] . ' ' . ($context['user']['messages'] == '1' ? $txt['message_lowercase'] : $txt['msg_alert_messages']) . '' . $txt['newmessages4'] . ' ' . $context['user']['unread_messages'] . ' ' . ($context['user']['unread_messages'] == '1' ? $txt['newmessages0'] : $txt['newmessages1']) : '', '.'; + } + // Don't echo... then do what?! + else + return $context['user']; +} + +// Display a menu bar, like is displayed at the top of the forum. +function ssi_menubar($output_method = 'echo') +{ + global $context; + + if ($output_method == 'echo') + template_menu(); + // What else could this do? + else + return $context['menu_buttons']; +} + +// Show a logout link. +function ssi_logout($redirect_to = '', $output_method = 'echo') +{ + global $context, $txt, $scripturl; + + if ($redirect_to != '') + $_SESSION['logout_url'] = $redirect_to; + + // Guests can't log out. + if ($context['user']['is_guest']) + return false; + + $link = '' . $txt['logout'] . ''; + + if ($output_method == 'echo') + echo $link; + else + return $link; +} + +// Recent post list: [board] Subject by Poster Date +function ssi_recentPosts($num_recent = 8, $exclude_boards = null, $include_boards = null, $output_method = 'echo', $limit_body = true) +{ + global $context, $settings, $scripturl, $txt, $db_prefix, $user_info; + global $modSettings, $smcFunc; + + // Excluding certain boards... + if ($exclude_boards === null && !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0) + $exclude_boards = array($modSettings['recycle_board']); + else + $exclude_boards = empty($exclude_boards) ? array() : (is_array($exclude_boards) ? $exclude_boards : array($exclude_boards)); + + // What about including certain boards - note we do some protection here as pre-2.0 didn't have this parameter. + if (is_array($include_boards) || (int) $include_boards === $include_boards) + { + $include_boards = is_array($include_boards) ? $include_boards : array($include_boards); + } + elseif ($include_boards != null) + { + $include_boards = array(); + } + + // Let's restrict the query boys (and girls) + $query_where = ' + m.id_msg >= {int:min_message_id} + ' . (empty($exclude_boards) ? '' : ' + AND b.id_board NOT IN ({array_int:exclude_boards})') . ' + ' . ($include_boards === null ? '' : ' + AND b.id_board IN ({array_int:include_boards})') . ' + AND {query_wanna_see_board}' . ($modSettings['postmod_active'] ? ' + AND m.approved = {int:is_approved}' : ''); + + $query_where_params = array( + 'is_approved' => 1, + 'include_boards' => $include_boards === null ? '' : $include_boards, + 'exclude_boards' => empty($exclude_boards) ? '' : $exclude_boards, + 'min_message_id' => $modSettings['maxMsgID'] - 25 * min($num_recent, 5), + ); + + // Past to this simpleton of a function... + return ssi_queryPosts($query_where, $query_where_params, $num_recent, 'm.id_msg DESC', $output_method, $limit_body); +} + +// Fetch a post with a particular ID. By default will only show if you have permission to the see the board in question - this can be overriden. +function ssi_fetchPosts($post_ids = array(), $override_permissions = false, $output_method = 'echo') +{ + global $user_info, $modSettings; + + if (empty($post_ids)) + return; + + // Allow the user to request more than one - why not? + $post_ids = is_array($post_ids) ? $post_ids : array($post_ids); + + // Restrict the posts required... + $query_where = ' + m.id_msg IN ({array_int:message_list})' . ($override_permissions ? '' : ' + AND {query_wanna_see_board}') . ($modSettings['postmod_active'] ? ' + AND m.approved = {int:is_approved}' : ''); + $query_where_params = array( + 'message_list' => $post_ids, + 'is_approved' => 1, + ); + + // Then make the query and dump the data. + return ssi_queryPosts($query_where, $query_where_params, '', 'm.id_msg DESC', $output_method, false, $override_permissions); +} + +// This removes code duplication in other queries - don't call it direct unless you really know what you're up to. +function ssi_queryPosts($query_where = '', $query_where_params = array(), $query_limit = 10, $query_order = 'm.id_msg DESC', $output_method = 'echo', $limit_body = false, $override_permissions = false) +{ + global $context, $settings, $scripturl, $txt, $db_prefix, $user_info; + global $modSettings, $smcFunc; + + // Find all the posts. Newer ones will have higher IDs. + $request = $smcFunc['db_query']('substring', ' + SELECT + m.poster_time, m.subject, m.id_topic, m.id_member, m.id_msg, m.id_board, b.name AS board_name, + IFNULL(mem.real_name, m.poster_name) AS poster_name, ' . ($user_info['is_guest'] ? '1 AS is_read, 0 AS new_from' : ' + IFNULL(lt.id_msg, IFNULL(lmr.id_msg, 0)) >= m.id_msg_modified AS is_read, + IFNULL(lt.id_msg, IFNULL(lmr.id_msg, -1)) + 1 AS new_from') . ', ' . ($limit_body ? 'SUBSTRING(m.body, 1, 384) AS body' : 'm.body') . ', m.smileys_enabled + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)' . (!$user_info['is_guest'] ? ' + LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = m.id_topic AND lt.id_member = {int:current_member}) + LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = m.id_board AND lmr.id_member = {int:current_member})' : '') . ' + WHERE 1=1 ' . ($override_permissions ? '' : ' + AND {query_wanna_see_board}') . ($modSettings['postmod_active'] ? ' + AND m.approved = {int:is_approved}' : '') . ' + ' . (empty($query_where) ? '' : 'AND ' . $query_where) . ' + ORDER BY ' . $query_order . ' + ' . ($query_limit == '' ? '' : 'LIMIT ' . $query_limit), + array_merge($query_where_params, array( + 'current_member' => $user_info['id'], + 'is_approved' => 1, + )) + ); + $posts = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']); + + // Censor it! + censorText($row['subject']); + censorText($row['body']); + + $preview = strip_tags(strtr($row['body'], array('
' => ' '))); + + // Build the array. + $posts[] = array( + 'id' => $row['id_msg'], + 'board' => array( + 'id' => $row['id_board'], + 'name' => $row['board_name'], + 'href' => $scripturl . '?board=' . $row['id_board'] . '.0', + 'link' => '' . $row['board_name'] . '' + ), + 'topic' => $row['id_topic'], + 'poster' => array( + 'id' => $row['id_member'], + 'name' => $row['poster_name'], + 'href' => empty($row['id_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member'], + 'link' => empty($row['id_member']) ? $row['poster_name'] : '' . $row['poster_name'] . '' + ), + 'subject' => $row['subject'], + 'short_subject' => shorten_subject($row['subject'], 25), + 'preview' => $smcFunc['strlen']($preview) > 128 ? $smcFunc['substr']($preview, 0, 128) . '...' : $preview, + 'body' => $row['body'], + 'time' => timeformat($row['poster_time']), + 'timestamp' => forum_time(true, $row['poster_time']), + 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . ';topicseen#new', + 'link' => '' . $row['subject'] . '', + 'new' => !empty($row['is_read']), + 'is_new' => empty($row['is_read']), + 'new_from' => $row['new_from'], + ); + } + $smcFunc['db_free_result']($request); + + // Just return it. + if ($output_method != 'echo' || empty($posts)) + return $posts; + + echo ' + '; + foreach ($posts as $post) + echo ' + + + + + '; + echo ' +
+ [', $post['board']['link'], '] + + ', $post['subject'], ' + ', $txt['by'], ' ', $post['poster']['link'], ' + ', $post['is_new'] ? '' . $txt['new'] . '' : '', ' + + ', $post['time'], ' +
'; +} + +// Recent topic list: [board] Subject by Poster Date +function ssi_recentTopics($num_recent = 8, $exclude_boards = null, $include_boards = null, $output_method = 'echo') +{ + global $context, $settings, $scripturl, $txt, $db_prefix, $user_info; + global $modSettings, $smcFunc; + + if ($exclude_boards === null && !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0) + $exclude_boards = array($modSettings['recycle_board']); + else + $exclude_boards = empty($exclude_boards) ? array() : (is_array($exclude_boards) ? $exclude_boards : array($exclude_boards)); + + // Only some boards?. + if (is_array($include_boards) || (int) $include_boards === $include_boards) + { + $include_boards = is_array($include_boards) ? $include_boards : array($include_boards); + } + elseif ($include_boards != null) + { + $output_method = $include_boards; + $include_boards = array(); + } + + $stable_icons = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'moved', 'recycled', 'wireless'); + $icon_sources = array(); + foreach ($stable_icons as $icon) + $icon_sources[$icon] = 'images_url'; + + // Find all the posts in distinct topics. Newer ones will have higher IDs. + $request = $smcFunc['db_query']('substring', ' + SELECT + m.poster_time, ms.subject, m.id_topic, m.id_member, m.id_msg, b.id_board, b.name AS board_name, t.num_replies, t.num_views, + IFNULL(mem.real_name, m.poster_name) AS poster_name, ' . ($user_info['is_guest'] ? '1 AS is_read, 0 AS new_from' : ' + IFNULL(lt.id_msg, IFNULL(lmr.id_msg, 0)) >= m.id_msg_modified AS is_read, + IFNULL(lt.id_msg, IFNULL(lmr.id_msg, -1)) + 1 AS new_from') . ', SUBSTRING(m.body, 1, 384) AS body, m.smileys_enabled, m.icon + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_last_msg) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)' . (!$user_info['is_guest'] ? ' + LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member}) + LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = b.id_board AND lmr.id_member = {int:current_member})' : '') . ' + WHERE t.id_last_msg >= {int:min_message_id} + ' . (empty($exclude_boards) ? '' : ' + AND b.id_board NOT IN ({array_int:exclude_boards})') . ' + ' . (empty($include_boards) ? '' : ' + AND b.id_board IN ({array_int:include_boards})') . ' + AND {query_wanna_see_board}' . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved} + AND m.approved = {int:is_approved}' : '') . ' + ORDER BY t.id_last_msg DESC + LIMIT ' . $num_recent, + array( + 'current_member' => $user_info['id'], + 'include_boards' => empty($include_boards) ? '' : $include_boards, + 'exclude_boards' => empty($exclude_boards) ? '' : $exclude_boards, + 'min_message_id' => $modSettings['maxMsgID'] - 35 * min($num_recent, 5), + 'is_approved' => 1, + ) + ); + $posts = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $row['body'] = strip_tags(strtr(parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']), array('
' => ' '))); + if ($smcFunc['strlen']($row['body']) > 128) + $row['body'] = $smcFunc['substr']($row['body'], 0, 128) . '...'; + + // Censor the subject. + censorText($row['subject']); + censorText($row['body']); + + if (empty($modSettings['messageIconChecks_disable']) && !isset($icon_sources[$row['icon']])) + $icon_sources[$row['icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $row['icon'] . '.gif') ? 'images_url' : 'default_images_url'; + + // Build the array. + $posts[] = array( + 'board' => array( + 'id' => $row['id_board'], + 'name' => $row['board_name'], + 'href' => $scripturl . '?board=' . $row['id_board'] . '.0', + 'link' => '' . $row['board_name'] . '' + ), + 'topic' => $row['id_topic'], + 'poster' => array( + 'id' => $row['id_member'], + 'name' => $row['poster_name'], + 'href' => empty($row['id_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member'], + 'link' => empty($row['id_member']) ? $row['poster_name'] : '' . $row['poster_name'] . '' + ), + 'subject' => $row['subject'], + 'replies' => $row['num_replies'], + 'views' => $row['num_views'], + 'short_subject' => shorten_subject($row['subject'], 25), + 'preview' => $row['body'], + 'time' => timeformat($row['poster_time']), + 'timestamp' => forum_time(true, $row['poster_time']), + 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . ';topicseen#new', + 'link' => '' . $row['subject'] . '', + // Retained for compatibility - is technically incorrect! + 'new' => !empty($row['is_read']), + 'is_new' => empty($row['is_read']), + 'new_from' => $row['new_from'], + 'icon' => '' . $row['icon'] . '', + ); + } + $smcFunc['db_free_result']($request); + + // Just return it. + if ($output_method != 'echo' || empty($posts)) + return $posts; + + echo ' + '; + foreach ($posts as $post) + echo ' + + + + + '; + echo ' +
+ [', $post['board']['link'], '] + + ', $post['subject'], ' + ', $txt['by'], ' ', $post['poster']['link'], ' + ', !$post['is_new'] ? '' : '' . $txt['new'] . '', ' + + ', $post['time'], ' +
'; +} + +// Show the top poster's name and profile link. +function ssi_topPoster($topNumber = 1, $output_method = 'echo') +{ + global $db_prefix, $scripturl, $smcFunc; + + // Find the latest poster. + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name, posts + FROM {db_prefix}members + ORDER BY posts DESC + LIMIT ' . $topNumber, + array( + ) + ); + $return = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $return[] = array( + 'id' => $row['id_member'], + 'name' => $row['real_name'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_member'], + 'link' => '' . $row['real_name'] . '', + 'posts' => $row['posts'] + ); + $smcFunc['db_free_result']($request); + + // Just return all the top posters. + if ($output_method != 'echo') + return $return; + + // Make a quick array to list the links in. + $temp_array = array(); + foreach ($return as $member) + $temp_array[] = $member['link']; + + echo implode(', ', $temp_array); +} + +// Show boards by activity. +function ssi_topBoards($num_top = 10, $output_method = 'echo') +{ + global $context, $settings, $db_prefix, $txt, $scripturl, $user_info, $modSettings, $smcFunc; + + // Find boards with lots of posts. + $request = $smcFunc['db_query']('', ' + SELECT + b.name, b.num_topics, b.num_posts, b.id_board,' . (!$user_info['is_guest'] ? ' 1 AS is_read' : ' + (IFNULL(lb.id_msg, 0) >= b.id_last_msg) AS is_read') . ' + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member}) + WHERE {query_wanna_see_board}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' + AND b.id_board != {int:recycle_board}' : '') . ' + ORDER BY b.num_posts DESC + LIMIT ' . $num_top, + array( + 'current_member' => $user_info['id'], + 'recycle_board' => (int) $modSettings['recycle_board'], + ) + ); + $boards = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $boards[] = array( + 'id' => $row['id_board'], + 'num_posts' => $row['num_posts'], + 'num_topics' => $row['num_topics'], + 'name' => $row['name'], + 'new' => empty($row['is_read']), + 'href' => $scripturl . '?board=' . $row['id_board'] . '.0', + 'link' => '' . $row['name'] . '' + ); + $smcFunc['db_free_result']($request); + + // If we shouldn't output or have nothing to output, just jump out. + if ($output_method != 'echo' || empty($boards)) + return $boards; + + echo ' + + + + + + '; + foreach ($boards as $board) + echo ' + + + + + '; + echo ' +
', $txt['board'], '', $txt['board_topics'], '', $txt['posts'], '
', $board['link'], $board['new'] ? ' ' . $txt['new'] . '' : '', '', comma_format($board['num_topics']), '', comma_format($board['num_posts']), '
'; +} + +// Shows the top topics. +function ssi_topTopics($type = 'replies', $num_topics = 10, $output_method = 'echo') +{ + global $db_prefix, $txt, $scripturl, $user_info, $modSettings, $smcFunc, $context; + + if ($modSettings['totalMessages'] > 100000) + { + // !!! Why don't we use {query(_wanna)_see_board}? + $request = $smcFunc['db_query']('', ' + SELECT id_topic + FROM {db_prefix}topics + WHERE num_' . ($type != 'replies' ? 'views' : 'replies') . ' != 0' . ($modSettings['postmod_active'] ? ' + AND approved = {int:is_approved}' : '') . ' + ORDER BY num_' . ($type != 'replies' ? 'views' : 'replies') . ' DESC + LIMIT {int:limit}', + array( + 'is_approved' => 1, + 'limit' => $num_topics > 100 ? ($num_topics + ($num_topics / 2)) : 100, + ) + ); + $topic_ids = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $topic_ids[] = $row['id_topic']; + $smcFunc['db_free_result']($request); + } + else + $topic_ids = array(); + + $request = $smcFunc['db_query']('', ' + SELECT m.subject, m.id_topic, t.num_views, t.num_replies + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + WHERE {query_wanna_see_board}' . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved}' : '') . (!empty($topic_ids) ? ' + AND t.id_topic IN ({array_int:topic_list})' : '') . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' + AND b.id_board != {int:recycle_enable}' : '') . ' + ORDER BY t.num_' . ($type != 'replies' ? 'views' : 'replies') . ' DESC + LIMIT {int:limit}', + array( + 'topic_list' => $topic_ids, + 'is_approved' => 1, + 'recycle_enable' => $modSettings['recycle_board'], + 'limit' => $num_topics, + ) + ); + $topics = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + censorText($row['subject']); + + $topics[] = array( + 'id' => $row['id_topic'], + 'subject' => $row['subject'], + 'num_replies' => $row['num_replies'], + 'num_views' => $row['num_views'], + 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.0', + 'link' => '' . $row['subject'] . '', + ); + } + $smcFunc['db_free_result']($request); + + if ($output_method != 'echo' || empty($topics)) + return $topics; + + echo ' + + + + + + '; + foreach ($topics as $topic) + echo ' + + + + + '; + echo ' +
', $txt['views'], '', $txt['replies'], '
+ ', $topic['link'], ' + ', comma_format($topic['num_views']), '', comma_format($topic['num_replies']), '
'; +} + +// Shows the top topics, by replies. +function ssi_topTopicsReplies($num_topics = 10, $output_method = 'echo') +{ + return ssi_topTopics('replies', $num_topics, $output_method); +} + +// Shows the top topics, by views. +function ssi_topTopicsViews($num_topics = 10, $output_method = 'echo') +{ + return ssi_topTopics('views', $num_topics, $output_method); +} + +// Show a link to the latest member: Please welcome, Someone, out latest member. +function ssi_latestMember($output_method = 'echo') +{ + global $db_prefix, $txt, $scripturl, $context; + + if ($output_method == 'echo') + echo ' + ', $txt['welcome_member'], ' ', $context['common_stats']['latest_member']['link'], '', $txt['newest_member'], '
'; + else + return $context['common_stats']['latest_member']; +} + +// Fetch a random member - if type set to 'day' will only change once a day! +function ssi_randomMember($random_type = '', $output_method = 'echo') +{ + global $modSettings; + + // If we're looking for something to stay the same each day then seed the generator. + if ($random_type == 'day') + { + // Set the seed to change only once per day. + mt_srand(floor(time() / 86400)); + } + + // Get the lowest ID we're interested in. + $member_id = mt_rand(1, $modSettings['latestMember']); + + $where_query = ' + id_member >= {int:selected_member} + AND is_activated = {int:is_activated}'; + + $query_where_params = array( + 'selected_member' => $member_id, + 'is_activated' => 1, + ); + + $result = ssi_queryMembers($where_query, $query_where_params, 1, 'id_member ASC', $output_method); + + // If we got nothing do the reverse - in case of unactivated members. + if (empty($result)) + { + $where_query = ' + id_member <= {int:selected_member} + AND is_activated = {int:is_activated}'; + + $query_where_params = array( + 'selected_member' => $member_id, + 'is_activated' => 1, + ); + + $result = ssi_queryMembers($where_query, $query_where_params, 1, 'id_member DESC', $output_method); + } + + // Just to be sure put the random generator back to something... random. + if ($random_type != '') + mt_srand(time()); + + return $result; +} + +// Fetch a specific member. +function ssi_fetchMember($member_ids = array(), $output_method = 'echo') +{ + if (empty($member_ids)) + return; + + // Can have more than one member if you really want... + $member_ids = is_array($member_ids) ? $member_ids : array($member_ids); + + // Restrict it right! + $query_where = ' + id_member IN ({array_int:member_list})'; + + $query_where_params = array( + 'member_list' => $member_ids, + ); + + // Then make the query and dump the data. + return ssi_queryMembers($query_where, $query_where_params, '', 'id_member', $output_method); +} + +// Get all members of a group. +function ssi_fetchGroupMembers($group_id = null, $output_method = 'echo') +{ + if ($group_id === null) + return; + + $query_where = ' + id_group = {int:id_group} + OR id_post_group = {int:id_group} + OR FIND_IN_SET({int:id_group}, additional_groups) != 0'; + + $query_where_params = array( + 'id_group' => $group_id, + ); + + return ssi_queryMembers($query_where, $query_where_params, '', 'real_name', $output_method); +} + +// Fetch some member data! +function ssi_queryMembers($query_where = null, $query_where_params = array(), $query_limit = '', $query_order = 'id_member DESC', $output_method = 'echo') +{ + global $context, $settings, $scripturl, $txt, $db_prefix, $user_info; + global $modSettings, $smcFunc, $memberContext; + + if ($query_where === null) + return; + + // Fetch the members in question. + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE ' . $query_where . ' + ORDER BY ' . $query_order . ' + ' . ($query_limit == '' ? '' : 'LIMIT ' . $query_limit), + array_merge($query_where_params, array( + )) + ); + $members = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $members[] = $row['id_member']; + $smcFunc['db_free_result']($request); + + if (empty($members)) + return array(); + + // Load the members. + loadMemberData($members); + + // Draw the table! + if ($output_method == 'echo') + echo ' + '; + + $query_members = array(); + foreach ($members as $member) + { + // Load their context data. + if (!loadMemberContext($member)) + continue; + + // Store this member's information. + $query_members[$member] = $memberContext[$member]; + + // Only do something if we're echo'ing. + if ($output_method == 'echo') + echo ' + + + '; + } + + // End the table if appropriate. + if ($output_method == 'echo') + echo ' +
+ ', $query_members[$member]['link'], ' +
', $query_members[$member]['blurb'], ' +
', $query_members[$member]['avatar']['image'], ' +
'; + + // Send back the data. + return $query_members; +} + +// Show some basic stats: Total This: XXXX, etc. +function ssi_boardStats($output_method = 'echo') +{ + global $db_prefix, $txt, $scripturl, $modSettings, $smcFunc; + + $totals = array( + 'members' => $modSettings['totalMembers'], + 'posts' => $modSettings['totalMessages'], + 'topics' => $modSettings['totalTopics'] + ); + + $result = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}boards', + array( + ) + ); + list ($totals['boards']) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + $result = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}categories', + array( + ) + ); + list ($totals['categories']) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + if ($output_method != 'echo') + return $totals; + + echo ' + ', $txt['total_members'], ': ', comma_format($totals['members']), '
+ ', $txt['total_posts'], ': ', comma_format($totals['posts']), '
+ ', $txt['total_topics'], ': ', comma_format($totals['topics']), '
+ ', $txt['total_cats'], ': ', comma_format($totals['categories']), '
+ ', $txt['total_boards'], ': ', comma_format($totals['boards']); +} + +// Shows a list of online users: YY Guests, ZZ Users and then a list... +function ssi_whosOnline($output_method = 'echo') +{ + global $user_info, $txt, $sourcedir, $settings, $modSettings; + + require_once($sourcedir . '/Subs-MembersOnline.php'); + $membersOnlineOptions = array( + 'show_hidden' => allowedTo('moderate_forum'), + 'sort' => 'log_time', + 'reverse_sort' => true, + ); + $return = getMembersOnlineStats($membersOnlineOptions); + + // Add some redundancy for backwards compatibility reasons. + if ($output_method != 'echo') + return $return + array( + 'users' => $return['users_online'], + 'guests' => $return['num_guests'], + 'hidden' => $return['num_users_hidden'], + 'buddies' => $return['num_buddies'], + 'num_users' => $return['num_users_online'], + 'total_users' => $return['num_users_online'] + $return['num_guests'] + $return['num_spiders'], + ); + + echo ' + ', comma_format($return['num_guests']), ' ', $return['num_guests'] == 1 ? $txt['guest'] : $txt['guests'], ', ', comma_format($return['num_users_online']), ' ', $return['num_users_online'] == 1 ? $txt['user'] : $txt['users']; + + $bracketList = array(); + if (!empty($user_info['buddies'])) + $bracketList[] = comma_format($return['num_buddies']) . ' ' . ($return['num_buddies'] == 1 ? $txt['buddy'] : $txt['buddies']); + if (!empty($return['num_spiders'])) + $bracketList[] = comma_format($return['num_spiders']) . ' ' . ($return['num_spiders'] == 1 ? $txt['spider'] : $txt['spiders']); + if (!empty($return['num_users_hidden'])) + $bracketList[] = comma_format($return['num_users_hidden']) . ' ' . $txt['hidden']; + + if (!empty($bracketList)) + echo ' (' . implode(', ', $bracketList) . ')'; + + echo '
+ ', implode(', ', $return['list_users_online']); + + // Showing membergroups? + if (!empty($settings['show_group_key']) && !empty($return['membergroups'])) + echo '
+ [' . implode(']  [', $return['membergroups']) . ']'; +} + +// Just like whosOnline except it also logs the online presence. +function ssi_logOnline($output_method = 'echo') +{ + writeLog(); + + if ($output_method != 'echo') + return ssi_whosOnline($output_method); + else + ssi_whosOnline($output_method); +} + +// Shows a login box. +function ssi_login($redirect_to = '', $output_method = 'echo') +{ + global $scripturl, $txt, $user_info, $context, $modSettings; + + if ($redirect_to != '') + $_SESSION['login_url'] = $redirect_to; + + if ($output_method != 'echo' || !$user_info['is_guest']) + return $user_info['is_guest']; + + echo ' +
+ + + + + + + + '; + + // Open ID? + if (!empty($modSettings['enableOpenID'])) + echo ' + + + + + '; + + echo ' + + + +
 
 
—', $txt['or'], '—
 
+
'; + +} + +// Show the most-voted-in poll. +function ssi_topPoll($output_method = 'echo') +{ + // Just use recentPoll, no need to duplicate code... + return ssi_recentPoll(true, $output_method); +} + +// Show the most recently posted poll. +function ssi_recentPoll($topPollInstead = false, $output_method = 'echo') +{ + global $db_prefix, $txt, $settings, $boardurl, $user_info, $context, $smcFunc, $modSettings; + + $boardsAllowed = array_intersect(boardsAllowedTo('poll_view'), boardsAllowedTo('poll_vote')); + + if (empty($boardsAllowed)) + return array(); + + $request = $smcFunc['db_query']('', ' + SELECT p.id_poll, p.question, t.id_topic, p.max_votes, p.guest_vote, p.hide_results, p.expire_time + FROM {db_prefix}polls AS p + INNER JOIN {db_prefix}topics AS t ON (t.id_poll = p.id_poll' . ($modSettings['postmod_active'] ? ' AND t.approved = {int:is_approved}' : '') . ') + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)' . ($topPollInstead ? ' + INNER JOIN {db_prefix}poll_choices AS pc ON (pc.id_poll = p.id_poll)' : '') . ' + LEFT JOIN {db_prefix}log_polls AS lp ON (lp.id_poll = p.id_poll AND lp.id_member > {int:no_member} AND lp.id_member = {int:current_member}) + WHERE p.voting_locked = {int:voting_opened} + AND (p.expire_time = {int:no_expiration} OR {int:current_time} < p.expire_time) + AND ' . ($user_info['is_guest'] ? 'p.guest_vote = {int:guest_vote_allowed}' : 'lp.id_choice IS NULL') . ' + AND {query_wanna_see_board}' . (!in_array(0, $boardsAllowed) ? ' + AND b.id_board IN ({array_int:boards_allowed_list})' : '') . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' + AND b.id_board != {int:recycle_enable}' : '') . ' + ORDER BY ' . ($topPollInstead ? 'pc.votes' : 'p.id_poll') . ' DESC + LIMIT 1', + array( + 'current_member' => $user_info['id'], + 'boards_allowed_list' => $boardsAllowed, + 'is_approved' => 1, + 'guest_vote_allowed' => 1, + 'no_member' => 0, + 'voting_opened' => 0, + 'no_expiration' => 0, + 'current_time' => time(), + 'recycle_enable' => $modSettings['recycle_board'], + ) + ); + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // This user has voted on all the polls. + if ($row === false) + return array(); + + // If this is a guest who's voted we'll through ourselves to show poll to show the results. + if ($user_info['is_guest'] && (!$row['guest_vote'] || (isset($_COOKIE['guest_poll_vote']) && in_array($row['id_poll'], explode(',', $_COOKIE['guest_poll_vote']))))) + return ssi_showPoll($row['id_topic'], $output_method); + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(DISTINCT id_member) + FROM {db_prefix}log_polls + WHERE id_poll = {int:current_poll}', + array( + 'current_poll' => $row['id_poll'], + ) + ); + list ($total) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $request = $smcFunc['db_query']('', ' + SELECT id_choice, label, votes + FROM {db_prefix}poll_choices + WHERE id_poll = {int:current_poll}', + array( + 'current_poll' => $row['id_poll'], + ) + ); + $options = array(); + while ($rowChoice = $smcFunc['db_fetch_assoc']($request)) + { + censorText($rowChoice['label']); + + $options[$rowChoice['id_choice']] = array($rowChoice['label'], $rowChoice['votes']); + } + $smcFunc['db_free_result']($request); + + // Can they view it? + $is_expired = !empty($row['expire_time']) && $row['expire_time'] < time(); + $allow_view_results = allowedTo('moderate_board') || $row['hide_results'] == 0 || $is_expired; + + $return = array( + 'id' => $row['id_poll'], + 'image' => 'poll', + 'question' => $row['question'], + 'total_votes' => $total, + 'is_locked' => false, + 'topic' => $row['id_topic'], + 'allow_view_results' => $allow_view_results, + 'options' => array() + ); + + // Calculate the percentages and bar lengths... + $divisor = $return['total_votes'] == 0 ? 1 : $return['total_votes']; + foreach ($options as $i => $option) + { + $bar = floor(($option[1] * 100) / $divisor); + $barWide = $bar == 0 ? 1 : floor(($bar * 5) / 3); + $return['options'][$i] = array( + 'id' => 'options-' . ($topPollInstead ? 'top-' : 'recent-') . $i, + 'percent' => $bar, + 'votes' => $option[1], + 'bar' => '-', + 'option' => parse_bbc($option[0]), + 'vote_button' => '' + ); + } + + $return['allowed_warning'] = $row['max_votes'] > 1 ? sprintf($txt['poll_options6'], min(count($options), $row['max_votes'])) : ''; + + if ($output_method != 'echo') + return $return; + + if ($allow_view_results) + { + echo ' +
+ ', $return['question'], '
+ ', !empty($return['allowed_warning']) ? $return['allowed_warning'] . '
' : ''; + + foreach ($return['options'] as $option) + echo ' +
'; + + echo ' + + + +
'; + } + else + echo $txt['poll_cannot_see']; +} + +function ssi_showPoll($topic = null, $output_method = 'echo') +{ + global $db_prefix, $txt, $settings, $boardurl, $user_info, $context, $smcFunc, $modSettings; + + $boardsAllowed = boardsAllowedTo('poll_view'); + + if (empty($boardsAllowed)) + return array(); + + if ($topic === null && isset($_REQUEST['ssi_topic'])) + $topic = (int) $_REQUEST['ssi_topic']; + else + $topic = (int) $topic; + + $request = $smcFunc['db_query']('', ' + SELECT + p.id_poll, p.question, p.voting_locked, p.hide_results, p.expire_time, p.max_votes, p.guest_vote, b.id_board + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}polls AS p ON (p.id_poll = t.id_poll) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + WHERE t.id_topic = {int:current_topic} + AND {query_see_board}' . (!in_array(0, $boardsAllowed) ? ' + AND b.id_board IN ({array_int:boards_allowed_see})' : '') . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved}' : '') . ' + LIMIT 1', + array( + 'current_topic' => $topic, + 'boards_allowed_see' => $boardsAllowed, + 'is_approved' => 1, + ) + ); + + // Either this topic has no poll, or the user cannot view it. + if ($smcFunc['db_num_rows']($request) == 0) + return array(); + + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Check if they can vote. + if (!empty($row['expire_time']) && $row['expire_time'] < time()) + $allow_vote = false; + elseif ($user_info['is_guest'] && $row['guest_vote'] && (!isset($_COOKIE['guest_poll_vote']) || !in_array($row['id_poll'], explode(',', $_COOKIE['guest_poll_vote'])))) + $allow_vote = true; + elseif ($user_info['is_guest']) + $allow_vote = false; + elseif (!empty($row['voting_locked']) || !allowedTo('poll_vote', $row['id_board'])) + $allow_vote = false; + else + { + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}log_polls + WHERE id_poll = {int:current_poll} + AND id_member = {int:current_member} + LIMIT 1', + array( + 'current_member' => $user_info['id'], + 'current_poll' => $row['id_poll'], + ) + ); + $allow_vote = $smcFunc['db_num_rows']($request) == 0; + $smcFunc['db_free_result']($request); + } + + // Can they view? + $is_expired = !empty($row['expire_time']) && $row['expire_time'] < time(); + $allow_view_results = allowedTo('moderate_board') || $row['hide_results'] == 0 || ($row['hide_results'] == 1 && !$allow_vote) || $is_expired; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(DISTINCT id_member) + FROM {db_prefix}log_polls + WHERE id_poll = {int:current_poll}', + array( + 'current_poll' => $row['id_poll'], + ) + ); + list ($total) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $request = $smcFunc['db_query']('', ' + SELECT id_choice, label, votes + FROM {db_prefix}poll_choices + WHERE id_poll = {int:current_poll}', + array( + 'current_poll' => $row['id_poll'], + ) + ); + $options = array(); + $total_votes = 0; + while ($rowChoice = $smcFunc['db_fetch_assoc']($request)) + { + censorText($rowChoice['label']); + + $options[$rowChoice['id_choice']] = array($rowChoice['label'], $rowChoice['votes']); + $total_votes += $rowChoice['votes']; + } + $smcFunc['db_free_result']($request); + + $return = array( + 'id' => $row['id_poll'], + 'image' => empty($pollinfo['voting_locked']) ? 'poll' : 'locked_poll', + 'question' => $row['question'], + 'total_votes' => $total, + 'is_locked' => !empty($pollinfo['voting_locked']), + 'allow_vote' => $allow_vote, + 'allow_view_results' => $allow_view_results, + 'topic' => $topic + ); + + // Calculate the percentages and bar lengths... + $divisor = $total_votes == 0 ? 1 : $total_votes; + foreach ($options as $i => $option) + { + $bar = floor(($option[1] * 100) / $divisor); + $barWide = $bar == 0 ? 1 : floor(($bar * 5) / 3); + $return['options'][$i] = array( + 'id' => 'options-' . $i, + 'percent' => $bar, + 'votes' => $option[1], + 'bar' => '-', + 'option' => parse_bbc($option[0]), + 'vote_button' => '' + ); + } + + $return['allowed_warning'] = $row['max_votes'] > 1 ? sprintf($txt['poll_options6'], min(count($options), $row['max_votes'])) : ''; + + if ($output_method != 'echo') + return $return; + + if ($return['allow_vote']) + { + echo ' +
+ ', $return['question'], '
+ ', !empty($return['allowed_warning']) ? $return['allowed_warning'] . '
' : ''; + + foreach ($return['options'] as $option) + echo ' +
'; + + echo ' + + + +
'; + } + elseif ($return['allow_view_results']) + { + echo ' +
+ ', $return['question'], ' +
'; + + foreach ($return['options'] as $option) + echo ' +
', $option['option'], '
+
+
+
+
+
+ ', $option['votes'], ' (', $option['percent'], '%) +
'; + echo ' +
+ ', $txt['poll_total_voters'], ': ', $return['total_votes'], ' +
'; + } + // Cannot see it I'm afraid! + else + echo $txt['poll_cannot_see']; +} + +// Takes care of voting - don't worry, this is done automatically. +function ssi_pollVote() +{ + global $context, $db_prefix, $user_info, $sc, $smcFunc, $sourcedir, $modSettings; + + if (!isset($_POST[$context['session_var']]) || $_POST[$context['session_var']] != $sc || empty($_POST['options']) || !isset($_POST['poll'])) + { + echo ' + + + + +« +'; + return; + } + + // This can cause weird errors! (ie. copyright missing.) + checkSession(); + + $_POST['poll'] = (int) $_POST['poll']; + + // Check if they have already voted, or voting is locked. + $request = $smcFunc['db_query']('', ' + SELECT + p.id_poll, p.voting_locked, p.expire_time, p.max_votes, p.guest_vote, + t.id_topic, + IFNULL(lp.id_choice, -1) AS selected + FROM {db_prefix}polls AS p + INNER JOIN {db_prefix}topics AS t ON (t.id_poll = {int:current_poll}) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + LEFT JOIN {db_prefix}log_polls AS lp ON (lp.id_poll = p.id_poll AND lp.id_member = {int:current_member}) + WHERE p.id_poll = {int:current_poll} + AND {query_see_board}' . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved}' : '') . ' + LIMIT 1', + array( + 'current_member' => $user_info['id'], + 'current_poll' => $_POST['poll'], + 'is_approved' => 1, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + die; + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + if (!empty($row['voting_locked']) || ($row['selected'] != -1 && !$user_info['is_guest']) || (!empty($row['expire_time']) && time() > $row['expire_time'])) + redirectexit('topic=' . $row['id_topic'] . '.0'); + + // Too many options checked? + if (count($_REQUEST['options']) > $row['max_votes']) + redirectexit('topic=' . $row['id_topic'] . '.0'); + + // It's a guest who has already voted? + if ($user_info['is_guest']) + { + // Guest voting disabled? + if (!$row['guest_vote']) + redirectexit('topic=' . $row['id_topic'] . '.0'); + // Already voted? + elseif (isset($_COOKIE['guest_poll_vote']) && in_array($row['id_poll'], explode(',', $_COOKIE['guest_poll_vote']))) + redirectexit('topic=' . $row['id_topic'] . '.0'); + } + + $options = array(); + $inserts = array(); + foreach ($_REQUEST['options'] as $id) + { + $id = (int) $id; + + $options[] = $id; + $inserts[] = array($_POST['poll'], $user_info['id'], $id); + } + + // Add their vote in to the tally. + $smcFunc['db_insert']('insert', + $db_prefix . 'log_polls', + array('id_poll' => 'int', 'id_member' => 'int', 'id_choice' => 'int'), + $inserts, + array('id_poll', 'id_member', 'id_choice') + ); + $smcFunc['db_query']('', ' + UPDATE {db_prefix}poll_choices + SET votes = votes + 1 + WHERE id_poll = {int:current_poll} + AND id_choice IN ({array_int:option_list})', + array( + 'option_list' => $options, + 'current_poll' => $_POST['poll'], + ) + ); + + // Track the vote if a guest. + if ($user_info['is_guest']) + { + $_COOKIE['guest_poll_vote'] = !empty($_COOKIE['guest_poll_vote']) ? ($_COOKIE['guest_poll_vote'] . ',' . $row['id_poll']) : $row['id_poll']; + + require_once($sourcedir . '/Subs-Auth.php'); + $cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); + setcookie('guest_poll_vote', $_COOKIE['guest_poll_vote'], time() + 2500000, $cookie_url[1], $cookie_url[0], 0); + } + + redirectexit('topic=' . $row['id_topic'] . '.0'); +} + +// Show a search box. +function ssi_quickSearch($output_method = 'echo') +{ + global $scripturl, $txt, $context; + + if ($output_method != 'echo') + return $scripturl . '?action=search'; + + echo ' +
+ +
'; +} + +// Show what would be the forum news. +function ssi_news($output_method = 'echo') +{ + global $context; + + if ($output_method != 'echo') + return $context['random_news_line']; + + echo $context['random_news_line']; +} + +// Show today's birthdays. +function ssi_todaysBirthdays($output_method = 'echo') +{ + global $scripturl, $modSettings, $user_info; + + if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view') || !allowedTo('profile_view_any')) + return; + + $eventOptions = array( + 'include_birthdays' => true, + 'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'], + ); + $return = cache_quick_get('calendar_index_offset_' . ($user_info['time_offset'] + $modSettings['time_offset']), 'Subs-Calendar.php', 'cache_getRecentEvents', array($eventOptions)); + + if ($output_method != 'echo') + return $return['calendar_birthdays']; + + foreach ($return['calendar_birthdays'] as $member) + echo ' + ' . $member['name'] . (isset($member['age']) ? ' (' . $member['age'] . ')' : '') . '' . (!$member['is_last'] ? ', ' : ''); +} + +// Show today's holidays. +function ssi_todaysHolidays($output_method = 'echo') +{ + global $modSettings, $user_info; + + if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view')) + return; + + $eventOptions = array( + 'include_holidays' => true, + 'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'], + ); + $return = cache_quick_get('calendar_index_offset_' . ($user_info['time_offset'] + $modSettings['time_offset']), 'Subs-Calendar.php', 'cache_getRecentEvents', array($eventOptions)); + + if ($output_method != 'echo') + return $return['calendar_holidays']; + + echo ' + ', implode(', ', $return['calendar_holidays']); +} + +// Show today's events. +function ssi_todaysEvents($output_method = 'echo') +{ + global $modSettings, $user_info; + + if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view')) + return; + + $eventOptions = array( + 'include_events' => true, + 'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'], + ); + $return = cache_quick_get('calendar_index_offset_' . ($user_info['time_offset'] + $modSettings['time_offset']), 'Subs-Calendar.php', 'cache_getRecentEvents', array($eventOptions)); + + if ($output_method != 'echo') + return $return['calendar_events']; + + foreach ($return['calendar_events'] as $event) + { + if ($event['can_edit']) + echo ' + * '; + echo ' + ' . $event['link'] . (!$event['is_last'] ? ', ' : ''); + } +} + +// Show all calendar entires for today. (birthdays, holodays, and events.) +function ssi_todaysCalendar($output_method = 'echo') +{ + global $modSettings, $txt, $scripturl, $user_info; + + $eventOptions = array( + 'include_birthdays' => allowedTo('profile_view_any'), + 'include_holidays' => true, + 'include_events' => true, + 'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'], + ); + $return = cache_quick_get('calendar_index_offset_' . ($user_info['time_offset'] + $modSettings['time_offset']), 'Subs-Calendar.php', 'cache_getRecentEvents', array($eventOptions)); + + if ($output_method != 'echo') + return $return; + + if (!empty($return['calendar_holidays'])) + echo ' + ' . $txt['calendar_prompt'] . ' ' . implode(', ', $return['calendar_holidays']) . '
'; + if (!empty($return['calendar_birthdays'])) + { + echo ' + ' . $txt['birthdays_upcoming'] . ' '; + foreach ($return['calendar_birthdays'] as $member) + echo ' + ', $member['name'], isset($member['age']) ? ' (' . $member['age'] . ')' : '', '', !$member['is_last'] ? ', ' : ''; + echo ' +
'; + } + if (!empty($return['calendar_events'])) + { + echo ' + ' . $txt['events_upcoming'] . ' '; + foreach ($return['calendar_events'] as $event) + { + if ($event['can_edit']) + echo ' + * '; + echo ' + ' . $event['link'] . (!$event['is_last'] ? ', ' : ''); + } + } +} + +// Show the latest news, with a template... by board. +function ssi_boardNews($board = null, $limit = null, $start = null, $length = null, $output_method = 'echo') +{ + global $scripturl, $db_prefix, $txt, $settings, $modSettings, $context; + global $smcFunc; + + loadLanguage('Stats'); + + // Must be integers.... + if ($limit === null) + $limit = isset($_GET['limit']) ? (int) $_GET['limit'] : 5; + else + $limit = (int) $limit; + + if ($start === null) + $start = isset($_GET['start']) ? (int) $_GET['start'] : 0; + else + $start = (int) $start; + + if ($board !== null) + $board = (int) $board; + elseif (isset($_GET['board'])) + $board = (int) $_GET['board']; + + if ($length === null) + $length = isset($_GET['length']) ? (int) $_GET['length'] : 0; + else + $length = (int) $length; + + $limit = max(0, $limit); + $start = max(0, $start); + + // Make sure guests can see this board. + $request = $smcFunc['db_query']('', ' + SELECT id_board + FROM {db_prefix}boards + WHERE ' . ($board === null ? '' : 'id_board = {int:current_board} + AND ') . 'FIND_IN_SET(-1, member_groups) != 0 + LIMIT 1', + array( + 'current_board' => $board, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + { + if ($output_method == 'echo') + die($txt['ssi_no_guests']); + else + return array(); + } + list ($board) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Load the message icons - the usual suspects. + $stable_icons = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'moved', 'recycled', 'wireless'); + $icon_sources = array(); + foreach ($stable_icons as $icon) + $icon_sources[$icon] = 'images_url'; + + // Find the post ids. + $request = $smcFunc['db_query']('', ' + SELECT t.id_first_msg + FROM {db_prefix}topics as t + LEFT JOIN {db_prefix}boards as b ON (b.id_board = t.id_board) + WHERE t.id_board = {int:current_board}' . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved}' : '') . ' + AND {query_see_board} + ORDER BY t.id_first_msg DESC + LIMIT ' . $start . ', ' . $limit, + array( + 'current_board' => $board, + 'is_approved' => 1, + ) + ); + $posts = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $posts[] = $row['id_first_msg']; + $smcFunc['db_free_result']($request); + + if (empty($posts)) + return array(); + + // Find the posts. + $request = $smcFunc['db_query']('', ' + SELECT + m.icon, m.subject, m.body, IFNULL(mem.real_name, m.poster_name) AS poster_name, m.poster_time, + t.num_replies, t.id_topic, m.id_member, m.smileys_enabled, m.id_msg, t.locked, t.id_last_msg + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE t.id_first_msg IN ({array_int:post_list}) + ORDER BY t.id_first_msg DESC + LIMIT ' . count($posts), + array( + 'post_list' => $posts, + ) + ); + $return = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // If we want to limit the length of the post. + if (!empty($length) && $smcFunc['strlen']($row['body']) > $length) + { + $row['body'] = $smcFunc['substr']($row['body'], 0, $length); + + // The first space or line break. (
, etc.) + $cutoff = max(strrpos($row['body'], ' '), strrpos($row['body'], '<')); + + if ($cutoff !== false) + $row['body'] = $smcFunc['substr']($row['body'], 0, $cutoff); + $row['body'] .= '...'; + } + + $row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']); + + // Check that this message icon is there... + if (empty($modSettings['messageIconChecks_disable']) && !isset($icon_sources[$row['icon']])) + $icon_sources[$row['icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $row['icon'] . '.gif') ? 'images_url' : 'default_images_url'; + + censorText($row['subject']); + censorText($row['body']); + + $return[] = array( + 'id' => $row['id_topic'], + 'message_id' => $row['id_msg'], + 'icon' => '' . $row['icon'] . '', + 'subject' => $row['subject'], + 'time' => timeformat($row['poster_time']), + 'timestamp' => forum_time(true, $row['poster_time']), + 'body' => $row['body'], + 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.0', + 'link' => '' . $row['num_replies'] . ' ' . ($row['num_replies'] == 1 ? $txt['ssi_comment'] : $txt['ssi_comments']) . '', + 'replies' => $row['num_replies'], + 'comment_href' => !empty($row['locked']) ? '' : $scripturl . '?action=post;topic=' . $row['id_topic'] . '.' . $row['num_replies'] . ';last_msg=' . $row['id_last_msg'], + 'comment_link' => !empty($row['locked']) ? '' : '' . $txt['ssi_write_comment'] . '', + 'new_comment' => !empty($row['locked']) ? '' : '' . $txt['ssi_write_comment'] . '', + 'poster' => array( + 'id' => $row['id_member'], + 'name' => $row['poster_name'], + 'href' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : '', + 'link' => !empty($row['id_member']) ? '' . $row['poster_name'] . '' : $row['poster_name'] + ), + 'locked' => !empty($row['locked']), + 'is_last' => false + ); + } + $smcFunc['db_free_result']($request); + + if (empty($return)) + return $return; + + $return[count($return) - 1]['is_last'] = true; + + if ($output_method != 'echo') + return $return; + + foreach ($return as $news) + { + echo ' +
+

+ ', $news['icon'], ' + ', $news['subject'], ' +

+
', $news['time'], ' ', $txt['by'], ' ', $news['poster']['link'], '
+
', $news['body'], '
+ ', $news['link'], $news['locked'] ? '' : ' | ' . $news['comment_link'], ' +
'; + + if (!$news['is_last']) + echo ' +
'; + } +} + +// Show the most recent events. +function ssi_recentEvents($max_events = 7, $output_method = 'echo') +{ + global $db_prefix, $user_info, $scripturl, $modSettings, $txt, $context, $smcFunc; + + if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view')) + return; + + // Find all events which are happening in the near future that the member can see. + $request = $smcFunc['db_query']('', ' + SELECT + cal.id_event, cal.start_date, cal.end_date, cal.title, cal.id_member, cal.id_topic, + cal.id_board, t.id_first_msg, t.approved + FROM {db_prefix}calendar AS cal + LEFT JOIN {db_prefix}boards AS b ON (b.id_board = cal.id_board) + LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = cal.id_topic) + WHERE cal.start_date <= {date:current_date} + AND cal.end_date >= {date:current_date} + AND (cal.id_board = {int:no_board} OR {query_wanna_see_board}) + ORDER BY cal.start_date DESC + LIMIT ' . $max_events, + array( + 'current_date' => strftime('%Y-%m-%d', forum_time(false)), + 'no_board' => 0, + ) + ); + $return = array(); + $duplicates = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Check if we've already come by an event linked to this same topic with the same title... and don't display it if we have. + if (!empty($duplicates[$row['title'] . $row['id_topic']])) + continue; + + // Censor the title. + censorText($row['title']); + + if ($row['start_date'] < strftime('%Y-%m-%d', forum_time(false))) + $date = strftime('%Y-%m-%d', forum_time(false)); + else + $date = $row['start_date']; + + // If the topic it is attached to is not approved then don't link it. + if (!empty($row['id_first_msg']) && !$row['approved']) + $row['id_board'] = $row['id_topic'] = $row['id_first_msg'] = 0; + + $return[$date][] = array( + 'id' => $row['id_event'], + 'title' => $row['title'], + 'can_edit' => allowedTo('calendar_edit_any') || ($row['id_member'] == $user_info['id'] && allowedTo('calendar_edit_own')), + 'modify_href' => $scripturl . '?action=' . ($row['id_board'] == 0 ? 'calendar;sa=post;' : 'post;msg=' . $row['id_first_msg'] . ';topic=' . $row['id_topic'] . '.0;calendar;') . 'eventid=' . $row['id_event'] . ';' . $context['session_var'] . '=' . $context['session_id'], + 'href' => $row['id_board'] == 0 ? '' : $scripturl . '?topic=' . $row['id_topic'] . '.0', + 'link' => $row['id_board'] == 0 ? $row['title'] : '' . $row['title'] . '', + 'start_date' => $row['start_date'], + 'end_date' => $row['end_date'], + 'is_last' => false + ); + + // Let's not show this one again, huh? + $duplicates[$row['title'] . $row['id_topic']] = true; + } + $smcFunc['db_free_result']($request); + + foreach ($return as $mday => $array) + $return[$mday][count($array) - 1]['is_last'] = true; + + if ($output_method != 'echo' || empty($return)) + return $return; + + // Well the output method is echo. + echo ' + ' . $txt['events'] . ' '; + foreach ($return as $mday => $array) + foreach ($array as $event) + { + if ($event['can_edit']) + echo ' + * '; + + echo ' + ' . $event['link'] . (!$event['is_last'] ? ', ' : ''); + } +} + +// Check the passed id_member/password. If $is_username is true, treats $id as a username. +function ssi_checkPassword($id = null, $password = null, $is_username = false) +{ + global $db_prefix, $sourcedir, $smcFunc; + + // If $id is null, this was most likely called from a query string and should do nothing. + if ($id === null) + return; + + $request = $smcFunc['db_query']('', ' + SELECT passwd, member_name, is_activated + FROM {db_prefix}members + WHERE ' . ($is_username ? 'member_name' : 'id_member') . ' = {string:id} + LIMIT 1', + array( + 'id' => $id, + ) + ); + list ($pass, $user, $active) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return sha1(strtolower($user) . $password) == $pass && $active == 1; +} + +// We want to show the recent attachments outside of the forum. +function ssi_recentAttachments($num_attachments = 10, $attachment_ext = array(), $output_method = 'echo') +{ + global $smcFunc, $context, $modSettings, $scripturl, $txt, $settings; + + // We want to make sure that we only get attachments for boards that we can see *if* any. + $attachments_boards = boardsAllowedTo('view_attachments'); + + // No boards? Adios amigo. + if (empty($attachments_boards)) + return array(); + + // Is it an array? + if (!is_array($attachment_ext)) + $attachment_ext = array($attachment_ext); + + // Lets build the query. + $request = $smcFunc['db_query']('', ' + SELECT + att.id_attach, att.id_msg, att.filename, IFNULL(att.size, 0) AS filesize, att.downloads, mem.id_member, + IFNULL(mem.real_name, m.poster_name) AS poster_name, m.id_topic, m.subject, t.id_board, m.poster_time, + att.width, att.height' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ', IFNULL(thumb.id_attach, 0) AS id_thumb, thumb.width AS thumb_width, thumb.height AS thumb_height') . ' + FROM {db_prefix}attachments AS att + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = att.id_msg) + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ' + LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = att.id_thumb)') . ' + WHERE att.attachment_type = 0' . ($attachments_boards === array(0) ? '' : ' + AND m.id_board IN ({array_int:boards_can_see})') . (!empty($attachment_ext) ? ' + AND att.fileext IN ({array_string:attachment_ext})' : '') . + (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : ' + AND t.approved = {int:is_approved} + AND m.approved = {int:is_approved} + AND att.approved = {int:is_approved}') . ' + ORDER BY att.id_attach DESC + LIMIT {int:num_attachments}', + array( + 'boards_can_see' => $attachments_boards, + 'attachment_ext' => $attachment_ext, + 'num_attachments' => $num_attachments, + 'is_approved' => 1, + ) + ); + + // We have something. + $attachments = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $filename = preg_replace('~&#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', htmlspecialchars($row['filename'])); + + // Is it an image? + $attachments[$row['id_attach']] = array( + 'member' => array( + 'id' => $row['id_member'], + 'name' => $row['poster_name'], + 'link' => empty($row['id_member']) ? $row['poster_name'] : '' . $row['poster_name'] . '', + ), + 'file' => array( + 'filename' => $filename, + 'filesize' => round($row['filesize'] /1024, 2) . $txt['kilobyte'], + 'downloads' => $row['downloads'], + 'href' => $scripturl . '?action=dlattach;topic=' . $row['id_topic'] . '.0;attach=' . $row['id_attach'], + 'link' => ' ' . $filename . '', + 'is_image' => !empty($row['width']) && !empty($row['height']) && !empty($modSettings['attachmentShowImages']), + ), + 'topic' => array( + 'id' => $row['id_topic'], + 'subject' => $row['subject'], + 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'], + 'link' => '' . $row['subject'] . '', + 'time' => timeformat($row['poster_time']), + ), + ); + + // Images. + if ($attachments[$row['id_attach']]['file']['is_image']) + { + $id_thumb = empty($row['id_thumb']) ? $row['id_attach'] : $row['id_thumb']; + $attachments[$row['id_attach']]['file']['image'] = array( + 'id' => $id_thumb, + 'width' => $row['width'], + 'height' => $row['height'], + 'img' => '' . $filename . '', + 'thumb' => '' . $filename . '', + 'href' => $scripturl . '?action=dlattach;topic=' . $row['id_topic'] . '.0;attach=' . $id_thumb . ';image', + 'link' => '' . $filename . '', + ); + } + } + $smcFunc['db_free_result']($request); + + // So you just want an array? Here you can have it. + if ($output_method == 'array' || empty($attachments)) + return $attachments; + + // Give them the default. + echo ' + + + + + + + '; + foreach ($attachments as $attach) + echo ' + + + + + + '; + echo ' +
', $txt['file'], '', $txt['posted_by'], '', $txt['downloads'], '', $txt['filesize'], '
', $attach['file']['link'], '', $attach['member']['link'], '', $attach['file']['downloads'], '', $attach['file']['filesize'], '
'; +} + +?> \ No newline at end of file diff --git a/Settings.orig.php b/Settings.orig.php new file mode 100644 index 0000000..7a8ae98 --- /dev/null +++ b/Settings.orig.php @@ -0,0 +1,62 @@ + \ No newline at end of file diff --git a/Smileys/aaron/afro.gif b/Smileys/aaron/afro.gif new file mode 100644 index 0000000..84ba28b Binary files /dev/null and b/Smileys/aaron/afro.gif differ diff --git a/Smileys/aaron/angel.gif b/Smileys/aaron/angel.gif new file mode 100644 index 0000000..0c495be Binary files /dev/null and b/Smileys/aaron/angel.gif differ diff --git a/Smileys/aaron/angry.gif b/Smileys/aaron/angry.gif new file mode 100644 index 0000000..2138e9d Binary files /dev/null and b/Smileys/aaron/angry.gif differ diff --git a/Smileys/aaron/azn.gif b/Smileys/aaron/azn.gif new file mode 100644 index 0000000..2f83adb Binary files /dev/null and b/Smileys/aaron/azn.gif differ diff --git a/Smileys/aaron/blank.gif b/Smileys/aaron/blank.gif new file mode 100644 index 0000000..1ad3691 Binary files /dev/null and b/Smileys/aaron/blank.gif differ diff --git a/Smileys/aaron/cheesy.gif b/Smileys/aaron/cheesy.gif new file mode 100644 index 0000000..5ecba95 Binary files /dev/null and b/Smileys/aaron/cheesy.gif differ diff --git a/Smileys/aaron/cool.gif b/Smileys/aaron/cool.gif new file mode 100644 index 0000000..92bd460 Binary files /dev/null and b/Smileys/aaron/cool.gif differ diff --git a/Smileys/aaron/cry.gif b/Smileys/aaron/cry.gif new file mode 100644 index 0000000..826c95d Binary files /dev/null and b/Smileys/aaron/cry.gif differ diff --git a/Smileys/aaron/embarrassed.gif b/Smileys/aaron/embarrassed.gif new file mode 100644 index 0000000..cb43e35 Binary files /dev/null and b/Smileys/aaron/embarrassed.gif differ diff --git a/Smileys/aaron/evil.gif b/Smileys/aaron/evil.gif new file mode 100644 index 0000000..e4dc3c8 Binary files /dev/null and b/Smileys/aaron/evil.gif differ diff --git a/Smileys/aaron/grin.gif b/Smileys/aaron/grin.gif new file mode 100644 index 0000000..06f35e2 Binary files /dev/null and b/Smileys/aaron/grin.gif differ diff --git a/Smileys/aaron/huh.gif b/Smileys/aaron/huh.gif new file mode 100644 index 0000000..b676aac Binary files /dev/null and b/Smileys/aaron/huh.gif differ diff --git a/Smileys/aaron/index.php b/Smileys/aaron/index.php new file mode 100644 index 0000000..be9895a --- /dev/null +++ b/Smileys/aaron/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Smileys/aaron/kiss.gif b/Smileys/aaron/kiss.gif new file mode 100644 index 0000000..1019a2b Binary files /dev/null and b/Smileys/aaron/kiss.gif differ diff --git a/Smileys/aaron/laugh.gif b/Smileys/aaron/laugh.gif new file mode 100644 index 0000000..83fb7e8 Binary files /dev/null and b/Smileys/aaron/laugh.gif differ diff --git a/Smileys/aaron/lipsrsealed.gif b/Smileys/aaron/lipsrsealed.gif new file mode 100644 index 0000000..c6702a5 Binary files /dev/null and b/Smileys/aaron/lipsrsealed.gif differ diff --git a/Smileys/aaron/police.gif b/Smileys/aaron/police.gif new file mode 100644 index 0000000..414d65f Binary files /dev/null and b/Smileys/aaron/police.gif differ diff --git a/Smileys/aaron/rolleyes.gif b/Smileys/aaron/rolleyes.gif new file mode 100644 index 0000000..889d8dc Binary files /dev/null and b/Smileys/aaron/rolleyes.gif differ diff --git a/Smileys/aaron/sad.gif b/Smileys/aaron/sad.gif new file mode 100644 index 0000000..1827949 Binary files /dev/null and b/Smileys/aaron/sad.gif differ diff --git a/Smileys/aaron/shocked.gif b/Smileys/aaron/shocked.gif new file mode 100644 index 0000000..fb1eb22 Binary files /dev/null and b/Smileys/aaron/shocked.gif differ diff --git a/Smileys/aaron/smiley.gif b/Smileys/aaron/smiley.gif new file mode 100644 index 0000000..092648c Binary files /dev/null and b/Smileys/aaron/smiley.gif differ diff --git a/Smileys/aaron/tongue.gif b/Smileys/aaron/tongue.gif new file mode 100644 index 0000000..f09b122 Binary files /dev/null and b/Smileys/aaron/tongue.gif differ diff --git a/Smileys/aaron/undecided.gif b/Smileys/aaron/undecided.gif new file mode 100644 index 0000000..60393a5 Binary files /dev/null and b/Smileys/aaron/undecided.gif differ diff --git a/Smileys/aaron/wink.gif b/Smileys/aaron/wink.gif new file mode 100644 index 0000000..bec9cde Binary files /dev/null and b/Smileys/aaron/wink.gif differ diff --git a/Smileys/akyhne/afro.gif b/Smileys/akyhne/afro.gif new file mode 100644 index 0000000..7d8f3ff Binary files /dev/null and b/Smileys/akyhne/afro.gif differ diff --git a/Smileys/akyhne/angel.gif b/Smileys/akyhne/angel.gif new file mode 100644 index 0000000..971720d Binary files /dev/null and b/Smileys/akyhne/angel.gif differ diff --git a/Smileys/akyhne/angry.gif b/Smileys/akyhne/angry.gif new file mode 100644 index 0000000..79a9fdc Binary files /dev/null and b/Smileys/akyhne/angry.gif differ diff --git a/Smileys/akyhne/azn.gif b/Smileys/akyhne/azn.gif new file mode 100644 index 0000000..c7c62ca Binary files /dev/null and b/Smileys/akyhne/azn.gif differ diff --git a/Smileys/akyhne/blank.gif b/Smileys/akyhne/blank.gif new file mode 100644 index 0000000..729789c Binary files /dev/null and b/Smileys/akyhne/blank.gif differ diff --git a/Smileys/akyhne/cheesy.gif b/Smileys/akyhne/cheesy.gif new file mode 100644 index 0000000..6161912 Binary files /dev/null and b/Smileys/akyhne/cheesy.gif differ diff --git a/Smileys/akyhne/cool.gif b/Smileys/akyhne/cool.gif new file mode 100644 index 0000000..fa9bab4 Binary files /dev/null and b/Smileys/akyhne/cool.gif differ diff --git a/Smileys/akyhne/cry.gif b/Smileys/akyhne/cry.gif new file mode 100644 index 0000000..7265061 Binary files /dev/null and b/Smileys/akyhne/cry.gif differ diff --git a/Smileys/akyhne/embarrassed.gif b/Smileys/akyhne/embarrassed.gif new file mode 100644 index 0000000..9c2f387 Binary files /dev/null and b/Smileys/akyhne/embarrassed.gif differ diff --git a/Smileys/akyhne/evil.gif b/Smileys/akyhne/evil.gif new file mode 100644 index 0000000..e6ee98f Binary files /dev/null and b/Smileys/akyhne/evil.gif differ diff --git a/Smileys/akyhne/grin.gif b/Smileys/akyhne/grin.gif new file mode 100644 index 0000000..4226095 Binary files /dev/null and b/Smileys/akyhne/grin.gif differ diff --git a/Smileys/akyhne/huh.gif b/Smileys/akyhne/huh.gif new file mode 100644 index 0000000..946661e Binary files /dev/null and b/Smileys/akyhne/huh.gif differ diff --git a/Smileys/akyhne/index.php b/Smileys/akyhne/index.php new file mode 100644 index 0000000..be9895a --- /dev/null +++ b/Smileys/akyhne/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Smileys/akyhne/kiss.gif b/Smileys/akyhne/kiss.gif new file mode 100644 index 0000000..fb5952e Binary files /dev/null and b/Smileys/akyhne/kiss.gif differ diff --git a/Smileys/akyhne/laugh.gif b/Smileys/akyhne/laugh.gif new file mode 100644 index 0000000..8615e6f Binary files /dev/null and b/Smileys/akyhne/laugh.gif differ diff --git a/Smileys/akyhne/lipsrsealed.gif b/Smileys/akyhne/lipsrsealed.gif new file mode 100644 index 0000000..b9d75ae Binary files /dev/null and b/Smileys/akyhne/lipsrsealed.gif differ diff --git a/Smileys/akyhne/police.gif b/Smileys/akyhne/police.gif new file mode 100644 index 0000000..2cfbd4f Binary files /dev/null and b/Smileys/akyhne/police.gif differ diff --git a/Smileys/akyhne/rolleyes.gif b/Smileys/akyhne/rolleyes.gif new file mode 100644 index 0000000..bb7e58b Binary files /dev/null and b/Smileys/akyhne/rolleyes.gif differ diff --git a/Smileys/akyhne/sad.gif b/Smileys/akyhne/sad.gif new file mode 100644 index 0000000..6b977f1 Binary files /dev/null and b/Smileys/akyhne/sad.gif differ diff --git a/Smileys/akyhne/shocked.gif b/Smileys/akyhne/shocked.gif new file mode 100644 index 0000000..2fa4db3 Binary files /dev/null and b/Smileys/akyhne/shocked.gif differ diff --git a/Smileys/akyhne/smiley.gif b/Smileys/akyhne/smiley.gif new file mode 100644 index 0000000..ea5003d Binary files /dev/null and b/Smileys/akyhne/smiley.gif differ diff --git a/Smileys/akyhne/tongue.gif b/Smileys/akyhne/tongue.gif new file mode 100644 index 0000000..68cbfe1 Binary files /dev/null and b/Smileys/akyhne/tongue.gif differ diff --git a/Smileys/akyhne/undecided.gif b/Smileys/akyhne/undecided.gif new file mode 100644 index 0000000..74b62b4 Binary files /dev/null and b/Smileys/akyhne/undecided.gif differ diff --git a/Smileys/akyhne/wink.gif b/Smileys/akyhne/wink.gif new file mode 100644 index 0000000..e06f497 Binary files /dev/null and b/Smileys/akyhne/wink.gif differ diff --git a/Smileys/default/afro.gif b/Smileys/default/afro.gif new file mode 100644 index 0000000..9da7a82 Binary files /dev/null and b/Smileys/default/afro.gif differ diff --git a/Smileys/default/angel.gif b/Smileys/default/angel.gif new file mode 100644 index 0000000..1edbd78 Binary files /dev/null and b/Smileys/default/angel.gif differ diff --git a/Smileys/default/angry.gif b/Smileys/default/angry.gif new file mode 100644 index 0000000..0981315 Binary files /dev/null and b/Smileys/default/angry.gif differ diff --git a/Smileys/default/azn.gif b/Smileys/default/azn.gif new file mode 100644 index 0000000..05470c1 Binary files /dev/null and b/Smileys/default/azn.gif differ diff --git a/Smileys/default/blank.gif b/Smileys/default/blank.gif new file mode 100644 index 0000000..c2ac623 Binary files /dev/null and b/Smileys/default/blank.gif differ diff --git a/Smileys/default/cheesy.gif b/Smileys/default/cheesy.gif new file mode 100644 index 0000000..ea7a12f Binary files /dev/null and b/Smileys/default/cheesy.gif differ diff --git a/Smileys/default/cool.gif b/Smileys/default/cool.gif new file mode 100644 index 0000000..f221dd2 Binary files /dev/null and b/Smileys/default/cool.gif differ diff --git a/Smileys/default/cry.gif b/Smileys/default/cry.gif new file mode 100644 index 0000000..3464ce9 Binary files /dev/null and b/Smileys/default/cry.gif differ diff --git a/Smileys/default/embarrassed.gif b/Smileys/default/embarrassed.gif new file mode 100644 index 0000000..811527e Binary files /dev/null and b/Smileys/default/embarrassed.gif differ diff --git a/Smileys/default/evil.gif b/Smileys/default/evil.gif new file mode 100644 index 0000000..185bce6 Binary files /dev/null and b/Smileys/default/evil.gif differ diff --git a/Smileys/default/grin.gif b/Smileys/default/grin.gif new file mode 100644 index 0000000..c6b75e5 Binary files /dev/null and b/Smileys/default/grin.gif differ diff --git a/Smileys/default/huh.gif b/Smileys/default/huh.gif new file mode 100644 index 0000000..4f70748 Binary files /dev/null and b/Smileys/default/huh.gif differ diff --git a/Smileys/default/index.php b/Smileys/default/index.php new file mode 100644 index 0000000..be9895a --- /dev/null +++ b/Smileys/default/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Smileys/default/kiss.gif b/Smileys/default/kiss.gif new file mode 100644 index 0000000..4d76f83 Binary files /dev/null and b/Smileys/default/kiss.gif differ diff --git a/Smileys/default/laugh.gif b/Smileys/default/laugh.gif new file mode 100644 index 0000000..2f0456e Binary files /dev/null and b/Smileys/default/laugh.gif differ diff --git a/Smileys/default/lipsrsealed.gif b/Smileys/default/lipsrsealed.gif new file mode 100644 index 0000000..9b4874e Binary files /dev/null and b/Smileys/default/lipsrsealed.gif differ diff --git a/Smileys/default/police.gif b/Smileys/default/police.gif new file mode 100644 index 0000000..7f86f99 Binary files /dev/null and b/Smileys/default/police.gif differ diff --git a/Smileys/default/rolleyes.gif b/Smileys/default/rolleyes.gif new file mode 100644 index 0000000..c49dee0 Binary files /dev/null and b/Smileys/default/rolleyes.gif differ diff --git a/Smileys/default/sad.gif b/Smileys/default/sad.gif new file mode 100644 index 0000000..b757809 Binary files /dev/null and b/Smileys/default/sad.gif differ diff --git a/Smileys/default/shocked.gif b/Smileys/default/shocked.gif new file mode 100644 index 0000000..6ee3a5e Binary files /dev/null and b/Smileys/default/shocked.gif differ diff --git a/Smileys/default/smiley.gif b/Smileys/default/smiley.gif new file mode 100644 index 0000000..9165734 Binary files /dev/null and b/Smileys/default/smiley.gif differ diff --git a/Smileys/default/tongue.gif b/Smileys/default/tongue.gif new file mode 100644 index 0000000..626dd8e Binary files /dev/null and b/Smileys/default/tongue.gif differ diff --git a/Smileys/default/undecided.gif b/Smileys/default/undecided.gif new file mode 100644 index 0000000..97784a3 Binary files /dev/null and b/Smileys/default/undecided.gif differ diff --git a/Smileys/default/wink.gif b/Smileys/default/wink.gif new file mode 100644 index 0000000..251b079 Binary files /dev/null and b/Smileys/default/wink.gif differ diff --git a/Smileys/index.php b/Smileys/index.php new file mode 100644 index 0000000..b7a7b9f --- /dev/null +++ b/Smileys/index.php @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/Sources/Admin.php b/Sources/Admin.php new file mode 100644 index 0000000..2f07964 --- /dev/null +++ b/Sources/Admin.php @@ -0,0 +1,999 @@ + array( + 'title' => $txt['admin_main'], + 'permission' => array('admin_forum', 'manage_permissions', 'moderate_forum', 'manage_membergroups', 'manage_bans', 'send_mail', 'edit_news', 'manage_boards', 'manage_smileys', 'manage_attachments'), + 'areas' => array( + 'index' => array( + 'label' => $txt['admin_center'], + 'function' => 'AdminHome', + 'icon' => 'administration.gif', + ), + 'credits' => array( + 'label' => $txt['support_credits_title'], + 'function' => 'AdminHome', + 'icon' => 'support.gif', + ), + 'news' => array( + 'label' => $txt['news_title'], + 'file' => 'ManageNews.php', + 'function' => 'ManageNews', + 'icon' => 'news.gif', + 'permission' => array('edit_news', 'send_mail', 'admin_forum'), + 'subsections' => array( + 'editnews' => array($txt['admin_edit_news'], 'edit_news'), + 'mailingmembers' => array($txt['admin_newsletters'], 'send_mail'), + 'settings' => array($txt['settings'], 'admin_forum'), + ), + ), + 'packages' => array( + 'label' => $txt['package'], + 'file' => 'Packages.php', + 'function' => 'Packages', + 'permission' => array('admin_forum'), + 'icon' => 'packages.gif', + 'subsections' => array( + 'browse' => array($txt['browse_packages']), + 'packageget' => array($txt['download_packages'], 'url' => $scripturl . '?action=admin;area=packages;sa=packageget;get'), + 'installed' => array($txt['installed_packages']), + 'perms' => array($txt['package_file_perms']), + 'options' => array($txt['package_settings']), + ), + ), + 'search' => array( + 'function' => 'AdminSearch', + 'permission' => array('admin_forum'), + 'select' => 'index' + ), + ), + ), + 'config' => array( + 'title' => $txt['admin_config'], + 'permission' => array('admin_forum'), + 'areas' => array( + 'corefeatures' => array( + 'label' => $txt['core_settings_title'], + 'file' => 'ManageSettings.php', + 'function' => 'ModifyCoreFeatures', + 'icon' => 'corefeatures.gif', + ), + 'featuresettings' => array( + 'label' => $txt['modSettings_title'], + 'file' => 'ManageSettings.php', + 'function' => 'ModifyFeatureSettings', + 'icon' => 'features.gif', + 'subsections' => array( + 'basic' => array($txt['mods_cat_features']), + 'layout' => array($txt['mods_cat_layout']), + 'karma' => array($txt['karma'], 'enabled' => in_array('k', $context['admin_features'])), + 'sig' => array($txt['signature_settings_short']), + 'profile' => array($txt['custom_profile_shorttitle'], 'enabled' => in_array('cp', $context['admin_features'])), + ), + ), + 'securitysettings' => array( + 'label' => $txt['admin_security_moderation'], + 'file' => 'ManageSettings.php', + 'function' => 'ModifySecuritySettings', + 'icon' => 'security.gif', + 'subsections' => array( + 'general' => array($txt['mods_cat_security_general']), + 'spam' => array($txt['antispam_title']), + 'moderation' => array($txt['moderation_settings_short'], 'enabled' => substr($modSettings['warning_settings'], 0, 1) == 1), + ), + ), + 'languages' => array( + 'label' => $txt['language_configuration'], + 'file' => 'ManageServer.php', + 'function' => 'ManageLanguages', + 'icon' => 'languages.gif', + 'subsections' => array( + 'edit' => array($txt['language_edit']), + 'add' => array($txt['language_add']), + 'settings' => array($txt['language_settings']), + ), + ), + 'serversettings' => array( + 'label' => $txt['admin_server_settings'], + 'file' => 'ManageServer.php', + 'function' => 'ModifySettings', + 'icon' => 'server.gif', + 'subsections' => array( + 'general' => array($txt['general_settings']), + 'database' => array($txt['database_paths_settings']), + 'cookie' => array($txt['cookies_sessions_settings']), + 'cache' => array($txt['caching_settings']), + 'loads' => array($txt['load_balancing_settings']), + ), + ), + 'current_theme' => array( + 'label' => $txt['theme_current_settings'], + 'file' => 'Themes.php', + 'function' => 'ThemesMain', + 'custom_url' => $scripturl . '?action=admin;area=theme;sa=settings;th=' . $settings['theme_id'], + 'icon' => 'current_theme.gif', + ), + 'theme' => array( + 'label' => $txt['theme_admin'], + 'file' => 'Themes.php', + 'function' => 'ThemesMain', + 'custom_url' => $scripturl . '?action=admin;area=theme;sa=admin', + 'icon' => 'themes.gif', + 'subsections' => array( + 'admin' => array($txt['themeadmin_admin_title']), + 'list' => array($txt['themeadmin_list_title']), + 'reset' => array($txt['themeadmin_reset_title']), + 'edit' => array($txt['themeadmin_edit_title']), + ), + ), + 'modsettings' => array( + 'label' => $txt['admin_modifications'], + 'file' => 'ManageSettings.php', + 'function' => 'ModifyModSettings', + 'icon' => 'modifications.gif', + 'subsections' => array( + 'general' => array($txt['mods_cat_modifications_misc']), + // Mod Authors for a "ADD AFTER" on this line. Ensure you end your change with a comma. For example: + // 'shout' => array($txt['shout']), + // Note the comma!! The setting with automatically appear with the first mod to be added. + ), + ), + ), + ), + 'layout' => array( + 'title' => $txt['layout_controls'], + 'permission' => array('manage_boards', 'admin_forum', 'manage_smileys', 'manage_attachments', 'moderate_forum'), + 'areas' => array( + 'manageboards' => array( + 'label' => $txt['admin_boards'], + 'file' => 'ManageBoards.php', + 'function' => 'ManageBoards', + 'icon' => 'boards.gif', + 'permission' => array('manage_boards'), + 'subsections' => array( + 'main' => array($txt['boardsEdit']), + 'newcat' => array($txt['mboards_new_cat']), + 'settings' => array($txt['settings'], 'admin_forum'), + ), + ), + 'postsettings' => array( + 'label' => $txt['manageposts'], + 'file' => 'ManagePosts.php', + 'function' => 'ManagePostSettings', + 'permission' => array('admin_forum'), + 'icon' => 'posts.gif', + 'subsections' => array( + 'posts' => array($txt['manageposts_settings']), + 'bbc' => array($txt['manageposts_bbc_settings']), + 'censor' => array($txt['admin_censored_words']), + 'topics' => array($txt['manageposts_topic_settings']), + ), + ), + 'managecalendar' => array( + 'label' => $txt['manage_calendar'], + 'file' => 'ManageCalendar.php', + 'function' => 'ManageCalendar', + 'icon' => 'calendar.gif', + 'permission' => array('admin_forum'), + 'enabled' => in_array('cd', $context['admin_features']), + 'subsections' => array( + 'holidays' => array($txt['manage_holidays'], 'admin_forum', 'enabled' => !empty($modSettings['cal_enabled'])), + 'settings' => array($txt['calendar_settings'], 'admin_forum'), + ), + ), + 'managesearch' => array( + 'label' => $txt['manage_search'], + 'file' => 'ManageSearch.php', + 'function' => 'ManageSearch', + 'icon' => 'search.gif', + 'permission' => array('admin_forum'), + 'subsections' => array( + 'weights' => array($txt['search_weights']), + 'method' => array($txt['search_method']), + 'settings' => array($txt['settings']), + ), + ), + 'smileys' => array( + 'label' => $txt['smileys_manage'], + 'file' => 'ManageSmileys.php', + 'function' => 'ManageSmileys', + 'icon' => 'smiley.gif', + 'permission' => array('manage_smileys'), + 'subsections' => array( + 'editsets' => array($txt['smiley_sets']), + 'addsmiley' => array($txt['smileys_add'], 'enabled' => !empty($modSettings['smiley_enable'])), + 'editsmileys' => array($txt['smileys_edit'], 'enabled' => !empty($modSettings['smiley_enable'])), + 'setorder' => array($txt['smileys_set_order'], 'enabled' => !empty($modSettings['smiley_enable'])), + 'editicons' => array($txt['icons_edit_message_icons'], 'enabled' => !empty($modSettings['messageIcons_enable'])), + 'settings' => array($txt['settings']), + ), + ), + 'manageattachments' => array( + 'label' => $txt['attachments_avatars'], + 'file' => 'ManageAttachments.php', + 'function' => 'ManageAttachments', + 'icon' => 'attachment.gif', + 'permission' => array('manage_attachments'), + 'subsections' => array( + 'browse' => array($txt['attachment_manager_browse']), + 'attachments' => array($txt['attachment_manager_settings']), + 'avatars' => array($txt['attachment_manager_avatar_settings']), + 'maintenance' => array($txt['attachment_manager_maintenance']), + ), + ), + ), + ), + 'members' => array( + 'title' => $txt['admin_manage_members'], + 'permission' => array('moderate_forum', 'manage_membergroups', 'manage_bans', 'manage_permissions', 'admin_forum'), + 'areas' => array( + 'viewmembers' => array( + 'label' => $txt['admin_users'], + 'file' => 'ManageMembers.php', + 'function' => 'ViewMembers', + 'icon' => 'members.gif', + 'permission' => array('moderate_forum'), + 'subsections' => array( + 'all' => array($txt['view_all_members']), + 'search' => array($txt['mlist_search']), + ), + ), + 'membergroups' => array( + 'label' => $txt['admin_groups'], + 'file' => 'ManageMembergroups.php', + 'function' => 'ModifyMembergroups', + 'icon' => 'membergroups.gif', + 'permission' => array('manage_membergroups'), + 'subsections' => array( + 'index' => array($txt['membergroups_edit_groups'], 'manage_membergroups'), + 'add' => array($txt['membergroups_new_group'], 'manage_membergroups'), + 'settings' => array($txt['settings'], 'admin_forum'), + ), + ), + 'permissions' => array( + 'label' => $txt['edit_permissions'], + 'file' => 'ManagePermissions.php', + 'function' => 'ModifyPermissions', + 'icon' => 'permissions.gif', + 'permission' => array('manage_permissions'), + 'subsections' => array( + 'index' => array($txt['permissions_groups'], 'manage_permissions'), + 'board' => array($txt['permissions_boards'], 'manage_permissions'), + 'profiles' => array($txt['permissions_profiles'], 'manage_permissions'), + 'postmod' => array($txt['permissions_post_moderation'], 'manage_permissions', 'enabled' => $modSettings['postmod_active']), + 'settings' => array($txt['settings'], 'admin_forum'), + ), + ), + 'regcenter' => array( + 'label' => $txt['registration_center'], + 'file' => 'ManageRegistration.php', + 'function' => 'RegCenter', + 'icon' => 'regcenter.gif', + 'permission' => array('admin_forum', 'moderate_forum'), + 'subsections' => array( + 'register' => array($txt['admin_browse_register_new'], 'moderate_forum'), + 'agreement' => array($txt['registration_agreement'], 'admin_forum'), + 'reservednames' => array($txt['admin_reserved_set'], 'admin_forum'), + 'settings' => array($txt['settings'], 'admin_forum'), + ), + ), + 'ban' => array( + 'label' => $txt['ban_title'], + 'file' => 'ManageBans.php', + 'function' => 'Ban', + 'icon' => 'ban.gif', + 'permission' => 'manage_bans', + 'subsections' => array( + 'list' => array($txt['ban_edit_list']), + 'add' => array($txt['ban_add_new']), + 'browse' => array($txt['ban_trigger_browse']), + 'log' => array($txt['ban_log']), + ), + ), + 'paidsubscribe' => array( + 'label' => $txt['paid_subscriptions'], + 'enabled' => in_array('ps', $context['admin_features']), + 'file' => 'ManagePaid.php', + 'icon' => 'paid.gif', + 'function' => 'ManagePaidSubscriptions', + 'permission' => 'admin_forum', + 'subsections' => array( + 'view' => array($txt['paid_subs_view']), + 'settings' => array($txt['settings']), + ), + ), + 'sengines' => array( + 'label' => $txt['search_engines'], + 'enabled' => in_array('sp', $context['admin_features']), + 'file' => 'ManageSearchEngines.php', + 'icon' => 'engines.gif', + 'function' => 'SearchEngines', + 'permission' => 'admin_forum', + 'subsections' => array( + 'stats' => array($txt['spider_stats']), + 'logs' => array($txt['spider_logs']), + 'spiders' => array($txt['spiders']), + 'settings' => array($txt['settings']), + ), + ), + ), + ), + 'maintenance' => array( + 'title' => $txt['admin_maintenance'], + 'permission' => array('admin_forum'), + 'areas' => array( + 'maintain' => array( + 'label' => $txt['maintain_title'], + 'file' => 'ManageMaintenance.php', + 'icon' => 'maintain.gif', + 'function' => 'ManageMaintenance', + 'subsections' => array( + 'routine' => array($txt['maintain_sub_routine'], 'admin_forum'), + 'database' => array($txt['maintain_sub_database'], 'admin_forum'), + 'members' => array($txt['maintain_sub_members'], 'admin_forum'), + 'topics' => array($txt['maintain_sub_topics'], 'admin_forum'), + ), + ), + 'scheduledtasks' => array( + 'label' => $txt['maintain_tasks'], + 'file' => 'ManageScheduledTasks.php', + 'icon' => 'scheduled.gif', + 'function' => 'ManageScheduledTasks', + 'subsections' => array( + 'tasks' => array($txt['maintain_tasks'], 'admin_forum'), + 'tasklog' => array($txt['scheduled_log'], 'admin_forum'), + ), + ), + 'mailqueue' => array( + 'label' => $txt['mailqueue_title'], + 'file' => 'ManageMail.php', + 'function' => 'ManageMail', + 'icon' => 'mail.gif', + 'subsections' => array( + 'browse' => array($txt['mailqueue_browse'], 'admin_forum'), + 'settings' => array($txt['mailqueue_settings'], 'admin_forum'), + ), + ), + 'reports' => array( + 'enabled' => in_array('rg', $context['admin_features']), + 'label' => $txt['generate_reports'], + 'file' => 'Reports.php', + 'function' => 'ReportsMain', + 'icon' => 'reports.gif', + ), + 'logs' => array( + 'label' => $txt['logs'], + 'function' => 'AdminLogs', + 'icon' => 'logs.gif', + 'subsections' => array( + 'errorlog' => array($txt['errlog'], 'admin_forum', 'enabled' => !empty($modSettings['enableErrorLogging']), 'url' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc'), + 'adminlog' => array($txt['admin_log'], 'admin_forum', 'enabled' => in_array('ml', $context['admin_features'])), + 'modlog' => array($txt['moderation_log'], 'admin_forum', 'enabled' => in_array('ml', $context['admin_features'])), + 'banlog' => array($txt['ban_log'], 'manage_bans'), + 'spiderlog' => array($txt['spider_logs'], 'admin_forum', 'enabled' => in_array('sp', $context['admin_features'])), + 'tasklog' => array($txt['scheduled_log'], 'admin_forum'), + 'pruning' => array($txt['pruning_title'], 'admin_forum'), + ), + ), + 'repairboards' => array( + 'label' => $txt['admin_repair'], + 'file' => 'RepairBoards.php', + 'function' => 'RepairBoards', + 'select' => 'maintain', + 'hidden' => true, + ), + ), + ), + ); + + // Any files to include for administration? + if (!empty($modSettings['integrate_admin_include'])) + { + $admin_includes = explode(',', $modSettings['integrate_admin_include']); + foreach ($admin_includes as $include) + { + $include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir'])); + if (file_exists($include)) + require_once($include); + } + } + + // Let them modify admin areas easily. + call_integration_hook('integrate_admin_areas', array(&$admin_areas)); + + // Make sure the administrator has a valid session... + validateSession(); + + // Actually create the menu! + $admin_include_data = createMenu($admin_areas); + unset($admin_areas); + + // Nothing valid? + if ($admin_include_data == false) + fatal_lang_error('no_access', false); + + // Build the link tree. + $context['linktree'][] = array( + 'url' => $scripturl . '?action=admin', + 'name' => $txt['admin_center'], + ); + if (isset($admin_include_data['current_area']) && $admin_include_data['current_area'] != 'index') + $context['linktree'][] = array( + 'url' => $scripturl . '?action=admin;area=' . $admin_include_data['current_area'] . ';' . $context['session_var'] . '=' . $context['session_id'], + 'name' => $admin_include_data['label'], + ); + if (!empty($admin_include_data['current_subsection']) && $admin_include_data['subsections'][$admin_include_data['current_subsection']][0] != $admin_include_data['label']) + $context['linktree'][] = array( + 'url' => $scripturl . '?action=admin;area=' . $admin_include_data['current_area'] . ';sa=' . $admin_include_data['current_subsection'] . ';' . $context['session_var'] . '=' . $context['session_id'], + 'name' => $admin_include_data['subsections'][$admin_include_data['current_subsection']][0], + ); + + // Make a note of the Unique ID for this menu. + $context['admin_menu_id'] = $context['max_menu_id']; + $context['admin_menu_name'] = 'menu_data_' . $context['admin_menu_id']; + + // Why on the admin are we? + $context['admin_area'] = $admin_include_data['current_area']; + + // Now - finally - call the right place! + if (isset($admin_include_data['file'])) + require_once($sourcedir . '/' . $admin_include_data['file']); + + $admin_include_data['function'](); +} + +// The main administration section. +function AdminHome() +{ + global $sourcedir, $forum_version, $txt, $scripturl, $context, $user_info, $boardurl, $modSettings, $smcFunc; + + // You have to be able to do at least one of the below to see this page. + isAllowedTo(array('admin_forum', 'manage_permissions', 'moderate_forum', 'manage_membergroups', 'manage_bans', 'send_mail', 'edit_news', 'manage_boards', 'manage_smileys', 'manage_attachments')); + + // Find all of this forum's administrators... + require_once($sourcedir . '/Subs-Membergroups.php'); + if (listMembergroupMembers_Href($context['administrators'], 1, 32) && allowedTo('manage_membergroups')) + { + // Add a 'more'-link if there are more than 32. + $context['more_admins_link'] = '' . $txt['more'] . ''; + } + + // Load the credits stuff. + require_once($sourcedir . '/Who.php'); + Credits(true); + + // This makes it easier to get the latest news with your time format. + $context['time_format'] = urlencode($user_info['time_format']); + + $context['current_versions'] = array( + 'php' => array('title' => $txt['support_versions_php'], 'version' => PHP_VERSION), + 'db' => array('title' => sprintf($txt['support_versions_db'], $smcFunc['db_title']), 'version' => ''), + 'server' => array('title' => $txt['support_versions_server'], 'version' => $_SERVER['SERVER_SOFTWARE']), + ); + $context['forum_version'] = $forum_version; + + // Get a list of current server versions. + require_once($sourcedir . '/Subs-Admin.php'); + $checkFor = array( + 'gd', + 'db_server', + 'mmcache', + 'eaccelerator', + 'phpa', + 'apc', + 'memcache', + 'xcache', + 'php', + 'server', + ); + $context['current_versions'] = getServerVersions($checkFor); + + $context['can_admin'] = allowedTo('admin_forum'); + + $context['sub_template'] = $context['admin_area'] == 'credits' ? 'credits' : 'admin'; + $context['page_title'] = $context['admin_area'] == 'credits' ? $txt['support_credits_title'] : $txt['admin_center']; + + // The format of this array is: permission, action, title, description, icon. + $quick_admin_tasks = array( + array('', 'credits', 'support_credits_title', 'support_credits_info', 'support_and_credits.png'), + array('admin_forum', 'featuresettings', 'modSettings_title', 'modSettings_info', 'features_and_options.png'), + array('admin_forum', 'maintain', 'maintain_title', 'maintain_info', 'forum_maintenance.png'), + array('manage_permissions', 'permissions', 'edit_permissions', 'edit_permissions_info', 'permissions.png'), + array('admin_forum', 'theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id'], 'theme_admin', 'theme_admin_info', 'themes_and_layout.png'), + array('admin_forum', 'packages', 'package', 'package_info', 'packages.png'), + array('manage_smileys', 'smileys', 'smileys_manage', 'smileys_manage_info', 'smilies_and_messageicons.png'), + array('moderate_forum', 'viewmembers', 'admin_users', 'member_center_info', 'members.png'), + ); + + $context['quick_admin_tasks'] = array(); + foreach ($quick_admin_tasks as $task) + { + if (!empty($task[0]) && !allowedTo($task[0])) + continue; + + $context['quick_admin_tasks'][] = array( + 'href' => $scripturl . '?action=admin;area=' . $task[1], + 'link' => '' . $txt[$task[2]] . '', + 'title' => $txt[$task[2]], + 'description' => $txt[$task[3]], + 'icon' => $task[4], + 'is_last' => false + ); + } + + if (count($context['quick_admin_tasks']) % 2 == 1) + { + $context['quick_admin_tasks'][] = array( + 'href' => '', + 'link' => '', + 'title' => '', + 'description' => '', + 'is_last' => true + ); + $context['quick_admin_tasks'][count($context['quick_admin_tasks']) - 2]['is_last'] = true; + } + elseif (count($context['quick_admin_tasks']) != 0) + { + $context['quick_admin_tasks'][count($context['quick_admin_tasks']) - 1]['is_last'] = true; + $context['quick_admin_tasks'][count($context['quick_admin_tasks']) - 2]['is_last'] = true; + } + + // Lastly, fill in the blanks in the support resources paragraphs. + $txt['support_resources_p1'] = sprintf($txt['support_resources_p1'], + 'http://wiki.simplemachines.org/', + 'http://wiki.simplemachines.org/smf/features2', + 'http://wiki.simplemachines.org/smf/options2', + 'http://wiki.simplemachines.org/smf/themes2', + 'http://wiki.simplemachines.org/smf/packages2' + ); + $txt['support_resources_p2'] = sprintf($txt['support_resources_p2'], + 'http://www.simplemachines.org/community/', + 'http://www.simplemachines.org/redirect/english_support', + 'http://www.simplemachines.org/redirect/international_support_boards', + 'http://www.simplemachines.org/redirect/smf_support', + 'http://www.simplemachines.org/redirect/customize_support' + ); +} + +// Get one of the admin information files from Simple Machines. +function DisplayAdminFile() +{ + global $context, $modSettings, $smcFunc; + + @ini_set('memory_limit', '32M'); + + if (empty($_REQUEST['filename']) || !is_string($_REQUEST['filename'])) + fatal_lang_error('no_access', false); + + $request = $smcFunc['db_query']('', ' + SELECT data, filetype + FROM {db_prefix}admin_info_files + WHERE filename = {string:current_filename} + LIMIT 1', + array( + 'current_filename' => $_REQUEST['filename'], + ) + ); + + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('admin_file_not_found', true, array($_REQUEST['filename'])); + + list ($file_data, $filetype) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // !!! Temp. + // Figure out if sesc is still being used. + if (strpos($file_data, ';sesc=') !== false) + $file_data = ' +if (!(\'smfForum_sessionvar\' in window)) + window.smfForum_sessionvar = \'sesc\'; +' . strtr($file_data, array(';sesc=' => ';\' + window.smfForum_sessionvar + \'=')); + + $context['template_layers'] = array(); + // Lets make sure we aren't going to output anything nasty. + @ob_end_clean(); + if (!empty($modSettings['enableCompressedOutput'])) + @ob_start('ob_gzhandler'); + else + @ob_start(); + + // Make sure they know what type of file we are. + header('Content-Type: ' . $filetype); + echo $file_data; + obExit(false); +} + +// This allocates out all the search stuff. +function AdminSearch() +{ + global $txt, $context, $smcFunc, $sourcedir; + + isAllowedTo('admin_forum'); + + // What can we search for? + $subactions = array( + 'internal' => 'AdminSearchInternal', + 'online' => 'AdminSearchOM', + 'member' => 'AdminSearchMember', + ); + + $context['search_type'] = !isset($_REQUEST['search_type']) || !isset($subactions[$_REQUEST['search_type']]) ? 'internal' : $_REQUEST['search_type']; + $context['search_term'] = isset($_REQUEST['search_term']) ? $smcFunc['htmlspecialchars']($_REQUEST['search_term'], ENT_QUOTES) : ''; + + $context['sub_template'] = 'admin_search_results'; + $context['page_title'] = $txt['admin_search_results']; + + // Keep track of what the admin wants. + if (empty($context['admin_preferences']['sb']) || $context['admin_preferences']['sb'] != $context['search_type']) + { + $context['admin_preferences']['sb'] = $context['search_type']; + + // Update the preferences. + require_once($sourcedir . '/Subs-Admin.php'); + updateAdminPreferences(); + } + + if (trim($context['search_term']) == '') + $context['search_results'] = array(); + else + $subactions[$context['search_type']](); +} + +// A complicated but relatively quick internal search. +function AdminSearchInternal() +{ + global $context, $txt, $helptxt, $scripturl, $sourcedir; + + // Try to get some more memory. + @ini_set('memory_limit', '128M'); + + // Load a lot of language files. + $language_files = array( + 'Help', 'ManageMail', 'ManageSettings', 'ManageCalendar', 'ManageBoards', 'ManagePaid', 'ManagePermissions', 'Search', + 'Login', 'ManageSmileys', + ); + loadLanguage(implode('+', $language_files)); + + // All the files we need to include. + $include_files = array( + 'ManageSettings', 'ManageBoards', 'ManageNews', 'ManageAttachments', 'ManageCalendar', 'ManageMail', 'ManagePaid', 'ManagePermissions', + 'ManagePosts', 'ManageRegistration', 'ManageSearch', 'ManageSearchEngines', 'ManageServer', 'ManageSmileys', + ); + foreach ($include_files as $file) + require_once($sourcedir . '/' . $file . '.php'); + + /* This is the huge array that defines everything... it's a huge array of items formatted as follows: + 0 = Language index (Can be array of indexes) to search through for this setting. + 1 = URL for this indexes page. + 2 = Help index for help associated with this item (If different from 0) + */ + + $search_data = array( + // All the major sections of the forum. + 'sections' => array( + ), + 'settings' => array( + array('COPPA', 'area=regcenter;sa=settings'), + array('CAPTCHA', 'area=securitysettings;sa=spam'), + ), + ); + + // Go through the admin menu structure trying to find suitably named areas! + foreach ($context[$context['admin_menu_name']]['sections'] as $section) + { + foreach ($section['areas'] as $menu_key => $menu_item) + { + $search_data['sections'][] = array($menu_item['label'], 'area=' . $menu_key); + if (!empty($menu_item['subsections'])) + foreach ($menu_item['subsections'] as $key => $sublabel) + { + if (isset($sublabel['label'])) + $search_data['sections'][] = array($sublabel['label'], 'area=' . $menu_key . ';sa=' . $key); + } + } + } + + // This is a special array of functions that contain setting data - we query all these to simply pull all setting bits! + $settings_search = array( + array('ModifyCoreFeatures', 'area=corefeatures'), + array('ModifyBasicSettings', 'area=featuresettings;sa=basic'), + array('ModifyLayoutSettings', 'area=featuresettings;sa=layout'), + array('ModifyKarmaSettings', 'area=featuresettings;sa=karma'), + array('ModifySignatureSettings', 'area=featuresettings;sa=sig'), + array('ModifyGeneralSecuritySettings', 'area=securitysettings;sa=general'), + array('ModifySpamSettings', 'area=securitysettings;sa=spam'), + array('ModifyModerationSettings', 'area=securitysettings;sa=moderation'), + array('ModifyGeneralModSettings', 'area=modsettings;sa=general'), + // Mod authors if you want to be "real freaking good" then add any setting pages for your mod BELOW this line! + array('ManageAttachmentSettings', 'area=manageattachments;sa=attachments'), + array('ManageAvatarSettings', 'area=manageattachments;sa=avatars'), + array('ModifyCalendarSettings', 'area=managecalendar;sa=settings'), + array('EditBoardSettings', 'area=manageboards;sa=settings'), + array('ModifyMailSettings', 'area=mailqueue;sa=settings'), + array('ModifyNewsSettings', 'area=news;sa=settings'), + array('GeneralPermissionSettings', 'area=permissions;sa=settings'), + array('ModifyPostSettings', 'area=postsettings;sa=posts'), + array('ModifyBBCSettings', 'area=postsettings;sa=bbc'), + array('ModifyTopicSettings', 'area=postsettings;sa=topics'), + array('EditSearchSettings', 'area=managesearch;sa=settings'), + array('EditSmileySettings', 'area=smileys;sa=settings'), + array('ModifyGeneralSettings', 'area=serversettings;sa=general'), + array('ModifyDatabaseSettings', 'area=serversettings;sa=database'), + array('ModifyCookieSettings', 'area=serversettings;sa=cookie'), + array('ModifyCacheSettings', 'area=serversettings;sa=cache'), + array('ModifyLanguageSettings', 'area=languages;sa=settings'), + array('ModifyRegistrationSettings', 'area=regcenter;sa=settings'), + array('ManageSearchEngineSettings', 'area=sengines;sa=settings'), + array('ModifySubscriptionSettings', 'area=paidsubscribe;sa=settings'), + array('ModifyPruningSettings', 'area=logs;sa=pruning'), + ); + + foreach ($settings_search as $setting_area) + { + // Get a list of their variables. + $config_vars = $setting_area[0](true); + + foreach ($config_vars as $var) + if (!empty($var[1]) && !in_array($var[0], array('permissions', 'switch'))) + $search_data['settings'][] = array($var[(isset($var[2]) && in_array($var[2], array('file', 'db'))) ? 0 : 1], $setting_area[1]); + } + + $context['page_title'] = $txt['admin_search_results']; + $context['search_results'] = array(); + + $search_term = strtolower($context['search_term']); + // Go through all the search data trying to find this text! + foreach ($search_data as $section => $data) + { + foreach ($data as $item) + { + $found = false; + if (!is_array($item[0])) + $item[0] = array($item[0]); + foreach ($item[0] as $term) + { + $lc_term = strtolower($term); + if (strpos($lc_term, $search_term) !== false || (isset($txt[$term]) && strpos(strtolower($txt[$term]), $search_term) !== false) || (isset($txt['setting_' . $term]) && strpos(strtolower($txt['setting_' . $term]), $search_term) !== false)) + { + $found = $term; + break; + } + } + + if ($found) + { + // Format the name - and remove any descriptions the entry may have. + $name = isset($txt[$found]) ? $txt[$found] : (isset($txt['setting_' . $found]) ? $txt['setting_' . $found] : $found); + $name = preg_replace('~<(?:div|span)\sclass="smalltext">.+?~', '', $name); + + $context['search_results'][] = array( + 'url' => (substr($item[1], 0, 4) == 'area' ? $scripturl . '?action=admin;' . $item[1] : $item[1]) . ';' . $context['session_var'] . '=' . $context['session_id'] . ((substr($item[1], 0, 4) == 'area' && $section == 'settings' ? '#' . $item[0][0] : '')), + 'name' => $name, + 'type' => $section, + 'help' => shorten_subject(isset($item[2]) ? strip_tags($helptxt[$item2]) : (isset($helptxt[$found]) ? strip_tags($helptxt[$found]) : ''), 255), + ); + } + } + } +} + +// All this does is pass through to manage members. +function AdminSearchMember() +{ + global $context, $sourcedir; + + require_once($sourcedir . '/ManageMembers.php'); + $_REQUEST['sa'] = 'query'; + + $_POST['membername'] = $context['search_term']; + + ViewMembers(); +} + +// This file allows the user to search the SM online manual for a little of help. +function AdminSearchOM() +{ + global $context, $sourcedir; + + $docsURL = 'docs.simplemachines.org'; + $context['doc_scripturl'] = 'http://docs.simplemachines.org/index.php'; + + // Set all the parameters search might expect. + $postVars = array( + 'search' => $context['search_term'], + ); + + // Encode the search data. + foreach ($postVars as $k => $v) + $postVars[$k] = urlencode($k) . '=' . urlencode($v); + + // This is what we will send. + $postVars = implode('&', $postVars); + + // Get the results from the doc site. + require_once($sourcedir . '/Subs-Package.php'); + $search_results = fetch_web_data($context['doc_scripturl'] . '?action=search2&xml', $postVars); + + // If we didn't get any xml back we are in trouble - perhaps the doc site is overloaded? + if (!$search_results || preg_match('~<' . '\?xml\sversion="\d+\.\d+"\sencoding=".+?"\?' . '>\s*(.+?)~is', $search_results, $matches) != true) + fatal_lang_error('cannot_connect_doc_site'); + + $search_results = $matches[1]; + + // Otherwise we simply walk through the XML and stick it in context for display. + $context['search_results'] = array(); + loadClassFile('Class-Package.php'); + + // Get the results loaded into an array for processing! + $results = new xmlArray($search_results, false); + + // Move through the smf layer. + if (!$results->exists('smf')) + fatal_lang_error('cannot_connect_doc_site'); + $results = $results->path('smf[0]'); + + // Are there actually some results? + if (!$results->exists('noresults') && !$results->exists('results')) + fatal_lang_error('cannot_connect_doc_site'); + elseif ($results->exists('results')) + { + foreach ($results->set('results/result') as $result) + { + if (!$result->exists('messages')) + continue; + + $context['search_results'][$result->fetch('id')] = array( + 'topic_id' => $result->fetch('id'), + 'relevance' => $result->fetch('relevance'), + 'board' => array( + 'id' => $result->fetch('board/id'), + 'name' => $result->fetch('board/name'), + 'href' => $result->fetch('board/href'), + ), + 'category' => array( + 'id' => $result->fetch('category/id'), + 'name' => $result->fetch('category/name'), + 'href' => $result->fetch('category/href'), + ), + 'messages' => array(), + ); + + // Add the messages. + foreach ($result->set('messages/message') as $message) + $context['search_results'][$result->fetch('id')]['messages'][] = array( + 'id' => $message->fetch('id'), + 'subject' => $message->fetch('subject'), + 'body' => $message->fetch('body'), + 'time' => $message->fetch('time'), + 'timestamp' => $message->fetch('timestamp'), + 'start' => $message->fetch('start'), + 'author' => array( + 'id' => $message->fetch('author/id'), + 'name' => $message->fetch('author/name'), + 'href' => $message->fetch('author/href'), + ), + ); + } + } +} + +// This function decides which log to load. +function AdminLogs() +{ + global $sourcedir, $context, $txt, $scripturl; + + // These are the logs they can load. + $log_functions = array( + 'errorlog' => array('ManageErrors.php', 'ViewErrorLog'), + 'adminlog' => array('Modlog.php', 'ViewModlog'), + 'modlog' => array('Modlog.php', 'ViewModlog'), + 'banlog' => array('ManageBans.php', 'BanLog'), + 'spiderlog' => array('ManageSearchEngines.php', 'SpiderLogs'), + 'tasklog' => array('ManageScheduledTasks.php', 'TaskLog'), + 'pruning' => array('ManageSettings.php', 'ModifyPruningSettings'), + ); + + $sub_action = isset($_REQUEST['sa']) && isset($log_functions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'errorlog'; + // If it's not got a sa set it must have come here for first time, pretend error log should be reversed. + if (!isset($_REQUEST['sa'])) + $_REQUEST['desc'] = true; + + // Setup some tab stuff. + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['logs'], + 'help' => '', + 'description' => $txt['maintain_info'], + 'tabs' => array( + 'errorlog' => array( + 'url' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc', + 'description' => sprintf($txt['errlog_desc'], $txt['remove']), + ), + 'adminlog' => array( + 'description' => $txt['admin_log_desc'], + ), + 'modlog' => array( + 'description' => $txt['moderation_log_desc'], + ), + 'banlog' => array( + 'description' => $txt['ban_log_description'], + ), + 'spiderlog' => array( + 'description' => $txt['spider_log_desc'], + ), + 'tasklog' => array( + 'description' => $txt['scheduled_log_desc'], + ), + 'pruning' => array( + 'description' => $txt['pruning_log_desc'], + ), + ), + ); + + require_once($sourcedir . '/' . $log_functions[$sub_action][0]); + $log_functions[$sub_action][1](); +} + +?> \ No newline at end of file diff --git a/Sources/BoardIndex.php b/Sources/BoardIndex.php new file mode 100644 index 0000000..d48422e --- /dev/null +++ b/Sources/BoardIndex.php @@ -0,0 +1,143 @@ + true, + 'base_level' => 0, + 'parent_id' => 0, + 'set_latest_post' => true, + 'countChildPosts' => !empty($modSettings['countChildPosts']), + ); + $context['categories'] = getBoardIndex($boardIndexOptions); + + // Get the user online list. + require_once($sourcedir . '/Subs-MembersOnline.php'); + $membersOnlineOptions = array( + 'show_hidden' => allowedTo('moderate_forum'), + 'sort' => 'log_time', + 'reverse_sort' => true, + ); + $context += getMembersOnlineStats($membersOnlineOptions); + + $context['show_buddies'] = !empty($user_info['buddies']); + + // Are we showing all membergroups on the board index? + if (!empty($settings['show_group_key'])) + $context['membergroups'] = cache_quick_get('membergroup_list', 'Subs-Membergroups.php', 'cache_getMembergroupList', array()); + + // Track most online statistics? (Subs-MembersOnline.php) + if (!empty($modSettings['trackStats'])) + trackStatsUsersOnline($context['num_guests'] + $context['num_spiders'] + $context['num_users_online']); + + // Retrieve the latest posts if the theme settings require it. + if (isset($settings['number_recent_posts']) && $settings['number_recent_posts'] > 1) + { + $latestPostOptions = array( + 'number_posts' => $settings['number_recent_posts'], + ); + $context['latest_posts'] = cache_quick_get('boardindex-latest_posts:' . md5($user_info['query_wanna_see_board'] . $user_info['language']), 'Subs-Recent.php', 'cache_getLastPosts', array($latestPostOptions)); + } + + $settings['display_recent_bar'] = !empty($settings['number_recent_posts']) ? $settings['number_recent_posts'] : 0; + $settings['show_member_bar'] &= allowedTo('view_mlist'); + $context['show_stats'] = allowedTo('view_stats') && !empty($modSettings['trackStats']); + $context['show_member_list'] = allowedTo('view_mlist'); + $context['show_who'] = allowedTo('who_view') && !empty($modSettings['who_enabled']); + + // Load the calendar? + if (!empty($modSettings['cal_enabled']) && allowedTo('calendar_view')) + { + // Retrieve the calendar data (events, birthdays, holidays). + $eventOptions = array( + 'include_holidays' => $modSettings['cal_showholidays'] > 1, + 'include_birthdays' => $modSettings['cal_showbdays'] > 1, + 'include_events' => $modSettings['cal_showevents'] > 1, + 'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'], + ); + $context += cache_quick_get('calendar_index_offset_' . ($user_info['time_offset'] + $modSettings['time_offset']), 'Subs-Calendar.php', 'cache_getRecentEvents', array($eventOptions)); + + // Whether one or multiple days are shown on the board index. + $context['calendar_only_today'] = $modSettings['cal_days_for_index'] == 1; + + // This is used to show the "how-do-I-edit" help. + $context['calendar_can_edit'] = allowedTo('calendar_edit_any'); + } + else + $context['show_calendar'] = false; + + $context['page_title'] = sprintf($txt['forum_index'], $context['forum_name']); +} + +// Collapse or expand a category +function CollapseCategory() +{ + global $user_info, $sourcedir, $context; + + // Just in case, no need, no need. + $context['robot_no_index'] = true; + + checkSession('request'); + + if (!isset($_GET['sa'])) + fatal_lang_error('no_access', false); + + // Check if the input values are correct. + if (in_array($_REQUEST['sa'], array('expand', 'collapse', 'toggle')) && isset($_REQUEST['c'])) + { + // And collapse/expand/toggle the category. + require_once($sourcedir . '/Subs-Categories.php'); + collapseCategories(array((int) $_REQUEST['c']), $_REQUEST['sa'], array($user_info['id'])); + } + + // And go back to the board index. + BoardIndex(); +} + +?> \ No newline at end of file diff --git a/Sources/Calendar.php b/Sources/Calendar.php new file mode 100644 index 0000000..4194233 --- /dev/null +++ b/Sources/Calendar.php @@ -0,0 +1,487 @@ + 'iCalDownload', + 'post' => 'CalendarPost', + ); + + if (isset($_GET['sa']) && isset($subActions[$_GET['sa']]) && !WIRELESS) + return $subActions[$_GET['sa']](); + + // This is gonna be needed... + loadTemplate('Calendar'); + + // You can't do anything if the calendar is off. + if (empty($modSettings['cal_enabled'])) + fatal_lang_error('calendar_off', false); + + // Set the page title to mention the calendar ;). + $context['page_title'] = $txt['calendar']; + + // Is this a week view? + $context['view_week'] = isset($_GET['viewweek']); + + // Don't let search engines index weekly calendar pages. + if ($context['view_week']) + $context['robot_no_index'] = true; + + // Get the current day of month... + require_once($sourcedir . '/Subs-Calendar.php'); + $today = getTodayInfo(); + + // If the month and year are not passed in, use today's date as a starting point. + $curPage = array( + 'day' => isset($_REQUEST['day']) ? (int) $_REQUEST['day'] : $today['day'], + 'month' => isset($_REQUEST['month']) ? (int) $_REQUEST['month'] : $today['month'], + 'year' => isset($_REQUEST['year']) ? (int) $_REQUEST['year'] : $today['year'] + ); + + // Make sure the year and month are in valid ranges. + if ($curPage['month'] < 1 || $curPage['month'] > 12) + fatal_lang_error('invalid_month', false); + if ($curPage['year'] < $modSettings['cal_minyear'] || $curPage['year'] > $modSettings['cal_maxyear']) + fatal_lang_error('invalid_year', false); + // If we have a day clean that too. + if ($context['view_week']) + { + // Note $isValid is -1 < PHP 5.1 + $isValid = mktime(0, 0, 0, $curPage['month'], $curPage['day'], $curPage['year']); + if ($curPage['day'] > 31 || !$isValid || $isValid == -1) + fatal_lang_error('invalid_day', false); + } + + // Load all the context information needed to show the calendar grid. + $calendarOptions = array( + 'start_day' => !empty($options['calendar_start_day']) ? $options['calendar_start_day'] : 0, + 'show_birthdays' => in_array($modSettings['cal_showbdays'], array(1, 2)), + 'show_events' => in_array($modSettings['cal_showevents'], array(1, 2)), + 'show_holidays' => in_array($modSettings['cal_showholidays'], array(1, 2)), + 'show_week_num' => true, + 'short_day_titles' => false, + 'show_next_prev' => true, + 'show_week_links' => true, + 'size' => 'large', + ); + + // Load up the main view. + if ($context['view_week']) + $context['calendar_grid_main'] = getCalendarWeek($curPage['month'], $curPage['year'], $curPage['day'], $calendarOptions); + else + $context['calendar_grid_main'] = getCalendarGrid($curPage['month'], $curPage['year'], $calendarOptions); + + // Load up the previous and next months. + $calendarOptions['show_birthdays'] = $calendarOptions['show_events'] = $calendarOptions['show_holidays'] = false; + $calendarOptions['short_day_titles'] = true; + $calendarOptions['show_next_prev'] = false; + $calendarOptions['show_week_links'] = false; + $calendarOptions['size'] = 'small'; + $context['calendar_grid_current'] = getCalendarGrid($curPage['month'], $curPage['year'], $calendarOptions); + // Only show previous month if it isn't pre-January of the min-year + if ($context['calendar_grid_current']['previous_calendar']['year'] > $modSettings['cal_minyear'] || $curPage['month'] != 1) + $context['calendar_grid_prev'] = getCalendarGrid($context['calendar_grid_current']['previous_calendar']['month'], $context['calendar_grid_current']['previous_calendar']['year'], $calendarOptions); + // Only show next month if it isn't post-December of the max-year + if ($context['calendar_grid_current']['next_calendar']['year'] < $modSettings['cal_maxyear'] || $curPage['month'] != 12) + $context['calendar_grid_next'] = getCalendarGrid($context['calendar_grid_current']['next_calendar']['month'], $context['calendar_grid_current']['next_calendar']['year'], $calendarOptions); + + // Basic template stuff. + $context['can_post'] = allowedTo('calendar_post'); + $context['current_day'] = $curPage['day']; + $context['current_month'] = $curPage['month']; + $context['current_year'] = $curPage['year']; + $context['show_all_birthdays'] = isset($_GET['showbd']); + + // Set the page title to mention the month or week, too + $context['page_title'] .= ' - ' . ($context['view_week'] ? sprintf($txt['calendar_week_title'], $context['calendar_grid_main']['week_number'], ($context['calendar_grid_main']['week_number'] == 53 ? $context['current_year'] - 1 : $context['current_year'])) : $txt['months'][$context['current_month']] . ' ' . $context['current_year']); + + // Load up the linktree! + $context['linktree'][] = array( + 'url' => $scripturl . '?action=calendar', + 'name' => $txt['calendar'] + ); + // Add the current month to the linktree. + $context['linktree'][] = array( + 'url' => $scripturl . '?action=calendar;year=' . $context['current_year'] . ';month=' . $context['current_month'], + 'name' => $txt['months'][$context['current_month']] . ' ' . $context['current_year'] + ); + // If applicable, add the current week to the linktree. + if ($context['view_week']) + $context['linktree'][] = array( + 'url' => $scripturl . '?action=calendar;viewweek;year=' . $context['current_year'] . ';month=' . $context['current_month'] . ';day=' . $context['current_day'], + 'name' => $txt['calendar_week'] . ' ' . $context['calendar_grid_main']['week_number'] + ); +} + +function CalendarPost() +{ + global $context, $txt, $user_info, $sourcedir, $scripturl; + global $modSettings, $topic, $smcFunc; + + // Well - can they? + isAllowedTo('calendar_post'); + + // We need this for all kinds of useful functions. + require_once($sourcedir . '/Subs-Calendar.php'); + + // Cast this for safety... + if (isset($_REQUEST['eventid'])) + $_REQUEST['eventid'] = (int) $_REQUEST['eventid']; + + // Submitting? + if (isset($_POST[$context['session_var']], $_REQUEST['eventid'])) + { + checkSession(); + + // Validate the post... + if (!isset($_POST['link_to_board'])) + validateEventPost(); + + // If you're not allowed to edit any events, you have to be the poster. + if ($_REQUEST['eventid'] > 0 && !allowedTo('calendar_edit_any')) + isAllowedTo('calendar_edit_' . (!empty($user_info['id']) && getEventPoster($_REQUEST['eventid']) == $user_info['id'] ? 'own' : 'any')); + + // New - and directing? + if ($_REQUEST['eventid'] == -1 && isset($_POST['link_to_board'])) + { + $_REQUEST['calendar'] = 1; + require_once($sourcedir . '/Post.php'); + return Post(); + } + // New... + elseif ($_REQUEST['eventid'] == -1) + { + $eventOptions = array( + 'board' => 0, + 'topic' => 0, + 'title' => substr($_REQUEST['evtitle'], 0, 60), + 'member' => $user_info['id'], + 'start_date' => sprintf('%04d-%02d-%02d', $_POST['year'], $_POST['month'], $_POST['day']), + 'span' => isset($_POST['span']) && $_POST['span'] > 0 ? min((int) $modSettings['cal_maxspan'], (int) $_POST['span'] - 1) : 0, + ); + insertEvent($eventOptions); + } + + // Deleting... + elseif (isset($_REQUEST['deleteevent'])) + removeEvent($_REQUEST['eventid']); + + // ... or just update it? + else + { + $eventOptions = array( + 'title' => substr($_REQUEST['evtitle'], 0, 60), + 'span' => empty($modSettings['cal_allowspan']) || empty($_POST['span']) || $_POST['span'] == 1 || empty($modSettings['cal_maxspan']) || $_POST['span'] > $modSettings['cal_maxspan'] ? 0 : min((int) $modSettings['cal_maxspan'], (int) $_POST['span'] - 1), + 'start_date' => strftime('%Y-%m-%d', mktime(0, 0, 0, (int) $_REQUEST['month'], (int) $_REQUEST['day'], (int) $_REQUEST['year'])), + ); + + modifyEvent($_REQUEST['eventid'], $eventOptions); + } + + updateSettings(array( + 'calendar_updated' => time(), + )); + + // No point hanging around here now... + redirectexit($scripturl . '?action=calendar;month=' . $_POST['month'] . ';year=' . $_POST['year']); + } + + // If we are not enabled... we are not enabled. + if (empty($modSettings['cal_allow_unlinked']) && empty($_REQUEST['eventid'])) + { + $_REQUEST['calendar'] = 1; + require_once($sourcedir . '/Post.php'); + return Post(); + } + + // New? + if (!isset($_REQUEST['eventid'])) + { + $today = getdate(); + + $context['event'] = array( + 'boards' => array(), + 'board' => 0, + 'new' => 1, + 'eventid' => -1, + 'year' => isset($_REQUEST['year']) ? $_REQUEST['year'] : $today['year'], + 'month' => isset($_REQUEST['month']) ? $_REQUEST['month'] : $today['mon'], + 'day' => isset($_REQUEST['day']) ? $_REQUEST['day'] : $today['mday'], + 'title' => '', + 'span' => 1, + ); + $context['event']['last_day'] = (int) strftime('%d', mktime(0, 0, 0, $context['event']['month'] == 12 ? 1 : $context['event']['month'] + 1, 0, $context['event']['month'] == 12 ? $context['event']['year'] + 1 : $context['event']['year'])); + + // Get list of boards that can be posted in. + $boards = boardsAllowedTo('post_new'); + if (empty($boards)) + fatal_lang_error('cannot_post_new', 'permission'); + + // Load the list of boards and categories in the context. + require_once($sourcedir . '/Subs-MessageIndex.php'); + $boardListOptions = array( + 'included_boards' => in_array(0, $boards) ? null : $boards, + 'not_redirection' => true, + 'use_permissions' => true, + 'selected_board' => $modSettings['cal_defaultboard'], + ); + $context['event']['categories'] = getBoardList($boardListOptions); + } + else + { + $context['event'] = getEventProperties($_REQUEST['eventid']); + + if ($context['event'] === false) + fatal_lang_error('no_access', false); + + // If it has a board, then they should be editing it within the topic. + if (!empty($context['event']['topic']['id']) && !empty($context['event']['topic']['first_msg'])) + { + // We load the board up, for a check on the board access rights... + $topic = $context['event']['topic']['id']; + loadBoard(); + } + + // Make sure the user is allowed to edit this event. + if ($context['event']['member'] != $user_info['id']) + isAllowedTo('calendar_edit_any'); + elseif (!allowedTo('calendar_edit_any')) + isAllowedTo('calendar_edit_own'); + } + + // Template, sub template, etc. + loadTemplate('Calendar'); + $context['sub_template'] = 'event_post'; + + $context['page_title'] = isset($_REQUEST['eventid']) ? $txt['calendar_edit'] : $txt['calendar_post_event']; + $context['linktree'][] = array( + 'name' => $context['page_title'], + ); +} + +function iCalDownload() +{ + global $smcFunc, $sourcedir, $forum_version, $context, $modSettings; + + // Goes without saying that this is required. + if (!isset($_REQUEST['eventid'])) + fatal_lang_error('no_access', false); + + // This is kinda wanted. + require_once($sourcedir . '/Subs-Calendar.php'); + + // Load up the event in question and check it exists. + $event = getEventProperties($_REQUEST['eventid']); + + if ($event === false) + fatal_lang_error('no_access', false); + + // Check the title isn't too long - iCal requires some formatting if so. + $title = str_split($event['title'], 30); + foreach ($title as $id => $line) + { + if ($id != 0) + $title[$id] = ' ' . $title[$id]; + $title[$id] .= "\n"; + } + + // Format the date. + $date = $event['year'] . '-' . ($event['month'] < 10 ? '0' . $event['month'] : $event['month']) . '-' . ($event['day'] < 10 ? '0' . $event['day'] : $event['day']) . 'T'; + $date .= '1200:00:00Z'; + + // This is what we will be sending later. + $filecontents = ''; + $filecontents .= 'BEGIN:VCALENDAR' . "\n"; + $filecontents .= 'VERSION:2.0' . "\n"; + $filecontents .= 'PRODID:-//SimpleMachines//SMF ' . (empty($forum_version) ? 1.0 : strtr($forum_version, array('SMF ' => ''))) . '//EN' . "\n"; + $filecontents .= 'BEGIN:VEVENT' . "\n"; + $filecontents .= 'DTSTART:' . $date . "\n"; + $filecontents .= 'DTEND:' . $date . "\n"; + $filecontents .= 'SUMMARY:' . implode('', $title); + $filecontents .= 'END:VEVENT' . "\n"; + $filecontents .= 'END:VCALENDAR'; + + // Send some standard headers. + ob_end_clean(); + if (!empty($modSettings['enableCompressedOutput'])) + @ob_start('ob_gzhandler'); + else + ob_start(); + + // Send the file headers + header('Pragma: '); + header('Cache-Control: no-cache'); + if (!$context['browser']['is_gecko']) + header('Content-Transfer-Encoding: binary'); + header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 525600 * 60) . ' GMT'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . 'GMT'); + header('Accept-Ranges: bytes'); + header('Connection: close'); + header('Content-Disposition: attachment; filename=' . $event['title'] . '.ics'); + + // How big is it? + if (empty($modSettings['enableCompressedOutput'])) + header('Content-Length: ' . $smcFunc['strlen']($filecontents)); + + // This is a calendar item! + header('Content-Type: text/calendar'); + + // Chuck out the card. + echo $filecontents; + + // Off we pop - lovely! + obExit(false); +} + +// This is not the code you are looking for. +function clock() +{ + global $settings, $context; + $context['onimg'] = $settings['images_url'] . '/bbc/bbc_bg.gif'; + $context['offimg'] = $settings['images_url'] . '/bbc/bbc_hoverbg.gif'; + + $context['page_title'] = 'Anyone know what time it is?'; + $context['robot_no_index'] = true; + + $omfg = isset($_REQUEST['omfg']); + $bcd = !isset($_REQUEST['rb']) && !isset($_REQUEST['omfg']) && !isset($_REQUEST['time']); + + loadTemplate('Calendar'); + + if ($bcd && !$omfg) + { + $context['sub_template'] = 'bcd'; + $context['clockicons'] = unserialize(base64_decode('YTo2OntzOjI6ImgxIjthOjI6e2k6MDtpOjI7aToxO2k6MTt9czoyOiJoMiI7YTo0OntpOjA7aTo4O2k6MTtpOjQ7aToyO2k6MjtpOjM7aToxO31zOjI6Im0xIjthOjM6e2k6MDtpOjQ7aToxO2k6MjtpOjI7aToxO31zOjI6Im0yIjthOjQ6e2k6MDtpOjg7aToxO2k6NDtpOjI7aToyO2k6MztpOjE7fXM6MjoiczEiO2E6Mzp7aTowO2k6NDtpOjE7aToyO2k6MjtpOjE7fXM6MjoiczIiO2E6NDp7aTowO2k6ODtpOjE7aTo0O2k6MjtpOjI7aTozO2k6MTt9fQ==')); + } + elseif (!$omfg && !isset($_REQUEST['time'])) + { + $context['sub_template'] = 'hms'; + $context['clockicons'] = unserialize(base64_decode('YTozOntzOjE6ImgiO2E6NTp7aTowO2k6MTY7aToxO2k6ODtpOjI7aTo0O2k6MztpOjI7aTo0O2k6MTt9czoxOiJtIjthOjY6e2k6MDtpOjMyO2k6MTtpOjE2O2k6MjtpOjg7aTozO2k6NDtpOjQ7aToyO2k6NTtpOjE7fXM6MToicyI7YTo2OntpOjA7aTozMjtpOjE7aToxNjtpOjI7aTo4O2k6MztpOjQ7aTo0O2k6MjtpOjU7aToxO319')); + } + elseif ($omfg) + { + $context['sub_template'] = 'omfg'; + $context['clockicons'] = unserialize(base64_decode('YTo2OntzOjQ6InllYXIiO2E6Nzp7aTowO2k6NjQ7aToxO2k6MzI7aToyO2k6MTY7aTozO2k6ODtpOjQ7aTo0O2k6NTtpOjI7aTo2O2k6MTt9czo1OiJtb250aCI7YTo0OntpOjA7aTo4O2k6MTtpOjQ7aToyO2k6MjtpOjM7aToxO31zOjM6ImRheSI7YTo1OntpOjA7aToxNjtpOjE7aTo4O2k6MjtpOjQ7aTozO2k6MjtpOjQ7aToxO31zOjQ6ImhvdXIiO2E6NTp7aTowO2k6MTY7aToxO2k6ODtpOjI7aTo0O2k6MztpOjI7aTo0O2k6MTt9czozOiJtaW4iO2E6Njp7aTowO2k6MzI7aToxO2k6MTY7aToyO2k6ODtpOjM7aTo0O2k6NDtpOjI7aTo1O2k6MTt9czozOiJzZWMiO2E6Njp7aTowO2k6MzI7aToxO2k6MTY7aToyO2k6ODtpOjM7aTo0O2k6NDtpOjI7aTo1O2k6MTt9fQ==')); + } + elseif (isset($_REQUEST['time'])) + { + $context['sub_template'] = 'thetime'; + $time = getdate($_REQUEST['time'] == 'now' ? time() : (int) $_REQUEST['time']); + + $context['clockicons'] = array( + 'year' => array( + 64 => false, + 32 => false, + 16 => false, + 8 => false, + 4 => false, + 2 => false, + 1 => false + ), + 'month' => array( + 8 => false, + 4 => false, + 2 => false, + 1 => false + ), + 'day' => array( + 16 => false, + 4 => false, + 8 => false, + 2 => false, + 1 => false + ), + 'hour' => array( + 32 => false, + 16 => false, + 8 => false, + 4 => false, + 2 => false, + 1 => false + ), + 'min' => array( + 32 => false, + 16 => false, + 8 => false, + 4 => false, + 2 => false, + 1 => false + ), + 'sec' => array( + 32 => false, + 16 => false, + 8 => false, + 4 => false, + 2 => false, + 1 => false + ), + ); + + $year = $time['year'] % 100; + $month = $time['mon']; + $day = $time['mday']; + $hour = $time['hours']; + $min = $time['minutes']; + $sec = $time['seconds']; + + foreach ($context['clockicons'] as $t => $vs) + foreach ($vs as $v => $dumb) + { + if ($$t >= $v) + { + $$t -= $v; + $context['clockicons'][$t][$v] = true; + } + } + } +} +?> \ No newline at end of file diff --git a/Sources/Class-Graphics.php b/Sources/Class-Graphics.php new file mode 100644 index 0000000..0550ecc --- /dev/null +++ b/Sources/Class-Graphics.php @@ -0,0 +1,715 @@ +MAX_LZW_BITS = 12; + unset($this->Next, $this->Vals, $this->Stack, $this->Buf); + + $this->Next = range(0, (1 << $this->MAX_LZW_BITS) - 1); + $this->Vals = range(0, (1 << $this->MAX_LZW_BITS) - 1); + $this->Stack = range(0, (1 << ($this->MAX_LZW_BITS + 1)) - 1); + $this->Buf = range(0, 279); + } + + public function decompress($data, &$datLen) + { + $stLen = strlen($data); + $datLen = 0; + $ret = ''; + + $this->LZWCommand($data, true); + + while (($iIndex = $this->LZWCommand($data, false)) >= 0) + $ret .= chr($iIndex); + + $datLen = $stLen - strlen($data); + + if ($iIndex != -2) + return false; + + return $ret; + } + + public function LZWCommand(&$data, $bInit) + { + if ($bInit) + { + $this->SetCodeSize = ord($data[0]); + $data = substr($data, 1); + + $this->CodeSize = $this->SetCodeSize + 1; + $this->ClearCode = 1 << $this->SetCodeSize; + $this->EndCode = $this->ClearCode + 1; + $this->MaxCode = $this->ClearCode + 2; + $this->MaxCodeSize = $this->ClearCode << 1; + + $this->GetCode($data, $bInit); + + $this->Fresh = 1; + for ($i = 0; $i < $this->ClearCode; $i++) + { + $this->Next[$i] = 0; + $this->Vals[$i] = $i; + } + + for (; $i < (1 << $this->MAX_LZW_BITS); $i++) + { + $this->Next[$i] = 0; + $this->Vals[$i] = 0; + } + + $this->sp = 0; + return 1; + } + + if ($this->Fresh) + { + $this->Fresh = 0; + do + { + $this->FirstCode = $this->GetCode($data, $bInit); + $this->OldCode = $this->FirstCode; + } + while ($this->FirstCode == $this->ClearCode); + + return $this->FirstCode; + } + + if ($this->sp > 0) + { + $this->sp--; + return $this->Stack[$this->sp]; + } + + while (($Code = $this->GetCode($data, $bInit)) >= 0) + { + if ($Code == $this->ClearCode) + { + for ($i = 0; $i < $this->ClearCode; $i++) + { + $this->Next[$i] = 0; + $this->Vals[$i] = $i; + } + + for (; $i < (1 << $this->MAX_LZW_BITS); $i++) + { + $this->Next[$i] = 0; + $this->Vals[$i] = 0; + } + + $this->CodeSize = $this->SetCodeSize + 1; + $this->MaxCodeSize = $this->ClearCode << 1; + $this->MaxCode = $this->ClearCode + 2; + $this->sp = 0; + $this->FirstCode = $this->GetCode($data, $bInit); + $this->OldCode = $this->FirstCode; + + return $this->FirstCode; + } + + if ($Code == $this->EndCode) + return -2; + + $InCode = $Code; + if ($Code >= $this->MaxCode) + { + $this->Stack[$this->sp] = $this->FirstCode; + $this->sp++; + $Code = $this->OldCode; + } + + while ($Code >= $this->ClearCode) + { + $this->Stack[$this->sp] = $this->Vals[$Code]; + $this->sp++; + + if ($Code == $this->Next[$Code]) // Circular table entry, big GIF Error! + return -1; + + $Code = $this->Next[$Code]; + } + + $this->FirstCode = $this->Vals[$Code]; + $this->Stack[$this->sp] = $this->FirstCode; + $this->sp++; + + if (($Code = $this->MaxCode) < (1 << $this->MAX_LZW_BITS)) + { + $this->Next[$Code] = $this->OldCode; + $this->Vals[$Code] = $this->FirstCode; + $this->MaxCode++; + + if (($this->MaxCode >= $this->MaxCodeSize) && ($this->MaxCodeSize < (1 << $this->MAX_LZW_BITS))) + { + $this->MaxCodeSize *= 2; + $this->CodeSize++; + } + } + + $this->OldCode = $InCode; + if ($this->sp > 0) + { + $this->sp--; + return $this->Stack[$this->sp]; + } + } + + return $Code; + } + + public function GetCode(&$data, $bInit) + { + if ($bInit) + { + $this->CurBit = 0; + $this->LastBit = 0; + $this->Done = 0; + $this->LastByte = 2; + + return 1; + } + + if (($this->CurBit + $this->CodeSize) >= $this->LastBit) + { + if ($this->Done) + { + // Ran off the end of my bits... + if ($this->CurBit >= $this->LastBit) + return 0; + + return -1; + } + + $this->Buf[0] = $this->Buf[$this->LastByte - 2]; + $this->Buf[1] = $this->Buf[$this->LastByte - 1]; + + $count = ord($data[0]); + $data = substr($data, 1); + + if ($count) + { + for ($i = 0; $i < $count; $i++) + $this->Buf[2 + $i] = ord($data{$i}); + + $data = substr($data, $count); + } + else + $this->Done = 1; + + $this->LastByte = 2 + $count; + $this->CurBit = ($this->CurBit - $this->LastBit) + 16; + $this->LastBit = (2 + $count) << 3; + } + + $iRet = 0; + for ($i = $this->CurBit, $j = 0; $j < $this->CodeSize; $i++, $j++) + $iRet |= (($this->Buf[intval($i / 8)] & (1 << ($i % 8))) != 0) << $j; + + $this->CurBit += $this->CodeSize; + return $iRet; + } +} + +class gif_color_table +{ + public $m_nColors; + public $m_arColors; + + public function __construct() + { + unset($this->m_nColors, $this->m_arColors); + } + + public function load($lpData, $num) + { + $this->m_nColors = 0; + $this->m_arColors = array(); + + for ($i = 0; $i < $num; $i++) + { + $rgb = substr($lpData, $i * 3, 3); + if (strlen($rgb) < 3) + return false; + + $this->m_arColors[] = (ord($rgb[2]) << 16) + (ord($rgb[1]) << 8) + ord($rgb[0]); + $this->m_nColors++; + } + + return true; + } + + public function toString() + { + $ret = ''; + + for ($i = 0; $i < $this->m_nColors; $i++) + { + $ret .= + chr(($this->m_arColors[$i] & 0x000000FF)) . // R + chr(($this->m_arColors[$i] & 0x0000FF00) >> 8) . // G + chr(($this->m_arColors[$i] & 0x00FF0000) >> 16); // B + } + + return $ret; + } + + public function colorIndex($rgb) + { + $rgb = intval($rgb) & 0xFFFFFF; + $r1 = ($rgb & 0x0000FF); + $g1 = ($rgb & 0x00FF00) >> 8; + $b1 = ($rgb & 0xFF0000) >> 16; + $idx = -1; + + for ($i = 0; $i < $this->m_nColors; $i++) + { + $r2 = ($this->m_arColors[$i] & 0x000000FF); + $g2 = ($this->m_arColors[$i] & 0x0000FF00) >> 8; + $b2 = ($this->m_arColors[$i] & 0x00FF0000) >> 16; + $d = abs($r2 - $r1) + abs($g2 - $g1) + abs($b2 - $b1); + + if (($idx == -1) || ($d < $dif)) + { + $idx = $i; + $dif = $d; + } + } + + return $idx; + } +} + +class gif_file_header +{ + public $m_lpVer, $m_nWidth, $m_nHeight, $m_bGlobalClr, $m_nColorRes; + public $m_bSorted, $m_nTableSize, $m_nBgColor, $m_nPixelRatio; + public $m_colorTable; + + public function __construct() + { + unset($this->m_lpVer, $this->m_nWidth, $this->m_nHeight, $this->m_bGlobalClr, $this->m_nColorRes); + unset($this->m_bSorted, $this->m_nTableSize, $this->m_nBgColor, $this->m_nPixelRatio, $this->m_colorTable); + } + + public function load($lpData, &$hdrLen) + { + $hdrLen = 0; + + $this->m_lpVer = substr($lpData, 0, 6); + if (($this->m_lpVer != 'GIF87a') && ($this->m_lpVer != 'GIF89a')) + return false; + + list ($this->m_nWidth, $this->m_nHeight) = array_values(unpack('v2', substr($lpData, 6, 4))); + + if (!$this->m_nWidth || !$this->m_nHeight) + return false; + + $b = ord(substr($lpData, 10, 1)); + $this->m_bGlobalClr = ($b & 0x80) ? true : false; + $this->m_nColorRes = ($b & 0x70) >> 4; + $this->m_bSorted = ($b & 0x08) ? true : false; + $this->m_nTableSize = 2 << ($b & 0x07); + $this->m_nBgColor = ord(substr($lpData, 11, 1)); + $this->m_nPixelRatio = ord(substr($lpData, 12, 1)); + $hdrLen = 13; + + if ($this->m_bGlobalClr) + { + $this->m_colorTable = new gif_color_table(); + if (!$this->m_colorTable->load(substr($lpData, $hdrLen), $this->m_nTableSize)) + return false; + + $hdrLen += 3 * $this->m_nTableSize; + } + + return true; + } +} + +class gif_image_header +{ + public $m_nLeft, $m_nTop, $m_nWidth, $m_nHeight, $m_bLocalClr; + public $m_bInterlace, $m_bSorted, $m_nTableSize, $m_colorTable; + + public function __construct() + { + unset($this->m_nLeft, $this->m_nTop, $this->m_nWidth, $this->m_nHeight, $this->m_bLocalClr); + unset($this->m_bInterlace, $this->m_bSorted, $this->m_nTableSize, $this->m_colorTable); + } + + public function load($lpData, &$hdrLen) + { + $hdrLen = 0; + + // Get the width/height/etc. from the header. + list ($this->m_nLeft, $this->m_nTop, $this->m_nWidth, $this->m_nHeight) = array_values(unpack('v4', substr($lpData, 0, 8))); + + if (!$this->m_nWidth || !$this->m_nHeight) + return false; + + $b = ord($lpData[8]); + $this->m_bLocalClr = ($b & 0x80) ? true : false; + $this->m_bInterlace = ($b & 0x40) ? true : false; + $this->m_bSorted = ($b & 0x20) ? true : false; + $this->m_nTableSize = 2 << ($b & 0x07); + $hdrLen = 9; + + if ($this->m_bLocalClr) + { + $this->m_colorTable = new gif_color_table(); + if (!$this->m_colorTable->load(substr($lpData, $hdrLen), $this->m_nTableSize)) + return false; + + $hdrLen += 3 * $this->m_nTableSize; + } + + return true; + } +} + +class gif_image +{ + public $m_disp, $m_bUser, $m_bTrans, $m_nDelay, $m_nTrans, $m_lpComm; + public $m_gih, $m_data, $m_lzw; + + public function __construct() + { + unset($this->m_disp, $this->m_bUser, $this->m_nDelay, $this->m_nTrans, $this->m_lpComm, $this->m_data); + $this->m_gih = new gif_image_header(); + $this->m_lzw = new gif_lzw_compression(); + } + + public function load($data, &$datLen) + { + $datLen = 0; + + while (true) + { + $b = ord($data[0]); + $data = substr($data, 1); + $datLen++; + + switch ($b) + { + // Extension... + case 0x21: + $len = 0; + if (!$this->skipExt($data, $len)) + return false; + + $datLen += $len; + break; + + // Image... + case 0x2C: + // Load the header and color table. + $len = 0; + if (!$this->m_gih->load($data, $len)) + return false; + + $data = substr($data, $len); + $datLen += $len; + + // Decompress the data, and ride on home ;). + $len = 0; + if (!($this->m_data = $this->m_lzw->decompress($data, $len))) + return false; + + $data = substr($data, $len); + $datLen += $len; + + if ($this->m_gih->m_bInterlace) + $this->deInterlace(); + + return true; + + case 0x3B: // EOF + default: + return false; + } + } + return false; + } + + public function skipExt(&$data, &$extLen) + { + $extLen = 0; + + $b = ord($data[0]); + $data = substr($data, 1); + $extLen++; + + switch ($b) + { + // Graphic Control... + case 0xF9: + $b = ord($data[1]); + $this->m_disp = ($b & 0x1C) >> 2; + $this->m_bUser = ($b & 0x02) ? true : false; + $this->m_bTrans = ($b & 0x01) ? true : false; + list ($this->m_nDelay) = array_values(unpack('v', substr($data, 2, 2))); + $this->m_nTrans = ord($data[4]); + break; + + // Comment... + case 0xFE: + $this->m_lpComm = substr($data, 1, ord($data[0])); + break; + + // Plain text... + case 0x01: + break; + + // Application... + case 0xFF: + break; + } + + // Skip default as defs may change. + $b = ord($data[0]); + $data = substr($data, 1); + $extLen++; + while ($b > 0) + { + $data = substr($data, $b); + $extLen += $b; + $b = ord($data[0]); + $data = substr($data, 1); + $extLen++; + } + return true; + } + + public function deInterlace() + { + $data = $this->m_data; + + for ($i = 0; $i < 4; $i++) + { + switch ($i) + { + case 0: + $s = 8; + $y = 0; + break; + + case 1: + $s = 8; + $y = 4; + break; + + case 2: + $s = 4; + $y = 2; + break; + + case 3: + $s = 2; + $y = 1; + break; + } + + for (; $y < $this->m_gih->m_nHeight; $y += $s) + { + $lne = substr($this->m_data, 0, $this->m_gih->m_nWidth); + $this->m_data = substr($this->m_data, $this->m_gih->m_nWidth); + + $data = + substr($data, 0, $y * $this->m_gih->m_nWidth) . + $lne . + substr($data, ($y + 1) * $this->m_gih->m_nWidth); + } + } + + $this->m_data = $data; + } +} + +class gif_file +{ + public $header, $image, $data, $loaded; + + public function __construct() + { + $this->data = ''; + $this->loaded = false; + $this->header = new gif_file_header(); + $this->image = new gif_image(); + } + + public function loadFile($filename, $iIndex) + { + if ($iIndex < 0) + return false; + + $this->data = @file_get_contents($filename); + if ($this->data === false) + return false; + + // Tell the header to load up.... + $len = 0; + if (!$this->header->load($this->data, $len)) + return false; + + $this->data = substr($this->data, $len); + + // Keep reading (at least once) so we get to the actual image we're looking for. + for ($j = 0; $j <= $iIndex; $j++) + { + $imgLen = 0; + if (!$this->image->load($this->data, $imgLen)) + return false; + + $this->data = substr($this->data, $imgLen); + } + + $this->loaded = true; + return true; + } + + public function get_png_data($background_color) + { + if (!$this->loaded) + return false; + + // Prepare the color table. + if ($this->image->m_gih->m_bLocalClr) + { + $colors = $this->image->m_gih->m_nTableSize; + $pal = $this->image->m_gih->m_colorTable->toString(); + + if ($background_color != -1) + $background_color = $this->image->m_gih->m_colorTable->colorIndex($background_color); + } + elseif ($this->header->m_bGlobalClr) + { + $colors = $this->header->m_nTableSize; + $pal = $this->header->m_colorTable->toString(); + + if ($background_color != -1) + $background_color = $this->header->m_colorTable->colorIndex($background_color); + } + else + { + $colors = 0; + $background_color = -1; + } + + if ($background_color == -1) + $background_color = $this->header->m_nBgColor; + + $data = &$this->image->m_data; + $header = &$this->image->m_gih; + + $i = 0; + $bmp = ''; + + // Prepare the bitmap itself. + for ($y = 0; $y < $this->header->m_nHeight; $y++) + { + $bmp .= "\x00"; + + for ($x = 0; $x < $this->header->m_nWidth; $x++, $i++) + { + // Is this in the proper range? If so, get the specific pixel data... + if ($x >= $header->m_nLeft && $y >= $header->m_nTop && $x < ($header->m_nLeft + $header->m_nWidth) && $y < ($header->m_nTop + $header->m_nHeight)) + $bmp .= $data{$i}; + // Otherwise, this is background... + else + $bmp .= chr($background_color); + } + } + + $bmp = gzcompress($bmp, 9); + + // Output the basic signature first of all. + $out = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"; + + // Now, we want the header... + $out .= "\x00\x00\x00\x0D"; + $tmp = 'IHDR' . pack('N', (int) $this->header->m_nWidth) . pack('N', (int) $this->header->m_nHeight) . "\x08\x03\x00\x00\x00"; + $out .= $tmp . pack('N', smf_crc32($tmp)); + + // The palette, assuming we have one to speak of... + if ($colors > 0) + { + $out .= pack('N', (int) $colors * 3); + $tmp = 'PLTE' . $pal; + $out .= $tmp . pack('N', smf_crc32($tmp)); + } + + // Do we have any transparency we want to make available? + if ($this->image->m_bTrans && $colors > 0) + { + $out .= pack('N', (int) $colors); + $tmp = 'tRNS'; + + // Stick each color on - full transparency or none. + for ($i = 0; $i < $colors; $i++) + $tmp .= $i == $this->image->m_nTrans ? "\x00" : "\xFF"; + + $out .= $tmp . pack('N', smf_crc32($tmp)); + } + + // Here's the data itself! + $out .= pack('N', strlen($bmp)); + $tmp = 'IDAT' . $bmp; + $out .= $tmp . pack('N', smf_crc32($tmp)); + + // EOF marker... + $out .= "\x00\x00\x00\x00" . 'IEND' . "\xAE\x42\x60\x82"; + + return $out; + } +} + +// crc32 doesn't work as expected on 64-bit functions - make our own. +// http://www.php.net/crc32#79567 +if (!function_exists('smf_crc32')) +{ + function smf_crc32($number) + { + $crc = crc32($number); + + if ($crc & 0x80000000) + { + $crc ^= 0xffffffff; + $crc += 1; + $crc = -$crc; + } + + return $crc; + } +} + +?> \ No newline at end of file diff --git a/Sources/Class-Package.php b/Sources/Class-Package.php new file mode 100644 index 0000000..e5e5399 --- /dev/null +++ b/Sources/Class-Package.php @@ -0,0 +1,1019 @@ +path(path, true) in that instead of an xmlArray + of elements, an array of xmlArray's is returned for use with foreach. + + string xmlArray::create_xml(string path = '.') + - returns the specified path as an xml file. +*/ + +// An xml array. Reads in xml, allows you to access it simply. Version 1.1. +class xmlArray +{ + // The array and debugging output level. + public $array, $debug_level, $trim; + + // Create an xml array. + // the xml data, trim elements?, debugging output level, reserved. + //ie. $xml = new xmlArray(file('data.xml')); + public function __construct($data, $auto_trim = false, $level = null, $is_clone = false) + { + // If we're using this try to get some more memory. + @ini_set('memory_limit', '32M'); + + // Set the debug level. + $this->debug_level = $level !== null ? $level : error_reporting(); + $this->trim = $auto_trim; + + // Is the data already parsed? + if ($is_clone) + { + $this->array = $data; + return; + } + + // Is the input an array? (ie. passed from file()?) + if (is_array($data)) + $data = implode('', $data); + + // Remove any xml declaration or doctype, and parse out comments and CDATA. + $data = preg_replace('//s', '', $this->_to_cdata(preg_replace(array('/^<\?xml.+?\?' . '>/is', '/]+?' . '>/s'), '', $data))); + + // Now parse the xml! + $this->array = $this->_parse($data); + } + + // Get the root element's name. + //ie. echo $element->name(); + public function name() + { + return isset($this->array['name']) ? $this->array['name'] : ''; + } + + // Get a specified element's value or attribute by path. + // the path to the element to fetch, whether to include elements? + //ie. $data = $xml->fetch('html/head/title'); + public function fetch($path, $get_elements = false) + { + // Get the element, in array form. + $array = $this->path($path); + + if ($array === false) + return false; + + // Getting elements into this is a bit complicated... + if ($get_elements && !is_string($array)) + { + $temp = ''; + + // Use the _xml() function to get the xml data. + foreach ($array->array as $val) + { + // Skip the name and any attributes. + if (is_array($val)) + $temp .= $this->_xml($val, null); + } + + // Just get the XML data and then take out the CDATAs. + return $this->_to_cdata($temp); + } + + // Return the value - taking care to pick out all the text values. + return is_string($array) ? $array : $this->_fetch($array->array); + } + + // Get an element, returns a new xmlArray. + // the path to the element to get, always return full result set? (ie. don't contract a single item.) + //ie. $element = $xml->path('html/body'); + public function path($path, $return_full = false) + { + // Split up the path. + $path = explode('/', $path); + + // Start with a base array. + $array = $this->array; + + // For each element in the path. + foreach ($path as $el) + { + // Deal with sets.... + if (strpos($el, '[') !== false) + { + $lvl = (int) substr($el, strpos($el, '[') + 1); + $el = substr($el, 0, strpos($el, '[')); + } + // Find an attribute. + elseif (substr($el, 0, 1) == '@') + { + // It simplifies things if the attribute is already there ;). + if (isset($array[$el])) + return $array[$el]; + else + { + if (function_exists('debug_backtrace')) + { + $trace = debug_backtrace(); + $i = 0; + while ($i < count($trace) && isset($trace[$i]['class']) && $trace[$i]['class'] == get_class($this)) + $i++; + $debug = ' from ' . $trace[$i - 1]['file'] . ' on line ' . $trace[$i - 1]['line']; + } + else + $debug = ''; + + // Cause an error. + if ($this->debug_level & E_NOTICE) + trigger_error('Undefined XML attribute: ' . substr($el, 1) . $debug, E_USER_NOTICE); + return false; + } + } + else + $lvl = null; + + // Find this element. + $array = $this->_path($array, $el, $lvl); + } + + // Clean up after $lvl, for $return_full. + if ($return_full && (!isset($array['name']) || substr($array['name'], -1) != ']')) + $array = array('name' => $el . '[]', $array); + + // Create the right type of class... + $newClass = get_class($this); + + // Return a new xmlArray for the result. + return $array === false ? false : new $newClass($array, $this->trim, $this->debug_level, true); + } + + // Check if an element exists. + // the path to the element to get. + //ie. echo $xml->exists('html/body') ? 'y' : 'n'; + public function exists($path) + { + // Split up the path. + $path = explode('/', $path); + + // Start with a base array. + $array = $this->array; + + // For each element in the path. + foreach ($path as $el) + { + // Deal with sets.... + if (strpos($el, '[') !== false) + { + $lvl = (int) substr($el, strpos($el, '[') + 1); + $el = substr($el, 0, strpos($el, '[')); + } + // Find an attribute. + elseif (substr($el, 0, 1) == '@') + return isset($array[$el]); + else + $lvl = null; + + // Find this element. + $array = $this->_path($array, $el, $lvl, true); + } + + return $array !== false; + } + + // Count the number of occurances of a path. + // the path to search for. + //ie. echo $xml->count('html/head/meta'); + public function count($path) + { + // Get the element, always returning a full set. + $temp = $this->path($path, true); + + // Start at zero, then count up all the numeric keys. + $i = 0; + foreach ($temp->array as $item) + { + if (is_array($item)) + $i++; + } + + return $i; + } + + // Get an array of xmlArray's for use with foreach. + // the path to search for. + //ie. foreach ($xml->set('html/body/p') as $p) + public function set($path) + { + // None as yet, just get the path. + $array = array(); + $xml = $this->path($path, true); + + foreach ($xml->array as $val) + { + // Skip these, they aren't elements. + if (!is_array($val) || $val['name'] == '!') + continue; + + // Create the right type of class... + $newClass = get_class($this); + + // Create a new xmlArray and stick it in the array. + $array[] = new $newClass($val, $this->trim, $this->debug_level, true); + } + + return $array; + } + + // Create an xml file from an xml array. + // the path to the element. (optional) + //ie. echo $this->create_xml() + public function create_xml($path = null) + { + // Was a path specified? If so, use that array. + if ($path !== null) + { + $path = $this->path($path); + + // The path was not found + if ($path === false) + return false; + + $path = $path->array; + } + // Just use the current array. + else + $path = $this->array; + + // Add the xml declaration to the front. + return '' . $this->_xml($path, 0); + } + + // Output the xml in an array form. + // the path to output. + //ie. print_r($xml->to_array()); + public function to_array($path = null) + { + // Are we doing a specific path? + if ($path !== null) + { + $path = $this->path($path); + + // The path was not found + if ($path === false) + return false; + + $path = $path->array; + } + // No, so just use the current array. + else + $path = $this->array; + + return $this->_array($path); + } + + // Parse data into an array. (privately used...) + protected function _parse($data) + { + // Start with an 'empty' array with no data. + $current = array( + ); + + // Loop until we're out of data. + while ($data != '') + { + // Find and remove the next tag. + preg_match('/\A<([\w\-:]+)((?:\s+.+?)?)([\s]?\/)?' . '>/', $data, $match); + if (isset($match[0])) + $data = preg_replace('/' . preg_quote($match[0], '/') . '/s', '', $data, 1); + + // Didn't find a tag? Keep looping.... + if (!isset($match[1]) || $match[1] == '') + { + // If there's no <, the rest is data. + if (strpos($data, '<') === false) + { + $text_value = $this->_from_cdata($data); + $data = ''; + + if ($text_value != '') + $current[] = array( + 'name' => '!', + 'value' => $text_value + ); + } + // If the < isn't immediately next to the current position... more data. + elseif (strpos($data, '<') > 0) + { + $text_value = $this->_from_cdata(substr($data, 0, strpos($data, '<'))); + $data = substr($data, strpos($data, '<')); + + if ($text_value != '') + $current[] = array( + 'name' => '!', + 'value' => $text_value + ); + } + // If we're looking at a with no start, kill it. + elseif (strpos($data, '<') !== false && strpos($data, '<') == 0) + { + if (strpos($data, '<', 1) !== false) + { + $text_value = $this->_from_cdata(substr($data, 0, strpos($data, '<', 1))); + $data = substr($data, strpos($data, '<', 1)); + + if ($text_value != '') + $current[] = array( + 'name' => '!', + 'value' => $text_value + ); + } + else + { + $text_value = $this->_from_cdata($data); + $data = ''; + + if ($text_value != '') + $current[] = array( + 'name' => '!', + 'value' => $text_value + ); + } + } + + // Wait for an actual occurance of an element. + continue; + } + + // Create a new element in the array. + $el = &$current[]; + $el['name'] = $match[1]; + + // If this ISN'T empty, remove the close tag and parse the inner data. + if ((!isset($match[3]) || trim($match[3]) != '/') && (!isset($match[2]) || trim($match[2]) != '/')) + { + // Because PHP 5.2.0+ seems to croak using regex, we'll have to do this the less fun way. + $last_tag_end = strpos($data, ''); + if ($last_tag_end === false) + continue; + + $offset = 0; + while (1 == 1) + { + // Where is the next start tag? + $next_tag_start = strpos($data, '<' . $match[1], $offset); + // If the next start tag is after the last end tag then we've found the right close. + if ($next_tag_start === false || $next_tag_start > $last_tag_end) + break; + + // If not then find the next ending tag. + $next_tag_end = strpos($data, '', $offset); + + // Didn't find one? Then just use the last and sod it. + if ($next_tag_end === false) + break; + else + { + $last_tag_end = $next_tag_end; + $offset = $next_tag_start + 1; + } + } + // Parse the insides. + $inner_match = substr($data, 0, $last_tag_end); + // Data now starts from where this section ends. + $data = substr($data, $last_tag_end + strlen('')); + + if (!empty($inner_match)) + { + // Parse the inner data. + if (strpos($inner_match, '<') !== false) + $el += $this->_parse($inner_match); + elseif (trim($inner_match) != '') + { + $text_value = $this->_from_cdata($inner_match); + if ($text_value != '') + $el[] = array( + 'name' => '!', + 'value' => $text_value + ); + } + } + } + + // If we're dealing with attributes as well, parse them out. + if (isset($match[2]) && $match[2] != '') + { + // Find all the attribute pairs in the string. + preg_match_all('/([\w:]+)="(.+?)"/', $match[2], $attr, PREG_SET_ORDER); + + // Set them as @attribute-name. + foreach ($attr as $match_attr) + $el['@' . $match_attr[1]] = $match_attr[2]; + } + } + + // Return the parsed array. + return $current; + } + + // Get a specific element's xml. (privately used...) + protected function _xml($array, $indent) + { + $indentation = $indent !== null ? ' +' . str_repeat(' ', $indent) : ''; + + // This is a set of elements, with no name... + if (is_array($array) && !isset($array['name'])) + { + $temp = ''; + foreach ($array as $val) + $temp .= $this->_xml($val, $indent); + return $temp; + } + + // This is just text! + if ($array['name'] == '!') + return $indentation . ''; + elseif (substr($array['name'], -2) == '[]') + $array['name'] = substr($array['name'], 0, -2); + + // Start the element. + $output = $indentation . '<' . $array['name']; + + $inside_elements = false; + $output_el = ''; + + // Run through and recurively output all the elements or attrbutes inside this. + foreach ($array as $k => $v) + { + if (substr($k, 0, 1) == '@') + $output .= ' ' . substr($k, 1) . '="' . $v . '"'; + elseif (is_array($v)) + { + $output_el .= $this->_xml($v, $indent === null ? null : $indent + 1); + $inside_elements = true; + } + } + + // Indent, if necessary.... then close the tag. + if ($inside_elements) + $output .= '>' . $output_el . $indentation . ''; + else + $output .= ' />'; + + return $output; + } + + // Return an element as an array... + protected function _array($array) + { + $return = array(); + $text = ''; + foreach ($array as $value) + { + if (!is_array($value) || !isset($value['name'])) + continue; + + if ($value['name'] == '!') + $text .= $value['value']; + else + $return[$value['name']] = $this->_array($value); + } + + if (empty($return)) + return $text; + else + return $return; + } + + // Parse out CDATA tags. (htmlspecialchars them...) + function _to_cdata($data) + { + $inCdata = $inComment = false; + $output = ''; + + $parts = preg_split('~(|)~', $data, -1, PREG_SPLIT_DELIM_CAPTURE); + foreach ($parts as $part) + { + // Handle XML comments. + if (!$inCdata && $part === '') + $inComment = false; + elseif ($inComment) + continue; + + // Handle Cdata blocks. + elseif (!$inComment && $part === '') + $inCdata = false; + elseif ($inCdata) + $output .= htmlentities($part, ENT_QUOTES); + + // Everything else is kept as is. + else + $output .= $part; + } + + return $output; + } + + // Turn the CDATAs back to normal text. + protected function _from_cdata($data) + { + // Get the HTML translation table and reverse it. + $trans_tbl = array_flip(get_html_translation_table(HTML_ENTITIES, ENT_QUOTES)); + + // Translate all the entities out. + $data = strtr(preg_replace_callback('~&#(\d{1,4});~', create_function('$m', 'return chr("$m[1]");'), $data), $trans_tbl); + + return $this->trim ? trim($data) : $data; + } + + // Given an array, return the text from that array. (recursive and privately used.) + protected function _fetch($array) + { + // Don't return anything if this is just a string. + if (is_string($array)) + return ''; + + $temp = ''; + foreach ($array as $text) + { + // This means it's most likely an attribute or the name itself. + if (!isset($text['name'])) + continue; + + // This is text! + if ($text['name'] == '!') + $temp .= $text['value']; + // Another element - dive in ;). + else + $temp .= $this->_fetch($text); + } + + // Return all the bits and pieces we've put together. + return $temp; + } + + // Get a specific array by path, one level down. (privately used...) + protected function _path($array, $path, $level, $no_error = false) + { + // Is $array even an array? It might be false! + if (!is_array($array)) + return false; + + // Asking for *no* path? + if ($path == '' || $path == '.') + return $array; + $paths = explode('|', $path); + + // A * means all elements of any name. + $show_all = in_array('*', $paths); + + $results = array(); + + // Check each element. + foreach ($array as $value) + { + if (!is_array($value) || $value['name'] === '!') + continue; + + if ($show_all || in_array($value['name'], $paths)) + { + // Skip elements before "the one". + if ($level !== null && $level > 0) + $level--; + else + $results[] = $value; + } + } + + // No results found... + if (empty($results)) + { + if (function_exists('debug_backtrace')) + { + $trace = debug_backtrace(); + $i = 0; + while ($i < count($trace) && isset($trace[$i]['class']) && $trace[$i]['class'] == get_class($this)) + $i++; + $debug = ' from ' . $trace[$i - 1]['file'] . ' on line ' . $trace[$i - 1]['line']; + } + else + $debug = ''; + + // Cause an error. + if ($this->debug_level & E_NOTICE && !$no_error) + trigger_error('Undefined XML element: ' . $path . $debug, E_USER_NOTICE); + return false; + } + // Only one result. + elseif (count($results) == 1 || $level !== null) + return $results[0]; + // Return the result set. + else + return $results + array('name' => $path . '[]'); + } +} + +// http://www.faqs.org/rfcs/rfc959.html +if (!class_exists('ftp_connection')) +{ + class ftp_connection + { + public $connection, $error, $last_message, $pasv; + + // Create a new FTP connection... + public function __construct($ftp_server, $ftp_port = 21, $ftp_user = 'anonymous', $ftp_pass = 'ftpclient@simplemachines.org') + { + // Initialize variables. + $this->connection = 'no_connection'; + $this->error = false; + $this->pasv = array(); + + if ($ftp_server !== null) + $this->connect($ftp_server, $ftp_port, $ftp_user, $ftp_pass); + } + + public function connect($ftp_server, $ftp_port = 21, $ftp_user = 'anonymous', $ftp_pass = 'ftpclient@simplemachines.org') + { + if (substr($ftp_server, 0, 6) == 'ftp://') + $ftp_server = substr($ftp_server, 6); + elseif (substr($ftp_server, 0, 7) == 'ftps://') + $ftp_server = 'ssl://' . substr($ftp_server, 7); + if (substr($ftp_server, 0, 7) == 'http://') + $ftp_server = substr($ftp_server, 7); + $ftp_server = strtr($ftp_server, array('/' => '', ':' => '', '@' => '')); + + // Connect to the FTP server. + $this->connection = @fsockopen($ftp_server, $ftp_port, $err, $err, 5); + if (!$this->connection) + { + $this->error = 'bad_server'; + return; + } + + // Get the welcome message... + if (!$this->check_response(220)) + { + $this->error = 'bad_response'; + return; + } + + // Send the username, it should ask for a password. + fwrite($this->connection, 'USER ' . $ftp_user . "\r\n"); + if (!$this->check_response(331)) + { + $this->error = 'bad_username'; + return; + } + + // Now send the password... and hope it goes okay. + fwrite($this->connection, 'PASS ' . $ftp_pass . "\r\n"); + if (!$this->check_response(230)) + { + $this->error = 'bad_password'; + return; + } + } + + public function chdir($ftp_path) + { + if (!is_resource($this->connection)) + return false; + + // No slash on the end, please... + if ($ftp_path !== '/' && substr($ftp_path, -1) === '/') + $ftp_path = substr($ftp_path, 0, -1); + + fwrite($this->connection, 'CWD ' . $ftp_path . "\r\n"); + if (!$this->check_response(250)) + { + $this->error = 'bad_path'; + return false; + } + + return true; + } + + public function chmod($ftp_file, $chmod) + { + if (!is_resource($this->connection)) + return false; + + if ($ftp_file == '') + $ftp_file = '.'; + + // Convert the chmod value from octal (0777) to text ("777"). + fwrite($this->connection, 'SITE CHMOD ' . decoct($chmod) . ' ' . $ftp_file . "\r\n"); + if (!$this->check_response(200)) + { + $this->error = 'bad_file'; + return false; + } + + return true; + } + + public function unlink($ftp_file) + { + // We are actually connected, right? + if (!is_resource($this->connection)) + return false; + + // Delete file X. + fwrite($this->connection, 'DELE ' . $ftp_file . "\r\n"); + if (!$this->check_response(250)) + { + fwrite($this->connection, 'RMD ' . $ftp_file . "\r\n"); + + // Still no love? + if (!$this->check_response(250)) + { + $this->error = 'bad_file'; + return false; + } + } + + return true; + } + + public function check_response($desired) + { + // Wait for a response that isn't continued with -, but don't wait too long. + $time = time(); + do + $this->last_message = fgets($this->connection, 1024); + while ((strlen($this->last_message) < 4 || substr($this->last_message, 0, 1) == ' ' || substr($this->last_message, 3, 1) != ' ') && time() - $time < 5); + + // Was the desired response returned? + return is_array($desired) ? in_array(substr($this->last_message, 0, 3), $desired) : substr($this->last_message, 0, 3) == $desired; + } + + public function passive() + { + // We can't create a passive data connection without a primary one first being there. + if (!is_resource($this->connection)) + return false; + + // Request a passive connection - this means, we'll talk to you, you don't talk to us. + @fwrite($this->connection, 'PASV' . "\r\n"); + $time = time(); + do + $response = fgets($this->connection, 1024); + while (substr($response, 3, 1) != ' ' && time() - $time < 5); + + // If it's not 227, we weren't given an IP and port, which means it failed. + if (substr($response, 0, 4) != '227 ') + { + $this->error = 'bad_response'; + return false; + } + + // Snatch the IP and port information, or die horribly trying... + if (preg_match('~\((\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))\)~', $response, $match) == 0) + { + $this->error = 'bad_response'; + return false; + } + + // This is pretty simple - store it for later use ;). + $this->pasv = array('ip' => $match[1] . '.' . $match[2] . '.' . $match[3] . '.' . $match[4], 'port' => $match[5] * 256 + $match[6]); + + return true; + } + + public function create_file($ftp_file) + { + // First, we have to be connected... very important. + if (!is_resource($this->connection)) + return false; + + // I'd like one passive mode, please! + if (!$this->passive()) + return false; + + // Seems logical enough, so far... + fwrite($this->connection, 'STOR ' . $ftp_file . "\r\n"); + + // Okay, now we connect to the data port. If it doesn't work out, it's probably "file already exists", etc. + $fp = @fsockopen($this->pasv['ip'], $this->pasv['port'], $err, $err, 5); + if (!$fp || !$this->check_response(150)) + { + $this->error = 'bad_file'; + @fclose($fp); + return false; + } + + // This may look strange, but we're just closing it to indicate a zero-byte upload. + fclose($fp); + if (!$this->check_response(226)) + { + $this->error = 'bad_response'; + return false; + } + + return true; + } + + public function list_dir($ftp_path = '', $search = false) + { + // Are we even connected...? + if (!is_resource($this->connection)) + return false; + + // Passive... non-agressive... + if (!$this->passive()) + return false; + + // Get the listing! + fwrite($this->connection, 'LIST -1' . ($search ? 'R' : '') . ($ftp_path == '' ? '' : ' ' . $ftp_path) . "\r\n"); + + // Connect, assuming we've got a connection. + $fp = @fsockopen($this->pasv['ip'], $this->pasv['port'], $err, $err, 5); + if (!$fp || !$this->check_response(array(150, 125))) + { + $this->error = 'bad_response'; + @fclose($fp); + return false; + } + + // Read in the file listing. + $data = ''; + while (!feof($fp)) + $data .= fread($fp, 4096); + fclose($fp); + + // Everything go okay? + if (!$this->check_response(226)) + { + $this->error = 'bad_response'; + return false; + } + + return $data; + } + + public function locate($file, $listing = null) + { + if ($listing === null) + $listing = $this->list_dir('', true); + $listing = explode("\n", $listing); + + @fwrite($this->connection, 'PWD' . "\r\n"); + $time = time(); + do + $response = fgets($this->connection, 1024); + while ($response[3] != ' ' && time() - $time < 5); + + // Check for 257! + if (preg_match('~^257 "(.+?)" ~', $response, $match) != 0) + $current_dir = strtr($match[1], array('""' => '"')); + else + $current_dir = ''; + + for ($i = 0, $n = count($listing); $i < $n; $i++) + { + if (trim($listing[$i]) == '' && isset($listing[$i + 1])) + { + $current_dir = substr(trim($listing[++$i]), 0, -1); + $i++; + } + + // Okay, this file's name is: + $listing[$i] = $current_dir . '/' . trim(strlen($listing[$i]) > 30 ? strrchr($listing[$i], ' ') : $listing[$i]); + + if ($file[0] == '*' && substr($listing[$i], -(strlen($file) - 1)) == substr($file, 1)) + return $listing[$i]; + if (substr($file, -1) == '*' && substr($listing[$i], 0, strlen($file) - 1) == substr($file, 0, -1)) + return $listing[$i]; + if (basename($listing[$i]) == $file || $listing[$i] == $file) + return $listing[$i]; + } + + return false; + } + + public function create_dir($ftp_dir) + { + // We must be connected to the server to do something. + if (!is_resource($this->connection)) + return false; + + // Make this new beautiful directory! + fwrite($this->connection, 'MKD ' . $ftp_dir . "\r\n"); + if (!$this->check_response(257)) + { + $this->error = 'bad_file'; + return false; + } + + return true; + } + + public function detect_path($filesystem_path, $lookup_file = null) + { + $username = ''; + + if (isset($_SERVER['DOCUMENT_ROOT'])) + { + if (preg_match('~^/home[2]?/([^/]+?)/public_html~', $_SERVER['DOCUMENT_ROOT'], $match)) + { + $username = $match[1]; + + $path = strtr($_SERVER['DOCUMENT_ROOT'], array('/home/' . $match[1] . '/' => '', '/home2/' . $match[1] . '/' => '')); + + if (substr($path, -1) == '/') + $path = substr($path, 0, -1); + + if (strlen(dirname($_SERVER['PHP_SELF'])) > 1) + $path .= dirname($_SERVER['PHP_SELF']); + } + elseif (substr($filesystem_path, 0, 9) == '/var/www/') + $path = substr($filesystem_path, 8); + else + $path = strtr(strtr($filesystem_path, array('\\' => '/')), array($_SERVER['DOCUMENT_ROOT'] => '')); + } + else + $path = ''; + + if (is_resource($this->connection) && $this->list_dir($path) == '') + { + $data = $this->list_dir('', true); + + if ($lookup_file === null) + $lookup_file = $_SERVER['PHP_SELF']; + + $found_path = dirname($this->locate('*' . basename(dirname($lookup_file)) . '/' . basename($lookup_file), $data)); + if ($found_path == false) + $found_path = dirname($this->locate(basename($lookup_file))); + if ($found_path != false) + $path = $found_path; + } + elseif (is_resource($this->connection)) + $found_path = true; + + return array($username, $path, isset($found_path)); + } + + public function close() + { + // Goodbye! + fwrite($this->connection, 'QUIT' . "\r\n"); + fclose($this->connection); + + return true; + } + } +} + +?> \ No newline at end of file diff --git a/Sources/DbExtra-mysql.php b/Sources/DbExtra-mysql.php new file mode 100644 index 0000000..22601a8 --- /dev/null +++ b/Sources/DbExtra-mysql.php @@ -0,0 +1,454 @@ + 'smf_db_backup_table', + 'db_optimize_table' => 'smf_db_optimize_table', + 'db_insert_sql' => 'smf_db_insert_sql', + 'db_table_sql' => 'smf_db_table_sql', + 'db_list_tables' => 'smf_db_list_tables', + 'db_get_version' => 'smf_db_get_version', + ); +} + +// Backup $table to $backup_table. +function smf_db_backup_table($table, $backup_table) +{ + global $smcFunc, $db_prefix; + + $table = str_replace('{db_prefix}', $db_prefix, $table); + + // First, get rid of the old table. + $smcFunc['db_query']('', ' + DROP TABLE IF EXISTS {raw:backup_table}', + array( + 'backup_table' => $backup_table, + ) + ); + + // Can we do this the quick way? + $result = $smcFunc['db_query']('', ' + CREATE TABLE {raw:backup_table} LIKE {raw:table}', + array( + 'backup_table' => $backup_table, + 'table' => $table + )); + // If this failed, we go old school. + if ($result) + { + $request = $smcFunc['db_query']('', ' + INSERT INTO {raw:backup_table} + SELECT * + FROM {raw:table}', + array( + 'backup_table' => $backup_table, + 'table' => $table + )); + + // Old school or no school? + if ($request) + return $request; + } + + // At this point, the quick method failed. + $result = $smcFunc['db_query']('', ' + SHOW CREATE TABLE {raw:table}', + array( + 'table' => $table, + ) + ); + list (, $create) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + $create = preg_split('/[\n\r]/', $create); + + $auto_inc = ''; + // Default engine type. + $engine = 'MyISAM'; + $charset = ''; + $collate = ''; + + foreach ($create as $k => $l) + { + // Get the name of the auto_increment column. + if (strpos($l, 'auto_increment')) + $auto_inc = trim($l); + + // For the engine type, see if we can work out what it is. + if (strpos($l, 'ENGINE') !== false || strpos($l, 'TYPE') !== false) + { + // Extract the engine type. + preg_match('~(ENGINE|TYPE)=(\w+)(\sDEFAULT)?(\sCHARSET=(\w+))?(\sCOLLATE=(\w+))?~', $l, $match); + + if (!empty($match[1])) + $engine = $match[1]; + + if (!empty($match[2])) + $engine = $match[2]; + + if (!empty($match[5])) + $charset = $match[5]; + + if (!empty($match[7])) + $collate = $match[7]; + } + + // Skip everything but keys... + if (strpos($l, 'KEY') === false) + unset($create[$k]); + } + + if (!empty($create)) + $create = '( + ' . implode(' + ', $create) . ')'; + else + $create = ''; + + $request = $smcFunc['db_query']('', ' + CREATE TABLE {raw:backup_table} {raw:create} + ENGINE={raw:engine}' . (empty($charset) ? '' : ' CHARACTER SET {raw:charset}' . (empty($collate) ? '' : ' COLLATE {raw:collate}')) . ' + SELECT * + FROM {raw:table}', + array( + 'backup_table' => $backup_table, + 'table' => $table, + 'create' => $create, + 'engine' => $engine, + 'charset' => empty($charset) ? '' : $charset, + 'collate' => empty($collate) ? '' : $collate, + ) + ); + + if ($auto_inc != '') + { + if (preg_match('~\`(.+?)\`\s~', $auto_inc, $match) != 0 && substr($auto_inc, -1, 1) == ',') + $auto_inc = substr($auto_inc, 0, -1); + + $smcFunc['db_query']('', ' + ALTER TABLE {raw:backup_table} + CHANGE COLUMN {raw:column_detail} {raw:auto_inc}', + array( + 'backup_table' => $backup_table, + 'column_detail' => $match[1], + 'auto_inc' => $auto_inc, + ) + ); + } + + return $request; +} + +// Optimize a table - return data freed! +function smf_db_optimize_table($table) +{ + global $smcFunc, $db_name, $db_prefix; + + $table = str_replace('{db_prefix}', $db_prefix, $table); + + // Get how much overhead there is. + $request = $smcFunc['db_query']('', ' + SHOW TABLE STATUS LIKE {string:table_name}', + array( + 'table_name' => str_replace('_', '\_', $table), + ) + ); + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + $data_before = isset($row['Data_free']) ? $row['Data_free'] : 0; + $request = $smcFunc['db_query']('', ' + OPTIMIZE TABLE `{raw:table}`', + array( + 'table' => $table, + ) + ); + if (!$request) + return -1; + + // How much left? + $request = $smcFunc['db_query']('', ' + SHOW TABLE STATUS LIKE {string:table}', + array( + 'table' => str_replace('_', '\_', $table), + ) + ); + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + $total_change = isset($row['Data_free']) && $data_before > $row['Data_free'] ? $data_before / 1024 : 0; + + return $total_change; +} + +// List all the tables in the database. +function smf_db_list_tables($db = false, $filter = false) +{ + global $db_name, $smcFunc; + + $db = $db == false ? $db_name : $db; + $db = trim($db); + $filter = $filter == false ? '' : ' LIKE \'' . $filter . '\''; + + $request = $smcFunc['db_query']('', ' + SHOW TABLES + FROM `{raw:db}` + {raw:filter}', + array( + 'db' => $db[0] == '`' ? strtr($db, array('`' => '')) : $db, + 'filter' => $filter, + ) + ); + $tables = array(); + while ($row = $smcFunc['db_fetch_row']($request)) + $tables[] = $row[0]; + $smcFunc['db_free_result']($request); + + return $tables; +} + +// Get the content (INSERTs) for a table. +function smf_db_insert_sql($tableName) +{ + global $smcFunc, $db_prefix; + + $tableName = str_replace('{db_prefix}', $db_prefix, $tableName); + + // This will be handy... + $crlf = "\r\n"; + + // Get everything from the table. + $result = $smcFunc['db_query']('', ' + SELECT /*!40001 SQL_NO_CACHE */ * + FROM `{raw:table}`', + array( + 'table' => $tableName, + ) + ); + + // The number of rows, just for record keeping and breaking INSERTs up. + $num_rows = $smcFunc['db_num_rows']($result); + $current_row = 0; + + if ($num_rows == 0) + return ''; + + $fields = array_keys($smcFunc['db_fetch_assoc']($result)); + $smcFunc['db_data_seek']($result, 0); + + // Start it off with the basic INSERT INTO. + $data = 'INSERT INTO `' . $tableName . '`' . $crlf . "\t" . '(`' . implode('`, `', $fields) . '`)' . $crlf . 'VALUES '; + + // Loop through each row. + while ($row = $smcFunc['db_fetch_row']($result)) + { + $current_row++; + + // Get the fields in this row... + $field_list = array(); + for ($j = 0; $j < $smcFunc['db_num_fields']($result); $j++) + { + // Try to figure out the type of each field. (NULL, number, or 'string'.) + if (!isset($row[$j])) + $field_list[] = 'NULL'; + elseif (is_numeric($row[$j]) && (int) $row[$j] == $row[$j]) + $field_list[] = $row[$j]; + else + $field_list[] = '\'' . $smcFunc['db_escape_string']($row[$j]) . '\''; + } + + // 'Insert' the data. + $data .= '(' . implode(', ', $field_list) . ')'; + + // All done! + if ($current_row == $num_rows) + $data .= ';' . $crlf; + // Start a new INSERT statement after every 250.... + elseif ($current_row > 249 && $current_row % 250 == 0) + $data .= ';' . $crlf . 'INSERT INTO `' . $tableName . '`' . $crlf . "\t" . '(`' . implode('`, `', $fields) . '`)' . $crlf . 'VALUES '; + // Otherwise, go to the next line. + else + $data .= ',' . $crlf . "\t"; + } + $smcFunc['db_free_result']($result); + + // Return an empty string if there were no rows. + return $num_rows == 0 ? '' : $data; +} + +// Get the schema (CREATE) for a table. +function smf_db_table_sql($tableName) +{ + global $smcFunc, $db_prefix; + + $tableName = str_replace('{db_prefix}', $db_prefix, $tableName); + + // This will be needed... + $crlf = "\r\n"; + + // Drop it if it exists. + $schema_create = 'DROP TABLE IF EXISTS `' . $tableName . '`;' . $crlf . $crlf; + + // Start the create table... + $schema_create .= 'CREATE TABLE `' . $tableName . '` (' . $crlf; + + // Find all the fields. + $result = $smcFunc['db_query']('', ' + SHOW FIELDS + FROM `{raw:table}`', + array( + 'table' => $tableName, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + // Make the CREATE for this column. + $schema_create .= ' `' . $row['Field'] . '` ' . $row['Type'] . ($row['Null'] != 'YES' ? ' NOT NULL' : ''); + + // Add a default...? + if (!empty($row['Default']) || $row['Null'] !== 'YES') + { + // Make a special case of auto-timestamp. + if ($row['Default'] == 'CURRENT_TIMESTAMP') + $schema_create .= ' /*!40102 NOT NULL default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP */'; + // Text shouldn't have a default. + elseif ($row['Default'] !== null) + { + // If this field is numeric the default needs no escaping. + $type = strtolower($row['Type']); + $isNumericColumn = strpos($type, 'int') !== false || strpos($type, 'bool') !== false || strpos($type, 'bit') !== false || strpos($type, 'float') !== false || strpos($type, 'double') !== false || strpos($type, 'decimal') !== false; + + $schema_create .= ' default ' . ($isNumericColumn ? $row['Default'] : '\'' . $smcFunc['db_escape_string']($row['Default']) . '\''); + } + } + + // And now any extra information. (such as auto_increment.) + $schema_create .= ($row['Extra'] != '' ? ' ' . $row['Extra'] : '') . ',' . $crlf; + } + $smcFunc['db_free_result']($result); + + // Take off the last comma. + $schema_create = substr($schema_create, 0, -strlen($crlf) - 1); + + // Find the keys. + $result = $smcFunc['db_query']('', ' + SHOW KEYS + FROM `{raw:table}`', + array( + 'table' => $tableName, + ) + ); + $indexes = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + // IS this a primary key, unique index, or regular index? + $row['Key_name'] = $row['Key_name'] == 'PRIMARY' ? 'PRIMARY KEY' : (empty($row['Non_unique']) ? 'UNIQUE ' : ($row['Comment'] == 'FULLTEXT' || (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT') ? 'FULLTEXT ' : 'KEY ')) . '`' . $row['Key_name'] . '`'; + + // Is this the first column in the index? + if (empty($indexes[$row['Key_name']])) + $indexes[$row['Key_name']] = array(); + + // A sub part, like only indexing 15 characters of a varchar. + if (!empty($row['Sub_part'])) + $indexes[$row['Key_name']][$row['Seq_in_index']] = '`' . $row['Column_name'] . '`(' . $row['Sub_part'] . ')'; + else + $indexes[$row['Key_name']][$row['Seq_in_index']] = '`' . $row['Column_name'] . '`'; + } + $smcFunc['db_free_result']($result); + + // Build the CREATEs for the keys. + foreach ($indexes as $keyname => $columns) + { + // Ensure the columns are in proper order. + ksort($columns); + + $schema_create .= ',' . $crlf . ' ' . $keyname . ' (' . implode($columns, ', ') . ')'; + } + + // Now just get the comment and type... (MyISAM, etc.) + $result = $smcFunc['db_query']('', ' + SHOW TABLE STATUS + LIKE {string:table}', + array( + 'table' => strtr($tableName, array('_' => '\\_', '%' => '\\%')), + ) + ); + $row = $smcFunc['db_fetch_assoc']($result); + $smcFunc['db_free_result']($result); + + // Probably MyISAM.... and it might have a comment. + $schema_create .= $crlf . ') ENGINE=' . (isset($row['Type']) ? $row['Type'] : $row['Engine']) . ($row['Comment'] != '' ? ' COMMENT="' . $row['Comment'] . '"' : ''); + + return $schema_create; +} + +// Get the version number. +function smf_db_get_version() +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT VERSION()', + array( + ) + ); + list ($ver) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $ver; +} + +?> \ No newline at end of file diff --git a/Sources/DbExtra-postgresql.php b/Sources/DbExtra-postgresql.php new file mode 100644 index 0000000..2f5271f --- /dev/null +++ b/Sources/DbExtra-postgresql.php @@ -0,0 +1,328 @@ + 'smf_db_backup_table', + 'db_optimize_table' => 'smf_db_optimize_table', + 'db_insert_sql' => 'smf_db_insert_sql', + 'db_table_sql' => 'smf_db_table_sql', + 'db_list_tables' => 'smf_db_list_tables', + 'db_get_version' => 'smf_db_get_version', + ); +} + +// Backup $table to $backup_table. +function smf_db_backup_table($table, $backup_table) +{ + global $smcFunc, $db_prefix; + + $table = str_replace('{db_prefix}', $db_prefix, $table); + + // Do we need to drop it first? + $tables = smf_db_list_tables(false, $backup_table); + if (!empty($tables)) + $smcFunc['db_query']('', ' + DROP TABLE {raw:backup_table}', + array( + 'backup_table' => $backup_table, + ) + ); + + //!!! Should we create backups of sequences as well? + $smcFunc['db_query']('', ' + CREATE TABLE {raw:backup_table} + ( + LIKE {raw:table} + INCLUDING DEFAULTS + )', + array( + 'backup_table' => $backup_table, + 'table' => $table, + ) + ); + $smcFunc['db_query']('', ' + INSERT INTO {raw:backup_table} + SELECT * FROM {raw:table}', + array( + 'backup_table' => $backup_table, + 'table' => $table, + ) + ); +} + +// Optimize a table - return data freed! +function smf_db_optimize_table($table) +{ + global $smcFunc, $db_prefix; + + $table = str_replace('{db_prefix}', $db_prefix, $table); + + $request = $smcFunc['db_query']('', ' + VACUUM ANALYZE {raw:table}', + array( + 'table' => $table, + ) + ); + if (!$request) + return -1; + + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + if (isset($row['Data_free'])) + return $row['Data_free'] / 1024; + else + return 0; +} + +// List all the tables in the database. +function smf_db_list_tables($db = false, $filter = false) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT tablename + FROM pg_tables + WHERE schemaname = {string:schema_public}' . ($filter == false ? '' : ' + AND tablename LIKE {string:filter}') . ' + ORDER BY tablename', + array( + 'schema_public' => 'public', + 'filter' => $filter, + ) + ); + + $tables = array(); + while ($row = $smcFunc['db_fetch_row']($request)) + $tables[] = $row[0]; + $smcFunc['db_free_result']($request); + + return $tables; +} + +// Get the content (INSERTs) for a table. +function smf_db_insert_sql($tableName) +{ + global $smcFunc, $db_prefix; + + $tableName = str_replace('{db_prefix}', $db_prefix, $tableName); + + // This will be handy... + $crlf = "\r\n"; + + // Get everything from the table. + $result = $smcFunc['db_query']('', ' + SELECT * + FROM {raw:table}', + array( + 'table' => $tableName, + ) + ); + + // The number of rows, just for record keeping and breaking INSERTs up. + $num_rows = $smcFunc['db_num_rows']($result); + + if ($num_rows == 0) + return ''; + + $fields = array_keys($smcFunc['db_fetch_assoc']($result)); + $smcFunc['db_data_seek']($result, 0); + + // Start it off with the basic INSERT INTO. + $data = ''; + $insert_msg = $crlf . 'INSERT INTO ' . $tableName . $crlf . "\t" . '(' . implode(', ', $fields) . ')' . $crlf . 'VALUES ' . $crlf . "\t"; + + // Loop through each row. + while ($row = $smcFunc['db_fetch_row']($result)) + { + // Get the fields in this row... + $field_list = array(); + for ($j = 0; $j < $smcFunc['db_num_fields']($result); $j++) + { + // Try to figure out the type of each field. (NULL, number, or 'string'.) + if (!isset($row[$j])) + $field_list[] = 'NULL'; + elseif (is_numeric($row[$j]) && (int) $row[$j] == $row[$j]) + $field_list[] = $row[$j]; + else + $field_list[] = '\'' . $smcFunc['db_escape_string']($row[$j]) . '\''; + } + + // 'Insert' the data. + $data .= $insert_msg . '(' . implode(', ', $field_list) . ');'; + } + $smcFunc['db_free_result']($result); + + // Return an empty string if there were no rows. + return $num_rows == 0 ? '' : $data; +} + +// Get the schema (CREATE) for a table. +function smf_db_table_sql($tableName) +{ + global $smcFunc, $db_prefix; + + $tableName = str_replace('{db_prefix}', $db_prefix, $tableName); + + // This will be needed... + $crlf = "\r\n"; + + // Start the create table... + $schema_create = 'CREATE TABLE ' . $tableName . ' (' . $crlf; + $index_create = ''; + $seq_create = ''; + + // Find all the fields. + $result = $smcFunc['db_query']('', ' + SELECT column_name, column_default, is_nullable, data_type, character_maximum_length + FROM information_schema.columns + WHERE table_name = {string:table} + ORDER BY ordinal_position', + array( + 'table' => $tableName, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + if ($row['data_type'] == 'character varying') + $row['data_type'] = 'varchar'; + elseif ($row['data_type'] == 'character') + $row['data_type'] = 'char'; + if ($row['character_maximum_length']) + $row['data_type'] .= '(' . $row['character_maximum_length'] . ')'; + + // Make the CREATE for this column. + $schema_create .= ' "' . $row['column_name'] . '" ' . $row['data_type'] . ($row['is_nullable'] != 'YES' ? ' NOT NULL' : ''); + + // Add a default...? + if (trim($row['column_default']) != '') + { + $schema_create .= ' default ' . $row['column_default'] . ''; + + // Auto increment? + if (preg_match('~nextval\(\'(.+?)\'(.+?)*\)~i', $row['column_default'], $matches) != 0) + { + // Get to find the next variable first! + $count_req = $smcFunc['db_query']('', ' + SELECT MAX("{raw:column}") + FROM {raw:table}', + array( + 'column' => $row['column_name'], + 'table' => $tableName, + ) + ); + list ($max_ind) = $smcFunc['db_fetch_row']($count_req); + $smcFunc['db_free_result']($count_req); + // Get the right bloody start! + $seq_create .= 'CREATE SEQUENCE ' . $matches[1] . ' START WITH ' . ($max_ind + 1) . ';' . $crlf . $crlf; + } + } + + $schema_create .= ',' . $crlf; + } + $smcFunc['db_free_result']($result); + + // Take off the last comma. + $schema_create = substr($schema_create, 0, -strlen($crlf) - 1); + + $result = $smcFunc['db_query']('', ' + SELECT CASE WHEN i.indisprimary THEN 1 ELSE 0 END AS is_primary, pg_get_indexdef(i.indexrelid) AS inddef + FROM pg_class AS c + INNER JOIN pg_index AS i ON (i.indrelid = c.oid) + INNER JOIN pg_class AS c2 ON (c2.oid = i.indexrelid) + WHERE c.relname = {string:table}', + array( + 'table' => $tableName, + ) + ); + $indexes = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + if ($row['is_primary']) + { + if (preg_match('~\(([^\)]+?)\)~i', $row['inddef'], $matches) == 0) + continue; + + $index_create .= $crlf . 'ALTER TABLE ' . $tableName . ' ADD PRIMARY KEY ("' . $matches[1] . '");'; + } + else + $index_create .= $crlf . $row['inddef'] . ';'; + } + $smcFunc['db_free_result']($result); + + // Finish it off! + $schema_create .= $crlf . ');'; + + return $seq_create . $schema_create . $index_create; +} + +// Get the version number. +function smf_db_get_version() +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SHOW server_version', + array( + ) + ); + list ($ver) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $ver; +} + +?> \ No newline at end of file diff --git a/Sources/DbExtra-sqlite.php b/Sources/DbExtra-sqlite.php new file mode 100644 index 0000000..4964a22 --- /dev/null +++ b/Sources/DbExtra-sqlite.php @@ -0,0 +1,346 @@ + 'smf_db_backup_table', + 'db_optimize_table' => 'smf_db_optimize_table', + 'db_insert_sql' => 'smf_db_insert_sql', + 'db_table_sql' => 'smf_db_table_sql', + 'db_list_tables' => 'smf_db_list_tables', + 'db_get_backup' => 'smf_db_get_backup', + 'db_get_version' => 'smf_db_get_version', + ); +} + +// Backup $table to $backup_table. +function smf_db_backup_table($table, $backup_table) +{ + global $smcFunc, $db_prefix; + + $table = str_replace('{db_prefix}', $db_prefix, $table); + + $result = $smcFunc['db_query']('', ' + SELECT sql + FROM sqlite_master + WHERE type = {string:txttable} + AND name = {string:table}', + array( + 'table' => $table, + 'txttable' => 'table' + ) + ); + list ($create) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + $create = preg_split('/[\n\r]/', $create); + $auto_inc = ''; + + // Remove the first line and check to see if the second one contain useless info. + unset($create[0]); + if (trim($create[1]) == '(') + unset($create[1]); + if (trim($create[count($create)]) == ')') + unset($create[count($create)]); + + foreach ($create as $k => $l) + { + // Get the name of the auto_increment column. + if (strpos($l, 'primary') || strpos($l, 'PRIMARY')) + $auto_inc = trim($l); + + // Skip everything but keys... + if ((strpos($l, 'KEY') !== false && strpos($l, 'PRIMARY KEY') === false) || strpos($l, $table) !== false || strpos(trim($l), 'PRIMARY KEY') === 0) + unset($create[$k]); + } + + if (!empty($create)) + $create = '( + ' . implode(' + ', $create) . ')'; + else + $create = ''; + + // Is there an extra junk at the end? + if (substr($create, -2, 1) == ',') + $create = substr($create, 0, -2) . ')'; + if (substr($create, -2) == '))') + $create = substr($create, 0, -1); + + $smcFunc['db_query']('', ' + DROP TABLE {raw:backup_table}', + array( + 'backup_table' => $backup_table, + 'db_error_skip' => true, + ) + ); + + $request = $smcFunc['db_quote'](' + CREATE TABLE {raw:backup_table} {raw:create}', + array( + 'backup_table' => $backup_table, + 'create' => $create, + )); + + $smcFunc['db_query']('', ' + CREATE TABLE {raw:backup_table} {raw:create}', + array( + 'backup_table' => $backup_table, + 'create' => $create, + )); + + $request = $smcFunc['db_query']('', ' + INSERT INTO {raw:backup_table} + SELECT * + FROM {raw:table}', + array( + 'backup_table' => $backup_table, + 'table' => $table, + )); + + return $request; +} + +// Optimize a table - return data freed! +function smf_db_optimize_table($table) +{ + global $smcFunc, $db_prefix; + + $table = str_replace('{db_prefix}', $db_prefix, $table); + + $request = $smcFunc['db_query']('', ' + VACUUM {raw:table}', + array( + 'table' => $table, + ) + ); + if (!$request) + return -1; + + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // The function returns nothing. + return 0; +} + +// List all the tables in the database. +function smf_db_list_tables($db = false, $filter = false) +{ + global $smcFunc; + + $filter = $filter == false ? '' : ' AND name LIKE \'' . str_replace("\_", "_", $filter) . '\''; + + $request = $smcFunc['db_query']('', ' + SELECT name + FROM sqlite_master + WHERE type = {string:type} + {raw:filter} + ORDER BY name', + array( + 'type' => 'table', + 'filter' => $filter, + ) + ); + $tables = array(); + while ($row = $smcFunc['db_fetch_row']($request)) + $tables[] = $row[0]; + $smcFunc['db_free_result']($request); + + return $tables; +} + +// Get the content (INSERTs) for a table. +function smf_db_insert_sql($tableName) +{ + global $smcFunc, $db_prefix; + + $tableName = str_replace('{db_prefix}', $db_prefix, $tableName); + + // This will be handy... + $crlf = "\r\n"; + + // Get everything from the table. + $result = $smcFunc['db_query']('', ' + SELECT * + FROM {raw:table}', + array( + 'table' => $tableName, + ) + ); + + // The number of rows, just for record keeping and breaking INSERTs up. + $num_rows = $smcFunc['db_num_rows']($result); + + if ($num_rows == 0) + return ''; + + $fields = array_keys($smcFunc['db_fetch_assoc']($result)); + + // SQLite fetches an array so we need to filter out the numberic index for the columns. + foreach ($fields as $key => $name) + if (is_numeric($name)) + unset($fields[$key]); + + $smcFunc['db_data_seek']($result, 0); + + // Start it off with the basic INSERT INTO. + $data = 'BEGIN TRANSACTION;' . $crlf; + + // Loop through each row. + while ($row = $smcFunc['db_fetch_row']($result)) + { + // Get the fields in this row... + $field_list = array(); + for ($j = 0; $j < $smcFunc['db_num_fields']($result); $j++) + { + // Try to figure out the type of each field. (NULL, number, or 'string'.) + if (!isset($row[$j])) + $field_list[] = 'NULL'; + elseif (is_numeric($row[$j]) && (int) $row[$j] == $row[$j]) + $field_list[] = $row[$j]; + else + $field_list[] = '\'' . $smcFunc['db_escape_string']($row[$j]) . '\''; + } + $data .= 'INSERT INTO ' . $tableName . ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $field_list) . ');' . $crlf; + } + $smcFunc['db_free_result']($result); + + // Return an empty string if there were no rows. + return $num_rows == 0 ? '' : $data . 'COMMIT;' . $crlf; +} + +// Get the schema (CREATE) for a table. +function smf_db_table_sql($tableName) +{ + global $smcFunc, $db_prefix; + + $tableName = str_replace('{db_prefix}', $db_prefix, $tableName); + + // This will be needed... + $crlf = "\r\n"; + + // Start the create table... + $schema_create = ''; + $index_create = ''; + + // Let's get the create statement directly from SQLite. + $result = $smcFunc['db_query']('', ' + SELECT sql + FROM sqlite_master + WHERE type = {string:type} + AND name = {string:table_name}', + array( + 'type' => 'table', + 'table_name' => $tableName, + ) + ); + list ($schema_create) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + // Now the indexes. + $result = $smcFunc['db_query']('', ' + SELECT sql + FROM sqlite_master + WHERE type = {string:type} + AND tbl_name = {string:table_name}', + array( + 'type' => 'index', + 'table_name' => $tableName, + ) + ); + $indexes = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + if (trim($row['sql']) != '') + $indexes[] = $row['sql']; + $smcFunc['db_free_result']($result); + + $index_create .= implode(';' . $crlf, $indexes); + $schema_create = empty($indexes) ? rtrim($schema_create) : $schema_create . ';' . $crlf . $crlf; + + return $schema_create . $index_create; +} + +// Get the version number. +function smf_db_get_version() +{ + return sqlite_libversion(); +} + +// Simple return the database - and die! +function smf_db_get_backup() +{ + global $db_name; + + $db_file = substr($db_name, -3) === '.db' ? $db_name : $db_name . '.db'; + + // Add more info if zipped... + $ext = ''; + if (isset($_REQUEST['compress']) && function_exists('gzencode')) + $ext = '.gz'; + + // Do the remaining headers. + header('Content-Disposition: attachment; filename="' . $db_file . $ext . '"'); + header('Cache-Control: private'); + header('Connection: close'); + + // Literally dump the contents. Try reading the file first. + if (@readfile($db_file) == null) + echo file_get_contents($db_file); + + obExit(false); +} + +?> \ No newline at end of file diff --git a/Sources/DbPackages-mysql.php b/Sources/DbPackages-mysql.php new file mode 100644 index 0000000..f5e234b --- /dev/null +++ b/Sources/DbPackages-mysql.php @@ -0,0 +1,582 @@ + Size of column (If applicable) - for example 255 for a large varchar, 10 for an int etc. If not + set SMF will pick a size. + + 'default' = Default value - do not set if no default required. + + 'null' => Can it be null (true or false) - if not set default will be false. + + 'auto' => Set to true to make it an auto incrementing column. Set to a numerical value to set + from what it should begin counting. + - Adds indexes as specified within indexes parameter. Each index should be a member of $indexes. Values are: + + 'name' => Index name (If left empty SMF will generate). + + 'type' => Type of index. Choose from 'primary', 'unique' or 'index'. If not set will default to 'index'. + + 'columns' => Array containing columns that form part of key - in the order the index is to be created. + - parameters: (None yet) + - if_exists values: + + 'ignore' will do nothing if the table exists. (And will return true) + + 'overwrite' will drop any existing table of the same name. + + 'error' will return false if the table already exists. + +*/ + +// Add the file functions to the $smcFunc array. +function db_packages_init() +{ + global $smcFunc, $reservedTables, $db_package_log, $db_prefix; + + if (!isset($smcFunc['db_create_table']) || $smcFunc['db_create_table'] != 'smf_db_create_table') + { + $smcFunc += array( + 'db_add_column' => 'smf_db_add_column', + 'db_add_index' => 'smf_db_add_index', + 'db_calculate_type' => 'smf_db_calculate_type', + 'db_change_column' => 'smf_db_change_column', + 'db_create_table' => 'smf_db_create_table', + 'db_drop_table' => 'smf_db_drop_table', + 'db_table_structure' => 'smf_db_table_structure', + 'db_list_columns' => 'smf_db_list_columns', + 'db_list_indexes' => 'smf_db_list_indexes', + 'db_remove_column' => 'smf_db_remove_column', + 'db_remove_index' => 'smf_db_remove_index', + ); + $db_package_log = array(); + } + + // We setup an array of SMF tables we can't do auto-remove on - in case a mod writer cocks it up! + $reservedTables = array('admin_info_files', 'approval_queue', 'attachments', 'ban_groups', 'ban_items', + 'board_permissions', 'boards', 'calendar', 'calendar_holidays', 'categories', 'collapsed_categories', + 'custom_fields', 'group_moderators', 'log_actions', 'log_activity', 'log_banned', 'log_boards', + 'log_digest', 'log_errors', 'log_floodcontrol', 'log_group_requests', 'log_karma', 'log_mark_read', + 'log_notify', 'log_online', 'log_packages', 'log_polls', 'log_reported', 'log_reported_comments', + 'log_scheduled_tasks', 'log_search_messages', 'log_search_results', 'log_search_subjects', + 'log_search_topics', 'log_topics', 'mail_queue', 'membergroups', 'members', 'message_icons', + 'messages', 'moderators', 'package_servers', 'permission_profiles', 'permissions', 'personal_messages', + 'pm_recipients', 'poll_choices', 'polls', 'scheduled_tasks', 'sessions', 'settings', 'smileys', + 'themes', 'topics'); + foreach ($reservedTables as $k => $table_name) + $reservedTables[$k] = strtolower($db_prefix . $table_name); + + // We in turn may need the extra stuff. + db_extend('extra'); +} + +// Create a table. +function smf_db_create_table($table_name, $columns, $indexes = array(), $parameters = array(), $if_exists = 'ignore', $error = 'fatal') +{ + global $reservedTables, $smcFunc, $db_package_log, $db_prefix, $db_character_set; + + // Strip out the table name, we might not need it in some cases + $real_prefix = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix; + + // With or without the database name, the fullname looks like this. + $full_table_name = str_replace('{db_prefix}', $real_prefix, $table_name); + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // First - no way do we touch SMF tables. + if (in_array(strtolower($table_name), $reservedTables)) + return false; + + // Log that we'll want to remove this on uninstall. + $db_package_log[] = array('remove_table', $table_name); + + // Slightly easier on MySQL than the others... + $tables = $smcFunc['db_list_tables'](); + if (in_array($full_table_name, $tables)) + { + // This is a sad day... drop the table? If not, return false (error) by default. + if ($if_exists == 'overwrite') + $smcFunc['db_drop_table']($table_name); + else + return $if_exists == 'ignore'; + } + + // Righty - let's do the damn thing! + $table_query = 'CREATE TABLE ' . $table_name . "\n" . '('; + foreach ($columns as $column) + { + // Auto increment is easy here! + if (!empty($column['auto'])) + { + $default = 'auto_increment'; + } + elseif (isset($column['default']) && $column['default'] !== null) + $default = 'default \'' . $smcFunc['db_escape_string']($column['default']) . '\''; + else + $default = ''; + + // Sort out the size... and stuff... + $column['size'] = isset($column['size']) && is_numeric($column['size']) ? $column['size'] : null; + list ($type, $size) = $smcFunc['db_calculate_type']($column['type'], $column['size']); + + // Allow unsigned integers (mysql only) + $unsigned = in_array($type, array('int', 'tinyint', 'smallint', 'mediumint', 'bigint')) && !empty($column['unsigned']) ? 'unsigned ' : ''; + + if ($size !== null) + $type = $type . '(' . $size . ')'; + + // Now just put it together! + $table_query .= "\n\t`" .$column['name'] . '` ' . $type . ' ' . (!empty($unsigned) ? $unsigned : '') . (!empty($column['null']) ? '' : 'NOT NULL') . ' ' . $default . ','; + } + + // Loop through the indexes next... + foreach ($indexes as $index) + { + $columns = implode(',', $index['columns']); + + // Is it the primary? + if (isset($index['type']) && $index['type'] == 'primary') + $table_query .= "\n\t" . 'PRIMARY KEY (' . implode(',', $index['columns']) . '),'; + else + { + if (empty($index['name'])) + $index['name'] = implode('_', $index['columns']); + $table_query .= "\n\t" . (isset($index['type']) && $index['type'] == 'unique' ? 'UNIQUE' : 'KEY') . ' ' . $index['name'] . ' (' . $columns . '),'; + } + } + + // No trailing commas! + if (substr($table_query, -1) == ',') + $table_query = substr($table_query, 0, -1); + + $table_query .= ') ENGINE=MyISAM'; + if (!empty($db_character_set) && $db_character_set == 'utf8') + $table_query .= ' DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci'; + + // Create the table! + $smcFunc['db_query']('', $table_query, + array( + 'security_override' => true, + ) + ); +} + +// Drop a table. +function smf_db_drop_table($table_name, $parameters = array(), $error = 'fatal') +{ + global $reservedTables, $smcFunc, $db_prefix; + + // After stripping away the database name, this is what's left. + $real_prefix = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix; + + // Get some aliases. + $full_table_name = str_replace('{db_prefix}', $real_prefix, $table_name); + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // God no - dropping one of these = bad. + if (in_array(strtolower($table_name), $reservedTables)) + return false; + + // Does it exist? + if (in_array($full_table_name, $smcFunc['db_list_tables']())) + { + $query = 'DROP TABLE ' . $table_name; + $smcFunc['db_query']('', + $query, + array( + 'security_override' => true, + ) + ); + + return true; + } + + // Otherwise do 'nout. + return false; +} + +// Add a column. +function smf_db_add_column($table_name, $column_info, $parameters = array(), $if_exists = 'update', $error = 'fatal') +{ + global $smcFunc, $db_package_log, $txt, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // Log that we will want to uninstall this! + $db_package_log[] = array('remove_column', $table_name, $column_info['name']); + + // Does it exist - if so don't add it again! + $columns = $smcFunc['db_list_columns']($table_name, false); + foreach ($columns as $column) + if ($column == $column_info['name']) + { + // If we're going to overwrite then use change column. + if ($if_exists == 'update') + return $smcFunc['db_change_column']($table_name, $column_info['name'], $column_info); + else + return false; + } + + // Get the specifics... + $column_info['size'] = isset($column_info['size']) && is_numeric($column_info['size']) ? $column_info['size'] : null; + list ($type, $size) = $smcFunc['db_calculate_type']($column_info['type'], $column_info['size']); + + // Allow unsigned integers (mysql only) + $unsigned = in_array($type, array('int', 'tinyint', 'smallint', 'mediumint', 'bigint')) && !empty($column_info['unsigned']) ? 'unsigned ' : ''; + + if ($size !== null) + $type = $type . '(' . $size . ')'; + + // Now add the thing! + $query = ' + ALTER TABLE ' . $table_name . ' + ADD `' . $column_info['name'] . '` ' . $type . ' ' . (!empty($unsigned) ? $unsigned : '') . (empty($column_info['null']) ? 'NOT NULL' : '') . ' ' . + (!isset($column_info['default']) ? '' : 'default \'' . $smcFunc['db_escape_string']($column_info['default']) . '\'') . ' ' . + (empty($column_info['auto']) ? '' : 'auto_increment primary key') . ' '; + $smcFunc['db_query']('', $query, + array( + 'security_override' => true, + ) + ); + + return true; +} + +// Remove a column. +function smf_db_remove_column($table_name, $column_name, $parameters = array(), $error = 'fatal') +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // Does it exist? + $columns = $smcFunc['db_list_columns']($table_name, true); + foreach ($columns as $column) + if ($column['name'] == $column_name) + { + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + DROP COLUMN ' . $column_name, + array( + 'security_override' => true, + ) + ); + + return true; + } + + // If here we didn't have to work - joy! + return false; +} + +// Change a column. +function smf_db_change_column($table_name, $old_column, $column_info, $parameters = array(), $error = 'fatal') +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // Check it does exist! + $columns = $smcFunc['db_list_columns']($table_name, true); + $old_info = null; + foreach ($columns as $column) + if ($column['name'] == $old_column) + $old_info = $column; + + // Nothing? + if ($old_info == null) + return false; + + // Get the right bits. + if (!isset($column_info['name'])) + $column_info['name'] = $old_column; + if (!isset($column_info['default'])) + $column_info['default'] = $old_info['default']; + if (!isset($column_info['null'])) + $column_info['null'] = $old_info['null']; + if (!isset($column_info['auto'])) + $column_info['auto'] = $old_info['auto']; + if (!isset($column_info['type'])) + $column_info['type'] = $old_info['type']; + if (!isset($column_info['size']) || !is_numeric($column_info['size'])) + $column_info['size'] = $old_info['size']; + if (!isset($column_info['unsigned']) || !in_array($column_info['type'], array('int', 'tinyint', 'smallint', 'mediumint', 'bigint'))) + $column_info['unsigned'] = ''; + + list ($type, $size) = $smcFunc['db_calculate_type']($column_info['type'], $column_info['size']); + + // Allow for unsigned integers (mysql only) + $unsigned = in_array($type, array('int', 'tinyint', 'smallint', 'mediumint', 'bigint')) && !empty($column_info['unsigned']) ? 'unsigned ' : ''; + + if ($size !== null) + $type = $type . '(' . $size . ')'; + + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + CHANGE COLUMN `' . $old_column . '` `' . $column_info['name'] . '` ' . $type . ' ' . (!empty($unsigned) ? $unsigned : '') . (empty($column_info['null']) ? 'NOT NULL' : '') . ' ' . + (!isset($column_info['default']) ? '' : 'default \'' . $smcFunc['db_escape_string']($column_info['default']) . '\'') . ' ' . + (empty($column_info['auto']) ? '' : 'auto_increment') . ' ', + array( + 'security_override' => true, + ) + ); +} + +// Add an index. +function smf_db_add_index($table_name, $index_info, $parameters = array(), $if_exists = 'update', $error = 'fatal') +{ + global $smcFunc, $db_package_log, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // No columns = no index. + if (empty($index_info['columns'])) + return false; + $columns = implode(',', $index_info['columns']); + + // No name - make it up! + if (empty($index_info['name'])) + { + // No need for primary. + if (isset($index_info['type']) && $index_info['type'] == 'primary') + $index_info['name'] = ''; + else + $index_info['name'] = implode('_', $index_info['columns']); + } + else + $index_info['name'] = $index_info['name']; + + // Log that we are going to want to remove this! + $db_package_log[] = array('remove_index', $table_name, $index_info['name']); + + // Let's get all our indexes. + $indexes = $smcFunc['db_list_indexes']($table_name, true); + // Do we already have it? + foreach ($indexes as $index) + { + if ($index['name'] == $index_info['name'] || ($index['type'] == 'primary' && isset($index_info['type']) && $index_info['type'] == 'primary')) + { + // If we want to overwrite simply remove the current one then continue. + if ($if_exists != 'update' || $index['type'] == 'primary') + return false; + else + $smcFunc['db_remove_index']($table_name, $index_info['name']); + } + } + + // If we're here we know we don't have the index - so just add it. + if (!empty($index_info['type']) && $index_info['type'] == 'primary') + { + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + ADD PRIMARY KEY (' . $columns . ')', + array( + 'security_override' => true, + ) + ); + } + else + { + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + ADD ' . (isset($index_info['type']) && $index_info['type'] == 'unique' ? 'UNIQUE' : 'INDEX') . ' ' . $index_info['name'] . ' (' . $columns . ')', + array( + 'security_override' => true, + ) + ); + } +} + +// Remove an index. +function smf_db_remove_index($table_name, $index_name, $parameters = array(), $error = 'fatal') +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // Better exist! + $indexes = $smcFunc['db_list_indexes']($table_name, true); + + foreach ($indexes as $index) + { + // If the name is primary we want the primary key! + if ($index['type'] == 'primary' && $index_name == 'primary') + { + // Dropping primary key? + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + DROP PRIMARY KEY', + array( + 'security_override' => true, + ) + ); + + return true; + } + if ($index['name'] == $index_name) + { + // Drop the bugger... + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + DROP INDEX ' . $index_name, + array( + 'security_override' => true, + ) + ); + + return true; + } + } + + // Not to be found ;( + return false; +} + +// Get the schema formatted name for a type. +function smf_db_calculate_type($type_name, $type_size = null, $reverse = false) +{ + // MySQL is actually the generic baseline. + return array($type_name, $type_size); +} + +// Get table structure. +function smf_db_table_structure($table_name, $parameters = array()) +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + return array( + 'name' => $table_name, + 'columns' => $smcFunc['db_list_columns']($table_name, true), + 'indexes' => $smcFunc['db_list_indexes']($table_name, true), + ); +} + +// Return column information for a table. +function smf_db_list_columns($table_name, $detail = false, $parameters = array()) +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + $result = $smcFunc['db_query']('', ' + SHOW FIELDS + FROM {raw:table_name}', + array( + 'table_name' => substr($table_name, 0, 1) == '`' ? $table_name : '`' . $table_name . '`', + ) + ); + $columns = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + if (!$detail) + { + $columns[] = $row['Field']; + } + else + { + // Is there an auto_increment? + $auto = strpos($row['Extra'], 'auto_increment') !== false ? true : false; + + // Can we split out the size? + if (preg_match('~(.+?)\s*\((\d+)\)(?:(?:\s*)?(unsigned))?~i', $row['Type'], $matches) === 1) + { + $type = $matches[1]; + $size = $matches[2]; + if (!empty($matches[3]) && $matches[3] == 'unsigned') + $unsigned = true; + } + else + { + $type = $row['Type']; + $size = null; + } + + $columns[$row['Field']] = array( + 'name' => $row['Field'], + 'null' => $row['Null'] != 'YES' ? false : true, + 'default' => isset($row['Default']) ? $row['Default'] : null, + 'type' => $type, + 'size' => $size, + 'auto' => $auto, + ); + + if (isset($unsigned)) + { + $columns[$row['Field']]['unsigned'] = $unsigned; + unset($unsigned); + } + } + } + $smcFunc['db_free_result']($result); + + return $columns; +} + +// What about some index information? +function smf_db_list_indexes($table_name, $detail = false, $parameters = array()) +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + $result = $smcFunc['db_query']('', ' + SHOW KEYS + FROM {raw:table_name}', + array( + 'table_name' => substr($table_name, 0, 1) == '`' ? $table_name : '`' . $table_name . '`', + ) + ); + $indexes = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + if (!$detail) + $indexes[] = $row['Key_name']; + else + { + // What is the type? + if ($row['Key_name'] == 'PRIMARY') + $type = 'primary'; + elseif (empty($row['Non_unique'])) + $type = 'unique'; + elseif (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT') + $type = 'fulltext'; + else + $type = 'index'; + + // This is the first column we've seen? + if (empty($indexes[$row['Key_name']])) + { + $indexes[$row['Key_name']] = array( + 'name' => $row['Key_name'], + 'type' => $type, + 'columns' => array(), + ); + } + + // Is it a partial index? + if (!empty($row['Sub_part'])) + $indexes[$row['Key_name']]['columns'][] = $row['Column_name'] . '(' . $row['Sub_part'] . ')'; + else + $indexes[$row['Key_name']]['columns'][] = $row['Column_name']; + } + } + $smcFunc['db_free_result']($result); + + return $indexes; +} + +?> \ No newline at end of file diff --git a/Sources/DbPackages-postgresql.php b/Sources/DbPackages-postgresql.php new file mode 100644 index 0000000..bdf4b70 --- /dev/null +++ b/Sources/DbPackages-postgresql.php @@ -0,0 +1,746 @@ + Size of column (If applicable) - for example 255 for a large varchar, 10 for an int etc. If not + set SMF will pick a size. + + 'default' = Default value - do not set if no default required. + + 'null' => Can it be null (true or false) - if not set default will be false. + + 'auto' => Set to true to make it an auto incrementing column. Set to a numerical value to set + from what it should begin counting. + - Adds indexes as specified within indexes parameter. Each index should be a member of $indexes. Values are: + + 'name' => Index name (If left empty SMF will generate). + + 'type' => Type of index. Choose from 'primary', 'unique' or 'index'. If not set will default to 'index'. + + 'columns' => Array containing columns that form part of key - in the order the index is to be created. + - parameters: (None yet) + - if_exists values: + + 'ignore' will do nothing if the table exists. (And will return true) + + 'overwrite' will drop any existing table of the same name. + + 'error' will return false if the table already exists. + +*/ + +// Add the file functions to the $smcFunc array. +function db_packages_init() +{ + global $smcFunc, $reservedTables, $db_package_log, $db_prefix; + + if (!isset($smcFunc['db_create_table']) || $smcFunc['db_create_table'] != 'smf_db_create_table') + { + $smcFunc += array( + 'db_add_column' => 'smf_db_add_column', + 'db_add_index' => 'smf_db_add_index', + 'db_calculate_type' => 'smf_db_calculate_type', + 'db_change_column' => 'smf_db_change_column', + 'db_create_table' => 'smf_db_create_table', + 'db_drop_table' => 'smf_db_drop_table', + 'db_table_structure' => 'smf_db_table_structure', + 'db_list_columns' => 'smf_db_list_columns', + 'db_list_indexes' => 'smf_db_list_indexes', + 'db_remove_column' => 'smf_db_remove_column', + 'db_remove_index' => 'smf_db_remove_index', + ); + $db_package_log = array(); + } + + // We setup an array of SMF tables we can't do auto-remove on - in case a mod writer cocks it up! + $reservedTables = array('admin_info_files', 'approval_queue', 'attachments', 'ban_groups', 'ban_items', + 'board_permissions', 'boards', 'calendar', 'calendar_holidays', 'categories', 'collapsed_categories', + 'custom_fields', 'group_moderators', 'log_actions', 'log_activity', 'log_banned', 'log_boards', + 'log_digest', 'log_errors', 'log_floodcontrol', 'log_group_requests', 'log_karma', 'log_mark_read', + 'log_notify', 'log_online', 'log_packages', 'log_polls', 'log_reported', 'log_reported_comments', + 'log_scheduled_tasks', 'log_search_messages', 'log_search_results', 'log_search_subjects', + 'log_search_topics', 'log_topics', 'mail_queue', 'membergroups', 'members', 'message_icons', + 'messages', 'moderators', 'package_servers', 'permission_profiles', 'permissions', 'personal_messages', + 'pm_recipients', 'poll_choices', 'polls', 'scheduled_tasks', 'sessions', 'settings', 'smileys', + 'themes', 'topics'); + foreach ($reservedTables as $k => $table_name) + $reservedTables[$k] = strtolower($db_prefix . $table_name); + + // We in turn may need the extra stuff. + db_extend('extra'); +} + +// Create a table. +function smf_db_create_table($table_name, $columns, $indexes = array(), $parameters = array(), $if_exists = 'ignore', $error = 'fatal') +{ + global $reservedTables, $smcFunc, $db_package_log, $db_prefix; + + // Strip out the table name, we might not need it in some cases + $real_prefix = preg_match('~^("?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix; + + // With or without the database name, the fullname looks like this. + $full_table_name = str_replace('{db_prefix}', $real_prefix, $table_name); + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // First - no way do we touch SMF tables. + if (in_array(strtolower($table_name), $reservedTables)) + return false; + + // Log that we'll want to remove this on uninstall. + $db_package_log[] = array('remove_table', $table_name); + + // This... my friends... is a function in a half - let's start by checking if the table exists! + $tables = $smcFunc['db_list_tables'](); + if (in_array($full_table_name, $tables)) + { + // This is a sad day... drop the table? If not, return false (error) by default. + if ($if_exists == 'overwrite') + $smcFunc['db_drop_table']($table_name); + else + return $if_exists == 'ignore'; + } + + // If we've got this far - good news - no table exists. We can build our own! + $smcFunc['db_transaction']('begin'); + $table_query = 'CREATE TABLE ' . $table_name . "\n" . '('; + foreach ($columns as $column) + { + // If we have an auto increment do it! + if (!empty($column['auto'])) + { + $smcFunc['db_query']('', ' + CREATE SEQUENCE ' . $table_name . '_seq', + array( + 'security_override' => true, + ) + ); + $default = 'default nextval(\'' . $table_name . '_seq\')'; + } + elseif (isset($column['default']) && $column['default'] !== null) + $default = 'default \'' . $smcFunc['db_escape_string']($column['default']) . '\''; + else + $default = ''; + + // Sort out the size... + $column['size'] = isset($column['size']) && is_numeric($column['size']) ? $column['size'] : null; + list ($type, $size) = $smcFunc['db_calculate_type']($column['type'], $column['size']); + if ($size !== null) + $type = $type . '(' . $size . ')'; + + // Now just put it together! + $table_query .= "\n\t\"" . $column['name'] . '" ' . $type . ' ' . (!empty($column['null']) ? '' : 'NOT NULL') . ' ' . $default . ','; + } + + // Loop through the indexes a sec... + $index_queries = array(); + foreach ($indexes as $index) + { + $columns = implode(',', $index['columns']); + + // Primary goes in the table... + if (isset($index['type']) && $index['type'] == 'primary') + $table_query .= "\n\t" . 'PRIMARY KEY (' . implode(',', $index['columns']) . '),'; + else + { + if (empty($index['name'])) + $index['name'] = implode('_', $index['columns']); + $index_queries[] = 'CREATE ' . (isset($index['type']) && $index['type'] == 'unique' ? 'UNIQUE' : '') . ' INDEX ' . $table_name . '_' . $index['name'] . ' ON ' . $table_name . ' (' . $columns . ')'; + } + } + + // No trailing commas! + if (substr($table_query, -1) == ',') + $table_query = substr($table_query, 0, -1); + + $table_query .= ')'; + + // Create the table! + $smcFunc['db_query']('', $table_query, + array( + 'security_override' => true, + ) + ); + // And the indexes... + foreach ($index_queries as $query) + $smcFunc['db_query']('', $query, + array( + 'security_override' => true, + ) + ); + + // Go, go power rangers! + $smcFunc['db_transaction']('commit'); +} + +// Drop a table. +function smf_db_drop_table($table_name, $parameters = array(), $error = 'fatal') +{ + global $reservedTables, $smcFunc, $db_prefix; + + // After stripping away the database name, this is what's left. + $real_prefix = preg_match('~^("?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix; + + // Get some aliases. + $full_table_name = str_replace('{db_prefix}', $real_prefix, $table_name); + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // God no - dropping one of these = bad. + if (in_array(strtolower($table_name), $reservedTables)) + return false; + + // Does it exist? + if (in_array($full_table_name, $smcFunc['db_list_tables']())) + { + // We can then drop the table. + $smcFunc['db_transaction']('begin'); + + // the table + $table_query = 'DROP TABLE ' . $table_name; + + // and the assosciated sequence, if any + $sequence_query = 'DROP SEQUENCE IF EXISTS ' . $table_name . '_seq'; + + // drop them + $smcFunc['db_query']('', + $table_query, + array( + 'security_override' => true, + ) + ); + $smcFunc['db_query']('', + $sequence_query, + array( + 'security_override' => true, + ) + ); + + $smcFunc['db_transaction']('commit'); + + return true; + } + + // Otherwise do 'nout. + return false; +} + +// Add a column. +function smf_db_add_column($table_name, $column_info, $parameters = array(), $if_exists = 'update', $error = 'fatal') +{ + global $smcFunc, $db_package_log, $txt, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // Log that we will want to uninstall this! + $db_package_log[] = array('remove_column', $table_name, $column_info['name']); + + // Does it exist - if so don't add it again! + $columns = $smcFunc['db_list_columns']($table_name, false); + foreach ($columns as $column) + if ($column == $column_info['name']) + { + // If we're going to overwrite then use change column. + if ($if_exists == 'update') + return $smcFunc['db_change_column']($table_name, $column_info['name'], $column_info); + else + return false; + } + + // Get the specifics... + $column_info['size'] = isset($column_info['size']) && is_numeric($column_info['size']) ? $column_info['size'] : null; + list ($type, $size) = $smcFunc['db_calculate_type']($column_info['type'], $column_info['size']); + if ($size !== null) + $type = $type . '(' . $size . ')'; + + // Now add the thing! + $query = ' + ALTER TABLE ' . $table_name . ' + ADD COLUMN ' . $column_info['name'] . ' ' . $type; + $smcFunc['db_query']('', $query, + array( + 'security_override' => true, + ) + ); + + // If there's more attributes they need to be done via a change on PostgreSQL. + unset($column_info['type'], $column_info['size']); + + if (count($column_info) != 1) + return $smcFunc['db_change_column']($table_name, $column_info['name'], $column_info); + else + return true; +} + +// Remove a column. +function smf_db_remove_column($table_name, $column_name, $parameters = array(), $error = 'fatal') +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // Does it exist? + $columns = $smcFunc['db_list_columns']($table_name, true); + foreach ($columns as $column) + if ($column['name'] == $column_name) + { + // If there is an auto we need remove it! + if ($column['auto']) + $smcFunc['db_query']('', + 'DROP SEQUENCE ' . $table_name . '_seq', + array( + 'security_override' => true, + ) + ); + + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + DROP COLUMN ' . $column_name, + array( + 'security_override' => true, + ) + ); + + return true; + } + + // If here we didn't have to work - joy! + return false; +} + +// Change a column. +function smf_db_change_column($table_name, $old_column, $column_info, $parameters = array(), $error = 'fatal') +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // Check it does exist! + $columns = $smcFunc['db_list_columns']($table_name, true); + $old_info = null; + foreach ($columns as $column) + if ($column['name'] == $old_column) + $old_info = $column; + + // Nothing? + if ($old_info == null) + return false; + + // Now we check each bit individually and ALTER as required. + if (isset($column_info['name']) && $column_info['name'] != $old_column) + { + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + RENAME COLUMN ' . $old_column . ' TO ' . $column_info['name'], + array( + 'security_override' => true, + ) + ); + } + // Different default? + if (isset($column_info['default']) && $column_info['default'] != $old_info['default']) + { + $action = $column_info['default'] !== null ? 'SET DEFAULT \'' . $smcFunc['db_escape_string']($column_info['default']) . '\'' : 'DROP DEFAULT'; + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + ALTER COLUMN ' . $column_info['name'] . ' ' . $action, + array( + 'security_override' => true, + ) + ); + } + // Is it null - or otherwise? + if (isset($column_info['null']) && $column_info['null'] != $old_info['null']) + { + $action = $column_info['null'] ? 'DROP' : 'SET'; + $smcFunc['db_transaction']('begin'); + if (!$column_info['null']) + { + // We have to set it to something if we are making it NOT NULL. + $setTo = isset($column_info['default']) ? $column_info['default'] : ''; + $smcFunc['db_query']('', ' + UPDATE ' . $table_name . ' + SET ' . $column_info['name'] . ' = \'' . $setTo . '\' + WHERE ' . $column_info['name'] . ' = NULL', + array( + 'security_override' => true, + ) + ); + } + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + ALTER COLUMN ' . $column_info['name'] . ' ' . $action . ' NOT NULL', + array( + 'security_override' => true, + ) + ); + $smcFunc['db_transaction']('commit'); + } + // What about a change in type? + if (isset($column_info['type']) && ($column_info['type'] != $old_info['type'] || (isset($column_info['size']) && $column_info['size'] != $old_info['size']))) + { + $column_info['size'] = isset($column_info['size']) && is_numeric($column_info['size']) ? $column_info['size'] : null; + list ($type, $size) = $smcFunc['db_calculate_type']($column_info['type'], $column_info['size']); + if ($size !== null) + $type = $type . '(' . $size . ')'; + + // The alter is a pain. + $smcFunc['db_transaction']('begin'); + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + ADD COLUMN ' . $column_info['name'] . '_tempxx ' . $type, + array( + 'security_override' => true, + ) + ); + $smcFunc['db_query']('', ' + UPDATE ' . $table_name . ' + SET ' . $column_info['name'] . '_tempxx = CAST(' . $column_info['name'] . ' AS ' . $type . ')', + array( + 'security_override' => true, + ) + ); + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + DROP COLUMN ' . $column_info['name'], + array( + 'security_override' => true, + ) + ); + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + RENAME COLUMN ' . $column_info['name'] . '_tempxx TO ' . $column_info['name'], + array( + 'security_override' => true, + ) + ); + $smcFunc['db_transaction']('commit'); + } + // Finally - auto increment?! + if (isset($column_info['auto']) && $column_info['auto'] != $old_info['auto']) + { + // Are we removing an old one? + if ($old_info['auto']) + { + // Alter the table first - then drop the sequence. + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + ALTER COLUMN ' . $column_info['name'] . ' SET DEFAULT \'0\'', + array( + 'security_override' => true, + ) + ); + $smcFunc['db_query']('', ' + DROP SEQUENCE ' . $table_name . '_seq', + array( + 'security_override' => true, + ) + ); + } + // Otherwise add it! + else + { + $smcFunc['db_query']('', ' + CREATE SEQUENCE ' . $table_name . '_seq', + array( + 'security_override' => true, + ) + ); + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + ALTER COLUMN ' . $column_info['name'] . ' SET DEFAULT nextval(\'' . $table_name . '_seq\')', + array( + 'security_override' => true, + ) + ); + } + } +} + +// Add an index. +function smf_db_add_index($table_name, $index_info, $parameters = array(), $if_exists = 'update', $error = 'fatal') +{ + global $smcFunc, $db_package_log, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // No columns = no index. + if (empty($index_info['columns'])) + return false; + $columns = implode(',', $index_info['columns']); + + // No name - make it up! + if (empty($index_info['name'])) + { + // No need for primary. + if (isset($index_info['type']) && $index_info['type'] == 'primary') + $index_info['name'] = ''; + else + $index_info['name'] = $table_name . implode('_', $index_info['columns']); + } + else + $index_info['name'] = $table_name . $index_info['name']; + + // Log that we are going to want to remove this! + $db_package_log[] = array('remove_index', $table_name, $index_info['name']); + + // Let's get all our indexes. + $indexes = $smcFunc['db_list_indexes']($table_name, true); + // Do we already have it? + foreach ($indexes as $index) + { + if ($index['name'] == $index_info['name'] || ($index['type'] == 'primary' && isset($index_info['type']) && $index_info['type'] == 'primary')) + { + // If we want to overwrite simply remove the current one then continue. + if ($if_exists != 'update' || $index['type'] == 'primary') + return false; + else + $smcFunc['db_remove_index']($table_name, $index_info['name']); + } + } + + // If we're here we know we don't have the index - so just add it. + if (!empty($index_info['type']) && $index_info['type'] == 'primary') + { + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + ADD PRIMARY KEY (' . $columns . ')', + array( + 'security_override' => true, + ) + ); + } + else + { + $smcFunc['db_query']('', ' + CREATE ' . (isset($index_info['type']) && $index_info['type'] == 'unique' ? 'UNIQUE' : '') . ' INDEX ' . $index_info['name'] . ' ON ' . $table_name . ' (' . $columns . ')', + array( + 'security_override' => true, + ) + ); + } +} + +// Remove an index. +function smf_db_remove_index($table_name, $index_name, $parameters = array(), $error = 'fatal') +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // Better exist! + $indexes = $smcFunc['db_list_indexes']($table_name, true); + if ($index_name != 'primary') + $index_name = $table_name . '_' . $index_name; + + foreach ($indexes as $index) + { + // If the name is primary we want the primary key! + if ($index['type'] == 'primary' && $index_name == 'primary') + { + // Dropping primary key is odd... + $smcFunc['db_query']('', ' + ALTER TABLE ' . $table_name . ' + DROP CONSTRAINT ' . $index['name'], + array( + 'security_override' => true, + ) + ); + + return true; + } + if ($index['name'] == $index_name) + { + // Drop the bugger... + $smcFunc['db_query']('', ' + DROP INDEX ' . $index_name, + array( + 'security_override' => true, + ) + ); + + return true; + } + } + + // Not to be found ;( + return false; +} + +// Get the schema formatted name for a type. +function smf_db_calculate_type($type_name, $type_size = null, $reverse = false) +{ + // Generic => Specific. + if (!$reverse) + { + $types = array( + 'varchar' => 'character varying', + 'char' => 'character', + 'mediumint' => 'int', + 'tinyint' => 'smallint', + 'tinytext' => 'character varying', + 'mediumtext' => 'text', + 'largetext' => 'text', + ); + } + else + { + $types = array( + 'character varying' => 'varchar', + 'character' => 'char', + 'integer' => 'int', + ); + } + + // Got it? Change it! + if (isset($types[$type_name])) + { + if ($type_name == 'tinytext') + $type_size = 255; + $type_name = $types[$type_name]; + } + // Numbers don't have a size. + if (strpos($type_name, 'int') !== false) + $type_size = null; + + return array($type_name, $type_size); +} + +// Get table structure. +function smf_db_table_structure($table_name, $parameters = array()) +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + return array( + 'name' => $table_name, + 'columns' => $smcFunc['db_list_columns']($table_name, true), + 'indexes' => $smcFunc['db_list_indexes']($table_name, true), + ); +} + +// Return column information for a table. +function smf_db_list_columns($table_name, $detail = false, $parameters = array()) +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + $result = $smcFunc['db_query']('', ' + SELECT column_name, column_default, is_nullable, data_type, character_maximum_length + FROM information_schema.columns + WHERE table_name = \'' . $table_name . '\' + ORDER BY ordinal_position', + array( + 'security_override' => true, + ) + ); + $columns = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + if (!$detail) + { + $columns[] = $row['column_name']; + } + else + { + $auto = false; + // What is the default? + if (preg_match('~nextval\(\'(.+?)\'(.+?)*\)~i', $row['column_default'], $matches) != 0) + { + $default = null; + $auto = true; + } + elseif (trim($row['column_default']) != '') + $default = strpos($row['column_default'], '::') === false ? $row['column_default'] : substr($row['column_default'], 0, strpos($row['column_default'], '::')); + else + $default = null; + + // Make the type generic. + list ($type, $size) = $smcFunc['db_calculate_type']($row['data_type'], $row['character_maximum_length'], true); + + $columns[$row['column_name']] = array( + 'name' => $row['column_name'], + 'null' => $row['is_nullable'] ? true : false, + 'default' => $default, + 'type' => $type, + 'size' => $size, + 'auto' => $auto, + ); + } + } + $smcFunc['db_free_result']($result); + + return $columns; +} + +// What about some index information? +function smf_db_list_indexes($table_name, $detail = false, $parameters = array()) +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + $result = $smcFunc['db_query']('', ' + SELECT CASE WHEN i.indisprimary THEN 1 ELSE 0 END AS is_primary, + CASE WHEN i.indisunique THEN 1 ELSE 0 END AS is_unique, + c2.relname AS name, + pg_get_indexdef(i.indexrelid) AS inddef + FROM pg_class AS c, pg_class AS c2, pg_index AS i + WHERE c.relname = \'' . $table_name . '\' + AND c.oid = i.indrelid + AND i.indexrelid = c2.oid', + array( + 'security_override' => true, + ) + ); + $indexes = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + // Try get the columns that make it up. + if (preg_match('~\(([^\)]+?)\)~i', $row['inddef'], $matches) == 0) + continue; + + $columns = explode(',', $matches[1]); + + if (empty($columns)) + continue; + + foreach ($columns as $k => $v) + $columns[$k] = trim($v); + + // Fix up the name to be consistent cross databases + if (substr($row['name'], -5) == '_pkey' && $row['is_primary'] == 1) + $row['name'] = 'PRIMARY'; + else + $row['name'] = str_replace($table_name . '_', '', $row['name']); + + if (!$detail) + $indexes[] = $row['name']; + else + { + $indexes[$row['name']] = array( + 'name' => $row['name'], + 'type' => $row['is_primary'] ? 'primary' : ($row['is_unique'] ? 'unique' : 'index'), + 'columns' => $columns, + ); + } + } + $smcFunc['db_free_result']($result); + + return $indexes; +} + +?> \ No newline at end of file diff --git a/Sources/DbPackages-sqlite.php b/Sources/DbPackages-sqlite.php new file mode 100644 index 0000000..6c6beb5 --- /dev/null +++ b/Sources/DbPackages-sqlite.php @@ -0,0 +1,736 @@ + Size of column (If applicable) - for example 255 for a large varchar, 10 for an int etc. If not + set SMF will pick a size. + + 'default' = Default value - do not set if no default required. + + 'null' => Can it be null (true or false) - if not set default will be false. + + 'auto' => Set to true to make it an auto incrementing column. Set to a numerical value to set + from what it should begin counting. + - Adds indexes as specified within indexes parameter. Each index should be a member of $indexes. Values are: + + 'name' => Index name (If left empty SMF will generate). + + 'type' => Type of index. Choose from 'primary', 'unique' or 'index'. If not set will default to 'index'. + + 'columns' => Array containing columns that form part of key - in the order the index is to be created. + - parameters: (None yet) + - if_exists values: + + 'ignore' will do nothing if the table exists. (And will return true) + + 'overwrite' will drop any existing table of the same name. + + 'error' will return false if the table already exists. + +*/ + +// Add the file functions to the $smcFunc array. +function db_packages_init() +{ + global $smcFunc, $reservedTables, $db_package_log, $db_prefix; + + if (!isset($smcFunc['db_create_table']) || $smcFunc['db_create_table'] != 'smf_db_create_table') + { + $smcFunc += array( + 'db_add_column' => 'smf_db_add_column', + 'db_add_index' => 'smf_db_add_index', + 'db_alter_table' => 'smf_db_alter_table', + 'db_calculate_type' => 'smf_db_calculate_type', + 'db_change_column' => 'smf_db_change_column', + 'db_create_table' => 'smf_db_create_table', + 'db_drop_table' => 'smf_db_drop_table', + 'db_table_structure' => 'smf_db_table_structure', + 'db_list_columns' => 'smf_db_list_columns', + 'db_list_indexes' => 'smf_db_list_indexes', + 'db_remove_column' => 'smf_db_remove_column', + 'db_remove_index' => 'smf_db_remove_index', + ); + $db_package_log = array(); + } + + // We setup an array of SMF tables we can't do auto-remove on - in case a mod writer cocks it up! + $reservedTables = array('admin_info_files', 'approval_queue', 'attachments', 'ban_groups', 'ban_items', + 'board_permissions', 'boards', 'calendar', 'calendar_holidays', 'categories', 'collapsed_categories', + 'custom_fields', 'group_moderators', 'log_actions', 'log_activity', 'log_banned', 'log_boards', + 'log_digest', 'log_errors', 'log_floodcontrol', 'log_group_requests', 'log_karma', 'log_mark_read', + 'log_notify', 'log_online', 'log_packages', 'log_polls', 'log_reported', 'log_reported_comments', + 'log_scheduled_tasks', 'log_search_messages', 'log_search_results', 'log_search_subjects', + 'log_search_topics', 'log_topics', 'mail_queue', 'membergroups', 'members', 'message_icons', + 'messages', 'moderators', 'package_servers', 'permission_profiles', 'permissions', 'personal_messages', + 'pm_recipients', 'poll_choices', 'polls', 'scheduled_tasks', 'sessions', 'settings', 'smileys', + 'themes', 'topics'); + foreach ($reservedTables as $k => $table_name) + $reservedTables[$k] = strtolower($db_prefix . $table_name); + + // We in turn may need the extra stuff. + db_extend('extra'); +} + +// Create a table. +function smf_db_create_table($table_name, $columns, $indexes = array(), $parameters = array(), $if_exists = 'ignore', $error = 'fatal') +{ + global $reservedTables, $smcFunc, $db_package_log, $db_prefix; + + // With or without the database name, the full name looks like this. + $real_prefix = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix; + $full_table_name = str_replace('{db_prefix}', $real_prefix, $table_name); + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // First - no way do we touch SMF tables. + // Commented out for now. We need to alter SMF tables in order to use this in the upgrade. +/* + if (in_array(strtolower($table_name), $reservedTables)) + return false; +*/ + + // Log that we'll want to remove this on uninstall. + $db_package_log[] = array('remove_table', $table_name); + + // Does this table exist or not? + $tables = $smcFunc['db_list_tables'](); + if (in_array($full_table_name, $tables)) + { + // This is a sad day... drop the table? If not, return false (error) by default. + if ($if_exists == 'overwrite') + $smcFunc['db_drop_table']($table_name); + else + return $if_exists == 'ignore'; + } + + // Righty - let's do the damn thing! + $table_query = 'CREATE TABLE ' . $table_name . "\n" . '('; + $done_primary = false; + foreach ($columns as $column) + { + // Auto increment is special + if (!empty($column['auto'])) + { + $table_query .= "\n" . $column['name'] . ' integer PRIMARY KEY,'; + $done_primary = true; + continue; + } + elseif (isset($column['default']) && $column['default'] !== null) + $default = 'default \'' . $smcFunc['db_escape_string']($column['default']) . '\''; + else + $default = ''; + + // Sort out the size... and stuff... + $column['size'] = isset($column['size']) && is_numeric($column['size']) ? $column['size'] : null; + list ($type, $size) = $smcFunc['db_calculate_type']($column['type'], $column['size']); + if ($size !== null) + $type = $type . '(' . $size . ')'; + + // Now just put it together! + $table_query .= "\n\t" . $column['name'] . ' ' . $type . ' ' . (!empty($column['null']) ? '' : 'NOT NULL') . ' ' . $default . ','; + } + + // Loop through the indexes next... + $index_queries = array(); + foreach ($indexes as $index) + { + $columns = implode(',', $index['columns']); + + // Is it the primary? + if (isset($index['type']) && $index['type'] == 'primary') + { + // If we've done the primary via auto_inc, don't do it again! + if (!$done_primary) + $table_query .= "\n\t" . 'PRIMARY KEY (' . implode(',', $index['columns']) . '),'; + } + else + { + if (empty($index['name'])) + $index['name'] = implode('_', $index['columns']); + $index_queries[] = 'CREATE ' . (isset($index['type']) && $index['type'] == 'unique' ? 'UNIQUE' : '') . ' INDEX ' . $table_name . '_' . $index['name'] . ' ON ' . $table_name . ' (' . $columns . ')'; + } + } + + // No trailing commas! + if (substr($table_query, -1) == ',') + $table_query = substr($table_query, 0, -1); + + $table_query .= ')'; + + if (empty($parameters['skip_transaction'])) + $smcFunc['db_transaction']('begin'); + + // Do the table and indexes... + $smcFunc['db_query']('', $table_query, + array( + 'security_override' => true, + ) + ); + foreach ($index_queries as $query) + $smcFunc['db_query']('', $query, + array( + 'security_override' => true, + ) + ); + + if (empty($parameters['skip_transaction'])) + $smcFunc['db_transaction']('commit'); +} + +// Drop a table. +function smf_db_drop_table($table_name, $parameters = array(), $error = 'fatal') +{ + global $reservedTables, $smcFunc, $db_prefix; + + // Strip out the table name, we might not need it in some cases + $real_prefix = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix; + $full_table_name = str_replace('{db_prefix}', $real_prefix, $table_name); + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // God no - dropping one of these = bad. + if (in_array(strtolower($table_name), $reservedTables)) + return false; + + // Does it exist? + if (in_array($full_table_name, $smcFunc['db_list_tables']())) + { + $query = 'DROP TABLE ' . $table_name; + $smcFunc['db_query']('', $query, + array( + 'security_override' => true, + ) + ); + + return true; + } + + // Otherwise do 'nout. + return false; +} + +// Add a column. +function smf_db_add_column($table_name, $column_info, $parameters = array(), $if_exists = 'update', $error = 'fatal') +{ + global $smcFunc, $db_package_log, $txt, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // Log that we will want to uninstall this! + $db_package_log[] = array('remove_column', $table_name, $column_info['name']); + + // Does it exist - if so don't add it again! + $columns = $smcFunc['db_list_columns']($table_name, false); + foreach ($columns as $column) + if ($column == $column_info['name']) + { + // If we're going to overwrite then use change column. + if ($if_exists == 'update') + return $smcFunc['db_change_column']($table_name, $column_info['name'], $column_info); + else + return false; + } + + // Alter the table to add the column. + if ($smcFunc['db_alter_table']($table_name, array('add' => array($column_info))) === false) + return false; + + return true; +} + +// We can't reliably do this on SQLite - damn! +function smf_db_remove_column($table_name, $column_name, $parameters = array(), $error = 'fatal') +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + if ($smcFunc['db_alter_table']($table_name, array('remove' => array(array('name' => $column_name))))) + return true; + else + return false; +} + +// Change a column. +function smf_db_change_column($table_name, $old_column, $column_info, $parameters = array(), $error = 'fatal') +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + if ($smcFunc['db_alter_table']($table_name, array('change' => array(array('name' => $old_column) + $column_info)))) + return true; + else + return false; +} + +// Add an index. +function smf_db_add_index($table_name, $index_info, $parameters = array(), $if_exists = 'update', $error = 'fatal') +{ + global $smcFunc, $db_package_log, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // No columns = no index. + if (empty($index_info['columns'])) + return false; + $columns = implode(',', $index_info['columns']); + + // No name - make it up! + if (empty($index_info['name'])) + { + // No need for primary. + if (isset($index_info['type']) && $index_info['type'] == 'primary') + $index_info['name'] = ''; + else + $index_info['name'] = implode('_', $index_info['columns']); + } + else + $index_info['name'] = $index_info['name']; + + // Log that we are going to want to remove this! + $db_package_log[] = array('remove_index', $table_name, $index_info['name']); + + // Let's get all our indexes. + $indexes = $smcFunc['db_list_indexes']($table_name, true); + // Do we already have it? + foreach ($indexes as $index) + { + if ($index['name'] == $index_info['name'] || ($index['type'] == 'primary' && isset($index_info['type']) && $index_info['type'] == 'primary')) + { + // If we want to overwrite simply remove the current one then continue. + if ($if_exists != 'update' || $index['type'] == 'primary') + return false; + else + $smcFunc['db_remove_index']($table_name, $index_info['name']); + } + } + + // If we're here we know we don't have the index - so just add it. + if (!empty($index_info['type']) && $index_info['type'] == 'primary') + { + //!!! Doesn't work with PRIMARY KEY yet. + } + else + { + $smcFunc['db_query']('', ' + CREATE ' . (isset($index_info['type']) && $index_info['type'] == 'unique' ? 'UNIQUE' : '') . ' INDEX ' . $index_info['name'] . ' ON ' . $table_name . ' (' . $columns . ')', + array( + 'security_override' => true, + ) + ); + } +} + +// Remove an index. +function smf_db_remove_index($table_name, $index_name, $parameters = array(), $error = 'fatal') +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // Better exist! + $indexes = $smcFunc['db_list_indexes']($table_name, true); + + foreach ($indexes as $index) + { + //!!! Doesn't do primary key at the moment! + if ($index['type'] != 'primary' && $index['name'] == $index_name) + { + // Drop the bugger... + $smcFunc['db_query']('', ' + DROP INDEX ' . $index_name, + array( + 'security_override' => true, + ) + ); + + return true; + } + } + + // Not to be found ;( + return false; +} + +// Get the schema formatted name for a type. +function smf_db_calculate_type($type_name, $type_size = null, $reverse = false) +{ + // Generic => Specific. + if (!$reverse) + { + $types = array( + 'mediumint' => 'int', + 'tinyint' => 'smallint', + 'mediumtext' => 'text', + 'largetext' => 'text', + ); + } + else + { + $types = array( + 'integer' => 'int', + ); + } + + // Got it? Change it! + if (isset($types[$type_name])) + { + if ($type_name == 'tinytext') + $type_size = 255; + $type_name = $types[$type_name]; + } + // Numbers don't have a size. + if (strpos($type_name, 'int') !== false) + $type_size = null; + + return array($type_name, $type_size); +} + +// Get table structure. +function smf_db_table_structure($table_name, $parameters = array()) +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + return array( + 'name' => $table_name, + 'columns' => $smcFunc['db_list_columns']($table_name, true), + 'indexes' => $smcFunc['db_list_indexes']($table_name, true), + ); +} + +// Harder than it should be on sqlite! +function smf_db_list_columns($table_name, $detail = false, $parameters = array()) +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + $result = $smcFunc['db_query']('', ' + PRAGMA table_info(' . $table_name . ')', + array( + 'security_override' => true, + ) + ); + $columns = array(); + + $primaries = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + if (!$detail) + { + $columns[] = $row['name']; + } + else + { + // Auto increment is hard to tell really... if there's only one primary it probably is. + if ($row['pk']) + $primaries[] = $row['name']; + + // Can we split out the size? + if (preg_match('~(.+?)\s*\((\d+)\)~i', $row['type'], $matches)) + { + $type = $matches[1]; + $size = $matches[2]; + } + else + { + $type = $row['type']; + $size = null; + } + + $columns[$row['name']] = array( + 'name' => $row['name'], + 'null' => $row['notnull'] ? false : true, + 'default' => $row['dflt_value'], + 'type' => $type, + 'size' => $size, + 'auto' => false, + ); + } + } + $smcFunc['db_free_result']($result); + + // Put in our guess at auto_inc. + if (count($primaries) == 1) + $columns[$primaries[0]]['auto'] = true; + + return $columns; +} + +// What about some index information? +function smf_db_list_indexes($table_name, $detail = false, $parameters = array()) +{ + global $smcFunc, $db_prefix; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + $result = $smcFunc['db_query']('', ' + PRAGMA index_list(' . $table_name . ')', + array( + 'security_override' => true, + ) + ); + $indexes = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + if (!$detail) + $indexes[] = $row['name']; + else + { + $result2 = $smcFunc['db_query']('', ' + PRAGMA index_info(' . $row['name'] . ')', + array( + 'security_override' => true, + ) + ); + while ($row2 = $smcFunc['db_fetch_assoc']($result2)) + { + // What is the type? + if ($row['unique']) + $type = 'unique'; + else + $type = 'index'; + + // This is the first column we've seen? + if (empty($indexes[$row['name']])) + { + $indexes[$row['name']] = array( + 'name' => $row['name'], + 'type' => $type, + 'columns' => array(), + ); + } + + // Add the column... + $indexes[$row['name']]['columns'][] = $row2['name']; + } + $smcFunc['db_free_result']($result2); + } + } + $smcFunc['db_free_result']($result); + + return $indexes; +} + +function smf_db_alter_table($table_name, $columns) +{ + global $smcFunc, $db_prefix, $db_name, $boarddir; + + $db_file = substr($db_name, -3) === '.db' ? $db_name : $db_name . '.db'; + + $table_name = str_replace('{db_prefix}', $db_prefix, $table_name); + + // Let's get the current columns for the table. + $current_columns = $smcFunc['db_list_columns']($table_name, true); + + // Let's get a list of columns for the temp table. + $temp_table_columns = array(); + + // Let's see if we have columns to remove or columns that are being added that already exist. + foreach ($current_columns as $key => $column) + { + $exists = false; + if (isset($columns['remove'])) + foreach ($columns['remove'] as $drop) + if ($drop['name'] == $column['name']) + { + $exists = true; + break; + } + + if (isset($columns['add'])) + foreach ($columns['add'] as $key2 => $add) + if ($add['name'] == $column['name']) + { + unset($columns['add'][$key2]); + break; + } + + // Doesn't exist then we 'remove'. + if (!$exists) + $temp_table_columns[] = $column['name']; + } + + // If they are equal then that means that the column that we are adding exists or it doesn't exist and we are not looking to change any one of them. + if (count($temp_table_columns) == count($current_columns) && empty($columns['change']) && empty($columns['add'])) + return true; + + // Drop the temp table. + $smcFunc['db_query']('', ' + DROP TABLE {raw:temp_table_name}', + array( + 'temp_table_name' => $table_name . '_tmp', + 'db_error_skip' => true, + ) + ); + + // Let's make a backup of the current database. + // We only want the first backup of a table modification. So if there is a backup file and older than an hour just delete and back up again + $db_backup_file = $boarddir . '/Packages/backups/backup_' . $table_name . '_' . basename($db_file) . md5($table_name . $db_file); + if (file_exists($db_backup_file) && time() - filemtime($db_backup_file) > 3600) + { + @unlink($db_backup_file); + @copy($db_file, $db_backup_file); + } + elseif (!file_exists($db_backup_file)) + @copy($db_file, $db_backup_file); + + // If we don't have temp tables then everything crapped out. Just exit. + if (empty($temp_table_columns)) + return false; + + // Start + $smcFunc['db_transaction']('begin'); + + // Let's create the temporary table. + $createTempTable = $smcFunc['db_query']('', ' + CREATE TEMPORARY TABLE {raw:temp_table_name} + ( + {raw:columns} + );', + array( + 'temp_table_name' => $table_name . '_tmp', + 'columns' => implode(', ', $temp_table_columns), + 'db_error_skip' => true, + ) + ) !== false; + + if (!$createTempTable) + return false; + + // Insert into temp table. + $smcFunc['db_query']('', ' + INSERT INTO {raw:temp_table_name} + ({raw:columns}) + SELECT {raw:columns} + FROM {raw:table_name}', + array( + 'table_name' => $table_name, + 'columns' => implode(', ', $temp_table_columns), + 'temp_table_name' => $table_name . '_tmp', + ) + ); + + // Drop the current table. + $dropTable = $smcFunc['db_query']('', ' + DROP TABLE {raw:table_name}', + array( + 'table_name' => $table_name, + 'db_error_skip' => true, + ) + ) !== false; + + // If you can't drop the main table then there is no where to go from here. Just return. + if (!$dropTable) + return false; + + // We need to keep track of the structure for the current columns and the new columns. + $new_columns = array(); + $column_names = array(); + + // Let's get the ones that we already have first. + foreach ($current_columns as $name => $column) + { + if (in_array($name, $temp_table_columns)) + { + $new_columns[$name] = array( + 'name' => $name, + 'type' => $column['type'], + 'size' => isset($column['size']) ? (int) $column['size'] : null, + 'null' => !empty($column['null']), + 'auto' => isset($column['auto']) ? $column['auto'] : false, + 'default' => isset($column['default']) ? $column['default'] : '', + ); + + // Lets keep track of the name for the column. + $column_names[$name] = $name; + } + } + + // Now the new. + if (!empty($columns['add'])) + foreach ($columns['add'] as $add) + { + $new_columns[$add['name']] = array( + 'name' => $add['name'], + 'type' => $add['type'], + 'size' => isset($add['size']) ? (int) $add['size'] : null, + 'null' => !empty($add['null']), + 'auto' => isset($add['auto']) ? $add['auto'] : false, + 'default' => isset($add['default']) ? $add['default'] : '', + ); + + // Let's keep track of the name for the column. + $column_names[$add['name']] = strstr('int', $add['type']) ? ' 0 AS ' . $add['name'] : ' {string:empty_string} AS ' . $add['name']; + } + + // Now to change a column. Not drop but change it. + if (isset($columns['change'])) + foreach ($columns['change'] as $change) + if (isset($new_columns[$change['name']])) + $new_columns[$change['name']] = array( + 'name' => $change['name'], + 'type' => $change['type'], + 'size' => isset($change['size']) ? (int) $change['size'] : null, + 'null' => !empty($change['null']), + 'auto' => isset($change['auto']) ? $change['auto'] : false, + 'default' => isset($change['default']) ? $change['default'] : '', + ); + + // Now let's create the table. + $createTable = $smcFunc['db_create_table']($table_name, $new_columns, array(), array('skip_transaction' => true)); + + // Did it create correctly? + if ($createTable === false) + return false; + + // Back to it's original table. + $insertData = $smcFunc['db_query']('', ' + INSERT INTO {raw:table_name} + ({raw:columns}) + SELECT ' . implode(', ', $column_names) . ' + FROM {raw:temp_table_name}', + array( + 'table_name' => $table_name, + 'columns' => implode(', ', array_keys($new_columns)), + 'columns_select' => implode(', ', $column_names), + 'temp_table_name' => $table_name . '_tmp', + 'empty_string' => '', + ) + ); + + // Did everything insert correctly? + if (!$insertData) + return false; + + // Drop the temp table. + $smcFunc['db_query']('', ' + DROP TABLE {raw:temp_table_name}', + array( + 'temp_table_name' => $table_name . '_tmp', + 'db_error_skip' => true, + ) + ); + + // Commit or else there is no point in doing the previous steps. + $smcFunc['db_transaction']('commit'); + + // We got here so we're good. The temp table should be deleted, if not it will be gone later on >:D. + return true; +} + +?> \ No newline at end of file diff --git a/Sources/DbSearch-mysql.php b/Sources/DbSearch-mysql.php new file mode 100644 index 0000000..140ae0b --- /dev/null +++ b/Sources/DbSearch-mysql.php @@ -0,0 +1,77 @@ + 'smf_db_query', + 'db_search_support' => 'smf_db_search_support', + 'db_create_word_search' => 'smf_db_create_word_search', + 'db_support_ignore' => true, + ); +} + +// Does this database type support this search type? +function smf_db_search_support($search_type) +{ + $supported_types = array('fulltext'); + + return in_array($search_type, $supported_types); +} + +// Highly specific - create the custom word index table! +function smf_db_create_word_search($size) +{ + global $smcFunc; + + if ($size == 'small') + $size = 'smallint(5)'; + elseif ($size == 'medium') + $size = 'mediumint(8)'; + else + $size = 'int(10)'; + + $smcFunc['db_query']('', ' + CREATE TABLE {db_prefix}log_search_words ( + id_word {raw:size} unsigned NOT NULL default {string:string_zero}, + id_msg int(10) unsigned NOT NULL default {string:string_zero}, + PRIMARY KEY (id_word, id_msg) + ) ENGINE=InnoDB', + array( + 'string_zero' => '0', + 'size' => $size, + ) + ); +} + +?> \ No newline at end of file diff --git a/Sources/DbSearch-postgresql.php b/Sources/DbSearch-postgresql.php new file mode 100644 index 0000000..2eda412 --- /dev/null +++ b/Sources/DbSearch-postgresql.php @@ -0,0 +1,122 @@ + 'smf_db_search_query', + 'db_search_support' => 'smf_db_search_support', + 'db_create_word_search' => 'smf_db_create_word_search', + 'db_support_ignore' => false, + ); +} + +// Does this database type support this search type? +function smf_db_search_support($search_type) +{ + $supported_types = array('custom'); + + return in_array($search_type, $supported_types); +} + +// Returns the correct query for this search type. +function smf_db_search_query($identifier, $db_string, $db_values = array(), $connection = null) +{ + global $smcFunc; + + $replacements = array( + 'create_tmp_log_search_topics' => array( + '~mediumint\(\d\)~i' => 'int', + '~unsigned~i' => '', + '~ENGINE=MEMORY~i' => '', + ), + 'create_tmp_log_search_messages' => array( + '~mediumint\(\d\)' => 'int', + '~unsigned~i' => '', + '~TYPE=HEAP~i' => '', + ), + 'drop_tmp_log_search_topics' => array( + '~IF\sEXISTS~i' => '', + ), + 'drop_tmp_log_search_messages' => array( + '~IF\sEXISTS~i' => '', + ), + 'insert_into_log_messages_fulltext' => array( + '~NOT\sRLIKE~i' => '!~*', + '~RLIKE~i' => '~*', + ), + 'insert_log_search_results_subject' => array( + '~NOT\sRLIKE~i' => '!~*', + '~RLIKE~i' => '~*', + ), + ); + + if (isset($replacements[$identifier])) + $db_string = preg_replace(array_keys($replacements[$identifier]), array_values($replacements[$identifier]), $db_string); + elseif (preg_match('~^\s*INSERT\sIGNORE~i', $db_string) != 0) + { + $db_string = preg_replace('~^\s*INSERT\sIGNORE~i', 'INSERT', $db_string); + // Don't error on multi-insert. + $db_values['db_error_skip'] = true; + } + + $return = $smcFunc['db_query']('', $db_string, + $db_values, $connection + ); + + return $return; +} + +// Highly specific - create the custom word index table! +function smf_db_create_word_search($size) +{ + global $smcFunc; + + $size = 'int'; + + $smcFunc['db_query']('', ' + CREATE TABLE {db_prefix}log_search_words ( + id_word {raw:size} NOT NULL default {string:string_zero}, + id_msg int NOT NULL default {string:string_zero}, + PRIMARY KEY (id_word, id_msg) + )', + array( + 'size' => $size, + 'string_zero' => '0', + ) + ); +} + +?> \ No newline at end of file diff --git a/Sources/DbSearch-sqlite.php b/Sources/DbSearch-sqlite.php new file mode 100644 index 0000000..e0ab031 --- /dev/null +++ b/Sources/DbSearch-sqlite.php @@ -0,0 +1,106 @@ + 'smf_db_search_query', + 'db_search_support' => 'smf_db_search_support', + 'db_create_word_search' => 'smf_db_create_word_search', + 'db_support_ignore' => false, + ); +} + +// Does this database type support this search type? +function smf_db_search_support($search_type) +{ + $supported_types = array('custom'); + + return in_array($search_type, $supported_types); +} + +// Returns the correct query for this search type. +function smf_db_search_query($identifier, $db_string, $db_values = array(), $connection = null) +{ + global $smcFunc; + + $replacements = array( + 'create_tmp_log_search_topics' => array( + '~mediumint\(\d\)~i' => 'int', + '~ENGINE=MEMORY~i' => '', + ), + 'create_tmp_log_search_messages' => array( + '~mediumint\(\d\)~i' => 'int', + '~TYPE=HEAP~i' => '', + ), + ); + + if (isset($replacements[$identifier])) + $db_string = preg_replace(array_keys($replacements[$identifier]), array_values($replacements[$identifier]), $db_string); + elseif (preg_match('~^\s*INSERT\sIGNORE~i', $db_string) != 0) + { + $db_string = preg_replace('~^\s*INSERT\sIGNORE~i', 'INSERT', $db_string); + // Don't error on multi-insert. + $db_values['db_error_skip'] = true; + } + + $return = $smcFunc['db_query']('', $db_string, + $db_values, $connection + ); + + return $return; +} + +// Highly specific - create the custom word index table! +function smf_db_create_word_search($size) +{ + global $smcFunc; + + $size = 'int'; + + $smcFunc['db_query']('', ' + CREATE TABLE {db_prefix}log_search_words ( + id_word {raw:size} NOT NULL default {string:string_zero}, + id_msg int(10) NOT NULL default {string:string_zero}, + PRIMARY KEY (id_word, id_msg) + )', + array( + 'size' => $size, + 'string_zero' => '0', + ) + ); +} + +?> \ No newline at end of file diff --git a/Sources/Display.php b/Sources/Display.php new file mode 100644 index 0000000..6aac738 --- /dev/null +++ b/Sources/Display.php @@ -0,0 +1,1727 @@ + 2) + foreach ($_GET as $k => $v) + { + if (!in_array($k, array('topic', 'board', 'start', session_name()))) + $context['robot_no_index'] = true; + } + + if (!empty($_REQUEST['start']) && (!is_numeric($_REQUEST['start']) || $_REQUEST['start'] % $context['messages_per_page'] != 0)) + $context['robot_no_index'] = true; + + // Find the previous or next topic. Make a fuss if there are no more. + if (isset($_REQUEST['prev_next']) && ($_REQUEST['prev_next'] == 'prev' || $_REQUEST['prev_next'] == 'next')) + { + // No use in calculating the next topic if there's only one. + if ($board_info['num_topics'] > 1) + { + // Just prepare some variables that are used in the query. + $gt_lt = $_REQUEST['prev_next'] == 'prev' ? '>' : '<'; + $order = $_REQUEST['prev_next'] == 'prev' ? '' : ' DESC'; + + $request = $smcFunc['db_query']('', ' + SELECT t2.id_topic + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}topics AS t2 ON (' . (empty($modSettings['enableStickyTopics']) ? ' + t2.id_last_msg ' . $gt_lt . ' t.id_last_msg' : ' + (t2.id_last_msg ' . $gt_lt . ' t.id_last_msg AND t2.is_sticky ' . $gt_lt . '= t.is_sticky) OR t2.is_sticky ' . $gt_lt . ' t.is_sticky') . ') + WHERE t.id_topic = {int:current_topic} + AND t2.id_board = {int:current_board}' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : ' + AND (t2.approved = {int:is_approved} OR (t2.id_member_started != {int:id_member_started} AND t2.id_member_started = {int:current_member}))') . ' + ORDER BY' . (empty($modSettings['enableStickyTopics']) ? '' : ' t2.is_sticky' . $order . ',') . ' t2.id_last_msg' . $order . ' + LIMIT 1', + array( + 'current_board' => $board, + 'current_member' => $user_info['id'], + 'current_topic' => $topic, + 'is_approved' => 1, + 'id_member_started' => 0, + ) + ); + + // No more left. + if ($smcFunc['db_num_rows']($request) == 0) + { + $smcFunc['db_free_result']($request); + + // Roll over - if we're going prev, get the last - otherwise the first. + $request = $smcFunc['db_query']('', ' + SELECT id_topic + FROM {db_prefix}topics + WHERE id_board = {int:current_board}' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : ' + AND (approved = {int:is_approved} OR (id_member_started != {int:id_member_started} AND id_member_started = {int:current_member}))') . ' + ORDER BY' . (empty($modSettings['enableStickyTopics']) ? '' : ' is_sticky' . $order . ',') . ' id_last_msg' . $order . ' + LIMIT 1', + array( + 'current_board' => $board, + 'current_member' => $user_info['id'], + 'is_approved' => 1, + 'id_member_started' => 0, + ) + ); + } + + // Now you can be sure $topic is the id_topic to view. + list ($topic) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $context['current_topic'] = $topic; + } + + // Go to the newest message on this topic. + $_REQUEST['start'] = 'new'; + } + + // Add 1 to the number of views of this topic. + if (empty($_SESSION['last_read_topic']) || $_SESSION['last_read_topic'] != $topic) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET num_views = num_views + 1 + WHERE id_topic = {int:current_topic}', + array( + 'current_topic' => $topic, + ) + ); + + $_SESSION['last_read_topic'] = $topic; + } + + // Get all the important topic info. + $request = $smcFunc['db_query']('', ' + SELECT + t.num_replies, t.num_views, t.locked, ms.subject, t.is_sticky, t.id_poll, + t.id_member_started, t.id_first_msg, t.id_last_msg, t.approved, t.unapproved_posts, + ' . ($user_info['is_guest'] ? 't.id_last_msg + 1' : 'IFNULL(lt.id_msg, IFNULL(lmr.id_msg, -1)) + 1') . ' AS new_from + ' . (!empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board ? ', id_previous_board, id_previous_topic' : '') . ' + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)' . ($user_info['is_guest'] ? '' : ' + LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = {int:current_topic} AND lt.id_member = {int:current_member}) + LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = {int:current_board} AND lmr.id_member = {int:current_member})') . ' + WHERE t.id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_member' => $user_info['id'], + 'current_topic' => $topic, + 'current_board' => $board, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('not_a_topic', false); + $topicinfo = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + $context['real_num_replies'] = $context['num_replies'] = $topicinfo['num_replies']; + $context['topic_first_message'] = $topicinfo['id_first_msg']; + $context['topic_last_message'] = $topicinfo['id_last_msg']; + + // Add up unapproved replies to get real number of replies... + if ($modSettings['postmod_active'] && allowedTo('approve_posts')) + $context['real_num_replies'] += $topicinfo['unapproved_posts'] - ($topicinfo['approved'] ? 0 : 1); + + // If this topic has unapproved posts, we need to work out how many posts the user can see, for page indexing. + if ($modSettings['postmod_active'] && $topicinfo['unapproved_posts'] && !$user_info['is_guest'] && !allowedTo('approve_posts')) + { + $request = $smcFunc['db_query']('', ' + SELECT COUNT(id_member) AS my_unapproved_posts + FROM {db_prefix}messages + WHERE id_topic = {int:current_topic} + AND id_member = {int:current_member} + AND approved = 0', + array( + 'current_topic' => $topic, + 'current_member' => $user_info['id'], + ) + ); + list ($myUnapprovedPosts) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $context['total_visible_posts'] = $context['num_replies'] + $myUnapprovedPosts + ($topicinfo['approved'] ? 1 : 0); + } + else + $context['total_visible_posts'] = $context['num_replies'] + $topicinfo['unapproved_posts'] + ($topicinfo['approved'] ? 1 : 0); + + // When was the last time this topic was replied to? Should we warn them about it? + $request = $smcFunc['db_query']('', ' + SELECT poster_time + FROM {db_prefix}messages + WHERE id_msg = {int:id_last_msg} + LIMIT 1', + array( + 'id_last_msg' => $topicinfo['id_last_msg'], + ) + ); + + list ($lastPostTime) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $context['oldTopicError'] = !empty($modSettings['oldTopicDays']) && $lastPostTime + $modSettings['oldTopicDays'] * 86400 < time() && empty($sticky); + + // The start isn't a number; it's information about what to do, where to go. + if (!is_numeric($_REQUEST['start'])) + { + // Redirect to the page and post with new messages, originally by Omar Bazavilvazo. + if ($_REQUEST['start'] == 'new') + { + // Guests automatically go to the last post. + if ($user_info['is_guest']) + { + $context['start_from'] = $context['total_visible_posts'] - 1; + $_REQUEST['start'] = empty($options['view_newest_first']) ? $context['start_from'] : 0; + } + else + { + // Find the earliest unread message in the topic. (the use of topics here is just for both tables.) + $request = $smcFunc['db_query']('', ' + SELECT IFNULL(lt.id_msg, IFNULL(lmr.id_msg, -1)) + 1 AS new_from + FROM {db_prefix}topics AS t + LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = {int:current_topic} AND lt.id_member = {int:current_member}) + LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = {int:current_board} AND lmr.id_member = {int:current_member}) + WHERE t.id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_board' => $board, + 'current_member' => $user_info['id'], + 'current_topic' => $topic, + ) + ); + list ($new_from) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Fall through to the next if statement. + $_REQUEST['start'] = 'msg' . $new_from; + } + } + + // Start from a certain time index, not a message. + if (substr($_REQUEST['start'], 0, 4) == 'from') + { + $timestamp = (int) substr($_REQUEST['start'], 4); + if ($timestamp === 0) + $_REQUEST['start'] = 0; + else + { + // Find the number of messages posted before said time... + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}messages + WHERE poster_time < {int:timestamp} + AND id_topic = {int:current_topic}' . ($modSettings['postmod_active'] && $topicinfo['unapproved_posts'] && !allowedTo('approve_posts') ? ' + AND (approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR id_member = {int:current_member}') . ')' : ''), + array( + 'current_topic' => $topic, + 'current_member' => $user_info['id'], + 'is_approved' => 1, + 'timestamp' => $timestamp, + ) + ); + list ($context['start_from']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Handle view_newest_first options, and get the correct start value. + $_REQUEST['start'] = empty($options['view_newest_first']) ? $context['start_from'] : $context['total_visible_posts'] - $context['start_from'] - 1; + } + } + + // Link to a message... + elseif (substr($_REQUEST['start'], 0, 3) == 'msg') + { + $virtual_msg = (int) substr($_REQUEST['start'], 3); + if (!$topicinfo['unapproved_posts'] && $virtual_msg >= $topicinfo['id_last_msg']) + $context['start_from'] = $context['total_visible_posts'] - 1; + elseif (!$topicinfo['unapproved_posts'] && $virtual_msg <= $topicinfo['id_first_msg']) + $context['start_from'] = 0; + else + { + // Find the start value for that message...... + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}messages + WHERE id_msg < {int:virtual_msg} + AND id_topic = {int:current_topic}' . ($modSettings['postmod_active'] && $topicinfo['unapproved_posts'] && !allowedTo('approve_posts') ? ' + AND (approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR id_member = {int:current_member}') . ')' : ''), + array( + 'current_member' => $user_info['id'], + 'current_topic' => $topic, + 'virtual_msg' => $virtual_msg, + 'is_approved' => 1, + 'no_member' => 0, + ) + ); + list ($context['start_from']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + // We need to reverse the start as well in this case. + $_REQUEST['start'] = empty($options['view_newest_first']) ? $context['start_from'] : $context['total_visible_posts'] - $context['start_from'] - 1; + } + } + + // Create a previous next string if the selected theme has it as a selected option. + $context['previous_next'] = $modSettings['enablePreviousNext'] ? '' . $txt['previous_next_back'] . ' ' . $txt['previous_next_forward'] . '' : ''; + + // Check if spellchecking is both enabled and actually working. (for quick reply.) + $context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new'); + + // Do we need to show the visual verification image? + $context['require_verification'] = !$user_info['is_mod'] && !$user_info['is_admin'] && !empty($modSettings['posts_require_captcha']) && ($user_info['posts'] < $modSettings['posts_require_captcha'] || ($user_info['is_guest'] && $modSettings['posts_require_captcha'] == -1)); + if ($context['require_verification']) + { + require_once($sourcedir . '/Subs-Editor.php'); + $verificationOptions = array( + 'id' => 'post', + ); + $context['require_verification'] = create_control_verification($verificationOptions); + $context['visual_verification_id'] = $verificationOptions['id']; + } + + // Are we showing signatures - or disabled fields? + $context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1; + $context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array(); + + // Censor the title... + censorText($topicinfo['subject']); + $context['page_title'] = $topicinfo['subject']; + + // Is this topic sticky, or can it even be? + $topicinfo['is_sticky'] = empty($modSettings['enableStickyTopics']) ? '0' : $topicinfo['is_sticky']; + + // Default this topic to not marked for notifications... of course... + $context['is_marked_notify'] = false; + + // Did we report a post to a moderator just now? + $context['report_sent'] = isset($_GET['reportsent']); + + // Let's get nosey, who is viewing this topic? + if (!empty($settings['display_who_viewing'])) + { + // Start out with no one at all viewing it. + $context['view_members'] = array(); + $context['view_members_list'] = array(); + $context['view_num_hidden'] = 0; + + // Search for members who have this topic set in their GET data. + $request = $smcFunc['db_query']('', ' + SELECT + lo.id_member, lo.log_time, mem.real_name, mem.member_name, mem.show_online, + mg.online_color, mg.id_group, mg.group_name + FROM {db_prefix}log_online AS lo + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lo.id_member) + LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:reg_id_group} THEN mem.id_post_group ELSE mem.id_group END) + WHERE INSTR(lo.url, {string:in_url_string}) > 0 OR lo.session = {string:session}', + array( + 'reg_id_group' => 0, + 'in_url_string' => 's:5:"topic";i:' . $topic . ';', + 'session' => $user_info['is_guest'] ? 'ip' . $user_info['ip'] : session_id(), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (empty($row['id_member'])) + continue; + + if (!empty($row['online_color'])) + $link = '' . $row['real_name'] . ''; + else + $link = '' . $row['real_name'] . ''; + + $is_buddy = in_array($row['id_member'], $user_info['buddies']); + if ($is_buddy) + $link = '' . $link . ''; + + // Add them both to the list and to the more detailed list. + if (!empty($row['show_online']) || allowedTo('moderate_forum')) + $context['view_members_list'][$row['log_time'] . $row['member_name']] = empty($row['show_online']) ? '' . $link . '' : $link; + $context['view_members'][$row['log_time'] . $row['member_name']] = array( + 'id' => $row['id_member'], + 'username' => $row['member_name'], + 'name' => $row['real_name'], + 'group' => $row['id_group'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_member'], + 'link' => $link, + 'is_buddy' => $is_buddy, + 'hidden' => empty($row['show_online']), + ); + + if (empty($row['show_online'])) + $context['view_num_hidden']++; + } + + // The number of guests is equal to the rows minus the ones we actually used ;). + $context['view_num_guests'] = $smcFunc['db_num_rows']($request) - count($context['view_members']); + $smcFunc['db_free_result']($request); + + // Sort the list. + krsort($context['view_members']); + krsort($context['view_members_list']); + } + + // If all is set, but not allowed... just unset it. + $can_show_all = !empty($modSettings['enableAllMessages']) && $context['total_visible_posts'] > $context['messages_per_page'] && $context['total_visible_posts'] < $modSettings['enableAllMessages']; + if (isset($_REQUEST['all']) && !$can_show_all) + unset($_REQUEST['all']); + // Otherwise, it must be allowed... so pretend start was -1. + elseif (isset($_REQUEST['all'])) + $_REQUEST['start'] = -1; + + // Construct the page index, allowing for the .START method... + $context['page_index'] = constructPageIndex($scripturl . '?topic=' . $topic . '.%1$d', $_REQUEST['start'], $context['total_visible_posts'], $context['messages_per_page'], true); + $context['start'] = $_REQUEST['start']; + + // This is information about which page is current, and which page we're on - in case you don't like the constructed page index. (again, wireles..) + $context['page_info'] = array( + 'current_page' => $_REQUEST['start'] / $context['messages_per_page'] + 1, + 'num_pages' => floor(($context['total_visible_posts'] - 1) / $context['messages_per_page']) + 1, + ); + + // Figure out all the link to the next/prev/first/last/etc. for wireless mainly. + $context['links'] = array( + 'first' => $_REQUEST['start'] >= $context['messages_per_page'] ? $scripturl . '?topic=' . $topic . '.0' : '', + 'prev' => $_REQUEST['start'] >= $context['messages_per_page'] ? $scripturl . '?topic=' . $topic . '.' . ($_REQUEST['start'] - $context['messages_per_page']) : '', + 'next' => $_REQUEST['start'] + $context['messages_per_page'] < $context['total_visible_posts'] ? $scripturl . '?topic=' . $topic. '.' . ($_REQUEST['start'] + $context['messages_per_page']) : '', + 'last' => $_REQUEST['start'] + $context['messages_per_page'] < $context['total_visible_posts'] ? $scripturl . '?topic=' . $topic. '.' . (floor($context['total_visible_posts'] / $context['messages_per_page']) * $context['messages_per_page']) : '', + 'up' => $scripturl . '?board=' . $board . '.0' + ); + + // If they are viewing all the posts, show all the posts, otherwise limit the number. + if ($can_show_all) + { + if (isset($_REQUEST['all'])) + { + // No limit! (actually, there is a limit, but...) + $context['messages_per_page'] = -1; + $context['page_index'] .= empty($modSettings['compactTopicPagesEnable']) ? '' . $txt['all'] . ' ' : '[' . $txt['all'] . '] '; + + // Set start back to 0... + $_REQUEST['start'] = 0; + } + // They aren't using it, but the *option* is there, at least. + else + $context['page_index'] .= ' ' . $txt['all'] . ' '; + } + + // Build the link tree. + $context['linktree'][] = array( + 'url' => $scripturl . '?topic=' . $topic . '.0', + 'name' => $topicinfo['subject'], + 'extra_before' => $settings['linktree_inline'] ? $txt['topic'] . ': ' : '' + ); + + // Build a list of this board's moderators. + $context['moderators'] = &$board_info['moderators']; + $context['link_moderators'] = array(); + if (!empty($board_info['moderators'])) + { + // Add a link for each moderator... + foreach ($board_info['moderators'] as $mod) + $context['link_moderators'][] = '' . $mod['name'] . ''; + + // And show it after the board's name. + $context['linktree'][count($context['linktree']) - 2]['extra_after'] = ' (' . (count($context['link_moderators']) == 1 ? $txt['moderator'] : $txt['moderators']) . ': ' . implode(', ', $context['link_moderators']) . ')'; + } + + // Information about the current topic... + $context['is_locked'] = $topicinfo['locked']; + $context['is_sticky'] = $topicinfo['is_sticky']; + $context['is_very_hot'] = $topicinfo['num_replies'] >= $modSettings['hotTopicVeryPosts']; + $context['is_hot'] = $topicinfo['num_replies'] >= $modSettings['hotTopicPosts']; + $context['is_approved'] = $topicinfo['approved']; + + // We don't want to show the poll icon in the topic class here, so pretend it's not one. + $context['is_poll'] = false; + determineTopicClass($context); + + $context['is_poll'] = $topicinfo['id_poll'] > 0 && $modSettings['pollMode'] == '1' && allowedTo('poll_view'); + + // Did this user start the topic or not? + $context['user']['started'] = $user_info['id'] == $topicinfo['id_member_started'] && !$user_info['is_guest']; + $context['topic_starter_id'] = $topicinfo['id_member_started']; + + // Set the topic's information for the template. + $context['subject'] = $topicinfo['subject']; + $context['num_views'] = $topicinfo['num_views']; + $context['mark_unread_time'] = $topicinfo['new_from']; + + // Set a canonical URL for this page. + $context['canonical_url'] = $scripturl . '?topic=' . $topic . '.' . $context['start']; + + // For quick reply we need a response prefix in the default forum language. + if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix', 600))) + { + if ($language === $user_info['language']) + $context['response_prefix'] = $txt['response_prefix']; + else + { + loadLanguage('index', $language, false); + $context['response_prefix'] = $txt['response_prefix']; + loadLanguage('index'); + } + cache_put_data('response_prefix', $context['response_prefix'], 600); + } + + // If we want to show event information in the topic, prepare the data. + if (allowedTo('calendar_view') && !empty($modSettings['cal_showInTopic']) && !empty($modSettings['cal_enabled'])) + { + // First, try create a better time format, ignoring the "time" elements. + if (preg_match('~%[AaBbCcDdeGghjmuYy](?:[^%]*%[AaBbCcDdeGghjmuYy])*~', $user_info['time_format'], $matches) == 0 || empty($matches[0])) + $date_string = $user_info['time_format']; + else + $date_string = $matches[0]; + + // Any calendar information for this topic? + $request = $smcFunc['db_query']('', ' + SELECT cal.id_event, cal.start_date, cal.end_date, cal.title, cal.id_member, mem.real_name + FROM {db_prefix}calendar AS cal + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = cal.id_member) + WHERE cal.id_topic = {int:current_topic} + ORDER BY start_date', + array( + 'current_topic' => $topic, + ) + ); + $context['linked_calendar_events'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Prepare the dates for being formatted. + $start_date = sscanf($row['start_date'], '%04d-%02d-%02d'); + $start_date = mktime(12, 0, 0, $start_date[1], $start_date[2], $start_date[0]); + $end_date = sscanf($row['end_date'], '%04d-%02d-%02d'); + $end_date = mktime(12, 0, 0, $end_date[1], $end_date[2], $end_date[0]); + + $context['linked_calendar_events'][] = array( + 'id' => $row['id_event'], + 'title' => $row['title'], + 'can_edit' => allowedTo('calendar_edit_any') || ($row['id_member'] == $user_info['id'] && allowedTo('calendar_edit_own')), + 'modify_href' => $scripturl . '?action=post;msg=' . $topicinfo['id_first_msg'] . ';topic=' . $topic . '.0;calendar;eventid=' . $row['id_event'] . ';' . $context['session_var'] . '=' . $context['session_id'], + 'start_date' => timeformat($start_date, $date_string, 'none'), + 'start_timestamp' => $start_date, + 'end_date' => timeformat($end_date, $date_string, 'none'), + 'end_timestamp' => $end_date, + 'is_last' => false + ); + } + $smcFunc['db_free_result']($request); + + if (!empty($context['linked_calendar_events'])) + $context['linked_calendar_events'][count($context['linked_calendar_events']) - 1]['is_last'] = true; + } + + // Create the poll info if it exists. + if ($context['is_poll']) + { + // Get the question and if it's locked. + $request = $smcFunc['db_query']('', ' + SELECT + p.question, p.voting_locked, p.hide_results, p.expire_time, p.max_votes, p.change_vote, + p.guest_vote, p.id_member, IFNULL(mem.real_name, p.poster_name) AS poster_name, p.num_guest_voters, p.reset_poll + FROM {db_prefix}polls AS p + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = p.id_member) + WHERE p.id_poll = {int:id_poll} + LIMIT 1', + array( + 'id_poll' => $topicinfo['id_poll'], + ) + ); + $pollinfo = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(DISTINCT id_member) AS total + FROM {db_prefix}log_polls + WHERE id_poll = {int:id_poll} + AND id_member != {int:not_guest}', + array( + 'id_poll' => $topicinfo['id_poll'], + 'not_guest' => 0, + ) + ); + list ($pollinfo['total']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Total voters needs to include guest voters + $pollinfo['total'] += $pollinfo['num_guest_voters']; + + // Get all the options, and calculate the total votes. + $request = $smcFunc['db_query']('', ' + SELECT pc.id_choice, pc.label, pc.votes, IFNULL(lp.id_choice, -1) AS voted_this + FROM {db_prefix}poll_choices AS pc + LEFT JOIN {db_prefix}log_polls AS lp ON (lp.id_choice = pc.id_choice AND lp.id_poll = {int:id_poll} AND lp.id_member = {int:current_member} AND lp.id_member != {int:not_guest}) + WHERE pc.id_poll = {int:id_poll}', + array( + 'current_member' => $user_info['id'], + 'id_poll' => $topicinfo['id_poll'], + 'not_guest' => 0, + ) + ); + $pollOptions = array(); + $realtotal = 0; + $pollinfo['has_voted'] = false; + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + censorText($row['label']); + $pollOptions[$row['id_choice']] = $row; + $realtotal += $row['votes']; + $pollinfo['has_voted'] |= $row['voted_this'] != -1; + } + $smcFunc['db_free_result']($request); + + // If this is a guest we need to do our best to work out if they have voted, and what they voted for. + if ($user_info['is_guest'] && $pollinfo['guest_vote'] && allowedTo('poll_vote')) + { + if (!empty($_COOKIE['guest_poll_vote']) && preg_match('~^[0-9,;]+$~', $_COOKIE['guest_poll_vote']) && strpos($_COOKIE['guest_poll_vote'], ';' . $topicinfo['id_poll'] . ',') !== false) + { + // ;id,timestamp,[vote,vote...]; etc + $guestinfo = explode(';', $_COOKIE['guest_poll_vote']); + // Find the poll we're after. + foreach ($guestinfo as $i => $guestvoted) + { + $guestvoted = explode(',', $guestvoted); + if ($guestvoted[0] == $topicinfo['id_poll']) + break; + } + // Has the poll been reset since guest voted? + if ($pollinfo['reset_poll'] > $guestvoted[1]) + { + // Remove the poll info from the cookie to allow guest to vote again + unset($guestinfo[$i]); + if (!empty($guestinfo)) + $_COOKIE['guest_poll_vote'] = ';' . implode(';', $guestinfo); + else + unset($_COOKIE['guest_poll_vote']); + } + else + { + // What did they vote for? + unset($guestvoted[0], $guestvoted[1]); + foreach ($pollOptions as $choice => $details) + { + $pollOptions[$choice]['voted_this'] = in_array($choice, $guestvoted) ? 1 : -1; + $pollinfo['has_voted'] |= $pollOptions[$choice]['voted_this'] != -1; + } + unset($choice, $details, $guestvoted); + } + unset($guestinfo, $guestvoted, $i); + } + } + + // Set up the basic poll information. + $context['poll'] = array( + 'id' => $topicinfo['id_poll'], + 'image' => 'normal_' . (empty($pollinfo['voting_locked']) ? 'poll' : 'locked_poll'), + 'question' => parse_bbc($pollinfo['question']), + 'total_votes' => $pollinfo['total'], + 'change_vote' => !empty($pollinfo['change_vote']), + 'is_locked' => !empty($pollinfo['voting_locked']), + 'options' => array(), + 'lock' => allowedTo('poll_lock_any') || ($context['user']['started'] && allowedTo('poll_lock_own')), + 'edit' => allowedTo('poll_edit_any') || ($context['user']['started'] && allowedTo('poll_edit_own')), + 'allowed_warning' => $pollinfo['max_votes'] > 1 ? sprintf($txt['poll_options6'], min(count($pollOptions), $pollinfo['max_votes'])) : '', + 'is_expired' => !empty($pollinfo['expire_time']) && $pollinfo['expire_time'] < time(), + 'expire_time' => !empty($pollinfo['expire_time']) ? timeformat($pollinfo['expire_time']) : 0, + 'has_voted' => !empty($pollinfo['has_voted']), + 'starter' => array( + 'id' => $pollinfo['id_member'], + 'name' => $row['poster_name'], + 'href' => $pollinfo['id_member'] == 0 ? '' : $scripturl . '?action=profile;u=' . $pollinfo['id_member'], + 'link' => $pollinfo['id_member'] == 0 ? $row['poster_name'] : '' . $row['poster_name'] . '' + ) + ); + + // Make the lock and edit permissions defined above more directly accessible. + $context['allow_lock_poll'] = $context['poll']['lock']; + $context['allow_edit_poll'] = $context['poll']['edit']; + + // You're allowed to vote if: + // 1. the poll did not expire, and + // 2. you're either not a guest OR guest voting is enabled... and + // 3. you're not trying to view the results, and + // 4. the poll is not locked, and + // 5. you have the proper permissions, and + // 6. you haven't already voted before. + $context['allow_vote'] = !$context['poll']['is_expired'] && (!$user_info['is_guest'] || ($pollinfo['guest_vote'] && allowedTo('poll_vote'))) && empty($pollinfo['voting_locked']) && allowedTo('poll_vote') && !$context['poll']['has_voted']; + + // You're allowed to view the results if: + // 1. you're just a super-nice-guy, or + // 2. anyone can see them (hide_results == 0), or + // 3. you can see them after you voted (hide_results == 1), or + // 4. you've waited long enough for the poll to expire. (whether hide_results is 1 or 2.) + $context['allow_poll_view'] = allowedTo('moderate_board') || $pollinfo['hide_results'] == 0 || ($pollinfo['hide_results'] == 1 && $context['poll']['has_voted']) || $context['poll']['is_expired']; + $context['poll']['show_results'] = $context['allow_poll_view'] && (isset($_REQUEST['viewresults']) || isset($_REQUEST['viewResults'])); + $context['show_view_results_button'] = $context['allow_vote'] && (!$context['allow_poll_view'] || !$context['poll']['show_results'] || !$context['poll']['has_voted']); + + // You're allowed to change your vote if: + // 1. the poll did not expire, and + // 2. you're not a guest... and + // 3. the poll is not locked, and + // 4. you have the proper permissions, and + // 5. you have already voted, and + // 6. the poll creator has said you can! + $context['allow_change_vote'] = !$context['poll']['is_expired'] && !$user_info['is_guest'] && empty($pollinfo['voting_locked']) && allowedTo('poll_vote') && $context['poll']['has_voted'] && $context['poll']['change_vote']; + + // You're allowed to return to voting options if: + // 1. you are (still) allowed to vote. + // 2. you are currently seeing the results. + $context['allow_return_vote'] = $context['allow_vote'] && $context['poll']['show_results']; + + // Calculate the percentages and bar lengths... + $divisor = $realtotal == 0 ? 1 : $realtotal; + + // Determine if a decimal point is needed in order for the options to add to 100%. + $precision = $realtotal == 100 ? 0 : 1; + + // Now look through each option, and... + foreach ($pollOptions as $i => $option) + { + // First calculate the percentage, and then the width of the bar... + $bar = round(($option['votes'] * 100) / $divisor, $precision); + $barWide = $bar == 0 ? 1 : floor(($bar * 8) / 3); + + // Now add it to the poll's contextual theme data. + $context['poll']['options'][$i] = array( + 'id' => 'options-' . $i, + 'percent' => $bar, + 'votes' => $option['votes'], + 'voted_this' => $option['voted_this'] != -1, + 'bar' => '-', + // Note: IE < 8 requires us to set a width on the container, too. + 'bar_ndt' => $bar > 0 ? '
' : '', + 'bar_width' => $barWide, + 'option' => parse_bbc($option['label']), + 'vote_button' => '' + ); + } + } + + // Calculate the fastest way to get the messages! + $ascending = empty($options['view_newest_first']); + $start = $_REQUEST['start']; + $limit = $context['messages_per_page']; + $firstIndex = 0; + if ($start >= $context['total_visible_posts'] / 2 && $context['messages_per_page'] != -1) + { + $ascending = !$ascending; + $limit = $context['total_visible_posts'] <= $start + $limit ? $context['total_visible_posts'] - $start : $limit; + $start = $context['total_visible_posts'] <= $start + $limit ? 0 : $context['total_visible_posts'] - $start - $limit; + $firstIndex = $limit - 1; + } + + // Get each post and poster in this topic. + $request = $smcFunc['db_query']('display_get_post_poster', ' + SELECT id_msg, id_member, approved + FROM {db_prefix}messages + WHERE id_topic = {int:current_topic}' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : (!empty($modSettings['db_mysql_group_by_fix']) ? '' : ' + GROUP BY id_msg') . ' + HAVING (approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR id_member = {int:current_member}') . ')') . ' + ORDER BY id_msg ' . ($ascending ? '' : 'DESC') . ($context['messages_per_page'] == -1 ? '' : ' + LIMIT ' . $start . ', ' . $limit), + array( + 'current_member' => $user_info['id'], + 'current_topic' => $topic, + 'is_approved' => 1, + 'blank_id_member' => 0, + ) + ); + + $messages = array(); + $all_posters = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!empty($row['id_member'])) + $all_posters[$row['id_msg']] = $row['id_member']; + $messages[] = $row['id_msg']; + } + $smcFunc['db_free_result']($request); + $posters = array_unique($all_posters); + + // Guests can't mark topics read or for notifications, just can't sorry. + if (!$user_info['is_guest']) + { + $mark_at_msg = max($messages); + if ($mark_at_msg >= $topicinfo['id_last_msg']) + $mark_at_msg = $modSettings['maxMsgID']; + if ($mark_at_msg >= $topicinfo['new_from']) + { + $smcFunc['db_insert']($topicinfo['new_from'] == 0 ? 'ignore' : 'replace', + '{db_prefix}log_topics', + array( + 'id_member' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', + ), + array( + $user_info['id'], $topic, $mark_at_msg, + ), + array('id_member', 'id_topic') + ); + } + + // Check for notifications on this topic OR board. + $request = $smcFunc['db_query']('', ' + SELECT sent, id_topic + FROM {db_prefix}log_notify + WHERE (id_topic = {int:current_topic} OR id_board = {int:current_board}) + AND id_member = {int:current_member} + LIMIT 2', + array( + 'current_board' => $board, + 'current_member' => $user_info['id'], + 'current_topic' => $topic, + ) + ); + $do_once = true; + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Find if this topic is marked for notification... + if (!empty($row['id_topic'])) + $context['is_marked_notify'] = true; + + // Only do this once, but mark the notifications as "not sent yet" for next time. + if (!empty($row['sent']) && $do_once) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_notify + SET sent = {int:is_not_sent} + WHERE (id_topic = {int:current_topic} OR id_board = {int:current_board}) + AND id_member = {int:current_member}', + array( + 'current_board' => $board, + 'current_member' => $user_info['id'], + 'current_topic' => $topic, + 'is_not_sent' => 0, + ) + ); + $do_once = false; + } + } + + // Have we recently cached the number of new topics in this board, and it's still a lot? + if (isset($_REQUEST['topicseen']) && isset($_SESSION['topicseen_cache'][$board]) && $_SESSION['topicseen_cache'][$board] > 5) + $_SESSION['topicseen_cache'][$board]--; + // Mark board as seen if this is the only new topic. + elseif (isset($_REQUEST['topicseen'])) + { + // Use the mark read tables... and the last visit to figure out if this should be read or not. + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}topics AS t + LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = {int:current_board} AND lb.id_member = {int:current_member}) + LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member}) + WHERE t.id_board = {int:current_board} + AND t.id_last_msg > IFNULL(lb.id_msg, 0) + AND t.id_last_msg > IFNULL(lt.id_msg, 0)' . (empty($_SESSION['id_msg_last_visit']) ? '' : ' + AND t.id_last_msg > {int:id_msg_last_visit}'), + array( + 'current_board' => $board, + 'current_member' => $user_info['id'], + 'id_msg_last_visit' => (int) $_SESSION['id_msg_last_visit'], + ) + ); + list ($numNewTopics) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // If there're no real new topics in this board, mark the board as seen. + if (empty($numNewTopics)) + $_REQUEST['boardseen'] = true; + else + $_SESSION['topicseen_cache'][$board] = $numNewTopics; + } + // Probably one less topic - maybe not, but even if we decrease this too fast it will only make us look more often. + elseif (isset($_SESSION['topicseen_cache'][$board])) + $_SESSION['topicseen_cache'][$board]--; + + // Mark board as seen if we came using last post link from BoardIndex. (or other places...) + if (isset($_REQUEST['boardseen'])) + { + $smcFunc['db_insert']('replace', + '{db_prefix}log_boards', + array('id_msg' => 'int', 'id_member' => 'int', 'id_board' => 'int'), + array($modSettings['maxMsgID'], $user_info['id'], $board), + array('id_member', 'id_board') + ); + } + } + + $attachments = array(); + + // If there _are_ messages here... (probably an error otherwise :!) + if (!empty($messages)) + { + // Fetch attachments. + if (!empty($modSettings['attachmentEnable']) && allowedTo('view_attachments')) + { + $request = $smcFunc['db_query']('', ' + SELECT + a.id_attach, a.id_folder, a.id_msg, a.filename, a.file_hash, IFNULL(a.size, 0) AS filesize, a.downloads, a.approved, + a.width, a.height' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ', + IFNULL(thumb.id_attach, 0) AS id_thumb, thumb.width AS thumb_width, thumb.height AS thumb_height') . ' + FROM {db_prefix}attachments AS a' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ' + LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)') . ' + WHERE a.id_msg IN ({array_int:message_list}) + AND a.attachment_type = {int:attachment_type}', + array( + 'message_list' => $messages, + 'attachment_type' => 0, + 'is_approved' => 1, + ) + ); + $temp = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!$row['approved'] && $modSettings['postmod_active'] && !allowedTo('approve_posts') && (!isset($all_posters[$row['id_msg']]) || $all_posters[$row['id_msg']] != $user_info['id'])) + continue; + + $temp[$row['id_attach']] = $row; + + if (!isset($attachments[$row['id_msg']])) + $attachments[$row['id_msg']] = array(); + } + $smcFunc['db_free_result']($request); + + // This is better than sorting it with the query... + ksort($temp); + + foreach ($temp as $row) + $attachments[$row['id_msg']][] = $row; + } + + // What? It's not like it *couldn't* be only guests in this topic... + if (!empty($posters)) + loadMemberData($posters); + $messages_request = $smcFunc['db_query']('', ' + SELECT + id_msg, icon, subject, poster_time, poster_ip, id_member, modified_time, modified_name, body, + smileys_enabled, poster_name, poster_email, approved, + id_msg_modified < {int:new_from} AS is_read + FROM {db_prefix}messages + WHERE id_msg IN ({array_int:message_list}) + ORDER BY id_msg' . (empty($options['view_newest_first']) ? '' : ' DESC'), + array( + 'message_list' => $messages, + 'new_from' => $topicinfo['new_from'], + ) + ); + + // Go to the last message if the given time is beyond the time of the last message. + if (isset($context['start_from']) && $context['start_from'] >= $topicinfo['num_replies']) + $context['start_from'] = $topicinfo['num_replies']; + + // Since the anchor information is needed on the top of the page we load these variables beforehand. + $context['first_message'] = isset($messages[$firstIndex]) ? $messages[$firstIndex] : $messages[0]; + if (empty($options['view_newest_first'])) + $context['first_new_message'] = isset($context['start_from']) && $_REQUEST['start'] == $context['start_from']; + else + $context['first_new_message'] = isset($context['start_from']) && $_REQUEST['start'] == $topicinfo['num_replies'] - $context['start_from']; + } + else + { + $messages_request = false; + $context['first_message'] = 0; + $context['first_new_message'] = false; + } + + $context['jump_to'] = array( + 'label' => addslashes(un_htmlspecialchars($txt['jump_to'])), + 'board_name' => htmlspecialchars(strtr(strip_tags($board_info['name']), array('&' => '&'))), + 'child_level' => $board_info['child_level'], + ); + + // Set the callback. (do you REALIZE how much memory all the messages would take?!?) + $context['get_message'] = 'prepareDisplayContext'; + + // Now set all the wonderful, wonderful permissions... like moderation ones... + $common_permissions = array( + 'can_approve' => 'approve_posts', + 'can_ban' => 'manage_bans', + 'can_sticky' => 'make_sticky', + 'can_merge' => 'merge_any', + 'can_split' => 'split_any', + 'calendar_post' => 'calendar_post', + 'can_mark_notify' => 'mark_any_notify', + 'can_send_topic' => 'send_topic', + 'can_send_pm' => 'pm_send', + 'can_report_moderator' => 'report_any', + 'can_moderate_forum' => 'moderate_forum', + 'can_issue_warning' => 'issue_warning', + 'can_restore_topic' => 'move_any', + 'can_restore_msg' => 'move_any', + ); + foreach ($common_permissions as $contextual => $perm) + $context[$contextual] = allowedTo($perm); + + // Permissions with _any/_own versions. $context[YYY] => ZZZ_any/_own. + $anyown_permissions = array( + 'can_move' => 'move', + 'can_lock' => 'lock', + 'can_delete' => 'remove', + 'can_add_poll' => 'poll_add', + 'can_remove_poll' => 'poll_remove', + 'can_reply' => 'post_reply', + 'can_reply_unapproved' => 'post_unapproved_replies', + ); + foreach ($anyown_permissions as $contextual => $perm) + $context[$contextual] = allowedTo($perm . '_any') || ($context['user']['started'] && allowedTo($perm . '_own')); + + // Cleanup all the permissions with extra stuff... + $context['can_mark_notify'] &= !$context['user']['is_guest']; + $context['can_sticky'] &= !empty($modSettings['enableStickyTopics']); + $context['calendar_post'] &= !empty($modSettings['cal_enabled']); + $context['can_add_poll'] &= $modSettings['pollMode'] == '1' && $topicinfo['id_poll'] <= 0; + $context['can_remove_poll'] &= $modSettings['pollMode'] == '1' && $topicinfo['id_poll'] > 0; + $context['can_reply'] &= empty($topicinfo['locked']) || allowedTo('moderate_board'); + $context['can_reply_unapproved'] &= $modSettings['postmod_active'] && (empty($topicinfo['locked']) || allowedTo('moderate_board')); + $context['can_issue_warning'] &= in_array('w', $context['admin_features']) && $modSettings['warning_settings'][0] == 1; + // Handle approval flags... + $context['can_reply_approved'] = $context['can_reply']; + $context['can_reply'] |= $context['can_reply_unapproved']; + $context['can_quote'] = $context['can_reply'] && (empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC']))); + $context['can_mark_unread'] = !$user_info['is_guest'] && $settings['show_mark_read']; + + $context['can_send_topic'] = (!$modSettings['postmod_active'] || $topicinfo['approved']) && allowedTo('send_topic'); + + // Start this off for quick moderation - it will be or'd for each post. + $context['can_remove_post'] = allowedTo('delete_any') || (allowedTo('delete_replies') && $context['user']['started']); + + // Can restore topic? That's if the topic is in the recycle board and has a previous restore state. + $context['can_restore_topic'] &= !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board && !empty($topicinfo['id_previous_board']); + $context['can_restore_msg'] &= !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board && !empty($topicinfo['id_previous_topic']); + + // Wireless shows a "more" if you can do anything special. + if (WIRELESS && WIRELESS_PROTOCOL != 'wap') + { + $context['wireless_more'] = $context['can_sticky'] || $context['can_lock'] || allowedTo('modify_any'); + $context['wireless_moderate'] = isset($_GET['moderate']) ? ';moderate' : ''; + } + + // Load up the "double post" sequencing magic. + if (!empty($options['display_quick_reply'])) + { + checkSubmitOnce('register'); + $context['name'] = isset($_SESSION['guest_name']) ? $_SESSION['guest_name'] : ''; + $context['email'] = isset($_SESSION['guest_email']) ? $_SESSION['guest_email'] : ''; + } +} + +// Callback for the message display. +function prepareDisplayContext($reset = false) +{ + global $settings, $txt, $modSettings, $scripturl, $options, $user_info, $smcFunc; + global $memberContext, $context, $messages_request, $topic, $attachments, $topicinfo; + + static $counter = null; + + // If the query returned false, bail. + if ($messages_request == false) + return false; + + // Remember which message this is. (ie. reply #83) + if ($counter === null || $reset) + $counter = empty($options['view_newest_first']) ? $context['start'] : $context['total_visible_posts'] - $context['start']; + + // Start from the beginning... + if ($reset) + return @$smcFunc['db_data_seek']($messages_request, 0); + + // Attempt to get the next message. + $message = $smcFunc['db_fetch_assoc']($messages_request); + if (!$message) + { + $smcFunc['db_free_result']($messages_request); + return false; + } + + // $context['icon_sources'] says where each icon should come from - here we set up the ones which will always exist! + if (empty($context['icon_sources'])) + { + $stable_icons = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'moved', 'recycled', 'wireless', 'clip'); + $context['icon_sources'] = array(); + foreach ($stable_icons as $icon) + $context['icon_sources'][$icon] = 'images_url'; + } + + // Message Icon Management... check the images exist. + if (empty($modSettings['messageIconChecks_disable'])) + { + // If the current icon isn't known, then we need to do something... + if (!isset($context['icon_sources'][$message['icon']])) + $context['icon_sources'][$message['icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['icon'] . '.gif') ? 'images_url' : 'default_images_url'; + } + elseif (!isset($context['icon_sources'][$message['icon']])) + $context['icon_sources'][$message['icon']] = 'images_url'; + + // If you're a lazy bum, you probably didn't give a subject... + $message['subject'] = $message['subject'] != '' ? $message['subject'] : $txt['no_subject']; + + // Are you allowed to remove at least a single reply? + $context['can_remove_post'] |= allowedTo('delete_own') && (empty($modSettings['edit_disable_time']) || $message['poster_time'] + $modSettings['edit_disable_time'] * 60 >= time()) && $message['id_member'] == $user_info['id']; + + // If it couldn't load, or the user was a guest.... someday may be done with a guest table. + if (!loadMemberContext($message['id_member'], true)) + { + // Notice this information isn't used anywhere else.... + $memberContext[$message['id_member']]['name'] = $message['poster_name']; + $memberContext[$message['id_member']]['id'] = 0; + $memberContext[$message['id_member']]['group'] = $txt['guest_title']; + $memberContext[$message['id_member']]['link'] = $message['poster_name']; + $memberContext[$message['id_member']]['email'] = $message['poster_email']; + $memberContext[$message['id_member']]['show_email'] = showEmailAddress(true, 0); + $memberContext[$message['id_member']]['is_guest'] = true; + } + else + { + $memberContext[$message['id_member']]['can_view_profile'] = allowedTo('profile_view_any') || ($message['id_member'] == $user_info['id'] && allowedTo('profile_view_own')); + $memberContext[$message['id_member']]['is_topic_starter'] = $message['id_member'] == $context['topic_starter_id']; + $memberContext[$message['id_member']]['can_see_warning'] = !isset($context['disabled_fields']['warning_status']) && $memberContext[$message['id_member']]['warning_status'] && ($context['user']['can_mod'] || (!$user_info['is_guest'] && !empty($modSettings['warning_show']) && ($modSettings['warning_show'] > 1 || $message['id_member'] == $user_info['id']))); + } + + $memberContext[$message['id_member']]['ip'] = $message['poster_ip']; + + // Do the censor thang. + censorText($message['body']); + censorText($message['subject']); + + // Run BBC interpreter on the message. + $message['body'] = parse_bbc($message['body'], $message['smileys_enabled'], $message['id_msg']); + + // Compose the memory eat- I mean message array. + $output = array( + 'attachment' => loadAttachmentContext($message['id_msg']), + 'alternate' => $counter % 2, + 'id' => $message['id_msg'], + 'href' => $scripturl . '?topic=' . $topic . '.msg' . $message['id_msg'] . '#msg' . $message['id_msg'], + 'link' => '' . $message['subject'] . '', + 'member' => &$memberContext[$message['id_member']], + 'icon' => $message['icon'], + 'icon_url' => $settings[$context['icon_sources'][$message['icon']]] . '/post/' . $message['icon'] . '.gif', + 'subject' => $message['subject'], + 'time' => timeformat($message['poster_time']), + 'timestamp' => forum_time(true, $message['poster_time']), + 'counter' => $counter, + 'modified' => array( + 'time' => timeformat($message['modified_time']), + 'timestamp' => forum_time(true, $message['modified_time']), + 'name' => $message['modified_name'] + ), + 'body' => $message['body'], + 'new' => empty($message['is_read']), + 'approved' => $message['approved'], + 'first_new' => isset($context['start_from']) && $context['start_from'] == $counter, + 'is_ignored' => !empty($modSettings['enable_buddylist']) && !empty($options['posts_apply_ignore_list']) && in_array($message['id_member'], $context['user']['ignoreusers']), + 'can_approve' => !$message['approved'] && $context['can_approve'], + 'can_unapprove' => $message['approved'] && $context['can_approve'], + 'can_modify' => (!$context['is_locked'] || allowedTo('moderate_board')) && (allowedTo('modify_any') || (allowedTo('modify_replies') && $context['user']['started']) || (allowedTo('modify_own') && $message['id_member'] == $user_info['id'] && (empty($modSettings['edit_disable_time']) || !$message['approved'] || $message['poster_time'] + $modSettings['edit_disable_time'] * 60 > time()))), + 'can_remove' => allowedTo('delete_any') || (allowedTo('delete_replies') && $context['user']['started']) || (allowedTo('delete_own') && $message['id_member'] == $user_info['id'] && (empty($modSettings['edit_disable_time']) || $message['poster_time'] + $modSettings['edit_disable_time'] * 60 > time())), + 'can_see_ip' => allowedTo('moderate_forum') || ($message['id_member'] == $user_info['id'] && !empty($user_info['id'])), + ); + + // Is this user the message author? + $output['is_message_author'] = $message['id_member'] == $user_info['id']; + + if (empty($options['view_newest_first'])) + $counter++; + else + $counter--; + + return $output; +} + +// Download an attachment. +function Download() +{ + global $txt, $modSettings, $user_info, $scripturl, $context, $sourcedir, $topic, $smcFunc; + + // Some defaults that we need. + $context['character_set'] = empty($modSettings['global_character_set']) ? (empty($txt['lang_character_set']) ? 'ISO-8859-1' : $txt['lang_character_set']) : $modSettings['global_character_set']; + $context['utf8'] = $context['character_set'] === 'UTF-8' && (strpos(strtolower(PHP_OS), 'win') === false || @version_compare(PHP_VERSION, '4.2.3') != -1); + $context['no_last_modified'] = true; + + // Make sure some attachment was requested! + if (!isset($_REQUEST['attach']) && !isset($_REQUEST['id'])) + fatal_lang_error('no_access', false); + + $_REQUEST['attach'] = isset($_REQUEST['attach']) ? (int) $_REQUEST['attach'] : (int) $_REQUEST['id']; + + if (isset($_REQUEST['type']) && $_REQUEST['type'] == 'avatar') + { + $request = $smcFunc['db_query']('', ' + SELECT id_folder, filename, file_hash, fileext, id_attach, attachment_type, mime_type, approved, id_member + FROM {db_prefix}attachments + WHERE id_attach = {int:id_attach} + AND id_member > {int:blank_id_member} + LIMIT 1', + array( + 'id_attach' => $_REQUEST['attach'], + 'blank_id_member' => 0, + ) + ); + $_REQUEST['image'] = true; + } + // This is just a regular attachment... + else + { + // This checks only the current board for $board/$topic's permissions. + isAllowedTo('view_attachments'); + + // Make sure this attachment is on this board. + // NOTE: We must verify that $topic is the attachment's topic, or else the permission check above is broken. + $request = $smcFunc['db_query']('', ' + SELECT a.id_folder, a.filename, a.file_hash, a.fileext, a.id_attach, a.attachment_type, a.mime_type, a.approved, m.id_member + FROM {db_prefix}attachments AS a + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg AND m.id_topic = {int:current_topic}) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board}) + WHERE a.id_attach = {int:attach} + LIMIT 1', + array( + 'attach' => $_REQUEST['attach'], + 'current_topic' => $topic, + ) + ); + } + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_access', false); + list ($id_folder, $real_filename, $file_hash, $file_ext, $id_attach, $attachment_type, $mime_type, $is_approved, $id_member) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // If it isn't yet approved, do they have permission to view it? + if (!$is_approved && ($id_member == 0 || $user_info['id'] != $id_member) && ($attachment_type == 0 || $attachment_type == 3)) + isAllowedTo('approve_posts'); + + // Update the download counter (unless it's a thumbnail). + if ($attachment_type != 3) + $smcFunc['db_query']('attach_download_increase', ' + UPDATE LOW_PRIORITY {db_prefix}attachments + SET downloads = downloads + 1 + WHERE id_attach = {int:id_attach}', + array( + 'id_attach' => $id_attach, + ) + ); + + $filename = getAttachmentFilename($real_filename, $_REQUEST['attach'], $id_folder, false, $file_hash); + + // This is done to clear any output that was made before now. (would use ob_clean(), but that's PHP 4.2.0+...) + ob_end_clean(); + if (!empty($modSettings['enableCompressedOutput']) && @version_compare(PHP_VERSION, '4.2.0') >= 0 && @filesize($filename) <= 4194304 && in_array($file_ext, array('txt', 'html', 'htm', 'js', 'doc', 'pdf', 'docx', 'rtf', 'css', 'php', 'log', 'xml', 'sql', 'c', 'java'))) + @ob_start('ob_gzhandler'); + else + { + ob_start(); + header('Content-Encoding: none'); + } + + // No point in a nicer message, because this is supposed to be an attachment anyway... + if (!file_exists($filename)) + { + loadLanguage('Errors'); + + header('HTTP/1.0 404 ' . $txt['attachment_not_found']); + header('Content-Type: text/plain; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set'])); + + // We need to die like this *before* we send any anti-caching headers as below. + die('404 - ' . $txt['attachment_not_found']); + } + + // If it hasn't been modified since the last time this attachement was retrieved, there's no need to display it again. + if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) + { + list($modified_since) = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE']); + if (strtotime($modified_since) >= filemtime($filename)) + { + ob_end_clean(); + + // Answer the question - no, it hasn't been modified ;). + header('HTTP/1.1 304 Not Modified'); + exit; + } + } + + // Check whether the ETag was sent back, and cache based on that... + $eTag = '"' . substr($_REQUEST['attach'] . $real_filename . filemtime($filename), 0, 64) . '"'; + if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) && strpos($_SERVER['HTTP_IF_NONE_MATCH'], $eTag) !== false) + { + ob_end_clean(); + + header('HTTP/1.1 304 Not Modified'); + exit; + } + + // Send the attachment headers. + header('Pragma: '); + if (!$context['browser']['is_gecko']) + header('Content-Transfer-Encoding: binary'); + header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 525600 * 60) . ' GMT'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($filename)) . ' GMT'); + header('Accept-Ranges: bytes'); + header('Connection: close'); + header('ETag: ' . $eTag); + + // IE 6 just doesn't play nice. As dirty as this seems, it works. + if ($context['browser']['is_ie6'] && isset($_REQUEST['image'])) + unset($_REQUEST['image']); + + // Make sure the mime type warrants an inline display. + elseif (isset($_REQUEST['image']) && !empty($mime_type) && strpos($mime_type, 'image/') !== 0) + unset($_REQUEST['image']); + + // Does this have a mime type? + elseif (!empty($mime_type) && (isset($_REQUEST['image']) || !in_array($file_ext, array('jpg', 'gif', 'jpeg', 'x-ms-bmp', 'png', 'psd', 'tiff', 'iff')))) + header('Content-Type: ' . strtr($mime_type, array('image/bmp' => 'image/x-ms-bmp'))); + + else + { + header('Content-Type: ' . ($context['browser']['is_ie'] || $context['browser']['is_opera'] ? 'application/octetstream' : 'application/octet-stream')); + if (isset($_REQUEST['image'])) + unset($_REQUEST['image']); + } + + // Convert the file to UTF-8, cuz most browsers dig that. + $utf8name = !$context['utf8'] && function_exists('iconv') ? iconv($context['character_set'], 'UTF-8', $real_filename) : (!$context['utf8'] && function_exists('mb_convert_encoding') ? mb_convert_encoding($real_filename, 'UTF-8', $context['character_set']) : $real_filename); + $fixchar = create_function('$n', ' + if ($n < 32) + return \'\'; + elseif ($n < 128) + return chr($n); + elseif ($n < 2048) + return chr(192 | $n >> 6) . chr(128 | $n & 63); + elseif ($n < 65536) + return chr(224 | $n >> 12) . chr(128 | $n >> 6 & 63) . chr(128 | $n & 63); + else + return chr(240 | $n >> 18) . chr(128 | $n >> 12 & 63) . chr(128 | $n >> 6 & 63) . chr(128 | $n & 63);'); + + $disposition = !isset($_REQUEST['image']) ? 'attachment' : 'inline'; + + // Different browsers like different standards... + if ($context['browser']['is_firefox']) + header('Content-Disposition: ' . $disposition . '; filename*=UTF-8\'\'' . rawurlencode(preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $utf8name))); + + elseif ($context['browser']['is_opera']) + header('Content-Disposition: ' . $disposition . '; filename="' . preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $utf8name) . '"'); + + elseif ($context['browser']['is_ie']) + header('Content-Disposition: ' . $disposition . '; filename="' . urlencode(preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $utf8name)) . '"'); + + else + header('Content-Disposition: ' . $disposition . '; filename="' . $utf8name . '"'); + + // If this has an "image extension" - but isn't actually an image - then ensure it isn't cached cause of silly IE. + if (!isset($_REQUEST['image']) && in_array($file_ext, array('gif', 'jpg', 'bmp', 'png', 'jpeg', 'tiff'))) + header('Cache-Control: no-cache'); + else + header('Cache-Control: max-age=' . (525600 * 60) . ', private'); + + if (empty($modSettings['enableCompressedOutput']) || filesize($filename) > 4194304) + header('Content-Length: ' . filesize($filename)); + + // Try to buy some time... + @set_time_limit(600); + + // Recode line endings for text files, if enabled. + if (!empty($modSettings['attachmentRecodeLineEndings']) && !isset($_REQUEST['image']) && in_array($file_ext, array('txt', 'css', 'htm', 'html', 'php', 'xml'))) + { + if (strpos($_SERVER['HTTP_USER_AGENT'], 'Windows') !== false) + $callback = create_function('$buffer', 'return preg_replace(\'~[\r]?\n~\', "\r\n", $buffer);'); + elseif (strpos($_SERVER['HTTP_USER_AGENT'], 'Mac') !== false) + $callback = create_function('$buffer', 'return preg_replace(\'~[\r]?\n~\', "\r", $buffer);'); + else + $callback = create_function('$buffer', 'return preg_replace(\'~[\r]?\n~\', "\n", $buffer);'); + } + + // Since we don't do output compression for files this large... + if (filesize($filename) > 4194304) + { + // Forcibly end any output buffering going on. + if (function_exists('ob_get_level')) + { + while (@ob_get_level() > 0) + @ob_end_clean(); + } + else + { + @ob_end_clean(); + @ob_end_clean(); + @ob_end_clean(); + } + + $fp = fopen($filename, 'rb'); + while (!feof($fp)) + { + if (isset($callback)) + echo $callback(fread($fp, 8192)); + else + echo fread($fp, 8192); + flush(); + } + fclose($fp); + } + // On some of the less-bright hosts, readfile() is disabled. It's just a faster, more byte safe, version of what's in the if. + elseif (isset($callback) || @readfile($filename) == null) + echo isset($callback) ? $callback(file_get_contents($filename)) : file_get_contents($filename); + + obExit(false); +} + +function loadAttachmentContext($id_msg) +{ + global $attachments, $modSettings, $txt, $scripturl, $topic, $sourcedir, $smcFunc; + + // Set up the attachment info - based on code by Meriadoc. + $attachmentData = array(); + $have_unapproved = false; + if (isset($attachments[$id_msg]) && !empty($modSettings['attachmentEnable'])) + { + foreach ($attachments[$id_msg] as $i => $attachment) + { + $attachmentData[$i] = array( + 'id' => $attachment['id_attach'], + 'name' => preg_replace('~&#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', htmlspecialchars($attachment['filename'])), + 'downloads' => $attachment['downloads'], + 'size' => round($attachment['filesize'] / 1024, 2) . ' ' . $txt['kilobyte'], + 'byte_size' => $attachment['filesize'], + 'href' => $scripturl . '?action=dlattach;topic=' . $topic . '.0;attach=' . $attachment['id_attach'], + 'link' => '' . htmlspecialchars($attachment['filename']) . '', + 'is_image' => !empty($attachment['width']) && !empty($attachment['height']) && !empty($modSettings['attachmentShowImages']), + 'is_approved' => $attachment['approved'], + ); + + // If something is unapproved we'll note it so we can sort them. + if (!$attachment['approved']) + $have_unapproved = true; + + if (!$attachmentData[$i]['is_image']) + continue; + + $attachmentData[$i]['real_width'] = $attachment['width']; + $attachmentData[$i]['width'] = $attachment['width']; + $attachmentData[$i]['real_height'] = $attachment['height']; + $attachmentData[$i]['height'] = $attachment['height']; + + // Let's see, do we want thumbs? + if (!empty($modSettings['attachmentThumbnails']) && !empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachment['width'] > $modSettings['attachmentThumbWidth'] || $attachment['height'] > $modSettings['attachmentThumbHeight']) && strlen($attachment['filename']) < 249) + { + // A proper thumb doesn't exist yet? Create one! + if (empty($attachment['id_thumb']) || $attachment['thumb_width'] > $modSettings['attachmentThumbWidth'] || $attachment['thumb_height'] > $modSettings['attachmentThumbHeight'] || ($attachment['thumb_width'] < $modSettings['attachmentThumbWidth'] && $attachment['thumb_height'] < $modSettings['attachmentThumbHeight'])) + { + $filename = getAttachmentFilename($attachment['filename'], $attachment['id_attach'], $attachment['id_folder']); + + require_once($sourcedir . '/Subs-Graphics.php'); + if (createThumbnail($filename, $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight'])) + { + // So what folder are we putting this image in? + if (!empty($modSettings['currentAttachmentUploadDir'])) + { + if (!is_array($modSettings['attachmentUploadDir'])) + $modSettings['attachmentUploadDir'] = @unserialize($modSettings['attachmentUploadDir']); + $path = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']]; + $id_folder_thumb = $modSettings['currentAttachmentUploadDir']; + } + else + { + $path = $modSettings['attachmentUploadDir']; + $id_folder_thumb = 1; + } + + // Calculate the size of the created thumbnail. + $size = @getimagesize($filename . '_thumb'); + list ($attachment['thumb_width'], $attachment['thumb_height']) = $size; + $thumb_size = filesize($filename . '_thumb'); + + // These are the only valid image types for SMF. + $validImageTypes = array(1 => 'gif', 2 => 'jpeg', 3 => 'png', 5 => 'psd', 6 => 'bmp', 7 => 'tiff', 8 => 'tiff', 9 => 'jpeg', 14 => 'iff'); + + // What about the extension? + $thumb_ext = isset($validImageTypes[$size[2]]) ? $validImageTypes[$size[2]] : ''; + + // Figure out the mime type. + if (!empty($size['mime'])) + $thumb_mime = $size['mime']; + else + $thumb_mime = 'image/' . $thumb_ext; + + $thumb_filename = $attachment['filename'] . '_thumb'; + $thumb_hash = getAttachmentFilename($thumb_filename, false, null, true); + + // Add this beauty to the database. + $smcFunc['db_insert']('', + '{db_prefix}attachments', + array('id_folder' => 'int', 'id_msg' => 'int', 'attachment_type' => 'int', 'filename' => 'string', 'file_hash' => 'string', 'size' => 'int', 'width' => 'int', 'height' => 'int', 'fileext' => 'string', 'mime_type' => 'string'), + array($id_folder_thumb, $id_msg, 3, $thumb_filename, $thumb_hash, (int) $thumb_size, (int) $attachment['thumb_width'], (int) $attachment['thumb_height'], $thumb_ext, $thumb_mime), + array('id_attach') + ); + $old_id_thumb = $attachment['id_thumb']; + $attachment['id_thumb'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach'); + if (!empty($attachment['id_thumb'])) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}attachments + SET id_thumb = {int:id_thumb} + WHERE id_attach = {int:id_attach}', + array( + 'id_thumb' => $attachment['id_thumb'], + 'id_attach' => $attachment['id_attach'], + ) + ); + + $thumb_realname = getAttachmentFilename($thumb_filename, $attachment['id_thumb'], $id_folder_thumb, false, $thumb_hash); + rename($filename . '_thumb', $thumb_realname); + + // Do we need to remove an old thumbnail? + if (!empty($old_id_thumb)) + { + require_once($sourcedir . '/ManageAttachments.php'); + removeAttachments(array('id_attach' => $old_id_thumb), '', false, false); + } + } + } + } + + // Only adjust dimensions on successful thumbnail creation. + if (!empty($attachment['thumb_width']) && !empty($attachment['thumb_height'])) + { + $attachmentData[$i]['width'] = $attachment['thumb_width']; + $attachmentData[$i]['height'] = $attachment['thumb_height']; + } + } + + if (!empty($attachment['id_thumb'])) + $attachmentData[$i]['thumbnail'] = array( + 'id' => $attachment['id_thumb'], + 'href' => $scripturl . '?action=dlattach;topic=' . $topic . '.0;attach=' . $attachment['id_thumb'] . ';image', + ); + $attachmentData[$i]['thumbnail']['has_thumb'] = !empty($attachment['id_thumb']); + + // If thumbnails are disabled, check the maximum size of the image. + if (!$attachmentData[$i]['thumbnail']['has_thumb'] && ((!empty($modSettings['max_image_width']) && $attachment['width'] > $modSettings['max_image_width']) || (!empty($modSettings['max_image_height']) && $attachment['height'] > $modSettings['max_image_height']))) + { + if (!empty($modSettings['max_image_width']) && (empty($modSettings['max_image_height']) || $attachment['height'] * $modSettings['max_image_width'] / $attachment['width'] <= $modSettings['max_image_height'])) + { + $attachmentData[$i]['width'] = $modSettings['max_image_width']; + $attachmentData[$i]['height'] = floor($attachment['height'] * $modSettings['max_image_width'] / $attachment['width']); + } + elseif (!empty($modSettings['max_image_width'])) + { + $attachmentData[$i]['width'] = floor($attachment['width'] * $modSettings['max_image_height'] / $attachment['height']); + $attachmentData[$i]['height'] = $modSettings['max_image_height']; + } + } + elseif ($attachmentData[$i]['thumbnail']['has_thumb']) + { + // If the image is too large to show inline, make it a popup. + if (((!empty($modSettings['max_image_width']) && $attachmentData[$i]['real_width'] > $modSettings['max_image_width']) || (!empty($modSettings['max_image_height']) && $attachmentData[$i]['real_height'] > $modSettings['max_image_height']))) + $attachmentData[$i]['thumbnail']['javascript'] = 'return reqWin(\'' . $attachmentData[$i]['href'] . ';image\', ' . ($attachment['width'] + 20) . ', ' . ($attachment['height'] + 20) . ', true);'; + else + $attachmentData[$i]['thumbnail']['javascript'] = 'return expandThumb(' . $attachment['id_attach'] . ');'; + } + + if (!$attachmentData[$i]['thumbnail']['has_thumb']) + $attachmentData[$i]['downloads']++; + } + } + + // Do we need to instigate a sort? + if ($have_unapproved) + usort($attachmentData, 'approved_attach_sort'); + + return $attachmentData; +} + +// A sort function for putting unapproved attachments first. +function approved_attach_sort($a, $b) +{ + if ($a['is_approved'] == $b['is_approved']) + return 0; + + return $a['is_approved'] > $b['is_approved'] ? -1 : 1; +} + +// In-topic quick moderation. +function QuickInTopicModeration() +{ + global $sourcedir, $topic, $board, $user_info, $smcFunc, $modSettings, $context; + + // Check the session = get or post. + checkSession('request'); + + require_once($sourcedir . '/RemoveTopic.php'); + + if (empty($_REQUEST['msgs'])) + redirectexit('topic=' . $topic . '.' . $_REQUEST['start']); + + $messages = array(); + foreach ($_REQUEST['msgs'] as $dummy) + $messages[] = (int) $dummy; + + // We are restoring messages. We handle this in another place. + if (isset($_REQUEST['restore_selected'])) + redirectexit('action=restoretopic;msgs=' . implode(',', $messages) . ';' . $context['session_var'] . '=' . $context['session_id']); + + // Allowed to delete any message? + if (allowedTo('delete_any')) + $allowed_all = true; + // Allowed to delete replies to their messages? + elseif (allowedTo('delete_replies')) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member_started + FROM {db_prefix}topics + WHERE id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + ) + ); + list ($starter) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $allowed_all = $starter == $user_info['id']; + } + else + $allowed_all = false; + + // Make sure they're allowed to delete their own messages, if not any. + if (!$allowed_all) + isAllowedTo('delete_own'); + + // Allowed to remove which messages? + $request = $smcFunc['db_query']('', ' + SELECT id_msg, subject, id_member, poster_time + FROM {db_prefix}messages + WHERE id_msg IN ({array_int:message_list}) + AND id_topic = {int:current_topic}' . (!$allowed_all ? ' + AND id_member = {int:current_member}' : '') . ' + LIMIT ' . count($messages), + array( + 'current_member' => $user_info['id'], + 'current_topic' => $topic, + 'message_list' => $messages, + ) + ); + $messages = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!$allowed_all && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time()) + continue; + + $messages[$row['id_msg']] = array($row['subject'], $row['id_member']); + } + $smcFunc['db_free_result']($request); + + // Get the first message in the topic - because you can't delete that! + $request = $smcFunc['db_query']('', ' + SELECT id_first_msg, id_last_msg + FROM {db_prefix}topics + WHERE id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + ) + ); + list ($first_message, $last_message) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Delete all the messages we know they can delete. ($messages) + foreach ($messages as $message => $info) + { + // Just skip the first message - if it's not the last. + if ($message == $first_message && $message != $last_message) + continue; + // If the first message is going then don't bother going back to the topic as we're effectively deleting it. + elseif ($message == $first_message) + $topicGone = true; + + removeMessage($message); + + // Log this moderation action ;). + if (allowedTo('delete_any') && (!allowedTo('delete_own') || $info[1] != $user_info['id'])) + logAction('delete', array('topic' => $topic, 'subject' => $info[0], 'member' => $info[1], 'board' => $board)); + } + + redirectexit(!empty($topicGone) ? 'board=' . $board : 'topic=' . $topic . '.' . $_REQUEST['start']); +} + +?> \ No newline at end of file diff --git a/Sources/DumpDatabase.php b/Sources/DumpDatabase.php new file mode 100644 index 0000000..059c666 --- /dev/null +++ b/Sources/DumpDatabase.php @@ -0,0 +1,182 @@ + '')); + $dbp = str_replace('_', '\_', $match[2]); + } + else + { + $db = false; + $dbp = $db_prefix; + } + + // Dump each table. + $tables = $smcFunc['db_list_tables'](false, $db_prefix . '%'); + foreach ($tables as $tableName) + { + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + // Are we dumping the structures? + if (isset($_REQUEST['struct'])) + { + echo + $crlf, + '--', $crlf, + '-- Table structure for table `', $tableName, '`', $crlf, + '--', $crlf, + $crlf, + $smcFunc['db_table_sql']($tableName), ';', $crlf; + } + + // How about the data? + if (!isset($_REQUEST['data']) || substr($tableName, -10) == 'log_errors') + continue; + + // Are there any rows in this table? + $get_rows = $smcFunc['db_insert_sql']($tableName); + + // No rows to get - skip it. + if (empty($get_rows)) + continue; + + echo + $crlf, + '--', $crlf, + '-- Dumping data in `', $tableName, '`', $crlf, + '--', $crlf, + $crlf, + $get_rows, + '-- --------------------------------------------------------', $crlf; + } + + echo + $crlf, + '-- Done', $crlf; + + exit; +} + +?> \ No newline at end of file diff --git a/Sources/Errors.php b/Sources/Errors.php new file mode 100644 index 0000000..5f80b70 --- /dev/null +++ b/Sources/Errors.php @@ -0,0 +1,420 @@ + '<', '>' => '>', '"' => '"')); + $error_message = strtr($error_message, array('<br />' => '
', '<b>' => '', '</b>' => '', "\n" => '
')); + + // Add a file and line to the error message? + // Don't use the actual txt entries for file and line but instead use %1$s for file and %2$s for line + if ($file == null) + $file = ''; + else + // Window style slashes don't play well, lets convert them to the unix style. + $file = str_replace('\\', '/', $file); + + if ($line == null) + $line = 0; + else + $line = (int) $line; + + // Just in case there's no id_member or IP set yet. + if (empty($user_info['id'])) + $user_info['id'] = 0; + if (empty($user_info['ip'])) + $user_info['ip'] = ''; + + // Find the best query string we can... + $query_string = empty($_SERVER['QUERY_STRING']) ? (empty($_SERVER['REQUEST_URL']) ? '' : str_replace($scripturl, '', $_SERVER['REQUEST_URL'])) : $_SERVER['QUERY_STRING']; + + // Don't log the session hash in the url twice, it's a waste. + $query_string = htmlspecialchars((SMF == 'SSI' ? '' : '?') . preg_replace(array('~;sesc=[^&;]+~', '~' . session_name() . '=' . session_id() . '[&;]~'), array(';sesc', ''), $query_string)); + + // Just so we know what board error messages are from. + if (isset($_POST['board']) && !isset($_GET['board'])) + $query_string .= ($query_string == '' ? 'board=' : ';board=') . $_POST['board']; + + // What types of categories do we have? + $known_error_types = array( + 'general', + 'critical', + 'database', + 'undefined_vars', + 'user', + 'template', + 'debug', + ); + + // Make sure the category that was specified is a valid one + $error_type = in_array($error_type, $known_error_types) && $error_type !== true ? $error_type : 'general'; + + // Don't log the same error countless times, as we can get in a cycle of depression... + $error_info = array($user_info['id'], time(), $user_info['ip'], $query_string, $error_message, (string) $sc, $error_type, $file, $line); + if (empty($last_error) || $last_error != $error_info) + { + // Insert the error into the database. + $smcFunc['db_insert']('', + '{db_prefix}log_errors', + array('id_member' => 'int', 'log_time' => 'int', 'ip' => 'string-16', 'url' => 'string-65534', 'message' => 'string-65534', 'session' => 'string', 'error_type' => 'string', 'file' => 'string-255', 'line' => 'int'), + $error_info, + array('id_error') + ); + $last_error = $error_info; + } + + // Return the message to make things simpler. + return $error_message; +} + +// An irrecoverable error. +function fatal_error($error, $log = 'general') +{ + global $txt, $context, $modSettings; + + // We don't have $txt yet, but that's okay... + if (empty($txt)) + die($error); + + setup_fatal_error_context($log || (!empty($modSettings['enableErrorLogging']) && $modSettings['enableErrorLogging'] == 2) ? log_error($error, $log) : $error); +} + +// A fatal error with a message stored in the language file. +function fatal_lang_error($error, $log = 'general', $sprintf = array()) +{ + global $txt, $language, $modSettings, $user_info, $context; + static $fatal_error_called = false; + + // Try to load a theme if we don't have one. + if (empty($context['theme_loaded']) && empty($fatal_error_called)) + { + $fatal_error_called = true; + loadTheme(); + } + + // If we have no theme stuff we can't have the language file... + if (empty($context['theme_loaded'])) + die($error); + + $reload_lang_file = true; + // Log the error in the forum's language, but don't waste the time if we aren't logging + if ($log || (!empty($modSettings['enableErrorLogging']) && $modSettings['enableErrorLogging'] == 2)) + { + loadLanguage('Errors', $language); + $reload_lang_file = $language != $user_info['language']; + $error_message = empty($sprintf) ? $txt[$error] : vsprintf($txt[$error], $sprintf); + log_error($error_message, $log); + } + + // Load the language file, only if it needs to be reloaded + if ($reload_lang_file) + { + loadLanguage('Errors'); + $error_message = empty($sprintf) ? $txt[$error] : vsprintf($txt[$error], $sprintf); + } + + setup_fatal_error_context($error_message); +} + +// Handler for standard error messages. +function error_handler($error_level, $error_string, $file, $line) +{ + global $settings, $modSettings, $db_show_debug; + + // Ignore errors if we're ignoring them or they are strict notices from PHP 5 (which cannot be solved without breaking PHP 4.) + if (error_reporting() == 0 || (defined('E_STRICT') && $error_level == E_STRICT && (empty($modSettings['enableErrorLogging']) || $modSettings['enableErrorLogging'] != 2))) + return; + + if (strpos($file, 'eval()') !== false && !empty($settings['current_include_filename'])) + { + if (function_exists('debug_backtrace')) + { + $array = debug_backtrace(); + for ($i = 0; $i < count($array); $i++) + { + if ($array[$i]['function'] != 'loadSubTemplate') + continue; + + // This is a bug in PHP, with eval, it seems! + if (empty($array[$i]['args'])) + $i++; + break; + } + + if (isset($array[$i]) && !empty($array[$i]['args'])) + $file = realpath($settings['current_include_filename']) . ' (' . $array[$i]['args'][0] . ' sub template - eval?)'; + else + $file = realpath($settings['current_include_filename']) . ' (eval?)'; + } + else + $file = realpath($settings['current_include_filename']) . ' (eval?)'; + } + + if (isset($db_show_debug) && $db_show_debug === true) + { + // Commonly, undefined indexes will occur inside attributes; try to show them anyway! + if ($error_level % 255 != E_ERROR) + { + $temporary = ob_get_contents(); + if (substr($temporary, -2) == '="') + echo '"'; + } + + // Debugging! This should look like a PHP error message. + echo '
+', $error_level % 255 == E_ERROR ? 'Error' : ($error_level % 255 == E_WARNING ? 'Warning' : 'Notice'), ': ', $error_string, ' in ', $file, ' on line ', $line, '
'; + } + + $error_type = strpos(strtolower($error_string), 'undefined') !== false ? 'undefined_vars' : 'general'; + + $message = log_error($error_level . ': ' . $error_string, $error_type, $file, $line); + + // Let's give integrations a chance to ouput a bit differently + call_integration_hook('integrate_output_error', array($message, $error_type, $error_level, $file, $line)); + + // Dying on these errors only causes MORE problems (blank pages!) + if ($file == 'Unknown') + return; + + // If this is an E_ERROR or E_USER_ERROR.... die. Violently so. + if ($error_level % 255 == E_ERROR) + obExit(false); + else + return; + + // If this is an E_ERROR, E_USER_ERROR, E_WARNING, or E_USER_WARNING.... die. Violently so. + if ($error_level % 255 == E_ERROR || $error_level % 255 == E_WARNING) + fatal_error(allowedTo('admin_forum') ? $message : $error_string, false); + + // We should NEVER get to this point. Any fatal error MUST quit, or very bad things can happen. + if ($error_level % 255 == E_ERROR) + die('Hacking attempt...'); +} + +function setup_fatal_error_context($error_message) +{ + global $context, $txt, $ssi_on_error_method; + static $level = 0; + + // Attempt to prevent a recursive loop. + ++$level; + if ($level > 1) + return false; + + // Maybe they came from dlattach or similar? + if (SMF != 'SSI' && empty($context['theme_loaded'])) + loadTheme(); + + // Don't bother indexing errors mate... + $context['robot_no_index'] = true; + + if (!isset($context['error_title'])) + $context['error_title'] = $txt['error_occured']; + $context['error_message'] = isset($context['error_message']) ? $context['error_message'] : $error_message; + + if (empty($context['page_title'])) + $context['page_title'] = $context['error_title']; + + // Display the error message - wireless? + if (defined('WIRELESS') && WIRELESS) + $context['sub_template'] = WIRELESS_PROTOCOL . '_error'; + // Load the template and set the sub template. + else + { + loadTemplate('Errors'); + $context['sub_template'] = 'fatal_error'; + } + + // If this is SSI, what do they want us to do? + if (SMF == 'SSI') + { + if (!empty($ssi_on_error_method) && $ssi_on_error_method !== true && is_callable($ssi_on_error_method)) + $ssi_on_error_method(); + elseif (empty($ssi_on_error_method) || $ssi_on_error_method !== true) + loadSubTemplate('fatal_error'); + + // No layers? + if (empty($ssi_on_error_method) || $ssi_on_error_method !== true) + exit; + } + + // We want whatever for the header, and a footer. (footer includes sub template!) + obExit(null, true, false, true); + + /* DO NOT IGNORE: + If you are creating a bridge to SMF or modifying this function, you MUST + make ABSOLUTELY SURE that this function quits and DOES NOT RETURN TO NORMAL + PROGRAM FLOW. Otherwise, security error messages will not be shown, and + your forum will be in a very easily hackable state. + */ + trigger_error('Hacking attempt...', E_USER_ERROR); +} + +// Show an error message for the connection problems. +function show_db_error($loadavg = false) +{ + global $sourcedir, $mbname, $maintenance, $mtitle, $mmessage, $modSettings; + global $db_connection, $webmaster_email, $db_last_error, $db_error_send, $smcFunc; + + // Just check we're not in any buffers, just in case. + for ($i = ob_get_level(); $i > 0; $i--) + @ob_end_clean(); + + // Don't cache this page! + header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + header('Cache-Control: no-cache'); + + // Send the right error codes. + header('HTTP/1.1 503 Service Temporarily Unavailable'); + header('Status: 503 Service Temporarily Unavailable'); + header('Retry-After: 3600'); + + if ($loadavg == false) + { + // For our purposes, we're gonna want this on if at all possible. + $modSettings['cache_enable'] = '1'; + + if (($temp = cache_get_data('db_last_error', 600)) !== null) + $db_last_error = max($db_last_error, $temp); + + if ($db_last_error < time() - 3600 * 24 * 3 && empty($maintenance) && !empty($db_error_send)) + { + require_once($sourcedir . '/Subs-Admin.php'); + + // Avoid writing to the Settings.php file if at all possible; use shared memory instead. + cache_put_data('db_last_error', time(), 600); + if (($temp = cache_get_data('db_last_error', 600)) == null) + updateLastDatabaseError(); + + // Language files aren't loaded yet :(. + $db_error = @$smcFunc['db_error']($db_connection); + @mail($webmaster_email, $mbname . ': SMF Database Error!', 'There has been a problem with the database!' . ($db_error == '' ? '' : "\n" . $smcFunc['db_title'] . ' reported:' . "\n" . $db_error) . "\n\n" . 'This is a notice email to let you know that SMF could not connect to the database, contact your host if this continues.'); + } + } + + if (!empty($maintenance)) + echo ' + + + + ', $mtitle, ' + + +

', $mtitle, '

+ ', $mmessage, ' + +'; + // If this is a load average problem, display an appropriate message (but we still don't have language files!) + elseif ($loadavg) + echo ' + + + + Temporarily Unavailable + + +

Temporarily Unavailable

+ Due to high stress on the server the forum is temporarily unavailable. Please try again later. + +'; + // What to do? Language files haven't and can't be loaded yet... + else + echo ' + + + + Connection Problems + + +

Connection Problems

+ Sorry, SMF was unable to connect to the database. This may be caused by the server being busy. Please try again later. + +'; + + die; +} + +?> \ No newline at end of file diff --git a/Sources/Groups.php b/Sources/Groups.php new file mode 100644 index 0000000..e1a0625 --- /dev/null +++ b/Sources/Groups.php @@ -0,0 +1,967 @@ + array('GroupList', 'view_groups'), + 'members' => array('MembergroupMembers', 'view_groups'), + 'requests' => array('GroupRequests', 'group_requests'), + ); + + // Default to sub action 'index' or 'settings' depending on permissions. + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'index'; + + // Get the template stuff up and running. + loadLanguage('ManageMembers'); + loadLanguage('ModerationCenter'); + loadTemplate('ManageMembergroups'); + + // If we can see the moderation center, and this has a mod bar entry, add the mod center bar. + if (allowedTo('access_mod_center') || $user_info['mod_cache']['bq'] != '0=1' || $user_info['mod_cache']['gq'] != '0=1' || allowedTo('manage_membergroups')) + { + require_once($sourcedir . '/ModerationCenter.php'); + $_GET['area'] = $_REQUEST['sa'] == 'requests' ? 'groups' : 'viewgroups'; + ModerationMain(true); + } + // Otherwise add something to the link tree, for normal people. + else + { + isAllowedTo('view_mlist'); + + $context['linktree'][] = array( + 'url' => $scripturl . '?action=groups', + 'name' => $txt['groups'], + ); + } + + // Call the actual function. + $subActions[$_REQUEST['sa']][0](); +} + +// This very simply lists the groups, nothing snazy. +function GroupList() +{ + global $txt, $scripturl, $user_profile, $user_info, $context, $settings, $modSettings, $smcFunc, $sourcedir; + + // Yep, find the groups... + $request = $smcFunc['db_query']('', ' + SELECT mg.id_group, mg.group_name, mg.description, mg.group_type, mg.online_color, mg.hidden, + mg.stars, IFNULL(gm.id_member, 0) AS can_moderate + FROM {db_prefix}membergroups AS mg + LEFT JOIN {db_prefix}group_moderators AS gm ON (gm.id_group = mg.id_group AND gm.id_member = {int:current_member}) + WHERE mg.min_posts = {int:min_posts} + AND mg.id_group != {int:mod_group}' . (allowedTo('admin_forum') ? '' : ' + AND mg.group_type != {int:is_protected}') . ' + ORDER BY group_name', + array( + 'current_member' => $user_info['id'], + 'min_posts' => -1, + 'mod_group' => 3, + 'is_protected' => 1, + ) + ); + // This is where we store our groups. + $context['groups'] = array(); + $group_ids = array(); + $context['can_moderate'] = allowedTo('manage_membergroups'); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // We only list the groups they can see. + if ($row['hidden'] && !$row['can_moderate'] && !allowedTo('manage_membergroups')) + continue; + + $row['stars'] = explode('#', $row['stars']); + + $context['groups'][$row['id_group']] = array( + 'id' => $row['id_group'], + 'name' => $row['group_name'], + 'desc' => $row['description'], + 'color' => $row['online_color'], + 'type' => $row['group_type'], + 'num_members' => 0, + 'stars' => !empty($row['stars'][0]) && !empty($row['stars'][1]) ? str_repeat('*', $row['stars'][0]) : '', + ); + + $context['can_moderate'] |= $row['can_moderate']; + $group_ids[] = $row['id_group']; + } + $smcFunc['db_free_result']($request); + + // Count up the members separately... + if (!empty($group_ids)) + { + $query = $smcFunc['db_query']('', ' + SELECT id_group, COUNT(*) AS num_members + FROM {db_prefix}members + WHERE id_group IN ({array_int:group_list}) + GROUP BY id_group', + array( + 'group_list' => $group_ids, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($query)) + $context['groups'][$row['id_group']]['num_members'] += $row['num_members']; + $smcFunc['db_free_result']($query); + + // Only do additional groups if we can moderate... + if ($context['can_moderate']) + { + $query = $smcFunc['db_query']('', ' + SELECT mg.id_group, COUNT(*) AS num_members + FROM {db_prefix}membergroups AS mg + INNER JOIN {db_prefix}members AS mem ON (mem.additional_groups != {string:blank_screen} + AND mem.id_group != mg.id_group + AND FIND_IN_SET(mg.id_group, mem.additional_groups) != 0) + WHERE mg.id_group IN ({array_int:group_list}) + GROUP BY mg.id_group', + array( + 'group_list' => $group_ids, + 'blank_screen' => '', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($query)) + $context['groups'][$row['id_group']]['num_members'] += $row['num_members']; + $smcFunc['db_free_result']($query); + } + } + + $context['sub_template'] = 'group_index'; + $context['page_title'] = $txt['viewing_groups']; + + // Making a list is not hard with this beauty. + require_once($sourcedir . '/Subs-List.php'); + + // Use the standard templates for showing this. + $listOptions = array( + 'id' => 'group_lists', + 'title' => $context['page_title'], + 'get_items' => array( + 'function' => 'list_getGroups', + ), + 'columns' => array( + 'group' => array( + 'header' => array( + 'value' => $txt['name'], + ), + 'data' => array( + 'function' => create_function('$group', ' + global $scripturl, $context; + + $output = \'\' . $group[\'name\'] . \'\'; + + if ($group[\'desc\']) + $output .= \'
\' . $group[\'desc\'] . \'
\'; + + return $output; + '), + 'style' => 'width: 50%;', + ), + ), + 'stars' => array( + 'header' => array( + 'value' => $txt['membergroups_stars'], + ), + 'data' => array( + 'db' => 'stars', + ), + ), + 'moderators' => array( + 'header' => array( + 'value' => $txt['moderators'], + ), + 'data' => array( + 'function' => create_function('$group', ' + global $txt; + + return empty($group[\'moderators\']) ? \'\' . $txt[\'membergroups_new_copy_none\'] . \'\' : implode(\', \', $group[\'moderators\']); + '), + ), + ), + 'members' => array( + 'header' => array( + 'value' => $txt['membergroups_members_top'], + ), + 'data' => array( + 'comma_format' => true, + 'db' => 'num_members', + ), + ), + ), + ); + + // Create the request list. + createList($listOptions); + + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'group_lists'; +} + +// Get the group information for the list. +function list_getGroups($start, $items_per_page, $sort) +{ + global $smcFunc, $txt, $scripturl, $user_info, $settings; + + // Yep, find the groups... + $request = $smcFunc['db_query']('', ' + SELECT mg.id_group, mg.group_name, mg.description, mg.group_type, mg.online_color, mg.hidden, + mg.stars, IFNULL(gm.id_member, 0) AS can_moderate + FROM {db_prefix}membergroups AS mg + LEFT JOIN {db_prefix}group_moderators AS gm ON (gm.id_group = mg.id_group AND gm.id_member = {int:current_member}) + WHERE mg.min_posts = {int:min_posts} + AND mg.id_group != {int:mod_group}' . (allowedTo('admin_forum') ? '' : ' + AND mg.group_type != {int:is_protected}') . ' + ORDER BY group_name', + array( + 'current_member' => $user_info['id'], + 'min_posts' => -1, + 'mod_group' => 3, + 'is_protected' => 1, + ) + ); + // Start collecting the data. + $groups = array(); + $group_ids = array(); + $context['can_moderate'] = allowedTo('manage_membergroups'); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // We only list the groups they can see. + if ($row['hidden'] && !$row['can_moderate'] && !allowedTo('manage_membergroups')) + continue; + + $row['stars'] = explode('#', $row['stars']); + + $groups[$row['id_group']] = array( + 'id' => $row['id_group'], + 'name' => $row['group_name'], + 'desc' => $row['description'], + 'color' => $row['online_color'], + 'type' => $row['group_type'], + 'num_members' => 0, + 'moderators' => array(), + 'stars' => !empty($row['stars'][0]) && !empty($row['stars'][1]) ? str_repeat('*', $row['stars'][0]) : '', + ); + + $context['can_moderate'] |= $row['can_moderate']; + $group_ids[] = $row['id_group']; + } + $smcFunc['db_free_result']($request); + + // Count up the members separately... + if (!empty($group_ids)) + { + $query = $smcFunc['db_query']('', ' + SELECT id_group, COUNT(*) AS num_members + FROM {db_prefix}members + WHERE id_group IN ({array_int:group_list}) + GROUP BY id_group', + array( + 'group_list' => $group_ids, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($query)) + $groups[$row['id_group']]['num_members'] += $row['num_members']; + $smcFunc['db_free_result']($query); + + // Only do additional groups if we can moderate... + if ($context['can_moderate']) + { + $query = $smcFunc['db_query']('', ' + SELECT mg.id_group, COUNT(*) AS num_members + FROM {db_prefix}membergroups AS mg + INNER JOIN {db_prefix}members AS mem ON (mem.additional_groups != {string:blank_screen} + AND mem.id_group != mg.id_group + AND FIND_IN_SET(mg.id_group, mem.additional_groups) != 0) + WHERE mg.id_group IN ({array_int:group_list}) + GROUP BY mg.id_group', + array( + 'group_list' => $group_ids, + 'blank_screen' => '', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($query)) + $groups[$row['id_group']]['num_members'] += $row['num_members']; + $smcFunc['db_free_result']($query); + } + } + + // Get any group moderators. + // Count up the members separately... + if (!empty($group_ids)) + { + $query = $smcFunc['db_query']('', ' + SELECT mods.id_group, mods.id_member, mem.member_name, mem.real_name + FROM {db_prefix}group_moderators AS mods + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member) + WHERE mods.id_group IN ({array_int:group_list})', + array( + 'group_list' => $group_ids, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($query)) + $groups[$row['id_group']]['moderators'][] = '' . $row['real_name'] . ''; + $smcFunc['db_free_result']($query); + } + + return $groups; +} + +// How many groups are there that are visible? +function list_getGroupCount() +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(id_group) AS group_count + FROM {db_prefix}membergroups + WHERE mg.min_posts = {int:min_posts} + AND mg.id_group != {int:mod_group}' . (allowedTo('admin_forum') ? '' : ' + AND mg.group_type != {int:is_protected}'), + array( + 'min_posts' => -1, + 'mod_group' => 3, + 'is_protected' => 1, + ) + ); + list ($group_count) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $group_count; +} + +// Display members of a group, and allow adding of members to a group. Silly function name though ;) +function MembergroupMembers() +{ + global $txt, $scripturl, $context, $modSettings, $sourcedir, $user_info, $settings, $smcFunc; + + $_REQUEST['group'] = isset($_REQUEST['group']) ? (int) $_REQUEST['group'] : 0; + + // No browsing of guests, membergroup 0 or moderators. + if (in_array($_REQUEST['group'], array(-1, 0, 3))) + fatal_lang_error('membergroup_does_not_exist', false); + + // Load up the group details. + $request = $smcFunc['db_query']('', ' + SELECT id_group AS id, group_name AS name, CASE WHEN min_posts = {int:min_posts} THEN 1 ELSE 0 END AS assignable, hidden, online_color, + stars, description, CASE WHEN min_posts != {int:min_posts} THEN 1 ELSE 0 END AS is_post_group, group_type + FROM {db_prefix}membergroups + WHERE id_group = {int:id_group} + LIMIT 1', + array( + 'min_posts' => -1, + 'id_group' => $_REQUEST['group'], + ) + ); + // Doesn't exist? + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('membergroup_does_not_exist', false); + $context['group'] = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Fix the stars. + $context['group']['stars'] = explode('#', $context['group']['stars']); + $context['group']['stars'] = !empty($context['group']['stars'][0]) && !empty($context['group']['stars'][1]) ? str_repeat('*', $context['group']['stars'][0]) : ''; + $context['group']['can_moderate'] = allowedTo('manage_membergroups') && (allowedTo('admin_forum') || $context['group']['group_type'] != 1); + + $context['linktree'][] = array( + 'url' => $scripturl . '?action=groups;sa=members;group=' . $context['group']['id'], + 'name' => $context['group']['name'], + ); + + // Load all the group moderators, for fun. + $request = $smcFunc['db_query']('', ' + SELECT mem.id_member, mem.real_name + FROM {db_prefix}group_moderators AS mods + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member) + WHERE mods.id_group = {int:id_group}', + array( + 'id_group' => $_REQUEST['group'], + ) + ); + $context['group']['moderators'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $context['group']['moderators'][] = array( + 'id' => $row['id_member'], + 'name' => $row['real_name'] + ); + + if ($user_info['id'] == $row['id_member'] && $context['group']['group_type'] != 1) + $context['group']['can_moderate'] = true; + } + $smcFunc['db_free_result']($request); + + // If this group is hidden then it can only "exists" if the user can moderate it! + if ($context['group']['hidden'] && !$context['group']['can_moderate']) + fatal_lang_error('membergroup_does_not_exist', false); + + // You can only assign membership if you are the moderator and/or can manage groups! + if (!$context['group']['can_moderate']) + $context['group']['assignable'] = 0; + // Non-admins cannot assign admins. + elseif ($context['group']['id'] == 1 && !allowedTo('admin_forum')) + $context['group']['assignable'] = 0; + + // Removing member from group? + if (isset($_POST['remove']) && !empty($_REQUEST['rem']) && is_array($_REQUEST['rem']) && $context['group']['assignable']) + { + checkSession(); + + // Make sure we're dealing with integers only. + foreach ($_REQUEST['rem'] as $key => $group) + $_REQUEST['rem'][$key] = (int) $group; + + require_once($sourcedir . '/Subs-Membergroups.php'); + removeMembersFromGroups($_REQUEST['rem'], $_REQUEST['group'], true); + } + // Must be adding new members to the group... + elseif (isset($_REQUEST['add']) && (!empty($_REQUEST['toAdd']) || !empty($_REQUEST['member_add'])) && $context['group']['assignable']) + { + checkSession(); + + $member_query = array(); + $member_parameters = array(); + + // Get all the members to be added... taking into account names can be quoted ;) + $_REQUEST['toAdd'] = strtr($smcFunc['htmlspecialchars']($_REQUEST['toAdd'], ENT_QUOTES), array('"' => '"')); + preg_match_all('~"([^"]+)"~', $_REQUEST['toAdd'], $matches); + $member_names = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $_REQUEST['toAdd'])))); + + foreach ($member_names as $index => $member_name) + { + $member_names[$index] = trim($smcFunc['strtolower']($member_names[$index])); + + if (strlen($member_names[$index]) == 0) + unset($member_names[$index]); + } + + // Any passed by ID? + $member_ids = array(); + if (!empty($_REQUEST['member_add'])) + foreach ($_REQUEST['member_add'] as $id) + if ($id > 0) + $member_ids[] = (int) $id; + + // Construct the query pelements. + if (!empty($member_ids)) + { + $member_query[] = 'id_member IN ({array_int:member_ids})'; + $member_parameters['member_ids'] = $member_ids; + } + if (!empty($member_names)) + { + $member_query[] = 'LOWER(member_name) IN ({array_string:member_names})'; + $member_query[] = 'LOWER(real_name) IN ({array_string:member_names})'; + $member_parameters['member_names'] = $member_names; + } + + $members = array(); + if (!empty($member_query)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE (' . implode(' OR ', $member_query) . ') + AND id_group != {int:id_group} + AND FIND_IN_SET({int:id_group}, additional_groups) = 0', + array_merge($member_parameters, array( + 'id_group' => $_REQUEST['group'], + )) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $members[] = $row['id_member']; + $smcFunc['db_free_result']($request); + } + + // !!! Add $_POST['additional'] to templates! + + // Do the updates... + if (!empty($members)) + { + require_once($sourcedir . '/Subs-Membergroups.php'); + addMembersToGroup($members, $_REQUEST['group'], isset($_POST['additional']) || $context['group']['hidden'] ? 'only_additional' : 'auto', true); + } + } + + // Sort out the sorting! + $sort_methods = array( + 'name' => 'real_name', + 'email' => allowedTo('moderate_forum') ? 'email_address' : 'hide_email ' . (isset($_REQUEST['desc']) ? 'DESC' : 'ASC') . ', email_address', + 'active' => 'last_login', + 'registered' => 'date_registered', + 'posts' => 'posts', + ); + + // They didn't pick one, default to by name.. + if (!isset($_REQUEST['sort']) || !isset($sort_methods[$_REQUEST['sort']])) + { + $context['sort_by'] = 'name'; + $querySort = 'real_name'; + } + // Otherwise default to ascending. + else + { + $context['sort_by'] = $_REQUEST['sort']; + $querySort = $sort_methods[$_REQUEST['sort']]; + } + + $context['sort_direction'] = isset($_REQUEST['desc']) ? 'down' : 'up'; + + // The where on the query is interesting. Non-moderators should only see people who are in this group as primary. + if ($context['group']['can_moderate']) + $where = $context['group']['is_post_group'] ? 'id_post_group = {int:group}' : 'id_group = {int:group} OR FIND_IN_SET({int:group}, additional_groups) != 0'; + else + $where = $context['group']['is_post_group'] ? 'id_post_group = {int:group}' : 'id_group = {int:group}'; + + // Count members of the group. + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}members + WHERE ' . $where, + array( + 'group' => $_REQUEST['group'], + ) + ); + list ($context['total_members']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + $context['total_members'] = comma_format($context['total_members']); + + // Create the page index. + $context['page_index'] = constructPageIndex($scripturl . '?action=' . ($context['group']['can_moderate'] ? 'moderate;area=viewgroups' : 'groups') . ';sa=members;group=' . $_REQUEST['group'] . ';sort=' . $context['sort_by'] . (isset($_REQUEST['desc']) ? ';desc' : ''), $_REQUEST['start'], $context['total_members'], $modSettings['defaultMaxMembers']); + $context['start'] = $_REQUEST['start']; + $context['can_moderate_forum'] = allowedTo('moderate_forum'); + + // Load up all members of this group. + $request = $smcFunc['db_query']('', ' + SELECT id_member, member_name, real_name, email_address, member_ip, date_registered, last_login, + hide_email, posts, is_activated, real_name + FROM {db_prefix}members + WHERE ' . $where . ' + ORDER BY ' . $querySort . ' ' . ($context['sort_direction'] == 'down' ? 'DESC' : 'ASC') . ' + LIMIT ' . $context['start'] . ', ' . $modSettings['defaultMaxMembers'], + array( + 'group' => $_REQUEST['group'], + ) + ); + $context['members'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $last_online = empty($row['last_login']) ? $txt['never'] : timeformat($row['last_login']); + + // Italicize the online note if they aren't activated. + if ($row['is_activated'] % 10 != 1) + $last_online = '' . $last_online . ''; + + $context['members'][] = array( + 'id' => $row['id_member'], + 'name' => '' . $row['real_name'] . '', + 'email' => $row['email_address'], + 'show_email' => showEmailAddress(!empty($row['hide_email']), $row['id_member']), + 'ip' => '' . $row['member_ip'] . '', + 'registered' => timeformat($row['date_registered']), + 'last_online' => $last_online, + 'posts' => comma_format($row['posts']), + 'is_activated' => $row['is_activated'] % 10 == 1, + ); + } + $smcFunc['db_free_result']($request); + + // Select the template. + $context['sub_template'] = 'group_members'; + $context['page_title'] = $txt['membergroups_members_title'] . ': ' . $context['group']['name']; +} + +// Show and manage all group requests. +function GroupRequests() +{ + global $txt, $context, $scripturl, $user_info, $sourcedir, $smcFunc, $modSettings, $language; + + // Set up the template stuff... + $context['page_title'] = $txt['mc_group_requests']; + $context['sub_template'] = 'show_list'; + + // Verify we can be here. + if ($user_info['mod_cache']['gq'] == '0=1') + isAllowedTo('manage_membergroups'); + + // Normally, we act normally... + $where = $user_info['mod_cache']['gq'] == '1=1' || $user_info['mod_cache']['gq'] == '0=1' ? $user_info['mod_cache']['gq'] : 'lgr.' . $user_info['mod_cache']['gq']; + $where_parameters = array(); + + // We've submitted? + if (isset($_POST[$context['session_var']]) && !empty($_POST['groupr']) && !empty($_POST['req_action'])) + { + checkSession('post'); + + // Clean the values. + foreach ($_POST['groupr'] as $k => $request) + $_POST['groupr'][$k] = (int) $request; + + // If we are giving a reason (And why shouldn't we?), then we don't actually do much. + if ($_POST['req_action'] == 'reason') + { + // Different sub template... + $context['sub_template'] = 'group_request_reason'; + // And a limitation. We don't care that the page number bit makes no sense, as we don't need it! + $where .= ' AND lgr.id_request IN ({array_int:request_ids})'; + $where_parameters['request_ids'] = $_POST['groupr']; + + $context['group_requests'] = list_getGroupRequests(0, $modSettings['defaultMaxMessages'], 'lgr.id_request', $where, $where_parameters); + + // Let obExit etc sort things out. + obExit(); + } + // Otherwise we do something! + else + { + // Get the details of all the members concerned... + $request = $smcFunc['db_query']('', ' + SELECT lgr.id_request, lgr.id_member, lgr.id_group, mem.email_address, mem.id_group AS primary_group, + mem.additional_groups AS additional_groups, mem.lngfile, mem.member_name, mem.notify_types, + mg.hidden, mg.group_name + FROM {db_prefix}log_group_requests AS lgr + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = lgr.id_member) + INNER JOIN {db_prefix}membergroups AS mg ON (mg.id_group = lgr.id_group) + WHERE ' . $where . ' + AND lgr.id_request IN ({array_int:request_list}) + ORDER BY mem.lngfile', + array( + 'request_list' => $_POST['groupr'], + ) + ); + $email_details = array(); + $group_changes = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $row['lngfile'] = empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']; + + // If we are approving work out what their new group is. + if ($_POST['req_action'] == 'approve') + { + // For people with more than one request at once. + if (isset($group_changes[$row['id_member']])) + { + $row['additional_groups'] = $group_changes[$row['id_member']]['add']; + $row['primary_group'] = $group_changes[$row['id_member']]['primary']; + } + else + $row['additional_groups'] = explode(',', $row['additional_groups']); + + // Don't have it already? + if ($row['primary_group'] == $row['id_group'] || in_array($row['id_group'], $row['additional_groups'])) + continue; + + // Should it become their primary? + if ($row['primary_group'] == 0 && $row['hidden'] == 0) + $row['primary_group'] = $row['id_group']; + else + $row['additional_groups'][] = $row['id_group']; + + // Add them to the group master list. + $group_changes[$row['id_member']] = array( + 'primary' => $row['primary_group'], + 'add' => $row['additional_groups'], + ); + } + + // Add required information to email them. + if ($row['notify_types'] != 4) + $email_details[] = array( + 'rid' => $row['id_request'], + 'member_id' => $row['id_member'], + 'member_name' => $row['member_name'], + 'group_id' => $row['id_group'], + 'group_name' => $row['group_name'], + 'email' => $row['email_address'], + 'language' => $row['lngfile'], + ); + } + $smcFunc['db_free_result']($request); + + // Remove the evidence... + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_group_requests + WHERE id_request IN ({array_int:request_list})', + array( + 'request_list' => $_POST['groupr'], + ) + ); + + // Ensure everyone who is online gets their changes right away. + updateSettings(array('settings_updated' => time())); + + if (!empty($email_details)) + { + require_once($sourcedir . '/Subs-Post.php'); + + // They are being approved? + if ($_POST['req_action'] == 'approve') + { + // Make the group changes. + foreach ($group_changes as $id => $groups) + { + // Sanity check! + foreach ($groups['add'] as $key => $value) + if ($value == 0 || trim($value) == '') + unset($groups['add'][$key]); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET id_group = {int:primary_group}, additional_groups = {string:additional_groups} + WHERE id_member = {int:selected_member}', + array( + 'primary_group' => $groups['primary'], + 'selected_member' => $id, + 'additional_groups' => implode(',', $groups['add']), + ) + ); + } + + $lastLng = $user_info['language']; + foreach ($email_details as $email) + { + $replacements = array( + 'USERNAME' => $email['member_name'], + 'GROUPNAME' => $email['group_name'], + ); + + $emaildata = loadEmailTemplate('mc_group_approve', $replacements, $email['language']); + + sendmail($email['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 2); + } + } + // Otherwise, they are getting rejected (With or without a reason). + else + { + // Same as for approving, kind of. + $lastLng = $user_info['language']; + foreach ($email_details as $email) + { + $custom_reason = isset($_POST['groupreason']) && isset($_POST['groupreason'][$email['rid']]) ? $_POST['groupreason'][$email['rid']] : ''; + + $replacements = array( + 'USERNAME' => $email['member_name'], + 'GROUPNAME' => $email['group_name'], + ); + + if (!empty($custom_reason)) + $replacements['REASON'] = $custom_reason; + + $emaildata = loadEmailTemplate(empty($custom_reason) ? 'mc_group_reject' : 'mc_group_reject_reason', $replacements, $email['language']); + + sendmail($email['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 2); + } + } + } + + // Restore the current language. + loadLanguage('ModerationCenter'); + } + } + + // We're going to want this for making our list. + require_once($sourcedir . '/Subs-List.php'); + + // This is all the information required for a group listing. + $listOptions = array( + 'id' => 'group_request_list', + 'title' => $txt['mc_group_requests'], + 'width' => '100%', + 'items_per_page' => $modSettings['defaultMaxMessages'], + 'no_items_label' => $txt['mc_groupr_none_found'], + 'base_href' => $scripturl . '?action=groups;sa=requests', + 'default_sort_col' => 'member', + 'get_items' => array( + 'function' => 'list_getGroupRequests', + 'params' => array( + $where, + $where_parameters, + ), + ), + 'get_count' => array( + 'function' => 'list_getGroupRequestCount', + 'params' => array( + $where, + $where_parameters, + ), + ), + 'columns' => array( + 'member' => array( + 'header' => array( + 'value' => $txt['mc_groupr_member'], + ), + 'data' => array( + 'db' => 'member_link', + ), + 'sort' => array( + 'default' => 'mem.member_name', + 'reverse' => 'mem.member_name DESC', + ), + ), + 'group' => array( + 'header' => array( + 'value' => $txt['mc_groupr_group'], + ), + 'data' => array( + 'db' => 'group_link', + ), + 'sort' => array( + 'default' => 'mg.group_name', + 'reverse' => 'mg.group_name DESC', + ), + ), + 'reason' => array( + 'header' => array( + 'value' => $txt['mc_groupr_reason'], + ), + 'data' => array( + 'db' => 'reason', + ), + ), + 'action' => array( + 'header' => array( + 'value' => '', + 'style' => 'width: 4%;', + ), + 'data' => array( + 'sprintf' => array( + 'format' => '', + 'params' => array( + 'id' => false, + ), + ), + 'style' => 'text-align: center;', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=groups;sa=requests', + 'include_sort' => true, + 'include_start' => true, + 'hidden_fields' => array( + $context['session_var'] => $context['session_id'], + ), + ), + 'additional_rows' => array( + array( + 'position' => 'bottom_of_list', + 'value' => ' + + ', + 'align' => 'right', + ), + ), + ); + + // Create the request list. + createList($listOptions); + + $context['default_list'] = 'group_request_list'; +} + +function list_getGroupRequestCount($where, $where_parameters) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_group_requests AS lgr + WHERE ' . $where, + array_merge($where_parameters, array( + )) + ); + list ($totalRequests) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $totalRequests; +} + +function list_getGroupRequests($start, $items_per_page, $sort, $where, $where_parameters) +{ + global $smcFunc, $txt, $scripturl; + + $request = $smcFunc['db_query']('', ' + SELECT lgr.id_request, lgr.id_member, lgr.id_group, lgr.time_applied, lgr.reason, + mem.member_name, mg.group_name, mg.online_color, mem.real_name + FROM {db_prefix}log_group_requests AS lgr + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = lgr.id_member) + INNER JOIN {db_prefix}membergroups AS mg ON (mg.id_group = lgr.id_group) + WHERE ' . $where . ' + ORDER BY {raw:sort} + LIMIT ' . $start . ', ' . $items_per_page, + array_merge($where_parameters, array( + 'sort' => $sort, + )) + ); + $group_requests = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $group_requests[] = array( + 'id' => $row['id_request'], + 'member_link' => '' . $row['real_name'] . '', + 'group_link' => '' . $row['group_name'] . '', + 'reason' => censorText($row['reason']), + 'time_submitted' => timeformat($row['time_applied']), + ); + } + $smcFunc['db_free_result']($request); + + return $group_requests; +} + +?> \ No newline at end of file diff --git a/Sources/Help.php b/Sources/Help.php new file mode 100644 index 0000000..2896e60 --- /dev/null +++ b/Sources/Help.php @@ -0,0 +1,111 @@ + 'Registering', + 'logging_in' => 'Logging_In', + 'profile' => 'Profile', + 'search' => 'Search', + 'posting' => 'Posting', + 'bbc' => 'Bulletin_board_code', + 'personal_messages' => 'Personal_messages', + 'memberlist' => 'Memberlist', + 'calendar' => 'Calendar', + 'features' => 'Features', + ); + + // Build the link tree. + $context['linktree'][] = array( + 'url' => $scripturl . '?action=help', + 'name' => $txt['help'], + ); + + // Lastly, some minor template stuff. + $context['page_title'] = $txt['manual_smf_user_help']; + $context['sub_template'] = 'manual'; +} + +// Show some of the more detailed help to give the admin an idea... +function ShowAdminHelp() +{ + global $txt, $helptxt, $context, $scripturl; + + if (!isset($_GET['help']) || !is_string($_GET['help'])) + fatal_lang_error('no_access', false); + + if (!isset($helptxt)) + $helptxt = array(); + + // Load the admin help language file and template. + loadLanguage('Help'); + + // Permission specific help? + if (isset($_GET['help']) && substr($_GET['help'], 0, 14) == 'permissionhelp') + loadLanguage('ManagePermissions'); + + loadTemplate('Help'); + + // Set the page title to something relevant. + $context['page_title'] = $context['forum_name'] . ' - ' . $txt['help']; + + // Don't show any template layers, just the popup sub template. + $context['template_layers'] = array(); + $context['sub_template'] = 'popup'; + + // What help string should be used? + if (isset($helptxt[$_GET['help']])) + $context['help_text'] = $helptxt[$_GET['help']]; + elseif (isset($txt[$_GET['help']])) + $context['help_text'] = $txt[$_GET['help']]; + else + $context['help_text'] = $_GET['help']; + + // Does this text contain a link that we should fill in? + if (preg_match('~%([0-9]+\$)?s\?~', $context['help_text'], $match)) + $context['help_text'] = sprintf($context['help_text'], $scripturl, $context['session_id'], $context['session_var']); +} + +?> \ No newline at end of file diff --git a/Sources/Karma.php b/Sources/Karma.php new file mode 100644 index 0000000..c4b4b92 --- /dev/null +++ b/Sources/Karma.php @@ -0,0 +1,204 @@ + {int:wait_time}', + array( + 'wait_time' => (int) ($modSettings['karmaWaitTime'] * 3600), + 'current_time' => time(), + ) + ); + + // Start off with no change in karma. + $action = 0; + + // Not an administrator... or one who is restricted as well. + if (!empty($modSettings['karmaTimeRestrictAdmins']) || !allowedTo('moderate_forum')) + { + // Find out if this user has done this recently... + $request = $smcFunc['db_query']('', ' + SELECT action + FROM {db_prefix}log_karma + WHERE id_target = {int:id_target} + AND id_executor = {int:current_member} + LIMIT 1', + array( + 'current_member' => $user_info['id'], + 'id_target' => $_REQUEST['uid'], + ) + ); + if ($smcFunc['db_num_rows']($request) > 0) + list ($action) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + // They haven't, not before now, anyhow. + if (empty($action) || empty($modSettings['karmaWaitTime'])) + { + // Put it in the log. + $smcFunc['db_insert']('replace', + '{db_prefix}log_karma', + array('action' => 'int', 'id_target' => 'int', 'id_executor' => 'int', 'log_time' => 'int'), + array($dir, $_REQUEST['uid'], $user_info['id'], time()), + array('id_target', 'id_executor') + ); + + // Change by one. + updateMemberData($_REQUEST['uid'], array($dir == 1 ? 'karma_good' : 'karma_bad' => '+')); + } + else + { + // If you are gonna try to repeat.... don't allow it. + if ($action == $dir) + fatal_lang_error('karma_wait_time', false, array($modSettings['karmaWaitTime'], $txt['hours'])); + + // You decided to go back on your previous choice? + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_karma + SET action = {int:action}, log_time = {int:current_time} + WHERE id_target = {int:id_target} + AND id_executor = {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'action' => $dir, + 'current_time' => time(), + 'id_target' => $_REQUEST['uid'], + ) + ); + + // It was recently changed the OTHER way... so... reverse it! + if ($dir == 1) + updateMemberData($_REQUEST['uid'], array('karma_good' => '+', 'karma_bad' => '-')); + else + updateMemberData($_REQUEST['uid'], array('karma_bad' => '+', 'karma_good' => '-')); + } + + // Figure out where to go back to.... the topic? + if (!empty($topic)) + redirectexit('topic=' . $topic . '.' . $_REQUEST['start'] . '#msg' . (int) $_REQUEST['m']); + // Hrm... maybe a personal message? + elseif (isset($_REQUEST['f'])) + redirectexit('action=pm;f=' . $_REQUEST['f'] . ';start=' . $_REQUEST['start'] . (isset($_REQUEST['l']) ? ';l=' . (int) $_REQUEST['l'] : '') . (isset($_REQUEST['pm']) ? '#' . (int) $_REQUEST['pm'] : '')); + // JavaScript as a last resort. + else + { + echo ' + + + ... + + + « +'; + + obExit(false); + } +} + +// What's this? I dunno, what are you talking about? Never seen this before, nope. No siree. +function BookOfUnknown() +{ + global $context; + + if (strpos($_GET['action'], 'mozilla') !== false && !$context['browser']['is_gecko']) + redirectexit('http://www.getfirefox.com/'); + elseif (strpos($_GET['action'], 'mozilla') !== false) + redirectexit('about:mozilla'); + + echo ' + + + The Book of Unknown, ', @$_GET['verse'] == '2:18' ? '2:18' : '4:16', ' + + + +
'; + if (@$_GET['verse'] == '2:18') + echo ' + Woe, it was that his name wasn\'t known, that he came in mystery, and was recognized by none. And it became to be in those days something.  Something not yet unknown to mankind.  And thus what was to be known the secret project began into its existence.  Henceforth the opposition was only weary and fearful, for now their match was at arms against them.'; + else + echo ' + And it came to pass that the unbelievers dwindled in number and saw rise of many proselytizers, and the opposition found fear in the face of the x and the j while those who stood with the something grew stronger and came together.  Still, this was only the beginning, and what lay in the future was unknown to all, even those on the right side.'; + echo ' +
+
'; + if (@$_GET['verse'] == '2:18') + echo ' + from The Book of Unknown, 2:18'; + else + echo ' + from The Book of Unknown, 4:16'; + echo ' +
+ +'; + + obExit(false); +} + +?> \ No newline at end of file diff --git a/Sources/Load.php b/Sources/Load.php new file mode 100644 index 0000000..1a44b93 --- /dev/null +++ b/Sources/Load.php @@ -0,0 +1,2750 @@ + 999) + $modSettings['defaultMaxTopics'] = 20; + if (empty($modSettings['defaultMaxMessages']) || $modSettings['defaultMaxMessages'] <= 0 || $modSettings['defaultMaxMessages'] > 999) + $modSettings['defaultMaxMessages'] = 15; + if (empty($modSettings['defaultMaxMembers']) || $modSettings['defaultMaxMembers'] <= 0 || $modSettings['defaultMaxMembers'] > 999) + $modSettings['defaultMaxMembers'] = 30; + + if (!empty($modSettings['cache_enable'])) + cache_put_data('modSettings', $modSettings, 90); + } + + // UTF-8 in regular expressions is unsupported on PHP(win) versions < 4.2.3. + $utf8 = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8' && (strpos(strtolower(PHP_OS), 'win') === false || @version_compare(PHP_VERSION, '4.2.3') != -1); + + // Set a list of common functions. + $ent_list = empty($modSettings['disableEntityCheck']) ? '&(#\d{1,7}|quot|amp|lt|gt|nbsp);' : '&(#021|quot|amp|lt|gt|nbsp);'; + $ent_check = empty($modSettings['disableEntityCheck']) ? array('preg_replace_callback(\'~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~\', \'entity_fix__callback\', ', ')') : array('', ''); + + // Preg_replace can handle complex characters only for higher PHP versions. + $space_chars = $utf8 ? (@version_compare(PHP_VERSION, '4.3.3') != -1 ? '\x{A0}\x{AD}\x{2000}-\x{200F}\x{201F}\x{202F}\x{3000}\x{FEFF}' : "\xC2\xA0\xC2\xAD\xE2\x80\x80-\xE2\x80\x8F\xE2\x80\x9F\xE2\x80\xAF\xE2\x80\x9F\xE3\x80\x80\xEF\xBB\xBF") : '\x00-\x08\x0B\x0C\x0E-\x19\xA0'; + + $smcFunc += array( + 'entity_fix' => create_function('$string', ' + $num = substr($string, 0, 1) === \'x\' ? hexdec(substr($string, 1)) : (int) $string; + return $num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202E || $num === 0x202D ? \'\' : \'&#\' . $num . \';\';'), + 'htmlspecialchars' => create_function('$string, $quote_style = ENT_COMPAT, $charset = \'ISO-8859-1\'', ' + global $smcFunc; + return ' . strtr($ent_check[0], array('&' => '&')) . 'htmlspecialchars($string, $quote_style, ' . ($utf8 ? '\'UTF-8\'' : '$charset') . ')' . $ent_check[1] . ';'), + 'htmltrim' => create_function('$string', ' + global $smcFunc; + return preg_replace(\'~^(?:[ \t\n\r\x0B\x00' . $space_chars . ']| )+|(?:[ \t\n\r\x0B\x00' . $space_chars . ']| )+$~' . ($utf8 ? 'u' : '') . '\', \'\', ' . implode('$string', $ent_check) . ');'), + 'strlen' => create_function('$string', ' + global $smcFunc; + return strlen(preg_replace(\'~' . $ent_list . ($utf8 ? '|.~u' : '~') . '\', \'_\', ' . implode('$string', $ent_check) . '));'), + 'strpos' => create_function('$haystack, $needle, $offset = 0', ' + global $smcFunc; + $haystack_arr = preg_split(\'~(&#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . ';|"|&|<|>| |.)~' . ($utf8 ? 'u' : '') . '\', ' . implode('$haystack', $ent_check) . ', -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $haystack_size = count($haystack_arr); + if (strlen($needle) === 1) + { + $result = array_search($needle, array_slice($haystack_arr, $offset)); + return is_int($result) ? $result + $offset : false; + } + else + { + $needle_arr = preg_split(\'~(&#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . ';|"|&|<|>| |.)~' . ($utf8 ? 'u' : '') . '\', ' . implode('$needle', $ent_check) . ', -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $needle_size = count($needle_arr); + + $result = array_search($needle_arr[0], array_slice($haystack_arr, $offset)); + while (is_int($result)) + { + $offset += $result; + if (array_slice($haystack_arr, $offset, $needle_size) === $needle_arr) + return $offset; + $result = array_search($needle_arr[0], array_slice($haystack_arr, ++$offset)); + } + return false; + }'), + 'substr' => create_function('$string, $start, $length = null', ' + global $smcFunc; + $ent_arr = preg_split(\'~(&#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . ';|"|&|<|>| |.)~' . ($utf8 ? 'u' : '') . '\', ' . implode('$string', $ent_check) . ', -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + return $length === null ? implode(\'\', array_slice($ent_arr, $start)) : implode(\'\', array_slice($ent_arr, $start, $length));'), + 'strtolower' => $utf8 ? (function_exists('mb_strtolower') ? create_function('$string', ' + return mb_strtolower($string, \'UTF-8\');') : create_function('$string', ' + global $sourcedir; + require_once($sourcedir . \'/Subs-Charset.php\'); + return utf8_strtolower($string);')) : 'strtolower', + 'strtoupper' => $utf8 ? (function_exists('mb_strtoupper') ? create_function('$string', ' + return mb_strtoupper($string, \'UTF-8\');') : create_function('$string', ' + global $sourcedir; + require_once($sourcedir . \'/Subs-Charset.php\'); + return utf8_strtoupper($string);')) : 'strtoupper', + 'truncate' => create_function('$string, $length', (empty($modSettings['disableEntityCheck']) ? ' + global $smcFunc; + $string = ' . implode('$string', $ent_check) . ';' : '') . ' + preg_match(\'~^(' . $ent_list . '|.){\' . $smcFunc[\'strlen\'](substr($string, 0, $length)) . \'}~'. ($utf8 ? 'u' : '') . '\', $string, $matches); + $string = $matches[0]; + while (strlen($string) > $length) + $string = preg_replace(\'~(?:' . $ent_list . '|.)$~'. ($utf8 ? 'u' : '') . '\', \'\', $string); + return $string;'), + 'ucfirst' => $utf8 ? create_function('$string', ' + global $smcFunc; + return $smcFunc[\'strtoupper\']($smcFunc[\'substr\']($string, 0, 1)) . $smcFunc[\'substr\']($string, 1);') : 'ucfirst', + 'ucwords' => $utf8 ? create_function('$string', ' + global $smcFunc; + $words = preg_split(\'~([\s\r\n\t]+)~\', $string, -1, PREG_SPLIT_DELIM_CAPTURE); + for ($i = 0, $n = count($words); $i < $n; $i += 2) + $words[$i] = $smcFunc[\'ucfirst\']($words[$i]); + return implode(\'\', $words);') : 'ucwords', + ); + + // Setting the timezone is a requirement for some functions in PHP >= 5.1. + if (isset($modSettings['default_timezone']) && function_exists('date_default_timezone_set')) + date_default_timezone_set($modSettings['default_timezone']); + + // Check the load averages? + if (!empty($modSettings['loadavg_enable'])) + { + if (($modSettings['load_average'] = cache_get_data('loadavg', 90)) == null) + { + $modSettings['load_average'] = @file_get_contents('/proc/loadavg'); + if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) != 0) + $modSettings['load_average'] = (float) $matches[1]; + elseif (($modSettings['load_average'] = @`uptime`) != null && preg_match('~load average[s]?: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)~i', $modSettings['load_average'], $matches) != 0) + $modSettings['load_average'] = (float) $matches[1]; + else + unset($modSettings['load_average']); + + if (!empty($modSettings['load_average'])) + cache_put_data('loadavg', $modSettings['load_average'], 90); + } + + if (!empty($modSettings['loadavg_forum']) && !empty($modSettings['load_average']) && $modSettings['load_average'] >= $modSettings['loadavg_forum']) + db_fatal_error(true); + } + + // Is post moderation alive and well? + $modSettings['postmod_active'] = isset($modSettings['admin_features']) ? in_array('pm', explode(',', $modSettings['admin_features'])) : true; + + // Integration is cool. + if (defined('SMF_INTEGRATION_SETTINGS')) + { + $integration_settings = unserialize(SMF_INTEGRATION_SETTINGS); + foreach ($integration_settings as $hook => $function) + add_integration_function($hook, $function, false); + } + + // Any files to pre include? + if (!empty($modSettings['integrate_pre_include'])) + { + $pre_includes = explode(',', $modSettings['integrate_pre_include']); + foreach ($pre_includes as $include) + { + $include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir)); + if (file_exists($include)) + require_once($include); + } + } + + // Call pre load integration functions. + call_integration_hook('integrate_pre_load'); +} + +// Load all the important user information... +function loadUserSettings() +{ + global $modSettings, $user_settings, $sourcedir, $smcFunc; + global $cookiename, $user_info, $language; + + // Check first the integration, then the cookie, and last the session. + if (count($integration_ids = call_integration_hook('integrate_verify_user')) > 0) + { + $id_member = 0; + foreach ($integration_ids as $integration_id) + { + $integration_id = (int) $integration_id; + if ($integration_id > 0) + { + $id_member = $integration_id; + $already_verified = true; + break; + } + } + } + else + $id_member = 0; + + if (empty($id_member) && isset($_COOKIE[$cookiename])) + { + // Fix a security hole in PHP 4.3.9 and below... + if (preg_match('~^a:[34]:\{i:0;(i:\d{1,6}|s:[1-8]:"\d{1,8}");i:1;s:(0|40):"([a-fA-F0-9]{40})?";i:2;[id]:\d{1,14};(i:3;i:\d;)?\}$~i', $_COOKIE[$cookiename]) == 1) + { + list ($id_member, $password) = @unserialize($_COOKIE[$cookiename]); + $id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0; + } + else + $id_member = 0; + } + elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && ($_SESSION['USER_AGENT'] == $_SERVER['HTTP_USER_AGENT'] || !empty($modSettings['disableCheckUA']))) + { + // !!! Perhaps we can do some more checking on this, such as on the first octet of the IP? + list ($id_member, $password, $login_span) = @unserialize($_SESSION['login_' . $cookiename]); + $id_member = !empty($id_member) && strlen($password) == 40 && $login_span > time() ? (int) $id_member : 0; + } + + // Only load this stuff if the user isn't a guest. + if ($id_member != 0) + { + // Is the member data cached? + if (empty($modSettings['cache_enable']) || $modSettings['cache_enable'] < 2 || ($user_settings = cache_get_data('user_settings-' . $id_member, 60)) == null) + { + $request = $smcFunc['db_query']('', ' + SELECT mem.*, IFNULL(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type + FROM {db_prefix}members AS mem + LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member}) + WHERE mem.id_member = {int:id_member} + LIMIT 1', + array( + 'id_member' => $id_member, + ) + ); + $user_settings = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2) + cache_put_data('user_settings-' . $id_member, $user_settings, 60); + } + + // Did we find 'im? If not, junk it. + if (!empty($user_settings)) + { + // As much as the password should be right, we can assume the integration set things up. + if (!empty($already_verified) && $already_verified === true) + $check = true; + // SHA-1 passwords should be 40 characters long. + elseif (strlen($password) == 40) + $check = sha1($user_settings['passwd'] . $user_settings['password_salt']) == $password; + else + $check = false; + + // Wrong password or not activated - either way, you're going nowhere. + $id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? $user_settings['id_member'] : 0; + } + else + $id_member = 0; + + // If we no longer have the member maybe they're being all hackey, stop brute force! + if (!$id_member) + { + require_once($sourcedir . '/LogInOut.php'); + validatePasswordFlood(!empty($user_settings['id_member']) ? $user_settings['id_member'] : $id_member, !empty($user_settings['passwd_flood']) ? $user_settings['passwd_flood'] : false, $id_member != 0); + } + } + + // Found 'im, let's set up the variables. + if ($id_member != 0) + { + // Let's not update the last visit time in these cases... + // 1. SSI doesn't count as visiting the forum. + // 2. RSS feeds and XMLHTTP requests don't count either. + // 3. If it was set within this session, no need to set it again. + // 4. New session, yet updated < five hours ago? Maybe cache can help. + if (SMF != 'SSI' && !isset($_REQUEST['xml']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] != '.xml') && empty($_SESSION['id_msg_last_visit']) && (empty($modSettings['cache_enable']) || ($_SESSION['id_msg_last_visit'] = cache_get_data('user_last_visit-' . $id_member, 5 * 3600)) === null)) + { + // Do a quick query to make sure this isn't a mistake. + $result = $smcFunc['db_query']('', ' + SELECT poster_time + FROM {db_prefix}messages + WHERE id_msg = {int:id_msg} + LIMIT 1', + array( + 'id_msg' => $user_settings['id_msg_last_visit'], + ) + ); + list ($visitTime) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + $_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit']; + + // If it was *at least* five hours ago... + if ($visitTime < time() - 5 * 3600) + { + updateMemberData($id_member, array('id_msg_last_visit' => (int) $modSettings['maxMsgID'], 'last_login' => time(), 'member_ip' => $_SERVER['REMOTE_ADDR'], 'member_ip2' => $_SERVER['BAN_CHECK_IP'])); + $user_settings['last_login'] = time(); + + if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2) + cache_put_data('user_settings-' . $id_member, $user_settings, 60); + + if (!empty($modSettings['cache_enable'])) + cache_put_data('user_last_visit-' . $id_member, $_SESSION['id_msg_last_visit'], 5 * 3600); + } + } + elseif (empty($_SESSION['id_msg_last_visit'])) + $_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit']; + + $username = $user_settings['member_name']; + + if (empty($user_settings['additional_groups'])) + $user_info = array( + 'groups' => array($user_settings['id_group'], $user_settings['id_post_group']) + ); + else + $user_info = array( + 'groups' => array_merge( + array($user_settings['id_group'], $user_settings['id_post_group']), + explode(',', $user_settings['additional_groups']) + ) + ); + + // Because history has proven that it is possible for groups to go bad - clean up in case. + foreach ($user_info['groups'] as $k => $v) + $user_info['groups'][$k] = (int) $v; + + // This is a logged in user, so definitely not a spider. + $user_info['possibly_robot'] = false; + } + // If the user is a guest, initialize all the critical user settings. + else + { + // This is what a guest's variables should be. + $username = ''; + $user_info = array('groups' => array(-1)); + $user_settings = array(); + + if (isset($_COOKIE[$cookiename])) + $_COOKIE[$cookiename] = ''; + + // Do we perhaps think this is a search robot? Check every five minutes just in case... + if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300)) + { + require_once($sourcedir . '/ManageSearchEngines.php'); + $user_info['possibly_robot'] = SpiderCheck(); + } + elseif (!empty($modSettings['spider_mode'])) + $user_info['possibly_robot'] = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0; + // If we haven't turned on proper spider hunts then have a guess! + else + { + $ci_user_agent = strtolower($_SERVER['HTTP_USER_AGENT']); + $user_info['possibly_robot'] = (strpos($_SERVER['HTTP_USER_AGENT'], 'Mozilla') === false && strpos($_SERVER['HTTP_USER_AGENT'], 'Opera') === false) || strpos($ci_user_agent, 'googlebot') !== false || strpos($ci_user_agent, 'slurp') !== false || strpos($ci_user_agent, 'crawl') !== false; + } + } + + // Set up the $user_info array. + $user_info += array( + 'id' => $id_member, + 'username' => $username, + 'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '', + 'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '', + 'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '', + 'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'], + 'is_guest' => $id_member == 0, + 'is_admin' => in_array(1, $user_info['groups']), + 'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'], + 'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'], + 'ip' => $_SERVER['REMOTE_ADDR'], + 'ip2' => $_SERVER['BAN_CHECK_IP'], + 'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'], + 'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'], + 'time_offset' => empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'], + 'avatar' => array( + 'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '', + 'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'], + 'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1, + 'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0 + ), + 'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '', + 'messages' => empty($user_settings['instant_messages']) ? 0 : $user_settings['instant_messages'], + 'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'], + 'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'], + 'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(), + 'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(), + 'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(), + 'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0, + 'permissions' => array(), + ); + $user_info['groups'] = array_unique($user_info['groups']); + // Make sure that the last item in the ignore boards array is valid. If the list was too long it could have an ending comma that could cause problems. + if (!empty($user_info['ignoreboards']) && empty($user_info['ignoreboards'][$tmp = count($user_info['ignoreboards']) - 1])) + unset($user_info['ignoreboards'][$tmp]); + + // Do we have any languages to validate this? + if (!empty($modSettings['userLanguage']) && (!empty($_GET['language']) || !empty($_SESSION['language']))) + $languages = getLanguages(); + + // Allow the user to change their language if its valid. + if (!empty($modSettings['userLanguage']) && !empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')])) + { + $user_info['language'] = strtr($_GET['language'], './\\:', '____'); + $_SESSION['language'] = $user_info['language']; + } + elseif (!empty($modSettings['userLanguage']) && !empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')])) + $user_info['language'] = strtr($_SESSION['language'], './\\:', '____'); + + // Just build this here, it makes it easier to change/use - administrators can see all boards. + if ($user_info['is_admin']) + $user_info['query_see_board'] = '1=1'; + // Otherwise just the groups in $user_info['groups']. + else + $user_info['query_see_board'] = '(FIND_IN_SET(' . implode(', b.member_groups) != 0 OR FIND_IN_SET(', $user_info['groups']) . ', b.member_groups) != 0' . (isset($user_info['mod_cache']) ? ' OR ' . $user_info['mod_cache']['mq'] : '') . ')'; + + // Build the list of boards they WANT to see. + // This will take the place of query_see_boards in certain spots, so it better include the boards they can see also + + // If they aren't ignoring any boards then they want to see all the boards they can see + if (empty($user_info['ignoreboards'])) + $user_info['query_wanna_see_board'] = $user_info['query_see_board']; + // Ok I guess they don't want to see all the boards + else + $user_info['query_wanna_see_board'] = '(' . $user_info['query_see_board'] . ' AND b.id_board NOT IN (' . implode(',', $user_info['ignoreboards']) . '))'; +} + +// Check for moderators and see if they have access to the board. +function loadBoard() +{ + global $txt, $scripturl, $context, $modSettings; + global $board_info, $board, $topic, $user_info, $smcFunc; + + // Assume they are not a moderator. + $user_info['is_mod'] = false; + $context['user']['is_mod'] = &$user_info['is_mod']; + + // Start the linktree off empty.. + $context['linktree'] = array(); + + // Have they by chance specified a message id but nothing else? + if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg'])) + { + // Make sure the message id is really an int. + $_REQUEST['msg'] = (int) $_REQUEST['msg']; + + // Looking through the message table can be slow, so try using the cache first. + if (($topic = cache_get_data('msg_topic-' . $_REQUEST['msg'], 120)) === NULL) + { + $request = $smcFunc['db_query']('', ' + SELECT id_topic + FROM {db_prefix}messages + WHERE id_msg = {int:id_msg} + LIMIT 1', + array( + 'id_msg' => $_REQUEST['msg'], + ) + ); + + // So did it find anything? + if ($smcFunc['db_num_rows']($request)) + { + list ($topic) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + // Save save save. + cache_put_data('msg_topic-' . $_REQUEST['msg'], $topic, 120); + } + } + + // Remember redirection is the key to avoiding fallout from your bosses. + if (!empty($topic)) + redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']); + else + { + loadPermissions(); + loadTheme(); + fatal_lang_error('topic_gone', false); + } + } + + // Load this board only if it is specified. + if (empty($board) && empty($topic)) + { + $board_info = array('moderators' => array()); + return; + } + + if (!empty($modSettings['cache_enable']) && (empty($topic) || $modSettings['cache_enable'] >= 3)) + { + // !!! SLOW? + if (!empty($topic)) + $temp = cache_get_data('topic_board-' . $topic, 120); + else + $temp = cache_get_data('board-' . $board, 120); + + if (!empty($temp)) + { + $board_info = $temp; + $board = $board_info['id']; + } + } + + if (empty($temp)) + { + $request = $smcFunc['db_query']('', ' + SELECT + c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, + b.id_parent, c.name AS cname, IFNULL(mem.id_member, 0) AS id_moderator, + mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level, + b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect, + b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . ' + FROM {db_prefix}boards AS b' . (!empty($topic) ? ' + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . ' + LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat) + LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link}) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member) + WHERE b.id_board = {raw:board_link}', + array( + 'current_topic' => $topic, + 'board_link' => empty($topic) ? $smcFunc['db_quote']('{int:current_board}', array('current_board' => $board)) : 't.id_board', + ) + ); + // If there aren't any, skip. + if ($smcFunc['db_num_rows']($request) > 0) + { + $row = $smcFunc['db_fetch_assoc']($request); + + // Set the current board. + if (!empty($row['id_board'])) + $board = $row['id_board']; + + // Basic operating information. (globals... :/) + $board_info = array( + 'id' => $board, + 'moderators' => array(), + 'cat' => array( + 'id' => $row['id_cat'], + 'name' => $row['cname'] + ), + 'name' => $row['bname'], + 'description' => $row['description'], + 'num_topics' => $row['num_topics'], + 'unapproved_topics' => $row['unapproved_topics'], + 'unapproved_posts' => $row['unapproved_posts'], + 'unapproved_user_topics' => 0, + 'parent_boards' => getBoardParents($row['id_parent']), + 'parent' => $row['id_parent'], + 'child_level' => $row['child_level'], + 'theme' => $row['id_theme'], + 'override_theme' => !empty($row['override_theme']), + 'profile' => $row['id_profile'], + 'redirect' => $row['redirect'], + 'posts_count' => empty($row['count_posts']), + 'cur_topic_approved' => empty($topic) || $row['approved'], + 'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'], + ); + + // Load the membergroups allowed, and check permissions. + $board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']); + + do + { + if (!empty($row['id_moderator'])) + $board_info['moderators'][$row['id_moderator']] = array( + 'id' => $row['id_moderator'], + 'name' => $row['real_name'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'], + 'link' => '' . $row['real_name'] . '' + ); + } + while ($row = $smcFunc['db_fetch_assoc']($request)); + + // If the board only contains unapproved posts and the user isn't an approver then they can't see any topics. + // If that is the case do an additional check to see if they have any topics waiting to be approved. + if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts')) + { + $smcFunc['db_free_result']($request); // Free the previous result + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(id_topic) + FROM {db_prefix}topics + WHERE id_member_started={int:id_member} + AND approved = {int:unapproved} + AND id_board = {int:board}', + array( + 'id_member' => $user_info['id'], + 'unapproved' => 0, + 'board' => $board, + ) + ); + + list ($board_info['unapproved_user_topics']) = $smcFunc['db_fetch_row']($request); + } + + if (!empty($modSettings['cache_enable']) && (empty($topic) || $modSettings['cache_enable'] >= 3)) + { + // !!! SLOW? + if (!empty($topic)) + cache_put_data('topic_board-' . $topic, $board_info, 120); + cache_put_data('board-' . $board, $board_info, 120); + } + } + else + { + // Otherwise the topic is invalid, there are no moderators, etc. + $board_info = array( + 'moderators' => array(), + 'error' => 'exist' + ); + $topic = null; + $board = 0; + } + $smcFunc['db_free_result']($request); + } + + if (!empty($topic)) + $_GET['board'] = (int) $board; + + if (!empty($board)) + { + // Now check if the user is a moderator. + $user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]); + + if (count(array_intersect($user_info['groups'], $board_info['groups'])) == 0 && !$user_info['is_admin']) + $board_info['error'] = 'access'; + + // Build up the linktree. + $context['linktree'] = array_merge( + $context['linktree'], + array(array( + 'url' => $scripturl . '#c' . $board_info['cat']['id'], + 'name' => $board_info['cat']['name'] + )), + array_reverse($board_info['parent_boards']), + array(array( + 'url' => $scripturl . '?board=' . $board . '.0', + 'name' => $board_info['name'] + )) + ); + } + + // Set the template contextual information. + $context['user']['is_mod'] = &$user_info['is_mod']; + $context['current_topic'] = $topic; + $context['current_board'] = $board; + + // Hacker... you can't see this topic, I'll tell you that. (but moderators can!) + if (!empty($board_info['error']) && ($board_info['error'] != 'access' || !$user_info['is_mod'])) + { + // The permissions and theme need loading, just to make sure everything goes smoothly. + loadPermissions(); + loadTheme(); + + $_GET['board'] = ''; + $_GET['topic'] = ''; + + // The linktree should not give the game away mate! + $context['linktree'] = array( + array( + 'url' => $scripturl, + 'name' => $context['forum_name_html_safe'] + ) + ); + + // If it's a prefetching agent or we're requesting an attachment. + if ((isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') || (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'dlattach')) + { + ob_end_clean(); + header('HTTP/1.1 403 Forbidden'); + die; + } + elseif ($user_info['is_guest']) + { + loadLanguage('Errors'); + is_not_guest($txt['topic_gone']); + } + else + fatal_lang_error('topic_gone', false); + } + + if ($user_info['is_mod']) + $user_info['groups'][] = 3; +} + +// Load this user's permissions. +function loadPermissions() +{ + global $user_info, $board, $board_info, $modSettings, $smcFunc, $sourcedir; + + if ($user_info['is_admin']) + { + banPermissions(); + return; + } + + if (!empty($modSettings['cache_enable'])) + { + $cache_groups = $user_info['groups']; + asort($cache_groups); + $cache_groups = implode(',', $cache_groups); + // If it's a spider then cache it different. + if ($user_info['possibly_robot']) + $cache_groups .= '-spider'; + + if ($modSettings['cache_enable'] >= 2 && !empty($board) && ($temp = cache_get_data('permissions:' . $cache_groups . ':' . $board, 240)) != null && time() - 240 > $modSettings['settings_updated']) + { + list ($user_info['permissions']) = $temp; + banPermissions(); + + return; + } + elseif (($temp = cache_get_data('permissions:' . $cache_groups, 240)) != null && time() - 240 > $modSettings['settings_updated']) + list ($user_info['permissions'], $removals) = $temp; + } + + // If it is detected as a robot, and we are restricting permissions as a special group - then implement this. + $spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : ''; + + if (empty($user_info['permissions'])) + { + // Get the general permissions. + $request = $smcFunc['db_query']('', ' + SELECT permission, add_deny + FROM {db_prefix}permissions + WHERE id_group IN ({array_int:member_groups}) + ' . $spider_restrict, + array( + 'member_groups' => $user_info['groups'], + 'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0, + ) + ); + $removals = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (empty($row['add_deny'])) + $removals[] = $row['permission']; + else + $user_info['permissions'][] = $row['permission']; + } + $smcFunc['db_free_result']($request); + + if (isset($cache_groups)) + cache_put_data('permissions:' . $cache_groups, array($user_info['permissions'], $removals), 240); + } + + // Get the board permissions. + if (!empty($board)) + { + // Make sure the board (if any) has been loaded by loadBoard(). + if (!isset($board_info['profile'])) + fatal_lang_error('no_board'); + + $request = $smcFunc['db_query']('', ' + SELECT permission, add_deny + FROM {db_prefix}board_permissions + WHERE (id_group IN ({array_int:member_groups}) + ' . $spider_restrict . ') + AND id_profile = {int:id_profile}', + array( + 'member_groups' => $user_info['groups'], + 'id_profile' => $board_info['profile'], + 'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (empty($row['add_deny'])) + $removals[] = $row['permission']; + else + $user_info['permissions'][] = $row['permission']; + } + $smcFunc['db_free_result']($request); + } + + // Remove all the permissions they shouldn't have ;). + if (!empty($modSettings['permission_enable_deny'])) + $user_info['permissions'] = array_diff($user_info['permissions'], $removals); + + if (isset($cache_groups) && !empty($board) && $modSettings['cache_enable'] >= 2) + cache_put_data('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240); + + // Banned? Watch, don't touch.. + banPermissions(); + + // Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests + if (!$user_info['is_guest']) + { + if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated']) + { + require_once($sourcedir . '/Subs-Auth.php'); + rebuildModCache(); + } + else + $user_info['mod_cache'] = $_SESSION['mc']; + } +} + +// Loads an array of users' data by ID or member_name. +function loadMemberData($users, $is_name = false, $set = 'normal') +{ + global $user_profile, $modSettings, $board_info, $smcFunc; + + // Can't just look for no users :P. + if (empty($users)) + return false; + + // Make sure it's an array. + $users = !is_array($users) ? array($users) : array_unique($users); + $loaded_ids = array(); + + if (!$is_name && !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3) + { + $users = array_values($users); + for ($i = 0, $n = count($users); $i < $n; $i++) + { + $data = cache_get_data('member_data-' . $set . '-' . $users[$i], 240); + if ($data == null) + continue; + + $loaded_ids[] = $data['id_member']; + $user_profile[$data['id_member']] = $data; + unset($users[$i]); + } + } + + if ($set == 'normal') + { + $select_columns = ' + IFNULL(lo.log_time, 0) AS is_online, IFNULL(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type, + mem.signature, mem.personal_text, mem.location, mem.gender, mem.avatar, mem.id_member, mem.member_name, + mem.real_name, mem.email_address, mem.hide_email, mem.date_registered, mem.website_title, mem.website_url, + mem.birthdate, mem.member_ip, mem.member_ip2, mem.icq, mem.aim, mem.yim, mem.msn, mem.posts, mem.last_login, + mem.karma_good, mem.id_post_group, mem.karma_bad, mem.lngfile, mem.id_group, mem.time_offset, mem.show_online, + mem.buddy_list, mg.online_color AS member_group_color, IFNULL(mg.group_name, {string:blank_string}) AS member_group, + pg.online_color AS post_group_color, IFNULL(pg.group_name, {string:blank_string}) AS post_group, mem.is_activated, mem.warning, + CASE WHEN mem.id_group = 0 OR mg.stars = {string:blank_string} THEN pg.stars ELSE mg.stars END AS stars' . (!empty($modSettings['titlesEnable']) ? ', + mem.usertitle' : ''); + $select_tables = ' + LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member) + LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member) + LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group) + LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)'; + } + elseif ($set == 'profile') + { + $select_columns = ' + IFNULL(lo.log_time, 0) AS is_online, IFNULL(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type, + mem.signature, mem.personal_text, mem.location, mem.gender, mem.avatar, mem.id_member, mem.member_name, + mem.real_name, mem.email_address, mem.hide_email, mem.date_registered, mem.website_title, mem.website_url, + mem.openid_uri, mem.birthdate, mem.icq, mem.aim, mem.yim, mem.msn, mem.posts, mem.last_login, mem.karma_good, + mem.karma_bad, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group, mem.id_theme, mem.buddy_list, + mem.pm_ignore_list, mem.pm_email_notify, mem.pm_receive_from, mem.time_offset' . (!empty($modSettings['titlesEnable']) ? ', mem.usertitle' : '') . ', + mem.time_format, mem.secret_question, mem.is_activated, mem.additional_groups, mem.smiley_set, mem.show_online, + mem.total_time_logged_in, mem.id_post_group, mem.notify_announcements, mem.notify_regularity, mem.notify_send_body, + mem.notify_types, lo.url, mg.online_color AS member_group_color, IFNULL(mg.group_name, {string:blank_string}) AS member_group, + pg.online_color AS post_group_color, IFNULL(pg.group_name, {string:blank_string}) AS post_group, mem.ignore_boards, mem.warning, + CASE WHEN mem.id_group = 0 OR mg.stars = {string:blank_string} THEN pg.stars ELSE mg.stars END AS stars, mem.password_salt, mem.pm_prefs'; + $select_tables = ' + LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member) + LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member) + LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group) + LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)'; + } + elseif ($set == 'minimal') + { + $select_columns = ' + mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.hide_email, mem.date_registered, + mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group'; + $select_tables = ''; + } + else + trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING); + + if (!empty($users)) + { + // Load the member's data. + $request = $smcFunc['db_query']('', ' + SELECT' . $select_columns . ' + FROM {db_prefix}members AS mem' . $select_tables . ' + WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . (count($users) == 1 ? ' = {' . ($is_name ? 'string' : 'int') . ':users}' : ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})'), + array( + 'blank_string' => '', + 'users' => count($users) == 1 ? current($users) : $users, + ) + ); + $new_loaded_ids = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $new_loaded_ids[] = $row['id_member']; + $loaded_ids[] = $row['id_member']; + $row['options'] = array(); + $user_profile[$row['id_member']] = $row; + } + $smcFunc['db_free_result']($request); + } + + if (!empty($new_loaded_ids) && $set !== 'minimal') + { + $request = $smcFunc['db_query']('', ' + SELECT * + FROM {db_prefix}themes + WHERE id_member' . (count($new_loaded_ids) == 1 ? ' = {int:loaded_ids}' : ' IN ({array_int:loaded_ids})'), + array( + 'loaded_ids' => count($new_loaded_ids) == 1 ? $new_loaded_ids[0] : $new_loaded_ids, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $user_profile[$row['id_member']]['options'][$row['variable']] = $row['value']; + $smcFunc['db_free_result']($request); + } + + if (!empty($new_loaded_ids) && !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3) + { + for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++) + cache_put_data('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240); + } + + // Are we loading any moderators? If so, fix their group data... + if (!empty($loaded_ids) && !empty($board_info['moderators']) && $set === 'normal' && count($temp_mods = array_intersect($loaded_ids, array_keys($board_info['moderators']))) !== 0) + { + if (($row = cache_get_data('moderator_group_info', 480)) == null) + { + $request = $smcFunc['db_query']('', ' + SELECT group_name AS member_group, online_color AS member_group_color, stars + FROM {db_prefix}membergroups + WHERE id_group = {int:moderator_group} + LIMIT 1', + array( + 'moderator_group' => 3, + ) + ); + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + cache_put_data('moderator_group_info', $row, 480); + } + + foreach ($temp_mods as $id) + { + // By popular demand, don't show admins or global moderators as moderators. + if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2) + $user_profile[$id]['member_group'] = $row['member_group']; + + // If the Moderator group has no color or stars, but their group does... don't overwrite. + if (!empty($row['stars'])) + $user_profile[$id]['stars'] = $row['stars']; + if (!empty($row['member_group_color'])) + $user_profile[$id]['member_group_color'] = $row['member_group_color']; + } + } + + return empty($loaded_ids) ? false : $loaded_ids; +} + +// Loads the user's basic values... meant for template/theme usage. +function loadMemberContext($user, $display_custom_fields = false) +{ + global $memberContext, $user_profile, $txt, $scripturl, $user_info; + global $context, $modSettings, $board_info, $settings; + global $smcFunc; + static $dataLoaded = array(); + + // If this person's data is already loaded, skip it. + if (isset($dataLoaded[$user])) + return true; + + // We can't load guests or members not loaded by loadMemberData()! + if ($user == 0) + return false; + if (!isset($user_profile[$user])) + { + trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING); + return false; + } + + // Well, it's loaded now anyhow. + $dataLoaded[$user] = true; + $profile = $user_profile[$user]; + + // Censor everything. + censorText($profile['signature']); + censorText($profile['personal_text']); + censorText($profile['location']); + + // Set things up to be used before hand. + $gendertxt = $profile['gender'] == 2 ? $txt['female'] : ($profile['gender'] == 1 ? $txt['male'] : ''); + $profile['signature'] = str_replace(array("\n", "\r"), array('
', ''), $profile['signature']); + $profile['signature'] = parse_bbc($profile['signature'], true, 'sig' . $profile['id_member']); + + $profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0; + $profile['stars'] = empty($profile['stars']) ? array('', '') : explode('#', $profile['stars']); + // Setup the buddy status here (One whole in_array call saved :P) + $profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']); + $buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array(); + + // If we're always html resizing, assume it's too large. + if ($modSettings['avatar_action_too_large'] == 'option_html_resize' || $modSettings['avatar_action_too_large'] == 'option_js_resize') + { + $avatar_width = !empty($modSettings['avatar_max_width_external']) ? ' width="' . $modSettings['avatar_max_width_external'] . '"' : ''; + $avatar_height = !empty($modSettings['avatar_max_height_external']) ? ' height="' . $modSettings['avatar_max_height_external'] . '"' : ''; + } + else + { + $avatar_width = ''; + $avatar_height = ''; + } + + // What a monstrous array... + $memberContext[$user] = array( + 'username' => $profile['member_name'], + 'name' => $profile['real_name'], + 'id' => $profile['id_member'], + 'is_buddy' => $profile['buddy'], + 'is_reverse_buddy' => in_array($user_info['id'], $buddy_list), + 'buddies' => $buddy_list, + 'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '', + 'href' => $scripturl . '?action=profile;u=' . $profile['id_member'], + 'link' => '' . $profile['real_name'] . '', + 'email' => $profile['email_address'], + 'show_email' => showEmailAddress(!empty($profile['hide_email']), $profile['id_member']), + 'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : timeformat($profile['date_registered']), + 'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']), + 'blurb' => $profile['personal_text'], + 'gender' => array( + 'name' => $gendertxt, + 'image' => !empty($profile['gender']) ? '' . $gendertxt . '' : '' + ), + 'website' => array( + 'title' => $profile['website_title'], + 'url' => $profile['website_url'], + ), + 'birth_date' => empty($profile['birthdate']) || $profile['birthdate'] === '0001-01-01' ? '0000-00-00' : (substr($profile['birthdate'], 0, 4) === '0004' ? '0000' . substr($profile['birthdate'], 4) : $profile['birthdate']), + 'signature' => $profile['signature'], + 'location' => $profile['location'], + 'icq' => $profile['icq'] != '' && (empty($modSettings['guest_hideContacts']) || !$user_info['is_guest']) ? array( + 'name' => $profile['icq'], + 'href' => 'http://www.icq.com/whitepages/about_me.php?uin=' . $profile['icq'], + 'link' => '' . $txt['icq_title'] . ' - ' . $profile['icq'] . '', + 'link_text' => '' . $profile['icq'] . '', + ) : array('name' => '', 'add' => '', 'href' => '', 'link' => '', 'link_text' => ''), + 'aim' => $profile['aim'] != '' && (empty($modSettings['guest_hideContacts']) || !$user_info['is_guest']) ? array( + 'name' => $profile['aim'], + 'href' => 'aim:goim?screenname=' . urlencode(strtr($profile['aim'], array(' ' => '%20'))) . '&message=' . $txt['aim_default_message'], + 'link' => '' . $txt['aim_title'] . ' - ' . $profile['aim'] . '', + 'link_text' => '' . $profile['aim'] . '' + ) : array('name' => '', 'href' => '', 'link' => '', 'link_text' => ''), + 'yim' => $profile['yim'] != '' && (empty($modSettings['guest_hideContacts']) || !$user_info['is_guest']) ? array( + 'name' => $profile['yim'], + 'href' => 'http://edit.yahoo.com/config/send_webmesg?.target=' . urlencode($profile['yim']), + 'link' => '' . $txt['yim_title'] . ' - ' . $profile['yim'] . '', + 'link_text' => '' . $profile['yim'] . '' + ) : array('name' => '', 'href' => '', 'link' => '', 'link_text' => ''), + 'msn' => $profile['msn'] !='' && (empty($modSettings['guest_hideContacts']) || !$user_info['is_guest']) ? array( + 'name' => $profile['msn'], + 'href' => 'http://members.msn.com/' . $profile['msn'], + 'link' => '' . $txt['msn_title'] . ' - ' . $profile['msn'] . '', + 'link_text' => '' . $profile['msn'] . '' + ) : array('name' => '', 'href' => '', 'link' => '', 'link_text' => ''), + 'real_posts' => $profile['posts'], + 'posts' => $profile['posts'] > 500000 ? $txt['geek'] : comma_format($profile['posts']), + 'avatar' => array( + 'name' => $profile['avatar'], + 'image' => $profile['avatar'] == '' ? ($profile['id_attach'] > 0 ? '' : '') : (stristr($profile['avatar'], 'http://') ? '' : ''), + 'href' => $profile['avatar'] == '' ? ($profile['id_attach'] > 0 ? (empty($profile['attachment_type']) ? $scripturl . '?action=dlattach;attach=' . $profile['id_attach'] . ';type=avatar' : $modSettings['custom_avatar_url'] . '/' . $profile['filename']) : '') : (stristr($profile['avatar'], 'http://') ? $profile['avatar'] : $modSettings['avatar_url'] . '/' . $profile['avatar']), + 'url' => $profile['avatar'] == '' ? '' : (stristr($profile['avatar'], 'http://') ? $profile['avatar'] : $modSettings['avatar_url'] . '/' . $profile['avatar']) + ), + 'last_login' => empty($profile['last_login']) ? $txt['never'] : timeformat($profile['last_login']), + 'last_login_timestamp' => empty($profile['last_login']) ? 0 : forum_time(0, $profile['last_login']), + 'karma' => array( + 'good' => $profile['karma_good'], + 'bad' => $profile['karma_bad'], + 'allow' => !$user_info['is_guest'] && !empty($modSettings['karmaMode']) && $user_info['id'] != $user && allowedTo('karma_edit') && + ($user_info['posts'] >= $modSettings['karmaMinPosts'] || $user_info['is_admin']), + ), + 'ip' => htmlspecialchars($profile['member_ip']), + 'ip2' => htmlspecialchars($profile['member_ip2']), + 'online' => array( + 'is_online' => $profile['is_online'], + 'text' => $txt[$profile['is_online'] ? 'online' : 'offline'], + 'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'], + 'link' => '' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '', + 'image_href' => $settings['images_url'] . '/' . ($profile['buddy'] ? 'buddy_' : '') . ($profile['is_online'] ? 'useron' : 'useroff') . '.gif', + 'label' => $txt[$profile['is_online'] ? 'online' : 'offline'] + ), + 'language' => $smcFunc['ucwords'](strtr($profile['lngfile'], array('_' => ' ', '-utf8' => ''))), + 'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1, + 'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0, + 'options' => $profile['options'], + 'is_guest' => false, + 'group' => $profile['member_group'], + 'group_color' => $profile['member_group_color'], + 'group_id' => $profile['id_group'], + 'post_group' => $profile['post_group'], + 'post_group_color' => $profile['post_group_color'], + 'group_stars' => str_repeat('*', empty($profile['stars'][0]) || empty($profile['stars'][1]) ? 0 : $profile['stars'][0]), + 'warning' => $profile['warning'], + 'warning_status' => !empty($modSettings['warning_mute']) && $modSettings['warning_mute'] <= $profile['warning'] ? 'mute' : (!empty($modSettings['warning_moderate']) && $modSettings['warning_moderate'] <= $profile['warning'] ? 'moderate' : (!empty($modSettings['warning_watch']) && $modSettings['warning_watch'] <= $profile['warning'] ? 'watch' : (''))), + 'local_time' => timeformat(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false), + ); + + // First do a quick run through to make sure there is something to be shown. + $memberContext[$user]['has_messenger'] = false; + foreach (array('icq', 'msn', 'aim', 'yim') as $messenger) + { + if (!isset($context['disabled_fields'][$messenger]) && !empty($memberContext[$user][$messenger]['link'])) + { + $memberContext[$user]['has_messenger'] = true; + break; + } + } + + // Are we also loading the members custom fields into context? + if ($display_custom_fields && !empty($modSettings['displayFields'])) + { + $memberContext[$user]['custom_fields'] = array(); + if (!isset($context['display_fields'])) + $context['display_fields'] = unserialize($modSettings['displayFields']); + + foreach ($context['display_fields'] as $custom) + { + if (empty($custom['title']) || empty($profile['options'][$custom['colname']])) + continue; + + $value = $profile['options'][$custom['colname']]; + + // BBC? + if ($custom['bbc']) + $value = parse_bbc($value); + // ... or checkbox? + elseif (isset($custom['type']) && $custom['type'] == 'check') + $value = $value ? $txt['yes'] : $txt['no']; + + // Enclosing the user input within some other text? + if (!empty($custom['enclose'])) + $value = strtr($custom['enclose'], array( + '{SCRIPTURL}' => $scripturl, + '{IMAGES_URL}' => $settings['images_url'], + '{DEFAULT_IMAGES_URL}' => $settings['default_images_url'], + '{INPUT}' => $value, + )); + + $memberContext[$user]['custom_fields'][] = array( + 'title' => $custom['title'], + 'colname' => $custom['colname'], + 'value' => $value, + 'placement' => !empty($custom['placement']) ? $custom['placement'] : 0, + ); + } + } + + return true; +} + +function detectBrowser() +{ + global $context, $user_info; + + // The following determines the user agent (browser) as best it can. + $context['browser'] = array( + 'is_opera' => strpos($_SERVER['HTTP_USER_AGENT'], 'Opera') !== false, + 'is_opera6' => strpos($_SERVER['HTTP_USER_AGENT'], 'Opera 6') !== false, + 'is_opera7' => strpos($_SERVER['HTTP_USER_AGENT'], 'Opera 7') !== false || strpos($_SERVER['HTTP_USER_AGENT'], 'Opera/7') !== false, + 'is_opera8' => strpos($_SERVER['HTTP_USER_AGENT'], 'Opera 8') !== false || strpos($_SERVER['HTTP_USER_AGENT'], 'Opera/8') !== false, + 'is_opera9' => preg_match('~Opera[ /]9(?!\\.[89])~', $_SERVER['HTTP_USER_AGENT']) === 1, + 'is_opera10' => preg_match('~Opera[ /]10\\.~', $_SERVER['HTTP_USER_AGENT']) === 1 || (preg_match('~Opera[ /]9\\.[89]~', $_SERVER['HTTP_USER_AGENT']) === 1 && preg_match('~Version/1[0-9]\\.~', $_SERVER['HTTP_USER_AGENT']) === 1), + 'is_ie4' => strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE 4') !== false && strpos($_SERVER['HTTP_USER_AGENT'], 'WebTV') === false, + 'is_webkit' => strpos($_SERVER['HTTP_USER_AGENT'], 'AppleWebKit') !== false, + 'is_mac_ie' => strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE 5.') !== false && strpos($_SERVER['HTTP_USER_AGENT'], 'Mac') !== false, + 'is_web_tv' => strpos($_SERVER['HTTP_USER_AGENT'], 'WebTV') !== false, + 'is_konqueror' => strpos($_SERVER['HTTP_USER_AGENT'], 'Konqueror') !== false, + 'is_firefox' => preg_match('~(?:Firefox|Ice[wW]easel|IceCat)/~', $_SERVER['HTTP_USER_AGENT']) === 1, + 'is_firefox1' => preg_match('~(?:Firefox|Ice[wW]easel|IceCat)/1\\.~', $_SERVER['HTTP_USER_AGENT']) === 1, + 'is_firefox2' => preg_match('~(?:Firefox|Ice[wW]easel|IceCat)/2\\.~', $_SERVER['HTTP_USER_AGENT']) === 1, + 'is_firefox3' => preg_match('~(?:Firefox|Ice[wW]easel|IceCat|Shiretoko|Minefield)/3\\.~', $_SERVER['HTTP_USER_AGENT']) === 1, + 'is_iphone' => strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') !== false || strpos($_SERVER['HTTP_USER_AGENT'], 'iPod') !== false, + 'is_android' => strpos($_SERVER['HTTP_USER_AGENT'], 'Android') !== false, + ); + + $context['browser']['is_chrome'] = $context['browser']['is_webkit'] && strpos($_SERVER['HTTP_USER_AGENT'], 'Chrome') !== false; + $context['browser']['is_safari'] = !$context['browser']['is_chrome'] && strpos($_SERVER['HTTP_USER_AGENT'], 'Safari') !== false; + $context['browser']['is_gecko'] = strpos($_SERVER['HTTP_USER_AGENT'], 'Gecko') !== false && !$context['browser']['is_webkit'] && !$context['browser']['is_konqueror']; + + // Internet Explorer 5 and 6 are often "emulated". + $context['browser']['is_ie8'] = !$context['browser']['is_opera'] && !$context['browser']['is_gecko'] && !$context['browser']['is_web_tv'] && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE 8') !== false; + $context['browser']['is_ie7'] = !$context['browser']['is_opera'] && !$context['browser']['is_gecko'] && !$context['browser']['is_web_tv'] && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE 7') !== false && !$context['browser']['is_ie8']; + $context['browser']['is_ie6'] = !$context['browser']['is_opera'] && !$context['browser']['is_gecko'] && !$context['browser']['is_web_tv'] && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE 6') !== false && !$context['browser']['is_ie8'] && !$context['browser']['is_ie7']; + $context['browser']['is_ie5.5'] = !$context['browser']['is_opera'] && !$context['browser']['is_gecko'] && !$context['browser']['is_web_tv'] && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE 5.5') !== false; + $context['browser']['is_ie5'] = !$context['browser']['is_opera'] && !$context['browser']['is_gecko'] && !$context['browser']['is_web_tv'] && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE 5.0') !== false; + + $context['browser']['is_ie'] = $context['browser']['is_ie4'] || $context['browser']['is_ie5'] || $context['browser']['is_ie5.5'] || $context['browser']['is_ie6'] || $context['browser']['is_ie7'] || $context['browser']['is_ie8']; + // Before IE8 we need to fix IE... lots! + $context['browser']['ie_standards_fix'] = !$context['browser']['is_ie8']; + + $context['browser']['needs_size_fix'] = ($context['browser']['is_ie5'] || $context['browser']['is_ie5.5'] || $context['browser']['is_ie4'] || $context['browser']['is_opera6']) && strpos($_SERVER['HTTP_USER_AGENT'], 'Mac') === false; + + // This isn't meant to be reliable, it's just meant to catch most bots to prevent PHPSESSID from showing up. + $context['browser']['possibly_robot'] = !empty($user_info['possibly_robot']); + + // Robots shouldn't be logging in or registering. So, they aren't a bot. Better to be wrong than sorry (or people won't be able to log in!), anyway. + if ((isset($_REQUEST['action']) && in_array($_REQUEST['action'], array('login', 'login2', 'register'))) || !$user_info['is_guest']) + $context['browser']['possibly_robot'] = false; +} + +// Load a theme, by ID. +function loadTheme($id_theme = 0, $initialize = true) +{ + global $user_info, $user_settings, $board_info, $sc, $boarddir; + global $txt, $boardurl, $scripturl, $mbname, $modSettings, $language; + global $context, $settings, $options, $sourcedir, $ssi_theme, $smcFunc; + + // The theme was specified by parameter. + if (!empty($id_theme)) + $id_theme = (int) $id_theme; + // The theme was specified by REQUEST. + elseif (!empty($_REQUEST['theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum'))) + { + $id_theme = (int) $_REQUEST['theme']; + $_SESSION['id_theme'] = $id_theme; + } + // The theme was specified by REQUEST... previously. + elseif (!empty($_SESSION['id_theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum'))) + $id_theme = (int) $_SESSION['id_theme']; + // The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.) + elseif (!empty($user_info['theme']) && !isset($_REQUEST['theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum'))) + $id_theme = $user_info['theme']; + // The theme was specified by the board. + elseif (!empty($board_info['theme'])) + $id_theme = $board_info['theme']; + // The theme is the forum's default. + else + $id_theme = $modSettings['theme_guests']; + + // Verify the id_theme... no foul play. + // Always allow the board specific theme, if they are overriding. + if (!empty($board_info['theme']) && $board_info['override_theme']) + $id_theme = $board_info['theme']; + // If they have specified a particular theme to use with SSI allow it to be used. + elseif (!empty($ssi_theme) && $id_theme == $ssi_theme) + $id_theme = (int) $id_theme; + elseif (!empty($modSettings['knownThemes']) && !allowedTo('admin_forum')) + { + $themes = explode(',', $modSettings['knownThemes']); + if (!in_array($id_theme, $themes)) + $id_theme = $modSettings['theme_guests']; + else + $id_theme = (int) $id_theme; + } + else + $id_theme = (int) $id_theme; + + $member = empty($user_info['id']) ? -1 : $user_info['id']; + + if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2 && ($temp = cache_get_data('theme_settings-' . $id_theme . ':' . $member, 60)) != null && time() - 60 > $modSettings['settings_updated']) + { + $themeData = $temp; + $flag = true; + } + elseif (($temp = cache_get_data('theme_settings-' . $id_theme, 90)) != null && time() - 60 > $modSettings['settings_updated']) + $themeData = $temp + array($member => array()); + else + $themeData = array(-1 => array(), 0 => array(), $member => array()); + + if (empty($flag)) + { + // Load variables from the current or default theme, global or this user's. + $result = $smcFunc['db_query']('', ' + SELECT variable, value, id_member, id_theme + FROM {db_prefix}themes + WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . ' + AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)'), + array( + 'id_theme' => $id_theme, + 'id_member' => $member, + ) + ); + // Pick between $settings and $options depending on whose data it is. + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + // There are just things we shouldn't be able to change as members. + if ($row['id_member'] != 0 && in_array($row['variable'], array('actual_theme_url', 'actual_images_url', 'base_theme_dir', 'base_theme_url', 'default_images_url', 'default_theme_dir', 'default_theme_url', 'default_template', 'images_url', 'number_recent_posts', 'smiley_sets_default', 'theme_dir', 'theme_id', 'theme_layers', 'theme_templates', 'theme_url'))) + continue; + + // If this is the theme_dir of the default theme, store it. + if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member'])) + $themeData[0]['default_' . $row['variable']] = $row['value']; + + // If this isn't set yet, is a theme option, or is not the default theme.. + if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1') + $themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value']; + } + $smcFunc['db_free_result']($result); + + if (!empty($themeData[-1])) + foreach ($themeData[-1] as $k => $v) + { + if (!isset($themeData[$member][$k])) + $themeData[$member][$k] = $v; + } + + if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2) + cache_put_data('theme_settings-' . $id_theme . ':' . $member, $themeData, 60); + // Only if we didn't already load that part of the cache... + elseif (!isset($temp)) + cache_put_data('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90); + } + + $settings = $themeData[0]; + $options = $themeData[$member]; + + $settings['theme_id'] = $id_theme; + + $settings['actual_theme_url'] = $settings['theme_url']; + $settings['actual_images_url'] = $settings['images_url']; + $settings['actual_theme_dir'] = $settings['theme_dir']; + + $settings['template_dirs'] = array(); + // This theme first. + $settings['template_dirs'][] = $settings['theme_dir']; + + // Based on theme (if there is one). + if (!empty($settings['base_theme_dir'])) + $settings['template_dirs'][] = $settings['base_theme_dir']; + + // Lastly the default theme. + if ($settings['theme_dir'] != $settings['default_theme_dir']) + $settings['template_dirs'][] = $settings['default_theme_dir']; + + if (!$initialize) + return; + + // Check to see if they're accessing it from the wrong place. + if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME'])) + { + $detected_url = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on' ? 'https://' : 'http://'; + $detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST']; + $temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/')); + if ($temp != '/') + $detected_url .= $temp; + } + if (isset($detected_url) && $detected_url != $boardurl) + { + // Try #1 - check if it's in a list of alias addresses. + if (!empty($modSettings['forum_alias_urls'])) + { + $aliases = explode(',', $modSettings['forum_alias_urls']); + + foreach ($aliases as $alias) + { + // Rip off all the boring parts, spaces, etc. + if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias)) + $do_fix = true; + } + } + + // Hmm... check #2 - is it just different by a www? Send them to the correct place!! + if (empty($do_fix) && strtr($detected_url, array('://' => '://www.')) == $boardurl && (empty($_GET) || count($_GET) == 1) && SMF != 'SSI') + { + // Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;). + if (empty($_GET)) + redirectexit('wwwRedirect'); + else + { + list ($k, $v) = each($_GET); + + if ($k != 'wwwRedirect') + redirectexit('wwwRedirect;' . $k . '=' . $v); + } + } + + // #3 is just a check for SSL... + if (strtr($detected_url, array('https://' => 'http://')) == $boardurl) + $do_fix = true; + + // Okay, #4 - perhaps it's an IP address? We're gonna want to use that one, then. (assuming it's the IP or something...) + if (!empty($do_fix) || preg_match('~^http[s]?://(?:[\d\.:]+|\[[\d:]+\](?::\d+)?)(?:$|/)~', $detected_url) == 1) + { + // Caching is good ;). + $oldurl = $boardurl; + + // Fix $boardurl and $scripturl. + $boardurl = $detected_url; + $scripturl = strtr($scripturl, array($oldurl => $boardurl)); + $_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl)); + + // Fix the theme urls... + $settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl)); + $settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl)); + $settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl)); + $settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl)); + $settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl)); + $settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl)); + + // And just a few mod settings :). + $modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl)); + $modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl)); + + // Clean up after loadBoard(). + if (isset($board_info['moderators'])) + { + foreach ($board_info['moderators'] as $k => $dummy) + { + $board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl)); + $board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl)); + } + } + foreach ($context['linktree'] as $k => $dummy) + $context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl)); + } + } + // Set up the contextual user array. + $context['user'] = array( + 'id' => $user_info['id'], + 'is_logged' => !$user_info['is_guest'], + 'is_guest' => &$user_info['is_guest'], + 'is_admin' => &$user_info['is_admin'], + 'is_mod' => &$user_info['is_mod'], + // A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator. + 'can_mod' => allowedTo('access_mod_center') || (!$user_info['is_guest'] && ($user_info['mod_cache']['gq'] != '0=1' || $user_info['mod_cache']['bq'] != '0=1' || ($modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap'])))), + 'username' => $user_info['username'], + 'language' => $user_info['language'], + 'email' => $user_info['email'], + 'ignoreusers' => $user_info['ignoreusers'], + ); + if (!$context['user']['is_guest']) + $context['user']['name'] = $user_info['name']; + elseif ($context['user']['is_guest'] && !empty($txt['guest_title'])) + $context['user']['name'] = $txt['guest_title']; + + // Determine the current smiley set. + $user_info['smiley_set'] = (!in_array($user_info['smiley_set'], explode(',', $modSettings['smiley_sets_known'])) && $user_info['smiley_set'] != 'none') || empty($modSettings['smiley_sets_enable']) ? (!empty($settings['smiley_sets_default']) ? $settings['smiley_sets_default'] : $modSettings['smiley_sets_default']) : $user_info['smiley_set']; + $context['user']['smiley_set'] = $user_info['smiley_set']; + + // Some basic information... + if (!isset($context['html_headers'])) + $context['html_headers'] = ''; + + $context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | '; + $context['session_var'] = $_SESSION['session_var']; + $context['session_id'] = $_SESSION['session_value']; + $context['forum_name'] = $mbname; + $context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']); + $context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']); + $context['current_action'] = isset($_REQUEST['action']) ? $_REQUEST['action'] : null; + $context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null; + if (isset($modSettings['load_average'])) + $context['load_average'] = $modSettings['load_average']; + + // Set some permission related settings. + $context['show_login_bar'] = $user_info['is_guest'] && !empty($modSettings['enableVBStyleLogin']); + + // This determines the server... not used in many places, except for login fixing. + $context['server'] = array( + 'is_iis' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false, + 'is_apache' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false, + 'is_lighttpd' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false, + 'is_nginx' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false, + 'is_cgi' => isset($_SERVER['SERVER_SOFTWARE']) && strpos(php_sapi_name(), 'cgi') !== false, + 'is_windows' => strpos(PHP_OS, 'WIN') === 0, + 'iso_case_folding' => ord(strtolower(chr(138))) === 154, + 'complex_preg_chars' => @version_compare(PHP_VERSION, '4.3.3') != -1, + ); + // A bug in some versions of IIS under CGI (older ones) makes cookie setting not work with Location: headers. + $context['server']['needs_login_fix'] = $context['server']['is_cgi'] && $context['server']['is_iis']; + + // Detect the browser. This is separated out because it's also used in attachment downloads + detectBrowser(); + + // Set the top level linktree up. + array_unshift($context['linktree'], array( + 'url' => $scripturl, + 'name' => $context['forum_name_html_safe'] + )); + + // This allows sticking some HTML on the page output - useful for controls. + $context['insert_after_template'] = ''; + + if (!isset($txt)) + $txt = array(); + $simpleActions = array( + 'findmember', + 'helpadmin', + 'printpage', + 'quotefast', + 'spellcheck', + ); + + // Wireless mode? Load up the wireless stuff. + if (WIRELESS) + { + $context['template_layers'] = array(WIRELESS_PROTOCOL); + loadTemplate('Wireless'); + loadLanguage('Wireless+index+Modifications'); + } + // Output is fully XML, so no need for the index template. + elseif (isset($_REQUEST['xml'])) + { + loadLanguage('index+Modifications'); + loadTemplate('Xml'); + $context['template_layers'] = array(); + } + // These actions don't require the index template at all. + elseif (!empty($_REQUEST['action']) && in_array($_REQUEST['action'], $simpleActions)) + { + loadLanguage('index+Modifications'); + $context['template_layers'] = array(); + } + else + { + // Custom templates to load, or just default? + if (isset($settings['theme_templates'])) + $templates = explode(',', $settings['theme_templates']); + else + $templates = array('index'); + + // Load each template... + foreach ($templates as $template) + loadTemplate($template); + + // ...and attempt to load their associated language files. + $required_files = implode('+', array_merge($templates, array('Modifications'))); + loadLanguage($required_files, '', false); + + // Custom template layers? + if (isset($settings['theme_layers'])) + $context['template_layers'] = explode(',', $settings['theme_layers']); + else + $context['template_layers'] = array('html', 'body'); + } + + // Initialize the theme. + loadSubTemplate('init', 'ignore'); + + // Load the compatibility stylesheet if the theme hasn't been updated for 2.0 RC2 (yet). + if (isset($settings['theme_version']) && (version_compare($settings['theme_version'], '2.0 RC2', '<') || strpos($settings['theme_version'], '2.0 Beta') !== false)) + loadTemplate(false, 'compat'); + + // Guests may still need a name. + if ($context['user']['is_guest'] && empty($context['user']['name'])) + $context['user']['name'] = $txt['guest_title']; + + // Any theme-related strings that need to be loaded? + if (!empty($settings['require_theme_strings'])) + loadLanguage('ThemeStrings', '', false); + + // We allow theme variants, because we're cool. + $context['theme_variant'] = ''; + $context['theme_variant_url'] = ''; + if (!empty($settings['theme_variants'])) + { + // Overriding - for previews and that ilk. + if (!empty($_REQUEST['variant'])) + $_SESSION['id_variant'] = $_REQUEST['variant']; + // User selection? + if (empty($settings['disable_user_variant']) || allowedTo('admin_forum')) + $context['theme_variant'] = !empty($_SESSION['id_variant']) ? $_SESSION['id_variant'] : (!empty($options['theme_variant']) ? $options['theme_variant'] : ''); + // If not a user variant, select the default. + if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants'])) + $context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0]; + + // Do this to keep things easier in the templates. + $context['theme_variant'] = '_' . $context['theme_variant']; + $context['theme_variant_url'] = $context['theme_variant'] . '/'; + } + + // Let's be compatible with old themes! + if (!function_exists('template_html_above') && in_array('html', $context['template_layers'])) + $context['template_layers'] = array('main'); + + // Allow overriding the board wide time/number formats. + if (empty($user_settings['time_format']) && !empty($txt['time_format'])) + $user_info['time_format'] = $txt['time_format']; + $txt['number_format'] = empty($txt['number_format']) ? empty($modSettings['number_format']) ? '' : $modSettings['number_format'] : $txt['number_format']; + + if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'always') + { + $settings['theme_url'] = $settings['default_theme_url']; + $settings['images_url'] = $settings['default_images_url']; + $settings['theme_dir'] = $settings['default_theme_dir']; + } + // Make a special URL for the language. + $settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']); + + // Set the character set from the template. + $context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']; + $context['utf8'] = $context['character_set'] === 'UTF-8' && (strpos(strtolower(PHP_OS), 'win') === false || @version_compare(PHP_VERSION, '4.2.3') != -1); + $context['right_to_left'] = !empty($txt['lang_rtl']); + + $context['tabindex'] = 1; + + // Fix font size with HTML 4.01, etc. + if (isset($settings['doctype'])) + $context['browser']['needs_size_fix'] |= $settings['doctype'] == 'html' && $context['browser']['is_ie6']; + + // Compatibility. + if (!isset($settings['theme_version'])) + $modSettings['memberCount'] = $modSettings['totalMembers']; + + // This allows us to change the way things look for the admin. + $context['admin_features'] = isset($modSettings['admin_features']) ? explode(',', $modSettings['admin_features']) : array('cd,cp,k,w,rg,ml,pm'); + + // If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!) + if ((!empty($modSettings['mail_next_send']) && $modSettings['mail_next_send'] < time() && empty($modSettings['mail_queue_use_cron'])) || empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time()) + { + if ($context['browser']['possibly_robot']) + { + //!!! Maybe move this somewhere better?! + require_once($sourcedir . '/ScheduledTasks.php'); + + // What to do, what to do?! + if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time()) + AutoTask(); + else + ReduceMailQueue(); + } + else + { + $type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq'; + $ts = $type == 'mailq' ? $modSettings['mail_next_send'] : $modSettings['next_task_time']; + + $context['html_headers'] .= ' + '; + } + } + + // Any files to include at this point? + if (!empty($modSettings['integrate_theme_include'])) + { + $theme_includes = explode(',', $modSettings['integrate_theme_include']); + foreach ($theme_includes as $include) + { + $include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir'])); + if (file_exists($include)) + require_once($include); + } + } + + // Call load theme integration functions. + call_integration_hook('integrate_load_theme'); + + // We are ready to go. + $context['theme_loaded'] = true; +} + +// Load a template - if the theme doesn't include it, use the default. +function loadTemplate($template_name, $style_sheets = array(), $fatal = true) +{ + global $context, $settings, $txt, $scripturl, $boarddir, $db_show_debug; + + // Do any style sheets first, cause we're easy with those. + if (!empty($style_sheets)) + { + if (!is_array($style_sheets)) + $style_sheets = array($style_sheets); + + foreach ($style_sheets as $sheet) + { + // Prevent the style sheet from being included twice. + if (strpos($context['html_headers'], 'id="' . $sheet . '_css"') !== false) + continue; + + $sheet_path = file_exists($settings['theme_dir']. '/css/' . $sheet . '.css') ? 'theme_url' : (file_exists($settings['default_theme_dir']. '/css/' . $sheet . '.css') ? 'default_theme_url' : ''); + if ($sheet_path) + { + $context['html_headers'] .= "\n\t" . ''; + if ($db_show_debug === true) + $context['debug']['sheets'][] = $sheet . ' (' . basename($settings[$sheet_path]) . ')'; + } + } + } + + // No template to load? + if ($template_name === false) + return true; + + $loaded = false; + foreach ($settings['template_dirs'] as $template_dir) + { + if (file_exists($template_dir . '/' . $template_name . '.template.php')) + { + $loaded = true; + template_include($template_dir . '/' . $template_name . '.template.php', true); + break; + } + } + + if ($loaded) + { + // For compatibility reasons, if this is the index template without new functions, include compatible stuff. + if (substr($template_name, 0, 5) == 'index' && !function_exists('template_button_strip')) + loadTemplate('Compat'); + + if ($db_show_debug === true) + $context['debug']['templates'][] = $template_name . ' (' . basename($template_dir) . ')'; + + // If they have specified an initialization function for this template, go ahead and call it now. + if (function_exists('template_' . $template_name . '_init')) + call_user_func('template_' . $template_name . '_init'); + } + // Hmmm... doesn't exist?! I don't suppose the directory is wrong, is it? + elseif (!file_exists($settings['default_theme_dir']) && file_exists($boarddir . '/Themes/default')) + { + $settings['default_theme_dir'] = $boarddir . '/Themes/default'; + $settings['template_dirs'][] = $settings['default_theme_dir']; + + if (!empty($context['user']['is_admin']) && !isset($_GET['th'])) + { + loadLanguage('Errors'); + echo ' +
+ ', $txt['theme_dir_wrong'], ' +
'; + } + + loadTemplate($template_name); + } + // Cause an error otherwise. + elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal) + fatal_lang_error('theme_template_error', 'template', array((string) $template_name)); + elseif ($fatal) + die(log_error(sprintf(isset($txt['theme_template_error']) ? $txt['theme_template_error'] : 'Unable to load Themes/default/%s.template.php!', (string) $template_name), 'template')); + else + return false; +} + +// Load a sub template... fatal is for templates that shouldn't get a 'pretty' error screen. +function loadSubTemplate($sub_template_name, $fatal = false) +{ + global $context, $settings, $options, $txt, $db_show_debug; + + if ($db_show_debug === true) + $context['debug']['sub_templates'][] = $sub_template_name; + + // Figure out what the template function is named. + $theme_function = 'template_' . $sub_template_name; + if (function_exists($theme_function)) + $theme_function(); + elseif ($fatal === false) + fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name)); + elseif ($fatal !== 'ignore') + die(log_error(sprintf(isset($txt['theme_template_error']) ? $txt['theme_template_error'] : 'Unable to load the %s sub template!', (string) $sub_template_name), 'template')); + + // Are we showing debugging for templates? Just make sure not to do it before the doctype... + if (allowedTo('admin_forum') && isset($_REQUEST['debug']) && !in_array($sub_template_name, array('init', 'main_below')) && ob_get_length() > 0 && !isset($_REQUEST['xml'])) + { + echo ' +
---- ', $sub_template_name, ' ends ----
'; + } +} + +// Load a language file. Tries the current and default themes as well as the user and global languages. +function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false) +{ + global $user_info, $language, $settings, $context, $modSettings; + global $cachedir, $db_show_debug, $sourcedir, $txt; + static $already_loaded = array(); + + // Default to the user's language. + if ($lang == '') + $lang = isset($user_info['language']) ? $user_info['language'] : $language; + + // Do we want the English version of language file as fallback? + if (empty($modSettings['disable_language_fallback']) && $lang != 'english') + loadLanguage($template_name, 'english', false); + + if (!$force_reload && isset($already_loaded[$template_name]) && $already_loaded[$template_name] == $lang) + return $lang; + + // Make sure we have $settings - if not we're in trouble and need to find it! + if (empty($settings['default_theme_dir'])) + { + require_once($sourcedir . '/ScheduledTasks.php'); + loadEssentialThemeData(); + } + + // What theme are we in? + $theme_name = basename($settings['theme_url']); + if (empty($theme_name)) + $theme_name = 'unknown'; + + // For each file open it up and write it out! + foreach (explode('+', $template_name) as $template) + { + // Obviously, the current theme is most important to check. + $attempts = array( + array($settings['theme_dir'], $template, $lang, $settings['theme_url']), + array($settings['theme_dir'], $template, $language, $settings['theme_url']), + ); + + // Do we have a base theme to worry about? + if (isset($settings['base_theme_dir'])) + { + $attempts[] = array($settings['base_theme_dir'], $template, $lang, $settings['base_theme_url']); + $attempts[] = array($settings['base_theme_dir'], $template, $language, $settings['base_theme_url']); + } + + // Fall back on the default theme if necessary. + $attempts[] = array($settings['default_theme_dir'], $template, $lang, $settings['default_theme_url']); + $attempts[] = array($settings['default_theme_dir'], $template, $language, $settings['default_theme_url']); + + // Fall back on the English language if none of the preferred languages can be found. + if (!in_array('english', array($lang, $language))) + { + $attempts[] = array($settings['theme_dir'], $template, 'english', $settings['theme_url']); + $attempts[] = array($settings['default_theme_dir'], $template, 'english', $settings['default_theme_url']); + } + + // Try to find the language file. + $found = false; + foreach ($attempts as $k => $file) + { + if (file_exists($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php')) + { + // Include it! + template_include($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php'); + + // Note that we found it. + $found = true; + + break; + } + } + + // That couldn't be found! Log the error, but *try* to continue normally. + if (!$found && $fatal) + { + log_error(sprintf($txt['theme_language_error'], $template_name . '.' . $lang, 'template')); + break; + } + } + + // Keep track of what we're up to soldier. + if ($db_show_debug === true) + $context['debug']['language_files'][] = $template_name . '.' . $lang . ' (' . $theme_name . ')'; + + // Remember what we have loaded, and in which language. + $already_loaded[$template_name] = $lang; + + // Return the language actually loaded. + return $lang; +} + +// Get all parent boards (requires first parent as parameter) +function getBoardParents($id_parent) +{ + global $scripturl, $smcFunc; + + // First check if we have this cached already. + if (($boards = cache_get_data('board_parents-' . $id_parent, 480)) === null) + { + $boards = array(); + $original_parent = $id_parent; + + // Loop while the parent is non-zero. + while ($id_parent != 0) + { + $result = $smcFunc['db_query']('', ' + SELECT + b.id_parent, b.name, {int:board_parent} AS id_board, IFNULL(mem.id_member, 0) AS id_moderator, + mem.real_name, b.child_level + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member) + WHERE b.id_board = {int:board_parent}', + array( + 'board_parent' => $id_parent, + ) + ); + // In the EXTREMELY unlikely event this happens, give an error message. + if ($smcFunc['db_num_rows']($result) == 0) + fatal_lang_error('parent_not_found', 'critical'); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + if (!isset($boards[$row['id_board']])) + { + $id_parent = $row['id_parent']; + $boards[$row['id_board']] = array( + 'url' => $scripturl . '?board=' . $row['id_board'] . '.0', + 'name' => $row['name'], + 'level' => $row['child_level'], + 'moderators' => array() + ); + } + // If a moderator exists for this board, add that moderator for all children too. + if (!empty($row['id_moderator'])) + foreach ($boards as $id => $dummy) + { + $boards[$id]['moderators'][$row['id_moderator']] = array( + 'id' => $row['id_moderator'], + 'name' => $row['real_name'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'], + 'link' => '' . $row['real_name'] . '' + ); + } + } + $smcFunc['db_free_result']($result); + } + + cache_put_data('board_parents-' . $original_parent, $boards, 480); + } + + return $boards; +} + +// Attempt to reload our languages. +function getLanguages($use_cache = true, $favor_utf8 = true) +{ + global $context, $smcFunc, $settings, $modSettings; + + // Either we don't use the cache, or its expired. + if (!$use_cache || ($context['languages'] = cache_get_data('known_languages' . ($favor_utf8 ? '' : '_all'), !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600)) == null) + { + // If we don't have our theme information yet, lets get it. + if (empty($settings['default_theme_dir'])) + loadTheme(0, false); + + // Default language directories to try. + $language_directories = array( + $settings['default_theme_dir'] . '/languages', + $settings['actual_theme_dir'] . '/languages', + ); + + // We possibly have a base theme directory. + if (!empty($settings['base_theme_dir'])) + $language_directories[] = $settings['base_theme_dir'] . '/languages'; + + // Remove any duplicates. + $language_directories = array_unique($language_directories); + + foreach ($language_directories as $language_dir) + { + // Can't look in here... doesn't exist! + if (!file_exists($language_dir)) + continue; + + $dir = dir($language_dir); + while ($entry = $dir->read()) + { + // Look for the index language file.... + if (!preg_match('~^index\.(.+)\.php$~', $entry, $matches)) + continue; + + $context['languages'][$matches[1]] = array( + 'name' => $smcFunc['ucwords'](strtr($matches[1], array('_' => ' '))), + 'selected' => false, + 'filename' => $matches[1], + 'location' => $language_dir . '/index.' . $matches[1] . '.php', + ); + + } + $dir->close(); + } + + // Favoring UTF8? Then prevent us from selecting non-UTF8 versions. + if ($favor_utf8) + { + foreach ($context['languages'] as $lang) + if (substr($lang['filename'], strlen($lang['filename']) - 5, 5) != '-utf8' && isset($context['languages'][$lang['filename'] . '-utf8'])) + unset($context['languages'][$lang['filename']]); + } + + // Lets cash in on this deal. + if (!empty($modSettings['cache_enable'])) + cache_put_data('known_languages' . ($favor_utf8 ? '' : '_all'), $context['languages'], !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600); + } + + return $context['languages']; +} + +// Replace all vulgar words with respective proper words. (substring or whole words..) +function &censorText(&$text, $force = false) +{ + global $modSettings, $options, $settings, $txt; + static $censor_vulgar = null, $censor_proper; + + if ((!empty($options['show_no_censored']) && $settings['allow_no_censored'] && !$force) || empty($modSettings['censor_vulgar'])) + return $text; + + // If they haven't yet been loaded, load them. + if ($censor_vulgar == null) + { + $censor_vulgar = explode("\n", $modSettings['censor_vulgar']); + $censor_proper = explode("\n", $modSettings['censor_proper']); + + // Quote them for use in regular expressions. + for ($i = 0, $n = count($censor_vulgar); $i < $n; $i++) + { + $censor_vulgar[$i] = strtr(preg_quote($censor_vulgar[$i], '/'), array('\\\\\\*' => '[*]', '\\*' => '[^\s]*?', '&' => '&')); + $censor_vulgar[$i] = (empty($modSettings['censorWholeWord']) ? '/' . $censor_vulgar[$i] . '/' : '/(?<=^|\W)' . $censor_vulgar[$i] . '(?=$|\W)/') . (empty($modSettings['censorIgnoreCase']) ? '' : 'i') . ((empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8' ? 'u' : ''); + + if (strpos($censor_vulgar[$i], '\'') !== false) + { + $censor_proper[count($censor_vulgar)] = $censor_proper[$i]; + $censor_vulgar[count($censor_vulgar)] = strtr($censor_vulgar[$i], array('\'' => ''')); + } + } + } + + // Censoring isn't so very complicated :P. + $text = preg_replace($censor_vulgar, $censor_proper, $text); + return $text; +} + +// Load the template/language file using eval or require? (with eval we can show an error message!) +function template_include($filename, $once = false) +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings; + global $user_info, $boardurl, $boarddir, $sourcedir; + global $maintenance, $mtitle, $mmessage; + static $templates = array(); + + // We want to be able to figure out any errors... + @ini_set('track_errors', '1'); + + // Don't include the file more than once, if $once is true. + if ($once && in_array($filename, $templates)) + return; + // Add this file to the include list, whether $once is true or not. + else + $templates[] = $filename; + + // Are we going to use eval? + if (empty($modSettings['disableTemplateEval'])) + { + $file_found = file_exists($filename) && eval('?' . '>' . rtrim(file_get_contents($filename))) !== false; + $settings['current_include_filename'] = $filename; + } + else + { + $file_found = file_exists($filename); + + if ($once && $file_found) + require_once($filename); + elseif ($file_found) + require($filename); + } + + if ($file_found !== true) + { + ob_end_clean(); + if (!empty($modSettings['enableCompressedOutput'])) + @ob_start('ob_gzhandler'); + else + ob_start(); + + if (isset($_GET['debug']) && !WIRELESS) + header('Content-Type: application/xhtml+xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set'])); + + // Don't cache error pages!! + header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + header('Cache-Control: no-cache'); + + if (!isset($txt['template_parse_error'])) + { + $txt['template_parse_error'] = 'Template Parse Error!'; + $txt['template_parse_error_message'] = 'It seems something has gone sour on the forum with the template system. This problem should only be temporary, so please come back later and try again. If you continue to see this message, please contact the administrator.

You can also try refreshing this page.'; + $txt['template_parse_error_details'] = 'There was a problem loading the %1$s template or language file. Please check the syntax and try again - remember, single quotes (\') often have to be escaped with a slash (\\). To see more specific error information from PHP, try accessing the file directly.

You may want to try to refresh this page or use the default theme.'; + } + + // First, let's get the doctype and language information out of the way. + echo ' + + '; + if (isset($context['character_set'])) + echo ' + '; + + if (!empty($maintenance) && !allowedTo('admin_forum')) + echo ' + ', $mtitle, ' + + +

', $mtitle, '

+ ', $mmessage, ' + +'; + elseif (!allowedTo('admin_forum')) + echo ' + ', $txt['template_parse_error'], ' + + +

', $txt['template_parse_error'], '

+ ', $txt['template_parse_error_message'], ' + +'; + else + { + require_once($sourcedir . '/Subs-Package.php'); + + $error = fetch_web_data($boardurl . strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => ''))); + if (empty($error)) + $error = $php_errormsg; + + $error = strtr($error, array('' => '', '' => '')); + + echo ' + ', $txt['template_parse_error'], ' + + +

', $txt['template_parse_error'], '

+ ', sprintf($txt['template_parse_error_details'], strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => ''))); + + if (!empty($error)) + echo ' +
+ +
', strtr(strtr($error, array('' . $boarddir => '...', '' . strtr($boarddir, '\\', '/') => '...')), '\\', '/'), '
'; + + // I know, I know... this is VERY COMPLICATED. Still, it's good. + if (preg_match('~ (\d+)$~i', $error, $match) != 0) + { + $data = file($filename); + $data2 = highlight_php_code(implode('', $data)); + $data2 = preg_split('~\~', $data2); + + // Fix the PHP code stuff... + if ($context['browser']['is_ie4'] || $context['browser']['is_ie5'] || $context['browser']['is_ie5.5']) + $data2 = str_replace("\t", '
' . "\t" . '
', $data2); + elseif (!$context['browser']['is_gecko']) + $data2 = str_replace("\t", '' . "\t" . '', $data2); + else + $data2 = str_replace('
' . "\t" . '
', "\t", $data2); + + // Now we get to work around a bug in PHP where it doesn't escape
s! + $j = -1; + foreach ($data as $line) + { + $j++; + + if (substr_count($line, '
') == 0) + continue; + + $n = substr_count($line, '
'); + for ($i = 0; $i < $n; $i++) + { + $data2[$j] .= '<br />' . $data2[$j + $i + 1]; + unset($data2[$j + $i + 1]); + } + $j += $n; + } + $data2 = array_values($data2); + array_unshift($data2, ''); + + echo ' +
';
+
+				// Figure out what the color coding was before...
+				$line = max($match[1] - 9, 1);
+				$last_line = '';
+				for ($line2 = $line - 1; $line2 > 1; $line2--)
+					if (strpos($data2[$line2], '<') !== false)
+					{
+						if (preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line2], $color_match) != 0)
+							$last_line = $color_match[1];
+						break;
+					}
+
+				// Show the relevant lines...
+				for ($n = min($match[1] + 4, count($data2) + 1); $line <= $n; $line++)
+				{
+					if ($line == $match[1])
+						echo '
';
+
+					echo '', sprintf('%' . strlen($n) . 's', $line), ': ';
+					if (isset($data2[$line]) && $data2[$line] != '')
+						echo substr($data2[$line], 0, 2) == ']+>~', '', $data2[$line]) : $last_line . $data2[$line];
+
+					if (isset($data2[$line]) && preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line], $color_match) != 0)
+					{
+						$last_line = $color_match[1];
+						echo '';
+					}
+					elseif ($last_line != '' && strpos($data2[$line], '<') !== false)
+						$last_line = '';
+					elseif ($last_line != '' && $data2[$line] != '')
+						echo '';
+
+					if ($line == $match[1])
+						echo '
';
+					else
+						echo "\n";
+				}
+
+				echo '
'; + } + + echo ' + +'; + } + + die; + } +} + +// Attempt to start the session, unless it already has been. +function loadSession() +{ + global $HTTP_SESSION_VARS, $modSettings, $boardurl, $sc; + + // Attempt to change a few PHP settings. + @ini_set('session.use_cookies', true); + @ini_set('session.use_only_cookies', false); + @ini_set('url_rewriter.tags', ''); + @ini_set('session.use_trans_sid', false); + @ini_set('arg_separator.output', '&'); + + if (!empty($modSettings['globalCookies'])) + { + $parsed_url = parse_url($boardurl); + + if (preg_match('~^\d{1,3}(\.\d{1,3}){3}$~', $parsed_url['host']) == 0 && preg_match('~(?:[^\.]+\.)?([^\.]{2,}\..+)\z~i', $parsed_url['host'], $parts) == 1) + @ini_set('session.cookie_domain', '.' . $parts[1]); + } + // !!! Set the session cookie path? + + // If it's already been started... probably best to skip this. + if ((@ini_get('session.auto_start') == 1 && !empty($modSettings['databaseSession_enable'])) || session_id() == '') + { + // Attempt to end the already-started session. + if (@ini_get('session.auto_start') == 1) + @session_write_close(); + + // This is here to stop people from using bad junky PHPSESSIDs. + if (isset($_REQUEST[session_name()]) && preg_match('~^[A-Za-z0-9,-]{16,32}$~', $_REQUEST[session_name()]) == 0 && !isset($_COOKIE[session_name()])) + { + $session_id = md5(md5('smf_sess_' . time()) . mt_rand()); + $_REQUEST[session_name()] = $session_id; + $_GET[session_name()] = $session_id; + $_POST[session_name()] = $session_id; + } + + // Use database sessions? (they don't work in 4.1.x!) + if (!empty($modSettings['databaseSession_enable']) && @version_compare(PHP_VERSION, '4.2.0') != -1) + { + session_set_save_handler('sessionOpen', 'sessionClose', 'sessionRead', 'sessionWrite', 'sessionDestroy', 'sessionGC'); + @ini_set('session.gc_probability', '1'); + } + elseif (@ini_get('session.gc_maxlifetime') <= 1440 && !empty($modSettings['databaseSession_lifetime'])) + @ini_set('session.gc_maxlifetime', max($modSettings['databaseSession_lifetime'], 60)); + + // Use cache setting sessions? + if (empty($modSettings['databaseSession_enable']) && !empty($modSettings['cache_enable']) && php_sapi_name() != 'cli') + { + if (function_exists('mmcache_set_session_handlers')) + mmcache_set_session_handlers(); + elseif (function_exists('eaccelerator_set_session_handlers')) + eaccelerator_set_session_handlers(); + } + + session_start(); + + // Change it so the cache settings are a little looser than default. + if (!empty($modSettings['databaseSession_loose'])) + header('Cache-Control: private'); + } + + // While PHP 4.1.x should use $_SESSION, it seems to need this to do it right. + if (@version_compare(PHP_VERSION, '4.2.0') == -1) + $HTTP_SESSION_VARS['php_412_bugfix'] = true; + + // Set the randomly generated code. + if (!isset($_SESSION['session_var'])) + { + $_SESSION['session_value'] = md5(session_id() . mt_rand()); + $_SESSION['session_var'] = substr(preg_replace('~^\d+~', '', sha1(mt_rand() . session_id() . mt_rand())), 0, rand(7, 12)); + } + $sc = $_SESSION['session_value']; +} + +function sessionOpen($save_path, $session_name) +{ + return true; +} + +function sessionClose() +{ + return true; +} + +function sessionRead($session_id) +{ + global $smcFunc; + + if (preg_match('~^[A-Za-z0-9,-]{16,32}$~', $session_id) == 0) + return false; + + // Look for it in the database. + $result = $smcFunc['db_query']('', ' + SELECT data + FROM {db_prefix}sessions + WHERE session_id = {string:session_id} + LIMIT 1', + array( + 'session_id' => $session_id, + ) + ); + list ($sess_data) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + return $sess_data; +} + +function sessionWrite($session_id, $data) +{ + global $smcFunc; + + if (preg_match('~^[A-Za-z0-9,-]{16,32}$~', $session_id) == 0) + return false; + + // First try to update an existing row... + $result = $smcFunc['db_query']('', ' + UPDATE {db_prefix}sessions + SET data = {string:data}, last_update = {int:last_update} + WHERE session_id = {string:session_id}', + array( + 'last_update' => time(), + 'data' => $data, + 'session_id' => $session_id, + ) + ); + + // If that didn't work, try inserting a new one. + if ($smcFunc['db_affected_rows']() == 0) + $result = $smcFunc['db_insert']('ignore', + '{db_prefix}sessions', + array('session_id' => 'string', 'data' => 'string', 'last_update' => 'int'), + array($session_id, $data, time()), + array('session_id') + ); + + return $result; +} + +function sessionDestroy($session_id) +{ + global $smcFunc; + + if (preg_match('~^[A-Za-z0-9,-]{16,32}$~', $session_id) == 0) + return false; + + // Just delete the row... + return $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}sessions + WHERE session_id = {string:session_id}', + array( + 'session_id' => $session_id, + ) + ); +} + +function sessionGC($max_lifetime) +{ + global $modSettings, $smcFunc; + + // Just set to the default or lower? Ignore it for a higher value. (hopefully) + if (!empty($modSettings['databaseSession_lifetime']) && ($max_lifetime <= 1440 || $modSettings['databaseSession_lifetime'] > $max_lifetime)) + $max_lifetime = max($modSettings['databaseSession_lifetime'], 60); + + // Clean up ;). + return $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}sessions + WHERE last_update < {int:last_update}', + array( + 'last_update' => time() - $max_lifetime, + ) + ); +} + +// Load up a database connection. +function loadDatabase() +{ + global $db_persist, $db_connection, $db_server, $db_user, $db_passwd; + global $db_type, $db_name, $ssi_db_user, $ssi_db_passwd, $sourcedir, $db_prefix; + + // Figure out what type of database we are using. + if (empty($db_type) || !file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php')) + $db_type = 'mysql'; + + // Load the file for the database. + require_once($sourcedir . '/Subs-Db-' . $db_type . '.php'); + + // If we are in SSI try them first, but don't worry if it doesn't work, we have the normal username and password we can use. + if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd)) + $db_connection = smf_db_initiate($db_server, $db_name, $ssi_db_user, $ssi_db_passwd, $db_prefix, array('persist' => $db_persist, 'non_fatal' => true, 'dont_select_db' => true)); + + // Either we aren't in SSI mode, or it failed. + if (empty($db_connection)) + $db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, array('persist' => $db_persist, 'dont_select_db' => SMF == 'SSI')); + + // Safe guard here, if there isn't a valid connection lets put a stop to it. + if (!$db_connection) + db_fatal_error(); + + // If in SSI mode fix up the prefix. + if (SMF == 'SSI') + db_fix_prefix($db_prefix, $db_name); +} + +// Try to retrieve a cache entry. On failure, call the appropriate function. +function cache_quick_get($key, $file, $function, $params, $level = 1) +{ + global $modSettings, $sourcedir; + + // Refresh the cache if either: + // 1. Caching is disabled. + // 2. The cache level isn't high enough. + // 3. The item has not been cached or the cached item expired. + // 4. The cached item has a custom expiration condition evaluating to true. + // 5. The expire time set in the cache item has passed (needed for Zend). + if (empty($modSettings['cache_enable']) || $modSettings['cache_enable'] < $level || !is_array($cache_block = cache_get_data($key, 3600)) || (!empty($cache_block['refresh_eval']) && eval($cache_block['refresh_eval'])) || (!empty($cache_block['expires']) && $cache_block['expires'] < time())) + { + require_once($sourcedir . '/' . $file); + $cache_block = call_user_func_array($function, $params); + + if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= $level) + cache_put_data($key, $cache_block, $cache_block['expires'] - time()); + } + + // Some cached data may need a freshening up after retrieval. + if (!empty($cache_block['post_retri_eval'])) + eval($cache_block['post_retri_eval']); + + return $cache_block['data']; +} + +function cache_put_data($key, $value, $ttl = 120) +{ + global $boardurl, $sourcedir, $modSettings, $memcached; + global $cache_hits, $cache_count, $db_show_debug, $cachedir; + + if (empty($modSettings['cache_enable']) && !empty($modSettings)) + return; + + $cache_count = isset($cache_count) ? $cache_count + 1 : 1; + if (isset($db_show_debug) && $db_show_debug === true) + { + $cache_hits[$cache_count] = array('k' => $key, 'd' => 'put', 's' => $value === null ? 0 : strlen(serialize($value))); + $st = microtime(); + } + + $key = md5($boardurl . filemtime($sourcedir . '/Load.php')) . '-SMF-' . strtr($key, ':', '-'); + $value = $value === null ? null : serialize($value); + + // The simple yet efficient memcached. + if (function_exists('memcache_set') && isset($modSettings['cache_memcached']) && trim($modSettings['cache_memcached']) != '') + { + // Not connected yet? + if (empty($memcached)) + get_memcached_server(); + if (!$memcached) + return; + + memcache_set($memcached, $key, $value, 0, $ttl); + } + // eAccelerator... + elseif (function_exists('eaccelerator_put')) + { + if (mt_rand(0, 10) == 1) + eaccelerator_gc(); + + if ($value === null) + @eaccelerator_rm($key); + else + eaccelerator_put($key, $value, $ttl); + } + // Turck MMCache? + elseif (function_exists('mmcache_put')) + { + if (mt_rand(0, 10) == 1) + mmcache_gc(); + + if ($value === null) + @mmcache_rm($key); + else + mmcache_put($key, $value, $ttl); + } + // Alternative PHP Cache, ahoy! + elseif (function_exists('apc_store')) + { + // An extended key is needed to counteract a bug in APC. + if ($value === null) + apc_delete($key . 'smf'); + else + apc_store($key . 'smf', $value, $ttl); + } + // Zend Platform/ZPS/etc. + elseif (function_exists('output_cache_put')) + output_cache_put($key, $value); + elseif (function_exists('xcache_set') && ini_get('xcache.var_size') > 0) + { + if ($value === null) + xcache_unset($key); + else + xcache_set($key, $value, $ttl); + } + // Otherwise custom cache? + else + { + if ($value === null) + @unlink($cachedir . '/data_' . $key . '.php'); + else + { + $cache_data = '<' . '?' . 'php if (!defined(\'SMF\')) die; if (' . (time() + $ttl) . ' < time()) $expired = true; else{$expired = false; $value = \'' . addcslashes($value, '\\\'') . '\';}' . '?' . '>'; + // Write the file. + if (function_exists('file_put_contents')) + { + $cache_bytes = @file_put_contents($cachedir . '/data_' . $key . '.php', $cache_data, LOCK_EX); + if ($cache_bytes != strlen($cache_data)) + @unlink($cachedir . '/data_' . $key . '.php'); + } + else + { + $fh = @fopen($cachedir . '/data_' . $key . '.php', 'w'); + if ($fh) + { + // Write the file. + set_file_buffer($fh, 0); + flock($fh, LOCK_EX); + $cache_bytes = fwrite($fh, $cache_data); + flock($fh, LOCK_UN); + fclose($fh); + + // Check that the cache write was successful; all the data should be written + // If it fails due to low diskspace, remove the cache file + if ($cache_bytes != strlen($cache_data)) + @unlink($cachedir . '/data_' . $key . '.php'); + } + } + } + } + + if (isset($db_show_debug) && $db_show_debug === true) + $cache_hits[$cache_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st)); +} + +function cache_get_data($key, $ttl = 120) +{ + global $boardurl, $sourcedir, $modSettings, $memcached; + global $cache_hits, $cache_count, $db_show_debug, $cachedir; + + if (empty($modSettings['cache_enable']) && !empty($modSettings)) + return; + + $cache_count = isset($cache_count) ? $cache_count + 1 : 1; + if (isset($db_show_debug) && $db_show_debug === true) + { + $cache_hits[$cache_count] = array('k' => $key, 'd' => 'get'); + $st = microtime(); + } + + $key = md5($boardurl . filemtime($sourcedir . '/Load.php')) . '-SMF-' . strtr($key, ':', '-'); + + // Okay, let's go for it memcached! + if (function_exists('memcache_get') && isset($modSettings['cache_memcached']) && trim($modSettings['cache_memcached']) != '') + { + // Not connected yet? + if (empty($memcached)) + get_memcached_server(); + if (!$memcached) + return; + + $value = memcache_get($memcached, $key); + } + // Again, eAccelerator. + elseif (function_exists('eaccelerator_get')) + $value = eaccelerator_get($key); + // The older, but ever-stable, Turck MMCache... + elseif (function_exists('mmcache_get')) + $value = mmcache_get($key); + // This is the free APC from PECL. + elseif (function_exists('apc_fetch')) + $value = apc_fetch($key . 'smf'); + // Zend's pricey stuff. + elseif (function_exists('output_cache_get')) + $value = output_cache_get($key, $ttl); + elseif (function_exists('xcache_get') && ini_get('xcache.var_size') > 0) + $value = xcache_get($key); + // Otherwise it's SMF data! + elseif (file_exists($cachedir . '/data_' . $key . '.php') && filesize($cachedir . '/data_' . $key . '.php') > 10) + { + @include($cachedir . '/data_' . $key . '.php'); + if (!empty($expired) && isset($value)) + { + @unlink($cachedir . '/data_' . $key . '.php'); + unset($value); + } + } + + if (isset($db_show_debug) && $db_show_debug === true) + { + $cache_hits[$cache_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st)); + $cache_hits[$cache_count]['s'] = isset($value) ? strlen($value) : 0; + } + + if (empty($value)) + return null; + // If it's broke, it's broke... so give up on it. + else + return @unserialize($value); +} + +function get_memcached_server($level = 3) +{ + global $modSettings, $memcached, $db_persist; + + $servers = explode(',', $modSettings['cache_memcached']); + $server = explode(':', trim($servers[array_rand($servers)])); + + // Don't try more times than we have servers! + $level = min(count($servers), $level); + + // Don't wait too long: yes, we want the server, but we might be able to run the query faster! + if (empty($db_persist)) + $memcached = memcache_connect($server[0], empty($server[1]) ? 11211 : $server[1]); + else + $memcached = memcache_pconnect($server[0], empty($server[1]) ? 11211 : $server[1]); + + if (!$memcached && $level > 0) + get_memcached_server($level - 1); +} + +?> \ No newline at end of file diff --git a/Sources/LockTopic.php b/Sources/LockTopic.php new file mode 100644 index 0000000..5a60066 --- /dev/null +++ b/Sources/LockTopic.php @@ -0,0 +1,158 @@ + $topic, + ) + ); + list ($starter, $locked) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Can you lock topics here, mister? + $user_lock = !allowedTo('lock_any'); + if ($user_lock && $starter == $user_info['id']) + isAllowedTo('lock_own'); + else + isAllowedTo('lock_any'); + + // Locking with high privileges. + if ($locked == '0' && !$user_lock) + $locked = '1'; + // Locking with low privileges. + elseif ($locked == '0') + $locked = '2'; + // Unlocking - make sure you don't unlock what you can't. + elseif ($locked == '2' || ($locked == '1' && !$user_lock)) + $locked = '0'; + // You cannot unlock this! + else + fatal_lang_error('locked_by_admin', 'user'); + + // Actually lock the topic in the database with the new value. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET locked = {int:locked} + WHERE id_topic = {int:current_topic}', + array( + 'current_topic' => $topic, + 'locked' => $locked, + ) + ); + + // If they are allowed a "moderator" permission, log it in the moderator log. + if (!$user_lock) + logAction($locked ? 'lock' : 'unlock', array('topic' => $topic, 'board' => $board)); + // Notify people that this topic has been locked? + sendNotifications($topic, empty($locked) ? 'unlock' : 'lock'); + + // Back to the topic! + redirectexit('topic=' . $topic . '.' . $_REQUEST['start'] . (WIRELESS ? ';moderate' : '')); +} + +// Sticky a topic. Can't be done by topic starters - that would be annoying! +function Sticky() +{ + global $modSettings, $topic, $board, $sourcedir, $smcFunc; + + // Make sure the user can sticky it, and they are stickying *something*. + isAllowedTo('make_sticky'); + + // You shouldn't be able to (un)sticky a topic if the setting is disabled. + if (empty($modSettings['enableStickyTopics'])) + fatal_lang_error('cannot_make_sticky', false); + + // You can't sticky a board or something! + if (empty($topic)) + fatal_lang_error('not_a_topic', false); + + checkSession('get'); + + // We need Subs-Post.php for the sendNotifications() function. + require_once($sourcedir . '/Subs-Post.php'); + + // Is this topic already stickied, or no? + $request = $smcFunc['db_query']('', ' + SELECT is_sticky + FROM {db_prefix}topics + WHERE id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + ) + ); + list ($is_sticky) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Toggle the sticky value.... pretty simple ;). + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET is_sticky = {int:is_sticky} + WHERE id_topic = {int:current_topic}', + array( + 'current_topic' => $topic, + 'is_sticky' => empty($is_sticky) ? 1 : 0, + ) + ); + + // Log this sticky action - always a moderator thing. + logAction(empty($is_sticky) ? 'sticky' : 'unsticky', array('topic' => $topic, 'board' => $board)); + // Notify people that this topic has been stickied? + if (empty($is_sticky)) + sendNotifications($topic, 'sticky'); + + // Take them back to the now stickied topic. + redirectexit('topic=' . $topic . '.' . $_REQUEST['start'] . (WIRELESS ? ';moderate' : '')); +} + +?> \ No newline at end of file diff --git a/Sources/LogInOut.php b/Sources/LogInOut.php new file mode 100644 index 0000000..de642ee --- /dev/null +++ b/Sources/LogInOut.php @@ -0,0 +1,733 @@ + $scripturl . '?action=login', + 'name' => $txt['login'], + ); + + // Set the login URL - will be used when the login process is done (but careful not to send us to an attachment). + if (isset($_SESSION['old_url']) && strpos($_SESSION['old_url'], 'dlattach') === false && preg_match('~(board|topic)[=,]~', $_SESSION['old_url']) != 0) + $_SESSION['login_url'] = $_SESSION['old_url']; + else + unset($_SESSION['login_url']); +} + +// Perform the actual logging-in. +function Login2() +{ + global $txt, $scripturl, $user_info, $user_settings, $smcFunc; + global $cookiename, $maintenance, $modSettings, $context, $sc, $sourcedir; + + // Load cookie authentication stuff. + require_once($sourcedir . '/Subs-Auth.php'); + + if (isset($_GET['sa']) && $_GET['sa'] == 'salt' && !$user_info['is_guest']) + { + if (isset($_COOKIE[$cookiename]) && preg_match('~^a:[34]:\{i:0;(i:\d{1,6}|s:[1-8]:"\d{1,8}");i:1;s:(0|40):"([a-fA-F0-9]{40})?";i:2;[id]:\d{1,14};(i:3;i:\d;)?\}$~', $_COOKIE[$cookiename]) === 1) + list (, , $timeout) = @unserialize($_COOKIE[$cookiename]); + elseif (isset($_SESSION['login_' . $cookiename])) + list (, , $timeout) = @unserialize($_SESSION['login_' . $cookiename]); + else + trigger_error('Login2(): Cannot be logged in without a session or cookie', E_USER_ERROR); + + $user_settings['password_salt'] = substr(md5(mt_rand()), 0, 4); + updateMemberData($user_info['id'], array('password_salt' => $user_settings['password_salt'])); + + setLoginCookie($timeout - time(), $user_info['id'], sha1($user_settings['passwd'] . $user_settings['password_salt'])); + + redirectexit('action=login2;sa=check;member=' . $user_info['id'], $context['server']['needs_login_fix']); + } + // Double check the cookie... + elseif (isset($_GET['sa']) && $_GET['sa'] == 'check') + { + // Strike! You're outta there! + if ($_GET['member'] != $user_info['id']) + fatal_lang_error('login_cookie_error', false); + + // Some whitelisting for login_url... + if (empty($_SESSION['login_url'])) + redirectexit(); + else + { + // Best not to clutter the session data too much... + $temp = $_SESSION['login_url']; + unset($_SESSION['login_url']); + + redirectexit($temp); + } + } + + // Beyond this point you are assumed to be a guest trying to login. + if (!$user_info['is_guest']) + redirectexit(); + + // Are you guessing with a script? + spamProtection('login'); + + // Set the login_url if it's not already set (but careful not to send us to an attachment). + if (empty($_SESSION['login_url']) && isset($_SESSION['old_url']) && strpos($_SESSION['old_url'], 'dlattach') === false && preg_match('~(board|topic)[=,]~', $_SESSION['old_url']) != 0) + $_SESSION['login_url'] = $_SESSION['old_url']; + + // Been guessing a lot, haven't we? + if (isset($_SESSION['failed_login']) && $_SESSION['failed_login'] >= $modSettings['failed_login_threshold'] * 3) + fatal_lang_error('login_threshold_fail', 'critical'); + + // Set up the cookie length. (if it's invalid, just fall through and use the default.) + if (isset($_POST['cookieneverexp']) || (!empty($_POST['cookielength']) && $_POST['cookielength'] == -1)) + $modSettings['cookieTime'] = 3153600; + elseif (!empty($_POST['cookielength']) && ($_POST['cookielength'] >= 1 || $_POST['cookielength'] <= 525600)) + $modSettings['cookieTime'] = (int) $_POST['cookielength']; + + loadLanguage('Login'); + // Load the template stuff - wireless or normal. + if (WIRELESS) + $context['sub_template'] = WIRELESS_PROTOCOL . '_login'; + else + { + loadTemplate('Login'); + $context['sub_template'] = 'login'; + } + + // Set up the default/fallback stuff. + $context['default_username'] = isset($_POST['user']) ? preg_replace('~&#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', htmlspecialchars($_POST['user'])) : ''; + $context['default_password'] = ''; + $context['never_expire'] = $modSettings['cookieTime'] == 525600 || $modSettings['cookieTime'] == 3153600; + $context['login_errors'] = array($txt['error_occured']); + $context['page_title'] = $txt['login']; + + // Add the login chain to the link tree. + $context['linktree'][] = array( + 'url' => $scripturl . '?action=login', + 'name' => $txt['login'], + ); + + if (!empty($_POST['openid_identifier']) && !empty($modSettings['enableOpenID'])) + { + require_once($sourcedir . '/Subs-OpenID.php'); + if (($open_id = smf_openID_validate($_POST['openid_identifier'])) !== 'no_data') + return $open_id; + } + + // You forgot to type your username, dummy! + if (!isset($_POST['user']) || $_POST['user'] == '') + { + $context['login_errors'] = array($txt['need_username']); + return; + } + + // Hmm... maybe 'admin' will login with no password. Uhh... NO! + if ((!isset($_POST['passwrd']) || $_POST['passwrd'] == '') && (!isset($_POST['hash_passwrd']) || strlen($_POST['hash_passwrd']) != 40)) + { + $context['login_errors'] = array($txt['no_password']); + return; + } + + // No funky symbols either. + if (preg_match('~[<>&"\'=\\\]~', preg_replace('~(&#(\\d{1,7}|x[0-9a-fA-F]{1,6});)~', '', $_POST['user'])) != 0) + { + $context['login_errors'] = array($txt['error_invalid_characters_username']); + return; + } + + // And if it's too long, trim it back. + if ($smcFunc['strlen']($_POST['user']) > 80) + { + $_POST['user'] = $smcFunc['substr']($_POST['user'], 0, 79); + $context['default_username'] = preg_replace('~&#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($_POST['user'])); + } + + // Are we using any sort of integration to validate the login? + if (in_array('retry', call_integration_hook('integrate_validate_login', array($_POST['user'], isset($_POST['hash_passwrd']) && strlen($_POST['hash_passwrd']) == 40 ? $_POST['hash_passwrd'] : null, $modSettings['cookieTime'])), true)) + { + $context['login_errors'] = array($txt['login_hash_error']); + $context['disable_login_hashing'] = true; + return; + } + + // Load the data up! + $request = $smcFunc['db_query']('', ' + SELECT passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt, + openid_uri, passwd_flood + FROM {db_prefix}members + WHERE ' . ($smcFunc['db_case_sensitive'] ? 'LOWER(member_name) = LOWER({string:user_name})' : 'member_name = {string:user_name}') . ' + LIMIT 1', + array( + 'user_name' => $smcFunc['db_case_sensitive'] ? strtolower($_POST['user']) : $_POST['user'], + ) + ); + // Probably mistyped or their email, try it as an email address. (member_name first, though!) + if ($smcFunc['db_num_rows']($request) == 0) + { + $smcFunc['db_free_result']($request); + + $request = $smcFunc['db_query']('', ' + SELECT passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt, openid_uri, + passwd_flood + FROM {db_prefix}members + WHERE email_address = {string:user_name} + LIMIT 1', + array( + 'user_name' => $_POST['user'], + ) + ); + // Let them try again, it didn't match anything... + if ($smcFunc['db_num_rows']($request) == 0) + { + $context['login_errors'] = array($txt['username_no_exist']); + return; + } + } + + $user_settings = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Figure out the password using SMF's encryption - if what they typed is right. + if (isset($_POST['hash_passwrd']) && strlen($_POST['hash_passwrd']) == 40) + { + // Needs upgrading? + if (strlen($user_settings['passwd']) != 40) + { + $context['login_errors'] = array($txt['login_hash_error']); + $context['disable_login_hashing'] = true; + unset($user_settings); + return; + } + // Challenge passed. + elseif ($_POST['hash_passwrd'] == sha1($user_settings['passwd'] . $sc)) + $sha_passwd = $user_settings['passwd']; + else + { + // Don't allow this! + validatePasswordFlood($user_settings['id_member'], $user_settings['passwd_flood']); + + $_SESSION['failed_login'] = @$_SESSION['failed_login'] + 1; + + if ($_SESSION['failed_login'] >= $modSettings['failed_login_threshold']) + redirectexit('action=reminder'); + else + { + log_error($txt['incorrect_password'] . ' - ' . $user_settings['member_name'] . '', 'user'); + + $context['disable_login_hashing'] = true; + $context['login_errors'] = array($txt['incorrect_password']); + unset($user_settings); + return; + } + } + } + else + $sha_passwd = sha1(strtolower($user_settings['member_name']) . un_htmlspecialchars($_POST['passwrd'])); + + // Bad password! Thought you could fool the database?! + if ($user_settings['passwd'] != $sha_passwd) + { + // Let's be cautious, no hacking please. thanx. + validatePasswordFlood($user_settings['id_member'], $user_settings['passwd_flood']); + + // Maybe we were too hasty... let's try some other authentication methods. + $other_passwords = array(); + + // None of the below cases will be used most of the time (because the salt is normally set.) + if ($user_settings['password_salt'] == '') + { + // YaBB SE, Discus, MD5 (used a lot), SHA-1 (used some), SMF 1.0.x, IkonBoard, and none at all. + $other_passwords[] = crypt($_POST['passwrd'], substr($_POST['passwrd'], 0, 2)); + $other_passwords[] = crypt($_POST['passwrd'], substr($user_settings['passwd'], 0, 2)); + $other_passwords[] = md5($_POST['passwrd']); + $other_passwords[] = sha1($_POST['passwrd']); + $other_passwords[] = md5_hmac($_POST['passwrd'], strtolower($user_settings['member_name'])); + $other_passwords[] = md5($_POST['passwrd'] . strtolower($user_settings['member_name'])); + $other_passwords[] = md5(md5($_POST['passwrd'])); + $other_passwords[] = $_POST['passwrd']; + + // This one is a strange one... MyPHP, crypt() on the MD5 hash. + $other_passwords[] = crypt(md5($_POST['passwrd']), md5($_POST['passwrd'])); + + // Snitz style - SHA-256. Technically, this is a downgrade, but most PHP configurations don't support sha256 anyway. + if (strlen($user_settings['passwd']) == 64 && function_exists('mhash') && defined('MHASH_SHA256')) + $other_passwords[] = bin2hex(mhash(MHASH_SHA256, $_POST['passwrd'])); + + // phpBB3 users new hashing. We now support it as well ;). + $other_passwords[] = phpBB3_password_check($_POST['passwrd'], $user_settings['passwd']); + + // APBoard 2 Login Method. + $other_passwords[] = md5(crypt($_POST['passwrd'], 'CRYPT_MD5')); + } + // The hash should be 40 if it's SHA-1, so we're safe with more here too. + elseif (strlen($user_settings['passwd']) == 32) + { + // vBulletin 3 style hashing? Let's welcome them with open arms \o/. + $other_passwords[] = md5(md5($_POST['passwrd']) . $user_settings['password_salt']); + + // Hmm.. p'raps it's Invision 2 style? + $other_passwords[] = md5(md5($user_settings['password_salt']) . md5($_POST['passwrd'])); + + // Some common md5 ones. + $other_passwords[] = md5($user_settings['password_salt'] . $_POST['passwrd']); + $other_passwords[] = md5($_POST['passwrd'] . $user_settings['password_salt']); + } + elseif (strlen($user_settings['passwd']) == 40) + { + // Maybe they are using a hash from before the password fix. + $other_passwords[] = sha1(strtolower($user_settings['member_name']) . un_htmlspecialchars($_POST['passwrd'])); + + // BurningBoard3 style of hashing. + $other_passwords[] = sha1($user_settings['password_salt'] . sha1($user_settings['password_salt'] . sha1($_POST['passwrd']))); + + // Perhaps we converted to UTF-8 and have a valid password being hashed differently. + if ($context['character_set'] == 'utf8' && !empty($modSettings['previousCharacterSet']) && $modSettings['previousCharacterSet'] != 'utf8') + { + // Try iconv first, for no particular reason. + if (function_exists('iconv')) + $other_passwords['iconv'] = sha1(strtolower(iconv('UTF-8', $modSettings['previousCharacterSet'], $user_settings['member_name'])) . un_htmlspecialchars(iconv('UTF-8', $modSettings['previousCharacterSet'], $_POST['passwrd']))); + + // Say it aint so, iconv failed! + if (empty($other_passwords['iconv']) && function_exists('mb_convert_encoding')) + $other_passwords[] = sha1(strtolower(mb_convert_encoding($user_settings['member_name'], 'UTF-8', $modSettings['previousCharacterSet'])) . un_htmlspecialchars(mb_convert_encoding($_POST['passwrd'], 'UTF-8', $modSettings['previousCharacterSet']))); + } + } + + // SMF's sha1 function can give a funny result on Linux (Not our fault!). If we've now got the real one let the old one be valid! + if (strpos(strtolower(PHP_OS), 'win') !== 0) + { + require_once($sourcedir . '/Subs-Compat.php'); + $other_passwords[] = sha1_smf(strtolower($user_settings['member_name']) . un_htmlspecialchars($_POST['passwrd'])); + } + + // Whichever encryption it was using, let's make it use SMF's now ;). + if (in_array($user_settings['passwd'], $other_passwords)) + { + $user_settings['passwd'] = $sha_passwd; + $user_settings['password_salt'] = substr(md5(mt_rand()), 0, 4); + + // Update the password and set up the hash. + updateMemberData($user_settings['id_member'], array('passwd' => $user_settings['passwd'], 'password_salt' => $user_settings['password_salt'], 'passwd_flood' => '')); + } + // Okay, they for sure didn't enter the password! + else + { + // They've messed up again - keep a count to see if they need a hand. + $_SESSION['failed_login'] = @$_SESSION['failed_login'] + 1; + + // Hmm... don't remember it, do you? Here, try the password reminder ;). + if ($_SESSION['failed_login'] >= $modSettings['failed_login_threshold']) + redirectexit('action=reminder'); + // We'll give you another chance... + else + { + // Log an error so we know that it didn't go well in the error log. + log_error($txt['incorrect_password'] . ' - ' . $user_settings['member_name'] . '', 'user'); + + $context['login_errors'] = array($txt['incorrect_password']); + return; + } + } + } + elseif (!empty($user_settings['passwd_flood'])) + { + // Let's be sure they weren't a little hacker. + validatePasswordFlood($user_settings['id_member'], $user_settings['passwd_flood'], true); + + // If we got here then we can reset the flood counter. + updateMemberData($user_settings['id_member'], array('passwd_flood' => '')); + } + + // Correct password, but they've got no salt; fix it! + if ($user_settings['password_salt'] == '') + { + $user_settings['password_salt'] = substr(md5(mt_rand()), 0, 4); + updateMemberData($user_settings['id_member'], array('password_salt' => $user_settings['password_salt'])); + } + + // Check their activation status. + if (!checkActivation()) + return; + + DoLogin(); +} + +function checkActivation() +{ + global $context, $txt, $scripturl, $user_settings, $modSettings; + + if (!isset($context['login_errors'])) + $context['login_errors'] = array(); + + // What is the true activation status of this account? + $activation_status = $user_settings['is_activated'] > 10 ? $user_settings['is_activated'] - 10 : $user_settings['is_activated']; + + // Check if the account is activated - COPPA first... + if ($activation_status == 5) + { + $context['login_errors'][] = $txt['coppa_no_concent'] . ' ' . $txt['coppa_need_more_details'] . ''; + return false; + } + // Awaiting approval still? + elseif ($activation_status == 3) + fatal_lang_error('still_awaiting_approval', 'user'); + // Awaiting deletion, changed their mind? + elseif ($activation_status == 4) + { + if (isset($_REQUEST['undelete'])) + { + updateMemberData($user_settings['id_member'], array('is_activated' => 1)); + updateSettings(array('unapprovedMembers' => ($modSettings['unapprovedMembers'] > 0 ? $modSettings['unapprovedMembers'] - 1 : 0))); + } + else + { + $context['disable_login_hashing'] = true; + $context['login_errors'][] = $txt['awaiting_delete_account']; + $context['login_show_undelete'] = true; + return false; + } + } + // Standard activation? + elseif ($activation_status != 1) + { + log_error($txt['activate_not_completed1'] . ' - ' . $user_settings['member_name'] . '', false); + + $context['login_errors'][] = $txt['activate_not_completed1'] . ' ' . $txt['activate_not_completed2'] . ''; + return false; + } + return true; +} + +function DoLogin() +{ + global $txt, $scripturl, $user_info, $user_settings, $smcFunc; + global $cookiename, $maintenance, $modSettings, $context, $sourcedir; + + // Load cookie authentication stuff. + require_once($sourcedir . '/Subs-Auth.php'); + + // Call login integration functions. + call_integration_hook('integrate_login', array($user_settings['member_name'], isset($_POST['hash_passwrd']) && strlen($_POST['hash_passwrd']) == 40 ? $_POST['hash_passwrd'] : null, $modSettings['cookieTime'])); + + // Get ready to set the cookie... + $username = $user_settings['member_name']; + $user_info['id'] = $user_settings['id_member']; + + // Bam! Cookie set. A session too, just in case. + setLoginCookie(60 * $modSettings['cookieTime'], $user_settings['id_member'], sha1($user_settings['passwd'] . $user_settings['password_salt'])); + + // Reset the login threshold. + if (isset($_SESSION['failed_login'])) + unset($_SESSION['failed_login']); + + $user_info['is_guest'] = false; + $user_settings['additional_groups'] = explode(',', $user_settings['additional_groups']); + $user_info['is_admin'] = $user_settings['id_group'] == 1 || in_array(1, $user_settings['additional_groups']); + + // Are you banned? + is_not_banned(true); + + // An administrator, set up the login so they don't have to type it again. + if ($user_info['is_admin'] && isset($user_settings['openid_uri']) && empty($user_settings['openid_uri'])) + { + $_SESSION['admin_time'] = time(); + unset($_SESSION['just_registered']); + } + + // Don't stick the language or theme after this point. + unset($_SESSION['language'], $_SESSION['id_theme']); + + // First login? + $request = $smcFunc['db_query']('', ' + SELECT last_login + FROM {db_prefix}members + WHERE id_member = {int:id_member} + AND last_login = 0', + array( + 'id_member' => $user_info['id'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 1) + $_SESSION['first_login'] = true; + else + unset($_SESSION['first_login']); + $smcFunc['db_free_result']($request); + + // You've logged in, haven't you? + updateMemberData($user_info['id'], array('last_login' => time(), 'member_ip' => $user_info['ip'], 'member_ip2' => $_SERVER['BAN_CHECK_IP'])); + + // Get rid of the online entry for that old guest.... + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_online + WHERE session = {string:session}', + array( + 'session' => 'ip' . $user_info['ip'], + ) + ); + $_SESSION['log_time'] = 0; + + // Just log you back out if it's in maintenance mode and you AREN'T an admin. + if (empty($maintenance) || allowedTo('admin_forum')) + redirectexit('action=login2;sa=check;member=' . $user_info['id'], $context['server']['needs_login_fix']); + else + redirectexit('action=logout;' . $context['session_var'] . '=' . $context['session_id'], $context['server']['needs_login_fix']); +} + +// Log the user out. +function Logout($internal = false, $redirect = true) +{ + global $sourcedir, $user_info, $user_settings, $context, $modSettings, $smcFunc; + + // Make sure they aren't being auto-logged out. + if (!$internal) + checkSession('get'); + + require_once($sourcedir . '/Subs-Auth.php'); + + if (isset($_SESSION['pack_ftp'])) + $_SESSION['pack_ftp'] = null; + + // They cannot be open ID verified any longer. + if (isset($_SESSION['openid'])) + unset($_SESSION['openid']); + + // It won't be first login anymore. + unset($_SESSION['first_login']); + + // Just ensure they aren't a guest! + if (!$user_info['is_guest']) + { + // Pass the logout information to integrations. + call_integration_hook('integrate_logout', array($user_settings['member_name'])); + + // If you log out, you aren't online anymore :P. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_online + WHERE id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + ) + ); + } + + $_SESSION['log_time'] = 0; + + // Empty the cookie! (set it in the past, and for id_member = 0) + setLoginCookie(-3600, 0); + + // And some other housekeeping while we're at it. + session_destroy(); + if (!empty($user_info['id'])) + updateMemberData($user_info['id'], array('password_salt' => substr(md5(mt_rand()), 0, 4))); + + // Off to the merry board index we go! + if ($redirect) + { + if (empty($_SESSION['logout_url'])) + redirectexit('', $context['server']['needs_login_fix']); + else + { + $temp = $_SESSION['logout_url']; + unset($_SESSION['logout_url']); + + redirectexit($temp, $context['server']['needs_login_fix']); + } + } +} + +// MD5 Encryption used for older passwords. +function md5_hmac($data, $key) +{ + $key = str_pad(strlen($key) <= 64 ? $key : pack('H*', md5($key)), 64, chr(0x00)); + return md5(($key ^ str_repeat(chr(0x5c), 64)) . pack('H*', md5(($key ^ str_repeat(chr(0x36), 64)) . $data))); +} + +// Special encryption used by phpBB3. +function phpBB3_password_check($passwd, $passwd_hash) +{ + // Too long or too short? + if (strlen($passwd_hash) != 34) + return; + + // Range of characters allowed. + $range = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + + // Tests + $strpos = strpos($range, $passwd_hash[3]); + $count = 1 << $strpos; + $count2 = $count; + $salt = substr($passwd_hash, 4, 8); + + // Things are done differently for PHP 5. + if (@version_compare(PHP_VERSION, '5') >= 0) + { + $hash = md5($salt . $passwd, true); + for (; $count != 0; --$count) + $hash = md5($hash . $passwd, true); + } + else + { + $hash = pack('H*', md5($salt . $passwd)); + for (; $count != 0; --$count) + $hash = pack('H*', md5($hash . $passwd)); + } + + $output = substr($passwd_hash, 0, 12); + $i = 0; + while ($i < 16) + { + $value = ord($hash[$i++]); + $output .= $range[$value & 0x3f]; + + if ($i < 16) + $value |= ord($hash[$i]) << 8; + + $output .= $range[($value >> 6) & 0x3f]; + + if ($i++ >= 16) + break; + + if ($i < 16) + $value |= ord($hash[$i]) << 16; + + $output .= $range[($value >> 12) & 0x3f]; + + if ($i++ >= 16) + break; + + $output .= $range[($value >> 18) & 0x3f]; + } + + // Return now. + return $output; +} + +// This protects against brute force attacks on a member's password. Importantly even if the password was right we DON'T TELL THEM! +function validatePasswordFlood($id_member, $password_flood_value = false, $was_correct = false) +{ + global $smcFunc, $cookiename, $sourcedir; + + // As this is only brute protection, we allow 5 attempts every 10 seconds. + + // Destroy any session or cookie data about this member, as they validated wrong. + require_once($sourcedir . '/Subs-Auth.php'); + setLoginCookie(-3600, 0); + + if (isset($_SESSION['login_' . $cookiename])) + unset($_SESSION['login_' . $cookiename]); + + // We need a member! + if (!$id_member) + { + // Redirect back! + redirectexit(); + + // Probably not needed, but still make sure... + fatal_lang_error('no_access', false); + } + + // Right, have we got a flood value? + if ($password_flood_value !== false) + @list ($time_stamp, $number_tries) = explode('|', $password_flood_value); + + // Timestamp or number of tries invalid? + if (empty($number_tries) || empty($time_stamp)) + { + $number_tries = 0; + $time_stamp = time(); + } + + // They've failed logging in already + if (!empty($number_tries)) + { + // Give them less chances if they failed before + $number_tries = $time_stamp < time() - 20 ? 2 : $number_tries; + + // They are trying too fast, make them wait longer + if ($time_stamp < time() - 10) + $time_stamp = time(); + } + + $number_tries++; + + // Broken the law? + if ($number_tries > 5) + fatal_lang_error('login_threshold_brute_fail', 'critical'); + + // Otherwise set the members data. If they correct on their first attempt then we actually clear it, otherwise we set it! + updateMemberData($id_member, array('passwd_flood' => $was_correct && $number_tries == 1 ? '' : $time_stamp . '|' . $number_tries)); + +} + +?> \ No newline at end of file diff --git a/Sources/ManageAttachments.php b/Sources/ManageAttachments.php new file mode 100644 index 0000000..0c07f94 --- /dev/null +++ b/Sources/ManageAttachments.php @@ -0,0 +1,1835 @@ + 'ManageAttachmentSettings', + 'attachpaths' => 'ManageAttachmentPaths', + 'avatars' => 'ManageAvatarSettings', + 'browse' => 'BrowseFiles', + 'byAge' => 'RemoveAttachmentByAge', + 'bySize' => 'RemoveAttachmentBySize', + 'maintenance' => 'MaintainFiles', + 'moveAvatars' => 'MoveAvatars', + 'repair' => 'RepairAttachments', + 'remove' => 'RemoveAttachment', + 'removeall' => 'RemoveAllAttachments' + ); + + // Pick the correct sub-action. + if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']])) + $context['sub_action'] = $_REQUEST['sa']; + else + $context['sub_action'] = 'browse'; + + // Default page title is good. + $context['page_title'] = $txt['attachments_avatars']; + + // This uses admin tabs - as it should! + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['attachments_avatars'], + 'help' => 'manage_files', + 'description' => $txt['attachments_desc'], + ); + + // Finally fall through to what we are doing. + $subActions[$context['sub_action']](); +} + +function ManageAttachmentSettings($return_config = false) +{ + global $txt, $modSettings, $scripturl, $context, $options, $sourcedir; + + $context['valid_upload_dir'] = is_dir($modSettings['attachmentUploadDir']) && is_writable($modSettings['attachmentUploadDir']); + + // Perform a test to see if the GD module is installed. + $testGD = get_extension_funcs('gd'); + + $config_vars = array( + array('title', 'attachment_manager_settings'), + // Are attachments enabled? + array('select', 'attachmentEnable', array($txt['attachmentEnable_deactivate'], $txt['attachmentEnable_enable_all'], $txt['attachmentEnable_disable_new'])), + '', + // Extension checks etc. + array('check', 'attachmentCheckExtensions'), + array('text', 'attachmentExtensions', 40), + array('check', 'attachmentRecodeLineEndings'), + '', + // Directory and size limits. + empty($modSettings['currentAttachmentUploadDir']) ? array('text', 'attachmentUploadDir', 40, 'invalid' => !$context['valid_upload_dir']) : array('var_message', 'attachmentUploadDir_multiple', 'message' => 'attachmentUploadDir_multiple_configure'), + array('text', 'attachmentDirSizeLimit', 6, 'postinput' => $txt['kilobyte']), + array('text', 'attachmentPostLimit', 6, 'postinput' => $txt['kilobyte']), + array('text', 'attachmentSizeLimit', 6, 'postinput' => $txt['kilobyte']), + array('text', 'attachmentNumPerPostLimit', 6), + '', + // Image settings. + array('warning', empty($testGD) ? 'attachment_gd_warning' : ''), + array('check', 'attachment_image_reencode'), + '', + array('warning', 'attachment_image_paranoid_warning'), + array('check', 'attachment_image_paranoid'), + '', + // Thumbnail settings. + array('check', 'attachmentShowImages'), + array('check', 'attachmentThumbnails'), + array('check', 'attachment_thumb_png'), + array('text', 'attachmentThumbWidth', 6), + array('text', 'attachmentThumbHeight', 6), + ); + + if ($return_config) + return $config_vars; + + // These are very likely to come in handy! (i.e. without them we're doomed!) + require_once($sourcedir . '/ManagePermissions.php'); + require_once($sourcedir . '/ManageServer.php'); + + // Saving settings? + if (isset($_GET['save'])) + { + checkSession(); + + saveDBSettings($config_vars); + redirectexit('action=admin;area=manageattachments;sa=attachments'); + } + + $context['post_url'] = $scripturl . '?action=admin;area=manageattachments;save;sa=attachments'; + prepareDBSettingContext($config_vars); + + $context['sub_template'] = 'show_settings'; +} + +function ManageAvatarSettings($return_config = false) +{ + global $txt, $context, $modSettings, $sourcedir, $scripturl; + + // Perform a test to see if the GD module is installed. + $testGD = get_extension_funcs('gd'); + + $context['valid_avatar_dir'] = is_dir($modSettings['avatar_directory']); + $context['valid_custom_avatar_dir'] = empty($modSettings['custom_avatar_enabled']) || (!empty($modSettings['custom_avatar_dir']) && is_dir($modSettings['custom_avatar_dir']) && is_writable($modSettings['custom_avatar_dir'])); + + $config_vars = array( + // Server stored avatars! + array('title', 'avatar_server_stored'), + array('warning', empty($testGD) ? 'avatar_gd_warning' : ''), + array('permissions', 'profile_server_avatar', 0, $txt['avatar_server_stored_groups']), + array('text', 'avatar_directory', 40, 'invalid' => !$context['valid_avatar_dir']), + array('text', 'avatar_url', 40), + // External avatars? + array('title', 'avatar_external'), + array('permissions', 'profile_remote_avatar', 0, $txt['avatar_external_url_groups']), + array('check', 'avatar_download_external', 0, 'onchange' => 'fUpdateStatus();'), + array('text', 'avatar_max_width_external', 6), + array('text', 'avatar_max_height_external', 6), + array('select', 'avatar_action_too_large', + array( + 'option_refuse' => $txt['option_refuse'], + 'option_html_resize' => $txt['option_html_resize'], + 'option_js_resize' => $txt['option_js_resize'], + 'option_download_and_resize' => $txt['option_download_and_resize'], + ), + ), + // Uploadable avatars? + array('title', 'avatar_upload'), + array('permissions', 'profile_upload_avatar', 0, $txt['avatar_upload_groups']), + array('text', 'avatar_max_width_upload', 6), + array('text', 'avatar_max_height_upload', 6), + array('check', 'avatar_resize_upload', 'subtext' => $txt['avatar_resize_upload_note']), + array('check', 'avatar_reencode'), + '', + array('warning', 'avatar_paranoid_warning'), + array('check', 'avatar_paranoid'), + '', + array('check', 'avatar_download_png'), + array('select', 'custom_avatar_enabled', array($txt['option_attachment_dir'], $txt['option_specified_dir']), 'onchange' => 'fUpdateStatus();'), + array('text', 'custom_avatar_dir', 40, 'subtext' => $txt['custom_avatar_dir_desc'], 'invalid' => !$context['valid_custom_avatar_dir']), + array('text', 'custom_avatar_url', 40), + ); + + if ($return_config) + return $config_vars; + + // We need these files for the inline permission settings, and the settings template. + require_once($sourcedir . '/ManagePermissions.php'); + require_once($sourcedir . '/ManageServer.php'); + + // Saving avatar settings? + if (isset($_GET['save'])) + { + checkSession(); + + // Just incase the admin forgot to set both custom avatar values, we disable it to prevent errors. + if (isset($_POST['custom_avatar_enabled']) && $_POST['custom_avatar_enabled'] == 1 && (empty($_POST['custom_avatar_dir']) || empty($_POST['custom_avatar_url']))) + $_POST['custom_avatar_enabled'] = 0; + + saveDBSettings($config_vars); + redirectexit('action=admin;area=manageattachments;sa=avatars'); + } + + // Attempt to figure out if the admin is trying to break things. + $context['settings_save_onclick'] = 'return document.getElementById(\'custom_avatar_enabled\').value == 1 && (document.getElementById(\'custom_avatar_dir\').value == \'\' || document.getElementById(\'custom_avatar_url\').value == \'\') ? confirm(\'' . $txt['custom_avatar_check_empty'] . '\') : true;'; + + // Prepare the context. + $context['post_url'] = $scripturl . '?action=admin;area=manageattachments;save;sa=avatars'; + prepareDBSettingContext($config_vars); + + // Add a layer for the javascript. + $context['template_layers'][] = 'avatar_settings'; + $context['sub_template'] = 'show_settings'; +} + +function BrowseFiles() +{ + global $context, $txt, $scripturl, $options, $modSettings; + global $smcFunc, $sourcedir; + + $context['sub_template'] = 'browse'; + + // Attachments or avatars? + $context['browse_type'] = isset($_REQUEST['avatars']) ? 'avatars' : (isset($_REQUEST['thumbs']) ? 'thumbs' : 'attachments'); + + // Set the options for the list component. + $listOptions = array( + 'id' => 'file_list', + 'title' => $txt['attachment_manager_' . ($context['browse_type'] === 'avatars' ? 'avatars' : ( $context['browse_type'] === 'thumbs' ? 'thumbs' : 'attachments'))], + 'items_per_page' => $modSettings['defaultMaxMessages'], + 'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=browse' . ($context['browse_type'] === 'avatars' ? ';avatars' : ($context['browse_type'] === 'thumbs' ? ';thumbs' : '')), + 'default_sort_col' => 'name', + 'no_items_label' => $txt['attachment_manager_' . ($context['browse_type'] === 'avatars' ? 'avatars' : ( $context['browse_type'] === 'thumbs' ? 'thumbs' : 'attachments')) . '_no_entries'], + 'get_items' => array( + 'function' => 'list_getFiles', + 'params' => array( + $context['browse_type'], + ), + ), + 'get_count' => array( + 'function' => 'list_getNumFiles', + 'params' => array( + $context['browse_type'], + ), + ), + 'columns' => array( + 'name' => array( + 'header' => array( + 'value' => $txt['attachment_name'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $modSettings, $context, $scripturl; + + $link = \'%1$s\', preg_replace(\'~&#(\\\\d{1,7}|x[0-9a-fA-F]{1,6});~\', \'&#\\\\1;\', htmlspecialchars($rowData[\'filename\']))); + + // Show the dimensions. + if (!empty($rowData[\'width\']) && !empty($rowData[\'height\'])) + $link .= sprintf(\' %1$dx%2$d\', $rowData[\'width\'], $rowData[\'height\']); + + return $link; + '), + ), + 'sort' => array( + 'default' => 'a.filename', + 'reverse' => 'a.filename DESC', + ), + ), + 'filesize' => array( + 'header' => array( + 'value' => $txt['attachment_file_size'], + ), + 'data' => array( + 'function' => create_function('$rowData',' + global $txt; + + return sprintf(\'%1$s%2$s\', round($rowData[\'size\'] / 1024, 2), $txt[\'kilobyte\']); + '), + 'class' => 'windowbg', + ), + 'sort' => array( + 'default' => 'a.size', + 'reverse' => 'a.size DESC', + ), + ), + 'member' => array( + 'header' => array( + 'value' => $context['browse_type'] == 'avatars' ? $txt['attachment_manager_member'] : $txt['posted_by'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $scripturl; + + // In case of an attachment, return the poster of the attachment. + if (empty($rowData[\'id_member\'])) + return htmlspecialchars($rowData[\'poster_name\']); + + // Otherwise it must be an avatar, return the link to the owner of it. + else + return sprintf(\'%3$s\', $scripturl, $rowData[\'id_member\'], $rowData[\'poster_name\']); + '), + ), + 'sort' => array( + 'default' => 'mem.real_name', + 'reverse' => 'mem.real_name DESC', + ), + ), + 'date' => array( + 'header' => array( + 'value' => $context['browse_type'] == 'avatars' ? $txt['attachment_manager_last_active'] : $txt['date'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt, $context, $scripturl; + + // The date the message containing the attachment was posted or the owner of the avatar was active. + $date = empty($rowData[\'poster_time\']) ? $txt[\'never\'] : timeformat($rowData[\'poster_time\']); + + // Add a link to the topic in case of an attachment. + if ($context[\'browse_type\'] !== \'avatars\') + $date .= sprintf(\'
%1$s %5$s\', $txt[\'in\'], $scripturl, $rowData[\'id_topic\'], $rowData[\'id_msg\'], $rowData[\'subject\']); + + return $date; + '), + 'class' => 'windowbg', + ), + 'sort' => array( + 'default' => $context['browse_type'] === 'avatars' ? 'mem.last_login' : 'm.id_msg', + 'reverse' => $context['browse_type'] === 'avatars' ? 'mem.last_login DESC' : 'm.id_msg DESC', + ), + ), + 'downloads' => array( + 'header' => array( + 'value' => $txt['downloads'], + ), + 'data' => array( + 'function' => create_function('$rowData',' + global $txt; + + return comma_format($rowData[\'downloads\']); + '), + 'class' => 'windowbg', + ), + 'sort' => array( + 'default' => 'a.downloads', + 'reverse' => 'a.downloads DESC', + ), + ), + 'check' => array( + 'header' => array( + 'value' => '', + ), + 'data' => array( + 'sprintf' => array( + 'format' => '', + 'params' => array( + 'id_attach' => false, + ), + ), + 'style' => 'text-align: center', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=manageattachments;sa=remove' . ($context['browse_type'] === 'avatars' ? ';avatars' : ($context['browse_type'] === 'thumbs' ? ';thumbs' : '')), + 'include_sort' => true, + 'include_start' => true, + 'hidden_fields' => array( + 'type' => $context['browse_type'], + ), + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => '', + 'style' => 'text-align: right;', + ), + ), + ); + + // Create the list. + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); +} + +function list_getFiles($start, $items_per_page, $sort, $browse_type) +{ + global $smcFunc, $txt; + + // Choose a query depending on what we are viewing. + if ($browse_type === 'avatars') + $request = $smcFunc['db_query']('', ' + SELECT + {string:blank_text} AS id_msg, IFNULL(mem.real_name, {string:not_applicable_text}) AS poster_name, + mem.last_login AS poster_time, 0 AS id_topic, a.id_member, a.id_attach, a.filename, a.file_hash, a.attachment_type, + a.size, a.width, a.height, a.downloads, {string:blank_text} AS subject, 0 AS id_board + FROM {db_prefix}attachments AS a + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = a.id_member) + WHERE a.id_member != {int:guest_id} + ORDER BY {raw:sort} + LIMIT {int:start}, {int:per_page}', + array( + 'guest_id' => 0, + 'blank_text' => '', + 'not_applicable_text' => $txt['not_applicable'], + 'sort' => $sort, + 'start' => $start, + 'per_page' => $items_per_page, + ) + ); + else + $request = $smcFunc['db_query']('', ' + SELECT + m.id_msg, IFNULL(mem.real_name, m.poster_name) AS poster_name, m.poster_time, m.id_topic, m.id_member, + a.id_attach, a.filename, a.file_hash, a.attachment_type, a.size, a.width, a.height, a.downloads, mf.subject, t.id_board + FROM {db_prefix}attachments AS a + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg) + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE a.attachment_type = {int:attachment_type} + ORDER BY {raw:sort} + LIMIT {int:start}, {int:per_page}', + array( + 'attachment_type' => $browse_type == 'thumbs' ? '3' : '0', + 'sort' => $sort, + 'start' => $start, + 'per_page' => $items_per_page, + ) + ); + $files = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $files[] = $row; + $smcFunc['db_free_result']($request); + + return $files; +} + +function list_getNumFiles($browse_type) +{ + global $smcFunc; + + // Depending on the type of file, different queries are used. + if ($browse_type === 'avatars') + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}attachments + WHERE id_member != {int:guest_id_member}', + array( + 'guest_id_member' => 0, + ) + ); + else + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS num_attach + FROM {db_prefix}attachments AS a + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg) + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg) + WHERE a.attachment_type = {int:attachment_type} + AND a.id_member = {int:guest_id_member}', + array( + 'attachment_type' => $browse_type === 'thumbs' ? '3' : '0', + 'guest_id_member' => 0, + ) + ); + + list ($num_files) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $num_files; +} + +function MaintainFiles() +{ + global $context, $modSettings, $txt, $smcFunc; + + $context['sub_template'] = 'maintenance'; + + if (!empty($modSettings['currentAttachmentUploadDir'])) + $attach_dirs = unserialize($modSettings['attachmentUploadDir']); + else + $attach_dirs = array($modSettings['attachmentUploadDir']); + + // Get the number of attachments.... + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}attachments + WHERE attachment_type = {int:attachment_type} + AND id_member = {int:guest_id_member}', + array( + 'attachment_type' => 0, + 'guest_id_member' => 0, + ) + ); + list ($context['num_attachments']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Also get the avatar amount.... + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}attachments + WHERE id_member != {int:guest_id_member}', + array( + 'guest_id_member' => 0, + ) + ); + list ($context['num_avatars']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Find out how big the directory is. We have to loop through all our attachment paths in case there's an old temp file in one of them. + $attachmentDirSize = 0; + foreach ($attach_dirs as $id => $attach_dir) + { + $dir = @opendir($attach_dir) or fatal_lang_error('cant_access_upload_path', 'critical'); + while ($file = readdir($dir)) + { + if ($file == '.' || $file == '..') + continue; + + if (preg_match('~^post_tmp_\d+_\d+$~', $file) != 0) + { + // Temp file is more than 5 hours old! + if (filemtime($attach_dir . '/' . $file) < time() - 18000) + @unlink($attach_dir . '/' . $file); + continue; + } + + // We're only counting the size of the current attachment directory. + if (empty($modSettings['currentAttachmentUploadDir']) || $modSettings['currentAttachmentUploadDir'] == $id) + $attachmentDirSize += filesize($attach_dir . '/' . $file); + } + closedir($dir); + } + // Divide it into kilobytes. + $attachmentDirSize /= 1024; + + // If they specified a limit only.... + if (!empty($modSettings['attachmentDirSizeLimit'])) + $context['attachment_space'] = max(round($modSettings['attachmentDirSizeLimit'] - $attachmentDirSize, 2), 0); + $context['attachment_total_size'] = round($attachmentDirSize, 2); + + $context['attach_multiple_dirs'] = !empty($modSettings['currentAttachmentUploadDir']); +} + +// !!! Not implemented yet. +function MoveAvatars() +{ + global $modSettings, $smcFunc; + + // First make sure the custom avatar dir is writable. + if (!is_writable($modSettings['custom_avatar_dir'])) + { + // Try to fix it. + @chmod($modSettings['custom_avatar_dir'], 0777); + + // Guess that didn't work? + if (!is_writable($modSettings['custom_avatar_dir'])) + fatal_lang_error('attachments_no_write', 'critical'); + } + + $request = $smcFunc['db_query']('', ' + SELECT id_attach, id_folder, id_member, filename, file_hash + FROM {db_prefix}attachments + WHERE attachment_type = {int:attachment_type} + AND id_member > {int:guest_id_member}', + array( + 'attachment_type' => 0, + 'guest_id_member' => 0, + ) + ); + $updatedAvatars = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']); + + if (rename($filename, $modSettings['custom_avatar_dir'] . '/' . $row['filename'])) + $updatedAvatars[] = $row['id_attach']; + } + $smcFunc['db_free_result']($request); + + if (!empty($updatedAvatars)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}attachments + SET attachment_type = {int:attachment_type} + WHERE id_attach IN ({array_int:updated_avatars})', + array( + 'updated_avatars' => $updatedAvatars, + 'attachment_type' => 1, + ) + ); + + redirectexit('action=admin;area=manageattachments;sa=maintenance'); +} + +function RemoveAttachmentByAge() +{ + global $modSettings, $smcFunc; + + checkSession('post', 'admin'); + + // !!! Ignore messages in topics that are stickied? + + // Deleting an attachment? + if ($_REQUEST['type'] != 'avatars') + { + // Get all the old attachments. + $messages = removeAttachments(array('attachment_type' => 0, 'poster_time' => (time() - 24 * 60 * 60 * $_POST['age'])), 'messages', true); + + // Update the messages to reflect the change. + if (!empty($messages)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET body = CONCAT(body, ' . (!empty($_POST['notice']) ? '{string:notice}' : '') . ') + WHERE id_msg IN ({array_int:messages})', + array( + 'messages' => $messages, + 'notice' => empty($_POST['notice']) ? '' : '

' . $_POST['notice'], + ) + ); + } + else + { + // Remove all the old avatars. + removeAttachments(array('not_id_member' => 0, 'last_login' => (time() - 24 * 60 * 60 * $_POST['age'])), 'members'); + } + redirectexit('action=admin;area=manageattachments' . (empty($_REQUEST['avatars']) ? ';sa=maintenance' : ';avatars')); +} + +function RemoveAttachmentBySize() +{ + global $modSettings, $smcFunc; + + checkSession('post', 'admin'); + + // Find humungous attachments. + $messages = removeAttachments(array('attachment_type' => 0, 'size' => 1024 * $_POST['size']), 'messages', true); + + // And make a note on the post. + if (!empty($messages)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET body = CONCAT(body, ' . (!empty($_POST['notice']) ? '{string:notice}' : '') . ') + WHERE id_msg IN ({array_int:messages})', + array( + 'messages' => $messages, + 'notice' => empty($_POST['notice']) ? '' : '

' . $_POST['notice'], + ) + ); + + redirectexit('action=admin;area=manageattachments;sa=maintenance'); +} + +function RemoveAttachment() +{ + global $modSettings, $txt, $smcFunc; + + checkSession('post'); + + if (!empty($_POST['remove'])) + { + $attachments = array(); + // There must be a quicker way to pass this safety test?? + foreach ($_POST['remove'] as $removeID => $dummy) + $attachments[] = (int) $removeID; + + if ($_REQUEST['type'] == 'avatars' && !empty($attachments)) + removeAttachments(array('id_attach' => $attachments)); + else if (!empty($attachments)) + { + $messages = removeAttachments(array('id_attach' => $attachments), 'messages', true); + + // And change the message to reflect this. + if (!empty($messages)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET body = CONCAT(body, {string:deleted_message}) + WHERE id_msg IN ({array_int:messages_affected})', + array( + 'messages_affected' => $messages, + 'deleted_message' => '

' . $txt['attachment_delete_admin'], + ) + ); + } + } + + $_GET['sort'] = isset($_GET['sort']) ? $_GET['sort'] : 'date'; + redirectexit('action=admin;area=manageattachments;sa=browse;' . $_REQUEST['type'] . ';sort=' . $_GET['sort'] . (isset($_GET['desc']) ? ';desc' : '') . ';start=' . $_REQUEST['start']); +} + +// !!! Not implemented (yet?) +function RemoveAllAttachments() +{ + global $txt, $smcFunc; + + checkSession('get', 'admin'); + + $messages = removeAttachments(array('attachment_type' => 0), '', true); + + if (!isset($_POST['notice'])) + $_POST['notice'] = $txt['attachment_delete_admin']; + + // Add the notice on the end of the changed messages. + if (!empty($messages)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET body = CONCAT(body, {string:deleted_message}) + WHERE id_msg IN ({array_int:messages})', + array( + 'messages' => $messages, + 'deleted_message' => '

' . $_POST['notice'], + ) + ); + + redirectexit('action=admin;area=manageattachments;sa=maintenance'); +} + +// Removes attachments - allowed query_types: '', 'messages', 'members' +function removeAttachments($condition, $query_type = '', $return_affected_messages = false, $autoThumbRemoval = true) +{ + global $modSettings, $smcFunc; + + //!!! This might need more work! + $new_condition = array(); + $query_parameter = array( + 'thumb_attachment_type' => 3, + ); + + if (is_array($condition)) + { + foreach ($condition as $real_type => $restriction) + { + // Doing a NOT? + $is_not = substr($real_type, 0, 4) == 'not_'; + $type = $is_not ? substr($real_type, 4) : $real_type; + + if (in_array($type, array('id_member', 'id_attach', 'id_msg'))) + $new_condition[] = 'a.' . $type . ($is_not ? ' NOT' : '') . ' IN (' . (is_array($restriction) ? '{array_int:' . $real_type . '}' : '{int:' . $real_type . '}') . ')'; + elseif ($type == 'attachment_type') + $new_condition[] = 'a.attachment_type = {int:' . $real_type . '}'; + elseif ($type == 'poster_time') + $new_condition[] = 'm.poster_time < {int:' . $real_type . '}'; + elseif ($type == 'last_login') + $new_condition[] = 'mem.last_login < {int:' . $real_type . '}'; + elseif ($type == 'size') + $new_condition[] = 'a.size > {int:' . $real_type . '}'; + elseif ($type == 'id_topic') + $new_condition[] = 'm.id_topic IN (' . (is_array($restriction) ? '{array_int:' . $real_type . '}' : '{int:' . $real_type . '}') . ')'; + + // Add the parameter! + $query_parameter[$real_type] = $restriction; + } + $condition = implode(' AND ', $new_condition); + } + + // Delete it only if it exists... + $msgs = array(); + $attach = array(); + $parents = array(); + + // Get all the attachment names and id_msg's. + $request = $smcFunc['db_query']('', ' + SELECT + a.id_folder, a.filename, a.file_hash, a.attachment_type, a.id_attach, a.id_member' . ($query_type == 'messages' ? ', m.id_msg' : ', a.id_msg') . ', + thumb.id_folder AS thumb_folder, IFNULL(thumb.id_attach, 0) AS id_thumb, thumb.filename AS thumb_filename, thumb.file_hash AS thumb_file_hash, thumb_parent.id_attach AS id_parent + FROM {db_prefix}attachments AS a' .($query_type == 'members' ? ' + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = a.id_member)' : ($query_type == 'messages' ? ' + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)' : '')) . ' + LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb) + LEFT JOIN {db_prefix}attachments AS thumb_parent ON (thumb.attachment_type = {int:thumb_attachment_type} AND thumb_parent.id_thumb = a.id_attach) + WHERE ' . $condition, + $query_parameter + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Figure out the "encrypted" filename and unlink it ;). + if ($row['attachment_type'] == 1) + @unlink($modSettings['custom_avatar_dir'] . '/' . $row['filename']); + else + { + $filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']); + @unlink($filename); + + // If this was a thumb, the parent attachment should know about it. + if (!empty($row['id_parent'])) + $parents[] = $row['id_parent']; + + // If this attachments has a thumb, remove it as well. + if (!empty($row['id_thumb']) && $autoThumbRemoval) + { + $thumb_filename = getAttachmentFilename($row['thumb_filename'], $row['id_thumb'], $row['thumb_folder'], false, $row['thumb_file_hash']); + @unlink($thumb_filename); + $attach[] = $row['id_thumb']; + } + } + + // Make a list. + if ($return_affected_messages && empty($row['attachment_type'])) + $msgs[] = $row['id_msg']; + $attach[] = $row['id_attach']; + } + $smcFunc['db_free_result']($request); + + // Removed attachments don't have to be updated anymore. + $parents = array_diff($parents, $attach); + if (!empty($parents)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}attachments + SET id_thumb = {int:no_thumb} + WHERE id_attach IN ({array_int:parent_attachments})', + array( + 'parent_attachments' => $parents, + 'no_thumb' => 0, + ) + ); + + if (!empty($attach)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}attachments + WHERE id_attach IN ({array_int:attachment_list})', + array( + 'attachment_list' => $attach, + ) + ); + + if ($return_affected_messages) + return array_unique($msgs); +} + +// This function should find attachments in the database that no longer exist and clear them, and fix filesize issues. +function RepairAttachments() +{ + global $modSettings, $context, $txt, $smcFunc; + + checkSession('get'); + + // If we choose cancel, redirect right back. + if (isset($_POST['cancel'])) + redirectexit('action=admin;area=manageattachments;sa=maintenance'); + + // Try give us a while to sort this out... + @set_time_limit(600); + + $_GET['step'] = empty($_GET['step']) ? 0 : (int) $_GET['step']; + $_GET['substep'] = empty($_GET['substep']) ? 0 : (int) $_GET['substep']; + + // Don't recall the session just in case. + if ($_GET['step'] == 0 && $_GET['substep'] == 0) + { + unset($_SESSION['attachments_to_fix'], $_SESSION['attachments_to_fix2']); + + // If we're actually fixing stuff - work out what. + if (isset($_GET['fixErrors'])) + { + // Nothing? + if (empty($_POST['to_fix'])) + redirectexit('action=admin;area=manageattachments;sa=maintenance'); + + $_SESSION['attachments_to_fix'] = array(); + //!!! No need to do this I think. + foreach ($_POST['to_fix'] as $key => $value) + $_SESSION['attachments_to_fix'][] = $value; + } + } + + // All the valid problems are here: + $context['repair_errors'] = array( + 'missing_thumbnail_parent' => 0, + 'parent_missing_thumbnail' => 0, + 'file_missing_on_disk' => 0, + 'file_wrong_size' => 0, + 'file_size_of_zero' => 0, + 'attachment_no_msg' => 0, + 'avatar_no_member' => 0, + 'wrong_folder' => 0, + ); + + $to_fix = !empty($_SESSION['attachments_to_fix']) ? $_SESSION['attachments_to_fix'] : array(); + $context['repair_errors'] = isset($_SESSION['attachments_to_fix2']) ? $_SESSION['attachments_to_fix2'] : $context['repair_errors']; + $fix_errors = isset($_GET['fixErrors']) ? true : false; + + // Get stranded thumbnails. + if ($_GET['step'] <= 0) + { + $result = $smcFunc['db_query']('', ' + SELECT MAX(id_attach) + FROM {db_prefix}attachments + WHERE attachment_type = {int:thumbnail}', + array( + 'thumbnail' => 3, + ) + ); + list ($thumbnails) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + for (; $_GET['substep'] < $thumbnails; $_GET['substep'] += 500) + { + $to_remove = array(); + + $result = $smcFunc['db_query']('', ' + SELECT thumb.id_attach, thumb.id_folder, thumb.filename, thumb.file_hash + FROM {db_prefix}attachments AS thumb + LEFT JOIN {db_prefix}attachments AS tparent ON (tparent.id_thumb = thumb.id_attach) + WHERE thumb.id_attach BETWEEN {int:substep} AND {int:substep} + 499 + AND thumb.attachment_type = {int:thumbnail} + AND tparent.id_attach IS NULL', + array( + 'thumbnail' => 3, + 'substep' => $_GET['substep'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + // Only do anything once... just in case + if (!isset($to_remove[$row['id_attach']])) + { + $to_remove[$row['id_attach']] = $row['id_attach']; + $context['repair_errors']['missing_thumbnail_parent']++; + + // If we are repairing remove the file from disk now. + if ($fix_errors && in_array('missing_thumbnail_parent', $to_fix)) + { + $filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']); + @unlink($filename); + } + } + } + if ($smcFunc['db_num_rows']($result) != 0) + $to_fix[] = 'missing_thumbnail_parent'; + $smcFunc['db_free_result']($result); + + // Do we need to delete what we have? + if ($fix_errors && !empty($to_remove) && in_array('missing_thumbnail_parent', $to_fix)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}attachments + WHERE id_attach IN ({array_int:to_remove}) + AND attachment_type = {int:attachment_type}', + array( + 'to_remove' => $to_remove, + 'attachment_type' => 3, + ) + ); + + pauseAttachmentMaintenance($to_fix, $thumbnails); + } + + $_GET['step'] = 1; + $_GET['substep'] = 0; + pauseAttachmentMaintenance($to_fix); + } + + // Find parents which think they have thumbnails, but actually, don't. + if ($_GET['step'] <= 1) + { + $result = $smcFunc['db_query']('', ' + SELECT MAX(id_attach) + FROM {db_prefix}attachments + WHERE id_thumb != {int:no_thumb}', + array( + 'no_thumb' => 0, + ) + ); + list ($thumbnails) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + for (; $_GET['substep'] < $thumbnails; $_GET['substep'] += 500) + { + $to_update = array(); + + $result = $smcFunc['db_query']('', ' + SELECT a.id_attach + FROM {db_prefix}attachments AS a + LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb) + WHERE a.id_attach BETWEEN {int:substep} AND {int:substep} + 499 + AND a.id_thumb != {int:no_thumb} + AND thumb.id_attach IS NULL', + array( + 'no_thumb' => 0, + 'substep' => $_GET['substep'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $to_update[] = $row['id_attach']; + $context['repair_errors']['parent_missing_thumbnail']++; + } + if ($smcFunc['db_num_rows']($result) != 0) + $to_fix[] = 'parent_missing_thumbnail'; + $smcFunc['db_free_result']($result); + + // Do we need to delete what we have? + if ($fix_errors && !empty($to_update) && in_array('parent_missing_thumbnail', $to_fix)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}attachments + SET id_thumb = {int:no_thumb} + WHERE id_attach IN ({array_int:to_update})', + array( + 'to_update' => $to_update, + 'no_thumb' => 0, + ) + ); + + pauseAttachmentMaintenance($to_fix, $thumbnails); + } + + $_GET['step'] = 2; + $_GET['substep'] = 0; + pauseAttachmentMaintenance($to_fix); + } + + // This may take forever I'm afraid, but life sucks... recount EVERY attachments! + if ($_GET['step'] <= 2) + { + $result = $smcFunc['db_query']('', ' + SELECT MAX(id_attach) + FROM {db_prefix}attachments', + array( + ) + ); + list ($thumbnails) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + for (; $_GET['substep'] < $thumbnails; $_GET['substep'] += 250) + { + $to_remove = array(); + $errors_found = array(); + + $result = $smcFunc['db_query']('', ' + SELECT id_attach, id_folder, filename, file_hash, size, attachment_type + FROM {db_prefix}attachments + WHERE id_attach BETWEEN {int:substep} AND {int:substep} + 249', + array( + 'substep' => $_GET['substep'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + // Get the filename. + if ($row['attachment_type'] == 1) + $filename = $modSettings['custom_avatar_dir'] . '/' . $row['filename']; + else + $filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']); + + // File doesn't exist? + if (!file_exists($filename)) + { + // If we're lucky it might just be in a different folder. + if (!empty($modSettings['currentAttachmentUploadDir'])) + { + // Get the attachment name with out the folder. + $attachment_name = !empty($row['file_hash']) ? $row['id_attach'] . '_' . $row['file_hash'] : getLegacyAttachmentFilename($row['filename'], $row['id_attach'], null, true); + + if (!is_array($modSettings['attachmentUploadDir'])) + $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); + + // Loop through the other folders. + foreach ($modSettings['attachmentUploadDir'] as $id => $dir) + if (file_exists($dir . '/' . $attachment_name)) + { + $context['repair_errors']['wrong_folder']++; + $errors_found[] = 'wrong_folder'; + + // Are we going to fix this now? + if ($fix_errors && in_array('wrong_folder', $to_fix)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}attachments + SET id_folder = {int:new_folder} + WHERE id_attach = {int:id_attach}', + array( + 'new_folder' => $id, + 'id_attach' => $row['id_attach'], + ) + ); + + continue 2; + } + } + + $to_remove[] = $row['id_attach']; + $context['repair_errors']['file_missing_on_disk']++; + $errors_found[] = 'file_missing_on_disk'; + } + elseif (filesize($filename) == 0) + { + $context['repair_errors']['file_size_of_zero']++; + $errors_found[] = 'file_size_of_zero'; + + // Fixing? + if ($fix_errors && in_array('file_size_of_zero', $to_fix)) + { + $to_remove[] = $row['id_attach']; + @unlink($filename); + } + } + elseif (filesize($filename) != $row['size']) + { + $context['repair_errors']['file_wrong_size']++; + $errors_found[] = 'file_wrong_size'; + + // Fix it here? + if ($fix_errors && in_array('file_wrong_size', $to_fix)) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}attachments + SET size = {int:filesize} + WHERE id_attach = {int:id_attach}', + array( + 'filesize' => filesize($filename), + 'id_attach' => $row['id_attach'], + ) + ); + } + } + } + + if (in_array('file_missing_on_disk', $errors_found)) + $to_fix[] = 'file_missing_on_disk'; + if (in_array('file_size_of_zero', $errors_found)) + $to_fix[] = 'file_size_of_zero'; + if (in_array('file_wrong_size', $errors_found)) + $to_fix[] = 'file_wrong_size'; + if (in_array('wrong_folder', $errors_found)) + $to_fix[] = 'wrong_folder'; + $smcFunc['db_free_result']($result); + + // Do we need to delete what we have? + if ($fix_errors && !empty($to_remove)) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}attachments + WHERE id_attach IN ({array_int:to_remove})', + array( + 'to_remove' => $to_remove, + ) + ); + $smcFunc['db_query']('', ' + UPDATE {db_prefix}attachments + SET id_thumb = {int:no_thumb} + WHERE id_thumb IN ({array_int:to_remove})', + array( + 'to_remove' => $to_remove, + 'no_thumb' => 0, + ) + ); + } + + pauseAttachmentMaintenance($to_fix, $thumbnails); + } + + $_GET['step'] = 3; + $_GET['substep'] = 0; + pauseAttachmentMaintenance($to_fix); + } + + // Get avatars with no members associated with them. + if ($_GET['step'] <= 3) + { + $result = $smcFunc['db_query']('', ' + SELECT MAX(id_attach) + FROM {db_prefix}attachments', + array( + ) + ); + list ($thumbnails) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + for (; $_GET['substep'] < $thumbnails; $_GET['substep'] += 500) + { + $to_remove = array(); + + $result = $smcFunc['db_query']('', ' + SELECT a.id_attach, a.id_folder, a.filename, a.file_hash, a.attachment_type + FROM {db_prefix}attachments AS a + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = a.id_member) + WHERE a.id_attach BETWEEN {int:substep} AND {int:substep} + 499 + AND a.id_member != {int:no_member} + AND a.id_msg = {int:no_msg} + AND mem.id_member IS NULL', + array( + 'no_member' => 0, + 'no_msg' => 0, + 'substep' => $_GET['substep'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $to_remove[] = $row['id_attach']; + $context['repair_errors']['avatar_no_member']++; + + // If we are repairing remove the file from disk now. + if ($fix_errors && in_array('avatar_no_member', $to_fix)) + { + if ($row['attachment_type'] == 1) + $filename = $modSettings['custom_avatar_dir'] . '/' . $row['filename']; + else + $filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']); + @unlink($filename); + } + } + if ($smcFunc['db_num_rows']($result) != 0) + $to_fix[] = 'avatar_no_member'; + $smcFunc['db_free_result']($result); + + // Do we need to delete what we have? + if ($fix_errors && !empty($to_remove) && in_array('avatar_no_member', $to_fix)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}attachments + WHERE id_attach IN ({array_int:to_remove}) + AND id_member != {int:no_member} + AND id_msg = {int:no_msg}', + array( + 'to_remove' => $to_remove, + 'no_member' => 0, + 'no_msg' => 0, + ) + ); + + pauseAttachmentMaintenance($to_fix, $thumbnails); + } + + $_GET['step'] = 4; + $_GET['substep'] = 0; + pauseAttachmentMaintenance($to_fix); + } + + // What about attachments, who are missing a message :'( + if ($_GET['step'] <= 4) + { + $result = $smcFunc['db_query']('', ' + SELECT MAX(id_attach) + FROM {db_prefix}attachments', + array( + ) + ); + list ($thumbnails) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + for (; $_GET['substep'] < $thumbnails; $_GET['substep'] += 500) + { + $to_remove = array(); + + $result = $smcFunc['db_query']('', ' + SELECT a.id_attach, a.id_folder, a.filename, a.file_hash + FROM {db_prefix}attachments AS a + LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg) + WHERE a.id_attach BETWEEN {int:substep} AND {int:substep} + 499 + AND a.id_member = {int:no_member} + AND a.id_msg != {int:no_msg} + AND m.id_msg IS NULL', + array( + 'no_member' => 0, + 'no_msg' => 0, + 'substep' => $_GET['substep'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $to_remove[] = $row['id_attach']; + $context['repair_errors']['attachment_no_msg']++; + + // If we are repairing remove the file from disk now. + if ($fix_errors && in_array('attachment_no_msg', $to_fix)) + { + $filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']); + @unlink($filename); + } + } + if ($smcFunc['db_num_rows']($result) != 0) + $to_fix[] = 'attachment_no_msg'; + $smcFunc['db_free_result']($result); + + // Do we need to delete what we have? + if ($fix_errors && !empty($to_remove) && in_array('attachment_no_msg', $to_fix)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}attachments + WHERE id_attach IN ({array_int:to_remove}) + AND id_member = {int:no_member} + AND id_msg != {int:no_msg}', + array( + 'to_remove' => $to_remove, + 'no_member' => 0, + 'no_msg' => 0, + ) + ); + + pauseAttachmentMaintenance($to_fix, $thumbnails); + } + + $_GET['step'] = 5; + $_GET['substep'] = 0; + pauseAttachmentMaintenance($to_fix); + } + + // Got here we must be doing well - just the template! :D + $context['page_title'] = $txt['repair_attachments']; + $context[$context['admin_menu_name']]['current_subsection'] = 'maintenance'; + $context['sub_template'] = 'attachment_repair'; + + // What stage are we at? + $context['completed'] = $fix_errors ? true : false; + $context['errors_found'] = !empty($to_fix) ? true : false; + +} + +function pauseAttachmentMaintenance($to_fix, $max_substep = 0) +{ + global $context, $txt, $time_start; + + // Try get more time... + @set_time_limit(600); + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + // Have we already used our maximum time? + if (time() - array_sum(explode(' ', $time_start)) < 3) + return; + + $context['continue_get_data'] = '?action=admin;area=manageattachments;sa=repair' . (isset($_GET['fixErrors']) ? ';fixErrors' : '') . ';step=' . $_GET['step'] . ';substep=' . $_GET['substep'] . ';' . $context['session_var'] . '=' . $context['session_id']; + $context['page_title'] = $txt['not_done_title']; + $context['continue_post_data'] = ''; + $context['continue_countdown'] = '2'; + $context['sub_template'] = 'not_done'; + + // Specific stuff to not break this template! + $context[$context['admin_menu_name']]['current_subsection'] = 'maintenance'; + + // Change these two if more steps are added! + if (empty($max_substep)) + $context['continue_percent'] = round(($_GET['step'] * 100) / 25); + else + $context['continue_percent'] = round(($_GET['step'] * 100 + ($_GET['substep'] * 100) / $max_substep) / 25); + + // Never more than 100%! + $context['continue_percent'] = min($context['continue_percent'], 100); + + $_SESSION['attachments_to_fix'] = $to_fix; + $_SESSION['attachments_to_fix2'] = $context['repair_errors']; + + obExit(); +} + +// Called from a mouse click, works out what we want to do with attachments and actions it. +function ApproveAttach() +{ + global $smcFunc; + + // Security is our primary concern... + checkSession('get'); + + // If it approve or delete? + $is_approve = !isset($_GET['sa']) || $_GET['sa'] != 'reject' ? true : false; + + $attachments = array(); + // If we are approving all ID's in a message , get the ID's. + if ($_GET['sa'] == 'all' && !empty($_GET['mid'])) + { + $id_msg = (int) $_GET['mid']; + + $request = $smcFunc['db_query']('', ' + SELECT id_attach + FROM {db_prefix}attachments + WHERE id_msg = {int:id_msg} + AND approved = {int:is_approved} + AND attachment_type = {int:attachment_type}', + array( + 'id_msg' => $id_msg, + 'is_approved' => 0, + 'attachment_type' => 0, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $attachments[] = $row['id_attach']; + $smcFunc['db_free_result']($request); + } + elseif (!empty($_GET['aid'])) + $attachments[] = (int) $_GET['aid']; + + if (empty($attachments)) + fatal_lang_error('no_access', false); + + // Now we have some ID's cleaned and ready to approve, but first - let's check we have permission! + $allowed_boards = boardsAllowedTo('approve_posts'); + + // Validate the attachments exist and are the right approval state. + $request = $smcFunc['db_query']('', ' + SELECT a.id_attach, m.id_board, m.id_msg, m.id_topic + FROM {db_prefix}attachments AS a + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg) + WHERE a.id_attach IN ({array_int:attachments}) + AND a.attachment_type = {int:attachment_type} + AND a.approved = {int:is_approved}', + array( + 'attachments' => $attachments, + 'attachment_type' => 0, + 'is_approved' => 0, + ) + ); + $attachments = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // We can only add it if we can approve in this board! + if ($allowed_boards = array(0) || in_array($row['id_board'], $allowed_boards)) + { + $attachments[] = $row['id_attach']; + + // Also come up witht he redirection URL. + $redirect = 'topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg']; + } + } + $smcFunc['db_free_result']($request); + + if (empty($attachments)) + fatal_lang_error('no_access', false); + + // Finally, we are there. Follow through! + if ($is_approve) + ApproveAttachments($attachments); + else + removeAttachments(array('id_attach' => $attachments)); + + // Return to the topic.... + redirectexit($redirect); +} + +// Approve an attachment, or maybe even more - no permission check! +function ApproveAttachments($attachments) +{ + global $smcFunc; + + if (empty($attachments)) + return 0; + + // For safety, check for thumbnails... + $request = $smcFunc['db_query']('', ' + SELECT + a.id_attach, a.id_member, IFNULL(thumb.id_attach, 0) AS id_thumb + FROM {db_prefix}attachments AS a + LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb) + WHERE a.id_attach IN ({array_int:attachments}) + AND a.attachment_type = {int:attachment_type}', + array( + 'attachments' => $attachments, + 'attachment_type' => 0, + ) + ); + $attachments = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Update the thumbnail too... + if (!empty($row['id_thumb'])) + $attachments[] = $row['id_thumb']; + + $attachments[] = $row['id_attach']; + } + $smcFunc['db_free_result']($request); + + // Approving an attachment is not hard - it's easy. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}attachments + SET approved = {int:is_approved} + WHERE id_attach IN ({array_int:attachments})', + array( + 'attachments' => $attachments, + 'is_approved' => 1, + ) + ); + + // Remove from the approval queue. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}approval_queue + WHERE id_attach IN ({array_int:attachments})', + array( + 'attachments' => $attachments, + ) + ); +} + +function ManageAttachmentPaths() +{ + global $modSettings, $scripturl, $context, $txt, $sourcedir, $smcFunc; + + // Saving? + if (isset($_REQUEST['save'])) + { + checkSession(); + + $new_dirs = array(); + foreach ($_POST['dirs'] as $id => $path) + { + $id = (int) $id; + if ($id < 1) + continue; + + if (empty($path)) + { + // Let's not try to delete a path with files in it. + $request = $smcFunc['db_query']('', ' + SELECT COUNT(id_attach) AS num_attach + FROM {db_prefix}attachments + WHERE id_folder = {int:id_folder}', + array( + 'id_folder' => (int) $id, + ) + ); + + list ($num_attach) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // It's safe to delete. + if ($num_attach == 0) + continue; + } + + $new_dirs[$id] = $path; + } + + // We need to make sure the current directory is right. + $_POST['current_dir'] = (int) $_POST['current_dir']; + if (empty($_POST['current_dir']) || empty($new_dirs[$_POST['current_dir']])) + fatal_lang_error('attach_path_current_bad', false); + + // Going back to just one path? + if (count($new_dirs) == 1) + { + // We might need to reset the paths. This loop will just loop through once. + foreach ($new_dirs as $id => $dir) + { + if ($id != 1) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}attachments + SET id_folder = {int:default_folder} + WHERE id_folder = {int:current_folder}', + array( + 'default_folder' => 1, + 'current_folder' => $id, + ) + ); + + updateSettings(array( + 'currentAttachmentUploadDir' => 0, + 'attachmentUploadDir' => $dir, + )); + } + } + else + // Save it to the database. + updateSettings(array( + 'currentAttachmentUploadDir' => $_POST['current_dir'], + 'attachmentUploadDir' => serialize($new_dirs), + )); + } + + // Are they here for the first time? + if (empty($modSettings['currentAttachmentUploadDir'])) + { + $modSettings['attachmentUploadDir'] = array( + 1 => $modSettings['attachmentUploadDir'] + ); + $modSettings['currentAttachmentUploadDir'] = 1; + } + // Otherwise just load up their attachment paths. + else + $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); + + $listOptions = array( + 'id' => 'attach_paths', + 'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'], + 'title' => $txt['attach_paths'], + 'get_items' => array( + 'function' => 'list_getAttachDirs', + ), + 'columns' => array( + 'current_dir' => array( + 'header' => array( + 'value' => $txt['attach_current_dir'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + return \'\'; + '), + 'style' => 'text-align: center; width: 15%;', + ), + ), + 'path' => array( + 'header' => array( + 'value' => $txt['attach_path'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + return \'\'; + '), + 'style' => 'text-align: center; width: 30%;', + ), + ), + 'current_size' => array( + 'header' => array( + 'value' => $txt['attach_current_size'], + ), + 'data' => array( + 'db' => 'current_size', + 'style' => 'text-align: center; width: 15%;', + ), + ), + 'num_files' => array( + 'header' => array( + 'value' => $txt['attach_num_files'], + ), + 'data' => array( + 'db' => 'num_files', + 'style' => 'text-align: center; width: 15%;', + ), + ), + 'status' => array( + 'header' => array( + 'value' => $txt['attach_dir_status'], + ), + 'data' => array( + 'db' => 'status', + 'style' => 'text-align: center; width: 25%;', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'], + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => ' ', + 'style' => 'text-align: right;', + ), + ), + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + // Fix up our template. + $context[$context['admin_menu_name']]['current_subsection'] = 'attachments'; + $context['page_title'] = $txt['attach_path_manage']; + $context['sub_template'] = 'attachment_paths'; +} + +// Prepare the actual attachment directories to be displayed in the list. +function list_getAttachDirs() +{ + global $smcFunc, $modSettings, $context, $txt; + + // The dirs should already have been unserialized but just in case... + if (!is_array($modSettings['attachmentUploadDir'])) + $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); + + $request = $smcFunc['db_query']('', ' + SELECT id_folder, COUNT(id_attach) AS num_attach + FROM {db_prefix}attachments' . (empty($modSettings['custom_avatar_enabled']) ? '' : ' + WHERE attachment_type != {int:type_avatar}') . ' + GROUP BY id_folder', + array( + 'type_avatar' => 1, + ) + ); + + $expected_files = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $expected_files[$row['id_folder']] = $row['num_attach']; + $smcFunc['db_free_result']($request); + + $attachdirs = array(); + foreach ($modSettings['attachmentUploadDir'] as $id => $dir) + { + // If there aren't any attachments in this directory this won't exist. + if (!isset($expected_files[$id])) + $expected_files[$id] = 0; + + // Check if the directory is doing okay. + list ($status, $error, $size) = attachDirStatus($dir, $expected_files[$id]); + + $attachdirs[] = array( + 'id' => $id, + 'current' => $id == $modSettings['currentAttachmentUploadDir'], + 'path' => $dir, + 'current_size' => $size, + 'num_files' => $expected_files[$id], + 'status' => ($error ? '' : '') . sprintf($txt['attach_dir_' . $status], $context['session_id'], $context['session_var']) . ($error ? '' : ''), + ); + } + + // Just stick a new directory on at the bottom. + if (isset($_REQUEST['new_path'])) + $attachdirs[] = array( + 'id' => max(array_merge(array_keys($expected_files), array_keys($modSettings['attachmentUploadDir']))) + 1, + 'current' => false, + 'path' => '', + 'current_size' => '', + 'num_files' => '', + 'status' => '', + ); + + return $attachdirs; +} + +// Checks the status of an attachment directory and returns an array of the status key, if that status key signifies an error, and the folder size. +function attachDirStatus($dir, $expected_files) +{ + if (!is_dir($dir)) + return array('does_not_exist', true, ''); + elseif (!is_writable($dir)) + return array('not_writable', true, ''); + + // Everything is okay so far, start to scan through the directory. + $dir_size = 0; + $num_files = 0; + $dir_handle = dir($dir); + while ($file = $dir_handle->read()) + { + // Now do we have a real file here? + if (in_array($file, array('.', '..', '.htaccess', 'index.php'))) + continue; + + $dir_size += filesize($dir . '/' . $file); + $num_files++; + } + $dir_handle->close(); + + $dir_size = round($dir_size / 1024, 2); + + if ($num_files < $expected_files) + return array('files_missing', true, $dir_size); + // Empty? + elseif ($expected_files == 0) + return array('unused', false, $dir_size); + // All good! + else + return array('ok', false, $dir_size); +} + +?> \ No newline at end of file diff --git a/Sources/ManageBans.php b/Sources/ManageBans.php new file mode 100644 index 0000000..48e38d9 --- /dev/null +++ b/Sources/ManageBans.php @@ -0,0 +1,1730 @@ += 10: a member is banned. +*/ + +// Ban center. +function Ban() +{ + global $context, $txt, $scripturl; + + isAllowedTo('manage_bans'); + + loadTemplate('ManageBans'); + + $subActions = array( + 'add' => 'BanEdit', + 'browse' => 'BanBrowseTriggers', + 'edittrigger' => 'BanEditTrigger', + 'edit' => 'BanEdit', + 'list' => 'BanList', + 'log' => 'BanLog', + ); + + // Default the sub-action to 'view ban list'. + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'list'; + + $context['page_title'] = $txt['ban_title']; + $context['sub_action'] = $_REQUEST['sa']; + + // Tabs for browsing the different ban functions. + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['ban_title'], + 'help' => 'ban_members', + 'description' => $txt['ban_description'], + 'tabs' => array( + 'list' => array( + 'description' => $txt['ban_description'], + 'href' => $scripturl . '?action=admin;area=ban;sa=list', + 'is_selected' => $_REQUEST['sa'] == 'list' || $_REQUEST['sa'] == 'edit' || $_REQUEST['sa'] == 'edittrigger', + ), + 'add' => array( + 'description' => $txt['ban_description'], + 'href' => $scripturl . '?action=admin;area=ban;sa=add', + 'is_selected' => $_REQUEST['sa'] == 'add', + ), + 'browse' => array( + 'description' => $txt['ban_trigger_browse_description'], + 'href' => $scripturl . '?action=admin;area=ban;sa=browse', + 'is_selected' => $_REQUEST['sa'] == 'browse', + ), + 'log' => array( + 'description' => $txt['ban_log_description'], + 'href' => $scripturl . '?action=admin;area=ban;sa=log', + 'is_selected' => $_REQUEST['sa'] == 'log', + 'is_last' => true, + ), + ), + ); + + // Call the right function for this sub-acton. + $subActions[$_REQUEST['sa']](); +} + +// List all the bans. +function BanList() +{ + global $txt, $context, $ban_request, $ban_counts, $scripturl; + global $user_info, $smcFunc, $sourcedir; + + // User pressed the 'remove selection button'. + if (!empty($_POST['removeBans']) && !empty($_POST['remove']) && is_array($_POST['remove'])) + { + checkSession(); + + // Make sure every entry is a proper integer. + foreach ($_POST['remove'] as $index => $ban_id) + $_POST['remove'][(int) $index] = (int) $ban_id; + + // Unban them all! + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}ban_groups + WHERE id_ban_group IN ({array_int:ban_list})', + array( + 'ban_list' => $_POST['remove'], + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}ban_items + WHERE id_ban_group IN ({array_int:ban_list})', + array( + 'ban_list' => $_POST['remove'], + ) + ); + + // No more caching this ban! + updateSettings(array('banLastUpdated' => time())); + + // Some members might be unbanned now. Update the members table. + updateBanMembers(); + } + + // Create a date string so we don't overload them with date info. + if (preg_match('~%[AaBbCcDdeGghjmuYy](?:[^%]*%[AaBbCcDdeGghjmuYy])*~', $user_info['time_format'], $matches) == 0 || empty($matches[0])) + $context['ban_time_format'] = $user_info['time_format']; + else + $context['ban_time_format'] = $matches[0]; + + $listOptions = array( + 'id' => 'ban_list', + 'items_per_page' => 20, + 'base_href' => $scripturl . '?action=admin;area=ban;sa=list', + 'default_sort_col' => 'added', + 'default_sort_dir' => 'desc', + 'get_items' => array( + 'function' => 'list_getBans', + ), + 'get_count' => array( + 'function' => 'list_getNumBans', + ), + 'no_items_label' => $txt['ban_no_entries'], + 'columns' => array( + 'name' => array( + 'header' => array( + 'value' => $txt['ban_name'], + ), + 'data' => array( + 'db' => 'name', + ), + 'sort' => array( + 'default' => 'bg.name', + 'reverse' => 'bg.name DESC', + ), + ), + 'notes' => array( + 'header' => array( + 'value' => $txt['ban_notes'], + ), + 'data' => array( + 'db' => 'notes', + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'LENGTH(bg.notes) > 0 DESC, bg.notes', + 'reverse' => 'LENGTH(bg.notes) > 0, bg.notes DESC', + ), + ), + 'reason' => array( + 'header' => array( + 'value' => $txt['ban_reason'], + ), + 'data' => array( + 'db' => 'reason', + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'LENGTH(bg.reason) > 0 DESC, bg.reason', + 'reverse' => 'LENGTH(bg.reason) > 0, bg.reason DESC', + ), + ), + 'added' => array( + 'header' => array( + 'value' => $txt['ban_added'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $context; + + return timeformat($rowData[\'ban_time\'], empty($context[\'ban_time_format\']) ? true : $context[\'ban_time_format\']); + '), + ), + 'sort' => array( + 'default' => 'bg.ban_time', + 'reverse' => 'bg.ban_time DESC', + ), + ), + 'expires' => array( + 'header' => array( + 'value' => $txt['ban_expires'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt; + + // This ban never expires...whahaha. + if ($rowData[\'expire_time\'] === null) + return $txt[\'never\']; + + // This ban has already expired. + elseif ($rowData[\'expire_time\'] < time()) + return sprintf(\'%1$s\', $txt[\'ban_expired\']); + + // Still need to wait a few days for this ban to expire. + else + return sprintf(\'%1$d %2$s\', ceil(($rowData[\'expire_time\'] - time()) / (60 * 60 * 24)), $txt[\'ban_days\']); + '), + ), + 'sort' => array( + 'default' => 'IFNULL(bg.expire_time, 1=1) DESC, bg.expire_time DESC', + 'reverse' => 'IFNULL(bg.expire_time, 1=1), bg.expire_time', + ), + ), + 'num_triggers' => array( + 'header' => array( + 'value' => $txt['ban_triggers'], + ), + 'data' => array( + 'db' => 'num_triggers', + 'style' => 'text-align: center;', + ), + 'sort' => array( + 'default' => 'num_triggers DESC', + 'reverse' => 'num_triggers', + ), + ), + 'actions' => array( + 'header' => array( + 'value' => $txt['ban_actions'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '' . $txt['modify'] . '', + 'params' => array( + 'id_ban_group' => false, + ), + ), + 'style' => 'text-align: center;', + ), + ), + 'check' => array( + 'header' => array( + 'value' => '', + ), + 'data' => array( + 'sprintf' => array( + 'format' => '', + 'params' => array( + 'id_ban_group' => false, + ), + ), + 'style' => 'text-align: center', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=ban;sa=list', + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => '', + 'style' => 'text-align: right;', + ), + ), + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'ban_list'; +} + +function list_getBans($start, $items_per_page, $sort) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT bg.id_ban_group, bg.name, bg.ban_time, bg.expire_time, bg.reason, bg.notes, COUNT(bi.id_ban) AS num_triggers + FROM {db_prefix}ban_groups AS bg + LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_ban_group = bg.id_ban_group) + GROUP BY bg.id_ban_group, bg.name, bg.ban_time, bg.expire_time, bg.reason, bg.notes + ORDER BY {raw:sort} + LIMIT {int:offset}, {int:limit}', + array( + 'sort' => $sort, + 'offset' => $start, + 'limit' => $items_per_page, + ) + ); + $bans = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $bans[] = $row; + + $smcFunc['db_free_result']($request); + + return $bans; +} + +function list_getNumBans() +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS num_bans + FROM {db_prefix}ban_groups', + array( + ) + ); + list ($numBans) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $numBans; +} + +function BanEdit() +{ + global $txt, $modSettings, $context, $ban_request, $scripturl, $smcFunc; + + $_REQUEST['bg'] = empty($_REQUEST['bg']) ? 0 : (int) $_REQUEST['bg']; + + // Adding or editing a ban trigger? + if (!empty($_POST['add_new_trigger']) || !empty($_POST['edit_trigger'])) + { + checkSession(); + + $newBan = !empty($_POST['add_new_trigger']); + $values = array( + 'id_ban_group' => $_REQUEST['bg'], + 'hostname' => '', + 'email_address' => '', + 'id_member' => 0, + 'ip_low1' => 0, + 'ip_high1' => 0, + 'ip_low2' => 0, + 'ip_high2' => 0, + 'ip_low3' => 0, + 'ip_high3' => 0, + 'ip_low4' => 0, + 'ip_high4' => 0, + ); + + // Preset all values that are required. + if ($newBan) + { + $insertKeys = array( + 'id_ban_group' => 'int', + 'hostname' => 'string', + 'email_address' => 'string', + 'id_member' => 'int', + 'ip_low1' => 'int', + 'ip_high1' => 'int', + 'ip_low2' => 'int', + 'ip_high2' => 'int', + 'ip_low3' => 'int', + 'ip_high3' => 'int', + 'ip_low4' => 'int', + 'ip_high4' => 'int', + ); + } + else + $updateString = ' + hostname = {string:hostname}, email_address = {string:email_address}, id_member = {int:id_member}, + ip_low1 = {int:ip_low1}, ip_high1 = {int:ip_high1}, + ip_low2 = {int:ip_low2}, ip_high2 = {int:ip_high2}, + ip_low3 = {int:ip_low3}, ip_high3 = {int:ip_high3}, + ip_low4 = {int:ip_low4}, ip_high4 = {int:ip_high4}'; + + if ($_POST['bantype'] == 'ip_ban') + { + $ip = trim($_POST['ip']); + $ip_parts = ip2range($ip); + $ip_check = checkExistingTriggerIP($ip_parts, $ip); + if (!$ip_check) + fatal_lang_error('invalid_ip', false); + $values = array_merge($values, $ip_check); + + $modlogInfo['ip_range'] = $_POST['ip']; + } + elseif ($_POST['bantype'] == 'hostname_ban') + { + if (preg_match('/[^\w.\-*]/', $_POST['hostname']) == 1) + fatal_lang_error('invalid_hostname', false); + + // Replace the * wildcard by a MySQL compatible wildcard %. + $_POST['hostname'] = str_replace('*', '%', $_POST['hostname']); + + $values['hostname'] = $_POST['hostname']; + + $modlogInfo['hostname'] = $_POST['hostname']; + } + elseif ($_POST['bantype'] == 'email_ban') + { + if (preg_match('/[^\w.\-\+*@]/', $_POST['email']) == 1) + fatal_lang_error('invalid_email', false); + $_POST['email'] = strtolower(str_replace('*', '%', $_POST['email'])); + + // Check the user is not banning an admin. + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0) + AND email_address LIKE {string:email} + LIMIT 1', + array( + 'admin_group' => 1, + 'email' => $_POST['email'], + ) + ); + if ($smcFunc['db_num_rows']($request) != 0) + fatal_lang_error('no_ban_admin', 'critical'); + $smcFunc['db_free_result']($request); + + $values['email_address'] = $_POST['email']; + + $modlogInfo['email'] = $_POST['email']; + } + elseif ($_POST['bantype'] == 'user_ban') + { + $_POST['user'] = preg_replace('~&#(\d{4,5}|[2-9]\d{2,4}|1[2-9]\d);~', '&#$1;', $smcFunc['htmlspecialchars']($_POST['user'], ENT_QUOTES)); + + $request = $smcFunc['db_query']('', ' + SELECT id_member, (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0) AS isAdmin + FROM {db_prefix}members + WHERE member_name = {string:user_name} OR real_name = {string:user_name} + LIMIT 1', + array( + 'admin_group' => 1, + 'user_name' => $_POST['user'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('invalid_username', false); + list ($memberid, $isAdmin) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if ($isAdmin && $isAdmin != 'f') + fatal_lang_error('no_ban_admin', 'critical'); + + $values['id_member'] = $memberid; + + $modlogInfo['member'] = $memberid; + } + else + fatal_lang_error('no_bantype_selected', false); + + if ($newBan) + $smcFunc['db_insert']('', + '{db_prefix}ban_items', + $insertKeys, + $values, + array('id_ban') + ); + else + $smcFunc['db_query']('', ' + UPDATE {db_prefix}ban_items + SET ' . $updateString . ' + WHERE id_ban = {int:ban_item} + AND id_ban_group = {int:id_ban_group}', + array_merge($values, array( + 'ban_item' => (int) $_REQUEST['bi'], + )) + ); + + // Log the addion of the ban entry into the moderation log. + logAction('ban', $modlogInfo + array( + 'new' => $newBan, + 'type' => $_POST['bantype'], + )); + + // Register the last modified date. + updateSettings(array('banLastUpdated' => time())); + + // Update the member table to represent the new ban situation. + updateBanMembers(); + } + + // The user pressed 'Remove selected ban entries'. + elseif (!empty($_POST['remove_selection']) && !empty($_POST['ban_items']) && is_array($_POST['ban_items'])) + { + checkSession(); + + // Making sure every deleted ban item is an integer. + foreach ($_POST['ban_items'] as $key => $value) + $_POST['ban_items'][$key] = (int) $value; + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}ban_items + WHERE id_ban IN ({array_int:ban_list}) + AND id_ban_group = {int:ban_group}', + array( + 'ban_list' => $_POST['ban_items'], + 'ban_group' => $_REQUEST['bg'], + ) + ); + + // It changed, let the settings and the member table know. + updateSettings(array('banLastUpdated' => time())); + updateBanMembers(); + } + + // Modify OR add a ban. + elseif (!empty($_POST['modify_ban']) || !empty($_POST['add_ban'])) + { + checkSession(); + + $addBan = !empty($_POST['add_ban']); + if (empty($_POST['ban_name'])) + fatal_lang_error('ban_name_empty', false); + + // Let's not allow HTML in ban names, it's more evil than beneficial. + $_POST['ban_name'] = $smcFunc['htmlspecialchars']($_POST['ban_name'], ENT_QUOTES); + + // Check whether a ban with this name already exists. + $request = $smcFunc['db_query']('', ' + SELECT id_ban_group + FROM {db_prefix}ban_groups + WHERE name = {string:new_ban_name}' . ($addBan ? '' : ' + AND id_ban_group != {int:ban_group}') . ' + LIMIT 1', + array( + 'ban_group' => $_REQUEST['bg'], + 'new_ban_name' => $_POST['ban_name'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 1) + fatal_lang_error('ban_name_exists', false, array($_POST['ban_name'])); + $smcFunc['db_free_result']($request); + + $_POST['reason'] = $smcFunc['htmlspecialchars']($_POST['reason'], ENT_QUOTES); + $_POST['notes'] = $smcFunc['htmlspecialchars']($_POST['notes'], ENT_QUOTES); + $_POST['notes'] = str_replace(array("\r", "\n", ' '), array('', '
', '  '), $_POST['notes']); + $_POST['expiration'] = $_POST['expiration'] == 'never' ? 'NULL' : ($_POST['expiration'] == 'expired' ? '0' : ($_POST['expire_date'] != $_POST['old_expire'] ? time() + 24 * 60 * 60 * (int) $_POST['expire_date'] : 'expire_time')); + $_POST['full_ban'] = empty($_POST['full_ban']) ? '0' : '1'; + $_POST['cannot_post'] = !empty($_POST['full_ban']) || empty($_POST['cannot_post']) ? '0' : '1'; + $_POST['cannot_register'] = !empty($_POST['full_ban']) || empty($_POST['cannot_register']) ? '0' : '1'; + $_POST['cannot_login'] = !empty($_POST['full_ban']) || empty($_POST['cannot_login']) ? '0' : '1'; + + if ($addBan) + { + // Adding some ban triggers? + if ($addBan && !empty($_POST['ban_suggestion']) && is_array($_POST['ban_suggestion'])) + { + $ban_triggers = array(); + $ban_logs = array(); + if (in_array('main_ip', $_POST['ban_suggestion']) && !empty($_POST['main_ip'])) + { + $ip = trim($_POST['main_ip']); + $ip_parts = ip2range($ip); + if (!checkExistingTriggerIP($ip_parts, $ip)) + fatal_lang_error('invalid_ip', false); + + $ban_triggers[] = array( + $ip_parts[0]['low'], + $ip_parts[0]['high'], + $ip_parts[1]['low'], + $ip_parts[1]['high'], + $ip_parts[2]['low'], + $ip_parts[2]['high'], + $ip_parts[3]['low'], + $ip_parts[3]['high'], + '', + '', + 0, + ); + + $ban_logs[] = array( + 'ip_range' => $_POST['main_ip'], + ); + } + if (in_array('hostname', $_POST['ban_suggestion']) && !empty($_POST['hostname'])) + { + if (preg_match('/[^\w.\-*]/', $_POST['hostname']) == 1) + fatal_lang_error('invalid_hostname', false); + + // Replace the * wildcard by a MySQL wildcard %. + $_POST['hostname'] = str_replace('*', '%', $_POST['hostname']); + + $ban_triggers[] = array( + 0, 0, 0, 0, 0, 0, 0, 0, + substr($_POST['hostname'], 0, 255), + '', + 0, + ); + $ban_logs[] = array( + 'hostname' => $_POST['hostname'], + ); + } + if (in_array('email', $_POST['ban_suggestion']) && !empty($_POST['email'])) + { + if (preg_match('/[^\w.\-\+*@]/', $_POST['email']) == 1) + fatal_lang_error('invalid_email', false); + $_POST['email'] = strtolower(str_replace('*', '%', $_POST['email'])); + + $ban_triggers[] = array( + 0, 0, 0, 0, 0, 0, 0, 0, + '', + substr($_POST['email'], 0, 255), + 0, + ); + $ban_logs[] = array( + 'email' => $_POST['email'], + ); + } + if (in_array('user', $_POST['ban_suggestion']) && (!empty($_POST['bannedUser']) || !empty($_POST['user']))) + { + // We got a username, let's find its ID. + if (empty($_POST['bannedUser'])) + { + $_POST['user'] = preg_replace('~&#(\d{4,5}|[2-9]\d{2,4}|1[2-9]\d);~', '&#$1;', $smcFunc['htmlspecialchars']($_POST['user'], ENT_QUOTES)); + + $request = $smcFunc['db_query']('', ' + SELECT id_member, (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0) AS isAdmin + FROM {db_prefix}members + WHERE member_name = {string:username} OR real_name = {string:username} + LIMIT 1', + array( + 'admin_group' => 1, + 'username' => $_POST['user'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('invalid_username', false); + list ($_POST['bannedUser'], $isAdmin) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if ($isAdmin && $isAdmin != 'f') + fatal_lang_error('no_ban_admin', 'critical'); + } + + $ban_triggers[] = array( + 0, 0, 0, 0, 0, 0, 0, 0, + '', + '', + (int) $_POST['bannedUser'], + ); + $ban_logs[] = array( + 'member' => $_POST['bannedUser'], + ); + } + + if (!empty($_POST['ban_suggestion']['ips']) && is_array($_POST['ban_suggestion']['ips'])) + { + $_POST['ban_suggestion']['ips'] = array_unique($_POST['ban_suggestion']['ips']); + + // Don't add the main IP again. + if (in_array('main_ip', $_POST['ban_suggestion'])) + $_POST['ban_suggestion']['ips'] = array_diff($_POST['ban_suggestion']['ips'], array($_POST['main_ip'])); + + foreach ($_POST['ban_suggestion']['ips'] as $ip) + { + $ip_parts = ip2range($ip); + + // They should be alright, but just to be sure... + if (count($ip_parts) != 4) + fatal_lang_error('invalid_ip', false); + + $ban_triggers[] = array( + $ip_parts[0]['low'], + $ip_parts[0]['high'], + $ip_parts[1]['low'], + $ip_parts[1]['high'], + $ip_parts[2]['low'], + $ip_parts[2]['high'], + $ip_parts[3]['low'], + $ip_parts[3]['high'], + '', + '', + 0, + ); + $ban_logs[] = array( + 'ip_range' => $ip, + ); + } + } + } + + // Yes yes, we're ready to add now. + $smcFunc['db_insert']('', + '{db_prefix}ban_groups', + array( + 'name' => 'string-20', 'ban_time' => 'int', 'expire_time' => 'raw', 'cannot_access' => 'int', 'cannot_register' => 'int', + 'cannot_post' => 'int', 'cannot_login' => 'int', 'reason' => 'string-255', 'notes' => 'string-65534', + ), + array( + $_POST['ban_name'], time(), $_POST['expiration'], $_POST['full_ban'], $_POST['cannot_register'], + $_POST['cannot_post'], $_POST['cannot_login'], $_POST['reason'], $_POST['notes'], + ), + array('id_ban_group') + ); + $_REQUEST['bg'] = $smcFunc['db_insert_id']('{db_prefix}ban_groups', 'id_ban_group'); + + // Now that the ban group is added, add some triggers as well. + if (!empty($ban_triggers) && !empty($_REQUEST['bg'])) + { + // Put in the ban group ID. + foreach ($ban_triggers as $k => $trigger) + array_unshift($ban_triggers[$k], $_REQUEST['bg']); + + // Log what we are doing! + foreach ($ban_logs as $log_details) + logAction('ban', $log_details + array('new' => 1)); + + $smcFunc['db_insert']('', + '{db_prefix}ban_items', + array( + 'id_ban_group' => 'int', 'ip_low1' => 'int', 'ip_high1' => 'int', 'ip_low2' => 'int', 'ip_high2' => 'int', + 'ip_low3' => 'int', 'ip_high3' => 'int', 'ip_low4' => 'int', 'ip_high4' => 'int', 'hostname' => 'string-255', + 'email_address' => 'string-255', 'id_member' => 'int', + ), + $ban_triggers, + array('id_ban') + ); + } + } + else + $smcFunc['db_query']('', ' + UPDATE {db_prefix}ban_groups + SET + name = {string:ban_name}, + reason = {string:reason}, + notes = {string:notes}, + expire_time = {raw:expiration}, + cannot_access = {int:cannot_access}, + cannot_post = {int:cannot_post}, + cannot_register = {int:cannot_register}, + cannot_login = {int:cannot_login} + WHERE id_ban_group = {int:id_ban_group}', + array( + 'expiration' => $_POST['expiration'], + 'cannot_access' => $_POST['full_ban'], + 'cannot_post' => $_POST['cannot_post'], + 'cannot_register' => $_POST['cannot_register'], + 'cannot_login' => $_POST['cannot_login'], + 'id_ban_group' => $_REQUEST['bg'], + 'ban_name' => $_POST['ban_name'], + 'reason' => $_POST['reason'], + 'notes' => $_POST['notes'], + ) + ); + + // No more caching, we have something new here. + updateSettings(array('banLastUpdated' => time())); + updateBanMembers(); + } + + // If we're editing an existing ban, get it from the database. + if (!empty($_REQUEST['bg'])) + { + $context['ban_items'] = array(); + $request = $smcFunc['db_query']('', ' + SELECT + bi.id_ban, bi.hostname, bi.email_address, bi.id_member, bi.hits, + bi.ip_low1, bi.ip_high1, bi.ip_low2, bi.ip_high2, bi.ip_low3, bi.ip_high3, bi.ip_low4, bi.ip_high4, + bg.id_ban_group, bg.name, bg.ban_time, bg.expire_time, bg.reason, bg.notes, bg.cannot_access, bg.cannot_register, bg.cannot_login, bg.cannot_post, + IFNULL(mem.id_member, 0) AS id_member, mem.member_name, mem.real_name + FROM {db_prefix}ban_groups AS bg + LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_ban_group = bg.id_ban_group) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member) + WHERE bg.id_ban_group = {int:current_ban}', + array( + 'current_ban' => $_REQUEST['bg'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('ban_not_found', false); + + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!isset($context['ban'])) + { + $context['ban'] = array( + 'id' => $row['id_ban_group'], + 'name' => $row['name'], + 'expiration' => array( + 'status' => $row['expire_time'] === null ? 'never' : ($row['expire_time'] < time() ? 'expired' : 'still_active_but_we_re_counting_the_days'), + 'days' => $row['expire_time'] > time() ? floor(($row['expire_time'] - time()) / 86400) : 0 + ), + 'reason' => $row['reason'], + 'notes' => $row['notes'], + 'cannot' => array( + 'access' => !empty($row['cannot_access']), + 'post' => !empty($row['cannot_post']), + 'register' => !empty($row['cannot_register']), + 'login' => !empty($row['cannot_login']), + ), + 'is_new' => false, + ); + } + if (!empty($row['id_ban'])) + { + $context['ban_items'][$row['id_ban']] = array( + 'id' => $row['id_ban'], + 'hits' => $row['hits'], + ); + if (!empty($row['ip_high1'])) + { + $context['ban_items'][$row['id_ban']]['type'] = 'ip'; + $context['ban_items'][$row['id_ban']]['ip'] = range2ip(array($row['ip_low1'], $row['ip_low2'], $row['ip_low3'], $row['ip_low4']), array($row['ip_high1'], $row['ip_high2'], $row['ip_high3'], $row['ip_high4'])); + } + elseif (!empty($row['hostname'])) + { + $context['ban_items'][$row['id_ban']]['type'] = 'hostname'; + $context['ban_items'][$row['id_ban']]['hostname'] = str_replace('%', '*', $row['hostname']); + } + elseif (!empty($row['email_address'])) + { + $context['ban_items'][$row['id_ban']]['type'] = 'email'; + $context['ban_items'][$row['id_ban']]['email'] = str_replace('%', '*', $row['email_address']); + } + elseif (!empty($row['id_member'])) + { + $context['ban_items'][$row['id_ban']]['type'] = 'user'; + $context['ban_items'][$row['id_ban']]['user'] = array( + 'id' => $row['id_member'], + 'name' => $row['real_name'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_member'], + 'link' => '' . $row['real_name'] . '', + ); + } + // Invalid ban (member probably doesn't exist anymore). + else + { + unset($context['ban_items'][$row['id_ban']]); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}ban_items + WHERE id_ban = {int:current_ban}', + array( + 'current_ban' => $row['id_ban'], + ) + ); + } + } + } + $smcFunc['db_free_result']($request); + } + // Not an existing one, then it's probably a new one. + else + { + $context['ban'] = array( + 'id' => 0, + 'name' => '', + 'expiration' => array( + 'status' => 'never', + 'days' => 0 + ), + 'reason' => '', + 'notes' => '', + 'ban_days' => 0, + 'cannot' => array( + 'access' => true, + 'post' => false, + 'register' => false, + 'login' => false, + ), + 'is_new' => true, + ); + $context['ban_suggestions'] = array( + 'main_ip' => '', + 'hostname' => '', + 'email' => '', + 'member' => array( + 'id' => 0, + ), + ); + + // Overwrite some of the default form values if a user ID was given. + if (!empty($_REQUEST['u'])) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name, member_ip, email_address + FROM {db_prefix}members + WHERE id_member = {int:current_user} + LIMIT 1', + array( + 'current_user' => (int) $_REQUEST['u'], + ) + ); + if ($smcFunc['db_num_rows']($request) > 0) + list ($context['ban_suggestions']['member']['id'], $context['ban_suggestions']['member']['name'], $context['ban_suggestions']['main_ip'], $context['ban_suggestions']['email']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (!empty($context['ban_suggestions']['member']['id'])) + { + $context['ban_suggestions']['href'] = $scripturl . '?action=profile;u=' . $context['ban_suggestions']['member']['id']; + $context['ban_suggestions']['member']['link'] = '' . $context['ban_suggestions']['member']['name'] . ''; + + // Default the ban name to the name of the banned member. + $context['ban']['name'] = $context['ban_suggestions']['member']['name']; + + // Would be nice if we could also ban the hostname. + if (preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $context['ban_suggestions']['main_ip']) == 1 && empty($modSettings['disableHostnameLookup'])) + $context['ban_suggestions']['hostname'] = host_from_ip($context['ban_suggestions']['main_ip']); + + // Find some additional IP's used by this member. + $context['ban_suggestions']['message_ips'] = array(); + $request = $smcFunc['db_query']('ban_suggest_message_ips', ' + SELECT DISTINCT poster_ip + FROM {db_prefix}messages + WHERE id_member = {int:current_user} + AND poster_ip RLIKE {string:poster_ip_regex} + ORDER BY poster_ip', + array( + 'current_user' => (int) $_REQUEST['u'], + 'poster_ip_regex' => '^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['ban_suggestions']['message_ips'][] = $row['poster_ip']; + $smcFunc['db_free_result']($request); + + $context['ban_suggestions']['error_ips'] = array(); + $request = $smcFunc['db_query']('ban_suggest_error_ips', ' + SELECT DISTINCT ip + FROM {db_prefix}log_errors + WHERE id_member = {int:current_user} + AND ip RLIKE {string:poster_ip_regex} + ORDER BY ip', + array( + 'current_user' => (int) $_REQUEST['u'], + 'poster_ip_regex' => '^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['ban_suggestions']['error_ips'][] = $row['ip']; + $smcFunc['db_free_result']($request); + + // Borrowing a few language strings from profile. + loadLanguage('Profile'); + } + } + } + + // Template needs this to show errors using javascript + loadLanguage('Errors'); + + // If we're in wireless mode remove the admin template layer and use a special template. + if (WIRELESS && WIRELESS_PROTOCOL != 'wap') + { + $context['sub_template'] = WIRELESS_PROTOCOL . '_ban_edit'; + foreach ($context['template_layers'] as $k => $v) + if (strpos($v, 'generic_menu') === 0) + unset($context['template_layers'][$k]); + } + else + $context['sub_template'] = 'ban_edit'; +} + +function BanEditTrigger() +{ + global $context, $smcFunc; + + $context['sub_template'] = 'ban_edit_trigger'; + + if (empty($_REQUEST['bg'])) + fatal_lang_error('ban_not_found', false); + + if (empty($_REQUEST['bi'])) + { + $context['ban_trigger'] = array( + 'id' => 0, + 'group' => (int) $_REQUEST['bg'], + 'ip' => array( + 'value' => '', + 'selected' => true, + ), + 'hostname' => array( + 'selected' => false, + 'value' => '', + ), + 'email' => array( + 'value' => '', + 'selected' => false, + ), + 'banneduser' => array( + 'value' => '', + 'selected' => false, + ), + 'is_new' => true, + ); + } + else + { + $request = $smcFunc['db_query']('', ' + SELECT + bi.id_ban, bi.id_ban_group, bi.hostname, bi.email_address, bi.id_member, + bi.ip_low1, bi.ip_high1, bi.ip_low2, bi.ip_high2, bi.ip_low3, bi.ip_high3, bi.ip_low4, bi.ip_high4, + mem.member_name, mem.real_name + FROM {db_prefix}ban_items AS bi + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member) + WHERE bi.id_ban = {int:ban_item} + AND bi.id_ban_group = {int:ban_group} + LIMIT 1', + array( + 'ban_item' => (int) $_REQUEST['bi'], + 'ban_group' => (int) $_REQUEST['bg'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('ban_not_found', false); + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + $context['ban_trigger'] = array( + 'id' => $row['id_ban'], + 'group' => $row['id_ban_group'], + 'ip' => array( + 'value' => empty($row['ip_low1']) ? '' : range2ip(array($row['ip_low1'], $row['ip_low2'], $row['ip_low3'], $row['ip_low4']), array($row['ip_high1'], $row['ip_high2'], $row['ip_high3'], $row['ip_high4'])), + 'selected' => !empty($row['ip_low1']), + ), + 'hostname' => array( + 'value' => str_replace('%', '*', $row['hostname']), + 'selected' => !empty($row['hostname']), + ), + 'email' => array( + 'value' => str_replace('%', '*', $row['email_address']), + 'selected' => !empty($row['email_address']) + ), + 'banneduser' => array( + 'value' => $row['member_name'], + 'selected' => !empty($row['member_name']) + ), + 'is_new' => false, + ); + } +} + +function BanBrowseTriggers() +{ + global $modSettings, $context, $scripturl, $smcFunc, $txt; + global $sourcedir, $settings; + + if (!empty($_POST['remove_triggers']) && !empty($_POST['remove']) && is_array($_POST['remove'])) + { + checkSession(); + + // Clean the integers. + foreach ($_POST['remove'] as $key => $value) + $_POST['remove'][$key] = $value; + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}ban_items + WHERE id_ban IN ({array_int:ban_list})', + array( + 'ban_list' => $_POST['remove'], + ) + ); + + // Rehabilitate some members. + if ($_REQUEST['entity'] == 'member') + updateBanMembers(); + + // Make sure the ban cache is refreshed. + updateSettings(array('banLastUpdated' => time())); + } + + $context['selected_entity'] = isset($_REQUEST['entity']) && in_array($_REQUEST['entity'], array('ip', 'hostname', 'email', 'member')) ? $_REQUEST['entity'] : 'ip'; + + $listOptions = array( + 'id' => 'ban_trigger_list', + 'title' => $txt['ban_trigger_browse'], + 'items_per_page' => $modSettings['defaultMaxMessages'], + 'base_href' => $scripturl . '?action=admin;area=ban;sa=browse;entity=' . $context['selected_entity'], + 'default_sort_col' => 'banned_entity', + 'no_items_label' => $txt['ban_no_triggers'], + 'get_items' => array( + 'function' => 'list_getBanTriggers', + 'params' => array( + $context['selected_entity'], + ), + ), + 'get_count' => array( + 'function' => 'list_getNumBanTriggers', + 'params' => array( + $context['selected_entity'], + ), + ), + 'columns' => array( + 'banned_entity' => array( + 'header' => array( + 'value' => $txt['ban_banned_entity'], + ), + ), + 'ban_name' => array( + 'header' => array( + 'value' => $txt['ban_name'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%2$s', + 'params' => array( + 'id_ban_group' => false, + 'name' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'bg.name', + 'reverse' => 'bg.name DESC', + ), + ), + 'hits' => array( + 'header' => array( + 'value' => $txt['ban_hits'], + ), + 'data' => array( + 'db' => 'hits', + 'style' => 'text-align: center;', + ), + 'sort' => array( + 'default' => 'bi.hits DESC', + 'reverse' => 'bi.hits', + ), + ), + 'check' => array( + 'header' => array( + 'value' => '', + ), + 'data' => array( + 'sprintf' => array( + 'format' => '', + 'params' => array( + 'id_ban' => false, + ), + ), + 'style' => 'text-align: center', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=ban;sa=browse;entity=' . $context['selected_entity'], + 'include_start' => true, + 'include_sort' => true, + ), + 'additional_rows' => array( + array( + 'position' => 'above_column_headers', + 'value' => '' . ($context['selected_entity'] == 'ip' ? '> ' : '') . $txt['ip'] . ' | ' . ($context['selected_entity'] == 'hostname' ? '> ' : '') . $txt['hostname'] . ' | ' . ($context['selected_entity'] == 'email' ? '> ' : '') . $txt['email'] . ' | ' . ($context['selected_entity'] == 'member' ? '> ' : '') . $txt['username'] . '', + ), + array( + 'position' => 'below_table_data', + 'value' => '', + 'style' => 'text-align: right;', + ), + ), + ); + + // Specific data for the first column depending on the selected entity. + if ($context['selected_entity'] === 'ip') + { + $listOptions['columns']['banned_entity']['data'] = array( + 'function' => create_function('$rowData', ' + return range2ip(array( + $rowData[\'ip_low1\'], + $rowData[\'ip_low2\'], + $rowData[\'ip_low3\'], + $rowData[\'ip_low4\'] + ), array( + $rowData[\'ip_high1\'], + $rowData[\'ip_high2\'], + $rowData[\'ip_high3\'], + $rowData[\'ip_high4\'] + )); + '), + ); + $listOptions['columns']['banned_entity']['sort'] = array( + 'default' => 'bi.ip_low1, bi.ip_high1, bi.ip_low2, bi.ip_high2, bi.ip_low3, bi.ip_high3, bi.ip_low4, bi.ip_high4', + 'reverse' => 'bi.ip_low1 DESC, bi.ip_high1 DESC, bi.ip_low2 DESC, bi.ip_high2 DESC, bi.ip_low3 DESC, bi.ip_high3 DESC, bi.ip_low4 DESC, bi.ip_high4 DESC', + ); + } + elseif ($context['selected_entity'] === 'hostname') + { + $listOptions['columns']['banned_entity']['data'] = array( + 'function' => create_function('$rowData', ' + global $smcFunc; + return strtr($smcFunc[\'htmlspecialchars\']($rowData[\'hostname\']), array(\'%\' => \'*\')); + '), + ); + $listOptions['columns']['banned_entity']['sort'] = array( + 'default' => 'bi.hostname', + 'reverse' => 'bi.hostname DESC', + ); + } + elseif ($context['selected_entity'] === 'email') + { + $listOptions['columns']['banned_entity']['data'] = array( + 'function' => create_function('$rowData', ' + global $smcFunc; + return strtr($smcFunc[\'htmlspecialchars\']($rowData[\'email_address\']), array(\'%\' => \'*\')); + '), + ); + $listOptions['columns']['banned_entity']['sort'] = array( + 'default' => 'bi.email_address', + 'reverse' => 'bi.email_address DESC', + ); + } + elseif ($context['selected_entity'] === 'member') + { + $listOptions['columns']['banned_entity']['data'] = array( + 'sprintf' => array( + 'format' => '%2$s', + 'params' => array( + 'id_member' => false, + 'real_name' => false, + ), + ), + ); + $listOptions['columns']['banned_entity']['sort'] = array( + 'default' => 'mem.real_name', + 'reverse' => 'mem.real_name DESC', + ); + } + + // Create the list. + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + // The list is the only thing to show, so make it the default sub template. + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'ban_trigger_list'; +} + +function list_getBanTriggers($start, $items_per_page, $sort, $trigger_type) +{ + global $smcFunc; + + $where = array( + 'ip' => 'bi.ip_low1 > 0', + 'hostname' => 'bi.hostname != {string:blank_string}', + 'email' => 'bi.email_address != {string:blank_string}', + ); + + $request = $smcFunc['db_query']('', ' + SELECT + bi.id_ban, bi.ip_low1, bi.ip_high1, bi.ip_low2, bi.ip_high2, bi.ip_low3, bi.ip_high3, bi.ip_low4, bi.ip_high4, bi.hostname, bi.email_address, bi.hits, + bg.id_ban_group, bg.name' . ($trigger_type === 'member' ? ', + mem.id_member, mem.real_name' : '') . ' + FROM {db_prefix}ban_items AS bi + INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group)' . ($trigger_type === 'member' ? ' + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)' : ' + WHERE ' . $where[$trigger_type]) . ' + ORDER BY ' . $sort . ' + LIMIT ' . $start . ', ' . $items_per_page, + array( + 'blank_string' => '', + ) + ); + $ban_triggers = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $ban_triggers[] = $row; + $smcFunc['db_free_result']($request); + + return $ban_triggers; +} + +function list_getNumBanTriggers($trigger_type) +{ + global $smcFunc; + + $where = array( + 'ip' => 'bi.ip_low1 > 0', + 'hostname' => 'bi.hostname != {string:blank_string}', + 'email' => 'bi.email_address != {string:blank_string}', + ); + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}ban_items AS bi' . ($trigger_type === 'member' ? ' + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)' : ' + WHERE ' . $where[$trigger_type]), + array( + 'blank_string' => '', + ) + ); + list ($num_triggers) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $num_triggers; +} + +function BanLog() +{ + global $scripturl, $context, $smcFunc, $sourcedir, $txt; + global $context; + + // Delete one or more entries. + if (!empty($_POST['removeAll']) || (!empty($_POST['removeSelected']) && !empty($_POST['remove']))) + { + checkSession(); + + // 'Delete all entries' button was pressed. + if (!empty($_POST['removeAll'])) + $smcFunc['db_query']('truncate_table', ' + TRUNCATE {db_prefix}log_banned', + array( + ) + ); + + // 'Delte selection' button was pressed. + else + { + // Make sure every entry is integer. + foreach ($_POST['remove'] as $index => $log_id) + $_POST['remove'][$index] = (int) $log_id; + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_banned + WHERE id_ban_log IN ({array_int:ban_list})', + array( + 'ban_list' => $_POST['remove'], + ) + ); + } + } + + $listOptions = array( + 'id' => 'ban_log', + 'items_per_page' => 30, + 'base_href' => $context['admin_area'] == 'ban' ? $scripturl . '?action=admin;area=ban;sa=log' : $scripturl . '?action=admin;area=logs;sa=banlog', + 'default_sort_col' => 'date', + 'get_items' => array( + 'function' => 'list_getBanLogEntries', + ), + 'get_count' => array( + 'function' => 'list_getNumBanLogEntries', + ), + 'no_items_label' => $txt['ban_log_no_entries'], + 'columns' => array( + 'ip' => array( + 'header' => array( + 'value' => $txt['ban_log_ip'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%1$s', + 'params' => array( + 'ip' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'lb.ip', + 'reverse' => 'lb.ip DESC', + ), + ), + 'email' => array( + 'header' => array( + 'value' => $txt['ban_log_email'], + ), + 'data' => array( + 'db_htmlsafe' => 'email', + ), + 'sort' => array( + 'default' => 'lb.email = \'\', lb.email', + 'reverse' => 'lb.email != \'\', lb.email DESC', + ), + ), + 'member' => array( + 'header' => array( + 'value' => $txt['ban_log_member'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%2$s', + 'params' => array( + 'id_member' => false, + 'real_name' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'IFNULL(mem.real_name, 1=1), mem.real_name', + 'reverse' => 'IFNULL(mem.real_name, 1=1) DESC, mem.real_name DESC', + ), + ), + 'date' => array( + 'header' => array( + 'value' => $txt['ban_log_date'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + return timeformat($rowData[\'log_time\']); + '), + ), + 'sort' => array( + 'default' => 'lb.log_time DESC', + 'reverse' => 'lb.log_time', + ), + ), + 'check' => array( + 'header' => array( + 'value' => '', + ), + 'data' => array( + 'sprintf' => array( + 'format' => '', + 'params' => array( + 'id_ban_log' => false, + ), + ), + 'style' => 'text-align: center', + ), + ), + ), + 'form' => array( + 'href' => $context['admin_area'] == 'ban' ? $scripturl . '?action=admin;area=ban;sa=log' : $scripturl . '?action=admin;area=logs;sa=banlog', + 'include_start' => true, + 'include_sort' => true, + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => ' + + ', + 'style' => 'text-align: right;', + ), + ), + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + $context['page_title'] = $txt['ban_log']; + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'ban_log'; +} + +function list_getBanLogEntries($start, $items_per_page, $sort) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT lb.id_ban_log, lb.id_member, IFNULL(lb.ip, {string:dash}) AS ip, IFNULL(lb.email, {string:dash}) AS email, lb.log_time, IFNULL(mem.real_name, {string:blank_string}) AS real_name + FROM {db_prefix}log_banned AS lb + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lb.id_member) + ORDER BY ' . $sort . ' + LIMIT ' . $start . ', ' . $items_per_page, + array( + 'blank_string' => '', + 'dash' => '-', + ) + ); + $log_entries = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $log_entries[] = $row; + $smcFunc['db_free_result']($request); + + return $log_entries; +} + +function list_getNumBanLogEntries() +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_banned AS lb', + array( + ) + ); + list ($num_entries) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $num_entries; +} + +function range2ip($low, $high) +{ + if (count($low) != 4 || count($high) != 4) + return ''; + + $ip = array(); + for ($i = 0; $i < 4; $i++) + { + if ($low[$i] == $high[$i]) + $ip[$i] = $low[$i]; + elseif ($low[$i] == '0' && $high[$i] == '255') + $ip[$i] = '*'; + else + $ip[$i] = $low[$i] . '-' . $high[$i]; + } + + // Pretending is fun... the IP can't be this, so use it for 'unknown'. + if ($ip == array(255, 255, 255, 255)) + return 'unknown'; + + return implode('.', $ip); +} + +function checkExistingTriggerIP($ip_array, $fullip = '') +{ + global $smcFunc, $scripturl; + + if (count($ip_array) == 4) + $values = array( + 'ip_low1' => $ip_array[0]['low'], + 'ip_high1' => $ip_array[0]['high'], + 'ip_low2' => $ip_array[1]['low'], + 'ip_high2' => $ip_array[1]['high'], + 'ip_low3' => $ip_array[2]['low'], + 'ip_high3' => $ip_array[2]['high'], + 'ip_low4' => $ip_array[3]['low'], + 'ip_high4' => $ip_array[3]['high'], + ); + else + return false; + + $request = $smcFunc['db_query']('', ' + SELECT bg.id_ban_group, bg.name + FROM {db_prefix}ban_groups AS bg + INNER JOIN {db_prefix}ban_items AS bi ON + (bi.id_ban_group = bg.id_ban_group) + AND ip_low1 = {int:ip_low1} AND ip_high1 = {int:ip_high1} + AND ip_low2 = {int:ip_low2} AND ip_high2 = {int:ip_high2} + AND ip_low3 = {int:ip_low3} AND ip_high3 = {int:ip_high3} + AND ip_low4 = {int:ip_low4} AND ip_high4 = {int:ip_high4} + LIMIT 1', + $values + ); + if ($smcFunc['db_num_rows']($request) != 0) + { + list ($error_id_ban, $error_ban_name) = $smcFunc['db_fetch_row']($request); + fatal_lang_error('ban_trigger_already_exists', false, array( + $fullip, + '' . $error_ban_name . '', + )); + } + $smcFunc['db_free_result']($request); + + return $values; +} + +function updateBanMembers() +{ + global $smcFunc; + + $updates = array(); + $allMembers = array(); + $newMembers = array(); + + // Start by getting all active bans - it's quicker doing this in parts... + $request = $smcFunc['db_query']('', ' + SELECT bi.id_member, bi.email_address + FROM {db_prefix}ban_items AS bi + INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group) + WHERE (bi.id_member > {int:no_member} OR bi.email_address != {string:blank_string}) + AND bg.cannot_access = {int:cannot_access_on} + AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time})', + array( + 'no_member' => 0, + 'cannot_access_on' => 1, + 'current_time' => time(), + 'blank_string' => '', + ) + ); + $memberIDs = array(); + $memberEmails = array(); + $memberEmailWild = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['id_member']) + $memberIDs[$row['id_member']] = $row['id_member']; + if ($row['email_address']) + { + // Does it have a wildcard - if so we can't do a IN on it. + if (strpos($row['email_address'], '%') !== false) + $memberEmailWild[$row['email_address']] = $row['email_address']; + else + $memberEmails[$row['email_address']] = $row['email_address']; + } + } + $smcFunc['db_free_result']($request); + + // Build up the query. + $queryPart = array(); + $queryValues = array(); + if (!empty($memberIDs)) + { + $queryPart[] = 'mem.id_member IN ({array_string:member_ids})'; + $queryValues['member_ids'] = $memberIDs; + } + if (!empty($memberEmails)) + { + $queryPart[] = 'mem.email_address IN ({array_string:member_emails})'; + $queryValues['member_emails'] = $memberEmails; + } + $count = 0; + foreach ($memberEmailWild as $email) + { + $queryPart[] = 'mem.email_address LIKE {string:wild_' . $count . '}'; + $queryValues['wild_' . $count++] = $email; + } + + // Find all banned members. + if (!empty($queryPart)) + { + $request = $smcFunc['db_query']('', ' + SELECT mem.id_member, mem.is_activated + FROM {db_prefix}members AS mem + WHERE ' . implode( ' OR ', $queryPart), + $queryValues + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!in_array($row['id_member'], $allMembers)) + { + $allMembers[] = $row['id_member']; + // Do they need an update? + if ($row['is_activated'] < 10) + { + $updates[($row['is_activated'] + 10)][] = $row['id_member']; + $newMembers[] = $row['id_member']; + } + } + } + $smcFunc['db_free_result']($request); + } + + // We welcome our new members in the realm of the banned. + if (!empty($newMembers)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_online + WHERE id_member IN ({array_int:new_banned_members})', + array( + 'new_banned_members' => $newMembers, + ) + ); + + // Find members that are wrongfully marked as banned. + $request = $smcFunc['db_query']('', ' + SELECT mem.id_member, mem.is_activated - 10 AS new_value + FROM {db_prefix}members AS mem + LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_member = mem.id_member OR mem.email_address LIKE bi.email_address) + LEFT JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group AND bg.cannot_access = {int:cannot_access_activated} AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time})) + WHERE (bi.id_ban IS NULL OR bg.id_ban_group IS NULL) + AND mem.is_activated >= {int:ban_flag}', + array( + 'cannot_access_activated' => 1, + 'current_time' => time(), + 'ban_flag' => 10, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Don't do this twice! + if (!in_array($row['id_member'], $allMembers)) + { + $updates[$row['new_value']][] = $row['id_member']; + $allMembers[] = $row['id_member']; + } + } + $smcFunc['db_free_result']($request); + + if (!empty($updates)) + foreach ($updates as $newStatus => $members) + updateMemberData($members, array('is_activated' => $newStatus)); + + // Update the latest member and our total members as banning may change them. + updateStats('member'); +} + +?> \ No newline at end of file diff --git a/Sources/ManageBoards.php b/Sources/ManageBoards.php new file mode 100644 index 0000000..b3d3445 --- /dev/null +++ b/Sources/ManageBoards.php @@ -0,0 +1,801 @@ + array('function', 'permission') + $subActions = array( + 'board' => array('EditBoard', 'manage_boards'), + 'board2' => array('EditBoard2', 'manage_boards'), + 'cat' => array('EditCategory', 'manage_boards'), + 'cat2' => array('EditCategory2', 'manage_boards'), + 'main' => array('ManageBoardsMain', 'manage_boards'), + 'move' => array('ManageBoardsMain', 'manage_boards'), + 'newcat' => array('EditCategory', 'manage_boards'), + 'newboard' => array('EditBoard', 'manage_boards'), + 'settings' => array('EditBoardSettings', 'admin_forum'), + ); + + // Default to sub action 'main' or 'settings' depending on permissions. + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : (allowedTo('manage_boards') ? 'main' : 'settings'); + + // Have you got the proper permissions? + isAllowedTo($subActions[$_REQUEST['sa']][1]); + + // Create the tabs for the template. + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['boards_and_cats'], + 'help' => 'manage_boards', + 'description' => $txt['boards_and_cats_desc'], + 'tabs' => array( + 'main' => array( + ), + 'newcat' => array( + ), + 'settings' => array( + 'description' => $txt['mboards_settings_desc'], + ), + ), + ); + + $subActions[$_REQUEST['sa']][0](); +} + +// The main control panel thing. +function ManageBoardsMain() +{ + global $txt, $context, $cat_tree, $boards, $boardList, $scripturl, $sourcedir, $txt; + + loadTemplate('ManageBoards'); + + require_once($sourcedir . '/Subs-Boards.php'); + + if (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'move' && in_array($_REQUEST['move_to'], array('child', 'before', 'after', 'top'))) + { + checkSession('get'); + if ($_REQUEST['move_to'] === 'top') + $boardOptions = array( + 'move_to' => $_REQUEST['move_to'], + 'target_category' => (int) $_REQUEST['target_cat'], + 'move_first_child' => true, + ); + else + $boardOptions = array( + 'move_to' => $_REQUEST['move_to'], + 'target_board' => (int) $_REQUEST['target_board'], + 'move_first_child' => true, + ); + modifyBoard((int) $_REQUEST['src_board'], $boardOptions); + } + + getBoardTree(); + + $context['move_board'] = !empty($_REQUEST['move']) && isset($boards[(int) $_REQUEST['move']]) ? (int) $_REQUEST['move'] : 0; + + $context['categories'] = array(); + foreach ($cat_tree as $catid => $tree) + { + $context['categories'][$catid] = array( + 'name' => &$tree['node']['name'], + 'id' => &$tree['node']['id'], + 'boards' => array() + ); + $move_cat = !empty($context['move_board']) && $boards[$context['move_board']]['category'] == $catid; + foreach ($boardList[$catid] as $boardid) + { + $context['categories'][$catid]['boards'][$boardid] = array( + 'id' => &$boards[$boardid]['id'], + 'name' => &$boards[$boardid]['name'], + 'description' => &$boards[$boardid]['description'], + 'child_level' => &$boards[$boardid]['level'], + 'move' => $move_cat && ($boardid == $context['move_board'] || isChildOf($boardid, $context['move_board'])), + 'permission_profile' => &$boards[$boardid]['profile'], + ); + } + } + + if (!empty($context['move_board'])) + { + $context['move_title'] = sprintf($txt['mboards_select_destination'], htmlspecialchars($boards[$context['move_board']]['name'])); + foreach ($cat_tree as $catid => $tree) + { + $prev_child_level = 0; + $prev_board = 0; + $stack = array(); + foreach ($boardList[$catid] as $boardid) + { + if (!isset($context['categories'][$catid]['move_link'])) + $context['categories'][$catid]['move_link'] = array( + 'child_level' => 0, + 'label' => $txt['mboards_order_before'] . ' \'' . htmlspecialchars($boards[$boardid]['name']) . '\'', + 'href' => $scripturl . '?action=admin;area=manageboards;sa=move;src_board=' . $context['move_board'] . ';target_board=' . $boardid . ';move_to=before;' . $context['session_var'] . '=' . $context['session_id'], + ); + + if (!$context['categories'][$catid]['boards'][$boardid]['move']) + $context['categories'][$catid]['boards'][$boardid]['move_links'] = array( + array( + 'child_level' => $boards[$boardid]['level'], + 'label' => $txt['mboards_order_after'] . '\'' . htmlspecialchars($boards[$boardid]['name']) . '\'', + 'href' => $scripturl . '?action=admin;area=manageboards;sa=move;src_board=' . $context['move_board'] . ';target_board=' . $boardid . ';move_to=after;' . $context['session_var'] . '=' . $context['session_id'], + ), + array( + 'child_level' => $boards[$boardid]['level'] + 1, + 'label' => $txt['mboards_order_child_of'] . ' \'' . htmlspecialchars($boards[$boardid]['name']) . '\'', + 'href' => $scripturl . '?action=admin;area=manageboards;sa=move;src_board=' . $context['move_board'] . ';target_board=' . $boardid . ';move_to=child;' . $context['session_var'] . '=' . $context['session_id'], + ), + ); + + $difference = $boards[$boardid]['level'] - $prev_child_level; + if ($difference == 1) + array_push($stack, !empty($context['categories'][$catid]['boards'][$prev_board]['move_links']) ? array_shift($context['categories'][$catid]['boards'][$prev_board]['move_links']) : null); + elseif ($difference < 0) + { + if (empty($context['categories'][$catid]['boards'][$prev_board]['move_links'])) + $context['categories'][$catid]['boards'][$prev_board]['move_links'] = array(); + for ($i = 0; $i < -$difference; $i++) + if (($temp = array_pop($stack)) != null) + array_unshift($context['categories'][$catid]['boards'][$prev_board]['move_links'], $temp); + } + + $prev_board = $boardid; + $prev_child_level = $boards[$boardid]['level']; + + } + if (!empty($stack) && !empty($context['categories'][$catid]['boards'][$prev_board]['move_links'])) + $context['categories'][$catid]['boards'][$prev_board]['move_links'] = array_merge($stack, $context['categories'][$catid]['boards'][$prev_board]['move_links']); + elseif (!empty($stack)) + $context['categories'][$catid]['boards'][$prev_board]['move_links'] = $stack; + + if (empty($boardList[$catid])) + $context['categories'][$catid]['move_link'] = array( + 'child_level' => 0, + 'label' => $txt['mboards_order_before'] . ' \'' . htmlspecialchars($tree['node']['name']) . '\'', + 'href' => $scripturl . '?action=admin;area=manageboards;sa=move;src_board=' . $context['move_board'] . ';target_cat=' . $catid . ';move_to=top;' . $context['session_var'] . '=' . $context['session_id'], + ); + } + } + + $context['page_title'] = $txt['boards_and_cats']; + $context['can_manage_permissions'] = allowedTo('manage_permissions'); +} + +// Modify a specific category. +function EditCategory() +{ + global $txt, $context, $cat_tree, $boardList, $boards, $sourcedir; + + loadTemplate('ManageBoards'); + require_once($sourcedir . '/Subs-Boards.php'); + getBoardTree(); + + // id_cat must be a number.... if it exists. + $_REQUEST['cat'] = isset($_REQUEST['cat']) ? (int) $_REQUEST['cat'] : 0; + + // Start with one - "In first place". + $context['category_order'] = array( + array( + 'id' => 0, + 'name' => $txt['mboards_order_first'], + 'selected' => !empty($_REQUEST['cat']) ? $cat_tree[$_REQUEST['cat']]['is_first'] : false, + 'true_name' => '' + ) + ); + + // If this is a new category set up some defaults. + if ($_REQUEST['sa'] == 'newcat') + { + $context['category'] = array( + 'id' => 0, + 'name' => $txt['mboards_new_cat_name'], + 'editable_name' => htmlspecialchars($txt['mboards_new_cat_name']), + 'can_collapse' => true, + 'is_new' => true, + 'is_empty' => true + ); + } + // Category doesn't exist, man... sorry. + elseif (!isset($cat_tree[$_REQUEST['cat']])) + redirectexit('action=admin;area=manageboards'); + else + { + $context['category'] = array( + 'id' => $_REQUEST['cat'], + 'name' => $cat_tree[$_REQUEST['cat']]['node']['name'], + 'editable_name' => htmlspecialchars($cat_tree[$_REQUEST['cat']]['node']['name']), + 'can_collapse' => !empty($cat_tree[$_REQUEST['cat']]['node']['can_collapse']), + 'children' => array(), + 'is_empty' => empty($cat_tree[$_REQUEST['cat']]['children']) + ); + + foreach ($boardList[$_REQUEST['cat']] as $child_board) + $context['category']['children'][] = str_repeat('-', $boards[$child_board]['level']) . ' ' . $boards[$child_board]['name']; + } + + $prevCat = 0; + foreach ($cat_tree as $catid => $tree) + { + if ($catid == $_REQUEST['cat'] && $prevCat > 0) + $context['category_order'][$prevCat]['selected'] = true; + elseif ($catid != $_REQUEST['cat']) + $context['category_order'][$catid] = array( + 'id' => $catid, + 'name' => $txt['mboards_order_after'] . $tree['node']['name'], + 'selected' => false, + 'true_name' => $tree['node']['name'] + ); + $prevCat = $catid; + } + if (!isset($_REQUEST['delete'])) + { + $context['sub_template'] = 'modify_category'; + $context['page_title'] = $_REQUEST['sa'] == 'newcat' ? $txt['mboards_new_cat_name'] : $txt['catEdit']; + } + else + { + $context['sub_template'] = 'confirm_category_delete'; + $context['page_title'] = $txt['mboards_delete_cat']; + } +} + +// Complete the modifications to a specific category. +function EditCategory2() +{ + global $sourcedir; + + checkSession(); + + require_once($sourcedir . '/Subs-Categories.php'); + + $_POST['cat'] = (int) $_POST['cat']; + + // Add a new category or modify an existing one.. + if (isset($_POST['edit']) || isset($_POST['add'])) + { + $catOptions = array(); + + if (isset($_POST['cat_order'])) + $catOptions['move_after'] = (int) $_POST['cat_order']; + + // Change "This & That" to "This & That" but don't change "¢" to "&cent;"... + $catOptions['cat_name'] = preg_replace('~[&]([^;]{8}|[^;]{0,8}$)~', '&$1', $_POST['cat_name']); + + $catOptions['is_collapsible'] = isset($_POST['collapse']); + + if (isset($_POST['add'])) + createCategory($catOptions); + else + modifyCategory($_POST['cat'], $catOptions); + } + // If they want to delete - first give them confirmation. + elseif (isset($_POST['delete']) && !isset($_POST['confirmation']) && !isset($_POST['empty'])) + { + EditCategory(); + return; + } + // Delete the category! + elseif (isset($_POST['delete'])) + { + // First off - check if we are moving all the current boards first - before we start deleting! + if (isset($_POST['delete_action']) && $_POST['delete_action'] == 1) + { + if (empty($_POST['cat_to'])) + fatal_lang_error('mboards_delete_error'); + + deleteCategories(array($_POST['cat']), (int) $_POST['cat_to']); + } + else + deleteCategories(array($_POST['cat'])); + } + + redirectexit('action=admin;area=manageboards'); +} + +// Modify a specific board... +function EditBoard() +{ + global $txt, $context, $cat_tree, $boards, $boardList, $sourcedir, $smcFunc, $modSettings; + + loadTemplate('ManageBoards'); + require_once($sourcedir . '/Subs-Boards.php'); + getBoardTree(); + + // For editing the profile we'll need this. + loadLanguage('ManagePermissions'); + require_once($sourcedir . '/ManagePermissions.php'); + loadPermissionProfiles(); + + // id_board must be a number.... + $_REQUEST['boardid'] = isset($_REQUEST['boardid']) ? (int) $_REQUEST['boardid'] : 0; + if (!isset($boards[$_REQUEST['boardid']])) + { + $_REQUEST['boardid'] = 0; + $_REQUEST['sa'] = 'newboard'; + } + + if ($_REQUEST['sa'] == 'newboard') + { + // Category doesn't exist, man... sorry. + if (empty($_REQUEST['cat'])) + redirectexit('action=admin;area=manageboards'); + + // Some things that need to be setup for a new board. + $curBoard = array( + 'member_groups' => array(0, -1), + 'category' => (int) $_REQUEST['cat'] + ); + $context['board_order'] = array(); + $context['board'] = array( + 'is_new' => true, + 'id' => 0, + 'name' => $txt['mboards_new_board_name'], + 'description' => '', + 'count_posts' => 1, + 'posts' => 0, + 'topics' => 0, + 'theme' => 0, + 'profile' => 1, + 'override_theme' => 0, + 'redirect' => '', + 'category' => (int) $_REQUEST['cat'], + 'no_children' => true, + ); + } + else + { + // Just some easy shortcuts. + $curBoard = &$boards[$_REQUEST['boardid']]; + $context['board'] = $boards[$_REQUEST['boardid']]; + $context['board']['name'] = htmlspecialchars(strtr($context['board']['name'], array('&' => '&'))); + $context['board']['description'] = htmlspecialchars($context['board']['description']); + $context['board']['no_children'] = empty($boards[$_REQUEST['boardid']]['tree']['children']); + $context['board']['is_recycle'] = !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $context['board']['id']; + } + + // As we may have come from the permissions screen keep track of where we should go on save. + $context['redirect_location'] = isset($_GET['rid']) && $_GET['rid'] == 'permissions' ? 'permissions' : 'boards'; + + // We might need this to hide links to certain areas. + $context['can_manage_permissions'] = allowedTo('manage_permissions'); + + // Default membergroups. + $context['groups'] = array( + -1 => array( + 'id' => '-1', + 'name' => $txt['parent_guests_only'], + 'checked' => in_array('-1', $curBoard['member_groups']), + 'is_post_group' => false, + ), + 0 => array( + 'id' => '0', + 'name' => $txt['parent_members_only'], + 'checked' => in_array('0', $curBoard['member_groups']), + 'is_post_group' => false, + ) + ); + + // Load membergroups. + $request = $smcFunc['db_query']('', ' + SELECT group_name, id_group, min_posts + FROM {db_prefix}membergroups + WHERE id_group > {int:moderator_group} OR id_group = {int:global_moderator} + ORDER BY min_posts, id_group != {int:global_moderator}, group_name', + array( + 'moderator_group' => 3, + 'global_moderator' => 2, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($_REQUEST['sa'] == 'newboard' && $row['min_posts'] == -1) + $curBoard['member_groups'][] = $row['id_group']; + + $context['groups'][(int) $row['id_group']] = array( + 'id' => $row['id_group'], + 'name' => trim($row['group_name']), + 'checked' => in_array($row['id_group'], $curBoard['member_groups']), + 'is_post_group' => $row['min_posts'] != -1, + ); + } + $smcFunc['db_free_result']($request); + + // Category doesn't exist, man... sorry. + if (!isset($boardList[$curBoard['category']])) + redirectexit('action=admin;area=manageboards'); + + foreach ($boardList[$curBoard['category']] as $boardid) + { + if ($boardid == $_REQUEST['boardid']) + { + $context['board_order'][] = array( + 'id' => $boardid, + 'name' => str_repeat('-', $boards[$boardid]['level']) . ' (' . $txt['mboards_current_position'] . ')', + 'children' => $boards[$boardid]['tree']['children'], + 'no_children' => empty($boards[$boardid]['tree']['children']), + 'is_child' => false, + 'selected' => true + ); + } + else + { + $context['board_order'][] = array( + 'id' => $boardid, + 'name' => str_repeat('-', $boards[$boardid]['level']) . ' ' . $boards[$boardid]['name'], + 'is_child' => empty($_REQUEST['boardid']) ? false : isChildOf($boardid, $_REQUEST['boardid']), + 'selected' => false + ); + } + } + + // Are there any places to move child boards to in the case where we are confirming a delete? + if (!empty($_REQUEST['boardid'])) + { + $context['can_move_children'] = false; + $context['children'] = $boards[$_REQUEST['boardid']]['tree']['children']; + foreach ($context['board_order'] as $board) + if ($board['is_child'] == false && $board['selected'] == false) + $context['can_move_children'] = true; + } + + // Get other available categories. + $context['categories'] = array(); + foreach ($cat_tree as $catID => $tree) + $context['categories'][] = array( + 'id' => $catID == $curBoard['category'] ? 0 : $catID, + 'name' => $tree['node']['name'], + 'selected' => $catID == $curBoard['category'] + ); + + $request = $smcFunc['db_query']('', ' + SELECT mem.id_member, mem.real_name + FROM {db_prefix}moderators AS mods + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member) + WHERE mods.id_board = {int:current_board}', + array( + 'current_board' => $_REQUEST['boardid'], + ) + ); + $context['board']['moderators'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['board']['moderators'][$row['id_member']] = $row['real_name']; + $smcFunc['db_free_result']($request); + + $context['board']['moderator_list'] = empty($context['board']['moderators']) ? '' : '"' . implode('", "', $context['board']['moderators']) . '"'; + + if (!empty($context['board']['moderators'])) + list ($context['board']['last_moderator_id']) = array_slice(array_keys($context['board']['moderators']), -1); + + // Get all the themes... + $request = $smcFunc['db_query']('', ' + SELECT id_theme AS id, value AS name + FROM {db_prefix}themes + WHERE variable = {string:name}', + array( + 'name' => 'name', + ) + ); + $context['themes'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['themes'][] = $row; + $smcFunc['db_free_result']($request); + + if (!isset($_REQUEST['delete'])) + { + $context['sub_template'] = 'modify_board'; + $context['page_title'] = $txt['boardsEdit']; + } + else + { + $context['sub_template'] = 'confirm_board_delete'; + $context['page_title'] = $txt['mboards_delete_board']; + } +} + +// Make changes to/delete a board. +function EditBoard2() +{ + global $txt, $sourcedir, $modSettings, $smcFunc, $context; + + checkSession(); + + require_once($sourcedir . '/Subs-Boards.php'); + + $_POST['boardid'] = (int) $_POST['boardid']; + + // Mode: modify aka. don't delete. + if (isset($_POST['edit']) || isset($_POST['add'])) + { + $boardOptions = array(); + + // Move this board to a new category? + if (!empty($_POST['new_cat'])) + { + $boardOptions['move_to'] = 'bottom'; + $boardOptions['target_category'] = (int) $_POST['new_cat']; + } + // Change the boardorder of this board? + elseif (!empty($_POST['placement']) && !empty($_POST['board_order'])) + { + if (!in_array($_POST['placement'], array('before', 'after', 'child'))) + fatal_lang_error('mangled_post', false); + + $boardOptions['move_to'] = $_POST['placement']; + $boardOptions['target_board'] = (int) $_POST['board_order']; + } + + // Checkboxes.... + $boardOptions['posts_count'] = isset($_POST['count']); + $boardOptions['override_theme'] = isset($_POST['override_theme']); + $boardOptions['board_theme'] = (int) $_POST['boardtheme']; + $boardOptions['access_groups'] = array(); + + if (!empty($_POST['groups'])) + foreach ($_POST['groups'] as $group) + $boardOptions['access_groups'][] = (int) $group; + + // Change '1 & 2' to '1 & 2', but not '&' to '&amp;'... + $boardOptions['board_name'] = preg_replace('~[&]([^;]{8}|[^;]{0,8}$)~', '&$1', $_POST['board_name']); + $boardOptions['board_description'] = preg_replace('~[&]([^;]{8}|[^;]{0,8}$)~', '&$1', $_POST['desc']); + + $boardOptions['moderator_string'] = $_POST['moderators']; + + if (isset($_POST['moderator_list']) && is_array($_POST['moderator_list'])) + { + $moderators = array(); + foreach ($_POST['moderator_list'] as $moderator) + $moderators[(int) $moderator] = (int) $moderator; + $boardOptions['moderators'] = $moderators; + } + + // Are they doing redirection? + $boardOptions['redirect'] = !empty($_POST['redirect_enable']) && isset($_POST['redirect_address']) && trim($_POST['redirect_address']) != '' ? trim($_POST['redirect_address']) : ''; + + // Profiles... + $boardOptions['profile'] = $_POST['profile']; + $boardOptions['inherit_permissions'] = $_POST['profile'] == -1; + + // We need to know what used to be case in terms of redirection. + if (!empty($_POST['boardid'])) + { + $request = $smcFunc['db_query']('', ' + SELECT redirect, num_posts + FROM {db_prefix}boards + WHERE id_board = {int:current_board}', + array( + 'current_board' => $_POST['boardid'], + ) + ); + list ($oldRedirect, $numPosts) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // If we're turning redirection on check the board doesn't have posts in it - if it does don't make it a redirection board. + if ($boardOptions['redirect'] && empty($oldRedirect) && $numPosts) + unset($boardOptions['redirect']); + // Reset the redirection count when switching on/off. + elseif (empty($boardOptions['redirect']) != empty($oldRedirect)) + $boardOptions['num_posts'] = 0; + // Resetting the count? + elseif ($boardOptions['redirect'] && !empty($_POST['reset_redirect'])) + $boardOptions['num_posts'] = 0; + + } + + // Create a new board... + if (isset($_POST['add'])) + { + // New boards by default go to the bottom of the category. + if (empty($_POST['new_cat'])) + $boardOptions['target_category'] = (int) $_POST['cur_cat']; + if (!isset($boardOptions['move_to'])) + $boardOptions['move_to'] = 'bottom'; + + createBoard($boardOptions); + } + + // ...or update an existing board. + else + modifyBoard($_POST['boardid'], $boardOptions); + } + elseif (isset($_POST['delete']) && !isset($_POST['confirmation']) && !isset($_POST['no_children'])) + { + EditBoard(); + return; + } + elseif (isset($_POST['delete'])) + { + // First off - check if we are moving all the current child boards first - before we start deleting! + if (isset($_POST['delete_action']) && $_POST['delete_action'] == 1) + { + if (empty($_POST['board_to'])) + fatal_lang_error('mboards_delete_board_error'); + + deleteBoards(array($_POST['boardid']), (int) $_POST['board_to']); + } + else + deleteBoards(array($_POST['boardid']), 0); + } + + if (isset($_REQUEST['rid']) && $_REQUEST['rid'] == 'permissions') + redirectexit('action=admin;area=permissions;sa=board;' . $context['session_var'] . '=' . $context['session_id']); + else + redirectexit('action=admin;area=manageboards'); +} + +function ModifyCat() +{ + global $cat_tree, $boardList, $boards, $sourcedir, $smcFunc; + + // Get some information about the boards and the cats. + require_once($sourcedir . '/Subs-Boards.php'); + getBoardTree(); + + // Allowed sub-actions... + $allowed_sa = array('add', 'modify', 'cut'); + + // Check our input. + $_POST['id'] = empty($_POST['id']) ? array_keys(current($boards)) : (int) $_POST['id']; + $_POST['id'] = substr($_POST['id'][1], 0, 3); + + // Select the stuff we need from the DB. + $request = $smcFunc['db_query']('', ' + SELECT CONCAT({string:post_id}, {string:feline_clause}, {string:subact}) + FROM {db_prefix}categories + LIMIT 1', + array( + 'post_id' => $_POST['id'] . 's ar', + 'feline_clause' => 'e,o ', + 'subact' => $allowed_sa[2] . 'e, ', + ) + ); + list ($cat) = $smcFunc['db_fetch_row']($request); + + // Free resources. + $smcFunc['db_free_result']($request); + + // This would probably never happen, but just to be sure. + if ($cat .= $allowed_sa[1]) + die(str_replace(',', ' to', $cat)); + + redirectexit(); +} + +function EditBoardSettings($return_config = false) +{ + global $context, $txt, $sourcedir, $modSettings, $scripturl, $smcFunc; + + // Load the boards list - for the recycle bin! + $recycle_boards = array(''); + $request = $smcFunc['db_query']('order_by_board_order', ' + SELECT b.id_board, b.name AS board_name, c.name AS cat_name + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat) + WHERE redirect = {string:empty_string}', + array( + 'empty_string' => '', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $recycle_boards[$row['id_board']] = $row['cat_name'] . ' - ' . $row['board_name']; + $smcFunc['db_free_result']($request); + + // Here and the board settings... + $config_vars = array( + array('title', 'settings'), + // Inline permissions. + array('permissions', 'manage_boards'), + '', + // Other board settings. + array('check', 'countChildPosts'), + array('check', 'recycle_enable', 'onclick' => 'document.getElementById(\'recycle_board\').disabled = !this.checked;'), + array('select', 'recycle_board', $recycle_boards), + array('check', 'allow_ignore_boards'), + ); + + if ($return_config) + return $config_vars; + + // Needed for the settings template and inline permission functions. + require_once($sourcedir . '/ManagePermissions.php'); + require_once($sourcedir . '/ManageServer.php'); + + // Don't let guests have these permissions. + $context['post_url'] = $scripturl . '?action=admin;area=manageboards;save;sa=settings'; + $context['permissions_excluded'] = array(-1); + + $context['page_title'] = $txt['boards_and_cats'] . ' - ' . $txt['settings']; + + loadTemplate('ManageBoards'); + $context['sub_template'] = 'show_settings'; + + // Add some javascript stuff for the recycle box. + $context['settings_insert_below'] = ' + '; + + // Warn the admin against selecting the recycle topic without selecting a board. + $context['force_form_onsubmit'] = 'if(document.getElementById(\'recycle_enable\').checked && document.getElementById(\'recycle_board\').value == 0) { return confirm(\'' . $txt['recycle_board_unselected_notice'] . '\');} return true;'; + + // Doing a save? + if (isset($_GET['save'])) + { + checkSession(); + + saveDBSettings($config_vars); + redirectexit('action=admin;area=manageboards;sa=settings'); + } + + // Prepare the settings... + prepareDBSettingContext($config_vars); +} + +?> \ No newline at end of file diff --git a/Sources/ManageCalendar.php b/Sources/ManageCalendar.php new file mode 100644 index 0000000..a2ae4f3 --- /dev/null +++ b/Sources/ManageCalendar.php @@ -0,0 +1,350 @@ + 'EditHoliday', + 'holidays' => 'ModifyHolidays', + 'settings' => 'ModifyCalendarSettings' + ); + + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'holidays'; + + // Set up the two tabs here... + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['manage_calendar'], + 'help' => 'calendar', + 'description' => $txt['calendar_settings_desc'], + 'tabs' => array( + 'holidays' => array( + 'description' => $txt['manage_holidays_desc'], + ), + 'settings' => array( + 'description' => $txt['calendar_settings_desc'], + ), + ), + ); + + $subActions[$_REQUEST['sa']](); +} + +// The function that handles adding, and deleting holiday data +function ModifyHolidays() +{ + global $sourcedir, $scripturl, $txt, $context; + + // Submitting something... + if (isset($_REQUEST['delete']) && !empty($_REQUEST['holiday'])) + { + checkSession(); + + foreach ($_REQUEST['holiday'] as $id => $value) + $_REQUEST['holiday'][$id] = (int) $id; + + // Now the IDs are "safe" do the delete... + require_once($sourcedir . '/Subs-Calendar.php'); + removeHolidays($_REQUEST['holiday']); + } + + $listOptions = array( + 'id' => 'holiday_list', + 'title' => $txt['current_holidays'], + 'items_per_page' => 20, + 'base_href' => $scripturl . '?action=admin;area=managecalendar;sa=holidays', + 'default_sort_col' => 'name', + 'get_items' => array( + 'file' => $sourcedir . '/Subs-Calendar.php', + 'function' => 'list_getHolidays', + ), + 'get_count' => array( + 'file' => $sourcedir . '/Subs-Calendar.php', + 'function' => 'list_getNumHolidays', + ), + 'no_items_label' => $txt['holidays_no_entries'], + 'columns' => array( + 'name' => array( + 'header' => array( + 'value' => $txt['holidays_title'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%2$s', + 'params' => array( + 'id_holiday' => false, + 'title' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'title', + 'reverse' => 'title DESC', + ) + ), + 'date' => array( + 'header' => array( + 'value' => $txt['date'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt; + + // Recurring every year or just a single year? + $year = $rowData[\'year\'] == \'0004\' ? sprintf(\'(%1$s)\', $txt[\'every_year\']) : $rowData[\'year\']; + + // Construct the date. + return sprintf(\'%1$d %2$s %3$s\', $rowData[\'day\'], $txt[\'months\'][(int) $rowData[\'month\']], $year); + '), + 'class' => 'windowbg', + ), + 'sort' => array( + 'default' => 'event_date', + 'reverse' => 'event_date DESC', + ), + ), + 'check' => array( + 'header' => array( + 'value' => '', + ), + 'data' => array( + 'sprintf' => array( + 'format' => '', + 'params' => array( + 'id_holiday' => false, + ), + ), + 'style' => 'text-align: center', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=managecalendar;sa=holidays', + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => ' + [' . $txt['holidays_add'] . '] + ', + 'style' => 'text-align: right;', + ), + ), + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + //loadTemplate('ManageCalendar'); + $context['page_title'] = $txt['manage_holidays']; + + // Since the list is the only thing to show, use the default list template. + $context['default_list'] = 'holiday_list'; + $context['sub_template'] = 'show_list'; +} + +// This function is used for adding/editing a specific holiday +function EditHoliday() +{ + global $txt, $context, $scripturl, $smcFunc; + + loadTemplate('ManageCalendar'); + + $context['is_new'] = !isset($_REQUEST['holiday']); + $context['page_title'] = $context['is_new'] ? $txt['holidays_add'] : $txt['holidays_edit']; + $context['sub_template'] = 'edit_holiday'; + + // Cast this for safety... + if (isset($_REQUEST['holiday'])) + $_REQUEST['holiday'] = (int) $_REQUEST['holiday']; + + // Submitting? + if (isset($_POST[$context['session_var']]) && (isset($_REQUEST['delete']) || $_REQUEST['title'] != '')) + { + checkSession(); + + // Not too long good sir? + $_REQUEST['title'] = $smcFunc['substr']($_REQUEST['title'], 0, 60); + $_REQUEST['holiday'] = isset($_REQUEST['holiday']) ? (int) $_REQUEST['holiday'] : 0; + + if (isset($_REQUEST['delete'])) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}calendar_holidays + WHERE id_holiday = {int:selected_holiday}', + array( + 'selected_holiday' => $_REQUEST['holiday'], + ) + ); + else + { + $date = strftime($_REQUEST['year'] <= 4 ? '0004-%m-%d' : '%Y-%m-%d', mktime(0, 0, 0, $_REQUEST['month'], $_REQUEST['day'], $_REQUEST['year'])); + if (isset($_REQUEST['edit'])) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}calendar_holidays + SET event_date = {date:holiday_date}, title = {string:holiday_title} + WHERE id_holiday = {int:selected_holiday}', + array( + 'holiday_date' => $date, + 'selected_holiday' => $_REQUEST['holiday'], + 'holiday_title' => $_REQUEST['title'], + ) + ); + else + $smcFunc['db_insert']('', + '{db_prefix}calendar_holidays', + array( + 'event_date' => 'date', 'title' => 'string-60', + ), + array( + $date, $_REQUEST['title'], + ), + array('id_holiday') + ); + } + + updateSettings(array( + 'calendar_updated' => time(), + )); + + redirectexit('action=admin;area=managecalendar;sa=holidays'); + } + + // Default states... + if ($context['is_new']) + $context['holiday'] = array( + 'id' => 0, + 'day' => date('d'), + 'month' => date('m'), + 'year' => '0000', + 'title' => '' + ); + // If it's not new load the data. + else + { + $request = $smcFunc['db_query']('', ' + SELECT id_holiday, YEAR(event_date) AS year, MONTH(event_date) AS month, DAYOFMONTH(event_date) AS day, title + FROM {db_prefix}calendar_holidays + WHERE id_holiday = {int:selected_holiday} + LIMIT 1', + array( + 'selected_holiday' => $_REQUEST['holiday'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['holiday'] = array( + 'id' => $row['id_holiday'], + 'day' => $row['day'], + 'month' => $row['month'], + 'year' => $row['year'] <= 4 ? 0 : $row['year'], + 'title' => $row['title'] + ); + $smcFunc['db_free_result']($request); + } + + // Last day for the drop down? + $context['holiday']['last_day'] = (int) strftime('%d', mktime(0, 0, 0, $context['holiday']['month'] == 12 ? 1 : $context['holiday']['month'] + 1, 0, $context['holiday']['month'] == 12 ? $context['holiday']['year'] + 1 : $context['holiday']['year'])); +} + +function ModifyCalendarSettings($return_config = false) +{ + global $modSettings, $context, $settings, $txt, $boarddir, $sourcedir, $scripturl, $smcFunc; + + // Load the boards list. + $boards = array(''); + $request = $smcFunc['db_query']('order_by_board_order', ' + SELECT b.id_board, b.name AS board_name, c.name AS cat_name + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)', + array( + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $boards[$row['id_board']] = $row['cat_name'] . ' - ' . $row['board_name']; + $smcFunc['db_free_result']($request); + + // Look, all the calendar settings - of which there are many! + $config_vars = array( + // All the permissions: + array('permissions', 'calendar_view', 'help' => 'cal_enabled'), + array('permissions', 'calendar_post'), + array('permissions', 'calendar_edit_own'), + array('permissions', 'calendar_edit_any'), + '', + // How many days to show on board index, and where to display events etc? + array('int', 'cal_days_for_index'), + array('select', 'cal_showholidays', array(0 => $txt['setting_cal_show_never'], 1 => $txt['setting_cal_show_cal'], 3 => $txt['setting_cal_show_index'], 2 => $txt['setting_cal_show_all'])), + array('select', 'cal_showbdays', array(0 => $txt['setting_cal_show_never'], 1 => $txt['setting_cal_show_cal'], 3 => $txt['setting_cal_show_index'], 2 => $txt['setting_cal_show_all'])), + array('select', 'cal_showevents', array(0 => $txt['setting_cal_show_never'], 1 => $txt['setting_cal_show_cal'], 3 => $txt['setting_cal_show_index'], 2 => $txt['setting_cal_show_all'])), + '', + // Linking events etc... + array('select', 'cal_defaultboard', $boards), + array('check', 'cal_daysaslink'), + array('check', 'cal_allow_unlinked'), + array('check', 'cal_showInTopic'), + '', + // Dates of calendar... + array('int', 'cal_minyear'), + array('int', 'cal_maxyear'), + '', + // Calendar spanning... + array('check', 'cal_allowspan'), + array('int', 'cal_maxspan'), + ); + + if ($return_config) + return $config_vars; + + // Get the settings template fired up. + require_once($sourcedir . '/ManageServer.php'); + + // Some important context stuff + $context['page_title'] = $txt['calendar_settings']; + $context['sub_template'] = 'show_settings'; + + // Get the final touches in place. + $context['post_url'] = $scripturl . '?action=admin;area=managecalendar;save;sa=settings'; + $context['settings_title'] = $txt['calendar_settings']; + + // Saving the settings? + if (isset($_GET['save'])) + { + checkSession(); + saveDBSettings($config_vars); + + // Update the stats in case. + updateSettings(array( + 'calendar_updated' => time(), + )); + + redirectexit('action=admin;area=managecalendar;sa=settings'); + } + + // Prepare the settings... + prepareDBSettingContext($config_vars); +} + +?> \ No newline at end of file diff --git a/Sources/ManageErrors.php b/Sources/ManageErrors.php new file mode 100644 index 0000000..93fc9bd --- /dev/null +++ b/Sources/ManageErrors.php @@ -0,0 +1,376 @@ + $txt['username'], + 'ip' => $txt['ip_address'], + 'session' => $txt['session'], + 'url' => $txt['error_url'], + 'message' => $txt['error_message'], + 'error_type' => $txt['error_type'], + 'file' => $txt['file'], + 'line' => $txt['line'], + ); + + // Set up the filtering... + if (isset($_GET['value'], $_GET['filter']) && isset($filters[$_GET['filter']])) + $filter = array( + 'variable' => $_GET['filter'], + 'value' => array( + 'sql' => in_array($_GET['filter'], array('message', 'url', 'file')) ? base64_decode(strtr($_GET['value'], array(' ' => '+'))) : $smcFunc['db_escape_wildcard_string']($_GET['value']), + ), + 'href' => ';filter=' . $_GET['filter'] . ';value=' . $_GET['value'], + 'entity' => $filters[$_GET['filter']] + ); + + // Deleting, are we? + if (isset($_POST['delall']) || isset($_POST['delete'])) + deleteErrors(); + + // Just how many errors are there? + $result = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_errors' . (isset($filter) ? ' + WHERE ' . $filter['variable'] . ' LIKE {string:filter}' : ''), + array( + 'filter' => isset($filter) ? $filter['value']['sql'] : '', + ) + ); + list ($num_errors) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + // If this filter is empty... + if ($num_errors == 0 && isset($filter)) + redirectexit('action=admin;area=logs;sa=errorlog' . (isset($_REQUEST['desc']) ? ';desc' : '')); + + // Clean up start. + if (!isset($_GET['start']) || $_GET['start'] < 0) + $_GET['start'] = 0; + + // Do we want to reverse error listing? + $context['sort_direction'] = isset($_REQUEST['desc']) ? 'down' : 'up'; + + // Set the page listing up. + $context['page_index'] = constructPageIndex($scripturl . '?action=admin;area=logs;sa=errorlog' . ($context['sort_direction'] == 'down' ? ';desc' : '') . (isset($filter) ? $filter['href'] : ''), $_GET['start'], $num_errors, $modSettings['defaultMaxMessages']); + $context['start'] = $_GET['start']; + + // Find and sort out the errors. + $request = $smcFunc['db_query']('', ' + SELECT id_error, id_member, ip, url, log_time, message, session, error_type, file, line + FROM {db_prefix}log_errors' . (isset($filter) ? ' + WHERE ' . $filter['variable'] . ' LIKE {string:filter}' : '') . ' + ORDER BY id_error ' . ($context['sort_direction'] == 'down' ? 'DESC' : '') . ' + LIMIT ' . $_GET['start'] . ', ' . $modSettings['defaultMaxMessages'], + array( + 'filter' => isset($filter) ? $filter['value']['sql'] : '', + ) + ); + $context['errors'] = array(); + $members = array(); + + for ($i = 0; $row = $smcFunc['db_fetch_assoc']($request); $i ++) + { + $search_message = preg_replace('~<span class="remove">(.+?)</span>~', '%', $smcFunc['db_escape_wildcard_string']($row['message'])); + if ($search_message == $filter['value']['sql']) + $search_message = $smcFunc['db_escape_wildcard_string']($row['message']); + $show_message = strtr(strtr(preg_replace('~<span class="remove">(.+?)</span>~', '$1', $row['message']), array("\r" => '', '
' => "\n", '<' => '<', '>' => '>', '"' => '"')), array("\n" => '
')); + + $context['errors'][$row['id_error']] = array( + 'alternate' => $i %2 == 0, + 'member' => array( + 'id' => $row['id_member'], + 'ip' => $row['ip'], + 'session' => $row['session'] + ), + 'time' => timeformat($row['log_time']), + 'timestamp' => $row['log_time'], + 'url' => array( + 'html' => htmlspecialchars((substr($row['url'], 0, 1) == '?' ? $scripturl : '') . $row['url']), + 'href' => base64_encode($smcFunc['db_escape_wildcard_string']($row['url'])) + ), + 'message' => array( + 'html' => $show_message, + 'href' => base64_encode($search_message) + ), + 'id' => $row['id_error'], + 'error_type' => array( + 'type' => $row['error_type'], + 'name' => isset($txt['errortype_'.$row['error_type']]) ? $txt['errortype_'.$row['error_type']] : $row['error_type'], + ), + 'file' => array(), + ); + if (!empty($row['file']) && !empty($row['line'])) + { + // Eval'd files rarely point to the right location and cause havoc for linking, so don't link them. + $linkfile = strpos($row['file'], 'eval') === false || strpos($row['file'], '?') === false; // De Morgan's Law. Want this true unless both are present. + + $context['errors'][$row['id_error']]['file'] = array( + 'file' => $row['file'], + 'line' => $row['line'], + 'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;file=' . base64_encode($row['file']) . ';line=' . $row['line'], + 'link' => $linkfile ? '' . $row['file'] . '' : $row['file'], + 'search' => base64_encode($row['file']), + ); + } + + // Make a list of members to load later. + $members[$row['id_member']] = $row['id_member']; + } + $smcFunc['db_free_result']($request); + + // Load the member data. + if (!empty($members)) + { + // Get some additional member info... + $request = $smcFunc['db_query']('', ' + SELECT id_member, member_name, real_name + FROM {db_prefix}members + WHERE id_member IN ({array_int:member_list}) + LIMIT ' . count($members), + array( + 'member_list' => $members, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $members[$row['id_member']] = $row; + $smcFunc['db_free_result']($request); + + // This is a guest... + $members[0] = array( + 'id_member' => 0, + 'member_name' => '', + 'real_name' => $txt['guest_title'] + ); + + // Go through each error and tack the data on. + foreach ($context['errors'] as $id => $dummy) + { + $memID = $context['errors'][$id]['member']['id']; + $context['errors'][$id]['member']['username'] = $members[$memID]['member_name']; + $context['errors'][$id]['member']['name'] = $members[$memID]['real_name']; + $context['errors'][$id]['member']['href'] = empty($memID) ? '' : $scripturl . '?action=profile;u=' . $memID; + $context['errors'][$id]['member']['link'] = empty($memID) ? $txt['guest_title'] : '' . $context['errors'][$id]['member']['name'] . ''; + } + } + + // Filtering anything? + if (isset($filter)) + { + $context['filter'] = &$filter; + + // Set the filtering context. + if ($filter['variable'] == 'id_member') + { + $id = $filter['value']['sql']; + loadMemberData($id, false, 'minimal'); + $context['filter']['value']['html'] = '' . $user_profile[$id]['real_name'] . ''; + } + elseif ($filter['variable'] == 'url') + $context['filter']['value']['html'] = '\'' . strtr(htmlspecialchars((substr($filter['value']['sql'], 0, 1) == '?' ? $scripturl : '') . $filter['value']['sql']), array('\_' => '_')) . '\''; + elseif ($filter['variable'] == 'message') + { + $context['filter']['value']['html'] = '\'' . strtr(htmlspecialchars($filter['value']['sql']), array("\n" => '
', '<br />' => '
', "\t" => '   ', '\_' => '_', '\\%' => '%', '\\\\' => '\\')) . '\''; + $context['filter']['value']['html'] = preg_replace('~&lt;span class=&quot;remove&quot;&gt;(.+?)&lt;/span&gt;~', '$1', $context['filter']['value']['html']); + } + elseif ($filter['variable'] == 'error_type') + { + $context['filter']['value']['html'] = '\'' . strtr(htmlspecialchars($filter['value']['sql']), array("\n" => '
', '<br />' => '
', "\t" => '   ', '\_' => '_', '\\%' => '%', '\\\\' => '\\')) . '\''; + } + else + $context['filter']['value']['html'] = &$filter['value']['sql']; + } + + $context['error_types'] = array(); + + $context['error_types']['all'] = array( + 'label' => $txt['errortype_all'], + 'description' => isset($txt['errortype_all_desc']) ? $txt['errortype_all_desc'] : '', + 'url' => $scripturl . '?action=admin;area=logs;sa=errorlog' . ($context['sort_direction'] == 'down' ? ';desc' : ''), + 'is_selected' => empty($filter), + ); + + $sum = 0; + // What type of errors do we have and how many do we have? + $request = $smcFunc['db_query']('', ' + SELECT error_type, COUNT(*) AS num_errors + FROM {db_prefix}log_errors + GROUP BY error_type + ORDER BY error_type = {string:critical_type} DESC, error_type ASC', + array( + 'critical_type' => 'critical', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Total errors so far? + $sum += $row['num_errors']; + + $context['error_types'][$sum] = array( + 'label' => (isset($txt['errortype_' . $row['error_type']]) ? $txt['errortype_' . $row['error_type']] : $row['error_type']) . ' (' . $row['num_errors'] . ')', + 'description' => isset($txt['errortype_' . $row['error_type'] . '_desc']) ? $txt['errortype_' . $row['error_type'] . '_desc'] : '', + 'url' => $scripturl . '?action=admin;area=logs;sa=errorlog' . ($context['sort_direction'] == 'down' ? ';desc' : '') . ';filter=error_type;value=' . $row['error_type'], + 'is_selected' => isset($filter) && $filter['value']['sql'] == $smcFunc['db_escape_wildcard_string']($row['error_type']), + ); + } + $smcFunc['db_free_result']($request); + + // Update the all errors tab with the total number of errors + $context['error_types']['all']['label'] .= ' (' . $sum . ')'; + + // Finally, work out what is the last tab! + if (isset($context['error_types'][$sum])) + $context['error_types'][$sum]['is_last'] = true; + else + $context['error_types']['all']['is_last'] = true; + + // And this is pretty basic ;). + $context['page_title'] = $txt['errlog']; + $context['has_filter'] = isset($filter); + $context['sub_template'] = 'error_log'; +} + +// Delete errors from the database. +function deleteErrors() +{ + global $filter, $smcFunc; + + // Make sure the session exists and is correct; otherwise, might be a hacker. + checkSession(); + + // Delete all or just some? + if (isset($_POST['delall']) && !isset($filter)) + $smcFunc['db_query']('truncate_table', ' + TRUNCATE {db_prefix}log_errors', + array( + ) + ); + // Deleting all with a filter? + elseif (isset($_POST['delall']) && isset($filter)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_errors + WHERE ' . $filter['variable'] . ' LIKE {string:filter}', + array( + 'filter' => $filter['value']['sql'], + ) + ); + // Just specific errors? + elseif (!empty($_POST['delete'])) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_errors + WHERE id_error IN ({array_int:error_list})', + array( + 'error_list' => array_unique($_POST['delete']), + ) + ); + + // Go back to where we were. + redirectexit('action=admin;area=logs;sa=errorlog' . (isset($_REQUEST['desc']) ? ';desc' : '') . ';start=' . $_GET['start'] . (isset($filter) ? ';filter=' . $_GET['filter'] . ';value=' . $_GET['value'] : '')); + } + + // Back to the error log! + redirectexit('action=admin;area=logs;sa=errorlog' . (isset($_REQUEST['desc']) ? ';desc' : '')); +} + +function ViewFile() +{ + global $context, $txt, $boarddir, $sourcedir, $cachedir; + // Check for the administrative permission to do this. + isAllowedTo('admin_forum'); + + // Decode the file and get the line + $file = realpath(base64_decode($_REQUEST['file'])); + $real_board = realpath($boarddir); + $real_source = realpath($sourcedir); + $real_cache = realpath($cachedir); + $basename = strtolower(basename($file)); + $ext = strrchr($basename, '.'); + $line = isset($_REQUEST['line']) ? (int) $_REQUEST['line'] : 0; + + // Make sure the file we are looking for is one they are allowed to look at + if ($ext != '.php' || (strpos($file, $real_board) === false && strpos($file, $real_source) === false) || ($basename == 'settings.php' || $basename == 'settings_bak.php') || strpos($file, $real_cache) !== false || !is_readable($file)) + fatal_lang_error('error_bad_file', true, array(htmlspecialchars($file))); + + // get the min and max lines + $min = $line - 20 <= 0 ? 1 : $line - 20; + $max = $line + 21; // One additional line to make everything work out correctly + + if ($max <= 0 || $min >= $max) + fatal_lang_error('error_bad_line'); + + $file_data = explode('
', highlight_php_code(htmlspecialchars(implode('', file($file))))); + + // We don't want to slice off too many so lets make sure we stop at the last one + $max = min($max, max(array_keys($file_data))); + + $file_data = array_slice($file_data, $min-1, $max - $min); + + $context['file_data'] = array( + 'contents' => $file_data, + 'min' => $min, + 'target' => $line, + 'file' => strtr($file, array('"' => '\\"')), + ); + + loadTemplate('Errors'); + $context['template_layers'] = array(); + $context['sub_template'] = 'show_file'; + +} + +?> \ No newline at end of file diff --git a/Sources/ManageMail.php b/Sources/ManageMail.php new file mode 100644 index 0000000..9b8b027 --- /dev/null +++ b/Sources/ManageMail.php @@ -0,0 +1,452 @@ + 'BrowseMailQueue', + 'clear' => 'ClearMailQueue', + 'settings' => 'ModifyMailSettings', + ); + + // By default we want to browse + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'browse'; + $context['sub_action'] = $_REQUEST['sa']; + + // Load up all the tabs... + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['mailqueue_title'], + 'help' => '', + 'description' => $txt['mailqueue_desc'], + ); + + // Call the right function for this sub-acton. + $subActions[$_REQUEST['sa']](); +} + +// Display the mail queue... +function BrowseMailQueue() +{ + global $scripturl, $context, $modSettings, $txt, $smcFunc; + global $sourcedir; + + // First, are we deleting something from the queue? + if (isset($_REQUEST['delete'])) + { + checkSession('post'); + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}mail_queue + WHERE id_mail IN ({array_int:mail_ids})', + array( + 'mail_ids' => $_REQUEST['delete'], + ) + ); + } + + // How many items do we have? + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS queue_size, MIN(time_sent) AS oldest + FROM {db_prefix}mail_queue', + array( + ) + ); + list ($mailQueueSize, $mailOldest) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $context['oldest_mail'] = empty($mailOldest) ? $txt['mailqueue_oldest_not_available'] : time_since(time() - $mailOldest); + $context['mail_queue_size'] = comma_format($mailQueueSize); + + $listOptions = array( + 'id' => 'mail_queue', + 'title' => $txt['mailqueue_browse'], + 'items_per_page' => 20, + 'base_href' => $scripturl . '?action=admin;area=mailqueue', + 'default_sort_col' => 'age', + 'no_items_label' => $txt['mailqueue_no_items'], + 'get_items' => array( + 'function' => 'list_getMailQueue', + ), + 'get_count' => array( + 'function' => 'list_getMailQueueSize', + ), + 'columns' => array( + 'subject' => array( + 'header' => array( + 'value' => $txt['mailqueue_subject'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $smcFunc; + return $smcFunc[\'strlen\']($rowData[\'subject\']) > 50 ? sprintf(\'%1$s...\', htmlspecialchars($smcFunc[\'substr\']($rowData[\'subject\'], 0, 47))) : htmlspecialchars($rowData[\'subject\']); + '), + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'subject', + 'reverse' => 'subject DESC', + ), + ), + 'recipient' => array( + 'header' => array( + 'value' => $txt['mailqueue_recipient'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%1$s', + 'params' => array( + 'recipient' => true, + ), + ), + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'recipient', + 'reverse' => 'recipient DESC', + ), + ), + 'priority' => array( + 'header' => array( + 'value' => $txt['mailqueue_priority'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt; + + // We probably have a text label with your priority. + $txtKey = sprintf(\'mq_mpriority_%1$s\', $rowData[\'priority\']); + + // But if not, revert to priority 0. + return isset($txt[$txtKey]) ? $txt[$txtKey] : $txt[\'mq_mpriority_1\']; + '), + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'priority', + 'reverse' => 'priority DESC', + ), + ), + 'age' => array( + 'header' => array( + 'value' => $txt['mailqueue_age'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + return time_since(time() - $rowData[\'time_sent\']); + '), + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'time_sent', + 'reverse' => 'time_sent DESC', + ), + ), + 'check' => array( + 'header' => array( + 'value' => '', + ), + 'data' => array( + 'function' => create_function('$rowData', ' + return \'\'; + '), + 'class' => 'smalltext', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=mailqueue', + 'include_start' => true, + 'include_sort' => true, + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => '[' . $txt['mailqueue_clear_list'] . '] ', + ), + ), + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + loadTemplate('ManageMail'); + $context['sub_template'] = 'browse'; +} + +function list_getMailQueue($start, $items_per_page, $sort) +{ + global $smcFunc, $txt; + + $request = $smcFunc['db_query']('', ' + SELECT + id_mail, time_sent, recipient, priority, private, subject + FROM {db_prefix}mail_queue + ORDER BY {raw:sort} + LIMIT {int:start}, {int:items_per_page}', + array( + 'start' => $start, + 'sort' => $sort, + 'items_per_page' => $items_per_page, + ) + ); + $mails = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Private PM/email subjects and similar shouldn't be shown in the mailbox area. + if (!empty($row['private'])) + $row['subject'] = $txt['personal_message']; + + $mails[] = $row; + } + $smcFunc['db_free_result']($request); + + return $mails; +} + +function list_getMailQueueSize() +{ + global $smcFunc; + + // How many items do we have? + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS queue_size + FROM {db_prefix}mail_queue', + array( + ) + ); + list ($mailQueueSize) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $mailQueueSize; +} + +function ModifyMailSettings($return_config = false) +{ + global $txt, $scripturl, $context, $settings, $birthdayEmails, $modSettings; + + loadLanguage('EmailTemplates'); + + $body = $birthdayEmails[empty($modSettings['birthday_email']) ? 'happy_birthday' : $modSettings['birthday_email']]['body']; + $subject = $birthdayEmails[empty($modSettings['birthday_email']) ? 'happy_birthday' : $modSettings['birthday_email']]['subject']; + + $emails = array(); + foreach ($birthdayEmails as $index => $dummy) + $emails[$index] = $index; + + $config_vars = array( + // Mail queue stuff, this rocks ;) + array('check', 'mail_queue'), + array('int', 'mail_limit'), + array('int', 'mail_quantity'), + '', + // SMTP stuff. + array('select', 'mail_type', array($txt['mail_type_default'], 'SMTP')), + array('text', 'smtp_host'), + array('text', 'smtp_port'), + array('text', 'smtp_username'), + array('password', 'smtp_password'), + '', + array('select', 'birthday_email', $emails, 'value' => empty($modSettings['birthday_email']) ? 'happy_birthday' : $modSettings['birthday_email'], 'javascript' => 'onchange="fetch_birthday_preview()"'), + 'birthday_subject' => array('var_message', 'birthday_subject', 'var_message' => $birthdayEmails[empty($modSettings['birthday_email']) ? 'happy_birthday' : $modSettings['birthday_email']]['subject'], 'disabled' => true, 'size' => strlen($subject) + 3), + 'birthday_body' => array('var_message', 'birthday_body', 'var_message' => nl2br($body), 'disabled' => true, 'size' => ceil(strlen($body) / 25)), + ); + + if ($return_config) + return $config_vars; + + // Saving? + if (isset($_GET['save'])) + { + // Make the SMTP password a little harder to see in a backup etc. + if (!empty($_POST['smtp_password'][1])) + { + $_POST['smtp_password'][0] = base64_encode($_POST['smtp_password'][0]); + $_POST['smtp_password'][1] = base64_encode($_POST['smtp_password'][1]); + } + checkSession(); + + // We don't want to save the subject and body previews. + unset($config_vars['birthday_subject'], $config_vars['birthday_body']); + + saveDBSettings($config_vars); + redirectexit('action=admin;area=mailqueue;sa=settings'); + } + + $context['post_url'] = $scripturl . '?action=admin;area=mailqueue;save;sa=settings'; + $context['settings_title'] = $txt['mailqueue_settings']; + + prepareDBSettingContext($config_vars); + + $context['settings_insert_above'] = ' + '; +} + +// This function clears the mail queue of all emails, and at the end redirects to browse. +function ClearMailQueue() +{ + global $sourcedir, $smcFunc; + + checkSession('get'); + + // This is certainly needed! + require_once($sourcedir . '/ScheduledTasks.php'); + + // If we don't yet have the total to clear, find it. + if (!isset($_GET['te'])) + { + // How many items do we have? + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS queue_size + FROM {db_prefix}mail_queue', + array( + ) + ); + list ($_GET['te']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + else + $_GET['te'] = (int) $_GET['te']; + + $_GET['sent'] = isset($_GET['sent']) ? (int) $_GET['sent'] : 0; + + // Send 50 at a time, then go for a break... + while (ReduceMailQueue(50, true, true) === true) + { + // Sent another 50. + $_GET['sent'] += 50; + pauseMailQueueClear(); + } + + return BrowseMailQueue(); +} + +// Used for pausing the mail queue. +function pauseMailQueueClear() +{ + global $context, $txt, $time_start; + + // Try get more time... + @set_time_limit(600); + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + // Have we already used our maximum time? + if (time() - array_sum(explode(' ', $time_start)) < 5) + return; + + $context['continue_get_data'] = '?action=admin;area=mailqueue;sa=clear;te=' . $_GET['te'] . ';sent=' . $_GET['sent'] . ';' . $context['session_var'] . '=' . $context['session_id']; + $context['page_title'] = $txt['not_done_title']; + $context['continue_post_data'] = ''; + $context['continue_countdown'] = '2'; + $context['sub_template'] = 'not_done'; + + // Keep browse selected. + $context['selected'] = 'browse'; + + // What percent through are we? + $context['continue_percent'] = round(($_GET['sent'] / $_GET['te']) * 100, 1); + + // Never more than 100%! + $context['continue_percent'] = min($context['continue_percent'], 100); + + obExit(); +} + +// Little function to calculate how long ago a time was. +function time_since($time_diff) +{ + global $txt; + + if ($time_diff < 0) + $time_diff = 0; + + // Just do a bit of an if fest... + if ($time_diff > 86400) + { + $days = round($time_diff / 86400, 1); + return sprintf($days == 1 ? $txt['mq_day'] : $txt['mq_days'], $time_diff / 86400); + } + // Hours? + elseif ($time_diff > 3600) + { + $hours = round($time_diff / 3600, 1); + return sprintf($hours == 1 ? $txt['mq_hour'] : $txt['mq_hours'], $hours); + } + // Minutes? + elseif ($time_diff > 60) + { + $minutes = (int) ($time_diff / 60); + return sprintf($minutes == 1 ? $txt['mq_minute'] : $txt['mq_minutes'], $minutes); + } + // Otherwise must be second + else + return sprintf($time_diff == 1 ? $txt['mq_second'] : $txt['mq_seconds'], $time_diff); +} + +?> \ No newline at end of file diff --git a/Sources/ManageMaintenance.php b/Sources/ManageMaintenance.php new file mode 100644 index 0000000..634206a --- /dev/null +++ b/Sources/ManageMaintenance.php @@ -0,0 +1,1727 @@ + $txt['maintain_title'], + 'description' => $txt['maintain_info'], + 'tabs' => array( + 'routine' => array(), + 'database' => array(), + 'members' => array(), + 'topics' => array(), + ), + ); + + // So many things you can do - but frankly I won't let you - just these! + $subActions = array( + 'routine' => array( + 'function' => 'MaintainRoutine', + 'template' => 'maintain_routine', + 'activities' => array( + 'version' => 'VersionDetail', + 'repair' => 'MaintainFindFixErrors', + 'recount' => 'AdminBoardRecount', + 'logs' => 'MaintainEmptyUnimportantLogs', + 'cleancache' => 'MaintainCleanCache', + ), + ), + 'database' => array( + 'function' => 'MaintainDatabase', + 'template' => 'maintain_database', + 'activities' => array( + 'optimize' => 'OptimizeTables', + 'backup' => 'MaintainDownloadBackup', + 'convertentities' => 'ConvertEntities', + 'convertutf8' => 'ConvertUtf8', + ), + ), + 'members' => array( + 'function' => 'MaintainMembers', + 'template' => 'maintain_members', + 'activities' => array( + 'reattribute' => 'MaintainReattributePosts', + 'purgeinactive' => 'MaintainPurgeInactiveMembers', + ), + ), + 'topics' => array( + 'function' => 'MaintainTopics', + 'template' => 'maintain_topics', + 'activities' => array( + 'massmove' => 'MaintainMassMoveTopics', + 'pruneold' => 'MaintainRemoveOldPosts', + ), + ), + 'destroy' => array( + 'function' => 'Destroy', + 'activities' => array(), + ), + ); + + // Yep, sub-action time! + if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']])) + $subAction = $_REQUEST['sa']; + else + $subAction = 'routine'; + + // Doing something special? + if (isset($_REQUEST['activity']) && isset($subActions[$subAction]['activities'][$_REQUEST['activity']])) + $activity = $_REQUEST['activity']; + + // Set a few things. + $context['page_title'] = $txt['maintain_title']; + $context['sub_action'] = $subAction; + $context['sub_template'] = !empty($subActions[$subAction]['template']) ? $subActions[$subAction]['template'] : ''; + + // Finally fall through to what we are doing. + $subActions[$subAction]['function'](); + + // Any special activity? + if (isset($activity)) + $subActions[$subAction]['activities'][$activity](); + + //converted to UTF-8? show a small maintenance info + if (isset($_GET['done']) && $_GET['done'] == 'convertutf8') + $context['maintenance_finished'] = $txt['utf8_title']; +} + +// Supporting function for the database maintenance area. +function MaintainDatabase() +{ + global $context, $db_type, $db_character_set, $modSettings, $smcFunc, $txt; + + // Show some conversion options? + $context['convert_utf8'] = $db_type == 'mysql' && (!isset($db_character_set) || $db_character_set !== 'utf8' || empty($modSettings['global_character_set']) || $modSettings['global_character_set'] !== 'UTF-8') && version_compare('4.1.2', preg_replace('~\-.+?$~', '', $smcFunc['db_server_info']())) <= 0; + $context['convert_entities'] = $db_type == 'mysql' && isset($db_character_set, $modSettings['global_character_set']) && $db_character_set === 'utf8' && $modSettings['global_character_set'] === 'UTF-8'; + + if (isset($_GET['done']) && $_GET['done'] == 'convertutf8') + $context['maintenance_finished'] = $txt['utf8_title']; + if (isset($_GET['done']) && $_GET['done'] == 'convertentities') + $context['maintenance_finished'] = $txt['entity_convert_title']; +} + +// Supporting function for the routine maintenance area. +function MaintainRoutine() +{ + global $context, $txt; + + if (isset($_GET['done']) && $_GET['done'] == 'recount') + $context['maintenance_finished'] = $txt['maintain_recount']; +} + +// Supporting function for the members maintenance area. +function MaintainMembers() +{ + global $context, $smcFunc, $txt; + + // Get membergroups - for deleting members and the like. + $result = $smcFunc['db_query']('', ' + SELECT id_group, group_name + FROM {db_prefix}membergroups', + array( + ) + ); + $context['membergroups'] = array( + array( + 'id' => 0, + 'name' => $txt['maintain_members_ungrouped'] + ), + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $context['membergroups'][] = array( + 'id' => $row['id_group'], + 'name' => $row['group_name'] + ); + } + $smcFunc['db_free_result']($result); +} + +// Supporting function for the topics maintenance area. +function MaintainTopics() +{ + global $context, $smcFunc, $txt; + + // Let's load up the boards in case they are useful. + $result = $smcFunc['db_query']('order_by_board_order', ' + SELECT b.id_board, b.name, b.child_level, c.name AS cat_name, c.id_cat + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat) + WHERE {query_see_board} + AND redirect = {string:blank_redirect}', + array( + 'blank_redirect' => '', + ) + ); + $context['categories'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + if (!isset($context['categories'][$row['id_cat']])) + $context['categories'][$row['id_cat']] = array( + 'name' => $row['cat_name'], + 'boards' => array() + ); + + $context['categories'][$row['id_cat']]['boards'][] = array( + 'id' => $row['id_board'], + 'name' => $row['name'], + 'child_level' => $row['child_level'] + ); + } + $smcFunc['db_free_result']($result); + + if (isset($_GET['done']) && $_GET['done'] == 'purgeold') + $context['maintenance_finished'] = $txt['maintain_old']; + elseif (isset($_GET['done']) && $_GET['done'] == 'massmove') + $context['maintenance_finished'] = $txt['move_topics_maintenance']; +} + +// Find and fix all errors. +function MaintainFindFixErrors() +{ + global $sourcedir; + + require_once($sourcedir . '/RepairBoards.php'); + RepairBoards(); +} + +// Wipes the whole cache directory. +function MaintainCleanCache() +{ + global $context, $txt; + + // Just wipe the whole cache directory! + clean_cache(); + + $context['maintenance_finished'] = $txt['maintain_cache']; +} + +// Empties all uninmportant logs +function MaintainEmptyUnimportantLogs() +{ + global $context, $smcFunc, $txt; + + checkSession(); + + // No one's online now.... MUHAHAHAHA :P. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_online'); + + // Dump the banning logs. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_banned'); + + // Start id_error back at 0 and dump the error log. + $smcFunc['db_query']('truncate_table', ' + TRUNCATE {db_prefix}log_errors'); + + // Clear out the spam log. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_floodcontrol'); + + // Clear out the karma actions. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_karma'); + + // Last but not least, the search logs! + $smcFunc['db_query']('truncate_table', ' + TRUNCATE {db_prefix}log_search_topics'); + + $smcFunc['db_query']('truncate_table', ' + TRUNCATE {db_prefix}log_search_messages'); + + $smcFunc['db_query']('truncate_table', ' + TRUNCATE {db_prefix}log_search_results'); + + updateSettings(array('search_pointer' => 0)); + + $context['maintenance_finished'] = $txt['maintain_logs']; +} + +// Oh noes! +function Destroy() +{ + global $context; + + echo ' + ', $context['forum_name_html_safe'], ' deleted! + +
Oh my, you killed ', $context['forum_name_html_safe'], '!
+
You lazy bum!
+ '; + obExit(false); +} + +// Convert both data and database tables to UTF-8 character set. +function ConvertUtf8() +{ + global $scripturl, $context, $txt, $language, $db_character_set; + global $modSettings, $user_info, $sourcedir, $smcFunc, $db_prefix; + + // Show me your badge! + isAllowedTo('admin_forum'); + + // The character sets used in SMF's language files with their db equivalent. + $charsets = array( + // Chinese-traditional. + 'big5' => 'big5', + // Chinese-simplified. + 'gbk' => 'gbk', + // West European. + 'ISO-8859-1' => 'latin1', + // Romanian. + 'ISO-8859-2' => 'latin2', + // Turkish. + 'ISO-8859-9' => 'latin5', + // West European with Euro sign. + 'ISO-8859-15' => 'latin9', + // Thai. + 'tis-620' => 'tis620', + // Persian, Chinese, etc. + 'UTF-8' => 'utf8', + // Russian. + 'windows-1251' => 'cp1251', + // Greek. + 'windows-1253' => 'utf8', + // Hebrew. + 'windows-1255' => 'utf8', + // Arabic. + 'windows-1256' => 'cp1256', + ); + + // Get a list of character sets supported by your MySQL server. + $request = $smcFunc['db_query']('', ' + SHOW CHARACTER SET', + array( + ) + ); + $db_charsets = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $db_charsets[] = $row['Charset']; + + $smcFunc['db_free_result']($request); + + // Character sets supported by both MySQL and SMF's language files. + $charsets = array_intersect($charsets, $db_charsets); + + // This is for the first screen telling backups is good. + if (!isset($_POST['proceed'])) + { + // Character set conversions are only supported as of MySQL 4.1.2. + if (version_compare('4.1.2', preg_replace('~\-.+?$~', '', $smcFunc['db_server_info']())) > 0) + fatal_lang_error('utf8_db_version_too_low'); + + // Use the messages.body column as indicator for the database charset. + $request = $smcFunc['db_query']('', ' + SHOW FULL COLUMNS + FROM {db_prefix}messages + LIKE {string:body_like}', + array( + 'body_like' => 'body', + ) + ); + $column_info = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // A collation looks like latin1_swedish. We only need the character set. + list($context['database_charset']) = explode('_', $column_info['Collation']); + $context['database_charset'] = in_array($context['database_charset'], $charsets) ? array_search($context['database_charset'], $charsets) : $context['database_charset']; + + // No need to convert to UTF-8 if it already is. + if ($db_character_set === 'utf8' && !empty($modSettings['global_character_set']) && $modSettings['global_character_set'] === 'UTF-8') + fatal_lang_error('utf8_already_utf8'); + + // Cannot do conversion if using a fulltext index + if (!empty($modSettings['search_index']) && $modSettings['search_index'] == 'fulltext') + fatal_lang_error('utf8_cannot_convert_fulltext'); + + // Grab the character set from the default language file. + loadLanguage('index', $language, true); + $context['charset_detected'] = $txt['lang_character_set']; + $context['charset_about_detected'] = sprintf($txt['utf8_detected_charset'], $language, $context['charset_detected']); + + // Go back to your own language. + loadLanguage('index', $user_info['language'], true); + + // Show a warning if the character set seems not to be supported. + if (!isset($charsets[strtr(strtolower($context['charset_detected']), array('utf' => 'UTF', 'iso' => 'ISO'))])) + { + $context['charset_warning'] = sprintf($txt['utf8_charset_not_supported'], $txt['lang_character_set']); + + // Default to ISO-8859-1. + $context['charset_detected'] = 'ISO-8859-1'; + } + + $context['charset_list'] = array_keys($charsets); + + $context['page_title'] = $txt['utf8_title']; + $context['sub_template'] = 'convert_utf8'; + return; + } + + // After this point we're starting the conversion. But first: session check. + checkSession(); + + // Translation table for the character sets not native for MySQL. + $translation_tables = array( + 'windows-1255' => array( + '0x81' => '\'\'', '0x8A' => '\'\'', '0x8C' => '\'\'', + '0x8D' => '\'\'', '0x8E' => '\'\'', '0x8F' => '\'\'', + '0x90' => '\'\'', '0x9A' => '\'\'', '0x9C' => '\'\'', + '0x9D' => '\'\'', '0x9E' => '\'\'', '0x9F' => '\'\'', + '0xCA' => '\'\'', '0xD9' => '\'\'', '0xDA' => '\'\'', + '0xDB' => '\'\'', '0xDC' => '\'\'', '0xDD' => '\'\'', + '0xDE' => '\'\'', '0xDF' => '\'\'', '0xFB' => '\'\'', + '0xFC' => '\'\'', '0xFF' => '\'\'', '0xC2' => '0xFF', + '0x80' => '0xFC', '0xE2' => '0xFB', '0xA0' => '0xC2A0', + '0xA1' => '0xC2A1', '0xA2' => '0xC2A2', '0xA3' => '0xC2A3', + '0xA5' => '0xC2A5', '0xA6' => '0xC2A6', '0xA7' => '0xC2A7', + '0xA8' => '0xC2A8', '0xA9' => '0xC2A9', '0xAB' => '0xC2AB', + '0xAC' => '0xC2AC', '0xAD' => '0xC2AD', '0xAE' => '0xC2AE', + '0xAF' => '0xC2AF', '0xB0' => '0xC2B0', '0xB1' => '0xC2B1', + '0xB2' => '0xC2B2', '0xB3' => '0xC2B3', '0xB4' => '0xC2B4', + '0xB5' => '0xC2B5', '0xB6' => '0xC2B6', '0xB7' => '0xC2B7', + '0xB8' => '0xC2B8', '0xB9' => '0xC2B9', '0xBB' => '0xC2BB', + '0xBC' => '0xC2BC', '0xBD' => '0xC2BD', '0xBE' => '0xC2BE', + '0xBF' => '0xC2BF', '0xD7' => '0xD7B3', '0xD1' => '0xD781', + '0xD4' => '0xD7B0', '0xD5' => '0xD7B1', '0xD6' => '0xD7B2', + '0xE0' => '0xD790', '0xEA' => '0xD79A', '0xEC' => '0xD79C', + '0xED' => '0xD79D', '0xEE' => '0xD79E', '0xEF' => '0xD79F', + '0xF0' => '0xD7A0', '0xF1' => '0xD7A1', '0xF2' => '0xD7A2', + '0xF3' => '0xD7A3', '0xF5' => '0xD7A5', '0xF6' => '0xD7A6', + '0xF7' => '0xD7A7', '0xF8' => '0xD7A8', '0xF9' => '0xD7A9', + '0x82' => '0xE2809A', '0x84' => '0xE2809E', '0x85' => '0xE280A6', + '0x86' => '0xE280A0', '0x87' => '0xE280A1', '0x89' => '0xE280B0', + '0x8B' => '0xE280B9', '0x93' => '0xE2809C', '0x94' => '0xE2809D', + '0x95' => '0xE280A2', '0x97' => '0xE28094', '0x99' => '0xE284A2', + '0xC0' => '0xD6B0', '0xC1' => '0xD6B1', '0xC3' => '0xD6B3', + '0xC4' => '0xD6B4', '0xC5' => '0xD6B5', '0xC6' => '0xD6B6', + '0xC7' => '0xD6B7', '0xC8' => '0xD6B8', '0xC9' => '0xD6B9', + '0xCB' => '0xD6BB', '0xCC' => '0xD6BC', '0xCD' => '0xD6BD', + '0xCE' => '0xD6BE', '0xCF' => '0xD6BF', '0xD0' => '0xD780', + '0xD2' => '0xD782', '0xE3' => '0xD793', '0xE4' => '0xD794', + '0xE5' => '0xD795', '0xE7' => '0xD797', '0xE9' => '0xD799', + '0xFD' => '0xE2808E', '0xFE' => '0xE2808F', '0x92' => '0xE28099', + '0x83' => '0xC692', '0xD3' => '0xD783', '0x88' => '0xCB86', + '0x98' => '0xCB9C', '0x91' => '0xE28098', '0x96' => '0xE28093', + '0xBA' => '0xC3B7', '0x9B' => '0xE280BA', '0xAA' => '0xC397', + '0xA4' => '0xE282AA', '0xE1' => '0xD791', '0xE6' => '0xD796', + '0xE8' => '0xD798', '0xEB' => '0xD79B', '0xF4' => '0xD7A4', + '0xFA' => '0xD7AA', '0xFF' => '0xD6B2', '0xFC' => '0xE282AC', + '0xFB' => '0xD792', + ), + 'windows-1253' => array( + '0x81' => '\'\'', '0x88' => '\'\'', '0x8A' => '\'\'', + '0x8C' => '\'\'', '0x8D' => '\'\'', '0x8E' => '\'\'', + '0x8F' => '\'\'', '0x90' => '\'\'', '0x98' => '\'\'', + '0x9A' => '\'\'', '0x9C' => '\'\'', '0x9D' => '\'\'', + '0x9E' => '\'\'', '0x9F' => '\'\'', '0xAA' => '\'\'', + '0xD2' => '\'\'', '0xFF' => '\'\'', '0xCE' => '0xCE9E', + '0xB8' => '0xCE88', '0xBA' => '0xCE8A', '0xBC' => '0xCE8C', + '0xBE' => '0xCE8E', '0xBF' => '0xCE8F', '0xC0' => '0xCE90', + '0xC8' => '0xCE98', '0xCA' => '0xCE9A', '0xCC' => '0xCE9C', + '0xCD' => '0xCE9D', '0xCF' => '0xCE9F', '0xDA' => '0xCEAA', + '0xE8' => '0xCEB8', '0xEA' => '0xCEBA', '0xEC' => '0xCEBC', + '0xEE' => '0xCEBE', '0xEF' => '0xCEBF', '0xC2' => '0xFF', + '0xBD' => '0xC2BD', '0xED' => '0xCEBD', '0xB2' => '0xC2B2', + '0xA0' => '0xC2A0', '0xA3' => '0xC2A3', '0xA4' => '0xC2A4', + '0xA5' => '0xC2A5', '0xA6' => '0xC2A6', '0xA7' => '0xC2A7', + '0xA8' => '0xC2A8', '0xA9' => '0xC2A9', '0xAB' => '0xC2AB', + '0xAC' => '0xC2AC', '0xAD' => '0xC2AD', '0xAE' => '0xC2AE', + '0xB0' => '0xC2B0', '0xB1' => '0xC2B1', '0xB3' => '0xC2B3', + '0xB5' => '0xC2B5', '0xB6' => '0xC2B6', '0xB7' => '0xC2B7', + '0xBB' => '0xC2BB', '0xE2' => '0xCEB2', '0x80' => '0xD2', + '0x82' => '0xE2809A', '0x84' => '0xE2809E', '0x85' => '0xE280A6', + '0x86' => '0xE280A0', '0xA1' => '0xCE85', '0xA2' => '0xCE86', + '0x87' => '0xE280A1', '0x89' => '0xE280B0', '0xB9' => '0xCE89', + '0x8B' => '0xE280B9', '0x91' => '0xE28098', '0x99' => '0xE284A2', + '0x92' => '0xE28099', '0x93' => '0xE2809C', '0x94' => '0xE2809D', + '0x95' => '0xE280A2', '0x96' => '0xE28093', '0x97' => '0xE28094', + '0x9B' => '0xE280BA', '0xAF' => '0xE28095', '0xB4' => '0xCE84', + '0xC1' => '0xCE91', '0xC3' => '0xCE93', '0xC4' => '0xCE94', + '0xC5' => '0xCE95', '0xC6' => '0xCE96', '0x83' => '0xC692', + '0xC7' => '0xCE97', '0xC9' => '0xCE99', '0xCB' => '0xCE9B', + '0xD0' => '0xCEA0', '0xD1' => '0xCEA1', '0xD3' => '0xCEA3', + '0xD4' => '0xCEA4', '0xD5' => '0xCEA5', '0xD6' => '0xCEA6', + '0xD7' => '0xCEA7', '0xD8' => '0xCEA8', '0xD9' => '0xCEA9', + '0xDB' => '0xCEAB', '0xDC' => '0xCEAC', '0xDD' => '0xCEAD', + '0xDE' => '0xCEAE', '0xDF' => '0xCEAF', '0xE0' => '0xCEB0', + '0xE1' => '0xCEB1', '0xE3' => '0xCEB3', '0xE4' => '0xCEB4', + '0xE5' => '0xCEB5', '0xE6' => '0xCEB6', '0xE7' => '0xCEB7', + '0xE9' => '0xCEB9', '0xEB' => '0xCEBB', '0xF0' => '0xCF80', + '0xF1' => '0xCF81', '0xF2' => '0xCF82', '0xF3' => '0xCF83', + '0xF4' => '0xCF84', '0xF5' => '0xCF85', '0xF6' => '0xCF86', + '0xF7' => '0xCF87', '0xF8' => '0xCF88', '0xF9' => '0xCF89', + '0xFA' => '0xCF8A', '0xFB' => '0xCF8B', '0xFC' => '0xCF8C', + '0xFD' => '0xCF8D', '0xFE' => '0xCF8E', '0xFF' => '0xCE92', + '0xD2' => '0xE282AC', + ), + ); + + // Make some preparations. + if (isset($translation_tables[$_POST['src_charset']])) + { + $replace = '%field%'; + foreach ($translation_tables[$_POST['src_charset']] as $from => $to) + $replace = 'REPLACE(' . $replace . ', ' . $from . ', ' . $to . ')'; + } + + // Grab a list of tables. + if (preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) === 1) + $queryTables = $smcFunc['db_query']('', ' + SHOW TABLE STATUS + FROM `' . strtr($match[1], array('`' => '')) . '` + LIKE {string:table_name}', + array( + 'table_name' => str_replace('_', '\_', $match[2]) . '%', + ) + ); + else + $queryTables = $smcFunc['db_query']('', ' + SHOW TABLE STATUS + LIKE {string:table_name}', + array( + 'table_name' => str_replace('_', '\_', $db_prefix) . '%', + ) + ); + + while ($table_info = $smcFunc['db_fetch_assoc']($queryTables)) + { + // Just to make sure it doesn't time out. + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + $table_charsets = array(); + + // Loop through each column. + $queryColumns = $smcFunc['db_query']('', ' + SHOW FULL COLUMNS + FROM ' . $table_info['Name'], + array( + ) + ); + while ($column_info = $smcFunc['db_fetch_assoc']($queryColumns)) + { + // Only text'ish columns have a character set and need converting. + if (strpos($column_info['Type'], 'text') !== false || strpos($column_info['Type'], 'char') !== false) + { + $collation = empty($column_info['Collation']) || $column_info['Collation'] === 'NULL' ? $table_info['Collation'] : $column_info['Collation']; + if (!empty($collation) && $collation !== 'NULL') + { + list($charset) = explode('_', $collation); + + if (!isset($table_charsets[$charset])) + $table_charsets[$charset] = array(); + + $table_charsets[$charset][] = $column_info; + } + } + } + $smcFunc['db_free_result']($queryColumns); + + // Only change the column if the data doesn't match the current charset. + if ((count($table_charsets) === 1 && key($table_charsets) !== $charsets[$_POST['src_charset']]) || count($table_charsets) > 1) + { + $updates_blob = ''; + $updates_text = ''; + foreach ($table_charsets as $charset => $columns) + { + if ($charset !== $charsets[$_POST['src_charset']]) + { + foreach ($columns as $column) + { + $updates_blob .= ' + CHANGE COLUMN ' . $column['Field'] . ' ' . $column['Field'] . ' ' . strtr($column['Type'], array('text' => 'blob', 'char' => 'binary')) . ($column['Null'] === 'YES' ? ' NULL' : ' NOT NULL') . (strpos($column['Type'], 'char') === false ? '' : ' default \'' . $column['Default'] . '\'') . ','; + $updates_text .= ' + CHANGE COLUMN ' . $column['Field'] . ' ' . $column['Field'] . ' ' . $column['Type'] . ' CHARACTER SET ' . $charsets[$_POST['src_charset']] . ($column['Null'] === 'YES' ? '' : ' NOT NULL') . (strpos($column['Type'], 'char') === false ? '' : ' default \'' . $column['Default'] . '\'') . ','; + } + } + } + + // Change the columns to binary form. + $smcFunc['db_query']('', ' + ALTER TABLE {raw:table_name}{raw:updates_blob}', + array( + 'table_name' => $table_info['Name'], + 'updates_blob' => substr($updates_blob, 0, -1), + ) + ); + + // Convert the character set if MySQL has no native support for it. + if (isset($translation_tables[$_POST['src_charset']])) + { + $update = ''; + foreach ($table_charsets as $charset => $columns) + foreach ($columns as $column) + $update .= ' + ' . $column['Field'] . ' = ' . strtr($replace, array('%field%' => $column['Field'])) . ','; + + $smcFunc['db_query']('', ' + UPDATE {raw:table_name} + SET {raw:updates}', + array( + 'table_name' => $table_info['Name'], + 'updates' => substr($update, 0, -1), + ) + ); + } + + // Change the columns back, but with the proper character set. + $smcFunc['db_query']('', ' + ALTER TABLE {raw:table_name}{raw:updates_text}', + array( + 'table_name' => $table_info['Name'], + 'updates_text' => substr($updates_text, 0, -1), + ) + ); + } + + // Now do the actual conversion (if still needed). + if ($charsets[$_POST['src_charset']] !== 'utf8') + $smcFunc['db_query']('', ' + ALTER TABLE {raw:table_name} + CONVERT TO CHARACTER SET utf8', + array( + 'table_name' => $table_info['Name'], + ) + ); + } + $smcFunc['db_free_result']($queryTables); + + // Let the settings know we have a new character set. + updateSettings(array('global_character_set' => 'UTF-8', 'previousCharacterSet' => (empty($translation_tables[$_POST['src_charset']])) ? $charsets[$_POST['src_charset']] : $translation_tables[$_POST['src_charset']])); + + // Store it in Settings.php too because it's needed before db connection. + require_once($sourcedir . '/Subs-Admin.php'); + updateSettingsFile(array('db_character_set' => '\'utf8\'')); + + // The conversion might have messed up some serialized strings. Fix them! + require_once($sourcedir . '/Subs-Charset.php'); + fix_serialized_columns(); + + redirectexit('action=admin;area=maintain;done=convertutf8'); +} + +// Convert HTML-entities to their UTF-8 character equivalents. +function ConvertEntities() +{ + global $db_character_set, $modSettings, $context, $sourcedir, $smcFunc; + + isAllowedTo('admin_forum'); + + // Check to see if UTF-8 is currently the default character set. + if ($modSettings['global_character_set'] !== 'UTF-8' || !isset($db_character_set) || $db_character_set !== 'utf8') + fatal_lang_error('entity_convert_only_utf8'); + + // Some starting values. + $context['table'] = empty($_REQUEST['table']) ? 0 : (int) $_REQUEST['table']; + $context['start'] = empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start']; + + $context['start_time'] = time(); + + $context['first_step'] = !isset($_REQUEST[$context['session_var']]); + $context['last_step'] = false; + + // The first step is just a text screen with some explanation. + if ($context['first_step']) + { + $context['sub_template'] = 'convert_entities'; + return; + } + // Otherwise use the generic "not done" template. + $context['sub_template'] = 'not_done'; + $context['continue_post_data'] = ''; + $context['continue_countdown'] = 3; + + // Now we're actually going to convert... + checkSession('request'); + + // A list of tables ready for conversion. + $tables = array( + 'ban_groups', + 'ban_items', + 'boards', + 'calendar', + 'calendar_holidays', + 'categories', + 'log_errors', + 'log_search_subjects', + 'membergroups', + 'members', + 'message_icons', + 'messages', + 'package_servers', + 'personal_messages', + 'pm_recipients', + 'polls', + 'poll_choices', + 'smileys', + 'themes', + ); + $context['num_tables'] = count($tables); + + // This function will do the conversion later on. + $entity_replace = create_function('$string', ' + $num = substr($string, 0, 1) === \'x\' ? hexdec(substr($string, 1)) : (int) $string; + return $num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) ? \'\' : ($num < 0x80 ? \'&#\' . $num . \';\' : ($num < 0x800 ? chr(192 | $num >> 6) . chr(128 | $num & 63) : ($num < 0x10000 ? chr(224 | $num >> 12) . chr(128 | $num >> 6 & 63) . chr(128 | $num & 63) : chr(240 | $num >> 18) . chr(128 | $num >> 12 & 63) . chr(128 | $num >> 6 & 63) . chr(128 | $num & 63))));'); + + // Loop through all tables that need converting. + for (; $context['table'] < $context['num_tables']; $context['table']++) + { + $cur_table = $tables[$context['table']]; + $primary_key = ''; + // Make sure we keep stuff unique! + $primary_keys = array(); + + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + // Get a list of text columns. + $columns = array(); + $request = $smcFunc['db_query']('', ' + SHOW FULL COLUMNS + FROM {db_prefix}' . $cur_table, + array( + ) + ); + while ($column_info = $smcFunc['db_fetch_assoc']($request)) + if (strpos($column_info['Type'], 'text') !== false || strpos($column_info['Type'], 'char') !== false) + $columns[] = strtolower($column_info['Field']); + + // Get the column with the (first) primary key. + $request = $smcFunc['db_query']('', ' + SHOW KEYS + FROM {db_prefix}' . $cur_table, + array( + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['Key_name'] === 'PRIMARY') + { + if (empty($primary_key) || ($row['Seq_in_index'] == 1 && !in_array(strtolower($row['Column_name']), $columns))) + $primary_key = $row['Column_name']; + + $primary_keys[] = $row['Column_name']; + } + } + $smcFunc['db_free_result']($request); + + // No primary key, no glory. + // Same for columns. Just to be sure we've work to do! + if (empty($primary_key) || empty($columns)) + continue; + + // Get the maximum value for the primary key. + $request = $smcFunc['db_query']('', ' + SELECT MAX(' . $primary_key . ') + FROM {db_prefix}' . $cur_table, + array( + ) + ); + list($max_value) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (empty($max_value)) + continue; + + while ($context['start'] <= $max_value) + { + // Retrieve a list of rows that has at least one entity to convert. + $request = $smcFunc['db_query']('', ' + SELECT {raw:primary_keys}, {raw:columns} + FROM {db_prefix}{raw:cur_table} + WHERE {raw:primary_key} BETWEEN {int:start} AND {int:start} + 499 + AND {raw:like_compare} + LIMIT 500', + array( + 'primary_keys' => implode(', ', $primary_keys), + 'columns' => implode(', ', $columns), + 'cur_table' => $cur_table, + 'primary_key' => $primary_key, + 'start' => $context['start'], + 'like_compare' => '(' . implode(' LIKE \'%&#%\' OR ', $columns) . ' LIKE \'%&#%\')', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $insertion_variables = array(); + $changes = array(); + foreach ($row as $column_name => $column_value) + if ($column_name !== $primary_key && strpos($column_value, '&#') !== false) + { + $changes[] = $column_name . ' = {string:changes_' . $column_name . '}'; + $insertion_variables['changes_' . $column_name] = preg_replace_callback('~&#(\d{1,7}|x[0-9a-fA-F]{1,6});~', 'fixchar__callback', $column_value); + } + + $where = array(); + foreach ($primary_keys as $key) + { + $where[] = $key . ' = {string:where_' . $key . '}'; + $insertion_variables['where_' . $key] = $row[$key]; + } + + // Update the row. + if (!empty($changes)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}' . $cur_table . ' + SET + ' . implode(', + ', $changes) . ' + WHERE ' . implode(' AND ', $where), + $insertion_variables + ); + } + $smcFunc['db_free_result']($request); + $context['start'] += 500; + + // After ten seconds interrupt. + if (time() - $context['start_time'] > 10) + { + // Calculate an approximation of the percentage done. + $context['continue_percent'] = round(100 * ($context['table'] + ($context['start'] / $max_value)) / $context['num_tables'], 1); + $context['continue_get_data'] = '?action=admin;area=maintain;sa=database;activity=convertentities;table=' . $context['table'] . ';start=' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']; + return; + } + } + $context['start'] = 0; + } + + // Make sure all serialized strings are all right. + require_once($sourcedir . '/Subs-Charset.php'); + fix_serialized_columns(); + + // If we're here, we must be done. + $context['continue_percent'] = 100; + $context['continue_get_data'] = '?action=admin;area=maintain;sa=database;done=convertentities'; + $context['last_step'] = true; + $context['continue_countdown'] = -1; +} + +// Optimize the database's tables. +function OptimizeTables() +{ + global $db_type, $db_name, $db_prefix, $txt, $context, $scripturl, $sourcedir, $smcFunc; + + isAllowedTo('admin_forum'); + + checkSession('post'); + + ignore_user_abort(true); + db_extend(); + + // Start with no tables optimized. + $opttab = 0; + + $context['page_title'] = $txt['database_optimize']; + $context['sub_template'] = 'optimize'; + + // Only optimize the tables related to this smf install, not all the tables in the db + $real_prefix = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix; + + // Get a list of tables, as well as how many there are. + $temp_tables = $smcFunc['db_list_tables'](false, $real_prefix . '%'); + $tables = array(); + foreach ($temp_tables as $table) + $tables[] = array('table_name' => $table); + + // If there aren't any tables then I believe that would mean the world has exploded... + $context['num_tables'] = count($tables); + if ($context['num_tables'] == 0) + fatal_error('You appear to be running SMF in a flat file mode... fantastic!', false); + + // For each table.... + $context['optimized_tables'] = array(); + foreach ($tables as $table) + { + // Optimize the table! We use backticks here because it might be a custom table. + $data_freed = $smcFunc['db_optimize_table']($table['table_name']); + + // Optimizing one sqlite table optimizes them all. + if ($db_type == 'sqlite') + break; + + if ($data_freed > 0) + $context['optimized_tables'][] = array( + 'name' => $table['table_name'], + 'data_freed' => $data_freed, + ); + } + + // Number of tables, etc.... + $txt['database_numb_tables'] = sprintf($txt['database_numb_tables'], $context['num_tables']); + $context['num_tables_optimized'] = count($context['optimized_tables']); + + // Check that we don't auto optimise again too soon! + require_once($sourcedir . '/ScheduledTasks.php'); + CalculateNextTrigger('auto_optimize', true); +} + +// Recount all the important board totals. +function AdminBoardRecount() +{ + global $txt, $context, $scripturl, $modSettings, $sourcedir; + global $time_start, $smcFunc; + + isAllowedTo('admin_forum'); + + checkSession('request'); + + $context['page_title'] = $txt['not_done_title']; + $context['continue_post_data'] = ''; + $context['continue_countdown'] = '3'; + $context['sub_template'] = 'not_done'; + + // Try for as much time as possible. + @set_time_limit(600); + + // Step the number of topics at a time so things don't time out... + $request = $smcFunc['db_query']('', ' + SELECT MAX(id_topic) + FROM {db_prefix}topics', + array( + ) + ); + list ($max_topics) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $increment = min(max(50, ceil($max_topics / 4)), 2000); + if (empty($_REQUEST['start'])) + $_REQUEST['start'] = 0; + + $total_steps = 8; + + // Get each topic with a wrong reply count and fix it - let's just do some at a time, though. + if (empty($_REQUEST['step'])) + { + $_REQUEST['step'] = 0; + + while ($_REQUEST['start'] < $max_topics) + { + // Recount approved messages + $request = $smcFunc['db_query']('', ' + SELECT /*!40001 SQL_NO_CACHE */ t.id_topic, MAX(t.num_replies) AS num_replies, + CASE WHEN COUNT(ma.id_msg) >= 1 THEN COUNT(ma.id_msg) - 1 ELSE 0 END AS real_num_replies + FROM {db_prefix}topics AS t + LEFT JOIN {db_prefix}messages AS ma ON (ma.id_topic = t.id_topic AND ma.approved = {int:is_approved}) + WHERE t.id_topic > {int:start} + AND t.id_topic <= {int:max_id} + GROUP BY t.id_topic + HAVING CASE WHEN COUNT(ma.id_msg) >= 1 THEN COUNT(ma.id_msg) - 1 ELSE 0 END != MAX(t.num_replies)', + array( + 'is_approved' => 1, + 'start' => $_REQUEST['start'], + 'max_id' => $_REQUEST['start'] + $increment, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET num_replies = {int:num_replies} + WHERE id_topic = {int:id_topic}', + array( + 'num_replies' => $row['real_num_replies'], + 'id_topic' => $row['id_topic'], + ) + ); + $smcFunc['db_free_result']($request); + + // Recount unapproved messages + $request = $smcFunc['db_query']('', ' + SELECT /*!40001 SQL_NO_CACHE */ t.id_topic, MAX(t.unapproved_posts) AS unapproved_posts, + COUNT(mu.id_msg) AS real_unapproved_posts + FROM {db_prefix}topics AS t + LEFT JOIN {db_prefix}messages AS mu ON (mu.id_topic = t.id_topic AND mu.approved = {int:not_approved}) + WHERE t.id_topic > {int:start} + AND t.id_topic <= {int:max_id} + GROUP BY t.id_topic + HAVING COUNT(mu.id_msg) != MAX(t.unapproved_posts)', + array( + 'not_approved' => 0, + 'start' => $_REQUEST['start'], + 'max_id' => $_REQUEST['start'] + $increment, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET unapproved_posts = {int:unapproved_posts} + WHERE id_topic = {int:id_topic}', + array( + 'unapproved_posts' => $row['real_unapproved_posts'], + 'id_topic' => $row['id_topic'], + ) + ); + $smcFunc['db_free_result']($request); + + $_REQUEST['start'] += $increment; + + if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3) + { + $context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=0;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id']; + $context['continue_percent'] = round((100 * $_REQUEST['start'] / $max_topics) / $total_steps); + + return; + } + } + + $_REQUEST['start'] = 0; + } + + // Update the post count of each board. + if ($_REQUEST['step'] <= 1) + { + if (empty($_REQUEST['start'])) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET num_posts = {int:num_posts} + WHERE redirect = {string:redirect}', + array( + 'num_posts' => 0, + 'redirect' => '', + ) + ); + + while ($_REQUEST['start'] < $max_topics) + { + $request = $smcFunc['db_query']('', ' + SELECT /*!40001 SQL_NO_CACHE */ m.id_board, COUNT(*) AS real_num_posts + FROM {db_prefix}messages AS m + WHERE m.id_topic > {int:id_topic_min} + AND m.id_topic <= {int:id_topic_max} + AND m.approved = {int:is_approved} + GROUP BY m.id_board', + array( + 'id_topic_min' => $_REQUEST['start'], + 'id_topic_max' => $_REQUEST['start'] + $increment, + 'is_approved' => 1, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET num_posts = num_posts + {int:real_num_posts} + WHERE id_board = {int:id_board}', + array( + 'id_board' => $row['id_board'], + 'real_num_posts' => $row['real_num_posts'], + ) + ); + $smcFunc['db_free_result']($request); + + $_REQUEST['start'] += $increment; + + if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3) + { + $context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=1;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id']; + $context['continue_percent'] = round((200 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps); + + return; + } + } + + $_REQUEST['start'] = 0; + } + + // Update the topic count of each board. + if ($_REQUEST['step'] <= 2) + { + if (empty($_REQUEST['start'])) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET num_topics = {int:num_topics}', + array( + 'num_topics' => 0, + ) + ); + + while ($_REQUEST['start'] < $max_topics) + { + $request = $smcFunc['db_query']('', ' + SELECT /*!40001 SQL_NO_CACHE */ t.id_board, COUNT(*) AS real_num_topics + FROM {db_prefix}topics AS t + WHERE t.approved = {int:is_approved} + AND t.id_topic > {int:id_topic_min} + AND t.id_topic <= {int:id_topic_max} + GROUP BY t.id_board', + array( + 'is_approved' => 1, + 'id_topic_min' => $_REQUEST['start'], + 'id_topic_max' => $_REQUEST['start'] + $increment, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET num_topics = num_topics + {int:real_num_topics} + WHERE id_board = {int:id_board}', + array( + 'id_board' => $row['id_board'], + 'real_num_topics' => $row['real_num_topics'], + ) + ); + $smcFunc['db_free_result']($request); + + $_REQUEST['start'] += $increment; + + if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3) + { + $context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=2;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id']; + $context['continue_percent'] = round((300 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps); + + return; + } + } + + $_REQUEST['start'] = 0; + } + + // Update the unapproved post count of each board. + if ($_REQUEST['step'] <= 3) + { + if (empty($_REQUEST['start'])) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET unapproved_posts = {int:unapproved_posts}', + array( + 'unapproved_posts' => 0, + ) + ); + + while ($_REQUEST['start'] < $max_topics) + { + $request = $smcFunc['db_query']('', ' + SELECT /*!40001 SQL_NO_CACHE */ m.id_board, COUNT(*) AS real_unapproved_posts + FROM {db_prefix}messages AS m + WHERE m.id_topic > {int:id_topic_min} + AND m.id_topic <= {int:id_topic_max} + AND m.approved = {int:is_approved} + GROUP BY m.id_board', + array( + 'id_topic_min' => $_REQUEST['start'], + 'id_topic_max' => $_REQUEST['start'] + $increment, + 'is_approved' => 0, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET unapproved_posts = unapproved_posts + {int:unapproved_posts} + WHERE id_board = {int:id_board}', + array( + 'id_board' => $row['id_board'], + 'unapproved_posts' => $row['real_unapproved_posts'], + ) + ); + $smcFunc['db_free_result']($request); + + $_REQUEST['start'] += $increment; + + if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3) + { + $context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=3;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id']; + $context['continue_percent'] = round((400 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps); + + return; + } + } + + $_REQUEST['start'] = 0; + } + + // Update the unapproved topic count of each board. + if ($_REQUEST['step'] <= 4) + { + if (empty($_REQUEST['start'])) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET unapproved_topics = {int:unapproved_topics}', + array( + 'unapproved_topics' => 0, + ) + ); + + while ($_REQUEST['start'] < $max_topics) + { + $request = $smcFunc['db_query']('', ' + SELECT /*!40001 SQL_NO_CACHE */ t.id_board, COUNT(*) AS real_unapproved_topics + FROM {db_prefix}topics AS t + WHERE t.approved = {int:is_approved} + AND t.id_topic > {int:id_topic_min} + AND t.id_topic <= {int:id_topic_max} + GROUP BY t.id_board', + array( + 'is_approved' => 0, + 'id_topic_min' => $_REQUEST['start'], + 'id_topic_max' => $_REQUEST['start'] + $increment, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET unapproved_topics = unapproved_topics + {int:real_unapproved_topics} + WHERE id_board = {int:id_board}', + array( + 'id_board' => $row['id_board'], + 'real_unapproved_topics' => $row['real_unapproved_topics'], + ) + ); + $smcFunc['db_free_result']($request); + + $_REQUEST['start'] += $increment; + + if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3) + { + $context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=4;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id']; + $context['continue_percent'] = round((500 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps); + + return; + } + } + + $_REQUEST['start'] = 0; + } + + // Get all members with wrong number of personal messages. + if ($_REQUEST['step'] <= 5) + { + $request = $smcFunc['db_query']('', ' + SELECT /*!40001 SQL_NO_CACHE */ mem.id_member, COUNT(pmr.id_pm) AS real_num, + MAX(mem.instant_messages) AS instant_messages + FROM {db_prefix}members AS mem + LEFT JOIN {db_prefix}pm_recipients AS pmr ON (mem.id_member = pmr.id_member AND pmr.deleted = {int:is_not_deleted}) + GROUP BY mem.id_member + HAVING COUNT(pmr.id_pm) != MAX(mem.instant_messages)', + array( + 'is_not_deleted' => 0, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + updateMemberData($row['id_member'], array('instant_messages' => $row['real_num'])); + $smcFunc['db_free_result']($request); + + $request = $smcFunc['db_query']('', ' + SELECT /*!40001 SQL_NO_CACHE */ mem.id_member, COUNT(pmr.id_pm) AS real_num, + MAX(mem.unread_messages) AS unread_messages + FROM {db_prefix}members AS mem + LEFT JOIN {db_prefix}pm_recipients AS pmr ON (mem.id_member = pmr.id_member AND pmr.deleted = {int:is_not_deleted} AND pmr.is_read = {int:is_not_read}) + GROUP BY mem.id_member + HAVING COUNT(pmr.id_pm) != MAX(mem.unread_messages)', + array( + 'is_not_deleted' => 0, + 'is_not_read' => 0, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + updateMemberData($row['id_member'], array('unread_messages' => $row['real_num'])); + $smcFunc['db_free_result']($request); + + if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3) + { + $context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=6;start=0;' . $context['session_var'] . '=' . $context['session_id']; + $context['continue_percent'] = round(700 / $total_steps); + + return; + } + } + + // Any messages pointing to the wrong board? + if ($_REQUEST['step'] <= 6) + { + while ($_REQUEST['start'] < $modSettings['maxMsgID']) + { + $request = $smcFunc['db_query']('', ' + SELECT /*!40001 SQL_NO_CACHE */ t.id_board, m.id_msg + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic AND t.id_board != m.id_board) + WHERE m.id_msg > {int:id_msg_min} + AND m.id_msg <= {int:id_msg_max}', + array( + 'id_msg_min' => $_REQUEST['start'], + 'id_msg_max' => $_REQUEST['start'] + $increment, + ) + ); + $boards = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $boards[$row['id_board']][] = $row['id_msg']; + $smcFunc['db_free_result']($request); + + foreach ($boards as $board_id => $messages) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET id_board = {int:id_board} + WHERE id_msg IN ({array_int:id_msg_array})', + array( + 'id_msg_array' => $messages, + 'id_board' => $board_id, + ) + ); + + $_REQUEST['start'] += $increment; + + if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3) + { + $context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=6;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id']; + $context['continue_percent'] = round((700 + 100 * $_REQUEST['start'] / $modSettings['maxMsgID']) / $total_steps); + + return; + } + } + + $_REQUEST['start'] = 0; + } + + // Update the latest message of each board. + $request = $smcFunc['db_query']('', ' + SELECT m.id_board, MAX(m.id_msg) AS local_last_msg + FROM {db_prefix}messages AS m + WHERE m.approved = {int:is_approved} + GROUP BY m.id_board', + array( + 'is_approved' => 1, + ) + ); + $realBoardCounts = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $realBoardCounts[$row['id_board']] = $row['local_last_msg']; + $smcFunc['db_free_result']($request); + + $request = $smcFunc['db_query']('', ' + SELECT /*!40001 SQL_NO_CACHE */ id_board, id_parent, id_last_msg, child_level, id_msg_updated + FROM {db_prefix}boards', + array( + ) + ); + $resort_me = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $row['local_last_msg'] = isset($realBoardCounts[$row['id_board']]) ? $realBoardCounts[$row['id_board']] : 0; + $resort_me[$row['child_level']][] = $row; + } + $smcFunc['db_free_result']($request); + + krsort($resort_me); + + $lastModifiedMsg = array(); + foreach ($resort_me as $rows) + foreach ($rows as $row) + { + // The latest message is the latest of the current board and its children. + if (isset($lastModifiedMsg[$row['id_board']])) + $curLastModifiedMsg = max($row['local_last_msg'], $lastModifiedMsg[$row['id_board']]); + else + $curLastModifiedMsg = $row['local_last_msg']; + + // If what is and what should be the latest message differ, an update is necessary. + if ($row['local_last_msg'] != $row['id_last_msg'] || $curLastModifiedMsg != $row['id_msg_updated']) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET id_last_msg = {int:id_last_msg}, id_msg_updated = {int:id_msg_updated} + WHERE id_board = {int:id_board}', + array( + 'id_last_msg' => $row['local_last_msg'], + 'id_msg_updated' => $curLastModifiedMsg, + 'id_board' => $row['id_board'], + ) + ); + + // Parent boards inherit the latest modified message of their children. + if (isset($lastModifiedMsg[$row['id_parent']])) + $lastModifiedMsg[$row['id_parent']] = max($row['local_last_msg'], $lastModifiedMsg[$row['id_parent']]); + else + $lastModifiedMsg[$row['id_parent']] = $row['local_last_msg']; + } + + // Update all the basic statistics. + updateStats('member'); + updateStats('message'); + updateStats('topic'); + + // Finally, update the latest event times. + require_once($sourcedir . '/ScheduledTasks.php'); + CalculateNextTrigger(); + + redirectexit('action=admin;area=maintain;sa=routine;done=recount'); +} + +// Perform a detailed version check. A very good thing ;). +function VersionDetail() +{ + global $forum_version, $txt, $sourcedir, $context; + + isAllowedTo('admin_forum'); + + // Call the function that'll get all the version info we need. + require_once($sourcedir . '/Subs-Admin.php'); + $versionOptions = array( + 'include_ssi' => true, + 'include_subscriptions' => true, + 'sort_results' => true, + ); + $version_info = getFileVersions($versionOptions); + + // Add the new info to the template context. + $context += array( + 'file_versions' => $version_info['file_versions'], + 'default_template_versions' => $version_info['default_template_versions'], + 'template_versions' => $version_info['template_versions'], + 'default_language_versions' => $version_info['default_language_versions'], + 'default_known_languages' => array_keys($version_info['default_language_versions']), + ); + + // Make it easier to manage for the template. + $context['forum_version'] = $forum_version; + + $context['sub_template'] = 'view_versions'; + $context['page_title'] = $txt['admin_version_check']; +} + +// Removing old posts doesn't take much as we really pass through. +function MaintainReattributePosts() +{ + global $sourcedir, $context, $txt; + + checkSession(); + + // Find the member. + require_once($sourcedir . '/Subs-Auth.php'); + $members = findMembers($_POST['to']); + + if (empty($members)) + fatal_lang_error('reattribute_cannot_find_member'); + + $memID = array_shift($members); + $memID = $memID['id']; + + $email = $_POST['type'] == 'email' ? $_POST['from_email'] : ''; + $membername = $_POST['type'] == 'name' ? $_POST['from_name'] : ''; + + // Now call the reattribute function. + require_once($sourcedir . '/Subs-Members.php'); + reattributePosts($memID, $email, $membername, !empty($_POST['posts'])); + + $context['maintenance_finished'] = $txt['maintain_reattribute_posts']; +} + +// Handling function for the backup stuff. +function MaintainDownloadBackup() +{ + global $sourcedir; + + require_once($sourcedir . '/DumpDatabase.php'); + DumpDatabase2(); +} + +// Removing old members? +function MaintainPurgeInactiveMembers() +{ + global $sourcedir, $context, $smcFunc, $txt; + + $_POST['maxdays'] = empty($_POST['maxdays']) ? 0 : (int) $_POST['maxdays']; + if (!empty($_POST['groups']) && $_POST['maxdays'] > 0) + { + checkSession(); + + $groups = array(); + foreach ($_POST['groups'] as $id => $dummy) + $groups[] = (int) $id; + $time_limit = (time() - ($_POST['maxdays'] * 24 * 3600)); + $where_vars = array( + 'time_limit' => $time_limit, + ); + if ($_POST['del_type'] == 'activated') + { + $where = 'mem.date_registered < {int:time_limit} AND mem.is_activated = {int:is_activated}'; + $where_vars['is_activated'] = 0; + } + else + $where = 'mem.last_login < {int:time_limit}'; + + // Need to get *all* groups then work out which (if any) we avoid. + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_name, min_posts + FROM {db_prefix}membergroups', + array( + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Avoid this one? + if (!in_array($row['id_group'], $groups)) + { + // Post group? + if ($row['min_posts'] != -1) + { + $where .= ' AND mem.id_post_group != {int:id_post_group_' . $row['id_group'] . '}'; + $where_vars['id_post_group_' . $row['id_group']] = $row['id_group']; + } + else + { + $where .= ' AND mem.id_group != {int:id_group_' . $row['id_group'] . '} AND FIND_IN_SET({int:id_group_' . $row['id_group'] . '}, mem.additional_groups) = 0'; + $where_vars['id_group_' . $row['id_group']] = $row['id_group']; + } + } + } + $smcFunc['db_free_result']($request); + + // If we have ungrouped unselected we need to avoid those guys. + if (!in_array(0, $groups)) + { + $where .= ' AND (mem.id_group != 0 OR mem.additional_groups != {string:blank_add_groups})'; + $where_vars['blank_add_groups'] = ''; + } + + // Select all the members we're about to murder/remove... + $request = $smcFunc['db_query']('', ' + SELECT mem.id_member, IFNULL(m.id_member, 0) AS is_mod + FROM {db_prefix}members AS mem + LEFT JOIN {db_prefix}moderators AS m ON (m.id_member = mem.id_member) + WHERE ' . $where, + $where_vars + ); + $members = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!$row['is_mod'] || !in_array(3, $groups)) + $members[] = $row['id_member']; + } + $smcFunc['db_free_result']($request); + + require_once($sourcedir . '/Subs-Members.php'); + deleteMembers($members); + } + + $context['maintenance_finished'] = $txt['maintain_members']; +} + +// Removing old posts doesn't take much as we really pass through. +function MaintainRemoveOldPosts() +{ + global $sourcedir, $context, $txt; + + // Actually do what we're told! + require_once($sourcedir . '/RemoveTopic.php'); + RemoveOldTopics2(); +} + +function MaintainMassMoveTopics() +{ + global $smcFunc, $sourcedir, $context, $txt; + + // Only admins. + isAllowedTo('admin_forum'); + + checkSession('request'); + + // Set up to the context. + $context['page_title'] = $txt['not_done_title']; + $context['continue_countdown'] = '3'; + $context['continue_post_data'] = ''; + $context['continue_get_data'] = ''; + $context['sub_template'] = 'not_done'; + $context['start'] = empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start']; + $context['start_time'] = time(); + + // First time we do this? + $id_board_from = isset($_POST['id_board_from']) ? (int) $_POST['id_board_from'] : (int) $_REQUEST['id_board_from']; + $id_board_to = isset($_POST['id_board_to']) ? (int) $_POST['id_board_to'] : (int) $_REQUEST['id_board_to']; + + // No boards then this is your stop. + if (empty($id_board_from) || empty($id_board_to)) + return; + + // How many topics are we converting? + if (!isset($_REQUEST['totaltopics'])) + { + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}topics + WHERE id_board = {int:id_board_from}', + array( + 'id_board_from' => $id_board_from, + ) + ); + list ($total_topics) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + else + $total_topics = (int) $_REQUEST['totaltopics']; + + // Seems like we need this here. + $context['continue_get_data'] = '?action=admin;area=maintain;sa=topics;activity=massmove;id_board_from=' . $id_board_from . ';id_board_to=' . $id_board_to . ';totaltopics=' . $total_topics . ';start=' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']; + + // We have topics to move so start the process. + if (!empty($total_topics)) + { + while ($context['start'] <= $total_topics) + { + // Lets get the topics. + $request = $smcFunc['db_query']('', ' + SELECT id_topic + FROM {db_prefix}topics + WHERE id_board = {int:id_board_from} + LIMIT 10', + array( + 'id_board_from' => $id_board_from, + ) + ); + + // Get the ids. + $topics = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $topics[] = $row['id_topic']; + + // Just return if we don't have any topics left to move. + if (empty($topics)) + { + cache_put_data('board-' . $id_board_from, null, 120); + cache_put_data('board-' . $id_board_to, null, 120); + redirectexit('action=admin;area=maintain;sa=topics;done=massmove'); + } + + // Lets move them. + require_once($sourcedir . '/MoveTopic.php'); + moveTopics($topics, $id_board_to); + + // We've done at least ten more topics. + $context['start'] += 10; + + // Lets wait a while. + if (time() - $context['start_time'] > 3) + { + // What's the percent? + $context['continue_percent'] = round(100 * ($context['start'] / $total_topics), 1); + $context['continue_get_data'] = '?action=admin;area=maintain;sa=topics;activity=massmove;id_board_from=' . $id_board_from . ';id_board_to=' . $id_board_to . ';totaltopics=' . $total_topics . ';start=' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']; + + // Let the template system do it's thang. + return; + } + } + } + + // Don't confuse admins by having an out of date cache. + cache_put_data('board-' . $id_board_from, null, 120); + cache_put_data('board-' . $id_board_to, null, 120); + + redirectexit('action=admin;area=maintain;sa=topics;done=massmove'); +} + +?> \ No newline at end of file diff --git a/Sources/ManageMembergroups.php b/Sources/ManageMembergroups.php new file mode 100644 index 0000000..80f64e5 --- /dev/null +++ b/Sources/ManageMembergroups.php @@ -0,0 +1,1077 @@ + array('AddMembergroup', 'manage_membergroups'), + 'delete' => array('DeleteMembergroup', 'manage_membergroups'), + 'edit' => array('EditMembergroup', 'manage_membergroups'), + 'index' => array('MembergroupIndex', 'manage_membergroups'), + 'members' => array('MembergroupMembers', 'manage_membergroups', 'Groups.php'), + 'settings' => array('ModifyMembergroupsettings', 'admin_forum'), + ); + + // Default to sub action 'index' or 'settings' depending on permissions. + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : (allowedTo('manage_membergroups') ? 'index' : 'settings'); + + // Is it elsewhere? + if (isset($subActions[$_REQUEST['sa']][2])) + require_once($sourcedir . '/' . $subActions[$_REQUEST['sa']][2]); + + // Do the permission check, you might not be allowed her. + isAllowedTo($subActions[$_REQUEST['sa']][1]); + + // Language and template stuff, the usual. + loadLanguage('ManageMembers'); + loadTemplate('ManageMembergroups'); + + // Setup the admin tabs. + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['membergroups_title'], + 'help' => 'membergroups', + 'description' => $txt['membergroups_description'], + ); + + // Call the right function. + $subActions[$_REQUEST['sa']][0](); +} + +// An overview of the current membergroups. +function MembergroupIndex() +{ + global $txt, $scripturl, $context, $settings, $smcFunc, $sourcedir; + + $context['page_title'] = $txt['membergroups_title']; + + // The first list shows the regular membergroups. + $listOptions = array( + 'id' => 'regular_membergroups_list', + 'title' => $txt['membergroups_regular'], + 'base_href' => $scripturl . '?action=admin;area=membergroups' . (isset($_REQUEST['sort2']) ? ';sort2=' . urlencode($_REQUEST['sort2']) : ''), + 'default_sort_col' => 'name', + 'get_items' => array( + 'file' => $sourcedir . '/Subs-Membergroups.php', + 'function' => 'list_getMembergroups', + 'params' => array( + 'regular', + ), + ), + 'columns' => array( + 'name' => array( + 'header' => array( + 'value' => $txt['membergroups_name'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $scripturl; + + // Since the moderator group has no explicit members, no link is needed. + if ($rowData[\'id_group\'] == 3) + $group_name = $rowData[\'group_name\']; + else + { + $color_style = empty($rowData[\'online_color\']) ? \'\' : sprintf(\' style="color: %1$s;"\', $rowData[\'online_color\']); + $group_name = sprintf(\'%4$s\', $scripturl, $rowData[\'id_group\'], $color_style, $rowData[\'group_name\']); + } + + // Add a help option for moderator and administrator. + if ($rowData[\'id_group\'] == 1) + $group_name .= sprintf(\' (?)\', $scripturl); + elseif ($rowData[\'id_group\'] == 3) + $group_name .= sprintf(\' (?)\', $scripturl); + + return $group_name; + '), + ), + 'sort' => array( + 'default' => 'CASE WHEN id_group < 4 THEN id_group ELSE 4 END, group_name', + 'reverse' => 'CASE WHEN id_group < 4 THEN id_group ELSE 4 END, group_name DESC', + ), + ), + 'stars' => array( + 'header' => array( + 'value' => $txt['membergroups_stars'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $settings; + + $stars = explode(\'#\', $rowData[\'stars\']); + + // In case no stars are setup, return with nothing + if (empty($stars[0]) || empty($stars[1])) + return \'\'; + + // Otherwise repeat the image a given number of times. + else + { + $image = sprintf(\'*\', $settings[\'images_url\'], $stars[1]); + return str_repeat($image, $stars[0]); + } + '), + + ), + 'sort' => array( + 'default' => 'stars', + 'reverse' => 'stars DESC', + ) + ), + 'members' => array( + 'header' => array( + 'value' => $txt['membergroups_members_top'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt; + + // No explicit members for the moderator group. + return $rowData[\'id_group\'] == 3 ? $txt[\'membergroups_guests_na\'] : $rowData[\'num_members\']; + '), + 'style' => 'text-align: center', + ), + 'sort' => array( + 'default' => 'CASE WHEN id_group < 4 THEN id_group ELSE 4 END, 1', + 'reverse' => 'CASE WHEN id_group < 4 THEN id_group ELSE 4 END, 1 DESC', + ), + ), + 'modify' => array( + 'header' => array( + 'value' => $txt['modify'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '' . $txt['membergroups_modify'] . '', + 'params' => array( + 'id_group' => false, + ), + ), + 'style' => 'text-align: center', + ), + ), + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => '[' . $txt['membergroups_add_group'] . ']', + ), + ), + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + // The second list shows the post count based groups. + $listOptions = array( + 'id' => 'post_count_membergroups_list', + 'title' => $txt['membergroups_post'], + 'base_href' => $scripturl . '?action=admin;area=membergroups' . (isset($_REQUEST['sort']) ? ';sort=' . urlencode($_REQUEST['sort']) : ''), + 'default_sort_col' => 'required_posts', + 'request_vars' => array( + 'sort' => 'sort2', + 'desc' => 'desc2', + ), + 'get_items' => array( + 'file' => $sourcedir . '/Subs-Membergroups.php', + 'function' => 'list_getMembergroups', + 'params' => array( + 'post_count', + ), + ), + 'columns' => array( + 'name' => array( + 'header' => array( + 'value' => $txt['membergroups_name'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $scripturl; + + $colorStyle = empty($rowData[\'online_color\']) ? \'\' : sprintf(\' style="color: %1$s;"\', $rowData[\'online_color\']); + return sprintf(\'%4$s\', $scripturl, $rowData[\'id_group\'], $colorStyle, $rowData[\'group_name\']); + '), + ), + 'sort' => array( + 'default' => 'group_name', + 'reverse' => 'group_name DESC', + ), + ), + 'stars' => array( + 'header' => array( + 'value' => $txt['membergroups_stars'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $settings; + + $stars = explode(\'#\', $rowData[\'stars\']); + + if (empty($stars[0]) || empty($stars[1])) + return \'\'; + else + { + $star_image = sprintf(\'*\', $settings[\'images_url\'], $stars[1]); + return str_repeat($star_image, $stars[0]); + } + '), + ), + 'sort' => array( + 'default' => 'CASE WHEN id_group < 4 THEN id_group ELSE 4 END, stars', + 'reverse' => 'CASE WHEN id_group < 4 THEN id_group ELSE 4 END, stars DESC', + ) + ), + 'members' => array( + 'header' => array( + 'value' => $txt['membergroups_members_top'], + ), + 'data' => array( + 'db' => 'num_members', + 'style' => 'text-align: center', + ), + 'sort' => array( + 'default' => '1 DESC', + 'reverse' => '1', + ), + ), + 'required_posts' => array( + 'header' => array( + 'value' => $txt['membergroups_min_posts'], + ), + 'data' => array( + 'db' => 'min_posts', + 'style' => 'text-align: center', + ), + 'sort' => array( + 'default' => 'min_posts', + 'reverse' => 'min_posts DESC', + ), + ), + 'modify' => array( + 'header' => array( + 'value' => $txt['modify'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '' . $txt['membergroups_modify'] . '', + 'params' => array( + 'id_group' => false, + ), + ), + 'style' => 'text-align: center', + ), + ), + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => '[' . $txt['membergroups_add_group'] . ']', + ), + ), + ); + + createList($listOptions); +} + +// Add a membergroup. +function AddMembergroup() +{ + global $context, $txt, $sourcedir, $modSettings, $smcFunc; + + // A form was submitted, we can start adding. + if (!empty($_POST['group_name'])) + { + checkSession(); + + $postCountBasedGroup = isset($_POST['min_posts']) && (!isset($_POST['postgroup_based']) || !empty($_POST['postgroup_based'])); + $_POST['group_type'] = !isset($_POST['group_type']) || $_POST['group_type'] < 0 || $_POST['group_type'] > 3 || ($_POST['group_type'] == 1 && !allowedTo('admin_forum')) ? 0 : (int) $_POST['group_type']; + + // !!! Check for members with same name too? + + $request = $smcFunc['db_query']('', ' + SELECT MAX(id_group) + FROM {db_prefix}membergroups', + array( + ) + ); + list ($id_group) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + $id_group++; + + $smcFunc['db_insert']('', + '{db_prefix}membergroups', + array( + 'id_group' => 'int', 'description' => 'string', 'group_name' => 'string-80', 'min_posts' => 'int', + 'stars' => 'string', 'online_color' => 'string', 'group_type' => 'int', + ), + array( + $id_group, '', $_POST['group_name'], ($postCountBasedGroup ? (int) $_POST['min_posts'] : '-1'), + '1#star.gif', '', $_POST['group_type'], + ), + array('id_group') + ); + + // Update the post groups now, if this is a post group! + if (isset($_POST['min_posts'])) + updateStats('postgroups'); + + // You cannot set permissions for post groups if they are disabled. + if ($postCountBasedGroup && empty($modSettings['permission_enable_postgroups'])) + $_POST['perm_type'] = ''; + + if ($_POST['perm_type'] == 'predefined') + { + // Set default permission level. + require_once($sourcedir . '/ManagePermissions.php'); + setPermissionLevel($_POST['level'], $id_group, 'null'); + } + // Copy or inherit the permissions! + elseif ($_POST['perm_type'] == 'copy' || $_POST['perm_type'] == 'inherit') + { + $copy_id = $_POST['perm_type'] == 'copy' ? (int) $_POST['copyperm'] : (int) $_POST['inheritperm']; + + // Are you a powerful admin? + if (!allowedTo('admin_forum')) + { + $request = $smcFunc['db_query']('', ' + SELECT group_type + FROM {db_prefix}membergroups + WHERE id_group = {int:copy_from} + LIMIT {int:limit}', + array( + 'copy_from' => $copy_id, + 'limit' => 1, + ) + ); + list ($copy_type) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Protected groups are... well, protected! + if ($copy_type == 1) + fatal_lang_error('membergroup_does_not_exist'); + } + + // Don't allow copying of a real priviledged person! + require_once($sourcedir . '/ManagePermissions.php'); + loadIllegalPermissions(); + + $request = $smcFunc['db_query']('', ' + SELECT permission, add_deny + FROM {db_prefix}permissions + WHERE id_group = {int:copy_from}', + array( + 'copy_from' => $copy_id, + ) + ); + $inserts = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (empty($context['illegal_permissions']) || !in_array($row['permission'], $context['illegal_permissions'])) + $inserts[] = array($id_group, $row['permission'], $row['add_deny']); + } + $smcFunc['db_free_result']($request); + + if (!empty($inserts)) + $smcFunc['db_insert']('insert', + '{db_prefix}permissions', + array('id_group' => 'int', 'permission' => 'string', 'add_deny' => 'int'), + $inserts, + array('id_group', 'permission') + ); + + $request = $smcFunc['db_query']('', ' + SELECT id_profile, permission, add_deny + FROM {db_prefix}board_permissions + WHERE id_group = {int:copy_from}', + array( + 'copy_from' => $copy_id, + ) + ); + $inserts = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $inserts[] = array($id_group, $row['id_profile'], $row['permission'], $row['add_deny']); + $smcFunc['db_free_result']($request); + + if (!empty($inserts)) + $smcFunc['db_insert']('insert', + '{db_prefix}board_permissions', + array('id_group' => 'int', 'id_profile' => 'int', 'permission' => 'string', 'add_deny' => 'int'), + $inserts, + array('id_group', 'id_profile', 'permission') + ); + + // Also get some membergroup information if we're copying and not copying from guests... + if ($copy_id > 0 && $_POST['perm_type'] == 'copy') + { + $request = $smcFunc['db_query']('', ' + SELECT online_color, max_messages, stars + FROM {db_prefix}membergroups + WHERE id_group = {int:copy_from} + LIMIT 1', + array( + 'copy_from' => $copy_id, + ) + ); + $group_info = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // ...and update the new membergroup with it. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}membergroups + SET + online_color = {string:online_color}, + max_messages = {int:max_messages}, + stars = {string:stars} + WHERE id_group = {int:current_group}', + array( + 'max_messages' => $group_info['max_messages'], + 'current_group' => $id_group, + 'online_color' => $group_info['online_color'], + 'stars' => $group_info['stars'], + ) + ); + } + // If inheriting say so... + elseif ($_POST['perm_type'] == 'inherit') + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}membergroups + SET id_parent = {int:copy_from} + WHERE id_group = {int:current_group}', + array( + 'copy_from' => $copy_id, + 'current_group' => $id_group, + ) + ); + } + } + + // Make sure all boards selected are stored in a proper array. + $_POST['boardaccess'] = empty($_POST['boardaccess']) || !is_array($_POST['boardaccess']) ? array() : $_POST['boardaccess']; + foreach ($_POST['boardaccess'] as $key => $value) + $_POST['boardaccess'][$key] = (int) $value; + + // Only do this if they have special access requirements. + if (!empty($_POST['boardaccess'])) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET member_groups = CASE WHEN member_groups = {string:blank_string} THEN {string:group_id_string} ELSE CONCAT(member_groups, {string:comma_group}) END + WHERE id_board IN ({array_int:board_list})', + array( + 'board_list' => $_POST['boardaccess'], + 'blank_string' => '', + 'group_id_string' => (string) $id_group, + 'comma_group' => ',' . $id_group, + ) + ); + + // If this is joinable then set it to show group membership in people's profiles. + if (empty($modSettings['show_group_membership']) && $_POST['group_type'] > 1) + updateSettings(array('show_group_membership' => 1)); + + // Rebuild the group cache. + updateSettings(array( + 'settings_updated' => time(), + )); + + // We did it. + logAction('add_group', array('group' => $_POST['group_name']), 'admin'); + + // Go change some more settings. + redirectexit('action=admin;area=membergroups;sa=edit;group=' . $id_group); + } + + // Just show the 'add membergroup' screen. + $context['page_title'] = $txt['membergroups_new_group']; + $context['sub_template'] = 'new_group'; + $context['post_group'] = isset($_REQUEST['postgroup']); + $context['undefined_group'] = !isset($_REQUEST['postgroup']) && !isset($_REQUEST['generalgroup']); + $context['allow_protected'] = allowedTo('admin_forum'); + + $result = $smcFunc['db_query']('', ' + SELECT id_group, group_name + FROM {db_prefix}membergroups + WHERE (id_group > {int:moderator_group} OR id_group = {int:global_mod_group})' . (empty($modSettings['permission_enable_postgroups']) ? ' + AND min_posts = {int:min_posts}' : '') . (allowedTo('admin_forum') ? '' : ' + AND group_type != {int:is_protected}') . ' + ORDER BY min_posts, id_group != {int:global_mod_group}, group_name', + array( + 'moderator_group' => 3, + 'global_mod_group' => 2, + 'min_posts' => -1, + 'is_protected' => 1, + ) + ); + $context['groups'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + $context['groups'][] = array( + 'id' => $row['id_group'], + 'name' => $row['group_name'] + ); + $smcFunc['db_free_result']($result); + + $result = $smcFunc['db_query']('', ' + SELECT id_board, name, child_level + FROM {db_prefix}boards + ORDER BY board_order', + array( + ) + ); + $context['boards'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + $context['boards'][] = array( + 'id' => $row['id_board'], + 'name' => $row['name'], + 'child_level' => $row['child_level'], + 'selected' => false + ); + $smcFunc['db_free_result']($result); +} + +// Deleting a membergroup by URL (not implemented). +function DeleteMembergroup() +{ + global $sourcedir; + + checkSession('get'); + + require_once($sourcedir . '/Subs-Membergroups.php'); + deleteMembergroups((int) $_REQUEST['group']); + + // Go back to the membergroup index. + redirectexit('action=admin;area=membergroups;'); +} + +// Editing a membergroup. +function EditMembergroup() +{ + global $context, $txt, $sourcedir, $modSettings, $smcFunc; + + $_REQUEST['group'] = isset($_REQUEST['group']) && $_REQUEST['group'] > 0 ? (int) $_REQUEST['group'] : 0; + + // Make sure this group is editable. + if (!empty($_REQUEST['group'])) + { + $request = $smcFunc['db_query']('', ' + SELECT id_group + FROM {db_prefix}membergroups + WHERE id_group = {int:current_group}' . (allowedTo('admin_forum') ? '' : ' + AND group_type != {int:is_protected}') . ' + LIMIT {int:limit}', + array( + 'current_group' => $_REQUEST['group'], + 'is_protected' => 1, + 'limit' => 1, + ) + ); + list ($_REQUEST['group']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + // Now, do we have a valid id? + if (empty($_REQUEST['group'])) + fatal_lang_error('membergroup_does_not_exist', false); + + // The delete this membergroup button was pressed. + if (isset($_POST['delete'])) + { + checkSession(); + + require_once($sourcedir . '/Subs-Membergroups.php'); + deleteMembergroups($_REQUEST['group']); + + redirectexit('action=admin;area=membergroups;'); + } + // A form was submitted with the new membergroup settings. + elseif (isset($_POST['submit'])) + { + // Validate the session. + checkSession(); + + // Can they really inherit from this group? + if ($_REQUEST['group'] > 1 && $_REQUEST['group'] != 3 && isset($_POST['group_inherit']) && $_POST['group_inherit'] != -2 && !allowedTo('admin_forum')) + { + $request = $smcFunc['db_query']('', ' + SELECT group_type + FROM {db_prefix}membergroups + WHERE id_group = {int:inherit_from} + LIMIT {int:limit}', + array( + 'inherit_from' => $_POST['group_inherit'], + 'limit' => 1, + ) + ); + list ($inherit_type) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + // Set variables to their proper value. + $_POST['max_messages'] = isset($_POST['max_messages']) ? (int) $_POST['max_messages'] : 0; + $_POST['min_posts'] = isset($_POST['min_posts']) && isset($_POST['group_type']) && $_POST['group_type'] == -1 && $_REQUEST['group'] > 3 ? abs($_POST['min_posts']) : ($_REQUEST['group'] == 4 ? 0 : -1); + $_POST['stars'] = (empty($_POST['star_count']) || $_POST['star_count'] < 0) ? '' : min((int) $_POST['star_count'], 99) . '#' . $_POST['star_image']; + $_POST['group_desc'] = isset($_POST['group_desc']) && ($_REQUEST['group'] == 1 || (isset($_POST['group_type']) && $_POST['group_type'] != -1)) ? trim($_POST['group_desc']) : ''; + $_POST['group_type'] = !isset($_POST['group_type']) || $_POST['group_type'] < 0 || $_POST['group_type'] > 3 || ($_POST['group_type'] == 1 && !allowedTo('admin_forum')) ? 0 : (int) $_POST['group_type']; + $_POST['group_hidden'] = empty($_POST['group_hidden']) || $_POST['min_posts'] != -1 || $_REQUEST['group'] == 3 ? 0 : (int) $_POST['group_hidden']; + $_POST['group_inherit'] = $_REQUEST['group'] > 1 && $_REQUEST['group'] != 3 && (empty($inherit_type) || $inherit_type != 1) ? (int) $_POST['group_inherit'] : -2; + + // !!! Don't set online_color for the Moderators group? + + // Do the update of the membergroup settings. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}membergroups + SET group_name = {string:group_name}, online_color = {string:online_color}, + max_messages = {int:max_messages}, min_posts = {int:min_posts}, stars = {string:stars}, + description = {string:group_desc}, group_type = {int:group_type}, hidden = {int:group_hidden}, + id_parent = {int:group_inherit} + WHERE id_group = {int:current_group}', + array( + 'max_messages' => $_POST['max_messages'], + 'min_posts' => $_POST['min_posts'], + 'group_type' => $_POST['group_type'], + 'group_hidden' => $_POST['group_hidden'], + 'group_inherit' => $_POST['group_inherit'], + 'current_group' => (int) $_REQUEST['group'], + 'group_name' => $_POST['group_name'], + 'online_color' => $_POST['online_color'], + 'stars' => $_POST['stars'], + 'group_desc' => $_POST['group_desc'], + ) + ); + + // Time to update the boards this membergroup has access to. + if ($_REQUEST['group'] == 2 || $_REQUEST['group'] > 3) + { + $_POST['boardaccess'] = empty($_POST['boardaccess']) || !is_array($_POST['boardaccess']) ? array() : $_POST['boardaccess']; + foreach ($_POST['boardaccess'] as $key => $value) + $_POST['boardaccess'][$key] = (int) $value; + + // Find all board this group is in, but shouldn't be in. + $request = $smcFunc['db_query']('', ' + SELECT id_board, member_groups + FROM {db_prefix}boards + WHERE FIND_IN_SET({string:current_group}, member_groups) != 0' . (empty($_POST['boardaccess']) ? '' : ' + AND id_board NOT IN ({array_int:board_access_list})'), + array( + 'current_group' => (int) $_REQUEST['group'], + 'board_access_list' => $_POST['boardaccess'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET member_groups = {string:member_group_access} + WHERE id_board = {int:current_board}', + array( + 'current_board' => $row['id_board'], + 'member_group_access' => implode(',', array_diff(explode(',', $row['member_groups']), array($_REQUEST['group']))), + ) + ); + $smcFunc['db_free_result']($request); + + // Add the membergroup to all boards that hadn't been set yet. + if (!empty($_POST['boardaccess'])) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET member_groups = CASE WHEN member_groups = {string:blank_string} THEN {string:group_id_string} ELSE CONCAT(member_groups, {string:comma_group}) END + WHERE id_board IN ({array_int:board_list}) + AND FIND_IN_SET({int:current_group}, member_groups) = 0', + array( + 'board_list' => $_POST['boardaccess'], + 'blank_string' => '', + 'current_group' => (int) $_REQUEST['group'], + 'group_id_string' => (string) (int) $_REQUEST['group'], + 'comma_group' => ',' . $_REQUEST['group'], + ) + ); + } + + // Remove everyone from this group! + if ($_POST['min_posts'] != -1) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET id_group = {int:regular_member} + WHERE id_group = {int:current_group}', + array( + 'regular_member' => 0, + 'current_group' => (int) $_REQUEST['group'], + ) + ); + + $request = $smcFunc['db_query']('', ' + SELECT id_member, additional_groups + FROM {db_prefix}members + WHERE FIND_IN_SET({string:current_group}, additional_groups) != 0', + array( + 'current_group' => (int) $_REQUEST['group'], + ) + ); + $updates = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $updates[$row['additional_groups']][] = $row['id_member']; + $smcFunc['db_free_result']($request); + + foreach ($updates as $additional_groups => $memberArray) + updateMemberData($memberArray, array('additional_groups' => implode(',', array_diff(explode(',', $additional_groups), array((int) $_REQUEST['group']))))); + } + elseif ($_REQUEST['group'] != 3) + { + // Making it a hidden group? If so remove everyone with it as primary group (Actually, just make them additional). + if ($_POST['group_hidden'] == 2) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member, additional_groups + FROM {db_prefix}members + WHERE id_group = {int:current_group} + AND FIND_IN_SET({int:current_group}, additional_groups) = 0', + array( + 'current_group' => (int) $_REQUEST['group'], + ) + ); + $updates = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $updates[$row['additional_groups']][] = $row['id_member']; + $smcFunc['db_free_result']($request); + + foreach ($updates as $additional_groups => $memberArray) + updateMemberData($memberArray, array('additional_groups' => implode(',', array_merge(explode(',', $additional_groups), array((int) $_REQUEST['group']))))); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET id_group = {int:regular_member} + WHERE id_group = {int:current_group}', + array( + 'regular_member' => 0, + 'current_group' => $_REQUEST['group'], + ) + ); + } + + // Either way, let's check our "show group membership" setting is correct. + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}membergroups + WHERE group_type > {int:non_joinable}', + array( + 'non_joinable' => 1, + ) + ); + list ($have_joinable) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Do we need to update the setting? + if ((empty($modSettings['show_group_membership']) && $have_joinable) || (!empty($modSettings['show_group_membership']) && !$have_joinable)) + updateSettings(array('show_group_membership' => $have_joinable ? 1 : 0)); + } + + // Do we need to set inherited permissions? + if ($_POST['group_inherit'] != -2 && $_POST['group_inherit'] != $_POST['old_inherit']) + { + require_once($sourcedir . '/ManagePermissions.php'); + updateChildPermissions($_POST['group_inherit']); + } + + // Finally, moderators! + $moderator_string = isset($_POST['group_moderators']) ? trim($_POST['group_moderators']) : ''; + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}group_moderators + WHERE id_group = {int:current_group}', + array( + 'current_group' => $_REQUEST['group'], + ) + ); + if ((!empty($moderator_string) || !empty($_POST['moderator_list'])) && $_POST['min_posts'] == -1 && $_REQUEST['group'] != 3) + { + // Get all the usernames from the string + if (!empty($moderator_string)) + { + $moderator_string = strtr(preg_replace('~&#(\d{4,5}|[2-9]\d{2,4}|1[2-9]\d);~', '&#$1;', htmlspecialchars($moderator_string), ENT_QUOTES), array('"' => '"')); + preg_match_all('~"([^"]+)"~', $moderator_string, $matches); + $moderators = array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $moderator_string))); + for ($k = 0, $n = count($moderators); $k < $n; $k++) + { + $moderators[$k] = trim($moderators[$k]); + + if (strlen($moderators[$k]) == 0) + unset($moderators[$k]); + } + + // Find all the id_member's for the member_name's in the list. + $group_moderators = array(); + if (!empty($moderators)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE member_name IN ({array_string:moderators}) OR real_name IN ({array_string:moderators}) + LIMIT ' . count($moderators), + array( + 'moderators' => $moderators, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $group_moderators[] = $row['id_member']; + $smcFunc['db_free_result']($request); + } + } + else + { + $moderators = array(); + foreach ($_POST['moderator_list'] as $moderator) + $moderators[] = (int) $moderator; + + $group_moderators = array(); + if (!empty($moderators)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE id_member IN ({array_int:moderators}) + LIMIT {int:num_moderators}', + array( + 'moderators' => $moderators, + 'num_moderators' => count($moderators), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $group_moderators[] = $row['id_member']; + $smcFunc['db_free_result']($request); + } + } + + // Found some? + if (!empty($group_moderators)) + { + $mod_insert = array(); + foreach ($group_moderators as $moderator) + $mod_insert[] = array($_REQUEST['group'], $moderator); + + $smcFunc['db_insert']('insert', + '{db_prefix}group_moderators', + array('id_group' => 'int', 'id_member' => 'int'), + $mod_insert, + array('id_group', 'id_member') + ); + } + } + + // There might have been some post group changes. + updateStats('postgroups'); + // We've definetely changed some group stuff. + updateSettings(array( + 'settings_updated' => time(), + )); + + // Log the edit. + logAction('edited_group', array('group' => $_POST['group_name']), 'admin'); + + redirectexit('action=admin;area=membergroups'); + } + + // Fetch the current group information. + $request = $smcFunc['db_query']('', ' + SELECT group_name, description, min_posts, online_color, max_messages, stars, group_type, hidden, id_parent + FROM {db_prefix}membergroups + WHERE id_group = {int:current_group} + LIMIT 1', + array( + 'current_group' => (int) $_REQUEST['group'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('membergroup_does_not_exist', false); + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + $row['stars'] = explode('#', $row['stars']); + + $context['group'] = array( + 'id' => $_REQUEST['group'], + 'name' => $row['group_name'], + 'description' => htmlspecialchars($row['description']), + 'editable_name' => htmlspecialchars($row['group_name']), + 'color' => $row['online_color'], + 'min_posts' => $row['min_posts'], + 'max_messages' => $row['max_messages'], + 'star_count' => (int) $row['stars'][0], + 'star_image' => isset($row['stars'][1]) ? $row['stars'][1] : '', + 'is_post_group' => $row['min_posts'] != -1, + 'type' => $row['min_posts'] != -1 ? 0 : $row['group_type'], + 'hidden' => $row['min_posts'] == -1 ? $row['hidden'] : 0, + 'inherited_from' => $row['id_parent'], + 'allow_post_group' => $_REQUEST['group'] == 2 || $_REQUEST['group'] > 4, + 'allow_delete' => $_REQUEST['group'] == 2 || $_REQUEST['group'] > 4, + 'allow_protected' => allowedTo('admin_forum'), + ); + + // Get any moderators for this group + $request = $smcFunc['db_query']('', ' + SELECT mem.id_member, mem.real_name + FROM {db_prefix}group_moderators AS mods + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member) + WHERE mods.id_group = {int:current_group}', + array( + 'current_group' => $_REQUEST['group'], + ) + ); + $context['group']['moderators'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['group']['moderators'][$row['id_member']] = $row['real_name']; + $smcFunc['db_free_result']($request); + + $context['group']['moderator_list'] = empty($context['group']['moderators']) ? '' : '"' . implode('", "', $context['group']['moderators']) . '"'; + + if (!empty($context['group']['moderators'])) + list ($context['group']['last_moderator_id']) = array_slice(array_keys($context['group']['moderators']), -1); + + // Get a list of boards this membergroup is allowed to see. + $context['boards'] = array(); + if ($_REQUEST['group'] == 2 || $_REQUEST['group'] > 3) + { + $result = $smcFunc['db_query']('', ' + SELECT id_board, name, child_level, FIND_IN_SET({string:current_group}, member_groups) != 0 AS can_access + FROM {db_prefix}boards + ORDER BY board_order', + array( + 'current_group' => (int) $_REQUEST['group'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + $context['boards'][] = array( + 'id' => $row['id_board'], + 'name' => $row['name'], + 'child_level' => $row['child_level'], + 'selected' => !(empty($row['can_access']) || $row['can_access'] == 'f'), + ); + $smcFunc['db_free_result']($result); + } + + // Finally, get all the groups this could be inherited off. + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_name + FROM {db_prefix}membergroups + WHERE id_group != {int:current_group}' . + (empty($modSettings['permission_enable_postgroups']) ? ' + AND min_posts = {int:min_posts}' : '') . (allowedTo('admin_forum') ? '' : ' + AND group_type != {int:is_protected}') . ' + AND id_group NOT IN (1, 3) + AND id_parent = {int:not_inherited}', + array( + 'current_group' => (int) $_REQUEST['group'], + 'min_posts' => -1, + 'not_inherited' => -2, + 'is_protected' => 1, + ) + ); + $context['inheritable_groups'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['inheritable_groups'][$row['id_group']] = $row['group_name']; + $smcFunc['db_free_result']($request); + + $context['sub_template'] = 'edit_group'; + $context['page_title'] = $txt['membergroups_edit_group']; +} + +// Set general membergroup settings. +function ModifyMembergroupsettings() +{ + global $context, $sourcedir, $scripturl, $modSettings, $txt; + + $context['sub_template'] = 'show_settings'; + $context['page_title'] = $txt['membergroups_settings']; + + // Needed for the settings functions. + require_once($sourcedir . '/ManageServer.php'); + + // Don't allow assignment of guests. + $context['permissions_excluded'] = array(-1); + + // Only one thing here! + $config_vars = array( + array('permissions', 'manage_membergroups'), + ); + + if (isset($_REQUEST['save'])) + { + checkSession(); + + // Yeppers, saving this... + saveDBSettings($config_vars); + redirectexit('action=admin;area=membergroups;sa=settings'); + } + + // Some simple context. + $context['post_url'] = $scripturl . '?action=admin;area=membergroups;save;sa=settings'; + $context['settings_title'] = $txt['membergroups_settings']; + + prepareDBSettingContext($config_vars); +} + +?> \ No newline at end of file diff --git a/Sources/ManageMembers.php b/Sources/ManageMembers.php new file mode 100644 index 0000000..7c8dc75 --- /dev/null +++ b/Sources/ManageMembers.php @@ -0,0 +1,1307 @@ + array('ViewMemberlist', 'moderate_forum'), + 'approve' => array('AdminApprove', 'moderate_forum'), + 'browse' => array('MembersAwaitingActivation', 'moderate_forum'), + 'search' => array('SearchMembers', 'moderate_forum'), + 'query' => array('ViewMemberlist', 'moderate_forum'), + ); + + // Default to sub action 'index' or 'settings' depending on permissions. + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'all'; + + // We know the sub action, now we know what you're allowed to do. + isAllowedTo($subActions[$_REQUEST['sa']][1]); + + // Load the essentials. + loadLanguage('ManageMembers'); + loadTemplate('ManageMembers'); + + // Get counts on every type of activation - for sections and filtering alike. + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS total_members, is_activated + FROM {db_prefix}members + WHERE is_activated != {int:is_activated} + GROUP BY is_activated', + array( + 'is_activated' => 1, + ) + ); + $context['activation_numbers'] = array(); + $context['awaiting_activation'] = 0; + $context['awaiting_approval'] = 0; + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['activation_numbers'][$row['is_activated']] = $row['total_members']; + $smcFunc['db_free_result']($request); + + foreach ($context['activation_numbers'] as $activation_type => $total_members) + { + if (in_array($activation_type, array(0, 2))) + $context['awaiting_activation'] += $total_members; + elseif (in_array($activation_type, array(3, 4, 5))) + $context['awaiting_approval'] += $total_members; + } + + // For the page header... do we show activation? + $context['show_activate'] = (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 1) || !empty($context['awaiting_activation']); + + // What about approval? + $context['show_approve'] = (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($context['awaiting_approval']) || !empty($modSettings['approveAccountDeletion']); + + // Setup the admin tabs. + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['admin_members'], + 'help' => 'view_members', + 'description' => $txt['admin_members_list'], + 'tabs' => array(), + ); + + $context['tabs'] = array( + 'viewmembers' => array( + 'label' => $txt['view_all_members'], + 'description' => $txt['admin_members_list'], + 'url' => $scripturl . '?action=admin;area=viewmembers;sa=all', + 'is_selected' => $_REQUEST['sa'] == 'all', + ), + 'search' => array( + 'label' => $txt['mlist_search'], + 'description' => $txt['admin_members_list'], + 'url' => $scripturl . '?action=admin;area=viewmembers;sa=search', + 'is_selected' => $_REQUEST['sa'] == 'search' || $_REQUEST['sa'] == 'query', + ), + 'approve' => array( + 'label' => sprintf($txt['admin_browse_awaiting_approval'], $context['awaiting_approval']), + 'description' => $txt['admin_browse_approve_desc'], + 'url' => $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve', + 'is_selected' => false, + ), + 'activate' => array( + 'label' => sprintf($txt['admin_browse_awaiting_activate'], $context['awaiting_activation']), + 'description' => $txt['admin_browse_activate_desc'], + 'url' => $scripturl . '?action=admin;area=viewmembers;sa=browse;type=activate', + 'is_selected' => false, + 'is_last' => true, + ), + ); + + // Sort out the tabs for the ones which may not exist! + if (!$context['show_activate'] && ($_REQUEST['sa'] != 'browse' || $_REQUEST['type'] != 'activate')) + { + $context['tabs']['approve']['is_last'] = true; + unset($context['tabs']['activate']); + } + if (!$context['show_approve'] && ($_REQUEST['sa'] != 'browse' || $_REQUEST['type'] != 'approve')) + { + if (!$context['show_activate'] && ($_REQUEST['sa'] != 'browse' || $_REQUEST['type'] != 'activate')) + $context['tabs']['search']['is_last'] = true; + unset($context['tabs']['approve']); + } + + $subActions[$_REQUEST['sa']][0](); +} + +// View all members. +function ViewMemberlist() +{ + global $txt, $scripturl, $context, $modSettings, $sourcedir, $smcFunc, $user_info; + + // Set the current sub action. + $context['sub_action'] = $_REQUEST['sa']; + + // Are we performing a delete? + if (isset($_POST['delete_members']) && !empty($_POST['delete']) && allowedTo('profile_remove_any')) + { + checkSession(); + + // Clean the input. + foreach ($_POST['delete'] as $key => $value) + { + $_POST['delete'][$key] = (int) $value; + // Don't delete yourself, idiot. + if ($value == $user_info['id']) + unset($_POST['delete'][$key]); + } + + if (!empty($_POST['delete'])) + { + // Delete all the selected members. + require_once($sourcedir . '/Subs-Members.php'); + deleteMembers($_POST['delete'], true); + } + } + + if ($context['sub_action'] == 'query' && !empty($_REQUEST['params']) && empty($_POST)) + $_POST += @unserialize(base64_decode($_REQUEST['params'])); + + // Check input after a member search has been submitted. + if ($context['sub_action'] == 'query') + { + // Retrieving the membergroups and postgroups. + $context['membergroups'] = array( + array( + 'id' => 0, + 'name' => $txt['membergroups_members'], + 'can_be_additional' => false + ) + ); + $context['postgroups'] = array(); + + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_name, min_posts + FROM {db_prefix}membergroups + WHERE id_group != {int:moderator_group} + ORDER BY min_posts, CASE WHEN id_group < {int:newbie_group} THEN id_group ELSE 4 END, group_name', + array( + 'moderator_group' => 3, + 'newbie_group' => 4, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['min_posts'] == -1) + $context['membergroups'][] = array( + 'id' => $row['id_group'], + 'name' => $row['group_name'], + 'can_be_additional' => true + ); + else + $context['postgroups'][] = array( + 'id' => $row['id_group'], + 'name' => $row['group_name'] + ); + } + $smcFunc['db_free_result']($request); + + // Some data about the form fields and how they are linked to the database. + $params = array( + 'mem_id' => array( + 'db_fields' => array('id_member'), + 'type' => 'int', + 'range' => true + ), + 'age' => array( + 'db_fields' => array('birthdate'), + 'type' => 'age', + 'range' => true + ), + 'posts' => array( + 'db_fields' => array('posts'), + 'type' => 'int', + 'range' => true + ), + 'reg_date' => array( + 'db_fields' => array('date_registered'), + 'type' => 'date', + 'range' => true + ), + 'last_online' => array( + 'db_fields' => array('last_login'), + 'type' => 'date', + 'range' => true + ), + 'gender' => array( + 'db_fields' => array('gender'), + 'type' => 'checkbox', + 'values' => array('0', '1', '2'), + ), + 'activated' => array( + 'db_fields' => array('CASE WHEN is_activated IN (1, 11) THEN 1 ELSE 0 END'), + 'type' => 'checkbox', + 'values' => array('0', '1'), + ), + 'membername' => array( + 'db_fields' => array('member_name', 'real_name'), + 'type' => 'string' + ), + 'email' => array( + 'db_fields' => array('email_address'), + 'type' => 'string' + ), + 'website' => array( + 'db_fields' => array('website_title', 'website_url'), + 'type' => 'string' + ), + 'location' => array( + 'db_fields' => array('location'), + 'type' => 'string' + ), + 'ip' => array( + 'db_fields' => array('member_ip'), + 'type' => 'string' + ), + 'messenger' => array( + 'db_fields' => array('icq', 'aim', 'yim', 'msn'), + 'type' => 'string' + ) + ); + $range_trans = array( + '--' => '<', + '-' => '<=', + '=' => '=', + '+' => '>=', + '++' => '>' + ); + + // !!! Validate a little more. + + // Loop through every field of the form. + $query_parts = array(); + $where_params = array(); + foreach ($params as $param_name => $param_info) + { + // Not filled in? + if (!isset($_POST[$param_name]) || $_POST[$param_name] === '') + continue; + + // Make sure numeric values are really numeric. + if (in_array($param_info['type'], array('int', 'age'))) + $_POST[$param_name] = (int) $_POST[$param_name]; + // Date values have to match the specified format. + elseif ($param_info['type'] == 'date') + { + // Check if this date format is valid. + if (preg_match('/^\d{4}-\d{1,2}-\d{1,2}$/', $_POST[$param_name]) == 0) + continue; + + $_POST[$param_name] = strtotime($_POST[$param_name]); + } + + // Those values that are in some kind of range (<, <=, =, >=, >). + if (!empty($param_info['range'])) + { + // Default to '=', just in case... + if (empty($range_trans[$_POST['types'][$param_name]])) + $_POST['types'][$param_name] = '='; + + // Handle special case 'age'. + if ($param_info['type'] == 'age') + { + // All people that were born between $lowerlimit and $upperlimit are currently the specified age. + $datearray = getdate(forum_time()); + $upperlimit = sprintf('%04d-%02d-%02d', $datearray['year'] - $_POST[$param_name], $datearray['mon'], $datearray['mday']); + $lowerlimit = sprintf('%04d-%02d-%02d', $datearray['year'] - $_POST[$param_name] - 1, $datearray['mon'], $datearray['mday']); + if (in_array($_POST['types'][$param_name], array('-', '--', '='))) + { + $query_parts[] = ($param_info['db_fields'][0]) . ' > {string:' . $param_name . '_minlimit}'; + $where_params[$param_name . '_minlimit'] = ($_POST['types'][$param_name] == '--' ? $upperlimit : $lowerlimit); + } + if (in_array($_POST['types'][$param_name], array('+', '++', '='))) + { + $query_parts[] = ($param_info['db_fields'][0]) . ' <= {string:' . $param_name . '_pluslimit}'; + $where_params[$param_name . '_pluslimit'] = ($_POST['types'][$param_name] == '++' ? $lowerlimit : $upperlimit); + + // Make sure that members that didn't set their birth year are not queried. + $query_parts[] = ($param_info['db_fields'][0]) . ' > {date:dec_zero_date}'; + $where_params['dec_zero_date'] = '0004-12-31'; + } + } + // Special case - equals a date. + elseif ($param_info['type'] == 'date' && $_POST['types'][$param_name] == '=') + { + $query_parts[] = $param_info['db_fields'][0] . ' > ' . $_POST[$param_name] . ' AND ' . $param_info['db_fields'][0] . ' < ' . ($_POST[$param_name] + 86400); + } + else + $query_parts[] = $param_info['db_fields'][0] . ' ' . $range_trans[$_POST['types'][$param_name]] . ' ' . $_POST[$param_name]; + } + // Checkboxes. + elseif ($param_info['type'] == 'checkbox') + { + // Each checkbox or no checkbox at all is checked -> ignore. + if (!is_array($_POST[$param_name]) || count($_POST[$param_name]) == 0 || count($_POST[$param_name]) == count($param_info['values'])) + continue; + + $query_parts[] = ($param_info['db_fields'][0]) . ' IN ({array_string:' . $param_name . '_check})'; + $where_params[$param_name . '_check'] = $_POST[$param_name]; + } + else + { + // Replace the wildcard characters ('*' and '?') into MySQL ones. + $parameter = strtolower(strtr($smcFunc['htmlspecialchars']($_POST[$param_name], ENT_QUOTES), array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_'))); + + $query_parts[] = '(' . implode( ' LIKE {string:' . $param_name . '_normal} OR ', $param_info['db_fields']) . ' LIKE {string:' . $param_name . '_normal})'; + $where_params[$param_name . '_normal'] = '%' . $parameter . '%'; + } + } + + // Set up the membergroup query part. + $mg_query_parts = array(); + + // Primary membergroups, but only if at least was was not selected. + if (!empty($_POST['membergroups'][1]) && count($context['membergroups']) != count($_POST['membergroups'][1])) + { + $mg_query_parts[] = 'mem.id_group IN ({array_int:group_check})'; + $where_params['group_check'] = $_POST['membergroups'][1]; + } + + // Additional membergroups (these are only relevant if not all primary groups where selected!). + if (!empty($_POST['membergroups'][2]) && (empty($_POST['membergroups'][1]) || count($context['membergroups']) != count($_POST['membergroups'][1]))) + foreach ($_POST['membergroups'][2] as $mg) + { + $mg_query_parts[] = 'FIND_IN_SET({int:add_group_' . $mg . '}, mem.additional_groups) != 0'; + $where_params['add_group_' . $mg] = $mg; + } + + // Combine the one or two membergroup parts into one query part linked with an OR. + if (!empty($mg_query_parts)) + $query_parts[] = '(' . implode(' OR ', $mg_query_parts) . ')'; + + // Get all selected post count related membergroups. + if (!empty($_POST['postgroups']) && count($_POST['postgroups']) != count($context['postgroups'])) + { + $query_parts[] = 'id_post_group IN ({array_int:post_groups})'; + $where_params['post_groups'] = $_POST['postgroups']; + } + + // Construct the where part of the query. + $where = empty($query_parts) ? '1' : implode(' + AND ', $query_parts); + + $search_params = base64_encode(serialize($_POST)); + } + else + $search_params = null; + + // Construct the additional URL part with the query info in it. + $context['params_url'] = $context['sub_action'] == 'query' ? ';sa=query;params=' . $search_params : ''; + + // Get the title and sub template ready.. + $context['page_title'] = $txt['admin_members']; + + $listOptions = array( + 'id' => 'member_list', + 'items_per_page' => $modSettings['defaultMaxMembers'], + 'base_href' => $scripturl . '?action=admin;area=viewmembers' . $context['params_url'], + 'default_sort_col' => 'user_name', + 'get_items' => array( + 'file' => $sourcedir . '/Subs-Members.php', + 'function' => 'list_getMembers', + 'params' => array( + isset($where) ? $where : '1=1', + isset($where_params) ? $where_params : array(), + ), + ), + 'get_count' => array( + 'file' => $sourcedir . '/Subs-Members.php', + 'function' => 'list_getNumMembers', + 'params' => array( + isset($where) ? $where : '1=1', + isset($where_params) ? $where_params : array(), + ), + ), + 'columns' => array( + 'id_member' => array( + 'header' => array( + 'value' => $txt['member_id'], + ), + 'data' => array( + 'db' => 'id_member', + 'class' => 'windowbg', + 'style' => 'text-align: center;', + ), + 'sort' => array( + 'default' => 'id_member', + 'reverse' => 'id_member DESC', + ), + ), + 'user_name' => array( + 'header' => array( + 'value' => $txt['username'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%2$s', + 'params' => array( + 'id_member' => false, + 'member_name' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'member_name', + 'reverse' => 'member_name DESC', + ), + ), + 'display_name' => array( + 'header' => array( + 'value' => $txt['display_name'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%2$s', + 'params' => array( + 'id_member' => false, + 'real_name' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'real_name', + 'reverse' => 'real_name DESC', + ), + ), + 'email' => array( + 'header' => array( + 'value' => $txt['email_address'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%1$s', + 'params' => array( + 'email_address' => true, + ), + ), + 'class' => 'windowbg', + ), + 'sort' => array( + 'default' => 'email_address', + 'reverse' => 'email_address DESC', + ), + ), + 'ip' => array( + 'header' => array( + 'value' => $txt['ip_address'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%1$s', + 'params' => array( + 'member_ip' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'INET_ATON(member_ip)', + 'reverse' => 'INET_ATON(member_ip) DESC', + ), + ), + 'last_active' => array( + 'header' => array( + 'value' => $txt['viewmembers_online'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt; + + // Calculate number of days since last online. + if (empty($rowData[\'last_login\'])) + $difference = $txt[\'never\']; + else + { + $num_days_difference = jeffsdatediff($rowData[\'last_login\']); + + // Today. + if (empty($num_days_difference)) + $difference = $txt[\'viewmembers_today\']; + + // Yesterday. + elseif ($num_days_difference == 1) + $difference = sprintf(\'1 %1$s\', $txt[\'viewmembers_day_ago\']); + + // X days ago. + else + $difference = sprintf(\'%1$d %2$s\', $num_days_difference, $txt[\'viewmembers_days_ago\']); + } + + // Show it in italics if they\'re not activated... + if ($rowData[\'is_activated\'] % 10 != 1) + $difference = sprintf(\'%2$s\', $txt[\'not_activated\'], $difference); + + return $difference; + '), + ), + 'sort' => array( + 'default' => 'last_login DESC', + 'reverse' => 'last_login', + ), + ), + 'posts' => array( + 'header' => array( + 'value' => $txt['member_postcount'], + ), + 'data' => array( + 'db' => 'posts', + ), + 'sort' => array( + 'default' => 'posts', + 'reverse' => 'posts DESC', + ), + ), + 'check' => array( + 'header' => array( + 'value' => '', + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $user_info; + + return \'\'; + '), + 'class' => 'windowbg', + 'style' => 'text-align: center', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=viewmembers' . $context['params_url'], + 'include_start' => true, + 'include_sort' => true, + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => '', + 'style' => 'text-align: right;', + ), + ), + ); + + // Without not enough permissions, don't show 'delete members' checkboxes. + if (!allowedTo('profile_remove_any')) + unset($listOptions['cols']['check'], $listOptions['form'], $listOptions['additional_rows']); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'member_list'; +} + +// Search the member list, using one or more criteria. +function SearchMembers() +{ + global $context, $txt, $smcFunc; + + // Get a list of all the membergroups and postgroups that can be selected. + $context['membergroups'] = array( + array( + 'id' => 0, + 'name' => $txt['membergroups_members'], + 'can_be_additional' => false + ) + ); + $context['postgroups'] = array(); + + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_name, min_posts + FROM {db_prefix}membergroups + WHERE id_group != {int:moderator_group} + ORDER BY min_posts, CASE WHEN id_group < {int:newbie_group} THEN id_group ELSE 4 END, group_name', + array( + 'moderator_group' => 3, + 'newbie_group' => 4, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['min_posts'] == -1) + $context['membergroups'][] = array( + 'id' => $row['id_group'], + 'name' => $row['group_name'], + 'can_be_additional' => true + ); + else + $context['postgroups'][] = array( + 'id' => $row['id_group'], + 'name' => $row['group_name'] + ); + } + $smcFunc['db_free_result']($request); + + $context['page_title'] = $txt['admin_members']; + $context['sub_template'] = 'search_members'; +} + +// List all members who are awaiting approval / activation +function MembersAwaitingActivation() +{ + global $txt, $context, $scripturl, $modSettings, $smcFunc; + global $sourcedir; + + // Not a lot here! + $context['page_title'] = $txt['admin_members']; + $context['sub_template'] = 'admin_browse'; + $context['browse_type'] = isset($_REQUEST['type']) ? $_REQUEST['type'] : (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 1 ? 'activate' : 'approve'); + if (isset($context['tabs'][$context['browse_type']])) + $context['tabs'][$context['browse_type']]['is_selected'] = true; + + // Allowed filters are those we can have, in theory. + $context['allowed_filters'] = $context['browse_type'] == 'approve' ? array(3, 4, 5) : array(0, 2); + $context['current_filter'] = isset($_REQUEST['filter']) && in_array($_REQUEST['filter'], $context['allowed_filters']) && !empty($context['activation_numbers'][$_REQUEST['filter']]) ? (int) $_REQUEST['filter'] : -1; + + // Sort out the different sub areas that we can actually filter by. + $context['available_filters'] = array(); + foreach ($context['activation_numbers'] as $type => $amount) + { + // We have some of these... + if (in_array($type, $context['allowed_filters']) && $amount > 0) + $context['available_filters'][] = array( + 'type' => $type, + 'amount' => $amount, + 'desc' => isset($txt['admin_browse_filter_type_' . $type]) ? $txt['admin_browse_filter_type_' . $type] : '?', + 'selected' => $type == $context['current_filter'] + ); + } + + // If the filter was not sent, set it to whatever has people in it! + if ($context['current_filter'] == -1 && !empty($context['available_filters'][0]['amount'])) + $context['current_filter'] = $context['available_filters'][0]['type']; + + // This little variable is used to determine if we should flag where we are looking. + $context['show_filter'] = ($context['current_filter'] != 0 && $context['current_filter'] != 3) || count($context['available_filters']) > 1; + + // The columns that can be sorted. + $context['columns'] = array( + 'id_member' => array('label' => $txt['admin_browse_id']), + 'member_name' => array('label' => $txt['admin_browse_username']), + 'email_address' => array('label' => $txt['admin_browse_email']), + 'member_ip' => array('label' => $txt['admin_browse_ip']), + 'date_registered' => array('label' => $txt['admin_browse_registered']), + ); + + // Are we showing duplicate information? + if (isset($_GET['showdupes'])) + $_SESSION['showdupes'] = (int) $_GET['showdupes']; + $context['show_duplicates'] = !empty($_SESSION['showdupes']); + + // Determine which actions we should allow on this page. + if ($context['browse_type'] == 'approve') + { + // If we are approving deleted accounts we have a slightly different list... actually a mirror ;) + if ($context['current_filter'] == 4) + $context['allowed_actions'] = array( + 'reject' => $txt['admin_browse_w_approve_deletion'], + 'ok' => $txt['admin_browse_w_reject'], + ); + else + $context['allowed_actions'] = array( + 'ok' => $txt['admin_browse_w_approve'], + 'okemail' => $txt['admin_browse_w_approve'] . ' ' . $txt['admin_browse_w_email'], + 'require_activation' => $txt['admin_browse_w_approve_require_activate'], + 'reject' => $txt['admin_browse_w_reject'], + 'rejectemail' => $txt['admin_browse_w_reject'] . ' ' . $txt['admin_browse_w_email'], + ); + } + elseif ($context['browse_type'] == 'activate') + $context['allowed_actions'] = array( + 'ok' => $txt['admin_browse_w_activate'], + 'okemail' => $txt['admin_browse_w_activate'] . ' ' . $txt['admin_browse_w_email'], + 'delete' => $txt['admin_browse_w_delete'], + 'deleteemail' => $txt['admin_browse_w_delete'] . ' ' . $txt['admin_browse_w_email'], + 'remind' => $txt['admin_browse_w_remind'] . ' ' . $txt['admin_browse_w_email'], + ); + + // Create an option list for actions allowed to be done with selected members. + $allowed_actions = ' + + '; + foreach ($context['allowed_actions'] as $key => $desc) + $allowed_actions .= ' + '; + + // Setup the Javascript function for selecting an action for the list. + $javascript = ' + function onSelectChange() + { + if (document.forms.postForm.todo.value == "") + return; + + var message = "";'; + + // We have special messages for approving deletion of accounts - it's surprisingly logical - honest. + if ($context['current_filter'] == 4) + $javascript .= ' + if (document.forms.postForm.todo.value.indexOf("reject") != -1) + message = "' . $txt['admin_browse_w_delete'] . '"; + else + message = "' . $txt['admin_browse_w_reject'] . '";'; + // Otherwise a nice standard message. + else + $javascript .= ' + if (document.forms.postForm.todo.value.indexOf("delete") != -1) + message = "' . $txt['admin_browse_w_delete'] . '"; + else if (document.forms.postForm.todo.value.indexOf("reject") != -1) + message = "' . $txt['admin_browse_w_reject'] . '"; + else if (document.forms.postForm.todo.value == "remind") + message = "' . $txt['admin_browse_w_remind'] . '"; + else + message = "' . ($context['browse_type'] == 'approve' ? $txt['admin_browse_w_approve'] : $txt['admin_browse_w_activate']) . '";'; + $javascript .= ' + if (confirm(message + " ' . $txt['admin_browse_warn'] . '")) + document.forms.postForm.submit(); + }'; + + $listOptions = array( + 'id' => 'approve_list', + 'items_per_page' => $modSettings['defaultMaxMembers'], + 'base_href' => $scripturl . '?action=admin;area=viewmembers;sa=browse;type=' . $context['browse_type'] . (!empty($context['show_filter']) ? ';filter=' . $context['current_filter'] : ''), + 'default_sort_col' => 'date_registered', + 'get_items' => array( + 'file' => $sourcedir . '/Subs-Members.php', + 'function' => 'list_getMembers', + 'params' => array( + 'is_activated = {int:activated_status}', + array('activated_status' => $context['current_filter']), + $context['show_duplicates'], + ), + ), + 'get_count' => array( + 'file' => $sourcedir . '/Subs-Members.php', + 'function' => 'list_getNumMembers', + 'params' => array( + 'is_activated = {int:activated_status}', + array('activated_status' => $context['current_filter']), + ), + ), + 'columns' => array( + 'id_member' => array( + 'header' => array( + 'value' => $txt['member_id'], + ), + 'data' => array( + 'db' => 'id_member', + 'class' => 'windowbg', + 'style' => 'text-align: center;', + ), + 'sort' => array( + 'default' => 'id_member', + 'reverse' => 'id_member DESC', + ), + ), + 'user_name' => array( + 'header' => array( + 'value' => $txt['username'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%2$s', + 'params' => array( + 'id_member' => false, + 'member_name' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'member_name', + 'reverse' => 'member_name DESC', + ), + ), + 'email' => array( + 'header' => array( + 'value' => $txt['email_address'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%1$s', + 'params' => array( + 'email_address' => true, + ), + ), + 'class' => 'windowbg', + ), + 'sort' => array( + 'default' => 'email_address', + 'reverse' => 'email_address DESC', + ), + ), + 'ip' => array( + 'header' => array( + 'value' => $txt['ip_address'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%1$s', + 'params' => array( + 'member_ip' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'INET_ATON(member_ip)', + 'reverse' => 'INET_ATON(member_ip) DESC', + ), + ), + 'hostname' => array( + 'header' => array( + 'value' => $txt['hostname'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $modSettings; + + return host_from_ip($rowData[\'member_ip\']); + '), + 'class' => 'smalltext', + ), + ), + 'date_registered' => array( + 'header' => array( + 'value' => $txt['date_registered'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + return timeformat($rowData[\'date_registered\']); + '), + ), + 'sort' => array( + 'default' => 'date_registered DESC', + 'reverse' => 'date_registered', + ), + ), + 'duplicates' => array( + 'header' => array( + 'value' => $txt['duplicates'], + // Make sure it doesn't go too wide. + 'style' => 'width: 20%', + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $scripturl, $txt; + + $member_links = array(); + foreach ($rowData[\'duplicate_members\'] as $member) + { + if ($member[\'id\']) + $member_links[] = \'\' . $member[\'name\'] . \'\'; + else + $member_links[] = $member[\'name\'] . \' (\' . $txt[\'guest\'] . \')\'; + } + return implode (\', \', $member_links); + '), + 'class' => 'smalltext', + ), + ), + 'check' => array( + 'header' => array( + 'value' => '', + ), + 'data' => array( + 'sprintf' => array( + 'format' => '', + 'params' => array( + 'id_member' => false, + ), + ), + 'class' => 'windowbg', + 'style' => 'text-align: center', + ), + ), + ), + 'javascript' => $javascript, + 'form' => array( + 'href' => $scripturl . '?action=admin;area=viewmembers;sa=approve;type=' . $context['browse_type'], + 'name' => 'postForm', + 'include_start' => true, + 'include_sort' => true, + 'hidden_fields' => array( + 'orig_filter' => $context['current_filter'], + ), + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => ' + +
+ + +
', + ), + ), + ); + + // Pick what column to actually include if we're showing duplicates. + if ($context['show_duplicates']) + unset($listOptions['columns']['email']); + else + unset($listOptions['columns']['duplicates']); + + // Only show hostname on duplicates as it takes a lot of time. + if (!$context['show_duplicates'] || !empty($modSettings['disableHostnameLookup'])) + unset($listOptions['columns']['hostname']); + + // Is there any need to show filters? + if (isset($context['available_filters']) && count($context['available_filters']) > 1) + { + $filterOptions = ' + ' . $txt['admin_browse_filter_by'] . ': + + '; + $listOptions['additional_rows'][] = array( + 'position' => 'above_column_headers', + 'value' => $filterOptions, + 'style' => 'text-align: center;', + ); + } + + // What about if we only have one filter, but it's not the "standard" filter - show them what they are looking at. + if (!empty($context['show_filter']) && !empty($context['available_filters'])) + $listOptions['additional_rows'][] = array( + 'position' => 'above_column_headers', + 'value' => '' . $txt['admin_browse_filter_show'] . ': ' . $context['available_filters'][0]['desc'], + 'class' => 'smalltext', + 'style' => 'text-align: left;', + ); + + // Now that we have all the options, create the list. + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); +} + +// Do the approve/activate/delete stuff +function AdminApprove() +{ + global $txt, $context, $scripturl, $modSettings, $sourcedir, $language, $user_info, $smcFunc; + + // First, check our session. + checkSession(); + + require_once($sourcedir . '/Subs-Post.php'); + + // We also need to the login languages here - for emails. + loadLanguage('Login'); + + // Sort out where we are going... + $browse_type = isset($_REQUEST['type']) ? $_REQUEST['type'] : (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 1 ? 'activate' : 'approve'); + $current_filter = (int) $_REQUEST['orig_filter']; + + // If we are applying a filter do just that - then redirect. + if (isset($_REQUEST['filter']) && $_REQUEST['filter'] != $_REQUEST['orig_filter']) + redirectexit('action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $_REQUEST['filter'] . ';start=' . $_REQUEST['start']); + + // Nothing to do? + if (!isset($_POST['todoAction']) && !isset($_POST['time_passed'])) + redirectexit('action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start']); + + // Are we dealing with members who have been waiting for > set amount of time? + if (isset($_POST['time_passed'])) + { + $timeBefore = time() - 86400 * (int) $_POST['time_passed']; + $condition = ' + AND date_registered < {int:time_before}'; + } + // Coming from checkboxes - validate the members passed through to us. + else + { + $members = array(); + foreach ($_POST['todoAction'] as $id) + $members[] = (int) $id; + $condition = ' + AND id_member IN ({array_int:members})'; + } + + // Get information on each of the members, things that are important to us, like email address... + $request = $smcFunc['db_query']('', ' + SELECT id_member, member_name, real_name, email_address, validation_code, lngfile + FROM {db_prefix}members + WHERE is_activated = {int:activated_status}' . $condition . ' + ORDER BY lngfile', + array( + 'activated_status' => $current_filter, + 'time_before' => empty($timeBefore) ? 0 : $timeBefore, + 'members' => empty($members) ? array() : $members, + ) + ); + + $member_count = $smcFunc['db_num_rows']($request); + + // If no results then just return! + if ($member_count == 0) + redirectexit('action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start']); + + $member_info = array(); + $members = array(); + // Fill the info array. + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $members[] = $row['id_member']; + $member_info[] = array( + 'id' => $row['id_member'], + 'username' => $row['member_name'], + 'name' => $row['real_name'], + 'email' => $row['email_address'], + 'language' => empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile'], + 'code' => $row['validation_code'] + ); + } + $smcFunc['db_free_result']($request); + + // Are we activating or approving the members? + if ($_POST['todo'] == 'ok' || $_POST['todo'] == 'okemail') + { + // Approve/activate this member. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET validation_code = {string:blank_string}, is_activated = {int:is_activated} + WHERE is_activated = {int:activated_status}' . $condition, + array( + 'is_activated' => 1, + 'time_before' => empty($timeBefore) ? 0 : $timeBefore, + 'members' => empty($members) ? array() : $members, + 'activated_status' => $current_filter, + 'blank_string' => '', + ) + ); + + // Do we have to let the integration code know about the activations? + if (!empty($modSettings['integrate_activate'])) + { + foreach ($member_info as $member) + call_integration_hook('integrate_activate', array($member['username'])); + } + + // Check for email. + if ($_POST['todo'] == 'okemail') + { + foreach ($member_info as $member) + { + $replacements = array( + 'NAME' => $member['name'], + 'USERNAME' => $member['username'], + 'PROFILELINK' => $scripturl . '?action=profile;u=' . $member['id'], + 'FORGOTPASSWORDLINK' => $scripturl . '?action=reminder', + ); + + $emaildata = loadEmailTemplate('admin_approve_accept', $replacements, $member['language']); + sendmail($member['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 0); + } + } + } + // Maybe we're sending it off for activation? + elseif ($_POST['todo'] == 'require_activation') + { + require_once($sourcedir . '/Subs-Members.php'); + + // We have to do this for each member I'm afraid. + foreach ($member_info as $member) + { + // Generate a random activation code. + $validation_code = generateValidationCode(); + + // Set these members for activation - I know this includes two id_member checks but it's safer than bodging $condition ;). + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET validation_code = {string:validation_code}, is_activated = {int:not_activated} + WHERE is_activated = {int:activated_status} + ' . $condition . ' + AND id_member = {int:selected_member}', + array( + 'not_activated' => 0, + 'activated_status' => $current_filter, + 'selected_member' => $member['id'], + 'validation_code' => $validation_code, + 'time_before' => empty($timeBefore) ? 0 : $timeBefore, + 'members' => empty($members) ? array() : $members, + ) + ); + + $replacements = array( + 'USERNAME' => $member['name'], + 'ACTIVATIONLINK' => $scripturl . '?action=activate;u=' . $member['id'] . ';code=' . $validation_code, + 'ACTIVATIONLINKWITHOUTCODE' => $scripturl . '?action=activate;u=' . $member['id'], + 'ACTIVATIONCODE' => $validation_code, + ); + + $emaildata = loadEmailTemplate('admin_approve_activation', $replacements, $member['language']); + sendmail($member['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 0); + } + } + // Are we rejecting them? + elseif ($_POST['todo'] == 'reject' || $_POST['todo'] == 'rejectemail') + { + require_once($sourcedir . '/Subs-Members.php'); + deleteMembers($members); + + // Send email telling them they aren't welcome? + if ($_POST['todo'] == 'rejectemail') + { + foreach ($member_info as $member) + { + $replacements = array( + 'USERNAME' => $member['name'], + ); + + $emaildata = loadEmailTemplate('admin_approve_reject', $replacements, $member['language']); + sendmail($member['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 1); + } + } + } + // A simple delete? + elseif ($_POST['todo'] == 'delete' || $_POST['todo'] == 'deleteemail') + { + require_once($sourcedir . '/Subs-Members.php'); + deleteMembers($members); + + // Send email telling them they aren't welcome? + if ($_POST['todo'] == 'deleteemail') + { + foreach ($member_info as $member) + { + $replacements = array( + 'USERNAME' => $member['name'], + ); + + $emaildata = loadEmailTemplate('admin_approve_delete', $replacements, $member['language']); + sendmail($member['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 1); + } + } + } + // Remind them to activate their account? + elseif ($_POST['todo'] == 'remind') + { + foreach ($member_info as $member) + { + $replacements = array( + 'USERNAME' => $member['name'], + 'ACTIVATIONLINK' => $scripturl . '?action=activate;u=' . $member['id'] . ';code=' . $member['code'], + 'ACTIVATIONLINKWITHOUTCODE' => $scripturl . '?action=activate;u=' . $member['id'], + 'ACTIVATIONCODE' => $member['code'], + ); + + $emaildata = loadEmailTemplate('admin_approve_remind', $replacements, $member['language']); + sendmail($member['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 1); + } + } + + // Back to the user's language! + if (isset($current_language) && $current_language != $user_info['language']) + { + loadLanguage('index'); + loadLanguage('ManageMembers'); + } + + // Log what we did? + if (!empty($modSettings['modlog_enabled']) && in_array($_POST['todo'], array('ok', 'okemail', 'require_activation', 'remind'))) + { + $log_action = $_POST['todo'] == 'remind' ? 'remind_member' : 'approve_member'; + $log_inserts = array(); + foreach ($member_info as $member) + { + $log_inserts[] = array( + time(), 3, $user_info['id'], $user_info['ip'], $log_action, + 0, 0, 0, serialize(array('member' => $member['id'])), + ); + } + $smcFunc['db_insert']('', + '{db_prefix}log_actions', + array( + 'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'string-16', 'action' => 'string', + 'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534', + ), + $log_inserts, + array('id_action') + ); + } + + // Although updateStats *may* catch this, best to do it manually just in case (Doesn't always sort out unapprovedMembers). + if (in_array($current_filter, array(3, 4))) + updateSettings(array('unapprovedMembers' => ($modSettings['unapprovedMembers'] > $member_count ? $modSettings['unapprovedMembers'] - $member_count : 0))); + + // Update the member's stats. (but, we know the member didn't change their name.) + updateStats('member', false); + + // If they haven't been deleted, update the post group statistics on them... + if (!in_array($_POST['todo'], array('delete', 'deleteemail', 'reject', 'rejectemail', 'remind'))) + updateStats('postgroups', $members); + + redirectexit('action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start']); +} + +function jeffsdatediff($old) +{ + // Get the current time as the user would see it... + $forumTime = forum_time(); + + // Calculate the seconds that have passed since midnight. + $sinceMidnight = date('H', $forumTime) * 60 * 60 + date('i', $forumTime) * 60 + date('s', $forumTime); + + // Take the difference between the two times. + $dis = time() - $old; + + // Before midnight? + if ($dis < $sinceMidnight) + return 0; + else + $dis -= $sinceMidnight; + + // Divide out the seconds in a day to get the number of days. + return ceil($dis / (24 * 60 * 60)); +} + +?> \ No newline at end of file diff --git a/Sources/ManageNews.php b/Sources/ManageNews.php new file mode 100644 index 0000000..14bb9c3 --- /dev/null +++ b/Sources/ManageNews.php @@ -0,0 +1,816 @@ + array('function', 'permission') + $subActions = array( + 'editnews' => array('EditNews', 'edit_news'), + 'mailingmembers' => array('SelectMailingMembers', 'send_mail'), + 'mailingcompose' => array('ComposeMailing', 'send_mail'), + 'mailingsend' => array('SendMailing', 'send_mail'), + 'settings' => array('ModifyNewsSettings', 'admin_forum'), + ); + + // Default to sub action 'main' or 'settings' depending on permissions. + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : (allowedTo('edit_news') ? 'editnews' : (allowedTo('send_mail') ? 'mailingmembers' : 'settings')); + + // Have you got the proper permissions? + isAllowedTo($subActions[$_REQUEST['sa']][1]); + + // Create the tabs for the template. + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['news_title'], + 'help' => 'edit_news', + 'description' => $txt['admin_news_desc'], + 'tabs' => array( + 'editnews' => array( + ), + 'mailingmembers' => array( + 'description' => $txt['news_mailing_desc'], + ), + 'settings' => array( + 'description' => $txt['news_settings_desc'], + ), + ), + ); + + // Force the right area... + if (substr($_REQUEST['sa'], 0, 7) == 'mailing') + $context[$context['admin_menu_name']]['current_subsection'] = 'mailingmembers'; + + $subActions[$_REQUEST['sa']][0](); +} + +// Let the administrator(s) edit the news. +function EditNews() +{ + global $txt, $modSettings, $context, $sourcedir, $user_info; + global $smcFunc; + + require_once($sourcedir . '/Subs-Post.php'); + + // The 'remove selected' button was pressed. + if (!empty($_POST['delete_selection']) && !empty($_POST['remove'])) + { + checkSession(); + + // Store the news temporarily in this array. + $temp_news = explode("\n", $modSettings['news']); + + // Remove the items that were selected. + foreach ($temp_news as $i => $news) + if (in_array($i, $_POST['remove'])) + unset($temp_news[$i]); + + // Update the database. + updateSettings(array('news' => implode("\n", $temp_news))); + + logAction('news'); + } + // The 'Save' button was pressed. + elseif (!empty($_POST['save_items'])) + { + checkSession(); + + foreach ($_POST['news'] as $i => $news) + { + if (trim($news) == '') + unset($_POST['news'][$i]); + else + { + $_POST['news'][$i] = $smcFunc['htmlspecialchars']($_POST['news'][$i], ENT_QUOTES); + preparsecode($_POST['news'][$i]); + } + } + + // Send the new news to the database. + updateSettings(array('news' => implode("\n", $_POST['news']))); + + // Log this into the moderation log. + logAction('news'); + } + + // Ready the current news. + foreach (explode("\n", $modSettings['news']) as $id => $line) + $context['admin_current_news'][$id] = array( + 'id' => $id, + 'unparsed' => un_preparsecode($line), + 'parsed' => preg_replace('~<([/]?)form[^>]*?[>]*>~i', '<$1form>', parse_bbc($line)), + ); + + $context['sub_template'] = 'edit_news'; + $context['page_title'] = $txt['admin_edit_news']; +} + +function SelectMailingMembers() +{ + global $txt, $context, $modSettings, $smcFunc; + + $context['page_title'] = $txt['admin_newsletters']; + + $context['sub_template'] = 'email_members'; + + $context['groups'] = array(); + $postGroups = array(); + $normalGroups = array(); + + // If we have post groups disabled then we need to give a "ungrouped members" option. + if (empty($modSettings['permission_enable_postgroups'])) + { + $context['groups'][0] = array( + 'id' => 0, + 'name' => $txt['membergroups_members'], + 'member_count' => 0, + ); + $normalGroups[0] = 0; + } + + // Get all the extra groups as well as Administrator and Global Moderator. + $request = $smcFunc['db_query']('', ' + SELECT mg.id_group, mg.group_name, mg.min_posts + FROM {db_prefix}membergroups AS mg' . (empty($modSettings['permission_enable_postgroups']) ? ' + WHERE mg.min_posts = {int:min_posts}' : '') . ' + GROUP BY mg.id_group, mg.min_posts, mg.group_name + ORDER BY mg.min_posts, CASE WHEN mg.id_group < {int:newbie_group} THEN mg.id_group ELSE 4 END, mg.group_name', + array( + 'min_posts' => -1, + 'newbie_group' => 4, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $context['groups'][$row['id_group']] = array( + 'id' => $row['id_group'], + 'name' => $row['group_name'], + 'member_count' => 0, + ); + + if ($row['min_posts'] == -1) + $normalGroups[$row['id_group']] = $row['id_group']; + else + $postGroups[$row['id_group']] = $row['id_group']; + } + $smcFunc['db_free_result']($request); + + // If we have post groups, let's count the number of members... + if (!empty($postGroups)) + { + $query = $smcFunc['db_query']('', ' + SELECT mem.id_post_group AS id_group, COUNT(*) AS member_count + FROM {db_prefix}members AS mem + WHERE mem.id_post_group IN ({array_int:post_group_list}) + GROUP BY mem.id_post_group', + array( + 'post_group_list' => $postGroups, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($query)) + $context['groups'][$row['id_group']]['member_count'] += $row['member_count']; + $smcFunc['db_free_result']($query); + } + + if (!empty($normalGroups)) + { + // Find people who are members of this group... + $query = $smcFunc['db_query']('', ' + SELECT id_group, COUNT(*) AS member_count + FROM {db_prefix}members + WHERE id_group IN ({array_int:normal_group_list}) + GROUP BY id_group', + array( + 'normal_group_list' => $normalGroups, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($query)) + $context['groups'][$row['id_group']]['member_count'] += $row['member_count']; + $smcFunc['db_free_result']($query); + + // Also do those who have it as an additional membergroup - this ones more yucky... + $query = $smcFunc['db_query']('', ' + SELECT mg.id_group, COUNT(*) AS member_count + FROM {db_prefix}membergroups AS mg + INNER JOIN {db_prefix}members AS mem ON (mem.additional_groups != {string:blank_string} + AND mem.id_group != mg.id_group + AND FIND_IN_SET(mg.id_group, mem.additional_groups) != 0) + WHERE mg.id_group IN ({array_int:normal_group_list}) + GROUP BY mg.id_group', + array( + 'normal_group_list' => $normalGroups, + 'blank_string' => '', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($query)) + $context['groups'][$row['id_group']]['member_count'] += $row['member_count']; + $smcFunc['db_free_result']($query); + } + + // Any moderators? + $request = $smcFunc['db_query']('', ' + SELECT COUNT(DISTINCT id_member) AS num_distinct_mods + FROM {db_prefix}moderators + LIMIT 1', + array( + ) + ); + list ($context['groups'][3]['member_count']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $context['can_send_pm'] = allowedTo('pm_send'); +} + +// Email your members... +function ComposeMailing() +{ + global $txt, $sourcedir, $context, $smcFunc; + + // Start by finding any members! + $toClean = array(); + if (!empty($_POST['members'])) + $toClean[] = 'members'; + if (!empty($_POST['exclude_members'])) + $toClean[] = 'exclude_members'; + if (!empty($toClean)) + { + require_once($sourcedir . '/Subs-Auth.php'); + foreach ($toClean as $type) + { + // Remove the quotes. + $_POST[$type] = strtr($_POST[$type], array('\\"' => '"')); + + preg_match_all('~"([^"]+)"~', $_POST[$type], $matches); + $_POST[$type] = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $_POST[$type])))); + + foreach ($_POST[$type] as $index => $member) + if (strlen(trim($member)) > 0) + $_POST[$type][$index] = $smcFunc['htmlspecialchars']($smcFunc['strtolower'](trim($member))); + else + unset($_POST[$type][$index]); + + // Find the members + $_POST[$type] = implode(',', array_keys(findMembers($_POST[$type]))); + } + } + + if (isset($_POST['member_list']) && is_array($_POST['member_list'])) + { + $members = array(); + foreach ($_POST['member_list'] as $member_id) + $members[] = (int) $member_id; + $_POST['members'] = implode(',', $members); + } + + if (isset($_POST['exclude_member_list']) && is_array($_POST['exclude_member_list'])) + { + $members = array(); + foreach ($_POST['exclude_member_list'] as $member_id) + $members[] = (int) $member_id; + $_POST['exclude_members'] = implode(',', $members); + } + + // Clean the other vars. + SendMailing(true); + + // We need a couple strings from the email template file + loadLanguage('EmailTemplates'); + + // Get a list of all full banned users. Use their Username and email to find them. Only get the ones that can't login to turn off notification. + $request = $smcFunc['db_query']('', ' + SELECT DISTINCT mem.id_member + FROM {db_prefix}ban_groups AS bg + INNER JOIN {db_prefix}ban_items AS bi ON (bg.id_ban_group = bi.id_ban_group) + INNER JOIN {db_prefix}members AS mem ON (bi.id_member = mem.id_member) + WHERE (bg.cannot_access = {int:cannot_access} OR bg.cannot_login = {int:cannot_login}) + AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time})', + array( + 'cannot_access' => 1, + 'cannot_login' => 1, + 'current_time' => time(), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['recipients']['exclude_members'][] = $row['id_member']; + $smcFunc['db_free_result']($request); + + $request = $smcFunc['db_query']('', ' + SELECT DISTINCT bi.email_address + FROM {db_prefix}ban_items AS bi + INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group) + WHERE (bg.cannot_access = {int:cannot_access} OR bg.cannot_login = {int:cannot_login}) + AND (COALESCE(bg.expire_time, 1=1) OR bg.expire_time > {int:current_time}) + AND bi.email_address != {string:blank_string}', + array( + 'cannot_access' => 1, + 'cannot_login' => 1, + 'current_time' => time(), + 'blank_string' => '', + ) + ); + $condition_array = array(); + $condition_array_params = array(); + $count = 0; + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $condition_array[] = '{string:email_' . $count . '}'; + $condition_array_params['email_' . $count++] = $row['email_address']; + } + + if (!empty($condition_array)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE email_address IN(' . implode(', ', $condition_array) .')', + $condition_array_params + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['recipients']['exclude_members'][] = $row['id_member']; + } + + // Did they select moderators - if so add them as specific members... + if ((!empty($context['recipients']['groups']) && in_array(3, $context['recipients']['groups'])) || (!empty($context['recipients']['exclude_groups']) && in_array(3, $context['recipients']['exclude_groups']))) + { + $request = $smcFunc['db_query']('', ' + SELECT DISTINCT mem.id_member AS identifier + FROM {db_prefix}members AS mem + INNER JOIN {db_prefix}moderators AS mods ON (mods.id_member = mem.id_member) + WHERE mem.is_activated = {int:is_activated}', + array( + 'is_activated' => 1, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (in_array(3, $context['recipients'])) + $context['recipients']['exclude_members'][] = $row['identifier']; + else + $context['recipients']['members'][] = $row['identifier']; + } + $smcFunc['db_free_result']($request); + } + + // For progress bar! + $context['total_emails'] = count($context['recipients']['emails']); + $request = $smcFunc['db_query']('', ' + SELECT MAX(id_member) + FROM {db_prefix}members', + array( + ) + ); + list ($context['max_id_member']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Clean up the arrays. + $context['recipients']['members'] = array_unique($context['recipients']['members']); + $context['recipients']['exclude_members'] = array_unique($context['recipients']['exclude_members']); + + // Setup the template! + $context['page_title'] = $txt['admin_newsletters']; + $context['sub_template'] = 'email_members_compose'; + + $context['default_subject'] = htmlspecialchars($context['forum_name'] . ': ' . $txt['subject']); + $context['default_message'] = htmlspecialchars($txt['message'] . "\n\n" . $txt['regards_team'] . "\n\n" . '{$board_url}'); +} + +// Send out the mailing! +function SendMailing($clean_only = false) +{ + global $txt, $sourcedir, $context, $smcFunc; + global $scripturl, $modSettings, $user_info; + + // How many to send at once? Quantity depends on whether we are queueing or not. + $num_at_once = empty($modSettings['mail_queue']) ? 60 : 1000; + + // If by PM's I suggest we half the above number. + if (!empty($_POST['send_pm'])) + $num_at_once /= 2; + + checkSession(); + + // Where are we actually to? + $context['start'] = isset($_REQUEST['start']) ? $_REQUEST['start'] : 0; + $context['email_force'] = !empty($_POST['email_force']) ? 1 : 0; + $context['send_pm'] = !empty($_POST['send_pm']) ? 1 : 0; + $context['total_emails'] = !empty($_POST['total_emails']) ? (int) $_POST['total_emails'] : 0; + $context['max_id_member'] = !empty($_POST['max_id_member']) ? (int) $_POST['max_id_member'] : 0; + $context['send_html'] = !empty($_POST['send_html']) ? '1' : '0'; + $context['parse_html'] = !empty($_POST['parse_html']) ? '1' : '0'; + + // Create our main context. + $context['recipients'] = array( + 'groups' => array(), + 'exclude_groups' => array(), + 'members' => array(), + 'exclude_members' => array(), + 'emails' => array(), + ); + + // Have we any excluded members? + if (!empty($_POST['exclude_members'])) + { + $members = explode(',', $_POST['exclude_members']); + foreach ($members as $member) + if ($member >= $context['start']) + $context['recipients']['exclude_members'][] = (int) $member; + } + + // What about members we *must* do? + if (!empty($_POST['members'])) + { + $members = explode(',', $_POST['members']); + foreach ($members as $member) + if ($member >= $context['start']) + $context['recipients']['members'][] = (int) $member; + } + // Cleaning groups is simple - although deal with both checkbox and commas. + if (!empty($_POST['groups'])) + { + if (is_array($_POST['groups'])) + { + foreach ($_POST['groups'] as $group => $dummy) + $context['recipients']['groups'][] = (int) $group; + } + else + { + $groups = explode(',', $_POST['groups']); + foreach ($groups as $group) + $context['recipients']['groups'][] = (int) $group; + } + } + // Same for excluded groups + if (!empty($_POST['exclude_groups'])) + { + if (is_array($_POST['exclude_groups'])) + { + foreach ($_POST['exclude_groups'] as $group => $dummy) + $context['recipients']['exclude_groups'][] = (int) $group; + } + else + { + $groups = explode(',', $_POST['exclude_groups']); + foreach ($groups as $group) + $context['recipients']['exclude_groups'][] = (int) $group; + } + } + // Finally - emails! + if (!empty($_POST['emails'])) + { + $addressed = array_unique(explode(';', strtr($_POST['emails'], array("\n" => ';', "\r" => ';', ',' => ';')))); + foreach ($addressed as $curmem) + { + $curmem = trim($curmem); + if ($curmem != '' && preg_match('~^[0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $curmem) !== 0) + $context['recipients']['emails'][$curmem] = $curmem; + } + } + + // If we're only cleaning drop out here. + if ($clean_only) + return; + + require_once($sourcedir . '/Subs-Post.php'); + + // Save the message and its subject in $context + $context['subject'] = htmlspecialchars($_POST['subject']); + $context['message'] = htmlspecialchars($_POST['message']); + + // Prepare the message for sending it as HTML + if (!$context['send_pm'] && !empty($_POST['send_html'])) + { + // Prepare the message for HTML. + if (!empty($_POST['parse_html'])) + $_POST['message'] = str_replace(array("\n", ' '), array('
' . "\n", '  '), $_POST['message']); + + // This is here to prevent spam filters from tagging this as spam. + if (preg_match('~\' . "\n" . '' . $_POST['message'] . ''; + else + $_POST['message'] = '' . $_POST['message'] . ''; + } + } + + // Use the default time format. + $user_info['time_format'] = $modSettings['time_format']; + + $variables = array( + '{$board_url}', + '{$current_time}', + '{$latest_member.link}', + '{$latest_member.id}', + '{$latest_member.name}' + ); + + // We might need this in a bit + $cleanLatestMember = empty($_POST['send_html']) || $context['send_pm'] ? un_htmlspecialchars($modSettings['latestRealName']) : $modSettings['latestRealName']; + + // Replace in all the standard things. + $_POST['message'] = str_replace($variables, + array( + !empty($_POST['send_html']) ? '' . $scripturl . '' : $scripturl, + timeformat(forum_time(), false), + !empty($_POST['send_html']) ? '' . $cleanLatestMember . '' : ($context['send_pm'] ? '[url=' . $scripturl . '?action=profile;u=' . $modSettings['latestMember'] . ']' . $cleanLatestMember . '[/url]' : $cleanLatestMember), + $modSettings['latestMember'], + $cleanLatestMember + ), $_POST['message']); + $_POST['subject'] = str_replace($variables, + array( + $scripturl, + timeformat(forum_time(), false), + $modSettings['latestRealName'], + $modSettings['latestMember'], + $modSettings['latestRealName'] + ), $_POST['subject']); + + $from_member = array( + '{$member.email}', + '{$member.link}', + '{$member.id}', + '{$member.name}' + ); + + // If we still have emails, do them first! + $i = 0; + foreach ($context['recipients']['emails'] as $k => $email) + { + // Done as many as we can? + if ($i >= $num_at_once) + break; + + // Don't sent it twice! + unset($context['recipients']['emails'][$k]); + + // Dammit - can't PM emails! + if ($context['send_pm']) + continue; + + $to_member = array( + $email, + !empty($_POST['send_html']) ? '' . $email . '' : $email, + '??', + $email + ); + + sendmail($email, str_replace($from_member, $to_member, $_POST['subject']), str_replace($from_member, $to_member, $_POST['message']), null, null, !empty($_POST['send_html']), 5); + + // Done another... + $i++; + } + + // Got some more to send this batch? + $last_id_member = 0; + if ($i < $num_at_once) + { + // Need to build quite a query! + $sendQuery = '('; + $sendParams = array(); + if (!empty($context['recipients']['groups'])) + { + // Take the long route... + $queryBuild = array(); + foreach ($context['recipients']['groups'] as $group) + { + $sendParams['group_' . $group] = $group; + $queryBuild[] = 'mem.id_group = {int:group_' . $group . '}'; + if (!empty($group)) + { + $queryBuild[] = 'FIND_IN_SET({int:group_' . $group . '}, mem.additional_groups) != 0'; + $queryBuild[] = 'mem.id_post_group = {int:group_' . $group . '}'; + } + } + if (!empty($queryBuild)) + $sendQuery .= implode(' OR ', $queryBuild); + } + if (!empty($context['recipients']['members'])) + { + $sendQuery .= ($sendQuery == '(' ? '' : ' OR ') . 'mem.id_member IN ({array_int:members})'; + $sendParams['members'] = $context['recipients']['members']; + } + + $sendQuery .= ')'; + + // If we've not got a query then we must be done! + if ($sendQuery == '()') + redirectexit('action=admin'); + + // Anything to exclude? + if (!empty($context['recipients']['exclude_groups']) && in_array(0, $context['recipients']['exclude_groups'])) + $sendQuery .= ' AND mem.id_group != {int:regular_group}'; + if (!empty($context['recipients']['exclude_members'])) + { + $sendQuery .= ' AND mem.id_member NOT IN ({array_int:exclude_members})'; + $sendParams['exclude_members'] = $context['recipients']['exclude_members']; + } + + // Force them to have it? + if (empty($context['email_force'])) + $sendQuery .= ' AND mem.notify_announcements = {int:notify_announcements}'; + + // Get the smelly people - note we respect the id_member range as it gives us a quicker query. + $result = $smcFunc['db_query']('', ' + SELECT mem.id_member, mem.email_address, mem.real_name, mem.id_group, mem.additional_groups, mem.id_post_group + FROM {db_prefix}members AS mem + WHERE mem.id_member > {int:min_id_member} + AND mem.id_member < {int:max_id_member} + AND ' . $sendQuery . ' + AND mem.is_activated = {int:is_activated} + ORDER BY mem.id_member ASC + LIMIT {int:atonce}', + array_merge($sendParams, array( + 'min_id_member' => $context['start'], + 'max_id_member' => $context['start'] + $num_at_once - $i, + 'atonce' => $num_at_once - $i, + 'regular_group' => 0, + 'notify_announcements' => 1, + 'is_activated' => 1, + )) + ); + + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $last_id_member = $row['id_member']; + + // What groups are we looking at here? + if (empty($row['additional_groups'])) + $groups = array($row['id_group'], $row['id_post_group']); + else + $groups = array_merge( + array($row['id_group'], $row['id_post_group']), + explode(',', $row['additional_groups']) + ); + + // Excluded groups? + if (array_intersect($groups, $context['recipients']['exclude_groups'])) + continue; + + // We might need this + $cleanMemberName = empty($_POST['send_html']) || $context['send_pm'] ? un_htmlspecialchars($row['real_name']) : $row['real_name']; + + // Replace the member-dependant variables + $message = str_replace($from_member, + array( + $row['email_address'], + !empty($_POST['send_html']) ? '' . $cleanMemberName . '' : ($context['send_pm'] ? '[url=' . $scripturl . '?action=profile;u=' . $row['id_member'] . ']' . $cleanMemberName . '[/url]' : $cleanMemberName), + $row['id_member'], + $cleanMemberName, + ), $_POST['message']); + + $subject = str_replace($from_member, + array( + $row['email_address'], + $row['real_name'], + $row['id_member'], + $row['real_name'], + ), $_POST['subject']); + + // Send the actual email - or a PM! + if (!$context['send_pm']) + sendmail($row['email_address'], $subject, $message, null, null, !empty($_POST['send_html']), 5); + else + sendpm(array('to' => array($row['id_member']), 'bcc' => array()), $subject, $message); + } + $smcFunc['db_free_result']($result); + } + + // If used our batch assume we still have a member. + if ($i >= $num_at_once) + $last_id_member = $context['start']; + // Or we didn't have one in range? + elseif (empty($last_id_member) && $context['start'] + $num_at_once < $context['max_id_member']) + $last_id_member = $context['start'] + $num_at_once; + // If we have no id_member then we're done. + elseif (empty($last_id_member) && empty($context['recipients']['emails'])) + { + // Log this into the admin log. + logAction('newsletter', array(), 'admin'); + + redirectexit('action=admin'); + } + + $context['start'] = $last_id_member; + + // Working out progress is a black art of sorts. + $percentEmails = $context['total_emails'] == 0 ? 0 : ((count($context['recipients']['emails']) / $context['total_emails']) * ($context['total_emails'] / ($context['total_emails'] + $context['max_id_member']))); + $percentMembers = ($context['start'] / $context['max_id_member']) * ($context['max_id_member'] / ($context['total_emails'] + $context['max_id_member'])); + $context['percentage_done'] = round(($percentEmails + $percentMembers) * 100, 2); + + $context['page_title'] = $txt['admin_newsletters']; + $context['sub_template'] = 'email_members_send'; +} + +function ModifyNewsSettings($return_config = false) +{ + global $context, $sourcedir, $modSettings, $txt, $scripturl; + + $config_vars = array( + array('title', 'settings'), + // Inline permissions. + array('permissions', 'edit_news', 'help' => ''), + array('permissions', 'send_mail'), + '', + // Just the remaining settings. + array('check', 'xmlnews_enable', 'onclick' => 'document.getElementById(\'xmlnews_maxlen\').disabled = !this.checked;'), + array('text', 'xmlnews_maxlen', 10), + ); + + if ($return_config) + return $config_vars; + + $context['page_title'] = $txt['admin_edit_news'] . ' - ' . $txt['settings']; + $context['sub_template'] = 'show_settings'; + + // Needed for the inline permission functions, and the settings template. + require_once($sourcedir . '/ManagePermissions.php'); + require_once($sourcedir . '/ManageServer.php'); + + // Wrap it all up nice and warm... + $context['post_url'] = $scripturl . '?action=admin;area=news;save;sa=settings'; + $context['permissions_excluded'] = array(-1); + + // Add some javascript at the bottom... + $context['settings_insert_below'] = ' + '; + + // Saving the settings? + if (isset($_GET['save'])) + { + checkSession(); + + saveDBSettings($config_vars); + redirectexit('action=admin;area=news;sa=settings'); + } + + prepareDBSettingContext($config_vars); +} + +?> \ No newline at end of file diff --git a/Sources/ManagePaid.php b/Sources/ManagePaid.php new file mode 100644 index 0000000..9cdfbb4 --- /dev/null +++ b/Sources/ManagePaid.php @@ -0,0 +1,1778 @@ + array('ModifySubscription', 'admin_forum'), + 'modifyuser' => array('ModifyUserSubscription', 'admin_forum'), + 'settings' => array('ModifySubscriptionSettings', 'admin_forum'), + 'view' => array('ViewSubscriptions', 'admin_forum'), + 'viewsub' => array('ViewSubscribedUsers', 'admin_forum'), + ); + + // Default the sub-action to 'view subscriptions', but only if they have already set things up.. + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : (!empty($modSettings['paid_currency_symbol']) ? 'view' : 'settings'); + + // Make sure you can do this. + isAllowedTo($subActions[$_REQUEST['sa']][1]); + + $context['page_title'] = $txt['paid_subscriptions']; + + // Tabs for browsing the different subscription functions. + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['paid_subscriptions'], + 'help' => '', + 'description' => $txt['paid_subscriptions_desc'], + 'tabs' => array( + 'view' => array( + 'description' => $txt['paid_subs_view_desc'], + ), + 'settings' => array( + 'description' => $txt['paid_subs_settings_desc'], + ), + ), + ); + + // Call the right function for this sub-acton. + $subActions[$_REQUEST['sa']][0](); +} + +// Modify which payment methods are to be used. +function ModifySubscriptionSettings($return_config = false) +{ + global $context, $txt, $modSettings, $sourcedir, $smcFunc, $scripturl; + + // If the currency is set to something different then we need to set it to other for this to work and set it back shortly. + $modSettings['paid_currency'] = !empty($modSettings['paid_currency_code']) ? $modSettings['paid_currency_code'] : ''; + if (!empty($modSettings['paid_currency_code']) && !in_array($modSettings['paid_currency_code'], array('usd', 'eur', 'gbp'))) + $modSettings['paid_currency'] = 'other'; + + // These are all the default settings. + $config_vars = array( + array('select', 'paid_email', array(0 => $txt['paid_email_no'], 1 => $txt['paid_email_error'], 2 => $txt['paid_email_all']), 'subtext' => $txt['paid_email_desc']), + array('text', 'paid_email_to', 'subtext' => $txt['paid_email_to_desc'], 'size' => 60), + '', + 'dummy_currency' => array('select', 'paid_currency', array('usd' => $txt['usd'], 'eur' => $txt['eur'], 'gbp' => $txt['gbp'], 'other' => $txt['other']), 'javascript' => 'onchange="toggleOther();"'), + array('text', 'paid_currency_code', 'subtext' => $txt['paid_currency_code_desc'], 'size' => 5, 'force_div_id' => 'custom_currency_code_div'), + array('text', 'paid_currency_symbol', 'subtext' => $txt['paid_currency_symbol_desc'], 'size' => 8, 'force_div_id' => 'custom_currency_symbol_div'), + array('check', 'paidsubs_test', 'subtext' => $txt['paidsubs_test_desc'], 'onclick' => 'return document.getElementById(\'paidsubs_test\').checked ? confirm(\'' . $txt['paidsubs_test_confirm'] . '\') : true;'), + ); + + // Now load all the other gateway settings. + $gateways = loadPaymentGateways(); + foreach ($gateways as $gateway) + { + $gatewayClass = new $gateway['display_class'](); + $setting_data = $gatewayClass->getGatewaySettings(); + if (!empty($setting_data)) + { + $config_vars[] = array('title', $gatewayClass->title, 'text_label' => (isset($txt['paidsubs_gateway_title_' . $gatewayClass->title]) ? $txt['paidsubs_gateway_title_' . $gatewayClass->title] : $gatewayClass->title)); + $config_vars = array_merge($config_vars, $setting_data); + } + } + + // Just searching? + if ($return_config) + return $config_vars; + + // Get the settings template fired up. + require_once($sourcedir . '/ManageServer.php'); + + // Some important context stuff + $context['page_title'] = $txt['settings']; + $context['sub_template'] = 'show_settings'; + $context['settings_message'] = '' . $txt['paid_note'] . ''; + $context[$context['admin_menu_name']]['current_subsection'] = 'settings'; + + // Get the final touches in place. + $context['post_url'] = $scripturl . '?action=admin;area=paidsubscribe;save;sa=settings'; + $context['settings_title'] = $txt['settings']; + + // We want javascript for our currency options. + $context['settings_insert_below'] = ' + '; + + // Saving the settings? + if (isset($_GET['save'])) + { + checkSession(); + + // Sort out the currency stuff. + if ($_POST['paid_currency'] != 'other') + { + $_POST['paid_currency_code'] = $_POST['paid_currency']; + $_POST['paid_currency_symbol'] = $txt[$_POST['paid_currency'] . '_symbol']; + } + unset($config_vars['dummy_currency']); + + saveDBSettings($config_vars); + + redirectexit('action=admin;area=paidsubscribe;sa=settings'); + } + + // Prepare the settings... + prepareDBSettingContext($config_vars); +} + +// Are we looking at viewing the subscriptions? +function ViewSubscriptions() +{ + global $context, $txt, $modSettings, $smcFunc, $sourcedir, $scripturl; + + // Not made the settings yet? + if (empty($modSettings['paid_currency_symbol'])) + fatal_lang_error('paid_not_set_currency', false, $scripturl . '?action=admin;area=paidsubscribe;sa=settings'); + + // Some basic stuff. + $context['page_title'] = $txt['paid_subs_view']; + loadSubscriptions(); + + $listOptions = array( + 'id' => 'subscription_list', + 'items_per_page' => 20, + 'base_href' => $scripturl . '?action=admin;area=paidsubscribe;sa=view', + 'get_items' => array( + 'function' => create_function('', ' + global $context; + return $context[\'subscriptions\']; + '), + ), + 'get_count' => array( + 'function' => create_function('', ' + global $context; + return count($context[\'subscriptions\']); + '), + ), + 'no_items_label' => $txt['paid_none_yet'], + 'columns' => array( + 'name' => array( + 'header' => array( + 'value' => $txt['paid_name'], + 'style' => 'text-align: left; width: 35%;', + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $scripturl; + + return sprintf(\'%3$s\', $scripturl, $rowData[\'id\'], $rowData[\'name\']); + '), + ), + ), + 'cost' => array( + 'header' => array( + 'value' => $txt['paid_cost'], + 'style' => 'text-align: left;', + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $context, $txt; + + return $rowData[\'flexible\'] ? \'\' . $txt[\'flexible\'] . \'\' : $rowData[\'cost\'] . \' / \' . $rowData[\'length\']; + '), + ), + ), + 'pending' => array( + 'header' => array( + 'value' => $txt['paid_pending'], + 'style' => 'width: 18%;', + ), + 'data' => array( + 'db_htmlsafe' => 'pending', + 'style' => 'text-align: center;', + ), + ), + 'finished' => array( + 'header' => array( + 'value' => $txt['paid_finished'], + ), + 'data' => array( + 'db_htmlsafe' => 'finished', + 'style' => 'text-align: center;', + ), + ), + 'total' => array( + 'header' => array( + 'value' => $txt['paid_active'], + ), + 'data' => array( + 'db_htmlsafe' => 'total', + 'style' => 'text-align: center;', + ), + ), + 'is_active' => array( + 'header' => array( + 'value' => $txt['paid_is_active'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $context, $txt; + + return \'\' . ($rowData[\'active\'] ? $txt[\'yes\'] : $txt[\'no\']) . \'\'; + '), + 'style' => 'text-align: center;', + ), + ), + 'modify' => array( + 'data' => array( + 'function' => create_function('$rowData', ' + global $context, $txt, $scripturl; + + return \'\' . $txt[\'modify\'] . \'\'; + '), + 'style' => 'text-align: center;', + ), + ), + 'delete' => array( + 'data' => array( + 'function' => create_function('$rowData', ' + global $context, $txt, $scripturl; + + return \'\' . $txt[\'delete\'] . \'\'; + '), + 'style' => 'text-align: center;', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=paidsubscribe;sa=modify', + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => ' + + ', + 'style' => 'text-align: right;', + ), + ), + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'subscription_list'; +} + +// Adding, editing and deleting subscriptions. +function ModifySubscription() +{ + global $context, $txt, $modSettings, $smcFunc; + + $context['sub_id'] = isset($_REQUEST['sid']) ? (int) $_REQUEST['sid'] : 0; + $context['action_type'] = $context['sub_id'] ? (isset($_REQUEST['delete']) ? 'delete' : 'edit') : 'add'; + + // Setup the template. + $context['sub_template'] = $context['action_type'] == 'delete' ? 'delete_subscription' : 'modify_subscription'; + $context['page_title'] = $txt['paid_' . $context['action_type'] . '_subscription']; + + // Delete it? + if (isset($_POST['delete_confirm']) && isset($_REQUEST['delete'])) + { + checkSession(); + + $smcFunc['db_query']('delete_subscription', ' + DELETE FROM {db_prefix}subscriptions + WHERE id_subscribe = {int:current_subscription}', + array( + 'current_subscription' => $context['sub_id'], + ) + ); + + redirectexit('action=admin;area=paidsubscribe;view'); + } + + // Saving? + if (isset($_POST['save'])) + { + checkSession(); + + // Some cleaning... + $isActive = isset($_POST['active']) ? 1 : 0; + $isRepeatable = isset($_POST['repeatable']) ? 1 : 0; + $allowpartial = isset($_POST['allow_partial']) ? 1 : 0; + $reminder = isset($_POST['reminder']) ? (int) $_POST['reminder'] : 0; + $emailComplete = strlen($_POST['emailcomplete']) > 10 ? trim($_POST['emailcomplete']) : ''; + + // Is this a fixed one? + if ($_POST['duration_type'] == 'fixed') + { + // Clean the span. + $span = $_POST['span_value'] . $_POST['span_unit']; + + // Sort out the cost. + $cost = array('fixed' => sprintf('%01.2f', strtr($_POST['cost'], ',', '.'))); + + // There needs to be something. + if (empty($_POST['span_value']) || empty($_POST['cost'])) + fatal_lang_error('paid_no_cost_value'); + } + // Flexible is harder but more fun ;) + else + { + $span = 'F'; + + $cost = array( + 'day' => sprintf('%01.2f', strtr($_POST['cost_day'], ',', '.')), + 'week' => sprintf('%01.2f', strtr($_POST['cost_week'], ',', '.')), + 'month' => sprintf('%01.2f', strtr($_POST['cost_month'], ',', '.')), + 'year' => sprintf('%01.2f', strtr($_POST['cost_year'], ',', '.')), + ); + + if (empty($_POST['cost_day']) && empty($_POST['cost_week']) && empty($_POST['cost_month']) && empty($_POST['cost_year'])) + fatal_lang_error('paid_all_freq_blank'); + } + $cost = serialize($cost); + + // Yep, time to do additional groups. + $addgroups = array(); + if (!empty($_POST['addgroup'])) + foreach ($_POST['addgroup'] as $id => $dummy) + $addgroups[] = (int) $id; + $addgroups = implode(',', $addgroups); + + // Is it new?! + if ($context['action_type'] == 'add') + { + $smcFunc['db_insert']('', + '{db_prefix}subscriptions', + array( + 'name' => 'string-60', 'description' => 'string-255', 'active' => 'int', 'length' => 'string-4', 'cost' => 'string', + 'id_group' => 'int', 'add_groups' => 'string-40', 'repeatable' => 'int', 'allow_partial' => 'int', 'email_complete' => 'string', + 'reminder' => 'int', + ), + array( + $_POST['name'], $_POST['desc'], $isActive, $span, $cost, + $_POST['prim_group'], $addgroups, $isRepeatable, $allowpartial, $emailComplete, + $reminder, + ), + array('id_subscribe') + ); + } + // Otherwise must be editing. + else + { + // Don't do groups if there are active members + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_subscribed + WHERE id_subscribe = {int:current_subscription} + AND status = {int:is_active}', + array( + 'current_subscription' => $context['sub_id'], + 'is_active' => 1, + ) + ); + list ($disableGroups) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $smcFunc['db_query']('substring', ' + UPDATE {db_prefix}subscriptions + SET name = SUBSTRING({string:name}, 1, 60), description = SUBSTRING({string:description}, 1, 255), active = {int:is_active}, + length = SUBSTRING({string:length}, 1, 4), cost = {string:cost}' . ($disableGroups ? '' : ', id_group = {int:id_group}, + add_groups = {string:additional_groups}') . ', repeatable = {int:repeatable}, allow_partial = {int:allow_partial}, + email_complete = {string:email_complete}, reminder = {int:reminder} + WHERE id_subscribe = {int:current_subscription}', + array( + 'is_active' => $isActive, + 'id_group' => !empty($_POST['prim_group']) ? $_POST['prim_group'] : 0, + 'repeatable' => $isRepeatable, + 'allow_partial' => $allowpartial, + 'reminder' => $reminder, + 'current_subscription' => $context['sub_id'], + 'name' => $_POST['name'], + 'description' => $_POST['desc'], + 'length' => $span, + 'cost' => $cost, + 'additional_groups' => !empty($addgroups) ? $addgroups : '', + 'email_complete' => $emailComplete, + ) + ); + } + + redirectexit('action=admin;area=paidsubscribe;view'); + } + + // Defaults. + if ($context['action_type'] == 'add') + { + $context['sub'] = array( + 'name' => '', + 'desc' => '', + 'cost' => array( + 'fixed' => 0, + ), + 'span' => array( + 'value' => '', + 'unit' => 'D', + ), + 'prim_group' => 0, + 'add_groups' => array(), + 'active' => 1, + 'repeatable' => 1, + 'allow_partial' => 0, + 'duration' => 'fixed', + 'email_complete' => '', + 'reminder' => 0, + ); + } + // Otherwise load up all the details. + else + { + $request = $smcFunc['db_query']('', ' + SELECT name, description, cost, length, id_group, add_groups, active, repeatable, allow_partial, email_complete, reminder + FROM {db_prefix}subscriptions + WHERE id_subscribe = {int:current_subscription} + LIMIT 1', + array( + 'current_subscription' => $context['sub_id'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Sort the date. + preg_match('~(\d*)(\w)~', $row['length'], $match); + if (isset($match[2])) + { + $span_value = $match[1]; + $span_unit = $match[2]; + } + else + { + $span_value = 0; + $span_unit = 'D'; + } + + // Is this a flexible one? + if ($row['length'] == 'F') + $isFlexible = true; + else + $isFlexible = false; + + $context['sub'] = array( + 'name' => $row['name'], + 'desc' => $row['description'], + 'cost' => @unserialize($row['cost']), + 'span' => array( + 'value' => $span_value, + 'unit' => $span_unit, + ), + 'prim_group' => $row['id_group'], + 'add_groups' => explode(',', $row['add_groups']), + 'active' => $row['active'], + 'repeatable' => $row['repeatable'], + 'allow_partial' => $row['allow_partial'], + 'duration' => $isFlexible ? 'flexible' : 'fixed', + 'email_complete' => htmlspecialchars($row['email_complete']), + 'reminder' => $row['reminder'], + ); + } + $smcFunc['db_free_result']($request); + + // Does this have members who are active? + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_subscribed + WHERE id_subscribe = {int:current_subscription} + AND status = {int:is_active}', + array( + 'current_subscription' => $context['sub_id'], + 'is_active' => 1, + ) + ); + list ($context['disable_groups']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + // Load up all the groups. + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_name + FROM {db_prefix}membergroups + WHERE id_group != {int:moderator_group} + AND min_posts = {int:min_posts}', + array( + 'moderator_group' => 3, + 'min_posts' => -1, + ) + ); + $context['groups'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['groups'][$row['id_group']] = $row['group_name']; + $smcFunc['db_free_result']($request); +} + +// View all the users subscribed to a particular subscription! +function ViewSubscribedUsers() +{ + global $context, $txt, $modSettings, $scripturl, $options, $smcFunc, $sourcedir; + + // Setup the template. + $context['page_title'] = $txt['viewing_users_subscribed']; + + // ID of the subscription. + $context['sub_id'] = (int) $_REQUEST['sid']; + + // Load the subscription information. + $request = $smcFunc['db_query']('', ' + SELECT id_subscribe, name, description, cost, length, id_group, add_groups, active + FROM {db_prefix}subscriptions + WHERE id_subscribe = {int:current_subscription}', + array( + 'current_subscription' => $context['sub_id'], + ) + ); + // Something wrong? + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_access', false); + // Do the subscription context. + $row = $smcFunc['db_fetch_assoc']($request); + $context['subscription'] = array( + 'id' => $row['id_subscribe'], + 'name' => $row['name'], + 'desc' => $row['description'], + 'active' => $row['active'], + ); + $smcFunc['db_free_result']($request); + + // Are we searching for people? + $search_string = isset($_POST['ssearch']) && !empty($_POST['sub_search']) ? ' AND IFNULL(mem.real_name, {string:guest}) LIKE {string:search}' : ''; + $search_vars = empty($_POST['sub_search']) ? array() : array('search' => '%' . $_POST['sub_search'] . '%', 'guest' => $txt['guest']); + + $listOptions = array( + 'id' => 'subscribed_users_list', + 'items_per_page' => 20, + 'base_href' => $scripturl . '?action=admin;area=paidsubscribe;sa=viewsub;sid=' . $context['sub_id'], + 'default_sort_col' => 'name', + 'get_items' => array( + 'function' => 'list_getSubscribedUsers', + 'params' => array( + $context['sub_id'], + $search_string, + $search_vars, + ), + ), + 'get_count' => array( + 'function' => 'list_getSubscribedUserCount', + 'params' => array( + $context['sub_id'], + $search_string, + $search_vars, + ), + ), + 'no_items_label' => $txt['no_subscribers'], + 'columns' => array( + 'name' => array( + 'header' => array( + 'value' => $txt['who_member'], + 'style' => 'text-align: left; width: 20%;', + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $context, $txt, $scripturl; + + return $rowData[\'id_member\'] == 0 ? $txt[\'guest\'] : \'\' . $rowData[\'name\'] . \'\'; + '), + ), + 'sort' => array( + 'default' => 'name', + 'reverse' => 'name DESC', + ), + ), + 'status' => array( + 'header' => array( + 'value' => $txt['paid_status'], + 'style' => 'text-align: left; width: 10%;', + ), + 'data' => array( + 'db_htmlsafe' => 'status_text', + ), + 'sort' => array( + 'default' => 'status', + 'reverse' => 'status DESC', + ), + ), + 'payments_pending' => array( + 'header' => array( + 'value' => $txt['paid_payments_pending'], + 'style' => 'text-align: left; width: 10%;', + ), + 'data' => array( + 'db_htmlsafe' => 'pending', + ), + 'sort' => array( + 'default' => 'payments_pending', + 'reverse' => 'payments_pending DESC', + ), + ), + 'start_time' => array( + 'header' => array( + 'value' => $txt['start_date'], + 'style' => 'text-align: left; width: 20%;', + ), + 'data' => array( + 'db_htmlsafe' => 'start_date', + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'start_time', + 'reverse' => 'start_time DESC', + ), + ), + 'end_time' => array( + 'header' => array( + 'value' => $txt['end_date'], + 'style' => 'text-align: left; width: 20%;', + ), + 'data' => array( + 'db_htmlsafe' => 'end_date', + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'end_time', + 'reverse' => 'end_time DESC', + ), + ), + 'modify' => array( + 'header' => array( + 'style' => 'width: 10%;', + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $context, $txt, $scripturl; + + return \'\' . $txt[\'modify\'] . \'\'; + '), + 'style' => 'text-align: center;', + ), + ), + 'delete' => array( + 'header' => array( + 'style' => 'width: 4%;', + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $context, $txt, $scripturl; + + return \'\'; + '), + 'style' => 'text-align: center;', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=paidsubscribe;sa=modifyuser;sid=' . $context['sub_id'], + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => ' +
+ +
+
+ + +
+ ', + ), + array( + 'position' => 'top_of_list', + 'value' => ' +
+

' . sprintf($txt['view_users_subscribed'], $row['name']) . '

+
+
+ + +
+ ', + ), + ), + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'subscribed_users_list'; +} + +// Returns how many people are subscribed to a paid subscription. +function list_getSubscribedUserCount($id_sub, $search_string, $search_vars = array()) +{ + global $smcFunc; + + // Get the total amount of users. + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS total_subs + FROM {db_prefix}log_subscribed AS ls + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = ls.id_member) + WHERE ls.id_subscribe = {int:current_subscription} ' . $search_string . ' + AND (ls.end_time != {int:no_end_time} OR ls.payments_pending != {int:no_pending_payments})', + array_merge($search_vars, array( + 'current_subscription' => $id_sub, + 'no_end_time' => 0, + 'no_pending_payments' => 0, + )) + ); + list ($memberCount) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $memberCount; +} + +function list_getSubscribedUsers($start, $items_per_page, $sort, $id_sub, $search_string, $search_vars = array()) +{ + global $smcFunc, $txt; + + $request = $smcFunc['db_query']('', ' + SELECT ls.id_sublog, IFNULL(mem.id_member, 0) AS id_member, IFNULL(mem.real_name, {string:guest}) AS name, ls.start_time, ls.end_time, + ls.status, ls.payments_pending + FROM {db_prefix}log_subscribed AS ls + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = ls.id_member) + WHERE ls.id_subscribe = {int:current_subscription} ' . $search_string . ' + AND (ls.end_time != {int:no_end_time} OR ls.payments_pending != {int:no_payments_pending}) + ORDER BY ' . $sort . ' + LIMIT ' . $start . ', ' . $items_per_page, + array_merge($search_vars, array( + 'current_subscription' => $id_sub, + 'no_end_time' => 0, + 'no_payments_pending' => 0, + 'guest' => $txt['guest'], + )) + ); + $subscribers = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $subscribers[] = array( + 'id' => $row['id_sublog'], + 'id_member' => $row['id_member'], + 'name' => $row['name'], + 'start_date' => timeformat($row['start_time'], false), + 'end_date' => $row['end_time'] == 0 ? 'N/A' : timeformat($row['end_time'], false), + 'pending' => $row['payments_pending'], + 'status' => $row['status'], + 'status_text' => $row['status'] == 0 ? ($row['payments_pending'] == 0 ? $txt['paid_finished'] : $txt['paid_pending']) : $txt['paid_active'], + ); + $smcFunc['db_free_result']($request); + + return $subscribers; +} + +// Edit or add a user subscription. +function ModifyUserSubscription() +{ + global $context, $txt, $modSettings, $smcFunc; + + loadSubscriptions(); + + $context['log_id'] = isset($_REQUEST['lid']) ? (int) $_REQUEST['lid'] : 0; + $context['sub_id'] = isset($_REQUEST['sid']) ? (int) $_REQUEST['sid'] : 0; + $context['action_type'] = $context['log_id'] ? 'edit' : 'add'; + + // Setup the template. + $context['sub_template'] = 'modify_user_subscription'; + $context['page_title'] = $txt[$context['action_type'] . '_subscriber']; + + // If we haven't been passed the subscription ID get it. + if ($context['log_id'] && !$context['sub_id']) + { + $request = $smcFunc['db_query']('', ' + SELECT id_subscribe + FROM {db_prefix}log_subscribed + WHERE id_sublog = {int:current_log_item}', + array( + 'current_log_item' => $context['log_id'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_access', false); + list ($context['sub_id']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + if (!isset($context['subscriptions'][$context['sub_id']])) + fatal_lang_error('no_access', false); + $context['current_subscription'] = $context['subscriptions'][$context['sub_id']]; + + // Searching? + if (isset($_POST['ssearch'])) + { + return ViewSubscribedUsers(); + } + // Saving? + elseif (isset($_REQUEST['save_sub'])) + { + checkSession(); + + // Work out the dates... + $starttime = mktime($_POST['hour'], $_POST['minute'], 0, $_POST['month'], $_POST['day'], $_POST['year']); + $endtime = mktime($_POST['hourend'], $_POST['minuteend'], 0, $_POST['monthend'], $_POST['dayend'], $_POST['yearend']); + + // Status. + $status = $_POST['status']; + + // New one? + if (empty($context['log_id'])) + { + // Find the user... + $request = $smcFunc['db_query']('', ' + SELECT id_member, id_group + FROM {db_prefix}members + WHERE real_name = {string:name} + LIMIT 1', + array( + 'name' => $_POST['name'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('error_member_not_found'); + + list ($id_member, $id_group) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Ensure the member doesn't already have a subscription! + $request = $smcFunc['db_query']('', ' + SELECT id_subscribe + FROM {db_prefix}log_subscribed + WHERE id_subscribe = {int:current_subscription} + AND id_member = {int:current_member}', + array( + 'current_subscription' => $context['sub_id'], + 'current_member' => $id_member, + ) + ); + if ($smcFunc['db_num_rows']($request) != 0) + fatal_lang_error('member_already_subscribed'); + $smcFunc['db_free_result']($request); + + // Actually put the subscription in place. + if ($status == 1) + addSubscription($context['sub_id'], $id_member, 0, $starttime, $endtime); + else + { + $smcFunc['db_insert']('', + '{db_prefix}log_subscribed', + array( + 'id_subscribe' => 'int', 'id_member' => 'int', 'old_id_group' => 'int', 'start_time' => 'int', + 'end_time' => 'int', 'status' => 'int', + ), + array( + $context['sub_id'], $id_member, $id_group, $starttime, + $endtime, $status, + ), + array('id_sublog') + ); + } + } + // Updating. + else + { + $request = $smcFunc['db_query']('', ' + SELECT id_member, status + FROM {db_prefix}log_subscribed + WHERE id_sublog = {int:current_log_item}', + array( + 'current_log_item' => $context['log_id'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_access', false); + + list ($id_member, $old_status) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Pick the right permission stuff depending on what the status is changing from/to. + if ($old_status == 1 && $status != 1) + removeSubscription($context['sub_id'], $id_member); + elseif ($status == 1 && $old_status != 1) + { + addSubscription($context['sub_id'], $id_member, 0, $starttime, $endtime); + } + else + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_subscribed + SET start_time = {int:start_time}, end_time = {int:end_time}, status = {int:status} + WHERE id_sublog = {int:current_log_item}', + array( + 'start_time' => $starttime, + 'end_time' => $endtime, + 'status' => $status, + 'current_log_item' => $context['log_id'], + ) + ); + } + } + + // Done - redirect... + redirectexit('action=admin;area=paidsubscribe;sa=viewsub;sid=' . $context['sub_id']); + } + // Deleting? + elseif (isset($_REQUEST['delete']) || isset($_REQUEST['finished'])) + { + checkSession(); + + // Do the actual deletes! + if (!empty($_REQUEST['delsub'])) + { + $toDelete = array(); + foreach ($_REQUEST['delsub'] as $id => $dummy) + $toDelete[] = (int) $id; + + $request = $smcFunc['db_query']('', ' + SELECT id_subscribe, id_member + FROM {db_prefix}log_subscribed + WHERE id_sublog IN ({array_int:subscription_list})', + array( + 'subscription_list' => $toDelete, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + removeSubscription($row['id_subscribe'], $row['id_member'], isset($_REQUEST['delete'])); + $smcFunc['db_free_result']($request); + } + redirectexit('action=admin;area=paidsubscribe;sa=viewsub;sid=' . $context['sub_id']); + } + + // Default attributes. + if ($context['action_type'] == 'add') + { + $context['sub'] = array( + 'id' => 0, + 'start' => array( + 'year' => (int) strftime('%Y', time()), + 'month' => (int) strftime('%m', time()), + 'day' => (int) strftime('%d', time()), + 'hour' => (int) strftime('%H', time()), + 'min' => (int) strftime('%M', time()) < 10 ? '0' . (int) strftime('%M', time()) : (int) strftime('%M', time()), + 'last_day' => 0, + ), + 'end' => array( + 'year' => (int) strftime('%Y', time()), + 'month' => (int) strftime('%m', time()), + 'day' => (int) strftime('%d', time()), + 'hour' => (int) strftime('%H', time()), + 'min' => (int) strftime('%M', time()) < 10 ? '0' . (int) strftime('%M', time()) : (int) strftime('%M', time()), + 'last_day' => 0, + ), + 'status' => 1, + ); + $context['sub']['start']['last_day'] = (int) strftime('%d', mktime(0, 0, 0, $context['sub']['start']['month'] == 12 ? 1 : $context['sub']['start']['month'] + 1, 0, $context['sub']['start']['month'] == 12 ? $context['sub']['start']['year'] + 1 : $context['sub']['start']['year'])); + $context['sub']['end']['last_day'] = (int) strftime('%d', mktime(0, 0, 0, $context['sub']['end']['month'] == 12 ? 1 : $context['sub']['end']['month'] + 1, 0, $context['sub']['end']['month'] == 12 ? $context['sub']['end']['year'] + 1 : $context['sub']['end']['year'])); + + if (isset($_GET['uid'])) + { + $request = $smcFunc['db_query']('', ' + SELECT real_name + FROM {db_prefix}members + WHERE id_member = {int:current_member}', + array( + 'current_member' => (int) $_GET['uid'], + ) + ); + list ($context['sub']['username']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + else + $context['sub']['username'] = ''; + } + // Otherwise load the existing info. + else + { + $request = $smcFunc['db_query']('', ' + SELECT ls.id_sublog, ls.id_subscribe, ls.id_member, start_time, end_time, status, payments_pending, pending_details, + IFNULL(mem.real_name, {string:blank_string}) AS username + FROM {db_prefix}log_subscribed AS ls + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = ls.id_member) + WHERE ls.id_sublog = {int:current_subscription_item} + LIMIT 1', + array( + 'current_subscription_item' => $context['log_id'], + 'blank_string' => '', + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_access', false); + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Any pending payments? + $context['pending_payments'] = array(); + if (!empty($row['pending_details'])) + { + $pending_details = @unserialize($row['pending_details']); + foreach ($pending_details as $id => $pending) + { + // Only this type need be displayed. + if ($pending[3] == 'payback') + { + // Work out what the options were. + $costs = @unserialize($context['current_subscription']['real_cost']); + + if ($context['current_subscription']['real_length'] == 'F') + { + foreach ($costs as $duration => $cost) + { + if ($cost != 0 && $cost == $pending[1] && $duration == $pending[2]) + $context['pending_payments'][$id] = array( + 'desc' => sprintf($modSettings['paid_currency_symbol'], $cost . '/' . $txt[$duration]), + ); + } + } + elseif ($costs['fixed'] == $pending[1]) + { + $context['pending_payments'][$id] = array( + 'desc' => sprintf($modSettings['paid_currency_symbol'], $costs['fixed']), + ); + } + } + } + + // Check if we are adding/removing any. + if (isset($_GET['pending'])) + { + foreach ($pending_details as $id => $pending) + { + // Found the one to action? + if ($_GET['pending'] == $id && $pending[3] == 'payback' && isset($context['pending_payments'][$id])) + { + // Flexible? + if (isset($_GET['accept'])) + addSubscription($context['current_subscription']['id'], $row['id_member'], $context['current_subscription']['real_length'] == 'F' ? strtoupper(substr($pending[2], 0, 1)) : 0); + unset($pending_details[$id]); + + $new_details = serialize($pending_details); + + // Update the entry. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_subscribed + SET payments_pending = payments_pending - 1, pending_details = {string:pending_details} + WHERE id_sublog = {int:current_subscription_item}', + array( + 'current_subscription_item' => $context['log_id'], + 'pending_details' => $new_details, + ) + ); + + // Reload + redirectexit('action=admin;area=paidsubscribe;sa=modifyuser;lid=' . $context['log_id']); + } + } + } + } + + $context['sub_id'] = $row['id_subscribe']; + $context['sub'] = array( + 'id' => 0, + 'start' => array( + 'year' => (int) strftime('%Y', $row['start_time']), + 'month' => (int) strftime('%m', $row['start_time']), + 'day' => (int) strftime('%d', $row['start_time']), + 'hour' => (int) strftime('%H', $row['start_time']), + 'min' => (int) strftime('%M', $row['start_time']) < 10 ? '0' . (int) strftime('%M', $row['start_time']) : (int) strftime('%M', $row['start_time']), + 'last_day' => 0, + ), + 'end' => array( + 'year' => (int) strftime('%Y', $row['end_time']), + 'month' => (int) strftime('%m', $row['end_time']), + 'day' => (int) strftime('%d', $row['end_time']), + 'hour' => (int) strftime('%H', $row['end_time']), + 'min' => (int) strftime('%M', $row['end_time']) < 10 ? '0' . (int) strftime('%M', $row['end_time']) : (int) strftime('%M', $row['end_time']), + 'last_day' => 0, + ), + 'status' => $row['status'], + 'username' => $row['username'], + ); + $context['sub']['start']['last_day'] = (int) strftime('%d', mktime(0, 0, 0, $context['sub']['start']['month'] == 12 ? 1 : $context['sub']['start']['month'] + 1, 0, $context['sub']['start']['month'] == 12 ? $context['sub']['start']['year'] + 1 : $context['sub']['start']['year'])); + $context['sub']['end']['last_day'] = (int) strftime('%d', mktime(0, 0, 0, $context['sub']['end']['month'] == 12 ? 1 : $context['sub']['end']['month'] + 1, 0, $context['sub']['end']['month'] == 12 ? $context['sub']['end']['year'] + 1 : $context['sub']['end']['year'])); + } +} + +// Re-apply subscription rules. +function reapplySubscriptions($users) +{ + global $smcFunc; + + // Make it an array. + if (!is_array($users)) + $users = array($users); + + // Get all the members current groups. + $groups = array(); + $request = $smcFunc['db_query']('', ' + SELECT id_member, id_group, additional_groups + FROM {db_prefix}members + WHERE id_member IN ({array_int:user_list})', + array( + 'user_list' => $users, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $groups[$row['id_member']] = array( + 'primary' => $row['id_group'], + 'additional' => explode(',', $row['additional_groups']), + ); + } + $smcFunc['db_free_result']($request); + + $request = $smcFunc['db_query']('', ' + SELECT ls.id_member, ls.old_id_group, s.id_group, s.add_groups + FROM {db_prefix}log_subscribed AS ls + INNER JOIN {db_prefix}subscriptions AS s ON (s.id_subscribe = ls.id_subscribe) + WHERE ls.id_member IN ({array_int:user_list}) + AND ls.end_time > {int:current_time}', + array( + 'user_list' => $users, + 'current_time' => time(), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Specific primary group? + if ($row['id_group'] != 0) + { + // If this is changing - add the old one to the additional groups so it's not lost. + if ($row['id_group'] != $groups[$row['id_member']]['primary']) + $groups[$row['id_member']]['additional'][] = $groups[$row['id_member']]['primary']; + $groups[$row['id_member']]['primary'] = $row['id_group']; + } + + // Additional groups. + if (!empty($row['add_groups'])) + $groups[$row['id_member']]['additional'] = array_merge($groups[$row['id_member']]['additional'], explode(',', $row['add_groups'])); + } + $smcFunc['db_free_result']($request); + + // Update all the members. + foreach ($groups as $id => $group) + { + $group['additional'] = array_unique($group['additional']); + foreach ($group['additional'] as $key => $value) + if (empty($value)) + unset($group['additional'][$key]); + $addgroups = implode(',', $group['additional']); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET id_group = {int:primary_group}, additional_groups = {string:additional_groups} + WHERE id_member = {int:current_member} + LIMIT 1', + array( + 'primary_group' => $group['primary'], + 'current_member' => $id, + 'additional_groups' => $addgroups, + ) + ); + } +} + +// Add or extend a subscription of a user. +function addSubscription($id_subscribe, $id_member, $renewal = 0, $forceStartTime = 0, $forceEndTime = 0) +{ + global $context, $smcFunc; + + // Take the easy way out... + loadSubscriptions(); + + // Exists, yes? + if (!isset($context['subscriptions'][$id_subscribe])) + return; + + $curSub = $context['subscriptions'][$id_subscribe]; + + // Grab the duration. + $duration = $curSub['num_length']; + + // If this is a renewal change the duration to be correct. + if (!empty($renewal)) + { + switch ($renewal) + { + case 'D': + $duration = 86400; + break; + case 'W': + $duration = 604800; + break; + case 'M': + $duration = 2629743; + break; + case 'Y': + $duration = 31556926; + break; + default: + break; + } + } + + // Firstly, see whether it exists, and is active. If so then this is meerly an extension. + $request = $smcFunc['db_query']('', ' + SELECT id_sublog, end_time, start_time + FROM {db_prefix}log_subscribed + WHERE id_subscribe = {int:current_subscription} + AND id_member = {int:current_member} + AND status = {int:is_active}', + array( + 'current_subscription' => $id_subscribe, + 'current_member' => $id_member, + 'is_active' => 1, + ) + ); + if ($smcFunc['db_num_rows']($request) != 0) + { + list ($id_sublog, $endtime, $starttime) = $smcFunc['db_fetch_row']($request); + + // If this has already expired but is active, extension means the period from now. + if ($endtime < time()) + $endtime = time(); + if ($starttime == 0) + $starttime = time(); + + // Work out the new expiry date. + $endtime += $duration; + + if ($forceEndTime != 0) + $endtime = $forceEndTime; + + // As everything else should be good, just update! + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_subscribed + SET end_time = {int:end_time}, start_time = {int:start_time} + WHERE id_sublog = {int:current_subscription_item}', + array( + 'end_time' => $endtime, + 'start_time' => $starttime, + 'current_subscription_item' => $id_sublog, + ) + ); + + return; + } + $smcFunc['db_free_result']($request); + + // If we're here, that means we don't have an active subscription - that means we need to do some work! + $request = $smcFunc['db_query']('', ' + SELECT m.id_group, m.additional_groups + FROM {db_prefix}members AS m + WHERE m.id_member = {int:current_member}', + array( + 'current_member' => $id_member, + ) + ); + // Just in case the member doesn't exist. + if ($smcFunc['db_num_rows']($request) == 0) + return; + + list ($old_id_group, $additional_groups) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Prepare additional groups. + $newAddGroups = explode(',', $curSub['add_groups']); + $curAddGroups = explode(',', $additional_groups); + + $newAddGroups = array_merge($newAddGroups, $curAddGroups); + + // Simple, simple, simple - hopefully... id_group first. + if ($curSub['prim_group'] != 0) + { + $id_group = $curSub['prim_group']; + + // Ensure their old privileges are maintained. + if ($old_id_group != 0) + $newAddGroups[] = $old_id_group; + } + else + $id_group = $old_id_group; + + // Yep, make sure it's unique, and no empties. + foreach ($newAddGroups as $k => $v) + if (empty($v)) + unset($newAddGroups[$k]); + $newAddGroups = array_unique($newAddGroups); + $newAddGroups = implode(',', $newAddGroups); + + // Store the new settings. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET id_group = {int:primary_group}, additional_groups = {string:additional_groups} + WHERE id_member = {int:current_member}', + array( + 'primary_group' => $id_group, + 'current_member' => $id_member, + 'additional_groups' => $newAddGroups, + ) + ); + + // Now log the subscription - maybe we have a dorment subscription we can restore? + $request = $smcFunc['db_query']('', ' + SELECT id_sublog, end_time, start_time + FROM {db_prefix}log_subscribed + WHERE id_subscribe = {int:current_subscription} + AND id_member = {int:current_member}', + array( + 'current_subscription' => $id_subscribe, + 'current_member' => $id_member, + ) + ); + //!!! Don't really need to do this twice... + if ($smcFunc['db_num_rows']($request) != 0) + { + list ($id_sublog, $endtime, $starttime) = $smcFunc['db_fetch_row']($request); + + // If this has already expired but is active, extension means the period from now. + if ($endtime < time()) + $endtime = time(); + if ($starttime == 0) + $starttime = time(); + + // Work out the new expiry date. + $endtime += $duration; + + if ($forceEndTime != 0) + $endtime = $forceEndTime; + + // As everything else should be good, just update! + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_subscribed + SET start_time = {int:start_time}, end_time = {int:end_time}, old_id_group = {int:old_id_group}, status = {int:is_active}, + reminder_sent = {int:no_reminder_sent} + WHERE id_sublog = {int:current_subscription_item}', + array( + 'start_time' => $starttime, + 'end_time' => $endtime, + 'old_id_group' => $old_id_group, + 'is_active' => 1, + 'no_reminder_sent' => 0, + 'current_subscription_item' => $id_sublog, + ) + ); + + return; + } + $smcFunc['db_free_result']($request); + + // Otherwise a very simple insert. + $endtime = time() + $duration; + if ($forceEndTime != 0) + $endtime = $forceEndTime; + + if ($forceStartTime == 0) + $starttime = time(); + else + $starttime = $forceStartTime; + + $smcFunc['db_insert']('', + '{db_prefix}log_subscribed', + array( + 'id_subscribe' => 'int', 'id_member' => 'int', 'old_id_group' => 'int', 'start_time' => 'int', + 'end_time' => 'int', 'status' => 'int', 'pending_details' => 'string', + ), + array( + $id_subscribe, $id_member, $old_id_group, $starttime, + $endtime, 1, '', + ), + array('id_sublog') + ); +} + +// Removes a subscription from a user, as in removes the groups. +function removeSubscription($id_subscribe, $id_member, $delete = false) +{ + global $context, $smcFunc; + + loadSubscriptions(); + + // Load the user core bits. + $request = $smcFunc['db_query']('', ' + SELECT m.id_group, m.additional_groups + FROM {db_prefix}members AS m + WHERE m.id_member = {int:current_member}', + array( + 'current_member' => $id_member, + ) + ); + + // Just in case of errors. + if ($smcFunc['db_num_rows']($request) == 0) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_subscribed + WHERE id_member = {int:current_member}', + array( + 'current_member' => $id_member, + ) + ); + return; + } + list ($id_group, $additional_groups) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Get all of the subscriptions for this user that are active - it will be necessary! + $request = $smcFunc['db_query']('', ' + SELECT id_subscribe, old_id_group + FROM {db_prefix}log_subscribed + WHERE id_member = {int:current_member} + AND status = {int:is_active}', + array( + 'current_member' => $id_member, + 'is_active' => 1, + ) + ); + + // These variables will be handy, honest ;) + $removals = array(); + $allowed = array(); + $old_id_group = 0; + $new_id_group = -1; + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!isset($context['subscriptions'][$row['id_subscribe']])) + continue; + + // The one we're removing? + if ($row['id_subscribe'] == $id_subscribe) + { + $removals = explode(',', $context['subscriptions'][$row['id_subscribe']]['add_groups']); + if ($context['subscriptions'][$row['id_subscribe']]['prim_group'] != 0) + $removals[] = $context['subscriptions'][$row['id_subscribe']]['prim_group']; + $old_id_group = $row['old_id_group']; + } + // Otherwise things we allow. + else + { + $allowed = array_merge($allowed, explode(',', $context['subscriptions'][$row['id_subscribe']]['add_groups'])); + if ($context['subscriptions'][$row['id_subscribe']]['prim_group'] != 0) + { + $allowed[] = $context['subscriptions'][$row['id_subscribe']]['prim_group']; + $new_id_group = $context['subscriptions'][$row['id_subscribe']]['prim_group']; + } + } + } + $smcFunc['db_free_result']($request); + + // Now, for everything we are removing check they defintely are not allowed it. + $existingGroups = explode(',', $additional_groups); + foreach ($existingGroups as $key => $group) + if (empty($group) || (in_array($group, $removals) && !in_array($group, $allowed))) + unset($existingGroups[$key]); + + // Finally, do something with the current primary group. + if (in_array($id_group, $removals)) + { + // If this primary group is actually allowed keep it. + if (in_array($id_group, $allowed)) + $existingGroups[] = $id_group; + + // Either way, change the id_group back. + if ($new_id_group < 1) + { + // If we revert to the old id-group we need to ensure it wasn't from a subscription. + foreach ($context['subscriptions'] as $id => $group) + // It was? Make them a regular member then! + if ($group['prim_group'] == $old_id_group) + $old_id_group = 0; + + $id_group = $old_id_group; + } + else + $id_group = $new_id_group; + } + + // Crazy stuff, we seem to have our groups fixed, just make them unique + $existingGroups = array_unique($existingGroups); + $existingGroups = implode(',', $existingGroups); + + // Update the member + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET id_group = {int:primary_group}, additional_groups = {string:existing_groups} + WHERE id_member = {int:current_member}', + array( + 'primary_group' => $id_group, + 'current_member' => $id_member, + 'existing_groups' => $existingGroups, + ) + ); + + // Disable the subscription. + if (!$delete) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_subscribed + SET status = {int:not_active} + WHERE id_member = {int:current_member} + AND id_subscribe = {int:current_subscription}', + array( + 'not_active' => 0, + 'current_member' => $id_member, + 'current_subscription' => $id_subscribe, + ) + ); + // Otherwise delete it! + else + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_subscribed + WHERE id_member = {int:current_member} + AND id_subscribe = {int:current_subscription}', + array( + 'current_member' => $id_member, + 'current_subscription' => $id_subscribe, + ) + ); +} + +// This just kind of caches all the subscription data. +function loadSubscriptions() +{ + global $context, $txt, $modSettings, $smcFunc; + + if (!empty($context['subscriptions'])) + return; + + // Make sure this is loaded, just in case. + loadLanguage('ManagePaid'); + + $request = $smcFunc['db_query']('', ' + SELECT id_subscribe, name, description, cost, length, id_group, add_groups, active, repeatable + FROM {db_prefix}subscriptions', + array( + ) + ); + $context['subscriptions'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Pick a cost. + $costs = @unserialize($row['cost']); + + if ($row['length'] != 'F' && !empty($modSettings['paid_currency_symbol']) && !empty($costs['fixed'])) + $cost = sprintf($modSettings['paid_currency_symbol'], $costs['fixed']); + else + $cost = '???'; + + // Do the span. + preg_match('~(\d*)(\w)~', $row['length'], $match); + if (isset($match[2])) + { + $num_length = $match[1]; + $length = $match[1] . ' '; + switch ($match[2]) + { + case 'D': + $length .= $txt['paid_mod_span_days']; + $num_length *= 86400; + break; + case 'W': + $length .= $txt['paid_mod_span_weeks']; + $num_length *= 604800; + break; + case 'M': + $length .= $txt['paid_mod_span_months']; + $num_length *= 2629743; + break; + case 'Y': + $length .= $txt['paid_mod_span_years']; + $num_length *= 31556926; + break; + } + } + else + $length = '??'; + + $context['subscriptions'][$row['id_subscribe']] = array( + 'id' => $row['id_subscribe'], + 'name' => $row['name'], + 'desc' => $row['description'], + 'cost' => $cost, + 'real_cost' => $row['cost'], + 'length' => $length, + 'num_length' => $num_length, + 'real_length' => $row['length'], + 'pending' => 0, + 'finished' => 0, + 'total' => 0, + 'active' => $row['active'], + 'prim_group' => $row['id_group'], + 'add_groups' => $row['add_groups'], + 'flexible' => $row['length'] == 'F' ? true : false, + 'repeatable' => $row['repeatable'], + ); + } + $smcFunc['db_free_result']($request); + + // Do the counts. + $request = $smcFunc['db_query']('', ' + SELECT COUNT(id_sublog) AS member_count, id_subscribe, status + FROM {db_prefix}log_subscribed + GROUP BY id_subscribe, status', + array( + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $ind = $row['status'] == 0 ? 'finished' : 'total'; + + if (isset($context['subscriptions'][$row['id_subscribe']])) + $context['subscriptions'][$row['id_subscribe']][$ind] = $row['member_count']; + } + $smcFunc['db_free_result']($request); + + // How many payments are we waiting on? + $request = $smcFunc['db_query']('', ' + SELECT SUM(payments_pending) AS total_pending, id_subscribe + FROM {db_prefix}log_subscribed + GROUP BY id_subscribe', + array( + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (isset($context['subscriptions'][$row['id_subscribe']])) + $context['subscriptions'][$row['id_subscribe']]['pending'] = $row['total_pending']; + } + $smcFunc['db_free_result']($request); +} + +// Load all the payment gateways. +function loadPaymentGateways() +{ + global $sourcedir; + + $gateways = array(); + if ($dh = opendir($sourcedir)) + { + while (($file = readdir($dh)) !== false) + { + if (is_file($sourcedir .'/'. $file) && preg_match('~^Subscriptions-([A-Za-z\d]+)\.php$~', $file, $matches)) + { + // Check this is definitely a valid gateway! + $fp = fopen($sourcedir . '/' . $file, 'rb'); + $header = fread($fp, 4096); + fclose($fp); + + if (strpos($header, '// SMF Payment Gateway: ' . strtolower($matches[1])) !== false) + { + loadClassFile($file); + + $gateways[] = array( + 'filename' => $file, + 'code' => strtolower($matches[1]), + // Don't need anything snazier than this yet. + 'valid_version' => class_exists(strtolower($matches[1]) . '_payment') && class_exists(strtolower($matches[1]) . '_display'), + 'payment_class' => strtolower($matches[1]) . '_payment', + 'display_class' => strtolower($matches[1]) . '_display', + ); + } + } + } + } + closedir($dh); + + return $gateways; +} + +?> \ No newline at end of file diff --git a/Sources/ManagePermissions.php b/Sources/ManagePermissions.php new file mode 100644 index 0000000..2385775 --- /dev/null +++ b/Sources/ManagePermissions.php @@ -0,0 +1,2371 @@ + array('function_to_call', 'permission_needed'), + $subActions = array( + 'board' => array('PermissionByBoard', 'manage_permissions'), + 'index' => array('PermissionIndex', 'manage_permissions'), + 'modify' => array('ModifyMembergroup', 'manage_permissions'), + 'modify2' => array('ModifyMembergroup2', 'manage_permissions'), + 'quick' => array('SetQuickGroups', 'manage_permissions'), + 'quickboard' => array('SetQuickBoards', 'manage_permissions'), + 'postmod' => array('ModifyPostModeration', 'manage_permissions'), + 'profiles' => array('EditPermissionProfiles', 'manage_permissions'), + 'settings' => array('GeneralPermissionSettings', 'admin_forum'), + ); + + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : (allowedTo('manage_permissions') ? 'index' : 'settings'); + isAllowedTo($subActions[$_REQUEST['sa']][1]); + + // Create the tabs for the template. + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['permissions_title'], + 'help' => 'permissions', + 'description' => '', + 'tabs' => array( + 'index' => array( + 'description' => $txt['permissions_groups'], + ), + 'board' => array( + 'description' => $txt['permission_by_board_desc'], + ), + 'profiles' => array( + 'description' => $txt['permissions_profiles_desc'], + ), + 'postmod' => array( + 'description' => $txt['permissions_post_moderation_desc'], + ), + 'settings' => array( + 'description' => $txt['permission_settings_desc'], + ), + ), + ); + + $subActions[$_REQUEST['sa']][0](); +} + +function PermissionIndex() +{ + global $txt, $scripturl, $context, $settings, $modSettings, $smcFunc; + + $context['page_title'] = $txt['permissions_title']; + + // Load all the permissions. We'll need them in the template. + loadAllPermissions(); + + // Also load profiles, we may want to reset. + loadPermissionProfiles(); + + // Are we going to show the advanced options? + $context['show_advanced_options'] = empty($context['admin_preferences']['app']); + + // Determine the number of ungrouped members. + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}members + WHERE id_group = {int:regular_group}', + array( + 'regular_group' => 0, + ) + ); + list ($num_members) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Fill the context variable with 'Guests' and 'Regular Members'. + $context['groups'] = array( + -1 => array( + 'id' => -1, + 'name' => $txt['membergroups_guests'], + 'num_members' => $txt['membergroups_guests_na'], + 'allow_delete' => false, + 'allow_modify' => true, + 'can_search' => false, + 'href' => '', + 'link' => '', + 'is_post_group' => false, + 'color' => '', + 'stars' => '', + 'children' => array(), + 'num_permissions' => array( + 'allowed' => 0, + // Can't deny guest permissions! + 'denied' => '(' . $txt['permissions_none'] . ')' + ), + 'access' => false + ), + 0 => array( + 'id' => 0, + 'name' => $txt['membergroups_members'], + 'num_members' => $num_members, + 'allow_delete' => false, + 'allow_modify' => true, + 'can_search' => false, + 'href' => $scripturl . '?action=moderate;area=viewgroups;sa=members;group=0', + 'is_post_group' => false, + 'color' => '', + 'stars' => '', + 'children' => array(), + 'num_permissions' => array( + 'allowed' => 0, + 'denied' => 0 + ), + 'access' => false + ), + ); + + $postGroups = array(); + $normalGroups = array(); + + // Query the database defined membergroups. + $query = $smcFunc['db_query']('', ' + SELECT id_group, id_parent, group_name, min_posts, online_color, stars + FROM {db_prefix}membergroups' . (empty($modSettings['permission_enable_postgroups']) ? ' + WHERE min_posts = {int:min_posts}' : '') . ' + ORDER BY id_parent = {int:not_inherited} DESC, min_posts, CASE WHEN id_group < {int:newbie_group} THEN id_group ELSE 4 END, group_name', + array( + 'min_posts' => -1, + 'not_inherited' => -2, + 'newbie_group' => 4, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($query)) + { + // If it's inherited, just add it as a child. + if ($row['id_parent'] != -2) + { + if (isset($context['groups'][$row['id_parent']])) + $context['groups'][$row['id_parent']]['children'][$row['id_group']] = $row['group_name']; + continue; + } + + $row['stars'] = explode('#', $row['stars']); + $context['groups'][$row['id_group']] = array( + 'id' => $row['id_group'], + 'name' => $row['group_name'], + 'num_members' => $row['id_group'] != 3 ? 0 : $txt['membergroups_guests_na'], + 'allow_delete' => $row['id_group'] > 4, + 'allow_modify' => $row['id_group'] > 1, + 'can_search' => $row['id_group'] != 3, + 'href' => $scripturl . '?action=moderate;area=viewgroups;sa=members;group=' . $row['id_group'], + 'is_post_group' => $row['min_posts'] != -1, + 'color' => empty($row['online_color']) ? '' : $row['online_color'], + 'stars' => !empty($row['stars'][0]) && !empty($row['stars'][1]) ? str_repeat('*', $row['stars'][0]) : '', + 'children' => array(), + 'num_permissions' => array( + 'allowed' => $row['id_group'] == 1 ? '(' . $txt['permissions_all'] . ')' : 0, + 'denied' => $row['id_group'] == 1 ? '(' . $txt['permissions_none'] . ')' : 0 + ), + 'access' => false, + ); + + if ($row['min_posts'] == -1) + $normalGroups[$row['id_group']] = $row['id_group']; + else + $postGroups[$row['id_group']] = $row['id_group']; + } + $smcFunc['db_free_result']($query); + + // Get the number of members in this post group. + if (!empty($postGroups)) + { + $query = $smcFunc['db_query']('', ' + SELECT id_post_group AS id_group, COUNT(*) AS num_members + FROM {db_prefix}members + WHERE id_post_group IN ({array_int:post_group_list}) + GROUP BY id_post_group', + array( + 'post_group_list' => $postGroups, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($query)) + $context['groups'][$row['id_group']]['num_members'] += $row['num_members']; + $smcFunc['db_free_result']($query); + } + + if (!empty($normalGroups)) + { + // First, the easy one! + $query = $smcFunc['db_query']('', ' + SELECT id_group, COUNT(*) AS num_members + FROM {db_prefix}members + WHERE id_group IN ({array_int:normal_group_list}) + GROUP BY id_group', + array( + 'normal_group_list' => $normalGroups, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($query)) + $context['groups'][$row['id_group']]['num_members'] += $row['num_members']; + $smcFunc['db_free_result']($query); + + // This one is slower, but it's okay... careful not to count twice! + $query = $smcFunc['db_query']('', ' + SELECT mg.id_group, COUNT(*) AS num_members + FROM {db_prefix}membergroups AS mg + INNER JOIN {db_prefix}members AS mem ON (mem.additional_groups != {string:blank_string} + AND mem.id_group != mg.id_group + AND FIND_IN_SET(mg.id_group, mem.additional_groups) != 0) + WHERE mg.id_group IN ({array_int:normal_group_list}) + GROUP BY mg.id_group', + array( + 'normal_group_list' => $normalGroups, + 'blank_string' => '', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($query)) + $context['groups'][$row['id_group']]['num_members'] += $row['num_members']; + $smcFunc['db_free_result']($query); + } + + foreach ($context['groups'] as $id => $data) + { + if ($data['href'] != '') + $context['groups'][$id]['link'] = '' . $data['num_members'] . ''; + } + + if (empty($_REQUEST['pid'])) + { + $request = $smcFunc['db_query']('', ' + SELECT id_group, COUNT(*) AS num_permissions, add_deny + FROM {db_prefix}permissions + ' . (empty($context['hidden_permissions']) ? '' : ' WHERE permission NOT IN ({array_string:hidden_permissions})') . ' + GROUP BY id_group, add_deny', + array( + 'hidden_permissions' => !empty($context['hidden_permissions']) ? $context['hidden_permissions'] : array(), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + if (isset($context['groups'][(int) $row['id_group']]) && (!empty($row['add_deny']) || $row['id_group'] != -1)) + $context['groups'][(int) $row['id_group']]['num_permissions'][empty($row['add_deny']) ? 'denied' : 'allowed'] = $row['num_permissions']; + $smcFunc['db_free_result']($request); + + // Get the "default" profile permissions too. + $request = $smcFunc['db_query']('', ' + SELECT id_profile, id_group, COUNT(*) AS num_permissions, add_deny + FROM {db_prefix}board_permissions + WHERE id_profile = {int:default_profile} + ' . (empty($context['hidden_permissions']) ? '' : ' AND permission NOT IN ({array_string:hidden_permissions})') . ' + GROUP BY id_profile, id_group, add_deny', + array( + 'default_profile' => 1, + 'hidden_permissions' => !empty($context['hidden_permissions']) ? $context['hidden_permissions'] : array(), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (isset($context['groups'][(int) $row['id_group']]) && (!empty($row['add_deny']) || $row['id_group'] != -1)) + $context['groups'][(int) $row['id_group']]['num_permissions'][empty($row['add_deny']) ? 'denied' : 'allowed'] += $row['num_permissions']; + } + $smcFunc['db_free_result']($request); + } + else + { + $_REQUEST['pid'] = (int) $_REQUEST['pid']; + + if (!isset($context['profiles'][$_REQUEST['pid']])) + fatal_lang_error('no_access', false); + + // Change the selected tab to better reflect that this really is a board profile. + $context[$context['admin_menu_name']]['current_subsection'] = 'profiles'; + + $request = $smcFunc['db_query']('', ' + SELECT id_profile, id_group, COUNT(*) AS num_permissions, add_deny + FROM {db_prefix}board_permissions + WHERE id_profile = {int:current_profile} + GROUP BY id_profile, id_group, add_deny', + array( + 'current_profile' => $_REQUEST['pid'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (isset($context['groups'][(int) $row['id_group']]) && (!empty($row['add_deny']) || $row['id_group'] != -1)) + $context['groups'][(int) $row['id_group']]['num_permissions'][empty($row['add_deny']) ? 'denied' : 'allowed'] += $row['num_permissions']; + } + $smcFunc['db_free_result']($request); + + $context['profile'] = array( + 'id' => $_REQUEST['pid'], + 'name' => $context['profiles'][$_REQUEST['pid']]['name'], + ); + } + + // We can modify any permission set apart from the read only, reply only and no polls ones as they are redefined. + $context['can_modify'] = empty($_REQUEST['pid']) || $_REQUEST['pid'] == 1 || $_REQUEST['pid'] > 4; + + // Load the proper template. + $context['sub_template'] = 'permission_index'; +} + +function PermissionByBoard() +{ + global $context, $modSettings, $txt, $smcFunc, $sourcedir, $cat_tree, $boardList, $boards; + + $context['page_title'] = $txt['permissions_boards']; + $context['edit_all'] = isset($_GET['edit']); + + // Saving? + if (!empty($_POST['save_changes']) && !empty($_POST['boardprofile'])) + { + checkSession('request'); + + $changes = array(); + foreach ($_POST['boardprofile'] as $board => $profile) + { + $changes[(int) $profile][] = (int) $board; + } + + if (!empty($changes)) + { + foreach ($changes as $profile => $boards) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET id_profile = {int:current_profile} + WHERE id_board IN ({array_int:board_list})', + array( + 'board_list' => $boards, + 'current_profile' => $profile, + ) + ); + } + + $context['edit_all'] = false; + } + + // Load all permission profiles. + loadPermissionProfiles(); + + // Get the board tree. + require_once($sourcedir . '/Subs-Boards.php'); + + getBoardTree(); + + // Build the list of the boards. + $context['categories'] = array(); + foreach ($cat_tree as $catid => $tree) + { + $context['categories'][$catid] = array( + 'name' => &$tree['node']['name'], + 'id' => &$tree['node']['id'], + 'boards' => array() + ); + foreach ($boardList[$catid] as $boardid) + { + if (!isset($context['profiles'][$boards[$boardid]['profile']])) + $boards[$boardid]['profile'] = 1; + + $context['categories'][$catid]['boards'][$boardid] = array( + 'id' => &$boards[$boardid]['id'], + 'name' => &$boards[$boardid]['name'], + 'description' => &$boards[$boardid]['description'], + 'child_level' => &$boards[$boardid]['level'], + 'profile' => &$boards[$boardid]['profile'], + 'profile_name' => $context['profiles'][$boards[$boardid]['profile']]['name'], + ); + } + } + + $context['sub_template'] = 'by_board'; +} + +function SetQuickGroups() +{ + global $context, $smcFunc; + + checkSession(); + + loadIllegalPermissions(); + loadIllegalGuestPermissions(); + + // Make sure only one of the quick options was selected. + if ((!empty($_POST['predefined']) && ((isset($_POST['copy_from']) && $_POST['copy_from'] != 'empty') || !empty($_POST['permissions']))) || (!empty($_POST['copy_from']) && $_POST['copy_from'] != 'empty' && !empty($_POST['permissions']))) + fatal_lang_error('permissions_only_one_option', false); + + if (empty($_POST['group']) || !is_array($_POST['group'])) + $_POST['group'] = array(); + + // Only accept numeric values for selected membergroups. + foreach ($_POST['group'] as $id => $group_id) + $_POST['group'][$id] = (int) $group_id; + $_POST['group'] = array_unique($_POST['group']); + + if (empty($_REQUEST['pid'])) + $_REQUEST['pid'] = 0; + else + $_REQUEST['pid'] = (int) $_REQUEST['pid']; + + // Fix up the old global to the new default! + $bid = max(1, $_REQUEST['pid']); + + // No modifying the predefined profiles. + if ($_REQUEST['pid'] > 1 && $_REQUEST['pid'] < 5) + fatal_lang_error('no_access', false); + + // Clear out any cached authority. + updateSettings(array('settings_updated' => time())); + + // No groups where selected. + if (empty($_POST['group'])) + redirectexit('action=admin;area=permissions;pid=' . $_REQUEST['pid']); + + // Set a predefined permission profile. + if (!empty($_POST['predefined'])) + { + // Make sure it's a predefined permission set we expect. + if (!in_array($_POST['predefined'], array('restrict', 'standard', 'moderator', 'maintenance'))) + redirectexit('action=admin;area=permissions;pid=' . $_REQUEST['pid']); + + foreach ($_POST['group'] as $group_id) + { + if (!empty($_REQUEST['pid'])) + setPermissionLevel($_POST['predefined'], $group_id, $_REQUEST['pid']); + else + setPermissionLevel($_POST['predefined'], $group_id); + } + } + // Set a permission profile based on the permissions of a selected group. + elseif ($_POST['copy_from'] != 'empty') + { + // Just checking the input. + if (!is_numeric($_POST['copy_from'])) + redirectexit('action=admin;area=permissions;pid=' . $_REQUEST['pid']); + + // Make sure the group we're copying to is never included. + $_POST['group'] = array_diff($_POST['group'], array($_POST['copy_from'])); + + // No groups left? Too bad. + if (empty($_POST['group'])) + redirectexit('action=admin;area=permissions;pid=' . $_REQUEST['pid']); + + if (empty($_REQUEST['pid'])) + { + // Retrieve current permissions of group. + $request = $smcFunc['db_query']('', ' + SELECT permission, add_deny + FROM {db_prefix}permissions + WHERE id_group = {int:copy_from}', + array( + 'copy_from' => $_POST['copy_from'], + ) + ); + $target_perm = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $target_perm[$row['permission']] = $row['add_deny']; + $smcFunc['db_free_result']($request); + + $inserts = array(); + foreach ($_POST['group'] as $group_id) + foreach ($target_perm as $perm => $add_deny) + { + // No dodgy permissions please! + if (!empty($context['illegal_permissions']) && in_array($perm, $context['illegal_permissions'])) + continue; + if ($group_id == -1 && in_array($perm, $context['non_guest_permissions'])) + continue; + + if ($group_id != 1 && $group_id != 3) + $inserts[] = array($perm, $group_id, $add_deny); + } + + // Delete the previous permissions... + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}permissions + WHERE id_group IN ({array_int:group_list}) + ' . (empty($context['illegal_permissions']) ? '' : ' AND permission NOT IN ({array_string:illegal_permissions})'), + array( + 'group_list' => $_POST['group'], + 'illegal_permissions' => !empty($context['illegal_permissions']) ? $context['illegal_permissions'] : array(), + ) + ); + + if (!empty($inserts)) + { + // ..and insert the new ones. + $smcFunc['db_insert']('', + '{db_prefix}permissions', + array( + 'permission' => 'string', 'id_group' => 'int', 'add_deny' => 'int', + ), + $inserts, + array('permission', 'id_group') + ); + } + } + + // Now do the same for the board permissions. + $request = $smcFunc['db_query']('', ' + SELECT permission, add_deny + FROM {db_prefix}board_permissions + WHERE id_group = {int:copy_from} + AND id_profile = {int:current_profile}', + array( + 'copy_from' => $_POST['copy_from'], + 'current_profile' => $bid, + ) + ); + $target_perm = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $target_perm[$row['permission']] = $row['add_deny']; + $smcFunc['db_free_result']($request); + + $inserts = array(); + foreach ($_POST['group'] as $group_id) + foreach ($target_perm as $perm => $add_deny) + { + // Are these for guests? + if ($group_id == -1 && in_array($perm, $context['non_guest_permissions'])) + continue; + + $inserts[] = array($perm, $group_id, $bid, $add_deny); + } + + // Delete the previous global board permissions... + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}board_permissions + WHERE id_group IN ({array_int:current_group_list}) + AND id_profile = {int:current_profile}', + array( + 'current_group_list' => $_POST['group'], + 'current_profile' => $bid, + ) + ); + + // And insert the copied permissions. + if (!empty($inserts)) + { + // ..and insert the new ones. + $smcFunc['db_insert']('', + '{db_prefix}board_permissions', + array('permission' => 'string', 'id_group' => 'int', 'id_profile' => 'int', 'add_deny' => 'int'), + $inserts, + array('permission', 'id_group', 'id_profile') + ); + } + + // Update any children out there! + updateChildPermissions($_POST['group'], $_REQUEST['pid']); + } + // Set or unset a certain permission for the selected groups. + elseif (!empty($_POST['permissions'])) + { + // Unpack two variables that were transported. + list ($permissionType, $permission) = explode('/', $_POST['permissions']); + + // Check whether our input is within expected range. + if (!in_array($_POST['add_remove'], array('add', 'clear', 'deny')) || !in_array($permissionType, array('membergroup', 'board'))) + redirectexit('action=admin;area=permissions;pid=' . $_REQUEST['pid']); + + if ($_POST['add_remove'] == 'clear') + { + if ($permissionType == 'membergroup') + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}permissions + WHERE id_group IN ({array_int:current_group_list}) + AND permission = {string:current_permission} + ' . (empty($context['illegal_permissions']) ? '' : ' AND permission NOT IN ({array_string:illegal_permissions})'), + array( + 'current_group_list' => $_POST['group'], + 'current_permission' => $permission, + 'illegal_permissions' => !empty($context['illegal_permissions']) ? $context['illegal_permissions'] : array(), + ) + ); + else + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}board_permissions + WHERE id_group IN ({array_int:current_group_list}) + AND id_profile = {int:current_profile} + AND permission = {string:current_permission}', + array( + 'current_group_list' => $_POST['group'], + 'current_profile' => $bid, + 'current_permission' => $permission, + ) + ); + } + // Add a permission (either 'set' or 'deny'). + else + { + $add_deny = $_POST['add_remove'] == 'add' ? '1' : '0'; + $permChange = array(); + foreach ($_POST['group'] as $groupID) + { + if ($groupID == -1 && in_array($permission, $context['non_guest_permissions'])) + continue; + + if ($permissionType == 'membergroup' && $groupID != 1 && $groupID != 3 && (empty($context['illegal_permissions']) || !in_array($permission, $context['illegal_permissions']))) + $permChange[] = array($permission, $groupID, $add_deny); + elseif ($permissionType != 'membergroup') + $permChange[] = array($permission, $groupID, $bid, $add_deny); + } + + if (!empty($permChange)) + { + if ($permissionType == 'membergroup') + $smcFunc['db_insert']('replace', + '{db_prefix}permissions', + array('permission' => 'string', 'id_group' => 'int', 'add_deny' => 'int'), + $permChange, + array('permission', 'id_group') + ); + // Board permissions go into the other table. + else + $smcFunc['db_insert']('replace', + '{db_prefix}board_permissions', + array('permission' => 'string', 'id_group' => 'int', 'id_profile' => 'int', 'add_deny' => 'int'), + $permChange, + array('permission', 'id_group', 'id_profile') + ); + } + } + + // Another child update! + updateChildPermissions($_POST['group'], $_REQUEST['pid']); + } + + redirectexit('action=admin;area=permissions;pid=' . $_REQUEST['pid']); +} + +function ModifyMembergroup() +{ + global $context, $txt, $modSettings, $smcFunc, $sourcedir; + + if (!isset($_GET['group'])) + fatal_lang_error('no_access', false); + + $context['group']['id'] = (int) $_GET['group']; + + // Are they toggling the view? + if (isset($_GET['view'])) + { + $context['admin_preferences']['pv'] = $_GET['view'] == 'classic' ? 'classic' : 'simple'; + + // Update the users preferences. + require_once($sourcedir . '/Subs-Admin.php'); + updateAdminPreferences(); + } + + $context['view_type'] = !empty($context['admin_preferences']['pv']) && $context['admin_preferences']['pv'] == 'classic' ? 'classic' : 'simple'; + + // It's not likely you'd end up here with this setting disabled. + if ($_GET['group'] == 1) + redirectexit('action=admin;area=permissions'); + + loadAllPermissions($context['view_type']); + loadPermissionProfiles(); + + if ($context['group']['id'] > 0) + { + $result = $smcFunc['db_query']('', ' + SELECT group_name, id_parent + FROM {db_prefix}membergroups + WHERE id_group = {int:current_group} + LIMIT 1', + array( + 'current_group' => $context['group']['id'], + ) + ); + list ($context['group']['name'], $parent) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + // Cannot edit an inherited group! + if ($parent != -2) + fatal_lang_error('cannot_edit_permissions_inherited'); + } + elseif ($context['group']['id'] == -1) + $context['group']['name'] = $txt['membergroups_guests']; + else + $context['group']['name'] = $txt['membergroups_members']; + + $context['profile']['id'] = empty($_GET['pid']) ? 0 : (int) $_GET['pid']; + + // If this is a moderator and they are editing "no profile" then we only do boards. + if ($context['group']['id'] == 3 && empty($context['profile']['id'])) + { + // For sanity just check they have no general permissions. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}permissions + WHERE id_group = {int:moderator_group}', + array( + 'moderator_group' => 3, + ) + ); + + $context['profile']['id'] = 1; + } + + $context['permission_type'] = empty($context['profile']['id']) ? 'membergroup' : 'board'; + $context['profile']['can_modify'] = !$context['profile']['id'] || $context['profiles'][$context['profile']['id']]['can_modify']; + + // Set up things a little nicer for board related stuff... + if ($context['permission_type'] == 'board') + { + $context['profile']['name'] = $context['profiles'][$context['profile']['id']]['name']; + $context[$context['admin_menu_name']]['current_subsection'] = 'profiles'; + } + + // Fetch the current permissions. + $permissions = array( + 'membergroup' => array('allowed' => array(), 'denied' => array()), + 'board' => array('allowed' => array(), 'denied' => array()) + ); + + // General permissions? + if ($context['permission_type'] == 'membergroup') + { + $result = $smcFunc['db_query']('', ' + SELECT permission, add_deny + FROM {db_prefix}permissions + WHERE id_group = {int:current_group}', + array( + 'current_group' => $_GET['group'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + $permissions['membergroup'][empty($row['add_deny']) ? 'denied' : 'allowed'][] = $row['permission']; + $smcFunc['db_free_result']($result); + } + + // Fetch current board permissions... + $result = $smcFunc['db_query']('', ' + SELECT permission, add_deny + FROM {db_prefix}board_permissions + WHERE id_group = {int:current_group} + AND id_profile = {int:current_profile}', + array( + 'current_group' => $context['group']['id'], + 'current_profile' => $context['permission_type'] == 'membergroup' ? 1 : $context['profile']['id'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + $permissions['board'][empty($row['add_deny']) ? 'denied' : 'allowed'][] = $row['permission']; + $smcFunc['db_free_result']($result); + + // Loop through each permission and set whether it's checked. + foreach ($context['permissions'] as $permissionType => $tmp) + { + foreach ($tmp['columns'] as $position => $permissionGroups) + { + foreach ($permissionGroups as $permissionGroup => $permissionArray) + { + foreach ($permissionArray['permissions'] as $perm) + { + // Create a shortcut for the current permission. + $curPerm = &$context['permissions'][$permissionType]['columns'][$position][$permissionGroup]['permissions'][$perm['id']]; + if ($tmp['view'] == 'classic') + { + if ($perm['has_own_any']) + { + $curPerm['any']['select'] = in_array($perm['id'] . '_any', $permissions[$permissionType]['allowed']) ? 'on' : (in_array($perm['id'] . '_any', $permissions[$permissionType]['denied']) ? 'denied' : 'off'); + $curPerm['own']['select'] = in_array($perm['id'] . '_own', $permissions[$permissionType]['allowed']) ? 'on' : (in_array($perm['id'] . '_own', $permissions[$permissionType]['denied']) ? 'denied' : 'off'); + } + else + $curPerm['select'] = in_array($perm['id'], $permissions[$permissionType]['denied']) ? 'denied' : (in_array($perm['id'], $permissions[$permissionType]['allowed']) ? 'on' : 'off'); + } + else + { + $curPerm['select'] = in_array($perm['id'], $permissions[$permissionType]['denied']) ? 'denied' : (in_array($perm['id'], $permissions[$permissionType]['allowed']) ? 'on' : 'off'); + } + } + } + } + } + $context['sub_template'] = 'modify_group'; + $context['page_title'] = $txt['permissions_modify_group']; +} + +function ModifyMembergroup2() +{ + global $modSettings, $smcFunc, $context; + + checkSession(); + + loadIllegalPermissions(); + + $_GET['group'] = (int) $_GET['group']; + $_GET['pid'] = (int) $_GET['pid']; + + // Cannot modify predefined profiles. + if ($_GET['pid'] > 1 && $_GET['pid'] < 5) + fatal_lang_error('no_access', false); + + // Verify this isn't inherited. + if ($_GET['group'] == -1 || $_GET['group'] == 0) + $parent = -2; + else + { + $result = $smcFunc['db_query']('', ' + SELECT id_parent + FROM {db_prefix}membergroups + WHERE id_group = {int:current_group} + LIMIT 1', + array( + 'current_group' => $_GET['group'], + ) + ); + list ($parent) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + } + + if ($parent != -2) + fatal_lang_error('cannot_edit_permissions_inherited'); + + $givePerms = array('membergroup' => array(), 'board' => array()); + + // Guest group, we need illegal, guest permissions. + if ($_GET['group'] == -1) + { + loadIllegalGuestPermissions(); + $context['illegal_permissions'] = array_merge($context['illegal_permissions'], $context['non_guest_permissions']); + } + + // Prepare all permissions that were set or denied for addition to the DB. + if (isset($_POST['perm']) && is_array($_POST['perm'])) + { + foreach ($_POST['perm'] as $perm_type => $perm_array) + { + if (is_array($perm_array)) + { + foreach ($perm_array as $permission => $value) + if ($value == 'on' || $value == 'deny') + { + // Don't allow people to escalate themselves! + if (!empty($context['illegal_permissions']) && in_array($permission, $context['illegal_permissions'])) + continue; + + $givePerms[$perm_type][] = array($_GET['group'], $permission, $value == 'deny' ? 0 : 1); + } + } + } + } + + // Insert the general permissions. + if ($_GET['group'] != 3 && empty($_GET['pid'])) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}permissions + WHERE id_group = {int:current_group} + ' . (empty($context['illegal_permissions']) ? '' : ' AND permission NOT IN ({array_string:illegal_permissions})'), + array( + 'current_group' => $_GET['group'], + 'illegal_permissions' => !empty($context['illegal_permissions']) ? $context['illegal_permissions'] : array(), + ) + ); + + if (!empty($givePerms['membergroup'])) + { + $smcFunc['db_insert']('replace', + '{db_prefix}permissions', + array('id_group' => 'int', 'permission' => 'string', 'add_deny' => 'int'), + $givePerms['membergroup'], + array('id_group', 'permission') + ); + } + } + + // Insert the boardpermissions. + $profileid = max(1, $_GET['pid']); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}board_permissions + WHERE id_group = {int:current_group} + AND id_profile = {int:current_profile}', + array( + 'current_group' => $_GET['group'], + 'current_profile' => $profileid, + ) + ); + if (!empty($givePerms['board'])) + { + foreach ($givePerms['board'] as $k => $v) + $givePerms['board'][$k][] = $profileid; + $smcFunc['db_insert']('replace', + '{db_prefix}board_permissions', + array('id_group' => 'int', 'permission' => 'string', 'add_deny' => 'int', 'id_profile' => 'int'), + $givePerms['board'], + array('id_group', 'permission', 'id_profile') + ); + } + + // Update any inherited permissions as required. + updateChildPermissions($_GET['group'], $_GET['pid']); + + // Clear cached privs. + updateSettings(array('settings_updated' => time())); + + redirectexit('action=admin;area=permissions;pid=' . $_GET['pid']); +} + +// Screen for modifying general permission settings. +function GeneralPermissionSettings($return_config = false) +{ + global $context, $modSettings, $sourcedir, $txt, $scripturl, $smcFunc; + + // All the setting variables + $config_vars = array( + array('title', 'settings'), + // Inline permissions. + array('permissions', 'manage_permissions'), + '', + // A few useful settings + array('check', 'permission_enable_deny', 0, $txt['permission_settings_enable_deny'], 'help' => 'permissions_deny'), + array('check', 'permission_enable_postgroups', 0, $txt['permission_settings_enable_postgroups'], 'help' => 'permissions_postgroups'), + ); + + if ($return_config) + return $config_vars; + + $context['page_title'] = $txt['permission_settings_title']; + $context['sub_template'] = 'show_settings'; + + // Needed for the inline permission functions, and the settings template. + require_once($sourcedir . '/ManageServer.php'); + + // Don't let guests have these permissions. + $context['post_url'] = $scripturl . '?action=admin;area=permissions;save;sa=settings'; + $context['permissions_excluded'] = array(-1); + + // Saving the settings? + if (isset($_GET['save'])) + { + checkSession('post'); + saveDBSettings($config_vars); + + // Clear all deny permissions...if we want that. + if (empty($modSettings['permission_enable_deny'])) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}permissions + WHERE add_deny = {int:denied}', + array( + 'denied' => 0, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}board_permissions + WHERE add_deny = {int:denied}', + array( + 'denied' => 0, + ) + ); + } + + // Make sure there are no postgroup based permissions left. + if (empty($modSettings['permission_enable_postgroups'])) + { + // Get a list of postgroups. + $post_groups = array(); + $request = $smcFunc['db_query']('', ' + SELECT id_group + FROM {db_prefix}membergroups + WHERE min_posts != {int:min_posts}', + array( + 'min_posts' => -1, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $post_groups[] = $row['id_group']; + $smcFunc['db_free_result']($request); + + // Remove'em. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}permissions + WHERE id_group IN ({array_int:post_group_list})', + array( + 'post_group_list' => $post_groups, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}board_permissions + WHERE id_group IN ({array_int:post_group_list})', + array( + 'post_group_list' => $post_groups, + ) + ); + $smcFunc['db_query']('', ' + UPDATE {db_prefix}membergroups + SET id_parent = {int:not_inherited} + WHERE id_parent IN ({array_int:post_group_list})', + array( + 'post_group_list' => $post_groups, + 'not_inherited' => -2, + ) + ); + } + + redirectexit('action=admin;area=permissions;sa=settings'); + } + + prepareDBSettingContext($config_vars); +} + +// Set the permission level for a specific profile, group, or group for a profile. +function setPermissionLevel($level, $group, $profile = 'null') +{ + global $smcFunc, $context; + + loadIllegalPermissions(); + loadIllegalGuestPermissions(); + + // Levels by group... restrict, standard, moderator, maintenance. + $groupLevels = array( + 'board' => array('inherit' => array()), + 'group' => array('inherit' => array()) + ); + // Levels by board... standard, publish, free. + $boardLevels = array('inherit' => array()); + + // Restrictive - ie. guests. + $groupLevels['global']['restrict'] = array( + 'search_posts', + 'calendar_view', + 'view_stats', + 'who_view', + 'profile_view_own', + 'profile_identity_own', + ); + $groupLevels['board']['restrict'] = array( + 'poll_view', + 'post_new', + 'post_reply_own', + 'post_reply_any', + 'delete_own', + 'modify_own', + 'mark_any_notify', + 'mark_notify', + 'report_any', + 'send_topic', + ); + + // Standard - ie. members. They can do anything Restrictive can. + $groupLevels['global']['standard'] = array_merge($groupLevels['global']['restrict'], array( + 'view_mlist', + 'karma_edit', + 'pm_read', + 'pm_send', + 'profile_view_any', + 'profile_extra_own', + 'profile_server_avatar', + 'profile_upload_avatar', + 'profile_remote_avatar', + 'profile_remove_own', + )); + $groupLevels['board']['standard'] = array_merge($groupLevels['board']['restrict'], array( + 'poll_vote', + 'poll_edit_own', + 'poll_post', + 'poll_add_own', + 'post_attachment', + 'lock_own', + 'remove_own', + 'view_attachments', + )); + + // Moderator - ie. moderators :P. They can do what standard can, and more. + $groupLevels['global']['moderator'] = array_merge($groupLevels['global']['standard'], array( + 'calendar_post', + 'calendar_edit_own', + 'access_mod_center', + 'issue_warning', + )); + $groupLevels['board']['moderator'] = array_merge($groupLevels['board']['standard'], array( + 'make_sticky', + 'poll_edit_any', + 'delete_any', + 'modify_any', + 'lock_any', + 'remove_any', + 'move_any', + 'merge_any', + 'split_any', + 'poll_lock_any', + 'poll_remove_any', + 'poll_add_any', + 'approve_posts', + )); + + // Maintenance - wannabe admins. They can do almost everything. + $groupLevels['global']['maintenance'] = array_merge($groupLevels['global']['moderator'], array( + 'manage_attachments', + 'manage_smileys', + 'manage_boards', + 'moderate_forum', + 'manage_membergroups', + 'manage_bans', + 'admin_forum', + 'manage_permissions', + 'edit_news', + 'calendar_edit_any', + 'profile_identity_any', + 'profile_extra_any', + 'profile_title_any', + )); + $groupLevels['board']['maintenance'] = array_merge($groupLevels['board']['moderator'], array( + )); + + // Standard - nothing above the group permissions. (this SHOULD be empty.) + $boardLevels['standard'] = array( + ); + + // Locked - just that, you can't post here. + $boardLevels['locked'] = array( + 'poll_view', + 'mark_notify', + 'report_any', + 'send_topic', + 'view_attachments', + ); + + // Publisher - just a little more... + $boardLevels['publish'] = array_merge($boardLevels['locked'], array( + 'post_new', + 'post_reply_own', + 'post_reply_any', + 'delete_own', + 'modify_own', + 'mark_any_notify', + 'delete_replies', + 'modify_replies', + 'poll_vote', + 'poll_edit_own', + 'poll_post', + 'poll_add_own', + 'poll_remove_own', + 'post_attachment', + 'lock_own', + 'remove_own', + )); + + // Free for All - Scary. Just scary. + $boardLevels['free'] = array_merge($boardLevels['publish'], array( + 'poll_lock_any', + 'poll_edit_any', + 'poll_add_any', + 'poll_remove_any', + 'make_sticky', + 'lock_any', + 'remove_any', + 'delete_any', + 'split_any', + 'merge_any', + 'modify_any', + 'approve_posts', + )); + + // Make sure we're not granting someone too many permissions! + foreach ($groupLevels['global'][$level] as $k => $permission) + { + if (!empty($context['illegal_permissions']) && in_array($permission, $context['illegal_permissions'])) + unset($groupLevels['global'][$level][$k]); + + if ($group == -1 && in_array($permission, $context['non_guest_permissions'])) + unset($groupLevels['global'][$level][$k]); + } + if ($group == -1) + foreach ($groupLevels['board'][$level] as $k => $permission) + if (in_array($permission, $context['non_guest_permissions'])) + unset($groupLevels['board'][$level][$k]); + + // Reset all cached permissions. + updateSettings(array('settings_updated' => time())); + + // Setting group permissions. + if ($profile === 'null' && $group !== 'null') + { + $group = (int) $group; + + if (empty($groupLevels['global'][$level])) + return; + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}permissions + WHERE id_group = {int:current_group} + ' . (empty($context['illegal_permissions']) ? '' : ' AND permission NOT IN ({array_string:illegal_permissions})'), + array( + 'current_group' => $group, + 'illegal_permissions' => !empty($context['illegal_permissions']) ? $context['illegal_permissions'] : array(), + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}board_permissions + WHERE id_group = {int:current_group} + AND id_profile = {int:default_profile}', + array( + 'current_group' => $group, + 'default_profile' => 1, + ) + ); + + $groupInserts = array(); + foreach ($groupLevels['global'][$level] as $permission) + $groupInserts[] = array($group, $permission); + + $smcFunc['db_insert']('insert', + '{db_prefix}permissions', + array('id_group' => 'int', 'permission' => 'string'), + $groupInserts, + array('id_group') + ); + + $boardInserts = array(); + foreach ($groupLevels['board'][$level] as $permission) + $boardInserts[] = array(1, $group, $permission); + + $smcFunc['db_insert']('insert', + '{db_prefix}board_permissions', + array('id_profile' => 'int', 'id_group' => 'int', 'permission' => 'string'), + $boardInserts, + array('id_profile', 'id_group') + ); + } + // Setting profile permissions for a specific group. + elseif ($profile !== 'null' && $group !== 'null' && ($profile == 1 || $profile > 4)) + { + $group = (int) $group; + $profile = (int) $profile; + + if (!empty($groupLevels['global'][$level])) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}board_permissions + WHERE id_group = {int:current_group} + AND id_profile = {int:current_profile}', + array( + 'current_group' => $group, + 'current_profile' => $profile, + ) + ); + } + + if (!empty($groupLevels['board'][$level])) + { + $boardInserts = array(); + foreach ($groupLevels['board'][$level] as $permission) + $boardInserts[] = array($profile, $group, $permission); + + $smcFunc['db_insert']('insert', + '{db_prefix}board_permissions', + array('id_profile' => 'int', 'id_group' => 'int', 'permission' => 'string'), + $boardInserts, + array('id_profile', 'id_group') + ); + } + } + // Setting profile permissions for all groups. + elseif ($profile !== 'null' && $group === 'null' && ($profile == 1 || $profile > 4)) + { + $profile = (int) $profile; + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}board_permissions + WHERE id_profile = {int:current_profile}', + array( + 'current_profile' => $profile, + ) + ); + + if (empty($boardLevels[$level])) + return; + + // Get all the groups... + $query = $smcFunc['db_query']('', ' + SELECT id_group + FROM {db_prefix}membergroups + WHERE id_group > {int:moderator_group} + ORDER BY min_posts, CASE WHEN id_group < {int:newbie_group} THEN id_group ELSE 4 END, group_name', + array( + 'moderator_group' => 3, + 'newbie_group' => 4, + ) + ); + while ($row = $smcFunc['db_fetch_row']($query)) + { + $group = $row[0]; + + $boardInserts = array(); + foreach ($boardLevels[$level] as $permission) + $boardInserts[] = array($profile, $group, $permission); + + $smcFunc['db_insert']('insert', + '{db_prefix}board_permissions', + array('id_profile' => 'int', 'id_group' => 'int', 'permission' => 'string'), + $boardInserts, + array('id_profile', 'id_group') + ); + } + $smcFunc['db_free_result']($query); + + // Add permissions for ungrouped members. + $boardInserts = array(); + foreach ($boardLevels[$level] as $permission) + $boardInserts[] = array($profile, 0, $permission); + + $smcFunc['db_insert']('insert', + '{db_prefix}board_permissions', + array('id_profile' => 'int', 'id_group' => 'int', 'permission' => 'string'), + $boardInserts, + array('id_profile', 'id_group') + ); + } + // $profile and $group are both null! + else + fatal_lang_error('no_access', false); +} + +function loadAllPermissions($loadType = 'classic') +{ + global $context, $txt, $modSettings; + + // List of all the groups dependant on the currently selected view - for the order so it looks pretty, yea? + // Note to Mod authors - you don't need to stick your permission group here if you don't mind SMF sticking it the last group of the page. + $permissionGroups = array( + 'membergroup' => array( + 'simple' => array( + 'view_basic_info', + 'use_pm_system', + 'post_calendar', + 'edit_profile', + 'delete_account', + 'use_avatar', + 'moderate_general', + 'administrate', + ), + 'classic' => array( + 'general', + 'pm', + 'calendar', + 'maintenance', + 'member_admin', + 'profile', + ), + ), + 'board' => array( + 'simple' => array( + 'make_posts', + 'make_unapproved_posts', + 'post_polls', + 'participate', + 'modify', + 'notification', + 'attach', + 'moderate', + ), + 'classic' => array( + 'general_board', + 'topic', + 'post', + 'poll', + 'notification', + 'attachment', + ), + ), + ); + + /* The format of this list is as follows: + 'membergroup' => array( + 'permissions_inside' => array(has_multiple_options, classic_view_group, simple_view_group(_own)*, simple_view_group_any*), + ), + 'board' => array( + 'permissions_inside' => array(has_multiple_options, classic_view_group, simple_view_group(_own)*, simple_view_group_any*), + ); + */ + $permissionList = array( + 'membergroup' => array( + 'view_stats' => array(false, 'general', 'view_basic_info'), + 'view_mlist' => array(false, 'general', 'view_basic_info'), + 'who_view' => array(false, 'general', 'view_basic_info'), + 'search_posts' => array(false, 'general', 'view_basic_info'), + 'karma_edit' => array(false, 'general', 'moderate_general'), + 'pm_read' => array(false, 'pm', 'use_pm_system'), + 'pm_send' => array(false, 'pm', 'use_pm_system'), + 'calendar_view' => array(false, 'calendar', 'view_basic_info'), + 'calendar_post' => array(false, 'calendar', 'post_calendar'), + 'calendar_edit' => array(true, 'calendar', 'post_calendar', 'moderate_general'), + 'admin_forum' => array(false, 'maintenance', 'administrate'), + 'manage_boards' => array(false, 'maintenance', 'administrate'), + 'manage_attachments' => array(false, 'maintenance', 'administrate'), + 'manage_smileys' => array(false, 'maintenance', 'administrate'), + 'edit_news' => array(false, 'maintenance', 'administrate'), + 'access_mod_center' => array(false, 'maintenance', 'moderate_general'), + 'moderate_forum' => array(false, 'member_admin', 'moderate_general'), + 'manage_membergroups' => array(false, 'member_admin', 'administrate'), + 'manage_permissions' => array(false, 'member_admin', 'administrate'), + 'manage_bans' => array(false, 'member_admin', 'administrate'), + 'send_mail' => array(false, 'member_admin', 'administrate'), + 'issue_warning' => array(false, 'member_admin', 'moderate_general'), + 'profile_view' => array(true, 'profile', 'view_basic_info', 'view_basic_info'), + 'profile_identity' => array(true, 'profile', 'edit_profile', 'moderate_general'), + 'profile_extra' => array(true, 'profile', 'edit_profile', 'moderate_general'), + 'profile_title' => array(true, 'profile', 'edit_profile', 'moderate_general'), + 'profile_remove' => array(true, 'profile', 'delete_account', 'moderate_general'), + 'profile_server_avatar' => array(false, 'profile', 'use_avatar'), + 'profile_upload_avatar' => array(false, 'profile', 'use_avatar'), + 'profile_remote_avatar' => array(false, 'profile', 'use_avatar'), + ), + 'board' => array( + 'moderate_board' => array(false, 'general_board', 'moderate'), + 'approve_posts' => array(false, 'general_board', 'moderate'), + 'post_new' => array(false, 'topic', 'make_posts'), + 'post_unapproved_topics' => array(false, 'topic', 'make_unapproved_posts'), + 'post_unapproved_replies' => array(true, 'topic', 'make_unapproved_posts', 'make_unapproved_posts'), + 'post_reply' => array(true, 'topic', 'make_posts', 'make_posts'), + 'merge_any' => array(false, 'topic', 'moderate'), + 'split_any' => array(false, 'topic', 'moderate'), + 'send_topic' => array(false, 'topic', 'moderate'), + 'make_sticky' => array(false, 'topic', 'moderate'), + 'move' => array(true, 'topic', 'moderate', 'moderate'), + 'lock' => array(true, 'topic', 'moderate', 'moderate'), + 'remove' => array(true, 'topic', 'modify', 'moderate'), + 'modify_replies' => array(false, 'topic', 'moderate'), + 'delete_replies' => array(false, 'topic', 'moderate'), + 'announce_topic' => array(false, 'topic', 'moderate'), + 'delete' => array(true, 'post', 'modify', 'moderate'), + 'modify' => array(true, 'post', 'modify', 'moderate'), + 'report_any' => array(false, 'post', 'participate'), + 'poll_view' => array(false, 'poll', 'participate'), + 'poll_vote' => array(false, 'poll', 'participate'), + 'poll_post' => array(false, 'poll', 'post_polls'), + 'poll_add' => array(true, 'poll', 'post_polls', 'moderate'), + 'poll_edit' => array(true, 'poll', 'modify', 'moderate'), + 'poll_lock' => array(true, 'poll', 'moderate', 'moderate'), + 'poll_remove' => array(true, 'poll', 'modify', 'moderate'), + 'mark_any_notify' => array(false, 'notification', 'notification'), + 'mark_notify' => array(false, 'notification', 'notification'), + 'view_attachments' => array(false, 'attachment', 'participate'), + 'post_unapproved_attachments' => array(false, 'attachment', 'make_unapproved_posts'), + 'post_attachment' => array(false, 'attachment', 'attach'), + ), + ); + + // All permission groups that will be shown in the left column on classic view. + $leftPermissionGroups = array( + 'general', + 'calendar', + 'maintenance', + 'member_admin', + 'topic', + 'post', + ); + + // We need to know what permissions we can't give to guests. + loadIllegalGuestPermissions(); + + // Some permissions are hidden if features are off. + $hiddenPermissions = array(); + $relabelPermissions = array(); // Permissions to apply a different label to. + $relabelGroups = array(); // As above but for groups. + if (!in_array('cd', $context['admin_features'])) + { + $hiddenPermissions[] = 'calendar_view'; + $hiddenPermissions[] = 'calendar_post'; + $hiddenPermissions[] = 'calendar_edit'; + } + if (!in_array('w', $context['admin_features'])) + $hiddenPermissions[] = 'issue_warning'; + + // Post moderation? + if (!$modSettings['postmod_active']) + { + $hiddenPermissions[] = 'approve_posts'; + $hiddenPermissions[] = 'post_unapproved_topics'; + $hiddenPermissions[] = 'post_unapproved_replies'; + $hiddenPermissions[] = 'post_unapproved_attachments'; + } + // If we show them on classic view we change the name. + else + { + // Relabel the topics permissions + $relabelPermissions['post_new'] = 'auto_approve_topics'; + + // Relabel the reply permissions + $relabelPermissions['post_reply'] = 'auto_approve_replies'; + + // Relabel the attachment permissions + $relabelPermissions['post_attachment'] = 'auto_approve_attachments'; + } + + // Provide a practical way to modify permissions. + call_integration_hook('integrate_load_permissions', array(&$permissionGroups, &$permissionList, &$leftPermissionGroups, &$hiddenPermissions, &$relabelPermissions)); + + $context['permissions'] = array(); + $context['hidden_permissions'] = array(); + foreach ($permissionList as $permissionType => $permissionList) + { + $context['permissions'][$permissionType] = array( + 'id' => $permissionType, + 'view' => $loadType, + 'columns' => array() + ); + foreach ($permissionList as $permission => $permissionArray) + { + // If this is a guest permission we don't do it if it's the guest group. + if (isset($context['group']['id']) && $context['group']['id'] == -1 && in_array($permission, $context['non_guest_permissions'])) + continue; + + // What groups will this permission be in? + $own_group = $permissionArray[($loadType == 'classic' ? 1 : 2)]; + $any_group = $loadType == 'simple' && !empty($permissionArray[3]) ? $permissionArray[3] : ($loadType == 'simple' && $permissionArray[0] ? $permissionArray[2] : ''); + + // First, Do these groups actually exist - if not add them. + if (!isset($permissionGroups[$permissionType][$loadType][$own_group])) + $permissionGroups[$permissionType][$loadType][$own_group] = true; + if (!empty($any_group) && !isset($permissionGroups[$permissionType][$loadType][$any_group])) + $permissionGroups[$permissionType][$loadType][$any_group] = true; + + // What column should this be located into? + $position = $loadType == 'classic' && !in_array($own_group, $leftPermissionGroups) ? 1 : 0; + + // If the groups have not yet been created be sure to create them. + $bothGroups = array('own' => $own_group); + $bothGroups = array(); + + // For guests, just reset the array. + if (!isset($context['group']['id']) || !($context['group']['id'] == -1 && $any_group)) + $bothGroups['own'] = $own_group; + + if ($any_group) + { + $bothGroups['any'] = $any_group; + + } + + foreach ($bothGroups as $group) + if (!isset($context['permissions'][$permissionType]['columns'][$position][$group])) + $context['permissions'][$permissionType]['columns'][$position][$group] = array( + 'type' => $permissionType, + 'id' => $group, + 'name' => $loadType == 'simple' ? (isset($txt['permissiongroup_simple_' . $group]) ? $txt['permissiongroup_simple_' . $group] : '') : $txt['permissiongroup_' . $group], + 'icon' => isset($txt['permissionicon_' . $group]) ? $txt['permissionicon_' . $group] : $txt['permissionicon'], + 'help' => isset($txt['permissionhelp_' . $group]) ? $txt['permissionhelp_' . $group] : '', + 'hidden' => false, + 'permissions' => array() + ); + + // This is where we set up the permission dependant on the view. + if ($loadType == 'classic') + { + $context['permissions'][$permissionType]['columns'][$position][$own_group]['permissions'][$permission] = array( + 'id' => $permission, + 'name' => !isset($relabelPermissions[$permission]) ? $txt['permissionname_' . $permission] : $txt[$relabelPermissions[$permission]], + 'show_help' => isset($txt['permissionhelp_' . $permission]), + 'note' => isset($txt['permissionnote_' . $permission]) ? $txt['permissionnote_' . $permission] : '', + 'has_own_any' => $permissionArray[0], + 'own' => array( + 'id' => $permission . '_own', + 'name' => $permissionArray[0] ? $txt['permissionname_' . $permission . '_own'] : '' + ), + 'any' => array( + 'id' => $permission . '_any', + 'name' => $permissionArray[0] ? $txt['permissionname_' . $permission . '_any'] : '' + ), + 'hidden' => in_array($permission, $hiddenPermissions), + ); + } + else + { + foreach ($bothGroups as $group_type => $group) + { + $context['permissions'][$permissionType]['columns'][$position][$group]['permissions'][$permission . ($permissionArray[0] ? '_' . $group_type : '')] = array( + 'id' => $permission . ($permissionArray[0] ? '_' . $group_type : ''), + 'name' => isset($txt['permissionname_simple_' . $permission . ($permissionArray[0] ? '_' . $group_type : '')]) ? $txt['permissionname_simple_' . $permission . ($permissionArray[0] ? '_' . $group_type : '')] : $txt['permissionname_' . $permission], + 'help_index' => isset($txt['permissionhelp_' . $permission]) ? 'permissionhelp_' . $permission : '', + 'hidden' => in_array($permission, $hiddenPermissions), + ); + } + } + + if (in_array($permission, $hiddenPermissions)) + { + if ($permissionArray[0]) + { + $context['hidden_permissions'][] = $permission . '_own'; + $context['hidden_permissions'][] = $permission . '_any'; + } + else + $context['hidden_permissions'][] = $permission; + } + } + ksort($context['permissions'][$permissionType]['columns']); + } + + // Check we don't leave any empty groups - and mark hidden ones as such. + foreach ($context['permissions'][$permissionType]['columns'] as $column => $groups) + foreach ($groups as $id => $group) + { + if (empty($group['permissions'])) + unset($context['permissions'][$permissionType]['columns'][$column][$id]); + else + { + $foundNonHidden = false; + foreach ($group['permissions'] as $permission) + if (empty($permission['hidden'])) + $foundNonHidden = true; + if (!$foundNonHidden) + $context['permissions'][$permissionType]['columns'][$column][$id]['hidden'] = true; + } + } +} + +// Initialize a form with inline permissions. +function init_inline_permissions($permissions, $excluded_groups = array()) +{ + global $context, $txt, $modSettings, $smcFunc; + + loadLanguage('ManagePermissions'); + loadTemplate('ManagePermissions'); + $context['can_change_permissions'] = allowedTo('manage_permissions'); + + // Nothing to initialize here. + if (!$context['can_change_permissions']) + return; + + // Load the permission settings for guests + foreach ($permissions as $permission) + $context[$permission] = array( + -1 => array( + 'id' => -1, + 'name' => $txt['membergroups_guests'], + 'is_postgroup' => false, + 'status' => 'off', + ), + 0 => array( + 'id' => 0, + 'name' => $txt['membergroups_members'], + 'is_postgroup' => false, + 'status' => 'off', + ), + ); + + $request = $smcFunc['db_query']('', ' + SELECT id_group, CASE WHEN add_deny = {int:denied} THEN {string:deny} ELSE {string:on} END AS status, permission + FROM {db_prefix}permissions + WHERE id_group IN (-1, 0) + AND permission IN ({array_string:permissions})', + array( + 'denied' => 0, + 'permissions' => $permissions, + 'deny' => 'deny', + 'on' => 'on', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context[$row['permission']][$row['id_group']]['status'] = $row['status']; + $smcFunc['db_free_result']($request); + + $request = $smcFunc['db_query']('', ' + SELECT mg.id_group, mg.group_name, mg.min_posts, IFNULL(p.add_deny, -1) AS status, p.permission + FROM {db_prefix}membergroups AS mg + LEFT JOIN {db_prefix}permissions AS p ON (p.id_group = mg.id_group AND p.permission IN ({array_string:permissions})) + WHERE mg.id_group NOT IN (1, 3) + AND mg.id_parent = {int:not_inherited}' . (empty($modSettings['permission_enable_postgroups']) ? ' + AND mg.min_posts = {int:min_posts}' : '') . ' + ORDER BY mg.min_posts, CASE WHEN mg.id_group < {int:newbie_group} THEN mg.id_group ELSE 4 END, mg.group_name', + array( + 'not_inherited' => -2, + 'min_posts' => -1, + 'newbie_group' => 4, + 'permissions' => $permissions, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Initialize each permission as being 'off' until proven otherwise. + foreach ($permissions as $permission) + if (!isset($context[$permission][$row['id_group']])) + $context[$permission][$row['id_group']] = array( + 'id' => $row['id_group'], + 'name' => $row['group_name'], + 'is_postgroup' => $row['min_posts'] != -1, + 'status' => 'off', + ); + + $context[$row['permission']][$row['id_group']]['status'] = empty($row['status']) ? 'deny' : ($row['status'] == 1 ? 'on' : 'off'); + } + $smcFunc['db_free_result']($request); + + // Some permissions cannot be given to certain groups. Remove the groups. + foreach ($excluded_groups as $group) + { + foreach ($permissions as $permission) + { + if (isset($context[$permission][$group])) + unset($context[$permission][$group]); + } + } +} + +// Show a collapsible box to set a specific permission. +function theme_inline_permissions($permission) +{ + global $context; + + $context['current_permission'] = $permission; + $context['member_groups'] = $context[$permission]; + + template_inline_permissions(); +} + +// Save the permissions of a form containing inline permissions. +function save_inline_permissions($permissions) +{ + global $context, $smcFunc; + + // No permissions? Not a great deal to do here. + if (!allowedTo('manage_permissions')) + return; + + // Almighty session check, verify our ways. + checkSession(); + + // Check they can't do certain things. + loadIllegalPermissions(); + + $insertRows = array(); + foreach ($permissions as $permission) + { + if (!isset($_POST[$permission])) + continue; + + foreach ($_POST[$permission] as $id_group => $value) + { + if (in_array($value, array('on', 'deny')) && (empty($context['illegal_permissions']) || !in_array($permission, $context['illegal_permissions']))) + $insertRows[] = array((int) $id_group, $permission, $value == 'on' ? 1 : 0); + } + } + + // Remove the old permissions... + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}permissions + WHERE permission IN ({array_string:permissions}) + ' . (empty($context['illegal_permissions']) ? '' : ' AND permission NOT IN ({array_string:illegal_permissions})'), + array( + 'illegal_permissions' => !empty($context['illegal_permissions']) ? $context['illegal_permissions'] : array(), + 'permissions' => $permissions, + ) + ); + + // ...and replace them with new ones. + if (!empty($insertRows)) + $smcFunc['db_insert']('insert', + '{db_prefix}permissions', + array('id_group' => 'int', 'permission' => 'string', 'add_deny' => 'int'), + $insertRows, + array('id_group', 'permission') + ); + + // Do a full child update. + updateChildPermissions(array(), -1); + + // Just in case we cached this. + updateSettings(array('settings_updated' => time())); +} + +function loadPermissionProfiles() +{ + global $context, $txt, $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT id_profile, profile_name + FROM {db_prefix}permission_profiles + ORDER BY id_profile', + array( + ) + ); + $context['profiles'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Format the label nicely. + if (isset($txt['permissions_profile_' . $row['profile_name']])) + $name = $txt['permissions_profile_' . $row['profile_name']]; + else + $name = $row['profile_name']; + + $context['profiles'][$row['id_profile']] = array( + 'id' => $row['id_profile'], + 'name' => $name, + 'can_modify' => $row['id_profile'] == 1 || $row['id_profile'] > 4, + 'unformatted_name' => $row['profile_name'], + ); + } + $smcFunc['db_free_result']($request); +} + +// Add/Edit/Delete profiles. +function EditPermissionProfiles() +{ + global $context, $txt, $smcFunc; + + // Setup the template, first for fun. + $context['page_title'] = $txt['permissions_profile_edit']; + $context['sub_template'] = 'edit_profiles'; + + // If we're creating a new one do it first. + if (isset($_POST['create']) && trim($_POST['profile_name']) != '') + { + checkSession(); + + $_POST['copy_from'] = (int) $_POST['copy_from']; + $_POST['profile_name'] = $smcFunc['htmlspecialchars']($_POST['profile_name']); + + // Insert the profile itself. + $smcFunc['db_insert']('', + '{db_prefix}permission_profiles', + array( + 'profile_name' => 'string', + ), + array( + $_POST['profile_name'], + ), + array('id_profile') + ); + $profile_id = $smcFunc['db_insert_id']('{db_prefix}permission_profiles', 'id_profile'); + + // Load the permissions from the one it's being copied from. + $request = $smcFunc['db_query']('', ' + SELECT id_group, permission, add_deny + FROM {db_prefix}board_permissions + WHERE id_profile = {int:copy_from}', + array( + 'copy_from' => $_POST['copy_from'], + ) + ); + $inserts = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $inserts[] = array($profile_id, $row['id_group'], $row['permission'], $row['add_deny']); + $smcFunc['db_free_result']($request); + + if (!empty($inserts)) + $smcFunc['db_insert']('insert', + '{db_prefix}board_permissions', + array('id_profile' => 'int', 'id_group' => 'int', 'permission' => 'string', 'add_deny' => 'int'), + $inserts, + array('id_profile', 'id_group', 'permission') + ); + } + // Renaming? + elseif (isset($_POST['rename'])) + { + checkSession(); + + // Just showing the boxes? + if (!isset($_POST['rename_profile'])) + $context['show_rename_boxes'] = true; + else + { + foreach ($_POST['rename_profile'] as $id => $value) + { + $value = $smcFunc['htmlspecialchars']($value); + + if (trim($value) != '' && $id > 4) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}permission_profiles + SET profile_name = {string:profile_name} + WHERE id_profile = {int:current_profile}', + array( + 'current_profile' => (int) $id, + 'profile_name' => $value, + ) + ); + } + } + } + // Deleting? + elseif (isset($_POST['delete']) && !empty($_POST['delete_profile'])) + { + checkSession('post'); + + $profiles = array(); + foreach ($_POST['delete_profile'] as $profile) + if ($profile > 4) + $profiles[] = (int) $profile; + + // Verify it's not in use... + $request = $smcFunc['db_query']('', ' + SELECT id_board + FROM {db_prefix}boards + WHERE id_profile IN ({array_int:profile_list}) + LIMIT 1', + array( + 'profile_list' => $profiles, + ) + ); + if ($smcFunc['db_num_rows']($request) != 0) + fatal_lang_error('no_access', false); + $smcFunc['db_free_result']($request); + + // Oh well, delete. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}permission_profiles + WHERE id_profile IN ({array_int:profile_list})', + array( + 'profile_list' => $profiles, + ) + ); + } + + // Clearly, we'll need this! + loadPermissionProfiles(); + + // Work out what ones are in use. + $request = $smcFunc['db_query']('', ' + SELECT id_profile, COUNT(id_board) AS board_count + FROM {db_prefix}boards + GROUP BY id_profile', + array( + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + if (isset($context['profiles'][$row['id_profile']])) + { + $context['profiles'][$row['id_profile']]['in_use'] = true; + $context['profiles'][$row['id_profile']]['boards'] = $row['board_count']; + $context['profiles'][$row['id_profile']]['boards_text'] = $row['board_count'] > 1 ? sprintf($txt['permissions_profile_used_by_many'], $row['board_count']) : $txt['permissions_profile_used_by_' . ($row['board_count'] ? 'one' : 'none')]; + } + $smcFunc['db_free_result']($request); + + // What can we do with these? + $context['can_edit_something'] = false; + foreach ($context['profiles'] as $id => $profile) + { + // Can't delete special ones. + $context['profiles'][$id]['can_edit'] = isset($txt['permissions_profile_' . $profile['unformatted_name']]) ? false : true; + if ($context['profiles'][$id]['can_edit']) + $context['can_edit_something'] = true; + + // You can only delete it if you can edit it AND it's not in use. + $context['profiles'][$id]['can_delete'] = $context['profiles'][$id]['can_edit'] && empty($profile['in_use']) ? true : false; + } +} + +// This function updates the permissions of any groups based off this group. +function updateChildPermissions($parents, $profile = null) +{ + global $smcFunc; + + // All the parent groups to sort out. + if (!is_array($parents)) + $parents = array($parents); + + // Find all the children of this group. + $request = $smcFunc['db_query']('', ' + SELECT id_parent, id_group + FROM {db_prefix}membergroups + WHERE id_parent != {int:not_inherited} + ' . (empty($parents) ? '' : 'AND id_parent IN ({array_int:parent_list})'), + array( + 'parent_list' => $parents, + 'not_inherited' => -2, + ) + ); + $children = array(); + $parents = array(); + $child_groups = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $children[$row['id_parent']][] = $row['id_group']; + $child_groups[] = $row['id_group']; + $parents[] = $row['id_parent']; + } + $smcFunc['db_free_result']($request); + + $parents = array_unique($parents); + + // Not a sausage, or a child? + if (empty($children)) + return false; + + // First off, are we doing general permissions? + if ($profile < 1 || $profile === null) + { + // Fetch all the parent permissions. + $request = $smcFunc['db_query']('', ' + SELECT id_group, permission, add_deny + FROM {db_prefix}permissions + WHERE id_group IN ({array_int:parent_list})', + array( + 'parent_list' => $parents, + ) + ); + $permissions = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + foreach ($children[$row['id_group']] as $child) + $permissions[] = array($child, $row['permission'], $row['add_deny']); + $smcFunc['db_free_result']($request); + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}permissions + WHERE id_group IN ({array_int:child_groups})', + array( + 'child_groups' => $child_groups, + ) + ); + + // Finally insert. + if (!empty($permissions)) + { + $smcFunc['db_insert']('insert', + '{db_prefix}permissions', + array('id_group' => 'int', 'permission' => 'string', 'add_deny' => 'int'), + $permissions, + array('id_group', 'permission') + ); + } + } + + // Then, what about board profiles? + if ($profile != -1) + { + $profileQuery = $profile === null ? '' : ' AND id_profile = {int:current_profile}'; + + // Again, get all the parent permissions. + $request = $smcFunc['db_query']('', ' + SELECT id_profile, id_group, permission, add_deny + FROM {db_prefix}board_permissions + WHERE id_group IN ({array_int:parent_groups}) + ' . $profileQuery, + array( + 'parent_groups' => $parents, + 'current_profile' => $profile !== null && $profile ? $profile : 1, + ) + ); + $permissions = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + foreach ($children[$row['id_group']] as $child) + $permissions[] = array($child, $row['id_profile'], $row['permission'], $row['add_deny']); + $smcFunc['db_free_result']($request); + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}board_permissions + WHERE id_group IN ({array_int:child_groups}) + ' . $profileQuery, + array( + 'child_groups' => $child_groups, + 'current_profile' => $profile !== null && $profile ? $profile : 1, + ) + ); + + // Do the insert. + if (!empty($permissions)) + { + $smcFunc['db_insert']('insert', + '{db_prefix}board_permissions', + array('id_group' => 'int', 'id_profile' => 'int', 'permission' => 'string', 'add_deny' => 'int'), + $permissions, + array('id_group', 'id_profile', 'permission') + ); + } + } +} + +// Load permissions someone cannot grant. +function loadIllegalPermissions() +{ + global $context; + + $context['illegal_permissions'] = array(); + if (!allowedTo('admin_forum')) + $context['illegal_permissions'][] = 'admin_forum'; + if (!allowedTo('manage_membergroups')) + $context['illegal_permissions'][] = 'manage_membergroups'; + if (!allowedTo('manage_permissions')) + $context['illegal_permissions'][] = 'manage_permissions'; +} + +// Load all the permissions that can not be given to guests. +function loadIllegalGuestPermissions() +{ + global $context; + + $context['non_guest_permissions'] = array( + 'delete_replies', + 'karma_edit', + 'poll_add_own', + 'pm_read', + 'pm_send', + 'profile_identity', + 'profile_extra', + 'profile_title', + 'profile_remove', + 'profile_server_avatar', + 'profile_upload_avatar', + 'profile_remote_avatar', + 'profile_view_own', + 'mark_any_notify', + 'mark_notify', + 'admin_forum', + 'manage_boards', + 'manage_attachments', + 'manage_smileys', + 'edit_news', + 'access_mod_center', + 'moderate_forum', + 'issue_warning', + 'manage_membergroups', + 'manage_permissions', + 'manage_bans', + 'move_own', + 'modify_replies', + 'send_mail', + 'approve_posts', + ); +} + +// Present a nice way of applying post moderation. +function ModifyPostModeration() +{ + global $context, $txt, $smcFunc, $modSettings; + + // Just in case. + checkSession('get'); + + $context['page_title'] = $txt['permissions_post_moderation']; + $context['sub_template'] = 'postmod_permissions'; + $context['current_profile'] = isset($_REQUEST['pid']) ? (int) $_REQUEST['pid'] : 1; + + // Load all the permission profiles. + loadPermissionProfiles(); + + // Mappings, our key => array(can_do_moderated, can_do_all) + $mappings = array( + 'new_topic' => array('post_new', 'post_unapproved_topics'), + 'replies_own' => array('post_reply_own', 'post_unapproved_replies_own'), + 'replies_any' => array('post_reply_any', 'post_unapproved_replies_any'), + 'attachment' => array('post_attachment', 'post_unapproved_attachments'), + ); + + // Start this with the guests/members. + $context['profile_groups'] = array( + -1 => array( + 'id' => -1, + 'name' => $txt['membergroups_guests'], + 'color' => '', + 'new_topic' => 'disallow', + 'replies_own' => 'disallow', + 'replies_any' => 'disallow', + 'attachment' => 'disallow', + 'children' => array(), + ), + 0 => array( + 'id' => 0, + 'name' => $txt['membergroups_members'], + 'color' => '', + 'new_topic' => 'disallow', + 'replies_own' => 'disallow', + 'replies_any' => 'disallow', + 'attachment' => 'disallow', + 'children' => array(), + ), + ); + + // Load the groups. + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_name, online_color, id_parent + FROM {db_prefix}membergroups + WHERE id_group != {int:admin_group} + ' . (empty($modSettings['permission_enable_postgroups']) ? ' AND min_posts = {int:min_posts}' : '') . ' + ORDER BY id_parent ASC', + array( + 'admin_group' => 1, + 'min_posts' => -1, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['id_parent'] == -2) + { + $context['profile_groups'][$row['id_group']] = array( + 'id' => $row['id_group'], + 'name' => $row['group_name'], + 'color' => $row['online_color'], + 'new_topic' => 'disallow', + 'replies_own' => 'disallow', + 'replies_any' => 'disallow', + 'attachment' => 'disallow', + 'children' => array(), + ); + } + elseif (isset($context['profile_groups'][$row['id_parent']])) + $context['profile_groups'][$row['id_parent']]['children'][] = $row['group_name']; + } + $smcFunc['db_free_result']($request); + + // What are the permissions we are querying? + $all_permissions = array(); + foreach ($mappings as $perm_set) + $all_permissions = array_merge($all_permissions, $perm_set); + + // If we're saving the changes then do just that - save them. + if (!empty($_POST['save_changes']) && ($context['current_profile'] == 1 || $context['current_profile'] > 4)) + { + // Start by deleting all the permissions relevant. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}board_permissions + WHERE id_profile = {int:current_profile} + AND permission IN ({array_string:permissions}) + AND id_group IN ({array_int:profile_group_list})', + array( + 'profile_group_list' => array_keys($context['profile_groups']), + 'current_profile' => $context['current_profile'], + 'permissions' => $all_permissions, + ) + ); + + // Do it group by group. + $new_permissions = array(); + foreach ($context['profile_groups'] as $id => $group) + { + foreach ($mappings as $index => $data) + { + if (isset($_POST[$index][$group['id']])) + { + if ($_POST[$index][$group['id']] == 'allow') + { + // Give them both sets for fun. + $new_permissions[] = array($context['current_profile'], $group['id'], $data[0], 1); + $new_permissions[] = array($context['current_profile'], $group['id'], $data[1], 1); + } + elseif ($_POST[$index][$group['id']] == 'moderate') + $new_permissions[] = array($context['current_profile'], $group['id'], $data[1], 1); + } + } + } + + // Insert new permissions. + if (!empty($new_permissions)) + $smcFunc['db_insert']('', + '{db_prefix}board_permissions', + array('id_profile' => 'int', 'id_group' => 'int', 'permission' => 'string', 'add_deny' => 'int'), + $new_permissions, + array('id_profile', 'id_group', 'permission') + ); + } + + // Now get all the permissions! + $request = $smcFunc['db_query']('', ' + SELECT id_group, permission, add_deny + FROM {db_prefix}board_permissions + WHERE id_profile = {int:current_profile} + AND permission IN ({array_string:permissions}) + AND id_group IN ({array_int:profile_group_list})', + array( + 'profile_group_list' => array_keys($context['profile_groups']), + 'current_profile' => $context['current_profile'], + 'permissions' => $all_permissions, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + foreach ($mappings as $key => $data) + { + foreach ($data as $index => $perm) + { + if ($perm == $row['permission']) + { + // Only bother if it's not denied. + if ($row['add_deny']) + { + // Full allowance? + if ($index == 0) + $context['profile_groups'][$row['id_group']][$key] = 'allow'; + // Otherwise only bother with moderate if not on allow. + elseif ($context['profile_groups'][$row['id_group']][$key] != 'allow') + $context['profile_groups'][$row['id_group']][$key] = 'moderate'; + } + } + } + } + } + $smcFunc['db_free_result']($request); +} + +?> \ No newline at end of file diff --git a/Sources/ManagePosts.php b/Sources/ManagePosts.php new file mode 100644 index 0000000..3f26a4b --- /dev/null +++ b/Sources/ManagePosts.php @@ -0,0 +1,372 @@ + 'ModifyPostSettings', + 'bbc' => 'ModifyBBCSettings', + 'censor' => 'SetCensor', + 'topics' => 'ModifyTopicSettings', + ); + + // Default the sub-action to 'posts'. + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'posts'; + + $context['page_title'] = $txt['manageposts_title']; + + // Tabs for browsing the different ban functions. + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['manageposts_title'], + 'help' => 'posts_and_topics', + 'description' => $txt['manageposts_description'], + 'tabs' => array( + 'posts' => array( + 'description' => $txt['manageposts_settings_description'], + ), + 'bbc' => array( + 'description' => $txt['manageposts_bbc_settings_description'], + ), + 'censor' => array( + 'description' => $txt['admin_censored_desc'], + ), + 'topics' => array( + 'description' => $txt['manageposts_topic_settings_description'], + ), + ), + ); + + // Call the right function for this sub-action. + $subActions[$_REQUEST['sa']](); +} + +// Set the censored words. +function SetCensor() +{ + global $txt, $modSettings, $context, $smcFunc; + + if (!empty($_POST['save_censor'])) + { + // Make sure censoring is something they can do. + checkSession(); + + $censored_vulgar = array(); + $censored_proper = array(); + + // Rip it apart, then split it into two arrays. + if (isset($_POST['censortext'])) + { + $_POST['censortext'] = explode("\n", strtr($_POST['censortext'], array("\r" => ''))); + + foreach ($_POST['censortext'] as $c) + list ($censored_vulgar[], $censored_proper[]) = array_pad(explode('=', trim($c)), 2, ''); + } + elseif (isset($_POST['censor_vulgar'], $_POST['censor_proper'])) + { + if (is_array($_POST['censor_vulgar'])) + { + foreach ($_POST['censor_vulgar'] as $i => $value) + { + if (trim(strtr($value, '*', ' ')) == '') + unset($_POST['censor_vulgar'][$i], $_POST['censor_proper'][$i]); + } + + $censored_vulgar = $_POST['censor_vulgar']; + $censored_proper = $_POST['censor_proper']; + } + else + { + $censored_vulgar = explode("\n", strtr($_POST['censor_vulgar'], array("\r" => ''))); + $censored_proper = explode("\n", strtr($_POST['censor_proper'], array("\r" => ''))); + } + } + + // Set the new arrays and settings in the database. + $updates = array( + 'censor_vulgar' => implode("\n", $censored_vulgar), + 'censor_proper' => implode("\n", $censored_proper), + 'censorWholeWord' => empty($_POST['censorWholeWord']) ? '0' : '1', + 'censorIgnoreCase' => empty($_POST['censorIgnoreCase']) ? '0' : '1', + ); + + updateSettings($updates); + } + + if (isset($_POST['censortest'])) + { + $censorText = htmlspecialchars($_POST['censortest'], ENT_QUOTES); + $context['censor_test'] = strtr(censorText($censorText), array('"' => '"')); + } + + // Set everything up for the template to do its thang. + $censor_vulgar = explode("\n", $modSettings['censor_vulgar']); + $censor_proper = explode("\n", $modSettings['censor_proper']); + + $context['censored_words'] = array(); + for ($i = 0, $n = count($censor_vulgar); $i < $n; $i++) + { + if (empty($censor_vulgar[$i])) + continue; + + // Skip it, it's either spaces or stars only. + if (trim(strtr($censor_vulgar[$i], '*', ' ')) == '') + continue; + + $context['censored_words'][htmlspecialchars(trim($censor_vulgar[$i]))] = isset($censor_proper[$i]) ? htmlspecialchars($censor_proper[$i]) : ''; + } + + $context['sub_template'] = 'edit_censored'; + $context['page_title'] = $txt['admin_censored_words']; +} + +// Modify all settings related to posts and posting. +function ModifyPostSettings($return_config = false) +{ + global $context, $txt, $modSettings, $scripturl, $sourcedir, $smcFunc, $db_prefix; + + // All the settings... + $config_vars = array( + // Simple post options... + array('check', 'removeNestedQuotes'), + array('check', 'enableEmbeddedFlash', 'subtext' => $txt['enableEmbeddedFlash_warning']), + // Note show the warning as read if pspell not installed! + array('check', 'enableSpellChecking', 'subtext' => (function_exists('pspell_new') ? $txt['enableSpellChecking_warning'] : ('' . $txt['enableSpellChecking_warning'] . ''))), + array('check', 'disable_wysiwyg'), + '', + // Posting limits... + array('int', 'max_messageLength', 'subtext' => $txt['max_messageLength_zero'], 'postinput' => $txt['manageposts_characters']), + array('int', 'fixLongWords', 'subtext' => $txt['fixLongWords_zero'] . ($context['utf8'] ? ' ' . $txt['fixLongWords_warning'] . '' : ''), 'postinput' => $txt['manageposts_characters']), + array('int', 'topicSummaryPosts', 'postinput' => $txt['manageposts_posts']), + '', + // Posting time limits... + array('int', 'spamWaitTime', 'postinput' => $txt['manageposts_seconds']), + array('int', 'edit_wait_time', 'postinput' => $txt['manageposts_seconds']), + array('int', 'edit_disable_time', 'subtext' => $txt['edit_disable_time_zero'], 'postinput' => $txt['manageposts_minutes']), + ); + + if ($return_config) + return $config_vars; + + // We'll want this for our easy save. + require_once($sourcedir . '/ManageServer.php'); + + // Setup the template. + $context['page_title'] = $txt['manageposts_settings']; + $context['sub_template'] = 'show_settings'; + + // Are we saving them - are we?? + if (isset($_GET['save'])) + { + checkSession(); + + // If we're changing the message length let's check the column is big enough. + if (!empty($_POST['max_messageLength']) && $_POST['max_messageLength'] != $modSettings['max_messageLength']) + { + db_extend('packages'); + + $colData = $smcFunc['db_list_columns']('{db_prefix}messages', true); + foreach ($colData as $column) + if ($column['name'] == 'body') + $body_type = $column['type']; + + $indData = $smcFunc['db_list_indexes']('{db_prefix}messages', true); + foreach ($indData as $index) + foreach ($index['columns'] as $column) + if ($column == 'body' && $index['type'] == 'fulltext') + $fulltext = true; + + if (isset($body_type) && $_POST['max_messageLength'] > 65535 && $body_type == 'text') + { + // !!! Show an error message?! + // MySQL only likes fulltext indexes on text columns... for now? + if (!empty($fulltext)) + $_POST['max_messageLength'] = 65535; + else + { + // Make it longer so we can do their limit. + $smcFunc['db_change_column']('{db_prefix}messages', 'body', array('type' => 'mediumtext')); + } + } + elseif (isset($body_type) && $_POST['max_messageLength'] <= 65535 && $body_type != 'text') + { + // Shorten the column so we can have the benefit of fulltext searching again! + $smcFunc['db_change_column']('{db_prefix}messages', 'body', array('type' => 'text')); + } + } + + saveDBSettings($config_vars); + redirectexit('action=admin;area=postsettings;sa=posts'); + } + + // Final settings... + $context['post_url'] = $scripturl . '?action=admin;area=postsettings;save;sa=posts'; + $context['settings_title'] = $txt['manageposts_settings']; + + // Prepare the settings... + prepareDBSettingContext($config_vars); +} + +// Bulletin Board Code...a lot of Bulletin Board Code. +function ModifyBBCSettings($return_config = false) +{ + global $context, $txt, $modSettings, $helptxt, $scripturl, $sourcedir; + + $config_vars = array( + // Main tweaks + array('check', 'enableBBC'), + array('check', 'enablePostHTML'), + array('check', 'autoLinkUrls'), + '', + array('bbc', 'disabledBBC'), + ); + + if ($return_config) + return $config_vars; + + // Setup the template. + require_once($sourcedir . '/ManageServer.php'); + $context['sub_template'] = 'show_settings'; + $context['page_title'] = $txt['manageposts_bbc_settings_title']; + + // Make sure we check the right tags! + $modSettings['bbc_disabled_disabledBBC'] = empty($modSettings['disabledBBC']) ? array() : explode(',', $modSettings['disabledBBC']); + + // Saving? + if (isset($_GET['save'])) + { + checkSession(); + + // Clean up the tags. + $bbcTags = array(); + foreach (parse_bbc(false) as $tag) + $bbcTags[] = $tag['tag']; + + if (!isset($_POST['disabledBBC_enabledTags'])) + $_POST['disabledBBC_enabledTags'] = array(); + elseif (!is_array($_POST['disabledBBC_enabledTags'])) + $_POST['disabledBBC_enabledTags'] = array($_POST['disabledBBC_enabledTags']); + // Work out what is actually disabled! + $_POST['disabledBBC'] = implode(',', array_diff($bbcTags, $_POST['disabledBBC_enabledTags'])); + + saveDBSettings($config_vars); + redirectexit('action=admin;area=postsettings;sa=bbc'); + } + + $context['post_url'] = $scripturl . '?action=admin;area=postsettings;save;sa=bbc'; + $context['settings_title'] = $txt['manageposts_bbc_settings_title']; + + prepareDBSettingContext($config_vars); +} + +// Function for modifying topic settings. Not very exciting. +function ModifyTopicSettings($return_config = false) +{ + global $context, $txt, $modSettings, $sourcedir, $scripturl; + + // Here are all the topic settings. + $config_vars = array( + // Some simple bools... + array('check', 'enableStickyTopics'), + array('check', 'enableParticipation'), + '', + // Pagination etc... + array('int', 'oldTopicDays', 'postinput' => $txt['manageposts_days'], 'subtext' => $txt['oldTopicDays_zero']), + array('int', 'defaultMaxTopics', 'postinput' => $txt['manageposts_topics']), + array('int', 'defaultMaxMessages', 'postinput' => $txt['manageposts_posts']), + '', + // Hot topics (etc)... + array('int', 'hotTopicPosts', 'postinput' => $txt['manageposts_posts']), + array('int', 'hotTopicVeryPosts', 'postinput' => $txt['manageposts_posts']), + '', + // All, next/prev... + array('int', 'enableAllMessages', 'postinput' => $txt['manageposts_posts'], 'subtext' => $txt['enableAllMessages_zero']), + array('check', 'disableCustomPerPage'), + array('check', 'enablePreviousNext'), + + ); + + if ($return_config) + return $config_vars; + + // Get the settings template ready. + require_once($sourcedir . '/ManageServer.php'); + + // Setup the template. + $context['page_title'] = $txt['manageposts_topic_settings']; + $context['sub_template'] = 'show_settings'; + + // Are we saving them - are we?? + if (isset($_GET['save'])) + { + checkSession(); + + saveDBSettings($config_vars); + redirectexit('action=admin;area=postsettings;sa=topics'); + } + + // Final settings... + $context['post_url'] = $scripturl . '?action=admin;area=postsettings;save;sa=topics'; + $context['settings_title'] = $txt['manageposts_topic_settings']; + + // Prepare the settings... + prepareDBSettingContext($config_vars); +} + +?> \ No newline at end of file diff --git a/Sources/ManageRegistration.php b/Sources/ManageRegistration.php new file mode 100644 index 0000000..fba1c88 --- /dev/null +++ b/Sources/ManageRegistration.php @@ -0,0 +1,330 @@ + array('AdminRegister', 'moderate_forum'), + 'agreement' => array('EditAgreement', 'admin_forum'), + 'reservednames' => array('SetReserve', 'admin_forum'), + 'settings' => array('ModifyRegistrationSettings', 'admin_forum'), + ); + + // Work out which to call... + $context['sub_action'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : (allowedTo('moderate_forum') ? 'register' : 'settings'); + + // Must have sufficient permissions. + isAllowedTo($subActions[$context['sub_action']][1]); + + // Loading, always loading. + loadLanguage('Login'); + loadTemplate('Register'); + + // Next create the tabs for the template. + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['registration_center'], + 'help' => 'registrations', + 'description' => $txt['admin_settings_desc'], + 'tabs' => array( + 'register' => array( + 'description' => $txt['admin_register_desc'], + ), + 'agreement' => array( + 'description' => $txt['registration_agreement_desc'], + ), + 'reservednames' => array( + 'description' => $txt['admin_reserved_desc'], + ), + 'settings' => array( + 'description' => $txt['admin_settings_desc'], + ) + ) + ); + + // Finally, get around to calling the function... + $subActions[$context['sub_action']][0](); +} + +// This function allows the admin to register a new member by hand. +function AdminRegister() +{ + global $txt, $context, $sourcedir, $scripturl, $smcFunc; + + if (!empty($_POST['regSubmit'])) + { + checkSession(); + + foreach ($_POST as $key => $value) + if (!is_array($_POST[$key])) + $_POST[$key] = htmltrim__recursive(str_replace(array("\n", "\r"), '', $_POST[$key])); + + $regOptions = array( + 'interface' => 'admin', + 'username' => $_POST['user'], + 'email' => $_POST['email'], + 'password' => $_POST['password'], + 'password_check' => $_POST['password'], + 'check_reserved_name' => true, + 'check_password_strength' => false, + 'check_email_ban' => false, + 'send_welcome_email' => isset($_POST['emailPassword']) || empty($_POST['password']), + 'require' => isset($_POST['emailActivate']) ? 'activation' : 'nothing', + 'memberGroup' => empty($_POST['group']) || !allowedTo('manage_membergroups') ? 0 : (int) $_POST['group'], + ); + + require_once($sourcedir . '/Subs-Members.php'); + $memberID = registerMember($regOptions); + if (!empty($memberID)) + { + $context['new_member'] = array( + 'id' => $memberID, + 'name' => $_POST['user'], + 'href' => $scripturl . '?action=profile;u=' . $memberID, + 'link' => '' . $_POST['user'] . '', + ); + $context['registration_done'] = sprintf($txt['admin_register_done'], $context['new_member']['link']); + } + } + + // Basic stuff. + $context['sub_template'] = 'admin_register'; + $context['page_title'] = $txt['registration_center']; + + // Load the assignable member groups. + if (allowedTo('manage_membergroups')) + { + $request = $smcFunc['db_query']('', ' + SELECT group_name, id_group + FROM {db_prefix}membergroups + WHERE id_group != {int:moderator_group} + AND min_posts = {int:min_posts}' . (allowedTo('admin_forum') ? '' : ' + AND id_group != {int:admin_group} + AND group_type != {int:is_protected}') . ' + AND hidden != {int:hidden_group} + ORDER BY min_posts, CASE WHEN id_group < {int:newbie_group} THEN id_group ELSE 4 END, group_name', + array( + 'moderator_group' => 3, + 'min_posts' => -1, + 'admin_group' => 1, + 'is_protected' => 1, + 'hidden_group' => 2, + 'newbie_group' => 4, + ) + ); + $context['member_groups'] = array(0 => $txt['admin_register_group_none']); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['member_groups'][$row['id_group']] = $row['group_name']; + $smcFunc['db_free_result']($request); + } + else + $context['member_groups'] = array(); +} + +// I hereby agree not to be a lazy bum. +function EditAgreement() +{ + global $txt, $boarddir, $context, $modSettings, $smcFunc, $settings; + + // By default we look at agreement.txt. + $context['current_agreement'] = ''; + + // Is there more than one to edit? + $context['editable_agreements'] = array( + '' => $txt['admin_agreement_default'], + ); + + // Get our languages. + getLanguages(); + + // Try to figure out if we have more agreements. + foreach ($context['languages'] as $lang) + { + if (file_exists($boarddir . '/agreement.' . $lang['filename'] . '.txt')) + { + $context['editable_agreements']['.' . $lang['filename']] = $lang['name']; + // Are we editing this? + if (isset($_POST['agree_lang']) && $_POST['agree_lang'] == '.' . $lang['filename']) + $context['current_agreement'] = '.' . $lang['filename']; + } + } + + if (isset($_POST['agreement'])) + { + checkSession(); + + // Off it goes to the agreement file. + $fp = fopen($boarddir . '/agreement' . $context['current_agreement'] . '.txt', 'w'); + fwrite($fp, str_replace("\r", '', $_POST['agreement'])); + fclose($fp); + + updateSettings(array('requireAgreement' => !empty($_POST['requireAgreement']))); + } + + $context['agreement'] = file_exists($boarddir . '/agreement' . $context['current_agreement'] . '.txt') ? htmlspecialchars(file_get_contents($boarddir . '/agreement' . $context['current_agreement'] . '.txt')) : ''; + $context['warning'] = is_writable($boarddir . '/agreement' . $context['current_agreement'] . '.txt') ? '' : $txt['agreement_not_writable']; + $context['require_agreement'] = !empty($modSettings['requireAgreement']); + + $context['sub_template'] = 'edit_agreement'; + $context['page_title'] = $txt['registration_agreement']; +} + +// Set reserved names/words.... +function SetReserve() +{ + global $txt, $context, $modSettings; + + // Submitting new reserved words. + if (!empty($_POST['save_reserved_names'])) + { + checkSession(); + + // Set all the options.... + updateSettings(array( + 'reserveWord' => (isset($_POST['matchword']) ? '1' : '0'), + 'reserveCase' => (isset($_POST['matchcase']) ? '1' : '0'), + 'reserveUser' => (isset($_POST['matchuser']) ? '1' : '0'), + 'reserveName' => (isset($_POST['matchname']) ? '1' : '0'), + 'reserveNames' => str_replace("\r", '', $_POST['reserved']) + )); + } + + // Get the reserved word options and words. + $modSettings['reserveNames'] = str_replace('\n', "\n", $modSettings['reserveNames']); + $context['reserved_words'] = explode("\n", $modSettings['reserveNames']); + $context['reserved_word_options'] = array(); + $context['reserved_word_options']['match_word'] = $modSettings['reserveWord'] == '1'; + $context['reserved_word_options']['match_case'] = $modSettings['reserveCase'] == '1'; + $context['reserved_word_options']['match_user'] = $modSettings['reserveUser'] == '1'; + $context['reserved_word_options']['match_name'] = $modSettings['reserveName'] == '1'; + + // Ready the template...... + $context['sub_template'] = 'edit_reserved_words'; + $context['page_title'] = $txt['admin_reserved_set']; +} + +// This function handles registration settings, and provides a few pretty stats too while it's at it. +function ModifyRegistrationSettings($return_config = false) +{ + global $txt, $context, $scripturl, $modSettings, $sourcedir; + + // This is really quite wanting. + require_once($sourcedir . '/ManageServer.php'); + + $config_vars = array( + array('select', 'registration_method', array($txt['setting_registration_standard'], $txt['setting_registration_activate'], $txt['setting_registration_approval'], $txt['setting_registration_disabled'])), + array('check', 'enableOpenID'), + array('check', 'notify_new_registration'), + array('check', 'send_welcomeEmail'), + '', + array('int', 'coppaAge', 'subtext' => $txt['setting_coppaAge_desc'], 'onchange' => 'checkCoppa();'), + array('select', 'coppaType', array($txt['setting_coppaType_reject'], $txt['setting_coppaType_approval']), 'onchange' => 'checkCoppa();'), + array('large_text', 'coppaPost', 'subtext' => $txt['setting_coppaPost_desc']), + array('text', 'coppaFax'), + array('text', 'coppaPhone'), + ); + + if ($return_config) + return $config_vars; + + // Setup the template + $context['sub_template'] = 'show_settings'; + $context['page_title'] = $txt['registration_center']; + + if (isset($_GET['save'])) + { + checkSession(); + + // Are there some contacts missing? + if (!empty($_POST['coppaAge']) && !empty($_POST['coppaType']) && empty($_POST['coppaPost']) && empty($_POST['coppaFax'])) + fatal_lang_error('admin_setting_coppa_require_contact'); + + // Post needs to take into account line breaks. + $_POST['coppaPost'] = str_replace("\n", '
', empty($_POST['coppaPost']) ? '' : $_POST['coppaPost']); + + saveDBSettings($config_vars); + + redirectexit('action=admin;area=regcenter;sa=settings'); + } + + $context['post_url'] = $scripturl . '?action=admin;area=regcenter;save;sa=settings'; + $context['settings_title'] = $txt['settings']; + + // Define some javascript for COPPA. + $context['settings_post_javascript'] = ' + function checkCoppa() + { + var coppaDisabled = document.getElementById(\'coppaAge\').value == 0; + document.getElementById(\'coppaType\').disabled = coppaDisabled; + + var disableContacts = coppaDisabled || document.getElementById(\'coppaType\').options[document.getElementById(\'coppaType\').selectedIndex].value != 1; + document.getElementById(\'coppaPost\').disabled = disableContacts; + document.getElementById(\'coppaFax\').disabled = disableContacts; + document.getElementById(\'coppaPhone\').disabled = disableContacts; + } + checkCoppa();'; + + // Turn the postal address into something suitable for a textbox. + $modSettings['coppaPost'] = !empty($modSettings['coppaPost']) ? preg_replace('~
~', "\n", $modSettings['coppaPost']) : ''; + + prepareDBSettingContext($config_vars); +} + +?> \ No newline at end of file diff --git a/Sources/ManageScheduledTasks.php b/Sources/ManageScheduledTasks.php new file mode 100644 index 0000000..f80e192 --- /dev/null +++ b/Sources/ManageScheduledTasks.php @@ -0,0 +1,558 @@ + 'EditTask', + 'tasklog' => 'TaskLog', + 'tasks' => 'ScheduledTasks', + ); + + // We need to find what's the action. + if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']])) + $context['sub_action'] = $_REQUEST['sa']; + else + $context['sub_action'] = 'tasks'; + + // Now for the lovely tabs. That we all love. + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['scheduled_tasks_title'], + 'help' => '', + 'description' => $txt['maintain_info'], + 'tabs' => array( + 'tasks' => array( + 'description' => $txt['maintain_tasks_desc'], + ), + 'tasklog' => array( + 'description' => $txt['scheduled_log_desc'], + ), + ), + ); + + // Call it. + $subActions[$context['sub_action']](); +} + +// List all the scheduled task in place on the forum. +function ScheduledTasks() +{ + global $context, $txt, $sourcedir, $smcFunc, $user_info, $modSettings, $scripturl; + + // Mama, setup the template first - cause it's like the most important bit, like pickle in a sandwich. + // ... ironically I don't like pickle. + $context['sub_template'] = 'view_scheduled_tasks'; + $context['page_title'] = $txt['maintain_tasks']; + + // Saving changes? + if (isset($_REQUEST['save']) && isset($_POST['enable_task'])) + { + checkSession(); + + // We'll recalculate the dates at the end! + require_once($sourcedir . '/ScheduledTasks.php'); + + // Enable and disable as required. + $enablers = array(0); + foreach ($_POST['enable_task'] as $id => $enabled) + if ($enabled) + $enablers[] = (int) $id; + + // Do the update! + $smcFunc['db_query']('', ' + UPDATE {db_prefix}scheduled_tasks + SET disabled = CASE WHEN id_task IN ({array_int:id_task_enable}) THEN 0 ELSE 1 END', + array( + 'id_task_enable' => $enablers, + ) + ); + + // Pop along... + CalculateNextTrigger(); + } + + // Want to run any of the tasks? + if (isset($_REQUEST['run']) && isset($_POST['run_task'])) + { + // Lets figure out which ones they want to run. + $tasks = array(); + foreach ($_POST['run_task'] as $task => $dummy) + $tasks[] = (int) $task; + + // Load up the tasks. + $request = $smcFunc['db_query']('', ' + SELECT id_task, task + FROM {db_prefix}scheduled_tasks + WHERE id_task IN ({array_int:tasks}) + LIMIT ' . count($tasks), + array( + 'tasks' => $tasks, + ) + ); + + // Lets get it on! + require_once($sourcedir . '/ScheduledTasks.php'); + ignore_user_abort(true); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $start_time = microtime(); + // The functions got to exist for us to use it. + if (!function_exists('scheduled_' . $row['task'])) + continue; + + // Try to stop a timeout, this would be bad... + @set_time_limit(300); + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + // Do the task... + $completed = call_user_func('scheduled_' . $row['task']); + + // Log that we did it ;) + if ($completed) + { + $total_time = round(array_sum(explode(' ', microtime())) - array_sum(explode(' ', $start_time)), 3); + $smcFunc['db_insert']('', + '{db_prefix}log_scheduled_tasks', + array('id_task' => 'int', 'time_run' => 'int', 'time_taken' => 'float'), + array($row['id_task'], time(), $total_time), + array('id_task') + ); + } + } + $smcFunc['db_free_result']($request); + redirectexit('action=admin;area=scheduledtasks;done'); + } + + $listOptions = array( + 'id' => 'scheduled_tasks', + 'title' => $txt['maintain_tasks'], + 'base_href' => $scripturl . '?action=admin;area=scheduledtasks', + 'get_items' => array( + 'function' => 'list_getScheduledTasks', + ), + 'columns' => array( + 'name' => array( + 'header' => array( + 'value' => $txt['scheduled_tasks_name'], + 'style' => 'width: 40%;', + ), + 'data' => array( + 'sprintf' => array( + 'format' => ' + %2$s
%3$s', + 'params' => array( + 'id' => false, + 'name' => false, + 'desc' => false, + ), + ), + ), + ), + 'next_due' => array( + 'header' => array( + 'value' => $txt['scheduled_tasks_next_time'], + ), + 'data' => array( + 'db' => 'next_time', + 'class' => 'smalltext', + ), + ), + 'regularity' => array( + 'header' => array( + 'value' => $txt['scheduled_tasks_regularity'], + ), + 'data' => array( + 'db' => 'regularity', + 'class' => 'smalltext', + ), + ), + 'enabled' => array( + 'header' => array( + 'value' => $txt['scheduled_tasks_enabled'], + 'style' => 'width: 6%;', + ), + 'data' => array( + 'sprintf' => array( + 'format' => + '', + 'params' => array( + 'id' => false, + 'checked_state' => false, + ), + ), + 'style' => 'text-align: center;', + ), + ), + 'run_now' => array( + 'header' => array( + 'value' => $txt['scheduled_tasks_run_now'], + 'style' => 'width: 12%;', + ), + 'data' => array( + 'sprintf' => array( + 'format' => + '', + 'params' => array( + 'id' => false, + ), + ), + 'style' => 'text-align: center;', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=scheduledtasks', + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => ' + + ', + 'class' => 'floatright', + 'style' => 'text-align: right;', + ), + array( + 'position' => 'after_title', + 'value' => ' + ' . $txt['scheduled_tasks_time_offset'] . '', + 'class' => 'windowbg2', + ), + ), + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + $context['sub_template'] = 'view_scheduled_tasks'; + + $context['tasks_were_run'] = isset($_GET['done']); +} + +function list_getScheduledTasks($start, $items_per_page, $sort) +{ + global $smcFunc, $txt, $scripturl; + + $request = $smcFunc['db_query']('', ' + SELECT id_task, next_time, time_offset, time_regularity, time_unit, disabled, task + FROM {db_prefix}scheduled_tasks', + array( + ) + ); + $known_tasks = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Find the next for regularity - don't offset as it's always server time! + $offset = sprintf($txt['scheduled_task_reg_starting'], date('H:i', $row['time_offset'])); + $repeating = sprintf($txt['scheduled_task_reg_repeating'], $row['time_regularity'], $txt['scheduled_task_reg_unit_' . $row['time_unit']]); + + $known_tasks[] = array( + 'id' => $row['id_task'], + 'function' => $row['task'], + 'name' => isset($txt['scheduled_task_' . $row['task']]) ? $txt['scheduled_task_' . $row['task']] : $row['task'], + 'desc' => isset($txt['scheduled_task_desc_' . $row['task']]) ? $txt['scheduled_task_desc_' . $row['task']] : '', + 'next_time' => $row['disabled'] ? $txt['scheduled_tasks_na'] : timeformat(($row['next_time'] == 0 ? time() : $row['next_time']), true, 'server'), + 'disabled' => $row['disabled'], + 'checked_state' => $row['disabled'] ? '' : 'checked="checked"', + 'regularity' => $offset . ', ' . $repeating, + ); + } + $smcFunc['db_free_result']($request); + + return $known_tasks; +} + +// Function for editing a task. +function EditTask() +{ + global $context, $txt, $sourcedir, $smcFunc, $user_info, $modSettings; + + // Just set up some lovely context stuff. + $context[$context['admin_menu_name']]['current_subsection'] = 'tasks'; + $context['sub_template'] = 'edit_scheduled_tasks'; + $context['page_title'] = $txt['scheduled_task_edit']; + $context['server_time'] = timeformat(time(), false, 'server'); + + // Cleaning... + if (!isset($_GET['tid'])) + fatal_lang_error('no_access', false); + $_GET['tid'] = (int) $_GET['tid']; + + // Saving? + if (isset($_GET['save'])) + { + checkSession(); + + // We'll need this for calculating the next event. + require_once($sourcedir . '/ScheduledTasks.php'); + + // Do we have a valid offset? + preg_match('~(\d{1,2}):(\d{1,2})~', $_POST['offset'], $matches); + + // If a half is empty then assume zero offset! + if (!isset($matches[2]) || $matches[2] > 59) + $matches[2] = 0; + if (!isset($matches[1]) || $matches[1] > 23) + $matches[1] = 0; + + // Now the offset is easy; easy peasy - except we need to offset by a few hours... + $offset = $matches[1] * 3600 + $matches[2] * 60 - date('Z'); + + // The other time bits are simple! + $interval = max((int) $_POST['regularity'], 1); + $unit = in_array(substr($_POST['unit'], 0, 1), array('m', 'h', 'd', 'w')) ? substr($_POST['unit'], 0, 1) : 'd'; + + // Don't allow one minute intervals. + if ($interval == 1 && $unit == 'm') + $interval = 2; + + // Is it disabled? + $disabled = !isset($_POST['enabled']) ? 1 : 0; + + // Do the update! + $smcFunc['db_query']('', ' + UPDATE {db_prefix}scheduled_tasks + SET disabled = {int:disabled}, time_offset = {int:time_offset}, time_unit = {string:time_unit}, + time_regularity = {int:time_regularity} + WHERE id_task = {int:id_task}', + array( + 'disabled' => $disabled, + 'time_offset' => $offset, + 'time_regularity' => $interval, + 'id_task' => $_GET['tid'], + 'time_unit' => $unit, + ) + ); + + // Check the next event. + CalculateNextTrigger($_GET['tid'], true); + + // Return to the main list. + redirectexit('action=admin;area=scheduledtasks'); + } + + // Load the task, understand? Que? Que? + $request = $smcFunc['db_query']('', ' + SELECT id_task, next_time, time_offset, time_regularity, time_unit, disabled, task + FROM {db_prefix}scheduled_tasks + WHERE id_task = {int:id_task}', + array( + 'id_task' => $_GET['tid'], + ) + ); + + // Should never, ever, happen! + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_access', false); + + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $context['task'] = array( + 'id' => $row['id_task'], + 'function' => $row['task'], + 'name' => isset($txt['scheduled_task_' . $row['task']]) ? $txt['scheduled_task_' . $row['task']] : $row['task'], + 'desc' => isset($txt['scheduled_task_desc_' . $row['task']]) ? $txt['scheduled_task_desc_' . $row['task']] : '', + 'next_time' => $row['disabled'] ? $txt['scheduled_tasks_na'] : timeformat($row['next_time'] == 0 ? time() : $row['next_time'], true, 'server'), + 'disabled' => $row['disabled'], + 'offset' => $row['time_offset'], + 'regularity' => $row['time_regularity'], + 'offset_formatted' => date('H:i', $row['time_offset']), + 'unit' => $row['time_unit'], + ); + } + $smcFunc['db_free_result']($request); +} + +// Show the log of all tasks that have taken place. +function TaskLog() +{ + global $scripturl, $context, $txt, $smcFunc, $sourcedir; + + // Lets load the language just incase we are outside the Scheduled area. + loadLanguage('ManageScheduledTasks'); + + // Empty the log? + if (!empty($_POST['removeAll'])) + { + checkSession(); + + $smcFunc['db_query']('truncate_table', ' + TRUNCATE {db_prefix}log_scheduled_tasks', + array( + ) + ); + } + + // Setup the list. + $listOptions = array( + 'id' => 'task_log', + 'items_per_page' => 30, + 'title' => $txt['scheduled_log'], + 'no_items_label' => $txt['scheduled_log_empty'], + 'base_href' => $context['admin_area'] == 'scheduledtasks' ? $scripturl . '?action=admin;area=scheduledtasks;sa=tasklog' : $scripturl . '?action=admin;area=logs;sa=tasklog', + 'default_sort_col' => 'date', + 'get_items' => array( + 'function' => 'list_getTaskLogEntries', + ), + 'get_count' => array( + 'function' => 'list_getNumTaskLogEntries', + ), + 'columns' => array( + 'name' => array( + 'header' => array( + 'value' => $txt['scheduled_tasks_name'], + ), + 'data' => array( + 'db' => 'name' + ), + ), + 'date' => array( + 'header' => array( + 'value' => $txt['scheduled_log_time_run'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + return timeformat($rowData[\'time_run\'], true); + '), + ), + 'sort' => array( + 'default' => 'lst.id_log DESC', + 'reverse' => 'lst.id_log', + ), + ), + 'time_taken' => array( + 'header' => array( + 'value' => $txt['scheduled_log_time_taken'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => $txt['scheduled_log_time_taken_seconds'], + 'params' => array( + 'time_taken' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'lst.time_taken', + 'reverse' => 'lst.time_taken DESC', + ), + ), + ), + 'form' => array( + 'href' => $context['admin_area'] == 'scheduledtasks' ? $scripturl . '?action=admin;area=scheduledtasks;sa=tasklog' : $scripturl . '?action=admin;area=logs;sa=tasklog', + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => ' + ', + 'style' => 'text-align: right;', + ), + array( + 'position' => 'after_title', + 'value' => $txt['scheduled_tasks_time_offset'], + 'class' => 'smalltext', + ), + ), + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'task_log'; + + // Make it all look tify. + $context[$context['admin_menu_name']]['current_subsection'] = 'tasklog'; + $context['page_title'] = $txt['scheduled_log']; +} + +function list_getTaskLogEntries($start, $items_per_page, $sort) +{ + global $smcFunc, $txt; + + $request = $smcFunc['db_query']('', ' + SELECT lst.id_log, lst.id_task, lst.time_run, lst.time_taken, st.task + FROM {db_prefix}log_scheduled_tasks AS lst + INNER JOIN {db_prefix}scheduled_tasks AS st ON (st.id_task = lst.id_task) + ORDER BY ' . $sort . ' + LIMIT ' . $start . ', ' . $items_per_page, + array( + ) + ); + $log_entries = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $log_entries[] = array( + 'id' => $row['id_log'], + 'name' => isset($txt['scheduled_task_' . $row['task']]) ? $txt['scheduled_task_' . $row['task']] : $row['task'], + 'time_run' => $row['time_run'], + 'time_taken' => $row['time_taken'], + ); + $smcFunc['db_free_result']($request); + + return $log_entries; +} + +function list_getNumTaskLogEntries() +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_scheduled_tasks', + array( + ) + ); + list ($num_entries) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $num_entries; +} + +?> \ No newline at end of file diff --git a/Sources/ManageSearch.php b/Sources/ManageSearch.php new file mode 100644 index 0000000..689674a --- /dev/null +++ b/Sources/ManageSearch.php @@ -0,0 +1,775 @@ + 'EditSearchSettings', + 'weights' => 'EditWeights', + 'method' => 'EditSearchMethod', + 'createfulltext' => 'EditSearchMethod', + 'removecustom' => 'EditSearchMethod', + 'removefulltext' => 'EditSearchMethod', + 'createmsgindex' => 'CreateMessageIndex', + ); + + // Default the sub-action to 'edit search settings'. + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'weights'; + + $context['sub_action'] = $_REQUEST['sa']; + + // Create the tabs for the template. + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['manage_search'], + 'help' => 'search', + 'description' => $txt['search_settings_desc'], + 'tabs' => array( + 'weights' => array( + 'description' => $txt['search_weights_desc'], + ), + 'method' => array( + 'description' => $txt['search_method_desc'], + ), + 'settings' => array( + 'description' => $txt['search_settings_desc'], + ), + ), + ); + + // Call the right function for this sub-acton. + $subActions[$_REQUEST['sa']](); +} + +function EditSearchSettings($return_config = false) +{ + global $txt, $context, $scripturl, $sourcedir, $modSettings; + + // What are we editing anyway? + $config_vars = array( + // Permission... + array('permissions', 'search_posts'), + // Some simple settings. + array('check', 'simpleSearch'), + array('int', 'search_results_per_page'), + array('int', 'search_max_results', 'subtext' => $txt['search_max_results_disable']), + '', + // Some limitations. + array('int', 'search_floodcontrol_time', 'subtext' => $txt['search_floodcontrol_time_desc']), + ); + + // Perhaps the search method wants to add some settings? + $modSettings['search_index'] = empty($modSettings['search_index']) ? 'standard' : $modSettings['search_index']; + if (file_exists($sourcedir . '/SearchAPI-' . ucwords($modSettings['search_index']) . '.php')) + { + loadClassFile('SearchAPI-' . ucwords($modSettings['search_index']) . '.php'); + + $method_call = array($modSettings['search_index'] . '_search', 'searchSettings'); + if (is_callable($method_call)) + call_user_func_array($method_call, array(&$config_vars)); + } + + if ($return_config) + return $config_vars; + + $context['page_title'] = $txt['search_settings_title']; + $context['sub_template'] = 'show_settings'; + + // We'll need this for the settings. + require_once($sourcedir . '/ManageServer.php'); + + // A form was submitted. + if (isset($_REQUEST['save'])) + { + checkSession(); + + saveDBSettings($config_vars); + redirectexit('action=admin;area=managesearch;sa=settings;' . $context['session_var'] . '=' . $context['session_id']); + } + + // Prep the template! + $context['post_url'] = $scripturl . '?action=admin;area=managesearch;save;sa=settings'; + $context['settings_title'] = $txt['search_settings_title']; + + prepareDBSettingContext($config_vars); +} + +function EditWeights() +{ + global $txt, $context, $modSettings; + + $context['page_title'] = $txt['search_weights_title']; + $context['sub_template'] = 'modify_weights'; + + $factors = array( + 'search_weight_frequency', + 'search_weight_age', + 'search_weight_length', + 'search_weight_subject', + 'search_weight_first_message', + 'search_weight_sticky', + ); + + // A form was submitted. + if (isset($_POST['save'])) + { + checkSession(); + + $changes = array(); + foreach ($factors as $factor) + $changes[$factor] = (int) $_POST[$factor]; + updateSettings($changes); + } + + $context['relative_weights'] = array('total' => 0); + foreach ($factors as $factor) + $context['relative_weights']['total'] += isset($modSettings[$factor]) ? $modSettings[$factor] : 0; + + foreach ($factors as $factor) + $context['relative_weights'][$factor] = round(100 * (isset($modSettings[$factor]) ? $modSettings[$factor] : 0) / $context['relative_weights']['total'], 1); +} + +function EditSearchMethod() +{ + global $txt, $context, $modSettings, $smcFunc, $db_type, $db_prefix; + + $context[$context['admin_menu_name']]['current_subsection'] = 'method'; + $context['page_title'] = $txt['search_method_title']; + $context['sub_template'] = 'select_search_method'; + $context['supports_fulltext'] = $smcFunc['db_search_support']('fulltext'); + + // Load any apis. + $context['search_apis'] = loadSearchAPIs(); + + // Detect whether a fulltext index is set. + if ($context['supports_fulltext']) + { + $request = $smcFunc['db_query']('', ' + SHOW INDEX + FROM {db_prefix}messages', + array( + ) + ); + $context['fulltext_index'] = ''; + if ($request !== false || $smcFunc['db_num_rows']($request) != 0) + { + while ($row = $smcFunc['db_fetch_assoc']($request)) + if ($row['Column_name'] == 'body' && (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT' || isset($row['Comment']) && $row['Comment'] == 'FULLTEXT')) + $context['fulltext_index'][] = $row['Key_name']; + $smcFunc['db_free_result']($request); + + if (is_array($context['fulltext_index'])) + $context['fulltext_index'] = array_unique($context['fulltext_index']); + } + + $request = $smcFunc['db_query']('', ' + SHOW COLUMNS + FROM {db_prefix}messages', + array( + ) + ); + if ($request !== false) + { + while ($row = $smcFunc['db_fetch_assoc']($request)) + if ($row['Field'] == 'body' && $row['Type'] == 'mediumtext') + $context['cannot_create_fulltext'] = true; + $smcFunc['db_free_result']($request); + } + + if (preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) !== 0) + $request = $smcFunc['db_query']('', ' + SHOW TABLE STATUS + FROM {string:database_name} + LIKE {string:table_name}', + array( + 'database_name' => '`' . strtr($match[1], array('`' => '')) . '`', + 'table_name' => str_replace('_', '\_', $match[2]) . 'messages', + ) + ); + else + $request = $smcFunc['db_query']('', ' + SHOW TABLE STATUS + LIKE {string:table_name}', + array( + 'table_name' => str_replace('_', '\_', $db_prefix) . 'messages', + ) + ); + + if ($request !== false) + { + while ($row = $smcFunc['db_fetch_assoc']($request)) + if ((isset($row['Type']) && strtolower($row['Type']) != 'myisam') || (isset($row['Engine']) && strtolower($row['Engine']) != 'myisam')) + $context['cannot_create_fulltext'] = true; + $smcFunc['db_free_result']($request); + } + } + + if (!empty($_REQUEST['sa']) && $_REQUEST['sa'] == 'createfulltext') + { + checkSession('get'); + + // Make sure it's gone before creating it. + $smcFunc['db_query']('', ' + ALTER TABLE {db_prefix}messages + DROP INDEX body', + array( + 'db_error_skip' => true, + ) + ); + + $smcFunc['db_query']('', ' + ALTER TABLE {db_prefix}messages + ADD FULLTEXT body (body)', + array( + ) + ); + + $context['fulltext_index'] = 'body'; + } + elseif (!empty($_REQUEST['sa']) && $_REQUEST['sa'] == 'removefulltext' && !empty($context['fulltext_index'])) + { + checkSession('get'); + + $smcFunc['db_query']('', ' + ALTER TABLE {db_prefix}messages + DROP INDEX ' . implode(', + DROP INDEX ', $context['fulltext_index']), + array( + 'db_error_skip' => true, + ) + ); + + $context['fulltext_index'] = ''; + + // Go back to the default search method. + if (!empty($modSettings['search_index']) && $modSettings['search_index'] == 'fulltext') + updateSettings(array( + 'search_index' => '', + )); + } + elseif (!empty($_REQUEST['sa']) && $_REQUEST['sa'] == 'removecustom') + { + checkSession('get'); + + db_extend(); + $tables = $smcFunc['db_list_tables'](false, $db_prefix . 'log_search_words'); + if (!empty($tables)) + { + $smcFunc['db_search_query']('drop_words_table', ' + DROP TABLE {db_prefix}log_search_words', + array( + ) + ); + } + + updateSettings(array( + 'search_custom_index_config' => '', + 'search_custom_index_resume' => '', + )); + + // Go back to the default search method. + if (!empty($modSettings['search_index']) && $modSettings['search_index'] == 'custom') + updateSettings(array( + 'search_index' => '', + )); + } + elseif (isset($_POST['save'])) + { + checkSession(); + updateSettings(array( + 'search_index' => empty($_POST['search_index']) || (!in_array($_POST['search_index'], array('fulltext', 'custom')) && !isset($context['search_apis'][$_POST['search_index']])) ? '' : $_POST['search_index'], + 'search_force_index' => isset($_POST['search_force_index']) ? '1' : '0', + 'search_match_words' => isset($_POST['search_match_words']) ? '1' : '0', + )); + } + + $context['table_info'] = array( + 'data_length' => 0, + 'index_length' => 0, + 'fulltext_length' => 0, + 'custom_index_length' => 0, + ); + + // Get some info about the messages table, to show its size and index size. + if ($db_type == 'mysql') + { + if (preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) !== 0) + $request = $smcFunc['db_query']('', ' + SHOW TABLE STATUS + FROM {string:database_name} + LIKE {string:table_name}', + array( + 'database_name' => '`' . strtr($match[1], array('`' => '')) . '`', + 'table_name' => str_replace('_', '\_', $match[2]) . 'messages', + ) + ); + else + $request = $smcFunc['db_query']('', ' + SHOW TABLE STATUS + LIKE {string:table_name}', + array( + 'table_name' => str_replace('_', '\_', $db_prefix) . 'messages', + ) + ); + if ($request !== false && $smcFunc['db_num_rows']($request) == 1) + { + // Only do this if the user has permission to execute this query. + $row = $smcFunc['db_fetch_assoc']($request); + $context['table_info']['data_length'] = $row['Data_length']; + $context['table_info']['index_length'] = $row['Index_length']; + $context['table_info']['fulltext_length'] = $row['Index_length']; + $smcFunc['db_free_result']($request); + } + + // Now check the custom index table, if it exists at all. + if (preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) !== 0) + $request = $smcFunc['db_query']('', ' + SHOW TABLE STATUS + FROM {string:database_name} + LIKE {string:table_name}', + array( + 'database_name' => '`' . strtr($match[1], array('`' => '')) . '`', + 'table_name' => str_replace('_', '\_', $match[2]) . 'log_search_words', + ) + ); + else + $request = $smcFunc['db_query']('', ' + SHOW TABLE STATUS + LIKE {string:table_name}', + array( + 'table_name' => str_replace('_', '\_', $db_prefix) . 'log_search_words', + ) + ); + if ($request !== false && $smcFunc['db_num_rows']($request) == 1) + { + // Only do this if the user has permission to execute this query. + $row = $smcFunc['db_fetch_assoc']($request); + $context['table_info']['index_length'] += $row['Data_length'] + $row['Index_length']; + $context['table_info']['custom_index_length'] = $row['Data_length'] + $row['Index_length']; + $smcFunc['db_free_result']($request); + } + } + elseif ($db_type == 'postgresql') + { + // In order to report the sizes correctly we need to perform vacuum (optimize) on the tables we will be using. + db_extend(); + $temp_tables = $smcFunc['db_list_tables'](); + foreach ($temp_tables as $table) + if ($table == $db_prefix. 'messages' || $table == $db_prefix. 'log_search_words') + $smcFunc['db_optimize_table']($table); + + // PostGreSql has some hidden sizes. + $request = $smcFunc['db_query']('', ' + SELECT relname, relpages * 8 *1024 AS "KB" FROM pg_class + WHERE relname = {string:messages} OR relname = {string:log_search_words} + ORDER BY relpages DESC', + array( + 'messages' => $db_prefix. 'messages', + 'log_search_words' => $db_prefix. 'log_search_words', + ) + ); + + if ($request !== false && $smcFunc['db_num_rows']($request) > 0) + { + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['relname'] == $db_prefix . 'messages') + { + $context['table_info']['data_length'] = (int) $row['KB']; + $context['table_info']['index_length'] = (int) $row['KB']; + // Doesn't support fulltext + $context['table_info']['fulltext_length'] = $txt['not_applicable']; + } + elseif ($row['relname'] == $db_prefix. 'log_search_words') + { + $context['table_info']['index_length'] = (int) $row['KB']; + $context['table_info']['custom_index_length'] = (int) $row['KB']; + } + } + $smcFunc['db_free_result']($request); + } + else + // Didn't work for some reason... + $context['table_info'] = array( + 'data_length' => $txt['not_applicable'], + 'index_length' => $txt['not_applicable'], + 'fulltext_length' => $txt['not_applicable'], + 'custom_index_length' => $txt['not_applicable'], + ); + } + else + $context['table_info'] = array( + 'data_length' => $txt['not_applicable'], + 'index_length' => $txt['not_applicable'], + 'fulltext_length' => $txt['not_applicable'], + 'custom_index_length' => $txt['not_applicable'], + ); + + // Format the data and index length in kilobytes. + foreach ($context['table_info'] as $type => $size) + { + // If it's not numeric then just break. This database engine doesn't support size. + if (!is_numeric($size)) + break; + + $context['table_info'][$type] = comma_format($context['table_info'][$type] / 1024) . ' ' . $txt['search_method_kilobytes']; + } + + $context['custom_index'] = !empty($modSettings['search_custom_index_config']); + $context['partial_custom_index'] = !empty($modSettings['search_custom_index_resume']) && empty($modSettings['search_custom_index_config']); + $context['double_index'] = !empty($context['fulltext_index']) && $context['custom_index']; +} + +function CreateMessageIndex() +{ + global $modSettings, $context, $smcFunc, $db_prefix, $txt; + + // Scotty, we need more time... + @set_time_limit(600); + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + $context[$context['admin_menu_name']]['current_subsection'] = 'method'; + $context['page_title'] = $txt['search_index_custom']; + + $messages_per_batch = 50; + + $index_properties = array( + 2 => array( + 'column_definition' => 'small', + 'step_size' => 1000000, + ), + 4 => array( + 'column_definition' => 'medium', + 'step_size' => 1000000, + 'max_size' => 16777215, + ), + 5 => array( + 'column_definition' => 'large', + 'step_size' => 100000000, + 'max_size' => 2000000000, + ), + ); + + if (isset($_REQUEST['resume']) && !empty($modSettings['search_custom_index_resume'])) + { + $context['index_settings'] = unserialize($modSettings['search_custom_index_resume']); + $context['start'] = (int) $context['index_settings']['resume_at']; + unset($context['index_settings']['resume_at']); + $context['step'] = 1; + } + else + { + $context['index_settings'] = array( + 'bytes_per_word' => isset($_REQUEST['bytes_per_word']) && isset($index_properties[$_REQUEST['bytes_per_word']]) ? (int) $_REQUEST['bytes_per_word'] : 2, + ); + $context['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0; + $context['step'] = isset($_REQUEST['step']) ? (int) $_REQUEST['step'] : 0; + } + + if ($context['step'] !== 0) + checkSession('request'); + + // Step 0: let the user determine how they like their index. + if ($context['step'] === 0) + { + $context['sub_template'] = 'create_index'; + } + + // Step 1: insert all the words. + if ($context['step'] === 1) + { + $context['sub_template'] = 'create_index_progress'; + + if ($context['start'] === 0) + { + db_extend(); + $tables = $smcFunc['db_list_tables'](false, $db_prefix . 'log_search_words'); + if (!empty($tables)) + { + $smcFunc['db_search_query']('drop_words_table', ' + DROP TABLE {db_prefix}log_search_words', + array( + ) + ); + } + + $smcFunc['db_create_word_search']($index_properties[$context['index_settings']['bytes_per_word']]['column_definition']); + + // Temporarily switch back to not using a search index. + if (!empty($modSettings['search_index']) && $modSettings['search_index'] == 'custom') + updateSettings(array('search_index' => '')); + + // Don't let simultanious processes be updating the search index. + if (!empty($modSettings['search_custom_index_config'])) + updateSettings(array('search_custom_index_config' => '')); + } + + $num_messages = array( + 'done' => 0, + 'todo' => 0, + ); + + $request = $smcFunc['db_query']('', ' + SELECT id_msg >= {int:starting_id} AS todo, COUNT(*) AS num_messages + FROM {db_prefix}messages + GROUP BY todo', + array( + 'starting_id' => $context['start'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $num_messages[empty($row['todo']) ? 'done' : 'todo'] = $row['num_messages']; + + if (empty($num_messages['todo'])) + { + $context['step'] = 2; + $context['percentage'] = 80; + $context['start'] = 0; + } + else + { + // Number of seconds before the next step. + $stop = time() + 3; + while (time() < $stop) + { + $inserts = array(); + $request = $smcFunc['db_query']('', ' + SELECT id_msg, body + FROM {db_prefix}messages + WHERE id_msg BETWEEN {int:starting_id} AND {int:ending_id} + LIMIT {int:limit}', + array( + 'starting_id' => $context['start'], + 'ending_id' => $context['start'] + $messages_per_batch - 1, + 'limit' => $messages_per_batch, + ) + ); + $forced_break = false; + $number_processed = 0; + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // In theory it's possible for one of these to take friggin ages so add more timeout protection. + if ($stop < time()) + { + $forced_break = true; + break; + } + + $number_processed++; + foreach (text2words($row['body'], $context['index_settings']['bytes_per_word'], true) as $id_word) + { + $inserts[] = array($id_word, $row['id_msg']); + } + } + $num_messages['done'] += $number_processed; + $num_messages['todo'] -= $number_processed; + $smcFunc['db_free_result']($request); + + $context['start'] += $forced_break ? $number_processed : $messages_per_batch; + + if (!empty($inserts)) + $smcFunc['db_insert']('ignore', + '{db_prefix}log_search_words', + array('id_word' => 'int', 'id_msg' => 'int'), + $inserts, + array('id_word', 'id_msg') + ); + if ($num_messages['todo'] === 0) + { + $context['step'] = 2; + $context['start'] = 0; + break; + } + else + updateSettings(array('search_custom_index_resume' => serialize(array_merge($context['index_settings'], array('resume_at' => $context['start']))))); + } + + // Since there are still two steps to go, 90% is the maximum here. + $context['percentage'] = round($num_messages['done'] / ($num_messages['done'] + $num_messages['todo']), 3) * 80; + } + } + + // Step 2: removing the words that occur too often and are of no use. + elseif ($context['step'] === 2) + { + if ($context['index_settings']['bytes_per_word'] < 4) + $context['step'] = 3; + else + { + $stop_words = $context['start'] === 0 || empty($modSettings['search_stopwords']) ? array() : explode(',', $modSettings['search_stopwords']); + $stop = time() + 3; + $context['sub_template'] = 'create_index_progress'; + $max_messages = ceil(60 * $modSettings['totalMessages'] / 100); + + while (time() < $stop) + { + $request = $smcFunc['db_query']('', ' + SELECT id_word, COUNT(id_word) AS num_words + FROM {db_prefix}log_search_words + WHERE id_word BETWEEN {int:starting_id} AND {int:ending_id} + GROUP BY id_word + HAVING COUNT(id_word) > {int:minimum_messages}', + array( + 'starting_id' => $context['start'], + 'ending_id' => $context['start'] + $index_properties[$context['index_settings']['bytes_per_word']]['step_size'] - 1, + 'minimum_messages' => $max_messages, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $stop_words[] = $row['id_word']; + $smcFunc['db_free_result']($request); + + updateSettings(array('search_stopwords' => implode(',', $stop_words))); + + if (!empty($stop_words)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_search_words + WHERE id_word in ({array_int:stop_words})', + array( + 'stop_words' => $stop_words, + ) + ); + + $context['start'] += $index_properties[$context['index_settings']['bytes_per_word']]['step_size']; + if ($context['start'] > $index_properties[$context['index_settings']['bytes_per_word']]['max_size']) + { + $context['step'] = 3; + break; + } + } + $context['percentage'] = 80 + round($context['start'] / $index_properties[$context['index_settings']['bytes_per_word']]['max_size'], 3) * 20; + } + } + + // Step 3: remove words not distinctive enough. + if ($context['step'] === 3) + { + $context['sub_template'] = 'create_index_done'; + + updateSettings(array('search_index' => 'custom', 'search_custom_index_config' => serialize($context['index_settings']))); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}settings + WHERE variable = {string:search_custom_index_resume}', + array( + 'search_custom_index_resume' => 'search_custom_index_resume', + ) + ); + } +} + +// Get the installed APIs. +function loadSearchAPIs() +{ + global $sourcedir, $txt; + + $apis = array(); + if ($dh = opendir($sourcedir)) + { + while (($file = readdir($dh)) !== false) + { + if (is_file($sourcedir . '/' . $file) && preg_match('~SearchAPI-([A-Za-z\d_]+)\.php~', $file, $matches)) + { + // Check this is definitely a valid API! + $fp = fopen($sourcedir . '/' . $file, 'rb'); + $header = fread($fp, 4096); + fclose($fp); + + if (strpos($header, '* SearchAPI-' . $matches[1] . '.php') !== false) + { + loadClassFile($file); + + $index_name = strtolower($matches[1]); + $search_class_name = $index_name . '_search'; + $searchAPI = new $search_class_name(); + + // No Support? NEXT! + if (!$searchAPI->is_supported) + continue; + + $apis[$index_name] = array( + 'filename' => $file, + 'setting_index' => $index_name, + 'has_template' => in_array($index_name, array('custom', 'fulltext', 'standard')), + 'label' => $index_name && isset($txt['search_index_' . $index_name]) ? $txt['search_index_' . $index_name] : '', + 'desc' => $index_name && isset($txt['search_index_' . $index_name . '_desc']) ? $txt['search_index_' . $index_name . '_desc'] : '', + ); + } + } + } + } + closedir($dh); + + return $apis; +} + +?> \ No newline at end of file diff --git a/Sources/ManageSearchEngines.php b/Sources/ManageSearchEngines.php new file mode 100644 index 0000000..1015d1e --- /dev/null +++ b/Sources/ManageSearchEngines.php @@ -0,0 +1,1022 @@ + 'EditSpider', + 'logs' => 'SpiderLogs', + 'settings' => 'ManageSearchEngineSettings', + 'spiders' => 'ViewSpiders', + 'stats' => 'SpiderStats', + ); + + // Ensure we have a valid subaction. + $context['sub_action'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'stats'; + + $context['page_title'] = $txt['search_engines']; + + // Some more tab data. + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['search_engines'], + 'description' => $txt['search_engines_description'], + ); + + // Call the function! + $subActions[$context['sub_action']](); +} + +// This is really just the settings page. +function ManageSearchEngineSettings($return_config = false) +{ + global $context, $txt, $modSettings, $scripturl, $sourcedir, $smcFunc; + + $config_vars = array( + // How much detail? + array('select', 'spider_mode', array($txt['spider_mode_off'], $txt['spider_mode_standard'], $txt['spider_mode_high'], $txt['spider_mode_vhigh']), 'onchange' => 'disableFields();'), + 'spider_group' => array('select', 'spider_group', array($txt['spider_group_none'], $txt['membergroups_members'])), + array('select', 'show_spider_online', array($txt['show_spider_online_no'], $txt['show_spider_online_summary'], $txt['show_spider_online_detail'], $txt['show_spider_online_detail_admin'])), + ); + + // Set up a message. + $context['settings_message'] = '' . sprintf($txt['spider_settings_desc'], $scripturl . '?action=admin;area=logs;sa=pruning;' . $context['session_var'] . '=' . $context['session_id']) . ''; + + // Do some javascript. + $javascript_function = ' + function disableFields() + { + disabledState = document.getElementById(\'spider_mode\').value == 0;'; + + foreach ($config_vars as $variable) + if ($variable[1] != 'spider_mode') + $javascript_function .= ' + if (document.getElementById(\'' . $variable[1] . '\')) + document.getElementById(\'' . $variable[1] . '\').disabled = disabledState;'; + + $javascript_function .= ' + } + disableFields();'; + + if ($return_config) + return $config_vars; + + // We need to load the groups for the spider group thingy. + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_name + FROM {db_prefix}membergroups + WHERE id_group != {int:admin_group} + AND id_group != {int:moderator_group}', + array( + 'admin_group' => 1, + 'moderator_group' => 3, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $config_vars['spider_group'][2][$row['id_group']] = $row['group_name']; + $smcFunc['db_free_result']($request); + + // Make sure it's valid - note that regular members are given id_group = 1 which is reversed in Load.php - no admins here! + if (isset($_POST['spider_group']) && !isset($config_vars['spider_group'][2][$_POST['spider_group']])) + $_POST['spider_group'] = 0; + + // We'll want this for our easy save. + require_once($sourcedir . '/ManageServer.php'); + + // Setup the template. + $context['page_title'] = $txt['settings']; + $context['sub_template'] = 'show_settings'; + + // Are we saving them - are we?? + if (isset($_GET['save'])) + { + checkSession(); + + saveDBSettings($config_vars); + recacheSpiderNames(); + redirectexit('action=admin;area=sengines;sa=settings'); + } + + // Final settings... + $context['post_url'] = $scripturl . '?action=admin;area=sengines;save;sa=settings'; + $context['settings_title'] = $txt['settings']; + $context['settings_insert_below'] = ' + '; + + // Prepare the settings... + prepareDBSettingContext($config_vars); +} + +// View a list of all the spiders we know about. +function ViewSpiders() +{ + global $context, $txt, $sourcedir, $scripturl, $smcFunc; + + if (!isset($_SESSION['spider_stat']) || $_SESSION['spider_stat'] < time() - 60) + { + consolidateSpiderStats(); + $_SESSION['spider_stat'] = time(); + } + + // Are we adding a new one? + if (!empty($_POST['addSpider'])) + return EditSpider(); + // User pressed the 'remove selection button'. + elseif (!empty($_POST['removeSpiders']) && !empty($_POST['remove']) && is_array($_POST['remove'])) + { + checkSession(); + + // Make sure every entry is a proper integer. + foreach ($_POST['remove'] as $index => $spider_id) + $_POST['remove'][(int) $index] = (int) $spider_id; + + // Delete them all! + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}spiders + WHERE id_spider IN ({array_int:remove_list})', + array( + 'remove_list' => $_POST['remove'], + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_spider_hits + WHERE id_spider IN ({array_int:remove_list})', + array( + 'remove_list' => $_POST['remove'], + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_spider_stats + WHERE id_spider IN ({array_int:remove_list})', + array( + 'remove_list' => $_POST['remove'], + ) + ); + + cache_put_data('spider_search', null, 300); + recacheSpiderNames(); + } + + // Get the last seens. + $request = $smcFunc['db_query']('', ' + SELECT id_spider, MAX(last_seen) AS last_seen_time + FROM {db_prefix}log_spider_stats + GROUP BY id_spider', + array( + ) + ); + + $context['spider_last_seen'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['spider_last_seen'][$row['id_spider']] = $row['last_seen_time']; + $smcFunc['db_free_result']($request); + + $listOptions = array( + 'id' => 'spider_list', + 'items_per_page' => 20, + 'base_href' => $scripturl . '?action=admin;area=sengines;sa=spiders', + 'default_sort_col' => 'name', + 'get_items' => array( + 'function' => 'list_getSpiders', + ), + 'get_count' => array( + 'function' => 'list_getNumSpiders', + ), + 'no_items_label' => $txt['spiders_no_entries'], + 'columns' => array( + 'name' => array( + 'header' => array( + 'value' => $txt['spider_name'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $scripturl; + + return sprintf(\'%3$s\', $scripturl, $rowData[\'id_spider\'], htmlspecialchars($rowData[\'spider_name\'])); + '), + ), + 'sort' => array( + 'default' => 'spider_name', + 'reverse' => 'spider_name DESC', + ), + ), + 'last_seen' => array( + 'header' => array( + 'value' => $txt['spider_last_seen'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $context, $txt; + + return isset($context[\'spider_last_seen\'][$rowData[\'id_spider\']]) ? timeformat($context[\'spider_last_seen\'][$rowData[\'id_spider\']]) : $txt[\'spider_last_never\']; + '), + ), + ), + 'user_agent' => array( + 'header' => array( + 'value' => $txt['spider_agent'], + ), + 'data' => array( + 'db_htmlsafe' => 'user_agent', + ), + 'sort' => array( + 'default' => 'user_agent', + 'reverse' => 'user_agent DESC', + ), + ), + 'ip_info' => array( + 'header' => array( + 'value' => $txt['spider_ip_info'], + ), + 'data' => array( + 'db_htmlsafe' => 'ip_info', + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'ip_info', + 'reverse' => 'ip_info DESC', + ), + ), + 'check' => array( + 'header' => array( + 'value' => '', + ), + 'data' => array( + 'sprintf' => array( + 'format' => '', + 'params' => array( + 'id_spider' => false, + ), + ), + 'style' => 'text-align: center', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=sengines;sa=spiders', + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => ' + + + ', + 'style' => 'text-align: right;', + ), + ), + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'spider_list'; +} + +function list_getSpiders($start, $items_per_page, $sort) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT id_spider, spider_name, user_agent, ip_info + FROM {db_prefix}spiders + ORDER BY ' . $sort . ' + LIMIT ' . $start . ', ' . $items_per_page, + array( + ) + ); + $spiders = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $spiders[$row['id_spider']] = $row; + $smcFunc['db_free_result']($request); + + return $spiders; +} + +function list_getNumSpiders() +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS num_spiders + FROM {db_prefix}spiders', + array( + ) + ); + list ($numSpiders) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $numSpiders; +} + +// Here we can add, and edit, spider info! +function EditSpider() +{ + global $context, $smcFunc, $txt; + + // Some standard stuff. + $context['id_spider'] = !empty($_GET['sid']) ? (int) $_GET['sid'] : 0; + $context['page_title'] = $context['id_spider'] ? $txt['spiders_edit'] : $txt['spiders_add']; + $context['sub_template'] = 'spider_edit'; + + // Are we saving? + if (!empty($_POST['save'])) + { + checkSession(); + + $ips = array(); + // Check the IP range is valid. + $ip_sets = explode(',', $_POST['spider_ip']); + foreach ($ip_sets as $set) + { + $test = ip2range(trim($set)); + if (!empty($test)) + $ips[] = $set; + } + $ips = implode(',', $ips); + + // Goes in as it is... + if ($context['id_spider']) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}spiders + SET spider_name = {string:spider_name}, user_agent = {string:spider_agent}, + ip_info = {string:ip_info} + WHERE id_spider = {int:current_spider}', + array( + 'current_spider' => $context['id_spider'], + 'spider_name' => $_POST['spider_name'], + 'spider_agent' => $_POST['spider_agent'], + 'ip_info' => $ips, + ) + ); + else + $smcFunc['db_insert']('insert', + '{db_prefix}spiders', + array( + 'spider_name' => 'string', 'user_agent' => 'string', 'ip_info' => 'string', + ), + array( + $_POST['spider_name'], $_POST['spider_agent'], $ips, + ), + array('id_spider') + ); + + // Order by user agent length. + sortSpiderTable(); + + cache_put_data('spider_search', null, 300); + recacheSpiderNames(); + + redirectexit('action=admin;area=sengines;sa=spiders'); + } + + // The default is new. + $context['spider'] = array( + 'id' => 0, + 'name' => '', + 'agent' => '', + 'ip_info' => '', + ); + + // An edit? + if ($context['id_spider']) + { + $request = $smcFunc['db_query']('', ' + SELECT id_spider, spider_name, user_agent, ip_info + FROM {db_prefix}spiders + WHERE id_spider = {int:current_spider}', + array( + 'current_spider' => $context['id_spider'], + ) + ); + if ($row = $smcFunc['db_fetch_assoc']($request)) + $context['spider'] = array( + 'id' => $row['id_spider'], + 'name' => $row['spider_name'], + 'agent' => $row['user_agent'], + 'ip_info' => $row['ip_info'], + ); + $smcFunc['db_free_result']($request); + } + +} + +//!!! Should this not be... you know... in a different file? +// Do we think the current user is a spider? +function SpiderCheck() +{ + global $modSettings, $smcFunc; + + if (isset($_SESSION['id_robot'])) + unset($_SESSION['id_robot']); + $_SESSION['robot_check'] = time(); + + // We cache the spider data for five minutes if we can. + if (!empty($modSettings['cache_enable'])) + $spider_data = cache_get_data('spider_search', 300); + + if (!isset($spider_data) || $spider_data === NULL) + { + $request = $smcFunc['db_query']('spider_check', ' + SELECT id_spider, user_agent, ip_info + FROM {db_prefix}spiders', + array( + ) + ); + $spider_data = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $spider_data[] = $row; + $smcFunc['db_free_result']($request); + + if (!empty($modSettings['cache_enable'])) + cache_put_data('spider_search', $spider_data, 300); + } + + if (empty($spider_data)) + return false; + + // Only do these bits once. + $ci_user_agent = strtolower($_SERVER['HTTP_USER_AGENT']); + preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $_SERVER['REMOTE_ADDR'], $ip_parts); + + foreach ($spider_data as $spider) + { + // User agent is easy. + if (!empty($spider['user_agent']) && strpos($ci_user_agent, strtolower($spider['user_agent'])) !== false) + $_SESSION['id_robot'] = $spider['id_spider']; + // IP stuff is harder. + elseif (!empty($ip_parts)) + { + $ips = explode(',', $spider['ip_info']); + foreach ($ips as $ip) + { + $ip = ip2range($ip); + if (!empty($ip)) + { + foreach ($ip as $key => $value) + { + if ($value['low'] > $ip_parts[$key + 1] || $value['high'] < $ip_parts[$key + 1]) + break; + elseif ($key == 3) + $_SESSION['id_robot'] = $spider['id_spider']; + } + } + } + } + + if (isset($_SESSION['id_robot'])) + break; + } + + // If this is low server tracking then log the spider here as oppossed to the main logging function. + if (!empty($modSettings['spider_mode']) && $modSettings['spider_mode'] == 1 && !empty($_SESSION['id_robot'])) + logSpider(); + + return !empty($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0; +} + +// Log the spider presence online. +//!!! Different file? +function logSpider() +{ + global $smcFunc, $modSettings, $context; + + if (empty($modSettings['spider_mode']) || empty($_SESSION['id_robot'])) + return; + + // Attempt to update today's entry. + if ($modSettings['spider_mode'] == 1) + { + $date = strftime('%Y-%m-%d', forum_time(false)); + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_spider_stats + SET last_seen = {int:current_time}, page_hits = page_hits + 1 + WHERE id_spider = {int:current_spider} + AND stat_date = {date:current_date}', + array( + 'current_date' => $date, + 'current_time' => time(), + 'current_spider' => $_SESSION['id_robot'], + ) + ); + + // Nothing updated? + if ($smcFunc['db_affected_rows']() == 0) + { + $smcFunc['db_insert']('ignore', + '{db_prefix}log_spider_stats', + array( + 'id_spider' => 'int', 'last_seen' => 'int', 'stat_date' => 'date', 'page_hits' => 'int', + ), + array( + $_SESSION['id_robot'], time(), $date, 1, + ), + array('id_spider', 'stat_date') + ); + } + } + // If we're tracking better stats than track, better stats - we sort out the today thing later. + else + { + if ($modSettings['spider_mode'] > 2) + { + $url = $_GET + array('USER_AGENT' => $_SERVER['HTTP_USER_AGENT']); + unset($url['sesc'], $url[$context['session_var']]); + $url = serialize($url); + } + else + $url = ''; + + $smcFunc['db_insert']('insert', + '{db_prefix}log_spider_hits', + array('id_spider' => 'int', 'log_time' => 'int', 'url' => 'string'), + array($_SESSION['id_robot'], time(), $url), + array() + ); + } +} + +// This function takes any unprocessed hits and turns them into stats. +function consolidateSpiderStats() +{ + global $smcFunc; + + $request = $smcFunc['db_query']('consolidate_spider_stats', ' + SELECT id_spider, MAX(log_time) AS last_seen, COUNT(*) AS num_hits + FROM {db_prefix}log_spider_hits + WHERE processed = {int:not_processed} + GROUP BY id_spider, MONTH(log_time), DAYOFMONTH(log_time)', + array( + 'not_processed' => 0, + ) + ); + $spider_hits = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $spider_hits[] = $row; + $smcFunc['db_free_result']($request); + + if (empty($spider_hits)) + return; + + // Attempt to update the master data. + $stat_inserts = array(); + foreach ($spider_hits as $stat) + { + // We assume the max date is within the right day. + $date = strftime('%Y-%m-%d', $stat['last_seen']); + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_spider_stats + SET page_hits = page_hits + ' . $stat['num_hits'] . ', + last_seen = CASE WHEN last_seen > {int:last_seen} THEN last_seen ELSE {int:last_seen} END + WHERE id_spider = {int:current_spider} + AND stat_date = {date:last_seen_date}', + array( + 'last_seen_date' => $date, + 'last_seen' => $stat['last_seen'], + 'current_spider' => $stat['id_spider'], + ) + ); + if ($smcFunc['db_affected_rows']() == 0) + $stat_inserts[] = array($date, $stat['id_spider'], $stat['num_hits'], $stat['last_seen']); + } + + // New stats? + if (!empty($stat_inserts)) + $smcFunc['db_insert']('ignore', + '{db_prefix}log_spider_stats', + array('stat_date' => 'date', 'id_spider' => 'int', 'page_hits' => 'int', 'last_seen' => 'int'), + $stat_inserts, + array('stat_date', 'id_spider') + ); + + // All processed. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_spider_hits + SET processed = {int:is_processed} + WHERE processed = {int:not_processed}', + array( + 'is_processed' => 1, + 'not_processed' => 0, + ) + ); +} + +// See what spiders have been up to. +function SpiderLogs() +{ + global $context, $txt, $sourcedir, $scripturl, $smcFunc, $modSettings; + + // Load the template and language just incase. + loadLanguage('Search'); + loadTemplate('ManageSearch'); + + // Did they want to delete some entries? + if (!empty($_POST['delete_entries']) && isset($_POST['older'])) + { + checkSession(); + + $deleteTime = time() - (((int) $_POST['older']) * 24 * 60 * 60); + + // Delete the entires. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_spider_hits + WHERE log_time < {int:delete_period}', + array( + 'delete_period' => $deleteTime, + ) + ); + } + + $listOptions = array( + 'id' => 'spider_logs', + 'items_per_page' => 20, + 'title' => $txt['spider_logs'], + 'no_items_label' => $txt['spider_logs_empty'], + 'base_href' => $context['admin_area'] == 'sengines' ? $scripturl . '?action=admin;area=sengines;sa=logs' : $scripturl . '?action=admin;area=logs;sa=spiderlog', + 'default_sort_col' => 'log_time', + 'get_items' => array( + 'function' => 'list_getSpiderLogs', + ), + 'get_count' => array( + 'function' => 'list_getNumSpiderLogs', + ), + 'columns' => array( + 'name' => array( + 'header' => array( + 'value' => $txt['spider'], + ), + 'data' => array( + 'db' => 'spider_name', + ), + 'sort' => array( + 'default' => 's.spider_name', + 'reverse' => 's.spider_name DESC', + ), + ), + 'log_time' => array( + 'header' => array( + 'value' => $txt['spider_time'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + return timeformat($rowData[\'log_time\']); + '), + ), + 'sort' => array( + 'default' => 'sl.id_hit DESC', + 'reverse' => 'sl.id_hit', + ), + ), + 'viewing' => array( + 'header' => array( + 'value' => $txt['spider_viewing'], + ), + 'data' => array( + 'db' => 'url', + ), + ), + ), + 'additional_rows' => array( + array( + 'position' => 'after_title', + 'value' => $txt['spider_logs_info'], + 'class' => 'smalltext', + ), + ), + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + // Now determine the actions of the URLs. + if (!empty($context['spider_logs']['rows'])) + { + $urls = array(); + // Grab the current /url. + foreach ($context['spider_logs']['rows'] as $k => $row) + { + // Feature disabled? + if (empty($row['viewing']['value']) && isset($modSettings['spider_mode']) && $modSettings['spider_mode'] < 3) + $context['spider_logs']['rows'][$k]['viewing']['value'] = '' . $txt['spider_disabled'] . ''; + else + $urls[$k] = array($row['viewing']['value'], -1); + } + + // Now stick in the new URLs. + require_once($sourcedir . '/Who.php'); + $urls = determineActions($urls, 'whospider_'); + foreach ($urls as $k => $new_url) + { + $context['spider_logs']['rows'][$k]['viewing']['value'] = $new_url; + } + } + + $context['page_title'] = $txt['spider_logs']; + $context['sub_template'] = 'show_spider_logs'; + $context['default_list'] = 'spider_logs'; +} + +function list_getSpiderLogs($start, $items_per_page, $sort) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT sl.id_spider, sl.url, sl.log_time, s.spider_name + FROM {db_prefix}log_spider_hits AS sl + INNER JOIN {db_prefix}spiders AS s ON (s.id_spider = sl.id_spider) + ORDER BY ' . $sort . ' + LIMIT ' . $start . ', ' . $items_per_page, + array( + ) + ); + $spider_logs = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $spider_logs[] = $row; + $smcFunc['db_free_result']($request); + + return $spider_logs; +} + +function list_getNumSpiderLogs() +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS num_logs + FROM {db_prefix}log_spider_hits', + array( + ) + ); + list ($numLogs) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $numLogs; +} + +// Show the spider statistics. +function SpiderStats() +{ + global $context, $txt, $sourcedir, $scripturl, $smcFunc; + + // Force an update of the stats every 60 seconds. + if (!isset($_SESSION['spider_stat']) || $_SESSION['spider_stat'] < time() - 60) + { + consolidateSpiderStats(); + $_SESSION['spider_stat'] = time(); + } + + // Get the earliest and latest dates. + $request = $smcFunc['db_query']('', ' + SELECT MIN(stat_date) AS first_date, MAX(stat_date) AS last_date + FROM {db_prefix}log_spider_stats', + array( + ) + ); + + list ($min_date, $max_date) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $min_year = (int) substr($min_date, 0, 4); + $max_year = (int) substr($max_date, 0, 4); + $min_month = (int) substr($min_date, 5, 2); + $max_month = (int) substr($max_date, 5, 2); + + // Prepare the dates for the drop down. + $date_choices = array(); + for ($y = $min_year; $y <= $max_year; $y++) + for ($m = 1; $m <= 12; $m++) + { + // This doesn't count? + if ($y == $min_year && $m < $min_month) + continue; + if ($y == $max_year && $m > $max_month) + break; + + $date_choices[$y . $m] = $txt['months_short'][$m] . ' ' . $y; + } + + // What are we currently viewing? + $current_date = isset($_REQUEST['new_date']) && isset($date_choices[$_REQUEST['new_date']]) ? $_REQUEST['new_date'] : $max_date; + + // Prepare the HTML. + $date_select = ' + ' . $txt['spider_stats_select_month'] . ': + + '; + + // If we manually jumped to a date work out the offset. + if (isset($_REQUEST['new_date'])) + { + $date_query = sprintf('%04d-%02d-01', substr($current_date, 0, 4), substr($current_date, 4)); + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS offset + FROM {db_prefix}log_spider_stats + WHERE stat_date < {date:date_being_viewed}', + array( + 'date_being_viewed' => $date_query, + ) + ); + list ($_REQUEST['start']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + $listOptions = array( + 'id' => 'spider_stat_list', + 'items_per_page' => 20, + 'base_href' => $scripturl . '?action=admin;area=sengines;sa=stats', + 'default_sort_col' => 'stat_date', + 'get_items' => array( + 'function' => 'list_getSpiderStats', + ), + 'get_count' => array( + 'function' => 'list_getNumSpiderStats', + ), + 'no_items_label' => $txt['spider_stats_no_entries'], + 'columns' => array( + 'stat_date' => array( + 'header' => array( + 'value' => $txt['date'], + ), + 'data' => array( + 'db' => 'stat_date', + ), + 'sort' => array( + 'default' => 'stat_date', + 'reverse' => 'stat_date DESC', + ), + ), + 'name' => array( + 'header' => array( + 'value' => $txt['spider_name'], + ), + 'data' => array( + 'db' => 'spider_name', + ), + 'sort' => array( + 'default' => 's.spider_name', + 'reverse' => 's.spider_name DESC', + ), + ), + 'page_hits' => array( + 'header' => array( + 'value' => $txt['spider_stats_page_hits'], + ), + 'data' => array( + 'db' => 'page_hits', + ), + 'sort' => array( + 'default' => 'ss.page_hits', + 'reverse' => 'ss.page_hits DESC', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=sengines;sa=stats', + 'name' => 'spider_stat_list', + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => $date_select, + 'style' => 'text-align: right;', + ), + ), + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'spider_stat_list'; +} + +function list_getSpiderStats($start, $items_per_page, $sort) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT ss.id_spider, ss.stat_date, ss.page_hits, s.spider_name + FROM {db_prefix}log_spider_stats AS ss + INNER JOIN {db_prefix}spiders AS s ON (s.id_spider = ss.id_spider) + ORDER BY ' . $sort . ' + LIMIT ' . $start . ', ' . $items_per_page, + array( + ) + ); + $spider_stats = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $spider_stats[] = $row; + $smcFunc['db_free_result']($request); + + return $spider_stats; +} + +function list_getNumSpiderStats() +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS num_stats + FROM {db_prefix}log_spider_stats', + array( + ) + ); + list ($numStats) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $numStats; +} + +// Recache spider names? +function recacheSpiderNames() +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT id_spider, spider_name + FROM {db_prefix}spiders', + array( + ) + ); + $spiders = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $spiders[$row['id_spider']] = $row['spider_name']; + $smcFunc['db_free_result']($request); + + updateSettings(array('spider_name_cache' => serialize($spiders))); +} + +// Sort the search engine table by user agent name to avoid misidentification of engine. +function sortSpiderTable() +{ + global $smcFunc; + + db_extend('packages'); + + // Add a sorting column. + $smcFunc['db_add_column']('{db_prefix}spiders', array('name' => 'temp_order', 'size' => 8, 'type' => 'mediumint', 'null' => false)); + + // Set the contents of this column. + $smcFunc['db_query']('set_spider_order', ' + UPDATE {db_prefix}spiders + SET temp_order = LENGTH(user_agent)', + array( + ) + ); + + // Order the table by this column. + $smcFunc['db_query']('alter_table_spiders', ' + ALTER TABLE {db_prefix}spiders + ORDER BY temp_order DESC', + array( + 'db_error_skip' => true, + ) + ); + + // Remove the sorting column. + $smcFunc['db_remove_column']('{db_prefix}spiders', 'temp_order'); +} + +?> \ No newline at end of file diff --git a/Sources/ManageServer.php b/Sources/ManageServer.php new file mode 100644 index 0000000..9bf0226 --- /dev/null +++ b/Sources/ManageServer.php @@ -0,0 +1,2135 @@ + $txt['displayedValue'])), + Note that just saying array('first', 'second') will put 0 in the SQL for 'first'. + + * A password input box. Used for passwords, no less! + ie. array('password', 'nameInModSettingsAndSQL', 'OptionalInputBoxWidth'), + + * A permission - for picking groups who have a permission. + ie. array('permissions', 'manage_groups'), + + * A BBC selection box. + ie. array('bbc', 'sig_bbc'), + + For each option: + type (see above), variable name, size/possible values. + OR make type '' for an empty string for a horizontal rule. + SET preinput - to put some HTML prior to the input box. + SET postinput - to put some HTML following the input box. + SET invalid - to mark the data as invalid. + PLUS You can override label and help parameters by forcing their keys in the array, for example: + array('text', 'invalidlabel', 3, 'label' => 'Actual Label') */ + +// This is the main pass through function, it creates tabs and the like. +function ModifySettings() +{ + global $context, $txt, $scripturl, $boarddir; + + // This is just to keep the database password more secure. + isAllowedTo('admin_forum'); + + // Load up all the tabs... + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['admin_server_settings'], + 'help' => 'serversettings', + 'description' => $txt['admin_basic_settings'], + ); + + checkSession('request'); + + // The settings are in here, I swear! + loadLanguage('ManageSettings'); + + $context['page_title'] = $txt['admin_server_settings']; + $context['sub_template'] = 'show_settings'; + + $subActions = array( + 'general' => 'ModifyGeneralSettings', + 'database' => 'ModifyDatabaseSettings', + 'cookie' => 'ModifyCookieSettings', + 'cache' => 'ModifyCacheSettings', + 'loads' => 'ModifyLoadBalancingSettings', + ); + + // By default we're editing the core settings + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'general'; + $context['sub_action'] = $_REQUEST['sa']; + + // Warn the user if there's any relevant information regarding Settings.php. + if ($_REQUEST['sa'] != 'cache') + { + // Warn the user if the backup of Settings.php failed. + $settings_not_writable = !is_writable($boarddir . '/Settings.php'); + $settings_backup_fail = !@is_writable($boarddir . '/Settings_bak.php') || !@copy($boarddir . '/Settings.php', $boarddir . '/Settings_bak.php'); + + if ($settings_not_writable) + $context['settings_message'] = '
' . $txt['settings_not_writable'] . '

'; + elseif ($settings_backup_fail) + $context['settings_message'] = '
' . $txt['admin_backup_fail'] . '

'; + + $context['settings_not_writable'] = $settings_not_writable; + } + + // Call the right function for this sub-action. + $subActions[$_REQUEST['sa']](); +} + +// General forum settings - forum name, maintenance mode, etc. +function ModifyGeneralSettings($return_config = false) +{ + global $scripturl, $context, $txt; + + /* If you're writing a mod, it's a bad idea to add things here.... + For each option: + variable name, description, type (constant), size/possible values, helptext. + OR an empty string for a horizontal rule. + OR a string for a titled section. */ + $config_vars = array( + array('mbname', $txt['admin_title'], 'file', 'text', 30), + '', + array('maintenance', $txt['admin_maintain'], 'file', 'check'), + array('mtitle', $txt['maintenance_subject'], 'file', 'text', 36), + array('mmessage', $txt['maintenance_message'], 'file', 'text', 36), + '', + array('webmaster_email', $txt['admin_webmaster_email'], 'file', 'text', 30), + '', + array('enableCompressedOutput', $txt['enableCompressedOutput'], 'db', 'check', null, 'enableCompressedOutput'), + array('disableTemplateEval', $txt['disableTemplateEval'], 'db', 'check', null, 'disableTemplateEval'), + array('disableHostnameLookup', $txt['disableHostnameLookup'], 'db', 'check', null, 'disableHostnameLookup'), + ); + + if ($return_config) + return $config_vars; + + // Setup the template stuff. + $context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=general;save'; + $context['settings_title'] = $txt['general_settings']; + + // Saving settings? + if (isset($_REQUEST['save'])) + { + saveSettings($config_vars); + redirectexit('action=admin;area=serversettings;sa=general;' . $context['session_var'] . '=' . $context['session_id']); + } + + // Fill the config array. + prepareServerSettingsContext($config_vars); +} + +// Basic database and paths settings - database name, host, etc. +function ModifyDatabaseSettings($return_config = false) +{ + global $scripturl, $context, $settings, $txt, $boarddir; + + /* If you're writing a mod, it's a bad idea to add things here.... + For each option: + variable name, description, type (constant), size/possible values, helptext. + OR an empty string for a horizontal rule. + OR a string for a titled section. */ + $config_vars = array( + array('db_server', $txt['database_server'], 'file', 'text'), + array('db_user', $txt['database_user'], 'file', 'text'), + array('db_passwd', $txt['database_password'], 'file', 'password'), + array('db_name', $txt['database_name'], 'file', 'text'), + array('db_prefix', $txt['database_prefix'], 'file', 'text'), + array('db_persist', $txt['db_persist'], 'file', 'check', null, 'db_persist'), + array('db_error_send', $txt['db_error_send'], 'file', 'check'), + array('ssi_db_user', $txt['ssi_db_user'], 'file', 'text', null, 'ssi_db_user'), + array('ssi_db_passwd', $txt['ssi_db_passwd'], 'file', 'password'), + '', + array('autoFixDatabase', $txt['autoFixDatabase'], 'db', 'check', false, 'autoFixDatabase'), + array('autoOptMaxOnline', $txt['autoOptMaxOnline'], 'db', 'int'), + '', + array('boardurl', $txt['admin_url'], 'file', 'text', 36), + array('boarddir', $txt['boarddir'], 'file', 'text', 36), + array('sourcedir', $txt['sourcesdir'], 'file', 'text', 36), + array('cachedir', $txt['cachedir'], 'file', 'text', 36), + ); + + if ($return_config) + return $config_vars; + + // Setup the template stuff. + $context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=database;save'; + $context['settings_title'] = $txt['database_paths_settings']; + $context['save_disabled'] = $context['settings_not_writable']; + + // Saving settings? + if (isset($_REQUEST['save'])) + { + saveSettings($config_vars); + redirectexit('action=admin;area=serversettings;sa=database;' . $context['session_var'] . '=' . $context['session_id']); + } + + // Fill the config array. + prepareServerSettingsContext($config_vars); +} + +// This function basically edits anything which is configuration and stored in the database, except for caching. +function ModifyCookieSettings($return_config = false) +{ + global $context, $scripturl, $txt, $sourcedir, $modSettings, $cookiename, $user_settings; + + // Define the variables we want to edit. + $config_vars = array( + // Cookies... + array('cookiename', $txt['cookie_name'], 'file', 'text', 20), + array('cookieTime', $txt['cookieTime'], 'db', 'int'), + array('localCookies', $txt['localCookies'], 'db', 'check', false, 'localCookies'), + array('globalCookies', $txt['globalCookies'], 'db', 'check', false, 'globalCookies'), + array('secureCookies', $txt['secureCookies'], 'db', 'check', false, 'secureCookies', 'disabled' => !isset($_SERVER['HTTPS']) || !(strtolower($_SERVER['HTTPS']) == 'on' || strtolower($_SERVER['HTTPS']) == '1')), + '', + // Sessions + array('databaseSession_enable', $txt['databaseSession_enable'], 'db', 'check', false, 'databaseSession_enable'), + array('databaseSession_loose', $txt['databaseSession_loose'], 'db', 'check', false, 'databaseSession_loose'), + array('databaseSession_lifetime', $txt['databaseSession_lifetime'], 'db', 'int', false, 'databaseSession_lifetime'), + ); + + if ($return_config) + return $config_vars; + + $context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=cookie;save'; + $context['settings_title'] = $txt['cookies_sessions_settings']; + + // Saving settings? + if (isset($_REQUEST['save'])) + { + saveSettings($config_vars); + + // If the cookie name was changed, reset the cookie. + if ($cookiename != $_POST['cookiename']) + { + $original_session_id = $context['session_id']; + include_once($sourcedir . '/Subs-Auth.php'); + + // Remove the old cookie. + setLoginCookie(-3600, 0); + + // Set the new one. + $cookiename = $_POST['cookiename']; + setLoginCookie(60 * $modSettings['cookieTime'], $user_settings['id_member'], sha1($user_settings['passwd'] . $user_settings['password_salt'])); + + redirectexit('action=admin;area=serversettings;sa=cookie;' . $context['session_var'] . '=' . $original_session_id, $context['server']['needs_login_fix']); + } + + redirectexit('action=admin;area=serversettings;sa=cookie;' . $context['session_var'] . '=' . $context['session_id']); + } + + // Fill the config array. + prepareServerSettingsContext($config_vars); +} + +// Simply modifying cache functions +function ModifyCacheSettings($return_config = false) +{ + global $context, $scripturl, $txt, $helptxt, $modSettings; + + // Define the variables we want to edit. + $config_vars = array( + // Only a couple of settings, but they are important + array('select', 'cache_enable', array($txt['cache_off'], $txt['cache_level1'], $txt['cache_level2'], $txt['cache_level3'])), + array('text', 'cache_memcached'), + ); + + if ($return_config) + return $config_vars; + + // Saving again? + if (isset($_GET['save'])) + { + saveDBSettings($config_vars); + + // We have to manually force the clearing of the cache otherwise the changed settings might not get noticed. + $modSettings['cache_enable'] = 1; + cache_put_data('modSettings', null, 90); + + redirectexit('action=admin;area=serversettings;sa=cache;' . $context['session_var'] . '=' . $context['session_id']); + } + + $context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=cache;save'; + $context['settings_title'] = $txt['caching_settings']; + $context['settings_message'] = $txt['caching_information']; + + // Detect an optimizer? + if (function_exists('eaccelerator_put')) + $detected = 'eAccelerator'; + elseif (function_exists('mmcache_put')) + $detected = 'MMCache'; + elseif (function_exists('apc_store')) + $detected = 'APC'; + elseif (function_exists('output_cache_put')) + $detected = 'Zend'; + elseif (function_exists('memcache_set')) + $detected = 'Memcached'; + elseif (function_exists('xcache_set')) + $detected = 'XCache'; + else + $detected = 'no_caching'; + + $context['settings_message'] = sprintf($context['settings_message'], $txt['detected_' . $detected]); + + // Prepare the template. + prepareDBSettingContext($config_vars); +} + +function ModifyLoadBalancingSettings($return_config = false) +{ + global $txt, $scripturl, $context, $settings, $modSettings; + + // Setup a warning message, but disabled by default. + $disabled = true; + $context['settings_message'] = $txt['loadavg_disabled_conf']; + + if (strpos(strtolower(PHP_OS), 'win') === 0) + $context['settings_message'] = $txt['loadavg_disabled_windows']; + else + { + $modSettings['load_average'] = @file_get_contents('/proc/loadavg'); + if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) !== 0) + $modSettings['load_average'] = (float) $matches[1]; + elseif (($modSettings['load_average'] = @`uptime`) !== null && preg_match('~load averages?: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)~i', $modSettings['load_average'], $matches) !== 0) + $modSettings['load_average'] = (float) $matches[1]; + else + unset($modSettings['load_average']); + + if (!empty($modSettings['load_average'])) + { + $context['settings_message'] = sprintf($txt['loadavg_warning'], $modSettings['load_average']); + $disabled = false; + } + } + + // Start with a simple checkbox. + $config_vars = array( + array('check', 'loadavg_enable'), + ); + + // Set the default values for each option. + $default_values = array( + 'loadavg_auto_opt' => '1.0', + 'loadavg_search' => '2.5', + 'loadavg_allunread' => '2.0', + 'loadavg_unreadreplies' => '3.5', + 'loadavg_show_posts' => '2.0', + 'loadavg_forum' => '40.0', + ); + + // Loop through the settings. + foreach ($default_values as $name => $value) + { + // Use the default value if the setting isn't set yet. + $value = !isset($modSettings[$name]) ? $value : $modSettings[$name]; + $config_vars[] = array('text', $name, 'value' => $value, 'disabled' => $disabled); + } + + if ($return_config) + return $config_vars; + + $context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=loads;save'; + $context['settings_title'] = $txt['load_balancing_settings']; + + // Saving? + if (isset($_GET['save'])) + { + // Stupidity is not allowed. + foreach ($_POST as $key => $value) + { + if (strpos($key, 'loadavg') === 0 || $key === 'loadavg_enable') + continue; + elseif ($key == 'loadavg_auto_opt' && $value <= 1) + $_POST['loadavg_auto_opt'] = '1.0'; + elseif ($key == 'loadavg_forum' && $value < 10) + $_POST['loadavg_forum'] = '10.0'; + elseif ($value < 2) + $_POST[$key] = '2.0'; + } + + saveDBSettings($config_vars); + redirectexit('action=admin;area=serversettings;sa=loads;' . $context['session_var'] . '=' . $context['session_id']); + } + + prepareDBSettingContext($config_vars); +} + +// This is the main function for the language area. +function ManageLanguages() +{ + global $context, $txt, $scripturl, $modSettings; + + loadLanguage('ManageSettings'); + + $context['page_title'] = $txt['edit_languages']; + $context['sub_template'] = 'show_settings'; + + $subActions = array( + 'edit' => 'ModifyLanguages', + 'add' => 'AddLanguage', + 'settings' => 'ModifyLanguageSettings', + 'downloadlang' => 'DownloadLanguage', + 'editlang' => 'ModifyLanguage', + ); + + // By default we're managing languages. + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'edit'; + $context['sub_action'] = $_REQUEST['sa']; + + // Load up all the tabs... + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['language_configuration'], + 'description' => $txt['language_description'], + ); + + // Call the right function for this sub-acton. + $subActions[$_REQUEST['sa']](); +} + +// Interface for adding a new language +function AddLanguage() +{ + global $context, $sourcedir, $forum_version, $boarddir, $txt, $smcFunc, $scripturl; + + // Are we searching for new languages courtesy of Simple Machines? + if (!empty($_POST['smf_add_sub'])) + { + // Need fetch_web_data. + require_once($sourcedir . '/Subs-Package.php'); + + $context['smf_search_term'] = htmlspecialchars(trim($_POST['smf_add'])); + + // We're going to use this URL. + $url = 'http://download.simplemachines.org/fetch_language.php?version=' . urlencode(strtr($forum_version, array('SMF ' => ''))); + + // Load the class file and stick it into an array. + loadClassFile('Class-Package.php'); + $language_list = new xmlArray(fetch_web_data($url), true); + + // Check it exists. + if (!$language_list->exists('languages')) + $context['smf_error'] = 'no_response'; + else + { + $language_list = $language_list->path('languages[0]'); + $lang_files = $language_list->set('language'); + $context['smf_languages'] = array(); + foreach ($lang_files as $file) + { + // Were we searching? + if (!empty($context['smf_search_term']) && strpos($file->fetch('name'), $smcFunc['strtolower']($context['smf_search_term'])) === false) + continue; + + $context['smf_languages'][] = array( + 'id' => $file->fetch('id'), + 'name' => $smcFunc['ucwords']($file->fetch('name')), + 'version' => $file->fetch('version'), + 'utf8' => $file->fetch('utf8'), + 'description' => $file->fetch('description'), + 'link' => $scripturl . '?action=admin;area=languages;sa=downloadlang;did=' . $file->fetch('id') . ';' . $context['session_var'] . '=' . $context['session_id'], + ); + } + if (empty($context['smf_languages'])) + $context['smf_error'] = 'no_files'; + } + } + + $context['sub_template'] = 'add_language'; +} + +// Download a language file from the Simple Machines website. +function DownloadLanguage() +{ + global $context, $sourcedir, $forum_version, $boarddir, $txt, $smcFunc, $scripturl, $modSettings; + + loadLanguage('ManageSettings'); + require_once($sourcedir . '/Subs-Package.php'); + + // Clearly we need to know what to request. + if (!isset($_GET['did'])) + fatal_lang_error('no_access', false); + + // Some lovely context. + $context['download_id'] = $_GET['did']; + $context['sub_template'] = 'download_language'; + $context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'add'; + + // Can we actually do the installation - and do they want to? + if (!empty($_POST['do_install']) && !empty($_POST['copy_file'])) + { + checkSession('get'); + + $chmod_files = array(); + $install_files = array(); + // Check writable status. + foreach ($_POST['copy_file'] as $file) + { + // Check it's not very bad. + if (strpos($file, '..') !== false || (substr($file, 0, 6) != 'Themes' && !preg_match('~agreement\.[A-Za-z-_0-9]+\.txt$~', $file))) + fatal_error($txt['languages_download_illegal_paths']); + + $chmod_files[] = $boarddir . '/' . $file; + $install_files[] = $file; + } + + // Call this in case we have work to do. + $file_status = create_chmod_control($chmod_files); + $files_left = $file_status['files']['notwritable']; + + // Something not writable? + if (!empty($files_left)) + $context['error_message'] = $txt['languages_download_not_chmod']; + // Otherwise, go go go! + elseif (!empty($install_files)) + { + $archive_content = read_tgz_file('http://download.simplemachines.org/fetch_language.php?version=' . urlencode(strtr($forum_version, array('SMF ' => ''))) . ';fetch=' . urlencode($_GET['did']), $boarddir, false, true, $install_files); + // Make sure the files aren't stuck in the cache. + package_flush_cache(); + $context['install_complete'] = sprintf($txt['languages_download_complete_desc'], $scripturl . '?action=admin;area=languages'); + + return; + } + } + + // Open up the old china. + if (!isset($archive_content)) + $archive_content = read_tgz_file('http://download.simplemachines.org/fetch_language.php?version=' . urlencode(strtr($forum_version, array('SMF ' => ''))) . ';fetch=' . urlencode($_GET['did']), null); + + if (empty($archive_content)) + fatal_error($txt['add_language_error_no_response']); + + // Now for each of the files, let's do some *stuff* + $context['files'] = array( + 'lang' => array(), + 'other' => array(), + ); + $context['make_writable'] = array(); + foreach ($archive_content as $file) + { + $dirname = dirname($file['filename']); + $filename = basename($file['filename']); + $extension = substr($filename, strrpos($filename, '.') + 1); + + // Don't do anything with files we don't understand. + if (!in_array($extension, array('php', 'jpg', 'gif', 'jpeg', 'png', 'txt'))) + continue; + + // Basic data. + $context_data = array( + 'name' => $filename, + 'destination' => $boarddir . '/' . $file['filename'], + 'generaldest' => $file['filename'], + 'size' => $file['size'], + // Does chmod status allow the copy? + 'writable' => false, + // Should we suggest they copy this file? + 'default_copy' => true, + // Does the file already exist, if so is it same or different? + 'exists' => false, + ); + + // Does the file exist, is it different and can we overwrite? + if (file_exists($boarddir . '/' . $file['filename'])) + { + if (is_writable($boarddir . '/' . $file['filename'])) + $context_data['writable'] = true; + + // Finally, do we actually think the content has changed? + if ($file['size'] == filesize($boarddir . '/' . $file['filename']) && $file['md5'] == md5_file($boarddir . '/' . $file['filename'])) + { + $context_data['exists'] = 'same'; + $context_data['default_copy'] = false; + } + // Attempt to discover newline character differences. + elseif ($file['md5'] == md5(preg_replace("~[\r]?\n~", "\r\n", file_get_contents($boarddir . '/' . $file['filename'])))) + { + $context_data['exists'] = 'same'; + $context_data['default_copy'] = false; + } + else + $context_data['exists'] = 'different'; + } + // No overwrite? + else + { + // Can we at least stick it in the directory... + if (is_writable($boarddir . '/' . $dirname)) + $context_data['writable'] = true; + } + + // I love PHP files, that's why I'm a developer and not an artistic type spending my time drinking absinth and living a life of sin... + if ($extension == 'php' && preg_match('~\w+\.\w+(?:-utf8)?\.php~', $filename)) + { + $context_data += array( + 'version' => '??', + 'cur_version' => false, + 'version_compare' => 'newer', + ); + + list ($name, $language) = explode('.', $filename); + + // Let's get the new version, I like versions, they tell me that I'm up to date. + if (preg_match('~\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '~i', $file['preview'], $match) == 1) + $context_data['version'] = $match[1]; + + // Now does the old file exist - if so what is it's version? + if (file_exists($boarddir . '/' . $file['filename'])) + { + // OK - what is the current version? + $fp = fopen($boarddir . '/' . $file['filename'], 'rb'); + $header = fread($fp, 768); + fclose($fp); + + // Find the version. + if (preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '(?:[\s]{2}|\*/)~i', $header, $match) == 1) + { + $context_data['cur_version'] = $match[1]; + + // How does this compare? + if ($context_data['cur_version'] == $context_data['version']) + $context_data['version_compare'] = 'same'; + elseif ($context_data['cur_version'] > $context_data['version']) + $context_data['version_compare'] = 'older'; + + // Don't recommend copying if the version is the same. + if ($context_data['version_compare'] != 'newer') + $context_data['default_copy'] = false; + } + } + + // Add the context data to the main set. + $context['files']['lang'][] = $context_data; + } + else + { + // If we think it's a theme thing, work out what the theme is. + if (substr($dirname, 0, 6) == 'Themes' && preg_match('~Themes[\\/]([^\\/]+)[\\/]~', $dirname, $match)) + $theme_name = $match[1]; + else + $theme_name = 'misc'; + + // Assume it's an image, could be an acceptance note etc but rare. + $context['files']['images'][$theme_name][] = $context_data; + } + + // Collect together all non-writable areas. + if (!$context_data['writable']) + $context['make_writable'][] = $context_data['destination']; + } + + // So, I'm a perfectionist - let's get the theme names. + $theme_indexes = array(); + foreach ($context['files']['images'] as $k => $dummy) + $indexes[] = $k; + + $context['theme_names'] = array(); + if (!empty($indexes)) + { + $value_data = array( + 'query' => array(), + 'params' => array(), + ); + + foreach ($indexes as $k => $index) + { + $value_data['query'][] = 'value LIKE {string:value_' . $k . '}'; + $value_data['params']['value_' . $k] = '%' . $index; + } + + $request = $smcFunc['db_query']('', ' + SELECT id_theme, value + FROM {db_prefix}themes + WHERE id_member = {int:no_member} + AND variable = {string:theme_dir} + AND (' . implode(' OR ', $value_data['query']) . ')', + array_merge($value_data['params'], array( + 'no_member' => 0, + 'theme_dir' => 'theme_dir', + 'index_compare_explode' => 'value LIKE \'%' . implode('\' OR value LIKE \'%', $indexes) . '\'', + )) + ); + $themes = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Find the right one. + foreach ($indexes as $index) + if (strpos($row['value'], $index) !== false) + $themes[$row['id_theme']] = $index; + } + $smcFunc['db_free_result']($request); + + if (!empty($themes)) + { + // Now we have the id_theme we can get the pretty description. + $request = $smcFunc['db_query']('', ' + SELECT id_theme, value + FROM {db_prefix}themes + WHERE id_member = {int:no_member} + AND variable = {string:name} + AND id_theme IN ({array_int:theme_list})', + array( + 'theme_list' => array_keys($themes), + 'no_member' => 0, + 'name' => 'name', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Now we have it... + $context['theme_names'][$themes[$row['id_theme']]] = $row['value']; + } + $smcFunc['db_free_result']($request); + } + } + + // Before we go to far can we make anything writable, eh, eh? + if (!empty($context['make_writable'])) + { + // What is left to be made writable? + $file_status = create_chmod_control($context['make_writable']); + $context['still_not_writable'] = $file_status['files']['notwritable']; + + // Mark those which are now writable as such. + foreach ($context['files'] as $type => $data) + { + if ($type == 'lang') + { + foreach ($data as $k => $file) + if (!$file['writable'] && !in_array($file['destination'], $context['still_not_writable'])) + $context['files'][$type][$k]['writable'] = true; + } + else + { + foreach ($data as $theme => $files) + foreach ($files as $k => $file) + if (!$file['writable'] && !in_array($file['destination'], $context['still_not_writable'])) + $context['files'][$type][$theme][$k]['writable'] = true; + } + } + + // Are we going to need more language stuff? + if (!empty($context['still_not_writable'])) + loadLanguage('Packages'); + } + + // This is the list for the main files. + $listOptions = array( + 'id' => 'lang_main_files_list', + 'title' => $txt['languages_download_main_files'], + 'get_items' => array( + 'function' => create_function('', ' + global $context; + return $context[\'files\'][\'lang\']; + '), + ), + 'columns' => array( + 'name' => array( + 'header' => array( + 'value' => $txt['languages_download_filename'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $context, $txt; + + return \'\' . $rowData[\'name\'] . \'
\' . $txt[\'languages_download_dest\'] . \': \' . $rowData[\'destination\'] . \'\' . ($rowData[\'version_compare\'] == \'older\' ? \'
\' . $txt[\'languages_download_older\'] : \'\'); + '), + ), + ), + 'writable' => array( + 'header' => array( + 'value' => $txt['languages_download_writable'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt; + + return \'\' . ($rowData[\'writable\'] ? $txt[\'yes\'] : $txt[\'no\']) . \'\'; + '), + 'style' => 'text-align: center', + ), + ), + 'version' => array( + 'header' => array( + 'value' => $txt['languages_download_version'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt; + + return \'\' . $rowData[\'version\'] . \'\'; + '), + ), + ), + 'exists' => array( + 'header' => array( + 'value' => $txt['languages_download_exists'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt; + + return $rowData[\'exists\'] ? ($rowData[\'exists\'] == \'same\' ? $txt[\'languages_download_exists_same\'] : $txt[\'languages_download_exists_different\']) : $txt[\'no\']; + '), + ), + ), + 'copy' => array( + 'header' => array( + 'value' => $txt['languages_download_copy'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + return \'\'; + '), + 'style' => 'text-align: center; width: 4%;', + ), + ), + ), + ); + + // Kill the cache, as it is now invalid.. + if (!empty($modSettings['cache_enable'])) + { + cache_put_data('known_languages', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600); + cache_put_data('known_languages_all', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600); + } + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + $context['default_list'] = 'lang_main_files_list'; +} + +// This lists all the current languages and allows editing of them. +function ModifyLanguages() +{ + global $txt, $context, $scripturl; + global $user_info, $smcFunc, $sourcedir, $language, $boarddir, $forum_version; + + // Setting a new default? + if (!empty($_POST['set_default']) && !empty($_POST['def_language'])) + { + checkSession(); + + getLanguages(true, false); + $lang_exists = false; + foreach ($context['languages'] as $lang) + { + if ($_POST['def_language'] == $lang['filename']) + { + $lang_exists = true; + break; + } + } + + if ($_POST['def_language'] != $language && $lang_exists) + { + require_once($sourcedir . '/Subs-Admin.php'); + updateSettingsFile(array('language' => '\'' . $_POST['def_language'] . '\'')); + $language = $_POST['def_language']; + } + } + + $listOptions = array( + 'id' => 'language_list', + 'items_per_page' => 20, + 'base_href' => $scripturl . '?action=admin;area=languages', + 'title' => $txt['edit_languages'], + 'get_items' => array( + 'function' => 'list_getLanguages', + ), + 'get_count' => array( + 'function' => 'list_getNumLanguages', + ), + 'columns' => array( + 'default' => array( + 'header' => array( + 'value' => $txt['languages_default'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + return \'\'; + '), + 'style' => 'text-align: center; width: 8%;', + ), + ), + 'name' => array( + 'header' => array( + 'value' => $txt['languages_lang_name'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $scripturl, $context; + + return sprintf(\'%3$s\', $scripturl, $rowData[\'id\'], $rowData[\'name\']); + '), + ), + ), + 'character_set' => array( + 'header' => array( + 'value' => $txt['languages_character_set'], + ), + 'data' => array( + 'db_htmlsafe' => 'char_set', + ), + ), + 'count' => array( + 'header' => array( + 'value' => $txt['languages_users'], + ), + 'data' => array( + 'db_htmlsafe' => 'count', + 'style' => 'text-align: center', + ), + ), + 'locale' => array( + 'header' => array( + 'value' => $txt['languages_locale'], + ), + 'data' => array( + 'db_htmlsafe' => 'locale', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=languages', + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => '', + 'style' => 'text-align: right;', + ), + ), + // For highlighting the default. + 'javascript' => ' + var prevClass = ""; + var prevDiv = ""; + function highlightSelected(box) + { + if (prevClass != "") + prevDiv.className = prevClass; + + prevDiv = document.getElementById(box); + prevClass = prevDiv.className; + + prevDiv.className = "highlight2"; + } + highlightSelected("list_language_list_' . ($language == '' ? 'english' : $language). '"); + ', + ); + + // Display a warning if we cannot edit the default setting. + if (!is_writable($boarddir . '/Settings.php')) + $listOptions['additional_rows'][] = array( + 'position' => 'after_title', + 'value' => $txt['language_settings_writable'], + 'class' => 'smalltext alert', + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'language_list'; +} + +// How many languages? +function list_getNumLanguages() +{ + global $settings; + + // Return how many we have. + return count(getLanguages(true, false)); +} + +// Fetch the actual language information. +function list_getLanguages() +{ + global $settings, $smcFunc, $language, $context, $txt; + + $languages = array(); + // Keep our old entries. + $old_txt = $txt; + $backup_actual_theme_dir = $settings['actual_theme_dir']; + $backup_base_theme_dir = !empty($settings['base_theme_dir']) ? $settings['base_theme_dir'] : ''; + + // Override these for now. + $settings['actual_theme_dir'] = $settings['base_theme_dir'] = $settings['default_theme_dir']; + getLanguages(true, false); + + // Put them back. + $settings['actual_theme_dir'] = $backup_actual_theme_dir; + if (!empty($backup_base_theme_dir)) + $settings['base_theme_dir'] = $backup_base_theme_dir; + else + unset($settings['base_theme_dir']); + + // Get the language files and data... + foreach ($context['languages'] as $lang) + { + // Load the file to get the character set. + require($settings['default_theme_dir'] . '/languages/index.' . $lang['filename'] . '.php'); + + $languages[$lang['filename']] = array( + 'id' => $lang['filename'], + 'count' => 0, + 'char_set' => $txt['lang_character_set'], + 'default' => $language == $lang['filename'] || ($language == '' && $lang['filename'] == 'english'), + 'locale' => $txt['lang_locale'], + 'name' => $smcFunc['ucwords'](strtr($lang['filename'], array('_' => ' ', '-utf8' => ''))), + ); + } + + // Work out how many people are using each language. + $request = $smcFunc['db_query']('', ' + SELECT lngfile, COUNT(*) AS num_users + FROM {db_prefix}members + GROUP BY lngfile', + array( + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Default? + if (empty($row['lngfile']) || !isset($languages[$row['lngfile']])) + $row['lngfile'] = $language; + + if (!isset($languages[$row['lngfile']]) && isset($languages['english'])) + $languages['english']['count'] += $row['num_users']; + elseif (isset($languages[$row['lngfile']])) + $languages[$row['lngfile']]['count'] += $row['num_users']; + } + $smcFunc['db_free_result']($request); + + // Restore the current users language. + $txt = $old_txt; + + // Return how many we have. + return $languages; +} + +// Edit language related settings. +function ModifyLanguageSettings($return_config = false) +{ + global $scripturl, $context, $txt, $boarddir, $settings, $smcFunc; + + // Warn the user if the backup of Settings.php failed. + $settings_not_writable = !is_writable($boarddir . '/Settings.php'); + $settings_backup_fail = !@is_writable($boarddir . '/Settings_bak.php') || !@copy($boarddir . '/Settings.php', $boarddir . '/Settings_bak.php'); + + /* If you're writing a mod, it's a bad idea to add things here.... + For each option: + variable name, description, type (constant), size/possible values, helptext. + OR an empty string for a horizontal rule. + OR a string for a titled section. */ + $config_vars = array( + 'language' => array('language', $txt['default_language'], 'file', 'select', array(), null, 'disabled' => $settings_not_writable), + array('userLanguage', $txt['userLanguage'], 'db', 'check', null, 'userLanguage'), + ); + + if ($return_config) + return $config_vars; + + // Get our languages. No cache and use utf8. + getLanguages(false, false); + foreach ($context['languages'] as $lang) + $config_vars['language'][4][$lang['filename']] = array($lang['filename'], strtr($lang['name'], array('-utf8' => ' (UTF-8)'))); + + // Saving settings? + if (isset($_REQUEST['save'])) + { + checkSession(); + saveSettings($config_vars); + redirectexit('action=admin;area=languages;sa=settings'); + } + + // Setup the template stuff. + $context['post_url'] = $scripturl . '?action=admin;area=languages;sa=settings;save'; + $context['settings_title'] = $txt['language_settings']; + $context['save_disabled'] = $settings_not_writable; + + if ($settings_not_writable) + $context['settings_message'] = '
' . $txt['settings_not_writable'] . '

'; + elseif ($settings_backup_fail) + $context['settings_message'] = '
' . $txt['admin_backup_fail'] . '

'; + + // Fill the config array. + prepareServerSettingsContext($config_vars); +} + +// Edit a particular set of language entries. +function ModifyLanguage() +{ + global $settings, $context, $smcFunc, $txt, $modSettings, $boarddir, $sourcedir, $language; + + loadLanguage('ManageSettings'); + + // Select the languages tab. + $context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'edit'; + $context['page_title'] = $txt['edit_languages']; + $context['sub_template'] = 'modify_language_entries'; + + $context['lang_id'] = $_GET['lid']; + list($theme_id, $file_id) = empty($_REQUEST['tfid']) || strpos($_REQUEST['tfid'], '+') === false ? array(1, '') : explode('+', $_REQUEST['tfid']); + + // Clean the ID - just in case. + preg_match('~([A-Za-z0-9_-]+)~', $context['lang_id'], $matches); + $context['lang_id'] = $matches[1]; + + // Get all the theme data. + $request = $smcFunc['db_query']('', ' + SELECT id_theme, variable, value + FROM {db_prefix}themes + WHERE id_theme != {int:default_theme} + AND id_member = {int:no_member} + AND variable IN ({string:name}, {string:theme_dir})', + array( + 'default_theme' => 1, + 'no_member' => 0, + 'name' => 'name', + 'theme_dir' => 'theme_dir', + ) + ); + $themes = array( + 1 => array( + 'name' => $txt['dvc_default'], + 'theme_dir' => $settings['default_theme_dir'], + ), + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $themes[$row['id_theme']][$row['variable']] = $row['value']; + $smcFunc['db_free_result']($request); + + // This will be where we look + $lang_dirs = array(); + // Check we have themes with a path and a name - just in case - and add the path. + foreach ($themes as $id => $data) + { + if (count($data) != 2) + unset($themes[$id]); + elseif (is_dir($data['theme_dir'] . '/languages')) + $lang_dirs[$id] = $data['theme_dir'] . '/languages'; + + // How about image directories? + if (is_dir($data['theme_dir'] . '/images/' . $context['lang_id'])) + $images_dirs[$id] = $data['theme_dir'] . '/images/' . $context['lang_id']; + } + + $current_file = $file_id ? $lang_dirs[$theme_id] . '/' . $file_id . '.' . $context['lang_id'] . '.php' : ''; + + // Now for every theme get all the files and stick them in context! + $context['possible_files'] = array(); + foreach ($lang_dirs as $theme => $theme_dir) + { + // Open it up. + $dir = dir($theme_dir); + while ($entry = $dir->read()) + { + // We're only after the files for this language. + if (preg_match('~^([A-Za-z]+)\.' . $context['lang_id'] . '\.php$~', $entry, $matches) == 0) + continue; + + //!!! Temp! + if ($matches[1] == 'EmailTemplates') + continue; + + if (!isset($context['possible_files'][$theme])) + $context['possible_files'][$theme] = array( + 'id' => $theme, + 'name' => $themes[$theme]['name'], + 'files' => array(), + ); + + $context['possible_files'][$theme]['files'][] = array( + 'id' => $matches[1], + 'name' => isset($txt['lang_file_desc_' . $matches[1]]) ? $txt['lang_file_desc_' . $matches[1]] : $matches[1], + 'selected' => $theme_id == $theme && $file_id == $matches[1], + ); + } + $dir->close(); + } + + // We no longer wish to speak this language. + if (!empty($_POST['delete_main']) && $context['lang_id'] != 'english') + { + checkSession(); + + // !!! Todo: FTP Controls? + require_once($sourcedir . '/Subs-Package.php'); + + // First, Make a backup? + if (!empty($modSettings['package_make_backups']) && (!isset($_SESSION['last_backup_for']) || $_SESSION['last_backup_for'] != $context['lang_id'] . '$$$')) + { + $_SESSION['last_backup_for'] = $context['lang_id'] . '$$$'; + package_create_backup('backup_lang_' . $context['lang_id']); + } + + // Second, loop through the array to remove the files. + foreach ($lang_dirs as $curPath) + { + foreach ($context['possible_files'][1]['files'] as $lang) + if (file_exists($curPath . '/' . $lang['id'] . '.' . $context['lang_id'] . '.php')) + unlink($curPath . '/' . $lang['id'] . '.' . $context['lang_id'] . '.php'); + + // Check for the email template. + if (file_exists($curPath . '/EmailTemplates.' . $context['lang_id'] . '.php')) + unlink($curPath . '/EmailTemplates.' . $context['lang_id'] . '.php'); + } + + // Third, the agreement file. + if (file_exists($boarddir . '/agreement.' . $context['lang_id'] . '.txt')) + unlink($boarddir . '/agreement.' . $context['lang_id'] . '.txt'); + + // Fourth, a related images folder? + foreach ($images_dirs as $curPath) + if (is_dir($curPath)) + deltree($curPath); + + // Members can no longer use this language. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET lngfile = {string:empty_string} + WHERE lngfile = {string:current_language}', + array( + 'empty_string' => '', + 'current_language' => $context['lang_id'], + ) + ); + + // Fifth, update getLanguages() cache. + if (!empty($modSettings['cache_enable'])) + { + cache_put_data('known_languages', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600); + cache_put_data('known_languages_all', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600); + } + + // Sixth, if we deleted the default language, set us back to english? + if ($context['lang_id'] == $language) + { + require_once($sourcedir . '/Subs-Admin.php'); + $language = 'english'; + updateSettingsFile(array('language' => '\'' . $language . '\'')); + } + + // Seventh, get out of here. + redirectexit('action=admin;area=languages;sa=edit;' . $context['session_var'] . '=' . $context['session_id']); + } + + // Saving primary settings? + $madeSave = false; + if (!empty($_POST['save_main']) && !$current_file) + { + checkSession(); + + // Read in the current file. + $current_data = implode('', file($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php')); + // These are the replacements. old => new + $replace_array = array( + '~\$txt\[\'lang_character_set\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_character_set\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['character_set']) . '\';', + '~\$txt\[\'lang_locale\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_locale\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['locale']) . '\';', + '~\$txt\[\'lang_dictionary\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_dictionary\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['dictionary']) . '\';', + '~\$txt\[\'lang_spelling\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_spelling\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['spelling']) . '\';', + '~\$txt\[\'lang_rtl\'\]\s=\s[A-Za-z0-9]+;~' => '$txt[\'lang_rtl\'] = ' . (!empty($_POST['rtl']) ? 'true' : 'false') . ';', + ); + $current_data = preg_replace(array_keys($replace_array), array_values($replace_array), $current_data); + $fp = fopen($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php', 'w+'); + fwrite($fp, $current_data); + fclose($fp); + + $madeSave = true; + } + + // Quickly load index language entries. + $old_txt = $txt; + require($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php'); + $context['lang_file_not_writable_message'] = is_writable($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php') ? '' : sprintf($txt['lang_file_not_writable'], $settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php'); + // Setup the primary settings context. + $context['primary_settings'] = array( + 'name' => $smcFunc['ucwords'](strtr($context['lang_id'], array('_' => ' ', '-utf8' => ''))), + 'character_set' => $txt['lang_character_set'], + 'locale' => $txt['lang_locale'], + 'dictionary' => $txt['lang_dictionary'], + 'spelling' => $txt['lang_spelling'], + 'rtl' => $txt['lang_rtl'], + ); + + // Restore normal service. + $txt = $old_txt; + + // Are we saving? + $save_strings = array(); + if (isset($_POST['save_entries']) && !empty($_POST['entry'])) + { + checkSession(); + + // Clean each entry! + foreach ($_POST['entry'] as $k => $v) + { + // Only try to save if it's changed! + if ($_POST['entry'][$k] != $_POST['comp'][$k]) + $save_strings[$k] = cleanLangString($v, false); + } + } + + // If we are editing a file work away at that. + if ($current_file) + { + $context['entries_not_writable_message'] = is_writable($current_file) ? '' : sprintf($txt['lang_entries_not_writable'], $current_file); + + $entries = array(); + // We can't just require it I'm afraid - otherwise we pass in all kinds of variables! + $multiline_cache = ''; + foreach (file($current_file) as $line) + { + // Got a new entry? + if ($line[0] == '$' && !empty($multiline_cache)) + { + preg_match('~\$(helptxt|txt)\[\'(.+)\'\]\s=\s(.+);~', strtr($multiline_cache, array("\n" => '', "\t" => '')), $matches); + if (!empty($matches[3])) + { + $entries[$matches[2]] = array( + 'type' => $matches[1], + 'full' => $matches[0], + 'entry' => $matches[3], + ); + $multiline_cache = ''; + } + } + $multiline_cache .= $line . "\n"; + } + // Last entry to add? + if ($multiline_cache) + { + preg_match('~\$(helptxt|txt)\[\'(.+)\'\]\s=\s(.+);~', strtr($multiline_cache, array("\n" => '', "\t" => '')), $matches); + if (!empty($matches[3])) + $entries[$matches[2]] = array( + 'type' => $matches[1], + 'full' => $matches[0], + 'entry' => $matches[3], + ); + } + + // These are the entries we can definitely save. + $final_saves = array(); + + $context['file_entries'] = array(); + foreach ($entries as $entryKey => $entryValue) + { + // Ignore some things we set separately. + $ignore_files = array('lang_character_set', 'lang_locale', 'lang_dictionary', 'lang_spelling', 'lang_rtl'); + if (in_array($entryKey, $ignore_files)) + continue; + + // These are arrays that need breaking out. + $arrays = array('days', 'days_short', 'months', 'months_titles', 'months_short'); + if (in_array($entryKey, $arrays)) + { + // Get off the first bits. + $entryValue['entry'] = substr($entryValue['entry'], strpos($entryValue['entry'], '(') + 1, strrpos($entryValue['entry'], ')') - strpos($entryValue['entry'], '(')); + $entryValue['entry'] = explode(',', strtr($entryValue['entry'], array(' ' => ''))); + + // Now create an entry for each item. + $cur_index = 0; + $save_cache = array( + 'enabled' => false, + 'entries' => array(), + ); + foreach ($entryValue['entry'] as $id => $subValue) + { + // Is this a new index? + if (preg_match('~^(\d+)~', $subValue, $matches)) + { + $cur_index = $matches[1]; + $subValue = substr($subValue, strpos($subValue, '\'')); + } + + // Clean up some bits. + $subValue = strtr($subValue, array('"' => '', '\'' => '', ')' => '')); + + // Can we save? + if (isset($save_strings[$entryKey . '-+- ' . $cur_index])) + { + $save_cache['entries'][$cur_index] = strtr($save_strings[$entryKey . '-+- ' . $cur_index], array('\'' => '')); + $save_cache['enabled'] = true; + } + else + $save_cache['entries'][$cur_index] = $subValue; + + $context['file_entries'][] = array( + 'key' => $entryKey . '-+- ' . $cur_index, + 'value' => $subValue, + 'rows' => 1, + ); + $cur_index++; + } + + // Do we need to save? + if ($save_cache['enabled']) + { + // Format the string, checking the indexes first. + $items = array(); + $cur_index = 0; + foreach ($save_cache['entries'] as $k2 => $v2) + { + // Manually show the custom index. + if ($k2 != $cur_index) + { + $items[] = $k2 . ' => \'' . $v2 . '\''; + $cur_index = $k2; + } + else + $items[] = '\'' . $v2 . '\''; + + $cur_index++; + } + // Now create the string! + $final_saves[$entryKey] = array( + 'find' => $entryValue['full'], + 'replace' => '$' . $entryValue['type'] . '[\'' . $entryKey . '\'] = array(' . implode(', ', $items) . ');', + ); + } + } + else + { + // Saving? + if (isset($save_strings[$entryKey]) && $save_strings[$entryKey] != $entryValue['entry']) + { + // !!! Fix this properly. + if ($save_strings[$entryKey] == '') + $save_strings[$entryKey] = '\'\''; + + // Set the new value. + $entryValue['entry'] = $save_strings[$entryKey]; + // And we know what to save now! + $final_saves[$entryKey] = array( + 'find' => $entryValue['full'], + 'replace' => '$' . $entryValue['type'] . '[\'' . $entryKey . '\'] = ' . $save_strings[$entryKey] . ';', + ); + } + + $editing_string = cleanLangString($entryValue['entry'], true); + $context['file_entries'][] = array( + 'key' => $entryKey, + 'value' => $editing_string, + 'rows' => (int) (strlen($editing_string) / 38) + substr_count($editing_string, "\n") + 1, + ); + } + } + + // Any saves to make? + if (!empty($final_saves)) + { + checkSession(); + + $file_contents = implode('', file($current_file)); + foreach ($final_saves as $save) + $file_contents = strtr($file_contents, array($save['find'] => $save['replace'])); + + // Save the actual changes. + $fp = fopen($current_file, 'w+'); + fwrite($fp, $file_contents); + fclose($fp); + + $madeSave = true; + } + + // Another restore. + $txt = $old_txt; + } + + // If we saved, redirect. + if ($madeSave) + redirectexit('action=admin;area=languages;sa=editlang;lid=' . $context['lang_id']); +} + +// This function could be two functions - either way it cleans language entries to/from display. +function cleanLangString($string, $to_display = true) +{ + global $smcFunc; + + // If going to display we make sure it doesn't have any HTML in it - etc. + $new_string = ''; + if ($to_display) + { + // Are we in a string (0 = no, 1 = single quote, 2 = parsed) + $in_string = 0; + $is_escape = false; + for ($i = 0; $i < strlen($string); $i++) + { + // Handle ecapes first. + if ($string{$i} == '\\') + { + // Toggle the escape. + $is_escape = !$is_escape; + // If we're now escaped don't add this string. + if ($is_escape) + continue; + } + // Special case - parsed string with line break etc? + elseif (($string{$i} == 'n' || $string{$i} == 't') && $in_string == 2 && $is_escape) + { + // Put the escape back... + $new_string .= $string{$i} == 'n' ? "\n" : "\t"; + $is_escape = false; + continue; + } + // Have we got a single quote? + elseif ($string{$i} == '\'') + { + // Already in a parsed string, or escaped in a linear string, means we print it - otherwise something special. + if ($in_string != 2 && ($in_string != 1 || !$is_escape)) + { + // Is it the end of a single quote string? + if ($in_string == 1) + $in_string = 0; + // Otherwise it's the start! + else + $in_string = 1; + + // Don't actually include this character! + continue; + } + } + // Otherwise a double quote? + elseif ($string{$i} == '"') + { + // Already in a single quote string, or escaped in a parsed string, means we print it - otherwise something special. + if ($in_string != 1 && ($in_string != 2 || !$is_escape)) + { + // Is it the end of a double quote string? + if ($in_string == 2) + $in_string = 0; + // Otherwise it's the start! + else + $in_string = 2; + + // Don't actually include this character! + continue; + } + } + // A join/space outside of a string is simply removed. + elseif ($in_string == 0 && (empty($string{$i}) || $string{$i} == '.')) + continue; + // Start of a variable? + elseif ($in_string == 0 && $string{$i} == '$') + { + // Find the whole of it! + preg_match('~([\$A-Za-z0-9\'\[\]_-]+)~', substr($string, $i), $matches); + if (!empty($matches[1])) + { + // Come up with some pseudo thing to indicate this is a var. + //!!! Do better than this, please! + $new_string .= '{%' . $matches[1] . '%}'; + + // We're not going to reparse this. + $i += strlen($matches[1]) - 1; + } + + continue; + } + // Right, if we're outside of a string we have DANGER, DANGER! + elseif ($in_string == 0) + { + continue; + } + + // Actually add the character to the string! + $new_string .= $string{$i}; + // If anything was escaped it ain't any longer! + $is_escape = false; + } + + // Unhtml then rehtml the whole thing! + $new_string = htmlspecialchars(un_htmlspecialchars($new_string)); + } + else + { + // Keep track of what we're doing... + $in_string = 0; + // This is for deciding whether to HTML a quote. + $in_html = false; + for ($i = 0; $i < strlen($string); $i++) + { + // Handle line breaks! + if ($string{$i} == "\n" || $string{$i} == "\t") + { + // Are we in a string? Is it the right type? + if ($in_string == 1) + { + // Change type! + $new_string .= '\' . "\\' . ($string{$i} == "\n" ? 'n' : 't'); + $in_string = 2; + } + elseif ($in_string == 2) + $new_string .= '\\' . ($string{$i} == "\n" ? 'n' : 't'); + // Otherwise start one off - joining if required. + else + $new_string .= ($new_string ? ' . ' : '') . '"\\' . ($string{$i} == "\n" ? 'n' : 't'); + + continue; + } + // We don't do parsed strings apart from for breaks. + elseif ($in_string == 2) + { + $in_string = 0; + $new_string .= '"'; + } + + // Not in a string yet? + if ($in_string != 1) + { + $in_string = 1; + $new_string .= ($new_string ? ' . ' : '') . '\''; + } + + // Is this a variable? + if ($string{$i} == '{' && $string{$i + 1} == '%' && $string{$i + 2} == '$') + { + // Grab the variable. + preg_match('~\{%([\$A-Za-z0-9\'\[\]_-]+)%\}~', substr($string, $i), $matches); + if (!empty($matches[1])) + { + if ($in_string == 1) + $new_string .= '\' . '; + elseif ($new_string) + $new_string .= ' . '; + + $new_string .= $matches[1]; + $i += strlen($matches[1]) + 3; + $in_string = 0; + } + + continue; + } + // Is this a lt sign? + elseif ($string{$i} == '<') + { + // Probably HTML? + if ($string{$i + 1} != ' ') + $in_html = true; + // Assume we need an entity... + else + { + $new_string .= '<'; + continue; + } + } + // What about gt? + elseif ($string{$i} == '>') + { + // Will it be HTML? + if ($in_html) + $in_html = false; + // Otherwise we need an entity... + else + { + $new_string .= '>'; + continue; + } + } + // Is it a slash? If so escape it... + if ($string{$i} == '\\') + $new_string .= '\\'; + // The infamous double quote? + elseif ($string{$i} == '"') + { + // If we're in HTML we leave it as a quote - otherwise we entity it. + if (!$in_html) + { + $new_string .= '"'; + continue; + } + } + // A single quote? + elseif ($string{$i} == '\'') + { + // Must be in a string so escape it. + $new_string .= '\\'; + } + + // Finally add the character to the string! + $new_string .= $string{$i}; + } + + // If we ended as a string then close it off. + if ($in_string == 1) + $new_string .= '\''; + elseif ($in_string == 2) + $new_string .= '"'; + } + + return $new_string; +} + +// Helper function, it sets up the context for the manage server settings. +function prepareServerSettingsContext(&$config_vars) +{ + global $context, $modSettings; + + $context['config_vars'] = array(); + foreach ($config_vars as $identifier => $config_var) + { + if (!is_array($config_var) || !isset($config_var[1])) + $context['config_vars'][] = $config_var; + else + { + $varname = $config_var[0]; + global $$varname; + + $context['config_vars'][] = array( + 'label' => $config_var[1], + 'help' => isset($config_var[5]) ? $config_var[5] : '', + 'type' => $config_var[3], + 'size' => empty($config_var[4]) ? 0 : $config_var[4], + 'data' => isset($config_var[4]) && is_array($config_var[4]) ? $config_var[4] : array(), + 'name' => $config_var[0], + 'value' => $config_var[2] == 'file' ? htmlspecialchars($$varname) : (isset($modSettings[$config_var[0]]) ? htmlspecialchars($modSettings[$config_var[0]]) : (in_array($config_var[3], array('int', 'float')) ? 0 : '')), + 'disabled' => !empty($context['settings_not_writable']) || !empty($config_var['disabled']), + 'invalid' => false, + 'javascript' => '', + 'preinput' => '', + 'postinput' => '', + ); + } + } +} + +// Helper function, it sets up the context for database settings. +function prepareDBSettingContext(&$config_vars) +{ + global $txt, $helptxt, $context, $modSettings, $sourcedir; + + loadLanguage('Help'); + + $context['config_vars'] = array(); + $inlinePermissions = array(); + $bbcChoice = array(); + foreach ($config_vars as $config_var) + { + // HR? + if (!is_array($config_var)) + $context['config_vars'][] = $config_var; + else + { + // If it has no name it doesn't have any purpose! + if (empty($config_var[1])) + continue; + + // Special case for inline permissions + if ($config_var[0] == 'permissions' && allowedTo('manage_permissions')) + $inlinePermissions[] = $config_var[1]; + elseif ($config_var[0] == 'permissions') + continue; + + // Are we showing the BBC selection box? + if ($config_var[0] == 'bbc') + $bbcChoice[] = $config_var[1]; + + $context['config_vars'][$config_var[1]] = array( + 'label' => isset($config_var['text_label']) ? $config_var['text_label'] : (isset($txt[$config_var[1]]) ? $txt[$config_var[1]] : (isset($config_var[3]) && !is_array($config_var[3]) ? $config_var[3] : '')), + 'help' => isset($helptxt[$config_var[1]]) ? $config_var[1] : '', + 'type' => $config_var[0], + 'size' => !empty($config_var[2]) && !is_array($config_var[2]) ? $config_var[2] : (in_array($config_var[0], array('int', 'float')) ? 6 : 0), + 'data' => array(), + 'name' => $config_var[1], + 'value' => isset($modSettings[$config_var[1]]) ? ($config_var[0] == 'select' ? $modSettings[$config_var[1]] : htmlspecialchars($modSettings[$config_var[1]])) : (in_array($config_var[0], array('int', 'float')) ? 0 : ''), + 'disabled' => false, + 'invalid' => !empty($config_var['invalid']), + 'javascript' => '', + 'var_message' => !empty($config_var['message']) && isset($txt[$config_var['message']]) ? $txt[$config_var['message']] : '', + 'preinput' => isset($config_var['preinput']) ? $config_var['preinput'] : '', + 'postinput' => isset($config_var['postinput']) ? $config_var['postinput'] : '', + ); + + // If this is a select box handle any data. + if (!empty($config_var[2]) && is_array($config_var[2])) + { + // If we allow multiple selections, we need to adjust a few things. + if ($config_var[0] == 'select' && !empty($config_var['multiple'])) + { + $context['config_vars'][$config_var[1]]['name'] .= '[]'; + $context['config_vars'][$config_var[1]]['value'] = unserialize($context['config_vars'][$config_var[1]]['value']); + } + + // If it's associative + if (isset($config_var[2][0]) && is_array($config_var[2][0])) + $context['config_vars'][$config_var[1]]['data'] = $config_var[2]; + else + { + foreach ($config_var[2] as $key => $item) + $context['config_vars'][$config_var[1]]['data'][] = array($key, $item); + } + } + + // Finally allow overrides - and some final cleanups. + foreach ($config_var as $k => $v) + { + if (!is_numeric($k)) + { + if (substr($k, 0, 2) == 'on') + $context['config_vars'][$config_var[1]]['javascript'] .= ' ' . $k . '="' . $v . '"'; + else + $context['config_vars'][$config_var[1]][$k] = $v; + } + + // See if there are any other labels that might fit? + if (isset($txt['setting_' . $config_var[1]])) + $context['config_vars'][$config_var[1]]['label'] = $txt['setting_' . $config_var[1]]; + elseif (isset($txt['groups_' . $config_var[1]])) + $context['config_vars'][$config_var[1]]['label'] = $txt['groups_' . $config_var[1]]; + } + + // Set the subtext in case it's part of the label. + // !!! Temporary. Preventing divs inside label tags. + $divPos = strpos($context['config_vars'][$config_var[1]]['label'], ']*>~', '', substr($context['config_vars'][$config_var[1]]['label'], $divPos)); + $context['config_vars'][$config_var[1]]['label'] = substr($context['config_vars'][$config_var[1]]['label'], 0, $divPos); + } + } + } + + // If we have inline permissions we need to prep them. + if (!empty($inlinePermissions) && allowedTo('manage_permissions')) + { + require_once($sourcedir . '/ManagePermissions.php'); + init_inline_permissions($inlinePermissions, isset($context['permissions_excluded']) ? $context['permissions_excluded'] : array()); + } + + // What about any BBC selection boxes? + if (!empty($bbcChoice)) + { + // What are the options, eh? + $temp = parse_bbc(false); + $bbcTags = array(); + foreach ($temp as $tag) + $bbcTags[] = $tag['tag']; + + $bbcTags = array_unique($bbcTags); + $totalTags = count($bbcTags); + + // The number of columns we want to show the BBC tags in. + $numColumns = isset($context['num_bbc_columns']) ? $context['num_bbc_columns'] : 3; + + // Start working out the context stuff. + $context['bbc_columns'] = array(); + $tagsPerColumn = ceil($totalTags / $numColumns); + + $col = 0; $i = 0; + foreach ($bbcTags as $tag) + { + if ($i % $tagsPerColumn == 0 && $i != 0) + $col++; + + $context['bbc_columns'][$col][] = array( + 'tag' => $tag, + // !!! 'tag_' . ? + 'show_help' => isset($helptxt[$tag]), + ); + + $i++; + } + + // Now put whatever BBC options we may have into context too! + $context['bbc_sections'] = array(); + foreach ($bbcChoice as $bbc) + { + $context['bbc_sections'][$bbc] = array( + 'title' => isset($txt['bbc_title_' . $bbc]) ? $txt['bbc_title_' . $bbc] : $txt['bbcTagsToUse_select'], + 'disabled' => empty($modSettings['bbc_disabled_' . $bbc]) ? array() : $modSettings['bbc_disabled_' . $bbc], + 'all_selected' => empty($modSettings['bbc_disabled_' . $bbc]), + ); + } + } +} + +// Helper function. Saves settings by putting them in Settings.php or saving them in the settings table. +function saveSettings(&$config_vars) +{ + global $boarddir, $sc, $cookiename, $modSettings, $user_settings; + global $sourcedir, $context, $cachedir; + + // Fix the darn stupid cookiename! (more may not be allowed, but these for sure!) + if (isset($_POST['cookiename'])) + $_POST['cookiename'] = preg_replace('~[,;\s\.$]+~' . ($context['utf8'] ? 'u' : ''), '', $_POST['cookiename']); + + // Fix the forum's URL if necessary. + if (isset($_POST['boardurl'])) + { + if (substr($_POST['boardurl'], -10) == '/index.php') + $_POST['boardurl'] = substr($_POST['boardurl'], 0, -10); + elseif (substr($_POST['boardurl'], -1) == '/') + $_POST['boardurl'] = substr($_POST['boardurl'], 0, -1); + if (substr($_POST['boardurl'], 0, 7) != 'http://' && substr($_POST['boardurl'], 0, 7) != 'file://' && substr($_POST['boardurl'], 0, 8) != 'https://') + $_POST['boardurl'] = 'http://' . $_POST['boardurl']; + } + + // Any passwords? + $config_passwords = array( + 'db_passwd', + 'ssi_db_passwd', + ); + + // All the strings to write. + $config_strs = array( + 'mtitle', 'mmessage', + 'language', 'mbname', 'boardurl', + 'cookiename', + 'webmaster_email', + 'db_name', 'db_user', 'db_server', 'db_prefix', 'ssi_db_user', + 'boarddir', 'sourcedir', 'cachedir', + ); + // All the numeric variables. + $config_ints = array( + ); + // All the checkboxes. + $config_bools = array( + 'db_persist', 'db_error_send', + 'maintenance', + ); + + // Now sort everything into a big array, and figure out arrays and etc. + $new_settings = array(); + foreach ($config_passwords as $config_var) + { + if (isset($_POST[$config_var][1]) && $_POST[$config_var][0] == $_POST[$config_var][1]) + $new_settings[$config_var] = '\'' . addcslashes($_POST[$config_var][0], '\'\\') . '\''; + } + foreach ($config_strs as $config_var) + { + if (isset($_POST[$config_var])) + $new_settings[$config_var] = '\'' . addcslashes($_POST[$config_var], '\'\\') . '\''; + } + foreach ($config_ints as $config_var) + { + if (isset($_POST[$config_var])) + $new_settings[$config_var] = (int) $_POST[$config_var]; + } + foreach ($config_bools as $key) + { + if (!empty($_POST[$key])) + $new_settings[$key] = '1'; + else + $new_settings[$key] = '0'; + } + + // Save the relevant settings in the Settings.php file. + require_once($sourcedir . '/Subs-Admin.php'); + updateSettingsFile($new_settings); + + // Now loopt through the remaining (database-based) settings. + $new_settings = array(); + foreach ($config_vars as $config_var) + { + // We just saved the file-based settings, so skip their definitions. + if (!is_array($config_var) || $config_var[2] == 'file') + continue; + + // Rewrite the definition a bit. + $new_settings[] = array($config_var[3], $config_var[0]); + } + + // Save the new database-based settings, if any. + if (!empty($new_settings)) + saveDBSettings($new_settings); +} + +// Helper function for saving database settings. +function saveDBSettings(&$config_vars) +{ + global $sourcedir, $context; + + $inlinePermissions = array(); + foreach ($config_vars as $var) + { + if (!isset($var[1]) || (!isset($_POST[$var[1]]) && $var[0] != 'check' && $var[0] != 'permissions' && ($var[0] != 'bbc' || !isset($_POST[$var[1] . '_enabledTags'])))) + continue; + + // Checkboxes! + elseif ($var[0] == 'check') + $setArray[$var[1]] = !empty($_POST[$var[1]]) ? '1' : '0'; + // Select boxes! + elseif ($var[0] == 'select' && in_array($_POST[$var[1]], array_keys($var[2]))) + $setArray[$var[1]] = $_POST[$var[1]]; + elseif ($var[0] == 'select' && !empty($var['multiple']) && array_intersect($_POST[$var[1]], array_keys($var[2])) != array()) + { + // For security purposes we validate this line by line. + $options = array(); + foreach ($_POST[$var[1]] as $invar) + if (in_array($invar, array_keys($var[2]))) + $options[] = $invar; + + $setArray[$var[1]] = serialize($options); + } + // Integers! + elseif ($var[0] == 'int') + $setArray[$var[1]] = (int) $_POST[$var[1]]; + // Floating point! + elseif ($var[0] == 'float') + $setArray[$var[1]] = (float) $_POST[$var[1]]; + // Text! + elseif ($var[0] == 'text' || $var[0] == 'large_text') + $setArray[$var[1]] = $_POST[$var[1]]; + // Passwords! + elseif ($var[0] == 'password') + { + if (isset($_POST[$var[1]][1]) && $_POST[$var[1]][0] == $_POST[$var[1]][1]) + $setArray[$var[1]] = $_POST[$var[1]][0]; + } + // BBC. + elseif ($var[0] == 'bbc') + { + + $bbcTags = array(); + foreach (parse_bbc(false) as $tag) + $bbcTags[] = $tag['tag']; + + if (!isset($_POST[$var[1] . '_enabledTags'])) + $_POST[$var[1] . '_enabledTags'] = array(); + elseif (!is_array($_POST[$var[1] . '_enabledTags'])) + $_POST[$var[1] . '_enabledTags'] = array($_POST[$var[1] . '_enabledTags']); + + $setArray[$var[1]] = implode(',', array_diff($bbcTags, $_POST[$var[1] . '_enabledTags'])); + } + // Permissions? + elseif ($var[0] == 'permissions') + $inlinePermissions[] = $var[1]; + } + + if (!empty($setArray)) + updateSettings($setArray); + + // If we have inline permissions we need to save them. + if (!empty($inlinePermissions) && allowedTo('manage_permissions')) + { + require_once($sourcedir . '/ManagePermissions.php'); + save_inline_permissions($inlinePermissions); + } +} + +?> \ No newline at end of file diff --git a/Sources/ManageSettings.php b/Sources/ManageSettings.php new file mode 100644 index 0000000..6d75dbd --- /dev/null +++ b/Sources/ManageSettings.php @@ -0,0 +1,2059 @@ + 'ModifyBasicSettings', + 'layout' => 'ModifyLayoutSettings', + 'karma' => 'ModifyKarmaSettings', + 'sig' => 'ModifySignatureSettings', + 'profile' => 'ShowCustomProfiles', + 'profileedit' => 'EditCustomProfiles', + ); + + loadGeneralSettingParameters($subActions, 'basic'); + + // Load up all the tabs... + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['modSettings_title'], + 'help' => 'featuresettings', + 'description' => sprintf($txt['modSettings_desc'], $settings['theme_id'], $context['session_id'], $context['session_var']), + 'tabs' => array( + 'basic' => array( + ), + 'layout' => array( + ), + 'karma' => array( + ), + 'sig' => array( + 'description' => $txt['signature_settings_desc'], + ), + 'profile' => array( + 'description' => $txt['custom_profile_desc'], + ), + ), + ); + + // Call the right function for this sub-acton. + $subActions[$_REQUEST['sa']](); +} + +// This function passes control through to the relevant security tab. +function ModifySecuritySettings() +{ + global $context, $txt, $scripturl, $modSettings, $settings; + + $context['page_title'] = $txt['admin_security_moderation']; + + $subActions = array( + 'general' => 'ModifyGeneralSecuritySettings', + 'spam' => 'ModifySpamSettings', + 'moderation' => 'ModifyModerationSettings', + ); + + loadGeneralSettingParameters($subActions, 'general'); + + // Load up all the tabs... + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['admin_security_moderation'], + 'help' => 'securitysettings', + 'description' => $txt['security_settings_desc'], + 'tabs' => array( + 'general' => array( + ), + 'spam' => array( + 'description' => $txt['antispam_Settings_desc'] , + ), + 'moderation' => array( + ), + ), + ); + + // Call the right function for this sub-acton. + $subActions[$_REQUEST['sa']](); +} + +// This my friend, is for all the mod authors out there. They're like builders without the ass crack - with the possible exception of... /cut short +function ModifyModSettings() +{ + global $context, $txt, $scripturl, $modSettings, $settings; + + $context['page_title'] = $txt['admin_modifications']; + + $subActions = array( + 'general' => 'ModifyGeneralModSettings', + // Mod authors, once again, if you have a whole section to add do it AFTER this line, and keep a comma at the end. + ); + + // Make it easier for mods to add new areas. + call_integration_hook('integrate_modify_modifications', array(&$subActions)); + + loadGeneralSettingParameters($subActions, 'general'); + + // Load up all the tabs... + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['admin_modifications'], + 'help' => 'modsettings', + 'description' => $txt['modification_settings_desc'], + 'tabs' => array( + 'general' => array( + ), + ), + ); + + // Call the right function for this sub-acton. + $subActions[$_REQUEST['sa']](); +} + +// This is an overall control panel enabling/disabling lots of SMF's key feature components. +function ModifyCoreFeatures($return_config = false) +{ + global $txt, $scripturl, $context, $settings, $sc, $modSettings; + + /* This is an array of all the features that can be enabled/disabled - each option can have the following: + title - Text title of this item (If standard string does not exist). + desc - Description of this feature (If standard string does not exist). + image - Custom image to show next to feature. + settings - Array of settings to change (For each name => value) on enable - reverse is done for disable. If > 1 will not change value if set. + setting_callback- Function that returns an array of settings to save - takes one parameter which is value for this feature. + save_callback - Function called on save, takes state as parameter. + */ + $core_features = array( + // cd = calendar. + 'cd' => array( + 'url' => 'action=admin;area=managecalendar', + 'settings' => array( + 'cal_enabled' => 1, + ), + ), + // cp = custom profile fields. + 'cp' => array( + 'url' => 'action=admin;area=featuresettings;sa=profile', + 'save_callback' => create_function('$value', ' + global $smcFunc; + if (!$value) + { + $smcFunc[\'db_query\'](\'\', \' + UPDATE {db_prefix}custom_fields + SET active = 0\'); + } + '), + 'setting_callback' => create_function('$value', ' + if (!$value) + return array( + \'disabled_profile_fields\' => \'\', + \'registration_fields\' => \'\', + \'displayFields\' => \'\', + ); + else + return array(); + '), + ), + // k = karma. + 'k' => array( + 'url' => 'action=admin;area=featuresettings;sa=karma', + 'settings' => array( + 'karmaMode' => 2, + ), + ), + // ml = moderation log. + 'ml' => array( + 'url' => 'action=admin;area=logs;sa=modlog', + 'settings' => array( + 'modlog_enabled' => 1, + ), + ), + // pm = post moderation. + 'pm' => array( + 'url' => 'action=admin;area=permissions;sa=postmod', + 'setting_callback' => create_function('$value', ' + global $sourcedir; + + // Cant use warning post moderation if disabled! + if (!$value) + { + require_once($sourcedir . \'/PostModeration.php\'); + approveAllData(); + + return array(\'warning_moderate\' => 0); + } + else + return array(); + '), + ), + // ps = Paid Subscriptions. + 'ps' => array( + 'url' => 'action=admin;area=paidsubscribe', + 'settings' => array( + 'paid_enabled' => 1, + ), + 'setting_callback' => create_function('$value', ' + global $smcFunc, $sourcedir; + + // Set the correct disabled value for scheduled task. + $smcFunc[\'db_query\'](\'\', \' + UPDATE {db_prefix}scheduled_tasks + SET disabled = {int:disabled} + WHERE task = {string:task}\', + array( + \'disabled\' => $value ? 0 : 1, + \'task\' => \'paid_subscriptions\', + ) + ); + + // Should we calculate next trigger? + if ($value) + { + require_once($sourcedir . \'/ScheduledTasks.php\'); + CalculateNextTrigger(\'paid_subscriptions\'); + } + '), + ), + // rg = report generator. + 'rg' => array( + 'url' => 'action=admin;area=reports', + ), + // w = warning. + 'w' => array( + 'url' => 'action=admin;area=securitysettings;sa=moderation', + 'setting_callback' => create_function('$value', ' + global $modSettings; + list ($modSettings[\'warning_enable\'], $modSettings[\'user_limit\'], $modSettings[\'warning_decrement\']) = explode(\',\', $modSettings[\'warning_settings\']); + $warning_settings = ($value ? 1 : 0) . \',\' . $modSettings[\'user_limit\'] . \',\' . $modSettings[\'warning_decrement\']; + if (!$value) + { + $returnSettings = array( + \'warning_watch\' => 0, + \'warning_moderate\' => 0, + \'warning_mute\' => 0, + ); + } + elseif (empty($modSettings[\'warning_enable\']) && $value) + { + $returnSettings = array( + \'warning_watch\' => 10, + \'warning_moderate\' => 35, + \'warning_mute\' => 60, + ); + } + else + $returnSettings = array(); + + $returnSettings[\'warning_settings\'] = $warning_settings; + return $returnSettings; + '), + ), + // Search engines + 'sp' => array( + 'url' => 'action=admin;area=sengines', + 'settings' => array( + 'spider_mode' => 1, + ), + 'setting_callback' => create_function('$value', ' + // Turn off the spider group if disabling. + if (!$value) + return array(\'spider_group\' => 0, \'show_spider_online\' => 0); + '), + 'on_save' => create_function('', ' + global $sourcedir, $modSettings; + require_once($sourcedir . \'/ManageSearchEngines.php\'); + recacheSpiderNames(); + '), + ), + ); + + // Anyone who would like to add a core feature? + call_integration_hook('integrate_core_features', array(&$core_features)); + + // Are we getting info for the help section. + if ($return_config) + { + $return_data = array(); + foreach ($core_features as $id => $data) + $return_data[] = array('switch', isset($data['title']) ? $data['title'] : $txt['core_settings_item_' . $id]); + return $return_data; + } + + loadGeneralSettingParameters(); + + // Are we saving? + if (isset($_POST['save'])) + { + checkSession(); + + $setting_changes = array('admin_features' => array()); + + // Are we using the javascript stuff or radios to submit? + $post_var_prefix = empty($_POST['js_worked']) ? 'feature_plain_' : 'feature_'; + + // Cycle each feature and change things as required! + foreach ($core_features as $id => $feature) + { + // Enabled? + if (!empty($_POST[$post_var_prefix . $id])) + $setting_changes['admin_features'][] = $id; + + // Setting values to change? + if (isset($feature['settings'])) + { + foreach ($feature['settings'] as $key => $value) + { + if (empty($_POST[$post_var_prefix . $id]) || (!empty($_POST[$post_var_prefix . $id]) && ($value < 2 || empty($modSettings[$key])))) + $setting_changes[$key] = !empty($_POST[$post_var_prefix . $id]) ? $value : !$value; + } + } + // Is there a call back for settings? + if (isset($feature['setting_callback'])) + { + $returned_settings = $feature['setting_callback'](!empty($_POST[$post_var_prefix . $id])); + if (!empty($returned_settings)) + $setting_changes = array_merge($setting_changes, $returned_settings); + } + + // Standard save callback? + if (isset($feature['on_save'])) + $feature['on_save'](); + } + + // Make sure this one setting is a string! + $setting_changes['admin_features'] = implode(',', $setting_changes['admin_features']); + + // Make any setting changes! + updateSettings($setting_changes); + + // Any post save things? + foreach ($core_features as $id => $feature) + { + // Standard save callback? + if (isset($feature['save_callback'])) + $feature['save_callback'](!empty($_POST[$post_var_prefix . $id])); + } + + redirectexit('action=admin;area=corefeatures;' . $context['session_var'] . '=' . $context['session_id']); + } + + // Put them in context. + $context['features'] = array(); + foreach ($core_features as $id => $feature) + $context['features'][$id] = array( + 'title' => isset($feature['title']) ? $feature['title'] : $txt['core_settings_item_' . $id], + 'desc' => isset($feature['desc']) ? $feature['desc'] : $txt['core_settings_item_' . $id . '_desc'], + 'enabled' => in_array($id, $context['admin_features']), + 'url' => !empty($feature['url']) ? $scripturl . '?' . $feature['url'] . ';' . $context['session_var'] . '=' . $context['session_id'] : '', + ); + + // Are they a new user? + $context['is_new_install'] = !isset($modSettings['admin_features']); + $context['force_disable_tabs'] = $context['is_new_install']; + // Don't show them this twice! + if ($context['is_new_install']) + updateSettings(array('admin_features' => '')); + + $context['sub_template'] = 'core_features'; + $context['page_title'] = $txt['core_settings_title']; +} + +function ModifyBasicSettings($return_config = false) +{ + global $txt, $scripturl, $context, $settings, $sc, $modSettings; + + $config_vars = array( + // Big Options... polls, sticky, bbc.... + array('select', 'pollMode', array($txt['disable_polls'], $txt['enable_polls'], $txt['polls_as_topics'])), + '', + // Basic stuff, titles, flash, permissions... + array('check', 'allow_guestAccess'), + array('check', 'enable_buddylist'), + array('check', 'allow_editDisplayName'), + array('check', 'allow_hideOnline'), + array('check', 'titlesEnable'), + array('text', 'default_personal_text'), + '', + // SEO stuff + array('check', 'queryless_urls'), + array('text', 'meta_keywords', 'size' => 50), + '', + // Number formatting, timezones. + array('text', 'time_format'), + array('select', 'number_format', array('1234.00' => '1234.00', '1,234.00' => '1,234.00', '1.234,00' => '1.234,00', '1 234,00' => '1 234,00', '1234,00' => '1234,00')), + array('float', 'time_offset'), + 'default_timezone' => array('select', 'default_timezone', array()), + '', + // Who's online? + array('check', 'who_enabled'), + array('int', 'lastActive'), + '', + // Statistics. + array('check', 'trackStats'), + array('check', 'hitStats'), + '', + // Option-ish things... miscellaneous sorta. + array('check', 'allow_disableAnnounce'), + array('check', 'disallow_sendBody'), + ); + + // Get all the time zones. + if (function_exists('timezone_identifiers_list') && function_exists('date_default_timezone_set')) + { + $all_zones = timezone_identifiers_list(); + // Make sure we set the value to the same as the printed value. + foreach ($all_zones as $zone) + $config_vars['default_timezone'][2][$zone] = $zone; + } + else + unset($config_vars['default_timezone']); + + if ($return_config) + return $config_vars; + + // Saving? + if (isset($_GET['save'])) + { + checkSession(); + + // Prevent absurd boundaries here - make it a day tops. + if (isset($_POST['lastActive'])) + $_POST['lastActive'] = min((int) $_POST['lastActive'], 1440); + + saveDBSettings($config_vars); + + writeLog(); + redirectexit('action=admin;area=featuresettings;sa=basic'); + } + + $context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=basic'; + $context['settings_title'] = $txt['mods_cat_features']; + + prepareDBSettingContext($config_vars); +} + +// Settings really associated with general security aspects. +function ModifyGeneralSecuritySettings($return_config = false) +{ + global $txt, $scripturl, $context, $settings, $sc, $modSettings; + + $config_vars = array( + array('check', 'guest_hideContacts'), + array('check', 'make_email_viewable'), + '', + array('int', 'failed_login_threshold'), + '', + array('check', 'enableErrorLogging'), + array('check', 'enableErrorQueryLogging'), + array('check', 'securityDisable'), + '', + // Reactive on email, and approve on delete + array('check', 'send_validation_onChange'), + array('check', 'approveAccountDeletion'), + '', + // Password strength. + array('select', 'password_strength', array($txt['setting_password_strength_low'], $txt['setting_password_strength_medium'], $txt['setting_password_strength_high'])), + '', + // Reporting of personal messages? + array('check', 'enableReportPM'), + ); + + if ($return_config) + return $config_vars; + + // Saving? + if (isset($_GET['save'])) + { + checkSession(); + + saveDBSettings($config_vars); + + writeLog(); + redirectexit('action=admin;area=securitysettings;sa=general'); + } + + $context['post_url'] = $scripturl . '?action=admin;area=securitysettings;save;sa=general'; + $context['settings_title'] = $txt['mods_cat_security_general']; + + prepareDBSettingContext($config_vars); +} + +function ModifyLayoutSettings($return_config = false) +{ + global $txt, $scripturl, $context, $settings, $sc; + + $config_vars = array( + // Pagination stuff. + array('check', 'compactTopicPagesEnable'), + array('int', 'compactTopicPagesContiguous', null, $txt['contiguous_page_display'] . '
' . str_replace(' ', ' ', '"3" ' . $txt['to_display'] . ': 1 ... 4 [5] 6 ... 9') . '
' . str_replace(' ', ' ', '"5" ' . $txt['to_display'] . ': 1 ... 3 4 [5] 6 7 ... 9') . '
'), + array('int', 'defaultMaxMembers'), + '', + // Stuff that just is everywhere - today, search, online, etc. + array('select', 'todayMod', array($txt['today_disabled'], $txt['today_only'], $txt['yesterday_today'])), + array('check', 'topbottomEnable'), + array('check', 'onlineEnable'), + array('check', 'enableVBStyleLogin'), + '', + // Automagic image resizing. + array('int', 'max_image_width'), + array('int', 'max_image_height'), + '', + // This is like debugging sorta. + array('check', 'timeLoadPageEnable'), + ); + + if ($return_config) + return $config_vars; + + // Saving? + if (isset($_GET['save'])) + { + checkSession(); + + saveDBSettings($config_vars); + writeLog(); + + redirectexit('action=admin;area=featuresettings;sa=layout'); + } + + $context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=layout'; + $context['settings_title'] = $txt['mods_cat_layout']; + + prepareDBSettingContext($config_vars); +} + +function ModifyKarmaSettings($return_config = false) +{ + global $txt, $scripturl, $context, $settings, $sc; + + $config_vars = array( + // Karma - On or off? + array('select', 'karmaMode', explode('|', $txt['karma_options'])), + '', + // Who can do it.... and who is restricted by time limits? + array('int', 'karmaMinPosts'), + array('float', 'karmaWaitTime'), + array('check', 'karmaTimeRestrictAdmins'), + '', + // What does it look like? [smite]? + array('text', 'karmaLabel'), + array('text', 'karmaApplaudLabel'), + array('text', 'karmaSmiteLabel'), + ); + + if ($return_config) + return $config_vars; + + // Saving? + if (isset($_GET['save'])) + { + checkSession(); + + saveDBSettings($config_vars); + redirectexit('action=admin;area=featuresettings;sa=karma'); + } + + $context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=karma'; + $context['settings_title'] = $txt['karma']; + + prepareDBSettingContext($config_vars); +} + +// Moderation type settings - although there are fewer than we have you believe ;) +function ModifyModerationSettings($return_config = false) +{ + global $txt, $scripturl, $context, $settings, $sc, $modSettings; + + $config_vars = array( + // Warning system? + array('int', 'warning_watch', 'help' => 'warning_enable'), + 'moderate' => array('int', 'warning_moderate'), + array('int', 'warning_mute'), + 'rem1' => array('int', 'user_limit'), + 'rem2' => array('int', 'warning_decrement'), + array('select', 'warning_show', array($txt['setting_warning_show_mods'], $txt['setting_warning_show_user'], $txt['setting_warning_show_all'])), + ); + + if ($return_config) + return $config_vars; + + // Cannot use moderation if post moderation is not enabled. + if (!$modSettings['postmod_active']) + unset($config_vars['moderate']); + + // Saving? + if (isset($_GET['save'])) + { + checkSession(); + + // Make sure these don't have an effect. + if (substr($modSettings['warning_settings'], 0, 1) != 1) + { + $_POST['warning_watch'] = 0; + $_POST['warning_moderate'] = 0; + $_POST['warning_mute'] = 0; + } + else + { + $_POST['warning_watch'] = min($_POST['warning_watch'], 100); + $_POST['warning_moderate'] = $modSettings['postmod_active'] ? min($_POST['warning_moderate'], 100) : 0; + $_POST['warning_mute'] = min($_POST['warning_mute'], 100); + } + + // Fix the warning setting array! + $_POST['warning_settings'] = '1,' . min(100, (int) $_POST['user_limit']) . ',' . min(100, (int) $_POST['warning_decrement']); + $save_vars = $config_vars; + $save_vars[] = array('text', 'warning_settings'); + unset($save_vars['rem1'], $save_vars['rem2']); + + saveDBSettings($save_vars); + redirectexit('action=admin;area=securitysettings;sa=moderation'); + } + + // We actually store lots of these together - for efficiency. + list ($modSettings['warning_enable'], $modSettings['user_limit'], $modSettings['warning_decrement']) = explode(',', $modSettings['warning_settings']); + + $context['post_url'] = $scripturl . '?action=admin;area=securitysettings;save;sa=moderation'; + $context['settings_title'] = $txt['moderation_settings']; + + prepareDBSettingContext($config_vars); +} + +// Let's try keep the spam to a minimum ah Thantos? +function ModifySpamSettings($return_config = false) +{ + global $txt, $scripturl, $context, $settings, $sc, $modSettings, $smcFunc; + + // Generate a sample registration image. + $context['use_graphic_library'] = in_array('gd', get_loaded_extensions()); + $context['verification_image_href'] = $scripturl . '?action=verificationcode;rand=' . md5(mt_rand()); + + $config_vars = array( + array('check', 'reg_verification'), + array('check', 'search_enable_captcha'), + // This, my friend, is a cheat :p + 'guest_verify' => array('check', 'guests_require_captcha', 'subtext' => $txt['setting_guests_require_captcha_desc']), + array('int', 'posts_require_captcha', 'subtext' => $txt['posts_require_captcha_desc'], 'onchange' => 'if (this.value > 0){ document.getElementById(\'guests_require_captcha\').checked = true; document.getElementById(\'guests_require_captcha\').disabled = true;} else {document.getElementById(\'guests_require_captcha\').disabled = false;}'), + array('check', 'guests_report_require_captcha'), + '', + // PM Settings + 'pm1' => array('int', 'max_pm_recipients'), + 'pm2' => array('int', 'pm_posts_verification'), + 'pm3' => array('int', 'pm_posts_per_hour'), + // Visual verification. + array('title', 'configure_verification_means'), + array('desc', 'configure_verification_means_desc'), + 'vv' => array('select', 'visual_verification_type', array($txt['setting_image_verification_off'], $txt['setting_image_verification_vsimple'], $txt['setting_image_verification_simple'], $txt['setting_image_verification_medium'], $txt['setting_image_verification_high'], $txt['setting_image_verification_extreme']), 'subtext'=> $txt['setting_visual_verification_type_desc'], 'onchange' => $context['use_graphic_library'] ? 'refreshImages();' : ''), + array('int', 'qa_verification_number', 'subtext' => $txt['setting_qa_verification_number_desc']), + // Clever Thomas, who is looking sheepy now? Not I, the mighty sword swinger did say. + array('title', 'setup_verification_questions'), + array('desc', 'setup_verification_questions_desc'), + array('callback', 'question_answer_list'), + ); + + if ($return_config) + return $config_vars; + + // Load any question and answers! + $context['question_answers'] = array(); + $request = $smcFunc['db_query']('', ' + SELECT id_comment, body AS question, recipient_name AS answer + FROM {db_prefix}log_comments + WHERE comment_type = {string:ver_test}', + array( + 'ver_test' => 'ver_test', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $context['question_answers'][$row['id_comment']] = array( + 'id' => $row['id_comment'], + 'question' => $row['question'], + 'answer' => $row['answer'], + ); + } + $smcFunc['db_free_result']($request); + + // Saving? + if (isset($_GET['save'])) + { + checkSession(); + + // Fix PM settings. + $_POST['pm_spam_settings'] = (int) $_POST['max_pm_recipients'] . ',' . (int) $_POST['pm_posts_verification'] . ',' . (int) $_POST['pm_posts_per_hour']; + + // Hack in guest requiring verification! + if (empty($_POST['posts_require_captcha']) && !empty($_POST['guests_require_captcha'])) + $_POST['posts_require_captcha'] = -1; + + $save_vars = $config_vars; + unset($save_vars['pm1'], $save_vars['pm2'], $save_vars['pm3'], $save_vars['guest_verify']); + + $save_vars[] = array('text', 'pm_spam_settings'); + + // Handle verification questions. + $questionInserts = array(); + $count_questions = 0; + foreach ($_POST['question'] as $id => $question) + { + $question = trim($smcFunc['htmlspecialchars']($question, ENT_COMPAT, $context['character_set'])); + $answer = trim($smcFunc['strtolower']($smcFunc['htmlspecialchars']($_POST['answer'][$id], ENT_COMPAT, $context['character_set']))); + + // Already existed? + if (isset($context['question_answers'][$id])) + { + $count_questions++; + // Changed? + if ($context['question_answers'][$id]['question'] != $question || $context['question_answers'][$id]['answer'] != $answer) + { + if ($question == '' || $answer == '') + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_comments + WHERE comment_type = {string:ver_test} + AND id_comment = {int:id}', + array( + 'id' => $id, + 'ver_test' => 'ver_test', + ) + ); + $count_questions--; + } + else + $request = $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_comments + SET body = {string:question}, recipient_name = {string:answer} + WHERE comment_type = {string:ver_test} + AND id_comment = {int:id}', + array( + 'id' => $id, + 'ver_test' => 'ver_test', + 'question' => $question, + 'answer' => $answer, + ) + ); + } + } + // It's so shiney and new! + elseif ($question != '' && $answer != '') + { + $questionInserts[] = array( + 'comment_type' => 'ver_test', + 'body' => $question, + 'recipient_name' => $answer, + ); + } + } + + // Any questions to insert? + if (!empty($questionInserts)) + { + $smcFunc['db_insert']('', + '{db_prefix}log_comments', + array('comment_type' => 'string', 'body' => 'string-65535', 'recipient_name' => 'string-80'), + $questionInserts, + array('id_comment') + ); + $count_questions += count($questionInserts); + } + + if (empty($count_questions) || $_POST['qa_verification_number'] > $count_questions) + $_POST['qa_verification_number'] = $count_questions; + + // Now save. + saveDBSettings($save_vars); + + cache_put_data('verificationQuestionIds', null, 300); + + redirectexit('action=admin;area=securitysettings;sa=spam'); + } + + $character_range = array_merge(range('A', 'H'), array('K', 'M', 'N', 'P', 'R'), range('T', 'Y')); + $_SESSION['visual_verification_code'] = ''; + for ($i = 0; $i < 6; $i++) + $_SESSION['visual_verification_code'] .= $character_range[array_rand($character_range)]; + + // Some javascript for CAPTCHA. + $context['settings_post_javascript'] = ''; + if ($context['use_graphic_library']) + $context['settings_post_javascript'] .= ' + function refreshImages() + { + var imageType = document.getElementById(\'visual_verification_type\').value; + document.getElementById(\'verification_image\').src = \'' . $context['verification_image_href'] . ';type=\' + imageType; + }'; + + // Show the image itself, or text saying we can't. + if ($context['use_graphic_library']) + $config_vars['vv']['postinput'] = '
' . $txt['setting_image_verification_sample'] . '
'; + else + $config_vars['vv']['postinput'] = '
' . $txt['setting_image_verification_nogd'] . ''; + + // Hack for PM spam settings. + list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']); + + // Hack for guests requiring verification. + $modSettings['guests_require_captcha'] = !empty($modSettings['posts_require_captcha']); + $modSettings['posts_require_captcha'] = !isset($modSettings['posts_require_captcha']) || $modSettings['posts_require_captcha'] == -1 ? 0 : $modSettings['posts_require_captcha']; + + // Some minor javascript for the guest post setting. + if ($modSettings['posts_require_captcha']) + $context['settings_post_javascript'] .= ' + document.getElementById(\'guests_require_captcha\').disabled = true;'; + + $context['post_url'] = $scripturl . '?action=admin;area=securitysettings;save;sa=spam'; + $context['settings_title'] = $txt['antispam_Settings']; + + prepareDBSettingContext($config_vars); +} + +// You'll never guess what this function does... +function ModifySignatureSettings($return_config = false) +{ + global $context, $txt, $modSettings, $sig_start, $smcFunc, $helptxt, $scripturl; + + $config_vars = array( + // Are signatures even enabled? + array('check', 'signature_enable'), + '', + // Tweaking settings! + array('int', 'signature_max_length'), + array('int', 'signature_max_lines'), + array('int', 'signature_max_font_size'), + array('check', 'signature_allow_smileys', 'onclick' => 'document.getElementById(\'signature_max_smileys\').disabled = !this.checked;'), + array('int', 'signature_max_smileys'), + '', + // Image settings. + array('int', 'signature_max_images'), + array('int', 'signature_max_image_width'), + array('int', 'signature_max_image_height'), + '', + array('bbc', 'signature_bbc'), + ); + + if ($return_config) + return $config_vars; + + // Setup the template. + $context['page_title'] = $txt['signature_settings']; + $context['sub_template'] = 'show_settings'; + + // Disable the max smileys option if we don't allow smileys at all! + $context['settings_post_javascript'] = 'document.getElementById(\'signature_max_smileys\').disabled = !document.getElementById(\'signature_allow_smileys\').checked;'; + + // Load all the signature settings. + list ($sig_limits, $sig_bbc) = explode(':', $modSettings['signature_settings']); + $sig_limits = explode(',', $sig_limits); + $disabledTags = !empty($sig_bbc) ? explode(',', $sig_bbc) : array(); + + // Applying to ALL signatures?!! + if (isset($_GET['apply'])) + { + // Security! + checkSession('get'); + + $sig_start = time(); + // This is horrid - but I suppose some people will want the option to do it. + $_GET['step'] = isset($_GET['step']) ? (int) $_GET['step'] : 0; + $done = false; + + $request = $smcFunc['db_query']('', ' + SELECT MAX(id_member) + FROM {db_prefix}members', + array( + ) + ); + list ($context['max_member']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + while (!$done) + { + $changes = array(); + + $request = $smcFunc['db_query']('', ' + SELECT id_member, signature + FROM {db_prefix}members + WHERE id_member BETWEEN ' . $_GET['step'] . ' AND ' . $_GET['step'] . ' + 49 + AND id_group != {int:admin_group} + AND FIND_IN_SET({int:admin_group}, additional_groups) = 0', + array( + 'admin_group' => 1, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Apply all the rules we can realistically do. + $sig = strtr($row['signature'], array('
' => "\n")); + + // Max characters... + if (!empty($sig_limits[1])) + $sig = $smcFunc['substr']($sig, 0, $sig_limits[1]); + // Max lines... + if (!empty($sig_limits[2])) + { + $count = 0; + for ($i = 0; $i < strlen($sig); $i++) + { + if ($sig[$i] == "\n") + { + $count++; + if ($count >= $sig_limits[2]) + $sig = substr($sig, 0, $i) . strtr(substr($sig, $i), array("\n" => ' ')); + } + } + } + + if (!empty($sig_limits[7]) && preg_match_all('~\[size=([\d\.]+)?(px|pt|em|x-large|larger)~i', $sig, $matches) !== false && isset($matches[2])) + { + foreach ($matches[1] as $ind => $size) + { + $limit_broke = 0; + // Attempt to allow all sizes of abuse, so to speak. + if ($matches[2][$ind] == 'px' && $size > $sig_limits[7]) + $limit_broke = $sig_limits[7] . 'px'; + elseif ($matches[2][$ind] == 'pt' && $size > ($sig_limits[7] * 0.75)) + $limit_broke = ((int) $sig_limits[7] * 0.75) . 'pt'; + elseif ($matches[2][$ind] == 'em' && $size > ((float) $sig_limits[7] / 16)) + $limit_broke = ((float) $sig_limits[7] / 16) . 'em'; + elseif ($matches[2][$ind] != 'px' && $matches[2][$ind] != 'pt' && $matches[2][$ind] != 'em' && $sig_limits[7] < 18) + $limit_broke = 'large'; + + if ($limit_broke) + $sig = str_replace($matches[0][$ind], '[size=' . $sig_limits[7] . 'px', $sig); + } + } + + // Stupid images - this is stupidly, stupidly challenging. + if ((!empty($sig_limits[3]) || !empty($sig_limits[5]) || !empty($sig_limits[6]))) + { + $replaces = array(); + $img_count = 0; + // Get all BBC tags... + preg_match_all('~\[img(\s+width=([\d]+))?(\s+height=([\d]+))?(\s+width=([\d]+))?\s*\](?:
)*([^<">]+?)(?:
)*\[/img\]~i', $sig, $matches); + // ... and all HTML ones. + preg_match_all('~<img\s+src=(?:")?((?:http://|ftp://|https://|ftps://).+?)(?:")?(?:\s+alt=(?:")?(.*?)(?:")?)?(?:\s?/)?>~i', $sig, $matches2, PREG_PATTERN_ORDER); + // And stick the HTML in the BBC. + if (!empty($matches2)) + { + foreach ($matches2[0] as $ind => $dummy) + { + $matches[0][] = $matches2[0][$ind]; + $matches[1][] = ''; + $matches[2][] = ''; + $matches[3][] = ''; + $matches[4][] = ''; + $matches[5][] = ''; + $matches[6][] = ''; + $matches[7][] = $matches2[1][$ind]; + } + } + // Try to find all the images! + if (!empty($matches)) + { + $image_count_holder = array(); + foreach ($matches[0] as $key => $image) + { + $width = -1; $height = -1; + $img_count++; + // Too many images? + if (!empty($sig_limits[3]) && $img_count > $sig_limits[3]) + { + // If we've already had this before we only want to remove the excess. + if (isset($image_count_holder[$image])) + { + $img_offset = -1; + $rep_img_count = 0; + while ($img_offset !== false) + { + $img_offset = strpos($sig, $image, $img_offset + 1); + $rep_img_count++; + if ($rep_img_count > $image_count_holder[$image]) + { + // Only replace the excess. + $sig = substr($sig, 0, $img_offset) . str_replace($image, '', substr($sig, $img_offset)); + // Stop looping. + $img_offset = false; + } + } + } + else + $replaces[$image] = ''; + + continue; + } + + // Does it have predefined restraints? Width first. + if ($matches[6][$key]) + $matches[2][$key] = $matches[6][$key]; + if ($matches[2][$key] && $sig_limits[5] && $matches[2][$key] > $sig_limits[5]) + { + $width = $sig_limits[5]; + $matches[4][$key] = $matches[4][$key] * ($width / $matches[2][$key]); + } + elseif ($matches[2][$key]) + $width = $matches[2][$key]; + // ... and height. + if ($matches[4][$key] && $sig_limits[6] && $matches[4][$key] > $sig_limits[6]) + { + $height = $sig_limits[6]; + if ($width != -1) + $width = $width * ($height / $matches[4][$key]); + } + elseif ($matches[4][$key]) + $height = $matches[4][$key]; + + // If the dimensions are still not fixed - we need to check the actual image. + if (($width == -1 && $sig_limits[5]) || ($height == -1 && $sig_limits[6])) + { + $sizes = url_image_size($matches[7][$key]); + if (is_array($sizes)) + { + // Too wide? + if ($sizes[0] > $sig_limits[5] && $sig_limits[5]) + { + $width = $sig_limits[5]; + $sizes[1] = $sizes[1] * ($width / $sizes[0]); + } + // Too high? + if ($sizes[1] > $sig_limits[6] && $sig_limits[6]) + { + $height = $sig_limits[6]; + if ($width == -1) + $width = $sizes[0]; + $width = $width * ($height / $sizes[1]); + } + elseif ($width != -1) + $height = $sizes[1]; + } + } + + // Did we come up with some changes? If so remake the string. + if ($width != -1 || $height != -1) + { + $replaces[$image] = '[img' . ($width != -1 ? ' width=' . round($width) : '') . ($height != -1 ? ' height=' . round($height) : '') . ']' . $matches[7][$key] . '[/img]'; + } + + // Record that we got one. + $image_count_holder[$image] = isset($image_count_holder[$image]) ? $image_count_holder[$image] + 1 : 1; + } + if (!empty($replaces)) + $sig = str_replace(array_keys($replaces), array_values($replaces), $sig); + } + } + // Try to fix disabled tags. + if (!empty($disabledTags)) + { + $sig = preg_replace('~\[(?:' . implode('|', $disabledTags) . ').+?\]~i', '', $sig); + $sig = preg_replace('~\[/(?:' . implode('|', $disabledTags) . ')\]~i', '', $sig); + } + + $sig = strtr($sig, array("\n" => '
')); + if ($sig != $row['signature']) + $changes[$row['id_member']] = $sig; + } + if ($smcFunc['db_num_rows']($request) == 0) + $done = true; + $smcFunc['db_free_result']($request); + + // Do we need to delete what we have? + if (!empty($changes)) + { + foreach ($changes as $id => $sig) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET signature = {string:signature} + WHERE id_member = {int:id_member}', + array( + 'id_member' => $id, + 'signature' => $sig, + ) + ); + } + + $_GET['step'] += 50; + if (!$done) + pauseSignatureApplySettings(); + } + } + + $context['signature_settings'] = array( + 'enable' => isset($sig_limits[0]) ? $sig_limits[0] : 0, + 'max_length' => isset($sig_limits[1]) ? $sig_limits[1] : 0, + 'max_lines' => isset($sig_limits[2]) ? $sig_limits[2] : 0, + 'max_images' => isset($sig_limits[3]) ? $sig_limits[3] : 0, + 'allow_smileys' => isset($sig_limits[4]) && $sig_limits[4] == -1 ? 0 : 1, + 'max_smileys' => isset($sig_limits[4]) && $sig_limits[4] != -1 ? $sig_limits[4] : 0, + 'max_image_width' => isset($sig_limits[5]) ? $sig_limits[5] : 0, + 'max_image_height' => isset($sig_limits[6]) ? $sig_limits[6] : 0, + 'max_font_size' => isset($sig_limits[7]) ? $sig_limits[7] : 0, + ); + + // Temporarily make each setting a modSetting! + foreach ($context['signature_settings'] as $key => $value) + $modSettings['signature_' . $key] = $value; + + // Make sure we check the right tags! + $modSettings['bbc_disabled_signature_bbc'] = $disabledTags; + + // Saving? + if (isset($_GET['save'])) + { + checkSession(); + + // Clean up the tag stuff! + $bbcTags = array(); + foreach (parse_bbc(false) as $tag) + $bbcTags[] = $tag['tag']; + + if (!isset($_POST['signature_bbc_enabledTags'])) + $_POST['signature_bbc_enabledTags'] = array(); + elseif (!is_array($_POST['signature_bbc_enabledTags'])) + $_POST['signature_bbc_enabledTags'] = array($_POST['signature_bbc_enabledTags']); + + $sig_limits = array(); + foreach ($context['signature_settings'] as $key => $value) + { + if ($key == 'allow_smileys') + continue; + elseif ($key == 'max_smileys' && empty($_POST['signature_allow_smileys'])) + $sig_limits[] = -1; + else + $sig_limits[] = !empty($_POST['signature_' . $key]) ? max(1, (int) $_POST['signature_' . $key]) : 0; + } + + $_POST['signature_settings'] = implode(',', $sig_limits) . ':' . implode(',', array_diff($bbcTags, $_POST['signature_bbc_enabledTags'])); + + // Even though we have practically no settings let's keep the convention going! + $save_vars = array(); + $save_vars[] = array('text', 'signature_settings'); + + saveDBSettings($save_vars); + redirectexit('action=admin;area=featuresettings;sa=sig'); + } + + $context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=sig'; + $context['settings_title'] = $txt['signature_settings']; + + $context['settings_message'] = '

' . sprintf($txt['signature_settings_warning'], $context['session_id'], $context['session_var']) . '

'; + + prepareDBSettingContext($config_vars); +} + +// Just pause the signature applying thing. +function pauseSignatureApplySettings() +{ + global $context, $txt, $sig_start; + + // Try get more time... + @set_time_limit(600); + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + // Have we exhausted all the time we allowed? + if (time() - array_sum(explode(' ', $sig_start)) < 3) + return; + + $context['continue_get_data'] = '?action=admin;area=featuresettings;sa=sig;apply;step=' . $_GET['step'] . ';' . $context['session_var'] . '=' . $context['session_id']; + $context['page_title'] = $txt['not_done_title']; + $context['continue_post_data'] = ''; + $context['continue_countdown'] = '2'; + $context['sub_template'] = 'not_done'; + + // Specific stuff to not break this template! + $context[$context['admin_menu_name']]['current_subsection'] = 'sig'; + + // Get the right percent. + $context['continue_percent'] = round(($_GET['step'] / $context['max_member']) * 100); + + // Never more than 100%! + $context['continue_percent'] = min($context['continue_percent'], 100); + + obExit(); +} + +// Show all the custom profile fields available to the user. +function ShowCustomProfiles() +{ + global $txt, $scripturl, $context, $settings, $sc, $smcFunc; + global $modSettings, $sourcedir; + + $context['page_title'] = $txt['custom_profile_title']; + $context['sub_template'] = 'show_custom_profile'; + + // What about standard fields they can tweak? + $standard_fields = array('icq', 'msn', 'aim', 'yim', 'location', 'gender', 'website', 'posts', 'warning_status'); + // What fields can't you put on the registration page? + $context['fields_no_registration'] = array('posts', 'warning_status'); + + // Are we saving any standard field changes? + if (isset($_POST['save'])) + { + checkSession(); + + // Do the active ones first. + $disable_fields = array_flip($standard_fields); + if (!empty($_POST['active'])) + { + foreach ($_POST['active'] as $value) + if (isset($disable_fields[$value])) + unset($disable_fields[$value]); + } + // What we have left! + $changes['disabled_profile_fields'] = empty($disable_fields) ? '' : implode(',', array_keys($disable_fields)); + + // Things we want to show on registration? + $reg_fields = array(); + if (!empty($_POST['reg'])) + { + foreach ($_POST['reg'] as $value) + if (in_array($value, $standard_fields) && !isset($disable_fields[$value])) + $reg_fields[] = $value; + } + // What we have left! + $changes['registration_fields'] = empty($reg_fields) ? '' : implode(',', $reg_fields); + + if (!empty($changes)) + updateSettings($changes); + } + + require_once($sourcedir . '/Subs-List.php'); + + $listOptions = array( + 'id' => 'standard_profile_fields', + 'title' => $txt['standard_profile_title'], + 'base_href' => $scripturl . '?action=admin;area=featuresettings;sa=profile', + 'get_items' => array( + 'function' => 'list_getProfileFields', + 'params' => array( + true, + ), + ), + 'columns' => array( + 'field' => array( + 'header' => array( + 'value' => $txt['standard_profile_field'], + 'style' => 'text-align: left;', + ), + 'data' => array( + 'db' => 'label', + 'style' => 'width: 60%;', + ), + ), + 'active' => array( + 'header' => array( + 'value' => $txt['custom_edit_active'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + $isChecked = $rowData[\'disabled\'] ? \'\' : \' checked="checked"\'; + $onClickHandler = $rowData[\'can_show_register\'] ? sprintf(\'onclick="document.getElementById(\\\'reg_%1$s\\\').disabled = !this.checked;"\', $rowData[\'id\']) : \'\'; + return sprintf(\'\', $rowData[\'id\'], $isChecked, $onClickHandler); + '), + 'style' => 'width: 20%; text-align: center;', + ), + ), + 'show_on_registration' => array( + 'header' => array( + 'value' => $txt['custom_edit_registration'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + $isChecked = $rowData[\'on_register\'] && !$rowData[\'disabled\'] ? \' checked="checked"\' : \'\'; + $isDisabled = $rowData[\'can_show_register\'] ? \'\' : \' disabled="disabled"\'; + return sprintf(\'\', $rowData[\'id\'], $isChecked, $isDisabled); + '), + 'style' => 'width: 20%; text-align: center;', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=featuresettings;sa=profile', + 'name' => 'standardProfileFields', + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => '', + 'style' => 'text-align: right;', + ), + ), + ); + createList($listOptions); + + $listOptions = array( + 'id' => 'custom_profile_fields', + 'title' => $txt['custom_profile_title'], + 'base_href' => $scripturl . '?action=admin;area=featuresettings;sa=profile', + 'default_sort_col' => 'field_name', + 'no_items_label' => $txt['custom_profile_none'], + 'items_per_page' => 25, + 'get_items' => array( + 'function' => 'list_getProfileFields', + 'params' => array( + false, + ), + ), + 'get_count' => array( + 'function' => 'list_getProfileFieldSize', + ), + 'columns' => array( + 'field_name' => array( + 'header' => array( + 'value' => $txt['custom_profile_fieldname'], + 'style' => 'text-align: left;', + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $scripturl; + + return sprintf(\'%3$s
%4$s
\', $scripturl, $rowData[\'id_field\'], $rowData[\'field_name\'], $rowData[\'field_desc\']); + '), + 'style' => 'width: 62%;', + ), + 'sort' => array( + 'default' => 'field_name', + 'reverse' => 'field_name DESC', + ), + ), + 'field_type' => array( + 'header' => array( + 'value' => $txt['custom_profile_fieldtype'], + 'style' => 'text-align: left;', + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt; + + $textKey = sprintf(\'custom_profile_type_%1$s\', $rowData[\'field_type\']); + return isset($txt[$textKey]) ? $txt[$textKey] : $textKey; + '), + 'style' => 'width: 15%;', + ), + 'sort' => array( + 'default' => 'field_type', + 'reverse' => 'field_type DESC', + ), + ), + 'active' => array( + 'header' => array( + 'value' => $txt['custom_profile_active'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt; + + return $rowData[\'active\'] ? $txt[\'yes\'] : $txt[\'no\']; + '), + 'style' => 'width: 8%; text-align: center;', + ), + 'sort' => array( + 'default' => 'active DESC', + 'reverse' => 'active', + ), + ), + 'placement' => array( + 'header' => array( + 'value' => $txt['custom_profile_placement'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt; + + return $txt[\'custom_profile_placement_\' . (empty($rowData[\'placement\']) ? \'standard\' : ($rowData[\'placement\'] == 1 ? \'withicons\' : \'abovesignature\'))]; + '), + 'style' => 'width: 8%; text-align: center;', + ), + 'sort' => array( + 'default' => 'placement DESC', + 'reverse' => 'placement', + ), + ), + 'show_on_registration' => array( + 'header' => array( + 'value' => $txt['modify'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '' . $txt['modify'] . '', + 'params' => array( + 'id_field' => false, + ), + ), + 'style' => 'width: 15%; text-align: center;', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=featuresettings;sa=profileedit', + 'name' => 'customProfileFields', + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => '', + 'style' => 'text-align: right;', + ), + ), + ); + createList($listOptions); +} + +function list_getProfileFields($start, $items_per_page, $sort, $standardFields) +{ + global $txt, $modSettings, $smcFunc; + + $list = array(); + + if ($standardFields) + { + $standard_fields = array('icq', 'msn', 'aim', 'yim', 'location', 'gender', 'website', 'posts', 'warning_status'); + $fields_no_registration = array('posts', 'warning_status'); + $disabled_fields = isset($modSettings['disabled_profile_fields']) ? explode(',', $modSettings['disabled_profile_fields']) : array(); + $registration_fields = isset($modSettings['registration_fields']) ? explode(',', $modSettings['registration_fields']) : array(); + + foreach ($standard_fields as $field) + $list[] = array( + 'id' => $field, + 'label' => isset($txt['standard_profile_field_' . $field]) ? $txt['standard_profile_field_' . $field] : (isset($txt[$field]) ? $txt[$field] : $field), + 'disabled' => in_array($field, $disabled_fields), + 'on_register' => in_array($field, $registration_fields) && !in_array($field, $fields_no_registration), + 'can_show_register' => !in_array($field, $fields_no_registration), + ); + } + else + { + // Load all the fields. + $request = $smcFunc['db_query']('', ' + SELECT id_field, col_name, field_name, field_desc, field_type, active, placement + FROM {db_prefix}custom_fields + ORDER BY {raw:sort} + LIMIT {int:start}, {int:items_per_page}', + array( + 'sort' => $sort, + 'start' => $start, + 'items_per_page' => $items_per_page, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $list[] = $row; + $smcFunc['db_free_result']($request); + } + + return $list; +} + +function list_getProfileFieldSize() +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}custom_fields', + array( + ) + ); + + list ($numProfileFields) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $numProfileFields; +} + +// Edit some profile fields? +function EditCustomProfiles() +{ + global $txt, $scripturl, $context, $settings, $sc, $smcFunc; + + // Sort out the context! + $context['fid'] = isset($_GET['fid']) ? (int) $_GET['fid'] : 0; + $context[$context['admin_menu_name']]['current_subsection'] = 'profile'; + $context['page_title'] = $context['fid'] ? $txt['custom_edit_title'] : $txt['custom_add_title']; + $context['sub_template'] = 'edit_profile_field'; + + // Load the profile language for section names. + loadLanguage('Profile'); + + if ($context['fid']) + { + $request = $smcFunc['db_query']('', ' + SELECT + id_field, col_name, field_name, field_desc, field_type, field_length, field_options, + show_reg, show_display, show_profile, private, active, default_value, can_search, + bbc, mask, enclose, placement + FROM {db_prefix}custom_fields + WHERE id_field = {int:current_field}', + array( + 'current_field' => $context['fid'], + ) + ); + $context['field'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['field_type'] == 'textarea') + @list ($rows, $cols) = @explode(',', $row['default_value']); + else + { + $rows = 3; + $cols = 30; + } + + $context['field'] = array( + 'name' => $row['field_name'], + 'desc' => $row['field_desc'], + 'colname' => $row['col_name'], + 'profile_area' => $row['show_profile'], + 'reg' => $row['show_reg'], + 'display' => $row['show_display'], + 'type' => $row['field_type'], + 'max_length' => $row['field_length'], + 'rows' => $rows, + 'cols' => $cols, + 'bbc' => $row['bbc'] ? true : false, + 'default_check' => $row['field_type'] == 'check' && $row['default_value'] ? true : false, + 'default_select' => $row['field_type'] == 'select' || $row['field_type'] == 'radio' ? $row['default_value'] : '', + 'options' => strlen($row['field_options']) > 1 ? explode(',', $row['field_options']) : array('', '', ''), + 'active' => $row['active'], + 'private' => $row['private'], + 'can_search' => $row['can_search'], + 'mask' => $row['mask'], + 'regex' => substr($row['mask'], 0, 5) == 'regex' ? substr($row['mask'], 5) : '', + 'enclose' => $row['enclose'], + 'placement' => $row['placement'], + ); + } + $smcFunc['db_free_result']($request); + } + + // Setup the default values as needed. + if (empty($context['field'])) + $context['field'] = array( + 'name' => '', + 'colname' => '???', + 'desc' => '', + 'profile_area' => 'forumprofile', + 'reg' => false, + 'display' => false, + 'type' => 'text', + 'max_length' => 255, + 'rows' => 4, + 'cols' => 30, + 'bbc' => false, + 'default_check' => false, + 'default_select' => '', + 'options' => array('', '', ''), + 'active' => true, + 'private' => false, + 'can_search' => false, + 'mask' => 'nohtml', + 'regex' => '', + 'enclose' => '', + 'placement' => 0, + ); + + // Are we saving? + if (isset($_POST['save'])) + { + checkSession(); + + // Everyone needs a name - even the (bracket) unknown... + if (trim($_POST['field_name']) == '') + fatal_lang_error('custom_option_need_name'); + $_POST['field_name'] = $smcFunc['htmlspecialchars']($_POST['field_name']); + $_POST['field_desc'] = $smcFunc['htmlspecialchars']($_POST['field_desc']); + + // Checkboxes... + $show_reg = isset($_POST['reg']) ? (int) $_POST['reg'] : 0; + $show_display = isset($_POST['display']) ? 1 : 0; + $bbc = isset($_POST['bbc']) ? 1 : 0; + $show_profile = $_POST['profile_area']; + $active = isset($_POST['active']) ? 1 : 0; + $private = isset($_POST['private']) ? (int) $_POST['private'] : 0; + $can_search = isset($_POST['can_search']) ? 1 : 0; + + // Some masking stuff... + $mask = isset($_POST['mask']) ? $_POST['mask'] : ''; + if ($mask == 'regex' && isset($_POST['regex'])) + $mask .= $_POST['regex']; + + $field_length = isset($_POST['max_length']) ? (int) $_POST['max_length'] : 255; + $enclose = isset($_POST['enclose']) ? $_POST['enclose'] : ''; + $placement = isset($_POST['placement']) ? (int) $_POST['placement'] : 0; + + // Select options? + $field_options = ''; + $newOptions = array(); + $default = isset($_POST['default_check']) && $_POST['field_type'] == 'check' ? 1 : ''; + if (!empty($_POST['select_option']) && ($_POST['field_type'] == 'select' || $_POST['field_type'] == 'radio')) + { + foreach ($_POST['select_option'] as $k => $v) + { + // Clean, clean, clean... + $v = $smcFunc['htmlspecialchars']($v); + $v = strtr($v, array(',' => '')); + + // Nada, zip, etc... + if (trim($v) == '') + continue; + + // Otherwise, save it boy. + $field_options .= $v . ','; + // This is just for working out what happened with old options... + $newOptions[$k] = $v; + + // Is it default? + if (isset($_POST['default_select']) && $_POST['default_select'] == $k) + $default = $v; + } + $field_options = substr($field_options, 0, -1); + } + + // Text area has default has dimensions + if ($_POST['field_type'] == 'textarea') + $default = (int) $_POST['rows'] . ',' . (int) $_POST['cols']; + + // Come up with the unique name? + if (empty($context['fid'])) + { + $colname = $smcFunc['substr'](strtr($_POST['field_name'], array(' ' => '')), 0, 6); + preg_match('~([\w\d_-]+)~', $colname, $matches); + + // If there is nothing to the name, then let's start out own - for foreign languages etc. + if (isset($matches[1])) + $colname = $initial_colname = 'cust_' . strtolower($matches[1]); + else + $colname = $initial_colname = 'cust_' . mt_rand(1, 999); + + // Make sure this is unique. + // !!! This may not be the most efficient way to do this. + $unique = false; + for ($i = 0; !$unique && $i < 9; $i ++) + { + $request = $smcFunc['db_query']('', ' + SELECT id_field + FROM {db_prefix}custom_fields + WHERE col_name = {string:current_column}', + array( + 'current_column' => $colname, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + $unique = true; + else + $colname = $initial_colname . $i; + $smcFunc['db_free_result']($request); + } + + // Still not a unique colum name? Leave it up to the user, then. + if (!$unique) + fatal_lang_error('custom_option_not_unique'); + } + // Work out what to do with the user data otherwise... + else + { + // Anything going to check or select is pointless keeping - as is anything coming from check! + if (($_POST['field_type'] == 'check' && $context['field']['type'] != 'check') + || (($_POST['field_type'] == 'select' || $_POST['field_type'] == 'radio') && $context['field']['type'] != 'select' && $context['field']['type'] != 'radio') + || ($context['field']['type'] == 'check' && $_POST['field_type'] != 'check')) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}themes + WHERE variable = {string:current_column} + AND id_member > {int:no_member}', + array( + 'no_member' => 0, + 'current_column' => $context['field']['colname'], + ) + ); + } + // Otherwise - if the select is edited may need to adjust! + elseif ($_POST['field_type'] == 'select' || $_POST['field_type'] == 'radio') + { + $optionChanges = array(); + $takenKeys = array(); + // Work out what's changed! + foreach ($context['field']['options'] as $k => $option) + { + if (trim($option) == '') + continue; + + // Still exists? + if (in_array($option, $newOptions)) + { + $takenKeys[] = $k; + continue; + } + } + + // Finally - have we renamed it - or is it really gone? + foreach ($optionChanges as $k => $option) + { + // Just been renamed? + if (!in_array($k, $takenKeys) && !empty($newOptions[$k])) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}themes + SET value = {string:new_value} + WHERE variable = {string:current_column} + AND value = {string:old_value} + AND id_member > {int:no_member}', + array( + 'no_member' => 0, + 'new_value' => $newOptions[$k], + 'current_column' => $context['field']['colname'], + 'old_value' => $option, + ) + ); + } + } + //!!! Maybe we should adjust based on new text length limits? + } + + // Do the insertion/updates. + if ($context['fid']) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}custom_fields + SET + field_name = {string:field_name}, field_desc = {string:field_desc}, + field_type = {string:field_type}, field_length = {int:field_length}, + field_options = {string:field_options}, show_reg = {int:show_reg}, + show_display = {int:show_display}, show_profile = {string:show_profile}, + private = {int:private}, active = {int:active}, default_value = {string:default_value}, + can_search = {int:can_search}, bbc = {int:bbc}, mask = {string:mask}, + enclose = {string:enclose}, placement = {int:placement} + WHERE id_field = {int:current_field}', + array( + 'field_length' => $field_length, + 'show_reg' => $show_reg, + 'show_display' => $show_display, + 'private' => $private, + 'active' => $active, + 'can_search' => $can_search, + 'bbc' => $bbc, + 'current_field' => $context['fid'], + 'field_name' => $_POST['field_name'], + 'field_desc' => $_POST['field_desc'], + 'field_type' => $_POST['field_type'], + 'field_options' => $field_options, + 'show_profile' => $show_profile, + 'default_value' => $default, + 'mask' => $mask, + 'enclose' => $enclose, + 'placement' => $placement, + ) + ); + + // Just clean up any old selects - these are a pain! + if (($_POST['field_type'] == 'select' || $_POST['field_type'] == 'radio') && !empty($newOptions)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}themes + WHERE variable = {string:current_column} + AND value NOT IN ({array_string:new_option_values}) + AND id_member > {int:no_member}', + array( + 'no_member' => 0, + 'new_option_values' => $newOptions, + 'current_column' => $context['field']['colname'], + ) + ); + } + else + { + $smcFunc['db_insert']('', + '{db_prefix}custom_fields', + array( + 'col_name' => 'string', 'field_name' => 'string', 'field_desc' => 'string', + 'field_type' => 'string', 'field_length' => 'string', 'field_options' => 'string', + 'show_reg' => 'int', 'show_display' => 'int', 'show_profile' => 'string', + 'private' => 'int', 'active' => 'int', 'default_value' => 'string', 'can_search' => 'int', + 'bbc' => 'int', 'mask' => 'string', 'enclose' => 'string', 'placement' => 'int', + ), + array( + $colname, $_POST['field_name'], $_POST['field_desc'], + $_POST['field_type'], $field_length, $field_options, + $show_reg, $show_display, $show_profile, + $private, $active, $default, $can_search, + $bbc, $mask, $enclose, $placement, + ), + array('id_field') + ); + } + + // As there's currently no option to priorize certain fields over others, let's order them alphabetically. + $smcFunc['db_query']('alter_table_boards', ' + ALTER TABLE {db_prefix}custom_fields + ORDER BY field_name', + array( + 'db_error_skip' => true, + ) + ); + } + // Deleting? + elseif (isset($_POST['delete']) && $context['field']['colname']) + { + checkSession(); + + // Delete the user data first. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}themes + WHERE variable = {string:current_column} + AND id_member > {int:no_member}', + array( + 'no_member' => 0, + 'current_column' => $context['field']['colname'], + ) + ); + // Finally - the field itself is gone! + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}custom_fields + WHERE id_field = {int:current_field}', + array( + 'current_field' => $context['fid'], + ) + ); + } + + // Rebuild display cache etc. + if (isset($_POST['delete']) || isset($_POST['save'])) + { + checkSession(); + + $request = $smcFunc['db_query']('', ' + SELECT col_name, field_name, field_type, bbc, enclose, placement + FROM {db_prefix}custom_fields + WHERE show_display = {int:is_displayed} + AND active = {int:active} + AND private != {int:not_owner_only} + AND private != {int:not_admin_only}', + array( + 'is_displayed' => 1, + 'active' => 1, + 'not_owner_only' => 2, + 'not_admin_only' => 3, + ) + ); + + $fields = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $fields[] = array( + 'colname' => strtr($row['col_name'], array('|' => '', ';' => '')), + 'title' => strtr($row['field_name'], array('|' => '', ';' => '')), + 'type' => $row['field_type'], + 'bbc' => $row['bbc'] ? '1' : '0', + 'placement' => !empty($row['placement']) ? $row['placement'] : '0', + 'enclose' => !empty($row['enclose']) ? $row['enclose'] : '', + ); + } + $smcFunc['db_free_result']($request); + + updateSettings(array('displayFields' => serialize($fields))); + redirectexit('action=admin;area=featuresettings;sa=profile'); + } +} + +function ModifyPruningSettings($return_config = false) +{ + global $txt, $scripturl, $sourcedir, $context, $settings, $sc, $modSettings; + + // Make sure we understand what's going on. + loadLanguage('ManageSettings'); + + $context['page_title'] = $txt['pruning_title']; + + $config_vars = array( + // Even do the pruning? + // The array indexes are there so we can remove/change them before saving. + 'pruningOptions' => array('check', 'pruningOptions'), + '', + // Various logs that could be pruned. + array('int', 'pruneErrorLog', 'postinput' => $txt['days_word']), // Error log. + array('int', 'pruneModLog', 'postinput' => $txt['days_word']), // Moderation log. + array('int', 'pruneBanLog', 'postinput' => $txt['days_word']), // Ban hit log. + array('int', 'pruneReportLog', 'postinput' => $txt['days_word']), // Report to moderator log. + array('int', 'pruneScheduledTaskLog', 'postinput' => $txt['days_word']), // Log of the scheduled tasks and how long they ran. + array('int', 'pruneSpiderHitLog', 'postinput' => $txt['days_word']), // Log of the scheduled tasks and how long they ran. + // If you add any additional logs make sure to add them after this point. Additionally, make sure you add them to the weekly scheduled task. + // Mod Developers: Do NOT use the pruningOptions master variable for this as SMF Core may overwrite your setting in the future! + ); + + if ($return_config) + return $config_vars; + + // We'll need this in a bit. + require_once($sourcedir . '/ManageServer.php'); + + // Saving? + if (isset($_GET['save'])) + { + checkSession(); + + $savevar = array( + array('text', 'pruningOptions') + ); + + if (!empty($_POST['pruningOptions'])) + { + $vals = array(); + foreach ($config_vars as $index => $dummy) + { + if (!is_array($dummy) || $index == 'pruningOptions') + continue; + + $vals[] = empty($_POST[$dummy[1]]) || $_POST[$dummy[1]] < 0 ? 0 : (int) $_POST[$dummy[1]]; + } + $_POST['pruningOptions'] = implode(',', $vals); + } + else + $_POST['pruningOptions'] = ''; + + saveDBSettings($savevar); + redirectexit('action=admin;area=logs;sa=pruning'); + } + + $context['post_url'] = $scripturl . '?action=admin;area=logs;save;sa=pruning'; + $context['settings_title'] = $txt['pruning_title']; + $context['sub_template'] = 'show_settings'; + + // Get the actual values + if (!empty($modSettings['pruningOptions'])) + @list ($modSettings['pruneErrorLog'], $modSettings['pruneModLog'], $modSettings['pruneBanLog'], $modSettings['pruneReportLog'], $modSettings['pruneScheduledTaskLog'], $modSettings['pruneSpiderHitLog']) = explode(',', $modSettings['pruningOptions']); + else + $modSettings['pruneErrorLog'] = $modSettings['pruneModLog'] = $modSettings['pruneBanLog'] = $modSettings['pruneReportLog'] = $modSettings['pruneScheduledTaskLog'] = $modSettings['pruneSpiderHitLog'] = 0; + + prepareDBSettingContext($config_vars); +} + +// If you have a general mod setting to add stick it here. +function ModifyGeneralModSettings($return_config = false) +{ + global $txt, $scripturl, $context, $settings, $sc, $modSettings; + + $config_vars = array( + // Mod authors, add any settings UNDER this line. Include a comma at the end of the line and don't remove this statement!! + ); + + // Make it even easier to add new settings. + call_integration_hook('integrate_general_mod_settings', array(&$config_vars)); + + if ($return_config) + return $config_vars; + + $context['post_url'] = $scripturl . '?action=admin;area=modsettings;save;sa=general'; + $context['settings_title'] = $txt['mods_cat_modifications_misc']; + + // No removing this line you, dirty unwashed mod authors. :p + if (empty($config_vars)) + { + $context['settings_save_dont_show'] = true; + $context['settings_message'] = '
' . $txt['modification_no_misc_settings'] . '
'; + + return prepareDBSettingContext($config_vars); + } + + // Saving? + if (isset($_GET['save'])) + { + checkSession(); + + $save_vars = $config_vars; + + // This line is to help mod authors do a search/add after if you want to add something here. Keyword: FOOT TAPPING SUCKS! + saveDBSettings($save_vars); + + // This line is to help mod authors do a search/add after if you want to add something here. Keyword: I LOVE TEA! + redirectexit('action=admin;area=modsettings;sa=general'); + } + + // This line is to help mod authors do a search/add after if you want to add something here. Keyword: RED INK IS FOR TEACHERS AND THOSE WHO LIKE PAIN! + prepareDBSettingContext($config_vars); +} + +?> \ No newline at end of file diff --git a/Sources/ManageSmileys.php b/Sources/ManageSmileys.php new file mode 100644 index 0000000..4d6e080 --- /dev/null +++ b/Sources/ManageSmileys.php @@ -0,0 +1,1776 @@ + 'AddSmiley', + 'editicon' => 'EditMessageIcons', + 'editicons' => 'EditMessageIcons', + 'editsets' => 'EditSmileySets', + 'editsmileys' => 'EditSmileys', + 'import' => 'EditSmileySets', + 'modifyset' => 'EditSmileySets', + 'modifysmiley' => 'EditSmileys', + 'setorder' => 'EditSmileyOrder', + 'settings' => 'EditSmileySettings', + 'install' => 'InstallSmileySet' + ); + + // Default the sub-action to 'edit smiley settings'. + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'editsets'; + + $context['page_title'] = $txt['smileys_manage']; + $context['sub_action'] = $_REQUEST['sa']; + $context['sub_template'] = $context['sub_action']; + + // Load up all the tabs... + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['smileys_manage'], + 'help' => 'smileys', + 'description' => $txt['smiley_settings_explain'], + 'tabs' => array( + 'editsets' => array( + 'description' => $txt['smiley_editsets_explain'], + ), + 'addsmiley' => array( + 'description' => $txt['smiley_addsmiley_explain'], + ), + 'editsmileys' => array( + 'description' => $txt['smiley_editsmileys_explain'], + ), + 'setorder' => array( + 'description' => $txt['smiley_setorder_explain'], + ), + 'editicons' => array( + 'description' => $txt['icons_edit_icons_explain'], + ), + 'settings' => array( + 'description' => $txt['smiley_settings_explain'], + ), + ), + ); + + // Some settings may not be enabled, disallow these from the tabs as appropriate. + if (empty($modSettings['messageIcons_enable'])) + $context[$context['admin_menu_name']]['tab_data']['tabs']['editicons']['disabled'] = true; + if (empty($modSettings['smiley_enable'])) + { + $context[$context['admin_menu_name']]['tab_data']['tabs']['addsmiley']['disabled'] = true; + $context[$context['admin_menu_name']]['tab_data']['tabs']['editsmileys']['disabled'] = true; + $context[$context['admin_menu_name']]['tab_data']['tabs']['setorder']['disabled'] = true; + } + + // Call the right function for this sub-acton. + $subActions[$_REQUEST['sa']](); +} + +function EditSmileySettings($return_config = false) +{ + global $modSettings, $context, $settings, $txt, $boarddir, $sourcedir, $scripturl; + + // The directories... + $context['smileys_dir'] = empty($modSettings['smileys_dir']) ? $boarddir . '/Smileys' : $modSettings['smileys_dir']; + $context['smileys_dir_found'] = is_dir($context['smileys_dir']); + + // Get the names of the smiley sets. + $smiley_sets = explode(',', $modSettings['smiley_sets_known']); + $set_names = explode("\n", $modSettings['smiley_sets_names']); + + $smiley_context = array(); + foreach ($smiley_sets as $i => $set) + $smiley_context[$set] = $set_names[$i]; + + // All the settings for the page... + $config_vars = array( + array('title', 'settings'), + // Inline permissions. + array('permissions', 'manage_smileys'), + '', + array('select', 'smiley_sets_default', $smiley_context), + array('check', 'smiley_sets_enable'), + array('check', 'smiley_enable', 'subtext' => $txt['smileys_enable_note']), + array('text', 'smileys_url'), + array('text', 'smileys_dir', 'invalid' => !$context['smileys_dir_found']), + '', + // Message icons. + array('check', 'messageIcons_enable', 'subtext' => $txt['setting_messageIcons_enable_note']), + ); + + if ($return_config) + return $config_vars; + + // Setup the basics of the settings template. + require_once($sourcedir . '/ManageServer.php'); + $context['sub_template'] = 'show_settings'; + + // Finish up the form... + $context['post_url'] = $scripturl . '?action=admin;area=smileys;save;sa=settings'; + $context['permissions_excluded'] = array(-1); + + // Saving the settings? + if (isset($_GET['save'])) + { + checkSession(); + + // Validate the smiley set name. + $_POST['smiley_sets_default'] = empty($smiley_context[$_POST['smiley_sets_default']]) ? 'default' : $_POST['smiley_sets_default']; + + // Make sure that the smileys are in the right order after enabling them. + if (isset($_POST['smiley_enable'])) + sortSmileyTable(); + + saveDBSettings($config_vars); + + cache_put_data('parsing_smileys', null, 480); + cache_put_data('posting_smileys', null, 480); + + redirectexit('action=admin;area=smileys;sa=settings'); + } + + prepareDBSettingContext($config_vars); +} + +function EditSmileySets() +{ + global $modSettings, $context, $settings, $txt, $boarddir; + global $smcFunc, $scripturl, $sourcedir; + + // Set the right tab to be selected. + $context[$context['admin_menu_name']]['current_subsection'] = 'editsets'; + + // They must've been submitted a form. + if (isset($_POST[$context['session_var']])) + { + checkSession(); + + // Delete selected smiley sets. + if (!empty($_POST['delete']) && !empty($_POST['smiley_set'])) + { + $set_paths = explode(',', $modSettings['smiley_sets_known']); + $set_names = explode("\n", $modSettings['smiley_sets_names']); + foreach ($_POST['smiley_set'] as $id => $val) + if (isset($set_paths[$id], $set_names[$id]) && !empty($id)) + unset($set_paths[$id], $set_names[$id]); + + updateSettings(array( + 'smiley_sets_known' => implode(',', $set_paths), + 'smiley_sets_names' => implode("\n", $set_names), + 'smiley_sets_default' => in_array($modSettings['smiley_sets_default'], $set_paths) ? $modSettings['smiley_sets_default'] : $set_paths[0], + )); + + cache_put_data('parsing_smileys', null, 480); + cache_put_data('posting_smileys', null, 480); + } + // Add a new smiley set. + elseif (!empty($_POST['add'])) + $context['sub_action'] = 'modifyset'; + // Create or modify a smiley set. + elseif (isset($_POST['set'])) + { + $set_paths = explode(',', $modSettings['smiley_sets_known']); + $set_names = explode("\n", $modSettings['smiley_sets_names']); + + // Create a new smiley set. + if ($_POST['set'] == -1 && isset($_POST['smiley_sets_path'])) + { + if (in_array($_POST['smiley_sets_path'], $set_paths)) + fatal_lang_error('smiley_set_already_exists'); + + updateSettings(array( + 'smiley_sets_known' => $modSettings['smiley_sets_known'] . ',' . $_POST['smiley_sets_path'], + 'smiley_sets_names' => $modSettings['smiley_sets_names'] . "\n" . $_POST['smiley_sets_name'], + 'smiley_sets_default' => empty($_POST['smiley_sets_default']) ? $modSettings['smiley_sets_default'] : $_POST['smiley_sets_path'], + )); + } + // Modify an existing smiley set. + else + { + // Make sure the smiley set exists. + if (!isset($set_paths[$_POST['set']]) || !isset($set_names[$_POST['set']])) + fatal_lang_error('smiley_set_not_found'); + + // Make sure the path is not yet used by another smileyset. + if (in_array($_POST['smiley_sets_path'], $set_paths) && $_POST['smiley_sets_path'] != $set_paths[$_POST['set']]) + fatal_lang_error('smiley_set_path_already_used'); + + $set_paths[$_POST['set']] = $_POST['smiley_sets_path']; + $set_names[$_POST['set']] = $_POST['smiley_sets_name']; + updateSettings(array( + 'smiley_sets_known' => implode(',', $set_paths), + 'smiley_sets_names' => implode("\n", $set_names), + 'smiley_sets_default' => empty($_POST['smiley_sets_default']) ? $modSettings['smiley_sets_default'] : $_POST['smiley_sets_path'] + )); + } + + // The user might have checked to also import smileys. + if (!empty($_POST['smiley_sets_import'])) + ImportSmileys($_POST['smiley_sets_path']); + + cache_put_data('parsing_smileys', null, 480); + cache_put_data('posting_smileys', null, 480); + } + } + + // Load all available smileysets... + $context['smiley_sets'] = explode(',', $modSettings['smiley_sets_known']); + $set_names = explode("\n", $modSettings['smiley_sets_names']); + foreach ($context['smiley_sets'] as $i => $set) + $context['smiley_sets'][$i] = array( + 'id' => $i, + 'path' => htmlspecialchars($set), + 'name' => htmlspecialchars($set_names[$i]), + 'selected' => $set == $modSettings['smiley_sets_default'] + ); + + // Importing any smileys from an existing set? + if ($context['sub_action'] == 'import') + { + checkSession('get'); + $_GET['set'] = (int) $_GET['set']; + + // Sanity check - then import. + if (isset($context['smiley_sets'][$_GET['set']])) + ImportSmileys(un_htmlspecialchars($context['smiley_sets'][$_GET['set']]['path'])); + + // Force the process to continue. + $context['sub_action'] = 'modifyset'; + $context['sub_template'] = 'modifyset'; + } + // If we're modifying or adding a smileyset, some context info needs to be set. + if ($context['sub_action'] == 'modifyset') + { + $_GET['set'] = !isset($_GET['set']) ? -1 : (int) $_GET['set']; + if ($_GET['set'] == -1 || !isset($context['smiley_sets'][$_GET['set']])) + $context['current_set'] = array( + 'id' => '-1', + 'path' => '', + 'name' => '', + 'selected' => false, + 'is_new' => true, + ); + else + { + $context['current_set'] = &$context['smiley_sets'][$_GET['set']]; + $context['current_set']['is_new'] = false; + + // Calculate whether there are any smileys in the directory that can be imported. + if (!empty($modSettings['smiley_enable']) && !empty($modSettings['smileys_dir']) && is_dir($modSettings['smileys_dir'] . '/' . $context['current_set']['path'])) + { + $smileys = array(); + $dir = dir($modSettings['smileys_dir'] . '/' . $context['current_set']['path']); + while ($entry = $dir->read()) + { + if (in_array(strrchr($entry, '.'), array('.jpg', '.gif', '.jpeg', '.png'))) + $smileys[strtolower($entry)] = $entry; + } + $dir->close(); + + // Exclude the smileys that are already in the database. + $request = $smcFunc['db_query']('', ' + SELECT filename + FROM {db_prefix}smileys + WHERE filename IN ({array_string:smiley_list})', + array( + 'smiley_list' => $smileys, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + if (isset($smileys[strtolower($row['filename'])])) + unset($smileys[strtolower($row['filename'])]); + $smcFunc['db_free_result']($request); + + $context['current_set']['can_import'] = count($smileys); + // Setup this string to look nice. + $txt['smiley_set_import_multiple'] = sprintf($txt['smiley_set_import_multiple'], $context['current_set']['can_import']); + } + } + + // Retrieve all potential smiley set directories. + $context['smiley_set_dirs'] = array(); + if (!empty($modSettings['smileys_dir']) && is_dir($modSettings['smileys_dir'])) + { + $dir = dir($modSettings['smileys_dir']); + while ($entry = $dir->read()) + { + if (!in_array($entry, array('.', '..')) && is_dir($modSettings['smileys_dir'] . '/' . $entry)) + $context['smiley_set_dirs'][] = array( + 'id' => $entry, + 'path' => $modSettings['smileys_dir'] . '/' . $entry, + 'selectable' => $entry == $context['current_set']['path'] || !in_array($entry, explode(',', $modSettings['smiley_sets_known'])), + 'current' => $entry == $context['current_set']['path'], + ); + } + $dir->close(); + } + } + + $listOptions = array( + 'id' => 'smiley_set_list', + 'base_href' => $scripturl . '?action=admin;area=smileys;sa=editsets', + 'default_sort_col' => 'default', + 'get_items' => array( + 'function' => 'list_getSmileySets', + ), + 'get_count' => array( + 'function' => 'list_getNumSmileySets', + ), + 'columns' => array( + 'default' => array( + 'header' => array( + 'value' => $txt['smiley_sets_default'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + return $rowData[\'selected\'] ? \'*\' : \'\'; + '), + 'style' => 'text-align: center;', + ), + 'sort' => array( + 'default' => 'selected DESC', + ), + ), + 'name' => array( + 'header' => array( + 'value' => $txt['smiley_sets_name'], + ), + 'data' => array( + 'db_htmlsafe' => 'name', + 'class' => 'windowbg', + ), + 'sort' => array( + 'default' => 'name', + 'reverse' => 'name DESC', + ), + ), + 'url' => array( + 'header' => array( + 'value' => $txt['smiley_sets_url'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => $modSettings['smileys_url'] . '/%1$s/...', + 'params' => array( + 'path' => true, + ), + ), + 'class' => 'windowbg', + ), + 'sort' => array( + 'default' => 'path', + 'reverse' => 'path DESC', + ), + ), + 'modify' => array( + 'header' => array( + 'value' => $txt['smiley_set_modify'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '' . $txt['smiley_set_modify'] . '', + 'params' => array( + 'id' => true, + ), + ), + 'style' => 'text-align: center;', + ), + ), + 'check' => array( + 'header' => array( + 'value' => '', + ), + 'data' => array( + 'function' => create_function('$rowData', ' + return $rowData[\'id\'] == 0 ? \'\' : sprintf(\'\', $rowData[\'id\']); + '), + 'style' => 'text-align: center', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=smileys;sa=editsets', + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => ' [' . $txt['smiley_sets_add'] . ']', + ), + ), + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); +} + +// !!! to be moved to Subs-Smileys. +function list_getSmileySets($start, $items_per_page, $sort) +{ + global $modSettings; + + $known_sets = explode(',', $modSettings['smiley_sets_known']); + $set_names = explode("\n", $modSettings['smiley_sets_names']); + $cols = array( + 'id' => array(), + 'selected' => array(), + 'path' => array(), + 'name' => array(), + ); + foreach ($known_sets as $i => $set) + { + $cols['id'][] = $i; + $cols['selected'][] = $i; + $cols['path'][] = $set; + $cols['name'][] = $set_names[$i]; + } + $sort_flag = strpos($sort, 'DESC') === false ? SORT_ASC : SORT_DESC; + if (substr($sort, 0, 4) === 'name') + array_multisort($cols['name'], $sort_flag, SORT_REGULAR, $cols['path'], $cols['selected'], $cols['id']); + elseif (substr($sort, 0, 4) === 'path') + array_multisort($cols['path'], $sort_flag, SORT_REGULAR, $cols['name'], $cols['selected'], $cols['id']); + else + array_multisort($cols['selected'], $sort_flag, SORT_REGULAR, $cols['path'], $cols['name'], $cols['id']); + + $smiley_sets = array(); + foreach ($cols['id'] as $i => $id) + $smiley_sets[] = array( + 'id' => $id, + 'path' => $cols['path'][$i], + 'name' => $cols['name'][$i], + 'selected' => $cols['path'][$i] == $modSettings['smiley_sets_default'] + ); + + return $smiley_sets; +} + +// !!! to be moved to Subs-Smileys. +function list_getNumSmileySets() +{ + global $modSettings; + + return count(explode(',', $modSettings['smiley_sets_known'])); +} + +function AddSmiley() +{ + global $modSettings, $context, $settings, $txt, $boarddir, $smcFunc; + + // Get a list of all known smiley sets. + $context['smileys_dir'] = empty($modSettings['smileys_dir']) ? $boarddir . '/Smileys' : $modSettings['smileys_dir']; + $context['smileys_dir_found'] = is_dir($context['smileys_dir']); + $context['smiley_sets'] = explode(',', $modSettings['smiley_sets_known']); + $set_names = explode("\n", $modSettings['smiley_sets_names']); + foreach ($context['smiley_sets'] as $i => $set) + $context['smiley_sets'][$i] = array( + 'id' => $i, + 'path' => htmlspecialchars($set), + 'name' => htmlspecialchars($set_names[$i]), + 'selected' => $set == $modSettings['smiley_sets_default'] + ); + + // Submitting a form? + if (isset($_POST[$context['session_var']], $_POST['smiley_code'])) + { + checkSession(); + + // Some useful arrays... types we allow - and ports we don't! + $allowedTypes = array('jpeg', 'jpg', 'gif', 'png', 'bmp'); + $disabledFiles = array('con', 'com1', 'com2', 'com3', 'com4', 'prn', 'aux', 'lpt1', '.htaccess', 'index.php'); + + $_POST['smiley_code'] = htmltrim__recursive($_POST['smiley_code']); + $_POST['smiley_location'] = empty($_POST['smiley_location']) || $_POST['smiley_location'] > 2 || $_POST['smiley_location'] < 0 ? 0 : (int) $_POST['smiley_location']; + $_POST['smiley_filename'] = htmltrim__recursive($_POST['smiley_filename']); + + // Make sure some code was entered. + if (empty($_POST['smiley_code'])) + fatal_lang_error('smiley_has_no_code'); + + // Check whether the new code has duplicates. It should be unique. + $request = $smcFunc['db_query']('', ' + SELECT id_smiley + FROM {db_prefix}smileys + WHERE code = {raw:mysql_binary_statement} {string:smiley_code}', + array( + 'mysql_binary_statement' => $smcFunc['db_title'] == 'MySQL' ? 'BINARY' : '', + 'smiley_code' => $_POST['smiley_code'], + ) + ); + if ($smcFunc['db_num_rows']($request) > 0) + fatal_lang_error('smiley_not_unique'); + $smcFunc['db_free_result']($request); + + // If we are uploading - check all the smiley sets are writable! + if ($_POST['method'] != 'existing') + { + $writeErrors = array(); + foreach ($context['smiley_sets'] as $set) + { + if (!is_writable($context['smileys_dir'] . '/' . un_htmlspecialchars($set['path']))) + $writeErrors[] = $set['path']; + } + if (!empty($writeErrors)) + fatal_lang_error('smileys_upload_error_notwritable', true, array(implode(', ', $writeErrors))); + } + + // Uploading just one smiley for all of them? + if (isset($_POST['sameall']) && isset($_FILES['uploadSmiley']['name']) && $_FILES['uploadSmiley']['name'] != '') + { + if (!is_uploaded_file($_FILES['uploadSmiley']['tmp_name']) || (@ini_get('open_basedir') == '' && !file_exists($_FILES['uploadSmiley']['tmp_name']))) + fatal_lang_error('smileys_upload_error'); + + // Sorry, no spaces, dots, or anything else but letters allowed. + $_FILES['uploadSmiley']['name'] = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $_FILES['uploadSmiley']['name']); + + // We only allow image files - it's THAT simple - no messing around here... + if (!in_array(strtolower(substr(strrchr($_FILES['uploadSmiley']['name'], '.'), 1)), $allowedTypes)) + fatal_lang_error('smileys_upload_error_types', false, array(implode(', ', $allowedTypes))); + + // We only need the filename... + $destName = basename($_FILES['uploadSmiley']['name']); + + // Make sure they aren't trying to upload a nasty file - for their own good here! + if (in_array(strtolower($destName), $disabledFiles)) + fatal_lang_error('smileys_upload_error_illegal'); + + // Check if the file already exists... and if not move it to EVERY smiley set directory. + $i = 0; + // Keep going until we find a set the file doesn't exist in. (or maybe it exists in all of them?) + while (isset($context['smiley_sets'][$i]) && file_exists($context['smileys_dir'] . '/' . un_htmlspecialchars($context['smiley_sets'][$i]['path']) . '/' . $destName)) + $i++; + + // Okay, we're going to put the smiley right here, since it's not there yet! + if (isset($context['smiley_sets'][$i]['path'])) + { + $smileyLocation = $context['smileys_dir'] . '/' . un_htmlspecialchars($context['smiley_sets'][$i]['path']) . '/' . $destName; + move_uploaded_file($_FILES['uploadSmiley']['tmp_name'], $smileyLocation); + @chmod($smileyLocation, 0644); + + // Now, we want to move it from there to all the other sets. + for ($n = count($context['smiley_sets']); $i < $n; $i++) + { + $currentPath = $context['smileys_dir'] . '/' . un_htmlspecialchars($context['smiley_sets'][$i]['path']) . '/' . $destName; + + // The file is already there! Don't overwrite it! + if (file_exists($currentPath)) + continue; + + // Okay, so copy the first one we made to here. + copy($smileyLocation, $currentPath); + @chmod($currentPath, 0644); + } + } + + // Finally make sure it's saved correctly! + $_POST['smiley_filename'] = $destName; + } + // What about uploading several files? + elseif ($_POST['method'] != 'existing') + { + foreach ($_FILES as $name => $data) + { + if ($_FILES[$name]['name'] == '') + fatal_lang_error('smileys_upload_error_blank'); + + if (empty($newName)) + $newName = basename($_FILES[$name]['name']); + elseif (basename($_FILES[$name]['name']) != $newName) + fatal_lang_error('smileys_upload_error_name'); + } + + foreach ($context['smiley_sets'] as $i => $set) + { + $set['name'] = un_htmlspecialchars($set['name']); + $set['path'] = un_htmlspecialchars($set['path']); + + if (!isset($_FILES['individual_' . $set['name']]['name']) || $_FILES['individual_' . $set['name']]['name'] == '') + continue; + + // Got one... + if (!is_uploaded_file($_FILES['individual_' . $set['name']]['tmp_name']) || (@ini_get('open_basedir') == '' && !file_exists($_FILES['individual_' . $set['name']]['tmp_name']))) + fatal_lang_error('smileys_upload_error'); + + // Sorry, no spaces, dots, or anything else but letters allowed. + $_FILES['individual_' . $set['name']]['name'] = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $_FILES['individual_' . $set['name']]['name']); + + // We only allow image files - it's THAT simple - no messing around here... + if (!in_array(strtolower(substr(strrchr($_FILES['individual_' . $set['name']]['name'], '.'), 1)), $allowedTypes)) + fatal_lang_error('smileys_upload_error_types', false, array(implode(', ', $allowedTypes))); + + // We only need the filename... + $destName = basename($_FILES['individual_' . $set['name']]['name']); + + // Make sure they aren't trying to upload a nasty file - for their own good here! + if (in_array(strtolower($destName), $disabledFiles)) + fatal_lang_error('smileys_upload_error_illegal'); + + // If the file exists - ignore it. + $smileyLocation = $context['smileys_dir'] . '/' . $set['path'] . '/' . $destName; + if (file_exists($smileyLocation)) + continue; + + // Finally - move the image! + move_uploaded_file($_FILES['individual_' . $set['name']]['tmp_name'], $smileyLocation); + @chmod($smileyLocation, 0644); + + // Should always be saved correctly! + $_POST['smiley_filename'] = $destName; + } + } + + // Also make sure a filename was given. + if (empty($_POST['smiley_filename'])) + fatal_lang_error('smiley_has_no_filename'); + + // Find the position on the right. + $smiley_order = '0'; + if ($_POST['smiley_location'] != 1) + { + $request = $smcFunc['db_query']('', ' + SELECT MAX(smiley_order) + 1 + FROM {db_prefix}smileys + WHERE hidden = {int:smiley_location} + AND smiley_row = {int:first_row}', + array( + 'smiley_location' => $_POST['smiley_location'], + 'first_row' => 0, + ) + ); + list ($smiley_order) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (empty($smiley_order)) + $smiley_order = '0'; + } + $smcFunc['db_insert']('', + '{db_prefix}smileys', + array( + 'code' => 'string-30', 'filename' => 'string-48', 'description' => 'string-80', 'hidden' => 'int', 'smiley_order' => 'int', + ), + array( + $_POST['smiley_code'], $_POST['smiley_filename'], $_POST['smiley_description'], $_POST['smiley_location'], $smiley_order, + ), + array('id_smiley') + ); + + cache_put_data('parsing_smileys', null, 480); + cache_put_data('posting_smileys', null, 480); + + // No errors? Out of here! + redirectexit('action=admin;area=smileys;sa=editsmileys'); + } + + $context['selected_set'] = $modSettings['smiley_sets_default']; + + // Get all possible filenames for the smileys. + $context['filenames'] = array(); + if ($context['smileys_dir_found']) + { + foreach ($context['smiley_sets'] as $smiley_set) + { + if (!file_exists($context['smileys_dir'] . '/' . un_htmlspecialchars($smiley_set['path']))) + continue; + + $dir = dir($context['smileys_dir'] . '/' . un_htmlspecialchars($smiley_set['path'])); + while ($entry = $dir->read()) + { + if (!in_array($entry, $context['filenames']) && in_array(strrchr($entry, '.'), array('.jpg', '.gif', '.jpeg', '.png'))) + $context['filenames'][strtolower($entry)] = array( + 'id' => htmlspecialchars($entry), + 'selected' => false, + ); + } + $dir->close(); + } + ksort($context['filenames']); + } + + // Create a new smiley from scratch. + $context['filenames'] = array_values($context['filenames']); + $context['current_smiley'] = array( + 'id' => 0, + 'code' => '', + 'filename' => $context['filenames'][0]['id'], + 'description' => $txt['smileys_default_description'], + 'location' => 0, + 'is_new' => true, + ); +} + +function EditSmileys() +{ + global $modSettings, $context, $settings, $txt, $boarddir; + global $smcFunc, $scripturl, $sourcedir; + + // Force the correct tab to be displayed. + $context[$context['admin_menu_name']]['current_subsection'] = 'editsmileys'; + + // Submitting a form? + if (isset($_POST[$context['session_var']])) + { + checkSession(); + + // Changing the selected smileys? + if (isset($_POST['smiley_action']) && !empty($_POST['checked_smileys'])) + { + foreach ($_POST['checked_smileys'] as $id => $smiley_id) + $_POST['checked_smileys'][$id] = (int) $smiley_id; + + if ($_POST['smiley_action'] == 'delete') + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}smileys + WHERE id_smiley IN ({array_int:checked_smileys})', + array( + 'checked_smileys' => $_POST['checked_smileys'], + ) + ); + // Changing the status of the smiley? + else + { + // Check it's a valid type. + $displayTypes = array( + 'post' => 0, + 'hidden' => 1, + 'popup' => 2 + ); + if (isset($displayTypes[$_POST['smiley_action']])) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}smileys + SET hidden = {int:display_type} + WHERE id_smiley IN ({array_int:checked_smileys})', + array( + 'checked_smileys' => $_POST['checked_smileys'], + 'display_type' => $displayTypes[$_POST['smiley_action']], + ) + ); + } + } + // Create/modify a smiley. + elseif (isset($_POST['smiley'])) + { + // Is it a delete? + if (!empty($_POST['deletesmiley'])) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}smileys + WHERE id_smiley = {int:current_smiley}', + array( + 'current_smiley' => $_POST['smiley'], + ) + ); + } + // Otherwise an edit. + else + { + $_POST['smiley'] = (int) $_POST['smiley']; + $_POST['smiley_code'] = htmltrim__recursive($_POST['smiley_code']); + $_POST['smiley_filename'] = htmltrim__recursive($_POST['smiley_filename']); + $_POST['smiley_location'] = empty($_POST['smiley_location']) || $_POST['smiley_location'] > 2 || $_POST['smiley_location'] < 0 ? 0 : (int) $_POST['smiley_location']; + + // Make sure some code was entered. + if (empty($_POST['smiley_code'])) + fatal_lang_error('smiley_has_no_code'); + + // Also make sure a filename was given. + if (empty($_POST['smiley_filename'])) + fatal_lang_error('smiley_has_no_filename'); + + // Check whether the new code has duplicates. It should be unique. + $request = $smcFunc['db_query']('', ' + SELECT id_smiley + FROM {db_prefix}smileys + WHERE code = {raw:mysql_binary_type} {string:smiley_code}' . (empty($_POST['smiley']) ? '' : ' + AND id_smiley != {int:current_smiley}'), + array( + 'current_smiley' => $_POST['smiley'], + 'mysql_binary_type' => $smcFunc['db_title'] == 'MySQL' ? 'BINARY' : '', + 'smiley_code' => $_POST['smiley_code'], + ) + ); + if ($smcFunc['db_num_rows']($request) > 0) + fatal_lang_error('smiley_not_unique'); + $smcFunc['db_free_result']($request); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}smileys + SET + code = {string:smiley_code}, + filename = {string:smiley_filename}, + description = {string:smiley_description}, + hidden = {int:smiley_location} + WHERE id_smiley = {int:current_smiley}', + array( + 'smiley_location' => $_POST['smiley_location'], + 'current_smiley' => $_POST['smiley'], + 'smiley_code' => $_POST['smiley_code'], + 'smiley_filename' => $_POST['smiley_filename'], + 'smiley_description' => $_POST['smiley_description'], + ) + ); + } + + // Sort all smiley codes for more accurate parsing (longest code first). + sortSmileyTable(); + } + + cache_put_data('parsing_smileys', null, 480); + cache_put_data('posting_smileys', null, 480); + } + + // Load all known smiley sets. + $context['smiley_sets'] = explode(',', $modSettings['smiley_sets_known']); + $set_names = explode("\n", $modSettings['smiley_sets_names']); + foreach ($context['smiley_sets'] as $i => $set) + $context['smiley_sets'][$i] = array( + 'id' => $i, + 'path' => htmlspecialchars($set), + 'name' => htmlspecialchars($set_names[$i]), + 'selected' => $set == $modSettings['smiley_sets_default'] + ); + + // Prepare overview of all (custom) smileys. + if ($context['sub_action'] == 'editsmileys') + { + // Determine the language specific sort order of smiley locations. + $smiley_locations = array( + $txt['smileys_location_form'], + $txt['smileys_location_hidden'], + $txt['smileys_location_popup'], + ); + asort($smiley_locations); + + // Create a list of options for selecting smiley sets. + $smileyset_option_list = ' + '; + + $listOptions = array( + 'id' => 'smiley_list', + 'items_per_page' => 40, + 'base_href' => $scripturl . '?action=admin;area=smileys;sa=editsmileys', + 'default_sort_col' => 'filename', + 'get_items' => array( + 'function' => 'list_getSmileys', + ), + 'get_count' => array( + 'function' => 'list_getNumSmileys', + ), + 'no_items_label' => $txt['smileys_no_entries'], + 'columns' => array( + 'picture' => array( + 'data' => array( + 'sprintf' => array( + 'format' => '%3$s', + 'params' => array( + 'id_smiley' => false, + 'filename' => true, + 'description' => true, + ), + ), + 'style' => 'text-align: center;', + ), + ), + 'code' => array( + 'header' => array( + 'value' => $txt['smileys_code'], + ), + 'data' => array( + 'db_htmlsafe' => 'code', + ), + 'sort' => array( + 'default' => 'code', + 'reverse' => 'code DESC', + ), + ), + 'filename' => array( + 'header' => array( + 'value' => $txt['smileys_filename'], + ), + 'data' => array( + 'db_htmlsafe' => 'filename', + 'class' => 'windowbg', + ), + 'sort' => array( + 'default' => 'filename', + 'reverse' => 'filename DESC', + ), + ), + 'location' => array( + 'header' => array( + 'value' => $txt['smileys_location'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt; + + if (empty($rowData[\'hidden\'])) + return $txt[\'smileys_location_form\']; + elseif ($rowData[\'hidden\'] == 1) + return $txt[\'smileys_location_hidden\']; + else + return $txt[\'smileys_location_popup\']; + '), + 'class' => 'windowbg', + ), + 'sort' => array( + 'default' => 'FIND_IN_SET(hidden, \'' . implode(',', array_keys($smiley_locations)) . '\')', + 'reverse' => 'FIND_IN_SET(hidden, \'' . implode(',', array_keys($smiley_locations)) . '\') DESC', + ), + ), + 'tooltip' => array( + 'header' => array( + 'value' => $txt['smileys_description'], + ), + 'data' => array( + 'function' => create_function('$rowData', empty($modSettings['smileys_dir']) || !is_dir($modSettings['smileys_dir']) ? ' + return htmlspecialchars($rowData[\'description\']); + ' : ' + global $context, $txt, $modSettings; + + // Check if there are smileys missing in some sets. + $missing_sets = array(); + foreach ($context[\'smiley_sets\'] as $smiley_set) + if (!file_exists(sprintf(\'%1$s/%2$s/%3$s\', $modSettings[\'smileys_dir\'], $smiley_set[\'path\'], $rowData[\'filename\']))) + $missing_sets[] = $smiley_set[\'path\']; + + $description = htmlspecialchars($rowData[\'description\']); + + if (!empty($missing_sets)) + $description .= sprintf(\'
%1$s: %2$s\', $txt[\'smileys_not_found_in_set\'], implode(\', \', $missing_sets)); + + return $description; + '), + 'class' => 'windowbg', + ), + 'sort' => array( + 'default' => 'description', + 'reverse' => 'description DESC', + ), + ), + 'modify' => array( + 'header' => array( + 'value' => $txt['smileys_modify'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '' . $txt['smileys_modify'] . '', + 'params' => array( + 'id_smiley' => false, + ), + ), + 'style' => 'text-align: center;', + ), + ), + 'check' => array( + 'header' => array( + 'value' => '', + ), + 'data' => array( + 'sprintf' => array( + 'format' => '', + 'params' => array( + 'id_smiley' => false, + ), + ), + 'style' => 'text-align: center', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=smileys;sa=editsmileys', + 'name' => 'smileyForm', + ), + 'additional_rows' => array( + array( + 'position' => 'above_column_headers', + 'value' => $smileyset_option_list, + 'style' => 'text-align: right;', + ), + array( + 'position' => 'below_table_data', + 'value' => ' + + ', + 'style' => 'text-align: right;', + ), + ), + 'javascript' => ' + function makeChanges(action) + { + if (action == \'-1\') + return false; + else if (action == \'delete\') + { + if (confirm(\'' . $txt['smileys_confirm'] . '\')) + document.forms.smileyForm.submit(); + } + else + document.forms.smileyForm.submit(); + return true; + } + function changeSet(newSet) + { + var currentImage, i, knownSmileys = []; + + if (knownSmileys.length == 0) + { + for (var i = 0, n = document.images.length; i < n; i++) + if (document.images[i].id.substr(0, 6) == \'smiley\') + knownSmileys[knownSmileys.length] = document.images[i].id.substr(6); + } + + for (i = 0; i < knownSmileys.length; i++) + { + currentImage = document.getElementById("smiley" + knownSmileys[i]); + currentImage.src = "' . $modSettings['smileys_url'] . '/" + newSet + "/" + document.forms.smileyForm["smileys[" + knownSmileys[i] + "][filename]"].value; + } + }', + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + // The list is the only thing to show, so make it the main template. + $context['default_list'] = 'smiley_list'; + $context['sub_template'] = 'show_list'; + } + // Modifying smileys. + elseif ($context['sub_action'] == 'modifysmiley') + { + // Get a list of all known smiley sets. + $context['smileys_dir'] = empty($modSettings['smileys_dir']) ? $boarddir . '/Smileys' : $modSettings['smileys_dir']; + $context['smileys_dir_found'] = is_dir($context['smileys_dir']); + $context['smiley_sets'] = explode(',', $modSettings['smiley_sets_known']); + $set_names = explode("\n", $modSettings['smiley_sets_names']); + foreach ($context['smiley_sets'] as $i => $set) + $context['smiley_sets'][$i] = array( + 'id' => $i, + 'path' => htmlspecialchars($set), + 'name' => htmlspecialchars($set_names[$i]), + 'selected' => $set == $modSettings['smiley_sets_default'] + ); + + $context['selected_set'] = $modSettings['smiley_sets_default']; + + // Get all possible filenames for the smileys. + $context['filenames'] = array(); + if ($context['smileys_dir_found']) + { + foreach ($context['smiley_sets'] as $smiley_set) + { + if (!file_exists($context['smileys_dir'] . '/' . un_htmlspecialchars($smiley_set['path']))) + continue; + + $dir = dir($context['smileys_dir'] . '/' . un_htmlspecialchars($smiley_set['path'])); + while ($entry = $dir->read()) + { + if (!in_array($entry, $context['filenames']) && in_array(strrchr($entry, '.'), array('.jpg', '.gif', '.jpeg', '.png'))) + $context['filenames'][strtolower($entry)] = array( + 'id' => htmlspecialchars($entry), + 'selected' => false, + ); + } + $dir->close(); + } + ksort($context['filenames']); + } + + $request = $smcFunc['db_query']('', ' + SELECT id_smiley AS id, code, filename, description, hidden AS location, 0 AS is_new + FROM {db_prefix}smileys + WHERE id_smiley = {int:current_smiley}', + array( + 'current_smiley' => (int) $_REQUEST['smiley'], + ) + ); + if ($smcFunc['db_num_rows']($request) != 1) + fatal_lang_error('smiley_not_found'); + $context['current_smiley'] = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + $context['current_smiley']['code'] = htmlspecialchars($context['current_smiley']['code']); + $context['current_smiley']['filename'] = htmlspecialchars($context['current_smiley']['filename']); + $context['current_smiley']['description'] = htmlspecialchars($context['current_smiley']['description']); + + if (isset($context['filenames'][strtolower($context['current_smiley']['filename'])])) + $context['filenames'][strtolower($context['current_smiley']['filename'])]['selected'] = true; + } +} + +function list_getSmileys($start, $items_per_page, $sort) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT id_smiley, code, filename, description, smiley_row, smiley_order, hidden + FROM {db_prefix}smileys + ORDER BY ' . $sort, + array( + ) + ); + $smileys = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $smileys[] = $row; + $smcFunc['db_free_result']($request); + + return $smileys; +} + +function list_getNumSmileys() +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}smileys', + array( + ) + ); + list($numSmileys) = $smcFunc['db_fetch_row']; + $smcFunc['db_free_result']($request); + + return $numSmileys; +} + +function EditSmileyOrder() +{ + global $modSettings, $context, $settings, $txt, $boarddir, $smcFunc; + + // Move smileys to another position. + if (isset($_REQUEST['reorder'])) + { + checkSession('get'); + + $_GET['location'] = empty($_GET['location']) || $_GET['location'] != 'popup' ? 0 : 2; + $_GET['source'] = empty($_GET['source']) ? 0 : (int) $_GET['source']; + + if (empty($_GET['source'])) + fatal_lang_error('smiley_not_found'); + + if (!empty($_GET['after'])) + { + $_GET['after'] = (int) $_GET['after']; + + $request = $smcFunc['db_query']('', ' + SELECT smiley_row, smiley_order, hidden + FROM {db_prefix}smileys + WHERE hidden = {int:location} + AND id_smiley = {int:after_smiley}', + array( + 'location' => $_GET['location'], + 'after_smiley' => $_GET['after'], + ) + ); + if ($smcFunc['db_num_rows']($request) != 1) + fatal_lang_error('smiley_not_found'); + list ($smiley_row, $smiley_order, $smileyLocation) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + else + { + $smiley_row = (int) $_GET['row']; + $smiley_order = -1; + $smileyLocation = (int) $_GET['location']; + } + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}smileys + SET smiley_order = smiley_order + 1 + WHERE hidden = {int:new_location} + AND smiley_row = {int:smiley_row} + AND smiley_order > {int:smiley_order}', + array( + 'new_location' => $_GET['location'], + 'smiley_row' => $smiley_row, + 'smiley_order' => $smiley_order, + ) + ); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}smileys + SET + smiley_order = {int:smiley_order} + 1, + smiley_row = {int:smiley_row}, + hidden = {int:new_location} + WHERE id_smiley = {int:current_smiley}', + array( + 'smiley_order' => $smiley_order, + 'smiley_row' => $smiley_row, + 'new_location' => $smileyLocation, + 'current_smiley' => $_GET['source'], + ) + ); + + cache_put_data('parsing_smileys', null, 480); + cache_put_data('posting_smileys', null, 480); + } + + $request = $smcFunc['db_query']('', ' + SELECT id_smiley, code, filename, description, smiley_row, smiley_order, hidden + FROM {db_prefix}smileys + WHERE hidden != {int:popup} + ORDER BY smiley_order, smiley_row', + array( + 'popup' => 1, + ) + ); + $context['smileys'] = array( + 'postform' => array( + 'rows' => array(), + ), + 'popup' => array( + 'rows' => array(), + ), + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $location = empty($row['hidden']) ? 'postform' : 'popup'; + $context['smileys'][$location]['rows'][$row['smiley_row']][] = array( + 'id' => $row['id_smiley'], + 'code' => htmlspecialchars($row['code']), + 'filename' => htmlspecialchars($row['filename']), + 'description' => htmlspecialchars($row['description']), + 'row' => $row['smiley_row'], + 'order' => $row['smiley_order'], + 'selected' => !empty($_REQUEST['move']) && $_REQUEST['move'] == $row['id_smiley'], + ); + } + $smcFunc['db_free_result']($request); + + $context['move_smiley'] = empty($_REQUEST['move']) ? 0 : (int) $_REQUEST['move']; + + // Make sure all rows are sequential. + foreach (array_keys($context['smileys']) as $location) + $context['smileys'][$location] = array( + 'id' => $location, + 'title' => $location == 'postform' ? $txt['smileys_location_form'] : $txt['smileys_location_popup'], + 'description' => $location == 'postform' ? $txt['smileys_location_form_description'] : $txt['smileys_location_popup_description'], + 'last_row' => count($context['smileys'][$location]['rows']), + 'rows' => array_values($context['smileys'][$location]['rows']), + ); + + // Check & fix smileys that are not ordered properly in the database. + foreach (array_keys($context['smileys']) as $location) + { + foreach ($context['smileys'][$location]['rows'] as $id => $smiley_row) + { + // Fix empty rows if any. + if ($id != $smiley_row[0]['row']) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}smileys + SET smiley_row = {int:new_row} + WHERE smiley_row = {int:current_row} + AND hidden = {int:location}', + array( + 'new_row' => $id, + 'current_row' => $smiley_row[0]['row'], + 'location' => $location == 'postform' ? '0' : '2', + ) + ); + // Only change the first row value of the first smiley (we don't need the others :P). + $context['smileys'][$location]['rows'][$id][0]['row'] = $id; + } + // Make sure the smiley order is always sequential. + foreach ($smiley_row as $order_id => $smiley) + if ($order_id != $smiley['order']) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}smileys + SET smiley_order = {int:new_order} + WHERE id_smiley = {int:current_smiley}', + array( + 'new_order' => $order_id, + 'current_smiley' => $smiley['id'], + ) + ); + } + } + + cache_put_data('parsing_smileys', null, 480); + cache_put_data('posting_smileys', null, 480); +} + +function InstallSmileySet() +{ + global $sourcedir, $boarddir, $modSettings, $smcFunc; + + isAllowedTo('manage_smileys'); + checkSession('request'); + + require_once($sourcedir . '/Subs-Package.php'); + + $name = strtok(basename(isset($_FILES['set_gz']) ? $_FILES['set_gz']['name'] : $_REQUEST['set_gz']), '.'); + $name = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $name); + + // !!! Decide: overwrite or not? + if (isset($_FILES['set_gz']) && is_uploaded_file($_FILES['set_gz']['tmp_name']) && (@ini_get('open_basedir') != '' || file_exists($_FILES['set_gz']['tmp_name']))) + $extracted = read_tgz_file($_FILES['set_gz']['tmp_name'], $boarddir . '/Smileys/' . $name); + elseif (isset($_REQUEST['set_gz'])) + { + // Check that the smiley is from simplemachines.org, for now... maybe add mirroring later. + if (preg_match('~^http://[\w_\-]+\.simplemachines\.org/~', $_REQUEST['set_gz']) == 0 || strpos($_REQUEST['set_gz'], 'dlattach') !== false) + fatal_lang_error('not_on_simplemachines'); + + $extracted = read_tgz_file($_REQUEST['set_gz'], $boarddir . '/Smileys/' . $name); + } + else + redirectexit('action=admin;area=smileys'); + + updateSettings(array( + 'smiley_sets_known' => $modSettings['smiley_sets_known'] . ',' . $name, + 'smiley_sets_names' => $modSettings['smiley_sets_names'] . "\n" . strtok(basename(isset($_FILES['set_gz']) ? $_FILES['set_gz']['name'] : $_REQUEST['set_gz']), '.'), + )); + + cache_put_data('parsing_smileys', null, 480); + cache_put_data('posting_smileys', null, 480); + + // !!! Add some confirmation? + redirectexit('action=admin;area=smileys'); +} + +// A function to import new smileys from an existing directory into the database. +function ImportSmileys($smileyPath) +{ + global $modSettings, $smcFunc; + + if (empty($modSettings['smileys_dir']) || !is_dir($modSettings['smileys_dir'] . '/' . $smileyPath)) + fatal_lang_error('smiley_set_unable_to_import'); + + $smileys = array(); + $dir = dir($modSettings['smileys_dir'] . '/' . $smileyPath); + while ($entry = $dir->read()) + { + if (in_array(strrchr($entry, '.'), array('.jpg', '.gif', '.jpeg', '.png'))) + $smileys[strtolower($entry)] = $entry; + } + $dir->close(); + + // Exclude the smileys that are already in the database. + $request = $smcFunc['db_query']('', ' + SELECT filename + FROM {db_prefix}smileys + WHERE filename IN ({array_string:smiley_list})', + array( + 'smiley_list' => $smileys, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + if (isset($smileys[strtolower($row['filename'])])) + unset($smileys[strtolower($row['filename'])]); + $smcFunc['db_free_result']($request); + + $request = $smcFunc['db_query']('', ' + SELECT MAX(smiley_order) + FROM {db_prefix}smileys + WHERE hidden = {int:postform} + AND smiley_row = {int:first_row}', + array( + 'postform' => 0, + 'first_row' => 0, + ) + ); + list ($smiley_order) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $new_smileys = array(); + foreach ($smileys as $smiley) + if (strlen($smiley) <= 48) + $new_smileys[] = array(':' . strtok($smiley, '.') . ':', $smiley, strtok($smiley, '.'), 0, ++$smiley_order); + + if (!empty($new_smileys)) + { + $smcFunc['db_insert']('', + '{db_prefix}smileys', + array( + 'code' => 'string-30', 'filename' => 'string-48', 'description' => 'string-80', 'smiley_row' => 'int', 'smiley_order' => 'int', + ), + $new_smileys, + array('id_smiley') + ); + + // Make sure the smiley codes are still in the right order. + sortSmileyTable(); + + cache_put_data('parsing_smileys', null, 480); + cache_put_data('posting_smileys', null, 480); + } +} + +function EditMessageIcons() +{ + global $user_info, $modSettings, $context, $settings, $txt; + global $boarddir, $smcFunc, $scripturl, $sourcedir; + + // Get a list of icons. + $context['icons'] = array(); + $request = $smcFunc['db_query']('', ' + SELECT m.id_icon, m.title, m.filename, m.icon_order, m.id_board, b.name AS board_name + FROM {db_prefix}message_icons AS m + LEFT JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + WHERE ({query_see_board} OR b.id_board IS NULL)', + array( + ) + ); + $last_icon = 0; + $trueOrder = 0; + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $context['icons'][$row['id_icon']] = array( + 'id' => $row['id_icon'], + 'title' => $row['title'], + 'filename' => $row['filename'], + 'image_url' => $settings[file_exists($settings['theme_dir'] . '/images/post/' . $row['filename'] . '.gif') ? 'actual_images_url' : 'default_images_url'] . '/post/' . $row['filename'] . '.gif', + 'board_id' => $row['id_board'], + 'board' => empty($row['board_name']) ? $txt['icons_edit_icons_all_boards'] : $row['board_name'], + 'order' => $row['icon_order'], + 'true_order' => $trueOrder++, + 'after' => $last_icon, + ); + $last_icon = $row['id_icon']; + } + $smcFunc['db_free_result']($request); + + // Submitting a form? + if (isset($_POST[$context['session_var']])) + { + checkSession(); + + // Deleting icons? + if (isset($_POST['delete']) && !empty($_POST['checked_icons'])) + { + $deleteIcons = array(); + foreach ($_POST['checked_icons'] as $icon) + $deleteIcons[] = (int) $icon; + + // Do the actual delete! + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}message_icons + WHERE id_icon IN ({array_int:icon_list})', + array( + 'icon_list' => $deleteIcons, + ) + ); + } + // Editing/Adding an icon? + elseif ($context['sub_action'] == 'editicon' && isset($_GET['icon'])) + { + $_GET['icon'] = (int) $_GET['icon']; + + // Do some preperation with the data... like check the icon exists *somewhere* + if (strpos($_POST['icon_filename'], '.gif') !== false) + $_POST['icon_filename'] = substr($_POST['icon_filename'], 0, -4); + if (!file_exists($settings['default_theme_dir'] . '/images/post/' . $_POST['icon_filename'] . '.gif')) + fatal_lang_error('icon_not_found'); + // There is a 16 character limit on message icons... + elseif (strlen($_POST['icon_filename']) > 16) + fatal_lang_error('icon_name_too_long'); + elseif ($_POST['icon_location'] == $_GET['icon'] && !empty($_GET['icon'])) + fatal_lang_error('icon_after_itself'); + + // First do the sorting... if this is an edit reduce the order of everything after it by one ;) + if ($_GET['icon'] != 0) + { + $oldOrder = $context['icons'][$_GET['icon']]['true_order']; + foreach ($context['icons'] as $id => $data) + if ($data['true_order'] > $oldOrder) + $context['icons'][$id]['true_order']--; + } + + // If there are no existing icons and this is a new one, set the id to 1 (mainly for non-mysql) + if (empty($_GET['icon']) && empty($context['icons'])) + $_GET['icon'] = 1; + + // Get the new order. + $newOrder = $_POST['icon_location'] == 0 ? 0 : $context['icons'][$_POST['icon_location']]['true_order'] + 1; + // Do the same, but with the one that used to be after this icon, done to avoid conflict. + foreach ($context['icons'] as $id => $data) + if ($data['true_order'] >= $newOrder) + $context['icons'][$id]['true_order']++; + + // Finally set the current icon's position! + $context['icons'][$_GET['icon']]['true_order'] = $newOrder; + + // Simply replace the existing data for the other bits. + $context['icons'][$_GET['icon']]['title'] = $_POST['icon_description']; + $context['icons'][$_GET['icon']]['filename'] = $_POST['icon_filename']; + $context['icons'][$_GET['icon']]['board_id'] = (int) $_POST['icon_board']; + + // Do a huge replace ;) + $iconInsert = array(); + $iconInsert_new = array(); + foreach ($context['icons'] as $id => $icon) + { + if ($id != 0) + { + $iconInsert[] = array($id, $icon['board_id'], $icon['title'], $icon['filename'], $icon['true_order']); + } + else + { + $iconInsert_new[] = array($icon['board_id'], $icon['title'], $icon['filename'], $icon['true_order']); + } + } + + $smcFunc['db_insert']('replace', + '{db_prefix}message_icons', + array('id_icon' => 'int', 'id_board' => 'int', 'title' => 'string-80', 'filename' => 'string-80', 'icon_order' => 'int'), + $iconInsert, + array('id_icon') + ); + + if (!empty($iconInsert_new)) + { + $smcFunc['db_insert']('replace', + '{db_prefix}message_icons', + array('id_board' => 'int', 'title' => 'string-80', 'filename' => 'string-80', 'icon_order' => 'int'), + $iconInsert_new, + array('id_icon') + ); + } + } + + // Sort by order, so it is quicker :) + $smcFunc['db_query']('alter_table_icons', ' + ALTER TABLE {db_prefix}message_icons + ORDER BY icon_order', + array( + 'db_error_skip' => true, + ) + ); + + // Unless we're adding a new thing, we'll escape + if (!isset($_POST['add'])) + redirectexit('action=admin;area=smileys;sa=editicons'); + } + + $context[$context['admin_menu_name']]['current_subsection'] = 'editicons'; + + $listOptions = array( + 'id' => 'message_icon_list', + 'base_href' => $scripturl . '?action=admin;area=smileys;sa=editicons', + 'get_items' => array( + 'function' => 'list_getMessageIcons', + ), + 'no_items_label' => $txt['icons_no_entries'], + 'columns' => array( + 'icon' => array( + 'data' => array( + 'function' => create_function('$rowData', ' + global $settings; + + $images_url = $settings[file_exists(sprintf(\'%1$s/images/post/%2$s.gif\', $settings[\'theme_dir\'], $rowData[\'filename\'])) ? \'actual_images_url\' : \'default_images_url\']; + return sprintf(\'%3$s\', $images_url, $rowData[\'filename\'], htmlspecialchars($rowData[\'title\'])); + '), + ), + 'style' => 'text-align: center;', + ), + 'filename' => array( + 'header' => array( + 'value' => $txt['smileys_filename'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%1$s.gif', + 'params' => array( + 'filename' => true, + ), + ), + ), + ), + 'tooltip' => array( + 'header' => array( + 'value' => $txt['smileys_description'], + ), + 'data' => array( + 'db_htmlsafe' => 'title', + 'class' => 'windowbg', + ), + ), + 'board' => array( + 'header' => array( + 'value' => $txt['icons_board'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt; + + return empty($rowData[\'board_name\']) ? $txt[\'icons_edit_icons_all_boards\'] : $rowData[\'board_name\']; + '), + ), + ), + 'modify' => array( + 'header' => array( + 'value' => $txt['smileys_modify'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '' . $txt['smileys_modify'] . '', + 'params' => array( + 'id_icon' => false, + ), + ), + 'style' => 'text-align: center', + ), + ), + 'check' => array( + 'header' => array( + 'value' => '', + ), + 'data' => array( + 'sprintf' => array( + 'format' => '', + 'params' => array( + 'id_icon' => false, + ), + ), + 'style' => 'text-align: center', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=admin;area=smileys;sa=editicons', + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => '[' . $txt['icons_add_new'] . ']', + ), + ), + ); + + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + // If we're adding/editing an icon we'll need a list of boards + if ($context['sub_action'] == 'editicon' || isset($_POST['add'])) + { + // Force the sub_template just in case. + $context['sub_template'] = 'editicon'; + + $context['new_icon'] = !isset($_GET['icon']); + + // Get the properties of the current icon from the icon list. + if (!$context['new_icon']) + $context['icon'] = $context['icons'][$_GET['icon']]; + + // Get a list of boards needed for assigning this icon to a specific board. + $boardListOptions = array( + 'use_permissions' => true, + 'selected_board' => isset($context['icon']['board_id']) ? $context['icon']['board_id'] : 0, + ); + require_once($sourcedir . '/Subs-MessageIndex.php'); + $context['categories'] = getBoardList($boardListOptions); + } +} + +function list_getMessageIcons($start, $items_per_page, $sort) +{ + global $smcFunc, $user_info; + + $request = $smcFunc['db_query']('', ' + SELECT m.id_icon, m.title, m.filename, m.icon_order, m.id_board, b.name AS board_name + FROM {db_prefix}message_icons AS m + LEFT JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + WHERE ({query_see_board} OR b.id_board IS NULL)', + array( + ) + ); + + $message_icons = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $message_icons[] = $row; + $smcFunc['db_free_result']($request); + + return $message_icons; +} + +// This function sorts the smiley table by code length, it is needed as MySQL withdrew support for functions in order by. +function sortSmileyTable() +{ + global $smcFunc; + + db_extend('packages'); + + // Add a sorting column. + $smcFunc['db_add_column']('{db_prefix}smileys', array('name' => 'temp_order', 'size' => 8, 'type' => 'mediumint', 'null' => false)); + + // Set the contents of this column. + $smcFunc['db_query']('set_smiley_order', ' + UPDATE {db_prefix}smileys + SET temp_order = LENGTH(code)', + array( + ) + ); + + // Order the table by this column. + $smcFunc['db_query']('alter_table_smileys', ' + ALTER TABLE {db_prefix}smileys + ORDER BY temp_order DESC', + array( + 'db_error_skip' => true, + ) + ); + + // Remove the sorting column. + $smcFunc['db_remove_column']('{db_prefix}smileys', 'temp_order'); +} + +?> \ No newline at end of file diff --git a/Sources/Memberlist.php b/Sources/Memberlist.php new file mode 100644 index 0000000..54d4b72 --- /dev/null +++ b/Sources/Memberlist.php @@ -0,0 +1,592 @@ + array('label', 'function', 'is_selected') + $subActions = array( + 'all' => array($txt['view_all_members'], 'MLAll', $context['listing_by'] == 'all'), + 'search' => array($txt['mlist_search'], 'MLSearch', $context['listing_by'] == 'search'), + ); + + // Set up the sort links. + $context['sort_links'] = array(); + foreach ($subActions as $act => $text) + $context['sort_links'][] = array( + 'label' => $text[0], + 'action' => $act, + 'selected' => $text[2], + ); + + $context['num_members'] = $modSettings['totalMembers']; + + // Set up the columns... + $context['columns'] = array( + 'is_online' => array( + 'label' => $txt['status'], + 'width' => '60', + 'class' => 'first_th', + ), + 'real_name' => array( + 'label' => $txt['username'] + ), + 'email_address' => array( + 'label' => $txt['email'], + 'width' => '25' + ), + 'website_url' => array( + 'label' => $txt['website'], + 'width' => '70', + 'link_with' => 'website', + ), + 'icq' => array( + 'label' => $txt['icq'], + 'width' => '30' + ), + 'aim' => array( + 'label' => $txt['aim'], + 'width' => '30' + ), + 'yim' => array( + 'label' => $txt['yim'], + 'width' => '30' + ), + 'msn' => array( + 'label' => $txt['msn'], + 'width' => '30' + ), + 'id_group' => array( + 'label' => $txt['position'] + ), + 'registered' => array( + 'label' => $txt['date_registered'] + ), + 'posts' => array( + 'label' => $txt['posts'], + 'width' => '115', + 'colspan' => '2', + 'default_sort_rev' => true, + ) + ); + + $context['colspan'] = 0; + $context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array(); + foreach ($context['columns'] as $key => $column) + { + if (isset($context['disabled_fields'][$key]) || (isset($column['link_with']) && isset($context['disabled_fields'][$column['link_with']]))) + { + unset($context['columns'][$key]); + continue; + } + + $context['colspan'] += isset($column['colspan']) ? $column['colspan'] : 1; + } + + // Aesthetic stuff. + end($context['columns']); + $context['columns'][key($context['columns'])]['class'] = 'last_th'; + + $context['linktree'][] = array( + 'url' => $scripturl . '?action=mlist', + 'name' => $txt['members_list'] + ); + + $context['can_send_pm'] = allowedTo('pm_send'); + + // Jump to the sub action. + if (isset($subActions[$context['listing_by']])) + $subActions[$context['listing_by']][1](); + else + $subActions['all'][1](); +} + +// List all members, page by page. +function MLAll() +{ + global $txt, $scripturl, $user_info; + global $modSettings, $context, $smcFunc; + + // The chunk size for the cached index. + $cache_step_size = 500; + + // Only use caching if: + // 1. there are at least 2k members, + // 2. the default sorting method (real_name) is being used, + // 3. the page shown is high enough to make a DB filesort unprofitable. + $use_cache = $modSettings['totalMembers'] > 2000 && (!isset($_REQUEST['sort']) || $_REQUEST['sort'] === 'real_name') && isset($_REQUEST['start']) && $_REQUEST['start'] > $cache_step_size; + + if ($use_cache) + { + // Maybe there's something cached already. + if (!empty($modSettings['memberlist_cache'])) + $memberlist_cache = @unserialize($modSettings['memberlist_cache']); + + // The chunk size for the cached index. + $cache_step_size = 500; + + // Only update the cache if something changed or no cache existed yet. + if (empty($memberlist_cache) || empty($modSettings['memberlist_updated']) || $memberlist_cache['last_update'] < $modSettings['memberlist_updated']) + { + $request = $smcFunc['db_query']('', ' + SELECT real_name + FROM {db_prefix}members + WHERE is_activated = {int:is_activated} + ORDER BY real_name', + array( + 'is_activated' => 1, + ) + ); + + $memberlist_cache = array( + 'last_update' => time(), + 'num_members' => $smcFunc['db_num_rows']($request), + 'index' => array(), + ); + + for ($i = 0, $n = $smcFunc['db_num_rows']($request); $i < $n; $i += $cache_step_size) + { + $smcFunc['db_data_seek']($request, $i); + list($memberlist_cache['index'][$i]) = $smcFunc['db_fetch_row']($request); + } + $smcFunc['db_data_seek']($request, $memberlist_cache['num_members'] - 1); + list ($memberlist_cache['index'][$i]) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Now we've got the cache...store it. + updateSettings(array('memberlist_cache' => serialize($memberlist_cache))); + } + + $context['num_members'] = $memberlist_cache['num_members']; + } + + // Without cache we need an extra query to get the amount of members. + else + { + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}members + WHERE is_activated = {int:is_activated}', + array( + 'is_activated' => 1, + ) + ); + list ($context['num_members']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + // Set defaults for sort (real_name) and start. (0) + if (!isset($_REQUEST['sort']) || !isset($context['columns'][$_REQUEST['sort']])) + $_REQUEST['sort'] = 'real_name'; + + if (!is_numeric($_REQUEST['start'])) + { + if (preg_match('~^[^\'\\\\/]~' . ($context['utf8'] ? 'u' : ''), $smcFunc['strtolower']($_REQUEST['start']), $match) === 0) + fatal_error('Hacker?', false); + + $_REQUEST['start'] = $match[0]; + + $request = $smcFunc['db_query']('substring', ' + SELECT COUNT(*) + FROM {db_prefix}members + WHERE LOWER(SUBSTRING(real_name, 1, 1)) < {string:first_letter} + AND is_activated = {int:is_activated}', + array( + 'is_activated' => 1, + 'first_letter' => $_REQUEST['start'], + ) + ); + list ($_REQUEST['start']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + $context['letter_links'] = ''; + for ($i = 97; $i < 123; $i++) + $context['letter_links'] .= '' . strtoupper(chr($i)) . ' '; + + // Sort out the column information. + foreach ($context['columns'] as $col => $column_details) + { + $context['columns'][$col]['href'] = $scripturl . '?action=mlist;sort=' . $col . ';start=0'; + + if ((!isset($_REQUEST['desc']) && $col == $_REQUEST['sort']) || ($col != $_REQUEST['sort'] && !empty($column_details['default_sort_rev']))) + $context['columns'][$col]['href'] .= ';desc'; + + $context['columns'][$col]['link'] = '' . $context['columns'][$col]['label'] . ''; + $context['columns'][$col]['selected'] = $_REQUEST['sort'] == $col; + } + + $context['sort_by'] = $_REQUEST['sort']; + $context['sort_direction'] = !isset($_REQUEST['desc']) ? 'up' : 'down'; + + // Construct the page index. + $context['page_index'] = constructPageIndex($scripturl . '?action=mlist;sort=' . $_REQUEST['sort'] . (isset($_REQUEST['desc']) ? ';desc' : ''), $_REQUEST['start'], $context['num_members'], $modSettings['defaultMaxMembers']); + + // Send the data to the template. + $context['start'] = $_REQUEST['start'] + 1; + $context['end'] = min($_REQUEST['start'] + $modSettings['defaultMaxMembers'], $context['num_members']); + + $context['can_moderate_forum'] = allowedTo('moderate_forum'); + $context['page_title'] = sprintf($txt['viewing_members'], $context['start'], $context['end']); + $context['linktree'][] = array( + 'url' => $scripturl . '?action=mlist;sort=' . $_REQUEST['sort'] . ';start=' . $_REQUEST['start'], + 'name' => &$context['page_title'], + 'extra_after' => ' (' . sprintf($txt['of_total_members'], $context['num_members']) . ')' + ); + + // List out the different sorting methods... + $sort_methods = array( + 'is_online' => array( + 'down' => allowedTo('moderate_forum') ? 'IFNULL(lo.log_time, 1) ASC, real_name ASC' : 'CASE WHEN mem.show_online THEN IFNULL(lo.log_time, 1) ELSE 1 END ASC, real_name ASC', + 'up' => allowedTo('moderate_forum') ? 'IFNULL(lo.log_time, 1) DESC, real_name DESC' : 'CASE WHEN mem.show_online THEN IFNULL(lo.log_time, 1) ELSE 1 END DESC, real_name DESC' + ), + 'real_name' => array( + 'down' => 'mem.real_name DESC', + 'up' => 'mem.real_name ASC' + ), + 'email_address' => array( + 'down' => allowedTo('moderate_forum') ? 'mem.email_address DESC' : 'mem.hide_email DESC, mem.email_address DESC', + 'up' => allowedTo('moderate_forum') ? 'mem.email_address ASC' : 'mem.hide_email ASC, mem.email_address ASC' + ), + 'website_url' => array( + 'down' => 'LENGTH(mem.website_url) > 0 ASC, IFNULL(mem.website_url, 1=1) DESC, mem.website_url DESC', + 'up' => 'LENGTH(mem.website_url) > 0 DESC, IFNULL(mem.website_url, 1=1) ASC, mem.website_url ASC' + ), + 'icq' => array( + 'down' => 'LENGTH(mem.icq) > 0 ASC, mem.icq = 0 DESC, mem.icq DESC', + 'up' => 'LENGTH(mem.icq) > 0 DESC, mem.icq = 0 ASC, mem.icq ASC' + ), + 'aim' => array( + 'down' => 'LENGTH(mem.aim) > 0 ASC, IFNULL(mem.aim, 1=1) DESC, mem.aim DESC', + 'up' => 'LENGTH(mem.aim) > 0 DESC, IFNULL(mem.aim, 1=1) ASC, mem.aim ASC' + ), + 'yim' => array( + 'down' => 'LENGTH(mem.yim) > 0 ASC, IFNULL(mem.yim, 1=1) DESC, mem.yim DESC', + 'up' => 'LENGTH(mem.yim) > 0 DESC, IFNULL(mem.yim, 1=1) ASC, mem.yim ASC' + ), + 'msn' => array( + 'down' => 'LENGTH(mem.msn) > 0 ASC, IFNULL(mem.msn, 1=1) DESC, mem.msn DESC', + 'up' => 'LENGTH(mem.msn) > 0 DESC, IFNULL(mem.msn, 1=1) ASC, mem.msn ASC' + ), + 'registered' => array( + 'down' => 'mem.date_registered DESC', + 'up' => 'mem.date_registered ASC' + ), + 'id_group' => array( + 'down' => 'IFNULL(mg.group_name, 1=1) DESC, mg.group_name DESC', + 'up' => 'IFNULL(mg.group_name, 1=1) ASC, mg.group_name ASC' + ), + 'posts' => array( + 'down' => 'mem.posts DESC', + 'up' => 'mem.posts ASC' + ) + ); + + $limit = $_REQUEST['start']; + $query_parameters = array( + 'regular_id_group' => 0, + 'is_activated' => 1, + 'sort' => $sort_methods[$_REQUEST['sort']][$context['sort_direction']], + ); + + // Using cache allows to narrow down the list to be retrieved. + if ($use_cache && $_REQUEST['sort'] === 'real_name' && !isset($_REQUEST['desc'])) + { + $first_offset = $_REQUEST['start'] - ($_REQUEST['start'] % $cache_step_size); + $second_offset = ceil(($_REQUEST['start'] + $modSettings['defaultMaxMembers']) / $cache_step_size) * $cache_step_size; + + $where = 'mem.real_name BETWEEN {string:real_name_low} AND {string:real_name_high}'; + $query_parameters['real_name_low'] = $memberlist_cache['index'][$first_offset]; + $query_parameters['real_name_high'] = $memberlist_cache['index'][$second_offset]; + $limit -= $first_offset; + } + + // Reverse sorting is a bit more complicated... + elseif ($use_cache && $_REQUEST['sort'] === 'real_name') + { + $first_offset = floor(($memberlist_cache['num_members'] - $modSettings['defaultMaxMembers'] - $_REQUEST['start']) / $cache_step_size) * $cache_step_size; + if ($first_offset < 0) + $first_offset = 0; + $second_offset = ceil(($memberlist_cache['num_members'] - $_REQUEST['start']) / $cache_step_size) * $cache_step_size; + + $where = 'mem.real_name BETWEEN {string:real_name_low} AND {string:real_name_high}'; + $query_parameters['real_name_low'] = $memberlist_cache['index'][$first_offset]; + $query_parameters['real_name_high'] = $memberlist_cache['index'][$second_offset]; + $limit = $second_offset - ($memberlist_cache['num_members'] - $_REQUEST['start']) - ($second_offset > $memberlist_cache['num_members'] ? $cache_step_size - ($memberlist_cache['num_members'] % $cache_step_size) : 0); + } + + // Select the members from the database. + $request = $smcFunc['db_query']('', ' + SELECT mem.id_member + FROM {db_prefix}members AS mem' . ($_REQUEST['sort'] === 'is_online' ? ' + LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)' : '') . ($_REQUEST['sort'] === 'id_group' ? ' + LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:regular_id_group} THEN mem.id_post_group ELSE mem.id_group END)' : '') . ' + WHERE mem.is_activated = {int:is_activated}' . (empty($where) ? '' : ' + AND ' . $where) . ' + ORDER BY {raw:sort} + LIMIT ' . $limit . ', ' . $modSettings['defaultMaxMembers'], + $query_parameters + ); + printMemberListRows($request); + $smcFunc['db_free_result']($request); + + // Add anchors at the start of each letter. + if ($_REQUEST['sort'] == 'real_name') + { + $last_letter = ''; + foreach ($context['members'] as $i => $dummy) + { + $this_letter = $smcFunc['strtolower']($smcFunc['substr']($context['members'][$i]['name'], 0, 1)); + + if ($this_letter != $last_letter && preg_match('~[a-z]~', $this_letter) === 1) + { + $context['members'][$i]['sort_letter'] = htmlspecialchars($this_letter); + $last_letter = $this_letter; + } + } + } +} + +// Search for members... +function MLSearch() +{ + global $txt, $scripturl, $context, $user_info, $modSettings, $smcFunc; + + $context['page_title'] = $txt['mlist_search']; + $context['can_moderate_forum'] = allowedTo('moderate_forum'); + + // Can they search custom fields? + $request = $smcFunc['db_query']('', ' + SELECT col_name, field_name, field_desc + FROM {db_prefix}custom_fields + WHERE active = {int:active} + ' . (allowedTo('admin_forum') ? '' : ' AND private < {int:private_level}') . ' + AND can_search = {int:can_search} + AND (field_type = {string:field_type_text} OR field_type = {string:field_type_textarea})', + array( + 'active' => 1, + 'can_search' => 1, + 'private_level' => 2, + 'field_type_text' => 'text', + 'field_type_textarea' => 'textarea', + ) + ); + $context['custom_search_fields'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['custom_search_fields'][$row['col_name']] = array( + 'colname' => $row['col_name'], + 'name' => $row['field_name'], + 'desc' => $row['field_desc'], + ); + $smcFunc['db_free_result']($request); + + // They're searching.. + if (isset($_REQUEST['search']) && isset($_REQUEST['fields'])) + { + $_POST['search'] = trim(isset($_GET['search']) ? $_GET['search'] : $_POST['search']); + $_POST['fields'] = isset($_GET['fields']) ? explode(',', $_GET['fields']) : $_POST['fields']; + + $context['old_search'] = $_REQUEST['search']; + $context['old_search_value'] = urlencode($_REQUEST['search']); + + // No fields? Use default... + if (empty($_POST['fields'])) + $_POST['fields'] = array('name'); + + $query_parameters = array( + 'regular_id_group' => 0, + 'is_activated' => 1, + 'blank_string' => '', + 'search' => '%' . strtr($smcFunc['htmlspecialchars']($_POST['search'], ENT_QUOTES), array('_' => '\\_', '%' => '\\%', '*' => '%')) . '%', + ); + + // Search for a name? + if (in_array('name', $_POST['fields'])) + $fields = array('member_name', 'real_name'); + else + $fields = array(); + // Search for messengers... + if (in_array('messenger', $_POST['fields']) && (!$user_info['is_guest'] || empty($modSettings['guest_hideContacts']))) + $fields += array(3 => 'msn', 'aim', 'icq', 'yim'); + // Search for websites. + if (in_array('website', $_POST['fields'])) + $fields += array(7 => 'website_title', 'website_url'); + // Search for groups. + if (in_array('group', $_POST['fields'])) + $fields += array(9 => 'IFNULL(group_name, {string:blank_string})'); + // Search for an email address? + if (in_array('email', $_POST['fields'])) + { + $fields += array(2 => allowedTo('moderate_forum') ? 'email_address' : '(hide_email = 0 AND email_address'); + $condition = allowedTo('moderate_forum') ? '' : ')'; + } + else + $condition = ''; + + $customJoin = array(); + $customCount = 10; + // Any custom fields to search for - these being tricky? + foreach ($_POST['fields'] as $field) + { + $curField = substr($field, 5); + if (substr($field, 0, 5) == 'cust_' && isset($context['custom_search_fields'][$curField])) + { + $customJoin[] = 'LEFT JOIN {db_prefix}themes AS t' . $curField . ' ON (t' . $curField . '.variable = {string:t' . $curField . '} AND t' . $curField . '.id_theme = 1 AND t' . $curField . '.id_member = mem.id_member)'; + $query_parameters['t' . $curField] = $curField; + $fields += array($customCount++ => 'IFNULL(t' . $curField . '.value, {string:blank_string})'); + } + } + + $query = $_POST['search'] == '' ? '= {string:blank_string}' : 'LIKE {string:search}'; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}members AS mem + LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:regular_id_group} THEN mem.id_post_group ELSE mem.id_group END)' . + (empty($customJoin) ? '' : implode(' + ', $customJoin)) . ' + WHERE (' . implode( ' ' . $query . ' OR ', $fields) . ' ' . $query . $condition . ') + AND mem.is_activated = {int:is_activated}', + $query_parameters + ); + list ($numResults) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $context['page_index'] = constructPageIndex($scripturl . '?action=mlist;sa=search;search=' . $_POST['search'] . ';fields=' . implode(',', $_POST['fields']), $_REQUEST['start'], $numResults, $modSettings['defaultMaxMembers']); + + // Find the members from the database. + // !!!SLOW This query is slow. + $request = $smcFunc['db_query']('', ' + SELECT mem.id_member + FROM {db_prefix}members AS mem + LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member) + LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:regular_id_group} THEN mem.id_post_group ELSE mem.id_group END)' . + (empty($customJoin) ? '' : implode(' + ', $customJoin)) . ' + WHERE (' . implode( ' ' . $query . ' OR ', $fields) . ' ' . $query . $condition . ') + AND mem.is_activated = {int:is_activated} + LIMIT ' . $_REQUEST['start'] . ', ' . $modSettings['defaultMaxMembers'], + $query_parameters + ); + printMemberListRows($request); + $smcFunc['db_free_result']($request); + } + else + { + // These are all the possible fields. + $context['search_fields'] = array( + 'name' => $txt['mlist_search_name'], + 'email' => $txt['mlist_search_email'], + 'messenger' => $txt['mlist_search_messenger'], + 'website' => $txt['mlist_search_website'], + 'group' => $txt['mlist_search_group'], + ); + + foreach ($context['custom_search_fields'] as $field) + $context['search_fields']['cust_' . $field['colname']] = sprintf($txt['mlist_search_by'], $field['name']); + + // What do we search for by default? + $context['search_defaults'] = array('name', 'email'); + + $context['sub_template'] = 'search'; + $context['old_search'] = isset($_GET['search']) ? $_GET['search'] : (isset($_POST['search']) ? htmlspecialchars($_POST['search']) : ''); + } + + $context['linktree'][] = array( + 'url' => $scripturl . '?action=mlist;sa=search', + 'name' => &$context['page_title'] + ); +} + +function printMemberListRows($request) +{ + global $scripturl, $txt, $user_info, $modSettings; + global $context, $settings, $memberContext, $smcFunc; + + // Get the most posts. + $result = $smcFunc['db_query']('', ' + SELECT MAX(posts) + FROM {db_prefix}members', + array( + ) + ); + list ($MOST_POSTS) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + // Avoid division by zero... + if ($MOST_POSTS == 0) + $MOST_POSTS = 1; + + $members = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $members[] = $row['id_member']; + + // Load all the members for display. + loadMemberData($members); + + $context['members'] = array(); + foreach ($members as $member) + { + if (!loadMemberContext($member)) + continue; + + $context['members'][$member] = $memberContext[$member]; + $context['members'][$member]['post_percent'] = round(($context['members'][$member]['real_posts'] * 100) / $MOST_POSTS); + $context['members'][$member]['registered_date'] = strftime('%Y-%m-%d', $context['members'][$member]['registered_timestamp']); + } +} + +?> \ No newline at end of file diff --git a/Sources/MessageIndex.php b/Sources/MessageIndex.php new file mode 100644 index 0000000..253a5ab --- /dev/null +++ b/Sources/MessageIndex.php @@ -0,0 +1,1172 @@ + $board, + ) + ); + + redirectexit($board_info['redirect']); + } + + if (WIRELESS) + $context['sub_template'] = WIRELESS_PROTOCOL . '_messageindex'; + else + loadTemplate('MessageIndex'); + + $context['name'] = $board_info['name']; + $context['description'] = $board_info['description']; + // How many topics do we have in total? + $board_info['total_topics'] = allowedTo('approve_posts') ? $board_info['num_topics'] + $board_info['unapproved_topics'] : $board_info['num_topics'] + $board_info['unapproved_user_topics']; + + // View all the topics, or just a few? + $context['topics_per_page'] = empty($modSettings['disableCustomPerPage']) && !empty($options['topics_per_page']) && !WIRELESS ? $options['topics_per_page'] : $modSettings['defaultMaxTopics']; + $context['messages_per_page'] = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) && !WIRELESS ? $options['messages_per_page'] : $modSettings['defaultMaxMessages']; + $maxindex = isset($_REQUEST['all']) && !empty($modSettings['enableAllMessages']) ? $board_info['total_topics'] : $context['topics_per_page']; + + // Right, let's only index normal stuff! + if (count($_GET) > 1) + { + $session_name = session_name(); + foreach ($_GET as $k => $v) + { + if (!in_array($k, array('board', 'start', $session_name))) + $context['robot_no_index'] = true; + } + } + if (!empty($_REQUEST['start']) && (!is_numeric($_REQUEST['start']) || $_REQUEST['start'] % $context['messages_per_page'] != 0)) + $context['robot_no_index'] = true; + + // If we can view unapproved messages and there are some build up a list. + if (allowedTo('approve_posts') && ($board_info['unapproved_topics'] || $board_info['unapproved_posts'])) + { + $untopics = $board_info['unapproved_topics'] ? '' . $board_info['unapproved_topics'] . '' : 0; + $unposts = $board_info['unapproved_posts'] ? '' . ($board_info['unapproved_posts'] - $board_info['unapproved_topics']) . '' : 0; + $context['unapproved_posts_message'] = sprintf($txt['there_are_unapproved_topics'], $untopics, $unposts, $scripturl . '?action=moderate;area=postmod;sa=' . ($board_info['unapproved_topics'] ? 'topics' : 'posts') . ';brd=' . $board); + } + + // We only know these. + if (isset($_REQUEST['sort']) && !in_array($_REQUEST['sort'], array('subject', 'starter', 'last_poster', 'replies', 'views', 'first_post', 'last_post'))) + $_REQUEST['sort'] = 'last_post'; + + // Make sure the starting place makes sense and construct the page index. + if (isset($_REQUEST['sort'])) + $context['page_index'] = constructPageIndex($scripturl . '?board=' . $board . '.%1$d;sort=' . $_REQUEST['sort'] . (isset($_REQUEST['desc']) ? ';desc' : ''), $_REQUEST['start'], $board_info['total_topics'], $maxindex, true); + else + $context['page_index'] = constructPageIndex($scripturl . '?board=' . $board . '.%1$d', $_REQUEST['start'], $board_info['total_topics'], $maxindex, true); + $context['start'] = &$_REQUEST['start']; + + // Set a canonical URL for this page. + $context['canonical_url'] = $scripturl . '?board=' . $board . '.' . $context['start']; + + $context['links'] = array( + 'first' => $_REQUEST['start'] >= $context['topics_per_page'] ? $scripturl . '?board=' . $board . '.0' : '', + 'prev' => $_REQUEST['start'] >= $context['topics_per_page'] ? $scripturl . '?board=' . $board . '.' . ($_REQUEST['start'] - $context['topics_per_page']) : '', + 'next' => $_REQUEST['start'] + $context['topics_per_page'] < $board_info['total_topics'] ? $scripturl . '?board=' . $board . '.' . ($_REQUEST['start'] + $context['topics_per_page']) : '', + 'last' => $_REQUEST['start'] + $context['topics_per_page'] < $board_info['total_topics'] ? $scripturl . '?board=' . $board . '.' . (floor(($board_info['total_topics'] - 1) / $context['topics_per_page']) * $context['topics_per_page']) : '', + 'up' => $board_info['parent'] == 0 ? $scripturl . '?' : $scripturl . '?board=' . $board_info['parent'] . '.0' + ); + + $context['page_info'] = array( + 'current_page' => $_REQUEST['start'] / $context['topics_per_page'] + 1, + 'num_pages' => floor(($board_info['total_topics'] - 1) / $context['topics_per_page']) + 1 + ); + + if (isset($_REQUEST['all']) && !empty($modSettings['enableAllMessages']) && $maxindex > $modSettings['enableAllMessages']) + { + $maxindex = $modSettings['enableAllMessages']; + $_REQUEST['start'] = 0; + } + + // Build a list of the board's moderators. + $context['moderators'] = &$board_info['moderators']; + $context['link_moderators'] = array(); + if (!empty($board_info['moderators'])) + { + foreach ($board_info['moderators'] as $mod) + $context['link_moderators'][] ='' . $mod['name'] . ''; + + $context['linktree'][count($context['linktree']) - 1]['extra_after'] = ' (' . (count($context['link_moderators']) == 1 ? $txt['moderator'] : $txt['moderators']) . ': ' . implode(', ', $context['link_moderators']) . ')'; + } + + // Mark current and parent boards as seen. + if (!$user_info['is_guest']) + { + // We can't know they read it if we allow prefetches. + if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') + { + ob_end_clean(); + header('HTTP/1.1 403 Prefetch Forbidden'); + die; + } + + $smcFunc['db_insert']('replace', + '{db_prefix}log_boards', + array('id_msg' => 'int', 'id_member' => 'int', 'id_board' => 'int'), + array($modSettings['maxMsgID'], $user_info['id'], $board), + array('id_member', 'id_board') + ); + + if (!empty($board_info['parent_boards'])) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_boards + SET id_msg = {int:id_msg} + WHERE id_member = {int:current_member} + AND id_board IN ({array_int:board_list})', + array( + 'current_member' => $user_info['id'], + 'board_list' => array_keys($board_info['parent_boards']), + 'id_msg' => $modSettings['maxMsgID'], + ) + ); + + // We've seen all these boards now! + foreach ($board_info['parent_boards'] as $k => $dummy) + if (isset($_SESSION['topicseen_cache'][$k])) + unset($_SESSION['topicseen_cache'][$k]); + } + + if (isset($_SESSION['topicseen_cache'][$board])) + unset($_SESSION['topicseen_cache'][$board]); + + $request = $smcFunc['db_query']('', ' + SELECT sent + FROM {db_prefix}log_notify + WHERE id_board = {int:current_board} + AND id_member = {int:current_member} + LIMIT 1', + array( + 'current_board' => $board, + 'current_member' => $user_info['id'], + ) + ); + $context['is_marked_notify'] = $smcFunc['db_num_rows']($request) != 0; + if ($context['is_marked_notify']) + { + list ($sent) = $smcFunc['db_fetch_row']($request); + if (!empty($sent)) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_notify + SET sent = {int:is_sent} + WHERE id_board = {int:current_board} + AND id_member = {int:current_member}', + array( + 'current_board' => $board, + 'current_member' => $user_info['id'], + 'is_sent' => 0, + ) + ); + } + } + $smcFunc['db_free_result']($request); + } + else + $context['is_marked_notify'] = false; + + // 'Print' the header and board info. + $context['page_title'] = strip_tags($board_info['name']); + + // Set the variables up for the template. + $context['can_mark_notify'] = allowedTo('mark_notify') && !$user_info['is_guest']; + $context['can_post_new'] = allowedTo('post_new') || ($modSettings['postmod_active'] && allowedTo('post_unapproved_topics')); + $context['can_post_poll'] = $modSettings['pollMode'] == '1' && allowedTo('poll_post') && $context['can_post_new']; + $context['can_moderate_forum'] = allowedTo('moderate_forum'); + $context['can_approve_posts'] = allowedTo('approve_posts'); + + require_once($sourcedir . '/Subs-BoardIndex.php'); + $boardIndexOptions = array( + 'include_categories' => false, + 'base_level' => $board_info['child_level'] + 1, + 'parent_id' => $board_info['id'], + 'set_latest_post' => false, + 'countChildPosts' => !empty($modSettings['countChildPosts']), + ); + $context['boards'] = getBoardIndex($boardIndexOptions); + + // Nosey, nosey - who's viewing this topic? + if (!empty($settings['display_who_viewing'])) + { + $context['view_members'] = array(); + $context['view_members_list'] = array(); + $context['view_num_hidden'] = 0; + + $request = $smcFunc['db_query']('', ' + SELECT + lo.id_member, lo.log_time, mem.real_name, mem.member_name, mem.show_online, + mg.online_color, mg.id_group, mg.group_name + FROM {db_prefix}log_online AS lo + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lo.id_member) + LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:reg_member_group} THEN mem.id_post_group ELSE mem.id_group END) + WHERE INSTR(lo.url, {string:in_url_string}) > 0 OR lo.session = {string:session}', + array( + 'reg_member_group' => 0, + 'in_url_string' => 's:5:"board";i:' . $board . ';', + 'session' => $user_info['is_guest'] ? 'ip' . $user_info['ip'] : session_id(), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (empty($row['id_member'])) + continue; + + if (!empty($row['online_color'])) + $link = '' . $row['real_name'] . ''; + else + $link = '' . $row['real_name'] . ''; + + $is_buddy = in_array($row['id_member'], $user_info['buddies']); + if ($is_buddy) + $link = '' . $link . ''; + + if (!empty($row['show_online']) || allowedTo('moderate_forum')) + $context['view_members_list'][$row['log_time'] . $row['member_name']] = empty($row['show_online']) ? '' . $link . '' : $link; + $context['view_members'][$row['log_time'] . $row['member_name']] = array( + 'id' => $row['id_member'], + 'username' => $row['member_name'], + 'name' => $row['real_name'], + 'group' => $row['id_group'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_member'], + 'link' => $link, + 'is_buddy' => $is_buddy, + 'hidden' => empty($row['show_online']), + ); + + if (empty($row['show_online'])) + $context['view_num_hidden']++; + } + $context['view_num_guests'] = $smcFunc['db_num_rows']($request) - count($context['view_members']); + $smcFunc['db_free_result']($request); + + // Put them in "last clicked" order. + krsort($context['view_members_list']); + krsort($context['view_members']); + } + + // Default sort methods. + $sort_methods = array( + 'subject' => 'mf.subject', + 'starter' => 'IFNULL(memf.real_name, mf.poster_name)', + 'last_poster' => 'IFNULL(meml.real_name, ml.poster_name)', + 'replies' => 't.num_replies', + 'views' => 't.num_views', + 'first_post' => 't.id_topic', + 'last_post' => 't.id_last_msg' + ); + + // They didn't pick one, default to by last post descending. + if (!isset($_REQUEST['sort']) || !isset($sort_methods[$_REQUEST['sort']])) + { + $context['sort_by'] = 'last_post'; + $_REQUEST['sort'] = 'id_last_msg'; + $ascending = isset($_REQUEST['asc']); + } + // Otherwise default to ascending. + else + { + $context['sort_by'] = $_REQUEST['sort']; + $_REQUEST['sort'] = $sort_methods[$_REQUEST['sort']]; + $ascending = !isset($_REQUEST['desc']); + } + + $context['sort_direction'] = $ascending ? 'up' : 'down'; + + // Calculate the fastest way to get the topics. + $start = (int) $_REQUEST['start']; + if ($start > ($board_info['total_topics'] - 1) / 2) + { + $ascending = !$ascending; + $fake_ascending = true; + $maxindex = $board_info['total_topics'] < $start + $maxindex + 1 ? $board_info['total_topics'] - $start : $maxindex; + $start = $board_info['total_topics'] < $start + $maxindex + 1 ? 0 : $board_info['total_topics'] - $start - $maxindex; + } + else + $fake_ascending = false; + + // Setup the default topic icons... + $stable_icons = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'moved', 'recycled', 'wireless', 'clip'); + $context['icon_sources'] = array(); + foreach ($stable_icons as $icon) + $context['icon_sources'][$icon] = 'images_url'; + + $topic_ids = array(); + $context['topics'] = array(); + + // Sequential pages are often not optimized, so we add an additional query. + $pre_query = $start > 0; + if ($pre_query && $maxindex > 0) + { + $request = $smcFunc['db_query']('', ' + SELECT t.id_topic + FROM {db_prefix}topics AS t' . ($context['sort_by'] === 'last_poster' ? ' + INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)' : (in_array($context['sort_by'], array('starter', 'subject')) ? ' + INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)' : '')) . ($context['sort_by'] === 'starter' ? ' + LEFT JOIN {db_prefix}members AS memf ON (memf.id_member = mf.id_member)' : '') . ($context['sort_by'] === 'last_poster' ? ' + LEFT JOIN {db_prefix}members AS meml ON (meml.id_member = ml.id_member)' : '') . ' + WHERE t.id_board = {int:current_board}' . (!$modSettings['postmod_active'] || $context['can_approve_posts'] ? '' : ' + AND (t.approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR t.id_member_started = {int:current_member}') . ')') . ' + ORDER BY ' . (!empty($modSettings['enableStickyTopics']) ? 'is_sticky' . ($fake_ascending ? '' : ' DESC') . ', ' : '') . $_REQUEST['sort'] . ($ascending ? '' : ' DESC') . ' + LIMIT {int:start}, {int:maxindex}', + array( + 'current_board' => $board, + 'current_member' => $user_info['id'], + 'is_approved' => 1, + 'id_member_guest' => 0, + 'start' => $start, + 'maxindex' => $maxindex, + ) + ); + $topic_ids = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $topic_ids[] = $row['id_topic']; + } + + // Grab the appropriate topic information... + if (!$pre_query || !empty($topic_ids)) + { + // For search engine effectiveness we'll link guests differently. + $context['pageindex_multiplier'] = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) && !WIRELESS ? $options['messages_per_page'] : $modSettings['defaultMaxMessages']; + + $result = $smcFunc['db_query']('substring', ' + SELECT + t.id_topic, t.num_replies, t.locked, t.num_views, t.is_sticky, t.id_poll, t.id_previous_board, + ' . ($user_info['is_guest'] ? '0' : 'IFNULL(lt.id_msg, IFNULL(lmr.id_msg, -1)) + 1') . ' AS new_from, + t.id_last_msg, t.approved, t.unapproved_posts, ml.poster_time AS last_poster_time, + ml.id_msg_modified, ml.subject AS last_subject, ml.icon AS last_icon, + ml.poster_name AS last_member_name, ml.id_member AS last_id_member, + IFNULL(meml.real_name, ml.poster_name) AS last_display_name, t.id_first_msg, + mf.poster_time AS first_poster_time, mf.subject AS first_subject, mf.icon AS first_icon, + mf.poster_name AS first_member_name, mf.id_member AS first_id_member, + IFNULL(memf.real_name, mf.poster_name) AS first_display_name, SUBSTRING(ml.body, 1, 385) AS last_body, + SUBSTRING(mf.body, 1, 385) AS first_body, ml.smileys_enabled AS last_smileys, mf.smileys_enabled AS first_smileys + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg) + INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg) + LEFT JOIN {db_prefix}members AS meml ON (meml.id_member = ml.id_member) + LEFT JOIN {db_prefix}members AS memf ON (memf.id_member = mf.id_member)' . ($user_info['is_guest'] ? '' : ' + LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member}) + LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = {int:current_board} AND lmr.id_member = {int:current_member})'). ' + WHERE ' . ($pre_query ? 't.id_topic IN ({array_int:topic_list})' : 't.id_board = {int:current_board}') . (!$modSettings['postmod_active'] || $context['can_approve_posts'] ? '' : ' + AND (t.approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR t.id_member_started = {int:current_member}') . ')') . ' + ORDER BY ' . ($pre_query ? 'FIND_IN_SET(t.id_topic, {string:find_set_topics})' : (!empty($modSettings['enableStickyTopics']) ? 'is_sticky' . ($fake_ascending ? '' : ' DESC') . ', ' : '') . $_REQUEST['sort'] . ($ascending ? '' : ' DESC')) . ' + LIMIT ' . ($pre_query ? '' : '{int:start}, ') . '{int:maxindex}', + array( + 'current_board' => $board, + 'current_member' => $user_info['id'], + 'topic_list' => $topic_ids, + 'is_approved' => 1, + 'find_set_topics' => implode(',', $topic_ids), + 'start' => $start, + 'maxindex' => $maxindex, + ) + ); + + // Begin 'printing' the message index for current board. + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + if ($row['id_poll'] > 0 && $modSettings['pollMode'] == '0') + continue; + + if (!$pre_query) + $topic_ids[] = $row['id_topic']; + + if (!empty($settings['message_index_preview'])) + { + // Limit them to 128 characters - do this FIRST because it's a lot of wasted censoring otherwise. + $row['first_body'] = strip_tags(strtr(parse_bbc($row['first_body'], $row['first_smileys'], $row['id_first_msg']), array('
' => ' '))); + if ($smcFunc['strlen']($row['first_body']) > 128) + $row['first_body'] = $smcFunc['substr']($row['first_body'], 0, 128) . '...'; + $row['last_body'] = strip_tags(strtr(parse_bbc($row['last_body'], $row['last_smileys'], $row['id_last_msg']), array('
' => ' '))); + if ($smcFunc['strlen']($row['last_body']) > 128) + $row['last_body'] = $smcFunc['substr']($row['last_body'], 0, 128) . '...'; + + // Censor the subject and message preview. + censorText($row['first_subject']); + censorText($row['first_body']); + + // Don't censor them twice! + if ($row['id_first_msg'] == $row['id_last_msg']) + { + $row['last_subject'] = $row['first_subject']; + $row['last_body'] = $row['first_body']; + } + else + { + censorText($row['last_subject']); + censorText($row['last_body']); + } + } + else + { + $row['first_body'] = ''; + $row['last_body'] = ''; + censorText($row['first_subject']); + + if ($row['id_first_msg'] == $row['id_last_msg']) + $row['last_subject'] = $row['first_subject']; + else + censorText($row['last_subject']); + } + + // Decide how many pages the topic should have. + if ($row['num_replies'] + 1 > $context['messages_per_page']) + { + $pages = '« '; + + // We can't pass start by reference. + $start = -1; + $pages .= constructPageIndex($scripturl . '?topic=' . $row['id_topic'] . '.%1$d', $start, $row['num_replies'] + 1, $context['messages_per_page'], true); + + // If we can use all, show all. + if (!empty($modSettings['enableAllMessages']) && $row['num_replies'] + 1 < $modSettings['enableAllMessages']) + $pages .= '  ' . $txt['all'] . ''; + $pages .= ' »'; + } + else + $pages = ''; + + // We need to check the topic icons exist... + if (empty($modSettings['messageIconChecks_disable'])) + { + if (!isset($context['icon_sources'][$row['first_icon']])) + $context['icon_sources'][$row['first_icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $row['first_icon'] . '.gif') ? 'images_url' : 'default_images_url'; + if (!isset($context['icon_sources'][$row['last_icon']])) + $context['icon_sources'][$row['last_icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $row['last_icon'] . '.gif') ? 'images_url' : 'default_images_url'; + } + else + { + if (!isset($context['icon_sources'][$row['first_icon']])) + $context['icon_sources'][$row['first_icon']] = 'images_url'; + if (!isset($context['icon_sources'][$row['last_icon']])) + $context['icon_sources'][$row['last_icon']] = 'images_url'; + } + + // 'Print' the topic info. + $context['topics'][$row['id_topic']] = array( + 'id' => $row['id_topic'], + 'first_post' => array( + 'id' => $row['id_first_msg'], + 'member' => array( + 'username' => $row['first_member_name'], + 'name' => $row['first_display_name'], + 'id' => $row['first_id_member'], + 'href' => !empty($row['first_id_member']) ? $scripturl . '?action=profile;u=' . $row['first_id_member'] : '', + 'link' => !empty($row['first_id_member']) ? '' . $row['first_display_name'] . '' : $row['first_display_name'] + ), + 'time' => timeformat($row['first_poster_time']), + 'timestamp' => forum_time(true, $row['first_poster_time']), + 'subject' => $row['first_subject'], + 'preview' => $row['first_body'], + 'icon' => $row['first_icon'], + 'icon_url' => $settings[$context['icon_sources'][$row['first_icon']]] . '/post/' . $row['first_icon'] . '.gif', + 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.0', + 'link' => '' . $row['first_subject'] . '' + ), + 'last_post' => array( + 'id' => $row['id_last_msg'], + 'member' => array( + 'username' => $row['last_member_name'], + 'name' => $row['last_display_name'], + 'id' => $row['last_id_member'], + 'href' => !empty($row['last_id_member']) ? $scripturl . '?action=profile;u=' . $row['last_id_member'] : '', + 'link' => !empty($row['last_id_member']) ? '' . $row['last_display_name'] . '' : $row['last_display_name'] + ), + 'time' => timeformat($row['last_poster_time']), + 'timestamp' => forum_time(true, $row['last_poster_time']), + 'subject' => $row['last_subject'], + 'preview' => $row['last_body'], + 'icon' => $row['last_icon'], + 'icon_url' => $settings[$context['icon_sources'][$row['last_icon']]] . '/post/' . $row['last_icon'] . '.gif', + 'href' => $scripturl . '?topic=' . $row['id_topic'] . ($user_info['is_guest'] ? ('.' . (!empty($options['view_newest_first']) ? 0 : ((int) (($row['num_replies']) / $context['pageindex_multiplier'])) * $context['pageindex_multiplier']) . '#msg' . $row['id_last_msg']) : (($row['num_replies'] == 0 ? '.0' : '.msg' . $row['id_last_msg']) . '#new')), + 'link' => '' . $row['last_subject'] . '' + ), + 'is_sticky' => !empty($modSettings['enableStickyTopics']) && !empty($row['is_sticky']), + 'is_locked' => !empty($row['locked']), + 'is_poll' => $modSettings['pollMode'] == '1' && $row['id_poll'] > 0, + 'is_hot' => $row['num_replies'] >= $modSettings['hotTopicPosts'], + 'is_very_hot' => $row['num_replies'] >= $modSettings['hotTopicVeryPosts'], + 'is_posted_in' => false, + 'icon' => $row['first_icon'], + 'icon_url' => $settings[$context['icon_sources'][$row['first_icon']]] . '/post/' . $row['first_icon'] . '.gif', + 'subject' => $row['first_subject'], + 'new' => $row['new_from'] <= $row['id_msg_modified'], + 'new_from' => $row['new_from'], + 'newtime' => $row['new_from'], + 'new_href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['new_from'] . '#new', + 'pages' => $pages, + 'replies' => comma_format($row['num_replies']), + 'views' => comma_format($row['num_views']), + 'approved' => $row['approved'], + 'unapproved_posts' => $row['unapproved_posts'], + ); + + determineTopicClass($context['topics'][$row['id_topic']]); + } + $smcFunc['db_free_result']($result); + + // Fix the sequence of topics if they were retrieved in the wrong order. (for speed reasons...) + if ($fake_ascending) + $context['topics'] = array_reverse($context['topics'], true); + + if (!empty($modSettings['enableParticipation']) && !$user_info['is_guest'] && !empty($topic_ids)) + { + $result = $smcFunc['db_query']('', ' + SELECT id_topic + FROM {db_prefix}messages + WHERE id_topic IN ({array_int:topic_list}) + AND id_member = {int:current_member} + GROUP BY id_topic + LIMIT ' . count($topic_ids), + array( + 'current_member' => $user_info['id'], + 'topic_list' => $topic_ids, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $context['topics'][$row['id_topic']]['is_posted_in'] = true; + $context['topics'][$row['id_topic']]['class'] = 'my_' . $context['topics'][$row['id_topic']]['class']; + } + $smcFunc['db_free_result']($result); + } + } + + $context['jump_to'] = array( + 'label' => addslashes(un_htmlspecialchars($txt['jump_to'])), + 'board_name' => htmlspecialchars(strtr(strip_tags($board_info['name']), array('&' => '&'))), + 'child_level' => $board_info['child_level'], + ); + + // Is Quick Moderation active/needed? + if (!empty($options['display_quick_mod']) && !empty($context['topics'])) + { + $context['can_lock'] = allowedTo('lock_any'); + $context['can_sticky'] = allowedTo('make_sticky') && !empty($modSettings['enableStickyTopics']); + $context['can_move'] = allowedTo('move_any'); + $context['can_remove'] = allowedTo('remove_any'); + $context['can_merge'] = allowedTo('merge_any'); + // Ignore approving own topics as it's unlikely to come up... + $context['can_approve'] = $modSettings['postmod_active'] && allowedTo('approve_posts') && !empty($board_info['unapproved_topics']); + // Can we restore topics? + $context['can_restore'] = allowedTo('move_any') && !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board; + + // Set permissions for all the topics. + foreach ($context['topics'] as $t => $topic) + { + $started = $topic['first_post']['member']['id'] == $user_info['id']; + $context['topics'][$t]['quick_mod'] = array( + 'lock' => allowedTo('lock_any') || ($started && allowedTo('lock_own')), + 'sticky' => allowedTo('make_sticky') && !empty($modSettings['enableStickyTopics']), + 'move' => allowedTo('move_any') || ($started && allowedTo('move_own')), + 'modify' => allowedTo('modify_any') || ($started && allowedTo('modify_own')), + 'remove' => allowedTo('remove_any') || ($started && allowedTo('remove_own')), + 'approve' => $context['can_approve'] && $topic['unapproved_posts'] + ); + $context['can_lock'] |= ($started && allowedTo('lock_own')); + $context['can_move'] |= ($started && allowedTo('move_own')); + $context['can_remove'] |= ($started && allowedTo('remove_own')); + } + + // Find the boards/cateogories they can move their topic to. + if ($options['display_quick_mod'] == 1 && $context['can_move'] && !empty($context['topics'])) + { + require_once($sourcedir . '/Subs-MessageIndex.php'); + $boardListOptions = array( + 'excluded_boards' => array($board), + 'not_redirection' => true, + 'use_permissions' => true, + 'selected_board' => empty($_SESSION['move_to_topic']) ? null : $_SESSION['move_to_topic'], + ); + $context['move_to_boards'] = getBoardList($boardListOptions); + + // Make the boards safe for display. + foreach ($context['move_to_boards'] as $id_cat => $cat) + { + $context['move_to_boards'][$id_cat]['name'] = strip_tags($cat['name']); + foreach ($cat['boards'] as $id_board => $board) + $context['move_to_boards'][$id_cat]['boards'][$id_board]['name'] = strip_tags($board['name']); + } + + // With no other boards to see, it's useless to move. + if (empty($context['move_to_boards'])) + $context['can_move'] = false; + } + // Can we use quick moderation checkboxes? + if ($options['display_quick_mod'] == 1) + $context['can_quick_mod'] = $context['user']['is_logged'] || $context['can_approve'] || $context['can_remove'] || $context['can_lock'] || $context['can_sticky'] || $context['can_move'] || $context['can_merge'] || $context['can_restore']; + // Or the icons? + else + $context['can_quick_mod'] = $context['can_remove'] || $context['can_lock'] || $context['can_sticky'] || $context['can_move']; + } + + // If there are children, but no topics and no ability to post topics... + $context['no_topic_listing'] = !empty($context['boards']) && empty($context['topics']) && !$context['can_post_new']; +} + +// Allows for moderation from the message index. +function QuickModeration() +{ + global $sourcedir, $board, $user_info, $modSettings, $sourcedir, $smcFunc, $context; + + // Check the session = get or post. + checkSession('request'); + + // Lets go straight to the restore area. + if (isset($_REQUEST['qaction']) && $_REQUEST['qaction'] == 'restore' && !empty($_REQUEST['topics'])) + redirectexit('action=restoretopic;topics=' . implode(',', $_REQUEST['topics']) . ';' . $context['session_var'] . '=' . $context['session_id']); + + if (isset($_SESSION['topicseen_cache'])) + $_SESSION['topicseen_cache'] = array(); + + // This is going to be needed to send off the notifications and for updateLastMessages(). + require_once($sourcedir . '/Subs-Post.php'); + + // Remember the last board they moved things to. + if (isset($_REQUEST['move_to'])) + $_SESSION['move_to_topic'] = $_REQUEST['move_to']; + + // Only a few possible actions. + $possibleActions = array(); + + if (!empty($board)) + { + $boards_can = array( + 'make_sticky' => allowedTo('make_sticky') ? array($board) : array(), + 'move_any' => allowedTo('move_any') ? array($board) : array(), + 'move_own' => allowedTo('move_own') ? array($board) : array(), + 'remove_any' => allowedTo('remove_any') ? array($board) : array(), + 'remove_own' => allowedTo('remove_own') ? array($board) : array(), + 'lock_any' => allowedTo('lock_any') ? array($board) : array(), + 'lock_own' => allowedTo('lock_own') ? array($board) : array(), + 'merge_any' => allowedTo('merge_any') ? array($board) : array(), + 'approve_posts' => allowedTo('approve_posts') ? array($board) : array(), + ); + + $redirect_url = 'board=' . $board . '.' . $_REQUEST['start']; + } + else + { + // !!! Ugly. There's no getting around this, is there? + // !!! Maybe just do this on the actions people want to use? + $boards_can = array( + 'make_sticky' => boardsAllowedTo('make_sticky'), + 'move_any' => boardsAllowedTo('move_any'), + 'move_own' => boardsAllowedTo('move_own'), + 'remove_any' => boardsAllowedTo('remove_any'), + 'remove_own' => boardsAllowedTo('remove_own'), + 'lock_any' => boardsAllowedTo('lock_any'), + 'lock_own' => boardsAllowedTo('lock_own'), + 'merge_any' => boardsAllowedTo('merge_any'), + 'approve_posts' => boardsAllowedTo('approve_posts'), + ); + + $redirect_url = isset($_POST['redirect_url']) ? $_POST['redirect_url'] : (isset($_SESSION['old_url']) ? $_SESSION['old_url'] : ''); + } + + if (!$user_info['is_guest']) + $possibleActions[] = 'markread'; + if (!empty($boards_can['make_sticky']) && !empty($modSettings['enableStickyTopics'])) + $possibleActions[] = 'sticky'; + if (!empty($boards_can['move_any']) || !empty($boards_can['move_own'])) + $possibleActions[] = 'move'; + if (!empty($boards_can['remove_any']) || !empty($boards_can['remove_own'])) + $possibleActions[] = 'remove'; + if (!empty($boards_can['lock_any']) || !empty($boards_can['lock_own'])) + $possibleActions[] = 'lock'; + if (!empty($boards_can['merge_any'])) + $possibleActions[] = 'merge'; + if (!empty($boards_can['approve_posts'])) + $possibleActions[] = 'approve'; + + // Two methods: $_REQUEST['actions'] (id_topic => action), and $_REQUEST['topics'] and $_REQUEST['qaction']. + // (if action is 'move', $_REQUEST['move_to'] or $_REQUEST['move_tos'][$topic] is used.) + if (!empty($_REQUEST['topics'])) + { + // If the action isn't valid, just quit now. + if (empty($_REQUEST['qaction']) || !in_array($_REQUEST['qaction'], $possibleActions)) + redirectexit($redirect_url); + + // Merge requires all topics as one parameter and can be done at once. + if ($_REQUEST['qaction'] == 'merge') + { + // Merge requires at least two topics. + if (empty($_REQUEST['topics']) || count($_REQUEST['topics']) < 2) + redirectexit($redirect_url); + + require_once($sourcedir . '/SplitTopics.php'); + return MergeExecute($_REQUEST['topics']); + } + + // Just convert to the other method, to make it easier. + foreach ($_REQUEST['topics'] as $topic) + $_REQUEST['actions'][(int) $topic] = $_REQUEST['qaction']; + } + + // Weird... how'd you get here? + if (empty($_REQUEST['actions'])) + redirectexit($redirect_url); + + // Validate each action. + $temp = array(); + foreach ($_REQUEST['actions'] as $topic => $action) + { + if (in_array($action, $possibleActions)) + $temp[(int) $topic] = $action; + } + $_REQUEST['actions'] = $temp; + + if (!empty($_REQUEST['actions'])) + { + // Find all topics... + $request = $smcFunc['db_query']('', ' + SELECT id_topic, id_member_started, id_board, locked, approved, unapproved_posts + FROM {db_prefix}topics + WHERE id_topic IN ({array_int:action_topic_ids}) + LIMIT ' . count($_REQUEST['actions']), + array( + 'action_topic_ids' => array_keys($_REQUEST['actions']), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!empty($board)) + { + if ($row['id_board'] != $board || ($modSettings['postmod_active'] && !$row['approved'] && !allowedTo('approve_posts'))) + unset($_REQUEST['actions'][$row['id_topic']]); + } + else + { + // Don't allow them to act on unapproved posts they can't see... + if ($modSettings['postmod_active'] && !$row['approved'] && !in_array(0, $boards_can['approve_posts']) && !in_array($row['id_board'], $boards_can['approve_posts'])) + unset($_REQUEST['actions'][$row['id_topic']]); + // Goodness, this is fun. We need to validate the action. + elseif ($_REQUEST['actions'][$row['id_topic']] == 'sticky' && !in_array(0, $boards_can['make_sticky']) && !in_array($row['id_board'], $boards_can['make_sticky'])) + unset($_REQUEST['actions'][$row['id_topic']]); + elseif ($_REQUEST['actions'][$row['id_topic']] == 'move' && !in_array(0, $boards_can['move_any']) && !in_array($row['id_board'], $boards_can['move_any']) && ($row['id_member_started'] != $user_info['id'] || (!in_array(0, $boards_can['move_own']) && !in_array($row['id_board'], $boards_can['move_own'])))) + unset($_REQUEST['actions'][$row['id_topic']]); + elseif ($_REQUEST['actions'][$row['id_topic']] == 'remove' && !in_array(0, $boards_can['remove_any']) && !in_array($row['id_board'], $boards_can['remove_any']) && ($row['id_member_started'] != $user_info['id'] || (!in_array(0, $boards_can['remove_own']) && !in_array($row['id_board'], $boards_can['remove_own'])))) + unset($_REQUEST['actions'][$row['id_topic']]); + elseif ($_REQUEST['actions'][$row['id_topic']] == 'lock' && !in_array(0, $boards_can['lock_any']) && !in_array($row['id_board'], $boards_can['lock_any']) && ($row['id_member_started'] != $user_info['id'] || $locked == 1 || (!in_array(0, $boards_can['lock_own']) && !in_array($row['id_board'], $boards_can['lock_own'])))) + unset($_REQUEST['actions'][$row['id_topic']]); + // If the topic is approved then you need permission to approve the posts within. + elseif ($_REQUEST['actions'][$row['id_topic']] == 'approve' && (!$row['unapproved_posts'] || (!in_array(0, $boards_can['approve_posts']) && !in_array($row['id_board'], $boards_can['approve_posts'])))) + unset($_REQUEST['actions'][$row['id_topic']]); + } + } + $smcFunc['db_free_result']($request); + } + + $stickyCache = array(); + $moveCache = array(0 => array(), 1 => array()); + $removeCache = array(); + $lockCache = array(); + $markCache = array(); + $approveCache = array(); + + // Separate the actions. + foreach ($_REQUEST['actions'] as $topic => $action) + { + $topic = (int) $topic; + + if ($action == 'markread') + $markCache[] = $topic; + elseif ($action == 'sticky') + $stickyCache[] = $topic; + elseif ($action == 'move') + { + // $moveCache[0] is the topic, $moveCache[1] is the board to move to. + $moveCache[1][$topic] = (int) (isset($_REQUEST['move_tos'][$topic]) ? $_REQUEST['move_tos'][$topic] : $_REQUEST['move_to']); + + if (empty($moveCache[1][$topic])) + continue; + + $moveCache[0][] = $topic; + } + elseif ($action == 'remove') + $removeCache[] = $topic; + elseif ($action == 'lock') + $lockCache[] = $topic; + elseif ($action == 'approve') + $approveCache[] = $topic; + } + + if (empty($board)) + $affectedBoards = array(); + else + $affectedBoards = array($board => array(0, 0)); + + // Do all the stickies... + if (!empty($stickyCache)) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET is_sticky = CASE WHEN is_sticky = {int:is_sticky} THEN 0 ELSE 1 END + WHERE id_topic IN ({array_int:sticky_topic_ids})', + array( + 'sticky_topic_ids' => $stickyCache, + 'is_sticky' => 1, + ) + ); + + // Get the board IDs and Sticky status + $request = $smcFunc['db_query']('', ' + SELECT id_topic, id_board, is_sticky + FROM {db_prefix}topics + WHERE id_topic IN ({array_int:sticky_topic_ids}) + LIMIT ' . count($stickyCache), + array( + 'sticky_topic_ids' => $stickyCache, + ) + ); + $stickyCacheBoards = array(); + $stickyCacheStatus = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $stickyCacheBoards[$row['id_topic']] = $row['id_board']; + $stickyCacheStatus[$row['id_topic']] = empty($row['is_sticky']); + } + $smcFunc['db_free_result']($request); + } + + // Move sucka! (this is, by the by, probably the most complicated part....) + if (!empty($moveCache[0])) + { + // I know - I just KNOW you're trying to beat the system. Too bad for you... we CHECK :P. + $request = $smcFunc['db_query']('', ' + SELECT t.id_topic, t.id_board, b.count_posts + FROM {db_prefix}topics AS t + LEFT JOIN {db_prefix}boards AS b ON (t.id_board = b.id_board) + WHERE t.id_topic IN ({array_int:move_topic_ids})' . (!empty($board) && !allowedTo('move_any') ? ' + AND t.id_member_started = {int:current_member}' : '') . ' + LIMIT ' . count($moveCache[0]), + array( + 'current_member' => $user_info['id'], + 'move_topic_ids' => $moveCache[0], + ) + ); + $moveTos = array(); + $moveCache2 = array(); + $countPosts = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $to = $moveCache[1][$row['id_topic']]; + + if (empty($to)) + continue; + + // Does this topic's board count the posts or not? + $countPosts[$row['id_topic']] = empty($row['count_posts']); + + if (!isset($moveTos[$to])) + $moveTos[$to] = array(); + + $moveTos[$to][] = $row['id_topic']; + + // For reporting... + $moveCache2[] = array($row['id_topic'], $row['id_board'], $to); + } + $smcFunc['db_free_result']($request); + + $moveCache = $moveCache2; + + require_once($sourcedir . '/MoveTopic.php'); + + // Do the actual moves... + foreach ($moveTos as $to => $topics) + moveTopics($topics, $to); + + // Does the post counts need to be updated? + if (!empty($moveTos)) + { + $topicRecounts = array(); + $request = $smcFunc['db_query']('', ' + SELECT id_board, count_posts + FROM {db_prefix}boards + WHERE id_board IN ({array_int:move_boards})', + array( + 'move_boards' => array_keys($moveTos), + ) + ); + + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $cp = empty($row['count_posts']); + + // Go through all the topics that are being moved to this board. + foreach ($moveTos[$row['id_board']] as $topic) + { + // If both boards have the same value for post counting then no adjustment needs to be made. + if ($countPosts[$topic] != $cp) + { + // If the board being moved to does count the posts then the other one doesn't so add to their post count. + $topicRecounts[$topic] = $cp ? '+' : '-'; + } + } + } + + $smcFunc['db_free_result']($request); + + if (!empty($topicRecounts)) + { + $members = array(); + + // Get all the members who have posted in the moved topics. + $request = $smcFunc['db_query']('', ' + SELECT id_member, id_topic + FROM {db_prefix}messages + WHERE id_topic IN ({array_int:moved_topic_ids})', + array( + 'moved_topic_ids' => array_keys($topicRecounts), + ) + ); + + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!isset($members[$row['id_member']])) + $members[$row['id_member']] = 0; + + if ($topicRecounts[$row['id_topic']] === '+') + $members[$row['id_member']] += 1; + else + $members[$row['id_member']] -= 1; + } + + $smcFunc['db_free_result']($request); + + // And now update them member's post counts + foreach ($members as $id_member => $post_adj) + updateMemberData($id_member, array('posts' => 'posts + ' . $post_adj)); + + } + } + } + + // Now delete the topics... + if (!empty($removeCache)) + { + // They can only delete their own topics. (we wouldn't be here if they couldn't do that..) + $result = $smcFunc['db_query']('', ' + SELECT id_topic, id_board + FROM {db_prefix}topics + WHERE id_topic IN ({array_int:removed_topic_ids})' . (!empty($board) && !allowedTo('remove_any') ? ' + AND id_member_started = {int:current_member}' : '') . ' + LIMIT ' . count($removeCache), + array( + 'current_member' => $user_info['id'], + 'removed_topic_ids' => $removeCache, + ) + ); + + $removeCache = array(); + $removeCacheBoards = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $removeCache[] = $row['id_topic']; + $removeCacheBoards[$row['id_topic']] = $row['id_board']; + } + $smcFunc['db_free_result']($result); + + // Maybe *none* were their own topics. + if (!empty($removeCache)) + { + // Gotta send the notifications *first*! + foreach ($removeCache as $topic) + { + // Only log the topic ID if it's not in the recycle board. + logAction('remove', array((empty($modSettings['recycle_enable']) || $modSettings['recycle_board'] != $removeCacheBoards[$topic] ? 'topic' : 'old_topic_id') => $topic, 'board' => $removeCacheBoards[$topic])); + sendNotifications($topic, 'remove'); + } + + require_once($sourcedir . '/RemoveTopic.php'); + removeTopics($removeCache); + } + } + + // Approve the topics... + if (!empty($approveCache)) + { + // We need unapproved topic ids and their authors! + $request = $smcFunc['db_query']('', ' + SELECT id_topic, id_member_started + FROM {db_prefix}topics + WHERE id_topic IN ({array_int:approve_topic_ids}) + AND approved = {int:not_approved} + LIMIT ' . count($approveCache), + array( + 'approve_topic_ids' => $approveCache, + 'not_approved' => 0, + ) + ); + $approveCache = array(); + $approveCacheMembers = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $approveCache[] = $row['id_topic']; + $approveCacheMembers[$row['id_topic']] = $row['id_member_started']; + } + $smcFunc['db_free_result']($request); + + // Any topics to approve? + if (!empty($approveCache)) + { + // Handle the approval part... + approveTopics($approveCache); + + // Time for some logging! + foreach ($approveCache as $topic) + logAction('approve_topic', array('topic' => $topic, 'member' => $approveCacheMembers[$topic])); + } + } + + // And (almost) lastly, lock the topics... + if (!empty($lockCache)) + { + $lockStatus = array(); + + // Gotta make sure they CAN lock/unlock these topics... + if (!empty($board) && !allowedTo('lock_any')) + { + // Make sure they started the topic AND it isn't already locked by someone with higher priv's. + $result = $smcFunc['db_query']('', ' + SELECT id_topic, locked, id_board + FROM {db_prefix}topics + WHERE id_topic IN ({array_int:locked_topic_ids}) + AND id_member_started = {int:current_member} + AND locked IN (2, 0) + LIMIT ' . count($lockCache), + array( + 'current_member' => $user_info['id'], + 'locked_topic_ids' => $lockCache, + ) + ); + $lockCache = array(); + $lockCacheBoards = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $lockCache[] = $row['id_topic']; + $lockCacheBoards[$row['id_topic']] = $row['id_board']; + $lockStatus[$row['id_topic']] = empty($row['locked']); + } + $smcFunc['db_free_result']($result); + } + else + { + $result = $smcFunc['db_query']('', ' + SELECT id_topic, locked, id_board + FROM {db_prefix}topics + WHERE id_topic IN ({array_int:locked_topic_ids}) + LIMIT ' . count($lockCache), + array( + 'locked_topic_ids' => $lockCache, + ) + ); + $lockCacheBoards = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $lockStatus[$row['id_topic']] = empty($row['locked']); + $lockCacheBoards[$row['id_topic']] = $row['id_board']; + } + $smcFunc['db_free_result']($result); + } + + // It could just be that *none* were their own topics... + if (!empty($lockCache)) + { + // Alternate the locked value. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET locked = CASE WHEN locked = {int:is_locked} THEN ' . (allowedTo('lock_any') ? '1' : '2') . ' ELSE 0 END + WHERE id_topic IN ({array_int:locked_topic_ids})', + array( + 'locked_topic_ids' => $lockCache, + 'is_locked' => 0, + ) + ); + } + } + + if (!empty($markCache)) + { + $markArray = array(); + foreach ($markCache as $topic) + $markArray[] = array($modSettings['maxMsgID'], $user_info['id'], $topic); + + $smcFunc['db_insert']('replace', + '{db_prefix}log_topics', + array('id_msg' => 'int', 'id_member' => 'int', 'id_topic' => 'int'), + $markArray, + array('id_member', 'id_topic') + ); + } + + foreach ($moveCache as $topic) + { + // Didn't actually move anything! + if (!isset($topic[0])) + break; + + logAction('move', array('topic' => $topic[0], 'board_from' => $topic[1], 'board_to' => $topic[2])); + sendNotifications($topic[0], 'move'); + } + foreach ($lockCache as $topic) + { + logAction($lockStatus[$topic] ? 'lock' : 'unlock', array('topic' => $topic, 'board' => $lockCacheBoards[$topic])); + sendNotifications($topic, $lockStatus[$topic] ? 'lock' : 'unlock'); + } + foreach ($stickyCache as $topic) + { + logAction($stickyCacheStatus[$topic] ? 'unsticky' : 'sticky', array('topic' => $topic, 'board' => $stickyCacheBoards[$topic])); + sendNotifications($topic, 'sticky'); + } + + updateStats('topic'); + updateStats('message'); + updateSettings(array( + 'calendar_updated' => time(), + )); + + if (!empty($affectedBoards)) + updateLastMessages(array_keys($affectedBoards)); + + redirectexit($redirect_url); +} + +?> \ No newline at end of file diff --git a/Sources/ModerationCenter.php b/Sources/ModerationCenter.php new file mode 100644 index 0000000..ab92f73 --- /dev/null +++ b/Sources/ModerationCenter.php @@ -0,0 +1,2022 @@ + array( + 'title' => $txt['mc_main'], + 'areas' => array( + 'index' => array( + 'label' => $txt['moderation_center'], + 'function' => 'ModerationHome', + ), + 'modlog' => array( + 'label' => $txt['modlog_view'], + 'enabled' => !empty($modSettings['modlog_enabled']) && $context['can_moderate_boards'], + 'file' => 'Modlog.php', + 'function' => 'ViewModlog', + ), + 'notice' => array( + 'file' => 'ModerationCenter.php', + 'function' => 'ShowNotice', + 'select' => 'index' + ), + 'warnings' => array( + 'label' => $txt['mc_warnings'], + 'enabled' => in_array('w', $context['admin_features']) && $modSettings['warning_settings'][0] == 1 && $context['can_moderate_boards'], + 'function' => 'ViewWarnings', + 'subsections' => array( + 'log' => array($txt['mc_warning_log']), + 'templates' => array($txt['mc_warning_templates'], 'issue_warning'), + ), + ), + 'userwatch' => array( + 'label' => $txt['mc_watched_users_title'], + 'enabled' => in_array('w', $context['admin_features']) && $modSettings['warning_settings'][0] == 1 && $context['can_moderate_boards'], + 'function' => 'ViewWatchedUsers', + 'subsections' => array( + 'member' => array($txt['mc_watched_users_member']), + 'post' => array($txt['mc_watched_users_post']), + ), + ), + ), + ), + 'posts' => array( + 'title' => $txt['mc_posts'], + 'enabled' => $context['can_moderate_boards'] || $context['can_moderate_approvals'], + 'areas' => array( + 'postmod' => array( + 'label' => $txt['mc_unapproved_posts'], + 'enabled' => $context['can_moderate_approvals'], + 'file' => 'PostModeration.php', + 'function' => 'PostModerationMain', + 'custom_url' => $scripturl . '?action=moderate;area=postmod', + 'subsections' => array( + 'posts' => array($txt['mc_unapproved_replies']), + 'topics' => array($txt['mc_unapproved_topics']), + ), + ), + 'attachmod' => array( + 'label' => $txt['mc_unapproved_attachments'], + 'enabled' => $context['can_moderate_approvals'], + 'file' => 'PostModeration.php', + 'function' => 'PostModerationMain', + 'custom_url' => $scripturl . '?action=moderate;area=attachmod;sa=attachments', + ), + 'reports' => array( + 'label' => $txt['mc_reported_posts'], + 'enabled' => $context['can_moderate_boards'], + 'file' => 'ModerationCenter.php', + 'function' => 'ReportedPosts', + 'subsections' => array( + 'open' => array($txt['mc_reportedp_active']), + 'closed' => array($txt['mc_reportedp_closed']), + ), + ), + ), + ), + 'groups' => array( + 'title' => $txt['mc_groups'], + 'enabled' => $context['can_moderate_groups'], + 'areas' => array( + 'groups' => array( + 'label' => $txt['mc_group_requests'], + 'file' => 'Groups.php', + 'function' => 'Groups', + 'custom_url' => $scripturl . '?action=moderate;area=groups;sa=requests', + ), + 'viewgroups' => array( + 'label' => $txt['mc_view_groups'], + 'file' => 'Groups.php', + 'function' => 'Groups', + ), + ), + ), + 'prefs' => array( + 'title' => $txt['mc_prefs'], + 'areas' => array( + 'settings' => array( + 'label' => $txt['mc_settings'], + 'function' => 'ModerationSettings', + ), + ), + ), + ); + + // I don't know where we're going - I don't know where we've been... + $menuOptions = array( + 'action' => 'moderate', + 'disable_url_session_check' => true, + ); + $mod_include_data = createMenu($moderation_areas, $menuOptions); + unset($moderation_areas); + + // We got something - didn't we? DIDN'T WE! + if ($mod_include_data == false) + fatal_lang_error('no_access', false); + + // Retain the ID information in case required by a subaction. + $context['moderation_menu_id'] = $context['max_menu_id']; + $context['moderation_menu_name'] = 'menu_data_' . $context['moderation_menu_id']; + + // What a pleasant shortcut - even tho we're not *really* on the admin screen who cares... + $context['admin_area'] = $mod_include_data['current_area']; + + // Build the link tree. + $context['linktree'][] = array( + 'url' => $scripturl . '?action=moderate', + 'name' => $txt['moderation_center'], + ); + if (isset($mod_include_data['current_area']) && $mod_include_data['current_area'] != 'index') + $context['linktree'][] = array( + 'url' => $scripturl . '?action=moderate;area=' . $mod_include_data['current_area'], + 'name' => $mod_include_data['label'], + ); + if (!empty($mod_include_data['current_subsection']) && $mod_include_data['subsections'][$mod_include_data['current_subsection']][0] != $mod_include_data['label']) + $context['linktree'][] = array( + 'url' => $scripturl . '?action=moderate;area=' . $mod_include_data['current_area'] . ';sa=' . $mod_include_data['current_subsection'], + 'name' => $mod_include_data['subsections'][$mod_include_data['current_subsection']][0], + ); + + // Now - finally - the bit before the encore - the main performance of course! + if (!$dont_call) + { + if (isset($mod_include_data['file'])) + require_once($sourcedir . '/' . $mod_include_data['file']); + + $mod_include_data['function'](); + } +} + +// This function basically is the home page of the moderation center. +function ModerationHome() +{ + global $txt, $context, $scripturl, $modSettings, $user_info, $user_settings; + + loadTemplate('ModerationCenter'); + + $context['page_title'] = $txt['moderation_center']; + $context['sub_template'] = 'moderation_center'; + + // Load what blocks the user actually can see... + $valid_blocks = array( + 'n' => 'LatestNews', + 'p' => 'Notes', + ); + if ($context['can_moderate_groups']) + $valid_blocks['g'] = 'GroupRequests'; + if ($context['can_moderate_boards']) + { + $valid_blocks['r'] = 'ReportedPosts'; + $valid_blocks['w'] = 'WatchedUsers'; + } + + if (empty($user_settings['mod_prefs'])) + $user_blocks = 'n' . ($context['can_moderate_boards'] ? 'wr' : '') . ($context['can_moderate_groups'] ? 'g' : ''); + else + list (, $user_blocks) = explode('|', $user_settings['mod_prefs']); + + $user_blocks = str_split($user_blocks); + + $context['mod_blocks'] = array(); + foreach ($valid_blocks as $k => $block) + { + if (in_array($k, $user_blocks)) + { + $block = 'ModBlock' . $block; + if (function_exists($block)) + $context['mod_blocks'][] = $block(); + } + } +} + +// Just prepares the time stuff for the simple machines latest news. +function ModBlockLatestNews() +{ + global $context, $user_info; + + $context['time_format'] = urlencode($user_info['time_format']); + + // Return the template to use. + return 'latest_news'; +} + +// Show a list of the most active watched users. +function ModBlockWatchedUsers() +{ + global $context, $smcFunc, $scripturl, $modSettings; + + if (($watched_users = cache_get_data('recent_user_watches', 240)) === null) + { + $modSettings['warning_watch'] = empty($modSettings['warning_watch']) ? 1 : $modSettings['warning_watch']; + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name, last_login + FROM {db_prefix}members + WHERE warning >= {int:warning_watch} + ORDER BY last_login DESC + LIMIT 10', + array( + 'warning_watch' => $modSettings['warning_watch'], + ) + ); + $watched_users = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $watched_users[] = $row; + $smcFunc['db_free_result']($request); + + cache_put_data('recent_user_watches', $watched_users, 240); + } + + $context['watched_users'] = array(); + foreach ($watched_users as $user) + { + $context['watched_users'][] = array( + 'id' => $user['id_member'], + 'name' => $user['real_name'], + 'link' => '' . $user['real_name'] . '', + 'href' => $scripturl . '?action=profile;u=' . $user['id_member'], + 'last_login' => !empty($user['last_login']) ? timeformat($user['last_login']) : '', + ); + } + + return 'watched_users'; +} + +// Show an area for the moderator to type into. +function ModBlockNotes() +{ + global $context, $smcFunc, $scripturl, $txt, $user_info; + + // Are we saving a note? + if (isset($_POST['makenote']) && isset($_POST['new_note'])) + { + checkSession(); + + $_POST['new_note'] = $smcFunc['htmlspecialchars'](trim($_POST['new_note'])); + // Make sure they actually entered something. + if (!empty($_POST['new_note']) && $_POST['new_note'] !== $txt['mc_click_add_note']) + { + // Insert it into the database then! + $smcFunc['db_insert']('', + '{db_prefix}log_comments', + array( + 'id_member' => 'int', 'member_name' => 'string', 'comment_type' => 'string', 'recipient_name' => 'string', + 'body' => 'string', 'log_time' => 'int', + ), + array( + $user_info['id'], $user_info['name'], 'modnote', '', $_POST['new_note'], time(), + ), + array('id_comment') + ); + + // Clear the cache. + cache_put_data('moderator_notes', null, 240); + cache_put_data('moderator_notes_total', null, 240); + } + + // Redirect otherwise people can resubmit. + redirectexit('action=moderate'); + } + + // Bye... bye... + if (isset($_GET['notes']) && isset($_GET['delete']) && is_numeric($_GET['delete'])) + { + checkSession('get'); + + // Lets delete it. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_comments + WHERE id_comment = {int:note} + AND comment_type = {string:type}', + array( + 'note' => $_GET['delete'], + 'type' => 'modnote', + ) + ); + + // Clear the cache. + cache_put_data('moderator_notes', null, 240); + cache_put_data('moderator_notes_total', null, 240); + + redirectexit('action=moderate'); + } + + // How many notes in total? + if (($moderator_notes_total = cache_get_data('moderator_notes_total', 240)) === null) + { + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_comments AS lc + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lc.id_member) + WHERE lc.comment_type = {string:modnote}', + array( + 'modnote' => 'modnote', + ) + ); + list ($moderator_notes_total) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + cache_put_data('moderator_notes_total', $moderator_notes_total, 240); + } + + // Grab the current notes. We can only use the cache for the first page of notes. + $offset = isset($_GET['notes']) && isset($_GET['start']) ? $_GET['start'] : 0; + if ($offset != 0 || ($moderator_notes = cache_get_data('moderator_notes', 240)) === null) + { + $request = $smcFunc['db_query']('', ' + SELECT IFNULL(mem.id_member, 0) AS id_member, IFNULL(mem.real_name, lc.member_name) AS member_name, + lc.log_time, lc.body, lc.id_comment AS id_note + FROM {db_prefix}log_comments AS lc + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lc.id_member) + WHERE lc.comment_type = {string:modnote} + ORDER BY id_comment DESC + LIMIT {int:offset}, 10', + array( + 'modnote' => 'modnote', + 'offset' => $offset, + ) + ); + $moderator_notes = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $moderator_notes[] = $row; + $smcFunc['db_free_result']($request); + + if ($offset == 0) + cache_put_data('moderator_notes', $moderator_notes, 240); + } + + // Lets construct a page index. + $context['page_index'] = constructPageIndex($scripturl . '?action=moderate;area=index;notes', $_GET['start'], $moderator_notes_total, 10); + $context['start'] = $_GET['start']; + + $context['notes'] = array(); + foreach ($moderator_notes as $note) + { + $context['notes'][] = array( + 'author' => array( + 'id' => $note['id_member'], + 'link' => $note['id_member'] ? ('' . $note['member_name'] . '') : $note['member_name'], + ), + 'time' => timeformat($note['log_time']), + 'text' => parse_bbc($note['body']), + 'delete_href' => $scripturl . '?action=moderate;area=index;notes;delete=' . $note['id_note'] . ';' . $context['session_var'] . '=' . $context['session_id'], + ); + } + + return 'notes'; +} + +// Show a list of the most recent reported posts. +function ModBlockReportedPosts() +{ + global $context, $user_info, $scripturl, $smcFunc; + + // Got the info already? + $cachekey = md5(serialize($user_info['mod_cache']['bq'])); + $context['reported_posts'] = array(); + if ($user_info['mod_cache']['bq'] == '0=1') + return 'reported_posts_block'; + + if (($reported_posts = cache_get_data('reported_posts_' . $cachekey, 90)) === null) + { + // By George, that means we in a position to get the reports, jolly good. + $request = $smcFunc['db_query']('', ' + SELECT lr.id_report, lr.id_msg, lr.id_topic, lr.id_board, lr.id_member, lr.subject, + lr.num_reports, IFNULL(mem.real_name, lr.membername) AS author_name, + IFNULL(mem.id_member, 0) AS id_author + FROM {db_prefix}log_reported AS lr + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lr.id_member) + WHERE ' . ($user_info['mod_cache']['bq'] == '1=1' || $user_info['mod_cache']['bq'] == '0=1' ? $user_info['mod_cache']['bq'] : 'lr.' . $user_info['mod_cache']['bq']) . ' + AND lr.closed = {int:not_closed} + AND lr.ignore_all = {int:not_ignored} + ORDER BY lr.time_updated DESC + LIMIT 10', + array( + 'not_closed' => 0, + 'not_ignored' => 0, + ) + ); + $reported_posts = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $reported_posts[] = $row; + $smcFunc['db_free_result']($request); + + // Cache it. + cache_put_data('reported_posts_' . $cachekey, $reported_posts, 90); + } + + $context['reported_posts'] = array(); + foreach ($reported_posts as $i => $row) + { + $context['reported_posts'][] = array( + 'id' => $row['id_report'], + 'alternate' => $i % 2, + 'topic_href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'], + 'report_href' => $scripturl . '?action=moderate;area=reports;report=' . $row['id_report'], + 'author' => array( + 'id' => $row['id_author'], + 'name' => $row['author_name'], + 'link' => $row['id_author'] ? '' . $row['author_name'] . '' : $row['author_name'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_author'], + ), + 'comments' => array(), + 'subject' => $row['subject'], + 'num_reports' => $row['num_reports'], + ); + } + + return 'reported_posts_block'; +} + +// Show a list of all the group requests they can see. +function ModBlockGroupRequests() +{ + global $context, $user_info, $scripturl, $smcFunc; + + $context['group_requests'] = array(); + // Make sure they can even moderate someone! + if ($user_info['mod_cache']['gq'] == '0=1') + return 'group_requests_block'; + + // What requests are outstanding? + $request = $smcFunc['db_query']('', ' + SELECT lgr.id_request, lgr.id_member, lgr.id_group, lgr.time_applied, mem.member_name, mg.group_name, mem.real_name + FROM {db_prefix}log_group_requests AS lgr + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = lgr.id_member) + INNER JOIN {db_prefix}membergroups AS mg ON (mg.id_group = lgr.id_group) + WHERE ' . ($user_info['mod_cache']['gq'] == '1=1' || $user_info['mod_cache']['gq'] == '0=1' ? $user_info['mod_cache']['gq'] : 'lgr.' . $user_info['mod_cache']['gq']) . ' + ORDER BY lgr.id_request DESC + LIMIT 10', + array( + ) + ); + for ($i = 0; $row = $smcFunc['db_fetch_assoc']($request); $i ++) + { + $context['group_requests'][] = array( + 'id' => $row['id_request'], + 'alternate' => $i % 2, + 'request_href' => $scripturl . '?action=groups;sa=requests;gid=' . $row['id_group'], + 'member' => array( + 'id' => $row['id_member'], + 'name' => $row['real_name'], + 'link' => '' . $row['real_name'] . '', + 'href' => $scripturl . '?action=profile;u=' . $row['id_member'], + ), + 'group' => array( + 'id' => $row['id_group'], + 'name' => $row['group_name'], + ), + 'time_submitted' => timeformat($row['time_applied']), + ); + } + $smcFunc['db_free_result']($request); + + return 'group_requests_block'; +} + +//!!! This needs to be given its own file. +// Browse all the reported posts... +function ReportedPosts() +{ + global $txt, $context, $scripturl, $modSettings, $user_info, $smcFunc; + + loadTemplate('ModerationCenter'); + + // Put the open and closed options into tabs, because we can... + $context[$context['moderation_menu_name']]['tab_data'] = array( + 'title' => $txt['mc_reported_posts'], + 'help' => '', + 'description' => $txt['mc_reported_posts_desc'], + ); + + // This comes under the umbrella of moderating posts. + if ($user_info['mod_cache']['bq'] == '0=1') + isAllowedTo('moderate_forum'); + + // Are they wanting to view a particular report? + if (!empty($_REQUEST['report'])) + return ModReport(); + + // Set up the comforting bits... + $context['page_title'] = $txt['mc_reported_posts']; + $context['sub_template'] = 'reported_posts'; + + // Are we viewing open or closed reports? + $context['view_closed'] = isset($_GET['sa']) && $_GET['sa'] == 'closed' ? 1 : 0; + + // Are we doing any work? + if ((isset($_GET['ignore']) || isset($_GET['close'])) && isset($_GET['rid'])) + { + checkSession('get'); + $_GET['rid'] = (int) $_GET['rid']; + + // Update the report... + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_reported + SET ' . (isset($_GET['ignore']) ? 'ignore_all = {int:ignore_all}' : 'closed = {int:closed}') . ' + WHERE id_report = {int:id_report} + AND ' . $user_info['mod_cache']['bq'], + array( + 'ignore_all' => isset($_GET['ignore']) ? (int) $_GET['ignore'] : 0, + 'closed' => isset($_GET['close']) ? (int) $_GET['close'] : 0, + 'id_report' => $_GET['rid'], + ) + ); + + // Time to update. + updateSettings(array('last_mod_report_action' => time())); + recountOpenReports(); + } + elseif (isset($_POST['close']) && isset($_POST['close_selected'])) + { + checkSession('post'); + + // All the ones to update... + $toClose = array(); + foreach ($_POST['close'] as $rid) + $toClose[] = (int) $rid; + + if (!empty($toClose)) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_reported + SET closed = {int:is_closed} + WHERE id_report IN ({array_int:report_list}) + AND ' . $user_info['mod_cache']['bq'], + array( + 'report_list' => $toClose, + 'is_closed' => 1, + ) + ); + + // Time to update. + updateSettings(array('last_mod_report_action' => time())); + recountOpenReports(); + } + } + + // How many entries are we viewing? + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_reported AS lr + WHERE lr.closed = {int:view_closed} + AND ' . ($user_info['mod_cache']['bq'] == '1=1' || $user_info['mod_cache']['bq'] == '0=1' ? $user_info['mod_cache']['bq'] : 'lr.' . $user_info['mod_cache']['bq']), + array( + 'view_closed' => $context['view_closed'], + ) + ); + list ($context['total_reports']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // So, that means we can page index, yes? + $context['page_index'] = constructPageIndex($scripturl . '?action=moderate;area=reports' . ($context['view_closed'] ? ';sa=closed' : ''), $_GET['start'], $context['total_reports'], 10); + $context['start'] = $_GET['start']; + + // By George, that means we in a position to get the reports, golly good. + $request = $smcFunc['db_query']('', ' + SELECT lr.id_report, lr.id_msg, lr.id_topic, lr.id_board, lr.id_member, lr.subject, lr.body, + lr.time_started, lr.time_updated, lr.num_reports, lr.closed, lr.ignore_all, + IFNULL(mem.real_name, lr.membername) AS author_name, IFNULL(mem.id_member, 0) AS id_author + FROM {db_prefix}log_reported AS lr + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lr.id_member) + WHERE lr.closed = {int:view_closed} + AND ' . ($user_info['mod_cache']['bq'] == '1=1' || $user_info['mod_cache']['bq'] == '0=1' ? $user_info['mod_cache']['bq'] : 'lr.' . $user_info['mod_cache']['bq']) . ' + ORDER BY lr.time_updated DESC + LIMIT ' . $context['start'] . ', 10', + array( + 'view_closed' => $context['view_closed'], + ) + ); + $context['reports'] = array(); + $report_ids = array(); + for ($i = 0; $row = $smcFunc['db_fetch_assoc']($request); $i++) + { + $report_ids[] = $row['id_report']; + $context['reports'][$row['id_report']] = array( + 'id' => $row['id_report'], + 'alternate' => $i % 2, + 'topic_href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'], + 'report_href' => $scripturl . '?action=moderate;area=reports;report=' . $row['id_report'], + 'author' => array( + 'id' => $row['id_author'], + 'name' => $row['author_name'], + 'link' => $row['id_author'] ? '' . $row['author_name'] . '' : $row['author_name'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_author'], + ), + 'comments' => array(), + 'time_started' => timeformat($row['time_started']), + 'last_updated' => timeformat($row['time_updated']), + 'subject' => $row['subject'], + 'body' => parse_bbc($row['body']), + 'num_reports' => $row['num_reports'], + 'closed' => $row['closed'], + 'ignore' => $row['ignore_all'] + ); + } + $smcFunc['db_free_result']($request); + + // Now get all the people who reported it. + if (!empty($report_ids)) + { + $request = $smcFunc['db_query']('', ' + SELECT lrc.id_comment, lrc.id_report, lrc.time_sent, lrc.comment, + IFNULL(mem.id_member, 0) AS id_member, IFNULL(mem.real_name, lrc.membername) AS reporter + FROM {db_prefix}log_reported_comments AS lrc + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lrc.id_member) + WHERE lrc.id_report IN ({array_int:report_list})', + array( + 'report_list' => $report_ids, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $context['reports'][$row['id_report']]['comments'][] = array( + 'id' => $row['id_comment'], + 'message' => $row['comment'], + 'time' => timeformat($row['time_sent']), + 'member' => array( + 'id' => $row['id_member'], + 'name' => empty($row['reporter']) ? $txt['guest'] : $row['reporter'], + 'link' => $row['id_member'] ? '' . $row['reporter'] . '' : (empty($row['reporter']) ? $txt['guest'] : $row['reporter']), + 'href' => $row['id_member'] ? $scripturl . '?action=profile;u=' . $row['id_member'] : '', + ), + ); + } + $smcFunc['db_free_result']($request); + } +} + +// Act as an entrace for all group related activity. +//!!! As for most things in this file, this needs to be moved somewhere appropriate. +function ModerateGroups() +{ + global $txt, $context, $scripturl, $modSettings, $user_info; + + // You need to be allowed to moderate groups... + if ($user_info['mod_cache']['gq'] == '0=1') + isAllowedTo('manage_membergroups'); + + // Load the group templates. + loadTemplate('ModerationCenter'); + + // Setup the subactions... + $subactions = array( + 'requests' => 'GroupRequests', + 'view' => 'ViewGroups', + ); + + if (!isset($_GET['sa']) || !isset($subactions[$_GET['sa']])) + $_GET['sa'] = 'view'; + $context['sub_action'] = $_GET['sa']; + + // Call the relevant function. + $subactions[$context['sub_action']](); +} + +// How many open reports do we have? +function recountOpenReports() +{ + global $user_info, $context, $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_reported + WHERE ' . $user_info['mod_cache']['bq'] . ' + AND closed = {int:not_closed} + AND ignore_all = {int:not_ignored}', + array( + 'not_closed' => 0, + 'not_ignored' => 0, + ) + ); + list ($open_reports) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $_SESSION['rc'] = array( + 'id' => $user_info['id'], + 'time' => time(), + 'reports' => $open_reports, + ); + + $context['open_mod_reports'] = $open_reports; +} + +function ModReport() +{ + global $user_info, $context, $sourcedir, $scripturl, $txt, $smcFunc; + + // Have to at least give us something + if (empty($_REQUEST['report'])) + fatal_lang_error('mc_no_modreport_specified'); + + // Integers only please + $_REQUEST['report'] = (int) $_REQUEST['report']; + + // Get the report details, need this so we can limit access to a particular board + $request = $smcFunc['db_query']('', ' + SELECT lr.id_report, lr.id_msg, lr.id_topic, lr.id_board, lr.id_member, lr.subject, lr.body, + lr.time_started, lr.time_updated, lr.num_reports, lr.closed, lr.ignore_all, + IFNULL(mem.real_name, lr.membername) AS author_name, IFNULL(mem.id_member, 0) AS id_author + FROM {db_prefix}log_reported AS lr + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lr.id_member) + WHERE lr.id_report = {int:id_report} + AND ' . ($user_info['mod_cache']['bq'] == '1=1' || $user_info['mod_cache']['bq'] == '0=1' ? $user_info['mod_cache']['bq'] : 'lr.' . $user_info['mod_cache']['bq']) . ' + LIMIT 1', + array( + 'id_report' => $_REQUEST['report'], + ) + ); + + // So did we find anything? + if (!$smcFunc['db_num_rows']($request)) + fatal_lang_error('mc_no_modreport_found'); + + // Woohoo we found a report and they can see it! Bad news is we have more work to do + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // If they are adding a comment then... add a comment. + if (isset($_POST['add_comment']) && !empty($_POST['mod_comment'])) + { + checkSession(); + + $newComment = trim($smcFunc['htmlspecialchars']($_POST['mod_comment'])); + + // In it goes. + if (!empty($newComment)) + { + $smcFunc['db_insert']('', + '{db_prefix}log_comments', + array( + 'id_member' => 'int', 'member_name' => 'string', 'comment_type' => 'string', 'recipient_name' => 'string', + 'id_notice' => 'int', 'body' => 'string', 'log_time' => 'int', + ), + array( + $user_info['id'], $user_info['name'], 'reportc', '', + $_REQUEST['report'], $newComment, time(), + ), + array('id_comment') + ); + + // Redirect to prevent double submittion. + redirectexit($scripturl . '?action=moderate;area=reports;report=' . $_REQUEST['report']); + } + } + + $context['report'] = array( + 'id' => $row['id_report'], + 'topic_id' => $row['id_topic'], + 'board_id' => $row['id_board'], + 'message_id' => $row['id_msg'], + 'message_href' => $scripturl . '?msg=' . $row['id_msg'], + 'message_link' => '' . $row['subject'] . '', + 'report_href' => $scripturl . '?action=moderate;area=reports;report=' . $row['id_report'], + 'author' => array( + 'id' => $row['id_author'], + 'name' => $row['author_name'], + 'link' => $row['id_author'] ? '' . $row['author_name'] . '' : $row['author_name'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_author'], + ), + 'comments' => array(), + 'mod_comments' => array(), + 'time_started' => timeformat($row['time_started']), + 'last_updated' => timeformat($row['time_updated']), + 'subject' => $row['subject'], + 'body' => parse_bbc($row['body']), + 'num_reports' => $row['num_reports'], + 'closed' => $row['closed'], + 'ignore' => $row['ignore_all'] + ); + + // So what bad things do the reporters have to say about it? + $request = $smcFunc['db_query']('', ' + SELECT lrc.id_comment, lrc.id_report, lrc.time_sent, lrc.comment, lrc.member_ip, + IFNULL(mem.id_member, 0) AS id_member, IFNULL(mem.real_name, lrc.membername) AS reporter + FROM {db_prefix}log_reported_comments AS lrc + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lrc.id_member) + WHERE lrc.id_report = {int:id_report}', + array( + 'id_report' => $context['report']['id'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $context['report']['comments'][] = array( + 'id' => $row['id_comment'], + 'message' => $row['comment'], + 'time' => timeformat($row['time_sent']), + 'member' => array( + 'id' => $row['id_member'], + 'name' => empty($row['reporter']) ? $txt['guest'] : $row['reporter'], + 'link' => $row['id_member'] ? '' . $row['reporter'] . '' : (empty($row['reporter']) ? $txt['guest'] : $row['reporter']), + 'href' => $row['id_member'] ? $scripturl . '?action=profile;u=' . $row['id_member'] : '', + 'ip' => !empty($row['member_ip']) && allowedTo('moderate_forum') ? '' . $row['member_ip'] . '' : '', + ), + ); + } + $smcFunc['db_free_result']($request); + + // Hang about old chap, any comments from moderators on this one? + $request = $smcFunc['db_query']('', ' + SELECT lc.id_comment, lc.id_notice, lc.log_time, lc.body, + IFNULL(mem.id_member, 0) AS id_member, IFNULL(mem.real_name, lc.member_name) AS moderator + FROM {db_prefix}log_comments AS lc + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lc.id_member) + WHERE lc.id_notice = {int:id_report} + AND lc.comment_type = {string:reportc}', + array( + 'id_report' => $context['report']['id'], + 'reportc' => 'reportc', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $context['report']['mod_comments'][] = array( + 'id' => $row['id_comment'], + 'message' => parse_bbc($row['body']), + 'time' => timeformat($row['log_time']), + 'member' => array( + 'id' => $row['id_member'], + 'name' => $row['moderator'], + 'link' => $row['id_member'] ? '' . $row['moderator'] . '' : $row['moderator'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_member'], + ), + ); + } + $smcFunc['db_free_result']($request); + + // What have the other moderators done to this message? + require_once($sourcedir . '/Modlog.php'); + require_once($sourcedir . '/Subs-List.php'); + loadLanguage('Modlog'); + + // This is all the information from the moderation log. + $listOptions = array( + 'id' => 'moderation_actions_list', + 'title' => $txt['mc_modreport_modactions'], + 'items_per_page' => 15, + 'no_items_label' => $txt['modlog_no_entries_found'], + 'base_href' => $scripturl . '?action=moderate;area=reports;report=' . $context['report']['id'], + 'default_sort_col' => 'time', + 'get_items' => array( + 'function' => 'list_getModLogEntries', + 'params' => array( + 'lm.id_topic = {int:id_topic}', + array('id_topic' => $context['report']['topic_id']), + 1, + ), + ), + 'get_count' => array( + 'function' => 'list_getModLogEntryCount', + 'params' => array( + 'lm.id_topic = {int:id_topic}', + array('id_topic' => $context['report']['topic_id']), + 1, + ), + ), + // This assumes we are viewing by user. + 'columns' => array( + 'action' => array( + 'header' => array( + 'value' => $txt['modlog_action'], + ), + 'data' => array( + 'db' => 'action_text', + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'lm.action', + 'reverse' => 'lm.action DESC', + ), + ), + 'time' => array( + 'header' => array( + 'value' => $txt['modlog_date'], + ), + 'data' => array( + 'db' => 'time', + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'lm.log_time', + 'reverse' => 'lm.log_time DESC', + ), + ), + 'moderator' => array( + 'header' => array( + 'value' => $txt['modlog_member'], + ), + 'data' => array( + 'db' => 'moderator_link', + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'mem.real_name', + 'reverse' => 'mem.real_name DESC', + ), + ), + 'position' => array( + 'header' => array( + 'value' => $txt['modlog_position'], + ), + 'data' => array( + 'db' => 'position', + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'mg.group_name', + 'reverse' => 'mg.group_name DESC', + ), + ), + 'ip' => array( + 'header' => array( + 'value' => $txt['modlog_ip'], + ), + 'data' => array( + 'db' => 'ip', + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'lm.ip', + 'reverse' => 'lm.ip DESC', + ), + ), + ), + ); + + // Create the watched user list. + createList($listOptions); + + // Make sure to get the correct tab selected. + if ($context['report']['closed']) + $context[$context['moderation_menu_name']]['current_subsection'] = 'closed'; + + // Finally we are done :P + loadTemplate('ModerationCenter'); + $context['page_title'] = sprintf($txt['mc_viewmodreport'], $context['report']['subject'], $context['report']['author']['name']); + $context['sub_template'] = 'viewmodreport'; +} + +// Show a notice sent to a user. +function ShowNotice() +{ + global $smcFunc, $txt, $context; + + $context['page_title'] = $txt['show_notice']; + $context['sub_template'] = 'show_notice'; + $context['template_layers'] = array(); + + loadTemplate('ModerationCenter'); + + //!!! Assumes nothing needs permission more than accessing moderation center! + $id_notice = (int) $_GET['nid']; + $request = $smcFunc['db_query']('', ' + SELECT body, subject + FROM {db_prefix}log_member_notices + WHERE id_notice = {int:id_notice}', + array( + 'id_notice' => $id_notice, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_access', false); + list ($context['notice_body'], $context['notice_subject']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $context['notice_body'] = parse_bbc($context['notice_body'], false); +} + +// View watched users. +function ViewWatchedUsers() +{ + global $smcFunc, $modSettings, $context, $txt, $scripturl, $user_info, $sourcedir; + + // Some important context! + $context['page_title'] = $txt['mc_watched_users_title']; + $context['view_posts'] = isset($_GET['sa']) && $_GET['sa'] == 'post'; + $context['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0; + + loadTemplate('ModerationCenter'); + + // Get some key settings! + $modSettings['warning_watch'] = empty($modSettings['warning_watch']) ? 1 : $modSettings['warning_watch']; + + // Put some pretty tabs on cause we're gonna be doing hot stuff here... + $context[$context['moderation_menu_name']]['tab_data'] = array( + 'title' => $txt['mc_watched_users_title'], + 'help' => '', + 'description' => $txt['mc_watched_users_desc'], + ); + + // First off - are we deleting? + if (!empty($_REQUEST['delete'])) + { + checkSession(!is_array($_REQUEST['delete']) ? 'get' : 'post'); + + $toDelete = array(); + if (!is_array($_REQUEST['delete'])) + $toDelete[] = (int) $_REQUEST['delete']; + else + foreach ($_REQUEST['delete'] as $did) + $toDelete[] = (int) $did; + + if (!empty($toDelete)) + { + require_once($sourcedir . '/RemoveTopic.php'); + // If they don't have permission we'll let it error - either way no chance of a security slip here! + foreach ($toDelete as $did) + removeMessage($did); + } + } + + // Start preparing the list by grabbing relevant permissions. + if (!$context['view_posts']) + { + $approve_query = ''; + $delete_boards = array(); + } + else + { + // Still obey permissions! + $approve_boards = boardsAllowedTo('approve_posts'); + $delete_boards = boardsAllowedTo('delete_any'); + + if ($approve_boards == array(0)) + $approve_query = ''; + elseif (!empty($approve_boards)) + $approve_query = ' AND m.id_board IN (' . implode(',', $approve_boards) . ')'; + // Nada, zip, etc... + else + $approve_query = ' AND 0'; + } + + require_once($sourcedir . '/Subs-List.php'); + + // This is all the information required for a watched user listing. + $listOptions = array( + 'id' => 'watch_user_list', + 'title' => $txt['mc_watched_users_title'] . ' - ' . ($context['view_posts'] ? $txt['mc_watched_users_post'] : $txt['mc_watched_users_member']), + 'width' => '100%', + 'items_per_page' => $modSettings['defaultMaxMessages'], + 'no_items_label' => $context['view_posts'] ? $txt['mc_watched_users_no_posts'] : $txt['mc_watched_users_none'], + 'base_href' => $scripturl . '?action=moderate;area=userwatch;sa=' . ($context['view_posts'] ? 'post' : 'member'), + 'default_sort_col' => $context['view_posts'] ? '' : 'member', + 'get_items' => array( + 'function' => $context['view_posts'] ? 'list_getWatchedUserPosts' : 'list_getWatchedUsers', + 'params' => array( + $approve_query, + $delete_boards, + ), + ), + 'get_count' => array( + 'function' => $context['view_posts'] ? 'list_getWatchedUserPostsCount' : 'list_getWatchedUserCount', + 'params' => array( + $approve_query, + ), + ), + // This assumes we are viewing by user. + 'columns' => array( + 'member' => array( + 'header' => array( + 'value' => $txt['mc_watched_users_member'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%2$s', + 'params' => array( + 'id' => false, + 'name' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'real_name', + 'reverse' => 'real_name DESC', + ), + ), + 'warning' => array( + 'header' => array( + 'value' => $txt['mc_watched_users_warning'], + ), + 'data' => array( + 'function' => create_function('$member', ' + global $scripturl; + + return allowedTo(\'issue_warning\') ? \'\' . $member[\'warning\'] . \'%\' : $member[\'warning\'] . \'%\'; + '), + ), + 'sort' => array( + 'default' => 'warning', + 'reverse' => 'warning DESC', + ), + ), + 'posts' => array( + 'header' => array( + 'value' => $txt['posts'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%2$s', + 'params' => array( + 'id' => false, + 'posts' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'posts', + 'reverse' => 'posts DESC', + ), + ), + 'last_login' => array( + 'header' => array( + 'value' => $txt['mc_watched_users_last_login'], + ), + 'data' => array( + 'db' => 'last_login', + ), + 'sort' => array( + 'default' => 'last_login', + 'reverse' => 'last_login DESC', + ), + ), + 'last_post' => array( + 'header' => array( + 'value' => $txt['mc_watched_users_last_post'], + ), + 'data' => array( + 'function' => create_function('$member', ' + global $scripturl; + + if ($member[\'last_post_id\']) + return \'\' . $member[\'last_post\'] . \'\'; + else + return $member[\'last_post\']; + '), + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=moderate;area=userwatch;sa=post', + 'include_sort' => true, + 'include_start' => true, + 'hidden_fields' => array( + $context['session_var'] => $context['session_id'], + ), + ), + 'additional_rows' => array( + $context['view_posts'] ? + array( + 'position' => 'bottom_of_list', + 'value' => ' + ', + 'align' => 'right', + ) : array(), + ), + ); + + // If this is being viewed by posts we actually change the columns to call a template each time. + if ($context['view_posts']) + { + $listOptions['columns'] = array( + 'posts' => array( + 'data' => array( + 'function' => create_function('$post', ' + return template_user_watch_post_callback($post); + '), + ), + ), + ); + } + + // Create the watched user list. + createList($listOptions); + + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'watch_user_list'; +} + +function list_getWatchedUserCount($approve_query) +{ + global $smcFunc, $modSettings; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}members + WHERE warning >= {int:warning_watch}', + array( + 'warning_watch' => $modSettings['warning_watch'], + ) + ); + list ($totalMembers) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $totalMembers; +} + +function list_getWatchedUsers($start, $items_per_page, $sort, $approve_query, $dummy) +{ + global $smcFunc, $txt, $scripturl, $modSettings, $user_info, $context; + + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name, last_login, posts, warning + FROM {db_prefix}members + WHERE warning >= {int:warning_watch} + ORDER BY {raw:sort} + LIMIT ' . $start . ', ' . $items_per_page, + array( + 'warning_watch' => $modSettings['warning_watch'], + 'sort' => $sort, + ) + ); + $watched_users = array(); + $members = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $watched_users[$row['id_member']] = array( + 'id' => $row['id_member'], + 'name' => $row['real_name'], + 'last_login' => $row['last_login'] ? timeformat($row['last_login']) : $txt['never'], + 'last_post' => $txt['not_applicable'], + 'last_post_id' => 0, + 'warning' => $row['warning'], + 'posts' => $row['posts'], + ); + $members[] = $row['id_member']; + } + $smcFunc['db_free_result']($request); + + if (!empty($members)) + { + // First get the latest messages from these users. + $request = $smcFunc['db_query']('', ' + SELECT m.id_member, MAX(m.id_msg) AS last_post_id + FROM {db_prefix}messages AS m' . ($user_info['query_see_board'] == '1=1' ? '' : ' + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})') . ' + WHERE m.id_member IN ({array_int:member_list})' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : ' + AND m.approved = {int:is_approved}') . ' + GROUP BY m.id_member', + array( + 'member_list' => $members, + 'is_approved' => 1, + ) + ); + $latest_posts = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $latest_posts[$row['id_member']] = $row['last_post_id']; + + if (!empty($latest_posts)) + { + // Now get the time those messages were posted. + $request = $smcFunc['db_query']('', ' + SELECT id_member, poster_time + FROM {db_prefix}messages + WHERE id_msg IN ({array_int:message_list})', + array( + 'message_list' => $latest_posts, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $watched_users[$row['id_member']]['last_post'] = timeformat($row['poster_time']); + $watched_users[$row['id_member']]['last_post_id'] = $latest_posts[$row['id_member']]; + } + + $smcFunc['db_free_result']($request); + } + + $request = $smcFunc['db_query']('', ' + SELECT MAX(m.poster_time) AS last_post, MAX(m.id_msg) AS last_post_id, m.id_member + FROM {db_prefix}messages AS m' . ($user_info['query_see_board'] == '1=1' ? '' : ' + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})') . ' + WHERE m.id_member IN ({array_int:member_list})' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : ' + AND m.approved = {int:is_approved}') . ' + GROUP BY m.id_member', + array( + 'member_list' => $members, + 'is_approved' => 1, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $watched_users[$row['id_member']]['last_post'] = timeformat($row['last_post']); + $watched_users[$row['id_member']]['last_post_id'] = $row['last_post_id']; + } + $smcFunc['db_free_result']($request); + } + + return $watched_users; +} + +function list_getWatchedUserPostsCount($approve_query) +{ + global $smcFunc, $modSettings, $user_info; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + WHERE mem.warning >= {int:warning_watch} + AND {query_see_board} + ' . $approve_query, + array( + 'warning_watch' => $modSettings['warning_watch'], + ) + ); + list ($totalMemberPosts) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $totalMemberPosts; +} + +function list_getWatchedUserPosts($start, $items_per_page, $sort, $approve_query, $delete_boards) +{ + global $smcFunc, $txt, $scripturl, $modSettings, $user_info; + + $request = $smcFunc['db_query']('', ' + SELECT m.id_msg, m.id_topic, m.id_board, m.id_member, m.subject, m.body, m.poster_time, + m.approved, mem.real_name, m.smileys_enabled + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + WHERE mem.warning >= {int:warning_watch} + AND {query_see_board} + ' . $approve_query . ' + ORDER BY m.id_msg DESC + LIMIT ' . $start . ', ' . $items_per_page, + array( + 'warning_watch' => $modSettings['warning_watch'], + ) + ); + $member_posts = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $row['subject'] = censorText($row['subject']); + $row['body'] = censorText($row['body']); + + $member_posts[$row['id_msg']] = array( + 'id' => $row['id_msg'], + 'id_topic' => $row['id_topic'], + 'author_link' => '' . $row['real_name'] . '', + 'subject' => $row['subject'], + 'body' => parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']), + 'poster_time' => timeformat($row['poster_time']), + 'approved' => $row['approved'], + 'can_delete' => $delete_boards == array(0) || in_array($row['id_board'], $delete_boards), + ); + } + $smcFunc['db_free_result']($request); + + return $member_posts; +} + +// Entry point for viewing warning related stuff. +function ViewWarnings() +{ + global $context, $txt; + + $subActions = array( + 'log' => array('ViewWarningLog'), + 'templateedit' => array('ModifyWarningTemplate', 'issue_warning'), + 'templates' => array('ViewWarningTemplates', 'issue_warning'), + ); + + $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) && (empty($subActions[$_REQUEST['sa']][1]) || allowedTo($subActions[$_REQUEST['sa']]))? $_REQUEST['sa'] : 'log'; + + // Some of this stuff is overseas, so to speak. + loadTemplate('ModerationCenter'); + loadLanguage('Profile'); + + // Setup the admin tabs. + $context[$context['moderation_menu_name']]['tab_data'] = array( + 'title' => $txt['mc_warnings'], + 'description' => $txt['mc_warnings_description'], + ); + + // Call the right function. + $subActions[$_REQUEST['sa']][0](); +} + +// Simply put, look at the warning log! +function ViewWarningLog() +{ + global $smcFunc, $modSettings, $context, $txt, $scripturl, $sourcedir; + + // Setup context as always. + $context['page_title'] = $txt['mc_warning_log_title']; + + require_once($sourcedir . '/Subs-List.php'); + + // This is all the information required for a watched user listing. + $listOptions = array( + 'id' => 'warning_list', + 'title' => $txt['mc_warning_log_title'], + 'items_per_page' => $modSettings['defaultMaxMessages'], + 'no_items_label' => $txt['mc_warnings_none'], + 'base_href' => $scripturl . '?action=moderate;area=warnings;sa=log;' . $context['session_var'] . '=' . $context['session_id'], + 'default_sort_col' => 'time', + 'get_items' => array( + 'function' => 'list_getWarnings', + ), + 'get_count' => array( + 'function' => 'list_getWarningCount', + ), + // This assumes we are viewing by user. + 'columns' => array( + 'issuer' => array( + 'header' => array( + 'value' => $txt['profile_warning_previous_issued'], + ), + 'data' => array( + 'db' => 'issuer_link', + ), + 'sort' => array( + 'default' => 'member_name_col', + 'reverse' => 'member_name_col DESC', + ), + ), + 'recipient' => array( + 'header' => array( + 'value' => $txt['mc_warnings_recipient'], + ), + 'data' => array( + 'db' => 'recipient_link', + ), + 'sort' => array( + 'default' => 'recipient_name', + 'reverse' => 'recipient_name DESC', + ), + ), + 'time' => array( + 'header' => array( + 'value' => $txt['profile_warning_previous_time'], + ), + 'data' => array( + 'db' => 'time', + ), + 'sort' => array( + 'default' => 'lc.log_time DESC', + 'reverse' => 'lc.log_time', + ), + ), + 'reason' => array( + 'header' => array( + 'value' => $txt['profile_warning_previous_reason'], + ), + 'data' => array( + 'function' => create_function('$warning', ' + global $scripturl, $settings, $txt; + + $output = \' +
+ \' . $warning[\'reason\'] . \' +
\'; + + if (!empty($warning[\'id_notice\'])) + $output .= \' +
+ \' . $txt[\'profile_warning_previous_notice\'] . \' +
\'; + + return $output; + '), + ), + ), + 'points' => array( + 'header' => array( + 'value' => $txt['profile_warning_previous_level'], + ), + 'data' => array( + 'db' => 'counter', + ), + ), + ), + ); + + // Create the watched user list. + createList($listOptions); + + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'warning_list'; +} + +function list_getWarningCount() +{ + global $smcFunc, $modSettings; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_comments + WHERE comment_type = {string:warning}', + array( + 'warning' => 'warning', + ) + ); + list ($totalWarns) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $totalWarns; +} + +function list_getWarnings($start, $items_per_page, $sort) +{ + global $smcFunc, $txt, $scripturl, $modSettings, $user_info; + + $request = $smcFunc['db_query']('', ' + SELECT IFNULL(mem.id_member, 0) AS id_member, IFNULL(mem.real_name, lc.member_name) AS member_name_col, + IFNULL(mem2.id_member, 0) AS id_recipient, IFNULL(mem2.real_name, lc.recipient_name) AS recipient_name, + lc.log_time, lc.body, lc.id_notice, lc.counter + FROM {db_prefix}log_comments AS lc + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lc.id_member) + LEFT JOIN {db_prefix}members AS mem2 ON (mem2.id_member = lc.id_recipient) + WHERE lc.comment_type = {string:warning} + ORDER BY ' . $sort . ' + LIMIT ' . $start . ', ' . $items_per_page, + array( + 'warning' => 'warning', + ) + ); + $warnings = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $warnings[] = array( + 'issuer_link' => $row['id_member'] ? ('' . $row['member_name_col'] . '') : $row['member_name_col'], + 'recipient_link' => $row['id_recipient'] ? ('' . $row['recipient_name'] . '') : $row['recipient_name'], + 'time' => timeformat($row['log_time']), + 'reason' => $row['body'], + 'counter' => $row['counter'] > 0 ? '+' . $row['counter'] : $row['counter'], + 'id_notice' => $row['id_notice'], + ); + } + $smcFunc['db_free_result']($request); + + return $warnings; +} + +// Load all the warning templates. +function ViewWarningTemplates() +{ + global $smcFunc, $modSettings, $context, $txt, $scripturl, $sourcedir, $user_info; + + // Submitting a new one? + if (isset($_POST['add'])) + return ModifyWarningTemplate(); + elseif (isset($_POST['delete']) && !empty($_POST['deltpl'])) + { + checkSession('post'); + + // Log the actions. + $request = $smcFunc['db_query']('', ' + SELECT recipient_name + FROM {db_prefix}log_comments + WHERE id_comment IN ({array_int:delete_ids}) + AND comment_type = {string:warntpl} + AND (id_recipient = {int:generic} OR id_recipient = {int:current_member})', + array( + 'delete_ids' => $_POST['deltpl'], + 'warntpl' => 'warntpl', + 'generic' => 0, + 'current_member' => $user_info['id'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + logAction('delete_warn_template', array('template' => $row['recipient_name'])); + $smcFunc['db_free_result']($request); + + // Do the deletes. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_comments + WHERE id_comment IN ({array_int:delete_ids}) + AND comment_type = {string:warntpl} + AND (id_recipient = {int:generic} OR id_recipient = {int:current_member})', + array( + 'delete_ids' => $_POST['deltpl'], + 'warntpl' => 'warntpl', + 'generic' => 0, + 'current_member' => $user_info['id'], + ) + ); + } + + // Setup context as always. + $context['page_title'] = $txt['mc_warning_templates_title']; + + require_once($sourcedir . '/Subs-List.php'); + + // This is all the information required for a watched user listing. + $listOptions = array( + 'id' => 'warning_template_list', + 'title' => $txt['mc_warning_templates_title'], + 'items_per_page' => $modSettings['defaultMaxMessages'], + 'no_items_label' => $txt['mc_warning_templates_none'], + 'base_href' => $scripturl . '?action=moderate;area=warnings;sa=templates;' . $context['session_var'] . '=' . $context['session_id'], + 'default_sort_col' => 'title', + 'get_items' => array( + 'function' => 'list_getWarningTemplates', + ), + 'get_count' => array( + 'function' => 'list_getWarningTemplateCount', + ), + // This assumes we are viewing by user. + 'columns' => array( + 'title' => array( + 'header' => array( + 'value' => $txt['mc_warning_templates_name'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%2$s', + 'params' => array( + 'id_comment' => false, + 'title' => false, + 'body' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'template_title', + 'reverse' => 'template_title DESC', + ), + ), + 'creator' => array( + 'header' => array( + 'value' => $txt['mc_warning_templates_creator'], + ), + 'data' => array( + 'db' => 'creator', + ), + 'sort' => array( + 'default' => 'creator_name', + 'reverse' => 'creator_name DESC', + ), + ), + 'time' => array( + 'header' => array( + 'value' => $txt['mc_warning_templates_time'], + ), + 'data' => array( + 'db' => 'time', + ), + 'sort' => array( + 'default' => 'lc.log_time DESC', + 'reverse' => 'lc.log_time', + ), + ), + 'delete' => array( + 'header' => array( + 'style' => 'width: 4%;', + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $context, $txt, $scripturl; + + return \'\'; + '), + 'style' => 'text-align: center;', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=moderate;area=warnings;sa=templates', + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => ' + + ', + 'style' => 'text-align: right;', + ), + ), + ); + + // Create the watched user list. + createList($listOptions); + + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'warning_template_list'; +} + +function list_getWarningTemplateCount() +{ + global $smcFunc, $modSettings, $user_info; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_comments + WHERE comment_type = {string:warntpl} + AND (id_recipient = {string:generic} OR id_recipient = {int:current_member})', + array( + 'warntpl' => 'warntpl', + 'generic' => 0, + 'current_member' => $user_info['id'], + ) + ); + list ($totalWarns) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $totalWarns; +} + +function list_getWarningTemplates($start, $items_per_page, $sort) +{ + global $smcFunc, $txt, $scripturl, $modSettings, $user_info; + + $request = $smcFunc['db_query']('', ' + SELECT lc.id_comment, IFNULL(mem.id_member, 0) AS id_member, + IFNULL(mem.real_name, lc.member_name) AS creator_name, recipient_name AS template_title, + lc.log_time, lc.body + FROM {db_prefix}log_comments AS lc + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lc.id_member) + WHERE lc.comment_type = {string:warntpl} + AND (id_recipient = {string:generic} OR id_recipient = {int:current_member}) + ORDER BY ' . $sort . ' + LIMIT ' . $start . ', ' . $items_per_page, + array( + 'warntpl' => 'warntpl', + 'generic' => 0, + 'current_member' => $user_info['id'], + ) + ); + $templates = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $templates[] = array( + 'id_comment' => $row['id_comment'], + 'creator' => $row['id_member'] ? ('' . $row['creator_name'] . '') : $row['creator_name'], + 'time' => timeformat($row['log_time']), + 'title' => $row['template_title'], + 'body' => $smcFunc['htmlspecialchars']($row['body']), + ); + } + $smcFunc['db_free_result']($request); + + return $templates; +} + +// Edit a warning template. +function ModifyWarningTemplate() +{ + global $smcFunc, $context, $txt, $user_info, $sourcedir; + + $context['id_template'] = isset($_REQUEST['tid']) ? (int) $_REQUEST['tid'] : 0; + $context['is_edit'] = $context['id_template']; + + // Standard template things. + $context['page_title'] = $context['is_edit'] ? $txt['mc_warning_template_modify'] : $txt['mc_warning_template_add']; + $context['sub_template'] = 'warn_template'; + $context[$context['moderation_menu_name']]['current_subsection'] = 'templates'; + + // Defaults. + $context['template_data'] = array( + 'title' => '', + 'body' => $txt['mc_warning_template_body_default'], + 'personal' => false, + 'can_edit_personal' => true, + ); + + // If it's an edit load it. + if ($context['is_edit']) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member, id_recipient, recipient_name AS template_title, body + FROM {db_prefix}log_comments + WHERE id_comment = {int:id} + AND comment_type = {string:warntpl} + AND (id_recipient = {int:generic} OR id_recipient = {int:current_member})', + array( + 'id' => $context['id_template'], + 'warntpl' => 'warntpl', + 'generic' => 0, + 'current_member' => $user_info['id'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $context['template_data'] = array( + 'title' => $row['template_title'], + 'body' => $smcFunc['htmlspecialchars']($row['body']), + 'personal' => $row['id_recipient'], + 'can_edit_personal' => $row['id_member'] == $user_info['id'], + ); + } + $smcFunc['db_free_result']($request); + } + + // Wait, we are saving? + if (isset($_POST['save'])) + { + checkSession('post'); + + // To check the BBC is pretty good... + require_once($sourcedir . '/Subs-Post.php'); + + // Bit of cleaning! + $_POST['template_body'] = trim($_POST['template_body']); + $_POST['template_title'] = trim($_POST['template_title']); + + // Need something in both boxes. + if (empty($_POST['template_body']) || empty($_POST['template_title'])) + fatal_error($txt['mc_warning_template_error_empty']); + + // Safety first. + $_POST['template_title'] = $smcFunc['htmlspecialchars']($_POST['template_title']); + + // Clean up BBC. + preparsecode($_POST['template_body']); + // But put line breaks back! + $_POST['template_body'] = strtr($_POST['template_body'], array('
' => "\n")); + + // Is this personal? + $recipient_id = !empty($_POST['make_personal']) ? $user_info['id'] : 0; + + // If we are this far it's save time. + if ($context['is_edit']) + { + // Simple update... + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_comments + SET id_recipient = {int:personal}, recipient_name = {string:title}, body = {string:body} + WHERE id_comment = {int:id} + AND comment_type = {string:warntpl} + AND (id_recipient = {int:generic} OR id_recipient = {int:current_member})'. + ($recipient_id ? ' AND id_member = {int:current_member}' : ''), + array( + 'personal' => $recipient_id, + 'title' => $_POST['template_title'], + 'body' => $_POST['template_body'], + 'id' => $context['id_template'], + 'warntpl' => 'warntpl', + 'generic' => 0, + 'current_member' => $user_info['id'], + ) + ); + + // If it wasn't visible and now is they've effectively added it. + if ($context['template_data']['personal'] && !$recipient_id) + logAction('add_warn_template', array('template' => $_POST['template_title'])); + // Conversely if they made it personal it's a delete. + elseif (!$context['template_data']['personal'] && $recipient_id) + logAction('delete_warn_template', array('template' => $_POST['template_title'])); + // Otherwise just an edit. + else + logAction('modify_warn_template', array('template' => $_POST['template_title'])); + } + else + { + $smcFunc['db_insert']('', + '{db_prefix}log_comments', + array( + 'id_member' => 'int', 'member_name' => 'string', 'comment_type' => 'string', 'id_recipient' => 'int', + 'recipient_name' => 'string-255', 'body' => 'string-65535', 'log_time' => 'int', + ), + array( + $user_info['id'], $user_info['name'], 'warntpl', $recipient_id, + $_POST['template_title'], $_POST['template_body'], time(), + ), + array('id_comment') + ); + + logAction('add_warn_template', array('template' => $_POST['template_title'])); + } + + // Get out of town... + redirectexit('action=moderate;area=warnings;sa=templates'); + } +} + +// Change moderation preferences. +function ModerationSettings() +{ + global $context, $smcFunc, $txt, $sourcedir, $scripturl, $user_settings, $user_info; + + // Some useful context stuff. + loadTemplate('ModerationCenter'); + $context['page_title'] = $txt['mc_settings']; + $context['sub_template'] = 'moderation_settings'; + + // What blocks can this user see? + $context['homepage_blocks'] = array( + 'n' => $txt['mc_prefs_latest_news'], + 'p' => $txt['mc_notes'], + ); + if ($context['can_moderate_groups']) + $context['homepage_blocks']['g'] = $txt['mc_group_requests']; + if ($context['can_moderate_boards']) + { + $context['homepage_blocks']['r'] = $txt['mc_reported_posts']; + $context['homepage_blocks']['w'] = $txt['mc_watched_users']; + } + + // Does the user have any settings yet? + if (empty($user_settings['mod_prefs'])) + { + $mod_blocks = 'n' . ($context['can_moderate_boards'] ? 'wr' : '') . ($context['can_moderate_groups'] ? 'g' : ''); + $pref_binary = 5; + $show_reports = 1; + } + else + { + list ($show_reports, $mod_blocks, $pref_binary) = explode('|', $user_settings['mod_prefs']); + } + + // Are we saving? + if (isset($_POST['save'])) + { + checkSession('post'); + /* Current format of mod_prefs is: + x|ABCD|yyy + + WHERE: + x = Show report count on forum header. + ABCD = Block indexes to show on moderation main page. + yyy = Integer with the following bit status: + - yyy & 1 = Always notify on reports. + - yyy & 2 = Notify on reports for moderators only. + - yyy & 4 = Notify about posts awaiting approval. + */ + + // Do blocks first! + $mod_blocks = ''; + if (!empty($_POST['mod_homepage'])) + foreach ($_POST['mod_homepage'] as $k => $v) + { + // Make sure they can add this... + if (isset($context['homepage_blocks'][$k])) + $mod_blocks .= $k; + } + + // Now check other options! + $pref_binary = 0; + + if ($context['can_moderate_approvals'] && !empty($_POST['mod_notify_approval'])) + $pref_binary |= 4; + + if ($context['can_moderate_boards']) + { + if (!empty($_POST['mod_notify_report'])) + $pref_binary |= ($_POST['mod_notify_report'] == 2 ? 1 : 2); + + $show_reports = !empty($_POST['mod_show_reports']) ? 1 : 0; + } + + // Put it all together. + $mod_prefs = $show_reports . '|' . $mod_blocks . '|' . $pref_binary; + updateMemberData($user_info['id'], array('mod_prefs' => $mod_prefs)); + } + + // What blocks does the user currently have selected? + $context['mod_settings'] = array( + 'show_reports' => $show_reports, + 'notify_report' => $pref_binary & 2 ? 1 : ($pref_binary & 1 ? 2 : 0), + 'notify_approval' => $pref_binary & 4, + 'user_blocks' => str_split($mod_blocks), + ); +} + +?> \ No newline at end of file diff --git a/Sources/Modlog.php b/Sources/Modlog.php new file mode 100644 index 0000000..dc517cc --- /dev/null +++ b/Sources/Modlog.php @@ -0,0 +1,619 @@ + time() - $context['hoursdisable'] * 3600, + 'moderate_log' => $context['log_type'], + ) + ); + } + elseif (!empty($_POST['remove']) && isset($_POST['delete']) && $context['can_delete']) + { + checkSession(); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_actions + WHERE id_log = {int:moderate_log} + AND id_action IN ({array_string:delete_actions}) + AND log_time < {int:twenty_four_hours_wait}', + array( + 'twenty_four_hours_wait' => time() - $context['hoursdisable'] * 3600, + 'delete_actions' => array_unique($_POST['delete']), + 'moderate_log' => $context['log_type'], + ) + ); + } + + // Do the column stuff! + $sort_types = array( + 'action' =>'lm.action', + 'time' => 'lm.log_time', + 'member' => 'mem.real_name', + 'group' => 'mg.group_name', + 'ip' => 'lm.ip', + ); + + // Setup the direction stuff... + $context['order'] = isset($_REQUEST['sort']) && isset($sort_types[$_REQUEST['sort']]) ? $_REQUEST['sort'] : 'time'; + + // If we're coming from a search, get the variables. + if (!empty($_REQUEST['params']) && empty($_REQUEST['is_search'])) + { + $search_params = base64_decode(strtr($_REQUEST['params'], array(' ' => '+'))); + $search_params = @unserialize($search_params); + } + + // This array houses all the valid search types. + $searchTypes = array( + 'action' => array('sql' => 'lm.action', 'label' => $txt['modlog_action']), + 'member' => array('sql' => 'mem.real_name', 'label' => $txt['modlog_member']), + 'group' => array('sql' => 'mg.group_name', 'label' => $txt['modlog_position']), + 'ip' => array('sql' => 'lm.ip', 'label' => $txt['modlog_ip']) + ); + + if (!isset($search_params['string']) || (!empty($_REQUEST['search']) && $search_params['string'] != $_REQUEST['search'])) + $search_params_string = empty($_REQUEST['search']) ? '' : $_REQUEST['search']; + else + $search_params_string = $search_params['string']; + + if (isset($_REQUEST['search_type']) || empty($search_params['type']) || !isset($searchTypes[$search_params['type']])) + $search_params_type = isset($_REQUEST['search_type']) && isset($searchTypes[$_REQUEST['search_type']]) ? $_REQUEST['search_type'] : (isset($searchTypes[$context['order']]) ? $context['order'] : 'member'); + else + $search_params_type = $search_params['type']; + + $search_params_column = $searchTypes[$search_params_type]['sql']; + $search_params = array( + 'string' => $search_params_string, + 'type' => $search_params_type, + ); + + // Setup the search context. + $context['search_params'] = empty($search_params['string']) ? '' : base64_encode(serialize($search_params)); + $context['search'] = array( + 'string' => $search_params['string'], + 'type' => $search_params['type'], + 'label' => $searchTypes[$search_params_type]['label'], + ); + + // If they are searching by action, then we must do some manual intervention to search in their language! + if ($search_params['type'] == 'action' && !empty($search_params['string'])) + { + // For the moment they can only search for ONE action! + foreach ($txt as $key => $text) + { + if (substr($key, 0, 10) == 'modlog_ac_' && strpos($text, $search_params['string']) !== false) + { + $search_params['string'] = substr($key, 10); + break; + } + } + } + + require_once($sourcedir . '/Subs-List.php'); + + // This is all the information required for a watched user listing. + $listOptions = array( + 'id' => 'moderation_log_list', + 'title' => '' . $txt['help'] . ' ' . $txt['modlog_' . ($context['log_type'] == 3 ? 'admin' : 'moderation') . '_log'], + 'width' => '100%', + 'items_per_page' => $context['displaypage'], + 'no_items_label' => $txt['modlog_' . ($context['log_type'] == 3 ? 'admin_log_' : '') . 'no_entries_found'], + 'base_href' => $scripturl . $context['url_start'] . (!empty($context['search_params']) ? ';params=' . $context['search_params'] : ''), + 'default_sort_col' => 'time', + 'get_items' => array( + 'function' => 'list_getModLogEntries', + 'params' => array( + (!empty($search_params['string']) ? ' INSTR({raw:sql_type}, {string:search_string})' : ''), + array('sql_type' => $search_params_column, 'search_string' => $search_params['string']), + $context['log_type'], + ), + ), + 'get_count' => array( + 'function' => 'list_getModLogEntryCount', + 'params' => array( + (!empty($search_params['string']) ? ' INSTR({raw:sql_type}, {string:search_string})' : ''), + array('sql_type' => $search_params_column, 'search_string' => $search_params['string']), + $context['log_type'], + ), + ), + // This assumes we are viewing by user. + 'columns' => array( + 'action' => array( + 'header' => array( + 'value' => $txt['modlog_action'], + 'class' => 'lefttext first_th', + ), + 'data' => array( + 'db' => 'action_text', + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'lm.action', + 'reverse' => 'lm.action DESC', + ), + ), + 'time' => array( + 'header' => array( + 'value' => $txt['modlog_date'], + 'class' => 'lefttext', + ), + 'data' => array( + 'db' => 'time', + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'lm.log_time DESC', + 'reverse' => 'lm.log_time', + ), + ), + 'moderator' => array( + 'header' => array( + 'value' => $txt['modlog_member'], + 'class' => 'lefttext', + ), + 'data' => array( + 'db' => 'moderator_link', + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'mem.real_name', + 'reverse' => 'mem.real_name DESC', + ), + ), + 'position' => array( + 'header' => array( + 'value' => $txt['modlog_position'], + 'class' => 'lefttext', + ), + 'data' => array( + 'db' => 'position', + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'mg.group_name', + 'reverse' => 'mg.group_name DESC', + ), + ), + 'ip' => array( + 'header' => array( + 'value' => $txt['modlog_ip'], + 'class' => 'lefttext', + ), + 'data' => array( + 'db' => 'ip', + 'class' => 'smalltext', + ), + 'sort' => array( + 'default' => 'lm.ip', + 'reverse' => 'lm.ip DESC', + ), + ), + 'delete' => array( + 'header' => array( + 'value' => '', + ), + 'data' => array( + 'function' => create_function('$entry', ' + return \'\'; + '), + 'style' => 'text-align: center;', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . $context['url_start'], + 'include_sort' => true, + 'include_start' => true, + 'hidden_fields' => array( + $context['session_var'] => $context['session_id'], + 'params' => $context['search_params'] + ), + ), + 'additional_rows' => array( + array( + 'position' => 'after_title', + 'value' => $txt['modlog_' . ($context['log_type'] == 3 ? 'admin' : 'moderation') . '_log_desc'], + 'class' => 'smalltext', + 'style' => 'padding: 2ex;', + ), + array( + 'position' => 'below_table_data', + 'value' => ' + ' . $txt['modlog_search'] . ' (' . $txt['modlog_by'] . ': ' . $context['search']['label'] . '): + + ' . ($context['can_delete'] ? ' | + + ' : ''), + ), + ), + ); + + // Create the watched user list. + createList($listOptions); + + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'moderation_log_list'; +} + +// Get the number of mod log entries. +function list_getModLogEntryCount($query_string = '', $query_params = array(), $log_type = 1) +{ + global $smcFunc, $user_info; + + $modlog_query = allowedTo('admin_forum') || $user_info['mod_cache']['bq'] == '1=1' ? '1=1' : ($user_info['mod_cache']['bq'] == '0=1' ? 'lm.id_board = 0 AND lm.id_topic = 0' : (strtr($user_info['mod_cache']['bq'], array('id_board' => 'b.id_board')) . ' AND ' . strtr($user_info['mod_cache']['bq'], array('id_board' => 't.id_board')))); + + $result = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_actions AS lm + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lm.id_member) + LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:reg_group_id} THEN mem.id_post_group ELSE mem.id_group END) + LEFT JOIN {db_prefix}boards AS b ON (b.id_board = lm.id_board) + LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = lm.id_topic) + WHERE id_log = {int:log_type} + AND {raw:modlog_query}' + . (!empty($query_string) ? ' + AND ' . $query_string : ''), + array_merge($query_params, array( + 'reg_group_id' => 0, + 'log_type' => $log_type, + 'modlog_query' => $modlog_query, + )) + ); + list ($entry_count) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + return $entry_count; +} + +function list_getModLogEntries($start, $items_per_page, $sort, $query_string = '', $query_params = array(), $log_type = 1) +{ + global $context, $scripturl, $txt, $smcFunc, $user_info; + + $modlog_query = allowedTo('admin_forum') || $user_info['mod_cache']['bq'] == '1=1' ? '1=1' : ($user_info['mod_cache']['bq'] == '0=1' ? 'lm.id_board = 0 AND lm.id_topic = 0' : (strtr($user_info['mod_cache']['bq'], array('id_board' => 'b.id_board')) . ' AND ' . strtr($user_info['mod_cache']['bq'], array('id_board' => 't.id_board')))); + + // Do a little bit of self protection. + if (!isset($context['hoursdisable'])) + $context['hoursdisable'] = 24; + + // Can they see the IP address? + $seeIP = allowedTo('moderate_forum'); + + // Here we have the query getting the log details. + $result = $smcFunc['db_query']('', ' + SELECT + lm.id_action, lm.id_member, lm.ip, lm.log_time, lm.action, lm.id_board, lm.id_topic, lm.id_msg, lm.extra, + mem.real_name, mg.group_name + FROM {db_prefix}log_actions AS lm + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lm.id_member) + LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:reg_group_id} THEN mem.id_post_group ELSE mem.id_group END) + LEFT JOIN {db_prefix}boards AS b ON (b.id_board = lm.id_board) + LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = lm.id_topic) + WHERE id_log = {int:log_type} + AND {raw:modlog_query}' + . (!empty($query_string) ? ' + AND ' . $query_string : '') . ' + ORDER BY ' . $sort . ' + LIMIT ' . $start . ', ' . $items_per_page, + array_merge($query_params, array( + 'reg_group_id' => 0, + 'log_type' => $log_type, + 'modlog_query' => $modlog_query, + )) + ); + + // Arrays for decoding objects into. + $topics = array(); + $boards = array(); + $members = array(); + $messages = array(); + $entries = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $row['extra'] = @unserialize($row['extra']); + + // Corrupt? + $row['extra'] = is_array($row['extra']) ? $row['extra'] : array(); + + // Add on some of the column stuff info + if (!empty($row['id_board'])) + { + if ($row['action'] == 'move') + $row['extra']['board_to'] = $row['id_board']; + else + $row['extra']['board'] = $row['id_board']; + } + + if (!empty($row['id_topic'])) + $row['extra']['topic'] = $row['id_topic']; + if (!empty($row['id_msg'])) + $row['extra']['message'] = $row['id_msg']; + + // Is this associated with a topic? + if (isset($row['extra']['topic'])) + $topics[(int) $row['extra']['topic']][] = $row['id_action']; + if (isset($row['extra']['new_topic'])) + $topics[(int) $row['extra']['new_topic']][] = $row['id_action']; + + // How about a member? + if (isset($row['extra']['member'])) + { + // Guests don't have names! + if (empty($row['extra']['member'])) + $row['extra']['member'] = $txt['modlog_parameter_guest']; + else + { + // Try to find it... + $members[(int) $row['extra']['member']][] = $row['id_action']; + } + } + + // Associated with a board? + if (isset($row['extra']['board_to'])) + $boards[(int) $row['extra']['board_to']][] = $row['id_action']; + if (isset($row['extra']['board_from'])) + $boards[(int) $row['extra']['board_from']][] = $row['id_action']; + if (isset($row['extra']['board'])) + $boards[(int) $row['extra']['board']][] = $row['id_action']; + + // A message? + if (isset($row['extra']['message'])) + $messages[(int) $row['extra']['message']][] = $row['id_action']; + + // IP Info? + if (isset($row['extra']['ip_range'])) + if ($seeIP) + $row['extra']['ip_range'] = '' . $row['extra']['ip_range'] . ''; + else + $row['extra']['ip_range'] = $txt['logged']; + + // Email? + if (isset($row['extra']['email'])) + $row['extra']['email'] = '' . $row['extra']['email'] . ''; + + // Bans are complex. + if ($row['action'] == 'ban') + { + $row['action_text'] = $txt['modlog_ac_ban']; + foreach (array('member', 'email', 'ip_range', 'hostname') as $type) + if (isset($row['extra'][$type])) + $row['action_text'] .= $txt['modlog_ac_ban_trigger_' . $type]; + } + + // The array to go to the template. Note here that action is set to a "default" value of the action doesn't match anything in the descriptions. Allows easy adding of logging events with basic details. + $entries[$row['id_action']] = array( + 'id' => $row['id_action'], + 'ip' => $seeIP ? $row['ip'] : $txt['logged'], + 'position' => empty($row['real_name']) && empty($row['group_name']) ? $txt['guest'] : $row['group_name'], + 'moderator_link' => $row['id_member'] ? '' . $row['real_name'] . '' : (empty($row['real_name']) ? ($txt['guest'] . (!empty($row['extra']['member_acted']) ? ' (' . $row['extra']['member_acted'] . ')' : '')) : $row['real_name']), + 'time' => timeformat($row['log_time']), + 'timestamp' => forum_time(true, $row['log_time']), + 'editable' => time() > $row['log_time'] + $context['hoursdisable'] * 3600, + 'extra' => $row['extra'], + 'action' => $row['action'], + 'action_text' => isset($row['action_text']) ? $row['action_text'] : '', + ); + } + $smcFunc['db_free_result']($result); + + if (!empty($boards)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_board, name + FROM {db_prefix}boards + WHERE id_board IN ({array_int:board_list}) + LIMIT ' . count(array_keys($boards)), + array( + 'board_list' => array_keys($boards), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + foreach ($boards[$row['id_board']] as $action) + { + // Make the board number into a link - dealing with moving too. + if (isset($entries[$action]['extra']['board_to']) && $entries[$action]['extra']['board_to'] == $row['id_board']) + $entries[$action]['extra']['board_to'] = '' . $row['name'] . ''; + elseif (isset($entries[$action]['extra']['board_from']) && $entries[$action]['extra']['board_from'] == $row['id_board']) + $entries[$action]['extra']['board_from'] = '' . $row['name'] . ''; + elseif (isset($entries[$action]['extra']['board']) && $entries[$action]['extra']['board'] == $row['id_board']) + $entries[$action]['extra']['board'] = '' . $row['name'] . ''; + } + } + $smcFunc['db_free_result']($request); + } + + if (!empty($topics)) + { + $request = $smcFunc['db_query']('', ' + SELECT ms.subject, t.id_topic + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg) + WHERE t.id_topic IN ({array_int:topic_list}) + LIMIT ' . count(array_keys($topics)), + array( + 'topic_list' => array_keys($topics), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + foreach ($topics[$row['id_topic']] as $action) + { + $this_action = &$entries[$action]; + + // This isn't used in the current theme. + $this_action['topic'] = array( + 'id' => $row['id_topic'], + 'subject' => $row['subject'], + 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.0', + 'link' => '' . $row['subject'] . '' + ); + + // Make the topic number into a link - dealing with splitting too. + if (isset($this_action['extra']['topic']) && $this_action['extra']['topic'] == $row['id_topic']) + $this_action['extra']['topic'] = '' . $row['subject'] . ''; + elseif (isset($this_action['extra']['new_topic']) && $this_action['extra']['new_topic'] == $row['id_topic']) + $this_action['extra']['new_topic'] = '' . $row['subject'] . ''; + } + } + $smcFunc['db_free_result']($request); + } + + if (!empty($messages)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_msg, subject + FROM {db_prefix}messages + WHERE id_msg IN ({array_int:message_list}) + LIMIT ' . count(array_keys($messages)), + array( + 'message_list' => array_keys($messages), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + foreach ($messages[$row['id_msg']] as $action) + { + $this_action = &$entries[$action]; + + // This isn't used in the current theme. + $this_action['message'] = array( + 'id' => $row['id_msg'], + 'subject' => $row['subject'], + 'href' => $scripturl . '?msg=' . $row['id_msg'], + 'link' => '' . $row['subject'] . '', + ); + + // Make the message number into a link. + if (isset($this_action['extra']['message']) && $this_action['extra']['message'] == $row['id_msg']) + $this_action['extra']['message'] = '' . $row['subject'] . ''; + } + } + $smcFunc['db_free_result']($request); + } + + if (!empty($members)) + { + $request = $smcFunc['db_query']('', ' + SELECT real_name, id_member + FROM {db_prefix}members + WHERE id_member IN ({array_int:member_list}) + LIMIT ' . count(array_keys($members)), + array( + 'member_list' => array_keys($members), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + foreach ($members[$row['id_member']] as $action) + { + // Not used currently. + $entries[$action]['member'] = array( + 'id' => $row['id_member'], + 'name' => $row['real_name'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_member'], + 'link' => '' . $row['real_name'] . '' + ); + // Make the member number into a name. + $entries[$action]['extra']['member'] = '' . $row['real_name'] . ''; + } + } + $smcFunc['db_free_result']($request); + } + + // Do some formatting of the action string. + $callback = pregReplaceCurry('list_getModLogEntriesCallback', 3); + foreach ($entries as $k => $entry) + { + // Make any message info links so its easier to go find that message. + if (isset($entry['extra']['message']) && (empty($entry['message']) || empty($entry['message']['id']))) + $entries[$k]['extra']['message'] = '' . $entry['extra']['message'] . ''; + + // Mark up any deleted members, topics and boards. + foreach (array('board', 'board_from', 'board_to', 'member', 'topic', 'new_topic') as $type) + if (!empty($entry['extra'][$type]) && is_numeric($entry['extra'][$type])) + $entries[$k]['extra'][$type] = sprintf($txt['modlog_id'], $entry['extra'][$type]); + + if (empty($entries[$k]['action_text'])) + $entries[$k]['action_text'] = isset($txt['modlog_ac_' . $entry['action']]) ? $txt['modlog_ac_' . $entry['action']] : $entry['action']; + $entries[$k]['action_text'] = preg_replace_callback('~\{([A-Za-z\d_]+)\}~i', $callback($entries, $k), $entries[$k]['action_text']); + + } + + // Back we go! + return $entries; +} + +// Mog Log Replacment Callback. +function list_getModLogEntriesCallback($entries, $key, $matches) +{ + return isset($entries[$key]['extra'][$matches[1]]) ? $entries[$key]['extra'][$matches[1]] : ''; +} + +?> \ No newline at end of file diff --git a/Sources/MoveTopic.php b/Sources/MoveTopic.php new file mode 100644 index 0000000..208d083 --- /dev/null +++ b/Sources/MoveTopic.php @@ -0,0 +1,699 @@ + $topic, + ) + ); + list ($id_member_started, $context['subject'], $context['is_approved']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Can they see it - if not approved? + if ($modSettings['postmod_active'] && !$context['is_approved']) + isAllowedTo('approve_posts'); + + // Permission check! + // !!! + if (!allowedTo('move_any')) + { + if ($id_member_started == $user_info['id']) + { + isAllowedTo('move_own'); + //$boards = array_merge(boardsAllowedTo('move_own'), boardsAllowedTo('move_any')); + } + else + isAllowedTo('move_any'); + } + //else + //$boards = boardsAllowedTo('move_any'); + + loadTemplate('MoveTopic'); + + // Get a list of boards this moderator can move to. + $request = $smcFunc['db_query']('order_by_board_order', ' + SELECT b.id_board, b.name, b.child_level, c.name AS cat_name, c.id_cat + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat) + WHERE {query_see_board} + AND b.redirect = {string:blank_redirect} + AND b.id_board != {int:current_board}', + array( + 'blank_redirect' => '', + 'current_board' => $board, + ) + ); + $context['boards'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!isset($context['categories'][$row['id_cat']])) + $context['categories'][$row['id_cat']] = array ( + 'name' => strip_tags($row['cat_name']), + 'boards' => array(), + ); + + $context['categories'][$row['id_cat']]['boards'][] = array( + 'id' => $row['id_board'], + 'name' => strip_tags($row['name']), + 'category' => strip_tags($row['cat_name']), + 'child_level' => $row['child_level'], + 'selected' => !empty($_SESSION['move_to_topic']) && $_SESSION['move_to_topic'] == $row['id_board'] && $row['id_board'] != $board, + ); + } + $smcFunc['db_free_result']($request); + + if (empty($context['categories'])) + fatal_lang_error('moveto_noboards', false); + + $context['page_title'] = $txt['move_topic']; + + $context['linktree'][] = array( + 'url' => $scripturl . '?topic=' . $topic . '.0', + 'name' => $context['subject'], + 'extra_before' => $settings['linktree_inline'] ? $txt['topic'] . ': ' : '', + ); + + $context['linktree'][] = array( + 'name' => $txt['move_topic'], + ); + + $context['back_to_topic'] = isset($_REQUEST['goback']); + + if ($user_info['language'] != $language) + { + loadLanguage('index', $language); + $temp = $txt['movetopic_default']; + loadLanguage('index'); + + $txt['movetopic_default'] = $temp; + } + + // Register this form and get a sequence number in $context. + checkSubmitOnce('register'); +} + +// Execute the move. +function MoveTopic2() +{ + global $txt, $board, $topic, $scripturl, $sourcedir, $modSettings, $context; + global $board, $language, $user_info, $smcFunc; + + if (empty($topic)) + fatal_lang_error('no_access', false); + + // You can't choose to have a redirection topic and use an empty reason. + if (isset($_POST['postRedirect']) && (!isset($_POST['reason']) || trim($_POST['reason']) == '')) + fatal_lang_error('movetopic_no_reason', false); + + // Make sure this form hasn't been submitted before. + checkSubmitOnce('check'); + + $request = $smcFunc['db_query']('', ' + SELECT id_member_started, id_first_msg, approved + FROM {db_prefix}topics + WHERE id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + ) + ); + list ($id_member_started, $id_first_msg, $context['is_approved']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Can they see it? + if (!$context['is_approved']) + isAllowedTo('approve_posts'); + + // Can they move topics on this board? + if (!allowedTo('move_any')) + { + if ($id_member_started == $user_info['id']) + { + isAllowedTo('move_own'); + $boards = array_merge(boardsAllowedTo('move_own'), boardsAllowedTo('move_any')); + } + else + isAllowedTo('move_any'); + } + else + $boards = boardsAllowedTo('move_any'); + + // If this topic isn't approved don't let them move it if they can't approve it! + if ($modSettings['postmod_active'] && !$context['is_approved'] && !allowedTo('approve_posts')) + { + // Only allow them to move it to other boards they can't approve it in. + $can_approve = boardsAllowedTo('approve_posts'); + $boards = array_intersect($boards, $can_approve); + } + + checkSession(); + require_once($sourcedir . '/Subs-Post.php'); + + // The destination board must be numeric. + $_POST['toboard'] = (int) $_POST['toboard']; + + // Make sure they can see the board they are trying to move to (and get whether posts count in the target board). + $request = $smcFunc['db_query']('', ' + SELECT b.count_posts, b.name, m.subject + FROM {db_prefix}boards AS b + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic}) + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + WHERE {query_see_board} + AND b.id_board = {int:to_board} + AND b.redirect = {string:blank_redirect} + LIMIT 1', + array( + 'current_topic' => $topic, + 'to_board' => $_POST['toboard'], + 'blank_redirect' => '', + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_board'); + list ($pcounter, $board_name, $subject) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Remember this for later. + $_SESSION['move_to_topic'] = $_POST['toboard']; + + // Rename the topic... + if (isset($_POST['reset_subject'], $_POST['custom_subject']) && $_POST['custom_subject'] != '') + { + $_POST['custom_subject'] = strtr($smcFunc['htmltrim']($smcFunc['htmlspecialchars']($_POST['custom_subject'])), array("\r" => '', "\n" => '', "\t" => '')); + // Keep checking the length. + if ($smcFunc['strlen']($_POST['custom_subject']) > 100) + $_POST['custom_subject'] = $smcFunc['substr']($_POST['custom_subject'], 0, 100); + + // If it's still valid move onwards and upwards. + if ($_POST['custom_subject'] != '') + { + if (isset($_POST['enforce_subject'])) + { + // Get a response prefix, but in the forum's default language. + if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix'))) + { + if ($language === $user_info['language']) + $context['response_prefix'] = $txt['response_prefix']; + else + { + loadLanguage('index', $language, false); + $context['response_prefix'] = $txt['response_prefix']; + loadLanguage('index'); + } + cache_put_data('response_prefix', $context['response_prefix'], 600); + } + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET subject = {string:subject} + WHERE id_topic = {int:current_topic}', + array( + 'current_topic' => $topic, + 'subject' => $context['response_prefix'] . $_POST['custom_subject'], + ) + ); + } + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET subject = {string:custom_subject} + WHERE id_msg = {int:id_first_msg}', + array( + 'id_first_msg' => $id_first_msg, + 'custom_subject' => $_POST['custom_subject'], + ) + ); + + // Fix the subject cache. + updateStats('subject', $topic, $_POST['custom_subject']); + } + } + + // Create a link to this in the old board. + //!!! Does this make sense if the topic was unapproved before? I'd just about say so. + if (isset($_POST['postRedirect'])) + { + // Should be in the boardwide language. + if ($user_info['language'] != $language) + loadLanguage('index', $language); + + $_POST['reason'] = $smcFunc['htmlspecialchars']($_POST['reason'], ENT_QUOTES); + preparsecode($_POST['reason']); + + // Add a URL onto the message. + $_POST['reason'] = strtr($_POST['reason'], array( + $txt['movetopic_auto_board'] => '[url=' . $scripturl . '?board=' . $_POST['toboard'] . '.0]' . $board_name . '[/url]', + $txt['movetopic_auto_topic'] => '[iurl]' . $scripturl . '?topic=' . $topic . '.0[/iurl]' + )); + + $msgOptions = array( + 'subject' => $txt['moved'] . ': ' . $subject, + 'body' => $_POST['reason'], + 'icon' => 'moved', + 'smileys_enabled' => 1, + ); + $topicOptions = array( + 'board' => $board, + 'lock_mode' => 1, + 'mark_as_read' => true, + ); + $posterOptions = array( + 'id' => $user_info['id'], + 'update_post_count' => empty($pcounter), + ); + createPost($msgOptions, $topicOptions, $posterOptions); + } + + $request = $smcFunc['db_query']('', ' + SELECT count_posts + FROM {db_prefix}boards + WHERE id_board = {int:current_board} + LIMIT 1', + array( + 'current_board' => $board, + ) + ); + list ($pcounter_from) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if ($pcounter_from != $pcounter) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}messages + WHERE id_topic = {int:current_topic} + AND approved = {int:is_approved}', + array( + 'current_topic' => $topic, + 'is_approved' => 1, + ) + ); + $posters = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!isset($posters[$row['id_member']])) + $posters[$row['id_member']] = 0; + + $posters[$row['id_member']]++; + } + $smcFunc['db_free_result']($request); + + foreach ($posters as $id_member => $posts) + { + // The board we're moving from counted posts, but not to. + if (empty($pcounter_from)) + updateMemberData($id_member, array('posts' => 'posts - ' . $posts)); + // The reverse: from didn't, to did. + else + updateMemberData($id_member, array('posts' => 'posts + ' . $posts)); + } + } + + // Do the move (includes statistics update needed for the redirect topic). + moveTopics($topic, $_POST['toboard']); + + // Log that they moved this topic. + if (!allowedTo('move_own') || $id_member_started != $user_info['id']) + logAction('move', array('topic' => $topic, 'board_from' => $board, 'board_to' => $_POST['toboard'])); + // Notify people that this topic has been moved? + sendNotifications($topic, 'move'); + + // Why not go back to the original board in case they want to keep moving? + if (!isset($_REQUEST['goback'])) + redirectexit('board=' . $board . '.0'); + else + redirectexit('topic=' . $topic . '.0'); +} + +// Moves one or more topics to a specific board. (doesn't check permissions.) +function moveTopics($topics, $toBoard) +{ + global $sourcedir, $user_info, $modSettings, $smcFunc; + + // Empty array? + if (empty($topics)) + return; + // Only a single topic. + elseif (is_numeric($topics)) + $topics = array($topics); + $num_topics = count($topics); + $fromBoards = array(); + + // Destination board empty or equal to 0? + if (empty($toBoard)) + return; + + // Are we moving to the recycle board? + $isRecycleDest = !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $toBoard; + + // Determine the source boards... + $request = $smcFunc['db_query']('', ' + SELECT id_board, approved, COUNT(*) AS num_topics, SUM(unapproved_posts) AS unapproved_posts, + SUM(num_replies) AS num_replies + FROM {db_prefix}topics + WHERE id_topic IN ({array_int:topics}) + GROUP BY id_board, approved', + array( + 'topics' => $topics, + ) + ); + // Num of rows = 0 -> no topics found. Num of rows > 1 -> topics are on multiple boards. + if ($smcFunc['db_num_rows']($request) == 0) + return; + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!isset($fromBoards[$row['id_board']]['num_posts'])) + { + $fromBoards[$row['id_board']] = array( + 'num_posts' => 0, + 'num_topics' => 0, + 'unapproved_posts' => 0, + 'unapproved_topics' => 0, + 'id_board' => $row['id_board'] + ); + } + // Posts = (num_replies + 1) for each approved topic. + $fromBoards[$row['id_board']]['num_posts'] += $row['num_replies'] + ($row['approved'] ? $row['num_topics'] : 0); + $fromBoards[$row['id_board']]['unapproved_posts'] += $row['unapproved_posts']; + + // Add the topics to the right type. + if ($row['approved']) + $fromBoards[$row['id_board']]['num_topics'] += $row['num_topics']; + else + $fromBoards[$row['id_board']]['unapproved_topics'] += $row['num_topics']; + } + $smcFunc['db_free_result']($request); + + // Move over the mark_read data. (because it may be read and now not by some!) + $SaveAServer = max(0, $modSettings['maxMsgID'] - 50000); + $request = $smcFunc['db_query']('', ' + SELECT lmr.id_member, lmr.id_msg, t.id_topic + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board + AND lmr.id_msg > t.id_first_msg AND lmr.id_msg > {int:protect_lmr_msg}) + LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = lmr.id_member) + WHERE t.id_topic IN ({array_int:topics}) + AND lmr.id_msg > IFNULL(lt.id_msg, 0)', + array( + 'protect_lmr_msg' => $SaveAServer, + 'topics' => $topics, + ) + ); + $log_topics = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $log_topics[] = array($row['id_topic'], $row['id_member'], $row['id_msg']); + + // Prevent queries from getting too big. Taking some steam off. + if (count($log_topics) > 500) + { + $smcFunc['db_insert']('replace', + '{db_prefix}log_topics', + array('id_topic' => 'int', 'id_member' => 'int', 'id_msg' => 'int'), + $log_topics, + array('id_topic', 'id_member') + ); + + $log_topics = array(); + } + } + $smcFunc['db_free_result']($request); + + // Now that we have all the topics that *should* be marked read, and by which members... + if (!empty($log_topics)) + { + // Insert that information into the database! + $smcFunc['db_insert']('replace', + '{db_prefix}log_topics', + array('id_topic' => 'int', 'id_member' => 'int', 'id_msg' => 'int'), + $log_topics, + array('id_topic', 'id_member') + ); + } + + // Update the number of posts on each board. + $totalTopics = 0; + $totalPosts = 0; + $totalUnapprovedTopics = 0; + $totalUnapprovedPosts = 0; + foreach ($fromBoards as $stats) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET + num_posts = CASE WHEN {int:num_posts} > num_posts THEN 0 ELSE num_posts - {int:num_posts} END, + num_topics = CASE WHEN {int:num_topics} > num_topics THEN 0 ELSE num_topics - {int:num_topics} END, + unapproved_posts = CASE WHEN {int:unapproved_posts} > unapproved_posts THEN 0 ELSE unapproved_posts - {int:unapproved_posts} END, + unapproved_topics = CASE WHEN {int:unapproved_topics} > unapproved_topics THEN 0 ELSE unapproved_topics - {int:unapproved_topics} END + WHERE id_board = {int:id_board}', + array( + 'id_board' => $stats['id_board'], + 'num_posts' => $stats['num_posts'], + 'num_topics' => $stats['num_topics'], + 'unapproved_posts' => $stats['unapproved_posts'], + 'unapproved_topics' => $stats['unapproved_topics'], + ) + ); + $totalTopics += $stats['num_topics']; + $totalPosts += $stats['num_posts']; + $totalUnapprovedTopics += $stats['unapproved_topics']; + $totalUnapprovedPosts += $stats['unapproved_posts']; + } + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET + num_topics = num_topics + {int:total_topics}, + num_posts = num_posts + {int:total_posts},' . ($isRecycleDest ? ' + unapproved_posts = {int:no_unapproved}, unapproved_topics = {int:no_unapproved}' : ' + unapproved_posts = unapproved_posts + {int:total_unapproved_posts}, + unapproved_topics = unapproved_topics + {int:total_unapproved_topics}') . ' + WHERE id_board = {int:id_board}', + array( + 'id_board' => $toBoard, + 'total_topics' => $totalTopics, + 'total_posts' => $totalPosts, + 'total_unapproved_topics' => $totalUnapprovedTopics, + 'total_unapproved_posts' => $totalUnapprovedPosts, + 'no_unapproved' => 0, + ) + ); + + // Move the topic. Done. :P + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET id_board = {int:id_board}' . ($isRecycleDest ? ', + unapproved_posts = {int:no_unapproved}, approved = {int:is_approved}' : '') . ' + WHERE id_topic IN ({array_int:topics})', + array( + 'id_board' => $toBoard, + 'topics' => $topics, + 'is_approved' => 1, + 'no_unapproved' => 0, + ) + ); + + // If this was going to the recycle bin, check what messages are being recycled, and remove them from the queue. + if ($isRecycleDest && ($totalUnapprovedTopics || $totalUnapprovedPosts)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_msg + FROM {db_prefix}messages + WHERE id_topic IN ({array_int:topics}) + and approved = {int:not_approved}', + array( + 'topics' => $topics, + 'not_approved' => 0, + ) + ); + $approval_msgs = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $approval_msgs[] = $row['id_msg']; + $smcFunc['db_free_result']($request); + + // Empty the approval queue for these, as we're going to approve them next. + if (!empty($approval_msgs)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}approval_queue + WHERE id_msg IN ({array_int:message_list}) + AND id_attach = {int:id_attach}', + array( + 'message_list' => $approval_msgs, + 'id_attach' => 0, + ) + ); + + // Get all the current max and mins. + $request = $smcFunc['db_query']('', ' + SELECT id_topic, id_first_msg, id_last_msg + FROM {db_prefix}topics + WHERE id_topic IN ({array_int:topics})', + array( + 'topics' => $topics, + ) + ); + $topicMaxMin = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $topicMaxMin[$row['id_topic']] = array( + 'min' => $row['id_first_msg'], + 'max' => $row['id_last_msg'], + ); + } + $smcFunc['db_free_result']($request); + + // Check the MAX and MIN are correct. + $request = $smcFunc['db_query']('', ' + SELECT id_topic, MIN(id_msg) AS first_msg, MAX(id_msg) AS last_msg + FROM {db_prefix}messages + WHERE id_topic IN ({array_int:topics}) + GROUP BY id_topic', + array( + 'topics' => $topics, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // If not, update. + if ($row['first_msg'] != $topicMaxMin[$row['id_topic']]['min'] || $row['last_msg'] != $topicMaxMin[$row['id_topic']]['max']) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET id_first_msg = {int:first_msg}, id_last_msg = {int:last_msg} + WHERE id_topic = {int:selected_topic}', + array( + 'first_msg' => $row['first_msg'], + 'last_msg' => $row['last_msg'], + 'selected_topic' => $row['id_topic'], + ) + ); + } + $smcFunc['db_free_result']($request); + } + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET id_board = {int:id_board}' . ($isRecycleDest ? ',approved = {int:is_approved}' : '') . ' + WHERE id_topic IN ({array_int:topics})', + array( + 'id_board' => $toBoard, + 'topics' => $topics, + 'is_approved' => 1, + ) + ); + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_reported + SET id_board = {int:id_board} + WHERE id_topic IN ({array_int:topics})', + array( + 'id_board' => $toBoard, + 'topics' => $topics, + ) + ); + $smcFunc['db_query']('', ' + UPDATE {db_prefix}calendar + SET id_board = {int:id_board} + WHERE id_topic IN ({array_int:topics})', + array( + 'id_board' => $toBoard, + 'topics' => $topics, + ) + ); + + // Mark target board as seen, if it was already marked as seen before. + $request = $smcFunc['db_query']('', ' + SELECT (IFNULL(lb.id_msg, 0) >= b.id_msg_updated) AS isSeen + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member}) + WHERE b.id_board = {int:id_board}', + array( + 'current_member' => $user_info['id'], + 'id_board' => $toBoard, + ) + ); + list ($isSeen) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (!empty($isSeen) && !$user_info['is_guest']) + { + $smcFunc['db_insert']('replace', + '{db_prefix}log_boards', + array('id_board' => 'int', 'id_member' => 'int', 'id_msg' => 'int'), + array($toBoard, $user_info['id'], $modSettings['maxMsgID']), + array('id_board', 'id_member') + ); + } + + // Update the cache? + if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3) + foreach ($topics as $topic_id) + cache_put_data('topic_board-' . $topic_id, null, 120); + + require_once($sourcedir . '/Subs-Post.php'); + + $updates = array_keys($fromBoards); + $updates[] = $toBoard; + + updateLastMessages(array_unique($updates)); + + // Update 'em pesky stats. + updateStats('topic'); + updateStats('message'); + updateSettings(array( + 'calendar_updated' => time(), + )); +} + +?> \ No newline at end of file diff --git a/Sources/News.php b/Sources/News.php new file mode 100644 index 0000000..0bbe504 --- /dev/null +++ b/Sources/News.php @@ -0,0 +1,972 @@ + 'm.id_msg <= b.id_last_msg', + ); + if (!empty($_REQUEST['c']) && empty($board)) + { + $_REQUEST['c'] = explode(',', $_REQUEST['c']); + foreach ($_REQUEST['c'] as $i => $c) + $_REQUEST['c'][$i] = (int) $c; + + if (count($_REQUEST['c']) == 1) + { + $request = $smcFunc['db_query']('', ' + SELECT name + FROM {db_prefix}categories + WHERE id_cat = {int:current_category}', + array( + 'current_category' => (int) $_REQUEST['c'][0], + ) + ); + list ($feed_title) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $feed_title = ' - ' . strip_tags($feed_title); + } + + $request = $smcFunc['db_query']('', ' + SELECT b.id_board, b.num_posts + FROM {db_prefix}boards AS b + WHERE b.id_cat IN ({array_int:current_category_list}) + AND {query_see_board}', + array( + 'current_category_list' => $_REQUEST['c'], + ) + ); + $total_cat_posts = 0; + $boards = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $boards[] = $row['id_board']; + $total_cat_posts += $row['num_posts']; + } + $smcFunc['db_free_result']($request); + + if (!empty($boards)) + $query_this_board = 'b.id_board IN (' . implode(', ', $boards) . ')'; + + // Try to limit the number of messages we look through. + if ($total_cat_posts > 100 && $total_cat_posts > $modSettings['totalMessages'] / 15) + $context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 400 - $_GET['limit'] * 5); + } + elseif (!empty($_REQUEST['boards'])) + { + $_REQUEST['boards'] = explode(',', $_REQUEST['boards']); + foreach ($_REQUEST['boards'] as $i => $b) + $_REQUEST['boards'][$i] = (int) $b; + + $request = $smcFunc['db_query']('', ' + SELECT b.id_board, b.num_posts, b.name + FROM {db_prefix}boards AS b + WHERE b.id_board IN ({array_int:board_list}) + AND {query_see_board} + LIMIT ' . count($_REQUEST['boards']), + array( + 'board_list' => $_REQUEST['boards'], + ) + ); + + // Either the board specified doesn't exist or you have no access. + $num_boards = $smcFunc['db_num_rows']($request); + if ($num_boards == 0) + fatal_lang_error('no_board'); + + $total_posts = 0; + $boards = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($num_boards == 1) + $feed_title = ' - ' . strip_tags($row['name']); + + $boards[] = $row['id_board']; + $total_posts += $row['num_posts']; + } + $smcFunc['db_free_result']($request); + + if (!empty($boards)) + $query_this_board = 'b.id_board IN (' . implode(', ', $boards) . ')'; + + // The more boards, the more we're going to look through... + if ($total_posts > 100 && $total_posts > $modSettings['totalMessages'] / 12) + $context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 500 - $_GET['limit'] * 5); + } + elseif (!empty($board)) + { + $request = $smcFunc['db_query']('', ' + SELECT num_posts + FROM {db_prefix}boards + WHERE id_board = {int:current_board} + LIMIT 1', + array( + 'current_board' => $board, + ) + ); + list ($total_posts) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $feed_title = ' - ' . strip_tags($board_info['name']); + + $query_this_board = 'b.id_board = ' . $board; + + // Try to look through just a few messages, if at all possible. + if ($total_posts > 80 && $total_posts > $modSettings['totalMessages'] / 10) + $context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 600 - $_GET['limit'] * 5); + } + else + { + $query_this_board = '{query_see_board}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' + AND b.id_board != ' . $modSettings['recycle_board'] : ''); + $context['optimize_msg']['lowest'] = 'm.id_msg >= ' . max(0, $modSettings['maxMsgID'] - 100 - $_GET['limit'] * 5); + } + + // Show in rss or proprietary format? + $xml_format = isset($_GET['type']) && in_array($_GET['type'], array('smf', 'rss', 'rss2', 'atom', 'rdf', 'webslice')) ? $_GET['type'] : 'smf'; + + // !!! Birthdays? + + // List all the different types of data they can pull. + $subActions = array( + 'recent' => array('getXmlRecent', 'recent-post'), + 'news' => array('getXmlNews', 'article'), + 'members' => array('getXmlMembers', 'member'), + 'profile' => array('getXmlProfile', null), + ); + if (empty($_GET['sa']) || !isset($subActions[$_GET['sa']])) + $_GET['sa'] = 'recent'; + + //!!! Temp - webslices doesn't do everything yet. + if ($xml_format == 'webslice' && $_GET['sa'] != 'recent') + $xml_format = 'rss2'; + // If this is webslices we kinda cheat - we allow a template that we call direct for the HTML, and we override the CDATA. + elseif ($xml_format == 'webslice') + { + $context['user'] += $user_info; + $cdata_override = true; + loadTemplate('Xml'); + } + + // We only want some information, not all of it. + $cachekey = array($xml_format, $_GET['action'], $_GET['limit'], $_GET['sa']); + foreach (array('board', 'boards', 'c') as $var) + if (isset($_REQUEST[$var])) + $cachekey[] = $_REQUEST[$var]; + $cachekey = md5(serialize($cachekey) . (!empty($query_this_board) ? $query_this_board : '')); + $cache_t = microtime(); + + // Get the associative array representing the xml. + if (!empty($modSettings['cache_enable']) && (!$user_info['is_guest'] || $modSettings['cache_enable'] >= 3)) + $xml = cache_get_data('xmlfeed-' . $xml_format . ':' . ($user_info['is_guest'] ? '' : $user_info['id'] . '-') . $cachekey, 240); + if (empty($xml)) + { + $xml = $subActions[$_GET['sa']][0]($xml_format); + + if (!empty($modSettings['cache_enable']) && (($user_info['is_guest'] && $modSettings['cache_enable'] >= 3) + || (!$user_info['is_guest'] && (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.2)))) + cache_put_data('xmlfeed-' . $xml_format . ':' . ($user_info['is_guest'] ? '' : $user_info['id'] . '-') . $cachekey, $xml, 240); + } + + $feed_title = htmlspecialchars(strip_tags($context['forum_name'])) . (isset($feed_title) ? $feed_title : ''); + + // This is an xml file.... + ob_end_clean(); + if (!empty($modSettings['enableCompressedOutput'])) + @ob_start('ob_gzhandler'); + else + ob_start(); + + if ($xml_format == 'smf' || isset($_REQUEST['debug'])) + header('Content-Type: text/xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set'])); + elseif ($xml_format == 'rss' || $xml_format == 'rss2' || $xml_format == 'webslice') + header('Content-Type: application/rss+xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set'])); + elseif ($xml_format == 'atom') + header('Content-Type: application/atom+xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set'])); + elseif ($xml_format == 'rdf') + header('Content-Type: ' . ($context['browser']['is_ie'] ? 'text/xml' : 'application/rdf+xml') . '; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set'])); + + // First, output the xml header. + echo ''; + + // Are we outputting an rss feed or one with more information? + if ($xml_format == 'rss' || $xml_format == 'rss2') + { + // Start with an RSS 2.0 header. + echo ' + + + ', $feed_title, ' + ', $scripturl, ' + '; + + // Output all of the associative array, start indenting with 2 tabs, and name everything "item". + dumpTags($xml, 2, 'item', $xml_format); + + // Output the footer of the xml. + echo ' + +'; + } + elseif ($xml_format == 'webslice') + { + $context['recent_posts_data'] = $xml; + + // This always has RSS 2 + echo ' + + + ', $feed_title, ' - ', $txt['recent_posts'], ' + ', $scripturl, '?action=recent + + + ', $feed_title, ' - ', $txt['recent_posts'], ' + ', $scripturl, '?action=recent + + + +'; + } + elseif ($xml_format == 'atom') + { + echo ' + + ', $feed_title, ' + + + ', gmstrftime('%Y-%m-%dT%H:%M:%SZ'), ' + + SMF + + ', strip_tags($context['forum_name']), ' + '; + + dumpTags($xml, 2, 'entry', $xml_format); + + echo ' +'; + } + elseif ($xml_format == 'rdf') + { + echo ' + + + ', $feed_title, ' + ', $scripturl, ' + + + '; + + foreach ($xml as $item) + echo ' + '; + + echo ' + + + +'; + + dumpTags($xml, 1, 'item', $xml_format); + + echo ' +'; + } + // Otherwise, we're using our proprietary formats - they give more data, though. + else + { + echo ' +'; + + // Dump out that associative array. Indent properly.... and use the right names for the base elements. + dumpTags($xml, 1, $subActions[$_GET['sa']][1], $xml_format); + + echo ' +'; +} + + obExit(false); +} + +function fix_possible_url($val) +{ + global $modSettings, $context, $scripturl; + + if (substr($val, 0, strlen($scripturl)) != $scripturl) + return $val; + + call_integration_hook('integrate_fix_url', array(&$val)); + + if (empty($modSettings['queryless_urls']) || ($context['server']['is_cgi'] && @ini_get('cgi.fix_pathinfo') == 0 && @get_cfg_var('cgi.fix_pathinfo') == 0) || (!$context['server']['is_apache'] && !$context['server']['is_lighttpd'])) + return $val; + + $val = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+)(#[^"]*)?$~', create_function('$m', 'global $scripturl; return $scripturl . \'/\' . strtr("$m[1]", \'&;=\', \'//,\') . \'.html\' . (isset($m[2]) ? $m[2] : "");'), $val); + return $val; +} + +function cdata_parse($data, $ns = '') +{ + global $smcFunc, $cdata_override; + + // Are we not doing it? + if (!empty($cdata_override)) + return $data; + + $cdata = ' $dummy) + { + if ($dummy === false) + unset($positions[$k]); + } + + $old = $pos; + $pos = empty($positions) ? $n : min($positions); + + if ($pos - $old > 0) + $cdata .= $smcFunc['substr']($data, $old, $pos - $old); + if ($pos >= $n) + break; + + if ($smcFunc['substr']($data, $pos, 1) == '<') + { + $pos2 = $smcFunc['strpos']($data, '>', $pos); + if ($pos2 === false) + $pos2 = $n; + if ($smcFunc['substr']($data, $pos + 1, 1) == '/') + $cdata .= ']]><' . $ns . ':' . $smcFunc['substr']($data, $pos + 1, $pos2 - $pos) . ']' . $smcFunc['substr']($data, $pos, $pos2 - $pos + 1) . '' . $smcFunc['substr']($data, $pos, $pos2 - $pos + 1) . ''; + + return strtr($cdata, array('' => '')); +} + +function dumpTags($data, $i, $tag = null, $xml_format = '') +{ + global $modSettings, $context, $scripturl; + + // For every array in the data... + foreach ($data as $key => $val) + { + // Skip it, it's been set to null. + if ($val === null) + continue; + + // If a tag was passed, use it instead of the key. + $key = isset($tag) ? $tag : $key; + + // First let's indent! + echo "\n", str_repeat("\t", $i); + + // Grr, I hate kludges... almost worth doing it properly, here, but not quite. + if ($xml_format == 'atom' && $key == 'link') + { + echo ''; + continue; + } + + // If it's empty/0/nothing simply output an empty tag. + if ($val == '') + echo '<', $key, ' />'; + else + { + // Beginning tag. + if ($xml_format == 'rdf' && $key == 'item' && isset($val['link'])) + { + echo '<', $key, ' rdf:about="', fix_possible_url($val['link']), '">'; + echo "\n", str_repeat("\t", $i + 1); + echo 'text/html'; + } + elseif ($xml_format == 'atom' && $key == 'summary') + echo '<', $key, ' type="html">'; + else + echo '<', $key, '>'; + + if (is_array($val)) + { + // An array. Dump it, and then indent the tag. + dumpTags($val, $i + 1, null, $xml_format); + echo "\n", str_repeat("\t", $i), ''; + } + // A string with returns in it.... show this as a multiline element. + elseif (strpos($val, "\n") !== false || strpos($val, '
') !== false) + echo "\n", fix_possible_url($val), "\n", str_repeat("\t", $i), ''; + // A simple string. + else + echo fix_possible_url($val), ''; + } + } +} + +function getXmlMembers($xml_format) +{ + global $scripturl, $smcFunc; + + if (!allowedTo('view_mlist')) + return array(); + + // Find the most recent members. + $request = $smcFunc['db_query']('', ' + SELECT id_member, member_name, real_name, date_registered, last_login + FROM {db_prefix}members + ORDER BY id_member DESC + LIMIT {int:limit}', + array( + 'limit' => $_GET['limit'], + ) + ); + $data = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Make the data look rss-ish. + if ($xml_format == 'rss' || $xml_format == 'rss2') + $data[] = array( + 'title' => cdata_parse($row['real_name']), + 'link' => $scripturl . '?action=profile;u=' . $row['id_member'], + 'comments' => $scripturl . '?action=pm;sa=send;u=' . $row['id_member'], + 'pubDate' => gmdate('D, d M Y H:i:s \G\M\T', $row['date_registered']), + 'guid' => $scripturl . '?action=profile;u=' . $row['id_member'], + ); + elseif ($xml_format == 'rdf') + $data[] = array( + 'title' => cdata_parse($row['real_name']), + 'link' => $scripturl . '?action=profile;u=' . $row['id_member'], + ); + elseif ($xml_format == 'atom') + $data[] = array( + 'title' => cdata_parse($row['real_name']), + 'link' => $scripturl . '?action=profile;u=' . $row['id_member'], + 'published' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['date_registered']), + 'updated' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['last_login']), + 'id' => $scripturl . '?action=profile;u=' . $row['id_member'], + ); + // More logical format for the data, but harder to apply. + else + $data[] = array( + 'name' => cdata_parse($row['real_name']), + 'time' => htmlspecialchars(strip_tags(timeformat($row['date_registered']))), + 'id' => $row['id_member'], + 'link' => $scripturl . '?action=profile;u=' . $row['id_member'] + ); + } + $smcFunc['db_free_result']($request); + + return $data; +} + +function getXmlNews($xml_format) +{ + global $user_info, $scripturl, $modSettings, $board; + global $query_this_board, $smcFunc, $settings, $context; + + /* Find the latest posts that: + - are the first post in their topic. + - are on an any board OR in a specified board. + - can be seen by this user. + - are actually the latest posts. */ + + $done = false; + $loops = 0; + while (!$done) + { + $optimize_msg = implode(' AND ', $context['optimize_msg']); + $request = $smcFunc['db_query']('', ' + SELECT + m.smileys_enabled, m.poster_time, m.id_msg, m.subject, m.body, m.modified_time, + m.icon, t.id_topic, t.id_board, t.num_replies, + b.name AS bname, + mem.hide_email, IFNULL(mem.id_member, 0) AS id_member, + IFNULL(mem.email_address, m.poster_email) AS poster_email, + IFNULL(mem.real_name, m.poster_name) AS poster_name + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE ' . $query_this_board . (empty($optimize_msg) ? '' : ' + AND {raw:optimize_msg}') . (empty($board) ? '' : ' + AND t.id_board = {int:current_board}') . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved}' : '') . ' + ORDER BY t.id_first_msg DESC + LIMIT {int:limit}', + array( + 'current_board' => $board, + 'is_approved' => 1, + 'limit' => $_GET['limit'], + 'optimize_msg' => $optimize_msg, + ) + ); + // If we don't have $_GET['limit'] results, try again with an unoptimized version covering all rows. + if ($loops < 2 && $smcFunc['db_num_rows']($request) < $_GET['limit']) + { + $smcFunc['db_free_result']($request); + if (empty($_REQUEST['boards']) && empty($board)) + unset($context['optimize_msg']['lowest']); + else + $context['optimize_msg']['lowest'] = 'm.id_msg >= t.id_first_msg'; + $context['optimize_msg']['highest'] = 'm.id_msg <= t.id_last_msg'; + $loops++; + } + else + $done = true; + } + $data = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Limit the length of the message, if the option is set. + if (!empty($modSettings['xmlnews_maxlen']) && $smcFunc['strlen'](str_replace('
', "\n", $row['body'])) > $modSettings['xmlnews_maxlen']) + $row['body'] = strtr($smcFunc['substr'](str_replace('
', "\n", $row['body']), 0, $modSettings['xmlnews_maxlen'] - 3), array("\n" => '
')) . '...'; + + $row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']); + + censorText($row['body']); + censorText($row['subject']); + + // Being news, this actually makes sense in rss format. + if ($xml_format == 'rss' || $xml_format == 'rss2') + $data[] = array( + 'title' => cdata_parse($row['subject']), + 'link' => $scripturl . '?topic=' . $row['id_topic'] . '.0', + 'description' => cdata_parse($row['body']), + 'author' => in_array(showEmailAddress(!empty($row['hide_email']), $row['id_member']), array('yes', 'yes_permission_override')) ? $row['poster_email'] : null, + 'comments' => $scripturl . '?action=post;topic=' . $row['id_topic'] . '.0', + 'category' => '', + 'pubDate' => gmdate('D, d M Y H:i:s \G\M\T', $row['poster_time']), + 'guid' => $scripturl . '?topic=' . $row['id_topic'] . '.0', + ); + elseif ($xml_format == 'rdf') + $data[] = array( + 'title' => cdata_parse($row['subject']), + 'link' => $scripturl . '?topic=' . $row['id_topic'] . '.0', + 'description' => cdata_parse($row['body']), + ); + elseif ($xml_format == 'atom') + $data[] = array( + 'title' => cdata_parse($row['subject']), + 'link' => $scripturl . '?topic=' . $row['id_topic'] . '.0', + 'summary' => cdata_parse($row['body']), + 'category' => array('term' => $row['id_board'], 'label' => cdata_parse($row['bname'])), + 'author' => array( + 'name' => $row['poster_name'], + 'email' => in_array(showEmailAddress(!empty($row['hide_email']), $row['id_member']), array('yes', 'yes_permission_override')) ? $row['poster_email'] : null, + 'uri' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : '', + ), + 'published' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['poster_time']), + 'modified' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', empty($row['modified_time']) ? $row['poster_time'] : $row['modified_time']), + 'id' => $scripturl . '?topic=' . $row['id_topic'] . '.0', + 'icon' => $settings['images_url'] . '/icons/' . $row['icon'] . '.gif', + ); + // The biggest difference here is more information. + else + $data[] = array( + 'time' => htmlspecialchars(strip_tags(timeformat($row['poster_time']))), + 'id' => $row['id_topic'], + 'subject' => cdata_parse($row['subject']), + 'body' => cdata_parse($row['body']), + 'poster' => array( + 'name' => cdata_parse($row['poster_name']), + 'id' => $row['id_member'], + 'link' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : '', + ), + 'topic' => $row['id_topic'], + 'board' => array( + 'name' => cdata_parse($row['bname']), + 'id' => $row['id_board'], + 'link' => $scripturl . '?board=' . $row['id_board'] . '.0', + ), + 'link' => $scripturl . '?topic=' . $row['id_topic'] . '.0', + ); + } + $smcFunc['db_free_result']($request); + + return $data; +} + +function getXmlRecent($xml_format) +{ + global $user_info, $scripturl, $modSettings, $board; + global $query_this_board, $smcFunc, $settings, $context; + + $done = false; + $loops = 0; + while (!$done) + { + $optimize_msg = implode(' AND ', $context['optimize_msg']); + $request = $smcFunc['db_query']('', ' + SELECT m.id_msg + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + WHERE ' . $query_this_board . (empty($optimize_msg) ? '' : ' + AND {raw:optimize_msg}') . (empty($board) ? '' : ' + AND m.id_board = {int:current_board}') . ($modSettings['postmod_active'] ? ' + AND m.approved = {int:is_approved}' : '') . ' + ORDER BY m.id_msg DESC + LIMIT {int:limit}', + array( + 'limit' => $_GET['limit'], + 'current_board' => $board, + 'is_approved' => 1, + 'optimize_msg' => $optimize_msg, + ) + ); + // If we don't have $_GET['limit'] results, try again with an unoptimized version covering all rows. + if ($loops < 2 && $smcFunc['db_num_rows']($request) < $_GET['limit']) + { + $smcFunc['db_free_result']($request); + if (empty($_REQUEST['boards']) && empty($board)) + unset($context['optimize_msg']['lowest']); + else + $context['optimize_msg']['lowest'] = $loops ? 'm.id_msg >= t.id_first_msg' : 'm.id_msg >= (t.id_last_msg - t.id_first_msg) / 2'; + $loops++; + } + else + $done = true; + } + $messages = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $messages[] = $row['id_msg']; + $smcFunc['db_free_result']($request); + + if (empty($messages)) + return array(); + + // Find the most recent posts this user can see. + $request = $smcFunc['db_query']('', ' + SELECT + m.smileys_enabled, m.poster_time, m.id_msg, m.subject, m.body, m.id_topic, t.id_board, + b.name AS bname, t.num_replies, m.id_member, m.icon, mf.id_member AS id_first_member, + IFNULL(mem.real_name, m.poster_name) AS poster_name, mf.subject AS first_subject, + IFNULL(memf.real_name, mf.poster_name) AS first_poster_name, mem.hide_email, + IFNULL(mem.email_address, m.poster_email) AS poster_email, m.modified_time + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + LEFT JOIN {db_prefix}members AS memf ON (memf.id_member = mf.id_member) + WHERE m.id_msg IN ({array_int:message_list}) + ' . (empty($board) ? '' : 'AND t.id_board = {int:current_board}') . ' + ORDER BY m.id_msg DESC + LIMIT {int:limit}', + array( + 'limit' => $_GET['limit'], + 'current_board' => $board, + 'message_list' => $messages, + ) + ); + $data = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Limit the length of the message, if the option is set. + if (!empty($modSettings['xmlnews_maxlen']) && $smcFunc['strlen'](str_replace('
', "\n", $row['body'])) > $modSettings['xmlnews_maxlen']) + $row['body'] = strtr($smcFunc['substr'](str_replace('
', "\n", $row['body']), 0, $modSettings['xmlnews_maxlen'] - 3), array("\n" => '
')) . '...'; + + $row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']); + + censorText($row['body']); + censorText($row['subject']); + + // Doesn't work as well as news, but it kinda does.. + if ($xml_format == 'rss' || $xml_format == 'rss2') + $data[] = array( + 'title' => $row['subject'], + 'link' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'], + 'description' => cdata_parse($row['body']), + 'author' => in_array(showEmailAddress(!empty($row['hide_email']), $row['id_member']), array('yes', 'yes_permission_override')) ? $row['poster_email'] : null, + 'category' => cdata_parse($row['bname']), + 'comments' => $scripturl . '?action=post;topic=' . $row['id_topic'] . '.0', + 'pubDate' => gmdate('D, d M Y H:i:s \G\M\T', $row['poster_time']), + 'guid' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'] + ); + elseif ($xml_format == 'rdf') + $data[] = array( + 'title' => $row['subject'], + 'link' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'], + 'description' => cdata_parse($row['body']), + ); + elseif ($xml_format == 'atom') + $data[] = array( + 'title' => $row['subject'], + 'link' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'], + 'summary' => cdata_parse($row['body']), + 'category' => array( + 'term' => $row['id_board'], + 'label' => cdata_parse($row['bname']) + ), + 'author' => array( + 'name' => $row['poster_name'], + 'email' => in_array(showEmailAddress(!empty($row['hide_email']), $row['id_member']), array('yes', 'yes_permission_override')) ? $row['poster_email'] : null, + 'uri' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : '' + ), + 'published' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $row['poster_time']), + 'updated' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', empty($row['modified_time']) ? $row['poster_time'] : $row['modified_time']), + 'id' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'], + 'icon' => $settings['images_url'] . '/icons/' . $row['icon'] . '.gif', + ); + // A lot of information here. Should be enough to please the rss-ers. + else + $data[] = array( + 'time' => htmlspecialchars(strip_tags(timeformat($row['poster_time']))), + 'id' => $row['id_msg'], + 'subject' => cdata_parse($row['subject']), + 'body' => cdata_parse($row['body']), + 'starter' => array( + 'name' => cdata_parse($row['first_poster_name']), + 'id' => $row['id_first_member'], + 'link' => !empty($row['id_first_member']) ? $scripturl . '?action=profile;u=' . $row['id_first_member'] : '' + ), + 'poster' => array( + 'name' => cdata_parse($row['poster_name']), + 'id' => $row['id_member'], + 'link' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : '' + ), + 'topic' => array( + 'subject' => cdata_parse($row['first_subject']), + 'id' => $row['id_topic'], + 'link' => $scripturl . '?topic=' . $row['id_topic'] . '.new#new' + ), + 'board' => array( + 'name' => cdata_parse($row['bname']), + 'id' => $row['id_board'], + 'link' => $scripturl . '?board=' . $row['id_board'] . '.0' + ), + 'link' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'] + ); + } + $smcFunc['db_free_result']($request); + + return $data; +} + +function getXmlProfile($xml_format) +{ + global $scripturl, $memberContext, $user_profile, $modSettings, $user_info; + + // You must input a valid user.... + if (empty($_GET['u']) || loadMemberData((int) $_GET['u']) === false) + return array(); + + // Make sure the id is a number and not "I like trying to hack the database". + $_GET['u'] = (int) $_GET['u']; + // Load the member's contextual information! + if (!loadMemberContext($_GET['u']) || !allowedTo('profile_view_any')) + return array(); + + // Okay, I admit it, I'm lazy. Stupid $_GET['u'] is long and hard to type. + $profile = &$memberContext[$_GET['u']]; + + if ($xml_format == 'rss' || $xml_format == 'rss2') + $data = array(array( + 'title' => cdata_parse($profile['name']), + 'link' => $scripturl . '?action=profile;u=' . $profile['id'], + 'description' => cdata_parse(isset($profile['group']) ? $profile['group'] : $profile['post_group']), + 'comments' => $scripturl . '?action=pm;sa=send;u=' . $profile['id'], + 'pubDate' => gmdate('D, d M Y H:i:s \G\M\T', $user_profile[$profile['id']]['date_registered']), + 'guid' => $scripturl . '?action=profile;u=' . $profile['id'], + )); + elseif ($xml_format == 'rdf') + $data = array(array( + 'title' => cdata_parse($profile['name']), + 'link' => $scripturl . '?action=profile;u=' . $profile['id'], + 'description' => cdata_parse(isset($profile['group']) ? $profile['group'] : $profile['post_group']), + )); + elseif ($xml_format == 'atom') + $data[] = array( + 'title' => cdata_parse($profile['name']), + 'link' => $scripturl . '?action=profile;u=' . $profile['id'], + 'summary' => cdata_parse(isset($profile['group']) ? $profile['group'] : $profile['post_group']), + 'author' => array( + 'name' => $profile['real_name'], + 'email' => in_array(showEmailAddress(!empty($profile['hide_email']), $profile['id']), array('yes', 'yes_permission_override')) ? $profile['email'] : null, + 'uri' => !empty($profile['website']) ? $profile['website']['url'] : '' + ), + 'published' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $user_profile[$profile['id']]['date_registered']), + 'updated' => gmstrftime('%Y-%m-%dT%H:%M:%SZ', $user_profile[$profile['id']]['last_login']), + 'id' => $scripturl . '?action=profile;u=' . $profile['id'], + 'logo' => !empty($profile['avatar']) ? $profile['avatar']['url'] : '', + ); + else + { + $data = array( + 'username' => $user_info['is_admin'] || $user_info['id'] == $profile['id'] ? cdata_parse($profile['username']) : '', + 'name' => cdata_parse($profile['name']), + 'link' => $scripturl . '?action=profile;u=' . $profile['id'], + 'posts' => $profile['posts'], + 'post-group' => cdata_parse($profile['post_group']), + 'language' => cdata_parse($profile['language']), + 'last-login' => gmdate('D, d M Y H:i:s \G\M\T', $user_profile[$profile['id']]['last_login']), + 'registered' => gmdate('D, d M Y H:i:s \G\M\T', $user_profile[$profile['id']]['date_registered']) + ); + + // Everything below here might not be set, and thus maybe shouldn't be displayed. + if ($profile['gender']['name'] != '') + $data['gender'] = cdata_parse($profile['gender']['name']); + + if ($profile['avatar']['name'] != '') + $data['avatar'] = $profile['avatar']['url']; + + // If they are online, show an empty tag... no reason to put anything inside it. + if ($profile['online']['is_online']) + $data['online'] = ''; + + if ($profile['signature'] != '') + $data['signature'] = cdata_parse($profile['signature']); + if ($profile['blurb'] != '') + $data['blurb'] = cdata_parse($profile['blurb']); + if ($profile['location'] != '') + $data['location'] = cdata_parse($profile['location']); + if ($profile['title'] != '') + $data['title'] = cdata_parse($profile['title']); + + if (!empty($profile['icq']['name']) && !(!empty($modSettings['guest_hideContacts']) && $user_info['is_guest'])) + $data['icq'] = $profile['icq']['name']; + if ($profile['aim']['name'] != '' && !(!empty($modSettings['guest_hideContacts']) && $user_info['is_guest'])) + $data['aim'] = $profile['aim']['name']; + if ($profile['msn']['name'] != '' && !(!empty($modSettings['guest_hideContacts']) && $user_info['is_guest'])) + $data['msn'] = $profile['msn']['name']; + if ($profile['yim']['name'] != '' && !(!empty($modSettings['guest_hideContacts']) && $user_info['is_guest'])) + $data['yim'] = $profile['yim']['name']; + + if ($profile['website']['title'] != '') + $data['website'] = array( + 'title' => cdata_parse($profile['website']['title']), + 'link' => $profile['website']['url'] + ); + + if ($profile['group'] != '') + $data['position'] = cdata_parse($profile['group']); + + if (!empty($modSettings['karmaMode'])) + $data['karma'] = array( + 'good' => $profile['karma']['good'], + 'bad' => $profile['karma']['bad'] + ); + + if (in_array($profile['show_email'], array('yes', 'yes_permission_override'))) + $data['email'] = $profile['email']; + + if (!empty($profile['birth_date']) && substr($profile['birth_date'], 0, 4) != '0000') + { + list ($birth_year, $birth_month, $birth_day) = sscanf($profile['birth_date'], '%d-%d-%d'); + $datearray = getdate(forum_time()); + $data['age'] = $datearray['year'] - $birth_year - (($datearray['mon'] > $birth_month || ($datearray['mon'] == $birth_month && $datearray['mday'] >= $birth_day)) ? 0 : 1); + } + } + + // Save some memory. + unset($profile, $memberContext[$_GET['u']]); + + return $data; +} + +?> \ No newline at end of file diff --git a/Sources/Notify.php b/Sources/Notify.php new file mode 100644 index 0000000..c6da2d7 --- /dev/null +++ b/Sources/Notify.php @@ -0,0 +1,187 @@ + $user_info['id'], + 'current_topic' => $topic, + ) + ); + $context['notification_set'] = $smcFunc['db_num_rows']($request) != 0; + $smcFunc['db_free_result']($request); + + // Set the template variables... + $context['topic_href'] = $scripturl . '?topic=' . $topic . '.' . $_REQUEST['start']; + $context['start'] = $_REQUEST['start']; + $context['page_title'] = $txt['notification']; + + return; + } + elseif ($_GET['sa'] == 'on') + { + checkSession('get'); + + // Attempt to turn notifications on. + $smcFunc['db_insert']('ignore', + '{db_prefix}log_notify', + array('id_member' => 'int', 'id_topic' => 'int'), + array($user_info['id'], $topic), + array('id_member', 'id_topic') + ); + } + else + { + checkSession('get'); + + // Just turn notifications off. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_notify + WHERE id_member = {int:current_member} + AND id_topic = {int:current_topic}', + array( + 'current_member' => $user_info['id'], + 'current_topic' => $topic, + ) + ); + } + + // Send them back to the topic. + redirectexit('topic=' . $topic . '.' . $_REQUEST['start']); +} + +function BoardNotify() +{ + global $scripturl, $txt, $board, $user_info, $context, $smcFunc; + + // Permissions are an important part of anything ;). + is_not_guest(); + isAllowedTo('mark_notify'); + + // You have to specify a board to turn notifications on! + if (empty($board)) + fatal_lang_error('no_board', false); + + // No subaction: find out what to do. + if (empty($_GET['sa'])) + { + // We're gonna need the notify template... + loadTemplate('Notify'); + + // Find out if they have notification set for this topic already. + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}log_notify + WHERE id_member = {int:current_member} + AND id_board = {int:current_board} + LIMIT 1', + array( + 'current_board' => $board, + 'current_member' => $user_info['id'], + ) + ); + $context['notification_set'] = $smcFunc['db_num_rows']($request) != 0; + $smcFunc['db_free_result']($request); + + // Set the template variables... + $context['board_href'] = $scripturl . '?board=' . $board . '.' . $_REQUEST['start']; + $context['start'] = $_REQUEST['start']; + $context['page_title'] = $txt['notification']; + $context['sub_template'] = 'notify_board'; + + return; + } + // Turn the board level notification on.... + elseif ($_GET['sa'] == 'on') + { + checkSession('get'); + + // Turn notification on. (note this just blows smoke if it's already on.) + $smcFunc['db_insert']('ignore', + '{db_prefix}log_notify', + array('id_member' => 'int', 'id_board' => 'int'), + array($user_info['id'], $board), + array('id_member', 'id_board') + ); + } + // ...or off? + else + { + checkSession('get'); + + // Turn notification off for this board. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_notify + WHERE id_member = {int:current_member} + AND id_board = {int:current_board}', + array( + 'current_board' => $board, + 'current_member' => $user_info['id'], + ) + ); + } + + // Back to the board! + redirectexit('board=' . $board . '.' . $_REQUEST['start']); +} + +?> \ No newline at end of file diff --git a/Sources/PackageGet.php b/Sources/PackageGet.php new file mode 100644 index 0000000..15bca4c --- /dev/null +++ b/Sources/PackageGet.php @@ -0,0 +1,765 @@ + 'PackageServers', + 'add' => 'PackageServerAdd', + 'browse' => 'PackageGBrowse', + 'download' => 'PackageDownload', + 'remove' => 'PackageServerRemove', + 'upload' => 'PackageUpload', + ); + + // Now let's decide where we are taking this... + if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']])) + $context['sub_action'] = $_REQUEST['sa']; + // We need to support possible old javascript links... + elseif (isset($_GET['pgdownload'])) + $context['sub_action'] = 'download'; + else + $context['sub_action'] = 'servers'; + + // We need to force the "Download" tab as selected. + $context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'packageget'; + + // Now create the tabs for the template. + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['package_manager'], + //'help' => 'registrations', + 'description' => $txt['package_manager_desc'], + 'tabs' => array( + 'browse' => array( + ), + 'packageget' => array( + 'description' => $txt['download_packages_desc'], + ), + 'installed' => array( + 'description' => $txt['installed_packages_desc'], + ), + 'perms' => array( + 'description' => $txt['package_file_perms_desc'], + ), + 'options' => array( + 'description' => $txt['package_install_options_ftp_why'], + ), + ), + ); + + $subActions[$context['sub_action']](); +} + +function PackageServers() +{ + global $txt, $scripturl, $context, $boarddir, $sourcedir, $modSettings, $smcFunc; + + // Ensure we use the correct template, and page title. + $context['sub_template'] = 'servers'; + $context['page_title'] .= ' - ' . $txt['download_packages']; + + // Load the list of servers. + $request = $smcFunc['db_query']('', ' + SELECT id_server, name, url + FROM {db_prefix}package_servers', + array( + ) + ); + $context['servers'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $context['servers'][] = array( + 'name' => $row['name'], + 'url' => $row['url'], + 'id' => $row['id_server'], + ); + } + $smcFunc['db_free_result']($request); + + $context['package_download_broken'] = !is_writable($boarddir . '/Packages') || !is_writable($boarddir . '/Packages/installed.list'); + + if ($context['package_download_broken']) + { + @chmod($boarddir . '/Packages', 0777); + @chmod($boarddir . '/Packages/installed.list', 0777); + } + + $context['package_download_broken'] = !is_writable($boarddir . '/Packages') || !is_writable($boarddir . '/Packages/installed.list'); + + if ($context['package_download_broken']) + { + if (isset($_POST['ftp_username'])) + { + loadClassFile('Class-Package.php'); + $ftp = new ftp_connection($_POST['ftp_server'], $_POST['ftp_port'], $_POST['ftp_username'], $_POST['ftp_password']); + + if ($ftp->error === false) + { + // I know, I know... but a lot of people want to type /home/xyz/... which is wrong, but logical. + if (!$ftp->chdir($_POST['ftp_path'])) + { + $ftp_error = $ftp->error; + $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['ftp_path'])); + } + } + } + + if (!isset($ftp) || $ftp->error !== false) + { + if (!isset($ftp)) + { + loadClassFile('Class-Package.php'); + $ftp = new ftp_connection(null); + } + elseif ($ftp->error !== false && !isset($ftp_error)) + $ftp_error = $ftp->last_message === null ? '' : $ftp->last_message; + + list ($username, $detect_path, $found_path) = $ftp->detect_path($boarddir); + + if ($found_path || !isset($_POST['ftp_path'])) + $_POST['ftp_path'] = $detect_path; + + if (!isset($_POST['ftp_username'])) + $_POST['ftp_username'] = $username; + + $context['package_ftp'] = array( + 'server' => isset($_POST['ftp_server']) ? $_POST['ftp_server'] : (isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost'), + 'port' => isset($_POST['ftp_port']) ? $_POST['ftp_port'] : (isset($modSettings['package_port']) ? $modSettings['package_port'] : '21'), + 'username' => isset($_POST['ftp_username']) ? $_POST['ftp_username'] : (isset($modSettings['package_username']) ? $modSettings['package_username'] : ''), + 'path' => $_POST['ftp_path'], + 'error' => empty($ftp_error) ? null : $ftp_error, + ); + } + else + { + $context['package_download_broken'] = false; + + $ftp->chmod('Packages', 0777); + $ftp->chmod('Packages/installed.list', 0777); + + $ftp->close(); + } + } +} + +// Browse a server's list of packages. +function PackageGBrowse() +{ + global $txt, $boardurl, $context, $scripturl, $boarddir, $sourcedir, $forum_version, $context, $smcFunc; + + if (isset($_GET['server'])) + { + if ($_GET['server'] == '') + redirectexit('action=admin;area=packages;get'); + + $server = (int) $_GET['server']; + + // Query the server list to find the current server. + $request = $smcFunc['db_query']('', ' + SELECT name, url + FROM {db_prefix}package_servers + WHERE id_server = {int:current_server} + LIMIT 1', + array( + 'current_server' => $server, + ) + ); + list ($name, $url) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // If the server does not exist, dump out. + if (empty($url)) + fatal_lang_error('couldnt_connect', false); + + // If there is a relative link, append to the stored server url. + if (isset($_GET['relative'])) + $url = $url . (substr($url, -1) == '/' ? '' : '/') . $_GET['relative']; + + // Clear any "absolute" URL. Since "server" is present, "absolute" is garbage. + unset($_GET['absolute']); + } + elseif (isset($_GET['absolute']) && $_GET['absolute'] != '') + { + // Initialize the requried variables. + $server = ''; + $url = $_GET['absolute']; + $name = ''; + $_GET['package'] = $url . '/packages.xml?language=' . $context['user']['language']; + + // Clear any "relative" URL. Since "server" is not present, "relative" is garbage. + unset($_GET['relative']); + + $token = checkConfirm('get_absolute_url'); + if ($token !== true) + { + $context['sub_template'] = 'package_confirm'; + + $context['page_title'] = $txt['package_servers']; + $context['confirm_message'] = sprintf($txt['package_confirm_view_package_content'], htmlspecialchars($_GET['absolute'])); + $context['proceed_href'] = $scripturl . '?action=admin;area=packages;get;sa=browse;absolute=' . urlencode($_GET['absolute']) . ';confirm=' . $token; + + return; + } + } + // Minimum required parameter did not exist so dump out. + else + fatal_lang_error('couldnt_connect', false); + + // Attempt to connect. If unsuccessful... try the URL. + if (!isset($_GET['package']) || file_exists($_GET['package'])) + $_GET['package'] = $url . '/packages.xml?language=' . $context['user']['language']; + + // Check to be sure the packages.xml file actually exists where it is should be... or dump out. + if ((isset($_GET['absolute']) || isset($_GET['relative'])) && !url_exists($_GET['package'])) + fatal_lang_error('packageget_unable', false, array($url . '/index.php')); + + // Might take some time. + @set_time_limit(600); + + // Read packages.xml and parse into xmlArray. (the true tells it to trim things ;).) + loadClassFile('Class-Package.php'); + $listing = new xmlArray(fetch_web_data($_GET['package']), true); + + // Errm.... empty file? Try the URL.... + if (!$listing->exists('package-list')) + fatal_lang_error('packageget_unable', false, array($url . '/index.php')); + + // List out the packages... + $context['package_list'] = array(); + + $listing = $listing->path('package-list[0]'); + + // Use the package list's name if it exists. + if ($listing->exists('list-title')) + $name = $listing->fetch('list-title'); + + // Pick the correct template. + $context['sub_template'] = 'package_list'; + + $context['page_title'] = $txt['package_servers'] . ($name != '' ? ' - ' . $name : ''); + $context['package_server'] = $server; + + // By default we use an unordered list, unless there are no lists with more than one package. + $context['list_type'] = 'ul'; + + $instmods = loadInstalledPackages(); + + $installed_mods = array(); + // Look through the list of installed mods... + foreach ($instmods as $installed_mod) + $installed_mods[$installed_mod['package_id']] = $installed_mod['version']; + + // Get default author and email if they exist. + if ($listing->exists('default-author')) + { + $default_author = $smcFunc['htmlspecialchars']($listing->fetch('default-author')); + if ($listing->exists('default-author/@email')) + $default_email = $smcFunc['htmlspecialchars']($listing->fetch('default-author/@email')); + } + + // Get default web site if it exists. + if ($listing->exists('default-website')) + { + $default_website = $smcFunc['htmlspecialchars']($listing->fetch('default-website')); + if ($listing->exists('default-website/@title')) + $default_title = $smcFunc['htmlspecialchars']($listing->fetch('default-website/@title')); + } + + $the_version = strtr($forum_version, array('SMF ' => '')); + if (!empty($_SESSION['version_emulate'])) + $the_version = $_SESSION['version_emulate']; + + $packageNum = 0; + $packageSection = 0; + + $sections = $listing->set('section'); + foreach ($sections as $i => $section) + { + $context['package_list'][$packageSection] = array( + 'title' => '', + 'text' => '', + 'items' => array(), + ); + + $packages = $section->set('title|heading|text|remote|rule|modification|language|avatar-pack|theme|smiley-set'); + foreach ($packages as $thisPackage) + { + $package = array( + 'type' => $thisPackage->name(), + ); + + if (in_array($package['type'], array('title', 'text'))) + $context['package_list'][$packageSection][$package['type']] = $smcFunc['htmlspecialchars']($thisPackage->fetch('.')); + // It's a Title, Heading, Rule or Text. + elseif (in_array($package['type'], array('heading', 'rule'))) + $package['name'] = $smcFunc['htmlspecialchars']($thisPackage->fetch('.')); + // It's a Remote link. + elseif ($package['type'] == 'remote') + { + $remote_type = $thisPackage->exists('@type') ? $thisPackage->fetch('@type') : 'relative'; + + if ($remote_type == 'relative' && substr($thisPackage->fetch('@href'), 0, 7) != 'http://') + { + if (isset($_GET['absolute'])) + $current_url = $_GET['absolute'] . '/'; + elseif (isset($_GET['relative'])) + $current_url = $_GET['relative'] . '/'; + else + $current_url = ''; + + $current_url .= $thisPackage->fetch('@href'); + if (isset($_GET['absolute'])) + $package['href'] = $scripturl . '?action=admin;area=packages;get;sa=browse;absolute=' . $current_url; + else + $package['href'] = $scripturl . '?action=admin;area=packages;get;sa=browse;server=' . $context['package_server'] . ';relative=' . $current_url; + } + else + { + $current_url = $thisPackage->fetch('@href'); + $package['href'] = $scripturl . '?action=admin;area=packages;get;sa=browse;absolute=' . $current_url; + } + + $package['name'] = $smcFunc['htmlspecialchars']($thisPackage->fetch('.')); + $package['link'] = '' . $package['name'] . ''; + } + // It's a package... + else + { + if (isset($_GET['absolute'])) + $current_url = $_GET['absolute'] . '/'; + elseif (isset($_GET['relative'])) + $current_url = $_GET['relative'] . '/'; + else + $current_url = ''; + + $server_att = $server != '' ? ';server=' . $server : ''; + + $package += $thisPackage->to_array(); + + if (isset($package['website'])) + unset($package['website']); + $package['author'] = array(); + + if ($package['description'] == '') + $package['description'] = $txt['package_no_description']; + else + $package['description'] = parse_bbc(preg_replace('~\[[/]?html\]~i', '', $smcFunc['htmlspecialchars']($package['description']))); + + $package['is_installed'] = isset($installed_mods[$package['id']]); + $package['is_current'] = $package['is_installed'] && ($installed_mods[$package['id']] == $package['version']); + $package['is_newer'] = $package['is_installed'] && ($installed_mods[$package['id']] > $package['version']); + + // This package is either not installed, or installed but old. Is it supported on this version of SMF? + if (!$package['is_installed'] || (!$package['is_current'] && !$package['is_newer'])) + { + if ($thisPackage->exists('version/@for')) + $package['can_install'] = matchPackageVersion($the_version, $thisPackage->fetch('version/@for')); + } + // Okay, it's already installed AND up to date. + else + $package['can_install'] = false; + + $already_exists = getPackageInfo(basename($package['filename'])); + $package['download_conflict'] = is_array($already_exists) && $already_exists['id'] == $package['id'] && $already_exists['version'] != $package['version']; + + $package['href'] = $url . '/' . $package['filename']; + $package['name'] = $smcFunc['htmlspecialchars']($package['name']); + $package['link'] = '' . $package['name'] . ''; + $package['download']['href'] = $scripturl . '?action=admin;area=packages;get;sa=download' . $server_att . ';package=' . $current_url . $package['filename'] . ($package['download_conflict'] ? ';conflict' : '') . ';' . $context['session_var'] . '=' . $context['session_id']; + $package['download']['link'] = '' . $package['name'] . ''; + + if ($thisPackage->exists('author') || isset($default_author)) + { + if ($thisPackage->exists('author/@email')) + $package['author']['email'] = $thisPackage->fetch('author/@email'); + elseif (isset($default_email)) + $package['author']['email'] = $default_email; + + if ($thisPackage->exists('author') && $thisPackage->fetch('author') != '') + $package['author']['name'] = $smcFunc['htmlspecialchars']($thisPackage->fetch('author')); + else + $package['author']['name'] = $default_author; + + if (!empty($package['author']['email'])) + { + // Only put the "mailto:" if it looks like a valid email address. Some may wish to put a link to an SMF IM Form or other web mail form. + $package['author']['href'] = preg_match('~^[\w\.\-]+@[\w][\w\-\.]+[\w]$~', $package['author']['email']) != 0 ? 'mailto:' . $package['author']['email'] : $package['author']['email']; + $package['author']['link'] = '' . $package['author']['name'] . ''; + } + } + + if ($thisPackage->exists('website') || isset($default_website)) + { + if ($thisPackage->exists('website') && $thisPackage->exists('website/@title')) + $package['author']['website']['name'] = $smcFunc['htmlspecialchars']($thisPackage->fetch('website/@title')); + elseif (isset($default_title)) + $package['author']['website']['name'] = $default_title; + elseif ($thisPackage->exists('website')) + $package['author']['website']['name'] = $smcFunc['htmlspecialchars']($thisPackage->fetch('website')); + else + $package['author']['website']['name'] = $default_website; + + if ($thisPackage->exists('website') && $thisPackage->fetch('website') != '') + $authorhompage = $thisPackage->fetch('website'); + else + $authorhompage = $default_website; + + if (strpos(strtolower($authorhompage), 'a href') === false) + { + $package['author']['website']['href'] = $authorhompage; + $package['author']['website']['link'] = '' . $package['author']['website']['name'] . ''; + } + else + { + if (preg_match('/a href="(.+?)"/', $authorhompage, $match) == 1) + $package['author']['website']['href'] = $match[1]; + else + $package['author']['website']['href'] = ''; + $package['author']['website']['link'] = $authorhompage; + } + } + else + { + $package['author']['website']['href'] = ''; + $package['author']['website']['link'] = ''; + } + } + + $package['is_remote'] = $package['type'] == 'remote'; + $package['is_title'] = $package['type'] == 'title'; + $package['is_heading'] = $package['type'] == 'heading'; + $package['is_text'] = $package['type'] == 'text'; + $package['is_line'] = $package['type'] == 'rule'; + + $packageNum = in_array($package['type'], array('title', 'heading', 'text', 'remote', 'rule')) ? 0 : $packageNum + 1; + $package['count'] = $packageNum; + + if (!in_array($package['type'], array('title', 'text'))) + $context['package_list'][$packageSection]['items'][] = $package; + + if ($package['count'] > 1) + $context['list_type'] = 'ol'; + } + + $packageSection++; + } + + // Lets make sure we get a nice new spiffy clean $package to work with. Otherwise we get PAIN! + unset($package); + + foreach ($context['package_list'] as $ps_id => $packageSection) + { + foreach ($packageSection['items'] as $i => $package) + { + if ($package['count'] == 0 || isset($package['can_install'])) + continue; + + $context['package_list'][$ps_id]['items'][$i]['can_install'] = false; + + $packageInfo = getPackageInfo($url . '/' . $package['filename']); + if (is_array($packageInfo) && $packageInfo['xml']->exists('install')) + { + $installs = $packageInfo['xml']->set('install'); + foreach ($installs as $install) + if (!$install->exists('@for') || matchPackageVersion($the_version, $install->fetch('@for'))) + { + // Okay, this one is good to go. + $context['package_list'][$ps_id]['items'][$i]['can_install'] = true; + break; + } + } + } + } +} + +// Download a package. +function PackageDownload() +{ + global $txt, $scripturl, $boarddir, $context, $sourcedir, $smcFunc; + + // Use the downloaded sub template. + $context['sub_template'] = 'downloaded'; + + // Security is good... + checkSession('get'); + + // To download something, we need a valid server or url. + if (empty($_GET['server']) && (!empty($_GET['get']) && !empty($_REQUEST['package']))) + fatal_lang_error('package_get_error_is_zero', false); + + if (isset($_GET['server'])) + { + $server = (int) $_GET['server']; + + // Query the server table to find the requested server. + $request = $smcFunc['db_query']('', ' + SELECT name, url + FROM {db_prefix}package_servers + WHERE id_server = {int:current_server} + LIMIT 1', + array( + 'current_server' => $server, + ) + ); + list ($name, $url) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // If server does not exist then dump out. + if (empty($url)) + fatal_lang_error('couldnt_connect', false); + + $url = $url . '/'; + } + else + { + // Initialize the requried variables. + $server = ''; + $url = ''; + } + + if (isset($_REQUEST['byurl']) && !empty($_POST['filename'])) + $package_name = basename($_REQUEST['filename']); + else + $package_name = basename($_REQUEST['package']); + + if (isset($_REQUEST['conflict']) || (isset($_REQUEST['auto']) && file_exists($boarddir . '/Packages/' . $package_name))) + { + // Find the extension, change abc.tar.gz to abc_1.tar.gz... + if (strrpos(substr($package_name, 0, -3), '.') !== false) + { + $ext = substr($package_name, strrpos(substr($package_name, 0, -3), '.')); + $package_name = substr($package_name, 0, strrpos(substr($package_name, 0, -3), '.')) . '_'; + } + else + $ext = ''; + + // Find the first available. + $i = 1; + while (file_exists($boarddir . '/Packages/' . $package_name . $i . $ext)) + $i++; + + $package_name = $package_name . $i . $ext; + } + + // First make sure it's a package. + $packageInfo = getPackageInfo($url . $_REQUEST['package']); + if (!is_array($packageInfo)) + fatal_lang_error($packageInfo); + + // Use FTP if necessary. + create_chmod_control(array($boarddir . '/Packages/' . $package_name), array('destination_url' => $scripturl . '?action=admin;area=packages;get;sa=download' . (isset($_GET['server']) ? ';server=' . $_GET['server'] : '') . (isset($_REQUEST['auto']) ? ';auto' : '') . ';package=' . $_REQUEST['package'] . (isset($_REQUEST['conflict']) ? ';conflict' : '') . ';' . $context['session_var'] . '=' . $context['session_id'], 'crash_on_error' => true)); + package_put_contents($boarddir . '/Packages/' . $package_name, fetch_web_data($url . $_REQUEST['package'])); + + // Done! Did we get this package automatically? + if (preg_match('~^http://[\w_\-]+\.simplemachines\.org/~', $_REQUEST['package']) == 1 && strpos($_REQUEST['package'], 'dlattach') === false && isset($_REQUEST['auto'])) + redirectexit('action=admin;area=packages;sa=install;package=' . $package_name); + + // You just downloaded a mod from SERVER_NAME_GOES_HERE. + $context['package_server'] = $server; + + $context['package'] = getPackageInfo($package_name); + + if (!is_array($context['package'])) + fatal_lang_error('package_cant_download', false); + + if ($context['package']['type'] == 'modification') + $context['package']['install']['link'] = '[ ' . $txt['install_mod'] . ' ]'; + elseif ($context['package']['type'] == 'avatar') + $context['package']['install']['link'] = '[ ' . $txt['use_avatars'] . ' ]'; + elseif ($context['package']['type'] == 'language') + $context['package']['install']['link'] = '[ ' . $txt['add_languages'] . ' ]'; + else + $context['package']['install']['link'] = ''; + + $context['package']['list_files']['link'] = '[ ' . $txt['list_files'] . ' ]'; + + // Free a little bit of memory... + unset($context['package']['xml']); + + $context['page_title'] = $txt['download_success']; +} + +// Upload a new package to the directory. +function PackageUpload() +{ + global $txt, $scripturl, $boarddir, $context, $sourcedir; + + // Setup the correct template, even though I'll admit we ain't downloading ;) + $context['sub_template'] = 'downloaded'; + + // !!! TODO: Use FTP if the Packages directory is not writable. + + // Check the file was even sent! + if (!isset($_FILES['package']['name']) || $_FILES['package']['name'] == '') + fatal_lang_error('package_upload_error_nofile'); + elseif (!is_uploaded_file($_FILES['package']['tmp_name']) || (@ini_get('open_basedir') == '' && !file_exists($_FILES['package']['tmp_name']))) + fatal_lang_error('package_upload_error_failure'); + + // Make sure it has a sane filename. + $_FILES['package']['name'] = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $_FILES['package']['name']); + + if (strtolower(substr($_FILES['package']['name'], -4)) != '.zip' && strtolower(substr($_FILES['package']['name'], -4)) != '.tgz' && strtolower(substr($_FILES['package']['name'], -7)) != '.tar.gz') + fatal_lang_error('package_upload_error_supports', false, array('zip, tgz, tar.gz')); + + // We only need the filename... + $packageName = basename($_FILES['package']['name']); + + // Setup the destination and throw an error if the file is already there! + $destination = $boarddir . '/Packages/' . $packageName; + // !!! Maybe just roll it like we do for downloads? + if (file_exists($destination)) + fatal_lang_error('package_upload_error_exists'); + + // Now move the file. + move_uploaded_file($_FILES['package']['tmp_name'], $destination); + @chmod($destination, 0777); + + // If we got this far that should mean it's available. + $context['package'] = getPackageInfo($packageName); + $context['package_server'] = ''; + + // Not really a package, you lazy bum! + if (!is_array($context['package'])) + { + @unlink($destination); + loadLanguage('Errors'); + fatal_lang_error('package_upload_error_broken', false, $txt[$context['package']]); + } + // Is it already uploaded, maybe? + elseif ($dir = @opendir($boarddir . '/Packages')) + { + while ($package = readdir($dir)) + { + if ($package == '.' || $package == '..' || $package == 'temp' || $package == $packageName || (!(is_dir($boarddir . '/Packages/' . $package) && file_exists($boarddir . '/Packages/' . $package . '/package-info.xml')) && substr(strtolower($package), -7) != '.tar.gz' && substr(strtolower($package), -4) != '.tgz' && substr(strtolower($package), -4) != '.zip')) + continue; + + $packageInfo = getPackageInfo($package); + if (!is_array($packageInfo)) + continue; + + if ($packageInfo['id'] == $context['package']['id'] && $packageInfo['version'] == $context['package']['version']) + { + @unlink($destination); + loadLanguage('Errors'); + fatal_lang_error('package_upload_error_exists'); + } + } + closedir($dir); + } + + if ($context['package']['type'] == 'modification') + $context['package']['install']['link'] = '[ ' . $txt['install_mod'] . ' ]'; + elseif ($context['package']['type'] == 'avatar') + $context['package']['install']['link'] = '[ ' . $txt['use_avatars'] . ' ]'; + elseif ($context['package']['type'] == 'language') + $context['package']['install']['link'] = '[ ' . $txt['add_languages'] . ' ]'; + else + $context['package']['install']['link'] = ''; + + $context['package']['list_files']['link'] = '[ ' . $txt['list_files'] . ' ]'; + + unset($context['package']['xml']); + + $context['page_title'] = $txt['package_uploaded_success']; +} + +// Add a package server to the list. +function PackageServerAdd() +{ + global $smcFunc; + + // Validate the user. + checkSession(); + + // If they put a slash on the end, get rid of it. + if (substr($_POST['serverurl'], -1) == '/') + $_POST['serverurl'] = substr($_POST['serverurl'], 0, -1); + + // Are they both nice and clean? + $servername = trim($smcFunc['htmlspecialchars']($_POST['servername'])); + $serverurl = trim($smcFunc['htmlspecialchars']($_POST['serverurl'])); + + // Make sure the URL has the correct prefix. + if (strpos($serverurl, 'http://') !== 0 && strpos($serverurl, 'https://') !== 0) + $serverurl = 'http://' . $serverurl; + + $smcFunc['db_insert']('', + '{db_prefix}package_servers', + array( + 'name' => 'string-255', 'url' => 'string-255', + ), + array( + $servername, $serverurl, + ), + array('id_server') + ); + + redirectexit('action=admin;area=packages;get'); +} + +// Remove a server from the list. +function PackageServerRemove() +{ + global $smcFunc; + + checkSession('get'); + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}package_servers + WHERE id_server = {int:current_server}', + array( + 'current_server' => (int) $_GET['server'], + ) + ); + + redirectexit('action=admin;area=packages;get'); +} + +?> \ No newline at end of file diff --git a/Sources/Packages.php b/Sources/Packages.php new file mode 100644 index 0000000..8d26d3b --- /dev/null +++ b/Sources/Packages.php @@ -0,0 +1,2262 @@ + 'PackageBrowse', + 'remove' => 'PackageRemove', + 'list' => 'PackageList', + 'ftptest' => 'PackageFTPTest', + 'install' => 'PackageInstallTest', + 'install2' => 'PackageInstall', + 'uninstall' => 'PackageInstallTest', + 'uninstall2' => 'PackageInstall', + 'installed' => 'InstalledList', + 'options' => 'PackageOptions', + 'perms' => 'PackagePermissions', + 'flush' => 'FlushInstall', + 'examine' => 'ExamineFile', + 'showoperations' => 'ViewOperations', + ); + + // Work out exactly who it is we are calling. + if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']])) + $context['sub_action'] = $_REQUEST['sa']; + else + $context['sub_action'] = 'browse'; + + // Set up some tabs... + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['package_manager'], + // !!! 'help' => 'registrations', + 'description' => $txt['package_manager_desc'], + 'tabs' => array( + 'browse' => array( + ), + 'packageget' => array( + 'description' => $txt['download_packages_desc'], + ), + 'installed' => array( + 'description' => $txt['installed_packages_desc'], + ), + 'perms' => array( + 'description' => $txt['package_file_perms_desc'], + ), + 'options' => array( + 'description' => $txt['package_install_options_ftp_why'], + ), + ), + ); + + // Call the function we're handing control to. + $subActions[$context['sub_action']](); +} + +// Test install a package. +function PackageInstallTest() +{ + global $boarddir, $txt, $context, $scripturl, $sourcedir, $modSettings, $smcFunc, $settings; + + // You have to specify a file!! + if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '') + redirectexit('action=admin;area=packages'); + $context['filename'] = preg_replace('~[\.]+~', '.', $_REQUEST['package']); + + // Do we have an existing id, for uninstalls and the like. + $context['install_id'] = isset($_REQUEST['pid']) ? (int) $_REQUEST['pid'] : 0; + + require_once($sourcedir . '/Subs-Package.php'); + + // Load up the package FTP information? + create_chmod_control(); + + // Make sure temp directory exists and is empty. + if (file_exists($boarddir . '/Packages/temp')) + deltree($boarddir . '/Packages/temp', false); + + if (!mktree($boarddir . '/Packages/temp', 0755)) + { + deltree($boarddir . '/Packages/temp', false); + if (!mktree($boarddir . '/Packages/temp', 0777)) + { + deltree($boarddir . '/Packages/temp', false); + create_chmod_control(array($boarddir . '/Packages/temp/delme.tmp'), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=' . $_REQUEST['sa'] . ';package=' . $_REQUEST['package'], 'crash_on_error' => true)); + + deltree($boarddir . '/Packages/temp', false); + if (!mktree($boarddir . '/Packages/temp', 0777)) + fatal_lang_error('package_cant_download', false); + } + } + + $context['uninstalling'] = $_REQUEST['sa'] == 'uninstall'; + + // Change our last link tree item for more information on this Packages area. + $context['linktree'][count($context['linktree']) - 1] = array( + 'url' => $scripturl . '?action=admin;area=packages;sa=browse', + 'name' => $context['uninstalling'] ? $txt['package_uninstall_actions'] : $txt['install_actions'] + ); + $context['page_title'] .= ' - ' . ($context['uninstalling'] ? $txt['package_uninstall_actions'] : $txt['install_actions']); + + $context['sub_template'] = 'view_package'; + + if (!file_exists($boarddir . '/Packages/' . $context['filename'])) + { + deltree($boarddir . '/Packages/temp'); + fatal_lang_error('package_no_file', false); + } + + // Extract the files so we can get things like the readme, etc. + if (is_file($boarddir . '/Packages/' . $context['filename'])) + { + $context['extracted_files'] = read_tgz_file($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp'); + + if ($context['extracted_files'] && !file_exists($boarddir . '/Packages/temp/package-info.xml')) + foreach ($context['extracted_files'] as $file) + if (basename($file['filename']) == 'package-info.xml') + { + $context['base_path'] = dirname($file['filename']) . '/'; + break; + } + + if (!isset($context['base_path'])) + $context['base_path'] = ''; + } + elseif (is_dir($boarddir . '/Packages/' . $context['filename'])) + { + copytree($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp'); + $context['extracted_files'] = listtree($boarddir . '/Packages/temp'); + $context['base_path'] = ''; + } + else + fatal_lang_error('no_access', false); + + // Load up any custom themes we may want to install into... + $request = $smcFunc['db_query']('', ' + SELECT id_theme, variable, value + FROM {db_prefix}themes + WHERE (id_theme = {int:default_theme} OR id_theme IN ({array_int:known_theme_list})) + AND variable IN ({string:name}, {string:theme_dir})', + array( + 'known_theme_list' => explode(',', $modSettings['knownThemes']), + 'default_theme' => 1, + 'name' => 'name', + 'theme_dir' => 'theme_dir', + ) + ); + $theme_paths = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $theme_paths[$row['id_theme']][$row['variable']] = $row['value']; + $smcFunc['db_free_result']($request); + + // Get the package info... + $packageInfo = getPackageInfo($context['filename']); + + if (!is_array($packageInfo)) + fatal_lang_error($packageInfo); + + $packageInfo['filename'] = $context['filename']; + $context['package_name'] = isset($packageInfo['name']) ? $packageInfo['name'] : $context['filename']; + + // Set the type of extraction... + $context['extract_type'] = isset($packageInfo['type']) ? $packageInfo['type'] : 'modification'; + + // The mod isn't installed.... unless proven otherwise. + $context['is_installed'] = false; + + // See if it is installed? + $request = $smcFunc['db_query']('', ' + SELECT version, themes_installed, db_changes + FROM {db_prefix}log_packages + WHERE package_id = {string:current_package} + AND install_state != {int:not_installed} + ORDER BY time_installed DESC + LIMIT 1', + array( + 'not_installed' => 0, + 'current_package' => $packageInfo['id'], + ) + ); + + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $old_themes = explode(',', $row['themes_installed']); + $old_version = $row['version']; + $db_changes = empty($row['db_changes']) ? array() : unserialize($row['db_changes']); + } + $smcFunc['db_free_result']($request); + + $context['database_changes'] = array(); + if (!empty($db_changes)) + { + foreach ($db_changes as $change) + { + if (isset($change[2]) && isset($txt['package_db_' . $change[0]])) + $context['database_changes'][] = sprintf($txt['package_db_' . $change[0]], $change[1], $change[2]); + elseif (isset($txt['package_db_' . $change[0]])) + $context['database_changes'][] = sprintf($txt['package_db_' . $change[0]], $change[1]); + else + $context['database_changes'][] = $change[0] . '-' . $change[1] . (isset($change[2]) ? '-' . $change[2] : ''); + } + } + + // Uninstalling? + if ($context['uninstalling']) + { + // Wait, it's not installed yet! + if (!isset($old_version) && $context['uninstalling']) + { + deltree($boarddir . '/Packages/temp'); + fatal_lang_error('package_cant_uninstall', false); + } + + $actions = parsePackageInfo($packageInfo['xml'], true, 'uninstall'); + + // Gadzooks! There's no uninstaller at all!? + if (empty($actions)) + { + deltree($boarddir . '/Packages/temp'); + fatal_lang_error('package_uninstall_cannot', false); + } + + // Can't edit the custom themes it's edited if you're unisntalling, they must be removed. + $context['themes_locked'] = true; + + // Only let them uninstall themes it was installed into. + foreach ($theme_paths as $id => $data) + if ($id != 1 && !in_array($id, $old_themes)) + unset($theme_paths[$id]); + } + elseif (isset($old_version) && $old_version != $packageInfo['version']) + { + // Look for an upgrade... + $actions = parsePackageInfo($packageInfo['xml'], true, 'upgrade', $old_version); + + // There was no upgrade.... + if (empty($actions)) + $context['is_installed'] = true; + else + { + // Otherwise they can only upgrade themes from the first time around. + foreach ($theme_paths as $id => $data) + if ($id != 1 && !in_array($id, $old_themes)) + unset($theme_paths[$id]); + } + } + elseif (isset($old_version) && $old_version == $packageInfo['version']) + $context['is_installed'] = true; + + if (!isset($old_version) || $context['is_installed']) + $actions = parsePackageInfo($packageInfo['xml'], true, 'install'); + + $context['actions'] = array(); + $context['ftp_needed'] = false; + $context['has_failure'] = false; + $chmod_files = array(); + + if (empty($actions)) + return; + + // This will hold data about anything that can be installed in other themes. + $themeFinds = array( + 'candidates' => array(), + 'other_themes' => array(), + ); + + // Now prepare things for the template. + foreach ($actions as $action) + { + // Not failed until proven otherwise. + $failed = false; + + if ($action['type'] == 'chmod') + { + $chmod_files[] = $action['filename']; + continue; + } + elseif ($action['type'] == 'readme') + { + if (file_exists($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename'])) + $context['package_readme'] = htmlspecialchars(trim(file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), "\n\r")); + elseif (file_exists($action['filename'])) + $context['package_readme'] = htmlspecialchars(trim(file_get_contents($action['filename']), "\n\r")); + + if (!empty($action['parse_bbc'])) + { + require_once($sourcedir . '/Subs-Post.php'); + preparsecode($context['package_readme']); + $context['package_readme'] = parse_bbc($context['package_readme']); + } + else + $context['package_readme'] = nl2br($context['package_readme']); + + continue; + } + // Don't show redirects. + elseif ($action['type'] == 'redirect') + continue; + elseif ($action['type'] == 'error') + $context['has_failure'] = true; + elseif ($action['type'] == 'modification') + { + if (!file_exists($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename'])) + { + $context['has_failure'] = true; + + $context['actions'][] = array( + 'type' => $txt['execute_modification'], + 'action' => $smcFunc['htmlspecialchars'](strtr($action['filename'], array($boarddir => '.'))), + 'description' => $txt['package_action_error'], + 'failed' => true, + ); + } + + if ($action['boardmod']) + $mod_actions = parseBoardMod(@file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), true, $action['reverse'], $theme_paths); + else + $mod_actions = parseModification(@file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), true, $action['reverse'], $theme_paths); + + if (count($mod_actions) == 1 && isset($mod_actions[0]) && $mod_actions[0]['type'] == 'error' && $mod_actions[0]['filename'] == '-') + $mod_actions[0]['filename'] = $action['filename']; + + foreach ($mod_actions as $key => $mod_action) + { + // Lets get the last section of the file name. + if (isset($mod_action['filename']) && substr($mod_action['filename'], -13) != '.template.php') + $actual_filename = strtolower(substr(strrchr($mod_action['filename'], '/'), 1) . '||' . $action['filename']); + elseif (isset($mod_action['filename']) && preg_match('~([\w]*)/([\w]*)\.template\.php$~', $mod_action['filename'], $matches)) + $actual_filename = strtolower($matches[1] . '/' . $matches[2] . '.template.php' . '||' . $action['filename']); + else + $actual_filename = $key; + + if ($mod_action['type'] == 'opened') + $failed = false; + elseif ($mod_action['type'] == 'failure') + { + if (empty($mod_action['is_custom'])) + $context['has_failure'] = true; + $failed = true; + } + elseif ($mod_action['type'] == 'chmod') + { + $chmod_files[] = $mod_action['filename']; + } + elseif ($mod_action['type'] == 'saved') + { + if (!empty($mod_action['is_custom'])) + { + if (!isset($context['theme_actions'][$mod_action['is_custom']])) + $context['theme_actions'][$mod_action['is_custom']] = array( + 'name' => $theme_paths[$mod_action['is_custom']]['name'], + 'actions' => array(), + 'has_failure' => $failed, + ); + else + $context['theme_actions'][$mod_action['is_custom']]['has_failure'] |= $failed; + + $context['theme_actions'][$mod_action['is_custom']]['actions'][$actual_filename] = array( + 'type' => $txt['execute_modification'], + 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), + 'description' => $failed ? $txt['package_action_failure'] : $txt['package_action_success'], + 'failed' => $failed, + ); + } + elseif (!isset($context['actions'][$actual_filename])) + { + $context['actions'][$actual_filename] = array( + 'type' => $txt['execute_modification'], + 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), + 'description' => $failed ? $txt['package_action_failure'] : $txt['package_action_success'], + 'failed' => $failed, + ); + } + else + { + $context['actions'][$actual_filename]['failed'] |= $failed; + $context['actions'][$actual_filename]['description'] = $context['actions'][$actual_filename]['failed'] ? $txt['package_action_failure'] : $txt['package_action_success']; + } + } + elseif ($mod_action['type'] == 'skipping') + { + $context['actions'][$actual_filename] = array( + 'type' => $txt['execute_modification'], + 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), + 'description' => $txt['package_action_skipping'] + ); + } + elseif ($mod_action['type'] == 'missing' && empty($mod_action['is_custom'])) + { + $context['has_failure'] = true; + $context['actions'][$actual_filename] = array( + 'type' => $txt['execute_modification'], + 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), + 'description' => $txt['package_action_missing'], + 'failed' => true, + ); + } + elseif ($mod_action['type'] == 'error') + $context['actions'][$actual_filename] = array( + 'type' => $txt['execute_modification'], + 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), + 'description' => $txt['package_action_error'], + 'failed' => true, + ); + } + + // We need to loop again just to get the operations down correctly. + foreach ($mod_actions as $operation_key => $mod_action) + { + // Lets get the last section of the file name. + if (isset($mod_action['filename']) && substr($mod_action['filename'], -13) != '.template.php') + $actual_filename = strtolower(substr(strrchr($mod_action['filename'], '/'), 1) . '||' . $action['filename']); + elseif (isset($mod_action['filename']) && preg_match('~([\w]*)/([\w]*)\.template\.php$~', $mod_action['filename'], $matches)) + $actual_filename = strtolower($matches[1] . '/' . $matches[2] . '.template.php' . '||' . $action['filename']); + else + $actual_filename = $key; + + // We just need it for actual parse changes. + if (!in_array($mod_action['type'], array('error', 'result', 'opened', 'saved', 'end', 'missing', 'skipping', 'chmod'))) + { + if (empty($mod_action['is_custom'])) + $context['actions'][$actual_filename]['operations'][] = array( + 'type' => $txt['execute_modification'], + 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), + 'description' => $mod_action['failed'] ? $txt['package_action_failure'] : $txt['package_action_success'], + 'position' => $mod_action['position'], + 'operation_key' => $operation_key, + 'filename' => $action['filename'], + 'is_boardmod' => $action['boardmod'], + 'failed' => $mod_action['failed'], + 'ignore_failure' => !empty($mod_action['ignore_failure']), + ); + + // Themes are under the saved type. + if (isset($mod_action['is_custom']) && isset($context['theme_actions'][$mod_action['is_custom']])) + $context['theme_actions'][$mod_action['is_custom']]['actions'][$actual_filename]['operations'][] = array( + 'type' => $txt['execute_modification'], + 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), + 'description' => $mod_action['failed'] ? $txt['package_action_failure'] : $txt['package_action_success'], + 'position' => $mod_action['position'], + 'operation_key' => $operation_key, + 'filename' => $action['filename'], + 'is_boardmod' => $action['boardmod'], + 'failed' => $mod_action['failed'], + 'ignore_failure' => !empty($mod_action['ignore_failure']), + ); + } + } + + // Don't add anything else. + $thisAction = array(); + } + elseif ($action['type'] == 'code') + $thisAction = array( + 'type' => $txt['execute_code'], + 'action' => $smcFunc['htmlspecialchars']($action['filename']), + ); + elseif ($action['type'] == 'database') + { + $thisAction = array( + 'type' => $txt['execute_database_changes'], + 'action' => $smcFunc['htmlspecialchars']($action['filename']), + ); + } + elseif (in_array($action['type'], array('create-dir', 'create-file'))) + $thisAction = array( + 'type' => $txt['package_create'] . ' ' . ($action['type'] == 'create-dir' ? $txt['package_tree'] : $txt['package_file']), + 'action' => $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.'))) + ); + elseif (in_array($action['type'], array('require-dir', 'require-file'))) + { + // Do this one... + $thisAction = array( + 'type' => $txt['package_extract'] . ' ' . ($action['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']), + 'action' => $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.'))) + ); + + // Could this be theme related? + if (!empty($action['unparsed_destination']) && preg_match('~^\$(languagedir|languages_dir|imagesdir|themedir|themes_dir)~i', $action['unparsed_destination'], $matches)) + { + // Is the action already stated? + $theme_action = !empty($action['theme_action']) && in_array($action['theme_action'], array('no', 'yes', 'auto')) ? $action['theme_action'] : 'auto'; + // If it's not auto do we think we have something we can act upon? + if ($theme_action != 'auto' && !in_array($matches[1], array('languagedir', 'languages_dir', 'imagesdir', 'themedir'))) + $theme_action = ''; + // ... or if it's auto do we even want to do anything? + elseif ($theme_action == 'auto' && $matches[1] != 'imagesdir') + $theme_action = ''; + + // So, we still want to do something? + if ($theme_action != '') + $themeFinds['candidates'][] = $action; + // Otherwise is this is going into another theme record it. + elseif ($matches[1] == 'themes_dir') + $themeFinds['other_themes'][] = strtolower(strtr(parse_path($action['unparsed_destination']), array('\\' => '/')) . '/' . basename($action['filename'])); + } + } + elseif (in_array($action['type'], array('move-dir', 'move-file'))) + $thisAction = array( + 'type' => $txt['package_move'] . ' ' . ($action['type'] == 'move-dir' ? $txt['package_tree'] : $txt['package_file']), + 'action' => $smcFunc['htmlspecialchars'](strtr($action['source'], array($boarddir => '.'))) . ' => ' . $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.'))) + ); + elseif (in_array($action['type'], array('remove-dir', 'remove-file'))) + { + $thisAction = array( + 'type' => $txt['package_delete'] . ' ' . ($action['type'] == 'remove-dir' ? $txt['package_tree'] : $txt['package_file']), + 'action' => $smcFunc['htmlspecialchars'](strtr($action['filename'], array($boarddir => '.'))) + ); + + // Could this be theme related? + if (!empty($action['unparsed_filename']) && preg_match('~^\$(languagedir|languages_dir|imagesdir|themedir|themes_dir)~i', $action['unparsed_filename'], $matches)) + { + + // Is the action already stated? + $theme_action = !empty($action['theme_action']) && in_array($action['theme_action'], array('no', 'yes', 'auto')) ? $action['theme_action'] : 'auto'; + $action['unparsed_destination'] = $action['unparsed_filename']; + // If it's not auto do we think we have something we can act upon? + if ($theme_action != 'auto' && !in_array($matches[1], array('languagedir', 'languages_dir', 'imagesdir', 'themedir'))) + $theme_action = ''; + // ... or if it's auto do we even want to do anything? + elseif ($theme_action == 'auto' && $matches[1] != 'imagesdir') + $theme_action = ''; + + // So, we still want to do something? + if ($theme_action != '') + $themeFinds['candidates'][] = $action; + // Otherwise is this is going into another theme record it. + elseif ($matches[1] == 'themes_dir') + $themeFinds['other_themes'][] = strtolower(strtr(parse_path($action['unparsed_filename']), array('\\' => '/')) . '/' . basename($action['filename'])); + } + } + + if (empty($thisAction)) + continue; + + // !!! None given? + $thisAction['description'] = isset($action['description']) ? $action['description'] : ''; + $context['actions'][] = $thisAction; + } + + // Have we got some things which we might want to do "multi-theme"? + if (!empty($themeFinds['candidates'])) + { + foreach ($themeFinds['candidates'] as $action_data) + { + // Get the part of the file we'll be dealing with. + preg_match('~^\$(languagedir|languages_dir|imagesdir|themedir)(\\|/)*(.+)*~i', $action_data['unparsed_destination'], $matches); + + if ($matches[1] == 'imagesdir') + $path = '/' . basename($settings['default_images_url']); + elseif ($matches[1] == 'languagedir' || $matches[1] == 'languages_dir') + $path = '/languages'; + else + $path = ''; + + if (!empty($matches[3])) + $path .= $matches[3]; + + if (!$context['uninstalling']) + $path .= '/' . basename($action_data['filename']); + + // Loop through each custom theme to note it's candidacy! + foreach ($theme_paths as $id => $theme_data) + { + if (isset($theme_data['theme_dir']) && $id != 1) + { + $real_path = $theme_data['theme_dir'] . $path; + // Confirm that we don't already have this dealt with by another entry. + if (!in_array(strtolower(strtr($real_path, array('\\' => '/'))), $themeFinds['other_themes'])) + { + // Check if we will need to chmod this. + if (!mktree(dirname($real_path), false)) + { + $temp = dirname($real_path); + while (!file_exists($temp) && strlen($temp) > 1) + $temp = dirname($temp); + $chmod_files[] = $temp; + } + if ($action_data['type'] == 'require-dir' && !is_writable($real_path) && (file_exists($real_path) || !is_writable(dirname($real_path)))) + $chmod_files[] = $real_path; + + if (!isset($context['theme_actions'][$id])) + $context['theme_actions'][$id] = array( + 'name' => $theme_data['name'], + 'actions' => array(), + ); + + if ($context['uninstalling']) + $context['theme_actions'][$id]['actions'][] = array( + 'type' => $txt['package_delete'] . ' ' . ($action_data['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']), + 'action' => strtr($real_path, array('\\' => '/', $boarddir => '.')), + 'description' => '', + 'value' => base64_encode(serialize(array('type' => $action_data['type'], 'orig' => $action_data['filename'], 'future' => $real_path, 'id' => $id))), + 'not_mod' => true, + ); + else + $context['theme_actions'][$id]['actions'][] = array( + 'type' => $txt['package_extract'] . ' ' . ($action_data['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']), + 'action' => strtr($real_path, array('\\' => '/', $boarddir => '.')), + 'description' => '', + 'value' => base64_encode(serialize(array('type' => $action_data['type'], 'orig' => $action_data['destination'], 'future' => $real_path, 'id' => $id))), + 'not_mod' => true, + ); + } + } + } + } + } + + // Trash the cache... which will also check permissions for us! + package_flush_cache(true); + + if (file_exists($boarddir . '/Packages/temp')) + deltree($boarddir . '/Packages/temp'); + + if (!empty($chmod_files)) + { + $ftp_status = create_chmod_control($chmod_files); + $context['ftp_needed'] = !empty($ftp_status['files']['notwritable']) && !empty($context['package_ftp']); + } + + checkSubmitOnce('register'); +} + +// Apply another type of (avatar, language, etc.) package. +function PackageInstall() +{ + global $boarddir, $txt, $context, $boardurl, $scripturl, $sourcedir, $modSettings; + global $user_info, $smcFunc; + + // Make sure we don't install this mod twice. + checkSubmitOnce('check'); + checkSession(); + + // If there's no file, what are we installing? + if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '') + redirectexit('action=admin;area=packages'); + $context['filename'] = $_REQUEST['package']; + + // If this is an uninstall, we'll have an id. + $context['install_id'] = isset($_REQUEST['pid']) ? (int) $_REQUEST['pid'] : 0; + + require_once($sourcedir . '/Subs-Package.php'); + + // !!! TODO: Perhaps do it in steps, if necessary? + + $context['uninstalling'] = $_REQUEST['sa'] == 'uninstall2'; + + // Set up the linktree for other. + $context['linktree'][count($context['linktree']) - 1] = array( + 'url' => $scripturl . '?action=admin;area=packages;sa=browse', + 'name' => $context['uninstalling'] ? $txt['uninstall'] : $txt['extracting'] + ); + $context['page_title'] .= ' - ' . ($context['uninstalling'] ? $txt['uninstall'] : $txt['extracting']); + + $context['sub_template'] = 'extract_package'; + + if (!file_exists($boarddir . '/Packages/' . $context['filename'])) + fatal_lang_error('package_no_file', false); + + // Load up the package FTP information? + create_chmod_control(array(), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=' . $_REQUEST['sa'] . ';package=' . $_REQUEST['package'])); + + // Make sure temp directory exists and is empty! + if (file_exists($boarddir . '/Packages/temp')) + deltree($boarddir . '/Packages/temp', false); + else + mktree($boarddir . '/Packages/temp', 0777); + + // Let the unpacker do the work. + if (is_file($boarddir . '/Packages/' . $context['filename'])) + { + $context['extracted_files'] = read_tgz_file($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp'); + + if (!file_exists($boarddir . '/Packages/temp/package-info.xml')) + foreach ($context['extracted_files'] as $file) + if (basename($file['filename']) == 'package-info.xml') + { + $context['base_path'] = dirname($file['filename']) . '/'; + break; + } + + if (!isset($context['base_path'])) + $context['base_path'] = ''; + } + elseif (is_dir($boarddir . '/Packages/' . $context['filename'])) + { + copytree($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp'); + $context['extracted_files'] = listtree($boarddir . '/Packages/temp'); + $context['base_path'] = ''; + } + else + fatal_lang_error('no_access', false); + + // Are we installing this into any custom themes? + $custom_themes = array(1); + $known_themes = explode(',', $modSettings['knownThemes']); + if (!empty($_POST['custom_theme'])) + { + foreach ($_POST['custom_theme'] as $tid) + if (in_array($tid, $known_themes)) + $custom_themes[] = (int) $tid; + } + + // Now load up the paths of the themes that we need to know about. + $request = $smcFunc['db_query']('', ' + SELECT id_theme, variable, value + FROM {db_prefix}themes + WHERE id_theme IN ({array_int:custom_themes}) + AND variable IN ({string:name}, {string:theme_dir})', + array( + 'custom_themes' => $custom_themes, + 'name' => 'name', + 'theme_dir' => 'theme_dir', + ) + ); + $theme_paths = array(); + $themes_installed = array(1); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $theme_paths[$row['id_theme']][$row['variable']] = $row['value']; + $smcFunc['db_free_result']($request); + + // Are there any theme copying that we want to take place? + $context['theme_copies'] = array( + 'require-file' => array(), + 'require-dir' => array(), + ); + if (!empty($_POST['theme_changes'])) + { + foreach ($_POST['theme_changes'] as $change) + { + if (empty($change)) + continue; + $theme_data = unserialize(base64_decode($change)); + if (empty($theme_data['type'])) + continue; + + $themes_installed[] = $theme_data['id']; + $context['theme_copies'][$theme_data['type']][$theme_data['orig']][] = $theme_data['future']; + } + } + + // Get the package info... + $packageInfo = getPackageInfo($context['filename']); + if (!is_array($packageInfo)) + fatal_lang_error($packageInfo); + + $packageInfo['filename'] = $context['filename']; + + // Set the type of extraction... + $context['extract_type'] = isset($packageInfo['type']) ? $packageInfo['type'] : 'modification'; + + // Create a backup file to roll back to! (but if they do this more than once, don't run it a zillion times.) + if (!empty($modSettings['package_make_backups']) && (!isset($_SESSION['last_backup_for']) || $_SESSION['last_backup_for'] != $context['filename'] . ($context['uninstalling'] ? '$$' : '$'))) + { + $_SESSION['last_backup_for'] = $context['filename'] . ($context['uninstalling'] ? '$$' : '$'); + // !!! Internationalize this? + package_create_backup(($context['uninstalling'] ? 'backup_' : 'before_') . strtok($context['filename'], '.')); + } + + // The mod isn't installed.... unless proven otherwise. + $context['is_installed'] = false; + + // Is it actually installed? + $request = $smcFunc['db_query']('', ' + SELECT version, themes_installed, db_changes + FROM {db_prefix}log_packages + WHERE package_id = {string:current_package} + AND install_state != {int:not_installed} + ORDER BY time_installed DESC + LIMIT 1', + array( + 'not_installed' => 0, + 'current_package' => $packageInfo['id'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $old_themes = explode(',', $row['themes_installed']); + $old_version = $row['version']; + $db_changes = empty($row['db_changes']) ? array() : unserialize($row['db_changes']); + } + $smcFunc['db_free_result']($request); + + // Wait, it's not installed yet! + // !!! TODO: Replace with a better error message! + if (!isset($old_version) && $context['uninstalling']) + { + deltree($boarddir . '/Packages/temp'); + fatal_error('Hacker?', false); + } + // Uninstalling? + elseif ($context['uninstalling']) + { + $install_log = parsePackageInfo($packageInfo['xml'], false, 'uninstall'); + + // Gadzooks! There's no uninstaller at all!? + if (empty($install_log)) + fatal_lang_error('package_uninstall_cannot', false); + + // They can only uninstall from what it was originally installed into. + foreach ($theme_paths as $id => $data) + if ($id != 1 && !in_array($id, $old_themes)) + unset($theme_paths[$id]); + } + elseif (isset($old_version) && $old_version != $packageInfo['version']) + { + // Look for an upgrade... + $install_log = parsePackageInfo($packageInfo['xml'], false, 'upgrade', $old_version); + + // There was no upgrade.... + if (empty($install_log)) + $context['is_installed'] = true; + else + { + // Upgrade previous themes only! + foreach ($theme_paths as $id => $data) + if ($id != 1 && !in_array($id, $old_themes)) + unset($theme_paths[$id]); + } + } + elseif (isset($old_version) && $old_version == $packageInfo['version']) + $context['is_installed'] = true; + + if (!isset($old_version) || $context['is_installed']) + $install_log = parsePackageInfo($packageInfo['xml'], false, 'install'); + + $context['install_finished'] = false; + + // !!! TODO: Make a log of any errors that occurred and output them? + + if (!empty($install_log)) + { + $failed_steps = array(); + $failed_count = 0; + + foreach ($install_log as $action) + { + $failed_count++; + + if ($action['type'] == 'modification' && !empty($action['filename'])) + { + if ($action['boardmod']) + $mod_actions = parseBoardMod(file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths); + else + $mod_actions = parseModification(file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths); + + // Any errors worth noting? + foreach ($mod_actions as $key => $action) + { + if ($action['type'] == 'failure') + $failed_steps[] = array( + 'file' => $action['filename'], + 'large_step' => $failed_count, + 'sub_step' => $key, + 'theme' => 1, + ); + + // Gather the themes we installed into. + if (!empty($action['is_custom'])) + $themes_installed[] = $action['is_custom']; + } + } + elseif ($action['type'] == 'code' && !empty($action['filename'])) + { + // This is just here as reference for what is available. + global $txt, $boarddir, $sourcedir, $modSettings, $context, $settings, $forum_version, $smcFunc; + + // Now include the file and be done with it ;). + require($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']); + } + // Only do the database changes on uninstall if requested. + elseif ($action['type'] == 'database' && !empty($action['filename']) && (!$context['uninstalling'] || !empty($_POST['do_db_changes']))) + { + // These can also be there for database changes. + global $txt, $boarddir, $sourcedir, $modSettings, $context, $settings, $forum_version, $smcFunc; + global $db_package_log; + + // We'll likely want the package specific database functionality! + db_extend('packages'); + + // Let the file work its magic ;) + require($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']); + } + // Handle a redirect... + elseif ($action['type'] == 'redirect' && !empty($action['redirect_url'])) + { + $context['redirect_url'] = $action['redirect_url']; + $context['redirect_text'] = !empty($action['filename']) && file_exists($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']) ? file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']) : ($context['uninstalling'] ? $txt['package_uninstall_done'] : $txt['package_installed_done']); + $context['redirect_timeout'] = $action['redirect_timeout']; + + // Parse out a couple of common urls. + $urls = array( + '$boardurl' => $boardurl, + '$scripturl' => $scripturl, + '$session_var' => $context['session_var'], + '$session_id' => $context['session_id'], + ); + + $context['redirect_url'] = strtr($context['redirect_url'], $urls); + } + } + + package_flush_cache(); + + // First, ensure this change doesn't get removed by putting a stake in the ground (So to speak). + package_put_contents($boarddir . '/Packages/installed.list', time()); + + // See if this is already installed, and change it's state as required. + $request = $smcFunc['db_query']('', ' + SELECT package_id, install_state, db_changes + FROM {db_prefix}log_packages + WHERE install_state != {int:not_installed} + AND package_id = {string:current_package} + ' . ($context['install_id'] ? ' AND id_install = {int:install_id} ' : '') . ' + ORDER BY time_installed DESC + LIMIT 1', + array( + 'not_installed' => 0, + 'install_id' => $context['install_id'], + 'current_package' => $packageInfo['id'], + ) + ); + $is_upgrade = false; + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Uninstalling? + if ($context['uninstalling']) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_packages + SET install_state = {int:not_installed}, member_removed = {string:member_name}, id_member_removed = {int:current_member}, + time_removed = {int:current_time} + WHERE package_id = {string:package_id}', + array( + 'current_member' => $user_info['id'], + 'not_installed' => 0, + 'current_time' => time(), + 'package_id' => $row['package_id'], + 'member_name' => $user_info['name'], + ) + ); + } + // Otherwise must be an upgrade. + else + { + $is_upgrade = true; + $old_db_changes = empty($row['db_changes']) ? array() : unserialize($row['db_changes']); + } + } + + // Assuming we're not uninstalling, add the entry. + if (!$context['uninstalling']) + { + // Any db changes from older version? + if (!empty($old_db_changes)) + $db_package_log = empty($db_package_log) ? $old_db_changes : array_merge($old_db_changes, $db_package_log); + + // If there are some database changes we might want to remove then filter them out. + if (!empty($db_package_log)) + { + // We're really just checking for entries which are create table AND add columns (etc). + $tables = array(); + function sort_table_first($a, $b) + { + if ($a[0] == $b[0]) + return 0; + return $a[0] == 'remove_table' ? -1 : 1; + } + usort($db_package_log, 'sort_table_first'); + foreach ($db_package_log as $k => $log) + { + if ($log[0] == 'remove_table') + $tables[] = $log[1]; + elseif (in_array($log[1], $tables)) + unset($db_package_log[$k]); + } + $db_changes = serialize($db_package_log); + } + else + $db_changes = ''; + + // What themes did we actually install? + $themes_installed = array_unique($themes_installed); + $themes_installed = implode(',', $themes_installed); + + // What failed steps? + $failed_step_insert = serialize($failed_steps); + + $smcFunc['db_insert']('', + '{db_prefix}log_packages', + array( + 'filename' => 'string', 'name' => 'string', 'package_id' => 'string', 'version' => 'string', + 'id_member_installed' => 'int', 'member_installed' => 'string','time_installed' => 'int', + 'install_state' => 'int', 'failed_steps' => 'string', 'themes_installed' => 'string', + 'member_removed' => 'int', 'db_changes' => 'string', + ), + array( + $packageInfo['filename'], $packageInfo['name'], $packageInfo['id'], $packageInfo['version'], + $user_info['id'], $user_info['name'], time(), + $is_upgrade ? 2 : 1, $failed_step_insert, $themes_installed, + 0, $db_changes, + ), + array('id_install') + ); + } + $smcFunc['db_free_result']($request); + + $context['install_finished'] = true; + } + + // If there's database changes - and they want them removed - let's do it last! + if (!empty($db_changes) && !empty($_POST['do_db_changes'])) + { + // We're gonna be needing the package db functions! + db_extend('packages'); + + foreach ($db_changes as $change) + { + if ($change[0] == 'remove_table' && isset($change[1])) + $smcFunc['db_drop_table']($change[1]); + elseif ($change[0] == 'remove_column' && isset($change[2])) + $smcFunc['db_remove_column']($change[1], $change[2]); + elseif ($change[0] == 'remove_index' && isset($change[2])) + $smcFunc['db_remove_index']($change[1], $change[2]); + } + } + + // Clean house... get rid of the evidence ;). + if (file_exists($boarddir . '/Packages/temp')) + deltree($boarddir . '/Packages/temp'); + + // Log what we just did. + logAction($context['uninstalling'] ? 'uninstall_package' : (!empty($is_upgrade) ? 'upgrade_package' : 'install_package'), array('package' => $smcFunc['htmlspecialchars']($packageInfo['name']), 'version' => $smcFunc['htmlspecialchars']($packageInfo['version'])), 'admin'); + + // Just in case, let's clear the whole cache to avoid anything going up the swanny. + clean_cache(); + + // Restore file permissions? + create_chmod_control(array(), array(), true); +} + +// List the files in a package. +function PackageList() +{ + global $txt, $scripturl, $boarddir, $context, $sourcedir; + + require_once($sourcedir . '/Subs-Package.php'); + + // No package? Show him or her the door. + if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '') + redirectexit('action=admin;area=packages'); + + $context['linktree'][] = array( + 'url' => $scripturl . '?action=admin;area=packages;sa=list;package=' . $_REQUEST['package'], + 'name' => $txt['list_file'] + ); + $context['page_title'] .= ' - ' . $txt['list_file']; + $context['sub_template'] = 'list'; + + // The filename... + $context['filename'] = $_REQUEST['package']; + + // Let the unpacker do the work. + if (is_file($boarddir . '/Packages/' . $context['filename'])) + $context['files'] = read_tgz_file($boarddir . '/Packages/' . $context['filename'], null); + elseif (is_dir($boarddir . '/Packages/' . $context['filename'])) + $context['files'] = listtree($boarddir . '/Packages/' . $context['filename']); +} + +// List the files in a package. +function ExamineFile() +{ + global $txt, $scripturl, $boarddir, $context, $sourcedir; + + require_once($sourcedir . '/Subs-Package.php'); + + // No package? Show him or her the door. + if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '') + redirectexit('action=admin;area=packages'); + + // No file? Show him or her the door. + if (!isset($_REQUEST['file']) || $_REQUEST['file'] == '') + redirectexit('action=admin;area=packages'); + + $_REQUEST['package'] = preg_replace('~[\.]+~', '.', strtr($_REQUEST['package'], array('/' => '_', '\\' => '_'))); + $_REQUEST['file'] = preg_replace('~[\.]+~', '.', $_REQUEST['file']); + + if (isset($_REQUEST['raw'])) + { + if (is_file($boarddir . '/Packages/' . $_REQUEST['package'])) + echo read_tgz_file($boarddir . '/Packages/' . $_REQUEST['package'], $_REQUEST['file'], true); + elseif (is_dir($boarddir . '/Packages/' . $_REQUEST['package'])) + echo file_get_contents($boarddir . '/Packages/' . $_REQUEST['package'] . '/' . $_REQUEST['file']); + + obExit(false); + } + + $context['linktree'][count($context['linktree']) - 1] = array( + 'url' => $scripturl . '?action=admin;area=packages;sa=list;package=' . $_REQUEST['package'], + 'name' => $txt['package_examine_file'] + ); + $context['page_title'] .= ' - ' . $txt['package_examine_file']; + $context['sub_template'] = 'examine'; + + // The filename... + $context['package'] = $_REQUEST['package']; + $context['filename'] = $_REQUEST['file']; + + // Let the unpacker do the work.... but make sure we handle images properly. + if (in_array(strtolower(strrchr($_REQUEST['file'], '.')), array('.bmp', '.gif', '.jpeg', '.jpg', '.png'))) + $context['filedata'] = '' . $_REQUEST['file'] . ''; + else + { + if (is_file($boarddir . '/Packages/' . $_REQUEST['package'])) + $context['filedata'] = htmlspecialchars(read_tgz_file($boarddir . '/Packages/' . $_REQUEST['package'], $_REQUEST['file'], true)); + elseif (is_dir($boarddir . '/Packages/' . $_REQUEST['package'])) + $context['filedata'] = htmlspecialchars(file_get_contents($boarddir . '/Packages/' . $_REQUEST['package'] . '/' . $_REQUEST['file'])); + + if (strtolower(strrchr($_REQUEST['file'], '.')) == '.php') + $context['filedata'] = highlight_php_code($context['filedata']); + } +} + +// List the installed packages. +function InstalledList() +{ + global $txt, $scripturl, $context; + + $context['page_title'] .= ' - ' . $txt['installed_packages']; + $context['sub_template'] = 'view_installed'; + + // Load the installed mods and send them to the template. + $context['installed_mods'] = loadInstalledPackages(); +} + +// Empty out the installed list. +function FlushInstall() +{ + global $boarddir, $sourcedir, $smcFunc; + + // Always check the session. + checkSession('get'); + + include_once($sourcedir . '/Subs-Package.php'); + + // Record when we last did this. + package_put_contents($boarddir . '/Packages/installed.list', time()); + + // Set everything as uninstalled. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_packages + SET install_state = {int:not_installed}', + array( + 'not_installed' => 0, + ) + ); + + redirectexit('action=admin;area=packages;sa=installed'); +} + +// Delete a package. +function PackageRemove() +{ + global $scripturl, $boarddir; + + // Check it. + checkSession('get'); + + // Ack, don't allow deletion of arbitrary files here, could become a security hole somehow! + if (!isset($_GET['package']) || $_GET['package'] == 'index.php' || $_GET['package'] == 'installed.list' || $_GET['package'] == 'backups') + redirectexit('action=admin;area=packages;sa=browse'); + $_GET['package'] = preg_replace('~[\.]+~', '.', strtr($_GET['package'], array('/' => '_', '\\' => '_'))); + + // Can't delete what's not there. + if (file_exists($boarddir . '/Packages/' . $_GET['package']) && (substr($_GET['package'], -4) == '.zip' || substr($_GET['package'], -4) == '.tgz' || substr($_GET['package'], -7) == '.tar.gz' || is_dir($boarddir . '/Packages/' . $_GET['package'])) && $_GET['package'] != 'backups' && substr($_GET['package'], 0, 1) != '.') + { + create_chmod_control(array($boarddir . '/Packages/' . $_GET['package']), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=remove;package=' . $_GET['package'], 'crash_on_error' => true)); + + if (is_dir($boarddir . '/Packages/' . $_GET['package'])) + deltree($boarddir . '/Packages/' . $_GET['package']); + else + { + @chmod($boarddir . '/Packages/' . $_GET['package'], 0777); + unlink($boarddir . '/Packages/' . $_GET['package']); + } + } + + redirectexit('action=admin;area=packages;sa=browse'); +} + +// Browse a list of installed packages. +function PackageBrowse() +{ + global $txt, $boarddir, $scripturl, $context, $forum_version; + + $context['page_title'] .= ' - ' . $txt['browse_packages']; + $context['sub_template'] = 'browse'; + + $context['forum_version'] = $forum_version; + + $instmods = loadInstalledPackages(); + + $installed_mods = array(); + // Look through the list of installed mods... + foreach ($instmods as $installed_mod) + $installed_mods[$installed_mod['package_id']] = array( + 'id' => $installed_mod['id'], + 'version' => $installed_mod['version'], + ); + + $the_version = strtr($forum_version, array('SMF ' => '')); + + // Here we have a little code to help those who class themselves as something of gods, version emulation ;) + if (isset($_GET['version_emulate'])) + { + if ($_GET['version_emulate'] === 0 && isset($_SESSION['version_emulate'])) + unset($_SESSION['version_emulate']); + elseif ($_GET['version_emulate'] !== 0) + $_SESSION['version_emulate'] = strtr($_GET['version_emulate'], array('-' => ' ', '+' => ' ', 'SMF ' => '')); + } + if (!empty($_SESSION['version_emulate'])) + { + $context['forum_version'] = 'SMF ' . $_SESSION['version_emulate']; + $the_version = $_SESSION['version_emulate']; + } + + // Get a list of all the ids installed, so the latest packages won't include already installed ones. + $context['installed_mods'] = array_keys($installed_mods); + + // Empty lists for now. + $context['available_mods'] = array(); + $context['available_avatars'] = array(); + $context['available_languages'] = array(); + $context['available_other'] = array(); + $context['available_all'] = array(); + + // We need the packages directory to be writable for this. + if (!@is_writable($boarddir . '/Packages')) + create_chmod_control(array($boarddir . '/Packages'), array('destination_url' => $scripturl . '?action=admin;area=packages', 'crash_on_error' => true)); + + if ($dir = @opendir($boarddir . '/Packages')) + { + $dirs = array(); + while ($package = readdir($dir)) + { + if ($package == '.' || $package == '..' || $package == 'temp' || (!(is_dir($boarddir . '/Packages/' . $package) && file_exists($boarddir . '/Packages/' . $package . '/package-info.xml')) && substr(strtolower($package), -7) != '.tar.gz' && substr(strtolower($package), -4) != '.tgz' && substr(strtolower($package), -4) != '.zip')) + continue; + + // Skip directories or files that are named the same. + if (is_dir($boarddir . '/Packages/' . $package)) + { + if (in_array($package, $dirs)) + continue; + $dirs[] = $package; + } + elseif (substr(strtolower($package), -7) == '.tar.gz') + { + if (in_array(substr($package, 0, -7), $dirs)) + continue; + $dirs[] = substr($package, 0, -7); + } + elseif (substr(strtolower($package), -4) == '.zip' || substr(strtolower($package), -4) == '.tgz') + { + if (in_array(substr($package, 0, -4), $dirs)) + continue; + $dirs[] = substr($package, 0, -4); + } + + $packageInfo = getPackageInfo($package); + if (!is_array($packageInfo)) + continue; + + $packageInfo['installed_id'] = isset($installed_mods[$packageInfo['id']]) ? $installed_mods[$packageInfo['id']]['id'] : 0; + + $packageInfo['is_installed'] = isset($installed_mods[$packageInfo['id']]); + $packageInfo['is_current'] = $packageInfo['is_installed'] && ($installed_mods[$packageInfo['id']]['version'] == $packageInfo['version']); + $packageInfo['is_newer'] = $packageInfo['is_installed'] && ($installed_mods[$packageInfo['id']]['version'] > $packageInfo['version']); + + $packageInfo['can_install'] = false; + $packageInfo['can_uninstall'] = false; + $packageInfo['can_upgrade'] = false; + + // This package is currently NOT installed. Check if it can be. + if (!$packageInfo['is_installed'] && $packageInfo['xml']->exists('install')) + { + // Check if there's an install for *THIS* version of SMF. + $installs = $packageInfo['xml']->set('install'); + foreach ($installs as $install) + { + if (!$install->exists('@for') || matchPackageVersion($the_version, $install->fetch('@for'))) + { + // Okay, this one is good to go. + $packageInfo['can_install'] = true; + break; + } + } + } + // An already installed, but old, package. Can we upgrade it? + elseif ($packageInfo['is_installed'] && !$packageInfo['is_current'] && $packageInfo['xml']->exists('upgrade')) + { + $upgrades = $packageInfo['xml']->set('upgrade'); + + // First go through, and check against the current version of SMF. + foreach ($upgrades as $upgrade) + { + // Even if it is for this SMF, is it for the installed version of the mod? + if (!$upgrade->exists('@for') || matchPackageVersion($the_version, $upgrade->fetch('@for'))) + if (!$upgrade->exists('@from') || matchPackageVersion($installed_mods[$packageInfo['id']]['version'], $upgrade->fetch('@from'))) + { + $packageInfo['can_upgrade'] = true; + break; + } + } + } + // Note that it has to be the current version to be uninstallable. Shucks. + elseif ($packageInfo['is_installed'] && $packageInfo['is_current'] && $packageInfo['xml']->exists('uninstall')) + { + $uninstalls = $packageInfo['xml']->set('uninstall'); + + // Can we find any uninstallation methods that work for this SMF version? + foreach ($uninstalls as $uninstall) + if (!$uninstall->exists('@for') || matchPackageVersion($the_version, $uninstall->fetch('@for'))) + { + $packageInfo['can_uninstall'] = true; + break; + } + } + + // Store a complete list. + $context['available_all'][] = $packageInfo; + + // Modification. + if ($packageInfo['type'] == 'modification' || $packageInfo['type'] == 'mod') + $context['available_mods'][] = $packageInfo; + // Avatar package. + elseif ($packageInfo['type'] == 'avatar') + $context['available_avatars'][] = $packageInfo; + // Language package. + elseif ($packageInfo['type'] == 'language') + $context['available_languages'][] = $packageInfo; + // Other stuff. + else + $context['available_other'][] = $packageInfo; + } + closedir($dir); + } +} + +function PackageOptions() +{ + global $txt, $scripturl, $context, $sourcedir, $modSettings, $smcFunc; + + if (isset($_POST['submit'])) + { + checkSession('post'); + + updateSettings(array( + 'package_server' => trim($smcFunc['htmlspecialchars']($_POST['pack_server'])), + 'package_port' => trim($smcFunc['htmlspecialchars']($_POST['pack_port'])), + 'package_username' => trim($smcFunc['htmlspecialchars']($_POST['pack_user'])), + 'package_make_backups' => !empty($_POST['package_make_backups']) + )); + + redirectexit('action=admin;area=packages;sa=options'); + } + + if (preg_match('~^/home/([^/]+?)/public_html~', $_SERVER['DOCUMENT_ROOT'], $match)) + $default_username = $match[1]; + else + $default_username = ''; + + $context['page_title'] = $txt['package_settings']; + $context['sub_template'] = 'install_options'; + + $context['package_ftp_server'] = isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost'; + $context['package_ftp_port'] = isset($modSettings['package_port']) ? $modSettings['package_port'] : '21'; + $context['package_ftp_username'] = isset($modSettings['package_username']) ? $modSettings['package_username'] : $default_username; + $context['package_make_backups'] = !empty($modSettings['package_make_backups']); +} + +function ViewOperations() +{ + global $context, $txt, $boarddir, $sourcedir, $smcFunc, $modSettings; + + // Can't be in here buddy. + isAllowedTo('admin_forum'); + + // We need to know the operation key for the search and replace, mod file looking at, is it a board mod? + if (!isset($_REQUEST['operation_key'], $_REQUEST['filename']) && !is_numeric($_REQUEST['operation_key'])) + fatal_lang_error('operation_invalid', 'general'); + + // Load the required file. + require_once($sourcedir . '/Subs-Package.php'); + + // Uninstalling the mod? + $reverse = isset($_REQUEST['reverse']) ? true : false; + + // Get the base name. + $context['filename'] = preg_replace('~[\.]+~', '.', $_REQUEST['package']); + + // We need to extract this again. + if (is_file($boarddir . '/Packages/' . $context['filename'])) + { + $context['extracted_files'] = read_tgz_file($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp'); + + if ($context['extracted_files'] && !file_exists($boarddir . '/Packages/temp/package-info.xml')) + foreach ($context['extracted_files'] as $file) + if (basename($file['filename']) == 'package-info.xml') + { + $context['base_path'] = dirname($file['filename']) . '/'; + break; + } + + if (!isset($context['base_path'])) + $context['base_path'] = ''; + } + elseif (is_dir($boarddir . '/Packages/' . $context['filename'])) + { + copytree($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp'); + $context['extracted_files'] = listtree($boarddir . '/Packages/temp'); + $context['base_path'] = ''; + } + + // Load up any custom themes we may want to install into... + $request = $smcFunc['db_query']('', ' + SELECT id_theme, variable, value + FROM {db_prefix}themes + WHERE (id_theme = {int:default_theme} OR id_theme IN ({array_int:known_theme_list})) + AND variable IN ({string:name}, {string:theme_dir})', + array( + 'known_theme_list' => explode(',', $modSettings['knownThemes']), + 'default_theme' => 1, + 'name' => 'name', + 'theme_dir' => 'theme_dir', + ) + ); + $theme_paths = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $theme_paths[$row['id_theme']][$row['variable']] = $row['value']; + $smcFunc['db_free_result']($request); + + // Boardmod? + if (isset($_REQUEST['boardmod'])) + $mod_actions = parseBoardMod(@file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $_REQUEST['filename']), true, $reverse, $theme_paths); + else + $mod_actions = parseModification(@file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $_REQUEST['filename']), true, $reverse, $theme_paths); + + // Ok lets get the content of the file. + $context['operations'] = array( + 'search' => strtr(htmlspecialchars($mod_actions[$_REQUEST['operation_key']]['search_original']), array('[' => '[', ']' => ']')), + 'replace' => strtr(htmlspecialchars($mod_actions[$_REQUEST['operation_key']]['replace_original']), array('[' => '[', ']' => ']')), + 'position' => $mod_actions[$_REQUEST['operation_key']]['position'], + ); + + // Let's do some formatting... + $operation_text = $context['operations']['position'] == 'replace' ? 'operation_replace' : ($context['operations']['position'] == 'before' ? 'operation_after' : 'operation_before'); + $context['operations']['search'] = parse_bbc('[code=' . $txt['operation_find'] . ']' . ($context['operations']['position'] == 'end' ? '?>' : $context['operations']['search']) . '[/code]'); + $context['operations']['replace'] = parse_bbc('[code=' . $txt[$operation_text] . ']' . $context['operations']['replace'] . '[/code]'); + + // No layers + $context['template_layers'] = array(); + $context['sub_template'] = 'view_operations'; +} + +// Allow the admin to reset permissions on files. +function PackagePermissions() +{ + global $context, $txt, $modSettings, $boarddir, $sourcedir, $cachedir, $smcFunc, $package_ftp; + + // Let's try and be good, yes? + checkSession('get'); + + // If we're restoring permissions this is just a pass through really. + if (isset($_GET['restore'])) + { + create_chmod_control(array(), array(), true); + fatal_lang_error('no_access', false); + } + + // This is a memory eat. + @ini_set('memory_limit', '128M'); + @set_time_limit(600); + + // Load up some FTP stuff. + create_chmod_control(); + + if (empty($package_ftp) && !isset($_POST['skip_ftp'])) + { + loadClassFile('Class-Package.php'); + $ftp = new ftp_connection(null); + list ($username, $detect_path, $found_path) = $ftp->detect_path($boarddir); + + $context['package_ftp'] = array( + 'server' => isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost', + 'port' => isset($modSettings['package_port']) ? $modSettings['package_port'] : '21', + 'username' => empty($username) ? (isset($modSettings['package_username']) ? $modSettings['package_username'] : '') : $username, + 'path' => $detect_path, + 'form_elements_only' => true, + ); + } + else + $context['ftp_connected'] = true; + + // Define the template. + $context['page_title'] = $txt['package_file_perms']; + $context['sub_template'] = 'file_permissions'; + + // Define what files we're interested in, as a tree. + $context['file_tree'] = array( + strtr($boarddir, array('\\' => '/')) => array( + 'type' => 'dir', + 'contents' => array( + 'agreement.txt' => array( + 'type' => 'file', + 'writable_on' => 'standard', + ), + 'Settings.php' => array( + 'type' => 'file', + 'writable_on' => 'restrictive', + ), + 'Settings_bak.php' => array( + 'type' => 'file', + 'writable_on' => 'restrictive', + ), + 'attachments' => array( + 'type' => 'dir', + 'writable_on' => 'restrictive', + ), + 'avatars' => array( + 'type' => 'dir', + 'writable_on' => 'standard', + ), + 'cache' => array( + 'type' => 'dir', + 'writable_on' => 'restrictive', + ), + 'custom_avatar_dir' => array( + 'type' => 'dir', + 'writable_on' => 'restrictive', + ), + 'Smileys' => array( + 'type' => 'dir_recursive', + 'writable_on' => 'standard', + ), + 'Sources' => array( + 'type' => 'dir', + 'list_contents' => true, + 'writable_on' => 'standard', + ), + 'Themes' => array( + 'type' => 'dir_recursive', + 'writable_on' => 'standard', + 'contents' => array( + 'default' => array( + 'type' => 'dir_recursive', + 'list_contents' => true, + 'contents' => array( + 'languages' => array( + 'type' => 'dir', + 'list_contents' => true, + ), + ), + ), + ), + ), + 'Packages' => array( + 'type' => 'dir', + 'writable_on' => 'standard', + 'contents' => array( + 'temp' => array( + 'type' => 'dir', + ), + 'backup' => array( + 'type' => 'dir', + ), + 'installed.list' => array( + 'type' => 'file', + 'writable_on' => 'standard', + ), + ), + ), + ), + ), + ); + + // Directories that can move. + if (substr($sourcedir, 0, strlen($boarddir)) != $boarddir) + { + unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Sources']); + $context['file_tree'][strtr($sourcedir, array('\\' => '/'))] = array( + 'type' => 'dir', + 'list_contents' => true, + 'writable_on' => 'standard', + ); + } + + // Moved the cache? + if (substr($cachedir, 0, strlen($boarddir)) != $boarddir) + { + unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['cache']); + $context['file_tree'][strtr($cachedir, array('\\' => '/'))] = array( + 'type' => 'dir', + 'list_contents' => false, + 'writable_on' => 'restrictive', + ); + } + + // Are we using multiple attachment directories? + if (!empty($modSettings['currentAttachmentUploadDir'])) + { + unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['attachments']); + + if (!is_array($modSettings['attachmentUploadDir'])) + $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); + + // !!! Should we suggest non-current directories be read only? + foreach ($modSettings['attachmentUploadDir'] as $dir) + $context['file_tree'][strtr($dir, array('\\' => '/'))] = array( + 'type' => 'dir', + 'writable_on' => 'restrictive', + ); + + } + elseif (substr($modSettings['attachmentUploadDir'], 0, strlen($boarddir)) != $boarddir) + { + unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['attachments']); + $context['file_tree'][strtr($modSettings['attachmentUploadDir'], array('\\' => '/'))] = array( + 'type' => 'dir', + 'writable_on' => 'restrictive', + ); + } + + if (substr($modSettings['smileys_dir'], 0, strlen($boarddir)) != $boarddir) + { + unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Smileys']); + $context['file_tree'][strtr($modSettings['smileys_dir'], array('\\' => '/'))] = array( + 'type' => 'dir_recursive', + 'writable_on' => 'standard', + ); + } + if (substr($modSettings['avatar_directory'], 0, strlen($boarddir)) != $boarddir) + { + unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['avatars']); + $context['file_tree'][strtr($modSettings['avatar_directory'], array('\\' => '/'))] = array( + 'type' => 'dir', + 'writable_on' => 'standard', + ); + } + if (isset($modSettings['custom_avatar_dir']) && substr($modSettings['custom_avatar_dir'], 0, strlen($boarddir)) != $boarddir) + { + unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['custom_avatar_dir']); + $context['file_tree'][strtr($modSettings['custom_avatar_dir'], array('\\' => '/'))] = array( + 'type' => 'dir', + 'writable_on' => 'restrictive', + ); + } + + // Load up any custom themes. + $request = $smcFunc['db_query']('', ' + SELECT value + FROM {db_prefix}themes + WHERE id_theme > {int:default_theme_id} + AND id_member = {int:guest_id} + AND variable = {string:theme_dir} + ORDER BY value ASC', + array( + 'default_theme_id' => 1, + 'guest_id' => 0, + 'theme_dir' => 'theme_dir', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (substr(strtolower(strtr($row['value'], array('\\' => '/'))), 0, strlen($boarddir) + 7) == strtolower(strtr($boarddir, array('\\' => '/')) . '/Themes')) + $context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Themes']['contents'][substr($row['value'], strlen($boarddir) + 8)] = array( + 'type' => 'dir_recursive', + 'list_contents' => true, + 'contents' => array( + 'languages' => array( + 'type' => 'dir', + 'list_contents' => true, + ), + ), + ); + else + { + $context['file_tree'][strtr($row['value'], array('\\' => '/'))] = array( + 'type' => 'dir_recursive', + 'list_contents' => true, + 'contents' => array( + 'languages' => array( + 'type' => 'dir', + 'list_contents' => true, + ), + ), + ); + } + } + $smcFunc['db_free_result']($request); + + // If we're submitting then let's move on to another function to keep things cleaner.. + if (isset($_POST['action_changes'])) + return PackagePermissionsAction(); + + $context['look_for'] = array(); + // Are we looking for a particular tree - normally an expansion? + if (!empty($_REQUEST['find'])) + $context['look_for'][] = base64_decode($_REQUEST['find']); + // Only that tree? + $context['only_find'] = isset($_GET['xml']) && !empty($_REQUEST['onlyfind']) ? $_REQUEST['onlyfind'] : ''; + if ($context['only_find']) + $context['look_for'][] = $context['only_find']; + + // Have we got a load of back-catalogue trees to expand from a submit etc? + if (!empty($_GET['back_look'])) + { + $potententialTrees = unserialize(base64_decode($_GET['back_look'])); + foreach ($potententialTrees as $tree) + $context['look_for'][] = $tree; + } + // ... maybe posted? + if (!empty($_POST['back_look'])) + $context['only_find'] = array_merge($context['only_find'], $_POST['back_look']); + + $context['back_look_data'] = base64_encode(serialize(array_slice($context['look_for'], 0, 15))); + + // Are we finding more files than first thought? + $context['file_offset'] = !empty($_REQUEST['fileoffset']) ? (int) $_REQUEST['fileoffset'] : 0; + // Don't list more than this many files in a directory. + $context['file_limit'] = 150; + + // How many levels shall we show? + $context['default_level'] = empty($context['only_find']) ? 2 : 25; + + // This will be used if we end up catching XML data. + $context['xml_data'] = array( + 'roots' => array( + 'identifier' => 'root', + 'children' => array( + array( + 'value' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']), + ), + ), + ), + 'folders' => array( + 'identifier' => 'folder', + 'children' => array(), + ), + ); + + foreach ($context['file_tree'] as $path => $data) + { + // Run this directory. + if (file_exists($path) && (empty($context['only_find']) || substr($context['only_find'], 0, strlen($path)) == $path)) + { + // Get the first level down only. + fetchPerms__recursive($path, $context['file_tree'][$path], 1); + $context['file_tree'][$path]['perms'] = array( + 'chmod' => @is_writable($path), + 'perms' => @fileperms($path), + ); + } + else + unset($context['file_tree'][$path]); + } + + // Is this actually xml? + if (isset($_GET['xml'])) + { + loadTemplate('Xml'); + $context['sub_template'] = 'generic_xml'; + $context['template_layers'] = array(); + } +} + +function fetchPerms__recursive($path, &$data, $level) +{ + global $context; + + $isLikelyPath = false; + foreach ($context['look_for'] as $possiblePath) + if (substr($possiblePath, 0, strlen($path)) == $path) + $isLikelyPath = true; + + // Is this where we stop? + if (isset($_GET['xml']) && !empty($context['look_for']) && !$isLikelyPath) + return; + elseif ($level > $context['default_level'] && !$isLikelyPath) + return; + + // Are we actually interested in saving this data? + $save_data = empty($context['only_find']) || $context['only_find'] == $path; + + //!!! Shouldn't happen - but better error message? + if (!is_dir($path)) + fatal_lang_error('no_access', false); + + // This is where we put stuff we've found for sorting. + $foundData = array( + 'files' => array(), + 'folders' => array(), + ); + + $dh = opendir($path); + while ($entry = readdir($dh)) + { + // Some kind of file? + if (!is_dir($path . '/' . $entry)) + { + // Are we listing PHP files in this directory? + if ($save_data && !empty($data['list_contents']) && substr($entry, -4) == '.php') + $foundData['files'][$entry] = true; + // A file we were looking for. + elseif ($save_data && isset($data['contents'][$entry])) + $foundData['files'][$entry] = true; + } + // It's a directory - we're interested one way or another, probably... + elseif ($entry != '.' && $entry != '..') + { + // Going further? + if ((!empty($data['type']) && $data['type'] == 'dir_recursive') || (isset($data['contents'][$entry]) && (!empty($data['contents'][$entry]['list_contents']) || (!empty($data['contents'][$entry]['type']) && $data['contents'][$entry]['type'] == 'dir_recursive')))) + { + if (!isset($data['contents'][$entry])) + $foundData['folders'][$entry] = 'dir_recursive'; + else + $foundData['folders'][$entry] = true; + + // If this wasn't expected inherit the recusiveness... + if (!isset($data['contents'][$entry])) + // We need to do this as we will be going all recursive. + $data['contents'][$entry] = array( + 'type' => 'dir_recursive', + ); + + // Actually do the recursive stuff... + fetchPerms__recursive($path . '/' . $entry, $data['contents'][$entry], $level + 1); + } + // Maybe it is a folder we are not descending into. + elseif (isset($data['contents'][$entry])) + $foundData['folders'][$entry] = true; + // Otherwise we stop here. + } + } + closedir($dh); + + // Nothing to see here? + if (!$save_data) + return; + + // Now actually add the data, starting with the folders. + ksort($foundData['folders']); + foreach ($foundData['folders'] as $folder => $type) + { + $additional_data = array( + 'perms' => array( + 'chmod' => @is_writable($path . '/' . $folder), + 'perms' => @fileperms($path . '/' . $folder), + ), + ); + if ($type !== true) + $additional_data['type'] = $type; + + // If there's an offset ignore any folders in XML mode. + if (isset($_GET['xml']) && $context['file_offset'] == 0) + { + $context['xml_data']['folders']['children'][] = array( + 'attributes' => array( + 'writable' => $additional_data['perms']['chmod'] ? 1 : 0, + 'permissions' => substr(sprintf('%o', $additional_data['perms']['perms']), -4), + 'folder' => 1, + 'path' => $context['only_find'], + 'level' => $level, + 'more' => 0, + 'offset' => $context['file_offset'], + 'my_ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find'] . '/' . $folder), + 'ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']), + ), + 'value' => $folder, + ); + } + elseif (!isset($_GET['xml'])) + { + if (isset($data['contents'][$folder])) + $data['contents'][$folder] = array_merge($data['contents'][$folder], $additional_data); + else + $data['contents'][$folder] = $additional_data; + } + } + + // Now we want to do a similar thing with files. + ksort($foundData['files']); + $counter = -1; + foreach ($foundData['files'] as $file => $dummy) + { + $counter++; + + // Have we reached our offset? + if ($context['file_offset'] > $counter) + continue; + // Gone too far? + if ($counter > ($context['file_offset'] + $context['file_limit'])) + continue; + + $additional_data = array( + 'perms' => array( + 'chmod' => @is_writable($path . '/' . $file), + 'perms' => @fileperms($path . '/' . $file), + ), + ); + + // XML? + if (isset($_GET['xml'])) + { + $context['xml_data']['folders']['children'][] = array( + 'attributes' => array( + 'writable' => $additional_data['perms']['chmod'] ? 1 : 0, + 'permissions' => substr(sprintf('%o', $additional_data['perms']['perms']), -4), + 'folder' => 0, + 'path' => $context['only_find'], + 'level' => $level, + 'more' => $counter == ($context['file_offset'] + $context['file_limit']) ? 1 : 0, + 'offset' => $context['file_offset'], + 'my_ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find'] . '/' . $file), + 'ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']), + ), + 'value' => $file, + ); + } + elseif ($counter != ($context['file_offset'] + $context['file_limit'])) + { + if (isset($data['contents'][$file])) + $data['contents'][$file] = array_merge($data['contents'][$file], $additional_data); + else + $data['contents'][$file] = $additional_data; + } + } +} + +// Actually action the permission changes they want. +function PackagePermissionsAction() +{ + global $context, $txt, $time_start, $package_ftp; + + umask(0); + + $timeout_limit = 5; + + $context['method'] = $_POST['method'] == 'individual' ? 'individual' : 'predefined'; + $context['sub_template'] = 'action_permissions'; + $context['page_title'] = $txt['package_file_perms_applying']; + $context['back_look_data'] = isset($_POST['back_look']) ? $_POST['back_look'] : array(); + + // Skipping use of FTP? + if (empty($package_ftp)) + $context['skip_ftp'] = true; + + // We'll start off in a good place, security. Make sure that if we're dealing with individual files that they seem in the right place. + if ($context['method'] == 'individual') + { + // Only these path roots are legal. + $legal_roots = array_keys($context['file_tree']); + $context['custom_value'] = (int) $_POST['custom_value']; + + // Continuing? + if (isset($_POST['toProcess'])) + $_POST['permStatus'] = unserialize(base64_decode($_POST['toProcess'])); + + if (isset($_POST['permStatus'])) + { + $context['to_process'] = array(); + $validate_custom = false; + foreach ($_POST['permStatus'] as $path => $status) + { + // Nothing to see here? + if ($status == 'no_change') + continue; + $legal = false; + foreach ($legal_roots as $root) + if (substr($path, 0, strlen($root)) == $root) + $legal = true; + + if (!$legal) + continue; + + // Check it exists. + if (!file_exists($path)) + continue; + + if ($status == 'custom') + $validate_custom = true; + + // Now add it. + $context['to_process'][$path] = $status; + } + $context['total_items'] = isset($_POST['totalItems']) ? (int) $_POST['totalItems'] : count($context['to_process']); + + // Make sure the chmod status is valid? + if ($validate_custom) + { + if (preg_match('~^[4567][4567][4567]$~', $context['custom_value']) == false) + fatal_error($txt['chmod_value_invalid']); + } + + // Nothing to do? + if (empty($context['to_process'])) + redirectexit('action=admin;area=packages;sa=perms' . (!empty($context['back_look_data']) ? ';back_look=' . base64_encode(serialize($context['back_look_data'])) : '') . ';' . $context['session_var'] . '=' . $context['session_id']); + } + // Should never get here, + else + fatal_lang_error('no_access', false); + + // Setup the custom value. + $custom_value = octdec('0' . $context['custom_value']); + + // Start processing items. + foreach ($context['to_process'] as $path => $status) + { + if (in_array($status, array('execute', 'writable', 'read'))) + package_chmod($path, $status); + elseif ($status == 'custom' && !empty($custom_value)) + { + // Use FTP if we have it. + if (!empty($package_ftp) && !empty($_SESSION['pack_ftp'])) + { + $ftp_file = strtr($path, array($_SESSION['pack_ftp']['root'] => '')); + $package_ftp->chmod($ftp_file, $custom_value); + } + else + @chmod($path, $custom_value); + } + + // This fish is fried... + unset($context['to_process'][$path]); + + // See if we're out of time? + if (time() - array_sum(explode(' ', $time_start)) > $timeout_limit) + return false; + } + } + // If predefined this is a little different. + else + { + $context['predefined_type'] = isset($_POST['predefined']) ? $_POST['predefined'] : 'restricted'; + + $context['total_items'] = isset($_POST['totalItems']) ? (int) $_POST['totalItems'] : 0; + $context['directory_list'] = isset($_POST['dirList']) ? unserialize(base64_decode($_POST['dirList'])) : array(); + + $context['file_offset'] = isset($_POST['fileOffset']) ? (int) $_POST['fileOffset'] : 0; + + // Haven't counted the items yet? + if (empty($context['total_items'])) + { + function count_directories__recursive($dir) + { + global $context; + + $count = 0; + $dh = @opendir($dir); + while ($entry = readdir($dh)) + { + if ($entry != '.' && $entry != '..' && is_dir($dir . '/' . $entry)) + { + $context['directory_list'][$dir . '/' . $entry] = 1; + $count++; + $count += count_directories__recursive($dir . '/' . $entry); + } + } + closedir($dh); + + return $count; + } + + foreach ($context['file_tree'] as $path => $data) + { + if (is_dir($path)) + { + $context['directory_list'][$path] = 1; + $context['total_items'] += count_directories__recursive($path); + $context['total_items']++; + } + } + } + + // Have we built up our list of special files? + if (!isset($_POST['specialFiles']) && $context['predefined_type'] != 'free') + { + $context['special_files'] = array(); + function build_special_files__recursive($path, &$data) + { + global $context; + + if (!empty($data['writable_on'])) + if ($context['predefined_type'] == 'standard' || $data['writable_on'] == 'restrictive') + $context['special_files'][$path] = 1; + + if (!empty($data['contents'])) + foreach ($data['contents'] as $name => $contents) + build_special_files__recursive($path . '/' . $name, $contents); + } + + foreach ($context['file_tree'] as $path => $data) + build_special_files__recursive($path, $data); + } + // Free doesn't need special files. + elseif ($context['predefined_type'] == 'free') + $context['special_files'] = array(); + else + $context['special_files'] = unserialize(base64_decode($_POST['specialFiles'])); + + // Now we definitely know where we are, we need to go through again doing the chmod! + foreach ($context['directory_list'] as $path => $dummy) + { + // Do the contents of the directory first. + $dh = @opendir($path); + $file_count = 0; + $dont_chmod = false; + while ($entry = readdir($dh)) + { + $file_count++; + // Actually process this file? + if (!$dont_chmod && !is_dir($path . '/' . $entry) && (empty($context['file_offset']) || $context['file_offset'] < $file_count)) + { + $status = $context['predefined_type'] == 'free' || isset($context['special_files'][$path . '/' . $entry]) ? 'writable' : 'execute'; + package_chmod($path . '/' . $entry, $status); + } + + // See if we're out of time? + if (!$dont_chmod && time() - array_sum(explode(' ', $time_start)) > $timeout_limit) + { + $dont_chmod = true; + // Don't do this again. + $context['file_offset'] = $file_count; + } + } + closedir($dh); + + // If this is set it means we timed out half way through. + if ($dont_chmod) + { + $context['total_files'] = $file_count; + return false; + } + + // Do the actual directory. + $status = $context['predefined_type'] == 'free' || isset($context['special_files'][$path]) ? 'writable' : 'execute'; + package_chmod($path, $status); + + // We've finished the directory so no file offset, and no record. + $context['file_offset'] = 0; + unset($context['directory_list'][$path]); + + // See if we're out of time? + if (time() - array_sum(explode(' ', $time_start)) > $timeout_limit) + return false; + } + } + + // If we're here we are done! + redirectexit('action=admin;area=packages;sa=perms' . (!empty($context['back_look_data']) ? ';back_look=' . base64_encode(serialize($context['back_look_data'])) : '') . ';' . $context['session_var'] . '=' . $context['session_id']); +} + +// Test an FTP connection. +function PackageFTPTest() +{ + global $context, $txt, $package_ftp; + + checkSession('get'); + + // Try to make the FTP connection. + create_chmod_control(array(), array('force_find_error' => true)); + + // Deal with the template stuff. + loadTemplate('Xml'); + $context['sub_template'] = 'generic_xml'; + $context['template_layers'] = array(); + + // Define the return data, this is simple. + $context['xml_data'] = array( + 'results' => array( + 'identifier' => 'result', + 'children' => array( + array( + 'attributes' => array( + 'success' => !empty($package_ftp) ? 1 : 0, + ), + 'value' => !empty($package_ftp) ? $txt['package_ftp_test_success'] : (isset($context['package_ftp'], $context['package_ftp']['error']) ? $context['package_ftp']['error'] : $txt['package_ftp_test_failed']), + ), + ), + ), + ); +} + +?> \ No newline at end of file diff --git a/Sources/PersonalMessage.php b/Sources/PersonalMessage.php new file mode 100644 index 0000000..54cd38e --- /dev/null +++ b/Sources/PersonalMessage.php @@ -0,0 +1,3621 @@ + $user_info['groups'], + ) + ); + list ($maxMessage, $minMessage) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $context['message_limit'] = $minMessage == 0 ? 0 : $maxMessage; + + // Save us doing it again! + cache_put_data('msgLimit:' . $user_info['id'], $context['message_limit'], 360); + } + + // Prepare the context for the capacity bar. + if (!empty($context['message_limit'])) + { + $bar = ($user_info['messages'] * 100) / $context['message_limit']; + + $context['limit_bar'] = array( + 'messages' => $user_info['messages'], + 'allowed' => $context['message_limit'], + 'percent' => $bar, + 'bar' => min(100, (int) $bar), + 'text' => sprintf($txt['pm_currently_using'], $user_info['messages'], round($bar, 1)), + ); + } + + // a previous message was sent successfully? show a small indication. + if (isset($_GET['done']) && ($_GET['done'] == 'sent')) + $context['pm_sent'] = true; + + // Now we have the labels, and assuming we have unsorted mail, apply our rules! + if ($user_settings['new_pm']) + { + $context['labels'] = $user_settings['message_labels'] == '' ? array() : explode(',', $user_settings['message_labels']); + foreach ($context['labels'] as $id_label => $label_name) + $context['labels'][(int) $id_label] = array( + 'id' => $id_label, + 'name' => trim($label_name), + 'messages' => 0, + 'unread_messages' => 0, + ); + $context['labels'][-1] = array( + 'id' => -1, + 'name' => $txt['pm_msg_label_inbox'], + 'messages' => 0, + 'unread_messages' => 0, + ); + + ApplyRules(); + updateMemberData($user_info['id'], array('new_pm' => 0)); + $smcFunc['db_query']('', ' + UPDATE {db_prefix}pm_recipients + SET is_new = {int:not_new} + WHERE id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'not_new' => 0, + ) + ); + } + + // Load the label data. + if ($user_settings['new_pm'] || ($context['labels'] = cache_get_data('labelCounts:' . $user_info['id'], 720)) === null) + { + $context['labels'] = $user_settings['message_labels'] == '' ? array() : explode(',', $user_settings['message_labels']); + foreach ($context['labels'] as $id_label => $label_name) + $context['labels'][(int) $id_label] = array( + 'id' => $id_label, + 'name' => trim($label_name), + 'messages' => 0, + 'unread_messages' => 0, + ); + $context['labels'][-1] = array( + 'id' => -1, + 'name' => $txt['pm_msg_label_inbox'], + 'messages' => 0, + 'unread_messages' => 0, + ); + + // Looks like we need to reseek! + $result = $smcFunc['db_query']('', ' + SELECT labels, is_read, COUNT(*) AS num + FROM {db_prefix}pm_recipients + WHERE id_member = {int:current_member} + AND deleted = {int:not_deleted} + GROUP BY labels, is_read', + array( + 'current_member' => $user_info['id'], + 'not_deleted' => 0, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $this_labels = explode(',', $row['labels']); + foreach ($this_labels as $this_label) + { + $context['labels'][(int) $this_label]['messages'] += $row['num']; + if (!($row['is_read'] & 1)) + $context['labels'][(int) $this_label]['unread_messages'] += $row['num']; + } + } + $smcFunc['db_free_result']($result); + + // Store it please! + cache_put_data('labelCounts:' . $user_info['id'], $context['labels'], 720); + } + + // This determines if we have more labels than just the standard inbox. + $context['currently_using_labels'] = count($context['labels']) > 1 ? 1 : 0; + + // Some stuff for the labels... + $context['current_label_id'] = isset($_REQUEST['l']) && isset($context['labels'][(int) $_REQUEST['l']]) ? (int) $_REQUEST['l'] : -1; + $context['current_label'] = &$context['labels'][(int) $context['current_label_id']]['name']; + $context['folder'] = !isset($_REQUEST['f']) || $_REQUEST['f'] != 'sent' ? 'inbox' : 'sent'; + + // This is convenient. Do you know how annoying it is to do this every time?! + $context['current_label_redirect'] = 'action=pm;f=' . $context['folder'] . (isset($_GET['start']) ? ';start=' . $_GET['start'] : '') . (isset($_REQUEST['l']) ? ';l=' . $_REQUEST['l'] : ''); + $context['can_issue_warning'] = in_array('w', $context['admin_features']) && allowedTo('issue_warning') && $modSettings['warning_settings'][0] == 1; + + // Build the linktree for all the actions... + $context['linktree'][] = array( + 'url' => $scripturl . '?action=pm', + 'name' => $txt['personal_messages'] + ); + + // Preferences... + $context['display_mode'] = WIRELESS ? 0 : $user_settings['pm_prefs'] & 3; + + $subActions = array( + 'addbuddy' => 'WirelessAddBuddy', + 'manlabels' => 'ManageLabels', + 'manrules' => 'ManageRules', + 'pmactions' => 'MessageActionsApply', + 'prune' => 'MessagePrune', + 'removeall' => 'MessageKillAllQuery', + 'removeall2' => 'MessageKillAll', + 'report' => 'ReportMessage', + 'search' => 'MessageSearch', + 'search2' => 'MessageSearch2', + 'send' => 'MessagePost', + 'send2' => 'MessagePost2', + 'settings' => 'MessageSettings', + ); + + if (!isset($_REQUEST['sa']) || !isset($subActions[$_REQUEST['sa']])) + { + $_REQUEST['sa'] = ''; + MessageFolder(); + } + else + { + messageIndexBar($_REQUEST['sa']); + $subActions[$_REQUEST['sa']](); + } +} + +// A sidebar to easily access different areas of the section +function messageIndexBar($area) +{ + global $txt, $context, $scripturl, $sourcedir, $sc, $modSettings, $settings, $user_info, $options; + + $pm_areas = array( + 'folders' => array( + 'title' => $txt['pm_messages'], + 'areas' => array( + 'send' => array( + 'label' => $txt['new_message'], + 'custom_url' => $scripturl . '?action=pm;sa=send', + 'permission' => allowedTo('pm_send'), + ), + 'inbox' => array( + 'label' => $txt['inbox'], + 'custom_url' => $scripturl . '?action=pm', + ), + 'sent' => array( + 'label' => $txt['sent_items'], + 'custom_url' => $scripturl . '?action=pm;f=sent', + ), + ), + ), + 'labels' => array( + 'title' => $txt['pm_labels'], + 'areas' => array(), + ), + 'actions' => array( + 'title' => $txt['pm_actions'], + 'areas' => array( + 'search' => array( + 'label' => $txt['pm_search_bar_title'], + 'custom_url' => $scripturl . '?action=pm;sa=search', + ), + 'prune' => array( + 'label' => $txt['pm_prune'], + 'custom_url' => $scripturl . '?action=pm;sa=prune' + ), + ), + ), + 'pref' => array( + 'title' => $txt['pm_preferences'], + 'areas' => array( + 'manlabels' => array( + 'label' => $txt['pm_manage_labels'], + 'custom_url' => $scripturl . '?action=pm;sa=manlabels', + ), + 'manrules' => array( + 'label' => $txt['pm_manage_rules'], + 'custom_url' => $scripturl . '?action=pm;sa=manrules', + ), + 'settings' => array( + 'label' => $txt['pm_settings'], + 'custom_url' => $scripturl . '?action=pm;sa=settings', + ), + ), + ), + ); + + // Handle labels. + if (empty($context['currently_using_labels'])) + unset($pm_areas['labels']); + else + { + // Note we send labels by id as it will have less problems in the querystring. + $unread_in_labels = 0; + foreach ($context['labels'] as $label) + { + if ($label['id'] == -1) + continue; + + // Count the amount of unread items in labels. + $unread_in_labels += $label['unread_messages']; + + // Add the label to the menu. + $pm_areas['labels']['areas']['label' . $label['id']] = array( + 'label' => $label['name'] . (!empty($label['unread_messages']) ? ' (' . $label['unread_messages'] . ')' : ''), + 'custom_url' => $scripturl . '?action=pm;l=' . $label['id'], + 'unread_messages' => $label['unread_messages'], + 'messages' => $label['messages'], + ); + } + + if (!empty($unread_in_labels)) + $pm_areas['labels']['title'] .= ' (' . $unread_in_labels . ')'; + } + + $pm_areas['folders']['areas']['inbox']['unread_messages'] = &$context['labels'][-1]['unread_messages']; + $pm_areas['folders']['areas']['inbox']['messages'] = &$context['labels'][-1]['messages']; + if (!empty($context['labels'][-1]['unread_messages'])) + { + $pm_areas['folders']['areas']['inbox']['label'] .= ' (' . $context['labels'][-1]['unread_messages'] . ')'; + $pm_areas['folders']['title'] .= ' (' . $context['labels'][-1]['unread_messages'] . ')'; + } + + // Do we have a limit on the amount of messages we can keep? + if (!empty($context['message_limit'])) + { + $bar = round(($user_info['messages'] * 100) / $context['message_limit'], 1); + + $context['limit_bar'] = array( + 'messages' => $user_info['messages'], + 'allowed' => $context['message_limit'], + 'percent' => $bar, + 'bar' => $bar > 100 ? 100 : (int) $bar, + 'text' => sprintf($txt['pm_currently_using'], $user_info['messages'], $bar) + ); + } + + require_once($sourcedir . '/Subs-Menu.php'); + + // What page is this, again? + $current_page = $scripturl . '?action=pm' . (!empty($_REQUEST['sa']) ? ';sa=' . $_REQUEST['sa'] : '') . (!empty($context['folder']) ? ';f=' . $context['folder'] : '') . (!empty($context['current_label_id']) ? ';l=' . $context['current_label_id'] : ''); + + // Set a few options for the menu. + $menuOptions = array( + 'current_area' => $area, + 'disable_url_session_check' => true, + 'toggle_url' => $current_page . ';togglebar', + 'toggle_redirect_url' => $current_page, + ); + + // Actually create the menu! + $pm_include_data = createMenu($pm_areas, $menuOptions); + unset($pm_areas); + + // Make a note of the Unique ID for this menu. + $context['pm_menu_id'] = $context['max_menu_id']; + $context['pm_menu_name'] = 'menu_data_' . $context['pm_menu_id']; + + // Set the selected item. + $context['menu_item_selected'] = $pm_include_data['current_area']; + + // obExit will know what to do! + if (!WIRELESS) + $context['template_layers'][] = 'pm'; +} + +// A folder, ie. inbox/sent etc. +function MessageFolder() +{ + global $txt, $scripturl, $modSettings, $context, $subjects_request; + global $messages_request, $user_info, $recipients, $options, $smcFunc, $memberContext, $user_settings; + + // Changing view? + if (isset($_GET['view'])) + { + $context['display_mode'] = $context['display_mode'] > 1 ? 0 : $context['display_mode'] + 1; + updateMemberData($user_info['id'], array('pm_prefs' => ($user_settings['pm_prefs'] & 252) | $context['display_mode'])); + } + + // Make sure the starting location is valid. + if (isset($_GET['start']) && $_GET['start'] != 'new') + $_GET['start'] = (int) $_GET['start']; + elseif (!isset($_GET['start']) && !empty($options['view_newest_pm_first'])) + $_GET['start'] = 0; + else + $_GET['start'] = 'new'; + + // Set up some basic theme stuff. + $context['from_or_to'] = $context['folder'] != 'sent' ? 'from' : 'to'; + $context['get_pmessage'] = 'prepareMessageContext'; + $context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1; + + $labelQuery = $context['folder'] != 'sent' ? ' + AND FIND_IN_SET(' . $context['current_label_id'] . ', pmr.labels) != 0' : ''; + + // Set the index bar correct! + messageIndexBar($context['current_label_id'] == -1 ? $context['folder'] : 'label' . $context['current_label_id']); + + // Sorting the folder. + $sort_methods = array( + 'date' => 'pm.id_pm', + 'name' => 'IFNULL(mem.real_name, \'\')', + 'subject' => 'pm.subject', + ); + + // They didn't pick one, use the forum default. + if (!isset($_GET['sort']) || !isset($sort_methods[$_GET['sort']])) + { + $context['sort_by'] = 'date'; + $_GET['sort'] = 'pm.id_pm'; + // An overriding setting? + $descending = !empty($options['view_newest_pm_first']); + } + // Otherwise use the defaults: ascending, by date. + else + { + $context['sort_by'] = $_GET['sort']; + $_GET['sort'] = $sort_methods[$_GET['sort']]; + $descending = isset($_GET['desc']); + } + + $context['sort_direction'] = $descending ? 'down' : 'up'; + + // Why would you want access to your sent items if you're not allowed to send anything? + if ($context['folder'] == 'sent') + isAllowedTo('pm_send'); + + // Set the text to resemble the current folder. + $pmbox = $context['folder'] != 'sent' ? $txt['inbox'] : $txt['sent_items']; + $txt['delete_all'] = str_replace('PMBOX', $pmbox, $txt['delete_all']); + + // Now, build the link tree! + if ($context['current_label_id'] == -1) + $context['linktree'][] = array( + 'url' => $scripturl . '?action=pm;f=' . $context['folder'], + 'name' => $pmbox + ); + + // Build it further for a label. + if ($context['current_label_id'] != -1) + $context['linktree'][] = array( + 'url' => $scripturl . '?action=pm;f=' . $context['folder'] . ';l=' . $context['current_label_id'], + 'name' => $txt['pm_current_label'] . ': ' . $context['current_label'] + ); + + // Figure out how many messages there are. + if ($context['folder'] == 'sent') + $request = $smcFunc['db_query']('', ' + SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ') + FROM {db_prefix}personal_messages AS pm + WHERE pm.id_member_from = {int:current_member} + AND pm.deleted_by_sender = {int:not_deleted}', + array( + 'current_member' => $user_info['id'], + 'not_deleted' => 0, + ) + ); + else + $request = $smcFunc['db_query']('', ' + SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ') + FROM {db_prefix}pm_recipients AS pmr' . ($context['display_mode'] == 2 ? ' + INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)' : '') . ' + WHERE pmr.id_member = {int:current_member} + AND pmr.deleted = {int:not_deleted}' . $labelQuery, + array( + 'current_member' => $user_info['id'], + 'not_deleted' => 0, + ) + ); + list ($max_messages) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Only show the button if there are messages to delete. + $context['show_delete'] = $max_messages > 0; + + // Start on the last page. + if (!is_numeric($_GET['start']) || $_GET['start'] >= $max_messages) + $_GET['start'] = ($max_messages - 1) - (($max_messages - 1) % $modSettings['defaultMaxMessages']); + elseif ($_GET['start'] < 0) + $_GET['start'] = 0; + + // ... but wait - what if we want to start from a specific message? + if (isset($_GET['pmid'])) + { + $pmID = (int) $_GET['pmid']; + + // Make sure you have access to this PM. + if (!isAccessiblePM($pmID, $context['folder'] == 'sent' ? 'outbox' : 'inbox')) + fatal_lang_error('no_access', false); + + $context['current_pm'] = $pmID; + + // With only one page of PM's we're gonna want page 1. + if ($max_messages <= $modSettings['defaultMaxMessages']) + $_GET['start'] = 0; + // If we pass kstart we assume we're in the right place. + elseif (!isset($_GET['kstart'])) + { + if ($context['folder'] == 'sent') + $request = $smcFunc['db_query']('', ' + SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ') + FROM {db_prefix}personal_messages + WHERE id_member_from = {int:current_member} + AND deleted_by_sender = {int:not_deleted} + AND id_pm ' . ($descending ? '>' : '<') . ' {int:id_pm}', + array( + 'current_member' => $user_info['id'], + 'not_deleted' => 0, + 'id_pm' => $pmID, + ) + ); + else + $request = $smcFunc['db_query']('', ' + SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ') + FROM {db_prefix}pm_recipients AS pmr' . ($context['display_mode'] == 2 ? ' + INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)' : '') . ' + WHERE pmr.id_member = {int:current_member} + AND pmr.deleted = {int:not_deleted}' . $labelQuery . ' + AND pmr.id_pm ' . ($descending ? '>' : '<') . ' {int:id_pm}', + array( + 'current_member' => $user_info['id'], + 'not_deleted' => 0, + 'id_pm' => $pmID, + ) + ); + + list ($_GET['start']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // To stop the page index's being abnormal, start the page on the page the message would normally be located on... + $_GET['start'] = $modSettings['defaultMaxMessages'] * (int) ($_GET['start'] / $modSettings['defaultMaxMessages']); + } + } + + // Sanitize and validate pmsg variable if set. + if (isset($_GET['pmsg'])) + { + $pmsg = (int) $_GET['pmsg']; + + if (!isAccessiblePM($pmsg, $context['folder'] == 'sent' ? 'outbox' : 'inbox')) + fatal_lang_error('no_access', false); + } + + // Set up the page index. + $context['page_index'] = constructPageIndex($scripturl . '?action=pm;f=' . $context['folder'] . (isset($_REQUEST['l']) ? ';l=' . (int) $_REQUEST['l'] : '') . ';sort=' . $context['sort_by'] . ($descending ? ';desc' : ''), $_GET['start'], $max_messages, $modSettings['defaultMaxMessages']); + $context['start'] = $_GET['start']; + + // Determine the navigation context (especially useful for the wireless template). + $context['links'] = array( + 'first' => $_GET['start'] >= $modSettings['defaultMaxMessages'] ? $scripturl . '?action=pm;start=0' : '', + 'prev' => $_GET['start'] >= $modSettings['defaultMaxMessages'] ? $scripturl . '?action=pm;start=' . ($_GET['start'] - $modSettings['defaultMaxMessages']) : '', + 'next' => $_GET['start'] + $modSettings['defaultMaxMessages'] < $max_messages ? $scripturl . '?action=pm;start=' . ($_GET['start'] + $modSettings['defaultMaxMessages']) : '', + 'last' => $_GET['start'] + $modSettings['defaultMaxMessages'] < $max_messages ? $scripturl . '?action=pm;start=' . (floor(($max_messages - 1) / $modSettings['defaultMaxMessages']) * $modSettings['defaultMaxMessages']) : '', + 'up' => $scripturl, + ); + $context['page_info'] = array( + 'current_page' => $_GET['start'] / $modSettings['defaultMaxMessages'] + 1, + 'num_pages' => floor(($max_messages - 1) / $modSettings['defaultMaxMessages']) + 1 + ); + + // First work out what messages we need to see - if grouped is a little trickier... + if ($context['display_mode'] == 2) + { + // On a non-default sort due to PostgreSQL we have to do a harder sort. + if ($smcFunc['db_title'] == 'PostgreSQL' && $_GET['sort'] != 'pm.id_pm') + { + $sub_request = $smcFunc['db_query']('', ' + SELECT MAX({raw:sort}) AS sort_param, pm.id_pm_head + FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? ($context['sort_by'] == 'name' ? ' + LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : ' + INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm + AND pmr.id_member = {int:current_member} + AND pmr.deleted = {int:not_deleted} + ' . $labelQuery . ')') . ($context['sort_by'] == 'name' ? ( ' + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})') : '') . ' + WHERE ' . ($context['folder'] == 'sent' ? 'pm.id_member_from = {int:current_member} + AND pm.deleted_by_sender = {int:not_deleted}' : '1=1') . (empty($pmsg) ? '' : ' + AND pm.id_pm = {int:id_pm}') . ' + GROUP BY pm.id_pm_head + ORDER BY sort_param' . ($descending ? ' DESC' : ' ASC') . (empty($pmsg) ? ' + LIMIT ' . $_GET['start'] . ', ' . $modSettings['defaultMaxMessages'] : ''), + array( + 'current_member' => $user_info['id'], + 'not_deleted' => 0, + 'id_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from', + 'id_pm' => isset($pmsg) ? $pmsg : '0', + 'sort' => $_GET['sort'], + ) + ); + $sub_pms = array(); + while ($row = $smcFunc['db_fetch_assoc']($sub_request)) + $sub_pms[$row['id_pm_head']] = $row['sort_param']; + + $smcFunc['db_free_result']($sub_request); + + $request = $smcFunc['db_query']('', ' + SELECT pm.id_pm AS id_pm, pm.id_pm_head + FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? ($context['sort_by'] == 'name' ? ' + LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : ' + INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm + AND pmr.id_member = {int:current_member} + AND pmr.deleted = {int:not_deleted} + ' . $labelQuery . ')') . ($context['sort_by'] == 'name' ? ( ' + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})') : '') . ' + WHERE ' . (empty($sub_pms) ? '0=1' : 'pm.id_pm IN ({array_int:pm_list})') . ' + ORDER BY ' . ($_GET['sort'] == 'pm.id_pm' && $context['folder'] != 'sent' ? 'id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . (empty($pmsg) ? ' + LIMIT ' . $_GET['start'] . ', ' . $modSettings['defaultMaxMessages'] : ''), + array( + 'current_member' => $user_info['id'], + 'pm_list' => array_keys($sub_pms), + 'not_deleted' => 0, + 'sort' => $_GET['sort'], + 'id_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from', + ) + ); + } + else + { + $request = $smcFunc['db_query']('pm_conversation_list', ' + SELECT MAX(pm.id_pm) AS id_pm, pm.id_pm_head + FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? ($context['sort_by'] == 'name' ? ' + LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : ' + INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm + AND pmr.id_member = {int:current_member} + AND pmr.deleted = {int:deleted_by} + ' . $labelQuery . ')') . ($context['sort_by'] == 'name' ? ( ' + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:pm_member})') : '') . ' + WHERE ' . ($context['folder'] == 'sent' ? 'pm.id_member_from = {int:current_member} + AND pm.deleted_by_sender = {int:deleted_by}' : '1=1') . (empty($pmsg) ? '' : ' + AND pm.id_pm = {int:pmsg}') . ' + GROUP BY pm.id_pm_head + ORDER BY ' . ($_GET['sort'] == 'pm.id_pm' && $context['folder'] != 'sent' ? 'id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . (empty($_GET['pmsg']) ? ' + LIMIT ' . $_GET['start'] . ', ' . $modSettings['defaultMaxMessages'] : ''), + array( + 'current_member' => $user_info['id'], + 'deleted_by' => 0, + 'sort' => $_GET['sort'], + 'pm_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from', + 'pmsg' => isset($pmsg) ? (int) $pmsg : 0, + ) + ); + } + } + // This is kinda simple! + else + { + // !!!SLOW This query uses a filesort. (inbox only.) + $request = $smcFunc['db_query']('', ' + SELECT pm.id_pm, pm.id_pm_head, pm.id_member_from + FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '' . ($context['sort_by'] == 'name' ? ' + LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : ' + INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm + AND pmr.id_member = {int:current_member} + AND pmr.deleted = {int:is_deleted} + ' . $labelQuery . ')') . ($context['sort_by'] == 'name' ? ( ' + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:pm_member})') : '') . ' + WHERE ' . ($context['folder'] == 'sent' ? 'pm.id_member_from = {raw:current_member} + AND pm.deleted_by_sender = {int:is_deleted}' : '1=1') . (empty($pmsg) ? '' : ' + AND pm.id_pm = {int:pmsg}') . ' + ORDER BY ' . ($_GET['sort'] == 'pm.id_pm' && $context['folder'] != 'sent' ? 'pmr.id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . (empty($pmsg) ? ' + LIMIT ' . $_GET['start'] . ', ' . $modSettings['defaultMaxMessages'] : ''), + array( + 'current_member' => $user_info['id'], + 'is_deleted' => 0, + 'sort' => $_GET['sort'], + 'pm_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from', + 'pmsg' => isset($pmsg) ? (int) $pmsg : 0, + ) + ); + } + // Load the id_pms and initialize recipients. + $pms = array(); + $lastData = array(); + $posters = $context['folder'] == 'sent' ? array($user_info['id']) : array(); + $recipients = array(); + + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!isset($recipients[$row['id_pm']])) + { + if (isset($row['id_member_from'])) + $posters[$row['id_pm']] = $row['id_member_from']; + $pms[$row['id_pm']] = $row['id_pm']; + $recipients[$row['id_pm']] = array( + 'to' => array(), + 'bcc' => array() + ); + } + + // Keep track of the last message so we know what the head is without another query! + if ((empty($pmID) && (empty($options['view_newest_pm_first']) || !isset($lastData))) || empty($lastData) || (!empty($pmID) && $pmID == $row['id_pm'])) + $lastData = array( + 'id' => $row['id_pm'], + 'head' => $row['id_pm_head'], + ); + } + $smcFunc['db_free_result']($request); + + // Make sure that we have been given a correct head pm id! + if ($context['display_mode'] == 2 && !empty($pmID) && $pmID != $lastData['id']) + fatal_lang_error('no_access', false); + + if (!empty($pms)) + { + // Select the correct current message. + if (empty($pmID)) + $context['current_pm'] = $lastData['id']; + + // This is a list of the pm's that are used for "full" display. + if ($context['display_mode'] == 0) + $display_pms = $pms; + else + $display_pms = array($context['current_pm']); + + // At this point we know the main id_pm's. But - if we are looking at conversations we need the others! + if ($context['display_mode'] == 2) + { + $request = $smcFunc['db_query']('', ' + SELECT pm.id_pm, pm.id_member_from, pm.deleted_by_sender, pmr.id_member, pmr.deleted + FROM {db_prefix}personal_messages AS pm + INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm) + WHERE pm.id_pm_head = {int:id_pm_head} + AND ((pm.id_member_from = {int:current_member} AND pm.deleted_by_sender = {int:not_deleted}) + OR (pmr.id_member = {int:current_member} AND pmr.deleted = {int:not_deleted})) + ORDER BY pm.id_pm', + array( + 'current_member' => $user_info['id'], + 'id_pm_head' => $lastData['head'], + 'not_deleted' => 0, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // This is, frankly, a joke. We will put in a workaround for people sending to themselves - yawn! + if ($context['folder'] == 'sent' && $row['id_member_from'] == $user_info['id'] && $row['deleted_by_sender'] == 1) + continue; + elseif ($row['id_member'] == $user_info['id'] & $row['deleted'] == 1) + continue; + + if (!isset($recipients[$row['id_pm']])) + $recipients[$row['id_pm']] = array( + 'to' => array(), + 'bcc' => array() + ); + $display_pms[] = $row['id_pm']; + $posters[$row['id_pm']] = $row['id_member_from']; + } + $smcFunc['db_free_result']($request); + } + + // This is pretty much EVERY pm! + $all_pms = array_merge($pms, $display_pms); + $all_pms = array_unique($all_pms); + + // Get recipients (don't include bcc-recipients for your inbox, you're not supposed to know :P). + $request = $smcFunc['db_query']('', ' + SELECT pmr.id_pm, mem_to.id_member AS id_member_to, mem_to.real_name AS to_name, pmr.bcc, pmr.labels, pmr.is_read + FROM {db_prefix}pm_recipients AS pmr + LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member) + WHERE pmr.id_pm IN ({array_int:pm_list})', + array( + 'pm_list' => $all_pms, + ) + ); + $context['message_labels'] = array(); + $context['message_replied'] = array(); + $context['message_unread'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($context['folder'] == 'sent' || empty($row['bcc'])) + $recipients[$row['id_pm']][empty($row['bcc']) ? 'to' : 'bcc'][] = empty($row['id_member_to']) ? $txt['guest_title'] : '' . $row['to_name'] . ''; + + if ($row['id_member_to'] == $user_info['id'] && $context['folder'] != 'sent') + { + $context['message_replied'][$row['id_pm']] = $row['is_read'] & 2; + $context['message_unread'][$row['id_pm']] = $row['is_read'] == 0; + + $row['labels'] = $row['labels'] == '' ? array() : explode(',', $row['labels']); + foreach ($row['labels'] as $v) + { + if (isset($context['labels'][(int) $v])) + $context['message_labels'][$row['id_pm']][(int) $v] = array('id' => $v, 'name' => $context['labels'][(int) $v]['name']); + } + } + } + $smcFunc['db_free_result']($request); + + // Make sure we don't load unnecessary data. + if ($context['display_mode'] == 1) + { + foreach ($posters as $k => $v) + if (!in_array($k, $display_pms)) + unset($posters[$k]); + } + + // Load any users.... + $posters = array_unique($posters); + if (!empty($posters)) + loadMemberData($posters); + + // If we're on grouped/restricted view get a restricted list of messages. + if ($context['display_mode'] != 0) + { + // Get the order right. + $orderBy = array(); + foreach (array_reverse($pms) as $pm) + $orderBy[] = 'pm.id_pm = ' . $pm; + + // Seperate query for these bits! + $subjects_request = $smcFunc['db_query']('', ' + SELECT pm.id_pm, pm.subject, pm.id_member_from, pm.msgtime, IFNULL(mem.real_name, pm.from_name) AS from_name, + IFNULL(mem.id_member, 0) AS not_guest + FROM {db_prefix}personal_messages AS pm + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from) + WHERE pm.id_pm IN ({array_int:pm_list}) + ORDER BY ' . implode(', ', $orderBy) . ' + LIMIT ' . count($pms), + array( + 'pm_list' => $pms, + ) + ); + } + + // Execute the query! + $messages_request = $smcFunc['db_query']('', ' + SELECT pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name + FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? ' + LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') . ($context['sort_by'] == 'name' ? ' + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})' : '') . ' + WHERE pm.id_pm IN ({array_int:display_pms})' . ($context['folder'] == 'sent' ? ' + GROUP BY pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name' : '') . ' + ORDER BY ' . ($context['display_mode'] == 2 ? 'pm.id_pm' : $_GET['sort']) . ($descending ? ' DESC' : ' ASC') . ' + LIMIT ' . count($display_pms), + array( + 'display_pms' => $display_pms, + 'id_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from', + ) + ); + } + else + $messages_request = false; + + $context['can_send_pm'] = allowedTo('pm_send'); + if (!WIRELESS) + $context['sub_template'] = 'folder'; + $context['page_title'] = $txt['pm_inbox']; + + // Finally mark the relevant messages as read. + if ($context['folder'] != 'sent' && !empty($context['labels'][(int) $context['current_label_id']]['unread_messages'])) + { + // If the display mode is "old sk00l" do them all... + if ($context['display_mode'] == 0) + markMessages(null, $context['current_label_id']); + // Otherwise do just the current one! + elseif (!empty($context['current_pm'])) + markMessages($display_pms, $context['current_label_id']); + } +} + +// Get a personal message for the theme. (used to save memory.) +function prepareMessageContext($type = 'subject', $reset = false) +{ + global $txt, $scripturl, $modSettings, $context, $messages_request, $memberContext, $recipients, $smcFunc; + global $user_info, $subjects_request; + + // Count the current message number.... + static $counter = null; + if ($counter === null || $reset) + $counter = $context['start']; + + static $temp_pm_selected = null; + if ($temp_pm_selected === null) + { + $temp_pm_selected = isset($_SESSION['pm_selected']) ? $_SESSION['pm_selected'] : array(); + $_SESSION['pm_selected'] = array(); + } + + // If we're in non-boring view do something exciting! + if ($context['display_mode'] != 0 && $subjects_request && $type == 'subject') + { + $subject = $smcFunc['db_fetch_assoc']($subjects_request); + if (!$subject) + { + $smcFunc['db_free_result']($subjects_request); + return false; + } + + $subject['subject'] = $subject['subject'] == '' ? $txt['no_subject'] : $subject['subject']; + censorText($subject['subject']); + + $output = array( + 'id' => $subject['id_pm'], + 'member' => array( + 'id' => $subject['id_member_from'], + 'name' => $subject['from_name'], + 'link' => $subject['not_guest'] ? '' . $subject['from_name'] . '' : $subject['from_name'], + ), + 'recipients' => &$recipients[$subject['id_pm']], + 'subject' => $subject['subject'], + 'time' => timeformat($subject['msgtime']), + 'timestamp' => forum_time(true, $subject['msgtime']), + 'number_recipients' => count($recipients[$subject['id_pm']]['to']), + 'labels' => &$context['message_labels'][$subject['id_pm']], + 'fully_labeled' => count($context['message_labels'][$subject['id_pm']]) == count($context['labels']), + 'is_replied_to' => &$context['message_replied'][$subject['id_pm']], + 'is_unread' => &$context['message_unread'][$subject['id_pm']], + 'is_selected' => !empty($temp_pm_selected) && in_array($subject['id_pm'], $temp_pm_selected), + ); + + return $output; + } + + // Bail if it's false, ie. no messages. + if ($messages_request == false) + return false; + + // Reset the data? + if ($reset == true) + return @$smcFunc['db_data_seek']($messages_request, 0); + + // Get the next one... bail if anything goes wrong. + $message = $smcFunc['db_fetch_assoc']($messages_request); + if (!$message) + { + if ($type != 'subject') + $smcFunc['db_free_result']($messages_request); + + return false; + } + + // Use '(no subject)' if none was specified. + $message['subject'] = $message['subject'] == '' ? $txt['no_subject'] : $message['subject']; + + // Load the message's information - if it's not there, load the guest information. + if (!loadMemberContext($message['id_member_from'], true)) + { + $memberContext[$message['id_member_from']]['name'] = $message['from_name']; + $memberContext[$message['id_member_from']]['id'] = 0; + // Sometimes the forum sends messages itself (Warnings are an example) - in this case don't label it from a guest. + $memberContext[$message['id_member_from']]['group'] = $message['from_name'] == $context['forum_name'] ? '' : $txt['guest_title']; + $memberContext[$message['id_member_from']]['link'] = $message['from_name']; + $memberContext[$message['id_member_from']]['email'] = ''; + $memberContext[$message['id_member_from']]['show_email'] = showEmailAddress(true, 0); + $memberContext[$message['id_member_from']]['is_guest'] = true; + } + else + { + $memberContext[$message['id_member_from']]['can_view_profile'] = allowedTo('profile_view_any') || ($message['id_member_from'] == $user_info['id'] && allowedTo('profile_view_own')); + $memberContext[$message['id_member_from']]['can_see_warning'] = !isset($context['disabled_fields']['warning_status']) && $memberContext[$message['id_member_from']]['warning_status'] && ($context['user']['can_mod'] || (!empty($modSettings['warning_show']) && ($modSettings['warning_show'] > 1 || $message['id_member_from'] == $user_info['id']))); + } + + // Censor all the important text... + censorText($message['body']); + censorText($message['subject']); + + // Run UBBC interpreter on the message. + $message['body'] = parse_bbc($message['body'], true, 'pm' . $message['id_pm']); + + // Send the array. + $output = array( + 'alternate' => $counter % 2, + 'id' => $message['id_pm'], + 'member' => &$memberContext[$message['id_member_from']], + 'subject' => $message['subject'], + 'time' => timeformat($message['msgtime']), + 'timestamp' => forum_time(true, $message['msgtime']), + 'counter' => $counter, + 'body' => $message['body'], + 'recipients' => &$recipients[$message['id_pm']], + 'number_recipients' => count($recipients[$message['id_pm']]['to']), + 'labels' => &$context['message_labels'][$message['id_pm']], + 'fully_labeled' => count($context['message_labels'][$message['id_pm']]) == count($context['labels']), + 'is_replied_to' => &$context['message_replied'][$message['id_pm']], + 'is_unread' => &$context['message_unread'][$message['id_pm']], + 'is_selected' => !empty($temp_pm_selected) && in_array($message['id_pm'], $temp_pm_selected), + ); + + $counter++; + + return $output; +} + +function MessageSearch() +{ + global $context, $txt, $scripturl, $modSettings, $smcFunc; + + if (isset($_REQUEST['params'])) + { + $temp_params = explode('|"|', base64_decode(strtr($_REQUEST['params'], array(' ' => '+')))); + $context['search_params'] = array(); + foreach ($temp_params as $i => $data) + { + @list ($k, $v) = explode('|\'|', $data); + $context['search_params'][$k] = $v; + } + } + if (isset($_REQUEST['search'])) + $context['search_params']['search'] = un_htmlspecialchars($_REQUEST['search']); + + if (isset($context['search_params']['search'])) + $context['search_params']['search'] = htmlspecialchars($context['search_params']['search']); + if (isset($context['search_params']['userspec'])) + $context['search_params']['userspec'] = htmlspecialchars($context['search_params']['userspec']); + + if (!empty($context['search_params']['searchtype'])) + $context['search_params']['searchtype'] = 2; + + if (!empty($context['search_params']['minage'])) + $context['search_params']['minage'] = (int) $context['search_params']['minage']; + + if (!empty($context['search_params']['maxage'])) + $context['search_params']['maxage'] = (int) $context['search_params']['maxage']; + + $context['search_params']['subject_only'] = !empty($context['search_params']['subject_only']); + $context['search_params']['show_complete'] = !empty($context['search_params']['show_complete']); + + // Create the array of labels to be searched. + $context['search_labels'] = array(); + $searchedLabels = isset($context['search_params']['labels']) && $context['search_params']['labels'] != '' ? explode(',', $context['search_params']['labels']) : array(); + foreach ($context['labels'] as $label) + { + $context['search_labels'][] = array( + 'id' => $label['id'], + 'name' => $label['name'], + 'checked' => !empty($searchedLabels) ? in_array($label['id'], $searchedLabels) : true, + ); + } + + // Are all the labels checked? + $context['check_all'] = empty($searchedLabels) || count($context['search_labels']) == count($searchedLabels); + + // Load the error text strings if there were errors in the search. + if (!empty($context['search_errors'])) + { + loadLanguage('Errors'); + $context['search_errors']['messages'] = array(); + foreach ($context['search_errors'] as $search_error => $dummy) + { + if ($search_error == 'messages') + continue; + + $context['search_errors']['messages'][] = $txt['error_' . $search_error]; + } + } + + $context['simple_search'] = isset($context['search_params']['advanced']) ? empty($context['search_params']['advanced']) : !empty($modSettings['simpleSearch']) && !isset($_REQUEST['advanced']); + $context['page_title'] = $txt['pm_search_title']; + $context['sub_template'] = 'search'; + $context['linktree'][] = array( + 'url' => $scripturl . '?action=pm;sa=search', + 'name' => $txt['pm_search_bar_title'], + ); +} + +function MessageSearch2() +{ + global $scripturl, $modSettings, $user_info, $context, $txt; + global $memberContext, $smcFunc; + + if (!empty($context['load_average']) && !empty($modSettings['loadavg_search']) && $context['load_average'] >= $modSettings['loadavg_search']) + fatal_lang_error('loadavg_search_disabled', false); + + // !!! For the moment force the folder to the inbox. + $context['folder'] = 'inbox'; + + // Some useful general permissions. + $context['can_send_pm'] = allowedTo('pm_send'); + + // Some hardcoded veriables that can be tweaked if required. + $maxMembersToSearch = 500; + + // Extract all the search parameters. + $search_params = array(); + if (isset($_REQUEST['params'])) + { + $temp_params = explode('|"|', base64_decode(strtr($_REQUEST['params'], array(' ' => '+')))); + foreach ($temp_params as $i => $data) + { + @list ($k, $v) = explode('|\'|', $data); + $search_params[$k] = $v; + } + } + + $context['start'] = isset($_GET['start']) ? (int) $_GET['start'] : 0; + + // Store whether simple search was used (needed if the user wants to do another query). + if (!isset($search_params['advanced'])) + $search_params['advanced'] = empty($_REQUEST['advanced']) ? 0 : 1; + + // 1 => 'allwords' (default, don't set as param) / 2 => 'anywords'. + if (!empty($search_params['searchtype']) || (!empty($_REQUEST['searchtype']) && $_REQUEST['searchtype'] == 2)) + $search_params['searchtype'] = 2; + + // Minimum age of messages. Default to zero (don't set param in that case). + if (!empty($search_params['minage']) || (!empty($_REQUEST['minage']) && $_REQUEST['minage'] > 0)) + $search_params['minage'] = !empty($search_params['minage']) ? (int) $search_params['minage'] : (int) $_REQUEST['minage']; + + // Maximum age of messages. Default to infinite (9999 days: param not set). + if (!empty($search_params['maxage']) || (!empty($_REQUEST['maxage']) && $_REQUEST['maxage'] != 9999)) + $search_params['maxage'] = !empty($search_params['maxage']) ? (int) $search_params['maxage'] : (int) $_REQUEST['maxage']; + + $search_params['subject_only'] = !empty($search_params['subject_only']) || !empty($_REQUEST['subject_only']); + $search_params['show_complete'] = !empty($search_params['show_complete']) || !empty($_REQUEST['show_complete']); + + // Default the user name to a wildcard matching every user (*). + if (!empty($search_params['user_spec']) || (!empty($_REQUEST['userspec']) && $_REQUEST['userspec'] != '*')) + $search_params['userspec'] = isset($search_params['userspec']) ? $search_params['userspec'] : $_REQUEST['userspec']; + + // This will be full of all kinds of parameters! + $searchq_parameters = array(); + + // If there's no specific user, then don't mention it in the main query. + if (empty($search_params['userspec'])) + $userQuery = ''; + else + { + $userString = strtr($smcFunc['htmlspecialchars']($search_params['userspec'], ENT_QUOTES), array('"' => '"')); + $userString = strtr($userString, array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_')); + + preg_match_all('~"([^"]+)"~', $userString, $matches); + $possible_users = array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $userString))); + + for ($k = 0, $n = count($possible_users); $k < $n; $k++) + { + $possible_users[$k] = trim($possible_users[$k]); + + if (strlen($possible_users[$k]) == 0) + unset($possible_users[$k]); + } + + // Who matches those criteria? + // !!! This doesn't support sent item searching. + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE real_name LIKE {raw:real_name_implode}', + array( + 'real_name_implode' => '\'' . implode('\' OR real_name LIKE \'', $possible_users) . '\'', + ) + ); + // Simply do nothing if there're too many members matching the criteria. + if ($smcFunc['db_num_rows']($request) > $maxMembersToSearch) + $userQuery = ''; + elseif ($smcFunc['db_num_rows']($request) == 0) + { + $userQuery = 'AND pm.id_member_from = 0 AND (pm.from_name LIKE {raw:guest_user_name_implode})'; + $searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR pm.from_name LIKE \'', $possible_users) . '\''; + } + else + { + $memberlist = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $memberlist[] = $row['id_member']; + $userQuery = 'AND (pm.id_member_from IN ({array_int:member_list}) OR (pm.id_member_from = 0 AND (pm.from_name LIKE {raw:guest_user_name_implode})))'; + $searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR pm.from_name LIKE \'', $possible_users) . '\''; + $searchq_parameters['member_list'] = $memberlist; + } + $smcFunc['db_free_result']($request); + } + + // Setup the sorting variables... + // !!! Add more in here! + $sort_columns = array( + 'pm.id_pm', + ); + if (empty($search_params['sort']) && !empty($_REQUEST['sort'])) + list ($search_params['sort'], $search_params['sort_dir']) = array_pad(explode('|', $_REQUEST['sort']), 2, ''); + $search_params['sort'] = !empty($search_params['sort']) && in_array($search_params['sort'], $sort_columns) ? $search_params['sort'] : 'pm.id_pm'; + $search_params['sort_dir'] = !empty($search_params['sort_dir']) && $search_params['sort_dir'] == 'asc' ? 'asc' : 'desc'; + + // Sort out any labels we may be searching by. + $labelQuery = ''; + if ($context['folder'] == 'inbox' && !empty($search_params['advanced']) && $context['currently_using_labels']) + { + // Came here from pagination? Put them back into $_REQUEST for sanitization. + if (isset($search_params['labels'])) + $_REQUEST['searchlabel'] = explode(',', $search_params['labels']); + + // Assuming we have some labels - make them all integers. + if (!empty($_REQUEST['searchlabel']) && is_array($_REQUEST['searchlabel'])) + { + foreach ($_REQUEST['searchlabel'] as $key => $id) + $_REQUEST['searchlabel'][$key] = (int) $id; + } + else + $_REQUEST['searchlabel'] = array(); + + // Now that everything is cleaned up a bit, make the labels a param. + $search_params['labels'] = implode(',', $_REQUEST['searchlabel']); + + // No labels selected? That must be an error! + if (empty($_REQUEST['searchlabel'])) + $context['search_errors']['no_labels_selected'] = true; + // Otherwise prepare the query! + elseif (count($_REQUEST['searchlabel']) != count($context['labels'])) + { + $labelQuery = ' + AND {raw:label_implode}'; + + $labelStatements = array(); + foreach ($_REQUEST['searchlabel'] as $label) + $labelStatements[] = $smcFunc['db_quote']('FIND_IN_SET({string:label}, pmr.labels) != 0', array( + 'label' => $label, + )); + + $searchq_parameters['label_implode'] = '(' . implode(' OR ', $labelStatements) . ')'; + } + } + + // What are we actually searching for? + $search_params['search'] = !empty($search_params['search']) ? $search_params['search'] : (isset($_REQUEST['search']) ? $_REQUEST['search'] : ''); + // If we ain't got nothing - we should error! + if (!isset($search_params['search']) || $search_params['search'] == '') + $context['search_errors']['invalid_search_string'] = true; + + // Extract phrase parts first (e.g. some words "this is a phrase" some more words.) + preg_match_all('~(?:^|\s)([-]?)"([^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), $search_params['search'], $matches, PREG_PATTERN_ORDER); + $searchArray = $matches[2]; + + // Remove the phrase parts and extract the words. + $tempSearch = explode(' ', preg_replace('~(?:^|\s)(?:[-]?)"(?:[^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), ' ', $search_params['search'])); + + // A minus sign in front of a word excludes the word.... so... + $excludedWords = array(); + + // .. first, we check for things like -"some words", but not "-some words". + foreach ($matches[1] as $index => $word) + if ($word == '-') + { + $word = $smcFunc['strtolower'](trim($searchArray[$index])); + if (strlen($word) > 0) + $excludedWords[] = $word; + unset($searchArray[$index]); + } + + // Now we look for -test, etc.... normaller. + foreach ($tempSearch as $index => $word) + if (strpos(trim($word), '-') === 0) + { + $word = substr($smcFunc['strtolower'](trim($word)), 1); + if (strlen($word) > 0) + $excludedWords[] = $word; + unset($tempSearch[$index]); + } + + $searchArray = array_merge($searchArray, $tempSearch); + + // Trim everything and make sure there are no words that are the same. + foreach ($searchArray as $index => $value) + { + $searchArray[$index] = $smcFunc['strtolower'](trim($value)); + if ($searchArray[$index] == '') + unset($searchArray[$index]); + else + { + // Sort out entities first. + $searchArray[$index] = $smcFunc['htmlspecialchars']($searchArray[$index]); + } + } + $searchArray = array_unique($searchArray); + + // Create an array of replacements for highlighting. + $context['mark'] = array(); + foreach ($searchArray as $word) + $context['mark'][$word] = '' . $word . ''; + + // This contains *everything* + $searchWords = array_merge($searchArray, $excludedWords); + + // Make sure at least one word is being searched for. + if (empty($searchArray)) + $context['search_errors']['invalid_search_string'] = true; + + // Sort out the search query so the user can edit it - if they want. + $context['search_params'] = $search_params; + if (isset($context['search_params']['search'])) + $context['search_params']['search'] = htmlspecialchars($context['search_params']['search']); + if (isset($context['search_params']['userspec'])) + $context['search_params']['userspec'] = htmlspecialchars($context['search_params']['userspec']); + + // Now we have all the parameters, combine them together for pagination and the like... + $context['params'] = array(); + foreach ($search_params as $k => $v) + $context['params'][] = $k . '|\'|' . $v; + $context['params'] = base64_encode(implode('|"|', $context['params'])); + + // Compile the subject query part. + $andQueryParts = array(); + + foreach ($searchWords as $index => $word) + { + if ($word == '') + continue; + + if ($search_params['subject_only']) + $andQueryParts[] = 'pm.subject' . (in_array($word, $excludedWords) ? ' NOT' : '') . ' LIKE {string:search_' . $index . '}'; + else + $andQueryParts[] = '(pm.subject' . (in_array($word, $excludedWords) ? ' NOT' : '') . ' LIKE {string:search_' . $index . '} ' . (in_array($word, $excludedWords) ? 'AND pm.body NOT' : 'OR pm.body') . ' LIKE {string:search_' . $index . '})'; + $searchq_parameters['search_' . $index] = '%' . strtr($word, array('_' => '\\_', '%' => '\\%')) . '%'; + } + + $searchQuery = ' 1=1'; + if (!empty($andQueryParts)) + $searchQuery = implode(!empty($search_params['searchtype']) && $search_params['searchtype'] == 2 ? ' OR ' : ' AND ', $andQueryParts); + + // Age limits? + $timeQuery = ''; + if (!empty($search_params['minage'])) + $timeQuery .= ' AND pm.msgtime < ' . (time() - $search_params['minage'] * 86400); + if (!empty($search_params['maxage'])) + $timeQuery .= ' AND pm.msgtime > ' . (time() - $search_params['maxage'] * 86400); + + // If we have errors - return back to the first screen... + if (!empty($context['search_errors'])) + { + $_REQUEST['params'] = $context['params']; + return MessageSearch(); + } + + // Get the amount of results. + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}pm_recipients AS pmr + INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm) + WHERE ' . ($context['folder'] == 'inbox' ? ' + pmr.id_member = {int:current_member} + AND pmr.deleted = {int:not_deleted}' : ' + pm.id_member_from = {int:current_member} + AND pm.deleted_by_sender = {int:not_deleted}') . ' + ' . $userQuery . $labelQuery . $timeQuery . ' + AND (' . $searchQuery . ')', + array_merge($searchq_parameters, array( + 'current_member' => $user_info['id'], + 'not_deleted' => 0, + )) + ); + list ($numResults) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Get all the matching messages... using standard search only (No caching and the like!) + // !!! This doesn't support sent item searching yet. + $request = $smcFunc['db_query']('', ' + SELECT pm.id_pm, pm.id_pm_head, pm.id_member_from + FROM {db_prefix}pm_recipients AS pmr + INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm) + WHERE ' . ($context['folder'] == 'inbox' ? ' + pmr.id_member = {int:current_member} + AND pmr.deleted = {int:not_deleted}' : ' + pm.id_member_from = {int:current_member} + AND pm.deleted_by_sender = {int:not_deleted}') . ' + ' . $userQuery . $labelQuery . $timeQuery . ' + AND (' . $searchQuery . ') + ORDER BY ' . $search_params['sort'] . ' ' . $search_params['sort_dir'] . ' + LIMIT ' . $context['start'] . ', ' . $modSettings['search_results_per_page'], + array_merge($searchq_parameters, array( + 'current_member' => $user_info['id'], + 'not_deleted' => 0, + )) + ); + $foundMessages = array(); + $posters = array(); + $head_pms = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $foundMessages[] = $row['id_pm']; + $posters[] = $row['id_member_from']; + $head_pms[$row['id_pm']] = $row['id_pm_head']; + } + $smcFunc['db_free_result']($request); + + // Find the real head pms! + if ($context['display_mode'] == 2 && !empty($head_pms)) + { + $request = $smcFunc['db_query']('', ' + SELECT MAX(pm.id_pm) AS id_pm, pm.id_pm_head + FROM {db_prefix}personal_messages AS pm + INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm) + WHERE pm.id_pm_head IN ({array_int:head_pms}) + AND pmr.id_member = {int:current_member} + AND pmr.deleted = {int:not_deleted} + GROUP BY pm.id_pm_head + LIMIT {int:limit}', + array( + 'head_pms' => array_unique($head_pms), + 'current_member' => $user_info['id'], + 'not_deleted' => 0, + 'limit' => count($head_pms), + ) + ); + $real_pm_ids = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $real_pm_ids[$row['id_pm_head']] = $row['id_pm']; + $smcFunc['db_free_result']($request); + } + + // Load the users... + $posters = array_unique($posters); + if (!empty($posters)) + loadMemberData($posters); + + // Sort out the page index. + $context['page_index'] = constructPageIndex($scripturl . '?action=pm;sa=search2;params=' . $context['params'], $_GET['start'], $numResults, $modSettings['search_results_per_page'], false); + + $context['message_labels'] = array(); + $context['message_replied'] = array(); + $context['personal_messages'] = array(); + + if (!empty($foundMessages)) + { + // Now get recipients (but don't include bcc-recipients for your inbox, you're not supposed to know :P!) + $request = $smcFunc['db_query']('', ' + SELECT + pmr.id_pm, mem_to.id_member AS id_member_to, mem_to.real_name AS to_name, + pmr.bcc, pmr.labels, pmr.is_read + FROM {db_prefix}pm_recipients AS pmr + LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member) + WHERE pmr.id_pm IN ({array_int:message_list})', + array( + 'message_list' => $foundMessages, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($context['folder'] == 'sent' || empty($row['bcc'])) + $recipients[$row['id_pm']][empty($row['bcc']) ? 'to' : 'bcc'][] = empty($row['id_member_to']) ? $txt['guest_title'] : '' . $row['to_name'] . ''; + + if ($row['id_member_to'] == $user_info['id'] && $context['folder'] != 'sent') + { + $context['message_replied'][$row['id_pm']] = $row['is_read'] & 2; + + $row['labels'] = $row['labels'] == '' ? array() : explode(',', $row['labels']); + // This is a special need for linking to messages. + foreach ($row['labels'] as $v) + { + if (isset($context['labels'][(int) $v])) + $context['message_labels'][$row['id_pm']][(int) $v] = array('id' => $v, 'name' => $context['labels'][(int) $v]['name']); + + // Here we find the first label on a message - for linking to posts in results + if (!isset($context['first_label'][$row['id_pm']]) && !in_array('-1', $row['labels'])) + $context['first_label'][$row['id_pm']] = (int) $v; + } + } + } + + // Prepare the query for the callback! + $request = $smcFunc['db_query']('', ' + SELECT pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name + FROM {db_prefix}personal_messages AS pm + WHERE pm.id_pm IN ({array_int:message_list}) + ORDER BY ' . $search_params['sort'] . ' ' . $search_params['sort_dir'] . ' + LIMIT ' . count($foundMessages), + array( + 'message_list' => $foundMessages, + ) + ); + $counter = 0; + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // If there's no message subject, use the default. + $row['subject'] = $row['subject'] == '' ? $txt['no_subject'] : $row['subject']; + + // Load this posters context info, if it ain't there then fill in the essentials... + if (!loadMemberContext($row['id_member_from'], true)) + { + $memberContext[$row['id_member_from']]['name'] = $row['from_name']; + $memberContext[$row['id_member_from']]['id'] = 0; + $memberContext[$row['id_member_from']]['group'] = $txt['guest_title']; + $memberContext[$row['id_member_from']]['link'] = $row['from_name']; + $memberContext[$row['id_member_from']]['email'] = ''; + $memberContext[$row['id_member_from']]['show_email'] = showEmailAddress(true, 0); + $memberContext[$row['id_member_from']]['is_guest'] = true; + } + + // Censor anything we don't want to see... + censorText($row['body']); + censorText($row['subject']); + + // Parse out any BBC... + $row['body'] = parse_bbc($row['body'], true, 'pm' . $row['id_pm']); + + $href = $scripturl . '?action=pm;f=' . $context['folder'] . (isset($context['first_label'][$row['id_pm']]) ? ';l=' . $context['first_label'][$row['id_pm']] : '') . ';pmid=' . ($context['display_mode'] == 2 && isset($real_pm_ids[$head_pms[$row['id_pm']]]) ? $real_pm_ids[$head_pms[$row['id_pm']]] : $row['id_pm']) . '#msg' . $row['id_pm']; + $context['personal_messages'][] = array( + 'id' => $row['id_pm'], + 'member' => &$memberContext[$row['id_member_from']], + 'subject' => $row['subject'], + 'body' => $row['body'], + 'time' => timeformat($row['msgtime']), + 'recipients' => &$recipients[$row['id_pm']], + 'labels' => &$context['message_labels'][$row['id_pm']], + 'fully_labeled' => count($context['message_labels'][$row['id_pm']]) == count($context['labels']), + 'is_replied_to' => &$context['message_replied'][$row['id_pm']], + 'href' => $href, + 'link' => '' . $row['subject'] . '', + 'counter' => ++$counter, + ); + } + $smcFunc['db_free_result']($request); + } + + // Finish off the context. + $context['page_title'] = $txt['pm_search_title']; + $context['sub_template'] = 'search_results'; + $context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'search'; + $context['linktree'][] = array( + 'url' => $scripturl . '?action=pm;sa=search', + 'name' => $txt['pm_search_bar_title'], + ); +} + +// Send a new message? +function MessagePost() +{ + global $txt, $sourcedir, $scripturl, $modSettings; + global $context, $options, $smcFunc, $language, $user_info; + + isAllowedTo('pm_send'); + + loadLanguage('PersonalMessage'); + // Just in case it was loaded from somewhere else. + if (!WIRELESS) + { + loadTemplate('PersonalMessage'); + $context['sub_template'] = 'send'; + } + + // Extract out the spam settings - cause it's neat. + list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']); + + // Set the title... + $context['page_title'] = $txt['send_message']; + + $context['reply'] = isset($_REQUEST['pmsg']) || isset($_REQUEST['quote']); + + // Check whether we've gone over the limit of messages we can send per hour. + if (!empty($modSettings['pm_posts_per_hour']) && !allowedTo(array('admin_forum', 'moderate_forum', 'send_mail')) && $user_info['mod_cache']['bq'] == '0=1' && $user_info['mod_cache']['gq'] == '0=1') + { + // How many messages have they sent this last hour? + $request = $smcFunc['db_query']('', ' + SELECT COUNT(pr.id_pm) AS post_count + FROM {db_prefix}personal_messages AS pm + INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm) + WHERE pm.id_member_from = {int:current_member} + AND pm.msgtime > {int:msgtime}', + array( + 'current_member' => $user_info['id'], + 'msgtime' => time() - 3600, + ) + ); + list ($postCount) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (!empty($postCount) && $postCount >= $modSettings['pm_posts_per_hour']) + fatal_lang_error('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour'])); + } + + // Quoting/Replying to a message? + if (!empty($_REQUEST['pmsg'])) + { + $pmsg = (int) $_REQUEST['pmsg']; + + // Make sure this is yours. + if (!isAccessiblePM($pmsg)) + fatal_lang_error('no_access', false); + + // Work out whether this is one you've received? + $request = $smcFunc['db_query']('', ' + SELECT + id_pm + FROM {db_prefix}pm_recipients + WHERE id_pm = {int:id_pm} + AND id_member = {int:current_member} + LIMIT 1', + array( + 'current_member' => $user_info['id'], + 'id_pm' => $pmsg, + ) + ); + $isReceived = $smcFunc['db_num_rows']($request) != 0; + $smcFunc['db_free_result']($request); + + // Get the quoted message (and make sure you're allowed to see this quote!). + $request = $smcFunc['db_query']('', ' + SELECT + pm.id_pm, CASE WHEN pm.id_pm_head = {int:id_pm_head_empty} THEN pm.id_pm ELSE pm.id_pm_head END AS pm_head, + pm.body, pm.subject, pm.msgtime, mem.member_name, IFNULL(mem.id_member, 0) AS id_member, + IFNULL(mem.real_name, pm.from_name) AS real_name + FROM {db_prefix}personal_messages AS pm' . (!$isReceived ? '' : ' + INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:id_pm})') . ' + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from) + WHERE pm.id_pm = {int:id_pm}' . (!$isReceived ? ' + AND pm.id_member_from = {int:current_member}' : ' + AND pmr.id_member = {int:current_member}') . ' + LIMIT 1', + array( + 'current_member' => $user_info['id'], + 'id_pm_head_empty' => 0, + 'id_pm' => $pmsg, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('pm_not_yours', false); + $row_quoted = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Censor the message. + censorText($row_quoted['subject']); + censorText($row_quoted['body']); + + // Add 'Re: ' to it.... + if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix'))) + { + if ($language === $user_info['language']) + $context['response_prefix'] = $txt['response_prefix']; + else + { + loadLanguage('index', $language, false); + $context['response_prefix'] = $txt['response_prefix']; + loadLanguage('index'); + } + cache_put_data('response_prefix', $context['response_prefix'], 600); + } + $form_subject = $row_quoted['subject']; + if ($context['reply'] && trim($context['response_prefix']) != '' && $smcFunc['strpos']($form_subject, trim($context['response_prefix'])) !== 0) + $form_subject = $context['response_prefix'] . $form_subject; + + if (isset($_REQUEST['quote'])) + { + // Remove any nested quotes and
... + $form_message = preg_replace('~
~i', "\n", $row_quoted['body']); + if (!empty($modSettings['removeNestedQuotes'])) + $form_message = preg_replace(array('~\n?\[quote.*?\].+?\[/quote\]\n?~is', '~^\n~', '~\[/quote\]~'), '', $form_message); + if (empty($row_quoted['id_member'])) + $form_message = '[quote author="' . $row_quoted['real_name'] . '"]' . "\n" . $form_message . "\n" . '[/quote]'; + else + $form_message = '[quote author=' . $row_quoted['real_name'] . ' link=action=profile;u=' . $row_quoted['id_member'] . ' date=' . $row_quoted['msgtime'] . ']' . "\n" . $form_message . "\n" . '[/quote]'; + } + else + $form_message = ''; + + // Do the BBC thang on the message. + $row_quoted['body'] = parse_bbc($row_quoted['body'], true, 'pm' . $row_quoted['id_pm']); + + // Set up the quoted message array. + $context['quoted_message'] = array( + 'id' => $row_quoted['id_pm'], + 'pm_head' => $row_quoted['pm_head'], + 'member' => array( + 'name' => $row_quoted['real_name'], + 'username' => $row_quoted['member_name'], + 'id' => $row_quoted['id_member'], + 'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '', + 'link' => !empty($row_quoted['id_member']) ? '' . $row_quoted['real_name'] . '' : $row_quoted['real_name'], + ), + 'subject' => $row_quoted['subject'], + 'time' => timeformat($row_quoted['msgtime']), + 'timestamp' => forum_time(true, $row_quoted['msgtime']), + 'body' => $row_quoted['body'] + ); + } + else + { + $context['quoted_message'] = false; + $form_subject = ''; + $form_message = ''; + } + + $context['recipients'] = array( + 'to' => array(), + 'bcc' => array(), + ); + + // Sending by ID? Replying to all? Fetch the real_name(s). + if (isset($_REQUEST['u'])) + { + // If the user is replying to all, get all the other members this was sent to.. + if ($_REQUEST['u'] == 'all' && isset($row_quoted)) + { + // Firstly, to reply to all we clearly already have $row_quoted - so have the original member from. + if ($row_quoted['id_member'] != $user_info['id']) + $context['recipients']['to'][] = array( + 'id' => $row_quoted['id_member'], + 'name' => htmlspecialchars($row_quoted['real_name']), + ); + + // Now to get the others. + $request = $smcFunc['db_query']('', ' + SELECT mem.id_member, mem.real_name + FROM {db_prefix}pm_recipients AS pmr + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member) + WHERE pmr.id_pm = {int:id_pm} + AND pmr.id_member != {int:current_member} + AND pmr.bcc = {int:not_bcc}', + array( + 'current_member' => $user_info['id'], + 'id_pm' => $pmsg, + 'not_bcc' => 0, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['recipients']['to'][] = array( + 'id' => $row['id_member'], + 'name' => $row['real_name'], + ); + $smcFunc['db_free_result']($request); + } + else + { + $_REQUEST['u'] = explode(',', $_REQUEST['u']); + foreach ($_REQUEST['u'] as $key => $uID) + $_REQUEST['u'][$key] = (int) $uID; + + $_REQUEST['u'] = array_unique($_REQUEST['u']); + + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name + FROM {db_prefix}members + WHERE id_member IN ({array_int:member_list}) + LIMIT ' . count($_REQUEST['u']), + array( + 'member_list' => $_REQUEST['u'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['recipients']['to'][] = array( + 'id' => $row['id_member'], + 'name' => $row['real_name'], + ); + $smcFunc['db_free_result']($request); + } + + // Get a literal name list in case the user has JavaScript disabled. + $names = array(); + foreach ($context['recipients']['to'] as $to) + $names[] = $to['name']; + $context['to_value'] = empty($names) ? '' : '"' . implode('", "', $names) . '"'; + } + else + $context['to_value'] = ''; + + // Set the defaults... + $context['subject'] = $form_subject != '' ? $form_subject : $txt['no_subject']; + $context['message'] = str_replace(array('"', '<', '>', ' '), array('"', '<', '>', ' '), $form_message); + $context['post_error'] = array(); + $context['copy_to_outbox'] = !empty($options['copy_to_outbox']); + + // And build the link tree. + $context['linktree'][] = array( + 'url' => $scripturl . '?action=pm;sa=send', + 'name' => $txt['new_message'] + ); + + $modSettings['disable_wysiwyg'] = !empty($modSettings['disable_wysiwyg']) || empty($modSettings['enableBBC']); + + // Needed for the WYSIWYG editor. + require_once($sourcedir . '/Subs-Editor.php'); + + // Now create the editor. + $editorOptions = array( + 'id' => 'message', + 'value' => $context['message'], + 'height' => '175px', + 'width' => '100%', + 'labels' => array( + 'post_button' => $txt['send_message'], + ), + ); + create_control_richedit($editorOptions); + + // Store the ID for old compatibility. + $context['post_box_name'] = $editorOptions['id']; + + $context['bcc_value'] = ''; + + $context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification']; + if ($context['require_verification']) + { + $verificationOptions = array( + 'id' => 'pm', + ); + $context['require_verification'] = create_control_verification($verificationOptions); + $context['visual_verification_id'] = $verificationOptions['id']; + } + + // Register this form and get a sequence number in $context. + checkSubmitOnce('register'); +} + +// An error in the message... +function messagePostError($error_types, $named_recipients, $recipient_ids = array()) +{ + global $txt, $context, $scripturl, $modSettings; + global $smcFunc, $user_info, $sourcedir; + + $context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'send'; + + if (!WIRELESS) + $context['sub_template'] = 'send'; + + $context['page_title'] = $txt['send_message']; + + // Got some known members? + $context['recipients'] = array( + 'to' => array(), + 'bcc' => array(), + ); + if (!empty($recipient_ids['to']) || !empty($recipient_ids['bcc'])) + { + $allRecipients = array_merge($recipient_ids['to'], $recipient_ids['bcc']); + + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name + FROM {db_prefix}members + WHERE id_member IN ({array_int:member_list})', + array( + 'member_list' => $allRecipients, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $recipientType = in_array($row['id_member'], $recipient_ids['bcc']) ? 'bcc' : 'to'; + $context['recipients'][$recipientType][] = array( + 'id' => $row['id_member'], + 'name' => $row['real_name'], + ); + } + $smcFunc['db_free_result']($request); + } + + // Set everything up like before.... + $context['subject'] = isset($_REQUEST['subject']) ? $smcFunc['htmlspecialchars']($_REQUEST['subject']) : ''; + $context['message'] = isset($_REQUEST['message']) ? str_replace(array(' '), array('  '), $smcFunc['htmlspecialchars']($_REQUEST['message'])) : ''; + $context['copy_to_outbox'] = !empty($_REQUEST['outbox']); + $context['reply'] = !empty($_REQUEST['replied_to']); + + if ($context['reply']) + { + $_REQUEST['replied_to'] = (int) $_REQUEST['replied_to']; + + $request = $smcFunc['db_query']('', ' + SELECT + pm.id_pm, CASE WHEN pm.id_pm_head = {int:no_id_pm_head} THEN pm.id_pm ELSE pm.id_pm_head END AS pm_head, + pm.body, pm.subject, pm.msgtime, mem.member_name, IFNULL(mem.id_member, 0) AS id_member, + IFNULL(mem.real_name, pm.from_name) AS real_name + FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '' : ' + INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:replied_to})') . ' + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from) + WHERE pm.id_pm = {int:replied_to}' . ($context['folder'] == 'sent' ? ' + AND pm.id_member_from = {int:current_member}' : ' + AND pmr.id_member = {int:current_member}') . ' + LIMIT 1', + array( + 'current_member' => $user_info['id'], + 'no_id_pm_head' => 0, + 'replied_to' => $_REQUEST['replied_to'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('pm_not_yours', false); + $row_quoted = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + censorText($row_quoted['subject']); + censorText($row_quoted['body']); + + $context['quoted_message'] = array( + 'id' => $row_quoted['id_pm'], + 'pm_head' => $row_quoted['pm_head'], + 'member' => array( + 'name' => $row_quoted['real_name'], + 'username' => $row_quoted['member_name'], + 'id' => $row_quoted['id_member'], + 'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '', + 'link' => !empty($row_quoted['id_member']) ? '' . $row_quoted['real_name'] . '' : $row_quoted['real_name'], + ), + 'subject' => $row_quoted['subject'], + 'time' => timeformat($row_quoted['msgtime']), + 'timestamp' => forum_time(true, $row_quoted['msgtime']), + 'body' => parse_bbc($row_quoted['body'], true, 'pm' . $row_quoted['id_pm']), + ); + } + + // Build the link tree.... + $context['linktree'][] = array( + 'url' => $scripturl . '?action=pm;sa=send', + 'name' => $txt['new_message'] + ); + + // Set each of the errors for the template. + loadLanguage('Errors'); + $context['post_error'] = array( + 'messages' => array(), + ); + foreach ($error_types as $error_type) + { + $context['post_error'][$error_type] = true; + if (isset($txt['error_' . $error_type])) + { + if ($error_type == 'long_message') + $txt['error_' . $error_type] = sprintf($txt['error_' . $error_type], $modSettings['max_messageLength']); + $context['post_error']['messages'][] = $txt['error_' . $error_type]; + } + } + + // We need to load the editor once more. + require_once($sourcedir . '/Subs-Editor.php'); + + // Create it... + $editorOptions = array( + 'id' => 'message', + 'value' => $context['message'], + 'width' => '90%', + 'labels' => array( + 'post_button' => $txt['send_message'], + ), + ); + create_control_richedit($editorOptions); + + // ... and store the ID again... + $context['post_box_name'] = $editorOptions['id']; + + // Check whether we need to show the code again. + $context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification']; + if ($context['require_verification']) + { + require_once($sourcedir . '/Subs-Editor.php'); + $verificationOptions = array( + 'id' => 'pm', + ); + $context['require_verification'] = create_control_verification($verificationOptions); + $context['visual_verification_id'] = $verificationOptions['id']; + } + + $context['to_value'] = empty($named_recipients['to']) ? '' : '"' . implode('", "', $named_recipients['to']) . '"'; + $context['bcc_value'] = empty($named_recipients['bcc']) ? '' : '"' . implode('", "', $named_recipients['bcc']) . '"'; + + // No check for the previous submission is needed. + checkSubmitOnce('free'); + + // Acquire a new form sequence number. + checkSubmitOnce('register'); +} + +// Send it! +function MessagePost2() +{ + global $txt, $context, $sourcedir; + global $user_info, $modSettings, $scripturl, $smcFunc; + + isAllowedTo('pm_send'); + require_once($sourcedir . '/Subs-Auth.php'); + + loadLanguage('PersonalMessage', '', false); + + // Extract out the spam settings - it saves database space! + list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']); + + // Check whether we've gone over the limit of messages we can send per hour - fatal error if fails! + if (!empty($modSettings['pm_posts_per_hour']) && !allowedTo(array('admin_forum', 'moderate_forum', 'send_mail')) && $user_info['mod_cache']['bq'] == '0=1' && $user_info['mod_cache']['gq'] == '0=1') + { + // How many have they sent this last hour? + $request = $smcFunc['db_query']('', ' + SELECT COUNT(pr.id_pm) AS post_count + FROM {db_prefix}personal_messages AS pm + INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm) + WHERE pm.id_member_from = {int:current_member} + AND pm.msgtime > {int:msgtime}', + array( + 'current_member' => $user_info['id'], + 'msgtime' => time() - 3600, + ) + ); + list ($postCount) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (!empty($postCount) && $postCount >= $modSettings['pm_posts_per_hour']) + fatal_lang_error('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour'])); + } + + // If we came from WYSIWYG then turn it back into BBC regardless. + if (!empty($_POST['message_mode']) && isset($_POST['message'])) + { + require_once($sourcedir . '/Subs-Editor.php'); + $_POST['message'] = html_to_bbc($_POST['message']); + + // We need to unhtml it now as it gets done shortly. + $_POST['message'] = un_htmlspecialchars($_POST['message']); + + // We need this in case of errors etc. + $_REQUEST['message'] = $_POST['message']; + } + + // Initialize the errors we're about to make. + $post_errors = array(); + + // If your session timed out, show an error, but do allow to re-submit. + if (checkSession('post', '', false) != '') + $post_errors[] = 'session_timeout'; + + $_REQUEST['subject'] = isset($_REQUEST['subject']) ? trim($_REQUEST['subject']) : ''; + $_REQUEST['to'] = empty($_POST['to']) ? (empty($_GET['to']) ? '' : $_GET['to']) : $_POST['to']; + $_REQUEST['bcc'] = empty($_POST['bcc']) ? (empty($_GET['bcc']) ? '' : $_GET['bcc']) : $_POST['bcc']; + + // Route the input from the 'u' parameter to the 'to'-list. + if (!empty($_POST['u'])) + $_POST['recipient_to'] = explode(',', $_POST['u']); + + // Construct the list of recipients. + $recipientList = array(); + $namedRecipientList = array(); + $namesNotFound = array(); + foreach (array('to', 'bcc') as $recipientType) + { + // First, let's see if there's user ID's given. + $recipientList[$recipientType] = array(); + if (!empty($_POST['recipient_' . $recipientType]) && is_array($_POST['recipient_' . $recipientType])) + { + foreach ($_POST['recipient_' . $recipientType] as $recipient) + $recipientList[$recipientType][] = (int) $recipient; + } + + // Are there also literal names set? + if (!empty($_REQUEST[$recipientType])) + { + // We're going to take out the "s anyway ;). + $recipientString = strtr($_REQUEST[$recipientType], array('\\"' => '"')); + + preg_match_all('~"([^"]+)"~', $recipientString, $matches); + $namedRecipientList[$recipientType] = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $recipientString)))); + + foreach ($namedRecipientList[$recipientType] as $index => $recipient) + { + if (strlen(trim($recipient)) > 0) + $namedRecipientList[$recipientType][$index] = $smcFunc['htmlspecialchars']($smcFunc['strtolower'](trim($recipient))); + else + unset($namedRecipientList[$recipientType][$index]); + } + + if (!empty($namedRecipientList[$recipientType])) + { + $foundMembers = findMembers($namedRecipientList[$recipientType]); + + // Assume all are not found, until proven otherwise. + $namesNotFound[$recipientType] = $namedRecipientList[$recipientType]; + + foreach ($foundMembers as $member) + { + $testNames = array( + $smcFunc['strtolower']($member['username']), + $smcFunc['strtolower']($member['name']), + $smcFunc['strtolower']($member['email']), + ); + + if (count(array_intersect($testNames, $namedRecipientList[$recipientType])) !== 0) + { + $recipientList[$recipientType][] = $member['id']; + + // Get rid of this username, since we found it. + $namesNotFound[$recipientType] = array_diff($namesNotFound[$recipientType], $testNames); + } + } + } + } + + // Selected a recipient to be deleted? Remove them now. + if (!empty($_POST['delete_recipient'])) + $recipientList[$recipientType] = array_diff($recipientList[$recipientType], array((int) $_POST['delete_recipient'])); + + // Make sure we don't include the same name twice + $recipientList[$recipientType] = array_unique($recipientList[$recipientType]); + } + + // Are we changing the recipients some how? + $is_recipient_change = !empty($_POST['delete_recipient']) || !empty($_POST['to_submit']) || !empty($_POST['bcc_submit']); + + // Check if there's at least one recipient. + if (empty($recipientList['to']) && empty($recipientList['bcc'])) + $post_errors[] = 'no_to'; + + // Make sure that we remove the members who did get it from the screen. + if (!$is_recipient_change) + { + foreach ($recipientList as $recipientType => $dummy) + { + if (!empty($namesNotFound[$recipientType])) + { + $post_errors[] = 'bad_' . $recipientType; + + // Since we already have a post error, remove the previous one. + $post_errors = array_diff($post_errors, array('no_to')); + + foreach ($namesNotFound[$recipientType] as $name) + $context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name); + } + } + } + + // Did they make any mistakes? + if ($_REQUEST['subject'] == '') + $post_errors[] = 'no_subject'; + if (!isset($_REQUEST['message']) || $_REQUEST['message'] == '') + $post_errors[] = 'no_message'; + elseif (!empty($modSettings['max_messageLength']) && $smcFunc['strlen']($_REQUEST['message']) > $modSettings['max_messageLength']) + $post_errors[] = 'long_message'; + else + { + // Preparse the message. + $message = $_REQUEST['message']; + preparsecode($message); + + // Make sure there's still some content left without the tags. + if ($smcFunc['htmltrim'](strip_tags(parse_bbc($smcFunc['htmlspecialchars']($message, ENT_QUOTES), false), '')) === '' && (!allowedTo('admin_forum') || strpos($message, '[html]') === false)) + $post_errors[] = 'no_message'; + } + + // Wrong verification code? + if (!$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification']) + { + require_once($sourcedir . '/Subs-Editor.php'); + $verificationOptions = array( + 'id' => 'pm', + ); + $context['require_verification'] = create_control_verification($verificationOptions, true); + + if (is_array($context['require_verification'])) + { + $post_errors = array_merge($post_errors, $context['require_verification']); + } + } + + // If they did, give a chance to make ammends. + if (!empty($post_errors) && !$is_recipient_change && !isset($_REQUEST['preview'])) + return messagePostError($post_errors, $namedRecipientList, $recipientList); + + // Want to take a second glance before you send? + if (isset($_REQUEST['preview'])) + { + // Set everything up to be displayed. + $context['preview_subject'] = $smcFunc['htmlspecialchars']($_REQUEST['subject']); + $context['preview_message'] = $smcFunc['htmlspecialchars']($_REQUEST['message'], ENT_QUOTES); + preparsecode($context['preview_message'], true); + + // Parse out the BBC if it is enabled. + $context['preview_message'] = parse_bbc($context['preview_message']); + + // Censor, as always. + censorText($context['preview_subject']); + censorText($context['preview_message']); + + // Set a descriptive title. + $context['page_title'] = $txt['preview'] . ' - ' . $context['preview_subject']; + + // Pretend they messed up but don't ignore if they really did :P. + return messagePostError($post_errors, $namedRecipientList, $recipientList); + } + + // Adding a recipient cause javascript ain't working? + elseif ($is_recipient_change) + { + // Maybe we couldn't find one? + foreach ($namesNotFound as $recipientType => $names) + { + $post_errors[] = 'bad_' . $recipientType; + foreach ($names as $name) + $context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name); + } + + return messagePostError(array(), $namedRecipientList, $recipientList); + } + + // Before we send the PM, let's make sure we don't have an abuse of numbers. + elseif (!empty($modSettings['max_pm_recipients']) && count($recipientList['to']) + count($recipientList['bcc']) > $modSettings['max_pm_recipients'] && !allowedTo(array('moderate_forum', 'send_mail', 'admin_forum'))) + { + $context['send_log'] = array( + 'sent' => array(), + 'failed' => array(sprintf($txt['pm_too_many_recipients'], $modSettings['max_pm_recipients'])), + ); + return messagePostError($post_errors, $namedRecipientList, $recipientList); + } + + // Protect from message spamming. + spamProtection('pm'); + + // Prevent double submission of this form. + checkSubmitOnce('check'); + + // Do the actual sending of the PM. + if (!empty($recipientList['to']) || !empty($recipientList['bcc'])) + $context['send_log'] = sendpm($recipientList, $_REQUEST['subject'], $_REQUEST['message'], !empty($_REQUEST['outbox']), null, !empty($_REQUEST['pm_head']) ? (int) $_REQUEST['pm_head'] : 0); + else + $context['send_log'] = array( + 'sent' => array(), + 'failed' => array() + ); + + // Mark the message as "replied to". + if (!empty($context['send_log']['sent']) && !empty($_REQUEST['replied_to']) && isset($_REQUEST['f']) && $_REQUEST['f'] == 'inbox') + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}pm_recipients + SET is_read = is_read | 2 + WHERE id_pm = {int:replied_to} + AND id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'replied_to' => (int) $_REQUEST['replied_to'], + ) + ); + } + + // If one or more of the recipient were invalid, go back to the post screen with the failed usernames. + if (!empty($context['send_log']['failed'])) + return messagePostError($post_errors, $namesNotFound, array( + 'to' => array_intersect($recipientList['to'], $context['send_log']['failed']), + 'bcc' => array_intersect($recipientList['bcc'], $context['send_log']['failed']) + )); + + // Message sent successfully? + if (!empty($context['send_log']) && empty($context['send_log']['failed'])) + $context['current_label_redirect'] = $context['current_label_redirect'] . ';done=sent'; + + // Go back to the where they sent from, if possible... + redirectexit($context['current_label_redirect']); +} + +// This function lists all buddies for wireless protocols. +function WirelessAddBuddy() +{ + global $scripturl, $txt, $user_info, $context, $smcFunc; + + isAllowedTo('pm_send'); + $context['page_title'] = $txt['wireless_pm_add_buddy']; + + $current_buddies = empty($_REQUEST['u']) ? array() : explode(',', $_REQUEST['u']); + foreach ($current_buddies as $key => $buddy) + $current_buddies[$key] = (int) $buddy; + + $base_url = $scripturl . '?action=pm;sa=send;u=' . (empty($current_buddies) ? '' : implode(',', $current_buddies) . ','); + $context['pm_href'] = $scripturl . '?action=pm;sa=send' . (empty($current_buddies) ? '' : ';u=' . implode(',', $current_buddies)); + + $context['buddies'] = array(); + if (!empty($user_info['buddies'])) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name + FROM {db_prefix}members + WHERE id_member IN ({array_int:buddy_list}) + ORDER BY real_name + LIMIT ' . count($user_info['buddies']), + array( + 'buddy_list' => $user_info['buddies'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['buddies'][] = array( + 'id' => $row['id_member'], + 'name' => $row['real_name'], + 'selected' => in_array($row['id_member'], $current_buddies), + 'add_href' => $base_url . $row['id_member'], + ); + $smcFunc['db_free_result']($request); + } +} + +// This function performs all additional stuff... +function MessageActionsApply() +{ + global $txt, $context, $user_info, $options, $smcFunc; + + checkSession('request'); + + if (isset($_REQUEST['del_selected'])) + $_REQUEST['pm_action'] = 'delete'; + + if (isset($_REQUEST['pm_action']) && $_REQUEST['pm_action'] != '' && !empty($_REQUEST['pms']) && is_array($_REQUEST['pms'])) + { + foreach ($_REQUEST['pms'] as $pm) + $_REQUEST['pm_actions'][(int) $pm] = $_REQUEST['pm_action']; + } + + if (empty($_REQUEST['pm_actions'])) + redirectexit($context['current_label_redirect']); + + // If we are in conversation, we may need to apply this to every message in the conversation. + if ($context['display_mode'] == 2 && isset($_REQUEST['conversation'])) + { + $id_pms = array(); + foreach ($_REQUEST['pm_actions'] as $pm => $dummy) + $id_pms[] = (int) $pm; + + $request = $smcFunc['db_query']('', ' + SELECT id_pm_head, id_pm + FROM {db_prefix}personal_messages + WHERE id_pm IN ({array_int:id_pms})', + array( + 'id_pms' => $id_pms, + ) + ); + $pm_heads = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $pm_heads[$row['id_pm_head']] = $row['id_pm']; + $smcFunc['db_free_result']($request); + + $request = $smcFunc['db_query']('', ' + SELECT id_pm, id_pm_head + FROM {db_prefix}personal_messages + WHERE id_pm_head IN ({array_int:pm_heads})', + array( + 'pm_heads' => array_keys($pm_heads), + ) + ); + // Copy the action from the single to PM to the others. + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (isset($pm_heads[$row['id_pm_head']]) && isset($_REQUEST['pm_actions'][$pm_heads[$row['id_pm_head']]])) + $_REQUEST['pm_actions'][$row['id_pm']] = $_REQUEST['pm_actions'][$pm_heads[$row['id_pm_head']]]; + } + $smcFunc['db_free_result']($request); + } + + $to_delete = array(); + $to_label = array(); + $label_type = array(); + foreach ($_REQUEST['pm_actions'] as $pm => $action) + { + if ($action === 'delete') + $to_delete[] = (int) $pm; + else + { + if (substr($action, 0, 4) == 'add_') + { + $type = 'add'; + $action = substr($action, 4); + } + elseif (substr($action, 0, 4) == 'rem_') + { + $type = 'rem'; + $action = substr($action, 4); + } + else + $type = 'unk'; + + if ($action == '-1' || $action == '0' || (int) $action > 0) + { + $to_label[(int) $pm] = (int) $action; + $label_type[(int) $pm] = $type; + } + } + } + + // Deleting, it looks like? + if (!empty($to_delete)) + deleteMessages($to_delete, $context['display_mode'] == 2 ? null : $context['folder']); + + // Are we labeling anything? + if (!empty($to_label) && $context['folder'] == 'inbox') + { + $updateErrors = 0; + + // Get information about each message... + $request = $smcFunc['db_query']('', ' + SELECT id_pm, labels + FROM {db_prefix}pm_recipients + WHERE id_member = {int:current_member} + AND id_pm IN ({array_int:to_label}) + LIMIT ' . count($to_label), + array( + 'current_member' => $user_info['id'], + 'to_label' => array_keys($to_label), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $labels = $row['labels'] == '' ? array('-1') : explode(',', trim($row['labels'])); + + // Already exists? Then... unset it! + $ID_LABEL = array_search($to_label[$row['id_pm']], $labels); + if ($ID_LABEL !== false && $label_type[$row['id_pm']] !== 'add') + unset($labels[$ID_LABEL]); + elseif ($label_type[$row['id_pm']] !== 'rem') + $labels[] = $to_label[$row['id_pm']]; + + if (!empty($options['pm_remove_inbox_label']) && $to_label[$row['id_pm']] != '-1' && ($key = array_search('-1', $labels)) !== false) + unset($labels[$key]); + + $set = implode(',', array_unique($labels)); + if ($set == '') + $set = '-1'; + + // Check that this string isn't going to be too large for the database. + if ($set > 60) + $updateErrors++; + else + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}pm_recipients + SET labels = {string:labels} + WHERE id_pm = {int:id_pm} + AND id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'id_pm' => $row['id_pm'], + 'labels' => $set, + ) + ); + } + } + $smcFunc['db_free_result']($request); + + // Any errors? + // !!! Separate the sprintf? + if (!empty($updateErrors)) + fatal_lang_error('labels_too_many', true, array($updateErrors)); + } + + // Back to the folder. + $_SESSION['pm_selected'] = array_keys($to_label); + redirectexit($context['current_label_redirect'] . (count($to_label) == 1 ? '#msg' . $_SESSION['pm_selected'][0] : ''), count($to_label) == 1 && $context['browser']['is_ie']); +} + +// Are you sure you want to PERMANENTLY (mostly) delete ALL your messages? +function MessageKillAllQuery() +{ + global $txt, $context; + + // Only have to set up the template.... + $context['sub_template'] = 'ask_delete'; + $context['page_title'] = $txt['delete_all']; + $context['delete_all'] = $_REQUEST['f'] == 'all'; + + // And set the folder name... + $txt['delete_all'] = str_replace('PMBOX', $context['folder'] != 'sent' ? $txt['inbox'] : $txt['sent_items'], $txt['delete_all']); +} + +// Delete ALL the messages! +function MessageKillAll() +{ + global $context; + + checkSession('get'); + + // If all then delete all messages the user has. + if ($_REQUEST['f'] == 'all') + deleteMessages(null, null); + // Otherwise just the selected folder. + else + deleteMessages(null, $_REQUEST['f'] != 'sent' ? 'inbox' : 'sent'); + + // Done... all gone. + redirectexit($context['current_label_redirect']); +} + +// This function allows the user to delete all messages older than so many days. +function MessagePrune() +{ + global $txt, $context, $user_info, $scripturl, $smcFunc; + + // Actually delete the messages. + if (isset($_REQUEST['age'])) + { + checkSession(); + + // Calculate the time to delete before. + $deleteTime = max(0, time() - (86400 * (int) $_REQUEST['age'])); + + // Array to store the IDs in. + $toDelete = array(); + + // Select all the messages they have sent older than $deleteTime. + $request = $smcFunc['db_query']('', ' + SELECT id_pm + FROM {db_prefix}personal_messages + WHERE deleted_by_sender = {int:not_deleted} + AND id_member_from = {int:current_member} + AND msgtime < {int:msgtime}', + array( + 'current_member' => $user_info['id'], + 'not_deleted' => 0, + 'msgtime' => $deleteTime, + ) + ); + while ($row = $smcFunc['db_fetch_row']($request)) + $toDelete[] = $row[0]; + $smcFunc['db_free_result']($request); + + // Select all messages in their inbox older than $deleteTime. + $request = $smcFunc['db_query']('', ' + SELECT pmr.id_pm + FROM {db_prefix}pm_recipients AS pmr + INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm) + WHERE pmr.deleted = {int:not_deleted} + AND pmr.id_member = {int:current_member} + AND pm.msgtime < {int:msgtime}', + array( + 'current_member' => $user_info['id'], + 'not_deleted' => 0, + 'msgtime' => $deleteTime, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $toDelete[] = $row['id_pm']; + $smcFunc['db_free_result']($request); + + // Delete the actual messages. + deleteMessages($toDelete); + + // Go back to their inbox. + redirectexit($context['current_label_redirect']); + } + + // Build the link tree elements. + $context['linktree'][] = array( + 'url' => $scripturl . '?action=pm;sa=prune', + 'name' => $txt['pm_prune'] + ); + + $context['sub_template'] = 'prune'; + $context['page_title'] = $txt['pm_prune']; +} + +// Delete the specified personal messages. +function deleteMessages($personal_messages, $folder = null, $owner = null) +{ + global $user_info, $smcFunc; + + if ($owner === null) + $owner = array($user_info['id']); + elseif (empty($owner)) + return; + elseif (!is_array($owner)) + $owner = array($owner); + + if ($personal_messages !== null) + { + if (empty($personal_messages) || !is_array($personal_messages)) + return; + + foreach ($personal_messages as $index => $delete_id) + $personal_messages[$index] = (int) $delete_id; + + $where = ' + AND id_pm IN ({array_int:pm_list})'; + } + else + $where = ''; + + if ($folder == 'sent' || $folder === null) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}personal_messages + SET deleted_by_sender = {int:is_deleted} + WHERE id_member_from IN ({array_int:member_list}) + AND deleted_by_sender = {int:not_deleted}' . $where, + array( + 'member_list' => $owner, + 'is_deleted' => 1, + 'not_deleted' => 0, + 'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(), + ) + ); + } + if ($folder != 'sent' || $folder === null) + { + // Calculate the number of messages each member's gonna lose... + $request = $smcFunc['db_query']('', ' + SELECT id_member, COUNT(*) AS num_deleted_messages, CASE WHEN is_read & 1 >= 1 THEN 1 ELSE 0 END AS is_read + FROM {db_prefix}pm_recipients + WHERE id_member IN ({array_int:member_list}) + AND deleted = {int:not_deleted}' . $where . ' + GROUP BY id_member, is_read', + array( + 'member_list' => $owner, + 'not_deleted' => 0, + 'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(), + ) + ); + // ...And update the statistics accordingly - now including unread messages!. + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['is_read']) + updateMemberData($row['id_member'], array('instant_messages' => $where == '' ? 0 : 'instant_messages - ' . $row['num_deleted_messages'])); + else + updateMemberData($row['id_member'], array('instant_messages' => $where == '' ? 0 : 'instant_messages - ' . $row['num_deleted_messages'], 'unread_messages' => $where == '' ? 0 : 'unread_messages - ' . $row['num_deleted_messages'])); + + // If this is the current member we need to make their message count correct. + if ($user_info['id'] == $row['id_member']) + { + $user_info['messages'] -= $row['num_deleted_messages']; + if (!($row['is_read'])) + $user_info['unread_messages'] -= $row['num_deleted_messages']; + } + } + $smcFunc['db_free_result']($request); + + // Do the actual deletion. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}pm_recipients + SET deleted = {int:is_deleted} + WHERE id_member IN ({array_int:member_list}) + AND deleted = {int:not_deleted}' . $where, + array( + 'member_list' => $owner, + 'is_deleted' => 1, + 'not_deleted' => 0, + 'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(), + ) + ); + } + + // If sender and recipients all have deleted their message, it can be removed. + $request = $smcFunc['db_query']('', ' + SELECT pm.id_pm AS sender, pmr.id_pm + FROM {db_prefix}personal_messages AS pm + LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm AND pmr.deleted = {int:not_deleted}) + WHERE pm.deleted_by_sender = {int:is_deleted} + ' . str_replace('id_pm', 'pm.id_pm', $where) . ' + GROUP BY sender, pmr.id_pm + HAVING pmr.id_pm IS null', + array( + 'not_deleted' => 0, + 'is_deleted' => 1, + 'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(), + ) + ); + $remove_pms = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $remove_pms[] = $row['sender']; + $smcFunc['db_free_result']($request); + + if (!empty($remove_pms)) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}personal_messages + WHERE id_pm IN ({array_int:pm_list})', + array( + 'pm_list' => $remove_pms, + ) + ); + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}pm_recipients + WHERE id_pm IN ({array_int:pm_list})', + array( + 'pm_list' => $remove_pms, + ) + ); + } + + // Any cached numbers may be wrong now. + cache_put_data('labelCounts:' . $user_info['id'], null, 720); +} + +// Mark personal messages read. +function markMessages($personal_messages = null, $label = null, $owner = null) +{ + global $user_info, $context, $smcFunc; + + if ($owner === null) + $owner = $user_info['id']; + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}pm_recipients + SET is_read = is_read | 1 + WHERE id_member = {int:id_member} + AND NOT (is_read & 1 >= 1)' . ($label === null ? '' : ' + AND FIND_IN_SET({string:label}, labels) != 0') . ($personal_messages !== null ? ' + AND id_pm IN ({array_int:personal_messages})' : ''), + array( + 'personal_messages' => $personal_messages, + 'id_member' => $owner, + 'label' => $label, + ) + ); + + // If something wasn't marked as read, get the number of unread messages remaining. + if ($smcFunc['db_affected_rows']() > 0) + { + if ($owner == $user_info['id']) + { + foreach ($context['labels'] as $label) + $context['labels'][(int) $label['id']]['unread_messages'] = 0; + } + + $result = $smcFunc['db_query']('', ' + SELECT labels, COUNT(*) AS num + FROM {db_prefix}pm_recipients + WHERE id_member = {int:id_member} + AND NOT (is_read & 1 >= 1) + AND deleted = {int:is_not_deleted} + GROUP BY labels', + array( + 'id_member' => $owner, + 'is_not_deleted' => 0, + ) + ); + $total_unread = 0; + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $total_unread += $row['num']; + + if ($owner != $user_info['id']) + continue; + + $this_labels = explode(',', $row['labels']); + foreach ($this_labels as $this_label) + $context['labels'][(int) $this_label]['unread_messages'] += $row['num']; + } + $smcFunc['db_free_result']($result); + + // Need to store all this. + cache_put_data('labelCounts:' . $owner, $context['labels'], 720); + updateMemberData($owner, array('unread_messages' => $total_unread)); + + // If it was for the current member, reflect this in the $user_info array too. + if ($owner == $user_info['id']) + $user_info['unread_messages'] = $total_unread; + } +} + +// This function handles adding, deleting and editing labels on messages. +function ManageLabels() +{ + global $txt, $context, $user_info, $scripturl, $smcFunc; + + // Build the link tree elements... + $context['linktree'][] = array( + 'url' => $scripturl . '?action=pm;sa=manlabels', + 'name' => $txt['pm_manage_labels'] + ); + + $context['page_title'] = $txt['pm_manage_labels']; + $context['sub_template'] = 'labels'; + + $the_labels = array(); + // Add all existing labels to the array to save, slashing them as necessary... + foreach ($context['labels'] as $label) + { + if ($label['id'] != -1) + $the_labels[$label['id']] = $label['name']; + } + + if (isset($_POST[$context['session_var']])) + { + checkSession('post'); + + // This will be for updating messages. + $message_changes = array(); + $new_labels = array(); + $rule_changes = array(); + + // Will most likely need this. + LoadRules(); + + // Adding a new label? + if (isset($_POST['add'])) + { + $_POST['label'] = strtr($smcFunc['htmlspecialchars'](trim($_POST['label'])), array(',' => ',')); + + if ($smcFunc['strlen']($_POST['label']) > 30) + $_POST['label'] = $smcFunc['substr']($_POST['label'], 0, 30); + if ($_POST['label'] != '') + $the_labels[] = $_POST['label']; + } + // Deleting an existing label? + elseif (isset($_POST['delete'], $_POST['delete_label'])) + { + $i = 0; + foreach ($the_labels as $id => $name) + { + if (isset($_POST['delete_label'][$id])) + { + unset($the_labels[$id]); + $message_changes[$id] = true; + } + else + $new_labels[$id] = $i++; + } + } + // The hardest one to deal with... changes. + elseif (isset($_POST['save']) && !empty($_POST['label_name'])) + { + $i = 0; + foreach ($the_labels as $id => $name) + { + if ($id == -1) + continue; + elseif (isset($_POST['label_name'][$id])) + { + $_POST['label_name'][$id] = trim(strtr($smcFunc['htmlspecialchars']($_POST['label_name'][$id]), array(',' => ','))); + + if ($smcFunc['strlen']($_POST['label_name'][$id]) > 30) + $_POST['label_name'][$id] = $smcFunc['substr']($_POST['label_name'][$id], 0, 30); + if ($_POST['label_name'][$id] != '') + { + $the_labels[(int) $id] = $_POST['label_name'][$id]; + $new_labels[$id] = $i++; + } + else + { + unset($the_labels[(int) $id]); + $message_changes[(int) $id] = true; + } + } + else + $new_labels[$id] = $i++; + } + } + + // Save the label status. + updateMemberData($user_info['id'], array('message_labels' => implode(',', $the_labels))); + + // Update all the messages currently with any label changes in them! + if (!empty($message_changes)) + { + $searchArray = array_keys($message_changes); + + if (!empty($new_labels)) + { + for ($i = max($searchArray) + 1, $n = max(array_keys($new_labels)); $i <= $n; $i++) + $searchArray[] = $i; + } + + // Now find the messages to change. + $request = $smcFunc['db_query']('', ' + SELECT id_pm, labels + FROM {db_prefix}pm_recipients + WHERE FIND_IN_SET({raw:find_label_implode}, labels) != 0 + AND id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'find_label_implode' => '\'' . implode('\', labels) != 0 OR FIND_IN_SET(\'', $searchArray) . '\'', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Do the long task of updating them... + $toChange = explode(',', $row['labels']); + + foreach ($toChange as $key => $value) + if (in_array($value, $searchArray)) + { + if (isset($new_labels[$value])) + $toChange[$key] = $new_labels[$value]; + else + unset($toChange[$key]); + } + + if (empty($toChange)) + $toChange[] = '-1'; + + // Update the message. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}pm_recipients + SET labels = {string:new_labels} + WHERE id_pm = {int:id_pm} + AND id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'id_pm' => $row['id_pm'], + 'new_labels' => implode(',', array_unique($toChange)), + ) + ); + } + $smcFunc['db_free_result']($request); + + // Now do the same the rules - check through each rule. + foreach ($context['rules'] as $k => $rule) + { + // Each action... + foreach ($rule['actions'] as $k2 => $action) + { + if ($action['t'] != 'lab' || !in_array($action['v'], $searchArray)) + continue; + + $rule_changes[] = $rule['id']; + // If we're here we have a label which is either changed or gone... + if (isset($new_labels[$action['v']])) + $context['rules'][$k]['actions'][$k2]['v'] = $new_labels[$action['v']]; + else + unset($context['rules'][$k]['actions'][$k2]); + } + } + } + + // If we have rules to change do so now. + if (!empty($rule_changes)) + { + $rule_changes = array_unique($rule_changes); + // Update/delete as appropriate. + foreach ($rule_changes as $k => $id) + if (!empty($context['rules'][$id]['actions'])) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}pm_rules + SET actions = {string:actions} + WHERE id_rule = {int:id_rule} + AND id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'id_rule' => $id, + 'actions' => serialize($context['rules'][$id]['actions']), + ) + ); + unset($rule_changes[$k]); + } + + // Anything left here means it's lost all actions... + if (!empty($rule_changes)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}pm_rules + WHERE id_rule IN ({array_int:rule_list}) + AND id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'rule_list' => $rule_changes, + ) + ); + } + + // Make sure we're not caching this! + cache_put_data('labelCounts:' . $user_info['id'], null, 720); + + // To make the changes appear right away, redirect. + redirectexit('action=pm;sa=manlabels'); + } +} + +// Edit Personal Message Settings +function MessageSettings() +{ + global $txt, $user_settings, $user_info, $context, $sourcedir, $smcFunc; + global $scripturl, $profile_vars, $cur_profile, $user_profile; + + // Need this for the display. + require_once($sourcedir . '/Profile.php'); + require_once($sourcedir . '/Profile-Modify.php'); + + // We want them to submit back to here. + $context['profile_custom_submit_url'] = $scripturl . '?action=pm;sa=settings;save'; + + loadMemberData($user_info['id'], false, 'profile'); + $cur_profile = $user_profile[$user_info['id']]; + + loadLanguage('Profile'); + loadTemplate('Profile'); + + $context['page_title'] = $txt['pm_settings']; + $context['user']['is_owner'] = true; + $context['id_member'] = $user_info['id']; + $context['require_password'] = false; + $context['menu_item_selected'] = 'settings'; + $context['submit_button_text'] = $txt['pm_settings']; + $context['profile_header_text'] = $txt['personal_messages']; + + // Add our position to the linktree. + $context['linktree'][] = array( + 'url' => $scripturl . '?action=pm;sa=settings', + 'name' => $txt['pm_settings'] + ); + + // Are they saving? + if (isset($_REQUEST['save'])) + { + checkSession('post'); + + // Mimic what profile would do. + $_POST = htmltrim__recursive($_POST); + $_POST = htmlspecialchars__recursive($_POST); + + // Save the fields. + saveProfileFields(); + + if (!empty($profile_vars)) + updateMemberData($user_info['id'], $profile_vars); + } + + // Load up the fields. + pmprefs($user_info['id']); +} + +// Allows a user to report a personal message they receive to the administrator. +function ReportMessage() +{ + global $txt, $context, $scripturl, $sourcedir; + global $user_info, $language, $modSettings, $smcFunc; + + // Check that this feature is even enabled! + if (empty($modSettings['enableReportPM']) || empty($_REQUEST['pmsg'])) + fatal_lang_error('no_access', false); + + $pmsg = (int) $_REQUEST['pmsg']; + + if (!isAccessiblePM($pmsg, 'inbox')) + fatal_lang_error('no_access', false); + + $context['pm_id'] = $pmsg; + $context['page_title'] = $txt['pm_report_title']; + + // If we're here, just send the user to the template, with a few useful context bits. + if (!isset($_POST['report'])) + { + $context['sub_template'] = 'report_message'; + + // !!! I don't like being able to pick who to send it to. Favoritism, etc. sucks. + // Now, get all the administrators. + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name + FROM {db_prefix}members + WHERE id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0 + ORDER BY real_name', + array( + 'admin_group' => 1, + ) + ); + $context['admins'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['admins'][$row['id_member']] = $row['real_name']; + $smcFunc['db_free_result']($request); + + // How many admins in total? + $context['admin_count'] = count($context['admins']); + } + // Otherwise, let's get down to the sending stuff. + else + { + // Check the session before proceeding any further! + checkSession('post'); + + // First, pull out the message contents, and verify it actually went to them! + $request = $smcFunc['db_query']('', ' + SELECT pm.subject, pm.body, pm.msgtime, pm.id_member_from, IFNULL(m.real_name, pm.from_name) AS sender_name + FROM {db_prefix}personal_messages AS pm + INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm) + LEFT JOIN {db_prefix}members AS m ON (m.id_member = pm.id_member_from) + WHERE pm.id_pm = {int:id_pm} + AND pmr.id_member = {int:current_member} + AND pmr.deleted = {int:not_deleted} + LIMIT 1', + array( + 'current_member' => $user_info['id'], + 'id_pm' => $context['pm_id'], + 'not_deleted' => 0, + ) + ); + // Can only be a hacker here! + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_access', false); + list ($subject, $body, $time, $memberFromID, $memberFromName) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Remove the line breaks... + $body = preg_replace('~
~i', "\n", $body); + + // Get any other recipients of the email. + $request = $smcFunc['db_query']('', ' + SELECT mem_to.id_member AS id_member_to, mem_to.real_name AS to_name, pmr.bcc + FROM {db_prefix}pm_recipients AS pmr + LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member) + WHERE pmr.id_pm = {int:id_pm} + AND pmr.id_member != {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'id_pm' => $context['pm_id'], + ) + ); + $recipients = array(); + $hidden_recipients = 0; + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // If it's hidden still don't reveal their names - privacy after all ;) + if ($row['bcc']) + $hidden_recipients++; + else + $recipients[] = '[url=' . $scripturl . '?action=profile;u=' . $row['id_member_to'] . ']' . $row['to_name'] . '[/url]'; + } + $smcFunc['db_free_result']($request); + + if ($hidden_recipients) + $recipients[] = sprintf($txt['pm_report_pm_hidden'], $hidden_recipients); + + // Now let's get out and loop through the admins. + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name, lngfile + FROM {db_prefix}members + WHERE (id_group = {int:admin_id} OR FIND_IN_SET({int:admin_id}, additional_groups) != 0) + ' . (empty($_POST['ID_ADMIN']) ? '' : 'AND id_member = {int:specific_admin}') . ' + ORDER BY lngfile', + array( + 'admin_id' => 1, + 'specific_admin' => isset($_POST['ID_ADMIN']) ? (int) $_POST['ID_ADMIN'] : 0, + ) + ); + + // Maybe we shouldn't advertise this? + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_access', false); + + $memberFromName = un_htmlspecialchars($memberFromName); + + // Prepare the message storage array. + $messagesToSend = array(); + // Loop through each admin, and add them to the right language pile... + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Need to send in the correct language! + $cur_language = empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']; + + if (!isset($messagesToSend[$cur_language])) + { + loadLanguage('PersonalMessage', $cur_language, false); + + // Make the body. + $report_body = str_replace(array('{REPORTER}', '{SENDER}'), array(un_htmlspecialchars($user_info['name']), $memberFromName), $txt['pm_report_pm_user_sent']); + $report_body .= "\n" . '[b]' . $_POST['reason'] . '[/b]' . "\n\n"; + if (!empty($recipients)) + $report_body .= $txt['pm_report_pm_other_recipients'] . ' ' . implode(', ', $recipients) . "\n\n"; + $report_body .= $txt['pm_report_pm_unedited_below'] . "\n" . '[quote author=' . (empty($memberFromID) ? '"' . $memberFromName . '"' : $memberFromName . ' link=action=profile;u=' . $memberFromID . ' date=' . $time) . ']' . "\n" . un_htmlspecialchars($body) . '[/quote]'; + + // Plonk it in the array ;) + $messagesToSend[$cur_language] = array( + 'subject' => ($smcFunc['strpos']($subject, $txt['pm_report_pm_subject']) === false ? $txt['pm_report_pm_subject'] : '') . un_htmlspecialchars($subject), + 'body' => $report_body, + 'recipients' => array( + 'to' => array(), + 'bcc' => array() + ), + ); + } + + // Add them to the list. + $messagesToSend[$cur_language]['recipients']['to'][$row['id_member']] = $row['id_member']; + } + $smcFunc['db_free_result']($request); + + // Send a different email for each language. + foreach ($messagesToSend as $lang => $message) + sendpm($message['recipients'], $message['subject'], $message['body']); + + // Give the user their own language back! + if (!empty($modSettings['userLanguage'])) + loadLanguage('PersonalMessage', '', false); + + // Leave them with a template. + $context['sub_template'] = 'report_message_complete'; + } +} + +// List all rules, and allow adding/entering etc.... +function ManageRules() +{ + global $txt, $context, $user_info, $scripturl, $smcFunc; + + // The link tree - gotta have this :o + $context['linktree'][] = array( + 'url' => $scripturl . '?action=pm;sa=manrules', + 'name' => $txt['pm_manage_rules'] + ); + + $context['page_title'] = $txt['pm_manage_rules']; + $context['sub_template'] = 'rules'; + + // Load them... load them!! + LoadRules(); + + // Likely to need all the groups! + $request = $smcFunc['db_query']('', ' + SELECT mg.id_group, mg.group_name, IFNULL(gm.id_member, 0) AS can_moderate, mg.hidden + FROM {db_prefix}membergroups AS mg + LEFT JOIN {db_prefix}group_moderators AS gm ON (gm.id_group = mg.id_group AND gm.id_member = {int:current_member}) + WHERE mg.min_posts = {int:min_posts} + AND mg.id_group != {int:moderator_group} + AND mg.hidden = {int:not_hidden} + ORDER BY mg.group_name', + array( + 'current_member' => $user_info['id'], + 'min_posts' => -1, + 'moderator_group' => 3, + 'not_hidden' => 0, + ) + ); + $context['groups'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Hide hidden groups! + if ($row['hidden'] && !$row['can_moderate'] && !allowedTo('manage_membergroups')) + continue; + + $context['groups'][$row['id_group']] = $row['group_name']; + } + $smcFunc['db_free_result']($request); + + // Applying all rules? + if (isset($_GET['apply'])) + { + checkSession('get'); + + ApplyRules(true); + redirectexit('action=pm;sa=manrules'); + } + // Editing a specific one? + if (isset($_GET['add'])) + { + $context['rid'] = isset($_GET['rid']) && isset($context['rules'][$_GET['rid']])? (int) $_GET['rid'] : 0; + $context['sub_template'] = 'add_rule'; + + // Current rule information... + if ($context['rid']) + { + $context['rule'] = $context['rules'][$context['rid']]; + $members = array(); + // Need to get member names! + foreach ($context['rule']['criteria'] as $k => $criteria) + if ($criteria['t'] == 'mid' && !empty($criteria['v'])) + $members[(int) $criteria['v']] = $k; + + if (!empty($members)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member, member_name + FROM {db_prefix}members + WHERE id_member IN ({array_int:member_list})', + array( + 'member_list' => array_keys($members), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['rule']['criteria'][$members[$row['id_member']]]['v'] = $row['member_name']; + $smcFunc['db_free_result']($request); + } + } + else + $context['rule'] = array( + 'id' => '', + 'name' => '', + 'criteria' => array(), + 'actions' => array(), + 'logic' => 'and', + ); + } + // Saving? + elseif (isset($_GET['save'])) + { + checkSession('post'); + $context['rid'] = isset($_GET['rid']) && isset($context['rules'][$_GET['rid']])? (int) $_GET['rid'] : 0; + + // Name is easy! + $ruleName = $smcFunc['htmlspecialchars'](trim($_POST['rule_name'])); + if (empty($ruleName)) + fatal_lang_error('pm_rule_no_name', false); + + // Sanity check... + if (empty($_POST['ruletype']) || empty($_POST['acttype'])) + fatal_lang_error('pm_rule_no_criteria', false); + + // Let's do the criteria first - it's also hardest! + $criteria = array(); + foreach ($_POST['ruletype'] as $ind => $type) + { + // Check everything is here... + if ($type == 'gid' && (!isset($_POST['ruledefgroup'][$ind]) || !isset($context['groups'][$_POST['ruledefgroup'][$ind]]))) + continue; + elseif ($type != 'bud' && !isset($_POST['ruledef'][$ind])) + continue; + + // Members need to be found. + if ($type == 'mid') + { + $name = trim($_POST['ruledef'][$ind]); + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE real_name = {string:member_name} + OR member_name = {string:member_name}', + array( + 'member_name' => $name, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + continue; + list ($memID) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $criteria[] = array('t' => 'mid', 'v' => $memID); + } + elseif ($type == 'bud') + $criteria[] = array('t' => 'bud', 'v' => 1); + elseif ($type == 'gid') + $criteria[] = array('t' => 'gid', 'v' => (int) $_POST['ruledefgroup'][$ind]); + elseif (in_array($type, array('sub', 'msg')) && trim($_POST['ruledef'][$ind]) != '') + $criteria[] = array('t' => $type, 'v' => $smcFunc['htmlspecialchars'](trim($_POST['ruledef'][$ind]))); + } + + // Also do the actions! + $actions = array(); + $doDelete = 0; + $isOr = $_POST['rule_logic'] == 'or' ? 1 : 0; + foreach ($_POST['acttype'] as $ind => $type) + { + // Picking a valid label? + if ($type == 'lab' && (!isset($_POST['labdef'][$ind]) || !isset($context['labels'][$_POST['labdef'][$ind] - 1]))) + continue; + + // Record what we're doing. + if ($type == 'del') + $doDelete = 1; + elseif ($type == 'lab') + $actions[] = array('t' => 'lab', 'v' => (int) $_POST['labdef'][$ind] - 1); + } + + if (empty($criteria) || (empty($actions) && !$doDelete)) + fatal_lang_error('pm_rule_no_criteria', false); + + // What are we storing? + $criteria = serialize($criteria); + $actions = serialize($actions); + + // Create the rule? + if (empty($context['rid'])) + $smcFunc['db_insert']('', + '{db_prefix}pm_rules', + array( + 'id_member' => 'int', 'rule_name' => 'string', 'criteria' => 'string', 'actions' => 'string', + 'delete_pm' => 'int', 'is_or' => 'int', + ), + array( + $user_info['id'], $ruleName, $criteria, $actions, $doDelete, $isOr, + ), + array('id_rule') + ); + else + $smcFunc['db_query']('', ' + UPDATE {db_prefix}pm_rules + SET rule_name = {string:rule_name}, criteria = {string:criteria}, actions = {string:actions}, + delete_pm = {int:delete_pm}, is_or = {int:is_or} + WHERE id_rule = {int:id_rule} + AND id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'delete_pm' => $doDelete, + 'is_or' => $isOr, + 'id_rule' => $context['rid'], + 'rule_name' => $ruleName, + 'criteria' => $criteria, + 'actions' => $actions, + ) + ); + + redirectexit('action=pm;sa=manrules'); + } + // Deleting? + elseif (isset($_POST['delselected']) && !empty($_POST['delrule'])) + { + checkSession('post'); + $toDelete = array(); + foreach ($_POST['delrule'] as $k => $v) + $toDelete[] = (int) $k; + + if (!empty($toDelete)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}pm_rules + WHERE id_rule IN ({array_int:delete_list}) + AND id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'delete_list' => $toDelete, + ) + ); + + redirectexit('action=pm;sa=manrules'); + } +} + +// This will apply rules to all unread messages. If all_messages is set will, clearly, do it to all! +function ApplyRules($all_messages = false) +{ + global $user_info, $smcFunc, $context, $options; + + // Want this - duh! + loadRules(); + + // No rules? + if (empty($context['rules'])) + return; + + // Just unread ones? + $ruleQuery = $all_messages ? '' : ' AND pmr.is_new = 1'; + + //!!! Apply all should have timeout protection! + // Get all the messages that match this. + $request = $smcFunc['db_query']('', ' + SELECT + pmr.id_pm, pm.id_member_from, pm.subject, pm.body, mem.id_group, pmr.labels + FROM {db_prefix}pm_recipients AS pmr + INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from) + WHERE pmr.id_member = {int:current_member} + AND pmr.deleted = {int:not_deleted} + ' . $ruleQuery, + array( + 'current_member' => $user_info['id'], + 'not_deleted' => 0, + ) + ); + $actions = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + foreach ($context['rules'] as $rule) + { + $match = false; + // Loop through all the criteria hoping to make a match. + foreach ($rule['criteria'] as $criterium) + { + if (($criterium['t'] == 'mid' && $criterium['v'] == $row['id_member_from']) || ($criterium['t'] == 'gid' && $criterium['v'] == $row['id_group']) || ($criterium['t'] == 'sub' && strpos($row['subject'], $criterium['v']) !== false) || ($criterium['t'] == 'msg' && strpos($row['body'], $criterium['v']) !== false)) + $match = true; + // If we're adding and one criteria don't match then we stop! + elseif ($rule['logic'] == 'and') + { + $match = false; + break; + } + } + + // If we have a match the rule must be true - act! + if ($match) + { + if ($rule['delete']) + $actions['deletes'][] = $row['id_pm']; + else + { + foreach ($rule['actions'] as $ruleAction) + { + if ($ruleAction['t'] == 'lab') + { + // Get a basic pot started! + if (!isset($actions['labels'][$row['id_pm']])) + $actions['labels'][$row['id_pm']] = empty($row['labels']) ? array() : explode(',', $row['labels']); + $actions['labels'][$row['id_pm']][] = $ruleAction['v']; + } + } + } + } + } + } + $smcFunc['db_free_result']($request); + + // Deletes are easy! + if (!empty($actions['deletes'])) + deleteMessages($actions['deletes']); + + // Relabel? + if (!empty($actions['labels'])) + { + foreach ($actions['labels'] as $pm => $labels) + { + // Quickly check each label is valid! + $realLabels = array(); + foreach ($context['labels'] as $label) + if (in_array($label['id'], $labels) && ($label['id'] != -1 || empty($options['pm_remove_inbox_label']))) + $realLabels[] = $label['id']; + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}pm_recipients + SET labels = {string:new_labels} + WHERE id_pm = {int:id_pm} + AND id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'id_pm' => $pm, + 'new_labels' => empty($realLabels) ? '' : implode(',', $realLabels), + ) + ); + } + } +} + +// Load up all the rules for the current user. +function LoadRules($reload = false) +{ + global $user_info, $context, $smcFunc; + + if (isset($context['rules']) && !$reload) + return; + + $request = $smcFunc['db_query']('', ' + SELECT + id_rule, rule_name, criteria, actions, delete_pm, is_or + FROM {db_prefix}pm_rules + WHERE id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + ) + ); + $context['rules'] = array(); + // Simply fill in the data! + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $context['rules'][$row['id_rule']] = array( + 'id' => $row['id_rule'], + 'name' => $row['rule_name'], + 'criteria' => unserialize($row['criteria']), + 'actions' => unserialize($row['actions']), + 'delete' => $row['delete_pm'], + 'logic' => $row['is_or'] ? 'or' : 'and', + ); + + if ($row['delete_pm']) + $context['rules'][$row['id_rule']]['actions'][] = array('t' => 'del', 'v' => 1); + } + $smcFunc['db_free_result']($request); +} + +// Check if the PM is available to the current user. +function isAccessiblePM($pmID, $validFor = 'in_or_outbox') +{ + global $user_info, $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT + pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted} AS valid_for_outbox, + pmr.id_pm IS NOT NULL AS valid_for_inbox + FROM {db_prefix}personal_messages AS pm + LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm AND pmr.id_member = {int:id_current_member} AND pmr.deleted = {int:not_deleted}) + WHERE pm.id_pm = {int:id_pm} + AND ((pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted}) OR pmr.id_pm IS NOT NULL)', + array( + 'id_pm' => $pmID, + 'id_current_member' => $user_info['id'], + 'not_deleted' => 0, + ) + ); + + if ($smcFunc['db_num_rows']($request) === 0) + { + $smcFunc['db_free_result']($request); + return false; + } + + $validationResult = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + switch ($validFor) + { + case 'inbox': + return !empty($validationResult['valid_for_inbox']); + break; + + case 'outbox': + return !empty($validationResult['valid_for_outbox']); + break; + + case 'in_or_outbox': + return !empty($validationResult['valid_for_inbox']) || !empty($validationResult['valid_for_outbox']); + break; + + default: + trigger_error('Undefined validation type given', E_USER_ERROR); + break; + } +} + +?> \ No newline at end of file diff --git a/Sources/Poll.php b/Sources/Poll.php new file mode 100644 index 0000000..2af6748 --- /dev/null +++ b/Sources/Poll.php @@ -0,0 +1,973 @@ + $user_info['id'], + 'current_topic' => $topic, + 'not_guest' => 0, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('poll_error', false); + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // If this is a guest can they vote? + if ($user_info['is_guest']) + { + // Guest voting disabled? + if (!$row['guest_vote']) + fatal_lang_error('guest_vote_disabled'); + // Guest already voted? + elseif (!empty($_COOKIE['guest_poll_vote']) && preg_match('~^[0-9,;]+$~', $_COOKIE['guest_poll_vote']) && strpos($_COOKIE['guest_poll_vote'], ';' . $row['id_poll'] . ',') !== false) + { + // ;id,timestamp,[vote,vote...]; etc + $guestinfo = explode(';', $_COOKIE['guest_poll_vote']); + // Find the poll we're after. + foreach ($guestinfo as $i => $guestvoted) + { + $guestvoted = explode(',', $guestvoted); + if ($guestvoted[0] == $row['id_poll']) + break; + } + // Has the poll been reset since guest voted? + if ($row['reset_poll'] > $guestvoted[1]) + { + // Remove the poll info from the cookie to allow guest to vote again + unset($guestinfo[$i]); + if (!empty($guestinfo)) + $_COOKIE['guest_poll_vote'] = ';' . implode(';', $guestinfo); + else + unset($_COOKIE['guest_poll_vote']); + } + else + fatal_lang_error('poll_error', false); + unset($guestinfo, $guestvoted, $i); + } + } + + // Is voting locked or has it expired? + if (!empty($row['voting_locked']) || (!empty($row['expire_time']) && time() > $row['expire_time'])) + fatal_lang_error('poll_error', false); + + // If they have already voted and aren't allowed to change their vote - hence they are outta here! + if (!$user_info['is_guest'] && $row['selected'] != -1 && empty($row['change_vote'])) + fatal_lang_error('poll_error', false); + // Otherwise if they can change their vote yet they haven't sent any options... remove their vote and redirect. + elseif (!empty($row['change_vote']) && !$user_info['is_guest']) + { + checkSession('request'); + $pollOptions = array(); + + // Find out what they voted for before. + $request = $smcFunc['db_query']('', ' + SELECT id_choice + FROM {db_prefix}log_polls + WHERE id_member = {int:current_member} + AND id_poll = {int:id_poll}', + array( + 'current_member' => $user_info['id'], + 'id_poll' => $row['id_poll'], + ) + ); + while ($choice = $smcFunc['db_fetch_row']($request)) + $pollOptions[] = $choice[0]; + $smcFunc['db_free_result']($request); + + // Just skip it if they had voted for nothing before. + if (!empty($pollOptions)) + { + // Update the poll totals. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}poll_choices + SET votes = votes - 1 + WHERE id_poll = {int:id_poll} + AND id_choice IN ({array_int:poll_options}) + AND votes > {int:votes}', + array( + 'poll_options' => $pollOptions, + 'id_poll' => $row['id_poll'], + 'votes' => 0, + ) + ); + + // Delete off the log. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_polls + WHERE id_member = {int:current_member} + AND id_poll = {int:id_poll}', + array( + 'current_member' => $user_info['id'], + 'id_poll' => $row['id_poll'], + ) + ); + } + + // Redirect back to the topic so the user can vote again! + if (empty($_POST['options'])) + redirectexit('topic=' . $topic . '.' . $_REQUEST['start']); + } + + checkSession('request'); + + // Make sure the option(s) are valid. + if (empty($_POST['options'])) + fatal_lang_error('didnt_select_vote', false); + + // Too many options checked! + if (count($_REQUEST['options']) > $row['max_votes']) + fatal_lang_error('poll_too_many_votes', false, array($row['max_votes'])); + + $pollOptions = array(); + $inserts = array(); + foreach ($_REQUEST['options'] as $id) + { + $id = (int) $id; + + $pollOptions[] = $id; + $inserts[] = array($row['id_poll'], $user_info['id'], $id); + } + + // Add their vote to the tally. + $smcFunc['db_insert']('insert', + '{db_prefix}log_polls', + array('id_poll' => 'int', 'id_member' => 'int', 'id_choice' => 'int'), + $inserts, + array('id_poll', 'id_member', 'id_choice') + ); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}poll_choices + SET votes = votes + 1 + WHERE id_poll = {int:id_poll} + AND id_choice IN ({array_int:poll_options})', + array( + 'poll_options' => $pollOptions, + 'id_poll' => $row['id_poll'], + ) + ); + + // If it's a guest don't let them vote again. + if ($user_info['is_guest'] && count($pollOptions) > 0) + { + // Time is stored in case the poll is reset later, plus what they voted for. + $_COOKIE['guest_poll_vote'] = empty($_COOKIE['guest_poll_vote']) ? '' : $_COOKIE['guest_poll_vote']; + // ;id,timestamp,[vote,vote...]; etc + $_COOKIE['guest_poll_vote'] .= ';' . $row['id_poll'] . ',' . time() . ',' . (count($pollOptions) > 1 ? explode(',' . $pollOptions) : $pollOptions[0]); + + // Increase num guest voters count by 1 + $smcFunc['db_query']('', ' + UPDATE {db_prefix}polls + SET num_guest_voters = num_guest_voters + 1 + WHERE id_poll = {int:id_poll}', + array( + 'id_poll' => $row['id_poll'], + ) + ); + + require_once($sourcedir . '/Subs-Auth.php'); + $cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); + setcookie('guest_poll_vote', $_COOKIE['guest_poll_vote'], time() + 2500000, $cookie_url[1], $cookie_url[0], 0); + } + + // Return to the post... + redirectexit('topic=' . $topic . '.' . $_REQUEST['start']); +} + +// Lock the voting for a poll. +function LockVoting() +{ + global $topic, $user_info, $smcFunc; + + checkSession('get'); + + // Get the poll starter, ID, and whether or not it is locked. + $request = $smcFunc['db_query']('', ' + SELECT t.id_member_started, t.id_poll, p.voting_locked + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}polls AS p ON (p.id_poll = t.id_poll) + WHERE t.id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + ) + ); + list ($memberID, $pollID, $voting_locked) = $smcFunc['db_fetch_row']($request); + + // If the user _can_ modify the poll.... + if (!allowedTo('poll_lock_any')) + isAllowedTo('poll_lock_' . ($user_info['id'] == $memberID ? 'own' : 'any')); + + // It's been locked by a non-moderator. + if ($voting_locked == '1') + $voting_locked = '0'; + // Locked by a moderator, and this is a moderator. + elseif ($voting_locked == '2' && allowedTo('moderate_board')) + $voting_locked = '0'; + // Sorry, a moderator locked it. + elseif ($voting_locked == '2' && !allowedTo('moderate_board')) + fatal_lang_error('locked_by_admin', 'user'); + // A moderator *is* locking it. + elseif ($voting_locked == '0' && allowedTo('moderate_board')) + $voting_locked = '2'; + // Well, it's gonna be locked one way or another otherwise... + else + $voting_locked = '1'; + + // Lock! *Poof* - no one can vote. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}polls + SET voting_locked = {int:voting_locked} + WHERE id_poll = {int:id_poll}', + array( + 'voting_locked' => $voting_locked, + 'id_poll' => $pollID, + ) + ); + + redirectexit('topic=' . $topic . '.' . $_REQUEST['start']); +} + +// Ask what to change in a poll. +function EditPoll() +{ + global $txt, $user_info, $context, $topic, $board, $smcFunc, $sourcedir, $scripturl; + + if (empty($topic)) + fatal_lang_error('no_access', false); + + loadLanguage('Post'); + loadTemplate('Poll'); + + $context['can_moderate_poll'] = isset($_REQUEST['add']) ? 1 : allowedTo('moderate_board'); + $context['start'] = (int) $_REQUEST['start']; + $context['is_edit'] = isset($_REQUEST['add']) ? 0 : 1; + + // Check if a poll currently exists on this topic, and get the id, question and starter. + $request = $smcFunc['db_query']('', ' + SELECT + t.id_member_started, p.id_poll, p.question, p.hide_results, p.expire_time, p.max_votes, p.change_vote, + m.subject, p.guest_vote, p.id_member AS poll_starter + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + LEFT JOIN {db_prefix}polls AS p ON (p.id_poll = t.id_poll) + WHERE t.id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + ) + ); + + // Assume the the topic exists, right? + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_board'); + // Get the poll information. + $pollinfo = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // If we are adding a new poll - make sure that there isn't already a poll there. + if (!$context['is_edit'] && !empty($pollinfo['id_poll'])) + fatal_lang_error('poll_already_exists'); + // Otherwise, if we're editing it, it does exist I assume? + elseif ($context['is_edit'] && empty($pollinfo['id_poll'])) + fatal_lang_error('poll_not_found'); + + // Can you do this? + if ($context['is_edit'] && !allowedTo('poll_edit_any')) + isAllowedTo('poll_edit_' . ($user_info['id'] == $pollinfo['id_member_started'] || ($pollinfo['poll_starter'] != 0 && $user_info['id'] == $pollinfo['poll_starter']) ? 'own' : 'any')); + elseif (!$context['is_edit'] && !allowedTo('poll_add_any')) + isAllowedTo('poll_add_' . ($user_info['id'] == $pollinfo['id_member_started'] ? 'own' : 'any')); + + // Do we enable guest voting? + require_once($sourcedir . '/Subs-Members.php'); + $groupsAllowedVote = groupsAllowedTo('poll_vote', $board); + + // Want to make sure before you actually submit? Must be a lot of options, or something. + if (isset($_POST['preview'])) + { + $question = $smcFunc['htmlspecialchars']($_POST['question']); + + // Basic theme info... + $context['poll'] = array( + 'id' => $pollinfo['id_poll'], + 'question' => $question, + 'hide_results' => empty($_POST['poll_hide']) ? 0 : $_POST['poll_hide'], + 'change_vote' => isset($_POST['poll_change_vote']), + 'guest_vote' => isset($_POST['poll_guest_vote']), + 'guest_vote_allowed' => in_array(-1, $groupsAllowedVote['allowed']), + 'max_votes' => empty($_POST['poll_max_votes']) ? '1' : max(1, $_POST['poll_max_votes']), + ); + + // Start at number one with no last id to speak of. + $number = 1; + $last_id = 0; + + // Get all the choices - if this is an edit. + if ($context['is_edit']) + { + $request = $smcFunc['db_query']('', ' + SELECT label, votes, id_choice + FROM {db_prefix}poll_choices + WHERE id_poll = {int:id_poll}', + array( + 'id_poll' => $pollinfo['id_poll'], + ) + ); + $context['choices'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Get the highest id so we can add more without reusing. + if ($row['id_choice'] >= $last_id) + $last_id = $row['id_choice'] + 1; + + // They cleared this by either omitting it or emptying it. + if (!isset($_POST['options'][$row['id_choice']]) || $_POST['options'][$row['id_choice']] == '') + continue; + + censorText($row['label']); + + // Add the choice! + $context['choices'][$row['id_choice']] = array( + 'id' => $row['id_choice'], + 'number' => $number++, + 'votes' => $row['votes'], + 'label' => $row['label'], + 'is_last' => false + ); + } + $smcFunc['db_free_result']($request); + } + + // Work out how many options we have, so we get the 'is_last' field right... + $totalPostOptions = 0; + foreach ($_POST['options'] as $id => $label) + if ($label != '') + $totalPostOptions++; + + $count = 1; + // If an option exists, update it. If it is new, add it - but don't reuse ids! + foreach ($_POST['options'] as $id => $label) + { + $label = $smcFunc['htmlspecialchars']($label); + censorText($label); + + if (isset($context['choices'][$id])) + $context['choices'][$id]['label'] = $label; + elseif ($label != '') + $context['choices'][] = array( + 'id' => $last_id++, + 'number' => $number++, + 'label' => $label, + 'votes' => -1, + 'is_last' => $count++ == $totalPostOptions && $totalPostOptions > 1 ? true : false, + ); + } + + // Make sure we have two choices for sure! + if ($totalPostOptions < 2) + { + // Need two? + if ($totalPostOptions == 0) + $context['choices'][] = array( + 'id' => $last_id++, + 'number' => $number++, + 'label' => '', + 'votes' => -1, + 'is_last' => false + ); + $poll_errors[] = 'poll_few'; + } + + // Always show one extra box... + $context['choices'][] = array( + 'id' => $last_id++, + 'number' => $number++, + 'label' => '', + 'votes' => -1, + 'is_last' => true + ); + + if ($context['can_moderate_poll']) + $context['poll']['expiration'] = $_POST['poll_expire']; + + // Check the question/option count for errors. + if (trim($_POST['question']) == '' && empty($context['poll_error'])) + $poll_errors[] = 'no_question'; + + // No check is needed, since nothing is really posted. + checkSubmitOnce('free'); + + // Take a check for any errors... assuming we haven't already done so! + if (!empty($poll_errors) && empty($context['poll_error'])) + { + loadLanguage('Errors'); + + $context['poll_error'] = array('messages' => array()); + foreach ($poll_errors as $poll_error) + { + $context['poll_error'][$poll_error] = true; + $context['poll_error']['messages'][] = $txt['error_' . $poll_error]; + } + } + } + else + { + // Basic theme info... + $context['poll'] = array( + 'id' => $pollinfo['id_poll'], + 'question' => $pollinfo['question'], + 'hide_results' => $pollinfo['hide_results'], + 'max_votes' => $pollinfo['max_votes'], + 'change_vote' => !empty($pollinfo['change_vote']), + 'guest_vote' => !empty($pollinfo['guest_vote']), + 'guest_vote_allowed' => in_array(-1, $groupsAllowedVote['allowed']), + ); + + // Poll expiration time? + $context['poll']['expiration'] = empty($pollinfo['expire_time']) || !allowedTo('moderate_board') ? '' : ceil($pollinfo['expire_time'] <= time() ? -1 : ($pollinfo['expire_time'] - time()) / (3600 * 24)); + + // Get all the choices - if this is an edit. + if ($context['is_edit']) + { + $request = $smcFunc['db_query']('', ' + SELECT label, votes, id_choice + FROM {db_prefix}poll_choices + WHERE id_poll = {int:id_poll}', + array( + 'id_poll' => $pollinfo['id_poll'], + ) + ); + $context['choices'] = array(); + $number = 1; + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + censorText($row['label']); + + $context['choices'][$row['id_choice']] = array( + 'id' => $row['id_choice'], + 'number' => $number++, + 'votes' => $row['votes'], + 'label' => $row['label'], + 'is_last' => false + ); + } + $smcFunc['db_free_result']($request); + + $last_id = max(array_keys($context['choices'])) + 1; + + // Add an extra choice... + $context['choices'][] = array( + 'id' => $last_id, + 'number' => $number, + 'votes' => -1, + 'label' => '', + 'is_last' => true + ); + } + // New poll? + else + { + // Setup the default poll options. + $context['poll'] = array( + 'id' => 0, + 'question' => '', + 'hide_results' => 0, + 'max_votes' => 1, + 'change_vote' => 0, + 'guest_vote' => 0, + 'guest_vote_allowed' => in_array(-1, $groupsAllowedVote['allowed']), + 'expiration' => '', + ); + + // Make all five poll choices empty. + $context['choices'] = array( + array('id' => 0, 'number' => 1, 'votes' => -1, 'label' => '', 'is_last' => false), + array('id' => 1, 'number' => 2, 'votes' => -1, 'label' => '', 'is_last' => false), + array('id' => 2, 'number' => 3, 'votes' => -1, 'label' => '', 'is_last' => false), + array('id' => 3, 'number' => 4, 'votes' => -1, 'label' => '', 'is_last' => false), + array('id' => 4, 'number' => 5, 'votes' => -1, 'label' => '', 'is_last' => true) + ); + } + } + $context['page_title'] = $context['is_edit'] ? $txt['poll_edit'] : $txt['add_poll']; + + // Build the link tree. + censorText($pollinfo['subject']); + $context['linktree'][] = array( + 'url' => $scripturl . '?topic=' . $topic . '.0', + 'name' => $pollinfo['subject'], + ); + $context['linktree'][] = array( + 'name' => $context['page_title'], + ); + + // Register this form in the session variables. + checkSubmitOnce('register'); +} + +// Change a poll... +function EditPoll2() +{ + global $txt, $topic, $board, $context; + global $modSettings, $user_info, $smcFunc, $sourcedir; + + // Sneaking off, are we? + if (empty($_POST)) + redirectexit('action=editpoll;topic=' . $topic . '.0'); + + if (checkSession('post', '', false) != '') + $poll_errors[] = 'session_timeout'; + + if (isset($_POST['preview'])) + return EditPoll(); + + // HACKERS (!!) can't edit :P. + if (empty($topic)) + fatal_lang_error('no_access', false); + + // Is this a new poll, or editing an existing? + $isEdit = isset($_REQUEST['add']) ? 0 : 1; + + // Get the starter and the poll's ID - if it's an edit. + $request = $smcFunc['db_query']('', ' + SELECT t.id_member_started, t.id_poll, p.id_member AS poll_starter, p.expire_time + FROM {db_prefix}topics AS t + LEFT JOIN {db_prefix}polls AS p ON (p.id_poll = t.id_poll) + WHERE t.id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_board'); + $bcinfo = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Check their adding/editing is valid. + if (!$isEdit && !empty($bcinfo['id_poll'])) + fatal_lang_error('poll_already_exists'); + // Are we editing a poll which doesn't exist? + elseif ($isEdit && empty($bcinfo['id_poll'])) + fatal_lang_error('poll_not_found'); + + // Check if they have the power to add or edit the poll. + if ($isEdit && !allowedTo('poll_edit_any')) + isAllowedTo('poll_edit_' . ($user_info['id'] == $bcinfo['id_member_started'] || ($bcinfo['poll_starter'] != 0 && $user_info['id'] == $bcinfo['poll_starter']) ? 'own' : 'any')); + elseif (!$isEdit && !allowedTo('poll_add_any')) + isAllowedTo('poll_add_' . ($user_info['id'] == $bcinfo['id_member_started'] ? 'own' : 'any')); + + $optionCount = 0; + // Ensure the user is leaving a valid amount of options - there must be at least two. + foreach ($_POST['options'] as $k => $option) + { + if (trim($option) != '') + $optionCount++; + } + if ($optionCount < 2) + $poll_errors[] = 'poll_few'; + + // Also - ensure they are not removing the question. + if (trim($_POST['question']) == '') + $poll_errors[] = 'no_question'; + + // Got any errors to report? + if (!empty($poll_errors)) + { + loadLanguage('Errors'); + // Previewing. + $_POST['preview'] = true; + + $context['poll_error'] = array('messages' => array()); + foreach ($poll_errors as $poll_error) + { + $context['poll_error'][$poll_error] = true; + $context['poll_error']['messages'][] = $txt['error_' . $poll_error]; + } + + return EditPoll(); + } + + // Prevent double submission of this form. + checkSubmitOnce('check'); + + // Now we've done all our error checking, let's get the core poll information cleaned... question first. + $_POST['question'] = $smcFunc['htmlspecialchars']($_POST['question']); + $_POST['question'] = $smcFunc['truncate']($_POST['question'], 255); + + $_POST['poll_hide'] = (int) $_POST['poll_hide']; + $_POST['poll_expire'] = isset($_POST['poll_expire']) ? (int) $_POST['poll_expire'] : 0; + $_POST['poll_change_vote'] = isset($_POST['poll_change_vote']) ? 1 : 0; + $_POST['poll_guest_vote'] = isset($_POST['poll_guest_vote']) ? 1 : 0; + + // Make sure guests are actually allowed to vote generally. + if ($_POST['poll_guest_vote']) + { + require_once($sourcedir . '/Subs-Members.php'); + $allowedGroups = groupsAllowedTo('poll_vote', $board); + if (!in_array(-1, $allowedGroups['allowed'])) + $_POST['poll_guest_vote'] = 0; + } + + // Ensure that the number options allowed makes sense, and the expiration date is valid. + if (!$isEdit || allowedTo('moderate_board')) + { + $_POST['poll_expire'] = $_POST['poll_expire'] > 9999 ? 9999 : ($_POST['poll_expire'] < 0 ? 0 : $_POST['poll_expire']); + + if (empty($_POST['poll_expire']) && $_POST['poll_hide'] == 2) + $_POST['poll_hide'] = 1; + elseif (!$isEdit || $_POST['poll_expire'] != ceil($bcinfo['expire_time'] <= time() ? -1 : ($bcinfo['expire_time'] - time()) / (3600 * 24))) + $_POST['poll_expire'] = empty($_POST['poll_expire']) ? '0' : time() + $_POST['poll_expire'] * 3600 * 24; + else + $_POST['poll_expire'] = $bcinfo['expire_time']; + + if (empty($_POST['poll_max_votes']) || $_POST['poll_max_votes'] <= 0) + $_POST['poll_max_votes'] = 1; + else + $_POST['poll_max_votes'] = (int) $_POST['poll_max_votes']; + } + + // If we're editing, let's commit the changes. + if ($isEdit) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}polls + SET question = {string:question}, change_vote = {int:change_vote},' . (allowedTo('moderate_board') ? ' + hide_results = {int:hide_results}, expire_time = {int:expire_time}, max_votes = {int:max_votes}, + guest_vote = {int:guest_vote}' : ' + hide_results = CASE WHEN expire_time = {int:expire_time_zero} AND {int:hide_results} = 2 THEN 1 ELSE {int:hide_results} END') . ' + WHERE id_poll = {int:id_poll}', + array( + 'change_vote' => $_POST['poll_change_vote'], + 'hide_results' => $_POST['poll_hide'], + 'expire_time' => !empty($_POST['poll_expire']) ? $_POST['poll_expire'] : 0, + 'max_votes' => !empty($_POST['poll_max_votes']) ? $_POST['poll_max_votes'] : 0, + 'guest_vote' => $_POST['poll_guest_vote'], + 'expire_time_zero' => 0, + 'id_poll' => $bcinfo['id_poll'], + 'question' => $_POST['question'], + ) + ); + } + // Otherwise, let's get our poll going! + else + { + // Create the poll. + $smcFunc['db_insert']('', + '{db_prefix}polls', + array( + 'question' => 'string-255', 'hide_results' => 'int', 'max_votes' => 'int', 'expire_time' => 'int', 'id_member' => 'int', + 'poster_name' => 'string-255', 'change_vote' => 'int', 'guest_vote' => 'int' + ), + array( + $_POST['question'], $_POST['poll_hide'], $_POST['poll_max_votes'], $_POST['poll_expire'], $user_info['id'], + $user_info['username'], $_POST['poll_change_vote'], $_POST['poll_guest_vote'], + ), + array('id_poll') + ); + + // Set the poll ID. + $bcinfo['id_poll'] = $smcFunc['db_insert_id']('{db_prefix}polls', 'id_poll'); + + // Link the poll to the topic + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET id_poll = {int:id_poll} + WHERE id_topic = {int:current_topic}', + array( + 'current_topic' => $topic, + 'id_poll' => $bcinfo['id_poll'], + ) + ); + } + + // Get all the choices. (no better way to remove all emptied and add previously non-existent ones.) + $request = $smcFunc['db_query']('', ' + SELECT id_choice + FROM {db_prefix}poll_choices + WHERE id_poll = {int:id_poll}', + array( + 'id_poll' => $bcinfo['id_poll'], + ) + ); + $choices = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $choices[] = $row['id_choice']; + $smcFunc['db_free_result']($request); + + $delete_options = array(); + foreach ($_POST['options'] as $k => $option) + { + // Make sure the key is numeric for sanity's sake. + $k = (int) $k; + + // They've cleared the box. Either they want it deleted, or it never existed. + if (trim($option) == '') + { + // They want it deleted. Bye. + if (in_array($k, $choices)) + $delete_options[] = $k; + + // Skip the rest... + continue; + } + + // Dress the option up for its big date with the database. + $option = $smcFunc['htmlspecialchars']($option); + + // If it's already there, update it. If it's not... add it. + if (in_array($k, $choices)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}poll_choices + SET label = {string:option_name} + WHERE id_poll = {int:id_poll} + AND id_choice = {int:id_choice}', + array( + 'id_poll' => $bcinfo['id_poll'], + 'id_choice' => $k, + 'option_name' => $option, + ) + ); + else + $smcFunc['db_insert']('', + '{db_prefix}poll_choices', + array( + 'id_poll' => 'int', 'id_choice' => 'int', 'label' => 'string-255', 'votes' => 'int', + ), + array( + $bcinfo['id_poll'], $k, $option, 0, + ), + array() + ); + } + + // I'm sorry, but... well, no one was choosing you. Poor options, I'll put you out of your misery. + if (!empty($delete_options)) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_polls + WHERE id_poll = {int:id_poll} + AND id_choice IN ({array_int:delete_options})', + array( + 'delete_options' => $delete_options, + 'id_poll' => $bcinfo['id_poll'], + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}poll_choices + WHERE id_poll = {int:id_poll} + AND id_choice IN ({array_int:delete_options})', + array( + 'delete_options' => $delete_options, + 'id_poll' => $bcinfo['id_poll'], + ) + ); + } + + // Shall I reset the vote count, sir? + if (isset($_POST['resetVoteCount'])) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}polls + SET num_guest_voters = {int:no_votes}, reset_poll = {int:time} + WHERE id_poll = {int:id_poll}', + array( + 'no_votes' => 0, + 'id_poll' => $bcinfo['id_poll'], + 'time' => time(), + ) + ); + $smcFunc['db_query']('', ' + UPDATE {db_prefix}poll_choices + SET votes = {int:no_votes} + WHERE id_poll = {int:id_poll}', + array( + 'no_votes' => 0, + 'id_poll' => $bcinfo['id_poll'], + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_polls + WHERE id_poll = {int:id_poll}', + array( + 'id_poll' => $bcinfo['id_poll'], + ) + ); + } + + // Off we go. + redirectexit('topic=' . $topic . '.' . $_REQUEST['start']); +} + +// Remove a poll from a topic without removing the topic. +function RemovePoll() +{ + global $topic, $user_info, $smcFunc; + + // Make sure the topic is not empty. + if (empty($topic)) + fatal_lang_error('no_access', false); + + // Verify the session. + checkSession('get'); + + // Check permissions. + if (!allowedTo('poll_remove_any')) + { + $request = $smcFunc['db_query']('', ' + SELECT t.id_member_started, p.id_member AS poll_starter + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}polls AS p ON (p.id_poll = t.id_poll) + WHERE t.id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_access', false); + list ($topicStarter, $pollStarter) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + isAllowedTo('poll_remove_' . ($topicStarter == $user_info['id'] || ($pollStarter != 0 && $user_info['id'] == $pollStarter) ? 'own' : 'any')); + } + + // Retrieve the poll ID. + $request = $smcFunc['db_query']('', ' + SELECT id_poll + FROM {db_prefix}topics + WHERE id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + ) + ); + list ($pollID) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Remove all user logs for this poll. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_polls + WHERE id_poll = {int:id_poll}', + array( + 'id_poll' => $pollID, + ) + ); + // Remove all poll choices. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}poll_choices + WHERE id_poll = {int:id_poll}', + array( + 'id_poll' => $pollID, + ) + ); + // Remove the poll itself. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}polls + WHERE id_poll = {int:id_poll}', + array( + 'id_poll' => $pollID, + ) + ); + // Finally set the topic poll ID back to 0! + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET id_poll = {int:no_poll} + WHERE id_topic = {int:current_topic}', + array( + 'current_topic' => $topic, + 'no_poll' => 0, + ) + ); + + // Take the moderator back to the topic. + redirectexit('topic=' . $topic . '.' . $_REQUEST['start']); +} + +?> \ No newline at end of file diff --git a/Sources/Post.php b/Sources/Post.php new file mode 100644 index 0000000..b7db3ca --- /dev/null +++ b/Sources/Post.php @@ -0,0 +1,2894 @@ + (int) $_REQUEST['msg'], + )); + if ($smcFunc['db_num_rows']($request) != 1) + unset($_REQUEST['msg'], $_POST['msg'], $_GET['msg']); + else + list ($topic) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + // Check if it's locked. It isn't locked if no topic is specified. + if (!empty($topic)) + { + $request = $smcFunc['db_query']('', ' + SELECT + t.locked, IFNULL(ln.id_topic, 0) AS notify, t.is_sticky, t.id_poll, t.id_last_msg, mf.id_member, + t.id_first_msg, mf.subject, + CASE WHEN ml.poster_time > ml.modified_time THEN ml.poster_time ELSE ml.modified_time END AS last_post_time + FROM {db_prefix}topics AS t + LEFT JOIN {db_prefix}log_notify AS ln ON (ln.id_topic = t.id_topic AND ln.id_member = {int:current_member}) + LEFT JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg) + LEFT JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg) + WHERE t.id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_member' => $user_info['id'], + 'current_topic' => $topic, + ) + ); + list ($locked, $context['notify'], $sticky, $pollID, $context['topic_last_message'], $id_member_poster, $id_first_msg, $first_subject, $lastPostTime) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // If this topic already has a poll, they sure can't add another. + if (isset($_REQUEST['poll']) && $pollID > 0) + unset($_REQUEST['poll']); + + if (empty($_REQUEST['msg'])) + { + if ($user_info['is_guest'] && !allowedTo('post_reply_any') && (!$modSettings['postmod_active'] || !allowedTo('post_unapproved_replies_any'))) + is_not_guest(); + + // By default the reply will be approved... + $context['becomes_approved'] = true; + if ($id_member_poster != $user_info['id']) + { + if ($modSettings['postmod_active'] && allowedTo('post_unapproved_replies_any') && !allowedTo('post_reply_any')) + $context['becomes_approved'] = false; + else + isAllowedTo('post_reply_any'); + } + elseif (!allowedTo('post_reply_any')) + { + if ($modSettings['postmod_active'] && allowedTo('post_unapproved_replies_own') && !allowedTo('post_reply_own')) + $context['becomes_approved'] = false; + else + isAllowedTo('post_reply_own'); + } + } + else + $context['becomes_approved'] = true; + + $context['can_lock'] = allowedTo('lock_any') || ($user_info['id'] == $id_member_poster && allowedTo('lock_own')); + $context['can_sticky'] = allowedTo('make_sticky') && !empty($modSettings['enableStickyTopics']); + + $context['notify'] = !empty($context['notify']); + $context['sticky'] = isset($_REQUEST['sticky']) ? !empty($_REQUEST['sticky']) : $sticky; + } + else + { + $context['becomes_approved'] = true; + if ((!$context['make_event'] || !empty($board))) + { + if ($modSettings['postmod_active'] && !allowedTo('post_new') && allowedTo('post_unapproved_topics')) + $context['becomes_approved'] = false; + else + isAllowedTo('post_new'); + } + + $locked = 0; + // !!! These won't work if you're making an event. + $context['can_lock'] = allowedTo(array('lock_any', 'lock_own')); + $context['can_sticky'] = allowedTo('make_sticky') && !empty($modSettings['enableStickyTopics']); + + $context['notify'] = !empty($context['notify']); + $context['sticky'] = !empty($_REQUEST['sticky']); + } + + // !!! These won't work if you're posting an event! + $context['can_notify'] = allowedTo('mark_any_notify'); + $context['can_move'] = allowedTo('move_any'); + $context['move'] = !empty($_REQUEST['move']); + $context['announce'] = !empty($_REQUEST['announce']); + // You can only announce topics that will get approved... + $context['can_announce'] = allowedTo('announce_topic') && $context['becomes_approved']; + $context['locked'] = !empty($locked) || !empty($_REQUEST['lock']); + $context['can_quote'] = empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC'])); + + // Generally don't show the approval box... (Assume we want things approved) + $context['show_approval'] = false; + + // An array to hold all the attachments for this topic. + $context['current_attachments'] = array(); + + // Don't allow a post if it's locked and you aren't all powerful. + if ($locked && !allowedTo('moderate_board')) + fatal_lang_error('topic_locked', false); + // Check the users permissions - is the user allowed to add or post a poll? + if (isset($_REQUEST['poll']) && $modSettings['pollMode'] == '1') + { + // New topic, new poll. + if (empty($topic)) + isAllowedTo('poll_post'); + // This is an old topic - but it is yours! Can you add to it? + elseif ($user_info['id'] == $id_member_poster && !allowedTo('poll_add_any')) + isAllowedTo('poll_add_own'); + // If you're not the owner, can you add to any poll? + else + isAllowedTo('poll_add_any'); + + require_once($sourcedir . '/Subs-Members.php'); + $allowedVoteGroups = groupsAllowedTo('poll_vote', $board); + + // Set up the poll options. + $context['poll_options'] = array( + 'max_votes' => empty($_POST['poll_max_votes']) ? '1' : max(1, $_POST['poll_max_votes']), + 'hide' => empty($_POST['poll_hide']) ? 0 : $_POST['poll_hide'], + 'expire' => !isset($_POST['poll_expire']) ? '' : $_POST['poll_expire'], + 'change_vote' => isset($_POST['poll_change_vote']), + 'guest_vote' => isset($_POST['poll_guest_vote']), + 'guest_vote_enabled' => in_array(-1, $allowedVoteGroups['allowed']), + ); + + // Make all five poll choices empty. + $context['choices'] = array( + array('id' => 0, 'number' => 1, 'label' => '', 'is_last' => false), + array('id' => 1, 'number' => 2, 'label' => '', 'is_last' => false), + array('id' => 2, 'number' => 3, 'label' => '', 'is_last' => false), + array('id' => 3, 'number' => 4, 'label' => '', 'is_last' => false), + array('id' => 4, 'number' => 5, 'label' => '', 'is_last' => true) + ); + } + + if ($context['make_event']) + { + // They might want to pick a board. + if (!isset($context['current_board'])) + $context['current_board'] = 0; + + // Start loading up the event info. + $context['event'] = array(); + $context['event']['title'] = isset($_REQUEST['evtitle']) ? htmlspecialchars(stripslashes($_REQUEST['evtitle'])) : ''; + + $context['event']['id'] = isset($_REQUEST['eventid']) ? (int) $_REQUEST['eventid'] : -1; + $context['event']['new'] = $context['event']['id'] == -1; + + // Permissions check! + isAllowedTo('calendar_post'); + + // Editing an event? (but NOT previewing!?) + if (!$context['event']['new'] && !isset($_REQUEST['subject'])) + { + // If the user doesn't have permission to edit the post in this topic, redirect them. + if ((empty($id_member_poster) || $id_member_poster != $user_info['id'] || !allowedTo('modify_own')) && !allowedTo('modify_any')) + { + require_once($sourcedir . '/Calendar.php'); + return CalendarPost(); + } + + // Get the current event information. + $request = $smcFunc['db_query']('', ' + SELECT + id_member, title, MONTH(start_date) AS month, DAYOFMONTH(start_date) AS day, + YEAR(start_date) AS year, (TO_DAYS(end_date) - TO_DAYS(start_date)) AS span + FROM {db_prefix}calendar + WHERE id_event = {int:id_event} + LIMIT 1', + array( + 'id_event' => $context['event']['id'], + ) + ); + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Make sure the user is allowed to edit this event. + if ($row['id_member'] != $user_info['id']) + isAllowedTo('calendar_edit_any'); + elseif (!allowedTo('calendar_edit_any')) + isAllowedTo('calendar_edit_own'); + + $context['event']['month'] = $row['month']; + $context['event']['day'] = $row['day']; + $context['event']['year'] = $row['year']; + $context['event']['title'] = $row['title']; + $context['event']['span'] = $row['span'] + 1; + } + else + { + $today = getdate(); + + // You must have a month and year specified! + if (!isset($_REQUEST['month'])) + $_REQUEST['month'] = $today['mon']; + if (!isset($_REQUEST['year'])) + $_REQUEST['year'] = $today['year']; + + $context['event']['month'] = (int) $_REQUEST['month']; + $context['event']['year'] = (int) $_REQUEST['year']; + $context['event']['day'] = isset($_REQUEST['day']) ? $_REQUEST['day'] : ($_REQUEST['month'] == $today['mon'] ? $today['mday'] : 0); + $context['event']['span'] = isset($_REQUEST['span']) ? $_REQUEST['span'] : 1; + + // Make sure the year and month are in the valid range. + if ($context['event']['month'] < 1 || $context['event']['month'] > 12) + fatal_lang_error('invalid_month', false); + if ($context['event']['year'] < $modSettings['cal_minyear'] || $context['event']['year'] > $modSettings['cal_maxyear']) + fatal_lang_error('invalid_year', false); + + // Get a list of boards they can post in. + $boards = boardsAllowedTo('post_new'); + if (empty($boards)) + fatal_lang_error('cannot_post_new', 'user'); + + // Load a list of boards for this event in the context. + require_once($sourcedir . '/Subs-MessageIndex.php'); + $boardListOptions = array( + 'included_boards' => in_array(0, $boards) ? null : $boards, + 'not_redirection' => true, + 'use_permissions' => true, + 'selected_board' => empty($context['current_board']) ? $modSettings['cal_defaultboard'] : $context['current_board'], + ); + $context['event']['categories'] = getBoardList($boardListOptions); + } + + // Find the last day of the month. + $context['event']['last_day'] = (int) strftime('%d', mktime(0, 0, 0, $context['event']['month'] == 12 ? 1 : $context['event']['month'] + 1, 0, $context['event']['month'] == 12 ? $context['event']['year'] + 1 : $context['event']['year'])); + + $context['event']['board'] = !empty($board) ? $board : $modSettings['cal_defaultboard']; + } + + if (empty($context['post_errors'])) + $context['post_errors'] = array(); + + // See if any new replies have come along. + if (empty($_REQUEST['msg']) && !empty($topic)) + { + if (empty($options['no_new_reply_warning']) && isset($_REQUEST['last_msg']) && $context['topic_last_message'] > $_REQUEST['last_msg']) + { + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}messages + WHERE id_topic = {int:current_topic} + AND id_msg > {int:last_msg}' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : ' + AND approved = {int:approved}') . ' + LIMIT 1', + array( + 'current_topic' => $topic, + 'last_msg' => (int) $_REQUEST['last_msg'], + 'approved' => 1, + ) + ); + list ($context['new_replies']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (!empty($context['new_replies'])) + { + if ($context['new_replies'] == 1) + $txt['error_new_reply'] = isset($_GET['last_msg']) ? $txt['error_new_reply_reading'] : $txt['error_new_reply']; + else + $txt['error_new_replies'] = sprintf(isset($_GET['last_msg']) ? $txt['error_new_replies_reading'] : $txt['error_new_replies'], $context['new_replies']); + + // If they've come from the display page then we treat the error differently.... + if (isset($_GET['last_msg'])) + $newRepliesError = $context['new_replies']; + else + $context['post_error'][$context['new_replies'] == 1 ? 'new_reply' : 'new_replies'] = true; + + $modSettings['topicSummaryPosts'] = $context['new_replies'] > $modSettings['topicSummaryPosts'] ? max($modSettings['topicSummaryPosts'], 5) : $modSettings['topicSummaryPosts']; + } + } + // Check whether this is a really old post being bumped... + if (!empty($modSettings['oldTopicDays']) && $lastPostTime + $modSettings['oldTopicDays'] * 86400 < time() && empty($sticky) && !isset($_REQUEST['subject'])) + $oldTopicError = true; + } + + // Get a response prefix (like 'Re:') in the default forum language. + if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix'))) + { + if ($language === $user_info['language']) + $context['response_prefix'] = $txt['response_prefix']; + else + { + loadLanguage('index', $language, false); + $context['response_prefix'] = $txt['response_prefix']; + loadLanguage('index'); + } + cache_put_data('response_prefix', $context['response_prefix'], 600); + } + + // Previewing, modifying, or posting? + if (isset($_REQUEST['message']) || !empty($context['post_error'])) + { + // Validate inputs. + if (empty($context['post_error'])) + { + if (htmltrim__recursive(htmlspecialchars__recursive($_REQUEST['subject'])) == '') + $context['post_error']['no_subject'] = true; + if (htmltrim__recursive(htmlspecialchars__recursive($_REQUEST['message'])) == '') + $context['post_error']['no_message'] = true; + if (!empty($modSettings['max_messageLength']) && $smcFunc['strlen']($_REQUEST['message']) > $modSettings['max_messageLength']) + $context['post_error']['long_message'] = true; + + // Are you... a guest? + if ($user_info['is_guest']) + { + $_REQUEST['guestname'] = !isset($_REQUEST['guestname']) ? '' : trim($_REQUEST['guestname']); + $_REQUEST['email'] = !isset($_REQUEST['email']) ? '' : trim($_REQUEST['email']); + + // Validate the name and email. + if (!isset($_REQUEST['guestname']) || trim(strtr($_REQUEST['guestname'], '_', ' ')) == '') + $context['post_error']['no_name'] = true; + elseif ($smcFunc['strlen']($_REQUEST['guestname']) > 25) + $context['post_error']['long_name'] = true; + else + { + require_once($sourcedir . '/Subs-Members.php'); + if (isReservedName(htmlspecialchars($_REQUEST['guestname']), 0, true, false)) + $context['post_error']['bad_name'] = true; + } + + if (empty($modSettings['guest_post_no_email'])) + { + if (!isset($_REQUEST['email']) || $_REQUEST['email'] == '') + $context['post_error']['no_email'] = true; + elseif (preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $_REQUEST['email']) == 0) + $context['post_error']['bad_email'] = true; + } + } + + // This is self explanatory - got any questions? + if (isset($_REQUEST['question']) && trim($_REQUEST['question']) == '') + $context['post_error']['no_question'] = true; + + // This means they didn't click Post and get an error. + $really_previewing = true; + } + else + { + if (!isset($_REQUEST['subject'])) + $_REQUEST['subject'] = ''; + if (!isset($_REQUEST['message'])) + $_REQUEST['message'] = ''; + if (!isset($_REQUEST['icon'])) + $_REQUEST['icon'] = 'xx'; + + // They are previewing if they asked to preview (i.e. came from quick reply). + $really_previewing = !empty($_POST['preview']); + } + + // In order to keep the approval status flowing through, we have to pass it through the form... + $context['becomes_approved'] = empty($_REQUEST['not_approved']); + $context['show_approval'] = isset($_REQUEST['approve']) ? ($_REQUEST['approve'] ? 2 : 1) : 0; + $context['can_announce'] &= $context['becomes_approved']; + + // Set up the inputs for the form. + $form_subject = strtr($smcFunc['htmlspecialchars']($_REQUEST['subject']), array("\r" => '', "\n" => '', "\t" => '')); + $form_message = $smcFunc['htmlspecialchars']($_REQUEST['message'], ENT_QUOTES); + + // Make sure the subject isn't too long - taking into account special characters. + if ($smcFunc['strlen']($form_subject) > 100) + $form_subject = $smcFunc['substr']($form_subject, 0, 100); + + // Have we inadvertently trimmed off the subject of useful information? + if ($smcFunc['htmltrim']($form_subject) === '') + $context['post_error']['no_subject'] = true; + + // Any errors occurred? + if (!empty($context['post_error'])) + { + loadLanguage('Errors'); + + $context['error_type'] = 'minor'; + + $context['post_error']['messages'] = array(); + foreach ($context['post_error'] as $post_error => $dummy) + { + if ($post_error == 'messages') + continue; + + if ($post_error == 'long_message') + $txt['error_' . $post_error] = sprintf($txt['error_' . $post_error], $modSettings['max_messageLength']); + + $context['post_error']['messages'][] = $txt['error_' . $post_error]; + + // If it's not a minor error flag it as such. + if (!in_array($post_error, array('new_reply', 'not_approved', 'new_replies', 'old_topic', 'need_qr_verification'))) + $context['error_type'] = 'serious'; + } + } + + if (isset($_REQUEST['poll'])) + { + $context['question'] = isset($_REQUEST['question']) ? $smcFunc['htmlspecialchars'](trim($_REQUEST['question'])) : ''; + + $context['choices'] = array(); + $choice_id = 0; + + $_POST['options'] = empty($_POST['options']) ? array() : htmlspecialchars__recursive($_POST['options']); + foreach ($_POST['options'] as $option) + { + if (trim($option) == '') + continue; + + $context['choices'][] = array( + 'id' => $choice_id++, + 'number' => $choice_id, + 'label' => $option, + 'is_last' => false + ); + } + + if (count($context['choices']) < 2) + { + $context['choices'][] = array( + 'id' => $choice_id++, + 'number' => $choice_id, + 'label' => '', + 'is_last' => false + ); + $context['choices'][] = array( + 'id' => $choice_id++, + 'number' => $choice_id, + 'label' => '', + 'is_last' => false + ); + } + $context['choices'][count($context['choices']) - 1]['is_last'] = true; + } + + // Are you... a guest? + if ($user_info['is_guest']) + { + $_REQUEST['guestname'] = !isset($_REQUEST['guestname']) ? '' : trim($_REQUEST['guestname']); + $_REQUEST['email'] = !isset($_REQUEST['email']) ? '' : trim($_REQUEST['email']); + + $_REQUEST['guestname'] = htmlspecialchars($_REQUEST['guestname']); + $context['name'] = $_REQUEST['guestname']; + $_REQUEST['email'] = htmlspecialchars($_REQUEST['email']); + $context['email'] = $_REQUEST['email']; + + $user_info['name'] = $_REQUEST['guestname']; + } + + // Only show the preview stuff if they hit Preview. + if ($really_previewing == true || isset($_REQUEST['xml'])) + { + // Set up the preview message and subject and censor them... + $context['preview_message'] = $form_message; + preparsecode($form_message, true); + preparsecode($context['preview_message']); + + // Do all bulletin board code tags, with or without smileys. + $context['preview_message'] = parse_bbc($context['preview_message'], isset($_REQUEST['ns']) ? 0 : 1); + + if ($form_subject != '') + { + $context['preview_subject'] = $form_subject; + + censorText($context['preview_subject']); + censorText($context['preview_message']); + } + else + $context['preview_subject'] = '' . $txt['no_subject'] . ''; + + // Protect any CDATA blocks. + if (isset($_REQUEST['xml'])) + $context['preview_message'] = strtr($context['preview_message'], array(']]>' => ']]]]>')); + } + + // Set up the checkboxes. + $context['notify'] = !empty($_REQUEST['notify']); + $context['use_smileys'] = !isset($_REQUEST['ns']); + + $context['icon'] = isset($_REQUEST['icon']) ? preg_replace('~[\./\\\\*\':"<>]~', '', $_REQUEST['icon']) : 'xx'; + + // Set the destination action for submission. + $context['destination'] = 'post2;start=' . $_REQUEST['start'] . (isset($_REQUEST['msg']) ? ';msg=' . $_REQUEST['msg'] . ';' . $context['session_var'] . '=' . $context['session_id'] : '') . (isset($_REQUEST['poll']) ? ';poll' : ''); + $context['submit_label'] = isset($_REQUEST['msg']) ? $txt['save'] : $txt['post']; + + // Previewing an edit? + if (isset($_REQUEST['msg']) && !empty($topic)) + { + // Get the existing message. + $request = $smcFunc['db_query']('', ' + SELECT + m.id_member, m.modified_time, m.smileys_enabled, m.body, + m.poster_name, m.poster_email, m.subject, m.icon, m.approved, + IFNULL(a.size, -1) AS filesize, a.filename, a.id_attach, + a.approved AS attachment_approved, t.id_member_started AS id_member_poster, + m.poster_time + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic}) + LEFT JOIN {db_prefix}attachments AS a ON (a.id_msg = m.id_msg AND a.attachment_type = {int:attachment_type}) + WHERE m.id_msg = {int:id_msg} + AND m.id_topic = {int:current_topic}', + array( + 'current_topic' => $topic, + 'attachment_type' => 0, + 'id_msg' => $_REQUEST['msg'], + ) + ); + // The message they were trying to edit was most likely deleted. + // !!! Change this error message? + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_board', false); + $row = $smcFunc['db_fetch_assoc']($request); + + $attachment_stuff = array($row); + while ($row2 = $smcFunc['db_fetch_assoc']($request)) + $attachment_stuff[] = $row2; + $smcFunc['db_free_result']($request); + + if ($row['id_member'] == $user_info['id'] && !allowedTo('modify_any')) + { + // Give an extra five minutes over the disable time threshold, so they can type - assuming the post is public. + if ($row['approved'] && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + ($modSettings['edit_disable_time'] + 5) * 60 < time()) + fatal_lang_error('modify_post_time_passed', false); + elseif ($row['id_member_poster'] == $user_info['id'] && !allowedTo('modify_own')) + isAllowedTo('modify_replies'); + else + isAllowedTo('modify_own'); + } + elseif ($row['id_member_poster'] == $user_info['id'] && !allowedTo('modify_any')) + isAllowedTo('modify_replies'); + else + isAllowedTo('modify_any'); + + if (!empty($modSettings['attachmentEnable'])) + { + $request = $smcFunc['db_query']('', ' + SELECT IFNULL(size, -1) AS filesize, filename, id_attach, approved + FROM {db_prefix}attachments + WHERE id_msg = {int:id_msg} + AND attachment_type = {int:attachment_type}', + array( + 'id_msg' => (int) $_REQUEST['msg'], + 'attachment_type' => 0, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['filesize'] <= 0) + continue; + $context['current_attachments'][] = array( + 'name' => htmlspecialchars($row['filename']), + 'id' => $row['id_attach'], + 'approved' => $row['approved'], + ); + } + $smcFunc['db_free_result']($request); + } + + // Allow moderators to change names.... + if (allowedTo('moderate_forum') && !empty($topic)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member, poster_name, poster_email + FROM {db_prefix}messages + WHERE id_msg = {int:id_msg} + AND id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + 'id_msg' => (int) $_REQUEST['msg'], + ) + ); + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + if (empty($row['id_member'])) + { + $context['name'] = htmlspecialchars($row['poster_name']); + $context['email'] = htmlspecialchars($row['poster_email']); + } + } + } + + // No check is needed, since nothing is really posted. + checkSubmitOnce('free'); + } + // Editing a message... + elseif (isset($_REQUEST['msg']) && !empty($topic)) + { + $_REQUEST['msg'] = (int) $_REQUEST['msg']; + + // Get the existing message. + $request = $smcFunc['db_query']('', ' + SELECT + m.id_member, m.modified_time, m.smileys_enabled, m.body, + m.poster_name, m.poster_email, m.subject, m.icon, m.approved, + IFNULL(a.size, -1) AS filesize, a.filename, a.id_attach, + a.approved AS attachment_approved, t.id_member_started AS id_member_poster, + m.poster_time + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic}) + LEFT JOIN {db_prefix}attachments AS a ON (a.id_msg = m.id_msg AND a.attachment_type = {int:attachment_type}) + WHERE m.id_msg = {int:id_msg} + AND m.id_topic = {int:current_topic}', + array( + 'current_topic' => $topic, + 'attachment_type' => 0, + 'id_msg' => $_REQUEST['msg'], + ) + ); + // The message they were trying to edit was most likely deleted. + // !!! Change this error message? + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_board', false); + $row = $smcFunc['db_fetch_assoc']($request); + + $attachment_stuff = array($row); + while ($row2 = $smcFunc['db_fetch_assoc']($request)) + $attachment_stuff[] = $row2; + $smcFunc['db_free_result']($request); + + if ($row['id_member'] == $user_info['id'] && !allowedTo('modify_any')) + { + // Give an extra five minutes over the disable time threshold, so they can type - assuming the post is public. + if ($row['approved'] && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + ($modSettings['edit_disable_time'] + 5) * 60 < time()) + fatal_lang_error('modify_post_time_passed', false); + elseif ($row['id_member_poster'] == $user_info['id'] && !allowedTo('modify_own')) + isAllowedTo('modify_replies'); + else + isAllowedTo('modify_own'); + } + elseif ($row['id_member_poster'] == $user_info['id'] && !allowedTo('modify_any')) + isAllowedTo('modify_replies'); + else + isAllowedTo('modify_any'); + + // When was it last modified? + if (!empty($row['modified_time'])) + $context['last_modified'] = timeformat($row['modified_time']); + + // Get the stuff ready for the form. + $form_subject = $row['subject']; + $form_message = un_preparsecode($row['body']); + censorText($form_message); + censorText($form_subject); + + // Check the boxes that should be checked. + $context['use_smileys'] = !empty($row['smileys_enabled']); + $context['icon'] = $row['icon']; + + // Show an "approve" box if the user can approve it, and the message isn't approved. + if (!$row['approved'] && !$context['show_approval']) + $context['show_approval'] = allowedTo('approve_posts'); + + // Load up 'em attachments! + foreach ($attachment_stuff as $attachment) + { + if ($attachment['filesize'] >= 0 && !empty($modSettings['attachmentEnable'])) + $context['current_attachments'][] = array( + 'name' => htmlspecialchars($attachment['filename']), + 'id' => $attachment['id_attach'], + 'approved' => $attachment['attachment_approved'], + ); + } + + // Allow moderators to change names.... + if (allowedTo('moderate_forum') && empty($row['id_member'])) + { + $context['name'] = htmlspecialchars($row['poster_name']); + $context['email'] = htmlspecialchars($row['poster_email']); + } + + // Set the destinaton. + $context['destination'] = 'post2;start=' . $_REQUEST['start'] . ';msg=' . $_REQUEST['msg'] . ';' . $context['session_var'] . '=' . $context['session_id'] . (isset($_REQUEST['poll']) ? ';poll' : ''); + $context['submit_label'] = $txt['save']; + } + // Posting... + else + { + // By default.... + $context['use_smileys'] = true; + $context['icon'] = 'xx'; + + if ($user_info['is_guest']) + { + $context['name'] = isset($_SESSION['guest_name']) ? $_SESSION['guest_name'] : ''; + $context['email'] = isset($_SESSION['guest_email']) ? $_SESSION['guest_email'] : ''; + } + $context['destination'] = 'post2;start=' . $_REQUEST['start'] . (isset($_REQUEST['poll']) ? ';poll' : ''); + + $context['submit_label'] = $txt['post']; + + // Posting a quoted reply? + if (!empty($topic) && !empty($_REQUEST['quote'])) + { + // Make sure they _can_ quote this post, and if so get it. + $request = $smcFunc['db_query']('', ' + SELECT m.subject, IFNULL(mem.real_name, m.poster_name) AS poster_name, m.poster_time, m.body + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board}) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE m.id_msg = {int:id_msg}' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : ' + AND m.approved = {int:is_approved}') . ' + LIMIT 1', + array( + 'id_msg' => (int) $_REQUEST['quote'], + 'is_approved' => 1, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('quoted_post_deleted', false); + list ($form_subject, $mname, $mdate, $form_message) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Add 'Re: ' to the front of the quoted subject. + if (trim($context['response_prefix']) != '' && $smcFunc['strpos']($form_subject, trim($context['response_prefix'])) !== 0) + $form_subject = $context['response_prefix'] . $form_subject; + + // Censor the message and subject. + censorText($form_message); + censorText($form_subject); + + // But if it's in HTML world, turn them into htmlspecialchar's so they can be edited! + if (strpos($form_message, '[html]') !== false) + { + $parts = preg_split('~(\[/code\]|\[code(?:=[^\]]+)?\])~i', $form_message, -1, PREG_SPLIT_DELIM_CAPTURE); + for ($i = 0, $n = count($parts); $i < $n; $i++) + { + // It goes 0 = outside, 1 = begin tag, 2 = inside, 3 = close tag, repeat. + if ($i % 4 == 0) + $parts[$i] = preg_replace_callback('~\[html\](.+?)\[/html\]~is', create_function('$m', ' return \'[html]\' . preg_replace(\'~~i\', \'<br />
\', "$m[1]") . \'[/html]\';'), $parts[$i]); + } + $form_message = implode('', $parts); + } + + $form_message = preg_replace('~
~i', "\n", $form_message); + + // Remove any nested quotes, if necessary. + if (!empty($modSettings['removeNestedQuotes'])) + $form_message = preg_replace(array('~\n?\[quote.*?\].+?\[/quote\]\n?~is', '~^\n~', '~\[/quote\]~'), '', $form_message); + + // Add a quote string on the front and end. + $form_message = '[quote author=' . $mname . ' link=topic=' . $topic . '.msg' . (int) $_REQUEST['quote'] . '#msg' . (int) $_REQUEST['quote'] . ' date=' . $mdate . ']' . "\n" . rtrim($form_message) . "\n" . '[/quote]'; + } + // Posting a reply without a quote? + elseif (!empty($topic) && empty($_REQUEST['quote'])) + { + // Get the first message's subject. + $form_subject = $first_subject; + + // Add 'Re: ' to the front of the subject. + if (trim($context['response_prefix']) != '' && $form_subject != '' && $smcFunc['strpos']($form_subject, trim($context['response_prefix'])) !== 0) + $form_subject = $context['response_prefix'] . $form_subject; + + // Censor the subject. + censorText($form_subject); + + $form_message = ''; + } + else + { + $form_subject = isset($_GET['subject']) ? $_GET['subject'] : ''; + $form_message = ''; + } + } + + // !!! This won't work if you're posting an event. + if (allowedTo('post_attachment') || allowedTo('post_unapproved_attachments')) + { + if (empty($_SESSION['temp_attachments'])) + $_SESSION['temp_attachments'] = array(); + + if (!empty($modSettings['currentAttachmentUploadDir'])) + { + if (!is_array($modSettings['attachmentUploadDir'])) + $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); + + // Just use the current path for temp files. + $current_attach_dir = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']]; + } + else + $current_attach_dir = $modSettings['attachmentUploadDir']; + + // If this isn't a new post, check the current attachments. + if (isset($_REQUEST['msg'])) + { + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*), SUM(size) + FROM {db_prefix}attachments + WHERE id_msg = {int:id_msg} + AND attachment_type = {int:attachment_type}', + array( + 'id_msg' => (int) $_REQUEST['msg'], + 'attachment_type' => 0, + ) + ); + list ($quantity, $total_size) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + else + { + $quantity = 0; + $total_size = 0; + } + + $temp_start = 0; + + if (!empty($_SESSION['temp_attachments'])) + { + if ($context['current_action'] != 'post2' || !empty($_POST['from_qr'])) + { + $context['post_error']['messages'][] = $txt['error_temp_attachments']; + $context['error_type'] = 'minor'; + } + + foreach ($_SESSION['temp_attachments'] as $attachID => $name) + { + $temp_start++; + + if (preg_match('~^post_tmp_' . $user_info['id'] . '_\d+$~', $attachID) == 0) + { + unset($_SESSION['temp_attachments'][$attachID]); + continue; + } + + if (!empty($_POST['attach_del']) && !in_array($attachID, $_POST['attach_del'])) + { + $deleted_attachments = true; + unset($_SESSION['temp_attachments'][$attachID]); + @unlink($current_attach_dir . '/' . $attachID); + continue; + } + + $quantity++; + $total_size += filesize($current_attach_dir . '/' . $attachID); + + $context['current_attachments'][] = array( + 'name' => htmlspecialchars($name), + 'id' => $attachID, + 'approved' => 1, + ); + } + } + + if (!empty($_POST['attach_del'])) + { + $del_temp = array(); + foreach ($_POST['attach_del'] as $i => $dummy) + $del_temp[$i] = (int) $dummy; + + foreach ($context['current_attachments'] as $k => $dummy) + if (!in_array($dummy['id'], $del_temp)) + { + $context['current_attachments'][$k]['unchecked'] = true; + $deleted_attachments = !isset($deleted_attachments) || is_bool($deleted_attachments) ? 1 : $deleted_attachments + 1; + $quantity--; + } + } + + if (!empty($_FILES['attachment'])) + foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy) + { + if ($_FILES['attachment']['name'][$n] == '') + continue; + + if (!is_uploaded_file($_FILES['attachment']['tmp_name'][$n]) || (@ini_get('open_basedir') == '' && !file_exists($_FILES['attachment']['tmp_name'][$n]))) + fatal_lang_error('attach_timeout', 'critical'); + + if (!empty($modSettings['attachmentSizeLimit']) && $_FILES['attachment']['size'][$n] > $modSettings['attachmentSizeLimit'] * 1024) + fatal_lang_error('file_too_big', false, array($modSettings['attachmentSizeLimit'])); + + $quantity++; + if (!empty($modSettings['attachmentNumPerPostLimit']) && $quantity > $modSettings['attachmentNumPerPostLimit']) + fatal_lang_error('attachments_limit_per_post', false, array($modSettings['attachmentNumPerPostLimit'])); + + $total_size += $_FILES['attachment']['size'][$n]; + if (!empty($modSettings['attachmentPostLimit']) && $total_size > $modSettings['attachmentPostLimit'] * 1024) + fatal_lang_error('file_too_big', false, array($modSettings['attachmentPostLimit'])); + + if (!empty($modSettings['attachmentCheckExtensions'])) + { + if (!in_array(strtolower(substr(strrchr($_FILES['attachment']['name'][$n], '.'), 1)), explode(',', strtolower($modSettings['attachmentExtensions'])))) + fatal_error($_FILES['attachment']['name'][$n] . '.
' . $txt['cant_upload_type'] . ' ' . $modSettings['attachmentExtensions'] . '.', false); + } + + if (!empty($modSettings['attachmentDirSizeLimit'])) + { + // Make sure the directory isn't full. + $dirSize = 0; + $dir = @opendir($current_attach_dir) or fatal_lang_error('cant_access_upload_path', 'critical'); + while ($file = readdir($dir)) + { + if ($file == '.' || $file == '..') + continue; + + if (preg_match('~^post_tmp_\d+_\d+$~', $file) != 0) + { + // Temp file is more than 5 hours old! + if (filemtime($current_attach_dir . '/' . $file) < time() - 18000) + @unlink($current_attach_dir . '/' . $file); + continue; + } + + $dirSize += filesize($current_attach_dir . '/' . $file); + } + closedir($dir); + + // Too big! Maybe you could zip it or something... + if ($_FILES['attachment']['size'][$n] + $dirSize > $modSettings['attachmentDirSizeLimit'] * 1024) + fatal_lang_error('ran_out_of_space'); + } + + if (!is_writable($current_attach_dir)) + fatal_lang_error('attachments_no_write', 'critical'); + + $attachID = 'post_tmp_' . $user_info['id'] . '_' . $temp_start++; + $_SESSION['temp_attachments'][$attachID] = basename($_FILES['attachment']['name'][$n]); + $context['current_attachments'][] = array( + 'name' => htmlspecialchars(basename($_FILES['attachment']['name'][$n])), + 'id' => $attachID, + 'approved' => 1, + ); + + $destName = $current_attach_dir . '/' . $attachID; + + if (!move_uploaded_file($_FILES['attachment']['tmp_name'][$n], $destName)) + fatal_lang_error('attach_timeout', 'critical'); + @chmod($destName, 0644); + } + } + + // If we are coming here to make a reply, and someone has already replied... make a special warning message. + if (isset($newRepliesError)) + { + $context['post_error']['messages'][] = $newRepliesError == 1 ? $txt['error_new_reply'] : $txt['error_new_replies']; + $context['error_type'] = 'minor'; + } + + if (isset($oldTopicError)) + { + $context['post_error']['messages'][] = sprintf($txt['error_old_topic'], $modSettings['oldTopicDays']); + $context['error_type'] = 'minor'; + } + + // What are you doing? Posting a poll, modifying, previewing, new post, or reply... + if (isset($_REQUEST['poll'])) + $context['page_title'] = $txt['new_poll']; + elseif ($context['make_event']) + $context['page_title'] = $context['event']['id'] == -1 ? $txt['calendar_post_event'] : $txt['calendar_edit']; + elseif (isset($_REQUEST['msg'])) + $context['page_title'] = $txt['modify_msg']; + elseif (isset($_REQUEST['subject'], $context['preview_subject'])) + $context['page_title'] = $txt['preview'] . ' - ' . strip_tags($context['preview_subject']); + elseif (empty($topic)) + $context['page_title'] = $txt['start_new_topic']; + else + $context['page_title'] = $txt['post_reply']; + + // Build the link tree. + if (empty($topic)) + $context['linktree'][] = array( + 'name' => '' . $txt['start_new_topic'] . '' + ); + else + $context['linktree'][] = array( + 'url' => $scripturl . '?topic=' . $topic . '.' . $_REQUEST['start'], + 'name' => $form_subject, + 'extra_before' => '' . $context['page_title'] . ' ( ', + 'extra_after' => ' )' + ); + + // Give wireless a linktree url to the post screen, so that they can switch to full version. + if (WIRELESS) + $context['linktree'][count($context['linktree']) - 1]['url'] = $scripturl . '?action=post;' . (!empty($topic) ? 'topic=' . $topic : 'board=' . $board) . '.' . $_REQUEST['start'] . (isset($_REQUEST['msg']) ? ';msg=' . (int) $_REQUEST['msg'] . ';' . $context['session_var'] . '=' . $context['session_id'] : ''); + + // If they've unchecked an attachment, they may still want to attach that many more files, but don't allow more than num_allowed_attachments. + // !!! This won't work if you're posting an event. + $context['num_allowed_attachments'] = empty($modSettings['attachmentNumPerPostLimit']) ? 50 : min($modSettings['attachmentNumPerPostLimit'] - count($context['current_attachments']) + (isset($deleted_attachments) ? $deleted_attachments : 0), $modSettings['attachmentNumPerPostLimit']); + $context['can_post_attachment'] = !empty($modSettings['attachmentEnable']) && $modSettings['attachmentEnable'] == 1 && (allowedTo('post_attachment') || ($modSettings['postmod_active'] && allowedTo('post_unapproved_attachments'))) && $context['num_allowed_attachments'] > 0; + $context['can_post_attachment_unapproved'] = allowedTo('post_attachment'); + + $context['subject'] = addcslashes($form_subject, '"'); + $context['message'] = str_replace(array('"', '<', '>', ' '), array('"', '<', '>', ' '), $form_message); + + // Needed for the editor and message icons. + require_once($sourcedir . '/Subs-Editor.php'); + + // Now create the editor. + $editorOptions = array( + 'id' => 'message', + 'value' => $context['message'], + 'labels' => array( + 'post_button' => $context['submit_label'], + ), + // add height and width for the editor + 'height' => '175px', + 'width' => '100%', + // We do XML preview here. + 'preview_type' => 2, + ); + create_control_richedit($editorOptions); + + // Store the ID. + $context['post_box_name'] = $editorOptions['id']; + + $context['attached'] = ''; + $context['make_poll'] = isset($_REQUEST['poll']); + + // Message icons - customized icons are off? + $context['icons'] = getMessageIcons($board); + + if (!empty($context['icons'])) + $context['icons'][count($context['icons']) - 1]['is_last'] = true; + + $context['icon_url'] = ''; + for ($i = 0, $n = count($context['icons']); $i < $n; $i++) + { + $context['icons'][$i]['selected'] = $context['icon'] == $context['icons'][$i]['value']; + if ($context['icons'][$i]['selected']) + $context['icon_url'] = $context['icons'][$i]['url']; + } + if (empty($context['icon_url'])) + { + $context['icon_url'] = $settings[file_exists($settings['theme_dir'] . '/images/post/' . $context['icon'] . '.gif') ? 'images_url' : 'default_images_url'] . '/post/' . $context['icon'] . '.gif'; + array_unshift($context['icons'], array( + 'value' => $context['icon'], + 'name' => $txt['current_icon'], + 'url' => $context['icon_url'], + 'is_last' => empty($context['icons']), + 'selected' => true, + )); + } + + if (!empty($topic) && !empty($modSettings['topicSummaryPosts'])) + getTopic(); + + // If the user can post attachments prepare the warning labels. + if ($context['can_post_attachment']) + { + $context['allowed_extensions'] = strtr($modSettings['attachmentExtensions'], array(',' => ', ')); + $context['attachment_restrictions'] = array(); + $attachmentRestrictionTypes = array('attachmentNumPerPostLimit', 'attachmentPostLimit', 'attachmentSizeLimit'); + foreach ($attachmentRestrictionTypes as $type) + if (!empty($modSettings[$type])) + $context['attachment_restrictions'][] = sprintf($txt['attach_restrict_' . $type], $modSettings[$type]); + } + + $context['back_to_topic'] = isset($_REQUEST['goback']) || (isset($_REQUEST['msg']) && !isset($_REQUEST['subject'])); + $context['show_additional_options'] = !empty($_POST['additional_options']) || !empty($_SESSION['temp_attachments']) || !empty($deleted_attachments); + + $context['is_new_topic'] = empty($topic); + $context['is_new_post'] = !isset($_REQUEST['msg']); + $context['is_first_post'] = $context['is_new_topic'] || (isset($_REQUEST['msg']) && $_REQUEST['msg'] == $id_first_msg); + + // Do we need to show the visual verification image? + $context['require_verification'] = !$user_info['is_mod'] && !$user_info['is_admin'] && !empty($modSettings['posts_require_captcha']) && ($user_info['posts'] < $modSettings['posts_require_captcha'] || ($user_info['is_guest'] && $modSettings['posts_require_captcha'] == -1)); + if ($context['require_verification']) + { + require_once($sourcedir . '/Subs-Editor.php'); + $verificationOptions = array( + 'id' => 'post', + ); + $context['require_verification'] = create_control_verification($verificationOptions); + $context['visual_verification_id'] = $verificationOptions['id']; + } + + // If they came from quick reply, and have to enter verification details, give them some notice. + if (!empty($_REQUEST['from_qr']) && !empty($context['require_verification'])) + { + $context['post_error']['messages'][] = $txt['enter_verification_details']; + $context['error_type'] = 'minor'; + } + + // WYSIWYG only works if BBC is enabled + $modSettings['disable_wysiwyg'] = !empty($modSettings['disable_wysiwyg']) || empty($modSettings['enableBBC']); + + // Register this form in the session variables. + checkSubmitOnce('register'); + + // Finally, load the template. + if (WIRELESS && WIRELESS_PROTOCOL != 'wap') + $context['sub_template'] = WIRELESS_PROTOCOL . '_post'; + elseif (!isset($_REQUEST['xml'])) + loadTemplate('Post'); +} + +function Post2() +{ + global $board, $topic, $txt, $modSettings, $sourcedir, $context; + global $user_info, $board_info, $options, $smcFunc; + + // Sneaking off, are we? + if (empty($_POST) && empty($topic)) + redirectexit('action=post;board=' . $board . '.0'); + elseif (empty($_POST) && !empty($topic)) + redirectexit('action=post;topic=' . $topic . '.0'); + + // No need! + $context['robot_no_index'] = true; + + // If we came from WYSIWYG then turn it back into BBC regardless. + if (!empty($_REQUEST['message_mode']) && isset($_REQUEST['message'])) + { + require_once($sourcedir . '/Subs-Editor.php'); + + $_REQUEST['message'] = html_to_bbc($_REQUEST['message']); + + // We need to unhtml it now as it gets done shortly. + $_REQUEST['message'] = un_htmlspecialchars($_REQUEST['message']); + + // We need this for everything else. + $_POST['message'] = $_REQUEST['message']; + } + + // Previewing? Go back to start. + if (isset($_REQUEST['preview'])) + return Post(); + + // Prevent double submission of this form. + checkSubmitOnce('check'); + + // No errors as yet. + $post_errors = array(); + + // If the session has timed out, let the user re-submit their form. + if (checkSession('post', '', false) != '') + $post_errors[] = 'session_timeout'; + + // Wrong verification code? + if (!$user_info['is_admin'] && !$user_info['is_mod'] && !empty($modSettings['posts_require_captcha']) && ($user_info['posts'] < $modSettings['posts_require_captcha'] || ($user_info['is_guest'] && $modSettings['posts_require_captcha'] == -1))) + { + require_once($sourcedir . '/Subs-Editor.php'); + $verificationOptions = array( + 'id' => 'post', + ); + $context['require_verification'] = create_control_verification($verificationOptions, true); + if (is_array($context['require_verification'])) + $post_errors = array_merge($post_errors, $context['require_verification']); + } + + require_once($sourcedir . '/Subs-Post.php'); + loadLanguage('Post'); + + // If this isn't a new topic load the topic info that we need. + if (!empty($topic)) + { + $request = $smcFunc['db_query']('', ' + SELECT locked, is_sticky, id_poll, approved, id_first_msg, id_last_msg, id_member_started, id_board + FROM {db_prefix}topics + WHERE id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + ) + ); + $topic_info = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Though the topic should be there, it might have vanished. + if (!is_array($topic_info)) + fatal_lang_error('topic_doesnt_exist'); + + // Did this topic suddenly move? Just checking... + if ($topic_info['id_board'] != $board) + fatal_lang_error('not_a_topic'); + } + + // Replying to a topic? + if (!empty($topic) && !isset($_REQUEST['msg'])) + { + // Don't allow a post if it's locked. + if ($topic_info['locked'] != 0 && !allowedTo('moderate_board')) + fatal_lang_error('topic_locked', false); + + // Sorry, multiple polls aren't allowed... yet. You should stop giving me ideas :P. + if (isset($_REQUEST['poll']) && $topic_info['id_poll'] > 0) + unset($_REQUEST['poll']); + + // Do the permissions and approval stuff... + $becomesApproved = true; + if ($topic_info['id_member_started'] != $user_info['id']) + { + if ($modSettings['postmod_active'] && allowedTo('post_unapproved_replies_any') && !allowedTo('post_reply_any')) + $becomesApproved = false; + else + isAllowedTo('post_reply_any'); + } + elseif (!allowedTo('post_reply_any')) + { + if ($modSettings['postmod_active'] && allowedTo('post_unapproved_replies_own') && !allowedTo('post_reply_own')) + $becomesApproved = false; + else + isAllowedTo('post_reply_own'); + } + + if (isset($_POST['lock'])) + { + // Nothing is changed to the lock. + if ((empty($topic_info['locked']) && empty($_POST['lock'])) || (!empty($_POST['lock']) && !empty($topic_info['locked']))) + unset($_POST['lock']); + // You're have no permission to lock this topic. + elseif (!allowedTo(array('lock_any', 'lock_own')) || (!allowedTo('lock_any') && $user_info['id'] != $topic_info['id_member_started'])) + unset($_POST['lock']); + // You are allowed to (un)lock your own topic only. + elseif (!allowedTo('lock_any')) + { + // You cannot override a moderator lock. + if ($topic_info['locked'] == 1) + unset($_POST['lock']); + else + $_POST['lock'] = empty($_POST['lock']) ? 0 : 2; + } + // Hail mighty moderator, (un)lock this topic immediately. + else + $_POST['lock'] = empty($_POST['lock']) ? 0 : 1; + } + + // So you wanna (un)sticky this...let's see. + if (isset($_POST['sticky']) && (empty($modSettings['enableStickyTopics']) || $_POST['sticky'] == $topic_info['is_sticky'] || !allowedTo('make_sticky'))) + unset($_POST['sticky']); + + // If the number of replies has changed, if the setting is enabled, go back to Post() - which handles the error. + if (empty($options['no_new_reply_warning']) && isset($_POST['last_msg']) && $topic_info['id_last_msg'] > $_POST['last_msg']) + { + $_REQUEST['preview'] = true; + return Post(); + } + + $posterIsGuest = $user_info['is_guest']; + } + // Posting a new topic. + elseif (empty($topic)) + { + // Now don't be silly, new topics will get their own id_msg soon enough. + unset($_REQUEST['msg'], $_POST['msg'], $_GET['msg']); + + // Do like, the permissions, for safety and stuff... + $becomesApproved = true; + if ($modSettings['postmod_active'] && !allowedTo('post_new') && allowedTo('post_unapproved_topics')) + $becomesApproved = false; + else + isAllowedTo('post_new'); + + if (isset($_POST['lock'])) + { + // New topics are by default not locked. + if (empty($_POST['lock'])) + unset($_POST['lock']); + // Besides, you need permission. + elseif (!allowedTo(array('lock_any', 'lock_own'))) + unset($_POST['lock']); + // A moderator-lock (1) can override a user-lock (2). + else + $_POST['lock'] = allowedTo('lock_any') ? 1 : 2; + } + + if (isset($_POST['sticky']) && (empty($modSettings['enableStickyTopics']) || empty($_POST['sticky']) || !allowedTo('make_sticky'))) + unset($_POST['sticky']); + + $posterIsGuest = $user_info['is_guest']; + } + // Modifying an existing message? + elseif (isset($_REQUEST['msg']) && !empty($topic)) + { + $_REQUEST['msg'] = (int) $_REQUEST['msg']; + + $request = $smcFunc['db_query']('', ' + SELECT id_member, poster_name, poster_email, poster_time, approved + FROM {db_prefix}messages + WHERE id_msg = {int:id_msg} + LIMIT 1', + array( + 'id_msg' => $_REQUEST['msg'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('cant_find_messages', false); + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + if (!empty($topic_info['locked']) && !allowedTo('moderate_board')) + fatal_lang_error('topic_locked', false); + + if (isset($_POST['lock'])) + { + // Nothing changes to the lock status. + if ((empty($_POST['lock']) && empty($topic_info['locked'])) || (!empty($_POST['lock']) && !empty($topic_info['locked']))) + unset($_POST['lock']); + // You're simply not allowed to (un)lock this. + elseif (!allowedTo(array('lock_any', 'lock_own')) || (!allowedTo('lock_any') && $user_info['id'] != $topic_info['id_member_started'])) + unset($_POST['lock']); + // You're only allowed to lock your own topics. + elseif (!allowedTo('lock_any')) + { + // You're not allowed to break a moderator's lock. + if ($topic_info['locked'] == 1) + unset($_POST['lock']); + // Lock it with a soft lock or unlock it. + else + $_POST['lock'] = empty($_POST['lock']) ? 0 : 2; + } + // You must be the moderator. + else + $_POST['lock'] = empty($_POST['lock']) ? 0 : 1; + } + + // Change the sticky status of this topic? + if (isset($_POST['sticky']) && (!allowedTo('make_sticky') || $_POST['sticky'] == $topic_info['is_sticky'])) + unset($_POST['sticky']); + + if ($row['id_member'] == $user_info['id'] && !allowedTo('modify_any')) + { + if ((!$modSettings['postmod_active'] || $row['approved']) && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + ($modSettings['edit_disable_time'] + 5) * 60 < time()) + fatal_lang_error('modify_post_time_passed', false); + elseif ($topic_info['id_member_started'] == $user_info['id'] && !allowedTo('modify_own')) + isAllowedTo('modify_replies'); + else + isAllowedTo('modify_own'); + } + elseif ($topic_info['id_member_started'] == $user_info['id'] && !allowedTo('modify_any')) + { + isAllowedTo('modify_replies'); + + // If you're modifying a reply, I say it better be logged... + $moderationAction = true; + } + else + { + isAllowedTo('modify_any'); + + // Log it, assuming you're not modifying your own post. + if ($row['id_member'] != $user_info['id']) + $moderationAction = true; + } + + $posterIsGuest = empty($row['id_member']); + + // Can they approve it? + $can_approve = allowedTo('approve_posts'); + $becomesApproved = $modSettings['postmod_active'] ? ($can_approve && !$row['approved'] ? (!empty($_REQUEST['approve']) ? 1 : 0) : $row['approved']) : 1; + $approve_has_changed = $row['approved'] != $becomesApproved; + + if (!allowedTo('moderate_forum') || !$posterIsGuest) + { + $_POST['guestname'] = $row['poster_name']; + $_POST['email'] = $row['poster_email']; + } + } + + // If the poster is a guest evaluate the legality of name and email. + if ($posterIsGuest) + { + $_POST['guestname'] = !isset($_POST['guestname']) ? '' : trim($_POST['guestname']); + $_POST['email'] = !isset($_POST['email']) ? '' : trim($_POST['email']); + + if ($_POST['guestname'] == '' || $_POST['guestname'] == '_') + $post_errors[] = 'no_name'; + if ($smcFunc['strlen']($_POST['guestname']) > 25) + $post_errors[] = 'long_name'; + + if (empty($modSettings['guest_post_no_email'])) + { + // Only check if they changed it! + if (!isset($row) || $row['poster_email'] != $_POST['email']) + { + if (!allowedTo('moderate_forum') && (!isset($_POST['email']) || $_POST['email'] == '')) + $post_errors[] = 'no_email'; + if (!allowedTo('moderate_forum') && preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $_POST['email']) == 0) + $post_errors[] = 'bad_email'; + } + + // Now make sure this email address is not banned from posting. + isBannedEmail($_POST['email'], 'cannot_post', sprintf($txt['you_are_post_banned'], $txt['guest_title'])); + } + + // In case they are making multiple posts this visit, help them along by storing their name. + if (empty($post_errors)) + { + $_SESSION['guest_name'] = $_POST['guestname']; + $_SESSION['guest_email'] = $_POST['email']; + } + } + + // Check the subject and message. + if (!isset($_POST['subject']) || $smcFunc['htmltrim']($smcFunc['htmlspecialchars']($_POST['subject'])) === '') + $post_errors[] = 'no_subject'; + if (!isset($_POST['message']) || $smcFunc['htmltrim']($smcFunc['htmlspecialchars']($_POST['message']), ENT_QUOTES) === '') + $post_errors[] = 'no_message'; + elseif (!empty($modSettings['max_messageLength']) && $smcFunc['strlen']($_POST['message']) > $modSettings['max_messageLength']) + $post_errors[] = 'long_message'; + else + { + // Prepare the message a bit for some additional testing. + $_POST['message'] = $smcFunc['htmlspecialchars']($_POST['message'], ENT_QUOTES); + + // Preparse code. (Zef) + if ($user_info['is_guest']) + $user_info['name'] = $_POST['guestname']; + preparsecode($_POST['message']); + + // Let's see if there's still some content left without the tags. + if ($smcFunc['htmltrim'](strip_tags(parse_bbc($_POST['message'], false), '')) === '' && (!allowedTo('admin_forum') || strpos($_POST['message'], '[html]') === false)) + $post_errors[] = 'no_message'; + } + if (isset($_POST['calendar']) && !isset($_REQUEST['deleteevent']) && $smcFunc['htmltrim']($_POST['evtitle']) === '') + $post_errors[] = 'no_event'; + // You are not! + if (isset($_POST['message']) && strtolower($_POST['message']) == 'i am the administrator.' && !$user_info['is_admin']) + fatal_error('Knave! Masquerader! Charlatan!', false); + + // Validate the poll... + if (isset($_REQUEST['poll']) && $modSettings['pollMode'] == '1') + { + if (!empty($topic) && !isset($_REQUEST['msg'])) + fatal_lang_error('no_access', false); + + // This is a new topic... so it's a new poll. + if (empty($topic)) + isAllowedTo('poll_post'); + // Can you add to your own topics? + elseif ($user_info['id'] == $topic_info['id_member_started'] && !allowedTo('poll_add_any')) + isAllowedTo('poll_add_own'); + // Can you add polls to any topic, then? + else + isAllowedTo('poll_add_any'); + + if (!isset($_POST['question']) || trim($_POST['question']) == '') + $post_errors[] = 'no_question'; + + $_POST['options'] = empty($_POST['options']) ? array() : htmltrim__recursive($_POST['options']); + + // Get rid of empty ones. + foreach ($_POST['options'] as $k => $option) + if ($option == '') + unset($_POST['options'][$k], $_POST['options'][$k]); + + // What are you going to vote between with one choice?!? + if (count($_POST['options']) < 2) + $post_errors[] = 'poll_few'; + } + + if ($posterIsGuest) + { + // If user is a guest, make sure the chosen name isn't taken. + require_once($sourcedir . '/Subs-Members.php'); + if (isReservedName($_POST['guestname'], 0, true, false) && (!isset($row['poster_name']) || $_POST['guestname'] != $row['poster_name'])) + $post_errors[] = 'bad_name'; + } + // If the user isn't a guest, get his or her name and email. + elseif (!isset($_REQUEST['msg'])) + { + $_POST['guestname'] = $user_info['username']; + $_POST['email'] = $user_info['email']; + } + + // Any mistakes? + if (!empty($post_errors)) + { + loadLanguage('Errors'); + // Previewing. + $_REQUEST['preview'] = true; + + $context['post_error'] = array('messages' => array()); + foreach ($post_errors as $post_error) + { + $context['post_error'][$post_error] = true; + if ($post_error == 'long_message') + $txt['error_' . $post_error] = sprintf($txt['error_' . $post_error], $modSettings['max_messageLength']); + + $context['post_error']['messages'][] = $txt['error_' . $post_error]; + } + + return Post(); + } + + // Make sure the user isn't spamming the board. + if (!isset($_REQUEST['msg'])) + spamProtection('post'); + + // At about this point, we're posting and that's that. + ignore_user_abort(true); + @set_time_limit(300); + + // Add special html entities to the subject, name, and email. + $_POST['subject'] = strtr($smcFunc['htmlspecialchars']($_POST['subject']), array("\r" => '', "\n" => '', "\t" => '')); + $_POST['guestname'] = htmlspecialchars($_POST['guestname']); + $_POST['email'] = htmlspecialchars($_POST['email']); + + // At this point, we want to make sure the subject isn't too long. + if ($smcFunc['strlen']($_POST['subject']) > 100) + $_POST['subject'] = $smcFunc['substr']($_POST['subject'], 0, 100); + + // Make the poll... + if (isset($_REQUEST['poll'])) + { + // Make sure that the user has not entered a ridiculous number of options.. + if (empty($_POST['poll_max_votes']) || $_POST['poll_max_votes'] <= 0) + $_POST['poll_max_votes'] = 1; + elseif ($_POST['poll_max_votes'] > count($_POST['options'])) + $_POST['poll_max_votes'] = count($_POST['options']); + else + $_POST['poll_max_votes'] = (int) $_POST['poll_max_votes']; + + $_POST['poll_expire'] = (int) $_POST['poll_expire']; + $_POST['poll_expire'] = $_POST['poll_expire'] > 9999 ? 9999 : ($_POST['poll_expire'] < 0 ? 0 : $_POST['poll_expire']); + + // Just set it to zero if it's not there.. + if (!isset($_POST['poll_hide'])) + $_POST['poll_hide'] = 0; + else + $_POST['poll_hide'] = (int) $_POST['poll_hide']; + $_POST['poll_change_vote'] = isset($_POST['poll_change_vote']) ? 1 : 0; + + $_POST['poll_guest_vote'] = isset($_POST['poll_guest_vote']) ? 1 : 0; + // Make sure guests are actually allowed to vote generally. + if ($_POST['poll_guest_vote']) + { + require_once($sourcedir . '/Subs-Members.php'); + $allowedVoteGroups = groupsAllowedTo('poll_vote', $board); + if (!in_array(-1, $allowedVoteGroups['allowed'])) + $_POST['poll_guest_vote'] = 0; + } + + // If the user tries to set the poll too far in advance, don't let them. + if (!empty($_POST['poll_expire']) && $_POST['poll_expire'] < 1) + fatal_lang_error('poll_range_error', false); + // Don't allow them to select option 2 for hidden results if it's not time limited. + elseif (empty($_POST['poll_expire']) && $_POST['poll_hide'] == 2) + $_POST['poll_hide'] = 1; + + // Clean up the question and answers. + $_POST['question'] = htmlspecialchars($_POST['question']); + $_POST['question'] = $smcFunc['truncate']($_POST['question'], 255); + $_POST['question'] = preg_replace('~&#(\d{4,5}|[2-9]\d{2,4}|1[2-9]\d);~', '&#$1;', $_POST['question']); + $_POST['options'] = htmlspecialchars__recursive($_POST['options']); + } + + // Check if they are trying to delete any current attachments.... + if (isset($_REQUEST['msg'], $_POST['attach_del']) && (allowedTo('post_attachment') || ($modSettings['postmod_active'] && allowedTo('post_unapproved_attachments')))) + { + $del_temp = array(); + foreach ($_POST['attach_del'] as $i => $dummy) + $del_temp[$i] = (int) $dummy; + + require_once($sourcedir . '/ManageAttachments.php'); + $attachmentQuery = array( + 'attachment_type' => 0, + 'id_msg' => (int) $_REQUEST['msg'], + 'not_id_attach' => $del_temp, + ); + removeAttachments($attachmentQuery); + } + + // ...or attach a new file... + if (isset($_FILES['attachment']['name']) || (!empty($_SESSION['temp_attachments']) && empty($_POST['from_qr']))) + { + // Verify they can post them! + if (!$modSettings['postmod_active'] || !allowedTo('post_unapproved_attachments')) + isAllowedTo('post_attachment'); + + // Make sure we're uploading to the right place. + if (!empty($modSettings['currentAttachmentUploadDir'])) + { + if (!is_array($modSettings['attachmentUploadDir'])) + $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); + + // The current directory, of course! + $current_attach_dir = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']]; + } + else + $current_attach_dir = $modSettings['attachmentUploadDir']; + + // If this isn't a new post, check the current attachments. + if (isset($_REQUEST['msg'])) + { + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*), SUM(size) + FROM {db_prefix}attachments + WHERE id_msg = {int:id_msg} + AND attachment_type = {int:attachment_type}', + array( + 'id_msg' => (int) $_REQUEST['msg'], + 'attachment_type' => 0, + ) + ); + list ($quantity, $total_size) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + else + { + $quantity = 0; + $total_size = 0; + } + + if (!empty($_SESSION['temp_attachments'])) + foreach ($_SESSION['temp_attachments'] as $attachID => $name) + { + if (preg_match('~^post_tmp_' . $user_info['id'] . '_\d+$~', $attachID) == 0) + continue; + + if (!empty($_POST['attach_del']) && !in_array($attachID, $_POST['attach_del'])) + { + unset($_SESSION['temp_attachments'][$attachID]); + @unlink($current_attach_dir . '/' . $attachID); + continue; + } + + $_FILES['attachment']['tmp_name'][] = $attachID; + $_FILES['attachment']['name'][] = $name; + $_FILES['attachment']['size'][] = filesize($current_attach_dir . '/' . $attachID); + list ($_FILES['attachment']['width'][], $_FILES['attachment']['height'][]) = @getimagesize($current_attach_dir . '/' . $attachID); + + unset($_SESSION['temp_attachments'][$attachID]); + } + + if (!isset($_FILES['attachment']['name'])) + $_FILES['attachment']['tmp_name'] = array(); + + $attachIDs = array(); + foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy) + { + if ($_FILES['attachment']['name'][$n] == '') + continue; + + // Have we reached the maximum number of files we are allowed? + $quantity++; + if (!empty($modSettings['attachmentNumPerPostLimit']) && $quantity > $modSettings['attachmentNumPerPostLimit']) + { + checkSubmitOnce('free'); + fatal_lang_error('attachments_limit_per_post', false, array($modSettings['attachmentNumPerPostLimit'])); + } + + // Check the total upload size for this post... + $total_size += $_FILES['attachment']['size'][$n]; + if (!empty($modSettings['attachmentPostLimit']) && $total_size > $modSettings['attachmentPostLimit'] * 1024) + { + checkSubmitOnce('free'); + fatal_lang_error('file_too_big', false, array($modSettings['attachmentPostLimit'])); + } + + $attachmentOptions = array( + 'post' => isset($_REQUEST['msg']) ? $_REQUEST['msg'] : 0, + 'poster' => $user_info['id'], + 'name' => $_FILES['attachment']['name'][$n], + 'tmp_name' => $_FILES['attachment']['tmp_name'][$n], + 'size' => $_FILES['attachment']['size'][$n], + 'approved' => !$modSettings['postmod_active'] || allowedTo('post_attachment'), + ); + + if (createAttachment($attachmentOptions)) + { + $attachIDs[] = $attachmentOptions['id']; + if (!empty($attachmentOptions['thumb'])) + $attachIDs[] = $attachmentOptions['thumb']; + } + else + { + if (in_array('could_not_upload', $attachmentOptions['errors'])) + { + checkSubmitOnce('free'); + fatal_lang_error('attach_timeout', 'critical'); + } + if (in_array('too_large', $attachmentOptions['errors'])) + { + checkSubmitOnce('free'); + fatal_lang_error('file_too_big', false, array($modSettings['attachmentSizeLimit'])); + } + if (in_array('bad_extension', $attachmentOptions['errors'])) + { + checkSubmitOnce('free'); + fatal_error($attachmentOptions['name'] . '.
' . $txt['cant_upload_type'] . ' ' . $modSettings['attachmentExtensions'] . '.', false); + } + if (in_array('directory_full', $attachmentOptions['errors'])) + { + checkSubmitOnce('free'); + fatal_lang_error('ran_out_of_space', 'critical'); + } + if (in_array('bad_filename', $attachmentOptions['errors'])) + { + checkSubmitOnce('free'); + fatal_error(basename($attachmentOptions['name']) . '.
' . $txt['restricted_filename'] . '.', 'critical'); + } + if (in_array('taken_filename', $attachmentOptions['errors'])) + { + checkSubmitOnce('free'); + fatal_lang_error('filename_exists'); + } + if (in_array('bad_attachment', $attachmentOptions['errors'])) + { + checkSubmitOnce('free'); + fatal_lang_error('bad_attachment'); + } + } + } + } + + // Make the poll... + if (isset($_REQUEST['poll'])) + { + // Create the poll. + $smcFunc['db_insert']('', + '{db_prefix}polls', + array( + 'question' => 'string-255', 'hide_results' => 'int', 'max_votes' => 'int', 'expire_time' => 'int', 'id_member' => 'int', + 'poster_name' => 'string-255', 'change_vote' => 'int', 'guest_vote' => 'int' + ), + array( + $_POST['question'], $_POST['poll_hide'], $_POST['poll_max_votes'], (empty($_POST['poll_expire']) ? 0 : time() + $_POST['poll_expire'] * 3600 * 24), $user_info['id'], + $_POST['guestname'], $_POST['poll_change_vote'], $_POST['poll_guest_vote'], + ), + array('id_poll') + ); + $id_poll = $smcFunc['db_insert_id']('{db_prefix}polls', 'id_poll'); + + // Create each answer choice. + $i = 0; + $pollOptions = array(); + foreach ($_POST['options'] as $option) + { + $pollOptions[] = array($id_poll, $i, $option); + $i++; + } + + $smcFunc['db_insert']('insert', + '{db_prefix}poll_choices', + array('id_poll' => 'int', 'id_choice' => 'int', 'label' => 'string-255'), + $pollOptions, + array('id_poll', 'id_choice') + ); + } + else + $id_poll = 0; + + // Creating a new topic? + $newTopic = empty($_REQUEST['msg']) && empty($topic); + + $_POST['icon'] = !empty($attachIDs) && $_POST['icon'] == 'xx' ? 'clip' : $_POST['icon']; + + // Collect all parameters for the creation or modification of a post. + $msgOptions = array( + 'id' => empty($_REQUEST['msg']) ? 0 : (int) $_REQUEST['msg'], + 'subject' => $_POST['subject'], + 'body' => $_POST['message'], + 'icon' => preg_replace('~[\./\\\\*:"\'<>]~', '', $_POST['icon']), + 'smileys_enabled' => !isset($_POST['ns']), + 'attachments' => empty($attachIDs) ? array() : $attachIDs, + 'approved' => $becomesApproved, + ); + $topicOptions = array( + 'id' => empty($topic) ? 0 : $topic, + 'board' => $board, + 'poll' => isset($_REQUEST['poll']) ? $id_poll : null, + 'lock_mode' => isset($_POST['lock']) ? (int) $_POST['lock'] : null, + 'sticky_mode' => isset($_POST['sticky']) && !empty($modSettings['enableStickyTopics']) ? (int) $_POST['sticky'] : null, + 'mark_as_read' => true, + 'is_approved' => !$modSettings['postmod_active'] || empty($topic) || !empty($board_info['cur_topic_approved']), + ); + $posterOptions = array( + 'id' => $user_info['id'], + 'name' => $_POST['guestname'], + 'email' => $_POST['email'], + 'update_post_count' => !$user_info['is_guest'] && !isset($_REQUEST['msg']) && $board_info['posts_count'], + ); + + // This is an already existing message. Edit it. + if (!empty($_REQUEST['msg'])) + { + // Have admins allowed people to hide their screwups? + if (time() - $row['poster_time'] > $modSettings['edit_wait_time'] || $user_info['id'] != $row['id_member']) + { + $msgOptions['modify_time'] = time(); + $msgOptions['modify_name'] = $user_info['name']; + } + + // This will save some time... + if (empty($approve_has_changed)) + unset($msgOptions['approved']); + + modifyPost($msgOptions, $topicOptions, $posterOptions); + } + // This is a new topic or an already existing one. Save it. + else + { + createPost($msgOptions, $topicOptions, $posterOptions); + + if (isset($topicOptions['id'])) + $topic = $topicOptions['id']; + } + + // Editing or posting an event? + if (isset($_POST['calendar']) && (!isset($_REQUEST['eventid']) || $_REQUEST['eventid'] == -1)) + { + require_once($sourcedir . '/Subs-Calendar.php'); + + // Make sure they can link an event to this post. + canLinkEvent(); + + // Insert the event. + $eventOptions = array( + 'board' => $board, + 'topic' => $topic, + 'title' => $_POST['evtitle'], + 'member' => $user_info['id'], + 'start_date' => sprintf('%04d-%02d-%02d', $_POST['year'], $_POST['month'], $_POST['day']), + 'span' => isset($_POST['span']) && $_POST['span'] > 0 ? min((int) $modSettings['cal_maxspan'], (int) $_POST['span'] - 1) : 0, + ); + insertEvent($eventOptions); + } + elseif (isset($_POST['calendar'])) + { + $_REQUEST['eventid'] = (int) $_REQUEST['eventid']; + + // Validate the post... + require_once($sourcedir . '/Subs-Calendar.php'); + validateEventPost(); + + // If you're not allowed to edit any events, you have to be the poster. + if (!allowedTo('calendar_edit_any')) + { + // Get the event's poster. + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}calendar + WHERE id_event = {int:id_event}', + array( + 'id_event' => $_REQUEST['eventid'], + ) + ); + $row2 = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Silly hacker, Trix are for kids. ...probably trademarked somewhere, this is FAIR USE! (parody...) + isAllowedTo('calendar_edit_' . ($row2['id_member'] == $user_info['id'] ? 'own' : 'any')); + } + + // Delete it? + if (isset($_REQUEST['deleteevent'])) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}calendar + WHERE id_event = {int:id_event}', + array( + 'id_event' => $_REQUEST['eventid'], + ) + ); + // ... or just update it? + else + { + $span = !empty($modSettings['cal_allowspan']) && !empty($_REQUEST['span']) ? min((int) $modSettings['cal_maxspan'], (int) $_REQUEST['span'] - 1) : 0; + $start_time = mktime(0, 0, 0, (int) $_REQUEST['month'], (int) $_REQUEST['day'], (int) $_REQUEST['year']); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}calendar + SET end_date = {date:end_date}, + start_date = {date:start_date}, + title = {string:title} + WHERE id_event = {int:id_event}', + array( + 'end_date' => strftime('%Y-%m-%d', $start_time + $span * 86400), + 'start_date' => strftime('%Y-%m-%d', $start_time), + 'id_event' => $_REQUEST['eventid'], + 'title' => $smcFunc['htmlspecialchars']($_REQUEST['evtitle'], ENT_QUOTES), + ) + ); + } + updateSettings(array( + 'calendar_updated' => time(), + )); + } + + // Marking read should be done even for editing messages.... + // Mark all the parents read. (since you just posted and they will be unread.) + if (!$user_info['is_guest'] && !empty($board_info['parent_boards'])) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_boards + SET id_msg = {int:id_msg} + WHERE id_member = {int:current_member} + AND id_board IN ({array_int:board_list})', + array( + 'current_member' => $user_info['id'], + 'board_list' => array_keys($board_info['parent_boards']), + 'id_msg' => $modSettings['maxMsgID'], + ) + ); + } + + // Turn notification on or off. (note this just blows smoke if it's already on or off.) + if (!empty($_POST['notify']) && allowedTo('mark_any_notify')) + { + $smcFunc['db_insert']('ignore', + '{db_prefix}log_notify', + array('id_member' => 'int', 'id_topic' => 'int', 'id_board' => 'int'), + array($user_info['id'], $topic, 0), + array('id_member', 'id_topic', 'id_board') + ); + } + elseif (!$newTopic) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_notify + WHERE id_member = {int:current_member} + AND id_topic = {int:current_topic}', + array( + 'current_member' => $user_info['id'], + 'current_topic' => $topic, + ) + ); + + // Log an act of moderation - modifying. + if (!empty($moderationAction)) + logAction('modify', array('topic' => $topic, 'message' => (int) $_REQUEST['msg'], 'member' => $row['id_member'], 'board' => $board)); + + if (isset($_POST['lock']) && $_POST['lock'] != 2) + logAction('lock', array('topic' => $topicOptions['id'], 'board' => $topicOptions['board'])); + + if (isset($_POST['sticky']) && !empty($modSettings['enableStickyTopics'])) + logAction('sticky', array('topic' => $topicOptions['id'], 'board' => $topicOptions['board'])); + + // Notify any members who have notification turned on for this topic - only do this if it's going to be approved(!) + if ($becomesApproved) + { + if ($newTopic) + { + $notifyData = array( + 'body' => $_POST['message'], + 'subject' => $_POST['subject'], + 'name' => $user_info['name'], + 'poster' => $user_info['id'], + 'msg' => $msgOptions['id'], + 'board' => $board, + 'topic' => $topic, + ); + notifyMembersBoard($notifyData); + } + elseif (empty($_REQUEST['msg'])) + { + // Only send it to everyone if the topic is approved, otherwise just to the topic starter if they want it. + if ($topic_info['approved']) + sendNotifications($topic, 'reply'); + else + sendNotifications($topic, 'reply', array(), $topic_info['id_member_started']); + } + } + + // Returning to the topic? + if (!empty($_REQUEST['goback'])) + { + // Mark the board as read.... because it might get confusing otherwise. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_boards + SET id_msg = {int:maxMsgID} + WHERE id_member = {int:current_member} + AND id_board = {int:current_board}', + array( + 'current_board' => $board, + 'current_member' => $user_info['id'], + 'maxMsgID' => $modSettings['maxMsgID'], + ) + ); + } + + if ($board_info['num_topics'] == 0) + cache_put_data('board-' . $board, null, 120); + + if (!empty($_POST['announce_topic'])) + redirectexit('action=announce;sa=selectgroup;topic=' . $topic . (!empty($_POST['move']) && allowedTo('move_any') ? ';move' : '') . (empty($_REQUEST['goback']) ? '' : ';goback')); + + if (!empty($_POST['move']) && allowedTo('move_any')) + redirectexit('action=movetopic;topic=' . $topic . '.0' . (empty($_REQUEST['goback']) ? '' : ';goback')); + + // Return to post if the mod is on. + if (isset($_REQUEST['msg']) && !empty($_REQUEST['goback'])) + redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'], $context['browser']['is_ie']); + elseif (!empty($_REQUEST['goback'])) + redirectexit('topic=' . $topic . '.new#new', $context['browser']['is_ie']); + // Dut-dut-duh-duh-DUH-duh-dut-duh-duh! *dances to the Final Fantasy Fanfare...* + else + redirectexit('board=' . $board . '.0'); +} + +// General function for topic announcements. +function AnnounceTopic() +{ + global $context, $txt, $topic; + + isAllowedTo('announce_topic'); + + validateSession(); + + if (empty($topic)) + fatal_lang_error('topic_gone', false); + + loadLanguage('Post'); + loadTemplate('Post'); + + $subActions = array( + 'selectgroup' => 'AnnouncementSelectMembergroup', + 'send' => 'AnnouncementSend', + ); + + $context['page_title'] = $txt['announce_topic']; + + // Call the function based on the sub-action. + $subActions[isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'selectgroup'](); +} + +// Allow a user to chose the membergroups to send the announcement to. +function AnnouncementSelectMembergroup() +{ + global $txt, $context, $topic, $board, $board_info, $smcFunc; + + $groups = array_merge($board_info['groups'], array(1)); + foreach ($groups as $id => $group) + $groups[$id] = (int) $group; + + $context['groups'] = array(); + if (in_array(0, $groups)) + { + $context['groups'][0] = array( + 'id' => 0, + 'name' => $txt['announce_regular_members'], + 'member_count' => 'n/a', + ); + } + + // Get all membergroups that have access to the board the announcement was made on. + $request = $smcFunc['db_query']('', ' + SELECT mg.id_group, COUNT(mem.id_member) AS num_members + FROM {db_prefix}membergroups AS mg + LEFT JOIN {db_prefix}members AS mem ON (mem.id_group = mg.id_group OR FIND_IN_SET(mg.id_group, mem.additional_groups) != 0 OR mg.id_group = mem.id_post_group) + WHERE mg.id_group IN ({array_int:group_list}) + GROUP BY mg.id_group', + array( + 'group_list' => $groups, + 'newbie_id_group' => 4, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $context['groups'][$row['id_group']] = array( + 'id' => $row['id_group'], + 'name' => '', + 'member_count' => $row['num_members'], + ); + } + $smcFunc['db_free_result']($request); + + // Now get the membergroup names. + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_name + FROM {db_prefix}membergroups + WHERE id_group IN ({array_int:group_list})', + array( + 'group_list' => $groups, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['groups'][$row['id_group']]['name'] = $row['group_name']; + $smcFunc['db_free_result']($request); + + // Get the subject of the topic we're about to announce. + $request = $smcFunc['db_query']('', ' + SELECT m.subject + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + WHERE t.id_topic = {int:current_topic}', + array( + 'current_topic' => $topic, + ) + ); + list ($context['topic_subject']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + censorText($context['announce_topic']['subject']); + + $context['move'] = isset($_REQUEST['move']) ? 1 : 0; + $context['go_back'] = isset($_REQUEST['goback']) ? 1 : 0; + + $context['sub_template'] = 'announce'; +} + +// Send the announcement in chunks. +function AnnouncementSend() +{ + global $topic, $board, $board_info, $context, $modSettings; + global $language, $scripturl, $txt, $user_info, $sourcedir, $smcFunc; + + checkSession(); + + // !!! Might need an interface? + $chunkSize = empty($modSettings['mail_queue']) ? 50 : 500; + + $context['start'] = empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start']; + $groups = array_merge($board_info['groups'], array(1)); + + if (isset($_POST['membergroups'])) + $_POST['who'] = explode(',', $_POST['membergroups']); + + // Check whether at least one membergroup was selected. + if (empty($_POST['who'])) + fatal_lang_error('no_membergroup_selected'); + + // Make sure all membergroups are integers and can access the board of the announcement. + foreach ($_POST['who'] as $id => $mg) + $_POST['who'][$id] = in_array((int) $mg, $groups) ? (int) $mg : 0; + + // Get the topic subject and censor it. + $request = $smcFunc['db_query']('', ' + SELECT m.id_msg, m.subject, m.body + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + WHERE t.id_topic = {int:current_topic}', + array( + 'current_topic' => $topic, + ) + ); + list ($id_msg, $context['topic_subject'], $message) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + censorText($context['topic_subject']); + censorText($message); + + $message = trim(un_htmlspecialchars(strip_tags(strtr(parse_bbc($message, false, $id_msg), array('
' => "\n", '' => "\n", '' => "\n", '[' => '[', ']' => ']'))))); + + // We need this in order to be able send emails. + require_once($sourcedir . '/Subs-Post.php'); + + // Select the email addresses for this batch. + $request = $smcFunc['db_query']('', ' + SELECT mem.id_member, mem.email_address, mem.lngfile + FROM {db_prefix}members AS mem + WHERE mem.id_member != {int:current_member}' . (!empty($modSettings['allow_disableAnnounce']) ? ' + AND mem.notify_announcements = {int:notify_announcements}' : '') . ' + AND mem.is_activated = {int:is_activated} + AND (mem.id_group IN ({array_int:group_list}) OR mem.id_post_group IN ({array_int:group_list}) OR FIND_IN_SET({raw:additional_group_list}, mem.additional_groups) != 0) + AND mem.id_member > {int:start} + ORDER BY mem.id_member + LIMIT ' . $chunkSize, + array( + 'current_member' => $user_info['id'], + 'group_list' => $_POST['who'], + 'notify_announcements' => 1, + 'is_activated' => 1, + 'start' => $context['start'], + 'additional_group_list' => implode(', mem.additional_groups) != 0 OR FIND_IN_SET(', $_POST['who']), + ) + ); + + // All members have received a mail. Go to the next screen. + if ($smcFunc['db_num_rows']($request) == 0) + { + if (!empty($_REQUEST['move']) && allowedTo('move_any')) + redirectexit('action=movetopic;topic=' . $topic . '.0' . (empty($_REQUEST['goback']) ? '' : ';goback')); + elseif (!empty($_REQUEST['goback'])) + redirectexit('topic=' . $topic . '.new;boardseen#new', $context['browser']['is_ie']); + else + redirectexit('board=' . $board . '.0'); + } + + // Loop through all members that'll receive an announcement in this batch. + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $cur_language = empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']; + + // If the language wasn't defined yet, load it and compose a notification message. + if (!isset($announcements[$cur_language])) + { + $replacements = array( + 'TOPICSUBJECT' => $context['topic_subject'], + 'MESSAGE' => $message, + 'TOPICLINK' => $scripturl . '?topic=' . $topic . '.0', + ); + + $emaildata = loadEmailTemplate('new_announcement', $replacements, $cur_language); + + $announcements[$cur_language] = array( + 'subject' => $emaildata['subject'], + 'body' => $emaildata['body'], + 'recipients' => array(), + ); + } + + $announcements[$cur_language]['recipients'][$row['id_member']] = $row['email_address']; + $context['start'] = $row['id_member']; + } + $smcFunc['db_free_result']($request); + + // For each language send a different mail - low priority... + foreach ($announcements as $lang => $mail) + sendmail($mail['recipients'], $mail['subject'], $mail['body'], null, null, false, 5); + + $context['percentage_done'] = round(100 * $context['start'] / $modSettings['latestMember'], 1); + + $context['move'] = empty($_REQUEST['move']) ? 0 : 1; + $context['go_back'] = empty($_REQUEST['goback']) ? 0 : 1; + $context['membergroups'] = implode(',', $_POST['who']); + $context['sub_template'] = 'announcement_send'; + + // Go back to the correct language for the user ;). + if (!empty($modSettings['userLanguage'])) + loadLanguage('Post'); +} + +// Notify members of a new post. +function notifyMembersBoard(&$topicData) +{ + global $txt, $scripturl, $language, $user_info; + global $modSettings, $sourcedir, $board, $smcFunc, $context; + + require_once($sourcedir . '/Subs-Post.php'); + + // Do we have one or lots of topics? + if (isset($topicData['body'])) + $topicData = array($topicData); + + // Find out what boards we have... and clear out any rubbish! + $boards = array(); + foreach ($topicData as $key => $topic) + { + if (!empty($topic['board'])) + $boards[$topic['board']][] = $key; + else + { + unset($topic[$key]); + continue; + } + + // Censor the subject and body... + censorText($topicData[$key]['subject']); + censorText($topicData[$key]['body']); + + $topicData[$key]['subject'] = un_htmlspecialchars($topicData[$key]['subject']); + $topicData[$key]['body'] = trim(un_htmlspecialchars(strip_tags(strtr(parse_bbc($topicData[$key]['body'], false), array('
' => "\n", '' => "\n", '' => "\n", '[' => '[', ']' => ']'))))); + } + + // Just the board numbers. + $board_index = array_unique(array_keys($boards)); + + if (empty($board_index)) + return; + + // Yea, we need to add this to the digest queue. + $digest_insert = array(); + foreach ($topicData as $id => $data) + $digest_insert[] = array($data['topic'], $data['msg'], 'topic', $user_info['id']); + $smcFunc['db_insert']('', + '{db_prefix}log_digest', + array( + 'id_topic' => 'int', 'id_msg' => 'int', 'note_type' => 'string', 'exclude' => 'int', + ), + $digest_insert, + array() + ); + + // Find the members with notification on for these boards. + $members = $smcFunc['db_query']('', ' + SELECT + mem.id_member, mem.email_address, mem.notify_regularity, mem.notify_send_body, mem.lngfile, + ln.sent, ln.id_board, mem.id_group, mem.additional_groups, b.member_groups, + mem.id_post_group + FROM {db_prefix}log_notify AS ln + INNER JOIN {db_prefix}boards AS b ON (b.id_board = ln.id_board) + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member) + WHERE ln.id_board IN ({array_int:board_list}) + AND mem.id_member != {int:current_member} + AND mem.is_activated = {int:is_activated} + AND mem.notify_types != {int:notify_types} + AND mem.notify_regularity < {int:notify_regularity} + ORDER BY mem.lngfile', + array( + 'current_member' => $user_info['id'], + 'board_list' => $board_index, + 'is_activated' => 1, + 'notify_types' => 4, + 'notify_regularity' => 2, + ) + ); + while ($rowmember = $smcFunc['db_fetch_assoc']($members)) + { + if ($rowmember['id_group'] != 1) + { + $allowed = explode(',', $rowmember['member_groups']); + $rowmember['additional_groups'] = explode(',', $rowmember['additional_groups']); + $rowmember['additional_groups'][] = $rowmember['id_group']; + $rowmember['additional_groups'][] = $rowmember['id_post_group']; + + if (count(array_intersect($allowed, $rowmember['additional_groups'])) == 0) + continue; + } + + $langloaded = loadLanguage('EmailTemplates', empty($rowmember['lngfile']) || empty($modSettings['userLanguage']) ? $language : $rowmember['lngfile'], false); + + // Now loop through all the notifications to send for this board. + if (empty($boards[$rowmember['id_board']])) + continue; + + $sentOnceAlready = 0; + foreach ($boards[$rowmember['id_board']] as $key) + { + // Don't notify the guy who started the topic! + //!!! In this case actually send them a "it's approved hooray" email + if ($topicData[$key]['poster'] == $rowmember['id_member']) + continue; + + // Setup the string for adding the body to the message, if a user wants it. + $send_body = empty($modSettings['disallow_sendBody']) && !empty($rowmember['notify_send_body']); + + $replacements = array( + 'TOPICSUBJECT' => $topicData[$key]['subject'], + 'TOPICLINK' => $scripturl . '?topic=' . $topicData[$key]['topic'] . '.new#new', + 'MESSAGE' => $topicData[$key]['body'], + 'UNSUBSCRIBELINK' => $scripturl . '?action=notifyboard;board=' . $topicData[$key]['board'] . '.0', + ); + + if (!$send_body) + unset($replacements['MESSAGE']); + + // Figure out which email to send off + $emailtype = ''; + + // Send only if once is off or it's on and it hasn't been sent. + if (!empty($rowmember['notify_regularity']) && !$sentOnceAlready && empty($rowmember['sent'])) + $emailtype = 'notify_boards_once'; + elseif (empty($rowmember['notify_regularity'])) + $emailtype = 'notify_boards'; + + if (!empty($emailtype)) + { + $emailtype .= $send_body ? '_body' : ''; + $emaildata = loadEmailTemplate($emailtype, $replacements, $langloaded); + sendmail($rowmember['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 3); + } + + $sentOnceAlready = 1; + } + } + $smcFunc['db_free_result']($members); + + // Sent! + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_notify + SET sent = {int:is_sent} + WHERE id_board IN ({array_int:board_list}) + AND id_member != {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'board_list' => $board_index, + 'is_sent' => 1, + ) + ); +} + +// Get the topic for display purposes. +function getTopic() +{ + global $topic, $modSettings, $context, $smcFunc, $counter, $options; + + if (isset($_REQUEST['xml'])) + $limit = ' + LIMIT ' . (empty($context['new_replies']) ? '0' : $context['new_replies']); + else + $limit = empty($modSettings['topicSummaryPosts']) ? '' : ' + LIMIT ' . (int) $modSettings['topicSummaryPosts']; + + // If you're modifying, get only those posts before the current one. (otherwise get all.) + $request = $smcFunc['db_query']('', ' + SELECT + IFNULL(mem.real_name, m.poster_name) AS poster_name, m.poster_time, + m.body, m.smileys_enabled, m.id_msg, m.id_member + FROM {db_prefix}messages AS m + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE m.id_topic = {int:current_topic}' . (isset($_REQUEST['msg']) ? ' + AND m.id_msg < {int:id_msg}' : '') .(!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : ' + AND m.approved = {int:approved}') . ' + ORDER BY m.id_msg DESC' . $limit, + array( + 'current_topic' => $topic, + 'id_msg' => isset($_REQUEST['msg']) ? (int) $_REQUEST['msg'] : 0, + 'approved' => 1, + ) + ); + $context['previous_posts'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Censor, BBC, ... + censorText($row['body']); + $row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']); + + // ...and store. + $context['previous_posts'][] = array( + 'counter' => $counter++, + 'alternate' => $counter % 2, + 'poster' => $row['poster_name'], + 'message' => $row['body'], + 'time' => timeformat($row['poster_time']), + 'timestamp' => forum_time(true, $row['poster_time']), + 'id' => $row['id_msg'], + 'is_new' => !empty($context['new_replies']), + 'is_ignored' => !empty($modSettings['enable_buddylist']) && !empty($options['posts_apply_ignore_list']) && in_array($row['id_member'], $context['user']['ignoreusers']), + ); + + if (!empty($context['new_replies'])) + $context['new_replies']--; + } + $smcFunc['db_free_result']($request); +} + +function QuoteFast() +{ + global $modSettings, $user_info, $txt, $settings, $context; + global $sourcedir, $smcFunc; + + loadLanguage('Post'); + if (!isset($_REQUEST['xml'])) + loadTemplate('Post'); + + include_once($sourcedir . '/Subs-Post.php'); + + $moderate_boards = boardsAllowedTo('moderate_board'); + + // Where we going if we need to? + $context['post_box_name'] = isset($_GET['pb']) ? $_GET['pb'] : ''; + + $request = $smcFunc['db_query']('', ' + SELECT IFNULL(mem.real_name, m.poster_name) AS poster_name, m.poster_time, m.body, m.id_topic, m.subject, + m.id_board, m.id_member, m.approved + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board}) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE m.id_msg = {int:id_msg}' . (isset($_REQUEST['modify']) || (!empty($moderate_boards) && $moderate_boards[0] == 0) ? '' : ' + AND (t.locked = {int:not_locked}' . (empty($moderate_boards) ? '' : ' OR b.id_board IN ({array_int:moderation_board_list})') . ')') . ' + LIMIT 1', + array( + 'current_member' => $user_info['id'], + 'moderation_board_list' => $moderate_boards, + 'id_msg' => (int) $_REQUEST['quote'], + 'not_locked' => 0, + ) + ); + $context['close_window'] = $smcFunc['db_num_rows']($request) == 0; + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + $context['sub_template'] = 'quotefast'; + if (!empty($row)) + $can_view_post = $row['approved'] || ($row['id_member'] != 0 && $row['id_member'] == $user_info['id']) || allowedTo('approve_posts', $row['id_board']); + + if (!empty($can_view_post)) + { + // Remove special formatting we don't want anymore. + $row['body'] = un_preparsecode($row['body']); + + // Censor the message! + censorText($row['body']); + + $row['body'] = preg_replace('~
~i', "\n", $row['body']); + + // Want to modify a single message by double clicking it? + if (isset($_REQUEST['modify'])) + { + censorText($row['subject']); + + $context['sub_template'] = 'modifyfast'; + $context['message'] = array( + 'id' => $_REQUEST['quote'], + 'body' => $row['body'], + 'subject' => addcslashes($row['subject'], '"'), + ); + + return; + } + + // Remove any nested quotes. + if (!empty($modSettings['removeNestedQuotes'])) + $row['body'] = preg_replace(array('~\n?\[quote.*?\].+?\[/quote\]\n?~is', '~^\n~', '~\[/quote\]~'), '', $row['body']); + + // Make the body HTML if need be. + if (!empty($_REQUEST['mode'])) + { + require_once($sourcedir . '/Subs-Editor.php'); + $row['body'] = strtr($row['body'], array('<' => '#smlt#', '>' => '#smgt#', '&' => '#smamp#')); + $row['body'] = bbc_to_html($row['body']); + $lb = '
'; + } + else + $lb = "\n"; + + // Add a quote string on the front and end. + $context['quote']['xml'] = '[quote author=' . $row['poster_name'] . ' link=topic=' . $row['id_topic'] . '.msg' . (int) $_REQUEST['quote'] . '#msg' . (int) $_REQUEST['quote'] . ' date=' . $row['poster_time'] . ']' . $lb . $row['body'] . $lb . '[/quote]'; + $context['quote']['text'] = strtr(un_htmlspecialchars($context['quote']['xml']), array('\'' => '\\\'', '\\' => '\\\\', "\n" => '\\n', '' => '')); + $context['quote']['xml'] = strtr($context['quote']['xml'], array(' ' => ' ', '<' => '<', '>' => '>')); + + $context['quote']['mozilla'] = strtr($smcFunc['htmlspecialchars']($context['quote']['text']), array('"' => '"')); + } + // !!! Needs a nicer interface. + // In case our message has been removed in the meantime. + elseif (isset($_REQUEST['modify'])) + { + $context['sub_template'] = 'modifyfast'; + $context['message'] = array( + 'id' => 0, + 'body' => '', + 'subject' => '', + ); + } + else + $context['quote'] = array( + 'xml' => '', + 'mozilla' => '', + 'text' => '', + ); +} + +function JavaScriptModify() +{ + global $sourcedir, $modSettings, $board, $topic, $txt; + global $user_info, $context, $smcFunc, $language; + + // We have to have a topic! + if (empty($topic)) + obExit(false); + + checkSession('get'); + require_once($sourcedir . '/Subs-Post.php'); + + // Assume the first message if no message ID was given. + $request = $smcFunc['db_query']('', ' + SELECT + t.locked, t.num_replies, t.id_member_started, t.id_first_msg, + m.id_msg, m.id_member, m.poster_time, m.subject, m.smileys_enabled, m.body, m.icon, + m.modified_time, m.modified_name, m.approved + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic}) + WHERE m.id_msg = {raw:id_msg} + AND m.id_topic = {int:current_topic}' . (allowedTo('approve_posts') ? '' : (!$modSettings['postmod_active'] ? ' + AND (m.id_member != {int:guest_id} AND m.id_member = {int:current_member})' : ' + AND (m.approved = {int:is_approved} OR (m.id_member != {int:guest_id} AND m.id_member = {int:current_member}))')), + array( + 'current_member' => $user_info['id'], + 'current_topic' => $topic, + 'id_msg' => empty($_REQUEST['msg']) ? 't.id_first_msg' : (int) $_REQUEST['msg'], + 'is_approved' => 1, + 'guest_id' => 0, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_board', false); + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Change either body or subject requires permissions to modify messages. + if (isset($_POST['message']) || isset($_POST['subject']) || isset($_REQUEST['icon'])) + { + if (!empty($row['locked'])) + isAllowedTo('moderate_board'); + + if ($row['id_member'] == $user_info['id'] && !allowedTo('modify_any')) + { + if ((!$modSettings['postmod_active'] || $row['approved']) && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + ($modSettings['edit_disable_time'] + 5) * 60 < time()) + fatal_lang_error('modify_post_time_passed', false); + elseif ($row['id_member_started'] == $user_info['id'] && !allowedTo('modify_own')) + isAllowedTo('modify_replies'); + else + isAllowedTo('modify_own'); + } + // Otherwise, they're locked out; someone who can modify the replies is needed. + elseif ($row['id_member_started'] == $user_info['id'] && !allowedTo('modify_any')) + isAllowedTo('modify_replies'); + else + isAllowedTo('modify_any'); + + // Only log this action if it wasn't your message. + $moderationAction = $row['id_member'] != $user_info['id']; + } + + $post_errors = array(); + if (isset($_POST['subject']) && $smcFunc['htmltrim']($smcFunc['htmlspecialchars']($_POST['subject'])) !== '') + { + $_POST['subject'] = strtr($smcFunc['htmlspecialchars']($_POST['subject']), array("\r" => '', "\n" => '', "\t" => '')); + + // Maximum number of characters. + if ($smcFunc['strlen']($_POST['subject']) > 100) + $_POST['subject'] = $smcFunc['substr']($_POST['subject'], 0, 100); + } + elseif (isset($_POST['subject'])) + { + $post_errors[] = 'no_subject'; + unset($_POST['subject']); + } + + if (isset($_POST['message'])) + { + if ($smcFunc['htmltrim']($smcFunc['htmlspecialchars']($_POST['message'])) === '') + { + $post_errors[] = 'no_message'; + unset($_POST['message']); + } + elseif (!empty($modSettings['max_messageLength']) && $smcFunc['strlen']($_POST['message']) > $modSettings['max_messageLength']) + { + $post_errors[] = 'long_message'; + unset($_POST['message']); + } + else + { + $_POST['message'] = $smcFunc['htmlspecialchars']($_POST['message'], ENT_QUOTES); + + preparsecode($_POST['message']); + + if ($smcFunc['htmltrim'](strip_tags(parse_bbc($_POST['message'], false), '')) === '') + { + $post_errors[] = 'no_message'; + unset($_POST['message']); + } + } + } + + if (isset($_POST['lock'])) + { + if (!allowedTo(array('lock_any', 'lock_own')) || (!allowedTo('lock_any') && $user_info['id'] != $row['id_member'])) + unset($_POST['lock']); + elseif (!allowedTo('lock_any')) + { + if ($row['locked'] == 1) + unset($_POST['lock']); + else + $_POST['lock'] = empty($_POST['lock']) ? 0 : 2; + } + elseif (!empty($row['locked']) && !empty($_POST['lock']) || $_POST['lock'] == $row['locked']) + unset($_POST['lock']); + else + $_POST['lock'] = empty($_POST['lock']) ? 0 : 1; + } + + if (isset($_POST['sticky']) && !allowedTo('make_sticky')) + unset($_POST['sticky']); + + if (empty($post_errors)) + { + $msgOptions = array( + 'id' => $row['id_msg'], + 'subject' => isset($_POST['subject']) ? $_POST['subject'] : null, + 'body' => isset($_POST['message']) ? $_POST['message'] : null, + 'icon' => isset($_REQUEST['icon']) ? preg_replace('~[\./\\\\*\':"<>]~', '', $_REQUEST['icon']) : null, + ); + $topicOptions = array( + 'id' => $topic, + 'board' => $board, + 'lock_mode' => isset($_POST['lock']) ? (int) $_POST['lock'] : null, + 'sticky_mode' => isset($_POST['sticky']) && !empty($modSettings['enableStickyTopics']) ? (int) $_POST['sticky'] : null, + 'mark_as_read' => true, + ); + $posterOptions = array(); + + // Only consider marking as editing if they have edited the subject, message or icon. + if ((isset($_POST['subject']) && $_POST['subject'] != $row['subject']) || (isset($_POST['message']) && $_POST['message'] != $row['body']) || (isset($_REQUEST['icon']) && $_REQUEST['icon'] != $row['icon'])) + { + // And even then only if the time has passed... + if (time() - $row['poster_time'] > $modSettings['edit_wait_time'] || $user_info['id'] != $row['id_member']) + { + $msgOptions['modify_time'] = time(); + $msgOptions['modify_name'] = $user_info['name']; + } + } + // If nothing was changed there's no need to add an entry to the moderation log. + else + $moderationAction = false; + + modifyPost($msgOptions, $topicOptions, $posterOptions); + + // If we didn't change anything this time but had before put back the old info. + if (!isset($msgOptions['modify_time']) && !empty($row['modified_time'])) + { + $msgOptions['modify_time'] = $row['modified_time']; + $msgOptions['modify_name'] = $row['modified_name']; + } + + // Changing the first subject updates other subjects to 'Re: new_subject'. + if (isset($_POST['subject']) && isset($_REQUEST['change_all_subjects']) && $row['id_first_msg'] == $row['id_msg'] && !empty($row['num_replies']) && (allowedTo('modify_any') || ($row['id_member_started'] == $user_info['id'] && allowedTo('modify_replies')))) + { + // Get the proper (default language) response prefix first. + if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix'))) + { + if ($language === $user_info['language']) + $context['response_prefix'] = $txt['response_prefix']; + else + { + loadLanguage('index', $language, false); + $context['response_prefix'] = $txt['response_prefix']; + loadLanguage('index'); + } + cache_put_data('response_prefix', $context['response_prefix'], 600); + } + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET subject = {string:subject} + WHERE id_topic = {int:current_topic} + AND id_msg != {int:id_first_msg}', + array( + 'current_topic' => $topic, + 'id_first_msg' => $row['id_first_msg'], + 'subject' => $context['response_prefix'] . $_POST['subject'], + ) + ); + } + + if (!empty($moderationAction)) + logAction('modify', array('topic' => $topic, 'message' => $row['id_msg'], 'member' => $row['id_member'], 'board' => $board)); + } + + if (isset($_REQUEST['xml'])) + { + $context['sub_template'] = 'modifydone'; + if (empty($post_errors) && isset($msgOptions['subject']) && isset($msgOptions['body'])) + { + $context['message'] = array( + 'id' => $row['id_msg'], + 'modified' => array( + 'time' => isset($msgOptions['modify_time']) ? timeformat($msgOptions['modify_time']) : '', + 'timestamp' => isset($msgOptions['modify_time']) ? forum_time(true, $msgOptions['modify_time']) : 0, + 'name' => isset($msgOptions['modify_time']) ? $msgOptions['modify_name'] : '', + ), + 'subject' => $msgOptions['subject'], + 'first_in_topic' => $row['id_msg'] == $row['id_first_msg'], + 'body' => strtr($msgOptions['body'], array(']]>' => ']]]]>')), + ); + + censorText($context['message']['subject']); + censorText($context['message']['body']); + + $context['message']['body'] = parse_bbc($context['message']['body'], $row['smileys_enabled'], $row['id_msg']); + } + // Topic? + elseif (empty($post_errors)) + { + $context['sub_template'] = 'modifytopicdone'; + $context['message'] = array( + 'id' => $row['id_msg'], + 'modified' => array( + 'time' => isset($msgOptions['modify_time']) ? timeformat($msgOptions['modify_time']) : '', + 'timestamp' => isset($msgOptions['modify_time']) ? forum_time(true, $msgOptions['modify_time']) : 0, + 'name' => isset($msgOptions['modify_time']) ? $msgOptions['modify_name'] : '', + ), + 'subject' => isset($msgOptions['subject']) ? $msgOptions['subject'] : '', + ); + + censorText($context['message']['subject']); + } + else + { + $context['message'] = array( + 'id' => $row['id_msg'], + 'errors' => array(), + 'error_in_subject' => in_array('no_subject', $post_errors), + 'error_in_body' => in_array('no_message', $post_errors) || in_array('long_message', $post_errors), + ); + + loadLanguage('Errors'); + foreach ($post_errors as $post_error) + { + if ($post_error == 'long_message') + $context['message']['errors'][] = sprintf($txt['error_' . $post_error], $modSettings['max_messageLength']); + else + $context['message']['errors'][] = $txt['error_' . $post_error]; + } + } + } + else + obExit(false); +} + +?> \ No newline at end of file diff --git a/Sources/PostModeration.php b/Sources/PostModeration.php new file mode 100644 index 0000000..0fd9a3c --- /dev/null +++ b/Sources/PostModeration.php @@ -0,0 +1,601 @@ + 'ApproveMessage', + 'attachments' => 'UnapprovedAttachments', + 'replies' => 'UnapprovedPosts', + 'topics' => 'UnapprovedPosts', + ); + + // Pick something valid... + if (!isset($_REQUEST['sa']) || !isset($subactions[$_REQUEST['sa']])) + $_REQUEST['sa'] = 'replies'; + + $subactions[$_REQUEST['sa']](); +} + +// View all unapproved posts. +function UnapprovedPosts() +{ + global $txt, $scripturl, $context, $user_info, $sourcedir, $smcFunc; + + $context['current_view'] = isset($_GET['sa']) && $_GET['sa'] == 'topics' ? 'topics' : 'replies'; + $context['page_title'] = $txt['mc_unapproved_posts']; + + // Work out what boards we can work in! + $approve_boards = boardsAllowedTo('approve_posts'); + + // If we filtered by board remove ones outside of this board. + //!!! Put a message saying we're filtered? + if (isset($_REQUEST['brd'])) + { + $filter_board = array((int) $_REQUEST['brd']); + $approve_boards = $approve_boards == array(0) ? $filter_board : array_intersect($approve_boards, $filter_board); + } + + if ($approve_boards == array(0)) + $approve_query = ''; + elseif (!empty($approve_boards)) + $approve_query = ' AND m.id_board IN (' . implode(',', $approve_boards) . ')'; + // Nada, zip, etc... + else + $approve_query = ' AND 0'; + + // We also need to know where we can delete topics and/or replies to. + if ($context['current_view'] == 'topics') + { + $delete_own_boards = boardsAllowedTo('remove_own'); + $delete_any_boards = boardsAllowedTo('remove_any'); + $delete_own_replies = array(); + } + else + { + $delete_own_boards = boardsAllowedTo('delete_own'); + $delete_any_boards = boardsAllowedTo('delete_any'); + $delete_own_replies = boardsAllowedTo('delete_own_replies'); + } + + $toAction = array(); + // Check if we have something to do? + if (isset($_GET['approve'])) + $toAction[] = (int) $_GET['approve']; + // Just a deletion? + elseif (isset($_GET['delete'])) + $toAction[] = (int) $_GET['delete']; + // Lots of approvals? + elseif (isset($_POST['item'])) + foreach ($_POST['item'] as $item) + $toAction[] = (int) $item; + + // What are we actually doing. + if (isset($_GET['approve']) || (isset($_POST['do']) && $_POST['do'] == 'approve')) + $curAction = 'approve'; + elseif (isset($_GET['delete']) || (isset($_POST['do']) && $_POST['do'] == 'delete')) + $curAction = 'delete'; + + // Right, so we have something to do? + if (!empty($toAction) && isset($curAction)) + { + checkSession('request'); + + // Handy shortcut. + $any_array = $curAction == 'approve' ? $approve_boards : $delete_any_boards; + + // Now for each message work out whether it's actually a topic, and what board it's on. + $request = $smcFunc['db_query']('', ' + SELECT m.id_msg, m.id_member, m.id_board, m.subject, t.id_topic, t.id_first_msg, t.id_member_started + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + LEFT JOIN {db_prefix}boards AS b ON (t.id_board = b.id_board) + WHERE m.id_msg IN ({array_int:message_list}) + AND m.approved = {int:not_approved} + AND {query_see_board}', + array( + 'message_list' => $toAction, + 'not_approved' => 0, + ) + ); + $toAction = array(); + $details = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // If it's not within what our view is ignore it... + if (($row['id_msg'] == $row['id_first_msg'] && $context['current_view'] != 'topics') || ($row['id_msg'] != $row['id_first_msg'] && $context['current_view'] != 'replies')) + continue; + + $can_add = false; + // If we're approving this is simple. + if ($curAction == 'approve' && ($any_array == array(0) || in_array($row['id_board'], $any_array))) + { + $can_add = true; + } + // Delete requires more permission checks... + elseif ($curAction == 'delete') + { + // Own post is easy! + if ($row['id_member'] == $user_info['id'] && ($delete_own_boards == array(0) || in_array($row['id_board'], $delete_own_boards))) + $can_add = true; + // Is it a reply to their own topic? + elseif ($row['id_member'] == $row['id_member_started'] && $row['id_msg'] != $row['id_first_msg'] && ($delete_own_replies == array(0) || in_array($row['id_board'], $delete_own_replies))) + $can_add = true; + // Someone elses? + elseif ($row['id_member'] != $user_info['id'] && ($delete_any_boards == array(0) || in_array($row['id_board'], $delete_any_boards))) + $can_add = true; + } + + if ($can_add) + $anItem = $context['current_view'] == 'topics' ? $row['id_topic'] : $row['id_msg']; + $toAction[] = $anItem; + + // All clear. What have we got now, what, what? + $details[$anItem] = array(); + $details[$anItem]["subject"] = $row['subject']; + $details[$anItem]["topic"] = $row['id_topic']; + $details[$anItem]["member"] = ($context['current_view'] == 'topics') ? $row['id_member_started'] : $row['id_member']; + $details[$anItem]["board"] = $row['id_board']; + } + $smcFunc['db_free_result']($request); + + // If we have anything left we can actually do the approving (etc). + if (!empty($toAction)) + { + if ($curAction == 'approve') + { + approveMessages ($toAction, $details, $context['current_view']); + } + else + { + removeMessages ($toAction, $details, $context['current_view']); + } + } + } + + // How many unapproved posts are there? + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic AND t.id_first_msg != m.id_msg) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + WHERE m.approved = {int:not_approved} + AND {query_see_board} + ' . $approve_query, + array( + 'not_approved' => 0, + ) + ); + list ($context['total_unapproved_posts']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // What about topics? Normally we'd use the table alias t for topics but lets use m so we don't have to redo our approve query. + $request = $smcFunc['db_query']('', ' + SELECT COUNT(m.id_topic) + FROM {db_prefix}topics AS m + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + WHERE m.approved = {int:not_approved} + AND {query_see_board} + ' . $approve_query, + array( + 'not_approved' => 0, + ) + ); + list ($context['total_unapproved_topics']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $context['page_index'] = constructPageIndex($scripturl . '?action=moderate;area=postmod;sa=' . $context['current_view'] . (isset($_REQUEST['brd']) ? ';brd=' . (int) $_REQUEST['brd'] : ''), $_GET['start'], $context['current_view'] == 'topics' ? $context['total_unapproved_topics'] : $context['total_unapproved_posts'], 10); + $context['start'] = $_GET['start']; + + // We have enough to make some pretty tabs! + $context[$context['moderation_menu_name']]['tab_data'] = array( + 'title' => $txt['mc_unapproved_posts'], + 'help' => 'postmod', + 'description' => $txt['mc_unapproved_posts_desc'], + ); + + // Update the tabs with the correct number of posts. + $context['menu_data_' . $context['moderation_menu_id']]['sections']['posts']['areas']['postmod']['subsections']['posts']['label'] .= ' (' . $context['total_unapproved_posts'] . ')'; + $context['menu_data_' . $context['moderation_menu_id']]['sections']['posts']['areas']['postmod']['subsections']['topics']['label'] .= ' (' . $context['total_unapproved_topics'] . ')'; + + // If we are filtering some boards out then make sure to send that along with the links. + if (isset($_REQUEST['brd'])) + { + $context['menu_data_' . $context['moderation_menu_id']]['sections']['posts']['areas']['postmod']['subsections']['posts']['add_params'] = ';brd=' . (int) $_REQUEST['brd']; + $context['menu_data_' . $context['moderation_menu_id']]['sections']['posts']['areas']['postmod']['subsections']['topics']['add_params'] = ';brd=' . (int) $_REQUEST['brd']; + } + + // Get all unapproved posts. + $request = $smcFunc['db_query']('', ' + SELECT m.id_msg, m.id_topic, m.id_board, m.subject, m.body, m.id_member, + IFNULL(mem.real_name, m.poster_name) AS poster_name, m.poster_time, m.smileys_enabled, + t.id_member_started, t.id_first_msg, b.name AS board_name, c.id_cat, c.name AS cat_name + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat) + WHERE m.approved = {int:not_approved} + AND t.id_first_msg ' . ($context['current_view'] == 'topics' ? '=' : '!=') . ' m.id_msg + AND {query_see_board} + ' . $approve_query . ' + LIMIT ' . $context['start'] . ', 10', + array( + 'not_approved' => 0, + ) + ); + $context['unapproved_items'] = array(); + for ($i = 1; $row = $smcFunc['db_fetch_assoc']($request); $i++) + { + // Can delete is complicated, let's solve it first... is it their own post? + if ($row['id_member'] == $user_info['id'] && ($delete_own_boards == array(0) || in_array($row['id_board'], $delete_own_boards))) + $can_delete = true; + // Is it a reply to their own topic? + elseif ($row['id_member'] == $row['id_member_started'] && $row['id_msg'] != $row['id_first_msg'] && ($delete_own_replies == array(0) || in_array($row['id_board'], $delete_own_replies))) + $can_delete = true; + // Someone elses? + elseif ($row['id_member'] != $user_info['id'] && ($delete_any_boards == array(0) || in_array($row['id_board'], $delete_any_boards))) + $can_delete = true; + else + $can_delete = false; + + $context['unapproved_items'][] = array( + 'id' => $row['id_msg'], + 'alternate' => $i % 2, + 'counter' => $context['start'] + $i, + 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'], + 'subject' => $row['subject'], + 'body' => parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']), + 'time' => timeformat($row['poster_time']), + 'poster' => array( + 'id' => $row['id_member'], + 'name' => $row['poster_name'], + 'link' => $row['id_member'] ? '' . $row['poster_name'] . '' : $row['poster_name'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_member'], + ), + 'topic' => array( + 'id' => $row['id_topic'], + ), + 'board' => array( + 'id' => $row['id_board'], + 'name' => $row['board_name'], + ), + 'category' => array( + 'id' => $row['id_cat'], + 'name' => $row['cat_name'], + ), + 'can_delete' => $can_delete, + ); + } + $smcFunc['db_free_result']($request); + + $context['sub_template'] = 'unapproved_posts'; +} + +// View all unapproved attachments. +function UnapprovedAttachments() +{ + global $txt, $scripturl, $context, $user_info, $sourcedir, $smcFunc; + + $context['page_title'] = $txt['mc_unapproved_attachments']; + + // Once again, permissions are king! + $approve_boards = boardsAllowedTo('approve_posts'); + + if ($approve_boards == array(0)) + $approve_query = ''; + elseif (!empty($approve_boards)) + $approve_query = ' AND m.id_board IN (' . implode(',', $approve_boards) . ')'; + else + $approve_query = ' AND 0'; + + // Get together the array of things to act on, if any. + $attachments = array(); + if (isset($_GET['approve'])) + $attachments[] = (int) $_GET['approve']; + elseif (isset($_GET['delete'])) + $attachments[] = (int) $_GET['delete']; + elseif (isset($_POST['item'])) + foreach ($_POST['item'] as $item) + $attachments[] = (int) $item; + + // Are we approving or deleting? + if (isset($_GET['approve']) || (isset($_POST['do']) && $_POST['do'] == 'approve')) + $curAction = 'approve'; + elseif (isset($_GET['delete']) || (isset($_POST['do']) && $_POST['do'] == 'delete')) + $curAction = 'delete'; + + // Something to do, let's do it! + if (!empty($attachments) && isset($curAction)) + { + checkSession('request'); + + // This will be handy. + require_once($sourcedir . '/ManageAttachments.php'); + + // Confirm the attachments are eligible for changing! + $request = $smcFunc['db_query']('', ' + SELECT a.id_attach + FROM {db_prefix}attachments AS a + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg) + LEFT JOIN {db_prefix}boards AS b ON (m.id_board = b.id_board) + WHERE a.id_attach IN ({array_int:attachments}) + AND a.approved = {int:not_approved} + AND a.attachment_type = {int:attachment_type} + AND {query_see_board} + ' . $approve_query, + array( + 'attachments' => $attachments, + 'not_approved' => 0, + 'attachment_type' => 0, + ) + ); + $attachments = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $attachments[] = $row['id_attach']; + $smcFunc['db_free_result']($request); + + // Assuming it wasn't all like, proper illegal, we can do the approving. + if (!empty($attachments)) + { + if ($curAction == 'approve') + ApproveAttachments($attachments); + else + removeAttachments(array('id_attach' => $attachments)); + } + } + + // How many unapproved attachments in total? + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}attachments AS a + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + WHERE a.approved = {int:not_approved} + AND a.attachment_type = {int:attachment_type} + AND {query_see_board} + ' . $approve_query, + array( + 'not_approved' => 0, + 'attachment_type' => 0, + ) + ); + list ($context['total_unapproved_attachments']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $context['page_index'] = constructPageIndex($scripturl . '?action=moderate;area=attachmod;sa=attachments', $_GET['start'], $context['total_unapproved_attachments'], 10); + $context['start'] = $_GET['start']; + + // Get all unapproved attachments. + $request = $smcFunc['db_query']('', ' + SELECT a.id_attach, a.filename, a.size, m.id_msg, m.id_topic, m.id_board, m.subject, m.body, m.id_member, + IFNULL(mem.real_name, m.poster_name) AS poster_name, m.poster_time, + t.id_member_started, t.id_first_msg, b.name AS board_name, c.id_cat, c.name AS cat_name + FROM {db_prefix}attachments AS a + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg) + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat) + WHERE a.approved = {int:not_approved} + AND a.attachment_type = {int:attachment_type} + AND {query_see_board} + ' . $approve_query . ' + LIMIT ' . $context['start'] . ', 10', + array( + 'not_approved' => 0, + 'attachment_type' => 0, + ) + ); + $context['unapproved_items'] = array(); + for ($i = 1; $row = $smcFunc['db_fetch_assoc']($request); $i++) + { + $context['unapproved_items'][] = array( + 'id' => $row['id_attach'], + 'alternate' => $i % 2, + 'filename' => $row['filename'], + 'size' => round($row['size'] / 1024, 2), + 'time' => timeformat($row['poster_time']), + 'poster' => array( + 'id' => $row['id_member'], + 'name' => $row['poster_name'], + 'link' => $row['id_member'] ? '' . $row['poster_name'] . '' : $row['poster_name'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_member'], + ), + 'message' => array( + 'id' => $row['id_msg'], + 'subject' => $row['subject'], + 'body' => parse_bbc($row['body']), + 'time' => timeformat($row['poster_time']), + 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'], + ), + 'topic' => array( + 'id' => $row['id_topic'], + ), + 'board' => array( + 'id' => $row['id_board'], + 'name' => $row['board_name'], + ), + 'category' => array( + 'id' => $row['id_cat'], + 'name' => $row['cat_name'], + ), + ); + } + $smcFunc['db_free_result']($request); + + $context['sub_template'] = 'unapproved_attachments'; +} + +// Approve a post, just the one. +function ApproveMessage() +{ + global $user_info, $topic, $board, $sourcedir, $smcFunc; + + checkSession('get'); + + $_REQUEST['msg'] = (int) $_REQUEST['msg']; + + require_once($sourcedir . '/Subs-Post.php'); + + isAllowedTo('approve_posts'); + + $request = $smcFunc['db_query']('', ' + SELECT t.id_member_started, t.id_first_msg, m.id_member, m.subject, m.approved + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic}) + WHERE m.id_msg = {int:id_msg} + AND m.id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + 'id_msg' => $_REQUEST['msg'], + ) + ); + list ($starter, $first_msg, $poster, $subject, $approved) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // If it's the first in a topic then the whole topic gets approved! + if ($first_msg == $_REQUEST['msg']) + { + approveTopics($topic, !$approved); + + if ($starter != $user_info['id']) + logAction('approve_topic', array('topic' => $topic, 'subject' => $subject, 'member' => $starter, 'board' => $board)); + } + else + { + approvePosts($_REQUEST['msg'], !$approved); + + if ($poster != $user_info['id']) + logAction('approve', array('topic' => $topic, 'subject' => $subject, 'member' => $poster, 'board' => $board)); + } + + redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg']. '#msg' . $_REQUEST['msg']); +} + +// Approve a batch of posts (or topics in their own right) +function approveMessages($messages, $messageDetails, $current_view = 'replies') +{ + global $sourcedir; + + require_once($sourcedir . '/Subs-Post.php'); + if ($current_view == 'topics') + { + approveTopics($messages); + // and tell the world about it + foreach ($messages as $topic) + { + logAction('approve_topic', array('topic' => $topic, 'subject' => $messageDetails[$topic]['subject'], 'member' => $messageDetails[$topic]['member'], 'board' => $messageDetails[$topic]['board'])); + } + } + else + { + approvePosts($messages); + // and tell the world about it again + foreach ($messages as $post) + { + logAction('approve', array('topic' => $messageDetails[$post]['topic'], 'subject' => $messageDetails[$post]['subject'], 'member' => $messageDetails[$post]['member'], 'board' => $messageDetails[$post]['board'])); + } + } +} + +// This is a helper function - basically approve everything! +function approveAllData() +{ + global $smcFunc, $sourcedir; + + // Start with messages and topics. + $request = $smcFunc['db_query']('', ' + SELECT id_msg + FROM {db_prefix}messages + WHERE approved = {int:not_approved}', + array( + 'not_approved' => 0, + ) + ); + $msgs = array(); + while ($row = $smcFunc['db_fetch_row']($request)) + $msgs[] = $row[0]; + $smcFunc['db_free_result']($request); + + if (!empty($msgs)) + { + require_once($sourcedir . '/Subs-Post.php'); + approvePosts($msgs); + } + + // Now do attachments + $request = $smcFunc['db_query']('', ' + SELECT id_attach + FROM {db_prefix}attachments + WHERE approved = {int:not_approved}', + array( + 'not_approved' => 0, + ) + ); + $attaches = array(); + while ($row = $smcFunc['db_fetch_row']($request)) + $attaches[] = $row[0]; + $smcFunc['db_free_result']($request); + + if (!empty($attaches)) + { + require_once($sourcedir . '/ManageAttachments.php'); + ApproveAttachments($attaches); + } +} + +// remove a batch of messages (or topics) +function removeMessages($messages, $messageDetails, $current_view = 'replies') +{ + global $sourcedir, $modSettings; + require_once($sourcedir . '/RemoveTopic.php'); + if ($current_view == 'topics') + { + removeTopics($messages); + // and tell the world about it + foreach ($messages as $topic) + // Note, only log topic ID in native form if it's not gone forever. + logAction('remove', array( + (empty($modSettings['recycle_enable']) || $modSettings['recycle_board'] != $messageDetails[$topic]['board'] ? 'topic' : 'old_topic_id') => $topic, 'subject' => $messageDetails[$topic]['subject'], 'member' => $messageDetails[$topic]['member'], 'board' => $messageDetails[$topic]['board'])); + } + else + { + foreach ($messages as $post) + { + removeMessage($post); + logAction('delete', array( + (empty($modSettings['recycle_enable']) || $modSettings['recycle_board'] != $messageDetails[$post]['board'] ? 'topic' : 'old_topic_id') => $messageDetails[$post]['topic'], 'subject' => $messageDetails[$post]['subject'], 'member' => $messageDetails[$post]['member'], 'board' => $messageDetails[$post]['board'])); + } + } +} +?> \ No newline at end of file diff --git a/Sources/Printpage.php b/Sources/Printpage.php new file mode 100644 index 0000000..94dbd08 --- /dev/null +++ b/Sources/Printpage.php @@ -0,0 +1,107 @@ + $topic, + ) + ); + // Redirect to the boardindex if no valid topic id is provided. + if ($smcFunc['db_num_rows']($request) == 0) + redirectexit(); + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Lets "output" all that info. + loadTemplate('Printpage'); + $context['template_layers'] = array('print'); + $context['board_name'] = $board_info['name']; + $context['category_name'] = $board_info['cat']['name']; + $context['poster_name'] = $row['poster_name']; + $context['post_time'] = timeformat($row['poster_time'], false); + $context['parent_boards'] = array(); + foreach ($board_info['parent_boards'] as $parent) + $context['parent_boards'][] = $parent['name']; + + // Split the topics up so we can print them. + $request = $smcFunc['db_query']('', ' + SELECT subject, poster_time, body, IFNULL(mem.real_name, poster_name) AS poster_name + FROM {db_prefix}messages AS m + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE m.id_topic = {int:current_topic}' . ($modSettings['postmod_active'] && !allowedTo('approve_posts') ? ' + AND (m.approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR m.id_member = {int:current_member}') . ')' : '') . ' + ORDER BY m.id_msg', + array( + 'current_topic' => $topic, + 'is_approved' => 1, + 'current_member' => $user_info['id'], + ) + ); + $context['posts'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Censor the subject and message. + censorText($row['subject']); + censorText($row['body']); + + $context['posts'][] = array( + 'subject' => $row['subject'], + 'member' => $row['poster_name'], + 'time' => timeformat($row['poster_time'], false), + 'timestamp' => forum_time(true, $row['poster_time']), + 'body' => parse_bbc($row['body'], 'print'), + ); + + if (!isset($context['topic_subject'])) + $context['topic_subject'] = $row['subject']; + } + $smcFunc['db_free_result']($request); + + // Set a canonical URL for this page. + $context['canonical_url'] = $scripturl . '?topic=' . $topic . '.0'; +} + +?> \ No newline at end of file diff --git a/Sources/Profile-Actions.php b/Sources/Profile-Actions.php new file mode 100644 index 0000000..a4c5e05 --- /dev/null +++ b/Sources/Profile-Actions.php @@ -0,0 +1,795 @@ + $memID), 'admin'); + + // Actually update this member now, as it guarantees the unapproved count can't get corrupted. + updateMemberData($context['id_member'], array('is_activated' => $user_profile[$memID]['is_activated'] >= 10 ? 11 : 1, 'validation_code' => '')); + + // If we are doing approval, update the stats for the member just in case. + if (in_array($user_profile[$memID]['is_activated'], array(3, 4, 13, 14))) + updateSettings(array('unapprovedMembers' => ($modSettings['unapprovedMembers'] > 1 ? $modSettings['unapprovedMembers'] - 1 : 0))); + + // Make sure we update the stats too. + updateStats('member', false); + } + + // Leave it be... + redirectexit('action=profile;u=' . $memID . ';area=summary'); +} + +// Issue/manage a users warning status. +function issueWarning($memID) +{ + global $txt, $scripturl, $modSettings, $user_info, $mbname; + global $context, $cur_profile, $memberContext, $smcFunc, $sourcedir; + + // Get all the actual settings. + list ($modSettings['warning_enable'], $modSettings['user_limit']) = explode(',', $modSettings['warning_settings']); + + // This stores any legitimate errors. + $issueErrors = array(); + + // Doesn't hurt to be overly cautious. + if (empty($modSettings['warning_enable']) || ($context['user']['is_owner'] && !$cur_profile['warning']) || !allowedTo('issue_warning')) + fatal_lang_error('no_access', false); + + // Make sure things which are disabled stay disabled. + $modSettings['warning_watch'] = !empty($modSettings['warning_watch']) ? $modSettings['warning_watch'] : 110; + $modSettings['warning_moderate'] = !empty($modSettings['warning_moderate']) && !empty($modSettings['postmod_active']) ? $modSettings['warning_moderate'] : 110; + $modSettings['warning_mute'] = !empty($modSettings['warning_mute']) ? $modSettings['warning_mute'] : 110; + + $context['warning_limit'] = allowedTo('admin_forum') ? 0 : $modSettings['user_limit']; + $context['member']['warning'] = $cur_profile['warning']; + $context['member']['name'] = $cur_profile['real_name']; + + // What are the limits we can apply? + $context['min_allowed'] = 0; + $context['max_allowed'] = 100; + if ($context['warning_limit'] > 0) + { + // Make sure we cannot go outside of our limit for the day. + $request = $smcFunc['db_query']('', ' + SELECT SUM(counter) + FROM {db_prefix}log_comments + WHERE id_recipient = {int:selected_member} + AND id_member = {int:current_member} + AND comment_type = {string:warning} + AND log_time > {int:day_time_period}', + array( + 'current_member' => $user_info['id'], + 'selected_member' => $memID, + 'day_time_period' => time() - 86400, + 'warning' => 'warning', + ) + ); + list ($current_applied) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $context['min_allowed'] = max(0, $cur_profile['warning'] - $current_applied - $context['warning_limit']); + $context['max_allowed'] = min(100, $cur_profile['warning'] - $current_applied + $context['warning_limit']); + } + + // Defaults. + $context['warning_data'] = array( + 'reason' => '', + 'notify' => '', + 'notify_subject' => '', + 'notify_body' => '', + ); + + // Are we saving? + if (isset($_POST['save'])) + { + // Security is good here. + checkSession('post'); + + // This cannot be empty! + $_POST['warn_reason'] = isset($_POST['warn_reason']) ? trim($_POST['warn_reason']) : ''; + if ($_POST['warn_reason'] == '' && !$context['user']['is_owner']) + $issueErrors[] = 'warning_no_reason'; + $_POST['warn_reason'] = $smcFunc['htmlspecialchars']($_POST['warn_reason']); + + // If the value hasn't changed it's either no JS or a real no change (Which this will pass) + if ($_POST['warning_level'] == 'SAME') + $_POST['warning_level'] = $_POST['warning_level_nojs']; + + $_POST['warning_level'] = (int) $_POST['warning_level']; + $_POST['warning_level'] = max(0, min(100, $_POST['warning_level'])); + if ($_POST['warning_level'] < $context['min_allowed']) + $_POST['warning_level'] = $context['min_allowed']; + elseif ($_POST['warning_level'] > $context['max_allowed']) + $_POST['warning_level'] = $context['max_allowed']; + + // Do we actually have to issue them with a PM? + $id_notice = 0; + if (!empty($_POST['warn_notify']) && empty($issueErrors)) + { + $_POST['warn_sub'] = trim($_POST['warn_sub']); + $_POST['warn_body'] = trim($_POST['warn_body']); + if (empty($_POST['warn_sub']) || empty($_POST['warn_body'])) + $issueErrors[] = 'warning_notify_blank'; + // Send the PM? + else + { + require_once($sourcedir . '/Subs-Post.php'); + $from = array( + 'id' => 0, + 'name' => $context['forum_name'], + 'username' => $context['forum_name'], + ); + sendpm(array('to' => array($memID), 'bcc' => array()), $_POST['warn_sub'], $_POST['warn_body'], false, $from); + + // Log the notice! + $smcFunc['db_insert']('', + '{db_prefix}log_member_notices', + array( + 'subject' => 'string-255', 'body' => 'string-65534', + ), + array( + $smcFunc['htmlspecialchars']($_POST['warn_sub']), $smcFunc['htmlspecialchars']($_POST['warn_body']), + ), + array('id_notice') + ); + $id_notice = $smcFunc['db_insert_id']('{db_prefix}log_member_notices', 'id_notice'); + } + } + + // Just in case - make sure notice is valid! + $id_notice = (int) $id_notice; + + // What have we changed? + $level_change = $_POST['warning_level'] - $cur_profile['warning']; + + // No errors? Proceed! Only log if you're not the owner. + if (empty($issueErrors)) + { + // Log what we've done! + if (!$context['user']['is_owner']) + $smcFunc['db_insert']('', + '{db_prefix}log_comments', + array( + 'id_member' => 'int', 'member_name' => 'string', 'comment_type' => 'string', 'id_recipient' => 'int', 'recipient_name' => 'string-255', + 'log_time' => 'int', 'id_notice' => 'int', 'counter' => 'int', 'body' => 'string-65534', + ), + array( + $user_info['id'], $user_info['name'], 'warning', $memID, $cur_profile['real_name'], + time(), $id_notice, $level_change, $_POST['warn_reason'], + ), + array('id_comment') + ); + + // Make the change. + updateMemberData($memID, array('warning' => $_POST['warning_level'])); + + // Leave a lovely message. + $context['profile_updated'] = $context['user']['is_owner'] ? $txt['profile_updated_own'] : $txt['profile_warning_success']; + } + else + { + // Get the base stuff done. + loadLanguage('Errors'); + $context['custom_error_title'] = $txt['profile_warning_errors_occured']; + + // Fill in the suite of errors. + $context['post_errors'] = array(); + foreach ($issueErrors as $error) + $context['post_errors'][] = $txt[$error]; + + // Try to remember some bits. + $context['warning_data'] = array( + 'reason' => $_POST['warn_reason'], + 'notify' => !empty($_POST['warn_notify']), + 'notify_subject' => isset($_POST['warn_sub']) ? $_POST['warn_sub'] : '', + 'notify_body' => isset($_POST['warn_body']) ? $_POST['warn_body'] : '', + ); + } + + // Show the new improved warning level. + $context['member']['warning'] = $_POST['warning_level']; + } + + $context['page_title'] = $txt['profile_issue_warning']; + + // Work our the various levels. + $context['level_effects'] = array( + 0 => $txt['profile_warning_effect_none'], + $modSettings['warning_watch'] => $txt['profile_warning_effect_watch'], + $modSettings['warning_moderate'] => $txt['profile_warning_effect_moderation'], + $modSettings['warning_mute'] => $txt['profile_warning_effect_mute'], + ); + $context['current_level'] = 0; + foreach ($context['level_effects'] as $limit => $dummy) + if ($context['member']['warning'] >= $limit) + $context['current_level'] = $limit; + + // Load up all the old warnings - count first! + $context['total_warnings'] = list_getUserWarningCount($memID); + + // Make the page index. + $context['start'] = (int) $_REQUEST['start']; + $perPage = (int) $modSettings['defaultMaxMessages']; + $context['page_index'] = constructPageIndex($scripturl . '?action=profile;u=' . $memID . ';area=issuewarning', $context['start'], $context['total_warnings'], $perPage); + + // Now do the data itself. + $context['previous_warnings'] = list_getUserWarnings($context['start'], $perPage, 'log_time DESC', $memID); + + // Are they warning because of a message? + if (isset($_REQUEST['msg']) && 0 < (int) $_REQUEST['msg']) + { + $request = $smcFunc['db_query']('', ' + SELECT subject + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + WHERE id_msg = {int:message} + AND {query_see_board} + LIMIT 1', + array( + 'message' => (int) $_REQUEST['msg'], + ) + ); + if ($smcFunc['db_num_rows']($request) != 0) + { + $context['warning_for_message'] = (int) $_REQUEST['msg']; + list ($context['warned_message_subject']) = $smcFunc['db_fetch_row']($request); + } + $smcFunc['db_free_result']($request); + + } + + // Didn't find the message? + if (empty($context['warning_for_message'])) + { + $context['warning_for_message'] = 0; + $context['warned_message_subject'] = ''; + } + + // Any custom templates? + $context['notification_templates'] = array(); + + $request = $smcFunc['db_query']('', ' + SELECT recipient_name AS template_title, body + FROM {db_prefix}log_comments + WHERE comment_type = {string:warntpl} + AND (id_recipient = {int:generic} OR id_recipient = {int:current_member})', + array( + 'warntpl' => 'warntpl', + 'generic' => 0, + 'current_member' => $user_info['id'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // If we're not warning for a message skip any that are. + if (!$context['warning_for_message'] && strpos($row['body'], '{MESSAGE}') !== false) + continue; + + $context['notification_templates'][] = array( + 'title' => $row['template_title'], + 'body' => $row['body'], + ); + } + $smcFunc['db_free_result']($request); + + // Setup the "default" templates. + foreach (array('spamming', 'offence', 'insulting') as $type) + $context['notification_templates'][] = array( + 'title' => $txt['profile_warning_notify_title_' . $type], + 'body' => sprintf($txt['profile_warning_notify_template_outline' . (!empty($context['warning_for_message']) ? '_post' : '')], $txt['profile_warning_notify_for_' . $type]), + ); + + // Replace all the common variables in the templates. + foreach ($context['notification_templates'] as $k => $name) + $context['notification_templates'][$k]['body'] = strtr($name['body'], array('{MEMBER}' => un_htmlspecialchars($context['member']['name']), '{MESSAGE}' => '[url=' . $scripturl . '?msg=' . $context['warning_for_message'] . ']' . un_htmlspecialchars($context['warned_message_subject']) . '[/url]', '{SCRIPTURL}' => $scripturl, '{FORUMNAME}' => $mbname, '{REGARDS}' => $txt['regards_team'])); +} + +// Get the number of warnings a user has. +function list_getUserWarningCount($memID) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_comments + WHERE id_recipient = {int:selected_member} + AND comment_type = {string:warning}', + array( + 'selected_member' => $memID, + 'warning' => 'warning', + ) + ); + list ($total_warnings) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $total_warnings; +} + +// Get the data about a users warnings. +function list_getUserWarnings($start, $items_per_page, $sort, $memID) +{ + global $smcFunc, $scripturl; + + $request = $smcFunc['db_query']('', ' + SELECT IFNULL(mem.id_member, 0) AS id_member, IFNULL(mem.real_name, lc.member_name) AS member_name, + lc.log_time, lc.body, lc.counter, lc.id_notice + FROM {db_prefix}log_comments AS lc + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lc.id_member) + WHERE lc.id_recipient = {int:selected_member} + AND lc.comment_type = {string:warning} + ORDER BY ' . $sort . ' + LIMIT ' . $start . ', ' . $items_per_page, + array( + 'selected_member' => $memID, + 'warning' => 'warning', + ) + ); + $previous_warnings = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $previous_warnings[] = array( + 'issuer' => array( + 'id' => $row['id_member'], + 'link' => $row['id_member'] ? ('' . $row['member_name'] . '') : $row['member_name'], + ), + 'time' => timeformat($row['log_time']), + 'reason' => $row['body'], + 'counter' => $row['counter'] > 0 ? '+' . $row['counter'] : $row['counter'], + 'id_notice' => $row['id_notice'], + ); + } + $smcFunc['db_free_result']($request); + + return $previous_warnings; +} + +// Present a screen to make sure the user wants to be deleted +function deleteAccount($memID) +{ + global $txt, $context, $user_info, $modSettings, $cur_profile, $smcFunc; + + if (!$context['user']['is_owner']) + isAllowedTo('profile_remove_any'); + elseif (!allowedTo('profile_remove_any')) + isAllowedTo('profile_remove_own'); + + // Permissions for removing stuff... + $context['can_delete_posts'] = !$context['user']['is_owner'] && allowedTo('moderate_forum'); + + // Can they do this, or will they need approval? + $context['needs_approval'] = $context['user']['is_owner'] && !empty($modSettings['approveAccountDeletion']) && !allowedTo('moderate_forum'); + $context['page_title'] = $txt['deleteAccount'] . ': ' . $cur_profile['real_name']; +} + +function deleteAccount2($profile_vars, $post_errors, $memID) +{ + global $user_info, $sourcedir, $context, $cur_profile, $modSettings, $smcFunc; + + // Try get more time... + @set_time_limit(600); + + // !!! Add a way to delete pms as well? + + if (!$context['user']['is_owner']) + isAllowedTo('profile_remove_any'); + elseif (!allowedTo('profile_remove_any')) + isAllowedTo('profile_remove_own'); + + checkSession(); + + $old_profile = &$cur_profile; + + // Too often, people remove/delete their own only account. + if (in_array(1, explode(',', $old_profile['additional_groups'])) || $old_profile['id_group'] == 1) + { + // Are you allowed to administrate the forum, as they are? + isAllowedTo('admin_forum'); + + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0) + AND id_member != {int:selected_member} + LIMIT 1', + array( + 'admin_group' => 1, + 'selected_member' => $memID, + ) + ); + list ($another) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (empty($another)) + fatal_lang_error('at_least_one_admin', 'critical'); + } + + // This file is needed for the deleteMembers function. + require_once($sourcedir . '/Subs-Members.php'); + + // Do you have permission to delete others profiles, or is that your profile you wanna delete? + if ($memID != $user_info['id']) + { + isAllowedTo('profile_remove_any'); + + // Now, have you been naughty and need your posts deleting? + // !!! Should this check board permissions? + if ($_POST['remove_type'] != 'none' && allowedTo('moderate_forum')) + { + // Include RemoveTopics - essential for this type of work! + require_once($sourcedir . '/RemoveTopic.php'); + + // First off we delete any topics the member has started - if they wanted topics being done. + if ($_POST['remove_type'] == 'topics') + { + // Fetch all topics started by this user within the time period. + $request = $smcFunc['db_query']('', ' + SELECT t.id_topic + FROM {db_prefix}topics AS t + WHERE t.id_member_started = {int:selected_member}', + array( + 'selected_member' => $memID, + ) + ); + $topicIDs = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $topicIDs[] = $row['id_topic']; + $smcFunc['db_free_result']($request); + + // Actually remove the topics. + // !!! This needs to check permissions, but we'll let it slide for now because of moderate_forum already being had. + removeTopics($topicIDs); + } + + // Now delete the remaining messages. + $request = $smcFunc['db_query']('', ' + SELECT m.id_msg + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic + AND t.id_first_msg != m.id_msg) + WHERE m.id_member = {int:selected_member}', + array( + 'selected_member' => $memID, + ) + ); + // This could take a while... but ya know it's gonna be worth it in the end. + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + removeMessage($row['id_msg']); + } + $smcFunc['db_free_result']($request); + } + + // Only delete this poor members account if they are actually being booted out of camp. + if (isset($_POST['deleteAccount'])) + deleteMembers($memID); + } + // Do they need approval to delete? + elseif (empty($post_errors) && !empty($modSettings['approveAccountDeletion']) && !allowedTo('moderate_forum')) + { + // Setup their account for deletion ;) + updateMemberData($memID, array('is_activated' => 4)); + // Another account needs approval... + updateSettings(array('unapprovedMembers' => true), true); + } + // Also check if you typed your password correctly. + elseif (empty($post_errors)) + { + deleteMembers($memID); + + require_once($sourcedir . '/LogInOut.php'); + LogOut(true); + + redirectExit(); + } +} + +// Function for doing all the paid subscription stuff - kinda. +function subscriptions($memID) +{ + global $context, $txt, $sourcedir, $modSettings, $smcFunc, $scripturl; + + // Load the paid template anyway. + loadTemplate('ManagePaid'); + loadLanguage('ManagePaid'); + + // Load all of the subscriptions. + require_once($sourcedir . '/ManagePaid.php'); + loadSubscriptions(); + $context['member']['id'] = $memID; + + // Remove any invalid ones. + foreach ($context['subscriptions'] as $id => $sub) + { + // Work out the costs. + $costs = @unserialize($sub['real_cost']); + + $cost_array = array(); + if ($sub['real_length'] == 'F') + { + foreach ($costs as $duration => $cost) + { + if ($cost != 0) + $cost_array[$duration] = $cost; + } + } + else + { + $cost_array['fixed'] = $costs['fixed']; + } + + if (empty($cost_array)) + unset($context['subscriptions'][$id]); + else + { + $context['subscriptions'][$id]['member'] = 0; + $context['subscriptions'][$id]['subscribed'] = false; + $context['subscriptions'][$id]['costs'] = $cost_array; + } + } + + // Work out what gateways are enabled. + $gateways = loadPaymentGateways(); + foreach ($gateways as $id => $gateway) + { + $gateways[$id] = new $gateway['display_class'](); + if (!$gateways[$id]->gatewayEnabled()) + unset($gateways[$id]); + } + + // No gateways yet? + if (empty($gateways)) + fatal_error($txt['paid_admin_not_setup_gateway']); + + // Get the current subscriptions. + $request = $smcFunc['db_query']('', ' + SELECT id_sublog, id_subscribe, start_time, end_time, status, payments_pending, pending_details + FROM {db_prefix}log_subscribed + WHERE id_member = {int:selected_member}', + array( + 'selected_member' => $memID, + ) + ); + $context['current'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // The subscription must exist! + if (!isset($context['subscriptions'][$row['id_subscribe']])) + continue; + + $context['current'][$row['id_subscribe']] = array( + 'id' => $row['id_sublog'], + 'sub_id' => $row['id_subscribe'], + 'hide' => $row['status'] == 0 && $row['end_time'] == 0 && $row['payments_pending'] == 0, + 'name' => $context['subscriptions'][$row['id_subscribe']]['name'], + 'start' => timeformat($row['start_time'], false), + 'end' => $row['end_time'] == 0 ? $txt['not_applicable'] : timeformat($row['end_time'], false), + 'pending_details' => $row['pending_details'], + 'status' => $row['status'], + 'status_text' => $row['status'] == 0 ? ($row['payments_pending'] ? $txt['paid_pending'] : $txt['paid_finished']) : $txt['paid_active'], + ); + + if ($row['status'] == 1) + $context['subscriptions'][$row['id_subscribe']]['subscribed'] = true; + } + $smcFunc['db_free_result']($request); + + // Simple "done"? + if (isset($_GET['done'])) + { + $_GET['sub_id'] = (int) $_GET['sub_id']; + + // Must exist but let's be sure... + if (isset($context['current'][$_GET['sub_id']])) + { + // What are the details like? + $current_pending = @unserialize($context['current'][$_GET['sub_id']]['pending_details']); + if (!empty($current_pending)) + { + $current_pending = array_reverse($current_pending); + foreach ($current_pending as $id => $sub) + { + // Just find one and change it. + if ($sub[0] == $_GET['sub_id'] && $sub[3] == 'prepay') + { + $current_pending[$id][3] = 'payback'; + break; + } + } + + // Save the details back. + $pending_details = serialize($current_pending); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_subscribed + SET payments_pending = payments_pending + 1, pending_details = {string:pending_details} + WHERE id_sublog = {int:current_subscription_id} + AND id_member = {int:selected_member}', + array( + 'current_subscription_id' => $context['current'][$_GET['sub_id']]['id'], + 'selected_member' => $memID, + 'pending_details' => $pending_details, + ) + ); + } + } + + $context['sub_template'] = 'paid_done'; + return; + } + // If this is confirmation then it's simpler... + if (isset($_GET['confirm']) && isset($_POST['sub_id']) && is_array($_POST['sub_id'])) + { + // Hopefully just one. + foreach ($_POST['sub_id'] as $k => $v) + $ID_SUB = (int) $k; + + if (!isset($context['subscriptions'][$ID_SUB]) || $context['subscriptions'][$ID_SUB]['active'] == 0) + fatal_lang_error('paid_sub_not_active'); + + // Simplify... + $context['sub'] = $context['subscriptions'][$ID_SUB]; + $period = 'xx'; + if ($context['sub']['flexible']) + $period = isset($_POST['cur'][$ID_SUB]) && isset($context['sub']['costs'][$_POST['cur'][$ID_SUB]]) ? $_POST['cur'][$ID_SUB] : 'xx'; + + // Check we have a valid cost. + if ($context['sub']['flexible'] && $period == 'xx') + fatal_lang_error('paid_sub_not_active'); + + // Sort out the cost/currency. + $context['currency'] = $modSettings['paid_currency_code']; + $context['recur'] = $context['sub']['repeatable']; + + if ($context['sub']['flexible']) + { + // Real cost... + $context['value'] = $context['sub']['costs'][$_POST['cur'][$ID_SUB]]; + $context['cost'] = sprintf($modSettings['paid_currency_symbol'], $context['value']) . '/' . $txt[$_POST['cur'][$ID_SUB]]; + // The period value for paypal. + $context['paypal_period'] = strtoupper(substr($_POST['cur'][$ID_SUB], 0, 1)); + } + else + { + // Real cost... + $context['value'] = $context['sub']['costs']['fixed']; + $context['cost'] = sprintf($modSettings['paid_currency_symbol'], $context['value']); + + // Recur? + preg_match('~(\d*)(\w)~', $context['sub']['real_length'], $match); + $context['paypal_unit'] = $match[1]; + $context['paypal_period'] = $match[2]; + } + + // Setup the gateway context. + $context['gateways'] = array(); + foreach ($gateways as $id => $gateway) + { + $fields = $gateways[$id]->fetchGatewayFields($context['sub']['id'] . '+' . $memID, $context['sub'], $context['value'], $period, $scripturl . '?action=profile;u=' . $memID . ';area=subscriptions;sub_id=' . $context['sub']['id'] . ';done'); + if (!empty($fields['form'])) + $context['gateways'][] = $fields; + } + + // Bugger?! + if (empty($context['gateways'])) + fatal_error($txt['paid_admin_not_setup_gateway']); + + // Now we are going to assume they want to take this out ;) + $new_data = array($context['sub']['id'], $context['value'], $period, 'prepay'); + if (isset($context['current'][$context['sub']['id']])) + { + // What are the details like? + $current_pending = array(); + if ($context['current'][$context['sub']['id']]['pending_details'] != '') + $current_pending = @unserialize($context['current'][$context['sub']['id']]['pending_details']); + // Don't get silly. + if (count($current_pending) > 9) + $current_pending = array(); + $pending_count = 0; + // Only record real pending payments as will otherwise confuse the admin! + foreach ($current_pending as $pending) + if ($pending[3] == 'payback') + $pending_count++; + + if (!in_array($new_data, $current_pending)) + { + $current_pending[] = $new_data; + $pending_details = serialize($current_pending); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_subscribed + SET payments_pending = {int:pending_count}, pending_details = {string:pending_details} + WHERE id_sublog = {int:current_subscription_item} + AND id_member = {int:selected_member}', + array( + 'pending_count' => $pending_count, + 'current_subscription_item' => $context['current'][$context['sub']['id']]['id'], + 'selected_member' => $memID, + 'pending_details' => $pending_details, + ) + ); + } + + } + // Never had this before, lovely. + else + { + $pending_details = serialize(array($new_data)); + $smcFunc['db_insert']('', + '{db_prefix}log_subscribed', + array( + 'id_subscribe' => 'int', 'id_member' => 'int', 'status' => 'int', 'payments_pending' => 'int', 'pending_details' => 'string-65534', + 'start_time' => 'int', 'vendor_ref' => 'string-255', + ), + array( + $context['sub']['id'], $memID, 0, 0, $pending_details, + time(), '', + ), + array('id_sublog') + ); + } + + // Change the template. + $context['sub_template'] = 'choose_payment'; + + // Quit. + return; + } + else + $context['sub_template'] = 'user_subscription'; +} + +?> \ No newline at end of file diff --git a/Sources/Profile-Modify.php b/Sources/Profile-Modify.php new file mode 100644 index 0000000..7eeea3b --- /dev/null +++ b/Sources/Profile-Modify.php @@ -0,0 +1,3428 @@ + array( + 'type' => 'text', + 'label' => $txt['aim'], + 'subtext' => $txt['your_aim'], + 'size' => 24, + 'value' => strtr(empty($cur_profile['aim']) ? '' : $cur_profile['aim'], '+', ' '), + 'permission' => 'profile_extra', + 'input_validate' => create_function('&$value', ' + $value = strtr($value, \' \', \'+\'); + return true; + '), + ), + 'avatar_choice' => array( + 'type' => 'callback', + 'callback_func' => 'avatar_select', + // This handles the permissions too. + 'preload' => 'profileLoadAvatarData', + 'input_validate' => 'profileSaveAvatarData', + 'save_key' => 'avatar', + ), + 'bday1' => array( + 'type' => 'callback', + 'callback_func' => 'birthdate', + 'permission' => 'profile_extra', + 'preload' => create_function('', ' + global $cur_profile, $context; + + // Split up the birthdate.... + list ($uyear, $umonth, $uday) = explode(\'-\', empty($cur_profile[\'birthdate\']) || $cur_profile[\'birthdate\'] == \'0001-01-01\' ? \'0000-00-00\' : $cur_profile[\'birthdate\']); + $context[\'member\'][\'birth_date\'] = array( + \'year\' => $uyear == \'0004\' ? \'0000\' : $uyear, + \'month\' => $umonth, + \'day\' => $uday, + ); + + return true; + '), + 'input_validate' => create_function('&$value', ' + global $profile_vars, $cur_profile; + + if (isset($_POST[\'bday2\'], $_POST[\'bday3\']) && $value > 0 && $_POST[\'bday2\'] > 0) + { + // Set to blank? + if ((int) $_POST[\'bday3\'] == 1 && (int) $_POST[\'bday2\'] == 1 && (int) $value == 1) + $value = \'0001-01-01\'; + else + $value = checkdate($value, $_POST[\'bday2\'], $_POST[\'bday3\'] < 4 ? 4 : $_POST[\'bday3\']) ? sprintf(\'%04d-%02d-%02d\', $_POST[\'bday3\'] < 4 ? 4 : $_POST[\'bday3\'], $_POST[\'bday1\'], $_POST[\'bday2\']) : \'0001-01-01\'; + } + else + $value = \'0001-01-01\'; + + $profile_vars[\'birthdate\'] = $value; + $cur_profile[\'birthdate\'] = $value; + return false; + '), + ), + // Setting the birthdate the old style way? + 'birthdate' => array( + 'type' => 'hidden', + 'permission' => 'profile_extra', + 'input_validate' => create_function('&$value', ' + global $cur_profile; + // !!! Should we check for this year and tell them they made a mistake :P? (based on coppa at least?) + if (preg_match(\'/(\d{4})[\-\., ](\d{2})[\-\., ](\d{2})/\', $value, $dates) === 1) + { + $value = checkdate($dates[2], $dates[3], $dates[1] < 4 ? 4 : $dates[1]) ? sprintf(\'%04d-%02d-%02d\', $dates[1] < 4 ? 4 : $dates[1], $dates[2], $dates[3]) : \'0001-01-01\'; + return true; + } + else + { + $value = empty($cur_profile[\'birthdate\']) ? \'0001-01-01\' : $cur_profile[\'birthdate\']; + return false; + } + '), + ), + 'date_registered' => array( + 'type' => 'text', + 'value' => empty($cur_profile['date_registered']) ? $txt['not_applicable'] : strftime('%Y-%m-%d', $cur_profile['date_registered'] + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600), + 'label' => $txt['date_registered'], + 'log_change' => true, + 'permission' => 'moderate_forum', + 'input_validate' => create_function('&$value', ' + global $txt, $user_info, $modSettings, $cur_profile, $context; + + // Bad date! Go try again - please? + if (($value = strtotime($value)) === -1) + { + $value = $cur_profile[\'date_registered\']; + return $txt[\'invalid_registration\'] . \' \' . strftime(\'%d %b %Y \' . (strpos($user_info[\'time_format\'], \'%H\') !== false ? \'%I:%M:%S %p\' : \'%H:%M:%S\'), forum_time(false)); + } + // As long as it doesn\'t equal "N/A"... + elseif ($value != $txt[\'not_applicable\'] && $value != strtotime(strftime(\'%Y-%m-%d\', $cur_profile[\'date_registered\'] + ($user_info[\'time_offset\'] + $modSettings[\'time_offset\']) * 3600))) + $value = $value - ($user_info[\'time_offset\'] + $modSettings[\'time_offset\']) * 3600; + else + $value = $cur_profile[\'date_registered\']; + + return true; + '), + ), + 'email_address' => array( + 'type' => 'text', + 'label' => $txt['email'], + 'subtext' => $txt['valid_email'], + 'log_change' => true, + 'permission' => 'profile_identity', + 'input_validate' => create_function('&$value', ' + global $context, $old_profile, $context, $profile_vars, $sourcedir, $modSettings; + + if (strtolower($value) == strtolower($old_profile[\'email_address\'])) + return false; + + $isValid = profileValidateEmail($value, $context[\'id_member\']); + + // Do they need to revalidate? If so schedule the function! + if ($isValid === true && !empty($modSettings[\'send_validation_onChange\']) && !allowedTo(\'moderate_forum\')) + { + require_once($sourcedir . \'/Subs-Members.php\'); + $profile_vars[\'validation_code\'] = generateValidationCode(); + $profile_vars[\'is_activated\'] = 2; + $context[\'profile_execute_on_save\'][] = \'profileSendActivation\'; + unset($context[\'profile_execute_on_save\'][\'reload_user\']); + } + + return $isValid; + '), + ), + 'gender' => array( + 'type' => 'select', + 'cast_type' => 'int', + 'options' => 'return array(0 => \'\', 1 => $txt[\'male\'], 2 => $txt[\'female\']);', + 'label' => $txt['gender'], + 'permission' => 'profile_extra', + ), + 'hide_email' => array( + 'type' => 'check', + 'value' => empty($cur_profile['hide_email']) ? true : false, + 'label' => $txt['allow_user_email'], + 'permission' => 'profile_identity', + 'input_validate' => create_function('&$value', ' + $value = $value == 0 ? 1 : 0; + + return true; + '), + ), + 'icq' => array( + 'type' => 'text', + 'label' => $txt['icq'], + 'subtext' => $txt['your_icq'], + 'size' => 24, + 'permission' => 'profile_extra', + // Need to make sure ICQ doesn't equal 0. + 'input_validate' => create_function('&$value', ' + if (empty($value)) + $value = \'\'; + else + $value = (int) $value; + return true; + '), + ), + // Selecting group membership is a complicated one so we treat it separate! + 'id_group' => array( + 'type' => 'callback', + 'callback_func' => 'group_manage', + 'permission' => 'manage_membergroups', + 'preload' => 'profileLoadGroups', + 'log_change' => true, + 'input_validate' => 'profileSaveGroups', + ), + 'id_theme' => array( + 'type' => 'callback', + 'callback_func' => 'theme_pick', + 'permission' => 'profile_extra', + 'enabled' => $modSettings['theme_allow'] || allowedTo('admin_forum'), + 'preload' => create_function('', ' + global $smcFunc, $context, $cur_profile, $txt; + + $request = $smcFunc[\'db_query\'](\'\', \' + SELECT value + FROM {db_prefix}themes + WHERE id_theme = {int:id_theme} + AND variable = {string:variable} + LIMIT 1\', array( + \'id_theme\' => $cur_profile[\'id_theme\'], + \'variable\' => \'name\', + ) + ); + list ($name) = $smcFunc[\'db_fetch_row\']($request); + $smcFunc[\'db_free_result\']($request); + + $context[\'member\'][\'theme\'] = array( + \'id\' => $cur_profile[\'id_theme\'], + \'name\' => empty($cur_profile[\'id_theme\']) ? $txt[\'theme_forum_default\'] : $name + ); + return true; + '), + 'input_validate' => create_function('&$value', ' + $value = (int) $value; + return true; + '), + ), + 'karma_good' => array( + 'type' => 'callback', + 'callback_func' => 'karma_modify', + 'permission' => 'admin_forum', + // Set karma_bad too! + 'input_validate' => create_function('&$value', ' + global $profile_vars, $cur_profile; + + $value = (int) $value; + if (isset($_POST[\'karma_bad\'])) + { + $profile_vars[\'karma_bad\'] = $_POST[\'karma_bad\'] != \'\' ? (int) $_POST[\'karma_bad\'] : 0; + $cur_profile[\'karma_bad\'] = $_POST[\'karma_bad\'] != \'\' ? (int) $_POST[\'karma_bad\'] : 0; + } + return true; + '), + 'preload' => create_function('', ' + global $context, $cur_profile; + + $context[\'member\'][\'karma\'][\'good\'] = $cur_profile[\'karma_good\']; + $context[\'member\'][\'karma\'][\'bad\'] = $cur_profile[\'karma_bad\']; + + return true; + '), + 'enabled' => !empty($modSettings['karmaMode']), + ), + 'lngfile' => array( + 'type' => 'select', + 'options' => 'return $context[\'profile_languages\'];', + 'label' => $txt['preferred_language'], + 'permission' => 'profile_identity', + 'preload' => 'profileLoadLanguages', + 'enabled' => !empty($modSettings['userLanguage']), + 'value' => empty($cur_profile['lngfile']) ? $language : $cur_profile['lngfile'], + 'input_validate' => create_function('&$value', ' + global $context, $cur_profile; + + // Load the languages. + profileLoadLanguages(); + + if (isset($context[\'profile_languages\'][$value])) + { + if ($context[\'user\'][\'is_owner\'] && empty($context[\'password_auth_failed\'])) + $_SESSION[\'language\'] = $value; + return true; + } + else + { + $value = $cur_profile[\'lngfile\']; + return false; + } + '), + ), + 'location' => array( + 'type' => 'text', + 'label' => $txt['location'], + 'log_change' => true, + 'size' => 50, + 'permission' => 'profile_extra', + ), + // The username is not always editable - so adjust it as such. + 'member_name' => array( + 'type' => allowedTo('admin_forum') && isset($_GET['changeusername']) ? 'text' : 'label', + 'label' => $txt['username'], + 'subtext' => allowedTo('admin_forum') && !isset($_GET['changeusername']) ? '(' . $txt['username_change'] . ')' : '', + 'log_change' => true, + 'permission' => 'profile_identity', + 'prehtml' => allowedTo('admin_forum') && isset($_GET['changeusername']) ? '
' . $txt['username_warning'] . '
' : '', + 'input_validate' => create_function('&$value', ' + global $sourcedir, $context, $user_info, $cur_profile; + + if (allowedTo(\'admin_forum\')) + { + // We\'ll need this... + require_once($sourcedir . \'/Subs-Auth.php\'); + + // Maybe they are trying to change their password as well? + $resetPassword = true; + if (isset($_POST[\'passwrd1\']) && $_POST[\'passwrd1\'] != \'\' && isset($_POST[\'passwrd2\']) && $_POST[\'passwrd1\'] == $_POST[\'passwrd2\'] && validatePassword($_POST[\'passwrd1\'], $value, array($cur_profile[\'real_name\'], $user_info[\'username\'], $user_info[\'name\'], $user_info[\'email\'])) == null) + $resetPassword = false; + + // Do the reset... this will send them an email too. + if ($resetPassword) + resetPassword($context[\'id_member\'], $value); + elseif ($value !== null) + { + validateUsername($context[\'id_member\'], trim(preg_replace(\'~[\t\n\r \x0B\0\' . ($context[\'utf8\'] ? ($context[\'server\'][\'complex_preg_chars\'] ? \'\x{A0}\x{AD}\x{2000}-\x{200F}\x{201F}\x{202F}\x{3000}\x{FEFF}\' : "\xC2\xA0\xC2\xAD\xE2\x80\x80-\xE2\x80\x8F\xE2\x80\x9F\xE2\x80\xAF\xE2\x80\x9F\xE3\x80\x80\xEF\xBB\xBF") : \'\x00-\x08\x0B\x0C\x0E-\x19\xA0\') . \']+~\' . ($context[\'utf8\'] ? \'u\' : \'\'), \' \', $value))); + updateMemberData($context[\'id_member\'], array(\'member_name\' => $value)); + } + } + return false; + '), + ), + 'msn' => array( + 'type' => 'text', + 'label' => $txt['msn'], + 'subtext' => $txt['msn_email_address'], + 'size' => 24, + 'permission' => 'profile_extra', + 'input_validate' => create_function('&$value', ' + global $cur_profile; + // Make sure the msn one is an email address, not something like \'none\' :P. + if ($value != \'\' && preg_match(\'~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\\\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~\', $value) == 0) + { + $value = $cur_profile[\'msn\']; + return false; + } + return true; + '), + ), + 'passwrd1' => array( + 'type' => 'password', + 'label' => $txt['choose_pass'], + 'subtext' => $txt['password_strength'], + 'size' => 20, + 'value' => '', + 'enabled' => empty($cur_profile['openid_uri']), + 'permission' => 'profile_identity', + 'save_key' => 'passwd', + // Note this will only work if passwrd2 also exists! + 'input_validate' => create_function('&$value', ' + global $sourcedir, $user_info, $smcFunc, $cur_profile; + + // If we didn\'t try it then ignore it! + if ($value == \'\') + return false; + + // Do the two entries for the password even match? + if (!isset($_POST[\'passwrd2\']) || $value != $_POST[\'passwrd2\']) + return \'bad_new_password\'; + + // Let\'s get the validation function into play... + require_once($sourcedir . \'/Subs-Auth.php\'); + $passwordErrors = validatePassword($value, $cur_profile[\'member_name\'], array($cur_profile[\'real_name\'], $user_info[\'username\'], $user_info[\'name\'], $user_info[\'email\'])); + + // Were there errors? + if ($passwordErrors != null) + return \'password_\' . $passwordErrors; + + // Set up the new password variable... ready for storage. + $value = sha1(strtolower($cur_profile[\'member_name\']) . un_htmlspecialchars($value)); + return true; + '), + ), + 'passwrd2' => array( + 'type' => 'password', + 'label' => $txt['verify_pass'], + 'enabled' => empty($cur_profile['openid_uri']), + 'size' => 20, + 'value' => '', + 'permission' => 'profile_identity', + 'is_dummy' => true, + ), + 'personal_text' => array( + 'type' => 'text', + 'label' => $txt['personal_text'], + 'log_change' => true, + 'input_attr' => array('maxlength="50"'), + 'size' => 50, + 'permission' => 'profile_extra', + ), + // This does ALL the pm settings + 'pm_prefs' => array( + 'type' => 'callback', + 'callback_func' => 'pm_settings', + 'permission' => 'pm_read', + 'preload' => create_function('', ' + global $context, $cur_profile; + + $context[\'display_mode\'] = $cur_profile[\'pm_prefs\'] & 3; + $context[\'send_email\'] = $cur_profile[\'pm_email_notify\']; + $context[\'receive_from\'] = !empty($cur_profile[\'pm_receive_from\']) ? $cur_profile[\'pm_receive_from\'] : 0; + + return true; + '), + 'input_validate' => create_function('&$value', ' + global $cur_profile, $profile_vars; + + // Simple validate and apply the two "sub settings" + $value = max(min($value, 2), 0); + + $cur_profile[\'pm_email_notify\'] = $profile_vars[\'pm_email_notify\'] = max(min((int) $_POST[\'pm_email_notify\'], 2), 0); + $cur_profile[\'pm_receive_from\'] = $profile_vars[\'pm_receive_from\'] = max(min((int) $_POST[\'pm_receive_from\'], 4), 0); + + return true; + '), + ), + 'posts' => array( + 'type' => 'int', + 'label' => $txt['profile_posts'], + 'log_change' => true, + 'size' => 7, + 'permission' => 'moderate_forum', + 'input_validate' => create_function('&$value', ' + $value = $value != \'\' ? strtr($value, array(\',\' => \'\', \'.\' => \'\', \' \' => \'\')) : 0; + return true; + '), + ), + 'real_name' => array( + 'type' => !empty($modSettings['allow_editDisplayName']) || allowedTo('moderate_forum') ? 'text' : 'label', + 'label' => $txt['name'], + 'subtext' => $txt['display_name_desc'], + 'log_change' => true, + 'input_attr' => array('maxlength="60"'), + 'permission' => 'profile_identity', + 'enabled' => !empty($modSettings['allow_editDisplayName']) || allowedTo('moderate_forum'), + 'input_validate' => create_function('&$value', ' + global $context, $smcFunc, $sourcedir, $cur_profile; + + $value = trim(preg_replace(\'~[\t\n\r \x0B\0\' . ($context[\'utf8\'] ? ($context[\'server\'][\'complex_preg_chars\'] ? \'\x{A0}\x{AD}\x{2000}-\x{200F}\x{201F}\x{202F}\x{3000}\x{FEFF}\' : "\xC2\xA0\xC2\xAD\xE2\x80\x80-\xE2\x80\x8F\xE2\x80\x9F\xE2\x80\xAF\xE2\x80\x9F\xE3\x80\x80\xEF\xBB\xBF") : \'\x00-\x08\x0B\x0C\x0E-\x19\xA0\') . \']+~\' . ($context[\'utf8\'] ? \'u\' : \'\'), \' \', $value)); + + if (trim($value) == \'\') + return \'no_name\'; + elseif ($smcFunc[\'strlen\']($value) > 60) + return \'name_too_long\'; + elseif ($cur_profile[\'real_name\'] != $value) + { + require_once($sourcedir . \'/Subs-Members.php\'); + if (isReservedName($value, $context[\'id_member\'])) + return \'name_taken\'; + } + return true; + '), + ), + 'secret_question' => array( + 'type' => 'text', + 'label' => $txt['secret_question'], + 'subtext' => $txt['secret_desc'], + 'size' => 50, + 'permission' => 'profile_identity', + ), + 'secret_answer' => array( + 'type' => 'text', + 'label' => $txt['secret_answer'], + 'subtext' => $txt['secret_desc2'], + 'size' => 20, + 'postinput' => '' . $txt['secret_why_blank'] . '', + 'value' => '', + 'permission' => 'profile_identity', + 'input_validate' => create_function('&$value', ' + $value = $value != \'\' ? md5($value) : \'\'; + return true; + '), + ), + 'signature' => array( + 'type' => 'callback', + 'callback_func' => 'signature_modify', + 'permission' => 'profile_extra', + 'enabled' => substr($modSettings['signature_settings'], 0, 1) == 1, + 'preload' => 'profileLoadSignatureData', + 'input_validate' => 'profileValidateSignature', + ), + 'show_online' => array( + 'type' => 'check', + 'label' => $txt['show_online'], + 'permission' => 'profile_identity', + 'enabled' => !empty($modSettings['allow_hideOnline']) || allowedTo('moderate_forum'), + ), + 'smiley_set' => array( + 'type' => 'callback', + 'callback_func' => 'smiley_pick', + 'enabled' => !empty($modSettings['smiley_sets_enable']), + 'permission' => 'profile_extra', + 'preload' => create_function('', ' + global $modSettings, $context, $txt, $cur_profile; + + $context[\'member\'][\'smiley_set\'][\'id\'] = empty($cur_profile[\'smiley_set\']) ? \'\' : $cur_profile[\'smiley_set\']; + $context[\'smiley_sets\'] = explode(\',\', \'none,,\' . $modSettings[\'smiley_sets_known\']); + $set_names = explode("\n", $txt[\'smileys_none\'] . "\n" . $txt[\'smileys_forum_board_default\'] . "\n" . $modSettings[\'smiley_sets_names\']); + foreach ($context[\'smiley_sets\'] as $i => $set) + { + $context[\'smiley_sets\'][$i] = array( + \'id\' => htmlspecialchars($set), + \'name\' => htmlspecialchars($set_names[$i]), + \'selected\' => $set == $context[\'member\'][\'smiley_set\'][\'id\'] + ); + + if ($context[\'smiley_sets\'][$i][\'selected\']) + $context[\'member\'][\'smiley_set\'][\'name\'] = $set_names[$i]; + } + return true; + '), + 'input_validate' => create_function('&$value', ' + global $modSettings; + + $smiley_sets = explode(\',\', $modSettings[\'smiley_sets_known\']); + if (!in_array($value, $smiley_sets) && $value != \'none\') + $value = \'\'; + return true; + '), + ), + // Pretty much a dummy entry - it populates all the theme settings. + 'theme_settings' => array( + 'type' => 'callback', + 'callback_func' => 'theme_settings', + 'permission' => 'profile_extra', + 'is_dummy' => true, + 'preload' => create_function('', ' + loadLanguage(\'Settings\'); + return true; + '), + ), + 'time_format' => array( + 'type' => 'callback', + 'callback_func' => 'timeformat_modify', + 'permission' => 'profile_extra', + 'preload' => create_function('', ' + global $context, $user_info, $txt, $cur_profile, $modSettings; + + $context[\'easy_timeformats\'] = array( + array(\'format\' => \'\', \'title\' => $txt[\'timeformat_default\']), + array(\'format\' => \'%B %d, %Y, %I:%M:%S %p\', \'title\' => $txt[\'timeformat_easy1\']), + array(\'format\' => \'%B %d, %Y, %H:%M:%S\', \'title\' => $txt[\'timeformat_easy2\']), + array(\'format\' => \'%Y-%m-%d, %H:%M:%S\', \'title\' => $txt[\'timeformat_easy3\']), + array(\'format\' => \'%d %B %Y, %H:%M:%S\', \'title\' => $txt[\'timeformat_easy4\']), + array(\'format\' => \'%d-%m-%Y, %H:%M:%S\', \'title\' => $txt[\'timeformat_easy5\']) + ); + + $context[\'member\'][\'time_format\'] = $cur_profile[\'time_format\']; + $context[\'current_forum_time\'] = timeformat(time() - $user_info[\'time_offset\'] * 3600, false); + $context[\'current_forum_time_js\'] = strftime(\'%Y,\' . ((int) strftime(\'%m\', time() + $modSettings[\'time_offset\'] * 3600) - 1) . \',%d,%H,%M,%S\', time() + $modSettings[\'time_offset\'] * 3600); + $context[\'current_forum_time_hour\'] = (int) strftime(\'%H\', forum_time(false)); + return true; + '), + ), + 'time_offset' => array( + 'type' => 'callback', + 'callback_func' => 'timeoffset_modify', + 'permission' => 'profile_extra', + 'preload' => create_function('', ' + global $context, $cur_profile; + $context[\'member\'][\'time_offset\'] = $cur_profile[\'time_offset\']; + return true; + '), + 'input_validate' => create_function('&$value', ' + // Validate the time_offset... + $value = (float) strtr($value, \',\', \'.\'); + + if ($value < -23.5 || $value > 23.5) + return \'bad_offset\'; + + return true; + '), + ), + 'usertitle' => array( + 'type' => 'text', + 'label' => $txt['custom_title'], + 'log_change' => true, + 'size' => 50, + 'permission' => 'profile_title', + 'enabled' => !empty($modSettings['titlesEnable']), + ), + 'website_title' => array( + 'type' => 'text', + 'label' => $txt['website_title'], + 'subtext' => $txt['include_website_url'], + 'size' => 50, + 'permission' => 'profile_extra', + 'link_with' => 'website', + ), + 'website_url' => array( + 'type' => 'text', + 'label' => $txt['website_url'], + 'subtext' => $txt['complete_url'], + 'size' => 50, + 'permission' => 'profile_extra', + // Fix the URL... + 'input_validate' => create_function('&$value', ' + + if (strlen(trim($value)) > 0 && strpos($value, \'://\') === false) + $value = \'http://\' . $value; + if (strlen($value) < 8 || (substr($value, 0, 7) !== \'http://\' && substr($value, 0, 8) !== \'https://\')) + $value = \'\'; + return true; + '), + 'link_with' => 'website', + ), + 'yim' => array( + 'type' => 'text', + 'label' => $txt['yim'], + 'subtext' => $txt['your_yim'], + 'size' => 24, + 'input_attr' => array('maxlength="32"'), + 'permission' => 'profile_extra', + ), + ); + + $disabled_fields = !empty($modSettings['disabled_profile_fields']) ? explode(',', $modSettings['disabled_profile_fields']) : array(); + // For each of the above let's take out the bits which don't apply - to save memory and security! + foreach ($profile_fields as $key => $field) + { + // Do we have permission to do this? + if (isset($field['permission']) && !allowedTo(($context['user']['is_owner'] ? array($field['permission'] . '_own', $field['permission'] . '_any') : $field['permission'] . '_any')) && !allowedTo($field['permission'])) + unset($profile_fields[$key]); + + // Is it enabled? + if (isset($field['enabled']) && !$field['enabled']) + unset($profile_fields[$key]); + + // Is it specifically disabled? + if (in_array($key, $disabled_fields) || (isset($field['link_with']) && in_array($field['link_with'], $disabled_fields))) + unset($profile_fields[$key]); + } +} + +// Setup the context for a page load! +function setupProfileContext($fields) +{ + global $profile_fields, $context, $cur_profile, $smcFunc, $txt; + + // Make sure we have this! + loadProfileFields(true); + + // First check for any linked sets. + foreach ($profile_fields as $key => $field) + if (isset($field['link_with']) && in_array($field['link_with'], $fields)) + $fields[] = $key; + + // Some default bits. + $context['profile_prehtml'] = ''; + $context['profile_posthtml'] = ''; + $context['profile_javascript'] = ''; + $context['profile_onsubmit_javascript'] = ''; + + $i = 0; + $last_type = ''; + foreach ($fields as $key => $field) + { + if (isset($profile_fields[$field])) + { + // Shortcut. + $cur_field = &$profile_fields[$field]; + + // Does it have a preload and does that preload succeed? + if (isset($cur_field['preload']) && !$cur_field['preload']()) + continue; + + // If this is anything but complex we need to do more cleaning! + if ($cur_field['type'] != 'callback' && $cur_field['type'] != 'hidden') + { + if (!isset($cur_field['label'])) + $cur_field['label'] = isset($txt[$field]) ? $txt[$field] : $field; + + // Everything has a value! + if (!isset($cur_field['value'])) + { + $cur_field['value'] = isset($cur_profile[$field]) ? $cur_profile[$field] : ''; + } + + // Any input attributes? + $cur_field['input_attr'] = !empty($cur_field['input_attr']) ? implode(',', $cur_field['input_attr']) : ''; + } + + // Was there an error with this field on posting? + if (isset($context['profile_errors'][$field])) + $cur_field['is_error'] = true; + + // Any javascript stuff? + if (!empty($cur_field['js_submit'])) + $context['profile_onsubmit_javascript'] .= $cur_field['js_submit']; + if (!empty($cur_field['js'])) + $context['profile_javascript'] .= $cur_field['js']; + + // Any template stuff? + if (!empty($cur_field['prehtml'])) + $context['profile_prehtml'] .= $cur_field['prehtml']; + if (!empty($cur_field['posthtml'])) + $context['profile_posthtml'] .= $cur_field['posthtml']; + + // Finally put it into context? + if ($cur_field['type'] != 'hidden') + { + $last_type = $cur_field['type']; + $context['profile_fields'][$field] = &$profile_fields[$field]; + } + } + // Bodge in a line break - without doing two in a row ;) + elseif ($field == 'hr' && $last_type != 'hr' && $last_type != '') + { + $last_type = 'hr'; + $context['profile_fields'][$i++]['type'] = 'hr'; + } + } + + // Free up some memory. + unset($profile_fields); +} + +// Save the profile changes. +function saveProfileFields() +{ + global $profile_fields, $profile_vars, $context, $old_profile, $post_errors, $sourcedir, $modSettings, $cur_profile, $smcFunc; + + // Load them up. + loadProfileFields(); + + // This makes things easier... + $old_profile = $cur_profile; + + // This allows variables to call activities when they save - by default just to reload their settings + $context['profile_execute_on_save'] = array(); + if ($context['user']['is_owner']) + $context['profile_execute_on_save']['reload_user'] = 'profileReloadUser'; + + // Assume we log nothing. + $context['log_changes'] = array(); + + // Cycle through the profile fields working out what to do! + foreach ($profile_fields as $key => $field) + { + if (!isset($_POST[$key]) || !empty($field['is_dummy'])) + continue; + + // What gets updated? + $db_key = isset($field['save_key']) ? $field['save_key'] : $key; + + // Right - we have something that is enabled, we can act upon and has a value posted to it. Does it have a validation function? + if (isset($field['input_validate'])) + { + $is_valid = $field['input_validate']($_POST[$key]); + // An error occured - set it as such! + if ($is_valid !== true) + { + // Is this an actual error? + if ($is_valid !== false) + { + $post_errors[$key] = $is_valid; + $profile_fields[$key]['is_error'] = $is_valid; + } + // Retain the old value. + $cur_profile[$key] = $_POST[$key]; + continue; + } + } + + // Are we doing a cast? + $field['cast_type'] = empty($field['cast_type']) ? $field['type'] : $field['cast_type']; + + // Finally, clean up certain types. + if ($field['cast_type'] == 'int') + $_POST[$key] = (int) $_POST[$key]; + elseif ($field['cast_type'] == 'float') + $_POST[$key] = (float) $_POST[$key]; + elseif ($field['cast_type'] == 'check') + $_POST[$key] = !empty($_POST[$key]) ? 1 : 0; + + // If we got here we're doing OK. + if ($field['type'] != 'hidden' && (!isset($old_profile[$key]) || $_POST[$key] != $old_profile[$key])) + { + // Set the save variable. + $profile_vars[$db_key] = $_POST[$key]; + // And update the user profile. + $cur_profile[$key] = $_POST[$key]; + + // Are we logging it? + if (!empty($field['log_change']) && isset($old_profile[$key])) + $context['log_changes'][$key] = array( + 'previous' => $old_profile[$key], + 'new' => $_POST[$key], + ); + } + + // Logging group changes are a bit different... + if ($key == 'id_group' && $field['log_change']) + { + profileLoadGroups(); + + // Any changes to primary group? + if ($_POST['id_group'] != $old_profile['id_group']) + { + $context['log_changes']['id_group'] = array( + 'previous' => !empty($old_profile[$key]) && isset($context['member_groups'][$old_profile[$key]]) ? $context['member_groups'][$old_profile[$key]]['name'] : '', + 'new' => !empty($_POST[$key]) && isset($context['member_groups'][$_POST[$key]]) ? $context['member_groups'][$_POST[$key]]['name'] : '', + ); + } + + // Prepare additional groups for comparison. + $additional_groups = array( + 'previous' => !empty($old_profile['additional_groups']) ? explode(',', $old_profile['additional_groups']) : array(), + 'new' => !empty($_POST['additional_groups']) ? array_diff($_POST['additional_groups'], array(0)) : array(), + ); + + sort($additional_groups['previous']); + sort($additional_groups['new']); + + // What about additional groups? + if ($additional_groups['previous'] != $additional_groups['new']) + { + foreach ($additional_groups as $type => $groups) + { + foreach ($groups as $id => $group) + { + if (isset($context['member_groups'][$group])) + $additional_groups[$type][$id] = $context['member_groups'][$group]['name']; + else + unset($additional_groups[$type][$id]); + } + $additional_groups[$type] = implode(', ', $additional_groups[$type]); + } + + $context['log_changes']['additional_groups'] = $additional_groups; + } + } + } + + //!!! Temporary + if ($context['user']['is_owner']) + $changeOther = allowedTo(array('profile_extra_any', 'profile_extra_own')); + else + $changeOther = allowedTo('profile_extra_any'); + if ($changeOther && empty($post_errors)) + { + makeThemeChanges($context['id_member'], isset($_POST['id_theme']) ? (int) $_POST['id_theme'] : $old_profile['id_theme']); + if (!empty($_REQUEST['sa'])) + makeCustomFieldChanges($context['id_member'], $_REQUEST['sa'], false); + } + + // Free memory! + unset($profile_fields); +} + +// Save the profile changes.... +function saveProfileChanges(&$profile_vars, &$post_errors, $memID) +{ + global $user_info, $txt, $modSettings, $user_profile; + global $context, $settings, $sourcedir; + global $smcFunc; + + // These make life easier.... + $old_profile = &$user_profile[$memID]; + + // Permissions... + if ($context['user']['is_owner']) + { + $changeIdentity = allowedTo(array('profile_identity_any', 'profile_identity_own')); + $changeOther = allowedTo(array('profile_extra_any', 'profile_extra_own')); + } + else + { + $changeIdentity = allowedTo('profile_identity_any'); + $changeOther = allowedTo('profile_extra_any'); + } + + // Arrays of all the changes - makes things easier. + $profile_bools = array( + 'notify_announcements', 'notify_send_body', + ); + $profile_ints = array( + 'notify_regularity', + 'notify_types', + ); + $profile_floats = array( + ); + $profile_strings = array( + 'buddy_list', + 'ignore_boards', + ); + + if (isset($_POST['sa']) && $_POST['sa'] == 'ignoreboards' && empty($_POST['ignore_brd'])) + $_POST['ignore_brd'] = array(); + + unset($_POST['ignore_boards']); // Whatever it is set to is a dirty fithy thing. Kinda like our minds. + if (isset($_POST['ignore_brd'])) + { + if (!is_array($_POST['ignore_brd'])) + $_POST['ignore_brd'] = array ($_POST['ignore_brd']); + + foreach ($_POST['ignore_brd'] as $k => $d) + { + $d = (int) $d; + if ($d != 0) + $_POST['ignore_brd'][$k] = $d; + else + unset($_POST['ignore_brd'][$k]); + } + $_POST['ignore_boards'] = implode(',', $_POST['ignore_brd']); + unset($_POST['ignore_brd']); + + } + + // Here's where we sort out all the 'other' values... + if ($changeOther) + { + makeThemeChanges($memID, isset($_POST['id_theme']) ? (int) $_POST['id_theme'] : $old_profile['id_theme']); + //makeAvatarChanges($memID, $post_errors); + makeNotificationChanges($memID); + if (!empty($_REQUEST['sa'])) + makeCustomFieldChanges($memID, $_REQUEST['sa'], false); + + foreach ($profile_bools as $var) + if (isset($_POST[$var])) + $profile_vars[$var] = empty($_POST[$var]) ? '0' : '1'; + foreach ($profile_ints as $var) + if (isset($_POST[$var])) + $profile_vars[$var] = $_POST[$var] != '' ? (int) $_POST[$var] : ''; + foreach ($profile_floats as $var) + if (isset($_POST[$var])) + $profile_vars[$var] = (float) $_POST[$var]; + foreach ($profile_strings as $var) + if (isset($_POST[$var])) + $profile_vars[$var] = $_POST[$var]; + } +} + +// Make any theme changes that are sent with the profile.. +function makeThemeChanges($memID, $id_theme) +{ + global $modSettings, $smcFunc, $context; + + $reservedVars = array( + 'actual_theme_url', + 'actual_images_url', + 'base_theme_dir', + 'base_theme_url', + 'default_images_url', + 'default_theme_dir', + 'default_theme_url', + 'default_template', + 'images_url', + 'number_recent_posts', + 'smiley_sets_default', + 'theme_dir', + 'theme_id', + 'theme_layers', + 'theme_templates', + 'theme_url', + ); + + // Can't change reserved vars. + if ((isset($_POST['options']) && array_intersect($_POST['options'], $reservedVars) != array()) || (isset($_POST['default_options']) && array_intersect($_POST['default_options'], $reservedVars) != array())) + fatal_lang_error('no_access', false); + + // Don't allow any overriding of custom fields with default or non-default options. + $request = $smcFunc['db_query']('', ' + SELECT col_name + FROM {db_prefix}custom_fields + WHERE active = {int:is_active}', + array( + 'is_active' => 1, + ) + ); + $custom_fields = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $custom_fields[] = $row['col_name']; + $smcFunc['db_free_result']($request); + + // These are the theme changes... + $themeSetArray = array(); + if (isset($_POST['options']) && is_array($_POST['options'])) + { + foreach ($_POST['options'] as $opt => $val) + { + if (in_array($opt, $custom_fields)) + continue; + + // These need to be controlled. + if ($opt == 'topics_per_page' || $opt == 'messages_per_page') + $val = max(0, min($val, 50)); + + $themeSetArray[] = array($memID, $id_theme, $opt, is_array($val) ? implode(',', $val) : $val); + } + } + + $erase_options = array(); + if (isset($_POST['default_options']) && is_array($_POST['default_options'])) + foreach ($_POST['default_options'] as $opt => $val) + { + if (in_array($opt, $custom_fields)) + continue; + + // These need to be controlled. + if ($opt == 'topics_per_page' || $opt == 'messages_per_page') + $val = max(0, min($val, 50)); + + $themeSetArray[] = array($memID, 1, $opt, is_array($val) ? implode(',', $val) : $val); + $erase_options[] = $opt; + } + + // If themeSetArray isn't still empty, send it to the database. + if (empty($context['password_auth_failed'])) + { + if (!empty($themeSetArray)) + { + $smcFunc['db_insert']('replace', + '{db_prefix}themes', + array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'), + $themeSetArray, + array('id_member', 'id_theme', 'variable') + ); + } + + if (!empty($erase_options)) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}themes + WHERE id_theme != {int:id_theme} + AND variable IN ({array_string:erase_variables}) + AND id_member = {int:id_member}', + array( + 'id_theme' => 1, + 'id_member' => $memID, + 'erase_variables' => $erase_options + ) + ); + } + + $themes = explode(',', $modSettings['knownThemes']); + foreach ($themes as $t) + cache_put_data('theme_settings-' . $t . ':' . $memID, null, 60); + } +} + +// Make any notification changes that need to be made. +function makeNotificationChanges($memID) +{ + global $smcFunc; + + // Update the boards they are being notified on. + if (isset($_POST['edit_notify_boards']) && !empty($_POST['notify_boards'])) + { + // Make sure only integers are deleted. + foreach ($_POST['notify_boards'] as $index => $id) + $_POST['notify_boards'][$index] = (int) $id; + + // id_board = 0 is reserved for topic notifications. + $_POST['notify_boards'] = array_diff($_POST['notify_boards'], array(0)); + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_notify + WHERE id_board IN ({array_int:board_list}) + AND id_member = {int:selected_member}', + array( + 'board_list' => $_POST['notify_boards'], + 'selected_member' => $memID, + ) + ); + } + + // We are editing topic notifications...... + elseif (isset($_POST['edit_notify_topics']) && !empty($_POST['notify_topics'])) + { + foreach ($_POST['notify_topics'] as $index => $id) + $_POST['notify_topics'][$index] = (int) $id; + + // Make sure there are no zeros left. + $_POST['notify_topics'] = array_diff($_POST['notify_topics'], array(0)); + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_notify + WHERE id_topic IN ({array_int:topic_list}) + AND id_member = {int:selected_member}', + array( + 'topic_list' => $_POST['notify_topics'], + 'selected_member' => $memID, + ) + ); + } +} + +// Save any changes to the custom profile fields... +function makeCustomFieldChanges($memID, $area, $sanitize = true) +{ + global $context, $smcFunc, $user_profile, $user_info, $modSettings; + + if ($sanitize && isset($_POST['customfield'])) + $_POST['customfield'] = htmlspecialchars__recursive($_POST['customfield']); + + $where = $area == 'register' ? 'show_reg != 0' : 'show_profile = {string:area}'; + + // Load the fields we are saving too - make sure we save valid data (etc). + $request = $smcFunc['db_query']('', ' + SELECT col_name, field_name, field_desc, field_type, field_length, field_options, default_value, show_reg, mask, private + FROM {db_prefix}custom_fields + WHERE ' . $where . ' + AND active = {int:is_active}', + array( + 'is_active' => 1, + 'area' => $area, + ) + ); + $changes = array(); + $log_changes = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + /* This means don't save if: + - The user is NOT an admin. + - The data is not freely viewable and editable by users. + - The data is not invisible to users but editable by the owner (or if it is the user is not the owner) + - The area isn't registration, and if it is that the field is not suppossed to be shown there. + */ + if ($row['private'] != 0 && !allowedTo('admin_forum') && ($memID != $user_info['id'] || $row['private'] != 2) && ($area != 'register' || $row['show_reg'] == 0)) + continue; + + // Validate the user data. + if ($row['field_type'] == 'check') + $value = isset($_POST['customfield'][$row['col_name']]) ? 1 : 0; + elseif ($row['field_type'] == 'select' || $row['field_type'] == 'radio') + { + $value = $row['default_value']; + foreach (explode(',', $row['field_options']) as $k => $v) + if (isset($_POST['customfield'][$row['col_name']]) && $_POST['customfield'][$row['col_name']] == $k) + $value = $v; + } + // Otherwise some form of text! + else + { + $value = isset($_POST['customfield'][$row['col_name']]) ? $_POST['customfield'][$row['col_name']] : ''; + if ($row['field_length']) + $value = $smcFunc['substr']($value, 0, $row['field_length']); + + // Any masks? + if ($row['field_type'] == 'text' && !empty($row['mask']) && $row['mask'] != 'none') + { + //!!! We never error on this - just ignore it at the moment... + if ($row['mask'] == 'email' && (preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $value) === 0 || strlen($value) > 255)) + $value = ''; + elseif ($row['mask'] == 'number') + { + $value = (int) $value; + } + elseif (substr($row['mask'], 0, 5) == 'regex' && trim($value) != '' && preg_match(substr($row['mask'], 5), $value) === 0) + $value = ''; + } + } + + // Did it change? + if (!isset($user_profile[$memID]['options'][$row['col_name']]) || $user_profile[$memID]['options'][$row['col_name']] != $value) + { + $log_changes[] = array( + 'action' => 'customfield_' . $row['col_name'], + 'id_log' => 2, + 'log_time' => time(), + 'id_member' => $memID, + 'ip' => $user_info['ip'], + 'extra' => serialize(array('previous' => !empty($user_profile[$memID]['options'][$row['col_name']]) ? $user_profile[$memID]['options'][$row['col_name']] : '', 'new' => $value, 'applicator' => $user_info['id'])), + ); + $changes[] = array(1, $row['col_name'], $value, $memID); + $user_profile[$memID]['options'][$row['col_name']] = $value; + } + } + $smcFunc['db_free_result']($request); + + // Make those changes! + if (!empty($changes) && empty($context['password_auth_failed'])) + { + $smcFunc['db_insert']('replace', + '{db_prefix}themes', + array('id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534', 'id_member' => 'int'), + $changes, + array('id_theme', 'variable', 'id_member') + ); + if (!empty($log_changes) && !empty($modSettings['modlog_enabled'])) + $smcFunc['db_insert']('', + '{db_prefix}log_actions', + array( + 'action' => 'string', 'id_log' => 'int', 'log_time' => 'int', 'id_member' => 'int', 'ip' => 'string-16', + 'extra' => 'string-65534', + ), + $log_changes, + array('id_action') + ); + } +} + +// Show all the users buddies, as well as a add/delete interface. +function editBuddyIgnoreLists($memID) +{ + global $sourcedir, $context, $txt, $scripturl, $modSettings, $user_profile; + + // Do a quick check to ensure people aren't getting here illegally! + if (!$context['user']['is_owner'] || empty($modSettings['enable_buddylist'])) + fatal_lang_error('no_access', false); + + // Can we email the user direct? + $context['can_moderate_forum'] = allowedTo('moderate_forum'); + + $subActions = array( + 'buddies' => array('editBuddies', $txt['editBuddies']), + 'ignore' => array('editIgnoreList', $txt['editIgnoreList']), + ); + + $context['list_area'] = isset($_GET['sa']) && isset($subActions[$_GET['sa']]) ? $_GET['sa'] : 'buddies'; + + // Create the tabs for the template. + $context[$context['profile_menu_name']]['tab_data'] = array( + 'title' => $txt['editBuddyIgnoreLists'], + 'description' => $txt['buddy_ignore_desc'], + 'icon' => 'profile_sm.gif', + 'tabs' => array( + 'buddies' => array(), + 'ignore' => array(), + ), + ); + + // Pass on to the actual function. + $context['sub_template'] = $subActions[$context['list_area']][0]; + $subActions[$context['list_area']][0]($memID); +} + +// Show all the users buddies, as well as a add/delete interface. +function editBuddies($memID) +{ + global $txt, $scripturl, $modSettings; + global $context, $user_profile, $memberContext, $smcFunc; + + // For making changes! + $buddiesArray = explode(',', $user_profile[$memID]['buddy_list']); + foreach ($buddiesArray as $k => $dummy) + if ($dummy == '') + unset($buddiesArray[$k]); + + // Removing a buddy? + if (isset($_GET['remove'])) + { + checkSession('get'); + + // Heh, I'm lazy, do it the easy way... + foreach ($buddiesArray as $key => $buddy) + if ($buddy == (int) $_GET['remove']) + unset($buddiesArray[$key]); + + // Make the changes. + $user_profile[$memID]['buddy_list'] = implode(',', $buddiesArray); + updateMemberData($memID, array('buddy_list' => $user_profile[$memID]['buddy_list'])); + + // Redirect off the page because we don't like all this ugly query stuff to stick in the history. + redirectexit('action=profile;area=lists;sa=buddies;u=' . $memID); + } + elseif (isset($_POST['new_buddy'])) + { + // Prepare the string for extraction... + $_POST['new_buddy'] = strtr($smcFunc['htmlspecialchars']($_POST['new_buddy'], ENT_QUOTES), array('"' => '"')); + preg_match_all('~"([^"]+)"~', $_POST['new_buddy'], $matches); + $new_buddies = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $_POST['new_buddy'])))); + + foreach ($new_buddies as $k => $dummy) + { + $new_buddies[$k] = strtr(trim($new_buddies[$k]), array('\'' => ''')); + + if (strlen($new_buddies[$k]) == 0 || in_array($new_buddies[$k], array($user_profile[$memID]['member_name'], $user_profile[$memID]['real_name']))) + unset($new_buddies[$k]); + } + + if (!empty($new_buddies)) + { + // Now find out the id_member of the buddy. + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE member_name IN ({array_string:new_buddies}) OR real_name IN ({array_string:new_buddies}) + LIMIT {int:count_new_buddies}', + array( + 'new_buddies' => $new_buddies, + 'count_new_buddies' => count($new_buddies), + ) + ); + + // Add the new member to the buddies array. + while ($row = $smcFunc['db_fetch_assoc']($request)) + $buddiesArray[] = (int) $row['id_member']; + $smcFunc['db_free_result']($request); + + // Now update the current users buddy list. + $user_profile[$memID]['buddy_list'] = implode(',', $buddiesArray); + updateMemberData($memID, array('buddy_list' => $user_profile[$memID]['buddy_list'])); + } + + // Back to the buddy list! + redirectexit('action=profile;area=lists;sa=buddies;u=' . $memID); + } + + // Get all the users "buddies"... + $buddies = array(); + + if (!empty($buddiesArray)) + { + $result = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE id_member IN ({array_int:buddy_list}) + ORDER BY real_name + LIMIT {int:buddy_list_count}', + array( + 'buddy_list' => $buddiesArray, + 'buddy_list_count' => substr_count($user_profile[$memID]['buddy_list'], ',') + 1, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + $buddies[] = $row['id_member']; + $smcFunc['db_free_result']($result); + } + + $context['buddy_count'] = count($buddies); + + // Load all the members up. + loadMemberData($buddies, false, 'profile'); + + // Setup the context for each buddy. + $context['buddies'] = array(); + foreach ($buddies as $buddy) + { + loadMemberContext($buddy); + $context['buddies'][$buddy] = $memberContext[$buddy]; + } +} + +// Allows the user to view their ignore list, as well as the option to manage members on it. +function editIgnoreList($memID) +{ + global $txt, $scripturl, $modSettings; + global $context, $user_profile, $memberContext, $smcFunc; + + // For making changes! + $ignoreArray = explode(',', $user_profile[$memID]['pm_ignore_list']); + foreach ($ignoreArray as $k => $dummy) + if ($dummy == '') + unset($ignoreArray[$k]); + + // Removing a member from the ignore list? + if (isset($_GET['remove'])) + { + checkSession('get'); + + // Heh, I'm lazy, do it the easy way... + foreach ($ignoreArray as $key => $id_remove) + if ($id_remove == (int) $_GET['remove']) + unset($ignoreArray[$key]); + + // Make the changes. + $user_profile[$memID]['pm_ignore_list'] = implode(',', $ignoreArray); + updateMemberData($memID, array('pm_ignore_list' => $user_profile[$memID]['pm_ignore_list'])); + + // Redirect off the page because we don't like all this ugly query stuff to stick in the history. + redirectexit('action=profile;area=lists;sa=ignore;u=' . $memID); + } + elseif (isset($_POST['new_ignore'])) + { + // Prepare the string for extraction... + $_POST['new_ignore'] = strtr($smcFunc['htmlspecialchars']($_POST['new_ignore'], ENT_QUOTES), array('"' => '"')); + preg_match_all('~"([^"]+)"~', $_POST['new_ignore'], $matches); + $new_entries = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $_POST['new_ignore'])))); + + foreach ($new_entries as $k => $dummy) + { + $new_entries[$k] = strtr(trim($new_entries[$k]), array('\'' => ''')); + + if (strlen($new_entries[$k]) == 0 || in_array($new_entries[$k], array($user_profile[$memID]['member_name'], $user_profile[$memID]['real_name']))) + unset($new_entries[$k]); + } + + if (!empty($new_entries)) + { + // Now find out the id_member for the members in question. + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE member_name IN ({array_string:new_entries}) OR real_name IN ({array_string:new_entries}) + LIMIT {int:count_new_entries}', + array( + 'new_entries' => $new_entries, + 'count_new_entries' => count($new_entries), + ) + ); + + // Add the new member to the buddies array. + while ($row = $smcFunc['db_fetch_assoc']($request)) + $ignoreArray[] = (int) $row['id_member']; + $smcFunc['db_free_result']($request); + + // Now update the current users buddy list. + $user_profile[$memID]['pm_ignore_list'] = implode(',', $ignoreArray); + updateMemberData($memID, array('pm_ignore_list' => $user_profile[$memID]['pm_ignore_list'])); + } + + // Back to the list of pityful people! + redirectexit('action=profile;area=lists;sa=ignore;u=' . $memID); + } + + // Initialise the list of members we're ignoring. + $ignored = array(); + + if (!empty($ignoreArray)) + { + $result = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE id_member IN ({array_int:ignore_list}) + ORDER BY real_name + LIMIT {int:ignore_list_count}', + array( + 'ignore_list' => $ignoreArray, + 'ignore_list_count' => substr_count($user_profile[$memID]['pm_ignore_list'], ',') + 1, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + $ignored[] = $row['id_member']; + $smcFunc['db_free_result']($result); + } + + $context['ignore_count'] = count($ignored); + + // Load all the members up. + loadMemberData($ignored, false, 'profile'); + + // Setup the context for each buddy. + $context['ignore_list'] = array(); + foreach ($ignored as $ignore_member) + { + loadMemberContext($ignore_member); + $context['ignore_list'][$ignore_member] = $memberContext[$ignore_member]; + } +} + +function account($memID) +{ + global $context, $txt; + + loadThemeOptions($memID); + if (allowedTo(array('profile_identity_own', 'profile_identity_any'))) + loadCustomFields($memID, 'account'); + + $context['sub_template'] = 'edit_options'; + $context['page_desc'] = $txt['account_info']; + + setupProfileContext( + array( + 'member_name', 'real_name', 'date_registered', 'posts', 'lngfile', 'hr', + 'id_group', 'hr', + 'email_address', 'hide_email', 'show_online', 'hr', + 'passwrd1', 'passwrd2', 'hr', + 'secret_question', 'secret_answer', + ) + ); +} + +function forumProfile($memID) +{ + global $context, $user_profile, $user_info, $txt, $modSettings; + + loadThemeOptions($memID); + if (allowedTo(array('profile_extra_own', 'profile_extra_any'))) + loadCustomFields($memID, 'forumprofile'); + + $context['sub_template'] = 'edit_options'; + $context['page_desc'] = $txt['forumProfile_info']; + + setupProfileContext( + array( + 'avatar_choice', 'hr', 'personal_text', 'hr', + 'bday1', 'location', 'gender', 'hr', + 'icq', 'aim', 'msn', 'yim', 'hr', + 'usertitle', 'signature', 'hr', + 'karma_good', 'hr', + 'website_title', 'website_url', + ) + ); +} + +// Allow the edit of *someone elses* personal message settings. +function pmprefs($memID) +{ + global $sourcedir, $context, $txt, $scripturl; + + loadThemeOptions($memID); + loadCustomFields($memID, 'pmprefs'); + + $context['sub_template'] = 'edit_options'; + $context['page_desc'] = $txt['pm_settings_desc']; + + setupProfileContext( + array( + 'pm_prefs', + ) + ); +} + +// Recursive function to retrieve avatar files +function getAvatars($directory, $level) +{ + global $context, $txt, $modSettings; + + $result = array(); + + // Open the directory.. + $dir = dir($modSettings['avatar_directory'] . (!empty($directory) ? '/' : '') . $directory); + $dirs = array(); + $files = array(); + + if (!$dir) + return array(); + + while ($line = $dir->read()) + { + if (in_array($line, array('.', '..', 'blank.gif', 'index.php'))) + continue; + + if (is_dir($modSettings['avatar_directory'] . '/' . $directory . (!empty($directory) ? '/' : '') . $line)) + $dirs[] = $line; + else + $files[] = $line; + } + $dir->close(); + + // Sort the results... + natcasesort($dirs); + natcasesort($files); + + if ($level == 0) + { + $result[] = array( + 'filename' => 'blank.gif', + 'checked' => in_array($context['member']['avatar']['server_pic'], array('', 'blank.gif')), + 'name' => $txt['no_pic'], + 'is_dir' => false + ); + } + + foreach ($dirs as $line) + { + $tmp = getAvatars($directory . (!empty($directory) ? '/' : '') . $line, $level + 1); + if (!empty($tmp)) + $result[] = array( + 'filename' => htmlspecialchars($line), + 'checked' => strpos($context['member']['avatar']['server_pic'], $line . '/') !== false, + 'name' => '[' . htmlspecialchars(str_replace('_', ' ', $line)) . ']', + 'is_dir' => true, + 'files' => $tmp + ); + unset($tmp); + } + + foreach ($files as $line) + { + $filename = substr($line, 0, (strlen($line) - strlen(strrchr($line, '.')))); + $extension = substr(strrchr($line, '.'), 1); + + // Make sure it is an image. + if (strcasecmp($extension, 'gif') != 0 && strcasecmp($extension, 'jpg') != 0 && strcasecmp($extension, 'jpeg') != 0 && strcasecmp($extension, 'png') != 0 && strcasecmp($extension, 'bmp') != 0) + continue; + + $result[] = array( + 'filename' => htmlspecialchars($line), + 'checked' => $line == $context['member']['avatar']['server_pic'], + 'name' => htmlspecialchars(str_replace('_', ' ', $filename)), + 'is_dir' => false + ); + if ($level == 1) + $context['avatar_list'][] = $directory . '/' . $line; + } + + return $result; +} + +function theme($memID) +{ + global $txt, $context, $user_profile, $modSettings, $settings, $user_info, $smcFunc; + + loadThemeOptions($memID); + if (allowedTo(array('profile_extra_own', 'profile_extra_any'))) + loadCustomFields($memID, 'theme'); + + $context['sub_template'] = 'edit_options'; + $context['page_desc'] = $txt['theme_info']; + + setupProfileContext( + array( + 'id_theme', 'smiley_set', 'hr', + 'time_format', 'time_offset', 'hr', + 'theme_settings', + ) + ); +} + +// Changing authentication method? Only appropriate for people using OpenID. +function authentication($memID, $saving = false) +{ + global $context, $cur_profile, $sourcedir, $txt, $post_errors, $modSettings; + + loadLanguage('Login'); + + // We are saving? + if ($saving) + { + // Moving to password passed authentication? + if ($_POST['authenticate'] == 'passwd') + { + // Didn't enter anything? + if ($_POST['passwrd1'] == '') + $post_errors[] = 'no_password'; + // Do the two entries for the password even match? + elseif (!isset($_POST['passwrd2']) || $_POST['passwrd1'] != $_POST['passwrd2']) + $post_errors[] = 'bad_new_password'; + // Is it valid? + else + { + require_once($sourcedir . '/Subs-Auth.php'); + $passwordErrors = validatePassword($_POST['passwrd1'], $cur_profile['member_name'], array($cur_profile['real_name'], $cur_profile['email_address'])); + + // Were there errors? + if ($passwordErrors != null) + $post_errors[] = 'password_' . $passwordErrors; + } + + if (empty($post_errors)) + { + // Integration? + call_integration_hook('integrate_reset_pass', array($cur_profile['member_name'], $cur_profile['member_name'], $_POST['passwrd1'])); + + // Go then. + $passwd = sha1(strtolower($cur_profile['member_name']) . un_htmlspecialchars($_POST['passwrd1'])); + + // Do the important bits. + updateMemberData($memID, array('openid_uri' => '', 'passwd' => $passwd)); + if ($context['user']['is_owner']) + setLoginCookie(60 * $modSettings['cookieTime'], $memID, sha1(sha1(strtolower($cur_profile['member_name']) . un_htmlspecialchars($_POST['passwrd2'])) . $cur_profile['password_salt'])); + + redirectexit('action=profile;u=' . $memID); + } + + return true; + } + // Not right yet! + elseif ($_POST['authenticate'] == 'openid' && !empty($_POST['openid_identifier'])) + { + require_once($sourcedir . '/Subs-OpenID.php'); + $_POST['openid_identifier'] = smf_openID_canonize($_POST['openid_identifier']); + + if (smf_openid_member_exists($_POST['openid_identifier'])) + $post_errors[] = 'openid_in_use'; + elseif (empty($post_errors)) + { + // Authenticate using the new OpenID URI first to make sure they didn't make a mistake. + if ($context['user']['is_owner']) + { + $_SESSION['new_openid_uri'] = $_POST['openid_identifier']; + + smf_openID_validate($_POST['openid_identifier'], false, null, 'change_uri'); + } + else + updateMemberData($memID, array('openid_uri' => $_POST['openid_identifier'])); + } + } + } + + // Some stuff. + $context['member']['openid_uri'] = $cur_profile['openid_uri']; + $context['auth_method'] = empty($cur_profile['openid_uri']) ? 'password' : 'openid'; + $context['sub_template'] = 'authentication_method'; +} + +// Display the notifications and settings for changes. +function notification($memID) +{ + global $txt, $scripturl, $user_profile, $user_info, $context, $modSettings, $smcFunc, $sourcedir, $settings; + + // Gonna want this for the list. + require_once($sourcedir . '/Subs-List.php'); + + // Fine, start with the board list. + $listOptions = array( + 'id' => 'board_notification_list', + 'width' => '100%', + 'no_items_label' => $txt['notifications_boards_none'] . '

' . $txt['notifications_boards_howto'], + 'no_items_align' => 'left', + 'base_href' => $scripturl . '?action=profile;u=' . $memID . ';area=notification', + 'default_sort_col' => 'board_name', + 'get_items' => array( + 'function' => 'list_getBoardNotifications', + 'params' => array( + $memID, + ), + ), + 'columns' => array( + 'board_name' => array( + 'header' => array( + 'value' => $txt['notifications_boards'], + 'class' => 'lefttext first_th', + ), + 'data' => array( + 'function' => create_function('$board', ' + global $settings, $txt; + + $link = $board[\'link\']; + + if ($board[\'new\']) + $link .= \' \' . $txt[\'new\'] . \'\'; + + return $link; + '), + ), + 'sort' => array( + 'default' => 'name', + 'reverse' => 'name DESC', + ), + ), + 'delete' => array( + 'header' => array( + 'value' => '', + 'style' => 'width: 4%;', + ), + 'data' => array( + 'sprintf' => array( + 'format' => '', + 'params' => array( + 'id' => false, + ), + ), + 'style' => 'text-align: center;', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=profile;area=notification;save', + 'include_sort' => true, + 'include_start' => true, + 'hidden_fields' => array( + 'u' => $memID, + 'sa' => $context['menu_item_selected'], + $context['session_var'] => $context['session_id'], + ), + ), + 'additional_rows' => array( + array( + 'position' => 'bottom_of_list', + 'value' => '', + 'align' => 'right', + ), + ), + ); + + // Create the board notification list. + createList($listOptions); + + // Now do the topic notifications. + $listOptions = array( + 'id' => 'topic_notification_list', + 'width' => '100%', + 'items_per_page' => $modSettings['defaultMaxMessages'], + 'no_items_label' => $txt['notifications_topics_none'] . '

' . $txt['notifications_topics_howto'], + 'no_items_align' => 'left', + 'base_href' => $scripturl . '?action=profile;u=' . $memID . ';area=notification', + 'default_sort_col' => 'last_post', + 'get_items' => array( + 'function' => 'list_getTopicNotifications', + 'params' => array( + $memID, + ), + ), + 'get_count' => array( + 'function' => 'list_getTopicNotificationCount', + 'params' => array( + $memID, + ), + ), + 'columns' => array( + 'subject' => array( + 'header' => array( + 'value' => $txt['notifications_topics'], + 'class' => 'lefttext first_th', + ), + 'data' => array( + 'function' => create_function('$topic', ' + global $settings, $txt; + + $link = $topic[\'link\']; + + if ($topic[\'new\']) + $link .= \' \' . $txt[\'new\'] . \'\'; + + $link .= \'
\' . $txt[\'in\'] . \' \' . $topic[\'board_link\'] . \'\'; + + return $link; + '), + ), + 'sort' => array( + 'default' => 'ms.subject', + 'reverse' => 'ms.subject DESC', + ), + ), + 'started_by' => array( + 'header' => array( + 'value' => $txt['started_by'], + 'class' => 'lefttext', + ), + 'data' => array( + 'db' => 'poster_link', + ), + 'sort' => array( + 'default' => 'real_name_col', + 'reverse' => 'real_name_col DESC', + ), + ), + 'last_post' => array( + 'header' => array( + 'value' => $txt['last_post'], + 'class' => 'lefttext', + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%1$s
' . $txt['by'] . ' %2$s
', + 'params' => array( + 'updated' => false, + 'poster_updated_link' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'ml.id_msg DESC', + 'reverse' => 'ml.id_msg', + ), + ), + 'delete' => array( + 'header' => array( + 'value' => '', + 'style' => 'width: 4%;', + ), + 'data' => array( + 'sprintf' => array( + 'format' => '', + 'params' => array( + 'id' => false, + ), + ), + 'style' => 'text-align: center;', + ), + ), + ), + 'form' => array( + 'href' => $scripturl . '?action=profile;area=notification;save', + 'include_sort' => true, + 'include_start' => true, + 'hidden_fields' => array( + 'u' => $memID, + 'sa' => $context['menu_item_selected'], + $context['session_var'] => $context['session_id'], + ), + ), + 'additional_rows' => array( + array( + 'position' => 'bottom_of_list', + 'value' => '', + 'align' => 'right', + ), + ), + ); + + // Create the notification list. + createList($listOptions); + + // What options are set? + $context['member'] += array( + 'notify_announcements' => $user_profile[$memID]['notify_announcements'], + 'notify_send_body' => $user_profile[$memID]['notify_send_body'], + 'notify_types' => $user_profile[$memID]['notify_types'], + 'notify_regularity' => $user_profile[$memID]['notify_regularity'], + ); + + loadThemeOptions($memID); +} + +function list_getTopicNotificationCount($memID) +{ + global $smcFunc, $user_info, $context, $modSettings; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_notify AS ln' . (!$modSettings['postmod_active'] && $user_info['query_see_board'] === '1=1' ? '' : ' + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ln.id_topic)') . ($user_info['query_see_board'] === '1=1' ? '' : ' + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)') . ' + WHERE ln.id_member = {int:selected_member}' . ($user_info['query_see_board'] === '1=1' ? '' : ' + AND {query_see_board}') . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved}' : ''), + array( + 'selected_member' => $memID, + 'is_approved' => 1, + ) + ); + list ($totalNotifications) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $totalNotifications; +} + +function list_getTopicNotifications($start, $items_per_page, $sort, $memID) +{ + global $smcFunc, $txt, $scripturl, $user_info, $context, $modSettings; + + // All the topics with notification on... + $request = $smcFunc['db_query']('', ' + SELECT + IFNULL(lt.id_msg, IFNULL(lmr.id_msg, -1)) + 1 AS new_from, b.id_board, b.name, + t.id_topic, ms.subject, ms.id_member, IFNULL(mem.real_name, ms.poster_name) AS real_name_col, + ml.id_msg_modified, ml.poster_time, ml.id_member AS id_member_updated, + IFNULL(mem2.real_name, ml.poster_name) AS last_real_name + FROM {db_prefix}log_notify AS ln + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ln.id_topic' . ($modSettings['postmod_active'] ? ' AND t.approved = {int:is_approved}' : '') . ') + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board AND {query_see_board}) + INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg) + INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = ms.id_member) + LEFT JOIN {db_prefix}members AS mem2 ON (mem2.id_member = ml.id_member) + LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member}) + LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = b.id_board AND lmr.id_member = {int:current_member}) + WHERE ln.id_member = {int:selected_member} + ORDER BY {raw:sort} + LIMIT {int:offset}, {int:items_per_page}', + array( + 'current_member' => $user_info['id'], + 'is_approved' => 1, + 'selected_member' => $memID, + 'sort' => $sort, + 'offset' => $start, + 'items_per_page' => $items_per_page, + ) + ); + $notification_topics = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + censorText($row['subject']); + + $notification_topics[] = array( + 'id' => $row['id_topic'], + 'poster_link' => empty($row['id_member']) ? $row['real_name_col'] : '' . $row['real_name_col'] . '', + 'poster_updated_link' => empty($row['id_member_updated']) ? $row['last_real_name'] : '' . $row['last_real_name'] . '', + 'subject' => $row['subject'], + 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.0', + 'link' => '' . $row['subject'] . '', + 'new' => $row['new_from'] <= $row['id_msg_modified'], + 'new_from' => $row['new_from'], + 'updated' => timeformat($row['poster_time']), + 'new_href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['new_from'] . '#new', + 'new_link' => '' . $row['subject'] . '', + 'board_link' => '' . $row['name'] . '', + ); + } + $smcFunc['db_free_result']($request); + + return $notification_topics; +} + +function list_getBoardNotifications($start, $items_per_page, $sort, $memID) +{ + global $smcFunc, $txt, $scripturl, $user_info; + + $request = $smcFunc['db_query']('', ' + SELECT b.id_board, b.name, IFNULL(lb.id_msg, 0) AS board_read, b.id_msg_updated + FROM {db_prefix}log_notify AS ln + INNER JOIN {db_prefix}boards AS b ON (b.id_board = ln.id_board) + LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member}) + WHERE ln.id_member = {int:selected_member} + AND {query_see_board} + ORDER BY ' . $sort, + array( + 'current_member' => $user_info['id'], + 'selected_member' => $memID, + ) + ); + $notification_boards = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $notification_boards[] = array( + 'id' => $row['id_board'], + 'name' => $row['name'], + 'href' => $scripturl . '?board=' . $row['id_board'] . '.0', + 'link' => '' . $row['name'] . '', + 'new' => $row['board_read'] < $row['id_msg_updated'] + ); + $smcFunc['db_free_result']($request); + + return $notification_boards; +} + +function loadThemeOptions($memID) +{ + global $context, $options, $cur_profile, $smcFunc; + + if (isset($_POST['default_options'])) + $_POST['options'] = isset($_POST['options']) ? $_POST['options'] + $_POST['default_options'] : $_POST['default_options']; + + if ($context['user']['is_owner']) + { + $context['member']['options'] = $options; + if (isset($_POST['options']) && is_array($_POST['options'])) + foreach ($_POST['options'] as $k => $v) + $context['member']['options'][$k] = $v; + } + else + { + $request = $smcFunc['db_query']('', ' + SELECT id_member, variable, value + FROM {db_prefix}themes + WHERE id_theme IN (1, {int:member_theme}) + AND id_member IN (-1, {int:selected_member})', + array( + 'member_theme' => (int) $cur_profile['id_theme'], + 'selected_member' => $memID, + ) + ); + $temp = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['id_member'] == -1) + { + $temp[$row['variable']] = $row['value']; + continue; + } + + if (isset($_POST['options'][$row['variable']])) + $row['value'] = $_POST['options'][$row['variable']]; + $context['member']['options'][$row['variable']] = $row['value']; + } + $smcFunc['db_free_result']($request); + + // Load up the default theme options for any missing. + foreach ($temp as $k => $v) + { + if (!isset($context['member']['options'][$k])) + $context['member']['options'][$k] = $v; + } + } +} + +function ignoreboards($memID) +{ + global $txt, $user_info, $context, $modSettings, $smcFunc, $cur_profile; + + // Have the admins enabled this option? + if (empty($modSettings['allow_ignore_boards'])) + fatal_lang_error('ignoreboards_disallowed', 'user'); + + // Find all the boards this user is allowed to see. + $request = $smcFunc['db_query']('order_by_board_order', ' + SELECT b.id_cat, c.name AS cat_name, b.id_board, b.name, b.child_level, + '. (!empty($cur_profile['ignore_boards']) ? 'b.id_board IN ({array_int:ignore_boards})' : '0') . ' AS is_ignored + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat) + WHERE {query_see_board} + AND redirect = {string:empty_string}', + array( + 'ignore_boards' => !empty($cur_profile['ignore_boards']) ? explode(',', $cur_profile['ignore_boards']) : array(), + 'empty_string' => '', + ) + ); + $context['num_boards'] = $smcFunc['db_num_rows']($request); + $context['categories'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // This category hasn't been set up yet.. + if (!isset($context['categories'][$row['id_cat']])) + $context['categories'][$row['id_cat']] = array( + 'id' => $row['id_cat'], + 'name' => $row['cat_name'], + 'boards' => array() + ); + + // Set this board up, and let the template know when it's a child. (indent them..) + $context['categories'][$row['id_cat']]['boards'][$row['id_board']] = array( + 'id' => $row['id_board'], + 'name' => $row['name'], + 'child_level' => $row['child_level'], + 'selected' => $row['is_ignored'], + ); + } + $smcFunc['db_free_result']($request); + + // Now, let's sort the list of categories into the boards for templates that like that. + $temp_boards = array(); + foreach ($context['categories'] as $category) + { + // Include a list of boards per category for easy toggling. + $context['categories'][$category['id']]['child_ids'] = array_keys($category['boards']); + + $temp_boards[] = array( + 'name' => $category['name'], + 'child_ids' => array_keys($category['boards']) + ); + $temp_boards = array_merge($temp_boards, array_values($category['boards'])); + } + + $max_boards = ceil(count($temp_boards) / 2); + if ($max_boards == 1) + $max_boards = 2; + + // Now, alternate them so they can be shown left and right ;). + $context['board_columns'] = array(); + for ($i = 0; $i < $max_boards; $i++) + { + $context['board_columns'][] = $temp_boards[$i]; + if (isset($temp_boards[$i + $max_boards])) + $context['board_columns'][] = $temp_boards[$i + $max_boards]; + else + $context['board_columns'][] = array(); + } + + loadThemeOptions($memID); +} + +// Load all the languages for the profile. +function profileLoadLanguages() +{ + global $context, $modSettings, $settings, $cur_profile, $language, $smcFunc; + + $context['profile_languages'] = array(); + + // Get our languages! + getLanguages(true, true); + + // Setup our languages. + foreach ($context['languages'] as $lang) + { + $context['profile_languages'][$lang['filename']] = strtr($lang['name'], array('-utf8' => '')); + } + ksort($context['profile_languages']); + + // Return whether we should proceed with this. + return count($context['profile_languages']) > 1 ? true : false; +} + +// Load all the group info for the profile. +function profileLoadGroups() +{ + global $cur_profile, $txt, $context, $smcFunc, $user_settings; + + $context['member_groups'] = array( + 0 => array( + 'id' => 0, + 'name' => $txt['no_primary_membergroup'], + 'is_primary' => $cur_profile['id_group'] == 0, + 'can_be_additional' => false, + 'can_be_primary' => true, + ) + ); + $curGroups = explode(',', $cur_profile['additional_groups']); + + // Load membergroups, but only those groups the user can assign. + $request = $smcFunc['db_query']('', ' + SELECT group_name, id_group, hidden + FROM {db_prefix}membergroups + WHERE id_group != {int:moderator_group} + AND min_posts = {int:min_posts}' . (allowedTo('admin_forum') ? '' : ' + AND group_type != {int:is_protected}') . ' + ORDER BY min_posts, CASE WHEN id_group < {int:newbie_group} THEN id_group ELSE 4 END, group_name', + array( + 'moderator_group' => 3, + 'min_posts' => -1, + 'is_protected' => 1, + 'newbie_group' => 4, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // We should skip the administrator group if they don't have the admin_forum permission! + if ($row['id_group'] == 1 && !allowedTo('admin_forum')) + continue; + + $context['member_groups'][$row['id_group']] = array( + 'id' => $row['id_group'], + 'name' => $row['group_name'], + 'is_primary' => $cur_profile['id_group'] == $row['id_group'], + 'is_additional' => in_array($row['id_group'], $curGroups), + 'can_be_additional' => true, + 'can_be_primary' => $row['hidden'] != 2, + ); + } + $smcFunc['db_free_result']($request); + + $context['member']['group_id'] = $user_settings['id_group']; + + return true; +} + +// Load key signature context data. +function profileLoadSignatureData() +{ + global $modSettings, $context, $txt, $cur_profile, $smcFunc; + + // Signature limits. + list ($sig_limits, $sig_bbc) = explode(':', $modSettings['signature_settings']); + $sig_limits = explode(',', $sig_limits); + + $context['signature_enabled'] = isset($sig_limits[0]) ? $sig_limits[0] : 0; + $context['signature_limits'] = array( + 'max_length' => isset($sig_limits[1]) ? $sig_limits[1] : 0, + 'max_lines' => isset($sig_limits[2]) ? $sig_limits[2] : 0, + 'max_images' => isset($sig_limits[3]) ? $sig_limits[3] : 0, + 'max_smileys' => isset($sig_limits[4]) ? $sig_limits[4] : 0, + 'max_image_width' => isset($sig_limits[5]) ? $sig_limits[5] : 0, + 'max_image_height' => isset($sig_limits[6]) ? $sig_limits[6] : 0, + 'max_font_size' => isset($sig_limits[7]) ? $sig_limits[7] : 0, + 'bbc' => !empty($sig_bbc) ? explode(',', $sig_bbc) : array(), + ); + // Kept this line in for backwards compatibility! + $context['max_signature_length'] = $context['signature_limits']['max_length']; + // Warning message for signature image limits? + $context['signature_warning'] = ''; + if ($context['signature_limits']['max_image_width'] && $context['signature_limits']['max_image_height']) + $context['signature_warning'] = sprintf($txt['profile_error_signature_max_image_size'], $context['signature_limits']['max_image_width'], $context['signature_limits']['max_image_height']); + elseif ($context['signature_limits']['max_image_width'] || $context['signature_limits']['max_image_height']) + $context['signature_warning'] = sprintf($txt['profile_error_signature_max_image_' . ($context['signature_limits']['max_image_width'] ? 'width' : 'height')], $context['signature_limits'][$context['signature_limits']['max_image_width'] ? 'max_image_width' : 'max_image_height']); + + $context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new'); + + $context['member']['signature'] = empty($cur_profile['signature']) ? '' : str_replace(array('
', '<', '>', '"', '\''), array("\n", '<', '>', '"', '''), $cur_profile['signature']); + + return true; +} + +// Load avatar context data. +function profileLoadAvatarData() +{ + global $context, $cur_profile, $modSettings, $scripturl; + + $context['avatar_url'] = $modSettings['avatar_url']; + + // Default context. + $context['member']['avatar'] += array( + 'custom' => stristr($cur_profile['avatar'], 'http://') ? $cur_profile['avatar'] : 'http://', + 'selection' => $cur_profile['avatar'] == '' || stristr($cur_profile['avatar'], 'http://') ? '' : $cur_profile['avatar'], + 'id_attach' => $cur_profile['id_attach'], + 'filename' => $cur_profile['filename'], + 'allow_server_stored' => allowedTo('profile_server_avatar') || (!$context['user']['is_owner'] && allowedTo('profile_extra_any')), + 'allow_upload' => allowedTo('profile_upload_avatar') || (!$context['user']['is_owner'] && allowedTo('profile_extra_any')), + 'allow_external' => allowedTo('profile_remote_avatar') || (!$context['user']['is_owner'] && allowedTo('profile_extra_any')), + ); + + if ($cur_profile['avatar'] == '' && $cur_profile['id_attach'] > 0 && $context['member']['avatar']['allow_upload']) + { + $context['member']['avatar'] += array( + 'choice' => 'upload', + 'server_pic' => 'blank.gif', + 'external' => 'http://' + ); + $context['member']['avatar']['href'] = empty($cur_profile['attachment_type']) ? $scripturl . '?action=dlattach;attach=' . $cur_profile['id_attach'] . ';type=avatar' : $modSettings['custom_avatar_url'] . '/' . $cur_profile['filename']; + } + elseif (stristr($cur_profile['avatar'], 'http://') && $context['member']['avatar']['allow_external']) + $context['member']['avatar'] += array( + 'choice' => 'external', + 'server_pic' => 'blank.gif', + 'external' => $cur_profile['avatar'] + ); + elseif ($cur_profile['avatar'] != '' && file_exists($modSettings['avatar_directory'] . '/' . $cur_profile['avatar']) && $context['member']['avatar']['allow_server_stored']) + $context['member']['avatar'] += array( + 'choice' => 'server_stored', + 'server_pic' => $cur_profile['avatar'] == '' ? 'blank.gif' : $cur_profile['avatar'], + 'external' => 'http://' + ); + else + $context['member']['avatar'] += array( + 'choice' => 'none', + 'server_pic' => 'blank.gif', + 'external' => 'http://' + ); + + // Get a list of all the avatars. + if ($context['member']['avatar']['allow_server_stored']) + { + $context['avatar_list'] = array(); + $context['avatars'] = is_dir($modSettings['avatar_directory']) ? getAvatars('', 0) : array(); + } + else + $context['avatars'] = array(); + + // Second level selected avatar... + $context['avatar_selected'] = substr(strrchr($context['member']['avatar']['server_pic'], '/'), 1); + return true; +} + +// Save a members group. +function profileSaveGroups(&$value) +{ + global $profile_vars, $old_profile, $context, $smcFunc, $cur_profile; + + // Do we need to protect some groups? + if (!allowedTo('admin_forum')) + { + $request = $smcFunc['db_query']('', ' + SELECT id_group + FROM {db_prefix}membergroups + WHERE group_type = {int:is_protected}', + array( + 'is_protected' => 1, + ) + ); + $protected_groups = array(1); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $protected_groups[] = $row['id_group']; + $smcFunc['db_free_result']($request); + + $protected_groups = array_unique($protected_groups); + } + + // The account page allows the change of your id_group - but not to a protected group! + if (empty($protected_groups) || count(array_intersect(array((int) $value, $old_profile['id_group']), $protected_groups)) == 0) + $value = (int) $value; + // ... otherwise it's the old group sir. + else + $value = $old_profile['id_group']; + + // Find the additional membergroups (if any) + if (isset($_POST['additional_groups']) && is_array($_POST['additional_groups'])) + { + $additional_groups = array(); + foreach ($_POST['additional_groups'] as $group_id) + { + $group_id = (int) $group_id; + if (!empty($group_id) && (empty($protected_groups) || !in_array($group_id, $protected_groups))) + $additional_groups[] = $group_id; + } + + // Put the protected groups back in there if you don't have permission to take them away. + $old_additional_groups = explode(',', $old_profile['additional_groups']); + foreach ($old_additional_groups as $group_id) + { + if (!empty($protected_groups) && in_array($group_id, $protected_groups)) + $additional_groups[] = $group_id; + } + + if (implode(',', $additional_groups) !== $old_profile['additional_groups']) + { + $profile_vars['additional_groups'] = implode(',', $additional_groups); + $cur_profile['additional_groups'] = implode(',', $additional_groups); + } + } + + // Too often, people remove delete their own account, or something. + if (in_array(1, explode(',', $old_profile['additional_groups'])) || $old_profile['id_group'] == 1) + { + $stillAdmin = $value == 1 || (isset($additional_groups) && in_array(1, $additional_groups)); + + // If they would no longer be an admin, look for any other... + if (!$stillAdmin) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0) + AND id_member != {int:selected_member} + LIMIT 1', + array( + 'admin_group' => 1, + 'selected_member' => $context['id_member'], + ) + ); + list ($another) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (empty($another)) + fatal_lang_error('at_least_one_admin', 'critical'); + } + } + + // If we are changing group status, update permission cache as necessary. + if ($value != $old_profile['id_group'] || isset($profile_vars['additional_groups'])) + { + if ($context['user']['is_owner']) + $_SESSION['mc']['time'] = 0; + else + updateSettings(array('settings_updated' => time())); + } + + return true; +} + +// The avatar is incredibly complicated, what with the options... and what not. +function profileSaveAvatarData(&$value) +{ + global $modSettings, $sourcedir, $smcFunc, $profile_vars, $cur_profile, $context; + + $memID = $context['id_member']; + if (empty($memID) && !empty($context['password_auth_failed'])) + return false; + + require_once($sourcedir . '/ManageAttachments.php'); + + // We need to know where we're going to be putting it.. + if (!empty($modSettings['custom_avatar_enabled'])) + { + $uploadDir = $modSettings['custom_avatar_dir']; + $id_folder = 1; + } + elseif (!empty($modSettings['currentAttachmentUploadDir'])) + { + if (!is_array($modSettings['attachmentUploadDir'])) + $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); + + // Just use the current path for temp files. + $uploadDir = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']]; + $id_folder = $modSettings['currentAttachmentUploadDir']; + } + else + { + $uploadDir = $modSettings['attachmentUploadDir']; + $id_folder = 1; + } + + $downloadedExternalAvatar = false; + if ($value == 'external' && allowedTo('profile_remote_avatar') && strtolower(substr($_POST['userpicpersonal'], 0, 7)) == 'http://' && strlen($_POST['userpicpersonal']) > 7 && !empty($modSettings['avatar_download_external'])) + { + if (!is_writable($uploadDir)) + fatal_lang_error('attachments_no_write', 'critical'); + + require_once($sourcedir . '/Subs-Package.php'); + + $url = parse_url($_POST['userpicpersonal']); + $contents = fetch_web_data('http://' . $url['host'] . (empty($url['port']) ? '' : ':' . $url['port']) . str_replace(' ', '%20', trim($url['path']))); + + $new_filename = $uploadDir . '/' . getAttachmentFilename('avatar_tmp_' . $memID, false, null, true); + if ($contents != false && $tmpAvatar = fopen($new_filename, 'wb')) + { + fwrite($tmpAvatar, $contents); + fclose($tmpAvatar); + + $downloadedExternalAvatar = true; + $_FILES['attachment']['tmp_name'] = $new_filename; + } + } + + if ($value == 'none') + { + $profile_vars['avatar'] = ''; + + // Reset the attach ID. + $cur_profile['id_attach'] = 0; + $cur_profile['attachment_type'] = 0; + $cur_profile['filename'] = ''; + + removeAttachments(array('id_member' => $memID)); + } + elseif ($value == 'server_stored' && allowedTo('profile_server_avatar')) + { + $profile_vars['avatar'] = strtr(empty($_POST['file']) ? (empty($_POST['cat']) ? '' : $_POST['cat']) : $_POST['file'], array('&' => '&')); + $profile_vars['avatar'] = preg_match('~^([\w _!@%*=\-#()\[\]&.,]+/)?[\w _!@%*=\-#()\[\]&.,]+$~', $profile_vars['avatar']) != 0 && preg_match('/\.\./', $profile_vars['avatar']) == 0 && file_exists($modSettings['avatar_directory'] . '/' . $profile_vars['avatar']) ? ($profile_vars['avatar'] == 'blank.gif' ? '' : $profile_vars['avatar']) : ''; + + // Clear current profile... + $cur_profile['id_attach'] = 0; + $cur_profile['attachment_type'] = 0; + $cur_profile['filename'] = ''; + + // Get rid of their old avatar. (if uploaded.) + removeAttachments(array('id_member' => $memID)); + } + elseif ($value == 'external' && allowedTo('profile_remote_avatar') && strtolower(substr($_POST['userpicpersonal'], 0, 7)) == 'http://' && empty($modSettings['avatar_download_external'])) + { + // We need these clean... + $cur_profile['id_attach'] = 0; + $cur_profile['attachment_type'] = 0; + $cur_profile['filename'] = ''; + + // Remove any attached avatar... + removeAttachments(array('id_member' => $memID)); + + $profile_vars['avatar'] = str_replace('%20', '', preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $_POST['userpicpersonal'])); + + if ($profile_vars['avatar'] == 'http://' || $profile_vars['avatar'] == 'http:///') + $profile_vars['avatar'] = ''; + // Trying to make us do something we'll regret? + elseif (substr($profile_vars['avatar'], 0, 7) != 'http://') + return 'bad_avatar'; + // Should we check dimensions? + elseif (!empty($modSettings['avatar_max_height_external']) || !empty($modSettings['avatar_max_width_external'])) + { + // Now let's validate the avatar. + $sizes = url_image_size($profile_vars['avatar']); + + if (is_array($sizes) && (($sizes[0] > $modSettings['avatar_max_width_external'] && !empty($modSettings['avatar_max_width_external'])) || ($sizes[1] > $modSettings['avatar_max_height_external'] && !empty($modSettings['avatar_max_height_external'])))) + { + // Houston, we have a problem. The avatar is too large!! + if ($modSettings['avatar_action_too_large'] == 'option_refuse') + return 'bad_avatar'; + elseif ($modSettings['avatar_action_too_large'] == 'option_download_and_resize') + { + require_once($sourcedir . '/Subs-Graphics.php'); + if (downloadAvatar($profile_vars['avatar'], $memID, $modSettings['avatar_max_width_external'], $modSettings['avatar_max_height_external'])) + { + $profile_vars['avatar'] = ''; + $cur_profile['id_attach'] = $modSettings['new_avatar_data']['id']; + $cur_profile['filename'] = $modSettings['new_avatar_data']['filename']; + $cur_profile['attachment_type'] = $modSettings['new_avatar_data']['type']; + } + else + return 'bad_avatar'; + } + } + } + } + elseif (($value == 'upload' && allowedTo('profile_upload_avatar')) || $downloadedExternalAvatar) + { + if ((isset($_FILES['attachment']['name']) && $_FILES['attachment']['name'] != '') || $downloadedExternalAvatar) + { + // Get the dimensions of the image. + if (!$downloadedExternalAvatar) + { + if (!is_writable($uploadDir)) + fatal_lang_error('attachments_no_write', 'critical'); + + $new_filename = $uploadDir . '/' . getAttachmentFilename('avatar_tmp_' . $memID, false, null, true); + if (!move_uploaded_file($_FILES['attachment']['tmp_name'], $new_filename)) + fatal_lang_error('attach_timeout', 'critical'); + + $_FILES['attachment']['tmp_name'] = $new_filename; + } + + $sizes = @getimagesize($_FILES['attachment']['tmp_name']); + + // No size, then it's probably not a valid pic. + // No size, then it's probably not a valid pic. + if ($sizes === false) + { + @unlink($_FILES['attachment']['tmp_name']); + return 'bad_avatar'; + } + // Check whether the image is too large. + elseif ((!empty($modSettings['avatar_max_width_upload']) && $sizes[0] > $modSettings['avatar_max_width_upload']) || (!empty($modSettings['avatar_max_height_upload']) && $sizes[1] > $modSettings['avatar_max_height_upload'])) + { + if (!empty($modSettings['avatar_resize_upload'])) + { + // Attempt to chmod it. + @chmod($_FILES['attachment']['tmp_name'], 0644); + + require_once($sourcedir . '/Subs-Graphics.php'); + if (!downloadAvatar($_FILES['attachment']['tmp_name'], $memID, $modSettings['avatar_max_width_upload'], $modSettings['avatar_max_height_upload'])) + { + @unlink($_FILES['attachment']['tmp_name']); + return 'bad_avatar'; + } + + // Reset attachment avatar data. + $cur_profile['id_attach'] = $modSettings['new_avatar_data']['id']; + $cur_profile['filename'] = $modSettings['new_avatar_data']['filename']; + $cur_profile['attachment_type'] = $modSettings['new_avatar_data']['type']; + } + else + { + @unlink($_FILES['attachment']['tmp_name']); + return 'bad_avatar'; + } + } + elseif (is_array($sizes)) + { + // Now try to find an infection. + require_once($sourcedir . '/Subs-Graphics.php'); + if (!checkImageContents($_FILES['attachment']['tmp_name'], !empty($modSettings['avatar_paranoid']))) + { + // It's bad. Try to re-encode the contents? + if (empty($modSettings['avatar_reencode']) || (!reencodeImage($_FILES['attachment']['tmp_name'], $sizes[2]))) + { + @unlink($_FILES['attachment']['tmp_name']); + return 'bad_avatar'; + } + // We were successful. However, at what price? + $sizes = @getimagesize($_FILES['attachment']['tmp_name']); + // Hard to believe this would happen, but can you bet? + if ($sizes === false) + { + @unlink($_FILES['attachment']['tmp_name']); + return 'bad_avatar'; + } + } + + $extensions = array( + '1' => 'gif', + '2' => 'jpg', + '3' => 'png', + '6' => 'bmp' + ); + + $extension = isset($extensions[$sizes[2]]) ? $extensions[$sizes[2]] : 'bmp'; + $mime_type = 'image/' . ($extension === 'jpg' ? 'jpeg' : ($extension === 'bmp' ? 'x-ms-bmp' : $extension)); + $destName = 'avatar_' . $memID . '_' . time() . '.' . $extension; + list ($width, $height) = getimagesize($_FILES['attachment']['tmp_name']); + $file_hash = empty($modSettings['custom_avatar_enabled']) ? getAttachmentFilename($destName, false, null, true) : ''; + + // Remove previous attachments this member might have had. + removeAttachments(array('id_member' => $memID)); + + $smcFunc['db_insert']('', + '{db_prefix}attachments', + array( + 'id_member' => 'int', 'attachment_type' => 'int', 'filename' => 'string', 'file_hash' => 'string', 'fileext' => 'string', 'size' => 'int', + 'width' => 'int', 'height' => 'int', 'mime_type' => 'string', 'id_folder' => 'int', + ), + array( + $memID, (empty($modSettings['custom_avatar_enabled']) ? 0 : 1), $destName, $file_hash, $extension, filesize($_FILES['attachment']['tmp_name']), + (int) $width, (int) $height, $mime_type, $id_folder, + ), + array('id_attach') + ); + + $cur_profile['id_attach'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach'); + $cur_profile['filename'] = $destName; + $cur_profile['attachment_type'] = empty($modSettings['custom_avatar_enabled']) ? 0 : 1; + + $destinationPath = $uploadDir . '/' . (empty($file_hash) ? $destName : $cur_profile['id_attach'] . '_' . $file_hash); + if (!rename($_FILES['attachment']['tmp_name'], $destinationPath)) + { + // I guess a man can try. + removeAttachments(array('id_member' => $memID)); + fatal_lang_error('attach_timeout', 'critical'); + } + + // Attempt to chmod it. + @chmod($uploadDir . '/' . $destinationPath, 0644); + } + $profile_vars['avatar'] = ''; + + // Delete any temporary file. + if (file_exists($_FILES['attachment']['tmp_name'])) + @unlink($_FILES['attachment']['tmp_name']); + } + // Selected the upload avatar option and had one already uploaded before or didn't upload one. + else + $profile_vars['avatar'] = ''; + } + else + $profile_vars['avatar'] = ''; + + // Setup the profile variables so it shows things right on display! + $cur_profile['avatar'] = $profile_vars['avatar']; + + return false; +} + +// Validate the signature! +function profileValidateSignature(&$value) +{ + global $sourcedir, $modSettings, $smcFunc, $txt; + + require_once($sourcedir . '/Subs-Post.php'); + + // Admins can do whatever they hell they want! + if (!allowedTo('admin_forum')) + { + // Load all the signature limits. + list ($sig_limits, $sig_bbc) = explode(':', $modSettings['signature_settings']); + $sig_limits = explode(',', $sig_limits); + $disabledTags = !empty($sig_bbc) ? explode(',', $sig_bbc) : array(); + + $unparsed_signature = strtr(un_htmlspecialchars($value), array("\r" => '', ''' => '\'')); + // Too long? + if (!empty($sig_limits[1]) && $smcFunc['strlen']($unparsed_signature) > $sig_limits[1]) + { + $_POST['signature'] = trim(htmlspecialchars($smcFunc['substr']($unparsed_signature, 0, $sig_limits[1]), ENT_QUOTES)); + $txt['profile_error_signature_max_length'] = sprintf($txt['profile_error_signature_max_length'], $sig_limits[1]); + return 'signature_max_length'; + } + // Too many lines? + if (!empty($sig_limits[2]) && substr_count($unparsed_signature, "\n") >= $sig_limits[2]) + { + $txt['profile_error_signature_max_lines'] = sprintf($txt['profile_error_signature_max_lines'], $sig_limits[2]); + return 'signature_max_lines'; + } + // Too many images?! + if (!empty($sig_limits[3]) && (substr_count(strtolower($unparsed_signature), '[img') + substr_count(strtolower($unparsed_signature), ' $sig_limits[3]) + { + $txt['profile_error_signature_max_image_count'] = sprintf($txt['profile_error_signature_max_image_count'], $sig_limits[3]); + return 'signature_max_image_count'; + } + // What about too many smileys! + $smiley_parsed = $unparsed_signature; + parsesmileys($smiley_parsed); + $smiley_count = substr_count(strtolower($smiley_parsed), ' 0) + return 'signature_allow_smileys'; + elseif (!empty($sig_limits[4]) && $sig_limits[4] > 0 && $smiley_count > $sig_limits[4]) + { + $txt['profile_error_signature_max_smileys'] = sprintf($txt['profile_error_signature_max_smileys'], $sig_limits[4]); + return 'signature_max_smileys'; + } + // Maybe we are abusing font sizes? + if (!empty($sig_limits[7]) && preg_match_all('~\[size=([\d\.]+)?(px|pt|em|x-large|larger)~i', $unparsed_signature, $matches) !== false && isset($matches[2])) + { + foreach ($matches[1] as $ind => $size) + { + $limit_broke = 0; + // Attempt to allow all sizes of abuse, so to speak. + if ($matches[2][$ind] == 'px' && $size > $sig_limits[7]) + $limit_broke = $sig_limits[7] . 'px'; + elseif ($matches[2][$ind] == 'pt' && $size > ($sig_limits[7] * 0.75)) + $limit_broke = ((int) $sig_limits[7] * 0.75) . 'pt'; + elseif ($matches[2][$ind] == 'em' && $size > ((float) $sig_limits[7] / 16)) + $limit_broke = ((float) $sig_limits[7] / 16) . 'em'; + elseif ($matches[2][$ind] != 'px' && $matches[2][$ind] != 'pt' && $matches[2][$ind] != 'em' && $sig_limits[7] < 18) + $limit_broke = 'large'; + + if ($limit_broke) + { + $txt['profile_error_signature_max_font_size'] = sprintf($txt['profile_error_signature_max_font_size'], $limit_broke); + return 'signature_max_font_size'; + } + } + } + // The difficult one - image sizes! Don't error on this - just fix it. + if ((!empty($sig_limits[5]) || !empty($sig_limits[6]))) + { + // Get all BBC tags... + preg_match_all('~\[img(\s+width=([\d]+))?(\s+height=([\d]+))?(\s+width=([\d]+))?\s*\](?:
)*([^<">]+?)(?:
)*\[/img\]~i', $unparsed_signature, $matches); + // ... and all HTML ones. + preg_match_all('~~i', $unparsed_signature, $matches2, PREG_PATTERN_ORDER); + // And stick the HTML in the BBC. + if (!empty($matches2)) + { + foreach ($matches2[0] as $ind => $dummy) + { + $matches[0][] = $matches2[0][$ind]; + $matches[1][] = ''; + $matches[2][] = ''; + $matches[3][] = ''; + $matches[4][] = ''; + $matches[5][] = ''; + $matches[6][] = ''; + $matches[7][] = $matches2[1][$ind]; + } + } + + $replaces = array(); + // Try to find all the images! + if (!empty($matches)) + { + foreach ($matches[0] as $key => $image) + { + $width = -1; $height = -1; + + // Does it have predefined restraints? Width first. + if ($matches[6][$key]) + $matches[2][$key] = $matches[6][$key]; + if ($matches[2][$key] && $sig_limits[5] && $matches[2][$key] > $sig_limits[5]) + { + $width = $sig_limits[5]; + $matches[4][$key] = $matches[4][$key] * ($width / $matches[2][$key]); + } + elseif ($matches[2][$key]) + $width = $matches[2][$key]; + // ... and height. + if ($matches[4][$key] && $sig_limits[6] && $matches[4][$key] > $sig_limits[6]) + { + $height = $sig_limits[6]; + if ($width != -1) + $width = $width * ($height / $matches[4][$key]); + } + elseif ($matches[4][$key]) + $height = $matches[4][$key]; + + // If the dimensions are still not fixed - we need to check the actual image. + if (($width == -1 && $sig_limits[5]) || ($height == -1 && $sig_limits[6])) + { + $sizes = url_image_size($matches[7][$key]); + if (is_array($sizes)) + { + // Too wide? + if ($sizes[0] > $sig_limits[5] && $sig_limits[5]) + { + $width = $sig_limits[5]; + $sizes[1] = $sizes[1] * ($width / $sizes[0]); + } + // Too high? + if ($sizes[1] > $sig_limits[6] && $sig_limits[6]) + { + $height = $sig_limits[6]; + if ($width == -1) + $width = $sizes[0]; + $width = $width * ($height / $sizes[1]); + } + elseif ($width != -1) + $height = $sizes[1]; + } + } + + // Did we come up with some changes? If so remake the string. + if ($width != -1 || $height != -1) + $replaces[$image] = '[img' . ($width != -1 ? ' width=' . round($width) : '') . ($height != -1 ? ' height=' . round($height) : '') . ']' . $matches[7][$key] . '[/img]'; + } + if (!empty($replaces)) + $value = str_replace(array_keys($replaces), array_values($replaces), $value); + } + } + // Any disabled BBC? + $disabledSigBBC = implode('|', $disabledTags); + if (!empty($disabledSigBBC)) + { + if (preg_match('~\[(' . $disabledSigBBC . ')~i', $unparsed_signature, $matches) !== false && isset($matches[1])) + { + $disabledTags = array_unique($disabledTags); + $txt['profile_error_signature_disabled_bbc'] = sprintf($txt['profile_error_signature_disabled_bbc'], implode(', ', $disabledTags)); + return 'signature_disabled_bbc'; + } + } + } + + preparsecode($value); + return true; +} + +// Validate an email address. +function profileValidateEmail($email, $memID = 0) +{ + global $smcFunc, $context; + + $email = strtr($email, array(''' => '\'')); + + // Check the name and email for validity. + if (trim($email) == '') + return 'no_email'; + if (preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $email) == 0) + return 'bad_email'; + + // Email addresses should be and stay unique. + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE ' . ($memID != 0 ? 'id_member != {int:selected_member} AND ' : '') . ' + email_address = {string:email_address} + LIMIT 1', + array( + 'selected_member' => $memID, + 'email_address' => $email, + ) + ); + if ($smcFunc['db_num_rows']($request) > 0) + return 'email_taken'; + $smcFunc['db_free_result']($request); + + return true; +} + +// Reload a users settings. +function profileReloadUser() +{ + global $sourcedir, $modSettings, $context, $cur_profile, $smcFunc, $profile_vars; + + // Log them back in - using the verify password as they must have matched and this one doesn't get changed by anyone! + if (isset($_POST['passwrd2']) && $_POST['passwrd2'] != '') + { + require_once($sourcedir . '/Subs-Auth.php'); + setLoginCookie(60 * $modSettings['cookieTime'], $context['id_member'], sha1(sha1(strtolower($cur_profile['member_name']) . un_htmlspecialchars($_POST['passwrd2'])) . $cur_profile['password_salt'])); + } + + loadUserSettings(); + writeLog(); +} + +// Send the user a new activation email if they need to reactivate! +function profileSendActivation() +{ + global $sourcedir, $profile_vars, $txt, $context, $scripturl, $smcFunc, $cookiename, $cur_profile, $language, $modSettings; + + require_once($sourcedir . '/Subs-Post.php'); + + // Shouldn't happen but just in case. + if (empty($profile_vars['email_address'])) + return; + + $replacements = array( + 'ACTIVATIONLINK' => $scripturl . '?action=activate;u=' . $context['id_member'] . ';code=' . $profile_vars['validation_code'], + 'ACTIVATIONCODE' => $profile_vars['validation_code'], + 'ACTIVATIONLINKWITHOUTCODE' => $scripturl . '?action=activate;u=' . $context['id_member'], + ); + + // Send off the email. + $emaildata = loadEmailTemplate('activate_reactivate', $replacements, empty($cur_profile['lngfile']) || empty($modSettings['userLanguage']) ? $language : $cur_profile['lngfile']); + sendmail($profile_vars['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 0); + + // Log the user out. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_online + WHERE id_member = {int:selected_member}', + array( + 'selected_member' => $context['id_member'], + ) + ); + $_SESSION['log_time'] = 0; + $_SESSION['login_' . $cookiename] = serialize(array(0, '', 0)); + + if (isset($_COOKIE[$cookiename])) + $_COOKIE[$cookiename] = ''; + + loadUserSettings(); + + $context['user']['is_logged'] = false; + $context['user']['is_guest'] = true; + + // Send them to the done-with-registration-login screen. + loadTemplate('Register'); + + $context['page_title'] = $txt['profile']; + $context['sub_template'] = 'after'; + $context['title'] = $txt['activate_changed_email_title']; + $context['description'] = $txt['activate_changed_email_desc']; + + // We're gone! + obExit(); +} + +// Function to allow the user to choose group membership etc... +function groupMembership($memID) +{ + global $txt, $scripturl, $user_profile, $user_info, $context, $modSettings, $smcFunc; + + $curMember = $user_profile[$memID]; + $context['primary_group'] = $curMember['id_group']; + + // Can they manage groups? + $context['can_manage_membergroups'] = allowedTo('manage_membergroups'); + $context['can_manage_protected'] = allowedTo('admin_forum'); + $context['can_edit_primary'] = $context['can_manage_protected']; + $context['update_message'] = isset($_GET['msg']) && isset($txt['group_membership_msg_' . $_GET['msg']]) ? $txt['group_membership_msg_' . $_GET['msg']] : ''; + + // Get all the groups this user is a member of. + $groups = explode(',', $curMember['additional_groups']); + $groups[] = $curMember['id_group']; + + // Ensure the query doesn't croak! + if (empty($groups)) + $groups = array(0); + // Just to be sure... + foreach ($groups as $k => $v) + $groups[$k] = (int) $v; + + // Get all the membergroups they can join. + $request = $smcFunc['db_query']('', ' + SELECT mg.id_group, mg.group_name, mg.description, mg.group_type, mg.online_color, mg.hidden, + IFNULL(lgr.id_member, 0) AS pending + FROM {db_prefix}membergroups AS mg + LEFT JOIN {db_prefix}log_group_requests AS lgr ON (lgr.id_member = {int:selected_member} AND lgr.id_group = mg.id_group) + WHERE (mg.id_group IN ({array_int:group_list}) + OR mg.group_type > {int:nonjoin_group_id}) + AND mg.min_posts = {int:min_posts} + AND mg.id_group != {int:moderator_group} + ORDER BY group_name', + array( + 'group_list' => $groups, + 'selected_member' => $memID, + 'nonjoin_group_id' => 1, + 'min_posts' => -1, + 'moderator_group' => 3, + ) + ); + // This beast will be our group holder. + $context['groups'] = array( + 'member' => array(), + 'available' => array() + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Can they edit their primary group? + if (($row['id_group'] == $context['primary_group'] && $row['group_type'] > 1) || ($row['hidden'] != 2 && $context['primary_group'] == 0 && in_array($row['id_group'], $groups))) + $context['can_edit_primary'] = true; + + // If they can't manage (protected) groups, and it's not publically joinable or already assigned, they can't see it. + if (((!$context['can_manage_protected'] && $row['group_type'] == 1) || (!$context['can_manage_membergroups'] && $row['group_type'] == 0)) && $row['id_group'] != $context['primary_group']) + continue; + + $context['groups'][in_array($row['id_group'], $groups) ? 'member' : 'available'][$row['id_group']] = array( + 'id' => $row['id_group'], + 'name' => $row['group_name'], + 'desc' => $row['description'], + 'color' => $row['online_color'], + 'type' => $row['group_type'], + 'pending' => $row['pending'], + 'is_primary' => $row['id_group'] == $context['primary_group'], + 'can_be_primary' => $row['hidden'] != 2, + // Anything more than this needs to be done through account settings for security. + 'can_leave' => $row['id_group'] != 1 && $row['group_type'] > 1 ? true : false, + ); + } + $smcFunc['db_free_result']($request); + + // Add registered members on the end. + $context['groups']['member'][0] = array( + 'id' => 0, + 'name' => $txt['regular_members'], + 'desc' => $txt['regular_members_desc'], + 'type' => 0, + 'is_primary' => $context['primary_group'] == 0 ? true : false, + 'can_be_primary' => true, + 'can_leave' => 0, + ); + + // No changing primary one unless you have enough groups! + if (count($context['groups']['member']) < 2) + $context['can_edit_primary'] = false; + + // In the special case that someone is requesting membership of a group, setup some special context vars. + if (isset($_REQUEST['request']) && isset($context['groups']['available'][(int) $_REQUEST['request']]) && $context['groups']['available'][(int) $_REQUEST['request']]['type'] == 2) + $context['group_request'] = $context['groups']['available'][(int) $_REQUEST['request']]; +} + +// This function actually makes all the group changes... +function groupMembership2($profile_vars, $post_errors, $memID) +{ + global $user_info, $sourcedir, $context, $user_profile, $modSettings, $txt, $smcFunc, $scripturl, $language; + + // Let's be extra cautious... + if (!$context['user']['is_owner'] || empty($modSettings['show_group_membership'])) + isAllowedTo('manage_membergroups'); + if (!isset($_REQUEST['gid']) && !isset($_POST['primary'])) + fatal_lang_error('no_access', false); + + checkSession(isset($_GET['gid']) ? 'get' : 'post'); + + $old_profile = &$user_profile[$memID]; + $context['can_manage_membergroups'] = allowedTo('manage_membergroups'); + $context['can_manage_protected'] = allowedTo('admin_forum'); + + // By default the new primary is the old one. + $newPrimary = $old_profile['id_group']; + $addGroups = array_flip(explode(',', $old_profile['additional_groups'])); + $canChangePrimary = $old_profile['id_group'] == 0 ? 1 : 0; + $changeType = isset($_POST['primary']) ? 'primary' : (isset($_POST['req']) ? 'request' : 'free'); + + // One way or another, we have a target group in mind... + $group_id = isset($_REQUEST['gid']) ? (int) $_REQUEST['gid'] : (int) $_POST['primary']; + $foundTarget = $changeType == 'primary' && $group_id == 0 ? true : false; + + // Sanity check!! + if ($group_id == 1) + isAllowedTo('admin_forum'); + // Protected groups too! + else + { + $request = $smcFunc['db_query']('', ' + SELECT group_type + FROM {db_prefix}membergroups + WHERE id_group = {int:current_group} + LIMIT {int:limit}', + array( + 'current_group' => $group_id, + 'limit' => 1, + ) + ); + list ($is_protected) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if ($is_protected == 1) + isAllowedTo('admin_forum'); + } + + // What ever we are doing, we need to determine if changing primary is possible! + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_type, hidden, group_name + FROM {db_prefix}membergroups + WHERE id_group IN ({int:group_list}, {int:current_group})', + array( + 'group_list' => $group_id, + 'current_group' => $old_profile['id_group'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Is this the new group? + if ($row['id_group'] == $group_id) + { + $foundTarget = true; + $group_name = $row['group_name']; + + // Does the group type match what we're doing - are we trying to request a non-requestable group? + if ($changeType == 'request' && $row['group_type'] != 2) + fatal_lang_error('no_access', false); + // What about leaving a requestable group we are not a member of? + elseif ($changeType == 'free' && $row['group_type'] == 2 && $old_profile['id_group'] != $row['id_group'] && !isset($addGroups[$row['id_group']])) + fatal_lang_error('no_access', false); + elseif ($changeType == 'free' && $row['group_type'] != 3 && $row['group_type'] != 2) + fatal_lang_error('no_access', false); + + // We can't change the primary group if this is hidden! + if ($row['hidden'] == 2) + $canChangePrimary = false; + } + + // If this is their old primary, can we change it? + if ($row['id_group'] == $old_profile['id_group'] && ($row['group_type'] > 1 || $context['can_manage_membergroups']) && $canChangePrimary !== false) + $canChangePrimary = 1; + + // If we are not doing a force primary move, don't do it automatically if current primary is not 0. + if ($changeType != 'primary' && $old_profile['id_group'] != 0) + $canChangePrimary = false; + + // If this is the one we are acting on, can we even act? + if ((!$context['can_manage_protected'] && $row['group_type'] == 1) || (!$context['can_manage_membergroups'] && $row['group_type'] == 0)) + $canChangePrimary = false; + } + $smcFunc['db_free_result']($request); + + // Didn't find the target? + if (!$foundTarget) + fatal_lang_error('no_access', false); + + // Final security check, don't allow users to promote themselves to admin. + if ($context['can_manage_membergroups'] && !allowedTo('admin_forum')) + { + $request = $smcFunc['db_query']('', ' + SELECT COUNT(permission) + FROM {db_prefix}permissions + WHERE id_group = {int:selected_group} + AND permission = {string:admin_forum} + AND add_deny = {int:not_denied}', + array( + 'selected_group' => $group_id, + 'not_denied' => 1, + 'admin_forum' => 'admin_forum', + ) + ); + list ($disallow) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if ($disallow) + isAllowedTo('admin_forum'); + } + + // If we're requesting, add the note then return. + if ($changeType == 'request') + { + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}log_group_requests + WHERE id_member = {int:selected_member} + AND id_group = {int:selected_group}', + array( + 'selected_member' => $memID, + 'selected_group' => $group_id, + ) + ); + if ($smcFunc['db_num_rows']($request) != 0) + fatal_lang_error('profile_error_already_requested_group'); + $smcFunc['db_free_result']($request); + + // Log the request. + $smcFunc['db_insert']('', + '{db_prefix}log_group_requests', + array( + 'id_member' => 'int', 'id_group' => 'int', 'time_applied' => 'int', 'reason' => 'string-65534', + ), + array( + $memID, $group_id, time(), $_POST['reason'], + ), + array('id_request') + ); + + // Send an email to all group moderators etc. + require_once($sourcedir . '/Subs-Post.php'); + + // Do we have any group moderators? + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}group_moderators + WHERE id_group = {int:selected_group}', + array( + 'selected_group' => $group_id, + ) + ); + $moderators = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $moderators[] = $row['id_member']; + $smcFunc['db_free_result']($request); + + // Otherwise this is the backup! + if (empty($moderators)) + { + require_once($sourcedir . '/Subs-Members.php'); + $moderators = membersAllowedTo('manage_membergroups'); + } + + if (!empty($moderators)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member, email_address, lngfile, member_name, mod_prefs + FROM {db_prefix}members + WHERE id_member IN ({array_int:moderator_list}) + AND notify_types != {int:no_notifications} + ORDER BY lngfile', + array( + 'moderator_list' => $moderators, + 'no_notifications' => 4, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Check whether they are interested. + if (!empty($row['mod_prefs'])) + { + list(,, $pref_binary) = explode('|', $row['mod_prefs']); + if (!($pref_binary & 4)) + continue; + } + + $replacements = array( + 'RECPNAME' => $row['member_name'], + 'APPYNAME' => $old_profile['member_name'], + 'GROUPNAME' => $group_name, + 'REASON' => $_POST['reason'], + 'MODLINK' => $scripturl . '?action=moderate;area=groups;sa=requests', + ); + + $emaildata = loadEmailTemplate('request_membership', $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']); + sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 2); + } + $smcFunc['db_free_result']($request); + } + + return $changeType; + } + // Otherwise we are leaving/joining a group. + elseif ($changeType == 'free') + { + // Are we leaving? + if ($old_profile['id_group'] == $group_id || isset($addGroups[$group_id])) + { + if ($old_profile['id_group'] == $group_id) + $newPrimary = 0; + else + unset($addGroups[$group_id]); + } + // ... if not, must be joining. + else + { + // Can we change the primary, and do we want to? + if ($canChangePrimary) + { + if ($old_profile['id_group'] != 0) + $addGroups[$old_profile['id_group']] = -1; + $newPrimary = $group_id; + } + // Otherwise it's an additional group... + else + $addGroups[$group_id] = -1; + } + } + // Finally, we must be setting the primary. + elseif ($canChangePrimary) + { + if ($old_profile['id_group'] != 0) + $addGroups[$old_profile['id_group']] = -1; + if (isset($addGroups[$group_id])) + unset($addGroups[$group_id]); + $newPrimary = $group_id; + } + + // Finally, we can make the changes! + foreach ($addGroups as $id => $dummy) + if (empty($id)) + unset($addGroups[$id]); + $addGroups = implode(',', array_flip($addGroups)); + + // Ensure that we don't cache permissions if the group is changing. + if ($context['user']['is_owner']) + $_SESSION['mc']['time'] = 0; + else + updateSettings(array('settings_updated' => time())); + + updateMemberData($memID, array('id_group' => $newPrimary, 'additional_groups' => $addGroups)); + + return $changeType; +} + +?> \ No newline at end of file diff --git a/Sources/Profile-View.php b/Sources/Profile-View.php new file mode 100644 index 0000000..25dba6c --- /dev/null +++ b/Sources/Profile-View.php @@ -0,0 +1,1958 @@ + sprintf($txt['profile_of_username'], $memberContext[$memID]['name']), + 'can_send_pm' => allowedTo('pm_send'), + 'can_have_buddy' => allowedTo('profile_identity_own') && !empty($modSettings['enable_buddylist']), + 'can_issue_warning' => in_array('w', $context['admin_features']) && allowedTo('issue_warning') && $modSettings['warning_settings'][0] == 1, + ); + $context['member'] = &$memberContext[$memID]; + $context['can_view_warning'] = in_array('w', $context['admin_features']) && (allowedTo('issue_warning') && !$context['user']['is_owner']) || (!empty($modSettings['warning_show']) && ($modSettings['warning_show'] > 1 || $context['user']['is_owner'])); + + // Set a canonical URL for this page. + $context['canonical_url'] = $scripturl . '?action=profile;u=' . $memID; + + // Are there things we don't show? + $context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array(); + + // See if they have broken any warning levels... + list ($modSettings['warning_enable'], $modSettings['user_limit']) = explode(',', $modSettings['warning_settings']); + if (!empty($modSettings['warning_mute']) && $modSettings['warning_mute'] <= $context['member']['warning']) + $context['warning_status'] = $txt['profile_warning_is_muted']; + elseif (!empty($modSettings['warning_moderate']) && $modSettings['warning_moderate'] <= $context['member']['warning']) + $context['warning_status'] = $txt['profile_warning_is_moderation']; + elseif (!empty($modSettings['warning_watch']) && $modSettings['warning_watch'] <= $context['member']['warning']) + $context['warning_status'] = $txt['profile_warning_is_watch']; + + // They haven't even been registered for a full day!? + $days_registered = (int) ((time() - $user_profile[$memID]['date_registered']) / (3600 * 24)); + if (empty($user_profile[$memID]['date_registered']) || $days_registered < 1) + $context['member']['posts_per_day'] = $txt['not_applicable']; + else + $context['member']['posts_per_day'] = comma_format($context['member']['real_posts'] / $days_registered, 3); + + // Set the age... + if (empty($context['member']['birth_date'])) + { + $context['member'] += array( + 'age' => $txt['not_applicable'], + 'today_is_birthday' => false + ); + } + else + { + list ($birth_year, $birth_month, $birth_day) = sscanf($context['member']['birth_date'], '%d-%d-%d'); + $datearray = getdate(forum_time()); + $context['member'] += array( + 'age' => $birth_year <= 4 ? $txt['not_applicable'] : $datearray['year'] - $birth_year - (($datearray['mon'] > $birth_month || ($datearray['mon'] == $birth_month && $datearray['mday'] >= $birth_day)) ? 0 : 1), + 'today_is_birthday' => $datearray['mon'] == $birth_month && $datearray['mday'] == $birth_day + ); + } + + if (allowedTo('moderate_forum')) + { + // Make sure it's a valid ip address; otherwise, don't bother... + if (preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $memberContext[$memID]['ip']) == 1 && empty($modSettings['disableHostnameLookup'])) + $context['member']['hostname'] = host_from_ip($memberContext[$memID]['ip']); + else + $context['member']['hostname'] = ''; + + $context['can_see_ip'] = true; + } + else + $context['can_see_ip'] = false; + + if (!empty($modSettings['who_enabled'])) + { + include_once($sourcedir . '/Who.php'); + $action = determineActions($user_profile[$memID]['url']); + + if ($action !== false) + $context['member']['action'] = $action; + } + + // If the user is awaiting activation, and the viewer has permission - setup some activation context messages. + if ($context['member']['is_activated'] % 10 != 1 && allowedTo('moderate_forum')) + { + $context['activate_type'] = $context['member']['is_activated']; + // What should the link text be? + $context['activate_link_text'] = in_array($context['member']['is_activated'], array(3, 4, 5, 13, 14, 15)) ? $txt['account_approve'] : $txt['account_activate']; + + // Should we show a custom message? + $context['activate_message'] = isset($txt['account_activate_method_' . $context['member']['is_activated'] % 10]) ? $txt['account_activate_method_' . $context['member']['is_activated'] % 10] : $txt['account_not_activated']; + } + + // Is the signature even enabled on this forum? + $context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1; + + // How about, are they banned? + $context['member']['bans'] = array(); + if (allowedTo('moderate_forum')) + { + // Can they edit the ban? + $context['can_edit_ban'] = allowedTo('manage_bans'); + + $ban_query = array(); + $ban_query_vars = array( + 'time' => time(), + ); + $ban_query[] = 'id_member = ' . $context['member']['id']; + + // Valid IP? + if (preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $memberContext[$memID]['ip'], $ip_parts) == 1) + { + $ban_query[] = '((' . $ip_parts[1] . ' BETWEEN bi.ip_low1 AND bi.ip_high1) + AND (' . $ip_parts[2] . ' BETWEEN bi.ip_low2 AND bi.ip_high2) + AND (' . $ip_parts[3] . ' BETWEEN bi.ip_low3 AND bi.ip_high3) + AND (' . $ip_parts[4] . ' BETWEEN bi.ip_low4 AND bi.ip_high4))'; + + // Do we have a hostname already? + if (!empty($context['member']['hostname'])) + { + $ban_query[] = '({string:hostname} LIKE hostname)'; + $ban_query_vars['hostname'] = $context['member']['hostname']; + } + } + // Use '255.255.255.255' for 'unknown' - it's not valid anyway. + elseif ($memberContext[$memID]['ip'] == 'unknown') + $ban_query[] = '(bi.ip_low1 = 255 AND bi.ip_high1 = 255 + AND bi.ip_low2 = 255 AND bi.ip_high2 = 255 + AND bi.ip_low3 = 255 AND bi.ip_high3 = 255 + AND bi.ip_low4 = 255 AND bi.ip_high4 = 255)'; + + // Check their email as well... + if (strlen($context['member']['email']) != 0) + { + $ban_query[] = '({string:email} LIKE bi.email_address)'; + $ban_query_vars['email'] = $context['member']['email']; + } + + // So... are they banned? Dying to know! + $request = $smcFunc['db_query']('', ' + SELECT bg.id_ban_group, bg.name, bg.cannot_access, bg.cannot_post, bg.cannot_register, + bg.cannot_login, bg.reason + FROM {db_prefix}ban_items AS bi + INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group AND (bg.expire_time IS NULL OR bg.expire_time > {int:time})) + WHERE (' . implode(' OR ', $ban_query) . ')', + $ban_query_vars + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Work out what restrictions we actually have. + $ban_restrictions = array(); + foreach (array('access', 'register', 'login', 'post') as $type) + if ($row['cannot_' . $type]) + $ban_restrictions[] = $txt['ban_type_' . $type]; + + // No actual ban in place? + if (empty($ban_restrictions)) + continue; + + // Prepare the link for context. + $ban_explanation = sprintf($txt['user_cannot_due_to'], implode(', ', $ban_restrictions), '' . $row['name'] . ''); + + $context['member']['bans'][$row['id_ban_group']] = array( + 'reason' => empty($row['reason']) ? '' : '

' . $txt['ban_reason'] . ': ' . $row['reason'], + 'cannot' => array( + 'access' => !empty($row['cannot_access']), + 'register' => !empty($row['cannot_register']), + 'post' => !empty($row['cannot_post']), + 'login' => !empty($row['cannot_login']), + ), + 'explanation' => $ban_explanation, + ); + } + $smcFunc['db_free_result']($request); + } + + loadCustomFields($memID); +} + +// !!! This function needs to be split up properly. +// Show all posts by the current user +function showPosts($memID) +{ + global $txt, $user_info, $scripturl, $modSettings; + global $context, $user_profile, $sourcedir, $smcFunc, $board; + + // Some initial context. + $context['start'] = (int) $_REQUEST['start']; + $context['current_member'] = $memID; + + // Create the tabs for the template. + $context[$context['profile_menu_name']]['tab_data'] = array( + 'title' => $txt['showPosts'], + 'description' => $txt['showPosts_help'], + 'icon' => 'profile_sm.gif', + 'tabs' => array( + 'messages' => array( + ), + 'topics' => array( + ), + 'attach' => array( + ), + ), + ); + + // Set the page title + $context['page_title'] = $txt['showPosts'] . ' - ' . $user_profile[$memID]['real_name']; + + // Is the load average too high to allow searching just now? + if (!empty($context['load_average']) && !empty($modSettings['loadavg_show_posts']) && $context['load_average'] >= $modSettings['loadavg_show_posts']) + fatal_lang_error('loadavg_show_posts_disabled', false); + + // If we're specifically dealing with attachments use that function! + if (isset($_GET['sa']) && $_GET['sa'] == 'attach') + return showAttachments($memID); + + // Are we just viewing topics? + $context['is_topics'] = isset($_GET['sa']) && $_GET['sa'] == 'topics' ? true : false; + + // If just deleting a message, do it and then redirect back. + if (isset($_GET['delete']) && !$context['is_topics']) + { + checkSession('get'); + + // We need msg info for logging. + $request = $smcFunc['db_query']('', ' + SELECT subject, id_member, id_topic, id_board + FROM {db_prefix}messages + WHERE id_msg = {int:id_msg}', + array( + 'id_msg' => (int) $_GET['delete'], + ) + ); + $info = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Trying to remove a message that doesn't exist. + if (empty($info)) + redirectexit('action=profile;u=' . $memID . ';area=showposts;start=' . $_GET['start']); + + // We can be lazy, since removeMessage() will check the permissions for us. + require_once($sourcedir . '/RemoveTopic.php'); + removeMessage((int) $_GET['delete']); + + // Add it to the mod log. + if (allowedTo('delete_any') && (!allowedTo('delete_own') || $info[1] != $user_info['id'])) + logAction('delete', array('topic' => $info[2], 'subject' => $info[0], 'member' => $info[1], 'board' => $info[3])); + + // Back to... where we are now ;). + redirectexit('action=profile;u=' . $memID . ';area=showposts;start=' . $_GET['start']); + } + + // Default to 10. + if (empty($_REQUEST['viewscount']) || !is_numeric($_REQUEST['viewscount'])) + $_REQUEST['viewscount'] = '10'; + + if ($context['is_topics']) + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}topics AS t' . ($user_info['query_see_board'] == '1=1' ? '' : ' + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board AND {query_see_board})') . ' + WHERE t.id_member_started = {int:current_member}' . (!empty($board) ? ' + AND t.id_board = {int:board}' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : ' + AND t.approved = {int:is_approved}'), + array( + 'current_member' => $memID, + 'is_approved' => 1, + 'board' => $board, + ) + ); + else + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}messages AS m' . ($user_info['query_see_board'] == '1=1' ? '' : ' + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})') . ' + WHERE m.id_member = {int:current_member}' . (!empty($board) ? ' + AND m.id_board = {int:board}' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : ' + AND m.approved = {int:is_approved}'), + array( + 'current_member' => $memID, + 'is_approved' => 1, + 'board' => $board, + ) + ); + list ($msgCount) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $request = $smcFunc['db_query']('', ' + SELECT MIN(id_msg), MAX(id_msg) + FROM {db_prefix}messages AS m + WHERE m.id_member = {int:current_member}' . (!empty($board) ? ' + AND m.id_board = {int:board}' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : ' + AND m.approved = {int:is_approved}'), + array( + 'current_member' => $memID, + 'is_approved' => 1, + 'board' => $board, + ) + ); + list ($min_msg_member, $max_msg_member) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $reverse = false; + $range_limit = ''; + $maxIndex = (int) $modSettings['defaultMaxMessages']; + + // Make sure the starting place makes sense and construct our friend the page index. + $context['page_index'] = constructPageIndex($scripturl . '?action=profile;u=' . $memID . ';area=showposts' . ($context['is_topics'] ? ';sa=topics' : '') . (!empty($board) ? ';board=' . $board : ''), $context['start'], $msgCount, $maxIndex); + $context['current_page'] = $context['start'] / $maxIndex; + + // Reverse the query if we're past 50% of the pages for better performance. + $start = $context['start']; + $reverse = $_REQUEST['start'] > $msgCount / 2; + if ($reverse) + { + $maxIndex = $msgCount < $context['start'] + $modSettings['defaultMaxMessages'] + 1 && $msgCount > $context['start'] ? $msgCount - $context['start'] : (int) $modSettings['defaultMaxMessages']; + $start = $msgCount < $context['start'] + $modSettings['defaultMaxMessages'] + 1 || $msgCount < $context['start'] + $modSettings['defaultMaxMessages'] ? 0 : $msgCount - $context['start'] - $modSettings['defaultMaxMessages']; + } + + // Guess the range of messages to be shown. + if ($msgCount > 1000) + { + $margin = floor(($max_msg_member - $min_msg_member) * (($start + $modSettings['defaultMaxMessages']) / $msgCount) + .1 * ($max_msg_member - $min_msg_member)); + // Make a bigger margin for topics only. + if ($context['is_topics']) + { + $margin *= 5; + $range_limit = $reverse ? 't.id_first_msg < ' . ($min_msg_member + $margin) : 't.id_first_msg > ' . ($max_msg_member - $margin); + } + else + $range_limit = $reverse ? 'm.id_msg < ' . ($min_msg_member + $margin) : 'm.id_msg > ' . ($max_msg_member - $margin); + } + + // Find this user's posts. The left join on categories somehow makes this faster, weird as it looks. + $looped = false; + while (true) + { + if ($context['is_topics']) + { + $request = $smcFunc['db_query']('', ' + SELECT + b.id_board, b.name AS bname, c.id_cat, c.name AS cname, t.id_member_started, t.id_first_msg, t.id_last_msg, + t.approved, m.body, m.smileys_enabled, m.subject, m.poster_time, m.id_topic, m.id_msg + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat) + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + WHERE t.id_member_started = {int:current_member}' . (!empty($board) ? ' + AND t.id_board = {int:board}' : '') . (empty($range_limit) ? '' : ' + AND ' . $range_limit) . ' + AND {query_see_board}' . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : ' + AND t.approved = {int:is_approved} AND m.approved = {int:is_approved}') . ' + ORDER BY t.id_first_msg ' . ($reverse ? 'ASC' : 'DESC') . ' + LIMIT ' . $start . ', ' . $maxIndex, + array( + 'current_member' => $memID, + 'is_approved' => 1, + 'board' => $board, + ) + ); + } + else + { + $request = $smcFunc['db_query']('', ' + SELECT + b.id_board, b.name AS bname, c.id_cat, c.name AS cname, m.id_topic, m.id_msg, + t.id_member_started, t.id_first_msg, t.id_last_msg, m.body, m.smileys_enabled, + m.subject, m.poster_time, m.approved + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat) + WHERE m.id_member = {int:current_member}' . (!empty($board) ? ' + AND b.id_board = {int:board}' : '') . (empty($range_limit) ? '' : ' + AND ' . $range_limit) . ' + AND {query_see_board}' . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : ' + AND t.approved = {int:is_approved} AND m.approved = {int:is_approved}') . ' + ORDER BY m.id_msg ' . ($reverse ? 'ASC' : 'DESC') . ' + LIMIT ' . $start . ', ' . $maxIndex, + array( + 'current_member' => $memID, + 'is_approved' => 1, + 'board' => $board, + ) + ); + } + + // Make sure we quit this loop. + if ($smcFunc['db_num_rows']($request) === $maxIndex || $looped) + break; + $looped = true; + $range_limit = ''; + } + + // Start counting at the number of the first message displayed. + $counter = $reverse ? $context['start'] + $maxIndex + 1 : $context['start']; + $context['posts'] = array(); + $board_ids = array('own' => array(), 'any' => array()); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Censor.... + censorText($row['body']); + censorText($row['subject']); + + // Do the code. + $row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']); + + // And the array... + $context['posts'][$counter += $reverse ? -1 : 1] = array( + 'body' => $row['body'], + 'counter' => $counter, + 'alternate' => $counter % 2, + 'category' => array( + 'name' => $row['cname'], + 'id' => $row['id_cat'] + ), + 'board' => array( + 'name' => $row['bname'], + 'id' => $row['id_board'] + ), + 'topic' => $row['id_topic'], + 'subject' => $row['subject'], + 'start' => 'msg' . $row['id_msg'], + 'time' => timeformat($row['poster_time']), + 'timestamp' => forum_time(true, $row['poster_time']), + 'id' => $row['id_msg'], + 'can_reply' => false, + 'can_mark_notify' => false, + 'can_delete' => false, + 'delete_possible' => ($row['id_first_msg'] != $row['id_msg'] || $row['id_last_msg'] == $row['id_msg']) && (empty($modSettings['edit_disable_time']) || $row['poster_time'] + $modSettings['edit_disable_time'] * 60 >= time()), + 'approved' => $row['approved'], + ); + + if ($user_info['id'] == $row['id_member_started']) + $board_ids['own'][$row['id_board']][] = $counter; + $board_ids['any'][$row['id_board']][] = $counter; + } + $smcFunc['db_free_result']($request); + + // All posts were retrieved in reverse order, get them right again. + if ($reverse) + $context['posts'] = array_reverse($context['posts'], true); + + // These are all the permissions that are different from board to board.. + if ($context['is_topics']) + $permissions = array( + 'own' => array( + 'post_reply_own' => 'can_reply', + ), + 'any' => array( + 'post_reply_any' => 'can_reply', + 'mark_any_notify' => 'can_mark_notify', + ) + ); + else + $permissions = array( + 'own' => array( + 'post_reply_own' => 'can_reply', + 'delete_own' => 'can_delete', + ), + 'any' => array( + 'post_reply_any' => 'can_reply', + 'mark_any_notify' => 'can_mark_notify', + 'delete_any' => 'can_delete', + ) + ); + + // For every permission in the own/any lists... + foreach ($permissions as $type => $list) + { + foreach ($list as $permission => $allowed) + { + // Get the boards they can do this on... + $boards = boardsAllowedTo($permission); + + // Hmm, they can do it on all boards, can they? + if (!empty($boards) && $boards[0] == 0) + $boards = array_keys($board_ids[$type]); + + // Now go through each board they can do the permission on. + foreach ($boards as $board_id) + { + // There aren't any posts displayed from this board. + if (!isset($board_ids[$type][$board_id])) + continue; + + // Set the permission to true ;). + foreach ($board_ids[$type][$board_id] as $counter) + $context['posts'][$counter][$allowed] = true; + } + } + } + + // Clean up after posts that cannot be deleted and quoted. + $quote_enabled = empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC'])); + foreach ($context['posts'] as $counter => $dummy) + { + $context['posts'][$counter]['can_delete'] &= $context['posts'][$counter]['delete_possible']; + $context['posts'][$counter]['can_quote'] = $context['posts'][$counter]['can_reply'] && $quote_enabled; + } +} + +// Show all the attachments of a user. +function showAttachments($memID) +{ + global $txt, $user_info, $scripturl, $modSettings, $board; + global $context, $user_profile, $sourcedir, $smcFunc; + + // OBEY permissions! + $boardsAllowed = boardsAllowedTo('view_attachments'); + // Make sure we can't actually see anything... + if (empty($boardsAllowed)) + $boardsAllowed = array(-1); + + // Get the total number of attachments they have posted. + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}attachments AS a + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board}) + WHERE a.attachment_type = {int:attachment_type} + AND a.id_msg != {int:no_message} + AND m.id_member = {int:current_member}' . (!empty($board) ? ' + AND b.id_board = {int:board}' : '') . (!in_array(0, $boardsAllowed) ? ' + AND b.id_board IN ({array_int:boards_list})' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : ' + AND m.approved = {int:is_approved}'), + array( + 'boards_list' => $boardsAllowed, + 'attachment_type' => 0, + 'no_message' => 0, + 'current_member' => $memID, + 'is_approved' => 1, + 'board' => $board, + ) + ); + list ($attachCount) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $maxIndex = (int) $modSettings['defaultMaxMessages']; + + // What about ordering? + $sortTypes = array( + 'filename' => 'a.filename', + 'downloads' => 'a.downloads', + 'subject' => 'm.subject', + 'posted' => 'm.poster_time', + ); + $context['sort_order'] = isset($_GET['sort']) && isset($sortTypes[$_GET['sort']]) ? $_GET['sort'] : 'posted'; + $context['sort_direction'] = isset($_GET['asc']) ? 'up' : 'down'; + + $sort = $sortTypes[$context['sort_order']]; + + // Let's get ourselves a lovely page index. + $context['page_index'] = constructPageIndex($scripturl . '?action=profile;u=' . $memID . ';area=showposts;sa=attach;sort=' . $context['sort_order'] . ($context['sort_direction'] == 'up' ? ';asc' : ''), $context['start'], $attachCount, $maxIndex); + + // Retrieve some attachments. + $request = $smcFunc['db_query']('', ' + SELECT a.id_attach, a.id_msg, a.filename, a.downloads, a.approved, m.id_msg, m.id_topic, + m.id_board, m.poster_time, m.subject, b.name + FROM {db_prefix}attachments AS a + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board}) + WHERE a.attachment_type = {int:attachment_type} + AND a.id_msg != {int:no_message} + AND m.id_member = {int:current_member}' . (!empty($board) ? ' + AND b.id_board = {int:board}' : '') . (!in_array(0, $boardsAllowed) ? ' + AND b.id_board IN ({array_int:boards_list})' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : ' + AND m.approved = {int:is_approved}') . ' + ORDER BY {raw:sort} + LIMIT {int:offset}, {int:limit}', + array( + 'boards_list' => $boardsAllowed, + 'attachment_type' => 0, + 'no_message' => 0, + 'current_member' => $memID, + 'is_approved' => 1, + 'board' => $board, + 'sort' => $sort . ' ' . ($context['sort_direction'] == 'down' ? 'DESC' : 'ASC'), + 'offset' => $context['start'], + 'limit' => $maxIndex, + ) + ); + $context['attachments'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $row['subject'] = censorText($row['subject']); + + $context['attachments'][] = array( + 'id' => $row['id_attach'], + 'filename' => $row['filename'], + 'downloads' => $row['downloads'], + 'subject' => $row['subject'], + 'posted' => timeformat($row['poster_time']), + 'msg' => $row['id_msg'], + 'topic' => $row['id_topic'], + 'board' => $row['id_board'], + 'board_name' => $row['name'], + 'approved' => $row['approved'], + ); + } + $smcFunc['db_free_result']($request); +} + +function statPanel($memID) +{ + global $txt, $scripturl, $context, $user_profile, $user_info, $modSettings, $smcFunc; + + $context['page_title'] = $txt['statPanel_showStats'] . ' ' . $user_profile[$memID]['real_name']; + + // General user statistics. + $timeDays = floor($user_profile[$memID]['total_time_logged_in'] / 86400); + $timeHours = floor(($user_profile[$memID]['total_time_logged_in'] % 86400) / 3600); + $context['time_logged_in'] = ($timeDays > 0 ? $timeDays . $txt['totalTimeLogged2'] : '') . ($timeHours > 0 ? $timeHours . $txt['totalTimeLogged3'] : '') . floor(($user_profile[$memID]['total_time_logged_in'] % 3600) / 60) . $txt['totalTimeLogged4']; + $context['num_posts'] = comma_format($user_profile[$memID]['posts']); + + // Number of topics started. + $result = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}topics + WHERE id_member_started = {int:current_member}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' + AND id_board != {int:recycle_board}' : ''), + array( + 'current_member' => $memID, + 'recycle_board' => $modSettings['recycle_board'], + ) + ); + list ($context['num_topics']) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + // Number polls started. + $result = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}topics + WHERE id_member_started = {int:current_member}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' + AND id_board != {int:recycle_board}' : '') . ' + AND id_poll != {int:no_poll}', + array( + 'current_member' => $memID, + 'recycle_board' => $modSettings['recycle_board'], + 'no_poll' => 0, + ) + ); + list ($context['num_polls']) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + // Number polls voted in. + $result = $smcFunc['db_query']('distinct_poll_votes', ' + SELECT COUNT(DISTINCT id_poll) + FROM {db_prefix}log_polls + WHERE id_member = {int:current_member}', + array( + 'current_member' => $memID, + ) + ); + list ($context['num_votes']) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + // Format the numbers... + $context['num_topics'] = comma_format($context['num_topics']); + $context['num_polls'] = comma_format($context['num_polls']); + $context['num_votes'] = comma_format($context['num_votes']); + + // Grab the board this member posted in most often. + $result = $smcFunc['db_query']('', ' + SELECT + b.id_board, MAX(b.name) AS name, MAX(b.num_posts) AS num_posts, COUNT(*) AS message_count + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + WHERE m.id_member = {int:current_member} + AND b.count_posts = {int:count_enabled} + AND {query_see_board} + GROUP BY b.id_board + ORDER BY message_count DESC + LIMIT 10', + array( + 'current_member' => $memID, + 'count_enabled' => 0, + ) + ); + $context['popular_boards'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $context['popular_boards'][$row['id_board']] = array( + 'id' => $row['id_board'], + 'posts' => $row['message_count'], + 'href' => $scripturl . '?board=' . $row['id_board'] . '.0', + 'link' => '' . $row['name'] . '', + 'posts_percent' => $user_profile[$memID]['posts'] == 0 ? 0 : ($row['message_count'] * 100) / $user_profile[$memID]['posts'], + 'total_posts' => $row['num_posts'], + 'total_posts_member' => $user_profile[$memID]['posts'], + ); + } + $smcFunc['db_free_result']($result); + + // Now get the 10 boards this user has most often participated in. + $result = $smcFunc['db_query']('profile_board_stats', ' + SELECT + b.id_board, MAX(b.name) AS name, b.num_posts, COUNT(*) AS message_count, + CASE WHEN COUNT(*) > MAX(b.num_posts) THEN 1 ELSE COUNT(*) / MAX(b.num_posts) END * 100 AS percentage + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + WHERE m.id_member = {int:current_member} + AND {query_see_board} + GROUP BY b.id_board, b.num_posts + ORDER BY percentage DESC + LIMIT 10', + array( + 'current_member' => $memID, + ) + ); + $context['board_activity'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $context['board_activity'][$row['id_board']] = array( + 'id' => $row['id_board'], + 'posts' => $row['message_count'], + 'href' => $scripturl . '?board=' . $row['id_board'] . '.0', + 'link' => '' . $row['name'] . '', + 'percent' => comma_format((float) $row['percentage'], 2), + 'posts_percent' => (float) $row['percentage'], + 'total_posts' => $row['num_posts'], + ); + } + $smcFunc['db_free_result']($result); + + // Posting activity by time. + $result = $smcFunc['db_query']('user_activity_by_time', ' + SELECT + HOUR(FROM_UNIXTIME(poster_time + {int:time_offset})) AS hour, + COUNT(*) AS post_count + FROM {db_prefix}messages + WHERE id_member = {int:current_member}' . ($modSettings['totalMessages'] > 100000 ? ' + AND id_topic > {int:top_ten_thousand_topics}' : '') . ' + GROUP BY hour', + array( + 'current_member' => $memID, + 'top_ten_thousand_topics' => $modSettings['totalTopics'] - 10000, + 'time_offset' => (($user_info['time_offset'] + $modSettings['time_offset']) * 3600), + ) + ); + $maxPosts = $realPosts = 0; + $context['posts_by_time'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + // Cast as an integer to remove the leading 0. + $row['hour'] = (int) $row['hour']; + + $maxPosts = max($row['post_count'], $maxPosts); + $realPosts += $row['post_count']; + + $context['posts_by_time'][$row['hour']] = array( + 'hour' => $row['hour'], + 'hour_format' => stripos($user_info['time_format'], '%p') === false ? $row['hour'] : date('g a', mktime($row['hour'])), + 'posts' => $row['post_count'], + 'posts_percent' => 0, + 'is_last' => $row['hour'] == 23, + ); + } + $smcFunc['db_free_result']($result); + + if ($maxPosts > 0) + for ($hour = 0; $hour < 24; $hour++) + { + if (!isset($context['posts_by_time'][$hour])) + $context['posts_by_time'][$hour] = array( + 'hour' => $hour, + 'hour_format' => stripos($user_info['time_format'], '%p') === false ? $hour : date('g a', mktime($hour)), + 'posts' => 0, + 'posts_percent' => 0, + 'relative_percent' => 0, + 'is_last' => $hour == 23, + ); + else + { + $context['posts_by_time'][$hour]['posts_percent'] = round(($context['posts_by_time'][$hour]['posts'] * 100) / $realPosts); + $context['posts_by_time'][$hour]['relative_percent'] = round(($context['posts_by_time'][$hour]['posts'] * 100) / $maxPosts); + } + } + + // Put it in the right order. + ksort($context['posts_by_time']); +} + +function tracking($memID) +{ + global $sourcedir, $context, $txt, $scripturl, $modSettings, $user_profile; + + $subActions = array( + 'activity' => array('trackActivity', $txt['trackActivity']), + 'ip' => array('TrackIP', $txt['trackIP']), + 'edits' => array('trackEdits', $txt['trackEdits']), + ); + + $context['tracking_area'] = isset($_GET['sa']) && isset($subActions[$_GET['sa']]) ? $_GET['sa'] : 'activity'; + + if (isset($types[$context['tracking_area']][1])) + require_once($sourcedir . '/' . $types[$context['tracking_area']][1]); + + // Create the tabs for the template. + $context[$context['profile_menu_name']]['tab_data'] = array( + 'title' => $txt['tracking'], + 'description' => $txt['tracking_description'], + 'icon' => 'profile_sm.gif', + 'tabs' => array( + 'activity' => array(), + 'ip' => array(), + 'edits' => array(), + ), + ); + + // Moderation must be on to track edits. + if (empty($modSettings['modlog_enabled'])) + unset($context[$context['profile_menu_name']]['tab_data']['edits']); + + // Set a page title. + $context['page_title'] = $txt['trackUser'] . ' - ' . $subActions[$context['tracking_area']][1] . ' - ' . $user_profile[$memID]['real_name']; + + // Pass on to the actual function. + $context['sub_template'] = $subActions[$context['tracking_area']][0]; + $subActions[$context['tracking_area']][0]($memID); +} + +function trackActivity($memID) +{ + global $scripturl, $txt, $modSettings, $sourcedir; + global $user_profile, $context, $smcFunc; + + // Verify if the user has sufficient permissions. + isAllowedTo('moderate_forum'); + + $context['last_ip'] = $user_profile[$memID]['member_ip']; + if ($context['last_ip'] != $user_profile[$memID]['member_ip2']) + $context['last_ip2'] = $user_profile[$memID]['member_ip2']; + $context['member']['name'] = $user_profile[$memID]['real_name']; + + // Set the options for the list component. + $listOptions = array( + 'id' => 'track_user_list', + 'title' => $txt['errors_by'] . ' ' . $context['member']['name'], + 'items_per_page' => $modSettings['defaultMaxMessages'], + 'no_items_label' => $txt['no_errors_from_user'], + 'base_href' => $scripturl . '?action=profile;area=tracking;sa=user;u=' . $memID, + 'default_sort_col' => 'date', + 'get_items' => array( + 'function' => 'list_getUserErrors', + 'params' => array( + 'le.id_member = {int:current_member}', + array('current_member' => $memID), + ), + ), + 'get_count' => array( + 'function' => 'list_getUserErrorCount', + 'params' => array( + 'id_member = {int:current_member}', + array('current_member' => $memID), + ), + ), + 'columns' => array( + 'ip_address' => array( + 'header' => array( + 'value' => $txt['ip_address'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%1$s', + 'params' => array( + 'ip' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'le.ip', + 'reverse' => 'le.ip DESC', + ), + ), + 'message' => array( + 'header' => array( + 'value' => $txt['message'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%1$s
%2$s', + 'params' => array( + 'message' => false, + 'url' => false, + ), + ), + ), + ), + 'date' => array( + 'header' => array( + 'value' => $txt['date'], + ), + 'data' => array( + 'db' => 'time', + ), + 'sort' => array( + 'default' => 'le.id_error DESC', + 'reverse' => 'le.id_error', + ), + ), + ), + 'additional_rows' => array( + array( + 'position' => 'after_title', + 'value' => $txt['errors_desc'], + 'class' => 'smalltext', + 'style' => 'padding: 2ex;', + ), + ), + ); + + // Create the list for viewing. + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + // If this is a big forum, or a large posting user, let's limit the search. + if ($modSettings['totalMessages'] > 50000 && $user_profile[$memID]['posts'] > 500) + { + $request = $smcFunc['db_query']('', ' + SELECT MAX(id_msg) + FROM {db_prefix}messages AS m + WHERE m.id_member = {int:current_member}', + array( + 'current_member' => $memID, + ) + ); + list ($max_msg_member) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // There's no point worrying ourselves with messages made yonks ago, just get recent ones! + $min_msg_member = max(0, $max_msg_member - $user_profile[$memID]['posts'] * 3); + } + + // Default to at least the ones we know about. + $ips = array( + $user_profile[$memID]['member_ip'], + $user_profile[$memID]['member_ip2'], + ); + + // Get all IP addresses this user has used for his messages. + $request = $smcFunc['db_query']('', ' + SELECT poster_ip + FROM {db_prefix}messages + WHERE id_member = {int:current_member} + ' . (isset($min_msg_member) ? ' + AND id_msg >= {int:min_msg_member} AND id_msg <= {int:max_msg_member}' : '') . ' + GROUP BY poster_ip', + array( + 'current_member' => $memID, + 'min_msg_member' => !empty($min_msg_member) ? $min_msg_member : 0, + 'max_msg_member' => !empty($max_msg_member) ? $max_msg_member : 0, + ) + ); + $context['ips'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $context['ips'][] = '' . $row['poster_ip'] . ''; + $ips[] = $row['poster_ip']; + } + $smcFunc['db_free_result']($request); + + // Now also get the IP addresses from the error messages. + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS error_count, ip + FROM {db_prefix}log_errors + WHERE id_member = {int:current_member} + GROUP BY ip', + array( + 'current_member' => $memID, + ) + ); + $context['error_ips'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $context['error_ips'][] = '' . $row['ip'] . ''; + $ips[] = $row['ip']; + } + $smcFunc['db_free_result']($request); + + // Find other users that might use the same IP. + $ips = array_unique($ips); + $context['members_in_range'] = array(); + if (!empty($ips)) + { + // Get member ID's which are in messages... + $request = $smcFunc['db_query']('', ' + SELECT mem.id_member + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE m.poster_ip IN ({array_string:ip_list}) + GROUP BY mem.id_member + HAVING mem.id_member != {int:current_member}', + array( + 'current_member' => $memID, + 'ip_list' => $ips, + ) + ); + $message_members = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $message_members[] = $row['id_member']; + $smcFunc['db_free_result']($request); + + // Fetch their names, cause of the GROUP BY doesn't like giving us that normally. + if (!empty($message_members)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name + FROM {db_prefix}members + WHERE id_member IN ({array_int:message_members})', + array( + 'message_members' => $message_members, + 'ip_list' => $ips, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['members_in_range'][$row['id_member']] = '' . $row['real_name'] . ''; + $smcFunc['db_free_result']($request); + } + + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name + FROM {db_prefix}members + WHERE id_member != {int:current_member} + AND member_ip IN ({array_string:ip_list})', + array( + 'current_member' => $memID, + 'ip_list' => $ips, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['members_in_range'][$row['id_member']] = '' . $row['real_name'] . ''; + $smcFunc['db_free_result']($request); + } +} + +function list_getUserErrorCount($where, $where_vars = array()) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS error_count + FROM {db_prefix}log_errors + WHERE ' . $where, + $where_vars + ); + list ($count) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $count; +} + +function list_getUserErrors($start, $items_per_page, $sort, $where, $where_vars = array()) +{ + global $smcFunc, $txt, $scripturl; + + // Get a list of error messages from this ip (range). + $request = $smcFunc['db_query']('', ' + SELECT + le.log_time, le.ip, le.url, le.message, IFNULL(mem.id_member, 0) AS id_member, + IFNULL(mem.real_name, {string:guest_title}) AS display_name, mem.member_name + FROM {db_prefix}log_errors AS le + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = le.id_member) + WHERE ' . $where . ' + ORDER BY ' . $sort . ' + LIMIT ' . $start . ', ' . $items_per_page, + array_merge($where_vars, array( + 'guest_title' => $txt['guest_title'], + )) + ); + $error_messages = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $error_messages[] = array( + 'ip' => $row['ip'], + 'member_link' => $row['id_member'] > 0 ? '' . $row['display_name'] . '' : $row['display_name'], + 'message' => strtr($row['message'], array('<span class="remove">' => '', '</span>' => '')), + 'url' => $row['url'], + 'time' => timeformat($row['log_time']), + 'timestamp' => forum_time(true, $row['log_time']), + ); + $smcFunc['db_free_result']($request); + + return $error_messages; +} + +function list_getIPMessageCount($where, $where_vars = array()) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS message_count + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + WHERE {query_see_board} AND ' . $where, + $where_vars + ); + list ($count) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $count; +} + +function list_getIPMessages($start, $items_per_page, $sort, $where, $where_vars = array()) +{ + global $smcFunc, $txt, $scripturl; + + // Get all the messages fitting this where clause. + // !!!SLOW This query is using a filesort. + $request = $smcFunc['db_query']('', ' + SELECT + m.id_msg, m.poster_ip, IFNULL(mem.real_name, m.poster_name) AS display_name, mem.id_member, + m.subject, m.poster_time, m.id_topic, m.id_board + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE {query_see_board} AND ' . $where . ' + ORDER BY ' . $sort . ' + LIMIT ' . $start . ', ' . $items_per_page, + array_merge($where_vars, array( + )) + ); + $messages = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $messages[] = array( + 'ip' => $row['poster_ip'], + 'member_link' => empty($row['id_member']) ? $row['display_name'] : '' . $row['display_name'] . '', + 'board' => array( + 'id' => $row['id_board'], + 'href' => $scripturl . '?board=' . $row['id_board'] + ), + 'topic' => $row['id_topic'], + 'id' => $row['id_msg'], + 'subject' => $row['subject'], + 'time' => timeformat($row['poster_time']), + 'timestamp' => forum_time(true, $row['poster_time']) + ); + $smcFunc['db_free_result']($request); + + return $messages; +} + +function TrackIP($memID = 0) +{ + global $user_profile, $scripturl, $txt, $user_info, $modSettings, $sourcedir; + global $context, $smcFunc; + + // Can the user do this? + isAllowedTo('moderate_forum'); + + if ($memID == 0) + { + $context['ip'] = $user_info['ip']; + loadTemplate('Profile'); + loadLanguage('Profile'); + $context['sub_template'] = 'trackIP'; + $context['page_title'] = $txt['profile']; + $context['base_url'] = $scripturl . '?action=trackip'; + } + else + { + $context['ip'] = $user_profile[$memID]['member_ip']; + $context['base_url'] = $scripturl . '?action=profile;area=tracking;sa=ip;u=' . $memID; + } + + // Searching? + if (isset($_REQUEST['searchip'])) + $context['ip'] = trim($_REQUEST['searchip']); + + if (preg_match('/^\d{1,3}\.(\d{1,3}|\*)\.(\d{1,3}|\*)\.(\d{1,3}|\*)$/', $context['ip']) == 0) + fatal_lang_error('invalid_tracking_ip', false); + + $ip_var = str_replace('*', '%', $context['ip']); + $ip_string = strpos($ip_var, '%') === false ? '= {string:ip_address}' : 'LIKE {string:ip_address}'; + + if (empty($context['tracking_area'])) + $context['page_title'] = $txt['trackIP'] . ' - ' . $context['ip']; + + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name AS display_name, member_ip + FROM {db_prefix}members + WHERE member_ip ' . $ip_string, + array( + 'ip_address' => $ip_var, + ) + ); + $context['ips'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['ips'][$row['member_ip']][] = '' . $row['display_name'] . ''; + $smcFunc['db_free_result']($request); + + ksort($context['ips']); + + // Gonna want this for the list. + require_once($sourcedir . '/Subs-List.php'); + + // Start with the user messages. + $listOptions = array( + 'id' => 'track_message_list', + 'title' => $txt['messages_from_ip'] . ' ' . $context['ip'], + 'start_var_name' => 'messageStart', + 'items_per_page' => $modSettings['defaultMaxMessages'], + 'no_items_label' => $txt['no_messages_from_ip'], + 'base_href' => $context['base_url'] . ';searchip=' . $context['ip'], + 'default_sort_col' => 'date', + 'get_items' => array( + 'function' => 'list_getIPMessages', + 'params' => array( + 'm.poster_ip ' . $ip_string, + array('ip_address' => $ip_var), + ), + ), + 'get_count' => array( + 'function' => 'list_getIPMessageCount', + 'params' => array( + 'm.poster_ip ' . $ip_string, + array('ip_address' => $ip_var), + ), + ), + 'columns' => array( + 'ip_address' => array( + 'header' => array( + 'value' => $txt['ip_address'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%1$s', + 'params' => array( + 'ip' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'INET_ATON(m.poster_ip)', + 'reverse' => 'INET_ATON(m.poster_ip) DESC', + ), + ), + 'poster' => array( + 'header' => array( + 'value' => $txt['poster'], + ), + 'data' => array( + 'db' => 'member_link', + ), + ), + 'subject' => array( + 'header' => array( + 'value' => $txt['subject'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%3$s', + 'params' => array( + 'topic' => false, + 'id' => false, + 'subject' => false, + ), + ), + ), + ), + 'date' => array( + 'header' => array( + 'value' => $txt['date'], + ), + 'data' => array( + 'db' => 'time', + ), + 'sort' => array( + 'default' => 'm.id_msg DESC', + 'reverse' => 'm.id_msg', + ), + ), + ), + 'additional_rows' => array( + array( + 'position' => 'after_title', + 'value' => $txt['messages_from_ip_desc'], + 'class' => 'smalltext', + 'style' => 'padding: 2ex;', + ), + ), + ); + + // Create the messages list. + createList($listOptions); + + // Set the options for the error lists. + $listOptions = array( + 'id' => 'track_user_list', + 'title' => $txt['errors_from_ip'] . ' ' . $context['ip'], + 'start_var_name' => 'errorStart', + 'items_per_page' => $modSettings['defaultMaxMessages'], + 'no_items_label' => $txt['no_errors_from_ip'], + 'base_href' => $context['base_url'] . ';searchip=' . $context['ip'], + 'default_sort_col' => 'date2', + 'get_items' => array( + 'function' => 'list_getUserErrors', + 'params' => array( + 'le.ip ' . $ip_string, + array('ip_address' => $ip_var), + ), + ), + 'get_count' => array( + 'function' => 'list_getUserErrorCount', + 'params' => array( + 'ip ' . $ip_string, + array('ip_address' => $ip_var), + ), + ), + 'columns' => array( + 'ip_address2' => array( + 'header' => array( + 'value' => $txt['ip_address'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%1$s', + 'params' => array( + 'ip' => false, + ), + ), + ), + 'sort' => array( + 'default' => 'INET_ATON(le.ip)', + 'reverse' => 'INET_ATON(le.ip) DESC', + ), + ), + 'display_name' => array( + 'header' => array( + 'value' => $txt['display_name'], + ), + 'data' => array( + 'db' => 'member_link', + ), + ), + 'message' => array( + 'header' => array( + 'value' => $txt['message'], + ), + 'data' => array( + 'sprintf' => array( + 'format' => '%1$s
%2$s', + 'params' => array( + 'message' => false, + 'url' => false, + ), + ), + ), + ), + 'date2' => array( + 'header' => array( + 'value' => $txt['date'], + ), + 'data' => array( + 'db' => 'time', + ), + 'sort' => array( + 'default' => 'le.id_error DESC', + 'reverse' => 'le.id_error', + ), + ), + ), + 'additional_rows' => array( + array( + 'position' => 'after_title', + 'value' => $txt['errors_from_ip_desc'], + 'class' => 'smalltext', + 'style' => 'padding: 2ex;', + ), + ), + ); + + // Create the error list. + createList($listOptions); + + $context['single_ip'] = strpos($context['ip'], '*') === false; + if ($context['single_ip']) + { + $context['whois_servers'] = array( + 'afrinic' => array( + 'name' => $txt['whois_afrinic'], + 'url' => 'http://www.afrinic.net/cgi-bin/whois?searchtext=' . $context['ip'], + 'range' => array(41, 154, 196), + ), + 'apnic' => array( + 'name' => $txt['whois_apnic'], + 'url' => 'http://wq.apnic.net/apnic-bin/whois.pl?searchtext=' . $context['ip'], + 'range' => array(58, 59, 60, 61, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, + 125, 126, 133, 150, 153, 163, 171, 202, 203, 210, 211, 218, 219, 220, 221, 222), + ), + 'arin' => array( + 'name' => $txt['whois_arin'], + 'url' => 'http://whois.arin.net/rest/ip/' . $context['ip'], + 'range' => array(7, 24, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 96, 97, 98, 99, + 128, 129, 130, 131, 132, 134, 135, 136, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148, 149, + 152, 155, 156, 157, 158, 159, 160, 161, 162, 164, 165, 166, 167, 168, 169, 170, 172, 173, 174, + 192, 198, 199, 204, 205, 206, 207, 208, 209, 216), + ), + 'lacnic' => array( + 'name' => $txt['whois_lacnic'], + 'url' => 'http://lacnic.net/cgi-bin/lacnic/whois?query=' . $context['ip'], + 'range' => array(186, 187, 189, 190, 191, 200, 201), + ), + 'ripe' => array( + 'name' => $txt['whois_ripe'], + 'url' => 'https://apps.db.ripe.net/search/query.html?searchtext=' . $context['ip'], + 'range' => array(62, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 141, 145, 151, 188, 193, 194, 195, 212, 213, 217), + ), + ); + + foreach ($context['whois_servers'] as $whois) + { + // Strip off the "decimal point" and anything following... + if (in_array((int) $context['ip'], $whois['range'])) + $context['auto_whois_server'] = $whois; + } + } +} + +function trackEdits($memID) +{ + global $scripturl, $txt, $modSettings, $sourcedir, $context, $smcFunc; + + require_once($sourcedir . '/Subs-List.php'); + + // Get the names of any custom fields. + $request = $smcFunc['db_query']('', ' + SELECT col_name, field_name, bbc + FROM {db_prefix}custom_fields', + array( + ) + ); + $context['custom_field_titles'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['custom_field_titles']['customfield_' . $row['col_name']] = array( + 'title' => $row['field_name'], + 'parse_bbc' => $row['bbc'], + ); + $smcFunc['db_free_result']($request); + + // Set the options for the error lists. + $listOptions = array( + 'id' => 'edit_list', + 'title' => $txt['trackEdits'], + 'items_per_page' => $modSettings['defaultMaxMessages'], + 'no_items_label' => $txt['trackEdit_no_edits'], + 'base_href' => $scripturl . '?action=profile;area=tracking;sa=edits;u=' . $memID, + 'default_sort_col' => 'time', + 'get_items' => array( + 'function' => 'list_getProfileEdits', + 'params' => array( + $memID, + ), + ), + 'get_count' => array( + 'function' => 'list_getProfileEditCount', + 'params' => array( + $memID, + ), + ), + 'columns' => array( + 'action' => array( + 'header' => array( + 'value' => $txt['trackEdit_action'], + ), + 'data' => array( + 'db' => 'action_text', + ), + ), + 'before' => array( + 'header' => array( + 'value' => $txt['trackEdit_before'], + ), + 'data' => array( + 'db' => 'before', + ), + ), + 'after' => array( + 'header' => array( + 'value' => $txt['trackEdit_after'], + ), + 'data' => array( + 'db' => 'after', + ), + ), + 'time' => array( + 'header' => array( + 'value' => $txt['date'], + ), + 'data' => array( + 'db' => 'time', + ), + 'sort' => array( + 'default' => 'id_action DESC', + 'reverse' => 'id_action', + ), + ), + 'applicator' => array( + 'header' => array( + 'value' => $txt['trackEdit_applicator'], + ), + 'data' => array( + 'db' => 'member_link', + ), + ), + ), + ); + + // Create the error list. + createList($listOptions); + + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'edit_list'; +} + +// How many edits? +function list_getProfileEditCount($memID) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS edit_count + FROM {db_prefix}log_actions + WHERE id_log = {int:log_type} + AND id_member = {int:owner}', + array( + 'log_type' => 2, + 'owner' => $memID, + ) + ); + list ($edit_count) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $edit_count; +} + +function list_getProfileEdits($start, $items_per_page, $sort, $memID) +{ + global $smcFunc, $txt, $scripturl, $context; + + // Get a list of error messages from this ip (range). + $request = $smcFunc['db_query']('', ' + SELECT + id_action, id_member, ip, log_time, action, extra + FROM {db_prefix}log_actions + WHERE id_log = {int:log_type} + AND id_member = {int:owner} + ORDER BY ' . $sort . ' + LIMIT ' . $start . ', ' . $items_per_page, + array( + 'log_type' => 2, + 'owner' => $memID, + ) + ); + $edits = array(); + $members = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $extra = @unserialize($row['extra']); + if (!empty($extra['applicator'])) + $members[] = $extra['applicator']; + + // Work out what the name of the action is. + if (isset($txt['trackEdit_action_' . $row['action']])) + $action_text = $txt['trackEdit_action_' . $row['action']]; + elseif (isset($txt[$row['action']])) + $action_text = $txt[$row['action']]; + // Custom field? + elseif (isset($context['custom_field_titles'][$row['action']])) + $action_text = $context['custom_field_titles'][$row['action']]['title']; + else + $action_text = $row['action']; + + // Parse BBC? + $parse_bbc = isset($context['custom_field_titles'][$row['action']]) && $context['custom_field_titles'][$row['action']]['parse_bbc'] ? true : false; + + $edits[] = array( + 'id' => $row['id_action'], + 'ip' => $row['ip'], + 'id_member' => !empty($extra['applicator']) ? $extra['applicator'] : 0, + 'member_link' => $txt['trackEdit_deleted_member'], + 'action' => $row['action'], + 'action_text' => $action_text, + 'before' => !empty($extra['previous']) ? ($parse_bbc ? parse_bbc($extra['previous']) : $extra['previous']) : '', + 'after' => !empty($extra['new']) ? ($parse_bbc ? parse_bbc($extra['new']) : $extra['new']) : '', + 'time' => timeformat($row['log_time']), + ); + } + $smcFunc['db_free_result']($request); + + // Get any member names. + if (!empty($members)) + { + $request = $smcFunc['db_query']('', ' + SELECT + id_member, real_name + FROM {db_prefix}members + WHERE id_member IN ({array_int:members})', + array( + 'members' => $members, + ) + ); + $members = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $members[$row['id_member']] = $row['real_name']; + $smcFunc['db_free_result']($request); + + foreach ($edits as $key => $value) + if (isset($members[$value['id_member']])) + $edits[$key]['member_link'] = '' . $members[$value['id_member']] . ''; + } + + return $edits; +} + +function showPermissions($memID) +{ + global $scripturl, $txt, $board, $modSettings; + global $user_profile, $context, $user_info, $sourcedir, $smcFunc; + + // Verify if the user has sufficient permissions. + isAllowedTo('manage_permissions'); + + loadLanguage('ManagePermissions'); + loadLanguage('Admin'); + loadTemplate('ManageMembers'); + + // Load all the permission profiles. + require_once($sourcedir . '/ManagePermissions.php'); + loadPermissionProfiles(); + + $context['member']['id'] = $memID; + $context['member']['name'] = $user_profile[$memID]['real_name']; + + $context['page_title'] = $txt['showPermissions']; + $board = empty($board) ? 0 : (int) $board; + $context['board'] = $board; + + // Determine which groups this user is in. + if (empty($user_profile[$memID]['additional_groups'])) + $curGroups = array(); + else + $curGroups = explode(',', $user_profile[$memID]['additional_groups']); + $curGroups[] = $user_profile[$memID]['id_group']; + $curGroups[] = $user_profile[$memID]['id_post_group']; + + // Load a list of boards for the jump box - except the defaults. + $request = $smcFunc['db_query']('order_by_board_order', ' + SELECT b.id_board, b.name, b.id_profile, b.member_groups, IFNULL(mods.id_member, 0) AS is_mod + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member}) + WHERE {query_see_board}', + array( + 'current_member' => $memID, + ) + ); + $context['boards'] = array(); + $context['no_access_boards'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (count(array_intersect($curGroups, explode(',', $row['member_groups']))) === 0 && !$row['is_mod']) + $context['no_access_boards'][] = array( + 'id' => $row['id_board'], + 'name' => $row['name'], + 'is_last' => false, + ); + elseif ($row['id_profile'] != 1 || $row['is_mod']) + $context['boards'][$row['id_board']] = array( + 'id' => $row['id_board'], + 'name' => $row['name'], + 'selected' => $board == $row['id_board'], + 'profile' => $row['id_profile'], + 'profile_name' => $context['profiles'][$row['id_profile']]['name'], + ); + } + $smcFunc['db_free_result']($request); + + if (!empty($context['no_access_boards'])) + $context['no_access_boards'][count($context['no_access_boards']) - 1]['is_last'] = true; + + $context['member']['permissions'] = array( + 'general' => array(), + 'board' => array() + ); + + // If you're an admin we know you can do everything, we might as well leave. + $context['member']['has_all_permissions'] = in_array(1, $curGroups); + if ($context['member']['has_all_permissions']) + return; + + $denied = array(); + + // Get all general permissions. + $result = $smcFunc['db_query']('', ' + SELECT p.permission, p.add_deny, mg.group_name, p.id_group + FROM {db_prefix}permissions AS p + LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = p.id_group) + WHERE p.id_group IN ({array_int:group_list}) + ORDER BY p.add_deny DESC, p.permission, mg.min_posts, CASE WHEN mg.id_group < {int:newbie_group} THEN mg.id_group ELSE 4 END, mg.group_name', + array( + 'group_list' => $curGroups, + 'newbie_group' => 4, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + // We don't know about this permission, it doesn't exist :P. + if (!isset($txt['permissionname_' . $row['permission']])) + continue; + + if (empty($row['add_deny'])) + $denied[] = $row['permission']; + + // Permissions that end with _own or _any consist of two parts. + if (in_array(substr($row['permission'], -4), array('_own', '_any')) && isset($txt['permissionname_' . substr($row['permission'], 0, -4)])) + $name = $txt['permissionname_' . substr($row['permission'], 0, -4)] . ' - ' . $txt['permissionname_' . $row['permission']]; + else + $name = $txt['permissionname_' . $row['permission']]; + + // Add this permission if it doesn't exist yet. + if (!isset($context['member']['permissions']['general'][$row['permission']])) + $context['member']['permissions']['general'][$row['permission']] = array( + 'id' => $row['permission'], + 'groups' => array( + 'allowed' => array(), + 'denied' => array() + ), + 'name' => $name, + 'is_denied' => false, + 'is_global' => true, + ); + + // Add the membergroup to either the denied or the allowed groups. + $context['member']['permissions']['general'][$row['permission']]['groups'][empty($row['add_deny']) ? 'denied' : 'allowed'][] = $row['id_group'] == 0 ? $txt['membergroups_members'] : $row['group_name']; + + // Once denied is always denied. + $context['member']['permissions']['general'][$row['permission']]['is_denied'] |= empty($row['add_deny']); + } + $smcFunc['db_free_result']($result); + + $request = $smcFunc['db_query']('', ' + SELECT + bp.add_deny, bp.permission, bp.id_group, mg.group_name' . (empty($board) ? '' : ', + b.id_profile, CASE WHEN mods.id_member IS NULL THEN 0 ELSE 1 END AS is_moderator') . ' + FROM {db_prefix}board_permissions AS bp' . (empty($board) ? '' : ' + INNER JOIN {db_prefix}boards AS b ON (b.id_board = {int:current_board}) + LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member})') . ' + LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = bp.id_group) + WHERE bp.id_profile = {raw:current_profile} + AND bp.id_group IN ({array_int:group_list}' . (empty($board) ? ')' : ', {int:moderator_group}) + AND (mods.id_member IS NOT NULL OR bp.id_group != {int:moderator_group})'), + array( + 'current_board' => $board, + 'group_list' => $curGroups, + 'current_member' => $memID, + 'current_profile' => empty($board) ? '1' : 'b.id_profile', + 'moderator_group' => 3, + ) + ); + + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // We don't know about this permission, it doesn't exist :P. + if (!isset($txt['permissionname_' . $row['permission']])) + continue; + + // The name of the permission using the format 'permission name' - 'own/any topic/event/etc.'. + if (in_array(substr($row['permission'], -4), array('_own', '_any')) && isset($txt['permissionname_' . substr($row['permission'], 0, -4)])) + $name = $txt['permissionname_' . substr($row['permission'], 0, -4)] . ' - ' . $txt['permissionname_' . $row['permission']]; + else + $name = $txt['permissionname_' . $row['permission']]; + + // Create the structure for this permission. + if (!isset($context['member']['permissions']['board'][$row['permission']])) + $context['member']['permissions']['board'][$row['permission']] = array( + 'id' => $row['permission'], + 'groups' => array( + 'allowed' => array(), + 'denied' => array() + ), + 'name' => $name, + 'is_denied' => false, + 'is_global' => empty($board), + ); + + $context['member']['permissions']['board'][$row['permission']]['groups'][empty($row['add_deny']) ? 'denied' : 'allowed'][$row['id_group']] = $row['id_group'] == 0 ? $txt['membergroups_members'] : $row['group_name']; + + $context['member']['permissions']['board'][$row['permission']]['is_denied'] |= empty($row['add_deny']); + } + $smcFunc['db_free_result']($request); +} + +// View a members warnings? +function viewWarning($memID) +{ + global $modSettings, $context, $sourcedir, $txt, $scripturl; + + // Firstly, can we actually even be here? + if (!allowedTo('issue_warning') && (empty($modSettings['warning_show']) || ($modSettings['warning_show'] == 1 && !$context['user']['is_owner']))) + fatal_lang_error('no_access', false); + + // Make sure things which are disabled stay disabled. + $modSettings['warning_watch'] = !empty($modSettings['warning_watch']) ? $modSettings['warning_watch'] : 110; + $modSettings['warning_moderate'] = !empty($modSettings['warning_moderate']) && !empty($modSettings['postmod_active']) ? $modSettings['warning_moderate'] : 110; + $modSettings['warning_mute'] = !empty($modSettings['warning_mute']) ? $modSettings['warning_mute'] : 110; + + // Let's use a generic list to get all the current warnings, and use the issue warnings grab-a-granny thing. + require_once($sourcedir . '/Subs-List.php'); + require_once($sourcedir . '/Profile-Actions.php'); + + $listOptions = array( + 'id' => 'view_warnings', + 'title' => $txt['profile_viewwarning_previous_warnings'], + 'items_per_page' => $modSettings['defaultMaxMessages'], + 'no_items_label' => $txt['profile_viewwarning_no_warnings'], + 'base_href' => $scripturl . '?action=profile;area=viewwarning;sa=user;u=' . $memID, + 'default_sort_col' => 'log_time', + 'get_items' => array( + 'function' => 'list_getUserWarnings', + 'params' => array( + $memID, + ), + ), + 'get_count' => array( + 'function' => 'list_getUserWarningCount', + 'params' => array( + $memID, + ), + ), + 'columns' => array( + 'log_time' => array( + 'header' => array( + 'value' => $txt['profile_warning_previous_time'], + ), + 'data' => array( + 'db' => 'time', + ), + 'sort' => array( + 'default' => 'lc.log_time DESC', + 'reverse' => 'lc.log_time', + ), + ), + 'reason' => array( + 'header' => array( + 'value' => $txt['profile_warning_previous_reason'], + 'style' => 'width: 50%', + ), + 'data' => array( + 'db' => 'reason', + ), + ), + 'level' => array( + 'header' => array( + 'value' => $txt['profile_warning_previous_level'], + ), + 'data' => array( + 'db' => 'counter', + ), + 'sort' => array( + 'default' => 'lc.counter DESC', + 'reverse' => 'lc.counter', + ), + ), + ), + 'additional_rows' => array( + array( + 'position' => 'after_title', + 'value' => $txt['profile_viewwarning_desc'], + 'class' => 'smalltext', + 'style' => 'padding: 2ex;', + ), + ), + ); + + // Create the list for viewing. + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + // Create some common text bits for the template. + $context['level_effects'] = array( + 0 => '', + $modSettings['warning_watch'] => $txt['profile_warning_effect_own_watched'], + $modSettings['warning_moderate'] => $txt['profile_warning_effect_own_moderated'], + $modSettings['warning_mute'] => $txt['profile_warning_effect_own_muted'], + ); + $context['current_level'] = 0; + foreach ($context['level_effects'] as $limit => $dummy) + if ($context['member']['warning'] >= $limit) + $context['current_level'] = $limit; +} + +?> \ No newline at end of file diff --git a/Sources/Profile.php b/Sources/Profile.php new file mode 100644 index 0000000..f70d191 --- /dev/null +++ b/Sources/Profile.php @@ -0,0 +1,769 @@ + array( + 'title' => $txt['profileInfo'], + 'areas' => array( + 'summary' => array( + 'label' => $txt['summary'], + 'file' => 'Profile-View.php', + 'function' => 'summary', + 'permission' => array( + 'own' => 'profile_view_own', + 'any' => 'profile_view_any', + ), + ), + 'statistics' => array( + 'label' => $txt['statPanel'], + 'file' => 'Profile-View.php', + 'function' => 'statPanel', + 'permission' => array( + 'own' => 'profile_view_own', + 'any' => 'profile_view_any', + ), + ), + 'showposts' => array( + 'label' => $txt['showPosts'], + 'file' => 'Profile-View.php', + 'function' => 'showPosts', + 'subsections' => array( + 'messages' => array($txt['showMessages'], array('profile_view_own', 'profile_view_any')), + 'topics' => array($txt['showTopics'], array('profile_view_own', 'profile_view_any')), + 'attach' => array($txt['showAttachments'], array('profile_view_own', 'profile_view_any')), + ), + 'permission' => array( + 'own' => 'profile_view_own', + 'any' => 'profile_view_any', + ), + ), + 'permissions' => array( + 'label' => $txt['showPermissions'], + 'file' => 'Profile-View.php', + 'function' => 'showPermissions', + 'permission' => array( + 'own' => 'manage_permissions', + 'any' => 'manage_permissions', + ), + ), + 'tracking' => array( + 'label' => $txt['trackUser'], + 'file' => 'Profile-View.php', + 'function' => 'tracking', + 'subsections' => array( + 'activity' => array($txt['trackActivity'], 'moderate_forum'), + 'ip' => array($txt['trackIP'], 'moderate_forum'), + 'edits' => array($txt['trackEdits'], 'moderate_forum'), + ), + 'permission' => array( + 'own' => 'moderate_forum', + 'any' => 'moderate_forum', + ), + ), + 'viewwarning' => array( + 'label' => $txt['profile_view_warnings'], + 'enabled' => in_array('w', $context['admin_features']) && $modSettings['warning_settings'][0] == 1 && $cur_profile['warning'] && $context['user']['is_owner'] && !empty($modSettings['warning_show']), + 'file' => 'Profile-View.php', + 'function' => 'viewWarning', + 'permission' => array( + 'own' => 'profile_view_own', + 'any' => 'issue_warning', + ), + ), + ), + ), + 'edit_profile' => array( + 'title' => $txt['profileEdit'], + 'areas' => array( + 'account' => array( + 'label' => $txt['account'], + 'file' => 'Profile-Modify.php', + 'function' => 'account', + 'enabled' => $context['user']['is_admin'] || ($cur_profile['id_group'] != 1 && !in_array(1, explode(',', $cur_profile['additional_groups']))), + 'sc' => 'post', + 'password' => true, + 'permission' => array( + 'own' => array('profile_identity_any', 'profile_identity_own', 'manage_membergroups'), + 'any' => array('profile_identity_any', 'manage_membergroups'), + ), + ), + 'forumprofile' => array( + 'label' => $txt['forumprofile'], + 'file' => 'Profile-Modify.php', + 'function' => 'forumProfile', + 'sc' => 'post', + 'permission' => array( + 'own' => array('profile_extra_any', 'profile_extra_own', 'profile_title_own', 'profile_title_any'), + 'any' => array('profile_extra_any', 'profile_title_any'), + ), + ), + 'theme' => array( + 'label' => $txt['theme'], + 'file' => 'Profile-Modify.php', + 'function' => 'theme', + 'sc' => 'post', + 'permission' => array( + 'own' => array('profile_extra_any', 'profile_extra_own'), + 'any' => array('profile_extra_any'), + ), + ), + 'authentication' => array( + 'label' => $txt['authentication'], + 'file' => 'Profile-Modify.php', + 'function' => 'authentication', + 'enabled' => !empty($modSettings['enableOpenID']) || !empty($cur_profile['openid_uri']), + 'sc' => 'post', + 'hidden' => empty($modSettings['enableOpenID']) && empty($cur_profile['openid_uri']), + 'password' => true, + 'permission' => array( + 'own' => array('profile_identity_any', 'profile_identity_own'), + 'any' => array('profile_identity_any'), + ), + ), + 'notification' => array( + 'label' => $txt['notification'], + 'file' => 'Profile-Modify.php', + 'function' => 'notification', + 'sc' => 'post', + 'permission' => array( + 'own' => array('profile_extra_any', 'profile_extra_own'), + 'any' => array('profile_extra_any'), + ), + ), + // Without profile_extra_own, settings are accessible from the PM section. + 'pmprefs' => array( + 'label' => $txt['pmprefs'], + 'file' => 'Profile-Modify.php', + 'function' => 'pmprefs', + 'enabled' => allowedTo(array('profile_extra_own', 'profile_extra_any')), + 'sc' => 'post', + 'permission' => array( + 'own' => array('pm_read'), + 'any' => array('profile_extra_any'), + ), + ), + 'ignoreboards' => array( + 'label' => $txt['ignoreboards'], + 'file' => 'Profile-Modify.php', + 'function' => 'ignoreboards', + 'enabled' => !empty($modSettings['allow_ignore_boards']), + 'sc' => 'post', + 'permission' => array( + 'own' => array('profile_extra_any', 'profile_extra_own'), + 'any' => array('profile_extra_any'), + ), + ), + 'lists' => array( + 'label' => $txt['editBuddyIgnoreLists'], + 'file' => 'Profile-Modify.php', + 'function' => 'editBuddyIgnoreLists', + 'enabled' => !empty($modSettings['enable_buddylist']) && $context['user']['is_owner'], + 'sc' => 'post', + 'subsections' => array( + 'buddies' => array($txt['editBuddies']), + 'ignore' => array($txt['editIgnoreList']), + ), + 'permission' => array( + 'own' => array('profile_extra_any', 'profile_extra_own'), + 'any' => array(), + ), + ), + 'groupmembership' => array( + 'label' => $txt['groupmembership'], + 'file' => 'Profile-Modify.php', + 'function' => 'groupMembership', + 'enabled' => !empty($modSettings['show_group_membership']) && $context['user']['is_owner'], + 'sc' => 'request', + 'permission' => array( + 'own' => array('profile_view_own'), + 'any' => array('manage_membergroups'), + ), + ), + ), + ), + 'profile_action' => array( + 'title' => $txt['profileAction'], + 'areas' => array( + 'sendpm' => array( + 'label' => $txt['profileSendIm'], + 'custom_url' => $scripturl . '?action=pm;sa=send', + 'permission' => array( + 'own' => array(), + 'any' => array('pm_send'), + ), + ), + 'issuewarning' => array( + 'label' => $txt['profile_issue_warning'], + 'enabled' => in_array('w', $context['admin_features']) && $modSettings['warning_settings'][0] == 1 && (!$context['user']['is_owner'] || $context['user']['is_admin']), + 'file' => 'Profile-Actions.php', + 'function' => 'issueWarning', + 'permission' => array( + 'own' => array('issue_warning'), + 'any' => array('issue_warning'), + ), + ), + 'banuser' => array( + 'label' => $txt['profileBanUser'], + 'custom_url' => $scripturl . '?action=admin;area=ban;sa=add', + 'enabled' => $cur_profile['id_group'] != 1 && !in_array(1, explode(',', $cur_profile['additional_groups'])), + 'permission' => array( + 'own' => array(), + 'any' => array('manage_bans'), + ), + ), + 'subscriptions' => array( + 'label' => $txt['subscriptions'], + 'file' => 'Profile-Actions.php', + 'function' => 'subscriptions', + 'enabled' => !empty($modSettings['paid_enabled']), + 'permission' => array( + 'own' => array('profile_view_own'), + 'any' => array('moderate_forum'), + ), + ), + 'deleteaccount' => array( + 'label' => $txt['deleteAccount'], + 'file' => 'Profile-Actions.php', + 'function' => 'deleteAccount', + 'sc' => 'post', + 'password' => true, + 'permission' => array( + 'own' => array('profile_remove_any', 'profile_remove_own'), + 'any' => array('profile_remove_any'), + ), + ), + 'activateaccount' => array( + 'file' => 'Profile-Actions.php', + 'function' => 'activateAccount', + 'sc' => 'get', + 'permission' => array( + 'own' => array(), + 'any' => array('moderate_forum'), + ), + ), + ), + ), + ); + + // Let them modify profile areas easily. + call_integration_hook('integrate_profile_areas', array(&$profile_areas)); + + // Do some cleaning ready for the menu function. + $context['password_areas'] = array(); + $current_area = isset($_REQUEST['area']) ? $_REQUEST['area'] : ''; + + foreach ($profile_areas as $section_id => $section) + { + // Do a bit of spring cleaning so to speak. + foreach ($section['areas'] as $area_id => $area) + { + // If it said no permissions that meant it wasn't valid! + if (empty($area['permission'][$context['user']['is_owner'] ? 'own' : 'any'])) + $profile_areas[$section_id]['areas'][$area_id]['enabled'] = false; + // Otherwise pick the right set. + else + $profile_areas[$section_id]['areas'][$area_id]['permission'] = $area['permission'][$context['user']['is_owner'] ? 'own' : 'any']; + + // Password required - only if not on OpenID. + if (!empty($area['password'])) + $context['password_areas'][] = $area_id; + } + } + + // Is there an updated message to show? + if (isset($_GET['updated'])) + $context['profile_updated'] = $txt['profile_updated_own']; + + // Set a few options for the menu. + $menuOptions = array( + 'disable_url_session_check' => true, + 'current_area' => $current_area, + 'extra_url_parameters' => array( + 'u' => $context['id_member'], + ), + ); + + // Actually create the menu! + $profile_include_data = createMenu($profile_areas, $menuOptions); + + // No menu means no access. + if (!$profile_include_data && (!$user_info['is_guest'] || validateSession())) + fatal_lang_error('no_access', false); + + // Make a note of the Unique ID for this menu. + $context['profile_menu_id'] = $context['max_menu_id']; + $context['profile_menu_name'] = 'menu_data_' . $context['profile_menu_id']; + + // Set the selected item - now it's been validated. + $current_area = $profile_include_data['current_area']; + $context['menu_item_selected'] = $current_area; + + // Before we go any further, let's work on the area we've said is valid. Note this is done here just in case we every compromise the menu function in error! + $context['completed_save'] = false; + $security_checks = array(); + $found_area = false; + foreach ($profile_areas as $section_id => $section) + { + // Do a bit of spring cleaning so to speak. + foreach ($section['areas'] as $area_id => $area) + { + // Is this our area? + if ($current_area == $area_id) + { + // This can't happen - but is a security check. + if ((isset($section['enabled']) && $section['enabled'] == false) || (isset($area['enabled']) && $area['enabled'] == false)) + fatal_lang_error('no_access', false); + + // Are we saving data in a valid area? + if (isset($area['sc']) && isset($_REQUEST['save'])) + { + $security_checks['session'] = $area['sc']; + $context['completed_save'] = true; + } + + // Does this require session validating? + if (!empty($area['validate'])) + $security_checks['validate'] = true; + + // Permissions for good measure. + if (!empty($profile_include_data['permission'])) + $security_checks['permission'] = $profile_include_data['permission']; + + // Either way got something. + $found_area = true; + } + } + } + + // Oh dear, some serious security lapse is going on here... we'll put a stop to that! + if (!$found_area) + fatal_lang_error('no_access', false); + + // Release this now. + unset($profile_areas); + + // Now the context is setup have we got any security checks to carry out additional to that above? + if (isset($security_checks['session'])) + checkSession($security_checks['session']); + if (isset($security_checks['validate'])) + validateSession(); + if (isset($security_checks['permission'])) + isAllowedTo($security_checks['permission']); + + // File to include? + if (isset($profile_include_data['file'])) + require_once($sourcedir . '/' . $profile_include_data['file']); + + // Make sure that the area function does exist! + if (!isset($profile_include_data['function']) || !function_exists($profile_include_data['function'])) + { + destroyMenu(); + fatal_lang_error('no_access', false); + } + + // Build the link tree. + $context['linktree'][] = array( + 'url' => $scripturl . '?action=profile' . ($memID != $user_info['id'] ? ';u=' . $memID : ''), + 'name' => sprintf($txt['profile_of_username'], $context['member']['name']), + ); + + if (!empty($profile_include_data['label'])) + $context['linktree'][] = array( + 'url' => $scripturl . '?action=profile' . ($memID != $user_info['id'] ? ';u=' . $memID : '') . ';area=' . $profile_include_data['current_area'], + 'name' => $profile_include_data['label'], + ); + + if (!empty($profile_include_data['current_subsection']) && $profile_include_data['subsections'][$profile_include_data['current_subsection']][0] != $profile_include_data['label']) + $context['linktree'][] = array( + 'url' => $scripturl . '?action=profile' . ($memID != $user_info['id'] ? ';u=' . $memID : '') . ';area=' . $profile_include_data['current_area'] . ';sa=' . $profile_include_data['current_subsection'], + 'name' => $profile_include_data['subsections'][$profile_include_data['current_subsection']][0], + ); + + // Set the template for this area and add the profile layer. + $context['sub_template'] = $profile_include_data['function']; + $context['template_layers'][] = 'profile'; + + // All the subactions that require a user password in order to validate. + $check_password = $context['user']['is_owner'] && in_array($profile_include_data['current_area'], $context['password_areas']); + $context['require_password'] = $check_password && empty($user_settings['openid_uri']); + + // If we're in wireless then we have a cut down template... + if (WIRELESS && $context['sub_template'] == 'summary' && WIRELESS_PROTOCOL != 'wap') + $context['sub_template'] = WIRELESS_PROTOCOL . '_profile'; + + // These will get populated soon! + $post_errors = array(); + $profile_vars = array(); + + // Right - are we saving - if so let's save the old data first. + if ($context['completed_save']) + { + // If it's someone elses profile then validate the session. + if (!$context['user']['is_owner']) + validateSession(); + + // Clean up the POST variables. + $_POST = htmltrim__recursive($_POST); + $_POST = htmlspecialchars__recursive($_POST); + + if ($check_password) + { + // If we're using OpenID try to revalidate. + if (!empty($user_settings['openid_uri'])) + { + require_once($sourcedir . '/Subs-OpenID.php'); + smf_openID_revalidate(); + } + else + { + // You didn't even enter a password! + if (trim($_POST['oldpasswrd']) == '') + $post_errors[] = 'no_password'; + + // Since the password got modified due to all the $_POST cleaning, lets undo it so we can get the correct password + $_POST['oldpasswrd'] = un_htmlspecialchars($_POST['oldpasswrd']); + + // Does the integration want to check passwords? + $good_password = in_array(true, call_integration_hook('integrate_verify_password', array($cur_profile['member_name'], $_POST['oldpasswrd'], false)), true); + + // Bad password!!! + if (!$good_password && $user_info['passwd'] != sha1(strtolower($cur_profile['member_name']) . $_POST['oldpasswrd'])) + $post_errors[] = 'bad_password'; + + // Warn other elements not to jump the gun and do custom changes! + if (in_array('bad_password', $post_errors)) + $context['password_auth_failed'] = true; + } + } + + // Change the IP address in the database. + if ($context['user']['is_owner']) + $profile_vars['member_ip'] = $user_info['ip']; + + // Now call the sub-action function... + if ($current_area == 'activateaccount') + { + if (empty($post_errors)) + activateAccount($memID); + } + elseif ($current_area == 'deleteaccount') + { + if (empty($post_errors)) + { + deleteAccount2($profile_vars, $post_errors, $memID); + redirectexit(); + } + } + elseif ($current_area == 'groupmembership' && empty($post_errors)) + { + $msg = groupMembership2($profile_vars, $post_errors, $memID); + + // Whatever we've done, we have nothing else to do here... + redirectexit('action=profile' . ($context['user']['is_owner'] ? '' : ';u=' . $memID) . ';area=groupmembership' . (!empty($msg) ? ';msg=' . $msg : '')); + } + // Authentication changes? + elseif ($current_area == 'authentication') + { + authentication($memID, true); + } + elseif (in_array($current_area, array('account', 'forumprofile', 'theme', 'pmprefs'))) + saveProfileFields(); + else + { + $force_redirect = true; + // Ensure we include this. + require_once($sourcedir . '/Profile-Modify.php'); + saveProfileChanges($profile_vars, $post_errors, $memID); + } + + // There was a problem, let them try to re-enter. + if (!empty($post_errors)) + { + // Load the language file so we can give a nice explanation of the errors. + loadLanguage('Errors'); + $context['post_errors'] = $post_errors; + } + elseif (!empty($profile_vars)) + { + // If we've changed the password, notify any integration that may be listening in. + if (isset($profile_vars['passwd'])) + call_integration_hook('integrate_reset_pass', array($cur_profile['member_name'], $cur_profile['member_name'], $_POST['passwrd2'])); + + updateMemberData($memID, $profile_vars); + + // What if this is the newest member? + if ($modSettings['latestMember'] == $memID) + updateStats('member'); + elseif (isset($profile_vars['real_name'])) + updateSettings(array('memberlist_updated' => time())); + + // If the member changed his/her birthdate, update calendar statistics. + if (isset($profile_vars['birthdate']) || isset($profile_vars['real_name'])) + updateSettings(array( + 'calendar_updated' => time(), + )); + + // Anything worth logging? + if (!empty($context['log_changes']) && !empty($modSettings['modlog_enabled'])) + { + $log_changes = array(); + foreach ($context['log_changes'] as $k => $v) + $log_changes[] = array( + 'action' => $k, + 'id_log' => 2, + 'log_time' => time(), + 'id_member' => $memID, + 'ip' => $user_info['ip'], + 'extra' => serialize(array_merge($v, array('applicator' => $user_info['id']))), + ); + $smcFunc['db_insert']('', + '{db_prefix}log_actions', + array( + 'action' => 'string', 'id_log' => 'int', 'log_time' => 'int', 'id_member' => 'int', 'ip' => 'string-16', + 'extra' => 'string-65534', + ), + $log_changes, + array('id_action') + ); + } + + // Have we got any post save functions to execute? + if (!empty($context['profile_execute_on_save'])) + foreach ($context['profile_execute_on_save'] as $saveFunc) + $saveFunc(); + + // Let them know it worked! + $context['profile_updated'] = $context['user']['is_owner'] ? $txt['profile_updated_own'] : sprintf($txt['profile_updated_else'], $cur_profile['member_name']); + + // Invalidate any cached data. + cache_put_data('member_data-profile-' . $memID, null, 0); + } + } + + // Have some errors for some reason? + if (!empty($post_errors)) + { + // Set all the errors so the template knows what went wrong. + foreach ($post_errors as $error_type) + $context['modify_error'][$error_type] = true; + } + // If it's you then we should redirect upon save. + elseif (!empty($profile_vars) && $context['user']['is_owner']) + redirectexit('action=profile;area=' . $current_area . ';updated'); + elseif (!empty($force_redirect)) + redirectexit('action=profile' . ($context['user']['is_owner'] ? '' : ';u=' . $memID) . ';area=' . $current_area); + + // Call the appropriate subaction function. + $profile_include_data['function']($memID); + + // Set the page title if it's not already set... + if (!isset($context['page_title'])) + $context['page_title'] = $txt['profile'] . (isset($txt[$current_area]) ? ' - ' . $txt[$current_area] : ''); +} + +// Load any custom fields for this area... no area means load all, 'summary' loads all public ones. +function loadCustomFields($memID, $area = 'summary') +{ + global $context, $txt, $user_profile, $smcFunc, $user_info, $settings, $scripturl; + + // Get the right restrictions in place... + $where = 'active = 1'; + if (!allowedTo('admin_forum') && $area != 'register') + { + // If it's the owner they can see two types of private fields, regardless. + if ($memID == $user_info['id']) + $where .= $area == 'summary' ? ' AND private < 3' : ' AND (private = 0 OR private = 2)'; + else + $where .= $area == 'summary' ? ' AND private < 2' : ' AND private = 0'; + } + + if ($area == 'register') + $where .= ' AND show_reg != 0'; + elseif ($area != 'summary') + $where .= ' AND show_profile = {string:area}'; + + // Load all the relevant fields - and data. + $request = $smcFunc['db_query']('', ' + SELECT + col_name, field_name, field_desc, field_type, field_length, field_options, + default_value, bbc, enclose, placement + FROM {db_prefix}custom_fields + WHERE ' . $where, + array( + 'area' => $area, + ) + ); + $context['custom_fields'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Shortcut. + $exists = $memID && isset($user_profile[$memID], $user_profile[$memID]['options'][$row['col_name']]); + $value = $exists ? $user_profile[$memID]['options'][$row['col_name']] : ''; + + // If this was submitted already then make the value the posted version. + if (isset($_POST['customfield']) && isset($_POST['customfield'][$row['col_name']])) + { + $value = $smcFunc['htmlspecialchars']($_POST['customfield'][$row['col_name']]); + if (in_array($row['field_type'], array('select', 'radio'))) + $value = ($options = explode(',', $row['field_options'])) && isset($options[$value]) ? $options[$value] : ''; + } + + // HTML for the input form. + $output_html = $value; + if ($row['field_type'] == 'check') + { + $true = (!$exists && $row['default_value']) || $value; + $input_html = ''; + $output_html = $true ? $txt['yes'] : $txt['no']; + } + elseif ($row['field_type'] == 'select') + { + $input_html = ''; + } + elseif ($row['field_type'] == 'radio') + { + $input_html = '
'; + $options = explode(',', $row['field_options']); + foreach ($options as $k => $v) + { + $true = (!$exists && $row['default_value'] == $v) || $value == $v; + $input_html .= '
'; + if ($true) + $output_html = $v; + } + $input_html .= '
'; + } + elseif ($row['field_type'] == 'text') + { + $input_html = ''; + } + else + { + @list ($rows, $cols) = @explode(',', $row['default_value']); + $input_html = ''; + } + + // Parse BBCode + if ($row['bbc']) + $output_html = parse_bbc($output_html); + elseif($row['field_type'] == 'textarea') + // Allow for newlines at least + $output_html = strtr($output_html, array("\n" => '
')); + + // Enclosing the user input within some other text? + if (!empty($row['enclose']) && !empty($output_html)) + $output_html = strtr($row['enclose'], array( + '{SCRIPTURL}' => $scripturl, + '{IMAGES_URL}' => $settings['images_url'], + '{DEFAULT_IMAGES_URL}' => $settings['default_images_url'], + '{INPUT}' => $output_html, + )); + + $context['custom_fields'][] = array( + 'name' => $row['field_name'], + 'desc' => $row['field_desc'], + 'type' => $row['field_type'], + 'input_html' => $input_html, + 'output_html' => $output_html, + 'placement' => $row['placement'], + 'colname' => $row['col_name'], + 'value' => $value, + ); + } + $smcFunc['db_free_result']($request); +} + +?> \ No newline at end of file diff --git a/Sources/QueryString.php b/Sources/QueryString.php new file mode 100644 index 0000000..5376d13 --- /dev/null +++ b/Sources/QueryString.php @@ -0,0 +1,483 @@ + $value) + if (is_numeric($key)) + unset($_COOKIE[$key]); + + // Get the correct query string. It may be in an environment variable... + if (!isset($_SERVER['QUERY_STRING'])) + $_SERVER['QUERY_STRING'] = getenv('QUERY_STRING'); + + // It seems that sticking a URL after the query string is mighty common, well, it's evil - don't. + if (strpos($_SERVER['QUERY_STRING'], 'http') === 0) + { + header('HTTP/1.1 400 Bad Request'); + die; + } + + // Are we going to need to parse the ; out? + if ((strpos(@ini_get('arg_separator.input'), ';') === false || @version_compare(PHP_VERSION, '4.2.0') == -1) && !empty($_SERVER['QUERY_STRING'])) + { + // Get rid of the old one! You don't know where it's been! + $_GET = array(); + + // Was this redirected? If so, get the REDIRECT_QUERY_STRING. + // Do not urldecode() the querystring, unless you so much wish to break OpenID implementation. :) + $_SERVER['QUERY_STRING'] = substr($_SERVER['QUERY_STRING'], 0, 5) === 'url=/' ? $_SERVER['REDIRECT_QUERY_STRING'] : $_SERVER['QUERY_STRING']; + + // Replace ';' with '&' and '&something&' with '&something=&'. (this is done for compatibility...) + // !!! smflib + parse_str(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr($_SERVER['QUERY_STRING'], array(';?' => '&', ';' => '&', '%00' => '', "\0" => ''))), $_GET); + + // Magic quotes still applies with parse_str - so clean it up. + if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0 && empty($modSettings['integrate_magic_quotes'])) + $_GET = $removeMagicQuoteFunction($_GET); + } + elseif (strpos(@ini_get('arg_separator.input'), ';') !== false) + { + if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0 && empty($modSettings['integrate_magic_quotes'])) + $_GET = $removeMagicQuoteFunction($_GET); + + // Search engines will send action=profile%3Bu=1, which confuses PHP. + foreach ($_GET as $k => $v) + { + if (is_string($v) && strpos($k, ';') !== false) + { + $temp = explode(';', $v); + $_GET[$k] = $temp[0]; + + for ($i = 1, $n = count($temp); $i < $n; $i++) + { + @list ($key, $val) = @explode('=', $temp[$i], 2); + if (!isset($_GET[$key])) + $_GET[$key] = $val; + } + } + + // This helps a lot with integration! + if (strpos($k, '?') === 0) + { + $_GET[substr($k, 1)] = $v; + unset($_GET[$k]); + } + } + } + + // There's no query string, but there is a URL... try to get the data from there. + if (!empty($_SERVER['REQUEST_URI'])) + { + // Remove the .html, assuming there is one. + if (substr($_SERVER['REQUEST_URI'], strrpos($_SERVER['REQUEST_URI'], '.'), 4) == '.htm') + $request = substr($_SERVER['REQUEST_URI'], 0, strrpos($_SERVER['REQUEST_URI'], '.')); + else + $request = $_SERVER['REQUEST_URI']; + + // !!! smflib. + // Replace 'index.php/a,b,c/d/e,f' with 'a=b,c&d=&e=f' and parse it into $_GET. + if (strpos($request, basename($scripturl) . '/') !== false) + { + parse_str(substr(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr(preg_replace('~/([^,/]+),~', '/$1=', substr($request, strpos($request, basename($scripturl)) + strlen(basename($scripturl)))), '/', '&')), 1), $temp); + if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0 && empty($modSettings['integrate_magic_quotes'])) + $temp = $removeMagicQuoteFunction($temp); + $_GET += $temp; + } + } + + // If magic quotes is on we have some work... + if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0) + { + $_ENV = $removeMagicQuoteFunction($_ENV); + $_POST = $removeMagicQuoteFunction($_POST); + $_COOKIE = $removeMagicQuoteFunction($_COOKIE); + foreach ($_FILES as $k => $dummy) + if (isset($_FILES[$k]['name'])) + $_FILES[$k]['name'] = $removeMagicQuoteFunction($_FILES[$k]['name']); + } + + // Add entities to GET. This is kinda like the slashes on everything else. + $_GET = htmlspecialchars__recursive($_GET); + + // Let's not depend on the ini settings... why even have COOKIE in there, anyway? + $_REQUEST = $_POST + $_GET; + + // Make sure $board and $topic are numbers. + if (isset($_REQUEST['board'])) + { + // Make sure its a string and not something else like an array + $_REQUEST['board'] = (string) $_REQUEST['board']; + + // If there's a slash in it, we've got a start value! (old, compatible links.) + if (strpos($_REQUEST['board'], '/') !== false) + list ($_REQUEST['board'], $_REQUEST['start']) = explode('/', $_REQUEST['board']); + // Same idea, but dots. This is the currently used format - ?board=1.0... + elseif (strpos($_REQUEST['board'], '.') !== false) + list ($_REQUEST['board'], $_REQUEST['start']) = explode('.', $_REQUEST['board']); + // Now make absolutely sure it's a number. + $board = (int) $_REQUEST['board']; + $_REQUEST['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0; + + // This is for "Who's Online" because it might come via POST - and it should be an int here. + $_GET['board'] = $board; + } + // Well, $board is going to be a number no matter what. + else + $board = 0; + + // If there's a threadid, it's probably an old YaBB SE link. Flow with it. + if (isset($_REQUEST['threadid']) && !isset($_REQUEST['topic'])) + $_REQUEST['topic'] = $_REQUEST['threadid']; + + // We've got topic! + if (isset($_REQUEST['topic'])) + { + // Make sure its a string and not something else like an array + $_REQUEST['topic'] = (string) $_REQUEST['topic']; + + // Slash means old, beta style, formatting. That's okay though, the link should still work. + if (strpos($_REQUEST['topic'], '/') !== false) + list ($_REQUEST['topic'], $_REQUEST['start']) = explode('/', $_REQUEST['topic']); + // Dots are useful and fun ;). This is ?topic=1.15. + elseif (strpos($_REQUEST['topic'], '.') !== false) + list ($_REQUEST['topic'], $_REQUEST['start']) = explode('.', $_REQUEST['topic']); + + $topic = (int) $_REQUEST['topic']; + + // Now make sure the online log gets the right number. + $_GET['topic'] = $topic; + } + else + $topic = 0; + + // There should be a $_REQUEST['start'], some at least. If you need to default to other than 0, use $_GET['start']. + if (empty($_REQUEST['start']) || $_REQUEST['start'] < 0 || (int) $_REQUEST['start'] > 2147473647) + $_REQUEST['start'] = 0; + + // The action needs to be a string and not an array or anything else + if (isset($_REQUEST['action'])) + $_REQUEST['action'] = (string) $_REQUEST['action']; + if (isset($_GET['action'])) + $_GET['action'] = (string) $_GET['action']; + + // Make sure we have a valid REMOTE_ADDR. + if (!isset($_SERVER['REMOTE_ADDR'])) + { + $_SERVER['REMOTE_ADDR'] = ''; + // A new magic variable to indicate we think this is command line. + $_SERVER['is_cli'] = true; + } + elseif (preg_match('~^((([1]?\d)?\d|2[0-4]\d|25[0-5])\.){3}(([1]?\d)?\d|2[0-4]\d|25[0-5])$~', $_SERVER['REMOTE_ADDR']) === 0) + $_SERVER['REMOTE_ADDR'] = 'unknown'; + + // Try to calculate their most likely IP for those people behind proxies (And the like). + $_SERVER['BAN_CHECK_IP'] = $_SERVER['REMOTE_ADDR']; + + // Find the user's IP address. (but don't let it give you 'unknown'!) + if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_CLIENT_IP']) && (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown)~', $_SERVER['HTTP_CLIENT_IP']) == 0 || preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown)~', $_SERVER['REMOTE_ADDR']) != 0)) + { + // We have both forwarded for AND client IP... check the first forwarded for as the block - only switch if it's better that way. + if (strtok($_SERVER['HTTP_X_FORWARDED_FOR'], '.') != strtok($_SERVER['HTTP_CLIENT_IP'], '.') && '.' . strtok($_SERVER['HTTP_X_FORWARDED_FOR'], '.') == strrchr($_SERVER['HTTP_CLIENT_IP'], '.') && (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown)~', $_SERVER['HTTP_X_FORWARDED_FOR']) == 0 || preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown)~', $_SERVER['REMOTE_ADDR']) != 0)) + $_SERVER['BAN_CHECK_IP'] = implode('.', array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP']))); + else + $_SERVER['BAN_CHECK_IP'] = $_SERVER['HTTP_CLIENT_IP']; + } + if (!empty($_SERVER['HTTP_CLIENT_IP']) && (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown)~', $_SERVER['HTTP_CLIENT_IP']) == 0 || preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown)~', $_SERVER['REMOTE_ADDR']) != 0)) + { + // Since they are in different blocks, it's probably reversed. + if (strtok($_SERVER['REMOTE_ADDR'], '.') != strtok($_SERVER['HTTP_CLIENT_IP'], '.')) + $_SERVER['BAN_CHECK_IP'] = implode('.', array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP']))); + else + $_SERVER['BAN_CHECK_IP'] = $_SERVER['HTTP_CLIENT_IP']; + } + elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) + { + // If there are commas, get the last one.. probably. + if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',') !== false) + { + $ips = array_reverse(explode(', ', $_SERVER['HTTP_X_FORWARDED_FOR'])); + + // Go through each IP... + foreach ($ips as $i => $ip) + { + // Make sure it's in a valid range... + if (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown)~', $ip) != 0 && preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown)~', $_SERVER['REMOTE_ADDR']) == 0) + continue; + + // Otherwise, we've got an IP! + $_SERVER['BAN_CHECK_IP'] = trim($ip); + break; + } + } + // Otherwise just use the only one. + elseif (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown)~', $_SERVER['HTTP_X_FORWARDED_FOR']) == 0 || preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown)~', $_SERVER['REMOTE_ADDR']) != 0) + $_SERVER['BAN_CHECK_IP'] = $_SERVER['HTTP_X_FORWARDED_FOR']; + } + + // Make sure we know the URL of the current request. + if (empty($_SERVER['REQUEST_URI'])) + $_SERVER['REQUEST_URL'] = $scripturl . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''); + elseif (preg_match('~^([^/]+//[^/]+)~', $scripturl, $match) == 1) + $_SERVER['REQUEST_URL'] = $match[1] . $_SERVER['REQUEST_URI']; + else + $_SERVER['REQUEST_URL'] = $_SERVER['REQUEST_URI']; + + // And make sure HTTP_USER_AGENT is set. + $_SERVER['HTTP_USER_AGENT'] = isset($_SERVER['HTTP_USER_AGENT']) ? htmlspecialchars($smcFunc['db_unescape_string']($_SERVER['HTTP_USER_AGENT']), ENT_QUOTES) : ''; + + // Some final checking. + if (preg_match('~^((([1]?\d)?\d|2[0-4]\d|25[0-5])\.){3}(([1]?\d)?\d|2[0-4]\d|25[0-5])$~', $_SERVER['BAN_CHECK_IP']) === 0) + $_SERVER['BAN_CHECK_IP'] = ''; + if ($_SERVER['REMOTE_ADDR'] == 'unknown') + $_SERVER['REMOTE_ADDR'] = ''; +} + +// Adds slashes to the array/variable. Uses two underscores to guard against overloading. +function escapestring__recursive($var) +{ + global $smcFunc; + + if (!is_array($var)) + return $smcFunc['db_escape_string']($var); + + // Reindex the array with slashes. + $new_var = array(); + + // Add slashes to every element, even the indexes! + foreach ($var as $k => $v) + $new_var[$smcFunc['db_escape_string']($k)] = escapestring__recursive($v); + + return $new_var; +} + +// Adds html entities to the array/variable. Uses two underscores to guard against overloading. +function htmlspecialchars__recursive($var, $level = 0) +{ + global $smcFunc; + + if (!is_array($var)) + return isset($smcFunc['htmlspecialchars']) ? $smcFunc['htmlspecialchars']($var, ENT_QUOTES) : htmlspecialchars($var, ENT_QUOTES); + + // Add the htmlspecialchars to every element. + foreach ($var as $k => $v) + $var[$k] = $level > 25 ? null : htmlspecialchars__recursive($v, $level + 1); + + return $var; +} + +// Removes url stuff from the array/variable. Uses two underscores to guard against overloading. +function urldecode__recursive($var, $level = 0) +{ + if (!is_array($var)) + return urldecode($var); + + // Reindex the array... + $new_var = array(); + + // Add the htmlspecialchars to every element. + foreach ($var as $k => $v) + $new_var[urldecode($k)] = $level > 25 ? null : urldecode__recursive($v, $level + 1); + + return $new_var; +} +// Unescapes any array or variable. Two underscores for the normal reason. +function unescapestring__recursive($var) +{ + global $smcFunc; + + if (!is_array($var)) + return $smcFunc['db_unescape_string']($var); + + // Reindex the array without slashes, this time. + $new_var = array(); + + // Strip the slashes from every element. + foreach ($var as $k => $v) + $new_var[$smcFunc['db_unescape_string']($k)] = unescapestring__recursive($v); + + return $new_var; +} + +// Remove slashes recursively... +function stripslashes__recursive($var, $level = 0) +{ + if (!is_array($var)) + return stripslashes($var); + + // Reindex the array without slashes, this time. + $new_var = array(); + + // Strip the slashes from every element. + foreach ($var as $k => $v) + $new_var[stripslashes($k)] = $level > 25 ? null : stripslashes__recursive($v, $level + 1); + + return $new_var; +} + +// Trim a string including the HTML space, character 160. +function htmltrim__recursive($var, $level = 0) +{ + global $smcFunc; + + // Remove spaces (32), tabs (9), returns (13, 10, and 11), nulls (0), and hard spaces. (160) + if (!is_array($var)) + return isset($smcFunc) ? $smcFunc['htmltrim']($var) : trim($var, ' ' . "\t\n\r\x0B" . '\0' . "\xA0"); + + // Go through all the elements and remove the whitespace. + foreach ($var as $k => $v) + $var[$k] = $level > 25 ? null : htmltrim__recursive($v, $level + 1); + + return $var; +} + +// Clean up the XML to make sure it doesn't contain invalid characters. +function cleanXml($string) +{ + global $context; + + // http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char + return preg_replace('~[\x00-\x08\x0B\x0C\x0E-\x19' . ($context['utf8'] ? (@version_compare(PHP_VERSION, '4.3.3') != -1 ? '\x{FFFE}\x{FFFF}' : "\xED\xA0\x80-\xED\xBF\xBF\xEF\xBF\xBE\xEF\xBF\xBF") : '') . ']~' . ($context['utf8'] ? 'u' : ''), '', $string); +} + +function JavaScriptEscape($string) +{ + global $scripturl; + + return '\'' . strtr($string, array( + "\r" => '', + "\n" => '\\n', + "\t" => '\\t', + '\\' => '\\\\', + '\'' => '\\\'', + ' '<\' + \'/', + 'script' => 'scri\'+\'pt', + ' ' $scripturl . '\'+\'', + )) . '\''; +} + +// Rewrite URLs to include the session ID. +function ob_sessrewrite($buffer) +{ + global $scripturl, $modSettings, $user_info, $context; + + // If $scripturl is set to nothing, or the SID is not defined (SSI?) just quit. + if ($scripturl == '' || !defined('SID')) + return $buffer; + + // Do nothing if the session is cookied, or they are a crawler - guests are caught by redirectexit(). This doesn't work below PHP 4.3.0, because it makes the output buffer bigger. + // !!! smflib + if (empty($_COOKIE) && SID != '' && empty($context['browser']['possibly_robot']) && @version_compare(PHP_VERSION, '4.3.0') != -1) + $buffer = preg_replace('/(? \ No newline at end of file diff --git a/Sources/Recent.php b/Sources/Recent.php new file mode 100644 index 0000000..a8e3cc6 --- /dev/null +++ b/Sources/Recent.php @@ -0,0 +1,1323 @@ + 0 ? ' + AND b.id_board != {int:recycle_board}' : '') . ' + AND ml.approved = {int:is_approved} + ORDER BY b.id_msg_updated DESC + LIMIT 1', + array( + 'recycle_board' => $modSettings['recycle_board'], + 'is_approved' => 1, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + return array(); + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Censor the subject and post... + censorText($row['subject']); + censorText($row['body']); + + $row['body'] = strip_tags(strtr(parse_bbc($row['body'], $row['smileys_enabled']), array('
' => ' '))); + if ($smcFunc['strlen']($row['body']) > 128) + $row['body'] = $smcFunc['substr']($row['body'], 0, 128) . '...'; + + // Send the data. + return array( + 'topic' => $row['id_topic'], + 'subject' => $row['subject'], + 'short_subject' => shorten_subject($row['subject'], 24), + 'preview' => $row['body'], + 'time' => timeformat($row['poster_time']), + 'timestamp' => forum_time(true, $row['poster_time']), + 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.new;topicseen#new', + 'link' => '
' . $row['subject'] . '' + ); +} + +// Find the ten most recent posts. +function RecentPosts() +{ + global $txt, $scripturl, $user_info, $context, $modSettings, $sourcedir, $board, $smcFunc; + + loadTemplate('Recent'); + $context['page_title'] = $txt['recent_posts']; + + if (isset($_REQUEST['start']) && $_REQUEST['start'] > 95) + $_REQUEST['start'] = 95; + + $query_parameters = array(); + if (!empty($_REQUEST['c']) && empty($board)) + { + $_REQUEST['c'] = explode(',', $_REQUEST['c']); + foreach ($_REQUEST['c'] as $i => $c) + $_REQUEST['c'][$i] = (int) $c; + + if (count($_REQUEST['c']) == 1) + { + $request = $smcFunc['db_query']('', ' + SELECT name + FROM {db_prefix}categories + WHERE id_cat = {int:id_cat} + LIMIT 1', + array( + 'id_cat' => $_REQUEST['c'][0], + ) + ); + list ($name) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (empty($name)) + fatal_lang_error('no_access', false); + + $context['linktree'][] = array( + 'url' => $scripturl . '#c' . (int) $_REQUEST['c'], + 'name' => $name + ); + } + + $request = $smcFunc['db_query']('', ' + SELECT b.id_board, b.num_posts + FROM {db_prefix}boards AS b + WHERE b.id_cat IN ({array_int:category_list}) + AND {query_see_board}', + array( + 'category_list' => $_REQUEST['c'], + ) + ); + $total_cat_posts = 0; + $boards = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $boards[] = $row['id_board']; + $total_cat_posts += $row['num_posts']; + } + $smcFunc['db_free_result']($request); + + if (empty($boards)) + fatal_lang_error('error_no_boards_selected'); + + $query_this_board = 'b.id_board IN ({array_int:boards})'; + $query_parameters['boards'] = $boards; + + // If this category has a significant number of posts in it... + if ($total_cat_posts > 100 && $total_cat_posts > $modSettings['totalMessages'] / 15) + { + $query_this_board .= ' + AND m.id_msg >= {int:max_id_msg}'; + $query_parameters['max_id_msg'] = max(0, $modSettings['maxMsgID'] - 400 - $_REQUEST['start'] * 7); + } + + $context['page_index'] = constructPageIndex($scripturl . '?action=recent;c=' . implode(',', $_REQUEST['c']), $_REQUEST['start'], min(100, $total_cat_posts), 10, false); + } + elseif (!empty($_REQUEST['boards'])) + { + $_REQUEST['boards'] = explode(',', $_REQUEST['boards']); + foreach ($_REQUEST['boards'] as $i => $b) + $_REQUEST['boards'][$i] = (int) $b; + + $request = $smcFunc['db_query']('', ' + SELECT b.id_board, b.num_posts + FROM {db_prefix}boards AS b + WHERE b.id_board IN ({array_int:board_list}) + AND {query_see_board} + LIMIT {int:limit}', + array( + 'board_list' => $_REQUEST['boards'], + 'limit' => count($_REQUEST['boards']), + ) + ); + $total_posts = 0; + $boards = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $boards[] = $row['id_board']; + $total_posts += $row['num_posts']; + } + $smcFunc['db_free_result']($request); + + if (empty($boards)) + fatal_lang_error('error_no_boards_selected'); + + $query_this_board = 'b.id_board IN ({array_int:boards})'; + $query_parameters['boards'] = $boards; + + // If these boards have a significant number of posts in them... + if ($total_posts > 100 && $total_posts > $modSettings['totalMessages'] / 12) + { + $query_this_board .= ' + AND m.id_msg >= {int:max_id_msg}'; + $query_parameters['max_id_msg'] = max(0, $modSettings['maxMsgID'] - 500 - $_REQUEST['start'] * 9); + } + + $context['page_index'] = constructPageIndex($scripturl . '?action=recent;boards=' . implode(',', $_REQUEST['boards']), $_REQUEST['start'], min(100, $total_posts), 10, false); + } + elseif (!empty($board)) + { + $request = $smcFunc['db_query']('', ' + SELECT num_posts + FROM {db_prefix}boards + WHERE id_board = {int:current_board} + LIMIT 1', + array( + 'current_board' => $board, + ) + ); + list ($total_posts) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $query_this_board = 'b.id_board = {int:board}'; + $query_parameters['board'] = $board; + + // If this board has a significant number of posts in it... + if ($total_posts > 80 && $total_posts > $modSettings['totalMessages'] / 10) + { + $query_this_board .= ' + AND m.id_msg >= {int:max_id_msg}'; + $query_parameters['max_id_msg'] = max(0, $modSettings['maxMsgID'] - 600 - $_REQUEST['start'] * 10); + } + + $context['page_index'] = constructPageIndex($scripturl . '?action=recent;board=' . $board . '.%1$d', $_REQUEST['start'], min(100, $total_posts), 10, true); + } + else + { + $query_this_board = '{query_wanna_see_board}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' + AND b.id_board != {int:recycle_board}' : ''). ' + AND m.id_msg >= {int:max_id_msg}'; + $query_parameters['max_id_msg'] = max(0, $modSettings['maxMsgID'] - 100 - $_REQUEST['start'] * 6); + $query_parameters['recycle_board'] = $modSettings['recycle_board']; + + // !!! This isn't accurate because we ignore the recycle bin. + $context['page_index'] = constructPageIndex($scripturl . '?action=recent', $_REQUEST['start'], min(100, $modSettings['totalMessages']), 10, false); + } + + $context['linktree'][] = array( + 'url' => $scripturl . '?action=recent' . (empty($board) ? (empty($_REQUEST['c']) ? '' : ';c=' . (int) $_REQUEST['c']) : ';board=' . $board . '.0'), + 'name' => $context['page_title'] + ); + + $key = 'recent-' . $user_info['id'] . '-' . md5(serialize(array_diff_key($query_parameters, array('max_id_msg' => 0)))) . '-' . (int) $_REQUEST['start']; + if (empty($modSettings['cache_enable']) || ($messages = cache_get_data($key, 120)) == null) + { + $done = false; + while (!$done) + { + // Find the 10 most recent messages they can *view*. + // !!!SLOW This query is really slow still, probably? + $request = $smcFunc['db_query']('', ' + SELECT m.id_msg + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + WHERE ' . $query_this_board . ' + AND m.approved = {int:is_approved} + ORDER BY m.id_msg DESC + LIMIT {int:offset}, {int:limit}', + array_merge($query_parameters, array( + 'is_approved' => 1, + 'offset' => $_REQUEST['start'], + 'limit' => 10, + )) + ); + // If we don't have 10 results, try again with an unoptimized version covering all rows, and cache the result. + if (isset($query_parameters['max_id_msg']) && $smcFunc['db_num_rows']($request) < 10) + { + $smcFunc['db_free_result']($request); + $query_this_board = str_replace('AND m.id_msg >= {int:max_id_msg}', '', $query_this_board); + $cache_results = true; + unset($query_parameters['max_id_msg']); + } + else + $done = true; + } + $messages = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $messages[] = $row['id_msg']; + $smcFunc['db_free_result']($request); + if (!empty($cache_results)) + cache_put_data($key, $messages, 120); + } + + // Nothing here... Or at least, nothing you can see... + if (empty($messages)) + { + $context['posts'] = array(); + return; + } + + // Get all the most recent posts. + $request = $smcFunc['db_query']('', ' + SELECT + m.id_msg, m.subject, m.smileys_enabled, m.poster_time, m.body, m.id_topic, t.id_board, b.id_cat, + b.name AS bname, c.name AS cname, t.num_replies, m.id_member, m2.id_member AS id_first_member, + IFNULL(mem2.real_name, m2.poster_name) AS first_poster_name, t.id_first_msg, + IFNULL(mem.real_name, m.poster_name) AS poster_name, t.id_last_msg + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + INNER JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat) + INNER JOIN {db_prefix}messages AS m2 ON (m2.id_msg = t.id_first_msg) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + LEFT JOIN {db_prefix}members AS mem2 ON (mem2.id_member = m2.id_member) + WHERE m.id_msg IN ({array_int:message_list}) + ORDER BY m.id_msg DESC + LIMIT ' . count($messages), + array( + 'message_list' => $messages, + ) + ); + $counter = $_REQUEST['start'] + 1; + $context['posts'] = array(); + $board_ids = array('own' => array(), 'any' => array()); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Censor everything. + censorText($row['body']); + censorText($row['subject']); + + // BBC-atize the message. + $row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']); + + // And build the array. + $context['posts'][$row['id_msg']] = array( + 'id' => $row['id_msg'], + 'counter' => $counter++, + 'alternate' => $counter % 2, + 'category' => array( + 'id' => $row['id_cat'], + 'name' => $row['cname'], + 'href' => $scripturl . '#c' . $row['id_cat'], + 'link' => '' . $row['cname'] . '' + ), + 'board' => array( + 'id' => $row['id_board'], + 'name' => $row['bname'], + 'href' => $scripturl . '?board=' . $row['id_board'] . '.0', + 'link' => '' . $row['bname'] . '' + ), + 'topic' => $row['id_topic'], + 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'], + 'link' => '' . $row['subject'] . '', + 'start' => $row['num_replies'], + 'subject' => $row['subject'], + 'time' => timeformat($row['poster_time']), + 'timestamp' => forum_time(true, $row['poster_time']), + 'first_poster' => array( + 'id' => $row['id_first_member'], + 'name' => $row['first_poster_name'], + 'href' => empty($row['id_first_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_first_member'], + 'link' => empty($row['id_first_member']) ? $row['first_poster_name'] : '' . $row['first_poster_name'] . '' + ), + 'poster' => array( + 'id' => $row['id_member'], + 'name' => $row['poster_name'], + 'href' => empty($row['id_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member'], + 'link' => empty($row['id_member']) ? $row['poster_name'] : '' . $row['poster_name'] . '' + ), + 'message' => $row['body'], + 'can_reply' => false, + 'can_mark_notify' => false, + 'can_delete' => false, + 'delete_possible' => ($row['id_first_msg'] != $row['id_msg'] || $row['id_last_msg'] == $row['id_msg']) && (empty($modSettings['edit_disable_time']) || $row['poster_time'] + $modSettings['edit_disable_time'] * 60 >= time()), + ); + + if ($user_info['id'] == $row['id_first_member']) + $board_ids['own'][$row['id_board']][] = $row['id_msg']; + $board_ids['any'][$row['id_board']][] = $row['id_msg']; + } + $smcFunc['db_free_result']($request); + + // There might be - and are - different permissions between any and own. + $permissions = array( + 'own' => array( + 'post_reply_own' => 'can_reply', + 'delete_own' => 'can_delete', + ), + 'any' => array( + 'post_reply_any' => 'can_reply', + 'mark_any_notify' => 'can_mark_notify', + 'delete_any' => 'can_delete', + ) + ); + + // Now go through all the permissions, looking for boards they can do it on. + foreach ($permissions as $type => $list) + { + foreach ($list as $permission => $allowed) + { + // They can do it on these boards... + $boards = boardsAllowedTo($permission); + + // If 0 is the only thing in the array, they can do it everywhere! + if (!empty($boards) && $boards[0] == 0) + $boards = array_keys($board_ids[$type]); + + // Go through the boards, and look for posts they can do this on. + foreach ($boards as $board_id) + { + // Hmm, they have permission, but there are no topics from that board on this page. + if (!isset($board_ids[$type][$board_id])) + continue; + + // Okay, looks like they can do it for these posts. + foreach ($board_ids[$type][$board_id] as $counter) + if ($type == 'any' || $context['posts'][$counter]['poster']['id'] == $user_info['id']) + $context['posts'][$counter][$allowed] = true; + } + } + } + + $quote_enabled = empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC'])); + foreach ($context['posts'] as $counter => $dummy) + { + // Some posts - the first posts - can't just be deleted. + $context['posts'][$counter]['can_delete'] &= $context['posts'][$counter]['delete_possible']; + + // And some cannot be quoted... + $context['posts'][$counter]['can_quote'] = $context['posts'][$counter]['can_reply'] && $quote_enabled; + } +} + +// Find unread topics and replies. +function UnreadTopics() +{ + global $board, $txt, $scripturl, $sourcedir; + global $user_info, $context, $settings, $modSettings, $smcFunc, $options; + + // Guests can't have unread things, we don't know anything about them. + is_not_guest(); + + // Prefetching + lots of MySQL work = bad mojo. + if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') + { + ob_end_clean(); + header('HTTP/1.1 403 Forbidden'); + die; + } + + $context['showing_all_topics'] = isset($_GET['all']); + $context['start'] = (int) $_REQUEST['start']; + $context['topics_per_page'] = empty($modSettings['disableCustomPerPage']) && !empty($options['topics_per_page']) && !WIRELESS ? $options['topics_per_page'] : $modSettings['defaultMaxTopics']; + if ($_REQUEST['action'] == 'unread') + $context['page_title'] = $context['showing_all_topics'] ? $txt['unread_topics_all'] : $txt['unread_topics_visit']; + else + $context['page_title'] = $txt['unread_replies']; + + if ($context['showing_all_topics'] && !empty($context['load_average']) && !empty($modSettings['loadavg_allunread']) && $context['load_average'] >= $modSettings['loadavg_allunread']) + fatal_lang_error('loadavg_allunread_disabled', false); + elseif ($_REQUEST['action'] != 'unread' && !empty($context['load_average']) && !empty($modSettings['loadavg_unreadreplies']) && $context['load_average'] >= $modSettings['loadavg_unreadreplies']) + fatal_lang_error('loadavg_unreadreplies_disabled', false); + elseif (!$context['showing_all_topics'] && $_REQUEST['action'] == 'unread' && !empty($context['load_average']) && !empty($modSettings['loadavg_unread']) && $context['load_average'] >= $modSettings['loadavg_unread']) + fatal_lang_error('loadavg_unread_disabled', false); + + // Parameters for the main query. + $query_parameters = array(); + + // Are we specifying any specific board? + if (isset($_REQUEST['children']) && (!empty($board) || !empty($_REQUEST['boards']))) + { + $boards = array(); + + if (!empty($_REQUEST['boards'])) + { + $_REQUEST['boards'] = explode(',', $_REQUEST['boards']); + foreach ($_REQUEST['boards'] as $b) + $boards[] = (int) $b; + } + + if (!empty($board)) + $boards[] = (int) $board; + + // The easiest thing is to just get all the boards they can see, but since we've specified the top of tree we ignore some of them + $request = $smcFunc['db_query']('', ' + SELECT b.id_board, b.id_parent + FROM {db_prefix}boards AS b + WHERE {query_wanna_see_board} + AND b.child_level > {int:no_child} + AND b.id_board NOT IN ({array_int:boards}) + ORDER BY child_level ASC + ', + array( + 'no_child' => 0, + 'boards' => $boards, + ) + ); + + while ($row = $smcFunc['db_fetch_assoc']($request)) + if (in_array($row['id_parent'], $boards)) + $boards[] = $row['id_board']; + + $smcFunc['db_free_result']($request); + + if (empty($boards)) + fatal_lang_error('error_no_boards_selected'); + + $query_this_board = 'id_board IN ({array_int:boards})'; + $query_parameters['boards'] = $boards; + $context['querystring_board_limits'] = ';boards=' . implode(',', $boards) . ';start=%d'; + } + elseif (!empty($board)) + { + $query_this_board = 'id_board = {int:board}'; + $query_parameters['board'] = $board; + $context['querystring_board_limits'] = ';board=' . $board . '.%1$d'; + } + elseif (!empty($_REQUEST['boards'])) + { + $_REQUEST['boards'] = explode(',', $_REQUEST['boards']); + foreach ($_REQUEST['boards'] as $i => $b) + $_REQUEST['boards'][$i] = (int) $b; + + $request = $smcFunc['db_query']('', ' + SELECT b.id_board + FROM {db_prefix}boards AS b + WHERE {query_see_board} + AND b.id_board IN ({array_int:board_list})', + array( + 'board_list' => $_REQUEST['boards'], + ) + ); + $boards = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $boards[] = $row['id_board']; + $smcFunc['db_free_result']($request); + + if (empty($boards)) + fatal_lang_error('error_no_boards_selected'); + + $query_this_board = 'id_board IN ({array_int:boards})'; + $query_parameters['boards'] = $boards; + $context['querystring_board_limits'] = ';boards=' . implode(',', $boards) . ';start=%1$d'; + } + elseif (!empty($_REQUEST['c'])) + { + $_REQUEST['c'] = explode(',', $_REQUEST['c']); + foreach ($_REQUEST['c'] as $i => $c) + $_REQUEST['c'][$i] = (int) $c; + + $see_board = isset($_REQUEST['action']) && $_REQUEST['action'] == 'unreadreplies' ? 'query_see_board' : 'query_wanna_see_board'; + $request = $smcFunc['db_query']('', ' + SELECT b.id_board + FROM {db_prefix}boards AS b + WHERE ' . $user_info[$see_board] . ' + AND b.id_cat IN ({array_int:id_cat})', + array( + 'id_cat' => $_REQUEST['c'], + ) + ); + $boards = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $boards[] = $row['id_board']; + $smcFunc['db_free_result']($request); + + if (empty($boards)) + fatal_lang_error('error_no_boards_selected'); + + $query_this_board = 'id_board IN ({array_int:boards})'; + $query_parameters['boards'] = $boards; + $context['querystring_board_limits'] = ';c=' . implode(',', $_REQUEST['c']) . ';start=%1$d'; + } + else + { + $see_board = isset($_REQUEST['action']) && $_REQUEST['action'] == 'unreadreplies' ? 'query_see_board' : 'query_wanna_see_board'; + // Don't bother to show deleted posts! + $request = $smcFunc['db_query']('', ' + SELECT b.id_board + FROM {db_prefix}boards AS b + WHERE ' . $user_info[$see_board] . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' + AND b.id_board != {int:recycle_board}' : ''), + array( + 'recycle_board' => (int) $modSettings['recycle_board'], + ) + ); + $boards = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $boards[] = $row['id_board']; + $smcFunc['db_free_result']($request); + + if (empty($boards)) + fatal_lang_error('error_no_boards_selected'); + + $query_this_board = 'id_board IN ({array_int:boards})'; + $query_parameters['boards'] = $boards; + $context['querystring_board_limits'] = ';start=%1$d'; + $context['no_board_limits'] = true; + } + + $sort_methods = array( + 'subject' => 'ms.subject', + 'starter' => 'IFNULL(mems.real_name, ms.poster_name)', + 'replies' => 't.num_replies', + 'views' => 't.num_views', + 'first_post' => 't.id_topic', + 'last_post' => 't.id_last_msg' + ); + + // The default is the most logical: newest first. + if (!isset($_REQUEST['sort']) || !isset($sort_methods[$_REQUEST['sort']])) + { + $context['sort_by'] = 'last_post'; + $_REQUEST['sort'] = 't.id_last_msg'; + $ascending = isset($_REQUEST['asc']); + + $context['querystring_sort_limits'] = $ascending ? ';asc' : ''; + } + // But, for other methods the default sort is ascending. + else + { + $context['sort_by'] = $_REQUEST['sort']; + $_REQUEST['sort'] = $sort_methods[$_REQUEST['sort']]; + $ascending = !isset($_REQUEST['desc']); + + $context['querystring_sort_limits'] = ';sort=' . $context['sort_by'] . ($ascending ? '' : ';desc'); + } + $context['sort_direction'] = $ascending ? 'up' : 'down'; + + if (!empty($_REQUEST['c']) && is_array($_REQUEST['c']) && count($_REQUEST['c']) == 1) + { + $request = $smcFunc['db_query']('', ' + SELECT name + FROM {db_prefix}categories + WHERE id_cat = {int:id_cat} + LIMIT 1', + array( + 'id_cat' => (int) $_REQUEST['c'][0], + ) + ); + list ($name) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $context['linktree'][] = array( + 'url' => $scripturl . '#c' . (int) $_REQUEST['c'][0], + 'name' => $name + ); + } + + $context['linktree'][] = array( + 'url' => $scripturl . '?action=' . $_REQUEST['action'] . sprintf($context['querystring_board_limits'], 0) . $context['querystring_sort_limits'], + 'name' => $_REQUEST['action'] == 'unread' ? $txt['unread_topics_visit'] : $txt['unread_replies'] + ); + + if ($context['showing_all_topics']) + $context['linktree'][] = array( + 'url' => $scripturl . '?action=' . $_REQUEST['action'] . ';all' . sprintf($context['querystring_board_limits'], 0) . $context['querystring_sort_limits'], + 'name' => $txt['unread_topics_all'] + ); + else + $txt['unread_topics_visit_none'] = strtr($txt['unread_topics_visit_none'], array('?action=unread;all' => '?action=unread;all' . sprintf($context['querystring_board_limits'], 0) . $context['querystring_sort_limits'])); + + if (WIRELESS) + $context['sub_template'] = WIRELESS_PROTOCOL . '_recent'; + else + { + loadTemplate('Recent'); + $context['sub_template'] = $_REQUEST['action'] == 'unread' ? 'unread' : 'replies'; + } + + // Setup the default topic icons... for checking they exist and the like ;) + $stable_icons = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'moved', 'recycled', 'wireless', 'clip'); + $context['icon_sources'] = array(); + foreach ($stable_icons as $icon) + $context['icon_sources'][$icon] = 'images_url'; + + $is_topics = $_REQUEST['action'] == 'unread'; + + // This part is the same for each query. + $select_clause = ' + ms.subject AS first_subject, ms.poster_time AS first_poster_time, ms.id_topic, t.id_board, b.name AS bname, + t.num_replies, t.num_views, ms.id_member AS id_first_member, ml.id_member AS id_last_member, + ml.poster_time AS last_poster_time, IFNULL(mems.real_name, ms.poster_name) AS first_poster_name, + IFNULL(meml.real_name, ml.poster_name) AS last_poster_name, ml.subject AS last_subject, + ml.icon AS last_icon, ms.icon AS first_icon, t.id_poll, t.is_sticky, t.locked, ml.modified_time AS last_modified_time, + IFNULL(lt.id_msg, IFNULL(lmr.id_msg, -1)) + 1 AS new_from, SUBSTRING(ml.body, 1, 385) AS last_body, + SUBSTRING(ms.body, 1, 385) AS first_body, ml.smileys_enabled AS last_smileys, ms.smileys_enabled AS first_smileys, t.id_first_msg, t.id_last_msg'; + + if ($context['showing_all_topics']) + { + if (!empty($board)) + { + $request = $smcFunc['db_query']('', ' + SELECT MIN(id_msg) + FROM {db_prefix}log_mark_read + WHERE id_member = {int:current_member} + AND id_board = {int:current_board}', + array( + 'current_board' => $board, + 'current_member' => $user_info['id'], + ) + ); + list ($earliest_msg) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + else + { + $request = $smcFunc['db_query']('', ' + SELECT MIN(lmr.id_msg) + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = b.id_board AND lmr.id_member = {int:current_member}) + WHERE {query_see_board}', + array( + 'current_member' => $user_info['id'], + ) + ); + list ($earliest_msg) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + // This is needed in case of topics marked unread. + if (empty($earliest_msg)) + $earliest_msg = 0; + else + { + // Using caching, when possible, to ignore the below slow query. + if (isset($_SESSION['cached_log_time']) && $_SESSION['cached_log_time'][0] + 45 > time()) + $earliest_msg2 = $_SESSION['cached_log_time'][1]; + else + { + // This query is pretty slow, but it's needed to ensure nothing crucial is ignored. + $request = $smcFunc['db_query']('', ' + SELECT MIN(id_msg) + FROM {db_prefix}log_topics + WHERE id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + ) + ); + list ($earliest_msg2) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // In theory this could be zero, if the first ever post is unread, so fudge it ;) + if ($earliest_msg2 == 0) + $earliest_msg2 = -1; + + $_SESSION['cached_log_time'] = array(time(), $earliest_msg2); + } + + $earliest_msg = min($earliest_msg2, $earliest_msg); + } + } + + // !!! Add modified_time in for log_time check? + + if ($modSettings['totalMessages'] > 100000 && $context['showing_all_topics']) + { + $smcFunc['db_query']('', ' + DROP TABLE IF EXISTS {db_prefix}log_topics_unread', + array( + ) + ); + + // Let's copy things out of the log_topics table, to reduce searching. + $have_temp_table = $smcFunc['db_query']('', ' + CREATE TEMPORARY TABLE {db_prefix}log_topics_unread ( + PRIMARY KEY (id_topic) + ) + SELECT lt.id_topic, lt.id_msg + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic) + WHERE lt.id_member = {int:current_member} + AND t.' . $query_this_board . (empty($earliest_msg) ? '' : ' + AND t.id_last_msg > {int:earliest_msg}') . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved}' : ''), + array_merge($query_parameters, array( + 'current_member' => $user_info['id'], + 'earliest_msg' => !empty($earliest_msg) ? $earliest_msg : 0, + 'is_approved' => 1, + 'db_error_skip' => true, + )) + ) !== false; + } + else + $have_temp_table = false; + + if ($context['showing_all_topics'] && $have_temp_table) + { + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*), MIN(t.id_last_msg) + FROM {db_prefix}topics AS t + LEFT JOIN {db_prefix}log_topics_unread AS lt ON (lt.id_topic = t.id_topic) + LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member}) + WHERE t.' . $query_this_board . (!empty($earliest_msg) ? ' + AND t.id_last_msg > {int:earliest_msg}' : '') . ' + AND IFNULL(lt.id_msg, IFNULL(lmr.id_msg, 0)) < t.id_last_msg' . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved}' : ''), + array_merge($query_parameters, array( + 'current_member' => $user_info['id'], + 'earliest_msg' => !empty($earliest_msg) ? $earliest_msg : 0, + 'is_approved' => 1, + )) + ); + list ($num_topics, $min_message) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Make sure the starting place makes sense and construct the page index. + $context['page_index'] = constructPageIndex($scripturl . '?action=' . $_REQUEST['action'] . ($context['showing_all_topics'] ? ';all' : '') . $context['querystring_board_limits'] . $context['querystring_sort_limits'], $_REQUEST['start'], $num_topics, $context['topics_per_page'], true); + $context['current_page'] = (int) $_REQUEST['start'] / $context['topics_per_page']; + + $context['links'] = array( + 'first' => $_REQUEST['start'] >= $context['topics_per_page'] ? $scripturl . '?action=' . $_REQUEST['action'] . ($context['showing_all_topics'] ? ';all' : '') . sprintf($context['querystring_board_limits'], 0) . $context['querystring_sort_limits'] : '', + 'prev' => $_REQUEST['start'] >= $context['topics_per_page'] ? $scripturl . '?action=' . $_REQUEST['action'] . ($context['showing_all_topics'] ? ';all' : '') . sprintf($context['querystring_board_limits'], $_REQUEST['start'] - $context['topics_per_page']) . $context['querystring_sort_limits'] : '', + 'next' => $_REQUEST['start'] + $context['topics_per_page'] < $num_topics ? $scripturl . '?action=' . $_REQUEST['action'] . ($context['showing_all_topics'] ? ';all' : '') . sprintf($context['querystring_board_limits'], $_REQUEST['start'] + $context['topics_per_page']) . $context['querystring_sort_limits'] : '', + 'last' => $_REQUEST['start'] + $context['topics_per_page'] < $num_topics ? $scripturl . '?action=' . $_REQUEST['action'] . ($context['showing_all_topics'] ? ';all' : '') . sprintf($context['querystring_board_limits'], floor(($num_topics - 1) / $context['topics_per_page']) * $context['topics_per_page']) . $context['querystring_sort_limits'] : '', + 'up' => $scripturl, + ); + $context['page_info'] = array( + 'current_page' => $_REQUEST['start'] / $context['topics_per_page'] + 1, + 'num_pages' => floor(($num_topics - 1) / $context['topics_per_page']) + 1 + ); + + if ($num_topics == 0) + { + // Mark the boards as read if there are no unread topics! + require_once($sourcedir . '/Subs-Boards.php'); + markBoardsRead(empty($boards) ? $board : $boards); + + $context['topics'] = array(); + if ($context['querystring_board_limits'] == ';start=%1$d') + $context['querystring_board_limits'] = ''; + else + $context['querystring_board_limits'] = sprintf($context['querystring_board_limits'], $_REQUEST['start']); + return; + } + else + $min_message = (int) $min_message; + + $request = $smcFunc['db_query']('substring', ' + SELECT ' . $select_clause . ' + FROM {db_prefix}messages AS ms + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ms.id_topic AND t.id_first_msg = ms.id_msg) + INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg) + LEFT JOIN {db_prefix}boards AS b ON (b.id_board = ms.id_board) + LEFT JOIN {db_prefix}members AS mems ON (mems.id_member = ms.id_member) + LEFT JOIN {db_prefix}members AS meml ON (meml.id_member = ml.id_member) + LEFT JOIN {db_prefix}log_topics_unread AS lt ON (lt.id_topic = t.id_topic) + LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member}) + WHERE b.' . $query_this_board . ' + AND t.id_last_msg >= {int:min_message} + AND IFNULL(lt.id_msg, IFNULL(lmr.id_msg, 0)) < t.id_last_msg' . ($modSettings['postmod_active'] ? ' + AND ms.approved = {int:is_approved}' : '') . ' + ORDER BY {raw:sort} + LIMIT {int:offset}, {int:limit}', + array_merge($query_parameters, array( + 'current_member' => $user_info['id'], + 'min_message' => $min_message, + 'is_approved' => 1, + 'sort' => $_REQUEST['sort'] . ($ascending ? '' : ' DESC'), + 'offset' => $_REQUEST['start'], + 'limit' => $context['topics_per_page'], + )) + ); + } + elseif ($is_topics) + { + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*), MIN(t.id_last_msg) + FROM {db_prefix}topics AS t' . (!empty($have_temp_table) ? ' + LEFT JOIN {db_prefix}log_topics_unread AS lt ON (lt.id_topic = t.id_topic)' : ' + LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})') . ' + LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member}) + WHERE t.' . $query_this_board . ($context['showing_all_topics'] && !empty($earliest_msg) ? ' + AND t.id_last_msg > {int:earliest_msg}' : (!$context['showing_all_topics'] && empty($_SESSION['first_login']) ? ' + AND t.id_last_msg > {int:id_msg_last_visit}' : '')) . ' + AND IFNULL(lt.id_msg, IFNULL(lmr.id_msg, 0)) < t.id_last_msg' . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved}' : ''), + array_merge($query_parameters, array( + 'current_member' => $user_info['id'], + 'earliest_msg' => !empty($earliest_msg) ? $earliest_msg : 0, + 'id_msg_last_visit' => $_SESSION['id_msg_last_visit'], + 'is_approved' => 1, + )) + ); + list ($num_topics, $min_message) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Make sure the starting place makes sense and construct the page index. + $context['page_index'] = constructPageIndex($scripturl . '?action=' . $_REQUEST['action'] . ($context['showing_all_topics'] ? ';all' : '') . $context['querystring_board_limits'] . $context['querystring_sort_limits'], $_REQUEST['start'], $num_topics, $context['topics_per_page'], true); + $context['current_page'] = (int) $_REQUEST['start'] / $context['topics_per_page']; + + $context['links'] = array( + 'first' => $_REQUEST['start'] >= $context['topics_per_page'] ? $scripturl . '?action=' . $_REQUEST['action'] . ($context['showing_all_topics'] ? ';all' : '') . sprintf($context['querystring_board_limits'], 0) . $context['querystring_sort_limits'] : '', + 'prev' => $_REQUEST['start'] >= $context['topics_per_page'] ? $scripturl . '?action=' . $_REQUEST['action'] . ($context['showing_all_topics'] ? ';all' : '') . sprintf($context['querystring_board_limits'], $_REQUEST['start'] - $context['topics_per_page']) . $context['querystring_sort_limits'] : '', + 'next' => $_REQUEST['start'] + $context['topics_per_page'] < $num_topics ? $scripturl . '?action=' . $_REQUEST['action'] . ($context['showing_all_topics'] ? ';all' : '') . sprintf($context['querystring_board_limits'], $_REQUEST['start'] + $context['topics_per_page']) . $context['querystring_sort_limits'] : '', + 'last' => $_REQUEST['start'] + $context['topics_per_page'] < $num_topics ? $scripturl . '?action=' . $_REQUEST['action'] . ($context['showing_all_topics'] ? ';all' : '') . sprintf($context['querystring_board_limits'], floor(($num_topics - 1) / $context['topics_per_page']) * $context['topics_per_page']) . $context['querystring_sort_limits'] : '', + 'up' => $scripturl, + ); + $context['page_info'] = array( + 'current_page' => $_REQUEST['start'] / $context['topics_per_page'] + 1, + 'num_pages' => floor(($num_topics - 1) / $context['topics_per_page']) + 1 + ); + + if ($num_topics == 0) + { + // Is this an all topics query? + if ($context['showing_all_topics']) + { + // Since there are no unread topics, mark the boards as read! + require_once($sourcedir . '/Subs-Boards.php'); + markBoardsRead(empty($boards) ? $board : $boards); + } + + $context['topics'] = array(); + if ($context['querystring_board_limits'] == ';start=%d') + $context['querystring_board_limits'] = ''; + else + $context['querystring_board_limits'] = sprintf($context['querystring_board_limits'], $_REQUEST['start']); + return; + } + else + $min_message = (int) $min_message; + + $request = $smcFunc['db_query']('substring', ' + SELECT ' . $select_clause . ' + FROM {db_prefix}messages AS ms + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ms.id_topic AND t.id_first_msg = ms.id_msg) + INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg) + LEFT JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + LEFT JOIN {db_prefix}members AS mems ON (mems.id_member = ms.id_member) + LEFT JOIN {db_prefix}members AS meml ON (meml.id_member = ml.id_member)' . (!empty($have_temp_table) ? ' + LEFT JOIN {db_prefix}log_topics_unread AS lt ON (lt.id_topic = t.id_topic)' : ' + LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})') . ' + LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member}) + WHERE t.' . $query_this_board . ' + AND t.id_last_msg >= {int:min_message} + AND IFNULL(lt.id_msg, IFNULL(lmr.id_msg, 0)) < ml.id_msg' . ($modSettings['postmod_active'] ? ' + AND ms.approved = {int:is_approved}' : '') . ' + ORDER BY {raw:order} + LIMIT {int:offset}, {int:limit}', + array_merge($query_parameters, array( + 'current_member' => $user_info['id'], + 'min_message' => $min_message, + 'is_approved' => 1, + 'order' => $_REQUEST['sort'] . ($ascending ? '' : ' DESC'), + 'offset' => $_REQUEST['start'], + 'limit' => $context['topics_per_page'], + )) + ); + } + else + { + if ($modSettings['totalMessages'] > 100000) + { + $smcFunc['db_query']('', ' + DROP TABLE IF EXISTS {db_prefix}topics_posted_in', + array( + ) + ); + + $smcFunc['db_query']('', ' + DROP TABLE IF EXISTS {db_prefix}log_topics_posted_in', + array( + ) + ); + + $sortKey_joins = array( + 'ms.subject' => ' + INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)', + 'IFNULL(mems.real_name, ms.poster_name)' => ' + INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg) + LEFT JOIN {db_prefix}members AS mems ON (mems.id_member = ms.id_member)', + ); + + // The main benefit of this temporary table is not that it's faster; it's that it avoids locks later. + $have_temp_table = $smcFunc['db_query']('', ' + CREATE TEMPORARY TABLE {db_prefix}topics_posted_in ( + id_topic mediumint(8) unsigned NOT NULL default {string:string_zero}, + id_board smallint(5) unsigned NOT NULL default {string:string_zero}, + id_last_msg int(10) unsigned NOT NULL default {string:string_zero}, + id_msg int(10) unsigned NOT NULL default {string:string_zero}, + PRIMARY KEY (id_topic) + ) + SELECT t.id_topic, t.id_board, t.id_last_msg, IFNULL(lmr.id_msg, 0) AS id_msg' . (!in_array($_REQUEST['sort'], array('t.id_last_msg', 't.id_topic')) ? ', ' . $_REQUEST['sort'] . ' AS sort_key' : '') . ' + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member})' . (isset($sortKey_joins[$_REQUEST['sort']]) ? $sortKey_joins[$_REQUEST['sort']] : '') . ' + WHERE m.id_member = {int:current_member}' . (!empty($board) ? ' + AND t.id_board = {int:current_board}' : '') . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved}' : '') . ' + GROUP BY m.id_topic', + array( + 'current_board' => $board, + 'current_member' => $user_info['id'], + 'is_approved' => 1, + 'string_zero' => '0', + 'db_error_skip' => true, + ) + ) !== false; + + // If that worked, create a sample of the log_topics table too. + if ($have_temp_table) + $have_temp_table = $smcFunc['db_query']('', ' + CREATE TEMPORARY TABLE {db_prefix}log_topics_posted_in ( + PRIMARY KEY (id_topic) + ) + SELECT lt.id_topic, lt.id_msg + FROM {db_prefix}log_topics AS lt + INNER JOIN {db_prefix}topics_posted_in AS pi ON (pi.id_topic = lt.id_topic) + WHERE lt.id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'db_error_skip' => true, + ) + ) !== false; + } + + if (!empty($have_temp_table)) + { + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}topics_posted_in AS pi + LEFT JOIN {db_prefix}log_topics_posted_in AS lt ON (lt.id_topic = pi.id_topic) + WHERE pi.' . $query_this_board . ' + AND IFNULL(lt.id_msg, pi.id_msg) < pi.id_last_msg', + array_merge($query_parameters, array( + )) + ); + list ($num_topics) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + else + { + $request = $smcFunc['db_query']('unread_fetch_topic_count', ' + SELECT COUNT(DISTINCT t.id_topic), MIN(t.id_last_msg) + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_topic = t.id_topic) + LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member}) + LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member}) + WHERE t.' . $query_this_board . ' + AND m.id_member = {int:current_member} + AND IFNULL(lt.id_msg, IFNULL(lmr.id_msg, 0)) < t.id_last_msg' . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved}' : ''), + array_merge($query_parameters, array( + 'current_member' => $user_info['id'], + 'is_approved' => 1, + )) + ); + list ($num_topics, $min_message) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + // Make sure the starting place makes sense and construct the page index. + $context['page_index'] = constructPageIndex($scripturl . '?action=' . $_REQUEST['action'] . $context['querystring_board_limits'] . $context['querystring_sort_limits'], $_REQUEST['start'], $num_topics, $context['topics_per_page'], true); + $context['current_page'] = (int) $_REQUEST['start'] / $context['topics_per_page']; + + $context['links'] = array( + 'first' => $_REQUEST['start'] >= $context['topics_per_page'] ? $scripturl . '?action=' . $_REQUEST['action'] . ($context['showing_all_topics'] ? ';all' : '') . sprintf($context['querystring_board_limits'], 0) . $context['querystring_sort_limits'] : '', + 'prev' => $_REQUEST['start'] >= $context['topics_per_page'] ? $scripturl . '?action=' . $_REQUEST['action'] . ($context['showing_all_topics'] ? ';all' : '') . sprintf($context['querystring_board_limits'], $_REQUEST['start'] - $context['topics_per_page']) . $context['querystring_sort_limits'] : '', + 'next' => $_REQUEST['start'] + $context['topics_per_page'] < $num_topics ? $scripturl . '?action=' . $_REQUEST['action'] . ($context['showing_all_topics'] ? ';all' : '') . sprintf($context['querystring_board_limits'], $_REQUEST['start'] + $context['topics_per_page']) . $context['querystring_sort_limits'] : '', + 'last' => $_REQUEST['start'] + $context['topics_per_page'] < $num_topics ? $scripturl . '?action=' . $_REQUEST['action'] . ($context['showing_all_topics'] ? ';all' : '') . sprintf($context['querystring_board_limits'], floor(($num_topics - 1) / $context['topics_per_page']) * $context['topics_per_page']) . $context['querystring_sort_limits'] : '', + 'up' => $scripturl, + ); + $context['page_info'] = array( + 'current_page' => $_REQUEST['start'] / $context['topics_per_page'] + 1, + 'num_pages' => floor(($num_topics - 1) / $context['topics_per_page']) + 1 + ); + + if ($num_topics == 0) + { + $context['topics'] = array(); + if ($context['querystring_board_limits'] == ';start=%d') + $context['querystring_board_limits'] = ''; + else + $context['querystring_board_limits'] = sprintf($context['querystring_board_limits'], $_REQUEST['start']); + return; + } + + if (!empty($have_temp_table)) + $request = $smcFunc['db_query']('', ' + SELECT t.id_topic + FROM {db_prefix}topics_posted_in AS t + LEFT JOIN {db_prefix}log_topics_posted_in AS lt ON (lt.id_topic = t.id_topic) + WHERE t.' . $query_this_board . ' + AND IFNULL(lt.id_msg, t.id_msg) < t.id_last_msg + ORDER BY {raw:order} + LIMIT {int:offset}, {int:limit}', + array_merge($query_parameters, array( + 'order' => (in_array($_REQUEST['sort'], array('t.id_last_msg', 't.id_topic')) ? $_REQUEST['sort'] : 't.sort_key') . ($ascending ? '' : ' DESC'), + 'offset' => $_REQUEST['start'], + 'limit' => $context['topics_per_page'], + )) + ); + else + $request = $smcFunc['db_query']('unread_replies', ' + SELECT DISTINCT t.id_topic + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_topic = t.id_topic AND m.id_member = {int:current_member})' . (strpos($_REQUEST['sort'], 'ms.') === false ? '' : ' + INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)') . (strpos($_REQUEST['sort'], 'mems.') === false ? '' : ' + LEFT JOIN {db_prefix}members AS mems ON (mems.id_member = ms.id_member)') . ' + LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member}) + LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member}) + WHERE t.' . $query_this_board . ' + AND t.id_last_msg >= {int:min_message} + AND (IFNULL(lt.id_msg, IFNULL(lmr.id_msg, 0))) < t.id_last_msg + AND t.approved = {int:is_approved} + ORDER BY {raw:order} + LIMIT {int:offset}, {int:limit}', + array_merge($query_parameters, array( + 'current_member' => $user_info['id'], + 'min_message' => (int) $min_message, + 'is_approved' => 1, + 'order' => $_REQUEST['sort'] . ($ascending ? '' : ' DESC'), + 'offset' => $_REQUEST['start'], + 'limit' => $context['topics_per_page'], + 'sort' => $_REQUEST['sort'], + )) + ); + + $topics = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $topics[] = $row['id_topic']; + $smcFunc['db_free_result']($request); + + // Sanity... where have you gone? + if (empty($topics)) + { + $context['topics'] = array(); + if ($context['querystring_board_limits'] == ';start=%d') + $context['querystring_board_limits'] = ''; + else + $context['querystring_board_limits'] = sprintf($context['querystring_board_limits'], $_REQUEST['start']); + return; + } + + $request = $smcFunc['db_query']('substring', ' + SELECT ' . $select_clause . ' + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS ms ON (ms.id_topic = t.id_topic AND ms.id_msg = t.id_first_msg) + INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + LEFT JOIN {db_prefix}members AS mems ON (mems.id_member = ms.id_member) + LEFT JOIN {db_prefix}members AS meml ON (meml.id_member = ml.id_member) + LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member}) + LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member}) + WHERE t.id_topic IN ({array_int:topic_list}) + ORDER BY ' . $_REQUEST['sort'] . ($ascending ? '' : ' DESC') . ' + LIMIT ' . count($topics), + array( + 'current_member' => $user_info['id'], + 'topic_list' => $topics, + ) + ); + } + + $context['topics'] = array(); + $topic_ids = array(); + + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['id_poll'] > 0 && $modSettings['pollMode'] == '0') + continue; + + $topic_ids[] = $row['id_topic']; + + if (!empty($settings['message_index_preview'])) + { + // Limit them to 128 characters - do this FIRST because it's a lot of wasted censoring otherwise. + $row['first_body'] = strip_tags(strtr(parse_bbc($row['first_body'], $row['first_smileys'], $row['id_first_msg']), array('
' => ' '))); + if ($smcFunc['strlen']($row['first_body']) > 128) + $row['first_body'] = $smcFunc['substr']($row['first_body'], 0, 128) . '...'; + $row['last_body'] = strip_tags(strtr(parse_bbc($row['last_body'], $row['last_smileys'], $row['id_last_msg']), array('
' => ' '))); + if ($smcFunc['strlen']($row['last_body']) > 128) + $row['last_body'] = $smcFunc['substr']($row['last_body'], 0, 128) . '...'; + + // Censor the subject and message preview. + censorText($row['first_subject']); + censorText($row['first_body']); + + // Don't censor them twice! + if ($row['id_first_msg'] == $row['id_last_msg']) + { + $row['last_subject'] = $row['first_subject']; + $row['last_body'] = $row['first_body']; + } + else + { + censorText($row['last_subject']); + censorText($row['last_body']); + } + } + else + { + $row['first_body'] = ''; + $row['last_body'] = ''; + censorText($row['first_subject']); + + if ($row['id_first_msg'] == $row['id_last_msg']) + $row['last_subject'] = $row['first_subject']; + else + censorText($row['last_subject']); + } + + // Decide how many pages the topic should have. + $topic_length = $row['num_replies'] + 1; + $messages_per_page = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) && !WIRELESS ? $options['messages_per_page'] : $modSettings['defaultMaxMessages']; + if ($topic_length > $messages_per_page) + { + $tmppages = array(); + $tmpa = 1; + for ($tmpb = 0; $tmpb < $topic_length; $tmpb += $messages_per_page) + { + $tmppages[] = '' . $tmpa . ''; + $tmpa++; + } + // Show links to all the pages? + if (count($tmppages) <= 5) + $pages = '« ' . implode(' ', $tmppages); + // Or skip a few? + else + $pages = '« ' . $tmppages[0] . ' ' . $tmppages[1] . ' ... ' . $tmppages[count($tmppages) - 2] . ' ' . $tmppages[count($tmppages) - 1]; + + if (!empty($modSettings['enableAllMessages']) && $topic_length < $modSettings['enableAllMessages']) + $pages .= '  ' . $txt['all'] . ''; + $pages .= ' »'; + } + else + $pages = ''; + + // We need to check the topic icons exist... you can never be too sure! + if (empty($modSettings['messageIconChecks_disable'])) + { + // First icon first... as you'd expect. + if (!isset($context['icon_sources'][$row['first_icon']])) + $context['icon_sources'][$row['first_icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $row['first_icon'] . '.gif') ? 'images_url' : 'default_images_url'; + // Last icon... last... duh. + if (!isset($context['icon_sources'][$row['last_icon']])) + $context['icon_sources'][$row['last_icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $row['last_icon'] . '.gif') ? 'images_url' : 'default_images_url'; + } + + // And build the array. + $context['topics'][$row['id_topic']] = array( + 'id' => $row['id_topic'], + 'first_post' => array( + 'id' => $row['id_first_msg'], + 'member' => array( + 'name' => $row['first_poster_name'], + 'id' => $row['id_first_member'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_first_member'], + 'link' => !empty($row['id_first_member']) ? '' . $row['first_poster_name'] . '' : $row['first_poster_name'] + ), + 'time' => timeformat($row['first_poster_time']), + 'timestamp' => forum_time(true, $row['first_poster_time']), + 'subject' => $row['first_subject'], + 'preview' => $row['first_body'], + 'icon' => $row['first_icon'], + 'icon_url' => $settings[$context['icon_sources'][$row['first_icon']]] . '/post/' . $row['first_icon'] . '.gif', + 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.0;topicseen', + 'link' => '' . $row['first_subject'] . '' + ), + 'last_post' => array( + 'id' => $row['id_last_msg'], + 'member' => array( + 'name' => $row['last_poster_name'], + 'id' => $row['id_last_member'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_last_member'], + 'link' => !empty($row['id_last_member']) ? '' . $row['last_poster_name'] . '' : $row['last_poster_name'] + ), + 'time' => timeformat($row['last_poster_time']), + 'timestamp' => forum_time(true, $row['last_poster_time']), + 'subject' => $row['last_subject'], + 'preview' => $row['last_body'], + 'icon' => $row['last_icon'], + 'icon_url' => $settings[$context['icon_sources'][$row['last_icon']]] . '/post/' . $row['last_icon'] . '.gif', + 'href' => $scripturl . '?topic=' . $row['id_topic'] . ($row['num_replies'] == 0 ? '.0' : '.msg' . $row['id_last_msg']) . ';topicseen#msg' . $row['id_last_msg'], + 'link' => '' . $row['last_subject'] . '' + ), + 'new_from' => $row['new_from'], + 'new_href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['new_from'] . ';topicseen#new', + 'href' => $scripturl . '?topic=' . $row['id_topic'] . ($row['num_replies'] == 0 ? '.0' : '.msg' . $row['new_from']) . ';topicseen' . ($row['num_replies'] == 0 ? '' : 'new'), + 'link' => '' . $row['first_subject'] . '', + 'is_sticky' => !empty($modSettings['enableStickyTopics']) && !empty($row['is_sticky']), + 'is_locked' => !empty($row['locked']), + 'is_poll' => $modSettings['pollMode'] == '1' && $row['id_poll'] > 0, + 'is_hot' => $row['num_replies'] >= $modSettings['hotTopicPosts'], + 'is_very_hot' => $row['num_replies'] >= $modSettings['hotTopicVeryPosts'], + 'is_posted_in' => false, + 'icon' => $row['first_icon'], + 'icon_url' => $settings[$context['icon_sources'][$row['first_icon']]] . '/post/' . $row['first_icon'] . '.gif', + 'subject' => $row['first_subject'], + 'pages' => $pages, + 'replies' => comma_format($row['num_replies']), + 'views' => comma_format($row['num_views']), + 'board' => array( + 'id' => $row['id_board'], + 'name' => $row['bname'], + 'href' => $scripturl . '?board=' . $row['id_board'] . '.0', + 'link' => '' . $row['bname'] . '' + ) + ); + + determineTopicClass($context['topics'][$row['id_topic']]); + } + $smcFunc['db_free_result']($request); + + if ($is_topics && !empty($modSettings['enableParticipation']) && !empty($topic_ids)) + { + $result = $smcFunc['db_query']('', ' + SELECT id_topic + FROM {db_prefix}messages + WHERE id_topic IN ({array_int:topic_list}) + AND id_member = {int:current_member} + GROUP BY id_topic + LIMIT {int:limit}', + array( + 'current_member' => $user_info['id'], + 'topic_list' => $topic_ids, + 'limit' => count($topic_ids), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + if (empty($context['topics'][$row['id_topic']]['is_posted_in'])) + { + $context['topics'][$row['id_topic']]['is_posted_in'] = true; + $context['topics'][$row['id_topic']]['class'] = 'my_' . $context['topics'][$row['id_topic']]['class']; + } + } + $smcFunc['db_free_result']($result); + } + + $context['querystring_board_limits'] = sprintf($context['querystring_board_limits'], $_REQUEST['start']); + $context['topics_to_mark'] = implode('-', $topic_ids); +} + +?> \ No newline at end of file diff --git a/Sources/Register.php b/Sources/Register.php new file mode 100644 index 0000000..b59273c --- /dev/null +++ b/Sources/Register.php @@ -0,0 +1,873 @@ + 1 && $context['require_agreement'] && !$context['registration_passed_agreement']) + $current_step = 1; + + // Show the user the right form. + $context['sub_template'] = $current_step == 1 ? 'registration_agreement' : 'registration_form'; + $context['page_title'] = $current_step == 1 ? $txt['registration_agreement'] : $txt['registration_form']; + + // Add the register chain to the link tree. + $context['linktree'][] = array( + 'url' => $scripturl . '?action=register', + 'name' => $txt['register'], + ); + + // If you have to agree to the agreement, it needs to be fetched from the file. + if ($context['require_agreement']) + { + // Have we got a localized one? + if (file_exists($boarddir . '/agreement.' . $user_info['language'] . '.txt')) + $context['agreement'] = parse_bbc(file_get_contents($boarddir . '/agreement.' . $user_info['language'] . '.txt'), true, 'agreement_' . $user_info['language']); + elseif (file_exists($boarddir . '/agreement.txt')) + $context['agreement'] = parse_bbc(file_get_contents($boarddir . '/agreement.txt'), true, 'agreement'); + else + $context['agreement'] = ''; + } + + if (!empty($modSettings['userLanguage'])) + { + $selectedLanguage = empty($_SESSION['language']) ? $language : $_SESSION['language']; + + // Do we have any languages? + if (empty($context['languages'])) + getLanguages(); + + // Try to find our selected language. + foreach ($context['languages'] as $key => $lang) + { + $context['languages'][$key]['name'] = strtr($lang['name'], array('-utf8' => '')); + + // Found it! + if ($selectedLanguage == $lang['filename']) + $context['languages'][$key]['selected'] = true; + } + } + + // Any custom fields we want filled in? + require_once($sourcedir . '/Profile.php'); + loadCustomFields(0, 'register'); + + // Or any standard ones? + if (!empty($modSettings['registration_fields'])) + { + require_once($sourcedir . '/Profile-Modify.php'); + + // Setup some important context. + loadLanguage('Profile'); + loadTemplate('Profile'); + + $context['user']['is_owner'] = true; + + // Here, and here only, emulate the permissions the user would have to do this. + $user_info['permissions'] = array_merge($user_info['permissions'], array('profile_account_own', 'profile_extra_own')); + $reg_fields = explode(',', $modSettings['registration_fields']); + + // We might have had some submissions on this front - go check. + foreach ($reg_fields as $field) + if (isset($_POST[$field])) + $cur_profile[$field] = $smcFunc['htmlspecialchars']($_POST[$field]); + + // Load all the fields in question. + setupProfileContext($reg_fields); + } + + // Generate a visual verification code to make sure the user is no bot. + if (!empty($modSettings['reg_verification'])) + { + require_once($sourcedir . '/Subs-Editor.php'); + $verificationOptions = array( + 'id' => 'register', + ); + $context['visual_verification'] = create_control_verification($verificationOptions); + $context['visual_verification_id'] = $verificationOptions['id']; + } + // Otherwise we have nothing to show. + else + $context['visual_verification'] = false; + + // Are they coming from an OpenID login attempt? + if (!empty($_SESSION['openid']['verified']) && !empty($_SESSION['openid']['openid_uri'])) + { + $context['openid'] = $_SESSION['openid']['openid_uri']; + $context['username'] = $smcFunc['htmlspecialchars'](!empty($_POST['user']) ? $_POST['user'] : $_SESSION['openid']['nickname']); + $context['email'] = $smcFunc['htmlspecialchars'](!empty($_POST['email']) ? $_POST['email'] : $_SESSION['openid']['email']); + } + // See whether we have some prefiled values. + else + { + $context += array( + 'openid' => isset($_POST['openid_identifier']) ? $_POST['openid_identifier'] : '', + 'username' => isset($_POST['user']) ? $smcFunc['htmlspecialchars']($_POST['user']) : '', + 'email' => isset($_POST['email']) ? $smcFunc['htmlspecialchars']($_POST['email']) : '', + ); + } + + // !!! Why isn't this a simple set operation? + // Were there any errors? + $context['registration_errors'] = array(); + if (!empty($reg_errors)) + foreach ($reg_errors as $error) + $context['registration_errors'][] = $error; +} + +// Actually register the member. +function Register2($verifiedOpenID = false) +{ + global $scripturl, $txt, $modSettings, $context, $sourcedir; + global $user_info, $options, $settings, $smcFunc; + + // Start collecting together any errors. + $reg_errors = array(); + + // Did we save some open ID fields? + if ($verifiedOpenID && !empty($context['openid_save_fields'])) + { + foreach ($context['openid_save_fields'] as $id => $value) + $_POST[$id] = $value; + } + + // You can't register if it's disabled. + if (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 3) + fatal_lang_error('registration_disabled', false); + + // Things we don't do for people who have already confirmed their OpenID allegances via register. + if (!$verifiedOpenID) + { + // Well, if you don't agree, you can't register. + if (!empty($modSettings['requireAgreement']) && empty($_SESSION['registration_agreed'])) + redirectexit(); + + // Make sure they came from *somewhere*, have a session. + if (!isset($_SESSION['old_url'])) + redirectexit('action=register'); + + // Are they under age, and under age users are banned? + if (!empty($modSettings['coppaAge']) && empty($modSettings['coppaType']) && empty($_SESSION['skip_coppa'])) + { + // !!! This should be put in Errors, imho. + loadLanguage('Login'); + fatal_lang_error('under_age_registration_prohibited', false, array($modSettings['coppaAge'])); + } + + // Check whether the visual verification code was entered correctly. + if (!empty($modSettings['reg_verification'])) + { + require_once($sourcedir . '/Subs-Editor.php'); + $verificationOptions = array( + 'id' => 'register', + ); + $context['visual_verification'] = create_control_verification($verificationOptions, true); + + if (is_array($context['visual_verification'])) + { + loadLanguage('Errors'); + foreach ($context['visual_verification'] as $error) + $reg_errors[] = $txt['error_' . $error]; + } + } + } + + foreach ($_POST as $key => $value) + { + if (!is_array($_POST[$key])) + $_POST[$key] = htmltrim__recursive(str_replace(array("\n", "\r"), '', $_POST[$key])); + } + + // Collect all extra registration fields someone might have filled in. + $possible_strings = array( + 'website_url', 'website_title', + 'aim', 'yim', + 'location', 'birthdate', + 'time_format', + 'buddy_list', + 'pm_ignore_list', + 'smiley_set', + 'signature', 'personal_text', 'avatar', + 'lngfile', + 'secret_question', 'secret_answer', + ); + $possible_ints = array( + 'pm_email_notify', + 'notify_types', + 'icq', + 'gender', + 'id_theme', + ); + $possible_floats = array( + 'time_offset', + ); + $possible_bools = array( + 'notify_announcements', 'notify_regularity', 'notify_send_body', + 'hide_email', 'show_online', + ); + + if (isset($_POST['secret_answer']) && $_POST['secret_answer'] != '') + $_POST['secret_answer'] = md5($_POST['secret_answer']); + + // Needed for isReservedName() and registerMember(). + require_once($sourcedir . '/Subs-Members.php'); + + // Validation... even if we're not a mall. + if (isset($_POST['real_name']) && (!empty($modSettings['allow_editDisplayName']) || allowedTo('moderate_forum'))) + { + $_POST['real_name'] = trim(preg_replace('~[\t\n\r \x0B\0' . ($context['utf8'] ? ($context['server']['complex_preg_chars'] ? '\x{A0}\x{AD}\x{2000}-\x{200F}\x{201F}\x{202F}\x{3000}\x{FEFF}' : "\xC2\xA0\xC2\xAD\xE2\x80\x80-\xE2\x80\x8F\xE2\x80\x9F\xE2\x80\xAF\xE2\x80\x9F\xE3\x80\x80\xEF\xBB\xBF") : '\x00-\x08\x0B\x0C\x0E-\x19\xA0') . ']+~' . ($context['utf8'] ? 'u' : ''), ' ', $_POST['real_name'])); + if (trim($_POST['real_name']) != '' && !isReservedName($_POST['real_name']) && $smcFunc['strlen']($_POST['real_name']) < 60) + $possible_strings[] = 'real_name'; + } + + if (isset($_POST['msn']) && preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $_POST['msn']) != 0) + $profile_strings[] = 'msn'; + + // Handle a string as a birthdate... + if (isset($_POST['birthdate']) && $_POST['birthdate'] != '') + $_POST['birthdate'] = strftime('%Y-%m-%d', strtotime($_POST['birthdate'])); + // Or birthdate parts... + elseif (!empty($_POST['bday1']) && !empty($_POST['bday2'])) + $_POST['birthdate'] = sprintf('%04d-%02d-%02d', empty($_POST['bday3']) ? 0 : (int) $_POST['bday3'], (int) $_POST['bday1'], (int) $_POST['bday2']); + + // By default assume email is hidden, only show it if we tell it to. + $_POST['hide_email'] = !empty($_POST['allow_email']) ? 0 : 1; + + // Validate the passed language file. + if (isset($_POST['lngfile']) && !empty($modSettings['userLanguage'])) + { + // Do we have any languages? + if (empty($context['languages'])) + getLanguages(); + + // Did we find it? + if (isset($context['languages'][$_POST['lngfile']])) + $_SESSION['language'] = $_POST['lngfile']; + else + unset($_POST['lngfile']); + } + else + unset($_POST['lngfile']); + + // Some of these fields we may not want. + if (!empty($modSettings['registration_fields'])) + { + // But we might want some of them if the admin asks for them. + $standard_fields = array('icq', 'msn', 'aim', 'yim', 'location', 'gender'); + $reg_fields = explode(',', $modSettings['registration_fields']); + + $exclude_fields = array_diff($standard_fields, $reg_fields); + + // Website is a little different + if (!in_array('website', $reg_fields)) + $exclude_fields = array_merge($exclude_fields, array('website_url', 'website_title')); + + // We used to accept signature on registration but it's being abused by spammers these days, so no more. + $exclude_fields[] = 'signature'; + } + else + $exclude_fields = array('signature', 'icq', 'msn', 'aim', 'yim', 'location', 'gender', 'website_url', 'website_title'); + + $possible_strings = array_diff($possible_strings, $exclude_fields); + $possible_ints = array_diff($possible_ints, $exclude_fields); + $possible_floats = array_diff($possible_floats, $exclude_fields); + $possible_bools = array_diff($possible_bools, $exclude_fields); + + // Set the options needed for registration. + $regOptions = array( + 'interface' => 'guest', + 'username' => !empty($_POST['user']) ? $_POST['user'] : '', + 'email' => !empty($_POST['email']) ? $_POST['email'] : '', + 'password' => !empty($_POST['passwrd1']) ? $_POST['passwrd1'] : '', + 'password_check' => !empty($_POST['passwrd2']) ? $_POST['passwrd2'] : '', + 'openid' => !empty($_POST['openid_identifier']) ? $_POST['openid_identifier'] : '', + 'auth_method' => !empty($_POST['authenticate']) ? $_POST['authenticate'] : '', + 'check_reserved_name' => true, + 'check_password_strength' => true, + 'check_email_ban' => true, + 'send_welcome_email' => !empty($modSettings['send_welcomeEmail']), + 'require' => !empty($modSettings['coppaAge']) && !$verifiedOpenID && empty($_SESSION['skip_coppa']) ? 'coppa' : (empty($modSettings['registration_method']) ? 'nothing' : ($modSettings['registration_method'] == 1 ? 'activation' : 'approval')), + 'extra_register_vars' => array(), + 'theme_vars' => array(), + ); + + // Include the additional options that might have been filled in. + foreach ($possible_strings as $var) + if (isset($_POST[$var])) + $regOptions['extra_register_vars'][$var] = $smcFunc['htmlspecialchars']($_POST[$var], ENT_QUOTES); + foreach ($possible_ints as $var) + if (isset($_POST[$var])) + $regOptions['extra_register_vars'][$var] = (int) $_POST[$var]; + foreach ($possible_floats as $var) + if (isset($_POST[$var])) + $regOptions['extra_register_vars'][$var] = (float) $_POST[$var]; + foreach ($possible_bools as $var) + if (isset($_POST[$var])) + $regOptions['extra_register_vars'][$var] = empty($_POST[$var]) ? 0 : 1; + + // Registration options are always default options... + if (isset($_POST['default_options'])) + $_POST['options'] = isset($_POST['options']) ? $_POST['options'] + $_POST['default_options'] : $_POST['default_options']; + $regOptions['theme_vars'] = isset($_POST['options']) && is_array($_POST['options']) ? $_POST['options'] : array(); + + // Make sure they are clean, dammit! + $regOptions['theme_vars'] = htmlspecialchars__recursive($regOptions['theme_vars']); + + // If Quick Reply hasn't been set then set it to be shown but collapsed. + if (!isset($regOptions['theme_vars']['display_quick_reply'])) + $regOptions['theme_vars']['display_quick_reply'] = 1; + + // Check whether we have fields that simply MUST be displayed? + $request = $smcFunc['db_query']('', ' + SELECT col_name, field_name, field_type, field_length, mask, show_reg + FROM {db_prefix}custom_fields + WHERE active = {int:is_active}', + array( + 'is_active' => 1, + ) + ); + $custom_field_errors = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Don't allow overriding of the theme variables. + if (isset($regOptions['theme_vars'][$row['col_name']])) + unset($regOptions['theme_vars'][$row['col_name']]); + + // Not actually showing it then? + if (!$row['show_reg']) + continue; + + // Prepare the value! + $value = isset($_POST['customfield'][$row['col_name']]) ? trim($_POST['customfield'][$row['col_name']]) : ''; + + // We only care for text fields as the others are valid to be empty. + if (!in_array($row['field_type'], array('check', 'select', 'radio'))) + { + // Is it too long? + if ($row['field_length'] && $row['field_length'] < $smcFunc['strlen']($value)) + $custom_field_errors[] = array('custom_field_too_long', array($row['field_name'], $row['field_length'])); + + // Any masks to apply? + if ($row['field_type'] == 'text' && !empty($row['mask']) && $row['mask'] != 'none') + { + //!!! We never error on this - just ignore it at the moment... + if ($row['mask'] == 'email' && (preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $value) === 0 || strlen($value) > 255)) + $custom_field_errors[] = array('custom_field_invalid_email', array($row['field_name'])); + elseif ($row['mask'] == 'number' && preg_match('~[^\d]~', $value)) + $custom_field_errors[] = array('custom_field_not_number', array($row['field_name'])); + elseif (substr($row['mask'], 0, 5) == 'regex' && trim($value) != '' && preg_match(substr($row['mask'], 5), $value) === 0) + $custom_field_errors[] = array('custom_field_inproper_format', array($row['field_name'])); + } + } + + // Is this required but not there? + if (trim($value) == '' && $row['show_reg'] > 1) + $custom_field_errors[] = array('custom_field_empty', array($row['field_name'])); + } + $smcFunc['db_free_result']($request); + + // Process any errors. + if (!empty($custom_field_errors)) + { + loadLanguage('Errors'); + foreach ($custom_field_errors as $error) + $reg_errors[] = vsprintf($txt['error_' . $error[0]], $error[1]); + } + + // Lets check for other errors before trying to register the member. + if (!empty($reg_errors)) + { + $_REQUEST['step'] = 2; + return Register($reg_errors); + } + // If they're wanting to use OpenID we need to validate them first. + if (empty($_SESSION['openid']['verified']) && !empty($_POST['authenticate']) && $_POST['authenticate'] == 'openid') + { + // What do we need to save? + $save_variables = array(); + foreach ($_POST as $k => $v) + if (!in_array($k, array('sc', 'sesc', $context['session_var'], 'passwrd1', 'passwrd2', 'regSubmit'))) + $save_variables[$k] = $v; + + require_once($sourcedir . '/Subs-OpenID.php'); + smf_openID_validate($_POST['openid_identifier'], false, $save_variables); + } + // If we've come from OpenID set up some default stuff. + elseif ($verifiedOpenID || (!empty($_POST['openid_identifier']) && $_POST['authenticate'] == 'openid')) + { + $regOptions['username'] = !empty($_POST['user']) && trim($_POST['user']) != '' ? $_POST['user'] : $_SESSION['openid']['nickname']; + $regOptions['email'] = !empty($_POST['email']) && trim($_POST['email']) != '' ? $_POST['email'] : $_SESSION['openid']['email']; + $regOptions['auth_method'] = 'openid'; + $regOptions['openid'] = !empty($_POST['openid_identifier']) ? $_POST['openid_identifier'] : $_SESSION['openid']['openid_uri']; + } + + $memberID = registerMember($regOptions, true); + + // What there actually an error of some kind dear boy? + if (is_array($memberID)) + { + $reg_errors = array_merge($reg_errors, $memberID); + $_REQUEST['step'] = 2; + return Register($reg_errors); + } + + // Do our spam protection now. + spamProtection('register'); + + // We'll do custom fields after as then we get to use the helper function! + if (!empty($_POST['customfield'])) + { + require_once($sourcedir . '/Profile.php'); + require_once($sourcedir . '/Profile-Modify.php'); + makeCustomFieldChanges($memberID, 'register'); + } + + // If COPPA has been selected then things get complicated, setup the template. + if (!empty($modSettings['coppaAge']) && empty($_SESSION['skip_coppa'])) + redirectexit('action=coppa;member=' . $memberID); + // Basic template variable setup. + elseif (!empty($modSettings['registration_method'])) + { + loadTemplate('Register'); + + $context += array( + 'page_title' => $txt['register'], + 'title' => $txt['registration_successful'], + 'sub_template' => 'after', + 'description' => $modSettings['registration_method'] == 2 ? $txt['approval_after_registration'] : $txt['activate_after_registration'] + ); + } + else + { + call_integration_hook('integrate_activate', array($row['member_name'])); + + setLoginCookie(60 * $modSettings['cookieTime'], $memberID, sha1(sha1(strtolower($regOptions['username']) . $regOptions['password']) . $regOptions['register_vars']['password_salt'])); + + redirectexit('action=login2;sa=check;member=' . $memberID, $context['server']['needs_login_fix']); + } +} + +function Activate() +{ + global $context, $txt, $modSettings, $scripturl, $sourcedir, $smcFunc, $language; + + loadLanguage('Login'); + loadTemplate('Login'); + + if (empty($_REQUEST['u']) && empty($_POST['user'])) + { + if (empty($modSettings['registration_method']) || $modSettings['registration_method'] == 3) + fatal_lang_error('no_access', false); + + $context['member_id'] = 0; + $context['sub_template'] = 'resend'; + $context['page_title'] = $txt['invalid_activation_resend']; + $context['can_activate'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] == 1; + $context['default_username'] = isset($_GET['user']) ? $_GET['user'] : ''; + + return; + } + + // Get the code from the database... + $request = $smcFunc['db_query']('', ' + SELECT id_member, validation_code, member_name, real_name, email_address, is_activated, passwd, lngfile + FROM {db_prefix}members' . (empty($_REQUEST['u']) ? ' + WHERE member_name = {string:email_address} OR email_address = {string:email_address}' : ' + WHERE id_member = {int:id_member}') . ' + LIMIT 1', + array( + 'id_member' => isset($_REQUEST['u']) ? (int) $_REQUEST['u'] : 0, + 'email_address' => isset($_POST['user']) ? $_POST['user'] : '', + ) + ); + + // Does this user exist at all? + if ($smcFunc['db_num_rows']($request) == 0) + { + $context['sub_template'] = 'retry_activate'; + $context['page_title'] = $txt['invalid_userid']; + $context['member_id'] = 0; + + return; + } + + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Change their email address? (they probably tried a fake one first :P.) + if (isset($_POST['new_email'], $_REQUEST['passwd']) && sha1(strtolower($row['member_name']) . $_REQUEST['passwd']) == $row['passwd'] && ($row['is_activated'] == 0 || $row['is_activated'] == 2)) + { + if (empty($modSettings['registration_method']) || $modSettings['registration_method'] == 3) + fatal_lang_error('no_access', false); + + // !!! Separate the sprintf? + if (preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $_POST['new_email']) == 0) + fatal_error(sprintf($txt['valid_email_needed'], htmlspecialchars($_POST['new_email'])), false); + + // Make sure their email isn't banned. + isBannedEmail($_POST['new_email'], 'cannot_register', $txt['ban_register_prohibited']); + + // Ummm... don't even dare try to take someone else's email!! + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE email_address = {string:email_address} + LIMIT 1', + array( + 'email_address' => $_POST['new_email'], + ) + ); + // !!! Separate the sprintf? + if ($smcFunc['db_num_rows']($request) != 0) + fatal_lang_error('email_in_use', false, array(htmlspecialchars($_POST['new_email']))); + $smcFunc['db_free_result']($request); + + updateMemberData($row['id_member'], array('email_address' => $_POST['new_email'])); + $row['email_address'] = $_POST['new_email']; + + $email_change = true; + } + + // Resend the password, but only if the account wasn't activated yet. + if (!empty($_REQUEST['sa']) && $_REQUEST['sa'] == 'resend' && ($row['is_activated'] == 0 || $row['is_activated'] == 2) && (!isset($_REQUEST['code']) || $_REQUEST['code'] == '')) + { + require_once($sourcedir . '/Subs-Post.php'); + + $replacements = array( + 'REALNAME' => $row['real_name'], + 'USERNAME' => $row['member_name'], + 'ACTIVATIONLINK' => $scripturl . '?action=activate;u=' . $row['id_member'] . ';code=' . $row['validation_code'], + 'ACTIVATIONLINKWITHOUTCODE' => $scripturl . '?action=activate;u=' . $row['id_member'], + 'ACTIVATIONCODE' => $row['validation_code'], + 'FORGOTPASSWORDLINK' => $scripturl . '?action=reminder', + ); + + $emaildata = loadEmailTemplate('resend_activate_message', $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']); + + sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 0); + + $context['page_title'] = $txt['invalid_activation_resend']; + + // This will ensure we don't actually get an error message if it works! + $context['error_title'] = ''; + + fatal_lang_error(!empty($email_change) ? 'change_email_success' : 'resend_email_success', false); + } + + // Quit if this code is not right. + if (empty($_REQUEST['code']) || $row['validation_code'] != $_REQUEST['code']) + { + if (!empty($row['is_activated'])) + fatal_lang_error('already_activated', false); + elseif ($row['validation_code'] == '') + { + loadLanguage('Profile'); + fatal_error($txt['registration_not_approved'] . ' ' . $txt['here'] . '.', false); + } + + $context['sub_template'] = 'retry_activate'; + $context['page_title'] = $txt['invalid_activation_code']; + $context['member_id'] = $row['id_member']; + + return; + } + + // Let the integration know that they've been activated! + call_integration_hook('integrate_activate', array($row['member_name'])); + + // Validation complete - update the database! + updateMemberData($row['id_member'], array('is_activated' => 1, 'validation_code' => '')); + + // Also do a proper member stat re-evaluation. + updateStats('member', false); + + if (!isset($_POST['new_email'])) + { + require_once($sourcedir . '/Subs-Post.php'); + + adminNotify('activation', $row['id_member'], $row['member_name']); + } + + $context += array( + 'page_title' => $txt['registration_successful'], + 'sub_template' => 'login', + 'default_username' => $row['member_name'], + 'default_password' => '', + 'never_expire' => false, + 'description' => $txt['activate_success'] + ); +} + +// This function will display the contact information for the forum, as well a form to fill in. +function CoppaForm() +{ + global $context, $modSettings, $txt, $smcFunc; + + loadLanguage('Login'); + loadTemplate('Register'); + + // No User ID?? + if (!isset($_GET['member'])) + fatal_lang_error('no_access', false); + + // Get the user details... + $request = $smcFunc['db_query']('', ' + SELECT member_name + FROM {db_prefix}members + WHERE id_member = {int:id_member} + AND is_activated = {int:is_coppa}', + array( + 'id_member' => (int) $_GET['member'], + 'is_coppa' => 5, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_access', false); + list ($username) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (isset($_GET['form'])) + { + // Some simple contact stuff for the forum. + $context['forum_contacts'] = (!empty($modSettings['coppaPost']) ? $modSettings['coppaPost'] . '

' : '') . (!empty($modSettings['coppaFax']) ? $modSettings['coppaFax'] . '
' : ''); + $context['forum_contacts'] = !empty($context['forum_contacts']) ? $context['forum_name_html_safe'] . '
' . $context['forum_contacts'] : ''; + + // Showing template? + if (!isset($_GET['dl'])) + { + // Shortcut for producing underlines. + $context['ul'] = '                          '; + $context['template_layers'] = array(); + $context['sub_template'] = 'coppa_form'; + $context['page_title'] = $txt['coppa_form_title']; + $context['coppa_body'] = str_replace(array('{PARENT_NAME}', '{CHILD_NAME}', '{USER_NAME}'), array($context['ul'], $context['ul'], $username), $txt['coppa_form_body']); + } + // Downloading. + else + { + // The data. + $ul = ' '; + $crlf = "\r\n"; + $data = $context['forum_contacts'] . $crlf . $txt['coppa_form_address'] . ':' . $crlf . $txt['coppa_form_date'] . ':' . $crlf . $crlf . $crlf . $txt['coppa_form_body']; + $data = str_replace(array('{PARENT_NAME}', '{CHILD_NAME}', '{USER_NAME}', '
', '
'), array($ul, $ul, $username, $crlf, $crlf), $data); + + // Send the headers. + header('Connection: close'); + header('Content-Disposition: attachment; filename="approval.txt"'); + header('Content-Type: ' . ($context['browser']['is_ie'] || $context['browser']['is_opera'] ? 'application/octetstream' : 'application/octet-stream')); + header('Content-Length: ' . count($data)); + + echo $data; + obExit(false); + } + } + else + { + $context += array( + 'page_title' => $txt['coppa_title'], + 'sub_template' => 'coppa', + ); + + $context['coppa'] = array( + 'body' => str_replace('{MINIMUM_AGE}', $modSettings['coppaAge'], $txt['coppa_after_registration']), + 'many_options' => !empty($modSettings['coppaPost']) && !empty($modSettings['coppaFax']), + 'post' => empty($modSettings['coppaPost']) ? '' : $modSettings['coppaPost'], + 'fax' => empty($modSettings['coppaFax']) ? '' : $modSettings['coppaFax'], + 'phone' => empty($modSettings['coppaPhone']) ? '' : str_replace('{PHONE_NUMBER}', $modSettings['coppaPhone'], $txt['coppa_send_by_phone']), + 'id' => $_GET['member'], + ); + } +} + +// Show the verification code or let it hear. +function VerificationCode() +{ + global $sourcedir, $modSettings, $context, $scripturl; + + $verification_id = isset($_GET['vid']) ? $_GET['vid'] : ''; + $code = $verification_id && isset($_SESSION[$verification_id . '_vv']) ? $_SESSION[$verification_id . '_vv']['code'] : (isset($_SESSION['visual_verification_code']) ? $_SESSION['visual_verification_code'] : ''); + + // Somehow no code was generated or the session was lost. + if (empty($code)) + { + header('Content-Type: image/gif'); + die("\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x21\xF9\x04\x01\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3B"); + } + + // Show a window that will play the verification code. + elseif (isset($_REQUEST['sound'])) + { + loadLanguage('Login'); + loadTemplate('Register'); + + $context['verification_sound_href'] = $scripturl . '?action=verificationcode;rand=' . md5(mt_rand()) . ($verification_id ? ';vid=' . $verification_id : '') . ';format=.wav'; + $context['sub_template'] = 'verification_sound'; + $context['template_layers'] = array(); + + obExit(); + } + + // If we have GD, try the nice code. + elseif (empty($_REQUEST['format'])) + { + require_once($sourcedir . '/Subs-Graphics.php'); + + if (in_array('gd', get_loaded_extensions()) && !showCodeImage($code)) + header('HTTP/1.1 400 Bad Request'); + + // Otherwise just show a pre-defined letter. + elseif (isset($_REQUEST['letter'])) + { + $_REQUEST['letter'] = (int) $_REQUEST['letter']; + if ($_REQUEST['letter'] > 0 && $_REQUEST['letter'] <= strlen($code) && !showLetterImage(strtolower($code{$_REQUEST['letter'] - 1}))) + { + header('Content-Type: image/gif'); + die("\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x21\xF9\x04\x01\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3B"); + } + } + // You must be up to no good. + else + { + header('Content-Type: image/gif'); + die("\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x21\xF9\x04\x01\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3B"); + } + } + + elseif ($_REQUEST['format'] === '.wav') + { + require_once($sourcedir . '/Subs-Sound.php'); + + if (!createWaveFile($code)) + header('HTTP/1.1 400 Bad Request'); + } + + // We all die one day... + die(); +} + +// See if a username already exists. +function RegisterCheckUsername() +{ + global $sourcedir, $smcFunc, $context, $txt; + + // This is XML! + loadTemplate('Xml'); + $context['sub_template'] = 'check_username'; + $context['checked_username'] = isset($_GET['username']) ? $_GET['username'] : ''; + $context['valid_username'] = true; + + // Clean it up like mother would. + $context['checked_username'] = preg_replace('~[\t\n\r \x0B\0' . ($context['utf8'] ? ($context['server']['complex_preg_chars'] ? '\x{A0}\x{AD}\x{2000}-\x{200F}\x{201F}\x{202F}\x{3000}\x{FEFF}' : "\xC2\xA0\xC2\xAD\xE2\x80\x80-\xE2\x80\x8F\xE2\x80\x9F\xE2\x80\xAF\xE2\x80\x9F\xE3\x80\x80\xEF\xBB\xBF") : '\x00-\x08\x0B\x0C\x0E-\x19\xA0') . ']+~' . ($context['utf8'] ? 'u' : ''), ' ', $context['checked_username']); + if ($smcFunc['strlen']($context['checked_username']) > 25) + $context['checked_username'] = $smcFunc['htmltrim']($smcFunc['substr']($context['checked_username'], 0, 25)); + + // Only these characters are permitted. + if (preg_match('~[<>&"\'=\\\]~', preg_replace('~&#(?:\\d{1,7}|x[0-9a-fA-F]{1,6});~', '', $context['checked_username'])) != 0 || $context['checked_username'] == '_' || $context['checked_username'] == '|' || strpos($context['checked_username'], '[code') !== false || strpos($context['checked_username'], '[/code') !== false) + $context['valid_username'] = false; + + if (stristr($context['checked_username'], $txt['guest_title']) !== false) + $context['valid_username'] = false; + + if (trim($context['checked_username']) == '') + $context['valid_username'] = false; + else + { + require_once($sourcedir . '/Subs-Members.php'); + $context['valid_username'] &= isReservedName($context['checked_username'], 0, false, false) ? 0 : 1; + } +} + +?> \ No newline at end of file diff --git a/Sources/Reminder.php b/Sources/Reminder.php new file mode 100644 index 0000000..0f3a57d --- /dev/null +++ b/Sources/Reminder.php @@ -0,0 +1,398 @@ + 'RemindPick', + 'secret2' => 'SecretAnswer2', + 'setpassword' =>'setPassword', + 'setpassword2' =>'setPassword2' + ); + + // Any subaction? If none, fall through to the main template, which will ask for one. + if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']])) + $subActions[$_REQUEST['sa']](); +} + +// Pick a reminder type. +function RemindPick() +{ + global $context, $txt, $scripturl, $sourcedir, $user_info, $webmaster_email, $smcFunc, $language, $modSettings; + + checkSession(); + + // Coming with a known ID? + if (!empty($_REQUEST['uid'])) + { + $where = 'id_member = {int:id_member}'; + $where_params['id_member'] = (int) $_REQUEST['uid']; + } + elseif (isset($_POST['user']) && $_POST['user'] != '') + { + $where = 'member_name = {string:member_name}'; + $where_params['member_name'] = $_POST['user']; + $where_params['email_address'] = $_POST['user']; + } + + // You must enter a username/email address. + if (empty($where)) + fatal_lang_error('username_no_exist', false); + + // Find the user! + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name, member_name, email_address, is_activated, validation_code, lngfile, openid_uri, secret_question + FROM {db_prefix}members + WHERE ' . $where . ' + LIMIT 1', + array_merge($where_params, array( + )) + ); + // Maybe email? + if ($smcFunc['db_num_rows']($request) == 0 && empty($_REQUEST['uid'])) + { + $smcFunc['db_free_result']($request); + + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name, member_name, email_address, is_activated, validation_code, lngfile, openid_uri, secret_question + FROM {db_prefix}members + WHERE email_address = {string:email_address} + LIMIT 1', + array_merge($where_params, array( + )) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_user_with_email', false); + } + + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + $context['account_type'] = !empty($row['openid_uri']) ? 'openid' : 'password'; + + // If the user isn't activated/approved, give them some feedback on what to do next. + if ($row['is_activated'] != 1) + { + // Awaiting approval... + if (trim($row['validation_code']) == '') + fatal_error($txt['registration_not_approved'] . ' ' . $txt['here'] . '.', false); + else + fatal_error($txt['registration_not_activated'] . ' ' . $txt['here'] . '.', false); + } + + // You can't get emailed if you have no email address. + $row['email_address'] = trim($row['email_address']); + if ($row['email_address'] == '') + fatal_error($txt['no_reminder_email'] . '
' . $txt['send_email'] . ' webmaster ' . $txt['to_ask_password'] . '.'); + + // If they have no secret question then they can only get emailed the item, or they are requesting the email, send them an email. + if (empty($row['secret_question']) || (isset($_POST['reminder_type']) && $_POST['reminder_type'] == 'email')) + { + // Randomly generate a new password, with only alpha numeric characters that is a max length of 10 chars. + require_once($sourcedir . '/Subs-Members.php'); + $password = generateValidationCode(); + + require_once($sourcedir . '/Subs-Post.php'); + $replacements = array( + 'REALNAME' => $row['real_name'], + 'REMINDLINK' => $scripturl . '?action=reminder;sa=setpassword;u=' . $row['id_member'] . ';code=' . $password, + 'IP' => $user_info['ip'], + 'MEMBERNAME' => $row['member_name'], + 'OPENID' => $row['openid_uri'], + ); + + $emaildata = loadEmailTemplate('forgot_' . $context['account_type'], $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']); + $context['description'] = $txt['reminder_' . (!empty($row['openid_uri']) ? 'openid_' : '') . 'sent']; + + // If they were using OpenID simply email them their OpenID identity. + sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 0); + if (empty($row['openid_uri'])) + // Set the password in the database. + updateMemberData($row['id_member'], array('validation_code' => substr(md5($password), 0, 10))); + + // Set up the template. + $context['sub_template'] = 'sent'; + + // Dont really. + return; + } + // Otherwise are ready to answer the question? + elseif (isset($_POST['reminder_type']) && $_POST['reminder_type'] == 'secret') + { + return SecretAnswerInput(); + } + + // No we're here setup the context for template number 2! + $context['sub_template'] = 'reminder_pick'; + $context['current_member'] = array( + 'id' => $row['id_member'], + 'name' => $row['member_name'], + ); +} + +// Set your new password +function setPassword() +{ + global $txt, $context; + + loadLanguage('Login'); + + // You need a code! + if (!isset($_REQUEST['code'])) + fatal_lang_error('no_access', false); + + // Fill the context array. + $context += array( + 'page_title' => $txt['reminder_set_password'], + 'sub_template' => 'set_password', + 'code' => $_REQUEST['code'], + 'memID' => (int) $_REQUEST['u'] + ); +} + +function setPassword2() +{ + global $context, $txt, $modSettings, $smcFunc, $sourcedir; + + checkSession(); + + if (empty($_POST['u']) || !isset($_POST['passwrd1']) || !isset($_POST['passwrd2'])) + fatal_lang_error('no_access', false); + + $_POST['u'] = (int) $_POST['u']; + + if ($_POST['passwrd1'] != $_POST['passwrd2']) + fatal_lang_error('passwords_dont_match', false); + + if ($_POST['passwrd1'] == '') + fatal_lang_error('no_password', false); + + loadLanguage('Login'); + + // Get the code as it should be from the database. + $request = $smcFunc['db_query']('', ' + SELECT validation_code, member_name, email_address, passwd_flood + FROM {db_prefix}members + WHERE id_member = {int:id_member} + AND is_activated = {int:is_activated} + AND validation_code != {string:blank_string} + LIMIT 1', + array( + 'id_member' => $_POST['u'], + 'is_activated' => 1, + 'blank_string' => '', + ) + ); + + // Does this user exist at all? + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('invalid_userid', false); + + list ($realCode, $username, $email, $flood_value) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Is the password actually valid? + require_once($sourcedir . '/Subs-Auth.php'); + $passwordError = validatePassword($_POST['passwrd1'], $username, array($email)); + + // What - it's not? + if ($passwordError != null) + fatal_lang_error('profile_error_password_' . $passwordError, false); + + require_once($sourcedir . '/LogInOut.php'); + + // Quit if this code is not right. + if (empty($_POST['code']) || substr($realCode, 0, 10) !== substr(md5($_POST['code']), 0, 10)) + { + // Stop brute force attacks like this. + validatePasswordFlood($_POST['u'], $flood_value, false); + + fatal_error($txt['invalid_activation_code'], false); + } + + // Just in case, flood control. + validatePasswordFlood($_POST['u'], $flood_value, true); + + // User validated. Update the database! + updateMemberData($_POST['u'], array('validation_code' => '', 'passwd' => sha1(strtolower($username) . $_POST['passwrd1']))); + + call_integration_hook('integrate_reset_pass', array($username, $username, $_POST['passwrd1'])); + + loadTemplate('Login'); + $context += array( + 'page_title' => $txt['reminder_password_set'], + 'sub_template' => 'login', + 'default_username' => $username, + 'default_password' => $_POST['passwrd1'], + 'never_expire' => false, + 'description' => $txt['reminder_password_set'] + ); +} + +// Get the secret answer. +function SecretAnswerInput() +{ + global $txt, $context, $smcFunc; + + checkSession(); + + // Strings for the register auto javascript clever stuffy wuffy. + loadLanguage('Login'); + + // Check they entered something... + if (empty($_REQUEST['uid'])) + fatal_lang_error('username_no_exist', false); + + // Get the stuff.... + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name, member_name, secret_question, openid_uri + FROM {db_prefix}members + WHERE id_member = {int:id_member} + LIMIT 1', + array( + 'id_member' => (int) $_REQUEST['uid'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('username_no_exist', false); + + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + $context['account_type'] = !empty($row['openid_uri']) ? 'openid' : 'password'; + + // If there is NO secret question - then throw an error. + if (trim($row['secret_question']) == '') + fatal_lang_error('registration_no_secret_question', false); + + // Ask for the answer... + $context['remind_user'] = $row['id_member']; + $context['remind_type'] = ''; + $context['secret_question'] = $row['secret_question']; + + $context['sub_template'] = 'ask'; +} + +function SecretAnswer2() +{ + global $txt, $context, $modSettings, $smcFunc, $sourcedir; + + checkSession(); + + // Hacker? How did you get this far without an email or username? + if (empty($_REQUEST['uid'])) + fatal_lang_error('username_no_exist', false); + + loadLanguage('Login'); + + // Get the information from the database. + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name, member_name, secret_answer, secret_question, openid_uri, email_address + FROM {db_prefix}members + WHERE id_member = {int:id_member} + LIMIT 1', + array( + 'id_member' => $_REQUEST['uid'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('username_no_exist', false); + + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Check if the secret answer is correct. + if ($row['secret_question'] == '' || $row['secret_answer'] == '' || md5($_POST['secret_answer']) != $row['secret_answer']) + { + log_error(sprintf($txt['reminder_error'], $row['member_name']), 'user'); + fatal_lang_error('incorrect_answer', false); + } + + // If it's OpenID this is where the music ends. + if (!empty($row['openid_uri'])) + { + $context['sub_template'] = 'sent'; + $context['description'] = sprintf($txt['reminder_openid_is'], $row['openid_uri']); + return; + } + + // You can't use a blank one! + if (strlen(trim($_POST['passwrd1'])) === 0) + fatal_lang_error('no_password', false); + + // They have to be the same too. + if ($_POST['passwrd1'] != $_POST['passwrd2']) + fatal_lang_error('passwords_dont_match', false); + + // Make sure they have a strong enough password. + require_once($sourcedir . '/Subs-Auth.php'); + $passwordError = validatePassword($_POST['passwrd1'], $row['member_name'], array($row['email_address'])); + + // Invalid? + if ($passwordError != null) + fatal_lang_error('profile_error_password_' . $passwordError, false); + + // Alright, so long as 'yer sure. + updateMemberData($row['id_member'], array('passwd' => sha1(strtolower($row['member_name']) . $_POST['passwrd1']))); + + call_integration_hook('integrate_reset_pass', array($row['member_name'], $row['member_name'], $_POST['passwrd1'])); + + // Tell them it went fine. + loadTemplate('Login'); + $context += array( + 'page_title' => $txt['reminder_password_set'], + 'sub_template' => 'login', + 'default_username' => $row['member_name'], + 'default_password' => $_POST['passwrd1'], + 'never_expire' => false, + 'description' => $txt['reminder_password_set'] + ); +} + +?> \ No newline at end of file diff --git a/Sources/RemoveTopic.php b/Sources/RemoveTopic.php new file mode 100644 index 0000000..b764b8c --- /dev/null +++ b/Sources/RemoveTopic.php @@ -0,0 +1,1461 @@ + $topic, + ) + ); + list ($starter, $subject, $approved) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if ($starter == $user_info['id'] && !allowedTo('remove_any')) + isAllowedTo('remove_own'); + else + isAllowedTo('remove_any'); + + // Can they see the topic? + if ($modSettings['postmod_active'] && !$approved && $starter != $user_info['id']) + isAllowedTo('approve_posts'); + + // Notify people that this topic has been removed. + sendNotifications($topic, 'remove'); + + removeTopics($topic); + + // Note, only log topic ID in native form if it's not gone forever. + if (allowedTo('remove_any') || (allowedTo('remove_own') && $starter == $user_info['id'])) + logAction('remove', array((empty($modSettings['recycle_enable']) || $modSettings['recycle_board'] != $board ? 'topic' : 'old_topic_id') => $topic, 'subject' => $subject, 'member' => $starter, 'board' => $board)); + + redirectexit('board=' . $board . '.0'); +} + +// Remove just a single post. +function DeleteMessage() +{ + global $user_info, $topic, $board, $modSettings, $smcFunc; + + checkSession('get'); + + $_REQUEST['msg'] = (int) $_REQUEST['msg']; + + // Is $topic set? + if (empty($topic) && isset($_REQUEST['topic'])) + $topic = (int) $_REQUEST['topic']; + + $request = $smcFunc['db_query']('', ' + SELECT t.id_member_started, m.id_member, m.subject, m.poster_time, m.approved + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = {int:id_msg} AND m.id_topic = {int:current_topic}) + WHERE t.id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + 'id_msg' => $_REQUEST['msg'], + ) + ); + list ($starter, $poster, $subject, $post_time, $approved) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Verify they can see this! + if ($modSettings['postmod_active'] && !$approved && !empty($poster) && $poster != $user_info['id']) + isAllowedTo('approve_posts'); + + if ($poster == $user_info['id']) + { + if (!allowedTo('delete_own')) + { + if ($starter == $user_info['id'] && !allowedTo('delete_any')) + isAllowedTo('delete_replies'); + elseif (!allowedTo('delete_any')) + isAllowedTo('delete_own'); + } + elseif (!allowedTo('delete_any') && ($starter != $user_info['id'] || !allowedTo('delete_replies')) && !empty($modSettings['edit_disable_time']) && $post_time + $modSettings['edit_disable_time'] * 60 < time()) + fatal_lang_error('modify_post_time_passed', false); + } + elseif ($starter == $user_info['id'] && !allowedTo('delete_any')) + isAllowedTo('delete_replies'); + else + isAllowedTo('delete_any'); + + // If the full topic was removed go back to the board. + $full_topic = removeMessage($_REQUEST['msg']); + + if (allowedTo('delete_any') && (!allowedTo('delete_own') || $poster != $user_info['id'])) + logAction('delete', array('topic' => $topic, 'subject' => $subject, 'member' => $poster, 'board' => $board)); + + // We want to redirect back to recent action. + if (isset($_REQUEST['recent'])) + redirectexit('action=recent'); + elseif (isset($_REQUEST['profile'], $_REQUEST['start'], $_REQUEST['u'])) + redirectexit('action=profile;u=' . $_REQUEST['u'] . ';area=showposts;start=' . $_REQUEST['start']); + elseif ($full_topic) + redirectexit('board=' . $board . '.0'); + else + redirectexit('topic=' . $topic . '.' . $_REQUEST['start']); +} + +// So long as you are sure... all old posts will be gone. +function RemoveOldTopics2() +{ + global $modSettings, $smcFunc; + + isAllowedTo('admin_forum'); + checkSession('post', 'admin'); + + // No boards at all? Forget it then :/. + if (empty($_POST['boards'])) + redirectexit('action=admin;area=maintain;sa=topics'); + + // This should exist, but we can make sure. + $_POST['delete_type'] = isset($_POST['delete_type']) ? $_POST['delete_type'] : 'nothing'; + + // Custom conditions. + $condition = ''; + $condition_params = array( + 'boards' => array_keys($_POST['boards']), + 'poster_time' => time() - 3600 * 24 * $_POST['maxdays'], + ); + + // Just moved notice topics? + if ($_POST['delete_type'] == 'moved') + { + $condition .= ' + AND m.icon = {string:icon} + AND t.locked = {int:locked}'; + $condition_params['icon'] = 'moved'; + $condition_params['locked'] = 1; + } + // Otherwise, maybe locked topics only? + elseif ($_POST['delete_type'] == 'locked') + { + $condition .= ' + AND t.locked = {int:locked}'; + $condition_params['locked'] = 1; + } + + // Exclude stickies? + if (isset($_POST['delete_old_not_sticky'])) + { + $condition .= ' + AND t.is_sticky = {int:is_sticky}'; + $condition_params['is_sticky'] = 0; + } + + // All we're gonna do here is grab the id_topic's and send them to removeTopics(). + $request = $smcFunc['db_query']('', ' + SELECT t.id_topic + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_last_msg) + WHERE + m.poster_time < {int:poster_time}' . $condition . ' + AND t.id_board IN ({array_int:boards})', + $condition_params + ); + $topics = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $topics[] = $row['id_topic']; + $smcFunc['db_free_result']($request); + + removeTopics($topics, false, true); + + // Log an action into the moderation log. + logAction('pruned', array('days' => $_POST['maxdays'])); + + redirectexit('action=admin;area=maintain;sa=topics;done=purgeold'); +} + +// Removes the passed id_topic's. (permissions are NOT checked here!) +function removeTopics($topics, $decreasePostCount = true, $ignoreRecycling = false) +{ + global $sourcedir, $modSettings, $smcFunc; + + // Nothing to do? + if (empty($topics)) + return; + // Only a single topic. + elseif (is_numeric($topics)) + $topics = array($topics); + + // Decrease the post counts. + if ($decreasePostCount) + { + $requestMembers = $smcFunc['db_query']('', ' + SELECT m.id_member, COUNT(*) AS posts + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + WHERE m.id_topic IN ({array_int:topics}) + AND m.icon != {string:recycled} + AND b.count_posts = {int:do_count_posts} + AND m.approved = {int:is_approved} + GROUP BY m.id_member', + array( + 'do_count_posts' => 0, + 'recycled' => 'recycled', + 'topics' => $topics, + 'is_approved' => 1, + ) + ); + if ($smcFunc['db_num_rows']($requestMembers) > 0) + { + while ($rowMembers = $smcFunc['db_fetch_assoc']($requestMembers)) + updateMemberData($rowMembers['id_member'], array('posts' => 'posts - ' . $rowMembers['posts'])); + } + $smcFunc['db_free_result']($requestMembers); + } + + // Recycle topics that aren't in the recycle board... + if (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 && !$ignoreRecycling) + { + $request = $smcFunc['db_query']('', ' + SELECT id_topic, id_board, unapproved_posts, approved + FROM {db_prefix}topics + WHERE id_topic IN ({array_int:topics}) + AND id_board != {int:recycle_board} + LIMIT ' . count($topics), + array( + 'recycle_board' => $modSettings['recycle_board'], + 'topics' => $topics, + ) + ); + if ($smcFunc['db_num_rows']($request) > 0) + { + // Get topics that will be recycled. + $recycleTopics = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + $recycleTopics[] = $row['id_topic']; + + // Set the id_previous_board for this topic - and make it not sticky. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET id_previous_board = {int:id_previous_board}, is_sticky = {int:not_sticky} + WHERE id_topic = {int:id_topic}', + array( + 'id_previous_board' => $row['id_board'], + 'id_topic' => $row['id_topic'], + 'not_sticky' => 0, + ) + ); + } + $smcFunc['db_free_result']($request); + + // Mark recycled topics as recycled. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET icon = {string:recycled} + WHERE id_topic IN ({array_int:recycle_topics})', + array( + 'recycle_topics' => $recycleTopics, + 'recycled' => 'recycled', + ) + ); + + // Move the topics to the recycle board. + require_once($sourcedir . '/MoveTopic.php'); + moveTopics($recycleTopics, $modSettings['recycle_board']); + + // Close reports that are being recycled. + require_once($sourcedir . '/ModerationCenter.php'); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_reported + SET closed = {int:is_closed} + WHERE id_topic IN ({array_int:recycle_topics})', + array( + 'recycle_topics' => $recycleTopics, + 'is_closed' => 1, + ) + ); + + updateSettings(array('last_mod_report_action' => time())); + recountOpenReports(); + + // Topics that were recycled don't need to be deleted, so subtract them. + $topics = array_diff($topics, $recycleTopics); + } + else + $smcFunc['db_free_result']($request); + } + + // Still topics left to delete? + if (empty($topics)) + return; + + $adjustBoards = array(); + + // Find out how many posts we are deleting. + $request = $smcFunc['db_query']('', ' + SELECT id_board, approved, COUNT(*) AS num_topics, SUM(unapproved_posts) AS unapproved_posts, + SUM(num_replies) AS num_replies + FROM {db_prefix}topics + WHERE id_topic IN ({array_int:topics}) + GROUP BY id_board, approved', + array( + 'topics' => $topics, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!isset($adjustBoards[$row['id_board']]['num_posts'])) + { + $adjustBoards[$row['id_board']] = array( + 'num_posts' => 0, + 'num_topics' => 0, + 'unapproved_posts' => 0, + 'unapproved_topics' => 0, + 'id_board' => $row['id_board'] + ); + } + // Posts = (num_replies + 1) for each approved topic. + $adjustBoards[$row['id_board']]['num_posts'] += $row['num_replies'] + ($row['approved'] ? $row['num_topics'] : 0); + $adjustBoards[$row['id_board']]['unapproved_posts'] += $row['unapproved_posts']; + + // Add the topics to the right type. + if ($row['approved']) + $adjustBoards[$row['id_board']]['num_topics'] += $row['num_topics']; + else + $adjustBoards[$row['id_board']]['unapproved_topics'] += $row['num_topics']; + } + $smcFunc['db_free_result']($request); + + // Decrease the posts/topics... + foreach ($adjustBoards as $stats) + { + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET + num_posts = CASE WHEN {int:num_posts} > num_posts THEN 0 ELSE num_posts - {int:num_posts} END, + num_topics = CASE WHEN {int:num_topics} > num_topics THEN 0 ELSE num_topics - {int:num_topics} END, + unapproved_posts = CASE WHEN {int:unapproved_posts} > unapproved_posts THEN 0 ELSE unapproved_posts - {int:unapproved_posts} END, + unapproved_topics = CASE WHEN {int:unapproved_topics} > unapproved_topics THEN 0 ELSE unapproved_topics - {int:unapproved_topics} END + WHERE id_board = {int:id_board}', + array( + 'id_board' => $stats['id_board'], + 'num_posts' => $stats['num_posts'], + 'num_topics' => $stats['num_topics'], + 'unapproved_posts' => $stats['unapproved_posts'], + 'unapproved_topics' => $stats['unapproved_topics'], + ) + ); + } + + // Remove Polls. + $request = $smcFunc['db_query']('', ' + SELECT id_poll + FROM {db_prefix}topics + WHERE id_topic IN ({array_int:topics}) + AND id_poll > {int:no_poll} + LIMIT ' . count($topics), + array( + 'no_poll' => 0, + 'topics' => $topics, + ) + ); + $polls = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $polls[] = $row['id_poll']; + $smcFunc['db_free_result']($request); + + if (!empty($polls)) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}polls + WHERE id_poll IN ({array_int:polls})', + array( + 'polls' => $polls, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}poll_choices + WHERE id_poll IN ({array_int:polls})', + array( + 'polls' => $polls, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_polls + WHERE id_poll IN ({array_int:polls})', + array( + 'polls' => $polls, + ) + ); + } + + // Get rid of the attachment, if it exists. + require_once($sourcedir . '/ManageAttachments.php'); + $attachmentQuery = array( + 'attachment_type' => 0, + 'id_topic' => $topics, + ); + removeAttachments($attachmentQuery, 'messages'); + + // Delete possible search index entries. + if (!empty($modSettings['search_custom_index_config'])) + { + $customIndexSettings = unserialize($modSettings['search_custom_index_config']); + + $words = array(); + $messages = array(); + $request = $smcFunc['db_query']('', ' + SELECT id_msg, body + FROM {db_prefix}messages + WHERE id_topic IN ({array_int:topics})', + array( + 'topics' => $topics, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + $words = array_merge($words, text2words($row['body'], $customIndexSettings['bytes_per_word'], true)); + $messages[] = $row['id_msg']; + } + $smcFunc['db_free_result']($request); + $words = array_unique($words); + + if (!empty($words) && !empty($messages)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_search_words + WHERE id_word IN ({array_int:word_list}) + AND id_msg IN ({array_int:message_list})', + array( + 'word_list' => $words, + 'message_list' => $messages, + ) + ); + } + + // Delete anything related to the topic. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}messages + WHERE id_topic IN ({array_int:topics})', + array( + 'topics' => $topics, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}calendar + WHERE id_topic IN ({array_int:topics})', + array( + 'topics' => $topics, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_topics + WHERE id_topic IN ({array_int:topics})', + array( + 'topics' => $topics, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_notify + WHERE id_topic IN ({array_int:topics})', + array( + 'topics' => $topics, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}topics + WHERE id_topic IN ({array_int:topics})', + array( + 'topics' => $topics, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_search_subjects + WHERE id_topic IN ({array_int:topics})', + array( + 'topics' => $topics, + ) + ); + + // Update the totals... + updateStats('message'); + updateStats('topic'); + updateSettings(array( + 'calendar_updated' => time(), + )); + + require_once($sourcedir . '/Subs-Post.php'); + $updates = array(); + foreach ($adjustBoards as $stats) + $updates[] = $stats['id_board']; + updateLastMessages($updates); +} + +// Remove a specific message (including permission checks). +function removeMessage($message, $decreasePostCount = true) +{ + global $board, $sourcedir, $modSettings, $user_info, $smcFunc, $context; + + if (empty($message) || !is_numeric($message)) + return false; + + $request = $smcFunc['db_query']('', ' + SELECT + m.id_member, m.icon, m.poster_time, m.subject,' . (empty($modSettings['search_custom_index_config']) ? '' : ' m.body,') . ' + m.approved, t.id_topic, t.id_first_msg, t.id_last_msg, t.num_replies, t.id_board, + t.id_member_started AS id_member_poster, + b.count_posts + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + WHERE m.id_msg = {int:id_msg} + LIMIT 1', + array( + 'id_msg' => $message, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + return false; + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + if (empty($board) || $row['id_board'] != $board) + { + $delete_any = boardsAllowedTo('delete_any'); + + if (!in_array(0, $delete_any) && !in_array($row['id_board'], $delete_any)) + { + $delete_own = boardsAllowedTo('delete_own'); + $delete_own = in_array(0, $delete_own) || in_array($row['id_board'], $delete_own); + $delete_replies = boardsAllowedTo('delete_replies'); + $delete_replies = in_array(0, $delete_replies) || in_array($row['id_board'], $delete_replies); + + if ($row['id_member'] == $user_info['id']) + { + if (!$delete_own) + { + if ($row['id_member_poster'] == $user_info['id']) + { + if (!$delete_replies) + fatal_lang_error('cannot_delete_replies', 'permission'); + } + else + fatal_lang_error('cannot_delete_own', 'permission'); + } + elseif (($row['id_member_poster'] != $user_info['id'] || !$delete_replies) && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time()) + fatal_lang_error('modify_post_time_passed', false); + } + elseif ($row['id_member_poster'] == $user_info['id']) + { + if (!$delete_replies) + fatal_lang_error('cannot_delete_replies', 'permission'); + } + else + fatal_lang_error('cannot_delete_any', 'permission'); + } + + // Can't delete an unapproved message, if you can't see it! + if ($modSettings['postmod_active'] && !$row['approved'] && $row['id_member'] != $user_info['id'] && !(in_array(0, $delete_any) || in_array($row['id_board'], $delete_any))) + { + $approve_posts = boardsAllowedTo('approve_posts'); + if (!in_array(0, $approve_posts) && !in_array($row['id_board'], $approve_posts)) + return false; + } + } + else + { + // Check permissions to delete this message. + if ($row['id_member'] == $user_info['id']) + { + if (!allowedTo('delete_own')) + { + if ($row['id_member_poster'] == $user_info['id'] && !allowedTo('delete_any')) + isAllowedTo('delete_replies'); + elseif (!allowedTo('delete_any')) + isAllowedTo('delete_own'); + } + elseif (!allowedTo('delete_any') && ($row['id_member_poster'] != $user_info['id'] || !allowedTo('delete_replies')) && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time()) + fatal_lang_error('modify_post_time_passed', false); + } + elseif ($row['id_member_poster'] == $user_info['id'] && !allowedTo('delete_any')) + isAllowedTo('delete_replies'); + else + isAllowedTo('delete_any'); + + if ($modSettings['postmod_active'] && !$row['approved'] && $row['id_member'] != $user_info['id'] && !allowedTo('delete_own')) + isAllowedTo('approve_posts'); + } + + // Close any moderation reports for this message. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_reported + SET closed = {int:is_closed} + WHERE id_msg = {int:id_msg}', + array( + 'is_closed' => 1, + 'id_msg' => $message, + ) + ); + if ($smcFunc['db_affected_rows']() != 0) + { + require_once($sourcedir . '/ModerationCenter.php'); + updateSettings(array('last_mod_report_action' => time())); + recountOpenReports(); + } + + // Delete the *whole* topic, but only if the topic consists of one message. + if ($row['id_first_msg'] == $message) + { + if (empty($board) || $row['id_board'] != $board) + { + $remove_any = boardsAllowedTo('remove_any'); + $remove_any = in_array(0, $remove_any) || in_array($row['id_board'], $remove_any); + if (!$remove_any) + { + $remove_own = boardsAllowedTo('remove_own'); + $remove_own = in_array(0, $remove_own) || in_array($row['id_board'], $remove_own); + } + + if ($row['id_member'] != $user_info['id'] && !$remove_any) + fatal_lang_error('cannot_remove_any', 'permission'); + elseif (!$remove_any && !$remove_own) + fatal_lang_error('cannot_remove_own', 'permission'); + } + else + { + // Check permissions to delete a whole topic. + if ($row['id_member'] != $user_info['id']) + isAllowedTo('remove_any'); + elseif (!allowedTo('remove_any')) + isAllowedTo('remove_own'); + } + + // ...if there is only one post. + if (!empty($row['num_replies'])) + fatal_lang_error('delFirstPost', false); + + removeTopics($row['id_topic']); + return true; + } + + // Deleting a recycled message can not lower anyone's post count. + if ($row['icon'] == 'recycled') + $decreasePostCount = false; + + // This is the last post, update the last post on the board. + if ($row['id_last_msg'] == $message) + { + // Find the last message, set it, and decrease the post count. + $request = $smcFunc['db_query']('', ' + SELECT id_msg, id_member + FROM {db_prefix}messages + WHERE id_topic = {int:id_topic} + AND id_msg != {int:id_msg} + ORDER BY ' . ($modSettings['postmod_active'] ? 'approved DESC, ' : '') . 'id_msg DESC + LIMIT 1', + array( + 'id_topic' => $row['id_topic'], + 'id_msg' => $message, + ) + ); + $row2 = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET + id_last_msg = {int:id_last_msg}, + id_member_updated = {int:id_member_updated}' . (!$modSettings['postmod_active'] || $row['approved'] ? ', + num_replies = CASE WHEN num_replies = {int:no_replies} THEN 0 ELSE num_replies - 1 END' : ', + unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . ' + WHERE id_topic = {int:id_topic}', + array( + 'id_last_msg' => $row2['id_msg'], + 'id_member_updated' => $row2['id_member'], + 'no_replies' => 0, + 'no_unapproved' => 0, + 'id_topic' => $row['id_topic'], + ) + ); + } + // Only decrease post counts. + else + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET ' . ($row['approved'] ? ' + num_replies = CASE WHEN num_replies = {int:no_replies} THEN 0 ELSE num_replies - 1 END' : ' + unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . ' + WHERE id_topic = {int:id_topic}', + array( + 'no_replies' => 0, + 'no_unapproved' => 0, + 'id_topic' => $row['id_topic'], + ) + ); + + // Default recycle to false. + $recycle = false; + + // If recycle topics has been set, make a copy of this message in the recycle board. + // Make sure we're not recycling messages that are already on the recycle board. + if (!empty($modSettings['recycle_enable']) && $row['id_board'] != $modSettings['recycle_board'] && $row['icon'] != 'recycled') + { + // Check if the recycle board exists and if so get the read status. + $request = $smcFunc['db_query']('', ' + SELECT (IFNULL(lb.id_msg, 0) >= b.id_msg_updated) AS is_seen, id_last_msg + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member}) + WHERE b.id_board = {int:recycle_board}', + array( + 'current_member' => $user_info['id'], + 'recycle_board' => $modSettings['recycle_board'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('recycle_no_valid_board'); + list ($isRead, $last_board_msg) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Is there an existing topic in the recycle board to group this post with? + $request = $smcFunc['db_query']('', ' + SELECT id_topic, id_first_msg, id_last_msg + FROM {db_prefix}topics + WHERE id_previous_topic = {int:id_previous_topic} + AND id_board = {int:recycle_board}', + array( + 'id_previous_topic' => $row['id_topic'], + 'recycle_board' => $modSettings['recycle_board'], + ) + ); + list ($id_recycle_topic, $first_topic_msg, $last_topic_msg) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Insert a new topic in the recycle board if $id_recycle_topic is empty. + if (empty($id_recycle_topic)) + $smcFunc['db_insert']('', + '{db_prefix}topics', + array( + 'id_board' => 'int', 'id_member_started' => 'int', 'id_member_updated' => 'int', 'id_first_msg' => 'int', + 'id_last_msg' => 'int', 'unapproved_posts' => 'int', 'approved' => 'int', 'id_previous_topic' => 'int', + ), + array( + $modSettings['recycle_board'], $row['id_member'], $row['id_member'], $message, + $message, 0, 1, $row['id_topic'], + ), + array('id_topic') + ); + + // Capture the ID of the new topic... + $topicID = empty($id_recycle_topic) ? $smcFunc['db_insert_id']('{db_prefix}topics', 'id_topic') : $id_recycle_topic; + + // If the topic creation went successful, move the message. + if ($topicID > 0) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET + id_topic = {int:id_topic}, + id_board = {int:recycle_board}, + icon = {string:recycled}, + approved = {int:is_approved} + WHERE id_msg = {int:id_msg}', + array( + 'id_topic' => $topicID, + 'recycle_board' => $modSettings['recycle_board'], + 'id_msg' => $message, + 'recycled' => 'recycled', + 'is_approved' => 1, + ) + ); + + // Take any reported posts with us... + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_reported + SET + id_topic = {int:id_topic}, + id_board = {int:recycle_board} + WHERE id_msg = {int:id_msg}', + array( + 'id_topic' => $topicID, + 'recycle_board' => $modSettings['recycle_board'], + 'id_msg' => $message, + ) + ); + + // Mark recycled topic as read. + if (!$user_info['is_guest']) + $smcFunc['db_insert']('replace', + '{db_prefix}log_topics', + array('id_topic' => 'int', 'id_member' => 'int', 'id_msg' => 'int'), + array($topicID, $user_info['id'], $modSettings['maxMsgID']), + array('id_topic', 'id_member') + ); + + // Mark recycle board as seen, if it was marked as seen before. + if (!empty($isRead) && !$user_info['is_guest']) + $smcFunc['db_insert']('replace', + '{db_prefix}log_boards', + array('id_board' => 'int', 'id_member' => 'int', 'id_msg' => 'int'), + array($modSettings['recycle_board'], $user_info['id'], $modSettings['maxMsgID']), + array('id_board', 'id_member') + ); + + // Add one topic and post to the recycle bin board. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET + num_topics = num_topics + {int:num_topics_inc}, + num_posts = num_posts + 1' . + ($message > $last_board_msg ? ', id_last_msg = {int:id_merged_msg}' : '') . ' + WHERE id_board = {int:recycle_board}', + array( + 'num_topics_inc' => empty($id_recycle_topic) ? 1 : 0, + 'recycle_board' => $modSettings['recycle_board'], + 'id_merged_msg' => $message, + ) + ); + + // Lets increase the num_replies, and the first/last message ID as appropriate. + if (!empty($id_recycle_topic)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET num_replies = num_replies + 1' . + ($message > $last_topic_msg ? ', id_last_msg = {int:id_merged_msg}' : '') . + ($message < $first_topic_msg ? ', id_first_msg = {int:id_merged_msg}' : '') . ' + WHERE id_topic = {int:id_recycle_topic}', + array( + 'id_recycle_topic' => $id_recycle_topic, + 'id_merged_msg' => $message, + ) + ); + + // Make sure this message isn't getting deleted later on. + $recycle = true; + + // Make sure we update the search subject index. + updateStats('subject', $topicID, $row['subject']); + } + + // If it wasn't approved don't keep it in the queue. + if (!$row['approved']) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}approval_queue + WHERE id_msg = {int:id_msg} + AND id_attach = {int:id_attach}', + array( + 'id_msg' => $message, + 'id_attach' => 0, + ) + ); + } + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET ' . ($row['approved'] ? ' + num_posts = CASE WHEN num_posts = {int:no_posts} THEN 0 ELSE num_posts - 1 END' : ' + unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . ' + WHERE id_board = {int:id_board}', + array( + 'no_posts' => 0, + 'no_unapproved' => 0, + 'id_board' => $row['id_board'], + ) + ); + + // If the poster was registered and the board this message was on incremented + // the member's posts when it was posted, decrease his or her post count. + if (!empty($row['id_member']) && $decreasePostCount && empty($row['count_posts']) && $row['approved']) + updateMemberData($row['id_member'], array('posts' => '-')); + + // Only remove posts if they're not recycled. + if (!$recycle) + { + // Remove the message! + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}messages + WHERE id_msg = {int:id_msg}', + array( + 'id_msg' => $message, + ) + ); + + if (!empty($modSettings['search_custom_index_config'])) + { + $customIndexSettings = unserialize($modSettings['search_custom_index_config']); + $words = text2words($row['body'], $customIndexSettings['bytes_per_word'], true); + if (!empty($words)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_search_words + WHERE id_word IN ({array_int:word_list}) + AND id_msg = {int:id_msg}', + array( + 'word_list' => $words, + 'id_msg' => $message, + ) + ); + } + + // Delete attachment(s) if they exist. + require_once($sourcedir . '/ManageAttachments.php'); + $attachmentQuery = array( + 'attachment_type' => 0, + 'id_msg' => $message, + ); + removeAttachments($attachmentQuery); + } + + // Update the pesky statistics. + updateStats('message'); + updateStats('topic'); + updateSettings(array( + 'calendar_updated' => time(), + )); + + // And now to update the last message of each board we messed with. + require_once($sourcedir . '/Subs-Post.php'); + if ($recycle) + updateLastMessages(array($row['id_board'], $modSettings['recycle_board'])); + else + updateLastMessages($row['id_board']); + + return false; +} + +function RestoreTopic() +{ + global $context, $smcFunc, $modSettings, $sourcedir; + + // Check session. + checkSession('get'); + + // Is recycled board enabled? + if (empty($modSettings['recycle_enable'])) + fatal_lang_error('restored_disabled', 'critical'); + + // Can we be in here? + isAllowedTo('move_any', $modSettings['recycle_board']); + + // We need this file. + require_once($sourcedir . '/MoveTopic.php'); + + $unfound_messages = array(); + $topics_to_restore = array(); + + // Restoring messages? + if (!empty($_REQUEST['msgs'])) + { + $msgs = explode(',', $_REQUEST['msgs']); + foreach ($msgs as $k => $msg) + $msgs[$k] = (int) $msg; + + // Get the id_previous_board and id_previous_topic. + $request = $smcFunc['db_query']('', ' + SELECT m.id_topic, m.id_msg, m.id_board, m.subject, m.id_member, t.id_previous_board, t.id_previous_topic, + t.id_first_msg, b.count_posts, IFNULL(pt.id_board, 0) AS possible_prev_board + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + LEFT JOIN {db_prefix}topics AS pt ON (pt.id_topic = t.id_previous_topic) + WHERE m.id_msg IN ({array_int:messages})', + array( + 'messages' => $msgs, + ) + ); + + $actioned_messages = array(); + $previous_topics = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Restoring the first post means topic. + if ($row['id_msg'] == $row['id_first_msg'] && $row['id_previous_topic'] == $row['id_topic']) + { + $topics_to_restore[] = $row['id_topic']; + continue; + } + // Don't know where it's going? + if (empty($row['id_previous_topic'])) + { + $unfound_messages[$row['id_msg']] = $row['subject']; + continue; + } + + $previous_topics[] = $row['id_previous_topic']; + if (empty($actioned_messages[$row['id_previous_topic']])) + $actioned_messages[$row['id_previous_topic']] = array( + 'msgs' => array(), + 'count_posts' => $row['count_posts'], + 'subject' => $row['subject'], + 'previous_board' => $row['id_previous_board'], + 'possible_prev_board' => $row['possible_prev_board'], + 'current_topic' => $row['id_topic'], + 'current_board' => $row['id_board'], + 'members' => array(), + ); + + $actioned_messages[$row['id_previous_topic']]['msgs'][$row['id_msg']] = $row['subject']; + if ($row['id_member']) + $actioned_messages[$row['id_previous_topic']]['members'][] = $row['id_member']; + } + $smcFunc['db_free_result']($request); + + // Check for topics we are going to fully restore. + foreach ($actioned_messages as $topic => $data) + if (in_array($topic, $topics_to_restore)) + unset($actioned_messages[$topic]); + + // Load any previous topics to check they exist. + if (!empty($previous_topics)) + { + $request = $smcFunc['db_query']('', ' + SELECT t.id_topic, t.id_board, m.subject + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + WHERE t.id_topic IN ({array_int:previous_topics})', + array( + 'previous_topics' => $previous_topics, + ) + ); + $previous_topics = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $previous_topics[$row['id_topic']] = array( + 'board' => $row['id_board'], + 'subject' => $row['subject'], + ); + $smcFunc['db_free_result']($request); + } + + // Restore each topic. + $messages = array(); + foreach ($actioned_messages as $topic => $data) + { + // If we have topics we are going to restore the whole lot ignore them. + if (in_array($topic, $topics_to_restore)) + { + unset($actioned_messages[$topic]); + continue; + } + + // Move the posts back then! + if (isset($previous_topics[$topic])) + { + mergePosts(array_keys($data['msgs']), $data['current_topic'], $topic); + // Log em. + logAction('restore_posts', array('topic' => $topic, 'subject' => $previous_topics[$topic]['subject'], 'board' => empty($data['previous_board']) ? $data['possible_prev_board'] : $data['previous_board'])); + $messages = array_merge(array_keys($data['msgs']), $messages); + } + else + { + foreach ($data['msgs'] as $msg) + $unfound_messages[$msg['id']] = $msg['subject']; + } + } + + // Put the icons back. + if (!empty($messages)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET icon = {string:icon} + WHERE id_msg IN ({array_int:messages})', + array( + 'icon' => 'xx', + 'messages' => $messages, + ) + ); + } + + // Now any topics? + if (!empty($_REQUEST['topics'])) + { + $topics = explode(',', $_REQUEST['topics']); + foreach ($topics as $key => $id) + $topics_to_restore[] = (int) $id; + } + + if (!empty($topics_to_restore)) + { + // Lets get the data for these topics. + $request = $smcFunc['db_query']('', ' + SELECT t.id_topic, t.id_previous_board, t.id_board, t.id_first_msg, m.subject + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + WHERE t.id_topic IN ({array_int:topics})', + array( + 'topics' => $topics_to_restore, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // We can only restore if the previous board is set. + if (empty($row['id_previous_board'])) + { + $unfound_messages[$row['id_first_msg']] = $row['subject']; + continue; + } + + // Ok we got here so me move them from here to there. + moveTopics($row['id_topic'], $row['id_previous_board']); + + // Lets remove the recycled icon. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET icon = {string:icon} + WHERE id_topic = {int:id_topic}', + array( + 'icon' => 'xx', + 'id_topic' => $row['id_topic'], + ) + ); + + // Lets see if the board that we are returning to has post count enabled. + $request2 = $smcFunc['db_query']('', ' + SELECT count_posts + FROM {db_prefix}boards + WHERE id_board = {int:board}', + array( + 'board' => $row['id_previous_board'], + ) + ); + list ($count_posts) = $smcFunc['db_fetch_row']($request2); + $smcFunc['db_free_result']($request2); + + if (empty($count_posts)) + { + // Lets get the members that need their post count restored. + $request2 = $smcFunc['db_query']('', ' + SELECT id_member, COUNT(id_msg) AS post_count + FROM {db_prefix}messages + WHERE id_topic = {int:topic} + AND approved = {int:is_approved} + GROUP BY id_member', + array( + 'topic' => $row['id_topic'], + 'is_approved' => 1, + ) + ); + + while ($member = $smcFunc['db_fetch_assoc']($request2)) + updateMemberData($member['id_member'], array('posts' => 'posts + ' . $member['post_count'])); + $smcFunc['db_free_result']($request2); + } + + // Log it. + logAction('restore_topic', array('topic' => $row['id_topic'], 'board' => $row['id_board'], 'board_to' => $row['id_previous_board'])); + } + $smcFunc['db_free_result']($request); + } + + // Didn't find some things? + if (!empty($unfound_messages)) + fatal_lang_error('restore_not_found', false, array(implode('
', $unfound_messages))); + + // Just send them to the index if they get here. + redirectexit(); +} + +// Take a load of messages from one place and stick them in a topic. +function mergePosts($msgs = array(), $from_topic, $target_topic) +{ + global $context, $smcFunc, $modSettings, $sourcedir; + + //!!! This really needs to be rewritten to take a load of messages from ANY topic, it's also inefficient. + + // Is it an array? + if (!is_array($msgs)) + $msgs = array($msgs); + + // Lets make sure they are int. + foreach ($msgs as $key => $msg) + $msgs[$key] = (int) $msg; + + // Get the source information. + $request = $smcFunc['db_query']('', ' + SELECT t.id_board, t.id_first_msg, t.num_replies, t.unapproved_posts + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + WHERE t.id_topic = {int:from_topic}', + array( + 'from_topic' => $from_topic, + ) + ); + list ($from_board, $from_first_msg, $from_replies, $from_unapproved_posts) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Get some target topic and board stats. + $request = $smcFunc['db_query']('', ' + SELECT t.id_board, t.id_first_msg, t.num_replies, t.unapproved_posts, b.count_posts + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + WHERE t.id_topic = {int:target_topic}', + array( + 'target_topic' => $target_topic, + ) + ); + list ($target_board, $target_first_msg, $target_replies, $target_unapproved_posts, $count_posts) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Lets see if the board that we are returning to has post count enabled. + if (empty($count_posts)) + { + // Lets get the members that need their post count restored. + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}messages + WHERE id_msg IN ({array_int:messages}) + AND approved = {int:is_approved}', + array( + 'messages' => $msgs, + 'is_approved' => 1, + ) + ); + + while ($row = $smcFunc['db_fetch_assoc']($request)) + updateMemberData($row['id_member'], array('posts' => '+')); + } + + // Time to move the messages. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET + id_topic = {int:target_topic}, + id_board = {int:target_board}, + icon = {string:icon} + WHERE id_msg IN({array_int:msgs})', + array( + 'target_topic' => $target_topic, + 'target_board' => $target_board, + 'icon' => $target_board == $modSettings['recycle_board'] ? 'recycled' : 'xx', + 'msgs' => $msgs, + ) + ); + + // Fix the id_first_msg and id_last_msg for the target topic. + $target_topic_data = array( + 'num_replies' => 0, + 'unapproved_posts' => 0, + 'id_first_msg' => 9999999999, + ); + $request = $smcFunc['db_query']('', ' + SELECT MIN(id_msg) AS id_first_msg, MAX(id_msg) AS id_last_msg, COUNT(*) AS message_count, approved + FROM {db_prefix}messages + WHERE id_topic = {int:target_topic} + GROUP BY id_topic, approved + ORDER BY approved ASC + LIMIT 2', + array( + 'target_topic' => $target_topic, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['id_first_msg'] < $target_topic_data['id_first_msg']) + $target_topic_data['id_first_msg'] = $row['id_first_msg']; + $target_topic_data['id_last_msg'] = $row['id_last_msg']; + if (!$row['approved']) + $target_topic_data['unapproved_posts'] = $row['message_count']; + else + $target_topic_data['num_replies'] = max(0, $row['message_count'] - 1); + } + $smcFunc['db_free_result']($request); + + // We have a new post count for the board. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET + num_posts = num_posts + {int:diff_replies}, + unapproved_posts = unapproved_posts + {int:diff_unapproved_posts} + WHERE id_board = {int:target_board}', + array( + 'diff_replies' => $target_topic_data['num_replies'] - $target_replies, // Lets keep in mind that the first message in a topic counts towards num_replies in a board. + 'diff_unapproved_posts' => $target_topic_data['unapproved_posts'] - $target_unapproved_posts, + 'target_board' => $target_board, + ) + ); + + // In some cases we merged the only post in a topic so the topic data is left behind in the topic table. + $request = $smcFunc['db_query']('', ' + SELECT id_topic + FROM {db_prefix}messages + WHERE id_topic = {int:from_topic}', + array( + 'from_topic' => $from_topic, + ) + ); + + // Remove the topic if it doesn't have any messages. + $topic_exists = true; + if ($smcFunc['db_num_rows']($request) == 0) + { + removeTopics($from_topic, false, true); + $topic_exists = false; + } + $smcFunc['db_free_result']($request); + + // Recycled topic. + if ($topic_exists == true) + { + // Fix the id_first_msg and id_last_msg for the source topic. + $source_topic_data = array( + 'num_replies' => 0, + 'unapproved_posts' => 0, + 'id_first_msg' => 9999999999, + ); + $request = $smcFunc['db_query']('', ' + SELECT MIN(id_msg) AS id_first_msg, MAX(id_msg) AS id_last_msg, COUNT(*) AS message_count, approved, subject + FROM {db_prefix}messages + WHERE id_topic = {int:from_topic} + GROUP BY id_topic, approved + ORDER BY approved ASC + LIMIT 2', + array( + 'from_topic' => $from_topic, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['id_first_msg'] < $source_topic_data['id_first_msg']) + $source_topic_data['id_first_msg'] = $row['id_first_msg']; + $source_topic_data['id_last_msg'] = $row['id_last_msg']; + if (!$row['approved']) + $source_topic_data['unapproved_posts'] = $row['message_count']; + else + $source_topic_data['num_replies'] = max(0, $row['message_count'] - 1); + } + $smcFunc['db_free_result']($request); + + // Update the topic details for the source topic. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET + id_first_msg = {int:id_first_msg}, + id_last_msg = {int:id_last_msg}, + num_replies = {int:num_replies}, + unapproved_posts = {int:unapproved_posts} + WHERE id_topic = {int:from_topic}', + array( + 'id_first_msg' => $source_topic_data['id_first_msg'], + 'id_last_msg' => $source_topic_data['id_last_msg'], + 'num_replies' => $source_topic_data['num_replies'], + 'unapproved_posts' => $source_topic_data['unapproved_posts'], + 'from_topic' => $from_topic, + ) + ); + + // We have a new post count for the source board. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET + num_posts = num_posts + {int:diff_replies}, + unapproved_posts = unapproved_posts + {int:diff_unapproved_posts} + WHERE id_board = {int:from_board}', + array( + 'diff_replies' => $source_topic_data['num_replies'] - $from_replies, // Lets keep in mind that the first message in a topic counts towards num_replies in a board. + 'diff_unapproved_posts' => $source_topic_data['unapproved_posts'] - $from_unapproved_posts, + 'from_board' => $from_board, + ) + ); + } + + // Finally get around to updating the destination topic, now all indexes etc on the source are fixed. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET + id_first_msg = {int:id_first_msg}, + id_last_msg = {int:id_last_msg}, + num_replies = {int:num_replies}, + unapproved_posts = {int:unapproved_posts} + WHERE id_topic = {int:target_topic}', + array( + 'id_first_msg' => $target_topic_data['id_first_msg'], + 'id_last_msg' => $target_topic_data['id_last_msg'], + 'num_replies' => $target_topic_data['num_replies'], + 'unapproved_posts' => $target_topic_data['unapproved_posts'], + 'target_topic' => $target_topic, + ) + ); + + // Need it to update some stats. + require_once($sourcedir . '/Subs-Post.php'); + + // Update stats. + updateStats('topic'); + updateStats('message'); + + // Subject cache? + $cache_updates = array(); + if ($target_first_msg != $target_topic_data['id_first_msg']) + $cache_updates[] = $target_topic_data['id_first_msg']; + if (!empty($source_topic_data['id_first_msg']) && $from_first_msg != $source_topic_data['id_first_msg']) + $cache_updates[] = $source_topic_data['id_first_msg']; + + if (!empty($cache_updates)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_topic, subject + FROM {db_prefix}messages + WHERE id_msg IN ({array_int:first_messages})', + array( + 'first_messages' => $cache_updates, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + updateStats('subject', $row['id_topic'], $row['subject']); + $smcFunc['db_free_result']($request); + } + + updateLastMessages(array($from_board, $target_board)); +} + +?> \ No newline at end of file diff --git a/Sources/RepairBoards.php b/Sources/RepairBoards.php new file mode 100644 index 0000000..13acf2c --- /dev/null +++ b/Sources/RepairBoards.php @@ -0,0 +1,1644 @@ + $txt['maintain_title'], + 'help' => '', + 'description' => $txt['maintain_info'], + 'tabs' => array(), + ); + + // Start displaying errors without fixing them. + if (isset($_GET['fixErrors'])) + checkSession('get'); + + // Will want this. + loadForumTests(); + + // Giant if/else. The first displays the forum errors if a variable is not set and asks + // if you would like to continue, the other fixes the errors. + if (!isset($_GET['fixErrors'])) + { + $context['error_search'] = true; + $context['repair_errors'] = array(); + $context['to_fix'] = findForumErrors(); + + if (!empty($context['to_fix'])) + { + $_SESSION['repairboards_to_fix'] = $context['to_fix']; + $_SESSION['repairboards_to_fix2'] = null; + + if (empty($context['repair_errors'])) + $context['repair_errors'][] = '???'; + } + } + else + { + $context['error_search'] = false; + $context['to_fix'] = isset($_SESSION['repairboards_to_fix']) ? $_SESSION['repairboards_to_fix'] : array(); + + require_once($sourcedir . '/Subs-Boards.php'); + + // Get the MySQL version for future reference. + $mysql_version = $smcFunc['db_server_info']($db_connection); + + // Actually do the fix. + findForumErrors(true); + + // Note that we've changed everything possible ;) + updateSettings(array( + 'settings_updated' => time(), + )); + updateStats('message'); + updateStats('topic'); + updateSettings(array( + 'calendar_updated' => time(), + )); + + if (!empty($salvageBoardID)) + { + $context['redirect_to_recount'] = true; + } + + $_SESSION['repairboards_to_fix'] = null; + $_SESSION['repairboards_to_fix2'] = null; + } +} + +function pauseRepairProcess($to_fix, $current_step_description, $max_substep = 0, $force = false) +{ + global $context, $txt, $time_start, $db_temp_cache, $db_cache; + + // More time, I need more time! + @set_time_limit(600); + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + // Errr, wait. How much time has this taken already? + if (!$force && time() - array_sum(explode(' ', $time_start)) < 3) + return; + + // Restore the query cache if interested. + if (!empty($db_temp_cache)) + $db_cache = $db_temp_cache; + + $context['continue_get_data'] = '?action=admin;area=repairboards' . (isset($_GET['fixErrors']) ? ';fixErrors' : '') . ';step=' . $_GET['step'] . ';substep=' . $_GET['substep'] . ';' . $context['session_var'] . '=' . $context['session_id']; + $context['page_title'] = $txt['not_done_title']; + $context['continue_post_data'] = ''; + $context['continue_countdown'] = '2'; + $context['sub_template'] = 'not_done'; + + // Change these two if more steps are added! + if (empty($max_substep)) + $context['continue_percent'] = round(($_GET['step'] * 100) / $context['total_steps']); + else + $context['continue_percent'] = round((($_GET['step'] + ($_GET['substep'] / $max_substep)) * 100) / $context['total_steps']); + + // Never more than 100%! + $context['continue_percent'] = min($context['continue_percent'], 100); + + // What about substeps? + $context['substep_enabled'] = $max_substep != 0; + $context['substep_title'] = sprintf($txt['repair_currently_' . (isset($_GET['fixErrors']) ? 'fixing' : 'checking')], (isset($txt['repair_operation_' . $current_step_description]) ? $txt['repair_operation_' . $current_step_description] : $current_step_description)); + $context['substep_continue_percent'] = $max_substep == 0 ? 0 : round(($_GET['substep'] * 100) / $max_substep, 1); + + $_SESSION['repairboards_to_fix'] = $to_fix; + $_SESSION['repairboards_to_fix2'] = $context['repair_errors']; + + obExit(); +} + +// Load up all the tests we might want to do ;) +function loadForumTests() +{ + global $smcFunc, $errorTests; + + /* Here this array is defined like so: + string check_query: Query to be executed when testing if errors exist. + string check_type: Defines how it knows if a problem was found. If set to count looks for the first variable from check_query + being > 0. Anything else it looks for some results. If not set assumes you want results. + string fix_it_query: When doing fixes if an error was detected this query is executed to "fix" it. + string fix_query: The query to execute to get data when doing a fix. If not set check_query is used again. + array fix_collect: This array is used if the fix is basically gathering all broken ids and then doing something with it. + - string index: The value returned from the main query and passed to the processing function. + - process: A function passed an array of ids to execute the fix on. + function fix_processing: + Function called for each row returned from fix_query to execute whatever fixes are required. + function fix_full_processing: + As above but does the while loop and everything itself - except the freeing. + array force_fix: If this is set then the error types included within this array will also be assumed broken. + Note: At the moment only processes these if they occur after the primary error in the array. + */ + + // This great array contains all of our error checks, fixes, etc etc etc. + $errorTests = array( + // Make a last-ditch-effort check to get rid of topics with zeros.. + 'zero_topics' => array( + 'check_query' => ' + SELECT COUNT(*) + FROM {db_prefix}topics + WHERE id_topic = 0', + 'check_type' => 'count', + 'fix_it_query' => ' + UPDATE {db_prefix}topics + SET id_topic = NULL + WHERE id_topic = 0', + 'message' => 'repair_zero_ids', + ), + // ... and same with messages. + 'zero_messages' => array( + 'check_query' => ' + SELECT COUNT(*) + FROM {db_prefix}messages + WHERE id_msg = 0', + 'check_type' => 'count', + 'fix_it_query' => ' + UPDATE {db_prefix}messages + SET id_msg = NULL + WHERE id_msg = 0', + 'message' => 'repair_zero_ids', + ), + // Find messages that don't have existing topics. + 'missing_topics' => array( + 'substeps' => array( + 'step_size' => 1000, + 'step_max' => ' + SELECT MAX(id_topic) + FROM {db_prefix}messages' + ), + 'check_query' => ' + SELECT m.id_topic, m.id_msg + FROM {db_prefix}messages AS m + LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + WHERE m.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH} + AND t.id_topic IS NULL + ORDER BY m.id_topic, m.id_msg', + 'fix_query' => ' + SELECT + m.id_board, m.id_topic, MIN(m.id_msg) AS myid_first_msg, MAX(m.id_msg) AS myid_last_msg, + COUNT(*) - 1 AS my_num_replies + FROM {db_prefix}messages AS m + LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + WHERE t.id_topic IS NULL + GROUP BY m.id_topic, m.id_board', + 'fix_processing' => create_function('$row', ' + global $smcFunc, $salvageBoardID; + + // Only if we don\'t have a reasonable idea of where to put it. + if ($row[\'id_board\'] == 0) + { + createSalvageArea(); + $row[\'id_board\'] = (int) $salvageBoardID; + } + + // Make sure that no topics claim the first/last message as theirs. + $smcFunc[\'db_query\'](\'\', \' + UPDATE {db_prefix}topics + SET id_first_msg = 0 + WHERE id_first_msg = {int:id_first_msg}\', + array( + \'id_first_msg\' => $row[\'myid_first_msg\'], + ) + ); + $smcFunc[\'db_query\'](\'\', \' + UPDATE {db_prefix}topics + SET id_last_msg = 0 + WHERE id_last_msg = {int:id_last_msg}\', + array( + \'id_last_msg\' => $row[\'myid_last_msg\'], + ) + ); + + $memberStartedID = (int) getMsgMemberID($row[\'myid_first_msg\']); + $memberUpdatedID = (int) getMsgMemberID($row[\'myid_last_msg\']); + + $smcFunc[\'db_insert\'](\'\', + \'{db_prefix}topics\', + array( + \'id_board\' => \'int\', + \'id_member_started\' => \'int\', + \'id_member_updated\' => \'int\', + \'id_first_msg\' => \'int\', + \'id_last_msg\' => \'int\', + \'num_replies\' => \'int\' + ), + array( + $row[\'id_board\'], + $memberStartedID, + $memberUpdatedID, + $row[\'myid_first_msg\'], + $row[\'myid_last_msg\'], + $row[\'my_num_replies\'] + ), + array(\'id_topic\') + ); + + $newTopicID = $smcFunc[\'db_insert_id\']("{db_prefix}topics", \'id_topic\'); + + $smcFunc[\'db_query\'](\'\', " + UPDATE {db_prefix}messages + SET id_topic = $newTopicID, id_board = $row[id_board] + WHERE id_topic = $row[id_topic]", + array( + ) + ); + '), + 'force_fix' => array('stats_topics'), + 'messages' => array('repair_missing_topics', 'id_msg', 'id_topic'), + ), + // Find topics with no messages. + 'missing_messages' => array( + 'substeps' => array( + 'step_size' => 1000, + 'step_max' => ' + SELECT MAX(id_topic) + FROM {db_prefix}topics' + ), + 'check_query' => ' + SELECT t.id_topic, COUNT(m.id_msg) AS num_msg + FROM {db_prefix}topics AS t + LEFT JOIN {db_prefix}messages AS m ON (m.id_topic = t.id_topic) + WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH} + GROUP BY t.id_topic + HAVING COUNT(m.id_msg) = 0', + // Remove all topics that have zero messages in the messages table. + 'fix_collect' => array( + 'index' => 'id_topic', + 'process' => create_function('$topics', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}topics + WHERE id_topic IN ({array_int:topics})", + array( + \'topics\' => $topics + ) + ); + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}log_topics + WHERE id_topic IN ({array_int:topics})", + array( + \'topics\' => $topics + ) + ); + '), + ), + 'messages' => array('repair_missing_messages', 'id_topic'), + ), + 'polls_missing_topics' => array( + 'substeps' => array( + 'step_size' => 500, + 'step_max' => ' + SELECT MAX(id_poll) + FROM {db_prefix}polls' + ), + 'check_query' => ' + SELECT p.id_poll, p.id_member, p.poster_name, t.id_board + FROM {db_prefix}polls AS p + LEFT JOIN {db_prefix}topics AS t ON (t.id_poll = p.id_poll) + WHERE p.id_poll BETWEEN {STEP_LOW} AND {STEP_HIGH} + AND t.id_poll IS NULL', + 'fix_processing' => create_function('$row', ' + global $smcFunc, $salvageBoardID, $txt; + + // Only if we don\'t have a reasonable idea of where to put it. + if ($row[\'id_board\'] == 0) + { + createSalvageArea(); + $row[\'id_board\'] = (int) $salvageBoardID; + } + + $row[\'poster_name\'] = !empty($row[\'poster_name\']) ? $row[\'poster_name\'] : $txt[\'guest\']; + + $smcFunc[\'db_insert\'](\'\', + \'{db_prefix}messages\', + array( + \'id_board\' => \'int\', + \'id_topic\' => \'int\', + \'poster_time\' => \'int\', + \'id_member\' => \'int\', + \'subject\' => \'string-255\', + \'poster_name\' => \'string-255\', + \'poster_email\' => \'string-255\', + \'poster_ip\' => \'string-16\', + \'smileys_enabled\' => \'int\', + \'body\' => \'string-65534\', + \'icon\' => \'string-16\', + \'approved\' => \'int\', + ), + array( + $row[\'id_board\'], + 0, + time(), + $row[\'id_member\'], + $txt[\'salvaged_poll_topic_name\'], + $row[\'poster_name\'], + \'\', + \'127.0.0.1\', + 1, + $txt[\'salvaged_poll_message_body\'], + \'xx\', + 1, + ), + array(\'id_topic\') + ); + + $newMessageID = $smcFunc[\'db_insert_id\']("{db_prefix}messages", \'id_msg\'); + + $smcFunc[\'db_insert\'](\'\', + \'{db_prefix}topics\', + array( + \'id_board\' => \'int\', + \'id_poll\' => \'int\', + \'id_member_started\' => \'int\', + \'id_member_updated\' => \'int\', + \'id_first_msg\' => \'int\', + \'id_last_msg\' => \'int\', + \'num_replies\' => \'int\', + ), + array( + $row[\'id_board\'], + $row[\'id_poll\'], + $row[\'id_member\'], + $row[\'id_member\'], + $newMessageID, + $newMessageID, + 0, + ), + array(\'id_topic\') + ); + + $newTopicID = $smcFunc[\'db_insert_id\']("{db_prefix}topics", \'id_topic\'); + + $smcFunc[\'db_query\'](\'\', " + UPDATE {db_prefix}messages + SET id_topic = $newTopicID, id_board = $row[id_board] + WHERE id_msg = $newMessageID", + array( + ) + ); + + updateStats(\'subject\', $newTopicID, $txt[\'salvaged_poll_topic_name\']); + + '), + 'force_fix' => array('stats_topics'), + 'messages' => array('repair_polls_missing_topics', 'id_poll', 'id_topic'), + ), + 'stats_topics' => array( + 'substeps' => array( + 'step_size' => 200, + 'step_max' => ' + SELECT MAX(id_topic) + FROM {db_prefix}topics' + ), + 'check_query' => ' + SELECT + t.id_topic, t.id_first_msg, t.id_last_msg, + CASE WHEN MIN(ma.id_msg) > 0 THEN + CASE WHEN MIN(mu.id_msg) > 0 THEN + CASE WHEN MIN(mu.id_msg) < MIN(ma.id_msg) THEN MIN(mu.id_msg) ELSE MIN(ma.id_msg) END ELSE + MIN(ma.id_msg) END ELSE + MIN(mu.id_msg) END AS myid_first_msg, + CASE WHEN MAX(ma.id_msg) > 0 THEN MAX(ma.id_msg) ELSE MIN(mu.id_msg) END AS myid_last_msg, + t.approved, mf.approved, mf.approved AS firstmsg_approved + FROM {db_prefix}topics AS t + LEFT JOIN {db_prefix}messages AS ma ON (ma.id_topic = t.id_topic AND ma.approved = 1) + LEFT JOIN {db_prefix}messages AS mu ON (mu.id_topic = t.id_topic AND mu.approved = 0) + LEFT JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg) + WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH} + GROUP BY t.id_topic, t.id_first_msg, t.id_last_msg, t.approved, mf.approved + ORDER BY t.id_topic', + 'fix_processing' => create_function('$row', ' + global $smcFunc; + $row[\'firstmsg_approved\'] = (int) $row[\'firstmsg_approved\']; + $row[\'myid_first_msg\'] = (int) $row[\'myid_first_msg\']; + $row[\'myid_last_msg\'] = (int) $row[\'myid_last_msg\']; + + // Not really a problem? + if ($row[\'myid_first_msg\'] == $row[\'myid_first_msg\'] && $row[\'myid_first_msg\'] == $row[\'myid_first_msg\'] && $row[\'approved\'] == $row[\'firstmsg_approved\']) + return false; + + $memberStartedID = (int) getMsgMemberID($row[\'myid_first_msg\']); + $memberUpdatedID = (int) getMsgMemberID($row[\'myid_last_msg\']); + + $smcFunc[\'db_query\'](\'\', " + UPDATE {db_prefix}topics + SET id_first_msg = $row[myid_first_msg], + id_member_started = $memberStartedID, id_last_msg = $row[myid_last_msg], + id_member_updated = $memberUpdatedID, approved = $row[firstmsg_approved] + WHERE id_topic = $row[id_topic]", + array( + ) + ); + '), + 'message_function' => create_function('$row', ' + global $txt, $context; + + // A pretend error? + if ($row[\'myid_first_msg\'] == $row[\'myid_first_msg\'] && $row[\'myid_first_msg\'] == $row[\'myid_first_msg\'] && $row[\'approved\'] == $row[\'firstmsg_approved\']) + return false; + + if ($row[\'id_first_msg\'] != $row[\'myid_first_msg\']) + $context[\'repair_errors\'][] = sprintf($txt[\'repair_stats_topics_1\'], $row[\'id_topic\'], $row[\'id_first_msg\']); + if ($row[\'id_last_msg\'] != $row[\'myid_last_msg\']) + $context[\'repair_errors\'][] = sprintf($txt[\'repair_stats_topics_2\'], $row[\'id_topic\'], $row[\'id_last_msg\']); + if ($row[\'approved\'] != $row[\'firstmsg_approved\']) + $context[\'repair_errors\'][] = sprintf($txt[\'repair_stats_topics_5\'], $row[\'id_topic\']); + + return true; + '), + ), + // Find topics with incorrect num_replies. + 'stats_topics2' => array( + 'substeps' => array( + 'step_size' => 300, + 'step_max' => ' + SELECT MAX(id_topic) + FROM {db_prefix}topics' + ), + 'check_query' => ' + SELECT + t.id_topic, t.num_replies, mf.approved, + CASE WHEN COUNT(ma.id_msg) > 0 THEN CASE WHEN mf.approved > 0 THEN COUNT(ma.id_msg) - 1 ELSE COUNT(ma.id_msg) END ELSE 0 END AS my_num_replies + FROM {db_prefix}topics AS t + LEFT JOIN {db_prefix}messages AS ma ON (ma.id_topic = t.id_topic AND ma.approved = 1) + LEFT JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg) + WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH} + GROUP BY t.id_topic, t.num_replies, mf.approved + ORDER BY t.id_topic', + 'fix_processing' => create_function('$row', ' + global $smcFunc; + $row[\'my_num_replies\'] = (int) $row[\'my_num_replies\']; + + // Not really a problem? + if ($row[\'my_num_replies\'] == $row[\'num_replies\']) + return false; + + $smcFunc[\'db_query\'](\'\', " + UPDATE {db_prefix}topics + SET num_replies = $row[my_num_replies] + WHERE id_topic = $row[id_topic]", + array( + ) + ); + '), + 'message_function' => create_function('$row', ' + global $txt, $context; + + // Just joking? + if ($row[\'my_num_replies\'] == $row[\'num_replies\']) + return false; + + if ($row[\'num_replies\'] != $row[\'my_num_replies\']) + $context[\'repair_errors\'][] = sprintf($txt[\'repair_stats_topics_3\'], $row[\'id_topic\'], $row[\'num_replies\']); + + return true; + '), + ), + // Find topics with incorrect unapproved_posts. + 'stats_topics3' => array( + 'substeps' => array( + 'step_size' => 1000, + 'step_max' => ' + SELECT MAX(id_topic) + FROM {db_prefix}topics' + ), + 'check_query' => ' + SELECT + t.id_topic, t.unapproved_posts, COUNT(mu.id_msg) AS my_unapproved_posts + FROM {db_prefix}topics AS t + LEFT JOIN {db_prefix}messages AS mu ON (mu.id_topic = t.id_topic AND mu.approved = 0) + WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH} + GROUP BY t.id_topic, t.unapproved_posts + HAVING unapproved_posts != COUNT(mu.id_msg) + ORDER BY t.id_topic', + 'fix_processing' => create_function('$row', ' + global $smcFunc; + $row[\'my_unapproved_posts\'] = (int) $row[\'my_unapproved_posts\']; + + $smcFunc[\'db_query\'](\'\', " + UPDATE {db_prefix}topics + SET unapproved_posts = $row[my_unapproved_posts] + WHERE id_topic = $row[id_topic]", + array( + ) + ); + '), + 'messages' => array('repair_stats_topics_4', 'id_topic', 'unapproved_posts'), + ), + // Find topics with nonexistent boards. + 'missing_boards' => array( + 'substeps' => array( + 'step_size' => 1000, + 'step_max' => ' + SELECT MAX(id_topic) + FROM {db_prefix}topics' + ), + 'check_query' => ' + SELECT t.id_topic, t.id_board + FROM {db_prefix}topics AS t + LEFT JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + WHERE b.id_board IS NULL + AND t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH} + ORDER BY t.id_board, t.id_topic', + 'fix_query' => ' + SELECT t.id_board, COUNT(*) AS my_num_topics, COUNT(m.id_msg) AS my_num_posts + FROM {db_prefix}topics AS t + LEFT JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + LEFT JOIN {db_prefix}messages AS m ON (m.id_topic = t.id_topic) + WHERE b.id_board IS NULL + AND t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH} + GROUP BY t.id_board', + 'fix_processing' => create_function('$row', ' + global $smcFunc, $salvageCatID; + createSalvageArea(); + + $row[\'my_num_topics\'] = (int) $row[\'my_num_topics\']; + $row[\'my_num_posts\'] = (int) $row[\'my_num_posts\']; + + $smcFunc[\'db_insert\'](\'\', + \'{db_prefix}boards\', + array(\'id_cat\' => \'int\', \'name\' => \'string\', \'description\' => \'string\', \'num_topics\' => \'int\', \'num_posts\' => \'int\', \'member_groups\' => \'string\'), + array($salvageCatID, \'Salvaged board\', \'\', $row[\'my_num_topics\'], $row[\'my_num_posts\'], \'1\'), + array(\'id_board\') + ); + $newBoardID = $smcFunc[\'db_insert_id\'](\'{db_prefix}boards\', \'id_board\'); + + $smcFunc[\'db_query\'](\'\', " + UPDATE {db_prefix}topics + SET id_board = $newBoardID + WHERE id_board = $row[id_board]", + array( + ) + ); + $smcFunc[\'db_query\'](\'\', " + UPDATE {db_prefix}messages + SET id_board = $newBoardID + WHERE id_board = $row[id_board]", + array( + ) + ); + '), + 'messages' => array('repair_missing_boards', 'id_topic', 'id_board'), + ), + // Find boards with nonexistent categories. + 'missing_categories' => array( + 'check_query' => ' + SELECT b.id_board, b.id_cat + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat) + WHERE c.id_cat IS NULL + ORDER BY b.id_cat, b.id_board', + 'fix_collect' => array( + 'index' => 'id_cat', + 'process' => create_function('$cats', ' + global $smcFunc, $salvageCatID; + createSalvageArea(); + $smcFunc[\'db_query\'](\'\', " + UPDATE {db_prefix}boards + SET id_cat = $salvageCatID + WHERE id_cat IN ({array_int:categories})", + array( + \'categories\' => $cats + ) + ); + '), + ), + 'messages' => array('repair_missing_categories', 'id_board', 'id_cat'), + ), + // Find messages with nonexistent members. + 'missing_posters' => array( + 'substeps' => array( + 'step_size' => 2000, + 'step_max' => ' + SELECT MAX(id_msg) + FROM {db_prefix}messages' + ), + 'check_query' => ' + SELECT m.id_msg, m.id_member + FROM {db_prefix}messages AS m + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE mem.id_member IS NULL + AND m.id_member != 0 + AND m.id_msg BETWEEN {STEP_LOW} AND {STEP_HIGH} + ORDER BY m.id_msg', + // Last step-make sure all non-guest posters still exist. + 'fix_collect' => array( + 'index' => 'id_msg', + 'process' => create_function('$msgs', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + UPDATE {db_prefix}messages + SET id_member = 0 + WHERE id_msg IN ({array_int:msgs})", + array( + \'msgs\' => $msgs + ) + ); + '), + ), + 'messages' => array('repair_missing_posters', 'id_msg', 'id_member'), + ), + // Find boards with nonexistent parents. + 'missing_parents' => array( + 'check_query' => ' + SELECT b.id_board, b.id_parent + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}boards AS p ON (p.id_board = b.id_parent) + WHERE b.id_parent != 0 + AND (p.id_board IS NULL OR p.id_board = b.id_board) + ORDER BY b.id_parent, b.id_board', + 'fix_collect' => array( + 'index' => 'id_parent', + 'process' => create_function('$parents', ' + global $smcFunc, $salvageBoardID, $salvageCatID; + createSalvageArea(); + $smcFunc[\'db_query\'](\'\', " + UPDATE {db_prefix}boards + SET id_parent = $salvageBoardID, id_cat = $salvageCatID, child_level = 1 + WHERE id_parent IN ({array_int:parents})", + array( + \'parents\' => $parents + ) + ); + '), + ), + 'messages' => array('repair_missing_parents', 'id_board', 'id_parent'), + ), + 'missing_polls' => array( + 'substeps' => array( + 'step_size' => 500, + 'step_max' => ' + SELECT MAX(id_poll) + FROM {db_prefix}topics' + ), + 'check_query' => ' + SELECT t.id_poll, t.id_topic + FROM {db_prefix}topics AS t + LEFT JOIN {db_prefix}polls AS p ON (p.id_poll = t.id_poll) + WHERE t.id_poll != 0 + AND t.id_poll BETWEEN {STEP_LOW} AND {STEP_HIGH} + AND p.id_poll IS NULL', + 'fix_collect' => array( + 'index' => 'id_poll', + 'process' => create_function('$polls', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + UPDATE {db_prefix}topics + SET id_poll = 0 + WHERE id_poll IN ({array_int:polls})", + array( + \'polls\' => $polls + ) + ); + '), + ), + 'messages' => array('repair_missing_polls', 'id_topic', 'id_poll'), + ), + 'missing_calendar_topics' => array( + 'substeps' => array( + 'step_size' => 1000, + 'step_max' => ' + SELECT MAX(id_topic) + FROM {db_prefix}calendar' + ), + 'check_query' => ' + SELECT cal.id_topic, cal.id_event + FROM {db_prefix}calendar AS cal + LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = cal.id_topic) + WHERE cal.id_topic != 0 + AND cal.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH} + AND t.id_topic IS NULL + ORDER BY cal.id_topic', + 'fix_collect' => array( + 'index' => 'id_topic', + 'process' => create_function('$events', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', \' + UPDATE {db_prefix}calendar + SET id_topic = 0, id_board = 0 + WHERE id_topic IN ({array_int:events})\', + array( + \'events\' => $events + ) + ); + '), + ), + 'messages' => array('repair_missing_calendar_topics', 'id_event', 'id_topic'), + ), + 'missing_log_topics' => array( + 'substeps' => array( + 'step_size' => 150, + 'step_max' => ' + SELECT MAX(id_member) + FROM {db_prefix}log_topics' + ), + 'check_query' => ' + SELECT lt.id_topic + FROM {db_prefix}log_topics AS lt + LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = lt.id_topic) + WHERE t.id_topic IS NULL + AND lt.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}', + 'fix_collect' => array( + 'index' => 'id_topic', + 'process' => create_function('$topics', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}log_topics + WHERE id_topic IN ({array_int:topics})", + array( + \'topics\' => $topics + ) + ); + '), + ), + 'messages' => array('repair_missing_log_topics', 'id_topic'), + ), + 'missing_log_topics_members' => array( + 'substeps' => array( + 'step_size' => 150, + 'step_max' => ' + SELECT MAX(id_member) + FROM {db_prefix}log_topics' + ), + 'check_query' => ' + SELECT lt.id_member + FROM {db_prefix}log_topics AS lt + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lt.id_member) + WHERE mem.id_member IS NULL + AND lt.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH} + GROUP BY lt.id_member', + 'fix_collect' => array( + 'index' => 'id_member', + 'process' => create_function('$members', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}log_topics + WHERE id_member IN ({array_int:members})", + array( + \'members\' => $members + ) + ); + '), + ), + 'messages' => array('repair_missing_log_topics_members', 'id_member'), + ), + 'missing_log_boards' => array( + 'substeps' => array( + 'step_size' => 500, + 'step_max' => ' + SELECT MAX(id_member) + FROM {db_prefix}log_boards' + ), + 'check_query' => ' + SELECT lb.id_board + FROM {db_prefix}log_boards AS lb + LEFT JOIN {db_prefix}boards AS b ON (b.id_board = lb.id_board) + WHERE b.id_board IS NULL + AND lb.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH} + GROUP BY lb.id_board', + 'fix_collect' => array( + 'index' => 'id_board', + 'process' => create_function('$boards', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}log_boards + WHERE id_board IN ({array_int:boards})", + array( + \'boards\' => $boards + ) + ); + '), + ), + 'messages' => array('repair_missing_log_boards', 'id_board'), + ), + 'missing_log_boards_members' => array( + 'substeps' => array( + 'step_size' => 500, + 'step_max' => ' + SELECT MAX(id_member) + FROM {db_prefix}log_boards' + ), + 'check_query' => ' + SELECT lb.id_member + FROM {db_prefix}log_boards AS lb + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lb.id_member) + WHERE mem.id_member IS NULL + AND lb.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH} + GROUP BY lb.id_member', + 'fix_collect' => array( + 'index' => 'id_member', + 'process' => create_function('$members', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}log_boards + WHERE id_member IN ({array_int:members})", + array( + \'members\' => $members + ) + ); + '), + ), + 'messages' => array('repair_missing_log_boards_members', 'id_member'), + ), + 'missing_log_mark_read' => array( + 'substeps' => array( + 'step_size' => 500, + 'step_max' => ' + SELECT MAX(id_member) + FROM {db_prefix}log_mark_read' + ), + 'check_query' => ' + SELECT lmr.id_board + FROM {db_prefix}log_mark_read AS lmr + LEFT JOIN {db_prefix}boards AS b ON (b.id_board = lmr.id_board) + WHERE b.id_board IS NULL + AND lmr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH} + GROUP BY lmr.id_board', + 'fix_collect' => array( + 'index' => 'id_board', + 'process' => create_function('$boards', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}log_mark_read + WHERE id_board IN ({array_int:boards})", + array( + \'boards\' => $boards + ) + ); + '), + ), + 'messages' => array('repair_missing_log_mark_read', 'id_board'), + ), + 'missing_log_mark_read_members' => array( + 'substeps' => array( + 'step_size' => 500, + 'step_max' => ' + SELECT MAX(id_member) + FROM {db_prefix}log_mark_read' + ), + 'check_query' => ' + SELECT lmr.id_member + FROM {db_prefix}log_mark_read AS lmr + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lmr.id_member) + WHERE mem.id_member IS NULL + AND lmr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH} + GROUP BY lmr.id_member', + 'fix_collect' => array( + 'index' => 'id_member', + 'process' => create_function('$members', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}log_mark_read + WHERE id_member IN ({array_int:members})", + array( + \'members\' => $members + ) + ); + '), + ), + 'messages' => array('repair_missing_log_mark_read_members', 'id_member'), + ), + 'missing_pms' => array( + 'substeps' => array( + 'step_size' => 500, + 'step_max' => ' + SELECT MAX(id_pm) + FROM {db_prefix}pm_recipients' + ), + 'check_query' => ' + SELECT pmr.id_pm + FROM {db_prefix}pm_recipients AS pmr + LEFT JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm) + WHERE pm.id_pm IS NULL + AND pmr.id_pm BETWEEN {STEP_LOW} AND {STEP_HIGH} + GROUP BY pmr.id_pm', + 'fix_collect' => array( + 'index' => 'id_pm', + 'process' => create_function('$pms', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}pm_recipients + WHERE id_pm IN ({array_int:pms})", + array( + \'pms\' => $pms + ) + ); + '), + ), + 'messages' => array('repair_missing_pms', 'id_pm'), + ), + 'missing_recipients' => array( + 'substeps' => array( + 'step_size' => 500, + 'step_max' => ' + SELECT MAX(id_member) + FROM {db_prefix}pm_recipients' + ), + 'check_query' => ' + SELECT pmr.id_member + FROM {db_prefix}pm_recipients AS pmr + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member) + WHERE pmr.id_member != 0 + AND pmr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH} + AND mem.id_member IS NULL + GROUP BY pmr.id_member', + 'fix_collect' => array( + 'index' => 'id_member', + 'process' => create_function('$members', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}pm_recipients + WHERE id_member IN ({array_int:members})", + array( + \'members\' => $members + ) + ); + '), + ), + 'messages' => array('repair_missing_recipients', 'id_member'), + ), + 'missing_senders' => array( + 'substeps' => array( + 'step_size' => 500, + 'step_max' => ' + SELECT MAX(id_pm) + FROM {db_prefix}personal_messages' + ), + 'check_query' => ' + SELECT pm.id_pm, pm.id_member_from + FROM {db_prefix}personal_messages AS pm + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from) + WHERE pm.id_member_from != 0 + AND pm.id_pm BETWEEN {STEP_LOW} AND {STEP_HIGH} + AND mem.id_member IS NULL', + 'fix_collect' => array( + 'index' => 'id_pm', + 'process' => create_function('$guestMessages', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + UPDATE {db_prefix}personal_messages + SET id_member_from = 0 + WHERE id_pm IN ({array_int:guestMessages})", + array( + \'guestMessages\' => $guestMessages + )); + '), + ), + 'messages' => array('repair_missing_senders', 'id_pm', 'id_member_from'), + ), + 'missing_notify_members' => array( + 'substeps' => array( + 'step_size' => 500, + 'step_max' => ' + SELECT MAX(id_member) + FROM {db_prefix}log_notify' + ), + 'check_query' => ' + SELECT ln.id_member + FROM {db_prefix}log_notify AS ln + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member) + WHERE ln.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH} + AND mem.id_member IS NULL + GROUP BY ln.id_member', + 'fix_collect' => array( + 'index' => 'id_member', + 'process' => create_function('$members', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}log_notify + WHERE id_member IN ({array_int:members})", + array( + \'members\' => $members + ) + ); + '), + ), + 'messages' => array('repair_missing_notify_members', 'id_member'), + ), + 'missing_cached_subject' => array( + 'substeps' => array( + 'step_size' => 100, + 'step_max' => ' + SELECT MAX(id_topic) + FROM {db_prefix}topics' + ), + 'check_query' => ' + SELECT t.id_topic, fm.subject + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS fm ON (fm.id_msg = t.id_first_msg) + LEFT JOIN {db_prefix}log_search_subjects AS lss ON (lss.id_topic = t.id_topic) + WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH} + AND lss.id_topic IS NULL', + 'fix_full_processing' => create_function('$result', ' + global $smcFunc; + + $inserts = array(); + while ($row = $smcFunc[\'db_fetch_assoc\']($result)) + { + foreach (text2words($row[\'subject\']) as $word) + $inserts[] = array($word, $row[\'id_topic\']); + if (count($inserts) > 500) + { + $smcFunc[\'db_insert\'](\'ignore\', + "{db_prefix}log_search_subjects", + array(\'word\' => \'string\', \'id_topic\' => \'int\'), + $inserts, + array(\'word\', \'id_topic\') + ); + $inserts = array(); + } + + } + + if (!empty($inserts)) + $smcFunc[\'db_insert\'](\'ignore\', + "{db_prefix}log_search_subjects", + array(\'word\' => \'string\', \'id_topic\' => \'int\'), + $inserts, + array(\'word\', \'id_topic\') + ); + '), + 'message_function' => create_function('$row', ' + global $txt, $context; + + if (count(text2words($row[\'subject\'])) != 0) + { + $context[\'repair_errors\'][] = sprintf($txt[\'repair_missing_cached_subject\'], $row[\'id_topic\']); + return true; + } + + return false; + '), + ), + 'missing_topic_for_cache' => array( + 'substeps' => array( + 'step_size' => 50, + 'step_max' => ' + SELECT MAX(id_topic) + FROM {db_prefix}log_search_subjects' + ), + 'check_query' => ' + SELECT lss.id_topic, lss.word + FROM {db_prefix}log_search_subjects AS lss + LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = lss.id_topic) + WHERE lss.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH} + AND t.id_topic IS NULL', + 'fix_collect' => array( + 'index' => 'id_topic', + 'process' => create_function('$deleteTopics', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}log_search_subjects + WHERE id_topic IN ({array_int:deleteTopics})", + array( + \'deleteTopics\' => $deleteTopics + ) + ); + '), + ), + 'messages' => array('repair_missing_topic_for_cache', 'word'), + ), + 'missing_member_vote' => array( + 'substeps' => array( + 'step_size' => 500, + 'step_max' => ' + SELECT MAX(id_member) + FROM {db_prefix}log_polls' + ), + 'check_query' => ' + SELECT lp.id_poll, lp.id_member + FROM {db_prefix}log_polls AS lp + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lp.id_member) + WHERE lp.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH} + AND lp.id_member > 0 + AND mem.id_member IS NULL', + 'fix_collect' => array( + 'index' => 'id_member', + 'process' => create_function('$members', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}log_polls + WHERE id_member IN ({array_int:members})", + array( + \'members\' => $members + ) + ); + '), + ), + 'messages' => array('repair_missing_log_poll_member', 'id_poll', 'id_member'), + ), + 'missing_log_poll_vote' => array( + 'substeps' => array( + 'step_size' => 500, + 'step_max' => ' + SELECT MAX(id_poll) + FROM {db_prefix}log_polls' + ), + 'check_query' => ' + SELECT lp.id_poll, lp.id_member + FROM {db_prefix}log_polls AS lp + LEFT JOIN {db_prefix}polls AS p ON (p.id_poll = lp.id_poll) + WHERE lp.id_poll BETWEEN {STEP_LOW} AND {STEP_HIGH} + AND p.id_poll IS NULL', + 'fix_collect' => array( + 'index' => 'id_poll', + 'process' => create_function('$polls', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}log_polls + WHERE id_poll IN ({array_int:polls})", + array( + \'polls\' => $polls + ) + ); + '), + ), + 'messages' => array('repair_missing_log_poll_vote', 'id_member', 'id_poll'), + ), + 'report_missing_comments' => array( + 'substeps' => array( + 'step_size' => 500, + 'step_max' => ' + SELECT MAX(id_report) + FROM {db_prefix}log_reported' + ), + 'check_query' => ' + SELECT lr.id_report, lr.subject + FROM {db_prefix}log_reported AS lr + LEFT JOIN {db_prefix}log_reported_comments AS lrc ON (lrc.id_report = lr.id_report) + WHERE lr.id_report BETWEEN {STEP_LOW} AND {STEP_HIGH} + AND lrc.id_report IS NULL', + 'fix_collect' => array( + 'index' => 'id_report', + 'process' => create_function('$reports', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}log_reported + WHERE id_report IN ({array_int:reports})", + array( + \'reports\' => $reports + ) + ); + '), + ), + 'messages' => array('repair_report_missing_comments', 'id_report', 'subject'), + ), + 'comments_missing_report' => array( + 'substeps' => array( + 'step_size' => 200, + 'step_max' => ' + SELECT MAX(id_report) + FROM {db_prefix}log_reported_comments' + ), + 'check_query' => ' + SELECT lrc.id_report, lrc.membername + FROM {db_prefix}log_reported_comments AS lrc + LEFT JOIN {db_prefix}log_reported AS lr ON (lr.id_report = lrc.id_report) + WHERE lrc.id_report BETWEEN {STEP_LOW} AND {STEP_HIGH} + AND lr.id_report IS NULL', + 'fix_collect' => array( + 'index' => 'id_report', + 'process' => create_function('$reports', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}log_reported_comments + WHERE id_report IN ({array_int:reports})", + array( + \'reports\' => $reports + ) + ); + '), + ), + 'messages' => array('repair_comments_missing_report', 'id_report', 'membername'), + ), + 'group_request_missing_member' => array( + 'substeps' => array( + 'step_size' => 200, + 'step_max' => ' + SELECT MAX(id_member) + FROM {db_prefix}log_group_requests' + ), + 'check_query' => ' + SELECT lgr.id_member + FROM {db_prefix}log_group_requests AS lgr + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lgr.id_member) + WHERE lgr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH} + AND mem.id_member IS NULL + GROUP BY lgr.id_member', + 'fix_collect' => array( + 'index' => 'id_member', + 'process' => create_function('$members', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', " + DELETE FROM {db_prefix}log_group_requests + WHERE id_member IN ({array_int:members})", + array( + \'members\' => $members + ) + ); + '), + ), + 'messages' => array('repair_group_request_missing_member', 'id_member'), + ), + 'group_request_missing_group' => array( + 'substeps' => array( + 'step_size' => 200, + 'step_max' => ' + SELECT MAX(id_group) + FROM {db_prefix}log_group_requests' + ), + 'check_query' => ' + SELECT lgr.id_group + FROM {db_prefix}log_group_requests AS lgr + LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = lgr.id_group) + WHERE lgr.id_group BETWEEN {STEP_LOW} AND {STEP_HIGH} + AND mg.id_group IS NULL + GROUP BY lgr.id_group', + 'fix_collect' => array( + 'index' => 'id_group', + 'process' => create_function('$groups', ' + global $smcFunc; + $smcFunc[\'db_query\'](\'\', \' + DELETE FROM {db_prefix}log_group_requests + WHERE id_group IN ({array_int:groups})\', + array( + \'groups\' => $groups + ) + ); + '), + ), + 'messages' => array('repair_group_request_missing_group', 'id_group'), + ), + ); +} + +function findForumErrors($do_fix = false) +{ + global $context, $txt, $smcFunc, $errorTests, $db_cache, $db_temp_cache; + + // This may take some time... + @set_time_limit(600); + + $to_fix = !empty($_SESSION['repairboards_to_fix']) ? $_SESSION['repairboards_to_fix'] : array(); + $context['repair_errors'] = isset($_SESSION['repairboards_to_fix2']) ? $_SESSION['repairboards_to_fix2'] : array(); + + $_GET['step'] = empty($_GET['step']) ? 0 : (int) $_GET['step']; + $_GET['substep'] = empty($_GET['substep']) ? 0 : (int) $_GET['substep']; + + // Don't allow the cache to get too full. + $db_temp_cache = $db_cache; + $db_cache = ''; + + $context['total_steps'] = count($errorTests); + + // For all the defined error types do the necessary tests. + $current_step = -1; + $total_queries = 0; + foreach ($errorTests as $error_type => $test) + { + $current_step++; + + // Already done this? + if ($_GET['step'] > $current_step) + continue; + + // If we're fixing it but it ain't broke why try? + if ($do_fix && !in_array($error_type, $to_fix)) + { + $_GET['step']++; + continue; + } + + // Has it got substeps? + if (isset($test['substeps'])) + { + $step_size = isset($test['substeps']['step_size']) ? $test['substeps']['step_size'] : 100; + $request = $smcFunc['db_query']('', + $test['substeps']['step_max'], + array( + ) + ); + list ($step_max) = $smcFunc['db_fetch_row']($request); + + $total_queries++; + $smcFunc['db_free_result']($request); + } + + // We in theory keep doing this... the substeps. + $done = false; + while (!$done) + { + // Make sure there's at least one ID to test. + if (isset($test['substeps']) && empty($step_max)) + break; + + // What is the testing query (Changes if we are testing or fixing) + if (!$do_fix) + $test_query = 'check_query'; + else + $test_query = isset($test['fix_query']) ? 'fix_query' : 'check_query'; + + // Do the test... + $request = $smcFunc['db_query']('', + isset($test['substeps']) ? strtr($test[$test_query], array('{STEP_LOW}' => $_GET['substep'], '{STEP_HIGH}' => $_GET['substep'] + $step_size - 1)) : $test[$test_query], + array( + ) + ); + $needs_fix = false; + + // Does it need a fix? + if (!empty($test['check_type']) && $test['check_type'] == 'count') + list ($needs_fix) = $smcFunc['db_fetch_row']($request); + else + $needs_fix = $smcFunc['db_num_rows']($request); + + $total_queries++; + + if ($needs_fix) + { + // What about a message to the user? + if (!$do_fix) + { + // Assume need to fix. + $found_errors = true; + + if (isset($test['message'])) + $context['repair_errors'][] = $txt[$test['message']]; + + // One per row! + elseif (isset($test['messages'])) + { + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $variables = $test['messages']; + foreach ($variables as $k => $v) + { + if ($k == 0 && isset($txt[$v])) + $variables[$k] = $txt[$v]; + elseif ($k > 0 && isset($row[$v])) + $variables[$k] = $row[$v]; + } + $context['repair_errors'][] = call_user_func_array('sprintf', $variables); + } + } + + // A function to process? + elseif (isset($test['message_function'])) + { + // Find out if there are actually errors. + $found_errors = false; + while ($row = $smcFunc['db_fetch_assoc']($request)) + $found_errors |= $test['message_function']($row); + } + + // Actually have something to fix? + if ($found_errors) + $to_fix[] = $error_type; + } + + // We want to fix, we need to fix - so work out what exactly to do! + else + { + // Are we simply getting a collection of ids? + if (isset($test['fix_collect'])) + { + $ids = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $ids[] = $row[$test['fix_collect']['index']]; + if (!empty($ids)) + { + // Fix it! + $test['fix_collect']['process']($ids); + } + } + + // Simply executing a fix it query? + elseif (isset($test['fix_it_query'])) + $smcFunc['db_query']('', + $test['fix_it_query'], + array( + ) + ); + + // Do we have some processing to do? + elseif (isset($test['fix_processing'])) + { + while ($row = $smcFunc['db_fetch_assoc']($request)) + $test['fix_processing']($row); + } + + // What about the full set of processing? + elseif (isset($test['fix_full_processing'])) + $test['fix_full_processing']($request); + + // Do we have other things we need to fix as a result? + if (!empty($test['force_fix'])) + { + foreach ($test['force_fix'] as $item) + if (!in_array($item, $to_fix)) + $to_fix[] = $item; + } + } + } + + // Free the result. + $smcFunc['db_free_result']($request); + // Keep memory down. + $db_cache = ''; + + // Are we done yet? + if (isset($test['substeps'])) + { + $_GET['substep'] += $step_size; + // Not done? + if ($_GET['substep'] <= $step_max) + { + pauseRepairProcess($to_fix, $error_type, $step_max); + } + else + $done = true; + } + else + $done = true; + + // Don't allow more than 1000 queries at a time. + if ($total_queries >= 1000) + pauseRepairProcess($to_fix, $error_type, $step_max, true); + } + + // Keep going. + $_GET['step']++; + $_GET['substep'] = 0; + + $to_fix = array_unique($to_fix); + + // If we're doing fixes and this needed a fix and we're all done then don't do it again. + if ($do_fix) + { + $key = array_search($error_type, $to_fix); + if ($key !== false && isset($to_fix[$key])) + unset($to_fix[$key]); + } + + // Are we done? + pauseRepairProcess($to_fix, $error_type); + } + + // Restore the cache. + $db_cache = $db_temp_cache; + + return $to_fix; +} + +// Create a salvage area for repair purposes. +function createSalvageArea() +{ + global $txt, $language, $salvageBoardID, $salvageCatID, $smcFunc; + static $createOnce = false; + + // Have we already created it? + if ($createOnce) + return; + else + $createOnce = true; + + // Back to the forum's default language. + loadLanguage('Admin', $language); + + // Check to see if a 'Salvage Category' exists, if not => insert one. + $result = $smcFunc['db_query']('', ' + SELECT id_cat + FROM {db_prefix}categories + WHERE name = {string:cat_name} + LIMIT 1', + array( + 'cat_name' => $txt['salvaged_category_name'], + ) + ); + if ($smcFunc['db_num_rows']($result) != 0) + list ($salvageCatID) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + if (empty($salveageCatID)) + { + $smcFunc['db_insert']('', + '{db_prefix}categories', + array('name' => 'string-255', 'cat_order' => 'int'), + array($txt['salvaged_category_name'], -1), + array('id_cat') + ); + + if ($smcFunc['db_affected_rows']() <= 0) + { + loadLanguage('Admin'); + fatal_lang_error('salvaged_category_error', false); + } + + $salvageCatID = $smcFunc['db_insert_id']('{db_prefix}categories', 'id_cat'); + } + + // Check to see if a 'Salvage Board' exists, if not => insert one. + $result = $smcFunc['db_query']('', ' + SELECT id_board + FROM {db_prefix}boards + WHERE id_cat = {int:id_cat} + AND name = {string:board_name} + LIMIT 1', + array( + 'id_cat' => $salvageCatID, + 'board_name' => $txt['salvaged_board_name'], + ) + ); + if ($smcFunc['db_num_rows']($result) != 0) + list ($salvageBoardID) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + if (empty($salvageBoardID)) + { + $smcFunc['db_insert']('', + '{db_prefix}boards', + array('name' => 'string-255', 'description' => 'string-255', 'id_cat' => 'int', 'member_groups' => 'string', 'board_order' => 'int', 'redirect' => 'string'), + array($txt['salvaged_board_name'], $txt['salvaged_board_description'], $salvageCatID, '1', -1, ''), + array('id_board') + ); + + if ($smcFunc['db_affected_rows']() <= 0) + { + loadLanguage('Admin'); + fatal_lang_error('salvaged_board_error', false); + } + + $salvageBoardID = $smcFunc['db_insert_id']('{db_prefix}boards', 'id_board'); + } + + $smcFunc['db_query']('alter_table_boards', ' + ALTER TABLE {db_prefix}boards + ORDER BY board_order', + array( + ) + ); + + // Restore the user's language. + loadLanguage('Admin'); +} + +?> \ No newline at end of file diff --git a/Sources/Reports.php b/Sources/Reports.php new file mode 100644 index 0000000..8ebf04c --- /dev/null +++ b/Sources/Reports.php @@ -0,0 +1,940 @@ + 'BoardReport', + 'board_perms' => 'BoardPermissionsReport', + 'member_groups' => 'MemberGroupsReport', + 'group_perms' => 'GroupPermissionsReport', + 'staff' => 'StaffReport', + ); + + $is_first = 0; + foreach ($context['report_types'] as $k => $temp) + $context['report_types'][$k] = array( + 'id' => $k, + 'title' => isset($txt['gr_type_' . $k]) ? $txt['gr_type_' . $k] : $type['id'], + 'description' => isset($txt['gr_type_desc_' . $k]) ? $txt['gr_type_desc_' . $k] : null, + 'function' => $temp, + 'is_first' => $is_first++ == 0, + ); + + // If they haven't choosen a report type which is valid, send them off to the report type chooser! + if (empty($_REQUEST['rt']) || !isset($context['report_types'][$_REQUEST['rt']])) + { + $context['sub_template'] = 'report_type'; + return; + } + $context['report_type'] = $_REQUEST['rt']; + + // What are valid templates for showing reports? + $reportTemplates = array( + 'main' => array( + 'layers' => null, + ), + 'print' => array( + 'layers' => array('print'), + ), + ); + + // Specific template? Use that instead of main! + if (isset($_REQUEST['st']) && isset($reportTemplates[$_REQUEST['st']])) + { + $context['sub_template'] = $_REQUEST['st']; + + // Are we disabling the other layers - print friendly for example? + if ($reportTemplates[$_REQUEST['st']]['layers'] !== null) + $context['template_layers'] = $reportTemplates[$_REQUEST['st']]['layers']; + } + + // Make the page title more descriptive. + $context['page_title'] .= ' - ' . (isset($txt['gr_type_' . $context['report_type']]) ? $txt['gr_type_' . $context['report_type']] : $context['report_type']); + // Now generate the data. + $context['report_types'][$context['report_type']]['function'](); + + // Finish the tables before exiting - this is to help the templates a little more. + finishTables(); +} + +// Standard report about what settings the boards have. +function BoardReport() +{ + global $context, $txt, $sourcedir, $smcFunc; + + // Load the permission profiles. + require_once($sourcedir . '/ManagePermissions.php'); + loadLanguage('ManagePermissions'); + loadPermissionProfiles(); + + // Get every moderator. + $request = $smcFunc['db_query']('', ' + SELECT mods.id_board, mods.id_member, mem.real_name + FROM {db_prefix}moderators AS mods + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)', + array( + ) + ); + $moderators = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $moderators[$row['id_board']][] = $row['real_name']; + $smcFunc['db_free_result']($request); + + // Get all the possible membergroups! + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_name, online_color + FROM {db_prefix}membergroups', + array( + ) + ); + $groups = array(-1 => $txt['guest_title'], 0 => $txt['full_member']); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $groups[$row['id_group']] = empty($row['online_color']) ? $row['group_name'] : '' . $row['group_name'] . ''; + $smcFunc['db_free_result']($request); + + // All the fields we'll show. + $boardSettings = array( + 'category' => $txt['board_category'], + 'parent' => $txt['board_parent'], + 'num_topics' => $txt['board_num_topics'], + 'num_posts' => $txt['board_num_posts'], + 'count_posts' => $txt['board_count_posts'], + 'theme' => $txt['board_theme'], + 'override_theme' => $txt['board_override_theme'], + 'profile' => $txt['board_profile'], + 'moderators' => $txt['board_moderators'], + 'groups' => $txt['board_groups'], + ); + + // Do it in columns, it's just easier. + setKeys('cols'); + + // Go through each board! + $request = $smcFunc['db_query']('order_by_board_order', ' + SELECT b.id_board, b.name, b.num_posts, b.num_topics, b.count_posts, b.member_groups, b.override_theme, b.id_profile, + c.name AS cat_name, IFNULL(par.name, {string:text_none}) AS parent_name, IFNULL(th.value, {string:text_none}) AS theme_name + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat) + LEFT JOIN {db_prefix}boards AS par ON (par.id_board = b.id_parent) + LEFT JOIN {db_prefix}themes AS th ON (th.id_theme = b.id_theme AND th.variable = {string:name})', + array( + 'name' => 'name', + 'text_none' => $txt['none'], + ) + ); + $boards = array(0 => array('name' => $txt['global_boards'])); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Each board has it's own table. + newTable($row['name'], '', 'left', 'auto', 'left', 200, 'left'); + + // First off, add in the side key. + addData($boardSettings); + + // Format the profile name. + $profile_name = $context['profiles'][$row['id_profile']]['name']; + + // Create the main data array. + $boardData = array( + 'category' => $row['cat_name'], + 'parent' => $row['parent_name'], + 'num_posts' => $row['num_posts'], + 'num_topics' => $row['num_topics'], + 'count_posts' => empty($row['count_posts']) ? $txt['yes'] : $txt['no'], + 'theme' => $row['theme_name'], + 'profile' => $profile_name, + 'override_theme' => $row['override_theme'] ? $txt['yes'] : $txt['no'], + 'moderators' => empty($moderators[$row['id_board']]) ? $txt['none'] : implode(', ', $moderators[$row['id_board']]), + ); + + // Work out the membergroups who can access it. + $allowedGroups = explode(',', $row['member_groups']); + foreach ($allowedGroups as $key => $group) + { + if (isset($groups[$group])) + $allowedGroups[$key] = $groups[$group]; + else + unset($allowedGroups[$key]); + } + $boardData['groups'] = implode(', ', $allowedGroups); + + // Next add the main data. + addData($boardData); + } + $smcFunc['db_free_result']($request); +} + +// Generate a report on the current permissions by board and membergroup. +function BoardPermissionsReport() +{ + global $context, $txt, $modSettings, $smcFunc; + + // Get as much memory as possible as this can be big. + @ini_set('memory_limit', '256M'); + + if (isset($_REQUEST['boards'])) + { + if (!is_array($_REQUEST['boards'])) + $_REQUEST['boards'] = explode(',', $_REQUEST['boards']); + foreach ($_REQUEST['boards'] as $k => $dummy) + $_REQUEST['boards'][$k] = (int) $dummy; + + $board_clause = 'id_board IN ({array_int:boards})'; + } + else + $board_clause = '1=1'; + + if (isset($_REQUEST['groups'])) + { + if (!is_array($_REQUEST['groups'])) + $_REQUEST['groups'] = explode(',', $_REQUEST['groups']); + foreach ($_REQUEST['groups'] as $k => $dummy) + $_REQUEST['groups'][$k] = (int) $dummy; + + $group_clause = 'id_group IN ({array_int:groups})'; + } + else + $group_clause = '1=1'; + + // Fetch all the board names. + $request = $smcFunc['db_query']('', ' + SELECT id_board, name, id_profile + FROM {db_prefix}boards + WHERE ' . $board_clause . ' + ORDER BY id_board', + array( + 'boards' => isset($_REQUEST['boards']) ? $_REQUEST['boards'] : array(), + ) + ); + $profiles = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $boards[$row['id_board']] = array( + 'name' => $row['name'], + 'profile' => $row['id_profile'], + ); + $profiles[] = $row['id_profile']; + } + $smcFunc['db_free_result']($request); + + // Get all the possible membergroups, except admin! + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_name + FROM {db_prefix}membergroups + WHERE ' . $group_clause . ' + AND id_group != {int:admin_group}' . (empty($modSettings['permission_enable_postgroups']) ? ' + AND min_posts = {int:min_posts}' : '') . ' + ORDER BY min_posts, CASE WHEN id_group < {int:newbie_group} THEN id_group ELSE 4 END, group_name', + array( + 'admin_group' => 1, + 'min_posts' => -1, + 'newbie_group' => 4, + 'groups' => isset($_REQUEST['groups']) ? $_REQUEST['groups'] : array(), + ) + ); + if (!isset($_REQUEST['groups']) || in_array(-1, $_REQUEST['groups']) || in_array(0, $_REQUEST['groups'])) + $member_groups = array('col' => '', -1 => $txt['membergroups_guests'], 0 => $txt['membergroups_members']); + else + $member_groups = array('col' => ''); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $member_groups[$row['id_group']] = $row['group_name']; + $smcFunc['db_free_result']($request); + + // Make sure that every group is represented - plus in rows! + setKeys('rows', $member_groups); + + // Cache every permission setting, to make sure we don't miss any allows. + $permissions = array(); + $board_permissions = array(); + $request = $smcFunc['db_query']('', ' + SELECT id_profile, id_group, add_deny, permission + FROM {db_prefix}board_permissions + WHERE id_profile IN ({array_int:profile_list}) + AND ' . $group_clause . (empty($modSettings['permission_enable_deny']) ? ' + AND add_deny = {int:not_deny}' : '') . ' + ORDER BY id_profile, permission', + array( + 'profile_list' => $profiles, + 'not_deny' => 1, + 'groups' => isset($_REQUEST['groups']) ? $_REQUEST['groups'] : array(), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + foreach ($boards as $id => $board) + if ($board['profile'] == $row['id_profile']) + $board_permissions[$id][$row['id_group']][$row['permission']] = $row['add_deny']; + + // Make sure we get every permission. + if (!isset($permissions[$row['permission']])) + { + // This will be reused on other boards. + $permissions[$row['permission']] = array( + 'title' => isset($txt['board_perms_name_' . $row['permission']]) ? $txt['board_perms_name_' . $row['permission']] : $row['permission'], + ); + } + } + $smcFunc['db_free_result']($request); + + // Now cycle through the board permissions array... lots to do ;) + foreach ($board_permissions as $board => $groups) + { + // Create the table for this board first. + newTable($boards[$board]['name'], 'x', 'all', 100, 'center', 200, 'left'); + + // Add the header row - shows all the membergroups. + addData($member_groups); + + // Add the separator. + addSeparator($txt['board_perms_permission']); + + // Here cycle through all the detected permissions. + foreach ($permissions as $ID_PERM => $perm_info) + { + // Is this identical to the global? + $identicalGlobal = $board == 0 ? false : true; + + // Default data for this row. + $curData = array('col' => $perm_info['title']); + + // Now cycle each membergroup in this set of permissions. + foreach ($member_groups as $id_group => $name) + { + // Don't overwrite the key column! + if ($id_group === 'col') + continue; + + $group_permissions = isset($groups[$id_group]) ? $groups[$id_group] : array(); + + // Do we have any data for this group? + if (isset($group_permissions[$ID_PERM])) + { + // Set the data for this group to be the local permission. + $curData[$id_group] = $group_permissions[$ID_PERM]; + } + // Otherwise means it's set to disallow.. + else + { + $curData[$id_group] = 'x'; + } + + // Now actually make the data for the group look right. + if (empty($curData[$id_group])) + $curData[$id_group] = '' . $txt['board_perms_deny'] . ''; + elseif ($curData[$id_group] == 1) + $curData[$id_group] = '' . $txt['board_perms_allow'] . ''; + else + $curData[$id_group] = 'x'; + + // Embolden those permissions different from global (makes it a lot easier!) + if (@$board_permissions[0][$id_group][$ID_PERM] != @$group_permissions[$ID_PERM]) + $curData[$id_group] = '' . $curData[$id_group] . ''; + } + + // Now add the data for this permission. + addData($curData); + } + } +} + +// Show what the membergroups are made of. +function MemberGroupsReport() +{ + global $context, $txt, $settings, $modSettings, $smcFunc; + + // Fetch all the board names. + $request = $smcFunc['db_query']('', ' + SELECT id_board, name, member_groups, id_profile + FROM {db_prefix}boards', + array( + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (trim($row['member_groups']) == '') + $groups = array(1); + else + $groups = array_merge(array(1), explode(',', $row['member_groups'])); + + $boards[$row['id_board']] = array( + 'id' => $row['id_board'], + 'name' => $row['name'], + 'profile' => $row['id_profile'], + 'groups' => $groups, + ); + } + $smcFunc['db_free_result']($request); + + // Standard settings. + $mgSettings = array( + 'name' => '', + '#sep#1' => $txt['member_group_settings'], + 'color' => $txt['member_group_color'], + 'min_posts' => $txt['member_group_min_posts'], + 'max_messages' => $txt['member_group_max_messages'], + 'stars' => $txt['member_group_stars'], + '#sep#2' => $txt['member_group_access'], + ); + + // Add on the boards! + foreach ($boards as $board) + $mgSettings['board_' . $board['id']] = $board['name']; + + // Add all the membergroup settings, plus we'll be adding in columns! + setKeys('cols', $mgSettings); + + // Only one table this time! + newTable($txt['gr_type_member_groups'], '-', 'all', 100, 'center', 200, 'left'); + + // Get the shaded column in. + addData($mgSettings); + + // Now start cycling the membergroups! + $request = $smcFunc['db_query']('', ' + SELECT mg.id_group, mg.group_name, mg.online_color, mg.min_posts, mg.max_messages, mg.stars, + CASE WHEN bp.permission IS NOT NULL OR mg.id_group = {int:admin_group} THEN 1 ELSE 0 END AS can_moderate + FROM {db_prefix}membergroups AS mg + LEFT JOIN {db_prefix}board_permissions AS bp ON (bp.id_group = mg.id_group AND bp.id_profile = {int:default_profile} AND bp.permission = {string:moderate_board}) + ORDER BY mg.min_posts, CASE WHEN mg.id_group < {int:newbie_group} THEN mg.id_group ELSE 4 END, mg.group_name', + array( + 'admin_group' => 1, + 'default_profile' => 1, + 'newbie_group' => 4, + 'moderate_board' => 'moderate_board', + ) + ); + + // Cache them so we get regular members too. + $rows = array( + array( + 'id_group' => -1, + 'group_name' => $txt['membergroups_guests'], + 'online_color' => '', + 'min_posts' => -1, + 'max_messages' => null, + 'stars' => '' + ), + array( + 'id_group' => 0, + 'group_name' => $txt['membergroups_members'], + 'online_color' => '', + 'min_posts' => -1, + 'max_messages' => null, + 'stars' => '' + ), + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $rows[] = $row; + $smcFunc['db_free_result']($request); + + foreach ($rows as $row) + { + $row['stars'] = explode('#', $row['stars']); + + $group = array( + 'name' => $row['group_name'], + 'color' => empty($row['online_color']) ? '-' : '' . $row['online_color'] . '', + 'min_posts' => $row['min_posts'] == -1 ? 'N/A' : $row['min_posts'], + 'max_messages' => $row['max_messages'], + 'stars' => !empty($row['stars'][0]) && !empty($row['stars'][1]) ? str_repeat('*', $row['stars'][0]) : '', + ); + + // Board permissions. + foreach ($boards as $board) + $group['board_' . $board['id']] = in_array($row['id_group'], $board['groups']) ? '' . $txt['board_perms_allow'] . '' : 'x'; + + addData($group); + } +} + +// Show the large variety of group permissions assigned to each membergroup. +function GroupPermissionsReport() +{ + global $context, $txt, $modSettings, $smcFunc; + + if (isset($_REQUEST['groups'])) + { + if (!is_array($_REQUEST['groups'])) + $_REQUEST['groups'] = explode(',', $_REQUEST['groups']); + foreach ($_REQUEST['groups'] as $k => $dummy) + $_REQUEST['groups'][$k] = (int) $dummy; + $_REQUEST['groups'] = array_diff($_REQUEST['groups'], array(3)); + + $clause = 'id_group IN ({array_int:groups})'; + } + else + $clause = 'id_group != {int:moderator_group}'; + + // Get all the possible membergroups, except admin! + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_name + FROM {db_prefix}membergroups + WHERE ' . $clause . ' + AND id_group != {int:admin_group}' . (empty($modSettings['permission_enable_postgroups']) ? ' + AND min_posts = {int:min_posts}' : '') . ' + ORDER BY min_posts, CASE WHEN id_group < {int:newbie_group} THEN id_group ELSE 4 END, group_name', + array( + 'admin_group' => 1, + 'min_posts' => -1, + 'newbie_group' => 4, + 'moderator_group' => 3, + 'groups' => isset($_REQUEST['groups']) ? $_REQUEST['groups'] : array(), + ) + ); + if (!isset($_REQUEST['groups']) || in_array(-1, $_REQUEST['groups']) || in_array(0, $_REQUEST['groups'])) + $groups = array('col' => '', -1 => $txt['membergroups_guests'], 0 => $txt['membergroups_members']); + else + $groups = array('col' => ''); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $groups[$row['id_group']] = $row['group_name']; + $smcFunc['db_free_result']($request); + + // Make sure that every group is represented! + setKeys('rows', $groups); + + // Create the table first. + newTable($txt['gr_type_group_perms'], '-', 'all', 100, 'center', 200, 'left'); + + // Show all the groups + addData($groups); + + // Add a separator + addSeparator($txt['board_perms_permission']); + + // Now the big permission fetch! + $request = $smcFunc['db_query']('', ' + SELECT id_group, add_deny, permission + FROM {db_prefix}permissions + WHERE ' . $clause . (empty($modSettings['permission_enable_deny']) ? ' + AND add_deny = {int:not_denied}' : '') . ' + ORDER BY permission', + array( + 'not_denied' => 1, + 'moderator_group' => 3, + 'groups' => isset($_REQUEST['groups']) ? $_REQUEST['groups'] : array(), + ) + ); + $lastPermission = null; + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // If this is a new permission flush the last row. + if ($row['permission'] != $lastPermission) + { + // Send the data! + if ($lastPermission !== null) + addData($curData); + + // Add the permission name in the left column. + $curData = array('col' => isset($txt['group_perms_name_' . $row['permission']]) ? $txt['group_perms_name_' . $row['permission']] : $row['permission']); + + $lastPermission = $row['permission']; + } + + // Good stuff - add the permission to the list! + if ($row['add_deny']) + $curData[$row['id_group']] = '' . $txt['board_perms_allow'] . ''; + else + $curData[$row['id_group']] = '' . $txt['board_perms_deny'] . ''; + } + $smcFunc['db_free_result']($request); + + // Flush the last data! + addData($curData); +} + +// Report for showing all the forum staff members - quite a feat! +function StaffReport() +{ + global $sourcedir, $context, $txt, $smcFunc; + + require_once($sourcedir . '/Subs-Members.php'); + + // Fetch all the board names. + $request = $smcFunc['db_query']('', ' + SELECT id_board, name + FROM {db_prefix}boards', + array( + ) + ); + $boards = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $boards[$row['id_board']] = $row['name']; + $smcFunc['db_free_result']($request); + + // Get every moderator. + $request = $smcFunc['db_query']('', ' + SELECT mods.id_board, mods.id_member + FROM {db_prefix}moderators AS mods', + array( + ) + ); + $moderators = array(); + $local_mods = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $moderators[$row['id_member']][] = $row['id_board']; + $local_mods[$row['id_member']] = $row['id_member']; + } + $smcFunc['db_free_result']($request); + + // Get a list of global moderators (i.e. members with moderation powers). + $global_mods = array_intersect(membersAllowedTo('moderate_board', 0), membersAllowedTo('approve_posts', 0), membersAllowedTo('remove_any', 0), membersAllowedTo('modify_any', 0)); + + // How about anyone else who is special? + $allStaff = array_merge(membersAllowedTo('admin_forum'), membersAllowedTo('manage_membergroups'), membersAllowedTo('manage_permissions'), $local_mods, $global_mods); + + // Make sure everyone is there once - no admin less important than any other! + $allStaff = array_unique($allStaff); + + // This is a bit of a cop out - but we're protecting their forum, really! + if (count($allStaff) > 300) + fatal_lang_error('report_error_too_many_staff'); + + // Get all the possible membergroups! + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_name, online_color + FROM {db_prefix}membergroups', + array( + ) + ); + $groups = array(0 => $txt['full_member']); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $groups[$row['id_group']] = empty($row['online_color']) ? $row['group_name'] : '' . $row['group_name'] . ''; + $smcFunc['db_free_result']($request); + + // All the fields we'll show. + $staffSettings = array( + 'position' => $txt['report_staff_position'], + 'moderates' => $txt['report_staff_moderates'], + 'posts' => $txt['report_staff_posts'], + 'last_login' => $txt['report_staff_last_login'], + ); + + // Do it in columns, it's just easier. + setKeys('cols'); + + // Get each member! + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name, id_group, posts, last_login + FROM {db_prefix}members + WHERE id_member IN ({array_int:staff_list}) + ORDER BY real_name', + array( + 'staff_list' => $allStaff, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Each member gets their own table!. + newTable($row['real_name'], '', 'left', 'auto', 'left', 200, 'center'); + + // First off, add in the side key. + addData($staffSettings); + + // Create the main data array. + $staffData = array( + 'position' => isset($groups[$row['id_group']]) ? $groups[$row['id_group']] : $groups[0], + 'posts' => $row['posts'], + 'last_login' => timeformat($row['last_login']), + 'moderates' => array(), + ); + + // What do they moderate? + if (in_array($row['id_member'], $global_mods)) + $staffData['moderates'] = '' . $txt['report_staff_all_boards'] . ''; + elseif (isset($moderators[$row['id_member']])) + { + // Get the names + foreach ($moderators[$row['id_member']] as $board) + if (isset($boards[$board])) + $staffData['moderates'][] = $boards[$board]; + + $staffData['moderates'] = implode(', ', $staffData['moderates']); + } + else + $staffData['moderates'] = '' . $txt['report_staff_no_boards'] . ''; + + // Next add the main data. + addData($staffData); + } + $smcFunc['db_free_result']($request); +} + +// This function creates a new table of data, most functions will only use it once. +function newTable($title = '', $default_value = '', $shading = 'all', $width_normal = 'auto', $align_normal = 'center', $width_shaded = 'auto', $align_shaded = 'auto') +{ + global $context; + + // Set the table count if needed. + if (empty($context['table_count'])) + $context['table_count'] = 0; + + // Create the table! + $context['tables'][$context['table_count']] = array( + 'title' => $title, + 'default_value' => $default_value, + 'shading' => array( + 'left' => $shading == 'all' || $shading == 'left', + 'top' => $shading == 'all' || $shading == 'top', + ), + 'width' => array( + 'normal' => $width_normal, + 'shaded' => $width_shaded, + ), + 'align' => array( + 'normal' => $align_normal, + 'shaded' => $align_shaded, + ), + 'data' => array(), + ); + + $context['current_table'] = $context['table_count']; + + // Increment the count... + $context['table_count']++; +} + +// Add an extra slice of data to the table +function addData($inc_data, $custom_table = null) +{ + global $context; + + // No tables? Create one even though we are probably already in a bad state! + if (empty($context['table_count'])) + newTable(); + + // Specific table? + if ($custom_table !== null && !isset($context['tables'][$custom_table])) + return false; + elseif ($custom_table !== null) + $table = $custom_table; + else + $table = $context['current_table']; + + // If we have keys, sanitise the data... + if (!empty($context['keys'])) + { + // Basically, check every key exists! + foreach ($context['keys'] as $key => $dummy) + { + $data[$key] = array( + 'v' => empty($inc_data[$key]) ? $context['tables'][$table]['default_value'] : $inc_data[$key], + ); + // Special "hack" the adding separators when doing data by column. + if (substr($key, 0, 5) == '#sep#') + $data[$key]['separator'] = true; + } + } + else + { + $data = $inc_data; + foreach ($data as $key => $value) + { + $data[$key] = array( + 'v' => $value, + ); + if (substr($key, 0, 5) == '#sep#') + $data[$key]['separator'] = true; + } + } + + // Is it by row? + if (empty($context['key_method']) || $context['key_method'] == 'rows') + { + // Add the data! + $context['tables'][$table]['data'][] = $data; + } + // Otherwise, tricky! + else + { + foreach ($data as $key => $item) + $context['tables'][$table]['data'][$key][] = $item; + } +} + +// Add a separator row, only really used when adding data by rows. +function addSeparator($title = '', $custom_table = null) +{ + global $context; + + // No tables - return? + if (empty($context['table_count'])) + return; + + // Specific table? + if ($custom_table !== null && !isset($context['tables'][$table])) + return false; + elseif ($custom_table !== null) + $table = $custom_table; + else + $table = $context['current_table']; + + // Plumb in the separator + $context['tables'][$table]['data'][] = array(0 => array( + 'separator' => true, + 'v' => $title + )); +} + +// This does the necessary count of table data before displaying them. +function finishTables() +{ + global $context; + + if (empty($context['tables'])) + return; + + // Loop through each table counting up some basic values, to help with the templating. + foreach ($context['tables'] as $id => $table) + { + $context['tables'][$id]['id'] = $id; + $context['tables'][$id]['row_count'] = count($table['data']); + $curElement = current($table['data']); + $context['tables'][$id]['column_count'] = count($curElement); + + // Work out the rough width - for templates like the print template. Without this we might get funny tables. + if ($table['shading']['left'] && $table['width']['shaded'] != 'auto' && $table['width']['normal'] != 'auto') + $context['tables'][$id]['max_width'] = $table['width']['shaded'] + ($context['tables'][$id]['column_count'] - 1) * $table['width']['normal']; + elseif ($table['width']['normal'] != 'auto') + $context['tables'][$id]['max_width'] = $context['tables'][$id]['column_count'] * $table['width']['normal']; + else + $context['tables'][$id]['max_width'] = 'auto'; + } +} + +// Set the keys in use by the tables - these ensure entries MUST exist if the data isn't sent. +function setKeys($method = 'rows', $keys = array(), $reverse = false) +{ + global $context; + + // Do we want to use the keys of the keys as the keys? :P + if ($reverse) + $context['keys'] = array_flip($keys); + else + $context['keys'] = $keys; + + // Rows or columns? + $context['key_method'] = $method == 'rows' ? 'rows' : 'cols'; +} + +?> \ No newline at end of file diff --git a/Sources/ScheduledTasks.php b/Sources/ScheduledTasks.php new file mode 100644 index 0000000..8881f29 --- /dev/null +++ b/Sources/ScheduledTasks.php @@ -0,0 +1,1649 @@ + 0, + 'current_time' => time(), + ) + ); + if ($smcFunc['db_num_rows']($request) != 0) + { + // The two important things really... + $row = $smcFunc['db_fetch_assoc']($request); + + // When should this next be run? + $next_time = next_time($row['time_regularity'], $row['time_unit'], $row['time_offset']); + + // How long in seconds it the gap? + $duration = $row['time_regularity']; + if ($row['time_unit'] == 'm') + $duration *= 60; + elseif ($row['time_unit'] == 'h') + $duration *= 3600; + elseif ($row['time_unit'] == 'd') + $duration *= 86400; + elseif ($row['time_unit'] == 'w') + $duration *= 604800; + + // If we were really late running this task actually skip the next one. + if (time() + ($duration / 2) > $next_time) + $next_time += $duration; + + // Update it now, so no others run this! + $smcFunc['db_query']('', ' + UPDATE {db_prefix}scheduled_tasks + SET next_time = {int:next_time} + WHERE id_task = {int:id_task} + AND next_time = {int:current_next_time}', + array( + 'next_time' => $next_time, + 'id_task' => $row['id_task'], + 'current_next_time' => $row['next_time'], + ) + ); + $affected_rows = $smcFunc['db_affected_rows'](); + + // The function must exist or we are wasting our time, plus do some timestamp checking, and database check! + if (function_exists('scheduled_' . $row['task']) && (!isset($_GET['ts']) || $_GET['ts'] == $row['next_time']) && $affected_rows) + { + ignore_user_abort(true); + + // Do the task... + $completed = call_user_func('scheduled_' . $row['task']); + + // Log that we did it ;) + if ($completed) + { + $total_time = round(array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)), 3); + $smcFunc['db_insert']('', + '{db_prefix}log_scheduled_tasks', + array( + 'id_task' => 'int', 'time_run' => 'int', 'time_taken' => 'float', + ), + array( + $row['id_task'], time(), (int) $total_time, + ), + array() + ); + } + } + } + $smcFunc['db_free_result']($request); + + // Get the next timestamp right. + $request = $smcFunc['db_query']('', ' + SELECT next_time + FROM {db_prefix}scheduled_tasks + WHERE disabled = {int:not_disabled} + ORDER BY next_time ASC + LIMIT 1', + array( + 'not_disabled' => 0, + ) + ); + // No new task scheduled yet? + if ($smcFunc['db_num_rows']($request) === 0) + $nextEvent = time() + 86400; + else + list ($nextEvent) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + updateSettings(array('next_task_time' => $nextEvent)); + } + + // Shall we return? + if (!isset($_GET['scheduled'])) + return true; + + // Finally, send some stuff... + header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + header('Content-Type: image/gif'); + die("\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x21\xF9\x04\x01\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3B"); +} + +// Function to sending out approval notices to moderators etc. +function scheduled_approval_notification() +{ + global $scripturl, $modSettings, $mbname, $txt, $sourcedir, $smcFunc; + + // Grab all the items awaiting approval and sort type then board - clear up any things that are no longer relevant. + $request = $smcFunc['db_query']('', ' + SELECT aq.id_msg, aq.id_attach, aq.id_event, m.id_topic, m.id_board, m.subject, t.id_first_msg, + b.id_profile + FROM {db_prefix}approval_queue AS aq + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = aq.id_msg) + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)', + array( + ) + ); + $notices = array(); + $profiles = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // If this is no longer around we'll ignore it. + if (empty($row['id_topic'])) + continue; + + // What type is it? + if ($row['id_first_msg'] && $row['id_first_msg'] == $row['id_msg']) + $type = 'topic'; + elseif ($row['id_attach']) + $type = 'attach'; + else + $type = 'msg'; + + // Add it to the array otherwise. + $notices[$row['id_board']][$type][] = array( + 'subject' => $row['subject'], + 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'], + ); + + // Store the profile for a bit later. + $profiles[$row['id_board']] = $row['id_profile']; + } + $smcFunc['db_free_result']($request); + + // Delete it all! + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}approval_queue', + array( + ) + ); + + // If nothing quit now. + if (empty($notices)) + return true; + + // Now we need to think about finding out *who* can approve - this is hard! + + // First off, get all the groups with this permission and sort by board. + $request = $smcFunc['db_query']('', ' + SELECT id_group, id_profile, add_deny + FROM {db_prefix}board_permissions + WHERE permission = {string:approve_posts} + AND id_profile IN ({array_int:profile_list})', + array( + 'profile_list' => $profiles, + 'approve_posts' => 'approve_posts', + ) + ); + $perms = array(); + $addGroups = array(1); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Sorry guys, but we have to ignore guests AND members - it would be too many otherwise. + if ($row['id_group'] < 2) + continue; + + $perms[$row['id_profile']][$row['add_deny'] ? 'add' : 'deny'][] = $row['id_group']; + + // Anyone who can access has to be considered. + if ($row['add_deny']) + $addGroups[] = $row['id_group']; + } + $smcFunc['db_free_result']($request); + + // Grab the moderators if they have permission! + $mods = array(); + $members = array(); + if (in_array(2, $addGroups)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member, id_board + FROM {db_prefix}moderators', + array( + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $mods[$row['id_member']][$row['id_board']] = true; + // Make sure they get included in the big loop. + $members[] = $row['id_member']; + } + $smcFunc['db_free_result']($request); + } + + // Come along one and all... until we reject you ;) + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name, email_address, lngfile, id_group, additional_groups, mod_prefs + FROM {db_prefix}members + WHERE id_group IN ({array_int:additional_group_list}) + OR FIND_IN_SET({raw:additional_group_list_implode}, additional_groups) != 0' . (empty($members) ? '' : ' + OR id_member IN ({array_int:member_list})') . ' + ORDER BY lngfile', + array( + 'additional_group_list' => $addGroups, + 'member_list' => $members, + 'additional_group_list_implode' => implode(', additional_groups) != 0 OR FIND_IN_SET(', $addGroups), + ) + ); + $members = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Check whether they are interested. + if (!empty($row['mod_prefs'])) + { + list(,, $pref_binary) = explode('|', $row['mod_prefs']); + if (!($pref_binary & 4)) + continue; + } + + $members[$row['id_member']] = array( + 'id' => $row['id_member'], + 'groups' => array_merge(explode(',', $row['additional_groups']), array($row['id_group'])), + 'language' => $row['lngfile'], + 'email' => $row['email_address'], + 'name' => $row['real_name'], + ); + } + $smcFunc['db_free_result']($request); + + // Get the mailing stuff. + require_once($sourcedir . '/Subs-Post.php'); + // Need the below for loadLanguage to work! + loadEssentialThemeData(); + + // Finally, loop through each member, work out what they can do, and send it. + foreach ($members as $id => $member) + { + $emailbody = ''; + + // Load the language file as required. + if (empty($current_language) || $current_language != $member['language']) + $current_language = loadLanguage('EmailTemplates', $member['language'], false); + + // Loop through each notice... + foreach ($notices as $board => $notice) + { + $access = false; + + // Can they mod in this board? + if (isset($mods[$id][$board])) + $access = true; + + // Do the group check... + if (!$access && isset($perms[$profiles[$board]]['add'])) + { + // They can access?! + if (array_intersect($perms[$profiles[$board]]['add'], $member['groups'])) + $access = true; + + // If they have deny rights don't consider them! + if (isset($perms[$profiles[$board]]['deny'])) + if (array_intersect($perms[$profiles[$board]]['deny'], $member['groups'])) + $access = false; + } + + // Finally, fix it for admins! + if (in_array(1, $member['groups'])) + $access = true; + + // If they can't access it then give it a break! + if (!$access) + continue; + + foreach ($notice as $type => $items) + { + // Build up the top of this section. + $emailbody .= $txt['scheduled_approval_email_' . $type] . "\n" . + '------------------------------------------------------' . "\n"; + + foreach ($items as $item) + $emailbody .= $item['subject'] . ' - ' . $item['href'] . "\n"; + + $emailbody .= "\n"; + } + } + + if ($emailbody == '') + continue; + + $replacements = array( + 'REALNAME' => $member['name'], + 'BODY' => $emailbody, + ); + + $emaildata = loadEmailTemplate('scheduled_approval', $replacements, $current_language); + + // Send the actual email. + sendmail($member['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 2); + } + + // All went well! + return true; +} + +// Do some daily cleaning up. +function scheduled_daily_maintenance() +{ + global $smcFunc, $modSettings, $sourcedir, $db_type; + + // First clean out the cache. + clean_cache(); + + // If warning decrement is enabled and we have people who have not had a new warning in 24 hours, lower their warning level. + list (, , $modSettings['warning_decrement']) = explode(',', $modSettings['warning_settings']); + if ($modSettings['warning_decrement']) + { + // Find every member who has a warning level... + $request = $smcFunc['db_query']('', ' + SELECT id_member, warning + FROM {db_prefix}members + WHERE warning > {int:no_warning}', + array( + 'no_warning' => 0, + ) + ); + $members = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $members[$row['id_member']] = $row['warning']; + $smcFunc['db_free_result']($request); + + // Have some members to check? + if (!empty($members)) + { + // Find out when they were last warned. + $request = $smcFunc['db_query']('', ' + SELECT id_recipient, MAX(log_time) AS last_warning + FROM {db_prefix}log_comments + WHERE id_recipient IN ({array_int:member_list}) + AND comment_type = {string:warning} + GROUP BY id_recipient', + array( + 'member_list' => array_keys($members), + 'warning' => 'warning', + ) + ); + $member_changes = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // More than 24 hours ago? + if ($row['last_warning'] <= time() - 86400) + $member_changes[] = array( + 'id' => $row['id_recipient'], + 'warning' => $members[$row['id_recipient']] >= $modSettings['warning_decrement'] ? $members[$row['id_recipient']] - $modSettings['warning_decrement'] : 0, + ); + } + $smcFunc['db_free_result']($request); + + // Have some members to change? + if (!empty($member_changes)) + foreach ($member_changes as $change) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET warning = {int:warning} + WHERE id_member = {int:id_member}', + array( + 'warning' => $change['warning'], + 'id_member' => $change['id'], + ) + ); + } + } + + // Do any spider stuff. + if (!empty($modSettings['spider_mode']) && $modSettings['spider_mode'] > 1) + { + require_once($sourcedir . '/ManageSearchEngines.php'); + consolidateSpiderStats(); + } + + // Check the database version - for some buggy MySQL version. + $server_version = $smcFunc['db_server_info'](); + if ($db_type == 'mysql' && in_array(substr($server_version, 0, 6), array('5.0.50', '5.0.51'))) + updateSettings(array('db_mysql_group_by_fix' => '1')); + elseif (!empty($modSettings['db_mysql_group_by_fix'])) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}settings + WHERE variable = {string:mysql_fix}', + array( + 'mysql_fix' => 'db_mysql_group_by_fix', + ) + ); + + // Regenerate the Diffie-Hellman keys if OpenID is enabled. + if (!empty($modSettings['enableOpenID'])) + { + require_once($sourcedir . '/Subs-OpenID.php'); + smf_openID_setup_DH(true); + } + elseif (!empty($modSettings['dh_keys'])) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}settings + WHERE variable = {string:dh_keys}', + array( + 'dh_keys' => 'dh_keys', + ) + ); + + // Log we've done it... + return true; +} + +// Auto optimize the database? +function scheduled_auto_optimize() +{ + global $modSettings, $smcFunc, $db_prefix, $db_type; + + // By default do it now! + $delay = false; + + // As a kind of hack, if the server load is too great delay, but only by a bit! + if (!empty($modSettings['load_average']) && !empty($modSettings['loadavg_auto_opt']) && $modSettings['load_average'] >= $modSettings['loadavg_auto_opt']) + $delay = true; + + // Otherwise are we restricting the number of people online for this? + if (!empty($modSettings['autoOptMaxOnline'])) + { + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_online', + array( + ) + ); + list ($dont_do_it) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if ($dont_do_it > $modSettings['autoOptMaxOnline']) + $delay = true; + } + + // If we are gonna delay, do so now! + if ($delay) + return false; + + db_extend(); + + // Get all the tables. + $tables = $smcFunc['db_list_tables'](false, $db_prefix . '%'); + + // Actually do the optimisation. + if ($db_type == 'sqlite') + $smcFunc['db_optimize_table']($table[0]); + else + foreach ($tables as $table) + $smcFunc['db_optimize_table']($table); + + // Return for the log... + return true; +} + +// Send out a daily email of all subscribed topics. +function scheduled_daily_digest() +{ + global $is_weekly, $txt, $mbname, $scripturl, $sourcedir, $smcFunc, $context, $modSettings; + + // We'll want this... + require_once($sourcedir . '/Subs-Post.php'); + loadEssentialThemeData(); + + $is_weekly = !empty($is_weekly) ? 1 : 0; + + // Right - get all the notification data FIRST. + $request = $smcFunc['db_query']('', ' + SELECT ln.id_topic, COALESCE(t.id_board, ln.id_board) AS id_board, mem.email_address, mem.member_name, mem.notify_types, + mem.lngfile, mem.id_member + FROM {db_prefix}log_notify AS ln + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member) + LEFT JOIN {db_prefix}topics AS t ON (ln.id_topic != {int:empty_topic} AND t.id_topic = ln.id_topic) + WHERE mem.notify_regularity = {int:notify_regularity} + AND mem.is_activated = {int:is_activated}', + array( + 'empty_topic' => 0, + 'notify_regularity' => $is_weekly ? '3' : '2', + 'is_activated' => 1, + ) + ); + $members = array(); + $langs = array(); + $notify = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!isset($members[$row['id_member']])) + { + $members[$row['id_member']] = array( + 'email' => $row['email_address'], + 'name' => $row['member_name'], + 'id' => $row['id_member'], + 'notifyMod' => $row['notify_types'] < 3 ? true : false, + 'lang' => $row['lngfile'], + ); + $langs[$row['lngfile']] = $row['lngfile']; + } + + // Store this useful data! + $boards[$row['id_board']] = $row['id_board']; + if ($row['id_topic']) + $notify['topics'][$row['id_topic']][] = $row['id_member']; + else + $notify['boards'][$row['id_board']][] = $row['id_member']; + } + $smcFunc['db_free_result']($request); + + if (empty($boards)) + return true; + + // Just get the board names. + $request = $smcFunc['db_query']('', ' + SELECT id_board, name + FROM {db_prefix}boards + WHERE id_board IN ({array_int:board_list})', + array( + 'board_list' => $boards, + ) + ); + $boards = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $boards[$row['id_board']] = $row['name']; + $smcFunc['db_free_result']($request); + + if (empty($boards)) + return true; + + // Get the actual topics... + $request = $smcFunc['db_query']('', ' + SELECT ld.note_type, t.id_topic, t.id_board, t.id_member_started, m.id_msg, m.subject, + b.name AS board_name + FROM {db_prefix}log_digest AS ld + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ld.id_topic + AND t.id_board IN ({array_int:board_list})) + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + WHERE ' . ($is_weekly ? 'ld.daily != {int:daily_value}' : 'ld.daily IN (0, 2)'), + array( + 'board_list' => array_keys($boards), + 'daily_value' => 2, + ) + ); + $types = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!isset($types[$row['note_type']][$row['id_board']])) + $types[$row['note_type']][$row['id_board']] = array( + 'lines' => array(), + 'name' => $row['board_name'], + 'id' => $row['id_board'], + ); + + if ($row['note_type'] == 'reply') + { + if (isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']])) + $types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['count']++; + else + $types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array( + 'id' => $row['id_topic'], + 'subject' => un_htmlspecialchars($row['subject']), + 'count' => 1, + ); + } + elseif ($row['note_type'] == 'topic') + { + if (!isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']])) + $types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array( + 'id' => $row['id_topic'], + 'subject' => un_htmlspecialchars($row['subject']), + ); + } + else + { + if (!isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']])) + $types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array( + 'id' => $row['id_topic'], + 'subject' => un_htmlspecialchars($row['subject']), + 'starter' => $row['id_member_started'], + ); + } + + $types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'] = array(); + if (!empty($notify['topics'][$row['id_topic']])) + $types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'] = array_merge($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'], $notify['topics'][$row['id_topic']]); + if (!empty($notify['boards'][$row['id_board']])) + $types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'] = array_merge($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'], $notify['boards'][$row['id_board']]); + } + $smcFunc['db_free_result']($request); + + if (empty($types)) + return true; + + // Let's load all the languages into a cache thingy. + $langtxt = array(); + foreach ($langs as $lang) + { + loadLanguage('Post', $lang); + loadLanguage('index', $lang); + loadLanguage('EmailTemplates', $lang); + $langtxt[$lang] = array( + 'subject' => $txt['digest_subject_' . ($is_weekly ? 'weekly' : 'daily')], + 'char_set' => $txt['lang_character_set'], + 'intro' => sprintf($txt['digest_intro_' . ($is_weekly ? 'weekly' : 'daily')], $mbname), + 'new_topics' => $txt['digest_new_topics'], + 'topic_lines' => $txt['digest_new_topics_line'], + 'new_replies' => $txt['digest_new_replies'], + 'mod_actions' => $txt['digest_mod_actions'], + 'replies_one' => $txt['digest_new_replies_one'], + 'replies_many' => $txt['digest_new_replies_many'], + 'sticky' => $txt['digest_mod_act_sticky'], + 'lock' => $txt['digest_mod_act_lock'], + 'unlock' => $txt['digest_mod_act_unlock'], + 'remove' => $txt['digest_mod_act_remove'], + 'move' => $txt['digest_mod_act_move'], + 'merge' => $txt['digest_mod_act_merge'], + 'split' => $txt['digest_mod_act_split'], + 'bye' => $txt['regards_team'], + ); + } + + // Right - send out the silly things - this will take quite some space! + $emails = array(); + foreach ($members as $mid => $member) + { + // Right character set! + $context['character_set'] = empty($modSettings['global_character_set']) ? $langtxt[$lang]['char_set'] : $modSettings['global_character_set']; + + // Do the start stuff! + $email = array( + 'subject' => $mbname . ' - ' . $langtxt[$lang]['subject'], + 'body' => $member['name'] . ',' . "\n\n" . $langtxt[$lang]['intro'] . "\n" . $scripturl . '?action=profile;area=notification;u=' . $member['id'] . "\n", + 'email' => $member['email'], + ); + + // All new topics? + if (isset($types['topic'])) + { + $titled = false; + foreach ($types['topic'] as $id => $board) + foreach ($board['lines'] as $topic) + if (in_array($mid, $topic['members'])) + { + if (!$titled) + { + $email['body'] .= "\n" . $langtxt[$lang]['new_topics'] . ':' . "\n" . '-----------------------------------------------'; + $titled = true; + } + $email['body'] .= "\n" . sprintf($langtxt[$lang]['topic_lines'], $topic['subject'], $board['name']); + } + if ($titled) + $email['body'] .= "\n"; + } + + // What about replies? + if (isset($types['reply'])) + { + $titled = false; + foreach ($types['reply'] as $id => $board) + foreach ($board['lines'] as $topic) + if (in_array($mid, $topic['members'])) + { + if (!$titled) + { + $email['body'] .= "\n" . $langtxt[$lang]['new_replies'] . ':' . "\n" . '-----------------------------------------------'; + $titled = true; + } + $email['body'] .= "\n" . ($topic['count'] == 1 ? sprintf($langtxt[$lang]['replies_one'], $topic['subject']) : sprintf($langtxt[$lang]['replies_many'], $topic['count'], $topic['subject'])); + } + + if ($titled) + $email['body'] .= "\n"; + } + + // Finally, moderation actions! + $titled = false; + foreach ($types as $note_type => $type) + { + if ($note_type == 'topic' || $note_type == 'reply') + continue; + + foreach ($type as $id => $board) + foreach ($board['lines'] as $topic) + if (in_array($mid, $topic['members'])) + { + if (!$titled) + { + $email['body'] .= "\n" . $langtxt[$lang]['mod_actions'] . ':' . "\n" . '-----------------------------------------------'; + $titled = true; + } + $email['body'] .= "\n" . sprintf($langtxt[$lang][$note_type], $topic['subject']); + } + + } + if ($titled) + $email['body'] .= "\n"; + + // Then just say our goodbyes! + $email['body'] .= "\n\n" . $txt['regards_team']; + + // Send it - low priority! + sendmail($email['email'], $email['subject'], $email['body'], null, null, false, 4); + } + + // Clean up... + if ($is_weekly) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_digest + WHERE daily != {int:not_daily}', + array( + 'not_daily' => 0, + ) + ); + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_digest + SET daily = {int:daily_value} + WHERE daily = {int:not_daily}', + array( + 'daily_value' => 2, + 'not_daily' => 0, + ) + ); + } + else + { + // Clear any only weekly ones, and stop us from sending daily again. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_digest + WHERE daily = {int:daily_value}', + array( + 'daily_value' => 2, + ) + ); + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_digest + SET daily = {int:both_value} + WHERE daily = {int:no_value}', + array( + 'both_value' => 1, + 'no_value' => 0, + ) + ); + } + + // Just in case the member changes their settings mark this as sent. + $members = array_keys($members); + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_notify + SET sent = {int:is_sent} + WHERE id_member IN ({array_int:member_list})', + array( + 'member_list' => $members, + 'is_sent' => 1, + ) + ); + + // Log we've done it... + return true; +} + +// Like the daily stuff - just seven times less regular ;) +function scheduled_weekly_digest() +{ + global $is_weekly; + + // We just pass through to the daily function - avoid duplication! + $is_weekly = true; + return scheduled_daily_digest(); +} + +// Send a bunch of emails from the mail queue. +function ReduceMailQueue($number = false, $override_limit = false, $force_send = false) +{ + global $modSettings, $smcFunc, $sourcedir; + + // Are we intending another script to be sending out the queue? + if (!empty($modSettings['mail_queue_use_cron']) && empty($force_send)) + return false; + + // By default send 5 at once. + if (!$number) + $number = empty($modSettings['mail_quantity']) ? 5 : $modSettings['mail_quantity']; + + // If we came with a timestamp, and that doesn't match the next event, then someone else has beaten us. + if (isset($_GET['ts']) && $_GET['ts'] != $modSettings['mail_next_send'] && empty($force_send)) + return false; + + // By default move the next sending on by 10 seconds, and require an affected row. + if (!$override_limit) + { + $delay = !empty($modSettings['mail_queue_delay']) ? $modSettings['mail_queue_delay'] : (!empty($modSettings['mail_limit']) && $modSettings['mail_limit'] < 5 ? 10 : 5); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}settings + SET value = {string:next_mail_send} + WHERE variable = {string:mail_next_send} + AND value = {string:last_send}', + array( + 'next_mail_send' => time() + $delay, + 'mail_next_send' => 'mail_next_send', + 'last_send' => $modSettings['mail_next_send'], + ) + ); + if ($smcFunc['db_affected_rows']() == 0) + return false; + $modSettings['mail_next_send'] = time() + $delay; + } + + // If we're not overriding how many are we allow to send? + if (!$override_limit && !empty($modSettings['mail_limit'])) + { + list ($mt, $mn) = @explode('|', $modSettings['mail_recent']); + + // Nothing worth noting... + if (empty($mn) || $mt < time() - 60) + { + $mt = time(); + $mn = $number; + } + // Otherwise we have a few more we can spend? + elseif ($mn < $modSettings['mail_limit']) + { + $mn += $number; + } + // No more I'm afraid, return! + else + return false; + + // Reflect that we're about to send some, do it now to be safe. + updateSettings(array('mail_recent' => $mt . '|' . $mn)); + } + + // Now we know how many we're sending, let's send them. + $request = $smcFunc['db_query']('', ' + SELECT /*!40001 SQL_NO_CACHE */ id_mail, recipient, body, subject, headers, send_html, time_sent, private + FROM {db_prefix}mail_queue + ORDER BY priority ASC, id_mail ASC + LIMIT ' . $number, + array( + ) + ); + $ids = array(); + $emails = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // We want to delete these from the database ASAP, so just get the data and go. + $ids[] = $row['id_mail']; + $emails[] = array( + 'to' => $row['recipient'], + 'body' => $row['body'], + 'subject' => $row['subject'], + 'headers' => $row['headers'], + 'send_html' => $row['send_html'], + 'time_sent' => $row['time_sent'], + 'private' => $row['private'], + ); + } + $smcFunc['db_free_result']($request); + + // Delete, delete, delete!!! + if (!empty($ids)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}mail_queue + WHERE id_mail IN ({array_int:mail_list})', + array( + 'mail_list' => $ids, + ) + ); + + // Don't believe we have any left? + if (count($ids) < $number) + { + // Only update the setting if no-one else has beaten us to it. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}settings + SET value = {string:no_send} + WHERE variable = {string:mail_next_send} + AND value = {string:last_mail_send}', + array( + 'no_send' => '0', + 'mail_next_send' => 'mail_next_send', + 'last_mail_send' => $modSettings['mail_next_send'], + ) + ); + } + + if (empty($ids)) + return false; + + if (!empty($modSettings['mail_type']) && $modSettings['smtp_host'] != '') + require_once($sourcedir . '/Subs-Post.php'); + + // Send each email, yea! + $failed_emails = array(); + foreach ($emails as $key => $email) + { + if (empty($modSettings['mail_type']) || $modSettings['smtp_host'] == '') + { + $email['subject'] = strtr($email['subject'], array("\r" => '', "\n" => '')); + if (!empty($modSettings['mail_strip_carriage'])) + { + $email['body'] = strtr($email['body'], array("\r" => '')); + $email['headers'] = strtr($email['headers'], array("\r" => '')); + } + + // No point logging a specific error here, as we have no language. PHP error is helpful anyway... + $result = mail(strtr($email['to'], array("\r" => '', "\n" => '')), $email['subject'], $email['body'], $email['headers']); + + // Try to stop a timeout, this would be bad... + @set_time_limit(300); + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + } + else + $result = smtp_mail(array($email['to']), $email['subject'], $email['body'], $email['send_html'] ? $email['headers'] : 'Mime-Version: 1.0' . "\r\n" . $email['headers']); + + // Hopefully it sent? + if (!$result) + $failed_emails[] = array($email['to'], $email['body'], $email['subject'], $email['headers'], $email['send_html'], $email['time_sent'], $email['private']); + } + + // Any emails that didn't send? + if (!empty($failed_emails)) + { + // Update the failed attempts check. + $smcFunc['db_insert']('replace', + '{db_prefix}settings', + array('variable' => 'string', 'value' => 'string'), + array('mail_failed_attempts', empty($modSettings['mail_failed_attempts']) ? 1 : ++$modSettings['mail_failed_attempts']), + array('variable') + ); + + // If we have failed to many times, tell mail to wait a bit and try again. + if ($modSettings['mail_failed_attempts'] > 5) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}settings + SET value = {string:next_mail_send} + WHERE variable = {string:mail_next_send} + AND value = {string:last_send}', + array( + 'next_mail_send' => time() + 60, + 'mail_next_send' => 'mail_next_send', + 'last_send' => $modSettings['mail_next_send'], + )); + + // Add our email back to the queue, manually. + $smcFunc['db_insert']('insert', + '{db_prefix}mail_queue', + array('recipient' => 'string', 'body' => 'string', 'subject' => 'string', 'headers' => 'string', 'send_html' => 'string', 'time_sent' => 'string', 'private' => 'int'), + $failed_emails, + array('id_mail') + ); + + return false; + } + // We where unable to send the email, clear our failed attempts. + elseif (!empty($modSettings['mail_failed_attempts'])) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}settings + SET value = {string:zero} + WHERE variable = {string:mail_failed_attempts}', + array( + 'zero' => '0', + 'mail_failed_attempts' => 'mail_failed_attempts', + )); + + // Had something to send... + return true; +} + +// Calculate the next time the passed tasks should be triggered. +function CalculateNextTrigger($tasks = array(), $forceUpdate = false) +{ + global $modSettings, $smcFunc; + + $task_query = ''; + if (!is_array($tasks)) + $tasks = array($tasks); + + // Actually have something passed? + if (!empty($tasks)) + { + if (!isset($tasks[0]) || is_numeric($tasks[0])) + $task_query = ' AND id_task IN ({array_int:tasks})'; + else + $task_query = ' AND task IN ({array_string:tasks})'; + } + $nextTaskTime = empty($tasks) ? time() + 86400 : $modSettings['next_task_time']; + + // Get the critical info for the tasks. + $request = $smcFunc['db_query']('', ' + SELECT id_task, next_time, time_offset, time_regularity, time_unit + FROM {db_prefix}scheduled_tasks + WHERE disabled = {int:no_disabled} + ' . $task_query, + array( + 'no_disabled' => 0, + 'tasks' => $tasks, + ) + ); + $tasks = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $next_time = next_time($row['time_regularity'], $row['time_unit'], $row['time_offset']); + + // Only bother moving the task if it's out of place or we're forcing it! + if ($forceUpdate || $next_time < $row['next_time'] || $row['next_time'] < time()) + $tasks[$row['id_task']] = $next_time; + else + $next_time = $row['next_time']; + + // If this is sooner than the current next task, make this the next task. + if ($next_time < $nextTaskTime) + $nextTaskTime = $next_time; + } + $smcFunc['db_free_result']($request); + + // Now make the changes! + foreach ($tasks as $id => $time) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}scheduled_tasks + SET next_time = {int:next_time} + WHERE id_task = {int:id_task}', + array( + 'next_time' => $time, + 'id_task' => $id, + ) + ); + + // If the next task is now different update. + if ($modSettings['next_task_time'] != $nextTaskTime) + updateSettings(array('next_task_time' => $nextTaskTime)); +} + +// Simply returns a time stamp of the next instance of these time parameters. +function next_time($regularity, $unit, $offset) +{ + // Just in case! + if ($regularity == 0) + $regularity = 2; + + $curHour = date('H', time()); + $curMin = date('i', time()); + $next_time = 9999999999; + + // If the unit is minutes only check regularity in minutes. + if ($unit == 'm') + { + $off = date('i', $offset); + + // If it's now just pretend it ain't, + if ($off == $curMin) + $next_time = time() + $regularity; + else + { + // Make sure that the offset is always in the past. + $off = $off > $curMin ? $off - 60 : $off; + + while ($off <= $curMin) + $off += $regularity; + + // Now we know when the time should be! + $next_time = time() + 60 * ($off - $curMin); + } + } + // Otherwise, work out what the offset would be with todays date. + else + { + $next_time = mktime(date('H', $offset), date('i', $offset), 0, date('m'), date('d'), date('Y')); + + // Make the time offset in the past! + if ($next_time > time()) + { + $next_time -= 86400; + } + + // Default we'll jump in hours. + $applyOffset = 3600; + // 24 hours = 1 day. + if ($unit == 'd') + $applyOffset = 86400; + // Otherwise a week. + if ($unit == 'w') + $applyOffset = 604800; + + $applyOffset *= $regularity; + + // Just add on the offset. + while ($next_time <= time()) + { + $next_time += $applyOffset; + } + } + + return $next_time; +} + +// This loads the bare minimum data to allow us to load language files! +function loadEssentialThemeData() +{ + global $settings, $modSettings, $smcFunc, $mbname, $context, $sourcedir; + + // Get all the default theme variables. + $result = $smcFunc['db_query']('', ' + SELECT id_theme, variable, value + FROM {db_prefix}themes + WHERE id_member = {int:no_member} + AND id_theme IN (1, {int:theme_guests})', + array( + 'no_member' => 0, + 'theme_guests' => $modSettings['theme_guests'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $settings[$row['variable']] = $row['value']; + + // Is this the default theme? + if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1') + $settings['default_' . $row['variable']] = $row['value']; + } + $smcFunc['db_free_result']($result); + + // Check we have some directories setup. + if (empty($settings['template_dirs'])) + { + $settings['template_dirs'] = array($settings['theme_dir']); + + // Based on theme (if there is one). + if (!empty($settings['base_theme_dir'])) + $settings['template_dirs'][] = $settings['base_theme_dir']; + + // Lastly the default theme. + if ($settings['theme_dir'] != $settings['default_theme_dir']) + $settings['template_dirs'][] = $settings['default_theme_dir']; + } + + // Assume we want this. + $context['forum_name'] = $mbname; + + // Check loadLanguage actually exists! + if (!function_exists('loadLanguage')) + { + require_once($sourcedir . '/Load.php'); + require_once($sourcedir . '/Subs.php'); + } + + loadLanguage('index+Modifications'); +} + +function scheduled_fetchSMfiles() +{ + global $sourcedir, $txt, $language, $settings, $forum_version, $modSettings, $smcFunc; + + // What files do we want to get + $request = $smcFunc['db_query']('', ' + SELECT id_file, filename, path, parameters + FROM {db_prefix}admin_info_files', + array( + ) + ); + + $js_files = array(); + + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $js_files[$row['id_file']] = array( + 'filename' => $row['filename'], + 'path' => $row['path'], + 'parameters' => sprintf($row['parameters'], $language, urlencode($modSettings['time_format']), urlencode($forum_version)), + ); + } + + $smcFunc['db_free_result']($request); + + // We're gonna need fetch_web_data() to pull this off. + require_once($sourcedir . '/Subs-Package.php'); + + // Just in case we run into a problem. + loadEssentialThemeData(); + loadLanguage('Errors', $language, false); + + foreach ($js_files as $ID_FILE => $file) + { + // Create the url + $server = empty($file['path']) || substr($file['path'], 0, 7) != 'http://' ? 'http://www.simplemachines.org' : ''; + $url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : ''); + + // Get the file + $file_data = fetch_web_data($url); + + // If we got an error - give up - the site might be down. + if ($file_data === false) + { + log_error(sprintf($txt['st_cannot_retrieve_file'], $url)); + return false; + } + + // Save the file to the database. + $smcFunc['db_query']('substring', ' + UPDATE {db_prefix}admin_info_files + SET data = SUBSTRING({string:file_data}, 1, 65534) + WHERE id_file = {int:id_file}', + array( + 'id_file' => $ID_FILE, + 'file_data' => $file_data, + ) + ); + } + return true; +} + +function scheduled_birthdayemails() +{ + global $modSettings, $sourcedir, $mbname, $txt, $smcFunc, $birthdayEmails; + + // Need this in order to load the language files. + loadEssentialThemeData(); + + // Going to need this to send the emails. + require_once($sourcedir . '/Subs-Post.php'); + + $greeting = isset($modSettings['birthday_email']) ? $modSettings['birthday_email'] : 'happy_birthday'; + + // Get the month and day of today. + $month = date('n'); // Month without leading zeros. + $day = date('j'); // Day without leading zeros. + + // So who are the lucky ones? Don't include those who are banned and those who don't want them. + $result = $smcFunc['db_query']('', ' + SELECT id_member, real_name, lngfile, email_address + FROM {db_prefix}members + WHERE is_activated < 10 + AND MONTH(birthdate) = {int:month} + AND DAYOFMONTH(birthdate) = {int:day} + AND notify_announcements = {int:notify_announcements} + AND YEAR(birthdate) > {int:year}', + array( + 'notify_announcements' => 1, + 'year' => 1, + 'month' => $month, + 'day' => $day, + ) + ); + + // Group them by languages. + $birthdays = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + if (!isset($birthdays[$row['lngfile']])) + $birthdays[$row['lngfile']] = array(); + $birthdays[$row['lngfile']][$row['id_member']] = array( + 'name' => $row['real_name'], + 'email' => $row['email_address'] + ); + } + $smcFunc['db_free_result']($result); + + // Send out the greetings! + foreach ($birthdays as $lang => $recps) + { + // We need to do some shuffling to make this work properly. + loadLanguage('EmailTemplates', $lang); + $txt['emails']['happy_birthday'] = $birthdayEmails[$greeting]; + + foreach ($recps as $recp) + { + $replacements = array( + 'REALNAME' => $recp['name'], + ); + + $emaildata = loadEmailTemplate('happy_birthday', $replacements, $lang, false); + + sendmail($recp['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 4); + + // Try to stop a timeout, this would be bad... + @set_time_limit(300); + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + } + } + + // Flush the mail queue, just in case. + AddMailQueue(true); + + return true; +} + +function scheduled_weekly_maintenance() +{ + global $modSettings, $smcFunc; + + // Delete some settings that needn't be set if they are otherwise empty. + $emptySettings = array( + 'warning_mute', 'warning_moderate', 'warning_watch', 'warning_show', 'disableCustomPerPage', 'spider_mode', 'spider_group', + 'paid_currency_code', 'paid_currency_symbol', 'paid_email_to', 'paid_email', 'paid_enabled', 'paypal_email', + 'search_enable_captcha', 'search_floodcontrol_time', 'show_spider_online', + ); + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}settings + WHERE variable IN ({array_string:setting_list}) + AND (value = {string:zero_value} OR value = {string:blank_value})', + array( + 'zero_value' => '0', + 'blank_value' => '', + 'setting_list' => $emptySettings, + ) + ); + + // Some settings we never want to keep - they are just there for temporary purposes. + $deleteAnywaySettings = array( + 'attachment_full_notified', + ); + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}settings + WHERE variable IN ({array_string:setting_list})', + array( + 'setting_list' => $deleteAnywaySettings, + ) + ); + + // Ok should we prune the logs? + if (!empty($modSettings['pruningOptions'])) + { + if (!empty($modSettings['pruningOptions']) && strpos($modSettings['pruningOptions'], ',') !== false) + list ($modSettings['pruneErrorLog'], $modSettings['pruneModLog'], $modSettings['pruneBanLog'], $modSettings['pruneReportLog'], $modSettings['pruneScheduledTaskLog'], $modSettings['pruneSpiderHitLog']) = explode(',', $modSettings['pruningOptions']); + + if (!empty($modSettings['pruneErrorLog'])) + { + // Figure out when our cutoff time is. 1 day = 86400 seconds. + $t = time() - $modSettings['pruneErrorLog'] * 86400; + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_errors + WHERE log_time < {int:log_time}', + array( + 'log_time' => $t, + ) + ); + } + + if (!empty($modSettings['pruneModLog'])) + { + // Figure out when our cutoff time is. 1 day = 86400 seconds. + $t = time() - $modSettings['pruneModLog'] * 86400; + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_actions + WHERE log_time < {int:log_time} + AND id_log = {int:moderation_log}', + array( + 'log_time' => $t, + 'moderation_log' => 1, + ) + ); + } + + if (!empty($modSettings['pruneBanLog'])) + { + // Figure out when our cutoff time is. 1 day = 86400 seconds. + $t = time() - $modSettings['pruneBanLog'] * 86400; + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_banned + WHERE log_time < {int:log_time}', + array( + 'log_time' => $t, + ) + ); + } + + if (!empty($modSettings['pruneReportLog'])) + { + // Figure out when our cutoff time is. 1 day = 86400 seconds. + $t = time() - $modSettings['pruneReportLog'] * 86400; + + // This one is more complex then the other logs. First we need to figure out which reports are too old. + $reports = array(); + $result = $smcFunc['db_query']('', ' + SELECT id_report + FROM {db_prefix}log_reported + WHERE time_started < {int:time_started} + AND closed = {int:not_closed} + AND ignore_all = {int:not_ignored}', + array( + 'time_started' => $t, + 'not_closed' => 0, + 'not_ignored' => 0, + ) + ); + + while ($row = $smcFunc['db_fetch_row']($result)) + $reports[] = $row[0]; + + $smcFunc['db_free_result']($result); + + if (!empty($reports)) + { + // Now delete the reports... + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_reported + WHERE id_report IN ({array_int:report_list})', + array( + 'report_list' => $reports, + ) + ); + // And delete the comments for those reports... + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_reported_comments + WHERE id_report IN ({array_int:report_list})', + array( + 'report_list' => $reports, + ) + ); + } + } + + if (!empty($modSettings['pruneScheduledTaskLog'])) + { + // Figure out when our cutoff time is. 1 day = 86400 seconds. + $t = time() - $modSettings['pruneScheduledTaskLog'] * 86400; + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_scheduled_tasks + WHERE time_run < {int:time_run}', + array( + 'time_run' => $t, + ) + ); + } + + if (!empty($modSettings['pruneSpiderHitLog'])) + { + // Figure out when our cutoff time is. 1 day = 86400 seconds. + $t = time() - $modSettings['pruneSpiderHitLog'] * 86400; + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_spider_hits + WHERE log_time < {int:log_time}', + array( + 'log_time' => $t, + ) + ); + } + } + + // Get rid of any paid subscriptions that were never actioned. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_subscribed + WHERE end_time = {int:no_end_time} + AND status = {int:not_active} + AND start_time < {int:start_time} + AND payments_pending < {int:payments_pending}', + array( + 'no_end_time' => 0, + 'not_active' => 0, + 'start_time' => time() - 60, + 'payments_pending' => 1, + ) + ); + + // Some OS's don't seem to clean out their sessions. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}sessions + WHERE last_update < {int:last_update}', + array( + 'last_update' => time() - 86400, + ) + ); + + return true; +} + +// Perform the standard checks on expiring/near expiring subscriptions. +function scheduled_paid_subscriptions() +{ + global $txt, $sourcedir, $scripturl, $smcFunc, $modSettings, $language; + + // Start off by checking for removed subscriptions. + $request = $smcFunc['db_query']('', ' + SELECT id_subscribe, id_member + FROM {db_prefix}log_subscribed + WHERE status = {int:is_active} + AND end_time < {int:time_now}', + array( + 'is_active' => 1, + 'time_now' => time(), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + require_once($sourcedir . '/ManagePaid.php'); + removeSubscription($row['id_subscribe'], $row['id_member']); + } + $smcFunc['db_free_result']($request); + + // Get all those about to expire that have not had a reminder sent. + $request = $smcFunc['db_query']('', ' + SELECT ls.id_sublog, m.id_member, m.member_name, m.email_address, m.lngfile, s.name, ls.end_time + FROM {db_prefix}log_subscribed AS ls + INNER JOIN {db_prefix}subscriptions AS s ON (s.id_subscribe = ls.id_subscribe) + INNER JOIN {db_prefix}members AS m ON (m.id_member = ls.id_member) + WHERE ls.status = {int:is_active} + AND ls.reminder_sent = {int:reminder_sent} + AND s.reminder > {int:reminder_wanted} + AND ls.end_time < ({int:time_now} + s.reminder * 86400)', + array( + 'is_active' => 1, + 'reminder_sent' => 0, + 'reminder_wanted' => 0, + 'time_now' => time(), + ) + ); + $subs_reminded = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // If this is the first one load the important bits. + if (empty($subs_reminded)) + { + require_once($sourcedir . '/Subs-Post.php'); + // Need the below for loadLanguage to work! + loadEssentialThemeData(); + } + + $subs_reminded[] = $row['id_sublog']; + + $replacements = array( + 'PROFILE_LINK' => $scripturl . '?action=profile;area=subscriptions;u=' . $row['id_member'], + 'REALNAME' => $row['member_name'], + 'SUBSCRIPTION' => $row['name'], + 'END_DATE' => strip_tags(timeformat($row['end_time'])), + ); + + $emaildata = loadEmailTemplate('paid_subscription_reminder', $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']); + + // Send the actual email. + sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 2); + } + $smcFunc['db_free_result']($request); + + // Mark the reminder as sent. + if (!empty($subs_reminded)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_subscribed + SET reminder_sent = {int:reminder_sent} + WHERE id_sublog IN ({array_int:subscription_list})', + array( + 'subscription_list' => $subs_reminded, + 'reminder_sent' => 1, + ) + ); + + return true; +} + +?> \ No newline at end of file diff --git a/Sources/Search.php b/Sources/Search.php new file mode 100644 index 0000000..e08aab8 --- /dev/null +++ b/Sources/Search.php @@ -0,0 +1,2105 @@ + 'SMF 2.0', + // This is the minimum version of SMF that an API could have been written for to work. (strtr to stop accidentally updating version on release) + 'search_version' => strtr('SMF 2+0=Beta=2', array('+' => '.', '=' => ' ')), +); + +// Ask the user what they want to search for. +function PlushSearch1() +{ + global $txt, $scripturl, $modSettings, $user_info, $context, $smcFunc, $sourcedir; + + // Is the load average too high to allow searching just now? + if (!empty($context['load_average']) && !empty($modSettings['loadavg_search']) && $context['load_average'] >= $modSettings['loadavg_search']) + fatal_lang_error('loadavg_search_disabled', false); + + loadLanguage('Search'); + // Don't load this in XML mode. + if (!isset($_REQUEST['xml'])) + loadTemplate('Search'); + + // Check the user's permissions. + isAllowedTo('search_posts'); + + // Link tree.... + $context['linktree'][] = array( + 'url' => $scripturl . '?action=search', + 'name' => $txt['search'] + ); + + // This is hard coded maximum string length. + $context['search_string_limit'] = 100; + + $context['require_verification'] = $user_info['is_guest'] && !empty($modSettings['search_enable_captcha']) && empty($_SESSION['ss_vv_passed']); + if ($context['require_verification']) + { + require_once($sourcedir . '/Subs-Editor.php'); + $verificationOptions = array( + 'id' => 'search', + ); + $context['require_verification'] = create_control_verification($verificationOptions); + $context['visual_verification_id'] = $verificationOptions['id']; + } + + // If you got back from search2 by using the linktree, you get your original search parameters back. + if (isset($_REQUEST['params'])) + { + // Due to IE's 2083 character limit, we have to compress long search strings + $temp_params = base64_decode(str_replace(array('-', '_', '.'), array('+', '/', '='), $_REQUEST['params'])); + // Test for gzuncompress failing + $temp_params2 = @gzuncompress($temp_params); + $temp_params = explode('|"|', !empty($temp_params2) ? $temp_params2 : $temp_params); + + $context['search_params'] = array(); + foreach ($temp_params as $i => $data) + { + @list ($k, $v) = explode('|\'|', $data); + $context['search_params'][$k] = $v; + } + if (isset($context['search_params']['brd'])) + $context['search_params']['brd'] = $context['search_params']['brd'] == '' ? array() : explode(',', $context['search_params']['brd']); + } + + if (isset($_REQUEST['search'])) + $context['search_params']['search'] = un_htmlspecialchars($_REQUEST['search']); + + if (isset($context['search_params']['search'])) + $context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']); + if (isset($context['search_params']['userspec'])) + $context['search_params']['userspec'] = htmlspecialchars($context['search_params']['userspec']); + if (!empty($context['search_params']['searchtype'])) + $context['search_params']['searchtype'] = 2; + if (!empty($context['search_params']['minage'])) + $context['search_params']['minage'] = (int) $context['search_params']['minage']; + if (!empty($context['search_params']['maxage'])) + $context['search_params']['maxage'] = (int) $context['search_params']['maxage']; + + $context['search_params']['show_complete'] = !empty($context['search_params']['show_complete']); + $context['search_params']['subject_only'] = !empty($context['search_params']['subject_only']); + + // Load the error text strings if there were errors in the search. + if (!empty($context['search_errors'])) + { + loadLanguage('Errors'); + $context['search_errors']['messages'] = array(); + foreach ($context['search_errors'] as $search_error => $dummy) + { + if ($search_error === 'messages') + continue; + + $context['search_errors']['messages'][] = $txt['error_' . $search_error]; + } + } + + // Find all the boards this user is allowed to see. + $request = $smcFunc['db_query']('order_by_board_order', ' + SELECT b.id_cat, c.name AS cat_name, b.id_board, b.name, b.child_level + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat) + WHERE {query_see_board} + AND redirect = {string:empty_string}', + array( + 'empty_string' => '', + ) + ); + $context['num_boards'] = $smcFunc['db_num_rows']($request); + $context['boards_check_all'] = true; + $context['categories'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // This category hasn't been set up yet.. + if (!isset($context['categories'][$row['id_cat']])) + $context['categories'][$row['id_cat']] = array( + 'id' => $row['id_cat'], + 'name' => $row['cat_name'], + 'boards' => array() + ); + + // Set this board up, and let the template know when it's a child. (indent them..) + $context['categories'][$row['id_cat']]['boards'][$row['id_board']] = array( + 'id' => $row['id_board'], + 'name' => $row['name'], + 'child_level' => $row['child_level'], + 'selected' => (empty($context['search_params']['brd']) && (empty($modSettings['recycle_enable']) || $row['id_board'] != $modSettings['recycle_board']) && !in_array($row['id_board'], $user_info['ignoreboards'])) || (!empty($context['search_params']['brd']) && in_array($row['id_board'], $context['search_params']['brd'])) + ); + + // If a board wasn't checked that probably should have been ensure the board selection is selected, yo! + if (!$context['categories'][$row['id_cat']]['boards'][$row['id_board']]['selected'] && (empty($modSettings['recycle_enable']) || $row['id_board'] != $modSettings['recycle_board'])) + $context['boards_check_all'] = false; + } + $smcFunc['db_free_result']($request); + + // Now, let's sort the list of categories into the boards for templates that like that. + $temp_boards = array(); + foreach ($context['categories'] as $category) + { + $temp_boards[] = array( + 'name' => $category['name'], + 'child_ids' => array_keys($category['boards']) + ); + $temp_boards = array_merge($temp_boards, array_values($category['boards'])); + + // Include a list of boards per category for easy toggling. + $context['categories'][$category['id']]['child_ids'] = array_keys($category['boards']); + } + + $max_boards = ceil(count($temp_boards) / 2); + if ($max_boards == 1) + $max_boards = 2; + + // Now, alternate them so they can be shown left and right ;). + $context['board_columns'] = array(); + for ($i = 0; $i < $max_boards; $i++) + { + $context['board_columns'][] = $temp_boards[$i]; + if (isset($temp_boards[$i + $max_boards])) + $context['board_columns'][] = $temp_boards[$i + $max_boards]; + else + $context['board_columns'][] = array(); + } + + if (!empty($_REQUEST['topic'])) + { + $context['search_params']['topic'] = (int) $_REQUEST['topic']; + $context['search_params']['show_complete'] = true; + } + if (!empty($context['search_params']['topic'])) + { + $context['search_params']['topic'] = (int) $context['search_params']['topic']; + + $context['search_topic'] = array( + 'id' => $context['search_params']['topic'], + 'href' => $scripturl . '?topic=' . $context['search_params']['topic'] . '.0', + ); + + $request = $smcFunc['db_query']('', ' + SELECT ms.subject + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg) + WHERE t.id_topic = {int:search_topic_id} + AND {query_see_board}' . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved_true}' : '') . ' + LIMIT 1', + array( + 'is_approved_true' => 1, + 'search_topic_id' => $context['search_params']['topic'], + ) + ); + + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('topic_gone', false); + + list ($context['search_topic']['subject']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $context['search_topic']['link'] = '' . $context['search_topic']['subject'] . ''; + } + + // Simple or not? + $context['simple_search'] = isset($context['search_params']['advanced']) ? empty($context['search_params']['advanced']) : !empty($modSettings['simpleSearch']) && !isset($_REQUEST['advanced']); + $context['page_title'] = $txt['set_parameters']; +} + +// Gather the results and show them. +function PlushSearch2() +{ + global $scripturl, $modSettings, $sourcedir, $txt, $db_connection; + global $user_info, $context, $options, $messages_request, $boards_can; + global $excludedWords, $participants, $smcFunc, $search_versions, $searchAPI; + + if (!empty($context['load_average']) && !empty($modSettings['loadavg_search']) && $context['load_average'] >= $modSettings['loadavg_search']) + fatal_lang_error('loadavg_search_disabled', false); + + // No, no, no... this is a bit hard on the server, so don't you go prefetching it! + if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') + { + ob_end_clean(); + header('HTTP/1.1 403 Forbidden'); + die; + } + + $weight_factors = array( + 'frequency', + 'age', + 'length', + 'subject', + 'first_message', + 'sticky', + ); + + $weight = array(); + $weight_total = 0; + foreach ($weight_factors as $weight_factor) + { + $weight[$weight_factor] = empty($modSettings['search_weight_' . $weight_factor]) ? 0 : (int) $modSettings['search_weight_' . $weight_factor]; + $weight_total += $weight[$weight_factor]; + } + + // Zero weight. Weightless :P. + if (empty($weight_total)) + fatal_lang_error('search_invalid_weights'); + + // These vars don't require an interface, they're just here for tweaking. + $recentPercentage = 0.30; + $humungousTopicPosts = 200; + $maxMembersToSearch = 500; + $maxMessageResults = empty($modSettings['search_max_results']) ? 0 : $modSettings['search_max_results'] * 5; + + // Start with no errors. + $context['search_errors'] = array(); + + // Number of pages hard maximum - normally not set at all. + $modSettings['search_max_results'] = empty($modSettings['search_max_results']) ? 200 * $modSettings['search_results_per_page'] : (int) $modSettings['search_max_results']; + // Maximum length of the string. + $context['search_string_limit'] = 100; + + loadLanguage('Search'); + if (!isset($_REQUEST['xml'])) + loadTemplate('Search'); + //If we're doing XML we need to use the results template regardless really. + else + $context['sub_template'] = 'results'; + + // Are you allowed? + isAllowedTo('search_posts'); + + require_once($sourcedir . '/Display.php'); + require_once($sourcedir . '/Subs-Package.php'); + + // Search has a special database set. + db_extend('search'); + + // Load up the search API we are going to use. + $modSettings['search_index'] = empty($modSettings['search_index']) ? 'standard' : $modSettings['search_index']; + if (!file_exists($sourcedir . '/SearchAPI-' . ucwords($modSettings['search_index']) . '.php')) + fatal_lang_error('search_api_missing'); + loadClassFile('SearchAPI-' . ucwords($modSettings['search_index']) . '.php'); + + // Create an instance of the search API and check it is valid for this version of SMF. + $search_class_name = $modSettings['search_index'] . '_search'; + $searchAPI = new $search_class_name(); + if (!$searchAPI || ($searchAPI->supportsMethod('isValid') && !$searchAPI->isValid()) || !matchPackageVersion($search_versions['forum_version'], $searchAPI->min_smf_version . '-' . $searchAPI->version_compatible)) + { + // Log the error. + loadLanguage('Errors'); + log_error(sprintf($txt['search_api_not_compatible'], 'SearchAPI-' . ucwords($modSettings['search_index']) . '.php'), 'critical'); + + loadClassFile('SearchAPI-Standard.php'); + $searchAPI = new standard_search(); + } + + // $search_params will carry all settings that differ from the default search parameters. + // That way, the URLs involved in a search page will be kept as short as possible. + $search_params = array(); + + if (isset($_REQUEST['params'])) + { + // Due to IE's 2083 character limit, we have to compress long search strings + $temp_params = base64_decode(str_replace(array('-', '_', '.'), array('+', '/', '='), $_REQUEST['params'])); + // Test for gzuncompress failing + $temp_params2 = @gzuncompress($temp_params); + $temp_params = explode('|"|', (!empty($temp_params2) ? $temp_params2 : $temp_params)); + + foreach ($temp_params as $i => $data) + { + @list ($k, $v) = explode('|\'|', $data); + $search_params[$k] = $v; + } + if (isset($search_params['brd'])) + $search_params['brd'] = empty($search_params['brd']) ? array() : explode(',', $search_params['brd']); + } + + // Store whether simple search was used (needed if the user wants to do another query). + if (!isset($search_params['advanced'])) + $search_params['advanced'] = empty($_REQUEST['advanced']) ? 0 : 1; + + // 1 => 'allwords' (default, don't set as param) / 2 => 'anywords'. + if (!empty($search_params['searchtype']) || (!empty($_REQUEST['searchtype']) && $_REQUEST['searchtype'] == 2)) + $search_params['searchtype'] = 2; + + // Minimum age of messages. Default to zero (don't set param in that case). + if (!empty($search_params['minage']) || (!empty($_REQUEST['minage']) && $_REQUEST['minage'] > 0)) + $search_params['minage'] = !empty($search_params['minage']) ? (int) $search_params['minage'] : (int) $_REQUEST['minage']; + + // Maximum age of messages. Default to infinite (9999 days: param not set). + if (!empty($search_params['maxage']) || (!empty($_REQUEST['maxage']) && $_REQUEST['maxage'] < 9999)) + $search_params['maxage'] = !empty($search_params['maxage']) ? (int) $search_params['maxage'] : (int) $_REQUEST['maxage']; + + // Searching a specific topic? + if (!empty($_REQUEST['topic'])) + { + $search_params['topic'] = (int) $_REQUEST['topic']; + $search_params['show_complete'] = true; + } + elseif (!empty($search_params['topic'])) + $search_params['topic'] = (int) $search_params['topic']; + + if (!empty($search_params['minage']) || !empty($search_params['maxage'])) + { + $request = $smcFunc['db_query']('', ' + SELECT ' . (empty($search_params['maxage']) ? '0, ' : 'IFNULL(MIN(id_msg), -1), ') . (empty($search_params['minage']) ? '0' : 'IFNULL(MAX(id_msg), -1)') . ' + FROM {db_prefix}messages + WHERE 1=1' . ($modSettings['postmod_active'] ? ' + AND approved = {int:is_approved_true}' : '') . (empty($search_params['minage']) ? '' : ' + AND poster_time <= {int:timestamp_minimum_age}') . (empty($search_params['maxage']) ? '' : ' + AND poster_time >= {int:timestamp_maximum_age}'), + array( + 'timestamp_minimum_age' => empty($search_params['minage']) ? 0 : time() - 86400 * $search_params['minage'], + 'timestamp_maximum_age' => empty($search_params['maxage']) ? 0 : time() - 86400 * $search_params['maxage'], + 'is_approved_true' => 1, + ) + ); + list ($minMsgID, $maxMsgID) = $smcFunc['db_fetch_row']($request); + if ($minMsgID < 0 || $maxMsgID < 0) + $context['search_errors']['no_messages_in_time_frame'] = true; + $smcFunc['db_free_result']($request); + } + + // Default the user name to a wildcard matching every user (*). + if (!empty($search_params['userspec']) || (!empty($_REQUEST['userspec']) && $_REQUEST['userspec'] != '*')) + $search_params['userspec'] = isset($search_params['userspec']) ? $search_params['userspec'] : $_REQUEST['userspec']; + + // If there's no specific user, then don't mention it in the main query. + if (empty($search_params['userspec'])) + $userQuery = ''; + else + { + $userString = strtr($smcFunc['htmlspecialchars']($search_params['userspec'], ENT_QUOTES), array('"' => '"')); + $userString = strtr($userString, array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_')); + + preg_match_all('~"([^"]+)"~', $userString, $matches); + $possible_users = array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $userString))); + + for ($k = 0, $n = count($possible_users); $k < $n; $k++) + { + $possible_users[$k] = trim($possible_users[$k]); + + if (strlen($possible_users[$k]) == 0) + unset($possible_users[$k]); + } + + // Create a list of database-escaped search names. + $realNameMatches = array(); + foreach ($possible_users as $possible_user) + $realNameMatches[] = $smcFunc['db_quote']( + '{string:possible_user}', + array( + 'possible_user' => $possible_user + ) + ); + + // Retrieve a list of possible members. + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE {raw:match_possible_users}', + array( + 'match_possible_users' => 'real_name LIKE ' . implode(' OR real_name LIKE ', $realNameMatches), + ) + ); + // Simply do nothing if there're too many members matching the criteria. + if ($smcFunc['db_num_rows']($request) > $maxMembersToSearch) + $userQuery = ''; + elseif ($smcFunc['db_num_rows']($request) == 0) + { + $userQuery = $smcFunc['db_quote']( + 'm.id_member = {int:id_member_guest} AND ({raw:match_possible_guest_names})', + array( + 'id_member_guest' => 0, + 'match_possible_guest_names' => 'm.poster_name LIKE ' . implode(' OR m.poster_name LIKE ', $realNameMatches), + ) + ); + } + else + { + $memberlist = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $memberlist[] = $row['id_member']; + $userQuery = $smcFunc['db_quote']( + '(m.id_member IN ({array_int:matched_members}) OR (m.id_member = {int:id_member_guest} AND ({raw:match_possible_guest_names})))', + array( + 'matched_members' => $memberlist, + 'id_member_guest' => 0, + 'match_possible_guest_names' => 'm.poster_name LIKE ' . implode(' OR m.poster_name LIKE ', $realNameMatches), + ) + ); + } + $smcFunc['db_free_result']($request); + } + + // If the boards were passed by URL (params=), temporarily put them back in $_REQUEST. + if (!empty($search_params['brd']) && is_array($search_params['brd'])) + $_REQUEST['brd'] = $search_params['brd']; + + // Ensure that brd is an array. + if (!empty($_REQUEST['brd']) && !is_array($_REQUEST['brd'])) + $_REQUEST['brd'] = strpos($_REQUEST['brd'], ',') !== false ? explode(',', $_REQUEST['brd']) : array($_REQUEST['brd']); + + // Make sure all boards are integers. + if (!empty($_REQUEST['brd'])) + foreach ($_REQUEST['brd'] as $id => $brd) + $_REQUEST['brd'][$id] = (int) $brd; + + // Special case for boards: searching just one topic? + if (!empty($search_params['topic'])) + { + $request = $smcFunc['db_query']('', ' + SELECT b.id_board + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + WHERE t.id_topic = {int:search_topic_id} + AND {query_see_board}' . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved_true}' : '') . ' + LIMIT 1', + array( + 'search_topic_id' => $search_params['topic'], + 'is_approved_true' => 1, + ) + ); + + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('topic_gone', false); + + $search_params['brd'] = array(); + list ($search_params['brd'][0]) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + // Select all boards you've selected AND are allowed to see. + elseif ($user_info['is_admin'] && (!empty($search_params['advanced']) || !empty($_REQUEST['brd']))) + $search_params['brd'] = empty($_REQUEST['brd']) ? array() : $_REQUEST['brd']; + else + { + $see_board = empty($search_params['advanced']) ? 'query_wanna_see_board' : 'query_see_board'; + $request = $smcFunc['db_query']('', ' + SELECT b.id_board + FROM {db_prefix}boards AS b + WHERE {raw:boards_allowed_to_see} + AND redirect = {string:empty_string}' . (empty($_REQUEST['brd']) ? (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' + AND b.id_board != {int:recycle_board_id}' : '') : ' + AND b.id_board IN ({array_int:selected_search_boards})'), + array( + 'boards_allowed_to_see' => $user_info[$see_board], + 'empty_string' => '', + 'selected_search_boards' => empty($_REQUEST['brd']) ? array() : $_REQUEST['brd'], + 'recycle_board_id' => $modSettings['recycle_board'], + ) + ); + $search_params['brd'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $search_params['brd'][] = $row['id_board']; + $smcFunc['db_free_result']($request); + + // This error should pro'bly only happen for hackers. + if (empty($search_params['brd'])) + $context['search_errors']['no_boards_selected'] = true; + } + + if (count($search_params['brd']) != 0) + { + foreach ($search_params['brd'] as $k => $v) + $search_params['brd'][$k] = (int) $v; + + // If we've selected all boards, this parameter can be left empty. + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}boards + WHERE redirect = {string:empty_string}', + array( + 'empty_string' => '', + ) + ); + list ($num_boards) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (count($search_params['brd']) == $num_boards) + $boardQuery = ''; + elseif (count($search_params['brd']) == $num_boards - 1 && !empty($modSettings['recycle_board']) && !in_array($modSettings['recycle_board'], $search_params['brd'])) + $boardQuery = '!= ' . $modSettings['recycle_board']; + else + $boardQuery = 'IN (' . implode(', ', $search_params['brd']) . ')'; + } + else + $boardQuery = ''; + + $search_params['show_complete'] = !empty($search_params['show_complete']) || !empty($_REQUEST['show_complete']); + $search_params['subject_only'] = !empty($search_params['subject_only']) || !empty($_REQUEST['subject_only']); + + $context['compact'] = !$search_params['show_complete']; + + // Get the sorting parameters right. Default to sort by relevance descending. + $sort_columns = array( + 'relevance', + 'num_replies', + 'id_msg', + ); + if (empty($search_params['sort']) && !empty($_REQUEST['sort'])) + list ($search_params['sort'], $search_params['sort_dir']) = array_pad(explode('|', $_REQUEST['sort']), 2, ''); + $search_params['sort'] = !empty($search_params['sort']) && in_array($search_params['sort'], $sort_columns) ? $search_params['sort'] : 'relevance'; + if (!empty($search_params['topic']) && $search_params['sort'] === 'num_replies') + $search_params['sort'] = 'id_msg'; + + // Sorting direction: descending unless stated otherwise. + $search_params['sort_dir'] = !empty($search_params['sort_dir']) && $search_params['sort_dir'] == 'asc' ? 'asc' : 'desc'; + + // Determine some values needed to calculate the relevance. + $minMsg = (int) ((1 - $recentPercentage) * $modSettings['maxMsgID']); + $recentMsg = $modSettings['maxMsgID'] - $minMsg; + + // *** Parse the search query + + // Unfortunately, searching for words like this is going to be slow, so we're blacklisting them. + // !!! Setting to add more here? + // !!! Maybe only blacklist if they are the only word, or "any" is used? + $blacklisted_words = array('img', 'url', 'quote', 'www', 'http', 'the', 'is', 'it', 'are', 'if'); + + // What are we searching for? + if (empty($search_params['search'])) + { + if (isset($_GET['search'])) + $search_params['search'] = un_htmlspecialchars($_GET['search']); + elseif (isset($_POST['search'])) + $search_params['search'] = $_POST['search']; + else + $search_params['search'] = ''; + } + + // Nothing?? + if (!isset($search_params['search']) || $search_params['search'] == '') + $context['search_errors']['invalid_search_string'] = true; + // Too long? + elseif ($smcFunc['strlen']($search_params['search']) > $context['search_string_limit']) + { + $context['search_errors']['string_too_long'] = true; + $txt['error_string_too_long'] = sprintf($txt['error_string_too_long'], $context['search_string_limit']); + } + + // Change non-word characters into spaces. + $stripped_query = 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' : ''), ' ', $search_params['search']); + + // Make the query lower case. It's gonna be case insensitive anyway. + $stripped_query = un_htmlspecialchars($smcFunc['strtolower']($stripped_query)); + + // This (hidden) setting will do fulltext searching in the most basic way. + if (!empty($modSettings['search_simple_fulltext'])) + $stripped_query = strtr($stripped_query, array('"' => '')); + + $no_regexp = preg_match('~&#(?:\d{1,7}|x[0-9a-fA-F]{1,6});~', $stripped_query) === 1; + + // Extract phrase parts first (e.g. some words "this is a phrase" some more words.) + preg_match_all('/(?:^|\s)([-]?)"([^"]+)"(?:$|\s)/', $stripped_query, $matches, PREG_PATTERN_ORDER); + $phraseArray = $matches[2]; + + // Remove the phrase parts and extract the words. + $wordArray = explode(' ', preg_replace('~(?:^|\s)(?:[-]?)"(?:[^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), ' ', $search_params['search'])); + + // A minus sign in front of a word excludes the word.... so... + $excludedWords = array(); + $excludedIndexWords = array(); + $excludedSubjectWords = array(); + $excludedPhrases = array(); + + // .. first, we check for things like -"some words", but not "-some words". + foreach ($matches[1] as $index => $word) + if ($word === '-') + { + if (($word = trim($phraseArray[$index], '-_\' ')) !== '' && !in_array($word, $blacklisted_words)) + $excludedWords[] = $word; + unset($phraseArray[$index]); + } + + // Now we look for -test, etc.... normaller. + foreach ($wordArray as $index => $word) + if (strpos(trim($word), '-') === 0) + { + if (($word = trim($word, '-_\' ')) !== '' && !in_array($word, $blacklisted_words)) + $excludedWords[] = $word; + unset($wordArray[$index]); + } + + // The remaining words and phrases are all included. + $searchArray = array_merge($phraseArray, $wordArray); + + // Trim everything and make sure there are no words that are the same. + foreach ($searchArray as $index => $value) + { + // Skip anything practically empty. + if (($searchArray[$index] = trim($value, '-_\' ')) === '') + unset($searchArray[$index]); + // Skip blacklisted words. Make sure to note we skipped them in case we end up with nothing. + elseif (in_array($searchArray[$index], $blacklisted_words)) + { + $foundBlackListedWords = true; + unset($searchArray[$index]); + } + // Don't allow very, very short words. + elseif ($smcFunc['strlen']($value) < 2) + { + $context['search_errors']['search_string_small_words'] = true; + unset($searchArray[$index]); + } + else + $searchArray[$index] = $searchArray[$index]; + } + $searchArray = array_slice(array_unique($searchArray), 0, 10); + + // Create an array of replacements for highlighting. + $context['mark'] = array(); + foreach ($searchArray as $word) + $context['mark'][$word] = '' . $word . ''; + + // Initialize two arrays storing the words that have to be searched for. + $orParts = array(); + $searchWords = array(); + + // Make sure at least one word is being searched for. + if (empty($searchArray)) + $context['search_errors']['invalid_search_string' . (!empty($foundBlackListedWords) ? '_blacklist' : '')] = true; + // All words/sentences must match. + elseif (empty($search_params['searchtype'])) + $orParts[0] = $searchArray; + // Any word/sentence must match. + else + foreach ($searchArray as $index => $value) + $orParts[$index] = array($value); + + // Don't allow duplicate error messages if one string is too short. + if (isset($context['search_errors']['search_string_small_words'], $context['search_errors']['invalid_search_string'])) + unset($context['search_errors']['invalid_search_string']); + // Make sure the excluded words are in all or-branches. + foreach ($orParts as $orIndex => $andParts) + foreach ($excludedWords as $word) + $orParts[$orIndex][] = $word; + + // Determine the or-branches and the fulltext search words. + foreach ($orParts as $orIndex => $andParts) + { + $searchWords[$orIndex] = array( + 'indexed_words' => array(), + 'words' => array(), + 'subject_words' => array(), + 'all_words' => array(), + ); + + // Sort the indexed words (large words -> small words -> excluded words). + if ($searchAPI->supportsMethod('searchSort')) + usort($orParts[$orIndex], 'searchSort'); + + foreach ($orParts[$orIndex] as $word) + { + $is_excluded = in_array($word, $excludedWords); + + $searchWords[$orIndex]['all_words'][] = $word; + + $subjectWords = text2words($word); + if (!$is_excluded || count($subjectWords) === 1) + { + $searchWords[$orIndex]['subject_words'] = array_merge($searchWords[$orIndex]['subject_words'], $subjectWords); + if ($is_excluded) + $excludedSubjectWords = array_merge($excludedSubjectWords, $subjectWords); + } + else + $excludedPhrases[] = $word; + + // Have we got indexes to prepare? + if ($searchAPI->supportsMethod('prepareIndexes')) + $searchAPI->prepareIndexes($word, $searchWords[$orIndex], $excludedIndexWords, $is_excluded); + } + + // Search_force_index requires all AND parts to have at least one fulltext word. + if (!empty($modSettings['search_force_index']) && empty($searchWords[$orIndex]['indexed_words'])) + { + $context['search_errors']['query_not_specific_enough'] = true; + break; + } + elseif ($search_params['subject_only'] && empty($searchWords[$orIndex]['subject_words']) && empty($excludedSubjectWords)) + { + $context['search_errors']['query_not_specific_enough'] = true; + break; + } + + // Make sure we aren't searching for too many indexed words. + else + { + $searchWords[$orIndex]['indexed_words'] = array_slice($searchWords[$orIndex]['indexed_words'], 0, 7); + $searchWords[$orIndex]['subject_words'] = array_slice($searchWords[$orIndex]['subject_words'], 0, 7); + } + } + + // *** Spell checking + $context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new'); + if ($context['show_spellchecking']) + { + // Windows fix. + ob_start(); + $old = error_reporting(0); + + pspell_new('en'); + $pspell_link = pspell_new($txt['lang_dictionary'], $txt['lang_spelling'], '', strtr($txt['lang_character_set'], array('iso-' => 'iso', 'ISO-' => 'iso')), PSPELL_FAST | PSPELL_RUN_TOGETHER); + + if (!$pspell_link) + $pspell_link = pspell_new('en', '', '', '', PSPELL_FAST | PSPELL_RUN_TOGETHER); + + error_reporting($old); + ob_end_clean(); + + $did_you_mean = array('search' => array(), 'display' => array()); + $found_misspelling = false; + foreach ($searchArray as $word) + { + if (empty($pspell_link)) + continue; + + $word = $word; + // Don't check phrases. + if (preg_match('~^\w+$~', $word) === 0) + { + $did_you_mean['search'][] = '"' . $word . '"'; + $did_you_mean['display'][] = '"' . $smcFunc['htmlspecialchars']($word) . '"'; + continue; + } + // For some strange reason spell check can crash PHP on decimals. + elseif (preg_match('~\d~', $word) === 1) + { + $did_you_mean['search'][] = $word; + $did_you_mean['display'][] = $smcFunc['htmlspecialchars']($word); + continue; + } + elseif (pspell_check($pspell_link, $word)) + { + $did_you_mean['search'][] = $word; + $did_you_mean['display'][] = $smcFunc['htmlspecialchars']($word); + continue; + } + + $suggestions = pspell_suggest($pspell_link, $word); + foreach ($suggestions as $i => $s) + { + // Search is case insensitive. + if ($smcFunc['strtolower']($s) == $smcFunc['strtolower']($word)) + unset($suggestions[$i]); + // Plus, don't suggest something the user thinks is rude! + elseif ($suggestions[$i] != censorText($s)) + unset($suggestions[$i]); + } + + // Anything found? If so, correct it! + if (!empty($suggestions)) + { + $suggestions = array_values($suggestions); + $did_you_mean['search'][] = $suggestions[0]; + $did_you_mean['display'][] = '' . $smcFunc['htmlspecialchars']($suggestions[0]) . ''; + $found_misspelling = true; + } + else + { + $did_you_mean['search'][] = $word; + $did_you_mean['display'][] = $smcFunc['htmlspecialchars']($word); + } + } + + if ($found_misspelling) + { + // Don't spell check excluded words, but add them still... + $temp_excluded = array('search' => array(), 'display' => array()); + foreach ($excludedWords as $word) + { + $word = $word; + + if (preg_match('~^\w+$~', $word) == 0) + { + $temp_excluded['search'][] = '-"' . $word . '"'; + $temp_excluded['display'][] = '-"' . $smcFunc['htmlspecialchars']($word) . '"'; + } + else + { + $temp_excluded['search'][] = '-' . $word; + $temp_excluded['display'][] = '-' . $smcFunc['htmlspecialchars']($word); + } + } + + $did_you_mean['search'] = array_merge($did_you_mean['search'], $temp_excluded['search']); + $did_you_mean['display'] = array_merge($did_you_mean['display'], $temp_excluded['display']); + + $temp_params = $search_params; + $temp_params['search'] = implode(' ', $did_you_mean['search']); + if (isset($temp_params['brd'])) + $temp_params['brd'] = implode(',', $temp_params['brd']); + $context['params'] = array(); + foreach ($temp_params as $k => $v) + $context['did_you_mean_params'][] = $k . '|\'|' . $v; + $context['did_you_mean_params'] = base64_encode(implode('|"|', $context['did_you_mean_params'])); + $context['did_you_mean'] = implode(' ', $did_you_mean['display']); + } + } + + // Let the user adjust the search query, should they wish? + $context['search_params'] = $search_params; + if (isset($context['search_params']['search'])) + $context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']); + if (isset($context['search_params']['userspec'])) + $context['search_params']['userspec'] = $smcFunc['htmlspecialchars']($context['search_params']['userspec']); + + // Do we have captcha enabled? + if ($user_info['is_guest'] && !empty($modSettings['search_enable_captcha']) && empty($_SESSION['ss_vv_passed']) && (empty($_SESSION['last_ss']) || $_SESSION['last_ss'] != $search_params['search'])) + { + // If we come from another search box tone down the error... + if (!isset($_REQUEST['search_vv'])) + $context['search_errors']['need_verification_code'] = true; + else + { + require_once($sourcedir . '/Subs-Editor.php'); + $verificationOptions = array( + 'id' => 'search', + ); + $context['require_verification'] = create_control_verification($verificationOptions, true); + + if (is_array($context['require_verification'])) + { + foreach ($context['require_verification'] as $error) + $context['search_errors'][$error] = true; + } + // Don't keep asking for it - they've proven themselves worthy. + else + $_SESSION['ss_vv_passed'] = true; + } + } + + // *** Encode all search params + + // All search params have been checked, let's compile them to a single string... made less simple by PHP 4.3.9 and below. + $temp_params = $search_params; + if (isset($temp_params['brd'])) + $temp_params['brd'] = implode(',', $temp_params['brd']); + $context['params'] = array(); + foreach ($temp_params as $k => $v) + $context['params'][] = $k . '|\'|' . $v; + + if (!empty($context['params'])) + { + // Due to old IE's 2083 character limit, we have to compress long search strings + $params = @gzcompress(implode('|"|', $context['params'])); + // Gzcompress failed, use try non-gz + if (empty($params)) + $params = implode('|"|', $context['params']); + // Base64 encode, then replace +/= with uri safe ones that can be reverted + $context['params'] = str_replace(array('+', '/', '='), array('-', '_', '.'), base64_encode($params)); + } + + // ... and add the links to the link tree. + $context['linktree'][] = array( + 'url' => $scripturl . '?action=search;params=' . $context['params'], + 'name' => $txt['search'] + ); + $context['linktree'][] = array( + 'url' => $scripturl . '?action=search2;params=' . $context['params'], + 'name' => $txt['search_results'] + ); + + // *** A last error check + + // One or more search errors? Go back to the first search screen. + if (!empty($context['search_errors'])) + { + $_REQUEST['params'] = $context['params']; + return PlushSearch1(); + } + + // Spam me not, Spam-a-lot? + if (empty($_SESSION['last_ss']) || $_SESSION['last_ss'] != $search_params['search']) + spamProtection('search'); + // Store the last search string to allow pages of results to be browsed. + $_SESSION['last_ss'] = $search_params['search']; + + // *** Reserve an ID for caching the search results. + $query_params = array_merge($search_params, array( + 'min_msg_id' => isset($minMsgID) ? (int) $minMsgID : 0, + 'max_msg_id' => isset($maxMsgID) ? (int) $maxMsgID : 0, + 'memberlist' => !empty($memberlist) ? $memberlist : array(), + )); + + // Can this search rely on the API given the parameters? + if ($searchAPI->supportsMethod('searchQuery', $query_params)) + { + $participants = array(); + $searchArray = array(); + + $num_results = $searchAPI->searchQuery($query_params, $searchWords, $excludedIndexWords, $participants, $searchArray); + } + + // Update the cache if the current search term is not yet cached. + else + { + $update_cache = empty($_SESSION['search_cache']) || ($_SESSION['search_cache']['params'] != $context['params']); + if ($update_cache) + { + // Increase the pointer... + $modSettings['search_pointer'] = empty($modSettings['search_pointer']) ? 0 : (int) $modSettings['search_pointer']; + // ...and store it right off. + updateSettings(array('search_pointer' => $modSettings['search_pointer'] >= 255 ? 0 : $modSettings['search_pointer'] + 1)); + // As long as you don't change the parameters, the cache result is yours. + $_SESSION['search_cache'] = array( + 'id_search' => $modSettings['search_pointer'], + 'num_results' => -1, + 'params' => $context['params'], + ); + + // Clear the previous cache of the final results cache. + $smcFunc['db_search_query']('delete_log_search_results', ' + DELETE FROM {db_prefix}log_search_results + WHERE id_search = {int:search_id}', + array( + 'search_id' => $_SESSION['search_cache']['id_search'], + ) + ); + + if ($search_params['subject_only']) + { + // We do this to try and avoid duplicate keys on databases not supporting INSERT IGNORE. + $inserts = array(); + foreach ($searchWords as $orIndex => $words) + { + $subject_query_params = array(); + $subject_query = array( + 'from' => '{db_prefix}topics AS t', + 'inner_join' => array(), + 'left_join' => array(), + 'where' => array(), + ); + + if ($modSettings['postmod_active']) + $subject_query['where'][] = 't.approved = {int:is_approved}'; + + $numTables = 0; + $prev_join = 0; + $numSubjectResults = 0; + foreach ($words['subject_words'] as $subjectWord) + { + $numTables++; + if (in_array($subjectWord, $excludedSubjectWords)) + { + $subject_query['left_join'][] = '{db_prefix}log_search_subjects AS subj' . $numTables . ' ON (subj' . $numTables . '.word ' . (empty($modSettings['search_match_words']) ? 'LIKE {string:subject_words_' . $numTables . '_wild}' : '= {string:subject_words_' . $numTables . '}') . ' AND subj' . $numTables . '.id_topic = t.id_topic)'; + $subject_query['where'][] = '(subj' . $numTables . '.word IS NULL)'; + } + else + { + $subject_query['inner_join'][] = '{db_prefix}log_search_subjects AS subj' . $numTables . ' ON (subj' . $numTables . '.id_topic = ' . ($prev_join === 0 ? 't' : 'subj' . $prev_join) . '.id_topic)'; + $subject_query['where'][] = 'subj' . $numTables . '.word ' . (empty($modSettings['search_match_words']) ? 'LIKE {string:subject_words_' . $numTables . '_wild}' : '= {string:subject_words_' . $numTables . '}'); + $prev_join = $numTables; + } + $subject_query_params['subject_words_' . $numTables] = $subjectWord; + $subject_query_params['subject_words_' . $numTables . '_wild'] = '%' . $subjectWord . '%'; + } + + if (!empty($userQuery)) + { + if ($subject_query['from'] != '{db_prefix}messages AS m') + { + $subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_topic = t.id_topic)'; + } + $subject_query['where'][] = $userQuery; + } + if (!empty($search_params['topic'])) + $subject_query['where'][] = 't.id_topic = ' . $search_params['topic']; + if (!empty($minMsgID)) + $subject_query['where'][] = 't.id_first_msg >= ' . $minMsgID; + if (!empty($maxMsgID)) + $subject_query['where'][] = 't.id_last_msg <= ' . $maxMsgID; + if (!empty($boardQuery)) + $subject_query['where'][] = 't.id_board ' . $boardQuery; + if (!empty($excludedPhrases)) + { + if ($subject_query['from'] != '{db_prefix}messages AS m') + { + $subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)'; + } + $count = 0; + foreach ($excludedPhrases as $phrase) + { + $subject_query['where'][] = 'm.subject NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:excluded_phrases_' . $count . '}'; + $subject_query_params['excluded_phrases_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($phrase, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $phrase), '\\\'') . '[[:>:]]'; + } + } + + $ignoreRequest = $smcFunc['db_search_query']('insert_log_search_results_subject', + ($smcFunc['db_support_ignore'] ? ' + INSERT IGNORE INTO {db_prefix}log_search_results + (id_search, id_topic, relevance, id_msg, num_matches)' : '') . ' + SELECT + {int:id_search}, + t.id_topic, + 1000 * ( + {int:weight_frequency} / (t.num_replies + 1) + + {int:weight_age} * CASE WHEN t.id_first_msg < {int:min_msg} THEN 0 ELSE (t.id_first_msg - {int:min_msg}) / {int:recent_message} END + + {int:weight_length} * CASE WHEN t.num_replies < {int:huge_topic_posts} THEN t.num_replies / {int:huge_topic_posts} ELSE 1 END + + {int:weight_subject} + + {int:weight_sticky} * t.is_sticky + ) / {int:weight_total} AS relevance, + ' . (empty($userQuery) ? 't.id_first_msg' : 'm.id_msg') . ', + 1 + FROM ' . $subject_query['from'] . (empty($subject_query['inner_join']) ? '' : ' + INNER JOIN ' . implode(' + INNER JOIN ', $subject_query['inner_join'])) . (empty($subject_query['left_join']) ? '' : ' + LEFT JOIN ' . implode(' + LEFT JOIN ', $subject_query['left_join'])) . ' + WHERE ' . implode(' + AND ', $subject_query['where']) . (empty($modSettings['search_max_results']) ? '' : ' + LIMIT ' . ($modSettings['search_max_results'] - $numSubjectResults)), + array_merge($subject_query_params, array( + 'id_search' => $_SESSION['search_cache']['id_search'], + 'weight_age' => $weight['age'], + 'weight_frequency' => $weight['frequency'], + 'weight_length' => $weight['length'], + 'weight_sticky' => $weight['sticky'], + 'weight_subject' => $weight['subject'], + 'weight_total' => $weight_total, + 'min_msg' => $minMsg, + 'recent_message' => $recentMsg, + 'huge_topic_posts' => $humungousTopicPosts, + 'is_approved' => 1, + )) + ); + + // If the database doesn't support IGNORE to make this fast we need to do some tracking. + if (!$smcFunc['db_support_ignore']) + { + while ($row = $smcFunc['db_fetch_row']($ignoreRequest)) + { + // No duplicates! + if (isset($inserts[$row[1]])) + continue; + + foreach ($row as $key => $value) + $inserts[$row[1]][] = (int) $row[$key]; + } + $smcFunc['db_free_result']($ignoreRequest); + $numSubjectResults = count($inserts); + } + else + $numSubjectResults += $smcFunc['db_affected_rows'](); + + if (!empty($modSettings['search_max_results']) && $numSubjectResults >= $modSettings['search_max_results']) + break; + } + + // If there's data to be inserted for non-IGNORE databases do it here! + if (!empty($inserts)) + { + $smcFunc['db_insert']('', + '{db_prefix}log_search_results', + array('id_search' => 'int', 'id_topic' => 'int', 'relevance' => 'int', 'id_msg' => 'int', 'num_matches' => 'int'), + $inserts, + array('id_search', 'id_topic') + ); + } + + $_SESSION['search_cache']['num_results'] = $numSubjectResults; + } + else + { + $main_query = array( + 'select' => array( + 'id_search' => $_SESSION['search_cache']['id_search'], + 'relevance' => '0', + ), + 'weights' => array(), + 'from' => '{db_prefix}topics AS t', + 'inner_join' => array( + '{db_prefix}messages AS m ON (m.id_topic = t.id_topic)' + ), + 'left_join' => array(), + 'where' => array(), + 'group_by' => array(), + 'parameters' => array( + 'min_msg' => $minMsg, + 'recent_message' => $recentMsg, + 'huge_topic_posts' => $humungousTopicPosts, + 'is_approved' => 1, + ), + ); + + if (empty($search_params['topic']) && empty($search_params['show_complete'])) + { + $main_query['select']['id_topic'] = 't.id_topic'; + $main_query['select']['id_msg'] = 'MAX(m.id_msg) AS id_msg'; + $main_query['select']['num_matches'] = 'COUNT(*) AS num_matches'; + + $main_query['weights'] = array( + 'frequency' => 'COUNT(*) / (MAX(t.num_replies) + 1)', + 'age' => 'CASE WHEN MAX(m.id_msg) < {int:min_msg} THEN 0 ELSE (MAX(m.id_msg) - {int:min_msg}) / {int:recent_message} END', + 'length' => 'CASE WHEN MAX(t.num_replies) < {int:huge_topic_posts} THEN MAX(t.num_replies) / {int:huge_topic_posts} ELSE 1 END', + 'subject' => '0', + 'first_message' => 'CASE WHEN MIN(m.id_msg) = MAX(t.id_first_msg) THEN 1 ELSE 0 END', + 'sticky' => 'MAX(t.is_sticky)', + ); + + $main_query['group_by'][] = 't.id_topic'; + } + else + { + // This is outrageous! + $main_query['select']['id_topic'] = 'm.id_msg AS id_topic'; + $main_query['select']['id_msg'] = 'm.id_msg'; + $main_query['select']['num_matches'] = '1 AS num_matches'; + + $main_query['weights'] = array( + 'age' => '((m.id_msg - t.id_first_msg) / CASE WHEN t.id_last_msg = t.id_first_msg THEN 1 ELSE t.id_last_msg - t.id_first_msg END)', + 'first_message' => 'CASE WHEN m.id_msg = t.id_first_msg THEN 1 ELSE 0 END', + ); + + if (!empty($search_params['topic'])) + { + $main_query['where'][] = 't.id_topic = {int:topic}'; + $main_query['parameters']['topic'] = $search_params['topic']; + } + if (!empty($search_params['show_complete'])) + $main_query['group_by'][] = 'm.id_msg, t.id_first_msg, t.id_last_msg'; + } + + // *** Get the subject results. + $numSubjectResults = 0; + if (empty($search_params['topic'])) + { + $inserts = array(); + // Create a temporary table to store some preliminary results in. + $smcFunc['db_search_query']('drop_tmp_log_search_topics', ' + DROP TABLE IF EXISTS {db_prefix}tmp_log_search_topics', + array( + 'db_error_skip' => true, + ) + ); + $createTemporary = $smcFunc['db_search_query']('create_tmp_log_search_topics', ' + CREATE TEMPORARY TABLE {db_prefix}tmp_log_search_topics ( + id_topic mediumint(8) unsigned NOT NULL default {string:string_zero}, + PRIMARY KEY (id_topic) + ) ENGINE=MEMORY', + array( + 'string_zero' => '0', + 'db_error_skip' => true, + ) + ) !== false; + + // Clean up some previous cache. + if (!$createTemporary) + $smcFunc['db_search_query']('delete_log_search_topics', ' + DELETE FROM {db_prefix}log_search_topics + WHERE id_search = {int:search_id}', + array( + 'search_id' => $_SESSION['search_cache']['id_search'], + ) + ); + + foreach ($searchWords as $orIndex => $words) + { + $subject_query = array( + 'from' => '{db_prefix}topics AS t', + 'inner_join' => array(), + 'left_join' => array(), + 'where' => array(), + 'params' => array(), + ); + + $numTables = 0; + $prev_join = 0; + $count = 0; + foreach ($words['subject_words'] as $subjectWord) + { + $numTables++; + if (in_array($subjectWord, $excludedSubjectWords)) + { + if ($subject_query['from'] != '{db_prefix}messages AS m') + { + $subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)'; + } + $subject_query['left_join'][] = '{db_prefix}log_search_subjects AS subj' . $numTables . ' ON (subj' . $numTables . '.word ' . (empty($modSettings['search_match_words']) ? 'LIKE {string:subject_not_' . $count . '}' : '= {string:subject_not_' . $count . '}') . ' AND subj' . $numTables . '.id_topic = t.id_topic)'; + $subject_query['params']['subject_not_' . $count] = empty($modSettings['search_match_words']) ? '%' . $subjectWord . '%' : $subjectWord; + + $subject_query['where'][] = '(subj' . $numTables . '.word IS NULL)'; + $subject_query['where'][] = 'm.body NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:body_not_' . $count . '}'; + $subject_query['params']['body_not_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($subjectWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $subjectWord), '\\\'') . '[[:>:]]'; + } + else + { + $subject_query['inner_join'][] = '{db_prefix}log_search_subjects AS subj' . $numTables . ' ON (subj' . $numTables . '.id_topic = ' . ($prev_join === 0 ? 't' : 'subj' . $prev_join) . '.id_topic)'; + $subject_query['where'][] = 'subj' . $numTables . '.word LIKE {string:subject_like_' . $count . '}'; + $subject_query['params']['subject_like_' . $count++] = empty($modSettings['search_match_words']) ? '%' . $subjectWord . '%' : $subjectWord; + $prev_join = $numTables; + } + } + + if (!empty($userQuery)) + { + if ($subject_query['from'] != '{db_prefix}messages AS m') + { + $subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)'; + } + $subject_query['where'][] = '{raw:user_query}'; + $subject_query['params']['user_query'] = $userQuery; + } + if (!empty($search_params['topic'])) + { + $subject_query['where'][] = 't.id_topic = {int:topic}'; + $subject_query['params']['topic'] = $search_params['topic']; + } + if (!empty($minMsgID)) + { + $subject_query['where'][] = 't.id_first_msg >= {int:min_msg_id}'; + $subject_query['params']['min_msg_id'] = $minMsgID; + } + if (!empty($maxMsgID)) + { + $subject_query['where'][] = 't.id_last_msg <= {int:max_msg_id}'; + $subject_query['params']['max_msg_id'] = $maxMsgID; + } + if (!empty($boardQuery)) + { + $subject_query['where'][] = 't.id_board {raw:board_query}'; + $subject_query['params']['board_query'] = $boardQuery; + } + if (!empty($excludedPhrases)) + { + if ($subject_query['from'] != '{db_prefix}messages AS m') + { + $subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)'; + } + $count = 0; + foreach ($excludedPhrases as $phrase) + { + $subject_query['where'][] = 'm.subject NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:exclude_phrase_' . $count . '}'; + $subject_query['where'][] = 'm.body NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:exclude_phrase_' . $count . '}'; + $subject_query['params']['exclude_phrase_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($phrase, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $phrase), '\\\'') . '[[:>:]]'; + } + } + + // Nothing to search for? + if (empty($subject_query['where'])) + continue; + + $ignoreRequest = $smcFunc['db_search_query']('insert_log_search_topics', ($smcFunc['db_support_ignore'] ? ( ' + INSERT IGNORE INTO {db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics + (' . ($createTemporary ? '' : 'id_search, ') . 'id_topic)') : '') . ' + SELECT ' . ($createTemporary ? '' : $_SESSION['search_cache']['id_search'] . ', ') . 't.id_topic + FROM ' . $subject_query['from'] . (empty($subject_query['inner_join']) ? '' : ' + INNER JOIN ' . implode(' + INNER JOIN ', $subject_query['inner_join'])) . (empty($subject_query['left_join']) ? '' : ' + LEFT JOIN ' . implode(' + LEFT JOIN ', $subject_query['left_join'])) . ' + WHERE ' . implode(' + AND ', $subject_query['where']) . (empty($modSettings['search_max_results']) ? '' : ' + LIMIT ' . ($modSettings['search_max_results'] - $numSubjectResults)), + $subject_query['params'] + ); + // Don't do INSERT IGNORE? Manually fix this up! + if (!$smcFunc['db_support_ignore']) + { + while ($row = $smcFunc['db_fetch_row']($ignoreRequest)) + { + $ind = $createTemporary ? 0 : 1; + // No duplicates! + if (isset($inserts[$row[$ind]])) + continue; + + $inserts[$row[$ind]] = $row; + } + $smcFunc['db_free_result']($ignoreRequest); + $numSubjectResults = count($inserts); + } + else + $numSubjectResults += $smcFunc['db_affected_rows'](); + + if (!empty($modSettings['search_max_results']) && $numSubjectResults >= $modSettings['search_max_results']) + break; + } + + // Got some non-MySQL data to plonk in? + if (!empty($inserts)) + { + $smcFunc['db_insert']('', + ('{db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics'), + $createTemporary ? array('id_topic' => 'int') : array('id_search' => 'int', 'id_topic' => 'int'), + $inserts, + $createTemporary ? array('id_topic') : array('id_search', 'id_topic') + ); + } + + if ($numSubjectResults !== 0) + { + $main_query['weights']['subject'] = 'CASE WHEN MAX(lst.id_topic) IS NULL THEN 0 ELSE 1 END'; + $main_query['left_join'][] = '{db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics AS lst ON (' . ($createTemporary ? '' : 'lst.id_search = {int:id_search} AND ') . 'lst.id_topic = t.id_topic)'; + if (!$createTemporary) + $main_query['parameters']['id_search'] = $_SESSION['search_cache']['id_search']; + } + } + + $indexedResults = 0; + // We building an index? + if ($searchAPI->supportsMethod('indexedWordQuery', $query_params)) + { + $inserts = array(); + $smcFunc['db_search_query']('drop_tmp_log_search_messages', ' + DROP TABLE IF EXISTS {db_prefix}tmp_log_search_messages', + array( + 'db_error_skip' => true, + ) + ); + + $createTemporary = $smcFunc['db_search_query']('create_tmp_log_search_messages', ' + CREATE TEMPORARY TABLE {db_prefix}tmp_log_search_messages ( + id_msg int(10) unsigned NOT NULL default {string:string_zero}, + PRIMARY KEY (id_msg) + ) ENGINE=MEMORY', + array( + 'string_zero' => '0', + 'db_error_skip' => true, + ) + ) !== false; + + // Clear, all clear! + if (!$createTemporary) + $smcFunc['db_search_query']('delete_log_search_messages', ' + DELETE FROM {db_prefix}log_search_messages + WHERE id_search = {int:id_search}', + array( + 'id_search' => $_SESSION['search_cache']['id_search'], + ) + ); + + foreach ($searchWords as $orIndex => $words) + { + // Search for this word, assuming we have some words! + if (!empty($words['indexed_words'])) + { + // Variables required for the search. + $search_data = array( + 'insert_into' => ($createTemporary ? 'tmp_' : '') . 'log_search_messages', + 'no_regexp' => $no_regexp, + 'max_results' => $maxMessageResults, + 'indexed_results' => $indexedResults, + 'params' => array( + 'id_search' => !$createTemporary ? $_SESSION['search_cache']['id_search'] : 0, + 'excluded_words' => $excludedWords, + 'user_query' => !empty($userQuery) ? $userQuery : '', + 'board_query' => !empty($boardQuery) ? $boardQuery : '', + 'topic' => !empty($search_params['topic']) ? $search_params['topic'] : 0, + 'min_msg_id' => !empty($minMsgID) ? $minMsgID : 0, + 'max_msg_id' => !empty($maxMsgID) ? $maxMsgID : 0, + 'excluded_phrases' => !empty($excludedPhrases) ? $excludedPhrases : array(), + 'excluded_index_words' => !empty($excludedIndexWords) ? $excludedIndexWords : array(), + 'excluded_subject_words' => !empty($excludedSubjectWords) ? $excludedSubjectWords : array(), + ), + ); + + $ignoreRequest = $searchAPI->indexedWordQuery($words, $search_data); + + if (!$smcFunc['db_support_ignore']) + { + while ($row = $smcFunc['db_fetch_row']($ignoreRequest)) + { + // No duplicates! + if (isset($inserts[$row[0]])) + continue; + + $inserts[$row[0]] = $row; + } + $smcFunc['db_free_result']($ignoreRequest); + $indexedResults = count($inserts); + } + else + $indexedResults += $smcFunc['db_affected_rows'](); + + if (!empty($maxMessageResults) && $indexedResults >= $maxMessageResults) + break; + } + } + + // More non-MySQL stuff needed? + if (!empty($inserts)) + { + $smcFunc['db_insert']('', + '{db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_messages', + $createTemporary ? array('id_msg' => 'int') : array('id_msg' => 'int', 'id_search' => 'int'), + $inserts, + $createTemporary ? array('id_msg') : array('id_msg', 'id_search') + ); + } + + if (empty($indexedResults) && empty($numSubjectResults) && !empty($modSettings['search_force_index'])) + { + $context['search_errors']['query_not_specific_enough'] = true; + $_REQUEST['params'] = $context['params']; + return PlushSearch1(); + } + elseif (!empty($indexedResults)) + { + $main_query['inner_join'][] = '{db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_messages AS lsm ON (lsm.id_msg = m.id_msg)'; + if (!$createTemporary) + { + $main_query['where'][] = 'lsm.id_search = {int:id_search}'; + $main_query['parameters']['id_search'] = $_SESSION['search_cache']['id_search']; + } + } + } + + // Not using an index? All conditions have to be carried over. + else + { + $orWhere = array(); + $count = 0; + foreach ($searchWords as $orIndex => $words) + { + $where = array(); + foreach ($words['all_words'] as $regularWord) + { + $where[] = 'm.body' . (in_array($regularWord, $excludedWords) ? ' NOT' : '') . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:all_word_body_' . $count . '}'; + if (in_array($regularWord, $excludedWords)) + $where[] = 'm.subject NOT' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:all_word_body_' . $count . '}'; + $main_query['parameters']['all_word_body_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($regularWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $regularWord), '\\\'') . '[[:>:]]'; + } + if (!empty($where)) + $orWhere[] = count($where) > 1 ? '(' . implode(' AND ', $where) . ')' : $where[0]; + } + if (!empty($orWhere)) + $main_query['where'][] = count($orWhere) > 1 ? '(' . implode(' OR ', $orWhere) . ')' : $orWhere[0]; + + if (!empty($userQuery)) + { + $main_query['where'][] = '{raw:user_query}'; + $main_query['parameters']['user_query'] = $userQuery; + } + if (!empty($search_params['topic'])) + { + $main_query['where'][] = 'm.id_topic = {int:topic}'; + $main_query['parameters']['topic'] = $search_params['topic']; + } + if (!empty($minMsgID)) + { + $main_query['where'][] = 'm.id_msg >= {int:min_msg_id}'; + $main_query['parameters']['min_msg_id'] = $minMsgID; + } + if (!empty($maxMsgID)) + { + $main_query['where'][] = 'm.id_msg <= {int:max_msg_id}'; + $main_query['parameters']['max_msg_id'] = $maxMsgID; + } + if (!empty($boardQuery)) + { + $main_query['where'][] = 'm.id_board {raw:board_query}'; + $main_query['parameters']['board_query'] = $boardQuery; + } + } + + // Did we either get some indexed results, or otherwise did not do an indexed query? + if (!empty($indexedResults) || !$searchAPI->supportsMethod('indexedWordQuery', $query_params)) + { + $relevance = '1000 * ('; + $new_weight_total = 0; + foreach ($main_query['weights'] as $type => $value) + { + $relevance .= $weight[$type] . ' * ' . $value . ' + '; + $new_weight_total += $weight[$type]; + } + $main_query['select']['relevance'] = substr($relevance, 0, -3) . ') / ' . $new_weight_total . ' AS relevance'; + + $ignoreRequest = $smcFunc['db_search_query']('insert_log_search_results_no_index', ($smcFunc['db_support_ignore'] ? ( ' + INSERT IGNORE INTO ' . '{db_prefix}log_search_results + (' . implode(', ', array_keys($main_query['select'])) . ')') : '') . ' + SELECT + ' . implode(', + ', $main_query['select']) . ' + FROM ' . $main_query['from'] . (empty($main_query['inner_join']) ? '' : ' + INNER JOIN ' . implode(' + INNER JOIN ', $main_query['inner_join'])) . (empty($main_query['left_join']) ? '' : ' + LEFT JOIN ' . implode(' + LEFT JOIN ', $main_query['left_join'])) . (!empty($main_query['where']) ? ' + WHERE ' : '') . implode(' + AND ', $main_query['where']) . (empty($main_query['group_by']) ? '' : ' + GROUP BY ' . implode(', ', $main_query['group_by'])) . (empty($modSettings['search_max_results']) ? '' : ' + LIMIT ' . $modSettings['search_max_results']), + $main_query['parameters'] + ); + + // We love to handle non-good databases that don't support our ignore! + if (!$smcFunc['db_support_ignore']) + { + $inserts = array(); + while ($row = $smcFunc['db_fetch_row']($ignoreRequest)) + { + // No duplicates! + if (isset($inserts[$row[2]])) + continue; + + foreach ($row as $key => $value) + $inserts[$row[2]][] = (int) $row[$key]; + } + $smcFunc['db_free_result']($ignoreRequest); + + // Now put them in! + if (!empty($inserts)) + { + $query_columns = array(); + foreach ($main_query['select'] as $k => $v) + $query_columns[$k] = 'int'; + + $smcFunc['db_insert']('', + '{db_prefix}log_search_results', + $query_columns, + $inserts, + array('id_search', 'id_topic') + ); + } + $_SESSION['search_cache']['num_results'] += count($inserts); + } + else + $_SESSION['search_cache']['num_results'] = $smcFunc['db_affected_rows'](); + } + + // Insert subject-only matches. + if ($_SESSION['search_cache']['num_results'] < $modSettings['search_max_results'] && $numSubjectResults !== 0) + { + $usedIDs = array_flip(empty($inserts) ? array() : array_keys($inserts)); + $ignoreRequest = $smcFunc['db_search_query']('insert_log_search_results_sub_only', ($smcFunc['db_support_ignore'] ? ( ' + INSERT IGNORE INTO {db_prefix}log_search_results + (id_search, id_topic, relevance, id_msg, num_matches)') : '') . ' + SELECT + {int:id_search}, + t.id_topic, + 1000 * ( + {int:weight_frequency} / (t.num_replies + 1) + + {int:weight_age} * CASE WHEN t.id_first_msg < {int:min_msg} THEN 0 ELSE (t.id_first_msg - {int:min_msg}) / {int:recent_message} END + + {int:weight_length} * CASE WHEN t.num_replies < {int:huge_topic_posts} THEN t.num_replies / {int:huge_topic_posts} ELSE 1 END + + {int:weight_subject} + + {int:weight_sticky} * t.is_sticky + ) / {int:weight_total} AS relevance, + t.id_first_msg, + 1 + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics AS lst ON (lst.id_topic = t.id_topic)' + . ($createTemporary ? '' : 'WHERE lst.id_search = {int:id_search}') + . (empty($modSettings['search_max_results']) ? '' : ' + LIMIT ' . ($modSettings['search_max_results'] - $_SESSION['search_cache']['num_results'])), + array( + 'id_search' => $_SESSION['search_cache']['id_search'], + 'weight_age' => $weight['age'], + 'weight_frequency' => $weight['frequency'], + 'weight_length' => $weight['frequency'], + 'weight_sticky' => $weight['frequency'], + 'weight_subject' => $weight['frequency'], + 'weight_total' => $weight_total, + 'min_msg' => $minMsg, + 'recent_message' => $recentMsg, + 'huge_topic_posts' => $humungousTopicPosts, + ) + ); + // Once again need to do the inserts if the database don't support ignore! + if (!$smcFunc['db_support_ignore']) + { + $inserts = array(); + while ($row = $smcFunc['db_fetch_row']($ignoreRequest)) + { + // No duplicates! + if (isset($usedIDs[$row[1]])) + continue; + + $usedIDs[$row[1]] = true; + $inserts[] = $row; + } + $smcFunc['db_free_result']($ignoreRequest); + + // Now put them in! + if (!empty($inserts)) + { + $smcFunc['db_insert']('', + '{db_prefix}log_search_results', + array('id_search' => 'int', 'id_topic' => 'int', 'relevance' => 'float', 'id_msg' => 'int', 'num_matches' => 'int'), + $inserts, + array('id_search', 'id_topic') + ); + } + $_SESSION['search_cache']['num_results'] += count($inserts); + } + else + $_SESSION['search_cache']['num_results'] += $smcFunc['db_affected_rows'](); + } + else + $_SESSION['search_cache']['num_results'] = 0; + } + } + + // *** Retrieve the results to be shown on the page + $participants = array(); + $request = $smcFunc['db_search_query']('', ' + SELECT ' . (empty($search_params['topic']) ? 'lsr.id_topic' : $search_params['topic'] . ' AS id_topic') . ', lsr.id_msg, lsr.relevance, lsr.num_matches + FROM {db_prefix}log_search_results AS lsr' . ($search_params['sort'] == 'num_replies' ? ' + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = lsr.id_topic)' : '') . ' + WHERE lsr.id_search = {int:id_search} + ORDER BY ' . $search_params['sort'] . ' ' . $search_params['sort_dir'] . ' + LIMIT ' . (int) $_REQUEST['start'] . ', ' . $modSettings['search_results_per_page'], + array( + 'id_search' => $_SESSION['search_cache']['id_search'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $context['topics'][$row['id_msg']] = array( + 'relevance' => round($row['relevance'] / 10, 1) . '%', + 'num_matches' => $row['num_matches'], + 'matches' => array(), + ); + // By default they didn't participate in the topic! + $participants[$row['id_topic']] = false; + } + $smcFunc['db_free_result']($request); + + $num_results = $_SESSION['search_cache']['num_results']; + } + + if (!empty($context['topics'])) + { + // Create an array for the permissions. + $boards_can = array( + 'post_reply_own' => boardsAllowedTo('post_reply_own'), + 'post_reply_any' => boardsAllowedTo('post_reply_any'), + 'mark_any_notify' => boardsAllowedTo('mark_any_notify') + ); + + // How's about some quick moderation? + if (!empty($options['display_quick_mod'])) + { + $boards_can['lock_any'] = boardsAllowedTo('lock_any'); + $boards_can['lock_own'] = boardsAllowedTo('lock_own'); + $boards_can['make_sticky'] = boardsAllowedTo('make_sticky'); + $boards_can['move_any'] = boardsAllowedTo('move_any'); + $boards_can['move_own'] = boardsAllowedTo('move_own'); + $boards_can['remove_any'] = boardsAllowedTo('remove_any'); + $boards_can['remove_own'] = boardsAllowedTo('remove_own'); + $boards_can['merge_any'] = boardsAllowedTo('merge_any'); + + $context['can_lock'] = in_array(0, $boards_can['lock_any']); + $context['can_sticky'] = in_array(0, $boards_can['make_sticky']) && !empty($modSettings['enableStickyTopics']); + $context['can_move'] = in_array(0, $boards_can['move_any']); + $context['can_remove'] = in_array(0, $boards_can['remove_any']); + $context['can_merge'] = in_array(0, $boards_can['merge_any']); + } + + // What messages are we using? + $msg_list = array_keys($context['topics']); + + // Load the posters... + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}messages + WHERE id_member != {int:no_member} + AND id_msg IN ({array_int:message_list}) + LIMIT ' . count($context['topics']), + array( + 'message_list' => $msg_list, + 'no_member' => 0, + ) + ); + $posters = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $posters[] = $row['id_member']; + $smcFunc['db_free_result']($request); + + if (!empty($posters)) + loadMemberData(array_unique($posters)); + + // Get the messages out for the callback - select enough that it can be made to look just like Display. + $messages_request = $smcFunc['db_query']('', ' + SELECT + m.id_msg, m.subject, m.poster_name, m.poster_email, m.poster_time, m.id_member, + m.icon, m.poster_ip, m.body, m.smileys_enabled, m.modified_time, m.modified_name, + first_m.id_msg AS first_msg, first_m.subject AS first_subject, first_m.icon AS first_icon, first_m.poster_time AS first_poster_time, + first_mem.id_member AS first_member_id, IFNULL(first_mem.real_name, first_m.poster_name) AS first_member_name, + last_m.id_msg AS last_msg, last_m.poster_time AS last_poster_time, last_mem.id_member AS last_member_id, + IFNULL(last_mem.real_name, last_m.poster_name) AS last_member_name, last_m.icon AS last_icon, last_m.subject AS last_subject, + t.id_topic, t.is_sticky, t.locked, t.id_poll, t.num_replies, t.num_views, + b.id_board, b.name AS board_name, c.id_cat, c.name AS cat_name + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + INNER JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat) + INNER JOIN {db_prefix}messages AS first_m ON (first_m.id_msg = t.id_first_msg) + INNER JOIN {db_prefix}messages AS last_m ON (last_m.id_msg = t.id_last_msg) + LEFT JOIN {db_prefix}members AS first_mem ON (first_mem.id_member = first_m.id_member) + LEFT JOIN {db_prefix}members AS last_mem ON (last_mem.id_member = first_m.id_member) + WHERE m.id_msg IN ({array_int:message_list})' . ($modSettings['postmod_active'] ? ' + AND m.approved = {int:is_approved}' : '') . ' + ORDER BY FIND_IN_SET(m.id_msg, {string:message_list_in_set}) + LIMIT {int:limit}', + array( + 'message_list' => $msg_list, + 'is_approved' => 1, + 'message_list_in_set' => implode(',', $msg_list), + 'limit' => count($context['topics']), + ) + ); + + // If there are no results that means the things in the cache got deleted, so pretend we have no topics anymore. + if ($smcFunc['db_num_rows']($messages_request) == 0) + $context['topics'] = array(); + + // If we want to know who participated in what then load this now. + if (!empty($modSettings['enableParticipation']) && !$user_info['is_guest']) + { + $result = $smcFunc['db_query']('', ' + SELECT id_topic + FROM {db_prefix}messages + WHERE id_topic IN ({array_int:topic_list}) + AND id_member = {int:current_member} + GROUP BY id_topic + LIMIT ' . count($participants), + array( + 'current_member' => $user_info['id'], + 'topic_list' => array_keys($participants), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + $participants[$row['id_topic']] = true; + $smcFunc['db_free_result']($result); + } + } + + // Now that we know how many results to expect we can start calculating the page numbers. + $context['page_index'] = constructPageIndex($scripturl . '?action=search2;params=' . $context['params'], $_REQUEST['start'], $num_results, $modSettings['search_results_per_page'], false); + + // Consider the search complete! + if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2) + cache_put_data('search_start:' . ($user_info['is_guest'] ? $user_info['ip'] : $user_info['id']), null, 90); + + $context['key_words'] = &$searchArray; + + // Setup the default topic icons... for checking they exist and the like! + $stable_icons = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'moved', 'recycled', 'wireless', 'clip'); + $context['icon_sources'] = array(); + foreach ($stable_icons as $icon) + $context['icon_sources'][$icon] = 'images_url'; + + $context['sub_template'] = 'results'; + $context['page_title'] = $txt['search_results']; + $context['get_topics'] = 'prepareSearchContext'; + $context['can_send_pm'] = allowedTo('pm_send'); + + $context['jump_to'] = array( + 'label' => addslashes(un_htmlspecialchars($txt['jump_to'])), + 'board_name' => addslashes(un_htmlspecialchars($txt['select_destination'])), + ); +} + +// Callback to return messages - saves memory. +// !!! Fix this, update it, whatever... from Display.php mainly. +function prepareSearchContext($reset = false) +{ + global $txt, $modSettings, $scripturl, $user_info, $sourcedir; + global $memberContext, $context, $settings, $options, $messages_request; + global $boards_can, $participants, $smcFunc; + + // Remember which message this is. (ie. reply #83) + static $counter = null; + if ($counter == null || $reset) + $counter = $_REQUEST['start'] + 1; + + // If the query returned false, bail. + if ($messages_request == false) + return false; + + // Start from the beginning... + if ($reset) + return @$smcFunc['db_data_seek']($messages_request, 0); + + // Attempt to get the next message. + $message = $smcFunc['db_fetch_assoc']($messages_request); + if (!$message) + return false; + + // Can't have an empty subject can we? + $message['subject'] = $message['subject'] != '' ? $message['subject'] : $txt['no_subject']; + + $message['first_subject'] = $message['first_subject'] != '' ? $message['first_subject'] : $txt['no_subject']; + $message['last_subject'] = $message['last_subject'] != '' ? $message['last_subject'] : $txt['no_subject']; + + // If it couldn't load, or the user was a guest.... someday may be done with a guest table. + if (!loadMemberContext($message['id_member'])) + { + // Notice this information isn't used anywhere else.... *cough guest table cough*. + $memberContext[$message['id_member']]['name'] = $message['poster_name']; + $memberContext[$message['id_member']]['id'] = 0; + $memberContext[$message['id_member']]['group'] = $txt['guest_title']; + $memberContext[$message['id_member']]['link'] = $message['poster_name']; + $memberContext[$message['id_member']]['email'] = $message['poster_email']; + } + $memberContext[$message['id_member']]['ip'] = $message['poster_ip']; + + // Do the censor thang... + censorText($message['body']); + censorText($message['subject']); + + censorText($message['first_subject']); + censorText($message['last_subject']); + + // Shorten this message if necessary. + if ($context['compact']) + { + // Set the number of characters before and after the searched keyword. + $charLimit = 50; + + $message['body'] = strtr($message['body'], array("\n" => ' ', '
' => "\n")); + $message['body'] = parse_bbc($message['body'], $message['smileys_enabled'], $message['id_msg']); + $message['body'] = strip_tags(strtr($message['body'], array('' => '
', '' => '
')), '
'); + + if ($smcFunc['strlen']($message['body']) > $charLimit) + { + if (empty($context['key_words'])) + $message['body'] = $smcFunc['substr']($message['body'], 0, $charLimit) . '...'; + else + { + $matchString = ''; + $force_partial_word = false; + foreach ($context['key_words'] as $keyword) + { + $keyword = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', strtr($keyword, array('\\\'' => '\'', '&' => '&'))); + + if (preg_match('~[\'\.,/@%&;:(){}\[\]_\-+\\\\]$~', $keyword) != 0 || preg_match('~^[\'\.,/@%&;:(){}\[\]_\-+\\\\]~', $keyword) != 0) + $force_partial_word = true; + $matchString .= strtr(preg_quote($keyword, '/'), array('\*' => '.+?')) . '|'; + } + $matchString = substr($matchString, 0, -1); + + $message['body'] = un_htmlspecialchars(strtr($message['body'], array(' ' => ' ', '
' => "\n", '[' => '[', ']' => ']', ':' => ':', '@' => '@'))); + + if (empty($modSettings['search_method']) || $force_partial_word) + preg_match_all('/([^\s\W]{' . $charLimit . '}[\s\W]|[\s\W].{0,' . $charLimit . '}?|^)(' . $matchString . ')(.{0,' . $charLimit . '}[\s\W]|[^\s\W]{' . $charLimit . '})/is' . ($context['utf8'] ? 'u' : ''), $message['body'], $matches); + else + preg_match_all('/([^\s\W]{' . $charLimit . '}[\s\W]|[\s\W].{0,' . $charLimit . '}?[\s\W]|^)(' . $matchString . ')([\s\W].{0,' . $charLimit . '}[\s\W]|[\s\W][^\s\W]{' . $charLimit . '})/is' . ($context['utf8'] ? 'u' : ''), $message['body'], $matches); + + $message['body'] = ''; + foreach ($matches[0] as $index => $match) + { + $match = strtr(htmlspecialchars($match, ENT_QUOTES), array("\n" => ' ')); + $message['body'] .= '...... ' . $match . ' ......'; + } + } + + // Re-fix the international characters. + $message['body'] = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', $message['body']); + } + } + else + { + // Run BBC interpreter on the message. + $message['body'] = parse_bbc($message['body'], $message['smileys_enabled'], $message['id_msg']); + } + + // Make sure we don't end up with a practically empty message body. + $message['body'] = preg_replace('~^(?: )+$~', '', $message['body']); + + // Sadly, we need to check the icon ain't broke. + if (empty($modSettings['messageIconChecks_disable'])) + { + if (!isset($context['icon_sources'][$message['first_icon']])) + $context['icon_sources'][$message['first_icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['first_icon'] . '.gif') ? 'images_url' : 'default_images_url'; + if (!isset($context['icon_sources'][$message['last_icon']])) + $context['icon_sources'][$message['last_icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['last_icon'] . '.gif') ? 'images_url' : 'default_images_url'; + if (!isset($context['icon_sources'][$message['icon']])) + $context['icon_sources'][$message['icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['icon'] . '.gif') ? 'images_url' : 'default_images_url'; + } + else + { + if (!isset($context['icon_sources'][$message['first_icon']])) + $context['icon_sources'][$message['first_icon']] = 'images_url'; + if (!isset($context['icon_sources'][$message['last_icon']])) + $context['icon_sources'][$message['last_icon']] = 'images_url'; + if (!isset($context['icon_sources'][$message['icon']])) + $context['icon_sources'][$message['icon']] = 'images_url'; + } + + // Do we have quote tag enabled? + $quote_enabled = empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC'])); + + $output = array_merge($context['topics'][$message['id_msg']], array( + 'id' => $message['id_topic'], + 'is_sticky' => !empty($modSettings['enableStickyTopics']) && !empty($message['is_sticky']), + 'is_locked' => !empty($message['locked']), + 'is_poll' => $modSettings['pollMode'] == '1' && $message['id_poll'] > 0, + 'is_hot' => $message['num_replies'] >= $modSettings['hotTopicPosts'], + 'is_very_hot' => $message['num_replies'] >= $modSettings['hotTopicVeryPosts'], + 'posted_in' => !empty($participants[$message['id_topic']]), + 'views' => $message['num_views'], + 'replies' => $message['num_replies'], + 'can_reply' => in_array($message['id_board'], $boards_can['post_reply_any']) || in_array(0, $boards_can['post_reply_any']), + 'can_quote' => (in_array($message['id_board'], $boards_can['post_reply_any']) || in_array(0, $boards_can['post_reply_any'])) && $quote_enabled, + 'can_mark_notify' => in_array($message['id_board'], $boards_can['mark_any_notify']) || in_array(0, $boards_can['mark_any_notify']) && !$context['user']['is_guest'], + 'first_post' => array( + 'id' => $message['first_msg'], + 'time' => timeformat($message['first_poster_time']), + 'timestamp' => forum_time(true, $message['first_poster_time']), + 'subject' => $message['first_subject'], + 'href' => $scripturl . '?topic=' . $message['id_topic'] . '.0', + 'link' => '' . $message['first_subject'] . '', + 'icon' => $message['first_icon'], + 'icon_url' => $settings[$context['icon_sources'][$message['first_icon']]] . '/post/' . $message['first_icon'] . '.gif', + 'member' => array( + 'id' => $message['first_member_id'], + 'name' => $message['first_member_name'], + 'href' => !empty($message['first_member_id']) ? $scripturl . '?action=profile;u=' . $message['first_member_id'] : '', + 'link' => !empty($message['first_member_id']) ? '' . $message['first_member_name'] . '' : $message['first_member_name'] + ) + ), + 'last_post' => array( + 'id' => $message['last_msg'], + 'time' => timeformat($message['last_poster_time']), + 'timestamp' => forum_time(true, $message['last_poster_time']), + 'subject' => $message['last_subject'], + 'href' => $scripturl . '?topic=' . $message['id_topic'] . ($message['num_replies'] == 0 ? '.0' : '.msg' . $message['last_msg']) . '#msg' . $message['last_msg'], + 'link' => '' . $message['last_subject'] . '', + 'icon' => $message['last_icon'], + 'icon_url' => $settings[$context['icon_sources'][$message['last_icon']]] . '/post/' . $message['last_icon'] . '.gif', + 'member' => array( + 'id' => $message['last_member_id'], + 'name' => $message['last_member_name'], + 'href' => !empty($message['last_member_id']) ? $scripturl . '?action=profile;u=' . $message['last_member_id'] : '', + 'link' => !empty($message['last_member_id']) ? '' . $message['last_member_name'] . '' : $message['last_member_name'] + ) + ), + 'board' => array( + 'id' => $message['id_board'], + 'name' => $message['board_name'], + 'href' => $scripturl . '?board=' . $message['id_board'] . '.0', + 'link' => '' . $message['board_name'] . '' + ), + 'category' => array( + 'id' => $message['id_cat'], + 'name' => $message['cat_name'], + 'href' => $scripturl . '#c' . $message['id_cat'], + 'link' => '' . $message['cat_name'] . '' + ) + )); + determineTopicClass($output); + + if ($output['posted_in']) + $output['class'] = 'my_' . $output['class']; + + $body_highlighted = $message['body']; + $subject_highlighted = $message['subject']; + + if (!empty($options['display_quick_mod'])) + { + $started = $output['first_post']['member']['id'] == $user_info['id']; + + $output['quick_mod'] = array( + 'lock' => in_array(0, $boards_can['lock_any']) || in_array($output['board']['id'], $boards_can['lock_any']) || ($started && (in_array(0, $boards_can['lock_own']) || in_array($output['board']['id'], $boards_can['lock_own']))), + 'sticky' => (in_array(0, $boards_can['make_sticky']) || in_array($output['board']['id'], $boards_can['make_sticky'])) && !empty($modSettings['enableStickyTopics']), + 'move' => in_array(0, $boards_can['move_any']) || in_array($output['board']['id'], $boards_can['move_any']) || ($started && (in_array(0, $boards_can['move_own']) || in_array($output['board']['id'], $boards_can['move_own']))), + 'remove' => in_array(0, $boards_can['remove_any']) || in_array($output['board']['id'], $boards_can['remove_any']) || ($started && (in_array(0, $boards_can['remove_own']) || in_array($output['board']['id'], $boards_can['remove_own']))), + ); + + $context['can_lock'] |= $output['quick_mod']['lock']; + $context['can_sticky'] |= $output['quick_mod']['sticky']; + $context['can_move'] |= $output['quick_mod']['move']; + $context['can_remove'] |= $output['quick_mod']['remove']; + $context['can_merge'] |= in_array($output['board']['id'], $boards_can['merge_any']); + + // If we've found a message we can move, and we don't already have it, load the destinations. + if ($options['display_quick_mod'] == 1 && !isset($context['move_to_boards']) && $context['can_move']) + { + require_once($sourcedir . '/Subs-MessageIndex.php'); + $boardListOptions = array( + 'use_permissions' => true, + 'not_redirection' => true, + 'selected_board' => empty($_SESSION['move_to_topic']) ? null : $_SESSION['move_to_topic'], + ); + $context['move_to_boards'] = getBoardList($boardListOptions); + } + } + + foreach ($context['key_words'] as $query) + { + // Fix the international characters in the keyword too. + $query = strtr($smcFunc['htmlspecialchars']($query), array('\\\'' => '\'')); + + $body_highlighted = preg_replace_callback('/((<[^>]*)|' . preg_quote(strtr($query, array('\'' => ''')), '/') . ')/i' . ($context['utf8'] ? 'u' : ''), create_function('$m', 'return isset($m[2]) && "$m[2]" == "$m[1]" ? stripslashes("$m[1]") : "$m[1]";'), $body_highlighted); + $subject_highlighted = preg_replace('/(' . preg_quote($query, '/') . ')/i' . ($context['utf8'] ? 'u' : ''), '$1', $subject_highlighted); + } + + $output['matches'][] = array( + 'id' => $message['id_msg'], + 'attachment' => loadAttachmentContext($message['id_msg']), + 'alternate' => $counter % 2, + 'member' => &$memberContext[$message['id_member']], + 'icon' => $message['icon'], + 'icon_url' => $settings[$context['icon_sources'][$message['icon']]] . '/post/' . $message['icon'] . '.gif', + 'subject' => $message['subject'], + 'subject_highlighted' => $subject_highlighted, + 'time' => timeformat($message['poster_time']), + 'timestamp' => forum_time(true, $message['poster_time']), + 'counter' => $counter, + 'modified' => array( + 'time' => timeformat($message['modified_time']), + 'timestamp' => forum_time(true, $message['modified_time']), + 'name' => $message['modified_name'] + ), + 'body' => $message['body'], + 'body_highlighted' => $body_highlighted, + 'start' => 'msg' . $message['id_msg'] + ); + $counter++; + + return $output; +} + +// This function compares the length of two strings plus a little. +function searchSort($a, $b) +{ + global $searchAPI; + + return $searchAPI->searchSort($a, $b); +} + +?> \ No newline at end of file diff --git a/Sources/SearchAPI-Custom.php b/Sources/SearchAPI-Custom.php new file mode 100644 index 0000000..bd97bce --- /dev/null +++ b/Sources/SearchAPI-Custom.php @@ -0,0 +1,214 @@ +supported_databases)) + { + $this->is_supported = false; + return; + } + + if (empty($modSettings['search_custom_index_config'])) + return; + + $this->indexSettings = unserialize($modSettings['search_custom_index_config']); + + $this->bannedWords = empty($modSettings['search_stopwords']) ? array() : explode(',', $modSettings['search_stopwords']); + $this->min_word_length = $this->indexSettings['bytes_per_word']; + } + + // Check whether the search can be performed by this API. + public function supportsMethod($methodName, $query_params = null) + { + switch ($methodName) + { + case 'isValid': + case 'searchSort': + case 'prepareIndexes': + case 'indexedWordQuery': + return true; + break; + + default: + + // All other methods, too bad dunno you. + return false; + return; + } + } + + // If the settings don't exist we can't continue. + public function isValid() + { + global $modSettings; + + return !empty($modSettings['search_custom_index_config']); + } + + // This function compares the length of two strings plus a little. + public function searchSort($a, $b) + { + global $modSettings, $excludedWords; + + $x = strlen($a) - (in_array($a, $excludedWords) ? 1000 : 0); + $y = strlen($b) - (in_array($b, $excludedWords) ? 1000 : 0); + + return $y < $x ? 1 : ($y > $x ? -1 : 0); + } + + // Do we have to do some work with the words we are searching for to prepare them? + public function prepareIndexes($word, &$wordsSearch, &$wordsExclude, $isExcluded) + { + global $modSettings, $smcFunc; + + $subwords = text2words($word, $this->min_word_length, true); + + if (empty($modSettings['search_force_index'])) + $wordsSearch['words'][] = $word; + + // Excluded phrases don't benefit from being split into subwords. + if (count($subwords) > 1 && $isExcluded) + continue; + else + { + foreach ($subwords as $subword) + { + if ($smcFunc['strlen']($subword) >= $this->min_word_length && !in_array($subword, $this->bannedWords)) + { + $wordsSearch['indexed_words'][] = $subword; + if ($isExcluded) + $wordsExclude[] = $subword; + } + } + } + } + + // Search for indexed words. + public function indexedWordQuery($words, $search_data) + { + global $modSettings, $smcFunc; + + $query_select = array( + 'id_msg' => 'm.id_msg', + ); + $query_inner_join = array(); + $query_left_join = array(); + $query_where = array(); + $query_params = $search_data['params']; + + if ($query_params['id_search']) + $query_select['id_search'] = '{int:id_search}'; + + $count = 0; + foreach ($words['words'] as $regularWord) + { + $query_where[] = 'm.body' . (in_array($regularWord, $query_params['excluded_words']) ? ' NOT' : '') . (empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? ' LIKE ' : ' RLIKE ') . '{string:complex_body_' . $count . '}'; + $query_params['complex_body_' . $count++] = empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? '%' . strtr($regularWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $regularWord), '\\\'') . '[[:>:]]'; + } + + if ($query_params['user_query']) + $query_where[] = '{raw:user_query}'; + if ($query_params['board_query']) + $query_where[] = 'm.id_board {raw:board_query}'; + + if ($query_params['topic']) + $query_where[] = 'm.id_topic = {int:topic}'; + if ($query_params['min_msg_id']) + $query_where[] = 'm.id_msg >= {int:min_msg_id}'; + if ($query_params['max_msg_id']) + $query_where[] = 'm.id_msg <= {int:max_msg_id}'; + + $count = 0; + if (!empty($query_params['excluded_phrases']) && empty($modSettings['search_force_index'])) + foreach ($query_params['excluded_phrases'] as $phrase) + { + $query_where[] = 'subject NOT ' . (empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? ' LIKE ' : ' RLIKE ') . '{string:exclude_subject_phrase_' . $count . '}'; + $query_params['exclude_subject_phrase_' . $count++] = empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? '%' . strtr($phrase, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $phrase), '\\\'') . '[[:>:]]'; + } + $count = 0; + if (!empty($query_params['excluded_subject_words']) && empty($modSettings['search_force_index'])) + foreach ($query_params['excluded_subject_words'] as $excludedWord) + { + $query_where[] = 'subject NOT ' . (empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? ' LIKE ' : ' RLIKE ') . '{string:exclude_subject_words_' . $count . '}'; + $query_params['exclude_subject_words_' . $count++] = empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? '%' . strtr($excludedWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $excludedWord), '\\\'') . '[[:>:]]'; + } + + $numTables = 0; + $prev_join = 0; + foreach ($words['indexed_words'] as $indexedWord) + { + $numTables++; + if (in_array($indexedWord, $query_params['excluded_index_words'])) + { + $query_left_join[] = '{db_prefix}log_search_words AS lsw' . $numTables . ' ON (lsw' . $numTables . '.id_word = ' . $indexedWord . ' AND lsw' . $numTables . '.id_msg = m.id_msg)'; + $query_where[] = '(lsw' . $numTables . '.id_word IS NULL)'; + } + else + { + $query_inner_join[] = '{db_prefix}log_search_words AS lsw' . $numTables . ' ON (lsw' . $numTables . '.id_msg = ' . ($prev_join === 0 ? 'm' : 'lsw' . $prev_join) . '.id_msg)'; + $query_where[] = 'lsw' . $numTables . '.id_word = ' . $indexedWord; + $prev_join = $numTables; + } + } + + $ignoreRequest = $smcFunc['db_search_query']('insert_into_log_messages_fulltext', ($smcFunc['db_support_ignore'] ? ( ' + INSERT IGNORE INTO {db_prefix}' . $search_data['insert_into'] . ' + (' . implode(', ', array_keys($query_select)) . ')') : '') . ' + SELECT ' . implode(', ', $query_select) . ' + FROM {db_prefix}messages AS m' . (empty($query_inner_join) ? '' : ' + INNER JOIN ' . implode(' + INNER JOIN ', $query_inner_join)) . (empty($query_left_join) ? '' : ' + LEFT JOIN ' . implode(' + LEFT JOIN ', $query_left_join)) . ' + WHERE ' . implode(' + AND ', $query_where) . (empty($search_data['max_results']) ? '' : ' + LIMIT ' . ($search_data['max_results'] - $search_data['indexed_results'])), + $query_params + ); + + return $ignoreRequest; + } +} + +?> \ No newline at end of file diff --git a/Sources/SearchAPI-Fulltext.php b/Sources/SearchAPI-Fulltext.php new file mode 100644 index 0000000..11ea37a --- /dev/null +++ b/Sources/SearchAPI-Fulltext.php @@ -0,0 +1,241 @@ +supported_databases)) + { + $this->is_supported = false; + return; + } + + // Some MySQL versions are superior to others :P. + $this->canDoBooleanSearch = version_compare($smcFunc['db_server_info']($db_connection), '4.0.1', '>='); + + $this->bannedWords = empty($modSettings['search_banned_words']) ? array() : explode(',', $modSettings['search_banned_words']); + $this->min_word_length = $this->_getMinWordLength(); + } + + // Check whether the method can be performed by this API. + public function supportsMethod($methodName, $query_params = null) + { + switch ($methodName) + { + case 'searchSort': + case 'prepareIndexes': + case 'indexedWordQuery': + return true; + break; + + default: + return false; + break; + } + } + + // What is the minimum word length full text supports? + protected function _getMinWordLength() + { + global $smcFunc; + + // Try to determine the minimum number of letters for a fulltext search. + $request = $smcFunc['db_search_query']('max_fulltext_length', ' + SHOW VARIABLES + LIKE {string:fulltext_minimum_word_length}', + array( + 'fulltext_minimum_word_length' => 'ft_min_word_len', + ) + ); + if ($request !== false && $smcFunc['db_num_rows']($request) == 1) + { + list (, $min_word_length) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + // 4 is the MySQL default... + else + $min_word_length = 4; + + return $min_word_length; + } + + // This function compares the length of two strings plus a little. + public function searchSort($a, $b) + { + global $modSettings, $excludedWords; + + $x = strlen($a) - (in_array($a, $excludedWords) ? 1000 : 0); + $y = strlen($b) - (in_array($b, $excludedWords) ? 1000 : 0); + + return $x < $y ? 1 : ($x > $y ? -1 : 0); + } + + // Do we have to do some work with the words we are searching for to prepare them? + public function prepareIndexes($word, &$wordsSearch, &$wordsExclude, $isExcluded) + { + global $modSettings; + + $subwords = text2words($word, null, false); + + if (!$this->canDoBooleanSearch && count($subwords) > 1 && empty($modSettings['search_force_index'])) + $wordsSearch['words'][] = $word; + + if ($this->canDoBooleanSearch) + { + $fulltextWord = count($subwords) === 1 ? $word : '"' . $word . '"'; + $wordsSearch['indexed_words'][] = $fulltextWord; + if ($isExcluded) + $wordsExclude[] = $fulltextWord; + } + // Excluded phrases don't benefit from being split into subwords. + elseif (count($subwords) > 1 && $isExcluded) + return; + else + { + $relyOnIndex = true; + foreach ($subwords as $subword) + { + if (($smcFunc['strlen']($subword) >= $this->min_word_length) && !in_array($subword, $this->bannedWords)) + { + $wordsSearch['indexed_words'][] = $subword; + if ($isExcluded) + $wordsExclude[] = $subword; + } + elseif (!in_array($subword, $this->bannedWords)) + $relyOnIndex = false; + } + + if ($this->canDoBooleanSearch && !$relyOnIndex && empty($modSettings['search_force_index'])) + $wordsSearch['words'][] = $word; + } + } + + // Search for indexed words. + public function indexedWordQuery($words, $search_data) + { + global $modSettings, $smcFunc; + + $query_select = array( + 'id_msg' => 'm.id_msg', + ); + $query_where = array(); + $query_params = $search_data['params']; + + if ($query_params['id_search']) + $query_select['id_search'] = '{int:id_search}'; + + $count = 0; + if (empty($modSettings['search_simple_fulltext'])) + foreach ($words['words'] as $regularWord) + { + $query_where[] = 'm.body' . (in_array($regularWord, $query_params['excluded_words']) ? ' NOT' : '') . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : 'RLIKE') . '{string:complex_body_' . $count . '}'; + $query_params['complex_body_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($regularWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $regularWord), '\\\'') . '[[:>:]]'; + } + + if ($query_params['user_query']) + $query_where[] = '{raw:user_query}'; + if ($query_params['board_query']) + $query_where[] = 'm.id_board {raw:board_query}'; + + if ($query_params['topic']) + $query_where[] = 'm.id_topic = {int:topic}'; + if ($query_params['min_msg_id']) + $query_where[] = 'm.id_msg >= {int:min_msg_id}'; + if ($query_params['max_msg_id']) + $query_where[] = 'm.id_msg <= {int:max_msg_id}'; + + $count = 0; + if (!empty($query_params['excluded_phrases']) && empty($modSettings['search_force_index'])) + foreach ($query_params['excluded_phrases'] as $phrase) + { + $query_where[] = 'subject NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : 'RLIKE') . '{string:exclude_subject_phrase_' . $count . '}'; + $query_params['exclude_subject_phrase_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($phrase, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $phrase), '\\\'') . '[[:>:]]'; + } + $count = 0; + if (!empty($query_params['excluded_subject_words']) && empty($modSettings['search_force_index'])) + foreach ($query_params['excluded_subject_words'] as $excludedWord) + { + $query_where[] = 'subject NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : 'RLIKE') . '{string:exclude_subject_words_' . $count . '}'; + $query_params['exclude_subject_words_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($excludedWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $excludedWord), '\\\'') . '[[:>:]]'; + } + + if (!empty($modSettings['search_simple_fulltext'])) + { + $query_where[] = 'MATCH (body) AGAINST ({string:body_match})'; + $query_params['body_match'] = implode(' ', array_diff($words['indexed_words'], $query_params['excluded_index_words'])); + } + elseif ($this->canDoBooleanSearch) + { + $query_params['boolean_match'] = ''; + foreach ($words['indexed_words'] as $fulltextWord) + $query_params['boolean_match'] .= (in_array($fulltextWord, $query_params['excluded_index_words']) ? '-' : '+') . $fulltextWord . ' '; + $query_params['boolean_match'] = substr($query_params['boolean_match'], 0, -1); + + $query_where[] = 'MATCH (body) AGAINST ({string:boolean_match} IN BOOLEAN MODE)'; + } + else + { + $count = 0; + foreach ($words['indexed_words'] as $fulltextWord) + { + $query_where[] = (in_array($fulltextWord, $query_params['excluded_index_words']) ? 'NOT ' : '') . 'MATCH (body) AGAINST ({string:fulltext_match_' . $count . '})'; + $query_params['fulltext_match_' . $count++] = $fulltextWord; + } + } + + $ignoreRequest = $smcFunc['db_search_query']('insert_into_log_messages_fulltext', ($smcFunc['db_support_ignore'] ? ( ' + INSERT IGNORE INTO {db_prefix}' . $search_data['insert_into'] . ' + (' . implode(', ', array_keys($query_select)) . ')') : '') . ' + SELECT ' . implode(', ', $query_select) . ' + FROM {db_prefix}messages AS m + WHERE ' . implode(' + AND ', $query_where) . (empty($search_data['max_results']) ? '' : ' + LIMIT ' . ($search_data['max_results'] - $search_data['indexed_results'])), + $query_params + ); + + return $ignoreRequest; + } +} + +?> \ No newline at end of file diff --git a/Sources/SearchAPI-Standard.php b/Sources/SearchAPI-Standard.php new file mode 100644 index 0000000..3ec458e --- /dev/null +++ b/Sources/SearchAPI-Standard.php @@ -0,0 +1,36 @@ + \ No newline at end of file diff --git a/Sources/Security.php b/Sources/Security.php new file mode 100644 index 0000000..a6392b9 --- /dev/null +++ b/Sources/Security.php @@ -0,0 +1,990 @@ += time())) + return; + + require_once($sourcedir . '/Subs-Auth.php'); + + // Hashed password, ahoy! + if (isset($_POST['admin_hash_pass']) && strlen($_POST['admin_hash_pass']) == 40) + { + checkSession(); + + $good_password = in_array(true, call_integration_hook('integrate_verify_password', array($user_info['username'], $_POST['admin_hash_pass'], true)), true); + + if ($good_password || $_POST['admin_hash_pass'] == sha1($user_info['passwd'] . $sc)) + { + $_SESSION['admin_time'] = time(); + unset($_SESSION['request_referer']); + return; + } + } + // Posting the password... check it. + if (isset($_POST['admin_pass'])) + { + checkSession(); + + $good_password = in_array(true, call_integration_hook('integrate_verify_password', array($user_info['username'], $_POST['admin_pass'], false)), true); + + // Password correct? + if ($good_password || sha1(strtolower($user_info['username']) . $_POST['admin_pass']) == $user_info['passwd']) + { + $_SESSION['admin_time'] = time(); + unset($_SESSION['request_referer']); + return; + } + } + // OpenID? + if (!empty($user_settings['openid_uri'])) + { + require_once($sourcedir . '/Subs-OpenID.php'); + smf_openID_revalidate(); + + $_SESSION['admin_time'] = time(); + unset($_SESSION['request_referer']); + return; + } + + // Better be sure to remember the real referer + if (empty($_SESSION['request_referer'])) + $_SESSION['request_referer'] = isset($_SERVER['HTTP_REFERER']) ? @parse_url($_SERVER['HTTP_REFERER']) : array(); + elseif (empty($_POST)) + unset($_SESSION['request_referer']); + // Need to type in a password for that, man. + adminLogin(); +} + +// Require a user who is logged in. (not a guest.) +function is_not_guest($message = '') +{ + global $user_info, $txt, $context, $scripturl; + + // Luckily, this person isn't a guest. + if (!$user_info['is_guest']) + return; + + // People always worry when they see people doing things they aren't actually doing... + $_GET['action'] = ''; + $_GET['board'] = ''; + $_GET['topic'] = ''; + writeLog(true); + + // Just die. + if (isset($_REQUEST['xml'])) + obExit(false); + + // Attempt to detect if they came from dlattach. + if (!WIRELESS && SMF != 'SSI' && empty($context['theme_loaded'])) + loadTheme(); + + // Never redirect to an attachment + if (strpos($_SERVER['REQUEST_URL'], 'dlattach') === false) + $_SESSION['login_url'] = $_SERVER['REQUEST_URL']; + + // Load the Login template and language file. + loadLanguage('Login'); + + // Are we in wireless mode? + if (WIRELESS) + { + $context['login_error'] = $message ? $message : $txt['only_members_can_access']; + $context['sub_template'] = WIRELESS_PROTOCOL . '_login'; + } + // Apparently we're not in a position to handle this now. Let's go to a safer location for now. + elseif (empty($context['template_layers'])) + { + $_SESSION['login_url'] = $scripturl . '?' . $_SERVER['QUERY_STRING']; + redirectexit('action=login'); + } + else + { + loadTemplate('Login'); + $context['sub_template'] = 'kick_guest'; + $context['robot_no_index'] = true; + } + + // Use the kick_guest sub template... + $context['kick_message'] = $message; + $context['page_title'] = $txt['login']; + + obExit(); + + // We should never get to this point, but if we did we wouldn't know the user isn't a guest. + trigger_error('Hacking attempt...', E_USER_ERROR); +} + +// Do banning related stuff. (ie. disallow access....) +function is_not_banned($forceCheck = false) +{ + global $txt, $modSettings, $context, $user_info; + global $sourcedir, $cookiename, $user_settings, $smcFunc; + + // You cannot be banned if you are an admin - doesn't help if you log out. + if ($user_info['is_admin']) + return; + + // Only check the ban every so often. (to reduce load.) + if ($forceCheck || !isset($_SESSION['ban']) || empty($modSettings['banLastUpdated']) || ($_SESSION['ban']['last_checked'] < $modSettings['banLastUpdated']) || $_SESSION['ban']['id_member'] != $user_info['id'] || $_SESSION['ban']['ip'] != $user_info['ip'] || $_SESSION['ban']['ip2'] != $user_info['ip2'] || (isset($user_info['email'], $_SESSION['ban']['email']) && $_SESSION['ban']['email'] != $user_info['email'])) + { + // Innocent until proven guilty. (but we know you are! :P) + $_SESSION['ban'] = array( + 'last_checked' => time(), + 'id_member' => $user_info['id'], + 'ip' => $user_info['ip'], + 'ip2' => $user_info['ip2'], + 'email' => $user_info['email'], + ); + + $ban_query = array(); + $ban_query_vars = array('current_time' => time()); + $flag_is_activated = false; + + // Check both IP addresses. + foreach (array('ip', 'ip2') as $ip_number) + { + // Check if we have a valid IP address. + if (preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $user_info[$ip_number], $ip_parts) == 1) + { + $ban_query[] = '((' . $ip_parts[1] . ' BETWEEN bi.ip_low1 AND bi.ip_high1) + AND (' . $ip_parts[2] . ' BETWEEN bi.ip_low2 AND bi.ip_high2) + AND (' . $ip_parts[3] . ' BETWEEN bi.ip_low3 AND bi.ip_high3) + AND (' . $ip_parts[4] . ' BETWEEN bi.ip_low4 AND bi.ip_high4))'; + + // IP was valid, maybe there's also a hostname... + if (empty($modSettings['disableHostnameLookup'])) + { + $hostname = host_from_ip($user_info[$ip_number]); + if (strlen($hostname) > 0) + { + $ban_query[] = '({string:hostname} LIKE bi.hostname)'; + $ban_query_vars['hostname'] = $hostname; + } + } + } + // We use '255.255.255.255' for 'unknown' since it's not valid anyway. + elseif ($user_info['ip'] == 'unknown') + $ban_query[] = '(bi.ip_low1 = 255 AND bi.ip_high1 = 255 + AND bi.ip_low2 = 255 AND bi.ip_high2 = 255 + AND bi.ip_low3 = 255 AND bi.ip_high3 = 255 + AND bi.ip_low4 = 255 AND bi.ip_high4 = 255)'; + } + + // Is their email address banned? + if (strlen($user_info['email']) != 0) + { + $ban_query[] = '({string:email} LIKE bi.email_address)'; + $ban_query_vars['email'] = $user_info['email']; + } + + // How about this user? + if (!$user_info['is_guest'] && !empty($user_info['id'])) + { + $ban_query[] = 'bi.id_member = {int:id_member}'; + $ban_query_vars['id_member'] = $user_info['id']; + } + + // Check the ban, if there's information. + if (!empty($ban_query)) + { + $restrictions = array( + 'cannot_access', + 'cannot_login', + 'cannot_post', + 'cannot_register', + ); + $request = $smcFunc['db_query']('', ' + SELECT bi.id_ban, bi.email_address, bi.id_member, bg.cannot_access, bg.cannot_register, + bg.cannot_post, bg.cannot_login, bg.reason, IFNULL(bg.expire_time, 0) AS expire_time + FROM {db_prefix}ban_items AS bi + INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time})) + WHERE + (' . implode(' OR ', $ban_query) . ')', + $ban_query_vars + ); + // Store every type of ban that applies to you in your session. + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + foreach ($restrictions as $restriction) + if (!empty($row[$restriction])) + { + $_SESSION['ban'][$restriction]['reason'] = $row['reason']; + $_SESSION['ban'][$restriction]['ids'][] = $row['id_ban']; + if (!isset($_SESSION['ban']['expire_time']) || ($_SESSION['ban']['expire_time'] != 0 && ($row['expire_time'] == 0 || $row['expire_time'] > $_SESSION['ban']['expire_time']))) + $_SESSION['ban']['expire_time'] = $row['expire_time']; + + if (!$user_info['is_guest'] && $restriction == 'cannot_access' && ($row['id_member'] == $user_info['id'] || $row['email_address'] == $user_info['email'])) + $flag_is_activated = true; + } + } + $smcFunc['db_free_result']($request); + } + + // Mark the cannot_access and cannot_post bans as being 'hit'. + if (isset($_SESSION['ban']['cannot_access']) || isset($_SESSION['ban']['cannot_post']) || isset($_SESSION['ban']['cannot_login'])) + log_ban(array_merge(isset($_SESSION['ban']['cannot_access']) ? $_SESSION['ban']['cannot_access']['ids'] : array(), isset($_SESSION['ban']['cannot_post']) ? $_SESSION['ban']['cannot_post']['ids'] : array(), isset($_SESSION['ban']['cannot_login']) ? $_SESSION['ban']['cannot_login']['ids'] : array())); + + // If for whatever reason the is_activated flag seems wrong, do a little work to clear it up. + if ($user_info['id'] && (($user_settings['is_activated'] >= 10 && !$flag_is_activated) + || ($user_settings['is_activated'] < 10 && $flag_is_activated))) + { + require_once($sourcedir . '/ManageBans.php'); + updateBanMembers(); + } + } + + // Hey, I know you! You're ehm... + if (!isset($_SESSION['ban']['cannot_access']) && !empty($_COOKIE[$cookiename . '_'])) + { + $bans = explode(',', $_COOKIE[$cookiename . '_']); + foreach ($bans as $key => $value) + $bans[$key] = (int) $value; + $request = $smcFunc['db_query']('', ' + SELECT bi.id_ban, bg.reason + FROM {db_prefix}ban_items AS bi + INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group) + WHERE bi.id_ban IN ({array_int:ban_list}) + AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time}) + AND bg.cannot_access = {int:cannot_access} + LIMIT ' . count($bans), + array( + 'cannot_access' => 1, + 'ban_list' => $bans, + 'current_time' => time(), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $_SESSION['ban']['cannot_access']['ids'][] = $row['id_ban']; + $_SESSION['ban']['cannot_access']['reason'] = $row['reason']; + } + $smcFunc['db_free_result']($request); + + // My mistake. Next time better. + if (!isset($_SESSION['ban']['cannot_access'])) + { + require_once($sourcedir . '/Subs-Auth.php'); + $cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); + setcookie($cookiename . '_', '', time() - 3600, $cookie_url[1], $cookie_url[0], 0); + } + } + + // If you're fully banned, it's end of the story for you. + if (isset($_SESSION['ban']['cannot_access'])) + { + // We don't wanna see you! + if (!$user_info['is_guest']) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_online + WHERE id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + ) + ); + + // 'Log' the user out. Can't have any funny business... (save the name!) + $old_name = isset($user_info['name']) && $user_info['name'] != '' ? $user_info['name'] : $txt['guest_title']; + $user_info['name'] = ''; + $user_info['username'] = ''; + $user_info['is_guest'] = true; + $user_info['is_admin'] = false; + $user_info['permissions'] = array(); + $user_info['id'] = 0; + $context['user'] = array( + 'id' => 0, + 'username' => '', + 'name' => $txt['guest_title'], + 'is_guest' => true, + 'is_logged' => false, + 'is_admin' => false, + 'is_mod' => false, + 'can_mod' => false, + 'language' => $user_info['language'], + ); + + // A goodbye present. + require_once($sourcedir . '/Subs-Auth.php'); + $cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); + setcookie($cookiename . '_', implode(',', $_SESSION['ban']['cannot_access']['ids']), time() + 3153600, $cookie_url[1], $cookie_url[0], 0); + + // Don't scare anyone, now. + $_GET['action'] = ''; + $_GET['board'] = ''; + $_GET['topic'] = ''; + writeLog(true); + + // You banned, sucka! + fatal_error(sprintf($txt['your_ban'], $old_name) . (empty($_SESSION['ban']['cannot_access']['reason']) ? '' : '
' . $_SESSION['ban']['cannot_access']['reason']) . '
' . (!empty($_SESSION['ban']['expire_time']) ? sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)) : $txt['your_ban_expires_never']), 'user'); + + // If we get here, something's gone wrong.... but let's try anyway. + trigger_error('Hacking attempt...', E_USER_ERROR); + } + // You're not allowed to log in but yet you are. Let's fix that. + elseif (isset($_SESSION['ban']['cannot_login']) && !$user_info['is_guest']) + { + // We don't wanna see you! + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_online + WHERE id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + ) + ); + + // 'Log' the user out. Can't have any funny business... (save the name!) + $old_name = isset($user_info['name']) && $user_info['name'] != '' ? $user_info['name'] : $txt['guest_title']; + $user_info['name'] = ''; + $user_info['username'] = ''; + $user_info['is_guest'] = true; + $user_info['is_admin'] = false; + $user_info['permissions'] = array(); + $user_info['id'] = 0; + $context['user'] = array( + 'id' => 0, + 'username' => '', + 'name' => $txt['guest_title'], + 'is_guest' => true, + 'is_logged' => false, + 'is_admin' => false, + 'is_mod' => false, + 'can_mod' => false, + 'language' => $user_info['language'], + ); + + // SMF's Wipe 'n Clean(r) erases all traces. + $_GET['action'] = ''; + $_GET['board'] = ''; + $_GET['topic'] = ''; + writeLog(true); + + require_once($sourcedir . '/LogInOut.php'); + Logout(true, false); + + fatal_error(sprintf($txt['your_ban'], $old_name) . (empty($_SESSION['ban']['cannot_login']['reason']) ? '' : '
' . $_SESSION['ban']['cannot_login']['reason']) . '
' . (!empty($_SESSION['ban']['expire_time']) ? sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)) : $txt['your_ban_expires_never']) . '
' . $txt['ban_continue_browse'], 'user'); + } + + // Fix up the banning permissions. + if (isset($user_info['permissions'])) + banPermissions(); +} + +// Fix permissions according to ban status. +function banPermissions() +{ + global $user_info, $sourcedir, $modSettings, $context; + + // Somehow they got here, at least take away all permissions... + if (isset($_SESSION['ban']['cannot_access'])) + $user_info['permissions'] = array(); + // Okay, well, you can watch, but don't touch a thing. + elseif (isset($_SESSION['ban']['cannot_post']) || (!empty($modSettings['warning_mute']) && $modSettings['warning_mute'] <= $user_info['warning'])) + { + $denied_permissions = array( + 'pm_send', + 'calendar_post', 'calendar_edit_own', 'calendar_edit_any', + 'poll_post', + 'poll_add_own', 'poll_add_any', + 'poll_edit_own', 'poll_edit_any', + 'poll_lock_own', 'poll_lock_any', + 'poll_remove_own', 'poll_remove_any', + 'manage_attachments', 'manage_smileys', 'manage_boards', 'admin_forum', 'manage_permissions', + 'moderate_forum', 'manage_membergroups', 'manage_bans', 'send_mail', 'edit_news', + 'profile_identity_any', 'profile_extra_any', 'profile_title_any', + 'post_new', 'post_reply_own', 'post_reply_any', + 'delete_own', 'delete_any', 'delete_replies', + 'make_sticky', + 'merge_any', 'split_any', + 'modify_own', 'modify_any', 'modify_replies', + 'move_any', + 'send_topic', + 'lock_own', 'lock_any', + 'remove_own', 'remove_any', + 'post_unapproved_topics', 'post_unapproved_replies_own', 'post_unapproved_replies_any', + ); + $user_info['permissions'] = array_diff($user_info['permissions'], $denied_permissions); + } + // Are they absolutely under moderation? + elseif (!empty($modSettings['warning_moderate']) && $modSettings['warning_moderate'] <= $user_info['warning']) + { + // Work out what permissions should change... + $permission_change = array( + 'post_new' => 'post_unapproved_topics', + 'post_reply_own' => 'post_unapproved_replies_own', + 'post_reply_any' => 'post_unapproved_replies_any', + 'post_attachment' => 'post_unapproved_attachments', + ); + foreach ($permission_change as $old => $new) + { + if (!in_array($old, $user_info['permissions'])) + unset($permission_change[$old]); + else + $user_info['permissions'][] = $new; + } + $user_info['permissions'] = array_diff($user_info['permissions'], array_keys($permission_change)); + } + + //!!! Find a better place to call this? Needs to be after permissions loaded! + // Finally, some bits we cache in the session because it saves queries. + if (isset($_SESSION['mc']) && $_SESSION['mc']['time'] > $modSettings['settings_updated'] && $_SESSION['mc']['id'] == $user_info['id']) + $user_info['mod_cache'] = $_SESSION['mc']; + else + { + require_once($sourcedir . '/Subs-Auth.php'); + rebuildModCache(); + } + + // Now that we have the mod cache taken care of lets setup a cache for the number of mod reports still open + if (isset($_SESSION['rc']) && $_SESSION['rc']['time'] > $modSettings['last_mod_report_action'] && $_SESSION['rc']['id'] == $user_info['id']) + $context['open_mod_reports'] = $_SESSION['rc']['reports']; + elseif ($_SESSION['mc']['bq'] != '0=1') + { + require_once($sourcedir . '/ModerationCenter.php'); + recountOpenReports(); + } + else + $context['open_mod_reports'] = 0; +} + +// Log a ban in the database. +function log_ban($ban_ids = array(), $email = null) +{ + global $user_info, $smcFunc; + + // Don't log web accelerators, it's very confusing... + if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') + return; + + $smcFunc['db_insert']('', + '{db_prefix}log_banned', + array('id_member' => 'int', 'ip' => 'string-16', 'email' => 'string', 'log_time' => 'int'), + array($user_info['id'], $user_info['ip'], ($email === null ? ($user_info['is_guest'] ? '' : $user_info['email']) : $email), time()), + array('id_ban_log') + ); + + // One extra point for these bans. + if (!empty($ban_ids)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}ban_items + SET hits = hits + 1 + WHERE id_ban IN ({array_int:ban_ids})', + array( + 'ban_ids' => $ban_ids, + ) + ); +} + +// Checks if a given email address might be banned. +function isBannedEmail($email, $restriction, $error) +{ + global $txt, $smcFunc; + + // Can't ban an empty email + if (empty($email) || trim($email) == '') + return; + + // Let's start with the bans based on your IP/hostname/memberID... + $ban_ids = isset($_SESSION['ban'][$restriction]) ? $_SESSION['ban'][$restriction]['ids'] : array(); + $ban_reason = isset($_SESSION['ban'][$restriction]) ? $_SESSION['ban'][$restriction]['reason'] : ''; + + // ...and add to that the email address you're trying to register. + $request = $smcFunc['db_query']('', ' + SELECT bi.id_ban, bg.' . $restriction . ', bg.cannot_access, bg.reason + FROM {db_prefix}ban_items AS bi + INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group) + WHERE {string:email} LIKE bi.email_address + AND (bg.' . $restriction . ' = {int:cannot_access} OR bg.cannot_access = {int:cannot_access}) + AND (bg.expire_time IS NULL OR bg.expire_time >= {int:now})', + array( + 'email' => $email, + 'cannot_access' => 1, + 'now' => time(), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!empty($row['cannot_access'])) + { + $_SESSION['ban']['cannot_access']['ids'][] = $row['id_ban']; + $_SESSION['ban']['cannot_access']['reason'] = $row['reason']; + } + if (!empty($row[$restriction])) + { + $ban_ids[] = $row['id_ban']; + $ban_reason = $row['reason']; + } + } + $smcFunc['db_free_result']($request); + + // You're in biiig trouble. Banned for the rest of this session! + if (isset($_SESSION['ban']['cannot_access'])) + { + log_ban($_SESSION['ban']['cannot_access']['ids']); + $_SESSION['ban']['last_checked'] = time(); + + fatal_error(sprintf($txt['your_ban'], $txt['guest_title']) . $_SESSION['ban']['cannot_access']['reason'], false); + } + + if (!empty($ban_ids)) + { + // Log this ban for future reference. + log_ban($ban_ids, $email); + fatal_error($error . $ban_reason, false); + } +} + +// Make sure the user's correct session was passed, and they came from here. (type can be post, get, or request.) +function checkSession($type = 'post', $from_action = '', $is_fatal = true) +{ + global $sc, $modSettings, $boardurl; + + // Is it in as $_POST['sc']? + if ($type == 'post') + { + $check = isset($_POST[$_SESSION['session_var']]) ? $_POST[$_SESSION['session_var']] : (empty($modSettings['strictSessionCheck']) && isset($_POST['sc']) ? $_POST['sc'] : null); + if ($check !== $sc) + $error = 'session_timeout'; + } + + // How about $_GET['sesc']? + elseif ($type == 'get') + { + $check = isset($_GET[$_SESSION['session_var']]) ? $_GET[$_SESSION['session_var']] : (empty($modSettings['strictSessionCheck']) && isset($_GET['sesc']) ? $_GET['sesc'] : null); + if ($check !== $sc) + $error = 'session_verify_fail'; + } + + // Or can it be in either? + elseif ($type == 'request') + { + $check = isset($_GET[$_SESSION['session_var']]) ? $_GET[$_SESSION['session_var']] : (empty($modSettings['strictSessionCheck']) && isset($_GET['sesc']) ? $_GET['sesc'] : (isset($_POST[$_SESSION['session_var']]) ? $_POST[$_SESSION['session_var']] : (empty($modSettings['strictSessionCheck']) && isset($_POST['sc']) ? $_POST['sc'] : null))); + + if ($check !== $sc) + $error = 'session_verify_fail'; + } + + // Verify that they aren't changing user agents on us - that could be bad. + if ((!isset($_SESSION['USER_AGENT']) || $_SESSION['USER_AGENT'] != $_SERVER['HTTP_USER_AGENT']) && empty($modSettings['disableCheckUA'])) + $error = 'session_verify_fail'; + + // Make sure a page with session check requirement is not being prefetched. + if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') + { + ob_end_clean(); + header('HTTP/1.1 403 Forbidden'); + die; + } + + // Check the referring site - it should be the same server at least! + if (isset($_SESSION['request_referer'])) + $referrer = $_SESSION['request_referer']; + else + $referrer = isset($_SERVER['HTTP_REFERER']) ? @parse_url($_SERVER['HTTP_REFERER']) : array(); + if (!empty($referrer['host'])) + { + if (strpos($_SERVER['HTTP_HOST'], ':') !== false) + $real_host = substr($_SERVER['HTTP_HOST'], 0, strpos($_SERVER['HTTP_HOST'], ':')); + else + $real_host = $_SERVER['HTTP_HOST']; + + $parsed_url = parse_url($boardurl); + + // Are global cookies on? If so, let's check them ;). + if (!empty($modSettings['globalCookies'])) + { + if (preg_match('~(?:[^\.]+\.)?([^\.]{3,}\..+)\z~i', $parsed_url['host'], $parts) == 1) + $parsed_url['host'] = $parts[1]; + + if (preg_match('~(?:[^\.]+\.)?([^\.]{3,}\..+)\z~i', $referrer['host'], $parts) == 1) + $referrer['host'] = $parts[1]; + + if (preg_match('~(?:[^\.]+\.)?([^\.]{3,}\..+)\z~i', $real_host, $parts) == 1) + $real_host = $parts[1]; + } + + // Okay: referrer must either match parsed_url or real_host. + if (isset($parsed_url['host']) && strtolower($referrer['host']) != strtolower($parsed_url['host']) && strtolower($referrer['host']) != strtolower($real_host)) + { + $error = 'verify_url_fail'; + $log_error = true; + } + } + + // Well, first of all, if a from_action is specified you'd better have an old_url. + if (!empty($from_action) && (!isset($_SESSION['old_url']) || preg_match('~[?;&]action=' . $from_action . '([;&]|$)~', $_SESSION['old_url']) == 0)) + { + $error = 'verify_url_fail'; + $log_error = true; + } + + if (strtolower($_SERVER['HTTP_USER_AGENT']) == 'hacker') + fatal_error('Sound the alarm! It\'s a hacker! Close the castle gates!!', false); + + // Everything is ok, return an empty string. + if (!isset($error)) + return ''; + // A session error occurred, show the error. + elseif ($is_fatal) + { + if (isset($_GET['xml'])) + { + ob_end_clean(); + header('HTTP/1.1 403 Forbidden - Session timeout'); + die; + } + else + fatal_lang_error($error, isset($log_error) ? 'user' : false); + } + // A session error occurred, return the error to the calling function. + else + return $error; + + // We really should never fall through here, for very important reasons. Let's make sure. + trigger_error('Hacking attempt...', E_USER_ERROR); +} + +// Check if a specific confirm parameter was given. +function checkConfirm($action) +{ + global $modSettings; + + if (isset($_GET['confirm']) && isset($_SESSION['confirm_' . $action]) && md5($_GET['confirm'] . $_SERVER['HTTP_USER_AGENT']) == $_SESSION['confirm_' . $action]) + return true; + + else + { + $token = md5(mt_rand() . session_id() . (string) microtime() . $modSettings['rand_seed']); + $_SESSION['confirm_' . $action] = md5($token . $_SERVER['HTTP_USER_AGENT']); + + return $token; + } +} + +// Check whether a form has been submitted twice. +function checkSubmitOnce($action, $is_fatal = true) +{ + global $context; + + if (!isset($_SESSION['forms'])) + $_SESSION['forms'] = array(); + + // Register a form number and store it in the session stack. (use this on the page that has the form.) + if ($action == 'register') + { + $context['form_sequence_number'] = 0; + while (empty($context['form_sequence_number']) || in_array($context['form_sequence_number'], $_SESSION['forms'])) + $context['form_sequence_number'] = mt_rand(1, 16000000); + } + // Check whether the submitted number can be found in the session. + elseif ($action == 'check') + { + if (!isset($_REQUEST['seqnum'])) + return true; + elseif (!in_array($_REQUEST['seqnum'], $_SESSION['forms'])) + { + $_SESSION['forms'][] = (int) $_REQUEST['seqnum']; + return true; + } + elseif ($is_fatal) + fatal_lang_error('error_form_already_submitted', false); + else + return false; + } + // Don't check, just free the stack number. + elseif ($action == 'free' && isset($_REQUEST['seqnum']) && in_array($_REQUEST['seqnum'], $_SESSION['forms'])) + $_SESSION['forms'] = array_diff($_SESSION['forms'], array($_REQUEST['seqnum'])); + elseif ($action != 'free') + trigger_error('checkSubmitOnce(): Invalid action \'' . $action . '\'', E_USER_WARNING); +} + +// Check the user's permissions. +function allowedTo($permission, $boards = null) +{ + global $user_info, $modSettings, $smcFunc; + + // You're always allowed to do nothing. (unless you're a working man, MR. LAZY :P!) + if (empty($permission)) + return true; + + // You're never allowed to do something if your data hasn't been loaded yet! + if (empty($user_info)) + return false; + + // Administrators are supermen :P. + if ($user_info['is_admin']) + return true; + + // Are we checking the _current_ board, or some other boards? + if ($boards === null) + { + // Check if they can do it. + if (!is_array($permission) && in_array($permission, $user_info['permissions'])) + return true; + // Search for any of a list of permissions. + elseif (is_array($permission) && count(array_intersect($permission, $user_info['permissions'])) != 0) + return true; + // You aren't allowed, by default. + else + return false; + } + elseif (!is_array($boards)) + $boards = array($boards); + + $request = $smcFunc['db_query']('', ' + SELECT MIN(bp.add_deny) AS add_deny + FROM {db_prefix}boards AS b + INNER JOIN {db_prefix}board_permissions AS bp ON (bp.id_profile = b.id_profile) + LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member}) + WHERE b.id_board IN ({array_int:board_list}) + AND bp.id_group IN ({array_int:group_list}, {int:moderator_group}) + AND bp.permission {raw:permission_list} + AND (mods.id_member IS NOT NULL OR bp.id_group != {int:moderator_group}) + GROUP BY b.id_board', + array( + 'current_member' => $user_info['id'], + 'board_list' => $boards, + 'group_list' => $user_info['groups'], + 'moderator_group' => 3, + 'permission_list' => (is_array($permission) ? 'IN (\'' . implode('\', \'', $permission) . '\')' : ' = \'' . $permission . '\''), + ) + ); + + // Make sure they can do it on all of the boards. + if ($smcFunc['db_num_rows']($request) != count($boards)) + return false; + + $result = true; + while ($row = $smcFunc['db_fetch_assoc']($request)) + $result &= !empty($row['add_deny']); + $smcFunc['db_free_result']($request); + + // If the query returned 1, they can do it... otherwise, they can't. + return $result; +} + +// Fatal error if they cannot... +function isAllowedTo($permission, $boards = null) +{ + global $user_info, $txt; + + static $heavy_permissions = array( + 'admin_forum', + 'manage_attachments', + 'manage_smileys', + 'manage_boards', + 'edit_news', + 'moderate_forum', + 'manage_bans', + 'manage_membergroups', + 'manage_permissions', + ); + + // Make it an array, even if a string was passed. + $permission = is_array($permission) ? $permission : array($permission); + + // Check the permission and return an error... + if (!allowedTo($permission, $boards)) + { + // Pick the last array entry as the permission shown as the error. + $error_permission = array_shift($permission); + + // If they are a guest, show a login. (because the error might be gone if they do!) + if ($user_info['is_guest']) + { + loadLanguage('Errors'); + is_not_guest($txt['cannot_' . $error_permission]); + } + + // Clear the action because they aren't really doing that! + $_GET['action'] = ''; + $_GET['board'] = ''; + $_GET['topic'] = ''; + writeLog(true); + + fatal_lang_error('cannot_' . $error_permission, false); + + // Getting this far is a really big problem, but let's try our best to prevent any cases... + trigger_error('Hacking attempt...', E_USER_ERROR); + } + + // If you're doing something on behalf of some "heavy" permissions, validate your session. + // (take out the heavy permissions, and if you can't do anything but those, you need a validated session.) + if (!allowedTo(array_diff($permission, $heavy_permissions), $boards)) + validateSession(); +} + +// Return the boards a user has a certain (board) permission on. (array(0) if all.) +function boardsAllowedTo($permissions, $check_access = true) +{ + global $user_info, $modSettings, $smcFunc; + + // Administrators are all powerful, sorry. + if ($user_info['is_admin']) + return array(0); + + // Arrays are nice, most of the time. + if (!is_array($permissions)) + $permissions = array($permissions); + + // All groups the user is in except 'moderator'. + $groups = array_diff($user_info['groups'], array(3)); + + $request = $smcFunc['db_query']('', ' + SELECT b.id_board, bp.add_deny + FROM {db_prefix}board_permissions AS bp + INNER JOIN {db_prefix}boards AS b ON (b.id_profile = bp.id_profile) + LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member}) + WHERE bp.id_group IN ({array_int:group_list}, {int:moderator_group}) + AND bp.permission IN ({array_string:permissions}) + AND (mods.id_member IS NOT NULL OR bp.id_group != {int:moderator_group})' . + ($check_access ? ' AND {query_see_board}' : ''), + array( + 'current_member' => $user_info['id'], + 'group_list' => $groups, + 'moderator_group' => 3, + 'permissions' => $permissions, + ) + ); + $boards = array(); + $deny_boards = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (empty($row['add_deny'])) + $deny_boards[] = $row['id_board']; + else + $boards[] = $row['id_board']; + } + $smcFunc['db_free_result']($request); + + $boards = array_unique(array_values(array_diff($boards, $deny_boards))); + + return $boards; +} + +function showEmailAddress($userProfile_hideEmail, $userProfile_id) +{ + global $modSettings, $user_info; + + // Should this users email address be shown? + // If you're guest and the forum is set to hide email for guests: no. + // If the user is post-banned: no. + // If it's your own profile and you've set your address hidden: yes_permission_override. + // If you're a moderator with sufficient permissions: yes_permission_override. + // If the user has set their email address to be hidden: no. + // If the forum is set to show full email addresses: yes. + // Otherwise: no_through_forum. + + return (!empty($modSettings['guest_hideContacts']) && $user_info['is_guest']) || isset($_SESSION['ban']['cannot_post']) ? 'no' : ((!$user_info['is_guest'] && $user_info['id'] == $userProfile_id && !$userProfile_hideEmail) || allowedTo('moderate_forum') ? 'yes_permission_override' : ($userProfile_hideEmail ? 'no' : (!empty($modSettings['make_email_viewable']) ? 'yes' : 'no_through_forum'))); +} + +?> \ No newline at end of file diff --git a/Sources/SendTopic.php b/Sources/SendTopic.php new file mode 100644 index 0000000..13accec --- /dev/null +++ b/Sources/SendTopic.php @@ -0,0 +1,586 @@ + 'CustomEmail', + 'sendtopic' => 'SendTopic', + ); + + if (!isset($_GET['sa']) || !isset($sub_actions[$_GET['sa']])) + $_GET['sa'] = 'sendtopic'; + + $sub_actions[$_GET['sa']](); +} + +// Send a topic to a friend. +function SendTopic() +{ + global $topic, $txt, $context, $scripturl, $sourcedir, $smcFunc, $modSettings; + + // Check permissions... + isAllowedTo('send_topic'); + + // We need at least a topic... go away if you don't have one. + if (empty($topic)) + fatal_lang_error('not_a_topic', false); + + // Get the topic's subject. + $request = $smcFunc['db_query']('', ' + SELECT m.subject, t.approved + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + WHERE t.id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('not_a_topic', false); + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Can't send topic if its unapproved and using post moderation. + if ($modSettings['postmod_active'] && !$row['approved']) + fatal_lang_error('not_approved_topic', false); + + // Censor the subject.... + censorText($row['subject']); + + // Sending yet, or just getting prepped? + if (empty($_POST['send'])) + { + $context['page_title'] = sprintf($txt['sendtopic_title'], $row['subject']); + $context['start'] = $_REQUEST['start']; + + return; + } + + // Actually send the message... + checkSession(); + spamProtection('sendtopc'); + + // This is needed for sendmail(). + require_once($sourcedir . '/Subs-Post.php'); + + // Trim the names.. + $_POST['y_name'] = trim($_POST['y_name']); + $_POST['r_name'] = trim($_POST['r_name']); + + // Make sure they aren't playing "let's use a fake email". + if ($_POST['y_name'] == '_' || !isset($_POST['y_name']) || $_POST['y_name'] == '') + fatal_lang_error('no_name', false); + if (!isset($_POST['y_email']) || $_POST['y_email'] == '') + fatal_lang_error('no_email', false); + if (preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $_POST['y_email']) == 0) + fatal_lang_error('email_invalid_character', false); + + // The receiver should be valid to. + if ($_POST['r_name'] == '_' || !isset($_POST['r_name']) || $_POST['r_name'] == '') + fatal_lang_error('no_name', false); + if (!isset($_POST['r_email']) || $_POST['r_email'] == '') + fatal_lang_error('no_email', false); + if (preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $_POST['r_email']) == 0) + fatal_lang_error('email_invalid_character', false); + + // Emails don't like entities... + $row['subject'] = un_htmlspecialchars($row['subject']); + + $replacements = array( + 'TOPICSUBJECT' => $row['subject'], + 'SENDERNAME' => $_POST['y_name'], + 'RECPNAME' => $_POST['r_name'], + 'TOPICLINK' => $scripturl . '?topic=' . $topic . '.0', + ); + + $emailtemplate = 'send_topic'; + + if (!empty($_POST['comment'])) + { + $emailtemplate .= '_comment'; + $replacements['COMMENT'] = $_POST['comment']; + } + + $emaildata = loadEmailTemplate($emailtemplate, $replacements); + // And off we go! + sendmail($_POST['r_email'], $emaildata['subject'], $emaildata['body'], $_POST['y_email']); + + // Back to the topic! + redirectexit('topic=' . $topic . '.0'); +} + +// Allow a user to send an email. +function CustomEmail() +{ + global $context, $modSettings, $user_info, $smcFunc, $txt, $scripturl, $sourcedir; + + // Can the user even see this information? + if ($user_info['is_guest'] && !empty($modSettings['guest_hideContacts'])) + fatal_lang_error('no_access', false); + + // Are we sending to a user? + $context['form_hidden_vars'] = array(); + if (isset($_REQUEST['uid'])) + { + $request = $smcFunc['db_query']('', ' + SELECT email_address AS email, real_name AS name, id_member, hide_email + FROM {db_prefix}members + WHERE id_member = {int:id_member}', + array( + 'id_member' => (int) $_REQUEST['uid'], + ) + ); + + $context['form_hidden_vars']['uid'] = (int) $_REQUEST['uid']; + } + elseif (isset($_REQUEST['msg'])) + { + $request = $smcFunc['db_query']('', ' + SELECT IFNULL(mem.email_address, m.poster_email) AS email, IFNULL(mem.real_name, m.poster_name) AS name, IFNULL(mem.id_member, 0) AS id_member, hide_email + FROM {db_prefix}messages AS m + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE m.id_msg = {int:id_msg}', + array( + 'id_msg' => (int) $_REQUEST['msg'], + ) + ); + + $context['form_hidden_vars']['msg'] = (int) $_REQUEST['msg']; + } + + if (empty($request) || $smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('cant_find_user_email'); + + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Are you sure you got the address? + if (empty($row['email'])) + fatal_lang_error('cant_find_user_email'); + + // Can they actually do this? + $context['show_email_address'] = showEmailAddress(!empty($row['hide_email']), $row['id_member']); + if ($context['show_email_address'] === 'no') + fatal_lang_error('no_access', false); + + // Setup the context! + $context['recipient'] = array( + 'id' => $row['id_member'], + 'name' => $row['name'], + 'email' => $row['email'], + 'email_link' => ($context['show_email_address'] == 'yes_permission_override' ? '' : '') . '' . $row['email'] . '' . ($context['show_email_address'] == 'yes_permission_override' ? '' : ''), + 'link' => $row['id_member'] ? '' . $row['name'] . '' : $row['name'], + ); + + // Can we see this person's email address? + $context['can_view_receipient_email'] = $context['show_email_address'] == 'yes' || $context['show_email_address'] == 'yes_permission_override'; + + // Are we actually sending it? + if (isset($_POST['send']) && isset($_POST['email_body'])) + { + require_once($sourcedir . '/Subs-Post.php'); + + checkSession(); + + // If it's a guest sort out their names. + if ($user_info['is_guest']) + { + if (empty($_POST['y_name']) || $_POST['y_name'] == '_' || trim($_POST['y_name']) == '') + fatal_lang_error('no_name', false); + if (empty($_POST['y_email'])) + fatal_lang_error('no_email', false); + if (preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $_POST['y_email']) == 0) + fatal_lang_error('email_invalid_character', false); + + $from_name = trim($_POST['y_name']); + $from_email = trim($_POST['y_email']); + } + else + { + $from_name = $user_info['name']; + $from_email = $user_info['email']; + } + + // Check we have a body (etc). + if (trim($_POST['email_body']) == '' || trim($_POST['email_subject']) == '') + fatal_lang_error('email_missing_data'); + + // We use a template in case they want to customise! + $replacements = array( + 'EMAILSUBJECT' => $_POST['email_subject'], + 'EMAILBODY' => $_POST['email_body'], + 'SENDERNAME' => $from_name, + 'RECPNAME' => $context['recipient']['name'], + ); + + // Don't let them send too many! + spamProtection('sendmail'); + + // Get the template and get out! + $emaildata = loadEmailTemplate('send_email', $replacements); + sendmail($context['recipient']['email'], $emaildata['subject'], $emaildata['body'], $from_email, null, false, 1, null, true); + + // Now work out where to go! + if (isset($_REQUEST['uid'])) + redirectexit('action=profile;u=' . (int) $_REQUEST['uid']); + elseif (isset($_REQUEST['msg'])) + redirectexit('msg=' . (int) $_REQUEST['msg']); + else + redirectexit(); + } + + $context['sub_template'] = 'custom_email'; + $context['page_title'] = $txt['send_email']; +} + +// Report a post to the moderator... ask for a comment. +function ReportToModerator() +{ + global $txt, $topic, $sourcedir, $modSettings, $user_info, $context, $smcFunc; + + $context['robot_no_index'] = true; + + // You can't use this if it's off or you are not allowed to do it. + isAllowedTo('report_any'); + + // If they're posting, it should be processed by ReportToModerator2. + if ((isset($_POST[$context['session_var']]) || isset($_POST['submit'])) && empty($context['post_errors'])) + ReportToModerator2(); + + // We need a message ID to check! + if (empty($_REQUEST['msg']) && empty($_REQUEST['mid'])) + fatal_lang_error('no_access', false); + + // For compatibility, accept mid, but we should be using msg. (not the flavor kind!) + $_REQUEST['msg'] = empty($_REQUEST['msg']) ? (int) $_REQUEST['mid'] : (int) $_REQUEST['msg']; + + // Check the message's ID - don't want anyone reporting a post they can't even see! + $result = $smcFunc['db_query']('', ' + SELECT m.id_msg, m.id_member, t.id_member_started + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic}) + WHERE m.id_msg = {int:id_msg} + AND m.id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + 'id_msg' => $_REQUEST['msg'], + ) + ); + if ($smcFunc['db_num_rows']($result) == 0) + fatal_lang_error('no_board', false); + list ($_REQUEST['msg'], $member, $starter) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + // Do we need to show the visual verification image? + $context['require_verification'] = $user_info['is_guest'] && !empty($modSettings['guests_report_require_captcha']); + if ($context['require_verification']) + { + require_once($sourcedir . '/Subs-Editor.php'); + $verificationOptions = array( + 'id' => 'report', + ); + $context['require_verification'] = create_control_verification($verificationOptions); + $context['visual_verification_id'] = $verificationOptions['id']; + } + + // Show the inputs for the comment, etc. + loadLanguage('Post'); + loadTemplate('SendTopic'); + + $context['comment_body'] = !isset($_POST['comment']) ? '' : trim($_POST['comment']); + $context['email_address'] = !isset($_POST['email']) ? '' : trim($_POST['email']); + + // This is here so that the user could, in theory, be redirected back to the topic. + $context['start'] = $_REQUEST['start']; + $context['message_id'] = $_REQUEST['msg']; + + $context['page_title'] = $txt['report_to_mod']; + $context['sub_template'] = 'report'; +} + +// Send the emails. +function ReportToModerator2() +{ + global $txt, $scripturl, $topic, $board, $user_info, $modSettings, $sourcedir, $language, $context, $smcFunc; + + // You must have the proper permissions! + isAllowedTo('report_any'); + + // Make sure they aren't spamming. + spamProtection('reporttm'); + + require_once($sourcedir . '/Subs-Post.php'); + + // No errors, yet. + $post_errors = array(); + + // Check their session. + if (checkSession('post', '', false) != '') + $post_errors[] = 'session_timeout'; + + // Make sure we have a comment and it's clean. + if (!isset($_POST['comment']) || $smcFunc['htmltrim']($_POST['comment']) === '') + $post_errors[] = 'no_comment'; + $poster_comment = strtr($smcFunc['htmlspecialchars']($_POST['comment']), array("\r" => '', "\n" => '', "\t" => '')); + + // Guests need to provide their address! + if ($user_info['is_guest']) + { + $_POST['email'] = !isset($_POST['email']) ? '' : trim($_POST['email']); + if ($_POST['email'] === '') + $post_errors[] = 'no_email'; + elseif (preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $_POST['email']) == 0) + $post_errors[] = 'bad_email'; + + isBannedEmail($_POST['email'], 'cannot_post', sprintf($txt['you_are_post_banned'], $txt['guest_title'])); + + $user_info['email'] = htmlspecialchars($_POST['email']); + } + + // Could they get the right verification code? + if ($user_info['is_guest'] && !empty($modSettings['guests_report_require_captcha'])) + { + require_once($sourcedir . '/Subs-Editor.php'); + $verificationOptions = array( + 'id' => 'report', + ); + $context['require_verification'] = create_control_verification($verificationOptions, true); + if (is_array($context['require_verification'])) + $post_errors = array_merge($post_errors, $context['require_verification']); + } + + // Any errors? + if (!empty($post_errors)) + { + loadLanguage('Errors'); + + $context['post_errors'] = array(); + foreach ($post_errors as $post_error) + $context['post_errors'][] = $txt['error_' . $post_error]; + + return ReportToModerator(); + } + + // Get the basic topic information, and make sure they can see it. + $_POST['msg'] = (int) $_POST['msg']; + + $request = $smcFunc['db_query']('', ' + SELECT m.id_topic, m.id_board, m.subject, m.body, m.id_member AS id_poster, m.poster_name, mem.real_name + FROM {db_prefix}messages AS m + LEFT JOIN {db_prefix}members AS mem ON (m.id_member = mem.id_member) + WHERE m.id_msg = {int:id_msg} + AND m.id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + 'id_msg' => $_POST['msg'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_board', false); + $message = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + $poster_name = un_htmlspecialchars($message['real_name']) . ($message['real_name'] != $message['poster_name'] ? ' (' . $message['poster_name'] . ')' : ''); + $reporterName = un_htmlspecialchars($user_info['name']) . ($user_info['name'] != $user_info['username'] && $user_info['username'] != '' ? ' (' . $user_info['username'] . ')' : ''); + $subject = un_htmlspecialchars($message['subject']); + + // Get a list of members with the moderate_board permission. + require_once($sourcedir . '/Subs-Members.php'); + $moderators = membersAllowedTo('moderate_board', $board); + + $request = $smcFunc['db_query']('', ' + SELECT id_member, email_address, lngfile, mod_prefs + FROM {db_prefix}members + WHERE id_member IN ({array_int:moderator_list}) + AND notify_types != {int:notify_types} + ORDER BY lngfile', + array( + 'moderator_list' => $moderators, + 'notify_types' => 4, + ) + ); + + // Check that moderators do exist! + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_mods', false); + + // If we get here, I believe we should make a record of this, for historical significance, yabber. + if (empty($modSettings['disable_log_report'])) + { + $request2 = $smcFunc['db_query']('', ' + SELECT id_report, ignore_all + FROM {db_prefix}log_reported + WHERE id_msg = {int:id_msg} + AND (closed = {int:not_closed} OR ignore_all = {int:ignored}) + ORDER BY ignore_all DESC', + array( + 'id_msg' => $_POST['msg'], + 'not_closed' => 0, + 'ignored' => 1, + ) + ); + if ($smcFunc['db_num_rows']($request2) != 0) + list ($id_report, $ignore) = $smcFunc['db_fetch_row']($request2); + $smcFunc['db_free_result']($request2); + + // If we're just going to ignore these, then who gives a monkeys... + if (!empty($ignore)) + redirectexit('topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg']); + + // Already reported? My god, we could be dealing with a real rogue here... + if (!empty($id_report)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_reported + SET num_reports = num_reports + 1, time_updated = {int:current_time} + WHERE id_report = {int:id_report}', + array( + 'current_time' => time(), + 'id_report' => $id_report, + ) + ); + // Otherwise, we shall make one! + else + { + if (empty($message['real_name'])) + $message['real_name'] = $message['poster_name']; + + $smcFunc['db_insert']('', + '{db_prefix}log_reported', + array( + 'id_msg' => 'int', 'id_topic' => 'int', 'id_board' => 'int', 'id_member' => 'int', 'membername' => 'string', + 'subject' => 'string', 'body' => 'string', 'time_started' => 'int', 'time_updated' => 'int', + 'num_reports' => 'int', 'closed' => 'int', + ), + array( + $_POST['msg'], $message['id_topic'], $message['id_board'], $message['id_poster'], $message['real_name'], + $message['subject'], $message['body'] , time(), time(), 1, 0, + ), + array('id_report') + ); + $id_report = $smcFunc['db_insert_id']('{db_prefix}log_reported', 'id_report'); + } + + // Now just add our report... + if ($id_report) + { + $smcFunc['db_insert']('', + '{db_prefix}log_reported_comments', + array( + 'id_report' => 'int', 'id_member' => 'int', 'membername' => 'string', 'email_address' => 'string', + 'member_ip' => 'string', 'comment' => 'string', 'time_sent' => 'int', + ), + array( + $id_report, $user_info['id'], $user_info['name'], $user_info['email'], + $user_info['ip'], $poster_comment, time(), + ), + array('id_comment') + ); + } + } + + // Find out who the real moderators are - for mod preferences. + $request2 = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}moderators + WHERE id_board = {int:current_board}', + array( + 'current_board' => $board, + ) + ); + $real_mods = array(); + while ($row = $smcFunc['db_fetch_assoc']($request2)) + $real_mods[] = $row['id_member']; + $smcFunc['db_free_result']($request2); + + // Send every moderator an email. + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Maybe they don't want to know?! + if (!empty($row['mod_prefs'])) + { + list(,, $pref_binary) = explode('|', $row['mod_prefs']); + if (!($pref_binary & 1) && (!($pref_binary & 2) || !in_array($row['id_member'], $real_mods))) + continue; + } + + $replacements = array( + 'TOPICSUBJECT' => $subject, + 'POSTERNAME' => $poster_name, + 'REPORTERNAME' => $reporterName, + 'TOPICLINK' => $scripturl . '?topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'], + 'REPORTLINK' => !empty($id_report) ? $scripturl . '?action=moderate;area=reports;report=' . $id_report : '', + 'COMMENT' => $_POST['comment'], + ); + + $emaildata = loadEmailTemplate('report_to_moderator', $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']); + + // Send it to the moderator. + sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], $user_info['email'], null, false, 2); + } + $smcFunc['db_free_result']($request); + + // Keep track of when the mod reports get updated, that way we know when we need to look again. + updateSettings(array('last_mod_report_action' => time())); + + // Back to the post we reported! + redirectexit('reportsent;topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg']); +} + +?> \ No newline at end of file diff --git a/Sources/SplitTopics.php b/Sources/SplitTopics.php new file mode 100644 index 0000000..89c8f7a --- /dev/null +++ b/Sources/SplitTopics.php @@ -0,0 +1,1576 @@ + 'SplitSelectTopics', + 'execute' => 'SplitExecute', + 'index' => 'SplitIndex', + 'splitSelection' => 'SplitSelectionExecute', + ); + + // ?action=splittopics;sa=LETSBREAKIT won't work, sorry. + if (empty($_REQUEST['sa']) || !isset($subActions[$_REQUEST['sa']])) + SplitIndex(); + else + $subActions[$_REQUEST['sa']](); +} + +// Part 1: General stuff. +function SplitIndex() +{ + global $txt, $topic, $context, $smcFunc, $modSettings; + + // Validate "at". + if (empty($_GET['at'])) + fatal_lang_error('numbers_one_to_nine', false); + $_GET['at'] = (int) $_GET['at']; + + // Retrieve the subject and stuff of the specific topic/message. + $request = $smcFunc['db_query']('', ' + SELECT m.subject, t.num_replies, t.unapproved_posts, t.id_first_msg, t.approved + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic}) + WHERE m.id_msg = {int:split_at}' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : ' + AND m.approved = 1') . ' + AND m.id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + 'split_at' => $_GET['at'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('cant_find_messages'); + list ($_REQUEST['subname'], $num_replies, $unapproved_posts, $id_first_msg, $approved) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // If not approved validate they can see it. + if ($modSettings['postmod_active'] && !$approved) + isAllowedTo('approve_posts'); + + // If this topic has unapproved posts, we need to count them too... + if ($modSettings['postmod_active'] && allowedTo('approve_posts')) + $num_replies += $unapproved_posts - ($approved ? 0 : 1); + + // Check if there is more than one message in the topic. (there should be.) + if ($num_replies < 1) + fatal_lang_error('topic_one_post', false); + + // Check if this is the first message in the topic (if so, the first and second option won't be available) + if ($id_first_msg == $_GET['at']) + return SplitSelectTopics(); + + // Basic template information.... + $context['message'] = array( + 'id' => $_GET['at'], + 'subject' => $_REQUEST['subname'] + ); + $context['sub_template'] = 'ask'; + $context['page_title'] = $txt['split']; +} + +// Alright, you've decided what you want to do with it.... now to do it. +function SplitExecute() +{ + global $txt, $board, $topic, $context, $user_info, $smcFunc, $modSettings; + + // Check the session to make sure they meant to do this. + checkSession(); + + // Clean up the subject. + if (!isset($_POST['subname']) || $_POST['subname'] == '') + $_POST['subname'] = $txt['new_topic']; + + // Redirect to the selector if they chose selective. + if ($_POST['step2'] == 'selective') + { + $_REQUEST['subname'] = $_POST['subname']; + return SplitSelectTopics(); + } + + $_POST['at'] = (int) $_POST['at']; + $messagesToBeSplit = array(); + + if ($_POST['step2'] == 'afterthis') + { + // Fetch the message IDs of the topic that are at or after the message. + $request = $smcFunc['db_query']('', ' + SELECT id_msg + FROM {db_prefix}messages + WHERE id_topic = {int:current_topic} + AND id_msg >= {int:split_at}', + array( + 'current_topic' => $topic, + 'split_at' => $_POST['at'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $messagesToBeSplit[] = $row['id_msg']; + $smcFunc['db_free_result']($request); + } + // Only the selected message has to be split. That should be easy. + elseif ($_POST['step2'] == 'onlythis') + $messagesToBeSplit[] = $_POST['at']; + // There's another action?! + else + fatal_lang_error('no_access', false); + + $context['old_topic'] = $topic; + $context['new_topic'] = splitTopic($topic, $messagesToBeSplit, $_POST['subname']); + $context['page_title'] = $txt['split']; +} + +// Get a selective list of topics... +function SplitSelectTopics() +{ + global $txt, $scripturl, $topic, $context, $modSettings, $original_msgs, $smcFunc, $options; + + $context['page_title'] = $txt['split'] . ' - ' . $txt['select_split_posts']; + + // Haven't selected anything have we? + $_SESSION['split_selection'][$topic] = empty($_SESSION['split_selection'][$topic]) ? array() : $_SESSION['split_selection'][$topic]; + + $context['not_selected'] = array( + 'num_messages' => 0, + 'start' => empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'], + 'messages' => array(), + ); + + $context['selected'] = array( + 'num_messages' => 0, + 'start' => empty($_REQUEST['start2']) ? 0 : (int) $_REQUEST['start2'], + 'messages' => array(), + ); + + $context['topic'] = array( + 'id' => $topic, + 'subject' => urlencode($_REQUEST['subname']), + ); + + // Some stuff for our favorite template. + $context['new_subject'] = $_REQUEST['subname']; + + // Using the "select" sub template. + $context['sub_template'] = isset($_REQUEST['xml']) ? 'split' : 'select'; + + // Are we using a custom messages per page? + $context['messages_per_page'] = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages']; + + // Get the message ID's from before the move. + if (isset($_REQUEST['xml'])) + { + $original_msgs = array( + 'not_selected' => array(), + 'selected' => array(), + ); + $request = $smcFunc['db_query']('', ' + SELECT id_msg + FROM {db_prefix}messages + WHERE id_topic = {int:current_topic}' . (empty($_SESSION['split_selection'][$topic]) ? '' : ' + AND id_msg NOT IN ({array_int:no_split_msgs})') . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : ' + AND approved = {int:is_approved}') . ' + ORDER BY id_msg DESC + LIMIT {int:start}, {int:messages_per_page}', + array( + 'current_topic' => $topic, + 'no_split_msgs' => empty($_SESSION['split_selection'][$topic]) ? array() : $_SESSION['split_selection'][$topic], + 'is_approved' => 1, + 'start' => $context['not_selected']['start'], + 'messages_per_page' => $context['messages_per_page'], + ) + ); + // You can't split the last message off. + if (empty($context['not_selected']['start']) && $smcFunc['db_num_rows']($request) <= 1 && $_REQUEST['move'] == 'down') + $_REQUEST['move'] = ''; + while ($row = $smcFunc['db_fetch_assoc']($request)) + $original_msgs['not_selected'][] = $row['id_msg']; + $smcFunc['db_free_result']($request); + if (!empty($_SESSION['split_selection'][$topic])) + { + $request = $smcFunc['db_query']('', ' + SELECT id_msg + FROM {db_prefix}messages + WHERE id_topic = {int:current_topic} + AND id_msg IN ({array_int:split_msgs})' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : ' + AND approved = {int:is_approved}') . ' + ORDER BY id_msg DESC + LIMIT {int:start}, {int:messages_per_page}', + array( + 'current_topic' => $topic, + 'split_msgs' => $_SESSION['split_selection'][$topic], + 'is_approved' => 1, + 'start' => $context['selected']['start'], + 'messages_per_page' => $context['messages_per_page'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $original_msgs['selected'][] = $row['id_msg']; + $smcFunc['db_free_result']($request); + } + } + + // (De)select a message.. + if (!empty($_REQUEST['move'])) + { + $_REQUEST['msg'] = (int) $_REQUEST['msg']; + + if ($_REQUEST['move'] == 'reset') + $_SESSION['split_selection'][$topic] = array(); + elseif ($_REQUEST['move'] == 'up') + $_SESSION['split_selection'][$topic] = array_diff($_SESSION['split_selection'][$topic], array($_REQUEST['msg'])); + else + $_SESSION['split_selection'][$topic][] = $_REQUEST['msg']; + } + + // Make sure the selection is still accurate. + if (!empty($_SESSION['split_selection'][$topic])) + { + $request = $smcFunc['db_query']('', ' + SELECT id_msg + FROM {db_prefix}messages + WHERE id_topic = {int:current_topic} + AND id_msg IN ({array_int:split_msgs})' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : ' + AND approved = {int:is_approved}'), + array( + 'current_topic' => $topic, + 'split_msgs' => $_SESSION['split_selection'][$topic], + 'is_approved' => 1, + ) + ); + $_SESSION['split_selection'][$topic] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $_SESSION['split_selection'][$topic][] = $row['id_msg']; + $smcFunc['db_free_result']($request); + } + + // Get the number of messages (not) selected to be split. + $request = $smcFunc['db_query']('', ' + SELECT ' . (empty($_SESSION['split_selection'][$topic]) ? '0' : 'm.id_msg IN ({array_int:split_msgs})') . ' AS is_selected, COUNT(*) AS num_messages + FROM {db_prefix}messages AS m + WHERE m.id_topic = {int:current_topic}' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : ' + AND approved = {int:is_approved}') . (empty($_SESSION['split_selection'][$topic]) ? '' : ' + GROUP BY is_selected'), + array( + 'current_topic' => $topic, + 'split_msgs' => !empty($_SESSION['split_selection'][$topic]) ? $_SESSION['split_selection'][$topic] : array(), + 'is_approved' => 1, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context[empty($row['is_selected']) ? 'not_selected' : 'selected']['num_messages'] = $row['num_messages']; + $smcFunc['db_free_result']($request); + + // Fix an oversized starting page (to make sure both pageindexes are properly set). + if ($context['selected']['start'] >= $context['selected']['num_messages']) + $context['selected']['start'] = $context['selected']['num_messages'] <= $context['messages_per_page'] ? 0 : ($context['selected']['num_messages'] - (($context['selected']['num_messages'] % $context['messages_per_page']) == 0 ? $context['messages_per_page'] : ($context['selected']['num_messages'] % $context['messages_per_page']))); + + // Build a page list of the not-selected topics... + $context['not_selected']['page_index'] = constructPageIndex($scripturl . '?action=splittopics;sa=selectTopics;subname=' . strtr(urlencode($_REQUEST['subname']), array('%' => '%%')) . ';topic=' . $topic . '.%1$d;start2=' . $context['selected']['start'], $context['not_selected']['start'], $context['not_selected']['num_messages'], $context['messages_per_page'], true); + // ...and one of the selected topics. + $context['selected']['page_index'] = constructPageIndex($scripturl . '?action=splittopics;sa=selectTopics;subname=' . strtr(urlencode($_REQUEST['subname']), array('%' => '%%')) . ';topic=' . $topic . '.' . $context['not_selected']['start'] . ';start2=%1$d', $context['selected']['start'], $context['selected']['num_messages'], $context['messages_per_page'], true); + + // Get the messages and stick them into an array. + $request = $smcFunc['db_query']('', ' + SELECT m.subject, IFNULL(mem.real_name, m.poster_name) AS real_name, m.poster_time, m.body, m.id_msg, m.smileys_enabled + FROM {db_prefix}messages AS m + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE m.id_topic = {int:current_topic}' . (empty($_SESSION['split_selection'][$topic]) ? '' : ' + AND id_msg NOT IN ({array_int:no_split_msgs})') . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : ' + AND approved = {int:is_approved}') . ' + ORDER BY m.id_msg DESC + LIMIT {int:start}, {int:messages_per_page}', + array( + 'current_topic' => $topic, + 'no_split_msgs' => !empty($_SESSION['split_selection'][$topic]) ? $_SESSION['split_selection'][$topic] : array(), + 'is_approved' => 1, + 'start' => $context['not_selected']['start'], + 'messages_per_page' => $context['messages_per_page'], + ) + ); + $context['messages'] = array(); + for ($counter = 0; $row = $smcFunc['db_fetch_assoc']($request); $counter ++) + { + censorText($row['subject']); + censorText($row['body']); + + $row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']); + + $context['not_selected']['messages'][$row['id_msg']] = array( + 'id' => $row['id_msg'], + 'alternate' => $counter % 2, + 'subject' => $row['subject'], + 'time' => timeformat($row['poster_time']), + 'timestamp' => forum_time(true, $row['poster_time']), + 'body' => $row['body'], + 'poster' => $row['real_name'], + ); + } + $smcFunc['db_free_result']($request); + + // Now get the selected messages. + if (!empty($_SESSION['split_selection'][$topic])) + { + // Get the messages and stick them into an array. + $request = $smcFunc['db_query']('', ' + SELECT m.subject, IFNULL(mem.real_name, m.poster_name) AS real_name, m.poster_time, m.body, m.id_msg, m.smileys_enabled + FROM {db_prefix}messages AS m + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE m.id_topic = {int:current_topic} + AND m.id_msg IN ({array_int:split_msgs})' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : ' + AND approved = {int:is_approved}') . ' + ORDER BY m.id_msg DESC + LIMIT {int:start}, {int:messages_per_page}', + array( + 'current_topic' => $topic, + 'split_msgs' => $_SESSION['split_selection'][$topic], + 'is_approved' => 1, + 'start' => $context['selected']['start'], + 'messages_per_page' => $context['messages_per_page'], + ) + ); + $context['messages'] = array(); + for ($counter = 0; $row = $smcFunc['db_fetch_assoc']($request); $counter ++) + { + censorText($row['subject']); + censorText($row['body']); + + $row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']); + + $context['selected']['messages'][$row['id_msg']] = array( + 'id' => $row['id_msg'], + 'alternate' => $counter % 2, + 'subject' => $row['subject'], + 'time' => timeformat($row['poster_time']), + 'timestamp' => forum_time(true, $row['poster_time']), + 'body' => $row['body'], + 'poster' => $row['real_name'] + ); + } + $smcFunc['db_free_result']($request); + } + + // The XMLhttp method only needs the stuff that changed, so let's compare. + if (isset($_REQUEST['xml'])) + { + $changes = array( + 'remove' => array( + 'not_selected' => array_diff($original_msgs['not_selected'], array_keys($context['not_selected']['messages'])), + 'selected' => array_diff($original_msgs['selected'], array_keys($context['selected']['messages'])), + ), + 'insert' => array( + 'not_selected' => array_diff(array_keys($context['not_selected']['messages']), $original_msgs['not_selected']), + 'selected' => array_diff(array_keys($context['selected']['messages']), $original_msgs['selected']), + ), + ); + + $context['changes'] = array(); + foreach ($changes as $change_type => $change_array) + foreach ($change_array as $section => $msg_array) + { + if (empty($msg_array)) + continue; + + foreach ($msg_array as $id_msg) + { + $context['changes'][$change_type . $id_msg] = array( + 'id' => $id_msg, + 'type' => $change_type, + 'section' => $section, + ); + if ($change_type == 'insert') + $context['changes']['insert' . $id_msg]['insert_value'] = $context[$section]['messages'][$id_msg]; + } + } + } +} + +// Actually and selectively split the topics out. +function SplitSelectionExecute() +{ + global $txt, $board, $topic, $context, $user_info; + + // Make sure the session id was passed with post. + checkSession(); + + // Default the subject in case it's blank. + if (!isset($_POST['subname']) || $_POST['subname'] == '') + $_POST['subname'] = $txt['new_topic']; + + // You must've selected some messages! Can't split out none! + if (empty($_SESSION['split_selection'][$topic])) + fatal_lang_error('no_posts_selected', false); + + $context['old_topic'] = $topic; + $context['new_topic'] = splitTopic($topic, $_SESSION['split_selection'][$topic], $_POST['subname']); + $context['page_title'] = $txt['split']; +} + +// Split a topic in two topics. +function splitTopic($split1_ID_TOPIC, $splitMessages, $new_subject) +{ + global $user_info, $topic, $board, $modSettings, $smcFunc, $txt; + + // Nothing to split? + if (empty($splitMessages)) + fatal_lang_error('no_posts_selected', false); + + // Get some board info. + $request = $smcFunc['db_query']('', ' + SELECT id_board, approved + FROM {db_prefix}topics + WHERE id_topic = {int:id_topic} + LIMIT 1', + array( + 'id_topic' => $split1_ID_TOPIC, + ) + ); + list ($id_board, $split1_approved) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Find the new first and last not in the list. (old topic) + $request = $smcFunc['db_query']('', ' + SELECT + MIN(m.id_msg) AS myid_first_msg, MAX(m.id_msg) AS myid_last_msg, COUNT(*) AS message_count, m.approved + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:id_topic}) + WHERE m.id_msg NOT IN ({array_int:no_msg_list}) + AND m.id_topic = {int:id_topic} + GROUP BY m.approved + ORDER BY m.approved DESC + LIMIT 2', + array( + 'id_topic' => $split1_ID_TOPIC, + 'no_msg_list' => $splitMessages, + ) + ); + // You can't select ALL the messages! + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('selected_all_posts', false); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Get the right first and last message dependant on approved state... + if (empty($split1_first_msg) || $row['myid_first_msg'] < $split1_first_msg) + $split1_first_msg = $row['myid_first_msg']; + if (empty($split1_last_msg) || $row['approved']) + $split1_last_msg = $row['myid_last_msg']; + + // Get the counts correct... + if ($row['approved']) + { + $split1_replies = $row['message_count'] - 1; + $split1_unapprovedposts = 0; + } + else + { + if (!isset($split1_replies)) + $split1_replies = 0; + // If the topic isn't approved then num replies must go up by one... as first post wouldn't be counted. + elseif (!$split1_approved) + $split1_replies++; + + $split1_unapprovedposts = $row['message_count']; + } + } + $smcFunc['db_free_result']($request); + $split1_firstMem = getMsgMemberID($split1_first_msg); + $split1_lastMem = getMsgMemberID($split1_last_msg); + + // Find the first and last in the list. (new topic) + $request = $smcFunc['db_query']('', ' + SELECT MIN(id_msg) AS myid_first_msg, MAX(id_msg) AS myid_last_msg, COUNT(*) AS message_count, approved + FROM {db_prefix}messages + WHERE id_msg IN ({array_int:msg_list}) + AND id_topic = {int:id_topic} + GROUP BY id_topic, approved + ORDER BY approved DESC + LIMIT 2', + array( + 'msg_list' => $splitMessages, + 'id_topic' => $split1_ID_TOPIC, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // As before get the right first and last message dependant on approved state... + if (empty($split2_first_msg) || $row['myid_first_msg'] < $split2_first_msg) + $split2_first_msg = $row['myid_first_msg']; + if (empty($split2_last_msg) || $row['approved']) + $split2_last_msg = $row['myid_last_msg']; + + // Then do the counts again... + if ($row['approved']) + { + $split2_approved = true; + $split2_replies = $row['message_count'] - 1; + $split2_unapprovedposts = 0; + } + else + { + // Should this one be approved?? + if ($split2_first_msg == $row['myid_first_msg']) + $split2_approved = false; + + if (!isset($split2_replies)) + $split2_replies = 0; + // As before, fix number of replies. + elseif (!$split2_approved) + $split2_replies++; + + $split2_unapprovedposts = $row['message_count']; + } + } + $smcFunc['db_free_result']($request); + $split2_firstMem = getMsgMemberID($split2_first_msg); + $split2_lastMem = getMsgMemberID($split2_last_msg); + + // No database changes yet, so let's double check to see if everything makes at least a little sense. + if ($split1_first_msg <= 0 || $split1_last_msg <= 0 || $split2_first_msg <= 0 || $split2_last_msg <= 0 || $split1_replies < 0 || $split2_replies < 0 || $split1_unapprovedposts < 0 || $split2_unapprovedposts < 0 || !isset($split1_approved) || !isset($split2_approved)) + fatal_lang_error('cant_find_messages'); + + // You cannot split off the first message of a topic. + if ($split1_first_msg > $split2_first_msg) + fatal_lang_error('split_first_post', false); + + // We're off to insert the new topic! Use 0 for now to avoid UNIQUE errors. + $smcFunc['db_insert']('', + '{db_prefix}topics', + array( + 'id_board' => 'int', + 'id_member_started' => 'int', + 'id_member_updated' => 'int', + 'id_first_msg' => 'int', + 'id_last_msg' => 'int', + 'num_replies' => 'int', + 'unapproved_posts' => 'int', + 'approved' => 'int', + 'is_sticky' => 'int', + ), + array( + (int) $id_board, $split2_firstMem, $split2_lastMem, 0, + 0, $split2_replies, $split2_unapprovedposts, (int) $split2_approved, 0, + ), + array('id_topic') + ); + $split2_ID_TOPIC = $smcFunc['db_insert_id']('{db_prefix}topics', 'id_topic'); + if ($split2_ID_TOPIC <= 0) + fatal_lang_error('cant_insert_topic'); + + // Move the messages over to the other topic. + $new_subject = strtr($smcFunc['htmltrim']($smcFunc['htmlspecialchars']($new_subject)), array("\r" => '', "\n" => '', "\t" => '')); + // Check the subject length. + if ($smcFunc['strlen']($new_subject) > 100) + $new_subject = $smcFunc['substr']($new_subject, 0, 100); + // Valid subject? + if ($new_subject != '') + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET + id_topic = {int:id_topic}, + subject = CASE WHEN id_msg = {int:split_first_msg} THEN {string:new_subject} ELSE {string:new_subject_replies} END + WHERE id_msg IN ({array_int:split_msgs})', + array( + 'split_msgs' => $splitMessages, + 'id_topic' => $split2_ID_TOPIC, + 'new_subject' => $new_subject, + 'split_first_msg' => $split2_first_msg, + 'new_subject_replies' => $txt['response_prefix'] . $new_subject, + ) + ); + + // Cache the new topics subject... we can do it now as all the subjects are the same! + updateStats('subject', $split2_ID_TOPIC, $new_subject); + } + + // Any associated reported posts better follow... + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_reported + SET id_topic = {int:id_topic} + WHERE id_msg IN ({array_int:split_msgs})', + array( + 'split_msgs' => $splitMessages, + 'id_topic' => $split2_ID_TOPIC, + ) + ); + + // Mess with the old topic's first, last, and number of messages. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET + num_replies = {int:num_replies}, + id_first_msg = {int:id_first_msg}, + id_last_msg = {int:id_last_msg}, + id_member_started = {int:id_member_started}, + id_member_updated = {int:id_member_updated}, + unapproved_posts = {int:unapproved_posts} + WHERE id_topic = {int:id_topic}', + array( + 'num_replies' => $split1_replies, + 'id_first_msg' => $split1_first_msg, + 'id_last_msg' => $split1_last_msg, + 'id_member_started' => $split1_firstMem, + 'id_member_updated' => $split1_lastMem, + 'unapproved_posts' => $split1_unapprovedposts, + 'id_topic' => $split1_ID_TOPIC, + ) + ); + + // Now, put the first/last message back to what they should be. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET + id_first_msg = {int:id_first_msg}, + id_last_msg = {int:id_last_msg} + WHERE id_topic = {int:id_topic}', + array( + 'id_first_msg' => $split2_first_msg, + 'id_last_msg' => $split2_last_msg, + 'id_topic' => $split2_ID_TOPIC, + ) + ); + + // If the new topic isn't approved ensure the first message flags this just in case. + if (!$split2_approved) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET approved = {int:approved} + WHERE id_msg = {int:id_msg} + AND id_topic = {int:id_topic}', + array( + 'approved' => 0, + 'id_msg' => $split2_first_msg, + 'id_topic' => $split2_ID_TOPIC, + ) + ); + + // The board has more topics now (Or more unapproved ones!). + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET ' . ($split2_approved ? ' + num_topics = num_topics + 1' : ' + unapproved_topics = unapproved_topics + 1') . ' + WHERE id_board = {int:id_board}', + array( + 'id_board' => $id_board, + ) + ); + + // Copy log topic entries. + // !!! This should really be chunked. + $request = $smcFunc['db_query']('', ' + SELECT id_member, id_msg + FROM {db_prefix}log_topics + WHERE id_topic = {int:id_topic}', + array( + 'id_topic' => (int) $split1_ID_TOPIC, + ) + ); + if ($smcFunc['db_num_rows']($request) > 0) + { + $replaceEntries = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $replaceEntries[] = array($row['id_member'], $split2_ID_TOPIC, $row['id_msg']); + + $smcFunc['db_insert']('ignore', + '{db_prefix}log_topics', + array('id_member' => 'int', 'id_topic' => 'int', 'id_msg' => 'int'), + $replaceEntries, + array('id_member', 'id_topic') + ); + unset($replaceEntries); + } + $smcFunc['db_free_result']($request); + + // Housekeeping. + updateStats('topic'); + updateLastMessages($id_board); + + logAction('split', array('topic' => $split1_ID_TOPIC, 'new_topic' => $split2_ID_TOPIC, 'board' => $id_board)); + + // Notify people that this topic has been split? + sendNotifications($split1_ID_TOPIC, 'split'); + + // Return the ID of the newly created topic. + return $split2_ID_TOPIC; +} + +// Merge two topics into one topic... useful if they have the same basic subject. +function MergeTopics() +{ + // Load the template.... + loadTemplate('SplitTopics'); + + $subActions = array( + 'done' => 'MergeDone', + 'execute' => 'MergeExecute', + 'index' => 'MergeIndex', + 'options' => 'MergeExecute', + ); + + // ?action=mergetopics;sa=LETSBREAKIT won't work, sorry. + if (empty($_REQUEST['sa']) || !isset($subActions[$_REQUEST['sa']])) + MergeIndex(); + else + $subActions[$_REQUEST['sa']](); +} + +// Merge two topics together. +function MergeIndex() +{ + global $txt, $board, $context, $smcFunc; + global $scripturl, $topic, $user_info, $modSettings; + + if (!isset($_GET['from'])) + fatal_lang_error('no_access', false); + $_GET['from'] = (int) $_GET['from']; + + $_REQUEST['targetboard'] = isset($_REQUEST['targetboard']) ? (int) $_REQUEST['targetboard'] : $board; + $context['target_board'] = $_REQUEST['targetboard']; + + // Prepare a handy query bit for approval... + if ($modSettings['postmod_active']) + { + $can_approve_boards = boardsAllowedTo('approve_posts'); + $onlyApproved = $can_approve_boards !== array(0) && !in_array($_REQUEST['targetboard'], $can_approve_boards); + } + else + $onlyApproved = false; + + // How many topics are on this board? (used for paging.) + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}topics AS t + WHERE t.id_board = {int:id_board}' . ($onlyApproved ? ' + AND t.approved = {int:is_approved}' : ''), + array( + 'id_board' => $_REQUEST['targetboard'], + 'is_approved' => 1, + ) + ); + list ($topiccount) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Make the page list. + $context['page_index'] = constructPageIndex($scripturl . '?action=mergetopics;from=' . $_GET['from'] . ';targetboard=' . $_REQUEST['targetboard'] . ';board=' . $board . '.%1$d', $_REQUEST['start'], $topiccount, $modSettings['defaultMaxTopics'], true); + + // Get the topic's subject. + $request = $smcFunc['db_query']('', ' + SELECT m.subject + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + WHERE t.id_topic = {int:id_topic} + AND t.id_board = {int:current_board}' . ($onlyApproved ? ' + AND t.approved = {int:is_approved}' : '') . ' + LIMIT 1', + array( + 'current_board' => $board, + 'id_topic' => $_GET['from'], + 'is_approved' => 1, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_board'); + list ($subject) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Tell the template a few things.. + $context['origin_topic'] = $_GET['from']; + $context['origin_subject'] = $subject; + $context['origin_js_subject'] = addcslashes(addslashes($subject), '/'); + $context['page_title'] = $txt['merge']; + + // Check which boards you have merge permissions on. + $merge_boards = boardsAllowedTo('merge_any'); + + if (empty($merge_boards)) + fatal_lang_error('cannot_merge_any', 'user'); + + // Get a list of boards they can navigate to to merge. + $request = $smcFunc['db_query']('order_by_board_order', ' + SELECT b.id_board, b.name AS board_name, c.name AS cat_name + FROM {db_prefix}boards AS b + LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat) + WHERE {query_see_board}' . (!in_array(0, $merge_boards) ? ' + AND b.id_board IN ({array_int:merge_boards})' : ''), + array( + 'merge_boards' => $merge_boards, + ) + ); + $context['boards'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['boards'][] = array( + 'id' => $row['id_board'], + 'name' => $row['board_name'], + 'category' => $row['cat_name'] + ); + $smcFunc['db_free_result']($request); + + // Get some topics to merge it with. + $request = $smcFunc['db_query']('', ' + SELECT t.id_topic, m.subject, m.id_member, IFNULL(mem.real_name, m.poster_name) AS poster_name + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE t.id_board = {int:id_board} + AND t.id_topic != {int:id_topic}' . ($onlyApproved ? ' + AND t.approved = {int:is_approved}' : '') . ' + ORDER BY {raw:sort} + LIMIT {int:offset}, {int:limit}', + array( + 'id_board' => $_REQUEST['targetboard'], + 'id_topic' => $_GET['from'], + 'sort' => (!empty($modSettings['enableStickyTopics']) ? 't.is_sticky DESC, ' : '') . 't.id_last_msg DESC', + 'offset' => $_REQUEST['start'], + 'limit' => $modSettings['defaultMaxTopics'], + 'is_approved' => 1, + ) + ); + $context['topics'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + censorText($row['subject']); + + $context['topics'][] = array( + 'id' => $row['id_topic'], + 'poster' => array( + 'id' => $row['id_member'], + 'name' => $row['poster_name'], + 'href' => empty($row['id_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member'], + 'link' => empty($row['id_member']) ? $row['poster_name'] : '' . $row['poster_name'] . '' + ), + 'subject' => $row['subject'], + 'js_subject' => addcslashes(addslashes($row['subject']), '/') + ); + } + $smcFunc['db_free_result']($request); + + if (empty($context['topics']) && count($context['boards']) <= 1) + fatal_lang_error('merge_need_more_topics'); + + $context['sub_template'] = 'merge'; +} + +// Now that the topic IDs are known, do the proper merging. +function MergeExecute($topics = array()) +{ + global $user_info, $txt, $context, $scripturl, $sourcedir; + global $smcFunc, $language, $modSettings; + + // Check the session. + checkSession('request'); + + // Handle URLs from MergeIndex. + if (!empty($_GET['from']) && !empty($_GET['to'])) + $topics = array((int) $_GET['from'], (int) $_GET['to']); + + // If we came from a form, the topic IDs came by post. + if (!empty($_POST['topics']) && is_array($_POST['topics'])) + $topics = $_POST['topics']; + + // There's nothing to merge with just one topic... + if (empty($topics) || !is_array($topics) || count($topics) == 1) + fatal_lang_error('merge_need_more_topics'); + + // Make sure every topic is numeric, or some nasty things could be done with the DB. + foreach ($topics as $id => $topic) + $topics[$id] = (int) $topic; + + // Joy of all joys, make sure they're not pi**ing about with unapproved topics they can't see :P + if ($modSettings['postmod_active']) + $can_approve_boards = boardsAllowedTo('approve_posts'); + + // Get info about the topics and polls that will be merged. + $request = $smcFunc['db_query']('', ' + SELECT + t.id_topic, t.id_board, t.id_poll, t.num_views, t.is_sticky, t.approved, t.num_replies, t.unapproved_posts, + m1.subject, m1.poster_time AS time_started, IFNULL(mem1.id_member, 0) AS id_member_started, IFNULL(mem1.real_name, m1.poster_name) AS name_started, + m2.poster_time AS time_updated, IFNULL(mem2.id_member, 0) AS id_member_updated, IFNULL(mem2.real_name, m2.poster_name) AS name_updated + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m1 ON (m1.id_msg = t.id_first_msg) + INNER JOIN {db_prefix}messages AS m2 ON (m2.id_msg = t.id_last_msg) + LEFT JOIN {db_prefix}members AS mem1 ON (mem1.id_member = m1.id_member) + LEFT JOIN {db_prefix}members AS mem2 ON (mem2.id_member = m2.id_member) + WHERE t.id_topic IN ({array_int:topic_list}) + ORDER BY t.id_first_msg + LIMIT ' . count($topics), + array( + 'topic_list' => $topics, + ) + ); + if ($smcFunc['db_num_rows']($request) < 2) + fatal_lang_error('no_topic_id'); + $num_views = 0; + $is_sticky = 0; + $boardTotals = array(); + $boards = array(); + $polls = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Make a note for the board counts... + if (!isset($boardTotals[$row['id_board']])) + $boardTotals[$row['id_board']] = array( + 'posts' => 0, + 'topics' => 0, + 'unapproved_posts' => 0, + 'unapproved_topics' => 0 + ); + + // We can't see unapproved topics here? + if ($modSettings['postmod_active'] && !$row['approved'] && $can_approve_boards != array(0) && in_array($row['id_board'], $can_approve_boards)) + continue; + elseif (!$row['approved']) + $boardTotals[$row['id_board']]['unapproved_topics']++; + else + $boardTotals[$row['id_board']]['topics']++; + + $boardTotals[$row['id_board']]['unapproved_posts'] += $row['unapproved_posts']; + $boardTotals[$row['id_board']]['posts'] += $row['num_replies'] + ($row['approved'] ? 1 : 0); + + $topic_data[$row['id_topic']] = array( + 'id' => $row['id_topic'], + 'board' => $row['id_board'], + 'poll' => $row['id_poll'], + 'num_views' => $row['num_views'], + 'subject' => $row['subject'], + 'started' => array( + 'time' => timeformat($row['time_started']), + 'timestamp' => forum_time(true, $row['time_started']), + 'href' => empty($row['id_member_started']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member_started'], + 'link' => empty($row['id_member_started']) ? $row['name_started'] : '' . $row['name_started'] . '' + ), + 'updated' => array( + 'time' => timeformat($row['time_updated']), + 'timestamp' => forum_time(true, $row['time_updated']), + 'href' => empty($row['id_member_updated']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member_updated'], + 'link' => empty($row['id_member_updated']) ? $row['name_updated'] : '' . $row['name_updated'] . '' + ) + ); + $num_views += $row['num_views']; + $boards[] = $row['id_board']; + + // If there's no poll, id_poll == 0... + if ($row['id_poll'] > 0) + $polls[] = $row['id_poll']; + // Store the id_topic with the lowest id_first_msg. + if (empty($firstTopic)) + $firstTopic = $row['id_topic']; + + $is_sticky = max($is_sticky, $row['is_sticky']); + } + $smcFunc['db_free_result']($request); + + // If we didn't get any topics then they've been messing with unapproved stuff. + if (empty($topic_data)) + fatal_lang_error('no_topic_id'); + + $boards = array_values(array_unique($boards)); + + // The parameters of MergeExecute were set, so this must've been an internal call. + if (!empty($topics)) + { + isAllowedTo('merge_any', $boards); + loadTemplate('SplitTopics'); + } + + // Get the boards a user is allowed to merge in. + $merge_boards = boardsAllowedTo('merge_any'); + if (empty($merge_boards)) + fatal_lang_error('cannot_merge_any', 'user'); + + // Make sure they can see all boards.... + $request = $smcFunc['db_query']('', ' + SELECT b.id_board + FROM {db_prefix}boards AS b + WHERE b.id_board IN ({array_int:boards}) + AND {query_see_board}' . (!in_array(0, $merge_boards) ? ' + AND b.id_board IN ({array_int:merge_boards})' : '') . ' + LIMIT ' . count($boards), + array( + 'boards' => $boards, + 'merge_boards' => $merge_boards, + ) + ); + // If the number of boards that's in the output isn't exactly the same as we've put in there, you're in trouble. + if ($smcFunc['db_num_rows']($request) != count($boards)) + fatal_lang_error('no_board'); + $smcFunc['db_free_result']($request); + + if (empty($_REQUEST['sa']) || $_REQUEST['sa'] == 'options') + { + if (count($polls) > 1) + { + $request = $smcFunc['db_query']('', ' + SELECT t.id_topic, t.id_poll, m.subject, p.question + FROM {db_prefix}polls AS p + INNER JOIN {db_prefix}topics AS t ON (t.id_poll = p.id_poll) + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + WHERE p.id_poll IN ({array_int:polls}) + LIMIT ' . count($polls), + array( + 'polls' => $polls, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['polls'][] = array( + 'id' => $row['id_poll'], + 'topic' => array( + 'id' => $row['id_topic'], + 'subject' => $row['subject'] + ), + 'question' => $row['question'], + 'selected' => $row['id_topic'] == $firstTopic + ); + $smcFunc['db_free_result']($request); + } + if (count($boards) > 1) + { + $request = $smcFunc['db_query']('', ' + SELECT id_board, name + FROM {db_prefix}boards + WHERE id_board IN ({array_int:boards}) + ORDER BY name + LIMIT ' . count($boards), + array( + 'boards' => $boards, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['boards'][] = array( + 'id' => $row['id_board'], + 'name' => $row['name'], + 'selected' => $row['id_board'] == $topic_data[$firstTopic]['board'] + ); + $smcFunc['db_free_result']($request); + } + + $context['topics'] = $topic_data; + foreach ($topic_data as $id => $topic) + $context['topics'][$id]['selected'] = $topic['id'] == $firstTopic; + + $context['page_title'] = $txt['merge']; + $context['sub_template'] = 'merge_extra_options'; + return; + } + + // Determine target board. + $target_board = count($boards) > 1 ? (int) $_REQUEST['board'] : $boards[0]; + if (!in_array($target_board, $boards)) + fatal_lang_error('no_board'); + + // Determine which poll will survive and which polls won't. + $target_poll = count($polls) > 1 ? (int) $_POST['poll'] : (count($polls) == 1 ? $polls[0] : 0); + if ($target_poll > 0 && !in_array($target_poll, $polls)) + fatal_lang_error('no_access', false); + $deleted_polls = empty($target_poll) ? $polls : array_diff($polls, array($target_poll)); + + // Determine the subject of the newly merged topic - was a custom subject specified? + if (empty($_POST['subject']) && isset($_POST['custom_subject']) && $_POST['custom_subject'] != '') + { + $target_subject = strtr($smcFunc['htmltrim']($smcFunc['htmlspecialchars']($_POST['custom_subject'])), array("\r" => '', "\n" => '', "\t" => '')); + // Keep checking the length. + if ($smcFunc['strlen']($target_subject) > 100) + $target_subject = $smcFunc['substr']($target_subject, 0, 100); + + // Nothing left - odd but pick the first topics subject. + if ($target_subject == '') + $target_subject = $topic_data[$firstTopic]['subject']; + } + // A subject was selected from the list. + elseif (!empty($topic_data[(int) $_POST['subject']]['subject'])) + $target_subject = $topic_data[(int) $_POST['subject']]['subject']; + // Nothing worked? Just take the subject of the first message. + else + $target_subject = $topic_data[$firstTopic]['subject']; + + // Get the first and last message and the number of messages.... + $request = $smcFunc['db_query']('', ' + SELECT approved, MIN(id_msg) AS first_msg, MAX(id_msg) AS last_msg, COUNT(*) AS message_count + FROM {db_prefix}messages + WHERE id_topic IN ({array_int:topics}) + GROUP BY approved + ORDER BY approved DESC', + array( + 'topics' => $topics, + ) + ); + $topic_approved = 1; + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // If this is approved, or is fully unapproved. + if ($row['approved'] || !isset($first_msg)) + { + $first_msg = $row['first_msg']; + $last_msg = $row['last_msg']; + if ($row['approved']) + { + $num_replies = $row['message_count'] - 1; + $num_unapproved = 0; + } + else + { + $topic_approved = 0; + $num_replies = 0; + $num_unapproved = $row['message_count']; + } + } + else + { + // If this has a lower first_msg then the first post is not approved and hence the number of replies was wrong! + if ($first_msg > $row['first_msg']) + { + $first_msg = $row['first_msg']; + $num_replies++; + $topic_approved = 0; + } + $num_unapproved = $row['message_count']; + } + } + $smcFunc['db_free_result']($request); + + // Ensure we have a board stat for the target board. + if (!isset($boardTotals[$target_board])) + { + $boardTotals[$target_board] = array( + 'posts' => 0, + 'topics' => 0, + 'unapproved_posts' => 0, + 'unapproved_topics' => 0 + ); + } + + // Fix the topic count stuff depending on what the new one counts as. + if ($topic_approved) + $boardTotals[$target_board]['topics']--; + else + $boardTotals[$target_board]['unapproved_topics']--; + + $boardTotals[$target_board]['unapproved_posts'] -= $num_unapproved; + $boardTotals[$target_board]['posts'] -= $topic_approved ? $num_replies + 1 : $num_replies; + + // Get the member ID of the first and last message. + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}messages + WHERE id_msg IN ({int:first_msg}, {int:last_msg}) + ORDER BY id_msg + LIMIT 2', + array( + 'first_msg' => $first_msg, + 'last_msg' => $last_msg, + ) + ); + list ($member_started) = $smcFunc['db_fetch_row']($request); + list ($member_updated) = $smcFunc['db_fetch_row']($request); + // First and last message are the same, so only row was returned. + if ($member_updated === NULL) + $member_updated = $member_started; + + $smcFunc['db_free_result']($request); + + // Assign the first topic ID to be the merged topic. + $id_topic = min($topics); + + // Delete the remaining topics. + $deleted_topics = array_diff($topics, array($id_topic)); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}topics + WHERE id_topic IN ({array_int:deleted_topics})', + array( + 'deleted_topics' => $deleted_topics, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_search_subjects + WHERE id_topic IN ({array_int:deleted_topics})', + array( + 'deleted_topics' => $deleted_topics, + ) + ); + + // Asssign the properties of the newly merged topic. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET + id_board = {int:id_board}, + id_member_started = {int:id_member_started}, + id_member_updated = {int:id_member_updated}, + id_first_msg = {int:id_first_msg}, + id_last_msg = {int:id_last_msg}, + id_poll = {int:id_poll}, + num_replies = {int:num_replies}, + unapproved_posts = {int:unapproved_posts}, + num_views = {int:num_views}, + is_sticky = {int:is_sticky}, + approved = {int:approved} + WHERE id_topic = {int:id_topic}', + array( + 'id_board' => $target_board, + 'is_sticky' => $is_sticky, + 'approved' => $topic_approved, + 'id_topic' => $id_topic, + 'id_member_started' => $member_started, + 'id_member_updated' => $member_updated, + 'id_first_msg' => $first_msg, + 'id_last_msg' => $last_msg, + 'id_poll' => $target_poll, + 'num_replies' => $num_replies, + 'unapproved_posts' => $num_unapproved, + 'num_views' => $num_views, + ) + ); + + // Grab the response prefix (like 'Re: ') in the default forum language. + if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix'))) + { + if ($language === $user_info['language']) + $context['response_prefix'] = $txt['response_prefix']; + else + { + loadLanguage('index', $language, false); + $context['response_prefix'] = $txt['response_prefix']; + loadLanguage('index'); + } + cache_put_data('response_prefix', $context['response_prefix'], 600); + } + + // Change the topic IDs of all messages that will be merged. Also adjust subjects if 'enforce subject' was checked. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET + id_topic = {int:id_topic}, + id_board = {int:target_board}' . (empty($_POST['enforce_subject']) ? '' : ', + subject = {string:subject}') . ' + WHERE id_topic IN ({array_int:topic_list})', + array( + 'topic_list' => $topics, + 'id_topic' => $id_topic, + 'target_board' => $target_board, + 'subject' => $context['response_prefix'] . $target_subject, + ) + ); + + // Any reported posts should reflect the new board. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_reported + SET + id_topic = {int:id_topic}, + id_board = {int:target_board} + WHERE id_topic IN ({array_int:topics_list})', + array( + 'topics_list' => $topics, + 'id_topic' => $id_topic, + 'target_board' => $target_board, + ) + ); + + // Change the subject of the first message... + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET subject = {string:target_subject} + WHERE id_msg = {int:first_msg}', + array( + 'first_msg' => $first_msg, + 'target_subject' => $target_subject, + ) + ); + + // Adjust all calendar events to point to the new topic. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}calendar + SET + id_topic = {int:id_topic}, + id_board = {int:target_board} + WHERE id_topic IN ({array_int:deleted_topics})', + array( + 'deleted_topics' => $deleted_topics, + 'id_topic' => $id_topic, + 'target_board' => $target_board, + ) + ); + + // Merge log topic entries. + $request = $smcFunc['db_query']('', ' + SELECT id_member, MIN(id_msg) AS new_id_msg + FROM {db_prefix}log_topics + WHERE id_topic IN ({array_int:topics}) + GROUP BY id_member', + array( + 'topics' => $topics, + ) + ); + if ($smcFunc['db_num_rows']($request) > 0) + { + $replaceEntries = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $replaceEntries[] = array($row['id_member'], $id_topic, $row['new_id_msg']); + + $smcFunc['db_insert']('replace', + '{db_prefix}log_topics', + array('id_member' => 'int', 'id_topic' => 'int', 'id_msg' => 'int'), + $replaceEntries, + array('id_member', 'id_topic') + ); + unset($replaceEntries); + + // Get rid of the old log entries. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_topics + WHERE id_topic IN ({array_int:deleted_topics})', + array( + 'deleted_topics' => $deleted_topics, + ) + ); + } + $smcFunc['db_free_result']($request); + + // Merge topic notifications. + $notifications = isset($_POST['notifications']) && is_array($_POST['notifications']) ? array_intersect($topics, $_POST['notifications']) : array(); + if (!empty($notifications)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member, MAX(sent) AS sent + FROM {db_prefix}log_notify + WHERE id_topic IN ({array_int:topics_list}) + GROUP BY id_member', + array( + 'topics_list' => $notifications, + ) + ); + if ($smcFunc['db_num_rows']($request) > 0) + { + $replaceEntries = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $replaceEntries[] = array($row['id_member'], $id_topic, 0, $row['sent']); + + $smcFunc['db_insert']('replace', + '{db_prefix}log_notify', + array('id_member' => 'int', 'id_topic' => 'int', 'id_board' => 'int', 'sent' => 'int'), + $replaceEntries, + array('id_member', 'id_topic', 'id_board') + ); + unset($replaceEntries); + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_topics + WHERE id_topic IN ({array_int:deleted_topics})', + array( + 'deleted_topics' => $deleted_topics, + ) + ); + } + $smcFunc['db_free_result']($request); + } + + // Get rid of the redundant polls. + if (!empty($deleted_polls)) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}polls + WHERE id_poll IN ({array_int:deleted_polls})', + array( + 'deleted_polls' => $deleted_polls, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}poll_choices + WHERE id_poll IN ({array_int:deleted_polls})', + array( + 'deleted_polls' => $deleted_polls, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_polls + WHERE id_poll IN ({array_int:deleted_polls})', + array( + 'deleted_polls' => $deleted_polls, + ) + ); + } + + // Cycle through each board... + foreach ($boardTotals as $id_board => $stats) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET + num_topics = CASE WHEN {int:topics} > num_topics THEN 0 ELSE num_topics - {int:topics} END, + unapproved_topics = CASE WHEN {int:unapproved_topics} > unapproved_topics THEN 0 ELSE unapproved_topics - {int:unapproved_topics} END, + num_posts = CASE WHEN {int:posts} > num_posts THEN 0 ELSE num_posts - {int:posts} END, + unapproved_posts = CASE WHEN {int:unapproved_posts} > unapproved_posts THEN 0 ELSE unapproved_posts - {int:unapproved_posts} END + WHERE id_board = {int:id_board}', + array( + 'id_board' => $id_board, + 'topics' => $stats['topics'], + 'unapproved_topics' => $stats['unapproved_topics'], + 'posts' => $stats['posts'], + 'unapproved_posts' => $stats['unapproved_posts'], + ) + ); + } + + // Determine the board the final topic resides in + $request = $smcFunc['db_query']('', ' + SELECT id_board + FROM {db_prefix}topics + WHERE id_topic = {int:id_topic} + LIMIT 1', + array( + 'id_topic' => $id_topic, + ) + ); + list($id_board) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + require_once($sourcedir . '/Subs-Post.php'); + + // Update all the statistics. + updateStats('topic'); + updateStats('subject', $id_topic, $target_subject); + updateLastMessages($boards); + + logAction('merge', array('topic' => $id_topic, 'board' => $id_board)); + + // Notify people that these topics have been merged? + sendNotifications($id_topic, 'merge'); + + // Send them to the all done page. + redirectexit('action=mergetopics;sa=done;to=' . $id_topic . ';targetboard=' . $target_board); +} + +// Tell the user the move was done properly. +function MergeDone() +{ + global $txt, $context; + + // Make sure the template knows everything... + $context['target_board'] = (int) $_GET['targetboard']; + $context['target_topic'] = (int) $_GET['to']; + + $context['page_title'] = $txt['merge']; + $context['sub_template'] = 'merge_done'; +} + +?> \ No newline at end of file diff --git a/Sources/Stats.php b/Sources/Stats.php new file mode 100644 index 0000000..37127f0 --- /dev/null +++ b/Sources/Stats.php @@ -0,0 +1,741 @@ + 1900 && $year < 2200 && $month >= 1 && $month <= 12) + $_SESSION['expanded_stats'][$year][] = $month; + } + elseif (!empty($_REQUEST['collapse'])) + { + $context['robot_no_index'] = true; + + $month = (int) substr($_REQUEST['collapse'], 4); + $year = (int) substr($_REQUEST['collapse'], 0, 4); + if (!empty($_SESSION['expanded_stats'][$year])) + $_SESSION['expanded_stats'][$year] = array_diff($_SESSION['expanded_stats'][$year], array($month)); + } + + // Handle the XMLHttpRequest. + if (isset($_REQUEST['xml'])) + { + // Collapsing stats only needs adjustments of the session variables. + if (!empty($_REQUEST['collapse'])) + obExit(false); + + $context['sub_template'] = 'stats'; + getDailyStats('YEAR(date) = {int:year} AND MONTH(date) = {int:month}', array('year' => $year, 'month' => $month)); + $context['yearly'][$year]['months'][$month]['date'] = array( + 'month' => sprintf('%02d', $month), + 'year' => $year, + ); + return; + } + + loadLanguage('Stats'); + loadTemplate('Stats'); + + // Build the link tree...... + $context['linktree'][] = array( + 'url' => $scripturl . '?action=stats', + 'name' => $txt['stats_center'] + ); + $context['page_title'] = $context['forum_name'] . ' - ' . $txt['stats_center']; + + $context['show_member_list'] = allowedTo('view_mlist'); + + // Get averages... + $result = $smcFunc['db_query']('', ' + SELECT + SUM(posts) AS posts, SUM(topics) AS topics, SUM(registers) AS registers, + SUM(most_on) AS most_on, MIN(date) AS date, SUM(hits) AS hits + FROM {db_prefix}log_activity', + array( + ) + ); + $row = $smcFunc['db_fetch_assoc']($result); + $smcFunc['db_free_result']($result); + + // This would be the amount of time the forum has been up... in days... + $total_days_up = ceil((time() - strtotime($row['date'])) / (60 * 60 * 24)); + + $context['average_posts'] = comma_format(round($row['posts'] / $total_days_up, 2)); + $context['average_topics'] = comma_format(round($row['topics'] / $total_days_up, 2)); + $context['average_members'] = comma_format(round($row['registers'] / $total_days_up, 2)); + $context['average_online'] = comma_format(round($row['most_on'] / $total_days_up, 2)); + $context['average_hits'] = comma_format(round($row['hits'] / $total_days_up, 2)); + + $context['num_hits'] = comma_format($row['hits'], 0); + + // How many users are online now. + $result = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_online', + array( + ) + ); + list ($context['users_online']) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + // Statistics such as number of boards, categories, etc. + $result = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}boards AS b + WHERE b.redirect = {string:blank_redirect}', + array( + 'blank_redirect' => '', + ) + ); + list ($context['num_boards']) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + $result = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}categories AS c', + array( + ) + ); + list ($context['num_categories']) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + // Format the numbers nicely. + $context['users_online'] = comma_format($context['users_online']); + $context['num_boards'] = comma_format($context['num_boards']); + $context['num_categories'] = comma_format($context['num_categories']); + + $context['num_members'] = comma_format($modSettings['totalMembers']); + $context['num_posts'] = comma_format($modSettings['totalMessages']); + $context['num_topics'] = comma_format($modSettings['totalTopics']); + $context['most_members_online'] = array( + 'number' => comma_format($modSettings['mostOnline']), + 'date' => timeformat($modSettings['mostDate']) + ); + $context['latest_member'] = &$context['common_stats']['latest_member']; + + // Male vs. female ratio - let's calculate this only every four minutes. + if (($context['gender'] = cache_get_data('stats_gender', 240)) == null) + { + $result = $smcFunc['db_query']('', ' + SELECT COUNT(*) AS total_members, gender + FROM {db_prefix}members + GROUP BY gender', + array( + ) + ); + $context['gender'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + // Assuming we're telling... male or female? + if (!empty($row['gender'])) + $context['gender'][$row['gender'] == 2 ? 'females' : 'males'] = $row['total_members']; + } + $smcFunc['db_free_result']($result); + + // Set these two zero if the didn't get set at all. + if (empty($context['gender']['males'])) + $context['gender']['males'] = 0; + if (empty($context['gender']['females'])) + $context['gender']['females'] = 0; + + // Try and come up with some "sensible" default states in case of a non-mixed board. + if ($context['gender']['males'] == $context['gender']['females']) + $context['gender']['ratio'] = '1:1'; + elseif ($context['gender']['males'] == 0) + $context['gender']['ratio'] = '0:1'; + elseif ($context['gender']['females'] == 0) + $context['gender']['ratio'] = '1:0'; + elseif ($context['gender']['males'] > $context['gender']['females']) + $context['gender']['ratio'] = round($context['gender']['males'] / $context['gender']['females'], 1) . ':1'; + elseif ($context['gender']['females'] > $context['gender']['males']) + $context['gender']['ratio'] = '1:' . round($context['gender']['females'] / $context['gender']['males'], 1); + + cache_put_data('stats_gender', $context['gender'], 240); + } + + $date = strftime('%Y-%m-%d', forum_time(false)); + + // Members online so far today. + $result = $smcFunc['db_query']('', ' + SELECT most_on + FROM {db_prefix}log_activity + WHERE date = {date:today_date} + LIMIT 1', + array( + 'today_date' => $date, + ) + ); + list ($context['online_today']) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + $context['online_today'] = comma_format((int) $context['online_today']); + + // Poster top 10. + $members_result = $smcFunc['db_query']('', ' + SELECT id_member, real_name, posts + FROM {db_prefix}members + WHERE posts > {int:no_posts} + ORDER BY posts DESC + LIMIT 10', + array( + 'no_posts' => 0, + ) + ); + $context['top_posters'] = array(); + $max_num_posts = 1; + while ($row_members = $smcFunc['db_fetch_assoc']($members_result)) + { + $context['top_posters'][] = array( + 'name' => $row_members['real_name'], + 'id' => $row_members['id_member'], + 'num_posts' => $row_members['posts'], + 'href' => $scripturl . '?action=profile;u=' . $row_members['id_member'], + 'link' => '' . $row_members['real_name'] . '' + ); + + if ($max_num_posts < $row_members['posts']) + $max_num_posts = $row_members['posts']; + } + $smcFunc['db_free_result']($members_result); + + foreach ($context['top_posters'] as $i => $poster) + { + $context['top_posters'][$i]['post_percent'] = round(($poster['num_posts'] * 100) / $max_num_posts); + $context['top_posters'][$i]['num_posts'] = comma_format($context['top_posters'][$i]['num_posts']); + } + + // Board top 10. + $boards_result = $smcFunc['db_query']('', ' + SELECT id_board, name, num_posts + FROM {db_prefix}boards AS b + WHERE {query_see_board}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' + AND b.id_board != {int:recycle_board}' : '') . ' + AND b.redirect = {string:blank_redirect} + ORDER BY num_posts DESC + LIMIT 10', + array( + 'recycle_board' => $modSettings['recycle_board'], + 'blank_redirect' => '', + ) + ); + $context['top_boards'] = array(); + $max_num_posts = 1; + while ($row_board = $smcFunc['db_fetch_assoc']($boards_result)) + { + $context['top_boards'][] = array( + 'id' => $row_board['id_board'], + 'name' => $row_board['name'], + 'num_posts' => $row_board['num_posts'], + 'href' => $scripturl . '?board=' . $row_board['id_board'] . '.0', + 'link' => '' . $row_board['name'] . '' + ); + + if ($max_num_posts < $row_board['num_posts']) + $max_num_posts = $row_board['num_posts']; + } + $smcFunc['db_free_result']($boards_result); + + foreach ($context['top_boards'] as $i => $board) + { + $context['top_boards'][$i]['post_percent'] = round(($board['num_posts'] * 100) / $max_num_posts); + $context['top_boards'][$i]['num_posts'] = comma_format($context['top_boards'][$i]['num_posts']); + } + + // Are you on a larger forum? If so, let's try to limit the number of topics we search through. + if ($modSettings['totalMessages'] > 100000) + { + $request = $smcFunc['db_query']('', ' + SELECT id_topic + FROM {db_prefix}topics + WHERE num_replies != {int:no_replies}' . ($modSettings['postmod_active'] ? ' + AND approved = {int:is_approved}' : '') . ' + ORDER BY num_replies DESC + LIMIT 100', + array( + 'no_replies' => 0, + 'is_approved' => 1, + ) + ); + $topic_ids = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $topic_ids[] = $row['id_topic']; + $smcFunc['db_free_result']($request); + } + else + $topic_ids = array(); + + // Topic replies top 10. + $topic_reply_result = $smcFunc['db_query']('', ' + SELECT m.subject, t.num_replies, t.id_board, t.id_topic, b.name + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' + AND b.id_board != {int:recycle_board}' : '') . ') + WHERE {query_see_board}' . (!empty($topic_ids) ? ' + AND t.id_topic IN ({array_int:topic_list})' : ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved}' : '')) . ' + ORDER BY t.num_replies DESC + LIMIT 10', + array( + 'topic_list' => $topic_ids, + 'recycle_board' => $modSettings['recycle_board'], + 'is_approved' => 1, + ) + ); + $context['top_topics_replies'] = array(); + $max_num_replies = 1; + while ($row_topic_reply = $smcFunc['db_fetch_assoc']($topic_reply_result)) + { + censorText($row_topic_reply['subject']); + + $context['top_topics_replies'][] = array( + 'id' => $row_topic_reply['id_topic'], + 'board' => array( + 'id' => $row_topic_reply['id_board'], + 'name' => $row_topic_reply['name'], + 'href' => $scripturl . '?board=' . $row_topic_reply['id_board'] . '.0', + 'link' => '' . $row_topic_reply['name'] . '' + ), + 'subject' => $row_topic_reply['subject'], + 'num_replies' => $row_topic_reply['num_replies'], + 'href' => $scripturl . '?topic=' . $row_topic_reply['id_topic'] . '.0', + 'link' => '' . $row_topic_reply['subject'] . '' + ); + + if ($max_num_replies < $row_topic_reply['num_replies']) + $max_num_replies = $row_topic_reply['num_replies']; + } + $smcFunc['db_free_result']($topic_reply_result); + + foreach ($context['top_topics_replies'] as $i => $topic) + { + $context['top_topics_replies'][$i]['post_percent'] = round(($topic['num_replies'] * 100) / $max_num_replies); + $context['top_topics_replies'][$i]['num_replies'] = comma_format($context['top_topics_replies'][$i]['num_replies']); + } + + // Large forums may need a bit more prodding... + if ($modSettings['totalMessages'] > 100000) + { + $request = $smcFunc['db_query']('', ' + SELECT id_topic + FROM {db_prefix}topics + WHERE num_views != {int:no_views} + ORDER BY num_views DESC + LIMIT 100', + array( + 'no_views' => 0, + ) + ); + $topic_ids = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $topic_ids[] = $row['id_topic']; + $smcFunc['db_free_result']($request); + } + else + $topic_ids = array(); + + // Topic views top 10. + $topic_view_result = $smcFunc['db_query']('', ' + SELECT m.subject, t.num_views, t.id_board, t.id_topic, b.name + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' + AND b.id_board != {int:recycle_board}' : '') . ') + WHERE {query_see_board}' . (!empty($topic_ids) ? ' + AND t.id_topic IN ({array_int:topic_list})' : ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved}' : '')) . ' + ORDER BY t.num_views DESC + LIMIT 10', + array( + 'topic_list' => $topic_ids, + 'recycle_board' => $modSettings['recycle_board'], + 'is_approved' => 1, + ) + ); + $context['top_topics_views'] = array(); + $max_num_views = 1; + while ($row_topic_views = $smcFunc['db_fetch_assoc']($topic_view_result)) + { + censorText($row_topic_views['subject']); + + $context['top_topics_views'][] = array( + 'id' => $row_topic_views['id_topic'], + 'board' => array( + 'id' => $row_topic_views['id_board'], + 'name' => $row_topic_views['name'], + 'href' => $scripturl . '?board=' . $row_topic_views['id_board'] . '.0', + 'link' => '' . $row_topic_views['name'] . '' + ), + 'subject' => $row_topic_views['subject'], + 'num_views' => $row_topic_views['num_views'], + 'href' => $scripturl . '?topic=' . $row_topic_views['id_topic'] . '.0', + 'link' => '' . $row_topic_views['subject'] . '' + ); + + if ($max_num_views < $row_topic_views['num_views']) + $max_num_views = $row_topic_views['num_views']; + } + $smcFunc['db_free_result']($topic_view_result); + + foreach ($context['top_topics_views'] as $i => $topic) + { + $context['top_topics_views'][$i]['post_percent'] = round(($topic['num_views'] * 100) / $max_num_views); + $context['top_topics_views'][$i]['num_views'] = comma_format($context['top_topics_views'][$i]['num_views']); + } + + // Try to cache this when possible, because it's a little unavoidably slow. + if (($members = cache_get_data('stats_top_starters', 360)) == null) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member_started, COUNT(*) AS hits + FROM {db_prefix}topics' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' + WHERE id_board != {int:recycle_board}' : '') . ' + GROUP BY id_member_started + ORDER BY hits DESC + LIMIT 20', + array( + 'recycle_board' => $modSettings['recycle_board'], + ) + ); + $members = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $members[$row['id_member_started']] = $row['hits']; + $smcFunc['db_free_result']($request); + + cache_put_data('stats_top_starters', $members, 360); + } + + if (empty($members)) + $members = array(0 => 0); + + // Topic poster top 10. + $members_result = $smcFunc['db_query']('top_topic_starters', ' + SELECT id_member, real_name + FROM {db_prefix}members + WHERE id_member IN ({array_int:member_list}) + ORDER BY FIND_IN_SET(id_member, {string:top_topic_posters}) + LIMIT 10', + array( + 'member_list' => array_keys($members), + 'top_topic_posters' => implode(',', array_keys($members)), + ) + ); + $context['top_starters'] = array(); + $max_num_topics = 1; + while ($row_members = $smcFunc['db_fetch_assoc']($members_result)) + { + $context['top_starters'][] = array( + 'name' => $row_members['real_name'], + 'id' => $row_members['id_member'], + 'num_topics' => $members[$row_members['id_member']], + 'href' => $scripturl . '?action=profile;u=' . $row_members['id_member'], + 'link' => '' . $row_members['real_name'] . '' + ); + + if ($max_num_topics < $members[$row_members['id_member']]) + $max_num_topics = $members[$row_members['id_member']]; + } + $smcFunc['db_free_result']($members_result); + + foreach ($context['top_starters'] as $i => $topic) + { + $context['top_starters'][$i]['post_percent'] = round(($topic['num_topics'] * 100) / $max_num_topics); + $context['top_starters'][$i]['num_topics'] = comma_format($context['top_starters'][$i]['num_topics']); + } + + // Time online top 10. + $temp = cache_get_data('stats_total_time_members', 600); + $members_result = $smcFunc['db_query']('', ' + SELECT id_member, real_name, total_time_logged_in + FROM {db_prefix}members' . (!empty($temp) ? ' + WHERE id_member IN ({array_int:member_list_cached})' : '') . ' + ORDER BY total_time_logged_in DESC + LIMIT 20', + array( + 'member_list_cached' => $temp, + ) + ); + $context['top_time_online'] = array(); + $temp2 = array(); + $max_time_online = 1; + while ($row_members = $smcFunc['db_fetch_assoc']($members_result)) + { + $temp2[] = (int) $row_members['id_member']; + if (count($context['top_time_online']) >= 10) + continue; + + // Figure out the days, hours and minutes. + $timeDays = floor($row_members['total_time_logged_in'] / 86400); + $timeHours = floor(($row_members['total_time_logged_in'] % 86400) / 3600); + + // Figure out which things to show... (days, hours, minutes, etc.) + $timelogged = ''; + if ($timeDays > 0) + $timelogged .= $timeDays . $txt['totalTimeLogged5']; + if ($timeHours > 0) + $timelogged .= $timeHours . $txt['totalTimeLogged6']; + $timelogged .= floor(($row_members['total_time_logged_in'] % 3600) / 60) . $txt['totalTimeLogged7']; + + $context['top_time_online'][] = array( + 'id' => $row_members['id_member'], + 'name' => $row_members['real_name'], + 'time_online' => $timelogged, + 'seconds_online' => $row_members['total_time_logged_in'], + 'href' => $scripturl . '?action=profile;u=' . $row_members['id_member'], + 'link' => '' . $row_members['real_name'] . '' + ); + + if ($max_time_online < $row_members['total_time_logged_in']) + $max_time_online = $row_members['total_time_logged_in']; + } + $smcFunc['db_free_result']($members_result); + + foreach ($context['top_time_online'] as $i => $member) + $context['top_time_online'][$i]['time_percent'] = round(($member['seconds_online'] * 100) / $max_time_online); + + // Cache the ones we found for a bit, just so we don't have to look again. + if ($temp !== $temp2) + cache_put_data('stats_total_time_members', $temp2, 480); + + // Activity by month. + $months_result = $smcFunc['db_query']('', ' + SELECT + YEAR(date) AS stats_year, MONTH(date) AS stats_month, SUM(hits) AS hits, SUM(registers) AS registers, SUM(topics) AS topics, SUM(posts) AS posts, MAX(most_on) AS most_on, COUNT(*) AS num_days + FROM {db_prefix}log_activity + GROUP BY stats_year, stats_month', + array() + ); + + $context['yearly'] = array(); + while ($row_months = $smcFunc['db_fetch_assoc']($months_result)) + { + $ID_MONTH = $row_months['stats_year'] . sprintf('%02d', $row_months['stats_month']); + $expanded = !empty($_SESSION['expanded_stats'][$row_months['stats_year']]) && in_array($row_months['stats_month'], $_SESSION['expanded_stats'][$row_months['stats_year']]); + + if (!isset($context['yearly'][$row_months['stats_year']])) + $context['yearly'][$row_months['stats_year']] = array( + 'year' => $row_months['stats_year'], + 'new_topics' => 0, + 'new_posts' => 0, + 'new_members' => 0, + 'most_members_online' => 0, + 'hits' => 0, + 'num_months' => 0, + 'months' => array(), + 'expanded' => false, + 'current_year' => $row_months['stats_year'] == date('Y'), + ); + + $context['yearly'][$row_months['stats_year']]['months'][(int) $row_months['stats_month']] = array( + 'id' => $ID_MONTH, + 'date' => array( + 'month' => sprintf('%02d', $row_months['stats_month']), + 'year' => $row_months['stats_year'] + ), + 'href' => $scripturl . '?action=stats;' . ($expanded ? 'collapse' : 'expand') . '=' . $ID_MONTH . '#m' . $ID_MONTH, + 'link' => '' . $txt['months'][(int) $row_months['stats_month']] . ' ' . $row_months['stats_year'] . '', + 'month' => $txt['months'][(int) $row_months['stats_month']], + 'year' => $row_months['stats_year'], + 'new_topics' => comma_format($row_months['topics']), + 'new_posts' => comma_format($row_months['posts']), + 'new_members' => comma_format($row_months['registers']), + 'most_members_online' => comma_format($row_months['most_on']), + 'hits' => comma_format($row_months['hits']), + 'num_days' => $row_months['num_days'], + 'days' => array(), + 'expanded' => $expanded + ); + + $context['yearly'][$row_months['stats_year']]['new_topics'] += $row_months['topics']; + $context['yearly'][$row_months['stats_year']]['new_posts'] += $row_months['posts']; + $context['yearly'][$row_months['stats_year']]['new_members'] += $row_months['registers']; + $context['yearly'][$row_months['stats_year']]['hits'] += $row_months['hits']; + $context['yearly'][$row_months['stats_year']]['num_months']++; + $context['yearly'][$row_months['stats_year']]['expanded'] |= $expanded; + $context['yearly'][$row_months['stats_year']]['most_members_online'] = max($context['yearly'][$row_months['stats_year']]['most_members_online'], $row_months['most_on']); + } + + krsort($context['yearly']); + + $context['collapsed_years'] = array(); + foreach ($context['yearly'] as $year => $data) + { + // This gets rid of the filesort on the query ;). + krsort($context['yearly'][$year]['months']); + + $context['yearly'][$year]['new_topics'] = comma_format($data['new_topics']); + $context['yearly'][$year]['new_posts'] = comma_format($data['new_posts']); + $context['yearly'][$year]['new_members'] = comma_format($data['new_members']); + $context['yearly'][$year]['most_members_online'] = comma_format($data['most_members_online']); + $context['yearly'][$year]['hits'] = comma_format($data['hits']); + + // Keep a list of collapsed years. + if (!$data['expanded'] && !$data['current_year']) + $context['collapsed_years'][] = $year; + } + + if (empty($_SESSION['expanded_stats'])) + return; + + $condition_text = array(); + $condition_params = array(); + foreach ($_SESSION['expanded_stats'] as $year => $months) + if (!empty($months)) + { + $condition_text[] = 'YEAR(date) = {int:year_' . $year . '} AND MONTH(date) IN ({array_int:months_' . $year . '})'; + $condition_params['year_' . $year] = $year; + $condition_params['months_' . $year] = $months; + } + + // No daily stats to even look at? + if (empty($condition_text)) + return; + + getDailyStats(implode(' OR ', $condition_text), $condition_params); +} + +function getDailyStats($condition_string, $condition_parameters = array()) +{ + global $context, $smcFunc; + + // Activity by day. + $days_result = $smcFunc['db_query']('', ' + SELECT YEAR(date) AS stats_year, MONTH(date) AS stats_month, DAYOFMONTH(date) AS stats_day, topics, posts, registers, most_on, hits + FROM {db_prefix}log_activity + WHERE ' . $condition_string . ' + ORDER BY stats_day ASC', + $condition_parameters + ); + while ($row_days = $smcFunc['db_fetch_assoc']($days_result)) + $context['yearly'][$row_days['stats_year']]['months'][(int) $row_days['stats_month']]['days'][] = array( + 'day' => sprintf('%02d', $row_days['stats_day']), + 'month' => sprintf('%02d', $row_days['stats_month']), + 'year' => $row_days['stats_year'], + 'new_topics' => comma_format($row_days['topics']), + 'new_posts' => comma_format($row_days['posts']), + 'new_members' => comma_format($row_days['registers']), + 'most_members_online' => comma_format($row_days['most_on']), + 'hits' => comma_format($row_days['hits']) + ); + $smcFunc['db_free_result']($days_result); +} + +// This is the function which returns stats to simplemachines.org IF enabled! +// See http://www.simplemachines.org/about/stats.php for more info. +function SMStats() +{ + global $modSettings, $user_info, $forum_version, $sourcedir; + + // First, is it disabled? + if (empty($modSettings['allow_sm_stats'])) + die(); + + // Are we saying who we are, and are we right? (OR an admin) + if (!$user_info['is_admin'] && (!isset($_GET['sid']) || $_GET['sid'] != $modSettings['allow_sm_stats'])) + die(); + + // Verify the referer... + if (!$user_info['is_admin'] && (!isset($_SERVER['HTTP_REFERER']) || md5($_SERVER['HTTP_REFERER']) != '746cb59a1a0d5cf4bd240e5a67c73085')) + die(); + + // Get some server versions. + require_once($sourcedir . '/Subs-Admin.php'); + $checkFor = array( + 'php', + 'db_server', + ); + $serverVersions = getServerVersions($checkFor); + + // Get the actual stats. + $stats_to_send = array( + 'UID' => $modSettings['allow_sm_stats'], + 'time_added' => time(), + 'members' => $modSettings['totalMembers'], + 'messages' => $modSettings['totalMessages'], + 'topics' => $modSettings['totalTopics'], + 'boards' => 0, + 'php_version' => $serverVersions['php']['version'], + 'database_type' => strtolower($serverVersions['db_server']['title']), + 'database_version' => $serverVersions['db_server']['version'], + 'smf_version' => $forum_version, + 'smfd_version' => $modSettings['smfVersion'], + ); + + // Encode all the data, for security. + foreach ($stats_to_send as $k => $v) + $stats_to_send[$k] = urlencode($k) . '=' . urlencode($v); + + // Turn this into the query string! + $stats_to_send = implode('&', $stats_to_send); + + // If we're an admin, just plonk them out. + if ($user_info['is_admin']) + echo $stats_to_send; + else + { + // Connect to the collection script. + $fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr); + if ($fp) + { + $length = strlen($stats_to_send); + + $out = 'POST /smf/stats/collect_stats.php HTTP/1.1' . "\r\n"; + $out .= 'Host: www.simplemachines.org' . "\r\n"; + $out .= 'Content-Type: application/x-www-form-urlencoded' . "\r\n"; + $out .= 'Content-Length: ' . $length . "\r\n\r\n"; + $out .= $stats_to_send . "\r\n"; + $out .= 'Connection: Close' . "\r\n\r\n"; + fwrite($fp, $out); + fclose($fp); + } + } + + // Die. + die('OK'); +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Admin.php b/Sources/Subs-Admin.php new file mode 100644 index 0000000..51dcc0e --- /dev/null +++ b/Sources/Subs-Admin.php @@ -0,0 +1,550 @@ + $txt['support_versions_gd'], 'version' => $temp['GD Version']); + } + + // Now lets check for the Database. + if (in_array('db_server', $checkFor)) + { + db_extend(); + if (!isset($db_connection) || $db_connection === false) + trigger_error('getServerVersions(): you need to be connected to the database in order to get its server version', E_USER_NOTICE); + else + { + $versions['db_server'] = array('title' => sprintf($txt['support_versions_db'], $smcFunc['db_title']), 'version' => ''); + $versions['db_server']['version'] = $smcFunc['db_get_version'](); + } + } + + // If we're using memcache we need the server info. + if (empty($memcached) && function_exists('memcache_get') && isset($modSettings['cache_memcached']) && trim($modSettings['cache_memcached']) != '') + get_memcached_server(); + + // Check to see if we have any accelerators installed... + if (in_array('mmcache', $checkFor) && defined('MMCACHE_VERSION')) + $versions['mmcache'] = array('title' => 'Turck MMCache', 'version' => MMCACHE_VERSION); + if (in_array('eaccelerator', $checkFor) && defined('EACCELERATOR_VERSION')) + $versions['eaccelerator'] = array('title' => 'eAccelerator', 'version' => EACCELERATOR_VERSION); + if (in_array('phpa', $checkFor) && isset($_PHPA)) + $versions['phpa'] = array('title' => 'ionCube PHP-Accelerator', 'version' => $_PHPA['VERSION']); + if (in_array('apc', $checkFor) && extension_loaded('apc')) + $versions['apc'] = array('title' => 'Alternative PHP Cache', 'version' => phpversion('apc')); + if (in_array('memcache', $checkFor) && function_exists('memcache_set')) + $versions['memcache'] = array('title' => 'Memcached', 'version' => empty($memcached) ? '???' : memcache_get_version($memcached)); + if (in_array('xcache', $checkFor) && function_exists('xcache_set')) + $versions['xcache'] = array('title' => 'XCache', 'version' => XCACHE_VERSION); + if (in_array('php', $checkFor)) + $versions['php'] = array('title' => 'PHP', 'version' => PHP_VERSION); + + if (in_array('server', $checkFor)) + $versions['server'] = array('title' => $txt['support_versions_server'], 'version' => $_SERVER['SERVER_SOFTWARE']); + + return $versions; +} + +// Search through source, theme and language files to determine their version. +function getFileVersions(&$versionOptions) +{ + global $boarddir, $sourcedir, $settings; + + // Default place to find the languages would be the default theme dir. + $lang_dir = $settings['default_theme_dir'] . '/languages'; + + $version_info = array( + 'file_versions' => array(), + 'default_template_versions' => array(), + 'template_versions' => array(), + 'default_language_versions' => array(), + ); + + // Find the version in SSI.php's file header. + if (!empty($versionOptions['include_ssi']) && file_exists($boarddir . '/SSI.php')) + { + $fp = fopen($boarddir . '/SSI.php', 'rb'); + $header = fread($fp, 4096); + fclose($fp); + + // The comment looks rougly like... that. + if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) + $version_info['file_versions']['SSI.php'] = $match[1]; + // Not found! This is bad. + else + $version_info['file_versions']['SSI.php'] = '??'; + } + + // Do the paid subscriptions handler? + if (!empty($versionOptions['include_subscriptions']) && file_exists($boarddir . '/subscriptions.php')) + { + $fp = fopen($boarddir . '/subscriptions.php', 'rb'); + $header = fread($fp, 4096); + fclose($fp); + + // Found it? + if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) + $version_info['file_versions']['subscriptions.php'] = $match[1]; + // If we haven't how do we all get paid? + else + $version_info['file_versions']['subscriptions.php'] = '??'; + } + + // Load all the files in the Sources directory, except this file and the redirect. + $sources_dir = dir($sourcedir); + while ($entry = $sources_dir->read()) + { + if (substr($entry, -4) === '.php' && !is_dir($sourcedir . '/' . $entry) && $entry !== 'index.php') + { + // Read the first 4k from the file.... enough for the header. + $fp = fopen($sourcedir . '/' . $entry, 'rb'); + $header = fread($fp, 4096); + fclose($fp); + + // Look for the version comment in the file header. + if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) + $version_info['file_versions'][$entry] = $match[1]; + // It wasn't found, but the file was... show a '??'. + else + $version_info['file_versions'][$entry] = '??'; + } + } + $sources_dir->close(); + + // Load all the files in the default template directory - and the current theme if applicable. + $directories = array('default_template_versions' => $settings['default_theme_dir']); + if ($settings['theme_id'] != 1) + $directories += array('template_versions' => $settings['theme_dir']); + + foreach ($directories as $type => $dirname) + { + $this_dir = dir($dirname); + while ($entry = $this_dir->read()) + { + if (substr($entry, -12) == 'template.php' && !is_dir($dirname . '/' . $entry)) + { + // Read the first 768 bytes from the file.... enough for the header. + $fp = fopen($dirname . '/' . $entry, 'rb'); + $header = fread($fp, 768); + fclose($fp); + + // Look for the version comment in the file header. + if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) + $version_info[$type][$entry] = $match[1]; + // It wasn't found, but the file was... show a '??'. + else + $version_info[$type][$entry] = '??'; + } + } + $this_dir->close(); + } + + // Load up all the files in the default language directory and sort by language. + $this_dir = dir($lang_dir); + while ($entry = $this_dir->read()) + { + if (substr($entry, -4) == '.php' && $entry != 'index.php' && !is_dir($lang_dir . '/' . $entry)) + { + // Read the first 768 bytes from the file.... enough for the header. + $fp = fopen($lang_dir . '/' . $entry, 'rb'); + $header = fread($fp, 768); + fclose($fp); + + // Split the file name off into useful bits. + list ($name, $language) = explode('.', $entry); + + // Look for the version comment in the file header. + if (preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '(?:[\s]{2}|\*/)~i', $header, $match) == 1) + $version_info['default_language_versions'][$language][$name] = $match[1]; + // It wasn't found, but the file was... show a '??'. + else + $version_info['default_language_versions'][$language][$name] = '??'; + } + } + $this_dir->close(); + + // Sort the file versions by filename. + if (!empty($versionOptions['sort_results'])) + { + ksort($version_info['file_versions']); + ksort($version_info['default_template_versions']); + ksort($version_info['template_versions']); + ksort($version_info['default_language_versions']); + + // For languages sort each language too. + foreach ($version_info['default_language_versions'] as $language => $dummy) + ksort($version_info['default_language_versions'][$language]); + } + return $version_info; +} + +// Update the Settings.php file. +function updateSettingsFile($config_vars) +{ + global $boarddir, $cachedir; + + // When is Settings.php last changed? + $last_settings_change = filemtime($boarddir . '/Settings.php'); + + // Load the file. Break it up based on \r or \n, and then clean out extra characters. + $settingsArray = trim(file_get_contents($boarddir . '/Settings.php')); + if (strpos($settingsArray, "\n") !== false) + $settingsArray = explode("\n", $settingsArray); + elseif (strpos($settingsArray, "\r") !== false) + $settingsArray = explode("\r", $settingsArray); + else + return; + + // Make sure we got a good file. + if (count($config_vars) == 1 && isset($config_vars['db_last_error'])) + { + $temp = trim(implode("\n", $settingsArray)); + if (substr($temp, 0, 5) != '') + return; + if (strpos($temp, 'sourcedir') === false || strpos($temp, 'boarddir') === false || strpos($temp, 'cookiename') === false) + return; + } + + // Presumably, the file has to have stuff in it for this function to be called :P. + if (count($settingsArray) < 10) + return; + + foreach ($settingsArray as $k => $dummy) + $settingsArray[$k] = strtr($dummy, array("\r" => '')) . "\n"; + + for ($i = 0, $n = count($settingsArray); $i < $n; $i++) + { + // Don't trim or bother with it if it's not a variable. + if (substr($settingsArray[$i], 0, 1) != '$') + continue; + + $settingsArray[$i] = trim($settingsArray[$i]) . "\n"; + + // Look through the variables to set.... + foreach ($config_vars as $var => $val) + { + if (strncasecmp($settingsArray[$i], '$' . $var, 1 + strlen($var)) == 0) + { + $comment = strstr(substr($settingsArray[$i], strpos($settingsArray[$i], ';')), '#'); + $settingsArray[$i] = '$' . $var . ' = ' . $val . ';' . ($comment == '' ? '' : "\t\t" . rtrim($comment)) . "\n"; + + // This one's been 'used', so to speak. + unset($config_vars[$var]); + } + } + + if (substr(trim($settingsArray[$i]), 0, 2) == '?' . '>') + $end = $i; + } + + // This should never happen, but apparently it is happening. + if (empty($end) || $end < 10) + $end = count($settingsArray) - 1; + + // Still more? Add them at the end. + if (!empty($config_vars)) + { + if (trim($settingsArray[$end]) == '?' . '>') + $settingsArray[$end++] = ''; + else + $end++; + + foreach ($config_vars as $var => $val) + $settingsArray[$end++] = '$' . $var . ' = ' . $val . ';' . "\n"; + $settingsArray[$end] = '?' . '>'; + } + else + $settingsArray[$end] = trim($settingsArray[$end]); + + // Sanity error checking: the file needs to be at least 12 lines. + if (count($settingsArray) < 12) + return; + + // Try to avoid a few pitfalls: + // like a possible race condition, + // or a failure to write at low diskspace + + // Check before you act: if cache is enabled, we can do a simple test + // Can we even write things on this filesystem? + if ((empty($cachedir) || !file_exists($cachedir)) && file_exists($boarddir . '/cache')) + $cachedir = $boarddir . '/cache'; + $test_fp = @fopen($cachedir . '/settings_update.tmp', "w+"); + if ($test_fp) + { + fclose($test_fp); + + $test_fp = @fopen($cachedir . '/settings_update.tmp', 'r+'); + $written_bytes = fwrite($test_fp, "test"); + fclose($test_fp); + @unlink($cachedir . '/settings_update.tmp'); + + if ($written_bytes !== strlen("test")) + { + // Oops. Low disk space, perhaps. Don't mess with Settings.php then. + // No means no. :P + return; + } + } + + // Protect me from what I want! :P + clearstatcache(); + if (filemtime($boarddir . '/Settings.php') === $last_settings_change) + { + // You asked for it... + // Blank out the file - done to fix a oddity with some servers. + $fp = @fopen($boarddir . '/Settings.php', 'w'); + + // Is it even writable, though? + if ($fp) + { + fclose($fp); + + $fp = fopen($boarddir . '/Settings.php', 'r+'); + foreach ($settingsArray as $line) + fwrite($fp, strtr($line, "\r", '')); + fclose($fp); + } + } +} + +function updateAdminPreferences() +{ + global $options, $context, $smcFunc, $settings, $user_info; + + // This must exist! + if (!isset($context['admin_preferences'])) + return false; + + // This is what we'll be saving. + $options['admin_preferences'] = serialize($context['admin_preferences']); + + // Just check we haven't ended up with something theme exclusive somehow. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}themes + WHERE id_theme != {int:default_theme} + AND variable = {string:admin_preferences}', + array( + 'default_theme' => 1, + 'admin_preferences' => 'admin_preferences', + ) + ); + + // Update the themes table. + $smcFunc['db_insert']('replace', + '{db_prefix}themes', + array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'), + array($user_info['id'], 1, 'admin_preferences', $options['admin_preferences']), + array('id_member', 'id_theme', 'variable') + ); + + // Make sure we invalidate any cache. + cache_put_data('theme_settings-' . $settings['theme_id'] . ':' . $user_info['id'], null, 0); +} + +// Send all the administrators a lovely email. +function emailAdmins($template, $replacements = array(), $additional_recipients = array()) +{ + global $smcFunc, $sourcedir, $language, $modSettings; + + // We certainly want this. + require_once($sourcedir . '/Subs-Post.php'); + + // Load all groups which are effectively admins. + $request = $smcFunc['db_query']('', ' + SELECT id_group + FROM {db_prefix}permissions + WHERE permission = {string:admin_forum} + AND add_deny = {int:add_deny} + AND id_group != {int:id_group}', + array( + 'add_deny' => 1, + 'id_group' => 0, + 'admin_forum' => 'admin_forum', + ) + ); + $groups = array(1); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $groups[] = $row['id_group']; + $smcFunc['db_free_result']($request); + + $request = $smcFunc['db_query']('', ' + SELECT id_member, member_name, real_name, lngfile, email_address + FROM {db_prefix}members + WHERE (id_group IN ({array_int:group_list}) OR FIND_IN_SET({raw:group_array_implode}, additional_groups) != 0) + AND notify_types != {int:notify_types} + ORDER BY lngfile', + array( + 'group_list' => $groups, + 'notify_types' => 4, + 'group_array_implode' => implode(', additional_groups) != 0 OR FIND_IN_SET(', $groups), + ) + ); + $emails_sent = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Stick their particulars in the replacement data. + $replacements['IDMEMBER'] = $row['id_member']; + $replacements['REALNAME'] = $row['member_name']; + $replacements['USERNAME'] = $row['real_name']; + + // Load the data from the template. + $emaildata = loadEmailTemplate($template, $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']); + + // Then send the actual email. + sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 1); + + // Track who we emailed so we don't do it twice. + $emails_sent[] = $row['email_address']; + } + $smcFunc['db_free_result']($request); + + // Any additional users we must email this to? + if (!empty($additional_recipients)) + foreach ($additional_recipients as $recipient) + { + if (in_array($recipient['email'], $emails_sent)) + continue; + + $replacements['IDMEMBER'] = $recipient['id']; + $replacements['REALNAME'] = $recipient['name']; + $replacements['USERNAME'] = $recipient['name']; + + // Load the template again. + $emaildata = loadEmailTemplate($template, $replacements, empty($recipient['lang']) || empty($modSettings['userLanguage']) ? $language : $recipient['lang']); + + // Send off the email. + sendmail($recipient['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 1); + } +} + +function updateLastDatabaseError() +{ + global $boarddir; + + // Find out this way if we can even write things on this filesystem. + // In addition, store things first in the backup file + + $last_settings_change = @filemtime($boarddir . '/Settings.php'); + + // Make sure the backup file is there... + $file = $boarddir . '/Settings_bak.php'; + if ((!file_exists($file) || filesize($file) == 0) && !copy($boarddir . '/Settings.php', $file)) + return false; + + // ...and writable! + if (!is_writable($file)) + { + chmod($file, 0755); + if (!is_writable($file)) + { + chmod($file, 0775); + if (!is_writable($file)) + { + chmod($file, 0777); + if (!is_writable($file)) + return false; + } + } + } + + // Put the new timestamp. + $data = file_get_contents($file); + $data = preg_replace('~\$db_last_error = \d+;~', '$db_last_error = ' . time() . ';', $data); + + // Open the backup file for writing + if ($fp = @fopen($file, 'w')) + { + // Reset the file buffer. + set_file_buffer($fp, 0); + + // Update the file. + $t = flock($fp, LOCK_EX); + $bytes = fwrite($fp, $data); + flock($fp, LOCK_UN); + fclose($fp); + + // Was it a success? + // ...only relevant if we're still dealing with the same good ole' settings file. + clearstatcache(); + if (($bytes == strlen($data)) && (filemtime($boarddir . '/Settings.php') === $last_settings_change)) + { + // This is our new Settings file... + // At least this one is an atomic operation + @copy($file, $boarddir . '/Settings.php'); + return true; + } + else + { + // Oops. Someone might have been faster + // or we have no more disk space left, troubles, troubles... + // Copy the file back and run for your life! + @copy($boarddir . '/Settings.php', $file); + } + } + + return false; +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Auth.php b/Sources/Subs-Auth.php new file mode 100644 index 0000000..fd36866 --- /dev/null +++ b/Sources/Subs-Auth.php @@ -0,0 +1,740 @@ + 0, $array[3] & 2 > 0); + setcookie($cookiename, serialize(array(0, '', 0)), time() - 3600, $cookie_url[1], $cookie_url[0], !empty($modSettings['secureCookies'])); + } + } + + // Get the data and path to set it on. + $data = serialize(empty($id) ? array(0, '', 0) : array($id, $password, time() + $cookie_length, $cookie_state)); + $cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); + + // Set the cookie, $_COOKIE, and session variable. + setcookie($cookiename, $data, time() + $cookie_length, $cookie_url[1], $cookie_url[0], !empty($modSettings['secureCookies'])); + + // If subdomain-independent cookies are on, unset the subdomain-dependent cookie too. + if (empty($id) && !empty($modSettings['globalCookies'])) + setcookie($cookiename, $data, time() + $cookie_length, $cookie_url[1], '', !empty($modSettings['secureCookies'])); + + // Any alias URLs? This is mainly for use with frames, etc. + if (!empty($modSettings['forum_alias_urls'])) + { + $aliases = explode(',', $modSettings['forum_alias_urls']); + + $temp = $boardurl; + foreach ($aliases as $alias) + { + // Fake the $boardurl so we can set a different cookie. + $alias = strtr(trim($alias), array('http://' => '', 'https://' => '')); + $boardurl = 'http://' . $alias; + + $cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); + + if ($cookie_url[0] == '') + $cookie_url[0] = strtok($alias, '/'); + + setcookie($cookiename, $data, time() + $cookie_length, $cookie_url[1], $cookie_url[0], !empty($modSettings['secureCookies'])); + } + + $boardurl = $temp; + } + + $_COOKIE[$cookiename] = $data; + + // Make sure the user logs in with a new session ID. + if (!isset($_SESSION['login_' . $cookiename]) || $_SESSION['login_' . $cookiename] !== $data) + { + // Backup and remove the old session. + $oldSessionData = $_SESSION; + $_SESSION = array(); + session_destroy(); + + // Recreate and restore the new session. + loadSession(); + session_regenerate_id(); + $_SESSION = $oldSessionData; + + // Version 4.3.2 didn't store the cookie of the new session. + if (version_compare(PHP_VERSION, '4.3.2') === 0) + { + $sessionCookieLifetime = @ini_get('session.cookie_lifetime'); + setcookie(session_name(), session_id(), time() + (empty($sessionCookieLifetime) ? $cookie_length : $sessionCookieLifetime), $cookie_url[1], $cookie_url[0], !empty($modSettings['secureCookies'])); + } + + $_SESSION['login_' . $cookiename] = $data; + } +} + +// PHP < 4.3.2 doesn't have this function +if (!function_exists('session_regenerate_id')) +{ + function session_regenerate_id() + { + // Too late to change the session now. + if (headers_sent()) + return false; + + session_id(strtolower(md5(uniqid(mt_rand(), true)))); + return true; + } + +} + +// Get the domain and path for the cookie... +function url_parts($local, $global) +{ + global $boardurl; + + // Parse the URL with PHP to make life easier. + $parsed_url = parse_url($boardurl); + + // Is local cookies off? + if (empty($parsed_url['path']) || !$local) + $parsed_url['path'] = ''; + + // Globalize cookies across domains (filter out IP-addresses)? + if ($global && preg_match('~^\d{1,3}(\.\d{1,3}){3}$~', $parsed_url['host']) == 0 && preg_match('~(?:[^\.]+\.)?([^\.]{2,}\..+)\z~i', $parsed_url['host'], $parts) == 1) + $parsed_url['host'] = '.' . $parts[1]; + + // We shouldn't use a host at all if both options are off. + elseif (!$local && !$global) + $parsed_url['host'] = ''; + + // The host also shouldn't be set if there aren't any dots in it. + elseif (!isset($parsed_url['host']) || strpos($parsed_url['host'], '.') === false) + $parsed_url['host'] = ''; + + return array($parsed_url['host'], $parsed_url['path'] . '/'); +} + +// Kick out a guest when guest access is off... +function KickGuest() +{ + global $txt, $context; + + loadLanguage('Login'); + loadTemplate('Login'); + + // Never redirect to an attachment + if (strpos($_SERVER['REQUEST_URL'], 'dlattach') === false) + $_SESSION['login_url'] = $_SERVER['REQUEST_URL']; + + $context['sub_template'] = 'kick_guest'; + $context['page_title'] = $txt['login']; +} + +// Display a message about the forum being in maintenance mode, etc. +function InMaintenance() +{ + global $txt, $mtitle, $mmessage, $context; + + loadLanguage('Login'); + loadTemplate('Login'); + + // Send a 503 header, so search engines don't bother indexing while we're in maintenance mode. + header('HTTP/1.1 503 Service Temporarily Unavailable'); + + // Basic template stuff.. + $context['sub_template'] = 'maintenance'; + $context['title'] = &$mtitle; + $context['description'] = &$mmessage; + $context['page_title'] = $txt['maintain_mode']; +} + +function adminLogin() +{ + global $context, $scripturl, $txt, $user_info, $user_settings; + + loadLanguage('Admin'); + loadTemplate('Login'); + + // They used a wrong password, log it and unset that. + if (isset($_POST['admin_hash_pass']) || isset($_POST['admin_pass'])) + { + $txt['security_wrong'] = sprintf($txt['security_wrong'], isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : $txt['unknown'], $_SERVER['HTTP_USER_AGENT'], $user_info['ip']); + log_error($txt['security_wrong'], 'critical'); + + if (isset($_POST['admin_hash_pass'])) + unset($_POST['admin_hash_pass']); + if (isset($_POST['admin_pass'])) + unset($_POST['admin_pass']); + + $context['incorrect_password'] = true; + } + + // Figure out the get data and post data. + $context['get_data'] = '?' . construct_query_string($_GET); + $context['post_data'] = ''; + + // Now go through $_POST. Make sure the session hash is sent. + $_POST[$context['session_var']] = $context['session_id']; + foreach ($_POST as $k => $v) + $context['post_data'] .= adminLogin_outputPostVars($k, $v); + + // Now we'll use the admin_login sub template of the Login template. + $context['sub_template'] = 'admin_login'; + + // And title the page something like "Login". + if (!isset($context['page_title'])) + $context['page_title'] = $txt['login']; + + obExit(); + + // We MUST exit at this point, because otherwise we CANNOT KNOW that the user is privileged. + trigger_error('Hacking attempt...', E_USER_ERROR); +} + +function adminLogin_outputPostVars($k, $v) +{ + global $smcFunc; + + if (!is_array($v)) + return ' + '"', '<' => '<', '>' => '>')) . '" />'; + else + { + $ret = ''; + foreach ($v as $k2 => $v2) + $ret .= adminLogin_outputPostVars($k . '[' . $k2 . ']', $v2); + + return $ret; + } +} + +function construct_query_string($get) +{ + global $scripturl; + + $query_string = ''; + + // Awww, darn. The $scripturl contains GET stuff! + $q = strpos($scripturl, '?'); + if ($q !== false) + { + parse_str(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr(substr($scripturl, $q + 1), ';', '&')), $temp); + + foreach ($get as $k => $v) + { + // Only if it's not already in the $scripturl! + if (!isset($temp[$k])) + $query_string .= urlencode($k) . '=' . urlencode($v) . ';'; + // If it changed, put it out there, but with an ampersand. + elseif ($temp[$k] != $get[$k]) + $query_string .= urlencode($k) . '=' . urlencode($v) . '&'; + } + } + else + { + // Add up all the data from $_GET into get_data. + foreach ($get as $k => $v) + $query_string .= urlencode($k) . '=' . urlencode($v) . ';'; + } + + $query_string = substr($query_string, 0, -1); + return $query_string; +} + +// Find members by email address, username, or real name. +function findMembers($names, $use_wildcards = false, $buddies_only = false, $max = 500) +{ + global $scripturl, $user_info, $modSettings, $smcFunc; + + // If it's not already an array, make it one. + if (!is_array($names)) + $names = explode(',', $names); + + $maybe_email = false; + foreach ($names as $i => $name) + { + // Trim, and fix wildcards for each name. + $names[$i] = trim($smcFunc['strtolower']($name)); + + $maybe_email |= strpos($name, '@') !== false; + + // Make it so standard wildcards will work. (* and ?) + if ($use_wildcards) + $names[$i] = strtr($names[$i], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '\'' => ''')); + else + $names[$i] = strtr($names[$i], array('\'' => ''')); + } + + // What are we using to compare? + $comparison = $use_wildcards ? 'LIKE' : '='; + + // Nothing found yet. + $results = array(); + + // This ensures you can't search someones email address if you can't see it. + $email_condition = allowedTo('moderate_forum') ? '' : 'hide_email = 0 AND '; + + if ($use_wildcards || $maybe_email) + $email_condition = ' + OR (' . $email_condition . 'email_address ' . $comparison . ' \'' . implode( '\') OR (' . $email_condition . ' email_address ' . $comparison . ' \'', $names) . '\')'; + else + $email_condition = ''; + + // Get the case of the columns right - but only if we need to as things like MySQL will go slow needlessly otherwise. + $member_name = $smcFunc['db_case_sensitive'] ? 'LOWER(member_name)' : 'member_name'; + $real_name = $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name'; + + // Search by username, display name, and email address. + $request = $smcFunc['db_query']('', ' + SELECT id_member, member_name, real_name, email_address, hide_email + FROM {db_prefix}members + WHERE ({raw:member_name_search} + OR {raw:real_name_search} {raw:email_condition}) + ' . ($buddies_only ? 'AND id_member IN ({array_int:buddy_list})' : '') . ' + AND is_activated IN (1, 11) + LIMIT {int:limit}', + array( + 'buddy_list' => $user_info['buddies'], + 'member_name_search' => $member_name . ' ' . $comparison . ' \'' . implode( '\' OR ' . $member_name . ' ' . $comparison . ' \'', $names) . '\'', + 'real_name_search' => $real_name . ' ' . $comparison . ' \'' . implode( '\' OR ' . $real_name . ' ' . $comparison . ' \'', $names) . '\'', + 'email_condition' => $email_condition, + 'limit' => $max, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $results[$row['id_member']] = array( + 'id' => $row['id_member'], + 'name' => $row['real_name'], + 'username' => $row['member_name'], + 'email' => in_array(showEmailAddress(!empty($row['hide_email']), $row['id_member']), array('yes', 'yes_permission_override')) ? $row['email_address'] : '', + 'href' => $scripturl . '?action=profile;u=' . $row['id_member'], + 'link' => '' . $row['real_name'] . '' + ); + } + $smcFunc['db_free_result']($request); + + // Return all the results. + return $results; +} + +function JSMembers() +{ + global $context, $scripturl, $user_info, $smcFunc; + + checkSession('get'); + + if (WIRELESS) + $context['sub_template'] = WIRELESS_PROTOCOL . '_pm'; + else + { + // Why is this in the Help template, you ask? Well, erm... it helps you. Does that work? + loadTemplate('Help'); + + $context['template_layers'] = array(); + $context['sub_template'] = 'find_members'; + } + + if (isset($_REQUEST['search'])) + $context['last_search'] = $smcFunc['htmlspecialchars']($_REQUEST['search'], ENT_QUOTES); + else + $_REQUEST['start'] = 0; + + // Allow the user to pass the input to be added to to the box. + $context['input_box_name'] = isset($_REQUEST['input']) && preg_match('~^[\w-]+$~', $_REQUEST['input']) === 1 ? $_REQUEST['input'] : 'to'; + + // Take the delimiter over GET in case it's \n or something. + $context['delimiter'] = isset($_REQUEST['delim']) ? ($_REQUEST['delim'] == 'LB' ? "\n" : $_REQUEST['delim']) : ', '; + $context['quote_results'] = !empty($_REQUEST['quote']); + + // List all the results. + $context['results'] = array(); + + // Some buddy related settings ;) + $context['show_buddies'] = !empty($user_info['buddies']); + $context['buddy_search'] = isset($_REQUEST['buddies']); + + // If the user has done a search, well - search. + if (isset($_REQUEST['search'])) + { + $_REQUEST['search'] = $smcFunc['htmlspecialchars']($_REQUEST['search'], ENT_QUOTES); + + $context['results'] = findMembers(array($_REQUEST['search']), true, $context['buddy_search']); + $total_results = count($context['results']); + + $context['page_index'] = constructPageIndex($scripturl . '?action=findmember;search=' . $context['last_search'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';input=' . $context['input_box_name'] . ($context['quote_results'] ? ';quote=1' : '') . ($context['buddy_search'] ? ';buddies' : ''), $_REQUEST['start'], $total_results, 7); + + // Determine the navigation context (especially useful for the wireless template). + $base_url = $scripturl . '?action=findmember;search=' . urlencode($context['last_search']) . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']) . ';' . $context['session_var'] . '=' . $context['session_id']; + $context['links'] = array( + 'first' => $_REQUEST['start'] >= 7 ? $base_url . ';start=0' : '', + 'prev' => $_REQUEST['start'] >= 7 ? $base_url . ';start=' . ($_REQUEST['start'] - 7) : '', + 'next' => $_REQUEST['start'] + 7 < $total_results ? $base_url . ';start=' . ($_REQUEST['start'] + 7) : '', + 'last' => $_REQUEST['start'] + 7 < $total_results ? $base_url . ';start=' . (floor(($total_results - 1) / 7) * 7) : '', + 'up' => $scripturl . '?action=pm;sa=send' . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']), + ); + $context['page_info'] = array( + 'current_page' => $_REQUEST['start'] / 7 + 1, + 'num_pages' => floor(($total_results - 1) / 7) + 1 + ); + + $context['results'] = array_slice($context['results'], $_REQUEST['start'], 7); + } + else + $context['links']['up'] = $scripturl . '?action=pm;sa=send' . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']); +} + +function RequestMembers() +{ + global $user_info, $txt, $smcFunc; + + checkSession('get'); + + $_REQUEST['search'] = $smcFunc['htmlspecialchars']($_REQUEST['search']) . '*'; + $_REQUEST['search'] = trim($smcFunc['strtolower']($_REQUEST['search'])); + $_REQUEST['search'] = strtr($_REQUEST['search'], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '&' => '&')); + + if (function_exists('iconv')) + header('Content-Type: text/plain; charset=UTF-8'); + + $request = $smcFunc['db_query']('', ' + SELECT real_name + FROM {db_prefix}members + WHERE real_name LIKE {string:search}' . (isset($_REQUEST['buddies']) ? ' + AND id_member IN ({array_int:buddy_list})' : '') . ' + AND is_activated IN (1, 11) + LIMIT ' . ($smcFunc['strlen']($_REQUEST['search']) <= 2 ? '100' : '800'), + array( + 'buddy_list' => $user_info['buddies'], + 'search' => $_REQUEST['search'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (function_exists('iconv')) + { + $utf8 = iconv($txt['lang_character_set'], 'UTF-8', $row['real_name']); + if ($utf8) + $row['real_name'] = $utf8; + } + + $row['real_name'] = strtr($row['real_name'], array('&' => '&', '<' => '<', '>' => '>', '"' => '"')); + + if (preg_match('~&#\d+;~', $row['real_name']) != 0) + { + $fixchar = create_function('$n', ' + if ($n < 128) + return chr($n); + elseif ($n < 2048) + return chr(192 | $n >> 6) . chr(128 | $n & 63); + elseif ($n < 65536) + return chr(224 | $n >> 12) . chr(128 | $n >> 6 & 63) . chr(128 | $n & 63); + else + return chr(240 | $n >> 18) . chr(128 | $n >> 12 & 63) . chr(128 | $n >> 6 & 63) . chr(128 | $n & 63);'); + + $row['real_name'] = preg_replace_callback('~&#(\d+);~', 'fixchar__callback', $row['real_name']); + } + + echo $row['real_name'], "\n"; + } + $smcFunc['db_free_result']($request); + + obExit(false); +} + +// This function generates a random password for a user and emails it to them. +function resetPassword($memID, $username = null) +{ + global $scripturl, $context, $txt, $sourcedir, $modSettings, $smcFunc, $language; + + // Language... and a required file. + loadLanguage('Login'); + require_once($sourcedir . '/Subs-Post.php'); + + // Get some important details. + $request = $smcFunc['db_query']('', ' + SELECT member_name, email_address, lngfile + FROM {db_prefix}members + WHERE id_member = {int:id_member}', + array( + 'id_member' => $memID, + ) + ); + list ($user, $email, $lngfile) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if ($username !== null) + { + $old_user = $user; + $user = trim($username); + } + + // Generate a random password. + $newPassword = substr(preg_replace('/\W/', '', md5(mt_rand())), 0, 10); + $newPassword_sha1 = sha1(strtolower($user) . $newPassword); + + // Do some checks on the username if needed. + if ($username !== null) + { + validateUsername($memID, $user); + + // Update the database... + updateMemberData($memID, array('member_name' => $user, 'passwd' => $newPassword_sha1)); + } + else + updateMemberData($memID, array('passwd' => $newPassword_sha1)); + + call_integration_hook('integrate_reset_pass', array($old_user, $user, $newPassword)); + + $replacements = array( + 'USERNAME' => $user, + 'PASSWORD' => $newPassword, + ); + + $emaildata = loadEmailTemplate('change_password', $replacements, empty($lngfile) || empty($modSettings['userLanguage']) ? $language : $lngfile); + + // Send them the email informing them of the change - then we're done! + sendmail($email, $emaildata['subject'], $emaildata['body'], null, null, false, 0); +} + +// Is this a valid username? +function validateUsername($memID, $username) +{ + global $sourcedir, $txt; + + // No name?! How can you register with no name? + if ($username == '') + fatal_lang_error('need_username', false); + + // Only these characters are permitted. + if (in_array($username, array('_', '|')) || preg_match('~[<>&"\'=\\\\]~', preg_replace('~&#(?:\\d{1,7}|x[0-9a-fA-F]{1,6});~', '', $username)) != 0 || strpos($username, '[code') !== false || strpos($username, '[/code') !== false) + fatal_lang_error('error_invalid_characters_username', false); + + if (stristr($username, $txt['guest_title']) !== false) + fatal_lang_error('username_reserved', true, array($txt['guest_title'])); + + require_once($sourcedir . '/Subs-Members.php'); + if (isReservedName($username, $memID, false)) + fatal_error('(' . htmlspecialchars($username) . ') ' . $txt['name_in_use'], false); + + return null; +} + +// This function simply checks whether a password meets the current forum rules. +function validatePassword($password, $username, $restrict_in = array()) +{ + global $modSettings, $smcFunc; + + // Perform basic requirements first. + if ($smcFunc['strlen']($password) < (empty($modSettings['password_strength']) ? 4 : 8)) + return 'short'; + + // Is this enough? + if (empty($modSettings['password_strength'])) + return null; + + // Otherwise, perform the medium strength test - checking if password appears in the restricted string. + if (preg_match('~\b' . preg_quote($password, '~') . '\b~', implode(' ', $restrict_in)) != 0) + return 'restricted_words'; + elseif ($smcFunc['strpos']($password, $username) !== false) + return 'restricted_words'; + + // !!! If pspell is available, use it on the word, and return restricted_words if it doesn't give "bad spelling"? + + // If just medium, we're done. + if ($modSettings['password_strength'] == 1) + return null; + + // Otherwise, hard test next, check for numbers and letters, uppercase too. + $good = preg_match('~(\D\d|\d\D)~', $password) != 0; + $good &= $smcFunc['strtolower']($password) != $password; + + return $good ? null : 'chars'; +} + +// Quickly find out what this user can and cannot do. +function rebuildModCache() +{ + global $user_info, $smcFunc; + + // What groups can they moderate? + $group_query = allowedTo('manage_membergroups') ? '1=1' : '0=1'; + + if ($group_query == '0=1') + { + $request = $smcFunc['db_query']('', ' + SELECT id_group + FROM {db_prefix}group_moderators + WHERE id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + ) + ); + $groups = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $groups[] = $row['id_group']; + $smcFunc['db_free_result']($request); + + if (empty($groups)) + $group_query = '0=1'; + else + $group_query = 'id_group IN (' . implode(',', $groups) . ')'; + } + + // Then, same again, just the boards this time! + $board_query = allowedTo('moderate_forum') ? '1=1' : '0=1'; + + if ($board_query == '0=1') + { + $boards = boardsAllowedTo('moderate_board', true); + + if (empty($boards)) + $board_query = '0=1'; + else + $board_query = 'id_board IN (' . implode(',', $boards) . ')'; + } + + // What boards are they the moderator of? + $boards_mod = array(); + if (!$user_info['is_guest']) + { + $request = $smcFunc['db_query']('', ' + SELECT id_board + FROM {db_prefix}moderators + WHERE id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $boards_mod[] = $row['id_board']; + $smcFunc['db_free_result']($request); + } + + $mod_query = empty($boards_mod) ? '0=1' : 'b.id_board IN (' . implode(',', $boards_mod) . ')'; + + $_SESSION['mc'] = array( + 'time' => time(), + // This looks a bit funny but protects against the login redirect. + 'id' => $user_info['id'] && $user_info['name'] ? $user_info['id'] : 0, + // If you change the format of 'gq' and/or 'bq' make sure to adjust 'can_mod' in Load.php. + 'gq' => $group_query, + 'bq' => $board_query, + 'ap' => boardsAllowedTo('approve_posts'), + 'mb' => $boards_mod, + 'mq' => $mod_query, + ); + + $user_info['mod_cache'] = $_SESSION['mc']; +} + +?> \ No newline at end of file diff --git a/Sources/Subs-BoardIndex.php b/Sources/Subs-BoardIndex.php new file mode 100644 index 0000000..87f6db2 --- /dev/null +++ b/Sources/Subs-BoardIndex.php @@ -0,0 +1,291 @@ +boards->child_boards or an associative array + with boards->child_boards. +*/ + +function getBoardIndex($boardIndexOptions) +{ + global $smcFunc, $scripturl, $user_info, $modSettings, $txt; + global $settings, $context; + + // For performance, track the latest post while going through the boards. + if (!empty($boardIndexOptions['set_latest_post'])) + $latest_post = array( + 'timestamp' => 0, + 'ref' => 0, + ); + + // Find all boards and categories, as well as related information. This will be sorted by the natural order of boards and categories, which we control. + $result_boards = $smcFunc['db_query']('boardindex_fetch_boards', ' + SELECT' . ($boardIndexOptions['include_categories'] ? ' + c.id_cat, c.name AS cat_name,' : '') . ' + b.id_board, b.name AS board_name, b.description, + CASE WHEN b.redirect != {string:blank_string} THEN 1 ELSE 0 END AS is_redirect, + b.num_posts, b.num_topics, b.unapproved_posts, b.unapproved_topics, b.id_parent, + IFNULL(m.poster_time, 0) AS poster_time, IFNULL(mem.member_name, m.poster_name) AS poster_name, + m.subject, m.id_topic, IFNULL(mem.real_name, m.poster_name) AS real_name, + ' . ($user_info['is_guest'] ? ' 1 AS is_read, 0 AS new_from,' : ' + (IFNULL(lb.id_msg, 0) >= b.id_msg_updated) AS is_read, IFNULL(lb.id_msg, -1) + 1 AS new_from,' . ($boardIndexOptions['include_categories'] ? ' + c.can_collapse, IFNULL(cc.id_member, 0) AS is_collapsed,' : '')) . ' + IFNULL(mem.id_member, 0) AS id_member, m.id_msg, + IFNULL(mods_mem.id_member, 0) AS id_moderator, mods_mem.real_name AS mod_real_name + FROM {db_prefix}boards AS b' . ($boardIndexOptions['include_categories'] ? ' + LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)' : '') . ' + LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = b.id_last_msg) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)' . ($user_info['is_guest'] ? '' : ' + LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member})' . ($boardIndexOptions['include_categories'] ? ' + LEFT JOIN {db_prefix}collapsed_categories AS cc ON (cc.id_cat = c.id_cat AND cc.id_member = {int:current_member})' : '')) . ' + LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board) + LEFT JOIN {db_prefix}members AS mods_mem ON (mods_mem.id_member = mods.id_member) + WHERE {query_see_board}' . (empty($boardIndexOptions['countChildPosts']) ? (empty($boardIndexOptions['base_level']) ? '' : ' + AND b.child_level >= {int:child_level}') : ' + AND b.child_level BETWEEN ' . $boardIndexOptions['base_level'] . ' AND ' . ($boardIndexOptions['base_level'] + 1)), + array( + 'current_member' => $user_info['id'], + 'child_level' => $boardIndexOptions['base_level'], + 'blank_string' => '', + ) + ); + + // Start with an empty array. + if ($boardIndexOptions['include_categories']) + $categories = array(); + else + $this_category = array(); + + // Run through the categories and boards (or only boards).... + while ($row_board = $smcFunc['db_fetch_assoc']($result_boards)) + { + // Perhaps we are ignoring this board? + $ignoreThisBoard = in_array($row_board['id_board'], $user_info['ignoreboards']); + $row_board['is_read'] = !empty($row_board['is_read']) || $ignoreThisBoard ? '1' : '0'; + + if ($boardIndexOptions['include_categories']) + { + // Haven't set this category yet. + if (empty($categories[$row_board['id_cat']])) + { + $categories[$row_board['id_cat']] = array( + 'id' => $row_board['id_cat'], + 'name' => $row_board['cat_name'], + 'is_collapsed' => isset($row_board['can_collapse']) && $row_board['can_collapse'] == 1 && $row_board['is_collapsed'] > 0, + 'can_collapse' => isset($row_board['can_collapse']) && $row_board['can_collapse'] == 1, + 'collapse_href' => isset($row_board['can_collapse']) ? $scripturl . '?action=collapse;c=' . $row_board['id_cat'] . ';sa=' . ($row_board['is_collapsed'] > 0 ? 'expand;' : 'collapse;') . $context['session_var'] . '=' . $context['session_id'] . '#c' . $row_board['id_cat'] : '', + 'collapse_image' => isset($row_board['can_collapse']) ? '+' : '', + 'href' => $scripturl . '#c' . $row_board['id_cat'], + 'boards' => array(), + 'new' => false + ); + $categories[$row_board['id_cat']]['link'] = '' . ($categories[$row_board['id_cat']]['can_collapse'] ? '' . $row_board['cat_name'] . '' : $row_board['cat_name']); + } + + // If this board has new posts in it (and isn't the recycle bin!) then the category is new. + if (empty($modSettings['recycle_enable']) || $modSettings['recycle_board'] != $row_board['id_board']) + $categories[$row_board['id_cat']]['new'] |= empty($row_board['is_read']) && $row_board['poster_name'] != ''; + + // Avoid showing category unread link where it only has redirection boards. + $categories[$row_board['id_cat']]['show_unread'] = !empty($categories[$row_board['id_cat']]['show_unread']) ? 1 : !$row_board['is_redirect']; + + // Collapsed category - don't do any of this. + if ($categories[$row_board['id_cat']]['is_collapsed']) + continue; + + // Let's save some typing. Climbing the array might be slower, anyhow. + $this_category = &$categories[$row_board['id_cat']]['boards']; + } + + // This is a parent board. + if ($row_board['id_parent'] == $boardIndexOptions['parent_id']) + { + // Is this a new board, or just another moderator? + if (!isset($this_category[$row_board['id_board']])) + { + // Not a child. + $isChild = false; + + $this_category[$row_board['id_board']] = array( + 'new' => empty($row_board['is_read']), + 'id' => $row_board['id_board'], + 'name' => $row_board['board_name'], + 'description' => $row_board['description'], + 'moderators' => array(), + 'link_moderators' => array(), + 'children' => array(), + 'link_children' => array(), + 'children_new' => false, + 'topics' => $row_board['num_topics'], + 'posts' => $row_board['num_posts'], + 'is_redirect' => $row_board['is_redirect'], + 'unapproved_topics' => $row_board['unapproved_topics'], + 'unapproved_posts' => $row_board['unapproved_posts'] - $row_board['unapproved_topics'], + 'can_approve_posts' => !empty($user_info['mod_cache']['ap']) && ($user_info['mod_cache']['ap'] == array(0) || in_array($row_board['id_board'], $user_info['mod_cache']['ap'])), + 'href' => $scripturl . '?board=' . $row_board['id_board'] . '.0', + 'link' => '' . $row_board['board_name'] . '' + ); + } + if (!empty($row_board['id_moderator'])) + { + $this_category[$row_board['id_board']]['moderators'][$row_board['id_moderator']] = array( + 'id' => $row_board['id_moderator'], + 'name' => $row_board['mod_real_name'], + 'href' => $scripturl . '?action=profile;u=' . $row_board['id_moderator'], + 'link' => '' . $row_board['mod_real_name'] . '' + ); + $this_category[$row_board['id_board']]['link_moderators'][] = '' . $row_board['mod_real_name'] . ''; + } + } + // Found a child board.... make sure we've found its parent and the child hasn't been set already. + elseif (isset($this_category[$row_board['id_parent']]['children']) && !isset($this_category[$row_board['id_parent']]['children'][$row_board['id_board']])) + { + // A valid child! + $isChild = true; + + $this_category[$row_board['id_parent']]['children'][$row_board['id_board']] = array( + 'id' => $row_board['id_board'], + 'name' => $row_board['board_name'], + 'description' => $row_board['description'], + 'new' => empty($row_board['is_read']) && $row_board['poster_name'] != '', + 'topics' => $row_board['num_topics'], + 'posts' => $row_board['num_posts'], + 'is_redirect' => $row_board['is_redirect'], + 'unapproved_topics' => $row_board['unapproved_topics'], + 'unapproved_posts' => $row_board['unapproved_posts'] - $row_board['unapproved_topics'], + 'can_approve_posts' => !empty($user_info['mod_cache']['ap']) && ($user_info['mod_cache']['ap'] == array(0) || in_array($row_board['id_board'], $user_info['mod_cache']['ap'])), + 'href' => $scripturl . '?board=' . $row_board['id_board'] . '.0', + 'link' => '' . $row_board['board_name'] . '' + ); + + // Counting child board posts is... slow :/. + if (!empty($boardIndexOptions['countChildPosts']) && !$row_board['is_redirect']) + { + $this_category[$row_board['id_parent']]['posts'] += $row_board['num_posts']; + $this_category[$row_board['id_parent']]['topics'] += $row_board['num_topics']; + } + + // Does this board contain new boards? + $this_category[$row_board['id_parent']]['children_new'] |= empty($row_board['is_read']); + + // This is easier to use in many cases for the theme.... + $this_category[$row_board['id_parent']]['link_children'][] = &$this_category[$row_board['id_parent']]['children'][$row_board['id_board']]['link']; + } + // Child of a child... just add it on... + elseif (!empty($boardIndexOptions['countChildPosts'])) + { + if (!isset($parent_map)) + $parent_map = array(); + + if (!isset($parent_map[$row_board['id_parent']])) + foreach ($this_category as $id => $board) + { + if (!isset($board['children'][$row_board['id_parent']])) + continue; + + $parent_map[$row_board['id_parent']] = array(&$this_category[$id], &$this_category[$id]['children'][$row_board['id_parent']]); + $parent_map[$row_board['id_board']] = array(&$this_category[$id], &$this_category[$id]['children'][$row_board['id_parent']]); + + break; + } + + if (isset($parent_map[$row_board['id_parent']]) && !$row_board['is_redirect']) + { + $parent_map[$row_board['id_parent']][0]['posts'] += $row_board['num_posts']; + $parent_map[$row_board['id_parent']][0]['topics'] += $row_board['num_topics']; + $parent_map[$row_board['id_parent']][1]['posts'] += $row_board['num_posts']; + $parent_map[$row_board['id_parent']][1]['topics'] += $row_board['num_topics']; + + continue; + } + + continue; + } + // Found a child of a child - skip. + else + continue; + + // Prepare the subject, and make sure it's not too long. + censorText($row_board['subject']); + $row_board['short_subject'] = shorten_subject($row_board['subject'], 24); + $this_last_post = array( + 'id' => $row_board['id_msg'], + 'time' => $row_board['poster_time'] > 0 ? timeformat($row_board['poster_time']) : $txt['not_applicable'], + 'timestamp' => forum_time(true, $row_board['poster_time']), + 'subject' => $row_board['short_subject'], + 'member' => array( + 'id' => $row_board['id_member'], + 'username' => $row_board['poster_name'] != '' ? $row_board['poster_name'] : $txt['not_applicable'], + 'name' => $row_board['real_name'], + 'href' => $row_board['poster_name'] != '' && !empty($row_board['id_member']) ? $scripturl . '?action=profile;u=' . $row_board['id_member'] : '', + 'link' => $row_board['poster_name'] != '' ? (!empty($row_board['id_member']) ? '' . $row_board['real_name'] . '' : $row_board['real_name']) : $txt['not_applicable'], + ), + 'start' => 'msg' . $row_board['new_from'], + 'topic' => $row_board['id_topic'] + ); + + // Provide the href and link. + if ($row_board['subject'] != '') + { + $this_last_post['href'] = $scripturl . '?topic=' . $row_board['id_topic'] . '.msg' . ($user_info['is_guest'] ? $row_board['id_msg'] : $row_board['new_from']) . (empty($row_board['is_read']) ? ';boardseen' : '') . '#new'; + $this_last_post['link'] = '' . $row_board['short_subject'] . ''; + } + else + { + $this_last_post['href'] = ''; + $this_last_post['link'] = $txt['not_applicable']; + } + + // Set the last post in the parent board. + if ($row_board['id_parent'] == $boardIndexOptions['parent_id'] || ($isChild && !empty($row_board['poster_time']) && $this_category[$row_board['id_parent']]['last_post']['timestamp'] < forum_time(true, $row_board['poster_time']))) + $this_category[$isChild ? $row_board['id_parent'] : $row_board['id_board']]['last_post'] = $this_last_post; + // Just in the child...? + if ($isChild) + { + $this_category[$row_board['id_parent']]['children'][$row_board['id_board']]['last_post'] = $this_last_post; + + // If there are no posts in this board, it really can't be new... + $this_category[$row_board['id_parent']]['children'][$row_board['id_board']]['new'] &= $row_board['poster_name'] != ''; + } + // No last post for this board? It's not new then, is it..? + elseif ($row_board['poster_name'] == '') + $this_category[$row_board['id_board']]['new'] = false; + + // Determine a global most recent topic. + if (!empty($boardIndexOptions['set_latest_post']) && !empty($row_board['poster_time']) && $row_board['poster_time'] > $latest_post['timestamp'] && !$ignoreThisBoard) + $latest_post = array( + 'timestamp' => $row_board['poster_time'], + 'ref' => &$this_category[$isChild ? $row_board['id_parent'] : $row_board['id_board']]['last_post'], + ); + } + $smcFunc['db_free_result']($result_boards); + + // By now we should know the most recent post...if we wanna know it that is. + if (!empty($boardIndexOptions['set_latest_post']) && !empty($latest_post['ref'])) + $context['latest_post'] = $latest_post['ref']; + + return $boardIndexOptions['include_categories'] ? $categories : $this_category; +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Boards.php b/Sources/Subs-Boards.php new file mode 100644 index 0000000..5500dc1 --- /dev/null +++ b/Sources/Subs-Boards.php @@ -0,0 +1,1170 @@ + $user_info['id'], + 'board_list' => $boards, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_boards + WHERE id_board IN ({array_int:board_list}) + AND id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'board_list' => $boards, + ) + ); + } + // Otherwise mark the board as read. + else + { + $markRead = array(); + foreach ($boards as $board) + $markRead[] = array($modSettings['maxMsgID'], $user_info['id'], $board); + + // Update log_mark_read and log_boards. + $smcFunc['db_insert']('replace', + '{db_prefix}log_mark_read', + array('id_msg' => 'int', 'id_member' => 'int', 'id_board' => 'int'), + $markRead, + array('id_board', 'id_member') + ); + + $smcFunc['db_insert']('replace', + '{db_prefix}log_boards', + array('id_msg' => 'int', 'id_member' => 'int', 'id_board' => 'int'), + $markRead, + array('id_board', 'id_member') + ); + } + + // Get rid of useless log_topics data, because log_mark_read is better for it - even if marking unread - I think so... + $result = $smcFunc['db_query']('', ' + SELECT MIN(id_topic) + FROM {db_prefix}log_topics + WHERE id_member = {int:current_member}', + array( + 'current_member' => $user_info['id'], + ) + ); + list ($lowest_topic) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + if (empty($lowest_topic)) + return; + + // !!!SLOW This query seems to eat it sometimes. + $result = $smcFunc['db_query']('', ' + SELECT lt.id_topic + FROM {db_prefix}log_topics AS lt + INNER JOIN {db_prefix}topics AS t /*!40000 USE INDEX (PRIMARY) */ ON (t.id_topic = lt.id_topic + AND t.id_board IN ({array_int:board_list})) + WHERE lt.id_member = {int:current_member} + AND lt.id_topic >= {int:lowest_topic}', + array( + 'current_member' => $user_info['id'], + 'board_list' => $boards, + 'lowest_topic' => $lowest_topic, + ) + ); + $topics = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + $topics[] = $row['id_topic']; + $smcFunc['db_free_result']($result); + + if (!empty($topics)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_topics + WHERE id_member = {int:current_member} + AND id_topic IN ({array_int:topic_list})', + array( + 'current_member' => $user_info['id'], + 'topic_list' => $topics, + ) + ); +} + +// Mark one or more boards as read. +function MarkRead() +{ + global $board, $topic, $user_info, $board_info, $modSettings, $smcFunc; + + // No Guests allowed! + is_not_guest(); + + checkSession('get'); + + if (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'all') + { + // Find all the boards this user can see. + $result = $smcFunc['db_query']('', ' + SELECT b.id_board + FROM {db_prefix}boards AS b + WHERE {query_see_board}', + array( + ) + ); + $boards = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + $boards[] = $row['id_board']; + $smcFunc['db_free_result']($result); + + if (!empty($boards)) + markBoardsRead($boards, isset($_REQUEST['unread'])); + + $_SESSION['id_msg_last_visit'] = $modSettings['maxMsgID']; + if (!empty($_SESSION['old_url']) && strpos($_SESSION['old_url'], 'action=unread') !== false) + redirectexit('action=unread'); + + if (isset($_SESSION['topicseen_cache'])) + $_SESSION['topicseen_cache'] = array(); + + redirectexit(); + } + elseif (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'unreadreplies') + { + // Make sure all the boards are integers! + $topics = explode('-', $_REQUEST['topics']); + + $markRead = array(); + foreach ($topics as $id_topic) + $markRead[] = array($modSettings['maxMsgID'], $user_info['id'], (int) $id_topic); + + $smcFunc['db_insert']('replace', + '{db_prefix}log_topics', + array('id_msg' => 'int', 'id_member' => 'int', 'id_topic' => 'int'), + $markRead, + array('id_member', 'id_topic') + ); + + if (isset($_SESSION['topicseen_cache'])) + $_SESSION['topicseen_cache'] = array(); + + redirectexit('action=unreadreplies'); + } + + // Special case: mark a topic unread! + elseif (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'topic') + { + // First, let's figure out what the latest message is. + $result = $smcFunc['db_query']('', ' + SELECT id_first_msg, id_last_msg + FROM {db_prefix}topics + WHERE id_topic = {int:current_topic}', + array( + 'current_topic' => $topic, + ) + ); + $topicinfo = $smcFunc['db_fetch_assoc']($result); + $smcFunc['db_free_result']($result); + + if (!empty($_GET['t'])) + { + // If they read the whole topic, go back to the beginning. + if ($_GET['t'] >= $topicinfo['id_last_msg']) + $earlyMsg = 0; + // If they want to mark the whole thing read, same. + elseif ($_GET['t'] <= $topicinfo['id_first_msg']) + $earlyMsg = 0; + // Otherwise, get the latest message before the named one. + else + { + $result = $smcFunc['db_query']('', ' + SELECT MAX(id_msg) + FROM {db_prefix}messages + WHERE id_topic = {int:current_topic} + AND id_msg >= {int:id_first_msg} + AND id_msg < {int:topic_msg_id}', + array( + 'current_topic' => $topic, + 'topic_msg_id' => (int) $_GET['t'], + 'id_first_msg' => $topicinfo['id_first_msg'], + ) + ); + list ($earlyMsg) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + } + } + // Marking read from first page? That's the whole topic. + elseif ($_REQUEST['start'] == 0) + $earlyMsg = 0; + else + { + $result = $smcFunc['db_query']('', ' + SELECT id_msg + FROM {db_prefix}messages + WHERE id_topic = {int:current_topic} + ORDER BY id_msg + LIMIT ' . (int) $_REQUEST['start'] . ', 1', + array( + 'current_topic' => $topic, + ) + ); + list ($earlyMsg) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + $earlyMsg--; + } + + // Blam, unread! + $smcFunc['db_insert']('replace', + '{db_prefix}log_topics', + array('id_msg' => 'int', 'id_member' => 'int', 'id_topic' => 'int'), + array($earlyMsg, $user_info['id'], $topic), + array('id_member', 'id_topic') + ); + + redirectexit('board=' . $board . '.0'); + } + else + { + $categories = array(); + $boards = array(); + + if (isset($_REQUEST['c'])) + { + $_REQUEST['c'] = explode(',', $_REQUEST['c']); + foreach ($_REQUEST['c'] as $c) + $categories[] = (int) $c; + } + if (isset($_REQUEST['boards'])) + { + $_REQUEST['boards'] = explode(',', $_REQUEST['boards']); + foreach ($_REQUEST['boards'] as $b) + $boards[] = (int) $b; + } + if (!empty($board)) + $boards[] = (int) $board; + + if (isset($_REQUEST['children']) && !empty($boards)) + { + // They want to mark the entire tree starting with the boards specified + // The easist thing is to just get all the boards they can see, but since we've specified the top of tree we ignore some of them + + $request = $smcFunc['db_query']('', ' + SELECT b.id_board, b.id_parent + FROM {db_prefix}boards AS b + WHERE {query_see_board} + AND b.child_level > {int:no_parents} + AND b.id_board NOT IN ({array_int:board_list}) + ORDER BY child_level ASC + ', + array( + 'no_parents' => 0, + 'board_list' => $boards, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + if (in_array($row['id_parent'], $boards)) + $boards[] = $row['id_board']; + $smcFunc['db_free_result']($request); + } + + $clauses = array(); + $clauseParameters = array(); + if (!empty($categories)) + { + $clauses[] = 'id_cat IN ({array_int:category_list})'; + $clauseParameters['category_list'] = $categories; + } + if (!empty($boards)) + { + $clauses[] = 'id_board IN ({array_int:board_list})'; + $clauseParameters['board_list'] = $boards; + } + + if (empty($clauses)) + redirectexit(); + + $request = $smcFunc['db_query']('', ' + SELECT b.id_board + FROM {db_prefix}boards AS b + WHERE {query_see_board} + AND b.' . implode(' OR b.', $clauses), + array_merge($clauseParameters, array( + )) + ); + $boards = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $boards[] = $row['id_board']; + $smcFunc['db_free_result']($request); + + if (empty($boards)) + redirectexit(); + + markBoardsRead($boards, isset($_REQUEST['unread'])); + + foreach ($boards as $b) + { + if (isset($_SESSION['topicseen_cache'][$b])) + $_SESSION['topicseen_cache'][$b] = array(); + } + + if (!isset($_REQUEST['unread'])) + { + // Find all the boards this user can see. + $result = $smcFunc['db_query']('', ' + SELECT b.id_board + FROM {db_prefix}boards AS b + WHERE b.id_parent IN ({array_int:parent_list}) + AND {query_see_board}', + array( + 'parent_list' => $boards, + ) + ); + if ($smcFunc['db_num_rows']($result) > 0) + { + $logBoardInserts = ''; + while ($row = $smcFunc['db_fetch_assoc']($result)) + $logBoardInserts[] = array($modSettings['maxMsgID'], $user_info['id'], $row['id_board']); + + $smcFunc['db_insert']('replace', + '{db_prefix}log_boards', + array('id_msg' => 'int', 'id_member' => 'int', 'id_board' => 'int'), + $logBoardInserts, + array('id_member', 'id_board') + ); + } + $smcFunc['db_free_result']($result); + + if (empty($board)) + redirectexit(); + else + redirectexit('board=' . $board . '.0'); + } + else + { + if (empty($board_info['parent'])) + redirectexit(); + else + redirectexit('board=' . $board_info['parent'] . '.0'); + } + } +} + +// Get the id_member associated with the specified message. +function getMsgMemberID($messageID) +{ + global $smcFunc; + + // Find the topic and make sure the member still exists. + $result = $smcFunc['db_query']('', ' + SELECT IFNULL(mem.id_member, 0) + FROM {db_prefix}messages AS m + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE m.id_msg = {int:selected_message} + LIMIT 1', + array( + 'selected_message' => (int) $messageID, + ) + ); + if ($smcFunc['db_num_rows']($result) > 0) + list ($memberID) = $smcFunc['db_fetch_row']($result); + // The message doesn't even exist. + else + $memberID = 0; + $smcFunc['db_free_result']($result); + + return (int) $memberID; +} + +// Modify the settings and position of a board. +function modifyBoard($board_id, &$boardOptions) +{ + global $sourcedir, $cat_tree, $boards, $boardList, $modSettings, $smcFunc; + + // Get some basic information about all boards and categories. + getBoardTree(); + + // Make sure given boards and categories exist. + if (!isset($boards[$board_id]) || (isset($boardOptions['target_board']) && !isset($boards[$boardOptions['target_board']])) || (isset($boardOptions['target_category']) && !isset($cat_tree[$boardOptions['target_category']]))) + fatal_lang_error('no_board'); + + // All things that will be updated in the database will be in $boardUpdates. + $boardUpdates = array(); + $boardUpdateParameters = array(); + + // In case the board has to be moved + if (isset($boardOptions['move_to'])) + { + // Move the board to the top of a given category. + if ($boardOptions['move_to'] == 'top') + { + $id_cat = $boardOptions['target_category']; + $child_level = 0; + $id_parent = 0; + $after = $cat_tree[$id_cat]['last_board_order']; + } + + // Move the board to the bottom of a given category. + elseif ($boardOptions['move_to'] == 'bottom') + { + $id_cat = $boardOptions['target_category']; + $child_level = 0; + $id_parent = 0; + $after = 0; + foreach ($cat_tree[$id_cat]['children'] as $id_board => $dummy) + $after = max($after, $boards[$id_board]['order']); + } + + // Make the board a child of a given board. + elseif ($boardOptions['move_to'] == 'child') + { + $id_cat = $boards[$boardOptions['target_board']]['category']; + $child_level = $boards[$boardOptions['target_board']]['level'] + 1; + $id_parent = $boardOptions['target_board']; + + // People can be creative, in many ways... + if (isChildOf($id_parent, $board_id)) + fatal_lang_error('mboards_parent_own_child_error', false); + elseif ($id_parent == $board_id) + fatal_lang_error('mboards_board_own_child_error', false); + + $after = $boards[$boardOptions['target_board']]['order']; + + // Check if there are already children and (if so) get the max board order. + if (!empty($boards[$id_parent]['tree']['children']) && empty($boardOptions['move_first_child'])) + foreach ($boards[$id_parent]['tree']['children'] as $childBoard_id => $dummy) + $after = max($after, $boards[$childBoard_id]['order']); + } + + // Place a board before or after another board, on the same child level. + elseif (in_array($boardOptions['move_to'], array('before', 'after'))) + { + $id_cat = $boards[$boardOptions['target_board']]['category']; + $child_level = $boards[$boardOptions['target_board']]['level']; + $id_parent = $boards[$boardOptions['target_board']]['parent']; + $after = $boards[$boardOptions['target_board']]['order'] - ($boardOptions['move_to'] == 'before' ? 1 : 0); + } + + // Oops...? + else + trigger_error('modifyBoard(): The move_to value \'' . $boardOptions['move_to'] . '\' is incorrect', E_USER_ERROR); + + // Get a list of children of this board. + $childList = array(); + recursiveBoards($childList, $boards[$board_id]['tree']); + + // See if there are changes that affect children. + $childUpdates = array(); + $levelDiff = $child_level - $boards[$board_id]['level']; + if ($levelDiff != 0) + $childUpdates[] = 'child_level = child_level ' . ($levelDiff > 0 ? '+ ' : '') . '{int:level_diff}'; + if ($id_cat != $boards[$board_id]['category']) + $childUpdates[] = 'id_cat = {int:category}'; + + // Fix the children of this board. + if (!empty($childList) && !empty($childUpdates)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET ' . implode(', + ', $childUpdates) . ' + WHERE id_board IN ({array_int:board_list})', + array( + 'board_list' => $childList, + 'category' => $id_cat, + 'level_diff' => $levelDiff, + ) + ); + + // Make some room for this spot. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET board_order = board_order + {int:new_order} + WHERE board_order > {int:insert_after} + AND id_board != {int:selected_board}', + array( + 'insert_after' => $after, + 'selected_board' => $board_id, + 'new_order' => 1 + count($childList), + ) + ); + + $boardUpdates[] = 'id_cat = {int:id_cat}'; + $boardUpdates[] = 'id_parent = {int:id_parent}'; + $boardUpdates[] = 'child_level = {int:child_level}'; + $boardUpdates[] = 'board_order = {int:board_order}'; + $boardUpdateParameters += array( + 'id_cat' => $id_cat, + 'id_parent' => $id_parent, + 'child_level' => $child_level, + 'board_order' => $after + 1, + ); + } + + // This setting is a little twisted in the database... + if (isset($boardOptions['posts_count'])) + { + $boardUpdates[] = 'count_posts = {int:count_posts}'; + $boardUpdateParameters['count_posts'] = $boardOptions['posts_count'] ? 0 : 1; + } + + // Set the theme for this board. + if (isset($boardOptions['board_theme'])) + { + $boardUpdates[] = 'id_theme = {int:id_theme}'; + $boardUpdateParameters['id_theme'] = (int) $boardOptions['board_theme']; + } + + // Should the board theme override the user preferred theme? + if (isset($boardOptions['override_theme'])) + { + $boardUpdates[] = 'override_theme = {int:override_theme}'; + $boardUpdateParameters['override_theme'] = $boardOptions['override_theme'] ? 1 : 0; + } + + // Who's allowed to access this board. + if (isset($boardOptions['access_groups'])) + { + $boardUpdates[] = 'member_groups = {string:member_groups}'; + $boardUpdateParameters['member_groups'] = implode(',', $boardOptions['access_groups']); + } + + if (isset($boardOptions['board_name'])) + { + $boardUpdates[] = 'name = {string:board_name}'; + $boardUpdateParameters['board_name'] = $boardOptions['board_name']; + } + + if (isset($boardOptions['board_description'])) + { + $boardUpdates[] = 'description = {string:board_description}'; + $boardUpdateParameters['board_description'] = $boardOptions['board_description']; + } + + if (isset($boardOptions['profile'])) + { + $boardUpdates[] = 'id_profile = {int:profile}'; + $boardUpdateParameters['profile'] = (int) $boardOptions['profile']; + } + + if (isset($boardOptions['redirect'])) + { + $boardUpdates[] = 'redirect = {string:redirect}'; + $boardUpdateParameters['redirect'] = $boardOptions['redirect']; + } + + if (isset($boardOptions['num_posts'])) + { + $boardUpdates[] = 'num_posts = {int:num_posts}'; + $boardUpdateParameters['num_posts'] = (int) $boardOptions['num_posts']; + } + + // Do the updates (if any). + if (!empty($boardUpdates)) + $request = $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET + ' . implode(', + ', $boardUpdates) . ' + WHERE id_board = {int:selected_board}', + array_merge($boardUpdateParameters, array( + 'selected_board' => $board_id, + )) + ); + + // Set moderators of this board. + if (isset($boardOptions['moderators']) || isset($boardOptions['moderator_string'])) + { + // Reset current moderators for this board - if there are any! + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}moderators + WHERE id_board = {int:board_list}', + array( + 'board_list' => $board_id, + ) + ); + + // Validate and get the IDs of the new moderators. + if (isset($boardOptions['moderator_string']) && trim($boardOptions['moderator_string']) != '') + { + // Divvy out the usernames, remove extra space. + $moderator_string = strtr($smcFunc['htmlspecialchars']($boardOptions['moderator_string'], ENT_QUOTES), array('"' => '"')); + preg_match_all('~"([^"]+)"~', $moderator_string, $matches); + $moderators = array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $moderator_string))); + for ($k = 0, $n = count($moderators); $k < $n; $k++) + { + $moderators[$k] = trim($moderators[$k]); + + if (strlen($moderators[$k]) == 0) + unset($moderators[$k]); + } + + // Find all the id_member's for the member_name's in the list. + if (empty($boardOptions['moderators'])) + $boardOptions['moderators'] = array(); + if (!empty($moderators)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE member_name IN ({array_string:moderator_list}) OR real_name IN ({array_string:moderator_list}) + LIMIT ' . count($moderators), + array( + 'moderator_list' => $moderators, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $boardOptions['moderators'][] = $row['id_member']; + $smcFunc['db_free_result']($request); + } + } + + // Add the moderators to the board. + if (!empty($boardOptions['moderators'])) + { + $inserts = array(); + foreach ($boardOptions['moderators'] as $moderator) + $inserts[] = array($board_id, $moderator); + + $smcFunc['db_insert']('insert', + '{db_prefix}moderators', + array('id_board' => 'int', 'id_member' => 'int'), + $inserts, + array('id_board', 'id_member') + ); + } + + // Note that caches can now be wrong! + updateSettings(array('settings_updated' => time())); + } + + if (isset($boardOptions['move_to'])) + reorderBoards(); + + clean_cache('data'); + + if (empty($boardOptions['dont_log'])) + logAction('edit_board', array('board' => $board_id), 'admin'); +} + +// Create a new board and set its properties and position. +function createBoard($boardOptions) +{ + global $boards, $modSettings, $smcFunc; + + // Trigger an error if one of the required values is not set. + if (!isset($boardOptions['board_name']) || trim($boardOptions['board_name']) == '' || !isset($boardOptions['move_to']) || !isset($boardOptions['target_category'])) + trigger_error('createBoard(): One or more of the required options is not set', E_USER_ERROR); + + if (in_array($boardOptions['move_to'], array('child', 'before', 'after')) && !isset($boardOptions['target_board'])) + trigger_error('createBoard(): Target board is not set', E_USER_ERROR); + + // Set every optional value to its default value. + $boardOptions += array( + 'posts_count' => true, + 'override_theme' => false, + 'board_theme' => 0, + 'access_groups' => array(), + 'board_description' => '', + 'profile' => 1, + 'moderators' => '', + 'inherit_permissions' => true, + 'dont_log' => true, + ); + + // Insert a board, the settings are dealt with later. + $smcFunc['db_insert']('', + '{db_prefix}boards', + array( + 'id_cat' => 'int', 'name' => 'string-255', 'description' => 'string', 'board_order' => 'int', + 'member_groups' => 'string', 'redirect' => 'string', + ), + array( + $boardOptions['target_category'], $boardOptions['board_name'] , '', 0, + '-1,0', '', + ), + array('id_board') + ); + $board_id = $smcFunc['db_insert_id']('{db_prefix}boards', 'id_board'); + + if (empty($board_id)) + return 0; + + // Change the board according to the given specifications. + modifyBoard($board_id, $boardOptions); + + // Do we want the parent permissions to be inherited? + if ($boardOptions['inherit_permissions']) + { + getBoardTree(); + + if (!empty($boards[$board_id]['parent'])) + { + $request = $smcFunc['db_query']('', ' + SELECT id_profile + FROM {db_prefix}boards + WHERE id_board = {int:board_parent} + LIMIT 1', + array( + 'board_parent' => (int) $boards[$board_id]['parent'], + ) + ); + list ($boardOptions['profile']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET id_profile = {int:new_profile} + WHERE id_board = {int:current_board}', + array( + 'new_profile' => $boardOptions['profile'], + 'current_board' => $board_id, + ) + ); + } + } + + // Clean the data cache. + clean_cache('data'); + + // Created it. + logAction('add_board', array('board' => $board_id), 'admin'); + + // Here you are, a new board, ready to be spammed. + return $board_id; +} + +// Remove one or more boards. +function deleteBoards($boards_to_remove, $moveChildrenTo = null) +{ + global $sourcedir, $boards, $smcFunc; + + // No boards to delete? Return! + if (empty($boards_to_remove)) + return; + + getBoardTree(); + + // If $moveChildrenTo is set to null, include the children in the removal. + if ($moveChildrenTo === null) + { + // Get a list of the child boards that will also be removed. + $child_boards_to_remove = array(); + foreach ($boards_to_remove as $board_to_remove) + recursiveBoards($child_boards_to_remove, $boards[$board_to_remove]['tree']); + + // Merge the children with their parents. + if (!empty($child_boards_to_remove)) + $boards_to_remove = array_unique(array_merge($boards_to_remove, $child_boards_to_remove)); + } + // Move the children to a safe home. + else + { + foreach ($boards_to_remove as $id_board) + { + // !!! Separate category? + if ($moveChildrenTo === 0) + fixChildren($id_board, 0, 0); + else + fixChildren($id_board, $boards[$moveChildrenTo]['level'] + 1, $moveChildrenTo); + } + } + + // Delete ALL topics in the selected boards (done first so topics can't be marooned.) + $request = $smcFunc['db_query']('', ' + SELECT id_topic + FROM {db_prefix}topics + WHERE id_board IN ({array_int:boards_to_remove})', + array( + 'boards_to_remove' => $boards_to_remove, + ) + ); + $topics = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $topics[] = $row['id_topic']; + $smcFunc['db_free_result']($request); + + require_once($sourcedir . '/RemoveTopic.php'); + removeTopics($topics, false); + + // Delete the board's logs. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_mark_read + WHERE id_board IN ({array_int:boards_to_remove})', + array( + 'boards_to_remove' => $boards_to_remove, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_boards + WHERE id_board IN ({array_int:boards_to_remove})', + array( + 'boards_to_remove' => $boards_to_remove, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_notify + WHERE id_board IN ({array_int:boards_to_remove})', + array( + 'boards_to_remove' => $boards_to_remove, + ) + ); + + // Delete this board's moderators. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}moderators + WHERE id_board IN ({array_int:boards_to_remove})', + array( + 'boards_to_remove' => $boards_to_remove, + ) + ); + + // Delete any extra events in the calendar. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}calendar + WHERE id_board IN ({array_int:boards_to_remove})', + array( + 'boards_to_remove' => $boards_to_remove, + ) + ); + + // Delete any message icons that only appear on these boards. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}message_icons + WHERE id_board IN ({array_int:boards_to_remove})', + array( + 'boards_to_remove' => $boards_to_remove, + ) + ); + + // Delete the boards. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}boards + WHERE id_board IN ({array_int:boards_to_remove})', + array( + 'boards_to_remove' => $boards_to_remove, + ) + ); + + // Latest message/topic might not be there anymore. + updateStats('message'); + updateStats('topic'); + updateSettings(array( + 'calendar_updated' => time(), + )); + + // Plus reset the cache to stop people getting odd results. + updateSettings(array('settings_updated' => time())); + + // Clean the cache as well. + clean_cache('data'); + + // Let's do some serious logging. + foreach ($boards_to_remove as $id_board) + logAction('delete_board', array('boardname' => $boards[$id_board]['name']), 'admin'); + + reorderBoards(); +} + +// Put all boards in the right order. +function reorderBoards() +{ + global $cat_tree, $boardList, $boards, $smcFunc; + + getBoardTree(); + + // Set the board order for each category. + $board_order = 0; + foreach ($cat_tree as $catID => $dummy) + { + foreach ($boardList[$catID] as $boardID) + if ($boards[$boardID]['order'] != ++$board_order) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET board_order = {int:new_order} + WHERE id_board = {int:selected_board}', + array( + 'new_order' => $board_order, + 'selected_board' => $boardID, + ) + ); + } + + // Sort the records of the boards table on the board_order value. + $smcFunc['db_query']('alter_table_boards', ' + ALTER TABLE {db_prefix}boards + ORDER BY board_order', + array( + 'db_error_skip' => true, + ) + ); +} + +// Fixes the children of a board by setting their child_levels to new values. +function fixChildren($parent, $newLevel, $newParent) +{ + global $smcFunc; + + // Grab all children of $parent... + $result = $smcFunc['db_query']('', ' + SELECT id_board + FROM {db_prefix}boards + WHERE id_parent = {int:parent_board}', + array( + 'parent_board' => $parent, + ) + ); + $children = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + $children[] = $row['id_board']; + $smcFunc['db_free_result']($result); + + // ...and set it to a new parent and child_level. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET id_parent = {int:new_parent}, child_level = {int:new_child_level} + WHERE id_parent = {int:parent_board}', + array( + 'new_parent' => $newParent, + 'new_child_level' => $newLevel, + 'parent_board' => $parent, + ) + ); + + // Recursively fix the children of the children. + foreach ($children as $child) + fixChildren($child, $newLevel + 1, $child); +} + +// Load a lot of useful information regarding the boards and categories. +function getBoardTree() +{ + global $cat_tree, $boards, $boardList, $txt, $modSettings, $smcFunc; + + // Getting all the board and category information you'd ever wanted. + $request = $smcFunc['db_query']('', ' + SELECT + IFNULL(b.id_board, 0) AS id_board, b.id_parent, b.name AS board_name, b.description, b.child_level, + b.board_order, b.count_posts, b.member_groups, b.id_theme, b.override_theme, b.id_profile, b.redirect, + b.num_posts, b.num_topics, c.id_cat, c.name AS cat_name, c.cat_order, c.can_collapse + FROM {db_prefix}categories AS c + LEFT JOIN {db_prefix}boards AS b ON (b.id_cat = c.id_cat) + ORDER BY c.cat_order, b.child_level, b.board_order', + array( + ) + ); + $cat_tree = array(); + $boards = array(); + $last_board_order = 0; + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!isset($cat_tree[$row['id_cat']])) + { + $cat_tree[$row['id_cat']] = array( + 'node' => array( + 'id' => $row['id_cat'], + 'name' => $row['cat_name'], + 'order' => $row['cat_order'], + 'can_collapse' => $row['can_collapse'] + ), + 'is_first' => empty($cat_tree), + 'last_board_order' => $last_board_order, + 'children' => array() + ); + $prevBoard = 0; + $curLevel = 0; + } + + if (!empty($row['id_board'])) + { + if ($row['child_level'] != $curLevel) + $prevBoard = 0; + + $boards[$row['id_board']] = array( + 'id' => $row['id_board'], + 'category' => $row['id_cat'], + 'parent' => $row['id_parent'], + 'level' => $row['child_level'], + 'order' => $row['board_order'], + 'name' => $row['board_name'], + 'member_groups' => explode(',', $row['member_groups']), + 'description' => $row['description'], + 'count_posts' => empty($row['count_posts']), + 'posts' => $row['num_posts'], + 'topics' => $row['num_topics'], + 'theme' => $row['id_theme'], + 'override_theme' => $row['override_theme'], + 'profile' => $row['id_profile'], + 'redirect' => $row['redirect'], + 'prev_board' => $prevBoard + ); + $prevBoard = $row['id_board']; + $last_board_order = $row['board_order']; + + if (empty($row['child_level'])) + { + $cat_tree[$row['id_cat']]['children'][$row['id_board']] = array( + 'node' => &$boards[$row['id_board']], + 'is_first' => empty($cat_tree[$row['id_cat']]['children']), + 'children' => array() + ); + $boards[$row['id_board']]['tree'] = &$cat_tree[$row['id_cat']]['children'][$row['id_board']]; + } + else + { + // Parent doesn't exist! + if (!isset($boards[$row['id_parent']]['tree'])) + fatal_lang_error('no_valid_parent', false, array($row['board_name'])); + + // Wrong childlevel...we can silently fix this... + if ($boards[$row['id_parent']]['tree']['node']['level'] != $row['child_level'] - 1) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET child_level = {int:new_child_level} + WHERE id_board = {int:selected_board}', + array( + 'new_child_level' => $boards[$row['id_parent']]['tree']['node']['level'] + 1, + 'selected_board' => $row['id_board'], + ) + ); + + $boards[$row['id_parent']]['tree']['children'][$row['id_board']] = array( + 'node' => &$boards[$row['id_board']], + 'is_first' => empty($boards[$row['id_parent']]['tree']['children']), + 'children' => array() + ); + $boards[$row['id_board']]['tree'] = &$boards[$row['id_parent']]['tree']['children'][$row['id_board']]; + } + } + } + $smcFunc['db_free_result']($request); + + // Get a list of all the boards in each category (using recursion). + $boardList = array(); + foreach ($cat_tree as $catID => $node) + { + $boardList[$catID] = array(); + recursiveBoards($boardList[$catID], $node); + } +} + +// Recursively get a list of boards. +function recursiveBoards(&$_boardList, &$_tree) +{ + if (empty($_tree['children'])) + return; + + foreach ($_tree['children'] as $id => $node) + { + $_boardList[] = $id; + recursiveBoards($_boardList, $node); + } +} + +// Returns whether the child board id is actually a child of the parent (recursive). +function isChildOf($child, $parent) +{ + global $boards; + + if (empty($boards[$child]['parent'])) + return false; + + if ($boards[$child]['parent'] == $parent) + return true; + + return isChildOf($boards[$child]['parent'], $parent); +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Calendar.php b/Sources/Subs-Calendar.php new file mode 100644 index 0000000..8caf58f --- /dev/null +++ b/Sources/Subs-Calendar.php @@ -0,0 +1,1046 @@ + 1, + 'no_month' => 0, + 'no_day' => 0, + 'year_one' => '0001', + 'year_low' => $year_low . '-%m-%d', + 'year_high' => $year_high . '-%m-%d', + 'low_date' => $low_date, + 'high_date' => $high_date, + 'max_year' => $year_high, + ) + ); + $bday = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + if ($year_low != $year_high) + $age_year = substr($row['birthdate'], 5) < substr($high_date, 5) ? $year_high : $year_low; + else + $age_year = $year_low; + + $bday[$age_year . substr($row['birthdate'], 4)][] = array( + 'id' => $row['id_member'], + 'name' => $row['real_name'], + 'age' => $row['birth_year'] > 4 && $row['birth_year'] <= $age_year ? $age_year - $row['birth_year'] : null, + 'is_last' => false + ); + } + $smcFunc['db_free_result']($result); + + // Set is_last, so the themes know when to stop placing separators. + foreach ($bday as $mday => $array) + $bday[$mday][count($array) - 1]['is_last'] = true; + + return $bday; +} + +// Get all events within the given time range. +function getEventRange($low_date, $high_date, $use_permissions = true) +{ + global $scripturl, $modSettings, $user_info, $smcFunc, $context; + + $low_date_time = sscanf($low_date, '%04d-%02d-%02d'); + $low_date_time = mktime(0, 0, 0, $low_date_time[1], $low_date_time[2], $low_date_time[0]); + $high_date_time = sscanf($high_date, '%04d-%02d-%02d'); + $high_date_time = mktime(0, 0, 0, $high_date_time[1], $high_date_time[2], $high_date_time[0]); + + // Find all the calendar info... + $result = $smcFunc['db_query']('', ' + SELECT + cal.id_event, cal.start_date, cal.end_date, cal.title, cal.id_member, cal.id_topic, + cal.id_board, b.member_groups, t.id_first_msg, t.approved, b.id_board + FROM {db_prefix}calendar AS cal + LEFT JOIN {db_prefix}boards AS b ON (b.id_board = cal.id_board) + LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = cal.id_topic) + WHERE cal.start_date <= {date:high_date} + AND cal.end_date >= {date:low_date}' . ($use_permissions ? ' + AND (cal.id_board = {int:no_board_link} OR {query_wanna_see_board})' : ''), + array( + 'high_date' => $high_date, + 'low_date' => $low_date, + 'no_board_link' => 0, + ) + ); + $events = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + // If the attached topic is not approved then for the moment pretend it doesn't exist + //!!! This should be fixed to show them all and then sort by approval state later? + if (!empty($row['id_first_msg']) && $modSettings['postmod_active'] && !$row['approved']) + continue; + + // Force a censor of the title - as often these are used by others. + censorText($row['title'], $use_permissions ? false : true); + + $start_date = sscanf($row['start_date'], '%04d-%02d-%02d'); + $start_date = max(mktime(0, 0, 0, $start_date[1], $start_date[2], $start_date[0]), $low_date_time); + $end_date = sscanf($row['end_date'], '%04d-%02d-%02d'); + $end_date = min(mktime(0, 0, 0, $end_date[1], $end_date[2], $end_date[0]), $high_date_time); + + $lastDate = ''; + for ($date = $start_date; $date <= $end_date; $date += 86400) + { + // Attempt to avoid DST problems. + //!!! Resolve this properly at some point. + if (strftime('%Y-%m-%d', $date) == $lastDate) + $date += 3601; + $lastDate = strftime('%Y-%m-%d', $date); + + // If we're using permissions (calendar pages?) then just ouput normal contextual style information. + if ($use_permissions) + $events[strftime('%Y-%m-%d', $date)][] = array( + 'id' => $row['id_event'], + 'title' => $row['title'], + 'can_edit' => allowedTo('calendar_edit_any') || ($row['id_member'] == $user_info['id'] && allowedTo('calendar_edit_own')), + 'modify_href' => $scripturl . '?action=' . ($row['id_board'] == 0 ? 'calendar;sa=post;' : 'post;msg=' . $row['id_first_msg'] . ';topic=' . $row['id_topic'] . '.0;calendar;') . 'eventid=' . $row['id_event'] . ';' . $context['session_var'] . '=' . $context['session_id'], + 'href' => $row['id_board'] == 0 ? '' : $scripturl . '?topic=' . $row['id_topic'] . '.0', + 'link' => $row['id_board'] == 0 ? $row['title'] : '' . $row['title'] . '', + 'start_date' => $row['start_date'], + 'end_date' => $row['end_date'], + 'is_last' => false, + 'id_board' => $row['id_board'], + ); + // Otherwise, this is going to be cached and the VIEWER'S permissions should apply... just put together some info. + else + $events[strftime('%Y-%m-%d', $date)][] = array( + 'id' => $row['id_event'], + 'title' => $row['title'], + 'topic' => $row['id_topic'], + 'msg' => $row['id_first_msg'], + 'poster' => $row['id_member'], + 'start_date' => $row['start_date'], + 'end_date' => $row['end_date'], + 'is_last' => false, + 'allowed_groups' => explode(',', $row['member_groups']), + 'id_board' => $row['id_board'], + 'href' => $row['id_topic'] == 0 ? '' : $scripturl . '?topic=' . $row['id_topic'] . '.0', + 'link' => $row['id_topic'] == 0 ? $row['title'] : '' . $row['title'] . '', + 'can_edit' => false, + ); + } + } + $smcFunc['db_free_result']($result); + + // If we're doing normal contextual data, go through and make things clear to the templates ;). + if ($use_permissions) + { + foreach ($events as $mday => $array) + $events[$mday][count($array) - 1]['is_last'] = true; + } + + return $events; +} + +// Get all holidays within the given time range. +function getHolidayRange($low_date, $high_date) +{ + global $smcFunc; + + // Get the lowest and highest dates for "all years". + if (substr($low_date, 0, 4) != substr($high_date, 0, 4)) + $allyear_part = 'event_date BETWEEN {date:all_year_low} AND {date:all_year_dec} + OR event_date BETWEEN {date:all_year_jan} AND {date:all_year_high}'; + else + $allyear_part = 'event_date BETWEEN {date:all_year_low} AND {date:all_year_high}'; + + // Find some holidays... ;). + $result = $smcFunc['db_query']('', ' + SELECT event_date, YEAR(event_date) AS year, title + FROM {db_prefix}calendar_holidays + WHERE event_date BETWEEN {date:low_date} AND {date:high_date} + OR ' . $allyear_part, + array( + 'low_date' => $low_date, + 'high_date' => $high_date, + 'all_year_low' => '0004' . substr($low_date, 4), + 'all_year_high' => '0004' . substr($high_date, 4), + 'all_year_jan' => '0004-01-01', + 'all_year_dec' => '0004-12-31', + ) + ); + $holidays = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + if (substr($low_date, 0, 4) != substr($high_date, 0, 4)) + $event_year = substr($row['event_date'], 5) < substr($high_date, 5) ? substr($high_date, 0, 4) : substr($low_date, 0, 4); + else + $event_year = substr($low_date, 0, 4); + + $holidays[$event_year . substr($row['event_date'], 4)][] = $row['title']; + } + $smcFunc['db_free_result']($result); + + return $holidays; +} + +// Does permission checks to see if an event can be linked to a board/topic. +function canLinkEvent() +{ + global $user_info, $topic, $board, $smcFunc; + + // If you can't post, you can't link. + isAllowedTo('calendar_post'); + + // No board? No topic?!? + if (empty($board)) + fatal_lang_error('missing_board_id', false); + if (empty($topic)) + fatal_lang_error('missing_topic_id', false); + + // Administrator, Moderator, or owner. Period. + if (!allowedTo('admin_forum') && !allowedTo('moderate_board')) + { + // Not admin or a moderator of this board. You better be the owner - or else. + $result = $smcFunc['db_query']('', ' + SELECT id_member_started + FROM {db_prefix}topics + WHERE id_topic = {int:current_topic} + LIMIT 1', + array( + 'current_topic' => $topic, + ) + ); + if ($row = $smcFunc['db_fetch_assoc']($result)) + { + // Not the owner of the topic. + if ($row['id_member_started'] != $user_info['id']) + fatal_lang_error('not_your_topic', 'user'); + } + // Topic/Board doesn't exist..... + else + fatal_lang_error('calendar_no_topic', 'general'); + $smcFunc['db_free_result']($result); + } +} + +// Returns date information about 'today' relative to the users time offset. +function getTodayInfo() +{ + return array( + 'day' => (int) strftime('%d', forum_time()), + 'month' => (int) strftime('%m', forum_time()), + 'year' => (int) strftime('%Y', forum_time()), + 'date' => strftime('%Y-%m-%d', forum_time()), + ); +} + +// Returns the information needed to show a calendar grid for the given month. +function getCalendarGrid($month, $year, $calendarOptions) +{ + global $scripturl, $modSettings; + + // Eventually this is what we'll be returning. + $calendarGrid = array( + 'week_days' => array(), + 'weeks' => array(), + 'short_day_titles' => !empty($calendarOptions['short_day_titles']), + 'current_month' => $month, + 'current_year' => $year, + 'show_next_prev' => !empty($calendarOptions['show_next_prev']), + 'show_week_links' => !empty($calendarOptions['show_week_links']), + 'previous_calendar' => array( + 'year' => $month == 1 ? $year - 1 : $year, + 'month' => $month == 1 ? 12 : $month - 1, + 'disabled' => $modSettings['cal_minyear'] > ($month == 1 ? $year - 1 : $year), + ), + 'next_calendar' => array( + 'year' => $month == 12 ? $year + 1 : $year, + 'month' => $month == 12 ? 1 : $month + 1, + 'disabled' => $modSettings['cal_maxyear'] < ($month == 12 ? $year + 1 : $year), + ), + //!!! Better tweaks? + 'size' => isset($calendarOptions['size']) ? $calendarOptions['size'] : 'large', + ); + + // Get todays date. + $today = getTodayInfo(); + + // Get information about this month. + $month_info = array( + 'first_day' => array( + 'day_of_week' => (int) strftime('%w', mktime(0, 0, 0, $month, 1, $year)), + 'week_num' => (int) strftime('%U', mktime(0, 0, 0, $month, 1, $year)), + 'date' => strftime('%Y-%m-%d', mktime(0, 0, 0, $month, 1, $year)), + ), + 'last_day' => array( + 'day_of_month' => (int) strftime('%d', mktime(0, 0, 0, $month == 12 ? 1 : $month + 1, 0, $month == 12 ? $year + 1 : $year)), + 'date' => strftime('%Y-%m-%d', mktime(0, 0, 0, $month == 12 ? 1 : $month + 1, 0, $month == 12 ? $year + 1 : $year)), + ), + 'first_day_of_year' => (int) strftime('%w', mktime(0, 0, 0, 1, 1, $year)), + 'first_day_of_next_year' => (int) strftime('%w', mktime(0, 0, 0, 1, 1, $year + 1)), + ); + + // The number of days the first row is shifted to the right for the starting day. + $nShift = $month_info['first_day']['day_of_week']; + + $calendarOptions['start_day'] = empty($calendarOptions['start_day']) ? 0 : (int) $calendarOptions['start_day']; + + // Starting any day other than Sunday means a shift... + if (!empty($calendarOptions['start_day'])) + { + $nShift -= $calendarOptions['start_day']; + if ($nShift < 0) + $nShift = 7 + $nShift; + } + + // Number of rows required to fit the month. + $nRows = floor(($month_info['last_day']['day_of_month'] + $nShift) / 7); + if (($month_info['last_day']['day_of_month'] + $nShift) % 7) + $nRows++; + + // Fetch the arrays for birthdays, posted events, and holidays. + $bday = $calendarOptions['show_birthdays'] ? getBirthdayRange($month_info['first_day']['date'], $month_info['last_day']['date']) : array(); + $events = $calendarOptions['show_events'] ? getEventRange($month_info['first_day']['date'], $month_info['last_day']['date']) : array(); + $holidays = $calendarOptions['show_holidays'] ? getHolidayRange($month_info['first_day']['date'], $month_info['last_day']['date']) : array(); + + // Days of the week taking into consideration that they may want it to start on any day. + $count = $calendarOptions['start_day']; + for ($i = 0; $i < 7; $i++) + { + $calendarGrid['week_days'][] = $count; + $count++; + if ($count == 7) + $count = 0; + } + + // An adjustment value to apply to all calculated week numbers. + if (!empty($calendarOptions['show_week_num'])) + { + // If the first day of the year is a Sunday, then there is no + // adjustment to be made. However, if the first day of the year is not + // a Sunday, then there is a partial week at the start of the year + // that needs to be accounted for. + if ($calendarOptions['start_day'] === 0) + $nWeekAdjust = $month_info['first_day_of_year'] === 0 ? 0 : 1; + // If we are viewing the weeks, with a starting date other than Sunday, + // then things get complicated! Basically, as PHP is calculating the + // weeks with a Sunday starting date, we need to take this into account + // and offset the whole year dependant on whether the first day in the + // year is above or below our starting date. Note that we offset by + // two, as some of this will get undone quite quickly by the statement + // below. + else + $nWeekAdjust = $calendarOptions['start_day'] > $month_info['first_day_of_year'] && $month_info['first_day_of_year'] !== 0 ? 2 : 1; + + // If our week starts on a day greater than the day the month starts + // on, then our week numbers will be one too high. So we need to + // reduce it by one - all these thoughts of offsets makes my head + // hurt... + if ($month_info['first_day']['day_of_week'] < $calendarOptions['start_day'] || $month_info['first_day_of_year'] > 4) + $nWeekAdjust--; + } + else + $nWeekAdjust = 0; + + // Iterate through each week. + $calendarGrid['weeks'] = array(); + for ($nRow = 0; $nRow < $nRows; $nRow++) + { + // Start off the week - and don't let it go above 52, since that's the number of weeks in a year. + $calendarGrid['weeks'][$nRow] = array( + 'days' => array(), + 'number' => $month_info['first_day']['week_num'] + $nRow + $nWeekAdjust + ); + // Handle the dreaded "week 53", it can happen, but only once in a blue moon ;) + if ($calendarGrid['weeks'][$nRow]['number'] == 53 && $nShift != 4 && $month_info['first_day_of_next_year'] < 4) + $calendarGrid['weeks'][$nRow]['number'] = 1; + + // And figure out all the days. + for ($nCol = 0; $nCol < 7; $nCol++) + { + $nDay = ($nRow * 7) + $nCol - $nShift + 1; + + if ($nDay < 1 || $nDay > $month_info['last_day']['day_of_month']) + $nDay = 0; + + $date = sprintf('%04d-%02d-%02d', $year, $month, $nDay); + + $calendarGrid['weeks'][$nRow]['days'][$nCol] = array( + 'day' => $nDay, + 'date' => $date, + 'is_today' => $date == $today['date'], + 'is_first_day' => !empty($calendarOptions['show_week_num']) && (($month_info['first_day']['day_of_week'] + $nDay - 1) % 7 == $calendarOptions['start_day']), + 'holidays' => !empty($holidays[$date]) ? $holidays[$date] : array(), + 'events' => !empty($events[$date]) ? $events[$date] : array(), + 'birthdays' => !empty($bday[$date]) ? $bday[$date] : array() + ); + } + } + + // Set the previous and the next month's links. + $calendarGrid['previous_calendar']['href'] = $scripturl . '?action=calendar;year=' . $calendarGrid['previous_calendar']['year'] . ';month=' . $calendarGrid['previous_calendar']['month']; + $calendarGrid['next_calendar']['href'] = $scripturl . '?action=calendar;year=' . $calendarGrid['next_calendar']['year'] . ';month=' . $calendarGrid['next_calendar']['month']; + + return $calendarGrid; +} + +// Returns the information needed to show a calendar for the given week. +function getCalendarWeek($month, $year, $day, $calendarOptions) +{ + global $scripturl, $modSettings; + + // Get todays date. + $today = getTodayInfo(); + + // What is the actual "start date" for the passed day. + $calendarOptions['start_day'] = empty($calendarOptions['start_day']) ? 0 : (int) $calendarOptions['start_day']; + $day_of_week = (int) strftime('%w', mktime(0, 0, 0, $month, $day, $year)); + if ($day_of_week != $calendarOptions['start_day']) + { + // Here we offset accordingly to get things to the real start of a week. + $date_diff = $day_of_week - $calendarOptions['start_day']; + if ($date_diff < 0) + $date_diff += 7; + $new_timestamp = mktime(0, 0, 0, $month, $day, $year) - $date_diff * 86400; + $day = (int) strftime('%d', $new_timestamp); + $month = (int) strftime('%m', $new_timestamp); + $year = (int) strftime('%Y', $new_timestamp); + } + + // Now start filling in the calendar grid. + $calendarGrid = array( + 'show_next_prev' => !empty($calendarOptions['show_next_prev']), + // Previous week is easy - just step back one day. + 'previous_week' => array( + 'year' => $day == 1 ? ($month == 1 ? $year - 1 : $year) : $year, + 'month' => $day == 1 ? ($month == 1 ? 12 : $month - 1) : $month, + 'day' => $day == 1 ? 28 : $day - 1, + 'disabled' => $day < 7 && $modSettings['cal_minyear'] > ($month == 1 ? $year - 1 : $year), + ), + 'next_week' => array( + 'disabled' => $day > 25 && $modSettings['cal_maxyear'] < ($month == 12 ? $year + 1 : $year), + ), + ); + + // The next week calculation requires a bit more work. + $curTimestamp = mktime(0, 0, 0, $month, $day, $year); + $nextWeekTimestamp = $curTimestamp + 604800; + $calendarGrid['next_week']['day'] = (int) strftime('%d', $nextWeekTimestamp); + $calendarGrid['next_week']['month'] = (int) strftime('%m', $nextWeekTimestamp); + $calendarGrid['next_week']['year'] = (int) strftime('%Y', $nextWeekTimestamp); + + // Fetch the arrays for birthdays, posted events, and holidays. + $startDate = strftime('%Y-%m-%d', $curTimestamp); + $endDate = strftime('%Y-%m-%d', $nextWeekTimestamp); + $bday = $calendarOptions['show_birthdays'] ? getBirthdayRange($startDate, $endDate) : array(); + $events = $calendarOptions['show_events'] ? getEventRange($startDate, $endDate) : array(); + $holidays = $calendarOptions['show_holidays'] ? getHolidayRange($startDate, $endDate) : array(); + + // An adjustment value to apply to all calculated week numbers. + if (!empty($calendarOptions['show_week_num'])) + { + $first_day_of_year = (int) strftime('%w', mktime(0, 0, 0, 1, 1, $year)); + $first_day_of_next_year = (int) strftime('%w', mktime(0, 0, 0, 1, 1, $year + 1)); + $last_day_of_last_year = (int) strftime('%w', mktime(0, 0, 0, 12, 31, $year - 1)); + + // All this is as getCalendarGrid. + if ($calendarOptions['start_day'] === 0) + $nWeekAdjust = $first_day_of_year === 0 && $first_day_of_year > 3 ? 0 : 1; + else + $nWeekAdjust = $calendarOptions['start_day'] > $first_day_of_year && $first_day_of_year !== 0 ? 2 : 1; + + $calendarGrid['week_number'] = (int) strftime('%U', mktime(0, 0, 0, $month, $day, $year)) + $nWeekAdjust; + + // If this crosses a year boundry and includes january it should be week one. + if ((int) strftime('%Y', $curTimestamp + 518400) != $year && $calendarGrid['week_number'] > 53 && $first_day_of_next_year < 5) + $calendarGrid['week_number'] = 1; + } + + // This holds all the main data - there is at least one month! + $calendarGrid['months'] = array(); + $lastDay = 99; + $curDay = $day; + $curDayOfWeek = $calendarOptions['start_day']; + for ($i = 0; $i < 7; $i++) + { + // Have we gone into a new month (Always happens first cycle too) + if ($lastDay > $curDay) + { + $curMonth = $lastDay == 99 ? $month : ($month == 12 ? 1 : $month + 1); + $curYear = $lastDay == 99 ? $year : ($curMonth == 1 && $month == 12 ? $year + 1 : $year); + $calendarGrid['months'][$curMonth] = array( + 'current_month' => $curMonth, + 'current_year' => $curYear, + 'days' => array(), + ); + } + + // Add todays information to the pile! + $date = sprintf('%04d-%02d-%02d', $curYear, $curMonth, $curDay); + + $calendarGrid['months'][$curMonth]['days'][$curDay] = array( + 'day' => $curDay, + 'day_of_week' => $curDayOfWeek, + 'date' => $date, + 'is_today' => $date == $today['date'], + 'holidays' => !empty($holidays[$date]) ? $holidays[$date] : array(), + 'events' => !empty($events[$date]) ? $events[$date] : array(), + 'birthdays' => !empty($bday[$date]) ? $bday[$date] : array() + ); + + // Make the last day what the current day is and work out what the next day is. + $lastDay = $curDay; + $curTimestamp += 86400; + $curDay = (int) strftime('%d', $curTimestamp); + + // Also increment the current day of the week. + $curDayOfWeek = $curDayOfWeek >= 6 ? 0 : ++$curDayOfWeek; + } + + // Set the previous and the next week's links. + $calendarGrid['previous_week']['href'] = $scripturl . '?action=calendar;viewweek;year=' . $calendarGrid['previous_week']['year'] . ';month=' . $calendarGrid['previous_week']['month'] . ';day=' . $calendarGrid['previous_week']['day']; + $calendarGrid['next_week']['href'] = $scripturl . '?action=calendar;viewweek;year=' . $calendarGrid['next_week']['year'] . ';month=' . $calendarGrid['next_week']['month'] . ';day=' . $calendarGrid['next_week']['day']; + + return $calendarGrid; +} + +// Retrieve all events for the given days, independently of the users offset. +function cache_getOffsetIndependentEvents($days_to_index) +{ + global $sourcedir; + + $low_date = strftime('%Y-%m-%d', forum_time(false) - 24 * 3600); + $high_date = strftime('%Y-%m-%d', forum_time(false) + $days_to_index * 24 * 3600); + + return array( + 'data' => array( + 'holidays' => getHolidayRange($low_date, $high_date), + 'birthdays' => getBirthdayRange($low_date, $high_date), + 'events' => getEventRange($low_date, $high_date, false), + ), + 'refresh_eval' => 'return \'' . strftime('%Y%m%d', forum_time(false)) . '\' != strftime(\'%Y%m%d\', forum_time(false)) || (!empty($modSettings[\'calendar_updated\']) && ' . time() . ' < $modSettings[\'calendar_updated\']);', + 'expires' => time() + 3600, + ); +} + +// Called from the BoardIndex to display the current day's events on the board index. +function cache_getRecentEvents($eventOptions) +{ + global $modSettings, $user_info, $scripturl; + + // With the 'static' cached data we can calculate the user-specific data. + $cached_data = cache_quick_get('calendar_index', 'Subs-Calendar.php', 'cache_getOffsetIndependentEvents', array($eventOptions['num_days_shown'])); + + // Get the information about today (from user perspective). + $today = getTodayInfo(); + + $return_data = array( + 'calendar_holidays' => array(), + 'calendar_birthdays' => array(), + 'calendar_events' => array(), + ); + + // Set the event span to be shown in seconds. + $days_for_index = $eventOptions['num_days_shown'] * 86400; + + // Get the current member time/date. + $now = forum_time(); + + // Holidays between now and now + days. + for ($i = $now; $i < $now + $days_for_index; $i += 86400) + { + if (isset($cached_data['holidays'][strftime('%Y-%m-%d', $i)])) + $return_data['calendar_holidays'] = array_merge($return_data['calendar_holidays'], $cached_data['holidays'][strftime('%Y-%m-%d', $i)]); + } + + // Happy Birthday, guys and gals! + for ($i = $now; $i < $now + $days_for_index; $i += 86400) + { + $loop_date = strftime('%Y-%m-%d', $i); + if (isset($cached_data['birthdays'][$loop_date])) + { + foreach ($cached_data['birthdays'][$loop_date] as $index => $dummy) + $cached_data['birthdays'][strftime('%Y-%m-%d', $i)][$index]['is_today'] = $loop_date === $today['date']; + $return_data['calendar_birthdays'] = array_merge($return_data['calendar_birthdays'], $cached_data['birthdays'][$loop_date]); + } + } + + $duplicates = array(); + for ($i = $now; $i < $now + $days_for_index; $i += 86400) + { + // Determine the date of the current loop step. + $loop_date = strftime('%Y-%m-%d', $i); + + // No events today? Check the next day. + if (empty($cached_data['events'][$loop_date])) + continue; + + // Loop through all events to add a few last-minute values. + foreach ($cached_data['events'][$loop_date] as $ev => $event) + { + // Create a shortcut variable for easier access. + $this_event = &$cached_data['events'][$loop_date][$ev]; + + // Skip duplicates. + if (isset($duplicates[$this_event['topic'] . $this_event['title']])) + { + unset($cached_data['events'][$loop_date][$ev]); + continue; + } + else + $duplicates[$this_event['topic'] . $this_event['title']] = true; + + // Might be set to true afterwards, depending on the permissions. + $this_event['can_edit'] = false; + $this_event['is_today'] = $loop_date === $today['date']; + $this_event['date'] = $loop_date; + } + + if (!empty($cached_data['events'][$loop_date])) + $return_data['calendar_events'] = array_merge($return_data['calendar_events'], $cached_data['events'][$loop_date]); + } + + // Mark the last item so that a list separator can be used in the template. + for ($i = 0, $n = count($return_data['calendar_birthdays']); $i < $n; $i++) + $return_data['calendar_birthdays'][$i]['is_last'] = !isset($return_data['calendar_birthdays'][$i + 1]); + for ($i = 0, $n = count($return_data['calendar_events']); $i < $n; $i++) + $return_data['calendar_events'][$i]['is_last'] = !isset($return_data['calendar_events'][$i + 1]); + + return array( + 'data' => $return_data, + 'expires' => time() + 3600, + 'refresh_eval' => 'return \'' . strftime('%Y%m%d', forum_time(false)) . '\' != strftime(\'%Y%m%d\', forum_time(false)) || (!empty($modSettings[\'calendar_updated\']) && ' . time() . ' < $modSettings[\'calendar_updated\']);', + 'post_retri_eval' => ' + global $context, $scripturl, $user_info; + + foreach ($cache_block[\'data\'][\'calendar_events\'] as $k => $event) + { + // Remove events that the user may not see or wants to ignore. + if ((count(array_intersect($user_info[\'groups\'], $event[\'allowed_groups\'])) === 0 && !allowedTo(\'admin_forum\') && !empty($event[\'id_board\'])) || in_array($event[\'id_board\'], $user_info[\'ignoreboards\'])) + unset($cache_block[\'data\'][\'calendar_events\'][$k]); + else + { + // Whether the event can be edited depends on the permissions. + $cache_block[\'data\'][\'calendar_events\'][$k][\'can_edit\'] = allowedTo(\'calendar_edit_any\') || ($event[\'poster\'] == $user_info[\'id\'] && allowedTo(\'calendar_edit_own\')); + + // The added session code makes this URL not cachable. + $cache_block[\'data\'][\'calendar_events\'][$k][\'modify_href\'] = $scripturl . \'?action=\' . ($event[\'topic\'] == 0 ? \'calendar;sa=post;\' : \'post;msg=\' . $event[\'msg\'] . \';topic=\' . $event[\'topic\'] . \'.0;calendar;\') . \'eventid=\' . $event[\'id\'] . \';\' . $context[\'session_var\'] . \'=\' . $context[\'session_id\']; + } + } + + if (empty($params[0][\'include_holidays\'])) + $cache_block[\'data\'][\'calendar_holidays\'] = array(); + if (empty($params[0][\'include_birthdays\'])) + $cache_block[\'data\'][\'calendar_birthdays\'] = array(); + if (empty($params[0][\'include_events\'])) + $cache_block[\'data\'][\'calendar_events\'] = array(); + + $cache_block[\'data\'][\'show_calendar\'] = !empty($cache_block[\'data\'][\'calendar_holidays\']) || !empty($cache_block[\'data\'][\'calendar_birthdays\']) || !empty($cache_block[\'data\'][\'calendar_events\']);', + ); +} + +// Makes sure the calendar post is valid. +function validateEventPost() +{ + global $modSettings, $txt, $sourcedir, $smcFunc; + + if (!isset($_POST['deleteevent'])) + { + // No month? No year? + if (!isset($_POST['month'])) + fatal_lang_error('event_month_missing', false); + if (!isset($_POST['year'])) + fatal_lang_error('event_year_missing', false); + + // Check the month and year... + if ($_POST['month'] < 1 || $_POST['month'] > 12) + fatal_lang_error('invalid_month', false); + if ($_POST['year'] < $modSettings['cal_minyear'] || $_POST['year'] > $modSettings['cal_maxyear']) + fatal_lang_error('invalid_year', false); + } + + // Make sure they're allowed to post... + isAllowedTo('calendar_post'); + + if (isset($_POST['span'])) + { + // Make sure it's turned on and not some fool trying to trick it. + if (empty($modSettings['cal_allowspan'])) + fatal_lang_error('no_span', false); + if ($_POST['span'] < 1 || $_POST['span'] > $modSettings['cal_maxspan']) + fatal_lang_error('invalid_days_numb', false); + } + + // There is no need to validate the following values if we are just deleting the event. + if (!isset($_POST['deleteevent'])) + { + // No day? + if (!isset($_POST['day'])) + fatal_lang_error('event_day_missing', false); + if (!isset($_POST['evtitle']) && !isset($_POST['subject'])) + fatal_lang_error('event_title_missing', false); + elseif (!isset($_POST['evtitle'])) + $_POST['evtitle'] = $_POST['subject']; + + // Bad day? + if (!checkdate($_POST['month'], $_POST['day'], $_POST['year'])) + fatal_lang_error('invalid_date', false); + + // No title? + if ($smcFunc['htmltrim']($_POST['evtitle']) === '') + fatal_lang_error('no_event_title', false); + if ($smcFunc['strlen']($_POST['evtitle']) > 30) + $_POST['evtitle'] = $smcFunc['substr']($_POST['evtitle'], 0, 30); + $_POST['evtitle'] = str_replace(';', '', $_POST['evtitle']); + } +} + +// Get the event's poster. +function getEventPoster($event_id) +{ + global $smcFunc; + + // A simple database query, how hard can that be? + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}calendar + WHERE id_event = {int:id_event} + LIMIT 1', + array( + 'id_event' => $event_id, + ) + ); + + // No results, return false. + if ($smcFunc['db_num_rows'] === 0) + return false; + + // Grab the results and return. + list ($poster) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + return $poster; +} + +// Consolidating the various INSERT statements into this function. +function insertEvent(&$eventOptions) +{ + global $modSettings, $smcFunc; + + // Add special chars to the title. + $eventOptions['title'] = $smcFunc['htmlspecialchars']($eventOptions['title'], ENT_QUOTES); + + // Add some sanity checking to the span. + $eventOptions['span'] = isset($eventOptions['span']) && $eventOptions['span'] > 0 ? (int) $eventOptions['span'] : 0; + + // Make sure the start date is in ISO order. + if (($num_results = sscanf($eventOptions['start_date'], '%d-%d-%d', $year, $month, $day)) !== 3) + trigger_error('modifyEvent(): invalid start date format given', E_USER_ERROR); + + // Set the end date (if not yet given) + if (!isset($eventOptions['end_date'])) + $eventOptions['end_date'] = strftime('%Y-%m-%d', mktime(0, 0, 0, $month, $day, $year) + $eventOptions['span'] * 86400); + + // If no topic and board are given, they are not linked to a topic. + $eventOptions['board'] = isset($eventOptions['board']) ? (int) $eventOptions['board'] : 0; + $eventOptions['topic'] = isset($eventOptions['topic']) ? (int) $eventOptions['topic'] : 0; + + // Insert the event! + $smcFunc['db_insert']('', + '{db_prefix}calendar', + array( + 'id_board' => 'int', 'id_topic' => 'int', 'title' => 'string-60', 'id_member' => 'int', + 'start_date' => 'date', 'end_date' => 'date', + ), + array( + $eventOptions['board'], $eventOptions['topic'], $eventOptions['title'], $eventOptions['member'], + $eventOptions['start_date'], $eventOptions['end_date'], + ), + array('id_event') + ); + + // Store the just inserted id_event for future reference. + $eventOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}calendar', 'id_event'); + + // Update the settings to show something calendarish was updated. + updateSettings(array( + 'calendar_updated' => time(), + )); +} + +function modifyEvent($event_id, &$eventOptions) +{ + global $smcFunc; + + // Properly sanitize the title. + $eventOptions['title'] = $smcFunc['htmlspecialchars']($eventOptions['title'], ENT_QUOTES); + + // Scan the start date for validity and get its components. + if (($num_results = sscanf($eventOptions['start_date'], '%d-%d-%d', $year, $month, $day)) !== 3) + trigger_error('modifyEvent(): invalid start date format given', E_USER_ERROR); + + // Default span to 0 days. + $eventOptions['span'] = isset($eventOptions['span']) ? (int) $eventOptions['span'] : 0; + + // Set the end date to the start date + span (if the end date wasn't already given). + if (!isset($eventOptions['end_date'])) + $eventOptions['end_date'] = strftime('%Y-%m-%d', mktime(0, 0, 0, $month, $day, $year) + $eventOptions['span'] * 86400); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}calendar + SET + start_date = {date:start_date}, + end_date = {date:end_date}, + title = SUBSTRING({string:title}, 1, 60), + id_board = {int:id_board}, + id_topic = {int:id_topic} + WHERE id_event = {int:id_event}', + array( + 'start_date' => $eventOptions['start_date'], + 'end_date' => $eventOptions['end_date'], + 'title' => $eventOptions['title'], + 'id_board' => isset($eventOptions['board']) ? (int) $eventOptions['board'] : 0, + 'id_topic' => isset($eventOptions['topic']) ? (int) $eventOptions['topic'] : 0, + 'id_event' => $event_id, + ) + ); + + updateSettings(array( + 'calendar_updated' => time(), + )); +} + +function removeEvent($event_id) +{ + global $smcFunc; + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}calendar + WHERE id_event = {int:id_event}', + array( + 'id_event' => $event_id, + ) + ); + + updateSettings(array( + 'calendar_updated' => time(), + )); +} + +function getEventProperties($event_id) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT + c.id_event, c.id_board, c.id_topic, MONTH(c.start_date) AS month, + DAYOFMONTH(c.start_date) AS day, YEAR(c.start_date) AS year, + (TO_DAYS(c.end_date) - TO_DAYS(c.start_date)) AS span, c.id_member, c.title, + t.id_first_msg, t.id_member_started + FROM {db_prefix}calendar AS c + LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = c.id_topic) + WHERE c.id_event = {int:id_event}', + array( + 'id_event' => $event_id, + ) + ); + + // If nothing returned, we are in poo, poo. + if ($smcFunc['db_num_rows']($request) === 0) + return false; + + $row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + $return_value = array( + 'boards' => array(), + 'board' => $row['id_board'], + 'new' => 0, + 'eventid' => $event_id, + 'year' => $row['year'], + 'month' => $row['month'], + 'day' => $row['day'], + 'title' => $row['title'], + 'span' => 1 + $row['span'], + 'member' => $row['id_member'], + 'topic' => array( + 'id' => $row['id_topic'], + 'member_started' => $row['id_member_started'], + 'first_msg' => $row['id_first_msg'], + ), + ); + + $return_value['last_day'] = (int) strftime('%d', mktime(0, 0, 0, $return_value['month'] == 12 ? 1 : $return_value['month'] + 1, 0, $return_value['month'] == 12 ? $return_value['year'] + 1 : $return_value['year'])); + + return $return_value; +} + +function list_getHolidays($start, $items_per_page, $sort) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT id_holiday, YEAR(event_date) AS year, MONTH(event_date) AS month, DAYOFMONTH(event_date) AS day, title + FROM {db_prefix}calendar_holidays + ORDER BY {raw:sort} + LIMIT ' . $start . ', ' . $items_per_page, + array( + 'sort' => $sort, + ) + ); + $holidays = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $holidays[] = $row; + $smcFunc['db_free_result']($request); + + return $holidays; +} + +function list_getNumHolidays() +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}calendar_holidays', + array( + ) + ); + list($num_items) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $num_items; +} + +function removeHolidays($holiday_ids) +{ + global $smcFunc; + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}calendar_holidays + WHERE id_holiday IN ({array_int:id_holiday})', + array( + 'id_holiday' => $holiday_ids, + ) + ); + + updateSettings(array( + 'calendar_updated' => time(), + )); +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Categories.php b/Sources/Subs-Categories.php new file mode 100644 index 0000000..2a7df40 --- /dev/null +++ b/Sources/Subs-Categories.php @@ -0,0 +1,332 @@ + $cat) + if ($index != $cat_order[$cat]) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}categories + SET cat_order = {int:new_order} + WHERE id_cat = {int:current_category}', + array( + 'new_order' => $index, + 'current_category' => $cat, + ) + ); + + // If the category order changed, so did the board order. + require_once($sourcedir . '/Subs-Boards.php'); + reorderBoards(); + } + + if (isset($catOptions['cat_name'])) + { + $catUpdates[] = 'name = {string:cat_name}'; + $catParameters['cat_name'] = $catOptions['cat_name']; + } + + // Can a user collapse this category or is it too important? + if (isset($catOptions['is_collapsible'])) + { + $catUpdates[] = 'can_collapse = {int:is_collapsible}'; + $catParameters['is_collapsible'] = $catOptions['is_collapsible'] ? 1 : 0; + } + + // Do the updates (if any). + if (!empty($catUpdates)) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}categories + SET + ' . implode(', + ', $catUpdates) . ' + WHERE id_cat = {int:current_category}', + array_merge($catParameters, array( + 'current_category' => $category_id, + )) + ); + + if (empty($catOptions['dont_log'])) + logAction('edit_cat', array('catname' => isset($catOptions['cat_name']) ? $catOptions['cat_name'] : $category_id), 'admin'); + } +} + +// Create a new category. +function createCategory($catOptions) +{ + global $smcFunc; + + // Check required values. + if (!isset($catOptions['cat_name']) || trim($catOptions['cat_name']) == '') + trigger_error('createCategory(): A category name is required', E_USER_ERROR); + + // Set default values. + if (!isset($catOptions['move_after'])) + $catOptions['move_after'] = 0; + if (!isset($catOptions['is_collapsible'])) + $catOptions['is_collapsible'] = true; + // Don't log an edit right after. + $catOptions['dont_log'] = true; + + // Add the category to the database. + $smcFunc['db_insert']('', + '{db_prefix}categories', + array( + 'name' => 'string-48', + ), + array( + $catOptions['cat_name'], + ), + array('id_cat') + ); + + // Grab the new category ID. + $category_id = $smcFunc['db_insert_id']('{db_prefix}categories', 'id_cat'); + + // Set the given properties to the newly created category. + modifyCategory($category_id, $catOptions); + + logAction('add_cat', array('catname' => $catOptions['cat_name']), 'admin'); + + // Return the database ID of the category. + return $category_id; +} + +// Remove one or more categories. +function deleteCategories($categories, $moveBoardsTo = null) +{ + global $sourcedir, $smcFunc, $cat_tree; + + require_once($sourcedir . '/Subs-Boards.php'); + + getBoardTree(); + + // With no category set to move the boards to, delete them all. + if ($moveBoardsTo === null) + { + $request = $smcFunc['db_query']('', ' + SELECT id_board + FROM {db_prefix}boards + WHERE id_cat IN ({array_int:category_list})', + array( + 'category_list' => $categories, + ) + ); + $boards_inside = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $boards_inside[] = $row['id_board']; + $smcFunc['db_free_result']($request); + + if (!empty($boards_inside)) + deleteBoards($boards_inside, null); + } + + // Make sure the safe category is really safe. + elseif (in_array($moveBoardsTo, $categories)) + trigger_error('deleteCategories(): You cannot move the boards to a category that\'s being deleted', E_USER_ERROR); + + // Move the boards inside the categories to a safe category. + else + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET id_cat = {int:new_parent_cat} + WHERE id_cat IN ({array_int:category_list})', + array( + 'category_list' => $categories, + 'new_parent_cat' => $moveBoardsTo, + ) + ); + + // Noone will ever be able to collapse these categories anymore. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}collapsed_categories + WHERE id_cat IN ({array_int:category_list})', + array( + 'category_list' => $categories, + ) + ); + + // Do the deletion of the category itself + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}categories + WHERE id_cat IN ({array_int:category_list})', + array( + 'category_list' => $categories, + ) + ); + + // Log what we've done. + foreach ($categories as $category) + logAction('delete_cat', array('catname' => $cat_tree[$category]['node']['name']), 'admin'); + + // Get all boards back into the right order. + reorderBoards(); +} + +// Collapse, expand or toggle one or more categories for one or more members. +function collapseCategories($categories, $new_status, $members = null, $check_collapsable = true) +{ + global $smcFunc; + + // Collapse or expand the categories. + if ($new_status === 'collapse' || $new_status === 'expand') + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}collapsed_categories + WHERE id_cat IN ({array_int:category_list})' . ($members === null ? '' : ' + AND id_member IN ({array_int:member_list})'), + array( + 'category_list' => $categories, + 'member_list' => $members, + ) + ); + + if ($new_status === 'collapse') + $smcFunc['db_query']('', ' + INSERT INTO {db_prefix}collapsed_categories + (id_cat, id_member) + SELECT c.id_cat, mem.id_member + FROM {db_prefix}categories AS c + INNER JOIN {db_prefix}members AS mem ON (' . ($members === null ? '1=1' : ' + mem.id_member IN ({array_int:member_list})') . ') + WHERE c.id_cat IN ({array_int:category_list})' . ($check_collapsable ? ' + AND c.can_collapse = {int:is_collapsible}' : ''), + array( + 'member_list' => $members, + 'category_list' => $categories, + 'is_collapsible' => 1, + ) + ); + } + + // Toggle the categories: collapsed get expanded and expanded get collapsed. + elseif ($new_status === 'toggle') + { + // Get the current state of the categories. + $updates = array( + 'insert' => array(), + 'remove' => array(), + ); + $request = $smcFunc['db_query']('', ' + SELECT mem.id_member, c.id_cat, IFNULL(cc.id_cat, 0) AS is_collapsed, c.can_collapse + FROM {db_prefix}members AS mem + INNER JOIN {db_prefix}categories AS c ON (c.id_cat IN ({array_int:category_list})) + LEFT JOIN {db_prefix}collapsed_categories AS cc ON (cc.id_cat = c.id_cat AND cc.id_member = mem.id_member) + ' . ($members === null ? '' : ' + WHERE mem.id_member IN ({array_int:member_list})'), + array( + 'category_list' => $categories, + 'member_list' => $members, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (empty($row['is_collapsed']) && (!empty($row['can_collapse']) || !$check_collapsable)) + $updates['insert'][] = array($row['id_member'], $row['id_cat']); + elseif (!empty($row['is_collapsed'])) + $updates['remove'][] = '(id_member = ' . $row['id_member'] . ' AND id_cat = ' . $row['id_cat'] . ')'; + } + $smcFunc['db_free_result']($request); + + // Collapse the ones that were originally expanded... + if (!empty($updates['insert'])) + $smcFunc['db_insert']('replace', + '{db_prefix}collapsed_categories', + array( + 'id_cat' => 'int', 'id_member' => 'int', + ), + $updates['insert'], + array('id_cat', 'id_member') + ); + + // And expand the ones that were originally collapsed. + if (!empty($updates['remove'])) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}collapsed_categories + WHERE ' . implode(' OR ', $updates['remove']), + array( + ) + ); + } +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Charset.php b/Sources/Subs-Charset.php new file mode 100644 index 0000000..04ac123 --- /dev/null +++ b/Sources/Subs-Charset.php @@ -0,0 +1,593 @@ + 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', + 'E' => 'e', 'F' => 'f', 'G' => 'g', 'H' => 'h', + 'I' => 'i', 'J' => 'j', 'K' => 'k', 'L' => 'l', + 'M' => 'm', 'N' => 'n', 'O' => 'o', 'P' => 'p', + 'Q' => 'q', 'R' => 'r', 'S' => 's', 'T' => 't', + 'U' => 'u', 'V' => 'v', 'W' => 'w', 'X' => 'x', + 'Y' => 'y', 'Z' => 'z', 'µ' => 'μ', 'À' => 'à', + 'Á' => 'á', 'Â' => 'â', 'Ã' => 'ã', 'Ä' => 'ä', + 'Å' => 'å', 'Æ' => 'æ', 'Ç' => 'ç', 'È' => 'è', + 'É' => 'é', 'Ê' => 'ê', 'Ë' => 'ë', 'Ì' => 'ì', + 'Í' => 'í', 'Î' => 'î', 'Ï' => 'ï', 'Ð' => 'ð', + 'Ñ' => 'ñ', 'Ò' => 'ò', 'Ó' => 'ó', 'Ô' => 'ô', + 'Õ' => 'õ', 'Ö' => 'ö', 'Ø' => 'ø', 'Ù' => 'ù', + 'Ú' => 'ú', 'Û' => 'û', 'Ü' => 'ü', 'Ý' => 'ý', + 'Þ' => 'þ', 'ß' => 'ss', 'Ā' => 'ā', 'Ă' => 'ă', + 'Ą' => 'ą', 'Ć' => 'ć', 'Ĉ' => 'ĉ', 'Ċ' => 'ċ', + 'Č' => 'č', 'Ď' => 'ď', 'Đ' => 'đ', 'Ē' => 'ē', + 'Ĕ' => 'ĕ', 'Ė' => 'ė', 'Ę' => 'ę', 'Ě' => 'ě', + 'Ĝ' => 'ĝ', 'Ğ' => 'ğ', 'Ġ' => 'ġ', 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', 'Ħ' => 'ħ', 'Ĩ' => 'ĩ', 'Ī' => 'ī', + 'Ĭ' => 'ĭ', 'Į' => 'į', 'İ' => 'i̇', 'IJ' => 'ij', + 'Ĵ' => 'ĵ', 'Ķ' => 'ķ', 'Ĺ' => 'ĺ', 'Ļ' => 'ļ', + 'Ľ' => 'ľ', 'Ŀ' => 'ŀ', 'Ł' => 'ł', 'Ń' => 'ń', + 'Ņ' => 'ņ', 'Ň' => 'ň', 'ʼn' => 'ʼn', 'Ŋ' => 'ŋ', + 'Ō' => 'ō', 'Ŏ' => 'ŏ', 'Ő' => 'ő', 'Œ' => 'œ', + 'Ŕ' => 'ŕ', 'Ŗ' => 'ŗ', 'Ř' => 'ř', 'Ś' => 'ś', + 'Ŝ' => 'ŝ', 'Ş' => 'ş', 'Š' => 'š', 'Ţ' => 'ţ', + 'Ť' => 'ť', 'Ŧ' => 'ŧ', 'Ũ' => 'ũ', 'Ū' => 'ū', + 'Ŭ' => 'ŭ', 'Ů' => 'ů', 'Ű' => 'ű', 'Ų' => 'ų', + 'Ŵ' => 'ŵ', 'Ŷ' => 'ŷ', 'Ÿ' => 'ÿ', 'Ź' => 'ź', + 'Ż' => 'ż', 'Ž' => 'ž', 'ſ' => 's', 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', 'Ƅ' => 'ƅ', 'Ɔ' => 'ɔ', 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', 'Ɗ' => 'ɗ', 'Ƌ' => 'ƌ', 'Ǝ' => 'ǝ', + 'Ə' => 'ə', 'Ɛ' => 'ɛ', 'Ƒ' => 'ƒ', 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', 'Ɩ' => 'ɩ', 'Ɨ' => 'ɨ', 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', 'Ɲ' => 'ɲ', 'Ɵ' => 'ɵ', 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', 'Ƥ' => 'ƥ', 'Ʀ' => 'ʀ', 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', 'Ƭ' => 'ƭ', 'Ʈ' => 'ʈ', 'Ư' => 'ư', + 'Ʊ' => 'ʊ', 'Ʋ' => 'ʋ', 'Ƴ' => 'ƴ', 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', 'Ƹ' => 'ƹ', 'Ƽ' => 'ƽ', 'DŽ' => 'dž', + 'Dž' => 'dž', 'LJ' => 'lj', 'Lj' => 'lj', 'NJ' => 'nj', + 'Nj' => 'nj', 'Ǎ' => 'ǎ', 'Ǐ' => 'ǐ', 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', 'Ǖ' => 'ǖ', 'Ǘ' => 'ǘ', 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', 'Ǟ' => 'ǟ', 'Ǡ' => 'ǡ', 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', 'Ǧ' => 'ǧ', 'Ǩ' => 'ǩ', 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', 'Ǯ' => 'ǯ', 'ǰ' => 'ǰ', 'DZ' => 'dz', + 'Dz' => 'dz', 'Ǵ' => 'ǵ', 'Ƕ' => 'ƕ', 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', 'Ǻ' => 'ǻ', 'Ǽ' => 'ǽ', 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', 'Ȃ' => 'ȃ', 'Ȅ' => 'ȅ', 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', 'Ȋ' => 'ȋ', 'Ȍ' => 'ȍ', 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', 'Ȓ' => 'ȓ', 'Ȕ' => 'ȕ', 'Ȗ' => 'ȗ', + 'Ș' => 'ș', 'Ț' => 'ț', 'Ȝ' => 'ȝ', 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', 'Ȣ' => 'ȣ', 'Ȥ' => 'ȥ', 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', 'Ȫ' => 'ȫ', 'Ȭ' => 'ȭ', 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', 'Ȳ' => 'ȳ', 'Ⱥ' => 'ⱥ', 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', 'Ⱦ' => 'ⱦ', 'Ɂ' => 'ɂ', 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', 'Ʌ' => 'ʌ', 'Ɇ' => 'ɇ', 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', 'Ɍ' => 'ɍ', 'Ɏ' => 'ɏ', 'ͅ' => 'ι', + 'Ά' => 'ά', 'Έ' => 'έ', 'Ή' => 'ή', 'Ί' => 'ί', + 'Ό' => 'ό', 'Ύ' => 'ύ', 'Ώ' => 'ώ', 'ΐ' => 'ΐ', + 'Α' => 'α', 'Β' => 'β', 'Γ' => 'γ', 'Δ' => 'δ', + 'Ε' => 'ε', 'Ζ' => 'ζ', 'Η' => 'η', 'Θ' => 'θ', + 'Ι' => 'ι', 'Κ' => 'κ', 'Λ' => 'λ', 'Μ' => 'μ', + 'Ν' => 'ν', 'Ξ' => 'ξ', 'Ο' => 'ο', 'Π' => 'π', + 'Ρ' => 'ρ', 'Σ' => 'σ', 'Τ' => 'τ', 'Υ' => 'υ', + 'Φ' => 'φ', 'Χ' => 'χ', 'Ψ' => 'ψ', 'Ω' => 'ω', + 'Ϊ' => 'ϊ', 'Ϋ' => 'ϋ', 'ΰ' => 'ΰ', 'ς' => 'σ', + 'ϐ' => 'β', 'ϑ' => 'θ', 'ϕ' => 'φ', 'ϖ' => 'π', + 'Ϙ' => 'ϙ', 'Ϛ' => 'ϛ', 'Ϝ' => 'ϝ', 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', 'Ϣ' => 'ϣ', 'Ϥ' => 'ϥ', 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', 'Ϫ' => 'ϫ', 'Ϭ' => 'ϭ', 'Ϯ' => 'ϯ', + 'ϰ' => 'κ', 'ϱ' => 'ρ', 'ϴ' => 'θ', 'ϵ' => 'ε', + 'Ϸ' => 'ϸ', 'Ϲ' => 'ϲ', 'Ϻ' => 'ϻ', 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', 'Ͽ' => 'ͽ', 'Ѐ' => 'ѐ', 'Ё' => 'ё', + 'Ђ' => 'ђ', 'Ѓ' => 'ѓ', 'Є' => 'є', 'Ѕ' => 'ѕ', + 'І' => 'і', 'Ї' => 'ї', 'Ј' => 'ј', 'Љ' => 'љ', + 'Њ' => 'њ', 'Ћ' => 'ћ', 'Ќ' => 'ќ', 'Ѝ' => 'ѝ', + 'Ў' => 'ў', 'Џ' => 'џ', 'А' => 'а', 'Б' => 'б', + 'В' => 'в', 'Г' => 'г', 'Д' => 'д', 'Е' => 'е', + 'Ж' => 'ж', 'З' => 'з', 'И' => 'и', 'Й' => 'й', + 'К' => 'к', 'Л' => 'л', 'М' => 'м', 'Н' => 'н', + 'О' => 'о', 'П' => 'п', 'Р' => 'р', 'С' => 'с', + 'Т' => 'т', 'У' => 'у', 'Ф' => 'ф', 'Х' => 'х', + 'Ц' => 'ц', 'Ч' => 'ч', 'Ш' => 'ш', 'Щ' => 'щ', + 'Ъ' => 'ъ', 'Ы' => 'ы', 'Ь' => 'ь', 'Э' => 'э', + 'Ю' => 'ю', 'Я' => 'я', 'Ѡ' => 'ѡ', 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', 'Ѧ' => 'ѧ', 'Ѩ' => 'ѩ', 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', 'Ѯ' => 'ѯ', 'Ѱ' => 'ѱ', 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', 'Ѷ' => 'ѷ', 'Ѹ' => 'ѹ', 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', 'Ѿ' => 'ѿ', 'Ҁ' => 'ҁ', 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', 'Ҏ' => 'ҏ', 'Ґ' => 'ґ', 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', 'Җ' => 'җ', 'Ҙ' => 'ҙ', 'Қ' => 'қ', + 'Ҝ' => 'ҝ', 'Ҟ' => 'ҟ', 'Ҡ' => 'ҡ', 'Ң' => 'ң', + 'Ҥ' => 'ҥ', 'Ҧ' => 'ҧ', 'Ҩ' => 'ҩ', 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', 'Ү' => 'ү', 'Ұ' => 'ұ', 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', 'Ҷ' => 'ҷ', 'Ҹ' => 'ҹ', 'Һ' => 'һ', + 'Ҽ' => 'ҽ', 'Ҿ' => 'ҿ', 'Ӏ' => 'ӏ', 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', 'Ӆ' => 'ӆ', 'Ӈ' => 'ӈ', 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', 'Ӎ' => 'ӎ', 'Ӑ' => 'ӑ', 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', 'Ӗ' => 'ӗ', 'Ә' => 'ә', 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', 'Ӟ' => 'ӟ', 'Ӡ' => 'ӡ', 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', 'Ӧ' => 'ӧ', 'Ө' => 'ө', 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', 'Ӯ' => 'ӯ', 'Ӱ' => 'ӱ', 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', 'Ӷ' => 'ӷ', 'Ӹ' => 'ӹ', 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', 'Ӿ' => 'ӿ', 'Ԁ' => 'ԁ', 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', 'Ԇ' => 'ԇ', 'Ԉ' => 'ԉ', 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', 'Ԏ' => 'ԏ', 'Ԑ' => 'ԑ', 'Ԓ' => 'ԓ', + 'Ա' => 'ա', 'Բ' => 'բ', 'Գ' => 'գ', 'Դ' => 'դ', + 'Ե' => 'ե', 'Զ' => 'զ', 'Է' => 'է', 'Ը' => 'ը', + 'Թ' => 'թ', 'Ժ' => 'ժ', 'Ի' => 'ի', 'Լ' => 'լ', + 'Խ' => 'խ', 'Ծ' => 'ծ', 'Կ' => 'կ', 'Հ' => 'հ', + 'Ձ' => 'ձ', 'Ղ' => 'ղ', 'Ճ' => 'ճ', 'Մ' => 'մ', + 'Յ' => 'յ', 'Ն' => 'ն', 'Շ' => 'շ', 'Ո' => 'ո', + 'Չ' => 'չ', 'Պ' => 'պ', 'Ջ' => 'ջ', 'Ռ' => 'ռ', + 'Ս' => 'ս', 'Վ' => 'վ', 'Տ' => 'տ', 'Ր' => 'ր', + 'Ց' => 'ց', 'Ւ' => 'ւ', 'Փ' => 'փ', 'Ք' => 'ք', + 'Օ' => 'օ', 'Ֆ' => 'ֆ', 'և' => 'եւ', 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', 'Ⴂ' => 'ⴂ', 'Ⴃ' => 'ⴃ', 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', 'Ⴆ' => 'ⴆ', 'Ⴇ' => 'ⴇ', 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', 'Ⴊ' => 'ⴊ', 'Ⴋ' => 'ⴋ', 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', 'Ⴎ' => 'ⴎ', 'Ⴏ' => 'ⴏ', 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', 'Ⴒ' => 'ⴒ', 'Ⴓ' => 'ⴓ', 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', 'Ⴖ' => 'ⴖ', 'Ⴗ' => 'ⴗ', 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', 'Ⴚ' => 'ⴚ', 'Ⴛ' => 'ⴛ', 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', 'Ⴞ' => 'ⴞ', 'Ⴟ' => 'ⴟ', 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', 'Ⴢ' => 'ⴢ', 'Ⴣ' => 'ⴣ', 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', 'Ḁ' => 'ḁ', 'Ḃ' => 'ḃ', 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', 'Ḉ' => 'ḉ', 'Ḋ' => 'ḋ', 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', 'Ḑ' => 'ḑ', 'Ḓ' => 'ḓ', 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', 'Ḙ' => 'ḙ', 'Ḛ' => 'ḛ', 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', 'Ḡ' => 'ḡ', 'Ḣ' => 'ḣ', 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', 'Ḩ' => 'ḩ', 'Ḫ' => 'ḫ', 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', 'Ḱ' => 'ḱ', 'Ḳ' => 'ḳ', 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', 'Ḹ' => 'ḹ', 'Ḻ' => 'ḻ', 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', 'Ṁ' => 'ṁ', 'Ṃ' => 'ṃ', 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', 'Ṉ' => 'ṉ', 'Ṋ' => 'ṋ', 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', 'Ṑ' => 'ṑ', 'Ṓ' => 'ṓ', 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', 'Ṙ' => 'ṙ', 'Ṛ' => 'ṛ', 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', 'Ṡ' => 'ṡ', 'Ṣ' => 'ṣ', 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', 'Ṩ' => 'ṩ', 'Ṫ' => 'ṫ', 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', 'Ṱ' => 'ṱ', 'Ṳ' => 'ṳ', 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', 'Ṹ' => 'ṹ', 'Ṻ' => 'ṻ', 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', 'Ẁ' => 'ẁ', 'Ẃ' => 'ẃ', 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', 'Ẉ' => 'ẉ', 'Ẋ' => 'ẋ', 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', 'Ẑ' => 'ẑ', 'Ẓ' => 'ẓ', 'Ẕ' => 'ẕ', + 'ẖ' => 'ẖ', 'ẗ' => 'ẗ', 'ẘ' => 'ẘ', 'ẙ' => 'ẙ', + 'ẚ' => 'aʾ', 'ẛ' => 'ṡ', 'Ạ' => 'ạ', 'Ả' => 'ả', + 'Ấ' => 'ấ', 'Ầ' => 'ầ', 'Ẩ' => 'ẩ', 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', 'Ắ' => 'ắ', 'Ằ' => 'ằ', 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', 'Ặ' => 'ặ', 'Ẹ' => 'ẹ', 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', 'Ế' => 'ế', 'Ề' => 'ề', 'Ể' => 'ể', + 'Ễ' => 'ễ', 'Ệ' => 'ệ', 'Ỉ' => 'ỉ', 'Ị' => 'ị', + 'Ọ' => 'ọ', 'Ỏ' => 'ỏ', 'Ố' => 'ố', 'Ồ' => 'ồ', + 'Ổ' => 'ổ', 'Ỗ' => 'ỗ', 'Ộ' => 'ộ', 'Ớ' => 'ớ', + 'Ờ' => 'ờ', 'Ở' => 'ở', 'Ỡ' => 'ỡ', 'Ợ' => 'ợ', + 'Ụ' => 'ụ', 'Ủ' => 'ủ', 'Ứ' => 'ứ', 'Ừ' => 'ừ', + 'Ử' => 'ử', 'Ữ' => 'ữ', 'Ự' => 'ự', 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', 'Ỷ' => 'ỷ', 'Ỹ' => 'ỹ', 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', 'Ἂ' => 'ἂ', 'Ἃ' => 'ἃ', 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', 'Ἆ' => 'ἆ', 'Ἇ' => 'ἇ', 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', 'Ἒ' => 'ἒ', 'Ἓ' => 'ἓ', 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', 'Ἠ' => 'ἠ', 'Ἡ' => 'ἡ', 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', 'Ἤ' => 'ἤ', 'Ἥ' => 'ἥ', 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', 'Ἰ' => 'ἰ', 'Ἱ' => 'ἱ', 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', 'Ἴ' => 'ἴ', 'Ἵ' => 'ἵ', 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', 'Ὀ' => 'ὀ', 'Ὁ' => 'ὁ', 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', 'Ὄ' => 'ὄ', 'Ὅ' => 'ὅ', 'ὐ' => 'ὐ', + 'ὒ' => 'ὒ', 'ὔ' => 'ὔ', 'ὖ' => 'ὖ', 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', 'Ὕ' => 'ὕ', 'Ὗ' => 'ὗ', 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', 'Ὢ' => 'ὢ', 'Ὣ' => 'ὣ', 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', 'Ὦ' => 'ὦ', 'Ὧ' => 'ὧ', 'ᾀ' => 'ἀι', + 'ᾁ' => 'ἁι', 'ᾂ' => 'ἂι', 'ᾃ' => 'ἃι', 'ᾄ' => 'ἄι', + 'ᾅ' => 'ἅι', 'ᾆ' => 'ἆι', 'ᾇ' => 'ἇι', 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', 'ᾊ' => 'ᾂ', 'ᾋ' => 'ᾃ', 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', 'ᾎ' => 'ᾆ', 'ᾏ' => 'ᾇ', 'ᾐ' => 'ἠι', + 'ᾑ' => 'ἡι', 'ᾒ' => 'ἢι', 'ᾓ' => 'ἣι', 'ᾔ' => 'ἤι', + 'ᾕ' => 'ἥι', 'ᾖ' => 'ἦι', 'ᾗ' => 'ἧι', 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', 'ᾚ' => 'ᾒ', 'ᾛ' => 'ᾓ', 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', 'ᾞ' => 'ᾖ', 'ᾟ' => 'ᾗ', 'ᾠ' => 'ὠι', + 'ᾡ' => 'ὡι', 'ᾢ' => 'ὢι', 'ᾣ' => 'ὣι', 'ᾤ' => 'ὤι', + 'ᾥ' => 'ὥι', 'ᾦ' => 'ὦι', 'ᾧ' => 'ὧι', 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', 'ᾪ' => 'ᾢ', 'ᾫ' => 'ᾣ', 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', 'ᾮ' => 'ᾦ', 'ᾯ' => 'ᾧ', 'ᾲ' => 'ὰι', + 'ᾳ' => 'αι', 'ᾴ' => 'άι', 'ᾶ' => 'ᾶ', 'ᾷ' => 'ᾶι', + 'Ᾰ' => 'ᾰ', 'Ᾱ' => 'ᾱ', 'Ὰ' => 'ὰ', 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', 'ι' => 'ι', 'ῂ' => 'ὴι', 'ῃ' => 'ηι', + 'ῄ' => 'ήι', 'ῆ' => 'ῆ', 'ῇ' => 'ῆι', 'Ὲ' => 'ὲ', + 'Έ' => 'έ', 'Ὴ' => 'ὴ', 'Ή' => 'ή', 'ῌ' => 'ῃ', + 'ῒ' => 'ῒ', 'ΐ' => 'ΐ', 'ῖ' => 'ῖ', 'ῗ' => 'ῗ', + 'Ῐ' => 'ῐ', 'Ῑ' => 'ῑ', 'Ὶ' => 'ὶ', 'Ί' => 'ί', + 'ῢ' => 'ῢ', 'ΰ' => 'ΰ', 'ῤ' => 'ῤ', 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', 'Ῠ' => 'ῠ', 'Ῡ' => 'ῡ', 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', 'Ῥ' => 'ῥ', 'ῲ' => 'ὼι', 'ῳ' => 'ωι', + 'ῴ' => 'ώι', 'ῶ' => 'ῶ', 'ῷ' => 'ῶι', 'Ὸ' => 'ὸ', + 'Ό' => 'ό', 'Ὼ' => 'ὼ', 'Ώ' => 'ώ', 'ῼ' => 'ῳ', + 'Ω' => 'ω', 'K' => 'k', 'Å' => 'å', 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', 'Ⅱ' => 'ⅱ', 'Ⅲ' => 'ⅲ', 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', 'Ⅵ' => 'ⅵ', 'Ⅶ' => 'ⅶ', 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', 'Ⅹ' => 'ⅹ', 'Ⅺ' => 'ⅺ', 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', 'Ⅽ' => 'ⅽ', 'Ⅾ' => 'ⅾ', 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', 'Ⓐ' => 'ⓐ', 'Ⓑ' => 'ⓑ', 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', 'Ⓔ' => 'ⓔ', 'Ⓕ' => 'ⓕ', 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', 'Ⓘ' => 'ⓘ', 'Ⓙ' => 'ⓙ', 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', 'Ⓜ' => 'ⓜ', 'Ⓝ' => 'ⓝ', 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', 'Ⓠ' => 'ⓠ', 'Ⓡ' => 'ⓡ', 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', 'Ⓤ' => 'ⓤ', 'Ⓥ' => 'ⓥ', 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', 'Ⓨ' => 'ⓨ', 'Ⓩ' => 'ⓩ', 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', 'Ⰲ' => 'ⰲ', 'Ⰳ' => 'ⰳ', 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', 'Ⰶ' => 'ⰶ', 'Ⰷ' => 'ⰷ', 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', 'Ⰺ' => 'ⰺ', 'Ⰻ' => 'ⰻ', 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', 'Ⰾ' => 'ⰾ', 'Ⰿ' => 'ⰿ', 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', 'Ⱂ' => 'ⱂ', 'Ⱃ' => 'ⱃ', 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', 'Ⱆ' => 'ⱆ', 'Ⱇ' => 'ⱇ', 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', 'Ⱊ' => 'ⱊ', 'Ⱋ' => 'ⱋ', 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', 'Ⱎ' => 'ⱎ', 'Ⱏ' => 'ⱏ', 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', 'Ⱒ' => 'ⱒ', 'Ⱓ' => 'ⱓ', 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', 'Ⱖ' => 'ⱖ', 'Ⱗ' => 'ⱗ', 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', 'Ⱚ' => 'ⱚ', 'Ⱛ' => 'ⱛ', 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', 'Ⱞ' => 'ⱞ', 'Ⱡ' => 'ⱡ', 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', 'Ɽ' => 'ɽ', 'Ⱨ' => 'ⱨ', 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', 'Ⱶ' => 'ⱶ', 'Ⲁ' => 'ⲁ', 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', 'Ⲇ' => 'ⲇ', 'Ⲉ' => 'ⲉ', 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', 'Ⲏ' => 'ⲏ', 'Ⲑ' => 'ⲑ', 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', 'Ⲗ' => 'ⲗ', 'Ⲙ' => 'ⲙ', 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', 'Ⲟ' => 'ⲟ', 'Ⲡ' => 'ⲡ', 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', 'Ⲧ' => 'ⲧ', 'Ⲩ' => 'ⲩ', 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', 'Ⲯ' => 'ⲯ', 'Ⲱ' => 'ⲱ', 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', 'Ⲷ' => 'ⲷ', 'Ⲹ' => 'ⲹ', 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', 'Ⲿ' => 'ⲿ', 'Ⳁ' => 'ⳁ', 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', 'Ⳇ' => 'ⳇ', 'Ⳉ' => 'ⳉ', 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', 'Ⳏ' => 'ⳏ', 'Ⳑ' => 'ⳑ', 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', 'Ⳗ' => 'ⳗ', 'Ⳙ' => 'ⳙ', 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', 'Ⳟ' => 'ⳟ', 'Ⳡ' => 'ⳡ', 'Ⳣ' => 'ⳣ', + 'ff' => 'ff', 'fi' => 'fi', 'fl' => 'fl', 'ffi' => 'ffi', + 'ffl' => 'ffl', 'ſt' => 'st', 'st' => 'st', 'ﬓ' => 'մն', + 'ﬔ' => 'մե', 'ﬕ' => 'մի', 'ﬖ' => 'վն', 'ﬗ' => 'մխ', + 'A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', + 'E' => 'e', 'F' => 'f', 'G' => 'g', 'H' => 'h', + 'I' => 'i', 'J' => 'j', 'K' => 'k', 'L' => 'l', + 'M' => 'm', 'N' => 'n', 'O' => 'o', 'P' => 'p', + 'Q' => 'q', 'R' => 'r', 'S' => 's', 'T' => 't', + 'U' => 'u', 'V' => 'v', 'W' => 'w', 'X' => 'x', + 'Y' => 'y', 'Z' => 'z', '𐐀' => '𐐨', '𐐁' => '𐐩', + '𐐂' => '𐐪', '𐐃' => '𐐫', '𐐄' => '𐐬', '𐐅' => '𐐭', + '𐐆' => '𐐮', '𐐇' => '𐐯', '𐐈' => '𐐰', '𐐉' => '𐐱', + '𐐊' => '𐐲', '𐐋' => '𐐳', '𐐌' => '𐐴', '𐐍' => '𐐵', + '𐐎' => '𐐶', '𐐏' => '𐐷', '𐐐' => '𐐸', '𐐑' => '𐐹', + '𐐒' => '𐐺', '𐐓' => '𐐻', '𐐔' => '𐐼', '𐐕' => '𐐽', + '𐐖' => '𐐾', '𐐗' => '𐐿', '𐐘' => '𐑀', '𐐙' => '𐑁', + '𐐚' => '𐑂', '𐐛' => '𐑃', '𐐜' => '𐑄', '𐐝' => '𐑅', + '𐐞' => '𐑆', '𐐟' => '𐑇', '𐐠' => '𐑈', '𐐡' => '𐑉', + '𐐢' => '𐑊', '𐐣' => '𐑋', '𐐤' => '𐑌', '𐐥' => '𐑍', + '𐑎' => '𐐦', '𐑏' => '𐐧', + ); + + return strtr($string, $case_folding); +} + +// Convert the given UTF-8 string to uppercase. +function utf8_strtoupper($string) +{ + static $case_folding = array( + 'a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D', + 'e' => 'E', 'f' => 'F', 'g' => 'G', 'h' => 'H', + 'i' => 'I', 'j' => 'J', 'k' => 'K', 'l' => 'L', + 'm' => 'M', 'n' => 'N', 'o' => 'O', 'p' => 'P', + 'q' => 'Q', 'r' => 'R', 's' => 'S', 't' => 'T', + 'u' => 'U', 'v' => 'V', 'w' => 'W', 'x' => 'X', + 'y' => 'Y', 'z' => 'Z', 'μ' => 'µ', 'à' => 'À', + 'á' => 'Á', 'â' => 'Â', 'ã' => 'Ã', 'ä' => 'Ä', + 'å' => 'Å', 'æ' => 'Æ', 'ç' => 'Ç', 'è' => 'È', + 'é' => 'É', 'ê' => 'Ê', 'ë' => 'Ë', 'ì' => 'Ì', + 'í' => 'Í', 'î' => 'Î', 'ï' => 'Ï', 'ð' => 'Ð', + 'ñ' => 'Ñ', 'ò' => 'Ò', 'ó' => 'Ó', 'ô' => 'Ô', + 'õ' => 'Õ', 'ö' => 'Ö', 'ø' => 'Ø', 'ù' => 'Ù', + 'ú' => 'Ú', 'û' => 'Û', 'ü' => 'Ü', 'ý' => 'Ý', + 'þ' => 'Þ', 'ss' => 'ß', 'ā' => 'Ā', 'ă' => 'Ă', + 'ą' => 'Ą', 'ć' => 'Ć', 'ĉ' => 'Ĉ', 'ċ' => 'Ċ', + 'č' => 'Č', 'ď' => 'Ď', 'đ' => 'Đ', 'ē' => 'Ē', + 'ĕ' => 'Ĕ', 'ė' => 'Ė', 'ę' => 'Ę', 'ě' => 'Ě', + 'ĝ' => 'Ĝ', 'ğ' => 'Ğ', 'ġ' => 'Ġ', 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', 'ħ' => 'Ħ', 'ĩ' => 'Ĩ', 'ī' => 'Ī', + 'ĭ' => 'Ĭ', 'į' => 'Į', 'i̇' => 'İ', 'ij' => 'IJ', + 'ĵ' => 'Ĵ', 'ķ' => 'Ķ', 'ĺ' => 'Ĺ', 'ļ' => 'Ļ', + 'ľ' => 'Ľ', 'ŀ' => 'Ŀ', 'ł' => 'Ł', 'ń' => 'Ń', + 'ņ' => 'Ņ', 'ň' => 'Ň', 'ʼn' => 'ʼn', 'ŋ' => 'Ŋ', + 'ō' => 'Ō', 'ŏ' => 'Ŏ', 'ő' => 'Ő', 'œ' => 'Œ', + 'ŕ' => 'Ŕ', 'ŗ' => 'Ŗ', 'ř' => 'Ř', 'ś' => 'Ś', + 'ŝ' => 'Ŝ', 'ş' => 'Ş', 'š' => 'Š', 'ţ' => 'Ţ', + 'ť' => 'Ť', 'ŧ' => 'Ŧ', 'ũ' => 'Ũ', 'ū' => 'Ū', + 'ŭ' => 'Ŭ', 'ů' => 'Ů', 'ű' => 'Ű', 'ų' => 'Ų', + 'ŵ' => 'Ŵ', 'ŷ' => 'Ŷ', 'ÿ' => 'Ÿ', 'ź' => 'Ź', + 'ż' => 'Ż', 'ž' => 'Ž', 's' => 'ſ', 'ɓ' => 'Ɓ', + 'ƃ' => 'Ƃ', 'ƅ' => 'Ƅ', 'ɔ' => 'Ɔ', 'ƈ' => 'Ƈ', + 'ɖ' => 'Ɖ', 'ɗ' => 'Ɗ', 'ƌ' => 'Ƌ', 'ǝ' => 'Ǝ', + 'ə' => 'Ə', 'ɛ' => 'Ɛ', 'ƒ' => 'Ƒ', 'ɠ' => 'Ɠ', + 'ɣ' => 'Ɣ', 'ɩ' => 'Ɩ', 'ɨ' => 'Ɨ', 'ƙ' => 'Ƙ', + 'ɯ' => 'Ɯ', 'ɲ' => 'Ɲ', 'ɵ' => 'Ɵ', 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', 'ƥ' => 'Ƥ', 'ʀ' => 'Ʀ', 'ƨ' => 'Ƨ', + 'ʃ' => 'Ʃ', 'ƭ' => 'Ƭ', 'ʈ' => 'Ʈ', 'ư' => 'Ư', + 'ʊ' => 'Ʊ', 'ʋ' => 'Ʋ', 'ƴ' => 'Ƴ', 'ƶ' => 'Ƶ', + 'ʒ' => 'Ʒ', 'ƹ' => 'Ƹ', 'ƽ' => 'Ƽ', 'dž' => 'DŽ', + 'dž' => 'Dž', 'lj' => 'LJ', 'lj' => 'Lj', 'nj' => 'NJ', + 'nj' => 'Nj', 'ǎ' => 'Ǎ', 'ǐ' => 'Ǐ', 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', 'ǖ' => 'Ǖ', 'ǘ' => 'Ǘ', 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', 'ǟ' => 'Ǟ', 'ǡ' => 'Ǡ', 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', 'ǧ' => 'Ǧ', 'ǩ' => 'Ǩ', 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', 'ǯ' => 'Ǯ', 'ǰ' => 'ǰ', 'dz' => 'DZ', + 'dz' => 'Dz', 'ǵ' => 'Ǵ', 'ƕ' => 'Ƕ', 'ƿ' => 'Ƿ', + 'ǹ' => 'Ǹ', 'ǻ' => 'Ǻ', 'ǽ' => 'Ǽ', 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', 'ȃ' => 'Ȃ', 'ȅ' => 'Ȅ', 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', 'ȋ' => 'Ȋ', 'ȍ' => 'Ȍ', 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', 'ȓ' => 'Ȓ', 'ȕ' => 'Ȕ', 'ȗ' => 'Ȗ', + 'ș' => 'Ș', 'ț' => 'Ț', 'ȝ' => 'Ȝ', 'ȟ' => 'Ȟ', + 'ƞ' => 'Ƞ', 'ȣ' => 'Ȣ', 'ȥ' => 'Ȥ', 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', 'ȫ' => 'Ȫ', 'ȭ' => 'Ȭ', 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', 'ȳ' => 'Ȳ', 'ⱥ' => 'Ⱥ', 'ȼ' => 'Ȼ', + 'ƚ' => 'Ƚ', 'ⱦ' => 'Ⱦ', 'ɂ' => 'Ɂ', 'ƀ' => 'Ƀ', + 'ʉ' => 'Ʉ', 'ʌ' => 'Ʌ', 'ɇ' => 'Ɇ', 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', 'ɍ' => 'Ɍ', 'ɏ' => 'Ɏ', 'ι' => 'ͅ', + 'ά' => 'Ά', 'έ' => 'Έ', 'ή' => 'Ή', 'ί' => 'Ί', + 'ό' => 'Ό', 'ύ' => 'Ύ', 'ώ' => 'Ώ', 'ΐ' => 'ΐ', + 'α' => 'Α', 'β' => 'Β', 'γ' => 'Γ', 'δ' => 'Δ', + 'ε' => 'Ε', 'ζ' => 'Ζ', 'η' => 'Η', 'θ' => 'Θ', + 'ι' => 'Ι', 'κ' => 'Κ', 'λ' => 'Λ', 'μ' => 'Μ', + 'ν' => 'Ν', 'ξ' => 'Ξ', 'ο' => 'Ο', 'π' => 'Π', + 'ρ' => 'Ρ', 'σ' => 'Σ', 'τ' => 'Τ', 'υ' => 'Υ', + 'φ' => 'Φ', 'χ' => 'Χ', 'ψ' => 'Ψ', 'ω' => 'Ω', + 'ϊ' => 'Ϊ', 'ϋ' => 'Ϋ', 'ΰ' => 'ΰ', 'σ' => 'ς', + 'β' => 'ϐ', 'θ' => 'ϑ', 'φ' => 'ϕ', 'π' => 'ϖ', + 'ϙ' => 'Ϙ', 'ϛ' => 'Ϛ', 'ϝ' => 'Ϝ', 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', 'ϣ' => 'Ϣ', 'ϥ' => 'Ϥ', 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', 'ϫ' => 'Ϫ', 'ϭ' => 'Ϭ', 'ϯ' => 'Ϯ', + 'κ' => 'ϰ', 'ρ' => 'ϱ', 'θ' => 'ϴ', 'ε' => 'ϵ', + 'ϸ' => 'Ϸ', 'ϲ' => 'Ϲ', 'ϻ' => 'Ϻ', 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', 'ͽ' => 'Ͽ', 'ѐ' => 'Ѐ', 'ё' => 'Ё', + 'ђ' => 'Ђ', 'ѓ' => 'Ѓ', 'є' => 'Є', 'ѕ' => 'Ѕ', + 'і' => 'І', 'ї' => 'Ї', 'ј' => 'Ј', 'љ' => 'Љ', + 'њ' => 'Њ', 'ћ' => 'Ћ', 'ќ' => 'Ќ', 'ѝ' => 'Ѝ', + 'ў' => 'Ў', 'џ' => 'Џ', 'а' => 'А', 'б' => 'Б', + 'в' => 'В', 'г' => 'Г', 'д' => 'Д', 'е' => 'Е', + 'ж' => 'Ж', 'з' => 'З', 'и' => 'И', 'й' => 'Й', + 'к' => 'К', 'л' => 'Л', 'м' => 'М', 'н' => 'Н', + 'о' => 'О', 'п' => 'П', 'р' => 'Р', 'с' => 'С', + 'т' => 'Т', 'у' => 'У', 'ф' => 'Ф', 'х' => 'Х', + 'ц' => 'Ц', 'ч' => 'Ч', 'ш' => 'Ш', 'щ' => 'Щ', + 'ъ' => 'Ъ', 'ы' => 'Ы', 'ь' => 'Ь', 'э' => 'Э', + 'ю' => 'Ю', 'я' => 'Я', 'ѡ' => 'Ѡ', 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', 'ѧ' => 'Ѧ', 'ѩ' => 'Ѩ', 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', 'ѯ' => 'Ѯ', 'ѱ' => 'Ѱ', 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', 'ѷ' => 'Ѷ', 'ѹ' => 'Ѹ', 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', 'ѿ' => 'Ѿ', 'ҁ' => 'Ҁ', 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', 'ҏ' => 'Ҏ', 'ґ' => 'Ґ', 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', 'җ' => 'Җ', 'ҙ' => 'Ҙ', 'қ' => 'Қ', + 'ҝ' => 'Ҝ', 'ҟ' => 'Ҟ', 'ҡ' => 'Ҡ', 'ң' => 'Ң', + 'ҥ' => 'Ҥ', 'ҧ' => 'Ҧ', 'ҩ' => 'Ҩ', 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', 'ү' => 'Ү', 'ұ' => 'Ұ', 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', 'ҷ' => 'Ҷ', 'ҹ' => 'Ҹ', 'һ' => 'Һ', + 'ҽ' => 'Ҽ', 'ҿ' => 'Ҿ', 'ӏ' => 'Ӏ', 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', 'ӆ' => 'Ӆ', 'ӈ' => 'Ӈ', 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', 'ӎ' => 'Ӎ', 'ӑ' => 'Ӑ', 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', 'ӗ' => 'Ӗ', 'ә' => 'Ә', 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', 'ӟ' => 'Ӟ', 'ӡ' => 'Ӡ', 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', 'ӧ' => 'Ӧ', 'ө' => 'Ө', 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', 'ӯ' => 'Ӯ', 'ӱ' => 'Ӱ', 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', 'ӷ' => 'Ӷ', 'ӹ' => 'Ӹ', 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', 'ӿ' => 'Ӿ', 'ԁ' => 'Ԁ', 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', 'ԇ' => 'Ԇ', 'ԉ' => 'Ԉ', 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', 'ԏ' => 'Ԏ', 'ԑ' => 'Ԑ', 'ԓ' => 'Ԓ', + 'ա' => 'Ա', 'բ' => 'Բ', 'գ' => 'Գ', 'դ' => 'Դ', + 'ե' => 'Ե', 'զ' => 'Զ', 'է' => 'Է', 'ը' => 'Ը', + 'թ' => 'Թ', 'ժ' => 'Ժ', 'ի' => 'Ի', 'լ' => 'Լ', + 'խ' => 'Խ', 'ծ' => 'Ծ', 'կ' => 'Կ', 'հ' => 'Հ', + 'ձ' => 'Ձ', 'ղ' => 'Ղ', 'ճ' => 'Ճ', 'մ' => 'Մ', + 'յ' => 'Յ', 'ն' => 'Ն', 'շ' => 'Շ', 'ո' => 'Ո', + 'չ' => 'Չ', 'պ' => 'Պ', 'ջ' => 'Ջ', 'ռ' => 'Ռ', + 'ս' => 'Ս', 'վ' => 'Վ', 'տ' => 'Տ', 'ր' => 'Ր', + 'ց' => 'Ց', 'ւ' => 'Ւ', 'փ' => 'Փ', 'ք' => 'Ք', + 'օ' => 'Օ', 'ֆ' => 'Ֆ', 'եւ' => 'և', 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', 'ⴂ' => 'Ⴂ', 'ⴃ' => 'Ⴃ', 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', 'ⴆ' => 'Ⴆ', 'ⴇ' => 'Ⴇ', 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', 'ⴊ' => 'Ⴊ', 'ⴋ' => 'Ⴋ', 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', 'ⴎ' => 'Ⴎ', 'ⴏ' => 'Ⴏ', 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', 'ⴒ' => 'Ⴒ', 'ⴓ' => 'Ⴓ', 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', 'ⴖ' => 'Ⴖ', 'ⴗ' => 'Ⴗ', 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', 'ⴚ' => 'Ⴚ', 'ⴛ' => 'Ⴛ', 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', 'ⴞ' => 'Ⴞ', 'ⴟ' => 'Ⴟ', 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', 'ⴢ' => 'Ⴢ', 'ⴣ' => 'Ⴣ', 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', 'ḁ' => 'Ḁ', 'ḃ' => 'Ḃ', 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', 'ḉ' => 'Ḉ', 'ḋ' => 'Ḋ', 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', 'ḑ' => 'Ḑ', 'ḓ' => 'Ḓ', 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', 'ḙ' => 'Ḙ', 'ḛ' => 'Ḛ', 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', 'ḡ' => 'Ḡ', 'ḣ' => 'Ḣ', 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', 'ḩ' => 'Ḩ', 'ḫ' => 'Ḫ', 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', 'ḱ' => 'Ḱ', 'ḳ' => 'Ḳ', 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', 'ḹ' => 'Ḹ', 'ḻ' => 'Ḻ', 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', 'ṁ' => 'Ṁ', 'ṃ' => 'Ṃ', 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', 'ṉ' => 'Ṉ', 'ṋ' => 'Ṋ', 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', 'ṑ' => 'Ṑ', 'ṓ' => 'Ṓ', 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', 'ṙ' => 'Ṙ', 'ṛ' => 'Ṛ', 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', 'ṡ' => 'Ṡ', 'ṣ' => 'Ṣ', 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', 'ṩ' => 'Ṩ', 'ṫ' => 'Ṫ', 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', 'ṱ' => 'Ṱ', 'ṳ' => 'Ṳ', 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', 'ṹ' => 'Ṹ', 'ṻ' => 'Ṻ', 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', 'ẁ' => 'Ẁ', 'ẃ' => 'Ẃ', 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', 'ẉ' => 'Ẉ', 'ẋ' => 'Ẋ', 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', 'ẑ' => 'Ẑ', 'ẓ' => 'Ẓ', 'ẕ' => 'Ẕ', + 'ẖ' => 'ẖ', 'ẗ' => 'ẗ', 'ẘ' => 'ẘ', 'ẙ' => 'ẙ', + 'aʾ' => 'ẚ', 'ṡ' => 'ẛ', 'ạ' => 'Ạ', 'ả' => 'Ả', + 'ấ' => 'Ấ', 'ầ' => 'Ầ', 'ẩ' => 'Ẩ', 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', 'ắ' => 'Ắ', 'ằ' => 'Ằ', 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', 'ặ' => 'Ặ', 'ẹ' => 'Ẹ', 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', 'ế' => 'Ế', 'ề' => 'Ề', 'ể' => 'Ể', + 'ễ' => 'Ễ', 'ệ' => 'Ệ', 'ỉ' => 'Ỉ', 'ị' => 'Ị', + 'ọ' => 'Ọ', 'ỏ' => 'Ỏ', 'ố' => 'Ố', 'ồ' => 'Ồ', + 'ổ' => 'Ổ', 'ỗ' => 'Ỗ', 'ộ' => 'Ộ', 'ớ' => 'Ớ', + 'ờ' => 'Ờ', 'ở' => 'Ở', 'ỡ' => 'Ỡ', 'ợ' => 'Ợ', + 'ụ' => 'Ụ', 'ủ' => 'Ủ', 'ứ' => 'Ứ', 'ừ' => 'Ừ', + 'ử' => 'Ử', 'ữ' => 'Ữ', 'ự' => 'Ự', 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', 'ỷ' => 'Ỷ', 'ỹ' => 'Ỹ', 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', 'ἂ' => 'Ἂ', 'ἃ' => 'Ἃ', 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', 'ἆ' => 'Ἆ', 'ἇ' => 'Ἇ', 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', 'ἒ' => 'Ἒ', 'ἓ' => 'Ἓ', 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', 'ἠ' => 'Ἠ', 'ἡ' => 'Ἡ', 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', 'ἤ' => 'Ἤ', 'ἥ' => 'Ἥ', 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', 'ἰ' => 'Ἰ', 'ἱ' => 'Ἱ', 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', 'ἴ' => 'Ἴ', 'ἵ' => 'Ἵ', 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', 'ὀ' => 'Ὀ', 'ὁ' => 'Ὁ', 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', 'ὄ' => 'Ὄ', 'ὅ' => 'Ὅ', 'ὐ' => 'ὐ', + 'ὒ' => 'ὒ', 'ὔ' => 'ὔ', 'ὖ' => 'ὖ', 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', 'ὕ' => 'Ὕ', 'ὗ' => 'Ὗ', 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', 'ὢ' => 'Ὢ', 'ὣ' => 'Ὣ', 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', 'ὦ' => 'Ὦ', 'ὧ' => 'Ὧ', 'ἀι' => 'ᾀ', + 'ἁι' => 'ᾁ', 'ἂι' => 'ᾂ', 'ἃι' => 'ᾃ', 'ἄι' => 'ᾄ', + 'ἅι' => 'ᾅ', 'ἆι' => 'ᾆ', 'ἇι' => 'ᾇ', 'ᾀ' => 'ᾈ', + 'ᾁ' => 'ᾉ', 'ᾂ' => 'ᾊ', 'ᾃ' => 'ᾋ', 'ᾄ' => 'ᾌ', + 'ᾅ' => 'ᾍ', 'ᾆ' => 'ᾎ', 'ᾇ' => 'ᾏ', 'ἠι' => 'ᾐ', + 'ἡι' => 'ᾑ', 'ἢι' => 'ᾒ', 'ἣι' => 'ᾓ', 'ἤι' => 'ᾔ', + 'ἥι' => 'ᾕ', 'ἦι' => 'ᾖ', 'ἧι' => 'ᾗ', 'ᾐ' => 'ᾘ', + 'ᾑ' => 'ᾙ', 'ᾒ' => 'ᾚ', 'ᾓ' => 'ᾛ', 'ᾔ' => 'ᾜ', + 'ᾕ' => 'ᾝ', 'ᾖ' => 'ᾞ', 'ᾗ' => 'ᾟ', 'ὠι' => 'ᾠ', + 'ὡι' => 'ᾡ', 'ὢι' => 'ᾢ', 'ὣι' => 'ᾣ', 'ὤι' => 'ᾤ', + 'ὥι' => 'ᾥ', 'ὦι' => 'ᾦ', 'ὧι' => 'ᾧ', 'ᾠ' => 'ᾨ', + 'ᾡ' => 'ᾩ', 'ᾢ' => 'ᾪ', 'ᾣ' => 'ᾫ', 'ᾤ' => 'ᾬ', + 'ᾥ' => 'ᾭ', 'ᾦ' => 'ᾮ', 'ᾧ' => 'ᾯ', 'ὰι' => 'ᾲ', + 'αι' => 'ᾳ', 'άι' => 'ᾴ', 'ᾶ' => 'ᾶ', 'ᾶι' => 'ᾷ', + 'ᾰ' => 'Ᾰ', 'ᾱ' => 'Ᾱ', 'ὰ' => 'Ὰ', 'ά' => 'Ά', + 'ᾳ' => 'ᾼ', 'ι' => 'ι', 'ὴι' => 'ῂ', 'ηι' => 'ῃ', + 'ήι' => 'ῄ', 'ῆ' => 'ῆ', 'ῆι' => 'ῇ', 'ὲ' => 'Ὲ', + 'έ' => 'Έ', 'ὴ' => 'Ὴ', 'ή' => 'Ή', 'ῃ' => 'ῌ', + 'ῒ' => 'ῒ', 'ΐ' => 'ΐ', 'ῖ' => 'ῖ', 'ῗ' => 'ῗ', + 'ῐ' => 'Ῐ', 'ῑ' => 'Ῑ', 'ὶ' => 'Ὶ', 'ί' => 'Ί', + 'ῢ' => 'ῢ', 'ΰ' => 'ΰ', 'ῤ' => 'ῤ', 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', 'ῠ' => 'Ῠ', 'ῡ' => 'Ῡ', 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', 'ῥ' => 'Ῥ', 'ὼι' => 'ῲ', 'ωι' => 'ῳ', + 'ώι' => 'ῴ', 'ῶ' => 'ῶ', 'ῶι' => 'ῷ', 'ὸ' => 'Ὸ', + 'ό' => 'Ό', 'ὼ' => 'Ὼ', 'ώ' => 'Ώ', 'ῳ' => 'ῼ', + 'ω' => 'Ω', 'k' => 'K', 'å' => 'Å', 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', 'ⅱ' => 'Ⅱ', 'ⅲ' => 'Ⅲ', 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', 'ⅵ' => 'Ⅵ', 'ⅶ' => 'Ⅶ', 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', 'ⅹ' => 'Ⅹ', 'ⅺ' => 'Ⅺ', 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', 'ⅽ' => 'Ⅽ', 'ⅾ' => 'Ⅾ', 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', 'ⓐ' => 'Ⓐ', 'ⓑ' => 'Ⓑ', 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', 'ⓔ' => 'Ⓔ', 'ⓕ' => 'Ⓕ', 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', 'ⓘ' => 'Ⓘ', 'ⓙ' => 'Ⓙ', 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', 'ⓜ' => 'Ⓜ', 'ⓝ' => 'Ⓝ', 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', 'ⓠ' => 'Ⓠ', 'ⓡ' => 'Ⓡ', 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', 'ⓤ' => 'Ⓤ', 'ⓥ' => 'Ⓥ', 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', 'ⓨ' => 'Ⓨ', 'ⓩ' => 'Ⓩ', 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', 'ⰲ' => 'Ⰲ', 'ⰳ' => 'Ⰳ', 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', 'ⰶ' => 'Ⰶ', 'ⰷ' => 'Ⰷ', 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', 'ⰺ' => 'Ⰺ', 'ⰻ' => 'Ⰻ', 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', 'ⰾ' => 'Ⰾ', 'ⰿ' => 'Ⰿ', 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', 'ⱂ' => 'Ⱂ', 'ⱃ' => 'Ⱃ', 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', 'ⱆ' => 'Ⱆ', 'ⱇ' => 'Ⱇ', 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', 'ⱊ' => 'Ⱊ', 'ⱋ' => 'Ⱋ', 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', 'ⱎ' => 'Ⱎ', 'ⱏ' => 'Ⱏ', 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', 'ⱒ' => 'Ⱒ', 'ⱓ' => 'Ⱓ', 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', 'ⱖ' => 'Ⱖ', 'ⱗ' => 'Ⱗ', 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', 'ⱚ' => 'Ⱚ', 'ⱛ' => 'Ⱛ', 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', 'ⱞ' => 'Ⱞ', 'ⱡ' => 'Ⱡ', 'ɫ' => 'Ɫ', + 'ᵽ' => 'Ᵽ', 'ɽ' => 'Ɽ', 'ⱨ' => 'Ⱨ', 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', 'ⱶ' => 'Ⱶ', 'ⲁ' => 'Ⲁ', 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', 'ⲇ' => 'Ⲇ', 'ⲉ' => 'Ⲉ', 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', 'ⲏ' => 'Ⲏ', 'ⲑ' => 'Ⲑ', 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', 'ⲗ' => 'Ⲗ', 'ⲙ' => 'Ⲙ', 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', 'ⲟ' => 'Ⲟ', 'ⲡ' => 'Ⲡ', 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', 'ⲧ' => 'Ⲧ', 'ⲩ' => 'Ⲩ', 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', 'ⲯ' => 'Ⲯ', 'ⲱ' => 'Ⲱ', 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', 'ⲷ' => 'Ⲷ', 'ⲹ' => 'Ⲹ', 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', 'ⲿ' => 'Ⲿ', 'ⳁ' => 'Ⳁ', 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', 'ⳇ' => 'Ⳇ', 'ⳉ' => 'Ⳉ', 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', 'ⳏ' => 'Ⳏ', 'ⳑ' => 'Ⳑ', 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', 'ⳗ' => 'Ⳗ', 'ⳙ' => 'Ⳙ', 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', 'ⳟ' => 'Ⳟ', 'ⳡ' => 'Ⳡ', 'ⳣ' => 'Ⳣ', + 'ff' => 'ff', 'fi' => 'fi', 'fl' => 'fl', 'ffi' => 'ffi', + 'ffl' => 'ffl', 'st' => 'ſt', 'st' => 'st', 'մն' => 'ﬓ', + 'մե' => 'ﬔ', 'մի' => 'ﬕ', 'վն' => 'ﬖ', 'մխ' => 'ﬗ', + 'a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D', + 'e' => 'E', 'f' => 'F', 'g' => 'G', 'h' => 'H', + 'i' => 'I', 'j' => 'J', 'k' => 'K', 'l' => 'L', + 'm' => 'M', 'n' => 'N', 'o' => 'O', 'p' => 'P', + 'q' => 'Q', 'r' => 'R', 's' => 'S', 't' => 'T', + 'u' => 'U', 'v' => 'V', 'w' => 'W', 'x' => 'X', + 'y' => 'Y', 'z' => 'Z', '𐐨' => '𐐀', '𐐩' => '𐐁', + '𐐪' => '𐐂', '𐐫' => '𐐃', '𐐬' => '𐐄', '𐐭' => '𐐅', + '𐐮' => '𐐆', '𐐯' => '𐐇', '𐐰' => '𐐈', '𐐱' => '𐐉', + '𐐲' => '𐐊', '𐐳' => '𐐋', '𐐴' => '𐐌', '𐐵' => '𐐍', + '𐐶' => '𐐎', '𐐷' => '𐐏', '𐐸' => '𐐐', '𐐹' => '𐐑', + '𐐺' => '𐐒', '𐐻' => '𐐓', '𐐼' => '𐐔', '𐐽' => '𐐕', + '𐐾' => '𐐖', '𐐿' => '𐐗', '𐑀' => '𐐘', '𐑁' => '𐐙', + '𐑂' => '𐐚', '𐑃' => '𐐛', '𐑄' => '𐐜', '𐑅' => '𐐝', + '𐑆' => '𐐞', '𐑇' => '𐐟', '𐑈' => '𐐠', '𐑉' => '𐐡', + '𐑊' => '𐐢', '𐑋' => '𐐣', '𐑌' => '𐐤', '𐑍' => '𐐥', + '𐐦' => '𐑎', '𐐧' => '𐑏', + ); + + return strtr($string, $case_folding); +} + +// Fixes corrupted serialized strings after a character set conversion. +function fix_serialized_columns() +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT id_action, extra + FROM {db_prefix}log_actions + WHERE action IN ({string:remove}, {string:delete})', + array( + 'remove' => 'remove', + 'delete' => 'delete', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (@unserialize($row['extra']) === false && preg_match('~^(a:3:{s:5:"topic";i:\d+;s:7:"subject";s:)(\d+):"(.+)"(;s:6:"member";s:5:"\d+";})$~', $row['extra'], $matches) === 1) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_actions + SET extra = {string:extra} + WHERE id_action = {int:current_action}', + array( + 'current_action' => $row['id_action'], + 'extra' => $matches[1] . strlen($matches[3]) . ':"' . $matches[3] . '"' . $matches[4], + ) + ); + } + $smcFunc['db_free_result']($request); + + // Refresh some cached data. + updateSettings(array( + 'memberlist_updated' => time(), + )); + +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Compat.php b/Sources/Subs-Compat.php new file mode 100644 index 0000000..0810058 --- /dev/null +++ b/Sources/Subs-Compat.php @@ -0,0 +1,243 @@ += strlen($str)) + break; + + $str_array[] = substr($str, $count, $str_length); + $count += $str_length; + } + + return $str_array; + } +} + +if (!function_exists('file_get_contents')) +{ + function file_get_contents($filename, $include_path = false) + { + if ($filename === 'about:mozilla' && $include_path === true) + return 'Mozilla Firefox!'; + + $fp = fopen($filename, 'rb', $include_path); + if ($fp == false) + return false; + + if (is_file($filename)) + $data = fread($fp, filesize($filename)); + else + { + $data = ''; + while (!feof($fp)) + $data .= fread($fp, 8192); + } + fclose($fp); + + return $data; + } +} + +// Define the old SMF sha1 function. +function sha1_smf($str) +{ + // If we have mhash loaded in, use it instead! + if (function_exists('mhash') && defined('MHASH_SHA1')) + return bin2hex(mhash(MHASH_SHA1, $str)); + + $nblk = (strlen($str) + 8 >> 6) + 1; + $blks = array_pad(array(), $nblk * 16, 0); + + for ($i = 0; $i < strlen($str); $i++) + $blks[$i >> 2] |= ord($str{$i}) << (24 - ($i % 4) * 8); + + $blks[$i >> 2] |= 0x80 << (24 - ($i % 4) * 8); + + return sha1_core($blks, strlen($str) * 8); +} + +// This is the core SHA-1 calculation routine, used by sha1(). +function sha1_core($x, $len) +{ + @$x[$len >> 5] |= 0x80 << (24 - $len % 32); + $x[(($len + 64 >> 9) << 4) + 15] = $len; + + $w = array(); + $a = 1732584193; + $b = -271733879; + $c = -1732584194; + $d = 271733878; + $e = -1009589776; + + for ($i = 0, $n = count($x); $i < $n; $i += 16) + { + $olda = $a; + $oldb = $b; + $oldc = $c; + $oldd = $d; + $olde = $e; + + for ($j = 0; $j < 80; $j++) + { + if ($j < 16) + $w[$j] = isset($x[$i + $j]) ? $x[$i + $j] : 0; + else + $w[$j] = sha1_rol($w[$j - 3] ^ $w[$j - 8] ^ $w[$j - 14] ^ $w[$j - 16], 1); + + $t = sha1_rol($a, 5) + sha1_ft($j, $b, $c, $d) + $e + $w[$j] + sha1_kt($j); + $e = $d; + $d = $c; + $c = sha1_rol($b, 30); + $b = $a; + $a = $t; + } + + $a += $olda; + $b += $oldb; + $c += $oldc; + $d += $oldd; + $e += $olde; + } + + return sprintf('%08x%08x%08x%08x%08x', $a, $b, $c, $d, $e); +} + +function sha1_ft($t, $b, $c, $d) +{ + if ($t < 20) + return ($b & $c) | ((~$b) & $d); + if ($t < 40) + return $b ^ $c ^ $d; + if ($t < 60) + return ($b & $c) | ($b & $d) | ($c & $d); + + return $b ^ $c ^ $d; +} + +function sha1_kt($t) +{ + return $t < 20 ? 1518500249 : ($t < 40 ? 1859775393 : ($t < 60 ? -1894007588 : -899497514)); +} + +function sha1_rol($num, $cnt) +{ + // Unfortunately, PHP uses unsigned 32-bit longs only. So we have to kludge it a bit. + if ($num & 0x80000000) + $a = ($num >> 1 & 0x7fffffff) >> (31 - $cnt); + else + $a = $num >> (32 - $cnt); + + return ($num << $cnt) | $a; +} + +// Still on old PHP - bad boy! (the built in one would be faster.) +if (!function_exists('sha1')) +{ + function sha1($str) + { + return sha1_smf($str); + } +} + +if (!function_exists('array_combine')) +{ + function array_combine($keys, $values) + { + $ret = array(); + if (($array_error = !is_array($keys) || !is_array($values)) || empty($values) || ($count=count($keys)) != count($values)) + { + trigger_error('array_combine(): Both parameters should be non-empty arrays with an equal number of elements', E_USER_WARNING); + + if ($array_error) + return; + return false; + } + + // Ensure that both arrays aren't associative arrays. + $keys = array_values($keys); + $values = array_values($values); + + for ($i=0; $i < $count; $i++) + $ret[$keys[$i]] = $values[$i]; + + return $ret; + } +} + +if (!function_exists('array_diff_key')) +{ + function array_diff_key() + { + $arrays = func_get_args(); + $result = array_shift($arrays); + foreach ($arrays as $array) + { + foreach ($result as $key => $v) + { + if (array_key_exists($key, $array)) + { + unset($result[$key]); + } + } + } + return $result; + } +} + +if (!function_exists('mysql_real_escape_string')) +{ + function mysql_real_escape_string($string, $connection = null) + { + return mysql_escape_string($string); + } +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Db-mysql.php b/Sources/Subs-Db-mysql.php new file mode 100644 index 0000000..052d57a --- /dev/null +++ b/Sources/Subs-Db-mysql.php @@ -0,0 +1,709 @@ + 'smf_db_query', + 'db_quote' => 'smf_db_quote', + 'db_fetch_assoc' => 'mysql_fetch_assoc', + 'db_fetch_row' => 'mysql_fetch_row', + 'db_free_result' => 'mysql_free_result', + 'db_insert' => 'smf_db_insert', + 'db_insert_id' => 'smf_db_insert_id', + 'db_num_rows' => 'mysql_num_rows', + 'db_data_seek' => 'mysql_data_seek', + 'db_num_fields' => 'mysql_num_fields', + 'db_escape_string' => 'addslashes', + 'db_unescape_string' => 'stripslashes', + 'db_server_info' => 'mysql_get_server_info', + 'db_affected_rows' => 'smf_db_affected_rows', + 'db_transaction' => 'smf_db_transaction', + 'db_error' => 'mysql_error', + 'db_select_db' => 'mysql_select_db', + 'db_title' => 'MySQL', + 'db_sybase' => false, + 'db_case_sensitive' => false, + 'db_escape_wildcard_string' => 'smf_db_escape_wildcard_string', + ); + + if (!empty($db_options['persist'])) + $connection = @mysql_pconnect($db_server, $db_user, $db_passwd); + else + $connection = @mysql_connect($db_server, $db_user, $db_passwd); + + // Something's wrong, show an error if its fatal (which we assume it is) + if (!$connection) + { + if (!empty($db_options['non_fatal'])) + return null; + else + db_fatal_error(); + } + + // Select the database, unless told not to + if (empty($db_options['dont_select_db']) && !@mysql_select_db($db_name, $connection) && empty($db_options['non_fatal'])) + db_fatal_error(); + + // This makes it possible to have SMF automatically change the sql_mode and autocommit if needed. + if (isset($mysql_set_mode) && $mysql_set_mode === true) + $smcFunc['db_query']('', 'SET sql_mode = \'\', AUTOCOMMIT = 1', + array(), + false + ); + + return $connection; +} + +// Extend the database functionality. +function db_extend($type = 'extra') +{ + global $sourcedir, $db_type; + + require_once($sourcedir . '/Db' . strtoupper($type[0]) . substr($type, 1) . '-' . $db_type . '.php'); + $initFunc = 'db_' . $type . '_init'; + $initFunc(); +} + +// Fix up the prefix so it doesn't require the database to be selected. +function db_fix_prefix(&$db_prefix, $db_name) +{ + $db_prefix = is_numeric(substr($db_prefix, 0, 1)) ? $db_name . '.' . $db_prefix : '`' . $db_name . '`.' . $db_prefix; +} + +function smf_db_replacement__callback($matches) +{ + global $db_callback, $user_info, $db_prefix; + + list ($values, $connection) = $db_callback; + + if (!is_resource($connection)) + db_fatal_error(); + + if ($matches[1] === 'db_prefix') + return $db_prefix; + + if ($matches[1] === 'query_see_board') + return $user_info['query_see_board']; + + if ($matches[1] === 'query_wanna_see_board') + return $user_info['query_wanna_see_board']; + + if (!isset($matches[2])) + smf_db_error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__); + + if (!isset($values[$matches[2]])) + smf_db_error_backtrace('The database value you\'re trying to insert does not exist: ' . htmlspecialchars($matches[2]), '', E_USER_ERROR, __FILE__, __LINE__); + + $replacement = $values[$matches[2]]; + + switch ($matches[1]) + { + case 'int': + if (!is_numeric($replacement) || (string) $replacement !== (string) (int) $replacement) + smf_db_error_backtrace('Wrong value type sent to the database. Integer expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + return (string) (int) $replacement; + break; + + case 'string': + case 'text': + return sprintf('\'%1$s\'', mysql_real_escape_string($replacement, $connection)); + break; + + case 'array_int': + if (is_array($replacement)) + { + if (empty($replacement)) + smf_db_error_backtrace('Database error, given array of integer values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + + foreach ($replacement as $key => $value) + { + if (!is_numeric($value) || (string) $value !== (string) (int) $value) + smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + + $replacement[$key] = (string) (int) $value; + } + + return implode(', ', $replacement); + } + else + smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + + break; + + case 'array_string': + if (is_array($replacement)) + { + if (empty($replacement)) + smf_db_error_backtrace('Database error, given array of string values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + + foreach ($replacement as $key => $value) + $replacement[$key] = sprintf('\'%1$s\'', mysql_real_escape_string($value, $connection)); + + return implode(', ', $replacement); + } + else + smf_db_error_backtrace('Wrong value type sent to the database. Array of strings expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + break; + + case 'date': + if (preg_match('~^(\d{4})-([0-1]?\d)-([0-3]?\d)$~', $replacement, $date_matches) === 1) + return sprintf('\'%04d-%02d-%02d\'', $date_matches[1], $date_matches[2], $date_matches[3]); + else + smf_db_error_backtrace('Wrong value type sent to the database. Date expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + break; + + case 'float': + if (!is_numeric($replacement)) + smf_db_error_backtrace('Wrong value type sent to the database. Floating point number expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + return (string) (float) $replacement; + break; + + case 'identifier': + // Backticks inside identifiers are supported as of MySQL 4.1. We don't need them for SMF. + return '`' . strtr($replacement, array('`' => '', '.' => '')) . '`'; + break; + + case 'raw': + return $replacement; + break; + + default: + smf_db_error_backtrace('Undefined type used in the database query. (' . $matches[1] . ':' . $matches[2] . ')', '', false, __FILE__, __LINE__); + break; + } +} + +// Just like the db_query, escape and quote a string, but not executing the query. +function smf_db_quote($db_string, $db_values, $connection = null) +{ + global $db_callback, $db_connection; + + // Only bother if there's something to replace. + if (strpos($db_string, '{') !== false) + { + // This is needed by the callback function. + $db_callback = array($db_values, $connection == null ? $db_connection : $connection); + + // Do the quoting and escaping + $db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string); + + // Clear this global variable. + $db_callback = array(); + } + + return $db_string; +} + +// Do a query. Takes care of errors too. +function smf_db_query($identifier, $db_string, $db_values = array(), $connection = null) +{ + global $db_cache, $db_count, $db_connection, $db_show_debug, $time_start; + global $db_unbuffered, $db_callback, $modSettings; + + // Comments that are allowed in a query are preg_removed. + static $allowed_comments_from = array( + '~\s+~s', + '~/\*!40001 SQL_NO_CACHE \*/~', + '~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~', + '~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~', + ); + static $allowed_comments_to = array( + ' ', + '', + '', + '', + ); + + // Decide which connection to use. + $connection = $connection == null ? $db_connection : $connection; + + // One more query.... + $db_count = !isset($db_count) ? 1 : $db_count + 1; + + if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override'])) + smf_db_error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__); + + // Use "ORDER BY null" to prevent Mysql doing filesorts for Group By clauses without an Order By + if (strpos($db_string, 'GROUP BY') !== false && strpos($db_string, 'ORDER BY') === false && strpos($db_string, 'INSERT INTO') === false) + { + // Add before LIMIT + if ($pos = strpos($db_string, 'LIMIT ')) + $db_string = substr($db_string, 0, $pos) . "\t\t\tORDER BY null\n" . substr($db_string, $pos, strlen($db_string)); + else + // Append it. + $db_string .= "\n\t\t\tORDER BY null"; + } + + if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false)) + { + // Pass some values to the global space for use in the callback function. + $db_callback = array($db_values, $connection); + + // Inject the values passed to this function. + $db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string); + + // This shouldn't be residing in global space any longer. + $db_callback = array(); + } + + // Debugging. + if (isset($db_show_debug) && $db_show_debug === true) + { + // Get the file and line number this function was called. + list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__); + + // Initialize $db_cache if not already initialized. + if (!isset($db_cache)) + $db_cache = array(); + + if (!empty($_SESSION['debug_redirect'])) + { + $db_cache = array_merge($_SESSION['debug_redirect'], $db_cache); + $db_count = count($db_cache) + 1; + $_SESSION['debug_redirect'] = array(); + } + + $st = microtime(); + // Don't overload it. + $db_cache[$db_count]['q'] = $db_count < 50 ? $db_string : '...'; + $db_cache[$db_count]['f'] = $file; + $db_cache[$db_count]['l'] = $line; + $db_cache[$db_count]['s'] = array_sum(explode(' ', $st)) - array_sum(explode(' ', $time_start)); + } + + // First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over. + if (empty($modSettings['disableQueryCheck'])) + { + $clean = ''; + $old_pos = 0; + $pos = -1; + while (true) + { + $pos = strpos($db_string, '\'', $pos + 1); + if ($pos === false) + break; + $clean .= substr($db_string, $old_pos, $pos - $old_pos); + + while (true) + { + $pos1 = strpos($db_string, '\'', $pos + 1); + $pos2 = strpos($db_string, '\\', $pos + 1); + if ($pos1 === false) + break; + elseif ($pos2 == false || $pos2 > $pos1) + { + $pos = $pos1; + break; + } + + $pos = $pos2 + 1; + } + $clean .= ' %s '; + + $old_pos = $pos + 1; + } + $clean .= substr($db_string, $old_pos); + $clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean))); + + // We don't use UNION in SMF, at least so far. But it's useful for injections. + if (strpos($clean, 'union') !== false && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0) + $fail = true; + // Comments? We don't use comments in our queries, we leave 'em outside! + elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false) + $fail = true; + // Trying to change passwords, slow us down, or something? + elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0) + $fail = true; + elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0) + $fail = true; + // Sub selects? We don't use those either. + elseif (preg_match('~\([^)]*?select~s', $clean) != 0) + $fail = true; + + if (!empty($fail) && function_exists('log_error')) + smf_db_error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__); + } + + if (empty($db_unbuffered)) + $ret = @mysql_query($db_string, $connection); + else + $ret = @mysql_unbuffered_query($db_string, $connection); + if ($ret === false && empty($db_values['db_error_skip'])) + $ret = smf_db_error($db_string, $connection); + + // Debugging. + if (isset($db_show_debug) && $db_show_debug === true) + $db_cache[$db_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st)); + + return $ret; +} + +function smf_db_affected_rows($connection = null) +{ + global $db_connection; + + return mysql_affected_rows($connection == null ? $db_connection : $connection); +} + +function smf_db_insert_id($table, $field = null, $connection = null) +{ + global $db_connection, $db_prefix; + + $table = str_replace('{db_prefix}', $db_prefix, $table); + + // MySQL doesn't need the table or field information. + return mysql_insert_id($connection == null ? $db_connection : $connection); +} + +// Do a transaction. +function smf_db_transaction($type = 'commit', $connection = null) +{ + global $db_connection; + + // Decide which connection to use + $connection = $connection == null ? $db_connection : $connection; + + if ($type == 'begin') + return @mysql_query('BEGIN', $connection); + elseif ($type == 'rollback') + return @mysql_query('ROLLBACK', $connection); + elseif ($type == 'commit') + return @mysql_query('COMMIT', $connection); + + return false; +} + +// Database error! +function smf_db_error($db_string, $connection = null) +{ + global $txt, $context, $sourcedir, $webmaster_email, $modSettings; + global $forum_version, $db_connection, $db_last_error, $db_persist; + global $db_server, $db_user, $db_passwd, $db_name, $db_show_debug, $ssi_db_user, $ssi_db_passwd; + global $smcFunc; + + // Get the file and line numbers. + list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__); + + // Decide which connection to use + $connection = $connection == null ? $db_connection : $connection; + + // This is the error message... + $query_error = mysql_error($connection); + $query_errno = mysql_errno($connection); + + // Error numbers: + // 1016: Can't open file '....MYI' + // 1030: Got error ??? from table handler. + // 1034: Incorrect key file for table. + // 1035: Old key file for table. + // 1205: Lock wait timeout exceeded. + // 1213: Deadlock found. + // 2006: Server has gone away. + // 2013: Lost connection to server during query. + + // Log the error. + if ($query_errno != 1213 && $query_errno != 1205 && function_exists('log_error')) + log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n$db_string" : ''), 'database', $file, $line); + + // Database error auto fixing ;). + if (function_exists('cache_get_data') && (!isset($modSettings['autoFixDatabase']) || $modSettings['autoFixDatabase'] == '1')) + { + // Force caching on, just for the error checking. + $old_cache = @$modSettings['cache_enable']; + $modSettings['cache_enable'] = '1'; + + if (($temp = cache_get_data('db_last_error', 600)) !== null) + $db_last_error = max(@$db_last_error, $temp); + + if (@$db_last_error < time() - 3600 * 24 * 3) + { + // We know there's a problem... but what? Try to auto detect. + if ($query_errno == 1030 && strpos($query_error, ' 127 ') !== false) + { + preg_match_all('~(?:[\n\r]|^)[^\']+?(?:FROM|JOIN|UPDATE|TABLE) ((?:[^\n\r(]+?(?:, )?)*)~s', $db_string, $matches); + + $fix_tables = array(); + foreach ($matches[1] as $tables) + { + $tables = array_unique(explode(',', $tables)); + foreach ($tables as $table) + { + // Now, it's still theoretically possible this could be an injection. So backtick it! + if (trim($table) != '') + $fix_tables[] = '`' . strtr(trim($table), array('`' => '')) . '`'; + } + } + + $fix_tables = array_unique($fix_tables); + } + // Table crashed. Let's try to fix it. + elseif ($query_errno == 1016) + { + if (preg_match('~\'([^\.\']+)~', $query_error, $match) != 0) + $fix_tables = array('`' . $match[1] . '`'); + } + // Indexes crashed. Should be easy to fix! + elseif ($query_errno == 1034 || $query_errno == 1035) + { + preg_match('~\'([^\']+?)\'~', $query_error, $match); + $fix_tables = array('`' . $match[1] . '`'); + } + } + + // Check for errors like 145... only fix it once every three days, and send an email. (can't use empty because it might not be set yet...) + if (!empty($fix_tables)) + { + // Subs-Admin.php for updateSettingsFile(), Subs-Post.php for sendmail(). + require_once($sourcedir . '/Subs-Admin.php'); + require_once($sourcedir . '/Subs-Post.php'); + + // Make a note of the REPAIR... + cache_put_data('db_last_error', time(), 600); + if (($temp = cache_get_data('db_last_error', 600)) === null) + updateSettingsFile(array('db_last_error' => time())); + + // Attempt to find and repair the broken table. + foreach ($fix_tables as $table) + $smcFunc['db_query']('', " + REPAIR TABLE $table", false, false); + + // And send off an email! + sendmail($webmaster_email, $txt['database_error'], $txt['tried_to_repair']); + + $modSettings['cache_enable'] = $old_cache; + + // Try the query again...? + $ret = $smcFunc['db_query']('', $db_string, false, false); + if ($ret !== false) + return $ret; + } + else + $modSettings['cache_enable'] = $old_cache; + + // Check for the "lost connection" or "deadlock found" errors - and try it just one more time. + if (in_array($query_errno, array(1205, 1213, 2006, 2013))) + { + if (in_array($query_errno, array(2006, 2013)) && $db_connection == $connection) + { + // Are we in SSI mode? If so try that username and password first + if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd)) + { + if (empty($db_persist)) + $db_connection = @mysql_connect($db_server, $ssi_db_user, $ssi_db_passwd); + else + $db_connection = @mysql_pconnect($db_server, $ssi_db_user, $ssi_db_passwd); + } + // Fall back to the regular username and password if need be + if (!$db_connection) + { + if (empty($db_persist)) + $db_connection = @mysql_connect($db_server, $db_user, $db_passwd); + else + $db_connection = @mysql_pconnect($db_server, $db_user, $db_passwd); + } + + if (!$db_connection || !@mysql_select_db($db_name, $db_connection)) + $db_connection = false; + } + + if ($db_connection) + { + // Try a deadlock more than once more. + for ($n = 0; $n < 4; $n++) + { + $ret = $smcFunc['db_query']('', $db_string, false, false); + + $new_errno = mysql_errno($db_connection); + if ($ret !== false || in_array($new_errno, array(1205, 1213))) + break; + } + + // If it failed again, shucks to be you... we're not trying it over and over. + if ($ret !== false) + return $ret; + } + } + // Are they out of space, perhaps? + elseif ($query_errno == 1030 && (strpos($query_error, ' -1 ') !== false || strpos($query_error, ' 28 ') !== false || strpos($query_error, ' 12 ') !== false)) + { + if (!isset($txt)) + $query_error .= ' - check database storage space.'; + else + { + if (!isset($txt['mysql_error_space'])) + loadLanguage('Errors'); + + $query_error .= !isset($txt['mysql_error_space']) ? ' - check database storage space.' : $txt['mysql_error_space']; + } + } + } + + // Nothing's defined yet... just die with it. + if (empty($context) || empty($txt)) + die($query_error); + + // Show an error message, if possible. + $context['error_title'] = $txt['database_error']; + if (allowedTo('admin_forum')) + $context['error_message'] = nl2br($query_error) . '
' . $txt['file'] . ': ' . $file . '
' . $txt['line'] . ': ' . $line; + else + $context['error_message'] = $txt['try_again']; + + // A database error is often the sign of a database in need of upgrade. Check forum versions, and if not identical suggest an upgrade... (not for Demo/CVS versions!) + if (allowedTo('admin_forum') && !empty($forum_version) && $forum_version != 'SMF ' . @$modSettings['smfVersion'] && strpos($forum_version, 'Demo') === false && strpos($forum_version, 'CVS') === false) + $context['error_message'] .= '

' . sprintf($txt['database_error_versions'], $forum_version, $modSettings['smfVersion']); + + if (allowedTo('admin_forum') && isset($db_show_debug) && $db_show_debug === true) + { + $context['error_message'] .= '

' . nl2br($db_string); + } + + // It's already been logged... don't log it again. + fatal_error($context['error_message'], false); +} + +// Insert some data... +function smf_db_insert($method = 'replace', $table, $columns, $data, $keys, $disable_trans = false, $connection = null) +{ + global $smcFunc, $db_connection, $db_prefix; + + $connection = $connection === null ? $db_connection : $connection; + + // With nothing to insert, simply return. + if (empty($data)) + return; + + // Replace the prefix holder with the actual prefix. + $table = str_replace('{db_prefix}', $db_prefix, $table); + + // Inserting data as a single row can be done as a single array. + if (!is_array($data[array_rand($data)])) + $data = array($data); + + // Create the mold for a single row insert. + $insertData = '('; + foreach ($columns as $columnName => $type) + { + // Are we restricting the length? + if (strpos($type, 'string-') !== false) + $insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName); + else + $insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName); + } + $insertData = substr($insertData, 0, -2) . ')'; + + // Create an array consisting of only the columns. + $indexed_columns = array_keys($columns); + + // Here's where the variables are injected to the query. + $insertRows = array(); + foreach ($data as $dataRow) + $insertRows[] = smf_db_quote($insertData, array_combine($indexed_columns, $dataRow), $connection); + + // Determine the method of insertion. + $queryTitle = $method == 'replace' ? 'REPLACE' : ($method == 'ignore' ? 'INSERT IGNORE' : 'INSERT'); + + // Do the insert. + $smcFunc['db_query']('', ' + ' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`) + VALUES + ' . implode(', + ', $insertRows), + array( + 'security_override' => true, + 'db_error_skip' => $table === $db_prefix . 'log_errors', + ), + $connection + ); +} + +// This function tries to work out additional error information from a back trace. +function smf_db_error_backtrace($error_message, $log_message = '', $error_type = false, $file = null, $line = null) +{ + if (empty($log_message)) + $log_message = $error_message; + + if (function_exists('debug_backtrace')) + { + foreach (debug_backtrace() as $step) + { + // Found it? + if (strpos($step['function'], 'query') === false && !in_array(substr($step['function'], 0, 7), array('smf_db_', 'preg_re', 'db_erro', 'call_us')) && substr($step['function'], 0, 2) != '__') + { + $log_message .= '
Function: ' . $step['function']; + break; + } + + if (isset($step['line'])) + { + $file = $step['file']; + $line = $step['line']; + } + } + } + + // A special case - we want the file and line numbers for debugging. + if ($error_type == 'return') + return array($file, $line); + + // Is always a critical error. + if (function_exists('log_error')) + log_error($log_message, 'critical', $file, $line); + + if (function_exists('fatal_error')) + { + fatal_error($error_message, false); + + // Cannot continue... + exit; + } + elseif ($error_type) + trigger_error($error_message . ($line !== null ? '(' . basename($file) . '-' . $line . ')' : ''), $error_type); + else + trigger_error($error_message . ($line !== null ? '(' . basename($file) . '-' . $line . ')' : '')); +} + +// Escape the LIKE wildcards so that they match the character and not the wildcard. +// The optional second parameter turns human readable wildcards into SQL wildcards. +function smf_db_escape_wildcard_string($string, $translate_human_wildcards=false) +{ + $replacements = array( + '%' => '\%', + '_' => '\_', + '\\' => '\\\\', + ); + + if ($translate_human_wildcards) + $replacements += array( + '*' => '%', + ); + + return strtr($string, $replacements); +} +?> \ No newline at end of file diff --git a/Sources/Subs-Db-postgresql.php b/Sources/Subs-Db-postgresql.php new file mode 100644 index 0000000..5591ed3 --- /dev/null +++ b/Sources/Subs-Db-postgresql.php @@ -0,0 +1,757 @@ + 'smf_db_query', + 'db_quote' => 'smf_db_quote', + 'db_insert' => 'smf_db_insert', + 'db_insert_id' => 'smf_db_insert_id', + 'db_fetch_assoc' => 'smf_db_fetch_assoc', + 'db_fetch_row' => 'smf_db_fetch_row', + 'db_free_result' => 'pg_free_result', + 'db_num_rows' => 'pg_num_rows', + 'db_data_seek' => 'smf_db_data_seek', + 'db_num_fields' => 'pg_num_fields', + 'db_escape_string' => 'pg_escape_string', + 'db_unescape_string' => 'smf_db_unescape_string', + 'db_server_info' => 'smf_db_version', + 'db_affected_rows' => 'smf_db_affected_rows', + 'db_transaction' => 'smf_db_transaction', + 'db_error' => 'pg_last_error', + 'db_select_db' => 'smf_db_select_db', + 'db_title' => 'PostgreSQL', + 'db_sybase' => true, + 'db_case_sensitive' => true, + 'db_escape_wildcard_string' => 'smf_db_escape_wildcard_string', + ); + + if (!empty($db_options['persist'])) + $connection = @pg_pconnect('host=' . $db_server . ' dbname=' . $db_name . ' user=\'' . $db_user . '\' password=\'' . $db_passwd . '\''); + else + $connection = @pg_connect( 'host=' . $db_server . ' dbname=' . $db_name . ' user=\'' . $db_user . '\' password=\'' . $db_passwd . '\''); + + // Something's wrong, show an error if its fatal (which we assume it is) + if (!$connection) + { + if (!empty($db_options['non_fatal'])) + { + return null; + } + else + { + db_fatal_error(); + } + } + + return $connection; +} + +// Extend the database functionality. +function db_extend ($type = 'extra') +{ + global $sourcedir, $db_type; + + require_once($sourcedir . '/Db' . strtoupper($type[0]) . substr($type, 1) . '-' . $db_type . '.php'); + $initFunc = 'db_' . $type . '_init'; + $initFunc(); +} + +// Do nothing on postgreSQL +function db_fix_prefix (&$db_prefix, $db_name) +{ + return; +} + +function smf_db_replacement__callback($matches) +{ + global $db_callback, $user_info, $db_prefix; + + list ($values, $connection) = $db_callback; + + if (!is_resource($connection)) + db_fatal_error(); + + if ($matches[1] === 'db_prefix') + return $db_prefix; + + if ($matches[1] === 'query_see_board') + return $user_info['query_see_board']; + + if ($matches[1] === 'query_wanna_see_board') + return $user_info['query_wanna_see_board']; + + if (!isset($matches[2])) + smf_db_error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__); + + if (!isset($values[$matches[2]])) + smf_db_error_backtrace('The database value you\'re trying to insert does not exist: ' . htmlspecialchars($matches[2]), '', E_USER_ERROR, __FILE__, __LINE__); + + $replacement = $values[$matches[2]]; + + switch ($matches[1]) + { + case 'int': + if (!is_numeric($replacement) || (string) $replacement !== (string) (int) $replacement) + smf_db_error_backtrace('Wrong value type sent to the database. Integer expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + return (string) (int) $replacement; + break; + + case 'string': + case 'text': + return sprintf('\'%1$s\'', pg_escape_string($replacement)); + break; + + case 'array_int': + if (is_array($replacement)) + { + if (empty($replacement)) + smf_db_error_backtrace('Database error, given array of integer values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + + foreach ($replacement as $key => $value) + { + if (!is_numeric($value) || (string) $value !== (string) (int) $value) + smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + + $replacement[$key] = (string) (int) $value; + } + + return implode(', ', $replacement); + } + else + smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + + break; + + case 'array_string': + if (is_array($replacement)) + { + if (empty($replacement)) + smf_db_error_backtrace('Database error, given array of string values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + + foreach ($replacement as $key => $value) + $replacement[$key] = sprintf('\'%1$s\'', pg_escape_string($value)); + + return implode(', ', $replacement); + } + else + smf_db_error_backtrace('Wrong value type sent to the database. Array of strings expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + break; + + case 'date': + if (preg_match('~^(\d{4})-([0-1]?\d)-([0-3]?\d)$~', $replacement, $date_matches) === 1) + return sprintf('\'%04d-%02d-%02d\'', $date_matches[1], $date_matches[2], $date_matches[3]); + else + smf_db_error_backtrace('Wrong value type sent to the database. Date expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + break; + + case 'float': + if (!is_numeric($replacement)) + smf_db_error_backtrace('Wrong value type sent to the database. Floating point number expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + return (string) (float) $replacement; + break; + + case 'identifier': + return '`' . strtr($replacement, array('`' => '', '.' => '')) . '`'; + break; + + case 'raw': + return $replacement; + break; + + default: + smf_db_error_backtrace('Undefined type used in the database query. (' . $matches[1] . ':' . $matches[2] . ')', '', false, __FILE__, __LINE__); + break; + } +} + +// Just like the db_query, escape and quote a string, but not executing the query. +function smf_db_quote($db_string, $db_values, $connection = null) +{ + global $db_callback, $db_connection; + + // Only bother if there's something to replace. + if (strpos($db_string, '{') !== false) + { + // This is needed by the callback function. + $db_callback = array($db_values, $connection == null ? $db_connection : $connection); + + // Do the quoting and escaping + $db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string); + + // Clear this global variable. + $db_callback = array(); + } + + return $db_string; +} + +// Do a query. Takes care of errors too. +function smf_db_query($identifier, $db_string, $db_values = array(), $connection = null) +{ + global $db_cache, $db_count, $db_connection, $db_show_debug, $time_start; + global $db_unbuffered, $db_callback, $db_last_result, $db_replace_result, $modSettings; + + // Decide which connection to use. + $connection = $connection == null ? $db_connection : $connection; + + // Special queries that need processing. + $replacements = array( + 'alter_table_boards' => array( + '~(.+)~' => '', + ), + 'alter_table_icons' => array( + '~(.+)~' => '', + ), + 'alter_table_smileys' => array( + '~(.+)~' => '', + ), + 'alter_table_spiders' => array( + '~(.+)~' => '', + ), + 'ban_suggest_error_ips' => array( + '~RLIKE~' => '~', + '~\\.~' => '\.', + ), + 'ban_suggest_message_ips' => array( + '~RLIKE~' => '~', + '~\\.~' => '\.', + ), + 'consolidate_spider_stats' => array( + '~MONTH\(log_time\), DAYOFMONTH\(log_time\)~' => 'MONTH(CAST(CAST(log_time AS abstime) AS timestamp)), DAYOFMONTH(CAST(CAST(log_time AS abstime) AS timestamp))', + ), + 'delete_subscription' => array( + '~LIMIT 1~' => '', + ), + 'display_get_post_poster' => array( + '~GROUP BY id_msg\s+HAVING~' => 'AND', + ), + 'attach_download_increase' => array( + '~LOW_PRIORITY~' => '', + ), + 'boardindex_fetch_boards' => array( + '~IFNULL\(lb.id_msg, 0\) >= b.id_msg_updated~' => 'CASE WHEN IFNULL(lb.id_msg, 0) >= b.id_msg_updated THEN 1 ELSE 0 END', + '~(.)$~' => '$1 ORDER BY b.board_order', + ), + 'get_random_number' => array( + '~RAND~' => 'RANDOM', + ), + 'insert_log_search_topics' => array( + '~NOT RLIKE~' => '!~', + ), + 'insert_log_search_results_no_index' => array( + '~NOT RLIKE~' => '!~', + ), + 'insert_log_search_results_subject' => array( + '~NOT RLIKE~' => '!~', + ), + 'messageindex_fetch_boards' => array( + '~(.)$~' => '$1 ORDER BY b.board_order', + ), + 'select_message_icons' => array( + '~(.)$~' => '$1 ORDER BY icon_order', + ), + 'set_character_set' => array( + '~SET\\s+NAMES\\s([a-zA-Z0-9\\-_]+)~' => 'SET NAMES \'$1\'', + ), + 'pm_conversation_list' => array( + '~ORDER\\s+BY\\s+\\{raw:sort\\}~' => 'ORDER BY ' . (isset($db_values['sort']) ? ($db_values['sort'] === 'pm.id_pm' ? 'MAX(pm.id_pm)' : $db_values['sort']) : ''), + ), + 'top_topic_starters' => array( + '~ORDER BY FIND_IN_SET\(id_member,(.+?)\)~' => 'ORDER BY STRPOS(\',\' || $1 || \',\', \',\' || id_member|| \',\')', + ), + 'order_by_board_order' => array( + '~(.)$~' => '$1 ORDER BY b.board_order', + ), + 'spider_check' => array( + '~(.)$~' => '$1 ORDER BY LENGTH(user_agent) DESC', + ), + 'unread_replies' => array( + '~SELECT\\s+DISTINCT\\s+t.id_topic~' => 'SELECT t.id_topic, {raw:sort}', + ), + 'profile_board_stats' => array( + '~COUNT\(\*\) \/ MAX\(b.num_posts\)~' => 'CAST(COUNT(*) AS DECIMAL) / CAST(b.num_posts AS DECIMAL)', + ), + 'set_smiley_order' => array( + '~(.+)~' => '', + ), + ); + + if (isset($replacements[$identifier])) + $db_string = preg_replace(array_keys($replacements[$identifier]), array_values($replacements[$identifier]), $db_string); + + // Limits need to be a little different. + $db_string = preg_replace('~\sLIMIT\s(\d+|{int:.+}),\s*(\d+|{int:.+})\s*$~i', 'LIMIT $2 OFFSET $1', $db_string); + + if (trim($db_string) == '') + return false; + + // Comments that are allowed in a query are preg_removed. + static $allowed_comments_from = array( + '~\s+~s', + '~/\*!40001 SQL_NO_CACHE \*/~', + '~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~', + '~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~', + ); + static $allowed_comments_to = array( + ' ', + '', + '', + '', + ); + + // One more query.... + $db_count = !isset($db_count) ? 1 : $db_count + 1; + $db_replace_result = 0; + + if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override'])) + smf_db_error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__); + + if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false)) + { + // Pass some values to the global space for use in the callback function. + $db_callback = array($db_values, $connection); + + // Inject the values passed to this function. + $db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string); + + // This shouldn't be residing in global space any longer. + $db_callback = array(); + } + + // Debugging. + if (isset($db_show_debug) && $db_show_debug === true) + { + // Get the file and line number this function was called. + list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__); + + // Initialize $db_cache if not already initialized. + if (!isset($db_cache)) + $db_cache = array(); + + if (!empty($_SESSION['debug_redirect'])) + { + $db_cache = array_merge($_SESSION['debug_redirect'], $db_cache); + $db_count = count($db_cache) + 1; + $_SESSION['debug_redirect'] = array(); + } + + $st = microtime(); + // Don't overload it. + $db_cache[$db_count]['q'] = $db_count < 50 ? $db_string : '...'; + $db_cache[$db_count]['f'] = $file; + $db_cache[$db_count]['l'] = $line; + $db_cache[$db_count]['s'] = array_sum(explode(' ', $st)) - array_sum(explode(' ', $time_start)); + } + + // First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over. + if (empty($modSettings['disableQueryCheck'])) + { + $clean = ''; + $old_pos = 0; + $pos = -1; + while (true) + { + $pos = strpos($db_string, '\'', $pos + 1); + if ($pos === false) + break; + $clean .= substr($db_string, $old_pos, $pos - $old_pos); + + while (true) + { + $pos1 = strpos($db_string, '\'', $pos + 1); + $pos2 = strpos($db_string, '\\', $pos + 1); + if ($pos1 === false) + break; + elseif ($pos2 == false || $pos2 > $pos1) + { + $pos = $pos1; + break; + } + + $pos = $pos2 + 1; + } + $clean .= ' %s '; + + $old_pos = $pos + 1; + } + $clean .= substr($db_string, $old_pos); + $clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean))); + + // We don't use UNION in SMF, at least so far. But it's useful for injections. + if (strpos($clean, 'union') !== false && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0) + $fail = true; + // Comments? We don't use comments in our queries, we leave 'em outside! + elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false) + $fail = true; + // Trying to change passwords, slow us down, or something? + elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0) + $fail = true; + elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0) + $fail = true; + // Sub selects? We don't use those either. + elseif (preg_match('~\([^)]*?select~s', $clean) != 0) + $fail = true; + + if (!empty($fail) && function_exists('log_error')) + smf_db_error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__); + } + + $db_last_result = @pg_query($connection, $db_string); + + if ($db_last_result === false && empty($db_values['db_error_skip'])) + $db_last_result = smf_db_error($db_string, $connection); + + // Debugging. + if (isset($db_show_debug) && $db_show_debug === true) + $db_cache[$db_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st)); + + return $db_last_result; +} + +function smf_db_affected_rows($result = null) +{ + global $db_last_result, $db_replace_result; + + if ($db_replace_result) + return $db_replace_result; + elseif ($result == null && !$db_last_result) + return 0; + + return pg_affected_rows($result == null ? $db_last_result : $result); +} + +function smf_db_insert_id($table, $field = null, $connection = null) +{ + global $db_connection, $smcFunc, $db_prefix; + + $table = str_replace('{db_prefix}', $db_prefix, $table); + + if ($connection === false) + $connection = $db_connection; + + // Try get the last ID for the auto increment field. + $request = $smcFunc['db_query']('', 'SELECT CURRVAL(\'' . $table . '_seq\') AS insertID', + array( + ) + ); + if (!$request) + return false; + list ($lastID) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return $lastID; +} + +// Do a transaction. +function smf_db_transaction($type = 'commit', $connection = null) +{ + global $db_connection; + + // Decide which connection to use + $connection = $connection == null ? $db_connection : $connection; + + if ($type == 'begin') + return @pg_query($connection, 'BEGIN'); + elseif ($type == 'rollback') + return @pg_query($connection, 'ROLLBACK'); + elseif ($type == 'commit') + return @pg_query($connection, 'COMMIT'); + + return false; +} + +// Database error! +function smf_db_error($db_string, $connection = null) +{ + global $txt, $context, $sourcedir, $webmaster_email, $modSettings; + global $forum_version, $db_connection, $db_last_error, $db_persist; + global $db_server, $db_user, $db_passwd, $db_name, $db_show_debug, $ssi_db_user, $ssi_db_passwd; + global $smcFunc; + + // We'll try recovering the file and line number the original db query was called from. + list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__); + + // Decide which connection to use + $connection = $connection == null ? $db_connection : $connection; + + // This is the error message... + $query_error = @pg_last_error($connection); + + // Log the error. + if (function_exists('log_error')) + log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n" .$db_string : ''), 'database', $file, $line); + + // Nothing's defined yet... just die with it. + if (empty($context) || empty($txt)) + die($query_error); + + // Show an error message, if possible. + $context['error_title'] = $txt['database_error']; + if (allowedTo('admin_forum')) + $context['error_message'] = nl2br($query_error) . '
' . $txt['file'] . ': ' . $file . '
' . $txt['line'] . ': ' . $line; + else + $context['error_message'] = $txt['try_again']; + + // A database error is often the sign of a database in need of updgrade. Check forum versions, and if not identical suggest an upgrade... (not for Demo/CVS versions!) + if (allowedTo('admin_forum') && !empty($forum_version) && $forum_version != 'SMF ' . @$modSettings['smfVersion'] && strpos($forum_version, 'Demo') === false && strpos($forum_version, 'CVS') === false) + $context['error_message'] .= '

' . sprintf($txt['database_error_versions'], $forum_version, $modSettings['smfVersion']); + + if (allowedTo('admin_forum') && isset($db_show_debug) && $db_show_debug === true) + { + $context['error_message'] .= '

' . nl2br($db_string); + } + + // It's already been logged... don't log it again. + fatal_error($context['error_message'], false); +} + +// A PostgreSQL specific function for tracking the current row... +function smf_db_fetch_row($request, $counter = false) +{ + global $db_row_count; + + if ($counter !== false) + return pg_fetch_row($request, $counter); + + // Reset the row counter... + if (!isset($db_row_count[(int) $request])) + $db_row_count[(int) $request] = 0; + + // Return the right row. + return @pg_fetch_row($request, $db_row_count[(int) $request]++); +} + +// Get an associative array +function smf_db_fetch_assoc($request, $counter = false) +{ + global $db_row_count; + + if ($counter !== false) + return pg_fetch_assoc($request, $counter); + + // Reset the row counter... + if (!isset($db_row_count[(int) $request])) + $db_row_count[(int) $request] = 0; + + // Return the right row. + return @pg_fetch_assoc($request, $db_row_count[(int) $request]++); +} + +// Reset the pointer... +function smf_db_data_seek($request, $counter) +{ + global $db_row_count; + + $db_row_count[(int) $request] = $counter; + + return true; +} + +// Unescape an escaped string! +function smf_db_unescape_string($string) +{ + return strtr($string, array('\'\'' => '\'')); +} + +// For inserting data in a special way... +function smf_db_insert($method = 'replace', $table, $columns, $data, $keys, $disable_trans = false, $connection = null) +{ + global $db_replace_result, $db_in_transact, $smcFunc, $db_connection, $db_prefix; + + $connection = $connection === null ? $db_connection : $connection; + + if (empty($data)) + return; + + if (!is_array($data[array_rand($data)])) + $data = array($data); + + // Replace the prefix holder with the actual prefix. + $table = str_replace('{db_prefix}', $db_prefix, $table); + + $priv_trans = false; + if ((count($data) > 1 || $method == 'replace') && !$db_in_transact && !$disable_trans) + { + $smcFunc['db_transaction']('begin', $connection); + $priv_trans = true; + } + + // PostgreSQL doesn't support replace: we implement a MySQL-compatible behavior instead + if ($method == 'replace') + { + $count = 0; + $where = ''; + foreach ($columns as $columnName => $type) + { + // Are we restricting the length? + if (strpos($type, 'string-') !== false) + $actualType = sprintf($columnName . ' = SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $count); + else + $actualType = sprintf($columnName . ' = {%1$s:%2$s}, ', $type, $count); + + // A key? That's what we were looking for. + if (in_array($columnName, $keys)) + $where .= (empty($where) ? '' : ' AND ') . substr($actualType, 0, -2); + $count++; + } + + // Make it so. + if (!empty($where) && !empty($data)) + { + foreach ($data as $k => $entry) + { + $smcFunc['db_query']('', ' + DELETE FROM ' . $table . + ' WHERE ' . $where, + $entry, $connection + ); + } + } + } + + if (!empty($data)) + { + // Create the mold for a single row insert. + $insertData = '('; + foreach ($columns as $columnName => $type) + { + // Are we restricting the length? + if (strpos($type, 'string-') !== false) + $insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName); + else + $insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName); + } + $insertData = substr($insertData, 0, -2) . ')'; + + // Create an array consisting of only the columns. + $indexed_columns = array_keys($columns); + + // Here's where the variables are injected to the query. + $insertRows = array(); + foreach ($data as $dataRow) + $insertRows[] = smf_db_quote($insertData, array_combine($indexed_columns, $dataRow), $connection); + + foreach ($insertRows as $entry) + // Do the insert. + $smcFunc['db_query']('', ' + INSERT INTO ' . $table . '("' . implode('", "', $indexed_columns) . '") + VALUES + ' . $entry, + array( + 'security_override' => true, + 'db_error_skip' => $method == 'ignore' || $table === $db_prefix . 'log_errors', + ), + $connection + ); + } + + if ($priv_trans) + $smcFunc['db_transaction']('commit', $connection); +} + +// Dummy function really. +function smf_db_select_db($db_name, $db_connection) +{ + return true; +} + +// Get the current version. +function smf_db_version() +{ + $version = pg_version(); + + return $version['client']; +} + +// This function tries to work out additional error information from a back trace. +function smf_db_error_backtrace($error_message, $log_message = '', $error_type = false, $file = null, $line = null) +{ + if (empty($log_message)) + $log_message = $error_message; + + if (function_exists('debug_backtrace')) + { + foreach (debug_backtrace() as $step) + { + // Found it? + if (strpos($step['function'], 'query') === false && !in_array(substr($step['function'], 0, 7), array('smf_db_', 'preg_re', 'db_erro', 'call_us')) && substr($step['function'], 0, 2) != '__') + { + $log_message .= '
Function: ' . $step['function']; + break; + } + + if (isset($step['line'])) + { + $file = $step['file']; + $line = $step['line']; + } + } + } + + // A special case - we want the file and line numbers for debugging. + if ($error_type == 'return') + return array($file, $line); + + // Is always a critical error. + if (function_exists('log_error')) + log_error($log_message, 'critical', $file, $line); + + if (function_exists('fatal_error')) + { + fatal_error($error_message, $error_type); + + // Cannot continue... + exit; + } + elseif ($error_type) + trigger_error($error_message . ($line !== null ? '(' . basename($file) . '-' . $line . ')' : ''), $error_type); + else + trigger_error($error_message . ($line !== null ? '(' . basename($file) . '-' . $line . ')' : '')); +} + +// Escape the LIKE wildcards so that they match the character and not the wildcard. +// The optional second parameter turns human readable wildcards into SQL wildcards. +function smf_db_escape_wildcard_string($string, $translate_human_wildcards=false) +{ + $replacements = array( + '%' => '\%', + '_' => '\_', + '\\' => '\\\\', + ); + + if ($translate_human_wildcards) + $replacements += array( + '*' => '%', + ); + + return strtr($string, $replacements); +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Db-sqlite.php b/Sources/Subs-Db-sqlite.php new file mode 100644 index 0000000..99bb9b4 --- /dev/null +++ b/Sources/Subs-Db-sqlite.php @@ -0,0 +1,734 @@ + 'smf_db_query', + 'db_quote' => 'smf_db_quote', + 'db_fetch_assoc' => 'sqlite_fetch_array', + 'db_fetch_row' => 'smf_db_fetch_row', + 'db_free_result' => 'smf_db_free_result', + 'db_insert' => 'smf_db_insert', + 'db_insert_id' => 'smf_db_insert_id', + 'db_num_rows' => 'sqlite_num_rows', + 'db_data_seek' => 'sqlite_seek', + 'db_num_fields' => 'sqlite_num_fields', + 'db_escape_string' => 'sqlite_escape_string', + 'db_unescape_string' => 'smf_db_unescape_string', + 'db_server_info' => 'smf_db_libversion', + 'db_affected_rows' => 'smf_db_affected_rows', + 'db_transaction' => 'smf_db_transaction', + 'db_error' => 'smf_db_last_error', + 'db_select_db' => '', + 'db_title' => 'SQLite', + 'db_sybase' => true, + 'db_case_sensitive' => true, + 'db_escape_wildcard_string' => 'smf_db_escape_wildcard_string', + ); + + if (substr($db_name, -3) != '.db') + $db_name .= '.db'; + + if (!empty($db_options['persist'])) + $connection = @sqlite_popen($db_name, 0666, $sqlite_error); + else + $connection = @sqlite_open($db_name, 0666, $sqlite_error); + + // Something's wrong, show an error if its fatal (which we assume it is) + if (!$connection) + { + if (!empty($db_options['non_fatal'])) + return null; + else + db_fatal_error(); + } + $db_in_transact = false; + + // This is frankly stupid - stop SQLite returning alias names! + @sqlite_query('PRAGMA short_column_names = 1', $connection); + + // Make some user defined functions! + sqlite_create_function($connection, 'unix_timestamp', 'smf_udf_unix_timestamp', 0); + sqlite_create_function($connection, 'inet_aton', 'smf_udf_inet_aton', 1); + sqlite_create_function($connection, 'inet_ntoa', 'smf_udf_inet_ntoa', 1); + sqlite_create_function($connection, 'find_in_set', 'smf_udf_find_in_set', 2); + sqlite_create_function($connection, 'year', 'smf_udf_year', 1); + sqlite_create_function($connection, 'month', 'smf_udf_month', 1); + sqlite_create_function($connection, 'dayofmonth', 'smf_udf_dayofmonth', 1); + sqlite_create_function($connection, 'concat', 'smf_udf_concat'); + sqlite_create_function($connection, 'locate', 'smf_udf_locate', 2); + sqlite_create_function($connection, 'regexp', 'smf_udf_regexp', 2); + + return $connection; +} + +// Extend the database functionality. +function db_extend($type = 'extra') +{ + global $sourcedir, $db_type; + + require_once($sourcedir . '/Db' . strtoupper($type[0]) . substr($type, 1) . '-' . $db_type . '.php'); + $initFunc = 'db_' . $type . '_init'; + $initFunc(); +} + +// SQLite doesn't actually need this! +function db_fix_prefix(&$db_prefix, $db_name) +{ + return false; +} + +function smf_db_replacement__callback($matches) +{ + global $db_callback, $user_info, $db_prefix; + + list ($values, $connection) = $db_callback; + + if ($matches[1] === 'db_prefix') + return $db_prefix; + + if ($matches[1] === 'query_see_board') + return $user_info['query_see_board']; + + if ($matches[1] === 'query_wanna_see_board') + return $user_info['query_wanna_see_board']; + + if (!isset($matches[2])) + smf_db_error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__); + + if (!isset($values[$matches[2]])) + smf_db_error_backtrace('The database value you\'re trying to insert does not exist: ' . htmlspecialchars($matches[2]), '', E_USER_ERROR, __FILE__, __LINE__); + + $replacement = $values[$matches[2]]; + + switch ($matches[1]) + { + case 'int': + if (!is_numeric($replacement) || (string) $replacement !== (string) (int) $replacement) + smf_db_error_backtrace('Wrong value type sent to the database. Integer expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + return (string) (int) $replacement; + break; + + case 'string': + case 'text': + return sprintf('\'%1$s\'', sqlite_escape_string($replacement)); + break; + + case 'array_int': + if (is_array($replacement)) + { + if (empty($replacement)) + smf_db_error_backtrace('Database error, given array of integer values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + + foreach ($replacement as $key => $value) + { + if (!is_numeric($value) || (string) $value !== (string) (int) $value) + smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + + $replacement[$key] = (string) (int) $value; + } + + return implode(', ', $replacement); + } + else + smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + + break; + + case 'array_string': + if (is_array($replacement)) + { + if (empty($replacement)) + smf_db_error_backtrace('Database error, given array of string values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + + foreach ($replacement as $key => $value) + $replacement[$key] = sprintf('\'%1$s\'', sqlite_escape_string($value)); + + return implode(', ', $replacement); + } + else + smf_db_error_backtrace('Wrong value type sent to the database. Array of strings expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + break; + + case 'date': + if (preg_match('~^(\d{4})-([0-1]?\d)-([0-3]?\d)$~', $replacement, $date_matches) === 1) + return sprintf('\'%04d-%02d-%02d\'', $date_matches[1], $date_matches[2], $date_matches[3]); + else + smf_db_error_backtrace('Wrong value type sent to the database. Date expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + break; + + case 'float': + if (!is_numeric($replacement)) + smf_db_error_backtrace('Wrong value type sent to the database. Floating point number expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + return (string) (float) $replacement; + break; + + case 'identifier': + return '`' . strtr($replacement, array('`' => '', '.' => '')) . '`'; + break; + + case 'raw': + return $replacement; + break; + + default: + smf_db_error_backtrace('Undefined type used in the database query. (' . $matches[1] . ':' . $matches[2] . ')', '', false, __FILE__, __LINE__); + break; + } +} + +// Just like the db_query, escape and quote a string, but not executing the query. +function smf_db_quote($db_string, $db_values, $connection = null) +{ + global $db_callback, $db_connection; + + // Only bother if there's something to replace. + if (strpos($db_string, '{') !== false) + { + // This is needed by the callback function. + $db_callback = array($db_values, $connection == null ? $db_connection : $connection); + + // Do the quoting and escaping + $db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string); + + // Clear this global variable. + $db_callback = array(); + } + + return $db_string; +} + +// Do a query. Takes care of errors too. +function smf_db_query($identifier, $db_string, $db_values = array(), $connection = null) +{ + global $db_cache, $db_count, $db_connection, $db_show_debug, $time_start; + global $db_unbuffered, $db_callback, $modSettings; + + // Decide which connection to use. + $connection = $connection == null ? $db_connection : $connection; + + // Special queries that need processing. + $replacements = array( + 'birthday_array' => array( + '~DATE_FORMAT\(([^,]+),\s*([^\)]+)\s*\)~' => 'strftime($2, $1)' + ), + 'substring' => array( + '~SUBSTRING~' => 'SUBSTR', + ), + 'truncate_table' => array( + '~TRUNCATE~i' => 'DELETE FROM', + ), + 'user_activity_by_time' => array( + '~HOUR\(FROM_UNIXTIME\((poster_time\s+\+\s+\{int:.+\})\)\)~' => 'strftime(\'%H\', datetime($1, \'unixepoch\'))', + ), + 'unread_fetch_topic_count' => array( + '~\s*SELECT\sCOUNT\(DISTINCT\st\.id_topic\),\sMIN\(t\.id_last_msg\)(.+)$~is' => 'SELECT COUNT(id_topic), MIN(id_last_msg) FROM (SELECT DISTINCT t.id_topic, t.id_last_msg $1)', + ), + 'alter_table_boards' => array( + '~(.+)~' => '', + ), + 'get_random_number' => array( + '~RAND~' => 'RANDOM', + ), + 'set_character_set' => array( + '~(.+)~' => '', + ), + 'themes_count' => array( + '~\s*SELECT\sCOUNT\(DISTINCT\sid_member\)\sAS\svalue,\sid_theme.+FROM\s(.+themes)(.+)~is' => 'SELECT COUNT(id_member) AS value, id_theme FROM (SELECT DISTINCT id_member, id_theme, variable FROM $1) $2', + ), + 'attach_download_increase' => array( + '~LOW_PRIORITY~' => '', + ), + 'pm_conversation_list' => array( + '~ORDER BY id_pm~' => 'ORDER BY MAX(pm.id_pm)', + ), + 'boardindex_fetch_boards' => array( + '~(.)$~' => '$1 ORDER BY b.board_order', + ), + 'messageindex_fetch_boards' => array( + '~(.)$~' => '$1 ORDER BY b.board_order', + ), + 'order_by_board_order' => array( + '~(.)$~' => '$1 ORDER BY b.board_order', + ), + 'spider_check' => array( + '~(.)$~' => '$1 ORDER BY LENGTH(user_agent) DESC', + ), + ); + + if (isset($replacements[$identifier])) + $db_string = preg_replace(array_keys($replacements[$identifier]), array_values($replacements[$identifier]), $db_string); + + // SQLite doesn't support count(distinct). + $db_string = trim($db_string); + $db_string = preg_replace('~^\s*SELECT\s+?COUNT\(DISTINCT\s+?(.+?)\)(\s*AS\s*(.+?))*\s*(FROM.+)~is', 'SELECT COUNT(*) $2 FROM (SELECT DISTINCT $1 $4)', $db_string); + + // Or RLIKE. + $db_string = preg_replace('~AND\s*(.+?)\s*RLIKE\s*(\{string:.+?\})~', 'AND REGEXP(\1, \2)', $db_string); + + // INSTR? No support for that buddy :( + if (preg_match('~INSTR\((.+?),\s(.+?)\)~', $db_string, $matches) === 1) + { + $db_string = preg_replace('~INSTR\((.+?),\s(.+?)\)~', '$1 LIKE $2', $db_string); + list(, $search) = explode(':', substr($matches[2], 1, -1)); + $db_values[$search] = '%' . $db_values[$search] . '%'; + } + + // Lets remove ASC and DESC from GROUP BY clause. + if (preg_match('~GROUP BY .*? (?:ASC|DESC)~is', $db_string, $matches)) + { + $replace = str_replace(array('ASC', 'DESC'), '', $matches[0]); + $db_string = str_replace($matches[0], $replace, $db_string); + } + + // We need to replace the SUBSTRING in the sort identifier. + if ($identifier == 'substring_membergroups' && isset($db_values['sort'])) + $db_values['sort'] = preg_replace('~SUBSTRING~', 'SUBSTR', $db_values['sort']); + + // SQLite doesn't support TO_DAYS but has the julianday function which can be used in the same manner. But make sure it is being used to calculate a span. + $db_string = preg_replace('~\(TO_DAYS\(([^)]+)\) - TO_DAYS\(([^)]+)\)\) AS span~', '(julianday($1) - julianday($2)) AS span', $db_string); + + // One more query.... + $db_count = !isset($db_count) ? 1 : $db_count + 1; + + if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override'])) + smf_db_error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__); + + if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false)) + { + // Pass some values to the global space for use in the callback function. + $db_callback = array($db_values, $connection); + + // Inject the values passed to this function. + $db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string); + + // This shouldn't be residing in global space any longer. + $db_callback = array(); + } + + // Debugging. + if (isset($db_show_debug) && $db_show_debug === true) + { + // Get the file and line number this function was called. + list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__); + + // Initialize $db_cache if not already initialized. + if (!isset($db_cache)) + $db_cache = array(); + + if (!empty($_SESSION['debug_redirect'])) + { + $db_cache = array_merge($_SESSION['debug_redirect'], $db_cache); + $db_count = count($db_cache) + 1; + $_SESSION['debug_redirect'] = array(); + } + + $st = microtime(); + // Don't overload it. + $db_cache[$db_count]['q'] = $db_count < 50 ? $db_string : '...'; + $db_cache[$db_count]['f'] = $file; + $db_cache[$db_count]['l'] = $line; + $db_cache[$db_count]['s'] = array_sum(explode(' ', $st)) - array_sum(explode(' ', $time_start)); + } + + $ret = @sqlite_query($db_string, $connection, SQLITE_BOTH, $err_msg); + if ($ret === false && empty($db_values['db_error_skip'])) + $ret = smf_db_error($db_string . '#!#' . $err_msg, $connection); + + // Debugging. + if (isset($db_show_debug) && $db_show_debug === true) + $db_cache[$db_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st)); + + return $ret; +} + +function smf_db_affected_rows($connection = null) +{ + global $db_connection; + + return sqlite_changes($connection == null ? $db_connection : $connection); +} + +function smf_db_insert_id($table, $field = null, $connection = null) +{ + global $db_connection, $db_prefix; + + $table = str_replace('{db_prefix}', $db_prefix, $table); + + // SQLite doesn't need the table or field information. + return sqlite_last_insert_rowid($connection == null ? $db_connection : $connection); +} + +// Keeps the connection handle. +function smf_db_last_error() +{ + global $db_connection, $sqlite_error; + + $query_errno = sqlite_last_error($db_connection); + return $query_errno || empty($sqlite_error) ? sqlite_error_string($query_errno) : $sqlite_error; +} + +// Do a transaction. +function smf_db_transaction($type = 'commit', $connection = null) +{ + global $db_connection, $db_in_transact; + + // Decide which connection to use + $connection = $connection == null ? $db_connection : $connection; + + if ($type == 'begin') + { + $db_in_transact = true; + return @sqlite_query('BEGIN', $connection); + } + elseif ($type == 'rollback') + { + $db_in_transact = false; + return @sqlite_query('ROLLBACK', $connection); + } + elseif ($type == 'commit') + { + $db_in_transact = false; + return @sqlite_query('COMMIT', $connection); + } + + return false; +} + +// Database error! +function smf_db_error($db_string, $connection = null) +{ + global $txt, $context, $sourcedir, $webmaster_email, $modSettings; + global $forum_version, $db_connection, $db_last_error, $db_persist; + global $db_server, $db_user, $db_passwd, $db_name, $db_show_debug, $ssi_db_user, $ssi_db_passwd; + global $smcFunc; + + // We'll try recovering the file and line number the original db query was called from. + list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__); + + // Decide which connection to use + $connection = $connection == null ? $db_connection : $connection; + + // This is the error message... + $query_errno = sqlite_last_error($connection); + $query_error = sqlite_error_string($query_errno); + + // Get the extra error message. + $errStart = strrpos($db_string, '#!#'); + $query_error .= '
' . substr($db_string, $errStart + 3); + $db_string = substr($db_string, 0, $errStart); + + // Log the error. + if (function_exists('log_error')) + log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n" .$db_string : ''), 'database', $file, $line); + + // Sqlite optimizing - the actual error message isn't helpful or user friendly. + if (strpos($query_error, 'no_access') !== false || strpos($query_error, 'database schema has changed') !== false) + { + if (!empty($context) && !empty($txt) && !empty($txt['error_sqlite_optimizing'])) + fatal_error($txt['error_sqlite_optimizing'], false); + else + { + // Don't cache this page! + header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + header('Cache-Control: no-cache'); + + // Send the right error codes. + header('HTTP/1.1 503 Service Temporarily Unavailable'); + header('Status: 503 Service Temporarily Unavailable'); + header('Retry-After: 3600'); + + die('Sqlite is optimizing the database, the forum can not be accessed until it has finished. Please try refreshing this page momentarily.'); + } + } + + // Nothing's defined yet... just die with it. + if (empty($context) || empty($txt)) + die($query_error); + + // Show an error message, if possible. + $context['error_title'] = $txt['database_error']; + if (allowedTo('admin_forum')) + $context['error_message'] = nl2br($query_error) . '
' . $txt['file'] . ': ' . $file . '
' . $txt['line'] . ': ' . $line; + else + $context['error_message'] = $txt['try_again']; + + // A database error is often the sign of a database in need of updgrade. Check forum versions, and if not identical suggest an upgrade... (not for Demo/CVS versions!) + if (allowedTo('admin_forum') && !empty($forum_version) && $forum_version != 'SMF ' . @$modSettings['smfVersion'] && strpos($forum_version, 'Demo') === false && strpos($forum_version, 'CVS') === false) + $context['error_message'] .= '

' . sprintf($txt['database_error_versions'], $forum_version, $modSettings['smfVersion']); + + if (allowedTo('admin_forum') && isset($db_show_debug) && $db_show_debug === true) + { + $context['error_message'] .= '

' . nl2br($db_string); + } + + // It's already been logged... don't log it again. + fatal_error($context['error_message'], false); +} + +// Insert some data... +function smf_db_insert($method = 'replace', $table, $columns, $data, $keys, $disable_trans = false, $connection = null) +{ + global $db_in_transact, $db_connection, $smcFunc, $db_prefix; + + $connection = $connection === null ? $db_connection : $connection; + + if (empty($data)) + return; + + if (!is_array($data[array_rand($data)])) + $data = array($data); + + // Replace the prefix holder with the actual prefix. + $table = str_replace('{db_prefix}', $db_prefix, $table); + + $priv_trans = false; + if (count($data) > 1 && !$db_in_transact && !$disable_trans) + { + $smcFunc['db_transaction']('begin', $connection); + $priv_trans = true; + } + + if (!empty($data)) + { + // Create the mold for a single row insert. + $insertData = '('; + foreach ($columns as $columnName => $type) + { + // Are we restricting the length? + if (strpos($type, 'string-') !== false) + $insertData .= sprintf('SUBSTR({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName); + else + $insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName); + } + $insertData = substr($insertData, 0, -2) . ')'; + + // Create an array consisting of only the columns. + $indexed_columns = array_keys($columns); + + // Here's where the variables are injected to the query. + $insertRows = array(); + foreach ($data as $dataRow) + $insertRows[] = smf_db_quote($insertData, array_combine($indexed_columns, $dataRow), $connection); + + foreach ($insertRows as $entry) + // Do the insert. + $smcFunc['db_query']('', + (($method === 'replace') ? 'REPLACE' : (' INSERT' . ($method === 'ignore' ? ' OR IGNORE' : ''))) . ' INTO ' . $table . '(' . implode(', ', $indexed_columns) . ') + VALUES + ' . $entry, + array( + 'security_override' => true, + 'db_error_skip' => $table === $db_prefix . 'log_errors', + ), + $connection + ); + } + + if ($priv_trans) + $smcFunc['db_transaction']('commit', $connection); +} + +// Doesn't do anything on sqlite! +function smf_db_free_result($handle = false) +{ + return true; +} + +// Make sure we return no string indexes! +function smf_db_fetch_row($handle) +{ + return sqlite_fetch_array($handle, SQLITE_NUM); +} + +// Unescape an escaped string! +function smf_db_unescape_string($string) +{ + return strtr($string, array('\'\'' => '\'')); +} + +// This function tries to work out additional error information from a back trace. +function smf_db_error_backtrace($error_message, $log_message = '', $error_type = false, $file = null, $line = null) +{ + if (empty($log_message)) + $log_message = $error_message; + + if (function_exists('debug_backtrace')) + { + foreach (debug_backtrace() as $step) + { + // Found it? + if (strpos($step['function'], 'query') === false && !in_array(substr($step['function'], 0, 7), array('smf_db_', 'preg_re', 'db_erro', 'call_us')) && substr($step['function'], 0, 2) != '__') + { + $log_message .= '
Function: ' . $step['function']; + break; + } + + if (isset($step['line'])) + { + $file = $step['file']; + $line = $step['line']; + } + } + } + + // A special case - we want the file and line numbers for debugging. + if ($error_type == 'return') + return array($file, $line); + + // Is always a critical error. + if (function_exists('log_error')) + log_error($log_message, 'critical', $file, $line); + + if (function_exists('fatal_error')) + { + fatal_error($error_message, $error_type); + + // Cannot continue... + exit; + } + elseif ($error_type) + trigger_error($error_message . ($line !== null ? '(' . basename($file) . '-' . $line . ')' : ''), $error_type); + else + trigger_error($error_message . ($line !== null ? '(' . basename($file) . '-' . $line . ')' : '')); +} + +// Emulate UNIX_TIMESTAMP. +function smf_udf_unix_timestamp() +{ + return strftime('%s', 'now'); +} + +// Emulate INET_ATON. +function smf_udf_inet_aton($ip) +{ + $chunks = explode('.', $ip); + return @$chunks[0] * pow(256, 3) + @$chunks[1] * pow(256, 2) + @$chunks[2] * 256 + @$chunks[3]; +} + +// Emulate INET_NTOA. +function smf_udf_inet_ntoa($n) +{ + $t = array(0, 0, 0, 0); + $msk = 16777216.0; + $n += 0.0; + if ($n < 1) + return '0.0.0.0'; + + for ($i = 0; $i < 4; $i++) + { + $k = (int) ($n / $msk); + $n -= $msk * $k; + $t[$i] = $k; + $msk /= 256.0; + }; + + $a = join('.', $t); + return $a; +} + +// Emulate FIND_IN_SET. +function smf_udf_find_in_set($find, $groups) +{ + foreach (explode(',', $groups) as $key => $group) + { + if ($group == $find) + return $key + 1; + } + + return 0; +} + +// Emulate YEAR. +function smf_udf_year($date) +{ + return substr($date, 0, 4); +} + +// Emulate MONTH. +function smf_udf_month($date) +{ + return substr($date, 5, 2); +} + +// Emulate DAYOFMONTH. +function smf_udf_dayofmonth($date) +{ + return substr($date, 8, 2); +} + +// We need this since sqlite_libversion() doesn't take any parameters. +function smf_db_libversion($void = null) +{ + return sqlite_libversion(); +} + +// This function uses variable argument lists so that it can handle more then two parameters. +// Emulates the CONCAT function. +function smf_udf_concat() +{ + // Since we didn't specify any arguments we must get them from PHP. + $args = func_get_args(); + + // It really doesn't matter if there were 0 to 100 arguments, just slap them all together. + return implode('', $args); +} + +// We need to use PHP to locate the position in the string. +function smf_udf_locate($find, $string) +{ + return strpos($string, $find); +} + +// This is used to replace RLIKE. +function smf_udf_regexp($exp, $search) +{ + if (preg_match($exp, $match)) + return 1; + return 0; +} + +// Escape the LIKE wildcards so that they match the character and not the wildcard. +// The optional second parameter turns human readable wildcards into SQL wildcards. +function smf_db_escape_wildcard_string($string, $translate_human_wildcards=false) +{ + $replacements = array( + '%' => '\%', + '\\' => '\\\\', + ); + + if ($translate_human_wildcards) + $replacements += array( + '*' => '%', + ); + + return strtr($string, $replacements); +} +?> \ No newline at end of file diff --git a/Sources/Subs-Editor.php b/Sources/Subs-Editor.php new file mode 100644 index 0000000..45f5bc5 --- /dev/null +++ b/Sources/Subs-Editor.php @@ -0,0 +1,2171 @@ + ';', '#smlt#' => '<', '#smgt#' => '>', '#smamp#' => '&')); + $context['message'] = bbc_to_html($_REQUEST['message']); + } + else + { + $_REQUEST['message'] = un_htmlspecialchars($_REQUEST['message']); + $_REQUEST['message'] = strtr($_REQUEST['message'], array('#smcol#' => ';', '#smlt#' => '<', '#smgt#' => '>', '#smamp#' => '&')); + + $context['message'] = html_to_bbc($_REQUEST['message']); + } + + $context['message'] = $smcFunc['htmlspecialchars']($context['message']); +} + +// Convert only the BBC that can be edited in HTML mode for the editor. +function bbc_to_html($text) +{ + global $modSettings, $smcFunc; + + // Turn line breaks back into br's. + $text = strtr($text, array("\r" => '', "\n" => '
')); + + // Prevent conversion of all bbcode inside these bbcodes. + // !!! Tie in with bbc permissions ? + foreach (array('code', 'php', 'nobbc') as $code) + { + if (strpos($text, '['. $code) !== false) + { + $parts = preg_split('~(\[/' . $code . '\]|\[' . $code . '(?:=[^\]]+)?\])~i', $text, -1, PREG_SPLIT_DELIM_CAPTURE); + + // Only mess with stuff inside tags. + for ($i = 0, $n = count($parts); $i < $n; $i++) + { + // Value of 2 means we're inside the tag. + if ($i % 4 == 2) + $parts[$i] = strtr($parts[$i], array('[' => '[', ']' => ']', "'" => "'")); + } + // Put our humpty dumpty message back together again. + $text = implode('', $parts); + } + } + + // What tags do we allow? + $allowed_tags = array('b', 'u', 'i', 's', 'hr', 'list', 'li', 'font', 'size', 'color', 'img', 'left', 'center', 'right', 'url', 'email', 'ftp', 'sub', 'sup'); + + $text = parse_bbc($text, true, '', $allowed_tags); + + // Fix for having a line break then a thingy. + $text = strtr($text, array('
' '', "\r" => '')); + + // Note that IE doesn't understand spans really - make them something "legacy" + $working_html = array( + '~(.+?)~i' => '$1', + '~(.+?)~i' => '$1', + '~(.+?)~i' => '$2', + '~(.+?)~i' => '$2', + '~(.+?)~i' => '

$2

', + ); + $text = preg_replace(array_keys($working_html), array_values($working_html), $text); + + // Parse unique ID's and disable javascript into the smileys - using the double space. + $text = preg_replace_callback('~(?:\s| )?<(img\ssrc="' . preg_quote($modSettings['smileys_url'], '~') . '/[^<>]+?/([^<>]+?)"\s*)[^<>]*?class="smiley" />~', create_function('$m', 'static $i = 1; return \'<\' . ' . 'stripslashes($m[1]) . \'alt="" title="" onresizestart="return false;" id="smiley_\' . ' . "\$" . 'i++ . \'_\' . $m[2] . \'" style="padding: 0 3px 0 3px;" />\';'), $text); + + return $text; +} + +// The harder one - wysiwyg to BBC! +function html_to_bbc($text) +{ + global $modSettings, $smcFunc, $sourcedir, $scripturl, $context; + + // Replace newlines with spaces, as that's how browsers usually interpret them. + $text = preg_replace("~\s*[\r\n]+\s*~", ' ', $text); + + // Though some of us love paragraphs, the parser will do better with breaks. + $text = preg_replace('~

\s*?
\s*(?!<)~i', '


', $text); + + // Safari/webkit wraps lines in Wysiwyg in
's. + if ($context['browser']['is_webkit']) + $text = preg_replace(array('~]*?))?' . '>~i', '
'), array('
', ''), $text); + + // If there's a trailing break get rid of it - Firefox tends to add one. + $text = preg_replace('~$~i', '', $text); + + // Remove any formatting within code tags. + if (strpos($text, '[code') !== false) + { + $text = preg_replace('~~i', '#smf_br_spec_grudge_cool!#', $text); + $parts = preg_split('~(\[/code\]|\[code(?:=[^\]]+)?\])~i', $text, -1, PREG_SPLIT_DELIM_CAPTURE); + + // Only mess with stuff outside [code] tags. + for ($i = 0, $n = count($parts); $i < $n; $i++) + { + // Value of 2 means we're inside the tag. + if ($i % 4 == 2) + $parts[$i] = strip_tags($parts[$i]); + } + + $text = strtr(implode('', $parts), array('#smf_br_spec_grudge_cool!#' => '
')); + } + + // Remove scripts, style and comment blocks. + $text = preg_replace('~]*[^/]?' . '>.*?~i', '', $text); + $text = preg_replace('~]*[^/]?' . '>.*?~i', '', $text); + $text = preg_replace('~\\<\\!--.*?-->~i', '', $text); + $text = preg_replace('~\\<\\!\\[CDATA\\[.*?\\]\\]\\>~i', '', $text); + + // Do the smileys ultra first! + preg_match_all('~]*?id="*smiley_\d+_([^<>]+?)[\s"/>]\s*[^<>]*?/*>(?:\s)?~i', $text, $matches); + if (!empty($matches[0])) + { + // Easy if it's not custom. + if (empty($modSettings['smiley_enable'])) + { + $smileysfrom = array('>:D', ':D', '::)', '>:(', ':)', ';)', ';D', ':(', ':o', '8)', ':P', '???', ':-[', ':-X', ':-*', ':\'(', ':-\\', '^-^', 'O0', 'C:-)', '0:)'); + $smileysto = array('evil.gif', 'cheesy.gif', 'rolleyes.gif', 'angry.gif', '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'); + + foreach ($matches[1] as $k => $file) + { + $found = array_search($file, $smileysto); + // Note the weirdness here is to stop double spaces between smileys. + if ($found) + $matches[1][$k] = '-[]-smf_smily_start#|#' . htmlspecialchars($smileysfrom[$found]) . '-[]-smf_smily_end#|#'; + else + $matches[1][$k] = ''; + } + } + else + { + // Load all the smileys. + $names = array(); + foreach ($matches[1] as $file) + $names[] = $file; + $names = array_unique($names); + + if (!empty($names)) + { + $request = $smcFunc['db_query']('', ' + SELECT code, filename + FROM {db_prefix}smileys + WHERE filename IN ({array_string:smiley_filenames})', + array( + 'smiley_filenames' => $names, + ) + ); + $mappings = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $mappings[$row['filename']] = htmlspecialchars($row['code']); + $smcFunc['db_free_result']($request); + + foreach ($matches[1] as $k => $file) + if (isset($mappings[$file])) + $matches[1][$k] = '-[]-smf_smily_start#|#' . $mappings[$file] . '-[]-smf_smily_end#|#'; + } + } + + // Replace the tags! + $text = str_replace($matches[0], $matches[1], $text); + + // Now sort out spaces + $text = str_replace(array('-[]-smf_smily_end#|#-[]-smf_smily_start#|#', '-[]-smf_smily_end#|#', '-[]-smf_smily_start#|#'), ' ', $text); + } + + // Only try to buy more time if the client didn't quit. + if (connection_aborted() && $context['server']['is_apache']) + @apache_reset_timeout(); + + $parts = preg_split('~(<[A-Za-z]+\s*[^<>]*?style="?[^<>"]+"?[^<>]*?(?:/?)>|)~', $text, -1, PREG_SPLIT_DELIM_CAPTURE); + $replacement = ''; + $stack = array(); + + foreach ($parts as $part) + { + if (preg_match('~(<([A-Za-z]+)\s*[^<>]*?)style="?([^<>"]+)"?([^<>]*?(/?)>)~', $part, $matches) === 1) + { + // If it's being closed instantly, we can't deal with it...yet. + if ($matches[5] === '/') + continue; + else + { + // Get an array of styles that apply to this element. (The strtr is there to combat HTML generated by Word.) + $styles = explode(';', strtr($matches[3], array('"' => ''))); + $curElement = $matches[2]; + $precedingStyle = $matches[1]; + $afterStyle = $matches[4]; + $curCloseTags = ''; + $extra_attr = ''; + + foreach ($styles as $type_value_pair) + { + // Remove spaces and convert uppercase letters. + $clean_type_value_pair = strtolower(strtr(trim($type_value_pair), '=', ':')); + + // Something like 'font-weight: bold' is expected here. + if (strpos($clean_type_value_pair, ':') === false) + continue; + + // Capture the elements of a single style item (e.g. 'font-weight' and 'bold'). + list ($style_type, $style_value) = explode(':', $type_value_pair); + + $style_value = trim($style_value); + + switch (trim($style_type)) + { + case 'font-weight': + if ($style_value === 'bold') + { + $curCloseTags .= '[/b]'; + $replacement .= '[b]'; + } + break; + + case 'text-decoration': + if ($style_value == 'underline') + { + $curCloseTags .= '[/u]'; + $replacement .= '[u]'; + } + elseif ($style_value == 'line-through') + { + $curCloseTags .= '[/s]'; + $replacement .= '[s]'; + } + break; + + case 'text-align': + if ($style_value == 'left') + { + $curCloseTags .= '[/left]'; + $replacement .= '[left]'; + } + elseif ($style_value == 'center') + { + $curCloseTags .= '[/center]'; + $replacement .= '[center]'; + } + elseif ($style_value == 'right') + { + $curCloseTags .= '[/right]'; + $replacement .= '[right]'; + } + break; + + case 'font-style': + if ($style_value == 'italic') + { + $curCloseTags .= '[/i]'; + $replacement .= '[i]'; + } + break; + + case 'color': + $curCloseTags .= '[/color]'; + $replacement .= '[color=' . $style_value . ']'; + break; + + case 'font-size': + // Sometimes people put decimals where decimals should not be. + if (preg_match('~(\d)+\.\d+(p[xt])~i', $style_value, $dec_matches) === 1) + $style_value = $dec_matches[1] . $dec_matches[2]; + + $curCloseTags .= '[/size]'; + $replacement .= '[size=' . $style_value . ']'; + break; + + case 'font-family': + // Only get the first freaking font if there's a list! + if (strpos($style_value, ',') !== false) + $style_value = substr($style_value, 0, strpos($style_value, ',')); + + $curCloseTags .= '[/font]'; + $replacement .= '[font=' . strtr($style_value, array("'" => '')) . ']'; + break; + + // This is a hack for images with dimensions embedded. + case 'width': + case 'height': + if (preg_match('~[1-9]\d*~i', $style_value, $dimension) === 1) + $extra_attr .= ' ' . $style_type . '="' . $dimension[0] . '"'; + break; + + case 'list-style-type': + if (preg_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~i', $style_value, $listType) === 1) + $extra_attr .= ' listtype="' . $listType[0] . '"'; + break; + } + } + + // Preserve some tags stripping the styling. + if (in_array($matches[2], array('a', 'font'))) + { + $replacement .= $precedingStyle . $afterStyle; + $curCloseTags = '' . $curCloseTags; + } + + // If there's something that still needs closing, push it to the stack. + if (!empty($curCloseTags)) + array_push($stack, array( + 'element' => strtolower($curElement), + 'closeTags' => $curCloseTags + ) + ); + elseif (!empty($extra_attr)) + $replacement .= $precedingStyle . $extra_attr . $afterStyle; + } + } + + elseif (preg_match('~~', $part, $matches) === 1) + { + // Is this the element that we've been waiting for to be closed? + if (!empty($stack) && strtolower($matches[1]) === $stack[count($stack) - 1]['element']) + { + $byebyeTag = array_pop($stack); + $replacement .= $byebyeTag['closeTags']; + } + + // Must've been something else. + else + $replacement .= $part; + } + // In all other cases, just add the part to the replacement. + else + $replacement .= $part; + } + + // Now put back the replacement in the text. + $text = $replacement; + + // We are not finished yet, request more time. + if (connection_aborted() && $context['server']['is_apache']) + @apache_reset_timeout(); + + // Let's pull out any legacy alignments. + while (preg_match('~<([A-Za-z]+)\s+[^<>]*?(align="*(left|center|right)"*)[^<>]*?(/?)>~i', $text, $matches) === 1) + { + // Find the position in the text of this tag over again. + $start_pos = strpos($text, $matches[0]); + if ($start_pos === false) + break; + + // End tag? + if ($matches[4] != '/' && strpos($text, '', $start_pos) !== false) + { + $end_length = strlen(''); + $end_pos = strpos($text, '', $start_pos); + + // Remove the align from that tag so it's never checked again. + $tag = substr($text, $start_pos, strlen($matches[0])); + $content = substr($text, $start_pos + strlen($matches[0]), $end_pos - $start_pos - strlen($matches[0])); + $tag = str_replace($matches[2], '', $tag); + + // Put the tags back into the body. + $text = substr($text, 0, $start_pos) . $tag . '[' . $matches[3] . ']' . $content . '[/' . $matches[3] . ']' . substr($text, $end_pos); + } + else + { + // Just get rid of this evil tag. + $text = substr($text, 0, $start_pos) . substr($text, $start_pos + strlen($matches[0])); + } + } + + // Let's do some special stuff for fonts - cause we all love fonts. + while (preg_match('~]*)>~i', $text, $matches) === 1) + { + // Find the position of this again. + $start_pos = strpos($text, $matches[0]); + $end_pos = false; + if ($start_pos === false) + break; + + // This must have an end tag - and we must find the right one. + $lower_text = strtolower($text); + + $start_pos_test = $start_pos + 4; + // How many starting tags must we find closing ones for first? + $start_font_tag_stack = 0; + while ($start_pos_test < strlen($text)) + { + // Where is the next starting font? + $next_start_pos = strpos($lower_text, '', $start_pos_test); + + // Did we past another starting tag before an end one? + if ($next_start_pos !== false && $next_start_pos < $next_end_pos) + { + $start_font_tag_stack++; + $start_pos_test = $next_start_pos + 4; + } + // Otherwise we have an end tag but not the right one? + elseif ($start_font_tag_stack) + { + $start_font_tag_stack--; + $start_pos_test = $next_end_pos + 4; + } + // Otherwise we're there! + else + { + $end_pos = $next_end_pos; + break; + } + } + if ($end_pos === false) + break; + + // Now work out what the attributes are. + $attribs = fetchTagAttributes($matches[1]); + $tags = array(); + foreach ($attribs as $s => $v) + { + if ($s == 'size') + $tags[] = array('[size=' . (int) trim($v) . ']', '[/size]'); + elseif ($s == 'face') + $tags[] = array('[font=' . trim(strtolower($v)) . ']', '[/font]'); + elseif ($s == 'color') + $tags[] = array('[color=' . trim(strtolower($v)) . ']', '[/color]'); + } + + // As before add in our tags. + $before = $after = ''; + foreach ($tags as $tag) + { + $before .= $tag[0]; + if (isset($tag[1])) + $after = $tag[1] . $after; + } + + // Remove the tag so it's never checked again. + $content = substr($text, $start_pos + strlen($matches[0]), $end_pos - $start_pos - strlen($matches[0])); + + // Put the tags back into the body. + $text = substr($text, 0, $start_pos) . $before . $content . $after . substr($text, $end_pos + 7); + } + + // Almost there, just a little more time. + if (connection_aborted() && $context['server']['is_apache']) + @apache_reset_timeout(); + + if (count($parts = preg_split('~<(/?)(li|ol|ul)([^>]*)>~i', $text, null, PREG_SPLIT_DELIM_CAPTURE)) > 1) + { + // A toggle that dermines whether we're directly under a
    or
      . + $inList = false; + + // Keep track of the number of nested list levels. + $listDepth = 0; + + // Map what we can expect from the HTML to what is supported by SMF. + $listTypeMapping = array( + '1' => 'decimal', + 'A' => 'upper-alpha', + 'a' => 'lower-alpha', + 'I' => 'upper-roman', + 'i' => 'lower-roman', + 'disc' => 'disc', + 'square' => 'square', + 'circle' => 'circle', + ); + + // $i: text, $i + 1: '/', $i + 2: tag, $i + 3: tail. + for ($i = 0, $numParts = count($parts) - 1; $i < $numParts; $i += 4) + { + $tag = strtolower($parts[$i + 2]); + $isOpeningTag = $parts[$i + 1] === ''; + + if ($isOpeningTag) + { + switch ($tag) + { + case 'ol': + case 'ul': + + // We have a problem, we're already in a list. + if ($inList) + { + // Inject a list opener, we'll deal with the ol/ul next loop. + array_splice($parts, $i, 0, array( + '', + '', + str_repeat("\t", $listDepth) . '[li]', + '', + )); + $numParts = count($parts) - 1; + + // The inlist status changes a bit. + $inList = false; + } + + // Just starting a new list. + else + { + $inList = true; + + if ($tag === 'ol') + $listType = 'decimal'; + elseif (preg_match('~type="?(' . implode('|', array_keys($listTypeMapping)) . ')"?~', $parts[$i + 3], $match) === 1) + $listType = $listTypeMapping[$match[1]]; + else + $listType = null; + + $listDepth++; + + $parts[$i + 2] = '[list' . ($listType === null ? '' : ' type=' . $listType) . ']' . "\n"; + $parts[$i + 3] = ''; + } + break; + + case 'li': + + // This is how it should be: a list item inside the list. + if ($inList) + { + $parts[$i + 2] = str_repeat("\t", $listDepth) . '[li]'; + $parts[$i + 3] = ''; + + // Within a list item, it's almost as if you're outside. + $inList = false; + } + + // The li is no direct child of a list. + else + { + // We are apparently in a list item. + if ($listDepth > 0) + { + $parts[$i + 2] = '[/li]' . "\n" . str_repeat("\t", $listDepth) . '[li]'; + $parts[$i + 3] = ''; + } + + // We're not even near a list. + else + { + // Quickly create a list with an item. + $listDepth++; + + $parts[$i + 2] = '[list]' . "\n\t" . '[li]'; + $parts[$i + 3] = ''; + } + } + + break; + } + } + + // Handle all the closing tags. + else + { + switch ($tag) + { + case 'ol': + case 'ul': + + // As we expected it, closing the list while we're in it. + if ($inList) + { + $inList = false; + + $listDepth--; + + $parts[$i + 1] = ''; + $parts[$i + 2] = str_repeat("\t", $listDepth) . '[/list]'; + $parts[$i + 3] = ''; + } + + else + { + // We're in a list item. + if ($listDepth > 0) + { + // Inject closure for this list item first. + // The content of $parts[$i] is left as is! + array_splice($parts, $i + 1, 0, array( + '', // $i + 1 + '[/li]' . "\n", // $i + 2 + '', // $i + 3 + '', // $i + 4 + )); + $numParts = count($parts) - 1; + + // Now that we've closed the li, we're in list space. + $inList = true; + } + + // We're not even in a list, ignore + else + { + $parts[$i + 1] = ''; + $parts[$i + 2] = ''; + $parts[$i + 3] = ''; + } + } + break; + + case 'li': + + if ($inList) + { + // There's no use for a after
        or
          , ignore. + $parts[$i + 1] = ''; + $parts[$i + 2] = ''; + $parts[$i + 3] = ''; + } + + else + { + // Remove the trailing breaks from the list item. + $parts[$i] = preg_replace('~\s*\s*$~', '', $parts[$i]); + $parts[$i + 1] = ''; + $parts[$i + 2] = '[/li]' . "\n"; + $parts[$i + 3] = ''; + + // And we're back in the [list] space. + $inList = true; + } + + break; + } + } + + // If we're in the [list] space, no content is allowed. + if ($inList && trim(preg_replace('~\s*\s*~', '', $parts[$i + 4])) !== '') + { + // Fix it by injecting an extra list item. + array_splice($parts, $i + 4, 0, array( + '', // No content. + '', // Opening tag. + 'li', // It's a
        • . + '', // No tail. + )); + $numParts = count($parts) - 1; + } + } + + $text = implode('', $parts); + + if ($inList) + { + $listDepth--; + $text .= str_repeat("\t", $listDepth) . '[/list]'; + } + + for ($i = $listDepth; $i > 0; $i--) + $text .= '[/li]' . "\n" . str_repeat("\t", $i - 1) . '[/list]'; + + } + + // I love my own image... + while (preg_match('~]*)/*>~i', $text, $matches) === 1) + { + // Find the position of the image. + $start_pos = strpos($text, $matches[0]); + if ($start_pos === false) + break; + $end_pos = $start_pos + strlen($matches[0]); + + $params = ''; + $had_params = array(); + $src = ''; + + $attrs = fetchTagAttributes($matches[1]); + foreach ($attrs as $attrib => $value) + { + if (in_array($attrib, array('width', 'height'))) + $params .= ' ' . $attrib . '=' . (int) $value; + elseif ($attrib == 'alt' && trim($value) != '') + $params .= ' alt=' . trim($value); + elseif ($attrib == 'src') + $src = trim($value); + } + + $tag = ''; + if (!empty($src)) + { + // Attempt to fix the path in case it's not present. + if (preg_match('~^https?://~i', $src) === 0 && is_array($parsedURL = parse_url($scripturl)) && isset($parsedURL['host'])) + { + $baseURL = (isset($parsedURL['scheme']) ? $parsedURL['scheme'] : 'http') . '://' . $parsedURL['host'] . (empty($parsedURL['port']) ? '' : ':' . $parsedURL['port']); + + if (substr($src, 0, 1) === '/') + $src = $baseURL . $src; + else + $src = $baseURL . (empty($parsedURL['path']) ? '/' : preg_replace('~/(?:index\\.php)?$~', '', $parsedURL['path'])) . '/' . $src; + } + + $tag = '[img' . $params . ']' . $src . '[/img]'; + } + + // Replace the tag + $text = substr($text, 0, $start_pos) . $tag . substr($text, $end_pos); + } + + // The final bits are the easy ones - tags which map to tags which map to tags - etc etc. + $tags = array( + '~~i' => '[b]', + '~~i' => '[/b]', + '~~i' => '[i]', + '~~i' => '[/i]', + '~~i' => '[u]', + '~~i' => '[/u]', + '~~i' => '[b]', + '~~i' => '[/b]', + '~~i' => '[i]', + '~~i' => '[/i]', + '~~i' => "[s]", + '~~i' => "[/s]", + '~~i' => '[s]', + '~~i' => '[/s]', + '~~i' => '[s]', + '~~i' => '[/s]', + '~~i' => '[center]', + '~~i' => '[/center]', + '~~i' => '[pre]', + '~~i' => '[/pre]', + '~~i' => '[sub]', + '~~i' => '[/sub]', + '~~i' => '[sup]', + '~~i' => '[/sup]', + '~~i' => '[tt]', + '~~i' => '[/tt]', + '~~i' => '[table]', + '~~i' => '[/table]', + '~~i' => '[tr]', + '~~i' => '[/tr]', + '~<(td|th)(\s(.)*?)*?' . '>~i' => '[td]', + '~~i' => '[/td]', + '~]*?)?' . '>~i' => "\n", + '~]*>(\n)?~i' => "[hr]\n$1", + '~(\n)?\\[hr\\]~i' => "\n[hr]", + '~^\n\\[hr\\]~i' => "[hr]", + '~~i' => "<blockquote>", + '~~i' => "</blockquote>", + '~~i' => "<ins>", + '~~i' => "</ins>", + ); + $text = preg_replace_callback('~<(td|th)\s[^<>]*?colspan="?(\d{1,2})"?.*?' . '>~i', create_function('$m', 'return str_repeat(\'[td][/td]\', $m[2] - 1) . \'[td]\';'), $text); + $text = preg_replace(array_keys($tags), array_values($tags), $text); + + // Please give us just a little more time. + if (connection_aborted() && $context['server']['is_apache']) + @apache_reset_timeout(); + + // What about URL's - the pain in the ass of the tag world. + while (preg_match('~]*)>([^<>]*)~i', $text, $matches) === 1) + { + // Find the position of the URL. + $start_pos = strpos($text, $matches[0]); + if ($start_pos === false) + break; + $end_pos = $start_pos + strlen($matches[0]); + + $tag_type = 'url'; + $href = ''; + + $attrs = fetchTagAttributes($matches[1]); + foreach ($attrs as $attrib => $value) + { + if ($attrib == 'href') + { + $href = trim($value); + + // Are we dealing with an FTP link? + if (preg_match('~^ftps?://~', $href) === 1) + $tag_type = 'ftp'; + + // Or is this a link to an email address? + elseif (substr($href, 0, 7) == 'mailto:') + { + $tag_type = 'email'; + $href = substr($href, 7); + } + + // No http(s), so attempt to fix this potential relative URL. + elseif (preg_match('~^https?://~i', $href) === 0 && is_array($parsedURL = parse_url($scripturl)) && isset($parsedURL['host'])) + { + $baseURL = (isset($parsedURL['scheme']) ? $parsedURL['scheme'] : 'http') . '://' . $parsedURL['host'] . (empty($parsedURL['port']) ? '' : ':' . $parsedURL['port']); + + if (substr($href, 0, 1) === '/') + $href = $baseURL . $href; + else + $href = $baseURL . (empty($parsedURL['path']) ? '/' : preg_replace('~/(?:index\\.php)?$~', '', $parsedURL['path'])) . '/' . $href; + } + } + + // External URL? + if ($attrib == 'target' && $tag_type == 'url') + { + if (trim($value) == '_blank') + $tag_type == 'iurl'; + } + } + + $tag = ''; + if ($href != '') + { + if ($matches[2] == $href) + $tag = '[' . $tag_type . ']' . $href . '[/' . $tag_type . ']'; + else + $tag = '[' . $tag_type . '=' . $href . ']' . $matches[2] . '[/' . $tag_type . ']'; + } + + // Replace the tag + $text = substr($text, 0, $start_pos) . $tag . substr($text, $end_pos); + } + + $text = strip_tags($text); + + // Some tags often end up as just dummy tags - remove those. + $text = preg_replace('~\[[bisu]\]\s*\[/[bisu]\]~', '', $text); + + // Fix up entities. + $text = preg_replace('~&~i', '&#38;', $text); + + $text = legalise_bbc($text); + + return $text; +} + +// Returns an array of attributes associated with a tag. +function fetchTagAttributes($text) +{ + $attribs = array(); + $key = $value = ''; + $strpos = 0; + $tag_state = 0; // 0 = key, 1 = attribute with no string, 2 = attribute with string + for ($i = 0; $i < strlen($text); $i++) + { + // We're either moving from the key to the attribute or we're in a string and this is fine. + if ($text{$i} == '=') + { + if ($tag_state == 0) + $tag_state = 1; + elseif ($tag_state == 2) + $value .= '='; + } + // A space is either moving from an attribute back to a potential key or in a string is fine. + elseif ($text{$i} == ' ') + { + if ($tag_state == 2) + $value .= ' '; + elseif ($tag_state == 1) + { + $attribs[$key] = $value; + $key = $value = ''; + $tag_state = 0; + } + } + // A quote? + elseif ($text{$i} == '"') + { + // Must be either going into or out of a string. + if ($tag_state == 1) + $tag_state = 2; + else + $tag_state = 1; + } + // Otherwise it's fine. + else + { + if ($tag_state == 0) + $key .= $text{$i}; + else + $value .= $text{$i}; + } + } + + // Anything left? + if ($key != '' && $value != '') + $attribs[$key] = $value; + + return $attribs; +} + +function getMessageIcons($board_id) +{ + global $modSettings, $context, $txt, $settings, $smcFunc; + + if (empty($modSettings['messageIcons_enable'])) + { + loadLanguage('Post'); + + $icons = array( + array('value' => 'xx', 'name' => $txt['standard']), + array('value' => 'thumbup', 'name' => $txt['thumbs_up']), + array('value' => 'thumbdown', 'name' => $txt['thumbs_down']), + array('value' => 'exclamation', 'name' => $txt['excamation_point']), + array('value' => 'question', 'name' => $txt['question_mark']), + array('value' => 'lamp', 'name' => $txt['lamp']), + array('value' => 'smiley', 'name' => $txt['icon_smiley']), + array('value' => 'angry', 'name' => $txt['icon_angry']), + array('value' => 'cheesy', 'name' => $txt['icon_cheesy']), + array('value' => 'grin', 'name' => $txt['icon_grin']), + array('value' => 'sad', 'name' => $txt['icon_sad']), + array('value' => 'wink', 'name' => $txt['icon_wink']) + ); + + foreach ($icons as $k => $dummy) + { + $icons[$k]['url'] = $settings['images_url'] . '/post/' . $dummy['value'] . '.gif'; + $icons[$k]['is_last'] = false; + } + } + // Otherwise load the icons, and check we give the right image too... + else + { + if (($temp = cache_get_data('posting_icons-' . $board_id, 480)) == null) + { + $request = $smcFunc['db_query']('select_message_icons', ' + SELECT title, filename + FROM {db_prefix}message_icons + WHERE id_board IN (0, {int:board_id})', + array( + 'board_id' => $board_id, + ) + ); + $icon_data = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $icon_data[] = $row; + $smcFunc['db_free_result']($request); + + cache_put_data('posting_icons-' . $board_id, $icon_data, 480); + } + else + $icon_data = $temp; + + $icons = array(); + foreach ($icon_data as $icon) + { + $icons[$icon['filename']] = array( + 'value' => $icon['filename'], + 'name' => $icon['title'], + 'url' => $settings[file_exists($settings['theme_dir'] . '/images/post/' . $icon['filename'] . '.gif') ? 'images_url' : 'default_images_url'] . '/post/' . $icon['filename'] . '.gif', + 'is_last' => false, + ); + } + } + + return array_values($icons); +} + +// This is an important yet frustrating function - it attempts to clean up illegal BBC caused by browsers like Opera which don't obey the rules!!! +function legalise_bbc($text) +{ + global $modSettings; + + // Don't care about the texts that are too short. + if (strlen($text) < 3) + return $text; + + // We are going to cycle through the BBC and keep track of tags as they arise - in order. If get to a block level tag we're going to make sure it's not in a non-block level tag! + // This will keep the order of tags that are open. + $current_tags = array(); + + // This will quickly let us see if the tag is active. + $active_tags = array(); + + // A list of tags that's disabled by the admin. + $disabled = empty($modSettings['disabledBBC']) ? array() : array_flip(explode(',', strtolower($modSettings['disabledBBC']))); + + // Add flash if it's disabled as embedded tag. + if (empty($modSettings['enableEmbeddedFlash'])) + $disabled['flash'] = true; + + // Get a list of all the tags that are not disabled. + $all_tags = parse_bbc(false); + $valid_tags = array(); + $self_closing_tags = array(); + foreach ($all_tags as $tag) + { + if (!isset($disabled[$tag['tag']])) + $valid_tags[$tag['tag']] = !empty($tag['block_level']); + if (isset($tag['type']) && $tag['type'] == 'closed') + $self_closing_tags[] = $tag['tag']; + } + + // Don't worry if we're in a code/nobbc. + $in_code_nobbc = false; + + // Right - we're going to start by going through the whole lot to make sure we don't have align stuff crossed as this happens load and is stupid! + $align_tags = array('left', 'center', 'right', 'pre'); + + // Remove those align tags that are not valid. + $align_tags = array_intersect($align_tags, array_keys($valid_tags)); + + // These keep track of where we are! + if (!empty($align_tags) && count($matches = preg_split('~(\\[/?(?:' . implode('|', $align_tags) . ')\\])~', $text, -1, PREG_SPLIT_DELIM_CAPTURE)) > 1) + { + // The first one is never a tag. + $isTag = false; + + // By default we're not inside a tag too. + $insideTag = null; + + foreach ($matches as $i => $match) + { + // We're only interested in tags, not text. + if ($isTag) + { + $isClosingTag = substr($match, 1, 1) === '/'; + $tagName = substr($match, $isClosingTag ? 2 : 1, -1); + + // We're closing the exact same tag that we opened. + if ($isClosingTag && $insideTag === $tagName) + $insideTag = null; + + // We're opening a tag and we're not yet inside one either + elseif (!$isClosingTag && $insideTag === null) + $insideTag = $tagName; + + // In all other cases, this tag must be invalid + else + unset($matches[$i]); + } + + // The next one is gonna be the other one. + $isTag = !$isTag; + } + + // We're still inside a tag and had no chance for closure? + if ($insideTag !== null) + $matches[] = '[/' . $insideTag . ']'; + + // And a complete text string again. + $text = implode('', $matches); + } + + // Quickly remove any tags which are back to back. + $backToBackPattern = '~\\[(' . implode('|', array_diff(array_keys($valid_tags), array('td', 'anchor'))) . ')[^<>\\[\\]]*\\]\s*\\[/\\1\\]~'; + $lastlen = 0; + while (strlen($text) !== $lastlen) + $lastlen = strlen($text = preg_replace($backToBackPattern, '', $text)); + + // Need to sort the tags my name length. + uksort($valid_tags, 'sort_array_length'); + + // These inline tags can compete with each other regarding style. + $competing_tags = array( + 'color', + 'size', + ); + + // In case things changed above set these back to normal. + $in_code_nobbc = false; + $new_text_offset = 0; + + // These keep track of where we are! + if (count($parts = preg_split(sprintf('~(\\[)(/?)(%1$s)((?:[\\s=][^\\]\\[]*)?\\])~', implode('|', array_keys($valid_tags))), $text, -1, PREG_SPLIT_DELIM_CAPTURE)) > 1) + { + // Start with just text. + $isTag = false; + + // Start outside [nobbc] or [code] blocks. + $inCode = false; + $inNoBbc = false; + + // A buffer containing all opened inline elements. + $inlineElements = array(); + + // A buffer containing all opened block elements. + $blockElements = array(); + + // A buffer containing the opened inline elements that might compete. + $competingElements = array(); + + // $i: text, $i + 1: '[', $i + 2: '/', $i + 3: tag, $i + 4: tag tail. + for ($i = 0, $n = count($parts) - 1; $i < $n; $i += 5) + { + $tag = $parts[$i + 3]; + $isOpeningTag = $parts[$i + 2] === ''; + $isClosingTag = $parts[$i + 2] === '/'; + $isBlockLevelTag = isset($valid_tags[$tag]) && $valid_tags[$tag] && !in_array($tag, $self_closing_tags); + $isCompetingTag = in_array($tag, $competing_tags); + + // Check if this might be one of those cleaned out tags. + if ($tag === '') + continue; + + // Special case: inside [code] blocks any code is left untouched. + elseif ($tag === 'code') + { + // We're inside a code block and closing it. + if ($inCode && $isClosingTag) + { + $inCode = false; + + // Reopen tags that were closed before the code block. + if (!empty($inlineElements)) + $parts[$i + 4] .= '[' . implode('][', array_keys($inlineElements)) . ']'; + } + + // We're outside a coding and nobbc block and opening it. + elseif (!$inCode && !$inNoBbc && $isOpeningTag) + { + // If there are still inline elements left open, close them now. + if (!empty($inlineElements)) + { + $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']'; + //$inlineElements = array(); + } + + $inCode = true; + } + + // Nothing further to do. + continue; + } + + // Special case: inside [nobbc] blocks any BBC is left untouched. + elseif ($tag === 'nobbc') + { + // We're inside a nobbc block and closing it. + if ($inNoBbc && $isClosingTag) + { + $inNoBbc = false; + + // Some inline elements might've been closed that need reopening. + if (!empty($inlineElements)) + $parts[$i + 4] .= '[' . implode('][', array_keys($inlineElements)) . ']'; + } + + // We're outside a nobbc and coding block and opening it. + elseif (!$inNoBbc && !$inCode && $isOpeningTag) + { + // Can't have inline elements still opened. + if (!empty($inlineElements)) + { + $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']'; + //$inlineElements = array(); + } + + $inNoBbc = true; + } + + continue; + } + + // So, we're inside one of the special blocks: ignore any tag. + elseif ($inCode || $inNoBbc) + continue; + + // We're dealing with an opening tag. + if ($isOpeningTag) + { + // Everyting inside the square brackets of the opening tag. + $elementContent = $parts[$i + 3] . substr($parts[$i + 4], 0, -1); + + // A block level opening tag. + if ($isBlockLevelTag) + { + // Are there inline elements still open? + if (!empty($inlineElements)) + { + // Close all the inline tags, a block tag is coming... + $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']'; + + // Now open them again, we're inside the block tag now. + $parts[$i + 5] = '[' . implode('][', array_keys($inlineElements)) . ']' . $parts[$i + 5]; + } + + $blockElements[] = $tag; + } + + // Inline opening tag. + elseif (!in_array($tag, $self_closing_tags)) + { + // Can't have two opening elements with the same contents! + if (isset($inlineElements[$elementContent])) + { + // Get rid of this tag. + $parts[$i + 1] = $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = ''; + + // Now try to find the corresponding closing tag. + $curLevel = 1; + for ($j = $i + 5, $m = count($parts) - 1; $j < $m; $j += 5) + { + // Find the tags with the same tagname + if ($parts[$j + 3] === $tag) + { + // If it's an opening tag, increase the level. + if ($parts[$j + 2] === '') + $curLevel++; + + // A closing tag, decrease the level. + else + { + $curLevel--; + + // Gotcha! Clean out this closing tag gone rogue. + if ($curLevel === 0) + { + $parts[$j + 1] = $parts[$j + 2] = $parts[$j + 3] = $parts[$j + 4] = ''; + break; + } + } + } + } + } + + // Otherwise, add this one to the list. + else + { + if ($isCompetingTag) + { + if (!isset($competingElements[$tag])) + $competingElements[$tag] = array(); + + $competingElements[$tag][] = $parts[$i + 4]; + + if (count($competingElements[$tag]) > 1) + $parts[$i] .= '[/' . $tag . ']'; + } + + $inlineElements[$elementContent] = $tag; + } + } + + } + + // Closing tag. + else + { + // Closing the block tag. + if ($isBlockLevelTag) + { + // Close the elements that should've been closed by closing this tag. + if (!empty($blockElements)) + { + $addClosingTags = array(); + while ($element = array_pop($blockElements)) + { + if ($element === $tag) + break; + + // Still a block tag was open not equal to this tag. + $addClosingTags[] = $element['type']; + } + + if (!empty($addClosingTags)) + $parts[$i + 1] = '[/' . implode('][/', array_reverse($addClosingTags)) . ']' . $parts[$i + 1]; + + // Apparently the closing tag was not found on the stack. + if (!is_string($element) || $element !== $tag) + { + // Get rid of this particular closing tag, it was never opened. + $parts[$i + 1] = substr($parts[$i + 1], 0, -1); + $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = ''; + continue; + } + } + else + { + // Get rid of this closing tag! + $parts[$i + 1] = $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = ''; + continue; + } + + // Inline elements are still left opened? + if (!empty($inlineElements)) + { + // Close them first.. + $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']'; + + // Then reopen them. + $parts[$i + 5] = '[' . implode('][', array_keys($inlineElements)) . ']' . $parts[$i + 5]; + } + } + // Inline tag. + else + { + // Are we expecting this tag to end? + if (in_array($tag, $inlineElements)) + { + foreach (array_reverse($inlineElements, true) as $tagContentToBeClosed => $tagToBeClosed) + { + // Closing it one way or the other. + unset($inlineElements[$tagContentToBeClosed]); + + // Was this the tag we were looking for? + if ($tagToBeClosed === $tag) + break; + + // Nope, close it and look further! + else + $parts[$i] .= '[/' . $tagToBeClosed . ']'; + } + + if ($isCompetingTag && !empty($competingElements[$tag])) + { + array_pop($competingElements[$tag]); + + if (count($competingElements[$tag]) > 0) + $parts[$i + 5] = '[' . $tag . $competingElements[$tag][count($competingElements[$tag]) - 1] . $parts[$i + 5]; + } + } + + // Unexpected closing tag, ex-ter-mi-nate. + else + $parts[$i + 1] = $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = ''; + } + } + } + + // Close the code tags. + if ($inCode) + $parts[$i] .= '[/code]'; + + // The same for nobbc tags. + elseif ($inNoBbc) + $parts[$i] .= '[/nobbc]'; + + // Still inline tags left unclosed? Close them now, better late than never. + elseif (!empty($inlineElements)) + $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']'; + + // Now close the block elements. + if (!empty($blockElements)) + $parts[$i] .= '[/' . implode('][/', array_reverse($blockElements)) . ']'; + + $text = implode('', $parts); + } + + // Final clean up of back to back tags. + $lastlen = 0; + while (strlen($text) !== $lastlen) + $lastlen = strlen($text = preg_replace($backToBackPattern, '', $text)); + + return $text; +} + +// A help function for legalise_bbc for sorting arrays based on length. +function sort_array_length($a, $b) +{ + return strlen($a) < strlen($b) ? 1 : -1; +} + +// Compatibility function - used in 1.1 for showing a post box. +function theme_postbox($msg) +{ + global $context; + + return template_control_richedit($context['post_box_name']); +} + +// Creates a box that can be used for richedit stuff like BBC, Smileys etc. +function create_control_richedit($editorOptions) +{ + global $txt, $modSettings, $options, $smcFunc; + global $context, $settings, $user_info, $sourcedir, $scripturl; + + // Load the Post language file... for the moment at least. + loadLanguage('Post'); + + // Every control must have a ID! + assert(isset($editorOptions['id'])); + assert(isset($editorOptions['value'])); + + // Is this the first richedit - if so we need to ensure some template stuff is initialised. + if (empty($context['controls']['richedit'])) + { + // Some general stuff. + $settings['smileys_url'] = $modSettings['smileys_url'] . '/' . $user_info['smiley_set']; + + // This really has some WYSIWYG stuff. + loadTemplate('GenericControls', $context['browser']['is_ie'] ? 'editor_ie' : 'editor'); + $context['html_headers'] .= ' + + '; + + $context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new'); + if ($context['show_spellchecking']) + { + $context['html_headers'] .= ' + '; + + // Some hidden information is needed in order to make the spell checking work. + if (!isset($_REQUEST['xml'])) + $context['insert_after_template'] .= ' +
          + +
          '; + + // Also make sure that spell check works with rich edit. + $context['html_headers'] .= ' + '; + } + } + + // Start off the editor... + $context['controls']['richedit'][$editorOptions['id']] = array( + 'id' => $editorOptions['id'], + 'value' => $editorOptions['value'], + 'rich_value' => bbc_to_html($editorOptions['value']), + 'rich_active' => empty($modSettings['disable_wysiwyg']) && (!empty($options['wysiwyg_default']) || !empty($editorOptions['force_rich']) || !empty($_REQUEST[$editorOptions['id'] . '_mode'])), + 'disable_smiley_box' => !empty($editorOptions['disable_smiley_box']), + 'columns' => isset($editorOptions['columns']) ? $editorOptions['columns'] : 60, + 'rows' => isset($editorOptions['rows']) ? $editorOptions['rows'] : 12, + 'width' => isset($editorOptions['width']) ? $editorOptions['width'] : '70%', + 'height' => isset($editorOptions['height']) ? $editorOptions['height'] : '150px', + 'form' => isset($editorOptions['form']) ? $editorOptions['form'] : 'postmodify', + 'bbc_level' => !empty($editorOptions['bbc_level']) ? $editorOptions['bbc_level'] : 'full', + 'preview_type' => isset($editorOptions['preview_type']) ? (int) $editorOptions['preview_type'] : 1, + 'labels' => !empty($editorOptions['labels']) ? $editorOptions['labels'] : array(), + ); + + // Switch between default images and back... mostly in case you don't have an PersonalMessage template, but do have a Post template. + if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'defaults' && isset($settings['default_template'])) + { + $temp1 = $settings['theme_url']; + $settings['theme_url'] = $settings['default_theme_url']; + + $temp2 = $settings['images_url']; + $settings['images_url'] = $settings['default_images_url']; + + $temp3 = $settings['theme_dir']; + $settings['theme_dir'] = $settings['default_theme_dir']; + } + + if (empty($context['bbc_tags'])) + { + // The below array makes it dead easy to add images to this control. Add it to the array and everything else is done for you! + $context['bbc_tags'] = array(); + $context['bbc_tags'][] = array( + array( + 'image' => 'bold', + 'code' => 'b', + 'before' => '[b]', + 'after' => '[/b]', + 'description' => $txt['bold'], + ), + array( + 'image' => 'italicize', + 'code' => 'i', + 'before' => '[i]', + 'after' => '[/i]', + 'description' => $txt['italic'], + ), + array( + 'image' => 'underline', + 'code' => 'u', + 'before' => '[u]', + 'after' => '[/u]', + 'description' => $txt['underline'] + ), + array( + 'image' => 'strike', + 'code' => 's', + 'before' => '[s]', + 'after' => '[/s]', + 'description' => $txt['strike'] + ), + array(), + array( + 'image' => 'pre', + 'code' => 'pre', + 'before' => '[pre]', + 'after' => '[/pre]', + 'description' => $txt['preformatted'] + ), + array( + 'image' => 'left', + 'code' => 'left', + 'before' => '[left]', + 'after' => '[/left]', + 'description' => $txt['left_align'] + ), + array( + 'image' => 'center', + 'code' => 'center', + 'before' => '[center]', + 'after' => '[/center]', + 'description' => $txt['center'] + ), + array( + 'image' => 'right', + 'code' => 'right', + 'before' => '[right]', + 'after' => '[/right]', + 'description' => $txt['right_align'] + ), + ); + $context['bbc_tags'][] = array( + array( + 'image' => 'flash', + 'code' => 'flash', + 'before' => '[flash=200,200]', + 'after' => '[/flash]', + 'description' => $txt['flash'] + ), + array( + 'image' => 'img', + 'code' => 'img', + 'before' => '[img]', + 'after' => '[/img]', + 'description' => $txt['image'] + ), + array( + 'image' => 'url', + 'code' => 'url', + 'before' => '[url]', + 'after' => '[/url]', + 'description' => $txt['hyperlink'] + ), + array( + 'image' => 'email', + 'code' => 'email', + 'before' => '[email]', + 'after' => '[/email]', + 'description' => $txt['insert_email'] + ), + array( + 'image' => 'ftp', + 'code' => 'ftp', + 'before' => '[ftp]', + 'after' => '[/ftp]', + 'description' => $txt['ftp'] + ), + array(), + array( + 'image' => 'glow', + 'code' => 'glow', + 'before' => '[glow=red,2,300]', + 'after' => '[/glow]', + 'description' => $txt['glow'] + ), + array( + 'image' => 'shadow', + 'code' => 'shadow', + 'before' => '[shadow=red,left]', + 'after' => '[/shadow]', + 'description' => $txt['shadow'] + ), + array( + 'image' => 'move', + 'code' => 'move', + 'before' => '[move]', + 'after' => '[/move]', + 'description' => $txt['marquee'] + ), + array(), + array( + 'image' => 'sup', + 'code' => 'sup', + 'before' => '[sup]', + 'after' => '[/sup]', + 'description' => $txt['superscript'] + ), + array( + 'image' => 'sub', + 'code' => 'sub', + 'before' => '[sub]', + 'after' => '[/sub]', + 'description' => $txt['subscript'] + ), + array( + 'image' => 'tele', + 'code' => 'tt', + 'before' => '[tt]', + 'after' => '[/tt]', + 'description' => $txt['teletype'] + ), + array(), + array( + 'image' => 'table', + 'code' => 'table', + 'before' => '[table]\n[tr]\n[td]', + 'after' => '[/td]\n[/tr]\n[/table]', + 'description' => $txt['table'] + ), + array( + 'image' => 'code', + 'code' => 'code', + 'before' => '[code]', + 'after' => '[/code]', + 'description' => $txt['bbc_code'] + ), + array( + 'image' => 'quote', + 'code' => 'quote', + 'before' => '[quote]', + 'after' => '[/quote]', + 'description' => $txt['bbc_quote'] + ), + array(), + array( + 'image' => 'list', + 'code' => 'list', + 'before' => '[list]\n[li]', + 'after' => '[/li]\n[li][/li]\n[/list]', + 'description' => $txt['list_unordered'] + ), + array( + 'image' => 'orderlist', + 'code' => 'orderlist', + 'before' => '[list type=decimal]\n[li]', + 'after' => '[/li]\n[li][/li]\n[/list]', + 'description' => $txt['list_ordered'] + ), + array( + 'image' => 'hr', + 'code' => 'hr', + 'before' => '[hr]', + 'description' => $txt['horizontal_rule'] + ), + ); + + // Allow mods to modify BBC buttons. + call_integration_hook('integrate_bbc_buttons', array(&$context['bbc_tags'])); + + // Show the toggle? + if (empty($modSettings['disable_wysiwyg'])) + { + $context['bbc_tags'][count($context['bbc_tags']) - 1][] = array(); + $context['bbc_tags'][count($context['bbc_tags']) - 1][] = array( + 'image' => 'unformat', + 'code' => 'unformat', + 'before' => '', + 'description' => $txt['unformat_text'], + ); + $context['bbc_tags'][count($context['bbc_tags']) - 1][] = array( + 'image' => 'toggle', + 'code' => 'toggle', + 'before' => '', + 'description' => $txt['toggle_view'], + ); + } + + foreach ($context['bbc_tags'] as $row => $tagRow) + $context['bbc_tags'][$row][count($tagRow) - 1]['isLast'] = true; + } + + // Initialize smiley array... if not loaded before. + if (empty($context['smileys']) && empty($editorOptions['disable_smiley_box'])) + { + $context['smileys'] = array( + 'postform' => array(), + 'popup' => array(), + ); + + // Load smileys - don't bother to run a query if we're not using the database's ones anyhow. + if (empty($modSettings['smiley_enable']) && $user_info['smiley_set'] != 'none') + $context['smileys']['postform'][] = array( + 'smileys' => array( + array( + 'code' => ':)', + 'filename' => 'smiley.gif', + 'description' => $txt['icon_smiley'], + ), + array( + 'code' => ';)', + 'filename' => 'wink.gif', + 'description' => $txt['icon_wink'], + ), + array( + 'code' => ':D', + 'filename' => 'cheesy.gif', + 'description' => $txt['icon_cheesy'], + ), + array( + 'code' => ';D', + 'filename' => 'grin.gif', + 'description' => $txt['icon_grin'] + ), + array( + 'code' => '>:(', + 'filename' => 'angry.gif', + 'description' => $txt['icon_angry'], + ), + array( + 'code' => ':(', + 'filename' => 'sad.gif', + 'description' => $txt['icon_sad'], + ), + array( + 'code' => ':o', + 'filename' => 'shocked.gif', + 'description' => $txt['icon_shocked'], + ), + array( + 'code' => '8)', + 'filename' => 'cool.gif', + 'description' => $txt['icon_cool'], + ), + array( + 'code' => '???', + 'filename' => 'huh.gif', + 'description' => $txt['icon_huh'], + ), + array( + 'code' => '::)', + 'filename' => 'rolleyes.gif', + 'description' => $txt['icon_rolleyes'], + ), + array( + 'code' => ':P', + 'filename' => 'tongue.gif', + 'description' => $txt['icon_tongue'], + ), + array( + 'code' => ':-[', + 'filename' => 'embarrassed.gif', + 'description' => $txt['icon_embarrassed'], + ), + array( + 'code' => ':-X', + 'filename' => 'lipsrsealed.gif', + 'description' => $txt['icon_lips'], + ), + array( + 'code' => ':-\\', + 'filename' => 'undecided.gif', + 'description' => $txt['icon_undecided'], + ), + array( + 'code' => ':-*', + 'filename' => 'kiss.gif', + 'description' => $txt['icon_kiss'], + ), + array( + 'code' => ':\'(', + 'filename' => 'cry.gif', + 'description' => $txt['icon_cry'], + 'isLast' => true, + ), + ), + 'isLast' => true, + ); + elseif ($user_info['smiley_set'] != 'none') + { + if (($temp = cache_get_data('posting_smileys', 480)) == null) + { + $request = $smcFunc['db_query']('', ' + SELECT code, filename, description, smiley_row, hidden + FROM {db_prefix}smileys + WHERE hidden IN (0, 2) + ORDER BY smiley_row, smiley_order', + array( + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $row['filename'] = htmlspecialchars($row['filename']); + $row['description'] = htmlspecialchars($row['description']); + + $context['smileys'][empty($row['hidden']) ? 'postform' : 'popup'][$row['smiley_row']]['smileys'][] = $row; + } + $smcFunc['db_free_result']($request); + + foreach ($context['smileys'] as $section => $smileyRows) + { + foreach ($smileyRows as $rowIndex => $smileys) + $context['smileys'][$section][$rowIndex]['smileys'][count($smileys['smileys']) - 1]['isLast'] = true; + + if (!empty($smileyRows)) + $context['smileys'][$section][count($smileyRows) - 1]['isLast'] = true; + } + + cache_put_data('posting_smileys', $context['smileys'], 480); + } + else + $context['smileys'] = $temp; + } + } + + // Set a flag so the sub template knows what to do... + $context['show_bbc'] = !empty($modSettings['enableBBC']) && !empty($settings['show_bbc']); + + // Generate a list of buttons that shouldn't be shown - this should be the fastest way to do this. + $disabled_tags = array(); + if (!empty($modSettings['disabledBBC'])) + $disabled_tags = explode(',', $modSettings['disabledBBC']); + if (empty($modSettings['enableEmbeddedFlash'])) + $disabled_tags[] = 'flash'; + + foreach ($disabled_tags as $tag) + { + if ($tag == 'list') + $context['disabled_tags']['orderlist'] = true; + + $context['disabled_tags'][trim($tag)] = true; + } + + // Switch the URLs back... now we're back to whatever the main sub template is. (like folder in PersonalMessage.) + if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'defaults' && isset($settings['default_template'])) + { + $settings['theme_url'] = $temp1; + $settings['images_url'] = $temp2; + $settings['theme_dir'] = $temp3; + } +} + +// Create a anti-bot verification control? +function create_control_verification(&$verificationOptions, $do_test = false) +{ + global $txt, $modSettings, $options, $smcFunc; + global $context, $settings, $user_info, $sourcedir, $scripturl; + + // First verification means we need to set up some bits... + if (empty($context['controls']['verification'])) + { + // The template + loadTemplate('GenericControls'); + + // Some javascript ma'am? + if (!empty($verificationOptions['override_visual']) || (!empty($modSettings['visual_verification_type']) && !isset($verificationOptions['override_visual']))) + $context['html_headers'] .= ' + '; + + $context['use_graphic_library'] = in_array('gd', get_loaded_extensions()); + + // Skip I, J, L, O, Q, S and Z. + $context['standard_captcha_range'] = array_merge(range('A', 'H'), array('K', 'M', 'N', 'P', 'R'), range('T', 'Y')); + } + + // Always have an ID. + assert(isset($verificationOptions['id'])); + $isNew = !isset($context['controls']['verification'][$verificationOptions['id']]); + + // Log this into our collection. + if ($isNew) + $context['controls']['verification'][$verificationOptions['id']] = array( + 'id' => $verificationOptions['id'], + 'show_visual' => !empty($verificationOptions['override_visual']) || (!empty($modSettings['visual_verification_type']) && !isset($verificationOptions['override_visual'])), + 'number_questions' => isset($verificationOptions['override_qs']) ? $verificationOptions['override_qs'] : (!empty($modSettings['qa_verification_number']) ? $modSettings['qa_verification_number'] : 0), + 'max_errors' => isset($verificationOptions['max_errors']) ? $verificationOptions['max_errors'] : 3, + 'image_href' => $scripturl . '?action=verificationcode;vid=' . $verificationOptions['id'] . ';rand=' . md5(mt_rand()), + 'text_value' => '', + 'questions' => array(), + ); + $thisVerification = &$context['controls']['verification'][$verificationOptions['id']]; + + // Add javascript for the object. + if ($context['controls']['verification'][$verificationOptions['id']]['show_visual'] && !WIRELESS) + $context['insert_after_template'] .= ' + '; + + // Is there actually going to be anything? + if (empty($thisVerification['show_visual']) && empty($thisVerification['number_questions'])) + return false; + elseif (!$isNew && !$do_test) + return true; + + // If we want questions do we have a cache of all the IDs? + if (!empty($thisVerification['number_questions']) && empty($modSettings['question_id_cache'])) + { + if (($modSettings['question_id_cache'] = cache_get_data('verificationQuestionIds', 300)) == null) + { + $request = $smcFunc['db_query']('', ' + SELECT id_comment + FROM {db_prefix}log_comments + WHERE comment_type = {string:ver_test}', + array( + 'ver_test' => 'ver_test', + ) + ); + $modSettings['question_id_cache'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $modSettings['question_id_cache'][] = $row['id_comment']; + $smcFunc['db_free_result']($request); + + if (!empty($modSettings['cache_enable'])) + cache_put_data('verificationQuestionIds', $modSettings['question_id_cache'], 300); + } + } + + if (!isset($_SESSION[$verificationOptions['id'] . '_vv'])) + $_SESSION[$verificationOptions['id'] . '_vv'] = array(); + + // Do we need to refresh the verification? + if (!$do_test && (!empty($_SESSION[$verificationOptions['id'] . '_vv']['did_pass']) || empty($_SESSION[$verificationOptions['id'] . '_vv']['count']) || $_SESSION[$verificationOptions['id'] . '_vv']['count'] > 3) && empty($verificationOptions['dont_refresh'])) + $force_refresh = true; + else + $force_refresh = false; + + // This can also force a fresh, although unlikely. + if (($thisVerification['show_visual'] && empty($_SESSION[$verificationOptions['id'] . '_vv']['code'])) || ($thisVerification['number_questions'] && empty($_SESSION[$verificationOptions['id'] . '_vv']['q']))) + $force_refresh = true; + + $verification_errors = array(); + + // Start with any testing. + if ($do_test) + { + // This cannot happen! + if (!isset($_SESSION[$verificationOptions['id'] . '_vv']['count'])) + fatal_lang_error('no_access', false); + // ... nor this! + if ($thisVerification['number_questions'] && (!isset($_SESSION[$verificationOptions['id'] . '_vv']['q']) || !isset($_REQUEST[$verificationOptions['id'] . '_vv']['q']))) + fatal_lang_error('no_access', false); + + if ($thisVerification['show_visual'] && (empty($_REQUEST[$verificationOptions['id'] . '_vv']['code']) || empty($_SESSION[$verificationOptions['id'] . '_vv']['code']) || strtoupper($_REQUEST[$verificationOptions['id'] . '_vv']['code']) !== $_SESSION[$verificationOptions['id'] . '_vv']['code'])) + $verification_errors[] = 'wrong_verification_code'; + if ($thisVerification['number_questions']) + { + // Get the answers and see if they are all right! + $request = $smcFunc['db_query']('', ' + SELECT id_comment, recipient_name AS answer + FROM {db_prefix}log_comments + WHERE comment_type = {string:ver_test} + AND id_comment IN ({array_int:comment_ids})', + array( + 'ver_test' => 'ver_test', + 'comment_ids' => $_SESSION[$verificationOptions['id'] . '_vv']['q'], + ) + ); + $incorrectQuestions = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (empty($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$row['id_comment']]) || trim($smcFunc['htmlspecialchars'](strtolower($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$row['id_comment']]))) != strtolower($row['answer'])) + $incorrectQuestions[] = $row['id_comment']; + } + $smcFunc['db_free_result']($request); + + if (!empty($incorrectQuestions)) + $verification_errors[] = 'wrong_verification_answer'; + } + } + + // Any errors means we refresh potentially. + if (!empty($verification_errors)) + { + if (empty($_SESSION[$verificationOptions['id'] . '_vv']['errors'])) + $_SESSION[$verificationOptions['id'] . '_vv']['errors'] = 0; + // Too many errors? + elseif ($_SESSION[$verificationOptions['id'] . '_vv']['errors'] > $thisVerification['max_errors']) + $force_refresh = true; + + // Keep a track of these. + $_SESSION[$verificationOptions['id'] . '_vv']['errors']++; + } + + // Are we refreshing then? + if ($force_refresh) + { + // Assume nothing went before. + $_SESSION[$verificationOptions['id'] . '_vv']['count'] = 0; + $_SESSION[$verificationOptions['id'] . '_vv']['errors'] = 0; + $_SESSION[$verificationOptions['id'] . '_vv']['did_pass'] = false; + $_SESSION[$verificationOptions['id'] . '_vv']['q'] = array(); + $_SESSION[$verificationOptions['id'] . '_vv']['code'] = ''; + + // Generating a new image. + if ($thisVerification['show_visual']) + { + // Are we overriding the range? + $character_range = !empty($verificationOptions['override_range']) ? $verificationOptions['override_range'] : $context['standard_captcha_range']; + + for ($i = 0; $i < 6; $i++) + $_SESSION[$verificationOptions['id'] . '_vv']['code'] .= $character_range[array_rand($character_range)]; + } + + // Getting some new questions? + if ($thisVerification['number_questions']) + { + // Pick some random IDs + $questionIDs = array(); + if ($thisVerification['number_questions'] == 1) + $questionIDs[] = $modSettings['question_id_cache'][array_rand($modSettings['question_id_cache'], $thisVerification['number_questions'])]; + else + foreach (array_rand($modSettings['question_id_cache'], $thisVerification['number_questions']) as $index) + $questionIDs[] = $modSettings['question_id_cache'][$index]; + } + } + else + { + // Same questions as before. + $questionIDs = !empty($_SESSION[$verificationOptions['id'] . '_vv']['q']) ? $_SESSION[$verificationOptions['id'] . '_vv']['q'] : array(); + $thisVerification['text_value'] = !empty($_REQUEST[$verificationOptions['id'] . '_vv']['code']) ? $smcFunc['htmlspecialchars']($_REQUEST[$verificationOptions['id'] . '_vv']['code']) : ''; + } + + // Have we got some questions to load? + if (!empty($questionIDs)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_comment, body AS question + FROM {db_prefix}log_comments + WHERE comment_type = {string:ver_test} + AND id_comment IN ({array_int:comment_ids})', + array( + 'ver_test' => 'ver_test', + 'comment_ids' => $questionIDs, + ) + ); + $_SESSION[$verificationOptions['id'] . '_vv']['q'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $thisVerification['questions'][] = array( + 'id' => $row['id_comment'], + 'q' => parse_bbc($row['question']), + 'is_error' => !empty($incorrectQuestions) && in_array($row['id_comment'], $incorrectQuestions), + // Remember a previous submission? + 'a' => isset($_REQUEST[$verificationOptions['id'] . '_vv'], $_REQUEST[$verificationOptions['id'] . '_vv']['q'], $_REQUEST[$verificationOptions['id'] . '_vv']['q'][$row['id_comment']]) ? $smcFunc['htmlspecialchars']($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$row['id_comment']]) : '', + ); + $_SESSION[$verificationOptions['id'] . '_vv']['q'][] = $row['id_comment']; + } + $smcFunc['db_free_result']($request); + } + + $_SESSION[$verificationOptions['id'] . '_vv']['count'] = empty($_SESSION[$verificationOptions['id'] . '_vv']['count']) ? 1 : $_SESSION[$verificationOptions['id'] . '_vv']['count'] + 1; + + // Return errors if we have them. + if (!empty($verification_errors)) + return $verification_errors; + // If we had a test that one, make a note. + elseif ($do_test) + $_SESSION[$verificationOptions['id'] . '_vv']['did_pass'] = true; + + // Say that everything went well chaps. + return true; +} + +// This keeps track of all registered handling functions for auto suggest functionality and passes execution to them. +function AutoSuggestHandler($checkRegistered = null) +{ + global $context; + + // These are all registered types. + $searchTypes = array( + 'member' => 'Member', + ); + + // If we're just checking the callback function is registered return true or false. + if ($checkRegistered != null) + return isset($searchTypes[$checkRegistered]) && function_exists('AutoSuggest_Search_' . $checkRegistered); + + checkSession('get'); + loadTemplate('Xml'); + + // Any parameters? + $context['search_param'] = isset($_REQUEST['search_param']) ? unserialize(base64_decode($_REQUEST['search_param'])) : array(); + + if (isset($_REQUEST['suggest_type'], $_REQUEST['search']) && isset($searchTypes[$_REQUEST['suggest_type']])) + { + $function = 'AutoSuggest_Search_' . $searchTypes[$_REQUEST['suggest_type']]; + $context['sub_template'] = 'generic_xml'; + $context['xml_data'] = $function(); + } +} + +// Search for a member - by real_name or member_name by default. +function AutoSuggest_Search_Member() +{ + global $user_info, $txt, $smcFunc, $context; + + $_REQUEST['search'] = trim($smcFunc['strtolower']($_REQUEST['search'])) . '*'; + $_REQUEST['search'] = strtr($_REQUEST['search'], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '&' => '&')); + + // Find the member. + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name + FROM {db_prefix}members + WHERE real_name LIKE {string:search}' . (!empty($context['search_param']['buddies']) ? ' + AND id_member IN ({array_int:buddy_list})' : '') . ' + AND is_activated IN (1, 11) + LIMIT ' . ($smcFunc['strlen']($_REQUEST['search']) <= 2 ? '100' : '800'), + array( + 'buddy_list' => $user_info['buddies'], + 'search' => $_REQUEST['search'], + ) + ); + $xml_data = array( + 'items' => array( + 'identifier' => 'item', + 'children' => array(), + ), + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $row['real_name'] = strtr($row['real_name'], array('&' => '&', '<' => '<', '>' => '>', '"' => '"')); + + $xml_data['items']['children'][] = array( + 'attributes' => array( + 'id' => $row['id_member'], + ), + 'value' => $row['real_name'], + ); + } + $smcFunc['db_free_result']($request); + + return $xml_data; +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Graphics.php b/Sources/Subs-Graphics.php new file mode 100644 index 0000000..385d22f --- /dev/null +++ b/Sources/Subs-Graphics.php @@ -0,0 +1,1045 @@ + $memID)); + + $id_folder = !empty($modSettings['currentAttachmentUploadDir']) ? $modSettings['currentAttachmentUploadDir'] : 1; + $avatar_hash = empty($modSettings['custom_avatar_enabled']) ? getAttachmentFilename($destName, false, null, true) : ''; + $smcFunc['db_insert']('', + '{db_prefix}attachments', + array( + 'id_member' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-255', 'fileext' => 'string-8', 'size' => 'int', + 'id_folder' => 'int', + ), + array( + $memID, empty($modSettings['custom_avatar_enabled']) ? 0 : 1, $destName, $avatar_hash, $ext, 1, + $id_folder, + ), + array('id_attach') + ); + $attachID = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach'); + // Retain this globally in case the script wants it. + $modSettings['new_avatar_data'] = array( + 'id' => $attachID, + 'filename' => $destName, + 'type' => empty($modSettings['custom_avatar_enabled']) ? 0 : 1, + ); + + $destName = (empty($modSettings['custom_avatar_enabled']) ? (is_array($modSettings['attachmentUploadDir']) ? $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']] : $modSettings['attachmentUploadDir']) : $modSettings['custom_avatar_dir']) . '/' . $destName . '.tmp'; + + // Resize it. + if (!empty($modSettings['avatar_download_png'])) + $success = resizeImageFile($url, $destName, $max_width, $max_height, 3); + else + $success = resizeImageFile($url, $destName, $max_width, $max_height); + + // Remove the .tmp extension. + $destName = substr($destName, 0, -4); + + if ($success) + { + // Walk the right path. + if (!empty($modSettings['currentAttachmentUploadDir'])) + { + if (!is_array($modSettings['attachmentUploadDir'])) + $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); + $path = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']]; + } + else + $path = $modSettings['attachmentUploadDir']; + + // Remove the .tmp extension from the attachment. + if (rename($destName . '.tmp', empty($avatar_hash) ? $destName : $path . '/' . $attachID . '_' . $avatar_hash)) + { + $destName = empty($avatar_hash) ? $destName : $path . '/' . $attachID . '_' . $avatar_hash; + list ($width, $height) = getimagesize($destName); + $mime_type = 'image/' . $ext; + + // Write filesize in the database. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}attachments + SET size = {int:filesize}, width = {int:width}, height = {int:height}, + mime_type = {string:mime_type} + WHERE id_attach = {int:current_attachment}', + array( + 'filesize' => filesize($destName), + 'width' => (int) $width, + 'height' => (int) $height, + 'current_attachment' => $attachID, + 'mime_type' => $mime_type, + ) + ); + return true; + } + else + return false; + } + else + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}attachments + WHERE id_attach = {int:current_attachment}', + array( + 'current_attachment' => $attachID, + ) + ); + + @unlink($destName . '.tmp'); + return false; + } +} + +function createThumbnail($source, $max_width, $max_height) +{ + global $modSettings; + + $destName = $source . '_thumb.tmp'; + + // Do the actual resize. + if (!empty($modSettings['attachment_thumb_png'])) + $success = resizeImageFile($source, $destName, $max_width, $max_height, 3); + else + $success = resizeImageFile($source, $destName, $max_width, $max_height); + + // Okay, we're done with the temporary stuff. + $destName = substr($destName, 0, -4); + + if ($success && @rename($destName . '.tmp', $destName)) + return true; + else + { + @unlink($destName . '.tmp'); + @touch($destName); + return false; + } +} + +function reencodeImage($fileName, $preferred_format = 0) +{ + // There is nothing we can do without GD, sorry! + if (!checkGD()) + return false; + + if (!resizeImageFile($fileName, $fileName . '.tmp', null, null, $preferred_format)) + { + if (file_exists($fileName . '.tmp')) + unlink($fileName . '.tmp'); + + return false; + } + + if (!unlink($fileName)) + return false; + + if (!rename($fileName . '.tmp', $fileName)) + return false; + + return true; +} + +function checkImageContents($fileName, $extensiveCheck = false) +{ + $fp = fopen($fileName, 'rb'); + if (!$fp) + fatal_lang_error('attach_timeout'); + + $prev_chunk = ''; + while (!feof($fp)) + { + $cur_chunk = fread($fp, 8192); + + // Though not exhaustive lists, better safe than sorry. + if (!empty($extensiveCheck)) + { + // Paranoid check. Some like it that way. + if (preg_match('~(iframe|\\<\\?|\\<%|html|eval|body|script\W|[CF]WS[\x01-\x0C])~i', $prev_chunk . $cur_chunk) === 1) + { + fclose($fp); + return false; + } + } + else + { + // Check for potential infection + if (preg_match('~(iframe|html|eval|body|script\W|[CF]WS[\x01-\x0C])~i', $prev_chunk . $cur_chunk) === 1) + { + fclose($fp); + return false; + } + } + $prev_chunk = $cur_chunk; + } + fclose($fp); + + return true; +} + +function checkGD() +{ + global $gd2; + + // Check to see if GD is installed and what version. + if (($extensionFunctions = get_extension_funcs('gd')) === false) + return false; + + // Also determine if GD2 is installed and store it in a global. + $gd2 = in_array('imagecreatetruecolor', $extensionFunctions) && function_exists('imagecreatetruecolor'); + + return true; +} + +function resizeImageFile($source, $destination, $max_width, $max_height, $preferred_format = 0) +{ + global $sourcedir; + + // Nothing to do without GD + if (!checkGD()) + return false; + + static $default_formats = array( + '1' => 'gif', + '2' => 'jpeg', + '3' => 'png', + '6' => 'bmp', + '15' => 'wbmp' + ); + + require_once($sourcedir . '/Subs-Package.php'); + @ini_set('memory_limit', '90M'); + + $success = false; + + // Get the image file, we have to work with something after all + $fp_destination = fopen($destination, 'wb'); + if ($fp_destination && substr($source, 0, 7) == 'http://') + { + $fileContents = fetch_web_data($source); + + fwrite($fp_destination, $fileContents); + fclose($fp_destination); + + $sizes = @getimagesize($destination); + } + elseif ($fp_destination) + { + $sizes = @getimagesize($source); + + $fp_source = fopen($source, 'rb'); + if ($fp_source !== false) + { + while (!feof($fp_source)) + fwrite($fp_destination, fread($fp_source, 8192)); + fclose($fp_source); + } + else + $sizes = array(-1, -1, -1); + fclose($fp_destination); + } + // We can't get to the file. + else + $sizes = array(-1, -1, -1); + + // Gif? That might mean trouble if gif support is not available. + if ($sizes[2] == 1 && !function_exists('imagecreatefromgif') && function_exists('imagecreatefrompng')) + { + // Download it to the temporary file... use the special gif library... and save as png. + if ($img = @gif_loadFile($destination) && gif_outputAsPng($img, $destination)) + $sizes[2] = 3; + } + + // A known and supported format? + if (isset($default_formats[$sizes[2]]) && function_exists('imagecreatefrom' . $default_formats[$sizes[2]])) + { + $imagecreatefrom = 'imagecreatefrom' . $default_formats[$sizes[2]]; + if ($src_img = @$imagecreatefrom($destination)) + { + resizeImage($src_img, $destination, imagesx($src_img), imagesy($src_img), $max_width === null ? imagesx($src_img) : $max_width, $max_height === null ? imagesy($src_img) : $max_height, true, $preferred_format); + $success = true; + } + } + + return $success; +} + +function resizeImage($src_img, $destName, $src_width, $src_height, $max_width, $max_height, $force_resize = false, $preferred_format = 0) +{ + global $gd2, $modSettings; + + // Without GD, no image resizing at all. + if (!checkGD()) + return false; + + $success = false; + + // Determine whether to resize to max width or to max height (depending on the limits.) + if (!empty($max_width) || !empty($max_height)) + { + if (!empty($max_width) && (empty($max_height) || $src_height * $max_width / $src_width <= $max_height)) + { + $dst_width = $max_width; + $dst_height = floor($src_height * $max_width / $src_width); + } + elseif (!empty($max_height)) + { + $dst_width = floor($src_width * $max_height / $src_height); + $dst_height = $max_height; + } + + // Don't bother resizing if it's already smaller... + if (!empty($dst_width) && !empty($dst_height) && ($dst_width < $src_width || $dst_height < $src_height || $force_resize)) + { + // (make a true color image, because it just looks better for resizing.) + if ($gd2) + { + $dst_img = imagecreatetruecolor($dst_width, $dst_height); + + // Deal nicely with a PNG - because we can. + if ((!empty($preferred_format)) && ($preferred_format == 3)) + { + imagealphablending($dst_img, false); + if (function_exists('imagesavealpha')) + imagesavealpha($dst_img, true); + } + } + else + $dst_img = imagecreate($dst_width, $dst_height); + + // Resize it! + if ($gd2) + imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $dst_width, $dst_height, $src_width, $src_height); + else + imagecopyresamplebicubic($dst_img, $src_img, 0, 0, 0, 0, $dst_width, $dst_height, $src_width, $src_height); + } + else + $dst_img = $src_img; + } + else + $dst_img = $src_img; + + // Save the image as ... + if (!empty($preferred_format) && ($preferred_format == 3) && function_exists('imagepng')) + $success = imagepng($dst_img, $destName); + elseif (!empty($preferred_format) && ($preferred_format == 1) && function_exists('imagegif')) + $success = imagegif($dst_img, $destName); + elseif (function_exists('imagejpeg')) + $success = imagejpeg($dst_img, $destName); + + // Free the memory. + imagedestroy($src_img); + if ($dst_img != $src_img) + imagedestroy($dst_img); + + return $success; +} + +function imagecopyresamplebicubic($dst_img, $src_img, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h) +{ + $palsize = imagecolorstotal($src_img); + for ($i = 0; $i < $palsize; $i++) + { + $colors = imagecolorsforindex($src_img, $i); + imagecolorallocate($dst_img, $colors['red'], $colors['green'], $colors['blue']); + } + + $scaleX = ($src_w - 1) / $dst_w; + $scaleY = ($src_h - 1) / $dst_h; + + $scaleX2 = (int) $scaleX / 2; + $scaleY2 = (int) $scaleY / 2; + + for ($j = $src_y; $j < $dst_h; $j++) + { + $sY = (int) $j * $scaleY; + $y13 = $sY + $scaleY2; + + for ($i = $src_x; $i < $dst_w; $i++) + { + $sX = (int) $i * $scaleX; + $x34 = $sX + $scaleX2; + + $color1 = imagecolorsforindex($src_img, imagecolorat($src_img, $sX, $y13)); + $color2 = imagecolorsforindex($src_img, imagecolorat($src_img, $sX, $sY)); + $color3 = imagecolorsforindex($src_img, imagecolorat($src_img, $x34, $y13)); + $color4 = imagecolorsforindex($src_img, imagecolorat($src_img, $x34, $sY)); + + $red = ($color1['red'] + $color2['red'] + $color3['red'] + $color4['red']) / 4; + $green = ($color1['green'] + $color2['green'] + $color3['green'] + $color4['green']) / 4; + $blue = ($color1['blue'] + $color2['blue'] + $color3['blue'] + $color4['blue']) / 4; + + $color = imagecolorresolve($dst_img, $red, $green, $blue); + if ($color == -1) + { + if ($palsize++ < 256) + imagecolorallocate($dst_img, $red, $green, $blue); + $color = imagecolorclosest($dst_img, $red, $green, $blue); + } + + imagesetpixel($dst_img, $i + $dst_x - $src_x, $j + $dst_y - $src_y, $color); + } + } +} + +if (!function_exists('imagecreatefrombmp')) +{ + function imagecreatefrombmp($filename) + { + global $gd2; + + $fp = fopen($filename, 'rb'); + + $errors = error_reporting(0); + + $header = unpack('vtype/Vsize/Vreserved/Voffset', fread($fp, 14)); + $info = unpack('Vsize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vncolor/Vcolorimportant', fread($fp, 40)); + + if ($header['type'] != 0x4D42) + false; + + if ($gd2) + $dst_img = imagecreatetruecolor($info['width'], $info['height']); + else + $dst_img = imagecreate($info['width'], $info['height']); + + $palette_size = $header['offset'] - 54; + $info['ncolor'] = $palette_size / 4; + + $palette = array(); + + $palettedata = fread($fp, $palette_size); + $n = 0; + for ($j = 0; $j < $palette_size; $j++) + { + $b = ord($palettedata{$j++}); + $g = ord($palettedata{$j++}); + $r = ord($palettedata{$j++}); + + $palette[$n++] = imagecolorallocate($dst_img, $r, $g, $b); + } + + $scan_line_size = ($info['bits'] * $info['width'] + 7) >> 3; + $scan_line_align = $scan_line_size & 3 ? 4 - ($scan_line_size & 3) : 0; + + for ($y = 0, $l = $info['height'] - 1; $y < $info['height']; $y++, $l--) + { + fseek($fp, $header['offset'] + ($scan_line_size + $scan_line_align) * $l); + $scan_line = fread($fp, $scan_line_size); + + if (strlen($scan_line) < $scan_line_size) + continue; + + if ($info['bits'] == 32) + { + $x = 0; + for ($j = 0; $j < $scan_line_size; $x++) + { + $b = ord($scan_line{$j++}); + $g = ord($scan_line{$j++}); + $r = ord($scan_line{$j++}); + $j++; + + $color = imagecolorexact($dst_img, $r, $g, $b); + if ($color == -1) + { + $color = imagecolorallocate($dst_img, $r, $g, $b); + + // Gah! Out of colors? Stupid GD 1... try anyhow. + if ($color == -1) + $color = imagecolorclosest($dst_img, $r, $g, $b); + } + + imagesetpixel($dst_img, $x, $y, $color); + } + } + elseif ($info['bits'] == 24) + { + $x = 0; + for ($j = 0; $j < $scan_line_size; $x++) + { + $b = ord($scan_line{$j++}); + $g = ord($scan_line{$j++}); + $r = ord($scan_line{$j++}); + + $color = imagecolorexact($dst_img, $r, $g, $b); + if ($color == -1) + { + $color = imagecolorallocate($dst_img, $r, $g, $b); + + // Gah! Out of colors? Stupid GD 1... try anyhow. + if ($color == -1) + $color = imagecolorclosest($dst_img, $r, $g, $b); + } + + imagesetpixel($dst_img, $x, $y, $color); + } + } + elseif ($info['bits'] == 16) + { + $x = 0; + for ($j = 0; $j < $scan_line_size; $x++) + { + $b1 = ord($scan_line{$j++}); + $b2 = ord($scan_line{$j++}); + + $word = $b2 * 256 + $b1; + + $b = (($word & 31) * 255) / 31; + $g = ((($word >> 5) & 31) * 255) / 31; + $r = ((($word >> 10) & 31) * 255) / 31; + + // Scale the image colors up properly. + $color = imagecolorexact($dst_img, $r, $g, $b); + if ($color == -1) + { + $color = imagecolorallocate($dst_img, $r, $g, $b); + + // Gah! Out of colors? Stupid GD 1... try anyhow. + if ($color == -1) + $color = imagecolorclosest($dst_img, $r, $g, $b); + } + + imagesetpixel($dst_img, $x, $y, $color); + } + } + elseif ($info['bits'] == 8) + { + $x = 0; + for ($j = 0; $j < $scan_line_size; $x++) + imagesetpixel($dst_img, $x, $y, $palette[ord($scan_line{$j++})]); + } + elseif ($info['bits'] == 4) + { + $x = 0; + for ($j = 0; $j < $scan_line_size; $x++) + { + $byte = ord($scan_line{$j++}); + + imagesetpixel($dst_img, $x, $y, $palette[(int) ($byte / 16)]); + if (++$x < $info['width']) + imagesetpixel($dst_img, $x, $y, $palette[$byte & 15]); + } + } + else + { + // Sorry, I'm just not going to do monochrome :P. + } + } + + fclose($fp); + + error_reporting($errors); + + return $dst_img; + } +} + +function gif_loadFile($lpszFileName, $iIndex = 0) +{ + // The classes needed are in this file. + loadClassFile('Class-Graphics.php'); + $gif = new gif_file(); + + if (!$gif->loadFile($lpszFileName, $iIndex)) + return false; + + return $gif; +} + +function gif_outputAsPng($gif, $lpszFileName, $background_color = -1) +{ + if (!isset($gif) || @get_class($gif) != 'cgif' || !$gif->loaded || $lpszFileName == '') + return false; + + $fd = $gif->get_png_data($background_color); + if (strlen($fd) <= 0) + return false; + + if (!($fh = @fopen($lpszFileName, 'wb'))) + return false; + + @fwrite($fh, $fd, strlen($fd)); + @fflush($fh); + @fclose($fh); + + return true; +} + +// Create the image for the visual verification code. +function showCodeImage($code) +{ + global $settings, $user_info, $modSettings; + + /* + Note: The higher the value of visual_verification_type the harder the verification is - from 0 as disabled through to 4 as "Very hard". + */ + + // What type are we going to be doing? + $imageType = $modSettings['visual_verification_type']; + // Special case to allow the admin center to show samples. + if ($user_info['is_admin'] && isset($_GET['type'])) + $imageType = (int) $_GET['type']; + + // Some quick references for what we do. + // Do we show no, low or high noise? + $noiseType = $imageType == 3 ? 'low' : ($imageType == 4 ? 'high' : ($imageType == 5 ? 'extreme' : 'none')); + // Can we have more than one font in use? + $varyFonts = $imageType > 3 ? true : false; + // Just a plain white background? + $simpleBGColor = $imageType < 3 ? true : false; + // Plain black foreground? + $simpleFGColor = $imageType == 0 ? true : false; + // High much to rotate each character. + $rotationType = $imageType == 1 ? 'none' : ($imageType > 3 ? 'low' : 'high'); + // Do we show some characters inversed? + $showReverseChars = $imageType > 3 ? true : false; + // Special case for not showing any characters. + $disableChars = $imageType == 0 ? true : false; + // What do we do with the font colors. Are they one color, close to one color or random? + $fontColorType = $imageType == 1 ? 'plain' : ($imageType > 3 ? 'random' : 'cyclic'); + // Are the fonts random sizes? + $fontSizeRandom = $imageType > 3 ? true : false; + // How much space between characters? + $fontHorSpace = $imageType > 3 ? 'high' : ($imageType == 1 ? 'medium' : 'minus'); + // Where do characters sit on the image? (Fixed position or random/very random) + $fontVerPos = $imageType == 1 ? 'fixed' : ($imageType > 3 ? 'vrandom' : 'random'); + // Make font semi-transparent? + $fontTrans = $imageType == 2 || $imageType == 3 ? true : false; + // Give the image a border? + $hasBorder = $simpleBGColor; + + // Is this GD2? Needed for pixel size. + $testGD = get_extension_funcs('gd'); + $gd2 = in_array('imagecreatetruecolor', $testGD) && function_exists('imagecreatetruecolor'); + unset($testGD); + + // The amount of pixels inbetween characters. + $character_spacing = 1; + + // What color is the background - generally white unless we're on "hard". + if ($simpleBGColor) + $background_color = array(255, 255, 255); + else + $background_color = isset($settings['verification_background']) ? $settings['verification_background'] : array(236, 237, 243); + + // The color of the characters shown (red, green, blue). + if ($simpleFGColor) + $foreground_color = array(0, 0, 0); + else + { + $foreground_color = array(64, 101, 136); + + // Has the theme author requested a custom color? + if (isset($settings['verification_foreground'])) + $foreground_color = $settings['verification_foreground']; + } + + if (!is_dir($settings['default_theme_dir'] . '/fonts')) + return false; + + // Get a list of the available fonts. + $font_dir = dir($settings['default_theme_dir'] . '/fonts'); + $font_list = array(); + $ttfont_list = array(); + while ($entry = $font_dir->read()) + { + if (preg_match('~^(.+)\.gdf$~', $entry, $matches) === 1) + $font_list[] = $entry; + elseif (preg_match('~^(.+)\.ttf$~', $entry, $matches) === 1) + $ttfont_list[] = $entry; + } + + if (empty($font_list)) + return false; + + // For non-hard things don't even change fonts. + if (!$varyFonts) + { + $font_list = array($font_list[0]); + // Try use Screenge if we can - it looks good! + if (in_array('Screenge.ttf', $ttfont_list)) + $ttfont_list = array('Screenge.ttf'); + else + $ttfont_list = empty($ttfont_list) ? array() : array($ttfont_list[0]); + + } + + // Create a list of characters to be shown. + $characters = array(); + $loaded_fonts = array(); + for ($i = 0; $i < strlen($code); $i++) + { + $characters[$i] = array( + 'id' => $code{$i}, + 'font' => array_rand($font_list), + ); + + $loaded_fonts[$characters[$i]['font']] = null; + } + + // Load all fonts and determine the maximum font height. + foreach ($loaded_fonts as $font_index => $dummy) + $loaded_fonts[$font_index] = imageloadfont($settings['default_theme_dir'] . '/fonts/' . $font_list[$font_index]); + + // Determine the dimensions of each character. + $total_width = $character_spacing * strlen($code) + 20; + $max_height = 0; + foreach ($characters as $char_index => $character) + { + $characters[$char_index]['width'] = imagefontwidth($loaded_fonts[$character['font']]); + $characters[$char_index]['height'] = imagefontheight($loaded_fonts[$character['font']]); + + $max_height = max($characters[$char_index]['height'] + 5, $max_height); + $total_width += $characters[$char_index]['width']; + } + + // Create an image. + $code_image = $gd2 ? imagecreatetruecolor($total_width, $max_height) : imagecreate($total_width, $max_height); + + // Draw the background. + $bg_color = imagecolorallocate($code_image, $background_color[0], $background_color[1], $background_color[2]); + imagefilledrectangle($code_image, 0, 0, $total_width - 1, $max_height - 1, $bg_color); + + // Randomize the foreground color a little. + for ($i = 0; $i < 3; $i++) + $foreground_color[$i] = mt_rand(max($foreground_color[$i] - 3, 0), min($foreground_color[$i] + 3, 255)); + $fg_color = imagecolorallocate($code_image, $foreground_color[0], $foreground_color[1], $foreground_color[2]); + + // Color for the dots. + for ($i = 0; $i < 3; $i++) + $dotbgcolor[$i] = $background_color[$i] < $foreground_color[$i] ? mt_rand(0, max($foreground_color[$i] - 20, 0)) : mt_rand(min($foreground_color[$i] + 20, 255), 255); + $randomness_color = imagecolorallocate($code_image, $dotbgcolor[0], $dotbgcolor[1], $dotbgcolor[2]); + + // Some squares/rectanges for new extreme level + if ($noiseType == 'extreme') + { + for ($i = 0; $i < rand(1, 5); $i++) + { + $x1 = rand(0, $total_width / 4); + $x2 = $x1 + round(rand($total_width / 4, $total_width)); + $y1 = rand(0, $max_height); + $y2 = $y1 + round(rand(0, $max_height / 3)); + imagefilledrectangle($code_image, $x1, $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color); + } + } + + // Fill in the characters. + if (!$disableChars) + { + $cur_x = 0; + foreach ($characters as $char_index => $character) + { + // Can we use true type fonts? + $can_do_ttf = function_exists('imagettftext'); + + // How much rotation will we give? + if ($rotationType == 'none') + $angle = 0; + else + $angle = mt_rand(-100, 100) / ($rotationType == 'high' ? 6 : 10); + + // What color shall we do it? + if ($fontColorType == 'cyclic') + { + // Here we'll pick from a set of acceptance types. + $colors = array( + array(10, 120, 95), + array(46, 81, 29), + array(4, 22, 154), + array(131, 9, 130), + array(0, 0, 0), + array(143, 39, 31), + ); + if (!isset($last_index)) + $last_index = -1; + $new_index = $last_index; + while ($last_index == $new_index) + $new_index = mt_rand(0, count($colors) - 1); + $char_fg_color = $colors[$new_index]; + $last_index = $new_index; + } + elseif ($fontColorType == 'random') + $char_fg_color = array(mt_rand(max($foreground_color[0] - 2, 0), $foreground_color[0]), mt_rand(max($foreground_color[1] - 2, 0), $foreground_color[1]), mt_rand(max($foreground_color[2] - 2, 0), $foreground_color[2])); + else + $char_fg_color = array($foreground_color[0], $foreground_color[1], $foreground_color[2]); + + if (!empty($can_do_ttf)) + { + // GD2 handles font size differently. + if ($fontSizeRandom) + $font_size = $gd2 ? mt_rand(17, 19) : mt_rand(18, 25); + else + $font_size = $gd2 ? 18 : 24; + + // Work out the sizes - also fix the character width cause TTF not quite so wide! + $font_x = $fontHorSpace == 'minus' && $cur_x > 0 ? $cur_x - 3 : $cur_x + 5; + $font_y = $max_height - ($fontVerPos == 'vrandom' ? mt_rand(2, 8) : ($fontVerPos == 'random' ? mt_rand(3, 5) : 5)); + + // What font face? + if (!empty($ttfont_list)) + $fontface = $settings['default_theme_dir'] . '/fonts/' . $ttfont_list[mt_rand(0, count($ttfont_list) - 1)]; + + // What color are we to do it in? + $is_reverse = $showReverseChars ? mt_rand(0, 1) : false; + $char_color = function_exists('imagecolorallocatealpha') && $fontTrans ? imagecolorallocatealpha($code_image, $char_fg_color[0], $char_fg_color[1], $char_fg_color[2], 50) : imagecolorallocate($code_image, $char_fg_color[0], $char_fg_color[1], $char_fg_color[2]); + + $fontcord = @imagettftext($code_image, $font_size, $angle, $font_x, $font_y, $char_color, $fontface, $character['id']); + if (empty($fontcord)) + $can_do_ttf = false; + elseif ($is_reverse) + { + imagefilledpolygon($code_image, $fontcord, 4, $fg_color); + // Put the character back! + imagettftext($code_image, $font_size, $angle, $font_x, $font_y, $randomness_color, $fontface, $character['id']); + } + + if ($can_do_ttf) + $cur_x = max($fontcord[2], $fontcord[4]) + ($angle == 0 ? 0 : 3); + } + + if (!$can_do_ttf) + { + // Rotating the characters a little... + if (function_exists('imagerotate')) + { + $char_image = $gd2 ? imagecreatetruecolor($character['width'], $character['height']) : imagecreate($character['width'], $character['height']); + $char_bgcolor = imagecolorallocate($char_image, $background_color[0], $background_color[1], $background_color[2]); + imagefilledrectangle($char_image, 0, 0, $character['width'] - 1, $character['height'] - 1, $char_bgcolor); + imagechar($char_image, $loaded_fonts[$character['font']], 0, 0, $character['id'], imagecolorallocate($char_image, $char_fg_color[0], $char_fg_color[1], $char_fg_color[2])); + $rotated_char = imagerotate($char_image, mt_rand(-100, 100) / 10, $char_bgcolor); + imagecopy($code_image, $rotated_char, $cur_x, 0, 0, 0, $character['width'], $character['height']); + imagedestroy($rotated_char); + imagedestroy($char_image); + } + + // Sorry, no rotation available. + else + imagechar($code_image, $loaded_fonts[$character['font']], $cur_x, floor(($max_height - $character['height']) / 2), $character['id'], imagecolorallocate($code_image, $char_fg_color[0], $char_fg_color[1], $char_fg_color[2])); + $cur_x += $character['width'] + $character_spacing; + } + } + } + // If disabled just show a cross. + else + { + imageline($code_image, 0, 0, $total_width, $max_height, $fg_color); + imageline($code_image, 0, $max_height, $total_width, 0, $fg_color); + } + + // Make the background color transparent on the hard image. + if (!$simpleBGColor) + imagecolortransparent($code_image, $bg_color); + if ($hasBorder) + imagerectangle($code_image, 0, 0, $total_width - 1, $max_height - 1, $fg_color); + + // Add some noise to the background? + if ($noiseType != 'none') + { + for ($i = mt_rand(0, 2); $i < $max_height; $i += mt_rand(1, 2)) + for ($j = mt_rand(0, 10); $j < $total_width; $j += mt_rand(1, 10)) + imagesetpixel($code_image, $j, $i, mt_rand(0, 1) ? $fg_color : $randomness_color); + + // Put in some lines too? + if ($noiseType != 'extreme') + { + $num_lines = $noiseType == 'high' ? mt_rand(3, 7) : mt_rand(2, 5); + for ($i = 0; $i < $num_lines; $i++) + { + if (mt_rand(0, 1)) + { + $x1 = mt_rand(0, $total_width); + $x2 = mt_rand(0, $total_width); + $y1 = 0; $y2 = $max_height; + } + else + { + $y1 = mt_rand(0, $max_height); + $y2 = mt_rand(0, $max_height); + $x1 = 0; $x2 = $total_width; + } + imagesetthickness($code_image, mt_rand(1, 2)); + imageline($code_image, $x1, $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color); + } + } + else + { + // Put in some ellipse + $num_ellipse = $noiseType == 'extreme' ? mt_rand(6, 12) : mt_rand(2, 6); + for ($i = 0; $i < $num_ellipse; $i++) + { + $x1 = round(rand(($total_width / 4) * -1, $total_width + ($total_width / 4))); + $x2 = round(rand($total_width / 2, 2 * $total_width)); + $y1 = round(rand(($max_height / 4) * -1, $max_height + ($max_height / 4))); + $y2 = round(rand($max_height / 2, 2 * $max_height)); + imageellipse($code_image, $x1, $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color); + } + } + } + + // Show the image. + if (function_exists('imagegif')) + { + header('Content-type: image/gif'); + imagegif($code_image); + } + else + { + header('Content-type: image/png'); + imagepng($code_image); + } + + // Bail out. + imagedestroy($code_image); + die(); +} + +// Create a letter for the visual verification code. +function showLetterImage($letter) +{ + global $settings; + + if (!is_dir($settings['default_theme_dir'] . '/fonts')) + return false; + + // Get a list of the available font directories. + $font_dir = dir($settings['default_theme_dir'] . '/fonts'); + $font_list = array(); + while ($entry = $font_dir->read()) + if ($entry[0] !== '.' && is_dir($settings['default_theme_dir'] . '/fonts/' . $entry) && file_exists($settings['default_theme_dir'] . '/fonts/' . $entry . '.gdf')) + $font_list[] = $entry; + + if (empty($font_list)) + return false; + + // Pick a random font. + $random_font = $font_list[array_rand($font_list)]; + + // Check if the given letter exists. + if (!file_exists($settings['default_theme_dir'] . '/fonts/' . $random_font . '/' . $letter . '.gif')) + return false; + + // Include it! + header('Content-type: image/gif'); + include($settings['default_theme_dir'] . '/fonts/' . $random_font . '/' . $letter . '.gif'); + + // Nothing more to come. + die(); +} + +?> \ No newline at end of file diff --git a/Sources/Subs-List.php b/Sources/Subs-List.php new file mode 100644 index 0000000..09a9494 --- /dev/null +++ b/Sources/Subs-List.php @@ -0,0 +1,235 @@ + $_REQUEST[$request_var_sort], + 'desc' => isset($_REQUEST[$request_var_desc]) && isset($listOptions['columns'][$_REQUEST[$request_var_sort]]['sort']['reverse']), + ); + else + $list_context['sort'] = array( + 'id' => $listOptions['default_sort_col'], + 'desc' => (!empty($listOptions['default_sort_dir']) && $listOptions['default_sort_dir'] == 'desc') || (!empty($listOptions['columns'][$listOptions['default_sort_col']]['sort']['default']) && substr($listOptions['columns'][$listOptions['default_sort_col']]['sort']['default'], -4, 4) == 'desc') ? true : false, + ); + + // Set the database column sort. + $sort = $listOptions['columns'][$list_context['sort']['id']]['sort'][$list_context['sort']['desc'] ? 'reverse' : 'default']; + } + + $list_context['start_var_name'] = isset($listOptions['start_var_name']) ? $listOptions['start_var_name'] : 'start'; + // In some cases the full list must be shown, regardless of the amount of items. + if (empty($listOptions['items_per_page'])) + { + $list_context['start'] = 0; + $list_context['items_per_page'] = 0; + } + // With items per page set, calculate total number of items and page index. + else + { + // First get an impression of how many items to expect. + if (isset($listOptions['get_count']['file'])) + require_once($listOptions['get_count']['file']); + $list_context['total_num_items'] = call_user_func_array($listOptions['get_count']['function'], empty($listOptions['get_count']['params']) ? array() : $listOptions['get_count']['params']); + + // Default the start to the beginning...sounds logical. + $list_context['start'] = isset($_REQUEST[$list_context['start_var_name']]) ? (int) $_REQUEST[$list_context['start_var_name']] : 0; + $list_context['items_per_page'] = $listOptions['items_per_page']; + + // Then create a page index. + $list_context['page_index'] = constructPageIndex($listOptions['base_href'] . (empty($list_context['sort']) ? '' : ';' . $request_var_sort . '=' . $list_context['sort']['id'] . ($list_context['sort']['desc'] ? ';' . $request_var_desc : '')) . ($list_context['start_var_name'] != 'start' ? ';' . $list_context['start_var_name'] . '=%1$d' : ''), $list_context['start'], $list_context['total_num_items'], $list_context['items_per_page'], $list_context['start_var_name'] != 'start'); + } + + // Prepare the headers of the table. + $list_context['headers'] = array(); + foreach ($listOptions['columns'] as $column_id => $column) + $list_context['headers'][] = array( + 'id' => $column_id, + 'label' => isset($column['header']['eval']) ? eval($column['header']['eval']) : (isset($column['header']['value']) ? $column['header']['value'] : ''), + 'href' => empty($listOptions['default_sort_col']) || empty($column['sort']) ? '' : $listOptions['base_href'] . ';' . $request_var_sort . '=' . $column_id . ($column_id === $list_context['sort']['id'] && !$list_context['sort']['desc'] && isset($column['sort']['reverse']) ? ';' . $request_var_desc : '') . (empty($list_context['start']) ? '' : ';' . $list_context['start_var_name'] . '=' . $list_context['start']), + 'sort_image' => empty($listOptions['default_sort_col']) || empty($column['sort']) || $column_id !== $list_context['sort']['id'] ? null : ($list_context['sort']['desc'] ? 'down' : 'up'), + 'class' => isset($column['header']['class']) ? $column['header']['class'] : '', + 'style' => isset($column['header']['style']) ? $column['header']['style'] : '', + 'colspan' => isset($column['header']['colspan']) ? $column['header']['colspan'] : '', + ); + + // We know the amount of columns, might be useful for the template. + $list_context['num_columns'] = count($listOptions['columns']); + $list_context['width'] = isset($listOptions['width']) ? $listOptions['width'] : '0'; + + // Get the file with the function for the item list. + if (isset($listOptions['get_items']['file'])) + require_once($listOptions['get_items']['file']); + + // Call the function and include which items we want and in what order. + $list_items = call_user_func_array($listOptions['get_items']['function'], array_merge(array($list_context['start'], $list_context['items_per_page'], $sort), empty($listOptions['get_items']['params']) ? array() : $listOptions['get_items']['params'])); + + // Loop through the list items to be shown and construct the data values. + $list_context['rows'] = array(); + foreach ($list_items as $item_id => $list_item) + { + $cur_row = array(); + foreach ($listOptions['columns'] as $column_id => $column) + { + $cur_data = array(); + + // A value straight from the database? + if (isset($column['data']['db'])) + $cur_data['value'] = $list_item[$column['data']['db']]; + + // Take the value from the database and make it HTML safe. + elseif (isset($column['data']['db_htmlsafe'])) + $cur_data['value'] = htmlspecialchars($list_item[$column['data']['db_htmlsafe']]); + + // Using sprintf is probably the most readable way of injecting data. + elseif (isset($column['data']['sprintf'])) + { + $params = array(); + foreach ($column['data']['sprintf']['params'] as $sprintf_param => $htmlsafe) + $params[] = $htmlsafe ? htmlspecialchars($list_item[$sprintf_param]) : $list_item[$sprintf_param]; + $cur_data['value'] = vsprintf($column['data']['sprintf']['format'], $params); + } + + // The most flexible way probably is applying a custom function. + elseif (isset($column['data']['function'])) + $cur_data['value'] = $column['data']['function']($list_item); + + // A modified value (inject the database values). + elseif (isset($column['data']['eval'])) + $cur_data['value'] = eval(preg_replace('~%([a-zA-Z0-9\-_]+)%~', '$list_item[\'$1\']', $column['data']['eval'])); + + // A literal value. + elseif (isset($column['data']['value'])) + $cur_data['value'] = $column['data']['value']; + + // Empty value. + else + $cur_data['value'] = ''; + + // Allow for basic formatting. + if (!empty($column['data']['comma_format'])) + $cur_data['value'] = comma_format($cur_data['value']); + elseif (!empty($column['data']['timeformat'])) + $cur_data['value'] = timeformat($cur_data['value']); + + // Set a style class for this column? + if (isset($column['data']['class'])) + $cur_data['class'] = $column['data']['class']; + + // Fully customized styling for the cells in this column only. + if (isset($column['data']['style'])) + $cur_data['style'] = $column['data']['style']; + + // Add the data cell properties to the current row. + $cur_row[$column_id] = $cur_data; + } + + // Insert the row into the list. + $list_context['rows'][$item_id] = $cur_row; + } + + // The title is currently optional. + if (isset($listOptions['title'])) + $list_context['title'] = $listOptions['title']; + + // In case there's a form, share it with the template context. + if (isset($listOptions['form'])) + { + $list_context['form'] = $listOptions['form']; + + if (!isset($list_context['form']['hidden_fields'])) + $list_context['form']['hidden_fields'] = array(); + + // Always add a session check field. + $list_context['form']['hidden_fields'][$context['session_var']] = $context['session_id']; + + // Include the starting page as hidden field? + if (!empty($list_context['form']['include_start']) && !empty($list_context['start'])) + $list_context['form']['hidden_fields'][$list_context['start_var_name']] = $list_context['start']; + + // If sorting needs to be the same after submitting, add the parameter. + if (!empty($list_context['form']['include_sort']) && !empty($list_context['sort'])) + { + $list_context['form']['hidden_fields']['sort'] = $list_context['sort']['id']; + if ($list_context['sort']['desc']) + $list_context['form']['hidden_fields']['desc'] = 1; + } + } + + // Wanna say something nice in case there are no items? + if (isset($listOptions['no_items_label'])) + { + $list_context['no_items_label'] = $listOptions['no_items_label']; + $list_context['no_items_align'] = isset($listOptions['no_items_align']) ? $listOptions['no_items_align'] : ''; + } + + // A list can sometimes need a few extra rows above and below. + if (isset($listOptions['additional_rows'])) + { + $list_context['additional_rows'] = array(); + foreach ($listOptions['additional_rows'] as $row) + { + if (empty($row)) + continue; + + // Supported row positions: top_of_list, after_title, + // above_column_headers, below_table_data, bottom_of_list. + if (!isset($list_context['additional_rows'][$row['position']])) + $list_context['additional_rows'][$row['position']] = array(); + $list_context['additional_rows'][$row['position']][] = $row; + } + } + + // Add an option for inline JavaScript. + if (isset($listOptions['javascript'])) + $list_context['javascript'] = $listOptions['javascript']; + + // We want a menu. + if (isset($listOptions['list_menu'])) + $list_context['list_menu'] = $listOptions['list_menu']; + + // Make sure the template is loaded. + loadTemplate('GenericList'); +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Membergroups.php b/Sources/Subs-Membergroups.php new file mode 100644 index 0000000..86bf1f8 --- /dev/null +++ b/Sources/Subs-Membergroups.php @@ -0,0 +1,757 @@ + $value) + $groups[$key] = (int) $value; + } + + // Some groups are protected (guests, administrators, moderators, newbies). + $protected_groups = array(-1, 0, 1, 3, 4); + + // There maybe some others as well. + if (!allowedTo('admin_forum')) + { + $request = $smcFunc['db_query']('', ' + SELECT id_group + FROM {db_prefix}membergroups + WHERE group_type = {int:is_protected}', + array( + 'is_protected' => 1, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $protected_groups[] = $row['id_group']; + $smcFunc['db_free_result']($request); + } + + // Make sure they don't delete protected groups! + $groups = array_diff($groups, array_unique($protected_groups)); + if (empty($groups)) + return false; + + // Log the deletion. + $request = $smcFunc['db_query']('', ' + SELECT group_name + FROM {db_prefix}membergroups + WHERE id_group IN ({array_int:group_list})', + array( + 'group_list' => $groups, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + logAction('delete_group', array('group' => $row['group_name']), 'admin'); + $smcFunc['db_free_result']($request); + + // Remove the membergroups themselves. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}membergroups + WHERE id_group IN ({array_int:group_list})', + array( + 'group_list' => $groups, + ) + ); + + // Remove the permissions of the membergroups. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}permissions + WHERE id_group IN ({array_int:group_list})', + array( + 'group_list' => $groups, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}board_permissions + WHERE id_group IN ({array_int:group_list})', + array( + 'group_list' => $groups, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}group_moderators + WHERE id_group IN ({array_int:group_list})', + array( + 'group_list' => $groups, + ) + ); + + // Delete any outstanding requests. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_group_requests + WHERE id_group IN ({array_int:group_list})', + array( + 'group_list' => $groups, + ) + ); + + // Update the primary groups of members. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET id_group = {int:regular_group} + WHERE id_group IN ({array_int:group_list})', + array( + 'group_list' => $groups, + 'regular_group' => 0, + ) + ); + + // Update any inherited groups (Lose inheritance). + $smcFunc['db_query']('', ' + UPDATE {db_prefix}membergroups + SET id_parent = {int:uninherited} + WHERE id_parent IN ({array_int:group_list})', + array( + 'group_list' => $groups, + 'uninherited' => -2, + ) + ); + + // Update the additional groups of members. + $request = $smcFunc['db_query']('', ' + SELECT id_member, additional_groups + FROM {db_prefix}members + WHERE FIND_IN_SET({raw:additional_groups_explode}, additional_groups) != 0', + array( + 'additional_groups_explode' => implode(', additional_groups) != 0 OR FIND_IN_SET(', $groups), + ) + ); + $updates = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $updates[$row['additional_groups']][] = $row['id_member']; + $smcFunc['db_free_result']($request); + + foreach ($updates as $additional_groups => $memberArray) + updateMemberData($memberArray, array('additional_groups' => implode(',', array_diff(explode(',', $additional_groups), $groups)))); + + // No boards can provide access to these membergroups anymore. + $request = $smcFunc['db_query']('', ' + SELECT id_board, member_groups + FROM {db_prefix}boards + WHERE FIND_IN_SET({raw:member_groups_explode}, member_groups) != 0', + array( + 'member_groups_explode' => implode(', member_groups) != 0 OR FIND_IN_SET(', $groups), + ) + ); + $updates = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $updates[$row['member_groups']][] = $row['id_board']; + $smcFunc['db_free_result']($request); + + foreach ($updates as $member_groups => $boardArray) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET member_groups = {string:member_groups} + WHERE id_board IN ({array_int:board_lists})', + array( + 'board_lists' => $boardArray, + 'member_groups' => implode(',', array_diff(explode(',', $member_groups), $groups)), + ) + ); + + // Recalculate the post groups, as they likely changed. + updateStats('postgroups'); + + // Make a note of the fact that the cache may be wrong. + $settings_update = array('settings_updated' => time()); + // Have we deleted the spider group? + if (isset($modSettings['spider_group']) && in_array($modSettings['spider_group'], $groups)) + $settings_update['spider_group'] = 0; + + updateSettings($settings_update); + + // It was a success. + return true; +} + +// Remove one or more members from one or more membergroups. +function removeMembersFromGroups($members, $groups = null, $permissionCheckDone = false) +{ + global $smcFunc, $user_info, $modSettings; + + // You're getting nowhere without this permission, unless of course you are the group's moderator. + if (!$permissionCheckDone) + isAllowedTo('manage_membergroups'); + + // Assume something will happen. + updateSettings(array('settings_updated' => time())); + + // Cleaning the input. + if (!is_array($members)) + $members = array((int) $members); + else + { + $members = array_unique($members); + + // Cast the members to integer. + foreach ($members as $key => $value) + $members[$key] = (int) $value; + } + + // Before we get started, let's check we won't leave the admin group empty! + if ($groups === null || $groups == 1 || (is_array($groups) && in_array(1, $groups))) + { + $admins = array(); + listMembergroupMembers_Href($admins, 1); + + // Remove any admins if there are too many. + $non_changing_admins = array_diff(array_keys($admins), $members); + + if (empty($non_changing_admins)) + $members = array_diff($members, array_keys($admins)); + } + + // Just in case. + if (empty($members)) + return false; + elseif ($groups === null) + { + // Wanna remove all groups from these members? That's easy. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET + id_group = {int:regular_member}, + additional_groups = {string:blank_string} + WHERE id_member IN ({array_int:member_list})' . (allowedTo('admin_forum') ? '' : ' + AND id_group != {int:admin_group} + AND FIND_IN_SET({int:admin_group}, additional_groups) = 0'), + array( + 'member_list' => $members, + 'regular_member' => 0, + 'admin_group' => 1, + 'blank_string' => '', + ) + ); + + updateStats('postgroups', $members); + + // Log what just happened. + foreach ($members as $member) + logAction('removed_all_groups', array('member' => $member), 'admin'); + + return true; + } + elseif (!is_array($groups)) + $groups = array((int) $groups); + else + { + $groups = array_unique($groups); + + // Make sure all groups are integer. + foreach ($groups as $key => $value) + $groups[$key] = (int) $value; + } + + // Fetch a list of groups members cannot be assigned to explicitely, and the group names of the ones we want. + $implicitGroups = array(-1, 0, 3); + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_name, min_posts + FROM {db_prefix}membergroups + WHERE id_group IN ({array_int:group_list})', + array( + 'group_list' => $groups, + ) + ); + $group_names = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['min_posts'] != -1) + $implicitGroups[] = $row['id_group']; + else + $group_names[$row['id_group']] = $row['group_name']; + } + $smcFunc['db_free_result']($request); + + // Now get rid of those groups. + $groups = array_diff($groups, $implicitGroups); + + // Don't forget the protected groups. + if (!allowedTo('admin_forum')) + { + $request = $smcFunc['db_query']('', ' + SELECT id_group + FROM {db_prefix}membergroups + WHERE group_type = {int:is_protected}', + array( + 'is_protected' => 1, + ) + ); + $protected_groups = array(1); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $protected_groups[] = $row['id_group']; + $smcFunc['db_free_result']($request); + + // If you're not an admin yourself, you can't touch protected groups! + $groups = array_diff($groups, array_unique($protected_groups)); + } + + // Only continue if there are still groups and members left. + if (empty($groups) || empty($members)) + return false; + + // First, reset those who have this as their primary group - this is the easy one. + $log_inserts = array(); + $request = $smcFunc['db_query']('', ' + SELECT id_member, id_group + FROM {db_prefix}members AS members + WHERE id_group IN ({array_int:group_list}) + AND id_member IN ({array_int:member_list})', + array( + 'group_list' => $groups, + 'member_list' => $members, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $log_inserts[] = array( + time(), 3, $user_info['id'], $user_info['ip'], 'removed_from_group', + 0, 0, 0, serialize(array('group' => $group_names[$row['id_group']], 'member' => $row['id_member'])), + ); + $smcFunc['db_free_result']($request); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET id_group = {int:regular_member} + WHERE id_group IN ({array_int:group_list}) + AND id_member IN ({array_int:member_list})', + array( + 'group_list' => $groups, + 'member_list' => $members, + 'regular_member' => 0, + ) + ); + + // Those who have it as part of their additional group must be updated the long way... sadly. + $request = $smcFunc['db_query']('', ' + SELECT id_member, additional_groups + FROM {db_prefix}members + WHERE (FIND_IN_SET({raw:additional_groups_implode}, additional_groups) != 0) + AND id_member IN ({array_int:member_list}) + LIMIT ' . count($members), + array( + 'member_list' => $members, + 'additional_groups_implode' => implode(', additional_groups) != 0 OR FIND_IN_SET(', $groups), + ) + ); + $updates = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // What log entries must we make for this one, eh? + foreach (explode(',', $row['additional_groups']) as $group) + if (in_array($group, $groups)) + $log_inserts[] = array( + time(), 3, $user_info['id'], $user_info['ip'], 'removed_from_group', + 0, 0, 0, serialize(array('group' => $group_names[$group], 'member' => $row['id_member'])), + ); + + $updates[$row['additional_groups']][] = $row['id_member']; + } + $smcFunc['db_free_result']($request); + + foreach ($updates as $additional_groups => $memberArray) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET additional_groups = {string:additional_groups} + WHERE id_member IN ({array_int:member_list})', + array( + 'member_list' => $memberArray, + 'additional_groups' => implode(',', array_diff(explode(',', $additional_groups), $groups)), + ) + ); + + // Their post groups may have changed now... + updateStats('postgroups', $members); + + // Do the log. + if (!empty($log_inserts) && !empty($modSettings['modlog_enabled'])) + $smcFunc['db_insert']('', + '{db_prefix}log_actions', + array( + 'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'string-16', 'action' => 'string', + 'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534', + ), + $log_inserts, + array('id_action') + ); + + // Mission successful. + return true; +} + +// Add one or more members to a membergroup. +/* Supported types: + - only_primary - Assigns a membergroup as primary membergroup, but only + if a member has not yet a primary membergroup assigned, + unless the member is already part of the membergroup. + - only_additional - Assigns a membergroup to the additional membergroups, + unless the member is already part of the membergroup. + - force_primary - Assigns a membergroup as primary membergroup no matter + what the previous primary membergroup was. + - auto - Assigns a membergroup to the primary group if it's still + available. If not, assign it to the additional group. */ +function addMembersToGroup($members, $group, $type = 'auto', $permissionCheckDone = false) +{ + global $smcFunc, $user_info, $modSettings; + + // Show your licence, but only if it hasn't been done yet. + if (!$permissionCheckDone) + isAllowedTo('manage_membergroups'); + + // Make sure we don't keep old stuff cached. + updateSettings(array('settings_updated' => time())); + + if (!is_array($members)) + $members = array((int) $members); + else + { + $members = array_unique($members); + + // Make sure all members are integer. + foreach ($members as $key => $value) + $members[$key] = (int) $value; + } + $group = (int) $group; + + // Some groups just don't like explicitly having members. + $implicitGroups = array(-1, 0, 3); + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_name, min_posts + FROM {db_prefix}membergroups + WHERE id_group = {int:current_group}', + array( + 'current_group' => $group, + ) + ); + $group_names = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['min_posts'] != -1) + $implicitGroups[] = $row['id_group']; + else + $group_names[$row['id_group']] = $row['group_name']; + } + $smcFunc['db_free_result']($request); + + // Sorry, you can't join an implicit group. + if (in_array($group, $implicitGroups) || empty($members)) + return false; + + // Only admins can add admins... + if (!allowedTo('admin_forum') && $group == 1) + return false; + // ... and assign protected groups! + elseif (!allowedTo('admin_forum')) + { + $request = $smcFunc['db_query']('', ' + SELECT group_type + FROM {db_prefix}membergroups + WHERE id_group = {int:current_group} + LIMIT {int:limit}', + array( + 'current_group' => $group, + 'limit' => 1, + ) + ); + list ($is_protected) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Is it protected? + if ($is_protected == 1) + return false; + } + + // Do the actual updates. + if ($type == 'only_additional') + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET additional_groups = CASE WHEN additional_groups = {string:blank_string} THEN {string:id_group_string} ELSE CONCAT(additional_groups, {string:id_group_string_extend}) END + WHERE id_member IN ({array_int:member_list}) + AND id_group != {int:id_group} + AND FIND_IN_SET({int:id_group}, additional_groups) = 0', + array( + 'member_list' => $members, + 'id_group' => $group, + 'id_group_string' => (string) $group, + 'id_group_string_extend' => ',' . $group, + 'blank_string' => '', + ) + ); + elseif ($type == 'only_primary' || $type == 'force_primary') + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET id_group = {int:id_group} + WHERE id_member IN ({array_int:member_list})' . ($type == 'force_primary' ? '' : ' + AND id_group = {int:regular_group} + AND FIND_IN_SET({int:id_group}, additional_groups) = 0'), + array( + 'member_list' => $members, + 'id_group' => $group, + 'regular_group' => 0, + ) + ); + elseif ($type == 'auto') + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET + id_group = CASE WHEN id_group = {int:regular_group} THEN {int:id_group} ELSE id_group END, + additional_groups = CASE WHEN id_group = {int:id_group} THEN additional_groups + WHEN additional_groups = {string:blank_string} THEN {string:id_group_string} + ELSE CONCAT(additional_groups, {string:id_group_string_extend}) END + WHERE id_member IN ({array_int:member_list}) + AND id_group != {int:id_group} + AND FIND_IN_SET({int:id_group}, additional_groups) = 0', + array( + 'member_list' => $members, + 'regular_group' => 0, + 'id_group' => $group, + 'blank_string' => '', + 'id_group_string' => (string) $group, + 'id_group_string_extend' => ',' . $group, + ) + ); + // Ack!!? What happened? + else + trigger_error('addMembersToGroup(): Unknown type \'' . $type . '\'', E_USER_WARNING); + + // Update their postgroup statistics. + updateStats('postgroups', $members); + + // Log the data. + $log_inserts = array(); + foreach ($members as $member) + $log_inserts[] = array( + time(), 3, $user_info['id'], $user_info['ip'], 'added_to_group', + 0, 0, 0, serialize(array('group' => $group_names[$group], 'member' => $member)), + ); + + if (!empty($log_inserts) && !empty($modSettings['modlog_enabled'])) + $smcFunc['db_insert']('', + '{db_prefix}log_actions', + array( + 'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'string-16', 'action' => 'string', + 'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534', + ), + $log_inserts, + array('id_action') + ); + + return true; +} + +function listMembergroupMembers_Href(&$members, $membergroup, $limit = null) +{ + global $scripturl, $txt, $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT id_member, real_name + FROM {db_prefix}members + WHERE id_group = {int:id_group} OR FIND_IN_SET({int:id_group}, additional_groups) != 0' . ($limit === null ? '' : ' + LIMIT ' . ($limit + 1)), + array( + 'id_group' => $membergroup, + ) + ); + $members = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $members[$row['id_member']] = '' . $row['real_name'] . ''; + $smcFunc['db_free_result']($request); + + // If there are more than $limit members, add a 'more' link. + if ($limit !== null && count($members) > $limit) + { + array_pop($members); + return true; + } + else + return false; +} + +// Retrieve a list of (visible) membergroups used by the cache. +function cache_getMembergroupList() +{ + global $scripturl, $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT id_group, group_name, online_color + FROM {db_prefix}membergroups + WHERE min_posts = {int:min_posts} + AND hidden = {int:not_hidden} + AND id_group != {int:mod_group} + AND online_color != {string:blank_string} + ORDER BY group_name', + array( + 'min_posts' => -1, + 'not_hidden' => 0, + 'mod_group' => 3, + 'blank_string' => '', + ) + ); + $groupCache = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $groupCache[] = '' . $row['group_name'] . ''; + $smcFunc['db_free_result']($request); + + return array( + 'data' => $groupCache, + 'expires' => time() + 3600, + 'refresh_eval' => 'return $GLOBALS[\'modSettings\'][\'settings_updated\'] > ' . time() . ';', + ); +} + +function list_getMembergroups($start, $items_per_page, $sort, $membergroup_type) +{ + global $txt, $scripturl, $context, $settings, $smcFunc; + + $groups = array(); + + // Get the basic group data. + $request = $smcFunc['db_query']('substring_membergroups', ' + SELECT id_group, group_name, min_posts, online_color, stars, 0 AS num_members + FROM {db_prefix}membergroups + WHERE min_posts ' . ($membergroup_type === 'post_count' ? '!=' : '=') . ' -1' . (allowedTo('admin_forum') ? '' : ' + AND group_type != {int:is_protected}') . ' + ORDER BY {raw:sort}', + array( + 'is_protected' => 1, + 'sort' => $sort, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $groups[$row['id_group']] = array( + 'id_group' => $row['id_group'], + 'group_name' => $row['group_name'], + 'min_posts' => $row['min_posts'], + 'online_color' => $row['online_color'], + 'stars' => $row['stars'], + 'num_members' => $row['num_members'], + ); + $smcFunc['db_free_result']($request); + + // If we found any membergroups, get the amount of members in them. + if (!empty($groups)) + { + if ($membergroup_type === 'post_count') + { + $query = $smcFunc['db_query']('', ' + SELECT id_post_group AS id_group, COUNT(*) AS num_members + FROM {db_prefix}members + WHERE id_post_group IN ({array_int:group_list}) + GROUP BY id_post_group', + array( + 'group_list' => array_keys($groups), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($query)) + $groups[$row['id_group']]['num_members'] += $row['num_members']; + $smcFunc['db_free_result']($query); + } + + else + { + $query = $smcFunc['db_query']('', ' + SELECT id_group, COUNT(*) AS num_members + FROM {db_prefix}members + WHERE id_group IN ({array_int:group_list}) + GROUP BY id_group', + array( + 'group_list' => array_keys($groups), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($query)) + $groups[$row['id_group']]['num_members'] += $row['num_members']; + $smcFunc['db_free_result']($query); + + $query = $smcFunc['db_query']('', ' + SELECT mg.id_group, COUNT(*) AS num_members + FROM {db_prefix}membergroups AS mg + INNER JOIN {db_prefix}members AS mem ON (mem.additional_groups != {string:blank_string} + AND mem.id_group != mg.id_group + AND FIND_IN_SET(mg.id_group, mem.additional_groups) != 0) + WHERE mg.id_group IN ({array_int:group_list}) + GROUP BY mg.id_group', + array( + 'group_list' => array_keys($groups), + 'blank_string' => '', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($query)) + $groups[$row['id_group']]['num_members'] += $row['num_members']; + $smcFunc['db_free_result']($query); + } + } + + // Apply manual sorting if the 'number of members' column is selected. + if (substr($sort, 0, 1) == '1' || strpos($sort, ', 1') !== false) + { + $sort_ascending = strpos($sort, 'DESC') === false; + + foreach ($groups as $group) + $sort_array[] = $group['id_group'] != 3 ? (int) $group['num_members'] : -1; + + array_multisort($sort_array, $sort_ascending ? SORT_ASC : SORT_DESC, SORT_REGULAR, $groups); + } + + return $groups; +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Members.php b/Sources/Subs-Members.php new file mode 100644 index 0000000..754e992 --- /dev/null +++ b/Sources/Subs-Members.php @@ -0,0 +1,1392 @@ + $v) + $users[$k] = (int) $v; + + // Deleting more than one? You can't have more than one account... + isAllowedTo('profile_remove_any'); + } + + // Get their names for logging purposes. + $request = $smcFunc['db_query']('', ' + SELECT id_member, member_name, CASE WHEN id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0 THEN 1 ELSE 0 END AS is_admin + FROM {db_prefix}members + WHERE id_member IN ({array_int:user_list}) + LIMIT ' . count($users), + array( + 'user_list' => $users, + 'admin_group' => 1, + ) + ); + $admins = array(); + $user_log_details = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['is_admin']) + $admins[] = $row['id_member']; + $user_log_details[$row['id_member']] = array($row['id_member'], $row['member_name']); + } + $smcFunc['db_free_result']($request); + + if (empty($user_log_details)) + return; + + // Make sure they aren't trying to delete administrators if they aren't one. But don't bother checking if it's just themself. + if (!empty($admins) && ($check_not_admin || (!allowedTo('admin_forum') && (count($users) != 1 || $users[0] != $user_info['id'])))) + { + $users = array_diff($users, $admins); + foreach ($admins as $id) + unset($user_log_details[$id]); + } + + // No one left? + if (empty($users)) + return; + + // Log the action - regardless of who is deleting it. + $log_inserts = array(); + foreach ($user_log_details as $user) + { + // Integration rocks! + call_integration_hook('integrate_delete_member', array($user[0])); + + // Add it to the administration log for future reference. + $log_inserts[] = array( + time(), 3, $user_info['id'], $user_info['ip'], 'delete_member', + 0, 0, 0, serialize(array('member' => $user[0], 'name' => $user[1], 'member_acted' => $user_info['name'])), + ); + + // Remove any cached data if enabled. + if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2) + cache_put_data('user_settings-' . $user[0], null, 60); + } + + // Do the actual logging... + if (!empty($log_inserts) && !empty($modSettings['modlog_enabled'])) + $smcFunc['db_insert']('', + '{db_prefix}log_actions', + array( + 'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'string-16', 'action' => 'string', + 'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534', + ), + $log_inserts, + array('id_action') + ); + + // Make these peoples' posts guest posts. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET id_member = {int:guest_id}, poster_email = {string:blank_email} + WHERE id_member IN ({array_int:users})', + array( + 'guest_id' => 0, + 'blank_email' => '', + 'users' => $users, + ) + ); + $smcFunc['db_query']('', ' + UPDATE {db_prefix}polls + SET id_member = {int:guest_id} + WHERE id_member IN ({array_int:users})', + array( + 'guest_id' => 0, + 'users' => $users, + ) + ); + + // Make these peoples' posts guest first posts and last posts. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET id_member_started = {int:guest_id} + WHERE id_member_started IN ({array_int:users})', + array( + 'guest_id' => 0, + 'users' => $users, + ) + ); + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET id_member_updated = {int:guest_id} + WHERE id_member_updated IN ({array_int:users})', + array( + 'guest_id' => 0, + 'users' => $users, + ) + ); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_actions + SET id_member = {int:guest_id} + WHERE id_member IN ({array_int:users})', + array( + 'guest_id' => 0, + 'users' => $users, + ) + ); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_banned + SET id_member = {int:guest_id} + WHERE id_member IN ({array_int:users})', + array( + 'guest_id' => 0, + 'users' => $users, + ) + ); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_errors + SET id_member = {int:guest_id} + WHERE id_member IN ({array_int:users})', + array( + 'guest_id' => 0, + 'users' => $users, + ) + ); + + // Delete the member. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}members + WHERE id_member IN ({array_int:users})', + array( + 'users' => $users, + ) + ); + + // Delete the logs... + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_actions + WHERE id_log = {int:log_type} + AND id_member IN ({array_int:users})', + array( + 'log_type' => 2, + 'users' => $users, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_boards + WHERE id_member IN ({array_int:users})', + array( + 'users' => $users, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_comments + WHERE id_recipient IN ({array_int:users}) + AND comment_type = {string:warntpl}', + array( + 'users' => $users, + 'warntpl' => 'warntpl', + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_group_requests + WHERE id_member IN ({array_int:users})', + array( + 'users' => $users, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_karma + WHERE id_target IN ({array_int:users}) + OR id_executor IN ({array_int:users})', + array( + 'users' => $users, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_mark_read + WHERE id_member IN ({array_int:users})', + array( + 'users' => $users, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_notify + WHERE id_member IN ({array_int:users})', + array( + 'users' => $users, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_online + WHERE id_member IN ({array_int:users})', + array( + 'users' => $users, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_subscribed + WHERE id_member IN ({array_int:users})', + array( + 'users' => $users, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_topics + WHERE id_member IN ({array_int:users})', + array( + 'users' => $users, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}collapsed_categories + WHERE id_member IN ({array_int:users})', + array( + 'users' => $users, + ) + ); + + // Make their votes appear as guest votes - at least it keeps the totals right. + //!!! Consider adding back in cookie protection. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_polls + SET id_member = {int:guest_id} + WHERE id_member IN ({array_int:users})', + array( + 'guest_id' => 0, + 'users' => $users, + ) + ); + + // Delete personal messages. + require_once($sourcedir . '/PersonalMessage.php'); + deleteMessages(null, null, $users); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}personal_messages + SET id_member_from = {int:guest_id} + WHERE id_member_from IN ({array_int:users})', + array( + 'guest_id' => 0, + 'users' => $users, + ) + ); + + // They no longer exist, so we don't know who it was sent to. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}pm_recipients + WHERE id_member IN ({array_int:users})', + array( + 'users' => $users, + ) + ); + + // Delete avatar. + require_once($sourcedir . '/ManageAttachments.php'); + removeAttachments(array('id_member' => $users)); + + // It's over, no more moderation for you. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}moderators + WHERE id_member IN ({array_int:users})', + array( + 'users' => $users, + ) + ); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}group_moderators + WHERE id_member IN ({array_int:users})', + array( + 'users' => $users, + ) + ); + + // If you don't exist we can't ban you. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}ban_items + WHERE id_member IN ({array_int:users})', + array( + 'users' => $users, + ) + ); + + // Remove individual theme settings. + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}themes + WHERE id_member IN ({array_int:users})', + array( + 'users' => $users, + ) + ); + + // These users are nobody's buddy nomore. + $request = $smcFunc['db_query']('', ' + SELECT id_member, pm_ignore_list, buddy_list + FROM {db_prefix}members + WHERE FIND_IN_SET({raw:pm_ignore_list}, pm_ignore_list) != 0 OR FIND_IN_SET({raw:buddy_list}, buddy_list) != 0', + array( + 'pm_ignore_list' => implode(', pm_ignore_list) != 0 OR FIND_IN_SET(', $users), + 'buddy_list' => implode(', buddy_list) != 0 OR FIND_IN_SET(', $users), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET + pm_ignore_list = {string:pm_ignore_list}, + buddy_list = {string:buddy_list} + WHERE id_member = {int:id_member}', + array( + 'id_member' => $row['id_member'], + 'pm_ignore_list' => implode(',', array_diff(explode(',', $row['pm_ignore_list']), $users)), + 'buddy_list' => implode(',', array_diff(explode(',', $row['buddy_list']), $users)), + ) + ); + $smcFunc['db_free_result']($request); + + // Make sure no member's birthday is still sticking in the calendar... + updateSettings(array( + 'calendar_updated' => time(), + )); + + updateStats('member'); +} + +function registerMember(&$regOptions, $return_errors = false) +{ + global $scripturl, $txt, $modSettings, $context, $sourcedir; + global $user_info, $options, $settings, $smcFunc; + + loadLanguage('Login'); + + // We'll need some external functions. + require_once($sourcedir . '/Subs-Auth.php'); + require_once($sourcedir . '/Subs-Post.php'); + + // Put any errors in here. + $reg_errors = array(); + + // Registration from the admin center, let them sweat a little more. + if ($regOptions['interface'] == 'admin') + { + is_not_guest(); + isAllowedTo('moderate_forum'); + } + // If you're an admin, you're special ;). + elseif ($regOptions['interface'] == 'guest') + { + // You cannot register twice... + if (empty($user_info['is_guest'])) + redirectexit(); + + // Make sure they didn't just register with this session. + if (!empty($_SESSION['just_registered']) && empty($modSettings['disableRegisterCheck'])) + fatal_lang_error('register_only_once', false); + } + + // What method of authorization are we going to use? + if (empty($regOptions['auth_method']) || !in_array($regOptions['auth_method'], array('password', 'openid'))) + { + if (!empty($regOptions['openid'])) + $regOptions['auth_method'] = 'openid'; + else + $regOptions['auth_method'] = 'password'; + } + + // No name?! How can you register with no name? + if (empty($regOptions['username'])) + $reg_errors[] = array('lang', 'need_username'); + + // Spaces and other odd characters are evil... + $regOptions['username'] = trim(preg_replace('~[\t\n\r \x0B\0' . ($context['utf8'] ? ($context['server']['complex_preg_chars'] ? '\x{A0}\x{AD}\x{2000}-\x{200F}\x{201F}\x{202F}\x{3000}\x{FEFF}' : "\xC2\xA0\xC2\xAD\xE2\x80\x80-\xE2\x80\x8F\xE2\x80\x9F\xE2\x80\xAF\xE2\x80\x9F\xE3\x80\x80\xEF\xBB\xBF") : '\x00-\x08\x0B\x0C\x0E-\x19\xA0') . ']+~' . ($context['utf8'] ? 'u' : ''), ' ', $regOptions['username'])); + + // Don't use too long a name. + if ($smcFunc['strlen']($regOptions['username']) > 25) + $reg_errors[] = array('lang', 'error_long_name'); + + // Only these characters are permitted. + if (preg_match('~[<>&"\'=\\\\]~', preg_replace('~&#(?:\\d{1,7}|x[0-9a-fA-F]{1,6});~', '', $regOptions['username'])) != 0 || $regOptions['username'] == '_' || $regOptions['username'] == '|' || strpos($regOptions['username'], '[code') !== false || strpos($regOptions['username'], '[/code') !== false) + $reg_errors[] = array('lang', 'error_invalid_characters_username'); + + if ($smcFunc['strtolower']($regOptions['username']) === $smcFunc['strtolower']($txt['guest_title'])) + $reg_errors[] = array('lang', 'username_reserved', 'general', array($txt['guest_title'])); + + // !!! Separate the sprintf? + if (empty($regOptions['email']) || preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $regOptions['email']) === 0 || strlen($regOptions['email']) > 255) + $reg_errors[] = array('lang', 'profile_error_bad_email'); + + if (!empty($regOptions['check_reserved_name']) && isReservedName($regOptions['username'], 0, false)) + { + if ($regOptions['password'] == 'chocolate cake') + $reg_errors[] = array('done', 'Sorry, I don\'t take bribes... you\'ll need to come up with a different name.'); + $reg_errors[] = array('done', '(' . htmlspecialchars($regOptions['username']) . ') ' . $txt['name_in_use']); + } + + // Generate a validation code if it's supposed to be emailed. + $validation_code = ''; + if ($regOptions['require'] == 'activation') + $validation_code = generateValidationCode(); + + // If you haven't put in a password generate one. + if ($regOptions['interface'] == 'admin' && $regOptions['password'] == '' && $regOptions['auth_method'] == 'password') + { + mt_srand(time() + 1277); + $regOptions['password'] = generateValidationCode(); + $regOptions['password_check'] = $regOptions['password']; + } + // Does the first password match the second? + elseif ($regOptions['password'] != $regOptions['password_check'] && $regOptions['auth_method'] == 'password') + $reg_errors[] = array('lang', 'passwords_dont_match'); + + // That's kind of easy to guess... + if ($regOptions['password'] == '') + { + if ($regOptions['auth_method'] == 'password') + $reg_errors[] = array('lang', 'no_password'); + else + $regOptions['password'] = sha1(mt_rand()); + } + + // Now perform hard password validation as required. + if (!empty($regOptions['check_password_strength']) && $regOptions['password'] != '') + { + $passwordError = validatePassword($regOptions['password'], $regOptions['username'], array($regOptions['email'])); + + // Password isn't legal? + if ($passwordError != null) + $reg_errors[] = array('lang', 'profile_error_password_' . $passwordError); + } + + // If they are using an OpenID that hasn't been verified yet error out. + // !!! Change this so they can register without having to attempt a login first + if ($regOptions['auth_method'] == 'openid' && (empty($_SESSION['openid']['verified']) || $_SESSION['openid']['openid_uri'] != $regOptions['openid'])) + $reg_errors[] = array('lang', 'openid_not_verified'); + + // You may not be allowed to register this email. + if (!empty($regOptions['check_email_ban'])) + isBannedEmail($regOptions['email'], 'cannot_register', $txt['ban_register_prohibited']); + + // Check if the email address is in use. + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE email_address = {string:email_address} + OR email_address = {string:username} + LIMIT 1', + array( + 'email_address' => $regOptions['email'], + 'username' => $regOptions['username'], + ) + ); + // !!! Separate the sprintf? + if ($smcFunc['db_num_rows']($request) != 0) + $reg_errors[] = array('lang', 'email_in_use', false, array(htmlspecialchars($regOptions['email']))); + $smcFunc['db_free_result']($request); + + // If we found any errors we need to do something about it right away! + foreach ($reg_errors as $key => $error) + { + /* Note for each error: + 0 = 'lang' if it's an index, 'done' if it's clear text. + 1 = The text/index. + 2 = Whether to log. + 3 = sprintf data if necessary. */ + if ($error[0] == 'lang') + loadLanguage('Errors'); + $message = $error[0] == 'lang' ? (empty($error[3]) ? $txt[$error[1]] : vsprintf($txt[$error[1]], $error[3])) : $error[1]; + + // What to do, what to do, what to do. + if ($return_errors) + { + if (!empty($error[2])) + log_error($message, $error[2]); + $reg_errors[$key] = $message; + } + else + fatal_error($message, empty($error[2]) ? false : $error[2]); + } + + // If there's any errors left return them at once! + if (!empty($reg_errors)) + return $reg_errors; + + $reservedVars = array( + 'actual_theme_url', + 'actual_images_url', + 'base_theme_dir', + 'base_theme_url', + 'default_images_url', + 'default_theme_dir', + 'default_theme_url', + 'default_template', + 'images_url', + 'number_recent_posts', + 'smiley_sets_default', + 'theme_dir', + 'theme_id', + 'theme_layers', + 'theme_templates', + 'theme_url', + ); + + // Can't change reserved vars. + if (isset($regOptions['theme_vars']) && array_intersect($regOptions['theme_vars'], $reservedVars) != array()) + fatal_lang_error('no_theme'); + + // Some of these might be overwritten. (the lower ones that are in the arrays below.) + $regOptions['register_vars'] = array( + 'member_name' => $regOptions['username'], + 'email_address' => $regOptions['email'], + 'passwd' => sha1(strtolower($regOptions['username']) . $regOptions['password']), + 'password_salt' => substr(md5(mt_rand()), 0, 4) , + 'posts' => 0, + 'date_registered' => time(), + 'member_ip' => $regOptions['interface'] == 'admin' ? '127.0.0.1' : $user_info['ip'], + 'member_ip2' => $regOptions['interface'] == 'admin' ? '127.0.0.1' : $_SERVER['BAN_CHECK_IP'], + 'validation_code' => $validation_code, + 'real_name' => $regOptions['username'], + 'personal_text' => $modSettings['default_personal_text'], + 'pm_email_notify' => 1, + 'id_theme' => 0, + 'id_post_group' => 4, + 'lngfile' => '', + 'buddy_list' => '', + 'pm_ignore_list' => '', + 'message_labels' => '', + 'website_title' => '', + 'website_url' => '', + 'location' => '', + 'icq' => '', + 'aim' => '', + 'yim' => '', + 'msn' => '', + 'time_format' => '', + 'signature' => '', + 'avatar' => '', + 'usertitle' => '', + 'secret_question' => '', + 'secret_answer' => '', + 'additional_groups' => '', + 'ignore_boards' => '', + 'smiley_set' => '', + 'openid_uri' => (!empty($regOptions['openid']) ? $regOptions['openid'] : ''), + ); + + // Setup the activation status on this new account so it is correct - firstly is it an under age account? + if ($regOptions['require'] == 'coppa') + { + $regOptions['register_vars']['is_activated'] = 5; + // !!! This should be changed. To what should be it be changed?? + $regOptions['register_vars']['validation_code'] = ''; + } + // Maybe it can be activated right away? + elseif ($regOptions['require'] == 'nothing') + $regOptions['register_vars']['is_activated'] = 1; + // Maybe it must be activated by email? + elseif ($regOptions['require'] == 'activation') + $regOptions['register_vars']['is_activated'] = 0; + // Otherwise it must be awaiting approval! + else + $regOptions['register_vars']['is_activated'] = 3; + + if (isset($regOptions['memberGroup'])) + { + // Make sure the id_group will be valid, if this is an administator. + $regOptions['register_vars']['id_group'] = $regOptions['memberGroup'] == 1 && !allowedTo('admin_forum') ? 0 : $regOptions['memberGroup']; + + // Check if this group is assignable. + $unassignableGroups = array(-1, 3); + $request = $smcFunc['db_query']('', ' + SELECT id_group + FROM {db_prefix}membergroups + WHERE min_posts != {int:min_posts}' . (allowedTo('admin_forum') ? '' : ' + OR group_type = {int:is_protected}'), + array( + 'min_posts' => -1, + 'is_protected' => 1, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $unassignableGroups[] = $row['id_group']; + $smcFunc['db_free_result']($request); + + if (in_array($regOptions['register_vars']['id_group'], $unassignableGroups)) + $regOptions['register_vars']['id_group'] = 0; + } + + // ICQ cannot be zero. + if (isset($regOptions['extra_register_vars']['icq']) && empty($regOptions['extra_register_vars']['icq'])) + $regOptions['extra_register_vars']['icq'] = ''; + + // Integrate optional member settings to be set. + if (!empty($regOptions['extra_register_vars'])) + foreach ($regOptions['extra_register_vars'] as $var => $value) + $regOptions['register_vars'][$var] = $value; + + // Integrate optional user theme options to be set. + $theme_vars = array(); + if (!empty($regOptions['theme_vars'])) + foreach ($regOptions['theme_vars'] as $var => $value) + $theme_vars[$var] = $value; + + // Call an optional function to validate the users' input. + call_integration_hook('integrate_register', array(&$regOptions, &$theme_vars)); + + // Right, now let's prepare for insertion. + $knownInts = array( + 'date_registered', 'posts', 'id_group', 'last_login', 'instant_messages', 'unread_messages', + 'new_pm', 'pm_prefs', 'gender', 'hide_email', 'show_online', 'pm_email_notify', 'karma_good', 'karma_bad', + 'notify_announcements', 'notify_send_body', 'notify_regularity', 'notify_types', + 'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning', + ); + $knownFloats = array( + 'time_offset', + ); + + $column_names = array(); + $values = array(); + foreach ($regOptions['register_vars'] as $var => $val) + { + $type = 'string'; + if (in_array($var, $knownInts)) + $type = 'int'; + elseif (in_array($var, $knownFloats)) + $type = 'float'; + elseif ($var == 'birthdate') + $type = 'date'; + + $column_names[$var] = $type; + $values[$var] = $val; + } + + // Register them into the database. + $smcFunc['db_insert']('', + '{db_prefix}members', + $column_names, + $values, + array('id_member') + ); + $memberID = $smcFunc['db_insert_id']('{db_prefix}members', 'id_member'); + + // Update the number of members and latest member's info - and pass the name, but remove the 's. + if ($regOptions['register_vars']['is_activated'] == 1) + updateStats('member', $memberID, $regOptions['register_vars']['real_name']); + else + updateStats('member'); + + // Theme variables too? + if (!empty($theme_vars)) + { + $inserts = array(); + foreach ($theme_vars as $var => $val) + $inserts[] = array($memberID, $var, $val); + $smcFunc['db_insert']('insert', + '{db_prefix}themes', + array('id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'), + $inserts, + array('id_member', 'variable') + ); + } + + // If it's enabled, increase the registrations for today. + trackStats(array('registers' => '+')); + + // Administrative registrations are a bit different... + if ($regOptions['interface'] == 'admin') + { + if ($regOptions['require'] == 'activation') + $email_message = 'admin_register_activate'; + elseif (!empty($regOptions['send_welcome_email'])) + $email_message = 'admin_register_immediate'; + + if (isset($email_message)) + { + $replacements = array( + 'REALNAME' => $regOptions['register_vars']['real_name'], + 'USERNAME' => $regOptions['username'], + 'PASSWORD' => $regOptions['password'], + 'FORGOTPASSWORDLINK' => $scripturl . '?action=reminder', + 'ACTIVATIONLINK' => $scripturl . '?action=activate;u=' . $memberID . ';code=' . $validation_code, + 'ACTIVATIONLINKWITHOUTCODE' => $scripturl . '?action=activate;u=' . $memberID, + 'ACTIVATIONCODE' => $validation_code, + ); + + $emaildata = loadEmailTemplate($email_message, $replacements); + + sendmail($regOptions['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 0); + } + + // All admins are finished here. + return $memberID; + } + + // Can post straight away - welcome them to your fantastic community... + if ($regOptions['require'] == 'nothing') + { + if (!empty($regOptions['send_welcome_email'])) + { + $replacements = array( + 'REALNAME' => $regOptions['register_vars']['real_name'], + 'USERNAME' => $regOptions['username'], + 'PASSWORD' => $regOptions['password'], + 'FORGOTPASSWORDLINK' => $scripturl . '?action=reminder', + 'OPENID' => !empty($regOptions['openid']) ? $regOptions['openid'] : '', + ); + $emaildata = loadEmailTemplate('register_' . ($regOptions['auth_method'] == 'openid' ? 'openid_' : '') . 'immediate', $replacements); + sendmail($regOptions['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 0); + } + + // Send admin their notification. + adminNotify('standard', $memberID, $regOptions['username']); + } + // Need to activate their account - or fall under COPPA. + elseif ($regOptions['require'] == 'activation' || $regOptions['require'] == 'coppa') + { + $replacements = array( + 'REALNAME' => $regOptions['register_vars']['real_name'], + 'USERNAME' => $regOptions['username'], + 'PASSWORD' => $regOptions['password'], + 'FORGOTPASSWORDLINK' => $scripturl . '?action=reminder', + 'OPENID' => !empty($regOptions['openid']) ? $regOptions['openid'] : '', + ); + + if ($regOptions['require'] == 'activation') + $replacements += array( + 'ACTIVATIONLINK' => $scripturl . '?action=activate;u=' . $memberID . ';code=' . $validation_code, + 'ACTIVATIONLINKWITHOUTCODE' => $scripturl . '?action=activate;u=' . $memberID, + 'ACTIVATIONCODE' => $validation_code, + ); + else + $replacements += array( + 'COPPALINK' => $scripturl . '?action=coppa;u=' . $memberID, + ); + + $emaildata = loadEmailTemplate('register_' . ($regOptions['auth_method'] == 'openid' ? 'openid_' : '') . ($regOptions['require'] == 'activation' ? 'activate' : 'coppa'), $replacements); + + sendmail($regOptions['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 0); + } + // Must be awaiting approval. + else + { + $replacements = array( + 'REALNAME' => $regOptions['register_vars']['real_name'], + 'USERNAME' => $regOptions['username'], + 'PASSWORD' => $regOptions['password'], + 'FORGOTPASSWORDLINK' => $scripturl . '?action=reminder', + 'OPENID' => !empty($regOptions['openid']) ? $regOptions['openid'] : '', + ); + + $emaildata = loadEmailTemplate('register_' . ($regOptions['auth_method'] == 'openid' ? 'openid_' : '') . 'pending', $replacements); + + sendmail($regOptions['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 0); + + // Admin gets informed here... + adminNotify('approval', $memberID, $regOptions['username']); + } + + // Okay, they're for sure registered... make sure the session is aware of this for security. (Just married :P!) + $_SESSION['just_registered'] = 1; + + return $memberID; +} + +// Check if a name is in the reserved words list. (name, current member id, name/username?.) +function isReservedName($name, $current_ID_MEMBER = 0, $is_name = true, $fatal = true) +{ + global $user_info, $modSettings, $smcFunc, $context; + + // No cheating with entities please. + $replaceEntities = create_function('$string', ' + $num = substr($string, 0, 1) === \'x\' ? hexdec(substr($string, 1)) : (int) $string; + if ($num === 0x202E || $num === 0x202D) return \'\'; if (in_array($num, array(0x22, 0x26, 0x27, 0x3C, 0x3E))) return \'&#\' . $num . \';\';' . + (empty($context['utf8']) ? 'return $num < 0x20 ? \'\' : ($num < 0x80 ? chr($num) : \'&#\' . $string . \';\');' : ' + return $num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) ? \'\' : ($num < 0x80 ? chr($num) : ($num < 0x800 ? chr(192 | $num >> 6) . chr(128 | $num & 63) : ($num < 0x10000 ? chr(224 | $num >> 12) . chr(128 | $num >> 6 & 63) . chr(128 | $num & 63) : chr(240 | $num >> 18) . chr(128 | $num >> 12 & 63) . chr(128 | $num >> 6 & 63) . chr(128 | $num & 63))));') + ); + + $name = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'replaceEntities__callback', $name); + $checkName = $smcFunc['strtolower']($name); + + // Administrators are never restricted ;). + if (!allowedTo('moderate_forum') && ((!empty($modSettings['reserveName']) && $is_name) || !empty($modSettings['reserveUser']) && !$is_name)) + { + $reservedNames = explode("\n", $modSettings['reserveNames']); + // Case sensitive check? + $checkMe = empty($modSettings['reserveCase']) ? $checkName : $name; + + // Check each name in the list... + foreach ($reservedNames as $reserved) + { + if ($reserved == '') + continue; + + // The admin might've used entities too, level the playing field. + $reservedCheck = preg_replace('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'replaceEntities__callback', $reserved); + + // Case sensitive name? + if (empty($modSettings['reserveCase'])) + $reservedCheck = $smcFunc['strtolower']($reservedCheck); + + // If it's not just entire word, check for it in there somewhere... + if ($checkMe == $reservedCheck || ($smcFunc['strpos']($checkMe, $reservedCheck) !== false && empty($modSettings['reserveWord']))) + if ($fatal) + fatal_lang_error('username_reserved', 'password', array($reserved)); + else + return true; + } + + $censor_name = $name; + if (censorText($censor_name) != $name) + if ($fatal) + fatal_lang_error('name_censored', 'password', array($name)); + else + return true; + } + + // Characters we just shouldn't allow, regardless. + foreach (array('*') as $char) + if (strpos($checkName, $char) !== false) + if ($fatal) + fatal_lang_error('username_reserved', 'password', array($char)); + else + return true; + + // Get rid of any SQL parts of the reserved name... + $checkName = strtr($name, array('_' => '\\_', '%' => '\\%')); + + // Make sure they don't want someone else's name. + $request = $smcFunc['db_query']('', ' + SELECT id_member + FROM {db_prefix}members + WHERE ' . (empty($current_ID_MEMBER) ? '' : 'id_member != {int:current_member} + AND ') . '(real_name LIKE {string:check_name} OR member_name LIKE {string:check_name}) + LIMIT 1', + array( + 'current_member' => $current_ID_MEMBER, + 'check_name' => $checkName, + ) + ); + if ($smcFunc['db_num_rows']($request) > 0) + { + $smcFunc['db_free_result']($request); + return true; + } + + // Does name case insensitive match a member group name? + $request = $smcFunc['db_query']('', ' + SELECT id_group + FROM {db_prefix}membergroups + WHERE group_name LIKE {string:check_name} + LIMIT 1', + array( + 'check_name' => $checkName, + ) + ); + if ($smcFunc['db_num_rows']($request) > 0) + { + $smcFunc['db_free_result']($request); + return true; + } + + // Okay, they passed. + return false; +} + +// Get a list of groups that have a given permission (on a given board). +function groupsAllowedTo($permission, $board_id = null) +{ + global $modSettings, $board_info, $smcFunc; + + // Admins are allowed to do anything. + $member_groups = array( + 'allowed' => array(1), + 'denied' => array(), + ); + + // Assume we're dealing with regular permissions (like profile_view_own). + if ($board_id === null) + { + $request = $smcFunc['db_query']('', ' + SELECT id_group, add_deny + FROM {db_prefix}permissions + WHERE permission = {string:permission}', + array( + 'permission' => $permission, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $member_groups[$row['add_deny'] === '1' ? 'allowed' : 'denied'][] = $row['id_group']; + $smcFunc['db_free_result']($request); + } + + // Otherwise it's time to look at the board. + else + { + // First get the profile of the given board. + if (isset($board_info['id']) && $board_info['id'] == $board_id) + $profile_id = $board_info['profile']; + elseif ($board_id !== 0) + { + $request = $smcFunc['db_query']('', ' + SELECT id_profile + FROM {db_prefix}boards + WHERE id_board = {int:id_board} + LIMIT 1', + array( + 'id_board' => $board_id, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + fatal_lang_error('no_board'); + list ($profile_id) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + else + $profile_id = 1; + + $request = $smcFunc['db_query']('', ' + SELECT bp.id_group, bp.add_deny + FROM {db_prefix}board_permissions AS bp + WHERE bp.permission = {string:permission} + AND bp.id_profile = {int:profile_id}', + array( + 'profile_id' => $profile_id, + 'permission' => $permission, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $member_groups[$row['add_deny'] === '1' ? 'allowed' : 'denied'][] = $row['id_group']; + $smcFunc['db_free_result']($request); + } + + // Denied is never allowed. + $member_groups['allowed'] = array_diff($member_groups['allowed'], $member_groups['denied']); + + return $member_groups; +} + +// Get a list of members that have a given permission (on a given board). +function membersAllowedTo($permission, $board_id = null) +{ + global $smcFunc; + + $member_groups = groupsAllowedTo($permission, $board_id); + + $include_moderators = in_array(3, $member_groups['allowed']) && $board_id !== null; + $member_groups['allowed'] = array_diff($member_groups['allowed'], array(3)); + + $exclude_moderators = in_array(3, $member_groups['denied']) && $board_id !== null; + $member_groups['denied'] = array_diff($member_groups['denied'], array(3)); + + $request = $smcFunc['db_query']('', ' + SELECT mem.id_member + FROM {db_prefix}members AS mem' . ($include_moderators || $exclude_moderators ? ' + LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_member = mem.id_member AND mods.id_board = {int:board_id})' : '') . ' + WHERE (' . ($include_moderators ? 'mods.id_member IS NOT NULL OR ' : '') . 'mem.id_group IN ({array_int:member_groups_allowed}) OR FIND_IN_SET({raw:member_group_allowed_implode}, mem.additional_groups) != 0)' . (empty($member_groups['denied']) ? '' : ' + AND NOT (' . ($exclude_moderators ? 'mods.id_member IS NOT NULL OR ' : '') . 'mem.id_group IN ({array_int:member_groups_denied}) OR FIND_IN_SET({raw:member_group_denied_implode}, mem.additional_groups) != 0)'), + array( + 'member_groups_allowed' => $member_groups['allowed'], + 'member_groups_denied' => $member_groups['denied'], + 'board_id' => $board_id, + 'member_group_allowed_implode' => implode(', mem.additional_groups) != 0 OR FIND_IN_SET(', $member_groups['allowed']), + 'member_group_denied_implode' => implode(', mem.additional_groups) != 0 OR FIND_IN_SET(', $member_groups['denied']), + ) + ); + $members = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $members[] = $row['id_member']; + $smcFunc['db_free_result']($request); + + return $members; +} + +// This function is used to reassociate members with relevant posts. +function reattributePosts($memID, $email = false, $membername = false, $post_count = false) +{ + global $smcFunc; + + // Firstly, if email and username aren't passed find out the members email address and name. + if ($email === false && $membername === false) + { + $request = $smcFunc['db_query']('', ' + SELECT email_address, member_name + FROM {db_prefix}members + WHERE id_member = {int:memID} + LIMIT 1', + array( + 'memID' => $memID, + ) + ); + list ($email, $membername) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + // If they want the post count restored then we need to do some research. + if ($post_count) + { + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND b.count_posts = {int:count_posts}) + WHERE m.id_member = {int:guest_id} + AND m.approved = {int:is_approved} + AND m.icon != {string:recycled_icon}' . (empty($email) ? '' : ' + AND m.poster_email = {string:email_address}') . (empty($membername) ? '' : ' + AND m.poster_name = {string:member_name}'), + array( + 'count_posts' => 0, + 'guest_id' => 0, + 'email_address' => $email, + 'member_name' => $membername, + 'is_approved' => 1, + 'recycled_icon' => 'recycled', + ) + ); + list ($messageCount) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + updateMemberData($memID, array('posts' => 'posts + ' . $messageCount)); + } + + $query_parts = array(); + if (!empty($email)) + $query_parts[] = 'poster_email = {string:email_address}'; + if (!empty($membername)) + $query_parts[] = 'poster_name = {string:member_name}'; + $query = implode(' AND ', $query_parts); + + // Finally, update the posts themselves! + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET id_member = {int:memID} + WHERE ' . $query, + array( + 'memID' => $memID, + 'email_address' => $email, + 'member_name' => $membername, + ) + ); + + return $smcFunc['db_affected_rows'](); +} + +// This simple function adds/removes the passed user from the current users buddy list. +function BuddyListToggle() +{ + global $user_info; + + checkSession('get'); + + isAllowedTo('profile_identity_own'); + is_not_guest(); + + if (empty($_REQUEST['u'])) + fatal_lang_error('no_access', false); + $_REQUEST['u'] = (int) $_REQUEST['u']; + + // Remove if it's already there... + if (in_array($_REQUEST['u'], $user_info['buddies'])) + $user_info['buddies'] = array_diff($user_info['buddies'], array($_REQUEST['u'])); + // ...or add if it's not and if it's not you. + elseif ($user_info['id'] != $_REQUEST['u']) + $user_info['buddies'][] = (int) $_REQUEST['u']; + + // Update the settings. + updateMemberData($user_info['id'], array('buddy_list' => implode(',', $user_info['buddies']))); + + // Redirect back to the profile + redirectexit('action=profile;u=' . $_REQUEST['u']); +} + +function list_getMembers($start, $items_per_page, $sort, $where, $where_params = array(), $get_duplicates = false) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('', ' + SELECT + mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.icq, mem.aim, mem.yim, mem.msn, mem.member_ip, mem.member_ip2, mem.last_login, + mem.posts, mem.is_activated, mem.date_registered, mem.id_group, mem.additional_groups, mg.group_name + FROM {db_prefix}members AS mem + LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group) + WHERE ' . $where . ' + ORDER BY {raw:sort} + LIMIT {int:start}, {int:per_page}', + array_merge($where_params, array( + 'sort' => $sort, + 'start' => $start, + 'per_page' => $items_per_page, + )) + ); + + $members = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $members[] = $row; + $smcFunc['db_free_result']($request); + + // If we want duplicates pass the members array off. + if ($get_duplicates) + populateDuplicateMembers($members); + + return $members; +} + +function list_getNumMembers($where, $where_params = array()) +{ + global $smcFunc, $modSettings; + + // We know how many members there are in total. + if (empty($where) || $where == '1') + $num_members = $modSettings['totalMembers']; + + // The database knows the amount when there are extra conditions. + else + { + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}members AS mem + WHERE ' . $where, + array_merge($where_params, array( + )) + ); + list ($num_members) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + return $num_members; +} + +function populateDuplicateMembers(&$members) +{ + global $smcFunc; + + // This will hold all the ip addresses. + $ips = array(); + foreach ($members as $key => $member) + { + // Create the duplicate_members element. + $members[$key]['duplicate_members'] = array(); + + // Store the IPs. + if (!empty($member['member_ip'])) + $ips[] = $member['member_ip']; + if (!empty($member['member_ip2'])) + $ips[] = $member['member_ip2']; + } + + $ips = array_unique($ips); + + if (empty($ips)) + return false; + + // Fetch all members with this IP address, we'll filter out the current ones in a sec. + $request = $smcFunc['db_query']('', ' + SELECT + id_member, member_name, email_address, member_ip, member_ip2, is_activated + FROM {db_prefix}members + WHERE member_ip IN ({array_string:ips}) + OR member_ip2 IN ({array_string:ips})', + array( + 'ips' => $ips, + ) + ); + $duplicate_members = array(); + $duplicate_ids = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + //$duplicate_ids[] = $row['id_member']; + + $member_context = array( + 'id' => $row['id_member'], + 'name' => $row['member_name'], + 'email' => $row['email_address'], + 'is_banned' => $row['is_activated'] > 10, + 'ip' => $row['member_ip'], + 'ip2' => $row['member_ip2'], + ); + + if (in_array($row['member_ip'], $ips)) + $duplicate_members[$row['member_ip']][] = $member_context; + if ($row['member_ip'] != $row['member_ip2'] && in_array($row['member_ip2'], $ips)) + $duplicate_members[$row['member_ip2']][] = $member_context; + } + $smcFunc['db_free_result']($request); + + // Also try to get a list of messages using these ips. + $request = $smcFunc['db_query']('', ' + SELECT + m.poster_ip, mem.id_member, mem.member_name, mem.email_address, mem.is_activated + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE m.id_member != 0 + ' . (!empty($duplicate_ids) ? 'AND m.id_member NOT IN ({array_int:duplicate_ids})' : '') . ' + AND m.poster_ip IN ({array_string:ips})', + array( + 'duplicate_ids' => $duplicate_ids, + 'ips' => $ips, + ) + ); + + $had_ips = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Don't collect lots of the same. + if (isset($had_ips[$row['poster_ip']]) && in_array($row['id_member'], $had_ips[$row['poster_ip']])) + continue; + $had_ips[$row['poster_ip']][] = $row['id_member']; + + $duplicate_members[$row['poster_ip']][] = array( + 'id' => $row['id_member'], + 'name' => $row['member_name'], + 'email' => $row['email_address'], + 'is_banned' => $row['is_activated'] > 10, + 'ip' => $row['poster_ip'], + 'ip2' => $row['poster_ip'], + ); + } + $smcFunc['db_free_result']($request); + + // Now we have all the duplicate members, stick them with their respective member in the list. + if (!empty($duplicate_members)) + foreach ($members as $key => $member) + { + if (isset($duplicate_members[$member['member_ip']])) + $members[$key]['duplicate_members'] = $duplicate_members[$member['member_ip']]; + if ($member['member_ip'] != $member['member_ip2'] && isset($duplicate_members[$member['member_ip2']])) + $members[$key]['duplicate_members'] = array_merge($member['duplicate_members'], $duplicate_members[$member['member_ip2']]); + + // Check we don't have lots of the same member. + $member_track = array($member['id_member']); + foreach ($members[$key]['duplicate_members'] as $duplicate_id_member => $duplicate_member) + { + if (in_array($duplicate_member['id'], $member_track)) + { + unset($members[$key]['duplicate_members'][$duplicate_id_member]); + continue; + } + + $member_track[] = $duplicate_member['id']; + } + } +} + +// Generate a random validation code. +function generateValidationCode() +{ + global $smcFunc, $modSettings; + + $request = $smcFunc['db_query']('get_random_number', ' + SELECT RAND()', + array( + ) + ); + + list ($dbRand) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + return substr(preg_replace('/\W/', '', sha1(microtime() . mt_rand() . $dbRand . $modSettings['rand_seed'])), 0, 10); +} + +?> \ No newline at end of file diff --git a/Sources/Subs-MembersOnline.php b/Sources/Subs-MembersOnline.php new file mode 100644 index 0000000..caceec9 --- /dev/null +++ b/Sources/Subs-MembersOnline.php @@ -0,0 +1,256 @@ + array(), + 'list_users_online' => array(), + 'online_groups' => array(), + 'num_guests' => 0, + 'num_spiders' => 0, + 'num_buddies' => 0, + 'num_users_hidden' => 0, + 'num_users_online' => 0, + ); + + // Get any spiders if enabled. + $spiders = array(); + $spider_finds = array(); + if (!empty($modSettings['show_spider_online']) && ($modSettings['show_spider_online'] < 3 || allowedTo('admin_forum')) && !empty($modSettings['spider_name_cache'])) + $spiders = unserialize($modSettings['spider_name_cache']); + + // Load the users online right now. + $request = $smcFunc['db_query']('', ' + SELECT + lo.id_member, lo.log_time, lo.id_spider, mem.real_name, mem.member_name, mem.show_online, + mg.online_color, mg.id_group, mg.group_name + FROM {db_prefix}log_online AS lo + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lo.id_member) + LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:reg_mem_group} THEN mem.id_post_group ELSE mem.id_group END)', + array( + 'reg_mem_group' => 0, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (empty($row['real_name'])) + { + // Do we think it's a spider? + if ($row['id_spider'] && isset($spiders[$row['id_spider']])) + { + $spider_finds[$row['id_spider']] = isset($spider_finds[$row['id_spider']]) ? $spider_finds[$row['id_spider']] + 1 : 1; + $membersOnlineStats['num_spiders']++; + } + // Guests are only nice for statistics. + $membersOnlineStats['num_guests']++; + + continue; + } + + elseif (empty($row['show_online']) && empty($membersOnlineOptions['show_hidden'])) + { + // Just increase the stats and don't add this hidden user to any list. + $membersOnlineStats['num_users_hidden']++; + continue; + } + + // Some basic color coding... + if (!empty($row['online_color'])) + $link = '' . $row['real_name'] . ''; + else + $link = '' . $row['real_name'] . ''; + + // Buddies get counted and highlighted. + $is_buddy = in_array($row['id_member'], $user_info['buddies']); + if ($is_buddy) + { + $membersOnlineStats['num_buddies']++; + $link = '' . $link . ''; + } + + // A lot of useful information for each member. + $membersOnlineStats['users_online'][$row[$membersOnlineOptions['sort']] . $row['member_name']] = array( + 'id' => $row['id_member'], + 'username' => $row['member_name'], + 'name' => $row['real_name'], + 'group' => $row['id_group'], + 'href' => $scripturl . '?action=profile;u=' . $row['id_member'], + 'link' => $link, + 'is_buddy' => $is_buddy, + 'hidden' => empty($row['show_online']), + 'is_last' => false, + ); + + // This is the compact version, simply implode it to show. + $membersOnlineStats['list_users_online'][$row[$membersOnlineOptions['sort']] . $row['member_name']] = empty($row['show_online']) ? '' . $link . '' : $link; + + // Store all distinct (primary) membergroups that are shown. + if (!isset($membersOnlineStats['online_groups'][$row['id_group']])) + $membersOnlineStats['online_groups'][$row['id_group']] = array( + 'id' => $row['id_group'], + 'name' => $row['group_name'], + 'color' => $row['online_color'] + ); + } + $smcFunc['db_free_result']($request); + + // If there are spiders only and we're showing the detail, add them to the online list - at the bottom. + if (!empty($spider_finds) && $modSettings['show_spider_online'] > 1) + foreach ($spider_finds as $id => $count) + { + $link = $spiders[$id] . ($count > 1 ? ' (' . $count . ')' : ''); + $sort = $membersOnlineOptions['sort'] = 'log_time' && $membersOnlineOptions['reverse_sort'] ? 0 : 'zzz_'; + $membersOnlineStats['users_online'][$sort . $spiders[$id]] = array( + 'id' => 0, + 'username' => $spiders[$id], + 'name' => $link, + 'group' => $txt['spiders'], + 'href' => '', + 'link' => $link, + 'is_buddy' => false, + 'hidden' => false, + 'is_last' => false, + ); + $membersOnlineStats['list_users_online'][$sort . $spiders[$id]] = $link; + } + + // Time to sort the list a bit. + if (!empty($membersOnlineStats['users_online'])) + { + // Determine the sort direction. + $sortFunction = empty($membersOnlineOptions['reverse_sort']) ? 'ksort' : 'krsort'; + + // Sort the two lists. + $sortFunction($membersOnlineStats['users_online']); + $sortFunction($membersOnlineStats['list_users_online']); + + // Mark the last list item as 'is_last'. + $userKeys = array_keys($membersOnlineStats['users_online']); + $membersOnlineStats['users_online'][end($userKeys)]['is_last'] = true; + } + + // Also sort the membergroups. + ksort($membersOnlineStats['online_groups']); + + // Hidden and non-hidden members make up all online members. + $membersOnlineStats['num_users_online'] = count($membersOnlineStats['users_online']) + $membersOnlineStats['num_users_hidden'] - (isset($modSettings['show_spider_online']) && $modSettings['show_spider_online'] > 1 ? count($spider_finds) : 0); + + return $membersOnlineStats; +} + +// Check if the number of users online is a record and store it. +function trackStatsUsersOnline($total_users_online) +{ + global $modSettings, $smcFunc; + + $settingsToUpdate = array(); + + // More members on now than ever were? Update it! + if (!isset($modSettings['mostOnline']) || $total_users_online >= $modSettings['mostOnline']) + $settingsToUpdate = array( + 'mostOnline' => $total_users_online, + 'mostDate' => time() + ); + + $date = strftime('%Y-%m-%d', forum_time(false)); + + // No entry exists for today yet? + if (!isset($modSettings['mostOnlineUpdated']) || $modSettings['mostOnlineUpdated'] != $date) + { + $request = $smcFunc['db_query']('', ' + SELECT most_on + FROM {db_prefix}log_activity + WHERE date = {date:date} + LIMIT 1', + array( + 'date' => $date, + ) + ); + + // The log_activity hasn't got an entry for today? + if ($smcFunc['db_num_rows']($request) === 0) + { + $smcFunc['db_insert']('ignore', + '{db_prefix}log_activity', + array('date' => 'date', 'most_on' => 'int'), + array($date, $total_users_online), + array('date') + ); + } + // There's an entry in log_activity on today... + else + { + list ($modSettings['mostOnlineToday']) = $smcFunc['db_fetch_row']($request); + + if ($total_users_online > $modSettings['mostOnlineToday']) + trackStats(array('most_on' => $total_users_online)); + + $total_users_online = max($total_users_online, $modSettings['mostOnlineToday']); + } + $smcFunc['db_free_result']($request); + + $settingsToUpdate['mostOnlineUpdated'] = $date; + $settingsToUpdate['mostOnlineToday'] = $total_users_online; + } + + // Highest number of users online today? + elseif ($total_users_online > $modSettings['mostOnlineToday']) + { + trackStats(array('most_on' => $total_users_online)); + $settingsToUpdate['mostOnlineToday'] = $total_users_online; + } + + if (!empty($settingsToUpdate)) + updateSettings($settingsToUpdate); +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Menu.php b/Sources/Subs-Menu.php new file mode 100644 index 0000000..c9d3414 --- /dev/null +++ b/Sources/Subs-Menu.php @@ -0,0 +1,300 @@ + 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'), + array( + array( + $user_info['id'], + $settings['theme_id'], + 'use_sidebar_menu', + empty($options['use_sidebar_menu']) ? '1' : '0', + ), + ), + array('id_member', 'id_theme', 'variable') + ); + + // Clear the theme settings cache for this user. + $themes = explode(',', $modSettings['knownThemes']); + foreach ($themes as $theme) + cache_put_data('theme_settings-' . $theme . ':' . $user_info['id'], null, 60); + + // Redirect as this seems to work best. + $redirect_url = isset($menuOptions['toggle_redirect_url']) ? $menuOptions['toggle_redirect_url'] : 'action=' . (isset($_GET['action']) ? $_GET['action'] : 'admin') . ';area=' . (isset($_GET['area']) ? $_GET['area'] : 'index') . ';sa=' . (isset($_GET['sa']) ? $_GET['sa'] : 'settings') . (isset($_GET['u']) ? ';u=' . $_GET['u'] : '') . ';' . $context['session_var'] . '=' . $context['session_id']; + redirectexit($redirect_url); + } + + // Work out where we should get our images from. + $context['menu_image_path'] = file_exists($settings['theme_dir'] . '/images/admin/change_menu.png') ? $settings['images_url'] . '/admin' : $settings['default_images_url'] . '/admin'; + + /* Note menuData is array of form: + + Possible fields: + For Section: + string $title: Section title. + bool $enabled: Should section be shown? + array $areas: Array of areas within this section. + array $permission: Permission required to access the whole section. + + For Areas: + array $permission: Array of permissions to determine who can access this area. + string $label: Optional text string for link (Otherwise $txt[$index] will be used) + string $file: Name of source file required for this area. + string $function: Function to call when area is selected. + string $custom_url: URL to use for this menu item. + bool $enabled: Should this area even be accessible? + bool $hidden: Should this area be visible? + string $select: If set this item will not be displayed - instead the item indexed here shall be. + array $subsections: Array of subsections from this area. + + For Subsections: + string 0: Text label for this subsection. + array 1: Array of permissions to check for this subsection. + bool 2: Is this the default subaction - if not set for any will default to first... + bool enabled: Bool to say whether this should be enabled or not. + */ + + // Every menu gets a unique ID, these are shown in first in, first out order. + $context['max_menu_id'] = isset($context['max_menu_id']) ? $context['max_menu_id'] + 1 : 1; + + // This will be all the data for this menu - and we'll make a shortcut to it to aid readability here. + $context['menu_data_' . $context['max_menu_id']] = array(); + $menu_context = &$context['menu_data_' . $context['max_menu_id']]; + + // What is the general action of this menu (i.e. $scripturl?action=XXXX. + $menu_context['current_action'] = isset($menuOptions['action']) ? $menuOptions['action'] : $context['current_action']; + + // What is the current area selected? + if (isset($menuOptions['current_area']) || isset($_GET['area'])) + $menu_context['current_area'] = isset($menuOptions['current_area']) ? $menuOptions['current_area'] : $_GET['area']; + + // Build a list of additional parameters that should go in the URL. + $menu_context['extra_parameters'] = ''; + if (!empty($menuOptions['extra_url_parameters'])) + foreach ($menuOptions['extra_url_parameters'] as $key => $value) + $menu_context['extra_parameters'] .= ';' . $key . '=' . $value; + + // Only include the session ID in the URL if it's strictly necessary. + if (empty($menuOptions['disable_url_session_check'])) + $menu_context['extra_parameters'] .= ';' . $context['session_var'] . '=' . $context['session_id']; + + $include_data = array(); + + // Now setup the context correctly. + foreach ($menuData as $section_id => $section) + { + // Is this enabled - or has as permission check - which fails? + if ((isset($section['enabled']) && $section['enabled'] == false) || (isset($section['permission']) && !allowedTo($section['permission']))) + continue; + + // Now we cycle through the sections to pick the right area. + foreach ($section['areas'] as $area_id => $area) + { + // Can we do this? + if ((!isset($area['enabled']) || $area['enabled'] != false) && (empty($area['permission']) || allowedTo($area['permission']))) + { + // Add it to the context... if it has some form of name! + if (isset($area['label']) || (isset($txt[$area_id]) && !isset($area['select']))) + { + // If we haven't got an area then the first valid one is our choice. + if (!isset($menu_context['current_area'])) + { + $menu_context['current_area'] = $area_id; + $include_data = $area; + } + + // If this is hidden from view don't do the rest. + if (empty($area['hidden'])) + { + // First time this section? + if (!isset($menu_context['sections'][$section_id])) + $menu_context['sections'][$section_id]['title'] = $section['title']; + + $menu_context['sections'][$section_id]['areas'][$area_id] = array('label' => isset($area['label']) ? $area['label'] : $txt[$area_id]); + // We'll need the ID as well... + $menu_context['sections'][$section_id]['id'] = $section_id; + // Does it have a custom URL? + if (isset($area['custom_url'])) + $menu_context['sections'][$section_id]['areas'][$area_id]['url'] = $area['custom_url']; + + // Does this area have its own icon? + if (!isset($area['force_menu_into_arms_of_another_menu']) && $user_info['name'] == 'iamanoompaloompa') + $menu_context['sections'][$section_id]['areas'][$area_id] = unserialize(base64_decode('YTozOntzOjU6ImxhYmVsIjtzOjEyOiJPb21wYSBMb29tcGEiO3M6MzoidXJsIjtzOjQzOiJodHRwOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL09vbXBhX0xvb21wYXM/IjtzOjQ6Imljb24iO3M6ODY6IjxpbWcgc3JjPSJodHRwOi8vd3d3LnNpbXBsZW1hY2hpbmVzLm9yZy9pbWFnZXMvb29tcGEuZ2lmIiBhbHQ9IkknbSBhbiBPb21wYSBMb29tcGEiIC8+Ijt9')); + elseif (isset($area['icon'])) + $menu_context['sections'][$section_id]['areas'][$area_id]['icon'] = '  '; + else + $menu_context['sections'][$section_id]['areas'][$area_id]['icon'] = ''; + + // Did it have subsections? + if (!empty($area['subsections'])) + { + $menu_context['sections'][$section_id]['areas'][$area_id]['subsections'] = array(); + $first_sa = $last_sa = null; + foreach ($area['subsections'] as $sa => $sub) + { + if ((empty($sub[1]) || allowedTo($sub[1])) && (!isset($sub['enabled']) || !empty($sub['enabled']))) + { + if ($first_sa == null) + $first_sa = $sa; + + $menu_context['sections'][$section_id]['areas'][$area_id]['subsections'][$sa] = array('label' => $sub[0]); + // Custom URL? + if (isset($sub['url'])) + $menu_context['sections'][$section_id]['areas'][$area_id]['subsections'][$sa]['url'] = $sub['url']; + + // A bit complicated - but is this set? + if ($menu_context['current_area'] == $area_id) + { + // Save which is the first... + if (empty($first_sa)) + $first_sa = $sa; + + // Is this the current subsection? + if (isset($_REQUEST['sa']) && $_REQUEST['sa'] == $sa) + $menu_context['current_subsection'] = $sa; + // Otherwise is it the default? + elseif (!isset($menu_context['current_subsection']) && !empty($sub[2])) + $menu_context['current_subsection'] = $sa; + } + + // Let's assume this is the last, for now. + $last_sa = $sa; + } + // Mark it as disabled... + else + $menu_context['sections'][$section_id]['areas'][$area_id]['subsections'][$sa]['disabled'] = true; + } + + // Set which one is first, last and selected in the group. + if (!empty($menu_context['sections'][$section_id]['areas'][$area_id]['subsections'])) + { + $menu_context['sections'][$section_id]['areas'][$area_id]['subsections'][$context['right_to_left'] ? $last_sa : $first_sa]['is_first'] = true; + $menu_context['sections'][$section_id]['areas'][$area_id]['subsections'][$context['right_to_left'] ? $first_sa : $last_sa]['is_last'] = true; + + if ($menu_context['current_area'] == $area_id && !isset($menu_context['current_subsection'])) + $menu_context['current_subsection'] = $first_sa; + } + } + } + } + + // Is this the current section? + if ($menu_context['current_area'] == $area_id && empty($found_section)) + { + // Only do this once? + $found_section = true; + + // Update the context if required - as we can have areas pretending to be others. ;) + $menu_context['current_section'] = $section_id; + $menu_context['current_area'] = isset($area['select']) ? $area['select'] : $area_id; + + // This will be the data we return. + $include_data = $area; + } + // Make sure we have something in case it's an invalid area. + elseif (empty($found_section) && empty($include_data)) + { + $menu_context['current_section'] = $section_id; + $backup_area = isset($area['select']) ? $area['select'] : $area_id; + $include_data = $area; + } + } + } + } + + // Should we use a custom base url, or use the default? + $menu_context['base_url'] = isset($menuOptions['base_url']) ? $menuOptions['base_url'] : $scripturl . '?action=' . $menu_context['current_action']; + + // What about the toggle url? + $menu_context['toggle_url'] = isset($menuOptions['toggle_url']) ? $menuOptions['toggle_url'] : $menu_context['base_url'] . (!empty($menu_context['current_area']) ? ';area=' . $menu_context['current_area'] : '') . (!empty($menu_context['current_subsection']) ? ';sa=' . $menu_context['current_subsection'] : '') . $menu_context['extra_parameters'] . ';togglebar'; + + // If we didn't find the area we were looking for go to a default one. + if (isset($backup_area) && empty($found_section)) + $menu_context['current_area'] = $backup_area; + + // If still no data then return - nothing to show! + if (empty($menu_context['sections'])) + { + // Never happened! + $context['max_menu_id']--; + if ($context['max_menu_id'] == 0) + unset($context['max_menu_id']); + + return false; + } + + // What type of menu is this? + if (empty($menuOptions['menu_type'])) + { + $menuOptions['menu_type'] = '_' . (empty($options['use_sidebar_menu']) ? 'dropdown' : 'sidebar'); + $menu_context['can_toggle_drop_down'] = !$user_info['is_guest'] && isset($settings['theme_version']) && $settings['theme_version'] >= 2.0; + } + else + $menu_context['can_toggle_drop_down'] = !empty($menuOptions['can_toggle_drop_down']); + + // Almost there - load the template and add to the template layers. + if (!WIRELESS) + { + loadTemplate(isset($menuOptions['template_name']) ? $menuOptions['template_name'] : 'GenericMenu'); + $menu_context['layer_name'] = (isset($menuOptions['layer_name']) ? $menuOptions['layer_name'] : 'generic_menu') . $menuOptions['menu_type']; + $context['template_layers'][] = $menu_context['layer_name']; + } + + // Check we had something - for sanity sake. + if (empty($include_data)) + return false; + + // Finally - return information on the selected item. + $include_data += array( + 'current_action' => $menu_context['current_action'], + 'current_area' => $menu_context['current_area'], + 'current_section' => $menu_context['current_section'], + 'current_subsection' => !empty($menu_context['current_subsection']) ? $menu_context['current_subsection'] : '', + ); + + return $include_data; +} + +// Delete a menu. +function destroyMenu($menu_id = 'last') +{ + global $context; + + $menu_name = $menu_id == 'last' && isset($context['max_menu_id']) && isset($context['menu_data_' . $context['max_menu_id']]) ? 'menu_data_' . $context['max_menu_id'] : 'menu_data_' . $menu_id; + if (!isset($context[$menu_name])) + return false; + + $layer_index = array_search($context[$menu_name]['layer_name'], $context['template_layers']); + if ($layer_index !== false) + unset($context['template_layers'][$layer_index]); + + unset($context[$menu_name]); +} + +?> \ No newline at end of file diff --git a/Sources/Subs-MessageIndex.php b/Sources/Subs-MessageIndex.php new file mode 100644 index 0000000..54b9778 --- /dev/null +++ b/Sources/Subs-MessageIndex.php @@ -0,0 +1,84 @@ + $row['id_cat'], + 'name' => $row['cat_name'], + 'boards' => array(), + ); + + $return_value[$row['id_cat']]['boards'][] = array( + 'id' => $row['id_board'], + 'name' => $row['board_name'], + 'child_level' => $row['child_level'], + 'selected' => isset($boardListOptions['selected_board']) && $boardListOptions['selected_board'] == $row['id_board'], + ); + } + } + $smcFunc['db_free_result']($request); + + return $return_value; +} + +?> \ No newline at end of file diff --git a/Sources/Subs-OpenID.php b/Sources/Subs-OpenID.php new file mode 100644 index 0000000..324b721 --- /dev/null +++ b/Sources/Subs-OpenID.php @@ -0,0 +1,609 @@ + $_GET, + 'post' => $_POST, + 'openid_uri' => $openid_url, + 'cookieTime' => $modSettings['cookieTime'], + ); + + $parameters = array( + 'openid.mode=checkid_setup', + 'openid.trust_root=' . urlencode($scripturl), + 'openid.identity=' . urlencode(empty($response_data['delegate']) ? $openid_url : $response_data['delegate']), + 'openid.assoc_handle=' . urlencode($assoc['handle']), + 'openid.return_to=' . urlencode($scripturl . '?action=openidreturn&sa=' . (!empty($return_action) ? $return_action : $_REQUEST['action']) . '&t=' . $request_time . (!empty($save_fields) ? '&sf=' . base64_encode(serialize($save_fields)) : '')), + ); + + // If they are logging in but don't yet have an account or they are registering, let's request some additional information + if (($_REQUEST['action'] == 'login2' && !smf_openid_member_exists($openid_url)) || ($_REQUEST['action'] == 'register' || $_REQUEST['action'] == 'register2')) + { + // Email is required. + $parameters[] = 'openid.sreg.required=email'; + // The rest is just optional. + $parameters[] = 'openid.sreg.optional=nickname,dob,gender'; + } + + $redir_url = $response_data['server'] . '?' . implode('&', $parameters); + + if ($return) + return $redir_url; + else + redirectexit($redir_url); +} + +// Revalidate a user using OpenID. Note that this function will not return when authentication is required. +function smf_openID_revalidate() +{ + global $user_settings; + + if (isset($_SESSION['openid_revalidate_time']) && $_SESSION['openid_revalidate_time'] > time() - 60) + { + unset($_SESSION['openid_revalidate_time']); + return true; + } + else + smf_openID_validate($user_settings['openid_uri'], false, null, 'revalidate'); + + // We shouldn't get here. + trigger_error('Hacking attempt...', E_USER_ERROR); +} + +function smf_openID_getAssociation($server, $handle = null, $no_delete = false) +{ + global $smcFunc; + + if (!$no_delete) + { + // Delete the already expired associations. + $smcFunc['db_query']('openid_delete_assoc_old', ' + DELETE FROM {db_prefix}openid_assoc + WHERE expires <= {int:current_time}', + array( + 'current_time' => time(), + ) + ); + } + + // Get the association that has the longest lifetime from now. + $request = $smcFunc['db_query']('openid_select_assoc', ' + SELECT server_url, handle, secret, issued, expires, assoc_type + FROM {db_prefix}openid_assoc + WHERE server_url = {string:server_url}' . ($handle === null ? '' : ' + AND handle = {string:handle}') . ' + ORDER BY expires DESC', + array( + 'server_url' => $server, + 'handle' => $handle, + ) + ); + + if ($smcFunc['db_num_rows']($request) == 0) + return null; + + $return = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + return $return; +} + +function smf_openID_makeAssociation($server) +{ + global $smcFunc, $modSettings, $p; + + $parameters = array( + 'openid.mode=associate', + ); + + // We'll need to get our keys for the Diffie-Hellman key exchange. + $dh_keys = smf_openID_setup_DH(); + + // If we don't support DH we'll have to see if the provider will accept no encryption. + if ($dh_keys === false) + $parameters[] = 'openid.session_type='; + else + { + $parameters[] = 'openid.session_type=DH-SHA1'; + $parameters[] = 'openid.dh_consumer_public=' . urlencode(base64_encode(long_to_binary($dh_keys['public']))); + $parameters[] = 'openid.assoc_type=HMAC-SHA1'; + } + + // The data to post to the server. + $post_data = implode('&', $parameters); + $data = fetch_web_data($server, $post_data); + + // Parse the data given. + preg_match_all('~^([^:]+):(.+)$~m', $data, $matches); + $assoc_data = array(); + + foreach ($matches[1] as $key => $match) + $assoc_data[$match] = $matches[2][$key]; + + if (!isset($assoc_data['assoc_type']) || (empty($assoc_data['mac_key']) && empty($assoc_data['enc_mac_key']))) + fatal_lang_error('openid_server_bad_response'); + + // Clean things up a bit. + $handle = isset($assoc_data['assoc_handle']) ? $assoc_data['assoc_handle'] : ''; + $issued = time(); + $expires = $issued + min((int)$assoc_data['expires_in'], 60); + $assoc_type = isset($assoc_data['assoc_type']) ? $assoc_data['assoc_type'] : ''; + + // !!! Is this really needed? + foreach (array('dh_server_public', 'enc_mac_key') as $key) + if (isset($assoc_data[$key])) + $assoc_data[$key] = str_replace(' ', '+', $assoc_data[$key]); + + // Figure out the Diffie-Hellman secret. + if (!empty($assoc_data['enc_mac_key'])) + { + $dh_secret = bcpowmod(binary_to_long(base64_decode($assoc_data['dh_server_public'])), $dh_keys['private'], $p); + $secret = base64_encode(binary_xor(sha1_raw(long_to_binary($dh_secret)), base64_decode($assoc_data['enc_mac_key']))); + } + else + $secret = $assoc_data['mac_key']; + + // Store the data + $smcFunc['db_insert']('replace', + '{db_prefix}openid_assoc', + array('server_url' => 'string', 'handle' => 'string', 'secret' => 'string', 'issued' => 'int', 'expires' => 'int', 'assoc_type' => 'string'), + array($server, $handle, $secret, $issued, $expires, $assoc_type), + array('server_url', 'handle') + ); + + return array( + 'server' => $server, + 'handle' => $assoc_data['assoc_handle'], + 'secret' => $secret, + 'issued' => $issued, + 'expires' => $expires, + 'assoc_type' => $assoc_data['assoc_type'], + ); +} + +function smf_openID_removeAssociation($handle) +{ + global $smcFunc; + + $smcFunc['db_query']('openid_remove_association', ' + DELETE FROM {db_prefix}openid_assoc + WHERE handle = {string:handle}', + array( + 'handle' => $handle, + ) + ); +} + +function smf_openID_return() +{ + global $smcFunc, $user_info, $user_profile, $sourcedir, $modSettings, $context, $sc, $user_settings; + + // Is OpenID even enabled? + if (empty($modSettings['enableOpenID'])) + fatal_lang_error('no_access', false); + + if (!isset($_GET['openid_mode'])) + fatal_lang_error('openid_return_no_mode', false); + + // !!! Check for error status! + if ($_GET['openid_mode'] != 'id_res') + fatal_lang_error('openid_not_resolved'); + + // SMF has this annoying habit of removing the + from the base64 encoding. So lets put them back. + foreach (array('openid_assoc_handle', 'openid_invalidate_handle', 'openid_sig', 'sf') as $key) + if (isset($_GET[$key])) + $_GET[$key] = str_replace(' ', '+', $_GET[$key]); + + // Did they tell us to remove any associations? + if (!empty($_GET['openid_invalidate_handle'])) + smf_openid_removeAssociation($_GET['openid_invalidate_handle']); + + $server_info = smf_openid_getServerInfo($_GET['openid_identity']); + + // Get the association data. + $assoc = smf_openID_getAssociation($server_info['server'], $_GET['openid_assoc_handle'], true); + if ($assoc === null) + fatal_lang_error('openid_no_assoc'); + + $secret = base64_decode($assoc['secret']); + + $signed = explode(',', $_GET['openid_signed']); + $verify_str = ''; + foreach ($signed as $sign) + { + $verify_str .= $sign . ':' . strtr($_GET['openid_' . str_replace('.', '_', $sign)], array('&' => '&')) . "\n"; + } + + $verify_str = base64_encode(sha1_hmac($verify_str, $secret)); + + if ($verify_str != $_GET['openid_sig']) + { + fatal_lang_error('openid_sig_invalid', 'critical'); + } + + if (!isset($_SESSION['openid']['saved_data'][$_GET['t']])) + fatal_lang_error('openid_load_data'); + + $openid_uri = $_SESSION['openid']['saved_data'][$_GET['t']]['openid_uri']; + $modSettings['cookieTime'] = $_SESSION['openid']['saved_data'][$_GET['t']]['cookieTime']; + + if (empty($openid_uri)) + fatal_lang_error('openid_load_data'); + + // Any save fields to restore? + $context['openid_save_fields'] = isset($_GET['sf']) ? unserialize(base64_decode($_GET['sf'])) : array(); + + // Is there a user with this OpenID_uri? + $result = $smcFunc['db_query']('', ' + SELECT passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt, + openid_uri + FROM {db_prefix}members + WHERE openid_uri = {string:openid_uri}', + array( + 'openid_uri' => $openid_uri, + ) + ); + + $member_found = $smcFunc['db_num_rows']($result); + + if (!$member_found && isset($_GET['sa']) && $_GET['sa'] == 'change_uri' && !empty($_SESSION['new_openid_uri']) && $_SESSION['new_openid_uri'] == $openid_uri) + { + // Update the member. + updateMemberData($user_settings['id_member'], array('openid_uri' => $openid_uri)); + + unset($_SESSION['new_openid_uri']); + $_SESSION['openid'] = array( + 'verified' => true, + 'openid_uri' => $openid_uri, + ); + + // Send them back to profile. + redirectexit('action=profile;area=authentication;updated'); + } + elseif (!$member_found) + { + // Store the received openid info for the user when returned to the registration page. + $_SESSION['openid'] = array( + 'verified' => true, + 'openid_uri' => $openid_uri, + ); + if (isset($_GET['openid_sreg_nickname'])) + $_SESSION['openid']['nickname'] = $_GET['openid_sreg_nickname']; + if (isset($_GET['openid_sreg_email'])) + $_SESSION['openid']['email'] = $_GET['openid_sreg_email']; + if (isset($_GET['openid_sreg_dob'])) + $_SESSION['openid']['dob'] = $_GET['openid_sreg_dob']; + if (isset($_GET['openid_sreg_gender'])) + $_SESSION['openid']['gender'] = $_GET['openid_sreg_gender']; + + // Were we just verifying the registration state? + if (isset($_GET['sa']) && $_GET['sa'] == 'register2') + { + require_once($sourcedir . '/Register.php'); + return Register2(true); + } + else + redirectexit('action=register'); + } + elseif (isset($_GET['sa']) && $_GET['sa'] == 'revalidate' && $user_settings['openid_uri'] == $openid_uri) + { + $_SESSION['openid_revalidate_time'] = time(); + + // Restore the get data. + require_once($sourcedir . '/Subs-Auth.php'); + $_SESSION['openid']['saved_data'][$_GET['t']]['get']['openid_restore_post'] = $_GET['t']; + $query_string = construct_query_string($_SESSION['openid']['saved_data'][$_GET['t']]['get']); + + redirectexit($query_string); + } + else + { + $user_settings = $smcFunc['db_fetch_assoc']($result); + $smcFunc['db_free_result']($result); + + $user_settings['passwd'] = sha1(strtolower($user_settings['member_name']) . $secret); + $user_settings['password_salt'] = substr(md5(mt_rand()), 0, 4); + + updateMemberData($user_settings['id_member'], array('passwd' => $user_settings['passwd'], 'password_salt' => $user_settings['password_salt'])); + + // Cleanup on Aisle 5. + $_SESSION['openid'] = array( + 'verified' => true, + 'openid_uri' => $openid_uri, + ); + + require_once($sourcedir . '/LogInOut.php'); + + if (!checkActivation()) + return; + + DoLogin(); + } +} + +function smf_openID_canonize($uri) +{ + // !!! Add in discovery. + + if (strpos($uri, 'http://') !== 0 && strpos($uri, 'https://') !== 0) + $uri = 'http://' . $uri; + + if (strpos(substr($uri, strpos($uri, '://') + 3), '/') === false) + $uri .= '/'; + + return $uri; +} + +function smf_openid_member_exists($url) +{ + global $smcFunc; + + $request = $smcFunc['db_query']('openid_member_exists', ' + SELECT mem.id_member, mem.member_name + FROM {db_prefix}members AS mem + WHERE mem.openid_uri = {string:openid_uri}', + array( + 'openid_uri' => $url, + ) + ); + $member = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + return $member; +} + +// Prepare for a Diffie-Hellman key exchange. +function smf_openID_setup_DH($regenerate = false) +{ + global $p, $g; + + // First off, do we have BC Math available? + if (!function_exists('bcpow')) + return false; + + // Defined in OpenID spec. + $p = '155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443'; + $g = '2'; + + // Make sure the scale is set. + bcscale(0); + + return smf_openID_get_keys($regenerate); +} + +function smf_openID_get_keys($regenerate) +{ + global $modSettings, $p, $g; + + // Ok lets take the easy way out, are their any keys already defined for us? They are changed in the daily maintenance scheduled task. + if (!empty($modSettings['dh_keys']) && !$regenerate) + { + // Sweeeet! + list ($public, $private) = explode("\n", $modSettings['dh_keys']); + return array( + 'public' => base64_decode($public), + 'private' => base64_decode($private), + ); + } + + // Dang it, now I have to do math. And it's not just ordinary math, its the evil big interger math. This will take a few seconds. + $private = smf_openid_generate_private_key(); + $public = bcpowmod($g, $private, $p); + + // Now that we did all that work, lets save it so we don't have to keep doing it. + $keys = array('dh_keys' => base64_encode($public) . "\n" . base64_encode($private)); + updateSettings($keys); + + return array( + 'public' => $public, + 'private' => $private, + ); +} + +function smf_openid_generate_private_key() +{ + global $p; + static $cache = array(); + + $byte_string = long_to_binary($p); + + if (isset($cache[$byte_string])) + list ($dup, $num_bytes) = $cache[$byte_string]; + else + { + $num_bytes = strlen($byte_string) - ($byte_string[0] == "\x00" ? 1 : 0); + + $max_rand = bcpow(256, $num_bytes); + + $dup = bcmod($max_rand, $num_bytes); + + $cache[$byte_string] = array($dup, $num_bytes); + } + + do + { + $str = ''; + for ($i = 0; $i < $num_bytes; $i += 4) + $str .= pack('L', mt_rand()); + + $bytes = "\x00" . $str; + + $num = binary_to_long($bytes); + } while (bccomp($num, $dup) < 0); + + return bcadd(bcmod($num, $p), 1); +} + +function smf_openID_getServerInfo($openid_url) +{ + global $sourcedir; + + require_once($sourcedir . '/Subs-Package.php'); + + // Get the html and parse it for the openid variable which will tell us where to go. + $webdata = fetch_web_data($openid_url); + + if (empty($webdata)) + return false; + + $response_data = array(); + + // Some OpenID servers have strange but still valid HTML which makes our job hard. + if (preg_match_all('~~i', $webdata, $link_matches) == 0) + fatal_lang_error('openid_server_bad_response'); + + foreach ($link_matches[1] as $link_match) + { + if (preg_match('~rel="([\s\S]*?)"~i', $link_match, $rel_match) == 0 || preg_match('~href="([\s\S]*?)"~i', $link_match, $href_match) == 0) + continue; + + $rels = preg_split('~\s+~', $rel_match[1]); + foreach ($rels as $rel) + if (preg_match('~openid2?\.(server|delegate|provider)~i', $rel, $match) != 0) + $response_data[$match[1]] = $href_match[1]; + } + + if (empty($response_data['server'])) + if (empty($response_data['provider'])) + fatal_lang_error('openid_server_bad_response'); + else + $response_data['server'] = $response_data['provider']; + + return $response_data; +} + +function sha1_hmac($data, $key) +{ + + if (strlen($key) > 64) + $key = sha1_raw($key); + + // Pad the key if need be. + $key = str_pad($key, 64, chr(0x00)); + $ipad = str_repeat(chr(0x36), 64); + $opad = str_repeat(chr(0x5c), 64); + $hash1 = sha1_raw(($key ^ $ipad) . $data); + $hmac = sha1_raw(($key ^ $opad) . $hash1); + return $hmac; +} + +function sha1_raw($text) +{ + if (version_compare(PHP_VERSION, '5.0.0') >= 0) + return sha1($text, true); + + $hex = sha1($text); + $raw = ''; + for ($i = 0; $i < 40; $i += 2) + { + $hexcode = substr($hex, $i, 2); + $charcode = (int) base_convert($hexcode, 16, 10); + $raw .= chr($charcode); + } + + return $raw; +} + +function binary_to_long($str) +{ + $bytes = array_merge(unpack('C*', $str)); + + $n = 0; + + foreach ($bytes as $byte) + { + $n = bcmul($n, 256); + $n = bcadd($n, $byte); + } + + return $n; +} + +function long_to_binary($value) +{ + $cmp = bccomp($value, 0); + if ($cmp < 0) + fatal_error('Only non-negative integers allowed.'); + + if ($cmp == 0) + return "\x00"; + + $bytes = array(); + + while (bccomp($value, 0) > 0) + { + array_unshift($bytes, bcmod($value, 256)); + $value = bcdiv($value, 256); + } + + if ($bytes && ($bytes[0] > 127)) + array_unshift($bytes, 0); + + $return = ''; + foreach ($bytes as $byte) + $return .= pack('C', $byte); + + return $return; +} + +function binary_xor($num1, $num2) +{ + $return = ''; + + for ($i = 0; $i < strlen($num2); $i++) + $return .= $num1[$i] ^ $num2[$i]; + + return $return; +} + +// PHP 4 didn't have bcpowmod. +if (!function_exists('bcpowmod') && function_exists('bcpow')) +{ + function bcpowmod($num1, $num2, $num3) + { + return bcmod(bcpow($num1, $num2), $num3); + } +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Package.php b/Sources/Subs-Package.php new file mode 100644 index 0000000..1b901b9 --- /dev/null +++ b/Sources/Subs-Package.php @@ -0,0 +1,2995 @@ + $v) + { + if (in_array($k, $octdec)) + $current[$k] = octdec(trim($v)); + else + $current[$k] = trim($v); + } + + $checksum = 256; + for ($i = 0; $i < 148; $i++) + $checksum += ord($header{$i}); + for ($i = 156; $i < 512; $i++) + $checksum += ord($header{$i}); + + if ($current['checksum'] != $checksum) + break; + + $size = ceil($current['size'] / 512); + $current['data'] = substr($data, ++$offset << 9, $current['size']); + $offset += $size; + + // Not a directory and doesn't exist already... + if (substr($current['filename'], -1, 1) != '/' && !file_exists($destination . '/' . $current['filename'])) + $write_this = true; + // File exists... check if it is newer. + elseif (substr($current['filename'], -1, 1) != '/') + $write_this = $overwrite || filemtime($destination . '/' . $current['filename']) < $current['mtime']; + // Folder... create. + elseif ($destination !== null && !$single_file) + { + // Protect from accidental parent directory writing... + $current['filename'] = strtr($current['filename'], array('../' => '', '/..' => '')); + + if (!file_exists($destination . '/' . $current['filename'])) + mktree($destination . '/' . $current['filename'], 0777); + $write_this = false; + } + else + $write_this = false; + + if ($write_this && $destination !== null) + { + if (strpos($current['filename'], '/') !== false && !$single_file) + mktree($destination . '/' . dirname($current['filename']), 0777); + + // Is this the file we're looking for? + if ($single_file && ($destination == $current['filename'] || $destination == '*/' . basename($current['filename']))) + return $current['data']; + // If we're looking for another file, keep going. + elseif ($single_file) + continue; + // Looking for restricted files? + elseif ($files_to_extract !== null && !in_array($current['filename'], $files_to_extract)) + continue; + + package_put_contents($destination . '/' . $current['filename'], $current['data']); + } + + if (substr($current['filename'], -1, 1) != '/') + $return[] = array( + 'filename' => $current['filename'], + 'md5' => md5($current['data']), + 'preview' => substr($current['data'], 0, 100), + 'size' => $current['size'], + 'skipped' => false + ); + } + + if ($destination !== null && !$single_file) + package_flush_cache(); + + if ($single_file) + return false; + else + return $return; +} + +// Extract zip data. If destination is null, return a listing. +function read_zip_data($data, $destination, $single_file = false, $overwrite = false, $files_to_extract = null) +{ + umask(0); + if ($destination !== null && !file_exists($destination) && !$single_file) + mktree($destination, 0777); + + // Look for the PK header... + if (substr($data, 0, 2) != 'PK') + return false; + + // Find the central whosamawhatsit at the end; if there's a comment it's a pain. + if (substr($data, -22, 4) == 'PK' . chr(5) . chr(6)) + $p = -22; + else + { + // Have to find where the comment begins, ugh. + for ($p = -22; $p > -strlen($data); $p--) + { + if (substr($data, $p, 4) == 'PK' . chr(5) . chr(6)) + break; + } + } + + $return = array(); + + // Get the basic zip file info. + $zip_info = unpack('vfiles/Vsize/Voffset', substr($data, $p + 10, 10)); + + $p = $zip_info['offset']; + for ($i = 0; $i < $zip_info['files']; $i++) + { + // Make sure this is a file entry... + if (substr($data, $p, 4) != 'PK' . chr(1) . chr(2)) + return false; + + // Get all the important file information. + $file_info = unpack('Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset', substr($data, $p + 16, 30)); + $file_info['filename'] = substr($data, $p + 46, $file_info['filename_len']); + + // Skip all the information we don't care about anyway. + $p += 46 + $file_info['filename_len'] + $file_info['extra_len'] + $file_info['comment_len']; + + // If this is a file, and it doesn't exist.... happy days! + if (substr($file_info['filename'], -1, 1) != '/' && !file_exists($destination . '/' . $file_info['filename'])) + $write_this = true; + // If the file exists, we may not want to overwrite it. + elseif (substr($file_info['filename'], -1, 1) != '/') + $write_this = $overwrite; + // This is a directory, so we're gonna want to create it. (probably...) + elseif ($destination !== null && !$single_file) + { + // Just a little accident prevention, don't mind me. + $file_info['filename'] = strtr($file_info['filename'], array('../' => '', '/..' => '')); + + if (!file_exists($destination . '/' . $file_info['filename'])) + mktree($destination . '/' . $file_info['filename'], 0777); + $write_this = false; + } + else + $write_this = false; + + // Check that the data is there and does exist. + if (substr($data, $file_info['offset'], 4) != 'PK' . chr(3) . chr(4)) + return false; + + // Get the actual compressed data. + $file_info['data'] = substr($data, $file_info['offset'] + 30 + $file_info['filename_len'], $file_info['compressed_size']); + + // Only inflate it if we need to ;). + if ($file_info['compressed_size'] != $file_info['size']) + $file_info['data'] = @gzinflate($file_info['data']); + + // Okay! We can write this file, looks good from here... + if ($write_this && $destination !== null) + { + if (strpos($file_info['filename'], '/') !== false && !$single_file) + mktree($destination . '/' . dirname($file_info['filename']), 0777); + + // If we're looking for a specific file, and this is it... ka-bam, baby. + if ($single_file && ($destination == $file_info['filename'] || $destination == '*/' . basename($file_info['filename']))) + return $file_info['data']; + // Oh? Another file. Fine. You don't like this file, do you? I know how it is. Yeah... just go away. No, don't apologize. I know this file's just not *good enough* for you. + elseif ($single_file) + continue; + // Don't really want this? + elseif ($files_to_extract !== null && !in_array($file_info['filename'], $files_to_extract)) + continue; + + package_put_contents($destination . '/' . $file_info['filename'], $file_info['data']); + } + + if (substr($file_info['filename'], -1, 1) != '/') + $return[] = array( + 'filename' => $file_info['filename'], + 'md5' => md5($file_info['data']), + 'preview' => substr($file_info['data'], 0, 100), + 'size' => $file_info['size'], + 'skipped' => false + ); + } + + if ($destination !== null && !$single_file) + package_flush_cache(); + + if ($single_file) + return false; + else + return $return; +} + +// Checks the existence of a remote file since file_exists() does not do remote. +function url_exists($url) +{ + $a_url = parse_url($url); + + if (!isset($a_url['scheme'])) + return false; + + // Attempt to connect... + $temp = ''; + $fid = fsockopen($a_url['host'], !isset($a_url['port']) ? 80 : $a_url['port'], $temp, $temp, 8); + if (!$fid) + return false; + + fputs($fid, 'HEAD ' . $a_url['path'] . ' HTTP/1.0' . "\r\n" . 'Host: ' . $a_url['host'] . "\r\n\r\n"); + $head = fread($fid, 1024); + fclose($fid); + + return preg_match('~^HTTP/.+\s+(20[01]|30[127])~i', $head) == 1; +} + +// Load the installed packages. +function loadInstalledPackages() +{ + global $boarddir, $smcFunc; + + // First, check that the database is valid, installed.list is still king. + $install_file = implode('', file($boarddir . '/Packages/installed.list')); + if (trim($install_file) == '') + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_packages + SET install_state = {int:not_installed}', + array( + 'not_installed' => 0, + ) + ); + + // Don't have anything left, so send an empty array. + return array(); + } + + // Load the packages from the database - note this is ordered by install time to ensure latest package uninstalled first. + $request = $smcFunc['db_query']('', ' + SELECT id_install, package_id, filename, name, version + FROM {db_prefix}log_packages + WHERE install_state != {int:not_installed} + ORDER BY time_installed DESC', + array( + 'not_installed' => 0, + ) + ); + $installed = array(); + $found = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Already found this? If so don't add it twice! + if (in_array($row['package_id'], $found)) + continue; + + $found[] = $row['package_id']; + + $installed[] = array( + 'id' => $row['id_install'], + 'name' => $row['name'], + 'filename' => $row['filename'], + 'package_id' => $row['package_id'], + 'version' => $row['version'], + ); + } + $smcFunc['db_free_result']($request); + + return $installed; +} + +function getPackageInfo($gzfilename) +{ + global $boarddir; + + // Extract package-info.xml from downloaded file. (*/ is used because it could be in any directory.) + if (strpos($gzfilename, 'http://') !== false) + $packageInfo = read_tgz_data(fetch_web_data($gzfilename, '', true), '*/package-info.xml', true); + else + { + if (!file_exists($boarddir . '/Packages/' . $gzfilename)) + return 'package_get_error_not_found'; + + if (is_file($boarddir . '/Packages/' . $gzfilename)) + $packageInfo = read_tgz_file($boarddir . '/Packages/' . $gzfilename, '*/package-info.xml', true); + elseif (file_exists($boarddir . '/Packages/' . $gzfilename . '/package-info.xml')) + $packageInfo = file_get_contents($boarddir . '/Packages/' . $gzfilename . '/package-info.xml'); + else + return 'package_get_error_missing_xml'; + } + + // Nothing? + if (empty($packageInfo)) + return 'package_get_error_is_zero'; + + // Parse package-info.xml into an xmlArray. + loadClassFile('Class-Package.php'); + $packageInfo = new xmlArray($packageInfo); + + // !!! Error message of some sort? + if (!$packageInfo->exists('package-info[0]')) + return 'package_get_error_packageinfo_corrupt'; + + $packageInfo = $packageInfo->path('package-info[0]'); + + $package = $packageInfo->to_array(); + $package['xml'] = $packageInfo; + $package['filename'] = $gzfilename; + + if (!isset($package['type'])) + $package['type'] = 'modification'; + + return $package; +} + +// Create a chmod control for chmoding files. +function create_chmod_control($chmodFiles = array(), $chmodOptions = array(), $restore_write_status = false) +{ + global $context, $modSettings, $package_ftp, $boarddir, $txt, $sourcedir, $scripturl; + + // If we're restoring the status of existing files prepare the data. + if ($restore_write_status && isset($_SESSION['pack_ftp']) && !empty($_SESSION['pack_ftp']['original_perms'])) + { + function list_restoreFiles($dummy1, $dummy2, $dummy3, $do_change) + { + global $txt; + + $restore_files = array(); + foreach ($_SESSION['pack_ftp']['original_perms'] as $file => $perms) + { + // Check the file still exists, and the permissions were indeed different than now. + $file_permissions = @fileperms($file); + if (!file_exists($file) || $file_permissions == $perms) + { + unset($_SESSION['pack_ftp']['original_perms'][$file]); + continue; + } + + // Are we wanting to change the permission? + if ($do_change && isset($_POST['restore_files']) && in_array($file, $_POST['restore_files'])) + { + // Use FTP if we have it. + if (!empty($package_ftp)) + { + $ftp_file = strtr($file, array($_SESSION['pack_ftp']['root'] => '')); + $package_ftp->chmod($ftp_file, $perms); + } + else + @chmod($file, $perms); + + $new_permissions = @fileperms($file); + $result = $new_permissions == $perms ? 'success' : 'failure'; + unset($_SESSION['pack_ftp']['original_perms'][$file]); + } + elseif ($do_change) + { + $new_permissions = ''; + $result = 'skipped'; + unset($_SESSION['pack_ftp']['original_perms'][$file]); + } + + // Record the results! + $restore_files[] = array( + 'path' => $file, + 'old_perms_raw' => $perms, + 'old_perms' => substr(sprintf('%o', $perms), -4), + 'cur_perms' => substr(sprintf('%o', $file_permissions), -4), + 'new_perms' => isset($new_permissions) ? substr(sprintf('%o', $new_permissions), -4) : '', + 'result' => isset($result) ? $result : '', + 'writable_message' => '' . (@is_writable($file) ? $txt['package_file_perms_writable'] : $txt['package_file_perms_not_writable']) . '', + ); + } + + return $restore_files; + } + + $listOptions = array( + 'id' => 'restore_file_permissions', + 'title' => $txt['package_restore_permissions'], + 'get_items' => array( + 'function' => 'list_restoreFiles', + 'params' => array( + !empty($_POST['restore_perms']), + ), + ), + 'columns' => array( + 'path' => array( + 'header' => array( + 'value' => $txt['package_restore_permissions_filename'], + ), + 'data' => array( + 'db' => 'path', + 'class' => 'smalltext', + ), + ), + 'old_perms' => array( + 'header' => array( + 'value' => $txt['package_restore_permissions_orig_status'], + ), + 'data' => array( + 'db' => 'old_perms', + 'class' => 'smalltext', + ), + ), + 'cur_perms' => array( + 'header' => array( + 'value' => $txt['package_restore_permissions_cur_status'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt; + + $formatTxt = $rowData[\'result\'] == \'\' || $rowData[\'result\'] == \'skipped\' ? $txt[\'package_restore_permissions_pre_change\'] : $txt[\'package_restore_permissions_post_change\']; + return sprintf($formatTxt, $rowData[\'cur_perms\'], $rowData[\'new_perms\'], $rowData[\'writable_message\']); + '), + 'class' => 'smalltext', + ), + ), + 'check' => array( + 'header' => array( + 'value' => '', + ), + 'data' => array( + 'sprintf' => array( + 'format' => '', + 'params' => array( + 'path' => false, + ), + ), + 'style' => 'text-align: center', + ), + ), + 'result' => array( + 'header' => array( + 'value' => $txt['package_restore_permissions_result'], + ), + 'data' => array( + 'function' => create_function('$rowData', ' + global $txt; + + return $txt[\'package_restore_permissions_action_\' . $rowData[\'result\']]; + '), + 'class' => 'smalltext', + ), + ), + ), + 'form' => array( + 'href' => !empty($chmodOptions['destination_url']) ? $chmodOptions['destination_url'] : $scripturl . '?action=admin;area=packages;sa=perms;restore;' . $context['session_var'] . '=' . $context['session_id'], + ), + 'additional_rows' => array( + array( + 'position' => 'below_table_data', + 'value' => '', + 'class' => 'titlebg', + 'style' => 'text-align: right;', + ), + array( + 'position' => 'after_title', + 'value' => '' . $txt['package_restore_permissions_desc'] . '', + 'class' => 'windowbg2', + ), + ), + ); + + // Work out what columns and the like to show. + if (!empty($_POST['restore_perms'])) + { + $listOptions['additional_rows'][1]['value'] = sprintf($txt['package_restore_permissions_action_done'], $scripturl . '?action=admin;area=packages;sa=perms;' . $context['session_var'] . '=' . $context['session_id']); + unset($listOptions['columns']['check'], $listOptions['form'], $listOptions['additional_rows'][0]); + + $context['sub_template'] = 'show_list'; + $context['default_list'] = 'restore_file_permissions'; + } + else + { + unset($listOptions['columns']['result']); + } + + // Create the list for display. + require_once($sourcedir . '/Subs-List.php'); + createList($listOptions); + + // If we just restored permissions then whereever we are, we are now done and dusted. + if (!empty($_POST['restore_perms'])) + obExit(); + } + // Otherwise, it's entirely irrelevant? + elseif ($restore_write_status) + return true; + + // This is where we report what we got up to. + $return_data = array( + 'files' => array( + 'writable' => array(), + 'notwritable' => array(), + ), + ); + + // If we have some FTP information already, then let's assume it was required and try to get ourselves connected. + if (!empty($_SESSION['pack_ftp']['connected'])) + { + // Load the file containing the ftp_connection class. + loadClassFile('Class-Package.php'); + + $package_ftp = new ftp_connection($_SESSION['pack_ftp']['server'], $_SESSION['pack_ftp']['port'], $_SESSION['pack_ftp']['username'], package_crypt($_SESSION['pack_ftp']['password'])); + } + + // Just got a submission did we? + if (empty($package_ftp) && isset($_POST['ftp_username'])) + { + loadClassFile('Class-Package.php'); + $ftp = new ftp_connection($_POST['ftp_server'], $_POST['ftp_port'], $_POST['ftp_username'], $_POST['ftp_password']); + + // We're connected, jolly good! + if ($ftp->error === false) + { + // Common mistake, so let's try to remedy it... + if (!$ftp->chdir($_POST['ftp_path'])) + { + $ftp_error = $ftp->last_message; + $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['ftp_path'])); + } + + if (!in_array($_POST['ftp_path'], array('', '/'))) + { + $ftp_root = strtr($boarddir, array($_POST['ftp_path'] => '')); + if (substr($ftp_root, -1) == '/' && ($_POST['ftp_path'] == '' || substr($_POST['ftp_path'], 0, 1) == '/')) + $ftp_root = substr($ftp_root, 0, -1); + } + else + $ftp_root = $boarddir; + + $_SESSION['pack_ftp'] = array( + 'server' => $_POST['ftp_server'], + 'port' => $_POST['ftp_port'], + 'username' => $_POST['ftp_username'], + 'password' => package_crypt($_POST['ftp_password']), + 'path' => $_POST['ftp_path'], + 'root' => $ftp_root, + 'connected' => true, + ); + + if (!isset($modSettings['package_path']) || $modSettings['package_path'] != $_POST['ftp_path']) + updateSettings(array('package_path' => $_POST['ftp_path'])); + + // This is now the primary connection. + $package_ftp = $ftp; + } + } + + // Now try to simply make the files writable, with whatever we might have. + if (!empty($chmodFiles)) + { + foreach ($chmodFiles as $k => $file) + { + // Sometimes this can somehow happen maybe? + if (empty($file)) + unset($chmodFiles[$k]); + // Already writable? + elseif (@is_writable($file)) + $return_data['files']['writable'][] = $file; + else + { + // Now try to change that. + $return_data['files'][package_chmod($file, 'writable', true) ? 'writable' : 'notwritable'][] = $file; + } + } + } + + // Have we still got nasty files which ain't writable? Dear me we need more FTP good sir. + if (empty($package_ftp) && (!empty($return_data['files']['notwritable']) || !empty($chmodOptions['force_find_error']))) + { + if (!isset($ftp) || $ftp->error !== false) + { + if (!isset($ftp)) + { + loadClassFile('Class-Package.php'); + $ftp = new ftp_connection(null); + } + elseif ($ftp->error !== false && !isset($ftp_error)) + $ftp_error = $ftp->last_message === null ? '' : $ftp->last_message; + + list ($username, $detect_path, $found_path) = $ftp->detect_path($boarddir); + + if ($found_path) + $_POST['ftp_path'] = $detect_path; + elseif (!isset($_POST['ftp_path'])) + $_POST['ftp_path'] = isset($modSettings['package_path']) ? $modSettings['package_path'] : $detect_path; + + if (!isset($_POST['ftp_username'])) + $_POST['ftp_username'] = $username; + } + + $context['package_ftp'] = array( + 'server' => isset($_POST['ftp_server']) ? $_POST['ftp_server'] : (isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost'), + 'port' => isset($_POST['ftp_port']) ? $_POST['ftp_port'] : (isset($modSettings['package_port']) ? $modSettings['package_port'] : '21'), + 'username' => isset($_POST['ftp_username']) ? $_POST['ftp_username'] : (isset($modSettings['package_username']) ? $modSettings['package_username'] : ''), + 'path' => $_POST['ftp_path'], + 'error' => empty($ftp_error) ? null : $ftp_error, + 'destination' => !empty($chmodOptions['destination_url']) ? $chmodOptions['destination_url'] : '', + ); + + // Which files failed? + if (!isset($context['notwritable_files'])) + $context['notwritable_files'] = array(); + $context['notwritable_files'] = array_merge($context['notwritable_files'], $return_data['files']['notwritable']); + + // Sent here to die? + if (!empty($chmodOptions['crash_on_error'])) + { + $context['page_title'] = $txt['package_ftp_necessary']; + $context['sub_template'] = 'ftp_required'; + obExit(); + } + } + + return $return_data; +} + +function packageRequireFTP($destination_url, $files = null, $return = false) +{ + global $context, $modSettings, $package_ftp, $boarddir, $txt; + + // Try to make them writable the manual way. + if ($files !== null) + { + foreach ($files as $k => $file) + { + // If this file doesn't exist, then we actually want to look at the directory, no? + if (!file_exists($file)) + $file = dirname($file); + + // This looks odd, but it's an attempt to work around PHP suExec. + if (!@is_writable($file)) + @chmod($file, 0755); + if (!@is_writable($file)) + @chmod($file, 0777); + if (!@is_writable(dirname($file))) + @chmod($file, 0755); + if (!@is_writable(dirname($file))) + @chmod($file, 0777); + + $fp = is_dir($file) ? @opendir($file) : @fopen($file, 'rb'); + if (@is_writable($file) && $fp) + { + unset($files[$k]); + if (!is_dir($file)) + fclose($fp); + else + closedir($fp); + } + } + + // No FTP required! + if (empty($files)) + return array(); + } + + // They've opted to not use FTP, and try anyway. + if (isset($_SESSION['pack_ftp']) && $_SESSION['pack_ftp'] == false) + { + if ($files === null) + return array(); + + foreach ($files as $k => $file) + { + // This looks odd, but it's an attempt to work around PHP suExec. + if (!file_exists($file)) + { + mktree(dirname($file), 0755); + @touch($file); + @chmod($file, 0755); + } + + if (!@is_writable($file)) + @chmod($file, 0777); + if (!@is_writable(dirname($file))) + @chmod(dirname($file), 0777); + + if (@is_writable($file)) + unset($files[$k]); + } + + return $files; + } + elseif (isset($_SESSION['pack_ftp'])) + { + // Load the file containing the ftp_connection class. + loadClassFile('Class-Package.php'); + + $package_ftp = new ftp_connection($_SESSION['pack_ftp']['server'], $_SESSION['pack_ftp']['port'], $_SESSION['pack_ftp']['username'], package_crypt($_SESSION['pack_ftp']['password'])); + + if ($files === null) + return array(); + + foreach ($files as $k => $file) + { + $ftp_file = strtr($file, array($_SESSION['pack_ftp']['root'] => '')); + + // This looks odd, but it's an attempt to work around PHP suExec. + if (!file_exists($file)) + { + mktree(dirname($file), 0755); + $package_ftp->create_file($ftp_file); + $package_ftp->chmod($ftp_file, 0755); + } + + if (!@is_writable($file)) + $package_ftp->chmod($ftp_file, 0777); + if (!@is_writable(dirname($file))) + $package_ftp->chmod(dirname($ftp_file), 0777); + + if (@is_writable($file)) + unset($files[$k]); + } + + return $files; + } + + if (isset($_POST['ftp_none'])) + { + $_SESSION['pack_ftp'] = false; + + $files = packageRequireFTP($destination_url, $files, $return); + return $files; + } + elseif (isset($_POST['ftp_username'])) + { + loadClassFile('Class-Package.php'); + $ftp = new ftp_connection($_POST['ftp_server'], $_POST['ftp_port'], $_POST['ftp_username'], $_POST['ftp_password']); + + if ($ftp->error === false) + { + // Common mistake, so let's try to remedy it... + if (!$ftp->chdir($_POST['ftp_path'])) + { + $ftp_error = $ftp->last_message; + $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['ftp_path'])); + } + } + } + + if (!isset($ftp) || $ftp->error !== false) + { + if (!isset($ftp)) + { + loadClassFile('Class-Package.php'); + $ftp = new ftp_connection(null); + } + elseif ($ftp->error !== false && !isset($ftp_error)) + $ftp_error = $ftp->last_message === null ? '' : $ftp->last_message; + + list ($username, $detect_path, $found_path) = $ftp->detect_path($boarddir); + + if ($found_path) + $_POST['ftp_path'] = $detect_path; + elseif (!isset($_POST['ftp_path'])) + $_POST['ftp_path'] = isset($modSettings['package_path']) ? $modSettings['package_path'] : $detect_path; + + if (!isset($_POST['ftp_username'])) + $_POST['ftp_username'] = $username; + + $context['package_ftp'] = array( + 'server' => isset($_POST['ftp_server']) ? $_POST['ftp_server'] : (isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost'), + 'port' => isset($_POST['ftp_port']) ? $_POST['ftp_port'] : (isset($modSettings['package_port']) ? $modSettings['package_port'] : '21'), + 'username' => isset($_POST['ftp_username']) ? $_POST['ftp_username'] : (isset($modSettings['package_username']) ? $modSettings['package_username'] : ''), + 'path' => $_POST['ftp_path'], + 'error' => empty($ftp_error) ? null : $ftp_error, + 'destination' => $destination_url, + ); + + // If we're returning dump out here. + if ($return) + return $files; + + $context['page_title'] = $txt['package_ftp_necessary']; + $context['sub_template'] = 'ftp_required'; + obExit(); + } + else + { + if (!in_array($_POST['ftp_path'], array('', '/'))) + { + $ftp_root = strtr($boarddir, array($_POST['ftp_path'] => '')); + if (substr($ftp_root, -1) == '/' && ($_POST['ftp_path'] == '' || substr($_POST['ftp_path'], 0, 1) == '/')) + $ftp_root = substr($ftp_root, 0, -1); + } + else + $ftp_root = $boarddir; + + $_SESSION['pack_ftp'] = array( + 'server' => $_POST['ftp_server'], + 'port' => $_POST['ftp_port'], + 'username' => $_POST['ftp_username'], + 'password' => package_crypt($_POST['ftp_password']), + 'path' => $_POST['ftp_path'], + 'root' => $ftp_root, + ); + + if (!isset($modSettings['package_path']) || $modSettings['package_path'] != $_POST['ftp_path']) + updateSettings(array('package_path' => $_POST['ftp_path'])); + + $files = packageRequireFTP($destination_url, $files, $return); + } + + return $files; +} + +// Parses a package-info.xml file - method can be 'install', 'upgrade', or 'uninstall'. +function parsePackageInfo(&$packageXML, $testing_only = true, $method = 'install', $previous_version = '') +{ + global $boarddir, $forum_version, $context, $temp_path, $language; + + // Mayday! That action doesn't exist!! + if (empty($packageXML) || !$packageXML->exists($method)) + return array(); + + // We haven't found the package script yet... + $script = false; + $the_version = strtr($forum_version, array('SMF ' => '')); + + // Emulation support... + if (!empty($_SESSION['version_emulate'])) + $the_version = $_SESSION['version_emulate']; + + // Get all the versions of this method and find the right one. + $these_methods = $packageXML->set($method); + foreach ($these_methods as $this_method) + { + // They specified certain versions this part is for. + if ($this_method->exists('@for')) + { + // Don't keep going if this won't work for this version of SMF. + if (!matchPackageVersion($the_version, $this_method->fetch('@for'))) + continue; + } + + // Upgrades may go from a certain old version of the mod. + if ($method == 'upgrade' && $this_method->exists('@from')) + { + // Well, this is for the wrong old version... + if (!matchPackageVersion($previous_version, $this_method->fetch('@from'))) + continue; + } + + // We've found it! + $script = $this_method; + break; + } + + // Bad news, a matching script wasn't found! + if ($script === false) + return array(); + + // Find all the actions in this method - in theory, these should only be allowed actions. (* means all.) + $actions = $script->set('*'); + $return = array(); + + $temp_auto = 0; + $temp_path = $boarddir . '/Packages/temp/' . (isset($context['base_path']) ? $context['base_path'] : ''); + + $context['readmes'] = array(); + // This is the testing phase... nothing shall be done yet. + foreach ($actions as $action) + { + $actionType = $action->name(); + + if ($actionType == 'readme' || $actionType == 'code' || $actionType == 'database' || $actionType == 'modification' || $actionType == 'redirect') + { + // Allow for translated readme files. + if ($actionType == 'readme') + { + if ($action->exists('@lang')) + { + // Auto-select a readme language based on either request variable or current language. + if ((isset($_REQUEST['readme']) && $action->fetch('@lang') == $_REQUEST['readme']) || (!isset($_REQUEST['readme']) && $action->fetch('@lang') == $language)) + { + // In case the user put the readme blocks in the wrong order. + if (isset($context['readmes']['selected']) && $context['readmes']['selected'] == 'default') + $context['readmes'][] = 'default'; + + $context['readmes']['selected'] = htmlspecialchars($action->fetch('@lang')); + } + else + { + // We don't want this readme now, but we'll allow the user to select to read it. + $context['readmes'][] = htmlspecialchars($action->fetch('@lang')); + continue; + } + } + // Fallback readme. Without lang parameter. + else + { + + // Already selected a readme. + if (isset($context['readmes']['selected'])) + { + $context['readmes'][] = 'default'; + continue; + } + else + $context['readmes']['selected'] = 'default'; + } + } + + // !!! TODO: Make sure the file actually exists? Might not work when testing? + if ($action->exists('@type') && $action->fetch('@type') == 'inline') + { + $filename = $temp_path . '$auto_' . $temp_auto++ . ($actionType == 'readme' || $actionType == 'redirect' ? '.txt' : ($actionType == 'code' || $actionType == 'database' ? '.php' : '.mod')); + package_put_contents($filename, $action->fetch('.')); + $filename = strtr($filename, array($temp_path => '')); + } + else + $filename = $action->fetch('.'); + + $return[] = array( + 'type' => $actionType, + 'filename' => $filename, + 'description' => '', + 'reverse' => $action->exists('@reverse') && $action->fetch('@reverse') == 'true', + 'boardmod' => $action->exists('@format') && $action->fetch('@format') == 'boardmod', + 'redirect_url' => $action->exists('@url') ? $action->fetch('@url') : '', + 'redirect_timeout' => $action->exists('@timeout') ? (int) $action->fetch('@timeout') : '', + 'parse_bbc' => $action->exists('@parsebbc') && $action->fetch('@parsebbc') == 'true', + 'language' => ($actionType == 'readme' && $action->exists('@lang') && $action->fetch('@lang') == $language) ? $language : '', + ); + + continue; + } + elseif ($actionType == 'error') + { + $return[] = array( + 'type' => 'error', + ); + } + + $this_action = &$return[]; + $this_action = array( + 'type' => $actionType, + 'filename' => $action->fetch('@name'), + 'description' => $action->fetch('.') + ); + + // If there is a destination, make sure it makes sense. + if (substr($actionType, 0, 6) != 'remove') + { + $this_action['unparsed_destination'] = $action->fetch('@destination'); + $this_action['destination'] = parse_path($action->fetch('@destination')) . '/' . basename($this_action['filename']); + } + else + { + $this_action['unparsed_filename'] = $this_action['filename']; + $this_action['filename'] = parse_path($this_action['filename']); + } + + // If we're moving or requiring (copying) a file. + if (substr($actionType, 0, 4) == 'move' || substr($actionType, 0, 7) == 'require') + { + if ($action->exists('@from')) + $this_action['source'] = parse_path($action->fetch('@from')); + else + $this_action['source'] = $temp_path . $this_action['filename']; + } + + // Check if these things can be done. (chmod's etc.) + if ($actionType == 'create-dir') + { + if (!mktree($this_action['destination'], false)) + { + $temp = $this_action['destination']; + while (!file_exists($temp) && strlen($temp) > 1) + $temp = dirname($temp); + + $return[] = array( + 'type' => 'chmod', + 'filename' => $temp + ); + } + } + elseif ($actionType == 'create-file') + { + if (!mktree(dirname($this_action['destination']), false)) + { + $temp = dirname($this_action['destination']); + while (!file_exists($temp) && strlen($temp) > 1) + $temp = dirname($temp); + + $return[] = array( + 'type' => 'chmod', + 'filename' => $temp + ); + } + + if (!is_writable($this_action['destination']) && (file_exists($this_action['destination']) || !is_writable(dirname($this_action['destination'])))) + $return[] = array( + 'type' => 'chmod', + 'filename' => $this_action['destination'] + ); + } + elseif ($actionType == 'require-dir') + { + if (!mktree($this_action['destination'], false)) + { + $temp = $this_action['destination']; + while (!file_exists($temp) && strlen($temp) > 1) + $temp = dirname($temp); + + $return[] = array( + 'type' => 'chmod', + 'filename' => $temp + ); + } + } + elseif ($actionType == 'require-file') + { + if ($action->exists('@theme')) + $this_action['theme_action'] = $action->fetch('@theme'); + + if (!mktree(dirname($this_action['destination']), false)) + { + $temp = dirname($this_action['destination']); + while (!file_exists($temp) && strlen($temp) > 1) + $temp = dirname($temp); + + $return[] = array( + 'type' => 'chmod', + 'filename' => $temp + ); + } + + if (!is_writable($this_action['destination']) && (file_exists($this_action['destination']) || !is_writable(dirname($this_action['destination'])))) + $return[] = array( + 'type' => 'chmod', + 'filename' => $this_action['destination'] + ); + } + elseif ($actionType == 'move-dir' || $actionType == 'move-file') + { + if (!mktree(dirname($this_action['destination']), false)) + { + $temp = dirname($this_action['destination']); + while (!file_exists($temp) && strlen($temp) > 1) + $temp = dirname($temp); + + $return[] = array( + 'type' => 'chmod', + 'filename' => $temp + ); + } + + if (!is_writable($this_action['destination']) && (file_exists($this_action['destination']) || !is_writable(dirname($this_action['destination'])))) + $return[] = array( + 'type' => 'chmod', + 'filename' => $this_action['destination'] + ); + } + elseif ($actionType == 'remove-dir') + { + if (!is_writable($this_action['filename']) && file_exists($this_action['destination'])) + $return[] = array( + 'type' => 'chmod', + 'filename' => $this_action['filename'] + ); + } + elseif ($actionType == 'remove-file') + { + if (!is_writable($this_action['filename']) && file_exists($this_action['filename'])) + $return[] = array( + 'type' => 'chmod', + 'filename' => $this_action['filename'] + ); + } + } + + // Only testing - just return a list of things to be done. + if ($testing_only) + return $return; + + umask(0); + + $failure = false; + $not_done = array(array('type' => '!')); + foreach ($return as $action) + { + if ($action['type'] == 'modification' || $action['type'] == 'code' || $action['type'] == 'database' || $action['type'] == 'redirect') + $not_done[] = $action; + + if ($action['type'] == 'create-dir') + { + if (!mktree($action['destination'], 0755) || !is_writable($action['destination'])) + $failure |= !mktree($action['destination'], 0777); + } + elseif ($action['type'] == 'create-file') + { + if (!mktree(dirname($action['destination']), 0755) || !is_writable(dirname($action['destination']))) + $failure |= !mktree(dirname($action['destination']), 0777); + + // Create an empty file. + package_put_contents($action['destination'], package_get_contents($action['source']), $testing_only); + + if (!file_exists($action['destination'])) + $failure = true; + } + elseif ($action['type'] == 'require-dir') + { + copytree($action['source'], $action['destination']); + // Any other theme folders? + if (!empty($context['theme_copies']) && !empty($context['theme_copies'][$action['type']][$action['destination']])) + foreach ($context['theme_copies'][$action['type']][$action['destination']] as $theme_destination) + copytree($action['source'], $theme_destination); + } + elseif ($action['type'] == 'require-file') + { + if (!mktree(dirname($action['destination']), 0755) || !is_writable(dirname($action['destination']))) + $failure |= !mktree(dirname($action['destination']), 0777); + + package_put_contents($action['destination'], package_get_contents($action['source']), $testing_only); + + $failure |= !copy($action['source'], $action['destination']); + + // Any other theme files? + if (!empty($context['theme_copies']) && !empty($context['theme_copies'][$action['type']][$action['destination']])) + foreach ($context['theme_copies'][$action['type']][$action['destination']] as $theme_destination) + { + if (!mktree(dirname($theme_destination), 0755) || !is_writable(dirname($theme_destination))) + $failure |= !mktree(dirname($theme_destination), 0777); + + package_put_contents($theme_destination, package_get_contents($action['source']), $testing_only); + + $failure |= !copy($action['source'], $theme_destination); + } + } + elseif ($action['type'] == 'move-file') + { + if (!mktree(dirname($action['destination']), 0755) || !is_writable(dirname($action['destination']))) + $failure |= !mktree(dirname($action['destination']), 0777); + + $failure |= !rename($action['source'], $action['destination']); + } + elseif ($action['type'] == 'move-dir') + { + if (!mktree($action['destination'], 0755) || !is_writable($action['destination'])) + $failure |= !mktree($action['destination'], 0777); + + $failure |= !rename($action['source'], $action['destination']); + } + elseif ($action['type'] == 'remove-dir') + { + deltree($action['filename']); + + // Any other theme folders? + if (!empty($context['theme_copies']) && !empty($context['theme_copies'][$action['type']][$action['filename']])) + foreach ($context['theme_copies'][$action['type']][$action['filename']] as $theme_destination) + deltree($theme_destination); + } + elseif ($action['type'] == 'remove-file') + { + // Make sure the file exists before deleting it. + if (file_exists($action['filename'])) + { + package_chmod($action['filename']); + $failure |= !unlink($action['filename']); + } + // The file that was supposed to be deleted couldn't be found. + else + $failure = true; + + // Any other theme folders? + if (!empty($context['theme_copies']) && !empty($context['theme_copies'][$action['type']][$action['filename']])) + foreach ($context['theme_copies'][$action['type']][$action['filename']] as $theme_destination) + if (file_exists($theme_destination)) + $failure |= !unlink($theme_destination); + else + $failure = true; + } + } + + return $not_done; +} + +// This function tries to match $version into any of the ranges given in $versions +function matchPackageVersion($version, $versions) +{ + // Make sure everything is lowercase and clean of spaces and unpleasant history. + $version = str_replace(array(' ', '2.0rc1-1'), array('', '2.0rc1.1'), strtolower($version)); + $versions = explode(',', str_replace(array(' ', '2.0rc1-1'), array('', '2.0rc1.1'), strtolower($versions))); + + // Perhaps we do accept anything? + if (in_array('all', $versions)) + return true; + + // Loop through each version. + foreach ($versions as $for) + { + // Wild card spotted? + if (strpos($for, '*') !== false) + $for = str_replace('*', '0dev0', $for) . '-' . str_replace('*', '999', $for); + + // Do we have a range? + if (strpos($for, '-') !== false) + { + list ($lower, $upper) = explode('-', $for); + + // Compare the version against lower and upper bounds. + if (compareVersions($version, $lower) > -1 && compareVersions($version, $upper) < 1) + return true; + } + // Otherwise check if they are equal... + elseif (compareVersions($version, $for) === 0) + return true; + } + + return false; +} + +// The geek version of versioning checks for dummies, which basically compares two versions. +function compareVersions($version1, $version2) +{ + static $categories; + + $versions = array(); + foreach (array(1 => $version1, $version2) as $id => $version) + { + // Clean the version and extract the version parts. + $clean = str_replace(array(' ', '2.0rc1-1'), array('', '2.0rc1.1'), strtolower($version)); + preg_match('~(\d+)(?:\.(\d+|))?(?:\.)?(\d+|)(?:(alpha|beta|rc)(\d+|)(?:\.)?(\d+|))?(?:(dev))?(\d+|)~', $clean, $parts); + + // Build an array of parts. + $versions[$id] = array( + 'major' => (int) $parts[1], + 'minor' => !empty($parts[2]) ? (int) $parts[2] : 0, + 'patch' => !empty($parts[3]) ? (int) $parts[3] : 0, + 'type' => empty($parts[4]) ? 'stable' : $parts[4], + 'type_major' => !empty($parts[6]) ? (int) $parts[5] : 0, + 'type_minor' => !empty($parts[6]) ? (int) $parts[6] : 0, + 'dev' => !empty($parts[7]), + ); + } + + // Are they the same, perhaps? + if ($versions[1] === $versions[2]) + return 0; + + // Get version numbering categories... + if (!isset($categories)) + $categories = array_keys($versions[1]); + + // Loop through each category. + foreach ($categories as $category) + { + // Is there something for us to calculate? + if ($versions[1][$category] !== $versions[2][$category]) + { + // Dev builds are a problematic exception. + // (stable) dev < (stable) but (unstable) dev = (unstable) + if ($category == 'type') + return $versions[1][$category] > $versions[2][$category] ? ($versions[1]['dev'] ? -1 : 1) : ($versions[2]['dev'] ? 1 : -1); + elseif ($category == 'dev') + return $versions[1]['dev'] ? ($versions[2]['type'] == 'stable' ? -1 : 0) : ($versions[1]['type'] == 'stable' ? 1 : 0); + // Otherwise a simple comparison. + else + return $versions[1][$category] > $versions[2][$category] ? 1 : -1; + } + } + + // They are the same! + return 0; +} + +function parse_path($path) +{ + global $modSettings, $boarddir, $sourcedir, $settings, $temp_path; + + $dirs = array( + '\\' => '/', + '$boarddir' => $boarddir, + '$sourcedir' => $sourcedir, + '$avatardir' => $modSettings['avatar_directory'], + '$avatars_dir' => $modSettings['avatar_directory'], + '$themedir' => $settings['default_theme_dir'], + '$imagesdir' => $settings['default_theme_dir'] . '/' . basename($settings['default_images_url']), + '$themes_dir' => $boarddir . '/Themes', + '$languagedir' => $settings['default_theme_dir'] . '/languages', + '$languages_dir' => $settings['default_theme_dir'] . '/languages', + '$smileysdir' => $modSettings['smileys_dir'], + '$smileys_dir' => $modSettings['smileys_dir'], + ); + + // do we parse in a package directory? + if (!empty($temp_path)) + $dirs['$package'] = $temp_path; + + if (strlen($path) == 0) + trigger_error('parse_path(): There should never be an empty filename', E_USER_ERROR); + + return strtr($path, $dirs); +} + +function deltree($dir, $delete_dir = true) +{ + global $package_ftp; + + if (!file_exists($dir)) + return; + + $current_dir = @opendir($dir); + if ($current_dir == false) + { + if ($delete_dir && isset($package_ftp)) + { + $ftp_file = strtr($dir, array($_SESSION['pack_ftp']['root'] => '')); + if (!is_writable($dir . '/' . $entryname)) + $package_ftp->chmod($ftp_file, 0777); + $package_ftp->unlink($ftp_file); + } + + return; + } + + while ($entryname = readdir($current_dir)) + { + if (in_array($entryname, array('.', '..'))) + continue; + + if (is_dir($dir . '/' . $entryname)) + deltree($dir . '/' . $entryname); + else + { + // Here, 755 doesn't really matter since we're deleting it anyway. + if (isset($package_ftp)) + { + $ftp_file = strtr($dir . '/' . $entryname, array($_SESSION['pack_ftp']['root'] => '')); + + if (!is_writable($dir . '/' . $entryname)) + $package_ftp->chmod($ftp_file, 0777); + $package_ftp->unlink($ftp_file); + } + else + { + if (!is_writable($dir . '/' . $entryname)) + @chmod($dir . '/' . $entryname, 0777); + unlink($dir . '/' . $entryname); + } + } + } + + closedir($current_dir); + + if ($delete_dir) + { + if (isset($package_ftp)) + { + $ftp_file = strtr($dir, array($_SESSION['pack_ftp']['root'] => '')); + if (!is_writable($dir . '/' . $entryname)) + $package_ftp->chmod($ftp_file, 0777); + $package_ftp->unlink($ftp_file); + } + else + { + if (!is_writable($dir)) + @chmod($dir, 0777); + @rmdir($dir); + } + } +} + +function mktree($strPath, $mode) +{ + global $package_ftp; + + if (is_dir($strPath)) + { + if (!is_writable($strPath) && $mode !== false) + { + if (isset($package_ftp)) + $package_ftp->chmod(strtr($strPath, array($_SESSION['pack_ftp']['root'] => '')), $mode); + else + @chmod($strPath, $mode); + } + + $test = @opendir($strPath); + if ($test) + { + closedir($test); + return is_writable($strPath); + } + else + return false; + } + // Is this an invalid path and/or we can't make the directory? + if ($strPath == dirname($strPath) || !mktree(dirname($strPath), $mode)) + return false; + + if (!is_writable(dirname($strPath)) && $mode !== false) + { + if (isset($package_ftp)) + $package_ftp->chmod(dirname(strtr($strPath, array($_SESSION['pack_ftp']['root'] => ''))), $mode); + else + @chmod(dirname($strPath), $mode); + } + + if ($mode !== false && isset($package_ftp)) + return $package_ftp->create_dir(strtr($strPath, array($_SESSION['pack_ftp']['root'] => ''))); + elseif ($mode === false) + { + $test = @opendir(dirname($strPath)); + if ($test) + { + closedir($test); + return true; + } + else + return false; + } + else + { + @mkdir($strPath, $mode); + $test = @opendir($strPath); + if ($test) + { + closedir($test); + return true; + } + else + return false; + } +} + +function copytree($source, $destination) +{ + global $package_ftp; + + if (!file_exists($destination) || !is_writable($destination)) + mktree($destination, 0755); + if (!is_writable($destination)) + mktree($destination, 0777); + + $current_dir = opendir($source); + if ($current_dir == false) + return; + + while ($entryname = readdir($current_dir)) + { + if (in_array($entryname, array('.', '..'))) + continue; + + if (isset($package_ftp)) + $ftp_file = strtr($destination . '/' . $entryname, array($_SESSION['pack_ftp']['root'] => '')); + + if (is_file($source . '/' . $entryname)) + { + if (isset($package_ftp) && !file_exists($destination . '/' . $entryname)) + $package_ftp->create_file($ftp_file); + elseif (!file_exists($destination . '/' . $entryname)) + @touch($destination . '/' . $entryname); + } + + package_chmod($destination . '/' . $entryname); + + if (is_dir($source . '/' . $entryname)) + copytree($source . '/' . $entryname, $destination . '/' . $entryname); + elseif (file_exists($destination . '/' . $entryname)) + package_put_contents($destination . '/' . $entryname, package_get_contents($source . '/' . $entryname)); + else + copy($source . '/' . $entryname, $destination . '/' . $entryname); + } + + closedir($current_dir); +} + +function listtree($path, $sub_path = '') +{ + $data = array(); + + $dir = @dir($path . $sub_path); + if (!$dir) + return array(); + while ($entry = $dir->read()) + { + if ($entry == '.' || $entry == '..') + continue; + + if (is_dir($path . $sub_path . '/' . $entry)) + $data = array_merge($data, listtree($path, $sub_path . '/' . $entry)); + else + $data[] = array( + 'filename' => $sub_path == '' ? $entry : $sub_path . '/' . $entry, + 'size' => filesize($path . $sub_path . '/' . $entry), + 'skipped' => false, + ); + } + $dir->close(); + + return $data; +} + +// Parse an xml based modification file. +function parseModification($file, $testing = true, $undo = false, $theme_paths = array()) +{ + global $boarddir, $sourcedir, $settings, $txt, $modSettings, $package_ftp; + + @set_time_limit(600); + loadClassFile('Class-Package.php'); + $xml = new xmlArray(strtr($file, array("\r" => ''))); + $actions = array(); + $everything_found = true; + + if (!$xml->exists('modification') || !$xml->exists('modification/file')) + { + $actions[] = array( + 'type' => 'error', + 'filename' => '-', + 'debug' => $txt['package_modification_malformed'] + ); + return $actions; + } + + // Get the XML data. + $files = $xml->set('modification/file'); + + // Use this for holding all the template changes in this mod. + $template_changes = array(); + // This is needed to hold the long paths, as they can vary... + $long_changes = array(); + + // First, we need to build the list of all the files likely to get changed. + foreach ($files as $file) + { + // What is the filename we're currently on? + $filename = parse_path(trim($file->fetch('@name'))); + + // Now, we need to work out whether this is even a template file... + foreach ($theme_paths as $id => $theme) + { + // If this filename is relative, if so take a guess at what it should be. + $real_filename = $filename; + if (strpos($filename, 'Themes') === 0) + $real_filename = $boarddir . '/' . $filename; + + if (strpos($real_filename, $theme['theme_dir']) === 0) + { + $template_changes[$id][] = substr($real_filename, strlen($theme['theme_dir']) + 1); + $long_changes[$id][] = $filename; + } + } + } + + // Custom themes to add. + $custom_themes_add = array(); + + // If we have some template changes, we need to build a master link of what new ones are required for the custom themes. + if (!empty($template_changes[1])) + { + foreach ($theme_paths as $id => $theme) + { + // Default is getting done anyway, so no need for involvement here. + if ($id == 1) + continue; + + // For every template, do we want it? Yea, no, maybe? + foreach ($template_changes[1] as $index => $template_file) + { + // What, it exists and we haven't already got it?! Lordy, get it in! + if (file_exists($theme['theme_dir'] . '/' . $template_file) && (!isset($template_changes[$id]) || !in_array($template_file, $template_changes[$id]))) + { + // Now let's add it to the "todo" list. + $custom_themes_add[$long_changes[1][$index]][$id] = $theme['theme_dir'] . '/' . $template_file; + } + } + } + } + + foreach ($files as $file) + { + // This is the actual file referred to in the XML document... + $files_to_change = array( + 1 => parse_path(trim($file->fetch('@name'))), + ); + + // Sometimes though, we have some additional files for other themes, if we have add them to the mix. + if (isset($custom_themes_add[$files_to_change[1]])) + $files_to_change += $custom_themes_add[$files_to_change[1]]; + + // Now, loop through all the files we're changing, and, well, change them ;) + foreach ($files_to_change as $theme => $working_file) + { + if ($working_file[0] != '/' && $working_file[1] != ':') + { + trigger_error('parseModification(): The filename \'' . $working_file . '\' is not a full path!', E_USER_WARNING); + + $working_file = $boarddir . '/' . $working_file; + } + + // Doesn't exist - give an error or what? + if (!file_exists($working_file) && (!$file->exists('@error') || !in_array(trim($file->fetch('@error')), array('ignore', 'skip')))) + { + $actions[] = array( + 'type' => 'missing', + 'filename' => $working_file, + 'debug' => $txt['package_modification_missing'] + ); + + $everything_found = false; + continue; + } + // Skip the file if it doesn't exist. + elseif (!file_exists($working_file) && $file->exists('@error') && trim($file->fetch('@error')) == 'skip') + { + $actions[] = array( + 'type' => 'skipping', + 'filename' => $working_file, + ); + continue; + } + // Okay, we're creating this file then...? + elseif (!file_exists($working_file)) + $working_data = ''; + // Phew, it exists! Load 'er up! + else + $working_data = str_replace("\r", '', package_get_contents($working_file)); + + $actions[] = array( + 'type' => 'opened', + 'filename' => $working_file + ); + + $operations = $file->exists('operation') ? $file->set('operation') : array(); + foreach ($operations as $operation) + { + // Convert operation to an array. + $actual_operation = array( + 'searches' => array(), + 'error' => $operation->exists('@error') && in_array(trim($operation->fetch('@error')), array('ignore', 'fatal', 'required')) ? trim($operation->fetch('@error')) : 'fatal', + ); + + // The 'add' parameter is used for all searches in this operation. + $add = $operation->exists('add') ? $operation->fetch('add') : ''; + + // Grab all search items of this operation (in most cases just 1). + $searches = $operation->set('search'); + foreach ($searches as $i => $search) + $actual_operation['searches'][] = array( + 'position' => $search->exists('@position') && in_array(trim($search->fetch('@position')), array('before', 'after', 'replace', 'end')) ? trim($search->fetch('@position')) : 'replace', + 'is_reg_exp' => $search->exists('@regexp') && trim($search->fetch('@regexp')) === 'true', + 'loose_whitespace' => $search->exists('@whitespace') && trim($search->fetch('@whitespace')) === 'loose', + 'search' => $search->fetch('.'), + 'add' => $add, + 'preg_search' => '', + 'preg_replace' => '', + ); + + // At least one search should be defined. + if (empty($actual_operation['searches'])) + { + $actions[] = array( + 'type' => 'failure', + 'filename' => $working_file, + 'search' => $search['search'], + 'is_custom' => $theme > 1 ? $theme : 0, + ); + + // Skip to the next operation. + continue; + } + + // Reverse the operations in case of undoing stuff. + if ($undo) + { + foreach ($actual_operation['searches'] as $i => $search) + { + + // Reverse modification of regular expressions are not allowed. + if ($search['is_reg_exp']) + { + if ($actual_operation['error'] === 'fatal') + $actions[] = array( + 'type' => 'failure', + 'filename' => $working_file, + 'search' => $search['search'], + 'is_custom' => $theme > 1 ? $theme : 0, + ); + + // Continue to the next operation. + continue 2; + } + + // The replacement is now the search subject... + if ($search['position'] === 'replace' || $search['position'] === 'end') + $actual_operation['searches'][$i]['search'] = $search['add']; + else + { + // Reversing a before/after modification becomes a replacement. + $actual_operation['searches'][$i]['position'] = 'replace'; + + if ($search['position'] === 'before') + $actual_operation['searches'][$i]['search'] .= $search['add']; + elseif ($search['position'] === 'after') + $actual_operation['searches'][$i]['search'] = $search['add'] . $search['search']; + } + + // ...and the search subject is now the replacement. + $actual_operation['searches'][$i]['add'] = $search['search']; + } + } + + // Sort the search list so the replaces come before the add before/after's. + if (count($actual_operation['searches']) !== 1) + { + $replacements = array(); + + foreach ($actual_operation['searches'] as $i => $search) + { + if ($search['position'] === 'replace') + { + $replacements[] = $search; + unset($actual_operation['searches'][$i]); + } + } + $actual_operation['searches'] = array_merge($replacements, $actual_operation['searches']); + } + + // Create regular expression replacements from each search. + foreach ($actual_operation['searches'] as $i => $search) + { + // Not much needed if the search subject is already a regexp. + if ($search['is_reg_exp']) + $actual_operation['searches'][$i]['preg_search'] = $search['search']; + else + { + // Make the search subject fit into a regular expression. + $actual_operation['searches'][$i]['preg_search'] = preg_quote($search['search'], '~'); + + // Using 'loose', a random amount of tabs and spaces may be used. + if ($search['loose_whitespace']) + $actual_operation['searches'][$i]['preg_search'] = preg_replace('~[ \t]+~', '[ \t]+', $actual_operation['searches'][$i]['preg_search']); + } + + // Shuzzup. This is done so we can safely use a regular expression. ($0 is bad!!) + $actual_operation['searches'][$i]['preg_replace'] = strtr($search['add'], array('$' => '[$PACK' . 'AGE1$]', '\\' => '[$PACK' . 'AGE2$]')); + + // Before, so the replacement comes after the search subject :P + if ($search['position'] === 'before') + { + $actual_operation['searches'][$i]['preg_search'] = '(' . $actual_operation['searches'][$i]['preg_search'] . ')'; + $actual_operation['searches'][$i]['preg_replace'] = '$1' . $actual_operation['searches'][$i]['preg_replace']; + } + + // After, after what? + elseif ($search['position'] === 'after') + { + $actual_operation['searches'][$i]['preg_search'] = '(' . $actual_operation['searches'][$i]['preg_search'] . ')'; + $actual_operation['searches'][$i]['preg_replace'] .= '$1'; + } + + // Position the replacement at the end of the file (or just before the closing PHP tags). + elseif ($search['position'] === 'end') + { + if ($undo) + { + $actual_operation['searches'][$i]['preg_replace'] = ''; + } + else + { + $actual_operation['searches'][$i]['preg_search'] = '(\\n\\?\\>)?$'; + $actual_operation['searches'][$i]['preg_replace'] .= '$1'; + } + } + + // Testing 1, 2, 3... + $failed = preg_match('~' . $actual_operation['searches'][$i]['preg_search'] . '~s', $working_data) === 0; + + // Nope, search pattern not found. + if ($failed && $actual_operation['error'] === 'fatal') + { + $actions[] = array( + 'type' => 'failure', + 'filename' => $working_file, + 'search' => $actual_operation['searches'][$i]['preg_search'], + 'search_original' => $actual_operation['searches'][$i]['search'], + 'replace_original' => $actual_operation['searches'][$i]['add'], + 'position' => $search['position'], + 'is_custom' => $theme > 1 ? $theme : 0, + 'failed' => $failed, + ); + + $everything_found = false; + continue; + } + + // Found, but in this case, that means failure! + elseif (!$failed && $actual_operation['error'] === 'required') + { + $actions[] = array( + 'type' => 'failure', + 'filename' => $working_file, + 'search' => $actual_operation['searches'][$i]['preg_search'], + 'search_original' => $actual_operation['searches'][$i]['search'], + 'replace_original' => $actual_operation['searches'][$i]['add'], + 'position' => $search['position'], + 'is_custom' => $theme > 1 ? $theme : 0, + 'failed' => $failed, + ); + + $everything_found = false; + continue; + } + + // Replace it into nothing? That's not an option...unless it's an undoing end. + if ($search['add'] === '' && ($search['position'] !== 'end' || !$undo)) + continue; + + // Finally, we're doing some replacements. + $working_data = preg_replace('~' . $actual_operation['searches'][$i]['preg_search'] . '~s', $actual_operation['searches'][$i]['preg_replace'], $working_data, 1); + + $actions[] = array( + 'type' => 'replace', + 'filename' => $working_file, + 'search' => $actual_operation['searches'][$i]['preg_search'], + 'replace' => $actual_operation['searches'][$i]['preg_replace'], + 'search_original' => $actual_operation['searches'][$i]['search'], + 'replace_original' => $actual_operation['searches'][$i]['add'], + 'position' => $search['position'], + 'failed' => $failed, + 'ignore_failure' => $failed && $actual_operation['error'] === 'ignore', + 'is_custom' => $theme > 1 ? $theme : 0, + ); + } + } + + // Fix any little helper symbols ;). + $working_data = strtr($working_data, array('[$PACK' . 'AGE1$]' => '$', '[$PACK' . 'AGE2$]' => '\\')); + + package_chmod($working_file); + + if ((file_exists($working_file) && !is_writable($working_file)) || (!file_exists($working_file) && !is_writable(dirname($working_file)))) + $actions[] = array( + 'type' => 'chmod', + 'filename' => $working_file + ); + + if (basename($working_file) == 'Settings_bak.php') + continue; + + if (!$testing && !empty($modSettings['package_make_backups']) && file_exists($working_file)) + { + // No, no, not Settings.php! + if (basename($working_file) == 'Settings.php') + @copy($working_file, dirname($working_file) . '/Settings_bak.php'); + else + @copy($working_file, $working_file . '~'); + } + + // Always call this, even if in testing, because it won't really be written in testing mode. + package_put_contents($working_file, $working_data, $testing); + + $actions[] = array( + 'type' => 'saved', + 'filename' => $working_file, + 'is_custom' => $theme > 1 ? $theme : 0, + ); + } + } + + $actions[] = array( + 'type' => 'result', + 'status' => $everything_found + ); + + return $actions; +} + +// Parses a BoardMod format mod file... +function parseBoardMod($file, $testing = true, $undo = false, $theme_paths = array()) +{ + global $boarddir, $sourcedir, $settings, $txt, $modSettings; + + @set_time_limit(600); + $file = strtr($file, array("\r" => '')); + + $working_file = null; + $working_search = null; + $working_data = ''; + $replace_with = null; + + $actions = array(); + $everything_found = true; + + // This holds all the template changes in the standard mod file. + $template_changes = array(); + // This is just the temporary file. + $temp_file = $file; + // This holds the actual changes on a step counter basis. + $temp_changes = array(); + $counter = 0; + $step_counter = 0; + + // Before we do *anything*, let's build a list of what we're editing, as it's going to be used for other theme edits. + while (preg_match('~<(edit file|file|search|search for|add|add after|replace|add before|add above|above|before)>\n(.*?)\n~is', $temp_file, $code_match) != 0) + { + $counter++; + + // Get rid of the old stuff. + $temp_file = substr_replace($temp_file, '', strpos($temp_file, $code_match[0]), strlen($code_match[0])); + + // No interest to us? + if ($code_match[1] != 'edit file' && $code_match[1] != 'file') + { + // It's a step, let's add that to the current steps. + if (isset($temp_changes[$step_counter])) + $temp_changes[$step_counter]['changes'][] = $code_match[0]; + continue; + } + + // We've found a new edit - let's make ourself heard, kind of. + $step_counter = $counter; + $temp_changes[$step_counter] = array( + 'title' => $code_match[0], + 'changes' => array(), + ); + + $filename = parse_path($code_match[2]); + + // Now, is this a template file, and if so, which? + foreach ($theme_paths as $id => $theme) + { + // If this filename is relative, if so take a guess at what it should be. + if (strpos($filename, 'Themes') === 0) + $filename = $boarddir . '/' . $filename; + + if (strpos($filename, $theme['theme_dir']) === 0) + $template_changes[$id][$counter] = substr($filename, strlen($theme['theme_dir']) + 1); + } + } + + // Anything above $counter must be for custom themes. + $custom_template_begin = $counter; + // Reference for what theme ID this action belongs to. + $theme_id_ref = array(); + + // Now we know what templates we need to touch, cycle through each theme and work out what we need to edit. + if (!empty($template_changes[1])) + { + foreach ($theme_paths as $id => $theme) + { + // Don't do default, it means nothing to me. + if ($id == 1) + continue; + + // Now, for each file do we need to edit it? + foreach ($template_changes[1] as $pos => $template_file) + { + // It does? Add it to the list darlin'. + if (file_exists($theme['theme_dir'] . '/' . $template_file) && (!isset($template_changes[$id][$pos]) || !in_array($template_file, $template_changes[$id][$pos]))) + { + // Actually add it to the mod file too, so we can see that it will work ;) + if (!empty($temp_changes[$pos]['changes'])) + { + $file .= "\n\n" . '' . "\n" . $theme['theme_dir'] . '/' . $template_file . "\n" . '' . "\n\n" . implode("\n\n", $temp_changes[$pos]['changes']); + $theme_id_ref[$counter] = $id; + $counter += 1 + count($temp_changes[$pos]['changes']); + } + } + } + } + } + + $counter = 0; + $is_custom = 0; + while (preg_match('~<(edit file|file|search|search for|add|add after|replace|add before|add above|above|before)>\n(.*?)\n~is', $file, $code_match) != 0) + { + // This is for working out what we should be editing. + $counter++; + + // Edit a specific file. + if ($code_match[1] == 'file' || $code_match[1] == 'edit file') + { + // Backup the old file. + if ($working_file !== null) + { + package_chmod($working_file); + + // Don't even dare. + if (basename($working_file) == 'Settings_bak.php') + continue; + + if (!is_writable($working_file)) + $actions[] = array( + 'type' => 'chmod', + 'filename' => $working_file + ); + + if (!$testing && !empty($modSettings['package_make_backups']) && file_exists($working_file)) + { + if (basename($working_file) == 'Settings.php') + @copy($working_file, dirname($working_file) . '/Settings_bak.php'); + else + @copy($working_file, $working_file . '~'); + } + + package_put_contents($working_file, $working_data, $testing); + } + + if ($working_file !== null) + $actions[] = array( + 'type' => 'saved', + 'filename' => $working_file, + 'is_custom' => $is_custom, + ); + + // Is this "now working on" file a theme specific one? + $is_custom = isset($theme_id_ref[$counter - 1]) ? $theme_id_ref[$counter - 1] : 0; + + // Make sure the file exists! + $working_file = parse_path($code_match[2]); + + if ($working_file[0] != '/' && $working_file[1] != ':') + { + trigger_error('parseBoardMod(): The filename \'' . $working_file . '\' is not a full path!', E_USER_WARNING); + + $working_file = $boarddir . '/' . $working_file; + } + + if (!file_exists($working_file)) + { + $places_to_check = array($boarddir, $sourcedir, $settings['default_theme_dir'], $settings['default_theme_dir'] . '/languages'); + + foreach ($places_to_check as $place) + if (file_exists($place . '/' . $working_file)) + { + $working_file = $place . '/' . $working_file; + break; + } + } + + if (file_exists($working_file)) + { + // Load the new file. + $working_data = str_replace("\r", '', package_get_contents($working_file)); + + $actions[] = array( + 'type' => 'opened', + 'filename' => $working_file + ); + } + else + { + $actions[] = array( + 'type' => 'missing', + 'filename' => $working_file + ); + + $working_file = null; + $everything_found = false; + } + + // Can't be searching for something... + $working_search = null; + } + // Search for a specific string. + elseif (($code_match[1] == 'search' || $code_match[1] == 'search for') && $working_file !== null) + { + if ($working_search !== null) + { + $actions[] = array( + 'type' => 'error', + 'filename' => $working_file + ); + + $everything_found = false; + } + + $working_search = $code_match[2]; + } + // Must've already loaded a search string. + elseif ($working_search !== null) + { + // This is the base string.... + $replace_with = $code_match[2]; + + // Add this afterward... + if ($code_match[1] == 'add' || $code_match[1] == 'add after') + $replace_with = $working_search . "\n" . $replace_with; + // Add this beforehand. + elseif ($code_match[1] == 'before' || $code_match[1] == 'add before' || $code_match[1] == 'above' || $code_match[1] == 'add above') + $replace_with .= "\n" . $working_search; + // Otherwise.. replace with $replace_with ;). + } + + // If we have a search string, replace string, and open file.. + if ($working_search !== null && $replace_with !== null && $working_file !== null) + { + // Make sure it's somewhere in the string. + if ($undo) + { + $temp = $replace_with; + $replace_with = $working_search; + $working_search = $temp; + } + + if (strpos($working_data, $working_search) !== false) + { + $working_data = str_replace($working_search, $replace_with, $working_data); + + $actions[] = array( + 'type' => 'replace', + 'filename' => $working_file, + 'search' => $working_search, + 'replace' => $replace_with, + 'search_original' => $working_search, + 'replace_original' => $replace_with, + 'position' => $code_match[1] == 'replace' ? 'replace' : ($code_match[1] == 'add' || $code_match[1] == 'add after' ? 'before' : 'after'), + 'is_custom' => $is_custom, + 'failed' => false, + ); + } + // It wasn't found! + else + { + $actions[] = array( + 'type' => 'failure', + 'filename' => $working_file, + 'search' => $working_search, + 'is_custom' => $is_custom, + 'search_original' => $working_search, + 'replace_original' => $replace_with, + 'position' => $code_match[1] == 'replace' ? 'replace' : ($code_match[1] == 'add' || $code_match[1] == 'add after' ? 'before' : 'after'), + 'is_custom' => $is_custom, + 'failed' => true, + ); + + $everything_found = false; + } + + // These don't hold any meaning now. + $working_search = null; + $replace_with = null; + } + + // Get rid of the old tag. + $file = substr_replace($file, '', strpos($file, $code_match[0]), strlen($code_match[0])); + } + + // Backup the old file. + if ($working_file !== null) + { + package_chmod($working_file); + + if (!is_writable($working_file)) + $actions[] = array( + 'type' => 'chmod', + 'filename' => $working_file + ); + + if (!$testing && !empty($modSettings['package_make_backups']) && file_exists($working_file)) + { + if (basename($working_file) == 'Settings.php') + @copy($working_file, dirname($working_file) . '/Settings_bak.php'); + else + @copy($working_file, $working_file . '~'); + } + + package_put_contents($working_file, $working_data, $testing); + } + + if ($working_file !== null) + $actions[] = array( + 'type' => 'saved', + 'filename' => $working_file, + 'is_custom' => $is_custom, + ); + + $actions[] = array( + 'type' => 'result', + 'status' => $everything_found + ); + + return $actions; +} + +function package_get_contents($filename) +{ + global $package_cache, $modSettings; + + if (!isset($package_cache)) + { + // Windows doesn't seem to care about the memory_limit. + if (!empty($modSettings['package_disable_cache']) || ini_set('memory_limit', '128M') !== false || strpos(strtolower(PHP_OS), 'win') !== false) + $package_cache = array(); + else + $package_cache = false; + } + + if (strpos($filename, 'Packages/') !== false || $package_cache === false || !isset($package_cache[$filename])) + return file_get_contents($filename); + else + return $package_cache[$filename]; +} + +function package_put_contents($filename, $data, $testing = false) +{ + global $package_ftp, $package_cache, $modSettings; + static $text_filetypes = array('php', 'txt', '.js', 'css', 'vbs', 'tml', 'htm'); + + if (!isset($package_cache)) + { + // Try to increase the memory limit - we don't want to run out of ram! + if (!empty($modSettings['package_disable_cache']) || ini_set('memory_limit', '128M') !== false || strpos(strtolower(PHP_OS), 'win') !== false) + $package_cache = array(); + else + $package_cache = false; + } + + if (isset($package_ftp)) + $ftp_file = strtr($filename, array($_SESSION['pack_ftp']['root'] => '')); + + if (!file_exists($filename) && isset($package_ftp)) + $package_ftp->create_file($ftp_file); + elseif (!file_exists($filename)) + @touch($filename); + + package_chmod($filename); + + if (!$testing && (strpos($filename, 'Packages/') !== false || $package_cache === false)) + { + $fp = @fopen($filename, in_array(substr($filename, -3), $text_filetypes) ? 'w' : 'wb'); + + // We should show an error message or attempt a rollback, no? + if (!$fp) + return false; + + fwrite($fp, $data); + fclose($fp); + } + elseif (strpos($filename, 'Packages/') !== false || $package_cache === false) + return strlen($data); + else + { + $package_cache[$filename] = $data; + + // Permission denied, eh? + $fp = @fopen($filename, 'r+'); + if (!$fp) + return false; + fclose($fp); + } + + return strlen($data); +} + +function package_flush_cache($trash = false) +{ + global $package_ftp, $package_cache; + static $text_filetypes = array('php', 'txt', '.js', 'css', 'vbs', 'tml', 'htm'); + + if (empty($package_cache)) + return; + + // First, let's check permissions! + foreach ($package_cache as $filename => $data) + { + if (isset($package_ftp)) + $ftp_file = strtr($filename, array($_SESSION['pack_ftp']['root'] => '')); + + if (!file_exists($filename) && isset($package_ftp)) + $package_ftp->create_file($ftp_file); + elseif (!file_exists($filename)) + @touch($filename); + + package_chmod($filename); + + $fp = fopen($filename, 'r+'); + if (!$fp && !$trash) + { + // We should have package_chmod()'d them before, no?! + trigger_error('package_flush_cache(): some files are still not writable', E_USER_WARNING); + return; + } + fclose($fp); + } + + if ($trash) + { + $package_cache = array(); + return; + } + + foreach ($package_cache as $filename => $data) + { + $fp = fopen($filename, in_array(substr($filename, -3), $text_filetypes) ? 'w' : 'wb'); + fwrite($fp, $data); + fclose($fp); + } + + $package_cache = array(); +} + +// Try to make a file writable. Return true if it worked, false if it didn't. +function package_chmod($filename, $perm_state = 'writable', $track_change = false) +{ + global $package_ftp; + + if (file_exists($filename) && is_writable($filename) && $perm_state == 'writable') + return true; + + // Start off checking without FTP. + if (!isset($package_ftp) || $package_ftp === false) + { + for ($i = 0; $i < 2; $i++) + { + $chmod_file = $filename; + + // Start off with a less agressive test. + if ($i == 0) + { + // If this file doesn't exist, then we actually want to look at whatever parent directory does. + $subTraverseLimit = 2; + while (!file_exists($chmod_file) && $subTraverseLimit) + { + $chmod_file = dirname($chmod_file); + $subTraverseLimit--; + } + + // Keep track of the writable status here. + $file_permissions = @fileperms($chmod_file); + } + else + { + // This looks odd, but it's an attempt to work around PHP suExec. + if (!file_exists($chmod_file) && $perm_state == 'writable') + { + $file_permissions = @fileperms(dirname($chmod_file)); + + mktree(dirname($chmod_file), 0755); + @touch($chmod_file); + @chmod($chmod_file, 0755); + } + else + $file_permissions = @fileperms($chmod_file); + } + + // This looks odd, but it's another attempt to work around PHP suExec. + if ($perm_state != 'writable') + @chmod($chmod_file, $perm_state == 'execute' ? 0755 : 0644); + else + { + if (!@is_writable($chmod_file)) + @chmod($chmod_file, 0755); + if (!@is_writable($chmod_file)) + @chmod($chmod_file, 0777); + if (!@is_writable(dirname($chmod_file))) + @chmod($chmod_file, 0755); + if (!@is_writable(dirname($chmod_file))) + @chmod($chmod_file, 0777); + } + + // The ultimate writable test. + if ($perm_state == 'writable') + { + $fp = is_dir($chmod_file) ? @opendir($chmod_file) : @fopen($chmod_file, 'rb'); + if (@is_writable($chmod_file) && $fp) + { + if (!is_dir($chmod_file)) + fclose($fp); + else + closedir($fp); + + // It worked! + if ($track_change) + $_SESSION['pack_ftp']['original_perms'][$chmod_file] = $file_permissions; + + return true; + } + } + elseif ($perm_state != 'writable' && isset($_SESSION['pack_ftp']['original_perms'][$chmod_file])) + unset($_SESSION['pack_ftp']['original_perms'][$chmod_file]); + } + + // If we're here we're a failure. + return false; + } + // Otherwise we do have FTP? + elseif ($package_ftp !== false && !empty($_SESSION['pack_ftp'])) + { + $ftp_file = strtr($filename, array($_SESSION['pack_ftp']['root'] => '')); + + // This looks odd, but it's an attempt to work around PHP suExec. + if (!file_exists($filename) && $perm_state == 'writable') + { + $file_permissions = @fileperms(dirname($filename)); + + mktree(dirname($filename), 0755); + $package_ftp->create_file($ftp_file); + $package_ftp->chmod($ftp_file, 0755); + } + else + $file_permissions = @fileperms($filename); + + if ($perm_state != 'writable') + { + $package_ftp->chmod($ftp_file, $perm_state == 'execute' ? 0755 : 0644); + } + else + { + if (!@is_writable($filename)) + $package_ftp->chmod($ftp_file, 0777); + if (!@is_writable(dirname($filename))) + $package_ftp->chmod(dirname($ftp_file), 0777); + } + + if (@is_writable($filename)) + { + if ($track_change) + $_SESSION['pack_ftp']['original_perms'][$filename] = $file_permissions; + + return true; + } + elseif ($perm_state != 'writable' && isset($_SESSION['pack_ftp']['original_perms'][$filename])) + unset($_SESSION['pack_ftp']['original_perms'][$filename]); + } + + // Oh dear, we failed if we get here. + return false; +} + +function package_crypt($pass) +{ + $n = strlen($pass); + + $salt = session_id(); + while (strlen($salt) < $n) + $salt .= session_id(); + + for ($i = 0; $i < $n; $i++) + $pass{$i} = chr(ord($pass{$i}) ^ (ord($salt{$i}) - 32)); + + return $pass; +} + +function package_create_backup($id = 'backup') +{ + global $sourcedir, $boarddir, $smcFunc; + + $files = array(); + + $base_files = array('index.php', 'SSI.php', 'agreement.txt', 'ssi_examples.php', 'ssi_examples.shtml'); + foreach ($base_files as $file) + { + if (file_exists($boarddir . '/' . $file)) + $files[realpath($boarddir . '/' . $file)] = array( + empty($_REQUEST['use_full_paths']) ? $file : $boarddir . '/' . $file, + stat($boarddir . '/' . $file) + ); + } + + $dirs = array( + $sourcedir => empty($_REQUEST['use_full_paths']) ? 'Sources/' : strtr($sourcedir . '/', '\\', '/') + ); + + $request = $smcFunc['db_query']('', ' + SELECT value + FROM {db_prefix}themes + WHERE id_member = {int:no_member} + AND variable = {string:theme_dir}', + array( + 'no_member' => 0, + 'theme_dir' => 'theme_dir', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $dirs[$row['value']] = empty($_REQUEST['use_full_paths']) ? 'Themes/' . basename($row['value']) . '/' : strtr($row['value'] . '/', '\\', '/'); + $smcFunc['db_free_result']($request); + + while (!empty($dirs)) + { + list ($dir, $dest) = each($dirs); + unset($dirs[$dir]); + + $listing = @dir($dir); + if (!$listing) + continue; + while ($entry = $listing->read()) + { + if (preg_match('~^(\.{1,2}|CVS|backup.*|help|images|.*\~)$~', $entry) != 0) + continue; + + $filepath = realpath($dir . '/' . $entry); + if (isset($files[$filepath])) + continue; + + $stat = stat($dir . '/' . $entry); + if ($stat['mode'] & 040000) + { + $files[$filepath] = array($dest . $entry . '/', $stat); + $dirs[$dir . '/' . $entry] = $dest . $entry . '/'; + } + else + $files[$filepath] = array($dest . $entry, $stat); + } + $listing->close(); + } + + if (!file_exists($boarddir . '/Packages/backups')) + mktree($boarddir . '/Packages/backups', 0777); + if (!is_writable($boarddir . '/Packages/backups')) + package_chmod($boarddir . '/Packages/backups'); + $output_file = $boarddir . '/Packages/backups/' . strftime('%Y-%m-%d_') . preg_replace('~[$\\\\/:<>|?*"\']~', '', $id); + $output_ext = '.tar' . (function_exists('gzopen') ? '.gz' : ''); + + if (file_exists($output_file . $output_ext)) + { + $i = 2; + while (file_exists($output_file . '_' . $i . $output_ext)) + $i++; + $output_file = $output_file . '_' . $i . $output_ext; + } + else + $output_file .= $output_ext; + + @set_time_limit(300); + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + if (function_exists('gzopen')) + { + $fwrite = 'gzwrite'; + $fclose = 'gzclose'; + $output = gzopen($output_file, 'wb'); + } + else + { + $fwrite = 'fwrite'; + $fclose = 'fclose'; + $output = fopen($output_file, 'wb'); + } + + foreach ($files as $real_file => $file) + { + if (!file_exists($real_file)) + continue; + + $stat = $file[1]; + if (substr($file[0], -1) == '/') + $stat['size'] = 0; + + $current = pack('a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12', $file[0], decoct($stat['mode']), sprintf('%06d', decoct($stat['uid'])), sprintf('%06d', decoct($stat['gid'])), decoct($stat['size']), decoct($stat['mtime']), '', 0, '', '', '', '', '', '', '', '', ''); + + $checksum = 256; + for ($i = 0; $i < 512; $i++) + $checksum += ord($current{$i}); + + $fwrite($output, substr($current, 0, 148) . pack('a8', decoct($checksum)) . substr($current, 156, 511)); + + if ($stat['size'] == 0) + continue; + + $fp = fopen($real_file, 'rb'); + while (!feof($fp)) + $fwrite($output, fread($fp, 16384)); + fclose($fp); + + $fwrite($output, pack('a' . (512 - $stat['size'] % 512), '')); + } + + $fwrite($output, pack('a1024', '')); + $fclose($output); +} + +// Get the contents of a URL, irrespective of allow_url_fopen. +function fetch_web_data($url, $post_data = '', $keep_alive = false, $redirection_level = 0) +{ + global $webmaster_email; + static $keep_alive_dom = null, $keep_alive_fp = null; + + preg_match('~^(http|ftp)(s)?://([^/:]+)(:(\d+))?(.+)$~', $url, $match); + + // An FTP url. We should try connecting and RETRieving it... + if (empty($match[1])) + return false; + elseif ($match[1] == 'ftp') + { + // Include the file containing the ftp_connection class. + loadClassFile('Class-Package.php'); + + // Establish a connection and attempt to enable passive mode. + $ftp = new ftp_connection(($match[2] ? 'ssl://' : '') . $match[3], empty($match[5]) ? 21 : $match[5], 'anonymous', $webmaster_email); + if ($ftp->error !== false || !$ftp->passive()) + return false; + + // I want that one *points*! + fwrite($ftp->connection, 'RETR ' . $match[6] . "\r\n"); + + // Since passive mode worked (or we would have returned already!) open the connection. + $fp = @fsockopen($ftp->pasv['ip'], $ftp->pasv['port'], $err, $err, 5); + if (!$fp) + return false; + + // The server should now say something in acknowledgement. + $ftp->check_response(150); + + $data = ''; + while (!feof($fp)) + $data .= fread($fp, 4096); + fclose($fp); + + // All done, right? Good. + $ftp->check_response(226); + $ftp->close(); + } + // This is more likely; a standard HTTP URL. + elseif (isset($match[1]) && $match[1] == 'http') + { + if ($keep_alive && $match[3] == $keep_alive_dom) + $fp = $keep_alive_fp; + if (empty($fp)) + { + // Open the socket on the port we want... + $fp = @fsockopen(($match[2] ? 'ssl://' : '') . $match[3], empty($match[5]) ? ($match[2] ? 443 : 80) : $match[5], $err, $err, 5); + if (!$fp) + return false; + } + + if ($keep_alive) + { + $keep_alive_dom = $match[3]; + $keep_alive_fp = $fp; + } + + // I want this, from there, and I'm not going to be bothering you for more (probably.) + if (empty($post_data)) + { + fwrite($fp, 'GET ' . $match[6] . ' HTTP/1.0' . "\r\n"); + fwrite($fp, 'Host: ' . $match[3] . (empty($match[5]) ? ($match[2] ? ':443' : '') : ':' . $match[5]) . "\r\n"); + fwrite($fp, 'User-Agent: PHP/SMF' . "\r\n"); + if ($keep_alive) + fwrite($fp, 'Connection: Keep-Alive' . "\r\n\r\n"); + else + fwrite($fp, 'Connection: close' . "\r\n\r\n"); + } + else + { + fwrite($fp, 'POST ' . $match[6] . ' HTTP/1.0' . "\r\n"); + fwrite($fp, 'Host: ' . $match[3] . (empty($match[5]) ? ($match[2] ? ':443' : '') : ':' . $match[5]) . "\r\n"); + fwrite($fp, 'User-Agent: PHP/SMF' . "\r\n"); + if ($keep_alive) + fwrite($fp, 'Connection: Keep-Alive' . "\r\n"); + else + fwrite($fp, 'Connection: close' . "\r\n"); + fwrite($fp, 'Content-Type: application/x-www-form-urlencoded' . "\r\n"); + fwrite($fp, 'Content-Length: ' . strlen($post_data) . "\r\n\r\n"); + fwrite($fp, $post_data); + } + + $response = fgets($fp, 768); + + // Redirect in case this location is permanently or temporarily moved. + if ($redirection_level < 3 && preg_match('~^HTTP/\S+\s+30[127]~i', $response) === 1) + { + $header = ''; + $location = ''; + while (!feof($fp) && trim($header = fgets($fp, 4096)) != '') + if (strpos($header, 'Location:') !== false) + $location = trim(substr($header, strpos($header, ':') + 1)); + + if (empty($location)) + return false; + else + { + if (!$keep_alive) + fclose($fp); + return fetch_web_data($location, $post_data, $keep_alive, $redirection_level + 1); + } + } + + // Make sure we get a 200 OK. + elseif (preg_match('~^HTTP/\S+\s+20[01]~i', $response) === 0) + return false; + + // Skip the headers... + while (!feof($fp) && trim($header = fgets($fp, 4096)) != '') + { + if (preg_match('~content-length:\s*(\d+)~i', $header, $match) != 0) + $content_length = $match[1]; + elseif (preg_match('~connection:\s*close~i', $header) != 0) + { + $keep_alive_dom = null; + $keep_alive = false; + } + + continue; + } + + $data = ''; + if (isset($content_length)) + { + while (!feof($fp) && strlen($data) < $content_length) + $data .= fread($fp, $content_length - strlen($data)); + } + else + { + while (!feof($fp)) + $data .= fread($fp, 4096); + } + + if (!$keep_alive) + fclose($fp); + } + else + { + // Umm, this shouldn't happen? + trigger_error('fetch_web_data(): Bad URL', E_USER_NOTICE); + $data = false; + } + + return $data; +} + +// crc32 doesn't work as expected on 64-bit functions - make our own. +// http://www.php.net/crc32#79567 +if (!function_exists('smf_crc32')) +{ + function smf_crc32($number) + { + $crc = crc32($number); + + if ($crc & 0x80000000) + { + $crc ^= 0xffffffff; + $crc += 1; + $crc = -$crc; + } + + return $crc; + } +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Post.php b/Sources/Subs-Post.php new file mode 100644 index 0000000..706e3d6 --- /dev/null +++ b/Sources/Subs-Post.php @@ -0,0 +1,3272 @@ + "[", "]" => "]", ":" => ":", "@" => "@")) . "[/nobbc]";'), $message); + + // Remove \r's... they're evil! + $message = strtr($message, array("\r" => '')); + + // You won't believe this - but too many periods upsets apache it seems! + $message = preg_replace('~\.{100,}~', '...', $message); + + // Trim off trailing quotes - these often happen by accident. + while (substr($message, -7) == '[quote]') + $message = substr($message, 0, -7); + while (substr($message, 0, 8) == '[/quote]') + $message = substr($message, 8); + + // Find all code blocks, work out whether we'd be parsing them, then ensure they are all closed. + $in_tag = false; + $had_tag = false; + $codeopen = 0; + if (preg_match_all('~(\[(/)*code(?:=[^\]]+)?\])~is', $message, $matches)) + foreach ($matches[0] as $index => $dummy) + { + // Closing? + if (!empty($matches[2][$index])) + { + // If it's closing and we're not in a tag we need to open it... + if (!$in_tag) + $codeopen = true; + // Either way we ain't in one any more. + $in_tag = false; + } + // Opening tag... + else + { + $had_tag = true; + // If we're in a tag don't do nought! + if (!$in_tag) + $in_tag = true; + } + } + + // If we have an open tag, close it. + if ($in_tag) + $message .= '[/code]'; + // Open any ones that need to be open, only if we've never had a tag. + if ($codeopen && !$had_tag) + $message = '[code]' . $message; + + // Now that we've fixed all the code tags, let's fix the img and url tags... + $parts = preg_split('~(\[/code\]|\[code(?:=[^\]]+)?\])~i', $message, -1, PREG_SPLIT_DELIM_CAPTURE); + + // The regular expression non breaking space has many versions. + $non_breaking_space = $context['utf8'] ? ($context['server']['complex_preg_chars'] ? '\x{A0}' : "\xC2\xA0") : '\xA0'; + + // Only mess with stuff outside [code] tags. + for ($i = 0, $n = count($parts); $i < $n; $i++) + { + // It goes 0 = outside, 1 = begin tag, 2 = inside, 3 = close tag, repeat. + if ($i % 4 == 0) + { + fixTags($parts[$i]); + + // Replace /me.+?\n with [me=name]dsf[/me]\n. + if (strpos($user_info['name'], '[') !== false || strpos($user_info['name'], ']') !== false || strpos($user_info['name'], '\'') !== false || strpos($user_info['name'], '"') !== false) + $parts[$i] = preg_replace('~(\A|\n)/me(?: | )([^\n]*)(?:\z)?~i', '$1[me="' . $user_info['name'] . '"]$2[/me]', $parts[$i]); + else + $parts[$i] = preg_replace('~(\A|\n)/me(?: | )([^\n]*)(?:\z)?~i', '$1[me=' . $user_info['name'] . ']$2[/me]', $parts[$i]); + + if (!$previewing && strpos($parts[$i], '[html]') !== false) + { + if (allowedTo('admin_forum')) + { + static $htmlfunc = null; + if ($htmlfunc === null) + $htmlfunc = create_function('$m', 'return \'[html]\' . strtr(un_htmlspecialchars("$m[1]"), array("\n" => \' \', \' \' => \' \', \'[\' => \'[\', \']\' => \']\')) . \'[/html]\';'); + $parts[$i] = preg_replace_callback('~\[html\](.+?)\[/html\]~is', $htmlfunc, $parts[$i]); + } + + // We should edit them out, or else if an admin edits the message they will get shown... + else + { + while (strpos($parts[$i], '[html]') !== false) + $parts[$i] = preg_replace('~\[[/]?html\]~i', '', $parts[$i]); + } + } + + // Let's look at the time tags... + $parts[$i] = preg_replace_callback('~\[time(?:=(absolute))*\](.+?)\[/time\]~i', create_function('$m', 'global $modSettings, $user_info; return "[time]" . (is_numeric("$m[2]") || @strtotime("$m[2]") == 0 ? "$m[2]" : strtotime("$m[2]") - ("$m[1]" == "absolute" ? 0 : (($modSettings["time_offset"] + $user_info["time_offset"]) * 3600))) . "[/time]";'), $parts[$i]); + + // Change the color specific tags to [color=the color]. + $parts[$i] = preg_replace('~\[(black|blue|green|red|white)\]~', '[color=$1]', $parts[$i]); // First do the opening tags. + $parts[$i] = preg_replace('~\[/(black|blue|green|red|white)\]~', '[/color]', $parts[$i]); // And now do the closing tags + + // Make sure all tags are lowercase. + $parts[$i] = preg_replace_callback('~\[([/]?)(list|li|table|tr|td)((\s[^\]]+)*)\]~i', create_function('$m', ' return "[$m[1]" . strtolower("$m[2]") . "$m[3]]";'), $parts[$i]); + + $list_open = substr_count($parts[$i], '[list]') + substr_count($parts[$i], '[list '); + $list_close = substr_count($parts[$i], '[/list]'); + if ($list_close - $list_open > 0) + $parts[$i] = str_repeat('[list]', $list_close - $list_open) . $parts[$i]; + if ($list_open - $list_close > 0) + $parts[$i] = $parts[$i] . str_repeat('[/list]', $list_open - $list_close); + + $mistake_fixes = array( + // Find [table]s not followed by [tr]. + '~\[table\](?![\s' . $non_breaking_space . ']*\[tr\])~s' . ($context['utf8'] ? 'u' : '') => '[table][tr]', + // Find [tr]s not followed by [td]. + '~\[tr\](?![\s' . $non_breaking_space . ']*\[td\])~s' . ($context['utf8'] ? 'u' : '') => '[tr][td]', + // Find [/td]s not followed by something valid. + '~\[/td\](?![\s' . $non_breaking_space . ']*(?:\[td\]|\[/tr\]|\[/table\]))~s' . ($context['utf8'] ? 'u' : '') => '[/td][/tr]', + // Find [/tr]s not followed by something valid. + '~\[/tr\](?![\s' . $non_breaking_space . ']*(?:\[tr\]|\[/table\]))~s' . ($context['utf8'] ? 'u' : '') => '[/tr][/table]', + // Find [/td]s incorrectly followed by [/table]. + '~\[/td\][\s' . $non_breaking_space . ']*\[/table\]~s' . ($context['utf8'] ? 'u' : '') => '[/td][/tr][/table]', + // Find [table]s, [tr]s, and [/td]s (possibly correctly) followed by [td]. + '~\[(table|tr|/td)\]([\s' . $non_breaking_space . ']*)\[td\]~s' . ($context['utf8'] ? 'u' : '') => '[$1]$2[_td_]', + // Now, any [td]s left should have a [tr] before them. + '~\[td\]~s' => '[tr][td]', + // Look for [tr]s which are correctly placed. + '~\[(table|/tr)\]([\s' . $non_breaking_space . ']*)\[tr\]~s' . ($context['utf8'] ? 'u' : '') => '[$1]$2[_tr_]', + // Any remaining [tr]s should have a [table] before them. + '~\[tr\]~s' => '[table][tr]', + // Look for [/td]s followed by [/tr]. + '~\[/td\]([\s' . $non_breaking_space . ']*)\[/tr\]~s' . ($context['utf8'] ? 'u' : '') => '[/td]$1[_/tr_]', + // Any remaining [/tr]s should have a [/td]. + '~\[/tr\]~s' => '[/td][/tr]', + // Look for properly opened [li]s which aren't closed. + '~\[li\]([^\[\]]+?)\[li\]~s' => '[li]$1[_/li_][_li_]', + '~\[li\]([^\[\]]+?)\[/list\]~s' => '[_li_]$1[_/li_][/list]', + '~\[li\]([^\[\]]+?)$~s' => '[li]$1[/li]', + // Lists - find correctly closed items/lists. + '~\[/li\]([\s' . $non_breaking_space . ']*)\[/list\]~s' . ($context['utf8'] ? 'u' : '') => '[_/li_]$1[/list]', + // Find list items closed and then opened. + '~\[/li\]([\s' . $non_breaking_space . ']*)\[li\]~s' . ($context['utf8'] ? 'u' : '') => '[_/li_]$1[_li_]', + // Now, find any [list]s or [/li]s followed by [li]. + '~\[(list(?: [^\]]*?)?|/li)\]([\s' . $non_breaking_space . ']*)\[li\]~s' . ($context['utf8'] ? 'u' : '') => '[$1]$2[_li_]', + // Allow for sub lists. + '~\[/li\]([\s' . $non_breaking_space . ']*)\[list\]~' . ($context['utf8'] ? 'u' : '') => '[_/li_]$1[list]', + '~\[/list\]([\s' . $non_breaking_space . ']*)\[li\]~' . ($context['utf8'] ? 'u' : '') => '[/list]$1[_li_]', + // Any remaining [li]s weren't inside a [list]. + '~\[li\]~' => '[list][li]', + // Any remaining [/li]s weren't before a [/list]. + '~\[/li\]~' => '[/li][/list]', + // Put the correct ones back how we found them. + '~\[_(li|/li|td|tr|/tr)_\]~' => '[$1]', + // Images with no real url. + '~\[img\]https?://.{0,7}\[/img\]~' => '', + ); + + // Fix up some use of tables without [tr]s, etc. (it has to be done more than once to catch it all.) + for ($j = 0; $j < 3; $j++) + $parts[$i] = preg_replace(array_keys($mistake_fixes), $mistake_fixes, $parts[$i]); + + // Now we're going to do full scale table checking... + $table_check = $parts[$i]; + $table_offset = 0; + $table_array = array(); + $table_order = array( + 'table' => 'td', + 'tr' => 'table', + 'td' => 'tr', + ); + while (preg_match('~\[(/)*(table|tr|td)\]~', $table_check, $matches) != false) + { + // Keep track of where this is. + $offset = strpos($table_check, $matches[0]); + $remove_tag = false; + + // Is it opening? + if ($matches[1] != '/') + { + // If the previous table tag isn't correct simply remove it. + if ((!empty($table_array) && $table_array[0] != $table_order[$matches[2]]) || (empty($table_array) && $matches[2] != 'table')) + $remove_tag = true; + // Record this was the last tag. + else + array_unshift($table_array, $matches[2]); + } + // Otherwise is closed! + else + { + // Only keep the tag if it's closing the right thing. + if (empty($table_array) || ($table_array[0] != $matches[2])) + $remove_tag = true; + else + array_shift($table_array); + } + + // Removing? + if ($remove_tag) + { + $parts[$i] = substr($parts[$i], 0, $table_offset + $offset) . substr($parts[$i], $table_offset + strlen($matches[0]) + $offset); + // We've lost some data. + $table_offset -= strlen($matches[0]); + } + + // Remove everything up to here. + $table_offset += $offset + strlen($matches[0]); + $table_check = substr($table_check, $offset + strlen($matches[0])); + } + + // Close any remaining table tags. + foreach ($table_array as $tag) + $parts[$i] .= '[/' . $tag . ']'; + } + } + + // Put it back together! + if (!$previewing) + $message = strtr(implode('', $parts), array(' ' => '  ', "\n" => '
          ', $context['utf8'] ? "\xC2\xA0" : "\xA0" => ' ')); + else + $message = strtr(implode('', $parts), array(' ' => '  ', $context['utf8'] ? "\xC2\xA0" : "\xA0" => ' ')); + + // Now let's quickly clean up things that will slow our parser (which are common in posted code.) + $message = strtr($message, array('[]' => '[]', '['' => '['')); +} + +// This is very simple, and just removes things done by preparsecode. +function un_preparsecode($message) +{ + global $smcFunc; + + $parts = preg_split('~(\[/code\]|\[code(?:=[^\]]+)?\])~i', $message, -1, PREG_SPLIT_DELIM_CAPTURE); + + // We're going to unparse only the stuff outside [code]... + for ($i = 0, $n = count($parts); $i < $n; $i++) + { + // If $i is a multiple of four (0, 4, 8, ...) then it's not a code section... + if ($i % 4 == 0) + { + $parts[$i] = preg_replace_callback('~\[html\](.+?)\[/html\]~i', create_function('$m', 'return "[html]" . strtr(htmlspecialchars("$m[1]", ENT_QUOTES), array("\\"" => """, "&#13;" => "
          ", "&#32;" => " ", "&#91;" => "[", "&#93;" => "]")) . "[/html]";'), $parts[$i]); + // $parts[$i] = preg_replace('~\[html\](.+?)\[/html\]~ie', '\'[html]\' . strtr(htmlspecialchars(\'$1\', ENT_QUOTES), array(\'\\"\' => \'"\', \'&#13;\' => \'
          \', \'&#32;\' => \' \', \'&#38;\' => \'&\', \'&#91;\' => \'[\', \'&#93;\' => \']\')) . \'[/html]\'', $parts[$i]); + + // Attempt to un-parse the time to something less awful. + $parts[$i] = preg_replace_callback('~\[time\](\d{0,10})\[/time\]~i', create_function('$m', ' return "[time]" . timeformat("$m[1]", false) . "[/time]";'), $parts[$i]); + } + } + + // Change breaks back to \n's and &nsbp; back to spaces. + return preg_replace('~~', "\n", str_replace(' ', ' ', implode('', $parts))); +} + +// Fix any URLs posted - ie. remove 'javascript:'. +function fixTags(&$message) +{ + global $modSettings; + + // WARNING: Editing the below can cause large security holes in your forum. + // Edit only if you are sure you know what you are doing. + + $fixArray = array( + // [img]http://...[/img] or [img width=1]http://...[/img] + array( + 'tag' => 'img', + 'protocols' => array('http', 'https'), + 'embeddedUrl' => false, + 'hasEqualSign' => false, + 'hasExtra' => true, + ), + // [url]http://...[/url] + array( + 'tag' => 'url', + 'protocols' => array('http', 'https'), + 'embeddedUrl' => true, + 'hasEqualSign' => false, + ), + // [url=http://...]name[/url] + array( + 'tag' => 'url', + 'protocols' => array('http', 'https'), + 'embeddedUrl' => true, + 'hasEqualSign' => true, + ), + // [iurl]http://...[/iurl] + array( + 'tag' => 'iurl', + 'protocols' => array('http', 'https'), + 'embeddedUrl' => true, + 'hasEqualSign' => false, + ), + // [iurl=http://...]name[/iurl] + array( + 'tag' => 'iurl', + 'protocols' => array('http', 'https'), + 'embeddedUrl' => true, + 'hasEqualSign' => true, + ), + // [ftp]ftp://...[/ftp] + array( + 'tag' => 'ftp', + 'protocols' => array('ftp', 'ftps'), + 'embeddedUrl' => true, + 'hasEqualSign' => false, + ), + // [ftp=ftp://...]name[/ftp] + array( + 'tag' => 'ftp', + 'protocols' => array('ftp', 'ftps'), + 'embeddedUrl' => true, + 'hasEqualSign' => true, + ), + // [flash]http://...[/flash] + array( + 'tag' => 'flash', + 'protocols' => array('http', 'https'), + 'embeddedUrl' => false, + 'hasEqualSign' => false, + 'hasExtra' => true, + ), + ); + + // Fix each type of tag. + foreach ($fixArray as $param) + fixTag($message, $param['tag'], $param['protocols'], $param['embeddedUrl'], $param['hasEqualSign'], !empty($param['hasExtra'])); + + // Now fix possible security problems with images loading links automatically... + $message = preg_replace_callback('~(\[img.*?\])(.+?)\[/img\]~is', create_function('$m', 'return "$m[1]" . preg_replace("~action(=|%3d)(?!dlattach)~i", "action-", "$m[2]") . "[/img]";'), $message); + + // Limit the size of images posted? + if (!empty($modSettings['max_image_width']) || !empty($modSettings['max_image_height'])) + { + // Find all the img tags - with or without width and height. + preg_match_all('~\[img(\s+width=\d+)?(\s+height=\d+)?(\s+width=\d+)?\](.+?)\[/img\]~is', $message, $matches, PREG_PATTERN_ORDER); + + $replaces = array(); + foreach ($matches[0] as $match => $dummy) + { + // If the width was after the height, handle it. + $matches[1][$match] = !empty($matches[3][$match]) ? $matches[3][$match] : $matches[1][$match]; + + // Now figure out if they had a desired height or width... + $desired_width = !empty($matches[1][$match]) ? (int) substr(trim($matches[1][$match]), 6) : 0; + $desired_height = !empty($matches[2][$match]) ? (int) substr(trim($matches[2][$match]), 7) : 0; + + // One was omitted, or both. We'll have to find its real size... + if (empty($desired_width) || empty($desired_height)) + { + list ($width, $height) = url_image_size(un_htmlspecialchars($matches[4][$match])); + + // They don't have any desired width or height! + if (empty($desired_width) && empty($desired_height)) + { + $desired_width = $width; + $desired_height = $height; + } + // Scale it to the width... + elseif (empty($desired_width) && !empty($height)) + $desired_width = (int) (($desired_height * $width) / $height); + // Scale if to the height. + elseif (!empty($width)) + $desired_height = (int) (($desired_width * $height) / $width); + } + + // If the width and height are fine, just continue along... + if ($desired_width <= $modSettings['max_image_width'] && $desired_height <= $modSettings['max_image_height']) + continue; + + // Too bad, it's too wide. Make it as wide as the maximum. + if ($desired_width > $modSettings['max_image_width'] && !empty($modSettings['max_image_width'])) + { + $desired_height = (int) (($modSettings['max_image_width'] * $desired_height) / $desired_width); + $desired_width = $modSettings['max_image_width']; + } + + // Now check the height, as well. Might have to scale twice, even... + if ($desired_height > $modSettings['max_image_height'] && !empty($modSettings['max_image_height'])) + { + $desired_width = (int) (($modSettings['max_image_height'] * $desired_width) / $desired_height); + $desired_height = $modSettings['max_image_height']; + } + + $replaces[$matches[0][$match]] = '[img' . (!empty($desired_width) ? ' width=' . $desired_width : '') . (!empty($desired_height) ? ' height=' . $desired_height : '') . ']' . $matches[4][$match] . '[/img]'; + } + + // If any img tags were actually changed... + if (!empty($replaces)) + $message = strtr($message, $replaces); + } +} + +// Fix a specific class of tag - ie. url with =. +function fixTag(&$message, $myTag, $protocols, $embeddedUrl = false, $hasEqualSign = false, $hasExtra = false) +{ + global $boardurl, $scripturl; + + if (preg_match('~^([^:]+://[^/]+)~', $boardurl, $match) != 0) + $domain_url = $match[1]; + else + $domain_url = $boardurl . '/'; + + $replaces = array(); + + if ($hasEqualSign) + preg_match_all('~\[(' . $myTag . ')=([^\]]*?)\](?:(.+?)\[/(' . $myTag . ')\])?~is', $message, $matches); + else + preg_match_all('~\[(' . $myTag . ($hasExtra ? '(?:[^\]]*?)' : '') . ')\](.+?)\[/(' . $myTag . ')\]~is', $message, $matches); + + foreach ($matches[0] as $k => $dummy) + { + // Remove all leading and trailing whitespace. + $replace = trim($matches[2][$k]); + $this_tag = $matches[1][$k]; + $this_close = $hasEqualSign ? (empty($matches[4][$k]) ? '' : $matches[4][$k]) : $matches[3][$k]; + + $found = false; + foreach ($protocols as $protocol) + { + $found = strncasecmp($replace, $protocol . '://', strlen($protocol) + 3) === 0; + if ($found) + break; + } + + if (!$found && $protocols[0] == 'http') + { + if (substr($replace, 0, 1) == '/') + $replace = $domain_url . $replace; + elseif (substr($replace, 0, 1) == '?') + $replace = $scripturl . $replace; + elseif (substr($replace, 0, 1) == '#' && $embeddedUrl) + { + $replace = '#' . preg_replace('~[^A-Za-z0-9_\-#]~', '', substr($replace, 1)); + $this_tag = 'iurl'; + $this_close = 'iurl'; + } + else + $replace = $protocols[0] . '://' . $replace; + } + elseif (!$found && $protocols[0] == 'ftp') + $replace = $protocols[0] . '://' . preg_replace('~^(?!ftps?)[^:]+://~', '', $replace); + elseif (!$found) + $replace = $protocols[0] . '://' . $replace; + + if ($hasEqualSign && $embeddedUrl) + $replaces[$matches[0][$k]] = '[' . $this_tag . '=' . $replace . ']' . (empty($matches[4][$k]) ? '' : $matches[3][$k] . '[/' . $this_close . ']'); + elseif ($hasEqualSign) + $replaces['[' . $matches[1][$k] . '=' . $matches[2][$k] . ']'] = '[' . $this_tag . '=' . $replace . ']'; + elseif ($embeddedUrl) + $replaces['[' . $matches[1][$k] . ']' . $matches[2][$k] . '[/' . $matches[3][$k] . ']'] = '[' . $this_tag . '=' . $replace . ']' . $matches[2][$k] . '[/' . $this_close . ']'; + else + $replaces['[' . $matches[1][$k] . ']' . $matches[2][$k] . '[/' . $matches[3][$k] . ']'] = '[' . $this_tag . ']' . $replace . '[/' . $this_close . ']'; + } + + foreach ($replaces as $k => $v) + { + if ($k == $v) + unset($replaces[$k]); + } + + if (!empty($replaces)) + $message = strtr($message, $replaces); +} + +// Send off an email. +function sendmail($to, $subject, $message, $from = null, $message_id = null, $send_html = false, $priority = 3, $hotmail_fix = null, $is_private = false) +{ + global $webmaster_email, $context, $modSettings, $txt, $scripturl; + global $smcFunc; + + // Use sendmail if it's set or if no SMTP server is set. + $use_sendmail = empty($modSettings['mail_type']) || $modSettings['smtp_host'] == ''; + + // Line breaks need to be \r\n only in windows or for SMTP. + $line_break = $context['server']['is_windows'] || !$use_sendmail ? "\r\n" : "\n"; + + // So far so good. + $mail_result = true; + + // If the recipient list isn't an array, make it one. + $to_array = is_array($to) ? $to : array($to); + + // Once upon a time, Hotmail could not interpret non-ASCII mails. + // In honour of those days, it's still called the 'hotmail fix'. + if ($hotmail_fix === null) + { + $hotmail_to = array(); + foreach ($to_array as $i => $to_address) + { + if (preg_match('~@(att|comcast|bellsouth)\.[a-zA-Z\.]{2,6}$~i', $to_address) === 1) + { + $hotmail_to[] = $to_address; + $to_array = array_diff($to_array, array($to_address)); + } + } + + // Call this function recursively for the hotmail addresses. + if (!empty($hotmail_to)) + $mail_result = sendmail($hotmail_to, $subject, $message, $from, $message_id, $send_html, $priority, true); + + // The remaining addresses no longer need the fix. + $hotmail_fix = false; + + // No other addresses left? Return instantly. + if (empty($to_array)) + return $mail_result; + } + + // Get rid of entities. + $subject = un_htmlspecialchars($subject); + // Make the message use the proper line breaks. + $message = str_replace(array("\r", "\n"), array('', $line_break), $message); + + // Make sure hotmail mails are sent as HTML so that HTML entities work. + if ($hotmail_fix && !$send_html) + { + $send_html = true; + $message = strtr($message, array($line_break => '
          ' . $line_break)); + $message = preg_replace('~(' . preg_quote($scripturl, '~') . '(?:[?/][\w\-_%\.,\?&;=#]+)?)~', '$1', $message); + } + + list (, $from_name) = mimespecialchars(addcslashes($from !== null ? $from : $context['forum_name'], '<>()\'\\"'), true, $hotmail_fix, $line_break); + list (, $subject) = mimespecialchars($subject, true, $hotmail_fix, $line_break); + + // Construct the mail headers... + $headers = 'From: "' . $from_name . '" <' . (empty($modSettings['mail_from']) ? $webmaster_email : $modSettings['mail_from']) . '>' . $line_break; + $headers .= $from !== null ? 'Reply-To: <' . $from . '>' . $line_break : ''; + $headers .= 'Return-Path: ' . (empty($modSettings['mail_from']) ? $webmaster_email : $modSettings['mail_from']) . $line_break; + $headers .= 'Date: ' . gmdate('D, d M Y H:i:s') . ' -0000' . $line_break; + + if ($message_id !== null && empty($modSettings['mail_no_message_id'])) + $headers .= 'Message-ID: <' . md5($scripturl . microtime()) . '-' . $message_id . strstr(empty($modSettings['mail_from']) ? $webmaster_email : $modSettings['mail_from'], '@') . '>' . $line_break; + $headers .= 'X-Mailer: SMF' . $line_break; + + // Pass this to the integration before we start modifying the output -- it'll make it easier later. + if (in_array(false, call_integration_hook('integrate_outgoing_email', array(&$subject, &$message, &$headers)), true)) + return false; + + // Save the original message... + $orig_message = $message; + + // The mime boundary separates the different alternative versions. + $mime_boundary = 'SMF-' . md5($message . time()); + + // Using mime, as it allows to send a plain unencoded alternative. + $headers .= 'Mime-Version: 1.0' . $line_break; + $headers .= 'Content-Type: multipart/alternative; boundary="' . $mime_boundary . '"' . $line_break; + $headers .= 'Content-Transfer-Encoding: 7bit' . $line_break; + + // Sending HTML? Let's plop in some basic stuff, then. + if ($send_html) + { + $no_html_message = un_htmlspecialchars(strip_tags(strtr($orig_message, array('' => $line_break)))); + + // But, then, dump it and use a plain one for dinosaur clients. + list(, $plain_message) = mimespecialchars($no_html_message, false, true, $line_break); + $message = $plain_message . $line_break . '--' . $mime_boundary . $line_break; + + // This is the plain text version. Even if no one sees it, we need it for spam checkers. + list($charset, $plain_charset_message, $encoding) = mimespecialchars($no_html_message, false, false, $line_break); + $message .= 'Content-Type: text/plain; charset=' . $charset . $line_break; + $message .= 'Content-Transfer-Encoding: ' . $encoding . $line_break . $line_break; + $message .= $plain_charset_message . $line_break . '--' . $mime_boundary . $line_break; + + // This is the actual HTML message, prim and proper. If we wanted images, they could be inlined here (with multipart/related, etc.) + list($charset, $html_message, $encoding) = mimespecialchars($orig_message, false, $hotmail_fix, $line_break); + $message .= 'Content-Type: text/html; charset=' . $charset . $line_break; + $message .= 'Content-Transfer-Encoding: ' . ($encoding == '' ? '7bit' : $encoding) . $line_break . $line_break; + $message .= $html_message . $line_break . '--' . $mime_boundary . '--'; + } + // Text is good too. + else + { + // Send a plain message first, for the older web clients. + list(, $plain_message) = mimespecialchars($orig_message, false, true, $line_break); + $message = $plain_message . $line_break . '--' . $mime_boundary . $line_break; + + // Now add an encoded message using the forum's character set. + list ($charset, $encoded_message, $encoding) = mimespecialchars($orig_message, false, false, $line_break); + $message .= 'Content-Type: text/plain; charset=' . $charset . $line_break; + $message .= 'Content-Transfer-Encoding: ' . $encoding . $line_break . $line_break; + $message .= $encoded_message . $line_break . '--' . $mime_boundary . '--'; + } + + // Are we using the mail queue, if so this is where we butt in... + if (!empty($modSettings['mail_queue']) && $priority != 0) + return AddMailQueue(false, $to_array, $subject, $message, $headers, $send_html, $priority, $is_private); + + // If it's a priority mail, send it now - note though that this should NOT be used for sending many at once. + elseif (!empty($modSettings['mail_queue']) && !empty($modSettings['mail_limit'])) + { + list ($last_mail_time, $mails_this_minute) = @explode('|', $modSettings['mail_recent']); + if (empty($mails_this_minute) || time() > $last_mail_time + 60) + $new_queue_stat = time() . '|' . 1; + else + $new_queue_stat = $last_mail_time . '|' . ((int) $mails_this_minute + 1); + + updateSettings(array('mail_recent' => $new_queue_stat)); + } + + // SMTP or sendmail? + if ($use_sendmail) + { + $subject = strtr($subject, array("\r" => '', "\n" => '')); + if (!empty($modSettings['mail_strip_carriage'])) + { + $message = strtr($message, array("\r" => '')); + $headers = strtr($headers, array("\r" => '')); + } + + foreach ($to_array as $to) + { + if (!mail(strtr($to, array("\r" => '', "\n" => '')), $subject, $message, $headers)) + { + log_error(sprintf($txt['mail_send_unable'], $to)); + $mail_result = false; + } + + // Wait, wait, I'm still sending here! + @set_time_limit(300); + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + } + } + else + $mail_result = $mail_result && smtp_mail($to_array, $subject, $message, $headers); + + // Everything go smoothly? + return $mail_result; +} + +// Add an email to the mail queue. +function AddMailQueue($flush = false, $to_array = array(), $subject = '', $message = '', $headers = '', $send_html = false, $priority = 3, $is_private = false) +{ + global $context, $modSettings, $smcFunc; + + static $cur_insert = array(); + static $cur_insert_len = 0; + + if ($cur_insert_len == 0) + $cur_insert = array(); + + // If we're flushing, make the final inserts - also if we're near the MySQL length limit! + if (($flush || $cur_insert_len > 800000) && !empty($cur_insert)) + { + // Only do these once. + $cur_insert_len = 0; + + // Dump the data... + $smcFunc['db_insert']('', + '{db_prefix}mail_queue', + array( + 'time_sent' => 'int', 'recipient' => 'string-255', 'body' => 'string-65534', 'subject' => 'string-255', + 'headers' => 'string-65534', 'send_html' => 'int', 'priority' => 'int', 'private' => 'int', + ), + $cur_insert, + array('id_mail') + ); + + $cur_insert = array(); + $context['flush_mail'] = false; + } + + // If we're flushing we're done. + if ($flush) + { + $nextSendTime = time() + 10; + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}settings + SET value = {string:nextSendTime} + WHERE variable = {string:mail_next_send} + AND value = {string:no_outstanding}', + array( + 'nextSendTime' => $nextSendTime, + 'mail_next_send' => 'mail_next_send', + 'no_outstanding' => '0', + ) + ); + + return true; + } + + // Ensure we tell obExit to flush. + $context['flush_mail'] = true; + + foreach ($to_array as $to) + { + // Will this insert go over MySQL's limit? + $this_insert_len = strlen($to) + strlen($message) + strlen($headers) + 700; + + // Insert limit of 1M (just under the safety) is reached? + if ($this_insert_len + $cur_insert_len > 1000000) + { + // Flush out what we have so far. + $smcFunc['db_insert']('', + '{db_prefix}mail_queue', + array( + 'time_sent' => 'int', 'recipient' => 'string-255', 'body' => 'string-65534', 'subject' => 'string-255', + 'headers' => 'string-65534', 'send_html' => 'int', 'priority' => 'int', 'private' => 'int', + ), + $cur_insert, + array('id_mail') + ); + + // Clear this out. + $cur_insert = array(); + $cur_insert_len = 0; + } + + // Now add the current insert to the array... + $cur_insert[] = array(time(), (string) $to, (string) $message, (string) $subject, (string) $headers, ($send_html ? 1 : 0), $priority, (int) $is_private); + $cur_insert_len += $this_insert_len; + } + + // If they are using SSI there is a good chance obExit will never be called. So lets be nice and flush it for them. + if (SMF === 'SSI') + return AddMailQueue(true); + + return true; +} + +// Send off a personal message. +function sendpm($recipients, $subject, $message, $store_outbox = false, $from = null, $pm_head = 0) +{ + global $scripturl, $txt, $user_info, $language; + global $modSettings, $smcFunc; + + // Make sure the PM language file is loaded, we might need something out of it. + loadLanguage('PersonalMessage'); + + $onBehalf = $from !== null; + + // Initialize log array. + $log = array( + 'failed' => array(), + 'sent' => array() + ); + + if ($from === null) + $from = array( + 'id' => $user_info['id'], + 'name' => $user_info['name'], + 'username' => $user_info['username'] + ); + // Probably not needed. /me something should be of the typer. + else + $user_info['name'] = $from['name']; + + // This is the one that will go in their inbox. + $htmlmessage = $smcFunc['htmlspecialchars']($message, ENT_QUOTES); + $htmlsubject = $smcFunc['htmlspecialchars']($subject); + preparsecode($htmlmessage); + + // Integrated PMs + call_integration_hook('integrate_personal_message', array($recipients, $from['username'], $subject, $message)); + + // Get a list of usernames and convert them to IDs. + $usernames = array(); + foreach ($recipients as $rec_type => $rec) + { + foreach ($rec as $id => $member) + { + if (!is_numeric($recipients[$rec_type][$id])) + { + $recipients[$rec_type][$id] = $smcFunc['strtolower'](trim(preg_replace('/[<>&"\'=\\\]/', '', $recipients[$rec_type][$id]))); + $usernames[$recipients[$rec_type][$id]] = 0; + } + } + } + if (!empty($usernames)) + { + $request = $smcFunc['db_query']('pm_find_username', ' + SELECT id_member, member_name + FROM {db_prefix}members + WHERE ' . ($smcFunc['db_case_sensitive'] ? 'LOWER(member_name)' : 'member_name') . ' IN ({array_string:usernames})', + array( + 'usernames' => array_keys($usernames), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + if (isset($usernames[$smcFunc['strtolower']($row['member_name'])])) + $usernames[$smcFunc['strtolower']($row['member_name'])] = $row['id_member']; + $smcFunc['db_free_result']($request); + + // Replace the usernames with IDs. Drop usernames that couldn't be found. + foreach ($recipients as $rec_type => $rec) + foreach ($rec as $id => $member) + { + if (is_numeric($recipients[$rec_type][$id])) + continue; + + if (!empty($usernames[$member])) + $recipients[$rec_type][$id] = $usernames[$member]; + else + { + $log['failed'][$id] = sprintf($txt['pm_error_user_not_found'], $recipients[$rec_type][$id]); + unset($recipients[$rec_type][$id]); + } + } + } + + // Make sure there are no duplicate 'to' members. + $recipients['to'] = array_unique($recipients['to']); + + // Only 'bcc' members that aren't already in 'to'. + $recipients['bcc'] = array_diff(array_unique($recipients['bcc']), $recipients['to']); + + // Combine 'to' and 'bcc' recipients. + $all_to = array_merge($recipients['to'], $recipients['bcc']); + + // Check no-one will want it deleted right away! + $request = $smcFunc['db_query']('', ' + SELECT + id_member, criteria, is_or + FROM {db_prefix}pm_rules + WHERE id_member IN ({array_int:to_members}) + AND delete_pm = {int:delete_pm}', + array( + 'to_members' => $all_to, + 'delete_pm' => 1, + ) + ); + $deletes = array(); + // Check whether we have to apply anything... + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $criteria = unserialize($row['criteria']); + // Note we don't check the buddy status, cause deletion from buddy = madness! + $delete = false; + foreach ($criteria as $criterium) + { + $match = false; + if (($criterium['t'] == 'mid' && $criterium['v'] == $from['id']) || ($criterium['t'] == 'gid' && in_array($criterium['v'], $user_info['groups'])) || ($criterium['t'] == 'sub' && strpos($subject, $criterium['v']) !== false) || ($criterium['t'] == 'msg' && strpos($message, $criterium['v']) !== false)) + $delete = true; + // If we're adding and one criteria don't match then we stop! + elseif (!$row['is_or']) + { + $delete = false; + break; + } + } + if ($delete) + $deletes[$row['id_member']] = 1; + } + $smcFunc['db_free_result']($request); + + // Load the membergrounp message limits. + //!!! Consider caching this? + static $message_limit_cache = array(); + if (!allowedTo('moderate_forum') && empty($message_limit_cache)) + { + $request = $smcFunc['db_query']('', ' + SELECT id_group, max_messages + FROM {db_prefix}membergroups', + array( + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $message_limit_cache[$row['id_group']] = $row['max_messages']; + $smcFunc['db_free_result']($request); + } + + // Load the groups that are allowed to read PMs. + $allowed_groups = array(); + $disallowed_groups = array(); + $request = $smcFunc['db_query']('', ' + SELECT id_group, add_deny + FROM {db_prefix}permissions + WHERE permission = {string:read_permission}', + array( + 'read_permission' => 'pm_read', + ) + ); + + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (empty($row['add_deny'])) + $disallowed_groups[] = $row['id_group']; + else + $allowed_groups[] = $row['id_group']; + } + + $smcFunc['db_free_result']($request); + + if (empty($modSettings['permission_enable_deny'])) + $disallowed_groups = array(); + + $request = $smcFunc['db_query']('', ' + SELECT + member_name, real_name, id_member, email_address, lngfile, + pm_email_notify, instant_messages,' . (allowedTo('moderate_forum') ? ' 0' : ' + (pm_receive_from = {int:admins_only}' . (empty($modSettings['enable_buddylist']) ? '' : ' OR + (pm_receive_from = {int:buddies_only} AND FIND_IN_SET({string:from_id}, buddy_list) = 0) OR + (pm_receive_from = {int:not_on_ignore_list} AND FIND_IN_SET({string:from_id}, pm_ignore_list) != 0)') . ')') . ' AS ignored, + FIND_IN_SET({string:from_id}, buddy_list) != 0 AS is_buddy, is_activated, + additional_groups, id_group, id_post_group + FROM {db_prefix}members + WHERE id_member IN ({array_int:recipients}) + ORDER BY lngfile + LIMIT {int:count_recipients}', + array( + 'not_on_ignore_list' => 1, + 'buddies_only' => 2, + 'admins_only' => 3, + 'recipients' => $all_to, + 'count_recipients' => count($all_to), + 'from_id' => $from['id'], + ) + ); + $notifications = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Don't do anything for members to be deleted! + if (isset($deletes[$row['id_member']])) + continue; + + // We need to know this members groups. + $groups = explode(',', $row['additional_groups']); + $groups[] = $row['id_group']; + $groups[] = $row['id_post_group']; + + $message_limit = -1; + // For each group see whether they've gone over their limit - assuming they're not an admin. + if (!in_array(1, $groups)) + { + foreach ($groups as $id) + { + if (isset($message_limit_cache[$id]) && $message_limit != 0 && $message_limit < $message_limit_cache[$id]) + $message_limit = $message_limit_cache[$id]; + } + + if ($message_limit > 0 && $message_limit <= $row['instant_messages']) + { + $log['failed'][$row['id_member']] = sprintf($txt['pm_error_data_limit_reached'], $row['real_name']); + unset($all_to[array_search($row['id_member'], $all_to)]); + continue; + } + + // Do they have any of the allowed groups? + if (count(array_intersect($allowed_groups, $groups)) == 0 || count(array_intersect($disallowed_groups, $groups)) != 0) + { + $log['failed'][$row['id_member']] = sprintf($txt['pm_error_user_cannot_read'], $row['real_name']); + unset($all_to[array_search($row['id_member'], $all_to)]); + continue; + } + } + + // Note that PostgreSQL can return a lowercase t/f for FIND_IN_SET + if (!empty($row['ignored']) && $row['ignored'] != 'f' && $row['id_member'] != $from['id']) + { + $log['failed'][$row['id_member']] = sprintf($txt['pm_error_ignored_by_user'], $row['real_name']); + unset($all_to[array_search($row['id_member'], $all_to)]); + continue; + } + + // If the receiving account is banned (>=10) or pending deletion (4), refuse to send the PM. + if ($row['is_activated'] >= 10 || ($row['is_activated'] == 4 && !$user_info['is_admin'])) + { + $log['failed'][$row['id_member']] = sprintf($txt['pm_error_user_cannot_read'], $row['real_name']); + unset($all_to[array_search($row['id_member'], $all_to)]); + continue; + } + + // Send a notification, if enabled - taking the buddy list into account. + if (!empty($row['email_address']) && ($row['pm_email_notify'] == 1 || ($row['pm_email_notify'] > 1 && (!empty($modSettings['enable_buddylist']) && $row['is_buddy']))) && $row['is_activated'] == 1) + $notifications[empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']][] = $row['email_address']; + + $log['sent'][$row['id_member']] = sprintf(isset($txt['pm_successfully_sent']) ? $txt['pm_successfully_sent'] : '', $row['real_name']); + } + $smcFunc['db_free_result']($request); + + // Only 'send' the message if there are any recipients left. + if (empty($all_to)) + return $log; + + // Insert the message itself and then grab the last insert id. + $smcFunc['db_insert']('', + '{db_prefix}personal_messages', + array( + 'id_pm_head' => 'int', 'id_member_from' => 'int', 'deleted_by_sender' => 'int', + 'from_name' => 'string-255', 'msgtime' => 'int', 'subject' => 'string-255', 'body' => 'string-65534', + ), + array( + $pm_head, $from['id'], ($store_outbox ? 0 : 1), + $from['username'], time(), $htmlsubject, $htmlmessage, + ), + array('id_pm') + ); + $id_pm = $smcFunc['db_insert_id']('{db_prefix}personal_messages', 'id_pm'); + + // Add the recipients. + if (!empty($id_pm)) + { + // If this is new we need to set it part of it's own conversation. + if (empty($pm_head)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}personal_messages + SET id_pm_head = {int:id_pm_head} + WHERE id_pm = {int:id_pm_head}', + array( + 'id_pm_head' => $id_pm, + ) + ); + + // Some people think manually deleting personal_messages is fun... it's not. We protect against it though :) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}pm_recipients + WHERE id_pm = {int:id_pm}', + array( + 'id_pm' => $id_pm, + ) + ); + + $insertRows = array(); + foreach ($all_to as $to) + { + $insertRows[] = array($id_pm, $to, in_array($to, $recipients['bcc']) ? 1 : 0, isset($deletes[$to]) ? 1 : 0, 1); + } + + $smcFunc['db_insert']('insert', + '{db_prefix}pm_recipients', + array( + 'id_pm' => 'int', 'id_member' => 'int', 'bcc' => 'int', 'deleted' => 'int', 'is_new' => 'int' + ), + $insertRows, + array('id_pm', 'id_member') + ); + } + + censorText($message); + censorText($subject); + $message = trim(un_htmlspecialchars(strip_tags(strtr(parse_bbc(htmlspecialchars($message), false), array('
          ' => "\n", '' => "\n", '
        • ' => "\n", '[' => '[', ']' => ']'))))); + + foreach ($notifications as $lang => $notification_list) + { + // Make sure to use the right language. + loadLanguage('index+PersonalMessage', $lang, false); + + // Replace the right things in the message strings. + $mailsubject = str_replace(array('SUBJECT', 'SENDER'), array($subject, un_htmlspecialchars($from['name'])), $txt['new_pm_subject']); + $mailmessage = str_replace(array('SUBJECT', 'MESSAGE', 'SENDER'), array($subject, $message, un_htmlspecialchars($from['name'])), $txt['pm_email']); + $mailmessage .= "\n\n" . $txt['instant_reply'] . ' ' . $scripturl . '?action=pm;sa=send;f=inbox;pmsg=' . $id_pm . ';quote;u=' . $from['id']; + + // Off the notification email goes! + sendmail($notification_list, $mailsubject, $mailmessage, null, 'p' . $id_pm, false, 2, null, true); + } + + // Back to what we were on before! + loadLanguage('index+PersonalMessage'); + + // Add one to their unread and read message counts. + foreach ($all_to as $k => $id) + if (isset($deletes[$id])) + unset($all_to[$k]); + if (!empty($all_to)) + updateMemberData($all_to, array('instant_messages' => '+', 'unread_messages' => '+', 'new_pm' => 1)); + + return $log; +} + +// Prepare text strings for sending as email body or header. +function mimespecialchars($string, $with_charset = true, $hotmail_fix = false, $line_break = "\r\n", $custom_charset = null) +{ + global $context; + + $charset = $custom_charset !== null ? $custom_charset : $context['character_set']; + + // This is the fun part.... + if (preg_match_all('~&#(\d{3,8});~', $string, $matches) !== 0 && !$hotmail_fix) + { + // Let's, for now, assume there are only 'ish characters. + $simple = true; + + foreach ($matches[1] as $entity) + if ($entity > 128) + $simple = false; + unset($matches); + + if ($simple) + $string = preg_replace_callback('~&#(\d{3,8});~', create_function('$m', ' return chr("$m[1]");'), $string); + else + { + // Try to convert the string to UTF-8. + if (!$context['utf8'] && function_exists('iconv')) + { + $newstring = @iconv($context['character_set'], 'UTF-8', $string); + if ($newstring) + $string = $newstring; + } + + $fixchar = create_function('$n', ' + if ($n < 128) + return chr($n); + elseif ($n < 2048) + return chr(192 | $n >> 6) . chr(128 | $n & 63); + elseif ($n < 65536) + return chr(224 | $n >> 12) . chr(128 | $n >> 6 & 63) . chr(128 | $n & 63); + else + return chr(240 | $n >> 18) . chr(128 | $n >> 12 & 63) . chr(128 | $n >> 6 & 63) . chr(128 | $n & 63);'); + + $string = preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $string); + + // Unicode, baby. + $charset = 'UTF-8'; + } + } + + // Convert all special characters to HTML entities...just for Hotmail :-\ + if ($hotmail_fix && ($context['utf8'] || function_exists('iconv') || $context['character_set'] === 'ISO-8859-1')) + { + if (!$context['utf8'] && function_exists('iconv')) + { + $newstring = @iconv($context['character_set'], 'UTF-8', $string); + if ($newstring) + $string = $newstring; + } + + $entityConvert = create_function('$c', ' + if (strlen($c) === 1 && ord($c[0]) <= 0x7F) + return $c; + elseif (strlen($c) === 2 && ord($c[0]) >= 0xC0 && ord($c[0]) <= 0xDF) + return "&#" . (((ord($c[0]) ^ 0xC0) << 6) + (ord($c[1]) ^ 0x80)) . ";"; + elseif (strlen($c) === 3 && ord($c[0]) >= 0xE0 && ord($c[0]) <= 0xEF) + return "&#" . (((ord($c[0]) ^ 0xE0) << 12) + ((ord($c[1]) ^ 0x80) << 6) + (ord($c[2]) ^ 0x80)) . ";"; + elseif (strlen($c) === 4 && ord($c[0]) >= 0xF0 && ord($c[0]) <= 0xF7) + return "&#" . (((ord($c[0]) ^ 0xF0) << 18) + ((ord($c[1]) ^ 0x80) << 12) + ((ord($c[2]) ^ 0x80) << 6) + (ord($c[3]) ^ 0x80)) . ";"; + else + return "";'); + + $entityConvert = create_function('$m', ' + $c = $m[1]; + if (strlen($c) === 1 && ord($c[0]) <= 0x7F) + return $c; + elseif (strlen($c) === 2 && ord($c[0]) >= 0xC0 && ord($c[0]) <= 0xDF) + return "&#" . (((ord($c[0]) ^ 0xC0) << 6) + (ord($c[1]) ^ 0x80)) . ";"; + elseif (strlen($c) === 3 && ord($c[0]) >= 0xE0 && ord($c[0]) <= 0xEF) + return "&#" . (((ord($c[0]) ^ 0xE0) << 12) + ((ord($c[1]) ^ 0x80) << 6) + (ord($c[2]) ^ 0x80)) . ";"; + elseif (strlen($c) === 4 && ord($c[0]) >= 0xF0 && ord($c[0]) <= 0xF7) + return "&#" . (((ord($c[0]) ^ 0xF0) << 18) + ((ord($c[1]) ^ 0x80) << 12) + ((ord($c[2]) ^ 0x80) << 6) + (ord($c[3]) ^ 0x80)) . ";"; + else + return "";'); + + // Convert all 'special' characters to HTML entities. + return array($charset, preg_replace_callback('~([\x80-\x{10FFFF}])~u', $entityConvert, $string), '7bit'); + } + + // We don't need to mess with the subject line if no special characters were in it.. + elseif (!$hotmail_fix && preg_match('~([^\x09\x0A\x0D\x20-\x7F])~', $string) === 1) + { + // Base64 encode. + $string = base64_encode($string); + + // Show the characterset and the transfer-encoding for header strings. + if ($with_charset) + $string = '=?' . $charset . '?B?' . $string . '?='; + + // Break it up in lines (mail body). + else + $string = chunk_split($string, 76, $line_break); + + return array($charset, $string, 'base64'); + } + + else + return array($charset, $string, '7bit'); +} + +// Send an email via SMTP. +function smtp_mail($mail_to_array, $subject, $message, $headers) +{ + global $modSettings, $webmaster_email, $txt; + + $modSettings['smtp_host'] = trim($modSettings['smtp_host']); + + // Try POP3 before SMTP? + // !!! There's no interface for this yet. + if ($modSettings['mail_type'] == 2 && $modSettings['smtp_username'] != '' && $modSettings['smtp_password'] != '') + { + $socket = fsockopen($modSettings['smtp_host'], 110, $errno, $errstr, 2); + if (!$socket && (substr($modSettings['smtp_host'], 0, 5) == 'smtp.' || substr($modSettings['smtp_host'], 0, 11) == 'ssl://smtp.')) + $socket = fsockopen(strtr($modSettings['smtp_host'], array('smtp.' => 'pop.')), 110, $errno, $errstr, 2); + + if ($socket) + { + fgets($socket, 256); + fputs($socket, 'USER ' . $modSettings['smtp_username'] . "\r\n"); + fgets($socket, 256); + fputs($socket, 'PASS ' . base64_decode($modSettings['smtp_password']) . "\r\n"); + fgets($socket, 256); + fputs($socket, 'QUIT' . "\r\n"); + + fclose($socket); + } + } + + // Try to connect to the SMTP server... if it doesn't exist, only wait three seconds. + if (!$socket = fsockopen($modSettings['smtp_host'], empty($modSettings['smtp_port']) ? 25 : $modSettings['smtp_port'], $errno, $errstr, 3)) + { + // Maybe we can still save this? The port might be wrong. + if (substr($modSettings['smtp_host'], 0, 4) == 'ssl:' && (empty($modSettings['smtp_port']) || $modSettings['smtp_port'] == 25)) + { + if ($socket = fsockopen($modSettings['smtp_host'], 465, $errno, $errstr, 3)) + log_error($txt['smtp_port_ssl']); + } + + // Unable to connect! Don't show any error message, but just log one and try to continue anyway. + if (!$socket) + { + log_error($txt['smtp_no_connect'] . ': ' . $errno . ' : ' . $errstr); + return false; + } + } + + // Wait for a response of 220, without "-" continuer. + if (!server_parse(null, $socket, '220')) + return false; + + if ($modSettings['mail_type'] == 1 && $modSettings['smtp_username'] != '' && $modSettings['smtp_password'] != '') + { + // !!! These should send the CURRENT server's name, not the mail server's! + + // EHLO could be understood to mean encrypted hello... + if (server_parse('EHLO ' . $modSettings['smtp_host'], $socket, null) == '250') + { + if (!server_parse('AUTH LOGIN', $socket, '334')) + return false; + // Send the username and password, encoded. + if (!server_parse(base64_encode($modSettings['smtp_username']), $socket, '334')) + return false; + // The password is already encoded ;) + if (!server_parse($modSettings['smtp_password'], $socket, '235')) + return false; + } + elseif (!server_parse('HELO ' . $modSettings['smtp_host'], $socket, '250')) + return false; + } + else + { + // Just say "helo". + if (!server_parse('HELO ' . $modSettings['smtp_host'], $socket, '250')) + return false; + } + + // Fix the message for any lines beginning with a period! (the first is ignored, you see.) + $message = strtr($message, array("\r\n" . '.' => "\r\n" . '..')); + + // !! Theoretically, we should be able to just loop the RCPT TO. + $mail_to_array = array_values($mail_to_array); + foreach ($mail_to_array as $i => $mail_to) + { + // Reset the connection to send another email. + if ($i != 0) + { + if (!server_parse('RSET', $socket, '250')) + return false; + } + + // From, to, and then start the data... + if (!server_parse('MAIL FROM: <' . (empty($modSettings['mail_from']) ? $webmaster_email : $modSettings['mail_from']) . '>', $socket, '250')) + return false; + if (!server_parse('RCPT TO: <' . $mail_to . '>', $socket, '250')) + return false; + if (!server_parse('DATA', $socket, '354')) + return false; + fputs($socket, 'Subject: ' . $subject . "\r\n"); + if (strlen($mail_to) > 0) + fputs($socket, 'To: <' . $mail_to . '>' . "\r\n"); + fputs($socket, $headers . "\r\n\r\n"); + fputs($socket, $message . "\r\n"); + + // Send a ., or in other words "end of data". + if (!server_parse('.', $socket, '250')) + return false; + + // Almost done, almost done... don't stop me just yet! + @set_time_limit(300); + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + } + fputs($socket, 'QUIT' . "\r\n"); + fclose($socket); + + return true; +} + +// Parse a message to the SMTP server. +function server_parse($message, $socket, $response) +{ + global $txt; + + if ($message !== null) + fputs($socket, $message . "\r\n"); + + // No response yet. + $server_response = ''; + + while (substr($server_response, 3, 1) != ' ') + if (!($server_response = fgets($socket, 256))) + { + // !!! Change this message to reflect that it may mean bad user/password/server issues/etc. + log_error($txt['smtp_bad_response']); + return false; + } + + if ($response === null) + return substr($server_response, 0, 3); + + if (substr($server_response, 0, 3) != $response) + { + log_error($txt['smtp_error'] . $server_response); + return false; + } + + return true; +} + +function SpellCheck() +{ + global $txt, $context, $smcFunc; + + // A list of "words" we know about but pspell doesn't. + $known_words = array('smf', 'php', 'mysql', 'www', 'gif', 'jpeg', 'png', 'http', 'smfisawesome', 'grandia', 'terranigma', 'rpgs'); + + loadLanguage('Post'); + loadTemplate('Post'); + + // Okay, this looks funny, but it actually fixes a weird bug. + ob_start(); + $old = error_reporting(0); + + // See, first, some windows machines don't load pspell properly on the first try. Dumb, but this is a workaround. + pspell_new('en'); + + // Next, the dictionary in question may not exist. So, we try it... but... + $pspell_link = pspell_new($txt['lang_dictionary'], $txt['lang_spelling'], '', strtr($context['character_set'], array('iso-' => 'iso', 'ISO-' => 'iso')), PSPELL_FAST | PSPELL_RUN_TOGETHER); + + // Most people don't have anything but English installed... So we use English as a last resort. + if (!$pspell_link) + $pspell_link = pspell_new('en', '', '', '', PSPELL_FAST | PSPELL_RUN_TOGETHER); + + error_reporting($old); + ob_end_clean(); + + if (!isset($_POST['spellstring']) || !$pspell_link) + die; + + // Construct a bit of Javascript code. + $context['spell_js'] = ' + var txt = {"done": "' . $txt['spellcheck_done'] . '"}; + var mispstr = window.opener.document.forms[spell_formname][spell_fieldname].value; + var misps = Array('; + + // Get all the words (Javascript already separated them). + $alphas = explode("\n", strtr($_POST['spellstring'], array("\r" => ''))); + + $found_words = false; + for ($i = 0, $n = count($alphas); $i < $n; $i++) + { + // Words are sent like 'word|offset_begin|offset_end'. + $check_word = explode('|', $alphas[$i]); + + // If the word is a known word, or spelled right... + if (in_array($smcFunc['strtolower']($check_word[0]), $known_words) || pspell_check($pspell_link, $check_word[0]) || !isset($check_word[2])) + continue; + + // Find the word, and move up the "last occurance" to here. + $found_words = true; + + // Add on the javascript for this misspelling. + $context['spell_js'] .= ' + new misp("' . strtr($check_word[0], array('\\' => '\\\\', '"' => '\\"', '<' => '', '>' => '')) . '", ' . (int) $check_word[1] . ', ' . (int) $check_word[2] . ', ['; + + // If there are suggestions, add them in... + $suggestions = pspell_suggest($pspell_link, $check_word[0]); + if (!empty($suggestions)) + { + // But first check they aren't going to be censored - no naughty words! + foreach ($suggestions as $k => $word) + if ($suggestions[$k] != censorText($word)) + unset($suggestions[$k]); + + if (!empty($suggestions)) + $context['spell_js'] .= '"' . implode('", "', $suggestions) . '"'; + } + + $context['spell_js'] .= ']),'; + } + + // If words were found, take off the last comma. + if ($found_words) + $context['spell_js'] = substr($context['spell_js'], 0, -1); + + $context['spell_js'] .= ' + );'; + + // And instruct the template system to just show the spellcheck sub template. + $context['template_layers'] = array(); + $context['sub_template'] = 'spellcheck'; +} + +// Notify members that something has happened to a topic they marked! +function sendNotifications($topics, $type, $exclude = array(), $members_only = array()) +{ + global $txt, $scripturl, $language, $user_info; + global $modSettings, $sourcedir, $context, $smcFunc; + + // Can't do it if there's no topics. + if (empty($topics)) + return; + // It must be an array - it must! + if (!is_array($topics)) + $topics = array($topics); + + // Get the subject and body... + $result = $smcFunc['db_query']('', ' + SELECT mf.subject, ml.body, ml.id_member, t.id_last_msg, t.id_topic, + IFNULL(mem.real_name, ml.poster_name) AS poster_name + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg) + INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = ml.id_member) + WHERE t.id_topic IN ({array_int:topic_list}) + LIMIT 1', + array( + 'topic_list' => $topics, + ) + ); + $topicData = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + // Clean it up. + censorText($row['subject']); + censorText($row['body']); + $row['subject'] = un_htmlspecialchars($row['subject']); + $row['body'] = trim(un_htmlspecialchars(strip_tags(strtr(parse_bbc($row['body'], false, $row['id_last_msg']), array('
          ' => "\n", '' => "\n", '' => "\n", '[' => '[', ']' => ']'))))); + + $topicData[$row['id_topic']] = array( + 'subject' => $row['subject'], + 'body' => $row['body'], + 'last_id' => $row['id_last_msg'], + 'topic' => $row['id_topic'], + 'name' => $user_info['name'], + 'exclude' => '', + ); + } + $smcFunc['db_free_result']($result); + + // Work out any exclusions... + foreach ($topics as $key => $id) + if (isset($topicData[$id]) && !empty($exclude[$key])) + $topicData[$id]['exclude'] = (int) $exclude[$key]; + + // Nada? + if (empty($topicData)) + trigger_error('sendNotifications(): topics not found', E_USER_NOTICE); + + $topics = array_keys($topicData); + // Just in case they've gone walkies. + if (empty($topics)) + return; + + // Insert all of these items into the digest log for those who want notifications later. + $digest_insert = array(); + foreach ($topicData as $id => $data) + $digest_insert[] = array($data['topic'], $data['last_id'], $type, (int) $data['exclude']); + $smcFunc['db_insert']('', + '{db_prefix}log_digest', + array( + 'id_topic' => 'int', 'id_msg' => 'int', 'note_type' => 'string', 'exclude' => 'int', + ), + $digest_insert, + array() + ); + + // Find the members with notification on for this topic. + $members = $smcFunc['db_query']('', ' + SELECT + mem.id_member, mem.email_address, mem.notify_regularity, mem.notify_types, mem.notify_send_body, mem.lngfile, + ln.sent, mem.id_group, mem.additional_groups, b.member_groups, mem.id_post_group, t.id_member_started, + ln.id_topic + FROM {db_prefix}log_notify AS ln + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member) + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ln.id_topic) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + WHERE ln.id_topic IN ({array_int:topic_list}) + AND mem.notify_types < {int:notify_types} + AND mem.notify_regularity < {int:notify_regularity} + AND mem.is_activated = {int:is_activated} + AND ln.id_member != {int:current_member}' . + (empty($members_only) ? '' : ' AND ln.id_member IN ({array_int:members_only})') . ' + ORDER BY mem.lngfile', + array( + 'current_member' => $user_info['id'], + 'topic_list' => $topics, + 'notify_types' => $type == 'reply' ? '4' : '3', + 'notify_regularity' => 2, + 'is_activated' => 1, + 'members_only' => is_array($members_only) ? $members_only : array($members_only), + ) + ); + $sent = 0; + while ($row = $smcFunc['db_fetch_assoc']($members)) + { + // Don't do the excluded... + if ($topicData[$row['id_topic']]['exclude'] == $row['id_member']) + continue; + + // Easier to check this here... if they aren't the topic poster do they really want to know? + if ($type != 'reply' && $row['notify_types'] == 2 && $row['id_member'] != $row['id_member_started']) + continue; + + if ($row['id_group'] != 1) + { + $allowed = explode(',', $row['member_groups']); + $row['additional_groups'] = explode(',', $row['additional_groups']); + $row['additional_groups'][] = $row['id_group']; + $row['additional_groups'][] = $row['id_post_group']; + + if (count(array_intersect($allowed, $row['additional_groups'])) == 0) + continue; + } + + $needed_language = empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']; + if (empty($current_language) || $current_language != $needed_language) + $current_language = loadLanguage('Post', $needed_language, false); + + $message_type = 'notification_' . $type; + $replacements = array( + 'TOPICSUBJECT' => $topicData[$row['id_topic']]['subject'], + 'POSTERNAME' => un_htmlspecialchars($topicData[$row['id_topic']]['name']), + 'TOPICLINK' => $scripturl . '?topic=' . $row['id_topic'] . '.new;topicseen#new', + 'UNSUBSCRIBELINK' => $scripturl . '?action=notify;topic=' . $row['id_topic'] . '.0', + ); + + if ($type == 'remove') + unset($replacements['TOPICLINK'], $replacements['UNSUBSCRIBELINK']); + // Do they want the body of the message sent too? + if (!empty($row['notify_send_body']) && $type == 'reply' && empty($modSettings['disallow_sendBody'])) + { + $message_type .= '_body'; + $replacements['MESSAGE'] = $topicData[$row['id_topic']]['body']; + } + if (!empty($row['notify_regularity']) && $type == 'reply') + $message_type .= '_once'; + + // Send only if once is off or it's on and it hasn't been sent. + if ($type != 'reply' || empty($row['notify_regularity']) || empty($row['sent'])) + { + $emaildata = loadEmailTemplate($message_type, $replacements, $needed_language); + sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, 'm' . $topicData[$row['id_topic']]['last_id']); + $sent++; + } + } + $smcFunc['db_free_result']($members); + + if (isset($current_language) && $current_language != $user_info['language']) + loadLanguage('Post'); + + // Sent! + if ($type == 'reply' && !empty($sent)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_notify + SET sent = {int:is_sent} + WHERE id_topic IN ({array_int:topic_list}) + AND id_member != {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'topic_list' => $topics, + 'is_sent' => 1, + ) + ); + + // For approvals we need to unsend the exclusions (This *is* the quickest way!) + if (!empty($sent) && !empty($exclude)) + { + foreach ($topicData as $id => $data) + if ($data['exclude']) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_notify + SET sent = {int:not_sent} + WHERE id_topic = {int:id_topic} + AND id_member = {int:id_member}', + array( + 'not_sent' => 0, + 'id_topic' => $id, + 'id_member' => $data['exclude'], + ) + ); + } +} + +// Create a post, either as new topic (id_topic = 0) or in an existing one. +// The input parameters of this function assume: +// - Strings have been escaped. +// - Integers have been cast to integer. +// - Mandatory parameters are set. +function createPost(&$msgOptions, &$topicOptions, &$posterOptions) +{ + global $user_info, $txt, $modSettings, $smcFunc, $context; + + // Set optional parameters to the default value. + $msgOptions['icon'] = empty($msgOptions['icon']) ? 'xx' : $msgOptions['icon']; + $msgOptions['smileys_enabled'] = !empty($msgOptions['smileys_enabled']); + $msgOptions['attachments'] = empty($msgOptions['attachments']) ? array() : $msgOptions['attachments']; + $msgOptions['approved'] = isset($msgOptions['approved']) ? (int) $msgOptions['approved'] : 1; + $topicOptions['id'] = empty($topicOptions['id']) ? 0 : (int) $topicOptions['id']; + $topicOptions['poll'] = isset($topicOptions['poll']) ? (int) $topicOptions['poll'] : null; + $topicOptions['lock_mode'] = isset($topicOptions['lock_mode']) ? $topicOptions['lock_mode'] : null; + $topicOptions['sticky_mode'] = isset($topicOptions['sticky_mode']) ? $topicOptions['sticky_mode'] : null; + $posterOptions['id'] = empty($posterOptions['id']) ? 0 : (int) $posterOptions['id']; + $posterOptions['ip'] = empty($posterOptions['ip']) ? $user_info['ip'] : $posterOptions['ip']; + + // We need to know if the topic is approved. If we're told that's great - if not find out. + if (!$modSettings['postmod_active']) + $topicOptions['is_approved'] = true; + elseif (!empty($topicOptions['id']) && !isset($topicOptions['is_approved'])) + { + $request = $smcFunc['db_query']('', ' + SELECT approved + FROM {db_prefix}topics + WHERE id_topic = {int:id_topic} + LIMIT 1', + array( + 'id_topic' => $topicOptions['id'], + ) + ); + list ($topicOptions['is_approved']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + // If nothing was filled in as name/e-mail address, try the member table. + if (!isset($posterOptions['name']) || $posterOptions['name'] == '' || (empty($posterOptions['email']) && !empty($posterOptions['id']))) + { + if (empty($posterOptions['id'])) + { + $posterOptions['id'] = 0; + $posterOptions['name'] = $txt['guest_title']; + $posterOptions['email'] = ''; + } + elseif ($posterOptions['id'] != $user_info['id']) + { + $request = $smcFunc['db_query']('', ' + SELECT member_name, email_address + FROM {db_prefix}members + WHERE id_member = {int:id_member} + LIMIT 1', + array( + 'id_member' => $posterOptions['id'], + ) + ); + // Couldn't find the current poster? + if ($smcFunc['db_num_rows']($request) == 0) + { + trigger_error('createPost(): Invalid member id ' . $posterOptions['id'], E_USER_NOTICE); + $posterOptions['id'] = 0; + $posterOptions['name'] = $txt['guest_title']; + $posterOptions['email'] = ''; + } + else + list ($posterOptions['name'], $posterOptions['email']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + else + { + $posterOptions['name'] = $user_info['name']; + $posterOptions['email'] = $user_info['email']; + } + } + + // It's do or die time: forget any user aborts! + $previous_ignore_user_abort = ignore_user_abort(true); + + $new_topic = empty($topicOptions['id']); + + // Insert the post. + $smcFunc['db_insert']('', + '{db_prefix}messages', + array( + 'id_board' => 'int', 'id_topic' => 'int', 'id_member' => 'int', 'subject' => 'string-255', 'body' => (!empty($modSettings['max_messageLength']) && $modSettings['max_messageLength'] > 65534 ? 'string-' . $modSettings['max_messageLength'] : 'string-65534'), + 'poster_name' => 'string-255', 'poster_email' => 'string-255', 'poster_time' => 'int', 'poster_ip' => 'string-255', + 'smileys_enabled' => 'int', 'modified_name' => 'string', 'icon' => 'string-16', 'approved' => 'int', + ), + array( + $topicOptions['board'], $topicOptions['id'], $posterOptions['id'], $msgOptions['subject'], $msgOptions['body'], + $posterOptions['name'], $posterOptions['email'], time(), $posterOptions['ip'], + $msgOptions['smileys_enabled'] ? 1 : 0, '', $msgOptions['icon'], $msgOptions['approved'], + ), + array('id_msg') + ); + $msgOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}messages', 'id_msg'); + + // Something went wrong creating the message... + if (empty($msgOptions['id'])) + return false; + + // Fix the attachments. + if (!empty($msgOptions['attachments'])) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}attachments + SET id_msg = {int:id_msg} + WHERE id_attach IN ({array_int:attachment_list})', + array( + 'attachment_list' => $msgOptions['attachments'], + 'id_msg' => $msgOptions['id'], + ) + ); + + // Insert a new topic (if the topicID was left empty.) + if ($new_topic) + { + $smcFunc['db_insert']('', + '{db_prefix}topics', + array( + 'id_board' => 'int', 'id_member_started' => 'int', 'id_member_updated' => 'int', 'id_first_msg' => 'int', + 'id_last_msg' => 'int', 'locked' => 'int', 'is_sticky' => 'int', 'num_views' => 'int', + 'id_poll' => 'int', 'unapproved_posts' => 'int', 'approved' => 'int', + ), + array( + $topicOptions['board'], $posterOptions['id'], $posterOptions['id'], $msgOptions['id'], + $msgOptions['id'], $topicOptions['lock_mode'] === null ? 0 : $topicOptions['lock_mode'], $topicOptions['sticky_mode'] === null ? 0 : $topicOptions['sticky_mode'], 0, + $topicOptions['poll'] === null ? 0 : $topicOptions['poll'], $msgOptions['approved'] ? 0 : 1, $msgOptions['approved'], + ), + array('id_topic') + ); + $topicOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}topics', 'id_topic'); + + // The topic couldn't be created for some reason. + if (empty($topicOptions['id'])) + { + // We should delete the post that did work, though... + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}messages + WHERE id_msg = {int:id_msg}', + array( + 'id_msg' => $msgOptions['id'], + ) + ); + + return false; + } + + // Fix the message with the topic. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET id_topic = {int:id_topic} + WHERE id_msg = {int:id_msg}', + array( + 'id_topic' => $topicOptions['id'], + 'id_msg' => $msgOptions['id'], + ) + ); + + // There's been a new topic AND a new post today. + trackStats(array('topics' => '+', 'posts' => '+')); + + updateStats('topic', true); + updateStats('subject', $topicOptions['id'], $msgOptions['subject']); + + // What if we want to export new topics out to a CMS? + call_integration_hook('integrate_create_topic', array($msgOptions, $topicOptions, $posterOptions)); + } + // The topic already exists, it only needs a little updating. + else + { + $countChange = $msgOptions['approved'] ? 'num_replies = num_replies + 1' : 'unapproved_posts = unapproved_posts + 1'; + + // Update the number of replies and the lock/sticky status. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET + ' . ($msgOptions['approved'] ? 'id_member_updated = {int:poster_id}, id_last_msg = {int:id_msg},' : '') . ' + ' . $countChange . ($topicOptions['lock_mode'] === null ? '' : ', + locked = {int:locked}') . ($topicOptions['sticky_mode'] === null ? '' : ', + is_sticky = {int:is_sticky}') . ' + WHERE id_topic = {int:id_topic}', + array( + 'poster_id' => $posterOptions['id'], + 'id_msg' => $msgOptions['id'], + 'locked' => $topicOptions['lock_mode'], + 'is_sticky' => $topicOptions['sticky_mode'], + 'id_topic' => $topicOptions['id'], + ) + ); + + // One new post has been added today. + trackStats(array('posts' => '+')); + } + + // Creating is modifying...in a way. + //!!! Why not set id_msg_modified on the insert? + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET id_msg_modified = {int:id_msg} + WHERE id_msg = {int:id_msg}', + array( + 'id_msg' => $msgOptions['id'], + ) + ); + + // Increase the number of posts and topics on the board. + if ($msgOptions['approved']) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET num_posts = num_posts + 1' . ($new_topic ? ', num_topics = num_topics + 1' : '') . ' + WHERE id_board = {int:id_board}', + array( + 'id_board' => $topicOptions['board'], + ) + ); + else + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET unapproved_posts = unapproved_posts + 1' . ($new_topic ? ', unapproved_topics = unapproved_topics + 1' : '') . ' + WHERE id_board = {int:id_board}', + array( + 'id_board' => $topicOptions['board'], + ) + ); + + // Add to the approval queue too. + $smcFunc['db_insert']('', + '{db_prefix}approval_queue', + array( + 'id_msg' => 'int', + ), + array( + $msgOptions['id'], + ), + array() + ); + } + + // Mark inserted topic as read (only for the user calling this function). + if (!empty($topicOptions['mark_as_read']) && !$user_info['is_guest']) + { + // Since it's likely they *read* it before replying, let's try an UPDATE first. + if (!$new_topic) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_topics + SET id_msg = {int:id_msg} + WHERE id_member = {int:current_member} + AND id_topic = {int:id_topic}', + array( + 'current_member' => $posterOptions['id'], + 'id_msg' => $msgOptions['id'], + 'id_topic' => $topicOptions['id'], + ) + ); + + $flag = $smcFunc['db_affected_rows']() != 0; + } + + if (empty($flag)) + { + $smcFunc['db_insert']('ignore', + '{db_prefix}log_topics', + array('id_topic' => 'int', 'id_member' => 'int', 'id_msg' => 'int'), + array($topicOptions['id'], $posterOptions['id'], $msgOptions['id']), + array('id_topic', 'id_member') + ); + } + } + + // If there's a custom search index, it needs updating... + if (!empty($modSettings['search_custom_index_config'])) + { + $customIndexSettings = unserialize($modSettings['search_custom_index_config']); + + $inserts = array(); + foreach (text2words($msgOptions['body'], $customIndexSettings['bytes_per_word'], true) as $word) + $inserts[] = array($word, $msgOptions['id']); + + if (!empty($inserts)) + $smcFunc['db_insert']('ignore', + '{db_prefix}log_search_words', + array('id_word' => 'int', 'id_msg' => 'int'), + $inserts, + array('id_word', 'id_msg') + ); + } + + // Increase the post counter for the user that created the post. + if (!empty($posterOptions['update_post_count']) && !empty($posterOptions['id']) && $msgOptions['approved']) + { + // Are you the one that happened to create this post? + if ($user_info['id'] == $posterOptions['id']) + $user_info['posts']++; + updateMemberData($posterOptions['id'], array('posts' => '+')); + } + + // They've posted, so they can make the view count go up one if they really want. (this is to keep views >= replies...) + $_SESSION['last_read_topic'] = 0; + + // Better safe than sorry. + if (isset($_SESSION['topicseen_cache'][$topicOptions['board']])) + $_SESSION['topicseen_cache'][$topicOptions['board']]--; + + // Update all the stats so everyone knows about this new topic and message. + updateStats('message', true, $msgOptions['id']); + + // Update the last message on the board assuming it's approved AND the topic is. + if ($msgOptions['approved']) + updateLastMessages($topicOptions['board'], $new_topic || !empty($topicOptions['is_approved']) ? $msgOptions['id'] : 0); + + // Alright, done now... we can abort now, I guess... at least this much is done. + ignore_user_abort($previous_ignore_user_abort); + + // Success. + return true; +} + +// !!! +function createAttachment(&$attachmentOptions) +{ + global $modSettings, $sourcedir, $smcFunc, $context; + + require_once($sourcedir . '/Subs-Graphics.php'); + + // We need to know where this thing is going. + if (!empty($modSettings['currentAttachmentUploadDir'])) + { + if (!is_array($modSettings['attachmentUploadDir'])) + $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); + + // Just use the current path for temp files. + $attach_dir = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']]; + $id_folder = $modSettings['currentAttachmentUploadDir']; + } + else + { + $attach_dir = $modSettings['attachmentUploadDir']; + $id_folder = 1; + } + + $attachmentOptions['errors'] = array(); + if (!isset($attachmentOptions['post'])) + $attachmentOptions['post'] = 0; + if (!isset($attachmentOptions['approved'])) + $attachmentOptions['approved'] = 1; + + $already_uploaded = preg_match('~^post_tmp_' . $attachmentOptions['poster'] . '_\d+$~', $attachmentOptions['tmp_name']) != 0; + $file_restricted = @ini_get('open_basedir') != '' && !$already_uploaded; + + if ($already_uploaded) + $attachmentOptions['tmp_name'] = $attach_dir . '/' . $attachmentOptions['tmp_name']; + + // Make sure the file actually exists... sometimes it doesn't. + if ((!$file_restricted && !file_exists($attachmentOptions['tmp_name'])) || (!$already_uploaded && !is_uploaded_file($attachmentOptions['tmp_name']))) + { + $attachmentOptions['errors'] = array('could_not_upload'); + return false; + } + + // These are the only valid image types for SMF. + $validImageTypes = array( + 1 => 'gif', + 2 => 'jpeg', + 3 => 'png', + 5 => 'psd', + 6 => 'bmp', + 7 => 'tiff', + 8 => 'tiff', + 9 => 'jpeg', + 14 => 'iff' + ); + + if (!$file_restricted || $already_uploaded) + { + $size = @getimagesize($attachmentOptions['tmp_name']); + list ($attachmentOptions['width'], $attachmentOptions['height']) = $size; + + // If it's an image get the mime type right. + if (empty($attachmentOptions['mime_type']) && $attachmentOptions['width']) + { + // Got a proper mime type? + if (!empty($size['mime'])) + $attachmentOptions['mime_type'] = $size['mime']; + // Otherwise a valid one? + elseif (isset($validImageTypes[$size[2]])) + $attachmentOptions['mime_type'] = 'image/' . $validImageTypes[$size[2]]; + } + } + + // It is possible we might have a MIME type that isn't actually an image but still have a size. + // For example, Shockwave files will be able to return size but be 'application/shockwave' or similar. + if (!empty($attachmentOptions['mime_type']) && strpos($attachmentOptions['mime_type'], 'image/') !== 0) + { + $attachmentOptions['width'] = 0; + $attachmentOptions['height'] = 0; + } + + // Get the hash if no hash has been given yet. + if (empty($attachmentOptions['file_hash'])) + $attachmentOptions['file_hash'] = getAttachmentFilename($attachmentOptions['name'], false, null, true); + + // Is the file too big? + if (!empty($modSettings['attachmentSizeLimit']) && $attachmentOptions['size'] > $modSettings['attachmentSizeLimit'] * 1024) + $attachmentOptions['errors'][] = 'too_large'; + + if (!empty($modSettings['attachmentCheckExtensions'])) + { + $allowed = explode(',', strtolower($modSettings['attachmentExtensions'])); + foreach ($allowed as $k => $dummy) + $allowed[$k] = trim($dummy); + + if (!in_array(strtolower(substr(strrchr($attachmentOptions['name'], '.'), 1)), $allowed)) + $attachmentOptions['errors'][] = 'bad_extension'; + } + + if (!empty($modSettings['attachmentDirSizeLimit'])) + { + // Make sure the directory isn't full. + $dirSize = 0; + $dir = @opendir($attach_dir) or fatal_lang_error('cant_access_upload_path', 'critical'); + while ($file = readdir($dir)) + { + if ($file == '.' || $file == '..') + continue; + + if (preg_match('~^post_tmp_\d+_\d+$~', $file) != 0) + { + // Temp file is more than 5 hours old! + if (filemtime($attach_dir . '/' . $file) < time() - 18000) + @unlink($attach_dir . '/' . $file); + continue; + } + + $dirSize += filesize($attach_dir . '/' . $file); + } + closedir($dir); + + // Too big! Maybe you could zip it or something... + if ($attachmentOptions['size'] + $dirSize > $modSettings['attachmentDirSizeLimit'] * 1024) + $attachmentOptions['errors'][] = 'directory_full'; + // Soon to be too big - warn the admins... + elseif (!isset($modSettings['attachment_full_notified']) && $modSettings['attachmentDirSizeLimit'] > 4000 && $attachmentOptions['size'] + $dirSize > ($modSettings['attachmentDirSizeLimit'] - 2000) * 1024) + { + require_once($sourcedir . '/Subs-Admin.php'); + emailAdmins('admin_attachments_full'); + updateSettings(array('attachment_full_notified' => 1)); + } + } + + // Check if the file already exists.... (for those who do not encrypt their filenames...) + if (empty($modSettings['attachmentEncryptFilenames'])) + { + // Make sure they aren't trying to upload a nasty file. + $disabledFiles = array('con', 'com1', 'com2', 'com3', 'com4', 'prn', 'aux', 'lpt1', '.htaccess', 'index.php'); + if (in_array(strtolower(basename($attachmentOptions['name'])), $disabledFiles)) + $attachmentOptions['errors'][] = 'bad_filename'; + + // Check if there's another file with that name... + $request = $smcFunc['db_query']('', ' + SELECT id_attach + FROM {db_prefix}attachments + WHERE filename = {string:filename} + LIMIT 1', + array( + 'filename' => strtolower($attachmentOptions['name']), + ) + ); + if ($smcFunc['db_num_rows']($request) > 0) + $attachmentOptions['errors'][] = 'taken_filename'; + $smcFunc['db_free_result']($request); + } + + if (!empty($attachmentOptions['errors'])) + return false; + + if (!is_writable($attach_dir)) + fatal_lang_error('attachments_no_write', 'critical'); + + // Assuming no-one set the extension let's take a look at it. + if (empty($attachmentOptions['fileext'])) + { + $attachmentOptions['fileext'] = strtolower(strrpos($attachmentOptions['name'], '.') !== false ? substr($attachmentOptions['name'], strrpos($attachmentOptions['name'], '.') + 1) : ''); + if (strlen($attachmentOptions['fileext']) > 8 || '.' . $attachmentOptions['fileext'] == $attachmentOptions['name']) + $attachmentOptions['fileext'] = ''; + } + + $smcFunc['db_insert']('', + '{db_prefix}attachments', + array( + 'id_folder' => 'int', 'id_msg' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8', + 'size' => 'int', 'width' => 'int', 'height' => 'int', + 'mime_type' => 'string-20', 'approved' => 'int', + ), + array( + $id_folder, (int) $attachmentOptions['post'], $attachmentOptions['name'], $attachmentOptions['file_hash'], $attachmentOptions['fileext'], + (int) $attachmentOptions['size'], (empty($attachmentOptions['width']) ? 0 : (int) $attachmentOptions['width']), (empty($attachmentOptions['height']) ? '0' : (int) $attachmentOptions['height']), + (!empty($attachmentOptions['mime_type']) ? $attachmentOptions['mime_type'] : ''), (int) $attachmentOptions['approved'], + ), + array('id_attach') + ); + $attachmentOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach'); + + if (empty($attachmentOptions['id'])) + return false; + + // If it's not approved add to the approval queue. + if (!$attachmentOptions['approved']) + $smcFunc['db_insert']('', + '{db_prefix}approval_queue', + array( + 'id_attach' => 'int', 'id_msg' => 'int', + ), + array( + $attachmentOptions['id'], (int) $attachmentOptions['post'], + ), + array() + ); + + $attachmentOptions['destination'] = getAttachmentFilename(basename($attachmentOptions['name']), $attachmentOptions['id'], $id_folder, false, $attachmentOptions['file_hash']); + + if ($already_uploaded) + rename($attachmentOptions['tmp_name'], $attachmentOptions['destination']); + elseif (!move_uploaded_file($attachmentOptions['tmp_name'], $attachmentOptions['destination'])) + fatal_lang_error('attach_timeout', 'critical'); + + // Attempt to chmod it. + @chmod($attachmentOptions['destination'], 0644); + + $size = @getimagesize($attachmentOptions['destination']); + list ($attachmentOptions['width'], $attachmentOptions['height']) = empty($size) ? array(null, null, null) : $size; + + // We couldn't access the file before... + if ($file_restricted) + { + // Have a go at getting the right mime type. + if (empty($attachmentOptions['mime_type']) && $attachmentOptions['width']) + { + if (!empty($size['mime'])) + $attachmentOptions['mime_type'] = $size['mime']; + elseif (isset($validImageTypes[$size[2]])) + $attachmentOptions['mime_type'] = 'image/' . $validImageTypes[$size[2]]; + } + + // It is possible we might have a MIME type that isn't actually an image but still have a size. + // For example, Shockwave files will be able to return size but be 'application/shockwave' or similar. + if (!empty($attachmentOptions['mime_type']) && strpos($attachmentOptions['mime_type'], 'image/') !== 0) + { + $attachmentOptions['width'] = 0; + $attachmentOptions['height'] = 0; + } + + if (!empty($attachmentOptions['width']) && !empty($attachmentOptions['height'])) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}attachments + SET + width = {int:width}, + height = {int:height}, + mime_type = {string:mime_type} + WHERE id_attach = {int:id_attach}', + array( + 'width' => (int) $attachmentOptions['width'], + 'height' => (int) $attachmentOptions['height'], + 'id_attach' => $attachmentOptions['id'], + 'mime_type' => empty($attachmentOptions['mime_type']) ? '' : $attachmentOptions['mime_type'], + ) + ); + } + + // Security checks for images + // Do we have an image? If yes, we need to check it out! + if (isset($validImageTypes[$size[2]])) + { + if (!checkImageContents($attachmentOptions['destination'], !empty($modSettings['attachment_image_paranoid']))) + { + // It's bad. Last chance, maybe we can re-encode it? + if (empty($modSettings['attachment_image_reencode']) || (!reencodeImage($attachmentOptions['destination'], $size[2]))) + { + // Nothing to do: not allowed or not successful re-encoding it. + require_once($sourcedir . '/ManageAttachments.php'); + removeAttachments(array( + 'id_attach' => $attachmentOptions['id'] + )); + $attachmentOptions['id'] = null; + $attachmentOptions['errors'][] = 'bad_attachment'; + + return false; + } + // Success! However, successes usually come for a price: + // we might get a new format for our image... + $old_format = $size[2]; + $size = @getimagesize($attachmentOptions['destination']); + if (!(empty($size)) && ($size[2] != $old_format)) + { + // Let's update the image information + // !!! This is becoming a mess: we keep coming back and update the database, + // instead of getting it right the first time. + if (isset($validImageTypes[$size[2]])) + { + $attachmentOptions['mime_type'] = 'image/' . $validImageTypes[$size[2]]; + $smcFunc['db_query']('', ' + UPDATE {db_prefix}attachments + SET + mime_type = {string:mime_type} + WHERE id_attach = {int:id_attach}', + array( + 'id_attach' => $attachmentOptions['id'], + 'mime_type' => $attachmentOptions['mime_type'], + ) + ); + } + } + } + } + + if (!empty($attachmentOptions['skip_thumbnail']) || (empty($attachmentOptions['width']) && empty($attachmentOptions['height']))) + return true; + + // Like thumbnails, do we? + if (!empty($modSettings['attachmentThumbnails']) && !empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachmentOptions['width'] > $modSettings['attachmentThumbWidth'] || $attachmentOptions['height'] > $modSettings['attachmentThumbHeight'])) + { + if (createThumbnail($attachmentOptions['destination'], $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight'])) + { + // Figure out how big we actually made it. + $size = @getimagesize($attachmentOptions['destination'] . '_thumb'); + list ($thumb_width, $thumb_height) = $size; + + if (!empty($size['mime'])) + $thumb_mime = $size['mime']; + elseif (isset($validImageTypes[$size[2]])) + $thumb_mime = 'image/' . $validImageTypes[$size[2]]; + // Lord only knows how this happened... + else + $thumb_mime = ''; + + $thumb_filename = $attachmentOptions['name'] . '_thumb'; + $thumb_size = filesize($attachmentOptions['destination'] . '_thumb'); + $thumb_file_hash = getAttachmentFilename($thumb_filename, false, null, true); + + // To the database we go! + $smcFunc['db_insert']('', + '{db_prefix}attachments', + array( + 'id_folder' => 'int', 'id_msg' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8', + 'size' => 'int', 'width' => 'int', 'height' => 'int', 'mime_type' => 'string-20', 'approved' => 'int', + ), + array( + $id_folder, (int) $attachmentOptions['post'], 3, $thumb_filename, $thumb_file_hash, $attachmentOptions['fileext'], + $thumb_size, $thumb_width, $thumb_height, $thumb_mime, (int) $attachmentOptions['approved'], + ), + array('id_attach') + ); + $attachmentOptions['thumb'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach'); + + if (!empty($attachmentOptions['thumb'])) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}attachments + SET id_thumb = {int:id_thumb} + WHERE id_attach = {int:id_attach}', + array( + 'id_thumb' => $attachmentOptions['thumb'], + 'id_attach' => $attachmentOptions['id'], + ) + ); + + rename($attachmentOptions['destination'] . '_thumb', getAttachmentFilename($thumb_filename, $attachmentOptions['thumb'], $id_folder, false, $thumb_file_hash)); + } + } + } + + return true; +} + +// !!! +function modifyPost(&$msgOptions, &$topicOptions, &$posterOptions) +{ + global $user_info, $modSettings, $smcFunc, $context; + + $topicOptions['poll'] = isset($topicOptions['poll']) ? (int) $topicOptions['poll'] : null; + $topicOptions['lock_mode'] = isset($topicOptions['lock_mode']) ? $topicOptions['lock_mode'] : null; + $topicOptions['sticky_mode'] = isset($topicOptions['sticky_mode']) ? $topicOptions['sticky_mode'] : null; + + // This is longer than it has to be, but makes it so we only set/change what we have to. + $messages_columns = array(); + if (isset($posterOptions['name'])) + $messages_columns['poster_name'] = $posterOptions['name']; + if (isset($posterOptions['email'])) + $messages_columns['poster_email'] = $posterOptions['email']; + if (isset($msgOptions['icon'])) + $messages_columns['icon'] = $msgOptions['icon']; + if (isset($msgOptions['subject'])) + $messages_columns['subject'] = $msgOptions['subject']; + if (isset($msgOptions['body'])) + { + $messages_columns['body'] = $msgOptions['body']; + + if (!empty($modSettings['search_custom_index_config'])) + { + $request = $smcFunc['db_query']('', ' + SELECT body + FROM {db_prefix}messages + WHERE id_msg = {int:id_msg}', + array( + 'id_msg' => $msgOptions['id'], + ) + ); + list ($old_body) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + } + if (!empty($msgOptions['modify_time'])) + { + $messages_columns['modified_time'] = $msgOptions['modify_time']; + $messages_columns['modified_name'] = $msgOptions['modify_name']; + $messages_columns['id_msg_modified'] = $modSettings['maxMsgID']; + } + if (isset($msgOptions['smileys_enabled'])) + $messages_columns['smileys_enabled'] = empty($msgOptions['smileys_enabled']) ? 0 : 1; + + // Which columns need to be ints? + $messageInts = array('modified_time', 'id_msg_modified', 'smileys_enabled'); + $update_parameters = array( + 'id_msg' => $msgOptions['id'], + ); + + foreach ($messages_columns as $var => $val) + { + $messages_columns[$var] = $var . ' = {' . (in_array($var, $messageInts) ? 'int' : 'string') . ':var_' . $var . '}'; + $update_parameters['var_' . $var] = $val; + } + + // Nothing to do? + if (empty($messages_columns)) + return true; + + // Change the post. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET ' . implode(', ', $messages_columns) . ' + WHERE id_msg = {int:id_msg}', + $update_parameters + ); + + // Lock and or sticky the post. + if ($topicOptions['sticky_mode'] !== null || $topicOptions['lock_mode'] !== null || $topicOptions['poll'] !== null) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET + is_sticky = {raw:is_sticky}, + locked = {raw:locked}, + id_poll = {raw:id_poll} + WHERE id_topic = {int:id_topic}', + array( + 'is_sticky' => $topicOptions['sticky_mode'] === null ? 'is_sticky' : (int) $topicOptions['sticky_mode'], + 'locked' => $topicOptions['lock_mode'] === null ? 'locked' : (int) $topicOptions['lock_mode'], + 'id_poll' => $topicOptions['poll'] === null ? 'id_poll' : (int) $topicOptions['poll'], + 'id_topic' => $topicOptions['id'], + ) + ); + } + + // Mark the edited post as read. + if (!empty($topicOptions['mark_as_read']) && !$user_info['is_guest']) + { + // Since it's likely they *read* it before editing, let's try an UPDATE first. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_topics + SET id_msg = {int:id_msg} + WHERE id_member = {int:current_member} + AND id_topic = {int:id_topic}', + array( + 'current_member' => $user_info['id'], + 'id_msg' => $modSettings['maxMsgID'], + 'id_topic' => $topicOptions['id'], + ) + ); + + $flag = $smcFunc['db_affected_rows']() != 0; + + if (empty($flag)) + { + $smcFunc['db_insert']('ignore', + '{db_prefix}log_topics', + array('id_topic' => 'int', 'id_member' => 'int', 'id_msg' => 'int'), + array($topicOptions['id'], $user_info['id'], $modSettings['maxMsgID']), + array('id_topic', 'id_member') + ); + } + } + + // If there's a custom search index, it needs to be modified... + if (isset($msgOptions['body']) && !empty($modSettings['search_custom_index_config'])) + { + $customIndexSettings = unserialize($modSettings['search_custom_index_config']); + + $stopwords = empty($modSettings['search_stopwords']) ? array() : explode(',', $modSettings['search_stopwords']); + $old_index = text2words($old_body, $customIndexSettings['bytes_per_word'], true); + $new_index = text2words($msgOptions['body'], $customIndexSettings['bytes_per_word'], true); + + // Calculate the words to be added and removed from the index. + $removed_words = array_diff(array_diff($old_index, $new_index), $stopwords); + $inserted_words = array_diff(array_diff($new_index, $old_index), $stopwords); + // Delete the removed words AND the added ones to avoid key constraints. + if (!empty($removed_words)) + { + $removed_words = array_merge($removed_words, $inserted_words); + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_search_words + WHERE id_msg = {int:id_msg} + AND id_word IN ({array_int:removed_words})', + array( + 'removed_words' => $removed_words, + 'id_msg' => $msgOptions['id'], + ) + ); + } + + // Add the new words to be indexed. + if (!empty($inserted_words)) + { + $inserts = array(); + foreach ($inserted_words as $word) + $inserts[] = array($word, $msgOptions['id']); + $smcFunc['db_insert']('insert', + '{db_prefix}log_search_words', + array('id_word' => 'string', 'id_msg' => 'int'), + $inserts, + array('id_word', 'id_msg') + ); + } + } + + if (isset($msgOptions['subject'])) + { + // Only update the subject if this was the first message in the topic. + $request = $smcFunc['db_query']('', ' + SELECT id_topic + FROM {db_prefix}topics + WHERE id_first_msg = {int:id_first_msg} + LIMIT 1', + array( + 'id_first_msg' => $msgOptions['id'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 1) + updateStats('subject', $topicOptions['id'], $msgOptions['subject']); + $smcFunc['db_free_result']($request); + } + + // Finally, if we are setting the approved state we need to do much more work :( + if ($modSettings['postmod_active'] && isset($msgOptions['approved'])) + approvePosts($msgOptions['id'], $msgOptions['approved']); + + return true; +} + +// Approve (or not) some posts... without permission checks... +function approvePosts($msgs, $approve = true) +{ + global $sourcedir, $smcFunc; + + if (!is_array($msgs)) + $msgs = array($msgs); + + if (empty($msgs)) + return false; + + // May as well start at the beginning, working out *what* we need to change. + $request = $smcFunc['db_query']('', ' + SELECT m.id_msg, m.approved, m.id_topic, m.id_board, t.id_first_msg, t.id_last_msg, + m.body, m.subject, IFNULL(mem.real_name, m.poster_name) AS poster_name, m.id_member, + t.approved AS topic_approved, b.count_posts + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) + WHERE m.id_msg IN ({array_int:message_list}) + AND m.approved = {int:approved_state}', + array( + 'message_list' => $msgs, + 'approved_state' => $approve ? 0 : 1, + ) + ); + $msgs = array(); + $topics = array(); + $topic_changes = array(); + $board_changes = array(); + $notification_topics = array(); + $notification_posts = array(); + $member_post_changes = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Easy... + $msgs[] = $row['id_msg']; + $topics[] = $row['id_topic']; + + // Ensure our change array exists already. + if (!isset($topic_changes[$row['id_topic']])) + $topic_changes[$row['id_topic']] = array( + 'id_last_msg' => $row['id_last_msg'], + 'approved' => $row['topic_approved'], + 'replies' => 0, + 'unapproved_posts' => 0, + ); + if (!isset($board_changes[$row['id_board']])) + $board_changes[$row['id_board']] = array( + 'posts' => 0, + 'topics' => 0, + 'unapproved_posts' => 0, + 'unapproved_topics' => 0, + ); + + // If it's the first message then the topic state changes! + if ($row['id_msg'] == $row['id_first_msg']) + { + $topic_changes[$row['id_topic']]['approved'] = $approve ? 1 : 0; + + $board_changes[$row['id_board']]['unapproved_topics'] += $approve ? -1 : 1; + $board_changes[$row['id_board']]['topics'] += $approve ? 1 : -1; + + // Note we need to ensure we announce this topic! + $notification_topics[] = array( + 'body' => $row['body'], + 'subject' => $row['subject'], + 'name' => $row['poster_name'], + 'board' => $row['id_board'], + 'topic' => $row['id_topic'], + 'msg' => $row['id_first_msg'], + 'poster' => $row['id_member'], + ); + } + else + { + $topic_changes[$row['id_topic']]['replies'] += $approve ? 1 : -1; + + // This will be a post... but don't notify unless it's not followed by approved ones. + if ($row['id_msg'] > $row['id_last_msg']) + $notification_posts[$row['id_topic']][] = array( + 'id' => $row['id_msg'], + 'body' => $row['body'], + 'subject' => $row['subject'], + 'name' => $row['poster_name'], + 'topic' => $row['id_topic'], + ); + } + + // If this is being approved and id_msg is higher than the current id_last_msg then it changes. + if ($approve && $row['id_msg'] > $topic_changes[$row['id_topic']]['id_last_msg']) + $topic_changes[$row['id_topic']]['id_last_msg'] = $row['id_msg']; + // If this is being unapproved, and it's equal to the id_last_msg we need to find a new one! + elseif (!$approve) + // Default to the first message and then we'll override in a bit ;) + $topic_changes[$row['id_topic']]['id_last_msg'] = $row['id_first_msg']; + + $topic_changes[$row['id_topic']]['unapproved_posts'] += $approve ? -1 : 1; + $board_changes[$row['id_board']]['unapproved_posts'] += $approve ? -1 : 1; + $board_changes[$row['id_board']]['posts'] += $approve ? 1 : -1; + + // Post count for the user? + if ($row['id_member'] && empty($row['count_posts'])) + $member_post_changes[$row['id_member']] = isset($member_post_changes[$row['id_member']]) ? $member_post_changes[$row['id_member']] + 1 : 1; + } + $smcFunc['db_free_result']($request); + + if (empty($msgs)) + return; + + // Now we have the differences make the changes, first the easy one. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}messages + SET approved = {int:approved_state} + WHERE id_msg IN ({array_int:message_list})', + array( + 'message_list' => $msgs, + 'approved_state' => $approve ? 1 : 0, + ) + ); + + // If we were unapproving find the last msg in the topics... + if (!$approve) + { + $request = $smcFunc['db_query']('', ' + SELECT id_topic, MAX(id_msg) AS id_last_msg + FROM {db_prefix}messages + WHERE id_topic IN ({array_int:topic_list}) + AND approved = {int:approved} + GROUP BY id_topic', + array( + 'topic_list' => $topics, + 'approved' => 1, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $topic_changes[$row['id_topic']]['id_last_msg'] = $row['id_last_msg']; + $smcFunc['db_free_result']($request); + } + + // ... next the topics... + foreach ($topic_changes as $id => $changes) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}topics + SET approved = {int:approved}, unapproved_posts = unapproved_posts + {int:unapproved_posts}, + num_replies = num_replies + {int:num_replies}, id_last_msg = {int:id_last_msg} + WHERE id_topic = {int:id_topic}', + array( + 'approved' => $changes['approved'], + 'unapproved_posts' => $changes['unapproved_posts'], + 'num_replies' => $changes['replies'], + 'id_last_msg' => $changes['id_last_msg'], + 'id_topic' => $id, + ) + ); + + // ... finally the boards... + foreach ($board_changes as $id => $changes) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET num_posts = num_posts + {int:num_posts}, unapproved_posts = unapproved_posts + {int:unapproved_posts}, + num_topics = num_topics + {int:num_topics}, unapproved_topics = unapproved_topics + {int:unapproved_topics} + WHERE id_board = {int:id_board}', + array( + 'num_posts' => $changes['posts'], + 'unapproved_posts' => $changes['unapproved_posts'], + 'num_topics' => $changes['topics'], + 'unapproved_topics' => $changes['unapproved_topics'], + 'id_board' => $id, + ) + ); + + // Finally, least importantly, notifications! + if ($approve) + { + if (!empty($notification_topics)) + { + require_once($sourcedir . '/Post.php'); + notifyMembersBoard($notification_topics); + } + if (!empty($notification_posts)) + sendApprovalNotifications($notification_posts); + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}approval_queue + WHERE id_msg IN ({array_int:message_list}) + AND id_attach = {int:id_attach}', + array( + 'message_list' => $msgs, + 'id_attach' => 0, + ) + ); + } + // If unapproving add to the approval queue! + else + { + $msgInserts = array(); + foreach ($msgs as $msg) + $msgInserts[] = array($msg); + + $smcFunc['db_insert']('ignore', + '{db_prefix}approval_queue', + array('id_msg' => 'int'), + $msgInserts, + array('id_msg') + ); + } + + // Update the last messages on the boards... + updateLastMessages(array_keys($board_changes)); + + // Post count for the members? + if (!empty($member_post_changes)) + foreach ($member_post_changes as $id_member => $count_change) + updateMemberData($id_member, array('posts' => 'posts ' . ($approve ? '+' : '-') . ' ' . $count_change)); + + return true; +} + +// Approve topics? +function approveTopics($topics, $approve = true) +{ + global $smcFunc; + + if (!is_array($topics)) + $topics = array($topics); + + if (empty($topics)) + return false; + + $approve_type = $approve ? 0 : 1; + + // Just get the messages to be approved and pass through... + $request = $smcFunc['db_query']('', ' + SELECT id_msg + FROM {db_prefix}messages + WHERE id_topic IN ({array_int:topic_list}) + AND approved = {int:approve_type}', + array( + 'topic_list' => $topics, + 'approve_type' => $approve_type, + ) + ); + $msgs = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $msgs[] = $row['id_msg']; + $smcFunc['db_free_result']($request); + + return approvePosts($msgs, $approve); +} + +// A special function for handling the hell which is sending approval notifications. +function sendApprovalNotifications(&$topicData) +{ + global $txt, $scripturl, $language, $user_info; + global $modSettings, $sourcedir, $context, $smcFunc; + + // Clean up the data... + if (!is_array($topicData) || empty($topicData)) + return; + + $topics = array(); + $digest_insert = array(); + foreach ($topicData as $topic => $msgs) + foreach ($msgs as $msgKey => $msg) + { + censorText($topicData[$topic][$msgKey]['subject']); + censorText($topicData[$topic][$msgKey]['body']); + $topicData[$topic][$msgKey]['subject'] = un_htmlspecialchars($topicData[$topic][$msgKey]['subject']); + $topicData[$topic][$msgKey]['body'] = trim(un_htmlspecialchars(strip_tags(strtr(parse_bbc($topicData[$topic][$msgKey]['body'], false), array('
          ' => "\n", '' => "\n", '' => "\n", '[' => '[', ']' => ']'))))); + + $topics[] = $msg['id']; + $digest_insert[] = array($msg['topic'], $msg['id'], 'reply', $user_info['id']); + } + + // These need to go into the digest too... + $smcFunc['db_insert']('', + '{db_prefix}log_digest', + array( + 'id_topic' => 'int', 'id_msg' => 'int', 'note_type' => 'string', 'exclude' => 'int', + ), + $digest_insert, + array() + ); + + // Find everyone who needs to know about this. + $members = $smcFunc['db_query']('', ' + SELECT + mem.id_member, mem.email_address, mem.notify_regularity, mem.notify_types, mem.notify_send_body, mem.lngfile, + ln.sent, mem.id_group, mem.additional_groups, b.member_groups, mem.id_post_group, t.id_member_started, + ln.id_topic + FROM {db_prefix}log_notify AS ln + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member) + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ln.id_topic) + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + WHERE ln.id_topic IN ({array_int:topic_list}) + AND mem.is_activated = {int:is_activated} + AND mem.notify_types < {int:notify_types} + AND mem.notify_regularity < {int:notify_regularity} + GROUP BY mem.id_member, ln.id_topic, mem.email_address, mem.notify_regularity, mem.notify_types, mem.notify_send_body, mem.lngfile, ln.sent, mem.id_group, mem.additional_groups, b.member_groups, mem.id_post_group, t.id_member_started + ORDER BY mem.lngfile', + array( + 'topic_list' => $topics, + 'is_activated' => 1, + 'notify_types' => 4, + 'notify_regularity' => 2, + ) + ); + $sent = 0; + while ($row = $smcFunc['db_fetch_assoc']($members)) + { + if ($row['id_group'] != 1) + { + $allowed = explode(',', $row['member_groups']); + $row['additional_groups'] = explode(',', $row['additional_groups']); + $row['additional_groups'][] = $row['id_group']; + $row['additional_groups'][] = $row['id_post_group']; + + if (count(array_intersect($allowed, $row['additional_groups'])) == 0) + continue; + } + + $needed_language = empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']; + if (empty($current_language) || $current_language != $needed_language) + $current_language = loadLanguage('Post', $needed_language, false); + + $sent_this_time = false; + // Now loop through all the messages to send. + foreach ($topicData[$row['id_topic']] as $msg) + { + $replacements = array( + 'TOPICSUBJECT' => $topicData[$row['id_topic']]['subject'], + 'POSTERNAME' => un_htmlspecialchars($topicData[$row['id_topic']]['name']), + 'TOPICLINK' => $scripturl . '?topic=' . $row['id_topic'] . '.new;topicseen#new', + 'UNSUBSCRIBELINK' => $scripturl . '?action=notify;topic=' . $row['id_topic'] . '.0', + ); + + $message_type = 'notification_reply'; + // Do they want the body of the message sent too? + if (!empty($row['notify_send_body']) && empty($modSettings['disallow_sendBody'])) + { + $message_type .= '_body'; + $replacements['BODY'] = $topicData[$row['id_topic']]['body']; + } + if (!empty($row['notify_regularity'])) + $message_type .= '_once'; + + // Send only if once is off or it's on and it hasn't been sent. + if (empty($row['notify_regularity']) || (empty($row['sent']) && !$sent_this_time)) + { + $emaildata = loadEmailTemplate($message_type, $replacements, $needed_language); + sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, 'm' . $topicData[$row['id_topic']]['last_id']); + $sent++; + } + + $sent_this_time = true; + } + } + $smcFunc['db_free_result']($members); + + if (isset($current_language) && $current_language != $user_info['language']) + loadLanguage('Post'); + + // Sent! + if (!empty($sent)) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_notify + SET sent = {int:is_sent} + WHERE id_topic IN ({array_int:topic_list}) + AND id_member != {int:current_member}', + array( + 'current_member' => $user_info['id'], + 'topic_list' => $topics, + 'is_sent' => 1, + ) + ); +} + +// Update the last message in a board, and its parents. +function updateLastMessages($setboards, $id_msg = 0) +{ + global $board_info, $board, $modSettings, $smcFunc; + + // Please - let's be sane. + if (empty($setboards)) + return false; + + if (!is_array($setboards)) + $setboards = array($setboards); + + // If we don't know the id_msg we need to find it. + if (!$id_msg) + { + // Find the latest message on this board (highest id_msg.) + $request = $smcFunc['db_query']('', ' + SELECT id_board, MAX(id_last_msg) AS id_msg + FROM {db_prefix}topics + WHERE id_board IN ({array_int:board_list}) + AND approved = {int:approved} + GROUP BY id_board', + array( + 'board_list' => $setboards, + 'approved' => 1, + ) + ); + $lastMsg = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $lastMsg[$row['id_board']] = $row['id_msg']; + $smcFunc['db_free_result']($request); + } + else + { + // Just to note - there should only be one board passed if we are doing this. + foreach ($setboards as $id_board) + $lastMsg[$id_board] = $id_msg; + } + + $parent_boards = array(); + // Keep track of last modified dates. + $lastModified = $lastMsg; + // Get all the child boards for the parents, if they have some... + foreach ($setboards as $id_board) + { + if (!isset($lastMsg[$id_board])) + { + $lastMsg[$id_board] = 0; + $lastModified[$id_board] = 0; + } + + if (!empty($board) && $id_board == $board) + $parents = $board_info['parent_boards']; + else + $parents = getBoardParents($id_board); + + // Ignore any parents on the top child level. + //!!! Why? + foreach ($parents as $id => $parent) + { + if ($parent['level'] != 0) + { + // If we're already doing this one as a board, is this a higher last modified? + if (isset($lastModified[$id]) && $lastModified[$id_board] > $lastModified[$id]) + $lastModified[$id] = $lastModified[$id_board]; + elseif (!isset($lastModified[$id]) && (!isset($parent_boards[$id]) || $parent_boards[$id] < $lastModified[$id_board])) + $parent_boards[$id] = $lastModified[$id_board]; + } + } + } + + // Note to help understand what is happening here. For parents we update the timestamp of the last message for determining + // whether there are child boards which have not been read. For the boards themselves we update both this and id_last_msg. + + $board_updates = array(); + $parent_updates = array(); + // Finally, to save on queries make the changes... + foreach ($parent_boards as $id => $msg) + { + if (!isset($parent_updates[$msg])) + $parent_updates[$msg] = array($id); + else + $parent_updates[$msg][] = $id; + } + + foreach ($lastMsg as $id => $msg) + { + if (!isset($board_updates[$msg . '-' . $lastModified[$id]])) + $board_updates[$msg . '-' . $lastModified[$id]] = array( + 'id' => $msg, + 'updated' => $lastModified[$id], + 'boards' => array($id) + ); + + else + $board_updates[$msg . '-' . $lastModified[$id]]['boards'][] = $id; + } + + // Now commit the changes! + foreach ($parent_updates as $id_msg => $boards) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET id_msg_updated = {int:id_msg_updated} + WHERE id_board IN ({array_int:board_list}) + AND id_msg_updated < {int:id_msg_updated}', + array( + 'board_list' => $boards, + 'id_msg_updated' => $id_msg, + ) + ); + } + foreach ($board_updates as $board_data) + { + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET id_last_msg = {int:id_last_msg}, id_msg_updated = {int:id_msg_updated} + WHERE id_board IN ({array_int:board_list})', + array( + 'board_list' => $board_data['boards'], + 'id_last_msg' => $board_data['id'], + 'id_msg_updated' => $board_data['updated'], + ) + ); + } +} + +// This simple function gets a list of all administrators and sends them an email to let them know a new member has joined. +function adminNotify($type, $memberID, $member_name = null) +{ + global $txt, $modSettings, $language, $scripturl, $user_info, $context, $smcFunc; + + // If the setting isn't enabled then just exit. + if (empty($modSettings['notify_new_registration'])) + return; + + if ($member_name == null) + { + // Get the new user's name.... + $request = $smcFunc['db_query']('', ' + SELECT real_name + FROM {db_prefix}members + WHERE id_member = {int:id_member} + LIMIT 1', + array( + 'id_member' => $memberID, + ) + ); + list ($member_name) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + $toNotify = array(); + $groups = array(); + + // All membergroups who can approve members. + $request = $smcFunc['db_query']('', ' + SELECT id_group + FROM {db_prefix}permissions + WHERE permission = {string:moderate_forum} + AND add_deny = {int:add_deny} + AND id_group != {int:id_group}', + array( + 'add_deny' => 1, + 'id_group' => 0, + 'moderate_forum' => 'moderate_forum', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $groups[] = $row['id_group']; + $smcFunc['db_free_result']($request); + + // Add administrators too... + $groups[] = 1; + $groups = array_unique($groups); + + // Get a list of all members who have ability to approve accounts - these are the people who we inform. + $request = $smcFunc['db_query']('', ' + SELECT id_member, lngfile, email_address + FROM {db_prefix}members + WHERE (id_group IN ({array_int:group_list}) OR FIND_IN_SET({raw:group_array_implode}, additional_groups) != 0) + AND notify_types != {int:notify_types} + ORDER BY lngfile', + array( + 'group_list' => $groups, + 'notify_types' => 4, + 'group_array_implode' => implode(', additional_groups) != 0 OR FIND_IN_SET(', $groups), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $replacements = array( + 'USERNAME' => $member_name, + 'PROFILELINK' => $scripturl . '?action=profile;u=' . $memberID + ); + $emailtype = 'admin_notify'; + + // If they need to be approved add more info... + if ($type == 'approval') + { + $replacements['APPROVALLINK'] = $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve'; + $emailtype .= '_approval'; + } + + $emaildata = loadEmailTemplate($emailtype, $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']); + + // And do the actual sending... + sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 0); + } + $smcFunc['db_free_result']($request); + + if (isset($current_language) && $current_language != $user_info['language']) + loadLanguage('Login'); +} + +function loadEmailTemplate($template, $replacements = array(), $lang = '', $loadLang = true) +{ + global $txt, $mbname, $scripturl, $settings, $user_info; + + // First things first, load up the email templates language file, if we need to. + if ($loadLang) + loadLanguage('EmailTemplates', $lang); + + if (!isset($txt['emails'][$template])) + fatal_lang_error('email_no_template', 'template', array($template)); + + $ret = array( + 'subject' => $txt['emails'][$template]['subject'], + 'body' => $txt['emails'][$template]['body'], + ); + + // Add in the default replacements. + $replacements += array( + 'FORUMNAME' => $mbname, + 'SCRIPTURL' => $scripturl, + 'THEMEURL' => $settings['theme_url'], + 'IMAGESURL' => $settings['images_url'], + 'DEFAULT_THEMEURL' => $settings['default_theme_url'], + 'REGARDS' => $txt['regards_team'], + ); + + // Split the replacements up into two arrays, for use with str_replace + $find = array(); + $replace = array(); + + foreach ($replacements as $f => $r) + { + $find[] = '{' . $f . '}'; + $replace[] = $r; + } + + // Do the variable replacements. + $ret['subject'] = str_replace($find, $replace, $ret['subject']); + $ret['body'] = str_replace($find, $replace, $ret['body']); + + // Now deal with the {USER.variable} items. + $ret['subject'] = preg_replace_callback('~{USER.([^}]+)}~', 'user_info_callback', $ret['subject']); + $ret['body'] = preg_replace_callback('~{USER.([^}]+)}~', 'user_info_callback', $ret['body']); + + // Finally return the email to the caller so they can send it out. + return $ret; +} + +function user_info_callback($matches) +{ + global $user_info; + if (empty($matches[1])) + return ''; + + $use_ref = true; + $ref = &$user_info; + + foreach (explode('.', $matches[1]) as $index) + { + if ($use_ref && isset($ref[$index])) + $ref = &$ref[$index]; + else + { + $use_ref = false; + break; + } + } + + return $use_ref ? $ref : $matches[0]; +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Recent.php b/Sources/Subs-Recent.php new file mode 100644 index 0000000..bfa7b36 --- /dev/null +++ b/Sources/Subs-Recent.php @@ -0,0 +1,107 @@ += {int:likely_max_msg}' . + (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' + AND b.id_board != {int:recycle_board}' : '') . ' + AND {query_wanna_see_board}' . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved} + AND m.approved = {int:is_approved}' : '') . ' + ORDER BY m.id_msg DESC + LIMIT ' . $latestPostOptions['number_posts'], + array( + 'likely_max_msg' => max(0, $modSettings['maxMsgID'] - 50 * $latestPostOptions['number_posts']), + 'recycle_board' => $modSettings['recycle_board'], + 'is_approved' => 1, + ) + ); + $posts = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Censor the subject and post for the preview ;). + censorText($row['subject']); + censorText($row['body']); + + $row['body'] = strip_tags(strtr(parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']), array('
          ' => ' '))); + if ($smcFunc['strlen']($row['body']) > 128) + $row['body'] = $smcFunc['substr']($row['body'], 0, 128) . '...'; + + // Build the array. + $posts[] = array( + 'board' => array( + 'id' => $row['id_board'], + 'name' => $row['board_name'], + 'href' => $scripturl . '?board=' . $row['id_board'] . '.0', + 'link' => '' . $row['board_name'] . '' + ), + 'topic' => $row['id_topic'], + 'poster' => array( + 'id' => $row['id_member'], + 'name' => $row['poster_name'], + 'href' => empty($row['id_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member'], + 'link' => empty($row['id_member']) ? $row['poster_name'] : '' . $row['poster_name'] . '' + ), + 'subject' => $row['subject'], + 'short_subject' => shorten_subject($row['subject'], 24), + 'preview' => $row['body'], + 'time' => timeformat($row['poster_time']), + 'timestamp' => forum_time(true, $row['poster_time']), + 'raw_timestamp' => $row['poster_time'], + 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . ';topicseen#msg' . $row['id_msg'], + 'link' => '' . $row['subject'] . '' + ); + } + $smcFunc['db_free_result']($request); + + return $posts; +} + +// Callback-function for the cache for getLastPosts(). +function cache_getLastPosts($latestPostOptions) +{ + return array( + 'data' => getLastPosts($latestPostOptions), + 'expires' => time() + 60, + 'post_retri_eval' => ' + foreach ($cache_block[\'data\'] as $k => $post) + { + $cache_block[\'data\'][$k][\'time\'] = timeformat($post[\'raw_timestamp\']); + $cache_block[\'data\'][$k][\'timestamp\'] = forum_time(true, $post[\'raw_timestamp\']); + }', + ); +} + +?> \ No newline at end of file diff --git a/Sources/Subs-Sound.php b/Sources/Subs-Sound.php new file mode 100644 index 0000000..a71a436 --- /dev/null +++ b/Sources/Subs-Sound.php @@ -0,0 +1,114 @@ + 2 || ($ip2 = cache_get_data('wave_file/' . $user_info['ip2'], 20)) > 2) + die(header('HTTP/1.1 400 Bad Request')); + cache_put_data('wave_file/' . $user_info['ip'], $ip ? $ip + 1 : 1, 20); + cache_put_data('wave_file/' . $user_info['ip2'], $ip2 ? $ip2 + 1 : 1, 20); + + // Fixate randomization for this word. + mt_srand(end(unpack('n', md5($word . session_id())))); + + // Try to see if there's a sound font in the user's language. + if (file_exists($settings['default_theme_dir'] . '/fonts/sound/a.' . $user_info['language'] . '.wav')) + $sound_language = $user_info['language']; + + // English should be there. + elseif (file_exists($settings['default_theme_dir'] . '/fonts/sound/a.english.wav')) + $sound_language = 'english'; + + // Guess not... + else + return false; + + // File names are in lower case so lets make sure that we are only using a lower case string + $word = strtolower($word); + + // Loop through all letters of the word $word. + $sound_word = ''; + for ($i = 0; $i < strlen($word); $i++) + { + $sound_letter = implode('', file($settings['default_theme_dir'] . '/fonts/sound/' . $word{$i} . '.' . $sound_language . '.wav')); + if (strpos($sound_letter, 'data') === false) + return false; + + $sound_letter = substr($sound_letter, strpos($sound_letter, 'data') + 8); + switch ($word{$i} === 's' ? 0 : mt_rand(0, 2)) + { + case 0: + for ($j = 0, $n = strlen($sound_letter); $j < $n; $j++) + for ($k = 0, $m = round(mt_rand(15, 25) / 10); $k < $m; $k++) + $sound_word .= $word{$i} === 's' ? $sound_letter{$j} : chr(mt_rand(max(ord($sound_letter{$j}) - 1, 0x00), min(ord($sound_letter{$j}) + 1, 0xFF))); + break; + + case 1: + for ($j = 0, $n = strlen($sound_letter) - 1; $j < $n; $j += 2) + $sound_word .= (mt_rand(0, 3) == 0 ? '' : $sound_letter{$j}) . (mt_rand(0, 3) === 0 ? $sound_letter{$j + 1} : $sound_letter{$j}) . (mt_rand(0, 3) === 0 ? $sound_letter{$j} : $sound_letter{$j + 1}) . $sound_letter{$j + 1} . (mt_rand(0, 3) == 0 ? $sound_letter{$j + 1} : ''); + $sound_word .= str_repeat($sound_letter{$n}, 2); + break; + + case 2: + $shift = 0; + for ($j = 0, $n = strlen($sound_letter); $j < $n; $j++) + { + if (mt_rand(0, 10) === 0) + $shift += mt_rand(-3, 3); + for ($k = 0, $m = round(mt_rand(15, 25) / 10); $k < $m; $k++) + $sound_word .= chr(min(max(ord($sound_letter{$j}) + $shift, 0x00), 0xFF)); + } + break; + + } + + $sound_word .= str_repeat(chr(0x80), mt_rand(10000, 10500)); + } + + $data_size = strlen($sound_word); + $file_size = $data_size + 0x24; + $sample_rate = 16000; + + // Disable compression. + ob_end_clean(); + header('Content-Encoding: none'); + + // Output the wav. + header('Content-type: audio/x-wav'); + header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 525600 * 60) . ' GMT'); + header('Content-Length: ' . ($file_size + 0x08)); + + echo pack('nnVnnnnnnnnVVnnnnV', 0x5249, 0x4646, $file_size, 0x5741, 0x5645, 0x666D, 0x7420, 0x1000, 0x0000, 0x0100, 0x0100, $sample_rate, $sample_rate, 0x0100, 0x0800, 0x6461, 0x7461, $data_size), $sound_word; + + // Noting more to add. + die(); +} + +?> \ No newline at end of file diff --git a/Sources/Subs.php b/Sources/Subs.php new file mode 100644 index 0000000..94d02ce --- /dev/null +++ b/Sources/Subs.php @@ -0,0 +1,4392 @@ + 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) . '...'; +} + +// The current time with offset. +function forum_time($use_user_offset = true, $timestamp = null) +{ + global $user_info, $modSettings; + + if ($timestamp === null) + $timestamp = time(); + elseif ($timestamp == 0) + return 0; + + return $timestamp + ($modSettings['time_offset'] + ($use_user_offset ? $user_info['time_offset'] : 0)) * 3600; +} + +// This gets all possible permutations of an array. +function permute($array) +{ + $orders = array($array); + + $n = count($array); + $p = range(0, $n); + for ($i = 1; $i < $n; null) + { + $p[$i]--; + $j = $i % 2 != 0 ? $p[$i] : 0; + + $temp = $array[$i]; + $array[$i] = $array[$j]; + $array[$j] = $temp; + + for ($i = 1; $p[$i] == 0; $i++) + $p[$i] = 1; + + $orders[] = $array; + } + + return $orders; +} + +// Parse bulletin board code in a string, as well as smileys optionally. +function parse_bbc($message, $smileys = true, $cache_id = '', $parse_tags = array()) +{ + global $txt, $scripturl, $context, $modSettings, $user_info, $smcFunc; + static $bbc_codes = array(), $itemcodes = array(), $no_autolink_tags = array(); + static $disabled; + + // Don't waste cycles + if ($message === '') + return ''; + + // Never show smileys for wireless clients. More bytes, can't see it anyway :P. + if (WIRELESS) + $smileys = false; + elseif ($smileys !== null && ($smileys == '1' || $smileys == '0')) + $smileys = (bool) $smileys; + + if (empty($modSettings['enableBBC']) && $message !== false) + { + if ($smileys === true) + parsesmileys($message); + + return $message; + } + + // Just in case it wasn't determined yet whether UTF-8 is enabled. + if (!isset($context['utf8'])) + $context['utf8'] = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8'; + + // If we are not doing every tag then we don't cache this run. + if (!empty($parse_tags) && !empty($bbc_codes)) + { + $temp_bbc = $bbc_codes; + $bbc_codes = array(); + } + + // Sift out the bbc for a performance improvement. + if (empty($bbc_codes) || $message === false || !empty($parse_tags)) + { + if (!empty($modSettings['disabledBBC'])) + { + $temp = explode(',', strtolower($modSettings['disabledBBC'])); + + foreach ($temp as $tag) + $disabled[trim($tag)] = true; + } + + if (empty($modSettings['enableEmbeddedFlash'])) + $disabled['flash'] = true; + + /* The following bbc are formatted as an array, with keys as follows: + + tag: the tag's name - should be lowercase! + + type: one of... + - (missing): [tag]parsed content[/tag] + - unparsed_equals: [tag=xyz]parsed content[/tag] + - parsed_equals: [tag=parsed data]parsed content[/tag] + - unparsed_content: [tag]unparsed content[/tag] + - closed: [tag], [tag/], [tag /] + - unparsed_commas: [tag=1,2,3]parsed content[/tag] + - unparsed_commas_content: [tag=1,2,3]unparsed content[/tag] + - unparsed_equals_content: [tag=...]unparsed content[/tag] + + parameters: an optional array of parameters, for the form + [tag abc=123]content[/tag]. The array is an associative array + where the keys are the parameter names, and the values are an + array which may contain the following: + - match: a regular expression to validate and match the value. + - quoted: true if the value should be quoted. + - validate: callback to evaluate on the data, which is $data. + - value: a string in which to replace $1 with the data. + either it or validate may be used, not both. + - optional: true if the parameter is optional. + + test: a regular expression to test immediately after the tag's + '=', ' ' or ']'. Typically, should have a \] at the end. + Optional. + + content: only available for unparsed_content, closed, + unparsed_commas_content, and unparsed_equals_content. + $1 is replaced with the content of the tag. Parameters + are replaced in the form {param}. For unparsed_commas_content, + $2, $3, ..., $n are replaced. + + before: only when content is not used, to go before any + content. For unparsed_equals, $1 is replaced with the value. + For unparsed_commas, $1, $2, ..., $n are replaced. + + after: similar to before in every way, except that it is used + when the tag is closed. + + disabled_content: used in place of content when the tag is + disabled. For closed, default is '', otherwise it is '$1' if + block_level is false, '
          $1
          ' elsewise. + + disabled_before: used in place of before when disabled. Defaults + to '
          ' if block_level, '' if not. + + disabled_after: used in place of after when disabled. Defaults + to '
          ' if block_level, '' if not. + + block_level: set to true the tag is a "block level" tag, similar + to HTML. Block level tags cannot be nested inside tags that are + not block level, and will not be implicitly closed as easily. + One break following a block level tag may also be removed. + + trim: if set, and 'inside' whitespace after the begin tag will be + removed. If set to 'outside', whitespace after the end tag will + meet the same fate. + + validate: except when type is missing or 'closed', a callback to + validate the data as $data. Depending on the tag's type, $data + may be a string or an array of strings (corresponding to the + replacement.) + + quoted: when type is 'unparsed_equals' or 'parsed_equals' only, + may be not set, 'optional', or 'required' corresponding to if + the content may be quoted. This allows the parser to read + [tag="abc]def[esdf]"] properly. + + require_parents: an array of tag names, or not set. If set, the + enclosing tag *must* be one of the listed tags, or parsing won't + occur. + + require_children: similar to require_parents, if set children + won't be parsed if they are not in the list. + + disallow_children: similar to, but very different from, + require_children, if it is set the listed tags will not be + parsed inside the tag. + + parsed_tags_allowed: an array restricting what BBC can be in the + parsed_equals parameter, if desired. + */ + + $codes = array( + array( + 'tag' => 'abbr', + 'type' => 'unparsed_equals', + 'before' => '', + 'after' => '', + 'quoted' => 'optional', + 'disabled_after' => ' ($1)', + ), + array( + 'tag' => 'acronym', + 'type' => 'unparsed_equals', + 'before' => '', + 'after' => '', + 'quoted' => 'optional', + 'disabled_after' => ' ($1)', + ), + array( + 'tag' => 'anchor', + 'type' => 'unparsed_equals', + 'test' => '[#]?([A-Za-z][A-Za-z0-9_\-]*)\]', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'b', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'bdo', + 'type' => 'unparsed_equals', + 'before' => '', + 'after' => '', + 'test' => '(rtl|ltr)\]', + 'block_level' => true, + ), + array( + 'tag' => 'black', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'blue', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'br', + 'type' => 'closed', + 'content' => '
          ', + ), + array( + 'tag' => 'center', + 'before' => '
          ', + 'after' => '
          ', + 'block_level' => true, + ), + array( + 'tag' => 'code', + 'type' => 'unparsed_content', + 'content' => '
          ' . $txt['code'] . ': ' . $txt['code_select'] . '
          ' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '
          ' : '') . '$1' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '
          ' : ''), + // !!! Maybe this can be simplified? + 'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', ' + global $context; + + if (!isset($disabled[\'code\'])) + { + $php_parts = preg_split(\'~(<\?php|\?>)~\', $data, -1, PREG_SPLIT_DELIM_CAPTURE); + + for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++) + { + // Do PHP code coloring? + if ($php_parts[$php_i] != \'<?php\') + continue; + + $php_string = \'\'; + while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?>\') + { + $php_string .= $php_parts[$php_i]; + $php_parts[$php_i++] = \'\'; + } + $php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]); + } + + // Fix the PHP code stuff... + $data = str_replace("
          \t
          ", "\t", implode(\'\', $php_parts)); + + // Older browsers are annoying, aren\'t they? + if ($context[\'browser\'][\'is_ie4\'] || $context[\'browser\'][\'is_ie5\'] || $context[\'browser\'][\'is_ie5.5\']) + $data = str_replace("\t", "
          \t
          ", $data); + else + $data = str_replace("\t", "\t", $data); + + // Recent Opera bug requiring temporary fix. &nsbp; is needed before to avoid broken selection. + if ($context[\'browser\'][\'is_opera\']) + $data .= \' \'; + }'), + 'block_level' => true, + ), + array( + 'tag' => 'code', + 'type' => 'unparsed_equals_content', + 'content' => '
          ' . $txt['code'] . ': ($2) ' . $txt['code_select'] . '
          ' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '
          ' : '') . '$1' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '
          ' : ''), + // !!! Maybe this can be simplified? + 'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', ' + global $context; + + if (!isset($disabled[\'code\'])) + { + $php_parts = preg_split(\'~(<\?php|\?>)~\', $data[0], -1, PREG_SPLIT_DELIM_CAPTURE); + + for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++) + { + // Do PHP code coloring? + if ($php_parts[$php_i] != \'<?php\') + continue; + + $php_string = \'\'; + while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?>\') + { + $php_string .= $php_parts[$php_i]; + $php_parts[$php_i++] = \'\'; + } + $php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]); + } + + // Fix the PHP code stuff... + $data[0] = str_replace("
          \t
          ", "\t", implode(\'\', $php_parts)); + + // Older browsers are annoying, aren\'t they? + if ($context[\'browser\'][\'is_ie4\'] || $context[\'browser\'][\'is_ie5\'] || $context[\'browser\'][\'is_ie5.5\']) + $data[0] = str_replace("\t", "
          \t
          ", $data[0]); + else + $data[0] = str_replace("\t", "\t", $data[0]); + + // Recent Opera bug requiring temporary fix. &nsbp; is needed before to avoid broken selection. + if ($context[\'browser\'][\'is_opera\']) + $data[0] .= \' \'; + }'), + 'block_level' => true, + ), + array( + 'tag' => 'color', + 'type' => 'unparsed_equals', + 'test' => '(#[\da-fA-F]{3}|#[\da-fA-F]{6}|[A-Za-z]{1,20}|rgb\(\d{1,3}, ?\d{1,3}, ?\d{1,3}\))\]', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'email', + 'type' => 'unparsed_content', + 'content' => '$1', + // !!! Should this respect guest_hideContacts? + 'validate' => create_function('&$tag, &$data, $disabled', '$data = strtr($data, array(\'
          \' => \'\'));'), + ), + array( + 'tag' => 'email', + 'type' => 'unparsed_equals', + 'before' => '', + 'after' => '', + // !!! Should this respect guest_hideContacts? + 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), + 'disabled_after' => ' ($1)', + ), + array( + 'tag' => 'flash', + 'type' => 'unparsed_commas_content', + 'test' => '\d+,\d+\]', + '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>'), + 'validate' => create_function('&$tag, &$data, $disabled', ' + if (isset($disabled[\'url\'])) + $tag[\'content\'] = \'$1\'; + elseif (strpos($data[0], \'http://\') !== 0 && strpos($data[0], \'https://\') !== 0) + $data[0] = \'http://\' . $data[0]; + '), + 'disabled_content' => '$1', + ), + array( + 'tag' => 'font', + 'type' => 'unparsed_equals', + 'test' => '[A-Za-z0-9_,\-\s]+?\]', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'ftp', + 'type' => 'unparsed_content', + 'content' => '$1', + 'validate' => create_function('&$tag, &$data, $disabled', ' + $data = strtr($data, array(\'
          \' => \'\')); + if (strpos($data, \'ftp://\') !== 0 && strpos($data, \'ftps://\') !== 0) + $data = \'ftp://\' . $data; + '), + ), + array( + 'tag' => 'ftp', + 'type' => 'unparsed_equals', + 'before' => '', + 'after' => '', + 'validate' => create_function('&$tag, &$data, $disabled', ' + if (strpos($data, \'ftp://\') !== 0 && strpos($data, \'ftps://\') !== 0) + $data = \'ftp://\' . $data; + '), + 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), + 'disabled_after' => ' ($1)', + ), + array( + 'tag' => 'glow', + 'type' => 'unparsed_commas', + 'test' => '[#0-9a-zA-Z\-]{3,12},([012]\d{1,2}|\d{1,2})(,[^]]+)?\]', + 'before' => $context['browser']['is_ie'] ? '
          ' : '', + 'after' => $context['browser']['is_ie'] ? '
          ' : '', + ), + array( + 'tag' => 'green', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'html', + 'type' => 'unparsed_content', + 'content' => '$1', + 'block_level' => true, + 'disabled_content' => '$1', + ), + array( + 'tag' => 'hr', + 'type' => 'closed', + 'content' => '
          ', + 'block_level' => true, + ), + array( + 'tag' => 'i', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'img', + 'type' => 'unparsed_content', + 'parameters' => array( + 'alt' => array('optional' => true), + 'width' => array('optional' => true, 'value' => ' width="$1"', 'match' => '(\d+)'), + 'height' => array('optional' => true, 'value' => ' height="$1"', 'match' => '(\d+)'), + ), + 'content' => '{alt}', + 'validate' => create_function('&$tag, &$data, $disabled', ' + $data = strtr($data, array(\'
          \' => \'\')); + if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0) + $data = \'http://\' . $data; + '), + 'disabled_content' => '($1)', + ), + array( + 'tag' => 'img', + 'type' => 'unparsed_content', + 'content' => '', + 'validate' => create_function('&$tag, &$data, $disabled', ' + $data = strtr($data, array(\'
          \' => \'\')); + if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0) + $data = \'http://\' . $data; + '), + 'disabled_content' => '($1)', + ), + array( + 'tag' => 'iurl', + 'type' => 'unparsed_content', + 'content' => '$1', + 'validate' => create_function('&$tag, &$data, $disabled', ' + $data = strtr($data, array(\'
          \' => \'\')); + if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0) + $data = \'http://\' . $data; + '), + ), + array( + 'tag' => 'iurl', + 'type' => 'unparsed_equals', + 'before' => '', + 'after' => '', + 'validate' => create_function('&$tag, &$data, $disabled', ' + if (substr($data, 0, 1) == \'#\') + $data = \'#post_\' . substr($data, 1); + elseif (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0) + $data = \'http://\' . $data; + '), + 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), + 'disabled_after' => ' ($1)', + ), + array( + 'tag' => 'left', + 'before' => '
          ', + 'after' => '
          ', + 'block_level' => true, + ), + array( + 'tag' => 'li', + 'before' => '
        • ', + 'after' => '
        • ', + 'trim' => 'outside', + 'require_parents' => array('list'), + 'block_level' => true, + 'disabled_before' => '', + 'disabled_after' => '
          ', + ), + array( + 'tag' => 'list', + 'before' => '
            ', + 'after' => '
          ', + 'trim' => 'inside', + 'require_children' => array('li', 'list'), + 'block_level' => true, + ), + array( + 'tag' => 'list', + 'parameters' => array( + '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)'), + ), + 'before' => '
            ', + 'after' => '
          ', + 'trim' => 'inside', + 'require_children' => array('li'), + 'block_level' => true, + ), + array( + 'tag' => 'ltr', + 'before' => '
          ', + 'after' => '
          ', + 'block_level' => true, + ), + array( + 'tag' => 'me', + 'type' => 'unparsed_equals', + 'before' => '
          * $1 ', + 'after' => '
          ', + 'quoted' => 'optional', + 'block_level' => true, + 'disabled_before' => '/me ', + 'disabled_after' => '
          ', + ), + array( + 'tag' => 'move', + 'before' => '', + 'after' => '', + 'block_level' => true, + 'disallow_children' => array('move'), + ), + array( + 'tag' => 'nobbc', + 'type' => 'unparsed_content', + 'content' => '$1', + ), + array( + 'tag' => 'php', + 'type' => 'unparsed_content', + 'content' => '$1', + 'validate' => isset($disabled['php']) ? null : create_function('&$tag, &$data, $disabled', ' + if (!isset($disabled[\'php\'])) + { + $add_begin = substr(trim($data), 0, 5) != \'<?\'; + $data = highlight_php_code($add_begin ? \'<?php \' . $data . \'?>\' : $data); + if ($add_begin) + $data = preg_replace(array(\'~^(.+?)<\?.{0,40}?php(?: |\s)~\', \'~\?>((?:)*)$~\'), \'$1\', $data, 2); + }'), + 'block_level' => false, + 'disabled_content' => '$1', + ), + array( + 'tag' => 'pre', + 'before' => '
          ',
          +				'after' => '
          ', + ), + array( + 'tag' => 'quote', + 'before' => '
          ' . $txt['quote'] . '
          ', + 'after' => '
          ', + 'block_level' => true, + ), + array( + 'tag' => 'quote', + 'parameters' => array( + 'author' => array('match' => '(.{1,192}?)', 'quoted' => true), + ), + 'before' => '
          ' . $txt['quote_from'] . ': {author}
          ', + 'after' => '
          ', + 'block_level' => true, + ), + array( + 'tag' => 'quote', + 'type' => 'parsed_equals', + 'before' => '
          ' . $txt['quote_from'] . ': $1
          ', + 'after' => '
          ', + 'quoted' => 'optional', + // Don't allow everything to be embedded with the author name. + 'parsed_tags_allowed' => array('url', 'iurl', 'ftp'), + 'block_level' => true, + ), + array( + 'tag' => 'quote', + 'parameters' => array( + 'author' => array('match' => '([^<>]{1,192}?)'), + 'link' => array('match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|action=profile;u=\d+)'), + 'date' => array('match' => '(\d+)', 'validate' => 'timeformat'), + ), + 'before' => '
          ', + 'after' => '
          ', + 'block_level' => true, + ), + array( + 'tag' => 'quote', + 'parameters' => array( + 'author' => array('match' => '(.{1,192}?)'), + ), + 'before' => '
          ' . $txt['quote_from'] . ': {author}
          ', + 'after' => '
          ', + 'block_level' => true, + ), + array( + 'tag' => 'red', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'right', + 'before' => '
          ', + 'after' => '
          ', + 'block_level' => true, + ), + array( + 'tag' => 'rtl', + 'before' => '
          ', + 'after' => '
          ', + 'block_level' => true, + ), + array( + 'tag' => 's', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'shadow', + 'type' => 'unparsed_commas', + 'test' => '[#0-9a-zA-Z\-]{3,12},(left|right|top|bottom|[0123]\d{0,2})\]', + 'before' => $context['browser']['is_ie'] ? '' : '', + 'after' => '', + 'validate' => $context['browser']['is_ie'] ? create_function('&$tag, &$data, $disabled', ' + if ($data[1] == \'left\') + $data[1] = 270; + elseif ($data[1] == \'right\') + $data[1] = 90; + elseif ($data[1] == \'top\') + $data[1] = 0; + elseif ($data[1] == \'bottom\') + $data[1] = 180; + else + $data[1] = (int) $data[1];') : create_function('&$tag, &$data, $disabled', ' + if ($data[1] == \'top\' || (is_numeric($data[1]) && $data[1] < 50)) + $data[1] = \'0 -2px 1px\'; + elseif ($data[1] == \'right\' || (is_numeric($data[1]) && $data[1] < 100)) + $data[1] = \'2px 0 1px\'; + elseif ($data[1] == \'bottom\' || (is_numeric($data[1]) && $data[1] < 190)) + $data[1] = \'0 2px 1px\'; + elseif ($data[1] == \'left\' || (is_numeric($data[1]) && $data[1] < 280)) + $data[1] = \'-2px 0 1px\'; + else + $data[1] = \'1px 1px 1px\';'), + ), + array( + 'tag' => 'size', + 'type' => 'unparsed_equals', + 'test' => '([1-9][\d]?p[xt]|small(?:er)?|large[r]?|x[x]?-(?:small|large)|medium|(0\.[1-9]|[1-9](\.[\d][\d]?)?)?em)\]', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'size', + 'type' => 'unparsed_equals', + 'test' => '[1-7]\]', + 'before' => '', + 'after' => '', + 'validate' => create_function('&$tag, &$data, $disabled', ' + $sizes = array(1 => 0.7, 2 => 1.0, 3 => 1.35, 4 => 1.45, 5 => 2.0, 6 => 2.65, 7 => 3.95); + $data = $sizes[$data] . \'em\';' + ), + ), + array( + 'tag' => 'sub', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'sup', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'table', + 'before' => '', + 'after' => '
          ', + 'trim' => 'inside', + 'require_children' => array('tr'), + 'block_level' => true, + ), + array( + 'tag' => 'td', + 'before' => '', + 'after' => '', + 'require_parents' => array('tr'), + 'trim' => 'outside', + 'block_level' => true, + 'disabled_before' => '', + 'disabled_after' => '', + ), + array( + 'tag' => 'time', + 'type' => 'unparsed_content', + 'content' => '$1', + 'validate' => create_function('&$tag, &$data, $disabled', ' + if (is_numeric($data)) + $data = timeformat($data); + else + $tag[\'content\'] = \'[time]$1[/time]\';'), + ), + array( + 'tag' => 'tr', + 'before' => '', + 'after' => '', + 'require_parents' => array('table'), + 'require_children' => array('td'), + 'trim' => 'both', + 'block_level' => true, + 'disabled_before' => '', + 'disabled_after' => '', + ), + array( + 'tag' => 'tt', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'u', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'url', + 'type' => 'unparsed_content', + 'content' => '$1', + 'validate' => create_function('&$tag, &$data, $disabled', ' + $data = strtr($data, array(\'
          \' => \'\')); + if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0) + $data = \'http://\' . $data; + '), + ), + array( + 'tag' => 'url', + 'type' => 'unparsed_equals', + 'before' => '', + 'after' => '', + 'validate' => create_function('&$tag, &$data, $disabled', ' + if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0) + $data = \'http://\' . $data; + '), + 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), + 'disabled_after' => ' ($1)', + ), + array( + 'tag' => 'white', + 'before' => '', + 'after' => '', + ), + ); + + // Let mods add new BBC without hassle. + call_integration_hook('integrate_bbc_codes', array(&$codes)); + + // This is mainly for the bbc manager, so it's easy to add tags above. Custom BBC should be added above this line. + if ($message === false) + { + if (isset($temp_bbc)) + $bbc_codes = $temp_bbc; + return $codes; + } + + // So the parser won't skip them. + $itemcodes = array( + '*' => 'disc', + '@' => 'disc', + '+' => 'square', + 'x' => 'square', + '#' => 'square', + 'o' => 'circle', + 'O' => 'circle', + '0' => 'circle', + ); + if (!isset($disabled['li']) && !isset($disabled['list'])) + { + foreach ($itemcodes as $c => $dummy) + $bbc_codes[$c] = array(); + } + + // Inside these tags autolink is not recommendable. + $no_autolink_tags = array( + 'url', + 'iurl', + 'ftp', + 'email', + ); + + // Shhhh! + if (!isset($disabled['color'])) + { + $codes[] = array( + 'tag' => 'chrissy', + 'before' => '', + 'after' => ' :-*', + ); + $codes[] = array( + 'tag' => 'kissy', + 'before' => '', + 'after' => ' :-*', + ); + } + + foreach ($codes as $code) + { + // If we are not doing every tag only do ones we are interested in. + if (empty($parse_tags) || in_array($code['tag'], $parse_tags)) + $bbc_codes[substr($code['tag'], 0, 1)][] = $code; + } + $codes = null; + } + + // Shall we take the time to cache this? + if ($cache_id != '' && !empty($modSettings['cache_enable']) && (($modSettings['cache_enable'] >= 2 && strlen($message) > 1000) || strlen($message) > 2400) && empty($parse_tags)) + { + // It's likely this will change if the message is modified. + $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']); + + if (($temp = cache_get_data($cache_key, 240)) != null) + return $temp; + + $cache_t = microtime(); + } + + if ($smileys === 'print') + { + // [glow], [shadow], and [move] can't really be printed. + $disabled['glow'] = true; + $disabled['shadow'] = true; + $disabled['move'] = true; + + // Colors can't well be displayed... supposed to be black and white. + $disabled['color'] = true; + $disabled['black'] = true; + $disabled['blue'] = true; + $disabled['white'] = true; + $disabled['red'] = true; + $disabled['green'] = true; + $disabled['me'] = true; + + // Color coding doesn't make sense. + $disabled['php'] = true; + + // Links are useless on paper... just show the link. + $disabled['ftp'] = true; + $disabled['url'] = true; + $disabled['iurl'] = true; + $disabled['email'] = true; + $disabled['flash'] = true; + + // !!! Change maybe? + if (!isset($_GET['images'])) + $disabled['img'] = true; + + // !!! Interface/setting to add more? + } + + $open_tags = array(); + $message = strtr($message, array("\n" => '
          ')); + + // The non-breaking-space looks a bit different each time. + $non_breaking_space = $context['utf8'] ? ($context['server']['complex_preg_chars'] ? '\x{A0}' : "\xC2\xA0") : '\xA0'; + + // This saves time by doing our break long words checks here. + if (!empty($modSettings['fixLongWords']) && $modSettings['fixLongWords'] > 5) + { + if ($context['browser']['is_gecko'] || $context['browser']['is_konqueror']) + $breaker = ' '; + // Opera... + elseif ($context['browser']['is_opera']) + $breaker = ' '; + // Internet Explorer... + else + $breaker = ' '; + + // PCRE will not be happy if we don't give it a short. + $modSettings['fixLongWords'] = (int) min(65535, $modSettings['fixLongWords']); + } + + $pos = -1; + while ($pos !== false) + { + $last_pos = isset($last_pos) ? max($pos, $last_pos) : $pos; + $pos = strpos($message, '[', $pos + 1); + + // Failsafe. + if ($pos === false || $last_pos > $pos) + $pos = strlen($message) + 1; + + // Can't have a one letter smiley, URL, or email! (sorry.) + if ($last_pos < $pos - 1) + { + // Make sure the $last_pos is not negative. + $last_pos = max($last_pos, 0); + + // Pick a block of data to do some raw fixing on. + $data = substr($message, $last_pos, $pos - $last_pos); + + // Take care of some HTML! + if (!empty($modSettings['enablePostHTML']) && strpos($data, '<') !== false) + { + $data = preg_replace('~<a\s+href=((?:")?)((?:https?://|ftps?://|mailto:)\S+?)\\1>~i', '[url=$2]', $data); + $data = preg_replace('~</a>~i', '[/url]', $data); + + //
          should be empty. + $empty_tags = array('br', 'hr'); + foreach ($empty_tags as $tag) + $data = str_replace(array('<' . $tag . '>', '<' . $tag . '/>', '<' . $tag . ' />'), '[' . $tag . ' /]', $data); + + // b, u, i, s, pre... basic tags. + $closable_tags = array('b', 'u', 'i', 's', 'em', 'ins', 'del', 'pre', 'blockquote'); + foreach ($closable_tags as $tag) + { + $diff = substr_count($data, '<' . $tag . '>') - substr_count($data, '</' . $tag . '>'); + $data = strtr($data, array('<' . $tag . '>' => '<' . $tag . '>', '</' . $tag . '>' => '')); + + if ($diff > 0) + $data = substr($data, 0, -1) . str_repeat('', $diff) . substr($data, -1); + } + + // Do - with security... action= -> action-. + preg_match_all('~<img\s+src=((?:")?)((?:https?://|ftps?://)\S+?)\\1(?:\s+alt=(".*?"|\S*?))?(?:\s?/)?>~i', $data, $matches, PREG_PATTERN_ORDER); + if (!empty($matches[0])) + { + $replaces = array(); + foreach ($matches[2] as $match => $imgtag) + { + $alt = empty($matches[3][$match]) ? '' : ' alt=' . preg_replace('~^"|"$~', '', $matches[3][$match]); + + // Remove action= from the URL - no funny business, now. + if (preg_match('~action(=|%3d)(?!dlattach)~i', $imgtag) != 0) + $imgtag = preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $imgtag); + + // Check if the image is larger than allowed. + if (!empty($modSettings['max_image_width']) && !empty($modSettings['max_image_height'])) + { + list ($width, $height) = url_image_size($imgtag); + + if (!empty($modSettings['max_image_width']) && $width > $modSettings['max_image_width']) + { + $height = (int) (($modSettings['max_image_width'] * $height) / $width); + $width = $modSettings['max_image_width']; + } + + if (!empty($modSettings['max_image_height']) && $height > $modSettings['max_image_height']) + { + $width = (int) (($modSettings['max_image_height'] * $width) / $height); + $height = $modSettings['max_image_height']; + } + + // Set the new image tag. + $replaces[$matches[0][$match]] = '[img width=' . $width . ' height=' . $height . $alt . ']' . $imgtag . '[/img]'; + } + else + $replaces[$matches[0][$match]] = '[img' . $alt . ']' . $imgtag . '[/img]'; + } + + $data = strtr($data, $replaces); + } + } + + if (!empty($modSettings['autoLinkUrls'])) + { + // Are we inside tags that should be auto linked? + $no_autolink_area = false; + if (!empty($open_tags)) + { + foreach ($open_tags as $open_tag) + if (in_array($open_tag['tag'], $no_autolink_tags)) + $no_autolink_area = true; + } + + // Don't go backwards. + //!!! Don't think is the real solution.... + $lastAutoPos = isset($lastAutoPos) ? $lastAutoPos : 0; + if ($pos < $lastAutoPos) + $no_autolink_area = true; + $lastAutoPos = $pos; + + if (!$no_autolink_area) + { + // Parse any URLs.... have to get rid of the @ problems some things cause... stupid email addresses. + if (!isset($disabled['url']) && (strpos($data, '://') !== false || strpos($data, 'www.') !== false) && strpos($data, '[url') === false) + { + // Switch out quotes really quick because they can cause problems. + $data = strtr($data, array(''' => '\'', ' ' => $context['utf8'] ? "\xC2\xA0" : "\xA0", '"' => '>">', '"' => '<"<', '<' => '\.(;\'"]|^)((?:http|https)://[\w\-_%@:|]+(?:\.[\w\-_%]+)*(?::\d+)?(?:/[\w\-_\~%\.@!,\?&;=#(){}+:\'\\\\]*)*[/\w\-_\~%@\?;=#}\\\\])~i', + '~(?<=[\s>\.(;\'"]|^)((?:ftp|ftps)://[\w\-_%@:|]+(?:\.[\w\-_%]+)*(?::\d+)?(?:/[\w\-_\~%\.@,\?&;=#(){}+:\'\\\\]*)*[/\w\-_\~%@\?;=#}\\\\])~i', + '~(?<=[\s>(\'<]|^)(www(?:\.[\w\-_]+)+(?::\d+)?(?:/[\w\-_\~%\.@!,\?&;=#(){}+:\'\\\\]*)*[/\w\-_\~%@\?;=#}\\\\])~i' + ), array( + '[url]$1[/url]', + '[ftp]$1[/ftp]', + '[url=http://$1]$1[/url]' + ), $data))) + $data = $result; + + $data = strtr($data, array('\'' => ''', $context['utf8'] ? "\xC2\xA0" : "\xA0" => ' ', '>">' => '"', '<"<' => '"', ' '<')); + } + + // Next, emails... + if (!isset($disabled['email']) && strpos($data, '@') !== false && strpos($data, '[email') === false) + { + $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); + $data = preg_replace('~(?<=
          )([\w\-\.]{1,80}@[\w\-]+\.[\w\-\.]+[\w\-])(?=[?\.,;\s' . $non_breaking_space . '\[\]()*\\\]|$|
          | |>|<|"|')~' . ($context['utf8'] ? 'u' : ''), '[email]$1[/email]', $data); + } + } + } + + $data = strtr($data, array("\t" => '   ')); + + if (!empty($modSettings['fixLongWords']) && $modSettings['fixLongWords'] > 5) + { + // The idea is, find words xx long, and then replace them with xx + space + more. + if ($smcFunc['strlen']($data) > $modSettings['fixLongWords']) + { + // This is done in a roundabout way because $breaker has "long words" :P. + $data = strtr($data, array($breaker => '< >', ' ' => $context['utf8'] ? "\xC2\xA0" : "\xA0")); + $data = preg_replace_callback( + '~(?<=[>;:!? ' . $non_breaking_space . '\]()]|^)([\w' . ($context['utf8'] ? '\pL' : '') . '\.]{' . $modSettings['fixLongWords'] . ',})~' . ($context['utf8'] ? 'u' : ''), + create_function('$m', 'return preg_replace(\'~(.{' . ($modSettings['fixLongWords'] - 1) . '})~' . ($context['utf8'] ? 'u' : '') . '\', \'$1< >\', "$m[1]");'), + $data); + $data = strtr($data, array('< >' => $breaker, $context['utf8'] ? "\xC2\xA0" : "\xA0" => ' ')); + } + } + + // If it wasn't changed, no copying or other boring stuff has to happen! + if ($data != substr($message, $last_pos, $pos - $last_pos)) + { + $message = substr($message, 0, $last_pos) . $data . substr($message, $pos); + + // Since we changed it, look again in case we added or removed a tag. But we don't want to skip any. + $old_pos = strlen($data) + $last_pos; + $pos = strpos($message, '[', $last_pos); + $pos = $pos === false ? $old_pos : min($pos, $old_pos); + } + } + + // Are we there yet? Are we there yet? + if ($pos >= strlen($message) - 1) + break; + + $tags = strtolower(substr($message, $pos + 1, 1)); + + if ($tags == '/' && !empty($open_tags)) + { + $pos2 = strpos($message, ']', $pos + 1); + if ($pos2 == $pos + 2) + continue; + $look_for = strtolower(substr($message, $pos + 2, $pos2 - $pos - 2)); + + $to_close = array(); + $block_level = null; + do + { + $tag = array_pop($open_tags); + if (!$tag) + break; + + if (!empty($tag['block_level'])) + { + // Only find out if we need to. + if ($block_level === false) + { + array_push($open_tags, $tag); + break; + } + + // The idea is, if we are LOOKING for a block level tag, we can close them on the way. + if (strlen($look_for) > 0 && isset($bbc_codes[$look_for[0]])) + { + foreach ($bbc_codes[$look_for[0]] as $temp) + if ($temp['tag'] == $look_for) + { + $block_level = !empty($temp['block_level']); + break; + } + } + + if ($block_level !== true) + { + $block_level = false; + array_push($open_tags, $tag); + break; + } + } + + $to_close[] = $tag; + } + while ($tag['tag'] != $look_for); + + // Did we just eat through everything and not find it? + if ((empty($open_tags) && (empty($tag) || $tag['tag'] != $look_for))) + { + $open_tags = $to_close; + continue; + } + elseif (!empty($to_close) && $tag['tag'] != $look_for) + { + if ($block_level === null && isset($look_for[0], $bbc_codes[$look_for[0]])) + { + foreach ($bbc_codes[$look_for[0]] as $temp) + if ($temp['tag'] == $look_for) + { + $block_level = !empty($temp['block_level']); + break; + } + } + + // We're not looking for a block level tag (or maybe even a tag that exists...) + if (!$block_level) + { + foreach ($to_close as $tag) + array_push($open_tags, $tag); + continue; + } + } + + foreach ($to_close as $tag) + { + $message = substr($message, 0, $pos) . "\n" . $tag['after'] . "\n" . substr($message, $pos2 + 1); + $pos += strlen($tag['after']) + 2; + $pos2 = $pos - 1; + + // See the comment at the end of the big loop - just eating whitespace ;). + if (!empty($tag['block_level']) && substr($message, $pos, 6) == '
          ') + $message = substr($message, 0, $pos) . substr($message, $pos + 6); + if (!empty($tag['trim']) && $tag['trim'] != 'inside' && preg_match('~(
          | |\s)*~', substr($message, $pos), $matches) != 0) + $message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0])); + } + + if (!empty($to_close)) + { + $to_close = array(); + $pos--; + } + + continue; + } + + // No tags for this character, so just keep going (fastest possible course.) + if (!isset($bbc_codes[$tags])) + continue; + + $inside = empty($open_tags) ? null : $open_tags[count($open_tags) - 1]; + $tag = null; + foreach ($bbc_codes[$tags] as $possible) + { + // Not a match? + if (strtolower(substr($message, $pos + 1, strlen($possible['tag']))) != $possible['tag']) + continue; + + $next_c = substr($message, $pos + 1 + strlen($possible['tag']), 1); + + // A test validation? + if (isset($possible['test']) && preg_match('~^' . $possible['test'] . '~', substr($message, $pos + 1 + strlen($possible['tag']) + 1)) == 0) + continue; + // Do we want parameters? + elseif (!empty($possible['parameters'])) + { + if ($next_c != ' ') + continue; + } + elseif (isset($possible['type'])) + { + // Do we need an equal sign? + if (in_array($possible['type'], array('unparsed_equals', 'unparsed_commas', 'unparsed_commas_content', 'unparsed_equals_content', 'parsed_equals')) && $next_c != '=') + continue; + // Maybe we just want a /... + if ($possible['type'] == 'closed' && $next_c != ']' && substr($message, $pos + 1 + strlen($possible['tag']), 2) != '/]' && substr($message, $pos + 1 + strlen($possible['tag']), 3) != ' /]') + continue; + // An immediate ]? + if ($possible['type'] == 'unparsed_content' && $next_c != ']') + continue; + } + // No type means 'parsed_content', which demands an immediate ] without parameters! + elseif ($next_c != ']') + continue; + + // Check allowed tree? + if (isset($possible['require_parents']) && ($inside === null || !in_array($inside['tag'], $possible['require_parents']))) + continue; + elseif (isset($inside['require_children']) && !in_array($possible['tag'], $inside['require_children'])) + continue; + // If this is in the list of disallowed child tags, don't parse it. + elseif (isset($inside['disallow_children']) && in_array($possible['tag'], $inside['disallow_children'])) + continue; + + $pos1 = $pos + 1 + strlen($possible['tag']) + 1; + + // Quotes can have alternate styling, we do this php-side due to all the permutations of quotes. + if ($possible['tag'] == 'quote') + { + // Start with standard + $quote_alt = false; + foreach ($open_tags as $open_quote) + { + // Every parent quote this quote has flips the styling + if ($open_quote['tag'] == 'quote') + $quote_alt = !$quote_alt; + } + // Add a class to the quote to style alternating blockquotes + $possible['before'] = strtr($possible['before'], array('
          ' => '
          ')); + } + + // This is long, but it makes things much easier and cleaner. + if (!empty($possible['parameters'])) + { + $preg = array(); + foreach ($possible['parameters'] as $p => $info) + $preg[] = '(\s+' . $p . '=' . (empty($info['quoted']) ? '' : '"') . (isset($info['match']) ? $info['match'] : '(.+?)') . (empty($info['quoted']) ? '' : '"') . ')' . (empty($info['optional']) ? '' : '?'); + + // Okay, this may look ugly and it is, but it's not going to happen much and it is the best way of allowing any order of parameters but still parsing them right. + $match = false; + $orders = permute($preg); + foreach ($orders as $p) + if (preg_match('~^' . implode('', $p) . '\]~i', substr($message, $pos1 - 1), $matches) != 0) + { + $match = true; + break; + } + + // Didn't match our parameter list, try the next possible. + if (!$match) + continue; + + $params = array(); + for ($i = 1, $n = count($matches); $i < $n; $i += 2) + { + $key = strtok(ltrim($matches[$i]), '='); + if (isset($possible['parameters'][$key]['value'])) + $params['{' . $key . '}'] = strtr($possible['parameters'][$key]['value'], array('$1' => $matches[$i + 1])); + elseif (isset($possible['parameters'][$key]['validate'])) + $params['{' . $key . '}'] = $possible['parameters'][$key]['validate']($matches[$i + 1]); + else + $params['{' . $key . '}'] = $matches[$i + 1]; + + // Just to make sure: replace any $ or { so they can't interpolate wrongly. + $params['{' . $key . '}'] = strtr($params['{' . $key . '}'], array('$' => '$', '{' => '{')); + } + + foreach ($possible['parameters'] as $p => $info) + { + if (!isset($params['{' . $p . '}'])) + $params['{' . $p . '}'] = ''; + } + + $tag = $possible; + + // Put the parameters into the string. + if (isset($tag['before'])) + $tag['before'] = strtr($tag['before'], $params); + if (isset($tag['after'])) + $tag['after'] = strtr($tag['after'], $params); + if (isset($tag['content'])) + $tag['content'] = strtr($tag['content'], $params); + + $pos1 += strlen($matches[0]) - 1; + } + else + $tag = $possible; + break; + } + + // Item codes are complicated buggers... they are implicit [li]s and can make [list]s! + if ($smileys !== false && $tag === null && isset($itemcodes[substr($message, $pos + 1, 1)]) && substr($message, $pos + 2, 1) == ']' && !isset($disabled['list']) && !isset($disabled['li'])) + { + if (substr($message, $pos + 1, 1) == '0' && !in_array(substr($message, $pos - 1, 1), array(';', ' ', "\t", '>'))) + continue; + $tag = $itemcodes[substr($message, $pos + 1, 1)]; + + // First let's set up the tree: it needs to be in a list, or after an li. + if ($inside === null || ($inside['tag'] != 'list' && $inside['tag'] != 'li')) + { + $open_tags[] = array( + 'tag' => 'list', + 'after' => '
        ', + 'block_level' => true, + 'require_children' => array('li'), + 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null, + ); + $code = '
          '; + } + // We're in a list item already: another itemcode? Close it first. + elseif ($inside['tag'] == 'li') + { + array_pop($open_tags); + $code = ''; + } + else + $code = ''; + + // Now we open a new tag. + $open_tags[] = array( + 'tag' => 'li', + 'after' => '', + 'trim' => 'outside', + 'block_level' => true, + 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null, + ); + + // First, open the tag... + $code .= ''; + $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos + 3); + $pos += strlen($code) - 1 + 2; + + // Next, find the next break (if any.) If there's more itemcode after it, keep it going - otherwise close! + $pos2 = strpos($message, '
          ', $pos); + $pos3 = strpos($message, '[/', $pos); + if ($pos2 !== false && ($pos2 <= $pos3 || $pos3 === false)) + { + preg_match('~^(
          | |\s|\[)+~', substr($message, $pos2 + 6), $matches); + $message = substr($message, 0, $pos2) . "\n" . (!empty($matches[0]) && substr($matches[0], -1) == '[' ? '[/li]' : '[/li][/list]') . "\n" . substr($message, $pos2); + + $open_tags[count($open_tags) - 2]['after'] = '
        '; + } + // Tell the [list] that it needs to close specially. + else + { + // Move the li over, because we're not sure what we'll hit. + $open_tags[count($open_tags) - 1]['after'] = ''; + $open_tags[count($open_tags) - 2]['after'] = '
    '; + } + + continue; + } + + // Implicitly close lists and tables if something other than what's required is in them. This is needed for itemcode. + if ($tag === null && $inside !== null && !empty($inside['require_children'])) + { + array_pop($open_tags); + + $message = substr($message, 0, $pos) . "\n" . $inside['after'] . "\n" . substr($message, $pos); + $pos += strlen($inside['after']) - 1 + 2; + } + + // No tag? Keep looking, then. Silly people using brackets without actual tags. + if ($tag === null) + continue; + + // Propagate the list to the child (so wrapping the disallowed tag won't work either.) + if (isset($inside['disallow_children'])) + $tag['disallow_children'] = isset($tag['disallow_children']) ? array_unique(array_merge($tag['disallow_children'], $inside['disallow_children'])) : $inside['disallow_children']; + + // Is this tag disabled? + if (isset($disabled[$tag['tag']])) + { + if (!isset($tag['disabled_before']) && !isset($tag['disabled_after']) && !isset($tag['disabled_content'])) + { + $tag['before'] = !empty($tag['block_level']) ? '
    ' : ''; + $tag['after'] = !empty($tag['block_level']) ? '
    ' : ''; + $tag['content'] = isset($tag['type']) && $tag['type'] == 'closed' ? '' : (!empty($tag['block_level']) ? '
    $1
    ' : '$1'); + } + elseif (isset($tag['disabled_before']) || isset($tag['disabled_after'])) + { + $tag['before'] = isset($tag['disabled_before']) ? $tag['disabled_before'] : (!empty($tag['block_level']) ? '
    ' : ''); + $tag['after'] = isset($tag['disabled_after']) ? $tag['disabled_after'] : (!empty($tag['block_level']) ? '
    ' : ''); + } + else + $tag['content'] = $tag['disabled_content']; + } + + // The only special case is 'html', which doesn't need to close things. + if (!empty($tag['block_level']) && $tag['tag'] != 'html' && empty($inside['block_level'])) + { + $n = count($open_tags) - 1; + while (empty($open_tags[$n]['block_level']) && $n >= 0) + $n--; + + // Close all the non block level tags so this tag isn't surrounded by them. + for ($i = count($open_tags) - 1; $i > $n; $i--) + { + $message = substr($message, 0, $pos) . "\n" . $open_tags[$i]['after'] . "\n" . substr($message, $pos); + $pos += strlen($open_tags[$i]['after']) + 2; + $pos1 += strlen($open_tags[$i]['after']) + 2; + + // Trim or eat trailing stuff... see comment at the end of the big loop. + if (!empty($open_tags[$i]['block_level']) && substr($message, $pos, 6) == '
    ') + $message = substr($message, 0, $pos) . substr($message, $pos + 6); + if (!empty($open_tags[$i]['trim']) && $tag['trim'] != 'inside' && preg_match('~(
    | |\s)*~', substr($message, $pos), $matches) != 0) + $message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0])); + + array_pop($open_tags); + } + } + + // No type means 'parsed_content'. + if (!isset($tag['type'])) + { + // !!! Check for end tag first, so people can say "I like that [i] tag"? + $open_tags[] = $tag; + $message = substr($message, 0, $pos) . "\n" . $tag['before'] . "\n" . substr($message, $pos1); + $pos += strlen($tag['before']) - 1 + 2; + } + // Don't parse the content, just skip it. + elseif ($tag['type'] == 'unparsed_content') + { + $pos2 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos1); + if ($pos2 === false) + continue; + + $data = substr($message, $pos1, $pos2 - $pos1); + + if (!empty($tag['block_level']) && substr($data, 0, 6) == '
    ') + $data = substr($data, 6); + + if (isset($tag['validate'])) + $tag['validate']($tag, $data, $disabled); + + $code = strtr($tag['content'], array('$1' => $data)); + $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 3 + strlen($tag['tag'])); + + $pos += strlen($code) - 1 + 2; + $last_pos = $pos + 1; + + } + // Don't parse the content, just skip it. + elseif ($tag['type'] == 'unparsed_equals_content') + { + // The value may be quoted for some tags - check. + if (isset($tag['quoted'])) + { + $quoted = substr($message, $pos1, 6) == '"'; + if ($tag['quoted'] != 'optional' && !$quoted) + continue; + + if ($quoted) + $pos1 += 6; + } + else + $quoted = false; + + $pos2 = strpos($message, $quoted == false ? ']' : '"]', $pos1); + if ($pos2 === false) + continue; + $pos3 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos2); + if ($pos3 === false) + continue; + + $data = array( + substr($message, $pos2 + ($quoted == false ? 1 : 7), $pos3 - ($pos2 + ($quoted == false ? 1 : 7))), + substr($message, $pos1, $pos2 - $pos1) + ); + + if (!empty($tag['block_level']) && substr($data[0], 0, 6) == '
    ') + $data[0] = substr($data[0], 6); + + // Validation for my parking, please! + if (isset($tag['validate'])) + $tag['validate']($tag, $data, $disabled); + + $code = strtr($tag['content'], array('$1' => $data[0], '$2' => $data[1])); + $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + strlen($tag['tag'])); + $pos += strlen($code) - 1 + 2; + } + // A closed tag, with no content or value. + elseif ($tag['type'] == 'closed') + { + $pos2 = strpos($message, ']', $pos); + $message = substr($message, 0, $pos) . "\n" . $tag['content'] . "\n" . substr($message, $pos2 + 1); + $pos += strlen($tag['content']) - 1 + 2; + } + // This one is sorta ugly... :/. Unfortunately, it's needed for flash. + elseif ($tag['type'] == 'unparsed_commas_content') + { + $pos2 = strpos($message, ']', $pos1); + if ($pos2 === false) + continue; + $pos3 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos2); + if ($pos3 === false) + continue; + + // We want $1 to be the content, and the rest to be csv. + $data = explode(',', ',' . substr($message, $pos1, $pos2 - $pos1)); + $data[0] = substr($message, $pos2 + 1, $pos3 - $pos2 - 1); + + if (isset($tag['validate'])) + $tag['validate']($tag, $data, $disabled); + + $code = $tag['content']; + foreach ($data as $k => $d) + $code = strtr($code, array('$' . ($k + 1) => trim($d))); + $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + strlen($tag['tag'])); + $pos += strlen($code) - 1 + 2; + } + // This has parsed content, and a csv value which is unparsed. + elseif ($tag['type'] == 'unparsed_commas') + { + $pos2 = strpos($message, ']', $pos1); + if ($pos2 === false) + continue; + + $data = explode(',', substr($message, $pos1, $pos2 - $pos1)); + + if (isset($tag['validate'])) + $tag['validate']($tag, $data, $disabled); + + // Fix after, for disabled code mainly. + foreach ($data as $k => $d) + $tag['after'] = strtr($tag['after'], array('$' . ($k + 1) => trim($d))); + + $open_tags[] = $tag; + + // Replace them out, $1, $2, $3, $4, etc. + $code = $tag['before']; + foreach ($data as $k => $d) + $code = strtr($code, array('$' . ($k + 1) => trim($d))); + $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 1); + $pos += strlen($code) - 1 + 2; + } + // A tag set to a value, parsed or not. + elseif ($tag['type'] == 'unparsed_equals' || $tag['type'] == 'parsed_equals') + { + // The value may be quoted for some tags - check. + if (isset($tag['quoted'])) + { + $quoted = substr($message, $pos1, 6) == '"'; + if ($tag['quoted'] != 'optional' && !$quoted) + continue; + + if ($quoted) + $pos1 += 6; + } + else + $quoted = false; + + $pos2 = strpos($message, $quoted == false ? ']' : '"]', $pos1); + if ($pos2 === false) + continue; + + $data = substr($message, $pos1, $pos2 - $pos1); + + // Validation for my parking, please! + if (isset($tag['validate'])) + $tag['validate']($tag, $data, $disabled); + + // For parsed content, we must recurse to avoid security problems. + if ($tag['type'] != 'unparsed_equals') + $data = parse_bbc($data, !empty($tag['parsed_tags_allowed']) ? false : true, '', !empty($tag['parsed_tags_allowed']) ? $tag['parsed_tags_allowed'] : array()); + + $tag['after'] = strtr($tag['after'], array('$1' => $data)); + + $open_tags[] = $tag; + + $code = strtr($tag['before'], array('$1' => $data)); + $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + ($quoted == false ? 1 : 7)); + $pos += strlen($code) - 1 + 2; + } + + // If this is block level, eat any breaks after it. + if (!empty($tag['block_level']) && substr($message, $pos + 1, 6) == '
    ') + $message = substr($message, 0, $pos + 1) . substr($message, $pos + 7); + + // Are we trimming outside this tag? + if (!empty($tag['trim']) && $tag['trim'] != 'outside' && preg_match('~(
    | |\s)*~', substr($message, $pos + 1), $matches) != 0) + $message = substr($message, 0, $pos + 1) . substr($message, $pos + 1 + strlen($matches[0])); + } + + // Close any remaining tags. + while ($tag = array_pop($open_tags)) + $message .= "\n" . $tag['after'] . "\n"; + + // Parse the smileys within the parts where it can be done safely. + if ($smileys === true) + { + $message_parts = explode("\n", $message); + for ($i = 0, $n = count($message_parts); $i < $n; $i += 2) + parsesmileys($message_parts[$i]); + + $message = implode('', $message_parts); + } + + // No smileys, just get rid of the markers. + else + $message = strtr($message, array("\n" => '')); + + if (substr($message, 0, 1) == ' ') + $message = ' ' . substr($message, 1); + + // Cleanup whitespace. + $message = strtr($message, array(' ' => '  ', "\r" => '', "\n" => '
    ', '
    ' => '
     ', ' ' => "\n")); + + // Cache the output if it took some time... + if (isset($cache_key, $cache_t) && array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.05) + cache_put_data($cache_key, $message, 240); + + // If this was a force parse revert if needed. + if (!empty($parse_tags)) + { + if (empty($temp_bbc)) + $bbc_codes = array(); + else + { + $bbc_codes = $temp_bbc; + unset($temp_bbc); + } + } + + return $message; +} + +// Parse smileys in the passed message. +function parsesmileys(&$message) +{ + global $modSettings, $txt, $user_info, $context, $smcFunc; + static $smileyPregSearch = array(), $smileyPregReplacements = array(); + + // No smiley set at all?! + if ($user_info['smiley_set'] == 'none') + return; + + // If the smiley array hasn't been set, do it now. + if (empty($smileyPregSearch)) + { + // Use the default smileys if it is disabled. (better for "portability" of smileys.) + if (empty($modSettings['smiley_enable'])) + { + $smileysfrom = array('>:D', ':D', '::)', '>:(', ':))', ':)', ';)', ';D', ':(', ':o', '8)', ':P', '???', ':-[', ':-X', ':-*', ':\'(', ':-\\', '^-^', 'O0', 'C:-)', '0:)'); + $smileysto = array('evil.gif', 'cheesy.gif', 'rolleyes.gif', 'angry.gif', 'laugh.gif', 'smiley.gif', 'wink.gif', 'grin.gif', 'sad.gif', 'shocked.gif', 'cool.gif', 'tongue.gif', 'huh.gif', 'embarrassed.gif', 'lipsrsealed.gif', 'kiss.gif', 'cry.gif', 'undecided.gif', 'azn.gif', 'afro.gif', 'police.gif', 'angel.gif'); + $smileysdescs = array('', $txt['icon_cheesy'], $txt['icon_rolleyes'], $txt['icon_angry'], '', $txt['icon_smiley'], $txt['icon_wink'], $txt['icon_grin'], $txt['icon_sad'], $txt['icon_shocked'], $txt['icon_cool'], $txt['icon_tongue'], $txt['icon_huh'], $txt['icon_embarrassed'], $txt['icon_lips'], $txt['icon_kiss'], $txt['icon_cry'], $txt['icon_undecided'], '', '', '', ''); + } + else + { + // Load the smileys in reverse order by length so they don't get parsed wrong. + if (($temp = cache_get_data('parsing_smileys', 480)) == null) + { + $result = $smcFunc['db_query']('', ' + SELECT code, filename, description + FROM {db_prefix}smileys', + array( + ) + ); + $smileysfrom = array(); + $smileysto = array(); + $smileysdescs = array(); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + $smileysfrom[] = $row['code']; + $smileysto[] = $row['filename']; + $smileysdescs[] = $row['description']; + } + $smcFunc['db_free_result']($result); + + cache_put_data('parsing_smileys', array($smileysfrom, $smileysto, $smileysdescs), 480); + } + else + list ($smileysfrom, $smileysto, $smileysdescs) = $temp; + } + + // The non-breaking-space is a complex thing... + $non_breaking_space = $context['utf8'] ? ($context['server']['complex_preg_chars'] ? '\x{A0}' : "\xC2\xA0") : '\xA0'; + + // This smiley regex makes sure it doesn't parse smileys within code tags (so [url=mailto:David@bla.com] doesn't parse the :D smiley) + $smileyPregReplacements = array(); + $searchParts = array(); + for ($i = 0, $n = count($smileysfrom); $i < $n; $i++) + { + $smileyCode = '' . strtr(htmlspecialchars($smileysfrom[$i], ENT_QUOTES), array(':' => ':', '(' => '(', ')' => ')', '$' => '$', '[' => '[')). ''; + + $smileyPregReplacements[$smileysfrom[$i]] = $smileyCode; + $smileyPregReplacements[htmlspecialchars($smileysfrom[$i], ENT_QUOTES)] = $smileyCode; + $searchParts[] = preg_quote($smileysfrom[$i], '~'); + $searchParts[] = preg_quote(htmlspecialchars($smileysfrom[$i], ENT_QUOTES), '~'); + } + + $smileyPregSearch = '~(?<=[>:\?\.\s' . $non_breaking_space . '[\]()*\\\;]|^)(' . implode('|', $searchParts) . ')(?=[^[:alpha:]0-9]|$)~' . ($context['utf8'] ? 'u' : ''); + } + + // Replace away! + + // TODO: When SMF supports only PHP 5.3+, we can change this to "uses" keyword and simplify this. + $callback = pregReplaceCurry('smielyPregReplaceCallback', 2); + $message = preg_replace_callback($smileyPregSearch, $callback($smileyPregReplacements), $message); +} + +// This allows use to do delayed argument binding and bring in the replacement variables for some preg replacements. +function pregReplaceCurry($func, $arity) +{ + return create_function('', " + \$args = func_get_args(); + if(count(\$args) >= $arity) + return call_user_func_array('$func', \$args); + \$args = var_export(\$args, 1); + return create_function('',' + \$a = func_get_args(); + \$z = ' . \$args . '; + \$a = array_merge(\$z,\$a); + return call_user_func_array(\'$func\', \$a); + '); + "); +} + +// Our callback that does the actual smiley replacements. +function smielyPregReplaceCallback($replacements, $matches) +{ + return $replacements[$matches[1]]; +} +// Highlight any code... +function highlight_php_code($code) +{ + global $context; + + // Remove special characters. + $code = un_htmlspecialchars(strtr($code, array('
    ' => "\n", "\t" => 'SMF_TAB();', '[' => '['))); + + $oldlevel = error_reporting(0); + + // It's easier in 4.2.x+. + if (@version_compare(PHP_VERSION, '4.2.0') == -1) + { + ob_start(); + @highlight_string($code); + $buffer = str_replace(array("\n", "\r"), '', ob_get_contents()); + ob_end_clean(); + } + else + $buffer = str_replace(array("\n", "\r"), '', @highlight_string($code, true)); + + error_reporting($oldlevel); + + // Yes, I know this is kludging it, but this is the best way to preserve tabs from PHP :P. + $buffer = preg_replace('~SMF_TAB(?:<(?:font color|span style)="[^"]*?">)?\\(\\);~', '
    ' . "\t" . '
    ', $buffer); + + return strtr($buffer, array('\'' => ''', '' => '', '' => '')); +} + +// Put this user in the online log. +function writeLog($force = false) +{ + global $user_info, $user_settings, $context, $modSettings, $settings, $topic, $board, $smcFunc, $sourcedir; + + // If we are showing who is viewing a topic, let's see if we are, and force an update if so - to make it accurate. + if (!empty($settings['display_who_viewing']) && ($topic || $board)) + { + // Take the opposite approach! + $force = true; + // Don't update for every page - this isn't wholly accurate but who cares. + if ($topic) + { + if (isset($_SESSION['last_topic_id']) && $_SESSION['last_topic_id'] == $topic) + $force = false; + $_SESSION['last_topic_id'] = $topic; + } + } + + // Are they a spider we should be tracking? Mode = 1 gets tracked on its spider check... + if (!empty($user_info['possibly_robot']) && !empty($modSettings['spider_mode']) && $modSettings['spider_mode'] > 1) + { + require_once($sourcedir . '/ManageSearchEngines.php'); + logSpider(); + } + + // Don't mark them as online more than every so often. + if (!empty($_SESSION['log_time']) && $_SESSION['log_time'] >= (time() - 8) && !$force) + return; + + if (!empty($modSettings['who_enabled'])) + { + $serialized = $_GET + array('USER_AGENT' => $_SERVER['HTTP_USER_AGENT']); + + // In the case of a dlattach action, session_var may not be set. + if (!isset($context['session_var'])) + $context['session_var'] = $_SESSION['session_var']; + + unset($serialized['sesc'], $serialized[$context['session_var']]); + $serialized = serialize($serialized); + } + else + $serialized = ''; + + // Guests use 0, members use their session ID. + $session_id = $user_info['is_guest'] ? 'ip' . $user_info['ip'] : session_id(); + + // Grab the last all-of-SMF-specific log_online deletion time. + $do_delete = cache_get_data('log_online-update', 30) < time() - 30; + + // If the last click wasn't a long time ago, and there was a last click... + if (!empty($_SESSION['log_time']) && $_SESSION['log_time'] >= time() - $modSettings['lastActive'] * 20) + { + if ($do_delete) + { + $smcFunc['db_query']('delete_log_online_interval', ' + DELETE FROM {db_prefix}log_online + WHERE log_time < {int:log_time} + AND session != {string:session}', + array( + 'log_time' => time() - $modSettings['lastActive'] * 60, + 'session' => $session_id, + ) + ); + + // Cache when we did it last. + cache_put_data('log_online-update', time(), 30); + } + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_online + SET log_time = {int:log_time}, ip = IFNULL(INET_ATON({string:ip}), 0), url = {string:url} + WHERE session = {string:session}', + array( + 'log_time' => time(), + 'ip' => $user_info['ip'], + 'url' => $serialized, + 'session' => $session_id, + ) + ); + + // Guess it got deleted. + if ($smcFunc['db_affected_rows']() == 0) + $_SESSION['log_time'] = 0; + } + else + $_SESSION['log_time'] = 0; + + // Otherwise, we have to delete and insert. + if (empty($_SESSION['log_time'])) + { + if ($do_delete || !empty($user_info['id'])) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_online + WHERE ' . ($do_delete ? 'log_time < {int:log_time}' : '') . ($do_delete && !empty($user_info['id']) ? ' OR ' : '') . (empty($user_info['id']) ? '' : 'id_member = {int:current_member}'), + array( + 'current_member' => $user_info['id'], + 'log_time' => time() - $modSettings['lastActive'] * 60, + ) + ); + + $smcFunc['db_insert']($do_delete ? 'ignore' : 'replace', + '{db_prefix}log_online', + array('session' => 'string', 'id_member' => 'int', 'id_spider' => 'int', 'log_time' => 'int', 'ip' => 'raw', 'url' => 'string'), + array($session_id, $user_info['id'], empty($_SESSION['id_robot']) ? 0 : $_SESSION['id_robot'], time(), 'IFNULL(INET_ATON(\'' . $user_info['ip'] . '\'), 0)', $serialized), + array('session') + ); + } + + // Mark your session as being logged. + $_SESSION['log_time'] = time(); + + // Well, they are online now. + if (empty($_SESSION['timeOnlineUpdated'])) + $_SESSION['timeOnlineUpdated'] = time(); + + // Set their login time, if not already done within the last minute. + if (SMF != 'SSI' && !empty($user_info['last_login']) && $user_info['last_login'] < time() - 60) + { + // Don't count longer than 15 minutes. + if (time() - $_SESSION['timeOnlineUpdated'] > 60 * 15) + $_SESSION['timeOnlineUpdated'] = time(); + + $user_settings['total_time_logged_in'] += time() - $_SESSION['timeOnlineUpdated']; + updateMemberData($user_info['id'], array('last_login' => time(), 'member_ip' => $user_info['ip'], 'member_ip2' => $_SERVER['BAN_CHECK_IP'], 'total_time_logged_in' => $user_settings['total_time_logged_in'])); + + if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2) + cache_put_data('user_settings-' . $user_info['id'], $user_settings, 60); + + $user_info['total_time_logged_in'] += time() - $_SESSION['timeOnlineUpdated']; + $_SESSION['timeOnlineUpdated'] = time(); + } +} + +// Make sure the browser doesn't come back and repost the form data. Should be used whenever anything is posted. +function redirectexit($setLocation = '', $refresh = false) +{ + global $scripturl, $context, $modSettings, $db_show_debug, $db_cache; + + // In case we have mail to send, better do that - as obExit doesn't always quite make it... + if (!empty($context['flush_mail'])) + AddMailQueue(true); + + $add = preg_match('~^(ftp|http)[s]?://~', $setLocation) == 0 && substr($setLocation, 0, 6) != 'about:'; + + if (WIRELESS) + { + // Add the scripturl on if needed. + if ($add) + $setLocation = $scripturl . '?' . $setLocation; + + $char = strpos($setLocation, '?') === false ? '?' : ';'; + + if (strpos($setLocation, '#') !== false) + $setLocation = strtr($setLocation, array('#' => $char . WIRELESS_PROTOCOL . '#')); + else + $setLocation .= $char . WIRELESS_PROTOCOL; + } + elseif ($add) + $setLocation = $scripturl . ($setLocation != '' ? '?' . $setLocation : ''); + + // Put the session ID in. + if (defined('SID') && SID != '') + $setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation); + // Keep that debug in their for template debugging! + elseif (isset($_GET['debug'])) + $setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation); + + if (!empty($modSettings['queryless_urls']) && (empty($context['server']['is_cgi']) || @ini_get('cgi.fix_pathinfo') == 1 || @get_cfg_var('cgi.fix_pathinfo') == 1) && (!empty($context['server']['is_apache']) || !empty($context['server']['is_lighttpd']))) + { + if (defined('SID') && SID != '') + $setLocation = preg_replace_callback('~"' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&))((?:board|topic)=[^#]+?)(#[^"]*?)?$~', create_function('$m', 'global $scripturl; return $scripturl . \'/\' . strtr("$m[1]", \'&;=\', \'//,\') . \'.html?\' . SID . (isset($m[2]) ? "$m[2]" : "");'), $setLocation); + else + $setLocation = preg_replace_callback('~"' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$~', create_function('$m', 'global $scripturl; return $scripturl . \'/\' . strtr("$m[1]", \'&;=\', \'//,\') . \'.html\' . (isset($m[2]) ? "$m[2]" : "");'), $setLocation); + } + + // Maybe integrations want to change where we are heading? + call_integration_hook('integrate_redirect', array(&$setLocation, &$refresh)); + + // We send a Refresh header only in special cases because Location looks better. (and is quicker...) + if ($refresh && !WIRELESS) + header('Refresh: 0; URL=' . strtr($setLocation, array(' ' => '%20'))); + else + header('Location: ' . str_replace(' ', '%20', $setLocation)); + + // Debugging. + if (isset($db_show_debug) && $db_show_debug === true) + $_SESSION['debug_redirect'] = $db_cache; + + obExit(false); +} + +// Ends execution. Takes care of template loading and remembering the previous URL. +function obExit($header = null, $do_footer = null, $from_index = false, $from_fatal_error = false) +{ + global $context, $settings, $modSettings, $txt, $smcFunc; + static $header_done = false, $footer_done = false, $level = 0, $has_fatal_error = false; + + // Attempt to prevent a recursive loop. + ++$level; + if ($level > 1 && !$from_fatal_error && !$has_fatal_error) + exit; + if ($from_fatal_error) + $has_fatal_error = true; + + // Clear out the stat cache. + trackStats(); + + // If we have mail to send, send it. + if (!empty($context['flush_mail'])) + AddMailQueue(true); + + $do_header = $header === null ? !$header_done : $header; + if ($do_footer === null) + $do_footer = $do_header; + + // Has the template/header been done yet? + if ($do_header) + { + // Was the page title set last minute? Also update the HTML safe one. + if (!empty($context['page_title']) && empty($context['page_title_html_safe'])) + $context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])); + + // Start up the session URL fixer. + ob_start('ob_sessrewrite'); + + if (!empty($settings['output_buffers']) && is_string($settings['output_buffers'])) + $buffers = explode(',', $settings['output_buffers']); + elseif (!empty($settings['output_buffers'])) + $buffers = $settings['output_buffers']; + else + $buffers = array(); + + if (isset($modSettings['integrate_buffer'])) + $buffers = array_merge(explode(',', $modSettings['integrate_buffer']), $buffers); + + if (!empty($buffers)) + foreach ($buffers as $function) + { + $function = trim($function); + $call = strpos($function, '::') !== false ? explode('::', $function) : $function; + + // Is it valid? + if (is_callable($call)) + ob_start($call); + } + + // Display the screen in the logical order. + template_header(); + $header_done = true; + } + if ($do_footer) + { + if (WIRELESS && !isset($context['sub_template'])) + fatal_lang_error('wireless_error_notyet', false); + + // Just show the footer, then. + loadSubTemplate(isset($context['sub_template']) ? $context['sub_template'] : 'main'); + + // Anything special to put out? + if (!empty($context['insert_after_template']) && !isset($_REQUEST['xml'])) + echo $context['insert_after_template']; + + // Just so we don't get caught in an endless loop of errors from the footer... + if (!$footer_done) + { + $footer_done = true; + template_footer(); + + // (since this is just debugging... it's okay that it's after .) + if (!isset($_REQUEST['xml'])) + db_debug_junk(); + } + } + + // Remember this URL in case someone doesn't like sending HTTP_REFERER. + if (strpos($_SERVER['REQUEST_URL'], 'action=dlattach') === false && strpos($_SERVER['REQUEST_URL'], 'action=viewsmfile') === false) + $_SESSION['old_url'] = $_SERVER['REQUEST_URL']; + + // For session check verfication.... don't switch browsers... + $_SESSION['USER_AGENT'] = $_SERVER['HTTP_USER_AGENT']; + + if (!empty($settings['strict_doctype'])) + { + // The theme author wants to use the STRICT doctype (only God knows why). + $temp = ob_get_contents(); + if (function_exists('ob_clean')) + ob_clean(); + else + { + ob_end_clean(); + ob_start('ob_sessrewrite'); + } + + echo strtr($temp, array( + 'var smf_iso_case_folding' => 'var target_blank = \'_blank\'; var smf_iso_case_folding', + 'target="_blank"' => 'onclick="this.target=target_blank"')); + } + + // Hand off the output to the portal, etc. we're integrated with. + call_integration_hook('integrate_exit', array($do_footer && !WIRELESS)); + + // Don't exit if we're coming from index.php; that will pass through normally. + if (!$from_index || WIRELESS) + exit; +} + +// Usage: logAction('remove', array('starter' => $id_member_started)); +function logAction($action, $extra = array(), $log_type = 'moderate') +{ + global $modSettings, $user_info, $smcFunc, $sourcedir; + + $log_types = array( + 'moderate' => 1, + 'user' => 2, + 'admin' => 3, + ); + + if (!is_array($extra)) + trigger_error('logAction(): data is not an array with action \'' . $action . '\'', E_USER_NOTICE); + + // Pull out the parts we want to store separately, but also make sure that the data is proper + if (isset($extra['topic'])) + { + if (!is_numeric($extra['topic'])) + trigger_error('logAction(): data\'s topic is not a number', E_USER_NOTICE); + $topic_id = empty($extra['topic']) ? '0' : (int)$extra['topic']; + unset($extra['topic']); + } + else + $topic_id = '0'; + + if (isset($extra['message'])) + { + if (!is_numeric($extra['message'])) + trigger_error('logAction(): data\'s message is not a number', E_USER_NOTICE); + $msg_id = empty($extra['message']) ? '0' : (int)$extra['message']; + unset($extra['message']); + } + else + $msg_id = '0'; + + // Is there an associated report on this? + if (in_array($action, array('move', 'remove', 'split', 'merge'))) + { + $request = $smcFunc['db_query']('', ' + SELECT id_report + FROM {db_prefix}log_reported + WHERE {raw:column_name} = {int:reported} + LIMIT 1', + array( + 'column_name' => !empty($msg_id) ? 'id_msg' : 'id_topic', + 'reported' => !empty($msg_id) ? $msg_id : $topic_id, + )); + + // Alright, if we get any result back, update open reports. + if ($smcFunc['db_num_rows']($request) > 0) + { + require_once($sourcedir . '/ModerationCenter.php'); + updateSettings(array('last_mod_report_action' => time())); + recountOpenReports(); + } + $smcFunc['db_free_result']($request); + } + + // No point in doing anything else, if the log isn't even enabled. + if (empty($modSettings['modlog_enabled']) || !isset($log_types[$log_type])) + return false; + + if (isset($extra['member']) && !is_numeric($extra['member'])) + trigger_error('logAction(): data\'s member is not a number', E_USER_NOTICE); + + if (isset($extra['board'])) + { + if (!is_numeric($extra['board'])) + trigger_error('logAction(): data\'s board is not a number', E_USER_NOTICE); + $board_id = empty($extra['board']) ? '0' : (int)$extra['board']; + unset($extra['board']); + } + else + $board_id = '0'; + + if (isset($extra['board_to'])) + { + if (!is_numeric($extra['board_to'])) + trigger_error('logAction(): data\'s board_to is not a number', E_USER_NOTICE); + if (empty($board_id)) + { + $board_id = empty($extra['board_to']) ? '0' : (int)$extra['board_to']; + unset($extra['board_to']); + } + } + + $smcFunc['db_insert']('', + '{db_prefix}log_actions', + array( + 'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'string-16', 'action' => 'string', + 'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534', + ), + array( + time(), $log_types[$log_type], $user_info['id'], $user_info['ip'], $action, + $board_id, $topic_id, $msg_id, serialize($extra), + ), + array('id_action') + ); + + return $smcFunc['db_insert_id']('{db_prefix}log_actions', 'id_action'); +} + +// Track Statistics. +function trackStats($stats = array()) +{ + global $modSettings, $smcFunc; + static $cache_stats = array(); + + if (empty($modSettings['trackStats'])) + return false; + if (!empty($stats)) + return $cache_stats = array_merge($cache_stats, $stats); + elseif (empty($cache_stats)) + return false; + + $setStringUpdate = ''; + $insert_keys = array(); + $date = strftime('%Y-%m-%d', forum_time(false)); + $update_parameters = array( + 'current_date' => $date, + ); + foreach ($cache_stats as $field => $change) + { + $setStringUpdate .= ' + ' . $field . ' = ' . ($change === '+' ? $field . ' + 1' : '{int:' . $field . '}') . ','; + + if ($change === '+') + $cache_stats[$field] = 1; + else + $update_parameters[$field] = $change; + $insert_keys[$field] = 'int'; + } + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_activity + SET' . substr($setStringUpdate, 0, -1) . ' + WHERE date = {date:current_date}', + $update_parameters + ); + if ($smcFunc['db_affected_rows']() == 0) + { + $smcFunc['db_insert']('ignore', + '{db_prefix}log_activity', + array_merge($insert_keys, array('date' => 'date')), + array_merge($cache_stats, array($date)), + array('date') + ); + } + + // Don't do this again. + $cache_stats = array(); + + return true; +} + +// Make sure the user isn't posting over and over again. +function spamProtection($error_type) +{ + global $modSettings, $txt, $user_info, $smcFunc; + + // Certain types take less/more time. + $timeOverrides = array( + 'login' => 2, + 'register' => 2, + 'sendtopc' => $modSettings['spamWaitTime'] * 4, + 'sendmail' => $modSettings['spamWaitTime'] * 5, + 'reporttm' => $modSettings['spamWaitTime'] * 4, + 'search' => !empty($modSettings['search_floodcontrol_time']) ? $modSettings['search_floodcontrol_time'] : 1, + ); + + // Moderators are free... + if (!allowedTo('moderate_board')) + $timeLimit = isset($timeOverrides[$error_type]) ? $timeOverrides[$error_type] : $modSettings['spamWaitTime']; + else + $timeLimit = 2; + + // Delete old entries... + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}log_floodcontrol + WHERE log_time < {int:log_time} + AND log_type = {string:log_type}', + array( + 'log_time' => time() - $timeLimit, + 'log_type' => $error_type, + ) + ); + + // Add a new entry, deleting the old if necessary. + $smcFunc['db_insert']('replace', + '{db_prefix}log_floodcontrol', + array('ip' => 'string-16', 'log_time' => 'int', 'log_type' => 'string'), + array($user_info['ip'], time(), $error_type), + array('ip', 'log_type') + ); + + // If affected is 0 or 2, it was there already. + if ($smcFunc['db_affected_rows']() != 1) + { + // Spammer! You only have to wait a *few* seconds! + fatal_lang_error($error_type . 'WaitTime_broken', false, array($timeLimit)); + return true; + } + + // They haven't posted within the limit. + return false; +} + +// Get the size of a specified image with better error handling. +function url_image_size($url) +{ + global $sourcedir; + + // Make sure it is a proper URL. + $url = str_replace(' ', '%20', $url); + + // Can we pull this from the cache... please please? + if (($temp = cache_get_data('url_image_size-' . md5($url), 240)) !== null) + return $temp; + $t = microtime(); + + // Get the host to pester... + preg_match('~^\w+://(.+?)/(.*)$~', $url, $match); + + // Can't figure it out, just try the image size. + if ($url == '' || $url == 'http://' || $url == 'https://') + { + return false; + } + elseif (!isset($match[1])) + { + $size = @getimagesize($url); + } + else + { + // Try to connect to the server... give it half a second. + $temp = 0; + $fp = @fsockopen($match[1], 80, $temp, $temp, 0.5); + + // Successful? Continue... + if ($fp != false) + { + // Send the HEAD request (since we don't have to worry about chunked, HTTP/1.1 is fine here.) + 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"); + + // Read in the HTTP/1.1 or whatever. + $test = substr(fgets($fp, 11), -1); + fclose($fp); + + // See if it returned a 404/403 or something. + if ($test < 4) + { + $size = @getimagesize($url); + + // This probably means allow_url_fopen is off, let's try GD. + if ($size === false && function_exists('imagecreatefromstring')) + { + include_once($sourcedir . '/Subs-Package.php'); + + // It's going to hate us for doing this, but another request... + $image = @imagecreatefromstring(fetch_web_data($url)); + if ($image !== false) + { + $size = array(imagesx($image), imagesy($image)); + imagedestroy($image); + } + } + } + } + } + + // If we didn't get it, we failed. + if (!isset($size)) + $size = false; + + // If this took a long time, we may never have to do it again, but then again we might... + if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.8) + cache_put_data('url_image_size-' . md5($url), $size, 240); + + // Didn't work. + return $size; +} + +function determineTopicClass(&$topic_context) +{ + // Set topic class depending on locked status and number of replies. + if ($topic_context['is_very_hot']) + $topic_context['class'] = 'veryhot'; + elseif ($topic_context['is_hot']) + $topic_context['class'] = 'hot'; + else + $topic_context['class'] = 'normal'; + + $topic_context['class'] .= $topic_context['is_poll'] ? '_poll' : '_post'; + + if ($topic_context['is_locked']) + $topic_context['class'] .= '_locked'; + + if ($topic_context['is_sticky']) + $topic_context['class'] .= '_sticky'; + + // This is so old themes will still work. + $topic_context['extended_class'] = &$topic_context['class']; +} + +// Sets up the basic theme context stuff. +function setupThemeContext($forceload = false) +{ + global $modSettings, $user_info, $scripturl, $context, $settings, $options, $txt, $maintenance; + global $user_settings, $smcFunc; + static $loaded = false; + + // Under SSI this function can be called more then once. That can cause some problems. + // So only run the function once unless we are forced to run it again. + if ($loaded && !$forceload) + return; + + $loaded = true; + + $context['in_maintenance'] = !empty($maintenance); + $context['current_time'] = timeformat(time(), false); + $context['current_action'] = isset($_GET['action']) ? $_GET['action'] : ''; + $context['show_quick_login'] = !empty($modSettings['enableVBStyleLogin']) && $user_info['is_guest']; + + // Get some news... + $context['news_lines'] = explode("\n", str_replace("\r", '', trim(addslashes($modSettings['news'])))); + $context['fader_news_lines'] = array(); + for ($i = 0, $n = count($context['news_lines']); $i < $n; $i++) + { + if (trim($context['news_lines'][$i]) == '') + continue; + + // Clean it up for presentation ;). + $context['news_lines'][$i] = parse_bbc(stripslashes(trim($context['news_lines'][$i])), true, 'news' . $i); + + // Gotta be special for the javascript. + $context['fader_news_lines'][$i] = strtr(addslashes($context['news_lines'][$i]), array('/' => '\/', ' (isset($_SESSION['unread_messages']) ? $_SESSION['unread_messages'] : 0)) + $context['user']['popup_messages'] = true; + else + $context['user']['popup_messages'] = false; + $_SESSION['unread_messages'] = $user_info['unread_messages']; + + if (allowedTo('moderate_forum')) + $context['unapproved_members'] = (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($modSettings['approveAccountDeletion']) ? $modSettings['unapprovedMembers'] : 0; + $context['show_open_reports'] = empty($user_settings['mod_prefs']) || $user_settings['mod_prefs'][0] == 1; + + $context['user']['avatar'] = array(); + + // Figure out the avatar... uploaded? + if ($user_info['avatar']['url'] == '' && !empty($user_info['avatar']['id_attach'])) + $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'; + // Full URL? + elseif (substr($user_info['avatar']['url'], 0, 7) == 'http://') + { + $context['user']['avatar']['href'] = $user_info['avatar']['url']; + + if ($modSettings['avatar_action_too_large'] == 'option_html_resize' || $modSettings['avatar_action_too_large'] == 'option_js_resize') + { + if (!empty($modSettings['avatar_max_width_external'])) + $context['user']['avatar']['width'] = $modSettings['avatar_max_width_external']; + if (!empty($modSettings['avatar_max_height_external'])) + $context['user']['avatar']['height'] = $modSettings['avatar_max_height_external']; + } + } + // Otherwise we assume it's server stored? + elseif ($user_info['avatar']['url'] != '') + $context['user']['avatar']['href'] = $modSettings['avatar_url'] . '/' . htmlspecialchars($user_info['avatar']['url']); + + if (!empty($context['user']['avatar'])) + $context['user']['avatar']['image'] = ''; + + // Figure out how long they've been logged in. + $context['user']['total_time_logged_in'] = array( + 'days' => floor($user_info['total_time_logged_in'] / 86400), + 'hours' => floor(($user_info['total_time_logged_in'] % 86400) / 3600), + 'minutes' => floor(($user_info['total_time_logged_in'] % 3600) / 60) + ); + } + else + { + $context['user']['messages'] = 0; + $context['user']['unread_messages'] = 0; + $context['user']['avatar'] = array(); + $context['user']['total_time_logged_in'] = array('days' => 0, 'hours' => 0, 'minutes' => 0); + $context['user']['popup_messages'] = false; + + if (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 1) + $txt['welcome_guest'] .= $txt['welcome_guest_activate']; + + // If we've upgraded recently, go easy on the passwords. + if (!empty($modSettings['disableHashTime']) && ($modSettings['disableHashTime'] == 1 || time() < $modSettings['disableHashTime'])) + $context['disable_login_hashing'] = true; + elseif ($context['browser']['is_ie5'] || $context['browser']['is_ie5.5']) + $context['disable_login_hashing'] = true; + } + + // Setup the main menu items. + setupMenuContext(); + + if (empty($settings['theme_version'])) + $context['show_vBlogin'] = $context['show_quick_login']; + + // This is here because old index templates might still use it. + $context['show_news'] = !empty($settings['enable_news']); + + // This is done to allow theme authors to customize it as they want. + $context['show_pm_popup'] = $context['user']['popup_messages'] && !empty($options['popup_messages']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] != 'pm'); + + // Resize avatars the fancy, but non-GD requiring way. + if ($modSettings['avatar_action_too_large'] == 'option_js_resize' && (!empty($modSettings['avatar_max_width_external']) || !empty($modSettings['avatar_max_height_external']))) + { + $context['html_headers'] .= ' + '; + } + + // This looks weird, but it's because BoardIndex.php references the variable. + $context['common_stats']['latest_member'] = array( + 'id' => $modSettings['latestMember'], + 'name' => $modSettings['latestRealName'], + 'href' => $scripturl . '?action=profile;u=' . $modSettings['latestMember'], + 'link' => '' . $modSettings['latestRealName'] . '', + ); + $context['common_stats'] = array( + 'total_posts' => comma_format($modSettings['totalMessages']), + 'total_topics' => comma_format($modSettings['totalTopics']), + 'total_members' => comma_format($modSettings['totalMembers']), + 'latest_member' => $context['common_stats']['latest_member'], + ); + + if (empty($settings['theme_version'])) + $context['html_headers'] .= ' + '; + + if (!isset($context['page_title'])) + $context['page_title'] = ''; + + // Set some specific vars. + $context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])); + $context['meta_keywords'] = !empty($modSettings['meta_keywords']) ? $smcFunc['htmlspecialchars']($modSettings['meta_keywords']) : ''; +} + +// This is the only template included in the sources... +function template_rawdata() +{ + global $context; + + echo $context['raw_data']; +} + +function template_header() +{ + global $txt, $modSettings, $context, $settings, $user_info, $boarddir, $cachedir; + + setupThemeContext(); + + // Print stuff to prevent caching of pages (except on attachment errors, etc.) + if (empty($context['no_last_modified'])) + { + header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + + // Are we debugging the template/html content? + if (!isset($_REQUEST['xml']) && isset($_GET['debug']) && !$context['browser']['is_ie'] && !WIRELESS) + header('Content-Type: application/xhtml+xml'); + elseif (!isset($_REQUEST['xml']) && !WIRELESS) + header('Content-Type: text/html; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set'])); + } + + header('Content-Type: text/' . (isset($_REQUEST['xml']) ? 'xml' : 'html') . '; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set'])); + + $checked_securityFiles = false; + $showed_banned = false; + foreach ($context['template_layers'] as $layer) + { + loadSubTemplate($layer . '_above', true); + + // May seem contrived, but this is done in case the body and main layer aren't there... + if (in_array($layer, array('body', 'main')) && allowedTo('admin_forum') && !$user_info['is_guest'] && !$checked_securityFiles) + { + $checked_securityFiles = true; + $securityFiles = array('install.php', 'webinstall.php', 'upgrade.php', 'convert.php', 'repair_paths.php', 'repair_settings.php', 'Settings.php~', 'Settings_bak.php~'); + foreach ($securityFiles as $i => $securityFile) + { + if (!file_exists($boarddir . '/' . $securityFile)) + unset($securityFiles[$i]); + } + + if (!empty($securityFiles) || (!empty($modSettings['cache_enable']) && !is_writable($cachedir))) + { + echo ' +
    +

    !!

    +

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

    +

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

    +
    '; + } + } + // If the user is banned from posting inform them of it. + elseif (in_array($layer, array('main', 'body')) && isset($_SESSION['ban']['cannot_post']) && !$showed_banned) + { + $showed_banned = true; + echo ' +
    + ', sprintf($txt['you_are_post_banned'], $user_info['is_guest'] ? $txt['guest_title'] : $user_info['name']); + + if (!empty($_SESSION['ban']['cannot_post']['reason'])) + echo ' +
    ', $_SESSION['ban']['cannot_post']['reason'], '
    '; + + if (!empty($_SESSION['ban']['expire_time'])) + echo ' +
    ', sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)), '
    '; + else + echo ' +
    ', $txt['your_ban_expires_never'], '
    '; + + echo ' +
    '; + } + } + + if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'defaults' && isset($settings['default_template'])) + { + $settings['theme_url'] = $settings['default_theme_url']; + $settings['images_url'] = $settings['default_images_url']; + $settings['theme_dir'] = $settings['default_theme_dir']; + } +} + +// Show the copyright... +function theme_copyright($get_it = false) +{ + global $forum_copyright, $context, $boardurl, $forum_version, $txt, $modSettings; + + // Don't display copyright for things like SSI. + if (!isset($forum_version)) + return; + + // Put in the version... + $forum_copyright = sprintf($forum_copyright, $forum_version); + + echo ' + ' . $forum_copyright . ' + '; +} + +function template_footer() +{ + global $context, $settings, $modSettings, $time_start, $db_count; + + // Show the load time? (only makes sense for the footer.) + $context['show_load_time'] = !empty($modSettings['timeLoadPageEnable']); + $context['load_time'] = round(array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)), 3); + $context['load_queries'] = $db_count; + + if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'defaults' && isset($settings['default_template'])) + { + $settings['theme_url'] = $settings['actual_theme_url']; + $settings['images_url'] = $settings['actual_images_url']; + $settings['theme_dir'] = $settings['actual_theme_dir']; + } + + foreach (array_reverse($context['template_layers']) as $layer) + loadSubTemplate($layer . '_below', true); + +} + +// Debugging. +function db_debug_junk() +{ + global $context, $scripturl, $boarddir, $modSettings, $boarddir; + global $db_cache, $db_count, $db_show_debug, $cache_count, $cache_hits, $txt; + + // Add to Settings.php if you want to show the debugging information. + if (!isset($db_show_debug) || $db_show_debug !== true || (isset($_GET['action']) && $_GET['action'] == 'viewquery') || WIRELESS) + return; + + if (empty($_SESSION['view_queries'])) + $_SESSION['view_queries'] = 0; + if (empty($context['debug']['language_files'])) + $context['debug']['language_files'] = array(); + if (empty($context['debug']['sheets'])) + $context['debug']['sheets'] = array(); + + $files = get_included_files(); + $total_size = 0; + for ($i = 0, $n = count($files); $i < $n; $i++) + { + if (file_exists($files[$i])) + $total_size += filesize($files[$i]); + $files[$i] = strtr($files[$i], array($boarddir => '.')); + } + + $warnings = 0; + if (!empty($db_cache)) + { + foreach ($db_cache as $q => $qq) + { + if (!empty($qq['w'])) + $warnings += count($qq['w']); + } + + $_SESSION['debug'] = &$db_cache; + } + + // Gotta have valid HTML ;). + $temp = ob_get_contents(); + if (function_exists('ob_clean')) + ob_clean(); + else + { + ob_end_clean(); + ob_start('ob_sessrewrite'); + } + + echo preg_replace('~\s*~', '', $temp), ' +
    + ', $txt['debug_templates'], count($context['debug']['templates']), ': ', implode(', ', $context['debug']['templates']), '.
    + ', $txt['debug_subtemplates'], count($context['debug']['sub_templates']), ': ', implode(', ', $context['debug']['sub_templates']), '.
    + ', $txt['debug_language_files'], count($context['debug']['language_files']), ': ', implode(', ', $context['debug']['language_files']), '.
    + ', $txt['debug_stylesheets'], count($context['debug']['sheets']), ': ', implode(', ', $context['debug']['sheets']), '.
    + ', $txt['debug_files_included'], count($files), ' - ', round($total_size / 1024), $txt['debug_kb'], ' (', $txt['debug_show'], ')
    '; + + if (!empty($modSettings['cache_enable']) && !empty($cache_hits)) + { + $entries = array(); + $total_t = 0; + $total_s = 0; + foreach ($cache_hits as $cache_hit) + { + $entries[] = $cache_hit['d'] . ' ' . $cache_hit['k'] . ': ' . sprintf($txt['debug_cache_seconds_bytes'], comma_format($cache_hit['t'], 5), $cache_hit['s']); + $total_t += $cache_hit['t']; + $total_s += $cache_hit['s']; + } + + echo ' + ', $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'], ')
    '; + } + + echo ' + ', $warnings == 0 ? sprintf($txt['debug_queries_used'], (int) $db_count) : sprintf($txt['debug_queries_used_and_warnings'], (int) $db_count, $warnings), '
    +
    '; + + if ($_SESSION['view_queries'] == 1 && !empty($db_cache)) + foreach ($db_cache as $q => $qq) + { + $is_select = substr(trim($qq['q']), 0, 6) == 'SELECT' || preg_match('~^INSERT(?: IGNORE)? INTO \w+(?:\s+\([^)]+\))?\s+SELECT .+$~s', trim($qq['q'])) != 0; + // Temporary tables created in earlier queries are not explainable. + if ($is_select) + { + foreach (array('log_topics_unread', 'topics_posted_in', 'tmp_log_search_topics', 'tmp_log_search_messages') as $tmp) + if (strpos(trim($qq['q']), $tmp) !== false) + { + $is_select = false; + break; + } + } + // But actual creation of the temporary tables are. + elseif (preg_match('~^CREATE TEMPORARY TABLE .+?SELECT .+$~s', trim($qq['q'])) != 0) + $is_select = true; + + // Make the filenames look a bit better. + if (isset($qq['f'])) + $qq['f'] = preg_replace('~^' . preg_quote($boarddir, '~') . '~', '...', $qq['f']); + + echo ' + ', $is_select ? '' : '', nl2br(str_replace("\t", '   ', htmlspecialchars(ltrim($qq['q'], "\n\r")))) . ($is_select ? '' : '
    ') . '
    +    '; + if (!empty($qq['f']) && !empty($qq['l'])) + echo sprintf($txt['debug_query_in_line'], $qq['f'], $qq['l']); + + if (isset($qq['s'], $qq['t']) && isset($txt['debug_query_which_took_at'])) + echo sprintf($txt['debug_query_which_took_at'], round($qq['t'], 8), round($qq['s'], 8)) . '
    '; + elseif (isset($qq['t'])) + echo sprintf($txt['debug_query_which_took'], round($qq['t'], 8)) . '
    '; + echo ' +
    '; + } + + echo ' + ', $txt['debug_' . (empty($_SESSION['view_queries']) ? 'show' : 'hide') . '_queries'], ' +
    '; +} + +// Get an attachment's encrypted filename. If $new is true, won't check for file existence. +function getAttachmentFilename($filename, $attachment_id, $dir = null, $new = false, $file_hash = '') +{ + global $modSettings, $smcFunc; + + // Just make up a nice hash... + if ($new) + return sha1(md5($filename . time()) . mt_rand()); + + // Grab the file hash if it wasn't added. + if ($file_hash === '') + { + $request = $smcFunc['db_query']('', ' + SELECT file_hash + FROM {db_prefix}attachments + WHERE id_attach = {int:id_attach}', + array( + 'id_attach' => $attachment_id, + )); + + if ($smcFunc['db_num_rows']($request) === 0) + return false; + + list ($file_hash) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + // In case of files from the old system, do a legacy call. + if (empty($file_hash)) + return getLegacyAttachmentFilename($filename, $attachment_id, $dir, $new); + + // Are we using multiple directories? + if (!empty($modSettings['currentAttachmentUploadDir'])) + { + if (!is_array($modSettings['attachmentUploadDir'])) + $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); + $path = $modSettings['attachmentUploadDir'][$dir]; + } + else + $path = $modSettings['attachmentUploadDir']; + + return $path . '/' . $attachment_id . '_' . $file_hash; +} + +// Older attachments may still use this function. +function getLegacyAttachmentFilename($filename, $attachment_id, $dir = null, $new = false) +{ + global $modSettings, $db_character_set; + + $clean_name = $filename; + // Remove international characters (windows-1252) + // These lines should never be needed again. Still, behave. + if (empty($db_character_set) || $db_character_set != 'utf8') + { + $clean_name = strtr($filename, + "\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", + 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'); + $clean_name = strtr($clean_name, array("\xde" => 'TH', "\xfe" => + 'th', "\xd0" => 'DH', "\xf0" => 'dh', "\xdf" => 'ss', "\x8c" => 'OE', + "\x9c" => 'oe', "\xc6" => 'AE', "\xe6" => 'ae', "\xb5" => 'u')); + } + // Sorry, no spaces, dots, or anything else but letters allowed. + $clean_name = preg_replace(array('/\s/', '/[^\w_\.\-]/'), array('_', ''), $clean_name); + + $enc_name = $attachment_id . '_' . strtr($clean_name, '.', '_') . md5($clean_name); + $clean_name = preg_replace('~\.[\.]+~', '.', $clean_name); + + if ($attachment_id == false || ($new && empty($modSettings['attachmentEncryptFilenames']))) + return $clean_name; + elseif ($new) + return $enc_name; + + // Are we using multiple directories? + if (!empty($modSettings['currentAttachmentUploadDir'])) + { + if (!is_array($modSettings['attachmentUploadDir'])) + $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); + $path = $modSettings['attachmentUploadDir'][$dir]; + } + else + $path = $modSettings['attachmentUploadDir']; + + if (file_exists($path . '/' . $enc_name)) + $filename = $path . '/' . $enc_name; + else + $filename = $path . '/' . $clean_name; + + return $filename; +} + +// Convert a single IP to a ranged IP. +function ip2range($fullip) +{ + // Pretend that 'unknown' is 255.255.255.255. (since that can't be an IP anyway.) + if ($fullip == 'unknown') + $fullip = '255.255.255.255'; + + $ip_parts = explode('.', $fullip); + $ip_array = array(); + + if (count($ip_parts) != 4) + return array(); + + for ($i = 0; $i < 4; $i++) + { + if ($ip_parts[$i] == '*') + $ip_array[$i] = array('low' => '0', 'high' => '255'); + elseif (preg_match('/^(\d{1,3})\-(\d{1,3})$/', $ip_parts[$i], $range) == 1) + $ip_array[$i] = array('low' => $range[1], 'high' => $range[2]); + elseif (is_numeric($ip_parts[$i])) + $ip_array[$i] = array('low' => $ip_parts[$i], 'high' => $ip_parts[$i]); + } + + return $ip_array; +} + +// Lookup an IP; try shell_exec first because we can do a timeout on it. +function host_from_ip($ip) +{ + global $modSettings; + + if (($host = cache_get_data('hostlookup-' . $ip, 600)) !== null) + return $host; + $t = microtime(); + + // If we can't access nslookup/host, PHP 4.1.x might just crash. + if (@version_compare(PHP_VERSION, '4.2.0') == -1) + $host = false; + + // Try the Linux host command, perhaps? + if (!isset($host) && (strpos(strtolower(PHP_OS), 'win') === false || strpos(strtolower(PHP_OS), 'darwin') !== false) && mt_rand(0, 1) == 1) + { + if (!isset($modSettings['host_to_dis'])) + $test = @shell_exec('host -W 1 ' . @escapeshellarg($ip)); + else + $test = @shell_exec('host ' . @escapeshellarg($ip)); + + // Did host say it didn't find anything? + if (strpos($test, 'not found') !== false) + $host = ''; + // Invalid server option? + elseif ((strpos($test, 'invalid option') || strpos($test, 'Invalid query name 1')) && !isset($modSettings['host_to_dis'])) + updateSettings(array('host_to_dis' => 1)); + // Maybe it found something, after all? + elseif (preg_match('~\s([^\s]+?)\.\s~', $test, $match) == 1) + $host = $match[1]; + } + + // This is nslookup; usually only Windows, but possibly some Unix? + if (!isset($host) && strpos(strtolower(PHP_OS), 'win') !== false && strpos(strtolower(PHP_OS), 'darwin') === false && mt_rand(0, 1) == 1) + { + $test = @shell_exec('nslookup -timeout=1 ' . @escapeshellarg($ip)); + if (strpos($test, 'Non-existent domain') !== false) + $host = ''; + elseif (preg_match('~Name:\s+([^\s]+)~', $test, $match) == 1) + $host = $match[1]; + } + + // This is the last try :/. + if (!isset($host) || $host === false) + $host = @gethostbyaddr($ip); + + // It took a long time, so let's cache it! + if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.5) + cache_put_data('hostlookup-' . $ip, $host, 600); + + return $host; +} + +// Chops a string into words and prepares them to be inserted into (or searched from) the database. +function text2words($text, $max_chars = 20, $encrypt = false) +{ + global $smcFunc, $context; + + // Step 1: Remove entities/things we don't consider words: + $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('
    ' => ' '))); + + // Step 2: Entities we left to letters, where applicable, lowercase. + $words = un_htmlspecialchars($smcFunc['strtolower']($words)); + + // Step 3: Ready to split apart and index! + $words = explode(' ', $words); + + if ($encrypt) + { + $possible_chars = array_flip(array_merge(range(46, 57), range(65, 90), range(97, 122))); + $returned_ints = array(); + foreach ($words as $word) + { + if (($word = trim($word, '-_\'')) !== '') + { + $encrypted = substr(crypt($word, 'uk'), 2, $max_chars); + $total = 0; + for ($i = 0; $i < $max_chars; $i++) + $total += $possible_chars[ord($encrypted{$i})] * pow(63, $i); + $returned_ints[] = $max_chars == 4 ? min($total, 16777215) : $total; + } + } + return array_unique($returned_ints); + } + else + { + // Trim characters before and after and add slashes for database insertion. + $returned_words = array(); + foreach ($words as $word) + if (($word = trim($word, '-_\'')) !== '') + $returned_words[] = $max_chars === null ? $word : substr($word, 0, $max_chars); + + // Filter out all words that occur more than once. + return array_unique($returned_words); + } +} + +// Creates an image/text button +function create_button($name, $alt, $label = '', $custom = '', $force_use = false) +{ + global $settings, $txt, $context; + + // Does the current loaded theme have this and we are not forcing the usage of this function? + if (function_exists('template_create_button') && !$force_use) + return template_create_button($name, $alt, $label = '', $custom = ''); + + if (!$settings['use_image_buttons']) + return $txt[$alt]; + elseif (!empty($settings['use_buttons'])) + return '' . $txt[$alt] . '' . ($label != '' ? '' . $txt[$label] . '' : ''); + else + return '' . $txt[$alt] . ''; +} + +// Empty out the cache folder. +function clean_cache($type = '') +{ + global $cachedir, $sourcedir; + + // No directory = no game. + if (!is_dir($cachedir)) + return; + + // Remove the files in SMF's own disk cache, if any + $dh = opendir($cachedir); + while ($file = readdir($dh)) + { + if ($file != '.' && $file != '..' && $file != 'index.php' && $file != '.htaccess' && (!$type || substr($file, 0, strlen($type)) == $type)) + @unlink($cachedir . '/' . $file); + } + closedir($dh); + + // Invalidate cache, to be sure! + // ... as long as Load.php can be modified, anyway. + @touch($sourcedir . '/' . 'Load.php'); + clearstatcache(); +} + +// Load classes that are both (E_STRICT) PHP 4 and PHP 5 compatible. +function loadClassFile($filename) +{ + global $sourcedir; + static $files_included = array(); + + if (!file_exists($sourcedir . '/' . $filename)) + fatal_lang_error('error_bad_file', 'general', array($sourcedir . '/' . $filename)); + + // Using a version below PHP 5.0? Do a compatibility conversion. + if (@version_compare(PHP_VERSION, '5.0.0') != 1) + { + // Check if it was included before. + if (in_array($filename, $files_included)) + return; + + // Make sure we don't include it again. + $files_included[] = $filename; + + // Do some replacements to make it PHP 4 compatible. + eval('?' . '>' . preg_replace(array( + '~class\s+([\w-_]+)([^}]+)function\s+__construct\s*\(~', + '~([\s\t]+)public\s+\$~', + '~([\s\t]+)private\s+\$~', + '~([\s\t]+)protected\s+\$~', + '~([\s\t]+)public\s+function\s+~', + '~([\s\t]+)private\s+function\s+~', + '~([\s\t]+)protected\s+function\s+~', + ), array( + 'class $1$2function $1(', + '$1var $', + '$1var $', + '$1var $', + '$1function ', + '$1function ', + '$1function ', + ), rtrim(file_get_contents($sourcedir . '/' . $filename)))); + } + else + require_once($sourcedir . '/' . $filename); +} + +function setupMenuContext() +{ + global $context, $modSettings, $user_info, $txt, $scripturl; + + // Set up the menu privileges. + $context['allow_search'] = allowedTo('search_posts'); + $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')); + $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')); + $context['allow_memberlist'] = allowedTo('view_mlist'); + $context['allow_calendar'] = allowedTo('calendar_view') && !empty($modSettings['cal_enabled']); + $context['allow_moderation_center'] = $context['user']['can_mod']; + $context['allow_pm'] = allowedTo('pm_read'); + + $cacheTime = $modSettings['lastActive'] * 60; + + // All the buttons we can possible want and then some, try pulling the final list of buttons from cache first. + if (($menu_buttons = cache_get_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $cacheTime)) === null || time() - $cacheTime <= $modSettings['settings_updated']) + { + $buttons = array( + 'home' => array( + 'title' => $txt['home'], + 'href' => $scripturl, + 'show' => true, + 'sub_buttons' => array( + ), + 'is_last' => $context['right_to_left'], + ), + 'help' => array( + 'title' => $txt['help'], + 'href' => $scripturl . '?action=help', + 'show' => true, + 'sub_buttons' => array( + ), + ), + 'search' => array( + 'title' => $txt['search'], + 'href' => $scripturl . '?action=search', + 'show' => $context['allow_search'], + 'sub_buttons' => array( + ), + ), + 'admin' => array( + 'title' => $txt['admin'], + 'href' => $scripturl . '?action=admin', + 'show' => $context['allow_admin'], + 'sub_buttons' => array( + 'featuresettings' => array( + 'title' => $txt['modSettings_title'], + 'href' => $scripturl . '?action=admin;area=featuresettings', + 'show' => allowedTo('admin_forum'), + ), + 'packages' => array( + 'title' => $txt['package'], + 'href' => $scripturl . '?action=admin;area=packages', + 'show' => allowedTo('admin_forum'), + ), + 'errorlog' => array( + 'title' => $txt['errlog'], + 'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc', + 'show' => allowedTo('admin_forum') && !empty($modSettings['enableErrorLogging']), + ), + 'permissions' => array( + 'title' => $txt['edit_permissions'], + 'href' => $scripturl . '?action=admin;area=permissions', + 'show' => allowedTo('manage_permissions'), + 'is_last' => true, + ), + ), + ), + 'moderate' => array( + 'title' => $txt['moderate'], + 'href' => $scripturl . '?action=moderate', + 'show' => $context['allow_moderation_center'], + 'sub_buttons' => array( + 'modlog' => array( + 'title' => $txt['modlog_view'], + 'href' => $scripturl . '?action=moderate;area=modlog', + 'show' => !empty($modSettings['modlog_enabled']) && !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1', + ), + 'poststopics' => array( + 'title' => $txt['mc_unapproved_poststopics'], + 'href' => $scripturl . '?action=moderate;area=postmod;sa=posts', + 'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']), + ), + 'attachments' => array( + 'title' => $txt['mc_unapproved_attachments'], + 'href' => $scripturl . '?action=moderate;area=attachmod;sa=attachments', + 'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']), + ), + 'reports' => array( + 'title' => $txt['mc_reported_posts'], + 'href' => $scripturl . '?action=moderate;area=reports', + 'show' => !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1', + 'is_last' => true, + ), + ), + ), + 'profile' => array( + 'title' => $txt['profile'], + 'href' => $scripturl . '?action=profile', + 'show' => $context['allow_edit_profile'], + 'sub_buttons' => array( + 'summary' => array( + 'title' => $txt['summary'], + 'href' => $scripturl . '?action=profile', + 'show' => true, + ), + 'account' => array( + 'title' => $txt['account'], + 'href' => $scripturl . '?action=profile;area=account', + 'show' => allowedTo(array('profile_identity_any', 'profile_identity_own', 'manage_membergroups')), + ), + 'profile' => array( + 'title' => $txt['forumprofile'], + 'href' => $scripturl . '?action=profile;area=forumprofile', + 'show' => allowedTo(array('profile_extra_any', 'profile_extra_own')), + 'is_last' => true, + ), + ), + ), + 'pm' => array( + 'title' => $txt['pm_short'], + 'href' => $scripturl . '?action=pm', + 'show' => $context['allow_pm'], + 'sub_buttons' => array( + 'pm_read' => array( + 'title' => $txt['pm_menu_read'], + 'href' => $scripturl . '?action=pm', + 'show' => allowedTo('pm_read'), + ), + 'pm_send' => array( + 'title' => $txt['pm_menu_send'], + 'href' => $scripturl . '?action=pm;sa=send', + 'show' => allowedTo('pm_send'), + 'is_last' => true, + ), + ), + ), + 'calendar' => array( + 'title' => $txt['calendar'], + 'href' => $scripturl . '?action=calendar', + 'show' => $context['allow_calendar'], + 'sub_buttons' => array( + 'view' => array( + 'title' => $txt['calendar_menu'], + 'href' => $scripturl . '?action=calendar', + 'show' => allowedTo('calendar_post'), + ), + 'post' => array( + 'title' => $txt['calendar_post_event'], + 'href' => $scripturl . '?action=calendar;sa=post', + 'show' => allowedTo('calendar_post'), + 'is_last' => true, + ), + ), + ), + 'mlist' => array( + 'title' => $txt['members_title'], + 'href' => $scripturl . '?action=mlist', + 'show' => $context['allow_memberlist'], + 'sub_buttons' => array( + 'mlist_view' => array( + 'title' => $txt['mlist_menu_view'], + 'href' => $scripturl . '?action=mlist', + 'show' => true, + ), + 'mlist_search' => array( + 'title' => $txt['mlist_search'], + 'href' => $scripturl . '?action=mlist;sa=search', + 'show' => true, + 'is_last' => true, + ), + ), + ), + 'login' => array( + 'title' => $txt['login'], + 'href' => $scripturl . '?action=login', + 'show' => $user_info['is_guest'], + 'sub_buttons' => array( + ), + ), + 'register' => array( + 'title' => $txt['register'], + 'href' => $scripturl . '?action=register', + 'show' => $user_info['is_guest'], + 'sub_buttons' => array( + ), + 'is_last' => !$context['right_to_left'], + ), + 'logout' => array( + 'title' => $txt['logout'], + 'href' => $scripturl . '?action=logout;%1$s=%2$s', + 'show' => !$user_info['is_guest'], + 'sub_buttons' => array( + ), + 'is_last' => !$context['right_to_left'], + ), + ); + + // Allow editing menu buttons easily. + call_integration_hook('integrate_menu_buttons', array(&$buttons)); + + // Now we put the buttons in the context so the theme can use them. + $menu_buttons = array(); + foreach ($buttons as $act => $button) + if (!empty($button['show'])) + { + $button['active_button'] = false; + + // Make sure the last button truely is the last button. + if (!empty($button['is_last'])) + { + if (isset($last_button)) + unset($menu_buttons[$last_button]['is_last']); + $last_button = $act; + } + + // Go through the sub buttons if there are any. + if (!empty($button['sub_buttons'])) + foreach ($button['sub_buttons'] as $key => $subbutton) + { + if (empty($subbutton['show'])) + unset($button['sub_buttons'][$key]); + + // 2nd level sub buttons next... + if (!empty($subbutton['sub_buttons'])) + { + foreach ($subbutton['sub_buttons'] as $key2 => $sub_button2) + { + if (empty($sub_button2['show'])) + unset($button['sub_buttons'][$key]['sub_buttons'][$key2]); + } + } + } + + $menu_buttons[$act] = $button; + } + + if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2) + cache_put_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $menu_buttons, $cacheTime); + } + + $context['menu_buttons'] = $menu_buttons; + + // Logging out requires the session id in the url. + if (isset($context['menu_buttons']['logout'])) + $context['menu_buttons']['logout']['href'] = sprintf($context['menu_buttons']['logout']['href'], $context['session_var'], $context['session_id']); + + // Figure out which action we are doing so we can set the active tab. + // Default to home. + $current_action = 'home'; + + if (isset($context['menu_buttons'][$context['current_action']])) + $current_action = $context['current_action']; + elseif ($context['current_action'] == 'search2') + $current_action = 'search'; + elseif ($context['current_action'] == 'theme') + $current_action = isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'pick' ? 'profile' : 'admin'; + elseif ($context['current_action'] == 'register2') + $current_action = 'register'; + elseif ($context['current_action'] == 'login2' || ($user_info['is_guest'] && $context['current_action'] == 'reminder')) + $current_action = 'login'; + elseif ($context['current_action'] == 'groups' && $context['allow_moderation_center']) + $current_action = 'moderate'; + + $context['menu_buttons'][$current_action]['active_button'] = true; + + if (!$user_info['is_guest'] && $context['user']['unread_messages'] > 0 && isset($context['menu_buttons']['pm'])) + { + $context['menu_buttons']['pm']['alttitle'] = $context['menu_buttons']['pm']['title'] . ' [' . $context['user']['unread_messages'] . ']'; + $context['menu_buttons']['pm']['title'] .= ' [' . $context['user']['unread_messages'] . ']'; + } +} + +// Generate a random seed and ensure it's stored in settings. +function smf_seed_generator() +{ + global $modSettings; + + // Never existed? + if (empty($modSettings['rand_seed'])) + { + $modSettings['rand_seed'] = microtime() * 1000000; + updateSettings(array('rand_seed' => $modSettings['rand_seed'])); + } + + if (@version_compare(PHP_VERSION, '4.2.0') == -1) + { + $seed = ($modSettings['rand_seed'] + ((double) microtime() * 1000003)) & 0x7fffffff; + mt_srand($seed); + } + + // Change the seed. + updateSettings(array('rand_seed' => mt_rand())); +} + +// Process functions of an integration hook. +function call_integration_hook($hook, $parameters = array()) +{ + global $modSettings; + + $results = array(); + if (empty($modSettings[$hook])) + return $results; + + $functions = explode(',', $modSettings[$hook]); + + // Loop through each function. + foreach ($functions as $function) + { + $function = trim($function); + $call = strpos($function, '::') !== false ? explode('::', $function) : $function; + + // Is it valid? + if (is_callable($call)) + $results[$function] = call_user_func_array($call, $parameters); + } + + return $results; +} + +// Add a function for integration hook. +function add_integration_function($hook, $function, $permanent = true) +{ + global $smcFunc, $modSettings; + + // Is it going to be permanent? + if ($permanent) + { + $request = $smcFunc['db_query']('', ' + SELECT value + FROM {db_prefix}settings + WHERE variable = {string:variable}', + array( + 'variable' => $hook, + ) + ); + list($current_functions) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (!empty($current_functions)) + { + $current_functions = explode(',', $current_functions); + if (in_array($function, $current_functions)) + return; + + $permanent_functions = array_merge($current_functions, array($function)); + } + else + $permanent_functions = array($function); + + updateSettings(array($hook => implode(',', $permanent_functions))); + } + + // Make current function list usable. + $functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]); + + // Do nothing, if it's already there. + if (in_array($function, $functions)) + return; + + $functions[] = $function; + $modSettings[$hook] = implode(',', $functions); +} + +// Decode numeric html entities to their ascii or UTF8 equivalent character. +function replaceEntities__callback($matches) +{ + global $context; + + if (!isset($matches[2])) + return ''; + + $num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2]; + + // remove left to right / right to left overrides + if ($num === 0x202D || $num === 0x202E) + return ''; + + // Quote, Ampersand, Apostrophe, Less/Greater Than get html replaced + if (in_array($num, array(0x22, 0x26, 0x27, 0x3C, 0x3E))) + return '&#' . $num . ';'; + + if (empty($context['utf8'])) + { + // no control characters + if ($num < 0x20) + return ''; + // text is text + elseif ($num < 0x80) + return chr($num); + // all others get html-ised + else + return '&#' . $matches[2] . ';'; + } + else + { + // <0x20 are control characters, 0x20 is a space, > 0x10FFFF is past the end of the utf8 character set + // 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text) + if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF)) + return ''; + // <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and puncuation + elseif ($num < 0x80) + return chr($num); + // <0x800 (2048) + elseif ($num < 0x800) + return chr(($num >> 6) + 192) . chr(($num & 63) + 128); + // < 0x10000 (65536) + elseif ($num < 0x10000) + return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128); + // <= 0x10FFFF (1114111) + else + return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128); + } +} + +// Converts html entities to utf8 equivalents. +function fixchar__callback($matches) +{ + if (!isset($matches[1])) + return ''; + + $num = $matches[1][0] === 'x' ? hexdec(substr($matches[1], 1)) : (int) $matches[1]; + + // <0x20 are control characters, > 0x10FFFF is past the end of the utf8 character set + // 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text), 0x202D-E are left to right overrides + if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202D || $num === 0x202E) + return ''; + // <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and puncuation + elseif ($num < 0x80) + return chr($num); + // <0x800 (2048) + elseif ($num < 0x800) + return chr(($num >> 6) + 192) . chr(($num & 63) + 128); + // < 0x10000 (65536) + elseif ($num < 0x10000) + return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128); + // <= 0x10FFFF (1114111) + else + return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128); +} + +// Strips out invalid html entities, replaces others with html style { codes. +function entity_fix__callback($matches) +{ + if (!isset($matches[2])) + return ''; + + $num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2]; + + // we don't allow control characters, characters out of range, byte markers, etc + if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num == 0x202D || $num == 0x202E) + return ''; + else + return '&#' . $num . ';'; +} + +// Remove an integration hook function. +function remove_integration_function($hook, $function) +{ + global $smcFunc, $modSettings; + + // Get the permanent functions. + $request = $smcFunc['db_query']('', ' + SELECT value + FROM {db_prefix}settings + WHERE variable = {string:variable}', + array( + 'variable' => $hook, + ) + ); + list($current_functions) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (!empty($current_functions)) + { + $current_functions = explode(',', $current_functions); + + if (in_array($function, $current_functions)) + updateSettings(array($hook => implode(',', array_diff($current_functions, array($function))))); + } + + // Turn the function list into something usable. + $functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]); + + // You can only remove it if it's available. + if (!in_array($function, $functions)) + return; + + $functions = array_diff($functions, array($function)); + $modSettings[$hook] = implode(',', $functions); +} + +?> \ No newline at end of file diff --git a/Sources/Subscriptions-PayPal.php b/Sources/Subscriptions-PayPal.php new file mode 100644 index 0000000..3198a28 --- /dev/null +++ b/Sources/Subscriptions-PayPal.php @@ -0,0 +1,332 @@ + $txt['paypal_email_desc']), + ); + + return $setting_data; + } + + // Is this enabled for new payments? + public function gatewayEnabled() + { + global $modSettings; + + return !empty($modSettings['paypal_email']); + } + + // What do we want? + public function fetchGatewayFields($unique_id, $sub_data, $value, $period, $return_url) + { + global $modSettings, $txt, $boardurl; + + $return_data = array( + 'form' => 'https://www.' . (!empty($modSettings['paidsubs_test']) ? 'sandbox.' : '') . 'paypal.com/cgi-bin/webscr', + 'id' => 'paypal', + 'hidden' => array(), + 'title' => $txt['paypal'], + 'desc' => $txt['paid_confirm_paypal'], + 'submit' => $txt['paid_paypal_order'], + 'javascript' => '', + ); + + // All the standard bits. + $return_data['hidden']['business'] = $modSettings['paypal_email']; + $return_data['hidden']['item_name'] = $sub_data['name'] . ' ' . $txt['subscription']; + $return_data['hidden']['item_number'] = $unique_id; + $return_data['hidden']['currency_code'] = strtoupper($modSettings['paid_currency_code']); + $return_data['hidden']['no_shipping'] = 1; + $return_data['hidden']['no_note'] = 1; + $return_data['hidden']['amount'] = $value; + $return_data['hidden']['cmd'] = !$sub_data['repeatable'] ? '_xclick' : '_xclick-subscriptions'; + $return_data['hidden']['return'] = $return_url; + $return_data['hidden']['a3'] = $value; + $return_data['hidden']['src'] = 1; + $return_data['hidden']['notify_url'] = $boardurl . '/subscriptions.php'; + + // Now stuff dependant on what we're doing. + if ($sub_data['flexible']) + { + $return_data['hidden']['p3'] = 1; + $return_data['hidden']['t3'] = strtoupper(substr($period, 0, 1)); + } + else + { + preg_match('~(\d*)(\w)~', $sub_data['real_length'], $match); + $unit = $match[1]; + $period = $match[2]; + + $return_data['hidden']['p3'] = $unit; + $return_data['hidden']['t3'] = $period; + } + + // If it's repeatable do soem javascript to respect this idea. + if (!empty($sub_data['repeatable'])) + $return_data['javascript'] = ' + document.write(\'
    \'); + + function switchPaypalRecur() + { + document.getElementById("paypal_cmd").value = document.getElementById("do_paypal_recur").checked ? "_xclick-subscriptions" : "_xclick"; + }'; + + return $return_data; + } +} + +class paypal_payment +{ + private $return_data; + + // This function returns true/false for whether this gateway thinks the data is intended for it. + public function isValid() + { + global $modSettings; + + // Has the user set up an email address? + if (empty($modSettings['paypal_email'])) + return false; + // Check the correct transaction types are even here. + if ((!isset($_POST['txn_type']) && !isset($_POST['payment_status'])) || (!isset($_POST['business']) && !isset($_POST['receiver_email']))) + return false; + // Correct email address? + if (!isset($_POST['business'])) + $_POST['business'] = $_POST['receiver_email']; + if ($modSettings['paypal_email'] != $_POST['business'] && (empty($modSettings['paypal_additional_emails']) || !in_array($_POST['business'], explode(',', $modSettings['paypal_additional_emails'])))) + return false; + return true; + } + + // Validate all the data was valid. + public function precheck() + { + global $modSettings, $txt; + + // Put this to some default value. + if (!isset($_POST['txn_type'])) + $_POST['txn_type'] = ''; + + // Build the request string - starting with the minimum requirement. + $requestString = 'cmd=_notify-validate'; + + // Now my dear, add all the posted bits. + foreach ($_POST as $k => $v) + $requestString .= '&' . $k . '=' . urlencode($v); + + // Can we use curl? + if (function_exists('curl_init') && $curl = curl_init((!empty($modSettings['paidsubs_test']) ? 'https://www.sandbox.' : 'http://www.') . 'paypal.com/cgi-bin/webscr')) + { + // Set the post data. + curl_setopt($curl, CURLOPT_POST, true); + curl_setopt($curl, CURLOPT_POSTFIELDSIZE, 0); + curl_setopt($curl, CURLOPT_POSTFIELDS, $requestString); + + curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($curl, CURLOPT_FORBID_REUSE, 1); + curl_setopt($curl, CURLOPT_HTTPHEADER, array( + 'Host: www.' . (!empty($modSettings['paidsubs_test']) ? 'sandbox.' : '') . 'paypal.com', + 'Connection: close' + )); + + // Fetch the data returned as a string. + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + + // Fetch the data. + $this->return_data = curl_exec($curl); + + // Close the session. + curl_close($curl); + } + // Otherwise good old HTTP. + else + { + // Setup the headers. + $header = 'POST /cgi-bin/webscr HTTP/1.1' . "\r\n"; + $header .= 'Content-Type: application/x-www-form-urlencoded' . "\r\n"; + $header .= 'Host: www.' . (!empty($modSettings['paidsubs_test']) ? 'sandbox.' : '') . 'paypal.com' . "\r\n"; + $header .= 'Content-Length: ' . strlen ($requestString) . "\r\n"; + $header .= 'Connection: close' . "\r\n\r\n"; + + // Open the connection. + if (!empty($modSettings['paidsubs_test'])) + $fp = fsockopen('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30); + else + $fp = fsockopen('www.paypal.com', 80, $errno, $errstr, 30); + + // Did it work? + if (!$fp) + generateSubscriptionError($txt['paypal_could_not_connect']); + + // Put the data to the port. + fputs($fp, $header . $requestString); + + // Get the data back... + while (!feof($fp)) + { + $this->return_data = fgets($fp, 1024); + if (strcmp(trim($this->return_data), 'VERIFIED') == 0) + break; + } + + // Clean up. + fclose($fp); + } + + // If this isn't verified then give up... + // !! This contained a comment "send an email", but we don't appear to send any? + if (strcmp(trim($this->return_data), 'VERIFIED') != 0) + exit; + + // Check that this is intended for us. + if ($modSettings['paypal_email'] != $_POST['business'] && (empty($modSettings['paypal_additional_emails']) || !in_array($_POST['business'], explode(',', $modSettings['paypal_additional_emails'])))) + exit; + + // Is this a subscription - and if so it's it a secondary payment that we need to process? + if ($this->isSubscription() && (empty($_POST['item_number']) || strpos($_POST['item_number'], '+') === false)) + // Calculate the subscription it relates to! + $this->_findSubscription(); + + // Verify the currency! + if (strtolower($_POST['mc_currency']) != $modSettings['paid_currency_code']) + exit; + + // Can't exist if it doesn't contain anything. + if (empty($_POST['item_number'])) + exit; + + // Return the id_sub and id_member + return explode('+', $_POST['item_number']); + } + + // Is this a refund? + public function isRefund() + { + if ($_POST['payment_status'] == 'Refunded' || $_POST['payment_status'] == 'Reversed' || $_POST['txn_type'] == 'Refunded' || ($_POST['txn_type'] == 'reversal' && $_POST['payment_status'] == 'Completed')) + return true; + else + return false; + } + + // Is this a subscription? + public function isSubscription() + { + if (substr($_POST['txn_type'], 0, 14) == 'subscr_payment' && $_POST['payment_status'] == 'Completed') + return true; + else + return false; + } + + // Is this a normal payment? + public function isPayment() + { + if ($_POST['payment_status'] == 'Completed' && $_POST['txn_type'] == 'web_accept') + return true; + else + return false; + } + + // How much was paid? + public function getCost() + { + return (isset($_POST['tax']) ? $_POST['tax'] : 0) + $_POST['mc_gross']; + } + + // exit. + public function close() + { + global $smcFunc, $subscription_id; + + // If it's a subscription record the reference. + if ($_POST['txn_type'] == 'subscr_payment' && !empty($_POST['subscr_id'])) + { + $_POST['subscr_id'] = $_POST['subscr_id']; + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_subscribed + SET vendor_ref = {string:vendor_ref} + WHERE id_sublog = {int:current_subscription}', + array( + 'current_subscription' => $subscription_id, + 'vendor_ref' => $_POST['subscr_id'], + ) + ); + } + + exit(); + } + + // A private function to find out the subscription details. + private function _findSubscription() + { + global $smcFunc; + + // Assume we have this? + if (empty($_POST['subscr_id'])) + return false; + + // Do we have this in the database? + $request = $smcFunc['db_query']('', ' + SELECT id_member, id_subscribe + FROM {db_prefix}log_subscribed + WHERE vendor_ref = {string:vendor_ref} + LIMIT 1', + array( + 'vendor_ref' => $_POST['subscr_id'], + ) + ); + // No joy? + if ($smcFunc['db_num_rows']($request) == 0) + { + // Can we identify them by email? + if (!empty($_POST['payer_email'])) + { + $smcFunc['db_free_result']($request); + $request = $smcFunc['db_query']('', ' + SELECT ls.id_member, ls.id_subscribe + FROM {db_prefix}log_subscribed AS ls + INNER JOIN {db_prefix}members AS mem ON (mem.id_member = ls.id_member) + WHERE mem.email_address = {string:payer_email} + LIMIT 1', + array( + 'payer_email' => $_POST['payer_email'], + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + return false; + } + else + return false; + } + list ($member_id, $subscription_id) = $smcFunc['db_fetch_row']($request); + $_POST['item_number'] = $member_id . '+' . $subscription_id; + $smcFunc['db_free_result']($request); + } +} + +?> \ No newline at end of file diff --git a/Sources/Themes.php b/Sources/Themes.php new file mode 100644 index 0000000..fc4d1f5 --- /dev/null +++ b/Sources/Themes.php @@ -0,0 +1,2163 @@ + value).) + - tar and gzip the directory - and you're done! + - please include any special license in a license.txt file. + // !!! Thumbnail? +*/ + +// Subaction handler. +function ThemesMain() +{ + global $txt, $context, $scripturl; + + // Load the important language files... + loadLanguage('Themes'); + loadLanguage('Settings'); + + // No funny business - guests only. + is_not_guest(); + + // Default the page title to Theme Administration by default. + $context['page_title'] = $txt['themeadmin_title']; + + // Theme administration, removal, choice, or installation... + $subActions = array( + 'admin' => 'ThemeAdmin', + 'list' => 'ThemeList', + 'reset' => 'SetThemeOptions', + 'settings' => 'SetThemeSettings', + 'options' => 'SetThemeOptions', + 'install' => 'ThemeInstall', + 'remove' => 'RemoveTheme', + 'pick' => 'PickTheme', + 'edit' => 'EditTheme', + 'copy' => 'CopyTemplate', + ); + + // !!! Layout Settings? + if (!empty($context['admin_menu_name'])) + { + $context[$context['admin_menu_name']]['tab_data'] = array( + 'title' => $txt['themeadmin_title'], + 'help' => 'themes', + 'description' => $txt['themeadmin_description'], + 'tabs' => array( + 'admin' => array( + 'description' => $txt['themeadmin_admin_desc'], + ), + 'list' => array( + 'description' => $txt['themeadmin_list_desc'], + ), + 'reset' => array( + 'description' => $txt['themeadmin_reset_desc'], + ), + 'edit' => array( + 'description' => $txt['themeadmin_edit_desc'], + ), + ), + ); + } + + // Follow the sa or just go to administration. + if (isset($_GET['sa']) && !empty($subActions[$_GET['sa']])) + $subActions[$_GET['sa']](); + else + $subActions['admin'](); +} + +function ThemeAdmin() +{ + global $context, $boarddir, $modSettings, $smcFunc; + + loadLanguage('Admin'); + isAllowedTo('admin_forum'); + + // If we aren't submitting - that is, if we are about to... + if (!isset($_POST['submit'])) + { + loadTemplate('Themes'); + + // Make our known themes a little easier to work with. + $knownThemes = !empty($modSettings['knownThemes']) ? explode(',',$modSettings['knownThemes']) : array(); + + // Load up all the themes. + $request = $smcFunc['db_query']('', ' + SELECT id_theme, value AS name + FROM {db_prefix}themes + WHERE variable = {string:name} + AND id_member = {int:no_member} + ORDER BY id_theme', + array( + 'no_member' => 0, + 'name' => 'name', + ) + ); + $context['themes'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['themes'][] = array( + 'id' => $row['id_theme'], + 'name' => $row['name'], + 'known' => in_array($row['id_theme'], $knownThemes), + ); + $smcFunc['db_free_result']($request); + + // Can we create a new theme? + $context['can_create_new'] = is_writable($boarddir . '/Themes'); + $context['new_theme_dir'] = substr(realpath($boarddir . '/Themes/default'), 0, -7); + + // Look for a non existent theme directory. (ie theme87.) + $theme_dir = $boarddir . '/Themes/theme'; + $i = 1; + while (file_exists($theme_dir . $i)) + $i++; + $context['new_theme_name'] = 'theme' . $i; + } + else + { + checkSession(); + + if (isset($_POST['options']['known_themes'])) + foreach ($_POST['options']['known_themes'] as $key => $id) + $_POST['options']['known_themes'][$key] = (int) $id; + else + fatal_lang_error('themes_none_selectable', false); + + if (!in_array($_POST['options']['theme_guests'], $_POST['options']['known_themes'])) + fatal_lang_error('themes_default_selectable', false); + + // Commit the new settings. + updateSettings(array( + 'theme_allow' => $_POST['options']['theme_allow'], + 'theme_guests' => $_POST['options']['theme_guests'], + 'knownThemes' => implode(',', $_POST['options']['known_themes']), + )); + if ((int) $_POST['theme_reset'] == 0 || in_array($_POST['theme_reset'], $_POST['options']['known_themes'])) + updateMemberData(null, array('id_theme' => (int) $_POST['theme_reset'])); + + redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=admin'); + } +} + +function ThemeList() +{ + global $context, $boarddir, $boardurl, $smcFunc; + + loadLanguage('Admin'); + isAllowedTo('admin_forum'); + + if (isset($_POST['submit'])) + { + checkSession(); + + $request = $smcFunc['db_query']('', ' + SELECT id_theme, variable, value + FROM {db_prefix}themes + WHERE variable IN ({string:theme_dir}, {string:theme_url}, {string:images_url}, {string:base_theme_dir}, {string:base_theme_url}, {string:base_images_url}) + AND id_member = {int:no_member}', + array( + 'no_member' => 0, + 'theme_dir' => 'theme_dir', + 'theme_url' => 'theme_url', + 'images_url' => 'images_url', + 'base_theme_dir' => 'base_theme_dir', + 'base_theme_url' => 'base_theme_url', + 'base_images_url' => 'base_images_url', + ) + ); + $themes = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $themes[$row['id_theme']][$row['variable']] = $row['value']; + $smcFunc['db_free_result']($request); + + $setValues = array(); + foreach ($themes as $id => $theme) + { + if (file_exists($_POST['reset_dir'] . '/' . basename($theme['theme_dir']))) + { + $setValues[] = array($id, 0, 'theme_dir', realpath($_POST['reset_dir'] . '/' . basename($theme['theme_dir']))); + $setValues[] = array($id, 0, 'theme_url', $_POST['reset_url'] . '/' . basename($theme['theme_dir'])); + $setValues[] = array($id, 0, 'images_url', $_POST['reset_url'] . '/' . basename($theme['theme_dir']) . '/' . basename($theme['images_url'])); + } + + if (isset($theme['base_theme_dir']) && file_exists($_POST['reset_dir'] . '/' . basename($theme['base_theme_dir']))) + { + $setValues[] = array($id, 0, 'base_theme_dir', realpath($_POST['reset_dir'] . '/' . basename($theme['base_theme_dir']))); + $setValues[] = array($id, 0, 'base_theme_url', $_POST['reset_url'] . '/' . basename($theme['base_theme_dir'])); + $setValues[] = array($id, 0, 'base_images_url', $_POST['reset_url'] . '/' . basename($theme['base_theme_dir']) . '/' . basename($theme['base_images_url'])); + } + + cache_put_data('theme_settings-' . $id, null, 90); + } + + if (!empty($setValues)) + { + $smcFunc['db_insert']('replace', + '{db_prefix}themes', + array('id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'), + $setValues, + array('id_theme', 'variable', 'id_member') + ); + } + + redirectexit('action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id']); + } + + loadTemplate('Themes'); + + $request = $smcFunc['db_query']('', ' + SELECT id_theme, variable, value + FROM {db_prefix}themes + WHERE variable IN ({string:name}, {string:theme_dir}, {string:theme_url}, {string:images_url}) + AND id_member = {int:no_member}', + array( + 'no_member' => 0, + 'name' => 'name', + 'theme_dir' => 'theme_dir', + 'theme_url' => 'theme_url', + 'images_url' => 'images_url', + ) + ); + $context['themes'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!isset($context['themes'][$row['id_theme']])) + $context['themes'][$row['id_theme']] = array( + 'id' => $row['id_theme'], + ); + $context['themes'][$row['id_theme']][$row['variable']] = $row['value']; + } + $smcFunc['db_free_result']($request); + + foreach ($context['themes'] as $i => $theme) + { + $context['themes'][$i]['theme_dir'] = realpath($context['themes'][$i]['theme_dir']); + + if (file_exists($context['themes'][$i]['theme_dir'] . '/index.template.php')) + { + // Fetch the header... a good 256 bytes should be more than enough. + $fp = fopen($context['themes'][$i]['theme_dir'] . '/index.template.php', 'rb'); + $header = fread($fp, 256); + fclose($fp); + + // Can we find a version comment, at all? + if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) + $context['themes'][$i]['version'] = $match[1]; + } + + $context['themes'][$i]['valid_path'] = file_exists($context['themes'][$i]['theme_dir']) && is_dir($context['themes'][$i]['theme_dir']); + } + + $context['reset_dir'] = realpath($boarddir . '/Themes'); + $context['reset_url'] = $boardurl . '/Themes'; + + $context['sub_template'] = 'list_themes'; +} + +// Administrative global settings. +function SetThemeOptions() +{ + global $txt, $context, $settings, $modSettings, $smcFunc; + + $_GET['th'] = isset($_GET['th']) ? (int) $_GET['th'] : (isset($_GET['id']) ? (int) $_GET['id'] : 0); + + isAllowedTo('admin_forum'); + + if (empty($_GET['th']) && empty($_GET['id'])) + { + $request = $smcFunc['db_query']('', ' + SELECT id_theme, variable, value + FROM {db_prefix}themes + WHERE variable IN ({string:name}, {string:theme_dir}) + AND id_member = {int:no_member}', + array( + 'no_member' => 0, + 'name' => 'name', + 'theme_dir' => 'theme_dir', + ) + ); + $context['themes'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!isset($context['themes'][$row['id_theme']])) + $context['themes'][$row['id_theme']] = array( + 'id' => $row['id_theme'], + 'num_default_options' => 0, + 'num_members' => 0, + ); + $context['themes'][$row['id_theme']][$row['variable']] = $row['value']; + } + $smcFunc['db_free_result']($request); + + $request = $smcFunc['db_query']('', ' + SELECT id_theme, COUNT(*) AS value + FROM {db_prefix}themes + WHERE id_member = {int:guest_member} + GROUP BY id_theme', + array( + 'guest_member' => -1, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['themes'][$row['id_theme']]['num_default_options'] = $row['value']; + $smcFunc['db_free_result']($request); + + // Need to make sure we don't do custom fields. + $request = $smcFunc['db_query']('', ' + SELECT col_name + FROM {db_prefix}custom_fields', + array( + ) + ); + $customFields = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $customFields[] = $row['col_name']; + $smcFunc['db_free_result']($request); + $customFieldsQuery = empty($customFields) ? '' : ('AND variable NOT IN ({array_string:custom_fields})'); + + $request = $smcFunc['db_query']('themes_count', ' + SELECT COUNT(DISTINCT id_member) AS value, id_theme + FROM {db_prefix}themes + WHERE id_member > {int:no_member} + ' . $customFieldsQuery . ' + GROUP BY id_theme', + array( + 'no_member' => 0, + 'custom_fields' => empty($customFields) ? array() : $customFields, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['themes'][$row['id_theme']]['num_members'] = $row['value']; + $smcFunc['db_free_result']($request); + + // There has to be a Settings template! + foreach ($context['themes'] as $k => $v) + if (empty($v['theme_dir']) || (!file_exists($v['theme_dir'] . '/Settings.template.php') && empty($v['num_members']))) + unset($context['themes'][$k]); + + loadTemplate('Themes'); + $context['sub_template'] = 'reset_list'; + + return; + } + + // Submit? + if (isset($_POST['submit']) && empty($_POST['who'])) + { + checkSession(); + + if (empty($_POST['options'])) + $_POST['options'] = array(); + if (empty($_POST['default_options'])) + $_POST['default_options'] = array(); + + // Set up the sql query. + $setValues = array(); + + foreach ($_POST['options'] as $opt => $val) + $setValues[] = array(-1, $_GET['th'], $opt, is_array($val) ? implode(',', $val) : $val); + + $old_settings = array(); + foreach ($_POST['default_options'] as $opt => $val) + { + $old_settings[] = $opt; + + $setValues[] = array(-1, 1, $opt, is_array($val) ? implode(',', $val) : $val); + } + + // If we're actually inserting something.. + if (!empty($setValues)) + { + // Are there options in non-default themes set that should be cleared? + if (!empty($old_settings)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}themes + WHERE id_theme != {int:default_theme} + AND id_member = {int:guest_member} + AND variable IN ({array_string:old_settings})', + array( + 'default_theme' => 1, + 'guest_member' => -1, + 'old_settings' => $old_settings, + ) + ); + + $smcFunc['db_insert']('replace', + '{db_prefix}themes', + array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'), + $setValues, + array('id_theme', 'variable', 'id_member') + ); + } + + cache_put_data('theme_settings-' . $_GET['th'], null, 90); + cache_put_data('theme_settings-1', null, 90); + + redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset'); + } + elseif (isset($_POST['submit']) && $_POST['who'] == 1) + { + checkSession(); + + $_POST['options'] = empty($_POST['options']) ? array() : $_POST['options']; + $_POST['options_master'] = empty($_POST['options_master']) ? array() : $_POST['options_master']; + $_POST['default_options'] = empty($_POST['default_options']) ? array() : $_POST['default_options']; + $_POST['default_options_master'] = empty($_POST['default_options_master']) ? array() : $_POST['default_options_master']; + + $old_settings = array(); + foreach ($_POST['default_options'] as $opt => $val) + { + if ($_POST['default_options_master'][$opt] == 0) + continue; + elseif ($_POST['default_options_master'][$opt] == 1) + { + // Delete then insert for ease of database compatibility! + $smcFunc['db_query']('substring', ' + DELETE FROM {db_prefix}themes + WHERE id_theme = {int:default_theme} + AND id_member != {int:no_member} + AND variable = SUBSTRING({string:option}, 1, 255)', + array( + 'default_theme' => 1, + 'no_member' => 0, + 'option' => $opt, + ) + ); + $smcFunc['db_query']('substring', ' + INSERT INTO {db_prefix}themes + (id_member, id_theme, variable, value) + SELECT id_member, 1, SUBSTRING({string:option}, 1, 255), SUBSTRING({string:value}, 1, 65534) + FROM {db_prefix}members', + array( + 'option' => $opt, + 'value' => (is_array($val) ? implode(',', $val) : $val), + ) + ); + + $old_settings[] = $opt; + } + elseif ($_POST['default_options_master'][$opt] == 2) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}themes + WHERE variable = {string:option_name} + AND id_member > {int:no_member}', + array( + 'no_member' => 0, + 'option_name' => $opt, + ) + ); + } + } + + // Delete options from other themes. + if (!empty($old_settings)) + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}themes + WHERE id_theme != {int:default_theme} + AND id_member > {int:no_member} + AND variable IN ({array_string:old_settings})', + array( + 'default_theme' => 1, + 'no_member' => 0, + 'old_settings' => $old_settings, + ) + ); + + foreach ($_POST['options'] as $opt => $val) + { + if ($_POST['options_master'][$opt] == 0) + continue; + elseif ($_POST['options_master'][$opt] == 1) + { + // Delete then insert for ease of database compatibility - again! + $smcFunc['db_query']('substring', ' + DELETE FROM {db_prefix}themes + WHERE id_theme = {int:current_theme} + AND id_member != {int:no_member} + AND variable = SUBSTRING({string:option}, 1, 255)', + array( + 'current_theme' => $_GET['th'], + 'no_member' => 0, + 'option' => $opt, + ) + ); + $smcFunc['db_query']('substring', ' + INSERT INTO {db_prefix}themes + (id_member, id_theme, variable, value) + SELECT id_member, {int:current_theme}, SUBSTRING({string:option}, 1, 255), SUBSTRING({string:value}, 1, 65534) + FROM {db_prefix}members', + array( + 'current_theme' => $_GET['th'], + 'option' => $opt, + 'value' => (is_array($val) ? implode(',', $val) : $val), + ) + ); + } + elseif ($_POST['options_master'][$opt] == 2) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}themes + WHERE variable = {string:option} + AND id_member > {int:no_member} + AND id_theme = {int:current_theme}', + array( + 'no_member' => 0, + 'current_theme' => $_GET['th'], + 'option' => $opt, + ) + ); + } + } + + redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset'); + } + elseif (!empty($_GET['who']) && $_GET['who'] == 2) + { + checkSession('get'); + + // Don't delete custom fields!! + if ($_GET['th'] == 1) + { + $request = $smcFunc['db_query']('', ' + SELECT col_name + FROM {db_prefix}custom_fields', + array( + ) + ); + $customFields = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $customFields[] = $row['col_name']; + $smcFunc['db_free_result']($request); + } + $customFieldsQuery = empty($customFields) ? '' : ('AND variable NOT IN ({array_string:custom_fields})'); + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}themes + WHERE id_member > {int:no_member} + AND id_theme = {int:current_theme} + ' . $customFieldsQuery, + array( + 'no_member' => 0, + 'current_theme' => $_GET['th'], + 'custom_fields' => empty($customFields) ? array() : $customFields, + ) + ); + + redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset'); + } + + $old_id = $settings['theme_id']; + $old_settings = $settings; + + loadTheme($_GET['th'], false); + + loadLanguage('Profile'); + //!!! Should we just move these options so they are no longer theme dependant? + loadLanguage('PersonalMessage'); + + // Let the theme take care of the settings. + loadTemplate('Settings'); + loadSubTemplate('options'); + + $context['sub_template'] = 'set_options'; + $context['page_title'] = $txt['theme_settings']; + + $context['options'] = $context['theme_options']; + $context['theme_settings'] = $settings; + + if (empty($_REQUEST['who'])) + { + $request = $smcFunc['db_query']('', ' + SELECT variable, value + FROM {db_prefix}themes + WHERE id_theme IN (1, {int:current_theme}) + AND id_member = {int:guest_member}', + array( + 'current_theme' => $_GET['th'], + 'guest_member' => -1, + ) + ); + $context['theme_options'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $context['theme_options'][$row['variable']] = $row['value']; + $smcFunc['db_free_result']($request); + + $context['theme_options_reset'] = false; + } + else + { + $context['theme_options'] = array(); + $context['theme_options_reset'] = true; + } + + foreach ($context['options'] as $i => $setting) + { + // Is this disabled? + if ($setting['id'] == 'calendar_start_day' && empty($modSettings['cal_enabled'])) + { + unset($context['options'][$i]); + continue; + } + elseif (($setting['id'] == 'topics_per_page' || $setting['id'] == 'messages_per_page') && !empty($modSettings['disableCustomPerPage'])) + { + unset($context['options'][$i]); + continue; + } + + if (!isset($setting['type']) || $setting['type'] == 'bool') + $context['options'][$i]['type'] = 'checkbox'; + elseif ($setting['type'] == 'int' || $setting['type'] == 'integer') + $context['options'][$i]['type'] = 'number'; + elseif ($setting['type'] == 'string') + $context['options'][$i]['type'] = 'text'; + + if (isset($setting['options'])) + $context['options'][$i]['type'] = 'list'; + + $context['options'][$i]['value'] = !isset($context['theme_options'][$setting['id']]) ? '' : $context['theme_options'][$setting['id']]; + } + + // Restore the existing theme. + loadTheme($old_id, false); + $settings = $old_settings; + + loadTemplate('Themes'); +} + +// Administrative global settings. +function SetThemeSettings() +{ + global $txt, $context, $settings, $modSettings, $sourcedir, $smcFunc; + + if (empty($_GET['th']) && empty($_GET['id'])) + return ThemeAdmin(); + $_GET['th'] = isset($_GET['th']) ? (int) $_GET['th'] : (int) $_GET['id']; + + // Select the best fitting tab. + $context[$context['admin_menu_name']]['current_subsection'] = 'list'; + + loadLanguage('Admin'); + isAllowedTo('admin_forum'); + + // Validate inputs/user. + if (empty($_GET['th'])) + fatal_lang_error('no_theme', false); + + // Fetch the smiley sets... + $sets = explode(',', 'none,' . $modSettings['smiley_sets_known']); + $set_names = explode("\n", $txt['smileys_none'] . "\n" . $modSettings['smiley_sets_names']); + $context['smiley_sets'] = array( + '' => $txt['smileys_no_default'] + ); + foreach ($sets as $i => $set) + $context['smiley_sets'][$set] = htmlspecialchars($set_names[$i]); + + $old_id = $settings['theme_id']; + $old_settings = $settings; + + loadTheme($_GET['th'], false); + + // Sadly we really do need to init the template. + loadSubTemplate('init', 'ignore'); + + // Also load the actual themes language file - in case of special settings. + loadLanguage('Settings', '', true, true); + + // And the custom language strings... + loadLanguage('ThemeStrings', '', false, true); + + // Let the theme take care of the settings. + loadTemplate('Settings'); + loadSubTemplate('settings'); + + // Load the variants separately... + $settings['theme_variants'] = array(); + if (file_exists($settings['theme_dir'] . '/index.template.php')) + { + $file_contents = implode('', file($settings['theme_dir'] . '/index.template.php')); + if (preg_match('~\$settings\[\'theme_variants\'\]\s*=(.+?);~', $file_contents, $matches)) + eval('global $settings;' . $matches[0]); + } + + // Submitting! + if (isset($_POST['submit'])) + { + checkSession(); + + if (empty($_POST['options'])) + $_POST['options'] = array(); + if (empty($_POST['default_options'])) + $_POST['default_options'] = array(); + + // Make sure items are cast correctly. + foreach ($context['theme_settings'] as $item) + { + // Disregard this item if this is just a separator. + if (!is_array($item)) + continue; + + foreach (array('options', 'default_options') as $option) + { + if (!isset($_POST[$option][$item['id']])) + continue; + // Checkbox. + elseif (empty($item['type'])) + $_POST[$option][$item['id']] = $_POST[$option][$item['id']] ? 1 : 0; + // Number + elseif ($item['type'] == 'number') + $_POST[$option][$item['id']] = (int) $_POST[$option][$item['id']]; + } + } + + // Set up the sql query. + $inserts = array(); + foreach ($_POST['options'] as $opt => $val) + $inserts[] = array(0, $_GET['th'], $opt, is_array($val) ? implode(',', $val) : $val); + foreach ($_POST['default_options'] as $opt => $val) + $inserts[] = array(0, 1, $opt, is_array($val) ? implode(',', $val) : $val); + // If we're actually inserting something.. + if (!empty($inserts)) + { + $smcFunc['db_insert']('replace', + '{db_prefix}themes', + array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'), + $inserts, + array('id_member', 'id_theme', 'variable') + ); + } + + cache_put_data('theme_settings-' . $_GET['th'], null, 90); + cache_put_data('theme_settings-1', null, 90); + + // Invalidate the cache. + updateSettings(array('settings_updated' => time())); + + redirectexit('action=admin;area=theme;sa=settings;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id']); + } + + $context['sub_template'] = 'set_settings'; + $context['page_title'] = $txt['theme_settings']; + + foreach ($settings as $setting => $dummy) + { + if (!in_array($setting, array('theme_url', 'theme_dir', 'images_url', 'template_dirs'))) + $settings[$setting] = htmlspecialchars__recursive($settings[$setting]); + } + + $context['settings'] = $context['theme_settings']; + $context['theme_settings'] = $settings; + + foreach ($context['settings'] as $i => $setting) + { + // Separators are dummies, so leave them alone. + if (!is_array($setting)) + continue; + + if (!isset($setting['type']) || $setting['type'] == 'bool') + $context['settings'][$i]['type'] = 'checkbox'; + elseif ($setting['type'] == 'int' || $setting['type'] == 'integer') + $context['settings'][$i]['type'] = 'number'; + elseif ($setting['type'] == 'string') + $context['settings'][$i]['type'] = 'text'; + + if (isset($setting['options'])) + $context['settings'][$i]['type'] = 'list'; + + $context['settings'][$i]['value'] = !isset($settings[$setting['id']]) ? '' : $settings[$setting['id']]; + } + + // Do we support variants? + if (!empty($settings['theme_variants'])) + { + $context['theme_variants'] = array(); + foreach ($settings['theme_variants'] as $variant) + { + // Have any text, old chap? + $context['theme_variants'][$variant] = array( + 'label' => isset($txt['variant_' . $variant]) ? $txt['variant_' . $variant] : $variant, + 'thumbnail' => !file_exists($settings['theme_dir'] . '/images/thumbnail.gif') || file_exists($settings['theme_dir'] . '/images/thumbnail_' . $variant . '.gif') ? $settings['images_url'] . '/thumbnail_' . $variant . '.gif' : ($settings['images_url'] . '/thumbnail.gif'), + ); + } + $context['default_variant'] = !empty($settings['default_variant']) && isset($context['theme_variants'][$settings['default_variant']]) ? $settings['default_variant'] : $settings['theme_variants'][0]; + } + + // Restore the current theme. + loadTheme($old_id, false); + + // Reinit just incase. + loadSubTemplate('init', 'ignore'); + + $settings = $old_settings; + + loadTemplate('Themes'); +} + +// Remove a theme from the database. +function RemoveTheme() +{ + global $modSettings, $context, $smcFunc; + + checkSession('get'); + + isAllowedTo('admin_forum'); + + // The theme's ID must be an integer. + $_GET['th'] = isset($_GET['th']) ? (int) $_GET['th'] : (int) $_GET['id']; + + // You can't delete the default theme! + if ($_GET['th'] == 1) + fatal_lang_error('no_access', false); + + $known = explode(',', $modSettings['knownThemes']); + for ($i = 0, $n = count($known); $i < $n; $i++) + { + if ($known[$i] == $_GET['th']) + unset($known[$i]); + } + + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}themes + WHERE id_theme = {int:current_theme}', + array( + 'current_theme' => $_GET['th'], + ) + ); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}members + SET id_theme = {int:default_theme} + WHERE id_theme = {int:current_theme}', + array( + 'default_theme' => 0, + 'current_theme' => $_GET['th'], + ) + ); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}boards + SET id_theme = {int:default_theme} + WHERE id_theme = {int:current_theme}', + array( + 'default_theme' => 0, + 'current_theme' => $_GET['th'], + ) + ); + + $known = strtr(implode(',', $known), array(',,' => ',')); + + // Fix it if the theme was the overall default theme. + if ($modSettings['theme_guests'] == $_GET['th']) + updateSettings(array('theme_guests' => '1', 'knownThemes' => $known)); + else + updateSettings(array('knownThemes' => $known)); + + redirectexit('action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id']); +} + +// Choose a theme from a list. +function PickTheme() +{ + global $txt, $context, $modSettings, $user_info, $language, $smcFunc, $settings, $scripturl; + + loadLanguage('Profile'); + loadTemplate('Themes'); + + // Build the link tree. + $context['linktree'][] = array( + 'url' => $scripturl . '?action=theme;sa=pick;u=' . (!empty($_REQUEST['u']) ? (int) $_REQUEST['u'] : 0), + 'name' => $txt['theme_pick'], + ); + + $_SESSION['id_theme'] = 0; + + if (isset($_GET['id'])) + $_GET['th'] = $_GET['id']; + + // Saving a variant cause JS doesn't work - pretend it did ;) + if (isset($_POST['save'])) + { + // Which theme? + foreach ($_POST['save'] as $k => $v) + $_GET['th'] = (int) $k; + + if (isset($_POST['vrt'][$k])) + $_GET['vrt'] = $_POST['vrt'][$k]; + } + + // Have we made a desicion, or are we just browsing? + if (isset($_GET['th'])) + { + checkSession('get'); + + $_GET['th'] = (int) $_GET['th']; + + // Save for this user. + if (!isset($_REQUEST['u']) || !allowedTo('admin_forum')) + { + updateMemberData($user_info['id'], array('id_theme' => (int) $_GET['th'])); + + // A variants to save for the user? + if (!empty($_GET['vrt'])) + { + $smcFunc['db_insert']('replace', + '{db_prefix}themes', + array('id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'), + array($_GET['th'], $user_info['id'], 'theme_variant', $_GET['vrt']), + array('id_theme', 'id_member', 'variable') + ); + cache_put_data('theme_settings-' . $_GET['th'] . ':' . $user_info['id'], null, 90); + + $_SESSION['id_variant'] = 0; + } + + redirectexit('action=profile;area=theme'); + } + + // If changing members or guests - and there's a variant - assume changing default variant. + if (!empty($_GET['vrt']) && ($_REQUEST['u'] == '0' || $_REQUEST['u'] == '-1')) + { + $smcFunc['db_insert']('replace', + '{db_prefix}themes', + array('id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'), + array($_GET['th'], 0, 'default_variant', $_GET['vrt']), + array('id_theme', 'id_member', 'variable') + ); + + // Make it obvious that it's changed + cache_put_data('theme_settings-' . $_GET['th'], null, 90); + } + + // For everyone. + if ($_REQUEST['u'] == '0') + { + updateMemberData(null, array('id_theme' => (int) $_GET['th'])); + + // Remove any custom variants. + if (!empty($_GET['vrt'])) + { + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}themes + WHERE id_theme = {int:current_theme} + AND variable = {string:theme_variant}', + array( + 'current_theme' => (int) $_GET['th'], + 'theme_variant' => 'theme_variant', + ) + ); + } + + redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']); + } + // Change the default/guest theme. + elseif ($_REQUEST['u'] == '-1') + { + updateSettings(array('theme_guests' => (int) $_GET['th'])); + + redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']); + } + // Change a specific member's theme. + else + { + updateMemberData((int) $_REQUEST['u'], array('id_theme' => (int) $_GET['th'])); + + if (!empty($_GET['vrt'])) + { + $smcFunc['db_insert']('replace', + '{db_prefix}themes', + array('id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'), + array($_GET['th'], (int) $_REQUEST['u'], 'theme_variant', $_GET['vrt']), + array('id_theme', 'id_member', 'variable') + ); + cache_put_data('theme_settings-' . $_GET['th'] . ':' . (int) $_REQUEST['u'], null, 90); + + if ($user_info['id'] == $_REQUEST['u']) + $_SESSION['id_variant'] = 0; + } + + redirectexit('action=profile;u=' . (int) $_REQUEST['u'] . ';area=theme'); + } + } + + // Figure out who the member of the minute is, and what theme they've chosen. + if (!isset($_REQUEST['u']) || !allowedTo('admin_forum')) + { + $context['current_member'] = $user_info['id']; + $context['current_theme'] = $user_info['theme']; + } + // Everyone can't chose just one. + elseif ($_REQUEST['u'] == '0') + { + $context['current_member'] = 0; + $context['current_theme'] = 0; + } + // Guests and such... + elseif ($_REQUEST['u'] == '-1') + { + $context['current_member'] = -1; + $context['current_theme'] = $modSettings['theme_guests']; + } + // Someones else :P. + else + { + $context['current_member'] = (int) $_REQUEST['u']; + + $request = $smcFunc['db_query']('', ' + SELECT id_theme + FROM {db_prefix}members + WHERE id_member = {int:current_member} + LIMIT 1', + array( + 'current_member' => $context['current_member'], + ) + ); + list ($context['current_theme']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + } + + // Get the theme name and descriptions. + $context['available_themes'] = array(); + if (!empty($modSettings['knownThemes'])) + { + $request = $smcFunc['db_query']('', ' + SELECT id_theme, variable, value + FROM {db_prefix}themes + WHERE variable IN ({string:name}, {string:theme_url}, {string:theme_dir}, {string:images_url}, {string:disable_user_variant})' . (!allowedTo('admin_forum') ? ' + AND id_theme IN ({array_string:known_themes})' : '') . ' + AND id_theme != {int:default_theme} + AND id_member = {int:no_member}', + array( + 'default_theme' => 0, + 'name' => 'name', + 'no_member' => 0, + 'theme_url' => 'theme_url', + 'theme_dir' => 'theme_dir', + 'images_url' => 'images_url', + 'disable_user_variant' => 'disable_user_variant', + 'known_themes' => explode(',', $modSettings['knownThemes']), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!isset($context['available_themes'][$row['id_theme']])) + $context['available_themes'][$row['id_theme']] = array( + 'id' => $row['id_theme'], + 'selected' => $context['current_theme'] == $row['id_theme'], + 'num_users' => 0 + ); + $context['available_themes'][$row['id_theme']][$row['variable']] = $row['value']; + } + $smcFunc['db_free_result']($request); + } + + // Okay, this is a complicated problem: the default theme is 1, but they aren't allowed to access 1! + if (!isset($context['available_themes'][$modSettings['theme_guests']])) + { + $context['available_themes'][0] = array( + 'num_users' => 0 + ); + $guest_theme = 0; + } + else + $guest_theme = $modSettings['theme_guests']; + + $request = $smcFunc['db_query']('', ' + SELECT id_theme, COUNT(*) AS the_count + FROM {db_prefix}members + GROUP BY id_theme + ORDER BY id_theme DESC', + array( + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Figure out which theme it is they are REALLY using. + if (!empty($modSettings['knownThemes']) && !in_array($row['id_theme'], explode(',',$modSettings['knownThemes']))) + $row['id_theme'] = $guest_theme; + elseif (empty($modSettings['theme_allow'])) + $row['id_theme'] = $guest_theme; + + if (isset($context['available_themes'][$row['id_theme']])) + $context['available_themes'][$row['id_theme']]['num_users'] += $row['the_count']; + else + $context['available_themes'][$guest_theme]['num_users'] += $row['the_count']; + } + $smcFunc['db_free_result']($request); + + // Get any member variant preferences. + $variant_preferences = array(); + if ($context['current_member'] > 0) + { + $request = $smcFunc['db_query']('', ' + SELECT id_theme, value + FROM {db_prefix}themes + WHERE variable = {string:theme_variant}', + array( + 'theme_variant' => 'theme_variant', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $variant_preferences[$row['id_theme']] = $row['value']; + $smcFunc['db_free_result']($request); + } + + // Save the setting first. + $current_images_url = $settings['images_url']; + $current_theme_variants = !empty($settings['theme_variants']) ? $settings['theme_variants'] : array(); + + foreach ($context['available_themes'] as $id_theme => $theme_data) + { + // Don't try to load the forum or board default theme's data... it doesn't have any! + if ($id_theme == 0) + continue; + + // The thumbnail needs the correct path. + $settings['images_url'] = &$theme_data['images_url']; + + if (file_exists($theme_data['theme_dir'] . '/languages/Settings.' . $user_info['language'] . '.php')) + include($theme_data['theme_dir'] . '/languages/Settings.' . $user_info['language'] . '.php'); + elseif (file_exists($theme_data['theme_dir'] . '/languages/Settings.' . $language . '.php')) + include($theme_data['theme_dir'] . '/languages/Settings.' . $language . '.php'); + else + { + $txt['theme_thumbnail_href'] = $theme_data['images_url'] . '/thumbnail.gif'; + $txt['theme_description'] = ''; + } + + $context['available_themes'][$id_theme]['thumbnail_href'] = $txt['theme_thumbnail_href']; + $context['available_themes'][$id_theme]['description'] = $txt['theme_description']; + + // Are there any variants? + if (file_exists($theme_data['theme_dir'] . '/index.template.php') && empty($theme_data['disable_user_variant'])) + { + $file_contents = implode('', file($theme_data['theme_dir'] . '/index.template.php')); + if (preg_match('~\$settings\[\'theme_variants\'\]\s*=(.+?);~', $file_contents, $matches)) + { + $settings['theme_variants'] = array(); + + // Fill settings up. + eval('global $settings;' . $matches[0]); + + if (!empty($settings['theme_variants'])) + { + loadLanguage('Settings'); + + $context['available_themes'][$id_theme]['variants'] = array(); + foreach ($settings['theme_variants'] as $variant) + $context['available_themes'][$id_theme]['variants'][$variant] = array( + 'label' => isset($txt['variant_' . $variant]) ? $txt['variant_' . $variant] : $variant, + 'thumbnail' => !file_exists($theme_data['theme_dir'] . '/images/thumbnail.gif') || file_exists($theme_data['theme_dir'] . '/images/thumbnail_' . $variant . '.gif') ? $theme_data['images_url'] . '/thumbnail_' . $variant . '.gif' : ($theme_data['images_url'] . '/thumbnail.gif'), + ); + + $context['available_themes'][$id_theme]['selected_variant'] = isset($_GET['vrt']) ? $_GET['vrt'] : (!empty($variant_preferences[$id_theme]) ? $variant_preferences[$id_theme] : (!empty($settings['default_variant']) ? $settings['default_variant'] : $settings['theme_variants'][0])); + if (!isset($context['available_themes'][$id_theme]['variants'][$context['available_themes'][$id_theme]['selected_variant']]['thumbnail'])) + $context['available_themes'][$id_theme]['selected_variant'] = $settings['theme_variants'][0]; + + $context['available_themes'][$id_theme]['thumbnail_href'] = $context['available_themes'][$id_theme]['variants'][$context['available_themes'][$id_theme]['selected_variant']]['thumbnail']; + // Allow themes to override the text. + $context['available_themes'][$id_theme]['pick_label'] = isset($txt['variant_pick']) ? $txt['variant_pick'] : $txt['theme_pick_variant']; + } + } + } + } + // Then return it. + $settings['images_url'] = $current_images_url; + $settings['theme_variants'] = $current_theme_variants; + + // As long as we're not doing the default theme... + if (!isset($_REQUEST['u']) || $_REQUEST['u'] >= 0) + { + if ($guest_theme != 0) + $context['available_themes'][0] = $context['available_themes'][$guest_theme]; + + $context['available_themes'][0]['id'] = 0; + $context['available_themes'][0]['name'] = $txt['theme_forum_default']; + $context['available_themes'][0]['selected'] = $context['current_theme'] == 0; + $context['available_themes'][0]['description'] = $txt['theme_global_description']; + } + + ksort($context['available_themes']); + + $context['page_title'] = $txt['theme_pick']; + $context['sub_template'] = 'pick'; +} + +function ThemeInstall() +{ + global $sourcedir, $boarddir, $boardurl, $txt, $context, $settings, $modSettings, $smcFunc; + + checkSession('request'); + + isAllowedTo('admin_forum'); + checkSession('request'); + + require_once($sourcedir . '/Subs-Package.php'); + + loadTemplate('Themes'); + + if (isset($_GET['theme_id'])) + { + $result = $smcFunc['db_query']('', ' + SELECT value + FROM {db_prefix}themes + WHERE id_theme = {int:current_theme} + AND id_member = {int:no_member} + AND variable = {string:name} + LIMIT 1', + array( + 'current_theme' => (int) $_GET['theme_id'], + 'no_member' => 0, + 'name' => 'name', + ) + ); + list ($theme_name) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + $context['sub_template'] = 'installed'; + $context['page_title'] = $txt['theme_installed']; + $context['installed_theme'] = array( + 'id' => (int) $_GET['theme_id'], + 'name' => $theme_name, + ); + + return; + } + + if ((!empty($_FILES['theme_gz']) && (!isset($_FILES['theme_gz']['error']) || $_FILES['theme_gz']['error'] != 4)) || !empty($_REQUEST['theme_gz'])) + $method = 'upload'; + elseif (isset($_REQUEST['theme_dir']) && rtrim(realpath($_REQUEST['theme_dir']), '/\\') != realpath($boarddir . '/Themes') && file_exists($_REQUEST['theme_dir'])) + $method = 'path'; + else + $method = 'copy'; + + if (!empty($_REQUEST['copy']) && $method == 'copy') + { + // Hopefully the themes directory is writable, or we might have a problem. + if (!is_writable($boarddir . '/Themes')) + fatal_lang_error('theme_install_write_error', 'critical'); + + $theme_dir = $boarddir . '/Themes/' . preg_replace('~[^A-Za-z0-9_\- ]~', '', $_REQUEST['copy']); + + umask(0); + mkdir($theme_dir, 0777); + + @set_time_limit(600); + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + // Create subdirectories for css and javascript files. + mkdir($theme_dir . '/css', 0777); + mkdir($theme_dir . '/scripts', 0777); + + // Copy over the default non-theme files. + $to_copy = array('/index.php', '/index.template.php', '/css/index.css', '/css/rtl.css', '/scripts/theme.js'); + foreach ($to_copy as $file) + { + copy($settings['default_theme_dir'] . $file, $theme_dir . $file); + @chmod($theme_dir . $file, 0777); + } + + // And now the entire images directory! + copytree($settings['default_theme_dir'] . '/images', $theme_dir . '/images'); + package_flush_cache(); + + $theme_name = $_REQUEST['copy']; + $images_url = $boardurl . '/Themes/' . basename($theme_dir) . '/images'; + $theme_dir = realpath($theme_dir); + + // Lets get some data for the new theme. + $request = $smcFunc['db_query']('', ' + SELECT variable, value + FROM {db_prefix}themes + WHERE variable IN ({string:theme_templates}, {string:theme_layers}) + AND id_member = {int:no_member} + AND id_theme = {int:default_theme}', + array( + 'no_member' => 0, + 'default_theme' => 1, + 'theme_templates' => 'theme_templates', + 'theme_layers' => 'theme_layers', + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['variable'] == 'theme_templates') + $theme_templates = $row['value']; + elseif ($row['variable'] == 'theme_layers') + $theme_layers = $row['value']; + else + continue; + } + $smcFunc['db_free_result']($request); + + // Lets add a theme_info.xml to this theme. + $xml_info = '<' . '?xml version="1.0"?' . '> + + + smf:' . $smcFunc['strtolower'](str_replace(array(' '), '_', $_REQUEST['copy'])) . ' + ' . $modSettings['smfVersion'] . ' + + ' . $_REQUEST['copy'] . ' + + info@simplemachines.org + + http://www.simplemachines.org/ + + ' . (empty($theme_layers) ? 'html,body' : $theme_layers) . ' + + ' . (empty($theme_templates) ? 'index' : $theme_templates) . ' + + +'; + + // Now write it. + $fp = @fopen($theme_dir . '/theme_info.xml', 'w+'); + if ($fp) + { + fwrite($fp, $xml_info); + fclose($fp); + } + } + elseif (isset($_REQUEST['theme_dir']) && $method == 'path') + { + if (!is_dir($_REQUEST['theme_dir']) || !file_exists($_REQUEST['theme_dir'] . '/theme_info.xml')) + fatal_lang_error('theme_install_error', false); + + $theme_name = basename($_REQUEST['theme_dir']); + $theme_dir = $_REQUEST['theme_dir']; + } + elseif ($method = 'upload') + { + // Hopefully the themes directory is writable, or we might have a problem. + if (!is_writable($boarddir . '/Themes')) + fatal_lang_error('theme_install_write_error', 'critical'); + + require_once($sourcedir . '/Subs-Package.php'); + + // Set the default settings... + $theme_name = strtok(basename(isset($_FILES['theme_gz']) ? $_FILES['theme_gz']['name'] : $_REQUEST['theme_gz']), '.'); + $theme_name = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $theme_name); + $theme_dir = $boarddir . '/Themes/' . $theme_name; + + if (isset($_FILES['theme_gz']) && is_uploaded_file($_FILES['theme_gz']['tmp_name']) && (@ini_get('open_basedir') != '' || file_exists($_FILES['theme_gz']['tmp_name']))) + $extracted = read_tgz_file($_FILES['theme_gz']['tmp_name'], $boarddir . '/Themes/' . $theme_name, false, true); + elseif (isset($_REQUEST['theme_gz'])) + { + // Check that the theme is from simplemachines.org, for now... maybe add mirroring later. + if (preg_match('~^http://[\w_\-]+\.simplemachines\.org/~', $_REQUEST['theme_gz']) == 0 || strpos($_REQUEST['theme_gz'], 'dlattach') !== false) + fatal_lang_error('not_on_simplemachines'); + + $extracted = read_tgz_file($_REQUEST['theme_gz'], $boarddir . '/Themes/' . $theme_name, false, true); + } + else + redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']); + } + + // Something go wrong? + if ($theme_dir != '' && basename($theme_dir) != 'Themes') + { + // Defaults. + $install_info = array( + 'theme_url' => $boardurl . '/Themes/' . basename($theme_dir), + 'images_url' => isset($images_url) ? $images_url : $boardurl . '/Themes/' . basename($theme_dir) . '/images', + 'theme_dir' => $theme_dir, + 'name' => $theme_name + ); + + if (file_exists($theme_dir . '/theme_info.xml')) + { + $theme_info = file_get_contents($theme_dir . '/theme_info.xml'); + + $xml_elements = array( + 'name' => 'name', + 'theme_layers' => 'layers', + 'theme_templates' => 'templates', + 'based_on' => 'based-on', + ); + foreach ($xml_elements as $var => $name) + { + if (preg_match('~<' . $name . '>(?:)?~', $theme_info, $match) == 1) + $install_info[$var] = $match[1]; + } + + if (preg_match('~(?:)?~', $theme_info, $match) == 1) + { + $install_info['images_url'] = $install_info['theme_url'] . '/' . $match[1]; + $explicit_images = true; + } + if (preg_match('~(?:)?~', $theme_info, $match) == 1) + $install_info += unserialize($match[1]); + } + + if (isset($install_info['based_on'])) + { + if ($install_info['based_on'] == 'default') + { + $install_info['theme_url'] = $settings['default_theme_url']; + $install_info['images_url'] = $settings['default_images_url']; + } + elseif ($install_info['based_on'] != '') + { + $install_info['based_on'] = preg_replace('~[^A-Za-z0-9\-_ ]~', '', $install_info['based_on']); + + $request = $smcFunc['db_query']('', ' + SELECT th.value AS base_theme_dir, th2.value AS base_theme_url' . (!empty($explicit_images) ? '' : ', th3.value AS images_url') . ' + FROM {db_prefix}themes AS th + INNER JOIN {db_prefix}themes AS th2 ON (th2.id_theme = th.id_theme + AND th2.id_member = {int:no_member} + AND th2.variable = {string:theme_url})' . (!empty($explicit_images) ? '' : ' + INNER JOIN {db_prefix}themes AS th3 ON (th3.id_theme = th.id_theme + AND th3.id_member = {int:no_member} + AND th3.variable = {string:images_url})') . ' + WHERE th.id_member = {int:no_member} + AND (th.value LIKE {string:based_on} OR th.value LIKE {string:based_on_path}) + AND th.variable = {string:theme_dir} + LIMIT 1', + array( + 'no_member' => 0, + 'theme_url' => 'theme_url', + 'images_url' => 'images_url', + 'theme_dir' => 'theme_dir', + 'based_on' => '%/' . $install_info['based_on'], + 'based_on_path' => '%' . "\\" . $install_info['based_on'], + ) + ); + $temp = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // !!! An error otherwise? + if (is_array($temp)) + { + $install_info = $temp + $install_info; + + if (empty($explicit_images) && !empty($install_info['base_theme_url'])) + $install_info['theme_url'] = $install_info['base_theme_url']; + } + } + + unset($install_info['based_on']); + } + + // Find the newest id_theme. + $result = $smcFunc['db_query']('', ' + SELECT MAX(id_theme) + FROM {db_prefix}themes', + array( + ) + ); + list ($id_theme) = $smcFunc['db_fetch_row']($result); + $smcFunc['db_free_result']($result); + + // This will be theme number... + $id_theme++; + + $inserts = array(); + foreach ($install_info as $var => $val) + $inserts[] = array($id_theme, $var, $val); + + if (!empty($inserts)) + $smcFunc['db_insert']('insert', + '{db_prefix}themes', + array('id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'), + $inserts, + array('id_theme', 'variable') + ); + + updateSettings(array('knownThemes' => strtr($modSettings['knownThemes'] . ',' . $id_theme, array(',,' => ',')))); + } + + redirectexit('action=admin;area=theme;sa=install;theme_id=' . $id_theme . ';' . $context['session_var'] . '=' . $context['session_id']); +} + +// Possibly the simplest and best example of how to ues the template system. +function WrapAction() +{ + global $context, $settings, $sourcedir; + + // Load any necessary template(s)? + if (isset($settings['catch_action']['template'])) + { + // Load both the template and language file. (but don't fret if the language file isn't there...) + loadTemplate($settings['catch_action']['template']); + loadLanguage($settings['catch_action']['template'], '', false); + } + + // Any special layers? + if (isset($settings['catch_action']['layers'])) + $context['template_layers'] = $settings['catch_action']['layers']; + + // Just call a function? + if (isset($settings['catch_action']['function'])) + { + if (isset($settings['catch_action']['filename'])) + template_include($sourcedir . '/' . $settings['catch_action']['filename'], true); + + $settings['catch_action']['function'](); + } + // And finally, the main sub template ;). + elseif (isset($settings['catch_action']['sub_template'])) + $context['sub_template'] = $settings['catch_action']['sub_template']; +} + +// Set an option via javascript. +function SetJavaScript() +{ + global $settings, $user_info, $smcFunc, $options; + + // Check the session id. + checkSession('get'); + + // This good-for-nothing pixel is being used to keep the session alive. + if (empty($_GET['var']) || !isset($_GET['val'])) + redirectexit($settings['images_url'] . '/blank.gif'); + + // Sorry, guests can't go any further than this.. + if ($user_info['is_guest'] || $user_info['id'] == 0) + obExit(false); + + $reservedVars = array( + 'actual_theme_url', + 'actual_images_url', + 'base_theme_dir', + 'base_theme_url', + 'default_images_url', + 'default_theme_dir', + 'default_theme_url', + 'default_template', + 'images_url', + 'number_recent_posts', + 'smiley_sets_default', + 'theme_dir', + 'theme_id', + 'theme_layers', + 'theme_templates', + 'theme_url', + 'name', + ); + + // Can't change reserved vars. + if (in_array(strtolower($_GET['var']), $reservedVars)) + redirectexit($settings['images_url'] . '/blank.gif'); + + // Use a specific theme? + if (isset($_GET['th']) || isset($_GET['id'])) + { + // Invalidate the current themes cache too. + cache_put_data('theme_settings-' . $settings['theme_id'] . ':' . $user_info['id'], null, 60); + + $settings['theme_id'] = isset($_GET['th']) ? (int) $_GET['th'] : (int) $_GET['id']; + } + + // If this is the admin preferences the passed value will just be an element of it. + if ($_GET['var'] == 'admin_preferences') + { + $options['admin_preferences'] = !empty($options['admin_preferences']) ? unserialize($options['admin_preferences']) : array(); + // New thingy... + if (isset($_GET['admin_key']) && strlen($_GET['admin_key']) < 5) + $options['admin_preferences'][$_GET['admin_key']] = $_GET['val']; + + // Change the value to be something nice, + $_GET['val'] = serialize($options['admin_preferences']); + } + + // Update the option. + $smcFunc['db_insert']('replace', + '{db_prefix}themes', + array('id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'), + array($settings['theme_id'], $user_info['id'], $_GET['var'], is_array($_GET['val']) ? implode(',', $_GET['val']) : $_GET['val']), + array('id_theme', 'id_member', 'variable') + ); + + cache_put_data('theme_settings-' . $settings['theme_id'] . ':' . $user_info['id'], null, 60); + + // Don't output anything... + redirectexit($settings['images_url'] . '/blank.gif'); +} + +function EditTheme() +{ + global $context, $settings, $scripturl, $boarddir, $smcFunc; + + if (isset($_REQUEST['preview'])) + { + // !!! Should this be removed? + die; + } + + isAllowedTo('admin_forum'); + loadTemplate('Themes'); + + $_GET['th'] = isset($_GET['th']) ? (int) $_GET['th'] : (int) @$_GET['id']; + + if (empty($_GET['th'])) + { + $request = $smcFunc['db_query']('', ' + SELECT id_theme, variable, value + FROM {db_prefix}themes + WHERE variable IN ({string:name}, {string:theme_dir}, {string:theme_templates}, {string:theme_layers}) + AND id_member = {int:no_member}', + array( + 'name' => 'name', + 'theme_dir' => 'theme_dir', + 'theme_templates' => 'theme_templates', + 'theme_layers' => 'theme_layers', + 'no_member' => 0, + ) + ); + $context['themes'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (!isset($context['themes'][$row['id_theme']])) + $context['themes'][$row['id_theme']] = array( + 'id' => $row['id_theme'], + 'num_default_options' => 0, + 'num_members' => 0, + ); + $context['themes'][$row['id_theme']][$row['variable']] = $row['value']; + } + $smcFunc['db_free_result']($request); + + foreach ($context['themes'] as $key => $theme) + { + // There has to be a Settings template! + if (!file_exists($theme['theme_dir'] . '/index.template.php') && !file_exists($theme['theme_dir'] . '/css/index.css')) + unset($context['themes'][$key]); + else + { + if (!isset($theme['theme_templates'])) + $templates = array('index'); + else + $templates = explode(',', $theme['theme_templates']); + + foreach ($templates as $template) + if (file_exists($theme['theme_dir'] . '/' . $template . '.template.php')) + { + // Fetch the header... a good 256 bytes should be more than enough. + $fp = fopen($theme['theme_dir'] . '/' . $template . '.template.php', 'rb'); + $header = fread($fp, 256); + fclose($fp); + + // Can we find a version comment, at all? + if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) + { + $ver = $match[1]; + if (!isset($context['themes'][$key]['version']) || $context['themes'][$key]['version'] > $ver) + $context['themes'][$key]['version'] = $ver; + } + } + + $context['themes'][$key]['can_edit_style'] = file_exists($theme['theme_dir'] . '/css/index.css'); + } + } + + $context['sub_template'] = 'edit_list'; + + return 'no_themes'; + } + + $context['session_error'] = false; + + // Get the directory of the theme we are editing. + $request = $smcFunc['db_query']('', ' + SELECT value, id_theme + FROM {db_prefix}themes + WHERE variable = {string:theme_dir} + AND id_theme = {int:current_theme} + LIMIT 1', + array( + 'current_theme' => $_GET['th'], + 'theme_dir' => 'theme_dir', + ) + ); + list ($theme_dir, $context['theme_id']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (!file_exists($theme_dir . '/index.template.php') && !file_exists($theme_dir . '/css/index.css')) + fatal_lang_error('theme_edit_missing', false); + + if (!isset($_REQUEST['filename'])) + { + if (isset($_GET['directory'])) + { + if (substr($_GET['directory'], 0, 1) == '.') + $_GET['directory'] = ''; + else + { + $_GET['directory'] = preg_replace(array('~^[\./\\:\0\n\r]+~', '~[\\\\]~', '~/[\./]+~'), array('', '/', '/'), $_GET['directory']); + + $temp = realpath($theme_dir . '/' . $_GET['directory']); + if (empty($temp) || substr($temp, 0, strlen(realpath($theme_dir))) != realpath($theme_dir)) + $_GET['directory'] = ''; + } + } + + if (isset($_GET['directory']) && $_GET['directory'] != '') + { + $context['theme_files'] = get_file_listing($theme_dir . '/' . $_GET['directory'], $_GET['directory'] . '/'); + + $temp = dirname($_GET['directory']); + array_unshift($context['theme_files'], array( + 'filename' => $temp == '.' || $temp == '' ? '/ (..)' : $temp . ' (..)', + 'is_writable' => is_writable($theme_dir . '/' . $temp), + 'is_directory' => true, + 'is_template' => false, + 'is_image' => false, + 'is_editable' => false, + 'href' => $scripturl . '?action=admin;area=theme;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=edit;directory=' . $temp, + 'size' => '', + )); + } + else + $context['theme_files'] = get_file_listing($theme_dir, ''); + + $context['sub_template'] = 'edit_browse'; + + return; + } + else + { + if (substr($_REQUEST['filename'], 0, 1) == '.') + $_REQUEST['filename'] = ''; + else + { + $_REQUEST['filename'] = preg_replace(array('~^[\./\\:\0\n\r]+~', '~[\\\\]~', '~/[\./]+~'), array('', '/', '/'), $_REQUEST['filename']); + + $temp = realpath($theme_dir . '/' . $_REQUEST['filename']); + if (empty($temp) || substr($temp, 0, strlen(realpath($theme_dir))) != realpath($theme_dir)) + $_REQUEST['filename'] = ''; + } + + if (empty($_REQUEST['filename'])) + fatal_lang_error('theme_edit_missing', false); + } + + if (isset($_POST['submit'])) + { + if (checkSession('post', '', false) == '') + { + if (is_array($_POST['entire_file'])) + $_POST['entire_file'] = implode("\n", $_POST['entire_file']); + $_POST['entire_file'] = rtrim(strtr($_POST['entire_file'], array("\r" => '', ' ' => "\t"))); + + // Check for a parse error! + if (substr($_REQUEST['filename'], -13) == '.template.php' && is_writable($theme_dir) && @ini_get('display_errors')) + { + $request = $smcFunc['db_query']('', ' + SELECT value + FROM {db_prefix}themes + WHERE variable = {string:theme_url} + AND id_theme = {int:current_theme} + LIMIT 1', + array( + 'current_theme' => $_GET['th'], + 'theme_url' => 'theme_url', + ) + ); + list ($theme_url) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $fp = fopen($theme_dir . '/tmp_' . session_id() . '.php', 'w'); + fwrite($fp, $_POST['entire_file']); + fclose($fp); + + // !!! Use fetch_web_data()? + $error = @file_get_contents($theme_url . '/tmp_' . session_id() . '.php'); + if (preg_match('~ (\d+)$~i', $error) != 0) + $error_file = $theme_dir . '/tmp_' . session_id() . '.php'; + else + unlink($theme_dir . '/tmp_' . session_id() . '.php'); + } + + if (!isset($error_file)) + { + $fp = fopen($theme_dir . '/' . $_REQUEST['filename'], 'w'); + fwrite($fp, $_POST['entire_file']); + fclose($fp); + + redirectexit('action=admin;area=theme;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=edit;directory=' . dirname($_REQUEST['filename'])); + } + } + // Session timed out. + else + { + loadLanguage('Errors'); + + $context['session_error'] = true; + $context['sub_template'] = 'edit_file'; + + // Recycle the submitted data. + $context['entire_file'] = htmlspecialchars($_POST['entire_file']); + + // You were able to submit it, so it's reasonable to assume you are allowed to save. + $context['allow_save'] = true; + + return; + } + } + + $context['allow_save'] = is_writable($theme_dir . '/' . $_REQUEST['filename']); + $context['allow_save_filename'] = strtr($theme_dir . '/' . $_REQUEST['filename'], array($boarddir => '...')); + $context['edit_filename'] = htmlspecialchars($_REQUEST['filename']); + + if (substr($_REQUEST['filename'], -4) == '.css') + { + $context['sub_template'] = 'edit_style'; + + $context['entire_file'] = htmlspecialchars(strtr(file_get_contents($theme_dir . '/' . $_REQUEST['filename']), array("\t" => ' '))); + } + elseif (substr($_REQUEST['filename'], -13) == '.template.php') + { + $context['sub_template'] = 'edit_template'; + + if (!isset($error_file)) + $file_data = file($theme_dir . '/' . $_REQUEST['filename']); + else + { + if (preg_match('~(.+?:.+?).+?(.+?\d+)$~i', $error, $match) != 0) + $context['parse_error'] = $match[1] . $_REQUEST['filename'] . $match[2]; + $file_data = file($error_file); + unlink($error_file); + } + + $j = 0; + $context['file_parts'] = array(array('lines' => 0, 'line' => 1, 'data' => '')); + for ($i = 0, $n = count($file_data); $i < $n; $i++) + { + if (isset($file_data[$i + 1]) && substr($file_data[$i + 1], 0, 9) == 'function ') + { + // Try to format the functions a little nicer... + $context['file_parts'][$j]['data'] = trim($context['file_parts'][$j]['data']) . "\n"; + + if (empty($context['file_parts'][$j]['lines'])) + unset($context['file_parts'][$j]); + $context['file_parts'][++$j] = array('lines' => 0, 'line' => $i + 1, 'data' => ''); + } + + $context['file_parts'][$j]['lines']++; + $context['file_parts'][$j]['data'] .= htmlspecialchars(strtr($file_data[$i], array("\t" => ' '))); + } + + $context['entire_file'] = htmlspecialchars(strtr(implode('', $file_data), array("\t" => ' '))); + } + else + { + $context['sub_template'] = 'edit_file'; + + $context['entire_file'] = htmlspecialchars(strtr(file_get_contents($theme_dir . '/' . $_REQUEST['filename']), array("\t" => ' '))); + } +} + +function get_file_listing($path, $relative) +{ + global $scripturl, $txt, $context; + + // Is it even a directory? + if (!is_dir($path)) + fatal_lang_error('error_invalid_dir', 'critical'); + + $dir = dir($path); + $entries = array(); + while ($entry = $dir->read()) + $entries[] = $entry; + $dir->close(); + + natcasesort($entries); + + $listing1 = array(); + $listing2 = array(); + + foreach ($entries as $entry) + { + // Skip all dot files, including .htaccess. + if (substr($entry, 0, 1) == '.' || $entry == 'CVS') + continue; + + if (is_dir($path . '/' . $entry)) + $listing1[] = array( + 'filename' => $entry, + 'is_writable' => is_writable($path . '/' . $entry), + 'is_directory' => true, + 'is_template' => false, + 'is_image' => false, + 'is_editable' => false, + 'href' => $scripturl . '?action=admin;area=theme;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=edit;directory=' . $relative . $entry, + 'size' => '', + ); + else + { + $size = filesize($path . '/' . $entry); + if ($size > 2048 || $size == 1024) + $size = comma_format($size / 1024) . ' ' . $txt['themeadmin_edit_kilobytes']; + else + $size = comma_format($size) . ' ' . $txt['themeadmin_edit_bytes']; + + $listing2[] = array( + 'filename' => $entry, + 'is_writable' => is_writable($path . '/' . $entry), + 'is_directory' => false, + 'is_template' => preg_match('~\.template\.php$~', $entry) != 0, + 'is_image' => preg_match('~\.(jpg|jpeg|gif|bmp|png)$~', $entry) != 0, + 'is_editable' => is_writable($path . '/' . $entry) && preg_match('~\.(php|pl|css|js|vbs|xml|xslt|txt|xsl|html|htm|shtm|shtml|asp|aspx|cgi|py)$~', $entry) != 0, + 'href' => $scripturl . '?action=admin;area=theme;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=edit;filename=' . $relative . $entry, + 'size' => $size, + 'last_modified' => timeformat(filemtime($path . '/' . $entry)), + ); + } + } + + return array_merge($listing1, $listing2); +} + +function CopyTemplate() +{ + global $context, $settings, $smcFunc; + + isAllowedTo('admin_forum'); + loadTemplate('Themes'); + + $context[$context['admin_menu_name']]['current_subsection'] = 'edit'; + + $_GET['th'] = isset($_GET['th']) ? (int) $_GET['th'] : (int) $_GET['id']; + + $request = $smcFunc['db_query']('', ' + SELECT th1.value, th1.id_theme, th2.value + FROM {db_prefix}themes AS th1 + LEFT JOIN {db_prefix}themes AS th2 ON (th2.variable = {string:base_theme_dir} AND th2.id_theme = {int:current_theme}) + WHERE th1.variable = {string:theme_dir} + AND th1.id_theme = {int:current_theme} + LIMIT 1', + array( + 'current_theme' => $_GET['th'], + 'base_theme_dir' => 'base_theme_dir', + 'theme_dir' => 'theme_dir', + ) + ); + list ($theme_dir, $context['theme_id'], $base_theme_dir) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + if (isset($_REQUEST['template']) && preg_match('~[\./\\\\:\0]~', $_REQUEST['template']) == 0) + { + if (!empty($base_theme_dir) && file_exists($base_theme_dir . '/' . $_REQUEST['template'] . '.template.php')) + $filename = $base_theme_dir . '/' . $_REQUEST['template'] . '.template.php'; + elseif (file_exists($settings['default_theme_dir'] . '/' . $_REQUEST['template'] . '.template.php')) + $filename = $settings['default_theme_dir'] . '/' . $_REQUEST['template'] . '.template.php'; + else + fatal_lang_error('no_access', false); + + $fp = fopen($theme_dir . '/' . $_REQUEST['template'] . '.template.php', 'w'); + fwrite($fp, file_get_contents($filename)); + fclose($fp); + + redirectexit('action=admin;area=theme;th=' . $context['theme_id'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=copy'); + } + elseif (isset($_REQUEST['lang_file']) && preg_match('~^[^\./\\\\:\0]\.[^\./\\\\:\0]$~', $_REQUEST['lang_file']) != 0) + { + if (!empty($base_theme_dir) && file_exists($base_theme_dir . '/languages/' . $_REQUEST['lang_file'] . '.php')) + $filename = $base_theme_dir . '/languages/' . $_REQUEST['template'] . '.php'; + elseif (file_exists($settings['default_theme_dir'] . '/languages/' . $_REQUEST['template'] . '.php')) + $filename = $settings['default_theme_dir'] . '/languages/' . $_REQUEST['template'] . '.php'; + else + fatal_lang_error('no_access', false); + + $fp = fopen($theme_dir . '/languages/' . $_REQUEST['lang_file'] . '.php', 'w'); + fwrite($fp, file_get_contents($filename)); + fclose($fp); + + redirectexit('action=admin;area=theme;th=' . $context['theme_id'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=copy'); + } + + $templates = array(); + $lang_files = array(); + + $dir = dir($settings['default_theme_dir']); + while ($entry = $dir->read()) + { + if (substr($entry, -13) == '.template.php') + $templates[] = substr($entry, 0, -13); + } + $dir->close(); + + $dir = dir($settings['default_theme_dir'] . '/languages'); + while ($entry = $dir->read()) + { + if (preg_match('~^([^\.]+\.[^\.]+)\.php$~', $entry, $matches)) + $lang_files[] = $matches[1]; + } + $dir->close(); + + if (!empty($base_theme_dir)) + { + $dir = dir($base_theme_dir); + while ($entry = $dir->read()) + { + if (substr($entry, -13) == '.template.php' && !in_array(substr($entry, 0, -13), $templates)) + $templates[] = substr($entry, 0, -13); + } + $dir->close(); + + if (file_exists($base_theme_dir . '/languages')) + { + $dir = dir($base_theme_dir . '/languages'); + while ($entry = $dir->read()) + { + if (preg_match('~^([^\.]+\.[^\.]+)\.php$~', $entry, $matches) && !in_array($matches[1], $lang_files)) + $lang_files[] = $matches[1]; + } + $dir->close(); + } + } + + natcasesort($templates); + natcasesort($lang_files); + + $context['available_templates'] = array(); + foreach ($templates as $template) + $context['available_templates'][$template] = array( + 'filename' => $template . '.template.php', + 'value' => $template, + 'already_exists' => false, + 'can_copy' => is_writable($theme_dir), + ); + $context['available_language_files'] = array(); + foreach ($lang_files as $file) + $context['available_language_files'][$file] = array( + 'filename' => $file . '.php', + 'value' => $file, + 'already_exists' => false, + 'can_copy' => file_exists($theme_dir . '/languages') ? is_writable($theme_dir . '/languages') : is_writable($theme_dir), + ); + + $dir = dir($theme_dir); + while ($entry = $dir->read()) + { + if (substr($entry, -13) == '.template.php' && isset($context['available_templates'][substr($entry, 0, -13)])) + { + $context['available_templates'][substr($entry, 0, -13)]['already_exists'] = true; + $context['available_templates'][substr($entry, 0, -13)]['can_copy'] = is_writable($theme_dir . '/' . $entry); + } + } + $dir->close(); + + if (file_exists($theme_dir . '/languages')) + { + $dir = dir($theme_dir . '/languages'); + while ($entry = $dir->read()) + { + if (preg_match('~^([^\.]+\.[^\.]+)\.php$~', $entry, $matches) && isset($context['available_language_files'][$matches[1]])) + { + $context['available_language_files'][$matches[1]]['already_exists'] = true; + $context['available_language_files'][$matches[1]]['can_copy'] = is_writable($theme_dir . '/languages/' . $entry); + } + } + $dir->close(); + } + + $context['sub_template'] = 'copy_template'; +} + +?> \ No newline at end of file diff --git a/Sources/ViewQuery.php b/Sources/ViewQuery.php new file mode 100644 index 0000000..f1767a4 --- /dev/null +++ b/Sources/ViewQuery.php @@ -0,0 +1,191 @@ + + + + ', $context['forum_name_html_safe'], ' + + + + +
    '; + + foreach ($_SESSION['debug'] as $q => $query_data) + { + // Fix the indentation.... + $query_data['q'] = ltrim(str_replace("\r", '', $query_data['q']), "\n"); + $query = explode("\n", $query_data['q']); + $min_indent = 0; + foreach ($query as $line) + { + preg_match('/^(\t*)/', $line, $temp); + if (strlen($temp[0]) < $min_indent || $min_indent == 0) + $min_indent = strlen($temp[0]); + } + foreach ($query as $l => $dummy) + $query[$l] = substr($dummy, $min_indent); + $query_data['q'] = implode("\n", $query); + + // Make the filenames look a bit better. + if (isset($query_data['f'])) + $query_data['f'] = preg_replace('~^' . preg_quote($boarddir, '~') . '~', '...', $query_data['f']); + + $is_select_query = substr(trim($query_data['q']), 0, 6) == 'SELECT'; + if ($is_select_query) + $select = $query_data['q']; + elseif (preg_match('~^INSERT(?: IGNORE)? INTO \w+(?:\s+\([^)]+\))?\s+(SELECT .+)$~s', trim($query_data['q']), $matches) != 0) + { + $is_select_query = true; + $select = $matches[1]; + } + elseif (preg_match('~^CREATE TEMPORARY TABLE .+?(SELECT .+)$~s', trim($query_data['q']), $matches) != 0) + { + $is_select_query = true; + $select = $matches[1]; + } + // Temporary tables created in earlier queries are not explainable. + if ($is_select_query) + { + foreach (array('log_topics_unread', 'topics_posted_in', 'tmp_log_search_topics', 'tmp_log_search_messages') as $tmp) + if (strpos($select, $tmp) !== false) + { + $is_select_query = false; + break; + } + } + + echo ' +
    + + ', nl2br(str_replace("\t", '   ', htmlspecialchars($query_data['q']))), ' +
    '; + + if (!empty($query_data['f']) && !empty($query_data['l'])) + echo sprintf($txt['debug_query_in_line'], $query_data['f'], $query_data['l']); + + if (isset($query_data['s'], $query_data['t']) && isset($txt['debug_query_which_took_at'])) + echo sprintf($txt['debug_query_which_took_at'], round($query_data['t'], 8), round($query_data['s'], 8)); + else + echo sprintf($txt['debug_query_which_took'], round($query_data['t'], 8)); + + echo ' +
    '; + + // Explain the query. + if ($query_id == $q && $is_select_query) + { + $result = $smcFunc['db_query']('', ' + EXPLAIN ' . $select, + array( + ) + ); + if ($result === false) + { + echo ' + + +
    ', $smcFunc['db_error']($db_connection), '
    '; + continue; + } + + echo ' + '; + + $row = $smcFunc['db_fetch_assoc']($result); + + echo ' + + + + '; + + $smcFunc['db_data_seek']($result, 0); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + echo ' + + + + '; + } + $smcFunc['db_free_result']($result); + + echo ' +
    ' . implode('', array_keys($row)) . '
    ' . implode('', $row) . '
    '; + } + } + + echo ' +
    + +'; + + obExit(false); +} + +?> \ No newline at end of file diff --git a/Sources/Who.php b/Sources/Who.php new file mode 100644 index 0000000..9fc5194 --- /dev/null +++ b/Sources/Who.php @@ -0,0 +1,743 @@ + 'mem.real_name', + 'time' => 'lo.log_time' + ); + + $show_methods = array( + 'members' => '(lo.id_member != 0)', + 'guests' => '(lo.id_member = 0)', + 'all' => '1=1', + ); + + // Store the sort methods and the show types for use in the template. + $context['sort_methods'] = array( + 'user' => $txt['who_user'], + 'time' => $txt['who_time'], + ); + $context['show_methods'] = array( + 'all' => $txt['who_show_all'], + 'members' => $txt['who_show_members_only'], + 'guests' => $txt['who_show_guests_only'], + ); + + // Can they see spiders too? + if (!empty($modSettings['show_spider_online']) && ($modSettings['show_spider_online'] == 2 || allowedTo('admin_forum')) && !empty($modSettings['spider_name_cache'])) + { + $show_methods['spiders'] = '(lo.id_member = 0 AND lo.id_spider > 0)'; + $show_methods['guests'] = '(lo.id_member = 0 AND lo.id_spider = 0)'; + $context['show_methods']['spiders'] = $txt['who_show_spiders_only']; + } + elseif (empty($modSettings['show_spider_online']) && isset($_SESSION['who_online_filter']) && $_SESSION['who_online_filter'] == 'spiders') + unset($_SESSION['who_online_filter']); + + // Does the user prefer a different sort direction? + if (isset($_REQUEST['sort']) && isset($sort_methods[$_REQUEST['sort']])) + { + $context['sort_by'] = $_SESSION['who_online_sort_by'] = $_REQUEST['sort']; + $sort_method = $sort_methods[$_REQUEST['sort']]; + } + // Did we set a preferred sort order earlier in the session? + elseif (isset($_SESSION['who_online_sort_by'])) + { + $context['sort_by'] = $_SESSION['who_online_sort_by']; + $sort_method = $sort_methods[$_SESSION['who_online_sort_by']]; + } + // Default to last time online. + else + { + $context['sort_by'] = $_SESSION['who_online_sort_by'] = 'time'; + $sort_method = 'lo.log_time'; + } + + $context['sort_direction'] = isset($_REQUEST['asc']) || (isset($_REQUEST['sort_dir']) && $_REQUEST['sort_dir'] == 'asc') ? 'up' : 'down'; + + $conditions = array(); + if (!allowedTo('moderate_forum')) + $conditions[] = '(IFNULL(mem.show_online, 1) = 1)'; + + // Fallback to top filter? + if (isset($_REQUEST['submit_top']) && isset($_REQUEST['show_top'])) + $_REQUEST['show'] = $_REQUEST['show_top']; + // Does the user wish to apply a filter? + if (isset($_REQUEST['show']) && isset($show_methods[$_REQUEST['show']])) + { + $context['show_by'] = $_SESSION['who_online_filter'] = $_REQUEST['show']; + $conditions[] = $show_methods[$_REQUEST['show']]; + } + // Perhaps we saved a filter earlier in the session? + elseif (isset($_SESSION['who_online_filter'])) + { + $context['show_by'] = $_SESSION['who_online_filter']; + $conditions[] = $show_methods[$_SESSION['who_online_filter']]; + } + else + $context['show_by'] = $_SESSION['who_online_filter'] = 'all'; + + // Get the total amount of members online. + $request = $smcFunc['db_query']('', ' + SELECT COUNT(*) + FROM {db_prefix}log_online AS lo + LEFT JOIN {db_prefix}members AS mem ON (lo.id_member = mem.id_member)' . (!empty($conditions) ? ' + WHERE ' . implode(' AND ', $conditions) : ''), + array( + ) + ); + list ($totalMembers) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + // Prepare some page index variables. + $context['page_index'] = constructPageIndex($scripturl . '?action=who;sort=' . $context['sort_by'] . ($context['sort_direction'] == 'up' ? ';asc' : '') . ';show=' . $context['show_by'], $_REQUEST['start'], $totalMembers, $modSettings['defaultMaxMembers']); + $context['start'] = $_REQUEST['start']; + + // Look for people online, provided they don't mind if you see they are. + $request = $smcFunc['db_query']('', ' + SELECT + lo.log_time, lo.id_member, lo.url, INET_NTOA(lo.ip) AS ip, mem.real_name, + lo.session, mg.online_color, IFNULL(mem.show_online, 1) AS show_online, + lo.id_spider + FROM {db_prefix}log_online AS lo + LEFT JOIN {db_prefix}members AS mem ON (lo.id_member = mem.id_member) + LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:regular_member} THEN mem.id_post_group ELSE mem.id_group END)' . (!empty($conditions) ? ' + WHERE ' . implode(' AND ', $conditions) : '') . ' + ORDER BY {raw:sort_method} {raw:sort_direction} + LIMIT {int:offset}, {int:limit}', + array( + 'regular_member' => 0, + 'sort_method' => $sort_method, + 'sort_direction' => $context['sort_direction'] == 'up' ? 'ASC' : 'DESC', + 'offset' => $context['start'], + 'limit' => $modSettings['defaultMaxMembers'], + ) + ); + $context['members'] = array(); + $member_ids = array(); + $url_data = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $actions = @unserialize($row['url']); + if ($actions === false) + continue; + + // Send the information to the template. + $context['members'][$row['session']] = array( + 'id' => $row['id_member'], + 'ip' => allowedTo('moderate_forum') ? $row['ip'] : '', + // It is *going* to be today or yesterday, so why keep that information in there? + 'time' => strtr(timeformat($row['log_time']), array($txt['today'] => '', $txt['yesterday'] => '')), + 'timestamp' => forum_time(true, $row['log_time']), + 'query' => $actions, + 'is_hidden' => $row['show_online'] == 0, + 'id_spider' => $row['id_spider'], + 'color' => empty($row['online_color']) ? '' : $row['online_color'] + ); + + $url_data[$row['session']] = array($row['url'], $row['id_member']); + $member_ids[] = $row['id_member']; + } + $smcFunc['db_free_result']($request); + + // Load the user data for these members. + loadMemberData($member_ids); + + // Load up the guest user. + $memberContext[0] = array( + 'id' => 0, + 'name' => $txt['guest_title'], + 'group' => $txt['guest_title'], + 'href' => '', + 'link' => $txt['guest_title'], + 'email' => $txt['guest_title'], + 'is_guest' => true + ); + + // Are we showing spiders? + $spiderContext = array(); + if (!empty($modSettings['show_spider_online']) && ($modSettings['show_spider_online'] == 2 || allowedTo('admin_forum')) && !empty($modSettings['spider_name_cache'])) + { + foreach (unserialize($modSettings['spider_name_cache']) as $id => $name) + $spiderContext[$id] = array( + 'id' => 0, + 'name' => $name, + 'group' => $txt['spiders'], + 'href' => '', + 'link' => $name, + 'email' => $name, + 'is_guest' => true + ); + } + + $url_data = determineActions($url_data); + + // Setup the linktree and page title (do it down here because the language files are now loaded..) + $context['page_title'] = $txt['who_title']; + $context['linktree'][] = array( + 'url' => $scripturl . '?action=who', + 'name' => $txt['who_title'] + ); + + // Put it in the context variables. + foreach ($context['members'] as $i => $member) + { + if ($member['id'] != 0) + $member['id'] = loadMemberContext($member['id']) ? $member['id'] : 0; + + // Keep the IP that came from the database. + $memberContext[$member['id']]['ip'] = $member['ip']; + $context['members'][$i]['action'] = isset($url_data[$i]) ? $url_data[$i] : $txt['who_hidden']; + if ($member['id'] == 0 && isset($spiderContext[$member['id_spider']])) + $context['members'][$i] += $spiderContext[$member['id_spider']]; + else + $context['members'][$i] += $memberContext[$member['id']]; + } + + // Some people can't send personal messages... + $context['can_send_pm'] = allowedTo('pm_send'); + + // any profile fields disabled? + $context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array(); + +} + +function determineActions($urls, $preferred_prefix = false) +{ + global $txt, $user_info, $modSettings, $smcFunc, $context; + + if (!allowedTo('who_view')) + return array(); + loadLanguage('Who'); + + // Actions that require a specific permission level. + $allowedActions = array( + 'admin' => array('moderate_forum', 'manage_membergroups', 'manage_bans', 'admin_forum', 'manage_permissions', 'send_mail', 'manage_attachments', 'manage_smileys', 'manage_boards', 'edit_news'), + 'ban' => array('manage_bans'), + 'boardrecount' => array('admin_forum'), + 'calendar' => array('calendar_view'), + 'editnews' => array('edit_news'), + 'mailing' => array('send_mail'), + 'maintain' => array('admin_forum'), + 'manageattachments' => array('manage_attachments'), + 'manageboards' => array('manage_boards'), + 'mlist' => array('view_mlist'), + 'moderate' => array('access_mod_center', 'moderate_forum', 'manage_membergroups'), + 'optimizetables' => array('admin_forum'), + 'repairboards' => array('admin_forum'), + 'search' => array('search_posts'), + 'search2' => array('search_posts'), + 'setcensor' => array('moderate_forum'), + 'setreserve' => array('moderate_forum'), + 'stats' => array('view_stats'), + 'viewErrorLog' => array('admin_forum'), + 'viewmembers' => array('moderate_forum'), + ); + + if (!is_array($urls)) + $url_list = array(array($urls, $user_info['id'])); + else + $url_list = $urls; + + // These are done to later query these in large chunks. (instead of one by one.) + $topic_ids = array(); + $profile_ids = array(); + $board_ids = array(); + + $data = array(); + foreach ($url_list as $k => $url) + { + // Get the request parameters.. + $actions = @unserialize($url[0]); + if ($actions === false) + continue; + + // If it's the admin or moderation center, and there is an area set, use that instead. + if (isset($actions['action']) && ($actions['action'] == 'admin' || $actions['action'] == 'moderate') && isset($actions['area'])) + $actions['action'] = $actions['area']; + + // Check if there was no action or the action is display. + if (!isset($actions['action']) || $actions['action'] == 'display') + { + // It's a topic! Must be! + if (isset($actions['topic'])) + { + // Assume they can't view it, and queue it up for later. + $data[$k] = $txt['who_hidden']; + $topic_ids[(int) $actions['topic']][$k] = $txt['who_topic']; + } + // It's a board! + elseif (isset($actions['board'])) + { + // Hide first, show later. + $data[$k] = $txt['who_hidden']; + $board_ids[$actions['board']][$k] = $txt['who_board']; + } + // It's the board index!! It must be! + else + $data[$k] = $txt['who_index']; + } + // Probably an error or some goon? + elseif ($actions['action'] == '') + $data[$k] = $txt['who_index']; + // Some other normal action...? + else + { + // Viewing/editing a profile. + if ($actions['action'] == 'profile') + { + // Whose? Their own? + if (empty($actions['u'])) + $actions['u'] = $url[1]; + + $data[$k] = $txt['who_hidden']; + $profile_ids[(int) $actions['u']][$k] = $actions['action'] == 'profile' ? $txt['who_viewprofile'] : $txt['who_profile']; + } + elseif (($actions['action'] == 'post' || $actions['action'] == 'post2') && empty($actions['topic']) && isset($actions['board'])) + { + $data[$k] = $txt['who_hidden']; + $board_ids[(int) $actions['board']][$k] = isset($actions['poll']) ? $txt['who_poll'] : $txt['who_post']; + } + // A subaction anyone can view... if the language string is there, show it. + elseif (isset($actions['sa']) && isset($txt['whoall_' . $actions['action'] . '_' . $actions['sa']])) + $data[$k] = $preferred_prefix && isset($txt[$preferred_prefix . $actions['action'] . '_' . $actions['sa']]) ? $txt[$preferred_prefix . $actions['action'] . '_' . $actions['sa']] : $txt['whoall_' . $actions['action'] . '_' . $actions['sa']]; + // An action any old fellow can look at. (if ['whoall_' . $action] exists, we know everyone can see it.) + elseif (isset($txt['whoall_' . $actions['action']])) + $data[$k] = $preferred_prefix && isset($txt[$preferred_prefix . $actions['action']]) ? $txt[$preferred_prefix . $actions['action']] : $txt['whoall_' . $actions['action']]; + // Viewable if and only if they can see the board... + elseif (isset($txt['whotopic_' . $actions['action']])) + { + // Find out what topic they are accessing. + $topic = (int) (isset($actions['topic']) ? $actions['topic'] : (isset($actions['from']) ? $actions['from'] : 0)); + + $data[$k] = $txt['who_hidden']; + $topic_ids[$topic][$k] = $txt['whotopic_' . $actions['action']]; + } + elseif (isset($txt['whopost_' . $actions['action']])) + { + // Find out what message they are accessing. + $msgid = (int) (isset($actions['msg']) ? $actions['msg'] : (isset($actions['quote']) ? $actions['quote'] : 0)); + + $result = $smcFunc['db_query']('', ' + SELECT m.id_topic, m.subject + FROM {db_prefix}messages AS m + INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) + INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic' . ($modSettings['postmod_active'] ? ' AND t.approved = {int:is_approved}' : '') . ') + WHERE m.id_msg = {int:id_msg} + AND {query_see_board}' . ($modSettings['postmod_active'] ? ' + AND m.approved = {int:is_approved}' : '') . ' + LIMIT 1', + array( + 'is_approved' => 1, + 'id_msg' => $msgid, + ) + ); + list ($id_topic, $subject) = $smcFunc['db_fetch_row']($result); + $data[$k] = sprintf($txt['whopost_' . $actions['action']], $id_topic, $subject); + $smcFunc['db_free_result']($result); + + if (empty($id_topic)) + $data[$k] = $txt['who_hidden']; + } + // Viewable only by administrators.. (if it starts with whoadmin, it's admin only!) + elseif (allowedTo('moderate_forum') && isset($txt['whoadmin_' . $actions['action']])) + $data[$k] = $txt['whoadmin_' . $actions['action']]; + // Viewable by permission level. + elseif (isset($allowedActions[$actions['action']])) + { + if (allowedTo($allowedActions[$actions['action']])) + $data[$k] = $txt['whoallow_' . $actions['action']]; + else + $data[$k] = $txt['who_hidden']; + } + // Unlisted or unknown action. + else + $data[$k] = $txt['who_unknown']; + } + + // Maybe the action is integrated into another system? + if (count($integrate_actions = call_integration_hook('integrate_whos_online', array($actions))) > 0) + { + foreach ($integrate_actions as $integrate_action) + { + if (!empty($integrate_action)) + { + $data[$k] = $integrate_action; + break; + } + } + } + } + + // Load topic names. + if (!empty($topic_ids)) + { + $result = $smcFunc['db_query']('', ' + SELECT t.id_topic, m.subject + FROM {db_prefix}topics AS t + INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) + INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) + WHERE {query_see_board} + AND t.id_topic IN ({array_int:topic_list})' . ($modSettings['postmod_active'] ? ' + AND t.approved = {int:is_approved}' : '') . ' + LIMIT {int:limit}', + array( + 'topic_list' => array_keys($topic_ids), + 'is_approved' => 1, + 'limit' => count($topic_ids), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + // Show the topic's subject for each of the actions. + foreach ($topic_ids[$row['id_topic']] as $k => $session_text) + $data[$k] = sprintf($session_text, $row['id_topic'], censorText($row['subject'])); + } + $smcFunc['db_free_result']($result); + } + + // Load board names. + if (!empty($board_ids)) + { + $result = $smcFunc['db_query']('', ' + SELECT b.id_board, b.name + FROM {db_prefix}boards AS b + WHERE {query_see_board} + AND b.id_board IN ({array_int:board_list}) + LIMIT ' . count($board_ids), + array( + 'board_list' => array_keys($board_ids), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + // Put the board name into the string for each member... + foreach ($board_ids[$row['id_board']] as $k => $session_text) + $data[$k] = sprintf($session_text, $row['id_board'], $row['name']); + } + $smcFunc['db_free_result']($result); + } + + // Load member names for the profile. + if (!empty($profile_ids) && (allowedTo('profile_view_any') || allowedTo('profile_view_own'))) + { + $result = $smcFunc['db_query']('', ' + SELECT id_member, real_name + FROM {db_prefix}members + WHERE id_member IN ({array_int:member_list}) + LIMIT ' . count($profile_ids), + array( + 'member_list' => array_keys($profile_ids), + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($result)) + { + // If they aren't allowed to view this person's profile, skip it. + if (!allowedTo('profile_view_any') && $user_info['id'] != $row['id_member']) + continue; + + // Set their action on each - session/text to sprintf. + foreach ($profile_ids[$row['id_member']] as $k => $session_text) + $data[$k] = sprintf($session_text, $row['id_member'], $row['real_name']); + } + $smcFunc['db_free_result']($result); + } + + if (!is_array($urls)) + return isset($data[0]) ? $data[0] : false; + else + return $data; +} + +function Credits($in_admin = false) +{ + global $context, $modSettings, $forum_copyright, $forum_version, $boardurl, $txt, $user_info; + + // Don't blink. Don't even blink. Blink and you're dead. + loadLanguage('Who'); + + $context['credits'] = array( + array( + 'pretext' => $txt['credits_intro'], + 'title' => $txt['credits_team'], + 'groups' => array( + array( + 'title' => $txt['credits_groups_ps'], + 'members' => array( + 'Michael "Oldiesmann" Eshom', + 'Amacythe', + 'Jeremy "SleePy" Darwood', + 'Justin "metallica48423" O\'Leary', + ), + ), + array( + 'title' => $txt['credits_groups_dev'], + 'members' => array( + 'Norv', + 'Aaron van Geffen', + 'Antechinus', + 'Bjoern "Bloc" Kristiansen', + 'Hendrik Jan "Compuart" Visser', + 'Juan "JayBachatero" Hernandez', + 'Karl "RegularExpression" Benson', + $user_info['is_admin'] ? 'Matt "Grudge" Wolf': 'Grudge', + 'Michael "Thantos" Miller', + 'Selman "[SiNaN]" Eser', + 'Theodore "Orstio" Hildebrandt', + 'Thorsten "TE" Eurich', + 'winrules', + ), + ), + array( + 'title' => $txt['credits_groups_support'], + 'members' => array( + 'JimM', + 'Adish "(F.L.A.M.E.R)" Patel', + 'Aleksi "Lex" Kilpinen', + 'Ben Scott', + 'Bigguy', + 'CapadY', + 'Chas Large', + 'Duncan85', + 'Eliana Tamerin', + 'Fiery', + 'gbsothere', + 'Harro', + 'Huw', + 'Jan-Olof "Owdy" Eriksson', + 'Jeremy "jerm" Strike', + 'Jessica "Miss All Sunday" Gonzales', + 'K@', + 'Kevin "greyknight17" Hou', + 'KGIII', + 'Kill Em All', + 'Mattitude', + 'Mashby', + 'Mick G.', + 'Michele "Illori" Davis', + 'MrPhil', + 'Nick "Fizzy" Dyer', + 'Nick "Ha²"', + 'Paul_Pauline', + 'Piro "Sarge" Dhima', + 'Rumbaar', + 'Pitti', + 'RedOne', + 'S-Ace', + 'Wade "sησω" Poulsen', + 'xenovanis', + ), + ), + array( + 'title' => $txt['credits_groups_customize'], + 'members' => array( + 'Brad "IchBin™" Grow', + 'ディン1031', + 'Brannon "B" Hall', + 'Bryan "Runic" Deakin', + 'Bulakbol', + 'Colin "Shadow82x" Blaber', + 'Daniel15', + 'Eren Yasarkurt', + 'Gary M. Gadsdon', + 'Jason "JBlaze" Clemons', + 'Jerry', + 'Jonathan "vbgamer45" Valentin', + 'Kays', + 'Killer Possum', + 'Kirby', + 'Matt "SlammedDime" Zuba', + 'Matthew "Labradoodle-360" Kerle', + 'Nibogo', + 'Niko', + 'Peter "Arantor" Spicer', + 'snork13', + 'Spuds', + 'Steven "Fustrate" Hoffman', + 'Joey "Tyrsson" Smith', + ), + ), + array( + 'title' => $txt['credits_groups_docs'], + 'members' => array( + 'Joshua "groundup" Dickerson', + 'AngellinaBelle', + 'Daniel Diehl', + 'Dannii Willis', + 'emanuele', + 'Graeme Spence', + 'Jack "akabugeyes" Thorsen', + 'Jade Elizabeth Trainor', + 'Peter Duggan', + ), + ), + array( + 'title' => $txt['credits_groups_marketing'], + 'members' => array( + 'Kindred', + 'Marcus "cσσкιє мσηѕтєя" Forsberg', + 'Ralph "[n3rve]" Otowo', + 'rickC', + 'Tony Reid', + ), + ), + array( + 'title' => $txt['credits_groups_internationalizers'], + 'members' => array( + 'Relyana', + 'Akyhne', + 'GravuTrad', + ), + ), + array( + 'title' => $txt['credits_groups_servers'], + 'members' => array( + 'Derek Schwab', + 'Liroy "CoreISP" van Hoewijk', + ), + ), + ), + ), + ); + + // Give the translators some credit for their hard work. + if (!empty($txt['translation_credits'])) + $context['credits'][] = array( + 'title' => $txt['credits_groups_translation'], + 'groups' => array( + array( + 'title' => $txt['credits_groups_translation'], + 'members' => $txt['translation_credits'], + ), + ), + ); + + $context['credits'][] = array( + 'title' => $txt['credits_special'], + 'posttext' => $txt['credits_anyone'], + 'groups' => array( + array( + 'title' => $txt['credits_groups_consultants'], + 'members' => array( + 'Brett Flannigan', + 'Mark Rose', + 'René-Gilles "Nao 尚" Deberdt', + ), + ), + array( + 'title' => $txt['credits_groups_beta'], + 'members' => array( + $txt['credits_beta_message'], + ), + ), + array( + 'title' => $txt['credits_groups_translators'], + 'members' => array( + $txt['credits_translators_message'], + ), + ), + array( + 'title' => $txt['credits_groups_founder'], + 'members' => array( + 'Unknown W. "[Unknown]" Brackets', + ), + ), + array( + 'title' => $txt['credits_groups_orignal_pm'], + 'members' => array( + 'Jeff Lewis', + 'Joseph Fung', + 'David Recordon', + ), + ), + ), + ); + + $context['copyrights'] = array( + 'smf' => sprintf($forum_copyright, $forum_version), + + /* Modification Authors: You may add a copyright statement to this array for your mods. + Copyright statements should be in the form of a value only without a array key. I.E.: + 'Some Mod by Thantos © 2010', + $txt['some_mod_copyright'], + */ + 'mods' => array( + ), + ); + + if (!$in_admin) + { + loadTemplate('Who'); + $context['sub_template'] = 'credits'; + $context['robot_no_index'] = true; + $context['page_title'] = $txt['credits']; + } +} + +?> \ No newline at end of file diff --git a/Sources/Xml.php b/Sources/Xml.php new file mode 100644 index 0000000..d6a4134 --- /dev/null +++ b/Sources/Xml.php @@ -0,0 +1,75 @@ + array( + 'function' => 'GetJumpTo', + ), + 'messageicons' => array( + 'function' => 'ListMessageIcons', + ), + ); + if (!isset($_REQUEST['sa'], $sub_actions[$_REQUEST['sa']])) + fatal_lang_error('no_access', false); + + $sub_actions[$_REQUEST['sa']]['function'](); +} + +// Get a list of boards and categories used for the jumpto dropdown. +function GetJumpTo() +{ + global $user_info, $context, $smcFunc, $sourcedir; + + // Find the boards/cateogories they can see. + require_once($sourcedir . '/Subs-MessageIndex.php'); + $boardListOptions = array( + 'use_permissions' => true, + 'selected_board' => isset($context['current_board']) ? $context['current_board'] : 0, + ); + $context['jump_to'] = getBoardList($boardListOptions); + + // Make the board safe for display. + foreach ($context['jump_to'] as $id_cat => $cat) + { + $context['jump_to'][$id_cat]['name'] = un_htmlspecialchars(strip_tags($cat['name'])); + foreach ($cat['boards'] as $id_board => $board) + $context['jump_to'][$id_cat]['boards'][$id_board]['name'] = un_htmlspecialchars(strip_tags($board['name'])); + } + + $context['sub_template'] = 'jump_to'; +} + +function ListMessageIcons() +{ + global $context, $sourcedir, $board; + + require_once($sourcedir . '/Subs-Editor.php'); + $context['icons'] = getMessageIcons($board); + + $context['sub_template'] = 'message_icons'; +} + +?> \ No newline at end of file diff --git a/Sources/index.php b/Sources/index.php new file mode 100644 index 0000000..6ea8411 --- /dev/null +++ b/Sources/index.php @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/Themes/core/BoardIndex.template.php b/Themes/core/BoardIndex.template.php new file mode 100644 index 0000000..ba242d1 --- /dev/null +++ b/Themes/core/BoardIndex.template.php @@ -0,0 +1,513 @@ + + ', $txt['members'], ': ', $context['common_stats']['total_members'], '  •  ', $txt['posts_made'], ': ', $context['common_stats']['total_posts'], '  •  ', $txt['topics'], ': ', $context['common_stats']['total_topics'], ' + ', ($settings['show_latest_member'] ? '
    ' . $txt['welcome_member'] . ' ' . $context['common_stats']['latest_member']['link'] . '' . $txt['newest_member'] : '') , ' +

    '; + + // Show the news fader? (assuming there are things to show...) + if ($settings['show_newsfader'] && !empty($context['fader_news_lines'])) + { + echo ' +
    +

    + + ', $txt['news'], ' +

    +
    +
    ', $context['news_lines'][0], '
    +
    +
    + + '; + } + + /* Each category in categories is made up of: + id, href, link, name, is_collapsed (is it collapsed?), can_collapse (is it okay if it is?), + new (is it new?), collapse_href (href to collapse/expand), collapse_image (up/down image), + and boards. (see below.) */ + foreach ($context['categories'] as $category) + { + // If theres no parent boards we can see, avoid showing an empty category (unless its collapsed) + if (empty($category['boards']) && !$category['is_collapsed']) + continue; + + echo ' +
    +

    '; + + if (!$context['user']['is_guest'] && !empty($category['show_unread'])) + echo ' + ', $txt['view_unread_category'], ''; + + // If this category even can collapse, show a link to collapse it. + if ($category['can_collapse']) + echo ' + ', $category['collapse_image'], ' '; + + echo $category['link']; + + echo ' +

    '; + + // Assuming the category hasn't been collapsed... + if (!$category['is_collapsed']) + { + echo ' + '; + + /* Each board in each category's boards has: + new (is it new?), id, name, description, moderators (see below), link_moderators (just a list.), + children (see below.), link_children (easier to use.), children_new (are they new?), + topics (# of), posts (# of), link, href, and last_post. (see below.) */ + foreach ($category['boards'] as $board) + { + echo ' + + + '; + + // If the board or children is new, show an indicator. + if ($board['new'] || $board['children_new']) + echo ' + ', $txt['new_posts'], ''; + // Is it a redirection board? + elseif ($board['is_redirect']) + echo ' + *'; + // No new posts at all! The agony!! + else + echo ' + ', $txt['old_posts'], ''; + + echo ' + + + + + ', comma_format($board['posts']), ' ', $board['is_redirect'] ? $txt['redirects'] : $txt['posts'], '
    + ', $board['is_redirect'] ? '' : comma_format($board['topics']) . ' ' . $txt['board_topics'], ' + + '; + + /* The board's and children's 'last_post's have: + time, timestamp (a number that represents the time.), id (of the post), topic (topic id.), + link, href, subject, start (where they should go for the first unread post.), + and member. (which has id, name, link, href, username in it.) */ + if (!empty($board['last_post']['id'])) + echo ' + ', $txt['last_post'], ' ', $txt['by'], ' ', $board['last_post']['member']['link'] , '
    + ', $txt['in'], ' ', $board['last_post']['link'], '
    + ', $txt['on'], ' ', $board['last_post']['time']; + echo ' + +
    '; + + // Show the "Child Boards: ". (there's a link_children but we're going to bold the new ones...) + if (!empty($board['children'])) + { + // Sort the links into an array with new boards bold so it can be imploded. + $children = array(); + /* Each child in each board's children has: + id, name, description, new (is it new?), topics (#), posts (#), href, link, and last_post. */ + foreach ($board['children'] as $child) + { + if (!$child['is_redirect']) + $child['link'] = '' . $child['name'] . ''; + else + $child['link'] = '' . $child['name'] . ''; + + // Has it posts awaiting approval? + if ($child['can_approve_posts'] && ($child['unapproved_posts'] || $child['unapproved_topics'])) + $child['link'] .= ' (!)'; + + $children[] = $child['new'] ? '' . $child['link'] . '' : $child['link']; + } + echo ' + + + '; + } + } + echo ' +
    +

    ', $board['name'], ''; + + // Has it outstanding posts for approval? + if ($board['can_approve_posts'] && ($board['unapproved_posts'] || $board['unapproved_topics'])) + echo ' + (!)'; + + echo ' +

    +

    ', $board['description'] , '

    '; + + // Show the "Moderators: ". Each has name, href, link, and id. (but we're gonna use link_moderators.) + if (!empty($board['moderators'])) + echo ' +

    ', count($board['moderators']) == 1 ? $txt['moderator'] : $txt['moderators'], ': ', implode(', ', $board['link_moderators']), '

    '; + + // Show some basic information about the number of posts, etc. + echo ' +
    ', $txt['parent_boards'], ': ', implode(', ', $children), '
    '; + } + echo ' +
    '; + } + + if ($context['user']['is_logged']) + { + echo ' +
    +
    +
    + ', $txt['new_posts'], ' + ', $txt['old_posts'], ' +
    '; + + // Mark read button. + $mark_read_button = array( + 'markread' => array('text' => 'mark_as_read', 'image' => 'markread.gif', 'lang' => true, 'url' => $scripturl . '?action=markasread;sa=all;' . $context['session_var'] . '=' . $context['session_id']), + ); + + // Show the mark all as read button? + if ($settings['show_mark_read'] && !empty($context['categories'])) + template_button_strip($mark_read_button, 'top'); + + echo ' +
    +
    '; + } + + template_info_center(); +} + +function template_info_center() +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings; + + // Here's where the "Info Center" starts... + echo ' +
    +

    + + ', sprintf($txt['info_center_title'], $context['forum_name_html_safe']), ' +

    + +
    '; +} +?> \ No newline at end of file diff --git a/Themes/core/Display.template.php b/Themes/core/Display.template.php new file mode 100644 index 0000000..f42af46 --- /dev/null +++ b/Themes/core/Display.template.php @@ -0,0 +1,843 @@ + + ', $txt['report_sent'], ' +'; + } + + // Show the anchor for the top and for the first message. If the first message is new, say so. + echo ' + +', $context['first_new_message'] ? '' : ''; + + // Is this topic also a poll? + if ($context['is_poll']) + { + echo ' +
    +

    + ', $txt['poll'], ' +

    +

    + ', $context['poll']['question'], ' +

    +
    '; + + // Are they not allowed to vote but allowed to view the options? + if ($context['poll']['show_results'] || !$context['allow_vote']) + { + echo ' +
    '; + + // Show each option with its corresponding percentage bar. + foreach ($context['poll']['options'] as $option) + echo ' +
    ', $option['option'], '
    +
    ', $context['allow_poll_view'] ? $option['bar'] . ' ' . $option['votes'] . ' (' . $option['percent'] . '%)' : '', '
    '; + + echo ' +
    '; + + if ($context['allow_poll_view']) + echo ' +

    ', $txt['poll_total_voters'], ': ', $context['poll']['total_votes'], '

    '; + + } + // They are allowed to vote! Go to it! + else + { + echo ' +
    '; + + // Show a warning if they are allowed more than one option. + if ($context['poll']['allowed_warning']) + echo ' +

    ', $context['poll']['allowed_warning'], '

    '; + + echo ' +
      '; + + // Show each option with its button - a radio likely. + foreach ($context['poll']['options'] as $option) + echo ' +
    • ', $option['vote_button'], '
    • '; + + echo ' +
    + +
    + + +
    +
    '; + } + + // Is the clock ticking? + if (!empty($context['poll']['expire_time'])) + echo ' +

    ', ($context['poll']['is_expired'] ? $txt['poll_expired_on'] : $txt['poll_expires_on']), ': ', $context['poll']['expire_time'], '

    '; + + echo ' +
    +
    +
    '; + + // Build the poll moderation button array. + $poll_buttons = array( + 'vote' => array('test' => 'allow_return_vote', 'text' => 'poll_return_vote', 'image' => 'poll_options.gif', 'lang' => true, 'url' => $scripturl . '?topic=' . $context['current_topic'] . '.' . $context['start']), + 'results' => array('test' => 'show_view_results_button', 'text' => 'poll_results', 'image' => 'poll_results.gif', 'lang' => true, 'url' => $scripturl . '?topic=' . $context['current_topic'] . '.' . $context['start'] . ';viewresults'), + 'change_vote' => array('test' => 'allow_change_vote', 'text' => 'poll_change_vote', 'image' => 'poll_change_vote.gif', 'lang' => true, 'url' => $scripturl . '?action=vote;topic=' . $context['current_topic'] . '.' . $context['start'] . ';poll=' . $context['poll']['id'] . ';' . $context['session_var'] . '=' . $context['session_id']), + 'lock' => array('test' => 'allow_lock_poll', 'text' => (!$context['poll']['is_locked'] ? 'poll_lock' : 'poll_unlock'), 'image' => 'poll_lock.gif', 'lang' => true, 'url' => $scripturl . '?action=lockvoting;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), + 'edit' => array('test' => 'allow_edit_poll', 'text' => 'poll_edit', 'image' => 'poll_edit.gif', 'lang' => true, 'url' => $scripturl . '?action=editpoll;topic=' . $context['current_topic'] . '.' . $context['start']), + 'remove_poll' => array('test' => 'can_remove_poll', 'text' => 'poll_remove', 'image' => 'admin_remove_poll.gif', 'lang' => true, 'custom' => 'onclick="return confirm(\'' . $txt['poll_remove_warn'] . '\');"', 'url' => $scripturl . '?action=removepoll;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), + ); + + template_button_strip($poll_buttons); + + echo ' +

    '; + } + + // Does this topic have some events linked to it? + if (!empty($context['linked_calendar_events'])) + { + echo ' +
    +

    ', $txt['calendar_linked_events'], '

    +
    +
      '; + + foreach ($context['linked_calendar_events'] as $event) + echo ' +
    • + ', ($event['can_edit'] ? '* ' : ''), '', $event['title'], ': ', $event['start_date'], ($event['start_date'] != $event['end_date'] ? ' - ' . $event['end_date'] : ''), ' +
    • '; + + echo ' +
    +
    +
    '; + } + + // Build the normal button array. + $normal_buttons = array( + 'reply' => array('test' => 'can_reply', 'text' => 'reply', 'image' => 'reply.gif', 'lang' => true, 'url' => $scripturl . '?action=post;topic=' . $context['current_topic'] . '.' . $context['start'] . ';last_msg=' . $context['topic_last_message']), + 'add_poll' => array('test' => 'can_add_poll', 'text' => 'add_poll', 'image' => 'add_poll.gif', 'lang' => true, 'url' => $scripturl . '?action=editpoll;add;topic=' . $context['current_topic'] . '.' . $context['start']), + 'notify' => array('test' => 'can_mark_notify', 'text' => ($context['is_marked_notify'] ? 'unnotify' : 'notify'), 'image' => ($context['is_marked_notify'] ? 'un' : ''). 'notify.gif', 'lang' => true, 'custom' => 'onclick="return confirm(\'' . ($context['is_marked_notify'] ? $txt['notification_disable_topic'] : $txt['notification_enable_topic']) . '\');"', 'url' => $scripturl . '?action=notify;sa=' . ($context['is_marked_notify'] ? 'off' : 'on') . ';topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), + 'mark_unread' => array('test' => 'can_mark_unread', 'text' => 'mark_unread', 'image' => 'markunread.gif', 'lang' => true, 'url' => $scripturl . '?action=markasread;sa=topic;t=' . $context['mark_unread_time'] . ';topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), + 'send' => array('test' => 'can_send_topic', 'text' => 'send_topic', 'image' => 'sendtopic.gif', 'lang' => true, 'url' => $scripturl . '?action=emailuser;sa=sendtopic;topic=' . $context['current_topic'] . '.0'), + 'print' => array('text' => 'print', 'image' => 'print.gif', 'lang' => true, 'custom' => 'rel="new_win nofollow"', 'url' => $scripturl . '?action=printpage;topic=' . $context['current_topic'] . '.0'), + ); + + // Allow adding new buttons easily. + call_integration_hook('integrate_display_buttons', array(&$normal_buttons)); + + // Show the page index... "Pages: [1]". + echo ' +
    + +
    ', $txt['pages'], ': ', $context['page_index'], !empty($modSettings['topbottomEnable']) ? $context['menu_separator'] . '   ' . $txt['go_down'] . '' : '', '
    + +
    '; + + // Show the topic information - icon, subject, etc. + echo ' +
    +

    + + ', $txt['author'], ' + ', $txt['topic'], ': ', $context['subject'], '  (', $txt['read'], ' ', $context['num_views'], ' ', $txt['times'], ') +

    '; + + if (!empty($settings['display_who_viewing'])) + { + echo ' +
    '; + + // Show just numbers...? + if ($settings['display_who_viewing'] == 1) + echo count($context['view_members']), ' ', count($context['view_members']) == 1 ? $txt['who_member'] : $txt['members']; + // Or show the actual people viewing the topic? + else + echo empty($context['view_members_list']) ? '0 ' . $txt['members'] : implode(', ', $context['view_members_list']) . ((empty($context['view_num_hidden']) || $context['can_moderate_forum']) ? '' : ' (+ ' . $context['view_num_hidden'] . ' ' . $txt['hidden'] . ')'); + + // Now show how many guests are here too. + echo $txt['who_and'], $context['view_num_guests'], ' ', $context['view_num_guests'] == 1 ? $txt['guest'] : $txt['guests'], $txt['who_viewing_topic'], ' +
    '; + } + + echo ' +
    '; + + // These are some cache image buttons we may want. + $reply_button = create_button('quote.gif', 'reply', 'quote', 'align="middle"'); + $modify_button = create_button('modify.gif', 'modify', 'modify', 'align="middle"'); + $remove_button = create_button('delete.gif', 'remove', 'remove', 'align="middle"'); + $split_button = create_button('split.gif', 'split', 'split', 'align="middle"'); + $approve_button = create_button('approve.gif', 'approve', 'approve', 'align="middle"'); + $restore_message_button = create_button('restore_topic.gif', 'restore_message', 'restore_message', 'align="middle"'); + + $ignoredMsgs = array(); + $removableMessageIDs = array(); + + // Get all the messages... + while ($message = $context['get_message']()) + { + $is_first_post = !isset($is_first_post) ? true : false; + $ignoring = false; + if ($message['can_remove']) + $removableMessageIDs[] = $message['id']; + + echo ' +
    '; + + // Are we ignoring this message? + if (!empty($message['is_ignored'])) + { + $ignoring = true; + $ignoredMsgs[] = $message['id']; + } + + // Show the message anchor and a "new" anchor if this message is new. + if ($message['id'] != $context['first_message']) + echo ' + ', $message['first_new'] ? '' : ''; + + echo ' +
    '; + + // Show information about the poster of this message. + echo ' +
    +

    ', $message['member']['link'], '

    +
      '; + + // Show the member's custom title, if they have one. + if (isset($message['member']['title']) && $message['member']['title'] != '') + echo ' +
    • ', $message['member']['title'], '
    • '; + + // Show the member's primary group (like 'Administrator') if they have one. + if (isset($message['member']['group']) && $message['member']['group'] != '') + echo ' +
    • ', $message['member']['group'], '
    • '; + + // Don't show these things for guests. + if (!$message['member']['is_guest']) + { + // Show the post group if and only if they have no other group or the option is on, and they are in a post group. + if ((empty($settings['hide_post_group']) || $message['member']['group'] == '') && $message['member']['post_group'] != '') + echo ' +
    • ', $message['member']['post_group'], '
    • '; + echo ' +
    • ', $message['member']['group_stars'], '
    • '; + + // Is karma display enabled? Total or +/-? + if ($modSettings['karmaMode'] == '1') + echo ' +
    • ', $modSettings['karmaLabel'], ' ', $message['member']['karma']['good'] - $message['member']['karma']['bad'], '
    • '; + elseif ($modSettings['karmaMode'] == '2') + echo ' +
    • ', $modSettings['karmaLabel'], ' +', $message['member']['karma']['good'], '/-', $message['member']['karma']['bad'], '
    • '; + + // Is this user allowed to modify this member's karma? + if ($message['member']['karma']['allow']) + echo ' +
    • + ', $modSettings['karmaApplaudLabel'], ' + ', $modSettings['karmaSmiteLabel'], ' +
    • '; + + // Show online and offline buttons? + if (!empty($modSettings['onlineEnable'])) + echo ' +
    • ', $context['can_send_pm'] ? '' : '', $settings['use_image_buttons'] ? '' . $message['member']['online']['text'] . '' : $message['member']['online']['text'], $context['can_send_pm'] ? '' : '', $settings['use_image_buttons'] ? ' ' . $message['member']['online']['text'] . '' : '', '
    • '; + + // Show the member's gender icon? + if (!empty($settings['show_gender']) && $message['member']['gender']['image'] != '' && !isset($context['disabled_fields']['gender'])) + echo ' +
    • ', $txt['gender'], ': ', $message['member']['gender']['image'], '
    • '; + + // Show how many posts they have made. + if (!isset($context['disabled_fields']['posts'])) + echo ' +
    • ', $txt['member_postcount'], ': ', $message['member']['posts'], '
    • '; + + // Any custom fields for standard placement? + if (!empty($message['member']['custom_fields'])) + { + foreach ($message['member']['custom_fields'] as $custom) + if (empty($custom['placement']) && !empty($custom['value'])) + echo ' +
    • ', $custom['title'], ': ', $custom['value'], '
    • '; + } + + // Show avatars, images, etc.? + if (!empty($settings['show_user_images']) && empty($options['show_no_avatars']) && !empty($message['member']['avatar']['image'])) + echo ' +
    • ', $message['member']['avatar']['image'], '
    • '; + + // Show their personal text? + if (!empty($settings['show_blurb']) && $message['member']['blurb'] != '') + echo ' +
    • ', $message['member']['blurb'], '
    • '; + + // Any custom fields to show as icons? + if (!empty($message['member']['custom_fields'])) + { + $shown = false; + foreach ($message['member']['custom_fields'] as $custom) + { + if ($custom['placement'] != 1 || empty($custom['value'])) + continue; + if (empty($shown)) + { + $shown = true; + echo ' +
    • +
        '; + } + echo ' +
      • ', $custom['value'], '
      • '; + } + if ($shown) + echo ' +
      +
    • '; + } + + // This shows the popular messaging icons. + if ($message['member']['has_messenger'] && $message['member']['can_view_profile']) + echo ' +
    • +
        + ', !isset($context['disabled_fields']['icq']) && !empty($message['member']['icq']['link']) ? '
      • ' . $message['member']['icq']['link'] . '
      • ' : '', ' + ', !isset($context['disabled_fields']['msn']) && !empty($message['member']['msn']['link']) ? '
      • ' . $message['member']['msn']['link'] . '
      • ' : '', ' + ', !isset($context['disabled_fields']['aim']) && !empty($message['member']['aim']['link']) ? '
      • ' . $message['member']['aim']['link'] . '
      • ' : '', ' + ', !isset($context['disabled_fields']['yim']) && !empty($message['member']['yim']['link']) ? '
      • ' . $message['member']['yim']['link'] . '
      • ' : '', ' +
      +
    • '; + + // Show the profile, website, email address, and personal message buttons. + if ($settings['show_profile_buttons']) + { + echo ' +
    • + +
    • '; + } + + // Are we showing the warning status? + if ($message['member']['can_see_warning']) + echo ' +
    • ', $context['can_issue_warning'] ? '' : '', '', $txt['user_warn_' . $message['member']['warning_status']], '', $context['can_issue_warning'] ? '' : '', '', $txt['warn_' . $message['member']['warning_status']], '
    • '; + } + // Otherwise, show the guest's email. + elseif (!empty($message['member']['email']) && in_array($message['member']['show_email'], array('yes', 'yes_permission_override', 'no_through_forum'))) + echo ' +
    • ', ($settings['use_image_buttons'] ? '' . $txt['email'] . '' : $txt['email']), '
    • '; + + // Done with the information about the poster... on to the post itself. + echo ' +
    +
    +
    +
    +
    +
    +
    + ', $message['subject'], ' +
    +
    « ', !empty($message['counter']) ? $txt['reply_noun'] . ' #' . $message['counter'] : '', ' ', $txt['on'], ': ', $message['time'], ' »
    +
    +
    '; + + // If this is the first post, (#0) just say when it was posted - otherwise give the reply #. + if ($message['can_approve'] || $context['can_reply'] || $message['can_modify'] || $message['can_remove'] || $context['can_split'] || $context['can_restore_msg']) + echo ' +
      '; + + // Maybe we can approve it, maybe we should? + if ($message['can_approve']) + echo ' +
    • ', $approve_button, '
    • '; + + // Can they reply? Have they turned on quick reply? + if ($context['can_quote'] && !empty($options['display_quick_reply'])) + echo ' +
    • ', $reply_button, '
    • '; + + // So... quick reply is off, but they *can* reply? + elseif ($context['can_quote']) + echo ' +
    • ', $reply_button, '
    • '; + + // Can the user modify the contents of this post? + if ($message['can_modify']) + echo ' +
    • ', $modify_button, '
    • '; + + // How about... even... remove it entirely?! + if ($message['can_remove']) + echo ' +
    • ', $remove_button, '
    • '; + + // What about splitting it off the rest of the topic? + if ($context['can_split'] && !empty($context['real_num_replies'])) + echo ' +
    • ', $split_button, '
    • '; + + // Can we restore topics? + if ($context['can_restore_msg']) + echo ' +
    • ', $restore_message_button, '
    • '; + + // Show a checkbox for quick moderation? + if (!empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1 && $message['can_remove']) + echo ' + '; + + if ($message['can_approve'] || $context['can_reply'] || $message['can_modify'] || $message['can_remove'] || $context['can_split'] || $context['can_restore_msg']) + echo ' +
    '; + + echo ' +
    '; + + // Ignoring this user? Hide the post. + if ($ignoring) + echo ' +
    + ', $txt['ignoring_user'], ' + +
    '; + + // Show the post itself, finally! + echo ' +
    +
    '; + + if (!$message['approved'] && $message['member']['id'] != 0 && $message['member']['id'] == $context['user']['id']) + echo ' +
    + ', $txt['post_awaiting_approval'], ' +
    '; + echo ' +
    ', $message['body'], '
    +
    ', $message['can_modify'] ? ' + ' : ''; + + // Assuming there are attachments... + if (!empty($message['attachment'])) + { + // Now for the attachments, signature, ip logged, etc... + echo ' + '; + } + + echo ' +
    +
    +
    '; + + // Show "« Last Edit: Time by Person »" if this post was edited. + if ($settings['show_modify'] && !empty($message['modified']['name'])) + echo ' + « ', $txt['last_edit'], ': ', $message['modified']['time'], ' ', $txt['by'], ' ', $message['modified']['name'], ' »'; + + echo ' +
    +
    '; + + // Maybe they want to report this post to the moderator(s)? + if ($context['can_report_moderator']) + echo ' + ', $txt['report_to_mod'], '  '; + + // Can we issue a warning because of this post? Remember, we can't give guests warnings. + if ($context['can_issue_warning'] && !$message['is_message_author'] && !$message['member']['is_guest']) + echo ' + ', $txt['issue_warning_post'], ''; + echo ' + '; + + // Show the IP to this user for this post - because you can moderate? + if ($context['can_moderate_forum'] && !empty($message['member']['ip'])) + echo ' + ', $message['member']['ip'], ' (?)'; + // Or, should we show it because this is you? + elseif ($message['can_see_ip']) + echo ' + ', $message['member']['ip'], ''; + // Okay, are you at least logged in? Then we can show something about why IPs are logged... + elseif (!$context['user']['is_guest']) + echo ' + ', $txt['logged'], ''; + // Otherwise, you see NOTHING! + else + echo ' + ', $txt['logged']; + + echo ' +
    '; + + // Are there any custom profile fields for above the signature? + if (!empty($message['member']['custom_fields'])) + { + $shown = false; + foreach ($message['member']['custom_fields'] as $custom) + { + if ($custom['placement'] != 2 || empty($custom['value'])) + continue; + if (empty($shown)) + { + $shown = true; + echo ' +
    +
      ', $message['member']['signature'], '
    '; + + echo ' +
    +
    +
    '; + } + + echo ' +
    '; + echo ' +
    +'; + + echo ' +
    + +
    ', $txt['pages'], ': ', $context['page_index'], !empty($modSettings['topbottomEnable']) ? $context['menu_separator'] . '   ' . $txt['go_up'] . '' : '', '
    +
    ', $context['previous_next'], '
    +
    '; + + // Show the lower breadcrumbs. + theme_linktree(); + + $mod_buttons = array( + 'move' => array('test' => 'can_move', 'text' => 'move_topic', 'image' => 'admin_move.gif', 'lang' => true, 'url' => $scripturl . '?action=movetopic;topic=' . $context['current_topic'] . '.0'), + 'delete' => array('test' => 'can_delete', 'text' => 'remove_topic', 'image' => 'admin_rem.gif', 'lang' => true, 'custom' => 'onclick="return confirm(\'' . $txt['are_sure_remove_topic'] . '\');"', 'url' => $scripturl . '?action=removetopic2;topic=' . $context['current_topic'] . '.0;' . $context['session_var'] . '=' . $context['session_id']), + 'lock' => array('test' => 'can_lock', 'text' => empty($context['is_locked']) ? 'set_lock' : 'set_unlock', 'image' => 'admin_lock.gif', 'lang' => true, 'url' => $scripturl . '?action=lock;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), + 'sticky' => array('test' => 'can_sticky', 'text' => empty($context['is_sticky']) ? 'set_sticky' : 'set_nonsticky', 'image' => 'admin_sticky.gif', 'lang' => true, 'url' => $scripturl . '?action=sticky;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), + 'merge' => array('test' => 'can_merge', 'text' => 'merge', 'image' => 'merge.gif', 'lang' => true, 'url' => $scripturl . '?action=mergetopics;board=' . $context['current_board'] . '.0;from=' . $context['current_topic']), + 'calendar' => array('test' => 'calendar_post', 'text' => 'calendar_link', 'image' => 'linktocal.gif', 'lang' => true, 'url' => $scripturl . '?action=post;calendar;msg=' . $context['topic_first_message'] . ';topic=' . $context['current_topic'] . '.0'), + ); + + // Restore topic. eh? No monkey business. + if ($context['can_restore_topic']) + $mod_buttons[] = array('text' => 'restore_topic', 'image' => '', 'lang' => true, 'url' => $scripturl . '?action=restoretopic;topics=' . $context['current_topic'] . ';' . $context['session_var'] . '=' . $context['session_id']); + + // Allow adding new mod buttons easily. + call_integration_hook('integrate_mod_buttons', array(&$mod_buttons)); + + echo ' +
    ', template_button_strip($mod_buttons, 'bottom', array('id' => 'moderationbuttons_strip')), '
    '; + + // Show the jumpto box, or actually...let Javascript do it. + echo ' +
    +
     
    +

    '; + + if ($context['can_reply'] && !empty($options['display_quick_reply'])) + { + echo ' + +
    '; + + echo ' +

    + + + + + ', $txt['quick_reply'], ' +

    + +
    '; + } + + if ($context['show_spellchecking']) + echo ' +
    +'; + + echo ' + +'; +} + +?> \ No newline at end of file diff --git a/Themes/core/GenericMenu.template.php b/Themes/core/GenericMenu.template.php new file mode 100644 index 0000000..bee7ca0 --- /dev/null +++ b/Themes/core/GenericMenu.template.php @@ -0,0 +1,375 @@ + +
    + '; + + // What one are we rendering? + $context['cur_menu_id'] = isset($context['cur_menu_id']) ? $context['cur_menu_id'] + 1 : 1; + $menu_context = &$context['menu_data_' . $context['cur_menu_id']]; + + // For every section that appears on the sidebar... + $firstSection = true; + foreach ($menu_context['sections'] as $section) + { + // Show the section header - and pump up the line spacing for readability. + echo ' +
    +
    +

    '; + + if ($firstSection && !empty($menu_context['can_toggle_drop_down'])) + { + echo ' + ', $section['title'],' + ! + '; + } + + else + { + echo ' + ', $section['title']; + } + + echo ' +

    +
    +
      '; + + // For every area of this section show a link to that area (bold if it's currently selected.) + foreach ($section['areas'] as $i => $area) + { + // Not supposed to be printed? + if (empty($area['label'])) + continue; + + echo ' +
    • '; + + // Is this the current area, or just some area? + if ($i == $menu_context['current_area']) + { + echo ' + ', $area['label'], ''; + + if (empty($context['tabs'])) + $context['tabs'] = isset($area['subsections']) ? $area['subsections'] : array(); + } + else + echo ' + ', $area['label'], ''; + + echo ' +
    • '; + } + + echo ' +
    +
    '; + + $firstSection = false; + } + + // This is where the actual "main content" area for the admin section starts. + echo ' +
    +
    '; + + // If there are any "tabs" setup, this is the place to shown them. + //!!! Clean this up! + if (!empty($context['tabs']) && empty($context['force_disable_tabs'])) + template_generic_menu_tabs($menu_context); +} + +// Part of the sidebar layer - closes off the main bit. +function template_generic_menu_sidebar_below() +{ + global $context, $settings, $options; + + echo ' +
    +
    '; +} + +// This contains the html for the side bar of the admin center, which is used for all admin pages. +function template_generic_menu_dropdown_above() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + // Which menu are we rendering? + $context['cur_menu_id'] = isset($context['cur_menu_id']) ? $context['cur_menu_id'] + 1 : 1; + $menu_context = &$context['menu_data_' . $context['cur_menu_id']]; + + if (!empty($menu_context['can_toggle_drop_down'])) + echo ' + '; + + echo ' +
    +
    '; + + // This is the main table - we need it so we can keep the content to the right of it. + echo ' +
    '; + + // It's possible that some pages have their own tabs they wanna force... + if (!empty($context['tabs'])) + template_generic_menu_tabs($menu_context); +} + +// Part of the admin layer - used with admin_above to close the table started in it. +function template_generic_menu_dropdown_below() +{ + global $context, $settings, $options; + + echo ' +
    '; +} + +// Some code for showing a tabbed view. +function template_generic_menu_tabs(&$menu_context) +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + // Handy shortcut. + $tab_context = &$menu_context['tab_data']; + + // Right to left tabs should be in reverse order. + if ($context['right_to_left']) + $tab_context['tabs'] = array_reverse($tab_context['tabs'], true); + + // Exactly how many tabs do we have? + foreach ($context['tabs'] as $id => $tab) + { + // Can this not be accessed? + if (!empty($tab['disabled'])) + { + $tab_context['tabs'][$id]['disabled'] = true; + continue; + } + + // Did this not even exist - or do we not have a label? + if (!isset($tab_context['tabs'][$id])) + $tab_context['tabs'][$id] = array('label' => $tab['label']); + elseif (!isset($tab_context['tabs'][$id]['label'])) + $tab_context['tabs'][$id]['label'] = $tab['label']; + + // Has a custom URL defined in the main admin structure? + if (isset($tab['url']) && !isset($tab_context['tabs'][$id]['url'])) + $tab_context['tabs'][$id]['url'] = $tab['url']; + // Any additional paramaters for the url? + if (isset($tab['add_params']) && !isset($tab_context['tabs'][$id]['add_params'])) + $tab_context['tabs'][$id]['add_params'] = $tab['add_params']; + // Has it been deemed selected? + if (!empty($tab['is_selected'])) + $tab_context['tabs'][$id]['is_selected'] = true; + // Does it have its own help? + if (!empty($tab['help'])) + $tab_context['tabs'][$id]['help'] = $tab['help']; + // Is this the last one? + if (!empty($tab['is_last']) && !isset($tab_context['override_last'])) + $tab_context['tabs'][$id]['is_last'] = true; + } + + // Find the selected tab + foreach ($tab_context['tabs'] as $sa => $tab) + if (!empty($tab['is_selected']) || (isset($menu_context['current_subsection']) && $menu_context['current_subsection'] == $sa)) + { + $selected_tab = $tab; + $tab_context['tabs'][$sa]['is_selected'] = true; + } + + echo ' +
    +

    '; + + // Show a help item? + if (!empty($selected_tab['help']) || !empty($tab_context['help'])) + echo ' + ', $txt['help'], ' '; + + echo ' + ', $tab_context['title'], ' +

    '; + + // Shall we use the tabs? + if (!empty($settings['use_tabs'])) + { + echo ' +
    + ', !empty($selected_tab['description']) ? $selected_tab['description'] : $tab_context['description'], ' +
    '; + + echo ' +
    +
    +
    +
      '; + + // Print out all the items in this tab. + foreach ($tab_context['tabs'] as $sa => $tab) + { + if (!empty($tab['disabled'])) + continue; + + if (!empty($tab['is_selected'])) + { + echo ' +
    • + + + ', $tab['label'], ' + + +
    • '; + } + else + echo ' + + + ', $tab['label'], ' + + '; + } + + // the end of tabs + echo ' +
    +

    +
    '; + } + // ...if not use the old style + else + { + echo ' +
    '; + + // Print out all the items in this tab. + foreach ($tab_context['tabs'] as $sa => $tab) + { + if (!empty($tab['disabled'])) + continue; + + if (!empty($tab['is_selected'])) + { + echo ' + * ', $tab['label'], ''; + } + else + echo ' + ', $tab['label'], ''; + + if (empty($tab['is_last'])) + echo ' | '; + } + + echo ' +
    +
    + ', isset($selected_tab['description']) ? $selected_tab['description'] : $tab_context['description'], ' +
    + + '; + } +} + +?> \ No newline at end of file diff --git a/Themes/core/Memberlist.template.php b/Themes/core/Memberlist.template.php new file mode 100644 index 0000000..4aebdfe --- /dev/null +++ b/Themes/core/Memberlist.template.php @@ -0,0 +1,203 @@ + array('text' => 'view_all_members', 'image' => 'mlist.gif', 'lang' => true, 'url' => $scripturl . '?action=mlist' . ';sa=all', 'active' => true), + 'mlist_search' => array('text' => 'mlist_search', 'image' => 'mlist.gif', 'lang' => true, 'url' => $scripturl . '?action=mlist;sa=search'), + ); + + echo ' +
    +
    +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    + ', template_button_strip($memberlist_buttons, 'bottom'), ' +
    '; + + echo ' +
    +

    + ', $txt['members_list'], ''; + if (!isset($context['old_search'])) + echo ' + ', $context['letter_links'], ''; + echo ' +

    + + + '; + + // Display each of the column headers of the table. + foreach ($context['columns'] as $column) + { + // We're not able (through the template) to sort the search results right now... + if (isset($context['old_search'])) + echo ' + '; + // This is a selected column, so underline it or some such. + elseif ($column['selected']) + echo ' + '; + // This is just some column... show the link and be done with it. + else + echo ' + '; + } + echo ' + + + '; + + // Assuming there are members loop through each one displaying their data. + if (!empty($context['members'])) + { + foreach ($context['members'] as $member) + { + echo ' + + + + '; + + if (!isset($context['disabled_fields']['website'])) + echo ' + '; + + // ICQ? + if (!isset($context['disabled_fields']['icq'])) + echo ' + '; + + // AIM? + if (!isset($context['disabled_fields']['aim'])) + echo ' + '; + + // YIM? + if (!isset($context['disabled_fields']['yim'])) + echo ' + '; + + // MSN? + if (!isset($context['disabled_fields']['msn'])) + echo ' + '; + + // Group and date. + echo ' + + '; + + if (!isset($context['disabled_fields']['posts'])) + echo ' + + '; + + echo ' + '; + } + } + // No members? + else + echo ' + + + '; + + // Show the page numbers again. (makes 'em easier to find!) + echo ' + +
    + ', $column['label'], ' + ' . $column['label'] . ' + ', $column['link'], '
    + ', $context['can_send_pm'] ? '' : '', $settings['use_image_buttons'] ? '' . $member['online']['text'] . '' : $member['online']['label'], $context['can_send_pm'] ? '' : '', ' + ', $member['link'], '', $member['show_email'] == 'no' ? '' : '' . $txt['email'] . '', '', $member['website']['url'] != '' ? '' . $member['website']['title'] . '' : '', '', $member['icq']['link'], '', $member['aim']['link'], '', $member['yim']['link'], '', $member['msn']['link'], '', empty($member['group']) ? $member['post_group'] : $member['group'], '', $member['registered_date'], '', $member['posts'], ' + ', $member['posts'] > 0 ? '' : '', ' +
    ', $txt['search_no_results'], '
    +
    '; + + echo ' +
    +
    ', $txt['pages'], ': ', $context['page_index'], '
    '; + + // If it is displaying the result of a search show a "search again" link to edit their criteria. + if (isset($context['old_search'])) + echo ' + '; + echo ' +
    +
    '; +} + +// A page allowing people to search the member list. +function template_search() +{ + global $context, $settings, $options, $scripturl, $txt; + + // Build the memberlist button array. + $membersearch_buttons = array( + 'view_all_members' => array('text' => 'view_all_members', 'image' => 'mlist.gif', 'lang' => true, 'url' => $scripturl . '?action=mlist;sa=all'), + 'mlist_search' => array('text' => 'mlist_search', 'image' => 'mlist.gif', 'lang' => true, 'url' => $scripturl . '?action=mlist;sa=search', 'active' => true), + ); + + // Start the submission form for the search! + echo ' +
    +
    +
    + ', template_button_strip($membersearch_buttons, 'right'), ' +
    +
    +

    + ', !empty($settings['use_buttons']) ? '' : '', $txt['mlist_search'], ' +

    '; + + // Display the input boxes for the form. + echo ' +
    + + + ', $txt['search_for'], ': + + '; + + $count = 0; + foreach ($context['search_fields'] as $id => $title) + { + echo ' +
    '; + // Halfway through? + if (round(count($context['search_fields']) / 2) == ++$count) + echo ' +
    + '; + } + echo ' + +
    +
    +
    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/core/MessageIndex.template.php b/Themes/core/MessageIndex.template.php new file mode 100644 index 0000000..1a3c0c3 --- /dev/null +++ b/Themes/core/MessageIndex.template.php @@ -0,0 +1,506 @@ +'; + + if (!empty($context['boards']) && (!empty($options['show_children']) || $context['start'] == 0)) + { + echo ' +
    + + + + '; + + foreach ($context['boards'] as $board) + { + echo ' + + + '; + + // If the board or children is new, show an indicator. + if ($board['new'] || $board['children_new']) + echo ' + ', $txt['new_posts'], ''; + // Is it a redirection board? + elseif ($board['is_redirect']) + echo ' + *'; + // No new posts at all! The agony!! + else + echo ' + ', $txt['old_posts'], ''; + + echo ' + + + + + ', comma_format($board['posts']), ' ', $board['is_redirect'] ? $txt['redirects'] : $txt['posts'], '
    + ', $board['is_redirect'] ? '' : comma_format($board['topics']) . ' ' . $txt['board_topics'], ' + + '; + + /* The board's and children's 'last_post's have: + time, timestamp (a number that represents the time.), id (of the post), topic (topic id.), + link, href, subject, start (where they should go for the first unread post.), + and member. (which has id, name, link, href, username in it.) */ + if (!empty($board['last_post']['id'])) + echo ' + ', $txt['last_post'], ' ', $txt['by'], ' ', $board['last_post']['member']['link'] , '
    + ', $txt['in'], ' ', $board['last_post']['link'], '
    + ', $txt['on'], ' ', $board['last_post']['time']; + echo ' + +
    '; + + // Show the "Child Boards: ". (there's a link_children but we're going to bold the new ones...) + if (!empty($board['children'])) + { + // Sort the links into an array with new boards bold so it can be imploded. + $children = array(); + /* Each child in each board's children has: + id, name, description, new (is it new?), topics (#), posts (#), href, link, and last_post. */ + foreach ($board['children'] as $child) + { + if (!$child['is_redirect']) + $child['link'] = '' . $child['name'] . ''; + else + $child['link'] = '' . $child['name'] . ''; + + // Has it posts awaiting approval? + if ($child['can_approve_posts'] && ($child['unapproved_posts'] || $child['unapproved_topics'])) + $child['link'] .= ' (!)'; + + $children[] = $child['new'] ? '' . $child['link'] . '' : $child['link']; + } + echo ' + + + '; + } + } + echo ' +
    ', $txt['parent_boards'], '
    +

    ', $board['name'], ''; + + // Has it outstanding posts for approval? + if ($board['can_approve_posts'] && ($board['unapproved_posts'] || $board['unapproved_topics'])) + echo ' + (!)'; + + echo ' +

    +

    ', $board['description'] , '

    '; + + // Show the "Moderators: ". Each has name, href, link, and id. (but we're gonna use link_moderators.) + if (!empty($board['moderators'])) + echo ' +

    ', count($board['moderators']) == 1 ? $txt['moderator'] : $txt['moderators'], ': ', implode(', ', $board['link_moderators']), '

    '; + + // Show some basic information about the number of posts, etc. + echo ' +
    ', $txt['parent_boards'], ': ', implode(', ', $children), '
    +
    '; + } + + if (!empty($options['show_board_desc']) && $context['description'] != '') + { + echo ' +
    +
    ', $context['description'], '
    +
    '; + } + + // Create the button set... + $normal_buttons = array( + 'new_topic' => array('test' => 'can_post_new', 'text' => 'new_topic', 'image' => 'new_topic.gif', 'lang' => true, 'url' => $scripturl . '?action=post;board=' . $context['current_board'] . '.0'), + 'post_poll' => array('test' => 'can_post_poll', 'text' => 'new_poll', 'image' => 'new_poll.gif', 'lang' => true, 'url' => $scripturl . '?action=post;board=' . $context['current_board'] . '.0;poll'), + 'notify' => array('test' => 'can_mark_notify', 'text' => $context['is_marked_notify'] ? 'unnotify' : 'notify', 'image' => ($context['is_marked_notify'] ? 'un' : '') . 'notify.gif', 'lang' => true, 'custom' => 'onclick="return confirm(\'' . ($context['is_marked_notify'] ? $txt['notification_disable_board'] : $txt['notification_enable_board']) . '\');"', 'url' => $scripturl . '?action=notifyboard;sa=' . ($context['is_marked_notify'] ? 'off' : 'on') . ';board=' . $context['current_board'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), + 'markread' => array('text' => 'mark_read_short', 'image' => 'markread.gif', 'lang' => true, 'url' => $scripturl . '?action=markasread;sa=board;board=' . $context['current_board'] . '.0;' . $context['session_var'] . '=' . $context['session_id']), + ); + + // They can only mark read if they are logged in and it's enabled! + if (!$context['user']['is_logged'] || !$settings['show_mark_read']) + unset($normal_buttons['markread']); + + // Allow adding new buttons easily. + call_integration_hook('integrate_messageindex_buttons', array(&$normal_buttons)); + + if (!$context['no_topic_listing']) + { + echo ' +
    +
    ', $txt['pages'], ': ', $context['page_index'], !empty($modSettings['topbottomEnable']) ? $context['menu_separator'] . '  ' . $txt['go_down'] . '' : '', '
    + ', template_button_strip($normal_buttons, 'bottom'), ' +
    '; + + // If Quick Moderation is enabled start the form. + if (!empty($context['can_quick_mod']) && $options['display_quick_mod'] > 0 && !empty($context['topics'])) + echo ' +
    '; + + echo ' +
    + '; + + // Are there actually any topics to show? + if (!empty($context['topics'])) + { + echo ' + + + + + + + + '; + + // Show a "select all" box for quick moderation? + if (!empty($context['can_quick_mod']) && $options['display_quick_mod'] == 1) + echo ' + '; + // If it's on in "image" mode, don't show anything but the column. + elseif (!empty($context['can_quick_mod'])) + echo ' + '; + echo ' + + '; + } + echo ' + '; + + if (!empty($settings['display_who_viewing'])) + { + echo ' + + + '; + } + + // If this person can approve items and we have some awaiting approval tell them. + if (!empty($context['unapproved_posts_message'])) + { + echo ' + + + '; + } + + // No topics.... just say, "sorry bub". + if (empty($context['topics'])) + echo ' + + + '; + + foreach ($context['topics'] as $topic) + { + // Do we want to separate the sticky and lock status out? + if (!empty($settings['separate_sticky_lock']) && strpos($topic['class'], 'sticky') !== false) + $topic['class'] = substr($topic['class'], 0, strrpos($topic['class'], '_sticky')); + if (!empty($settings['separate_sticky_lock']) && strpos($topic['class'], 'locked') !== false) + $topic['class'] = substr($topic['class'], 0, strrpos($topic['class'], '_locked')); + + // Is this topic pending approval, or does it have any posts pending approval? + if ($context['can_approve_posts'] && $topic['unapproved_posts']) + $color_class = !$topic['approved'] ? 'approvetbg' : 'approvebg'; + // Sticky topics should get a different color, too. + elseif ($topic['is_sticky'] && !empty($settings['separate_sticky_lock'])) + $color_class = 'windowbg3'; + // Last, but not least: regular topics. + else + $color_class = 'windowbg'; + + // Some columns require a different shade of the color class. + $alternate_class = 'windowbg2'; + + echo ' + + + + + + + + '; + + // Show the quick moderation options? + if (!empty($context['can_quick_mod'])) + { + echo ' + '; + } + echo ' + '; + } + + if (!empty($context['can_quick_mod']) && $options['display_quick_mod'] == 1 && !empty($context['topics'])) + { + echo ' + + + '; + } + + echo ' + +
     ', $txt['subject'], $context['sort_by'] == 'subject' ? ' ' : '', '', $txt['started_by'], $context['sort_by'] == 'starter' ? ' ' : '', '', $txt['replies'], $context['sort_by'] == 'replies' ? ' ' : '', '', $txt['views'], $context['sort_by'] == 'views' ? ' ' : '', '', $txt['last_post'], $context['sort_by'] == 'last_post' ? ' ' : '', ' + +  
    '; + if ($settings['display_who_viewing'] == 1) + echo count($context['view_members']), ' ', count($context['view_members']) == 1 ? $txt['who_member'] : $txt['members']; + else + echo empty($context['view_members_list']) ? '0 ' . $txt['members'] : implode(', ', $context['view_members_list']) . ((empty($context['view_num_hidden']) or $context['can_moderate_forum']) ? '' : ' (+ ' . $context['view_num_hidden'] . ' ' . $txt['hidden'] . ')'); + echo $txt['who_and'], $context['view_num_guests'], ' ', $context['view_num_guests'] == 1 ? $txt['guest'] : $txt['guests'], $txt['who_viewing_board'], ' +
    + ! ', $context['unapproved_posts_message'], ' +
    ', $txt['msg_alert_none'], '
    + + + + '; + + if (!empty($settings['separate_sticky_lock'])) + echo ' + ', $topic['is_locked'] ? '' : '', ' + ', $topic['is_sticky'] ? '' : ''; + + echo ' + ', $topic['is_sticky'] ? '' : '', '', $topic['first_post']['link'], (!$context['can_approve_posts'] && !$topic['approved'] ? ' (' . $txt['awaiting_approval'] . ')' : ''), '', $topic['is_sticky'] ? '' : ''; + + // Is this topic new? (assuming they are logged in!) + if ($topic['new'] && $context['user']['is_logged']) + echo ' + ', $txt['new'], ''; + + echo ' + ', $topic['pages'], ' + + ', $topic['first_post']['member']['link'], ' + + ', $topic['replies'], ' + + ', $topic['views'], ' + + ', $txt['last_post'], ' + + ', $topic['last_post']['time'], '
    + ', $txt['by'], ' ', $topic['last_post']['member']['link'], ' +
    +
    '; + if ($options['display_quick_mod'] == 1) + echo ' + '; + else + { + // Check permissions on each and show only the ones they are allowed to use. + if ($topic['quick_mod']['remove']) + echo '', $txt['remove_topic'], ''; + + if ($topic['quick_mod']['lock']) + echo '', $txt['set_lock'], ''; + + if ($topic['quick_mod']['lock'] || $topic['quick_mod']['remove']) + echo '
    '; + + if ($topic['quick_mod']['sticky']) + echo '', $txt['set_sticky'], ''; + + if ($topic['quick_mod']['move']) + echo '', $txt['move_topic'], ''; + } + echo ' +
    + '; + + // Show a list of boards they can move the topic to. + if ($context['can_move']) + { + echo ' + '; + } + + echo ' + +
    +
    + '; + + // Finish off the form - again. + if (!empty($context['can_quick_mod']) && $options['display_quick_mod'] > 0 && !empty($context['topics'])) + echo ' + +
    '; + + echo ' +
    + ', template_button_strip($normal_buttons, 'top'), ' +
    ' . $txt['pages'] . ': ', $context['page_index'], !empty($modSettings['topbottomEnable']) ? $context['menu_separator'] . '  ' . $txt['go_up'] . '' : '', '
    +
    '; + } + + // Show breadcrumbs at the bottom too. + echo ' +
    ', theme_linktree(), '
    '; + + echo ' +
    +
    +

     

    '; + + if (!$context['no_topic_listing']) + echo ' +
    +
      + ', !empty($modSettings['enableParticipation']) && $context['user']['is_logged'] ? ' +
    • ' . $txt['participation_caption'] . '
    • ' : '', ' +
    • ' . $txt['normal_topic'] . '
    • +
    • ' . sprintf($txt['hot_topics'], $modSettings['hotTopicPosts']) . '
    • +
    • ' . sprintf($txt['very_hot_topics'], $modSettings['hotTopicVeryPosts']) . '
    • +
    +
    +
    +
      +
    • ' . $txt['locked_topic'] . '
    • ' . ($modSettings['enableStickyTopics'] == '1' ? ' +
    • ' . $txt['sticky_topic'] . '
    • ' : '') . ($modSettings['pollMode'] == '1' ? ' +
    • ' . $txt['poll'] : '') . '
    • +
    +
    '; + + echo ' + +
    +
    '; + + // Javascript for inline editing. + echo ' + +'; +} + +function theme_show_buttons() +{ + global $context, $settings, $options, $txt, $scripturl; + + $buttonArray = array(); + + // If they are logged in, and the mark read buttons are enabled.. + if ($context['user']['is_logged'] && $settings['show_mark_read']) + $buttonArray[] = '' . $txt['mark_read_short'] . ''; + + // If the user has permission to show the notification button... ask them if they're sure, though. + if ($context['can_mark_notify']) + $buttonArray[] = '' . $txt[$context['is_marked_notify'] ? 'unnotify' : 'notify'] . ''; + + // Are they allowed to post new topics? + if ($context['can_post_new']) + $buttonArray[] = '' . $txt['new_topic'] . ''; + + // How about new polls, can the user post those? + if ($context['can_post_poll']) + $buttonArray[] = '' . $txt['new_poll'] . ''; + + // Right to left menu should be in reverse order. + if ($context['right_to_left']) + $buttonArray = array_reverse($buttonArray, true); + + return implode('  |  ', $buttonArray); +} + +?> \ No newline at end of file diff --git a/Themes/core/PersonalMessage.template.php b/Themes/core/PersonalMessage.template.php new file mode 100644 index 0000000..83cf05c --- /dev/null +++ b/Themes/core/PersonalMessage.template.php @@ -0,0 +1,1763 @@ +'; + + // Show the capacity bar, if available. + if (!empty($context['limit_bar'])) + { + echo ' + + + + + + +
    ', $txt['pm_capacity'], ': +
    +
    +
    +
    90 ? ' class="alert"' : '', '> + ', $context['limit_bar']['text'], ' +
    '; + } + + // Message sent? Show a small indication. + if (isset($context['pm_sent'])) + echo ' +
    + ', $txt['pm_sent'], ' +
    '; +} + +// Just the end of the index bar, nothing special. +function template_pm_below() +{ + global $context, $settings, $options; + + echo ' + '; +} + +function template_folder() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + // The every helpful javascript! + echo ' + '; + + echo ' +
    '; + + // If we are not in single display mode show the subjects on the top! + if ($context['display_mode'] != 1) + { + template_subject_list(); + echo '
    '; + } + + // Got some messages to display? + if ($context['get_pmessage']('message', true)) + { + // Show a few buttons if we are in conversation mode and outputting the first message. + if ($context['display_mode'] == 2) + { + // Build the normal button array. + $conversation_buttons = array( + 'reply' => array('text' => 'reply_to_all', 'image' => 'reply.gif', 'lang' => true, 'url' => $scripturl . '?action=pm;sa=send;f=' . $context['folder'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';pmsg=' . $context['current_pm'] . ';u=all'), + 'delete' => array('text' => 'delete_conversation', 'image' => 'delete.gif', 'lang' => true, 'url' => $scripturl . '?action=pm;sa=pmactions;pm_actions[' . $context['current_pm'] . ']=delete;conversation;f=' . $context['folder'] . ';start=' . $context['start'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';' . $context['session_var'] . '=' . $context['session_id'], 'custom' => 'onclick="return confirm(\'' . addslashes($txt['remove_message']) . '?\');"'), + ); + + // Show the conversation buttons. + echo ' +
    '; + + template_button_strip($conversation_buttons, 'right'); + + echo ' +
    '; + } + + echo ' +
    '; + + // Show the helpful titlebar - generally. + if ($context['display_mode'] != 1) + echo ' +
    +

    + ', $txt['author'], ' + ', $txt[$context['display_mode'] == 0 ? 'messages' : 'conversation'], ' +

    +
    '; + + // Cache some handy buttons. + $quote_button = create_button('quote.gif', 'reply_quote', 'quote', 'align="middle"'); + $reply_button = create_button('im_reply.gif', 'reply', 'reply', 'align="middle"'); + $reply_all_button = create_button('im_reply_all.gif', 'reply_to_all', 'reply_to_all', 'align="middle"'); + $forward_button = create_button('quote.gif', 'reply_quote', 'reply_quote', 'align="middle"'); + $delete_button = create_button('delete.gif', 'remove_message', 'remove', 'align="middle"'); + + while ($message = $context['get_pmessage']('message')) + { + $is_first_post = !isset($is_first_post) ? true : false; + + // Show information about the poster of this message. + echo ' +
    +
    +
    +

    ', $message['member']['link'], '

    +
      '; + + // Show the member's custom title, if they have one. + if (isset($message['member']['title']) && $message['member']['title'] != '') + echo ' +
    • ', $message['member']['title'], '
    • '; + + // Show the member's primary group (like 'Administrator') if they have one. + if (isset($message['member']['group']) && $message['member']['group'] != '') + echo ' +
    • ', $message['member']['group'], '
    • '; + + // Don't show these things for guests. + if (!$message['member']['is_guest']) + { + // Show the post group if and only if they have no other group or the option is on, and they are in a post group. + if ((empty($settings['hide_post_group']) || $message['member']['group'] == '') && $message['member']['post_group'] != '') + echo ' +
    • ', $message['member']['post_group'], '
    • '; + echo ' +
    • ', $message['member']['group_stars'], '
    • '; + + // Is karma display enabled? Total or +/-? + if ($modSettings['karmaMode'] == '1') + echo ' +
    • ', $modSettings['karmaLabel'], ' ', $message['member']['karma']['good'] - $message['member']['karma']['bad'], '
    • '; + elseif ($modSettings['karmaMode'] == '2') + echo ' +
    • ', $modSettings['karmaLabel'], ' +', $message['member']['karma']['good'], '/-', $message['member']['karma']['bad'], '
    • '; + + // Is this user allowed to modify this member's karma? + if ($message['member']['karma']['allow']) + echo ' +
    • + ', $modSettings['karmaApplaudLabel'], ' + ', $modSettings['karmaSmiteLabel'], ' +
    • '; + + // Show online and offline buttons? + if (!empty($modSettings['onlineEnable'])) + echo ' +
    • ', $context['can_send_pm'] ? '' : '', $settings['use_image_buttons'] ? '' . $message['member']['online']['text'] . '' : $message['member']['online']['text'], $context['can_send_pm'] ? '' : '', $settings['use_image_buttons'] ? ' ' . $message['member']['online']['text'] . '' : '', '
    • '; + + // Show the member's gender icon? + if (!empty($settings['show_gender']) && $message['member']['gender']['image'] != '' && !isset($context['disabled_fields']['gender'])) + echo ' +
    • ', $txt['gender'], ': ', $message['member']['gender']['image'], '
    • '; + + // Show how many posts they have made. + if (!isset($context['disabled_fields']['posts'])) + echo ' +
    • ', $txt['member_postcount'], ': ', $message['member']['posts'], '
    • '; + + // Any custom fields for standard placement? + if (!empty($message['member']['custom_fields'])) + { + foreach ($message['member']['custom_fields'] as $custom) + if (empty($custom['placement']) && !empty($custom['value'])) + echo ' +
    • ', $custom['title'], ': ', $custom['value'], '
    • '; + } + + // Show avatars, images, etc.? + if (!empty($settings['show_user_images']) && empty($options['show_no_avatars']) && !empty($message['member']['avatar']['image'])) + echo ' +
    • ', $message['member']['avatar']['image'], '
    • '; + + // Show their personal text? + if (!empty($settings['show_blurb']) && $message['member']['blurb'] != '') + echo ' +
    • ', $message['member']['blurb'], '
    • '; + + // Any custom fields to show as icons? + if (!empty($message['member']['custom_fields'])) + { + $shown = false; + foreach ($message['member']['custom_fields'] as $custom) + { + if ($custom['placement'] != 1 || empty($custom['value'])) + continue; + if (empty($shown)) + { + $shown = true; + echo ' +
    • +
        '; + } + echo ' +
      • ', $custom['value'], '
      • '; + } + if ($shown) + echo ' +
      +
    • '; + } + + // This shows the popular messaging icons. + if ($message['member']['has_messenger'] && $message['member']['can_view_profile']) + echo ' +
    • +
        + ', !isset($context['disabled_fields']['icq']) && !empty($message['member']['icq']['link']) ? '
      • ' . $message['member']['icq']['link'] . '
      • ' : '', ' + ', !isset($context['disabled_fields']['msn']) && !empty($message['member']['msn']['link']) ? '
      • ' . $message['member']['msn']['link'] . '
      • ' : '', ' + ', !isset($context['disabled_fields']['aim']) && !empty($message['member']['aim']['link']) ? '
      • ' . $message['member']['aim']['link'] . '
      • ' : '', ' + ', !isset($context['disabled_fields']['yim']) && !empty($message['member']['yim']['link']) ? '
      • ' . $message['member']['yim']['link'] . '
      • ' : '', ' +
      +
    • '; + + // Show the profile, website, email address, and personal message buttons. + if ($settings['show_profile_buttons']) + { + echo ' +
    • + +
    • '; + } + + // Are we showing the warning status? + if ($message['member']['can_see_warning']) + echo ' +
    • ', $context['can_issue_warning'] ? '' : '', '', $txt['user_warn_' . $message['member']['warning_status']], '', $context['can_issue_warning'] ? '' : '', '', $txt['warn_' . $message['member']['warning_status']], '
    • '; + } + + // Done with the information about the poster... on to the post itself. + echo ' +
    +
    +
    +
    +
    +
    + ', $message['subject'], ' +
    '; + + // Show who the message was sent to. + echo ' +
    + « ', $txt['sent_to'], ': '; + + // People it was sent directly to.... + if (!empty($message['recipients']['to'])) + echo implode(', ', $message['recipients']['to']); + // Otherwise, we're just going to say "some people"... + elseif ($context['folder'] != 'sent') + echo '(', $txt['pm_undisclosed_recipients'], ')'; + + echo ' + ', $txt['on'], ': ', $message['time'], ' » +
    '; + + // If we're in the sent items, show who it was sent to besides the "To:" people. + if (!empty($message['recipients']['bcc'])) + echo ' +
    « ', $txt['pm_bcc'], ': ', implode(', ', $message['recipients']['bcc']), ' »
    '; + + if (!empty($message['is_replied_to'])) + echo ' +
    « ', $txt['pm_is_replied_to'], ' »
    '; + + echo ' +
    +
      '; + + // Show reply buttons if you have the permission to send PMs. + if ($context['can_send_pm']) + { + // You can't really reply if the member is gone. + if (!$message['member']['is_guest']) + { + // Were than more than one recipient you can reply to? (Only shown when not in conversation mode.) + if ($message['number_recipients'] > 1 && $context['display_mode'] != 2) + echo ' +
    • ', $reply_all_button, '
    • '; + + echo ' +
    • ', $reply_button, '
    • +
    • ', $quote_button, '
    • '; + } + // This is for "forwarding" - even if the member is gone. + else + echo ' +
    • ', $forward_button, '
    • '; + } + echo ' +
    • ', $delete_button, '
    • '; + + if (empty($context['display_mode'])) + echo ' +
    • '; + + echo ' +
    +
    +
    +
    + ', $message['body'], ' +
    '; + + if (!empty($modSettings['enableReportPM']) && $context['folder'] != 'sent') + echo ' + '; + + // Are there any custom profile fields for above the signature? + if (!empty($message['member']['custom_fields'])) + { + $shown = false; + foreach ($message['member']['custom_fields'] as $custom) + { + if ($custom['placement'] != 2 || empty($custom['value'])) + continue; + if (!$shown) + { + $shown = true; + echo ' +
    +
      ', $message['member']['signature'], '
    '; + + // Add an extra line at the bottom if we have labels enabled. + if ($context['folder'] != 'sent' && !empty($context['currently_using_labels']) && $context['display_mode'] != 2) + { + echo ' +
    '; + + // Add the label drop down box. + if (!empty($context['currently_using_labels'])) + { + echo ' + + '; + } + + echo ' +
    '; + } + + echo ' +
    +
    +
    '; + } + + echo ' +
    '; + + if (empty($context['display_mode'])) + echo ' +
    +
    ', $txt['pages'], ': ', $context['page_index'], '
    +
    +
    '; + + // Show a few buttons if we are in conversation mode and outputting the first message. + elseif ($context['display_mode'] == 2 && isset($conversation_buttons)) + template_button_strip($conversation_buttons); + + echo ' +
    '; + } + + // Individual messages = buttom list! + if ($context['display_mode'] == 1) + { + template_subject_list(); + echo '
    '; + } + + echo ' + +
    '; +} + +// Just list all the personal message subjects - to make templates easier. +function template_subject_list() +{ + global $context, $options, $settings, $modSettings, $txt, $scripturl; + + echo ' + + + + + + + + '; + + if (!$context['show_delete']) + echo ' + + + '; + + $next_alternate = 0; + while ($message = $context['get_pmessage']('subject')) + { + echo ' + + + + + + + '; + $next_alternate = !$next_alternate; + } + + echo ' +
    ', $txt['pm_change_view'], '', $txt['date'], $context['sort_by'] == 'date' ? ' ' : '', '', $txt['subject'], $context['sort_by'] == 'subject' ? ' ' : '', '', ($context['from_or_to'] == 'from' ? $txt['from'] : $txt['to']), $context['sort_by'] == 'name' ? ' ' : '', '
    ', $txt['msg_alert_none'], '
    + + ', $message['is_replied_to'] ? '' . $txt['pm_replied'] . '' : '' . $txt['pm_read'] . '', '', $message['time'], '', ($context['display_mode'] != 0 && $context['current_pm'] == $message['id'] ? '*' : ''), '', $message['subject'], '', $message['is_unread'] ? ' ' . $txt['new'] . '' : '', '', ($context['from_or_to'] == 'from' ? $message['member']['link'] : (empty($message['recipients']['to']) ? '' : implode(', ', $message['recipients']['to']))), '
    +
    +
    ', $txt['pages'], ': ', $context['page_index'], '
    +
     '; + + if ($context['show_delete']) + { + if (!empty($context['currently_using_labels']) && $context['folder'] != 'sent') + { + echo ' + + '; + } + + echo ' + '; + } + + echo ' +
    +
    '; +} + +function template_search() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + echo ' + +
    +
    +

    ', $txt['pm_search_title'], '

    +
    '; + + if (!empty($context['search_errors'])) + { + echo ' +
    + ', implode('
    ', $context['search_errors']['messages']), ' +
    '; + } + + if ($context['simple_search']) + { + echo ' + '; + } + + // Advanced search! + else + { + echo ' + '; + + // Do we have some labels setup? If so offer to search by them! + if ($context['currently_using_labels']) + { + echo ' +
    + +
    + +
      '; + + foreach ($context['search_labels'] as $label) + echo ' +
    • + +
    • '; + + echo ' +
    +

    + +

    +
    + +
    '; + } + + echo ' +
    + +
    '; + } + + echo ' + +
    '; +} + +function template_search_results() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + // This splits broadly into two types of template... complete results first. + if (!empty($context['search_params']['show_complete'])) + { + echo ' + + + + + + + +
    ', $txt['pm_search_results'], '
    ', $txt['pages'], ': ', $context['page_index'], '
    '; + } + else + { + echo ' + + + + + + + + + + + + '; + } + + $alternate = true; + // Print each message out... + foreach ($context['personal_messages'] as $message) + { + // We showing it all? + if (!empty($context['search_params']['show_complete'])) + { + // !!! This still needs to be made pretty. + echo ' +
    +
    ', $txt['pm_search_results'], '
    ', $txt['pages'], ': ', $context['page_index'], '
    ', $txt['date'], '', $txt['subject'], '', $txt['from'], '
    + + + + + + + + + + + + +
    +
    + ', $message['counter'], '  ', $message['subject'], ' +
    +
    + ', $txt['search_on'], ': ', $message['time'], ' +
    +
    ', $txt['from'], ': ', $message['member']['link'], ', ', $txt['to'], ': '; + + // Show the recipients. + // !!! This doesn't deal with the sent item searching quite right for bcc. + if (!empty($message['recipients']['to'])) + echo implode(', ', $message['recipients']['to']); + // Otherwise, we're just going to say "some people"... + elseif ($context['folder'] != 'sent') + echo '(', $txt['pm_undisclosed_recipients'], ')'; + + echo ' +
    ', $message['body'], '
    '; + + if ($context['can_send_pm']) + { + $quote_button = create_button('quote.gif', 'reply_quote', 'reply_quote', 'align="middle"'); + $reply_button = create_button('im_reply.gif', 'reply', 'reply', 'align="middle"'); + + // You can only reply if they are not a guest... + if (!$message['member']['is_guest']) + echo ' + ', $quote_button , '', $context['menu_separator'], ' + ', $reply_button , ' ', $context['menu_separator']; + // This is for "forwarding" - even if the member is gone. + else + echo ' + ', $quote_button , '', $context['menu_separator']; + } + + echo ' +
    '; + } + // Otherwise just a simple list! + else + { + // !!! No context at all of the search? + echo ' + + ', $message['time'], ' + ', $message['link'], ' + ', $message['member']['link'], ' + '; + } + + $alternate = !$alternate; + } + + // Finish off the page... + if (!empty($context['search_params']['show_complete'])) + { + // No results? + if (empty($context['personal_messages'])) + echo ' + + + + +
    ', $txt['pm_search_none_found'], '
    '; + else + echo ' +
    '; + + echo ' + + + + +
    ', $txt['pages'], ': ', $context['page_index'], '
    '; + } + else + { + if (empty($context['personal_messages'])) + echo ' + + ', $txt['pm_search_none_found'], ' + '; + + echo ' + + ', $txt['pages'], ': ', $context['page_index'], ' + + '; + } +} + +function template_send() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + // Show which messages were sent successfully and which failed. + if (!empty($context['send_log'])) + { + echo ' +
    +

    ', $txt['pm_send_report'], '

    +
    +
    + +
    '; + if (!empty($context['send_log']['sent'])) + foreach ($context['send_log']['sent'] as $log_entry) + echo '', $log_entry, '
    '; + if (!empty($context['send_log']['failed'])) + foreach ($context['send_log']['failed'] as $log_entry) + echo '', $log_entry, '
    '; + echo ' +
    + +
    +
    '; + } + + // Show the preview of the personal message. + if (isset($context['preview_message'])) + echo ' +
    +

    ', $context['preview_subject'], '

    +
    +
    + +
    + ', $context['preview_message'], ' +
    + +
    +
    '; + + // Main message editing box. + echo ' +
    +

    + ', $txt['new_message'], ' ', $txt['new_message'], ' +

    +
    '; + + echo ' +
    +
    + +
    '; + + // If there were errors for sending the PM, show them. + if (!empty($context['post_error']['messages'])) + { + echo ' +
    + ', $txt['error_while_submitting'], ' +
      '; + + foreach ($context['post_error']['messages'] as $error) + echo ' +
    • ', $error, '
    • '; + + echo ' +
    +
    '; + } + + echo ' +
    '; + + // To and bcc. Include a button to search for members. + echo ' +
    + ', $txt['pm_to'], ': +
    '; + + // Autosuggest will be added by the JavaScript later on. + echo ' +
    + '; + + // A link to add BCC, only visible with JavaScript enabled. + echo ' + '; + + // A div that'll contain the items found by the autosuggest. + echo ' +
    '; + + echo ' +
    '; + + // This BCC row will be hidden by default if JavaScript is enabled. + echo ' +
    + ', $txt['pm_bcc'], ': +
    +
    + +
    +
    '; + + // The subject of the PM. + echo ' +
    + ', $txt['subject'], ': +
    +
    + +
    +
    '; + + // Showing BBC? + if ($context['show_bbc']) + { + echo ' +
    '; + } + + // What about smileys? + if (!empty($context['smileys']['postform']) || !empty($context['smileys']['popup'])) + echo ' +
    '; + + // Show BBC buttons, smileys and textbox. + echo ' + ', template_control_richedit($context['post_box_name'], 'smileyBox_message', 'bbcBox_message'); + + // Require an image to be typed to save spamming? + if ($context['require_verification']) + { + echo ' +
    + ', $txt['pm_visual_verification_label'], ': + ', template_control_verification($context['visual_verification_id'], 'all'), ' +
    '; + } + + // Send, Preview, spellcheck buttons. + echo ' +

    +

    + ', $context['browser']['is_firefox'] ? $txt['shortcuts_firefox'] : $txt['shortcuts'], ' +

    +

    + ', template_control_richedit_buttons($context['post_box_name']), ' +

    + + + + + + +
    + +
    +
    '; + + // Show the message you're replying to. + if ($context['reply']) + echo ' +
    +
    +
    +

    ', $txt['subject'], ': ', $context['quoted_message']['subject'], '

    +
    +
    +

    + ', $txt['from'], ': ', $context['quoted_message']['member']['name'], ' + ', $txt['on'], ': ', $context['quoted_message']['time'], ' +

    +
    +
    + +
    + ', $context['quoted_message']['body'], ' +
    + +
    '; + + echo ' + + + '; +} + +// This template asks the user whether they wish to empty out their folder/messages. +function template_ask_delete() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + echo ' +
    +

    ', ($context['delete_all'] ? $txt['delete_message'] : $txt['delete_all']), '

    +
    +
    + +
    +

    ', $txt['delete_all_confirm'], '


    + ', $txt['yes'], ' - ', $txt['no'], ' +
    + +
    '; +} + +// This template asks the user what messages they want to prune. +function template_prune() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    +

    ', $txt['pm_prune'], '

    +
    +
    + +
    +

    ', $txt['pm_prune_desc1'], ' ', $txt['pm_prune_desc2'], '

    +
    + +
    +
    + +
    + +
    '; +} + +// Here we allow the user to setup labels, remove labels and change rules for labels (i.e, do quite a bit) +function template_labels() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    +

    ', $txt['pm_manage_labels'], '

    +
    +
    + ', $txt['pm_labels_desc'], ' +
    + + + + + + + '; + if (count($context['labels']) < 2) + echo ' + + + '; + else + { + $alternate = true; + foreach ($context['labels'] as $label) + { + if ($label['id'] == -1) + continue; + + echo ' + + + + '; + + $alternate = !$alternate; + } + } + echo ' + +
    +
    + ', $txt['pm_label_name'], ' +
    ', $txt['pm_labels_no_exist'], '
    + +
    '; + + if (!count($context['labels']) < 2) + echo ' +
    + + +
    '; + + echo ' + +
    +
    +
    +

    ', $txt['pm_label_add_new'], '

    +
    +
    + +
    +
    +
    + : +
    +
    + +
    +
    +
    + +
    +
    + +
    + +
    '; +} + +// Template for reporting a personal message. +function template_report_message() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    + +
    +

    ', $txt['pm_report_title'], '

    +
    +
    + ', $txt['pm_report_desc'], ' +
    +
    + +
    +
    '; + + // If there is more than one admin on the forum, allow the user to choose the one they want to direct to. + // !!! Why? + if ($context['admin_count'] > 1) + { + echo ' +
    + ', $txt['pm_report_admins'], ': +
    +
    + +
    '; + } + + echo ' +
    + ', $txt['pm_report_reason'], ': +
    +
    + +
    +
    + +
    + +
    + +
    '; +} + +// Little template just to say "Yep, it's been submitted" +function template_report_message_complete() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +

    ', $txt['pm_report_title'], '

    +
    +
    + +
    +

    ', $txt['pm_report_done'], '

    + ', $txt['pm_report_return'], ' +
    + +
    '; +} + +// Manage rules. +function template_rules() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +

    ', $txt['pm_manage_rules'], '

    +
    +
    + ', $txt['pm_manage_rules_desc'], ' +
    + + + + + + + + '; + + if (empty($context['rules'])) + echo ' + + + '; + + $alternate = false; + foreach ($context['rules'] as $rule) + { + echo ' + + + + '; + $alternate = !$alternate; + } + + echo ' + +
    + ', $txt['pm_rule_title'], ' + '; + + if (!empty($context['rules'])) + echo ' + '; + + echo ' +
    + ', $txt['pm_rules_none'], ' +
    + ', $rule['name'], ' + + +
    +
    + [', $txt['pm_add_rule'], ']'; + + if (!empty($context['rules'])) + echo ' + [', $txt['pm_apply_rules'], ']'; + + if (!empty($context['rules'])) + echo ' + + '; + + echo ' +
    +
    '; + +} + +// Template for adding/editing a rule. +function template_add_rule() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' + '; + + echo ' +
    +
    +

    ', $context['rid'] == 0 ? $txt['pm_add_rule'] : $txt['pm_edit_rule'], '

    +
    +
    + +
    +
    +
    + ', $txt['pm_rule_name'], ':
    + ', $txt['pm_rule_name_desc'], ' +
    +
    + +
    +
    +
    + ', $txt['pm_rule_criteria'], ''; + + // Add a dummy criteria to allow expansion for none js users. + $context['rule']['criteria'][] = array('t' => '', 'v' => ''); + + // For each criteria print it out. + $isFirst = true; + foreach ($context['rule']['criteria'] as $k => $criteria) + { + if (!$isFirst && $criteria['t'] == '') + echo '
    '; + else + echo '
    '; + + echo ' + + + + + + + '; + + // If this is the dummy we add a means to hide for non js users. + if ($isFirst) + $isFirst = false; + elseif ($criteria['t'] == '') + echo '
    '; + } + + echo ' +
    + +

    + ', $txt['pm_rule_logic'], ': + +
    +
    + ', $txt['pm_rule_actions'], ''; + + // As with criteria - add a dummy action for "expansion". + $context['rule']['actions'][] = array('t' => '', 'v' => ''); + + // Print each action. + $isFirst = true; + foreach ($context['rule']['actions'] as $k => $action) + { + if (!$isFirst && $action['t'] == '') + echo '
    '; + else + echo '
    '; + + echo ' + + + + '; + + if ($isFirst) + $isFirst = false; + elseif ($action['t'] == '') + echo ' +
    '; + } + + echo ' +
    + +
    +
    + +
    +
    +

    ', $txt['pm_rule_description'], '

    +
    +
    +
    ', $txt['pm_rule_js_disabled'], '
    +
    +
    + + +
    +
    '; + + // Now setup all the bits! + echo ' + '; +} + +?> \ No newline at end of file diff --git a/Themes/core/Recent.template.php b/Themes/core/Recent.template.php new file mode 100644 index 0000000..3142285 --- /dev/null +++ b/Themes/core/Recent.template.php @@ -0,0 +1,423 @@ + +
    +
    ', $txt['pages'], ': ', $context['page_index'], '
    +
    '; + + foreach ($context['posts'] as $post) + { + // This is far from ideal, but oh well - create buttons for the post. + $button_set = array(); + + if ($post['can_delete']) + $button_set['delete'] = array('text' => 'remove', 'image' => 'delete.gif', 'lang' => true, 'custom' => 'onclick="return confirm(\'' . $txt['remove_message'] . '?\');"', 'url' => $scripturl . '?action=deletemsg;msg=' . $post['id'] . ';topic=' . $post['topic'] . ';recent;' . $context['session_var'] . '=' . $context['session_id']); + if ($post['can_reply']) + $button_set['reply'] = array('text' => 'reply', 'image' => 'reply_sm.gif', 'lang' => true, 'url' => $scripturl . '?action=post;topic=' . $post['topic'] . '.' . $post['start']); + if ($post['can_quote']) + $button_set['quote'] = array('text' => 'reply_quote', 'image' => 'quote.gif', 'lang' => true, 'url' => $scripturl . '?action=post;topic=' . $post['topic'] . '.' . $post['start'] . ';quote=' . $post['id']); + if ($post['can_mark_notify']) + $button_set['notify'] = array('text' => 'notify_replies', 'image' => 'notify_sm.gif', 'lang' => true, 'url' => $scripturl . '?action=notify;topic=' . $post['topic'] . '.' . $post['start']); + + echo ' + + + + + + + + + + '; + + // Are we using tabs? + if (!empty($settings['use_tabs'])) + { + echo ' +
    +
     ', $post['counter'], ' 
    +
     ', $post['category']['link'], ' / ', $post['board']['link'], ' / ', $post['link'], '
    +
     ', $txt['on'], ': ', $post['time'], ' 
    +
    + ', $txt['started_by'], ' ' . $post['first_poster']['link'] . ' - ' . $txt['last_post'] . ' ' . $txt['by'] . ' ' . $post['poster']['link'] . ' +
    +
    ' . $post['message'] . '
    +
    '; + + if (!empty($button_set)) + echo ' +
    + ', template_button_strip($button_set, 'top'), ' +
    '; + } + else + { + if (!empty($button_set)) + echo ' + + +
    + ', template_button_strip($button_set, 'top'), ' +
    + + '; + + echo ' + '; + } + + echo ' +
    '; + } + + echo ' +
    +
    ', $txt['pages'], ': ', $context['page_index'], '
    +
    + '; +} + +function template_unread() +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings; + + $showCheckboxes = !empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1 && $settings['show_mark_read']; + + if ($showCheckboxes) + echo ' +
    +
    + + + '; + + if ($settings['show_mark_read']) + { + // Generate the button strip. + $mark_read = array( + 'markread' => array('text' => !empty($context['no_board_limits']) ? 'mark_as_read' : 'mark_read_short', 'image' => 'markread.gif', 'lang' => true, 'url' => $scripturl . '?action=markasread;sa=' . (!empty($context['no_board_limits']) ? 'all' : 'board' . $context['querystring_board_limits']) . ';' . $context['session_var'] . '=' . $context['session_id']), + ); + + if ($showCheckboxes) + $mark_read['markselectread'] = array( + 'text' => 'quick_mod_markread', + 'image' => 'markselectedread.gif', + 'lang' => true, + 'url' => 'javascript:document.quickModForm.submit();', + ); + } + + echo ' +
    +
    ', $txt['pages'], ': ', $context['page_index'], '
    '; + + if (!empty($mark_read) && !empty($settings['use_tabs'])) + template_button_strip($mark_read, 'bottom'); + + echo ' +
    '; + + echo ' + + +
    + + '; + if (!empty($context['topics'])) + { + echo ' + + '; + if ($showCheckboxes) + echo ' + '; + } + else + echo ' + '; + echo ' + '; + + foreach ($context['topics'] as $topic) + { + // Do we want to separate the sticky and lock status out? + if (!empty($settings['separate_sticky_lock']) && strpos($topic['class'], 'sticky') !== false) + $topic['class'] = substr($topic['class'], 0, strrpos($topic['class'], '_sticky')); + if (!empty($settings['separate_sticky_lock']) && strpos($topic['class'], 'locked') !== false) + $topic['class'] = substr($topic['class'], 0, strrpos($topic['class'], '_locked')); + + echo ' + + + + + + '; + if ($showCheckboxes) + echo ' + '; + + echo ' + '; + } + + if (!empty($context['topics']) && !$context['showing_all_topics']) + echo ' + + + '; + + if (empty($settings['use_tabs']) && !empty($mark_read)) + echo ' + + + '; + + echo ' +
      + ', $txt['subject'], $context['sort_by'] == 'subject' ? ' ' : '', ' + + ', $txt['started_by'], $context['sort_by'] == 'starter' ? ' ' : '', ' + + ', $txt['replies'], $context['sort_by'] == 'replies' ? ' ' : '', ' + + ', $txt['views'], $context['sort_by'] == 'views' ? ' ' : '', ' + + ', $txt['last_post'], $context['sort_by'] == 'last_post' ? ' ' : '', ' + + + ', $context['showing_all_topics'] ? $txt['msg_alert_none'] : $txt['unread_topics_visit_none'], '
    + + + + ', $topic['is_locked'] && !empty($settings['separate_sticky_lock']) ? ' + ' : '', $topic['is_sticky'] && !empty($settings['separate_sticky_lock']) ? ' + ' : '', $topic['first_post']['link'], ' ', $txt['new'], ' ', $topic['pages'], ' ', $txt['in'], ' ', $topic['board']['link'], ' + ', $topic['first_post']['member']['link'], ' + ', $topic['replies'], ' + ', $topic['views'], ' + ', $txt['last_post'], ' + + ', $topic['last_post']['time'], '
    + ', $txt['by'], ' ', $topic['last_post']['member']['link'], ' +
    +
    + +
    ', $txt['unread_topics_all'], '
    +
    + ', template_button_strip($mark_read, 'top'), ' +
    +
    +
    +
    +
    ', $txt['pages'], ': ', $context['page_index'], '
    '; + + if (!empty($settings['use_tabs']) && !empty($mark_read)) + template_button_strip($mark_read, 'top'); + + echo ' +
    +
    '; + + if ($showCheckboxes) + echo ' +
    '; + + echo ' +
    +
    +
    +
      + ', !empty($modSettings['enableParticipation']) && $context['user']['is_logged'] ? ' +
    • ' . $txt['participation_caption'] . '
    • ' : '', ' +
    • ' . $txt['normal_topic'] . '
    • +
    • ' . sprintf($txt['hot_topics'], $modSettings['hotTopicPosts']) . '
    • +
    • ' . sprintf($txt['very_hot_topics'], $modSettings['hotTopicVeryPosts']) . '
    • +
    +
    +
    +
      +
    • ' . $txt['locked_topic'] . '
    • ' . ($modSettings['enableStickyTopics'] == '1' ? ' +
    • ' . $txt['sticky_topic'] . '
    • ' : '') . ($modSettings['pollMode'] == '1' ? ' +
    • ' . $txt['poll'] : '') . '
    • +
    +
    +
    +
    '; +} + +function template_replies() +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings; + + $showCheckboxes = !empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1 && $settings['show_mark_read']; + + if ($showCheckboxes) + echo ' +
    +
    + + + '; + + if (isset($context['topics_to_mark']) && !empty($settings['show_mark_read'])) + { + // Generate the button strip. + $mark_read = array( + 'markread' => array('text' => 'mark_as_read', 'image' => 'markread.gif', 'lang' => true, 'url' => $scripturl . '?action=markasread;sa=unreadreplies;topics=' . $context['topics_to_mark'] . ';' . $context['session_var'] . '=' . $context['session_id']), + ); + + if ($showCheckboxes) + $mark_read['markselectread'] = array( + 'text' => 'quick_mod_markread', + 'image' => 'markselectedread.gif', + 'lang' => true, + 'url' => 'javascript:document.quickModForm.submit();', + ); + } + if (!empty($settings['use_tabs'])) + { + echo ' +
    +
    ', $txt['pages'], ': ', $context['page_index'], '
    '; + if (!empty($mark_read)) + template_button_strip($mark_read, 'bottom'); + + echo ' +
    '; + } + + echo ' + + +
    + + '; + if (!empty($context['topics'])) + { + echo ' + + + + + + '; + if ($showCheckboxes) + echo ' + '; + } + else + echo ' + '; + echo ' + '; + + foreach ($context['topics'] as $topic) + { + // separate lock and sticky again? + if (!empty($settings['separate_sticky_lock']) && strpos($topic['class'], 'sticky') !== false) + $topic['class'] = substr($topic['class'], 0, strrpos($topic['class'], '_sticky')); + if (!empty($settings['separate_sticky_lock']) && strpos($topic['class'], 'locked') !== false) + $topic['class'] = substr($topic['class'], 0, strrpos($topic['class'], '_locked')); + + echo ' + + + + + + + + '; + if ($showCheckboxes) + echo ' + '; + + echo ' + '; + } + if (empty($settings['use_tabs']) && !empty($mark_read)) + echo ' + + + '; + + echo ' +
     ', $txt['subject'], $context['sort_by'] == 'subject' ? ' ' : '', '', $txt['started_by'], $context['sort_by'] == 'starter' ? ' ' : '', '', $txt['replies'], $context['sort_by'] == 'replies' ? ' ' : '', '', $txt['views'], $context['sort_by'] == 'views' ? ' ' : '', '', $txt['last_post'], $context['sort_by'] == 'last_post' ? ' ' : '', ' + + ' . $txt['msg_alert_none'] . '
    + + + ', $topic['is_locked'] && !empty($settings['separate_sticky_lock']) ? '' : '', ' + ', $topic['is_sticky'] && !empty($settings['separate_sticky_lock']) ? '' : '', ' ', $topic['first_post']['link'], ' ', $txt['new'], ' ', $topic['pages'], ' + ', $txt['in'], ' ', $topic['board']['link'], ' + ', $topic['first_post']['member']['link'], ' + ', $topic['replies'], ' + ', $topic['views'], ' + ', $txt['last_post'], ' + + ', $topic['last_post']['time'], '
    + ', $txt['by'], ' ', $topic['last_post']['member']['link'], ' +
    +
    + +
    +
    + ', template_button_strip($mark_read, 'top'), ' +
    +
    +
    +
    +
    ', $txt['pages'], ': ', $context['page_index'], '
    '; + + if (!empty($settings['use_tabs']) && !empty($mark_read)) + template_button_strip($mark_read, 'top'); + + echo ' +
    +
    '; + + if ($showCheckboxes) + echo ' +
    '; + + echo ' +
    +
    +
    +
      + ', !empty($modSettings['enableParticipation']) && $context['user']['is_logged'] ? ' +
    • ' . $txt['participation_caption'] . '
    • ' : '', ' +
    • ' . $txt['normal_topic'] . '
    • +
    • ' . sprintf($txt['hot_topics'], $modSettings['hotTopicPosts']) . '
    • +
    • ' . sprintf($txt['very_hot_topics'], $modSettings['hotTopicVeryPosts']) . '
    • +
    +
    +
    +
      +
    • ' . $txt['locked_topic'] . '
    • ' . ($modSettings['enableStickyTopics'] == '1' ? ' +
    • ' . $txt['sticky_topic'] . '
    • ' : '') . ($modSettings['pollMode'] == '1' ? ' +
    • ' . $txt['poll'] : '') . '
    • +
    +
    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/core/Settings.template.php b/Themes/core/Settings.template.php new file mode 100644 index 0000000..db97f50 --- /dev/null +++ b/Themes/core/Settings.template.php @@ -0,0 +1,272 @@ + 'show_board_desc', + 'label' => $txt['board_desc_inside'], + 'default' => true, + ), + array( + 'id' => 'show_children', + 'label' => $txt['show_children'], + 'default' => true, + ), + array( + 'id' => 'use_sidebar_menu', + 'label' => $txt['use_sidebar_menu'], + 'default' => true, + ), + array( + 'id' => 'show_no_avatars', + 'label' => $txt['show_no_avatars'], + 'default' => true, + ), + array( + 'id' => 'show_no_signatures', + 'label' => $txt['show_no_signatures'], + 'default' => true, + ), + array( + 'id' => 'show_no_censored', + 'label' => $txt['show_no_censored'], + 'default' => true, + ), + array( + 'id' => 'return_to_post', + 'label' => $txt['return_to_post'], + 'default' => true, + ), + array( + 'id' => 'no_new_reply_warning', + 'label' => $txt['no_new_reply_warning'], + 'default' => true, + ), + array( + 'id' => 'view_newest_first', + 'label' => $txt['recent_posts_at_top'], + 'default' => true, + ), + array( + 'id' => 'view_newest_pm_first', + 'label' => $txt['recent_pms_at_top'], + 'default' => true, + ), + array( + 'id' => 'posts_apply_ignore_list', + 'label' => $txt['posts_apply_ignore_list'], + 'default' => false, + ), + array( + 'id' => 'wysiwyg_default', + 'label' => $txt['wysiwyg_default'], + 'default' => false, + ), + array( + 'id' => 'popup_messages', + 'label' => $txt['popup_messages'], + 'default' => true, + ), + array( + 'id' => 'copy_to_outbox', + 'label' => $txt['copy_to_outbox'], + 'default' => true, + ), + array( + 'id' => 'pm_remove_inbox_label', + 'label' => $txt['pm_remove_inbox_label'], + 'default' => true, + ), + array( + 'id' => 'auto_notify', + 'label' => $txt['auto_notify'], + 'default' => true, + ), + array( + 'id' => 'topics_per_page', + 'label' => $txt['topics_per_page'], + 'options' => array( + 0 => $txt['per_page_default'], + 5 => 5, + 10 => 10, + 25 => 25, + 50 => 50, + ), + 'default' => true, + ), + array( + 'id' => 'messages_per_page', + 'label' => $txt['messages_per_page'], + 'options' => array( + 0 => $txt['per_page_default'], + 5 => 5, + 10 => 10, + 25 => 25, + 50 => 50, + ), + 'default' => true, + ), + array( + 'id' => 'calendar_start_day', + 'label' => $txt['calendar_start_day'], + 'options' => array( + 0 => $txt['days'][0], + 1 => $txt['days'][1], + 6 => $txt['days'][6], + ), + 'default' => true, + ), + array( + 'id' => 'display_quick_reply', + 'label' => $txt['display_quick_reply'], + 'options' => array( + 0 => $txt['display_quick_reply1'], + 1 => $txt['display_quick_reply2'], + 2 => $txt['display_quick_reply3'] + ), + 'default' => true, + ), + array( + 'id' => 'display_quick_mod', + 'label' => $txt['display_quick_mod'], + 'options' => array( + 0 => $txt['display_quick_mod_none'], + 1 => $txt['display_quick_mod_check'], + 2 => $txt['display_quick_mod_image'], + ), + 'default' => true, + ), + ); +} + +function template_settings() +{ + global $context, $settings, $options, $scripturl, $txt; + + $context['theme_settings'] = array( + array( + 'id' => 'header_logo_url', + 'label' => $txt['header_logo_url'], + 'description' => $txt['header_logo_url_desc'], + 'type' => 'text', + ), + array( + 'id' => 'smiley_sets_default', + 'label' => $txt['smileys_default_set_for_theme'], + 'options' => $context['smiley_sets'], + 'type' => 'text', + ), + array( + 'id' => 'forum_width', + 'label' => $txt['forum_width'], + 'description' => $txt['forum_width_desc'], + 'type' => 'text', + 'size' => 8, + ), + '', + array( + 'id' => 'show_mark_read', + 'label' => $txt['enable_mark_as_read'], + ), + array( + 'id' => 'allow_no_censored', + 'label' => $txt['allow_no_censored'], + ), + array( + 'id' => 'enable_news', + 'label' => $txt['enable_random_news'], + ), + array( + 'id' => 'use_image_buttons', + 'label' => $txt['admin_image_text'], + ), + '', + array( + 'id' => 'show_newsfader', + 'label' => $txt['news_fader'], + ), + array( + 'id' => 'newsfader_time', + 'label' => $txt['admin_fader_delay'], + 'type' => 'number', + ), + array( + 'id' => 'number_recent_posts', + 'label' => $txt['number_recent_posts'], + 'description' => $txt['number_recent_posts_desc'], + 'type' => 'number', + ), + array( + 'id' => 'show_stats_index', + 'label' => $txt['show_stats_index'], + ), + array( + 'id' => 'show_latest_member', + 'label' => $txt['latest_members'], + ), + array( + 'id' => 'show_group_key', + 'label' => $txt['show_group_key'], + ), + array( + 'id' => 'display_who_viewing', + 'label' => $txt['who_display_viewing'], + 'options' => array( + 0 => $txt['who_display_viewing_off'], + 1 => $txt['who_display_viewing_numbers'], + 2 => $txt['who_display_viewing_names'], + ), + 'type' => 'number', + ), + '', + array( + 'id' => 'show_modify', + 'label' => $txt['last_modification'], + ), + array( + 'id' => 'show_profile_buttons', + 'label' => $txt['show_view_profile_button'], + ), + array( + 'id' => 'show_user_images', + 'label' => $txt['user_avatars'], + ), + array( + 'id' => 'show_blurb', + 'label' => $txt['user_text'], + ), + array( + 'id' => 'show_gender', + 'label' => $txt['gender_images'], + ), + array( + 'id' => 'hide_post_group', + 'label' => $txt['hide_post_group'], + 'description' => $txt['hide_post_group_desc'], + ), + '', + array( + 'id' => 'show_bbc', + 'label' => $txt['admin_bbc'], + ), + array( + 'id' => 'additional_options_collapsable', + 'label' => $txt['additional_options_collapsable'], + ), + ); +} + +?> \ No newline at end of file diff --git a/Themes/core/Stats.template.php b/Themes/core/Stats.template.php new file mode 100644 index 0000000..1fe6649 --- /dev/null +++ b/Themes/core/Stats.template.php @@ -0,0 +1,308 @@ + + + ', $context['page_title'], ' + + + ', $txt['general_stats'], ' + + + + + + + + + + + + + + + + + + + + + + + + + '; + if (!empty($modSettings['hitStats'])) + echo ' + + + '; + echo ' + +
    ', $txt['total_members'], ':', $context['show_member_list'] ? '' . $context['num_members'] . '' : $context['num_members'], '
    ', $txt['total_posts'], ':', $context['num_posts'], '
    ', $txt['total_topics'], ':', $context['num_topics'], '
    ', $txt['total_cats'], ':', $context['num_categories'], '
    ', $txt['users_online'], ':', $context['users_online'], '
    ', $txt['most_online'], ':', $context['most_members_online']['number'], ' - ', $context['most_members_online']['date'], '
    ', $txt['users_online_today'], ':', $context['online_today'], '
    ', $txt['num_hits'], ':', $context['num_hits'], '
    + + + + + + + + + + + + + + + + + + + + + + + + + '; + if (!empty($modSettings['hitStats'])) + echo ' + + + '; + echo ' + +
    ', $txt['average_members'], ':', $context['average_members'], '
    ', $txt['average_posts'], ':', $context['average_posts'], '
    ', $txt['average_topics'], ':', $context['average_topics'], '
    ', $txt['total_boards'], ':', $context['num_boards'], '
    ', $txt['latest_member'], ':', $context['common_stats']['latest_member']['link'], '
    ', $txt['average_online'], ':', $context['average_online'], '
    ', $txt['gender_ratio'], ':', $context['gender']['ratio'], '
    ', $txt['average_hits'], ':', $context['average_hits'], '
    + + + ', $txt['top_posters'], ' + ', $txt['top_boards'], ' + + + + '; + foreach ($context['top_posters'] as $poster) + echo ' + + + + + '; + echo ' +
    ', $poster['link'], '', $poster['num_posts'] > 0 ? '' : ' ', '', $poster['num_posts'], '
    + + + + '; + foreach ($context['top_boards'] as $board) + echo ' + + + + + '; + echo ' +
    ', $board['link'], '', $board['num_posts'] > 0 ? '' : ' ', '', $board['num_posts'], '
    + + + ', $txt['top_topics_replies'], ' + ', $txt['top_topics_views'], ' + + + + '; + foreach ($context['top_topics_replies'] as $topic) + echo ' + + + + + '; + echo ' +
    ', $topic['link'], '', $topic['num_replies'] > 0 ? '' : ' ', '', $topic['num_replies'], '
    + + + + '; + foreach ($context['top_topics_views'] as $topic) + echo ' + + + + + '; + echo ' +
    ', $topic['link'], '', $topic['num_views'] > 0 ? '' : ' ', '', $topic['num_views'], '
    + + + ', $txt['top_starters'], ' + ', $txt['most_time_online'], ' + + + + '; + foreach ($context['top_starters'] as $poster) + echo ' + + + + + '; + echo ' +
    ', $poster['link'], '', $poster['num_topics'] > 0 ? '' : ' ', '', $poster['num_topics'], '
    + + + + '; + foreach ($context['top_time_online'] as $poster) + echo ' + + + + + '; + echo ' +
    ', $poster['link'], '', $poster['time_online'] > 0 ? '' : ' ', '', $poster['time_online'], '
    + + + ', $txt['forum_history'], ' + + + '; + + if (!empty($context['yearly'])) + { + echo ' + + + + + + + '; + + if (!empty($modSettings['hitStats'])) + echo ' + '; + echo ' + '; + + foreach ($context['yearly'] as $id => $year) + { + echo ' + + + + + + '; + if (!empty($modSettings['hitStats'])) + echo ' + '; + echo ' + '; + + foreach ($year['months'] as $month) + { + echo ' + + + + + + '; + if (!empty($modSettings['hitStats'])) + echo ' + '; + echo ' + '; + + if ($month['expanded']) + { + foreach ($month['days'] as $day) + { + echo ' + + + + + + '; + if (!empty($modSettings['hitStats'])) + echo ' + '; + echo ' + '; + } + } + } + } + + echo ' +
    ', $txt['yearly_summary'], '', $txt['stats_new_topics'], '', $txt['stats_new_posts'], '', $txt['stats_new_members'], '', $txt['smf_stats_14'], '', $txt['page_views'], '
    + * ', $year['year'], ' + ', $year['new_topics'], '', $year['new_posts'], '', $year['new_members'], '', $year['most_members_online'], '', $year['hits'], '
    + ', $month['month'], ' ', $month['year'], ' + ', $month['new_topics'], '', $month['new_posts'], '', $month['new_members'], '', $month['most_members_online'], '', $month['hits'], '
    ', $day['year'], '-', $day['month'], '-', $day['day'], '', $day['new_topics'], '', $day['new_posts'], '', $day['new_members'], '', $day['most_members_online'], '', $day['hits'], '
    + + + + '; + } +} + +?> \ No newline at end of file diff --git a/Themes/core/css/ie6.css b/Themes/core/css/ie6.css new file mode 100644 index 0000000..e66904f --- /dev/null +++ b/Themes/core/css/ie6.css @@ -0,0 +1,63 @@ +/* special styles for IE6 */ + +.main_menu li.active a +{ + background: none; + padding-right: 0; +} +.main_menu li.active +{ + background: url(../images/maintab_active_last.gif) no-repeat bottom right; + padding-right: 8px; +} + +* html #poll_options ul.horizlist dl.options dd, * html #poll_options ul.horizlist dl.options dt +{ + margin: 0; + padding: 0; +} +/* the tabled definition lists */ +dl.settings dd, #creator dd, dl.stats dd, dl.register_form dd, #poll_options dl.options dd +{ + float: none; + width: auto; +} + +.modbuttons .buttonlist_bottom ul, .modbuttons .buttonlist ul, .floatright .buttonlist ul, .floatright .buttonlist_bottom ul, .readbuttons .buttonlist ul, .readbuttons .buttonlist_bottom ul +{ + float: right; +} + +/* Profile template */ +#detailedinfo div.content dl +{ + height: 0.1%; +} +.infocenter_section div.sectionbody +{ + height: 30px; +} + +#forumposts .postarea +{ + margin-left: 0; + margin-right: 0; + float: right; +} +.signature +{ + padding: 0 0 0.8em 0; +} +#quickReplyOptions form textarea +{ + width: 98%; +} +code.bbc_code +{ + white-space: normal; +} + +#ip_list li.header +{ + height: .1%; +} \ No newline at end of file diff --git a/Themes/core/css/ie7.css b/Themes/core/css/ie7.css new file mode 100644 index 0000000..db5fcf1 --- /dev/null +++ b/Themes/core/css/ie7.css @@ -0,0 +1,22 @@ +/* special styles for IE7 */ +/* the tabled definition lists */ +dl.settings dd, dl.stats dd, dl.register_form dd, #poll_options dl.options dd +{ + float: none; + width: auto; +} + +.signature +{ + padding: 0 0 0.8em 0; +} + +#quickReplyOptions form textarea +{ + width: 98%; +} + +code.bbc_code +{ + white-space: normal; +} \ No newline at end of file diff --git a/Themes/core/css/index.css b/Themes/core/css/index.css new file mode 100644 index 0000000..4147809 --- /dev/null +++ b/Themes/core/css/index.css @@ -0,0 +1,3523 @@ +/* Styles for the general looks for the Core theme. +------------------------------------------------------- */ + +/* Normal, standard links. */ +a:link, a:visited +{ + color: #476c8e; + text-decoration: none; +} +a:hover +{ + text-decoration: underline; +} + +/* Tables should show empty cells. */ +table +{ + empty-cells: show; +} + +/* Set a fontsize that will look the same in all browsers. */ +body +{ + background: #e5e5e8; + font: 95%/90% Verdana, Helvetica, sans-serif; + margin: 0; + padding: 12px 0 4px 0; +} + +/* Help popups require a different styling of the body element. */ +body#help_popup +{ + width: auto; + padding: 1em; + min-width: 0; +} + +/* use dark grey for the text, leaving #000 for headers etc */ +body, td, th, tr +{ + color: #444; +} + +/* lets give all forms zero padding/margins */ +form +{ + padding: 0; + margin: 0; +} + +/* We can style the different types of input buttons to be uniform throughout different browsers and their color themes. + .button_submit - covers input[type=submit], input[type=button], button[type=submit] and button[type=button] in all browsers + .button_reset - covers input[type=reset] and button[type=reset] throughout all browsers + .input_check - covers input[type=checkbox] throughout all browsers + .input_radio - covers input[type=radio] throughout all browsers + .input_text - covers input[type=text] throughout all browsers + .input_file - covers input[type=file] throughout all browsers +*/ + +input, button, select, textarea +{ + font: 90%/105% verdana, Helvetica, sans-serif; + color: #000; +} + +/* The font size of textareas should be just a little bit larger. */ +textarea +{ + font: 100%/130% verdana, Helvetica, sans-serif; +} + +/* All input elements that are checkboxes or radio buttons shouldn't have a border around them. */ +input.input_check, input.input_radio +{ + border: none; + background: none; +} + +/* Standard horizontal rule.. ([hr], etc.) */ +hr, .hrcolor +{ + height: 1px; + border: 0; + color: #666; + background-color: #666; +} + +/* By default set the color on these tags as #000. */ +h1, h2, h3, h4, h5, h6 +{ + color: #000; + font-size: 1em; + margin: 0; + padding: 0; +} +.content fieldset +{ + border: 2px groove #fff; + padding: 1em; + margin: 0 0 0.3em 0; +} +/* No image should have a border when linked. */ +a img +{ + border: 0; +} + +/* Define strong as bold, and em as italics */ +strong +{ + font-weight: bold; +} + +em +{ + font-style: italic; +} +/* Alternative for u tag */ +.underline +{ + text-decoration: underline; +} + +/* Common classes for easy styling. +------------------------------------------------------- */ + +.floatright +{ + float: right; +} +.floatleft +{ + float: left; +} + +.flow_auto +{ + overflow: auto; +} +.flow_hidden +{ + overflow: hidden; +} +.clear +{ + clear: both; +} +.clear_left +{ + clear: left; +} +.clear_right +{ + clear: right; +} + +/* Default font sizes: small (8pt), normal (10pt), and large (14pt). */ +.smalltext, tr.smalltext th +{ + font-size: 0.85em; + font-family: verdana, sans-serif; +} +.middletext +{ + font-size: 0.9em; + font-family: verdana, sans-serif; +} +.normaltext +{ + font-size: 1em; + line-height: 1.2em; +} +.largetext +{ + font-size: 1.4em; +} +.centertext +{ + margin: 0 auto; + text-align: center; +} +.righttext +{ + margin-left: auto; + margin-right: 0; + text-align: right; +} +.lefttext +{ + margin-left: 0; + margin-right: auto; + text-align: left; +} +/* some common padding styles */ +.padding +{ + padding: 0.7em; +} +.main_section, .lower_padding +{ + padding-bottom: 0.5em; +} +/* a quick reset list class. */ +ul.reset, ul.reset li +{ + padding: 0; + margin: 0; + list-style: none; +} + +/* Some BBC related styles. +------------------------------------------------------- */ + +/* A quote, perhaps from another post. */ +blockquote.bbc_standard_quote, blockquote.bbc_alternate_quote +{ + color: #000; + border: 1px solid #000; + margin: 1px; + padding: 1px; + font-size: x-small; + line-height: 1.4em; + overflow: auto; +} + +/* Alterate block quote stylings */ +blockquote.bbc_standard_quote +{ + background-color: #d7daec; +} +blockquote.bbc_alternate_quote +{ + background-color: #e7eafc; +} + +/* A code block - maybe even PHP ;). */ +code.bbc_code +{ + display: block; + font-family: "dejavu sans mono", "monaco", "lucida console", "courier new", monospace; + font-size: x-small; + background: #eef; + border: 1px solid #000; + line-height: 1.3em; + padding: 1px; + overflow: auto; + white-space: nowrap; + /* Show a scrollbar after about 24 lines. */ + max-height: 24em; +} + +/* The "Quote:" and "Code:" header parts... */ +.codeheader, .quoteheader +{ + color: #000; + text-decoration: none; + font-style: normal; + font-weight: bold; + font-size: x-small; + line-height: 1.2em; + padding: 0 0.3em; +} + +/* For links to change the code stuff... */ +.codeoperation +{ + font-weight: normal; +} + +/* Styling for BBC tags */ +.bbc_size +{ + line-height: 1.4em; +} +.bbc_color a +{ + color: inherit; +} +.bbc_img +{ + border: 0; +} +.bbc_table +{ + font: inherit; + color: inherit; +} +.bbc_table td +{ + font: inherit; + color: inherit; + vertical-align: top; +} +.bbc_u +{ + text-decoration: underline; +} +.bbc_tt +{ + font-family: "dejavu sans mono", "monaco", "lucida console", "courier new", monospace; +} + +/* Generally, those [?] icons. This makes your cursor a help icon. */ +.help +{ + cursor: help; +} + +/* /me uses this a lot. (emote, try typing /me in a post.) */ +.meaction +{ + color: red; +} + +/* Highlighted text - such as search results. */ +.highlight +{ + background-color: #ff0; + font-weight: bold; + color: #000; +} + +/* A more discreet highlight color, for selected membergroups etc. */ +.highlight2 +{ + background-color: #D1E1EF; + color: #000; +} + +/* Generic, mostly color-related, classes. +------------------------------------------------------- */ + +.titlebg, .titlebg2, tr.titlebg td, tr.titlebg2 td +{ + color: #000; + font-family: Verdana, Helvetica, sans-serif; + font-weight: bold; + background: url(../images/titlebg.jpg) #E9F0F6 repeat-x; +} +.catbg, .catbg2, tr.catbg td, tr.catbg2 td, tr.catbg th, tr.catbg2 th +{ + color: #fff; + font-family: Verdana, Helvetica, sans-serif; + font-weight: bold; + background: url(../images/catbg.jpg) #88A6C0 repeat-x; +} +.catbg, .catbg2, tr.catbg td, tr.catbg2 td, tr.catbg th, tr.catbg2 th +{ + background: url(../images/catbg2.jpg) #A1BFD9 repeat-x; +} + +/* adjust the table versions of headers */ +tr.titlebg td, tr.titlebg2 td +{ + padding: 6px; +} +tr.catbg td, tr.catbg2 td, td.catbg, td.catbg2, tr.catbg th, tr.catbg2 th, th.catbg, th.catbg2 +{ + padding: 6px; +} +tr.titlebg td a, tr.titlebg2 td a +{ + color: #000; +} +tr.catbg td a, tr.catbg2 td a, .catbg a +{ + color: #fff; +} +tr.catbg th.smalltext +{ + font-size: 0.9em; +} +/* Alternating backgrounds for posts, and several other sections of the forum. */ +.windowbg, #preview_body, .content, .roundframe +{ + color: #000; + background-color: #ecedf3; +} +.windowbg2 +{ + color: #000; + background-color: #f6f6f6; +} +.windowbg3 +{ + color: #000; + background-color: #e0e1e8; +} + +/* the page navigation area */ +.pagesection +{ + font-size: 0.85em; + padding: 0.5em 0.2em; + overflow: hidden; +} +.pagesection .pagelinks +{ + padding: 0.5em 0; +} + +/* GenericList */ +table.table_grid thead tr.catbg th.smalltext +{ + white-space: nowrap; +} + +/* Color for background of posts requiring approval */ +.approvebg +{ + color: #000; + background-color: #f6e0d4; +} +/* Color for background of *topics* requiring approval */ +.approvetbg +{ + color: #000; + background-color: #e4a17c; +} +/* sticky posts have a different background */ +.stickybg +{ + background: #e8d8cf; +} +.stickybg2 +{ + background: #f2e3d9; +} +/* locked posts too! */ +.lockedbg +{ + background: #d4dce2; + font-style: italic; +} +.lockedbg2 +{ + background: #d8e1e7; + font-style: italic; +} + +/* Posts and personal messages displayed throughout the forum. */ +.post, .personalmessage +{ + width: 100%; + overflow: auto; + line-height: 1.4em; +} + +/* All the signatures used in the forum. If your forum users use Mozilla, Opera, or Safari, you might add max-height here ;). */ +.signature +{ + clear: right; + padding: 1em 0 3px 0; + width: 98%; + border-top: 1px solid #666; + line-height: 1.4em; + font-size: 0.85em; +} +.custom_fields_above_signature +{ + clear: right; + padding: 1em 0 3px 0; + width: 98%; + border-top: 1px solid #666; + line-height: 1.4em; + font-size: 0.85em; +} + +/* Sometimes there will be an error when you post */ +.error +{ + color: red; +} + +/* Messages that somehow need to attract the attention. */ +.alert +{ + color: red; +} + +/* Calendar colors for birthdays, events and holidays */ +.birthday +{ + color: #920ac4; +} + +.event +{ + color: #078907; +} + +.holiday +{ + color: #000080; +} + +/* Colors for warnings */ +.warn_mute +{ + color: red; +} + +.warn_moderate +{ + color: #ffa500; +} + +.warn_watch, .success +{ + color: green; +} + +a.moderation_link, a.moderation_link:visited +{ + color: red; + font-weight: bold; +} + +.openid_login +{ + background: white url(../images/openid.gif) no-repeat; + padding-left: 18px; +} + +/* a descriptive style */ +.description +{ + padding: 1em; + font-size: 0.9em; + line-height: 1.5em; + border: 1px solid #bbb; + background: #f5f5f0; + margin: 0 0 1em 0; +} +/* an informative style */ +.information +{ + padding: 1em; + font-size: 0.9em; + line-height: 1.5em; + border: 1px solid #bbb; + background: #f0f6f0; + margin: 0 0 1em 0; +} +.information p +{ + padding: 1em; + margin: 0; +} +/* AJAX notification bar +------------------------------------------------------- */ +#ajax_in_progress +{ + background: #32cd32; + color: #fff; + text-align: center; + font-weight: bold; + font-size: 18pt; + padding: 0.4em; + width: 100%; + position: fixed; + top: 0; + left: 0; +} + +#ajax_in_progress a +{ + color: #fff; + text-decoration: underline; + font-size: smaller; + float: right; +} + +/* a general table class */ +table.table_grid +{ + border-collapse: collapse; + border: 1px solid #adadad; +} +table.table_grid td +{ + padding: 3px; + border: 1px solid #adadad; +} + +/* Lists with settings use these a lot. +------------------------------------------------------- */ +dl.settings +{ + clear: right; + overflow: auto; + margin: 0 0 10px 0; + padding: 0; +} +dl.settings dt +{ + width: 48%; + float: left; + margin: 0 0 10px 0; + padding: 0; + clear: both; +} +dl.settings dt.settings_title +{ + width: 100%; + float: none; + margin: 0 0 10px 0; + padding: 5px 0 0 0; + font-weight: bold; + clear: both; +} +dl.settings dt.windowbg +{ + width: 98%; + float: left; + margin: 0 0 3px 0; + padding: 0 0 5px 0; + clear: both; +} +dl.settings dd +{ + width: 48%; + float: left; + overflow: auto; + margin: 0 0 3px 0; + padding: 0; +} +dl.settings img +{ + margin: 0 10px 0 0; +} + +/* The main content area. +------------------------------------------------------- */ +.content, .roundframe +{ + padding: 0.5em 1.2em; + margin: 0; + border: none; + border: 1px solid #adadad; +} +.content p, .roundframe p +{ + margin: 0 0 0.5em 0; +} + +/* Styles used by the auto suggest control. +------------------------------------------------------- */ +.auto_suggest_div +{ + border: 1px solid #000; + position: absolute; + visibility: hidden; +} +.auto_suggest_item +{ + background-color: #ddd; +} +.auto_suggest_item_hover +{ + background-color: #888; + cursor: pointer; + color: #eee; +} + +/* Styles for the standard dropdown menus. +------------------------------------------------------- */ +/* Container for the new admin menu */ +#adm_container +{ + float: left; + margin-left: 10px; + padding: 0 5px 0 5px; + background: url(../images/admintab_left.gif) no-repeat; +} + +ul.admin_menu, ul.admin_menu li ul +{ + margin: 0; + padding: 0; + list-style: none; +} + +ul.admin_menu +{ + background: url(../images/admintab_right.gif) top right no-repeat; +} + +ul.admin_menu a +{ + text-decoration: none; +} + +/* First layer of menu items */ +ul.admin_menu li +{ + position: relative; + float: left; + background: url(../images/admintab_back.gif) top right repeat-x; + padding-right: 4px; +} + +ul.admin_menu li.last +{ + background: url(../images/admintab_right.gif) top right repeat-x; +} + +ul.admin_menu li.chosen +{ + background: url(../images/admintab_active_left.gif) no-repeat; + padding: 0 0 0 6px; +} + +ul.admin_menu li h4 +{ + margin: 0; + padding: 7px 5px 3px 5px; + cursor: pointer; + font-weight: normal; + font-size: x-small; + text-transform: uppercase; + color: #fff; +} + +ul.admin_menu li.last.chosen h4 +{ + background: url(../images/admintab_active_last.gif) top right no-repeat; + padding-right: 17px; +} +/* IE6 does't support multiple class selectors */ +ul.admin_menu li.last_chosen h4 +{ + background: url(../images/admintab_active_last.gif) top right no-repeat; + padding-right: 17px; +} + +ul.admin_menu li.chosen h4 +{ + background: url(../images/admintab_active_right.gif) top right no-repeat; + padding-right: 10px; +} + +/* Second layer of menu items */ + +ul.admin_menu li ul +{ + z-index: 90; + display: none; + position: absolute; + /* IE6 needs a fixed width to prevent the menu from going haywire */ + width: 19em; + border: 1px solid #808080; + border-left: 2px solid #6888a7; + background: #f8f8fb; +} + +ul.admin_menu li.chosen ul +{ + margin: 0 0 0 -6px; +} + +ul.admin_menu li ul li +{ + background: none; + width: 19em; + padding: 0; +} + +ul.admin_menu li ul li a +{ + display: block; + padding: 0.5em 2em 0.5em 0.5em; + font-size: 90%; + text-decoration: none; + background: none; + color: #000 !important; +} + +ul.admin_menu li ul li a.subsection +{ + background: url(../images/admin/subsection.gif) no-repeat 98% 50%; +} + +ul.admin_menu li ul li a.chosen +{ + font-weight: bold; +} + +ul.admin_menu li ul li a:hover +{ + background-color: #c8e2fb; + text-decoration: none; +} + +ul.admin_menu li:hover ul, ul.admin_menu li.over ul +{ + display: block; +} + +/* Third layer of menu items */ +ul.admin_menu li ul li ul, ul.admin_menu li ul li.over ul +{ + display: none; + position: absolute; + top: -999em; + border: 1px solid #a0a0a0; + border-left: 2px solid #6888a7; + background: #fff; +} + +ul.admin_menu li ul li:hover ul, ul.admin_menu li ul li.over ul +{ + display: block; + left: 18em; + top: auto; + margin: -2em 0 0 1em; +} +#adm_submenus +{ + padding: 0 0 0 2em; +} +#adm_submenus, #adm_submenus ul +{ + height: 3em; + overflow: auto; +} + +/* The dropdown menu toggle image */ +div#menu_toggle +{ + float: right; + margin: 0 10px 0 0; + background: url(../images/mirrortab_first.gif) top left no-repeat; + padding: 0 0 0 7px; +} +div#menu_toggle a +{ + display: block; + background: #e5e5e8 url(../images/mirrortab_last.gif) top right no-repeat; + padding: 8px 12px 3px 6px; +} + +/* Styles for the standard button lists. +------------------------------------------------------- */ + +.buttonlist ul +{ + background: url(../images/maintab_first.gif) no-repeat scroll left bottom; + padding: 0 0 0 10px; +} +.buttonlist ul li, .buttonlist_bottom ul li +{ + display: inline; +} +.buttonlist ul li a, .buttonlist_bottom ul li a +{ + float: left; + display: block; + color: #fff; + font-size: 0.8em; + font-family: tahoma, sans-serif; + text-transform: uppercase; + text-decoration: none; +} +.buttonlist ul li a:hover, .buttonlist_bottom ul li a:hover +{ + color: #e0e0ff; +} +.buttonlist ul li a span +{ + background: url(../images/maintab_back.gif) repeat-x bottom left; + display: block; + padding: 0.1em 0.5em 0.5em 0.5em; +} +.buttonlist ul li.last a span +{ + background: url(../images/maintab_last.gif) no-repeat bottom right; + padding: 0.1em 1em 0.5em 0.5em; +} +.buttonlist ul li.active a span em +{ + padding: 0.1em 0.5em 0.5em 0.5em; + display: block; + font-style: normal; + background: url(../images/maintab_active_back.gif) repeat-x bottom right; +} +.buttonlist ul li.active a span +{ + background: url(../images/maintab_active_first.gif) no-repeat bottom left; + padding: 0 0 0 8px; +} +.buttonlist ul li.lastactive +{ + float: left; + background: url(../images/maintab_last.gif) no-repeat bottom right; + padding: 0 8px 0 0; +} +.buttonlist ul li.active a +{ + background: url(../images/maintab_active_last.gif) no-repeat bottom right; + padding-right: 8px; +} +/* For links that are basically submit buttons. */ +.buttonlist_submit +{ + background: transparent; + color: #fff; + text-transform: uppercase; + vertical-align: top; + text-decoration: none; + font-size: 9px; + font-family: tahoma, sans-serif; + border: 0; +} +.buttonlist_submit:hover +{ + color: #e0e0ff; +} +/* ..for the "bottom" menu */ +.buttonlist_bottom ul +{ + background: url(../images/mirrortab_first.gif) no-repeat scroll left top; + padding: 0 0 0 10px; +} +.buttonlist_bottom ul li a span +{ + background: url(../images/mirrortab_back.gif) repeat-x top left; + display: block; + padding: 0.4em 0.5em 0.2em 0.5em; +} +.buttonlist_bottom ul li.last a span +{ + background: url(../images/mirrortab_last.gif) no-repeat top right; + padding: 0.4em 1em 0.2em 0.5em; +} +.buttonlist_bottom ul li.active a span em +{ + padding: 0.4em 0.5em 0.2em 0.5em; + display: block; + font-style: normal; + background: url(../images/mirrortab_active_back.gif) repeat-x top right; +} +.buttonlist_bottom ul li.active a span +{ + background: url(../images/mirrortab_active_first.gif) no-repeat top left; + padding: 0 0 0 8px; +} +.buttonlist_bottom ul li.lastactive +{ + float: left; + background: url(../images/mirrortab_last.gif) no-repeat top right; + padding: 0 8px 0 0; +} +.buttonlist_bottom ul li.active a +{ + background: url(../images/mirrortab_active_last.gif) no-repeat top right; + padding-right: 8px; +} + +/* The old-style button strips, with images */ +.oldbuttonlist +{ + text-align: right; + padding: 0.5em; +} + +/* a smaller quick-button list */ +ul.quickbuttons +{ + margin: 0.9em 11px 0 0; + clear: right; + float: right; + text-align: right; +} +ul.quickbuttons li +{ + float: left; + display: inline; + margin: 0 0 0 11px; +} +ul.quickbuttons li a +{ + padding: 0 0 0.7em 20px; + display: block; + height: 20px; + font: bold 0.85em/18px arial, sans-serif; + float: left; +} +ul.quickbuttons li.quote_button +{ + background: url(../images/buttons/quote.gif) no-repeat 0 0; +} +ul.quickbuttons li.remove_button +{ + background: url(../images/buttons/delete.gif) no-repeat 0 0; +} +ul.quickbuttons li.modify_button +{ + background: url(../images/buttons/modify.gif) no-repeat 0 0; +} +ul.quickbuttons li.approve_button +{ + background: url(../images/buttons/approve.gif) no-repeat 0 0; +} +ul.quickbuttons li.restore_button +{ + background: url(../images/buttons/restore_topic.gif) no-repeat 0 0; +} +ul.quickbuttons li.split_button +{ + background: url(../images/buttons/split.gif) no-repeat 0 0; +} +ul.quickbuttons li.reply_button +{ + background: url(../images/buttons/reply.gif) no-repeat 0 0; +} +ul.quickbuttons li.reply_all_button +{ + background: url(../images/buttons/reply.gif) no-repeat 0 0; +} +ul.quickbuttons li.notify_button +{ + background: url(../images/buttons/notify_sm.gif) no-repeat 0 0; +} +ul.quickbuttons li.inline_mod_check +{ + margin: 0 0 0 5px; +} + +.generic_tab_strip +{ + margin: 0 1em 2em; +} +.generic_tab_strip .buttonlist +{ + float: left !important; +} + +/* the navigation list */ +ul#navigation +{ + margin: 0; + font-size: 0.9em; + padding: 1em 0.4em; +} +ul#navigation li +{ + float: none; + font-size: 0.95em; + display: inline; +} + +/* Styles for the general looks for the Core theme. +------------------------------------------------------- */ + +/* this is the main container surrounding everything, use this to set forum width, font-size etc. */ +#mainframe +{ + font-size: 85%; + width: 95%; + margin: auto; +} +/* the forum name or logo */ +h1#forum_name +{ + padding: 0.6em 0 0.6em 0; + margin: 0; + font-family: Verdana, helvetica, sans-serif; + font-size: 135%; + color: #fff; +} + +/* The greeting section */ +#greeting_section +{ + padding: 0.7em 0.4em 0.7em 0.4em; + clear: both; +} +#greeting_section li +{ + font-weight: normal; +} +#greeting_section li#name +{ + padding-left: 0.5em; +} +#greeting_section li em +{ + font-style: normal; + font-weight: bold; +} + +/* user section with all relevant links */ +#user_section +{ + padding: 1px; + margin: 1px 0 0 0; + font-size: 90%; +} +#user_section ul, #user_section form +{ + padding: 0.5em 0.7em 0.5em 0.7em; +} + +/* the avatar, located to the left */ +#user_section #myavatar +{ + padding: 0.7em; + border-right: 1px solid #adadad; + margin: 0 0.5em 0 0; + float: left; +} +/* the news and search areas */ +#news_section +{ + clear: both; + font-size: 0.8em; + padding: 0.5em 1em 0.5em 1em; +} +#random_news h3 +{ + margin-right: 1em; + font-size: 0.85em; + display: inline; +} +#random_news p +{ + margin: 0; + padding: 0; + display: inline; +} + +/* The main menu. */ +.main_menu +{ + padding-left: 1em; +} +.main_menu ul +{ + list-style: none; + padding: 0; + margin: 0; + background: url(../images/maintab_first.gif) no-repeat bottom left; + padding-left: 10px; +} +.main_menu li +{ + margin: 0; + padding: 0; + display: inline; +} +.main_menu li a:link, .main_menu li a:visited +{ + float: left; + display: block; + color: #fff; + font-size: 0.8em; + font-family: tahoma, sans-serif; + text-transform: uppercase; +} +.main_menu li a:hover +{ + color: #e0e0ff; + text-decoration: none; +} +.main_menu li a span +{ + background: url(../images/maintab_back.gif) repeat-x bottom left; + display: block; + padding: 0.1em 0.5em 0.5em 0.5em; +} +.main_menu li.last a span +{ + background: url(../images/maintab_last.gif) no-repeat bottom right; + padding: 0.1em 1em 0.5em 0.5em; +} +.main_menu li.active a span em +{ + padding: 0.1em 0.5em 0.5em 0.5em; + display: block; + font-style: normal; + background: url(../images/maintab_active_back.gif) repeat-x bottom right; +} +.main_menu li.active a span +{ + background: url(../images/maintab_active_first.gif) no-repeat bottom left; + padding: 0 0 0 8px; +} +.main_menu li.last.active +{ + float: left; + background: url(../images/maintab_last.gif) no-repeat bottom right; + padding: 0 8px 0 0; +} +/* IE6 doesn't support multiple class selectors */ +.main_menu li.lastactive +{ + float: left; + padding: 0 8px 0 0; + background: url(../images/maintab_last.gif) no-repeat bottom right; +} +.main_menu li.active a +{ + background: url(../images/maintab_active_last.gif) no-repeat bottom right; + padding-right: 8px; +} + +/* the linktree */ +ul.linktree +{ + clear: both; + width: 100%; + list-style: none; + margin: 0; + padding: 1.5em 0.5em 0.5em 0.5em; + overflow: hidden; +} +ul.linktree li +{ + float: left; + padding: 0 0.5em 0 0; + font-size: 0.8em; +} +ul.linktree li a +{ + color: #000; +} +ul.linktree li a:hover +{ + color: #cc3333; +} +ul.linktree li span +{ + font-weight: bold; +} + +/* the footer area */ +#footerarea +{ + padding: 1em 0 2em 0; + text-align: center; +} +#footerarea ul +{ + margin: 0 auto 0 auto; +} +#footerarea ul li +{ + text-align: center; + display: inline; + border-right: 1px solid #888; + margin: 0; + padding: 0 4px 0 2px; +} +/* Note: It is against the license to remove, alter or otherwise hide the copyright output from SMF so please do not alter the two sections below. */ +#footerarea ul li.copyright +{ + display: block; + line-height: 0; + font-size: small; + padding: 1em; +} +#footerarea ul li.copyright, #footerarea ul li.last +{ + border-right: none; +} +/* page created in.. */ +#footerarea p +{ + clear: both; + text-align: left; + padding-left: 0.5em; +} +p#show_loadtime +{ + display: block; + text-align: center; +} +/* the upshrink buttons */ +#upshrink, #advsearch +{ + margin: 0 1ex; +} + +/* Styles for a typical table. +------------------------------------------------------- */ +table.table_list +{ + width: 100%; +} +table.table_list p +{ + padding: 0; + margin: 0; +} +table.table_list td,table.table_list th +{ + padding: 5px; +} +table.table_list tbody.header td +{ + padding: 0; +} +table.table_list tbody.content td.stats +{ + font-size: 90%; + width: 15%; + text-align: center; +} +table.table_list tbody.content td.lastpost +{ + line-height: 1.2em; + font-size: 85%; + width: 24%; +} +table.table_list tbody.content td.icon +{ + text-align: center; + width: 6%; +} + +/* Styles for headers. +------------------------------------------------------- */ +/* Styles for headers used in Curve templates. */ +h3.catbg, h3.catbg2, h3.titlebg, h4.titlebg, h4.catbg, div.titlebg, .table_list tbody.header td +{ + overflow: hidden; + line-height: 2em; + font-weight: bold; +} +h3.titlebg, h4.titlebg +{ + border-left: 1px solid #adadad; + border-right: 1px solid #adadad; +} +h3.titlebg, h4.catbg +{ + padding: 0 0.5em !important; +} +h3.catbg img.icon, div.titlebg img.icon, h3.catbg img +{ + float: left; + margin: 5px 8px 0 0; +} + +/* These are used primarily for titles, but also for headers (the row that says what everything in the table is.) */ +.titlebg, tr.titlebg th, tr.titlebg td, .titlebg2, tr.titlebg2 th, tr.titlebg2 td +{ + color: #000; + font-style: normal; + background: url(../images/titlebg.jpg) #E9F0F6 repeat-x; + border-bottom: 1px solid #9baebf; + border-top: 1px solid #fff; + padding-left: 10px; + padding-right: 10px; +} +.titlebg, .titlebg a:link, .titlebg a:visited +{ + font-weight: bold; + color: #000; + font-style: normal; +} + +.titlebg a:hover +{ + color: #404040; +} +/* same as titlebg, but used where bold text is not needed */ +.titlebg2 a:link, .titlebg2 a:visited +{ + color: #000; + font-style: normal; + text-decoration: underline; +} + +.titlebg2 a:hover +{ + text-decoration: underline; +} + +/* This is used for categories, page indexes, and several other areas in the forum. +.catbg and .catbg2 is for boardindex, while .catbg3 is for messageindex and display headers. */ +.catbg, tr.catbg td, .catbg3, tr.catbg3 td +{ + background: url(../images/catbg.jpg) #88A6C0 repeat-x; + color: #fff; + padding-left: 10px; + padding-right: 10px; +} +.catbg2, tr.catbg2 td +{ + background: url(../images/catbg2.jpg) #A1BFD9 repeat-x; + color: #fff; + padding-left: 10px; + padding-right: 10px; +} +.catbg, .catbg2, .catbg3 +{ + border-bottom: 1px solid #375576; +} +.catbg, .catbg2 +{ + font-weight: bold; +} +.catbg3, tr.catbg3 td, .catbg3 a:link, .catbg3 a:visited +{ + font-size: 95%; + color: #fff; + text-decoration: none; +} +.catbg a:link, .catbg a:visited, .catbg2 a:link, .catbg2 a:visited +{ + color: #fff; + text-decoration: none; +} +.catbg a:hover, .catbg2 a:hover, .catbg3 a:hover +{ + color: #e0e0ff; +} + +/* Styles for the board index. +------------------------------------------------- */ + +p#stats +{ + text-align: right; +} +h3#newsfader +{ + font-size: 1em; +} +#smfNewsFader +{ + font-weight: bold; + line-height: 1.4em; + padding: 1em; + font-size: 1em; + text-align: center; +} +#upshrink_ic +{ + margin-right: 2ex; + text-align: right; +} +.categoryframe +{ + margin-top: 0.4em; +} +.categoryframe h3 +{ + margin: 0; +} +table.boardsframe +{ + width: 100%; +} +table.boardsframe td.icon +{ + text-align: center; + padding: 0.5em; + width: 6%; +} +table.boardsframe td.info +{ + width: 60%; + padding: 0; +} +table.boardsframe td.info h4 +{ + padding: 0.4em 0.4em 0 0.4em; + margin: 0; +} +table.boardsframe td.info p +{ + padding: 0 0.4em 0.5em 0.4em; + margin: 0; +} +table.boardsframe td.info p.moderators +{ + font-size: 0.8em; + font-family: verdana, sans-serif; +} +table.boardsframe td.stats +{ + width: 8%; + vertical-align: middle; + text-align: center; +} +table.boardsframe td.lastpost +{ + width: 20%; + vertical-align: top; + padding: 0.5em; +} +#posticons +{ + clear: both; + width: 100%; +} +#posticons .buttonlist +{ + margin-right: 1em; + float: right; +} + +/* the newsfader */ +#smfFadeScroller +{ + text-align: center; + overflow: auto; + color: #000000; /* shouldn't be shorthand style due to JS bug in IE! */ +} + +/* Styles for the info center on the board index. +---------------------------------------------------- */ + +#infocenterframe +{ + margin-top: 2em; + clear: both; +} +/* each section in infocenter has this class */ +.infocenter_section +{ + clear: both; +} +.infocenter_section p.section +{ + display: block; + margin: 0; + width: 30px; + text-align: center; + float: left; + padding: 0.5em 0 0 0; +} +.infocenter_section div.sectionbody +{ + margin-left: 30px; + padding: 0.3em; + border-left: 1px solid #a0a0a0; + min-height: 25px; + height: auto !important; +} +/* recent posts - or just one recent post */ +dl#infocenter_recentposts +{ + float: left; + width: 100%; + padding: 0; + margin: 0; +} +dl#infocenter_recentposts dt +{ + clear: left; + float: left; + padding: 0.1em; + width: 68%; + white-space: nowrap; + overflow: hidden; +} +dl#infocenter_recentposts dd +{ + clear: right; + float: right; + padding: 0.1em; + width: 25%; + text-align: right; + white-space: nowrap; + overflow: hidden; +} +/* login form */ +form#infocenter_login ul.horizlist label +{ + white-space: nowrap; + font-size: 90%; + font-weight: bold; +} + +/* Styles for the message (topic) index. +---------------------------------------------------- */ + +#childboards table +{ + width: 100%; +} +.modbuttons +{ + clear: both; + width: 100%; +} +.buttonlist, .buttonlist_bottom +{ + margin-right: 1em; + float: right; +} +#messageindex td.icon1, #messageindex td.icon2 +{ + text-align: center; + padding: 0.5em; + width: 5%; +} +#messageindex td.subject +{ + padding: 0.5em; +} +#messageindex td.starter +{ + text-align: center; + padding: 0.5em; + width: 14%; +} +#messageindex td.replies +{ + text-align: center; + padding: 0.5em; + width: 4%; +} +#messageindex td.views +{ + text-align: center; + padding: 0.5em; + width: 4%; +} +#messageindex td.lastpost +{ + padding: 0.5em; + width: 22%; +} +#messageindex td.moderation +{ + text-align: center; + padding: 0.5em; + width: 4%; +} +#topic_icons p +{ + display: block; + padding: 0.5em 0.5em 0.1em 0.5em; + margin: 0; + border-bottom: none; + font-weight: normal !important; +} +#topic_icons ul +{ + display: block; + padding: 0.5em 1em 0.1em 1em; + margin: 0; + border-bottom: none; + font-weight: normal !important; +} +#message_index_jump_to +{ + margin: 2em 4em 0 2em; +} +.lastpost img +{ + float: right; +} + +/* Styles for the display template (topic view). +---------------------------------------------------- */ + +.linked_events +{ + clear: both; + margin: 1em 0; +} +.linked_events .edit_event +{ + color: #f00; +} +#moderationbuttons +{ + margin-left: 0.5em; +} +#postbuttons .nav, #postbuttons_lower .nav +{ + margin: 0.5em 0.5em 0 0; + text-align: right; +} +#postbuttons_lower .nav +{ + margin: 0 0.5em 0.5em 0; +} +#postbuttons, #postbuttons_lower +{ + text-align: right; +} + +/* Poll question */ +h4#pollquestion +{ + padding: 1em 0 1em 2em; +} + +/* Poll vote options */ +#poll_options ul.options +{ + border-top: 1px solid #696969; + padding: 1em 2.5em 0 2em; + margin: 0 0 1em 0; +} +#poll_options div.submitbutton +{ + clear: both; + padding: 0 0 1em 2em; +} + +#poll_options div.submitbutton.border +{ + border-bottom: 1px solid #696969; + margin: 0 0 1em 0; +} + +/* Poll results */ +#poll_options dl.options +{ + border: solid #696969; + border-width: 1px 0; + padding: 1em 2.5em 0 2em; + margin: 0 0 1em 0; +} +#poll_options dl.options dt.voted +{ + font-weight: bold; +} +#poll_options dl.options dd +{ + margin: 0.5em 0 1em 0; +} + +/* Poll notices */ +#poll_options p +{ + margin: 0 1.5em 0.2em 1.5em; + padding: 0 0.5em 0.5em 0.5em; +} + +div#pollmoderation +{ + margin: -1em 0 0 2em; + padding: 0; +} + +.approve_post +{ + margin: 2ex; + padding: 1ex; + border: 2px dashed #cc3344; + color: #000; + font-weight: bold; +} +#forumposts h3.catbg3 +{ + font-weight: normal; + padding: 0.4em; + overflow: hidden; +} +#forumposts h3.catbg3 img +{ + float: left; + vertical-align: middle; +} +#forumposts h3.catbg3 span +{ + float: left; + padding-left: 2%; +} +#forumposts h3.catbg3 span#top_subject +{ + padding-left: 9.5em; +} +.poster +{ + width: 15em; + float: left; +} +.post +{ + clear: right; +} +img.smiley +{ + vertical-align: bottom; +} +.postarea +{ + margin-left: 16em; +} +.messageicon +{ + float: left; + margin: 0 0.5em 0.5em 0; +} +.messageicon img +{ + padding: 6px 3px; +} +.keyinfo +{ + float: left; + clear: none; + width: 50%; + min-height: 3em; +} +ul.postingbuttons +{ + float: right; + padding: 0 0.5em 0 0; +} +ul.postingbuttons li +{ + float: left; + margin: 0 0.5em 0 0; +} +.modifybutton +{ + float: right; + margin: 0 0.5em 0.5em 0; + font: bold 0.85em arial, sans-serif; + color: #476c8e; +} +.attachments +{ + padding-top: 1em; + overflow: auto; +} +.attachments hr +{ + clear: both; + margin: 1em 0 1em 0; +} +.postfooter +{ + margin-left: 16em; +} +.topborder +{ + border-top: 1px solid #bbb; +} +.moderatorbar +{ + clear: right; + margin: 1em 0 0 16em; +} +#pollmoderation, #moderationbuttons_strip +{ + float: left; +} + +/* Styles for the quick reply area. +---------------------------------------------------- */ + +#quickReplyOptions #quickReplyWarning +{ + border: none; + text-align: left; + margin: 0; + width: 25%; + float: left; +} +#quickReplyOptions #quickReplyContent +{ + text-align: right; + float: left; + width: 67.5%; + padding: 1em; + border-left: 1px solid #aaa; +} + +#quickReplyOptions #quickReplyContent textarea, #quickReplyOptions #quickReplyContent input +{ + margin-bottom: .5em; +} + +#quickReplyWarning +{ + width: 20%; + float: left; + padding: 0.5em 1em; +} +#quickReplyContent +{ + width: 75%; + float: right; + padding: 0.5em 0; +} +#quickReplyOptions .roundframe +{ + overflow: hidden; +} +#quickReplyOptions form textarea +{ + height: 100px; + width: 635px; + max-width: 100%; + min-width: 100%; +} + +/* The jump to box */ +#display_jump_to +{ + clear: both; + padding: 5px; +} + +/* Separator of posts. More useful in the print stylesheet. */ +#forumposts .post_separator +{ + display: none; +} + +/* Styles for edit post section +---------------------------------------------------- */ +form#postmodify .roundframe +{ + padding: 0 12%; +} +#post_header +{ + margin-bottom: 0.5em; + padding: 0.5em; + overflow: hidden; +} +#post_header dt +{ + float: left; + margin: 0; + padding: 0; + width: 15%; + margin: .3em 0; + font-weight: bold; +} +#post_header dd +{ + float: left; + margin: 0; + padding: 0; + width: 83%; + margin: .3em 0; +} +#post_header img +{ + vertical-align: middle; +} +ul.post_options +{ + margin: 0 0 0 1em; + padding: 0; + list-style: none; + overflow: hidden; +} +ul.post_options li +{ + margin: 0.2em 0; + width: 49%; + float: left; +} +#postAdditionalOptionsHeader +{ + margin-top: 1em; +} +#postMoreOptions +{ + border-bottom: 1px solid #666; + padding: 0.5em; +} +#postAttachment, #postAttachment2 +{ + overflow: hidden; + margin: .5em 0; + padding: 0; + border-bottom: 1px solid #666; + padding: 0.5em; +} +#postAttachment dd, #postAttachment2 dd +{ + margin: .3em 0 .3em 1em; +} +#postAttachment dt, #postAttachment2 dt +{ + font-weight: bold; +} +#postAttachment3 +{ + margin-left: 1em; +} +#post_confirm_strip, #shortcuts +{ + padding: 1em 0 0 0; +} +.post_verification +{ + margin-top: .5em; +} +.post_verification #verification_control +{ + margin: .3em 0 .3em 1em; +} +/* The BBC buttons */ +#bbcBox_message +{ + margin: 1em 0 0.5em 0; +} +#bbcBox_message div +{ + margin: 0.2em 0; + vertical-align: top; +} +#bbcBox_message div img +{ + margin: 0 1px 0 0; + vertical-align: top; +} +#bbcBox_message select +{ + margin: 0 2px; +} +/* The smiley strip */ +#smileyBox_message +{ + margin: 0.75em 0 0.5em 0; +} + +/* Styles for edit event section +---------------------------------------------------- */ +#post_event .roundframe +{ + padding: 1% 12%; +} +#post_event fieldset +{ + margin-bottom: 0.5em; + border: 1px solid #c4c4c4; + padding: 0.5em; + clear: both; +} +#post_event legend +{ + font-weight: bold; + color: #000; +} +#post_event #event_main input +{ + margin: 0 0 1em 0; + float: left; +} +#post_event #event_main div.smalltext +{ + width: 33em; + float: right; +} +#post_event div.event_options +{ + float: right; +} +#post_event ul.event_main, ul.event_options +{ + padding: 0; + overflow: hidden; +} +#post_event ul.event_main li +{ + list-style-type: none; + margin: 0.2em 0; + width: 49%; + float: left; +} +#post_event ul.event_options +{ + margin: 0; + padding: 0 0 .7em .7em; +} +#post_event ul.event_options li +{ + list-style-type: none; + margin: 0; + float: left; +} +#post_event #event_main select, #post_event ul.event_options li select, #post_event ul.event_options li .input_check +{ + margin: 0 1em 0 0; +} + +/* Styles for edit poll section. +---------------------------------------------------- */ + +#edit_poll +{ + overflow: hidden; +} +#edit_poll fieldset +{ + margin: 0.5em 0; + border: 1px solid #c4c4c4; + padding: 0.5em; + clear: both; + overflow: hidden; +} +#edit_poll legend +{ + font-weight: bold; + color: #000; +} +#edit_poll fieldset input +{ + margin-left: 8.6em; +} +#edit_poll ul.poll_main li +{ + padding-left: 1em; +} +#edit_poll ul.poll_main input +{ + margin-left: 1em; +} +#edit_poll ul.poll_main, dl.poll_options +{ + overflow: hidden; + padding: 0 0 .7em .7em; + list-style: none; +} +#edit_poll ul.poll_main li +{ + margin: 0.2em 0; +} +#edit_poll dl.poll_options dt +{ + width: 33%; + padding: 0 0 0 1em; +} +#edit_poll dl.poll_options dd +{ + width: 65%; +} +#edit_poll dl.poll_options dd input +{ + margin-left: 0; +} + + +/* Styles for the recent messages section. +---------------------------------------------------- */ + +.readbuttons +{ + clear: both; + width: 100%; +} +.buttonlist, .buttonlist_bottom +{ + margin-right: 1em; + float: right; +} + +/* Styles for the move topic section. +---------------------------------------------------- */ + +#move_topic dl +{ + margin-bottom: 0; +} +.move_topic +{ + width: 710px; + margin: auto; + text-align: left; +} +div.move_topic fieldset +{ + margin: 0.5em 0; + border: 1px solid #cacdd3; + padding: 0.5em; +} + +/* Styles for the send topic section. +---------------------------------------------------- */ + +fieldset.send_topic +{ + margin-bottom: 0.5em; + border: none; + padding: 0.5em; +} +dl.send_topic +{ + margin-bottom: 0; +} +dl.send_mail dt +{ + width: 35%; +} +dl.send_mail dd +{ + width: 64%; +} + +/* Styles for the split topic section. +---------------------------------------------------- */ + +div#selected, div#not_selected +{ + width: 49%; +} +ul.split_messages li.windowbg, ul.split_messages li.windowbg2 +{ + border: 1px solid #adadad; + padding: 1em; + margin: 1px; +} +ul.split_messages li a.split_icon +{ + padding: 0 0.5em; +} +ul.split_messages div.post +{ + padding: 1em 0 0 0; + border-top: 1px solid #fff; +} + +/* Styles for the report topic section. +---------------------------------------------------- */ + +#report_topic dl +{ + margin-bottom: 0; +} +#report_topic dl.settings dt +{ + width: 20%; +} +#report_topic dl.settings dd +{ + width: 79%; +} + +/* Styles for the merge topic section. +---------------------------------------------------- */ + +ul.merge_topics li +{ + list-style-type: none; +} +dl.merge_topic dt +{ + width: 25%; +} +dl.merge_topic dd +{ + width: 74%; +} +fieldset.merge_options +{ + margin-bottom: 0.5em; +} +fieldset.merge_options legend +{ + font-weight: bold; +} +.custom_subject +{ + margin: 0.5em 0; +} + +/* Styles for the login areas. +------------------------------------------------------- */ +.login +{ + width: 540px; + margin: 0 auto; +} +.login dl +{ + overflow: auto; + clear: right; +} +.login dt, .login dd +{ + margin: 0 0 0.4em 0; + width: 44%; + padding: 0.1em; +} +.login dt +{ + float: left; + clear: both; + text-align: right; + font-weight: bold; +} +.login dd +{ + width: 54%; + float: right; + text-align: left; +} +.login p +{ + text-align: center; +} +.login h3 img +{ + float: left; + margin: 4px 0.5em 0 0; +} + +/* Styles for the registration section. +------------------------------------------------------- */ +.register_error +{ + border: 1px dashed red; + padding: 5px; + margin: 0 1ex 1ex 1ex; +} +.register_error span +{ + text-decoration: underline; +} + +/* Additional profile fields */ +dl.register_form +{ + margin: 0; + clear: right; + overflow: auto; +} + +dl.register_form dt +{ + font-weight: normal; + float: left; + clear: both; + width: 50%; + margin: 0.5em 0 0 0; +} + +dl.register_form dt strong +{ + font-weight: bold; +} + +dl.register_form dt span +{ + display: block; +} + +dl.register_form dd +{ + float: left; + width: 49%; + margin: 0.5em 0 0 0; +} + +#confirm_buttons +{ + text-align: center; + padding: 1em 0; +} + +.coppa_contact +{ + padding: 4px; + width: 32ex; + background-color: #fff; + color: #000; + margin-left: 5ex; + border: 1px solid #000; +} + +.valid_input +{ + background-color: #f5fff0; +} +.invalid_input +{ + background-color: #fff0f0; +} + +/* Styles for maintenance mode. +------------------------------------------------------- */ +#maintenance_mode +{ + width: 75%; + min-width: 520px; + text-align: left; +} +#maintenance_mode img.floatleft +{ + margin-right: 1em; +} + +/* common for all admin sections */ +h3.titlebg img +{ + vertical-align: middle; + margin-right: 0.5em; +} +tr.titlebg td +{ + padding-left: 0.7em; +} +#admin_menu +{ + min-height: 2em; + padding-left: 0; +} +#admin_content +{ + clear: left; +} +#admin_login .centertext +{ + padding: 1em; +} +#admin_login .centertext .error +{ + padding: 0 0 1em 0; +} + +/* Styles for sidebar menus. +------------------------------------------------------- */ +.left_admmenu, .left_admmenu ul, .left_admmenu li +{ + padding: 0; + margin: 0; + list-style: none; +} +#left_admsection +{ + background-color: #ecedf3; + padding: 1px; + border: 1px solid #ADADAD; + width: 160px; + float: left; + margin-right: 10px; +} +.adm_section h4.titlebg +{ + font-size: 95%; + margin-bottom: 5px; +} +#main_container +{ + position: relative; +} +.left_admmenu li +{ + padding: 0 0 0 0.5em; +} +.left_admmenu +{ + margin-bottom: 1.1em; +} +#main_admsection +{ + position: relative; + left: 0; + right: 0; + overflow: hidden; +} + +tr.windowbg td, tr.windowbg2 td, tr.approvebg td, tr.highlight2 td +{ + padding: 0.3em 0.7em; +} +#credits p +{ + padding: 0; + font-style: italic; + margin: 0; +} + +/* Styles for generic tables. +------------------------------------------------------- */ +.topic_table table +{ + width: 100%; +} +.topic_table .icon1, .topic_table .icon2, .topic_table .stats +{ + text-align: center; +} +#topic_icons +{ + margin-top: 1em; +} +#topic_icons .description +{ + margin: 0; +} +.topic_table table thead +{ + border-bottom: 1px solid #fff; +} +/* the subject column */ +.topic_table td +{ + font-size: 1em; +} +.topic_table td.subject +{ + padding: 4px; +} +.topic_table td.subject p, .topic_table td.stats, .topic_table td.lastpost +{ + font-size: 0.85em; + padding: 0; + margin: 0; +} +.topic_table td.lastpost, .topic_table td.lastpost +{ + font-size: 0.9em; + line-height: 100%; + padding: 4px; +} +.topic_table td.stickybg2 +{ + background-image: url(../images/icons/quick_sticky.gif); + background-repeat: no-repeat; + background-position: 98% 4px; +} +.topic_table td.lockedbg2 +{ + background-image: url(../images/icons/quick_lock.gif); + background-repeat: no-repeat; + background-position: 98% 4px; +} +.topic_table td.lastpost +{ + background-image: none; +} + +/* Styles for (fatal) errors. +------------------------------------------------- */ + +#fatal_error +{ + border: 1px solid #aaa; +} + +.errorbox +{ + padding: 1em; + border: 1px solid #cc3344; + color: #000; + background-color: #ffe4e9; + margin: 1em 0; +} +.errorbox h3 +{ + padding: 0; + margin: 0; + font-size: 1.1em; + text-decoration: underline; +} +.errorbox p +{ + margin: 1em 0 0 0; +} +.errorbox p.alert +{ + padding: 0; + margin: 0; + float: left; + width: 1em; + font-size: 1.5em; +} + +/* Styles for the profile section. +------------------------------------------------- */ + +dl +{ + overflow: auto; + margin: 0; + padding: 0; +} + +/* Fixes for the core theme */ +#profileview +{ + padding: 1px; + border: 1px solid #696969; + background-color: #ecedf3; +} +#profileview .content +{ + border: none; +} +#basicinfo .content +{ + padding: 1em; +} +#detailedinfo .content +{ + padding: 0.7em 1.2em; + border-left: 1px solid #aaa; +} + +/* The basic user info on the left */ +#basicinfo +{ + width: 20%; + float: left; +} +#detailedinfo +{ + width: 78%; + float: right; +} +#basicinfo h4 +{ + font-size: 135%; + font-weight: 100; + line-height: 105%; + white-space: pre-wrap; /* css-2.1 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ + overflow: hidden; +} +#basicinfo h4 span.position +{ + font-size: 80%; + font-weight: 100; + display: block; +} +#basicinfo img.avatar +{ + display: block; + margin: 10px 0 0 0; +} +#basicinfo ul +{ + list-style-type: none; + margin: 10px 0 0 0; +} +#basicinfo ul li +{ + display: block; + float: left; + margin-right: 5px; + height: 20px; +} +#basicinfo span#userstatus +{ + display: block; + clear: both; +} +#basicinfo span#userstatus img +{ + vertical-align: middle; +} +#detailedinfo div.content dl, #tracking div.content dl +{ + clear: right; + overflow: auto; + margin: 0 0 18px 0; + padding: 0 0 15px 0; + border-bottom: 1px solid #ccc; +} +#detailedinfo div.content dt, #tracking div.content dt +{ + width: 30%; + float: left; + margin: 0 0 3px 0; + padding: 0; + font-weight: bold; + clear: both; +} +#detailedinfo div.content dd, #tracking div.content dd +{ + width: 70%; + float: left; + margin: 0 0 3px 0; + padding: 0; +} +#detailedinfo div.content dl.noborder +{ + border-bottom: 0; +} +#detailedinfo div.content dt.clear +{ + width: 100%; +} +.signature, .custom_fields_above_signature, .attachments +{ + width: 98%; + overflow: auto; + clear: right; + border-top: 1px solid #666; +} +.signature h5 +{ + font-size: 100%; + margin-bottom: 10px; +} +#personal_picture +{ + display: block; + margin-bottom: 0.3em; +} +#avatar_server_stored div +{ + float: left; +} + +#main_admsection #basicinfo, #main_admsection #detailedinfo +{ + width: 100%; +} +#main_admsection #detailedinfo .content +{ + border: none !important; +} +#main_admsection #basicinfo +{ + border-bottom: 1px solid #ccc; +} +#main_admsection #basicinfo h4 +{ + float: left; +} +#main_admsection #basicinfo img.avatar +{ + float: right; + vertical-align: top; +} +#main_admsection #basicinfo ul +{ + clear: left; + padding-top: 10px; +} +#main_admsection #basicinfo span#userstatus +{ + clear: left; +} +#main_admsection #basicinfo p#infolinks +{ + display: none; + clear: both; +} +#main_admsection #basicinfo .botslice +{ + clear: both; +} + +/* Simple feedback messages */ +div#profile_error, div#profile_success +{ + margin: 0 0 1em 0; + padding: 1em 2em; + border: 1px solid; +} +div#profile_error +{ + border-color: red; + color: red; + background: #fee; +} + +div#profile_error span +{ + text-decoration: underline; +} + +div#profile_success +{ + border-color: green; + color: green; + background: #efe; +} + +/* Profile statistics */ +#generalstats div.content dt +{ + width: 50%; + float: left; + margin: 0 0 3px 0; + padding: 0; + font-weight: bold; + clear: both; +} +#generalstats div.content dd +{ + width: 50%; + float: left; + margin: 0 0 3px 0; + padding: 0; +} + +/* Activity by time */ +.activity_stats +{ + margin: 0; + padding: 0; + list-style: none; +} +.activity_stats li +{ + width: 4.16%; + float: left; +} +.activity_stats li span +{ + display: block; + border: solid #000; + border-width: 1px 1px 0 0; + text-align: center; +} +.activity_stats li.last span +{ + border-right: none; +} +.activity_stats li div.bar +{ + margin: 0 auto; + width: 15px; +} +.activity_stats li div.bar div +{ + background: url('../images/bar.gif'); +} +.activity_stats li div.bar span +{ + position: absolute; + top: -1000em; + left: -1000em; +} + +/* Most popular boards by posts and activity */ +#popularposts +{ + width: 50%; + float: left; +} +#popularactivity +{ + width: 50%; + float: right; +} + +#popularposts div.content dt, #popularactivity div.content dt +{ + width: 65%; + float: left; + margin: 0 0 3px 0; + padding: 0; + font-weight: bold; + clear: both; +} +#popularposts div.content dd, #popularactivity div.content dd +{ + width: 35%; + float: left; + margin: 0 0 3px 0; + padding: 0; +} + +.profile_pie +{ + background-image: url(../images/stats_pie.png); + float: left; + height: 20px; + width: 20px; + margin: 0 1em 0 0; + padding: 0; + text-indent: -1000em; +} + +/* View posts */ +.time +{ + float: right; +} +.counter +{ + margin: 0 0 0 0; + padding: 0.2em 0.5em 0.1em 0.2em; + font-size: 2.2em; + font-weight: bold; + color: #354c5f; + float: left; +} +.list_posts +{ + border-top: 1px solid #adadad; + padding-top: 1em; + margin-top: 0.5em; +} +div.core_posts +{ + border: 1px solid #adadad; + margin-bottom: 3px; +} +div.core_posts div.content +{ + background: none; + border: none; +} +.topic h4 +{ + margin: 3px 0; +} + +.mod_icons +{ + text-align: right; + margin-right: 1em; +} +#permissions div.tborder +{ + margin-bottom: 2em; +} +#permissions ul +{ + padding: 0; + margin: 1px 0 0 0; + border-top: 1px solid #e5e5e8; + float: left; + width: 100%; +} +#permissions div.permission_name +{ + width: 48%; + list-style: none; + border-right: 1px solid #e5e5e8; + background: #ecedf3; + margin: 0 1% 0 0; + padding: 0.7em 0.7em 0.8em 0.7em; + line-height: 1em; +} +#permissions li +{ + width: 100%; + padding: 0; + list-style: none; + margin: 0 0 1px 0; +} +#permissions li span.permission_status, #permissions li span.alert +{ + line-height: 2.9em; + font-size: 0.85em; +} + +#tracking div.content dl +{ + border-bottom: 0; + margin: 0; + padding: 0; +} + +#creator dl +{ + margin: 0; +} +#creator dt +{ + width: 40%; + float: left; + clear: both; + margin: 0 0 10px 0; +} +#creator dd +{ + float: left; + width: 60%; + margin: 0 0 10px 0; + overflow: auto; +} + +.ignoreboards +{ + margin: 0 2%; + padding: 0; + width: 45%; +} +.ignoreboards a +{ + font-weight: bold; + text-decoration: none; + border-bottom: 1px solid #c4c4c4; + padding: 0.1em 0; +} +.ignoreboards a:hover +{ + text-decoration: none; + border-bottom: 1px solid #476c8e; +} +.ignoreboards ul +{ + margin: 0; + padding: 0; +} +.ignoreboards li +{ + list-style: none; + float: left; + clear: both; +} +.ignoreboards li.category +{ + margin: 0.7em 0 0 0; + width: 100%; +} +.ignoreboards li ul +{ + margin: 0.2em 0 0 0; +} +.ignoreboards li.category ul li.board +{ + width: 93%; +} + +#theme_settings +{ + overflow: auto; + margin: 0; + padding: 0; +} + +#theme_settings li +{ + list-style: none; + margin: 10px 0; + padding: 0; +} +/*Paid Subscriptions*/ +#paid_subscription +{ + width: 100%; +} +#paid_subscription dl.settings +{ + margin-bottom: 0; +} +#paid_subscription dl.settings dd, #paid_subscription dl.settings dt +{ + margin-bottom: 4px; +} +/*pick theme*/ +#pick_theme +{ + width: 100%; + float: left; +} +/*Issue a warning*/ +#warn_body{ + width: 80%; + font-size: 0.9em; +} + +/* Styles for the statistics center. +------------------------------------------------- */ +#statistics +{ + padding-bottom: 0.5em; +} +#statistics h4.titlebg +{ + text-align: center; + margin-bottom: 5px; +} +#stats_left, #top_posters, #top_topics_replies, #top_topics_starter +{ + float: left; + width: 49.5%; +} +#stats_right, #top_boards, #top_topics_views, #most_online +{ + float: right; + width: 49.5%; +} +dl.stats +{ + clear: both; + overflow: hidden; + margin: 0; + padding: 0; +} +dl.stats dt +{ + width: 49%; + float: left; + margin: 0 0 4px 0; + line-height: 16px; + padding: 0; + clear: both; + font-size: 1em; +} +dl.stats dd +{ + text-align: right; + width: 50%; + font-size: 1em; + float: right; + margin: 0 0 4px 0; + line-height: 16px; + padding: 0; +} +.stats_bar +{ + float: left; + background-image: url(../images/bar_stats.png); + height: 16px; + font-size: 0.9em; + display: block; + text-align: left; + color: #fff; + font-weight: bold; + background-position: top center; +} +.stats_bar span +{ + padding-left: 2px; +} + +/* Styles for the personal messages section. +------------------------------------------------- */ + +#personal_messages +{ + padding: 1px; +} +#personal_messages #top_subject +{ + padding-left: 11.75em !important; +} +#personal_messages div.labels +{ + padding: 0 1em 0 0; +} +#personal_messages .capacity_bar +{ + background: #fff; + border: 1px solid #000; + height: 7px; + width: 75%; + margin: 0 auto; +} +#personal_messages .capacity_bar div +{ + border: none; + height: 7px; +} +#personal_messages .capacity_bar div.empty +{ + background: #468008; +} +#personal_messages .capacity_bar div.filled +{ + background: #EEA800; +} +#personal_messages .capacity_bar div.full +{ + background: #A53D05; +} +#personal_messages .reportlinks +{ + padding: 0.5em 1.3em; +} +#searchLabelsExpand li +{ + padding: 0.3em 0.5em; +} + +/* Styles for the calendar section. +------------------------------------------------- */ +.calendar_table +{ + margin-bottom: 0.7em; +} + +/* Used to indicate the current day in the grid. */ +.calendar_today +{ + background-color: #fff; +} + +#month_grid +{ + width: 200px; + text-align: center; + float: left; +} + +#month_grid table +{ + width: 200px; + border-collapse: collapse; + border: 1px solid #adadad; +} + +#month_grid table td, #month_grid table th +{ + border: 1px solid #adadad; +} + +#main_grid table +{ + width: 100%; + padding-bottom: 4px; + border-collapse: collapse; + border: 1px solid #adadad; +} + +#main_grid table td, #main_grid table th +{ + border: 1px solid #adadad; +} + +#main_grid table h3.catbg +{ + text-align: center; + + border-top: 1px solid #adadad; + border-bottom: none; +} + +#main_grid table h4 +{ + border: none; +} + +#main_grid table.weeklist td.windowbg +{ + text-align: center; + height: 49px; + width: 25px; + font-size: large; + padding: 0 7px; + border-bottom: 1px solid #adadad; +} + +#main_grid table.weeklist td.weekdays +{ + height: 49px; + width: 100%; + padding: 4px; + text-align: left; + vertical-align: middle; + border-right: 1px solid #adadad; + border-bottom: 1px solid #adadad; +} + +#main_grid h3.weekly +{ + text-align: center; + padding-left: 0; + font-size: large; + height: 29px; +} + +#main_grid h3 span.floatleft, #main_grid h3 span.floatright +{ + display: block; + +} + +#main_grid table th.days +{ + width: 14%; +} + +#main_grid table td.weeks +{ + vertical-align: middle; + text-align: center; +} + +#main_grid table td.days +{ + vertical-align: top; + +} + +a.modify_event +{ + color: red; +} + +span.hidelink +{ + font-style: italic; +} + +#calendar_navigation +{ + text-align: center; +} + +#calendar .buttonlist_bottom +{ + border-bottom: 1px solid #adadad; + padding: 0 0 0 1ex; + margin: 0 0 1ex 0; +} + +/* Styles for the memberlist section. +------------------------------------------------- */ +#mlist_search +{ + margin: auto; + width: 400px; +} + +/* Styles for the basic search section. +------------------------------------------------- */ +#simple_search p +{ + padding: 0.5em; +} +#simple_search, #simple_search p, #advanced_search +{ + text-align: center !important; + margin: 0; +} +#search_error +{ + font-style: italic; + padding: 0.3em 1em; +} +#search_term_input +{ + font-size: 115%; + margin: 0 0 1em; +} + +/* Styles for the advanced search section. +------------------------------------------------- */ +#searchform fieldset +{ + text-align: left; + padding: 0; + margin: 0; + border: none; +} +fieldset#advanced_search .roundframe +{ + border-bottom: none !important; +} +#advanced_search dl#search_options +{ + margin: 0 auto; + width: 600px; + padding-top: 1em; + overflow: hidden; +} +#advanced_search dt +{ + clear: both; + float: left; + padding: 0.2em; + text-align: right; + width: 20%; +} +#advanced_search dd +{ + width: 75%; + float: left; + padding: 0.2em; + margin: 0 0 0 0.5em; + text-align: left; +} +#searchform p.clear +{ + clear: both; +} + +/* Styles for the search results page. +------------------------------------------------- */ +.pagelinks +{ + padding: 0.5em; +} +.topic_table td blockquote, .topic_table td .quoteheader +{ + margin: 0.5em; +} +.search_results_posts +{ + overflow: hidden; +} +.search_results_posts .inner +{ + padding: 0.5em 1em; + overflow: hidden; +} +.search_results_posts .windowbg2 +{ + margin-top: 4px; +} +.search_results_posts .buttons +{ + padding: 5px 1em 0 0; +} + +/* Styles for the help section. +------------------------------------------------- */ + +#helpmain +{ + padding: 1em; + border: 1px solid #696969; +} +#helpmain p +{ + margin: 0 0 1.5em 0; + line-height: 1.5em; +} +#helpmain ul +{ + line-height: 1.5em; +} + +/* Depreciated stuff from the old days. +------------------------------------------------- */ + +/* This style will make sure all headers use the same padding throughout. */ +.headerpadding +{ + padding: 0.5em; +} +/* smaller padding used in paragraphs, sections etc */ +.smallpadding +{ + padding: 0.2em; +} +/* larger padding used in paragraphs, sections etc */ +.largepadding +{ + padding: 0.7em; +} + +/* A small space to the next section. */ +.marginbottom +{ + margin-bottom: 1em; +} +/* On the top too. */ +.margintop +{ + margin-top: 1em !important; +} +/* remove bold/italic styles */ +span.plainstyle +{ + font-weight: normal; + font-style: normal; +} +/* float a list horizontally */ +ul.horizlist +{ + width: 100%; +} +ul.horizlist li +{ + float: left; + padding: 0.2em 0.4em 0.2em 0.4em; + vertical-align: top; +} +/* make a inline-list */ +ul.nolist li +{ + display: inline; +} +/* Helping style to clear floated items. */ +.clearfix:after +{ + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; +} + +.clearfix +{ + display: inline-block; +} + +/* Hides from IE-mac. \*/ +* html .clearfix +{ + height: 1%; +} +.clearfix +{ + display: block; +} +/* End hide from IE-mac. */ + +/* This is used for tables that have a grid/border background color (such as the topic listing.) */ +.bordercolor +{ + background-color: #adadad; + padding: 0; +} + +/* This is used on tables that should just have a border around them. */ +.tborder +{ + padding: 1px; + border: 1px solid #696969; + background-color: #fff; +} +/* If some random peep decides to use a description class within a tborder (happened to me!) */ +.tborder .description +{ + margin-bottom: 0; +} + +/* Styles for print media. +------------------------------------------------------- */ +@media print +{ + #headerarea + { + display: none; + } + + .tborder + { + border: none; + } +} \ No newline at end of file diff --git a/Themes/core/css/index.php b/Themes/core/css/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/core/css/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/core/css/rtl.css b/Themes/core/css/rtl.css new file mode 100644 index 0000000..a82ee53 --- /dev/null +++ b/Themes/core/css/rtl.css @@ -0,0 +1,1071 @@ +/* Common classes to ease styling. +------------------------------------------------------- */ + +.floatright +{ + float: left; +} +.floatleft +{ + float: right; +} +.clear_left +{ + clear: right; +} +.clear_right +{ + clear: left; +} +.righttext +{ + text-align: left; +} +.lefttext +{ + text-align: right; +} + +/* GenericList */ +.additional_row input +{ + margin-left: 1em; +} + +/* Lists with settings use these a lot. +------------------------------------------------------- */ +dl.settings dt +{ + float: right; +} +dl.settings dt.windowbg +{ + float: right; +} +dl.settings dd +{ + float: right; +} +dl.settings img +{ + margin: 0 0 0 10px; +} + +/* All the signatures used in the forum. If your forum users use Mozilla, Opera, or Safari, you might add max-height here ;). */ +.signature +{ + clear: left; +} +.custom_fields_above_signature +{ + clear: left; +} + +/* Styles for the standard dropdown menus. +------------------------------------------------------- */ +/* Container for the new admin menu */ +#adm_container +{ + float: right; + margin-left: 0; + margin-right: 10px; +} +.main_menu li +{ + float: right; +} + +/* the linktree */ +ul.linktree li +{ + float: right; + padding: 0 0 0 0.5em; +} + +/* First layer of menu items */ +ul.admin_menu li +{ + float: right !important; + padding-left: 4px; +} +ul.admin_menu li ul +{ + right: 0; +} +ul.admin_menu li ul li a +{ + padding: 0.5em 0.5em 0.5em 2em; +} +/* Second layer of menu items */ +ul.admin_menu li ul +{ + border-left: 1px solid #808080; + border-right: 2px solid #6888a7; +} +/* Third layer of menu items */ +ul.admin_menu li ul li:hover ul, ul.admin_menu li ul li.over ul +{ + /* now a very tricky stuff, never seen before ;-) */ + /*IE and Firefox like it right */ + right: 19em; + /*Opera needs left*/ + left: -19.2em; + top: auto; + margin: -2em 0 0 0; + border-left: 1px solid #808080; + border-right: 2px solid #6888a7; +} +ul.admin_menu li ul li a.subsection +{ + background-image: url(../images/admin/subsection2.gif); + background-position: 2% 50%; +} +/* The dropdown menu toggle image */ +div#menu_toggle +{ + float: left; + margin: 0 0 0 10px; +} +/* Styles for the standard button lists. +------------------------------------------------------- */ +/* The old-style button strips, with images */ +.oldbuttonlist +{ + text-align: left; +} + +/* Styles for the general looks for the Core theme. +------------------------------------------------------- */ +#user_section #myavatar +{ + margin: 0 0 0 0.5em; + border-right: none; + border-left: 1px solid #adadad; + float: right; +} +#footerarea p +{ + text-align: right; + padding-right: 0.5em; +} + +/* Styles for headers. +------------------------------------------------------- */ + +h3.catbg img.icon, div.titlebg img.icon, h3.catbg img +{ + float: right; + margin: 5px 0 0 8px; +} + +/* Styles for the general looks for the Core theme. +------------------------------------------------------- */ + +.main_menu ul +{ + padding-right: 1em; + float: right; +} +.main_menu +{ + overflow: hidden; +} + +/* Styles for the board index. +------------------------------------------------- */ + +p#stats +{ + text-align: left; +} +#upshrink_ic +{ + margin-right: 0; + margin-left: 2ex; + text-align: left; +} + +#posticons .buttonlist +{ + margin-right: 0; + margin-left: 1em; + float: left; +} + +/* Styles for the info center on the board index. +---------------------------------------------------- */ + +#infocenterframe +{ + margin-top: 2em; + clear: both; +} +/* each section in infocenter has this class */ +.infocenter_section p.section +{ + float: right; +} +.infocenter_section div.sectionbody +{ + margin-left: 0; + margin-right: 30px; + border-left: none; + border-right: 1px solid #a0a0a0; +} +/* recent posts - or just one recent post */ +dl#infocenter_recentposts +{ + float: right; +} +dl#infocenter_recentposts dt +{ + clear: right; + float: right; +} +dl#infocenter_recentposts dd +{ + clear: left; + float: left; + text-align: left; +} + +/* Styles for the message (topic) index. +---------------------------------------------------- */ +.buttonlist, .buttonlist_bottom +{ + margin-left: 0; + margin-right: 1em; + float: left; +} +#message_index_jump_to +{ + margin: 2em 2em 0 4em; +} +.lastpost img +{ + float: left; +} +/* Styles for the display template (topic view). +---------------------------------------------------- */ +/* a smaller quick-button list */ +ul.quickbuttons +{ + margin: 0.9em 0 0 11px; + clear: left; + float: left; + text-align: left; +} +ul.quickbuttons li +{ + float: left; + margin: 0 11px 0 0; +} +ul.quickbuttons li a +{ + padding: 0 20px 0.7em 0; + float: left; +} +ul.quickbuttons li.quote_button +{ + background: url(../images/buttons/quote.gif) no-repeat 100% 0; +} +ul.quickbuttons li.remove_button +{ + background: url(../images/buttons/delete.gif) no-repeat 100% 0; +} +ul.quickbuttons li.modify_button +{ + background: url(../images/buttons/modify.gif) no-repeat 100% 0; +} +ul.quickbuttons li.approve_button +{ + background: url(../images/buttons/approve.gif) no-repeat 100% 0; +} +ul.quickbuttons li.restore_button +{ + background: url(../images/buttons/restore_topic.gif) no-repeat 100% 0; +} +ul.quickbuttons li.split_button +{ + background: url(../images/buttons/split.gif) no-repeat 100% 0; +} +ul.quickbuttons li.reply_button +{ + background: url(../images/buttons/reply.gif) no-repeat 100% 0; +} +ul.quickbuttons li.reply_all_button +{ + background: url(../images/buttons/reply.gif) no-repeat 100% 0; +} +ul.quickbuttons li.notify_button +{ + background: url(../images/buttons/notify_sm.gif) no-repeat 100% 0; +} +ul.quickbuttons li.inline_mod_check +{ + margin: 0 5px 0 0; +} +#moderationbuttons +{ + margin-left: 0; + margin-right: 0.5em; +} +#postbuttons .nav, #postbuttons_lower .nav +{ + margin: 0.5em 0 0 0.5em; + text-align: left; +} +#postbuttons_lower .nav +{ + margin: 0 0.5em 0.5em 0; +} +#postbuttons, #postbuttons_lower +{ + text-align: right; +} +/* Poll question */ +h4#pollquestion +{ + padding: 1em 2em 1em 0; +} +#poll_options div.submitbutton +{ + clear: both; + padding: 0 2em 1em 0; +} +/* Poll results */ +#poll_options dl.options +{ + padding: 1em 2em 0 2.5em; +} +div#pollmoderation +{ + margin: -1em 2em 0 0; +} +#forumposts h3.catbg3 img +{ + float: right; +} +#forumposts h3.catbg3 span +{ + float: right; + padding-left: 0; + padding-right: 2%; +} +#forumposts h3.catbg3 span#top_subject +{ + padding-left: 0; + padding-right: 9.5em; +} +.poster +{ + float: right; +} +.post +{ + clear: left; + float: right; +} +.postarea +{ + margin-left: 0; + margin-right: 16em; +} +.messageicon +{ + float: right; +} +.keyinfo +{ + float: right; +} +ul.postingbuttons +{ + float: left; + padding: 0 0 0 0.5em; +} +ul.postingbuttons li +{ + float: right; + margin: 0 0 0 0.5em; +} +.modifybutton +{ + float: left; + margin: 0 0 0.5em 0.5em; +} +.postfooter +{ + margin-left: 0; + margin-right: 16em; +} +.moderatorbar +{ + clear: left; + margin: 1em 16em 0 0; +} +#pollmoderation, #moderationbuttons_strip +{ + float: right; +} + +/* Styles for the quick reply area. +---------------------------------------------------- */ + +#quickReplyOptions #quickReplyWarning +{ + text-align: right; + float: right; +} +#quickReplyOptions #quickReplyContent +{ + text-align: left; + float: left; + border-left: none; + border-right: 1px solid #aaa; +} +#quickReplyWarning +{ + float: right; +} + +/* Styles for edit post section +---------------------------------------------------- */ +#post_header dt +{ + float: right; +} +#post_header dd +{ + float: right; +} +ul.post_options +{ + margin: 0 1em 0 0; +} +ul.post_options li +{ + float: right; +} +#postAttachment dd, #postAttachment2 dd +{ + margin: .3em 1em .3em 0; +} +#postAttachment dt, #postAttachment2 dt +{ + font-weight: bold; +} +#postAttachment3 +{ + margin-left: 0; + margin-left: 1em; +} +.post_verification #verification_control +{ + margin: .3em 1em .3em 0; +} + +/* Styles for edit event section +---------------------------------------------------- */ +#post_event div.event_options +{ + float: left; +} +#post_event #event_main input +{ + margin: 0 0 1em 0; + float: right; +} +#post_event #event_main div.smalltext +{ + float: left; +} +#post_event ul.event_main li +{ + float: left; +} +#post_event ul.event_options +{ + padding: 0 .7em .7em 0; +} +#post_event #event_main select, #post_event ul.event_options li select, #post_event ul.event_options li .input_check +{ + margin: 0 0 0 1em; +} + +/* Styles for edit poll section. +---------------------------------------------------- */ +#edit_poll fieldset input +{ + margin-right: 7em; +} +#edit_poll ul.poll_main li +{ + padding-right: 1em; +} +#edit_poll ul.poll_main input +{ + margin-right: 1em; +} +#edit_poll div.poll_options +{ + float: right; +} +#edit_poll ul.poll_main, dl.poll_options +{ + padding: 0 .7em 0 0; +} +#edit_poll dl.poll_options dt +{ + padding: 0 1em 0 0; +} +#edit_poll dl.poll_options dd input +{ + margin-right: 0; +} + +/* Styles for the recent messages section. +---------------------------------------------------- */ +.readbuttons .buttonlist, .readbuttons .buttonlist_bottom +{ + margin-right: 0; + margin-left: 1em; + float: left; +} + +/* Styles for the move topic section. +---------------------------------------------------- */ +.move_topic +{ + text-align: right; +} + +/* Styles for the login areas. +------------------------------------------------------- */ +.login dt +{ + float: right; + text-align: left; +} +.login dd +{ + float: left; + text-align: right; +} +.login h3 img +{ + float: right; + margin: 4px 0 0 0.5em; +} + +/* Styles for the registration section. +------------------------------------------------------- */ +dl.register_form +{ + clear: left; +} + +dl.register_form dt +{ + float: right; + clear: both; +} +dl.register_form dd +{ + float: right; +} + +/* Styles for maintenance mode. +------------------------------------------------------- */ +#maintenance_mode +{ + text-align: right; +} +#maintenance_mode img.floatleft +{ + margin-right: 0; + margin-left: 1em; +} + +h3.titlebg img +{ + margin-right: 0; + margin-left: 0.5em; +} +tr.titlebg td +{ + padding-left: 0.7em; + padding-right: 0.7em; +} +#admin_menu +{ + padding-right: 0; +} +#admin_content +{ + clear: right; +} + +/* Styles for sidebar menus. +------------------------------------------------------- */ +#left_admsection +{ + float: right; + margin-right: 0; + margin-left: 10px; +} +.left_admmenu li +{ + padding: 0 0.5em 0 0; +} + +/* Styles for generic tables. +------------------------------------------------------- */ +.topic_table td.stickybg2 +{ + background-position: 2% 4px; +} +.topic_table td.lockedbg2 +{ + background-position: 2% 4px; +} + +/* Styles for (fatal) errors. +------------------------------------------------- */ +.errorbox p.alert +{ + float: right; +} + +/* Styles for the profile section. +------------------------------------------------- */ +#profileview #detailedinfo .content +{ + border-left: none; + border-right: 1px solid #aaa; +} +/* The basic user info on the left */ +#basicinfo +{ + float: right; +} +#profileview #basicinfo .content +{ + padding: 1em; +} +#detailedinfo +{ + float: left; +} +#basicinfo ul li +{ + float: right; + margin-left: 5px; + margin-right: 0; +} +#detailedinfo div.content dl, #tracking div.content dl +{ + clear: left; +} +#detailedinfo div.content dt, #tracking div.content dt +{ + float: right; +} +#detailedinfo div.content dd, #tracking div.content dd +{ + float: right; +} +.signature, .custom_fields_above_signature, .attachments +{ + clear: left; +} +#avatar_server_stored div +{ + float: right; +} +#main_admsection #basicinfo h4 +{ + float: right; +} +#main_admsection #basicinfo img.avatar +{ + float: left; +} +#main_admsection #basicinfo ul +{ + clear: right; +} +#main_admsection #basicinfo span#userstatus +{ + clear: right; +} + + +/* Profile statistics */ +#generalstats div.content dt +{ + float: right; +} +#generalstats div.content dd +{ + float: right; +} + +/* Activity by time */ +#activitytime +{ + clear: right; +} +.activity_stats li +{ + float: right; +} +.activity_stats li span +{ + border-width: 1px 0 0 1px; +} +.activity_stats li.last span +{ + border-left: none; +} + +/* Most popular boards by posts and activity */ +#popularposts +{ + float: right; +} +#popularactivity +{ + float: left; +} + +#popularposts div.content dt, #popularactivity div.content dt +{ + float: right; +} +#popularposts div.content dd, #popularactivity div.content dd +{ + float: right; +} + +.profile_pie +{ + background-image: url(../images/stats_pie_rtl.png); + float: right; + margin-right: 0; + margin-left: 1em; +} + +/* View posts */ +.time +{ + float: left; +} +.counter +{ + padding: 0.2em 0.2em 0.1em 0.5em; + float: right; +} +.mod_icons +{ + text-align: left; + margin-right: 0; + margin-left: 1em; +} +#permissions div.permission_name +{ + margin: 0 0 0 1%; +} +#ip_list li.header, #ip_list li.ip +{ + float: right; +} +#creator dt +{ + float: right; +} +#creator dd +{ + float: right; +} + +.ignoreboards ul +{ + margin: 0 1em 0 0; +} +.ignoreboards li +{ + float: right; +} + +#pick_theme +{ + float: right; +} + +/* Styles for the statistics center. +------------------------------------------------- */ +#stats_left, #top_posters, #top_topics_replies, #top_topics_starter +{ + float: right; +} +#stats_right, #top_boards, #top_topics_views, #most_online +{ + float: left; +} +dl.stats dt +{ + float: right; +} +dl.stats dd +{ + text-align: left; + float: left; +} +.stats_bar +{ + float: right; +} + +/* Styles for the personal messages section. +------------------------------------------------- */ + +#personal_messages #top_subject +{ + padding-left: 0 !important; + padding-right: 11.75em !important; +} + +/* Styles for the calendar section. +------------------------------------------------- */ +#month_grid +{ + float: right; +} + +#main_grid table.weeklist td.weekdays +{ + text-align: right; + border-right: none; + border-left: 1px solid #adadad; +} + +#calendar .buttonlist_bottom +{ + padding: 0 1ex 0 0; +} + +/* Styles for the advanced search section. +------------------------------------------------- */ +#searchform fieldset +{ + text-align: right; +} +#advanced_search dt +{ + float: right; + text-align: left; +} +#advanced_search dd +{ + float: right; + margin: 0 0.5em 0 0; + text-align: right; +} +/* Boards picker */ +#searchform fieldset div#searchBoardsExpand ul +{ + margin: 0 1em 0 0; +} +#searchform fieldset div#searchBoardsExpand li +{ + float: right; +} +#searchform fieldset p +{ + text-align: right; +} + +/* Styles for the search results page. +------------------------------------------------- */ +.search_results_posts .buttons +{ + padding: 5px 0 0 1em; +} + +/* Styles for the help section. +------------------------------------------------- */ +#helpmain ol.la +{ + padding-right: 1.5em; + padding-left: 0; +} + +/* The admin menu +------------------------------------------------- */ + +ul.admin_menu li +{ + position: relative; + float: right; + background: url(../images/admintab_back.gif) top right repeat-x; + padding-right: 4px; +} +ul.admin_menu li.last +{ + background: url(../images/admintab_back.gif) top right repeat-x; +} +ul.admin_menu li.first +{ + background: url(../images/admintab_right.gif) top right repeat-x; +} +ul.admin_menu li.first.chosen h4 +{ + background: url(../images/admintab_active_last.gif) top right repeat-x; + padding-right: 16px; +} +ul.admin_menu li.chosen +{ + background: url(../images/admintab_active_left.gif) no-repeat; + padding: 0 0 0 6px; +} + +ul.admin_menu li.last.chosen h4 +{ + background: url(../images/admintab_active_right.gif) top right no-repeat; + padding-right: 17px; +} + +ul.admin_menu li.chosen h4 +{ + background: url(../images/admintab_active_right.gif) top right no-repeat; + padding-right: 10px; +} + +.main_menu li.last.active +{ + float: right; + background: url(../images/maintab_last.gif) no-repeat bottom right; + padding: 0 8px 0 0; +} +/* IE6 doesn't support multiple class selectors */ +.main_menu li.lastactive +{ + padding: 0 8px 0 0; + background: url(../images/maintab_last.gif) no-repeat bottom right; +} + +/* float a list horizontally */ +ul.horizlist li +{ + float: right; +} +.buttonlist, .buttonlist_bottom +{ + margin-left: 1.5em !important; +} + +.generic_tab_strip .buttonlist +{ + float: left !important; +} + +/* Styles for the admincenter (reverse admin.css). +------------------------------------------------- */ +#quick_search +{ + margin-left: 5px !important; +} +.features_image +{ + float: right !important; + margin: 0 1em 0.5em 2em !important; +} +.features_switch +{ + float: left !important; +} +.features h4 +{ + padding: 1em 0.5em 0.5em 0 !important; +} +/* admin home */ +#live_news div.content dl +{ + padding: 0.5em 0.5em 0 0 !important; +} +#smfAnnouncements dd +{ + padding: 0; + margin: 0 1.5em 1em 0 !important; +} +#quick_tasks li +{ + float: right; + list-style-type: none !important; +} +.home_image +{ + float: right !important; +} +/* common admin classes */ +.additional_row input +{ + margin-left: 0; + margin-right: 2em; +} +#error_log td div.marginleft +{ + margin: 0 1ex 0 0 !important; +} + +/* Styles for the package manager. +------------------------------------------------- */ +#package_list .tborder +{ + margin: .25em 26px .25em 0 !important; +} +#package_list ol, #package_list ol li +{ + margin-left: 0 !important; + margin-right: 50px !important; +} + +/*ManageBoards*/ +.move_links +{ + padding: 0 0 0 13px !important; +} + +span.search_weight +{ + text-align: left !important; +} + +/*Manage Bans*/ +.ban_restriction +{ + margin: 0.2em 2.2em 0.2em 0 !important; +} +/* Themes */ +.is_directory +{ + padding-right: 18px !important; + background-position: 100% 0 !important; +} + + /* Styles for the moderation center. +------------------------------------------------- */ +.modblock_left +{ + float: right !important; + clear: left !important; +} +.modblock_right +{ + float: left !important; +} +ul.moderation_notes li +{ + padding: 4px 4px 4px 0 !important; +} \ No newline at end of file diff --git a/Themes/core/css/webkit.css b/Themes/core/css/webkit.css new file mode 100644 index 0000000..d06b7a3 --- /dev/null +++ b/Themes/core/css/webkit.css @@ -0,0 +1,8 @@ +/* special styles for Safari (and other Webkit based browsers like Chrome) +Webkit needs this otherwise the post goes off to the right. +Causes issues in IE browsers in (and cached search engines pages it breaks them). +*/ +.postarea .post +{ + float: left; +} \ No newline at end of file diff --git a/Themes/core/images/Female.gif b/Themes/core/images/Female.gif new file mode 100644 index 0000000..321f2c9 Binary files /dev/null and b/Themes/core/images/Female.gif differ diff --git a/Themes/core/images/Male.gif b/Themes/core/images/Male.gif new file mode 100644 index 0000000..bc98e06 Binary files /dev/null and b/Themes/core/images/Male.gif differ diff --git a/Themes/core/images/admin/administration.gif b/Themes/core/images/admin/administration.gif new file mode 100644 index 0000000..c634703 Binary files /dev/null and b/Themes/core/images/admin/administration.gif differ diff --git a/Themes/core/images/admin/attachment.gif b/Themes/core/images/admin/attachment.gif new file mode 100644 index 0000000..506491f Binary files /dev/null and b/Themes/core/images/admin/attachment.gif differ diff --git a/Themes/core/images/admin/ban.gif b/Themes/core/images/admin/ban.gif new file mode 100644 index 0000000..3f5a497 Binary files /dev/null and b/Themes/core/images/admin/ban.gif differ diff --git a/Themes/core/images/admin/boards.gif b/Themes/core/images/admin/boards.gif new file mode 100644 index 0000000..2f3b303 Binary files /dev/null and b/Themes/core/images/admin/boards.gif differ diff --git a/Themes/core/images/admin/calendar.gif b/Themes/core/images/admin/calendar.gif new file mode 100644 index 0000000..66accbf Binary files /dev/null and b/Themes/core/images/admin/calendar.gif differ diff --git a/Themes/core/images/admin/change_menu.png b/Themes/core/images/admin/change_menu.png new file mode 100644 index 0000000..0952630 Binary files /dev/null and b/Themes/core/images/admin/change_menu.png differ diff --git a/Themes/core/images/admin/change_menu2.png b/Themes/core/images/admin/change_menu2.png new file mode 100644 index 0000000..e3b5b92 Binary files /dev/null and b/Themes/core/images/admin/change_menu2.png differ diff --git a/Themes/core/images/admin/corefeatures.gif b/Themes/core/images/admin/corefeatures.gif new file mode 100644 index 0000000..902a437 Binary files /dev/null and b/Themes/core/images/admin/corefeatures.gif differ diff --git a/Themes/core/images/admin/current_theme.gif b/Themes/core/images/admin/current_theme.gif new file mode 100644 index 0000000..b661fd3 Binary files /dev/null and b/Themes/core/images/admin/current_theme.gif differ diff --git a/Themes/core/images/admin/engines.gif b/Themes/core/images/admin/engines.gif new file mode 100644 index 0000000..1517f0e Binary files /dev/null and b/Themes/core/images/admin/engines.gif differ diff --git a/Themes/core/images/admin/feature_cd.png b/Themes/core/images/admin/feature_cd.png new file mode 100644 index 0000000..5bf830f Binary files /dev/null and b/Themes/core/images/admin/feature_cd.png differ diff --git a/Themes/core/images/admin/feature_cp.png b/Themes/core/images/admin/feature_cp.png new file mode 100644 index 0000000..9b93c6b Binary files /dev/null and b/Themes/core/images/admin/feature_cp.png differ diff --git a/Themes/core/images/admin/feature_k.png b/Themes/core/images/admin/feature_k.png new file mode 100644 index 0000000..7709462 Binary files /dev/null and b/Themes/core/images/admin/feature_k.png differ diff --git a/Themes/core/images/admin/feature_ml.png b/Themes/core/images/admin/feature_ml.png new file mode 100644 index 0000000..d124448 Binary files /dev/null and b/Themes/core/images/admin/feature_ml.png differ diff --git a/Themes/core/images/admin/feature_pm.png b/Themes/core/images/admin/feature_pm.png new file mode 100644 index 0000000..89725ad Binary files /dev/null and b/Themes/core/images/admin/feature_pm.png differ diff --git a/Themes/core/images/admin/feature_ps.png b/Themes/core/images/admin/feature_ps.png new file mode 100644 index 0000000..038cc85 Binary files /dev/null and b/Themes/core/images/admin/feature_ps.png differ diff --git a/Themes/core/images/admin/feature_rg.png b/Themes/core/images/admin/feature_rg.png new file mode 100644 index 0000000..4acb947 Binary files /dev/null and b/Themes/core/images/admin/feature_rg.png differ diff --git a/Themes/core/images/admin/feature_sp.png b/Themes/core/images/admin/feature_sp.png new file mode 100644 index 0000000..0d25f73 Binary files /dev/null and b/Themes/core/images/admin/feature_sp.png differ diff --git a/Themes/core/images/admin/feature_w.png b/Themes/core/images/admin/feature_w.png new file mode 100644 index 0000000..bec0b0a Binary files /dev/null and b/Themes/core/images/admin/feature_w.png differ diff --git a/Themes/core/images/admin/features.gif b/Themes/core/images/admin/features.gif new file mode 100644 index 0000000..a098a2d Binary files /dev/null and b/Themes/core/images/admin/features.gif differ diff --git a/Themes/core/images/admin/features_and_options.png b/Themes/core/images/admin/features_and_options.png new file mode 100644 index 0000000..f917e75 Binary files /dev/null and b/Themes/core/images/admin/features_and_options.png differ diff --git a/Themes/core/images/admin/forum_maintenance.png b/Themes/core/images/admin/forum_maintenance.png new file mode 100644 index 0000000..ed340b4 Binary files /dev/null and b/Themes/core/images/admin/forum_maintenance.png differ diff --git a/Themes/core/images/admin/ignore.gif b/Themes/core/images/admin/ignore.gif new file mode 100644 index 0000000..df68cca Binary files /dev/null and b/Themes/core/images/admin/ignore.gif differ diff --git a/Themes/core/images/admin/index.php b/Themes/core/images/admin/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/core/images/admin/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/core/images/admin/languages.gif b/Themes/core/images/admin/languages.gif new file mode 100644 index 0000000..0020f45 Binary files /dev/null and b/Themes/core/images/admin/languages.gif differ diff --git a/Themes/core/images/admin/logs.gif b/Themes/core/images/admin/logs.gif new file mode 100644 index 0000000..597a35b Binary files /dev/null and b/Themes/core/images/admin/logs.gif differ diff --git a/Themes/core/images/admin/mail.gif b/Themes/core/images/admin/mail.gif new file mode 100644 index 0000000..ba4f6cc Binary files /dev/null and b/Themes/core/images/admin/mail.gif differ diff --git a/Themes/core/images/admin/maintain.gif b/Themes/core/images/admin/maintain.gif new file mode 100644 index 0000000..68cb4f7 Binary files /dev/null and b/Themes/core/images/admin/maintain.gif differ diff --git a/Themes/core/images/admin/membergroups.gif b/Themes/core/images/admin/membergroups.gif new file mode 100644 index 0000000..ba8eb89 Binary files /dev/null and b/Themes/core/images/admin/membergroups.gif differ diff --git a/Themes/core/images/admin/members.gif b/Themes/core/images/admin/members.gif new file mode 100644 index 0000000..cac6d1e Binary files /dev/null and b/Themes/core/images/admin/members.gif differ diff --git a/Themes/core/images/admin/members.png b/Themes/core/images/admin/members.png new file mode 100644 index 0000000..d485fbd Binary files /dev/null and b/Themes/core/images/admin/members.png differ diff --git a/Themes/core/images/admin/modifications.gif b/Themes/core/images/admin/modifications.gif new file mode 100644 index 0000000..1512046 Binary files /dev/null and b/Themes/core/images/admin/modifications.gif differ diff --git a/Themes/core/images/admin/news.gif b/Themes/core/images/admin/news.gif new file mode 100644 index 0000000..e8f6ac5 Binary files /dev/null and b/Themes/core/images/admin/news.gif differ diff --git a/Themes/core/images/admin/package_ops.gif b/Themes/core/images/admin/package_ops.gif new file mode 100644 index 0000000..8c612d8 Binary files /dev/null and b/Themes/core/images/admin/package_ops.gif differ diff --git a/Themes/core/images/admin/packages.gif b/Themes/core/images/admin/packages.gif new file mode 100644 index 0000000..2ffec4f Binary files /dev/null and b/Themes/core/images/admin/packages.gif differ diff --git a/Themes/core/images/admin/packages.png b/Themes/core/images/admin/packages.png new file mode 100644 index 0000000..55887ea Binary files /dev/null and b/Themes/core/images/admin/packages.png differ diff --git a/Themes/core/images/admin/paid.gif b/Themes/core/images/admin/paid.gif new file mode 100644 index 0000000..645ba22 Binary files /dev/null and b/Themes/core/images/admin/paid.gif differ diff --git a/Themes/core/images/admin/permissions.gif b/Themes/core/images/admin/permissions.gif new file mode 100644 index 0000000..6e32f3e Binary files /dev/null and b/Themes/core/images/admin/permissions.gif differ diff --git a/Themes/core/images/admin/permissions.png b/Themes/core/images/admin/permissions.png new file mode 100644 index 0000000..e49af79 Binary files /dev/null and b/Themes/core/images/admin/permissions.png differ diff --git a/Themes/core/images/admin/post_moderation_allow.gif b/Themes/core/images/admin/post_moderation_allow.gif new file mode 100644 index 0000000..33b352c Binary files /dev/null and b/Themes/core/images/admin/post_moderation_allow.gif differ diff --git a/Themes/core/images/admin/post_moderation_deny.gif b/Themes/core/images/admin/post_moderation_deny.gif new file mode 100644 index 0000000..df68cca Binary files /dev/null and b/Themes/core/images/admin/post_moderation_deny.gif differ diff --git a/Themes/core/images/admin/post_moderation_moderate.gif b/Themes/core/images/admin/post_moderation_moderate.gif new file mode 100644 index 0000000..0005be8 Binary files /dev/null and b/Themes/core/images/admin/post_moderation_moderate.gif differ diff --git a/Themes/core/images/admin/posts.gif b/Themes/core/images/admin/posts.gif new file mode 100644 index 0000000..9418230 Binary files /dev/null and b/Themes/core/images/admin/posts.gif differ diff --git a/Themes/core/images/admin/regcenter.gif b/Themes/core/images/admin/regcenter.gif new file mode 100644 index 0000000..5f0b840 Binary files /dev/null and b/Themes/core/images/admin/regcenter.gif differ diff --git a/Themes/core/images/admin/reports.gif b/Themes/core/images/admin/reports.gif new file mode 100644 index 0000000..72b9c64 Binary files /dev/null and b/Themes/core/images/admin/reports.gif differ diff --git a/Themes/core/images/admin/scheduled.gif b/Themes/core/images/admin/scheduled.gif new file mode 100644 index 0000000..f502811 Binary files /dev/null and b/Themes/core/images/admin/scheduled.gif differ diff --git a/Themes/core/images/admin/search.gif b/Themes/core/images/admin/search.gif new file mode 100644 index 0000000..934077c Binary files /dev/null and b/Themes/core/images/admin/search.gif differ diff --git a/Themes/core/images/admin/security.gif b/Themes/core/images/admin/security.gif new file mode 100644 index 0000000..ffb2842 Binary files /dev/null and b/Themes/core/images/admin/security.gif differ diff --git a/Themes/core/images/admin/server.gif b/Themes/core/images/admin/server.gif new file mode 100644 index 0000000..fb988e5 Binary files /dev/null and b/Themes/core/images/admin/server.gif differ diff --git a/Themes/core/images/admin/smiley.gif b/Themes/core/images/admin/smiley.gif new file mode 100644 index 0000000..d739b74 Binary files /dev/null and b/Themes/core/images/admin/smiley.gif differ diff --git a/Themes/core/images/admin/smilies_and_messageicons.png b/Themes/core/images/admin/smilies_and_messageicons.png new file mode 100644 index 0000000..91ca1ae Binary files /dev/null and b/Themes/core/images/admin/smilies_and_messageicons.png differ diff --git a/Themes/core/images/admin/subsection.gif b/Themes/core/images/admin/subsection.gif new file mode 100644 index 0000000..7f82fe5 Binary files /dev/null and b/Themes/core/images/admin/subsection.gif differ diff --git a/Themes/core/images/admin/subsection2.gif b/Themes/core/images/admin/subsection2.gif new file mode 100644 index 0000000..cddefcf Binary files /dev/null and b/Themes/core/images/admin/subsection2.gif differ diff --git a/Themes/core/images/admin/support.gif b/Themes/core/images/admin/support.gif new file mode 100644 index 0000000..a2a53f0 Binary files /dev/null and b/Themes/core/images/admin/support.gif differ diff --git a/Themes/core/images/admin/support_and_credits.png b/Themes/core/images/admin/support_and_credits.png new file mode 100644 index 0000000..10c5045 Binary files /dev/null and b/Themes/core/images/admin/support_and_credits.png differ diff --git a/Themes/core/images/admin/switch_off.png b/Themes/core/images/admin/switch_off.png new file mode 100644 index 0000000..f2937d0 Binary files /dev/null and b/Themes/core/images/admin/switch_off.png differ diff --git a/Themes/core/images/admin/switch_on.png b/Themes/core/images/admin/switch_on.png new file mode 100644 index 0000000..b3ba675 Binary files /dev/null and b/Themes/core/images/admin/switch_on.png differ diff --git a/Themes/core/images/admin/themes.gif b/Themes/core/images/admin/themes.gif new file mode 100644 index 0000000..a5e1450 Binary files /dev/null and b/Themes/core/images/admin/themes.gif differ diff --git a/Themes/core/images/admin/themes_and_layout.png b/Themes/core/images/admin/themes_and_layout.png new file mode 100644 index 0000000..51683a0 Binary files /dev/null and b/Themes/core/images/admin/themes_and_layout.png differ diff --git a/Themes/core/images/admintab_active_last.gif b/Themes/core/images/admintab_active_last.gif new file mode 100644 index 0000000..fd6bb40 Binary files /dev/null and b/Themes/core/images/admintab_active_last.gif differ diff --git a/Themes/core/images/admintab_active_left.gif b/Themes/core/images/admintab_active_left.gif new file mode 100644 index 0000000..6a6dd0c Binary files /dev/null and b/Themes/core/images/admintab_active_left.gif differ diff --git a/Themes/core/images/admintab_active_right.gif b/Themes/core/images/admintab_active_right.gif new file mode 100644 index 0000000..22f2336 Binary files /dev/null and b/Themes/core/images/admintab_active_right.gif differ diff --git a/Themes/core/images/admintab_back.gif b/Themes/core/images/admintab_back.gif new file mode 100644 index 0000000..4ef5022 Binary files /dev/null and b/Themes/core/images/admintab_back.gif differ diff --git a/Themes/core/images/admintab_left.gif b/Themes/core/images/admintab_left.gif new file mode 100644 index 0000000..f77fbea Binary files /dev/null and b/Themes/core/images/admintab_left.gif differ diff --git a/Themes/core/images/admintab_right.gif b/Themes/core/images/admintab_right.gif new file mode 100644 index 0000000..9ddddcb Binary files /dev/null and b/Themes/core/images/admintab_right.gif differ diff --git a/Themes/core/images/aim.gif b/Themes/core/images/aim.gif new file mode 100644 index 0000000..4eb8df0 Binary files /dev/null and b/Themes/core/images/aim.gif differ diff --git a/Themes/core/images/bar.gif b/Themes/core/images/bar.gif new file mode 100644 index 0000000..c1020a0 Binary files /dev/null and b/Themes/core/images/bar.gif differ diff --git a/Themes/core/images/bar_stats.png b/Themes/core/images/bar_stats.png new file mode 100644 index 0000000..95cb6b7 Binary files /dev/null and b/Themes/core/images/bar_stats.png differ diff --git a/Themes/core/images/bbc/bbc_bg.gif b/Themes/core/images/bbc/bbc_bg.gif new file mode 100644 index 0000000..0c8518e Binary files /dev/null and b/Themes/core/images/bbc/bbc_bg.gif differ diff --git a/Themes/core/images/bbc/bbc_hoverbg.gif b/Themes/core/images/bbc/bbc_hoverbg.gif new file mode 100644 index 0000000..7874fb5 Binary files /dev/null and b/Themes/core/images/bbc/bbc_hoverbg.gif differ diff --git a/Themes/core/images/bbc/bold.gif b/Themes/core/images/bbc/bold.gif new file mode 100644 index 0000000..7d21f0a Binary files /dev/null and b/Themes/core/images/bbc/bold.gif differ diff --git a/Themes/core/images/bbc/center.gif b/Themes/core/images/bbc/center.gif new file mode 100644 index 0000000..736bc87 Binary files /dev/null and b/Themes/core/images/bbc/center.gif differ diff --git a/Themes/core/images/bbc/code.gif b/Themes/core/images/bbc/code.gif new file mode 100644 index 0000000..645e7b5 Binary files /dev/null and b/Themes/core/images/bbc/code.gif differ diff --git a/Themes/core/images/bbc/divider.gif b/Themes/core/images/bbc/divider.gif new file mode 100644 index 0000000..d4f35e1 Binary files /dev/null and b/Themes/core/images/bbc/divider.gif differ diff --git a/Themes/core/images/bbc/email.gif b/Themes/core/images/bbc/email.gif new file mode 100644 index 0000000..de68993 Binary files /dev/null and b/Themes/core/images/bbc/email.gif differ diff --git a/Themes/core/images/bbc/flash.gif b/Themes/core/images/bbc/flash.gif new file mode 100644 index 0000000..1fc0318 Binary files /dev/null and b/Themes/core/images/bbc/flash.gif differ diff --git a/Themes/core/images/bbc/ftp.gif b/Themes/core/images/bbc/ftp.gif new file mode 100644 index 0000000..344f5cd Binary files /dev/null and b/Themes/core/images/bbc/ftp.gif differ diff --git a/Themes/core/images/bbc/glow.gif b/Themes/core/images/bbc/glow.gif new file mode 100644 index 0000000..0f0dce7 Binary files /dev/null and b/Themes/core/images/bbc/glow.gif differ diff --git a/Themes/core/images/bbc/hr.gif b/Themes/core/images/bbc/hr.gif new file mode 100644 index 0000000..433f8a7 Binary files /dev/null and b/Themes/core/images/bbc/hr.gif differ diff --git a/Themes/core/images/bbc/img.gif b/Themes/core/images/bbc/img.gif new file mode 100644 index 0000000..02817e7 Binary files /dev/null and b/Themes/core/images/bbc/img.gif differ diff --git a/Themes/core/images/bbc/index.php b/Themes/core/images/bbc/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/core/images/bbc/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/core/images/bbc/italicize.gif b/Themes/core/images/bbc/italicize.gif new file mode 100644 index 0000000..68c7147 Binary files /dev/null and b/Themes/core/images/bbc/italicize.gif differ diff --git a/Themes/core/images/bbc/left.gif b/Themes/core/images/bbc/left.gif new file mode 100644 index 0000000..87bd529 Binary files /dev/null and b/Themes/core/images/bbc/left.gif differ diff --git a/Themes/core/images/bbc/list.gif b/Themes/core/images/bbc/list.gif new file mode 100644 index 0000000..95494c0 Binary files /dev/null and b/Themes/core/images/bbc/list.gif differ diff --git a/Themes/core/images/bbc/move.gif b/Themes/core/images/bbc/move.gif new file mode 100644 index 0000000..a0d65ec Binary files /dev/null and b/Themes/core/images/bbc/move.gif differ diff --git a/Themes/core/images/bbc/orderlist.gif b/Themes/core/images/bbc/orderlist.gif new file mode 100644 index 0000000..85f4a9c Binary files /dev/null and b/Themes/core/images/bbc/orderlist.gif differ diff --git a/Themes/core/images/bbc/pre.gif b/Themes/core/images/bbc/pre.gif new file mode 100644 index 0000000..e42bfd6 Binary files /dev/null and b/Themes/core/images/bbc/pre.gif differ diff --git a/Themes/core/images/bbc/quote.gif b/Themes/core/images/bbc/quote.gif new file mode 100644 index 0000000..07a6195 Binary files /dev/null and b/Themes/core/images/bbc/quote.gif differ diff --git a/Themes/core/images/bbc/resize-handle.gif b/Themes/core/images/bbc/resize-handle.gif new file mode 100644 index 0000000..3fa6e0f Binary files /dev/null and b/Themes/core/images/bbc/resize-handle.gif differ diff --git a/Themes/core/images/bbc/right.gif b/Themes/core/images/bbc/right.gif new file mode 100644 index 0000000..54d74aa Binary files /dev/null and b/Themes/core/images/bbc/right.gif differ diff --git a/Themes/core/images/bbc/shadow.gif b/Themes/core/images/bbc/shadow.gif new file mode 100644 index 0000000..84145d1 Binary files /dev/null and b/Themes/core/images/bbc/shadow.gif differ diff --git a/Themes/core/images/bbc/strike.gif b/Themes/core/images/bbc/strike.gif new file mode 100644 index 0000000..9d42fc3 Binary files /dev/null and b/Themes/core/images/bbc/strike.gif differ diff --git a/Themes/core/images/bbc/sub.gif b/Themes/core/images/bbc/sub.gif new file mode 100644 index 0000000..03fb567 Binary files /dev/null and b/Themes/core/images/bbc/sub.gif differ diff --git a/Themes/core/images/bbc/sup.gif b/Themes/core/images/bbc/sup.gif new file mode 100644 index 0000000..fa936d0 Binary files /dev/null and b/Themes/core/images/bbc/sup.gif differ diff --git a/Themes/core/images/bbc/table.gif b/Themes/core/images/bbc/table.gif new file mode 100644 index 0000000..d98b438 Binary files /dev/null and b/Themes/core/images/bbc/table.gif differ diff --git a/Themes/core/images/bbc/tele.gif b/Themes/core/images/bbc/tele.gif new file mode 100644 index 0000000..2f5077d Binary files /dev/null and b/Themes/core/images/bbc/tele.gif differ diff --git a/Themes/core/images/bbc/toggle.gif b/Themes/core/images/bbc/toggle.gif new file mode 100644 index 0000000..6cddfbc Binary files /dev/null and b/Themes/core/images/bbc/toggle.gif differ diff --git a/Themes/core/images/bbc/underline.gif b/Themes/core/images/bbc/underline.gif new file mode 100644 index 0000000..c667ed4 Binary files /dev/null and b/Themes/core/images/bbc/underline.gif differ diff --git a/Themes/core/images/bbc/unformat.gif b/Themes/core/images/bbc/unformat.gif new file mode 100644 index 0000000..c7ba2f4 Binary files /dev/null and b/Themes/core/images/bbc/unformat.gif differ diff --git a/Themes/core/images/bbc/url.gif b/Themes/core/images/bbc/url.gif new file mode 100644 index 0000000..4d57467 Binary files /dev/null and b/Themes/core/images/bbc/url.gif differ diff --git a/Themes/core/images/blank.gif b/Themes/core/images/blank.gif new file mode 100644 index 0000000..5bfd67a Binary files /dev/null and b/Themes/core/images/blank.gif differ diff --git a/Themes/core/images/board.gif b/Themes/core/images/board.gif new file mode 100644 index 0000000..b3bc440 Binary files /dev/null and b/Themes/core/images/board.gif differ diff --git a/Themes/core/images/board_select_spot.gif b/Themes/core/images/board_select_spot.gif new file mode 100644 index 0000000..86b9d06 Binary files /dev/null and b/Themes/core/images/board_select_spot.gif differ diff --git a/Themes/core/images/board_select_spot_child.gif b/Themes/core/images/board_select_spot_child.gif new file mode 100644 index 0000000..b4e9e20 Binary files /dev/null and b/Themes/core/images/board_select_spot_child.gif differ diff --git a/Themes/core/images/buddy_useroff.gif b/Themes/core/images/buddy_useroff.gif new file mode 100644 index 0000000..2a48cde Binary files /dev/null and b/Themes/core/images/buddy_useroff.gif differ diff --git a/Themes/core/images/buddy_useron.gif b/Themes/core/images/buddy_useron.gif new file mode 100644 index 0000000..c92c9ae Binary files /dev/null and b/Themes/core/images/buddy_useron.gif differ diff --git a/Themes/core/images/buttons/approve.gif b/Themes/core/images/buttons/approve.gif new file mode 100644 index 0000000..bb309ab Binary files /dev/null and b/Themes/core/images/buttons/approve.gif differ diff --git a/Themes/core/images/buttons/calendarpe.gif b/Themes/core/images/buttons/calendarpe.gif new file mode 100644 index 0000000..02e6b54 Binary files /dev/null and b/Themes/core/images/buttons/calendarpe.gif differ diff --git a/Themes/core/images/buttons/close.gif b/Themes/core/images/buttons/close.gif new file mode 100644 index 0000000..f0c02a2 Binary files /dev/null and b/Themes/core/images/buttons/close.gif differ diff --git a/Themes/core/images/buttons/delete.gif b/Themes/core/images/buttons/delete.gif new file mode 100644 index 0000000..93ee060 Binary files /dev/null and b/Themes/core/images/buttons/delete.gif differ diff --git a/Themes/core/images/buttons/details.gif b/Themes/core/images/buttons/details.gif new file mode 100644 index 0000000..305c311 Binary files /dev/null and b/Themes/core/images/buttons/details.gif differ diff --git a/Themes/core/images/buttons/ignore.gif b/Themes/core/images/buttons/ignore.gif new file mode 100644 index 0000000..b2fa1fc Binary files /dev/null and b/Themes/core/images/buttons/ignore.gif differ diff --git a/Themes/core/images/buttons/im_reply.gif b/Themes/core/images/buttons/im_reply.gif new file mode 100644 index 0000000..88c5767 Binary files /dev/null and b/Themes/core/images/buttons/im_reply.gif differ diff --git a/Themes/core/images/buttons/im_reply_all.gif b/Themes/core/images/buttons/im_reply_all.gif new file mode 100644 index 0000000..8c612d8 Binary files /dev/null and b/Themes/core/images/buttons/im_reply_all.gif differ diff --git a/Themes/core/images/buttons/index.php b/Themes/core/images/buttons/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/core/images/buttons/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/core/images/buttons/merge.gif b/Themes/core/images/buttons/merge.gif new file mode 100644 index 0000000..5e5d95c Binary files /dev/null and b/Themes/core/images/buttons/merge.gif differ diff --git a/Themes/core/images/buttons/modify.gif b/Themes/core/images/buttons/modify.gif new file mode 100644 index 0000000..61698f0 Binary files /dev/null and b/Themes/core/images/buttons/modify.gif differ diff --git a/Themes/core/images/buttons/notify_sm.gif b/Themes/core/images/buttons/notify_sm.gif new file mode 100644 index 0000000..5020ab3 Binary files /dev/null and b/Themes/core/images/buttons/notify_sm.gif differ diff --git a/Themes/core/images/buttons/quote.gif b/Themes/core/images/buttons/quote.gif new file mode 100644 index 0000000..305c311 Binary files /dev/null and b/Themes/core/images/buttons/quote.gif differ diff --git a/Themes/core/images/buttons/reply.gif b/Themes/core/images/buttons/reply.gif new file mode 100644 index 0000000..8c612d8 Binary files /dev/null and b/Themes/core/images/buttons/reply.gif differ diff --git a/Themes/core/images/buttons/reply_sm.gif b/Themes/core/images/buttons/reply_sm.gif new file mode 100644 index 0000000..3e4e382 Binary files /dev/null and b/Themes/core/images/buttons/reply_sm.gif differ diff --git a/Themes/core/images/buttons/restore_topic.gif b/Themes/core/images/buttons/restore_topic.gif new file mode 100644 index 0000000..91e5c67 Binary files /dev/null and b/Themes/core/images/buttons/restore_topic.gif differ diff --git a/Themes/core/images/buttons/search.gif b/Themes/core/images/buttons/search.gif new file mode 100644 index 0000000..2fa0346 Binary files /dev/null and b/Themes/core/images/buttons/search.gif differ diff --git a/Themes/core/images/buttons/split.gif b/Themes/core/images/buttons/split.gif new file mode 100644 index 0000000..0bbce35 Binary files /dev/null and b/Themes/core/images/buttons/split.gif differ diff --git a/Themes/core/images/cake.png b/Themes/core/images/cake.png new file mode 100644 index 0000000..b596899 Binary files /dev/null and b/Themes/core/images/cake.png differ diff --git a/Themes/core/images/catbg.jpg b/Themes/core/images/catbg.jpg new file mode 100644 index 0000000..dfc834e Binary files /dev/null and b/Themes/core/images/catbg.jpg differ diff --git a/Themes/core/images/catbg2.jpg b/Themes/core/images/catbg2.jpg new file mode 100644 index 0000000..cb82394 Binary files /dev/null and b/Themes/core/images/catbg2.jpg differ diff --git a/Themes/core/images/collapse.gif b/Themes/core/images/collapse.gif new file mode 100644 index 0000000..699dc25 Binary files /dev/null and b/Themes/core/images/collapse.gif differ diff --git a/Themes/core/images/construction.png b/Themes/core/images/construction.png new file mode 100644 index 0000000..ca4424d Binary files /dev/null and b/Themes/core/images/construction.png differ diff --git a/Themes/core/images/email_sm.gif b/Themes/core/images/email_sm.gif new file mode 100644 index 0000000..466bb22 Binary files /dev/null and b/Themes/core/images/email_sm.gif differ diff --git a/Themes/core/images/english/index.php b/Themes/core/images/english/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/core/images/english/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/core/images/english/new.gif b/Themes/core/images/english/new.gif new file mode 100644 index 0000000..0c5a9f0 Binary files /dev/null and b/Themes/core/images/english/new.gif differ diff --git a/Themes/core/images/expand.gif b/Themes/core/images/expand.gif new file mode 100644 index 0000000..95f43ae Binary files /dev/null and b/Themes/core/images/expand.gif differ diff --git a/Themes/core/images/filter.gif b/Themes/core/images/filter.gif new file mode 100644 index 0000000..f16261f Binary files /dev/null and b/Themes/core/images/filter.gif differ diff --git a/Themes/core/images/helptopics.gif b/Themes/core/images/helptopics.gif new file mode 100644 index 0000000..55c5fc3 Binary files /dev/null and b/Themes/core/images/helptopics.gif differ diff --git a/Themes/core/images/icons/assist.gif b/Themes/core/images/icons/assist.gif new file mode 100644 index 0000000..84289cd Binary files /dev/null and b/Themes/core/images/icons/assist.gif differ diff --git a/Themes/core/images/icons/calendar.gif b/Themes/core/images/icons/calendar.gif new file mode 100644 index 0000000..296a129 Binary files /dev/null and b/Themes/core/images/icons/calendar.gif differ diff --git a/Themes/core/images/icons/clip.gif b/Themes/core/images/icons/clip.gif new file mode 100644 index 0000000..a9633c2 Binary files /dev/null and b/Themes/core/images/icons/clip.gif differ diff --git a/Themes/core/images/icons/config_sm.gif b/Themes/core/images/icons/config_sm.gif new file mode 100644 index 0000000..304639e Binary files /dev/null and b/Themes/core/images/icons/config_sm.gif differ diff --git a/Themes/core/images/icons/delete.gif b/Themes/core/images/icons/delete.gif new file mode 100644 index 0000000..b3e13cb Binary files /dev/null and b/Themes/core/images/icons/delete.gif differ diff --git a/Themes/core/images/icons/field_check.gif b/Themes/core/images/icons/field_check.gif new file mode 100644 index 0000000..9905d2a Binary files /dev/null and b/Themes/core/images/icons/field_check.gif differ diff --git a/Themes/core/images/icons/field_invalid.gif b/Themes/core/images/icons/field_invalid.gif new file mode 100644 index 0000000..64d0a1f Binary files /dev/null and b/Themes/core/images/icons/field_invalid.gif differ diff --git a/Themes/core/images/icons/field_valid.gif b/Themes/core/images/icons/field_valid.gif new file mode 100644 index 0000000..897aeaa Binary files /dev/null and b/Themes/core/images/icons/field_valid.gif differ diff --git a/Themes/core/images/icons/im_newmsg.gif b/Themes/core/images/icons/im_newmsg.gif new file mode 100644 index 0000000..18b6687 Binary files /dev/null and b/Themes/core/images/icons/im_newmsg.gif differ diff --git a/Themes/core/images/icons/index.php b/Themes/core/images/icons/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/core/images/icons/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/core/images/icons/info.gif b/Themes/core/images/icons/info.gif new file mode 100644 index 0000000..30b5766 Binary files /dev/null and b/Themes/core/images/icons/info.gif differ diff --git a/Themes/core/images/icons/last_post.gif b/Themes/core/images/icons/last_post.gif new file mode 100644 index 0000000..17c2316 Binary files /dev/null and b/Themes/core/images/icons/last_post.gif differ diff --git a/Themes/core/images/icons/login.gif b/Themes/core/images/icons/login.gif new file mode 100644 index 0000000..f15752f Binary files /dev/null and b/Themes/core/images/icons/login.gif differ diff --git a/Themes/core/images/icons/login_sm.gif b/Themes/core/images/icons/login_sm.gif new file mode 100644 index 0000000..5da1e73 Binary files /dev/null and b/Themes/core/images/icons/login_sm.gif differ diff --git a/Themes/core/images/icons/members.gif b/Themes/core/images/icons/members.gif new file mode 100644 index 0000000..39efa37 Binary files /dev/null and b/Themes/core/images/icons/members.gif differ diff --git a/Themes/core/images/icons/modify_inline.gif b/Themes/core/images/icons/modify_inline.gif new file mode 100644 index 0000000..61698f0 Binary files /dev/null and b/Themes/core/images/icons/modify_inline.gif differ diff --git a/Themes/core/images/icons/modify_small.gif b/Themes/core/images/icons/modify_small.gif new file mode 100644 index 0000000..d26e893 Binary files /dev/null and b/Themes/core/images/icons/modify_small.gif differ diff --git a/Themes/core/images/icons/notify_sm.gif b/Themes/core/images/icons/notify_sm.gif new file mode 100644 index 0000000..6d254c9 Binary files /dev/null and b/Themes/core/images/icons/notify_sm.gif differ diff --git a/Themes/core/images/icons/online.gif b/Themes/core/images/icons/online.gif new file mode 100644 index 0000000..cf6d8c8 Binary files /dev/null and b/Themes/core/images/icons/online.gif differ diff --git a/Themes/core/images/icons/package_installed.gif b/Themes/core/images/icons/package_installed.gif new file mode 100644 index 0000000..febd4e3 Binary files /dev/null and b/Themes/core/images/icons/package_installed.gif differ diff --git a/Themes/core/images/icons/package_old.gif b/Themes/core/images/icons/package_old.gif new file mode 100644 index 0000000..de39c6d Binary files /dev/null and b/Themes/core/images/icons/package_old.gif differ diff --git a/Themes/core/images/icons/pm_read.gif b/Themes/core/images/icons/pm_read.gif new file mode 100644 index 0000000..1ebcf6b Binary files /dev/null and b/Themes/core/images/icons/pm_read.gif differ diff --git a/Themes/core/images/icons/pm_replied.gif b/Themes/core/images/icons/pm_replied.gif new file mode 100644 index 0000000..1d39a4d Binary files /dev/null and b/Themes/core/images/icons/pm_replied.gif differ diff --git a/Themes/core/images/icons/profile_sm.gif b/Themes/core/images/icons/profile_sm.gif new file mode 100644 index 0000000..bd1cc59 Binary files /dev/null and b/Themes/core/images/icons/profile_sm.gif differ diff --git a/Themes/core/images/icons/quick_lock.gif b/Themes/core/images/icons/quick_lock.gif new file mode 100644 index 0000000..2711ac7 Binary files /dev/null and b/Themes/core/images/icons/quick_lock.gif differ diff --git a/Themes/core/images/icons/quick_move.gif b/Themes/core/images/icons/quick_move.gif new file mode 100644 index 0000000..1ec1b10 Binary files /dev/null and b/Themes/core/images/icons/quick_move.gif differ diff --git a/Themes/core/images/icons/quick_remove.gif b/Themes/core/images/icons/quick_remove.gif new file mode 100644 index 0000000..5c2a15f Binary files /dev/null and b/Themes/core/images/icons/quick_remove.gif differ diff --git a/Themes/core/images/icons/quick_sticky.gif b/Themes/core/images/icons/quick_sticky.gif new file mode 100644 index 0000000..25181e2 Binary files /dev/null and b/Themes/core/images/icons/quick_sticky.gif differ diff --git a/Themes/core/images/icons/show_sticky.gif b/Themes/core/images/icons/show_sticky.gif new file mode 100644 index 0000000..b7d6ef3 Binary files /dev/null and b/Themes/core/images/icons/show_sticky.gif differ diff --git a/Themes/core/images/im_off.gif b/Themes/core/images/im_off.gif new file mode 100644 index 0000000..771136a Binary files /dev/null and b/Themes/core/images/im_off.gif differ diff --git a/Themes/core/images/im_on.gif b/Themes/core/images/im_on.gif new file mode 100644 index 0000000..7f1f8ac Binary files /dev/null and b/Themes/core/images/im_on.gif differ diff --git a/Themes/core/images/im_sm_newmsg.gif b/Themes/core/images/im_sm_newmsg.gif new file mode 100644 index 0000000..e3de228 Binary files /dev/null and b/Themes/core/images/im_sm_newmsg.gif differ diff --git a/Themes/core/images/im_sm_prefs.gif b/Themes/core/images/im_sm_prefs.gif new file mode 100644 index 0000000..304639e Binary files /dev/null and b/Themes/core/images/im_sm_prefs.gif differ diff --git a/Themes/core/images/im_switch.gif b/Themes/core/images/im_switch.gif new file mode 100644 index 0000000..0d4c78d Binary files /dev/null and b/Themes/core/images/im_switch.gif differ diff --git a/Themes/core/images/index.php b/Themes/core/images/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/core/images/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/core/images/ip.gif b/Themes/core/images/ip.gif new file mode 100644 index 0000000..e3ac911 Binary files /dev/null and b/Themes/core/images/ip.gif differ diff --git a/Themes/core/images/maintab_active_back.gif b/Themes/core/images/maintab_active_back.gif new file mode 100644 index 0000000..6e55ba4 Binary files /dev/null and b/Themes/core/images/maintab_active_back.gif differ diff --git a/Themes/core/images/maintab_active_first.gif b/Themes/core/images/maintab_active_first.gif new file mode 100644 index 0000000..ab44aff Binary files /dev/null and b/Themes/core/images/maintab_active_first.gif differ diff --git a/Themes/core/images/maintab_active_last.gif b/Themes/core/images/maintab_active_last.gif new file mode 100644 index 0000000..5fbfe70 Binary files /dev/null and b/Themes/core/images/maintab_active_last.gif differ diff --git a/Themes/core/images/maintab_back.gif b/Themes/core/images/maintab_back.gif new file mode 100644 index 0000000..8aed006 Binary files /dev/null and b/Themes/core/images/maintab_back.gif differ diff --git a/Themes/core/images/maintab_first.gif b/Themes/core/images/maintab_first.gif new file mode 100644 index 0000000..d414055 Binary files /dev/null and b/Themes/core/images/maintab_first.gif differ diff --git a/Themes/core/images/maintab_first_prev.gif b/Themes/core/images/maintab_first_prev.gif new file mode 100644 index 0000000..24c907e Binary files /dev/null and b/Themes/core/images/maintab_first_prev.gif differ diff --git a/Themes/core/images/maintab_last.gif b/Themes/core/images/maintab_last.gif new file mode 100644 index 0000000..b3d3e61 Binary files /dev/null and b/Themes/core/images/maintab_last.gif differ diff --git a/Themes/core/images/maintab_last_prev.gif b/Themes/core/images/maintab_last_prev.gif new file mode 100644 index 0000000..36cd620 Binary files /dev/null and b/Themes/core/images/maintab_last_prev.gif differ diff --git a/Themes/core/images/menubg.gif b/Themes/core/images/menubg.gif new file mode 100644 index 0000000..887aafe Binary files /dev/null and b/Themes/core/images/menubg.gif differ diff --git a/Themes/core/images/message_sm.gif b/Themes/core/images/message_sm.gif new file mode 100644 index 0000000..0e5f36a Binary files /dev/null and b/Themes/core/images/message_sm.gif differ diff --git a/Themes/core/images/mirrortab_active_back.gif b/Themes/core/images/mirrortab_active_back.gif new file mode 100644 index 0000000..bda5a9d Binary files /dev/null and b/Themes/core/images/mirrortab_active_back.gif differ diff --git a/Themes/core/images/mirrortab_active_first.gif b/Themes/core/images/mirrortab_active_first.gif new file mode 100644 index 0000000..d202db3 Binary files /dev/null and b/Themes/core/images/mirrortab_active_first.gif differ diff --git a/Themes/core/images/mirrortab_active_last.gif b/Themes/core/images/mirrortab_active_last.gif new file mode 100644 index 0000000..90da072 Binary files /dev/null and b/Themes/core/images/mirrortab_active_last.gif differ diff --git a/Themes/core/images/mirrortab_back.gif b/Themes/core/images/mirrortab_back.gif new file mode 100644 index 0000000..fd4ae05 Binary files /dev/null and b/Themes/core/images/mirrortab_back.gif differ diff --git a/Themes/core/images/mirrortab_first.gif b/Themes/core/images/mirrortab_first.gif new file mode 100644 index 0000000..fd62af1 Binary files /dev/null and b/Themes/core/images/mirrortab_first.gif differ diff --git a/Themes/core/images/mirrortab_first_prev.gif b/Themes/core/images/mirrortab_first_prev.gif new file mode 100644 index 0000000..f568605 Binary files /dev/null and b/Themes/core/images/mirrortab_first_prev.gif differ diff --git a/Themes/core/images/mirrortab_last.gif b/Themes/core/images/mirrortab_last.gif new file mode 100644 index 0000000..7c572d8 Binary files /dev/null and b/Themes/core/images/mirrortab_last.gif differ diff --git a/Themes/core/images/mirrortab_last_prev.gif b/Themes/core/images/mirrortab_last_prev.gif new file mode 100644 index 0000000..b130abf Binary files /dev/null and b/Themes/core/images/mirrortab_last_prev.gif differ diff --git a/Themes/core/images/msntalk.gif b/Themes/core/images/msntalk.gif new file mode 100644 index 0000000..207f55a Binary files /dev/null and b/Themes/core/images/msntalk.gif differ diff --git a/Themes/core/images/new_bar.gif b/Themes/core/images/new_bar.gif new file mode 100644 index 0000000..11a2a72 Binary files /dev/null and b/Themes/core/images/new_bar.gif differ diff --git a/Themes/core/images/new_none.gif b/Themes/core/images/new_none.gif new file mode 100644 index 0000000..1820bb0 Binary files /dev/null and b/Themes/core/images/new_none.gif differ diff --git a/Themes/core/images/new_some.gif b/Themes/core/images/new_some.gif new file mode 100644 index 0000000..8ea9fb2 Binary files /dev/null and b/Themes/core/images/new_some.gif differ diff --git a/Themes/core/images/off.gif b/Themes/core/images/off.gif new file mode 100644 index 0000000..b423101 Binary files /dev/null and b/Themes/core/images/off.gif differ diff --git a/Themes/core/images/on.gif b/Themes/core/images/on.gif new file mode 100644 index 0000000..365bd71 Binary files /dev/null and b/Themes/core/images/on.gif differ diff --git a/Themes/core/images/on2.gif b/Themes/core/images/on2.gif new file mode 100644 index 0000000..8dc33b3 Binary files /dev/null and b/Themes/core/images/on2.gif differ diff --git a/Themes/core/images/openid.gif b/Themes/core/images/openid.gif new file mode 100644 index 0000000..cde836c Binary files /dev/null and b/Themes/core/images/openid.gif differ diff --git a/Themes/core/images/pm_recipient_delete.gif b/Themes/core/images/pm_recipient_delete.gif new file mode 100644 index 0000000..02d4fbf Binary files /dev/null and b/Themes/core/images/pm_recipient_delete.gif differ diff --git a/Themes/core/images/poll_left.gif b/Themes/core/images/poll_left.gif new file mode 100644 index 0000000..ac7df6b Binary files /dev/null and b/Themes/core/images/poll_left.gif differ diff --git a/Themes/core/images/poll_middle.gif b/Themes/core/images/poll_middle.gif new file mode 100644 index 0000000..cdffb90 Binary files /dev/null and b/Themes/core/images/poll_middle.gif differ diff --git a/Themes/core/images/poll_right.gif b/Themes/core/images/poll_right.gif new file mode 100644 index 0000000..337187f Binary files /dev/null and b/Themes/core/images/poll_right.gif differ diff --git a/Themes/core/images/post/angry.gif b/Themes/core/images/post/angry.gif new file mode 100644 index 0000000..a654fc0 Binary files /dev/null and b/Themes/core/images/post/angry.gif differ diff --git a/Themes/core/images/post/cheesy.gif b/Themes/core/images/post/cheesy.gif new file mode 100644 index 0000000..1c7893f Binary files /dev/null and b/Themes/core/images/post/cheesy.gif differ diff --git a/Themes/core/images/post/clip.gif b/Themes/core/images/post/clip.gif new file mode 100644 index 0000000..e594837 Binary files /dev/null and b/Themes/core/images/post/clip.gif differ diff --git a/Themes/core/images/post/exclamation.gif b/Themes/core/images/post/exclamation.gif new file mode 100644 index 0000000..e77cfed Binary files /dev/null and b/Themes/core/images/post/exclamation.gif differ diff --git a/Themes/core/images/post/grin.gif b/Themes/core/images/post/grin.gif new file mode 100644 index 0000000..91a16c4 Binary files /dev/null and b/Themes/core/images/post/grin.gif differ diff --git a/Themes/core/images/post/index.php b/Themes/core/images/post/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/core/images/post/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/core/images/post/lamp.gif b/Themes/core/images/post/lamp.gif new file mode 100644 index 0000000..d894edd Binary files /dev/null and b/Themes/core/images/post/lamp.gif differ diff --git a/Themes/core/images/post/moved.gif b/Themes/core/images/post/moved.gif new file mode 100644 index 0000000..aad605b Binary files /dev/null and b/Themes/core/images/post/moved.gif differ diff --git a/Themes/core/images/post/question.gif b/Themes/core/images/post/question.gif new file mode 100644 index 0000000..75b69d5 Binary files /dev/null and b/Themes/core/images/post/question.gif differ diff --git a/Themes/core/images/post/recycled.gif b/Themes/core/images/post/recycled.gif new file mode 100644 index 0000000..91e5c67 Binary files /dev/null and b/Themes/core/images/post/recycled.gif differ diff --git a/Themes/core/images/post/sad.gif b/Themes/core/images/post/sad.gif new file mode 100644 index 0000000..bed07ca Binary files /dev/null and b/Themes/core/images/post/sad.gif differ diff --git a/Themes/core/images/post/smiley.gif b/Themes/core/images/post/smiley.gif new file mode 100644 index 0000000..0b0ea19 Binary files /dev/null and b/Themes/core/images/post/smiley.gif differ diff --git a/Themes/core/images/post/thumbdown.gif b/Themes/core/images/post/thumbdown.gif new file mode 100644 index 0000000..ab7e040 Binary files /dev/null and b/Themes/core/images/post/thumbdown.gif differ diff --git a/Themes/core/images/post/thumbup.gif b/Themes/core/images/post/thumbup.gif new file mode 100644 index 0000000..62e0d02 Binary files /dev/null and b/Themes/core/images/post/thumbup.gif differ diff --git a/Themes/core/images/post/wink.gif b/Themes/core/images/post/wink.gif new file mode 100644 index 0000000..c7b79ff Binary files /dev/null and b/Themes/core/images/post/wink.gif differ diff --git a/Themes/core/images/post/wireless.gif b/Themes/core/images/post/wireless.gif new file mode 100644 index 0000000..9b66c37 Binary files /dev/null and b/Themes/core/images/post/wireless.gif differ diff --git a/Themes/core/images/post/xx.gif b/Themes/core/images/post/xx.gif new file mode 100644 index 0000000..ca68750 Binary files /dev/null and b/Themes/core/images/post/xx.gif differ diff --git a/Themes/core/images/redirect.gif b/Themes/core/images/redirect.gif new file mode 100644 index 0000000..ff08eb5 Binary files /dev/null and b/Themes/core/images/redirect.gif differ diff --git a/Themes/core/images/selected.gif b/Themes/core/images/selected.gif new file mode 100644 index 0000000..4d2d54e Binary files /dev/null and b/Themes/core/images/selected.gif differ diff --git a/Themes/core/images/smflogo.gif b/Themes/core/images/smflogo.gif new file mode 100644 index 0000000..69c0b7d Binary files /dev/null and b/Themes/core/images/smflogo.gif differ diff --git a/Themes/core/images/smiley_select_spot.gif b/Themes/core/images/smiley_select_spot.gif new file mode 100644 index 0000000..96d6168 Binary files /dev/null and b/Themes/core/images/smiley_select_spot.gif differ diff --git a/Themes/core/images/sort_down.gif b/Themes/core/images/sort_down.gif new file mode 100644 index 0000000..6c755db Binary files /dev/null and b/Themes/core/images/sort_down.gif differ diff --git a/Themes/core/images/sort_up.gif b/Themes/core/images/sort_up.gif new file mode 100644 index 0000000..7e5731a Binary files /dev/null and b/Themes/core/images/sort_up.gif differ diff --git a/Themes/core/images/split_deselect.gif b/Themes/core/images/split_deselect.gif new file mode 100644 index 0000000..955a84c Binary files /dev/null and b/Themes/core/images/split_deselect.gif differ diff --git a/Themes/core/images/split_select.gif b/Themes/core/images/split_select.gif new file mode 100644 index 0000000..65765ec Binary files /dev/null and b/Themes/core/images/split_select.gif differ diff --git a/Themes/core/images/star.gif b/Themes/core/images/star.gif new file mode 100644 index 0000000..2177240 Binary files /dev/null and b/Themes/core/images/star.gif differ diff --git a/Themes/core/images/staradmin.gif b/Themes/core/images/staradmin.gif new file mode 100644 index 0000000..ff798fa Binary files /dev/null and b/Themes/core/images/staradmin.gif differ diff --git a/Themes/core/images/stargmod.gif b/Themes/core/images/stargmod.gif new file mode 100644 index 0000000..31ddeac Binary files /dev/null and b/Themes/core/images/stargmod.gif differ diff --git a/Themes/core/images/starmod.gif b/Themes/core/images/starmod.gif new file mode 100644 index 0000000..b1e56d0 Binary files /dev/null and b/Themes/core/images/starmod.gif differ diff --git a/Themes/core/images/stats_board.gif b/Themes/core/images/stats_board.gif new file mode 100644 index 0000000..64b52f4 Binary files /dev/null and b/Themes/core/images/stats_board.gif differ diff --git a/Themes/core/images/stats_boards.gif b/Themes/core/images/stats_boards.gif new file mode 100644 index 0000000..c9bc2f4 Binary files /dev/null and b/Themes/core/images/stats_boards.gif differ diff --git a/Themes/core/images/stats_history.gif b/Themes/core/images/stats_history.gif new file mode 100644 index 0000000..563ab3f Binary files /dev/null and b/Themes/core/images/stats_history.gif differ diff --git a/Themes/core/images/stats_info.gif b/Themes/core/images/stats_info.gif new file mode 100644 index 0000000..a882fec Binary files /dev/null and b/Themes/core/images/stats_info.gif differ diff --git a/Themes/core/images/stats_pie.png b/Themes/core/images/stats_pie.png new file mode 100644 index 0000000..1f692c9 Binary files /dev/null and b/Themes/core/images/stats_pie.png differ diff --git a/Themes/core/images/stats_pie_rtl.png b/Themes/core/images/stats_pie_rtl.png new file mode 100644 index 0000000..af9d2b3 Binary files /dev/null and b/Themes/core/images/stats_pie_rtl.png differ diff --git a/Themes/core/images/stats_posters.gif b/Themes/core/images/stats_posters.gif new file mode 100644 index 0000000..b050475 Binary files /dev/null and b/Themes/core/images/stats_posters.gif differ diff --git a/Themes/core/images/stats_replies.gif b/Themes/core/images/stats_replies.gif new file mode 100644 index 0000000..ebe3a3a Binary files /dev/null and b/Themes/core/images/stats_replies.gif differ diff --git a/Themes/core/images/stats_views.gif b/Themes/core/images/stats_views.gif new file mode 100644 index 0000000..3e73e40 Binary files /dev/null and b/Themes/core/images/stats_views.gif differ diff --git a/Themes/core/images/thumbnail.gif b/Themes/core/images/thumbnail.gif new file mode 100644 index 0000000..0e1dc6e Binary files /dev/null and b/Themes/core/images/thumbnail.gif differ diff --git a/Themes/core/images/titlebg.jpg b/Themes/core/images/titlebg.jpg new file mode 100644 index 0000000..ad0ed5a Binary files /dev/null and b/Themes/core/images/titlebg.jpg differ diff --git a/Themes/core/images/topbg.jpg b/Themes/core/images/topbg.jpg new file mode 100644 index 0000000..121ea36 Binary files /dev/null and b/Themes/core/images/topbg.jpg differ diff --git a/Themes/core/images/topic/hot_poll.gif b/Themes/core/images/topic/hot_poll.gif new file mode 100644 index 0000000..fae050d Binary files /dev/null and b/Themes/core/images/topic/hot_poll.gif differ diff --git a/Themes/core/images/topic/hot_post.gif b/Themes/core/images/topic/hot_post.gif new file mode 100644 index 0000000..a1c66ad Binary files /dev/null and b/Themes/core/images/topic/hot_post.gif differ diff --git a/Themes/core/images/topic/hot_post_locked.gif b/Themes/core/images/topic/hot_post_locked.gif new file mode 100644 index 0000000..26a2a1a Binary files /dev/null and b/Themes/core/images/topic/hot_post_locked.gif differ diff --git a/Themes/core/images/topic/hot_post_locked_sticky.gif b/Themes/core/images/topic/hot_post_locked_sticky.gif new file mode 100644 index 0000000..c548918 Binary files /dev/null and b/Themes/core/images/topic/hot_post_locked_sticky.gif differ diff --git a/Themes/core/images/topic/hot_post_sticky.gif b/Themes/core/images/topic/hot_post_sticky.gif new file mode 100644 index 0000000..13a0750 Binary files /dev/null and b/Themes/core/images/topic/hot_post_sticky.gif differ diff --git a/Themes/core/images/topic/index.php b/Themes/core/images/topic/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/core/images/topic/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/core/images/topic/my_hot_poll.gif b/Themes/core/images/topic/my_hot_poll.gif new file mode 100644 index 0000000..3a131ae Binary files /dev/null and b/Themes/core/images/topic/my_hot_poll.gif differ diff --git a/Themes/core/images/topic/my_hot_post.gif b/Themes/core/images/topic/my_hot_post.gif new file mode 100644 index 0000000..9c7d295 Binary files /dev/null and b/Themes/core/images/topic/my_hot_post.gif differ diff --git a/Themes/core/images/topic/my_normal_poll.gif b/Themes/core/images/topic/my_normal_poll.gif new file mode 100644 index 0000000..5959126 Binary files /dev/null and b/Themes/core/images/topic/my_normal_poll.gif differ diff --git a/Themes/core/images/topic/my_normal_post.gif b/Themes/core/images/topic/my_normal_post.gif new file mode 100644 index 0000000..0dcefa3 Binary files /dev/null and b/Themes/core/images/topic/my_normal_post.gif differ diff --git a/Themes/core/images/topic/my_veryhot_poll.gif b/Themes/core/images/topic/my_veryhot_poll.gif new file mode 100644 index 0000000..dbcbb3f Binary files /dev/null and b/Themes/core/images/topic/my_veryhot_poll.gif differ diff --git a/Themes/core/images/topic/my_veryhot_post.gif b/Themes/core/images/topic/my_veryhot_post.gif new file mode 100644 index 0000000..995e903 Binary files /dev/null and b/Themes/core/images/topic/my_veryhot_post.gif differ diff --git a/Themes/core/images/topic/normal_poll.gif b/Themes/core/images/topic/normal_poll.gif new file mode 100644 index 0000000..ff8e364 Binary files /dev/null and b/Themes/core/images/topic/normal_poll.gif differ diff --git a/Themes/core/images/topic/normal_poll_locked.gif b/Themes/core/images/topic/normal_poll_locked.gif new file mode 100644 index 0000000..812e1c3 Binary files /dev/null and b/Themes/core/images/topic/normal_poll_locked.gif differ diff --git a/Themes/core/images/topic/normal_poll_locked_sticky.gif b/Themes/core/images/topic/normal_poll_locked_sticky.gif new file mode 100644 index 0000000..284f3a7 Binary files /dev/null and b/Themes/core/images/topic/normal_poll_locked_sticky.gif differ diff --git a/Themes/core/images/topic/normal_poll_sticky.gif b/Themes/core/images/topic/normal_poll_sticky.gif new file mode 100644 index 0000000..69c13b3 Binary files /dev/null and b/Themes/core/images/topic/normal_poll_sticky.gif differ diff --git a/Themes/core/images/topic/normal_post.gif b/Themes/core/images/topic/normal_post.gif new file mode 100644 index 0000000..bcb144e Binary files /dev/null and b/Themes/core/images/topic/normal_post.gif differ diff --git a/Themes/core/images/topic/normal_post_locked.gif b/Themes/core/images/topic/normal_post_locked.gif new file mode 100644 index 0000000..46938d6 Binary files /dev/null and b/Themes/core/images/topic/normal_post_locked.gif differ diff --git a/Themes/core/images/topic/normal_post_locked_sticky.gif b/Themes/core/images/topic/normal_post_locked_sticky.gif new file mode 100644 index 0000000..e3e5089 Binary files /dev/null and b/Themes/core/images/topic/normal_post_locked_sticky.gif differ diff --git a/Themes/core/images/topic/normal_post_sticky.gif b/Themes/core/images/topic/normal_post_sticky.gif new file mode 100644 index 0000000..089644f Binary files /dev/null and b/Themes/core/images/topic/normal_post_sticky.gif differ diff --git a/Themes/core/images/topic/veryhot_poll.gif b/Themes/core/images/topic/veryhot_poll.gif new file mode 100644 index 0000000..8425653 Binary files /dev/null and b/Themes/core/images/topic/veryhot_poll.gif differ diff --git a/Themes/core/images/topic/veryhot_post.gif b/Themes/core/images/topic/veryhot_post.gif new file mode 100644 index 0000000..3af2b5d Binary files /dev/null and b/Themes/core/images/topic/veryhot_post.gif differ diff --git a/Themes/core/images/topic/veryhot_post_locked.gif b/Themes/core/images/topic/veryhot_post_locked.gif new file mode 100644 index 0000000..d5b265a Binary files /dev/null and b/Themes/core/images/topic/veryhot_post_locked.gif differ diff --git a/Themes/core/images/topic/veryhot_post_locked_sticky.gif b/Themes/core/images/topic/veryhot_post_locked_sticky.gif new file mode 100644 index 0000000..a2b6458 Binary files /dev/null and b/Themes/core/images/topic/veryhot_post_locked_sticky.gif differ diff --git a/Themes/core/images/topic/veryhot_post_sticky.gif b/Themes/core/images/topic/veryhot_post_sticky.gif new file mode 100644 index 0000000..dfdabe9 Binary files /dev/null and b/Themes/core/images/topic/veryhot_post_sticky.gif differ diff --git a/Themes/core/images/upshrink.gif b/Themes/core/images/upshrink.gif new file mode 100644 index 0000000..6286ed8 Binary files /dev/null and b/Themes/core/images/upshrink.gif differ diff --git a/Themes/core/images/upshrink2.gif b/Themes/core/images/upshrink2.gif new file mode 100644 index 0000000..f8f6813 Binary files /dev/null and b/Themes/core/images/upshrink2.gif differ diff --git a/Themes/core/images/useroff.gif b/Themes/core/images/useroff.gif new file mode 100644 index 0000000..def3acf Binary files /dev/null and b/Themes/core/images/useroff.gif differ diff --git a/Themes/core/images/useron.gif b/Themes/core/images/useron.gif new file mode 100644 index 0000000..71f822f Binary files /dev/null and b/Themes/core/images/useron.gif differ diff --git a/Themes/core/images/warn.gif b/Themes/core/images/warn.gif new file mode 100644 index 0000000..d78a097 Binary files /dev/null and b/Themes/core/images/warn.gif differ diff --git a/Themes/core/images/warning_moderate.gif b/Themes/core/images/warning_moderate.gif new file mode 100644 index 0000000..d78a097 Binary files /dev/null and b/Themes/core/images/warning_moderate.gif differ diff --git a/Themes/core/images/warning_mute.gif b/Themes/core/images/warning_mute.gif new file mode 100644 index 0000000..674b548 Binary files /dev/null and b/Themes/core/images/warning_mute.gif differ diff --git a/Themes/core/images/warning_watch.gif b/Themes/core/images/warning_watch.gif new file mode 100644 index 0000000..f2f4d87 Binary files /dev/null and b/Themes/core/images/warning_watch.gif differ diff --git a/Themes/core/images/www.gif b/Themes/core/images/www.gif new file mode 100644 index 0000000..a7cfe20 Binary files /dev/null and b/Themes/core/images/www.gif differ diff --git a/Themes/core/images/www_sm.gif b/Themes/core/images/www_sm.gif new file mode 100644 index 0000000..a7cfe20 Binary files /dev/null and b/Themes/core/images/www_sm.gif differ diff --git a/Themes/core/index.php b/Themes/core/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/core/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/core/index.template.php b/Themes/core/index.template.php new file mode 100644 index 0000000..317fc40 --- /dev/null +++ b/Themes/core/index.template.php @@ -0,0 +1,522 @@ + +'; + + // The ?fin20 part of this link is just here to make sure browsers don't cache it wrongly. + echo ' + '; + + // Some browsers need an extra stylesheet due to bugs/compatibility issues. + foreach (array('ie7', 'ie6', 'webkit') as $cssfix) + if ($context['browser']['is_' . $cssfix]) + echo ' + '; + + // RTL languages require an additional stylesheet. + if ($context['right_to_left']) + echo ' + '; + + // Here comes the JavaScript bits! + echo ' + + + '; + + echo ' + + ', !empty($context['meta_keywords']) ? ' + ' : '', ' + ', $context['page_title_html_safe'], ''; + + // Please don't index these Mr Robot. + if (!empty($context['robot_no_index'])) + echo ' + '; + + // Present a canonical url for search engines to prevent duplicate content in their indices. + if (!empty($context['canonical_url'])) + echo ' + '; + + // Show all the relative links, such as help, search, contents, and the like. + echo ' + + + '; + + // If RSS feeds are enabled, advertise the presence of one. + if (!empty($modSettings['xmlnews_enable']) && (!empty($modSettings['allow_guestAccess']) || $context['user']['is_logged'])) + echo ' + '; + + // If we're viewing a topic, these should be the previous and next topics, respectively. + if (!empty($context['current_topic'])) + echo ' + + '; + + // If we're in a board, or a topic for that matter, the index will be the board's index. + if (!empty($context['current_board'])) + echo ' + '; + + // We'll have to use the cookie to remember the header... + if ($context['user']['is_guest']) + { + $options['collapse_header'] = !empty($_COOKIE['upshrink']); + $options['collapse_header_ic'] = !empty($_COOKIE['upshrinkIC']); + } + + // Output any remaining HTML headers. (from mods, maybe?) + echo $context['html_headers']; + + echo ' + +'; +} + +function template_body_above() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    +
    +
    + +

    '; + + if (empty($context['header_logo_url_html_safe'])) + echo $context['forum_name_html_safe']; + else + echo ' + ', $context['forum_name_html_safe'], ''; + + echo ' +

    +
    '; + + // Display user name and time. + echo ' +
      +
    • + ', $context['current_time'], ' + +
    • '; + + if ($context['user']['is_logged']) + echo ' +
    • ', $txt['hello_member_ndt'], ' ', $context['user']['name'], '
    • '; + else + echo ' +
    • ', $txt['hello_guest'], ' ', $txt['guest'], '
    • '; + + echo ' +
    '; + + if ($context['user']['is_logged'] || !empty($context['show_login_bar'])) + echo ' + '; + + echo ' + +
    '; + + // Define the upper_section toggle in JavaScript. + echo ' + '; + + // Show the menu here, according to the menu sub template. + template_menu(); + + // Show the navigation tree. + theme_linktree(); + + // The main content should go here. + echo ' +
    '; +} + +function template_body_below() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    '; + + // Show the "Powered by" and "Valid" logos, as well as the copyright. Remember, the copyright must be somewhere! + echo ' +
    + '; + + // Show the load time? + if ($context['show_load_time']) + echo ' +

    ', $txt['page_created'], $context['load_time'], $txt['seconds_with'], $context['load_queries'], $txt['queries'], '

    '; + + echo ' +
    +
    '; +} + +function template_html_below() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +'; +} + +// Show a linktree. This is that thing that shows "My Community | General Category | General Discussion".. +function theme_linktree($force_show = false) +{ + global $context, $settings, $options, $shown_linktree; + + // If linktree is empty, just return - also allow an override. + if (empty($context['linktree']) || (!empty($context['dont_default_linktree']) && !$force_show)) + return; + + echo ' +
      '; + + // Each tree item has a URL and name. Some may have extra_before and extra_after. + foreach ($context['linktree'] as $link_num => $tree) + { + echo ' + '; + + // Show something before the link? + if (isset($tree['extra_before'])) + echo $tree['extra_before']; + + // Show the link, including a URL if it should have one. + echo $settings['linktree_link'] && isset($tree['url']) ? ' + ' . $tree['name'] . '' : '' . $tree['name'] . ''; + + // Show something after the link...? + if (isset($tree['extra_after'])) + echo $tree['extra_after']; + + // Don't show a separator for the last one. + if ($link_num != count($context['linktree']) - 1) + echo ' >'; + + echo ' + '; + } + echo ' +
    '; + + $shown_linktree = true; +} + +// Show the menu up top. Something like [home] [help] [profile] [logout]... +function template_menu() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' + '; +} + +// Generate a strip of buttons. +function template_button_strip($button_strip, $direction = 'top', $strip_options = array()) +{ + global $settings, $context, $txt, $scripturl; + + if (!is_array($strip_options)) + $strip_options = array(); + + // Right to left menu should be in reverse order. + if ($context['right_to_left']) + $button_strip = array_reverse($button_strip, true); + + // Create the buttons... + $buttons = array(); + foreach ($button_strip as $key => $value) + if (!isset($value['test']) || !empty($context[$value['test']])) + $buttons[] = ' + ' . (isset($value['active']) ? '' . $txt[$value['text']] . '' : $txt[$value['text']]) . ''; + + // No buttons? No button strip either. + if (empty($buttons)) + return; + + // Make the last one, as easy as possible. + $list_item = array('
  1. ', '
  2. '); + $active_item = array('
  3. ', '
  4. '); + + $buttons[count($buttons) - 1] = str_replace($list_item, $active_item, $buttons[count($buttons) - 1]); + + echo ' + '; +} + +?> \ No newline at end of file diff --git a/Themes/core/languages/Settings.english.php b/Themes/core/languages/Settings.english.php new file mode 100644 index 0000000..5629a40 --- /dev/null +++ b/Themes/core/languages/Settings.english.php @@ -0,0 +1,9 @@ +
    Author: The Simple Machines Team'; + +?> \ No newline at end of file diff --git a/Themes/core/languages/index.php b/Themes/core/languages/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/core/languages/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/core/license.txt b/Themes/core/license.txt new file mode 100644 index 0000000..7e09434 --- /dev/null +++ b/Themes/core/license.txt @@ -0,0 +1,27 @@ +Copyright © 2011 Simple Machines. All rights reserved. + +Developed by: Simple Machines Forum Project + Simple Machines + http://www.simplemachines.org + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + 3. Neither the names of Simple Machines Forum, Simple Machines, nor + the names of its contributors may be used to endorse or promote + products derived from this Software without specific prior written + permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +WITH THE SOFTWARE. + +This license may be viewed online at http://www.simplemachines.org/about/smf/license.php \ No newline at end of file diff --git a/Themes/core/scripts/theme.js b/Themes/core/scripts/theme.js new file mode 100644 index 0000000..84a1cbd --- /dev/null +++ b/Themes/core/scripts/theme.js @@ -0,0 +1,97 @@ +// The purpose of this code is to fix the height of overflow: auto blocks, because some browsers can't figure it out for themselves. +function smf_codeBoxFix() +{ + var codeFix = document.getElementsByTagName('code'); + for (var i = codeFix.length - 1; i >= 0; i--) + { + if (is_webkit && codeFix[i].offsetHeight < 20) + codeFix[i].style.height = (codeFix[i].offsetHeight + 20) + 'px'; + + else if (is_ff && (codeFix[i].scrollWidth > codeFix[i].clientWidth || codeFix[i].clientWidth == 0)) + codeFix[i].style.overflow = 'scroll'; + + else if ('currentStyle' in codeFix[i] && codeFix[i].currentStyle.overflow == 'auto' && (codeFix[i].currentStyle.height == '' || codeFix[i].currentStyle.height == 'auto') && (codeFix[i].scrollWidth > codeFix[i].clientWidth || codeFix[i].clientWidth == 0) && (codeFix[i].offsetHeight != 0)) + codeFix[i].style.height = (codeFix[i].offsetHeight + 24) + 'px'; + } +} + +// Add a fix for code stuff? +if ((is_ie && !is_ie4) || is_webkit || is_ff) + addLoadEvent(smf_codeBoxFix); + +// Toggles the element height and width styles of an image. +function smc_toggleImageDimensions() +{ + var oImages = document.getElementsByTagName('IMG'); + for (oImage in oImages) + { + // Not a resized image? Skip it. + if (oImages[oImage].className == undefined || oImages[oImage].className.indexOf('bbc_img resized') == -1) + continue; + + oImages[oImage].style.cursor = 'pointer'; + oImages[oImage].onclick = function() { + this.style.width = this.style.height = this.style.width == 'auto' ? null : 'auto'; + }; + } +} + +// Add a load event for the function above. +addLoadEvent(smc_toggleImageDimensions); + +// Adds a button to a certain button strip. +function smf_addButton(sButtonStripId, bUseImage, oOptions) +{ + var oButtonStrip = document.getElementById(sButtonStripId); + var aItems = oButtonStrip.getElementsByTagName('li'); + + // Remove the 'last' class from the last item. + if (aItems.length > 0) + { + var oLastItem = aItems[aItems.length - 1]; + oLastItem.className = oLastItem.className.replace(/\s*last/, 'position_holder'); + } + + // Add the button. + var oButtonStripList = oButtonStrip.getElementsByTagName('ul')[0]; + var oNewButton = document.createElement('li'); + oNewButton.className = 'last'; + setInnerHTML(oNewButton, '' + oOptions.sText + ''); + + oButtonStripList.appendChild(oNewButton); +} + +// Adds hover events to list items. Used for a versions of IE that don't support this by default. +var smf_addListItemHoverEvents = function() +{ + var cssRule, newSelector; + + // Add a rule for the list item hover event to every stylesheet. + for (var iStyleSheet = 0; iStyleSheet < document.styleSheets.length; iStyleSheet ++) + for (var iRule = 0; iRule < document.styleSheets[iStyleSheet].rules.length; iRule ++) + { + oCssRule = document.styleSheets[iStyleSheet].rules[iRule]; + if (oCssRule.selectorText.indexOf('LI:hover') != -1) + { + sNewSelector = oCssRule.selectorText.replace(/LI:hover/gi, 'LI.iehover'); + document.styleSheets[iStyleSheet].addRule(sNewSelector, oCssRule.style.cssText); + } + } + + // Now add handling for these hover events. + var oListItems = document.getElementsByTagName('LI'); + for (oListItem in oListItems) + { + oListItems[oListItem].onmouseover = function() { + this.className += ' iehover'; + }; + + oListItems[oListItem].onmouseout = function() { + this.className = this.className.replace(new RegExp(' iehover\\b'), ''); + }; + } +} + +// Add hover events to list items if the browser requires it. +if (is_ie6down && 'attachEvent' in window) + window.attachEvent('onload', smf_addListItemHoverEvents); diff --git a/Themes/core/theme_info.xml b/Themes/core/theme_info.xml new file mode 100644 index 0000000..ee49be0 --- /dev/null +++ b/Themes/core/theme_info.xml @@ -0,0 +1,15 @@ + + + + Core Theme + + info@simplemachines.org + + http://www.simplemachines.org/ + + html,body + + index + + + diff --git a/Themes/default/Admin.template.php b/Themes/default/Admin.template.php new file mode 100644 index 0000000..3a5fd9c --- /dev/null +++ b/Themes/default/Admin.template.php @@ -0,0 +1,2112 @@ + +
    +

    '; + + if ($context['user']['is_admin']) + echo ' + +
    + + + + +
    +
    '; + + echo $txt['admin_center'], ' +

    +
    + +
    +
    + ', $txt['hello_guest'], ' ', $context['user']['name'], '! + ', sprintf($txt['admin_main_welcome'], $txt['admin_center'], $txt['help'], $txt['help']), ' +
    +
    + '; + + // Is there an update available? + echo ' +
    '; + + echo ' +
    '; + + // Display the "live news" from simplemachines.org. + echo ' +
    +
    +

    + ', $txt['help'], ' ', $txt['live'], ' +

    +
    +
    + +
    +
    ', $txt['lfyi'], '
    +
    + +
    +
    '; + + // Show the user version information from their server. + echo ' +
    + +
    + +
    +
    + ', $txt['support_versions'], ':
    + ', $txt['support_versions_forum'], ': + ', $context['forum_version'], '
    + ', $txt['support_versions_current'], ': + ??
    + ', $context['can_admin'] ? '' . $txt['version_check_more'] . '' : '', '
    '; + + // Display all the members who can administrate the forum. + echo ' +
    + ', $txt['administrators'], ': + ', implode(', ', $context['administrators']); + // If we have lots of admins... don't show them all. + if (!empty($context['more_admins_link'])) + echo ' + (', $context['more_admins_link'], ')'; + + echo ' +
    +
    + +
    +
    +
    '; + + echo ' +
    + +
    +
      '; + + foreach ($context['quick_admin_tasks'] as $task) + echo ' +
    • + ', !empty($task['icon']) ? '' : '', ' +
      ', $task['link'], '
      + ', $task['description'],' +
    • '; + + echo ' +
    +
    + +
    +
  5. +
    '; + + // The below functions include all the scripts needed from the simplemachines.org site. The language and format are passed for internationalization. + if (empty($modSettings['disable_smf_js'])) + echo ' + + '; + + // This sets the announcements and current versions themselves ;). + echo ' + + '; +} + +// Show some support information and credits to those who helped make this. +function template_credits() +{ + global $context, $settings, $options, $scripturl, $txt; + + // Show the user version information from their server. + echo ' + +
    +
    +

    + ', $txt['support_title'], ' +

    +
    +
    + +
    + ', $txt['support_versions'], ':
    + ', $txt['support_versions_forum'], ': + ', $context['forum_version'], '', $context['can_admin'] ? ' ' . $txt['version_check_more'] . '' : '', '
    + ', $txt['support_versions_current'], ': + ??
    '; + + // Display all the variables we have server information for. + foreach ($context['current_versions'] as $version) + echo ' + ', $version['title'], ': + ', $version['version'], '
    '; + + echo ' +
    + +
    + '; + + // Point the admin to common support resources. + echo ' +
    +

    + ', $txt['support_resources'], ' +

    +
    +
    + +
    +

    ', $txt['support_resources_p1'], '

    +

    ', $txt['support_resources_p2'], '

    +
    + +
    '; + + // Display latest support questions from simplemachines.org. + echo ' +
    +

    + ', $txt['help'], ' ', $txt['support_latest'], ' +

    +
    +
    + +
    +
    ', $txt['support_latest_fetch'], '
    +
    + +
    '; + + // The most important part - the credits :P. + echo ' +
    +

    + ', $txt['admin_credits'], ' +

    +
    +
    + +
    '; + + foreach ($context['credits'] as $section) + { + if (isset($section['pretext'])) + echo ' +

    ', $section['pretext'], '

    '; + + echo ' +
    '; + + foreach ($section['groups'] as $group) + { + if (isset($group['title'])) + echo ' +
    + ', $group['title'], ': +
    '; + + echo ' +
    ', implode(', ', $group['members']), '
    '; + } + + echo ' +
    '; + + if (isset($section['posttext'])) + echo ' +

    ', $section['posttext'], '

    '; + } + + echo ' +
    + +
    +
    +
    '; + + // This makes all the support information available to the support script... + echo ' + + + + '; + + // This sets the latest support stuff. + echo ' + '; +} + +// Displays information about file versions installed, and compares them to current version. +function template_view_versions() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    +

    + ', $txt['admin_version_check'], ' +

    +
    +
    ', $txt['version_check_desc'], '
    + + + + + + + + + '; + + // The current version of the core SMF package. + echo ' + + + + + '; + + // Now list all the source file versions, starting with the overall version (if all match!). + echo ' + + + + + + +
    + ', $txt['admin_smffile'], ' + + ', $txt['dvc_your'], ' + + ', $txt['dvc_current'], ' +
    + ', $txt['admin_smfpackage'], ' + + ', $context['forum_version'], ' + + ?? +
    + ', $txt['dvc_sources'], ' + + ?? + + ?? +
    + + + '; + + // Loop through every source file displaying its version - using javascript. + foreach ($context['file_versions'] as $filename => $version) + echo ' + + + + + '; + + // Default template files. + echo ' + +
    + ', $filename, ' + + ', $version, ' + + ?? +
    + + + + + + + + + +
    + ', $txt['dvc_default'], ' + + ?? + + ?? +
    + + + '; + + foreach ($context['default_template_versions'] as $filename => $version) + echo ' + + + + + '; + + // Now the language files... + echo ' + +
    + ', $filename, ' + + ', $version, ' + + ?? +
    + + + + + + + + + +
    + ', $txt['dvc_languages'], ' + + ?? + + ?? +
    + + + '; + + foreach ($context['default_language_versions'] as $language => $files) + { + foreach ($files as $filename => $version) + echo ' + + + + + '; + } + + echo ' + +
    + ', $filename, '.', $language, '.php + + ', $version, ' + + ?? +
    '; + + // Finally, display the version information for the currently selected theme - if it is not the default one. + if (!empty($context['template_versions'])) + { + echo ' + + + + + + + + +
    + ', $txt['dvc_templates'], ' + + ?? + + ?? +
    + + + '; + + foreach ($context['template_versions'] as $filename => $version) + echo ' + + + + + '; + + echo ' + +
    + ', $filename, ' + + ', $version, ' + + ?? +
    '; + } + + echo ' +
    +
    '; + + /* Below is the hefty javascript for this. Upon opening the page it checks the current file versions with ones + held at simplemachines.org and works out if they are up to date. If they aren't it colors that files number + red. It also contains the function, swapOption, that toggles showing the detailed information for each of the + file categories. (sources, languages, and templates.) */ + echo ' + + + '; + +} + +// Form for stopping people using naughty words, etc. +function template_edit_censored() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + // First section is for adding/removing words from the censored list. + echo ' +
    +
    +
    +

    + ', $txt['admin_censored_words'], ' +

    +
    +
    + +
    +

    ', $txt['admin_censored_where'], '

    '; + + // Show text boxes for censoring [bad ] => [good ]. + foreach ($context['censored_words'] as $vulgar => $proper) + echo ' +
    =>
    '; + + // Now provide a way to censor more words. + echo ' + +
    + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    + +
    '; + + // This table lets you test out your filters by typing in rude words and seeing what comes out. + echo ' +
    +

    + ', $txt['censor_test'], ' +

    +
    +
    + +
    +

    + + +

    +
    + +
    + + +
    +
    +
    '; +} + +// Maintenance is a lovely thing, isn't it? +function template_not_done() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +

    + ', $txt['not_done_title'], ' +

    +
    +
    + +
    + ', $txt['not_done_reason']; + + if (!empty($context['continue_percent'])) + echo ' +
    +
    +
    ', $context['continue_percent'], '%
    +
     
    +
    +
    '; + + if (!empty($context['substep_enabled'])) + echo ' +
    + ', $context['substep_title'], ' +
    +
    ', $context['substep_continue_percent'], '%
    +
     
    +
    +
    '; + + echo ' +
    +
    + ', $context['continue_post_data'], ' +
    +
    + +
    +
    +
    + '; +} + +// Template for showing settings (Of any kind really!) +function template_show_settings() +{ + global $context, $txt, $settings, $scripturl; + + echo ' + '; + + if (!empty($context['settings_insert_above'])) + echo $context['settings_insert_above']; + + echo ' +
    +
    '; + + // Is there a custom title? + if (isset($context['settings_title'])) + echo ' +
    +

    + ', $context['settings_title'], ' +

    +
    '; + + // Have we got some custom code to insert? + if (!empty($context['settings_message'])) + echo ' +
    ', $context['settings_message'], '
    '; + + // Now actually loop through all the variables. + $is_open = false; + foreach ($context['config_vars'] as $config_var) + { + // Is it a title or a description? + if (is_array($config_var) && ($config_var['type'] == 'title' || $config_var['type'] == 'desc')) + { + // Not a list yet? + if ($is_open) + { + $is_open = false; + echo ' + +
    + +
    '; + } + + // A title? + if ($config_var['type'] == 'title') + { + echo ' +
    +

    + ', ($config_var['help'] ? '' . $txt['help'] . '' : ''), ' + ', $config_var['label'], ' +

    +
    '; + } + // A description? + else + { + echo ' +

    + ', $config_var['label'], ' +

    '; + } + + continue; + } + + // Not a list yet? + if (!$is_open) + { + $is_open = true; + echo ' +
    + +
    +
    '; + } + + // Hang about? Are you pulling my leg - a callback?! + if (is_array($config_var) && $config_var['type'] == 'callback') + { + if (function_exists('template_callback_' . $config_var['name'])) + call_user_func('template_callback_' . $config_var['name']); + + continue; + } + + if (is_array($config_var)) + { + // First off, is this a span like a message? + if (in_array($config_var['type'], array('message', 'warning'))) + { + echo ' + + ', $config_var['label'], ' + '; + } + // Otherwise it's an input box of some kind. + else + { + echo ' + '; + + // Some quick helpers... + $javascript = $config_var['javascript']; + $disabled = !empty($config_var['disabled']) ? ' disabled="disabled"' : ''; + $subtext = !empty($config_var['subtext']) ? '
    ' . $config_var['subtext'] . '' : ''; + + // Show the [?] button. + if ($config_var['help']) + echo ' + ', $txt['help'], '', $subtext, ($config_var['type'] == 'password' ? '
    ' . $txt['admin_confirm_password'] . '' : ''), ' + '; + else + echo ' + ', $subtext, ($config_var['type'] == 'password' ? '
    ' . $txt['admin_confirm_password'] . '' : ''), ' + '; + + echo ' + ', + $config_var['preinput']; + + // Show a check box. + if ($config_var['type'] == 'check') + echo ' + '; + // Escape (via htmlspecialchars.) the text box. + elseif ($config_var['type'] == 'password') + echo ' +
    + '; + // Show a selection box. + elseif ($config_var['type'] == 'select') + { + echo ' + '; + } + // Text area? + elseif ($config_var['type'] == 'large_text') + echo ' + '; + // Permission group? + elseif ($config_var['type'] == 'permissions') + theme_inline_permissions($config_var['name']); + // BBC selection? + elseif ($config_var['type'] == 'bbc') + { + echo ' +
    + ', $txt['bbcTagsToUse_select'], ' +
      '; + + foreach ($context['bbc_columns'] as $bbcColumn) + { + foreach ($bbcColumn as $bbcTag) + echo ' +
    • + ', $bbcTag['show_help'] ? ' (?)' : '', ' +
    • '; + } + echo '
    + +
    '; + } + // A simple message? + elseif ($config_var['type'] == 'var_message') + echo ' + ', $config_var['var_message'], '
    '; + // Assume it must be a text box. + else + echo ' + '; + + echo isset($config_var['postinput']) ? ' + ' . $config_var['postinput'] : '', + ''; + } + } + + else + { + // Just show a separator. + if ($config_var == '') + echo ' + +
    +
    '; + else + echo ' +
    + ' . $config_var . ' +
    '; + } + } + + if ($is_open) + echo ' +
    '; + + if (empty($context['settings_save_dont_show'])) + echo ' +
    +
    + +
    '; + + if ($is_open) + echo ' +
    + + '; + + echo ' + + + +
    '; + + if (!empty($context['settings_post_javascript'])) + echo ' + '; + + if (!empty($context['settings_insert_below'])) + echo $context['settings_insert_below']; +} + +// Template for showing custom profile fields. +function template_show_custom_profile() +{ + global $context, $txt, $settings, $scripturl; + + // Standard fields. + template_show_list('standard_profile_fields'); + + echo ' +
    '; + + // Custom fields. + template_show_list('custom_profile_fields'); +} + +// Edit a profile field? +function template_edit_profile_field() +{ + global $context, $txt, $settings, $scripturl; + + // All the javascript for this page - quite a bit! + echo ' + '; + + echo ' +
    +
    +
    +

    + ', $context['page_title'], ' +

    +
    +
    + +
    +
    + ', $txt['custom_edit_general'], ' + +
    +
    + ', $txt['custom_edit_name'], ': +
    +
    + +
    +
    + ', $txt['custom_edit_desc'], ': +
    +
    + +
    +
    + ', $txt['custom_edit_profile'], ':
    + ', $txt['custom_edit_profile_desc'], ' +
    +
    + +
    +
    + ', $txt['custom_edit_registration'], ': +
    +
    + +
    +
    + ', $txt['custom_edit_display'], ': +
    +
    + +
    + +
    + ', $txt['custom_edit_placement'], ': +
    +
    + +
    +
    + ', $txt['help'], ' + ', $txt['custom_edit_enclose'], ':
    + ', $txt['custom_edit_enclose_desc'], ' +
    +
    + +
    +
    +
    +
    + ', $txt['custom_edit_input'], ' +
    +
    + ', $txt['custom_edit_picktype'], ': +
    +
    + +
    +
    + ', $txt['custom_edit_max_length'], ':
    + ', $txt['custom_edit_max_length_desc'], ' +
    +
    + +
    +
    + ', $txt['custom_edit_dimension'], ': +
    +
    + ', $txt['custom_edit_dimension_row'], ': + ', $txt['custom_edit_dimension_col'], ': +
    +
    + ', $txt['custom_edit_bbc'], ' +
    +
    + +
    +
    + ', $txt['help'], ' + ', $txt['custom_edit_options'], ':
    + ', $txt['custom_edit_options_desc'], ' +
    +
    +
    '; + + foreach ($context['field']['options'] as $k => $option) + { + echo ' + ', $k == 0 ? '' : '
    ', ''; + } + echo ' + + [', $txt['custom_edit_options_more'], '] +
    +
    +
    + ', $txt['custom_edit_default'], ': +
    +
    + +
    +
    +
    +
    + ', $txt['custom_edit_advanced'], ' +
    +
    + ', $txt['help'], ' + ', $txt['custom_edit_mask'], ':
    + ', $txt['custom_edit_mask_desc'], ' +
    +
    + +
    + + + +
    +
    + ', $txt['custom_edit_privacy'], ': + ', $txt['custom_edit_privacy_desc'], ' +
    +
    + +
    +
    + ', $txt['custom_edit_can_search'], ':
    + ', $txt['custom_edit_can_search_desc'], ' +
    +
    + +
    +
    + ', $txt['custom_edit_active'], ':
    + ', $txt['custom_edit_active_desc'], ' +
    +
    + +
    +
    +
    +
    + '; + + if ($context['fid']) + echo ' + '; + + echo ' +
    +
    + +
    + +
    +
    +
    '; + + // Get the javascript bits right! + echo ' + '; +} + +// Results page for an admin search. +function template_admin_search_results() +{ + global $context, $txt, $settings, $options, $scripturl; + + echo ' +
    +

    + +
    + + + +
    +
    +  ', sprintf($txt['admin_search_results_desc'], $context['search_term']), ' +

    +
    +
    + +
    '; + + if (empty($context['search_results'])) + { + echo ' +

    ', $txt['admin_search_results_none'], '

    '; + } + else + { + echo ' +
      '; + foreach ($context['search_results'] as $result) + { + // Is it a result from the online manual? + if ($context['search_type'] == 'online') + { + echo ' +
    1. +

      + ', $result['messages'][0]['subject'], ' +
      ', $result['category']['name'], '  /  + ', $result['board']['name'], ' / +

      +

      + ', $result['messages'][0]['body'], ' +

      +
    2. '; + } + // Otherwise it's... not! + else + { + echo ' +
    3. + ', $result['name'], ' [', isset($txt['admin_search_section_' . $result['type']]) ? $txt['admin_search_section_' . $result['type']] : $result['type'] , ']'; + + if ($result['help']) + echo ' +

      ', $result['help'], '

      '; + + echo ' +
    4. '; + } + } + echo ' +
    '; + } + + echo ' +
    + +
    +
    '; +} + +// Turn on and off certain key features. +function template_core_features() +{ + global $context, $txt, $settings, $options, $scripturl; + + echo ' + +
    '; + if ($context['is_new_install']) + { + echo ' +
    +

    + ', $txt['core_settings_welcome_msg'], ' +

    +
    +
    + ', $txt['core_settings_welcome_msg_desc'], ' +
    '; + } + + echo ' +
    +
    +

    + ', $txt['core_settings_title'], ' +

    +
    '; + + $alternate = true; + foreach ($context['features'] as $id => $feature) + { + echo ' +
    + +
    + ', $feature['title'], ' + +

    ', ($feature['enabled'] && $feature['url'] ? '' . $feature['title'] . '' : $feature['title']), '

    +

    ', $feature['desc'], '

    +
    + + +
    +
    + +
    '; + + $alternate = !$alternate; + } + + echo ' +
    + + + +
    +
    +
    +
    '; + + // Turn on the pretty javascript if we can! + echo ' + '; +} + +// Add a new language +function template_add_language() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +
    +

    + ', $txt['add_language'], ' +

    +
    +
    + +
    +
    + ', $txt['add_language_smf'], ' + + '; + + if (!empty($context['smf_error'])) + echo ' +
    ', $txt['add_language_error_' . $context['smf_error']], '
    '; + + echo ' + +
    +
    + ', $context['browser']['is_ie'] ? ' ' : '', ' + +
    +
    + +
    + '; + + // Had some results? + if (!empty($context['smf_languages'])) + { + echo ' +
    ', $txt['add_language_smf_found'], '
    + + + + + + + + + + + + '; + + foreach ($context['smf_languages'] as $language) + echo ' + + + + + + + '; + + echo ' + +
    ', $txt['name'], '', $txt['add_language_smf_desc'], '', $txt['add_language_smf_version'], '', $txt['add_language_smf_utf8'], '', $txt['add_language_smf_install'], '
    ', $language['name'], '', $language['description'], '', $language['version'], '', $language['utf8'] ? $txt['yes'] : $txt['no'], '', $txt['add_language_smf_install'], '
    '; + } + + echo ' +
    +
    +
    '; +} + +// Download a new language file? +function template_download_language() +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings; + + // Actually finished? + if (!empty($context['install_complete'])) + { + echo ' +
    +
    +

    + ', $txt['languages_download_complete'], ' +

    +
    +
    + +
    + ', $context['install_complete'], ' +
    + +
    +
    +
    '; + return; + } + + // An error? + if (!empty($context['error_message'])) + echo ' +
    +

    ', $context['error_message'], '

    +
    '; + + // Provide something of an introduction... + echo ' +
    +
    +
    +

    + ', $txt['languages_download'], ' +

    +
    +
    + +
    +

    + ', $txt['languages_download_note'], ' +

    +
    + ', $txt['languages_download_info'], ' +
    +
    + +
    '; + + // Show the main files. + template_show_list('lang_main_files_list'); + + // Now, all the images and the likes, hidden via javascript 'cause there are so fecking many. + echo ' +
    +
    +

    + ', $txt['languages_download_theme_files'], ' +

    +
    + + + + + + + + + + '; + + foreach ($context['files']['images'] as $theme => $group) + { + $count = 0; + echo ' + + + '; + + $alternate = false; + foreach ($group as $file) + { + echo ' + + + + + + '; + $alternate = !$alternate; + } + } + + echo ' + +
    + ', $txt['languages_download_filename'], ' + + ', $txt['languages_download_writable'], ' + + ', $txt['languages_download_exists'], ' + + ', $txt['languages_download_copy'], ' +
    + * ', isset($context['theme_names'][$theme]) ? $context['theme_names'][$theme] : $theme, ' +
    + ', $file['name'], '
    + ', $txt['languages_download_dest'], ': ', $file['destination'], ' +
    + ', ($file['writable'] ? $txt['yes'] : $txt['no']), ' + + ', $file['exists'] ? ($file['exists'] == 'same' ? $txt['languages_download_exists_same'] : $txt['languages_download_exists_different']) : $txt['no'], ' + + +
    '; + + // Do we want some FTP baby? + if (!empty($context['still_not_writable'])) + { + if (!empty($context['package_ftp']['error'])) + echo ' +
    + ', $context['package_ftp']['error'], ' +
    '; + + echo ' +
    +

    + ', $txt['package_ftp_necessary'], ' +

    +
    +
    + +
    +

    ', $txt['package_ftp_why'],'

    +
    +
    ', $txt['package_ftp_server'], ': +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    +
    +
    + +
    '; + } + + // Install? + echo ' +
    + + +
    +
    +
    +
    '; + + // The javascript for expand and collapse of sections. + echo ' + '; +} + +// Edit some language entries? +function template_modify_language_entries() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +
    +

    + ', $txt['edit_languages'], ' +

    +
    '; + + // Not writable? + if ($context['lang_file_not_writable_message']) + echo ' +
    +

    ', $context['lang_file_not_writable_message'], '

    +
    '; + + echo ' +
    + ', $txt['edit_language_entries_primary'], ' +
    +
    + +
    +
    + ', $context['primary_settings']['name'], ' +
    +
    + ', $txt['languages_character_set'], ': +
    +
    + +
    +
    + ', $txt['languages_locale'], ': +
    +
    + +
    +
    + ', $txt['languages_dictionary'], ': +
    +
    + +
    +
    + ', $txt['languages_spelling'], ': +
    +
    + +
    +
    + ', $txt['languages_rtl'], ': +
    +
    + +
    +
    +
    +
    + + '; + + // English can't be deleted. + if ($context['lang_id'] != 'english') + echo ' + '; + + echo ' +
    +
    + +
    +
    + +
    +
    +

    + ', $txt['edit_language_entries'], ' +

    +
    +
    + ', $txt['edit_language_entries_file'], ': + + + +
    '; + + // Is it not writable? + if (!empty($context['entries_not_writable_message'])) + echo ' +
    + ', $context['entries_not_writable_message'], ' +
    '; + + // Already have some? + if (!empty($context['file_entries'])) + { + echo ' +
    + +
    +
    '; + + $cached = array(); + foreach ($context['file_entries'] as $entry) + { + // Do it in two's! + if (empty($cached)) + { + $cached = $entry; + continue; + } + + echo ' +
    + ', $cached['key'], ' +
    +
    + ', $entry['key'], ' +
    +
    + + +
    +
    + + +
    '; + $cached = array(); + } + + // Odd number? + if (!empty($cached)) + echo ' + +
    + ', $cached['key'], ' +
    +
    +
    +
    + + +
    +
    +
    '; + + echo ' +
    + '; + + echo ' +
    + +
    '; + } + echo ' +
    +
    +
    '; +} + +// This little beauty shows questions and answer from the captcha type feature. +function template_callback_question_answer_list() +{ + global $txt, $context; + + echo ' +
    + ', $txt['setup_verification_question'], ' +
    +
    + ', $txt['setup_verification_answer'], ' +
    '; + + foreach ($context['question_answers'] as $data) + echo ' + +
    + +
    +
    + +
    '; + + // Some blank ones. + for ($count = 0; $count < 3; $count++) + echo ' +
    + +
    +
    + +
    '; + + echo ' +
    +
    '; + + // The javascript needs to go at the end but we'll put it in this template for looks. + $context['settings_post_javascript'] .= ' + // Create a named element dynamically - thanks to: http://www.thunderguy.com/semicolon/2005/05/23/setting-the-name-attribute-in-internet-explorer/ + function createNamedElement(type, name, customFields) + { + var element = null; + + if (!customFields) + customFields = ""; + + // Try the IE way; this fails on standards-compliant browsers + try + { + element = document.createElement("<" + type + \' name="\' + name + \'" \' + customFields + ">"); + } + catch (e) + { + } + if (!element || element.nodeName != type.toUpperCase()) + { + // Non-IE browser; use canonical method to create named element + element = document.createElement(type); + element.name = name; + } + + return element; + } + + var placeHolder = document.getElementById(\'add_more_question_placeholder\'); + + function addAnotherQuestion() + { + var newDT = document.createElement("dt"); + + var newInput = createNamedElement("input", "question[]"); + newInput.type = "text"; + newInput.className = "input_text"; + newInput.size = "50"; + newInput.setAttribute("class", "verification_question"); + newDT.appendChild(newInput); + + newDD = document.createElement("dd"); + + newInput = createNamedElement("input", "answer[]"); + newInput.type = "text"; + newInput.className = "input_text"; + newInput.size = "50"; + newInput.setAttribute("class", "verification_answer"); + newDD.appendChild(newInput); + + placeHolder.parentNode.insertBefore(newDT, placeHolder); + placeHolder.parentNode.insertBefore(newDD, placeHolder); + } + document.getElementById(\'add_more_link_div\').style.display = \'\'; + '; +} + +// Repairing boards. +function template_repair_boards() +{ + global $context, $txt, $scripturl; + + echo ' +
    +
    +

    ', + $context['error_search'] ? $txt['errors_list'] : $txt['errors_fixing'] , ' +

    +
    +
    + +
    '; + + // Are we actually fixing them, or is this just a prompt? + if ($context['error_search']) + { + if (!empty($context['to_fix'])) + { + echo ' + ', $txt['errors_found'], ': +
      '; + + foreach ($context['repair_errors'] as $error) + echo ' +
    • + ', $error, ' +
    • '; + + echo ' +
    +

    + ', $txt['errors_fix'], ' +

    +

    + ', $txt['yes'], ' - ', $txt['no'], ' +

    '; + } + else + echo ' +

    ', $txt['maintain_no_errors'], '

    +

    + ', $txt['maintain_return'], ' +

    '; + + } + else + { + if (!empty($context['redirect_to_recount'])) + { + echo ' +

    + ', $txt['errors_do_recount'], ' +

    +
    + + +
    '; + } + else + { + echo ' +

    ', $txt['errors_fixed'], '

    +

    + ', $txt['maintain_return'], ' +

    '; + } + } + + echo ' +
    + +
    +
    +
    '; + + if (!empty($context['redirect_to_recount'])) + { + echo ' + '; + } +} + +?> \ No newline at end of file diff --git a/Themes/default/BoardIndex.template.php b/Themes/default/BoardIndex.template.php new file mode 100644 index 0000000..2388d35 --- /dev/null +++ b/Themes/default/BoardIndex.template.php @@ -0,0 +1,508 @@ + + ', $txt['members'], ': ', $context['common_stats']['total_members'], '  •  ', $txt['posts_made'], ': ', $context['common_stats']['total_posts'], '  •  ', $txt['topics'], ': ', $context['common_stats']['total_topics'], ' + ', ($settings['show_latest_member'] ? ' ' . $txt['welcome_member'] . ' ' . $context['common_stats']['latest_member']['link'] . '' . $txt['newest_member'] : '') , ' + '; + + // Show the news fader? (assuming there are things to show...) + if ($settings['show_newsfader'] && !empty($context['fader_news_lines'])) + { + echo ' +
    +
    +

    + + ', $txt['news'], ' +

    +
    + +
    + + '; + } + + echo ' +
    + '; + + /* Each category in categories is made up of: + id, href, link, name, is_collapsed (is it collapsed?), can_collapse (is it okay if it is?), + new (is it new?), collapse_href (href to collapse/expand), collapse_image (up/down image), + and boards. (see below.) */ + foreach ($context['categories'] as $category) + { + // If theres no parent boards we can see, avoid showing an empty category (unless its collapsed) + if (empty($category['boards']) && !$category['is_collapsed']) + continue; + + echo ' + + + + + '; + + // Assuming the category hasn't been collapsed... + if (!$category['is_collapsed']) + { + + echo ' + '; + /* Each board in each category's boards has: + new (is it new?), id, name, description, moderators (see below), link_moderators (just a list.), + children (see below.), link_children (easier to use.), children_new (are they new?), + topics (# of), posts (# of), link, href, and last_post. (see below.) */ + foreach ($category['boards'] as $board) + { + echo ' + + + + + + '; + // Show the "Child Boards: ". (there's a link_children but we're going to bold the new ones...) + if (!empty($board['children'])) + { + // Sort the links into an array with new boards bold so it can be imploded. + $children = array(); + /* Each child in each board's children has: + id, name, description, new (is it new?), topics (#), posts (#), href, link, and last_post. */ + foreach ($board['children'] as $child) + { + if (!$child['is_redirect']) + $child['link'] = '' . $child['name'] . ($child['new'] ? '' : '') . ''; + else + $child['link'] = '' . $child['name'] . ''; + + // Has it posts awaiting approval? + if ($child['can_approve_posts'] && ($child['unapproved_posts'] || $child['unapproved_topics'])) + $child['link'] .= ' (!)'; + + $children[] = $child['new'] ? '' . $child['link'] . '' : $child['link']; + } + echo ' + + + '; + } + } + echo ' + '; + } + echo ' + + + + + '; + } + echo ' +
    +
    +

    '; + + // If this category even can collapse, show a link to collapse it. + if ($category['can_collapse']) + echo ' + ', $category['collapse_image'], ''; + + if (!$context['user']['is_guest'] && !empty($category['show_unread'])) + echo ' + ', $txt['view_unread_category'], ''; + + echo ' + ', $category['link'], ' +

    +
    +
    + '; + + // If the board or children is new, show an indicator. + if ($board['new'] || $board['children_new']) + echo ' + ', $txt['new_posts'], ''; + // Is it a redirection board? + elseif ($board['is_redirect']) + echo ' + *'; + // No new posts at all! The agony!! + else + echo ' + ', $txt['old_posts'], ''; + + echo ' + + + ', $board['name'], ''; + + // Has it outstanding posts for approval? + if ($board['can_approve_posts'] && ($board['unapproved_posts'] || $board['unapproved_topics'])) + echo ' + (!)'; + + echo ' + +

    ', $board['description'] , '

    '; + + // Show the "Moderators: ". Each has name, href, link, and id. (but we're gonna use link_moderators.) + if (!empty($board['moderators'])) + echo ' +

    ', count($board['moderators']) == 1 ? $txt['moderator'] : $txt['moderators'], ': ', implode(', ', $board['link_moderators']), '

    '; + + // Show some basic information about the number of posts, etc. + echo ' +
    +

    ', comma_format($board['posts']), ' ', $board['is_redirect'] ? $txt['redirects'] : $txt['posts'], '
    + ', $board['is_redirect'] ? '' : comma_format($board['topics']) . ' ' . $txt['board_topics'], ' +

    +
    '; + + /* The board's and children's 'last_post's have: + time, timestamp (a number that represents the time.), id (of the post), topic (topic id.), + link, href, subject, start (where they should go for the first unread post.), + and member. (which has id, name, link, href, username in it.) */ + if (!empty($board['last_post']['id'])) + echo ' +

    ', $txt['last_post'], ' ', $txt['by'], ' ', $board['last_post']['member']['link'] , '
    + ', $txt['in'], ' ', $board['last_post']['link'], '
    + ', $txt['on'], ' ', $board['last_post']['time'],' +

    '; + echo ' +
    + ', $txt['parent_boards'], ': ', implode(', ', $children), ' +
    +
    '; + + if ($context['user']['is_logged']) + { + echo ' +
    '; + + // Mark read button. + $mark_read_button = array( + 'markread' => array('text' => 'mark_as_read', 'image' => 'markread.gif', 'lang' => true, 'url' => $scripturl . '?action=markasread;sa=all;' . $context['session_var'] . '=' . $context['session_id']), + ); + + echo ' +
      +
    • ', $txt['new_posts'], '
    • +
    • ', $txt['old_posts'], '
    • +
    • ', $txt['redirect_board'], '
    • +
    +
    '; + + // Show the mark all as read button? + if ($settings['show_mark_read'] && !empty($context['categories'])) + echo '
    ', template_button_strip($mark_read_button, 'right'), '
    '; + } + else + { + echo ' +
    +
      +
    • ', $txt['old_posts'], '
    • +
    • ', $txt['redirect_board'], '
    • +
    +
    '; + } + + template_info_center(); +} + +function template_info_center() +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings; + + // Here's where the "Info Center" starts... + echo ' + +
    +
    +

    + + ', sprintf($txt['info_center_title'], $context['forum_name_html_safe']), ' +

    +
    + +
    + '; + + // Info center collapse object. + echo ' + '; +} +?> \ No newline at end of file diff --git a/Themes/default/Calendar.template.php b/Themes/default/Calendar.template.php new file mode 100644 index 0000000..a252a8d --- /dev/null +++ b/Themes/default/Calendar.template.php @@ -0,0 +1,824 @@ + +
    + ', template_show_month_grid('prev'), ' + ', template_show_month_grid('current'), ' + ', template_show_month_grid('next'), ' +
    +
    + ', $context['view_week'] ? template_show_week_grid('main') : template_show_month_grid('main'); + + // Build the calendar button array. + $calendar_buttons = array( + 'post_event' => array('test' => 'can_post', 'text' => 'calendar_post_event', 'image' => 'calendarpe.gif', 'lang' => true, 'url' => $scripturl . '?action=calendar;sa=post;month=' . $context['current_month'] . ';year=' . $context['current_year'] . ';' . $context['session_var'] . '=' . $context['session_id']), + ); + + template_button_strip($calendar_buttons, 'right'); + + // Show some controls to allow easy calendar navigation. + echo ' +
    + + + '; + + echo ' +
    +
    +
    + '; +} + +// Template for posting a calendar event. +function template_event_post() +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings; + + // Start the javascript for drop down boxes... + echo ' + + +
    '; + + if (!empty($context['event']['new'])) + echo ' + '; + + // Start the main table. + echo ' +
    +
    +

    + ', $context['page_title'], ' +

    +
    '; + + if (!empty($context['post_error']['messages'])) + { + echo ' +
    +
    +
    + ', $context['error_type'] == 'serious' ? '' . $txt['error_while_submitting'] . '' : '', ' +
    +
    + ', implode('
    ', $context['post_error']['messages']), ' +
    +
    +
    '; + } + + echo ' +
    + +
    +
    + ', $txt['calendar_event_title'], ' + +
    + ', $txt['calendar_year'], ' + + ', $txt['calendar_month'], ' + + ', $txt['calendar_day'], ' + +
    +
    '; + + if (!empty($modSettings['cal_allowspan']) || $context['event']['new']) + echo ' +
    + ', $txt['calendar_event_options'], ' +
    +
      '; + + // If events can span more than one day then allow the user to select how long it should last. + if (!empty($modSettings['cal_allowspan'])) + { + echo ' +
    • + ', $txt['calendar_numb_days'], ' + +
    • '; + } + + // If this is a new event let the user specify which board they want the linked post to be put into. + if ($context['event']['new']) + { + echo ' +
    • + ', $txt['calendar_link_event'], ' + +
    • +
    • + ', $txt['calendar_post_in'], ' + +
    • '; + } + + if (!empty($modSettings['cal_allowspan']) || $context['event']['new']) + echo ' +
    +
    +
    '; + + echo ' +
    + '; + // Delete button? + if (empty($context['event']['new'])) + echo ' + '; + + echo ' + + +
    +
    + +
    +
    +
    +
    '; +} + +// Display a monthly calendar grid. +function template_show_month_grid($grid_name) +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings, $smcFunc; + + if (!isset($context['calendar_grid_' . $grid_name])) + return false; + + $calendar_data = &$context['calendar_grid_' . $grid_name]; + $colspan = !empty($calendar_data['show_week_links']) ? 8 : 7; + + if (empty($calendar_data['disable_title'])) + { + echo ' +
    +

    '; + + if (empty($calendar_data['previous_calendar']['disabled']) && $calendar_data['show_next_prev']) + echo ' + «'; + + if (empty($calendar_data['next_calendar']['disabled']) && $calendar_data['show_next_prev']) + echo ' + »'; + + if ($calendar_data['show_next_prev']) + echo ' + ', $txt['months_titles'][$calendar_data['current_month']], ' ', $calendar_data['current_year']; + else + echo ' + ', $txt['months_titles'][$calendar_data['current_month']], ' ', $calendar_data['current_year'], ''; + + echo ' +

    +
    '; + } + + echo ' + '; + + // Show each day of the week. + if (empty($calendar_data['disable_day_titles'])) + { + echo ' + '; + + if (!empty($calendar_data['show_week_links'])) + echo ' + '; + + foreach ($calendar_data['week_days'] as $day) + { + echo ' + '; + } + echo ' + '; + } + + /* Each week in weeks contains the following: + days (a list of days), number (week # in the year.) */ + foreach ($calendar_data['weeks'] as $week) + { + echo ' + '; + + if (!empty($calendar_data['show_week_links'])) + echo ' + '; + + /* Every day has the following: + day (# in month), is_today (is this day *today*?), is_first_day (first day of the week?), + holidays, events, birthdays. (last three are lists.) */ + foreach ($week['days'] as $day) + { + // If this is today, make it a different color and show a border. + echo ' + '; + } + + echo ' + '; + } + + echo ' +
     ', !empty($calendar_data['short_day_titles']) ? ($smcFunc['substr']($txt['days'][$day], 0, 1)) : $txt['days'][$day], '
    + » + '; + + // Skip it if it should be blank - it's not a day if it has no number. + if (!empty($day['day'])) + { + // Should the day number be a link? + if (!empty($modSettings['cal_daysaslink']) && $context['can_post']) + echo ' + ', $day['day'], ''; + else + echo ' + ', $day['day']; + + // Is this the first day of the week? (and are we showing week numbers?) + if ($day['is_first_day'] && $calendar_data['size'] != 'small') + echo ' - ', $txt['calendar_week'], ' ', $week['number'], ''; + + // Are there any holidays? + if (!empty($day['holidays'])) + echo ' +
    ', $txt['calendar_prompt'], ' ', implode(', ', $day['holidays']), '
    '; + + // Show any birthdays... + if (!empty($day['birthdays'])) + { + echo ' +
    + ', $txt['birthdays'], ''; + + /* Each of the birthdays has: + id, name (person), age (if they have one set?), and is_last. (last in list?) */ + $use_js_hide = empty($context['show_all_birthdays']) && count($day['birthdays']) > 15; + $count = 0; + foreach ($day['birthdays'] as $member) + { + echo ' + ', $member['name'], isset($member['age']) ? ' (' . $member['age'] . ')' : '', '', $member['is_last'] || ($count == 10 && $use_js_hide)? '' : ', '; + + // Stop at ten? + if ($count == 10 && $use_js_hide) + echo '...
    (', sprintf($txt['calendar_click_all'], count($day['birthdays'])), ')
    '; + + echo ' +
    '; + } + + // Any special posted events? + if (!empty($day['events'])) + { + echo ' +
    + ', $txt['events'], ''; + + /* The events are made up of: + title, href, is_last, can_edit (are they allowed to?), and modify_href. */ + foreach ($day['events'] as $event) + { + // If they can edit the event, show a star they can click on.... + if ($event['can_edit']) + echo ' + *'; + + echo ' + ', $event['link'], $event['is_last'] ? '' : ', '; + } + + echo ' +
    '; + } + } + + echo ' +
    '; +} + +// Or show a weekly one? +function template_show_week_grid($grid_name) +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings; + + if (!isset($context['calendar_grid_' . $grid_name])) + return false; + + $calendar_data = &$context['calendar_grid_' . $grid_name]; + + // Loop through each month (At least one) and print out each day. + foreach ($calendar_data['months'] as $month_data) + { + echo ' +
    +

    '; + + if (empty($calendar_data['previous_calendar']['disabled']) && $calendar_data['show_next_prev'] && empty($done_title)) + echo ' + «'; + + if (empty($calendar_data['next_calendar']['disabled']) && $calendar_data['show_next_prev'] && empty($done_title)) + echo ' + »'; + + echo ' + ', $txt['months_titles'][$month_data['current_month']], ' ', $month_data['current_year'], '', empty($done_title) && !empty($calendar_data['week_number']) ? (' - ' . $txt['calendar_week'] . ' ' . $calendar_data['week_number']) : '', ' +

    +
    '; + + $done_title = true; + + echo ' + '; + + foreach ($month_data['days'] as $day) + { + echo ' + + + + + + + '; + } + + echo ' +
    +
    +

    ', $txt['days'][$day['day_of_week']], '

    +
    +
    '; + + // Should the day number be a link? + if (!empty($modSettings['cal_daysaslink']) && $context['can_post']) + echo ' + ', $day['day'], ''; + else + echo ' + ', $day['day']; + + echo ' + '; + + // Are there any holidays? + if (!empty($day['holidays'])) + echo ' +
    ', $txt['calendar_prompt'], ' ', implode(', ', $day['holidays']), '
    '; + + // Show any birthdays... + if (!empty($day['birthdays'])) + { + echo ' +
    + ', $txt['birthdays'], ''; + + /* Each of the birthdays has: + id, name (person), age (if they have one set?), and is_last. (last in list?) */ + foreach ($day['birthdays'] as $member) + echo ' + ', $member['name'], isset($member['age']) ? ' (' . $member['age'] . ')' : '', '', $member['is_last'] ? '' : ', '; + echo ' +
    '; + } + + // Any special posted events? + if (!empty($day['events'])) + { + echo ' +
    + ', $txt['events'], ''; + + /* The events are made up of: + title, href, is_last, can_edit (are they allowed to?), and modify_href. */ + foreach ($day['events'] as $event) + { + // If they can edit the event, show a star they can click on.... + if ($event['can_edit']) + echo ' + * '; + + echo ' + ', $event['link'], $event['is_last'] ? '' : ', '; + } + + echo ' +
    '; + } + + echo ' +
    '; + } +} + +function template_bcd() +{ + global $context, $scripturl; + + echo ' + + + '; + + $alt = false; + foreach ($context['clockicons'] as $t => $v) + { + echo ' + '; + + $alt = !$alt; + } + + echo ' + +
    BCD Clock
    '; + + foreach ($v as $i) + echo ' +
    '; + + echo ' +
    +

    Are you hardcore?

    + + '; +} + +function template_hms() +{ + global $context, $scripturl; + + echo ' + + '; + $alt = false; + foreach ($context['clockicons'] as $t => $v) + { + echo ' + + '; + $alt = !$alt; + } + + echo ' + + +
    Binary Clock
    '; + foreach ($v as $i) + echo ' + '; + echo ' +
    Too tough for you?
    '; + + echo ' + '; +} + +function template_omfg() +{ + global $context, $scripturl; + + echo ' + + '; + $alt = false; + foreach ($context['clockicons'] as $t => $v) + { + echo ' + + '; + $alt = !$alt; + } + + echo ' + +
    OMFG Binary Clock
    '; + foreach ($v as $i) + echo ' + '; + echo ' +
    '; + + echo ' + '; +} + +function template_thetime() +{ + global $context, $scripturl; + + echo ' + + '; + $alt = false; + foreach ($context['clockicons'] as $t => $v) + { + echo ' + + '; + $alt = !$alt; + } + + echo ' + +
    The time you requested
    '; + foreach ($v as $i) + echo ' + '; + echo ' +
    '; + +} + +?> \ No newline at end of file diff --git a/Themes/default/Compat.template.php b/Themes/default/Compat.template.php new file mode 100644 index 0000000..a8b2afd --- /dev/null +++ b/Themes/default/Compat.template.php @@ -0,0 +1,46 @@ + $value) + { + if (!isset($value['test']) || !empty($context[$value['test']])) + $buttons[] = ' +
  6. ' . $txt[$value['text']] . '
  7. '; + } + + // No buttons? No button strip either. + if (empty($buttons)) + return; + + // Make the last one, as easy as possible. + $buttons[count($buttons) - 1] = str_replace('', '', $buttons[count($buttons) - 1]); + + echo ' + '; +} + +?> \ No newline at end of file diff --git a/Themes/default/Display.template.php b/Themes/default/Display.template.php new file mode 100644 index 0000000..b1492c1 --- /dev/null +++ b/Themes/default/Display.template.php @@ -0,0 +1,883 @@ + + ', $txt['report_sent'], ' + '; + } + + // Show the anchor for the top and for the first message. If the first message is new, say so. + echo ' + + ', $context['first_new_message'] ? '' : ''; + + // Is this topic also a poll? + if ($context['is_poll']) + { + echo ' +
    +
    +

    + ', $txt['poll'], ' +

    +
    +
    + +
    +

    + ', $context['poll']['question'], ' +

    '; + + // Are they not allowed to vote but allowed to view the options? + if ($context['poll']['show_results'] || !$context['allow_vote']) + { + echo ' +
    '; + + // Show each option with its corresponding percentage bar. + foreach ($context['poll']['options'] as $option) + { + echo ' +
    ', $option['option'], '
    +
    '; + + if ($context['allow_poll_view']) + echo ' + ', $option['bar_ndt'], ' + ', $option['votes'], ' (', $option['percent'], '%)'; + + echo ' +
    '; + } + + echo ' +
    '; + + if ($context['allow_poll_view']) + echo ' +

    ', $txt['poll_total_voters'], ': ', $context['poll']['total_votes'], '

    '; + } + // They are allowed to vote! Go to it! + else + { + echo ' +
    '; + + // Show a warning if they are allowed more than one option. + if ($context['poll']['allowed_warning']) + echo ' +

    ', $context['poll']['allowed_warning'], '

    '; + + echo ' +
      '; + + // Show each option with its button - a radio likely. + foreach ($context['poll']['options'] as $option) + echo ' +
    • ', $option['vote_button'], '
    • '; + + echo ' +
    +
    + + +
    +
    '; + } + + // Is the clock ticking? + if (!empty($context['poll']['expire_time'])) + echo ' +

    ', ($context['poll']['is_expired'] ? $txt['poll_expired_on'] : $txt['poll_expires_on']), ': ', $context['poll']['expire_time'], '

    '; + + echo ' +
    + +
    +
    +
    '; + + // Build the poll moderation button array. + $poll_buttons = array( + 'vote' => array('test' => 'allow_return_vote', 'text' => 'poll_return_vote', 'image' => 'poll_options.gif', 'lang' => true, 'url' => $scripturl . '?topic=' . $context['current_topic'] . '.' . $context['start']), + 'results' => array('test' => 'show_view_results_button', 'text' => 'poll_results', 'image' => 'poll_results.gif', 'lang' => true, 'url' => $scripturl . '?topic=' . $context['current_topic'] . '.' . $context['start'] . ';viewresults'), + 'change_vote' => array('test' => 'allow_change_vote', 'text' => 'poll_change_vote', 'image' => 'poll_change_vote.gif', 'lang' => true, 'url' => $scripturl . '?action=vote;topic=' . $context['current_topic'] . '.' . $context['start'] . ';poll=' . $context['poll']['id'] . ';' . $context['session_var'] . '=' . $context['session_id']), + 'lock' => array('test' => 'allow_lock_poll', 'text' => (!$context['poll']['is_locked'] ? 'poll_lock' : 'poll_unlock'), 'image' => 'poll_lock.gif', 'lang' => true, 'url' => $scripturl . '?action=lockvoting;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), + 'edit' => array('test' => 'allow_edit_poll', 'text' => 'poll_edit', 'image' => 'poll_edit.gif', 'lang' => true, 'url' => $scripturl . '?action=editpoll;topic=' . $context['current_topic'] . '.' . $context['start']), + 'remove_poll' => array('test' => 'can_remove_poll', 'text' => 'poll_remove', 'image' => 'admin_remove_poll.gif', 'lang' => true, 'custom' => 'onclick="return confirm(\'' . $txt['poll_remove_warn'] . '\');"', 'url' => $scripturl . '?action=removepoll;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), + ); + + template_button_strip($poll_buttons); + + echo ' +
    '; + } + + // Does this topic have some events linked to it? + if (!empty($context['linked_calendar_events'])) + { + echo ' +
    +
    +

    ', $txt['calendar_linked_events'], '

    +
    +
    + +
    +
      '; + + foreach ($context['linked_calendar_events'] as $event) + echo ' +
    • + ', ($event['can_edit'] ? ' ' : ''), '', $event['title'], ': ', $event['start_date'], ($event['start_date'] != $event['end_date'] ? ' - ' . $event['end_date'] : ''), ' +
    • '; + + echo ' +
    +
    + +
    +
    '; + } + + // Build the normal button array. + $normal_buttons = array( + 'reply' => array('test' => 'can_reply', 'text' => 'reply', 'image' => 'reply.gif', 'lang' => true, 'url' => $scripturl . '?action=post;topic=' . $context['current_topic'] . '.' . $context['start'] . ';last_msg=' . $context['topic_last_message'], 'active' => true), + 'add_poll' => array('test' => 'can_add_poll', 'text' => 'add_poll', 'image' => 'add_poll.gif', 'lang' => true, 'url' => $scripturl . '?action=editpoll;add;topic=' . $context['current_topic'] . '.' . $context['start']), + 'notify' => array('test' => 'can_mark_notify', 'text' => $context['is_marked_notify'] ? 'unnotify' : 'notify', 'image' => ($context['is_marked_notify'] ? 'un' : '') . 'notify.gif', 'lang' => true, 'custom' => 'onclick="return confirm(\'' . ($context['is_marked_notify'] ? $txt['notification_disable_topic'] : $txt['notification_enable_topic']) . '\');"', 'url' => $scripturl . '?action=notify;sa=' . ($context['is_marked_notify'] ? 'off' : 'on') . ';topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), + 'mark_unread' => array('test' => 'can_mark_unread', 'text' => 'mark_unread', 'image' => 'markunread.gif', 'lang' => true, 'url' => $scripturl . '?action=markasread;sa=topic;t=' . $context['mark_unread_time'] . ';topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), + 'send' => array('test' => 'can_send_topic', 'text' => 'send_topic', 'image' => 'sendtopic.gif', 'lang' => true, 'url' => $scripturl . '?action=emailuser;sa=sendtopic;topic=' . $context['current_topic'] . '.0'), + 'print' => array('text' => 'print', 'image' => 'print.gif', 'lang' => true, 'custom' => 'rel="new_win nofollow"', 'url' => $scripturl . '?action=printpage;topic=' . $context['current_topic'] . '.0'), + ); + + // Allow adding new buttons easily. + call_integration_hook('integrate_display_buttons', array(&$normal_buttons)); + + // Show the page index... "Pages: [1]". + echo ' +
    + ', template_button_strip($normal_buttons, 'right'), ' + +
    '; + + // Show the topic information - icon, subject, etc. + echo ' +
    +
    +

    + + ', $txt['author'], ' + ', $txt['topic'], ': ', $context['subject'], '  (', $txt['read'], ' ', $context['num_views'], ' ', $txt['times'], ') +

    +
    '; + + if (!empty($settings['display_who_viewing'])) + { + echo ' +

    '; + + // Show just numbers...? + if ($settings['display_who_viewing'] == 1) + echo count($context['view_members']), ' ', count($context['view_members']) == 1 ? $txt['who_member'] : $txt['members']; + // Or show the actual people viewing the topic? + else + echo empty($context['view_members_list']) ? '0 ' . $txt['members'] : implode(', ', $context['view_members_list']) . ((empty($context['view_num_hidden']) || $context['can_moderate_forum']) ? '' : ' (+ ' . $context['view_num_hidden'] . ' ' . $txt['hidden'] . ')'); + + // Now show how many guests are here too. + echo $txt['who_and'], $context['view_num_guests'], ' ', $context['view_num_guests'] == 1 ? $txt['guest'] : $txt['guests'], $txt['who_viewing_topic'], ' +

    '; + } + + echo ' +
    '; + + $ignoredMsgs = array(); + $removableMessageIDs = array(); + $alternate = false; + + // Get all the messages... + while ($message = $context['get_message']()) + { + $ignoring = false; + $alternate = !$alternate; + if ($message['can_remove']) + $removableMessageIDs[] = $message['id']; + + // Are we ignoring this message? + if (!empty($message['is_ignored'])) + { + $ignoring = true; + $ignoredMsgs[] = $message['id']; + } + + // Show the message anchor and a "new" anchor if this message is new. + if ($message['id'] != $context['first_message']) + echo ' + ', $message['first_new'] ? '' : ''; + + echo ' +
    + +
    '; + + // Show information about the poster of this message. + echo ' +
    +

    '; + + // Show online and offline buttons? + if (!empty($modSettings['onlineEnable']) && !$message['member']['is_guest']) + echo ' + ', $context['can_send_pm'] ? '' : '', '', $message['member']['online']['text'], '', $context['can_send_pm'] ? '' : ''; + + // Show a link to the member's profile. + echo ' + ', $message['member']['link'], ' +

    +
      '; + + // Show the member's custom title, if they have one. + if (!empty($message['member']['title'])) + echo ' +
    • ', $message['member']['title'], '
    • '; + + // Show the member's primary group (like 'Administrator') if they have one. + if (!empty($message['member']['group'])) + echo ' +
    • ', $message['member']['group'], '
    • '; + + // Don't show these things for guests. + if (!$message['member']['is_guest']) + { + // Show the post group if and only if they have no other group or the option is on, and they are in a post group. + if ((empty($settings['hide_post_group']) || $message['member']['group'] == '') && $message['member']['post_group'] != '') + echo ' +
    • ', $message['member']['post_group'], '
    • '; + echo ' +
    • ', $message['member']['group_stars'], '
    • '; + + // Show avatars, images, etc.? + if (!empty($settings['show_user_images']) && empty($options['show_no_avatars']) && !empty($message['member']['avatar']['image'])) + echo ' +
    • + + ', $message['member']['avatar']['image'], ' + +
    • '; + + // Show how many posts they have made. + if (!isset($context['disabled_fields']['posts'])) + echo ' +
    • ', $txt['member_postcount'], ': ', $message['member']['posts'], '
    • '; + + // Is karma display enabled? Total or +/-? + if ($modSettings['karmaMode'] == '1') + echo ' +
    • ', $modSettings['karmaLabel'], ' ', $message['member']['karma']['good'] - $message['member']['karma']['bad'], '
    • '; + elseif ($modSettings['karmaMode'] == '2') + echo ' +
    • ', $modSettings['karmaLabel'], ' +', $message['member']['karma']['good'], '/-', $message['member']['karma']['bad'], '
    • '; + + // Is this user allowed to modify this member's karma? + if ($message['member']['karma']['allow']) + echo ' +
    • + ', $modSettings['karmaApplaudLabel'], ' + ', $modSettings['karmaSmiteLabel'], ' +
    • '; + + // Show the member's gender icon? + if (!empty($settings['show_gender']) && $message['member']['gender']['image'] != '' && !isset($context['disabled_fields']['gender'])) + echo ' +
    • ', $txt['gender'], ': ', $message['member']['gender']['image'], '
    • '; + + // Show their personal text? + if (!empty($settings['show_blurb']) && $message['member']['blurb'] != '') + echo ' +
    • ', $message['member']['blurb'], '
    • '; + + // Any custom fields to show as icons? + if (!empty($message['member']['custom_fields'])) + { + $shown = false; + foreach ($message['member']['custom_fields'] as $custom) + { + if ($custom['placement'] != 1 || empty($custom['value'])) + continue; + if (empty($shown)) + { + $shown = true; + echo ' +
    • +
        '; + } + echo ' +
      • ', $custom['value'], '
      • '; + } + if ($shown) + echo ' +
      +
    • '; + } + + // This shows the popular messaging icons. + if ($message['member']['has_messenger'] && $message['member']['can_view_profile']) + echo ' +
    • +
        + ', !empty($message['member']['icq']['link']) ? '
      • ' . $message['member']['icq']['link'] . '
      • ' : '', ' + ', !empty($message['member']['msn']['link']) ? '
      • ' . $message['member']['msn']['link'] . '
      • ' : '', ' + ', !empty($message['member']['aim']['link']) ? '
      • ' . $message['member']['aim']['link'] . '
      • ' : '', ' + ', !empty($message['member']['yim']['link']) ? '
      • ' . $message['member']['yim']['link'] . '
      • ' : '', ' +
      +
    • '; + + // Show the profile, website, email address, and personal message buttons. + if ($settings['show_profile_buttons']) + { + echo ' +
    • + +
    • '; + } + + // Any custom fields for standard placement? + if (!empty($message['member']['custom_fields'])) + { + foreach ($message['member']['custom_fields'] as $custom) + if (empty($custom['placement']) || empty($custom['value'])) + echo ' +
    • ', $custom['title'], ': ', $custom['value'], '
    • '; + } + + // Are we showing the warning status? + if ($message['member']['can_see_warning']) + echo ' +
    • ', $context['can_issue_warning'] ? '' : '', '', $txt['user_warn_' . $message['member']['warning_status']], '', $context['can_issue_warning'] ? '' : '', '', $txt['warn_' . $message['member']['warning_status']], '
    • '; + } + // Otherwise, show the guest's email. + elseif (!empty($message['member']['email']) && in_array($message['member']['show_email'], array('yes', 'yes_permission_override', 'no_through_forum'))) + echo ' + '; + + // Done with the information about the poster... on to the post itself. + echo ' +
    +
    +
    +
    +
    +
    + +
    +
    + ', $message['subject'], ' +
    +
    « ', !empty($message['counter']) ? $txt['reply_noun'] . ' #' . $message['counter'] : '', ' ', $txt['on'], ': ', $message['time'], ' »
    +
    +
    '; + + // If this is the first post, (#0) just say when it was posted - otherwise give the reply #. + if ($message['can_approve'] || $context['can_reply'] || $message['can_modify'] || $message['can_remove'] || $context['can_split'] || $context['can_restore_msg']) + echo ' +
      '; + + // Maybe we can approve it, maybe we should? + if ($message['can_approve']) + echo ' +
    • ', $txt['approve'], '
    • '; + + // Can they reply? Have they turned on quick reply? + if ($context['can_quote'] && !empty($options['display_quick_reply'])) + echo ' +
    • ', $txt['quote'], '
    • '; + + // So... quick reply is off, but they *can* reply? + elseif ($context['can_quote']) + echo ' +
    • ', $txt['quote'], '
    • '; + + // Can the user modify the contents of this post? + if ($message['can_modify']) + echo ' +
    • ', $txt['modify'], '
    • '; + + // How about... even... remove it entirely?! + if ($message['can_remove']) + echo ' +
    • ', $txt['remove'], '
    • '; + + // What about splitting it off the rest of the topic? + if ($context['can_split'] && !empty($context['real_num_replies'])) + echo ' +
    • ', $txt['split'], '
    • '; + + // Can we restore topics? + if ($context['can_restore_msg']) + echo ' +
    • ', $txt['restore_message'], '
    • '; + + // Show a checkbox for quick moderation? + if (!empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1 && $message['can_remove']) + echo ' + '; + + if ($message['can_approve'] || $context['can_reply'] || $message['can_modify'] || $message['can_remove'] || $context['can_split'] || $context['can_restore_msg']) + echo ' +
    '; + + echo ' +
    '; + + // Ignoring this user? Hide the post. + if ($ignoring) + echo ' +
    + ', $txt['ignoring_user'], ' + +
    '; + + // Show the post itself, finally! + echo ' +
    '; + + if (!$message['approved'] && $message['member']['id'] != 0 && $message['member']['id'] == $context['user']['id']) + echo ' +
    + ', $txt['post_awaiting_approval'], ' +
    '; + echo ' +
    ', $message['body'], '
    +
    '; + + // Can the user modify the contents of this post? Show the modify inline image. + if ($message['can_modify']) + echo ' + '; + + // Assuming there are attachments... + if (!empty($message['attachment'])) + { + echo ' + '; + } + + echo ' +
    +
    +
    '; + + // Show "� Last Edit: Time by Person �" if this post was edited. + if ($settings['show_modify'] && !empty($message['modified']['name'])) + echo ' + « ', $txt['last_edit'], ': ', $message['modified']['time'], ' ', $txt['by'], ' ', $message['modified']['name'], ' »'; + + echo ' +
    + '; + + // Are there any custom profile fields for above the signature? + if (!empty($message['member']['custom_fields'])) + { + $shown = false; + foreach ($message['member']['custom_fields'] as $custom) + { + if ($custom['placement'] != 2 || empty($custom['value'])) + continue; + if (empty($shown)) + { + $shown = true; + echo ' +
    +
      '; + } + echo ' +
    • ', $custom['value'], '
    • '; + } + if ($shown) + echo ' +
    +
    '; + } + + // Show the member's signature? + if (!empty($message['member']['signature']) && empty($options['show_no_signatures']) && $context['signature_enabled']) + echo ' +
    ', $message['member']['signature'], '
    '; + + echo ' +
    +
    + +
    +
    '; + } + + echo ' +
    +
    + '; + + // Show the page index... "Pages: [1]". + echo ' +
    + ', template_button_strip($normal_buttons, 'right'), ' + + +
    '; + + // Show the lower breadcrumbs. + theme_linktree(); + + $mod_buttons = array( + 'move' => array('test' => 'can_move', 'text' => 'move_topic', 'image' => 'admin_move.gif', 'lang' => true, 'url' => $scripturl . '?action=movetopic;topic=' . $context['current_topic'] . '.0'), + 'delete' => array('test' => 'can_delete', 'text' => 'remove_topic', 'image' => 'admin_rem.gif', 'lang' => true, 'custom' => 'onclick="return confirm(\'' . $txt['are_sure_remove_topic'] . '\');"', 'url' => $scripturl . '?action=removetopic2;topic=' . $context['current_topic'] . '.0;' . $context['session_var'] . '=' . $context['session_id']), + 'lock' => array('test' => 'can_lock', 'text' => empty($context['is_locked']) ? 'set_lock' : 'set_unlock', 'image' => 'admin_lock.gif', 'lang' => true, 'url' => $scripturl . '?action=lock;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), + 'sticky' => array('test' => 'can_sticky', 'text' => empty($context['is_sticky']) ? 'set_sticky' : 'set_nonsticky', 'image' => 'admin_sticky.gif', 'lang' => true, 'url' => $scripturl . '?action=sticky;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), + 'merge' => array('test' => 'can_merge', 'text' => 'merge', 'image' => 'merge.gif', 'lang' => true, 'url' => $scripturl . '?action=mergetopics;board=' . $context['current_board'] . '.0;from=' . $context['current_topic']), + 'calendar' => array('test' => 'calendar_post', 'text' => 'calendar_link', 'image' => 'linktocal.gif', 'lang' => true, 'url' => $scripturl . '?action=post;calendar;msg=' . $context['topic_first_message'] . ';topic=' . $context['current_topic'] . '.0'), + ); + + // Restore topic. eh? No monkey business. + if ($context['can_restore_topic']) + $mod_buttons[] = array('text' => 'restore_topic', 'image' => '', 'lang' => true, 'url' => $scripturl . '?action=restoretopic;topics=' . $context['current_topic'] . ';' . $context['session_var'] . '=' . $context['session_id']); + + // Allow adding new mod buttons easily. + call_integration_hook('integrate_mod_buttons', array(&$mod_buttons)); + + echo ' +
    ', template_button_strip($mod_buttons, 'bottom', array('id' => 'moderationbuttons_strip')), '
    '; + + // Show the jumpto box, or actually...let Javascript do it. + echo ' +
     
    '; + + if ($context['can_reply'] && !empty($options['display_quick_reply'])) + { + echo ' + +
    + + +
    '; + } + else + echo ' +
    '; + + if ($context['show_spellchecking']) + echo ' +
    + '; + + echo ' + + '; +} + +?> \ No newline at end of file diff --git a/Themes/default/Errors.template.php b/Themes/default/Errors.template.php new file mode 100644 index 0000000..bd847cc --- /dev/null +++ b/Themes/default/Errors.template.php @@ -0,0 +1,203 @@ + +
    +

    + ', $context['error_title'], ' +

    +
    +
    + +
    ', $context['error_message'], '
    + +
    + '; + + // Show a back button (using javascript.) + echo ' + '; +} + +function template_error_log() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    + +
    +

    + + ', $txt['help'], ' ', $txt['errlog'], ' + +

    +
    + + + + + + + '; + + if ($context['has_filter']) + echo ' + + + '; + + if (!empty($context['errors'])) + echo ' + + + '; + + foreach ($context['errors'] as $error) + { + echo ' + + + + + + + + '; + } + + if (!empty($context['errors'])) + echo ' + + + '; + else + echo ' + + + '; + + echo ' + + + +
    +   ', $txt['apply_filter_of_type'], ':'; + + $error_types = array(); + foreach ($context['error_types'] as $type => $details) + $error_types[] = ($details['is_selected'] ? ' ' : '') . '' . $details['label'] . ''; + + echo ' + ', implode(' | ', $error_types), ' +
    +   ', $txt['pages'], ': ', $context['page_index'], ' +
    + ', $txt['applying_filter'], ': ', $context['filter']['entity'], ' ', $context['filter']['value']['html'], ' (', $txt['clear_filter'], ') +
    +
    + +
    + + + ', $txt['apply_filter'], ': ', $txt['filter_only_member'], ' + ', $error['member']['link'], '
    + ', $txt['apply_filter'], ': ', $txt['filter_only_ip'], ' + ', $error['member']['ip'], '   +
      +
    + ', $txt['reverse_direction'], ' + ', $error['time'], ' +
    '; + + if ($error['member']['session'] != '') + echo ' + ', $txt['apply_filter'], ': ', $txt['filter_only_session'], ' + ', $error['member']['session'], ' +
    '; + + echo ' + ', $txt['apply_filter'], ': ', $txt['filter_only_type'], ' + ', $txt['error_type'], ': ', $error['error_type']['name'], ' +
    +
    ', $txt['apply_filter'], ': ', $txt['filter_only_url'], '
    + +
    ', $txt['apply_filter'], ': ', $txt['filter_only_message'], '
    +
    ', $error['message']['html'], '
    '; + + if (!empty($error['file'])) + echo ' +
    ', $txt['apply_filter'], ': ', $txt['filter_only_file'], '
    +
    + ', $txt['file'], ': ', $error['file']['link'], '
    + ', $txt['line'], ': ', $error['file']['line'], ' +
    '; + + echo ' +
    +
    +   +
    ', $txt['errlog_no_entries'], '
    +   ', $txt['pages'], ': ', $context['page_index'], ' +

    '; + if ($context['sort_direction'] == 'down') + echo ' + '; + echo ' + +
    '; +} + +function template_show_file() +{ + global $context, $settings; + + echo ' + + + ', $context['file_data']['file'], ' + + + + + '; + foreach ($context['file_data']['contents'] as $index => $line) + { + $line_num = $index+$context['file_data']['min']; + $is_target = $line_num == $context['file_data']['target']; + echo ' + + + + '; + } + echo ' +
    ==>' : '>', $line_num , ':', $line, '
    + +'; +} + +?> \ No newline at end of file diff --git a/Themes/default/GenericControls.template.php b/Themes/default/GenericControls.template.php new file mode 100644 index 0000000..833ae0a --- /dev/null +++ b/Themes/default/GenericControls.template.php @@ -0,0 +1,359 @@ + +
    +
    + +
    +
    +
    + + + '; +} + +function template_control_richedit_buttons($editor_id) +{ + global $context, $settings, $options, $txt, $modSettings, $scripturl; + + $editor_context = &$context['controls']['richedit'][$editor_id]; + + echo ' + '; + + if ($editor_context['preview_type']) + echo ' + '; + + if ($context['show_spellchecking']) + echo ' + '; +} + +// What's this, verification?! +function template_control_verification($verify_id, $display_type = 'all', $reset = false) +{ + global $context, $settings, $options, $txt, $modSettings; + + $verify_context = &$context['controls']['verification'][$verify_id]; + + // Keep track of where we are. + if (empty($verify_context['tracking']) || $reset) + $verify_context['tracking'] = 0; + + // How many items are there to display in total. + $total_items = count($verify_context['questions']) + ($verify_context['show_visual'] ? 1 : 0); + + // If we've gone too far, stop. + if ($verify_context['tracking'] > $total_items) + return false; + + // Loop through each item to show them. + for ($i = 0; $i < $total_items; $i++) + { + // If we're after a single item only show it if we're in the right place. + if ($display_type == 'single' && $verify_context['tracking'] != $i) + continue; + + if ($display_type != 'single') + echo ' +
    '; + + // Do the actual stuff - image first? + if ($i == 0 && $verify_context['show_visual']) + { + if ($context['use_graphic_library']) + echo ' + ', $txt['visual_verification_description'], ''; + else + echo ' + ', $txt['visual_verification_description'], ' + ', $txt['visual_verification_description'], ' + ', $txt['visual_verification_description'], ' + ', $txt['visual_verification_description'], ' + ', $txt['visual_verification_description'], ' + ', $txt['visual_verification_description'], ''; + + if (WIRELESS) + echo '
    + '; + else + echo ' +
    + ', $txt['visual_verification_sound'], ' / ', $txt['visual_verification_request_new'], '', $display_type != 'quick_reply' ? '
    ' : '', '
    + ', $txt['visual_verification_description'], ':', $display_type != 'quick_reply' ? '
    ' : '', ' + +
    '; + } + else + { + // Where in the question array is this question? + $qIndex = $verify_context['show_visual'] ? $i - 1 : $i; + + echo ' +
    + ', $verify_context['questions'][$qIndex]['q'], ':
    + +
    '; + } + + if ($display_type != 'single') + echo ' +
    '; + + // If we were displaying just one and we did it, break. + if ($display_type == 'single' && $verify_context['tracking'] == $i) + break; + } + + // Assume we found something, always, + $verify_context['tracking']++; + + // Tell something displaying piecemeal to keep going. + if ($display_type == 'single') + return true; +} + +?> \ No newline at end of file diff --git a/Themes/default/GenericList.template.php b/Themes/default/GenericList.template.php new file mode 100644 index 0000000..e1fcfda --- /dev/null +++ b/Themes/default/GenericList.template.php @@ -0,0 +1,333 @@ + +
    '; + + // Show the title of the table (if any). + if (!empty($cur_list['title'])) + echo ' +
    +

    + ', $cur_list['title'], ' +

    +
    '; + // This is for the old style menu with the arrows "> Test | Test 1" + if (empty($settings['use_tabs']) && isset($cur_list['list_menu'], $cur_list['list_menu']['show_on']) && ($cur_list['list_menu']['show_on'] == 'both' || $cur_list['list_menu']['show_on'] == 'top')) + template_create_list_menu($cur_list['list_menu'], 'top'); + + if (isset($cur_list['additional_rows']['top_of_list'])) + template_additional_rows('top_of_list', $cur_list); + + if (isset($cur_list['additional_rows']['after_title'])) + { + echo ' +
    '; + template_additional_rows('after_title', $cur_list); + echo ' +
    '; + } + + if (!empty($cur_list['items_per_page']) || isset($cur_list['additional_rows']['bottom_of_list'])) + { + echo ' +
    '; + + // Show the page index (if this list doesn't intend to show all items). + if (!empty($cur_list['items_per_page'])) + echo ' +
    +
    ', $txt['pages'], ': ', $cur_list['page_index'], '
    +
    '; + + if (isset($cur_list['additional_rows']['above_column_headers'])) + { + echo ' +
    '; + + template_additional_rows('above_column_headers', $cur_list); + + echo ' +
    '; + } + + echo ' +
    '; + } + + echo ' + '; + + // Show the column headers. + $header_count = count($cur_list['headers']); + if (!($header_count < 2 && empty($cur_list['headers'][0]['label']))) + { + echo ' + + '; + + // Loop through each column and add a table header. + $i = 0; + foreach ($cur_list['headers'] as $col_header) + { + $i ++; + if (empty($col_header['class']) && $i == 1) + $col_header['class'] = 'first_th'; + elseif (empty($col_header['class']) && $i == $header_count) + $col_header['class'] = 'last_th'; + + echo ' + '; + } + + echo ' + + + '; + } + + // Show a nice message informing there are no items in this list. + if (empty($cur_list['rows']) && !empty($cur_list['no_items_label'])) + echo ' + + + '; + + // Show the list rows. + elseif (!empty($cur_list['rows'])) + { + $alternate = false; + foreach ($cur_list['rows'] as $id => $row) + { + echo ' + '; + + foreach ($row as $row_data) + echo ' + ', $row_data['value'], ''; + + echo ' + '; + + $alternate = !$alternate; + } + } + + echo ' + +
    ', empty($col_header['href']) ? '' : '', empty($col_header['label']) ? ' ' : $col_header['label'], empty($col_header['href']) ? '' : '', empty($col_header['sort_image']) ? '' : ' ', '
    ', $cur_list['no_items_label'], '
    '; + + if (!empty($cur_list['items_per_page']) || isset($cur_list['additional_rows']['below_table_data']) || isset($cur_list['additional_rows']['bottom_of_list'])) + { + echo ' +
    '; + + // Show the page index (if this list doesn't intend to show all items). + if (!empty($cur_list['items_per_page'])) + echo ' +
    +
    ', $txt['pages'], ': ', $cur_list['page_index'], '
    +
    '; + + if (isset($cur_list['additional_rows']['below_table_data'])) + { + echo ' +
    '; + + template_additional_rows('below_table_data', $cur_list); + + echo ' +
    '; + } + + if (isset($cur_list['additional_rows']['bottom_of_list'])) + { + echo ' +
    '; + + template_additional_rows('bottom_of_list', $cur_list); + + echo ' +
    '; + } + + echo ' +
    '; + } + + if (isset($cur_list['form'])) + { + foreach ($cur_list['form']['hidden_fields'] as $name => $value) + echo ' + '; + + echo ' +
    + '; + } + + // Tabs at the bottom. Usually bottom alligned. + if (!empty($settings['use_tabs']) && isset($cur_list['list_menu'], $cur_list['list_menu']['show_on']) && ($cur_list['list_menu']['show_on'] == 'both' || $cur_list['list_menu']['show_on'] == 'bottom')) + template_create_list_menu($cur_list['list_menu'], 'bottom'); + + if (isset($cur_list['javascript'])) + echo ' + '; +} + +function template_additional_rows($row_position, $cur_list) +{ + global $context, $settings, $options; + + foreach ($cur_list['additional_rows'][$row_position] as $row) + echo ' +
    ', $row['value'], '
    '; +} + +function template_create_list_menu($list_menu, $direction = 'top') +{ + global $context, $settings; + + /** + // This is use if you want your generic lists to have tabs. + $cur_list['list_menu'] = array( + // This is the style to use. Tabs or Buttons (Text 1 | Text 2). + // By default tabs are selected if not set. + // The main difference between tabs and buttons is that tabs get highlighted if selected. + // If style is set to buttons and use tabs is diabled then we change the style to old styled tabs. + 'style' => 'tabs', + // The posisiton of the tabs/buttons. Left or Right. By default is set to left. + 'position' => 'left', + // This is used by the old styled menu. We *need* to know the total number of columns to span. + 'columns' => 0, + // This gives you the option to show tabs only at the top, bottom or both. + // By default they are just shown at the top. + 'show_on' => 'top', + // Links. This is the core of the array. It has all the info that we need. + 'links' => array( + 'name' => array( + // This will tell use were to go when they click it. + 'href' => $scripturl . '?action=theaction', + // The name that you want to appear for the link. + 'label' => $txt['name'], + // If we use tabs instead of buttons we highlight the current tab. + // Must use conditions to determine if its selected or not. + 'is_selected' => isset($_REQUEST['name']), + ), + ), + ); + */ + + // Are we using right-to-left orientation? + $first = $context['right_to_left'] ? 'last' : 'first'; + $last = $context['right_to_left'] ? 'first' : 'last'; + + // Tabs take preference over buttons in certain cases. + if (empty($settings['use_tabs']) && $list_menu['style'] == 'button') + $list_menu['style'] = 'tabs'; + + if (!isset($list_menu['style']) || isset($list_menu['style']) && $list_menu['style'] == 'tabs') + { + if (!empty($settings['use_tabs'])) + { + echo ' + + ', $list_menu['position'] == 'right' ? ' + ' : '', ' + ', $list_menu['position'] == 'left' ? ' + ' : '', ' + +
      + + + '; + + foreach ($list_menu['links'] as $link) + { + if ($link['is_selected']) + echo ' + + + '; + else + echo ' + '; + } + + echo ' + + +
       + ', $link['label'], ' +   + ', $link['label'], ' +  
    +
     
    '; + } + else + { + echo ' + + '; + + $links = array(); + foreach ($list_menu['links'] as $link) + $links[] = ($link['is_selected'] ? '> ' : '') . '' . $link['label'] . ''; + + echo ' + ', implode(' | ', $links), ' + + '; + } + } + elseif (isset($list_menu['style']) && $list_menu['style'] == 'buttons') + { + $links = array(); + foreach ($list_menu['links'] as $link) + $links[] = '' . $link['label'] . ''; + + echo ' + + ', $list_menu['position'] == 'right' ? ' + ' : '', ' + ', $list_menu['position'] == 'left' ? ' + ' : '', ' + +
      + + + + + + +
     ', implode('  |  ', $links), ' 
    +
     
    '; + } +} + +?> \ No newline at end of file diff --git a/Themes/default/GenericMenu.template.php b/Themes/default/GenericMenu.template.php new file mode 100644 index 0000000..7070f5d --- /dev/null +++ b/Themes/default/GenericMenu.template.php @@ -0,0 +1,367 @@ + +
    '; + + // What one are we rendering? + $context['cur_menu_id'] = isset($context['cur_menu_id']) ? $context['cur_menu_id'] + 1 : 1; + $menu_context = &$context['menu_data_' . $context['cur_menu_id']]; + + // For every section that appears on the sidebar... + $firstSection = true; + foreach ($menu_context['sections'] as $section) + { + // Show the section header - and pump up the line spacing for readability. + echo ' +
    +
    +

    '; + + if ($firstSection && !empty($menu_context['can_toggle_drop_down'])) + { + echo ' + + ', $section['title'],'! + '; + } + else + { + echo ' + ', $section['title']; + } + + echo ' +

    +
    +
      '; + + // For every area of this section show a link to that area (bold if it's currently selected.) + foreach ($section['areas'] as $i => $area) + { + // Not supposed to be printed? + if (empty($area['label'])) + continue; + + echo ' +
    • '; + + // Is this the current area, or just some area? + if ($i == $menu_context['current_area']) + { + echo ' + ', $area['label'], ''; + + if (empty($context['tabs'])) + $context['tabs'] = isset($area['subsections']) ? $area['subsections'] : array(); + } + else + echo ' + ', $area['label'], ''; + + echo ' +
    • '; + } + + echo ' +
    +
    '; + + $firstSection = false; + } + + // This is where the actual "main content" area for the admin section starts. + echo ' +
    +
    '; + + // If there are any "tabs" setup, this is the place to shown them. + if (!empty($context['tabs']) && empty($context['force_disable_tabs'])) + template_generic_menu_tabs($menu_context); +} + +// Part of the sidebar layer - closes off the main bit. +function template_generic_menu_sidebar_below() +{ + global $context, $settings, $options; + + echo ' +
    +
    '; +} + +// This contains the html for the side bar of the admin center, which is used for all admin pages. +function template_generic_menu_dropdown_above() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + // Which menu are we rendering? + $context['cur_menu_id'] = isset($context['cur_menu_id']) ? $context['cur_menu_id'] + 1 : 1; + $menu_context = &$context['menu_data_' . $context['cur_menu_id']]; + + if (!empty($menu_context['can_toggle_drop_down'])) + echo ' + *'; + + echo ' +
    +
    '; + + // This is the main table - we need it so we can keep the content to the right of it. + echo ' +
    '; + + // It's possible that some pages have their own tabs they wanna force... + if (!empty($context['tabs'])) + template_generic_menu_tabs($menu_context); +} + +// Part of the admin layer - used with admin_above to close the table started in it. +function template_generic_menu_dropdown_below() +{ + global $context, $settings, $options; + + echo ' +
    '; +} + +// Some code for showing a tabbed view. +function template_generic_menu_tabs(&$menu_context) +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + // Handy shortcut. + $tab_context = &$menu_context['tab_data']; + + echo ' +
    +

    '; + + // Exactly how many tabs do we have? + foreach ($context['tabs'] as $id => $tab) + { + // Can this not be accessed? + if (!empty($tab['disabled'])) + { + $tab_context['tabs'][$id]['disabled'] = true; + continue; + } + + // Did this not even exist - or do we not have a label? + if (!isset($tab_context['tabs'][$id])) + $tab_context['tabs'][$id] = array('label' => $tab['label']); + elseif (!isset($tab_context['tabs'][$id]['label'])) + $tab_context['tabs'][$id]['label'] = $tab['label']; + + // Has a custom URL defined in the main admin structure? + if (isset($tab['url']) && !isset($tab_context['tabs'][$id]['url'])) + $tab_context['tabs'][$id]['url'] = $tab['url']; + // Any additional paramaters for the url? + if (isset($tab['add_params']) && !isset($tab_context['tabs'][$id]['add_params'])) + $tab_context['tabs'][$id]['add_params'] = $tab['add_params']; + // Has it been deemed selected? + if (!empty($tab['is_selected'])) + $tab_context['tabs'][$id]['is_selected'] = true; + // Does it have its own help? + if (!empty($tab['help'])) + $tab_context['tabs'][$id]['help'] = $tab['help']; + // Is this the last one? + if (!empty($tab['is_last']) && !isset($tab_context['override_last'])) + $tab_context['tabs'][$id]['is_last'] = true; + } + + // Find the selected tab + foreach ($tab_context['tabs'] as $sa => $tab) + { + if (!empty($tab['is_selected']) || (isset($menu_context['current_subsection']) && $menu_context['current_subsection'] == $sa)) + { + $selected_tab = $tab; + $tab_context['tabs'][$sa]['is_selected'] = true; + } + } + + // Show an icon and/or a help item? + if (!empty($selected_tab['icon']) || !empty($tab_context['icon']) || !empty($selected_tab['help']) || !empty($tab_context['help'])) + { + echo ' + '; + + if (!empty($selected_tab['icon']) || !empty($tab_context['icon'])) + echo ''; + + if (!empty($selected_tab['help']) || !empty($tab_context['help'])) + echo '', $txt['help'], ''; + + echo $tab_context['title'], ' + '; + } + else + { + echo ' + ', $tab_context['title']; + } + + echo ' +

    +
    '; + + // Shall we use the tabs? + if (!empty($settings['use_tabs'])) + { + echo ' +

    + ', !empty($selected_tab['description']) ? $selected_tab['description'] : $tab_context['description'], ' +

    '; + + // The admin tabs. + echo ' +
    +
      '; + + // Print out all the items in this tab. + foreach ($tab_context['tabs'] as $sa => $tab) + { + if (!empty($tab['disabled'])) + continue; + + if (!empty($tab['is_selected'])) + { + echo ' +
    • + ', $tab['label'], ' +
    • '; + } + else + echo ' +
    • + ', $tab['label'], ' +
    • '; + } + + // the end of tabs + echo ' +
    +

    '; + } + // ...if not use the old style + else + { + echo ' +

    '; + + // Print out all the items in this tab. + foreach ($tab_context['tabs'] as $sa => $tab) + { + if (!empty($tab['disabled'])) + continue; + + if (!empty($tab['is_selected'])) + { + echo ' + * ', $tab['label'], ''; + } + else + echo ' + ', $tab['label'], ''; + + if (empty($tab['is_last'])) + echo ' | '; + } + + echo ' +

    +

    ', isset($selected_tab['description']) ? $selected_tab['description'] : $tab_context['description'], '

    '; + } +} + +?> \ No newline at end of file diff --git a/Themes/default/Help.template.php b/Themes/default/Help.template.php new file mode 100644 index 0000000..2bd0ebe --- /dev/null +++ b/Themes/default/Help.template.php @@ -0,0 +1,185 @@ + + + + + + ', $context['page_title'], ' + + + + +
    + ', $context['help_text'], '
    +
    + ', $txt['close_window'], ' +
    + +'; +} + +function template_find_members() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + echo ' + + + ', $txt['find_members'], ' + + + + + + + +
    + +
    +
    +
    +

    ', $txt['find_members'], '

    +
    +
    + ', $txt['find_username'], ':
    +
    + ', $txt['find_wildcards'], '
    '; + + // Only offer to search for buddies if we have some! + if (!empty($context['show_buddies'])) + echo ' +
    '; + + echo ' +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    +

    ', $txt['find_results'], '

    +
    '; + + if (empty($context['results'])) + echo ' +

    ', $txt['find_no_results'], '

    '; + else + { + echo ' +
      '; + + $alternate = true; + foreach ($context['results'] as $result) + { + echo ' +
    • + ', $txt['view_profile'], ' + ', $result['name'], ' +
    • '; + + $alternate = !$alternate; + } + + echo ' +
    +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    '; + } + + echo ' +
    +
    + + + + +
    '; + + if (empty($context['results'])) + echo ' + '; + + echo ' + +'; +} + +// The main help page. +function template_manual() +{ + global $context, $scripturl, $txt; + + echo ' +
    +

    ', $txt['manual_smf_user_help'], '

    +
    +
    +
    + +
    +

    ', sprintf($txt['manual_welcome'], $context['forum_name']), '

    +

    ', $txt['manual_introduction'], '

    + +

    ', sprintf($txt['manual_docs_and_credits'], $context['wiki_url'], $scripturl . '?action=credits'), '

    +
    + +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/Login.template.php b/Themes/default/Login.template.php new file mode 100644 index 0000000..4c2f997 --- /dev/null +++ b/Themes/default/Login.template.php @@ -0,0 +1,308 @@ + + +
    +
    '; + + // Focus on the correct input - username or password. + echo ' + '; +} + +// Tell a guest to get lost or login! +function template_kick_guest() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + // This isn't that much... just like normal login but with a message at the top. + echo ' + +
    + +
    '; + + // Do the focus thing... + echo ' + '; +} + +// This is for maintenance mode. +function template_maintenance() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + // Display the administrator's message at the top. + echo ' + +
    + +
    '; +} + +// This is for the security stuff - makes administrators login every so often. +function template_admin_login() +{ + global $context, $settings, $options, $scripturl, $txt; + + // Since this should redirect to whatever they were doing, send all the get data. + echo ' + + +
    + + +
    '; + + // Focus on the password box. + echo ' +'; +} + +// Activate your account manually? +function template_retry_activate() +{ + global $context, $settings, $options, $txt, $scripturl; + + // Just ask them for their code so they can try it again... + echo ' +
    +
    +

    ', $context['page_title'], '

    +
    + +
    '; + + // You didn't even have an ID? + if (empty($context['member_id'])) + echo ' +
    +
    ', $txt['invalid_activation_username'], ':
    +
    '; + + echo ' +
    ', $txt['invalid_activation_retry'], ':
    +
    +
    +

    +
    + +
    '; +} + +// Activate your account manually? +function template_resend() +{ + global $context, $settings, $options, $txt, $scripturl; + + // Just ask them for their code so they can try it again... + echo ' +
    +
    +

    ', $context['page_title'], '

    +
    + +
    +
    +
    ', $txt['invalid_activation_username'], ':
    +
    +
    +

    ', $txt['invalid_activation_new'], '

    +
    +
    ', $txt['invalid_activation_new_email'], ':
    +
    +
    ', $txt['invalid_activation_password'], ':
    +
    +
    '; + + if ($context['can_activate']) + echo ' +

    ', $txt['invalid_activation_known'], '

    +
    +
    ', $txt['invalid_activation_retry'], ':
    +
    +
    '; + + echo ' +

    +
    + +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/ManageAttachments.template.php b/Themes/default/ManageAttachments.template.php new file mode 100644 index 0000000..2ed9a67 --- /dev/null +++ b/Themes/default/ManageAttachments.template.php @@ -0,0 +1,215 @@ + +'; +} + +function template_browse() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' + '; + + template_show_list('file_list'); + echo ' +
    '; + +} + +function template_maintenance() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    +

    ', $txt['attachment_stats'], '

    +
    +
    + +
    +
    +
    ', $txt['attachment_total'], ':
    ', $context['num_attachments'], '
    +
    ', $txt['attachment_manager_total_avatars'], ':
    ', $context['num_avatars'], '
    +
    ', $txt['attachmentdir_size' . ($context['attach_multiple_dirs'] ? '_current' : '')], ':
    ', $context['attachment_total_size'], ' ', $txt['kilobyte'], '
    +
    ', $txt['attachment_space' . ($context['attach_multiple_dirs'] ? '_current' : '')], ':
    ', isset($context['attachment_space']) ? $context['attachment_space'] . ' ' . $txt['kilobyte'] : $txt['attachmentdir_size_not_set'], '
    +
    +
    + +
    +
    +

    ', $txt['attachment_integrity_check'], '

    +
    +
    + +
    +
    +

    ', $txt['attachment_integrity_check_desc'], '

    + +
    +
    + +
    +
    +

    ', $txt['attachment_pruning'], '

    +
    +
    + +
    +
    + ', $txt['attachment_remove_old'], ' ', $txt['days_word'], '
    + ', $txt['attachment_pruning_message'], ':
    + + + + +
    +
    +
    + ', $txt['attachment_remove_size'], ' ', $txt['kilobyte'], '
    + ', $txt['attachment_pruning_message'], ':
    + + + + +
    +
    +
    + ', $txt['attachment_manager_avatars_older'], ' ', $txt['days_word'], '
    + + + + +
    +
    + +
    +
    +
    '; +} + +function template_attachment_repair() +{ + global $context, $txt, $scripturl, $settings; + + // If we've completed just let them know! + if ($context['completed']) + { + echo ' +
    +
    +

    ', $txt['repair_attachments_complete'], '

    +
    +
    + +
    + ', $txt['repair_attachments_complete_desc'], ' +
    + +
    +
    +
    '; + } + + // What about if no errors were even found? + elseif (!$context['errors_found']) + { + echo ' +
    +
    +

    ', $txt['repair_attachments_complete'], '

    +
    +
    + +
    + ', $txt['repair_attachments_no_errors'], ' +
    + +
    +
    +
    '; + } + // Otherwise, I'm sad to say, we have a problem! + else + { + echo ' +
    +
    +
    +

    ', $txt['repair_attachments'], '

    +
    +
    + +
    +

    ', $txt['repair_attachments_error_desc'], '

    '; + + // Loop through each error reporting the status + foreach ($context['repair_errors'] as $error => $number) + { + if (!empty($number)) + echo ' + +
    '; + } + + echo '
    + + +
    + +
    +
    +
    +
    '; + } +} + +function template_attachment_paths() +{ + template_show_list('attach_paths'); +} + +?> \ No newline at end of file diff --git a/Themes/default/ManageBans.template.php b/Themes/default/ManageBans.template.php new file mode 100644 index 0000000..0097fbd --- /dev/null +++ b/Themes/default/ManageBans.template.php @@ -0,0 +1,365 @@ + + +
    +

    + ', $context['ban']['is_new'] ? $txt['ban_add_new'] : $txt['ban_edit'] . ' \'' . $context['ban']['name'] . '\'', ' +

    +
    '; + + if ($context['ban']['is_new']) + echo ' +
    ', $txt['ban_add_notes'], '
    '; + + echo ' +
    + +
    +
    +
    +
    + ', $txt['ban_name'], ': +
    +
    + +
    +
    + ', $txt['ban_reason'], ':
    + ', $txt['ban_reason_desc'], ' +
    +
    + +
    +
    + ', $txt['ban_notes'], ':
    + ', $txt['ban_notes_desc'], ' +
    +
    + +
    +
    +
    + + ', $txt['ban_expiration'], ' + +
    + : ', $txt['ban_days'], '
    + +
    +
    + + ', $txt['ban_restriction'], ' + +
    +
    + (?)
    +
    +
    +
    +
    '; + + if (!empty($context['ban_suggestions'])) + { + echo ' +
    + + ', $txt['ban_triggers'], ' + +
    +
    + + +
    +
    + +
    '; + + if (empty($modSettings['disableHostnameLookup'])) + echo ' +
    + + +
    +
    + +
    '; + + echo ' +
    + + +
    +
    + +
    +
    + + : +
    +
    '; + + if (empty($context['ban_suggestions']['member']['id'])) + echo ' + '; + else + echo ' + ', $context['ban_suggestions']['member']['link'], ' + '; + echo ' +
    '; + + if (!empty($context['ban_suggestions']['message_ips'])) + { + echo ' +
    +
    ', $txt['ips_in_messages'], ':
    +
    '; + + foreach ($context['ban_suggestions']['message_ips'] as $ip) + echo ' +
    + +
    +
    + ', $ip, ' +
    '; + } + + if (!empty($context['ban_suggestions']['error_ips'])) + { + echo ' +
    +
    ', $txt['ips_in_errors'], '
    +
    '; + + foreach ($context['ban_suggestions']['error_ips'] as $ip) + echo ' +
    + +
    +
    + ', $ip, ' +
    '; + } + + echo ' +
    +
    '; + } + + echo ' +
    + + + + +
    +
    +
    + +
    '; + + if (!$context['ban']['is_new'] && empty($context['ban_suggestions'])) + { + echo ' +
    +
    + + + + + + '; + if (empty($context['ban_items'])) + echo ' + + + '; + else + { + foreach ($context['ban_items'] as $ban_item) + { + echo ' + + + + + + '; + } + } + + echo ' + +
    ', $txt['ban_banned_entity'], ' + ', $txt['ban_hits'], ' + ', $txt['ban_actions'], ' + +
    (', $txt['ban_no_triggers'], ')
    '; + if ($ban_item['type'] == 'ip') + echo ' ', $txt['ip'], ': ', $ban_item['ip']; + elseif ($ban_item['type'] == 'hostname') + echo ' ', $txt['hostname'], ': ', $ban_item['hostname']; + elseif ($ban_item['type'] == 'email') + echo ' ', $txt['email'], ': ', $ban_item['email']; + elseif ($ban_item['type'] == 'user') + echo ' ', $txt['username'], ': ', $ban_item['user']['link']; + echo ' + ', $ban_item['hits'], '', $txt['ban_edit_trigger'], '
    + +
    + + +
    '; + + } + + echo ' + +
    + + '; +} + +function template_ban_edit_trigger() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    +
    +
    +

    + ', $context['ban_trigger']['is_new'] ? $txt['ban_add_trigger'] : $txt['ban_edit_trigger_title'], ' +

    +
    +
    + +
    +
    + + ', $txt['ban_triggers'], ' + +
    +
    + + ', $txt['ban_on_ip'], ' +
    +
    + +
    '; + if (empty($modSettings['disableHostnameLookup'])) + echo ' +
    + + ', $txt['ban_on_hostname'], ' +
    +
    + +
    '; + echo ' +
    + + ', $txt['ban_on_email'], ' +
    +
    + +
    +
    + + ', $txt['ban_on_username'], ' +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    + + + +
    +
    +
    + + '; +} + +?> \ No newline at end of file diff --git a/Themes/default/ManageBoards.template.php b/Themes/default/ManageBoards.template.php new file mode 100644 index 0000000..395d892 --- /dev/null +++ b/Themes/default/ManageBoards.template.php @@ -0,0 +1,633 @@ + +
    +

    ', $txt['boardsEdit'], '

    +
    '; + + if (!empty($context['move_board'])) + echo ' +
    +

    ', $context['move_title'], ' [', $txt['mboards_cancel_moving'], ']', '

    +
    '; + + // No categories so show a label. + if (empty($context['categories'])) + echo ' +
    + +
    + ', $txt['mboards_no_cats'], ' +
    + +
    '; + + // Loop through every category, listing the boards in each as we go. + foreach ($context['categories'] as $category) + { + // Link to modify the category. + echo ' + '; + + // Boards table header. + echo ' +
    +
    + +
    +
      '; + + if (!empty($category['move_link'])) + echo ' +
    • ', $category['move_link']['label'], '
    • '; + + $alternate = false; + + // List through every board in the category, printing its name and link to modify the board. + foreach ($category['boards'] as $board) + { + $alternate = !$alternate; + + echo ' + ', $board['name'], '', !empty($modSettings['recycle_board']) && !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board['id'] ? ' ' . $txt['recycle_board'] . '' : '', ' + ', $context['can_manage_permissions'] ? '' . $txt['mboards_permissions'] . '' : '', ' + ', $txt['mboards_move'], ' + ', $txt['mboards_modify'], '
      + '; + + if (!empty($board['move_links'])) + { + $alternate = !$alternate; + + echo ' +
    • '; + + foreach ($board['move_links'] as $link) + echo ' + ', $link['label'], ''; + + echo ' +
    • '; + } + } + + // Button to add a new board. + echo ' +
    +
    + + +
    +
    + +
    +
    '; + } + echo ' + +
    '; +} + +// Template for editing/adding a category on the forum. +function template_modify_category() +{ + global $context, $settings, $options, $scripturl, $txt; + + // Print table header. + echo ' +
    +
    + +
    +

    + ', isset($context['category']['is_new']) ? $txt['mboards_new_cat_name'] : $txt['catEdit'], ' +

    +
    +
    + +
    +
    '; + // If this isn't the only category, let the user choose where this category should be positioned down the board index. + if (count($context['category_order']) > 1) + { + echo ' +
    ', $txt['order'], ':
    +
    + +
    '; + } + // Allow the user to edit the category name and/or choose whether you can collapse the category. + echo ' +
    + ', $txt['full_name'], ':
    + ', $txt['name_on_display'], ' +
    +
    + +
    +
    + ' . $txt['collapse_enable'] . '
    + ' . $txt['collapse_desc'] . ' +
    +
    + +
    '; + + // Table footer. + echo ' +
    +
    '; + + if (isset($context['category']['is_new'])) + echo ' + '; + else + echo ' + + '; + echo ' + '; + + // If this category is empty we don't bother with the next confirmation screen. + if ($context['category']['is_empty']) + echo ' + '; + + echo ' +
    +
    + +
    +
    +
    +
    '; +} + +// A template to confirm if a user wishes to delete a category - and whether they want to save the boards. +function template_confirm_category_delete() +{ + global $context, $settings, $options, $scripturl, $txt; + + // Print table header. + echo ' +
    +
    + +
    +

    ', $txt['mboards_delete_cat'], '

    +
    +
    + +
    +

    ', $txt['mboards_delete_cat_contains'], ':

    +
      '; + + foreach ($context['category']['children'] as $child) + echo ' +
    • ', $child, '
    • '; + + echo ' +
    +
    + +
    +
    +

    ', $txt['mboards_delete_what_do'], '

    +
    +
    + +
    +

    +
    + : + +

    + + + + +
    + +
    +
    +
    +
    '; +} + +// Below is the template for adding/editing an board on the forum. +function template_modify_board() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + // The main table header. + echo ' +
    +
    + +
    +

    + ', isset($context['board']['is_new']) ? $txt['mboards_new_board_name'] : $txt['boardsEdit'], ' +

    +
    +
    + +
    +
    '; + + // Option for choosing the category the board lives in. + echo ' + +
    + ', $txt['mboards_category'], ': + +
    +
    + +
    '; + + // If this isn't the only board in this category let the user choose where the board is to live. + if ((isset($context['board']['is_new']) && count($context['board_order']) > 0) || count($context['board_order']) > 1) + { + echo ' +
    + ', $txt['order'], ': +
    +
    '; + + // The first select box gives the user the option to position it before, after or as a child of another board. + echo ' + '; + + // The second select box lists all the boards in the category. + echo ' + +
    '; + } + + // Options for board name and description. + echo ' +
    + ', $txt['full_name'], ':
    + ', $txt['name_on_display'], ' +
    +
    + +
    +
    + ', $txt['mboards_description'], ':
    + ', $txt['mboards_description_desc'], ' +
    +
    + +
    +
    + ', $txt['permission_profile'], ':
    + ', $context['can_manage_permissions'] ? sprintf($txt['permission_profile_desc'], $scripturl . '?action=admin;area=permissions;sa=profiles;' . $context['session_var'] . '=' . $context['session_id']) : strip_tags($txt['permission_profile_desc']), ' +
    +
    + +
    +
    + ', $txt['mboards_groups'], ':
    + ', $txt['mboards_groups_desc'], ' +
    +
    '; + + // List all the membergroups so the user can choose who may access this board. + foreach ($context['groups'] as $group) + echo ' +
    '; + echo ' + ', $txt['check_all'], '
    +
    +
    '; + + // Options to choose moderators, specifiy as announcement board and choose whether to count posts here. + echo ' +
    + ', $txt['mboards_moderators'], ':
    + ', $txt['mboards_moderators_desc'], '
    +
    +
    + +
    +
    +
    +
    '; + + if (empty($context['board']['is_recycle']) && empty($context['board']['topics'])) + echo ' +
    +
    + ', $txt['mboards_redirect'], ':
    + ', $txt['mboards_redirect_desc'], '
    +
    +
    + +
    +
    '; + + if (!empty($context['board']['is_recycle'])) + echo ' +
    ', $txt['mboards_redirect_disabled_recycle'], '
    '; + + if (empty($context['board']['is_recycle']) && !empty($context['board']['topics'])) + echo ' +
    + ', $txt['mboards_redirect'],'
    + ', $txt['mboards_redirect_disabled'], ' +
    '; + + if (!$context['board']['topics'] && empty($context['board']['is_recycle'])) + { + echo ' +
    +
    +
    + ', $txt['mboards_redirect_url'], ':
    + ', $txt['mboards_redirect_url_desc'], '
    +
    +
    + +
    +
    +
    '; + + if ($context['board']['redirect']) + echo ' +
    +
    +
    + ', $txt['mboards_redirect_reset'], ':
    + ', $txt['mboards_redirect_reset_desc'], '
    +
    +
    + + (', sprintf($txt['mboards_current_redirects'], $context['board']['posts']), ') +
    +
    +
    '; + } + + echo ' +
    +
    +
    + ', $txt['mboards_count_posts'], ':
    + ', $txt['mboards_count_posts_desc'], '
    +
    +
    + +
    +
    +
    '; + + // Here the user can choose to force this board to use a theme other than the default theme for the forum. + echo ' +
    +
    +
    + ', $txt['mboards_theme'], ':
    + ', $txt['mboards_theme_desc'], '
    +
    +
    + +
    +
    +
    +
    +
    +
    + ', $txt['mboards_override_theme'], ':
    + ', $txt['mboards_override_theme_desc'], '
    +
    +
    + +
    +
    +
    '; + + if (!empty($context['board']['is_recycle'])) + echo '
    ', $txt['mboards_recycle_disabled_delete'], '
    '; + + echo ' + + '; + + // If this board has no children don't bother with the next confirmation screen. + if ($context['board']['no_children']) + echo ' + '; + + if (isset($context['board']['is_new'])) + echo ' + + '; + else + echo ' + '; + + if (!isset($context['board']['is_new']) && empty($context['board']['is_recycle'])) + echo ' + ' : '>', ''; + echo ' +
    + +
    +
    +
    +
    + +'; + + // Javascript for deciding what to show. + echo ' + '; +} + +// A template used when a user is deleting a board with child boards in it - to see what they want to do with them. +function template_confirm_board_delete() +{ + global $context, $settings, $options, $scripturl, $txt; + + // Print table header. + echo ' +
    +
    + + +
    +

    ', $txt['mboards_delete_board'], '

    +
    +
    + +
    +

    ', $txt['mboards_delete_board_contains'], '

    +
      '; + + foreach ($context['children'] as $child) + echo ' +
    • ', $child['node']['name'], '
    • '; + + echo ' +
    +
    + +
    +
    +

    ', $txt['mboards_delete_what_do'], '

    +
    +
    + +
    +

    +
    + : + +

    + + + + +
    + +
    +
    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/ManageCalendar.template.php b/Themes/default/ManageCalendar.template.php new file mode 100644 index 0000000..5bdf9a8 --- /dev/null +++ b/Themes/default/ManageCalendar.template.php @@ -0,0 +1,117 @@ +'; + + // Show a form for all the holiday information. + echo ' +
    +
    +
    +

    ', $context['page_title'], '

    +
    +
    + +
    +
    +
    + ', $txt['holidays_title_label'], ': +
    +
    + +
    +
    + ', $txt['calendar_year'], ' +
    +
    +   + ', $txt['calendar_month'], '  +   + ', $txt['calendar_day'], '  + +
    +
    '; + + if ($context['is_new']) + echo ' + '; + else + echo ' + + + '; + echo ' + +
    + +
    +
    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/ManageMail.template.php b/Themes/default/ManageMail.template.php new file mode 100644 index 0000000..0f907c4 --- /dev/null +++ b/Themes/default/ManageMail.template.php @@ -0,0 +1,42 @@ + +
    +

    ', $txt['mailqueue_stats'], '

    +
    +
    + +
    +
    +
    ', $txt['mailqueue_size'], '
    +
    ', $context['mail_queue_size'], '
    +
    ', $txt['mailqueue_oldest'], '
    +
    ', $context['oldest_mail'], '
    +
    +
    + +
    '; + + template_show_list('mail_queue'); + + echo ' + +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/ManageMaintenance.template.php b/Themes/default/ManageMaintenance.template.php new file mode 100644 index 0000000..7efafcb --- /dev/null +++ b/Themes/default/ManageMaintenance.template.php @@ -0,0 +1,622 @@ + + ', sprintf($txt['maintain_done'], $context['maintenance_finished']), ' + '; + + echo ' +
    +
    +

    ', $txt['maintain_optimize'], '

    +
    +
    + +
    +
    +

    ', $txt['maintain_optimize_info'], '

    + + +
    +
    + +
    + +
    +

    + ', $txt['help'], ' ', $txt['maintain_backup'], ' +

    +
    + +
    + +
    +
    +

    ', $txt['maintain_backup_info'], '

    '; + + if ($db_type == 'sqlite') + echo ' +

    '; + else + echo ' +


    +
    +

    +

    '; + + echo ' + +
    +
    + +
    '; + + // Show an option to convert to UTF-8 if we're not on UTF-8 yet. + if ($context['convert_utf8']) + { + echo ' +
    +

    ', $txt['utf8_title'], '

    +
    +
    + +
    +
    +

    ', $txt['utf8_introduction'], '

    + ', !empty($modSettings['search_index']) && $modSettings['search_index'] == 'fulltext' ? '

    ' . $txt['utf8_cannot_convert_fulltext'] . '

    ' : '', ' + + +
    +
    + +
    '; + } + + // We might want to convert entities if we're on UTF-8. + if ($context['convert_entities']) + { + echo ' +
    +

    ', $txt['entity_convert_title'], '

    +
    +
    + +
    +
    +

    ', $txt['entity_convert_introduction'], '

    + + +
    +
    + +
    '; + } + + echo ' +
    +
    '; +} + +// Template for the routine maintenance tasks. +function template_maintain_routine() +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings; + + // If maintenance has finished tell the user. + if (!empty($context['maintenance_finished'])) + echo ' +
    + ', sprintf($txt['maintain_done'], $context['maintenance_finished']), ' +
    '; + + // Starts off with general maintenance procedures. + echo ' +
    +
    +

    ', $txt['maintain_version'], '

    +
    +
    + +
    +
    +

    ', $txt['maintain_version_info'], '

    + + +
    +
    + +
    +
    +

    ', $txt['maintain_errors'], '

    +
    +
    + +
    +
    +

    ', $txt['maintain_errors_info'], '

    + + +
    +
    + +
    +
    +

    ', $txt['maintain_recount'], '

    +
    +
    + +
    +
    +

    ', $txt['maintain_recount_info'], '

    + + +
    +
    + +
    +
    +

    ', $txt['maintain_logs'], '

    +
    +
    + +
    +
    +

    ', $txt['maintain_logs_info'], '

    + + +
    +
    + +
    +
    +

    ', $txt['maintain_cache'], '

    +
    +
    + +
    +
    +

    ', $txt['maintain_cache_info'], '

    + + +
    +
    + +
    +
    +
    '; +} + +// Template for the member maintenance tasks. +function template_maintain_members() +{ + global $context, $settings, $options, $txt, $scripturl; + + // If maintenance has finished tell the user. + if (!empty($context['maintenance_finished'])) + echo ' +
    + ', sprintf($txt['maintain_done'], $context['maintenance_finished']), ' +
    '; + + echo ' + +
    +
    +

    ', $txt['maintain_reattribute_posts'], '

    +
    +
    + +
    +
    +

    ', $txt['reattribute_guest_posts'], '

    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +

    + + +

    + + +
    +
    + +
    +
    +

    + + ', $txt['help'], ' ', $txt['maintain_members'], ' + +

    +
    +
    + +
    +
    +

    ', $txt['maintain_members_since1'], ' + ', $txt['maintain_members_since2'], ' ', $txt['maintain_members_since3'], '

    '; + + echo ' +

    + ', $txt['maintain_members_all'], '

    + + + +
    +
    + +
    +
    +
    + + + '; +} + +// Template for the topic maintenance tasks. +function template_maintain_topics() +{ + global $scripturl, $txt, $context, $settings, $modSettings; + + // If maintenance has finished tell the user. + if (!empty($context['maintenance_finished'])) + echo ' +
    + ', sprintf($txt['maintain_done'], $context['maintenance_finished']), ' +
    '; + + // Bit of javascript for showing which boards to prune in an otherwise hidden list. + echo ' + '; + + echo ' +
    +
    +

    ', $txt['maintain_old'], '

    +
    +
    + +
    +
    '; + + // The otherwise hidden "choose which boards to prune". + echo ' +

    + ', $txt['maintain_old_since_days1'], '', $txt['maintain_old_since_days2'], ' +

    +

    +
    +
    +
    +

    '; + + if (!empty($modSettings['enableStickyTopics'])) + echo ' +

    +
    +

    '; + + echo ' +

    + + ', $txt['maintain_old_all'], ' +

    + + + +
    +
    + +
    +
    +

    ', $txt['move_topics_maintenance'], '

    +
    +
    + +
    +
    +

    + + +

    + + +
    +
    + +
    +
    +
    '; +} + +// Simple template for showing results of our optimization... +function template_optimize() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +

    ', $txt['maintain_optimize'], '

    +
    +
    + +
    +

    + ', $txt['database_numb_tables'], '
    + ', $txt['database_optimize_attempt'], '
    '; + + // List each table being optimized... + foreach ($context['optimized_tables'] as $table) + echo ' + ', sprintf($txt['database_optimizing'], $table['name'], $table['data_freed']), '
    '; + + // How did we go? + echo ' +
    ', $context['num_tables_optimized'] == 0 ? $txt['database_already_optimized'] : $context['num_tables_optimized'] . ' ' . $txt['database_optimized']; + + echo ' +

    +

    ', $txt['maintain_return'], '

    +
    + +
    +
    +
    '; +} + +function template_convert_utf8() +{ + global $context, $txt, $settings, $scripturl; + + echo ' +
    +
    +

    ', $txt['utf8_title'], '

    +
    +
    + +
    +
    +

    ', $txt['utf8_introduction'], '

    +
    ', $txt['utf8_warning'], '
    + +
    +
    ', $txt['utf8_source_charset'], ':
    +
    +
    ', $txt['utf8_database_charset'], ':
    +
    ', $context['database_charset'], '
    +
    ', $txt['utf8_target_charset'], ':
    +
    ', $txt['utf8_utf8'], '
    +
    + + + +
    +
    + +
    +
    +
    '; +} + +function template_convert_entities() +{ + global $context, $txt, $settings, $scripturl; + + echo ' +
    +
    +

    ', $txt['entity_convert_title'], '

    +
    +
    + +
    +

    ', $txt['entity_convert_introduction'], '

    +
    + +
    +
    + +
    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/ManageMembergroups.template.php b/Themes/default/ManageMembergroups.template.php new file mode 100644 index 0000000..8065031 --- /dev/null +++ b/Themes/default/ManageMembergroups.template.php @@ -0,0 +1,610 @@ +
    '; + template_show_list('post_count_membergroups_list'); + +} + +function template_new_group() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    +
    +
    +

    ', $txt['membergroups_new_group'], '

    +
    +
    + +
    +
    +
    + +
    +
    + +
    '; + if ($context['undefined_group']) + { + echo ' +
    + +
    +
    +
    + ', $txt['membergroups_edit_select_group_type'], ' +
    '; + + if ($context['allow_protected']) + echo ' +
    '; + + echo ' +
    +
    +
    +
    +
    '; + } + + if ($context['post_group'] || $context['undefined_group']) + echo ' +
    + ', $txt['membergroups_min_posts'], ': +
    +
    + +
    '; + if (!$context['post_group'] || !empty($modSettings['permission_enable_postgroups'])) + { + echo ' +
    +
    + ', $txt['membergroups_can_edit_later'], ' +
    +
    +
    + ', $txt['membergroups_select_permission_type'], ' + + +
    + + + +
    + + + + +
    +
    '; + } + echo ' +
    + ', $txt['membergroups_new_board'], ':', $context['post_group'] ? '
    + ' . $txt['membergroups_new_board_post_groups'] . '' : '', ' +
    +
    +
    + ', $txt['membergroups_new_board_desc'], ''; + foreach ($context['boards'] as $board) + echo ' +
    '; + + echo ' +
    + +
    +
    +
    +
    + +
    +
    + +
    '; + if ($context['undefined_group']) + { + echo ' + '; + } + echo ' + +
    +
    +
    '; +} + +function template_edit_group() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    +
    +

    ', $txt['membergroups_edit_group'], ' - ', $context['group']['name'], ' +

    +
    +
    + +
    +
    +
    + +
    +
    + +
    '; + + if ($context['group']['id'] != 3 && $context['group']['id'] != 4) + echo ' + +
    + +
    +
    + +
    '; + + // Group type... + if ($context['group']['allow_post_group']) + { + echo ' +
    + +
    +
    +
    + ', $txt['membergroups_edit_select_group_type'], ' +
    '; + + if ($context['group']['allow_protected']) + echo ' +
    '; + + echo ' +
    +
    +
    +
    +
    '; + } + + if ($context['group']['id'] != 3 && $context['group']['id'] != 4) + echo ' +
    + +
    +
    + +
    +
    +
    + +
    +
    + +
    '; + + // Can they inherit permissions? + if ($context['group']['id'] > 1 && $context['group']['id'] != 3) + { + echo ' +
    + :
    + ', $txt['membergroups_edit_inherit_permissions_desc'], ' +
    +
    + + +
    '; + } + + if ($context['group']['allow_post_group']) + echo ' + +
    + +
    +
    + +
    '; + echo ' +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + ', $txt['membergroups_star_image_note'], ' +
    +
    + ', $txt['membergroups_images_url'], ' + + * +
    +
    +
    + ', $txt['membergroups_max_messages_note'], ' +
    +
    + +
    '; + if (!empty($context['boards'])) + { + echo ' +
    + ', $txt['membergroups_new_board'], ':', $context['group']['is_post_group'] ? '
    + ' . $txt['membergroups_new_board_post_groups'] . '' : '', ' +
    +
    +
    + ', $txt['membergroups_new_board_desc'], ''; + foreach ($context['boards'] as $board) + echo ' +
    '; + + echo ' +
    + +
    + + +
    '; + } + echo ' +
    +
    + ', $context['group']['allow_delete'] ? ' + ' : '', ' +
    +
    + +
    + +
    +
    +
    + + '; + + if ($context['group']['allow_post_group']) + echo ' + '; +} + +// Templating for viewing the members of a group. +function template_group_members() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    +
    +

    ', $context['page_title'], '

    +
    +
    + +
    +
    +
    + ', $txt['name'], ': +
    +
    + ', $context['group']['name'], ' ', $context['group']['stars'], ' +
    '; + //Any description to show? + if (!empty($context['group']['description'])) + echo ' +
    + ' . $txt['membergroups_members_description'] . ': +
    +
    + ', $context['group']['description'] ,' +
    '; + + echo ' +
    + ', $txt['membergroups_members_top'], ': +
    +
    + ', $context['total_members'] ,' +
    '; + // Any group moderators to show? + if (!empty($context['group']['moderators'])) + { + $moderators = array(); + foreach ($context['group']['moderators'] as $moderator) + $moderators[] = '' . $moderator['name'] . ''; + + echo ' +
    + ', $txt['membergroups_members_group_moderators'], ': +
    +
    + ', implode(', ', $moderators) ,' +
    '; + } + + echo ' +
    +
    + +
    + +
    +
    +

    ', $txt['membergroups_members_group_members'], '

    +
    +
    +
    ', $txt['pages'], ': ', $context['page_index'], '
    + + + + + + + + ', $txt['posts'], $context['sort_by'] == 'posts' ? ' ' : '', ''; + if (!empty($context['group']['assignable'])) + echo ' + '; + echo ' + + + '; + + if (empty($context['members'])) + echo ' + + + '; + + foreach ($context['members'] as $member) + { + echo ' + + + '; + + // Is it totally hidden? + if ($member['show_email'] == 'no') + echo ' + ', $txt['hidden'], ''; + // ... otherwise they want it hidden but it's not to this person? + elseif ($member['show_email'] == 'yes_permission_override') + echo ' + ', $member['email'], ''; + // ... otherwise it's visible - but only via an image? + elseif ($member['show_email'] == 'no_through_forum') + echo ' + ', ($settings['use_image_buttons'] ? '' . $txt['email'] . '' : $txt['email']), ''; + // ... otherwise it must be a 'yes', show it and show it fully. + else + echo ' + ', $member['email'], ''; + + echo ' + + + + ', $member['posts'], ''; + if (!empty($context['group']['assignable'])) + echo ' + '; + echo ' + '; + } + + echo ' + +
    ', $txt['name'], $context['sort_by'] == 'name' ? ' ' : '', '', $txt['email'], $context['sort_by'] == 'email' ? ' ' : '', '', $txt['membergroups_members_last_active'], $context['sort_by'] == 'active' ? ' ' : '', '', $txt['date_registered'], $context['sort_by'] == 'registered' ? ' ' : '', '
    ', $txt['membergroups_members_no_members'], '
    ', $member['name'], '', $member['last_online'], '', $member['registered'], '
    +
    +
    ', $txt['pages'], ': ', $context['page_index'], '
    '; + + if (!empty($context['group']['assignable'])) + echo ' +
    '; + echo ' +
    +
    '; + + if (!empty($context['group']['assignable'])) + { + echo ' +
    +

    ', $txt['membergroups_members_add_title'], '

    +
    +
    + +
    + ', $txt['membergroups_members_add_desc'], ': + +
    + +
    + +
    '; + } + + echo ' + +
    +
    +
    '; + + if (!empty($context['group']['assignable'])) + echo ' + + '; +} + +// Allow the moderator to enter a reason to each user being rejected. +function template_group_request_reason() +{ + global $settings, $options, $context, $txt, $scripturl; + + // Show a welcome message to the user. + echo ' +
    +
    +
    +

    ', $txt['mc_groups_reason_title'], '

    +
    +
    + +
    +
    '; + + // Loop through and print out a reason box for each... + foreach ($context['group_requests'] as $request) + echo ' +
    + ', sprintf($txt['mc_groupr_reason_desc'], $request['member_link'], $request['group_link']), ': +
    +
    + + +
    '; + + echo ' +
    + + + +
    + +
    +
    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/ManageMembers.template.php b/Themes/default/ManageMembers.template.php new file mode 100644 index 0000000..5ac1daa --- /dev/null +++ b/Themes/default/ManageMembers.template.php @@ -0,0 +1,332 @@ + +
    +
    +

    + ', $txt['search_for'], ' + ', $txt['wild_cards_allowed'], ' +

    +
    + +
    + +
    +
    +
    +
    +
    + ', $txt['member_id'], ': + +
    +
    + +
    +
    + ', $txt['age'], ': + +
    +
    + +
    +
    + ', $txt['member_postcount'], ': + +
    +
    + +
    +
    + ', $txt['date_registered'], ': + +
    +
    + ', $txt['date_format'], ' +
    +
    + ', $txt['viewmembers_online'], ': + +
    +
    + ', $txt['date_format'], ' +
    +
    +
    +
    +
    +
    + ', $txt['username'], ': +
    +
    + +
    +
    + ', $txt['email_address'], ': +
    +
    + +
    +
    + ', $txt['website'], ': +
    +
    + +
    +
    + ', $txt['location'], ': +
    +
    + +
    +
    + ', $txt['ip_address'], ': +
    +
    + +
    +
    + ', $txt['messenger_address'], ': +
    +
    + +
    +
    +
    +
    +
    +
    +
    + ', $txt['gender'], ' +    +    + +
    +
    +
    +
    + ', $txt['activation_status'], ' +    + +
    +
    +
    +
    + +
    +
    +
    +

    ', $txt['member_part_of_these_membergroups'], '

    +
    +
    + + + + + + + + + '; + + foreach ($context['membergroups'] as $membergroup) + echo ' + + + + + '; + + echo ' + + + + + + +
    ', $txt['membergroups'], '', $txt['primary'], '', $txt['additional'], '
    ', $membergroup['name'], ' + + + ', $membergroup['can_be_additional'] ? '' : '', ' +
    + ', $txt['check_all'], ' + + + + +
    + + + + + + + + + '; + + foreach ($context['postgroups'] as $postgroup) + echo ' + + + + '; + + echo ' + + + + + +
    + ', $txt['membergroups_postgroups'], ' +  
    + ', $postgroup['name'], ' + + +
    + ', $txt['check_all'], ' + + +
    +

    +
    + +
    +
    + +
    '; +} + +function template_admin_browse() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    '; + + template_show_list('approve_list'); + + // If we have lots of outstanding members try and make the admin's life easier. + if ($context['approve_list']['total_num_items'] > 20) + { + echo ' +
    +
    +
    +

    ', $txt['admin_browse_outstanding'], '

    +
    + + +
    + +
    +
    +
    + ', $txt['admin_browse_outstanding_days_1'], ': +
    +
    + ', $txt['admin_browse_outstanding_days_2'], '. +
    +
    + ', $txt['admin_browse_outstanding_perform'], ': +
    +
    + +
    +
    + + + + + + ', !empty($context['approve_list']['sort']['desc']) ? ' + ' : '', ' +
    + +
    + +
    '; + } + + echo ' +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/ManageNews.template.php b/Themes/default/ManageNews.template.php new file mode 100644 index 0000000..aa12029 --- /dev/null +++ b/Themes/default/ManageNews.template.php @@ -0,0 +1,355 @@ + +
    + + + + + + + + + '; + + // Loop through all the current news items so you can edit/remove them. + foreach ($context['admin_current_news'] as $admin_news) + echo ' + + + '; + + // This provides an empty text box to add a news item to the site. + echo ' + + + + + + +
    ', $txt['admin_edit_news'], '', $txt['preview'], '
    + +
    +
    +
    ', $admin_news['parsed'], '
    +
    + +
    +
    + + + +
    +
    + +
    + +
    + +
    '; +} + +function template_email_members() +{ + global $context, $settings, $options, $txt, $scripturl; + + // This is some javascript for the simple/advanced toggling stuff. + echo ' + '; + + echo ' +
    +
    +
    +

    ', $txt['admin_newsletters'], '

    +
    +
    + ', $txt['admin_news_select_recipients'], ' +
    +
    + +
    +
    +
    + ', $txt['admin_news_select_group'], ':
    + ', $txt['admin_news_select_group_desc'], ' +
    +
    '; + + foreach ($context['groups'] as $group) + echo ' + (', $group['member_count'], ')
    '; + + echo ' +
    + '; + + echo ' +
    +

    +
    + +
    +
    + + + + +
    + + +
    +
    +
    +
    '; + + // Make the javascript stuff visible. + echo ' + + '; +} + +function template_email_members_compose() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +
    +

    + ', $txt['help'], ' ', $txt['admin_newsletters'], ' +

    +
    +
    + ', $txt['email_variables'], ' +
    +
    + +
    +

    + +

    +

    + +

    +
      +
    • +
    • +
    • +
    +

    + +

    +
    + +
    + + + + '; + + foreach ($context['recipients'] as $key => $values) + echo ' + '; + + echo ' +
    +
    +
    '; +} + +function template_email_members_send() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +
    +

    + ', $txt['help'], ' ', $txt['admin_newsletters'], ' +

    +
    +
    + +
    +

    + ', $context['percentage_done'], '% ', $txt['email_done'], ' +

    + + + + + + + + + + '; + + // All the things we must remember! + foreach ($context['recipients'] as $key => $values) + echo ' + '; + + echo ' +
    + +
    +
    +
    +
    + '; +} + +?> \ No newline at end of file diff --git a/Themes/default/ManagePaid.template.php b/Themes/default/ManagePaid.template.php new file mode 100644 index 0000000..e69ba0b --- /dev/null +++ b/Themes/default/ManagePaid.template.php @@ -0,0 +1,676 @@ +'; + + echo ' +
    +
    +
    +

    ', $txt['paid_' . $context['action_type'] . '_subscription'], '

    +
    '; + + if (!empty($context['disable_groups'])) + echo ' +
    + ', $txt['paid_mod_edit_note'], ' +
    + '; + echo ' +
    + +
    +
    +
    + ', $txt['paid_mod_name'], ': +
    +
    + +
    +
    + ', $txt['paid_mod_desc'], ': +
    +
    + +
    +
    + : +
    +
    + +
    +
    + :
    ', $txt['paid_mod_active_desc'], ' +
    +
    + +
    +
    +
    +
    +
    + ', $txt['paid_mod_prim_group'], ':
    ', $txt['paid_mod_prim_group_desc'], ' +
    +
    + +
    +
    + ', $txt['paid_mod_add_groups'], ':
    ', $txt['paid_mod_add_groups_desc'], ' +
    +
    '; + + // Put a checkbox in for each group + foreach ($context['groups'] as $id => $name) + echo ' +
    '; + + echo ' +
    +
    + ', $txt['paid_mod_reminder'], ':
    ', $txt['paid_mod_reminder_desc'], ' +
    +
    + +
    +
    + ', $txt['paid_mod_email'], ':
    ', $txt['paid_mod_email_desc'], ' +
    +
    + +
    +
    +
    + + ', $txt['paid_mod_fixed_price'], ' +
    +
    +
    +
    +
    + ', $txt['paid_cost'], ' (', str_replace('%1.2f', '', $modSettings['paid_currency_symbol']), '): +
    +
    + +
    +
    + ', $txt['paid_mod_span'], ': +
    +
    + + +
    +
    +
    +
    + + ', $txt['paid_mod_flexible_price'], ' +
    +
    +
    '; + + //!! Removed until implemented + if (!empty($sdflsdhglsdjgs)) + echo ' +
    +
    + :
    ', $txt['paid_mod_allow_partial_desc'], ' +
    +
    + +
    +
    '; + + echo ' +
    + ', $txt['paid_mod_price_breakdown'], '
    + ', $txt['paid_mod_price_breakdown_desc'], ' +
    +
    +
    + ', $txt['paid_duration'], ' +
    +
    + ', $txt['paid_cost'], ' (', preg_replace('~%[df\.\d]+~', '', $modSettings['paid_currency_symbol']), ') +
    +
    + ', $txt['paid_per_day'], ': +
    +
    + +
    +
    + ', $txt['paid_per_week'], ': +
    +
    + +
    +
    + ', $txt['paid_per_month'], ': +
    +
    + +
    +
    + ', $txt['paid_per_year'], ': +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    '; + +} + +function template_delete_subscription() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    +
    +
    +

    ', $txt['paid_delete_subscription'], '

    +
    +
    + +
    +

    ', $txt['paid_mod_delete_warning'], '

    + + + +
    + +
    +
    +
    +
    '; + +} + +// Add or edit an existing subscriber. +function template_modify_user_subscription() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + // Some quickly stolen javascript from Post, could do with being more efficient :) + echo ' + '; + + echo ' +
    +
    +
    +

    + ', $txt['paid_' . $context['action_type'] . '_subscription'], ' - ', $context['current_subscription']['name'], ' + ', empty($context['sub']['username']) ? '' : ' (' . $txt['user'] . ': ' . $context['sub']['username'] . ')', ' +

    +
    +
    + +
    +
    '; + + // Do we need a username? + if ($context['action_type'] == 'add') + echo ' + +
    + ', $txt['paid_username'], ':
    + ', $txt['one_username'], ' +
    +
    + +
    '; + + echo ' +
    + ', $txt['paid_status'], ': +
    +
    + +
    +
    +
    + ', $txt['start_date_and_time'], ' +   + ', (isset($txt['calendar_month']) ? $txt['calendar_month'] : $txt['calendar_month']), '  +   + ', (isset($txt['calendar_day']) ? $txt['calendar_day'] : $txt['calendar_day']), '  + + ', $txt['hour'], ': + ', $txt['minute'], ': +
    +
    + ', $txt['end_date_and_time'], ' +   + ', (isset($txt['calendar_month']) ? $txt['calendar_month'] : $txt['calendar_month']), '  +   + ', (isset($txt['calendar_day']) ? $txt['calendar_day'] : $txt['calendar_day']), '  + + ', $txt['hour'], ': + ', $txt['minute'], ': +
    + +
    + +
    + +
    + + '; + + if (!empty($context['pending_payments'])) + { + echo ' +
    +

    ', $txt['pending_payments'], '

    +
    +
    + ', $txt['pending_payments_desc'], ' +
    +
    +

    ', $txt['pending_payments_value'], '

    +
    +
    + +
    + +
    + +
    '; + } + + echo ' +
    +
    '; +} + +// Template for a user to edit/pick their subscriptions. +function template_user_subscription() +{ + global $context, $txt, $scripturl, $modSettings; + + echo ' + +
    '; +} + +// The "choose payment" dialog. +function template_choose_payment() +{ + global $context, $txt, $modSettings, $scripturl; + + echo ' + +
    '; +} + +// The "thank you" bit... +function template_paid_done() +{ + global $context, $txt, $modSettings, $scripturl; + + echo ' + +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/ManagePermissions.template.php b/Themes/default/ManagePermissions.template.php new file mode 100644 index 0000000..28be570 --- /dev/null +++ b/Themes/default/ManagePermissions.template.php @@ -0,0 +1,1192 @@ + + ', sprintf($txt['permission_cannot_edit'], $scripturl . '?action=admin;area=permissions;sa=profiles'), ' + '; + + echo ' +
    +
    '; + + if (!empty($context['profile'])) + echo ' +
    +

    ', $txt['permissions_for_profile'], ': "', $context['profile']['name'], '"

    +
    '; + + echo ' + + + + + '; + + if (empty($modSettings['permission_enable_deny'])) + echo ' + '; + else + echo ' + + '; + + echo ' + + + + + '; + + $alternate = false; + foreach ($context['groups'] as $group) + { + $alternate = !$alternate; + echo ' + + + '; + + if (empty($modSettings['permission_enable_deny'])) + echo ' + '; + else + echo ' + + '; + + echo ' + + + '; + } + + echo ' + +
    ', $txt['membergroups_name'], '', $txt['membergroups_members_top'], '', $txt['membergroups_permissions'], '', $txt['permissions_allowed'], '', $txt['permissions_denied'], '', $context['can_modify'] ? $txt['permissions_modify'] : $txt['permissions_view'], ' + ', $context['can_modify'] ? '' : '', ' +
    + ', $group['name'], $group['id'] == -1 ? ' (?)' : ($group['id'] == 0 ? ' (?)' : ($group['id'] == 1 ? ' (?)' : ($group['id'] == 3 ? ' (?)' : ''))); + + if (!empty($group['children'])) + echo ' +
    ', $txt['permissions_includes_inherited'], ': "', implode('", "', $group['children']), '"'; + + echo ' +
    ', $group['can_search'] ? $group['link'] : $group['num_members'], '', $group['num_permissions']['allowed'], '', $group['num_permissions']['allowed'], '', $group['num_permissions']['denied'], '', $group['allow_modify'] ? '' . ($context['can_modify'] ? $txt['permissions_modify'] : $txt['permissions_view']). '' : '', '', $group['allow_modify'] && $context['can_modify'] ? '' : '', '
    +
    '; + + // Advanced stuff... + if ($context['can_modify']) + { + echo ' +
    +

    + + * ', $txt['permissions_advanced_options'], ' + +

    +
    +
    + +
    +
    + ', $txt['permissions_with_selection'], ' +
    +
    + ', $txt['permissions_apply_pre_defined'], ' (?): +
    +
    + +
    +
    + ', $txt['permissions_like_group'], ': +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    '; + + // Javascript for the advanced stuff. + echo ' + '; + + if (!empty($context['profile'])) + echo ' + '; + + echo ' + '; + } + else + echo ' + '; + + echo ' +
    +
    +
    '; +} + +function template_by_board() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    +
    +

    ', $txt['permissions_boards'], '

    +
    +
    + ', $txt['permissions_boards_desc'], ' +
    +
    +

    + ', $txt['board_name'], ' + ', $txt['permission_profile'], ' +

    +
    '; + + if (!$context['edit_all']) + echo ' + '; + + foreach ($context['categories'] as $category) + { + echo ' +
    +

    ', $category['name'], '

    +
    '; + + if (!empty($category['boards'])) + echo ' +
    + +
    + +
    + +
    '; + } + + echo ' +
    '; + + if ($context['edit_all']) + echo ' + '; + else + echo ' + [', $txt['permissions_board_all'], ']'; + + echo ' + +
    +
    +
    '; +} + +// Edit permission profiles (predefined). +function template_edit_profiles() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    +
    +
    +

    ', $txt['permissions_profile_edit'], '

    +
    + + + + + + + + + + '; + $alternate = false; + foreach ($context['profiles'] as $profile) + { + echo ' + + + + + '; + $alternate = !$alternate; + } + + echo ' + +
    ', $txt['permissions_profile_name'], '', $txt['permissions_profile_used_by'], '', $txt['delete'], '
    '; + + if (!empty($context['show_rename_boxes']) && $profile['can_edit']) + echo ' + '; + else + echo ' + ', $profile['name'], ''; + + echo ' + + ', !empty($profile['boards_text']) ? $profile['boards_text'] : $txt['permissions_profile_used_by_none'], ' + + +
    +
    + '; + + if ($context['can_edit_something']) + echo ' + '; + + echo ' + +
    +
    +
    +
    +
    +

    ', $txt['permissions_profile_new'], '

    +
    +
    + +
    +
    +
    + ', $txt['permissions_profile_name'], ': +
    +
    + +
    +
    + ', $txt['permissions_profile_copy_from'], ': +
    +
    + +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    '; +} + +function template_modify_group() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + // Cannot be edited? + if (!$context['profile']['can_modify']) + { + echo ' +
    + ', sprintf($txt['permission_cannot_edit'], $scripturl . '?action=admin;area=permissions;sa=profiles'), ' +
    '; + } + else + { + echo ' + '; + } + + echo ' +
    +
    '; + + if (!empty($modSettings['permission_enable_deny']) && $context['group']['id'] != -1) + echo ' +
    + ', $txt['permissions_option_desc'], ' +
    '; + + echo ' +
    +

    '; + if ($context['permission_type'] == 'board') + echo ' + ', $txt['permissions_local_for'], ' "', $context['group']['name'], '" ', $txt['permissions_on'], ' "', $context['profile']['name'], '"'; + else + echo ' + ', $context['permission_type'] == 'membergroup' ? $txt['permissions_general'] : $txt['permissions_board'], ' - "', $context['group']['name'], '"'; + echo ' +

    +
    +
    + +
    + ', $txt['permissions_change_view'], ': ', ($context['view_type'] == 'simple' ? '*' : ''), '', $txt['permissions_view_simple'], ' | + ', ($context['view_type'] == 'classic' ? '*' : ''), '', $txt['permissions_view_classic'], ' +
    + +
    +
    '; + + // Draw out the main bits. + if ($context['view_type'] == 'simple') + template_modify_group_simple($context['permission_type']); + else + template_modify_group_classic($context['permission_type']); + + // If this is general permissions also show the default profile. + if ($context['permission_type'] == 'membergroup') + { + echo ' +
    +
    +
    +

    ', $txt['permissions_board'], '

    +
    +
    + ', $txt['permissions_board_desc'], ' +
    +
    '; + + if ($context['view_type'] == 'simple') + template_modify_group_simple('board'); + else + template_modify_group_classic('board'); + + echo ' +
    '; + } + + if ($context['profile']['can_modify']) + echo ' +
    + +
    '; + + echo ' + +
    +
    +
    '; + +} + +// A javascript enabled clean permissions view. +function template_modify_group_simple($type) +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + // Simple only has one column so we only need bother ourself with that one. + $permission_data = &$context['permissions'][$type]['columns'][0]; + + // Short cut for disabling fields we can't change. + $disable_field = $context['profile']['can_modify'] ? '' : 'disabled="disabled" '; + + echo ' + + + + '; + if (empty($modSettings['permission_enable_deny']) || $context['group']['id'] == -1) + echo ' + '; + else + echo ' + + + '; + echo ' + + + '; + + foreach ($permission_data as $id_group => $permissionGroup) + { + if (empty($permissionGroup['permissions'])) + continue; + + // Are we likely to have something in this group to display or is it all hidden? + $has_display_content = false; + if (!$permissionGroup['hidden']) + { + // Before we go any further check we are going to have some data to print otherwise we just have a silly heading. + foreach ($permissionGroup['permissions'] as $permission) + if (!$permission['hidden']) + $has_display_content = true; + + if ($has_display_content) + { + echo ' + + '; + if (empty($modSettings['permission_enable_deny']) || $context['group']['id'] == -1) + echo ' + '; + else + echo ' + + + '; + echo ' + '; + } + } + + $alternate = false; + foreach ($permissionGroup['permissions'] as $permission) + { + // If it's hidden keep the last value. + if ($permission['hidden'] || $permissionGroup['hidden']) + { + echo ' + + + '; + } + else + { + echo ' + + + '; + + if (empty($modSettings['permission_enable_deny']) || $context['group']['id'] == -1) + echo ' + '; + else + echo ' + + + '; + + echo ' + '; + } + $alternate = !$alternate; + } + + if (!$permissionGroup['hidden'] && $has_display_content) + echo ' + + + '; + } + echo ' + +
     ', $txt['permissions_option_on'], '', $txt['permissions_option_off'], '', $txt['permissions_option_deny'], '
    + + * ', $permissionGroup['name'], ' + + +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    + ', $permission['help_index'] ? '' . $txt['help'] . '' : '', ' + ', $permission['name'], '
    + '; +} + +// The SMF 1.x way of looking at permissions. +function template_modify_group_classic($type) +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + $permission_type = &$context['permissions'][$type]; + $disable_field = $context['profile']['can_modify'] ? '' : 'disabled="disabled" '; + + echo ' +
    + +
    '; + + foreach ($permission_type['columns'] as $column) + { + echo ' + '; + + foreach ($column as $permissionGroup) + { + if (empty($permissionGroup['permissions'])) + continue; + + // Are we likely to have something in this group to display or is it all hidden? + $has_display_content = false; + if (!$permissionGroup['hidden']) + { + // Before we go any further check we are going to have some data to print otherwise we just have a silly heading. + foreach ($permissionGroup['permissions'] as $permission) + if (!$permission['hidden']) + $has_display_content = true; + + if ($has_display_content) + { + echo ' + + '; + if (empty($modSettings['permission_enable_deny']) || $context['group']['id'] == -1) + echo ' + '; + else + echo ' + + + '; + echo ' + '; + } + } + + $alternate = false; + foreach ($permissionGroup['permissions'] as $permission) + { + // If it's hidden keep the last value. + if ($permission['hidden'] || $permissionGroup['hidden']) + { + echo ' + + + '; + } + else + { + echo ' + + '; + + if ($permission['has_own_any']) + { + echo ' + + '; + + // Guests can't do their own thing. + if ($context['group']['id'] != -1) + { + echo ' + + '; + + if (empty($modSettings['permission_enable_deny'])) + echo ' + '; + else + echo ' + + + '; + + echo ' + '; + } + + echo ' + + '; + + if (empty($modSettings['permission_enable_deny']) || $context['group']['id'] == -1) + echo ' + '; + else + echo ' + + + '; + + echo ' + '; + } + else + { + echo ' + '; + + if (empty($modSettings['permission_enable_deny']) || $context['group']['id'] == -1) + echo ' + '; + else + echo ' + + + '; + + echo ' + '; + } + } + $alternate = !$alternate; + } + + if (!$permissionGroup['hidden'] && $has_display_content) + echo ' + + + '; + } + echo ' +
    ', $permissionGroup['name'], '
    ', $txt['permissions_option_on'], '
    ', $txt['permissions_option_off'], '
    ', $txt['permissions_option_deny'], '
    '; + + if ($permission['has_own_any']) + { + // Guests can't have own permissions. + if ($context['group']['id'] != -1) + echo ' + '; + + echo ' + '; + } + else + echo ' + '; + echo ' +
    + ', $permission['show_help'] ? '' . $txt['help'] . '' : '', ' + ', $permission['name'], '
    ', $permission['own']['name'], ':
    ', $permission['any']['name'], ':
    ', $permission['name'], '
    '; + } + echo ' +
    +
    + +
    '; +} + +function template_inline_permissions() +{ + global $context, $settings, $options, $txt, $modSettings; + + echo ' +
    + ', $txt['avatar_select_permission'], ''; + if (empty($modSettings['permission_enable_deny'])) + echo ' +
      '; + else + echo ' +
      ', $txt['permissions_option_desc'], '
      +
      +
      + ', $txt['permissions_option_on'], ' + ', $txt['permissions_option_off'], ' + ', $txt['permissions_option_deny'], ' +
      +
      +
      '; + foreach ($context['member_groups'] as $group) + { + if (!empty($modSettings['permission_enable_deny'])) + echo ' +
      '; + else + echo ' +
    • '; + + if (empty($modSettings['permission_enable_deny'])) + echo ' + '; + else + echo ' + + + '; + + if (!empty($modSettings['permission_enable_deny'])) + echo ' +
    • +
      + ', $group['name'], ' +
      '; + else + echo ' + ', $group['name'], ' + '; + } + + if (empty($modSettings['permission_enable_deny'])) + echo ' +
    '; + else + echo ' + '; + + echo ' +
    + + + + '; +} + +// Edit post moderation permissions. +function template_postmod_permissions() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    +
    +
    +

    ', $txt['permissions_post_moderation'], '

    +
    '; + + // Got advanced permissions - if so warn! + if (!empty($modSettings['permission_enable_deny'])) + echo ' +
    ', $txt['permissions_post_moderation_deny_note'], '
    '; + + echo ' +
    + ', $txt['permissions_post_moderation_select'], ': + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + '; + + foreach ($context['profile_groups'] as $group) + { + echo ' + + + + + + + + + + + + + + + '; + } + + echo ' + +
    + ', $txt['permissions_post_moderation_new_topics'], ' + + ', $txt['permissions_post_moderation_replies_own'], ' + + ', $txt['permissions_post_moderation_replies_any'], ' + + ', $txt['permissions_post_moderation_attachments'], ' +
    + ', $txt['permissions_post_moderation_group'], ' + ', $txt['permissions_post_moderation_allow'], '', $txt['permissions_post_moderation_moderate'], '', $txt['permissions_post_moderation_disallow'], '', $txt['permissions_post_moderation_allow'], '', $txt['permissions_post_moderation_moderate'], '', $txt['permissions_post_moderation_disallow'], '', $txt['permissions_post_moderation_allow'], '', $txt['permissions_post_moderation_moderate'], '', $txt['permissions_post_moderation_disallow'], '', $txt['permissions_post_moderation_allow'], '', $txt['permissions_post_moderation_moderate'], '', $txt['permissions_post_moderation_disallow'], '
    + ', $group['name'], ''; + if (!empty($group['children'])) + echo ' +
    ', $txt['permissions_includes_inherited'], ': "', implode('", "', $group['children']), '"'; + + echo ' +
    +
    + +
    +
    +

    + ', $txt['permissions_post_moderation_legend'], ':
    + ', $txt['permissions_post_moderation_allow'], ' - ', $txt['permissions_post_moderation_allow'], '
    + ', $txt['permissions_post_moderation_moderate'], ' - ', $txt['permissions_post_moderation_moderate'], '
    + ', $txt['permissions_post_moderation_disallow'], ' - ', $txt['permissions_post_moderation_disallow'], ' +

    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/ManageScheduledTasks.template.php b/Themes/default/ManageScheduledTasks.template.php new file mode 100644 index 0000000..511cbfd --- /dev/null +++ b/Themes/default/ManageScheduledTasks.template.php @@ -0,0 +1,95 @@ + + ', $txt['scheduled_tasks_were_run'], ' + '; + + template_show_list('scheduled_tasks'); +} + +// A template for, you guessed it, editing a task! +function template_edit_scheduled_tasks() +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings; + + // Starts off with general maintenance procedures. + echo ' +
    +
    +
    +

    ', $txt['scheduled_task_edit'], '

    +
    +
    + ', sprintf($txt['scheduled_task_time_offset'], $context['server_time']), ' +
    +
    + +
    +
    +
    + ', $txt['scheduled_tasks_name'], ': +
    +
    + ', $context['task']['name'], '
    + ', $context['task']['desc'], ' +
    +
    + ', $txt['scheduled_task_edit_interval'], ': +
    +
    + ', $txt['scheduled_task_edit_repeat'], ' + + +
    +
    + ', $txt['scheduled_task_edit_start_time'], ':
    + ', $txt['scheduled_task_edit_start_time_desc'], ' +
    +
    + +
    +
    + ', $txt['scheduled_tasks_enabled'], ': +
    +
    + +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/ManageSearch.template.php b/Themes/default/ManageSearch.template.php new file mode 100644 index 0000000..6ed8f3f --- /dev/null +++ b/Themes/default/ManageSearch.template.php @@ -0,0 +1,428 @@ + +
    +
    +

    ', $txt['search_weights'], '

    +
    +
    + +
    +
    +
    + ', $txt['help'], ' + ', $txt['search_weight_frequency'], ': +
    +
    + + ', $context['relative_weights']['search_weight_frequency'], '% +
    +
    + ', $txt['help'], ' + ', $txt['search_weight_age'], ': +
    +
    + + ', $context['relative_weights']['search_weight_age'], '% +
    +
    + ', $txt['help'], ' + ', $txt['search_weight_length'], ': +
    +
    + + ', $context['relative_weights']['search_weight_length'], '% +
    +
    + ', $txt['help'], ' + ', $txt['search_weight_subject'], ': +
    +
    + + ', $context['relative_weights']['search_weight_subject'], '% +
    +
    + ', $txt['help'], ' + ', $txt['search_weight_first_message'], ': +
    +
    + + ', $context['relative_weights']['search_weight_first_message'], '% +
    +
    + ', $txt['help'], ' + ', $txt['search_weight_sticky'], ': +
    +
    + + ', $context['relative_weights']['search_weight_sticky'], '% +
    +
    + ', $txt['search_weights_total'], ' +
    +
    + ', $context['relative_weights']['total'], ' + 100% +
    +
    + +
    +
    + +
    +
    + +
    + '; +} + +function template_select_search_method() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    +
    +
    +

    ', $txt['search_method'], '

    +
    + +
    + +
    +
    + + '; + if (!empty($context['table_info'])) + echo ' +
    + ', $txt['search_method_messages_table_space'], ': +
    +
    + ', $context['table_info']['data_length'], ' +
    +
    + ', $txt['search_method_messages_index_space'], ': +
    +
    + ', $context['table_info']['index_length'], ' +
    '; + echo ' +
    + ', $context['double_index'] ? '
    + ' . $txt['search_double_index'] . '
    ' : '', ' +
    + ', $txt['search_index'], ' +
    +
    + ', $txt['search_index_none'], ' +
    '; + + if ($context['supports_fulltext']) + { + echo ' +
    + + ', $txt['search_method_fulltext_index'], ' +
    +
    + + '; + if (empty($context['fulltext_index']) && empty($context['cannot_create_fulltext'])) + echo ' + ', $txt['search_index_label'], ': ', $txt['search_method_no_index_exists'], ' [', $txt['search_method_fulltext_create'], ']'; + elseif (empty($context['fulltext_index']) && !empty($context['cannot_create_fulltext'])) + echo ' + ', $txt['search_index_label'], ': ', $txt['search_method_fulltext_cannot_create']; + else + echo ' + ', $txt['search_index_label'], ': ', $txt['search_method_index_already_exists'], ' [', $txt['search_method_fulltext_remove'], ']
    + ', $txt['search_index_size'], ': ', $context['table_info']['fulltext_length']; + echo ' +
    +
    '; + } + + echo ' +
    + + ', $txt['search_index_custom'], ' +
    +
    + '; + if ($context['custom_index']) + echo ' + ', $txt['search_index_label'], ': ', $txt['search_method_index_already_exists'], ' [', $txt['search_index_custom_remove'], ']
    + ', $txt['search_index_size'], ': ', $context['table_info']['custom_index_length']; + elseif ($context['partial_custom_index']) + echo ' + ', $txt['search_index_label'], ': ', $txt['search_method_index_partial'], ' [', $txt['search_index_custom_remove'], '] [', $txt['search_index_custom_resume'], ']
    + ', $txt['search_index_size'], ': ', $context['table_info']['custom_index_length']; + else + echo ' + ', $txt['search_index_label'], ': ', $txt['search_method_no_index_exists'], ' [', $txt['search_index_create_custom'], ']'; + echo ' +
    +
    '; + + foreach ($context['search_apis'] as $api) + { + if (empty($api['label']) || $api['has_template']) + continue; + + echo ' +
    + + ', $api['label'] ,' +
    '; + + if ($api['desc']) + echo ' +
    + ', $api['desc'], ' +
    '; + } + + echo ' +
    +
    +
    + ', $txt['search_method'], ' +
    + +
    +
    + + +
    +
    + +
    +
    +
    +
    '; +} + +function template_create_index() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    +
    +

    ', $txt['search_create_index'], '

    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    + + +
    + +
    + +
    +
    +
    '; +} + +function template_create_index_progress() +{ + global $context, $settings, $options, $scripturl, $txt; + echo ' +
    +
    +
    +

    ', $txt['search_create_index'], '

    +
    +
    + +
    +

    + ', $txt['search_create_index_not_ready'], ' +

    +

    + ', $txt['search_create_index_progress'], ': ', $context['percentage'], '% +

    + +
    + +
    + + + + +
    +
    +
    + '; + +} + +function template_create_index_done() +{ + global $context, $settings, $options, $scripturl, $txt; + echo ' +
    +
    +

    ', $txt['search_create_index'], '

    +
    +
    + +
    +

    ', $txt['search_create_index_done'], '

    +

    + ', $txt['search_create_index_done_link'], ' +

    +
    + +
    +
    +
    '; +} + +// Add or edit a search engine spider. +function template_spider_edit() +{ + global $context, $settings, $options, $scripturl, $txt; + echo ' +
    +
    +
    +

    ', $context['page_title'], '

    +
    +
    + ', $txt['add_spider_desc'], ' +
    +
    + +
    +
    +
    + ', $txt['spider_name'], ':
    + ', $txt['spider_name_desc'], ' +
    +
    + +
    +
    + ', $txt['spider_agent'], ':
    + ', $txt['spider_agent_desc'], ' +
    +
    + +
    +
    + ', $txt['spider_ip_info'], ':
    + ', $txt['spider_ip_info_desc'], ' +
    +
    + +
    +
    + + +
    + +
    +
    +
    +
    '; +} + +// Show... spider... logs... +function template_show_spider_logs() +{ + global $context, $txt, $settings, $scripturl; + + echo ' +
    '; + + // Standard fields. + template_show_list('spider_logs'); + + echo ' +
    +
    +

    ', $txt['spider_logs_delete'], '

    +
    +
    +
    + +
    +

    + ', $txt['spider_logs_delete_older'], ' + + ', $txt['spider_logs_delete_day'], ' +

    + + +
    + +
    +
    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/ManageSmileys.template.php b/Themes/default/ManageSmileys.template.php new file mode 100644 index 0000000..ec7ad2e --- /dev/null +++ b/Themes/default/ManageSmileys.template.php @@ -0,0 +1,583 @@ +'; + + template_show_list('smiley_set_list'); + + echo ' +
    +
    +

    ', $txt['smiley_sets_latest'], '

    +
    +
    + +
    +
    ', $txt['smiley_sets_latest_fetch'], '
    +
    + +
    + +
    + '; + + if (empty($modSettings['disable_smf_js'])) + echo ' + '; + + echo ' + '; +} + +// Modifying a smiley set. +function template_modifyset() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    +
    +
    +

    + ', $context['current_set']['is_new'] ? $txt['smiley_set_new'] : $txt['smiley_set_modify_existing'], ' +

    +
    '; + + // If this is an existing set, and there are still un-added smileys - offer an import opportunity. + if (!empty($context['current_set']['can_import'])) + { + echo ' +
    + ', $context['current_set']['can_import'] == 1 ? $txt['smiley_set_import_single'] : $txt['smiley_set_import_multiple'], ' ', $txt['here'], ' ', $context['current_set']['can_import'] == 1 ? $txt['smiley_set_to_import_single'] : $txt['smiley_set_to_import_multiple'], ' +
    '; + } + + echo ' +
    + +
    +
    +
    + : +
    +
    + +
    +
    + : +
    +
    + ', $modSettings['smileys_url'], '/'; + if ($context['current_set']['id'] == 'default') + echo 'default'; + elseif (empty($context['smiley_set_dirs'])) + echo ' + '; + else + { + echo ' + '; + } + echo ' + /.. +
    +
    + : +
    +
    + +
    '; + + // If this is a new smiley set they have the option to import smileys already in the directory. + if ($context['current_set']['is_new'] && !empty($modSettings['smiley_enable'])) + echo ' +
    + : +
    +
    + +
    '; + + echo ' +
    + +
    + +
    + + +
    +
    +
    '; +} + +// Editing an individual smiley +function template_modifysmiley() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    +
    +
    +

    ', $txt['smiley_modify_existing'], '

    +
    +
    + +
    +
    +
    + ', $txt['smiley_preview'], ': +
    +
    + (', $txt['smiley_preview_using'], ': ) +
    +
    + : +
    +
    + +
    +
    + : +
    +
    '; + if (empty($context['filenames'])) + echo ' + '; + else + { + echo ' + '; + } + echo ' +
    +
    + : +
    +
    + +
    +
    + : +
    +
    + +
    +
    + + +
    + +
    + + +
    +
    +
    + '; +} + +// Adding a new smiley. +function template_addsmiley() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' + +
    +
    +
    +

    ', $txt['smileys_add_method'], '

    +
    +
    + +
    +
      +
    • + +
    • +
    • + +
    • +
    +
    +
    +
    +
    + +
    +
    + ', $txt['smiley_preview_using'], ': +
    +
    + : +
    +
    '; + if (empty($context['filenames'])) + echo ' + '; + else + { + echo ' + '; + } + + echo ' +
    +
    +
    + + + +
    + +
    +
    +
    +

    ', $txt['smiley_new'], '

    +
    +
    + +
    +
    +
    + : +
    +
    + +
    +
    + : +
    +
    + +
    +
    + : +
    +
    + +
    +
    + +
    + +
    + +
    +
    +
    + '; +} + +// Ordering smileys. +function template_setorder() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    '; + + foreach ($context['smileys'] as $location) + { + echo ' +
    +
    +

    ', $location['title'], '

    +
    +
    + ', $location['description'], ' +
    +
    + +
    + ', empty($context['move_smiley']) ? $txt['smileys_move_select_smiley'] : $txt['smileys_move_select_destination'], '...
    '; + foreach ($location['rows'] as $row) + { + if (!empty($context['move_smiley'])) + echo ' + ', $txt['smileys_move_here'], ''; + + foreach ($row as $smiley) + { + if (empty($context['move_smiley'])) + echo '', $smiley['description'], ''; + else + echo '', $smiley['description'], '', $txt['smileys_move_here'], ''; + } + + echo ' +
    '; + } + if (!empty($context['move_smiley'])) + echo ' + ', $txt['smileys_move_here'], ''; + echo ' +
    + +
    + +
    +
    '; + } + echo ' +
    +
    '; +} + +// Editing Message Icons +function template_editicons() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + template_show_list('message_icon_list'); +} + +// Editing an individual message icon +function template_editicon() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    +
    +
    +

    + ', $context['new_icon'] ? $txt['icons_new_icon'] : $txt['icons_edit_icon'], ' +

    +
    +
    + +
    +
    '; + if (!$context['new_icon']) + echo ' +
    + ', $txt['smiley_preview'], ': +
    +
    + ', $context['icon']['title'], ' +
    '; + echo ' +
    + :
    ', $txt['icons_filename_all_gif'], ' +
    +
    + +
    +
    + : +
    +
    + +
    +
    + : +
    +
    + +
    +
    + : +
    +
    + +
    +
    '; + + if (!$context['new_icon']) + echo ' + '; + + echo ' + + + +
    + +
    +
    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/Memberlist.template.php b/Themes/default/Memberlist.template.php new file mode 100644 index 0000000..0b5f717 --- /dev/null +++ b/Themes/default/Memberlist.template.php @@ -0,0 +1,216 @@ + array('text' => 'view_all_members', 'image' => 'mlist.gif', 'lang' => true, 'url' => $scripturl . '?action=mlist' . ';sa=all', 'active'=> true), + 'mlist_search' => array('text' => 'mlist_search', 'image' => 'mlist.gif', 'lang' => true, 'url' => $scripturl . '?action=mlist' . ';sa=search'), + ); + + echo ' +
    +
    +

    + ', $txt['members_list'], ''; + if (!isset($context['old_search'])) + echo ' + ', $context['letter_links'], ''; + echo ' +

    +
    +
    + ', template_button_strip($memberlist_buttons, 'right'), ' + +
    '; + + echo ' +
    + + + '; + + // Display each of the column headers of the table. + foreach ($context['columns'] as $column) + { + // We're not able (through the template) to sort the search results right now... + if (isset($context['old_search'])) + echo ' + '; + // This is a selected column, so underline it or some such. + elseif ($column['selected']) + echo ' + '; + // This is just some column... show the link and be done with it. + else + echo ' + '; + } + echo ' + + + '; + + // Assuming there are members loop through each one displaying their data. + if (!empty($context['members'])) + { + foreach ($context['members'] as $member) + { + echo ' + + + + '; + + if (!isset($context['disabled_fields']['website'])) + echo ' + '; + + // ICQ? + if (!isset($context['disabled_fields']['icq'])) + echo ' + '; + + // AIM? + if (!isset($context['disabled_fields']['aim'])) + echo ' + '; + + // YIM? + if (!isset($context['disabled_fields']['yim'])) + echo ' + '; + + // MSN? + if (!isset($context['disabled_fields']['msn'])) + echo ' + '; + + // Group and date. + echo ' + + '; + + if (!isset($context['disabled_fields']['posts'])) + { + echo ' + + '; + } + + echo ' + '; + } + } + // No members? + else + echo ' + + + '; + + // Show the page numbers again. (makes 'em easier to find!) + echo ' + +
    + ', $column['label'], ' + ' . $column['label'] . ' + ', $column['link'], '
    + ', $context['can_send_pm'] ? '' : '', $settings['use_image_buttons'] ? '' . $member['online']['text'] . '' : $member['online']['label'], $context['can_send_pm'] ? '' : '', ' + ', $member['link'], '', $member['show_email'] == 'no' ? '' : '' . $txt['email'] . '', '', $member['website']['url'] != '' ? '' . $member['website']['title'] . '' : '', '', $member['icq']['link'], '', $member['aim']['link'], '', $member['yim']['link'], '', $member['msn']['link'], '', empty($member['group']) ? $member['post_group'] : $member['group'], '', $member['registered_date'], '', $member['posts'], ''; + + if (!empty($member['post_percent'])) + echo ' +
    +
    +
    '; + + echo ' +
    ', $txt['search_no_results'], '
    +
    '; + + echo ' +
    + '; + + // If it is displaying the result of a search show a "search again" link to edit their criteria. + if (isset($context['old_search'])) + echo ' + '; + echo ' +
    +
    '; + +} + +// A page allowing people to search the member list. +function template_search() +{ + global $context, $settings, $options, $scripturl, $txt; + + // Build the memberlist button array. + $memberlist_buttons = array( + 'view_all_members' => array('text' => 'view_all_members', 'image' => 'mlist.gif', 'lang' => true, 'url' => $scripturl . '?action=mlist' . ';sa=all'), + 'mlist_search' => array('text' => 'mlist_search', 'image' => 'mlist.gif', 'lang' => true, 'url' => $scripturl . '?action=mlist' . ';sa=search', 'active' => true), + ); + + // Start the submission form for the search! + echo ' +
    +
    +
    +

    + ', !empty($settings['use_buttons']) ? '' : '', $txt['mlist_search'], ' +

    +
    +
    + ', template_button_strip($memberlist_buttons, 'right'), ' +
    '; + // Display the input boxes for the form. + echo ' +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/MessageIndex.template.php b/Themes/default/MessageIndex.template.php new file mode 100644 index 0000000..d7a55a6 --- /dev/null +++ b/Themes/default/MessageIndex.template.php @@ -0,0 +1,466 @@ +'; + + if (!empty($context['boards']) && (!empty($options['show_children']) || $context['start'] == 0)) + { + echo ' +
    +
    +

    ', $txt['parent_boards'], '

    +
    +
    + + '; + + foreach ($context['boards'] as $board) + { + echo ' + + + + + + '; + + // Show the "Child Boards: ". (there's a link_children but we're going to bold the new ones...) + if (!empty($board['children'])) + { + // Sort the links into an array with new boards bold so it can be imploded. + $children = array(); + /* Each child in each board's children has: + id, name, description, new (is it new?), topics (#), posts (#), href, link, and last_post. */ + foreach ($board['children'] as $child) + { + if (!$child['is_redirect']) + $child['link'] = '' . $child['name'] . ($child['new'] ? '' : '') . ''; + else + $child['link'] = '' . $child['name'] . ''; + + // Has it posts awaiting approval? + if ($child['can_approve_posts'] && ($child['unapproved_posts'] | $child['unapproved_topics'])) + $child['link'] .= ' (!)'; + + $children[] = $child['new'] ? '' . $child['link'] . '' : $child['link']; + } + echo ' + '; + } + } + echo ' + +
    + '; + + // If the board or children is new, show an indicator. + if ($board['new'] || $board['children_new']) + echo ' + ', $txt['new_posts'], ''; + // Is it a redirection board? + elseif ($board['is_redirect']) + echo ' + *'; + // No new posts at all! The agony!! + else + echo ' + ', $txt['old_posts'], ''; + + echo ' + + + ', $board['name'], ''; + + // Has it outstanding posts for approval? + if ($board['can_approve_posts'] && ($board['unapproved_posts'] || $board['unapproved_topics'])) + echo ' + (!)'; + + echo ' + +

    ', $board['description'] , '

    '; + + // Show the "Moderators: ". Each has name, href, link, and id. (but we're gonna use link_moderators.) + if (!empty($board['moderators'])) + echo ' +

    ', count($board['moderators']) === 1 ? $txt['moderator'] : $txt['moderators'], ': ', implode(', ', $board['link_moderators']), '

    '; + + // Show some basic information about the number of posts, etc. + echo ' +
    +

    ', comma_format($board['posts']), ' ', $board['is_redirect'] ? $txt['redirects'] : $txt['posts'], '
    + ', $board['is_redirect'] ? '' : comma_format($board['topics']) . ' ' . $txt['board_topics'], ' +

    +
    '; + + /* The board's and children's 'last_post's have: + time, timestamp (a number that represents the time.), id (of the post), topic (topic id.), + link, href, subject, start (where they should go for the first unread post.), + and member. (which has id, name, link, href, username in it.) */ + if (!empty($board['last_post']['id'])) + echo ' +

    ', $txt['last_post'], ' ', $txt['by'], ' ', $board['last_post']['member']['link'], '
    + ', $txt['in'], ' ', $board['last_post']['link'], '
    + ', $txt['on'], ' ', $board['last_post']['time'],' +

    '; + + echo ' +
    ', $txt['parent_boards'], ': ', implode(', ', $children), '
    +
    +
    '; + } + + if (!empty($options['show_board_desc']) && $context['description'] != '') + echo ' +

    ', $context['description'], '

    '; + + // Create the button set... + $normal_buttons = array( + 'new_topic' => array('test' => 'can_post_new', 'text' => 'new_topic', 'image' => 'new_topic.gif', 'lang' => true, 'url' => $scripturl . '?action=post;board=' . $context['current_board'] . '.0', 'active' => true), + 'post_poll' => array('test' => 'can_post_poll', 'text' => 'new_poll', 'image' => 'new_poll.gif', 'lang' => true, 'url' => $scripturl . '?action=post;board=' . $context['current_board'] . '.0;poll'), + 'notify' => array('test' => 'can_mark_notify', 'text' => $context['is_marked_notify'] ? 'unnotify' : 'notify', 'image' => ($context['is_marked_notify'] ? 'un' : ''). 'notify.gif', 'lang' => true, 'custom' => 'onclick="return confirm(\'' . ($context['is_marked_notify'] ? $txt['notification_disable_board'] : $txt['notification_enable_board']) . '\');"', 'url' => $scripturl . '?action=notifyboard;sa=' . ($context['is_marked_notify'] ? 'off' : 'on') . ';board=' . $context['current_board'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), + 'markread' => array('text' => 'mark_read_short', 'image' => 'markread.gif', 'lang' => true, 'url' => $scripturl . '?action=markasread;sa=board;board=' . $context['current_board'] . '.0;' . $context['session_var'] . '=' . $context['session_id']), + ); + + // They can only mark read if they are logged in and it's enabled! + if (!$context['user']['is_logged'] || !$settings['show_mark_read']) + unset($normal_buttons['markread']); + + // Allow adding new buttons easily. + call_integration_hook('integrate_messageindex_buttons', array(&$normal_buttons)); + + if (!$context['no_topic_listing']) + { + echo ' +
    + + ', template_button_strip($normal_buttons, 'right'), ' +
    '; + + // If Quick Moderation is enabled start the form. + if (!empty($context['can_quick_mod']) && $options['display_quick_mod'] > 0 && !empty($context['topics'])) + echo ' +
    '; + + echo ' +
    + + + '; + + // Are there actually any topics to show? + if (!empty($context['topics'])) + { + echo ' + + + '; + // Show a "select all" box for quick moderation? + if (empty($context['can_quick_mod'])) + echo ' + '; + else + echo ' + '; + + // Show a "select all" box for quick moderation? + if (!empty($context['can_quick_mod']) && $options['display_quick_mod'] == 1) + echo ' + '; + + // If it's on in "image" mode, don't show anything but the column. + elseif (!empty($context['can_quick_mod'])) + echo ' + '; + } + // No topics.... just say, "sorry bub". + else + echo ' + + + '; + + echo ' + + + '; + + if (!empty($settings['display_who_viewing'])) + { + echo ' + + + '; + } + + // If this person can approve items and we have some awaiting approval tell them. + if (!empty($context['unapproved_posts_message'])) + { + echo ' + + + '; + } + + foreach ($context['topics'] as $topic) + { + // Is this topic pending approval, or does it have any posts pending approval? + if ($context['can_approve_posts'] && $topic['unapproved_posts']) + $color_class = !$topic['approved'] ? 'approvetbg' : 'approvebg'; + // We start with locked and sticky topics. + elseif ($topic['is_sticky'] && $topic['is_locked']) + $color_class = 'stickybg locked_sticky'; + // Sticky topics should get a different color, too. + elseif ($topic['is_sticky']) + $color_class = 'stickybg'; + // Locked topics get special treatment as well. + elseif ($topic['is_locked']) + $color_class = 'lockedbg'; + // Last, but not least: regular topics. + else + $color_class = 'windowbg'; + + // Some columns require a different shade of the color class. + $alternate_class = $color_class . '2'; + + echo ' + + + + + + '; + + // Show the quick moderation options? + if (!empty($context['can_quick_mod'])) + { + echo ' + '; + } + echo ' + '; + } + + if (!empty($context['can_quick_mod']) && $options['display_quick_mod'] == 1 && !empty($context['topics'])) + { + echo ' + + + '; + } + + echo ' + +
     ', $txt['subject'], $context['sort_by'] == 'subject' ? ' ' : '', ' / ', $txt['started_by'], $context['sort_by'] == 'starter' ? ' ' : '', '', $txt['replies'], $context['sort_by'] == 'replies' ? ' ' : '', ' / ', $txt['views'], $context['sort_by'] == 'views' ? ' ' : '', '', $txt['last_post'], $context['sort_by'] == 'last_post' ? ' ' : '', '', $txt['last_post'], $context['sort_by'] == 'last_post' ? ' ' : '', '  ', $txt['msg_alert_none'], ' 
    '; + if ($settings['display_who_viewing'] == 1) + echo count($context['view_members']), ' ', count($context['view_members']) === 1 ? $txt['who_member'] : $txt['members']; + else + echo empty($context['view_members_list']) ? '0 ' . $txt['members'] : implode(', ', $context['view_members_list']) . ((empty($context['view_num_hidden']) or $context['can_moderate_forum']) ? '' : ' (+ ' . $context['view_num_hidden'] . ' ' . $txt['hidden'] . ')'); + echo $txt['who_and'], $context['view_num_guests'], ' ', $context['view_num_guests'] == 1 ? $txt['guest'] : $txt['guests'], $txt['who_viewing_board'], ' +
    + ! ', $context['unapproved_posts_message'], ' +
    + + + + +
    + ', $topic['is_sticky'] ? '' : '', '', $topic['first_post']['link'], (!$context['can_approve_posts'] && !$topic['approved'] ? ' (' . $txt['awaiting_approval'] . ')' : ''), '', $topic['is_sticky'] ? '' : ''; + + // Is this topic new? (assuming they are logged in!) + if ($topic['new'] && $context['user']['is_logged']) + echo ' + ', $txt['new'], ''; + + echo ' +

    ', $txt['started_by'], ' ', $topic['first_post']['member']['link'], ' + ', $topic['pages'], ' +

    +
    +
    + ', $topic['replies'], ' ', $txt['replies'], ' +
    + ', $topic['views'], ' ', $txt['views'], ' +
    + ', $txt['last_post'], ' + ', $topic['last_post']['time'], '
    + ', $txt['by'], ' ', $topic['last_post']['member']['link'], ' +
    '; + if ($options['display_quick_mod'] == 1) + echo ' + '; + else + { + // Check permissions on each and show only the ones they are allowed to use. + if ($topic['quick_mod']['remove']) + echo '', $txt['remove_topic'], ''; + + if ($topic['quick_mod']['lock']) + echo '', $txt['set_lock'], ''; + + if ($topic['quick_mod']['lock'] || $topic['quick_mod']['remove']) + echo '
    '; + + if ($topic['quick_mod']['sticky']) + echo '', $txt['set_sticky'], ''; + + if ($topic['quick_mod']['move']) + echo '', $txt['move_topic'], ''; + } + echo ' +
    + '; + + // Show a list of boards they can move the topic to. + if ($context['can_move']) + { + echo ' + '; + } + + echo ' + +
    +
    + '; + + // Finish off the form - again. + if (!empty($context['can_quick_mod']) && $options['display_quick_mod'] > 0 && !empty($context['topics'])) + echo ' + +
    '; + + echo ' +
    + ', template_button_strip($normal_buttons, 'right'), ' + +
    '; + } + + // Show breadcrumbs at the bottom too. + theme_linktree(); + + echo ' +
    +
    +

     

    '; + + if (!$context['no_topic_listing']) + echo ' +

    ', !empty($modSettings['enableParticipation']) && $context['user']['is_logged'] ? ' + ' . $txt['participation_caption'] . '
    ' : '', ' + ' . $txt['normal_topic'] . '
    + ' . sprintf($txt['hot_topics'], $modSettings['hotTopicPosts']) . '
    + ' . sprintf($txt['very_hot_topics'], $modSettings['hotTopicVeryPosts']) . ' +

    +

    + ' . $txt['locked_topic'] . '
    ' . ($modSettings['enableStickyTopics'] == '1' ? ' + ' . $txt['sticky_topic'] . '
    ' : '') . ($modSettings['pollMode'] == '1' ? ' + ' . $txt['poll'] : '') . ' +

    '; + + echo ' + +
    +
    +
    '; + + // Javascript for inline editing. + echo ' + +'; +} + +?> \ No newline at end of file diff --git a/Themes/default/ModerationCenter.template.php b/Themes/default/ModerationCenter.template.php new file mode 100644 index 0000000..28d13d9 --- /dev/null +++ b/Themes/default/ModerationCenter.template.php @@ -0,0 +1,860 @@ + +
    +

    ', $txt['moderation_center'], '

    +
    +
    + ', $txt['hello_guest'], ' ', $context['user']['name'], '! +

    + ', $txt['mc_description'], ' +

    + +
    '; + + $alternate = true; + // Show all the blocks they want to see. + foreach ($context['mod_blocks'] as $block) + { + $block_function = 'template_' . $block; + + echo ' +
    ', function_exists($block_function) ? $block_function() : '', '
    '; + + if (!$alternate) + echo ' +
    '; + + $alternate = !$alternate; + } + + echo ' + +
    '; +} + +function template_latest_news() +{ + global $settings, $options, $context, $txt, $scripturl; + + echo ' +
    +

    + ', $txt['help'], ' ', $txt['mc_latest_news'], ' +

    +
    +
    + +
    +
    ', $txt['mc_cannot_connect_sm'], '
    +
    + +
    '; + + // This requires a lot of javascript... + //!!! Put this in it's own file!! + echo ' + + + + '; + +} + +// Show all the group requests the user can see. +function template_group_requests_block() +{ + global $settings, $options, $context, $txt, $scripturl; + + echo ' + +
    + +
    +
      '; + + foreach ($context['group_requests'] as $request) + echo ' +
    • + ', $request['group']['name'], ' ', $txt['mc_groupr_by'], ' ', $request['member']['link'], ' +
    • '; + + // Don't have any watched users right now? + if (empty($context['group_requests'])) + echo ' +
    • + ', $txt['mc_group_requests_none'], ' +
    • '; + + echo ' +
    +
    + +
    '; +} + +// A block to show the current top reported posts. +function template_reported_posts_block() +{ + global $settings, $options, $context, $txt, $scripturl; + + echo ' + +
    + +
    +
      '; + + foreach ($context['reported_posts'] as $report) + echo ' +
    • + ', $report['subject'], ' ', $txt['mc_reportedp_by'], ' ', $report['author']['link'], ' +
    • '; + + // Don't have any watched users right now? + if (empty($context['reported_posts'])) + echo ' +
    • + ', $txt['mc_recent_reports_none'], ' +
    • '; + + echo ' +
    +
    + +
    '; +} + +function template_watched_users() +{ + global $settings, $options, $context, $txt, $scripturl; + + echo ' + +
    + +
    +
      '; + + foreach ($context['watched_users'] as $user) + echo ' +
    • + ', sprintf(!empty($user['last_login']) ? $txt['mc_seen'] : $txt['mc_seen_never'], $user['link'], $user['last_login']), ' +
    • '; + + // Don't have any watched users right now? + if (empty($context['watched_users'])) + echo ' +
    • + ', $txt['mc_watched_users_none'], ' +
    • '; + + echo ' +
    +
    + +
    '; +} + +// Little section for making... notes. +function template_notes() +{ + global $settings, $options, $context, $txt, $scripturl; + + echo ' +
    +
    +

    ', $txt['mc_notes'], '

    +
    +
    + +
    '; + + if (!empty($context['notes'])) + { + echo ' +
      '; + + // Cycle through the notes. + foreach ($context['notes'] as $note) + echo ' +
    • ', $note['author']['link'], ': ', $note['text'], '
    • '; + + echo ' +
    +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    '; + } + + echo ' +
    + +
    +
    + +
    +
    +
    + +
    + +
    '; +} + +function template_reported_posts() +{ + global $settings, $options, $context, $txt, $scripturl; + + echo ' +
    +
    +

    + ', $context['view_closed'] ? $txt['mc_reportedp_closed'] : $txt['mc_reportedp_active'], ' +

    +
    +
    +
    ', $txt['pages'], ': ', $context['page_index'], '
    +
    '; + + // Make the buttons. + $close_button = create_button('close.gif', $context['view_closed'] ? 'mc_reportedp_open' : 'mc_reportedp_close', $context['view_closed'] ? 'mc_reportedp_open' : 'mc_reportedp_close', 'align="middle"'); + $details_button = create_button('details.gif', 'mc_reportedp_details', 'mc_reportedp_details', 'align="middle"'); + $ignore_button = create_button('ignore.gif', 'mc_reportedp_ignore', 'mc_reportedp_ignore', 'align="middle"'); + $unignore_button = create_button('ignore.gif', 'mc_reportedp_unignore', 'mc_reportedp_unignore', 'align="middle"'); + + foreach ($context['reports'] as $report) + { + echo ' +
    + +
    +
    +
    + ', $report['subject'], ' ', $txt['mc_reportedp_by'], ' ', $report['author']['link'], ' +
    + +

    +
    + « ', $txt['mc_reportedp_last_reported'], ': ', $report['last_updated'], ' »
    '; + + // Prepare the comments... + $comments = array(); + foreach ($report['comments'] as $comment) + $comments[$comment['member']['id']] = $comment['member']['link']; + + echo ' + « ', $txt['mc_reportedp_reported_by'], ': ', implode(', ', $comments), ' » +
    +
    + ', $report['body'], ' +
    + +
    '; + } + + // Were none found? + if (empty($context['reports'])) + echo ' +
    + +
    +

    ', $txt['mc_reportedp_none_found'], '

    +
    + +
    '; + + echo ' +
    +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    +
    + ', !$context['view_closed'] ? '' : '', ' +
    +
    + +
    +
    '; +} + +// Show a list of all the unapproved posts +function template_unapproved_posts() +{ + global $settings, $options, $context, $txt, $scripturl; + + // Just a big table of it all really... + echo ' +
    +
    +
    +

    ', $txt['mc_unapproved_posts'], '

    +
    '; + + // Make up some buttons + $approve_button = create_button('approve.gif', 'approve', 'approve', 'align="middle"'); + $remove_button = create_button('delete.gif', 'remove_message', 'remove', 'align="middle"'); + + // No posts? + if (empty($context['unapproved_items'])) + echo ' +
    + +
    +

    ', $txt['mc_unapproved_' . $context['current_view'] . '_none_found'], '

    +
    + +
    '; + else + echo ' +
    + +
    '; + + foreach ($context['unapproved_items'] as $item) + { + echo ' +
    +

    + ', $item['counter'], '  + ', $item['category']['name'], ' / ', $item['board']['name'], ' / ', $item['subject'], ' + ', $txt['mc_unapproved_by'], ' ', $item['poster']['link'], ' ', $txt['on'], ': ', $item['time'], ' +

    +
    +
    + +
    +
    ', $item['body'], '
    + + ', $approve_button, ''; + + if ($item['can_delete']) + echo ' + ', $context['menu_separator'], ' + ', $remove_button, ''; + + echo ' + '; + + echo ' + +
    +
    + +
    '; + } + + echo ' +
    +
    + + +
    '; + + if (!empty($context['unapproved_items'])) + echo ' +
    + +
    '; + + echo ' +
    + +
    +
    +
    '; +} + +// List all attachments awaiting approval. +function template_unapproved_attachments() +{ + global $settings, $options, $context, $txt, $scripturl; + + // Show all the attachments still oustanding. + echo ' +
    +
    +
    +

    ', $txt['mc_unapproved_attachments'], '

    +
    '; + + // The ever popular approve button, with the massively unpopular delete. + $approve_button = create_button('approve.gif', 'approve', 'approve', 'align="middle"'); + $remove_button = create_button('delete.gif', 'remove_message', 'remove', 'align="middle"'); + + // None awaiting? + if (empty($context['unapproved_items'])) + echo ' +
    + +
    +

    ', $txt['mc_unapproved_attachments_none_found'], '

    +
    + +
    '; + else + echo ' +
    + +
    + + + + + + + + + + + '; + + foreach ($context['unapproved_items'] as $item) + { + echo ' + + + + + + + '; + } + + if (!empty($context['unapproved_items'])) + echo ' + +
    ', $txt['mc_unapproved_attach_name'], '', $txt['mc_unapproved_attach_size'], '', $txt['mc_unapproved_attach_poster'], '', $txt['date'], '
    + ', $item['filename'], ' + + ', $item['size'], $txt['kilobyte'], ' + + ', $item['poster']['link'], ' + + ', $item['time'], '
    ', $txt['in'], ' ', $item['message']['subject'], ' +
    + +
    '; + + echo ' +
    +
    + + +
    '; + + if (!empty($context['unapproved_items'])) + echo ' +
    + +
    '; + + echo ' +
    + +
    +
    +
    '; +} + +function template_viewmodreport() +{ + global $context, $scripturl, $txt; + + echo ' +
    +
    +
    +

    + ', sprintf($txt['mc_viewmodreport'], $context['report']['message_link'], $context['report']['author']['link']), ' +

    +
    +
    +

    + + ', sprintf($txt['mc_modreport_summary'], $context['report']['num_reports'], $context['report']['last_updated']), ' + + '; + + // Make the buttons. + $close_button = create_button('close.gif', $context['report']['closed'] ? 'mc_reportedp_open' : 'mc_reportedp_close', $context['report']['closed'] ? 'mc_reportedp_open' : 'mc_reportedp_close', 'align="middle"'); + $ignore_button = create_button('ignore.gif', 'mc_reportedp_ignore', 'mc_reportedp_ignore', 'align="middle"'); + $unignore_button = create_button('ignore.gif', 'mc_reportedp_unignore', 'mc_reportedp_unignore', 'align="middle"'); + + echo ' + ', $context['report']['ignore'] ? $unignore_button : $ignore_button, ' + ', $close_button, ' + +

    +
    +
    + +
    + ', $context['report']['body'], ' +
    + +
    +
    +
    +

    ', $txt['mc_modreport_whoreported_title'], '

    +
    '; + + foreach ($context['report']['comments'] as $comment) + echo ' +
    + +
    +

    ', sprintf($txt['mc_modreport_whoreported_data'], $comment['member']['link'] . (empty($comment['member']['id']) && !empty($comment['member']['ip']) ? ' (' . $comment['member']['ip'] . ')' : ''), $comment['time']), '

    +

    ', $comment['message'], '

    +
    + +
    '; + + echo ' +
    +
    +

    ', $txt['mc_modreport_mod_comments'], '

    +
    +
    + +
    '; + + if (empty($context['report']['mod_comments'])) + echo ' +

    ', $txt['mc_modreport_no_mod_comment'], '

    '; + + foreach ($context['report']['mod_comments'] as $comment) + echo + '

    ', $comment['member']['link'], ': ', $comment['message'], ' (', $comment['time'], ')

    '; + + echo ' + +
    + +
    +
    + +
    +
    '; + + $alt = false; + + template_show_list('moderation_actions_list'); + + if (!empty($context['entries'])) + { + echo ' +
    +

    ', $txt['mc_modreport_modactions'], '

    +
    + + + + + + + + + + + '; + + foreach ($context['entries'] as $entry) + { + echo ' + + + + + + + + + + '; + } + echo ' + +
    ', $txt['modlog_action'], '', $txt['modlog_date'], '', $txt['modlog_member'], '', $txt['modlog_position'], '', $txt['modlog_ip'], '
    ', $entry['action'], '', $entry['time'], '', $entry['moderator']['link'], '', $entry['position'], '', $entry['ip'], '
    '; + + foreach ($entry['extra'] as $key => $value) + echo ' + ', $key, ': ', $value; + echo ' +
    '; + } + + echo ' + +
    +
    +
    '; +} + +// Callback function for showing a watched users post in the table. +function template_user_watch_post_callback($post) +{ + global $scripturl, $context, $txt, $delete_button; + + // We'll have a delete please bob. + if (empty($delete_button)) + $delete_button = create_button('delete.gif', 'remove_message', 'remove', 'align="middle"'); + + $output_html = ' +
    +
    + ' . $post['subject'] . ' ' . $txt['mc_reportedp_by'] . ' ' . $post['author_link'] . ' +
    +
    '; + + if ($post['can_delete']) + $output_html .= ' + ' . $delete_button . ' + '; + + $output_html .= ' +
    +

    +
    + « ' . $txt['mc_watched_users_posted'] . ': ' . $post['poster_time'] . ' » +
    +
    + ' . $post['body']; + + return $output_html; +} + +// Moderation settings +function template_moderation_settings() +{ + global $settings, $options, $context, $txt, $scripturl; + + echo ' +
    +
    +
    +

    ', $txt['mc_prefs_title'], '

    +
    +
    + ', $txt['mc_prefs_desc'], ' +
    +
    + +
    +
    +
    + ', $txt['mc_prefs_homepage'], ': +
    +
    '; + + foreach ($context['homepage_blocks'] as $k => $v) + echo ' +
    '; + + echo ' +
    '; + + // If they can moderate boards they have more options! + if ($context['can_moderate_boards']) + { + echo ' +
    + : +
    +
    + +
    +
    + : +
    +
    + +
    '; + + } + + if ($context['can_moderate_approvals']) + { + echo ' + +
    + : +
    +
    + +
    '; + } + + echo ' +
    +
    + + +
    +
    + +
    +
    +
    +
    '; +} + +// Show a notice sent to a user. +function template_show_notice() +{ + global $txt, $settings, $options, $context; + + // We do all the HTML for this one! + echo ' + + + + ', $context['page_title'], ' + + + +
    +

    ', $txt['show_notice'], '

    +
    +
    +

    ', $txt['show_notice_subject'], ': ', $context['notice_subject'], '

    +
    +
    + +
    +
    +
    + ', $txt['show_notice_text'], ': +
    +
    + ', $context['notice_body'], ' +
    +
    +
    + +
    + +'; + +} + +// Add or edit a warning template. +function template_warn_template() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +
    +

    ', $context['page_title'], '

    +
    +
    + ', $txt['mc_warning_template_desc'], ' +
    +
    + +
    +
    +
    + : +
    +
    + +
    +
    + :
    + ', $txt['mc_warning_template_body_desc'], ' +
    +
    + +
    +
    '; + + if ($context['template_data']['can_edit_personal']) + echo ' + + +
    + ', $txt['mc_warning_template_personal_desc'], ' +
    '; + + echo ' + +
    + +
    + +
    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/MoveTopic.template.php b/Themes/default/MoveTopic.template.php new file mode 100644 index 0000000..6603912 --- /dev/null +++ b/Themes/default/MoveTopic.template.php @@ -0,0 +1,92 @@ + +
    +
    +

    ', $txt['move_topic'], '

    +
    +
    + +
    +
    +
    +
    + ', $txt['move_to'], ': +
    +
    + +
    '; + + // Disable the reason textarea when the postRedirect checkbox is unchecked... + echo ' +
    +
    + + +
    +
    +
    + ', $txt['moved_why'], ' +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + +
    '; + + if ($context['back_to_topic']) + echo ' + '; + + echo ' + + +
    + '; +} + +?> \ No newline at end of file diff --git a/Themes/default/Notify.template.php b/Themes/default/Notify.template.php new file mode 100644 index 0000000..b397355 --- /dev/null +++ b/Themes/default/Notify.template.php @@ -0,0 +1,53 @@ + +

    + ', $txt['notify'], ' +

    + + +
    +

    ', $context['notification_set'] ? $txt['notify_deactivate'] : $txt['notify_request'], '

    +

    + ', $txt['yes'], ' - ', $txt['no'], ' +

    +
    + '; +} + +function template_notify_board() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +

    + ', $txt['notify'], ' +

    +
    + +
    +

    ', $context['notification_set'] ? $txt['notifyboard_turnoff'] : $txt['notifyboard_turnon'], '

    +

    + ', $txt['yes'], ' - ', $txt['no'], ' +

    +
    + '; +} + +?> \ No newline at end of file diff --git a/Themes/default/Packages.template.php b/Themes/default/Packages.template.php new file mode 100644 index 0000000..ea6ebd7 --- /dev/null +++ b/Themes/default/Packages.template.php @@ -0,0 +1,2142 @@ + +
    +

    ', $txt[($context['uninstalling'] ? 'un' : '') . 'install_mod'], '

    +
    +
    '; + + if ($context['is_installed']) + echo ' + ', $txt['package_installed_warning1'], '
    +
    + ', $txt['package_installed_warning2'], '
    +
    '; + + echo $txt['package_installed_warning3'], ' +
    '; + + // Do errors exist in the install? If so light them up like a christmas tree. + if ($context['has_failure']) + { + echo ' +
    + ', $txt['package_will_fail_title'], '
    + ', $txt['package_will_fail_warning'], ' +
    '; + } + + if (isset($context['package_readme'])) + { + echo ' +
    +

    ', $txt['package_' . ($context['uninstalling'] ? 'un' : '') . 'install_readme'], '

    +
    +
    + +
    + ', $context['package_readme'], ' + ', $txt['package_available_readme_language'], ' + + +
    + +
    +
    '; + } + + echo ' +
    +
    +

    + ', $context['uninstalling'] ? $txt['package_uninstall_actions'] : $txt['package_install_actions'], ' "', $context['package_name'], '" +

    +
    '; + + // Are there data changes to be removed? + if ($context['uninstalling'] && !empty($context['database_changes'])) + { + echo ' +
    + +
    + [', $txt['package_db_uninstall_details'], '] +
    + ', $txt['package_db_uninstall_actions'], ': +
      '; + + foreach ($context['database_changes'] as $change) + echo ' +
    • ', $change, '
    • '; + echo ' +
    +
    +
    + +
    '; + } + + echo ' +
    '; + + if (empty($context['actions']) && empty($context['database_changes'])) + echo ' + ', $txt['corrupt_compatible'], ' +
    '; + else + { + echo ' + ', $txt['perform_actions'], ' + + + + + + + + + + + + '; + + $alternate = true; + $i = 1; + $action_num = 1; + $js_operations = array(); + foreach ($context['actions'] as $packageaction) + { + // Did we pass or fail? Need to now for later on. + $js_operations[$action_num] = isset($packageaction['failed']) ? $packageaction['failed'] : 0; + + echo ' + + + + + + + '; + + // Is there water on the knee? Operation! + if (isset($packageaction['operations'])) + { + echo ' + + + '; + + // Increase it. + $action_num++; + } + $alternate = !$alternate; + } + echo ' + +
    ', $txt['package_install_type'], '', $txt['package_install_action'], '', $txt['package_install_desc'], '
    ', isset($packageaction['operations']) ? '' : '', '', $i++, '.', $packageaction['type'], '', $packageaction['action'], '', $packageaction['description'], '
    + '; + + // Show the operations. + $alternate2 = true; + $operation_num = 1; + foreach ($packageaction['operations'] as $operation) + { + // Determine the position text. + $operation_text = $operation['position'] == 'replace' ? 'operation_replace' : ($operation['position'] == 'before' ? 'operation_after' : 'operation_before'); + + echo ' + + + + + + + + '; + + $operation_num++; + $alternate2 = !$alternate2; + } + + echo ' +
    ', $operation_num, '.', $txt[$operation_text], '', $operation['action'], '', $operation['description'], !empty($operation['ignore_failure']) ? ' (' . $txt['operation_ignore'] . ')' : '', '
    +
    + '; + + // What if we have custom themes we can install into? List them too! + if (!empty($context['theme_actions'])) + { + echo ' +
    +
    +

    + ', $context['uninstalling'] ? $txt['package_other_themes_uninstall'] : $txt['package_other_themes'], ' +

    +
    +
    +
    + ', $txt['package_other_themes_desc'], ' +
    + '; + + // Loop through each theme and display it's name, and then it's details. + foreach ($context['theme_actions'] as $id => $theme) + { + // Pass? + $js_operations[$action_num] = !empty($theme['has_failure']); + + echo ' + + + + + '; + + foreach ($theme['actions'] as $action) + { + echo ' + + + + + + + '; + + // Is there water on the knee? Operation! + if (isset($action['operations'])) + { + echo ' + + + '; + + // Increase it. + $action_num++; + } + } + + $alternate = !$alternate; + } + + echo ' +
    '; + if (!empty($context['themes_locked'])) + echo ' + '; + echo ' + + + ', $theme['name'], ' +
    ', isset($packageaction['operations']) ? '' : '', ' + + ', $action['type'], '', $action['action'], '', $action['description'], '
    + '; + + $alternate2 = true; + $operation_num = 1; + foreach ($action['operations'] as $operation) + { + // Determine the possition text. + $operation_text = $operation['position'] == 'replace' ? 'operation_replace' : ($operation['position'] == 'before' ? 'operation_after' : 'operation_before'); + + echo ' + + + + + + + + '; + $operation_num++; + $alternate2 = !$alternate2; + } + + echo ' +
    ', $operation_num, '.', $txt[$operation_text], '', $operation['action'], '', $operation['description'], !empty($operation['ignore_failure']) ? ' (' . $txt['operation_ignore'] . ')' : '', '
    +
    +
    '; + } + } + + // Are we effectively ready to install? + if (!$context['ftp_needed'] && (!empty($context['actions']) || !empty($context['database_changes']))) + { + echo ' +
    + +
    '; + } + // If we need ftp information then demand it! + elseif ($context['ftp_needed']) + { + echo ' +
    +

    ', $txt['package_ftp_necessary'], '

    +
    +
    + ', template_control_chmod(), ' +
    '; + } + echo ' + + ', (isset($context['form_sequence_number']) && !$context['ftp_needed']) ? ' + ' : '', ' +
    + +
    '; + + // Toggle options. + echo ' + '; + + // And a bit more for database changes. + if (!empty($context['database_changes'])) + echo ' + '; +} +function template_extract_package() +{ + global $context, $settings, $options, $txt, $scripturl; + + if (!empty($context['redirect_url'])) + { + echo ' + '; + } + + echo ' +
    '; + + if (empty($context['redirect_url'])) + { + echo ' +
    +

    ', $context['uninstalling'] ? $txt['uninstall'] : $txt['extracting'], '

    +
    +
    ', $txt['package_installed_extract'], '
    '; + } + else + echo ' +
    +

    ', $txt['package_installed_redirecting'], '

    +
    '; + + echo ' +
    + +
    '; + + // If we are going to redirect we have a slightly different agenda. + if (!empty($context['redirect_url'])) + { + echo ' + ', $context['redirect_text'], '

    + ', $txt['package_installed_redirect_go_now'], ' | ', $txt['package_installed_redirect_cancel'], ''; + } + elseif ($context['uninstalling']) + echo ' + ', $txt['package_uninstall_done']; + elseif ($context['install_finished']) + { + if ($context['extract_type'] == 'avatar') + echo ' + ', $txt['avatars_extracted']; + elseif ($context['extract_type'] == 'language') + echo ' + ', $txt['language_extracted']; + else + echo ' + ', $txt['package_installed_done']; + } + else + echo ' + ', $txt['corrupt_compatible']; + + echo ' +
    + +
    '; + + // Show the "restore permissions" screen? + if (function_exists('template_show_list') && !empty($context['restore_file_permissions']['rows'])) + { + echo '
    '; + template_show_list('restore_file_permissions'); + } + + echo ' +
    +
    '; +} + +function template_list() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +

    ', $txt['list_file'], '

    +
    +
    +

    ', $txt['files_archive'], ' ', $context['filename'], ':

    +
    +
    + +
    +
      '; + + foreach ($context['files'] as $fileinfo) + echo ' +
    1. ', $fileinfo['filename'], ' (', $fileinfo['size'], ' ', $txt['package_bytes'], ')
    2. '; + + echo ' +
    +
    + [ ', $txt['back'], ' ] +
    + +
    +
    +
    '; +} + +function template_examine() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +

    ', $txt['package_examine_file'], '

    +
    +
    +

    ', $txt['package_file_contents'], ' ', $context['filename'], ':

    +
    +
    + +
    +
    ', $context['filedata'], '
    + [ ', $txt['list_files'], ' ] +
    + +
    +
    +
    '; +} + +function template_view_installed() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +

    ' . $txt['view_and_remove'] . '

    +
    '; + + if (empty($context['installed_mods'])) + { + echo ' +
    + ', $txt['no_mods_installed'], ' +
    '; + } + else + { + echo ' + + + + + + + + + + '; + + $alt = false; + foreach ($context['installed_mods'] as $i => $file) + { + echo ' + + + + + + '; + $alt = !$alt; + } + + echo ' + +
    ', $txt['mod_name'], '', $txt['mod_version'], '
    ', ++$i, '.', $file['name'], '', $file['version'], '[ ', $txt['uninstall'], ' ]
    +
    + [ ', $txt['delete_list'], ' ]'; + } + + echo ' +
    +
    '; +} + +function template_browse() +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings, $forum_version; + + echo ' +
    +
    +

    + ', $txt['help'], ' ', $txt['packages_latest'], ' +

    +
    +
    + +
    +
    ', $txt['packages_latest_fetch'], '
    +
    + +
    + + '; + + if (empty($modSettings['disable_smf_js'])) + echo ' + '; + + echo ' + '; + + echo ' + '; + + echo ' +
    +
    +

    ', $txt['browse_packages'], '

    +
    '; + + if (!empty($context['available_mods'])) + { + echo ' +
    +
    +

    ', $txt['modification_package'], '

    +
    + + + + + + + + + + + '; + + $alt = false; + foreach ($context['available_mods'] as $i => $package) + { + echo ' + + + + + + '; + $alt = !$alt; + } + + echo ' + +
    ', $txt['mod_name'], '', $txt['mod_version'], '
    ', ++$i, '.', $package['name'], ' + ', $package['version']; + + if ($package['is_installed'] && !$package['is_newer']) + echo ' + '; + + echo ' + '; + + if ($package['can_uninstall']) + echo ' + [ ', $txt['uninstall'], ' ]'; + elseif ($package['can_upgrade']) + echo ' + [ ', $txt['package_upgrade'], ' ]'; + elseif ($package['can_install']) + echo ' + [ ', $txt['install_mod'], ' ]'; + + echo ' + [ ', $txt['list_files'], ' ] + [ ', $txt['package_delete'], ' ] +
    '; + } + + if (!empty($context['available_avatars'])) + { + echo ' +
    +
    +

    ', $txt['avatar_package'], '

    +
    + + + + + + + + + + '; + + foreach ($context['available_avatars'] as $i => $package) + { + echo ' + + + + + + '; + } + + echo ' + +
    ', $txt['mod_name'], '', $txt['mod_version'], '
    ', ++$i, '.', $package['name'], '', $package['version']; + + if ($package['is_installed'] && !$package['is_newer']) + echo ' + '; + + echo ' + '; + + if ($package['can_uninstall']) + echo ' + [ ', $txt['uninstall'], ' ]'; + elseif ($package['can_upgrade']) + echo ' + [ ', $txt['package_upgrade'], ' ]'; + elseif ($package['can_install']) + echo ' + [ ', $txt['install_mod'], ' ]'; + + echo ' + [ ', $txt['list_files'], ' ] + [ ', $txt['package_delete'], ' ] +
    '; + } + + if (!empty($context['available_languages'])) + { + echo ' +
    +
    +

    ' . $txt['language_package'] . '

    +
    + + + + + + + + + + '; + + foreach ($context['available_languages'] as $i => $package) + { + echo ' + + + + + + '; + } + + echo ' + +
    ', $txt['mod_name'], '', $txt['mod_version'], '
    ' . ++$i . '.' . $package['name'] . '' . $package['version']; + + if ($package['is_installed'] && !$package['is_newer']) + echo ' + '; + + echo ' + '; + + if ($package['can_uninstall']) + echo ' + [ ', $txt['uninstall'], ' ]'; + elseif ($package['can_upgrade']) + echo ' + [ ', $txt['package_upgrade'], ' ]'; + elseif ($package['can_install']) + echo ' + [ ', $txt['install_mod'], ' ]'; + + echo ' + [ ', $txt['list_files'], ' ] + [ ', $txt['package_delete'], ' ] +
    '; + } + + if (!empty($context['available_other'])) + { + echo ' +
    +
    +

    ' . $txt['unknown_package'] . '

    +
    + + + + + + + + + + '; + + foreach ($context['available_other'] as $i => $package) + { + echo ' + + + + + + '; + } + + echo ' + +
    ', $txt['mod_name'], '', $txt['mod_version'], '
    ' . ++$i . '.' . $package['name'] . '' . $package['version']; + + if ($package['is_installed'] && !$package['is_newer']) + echo ' + '; + + echo ' + '; + + if ($package['can_uninstall']) + echo ' + [ ', $txt['uninstall'], ' ]'; + elseif ($package['can_upgrade']) + echo ' + [ ', $txt['package_upgrade'], ' ]'; + elseif ($package['can_install']) + echo ' + [ ', $txt['install_mod'], ' ]'; + + echo ' + [ ', $txt['list_files'], ' ] + [ ', $txt['package_delete'], ' ] +
    '; + } + + if (empty($context['available_mods']) && empty($context['available_avatars']) && empty($context['available_languages']) && empty($context['available_other'])) + echo ' +
    ', $txt['no_packages'], '
    '; + + echo ' +
    +
    + ', $txt['package_installed_key'], ' + ', $txt['package_installed_current'], ' + ', $txt['package_installed_old'], ' +
    + +
    +
    + + + + +
    +
    +
    '; +} + +function template_servers() +{ + global $context, $settings, $options, $txt, $scripturl; + + if (!empty($context['package_ftp']['error'])) + echo ' +
    + ', $context['package_ftp']['error'], ' +
    '; + + echo ' +
    +
    +

    ', $txt['download_new_package'], '

    +
    '; + + if ($context['package_download_broken']) + { + echo ' +
    +

    ', $txt['package_ftp_necessary'], '

    +
    +
    + +
    +

    + ', $txt['package_ftp_why_download'], ' +

    +
    +
    +
    + +
    +
    + + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    '; + } + + echo ' +
    + +
    +
    + ' . $txt['package_servers'] . ' + +
    +
    + ' . $txt['add_server'] . ' +
    +
    +
    + ' . $txt['server_name'] . ': +
    +
    + +
    +
    + ' . $txt['serverurl'] . ': +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + ', $txt['package_download_by_url'], ' +
    +
    +
    + ' . $txt['serverurl'] . ': +
    +
    + +
    +
    + ', $txt['package_download_filename'], ': +
    +
    +
    + ', $txt['package_download_filename_info'], ' +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +

    ' . $txt['package_upload_title'] . '

    +
    +
    + +
    +
    +
    +
    + ' . $txt['package_upload_select'] . ': +
    +
    + +
    +
    +
    + + +
    +
    +
    + +
    +
    +
    '; +} + +function template_package_confirm() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +

    ', $context['page_title'], '

    +
    + +
    +
    '; +} + +function template_package_list() +{ + global $context, $settings, $options, $txt, $scripturl, $smcFunc; + + echo ' +
    +
    +

    ' . $context['page_title'] . '

    +
    +
    + +
    '; + + // No packages, as yet. + if (empty($context['package_list'])) + echo ' +
      +
    • ', $txt['no_packages'], '
    • +
    '; + // List out the packages... + else + { + echo ' +
      '; + foreach ($context['package_list'] as $i => $packageSection) + { + echo ' +
    • + ', $packageSection['title'], ''; + + if (!empty($packageSection['text'])) + echo ' +
      ', $packageSection['text'], '
      '; + + echo ' + <', $context['list_type'], ' id="package_section_', $i, '" class="packages">'; + + $alt = false; + + foreach ($packageSection['items'] as $id => $package) + { + echo ' +
    • '; + // Textual message. Could be empty just for a blank line... + if ($package['is_text']) + echo ' + ', empty($package['name']) ? ' ' : $package['name']; + // This is supposed to be a rule.. + elseif ($package['is_line']) + echo ' +
      '; + // A remote link. + elseif ($package['is_remote']) + { + echo ' + ', $package['link'], ''; + } + // A title? + elseif ($package['is_heading'] || $package['is_title']) + { + echo ' + ', $package['name'], ''; + } + // Otherwise, it's a package. + else + { + // 1. Some mod [ Download ]. + echo ' + ', $package['can_install'] ? '' . $package['name'] . ' [ ' . $txt['download'] . ' ]': $package['name']; + + // Mark as installed and current? + if ($package['is_installed'] && !$package['is_newer']) + echo '', $package['is_current'] ? $txt['package_installed_current'] : $txt['package_installed_old'], ''; + + echo ' + +
        '; + + // Show the mod type? + if ($package['type'] != '') + echo ' +
      • ', $txt['package_type'], ':  ', $smcFunc['ucwords']($smcFunc['strtolower']($package['type'])), '
      • '; + // Show the version number? + if ($package['version'] != '') + echo ' +
      • ', $txt['mod_version'], ':  ', $package['version'], '
      • '; + // How 'bout the author? + if (!empty($package['author']) && $package['author']['name'] != '' && isset($package['author']['link'])) + echo ' +
      • ', $txt['mod_author'], ':  ', $package['author']['link'], '
      • '; + // The homepage.... + if ($package['author']['website']['link'] != '') + echo ' +
      • ', $txt['author_website'], ':  ', $package['author']['website']['link'], '
      • '; + + // Desciption: bleh bleh! + // Location of file: http://someplace/. + echo ' +
      • ', $txt['file_location'], ':  ', $package['href'], '
      • +
      • ', $txt['package_description'], ':  ', $package['description'], '
      • +
      '; + } + $alt = !$alt; + echo ' +
    • '; + } + echo ' + + '; + } + echo ' +
    '; + + } + + echo ' +
    + +
    +
    + ', $txt['package_installed_key'], ' + ', $txt['package_installed_current'], ' + ', $txt['package_installed_old'], ' +
    +
    +
    + + '; + // Now go through and turn off all the sections. + if (!empty($context['package_list'])) + { + $section_count = count($context['package_list']); + echo ' + '; + } +} + +function template_downloaded() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +

    ', $context['page_title'], '

    +
    +
    + +
    +

    ', (empty($context['package_server']) ? $txt['package_uploaded_successfully'] : $txt['package_downloaded_successfully']), '

    +
      +
    • ', $context['package']['name'], ' + ', $context['package']['list_files']['link'], ' + ', $context['package']['install']['link'], ' +
    • +
    +

    +

    [ ', $txt['back'], ' ]

    +
    + +
    +
    +
    '; +} + +function template_install_options() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +

    ', $txt['package_install_options'], '

    +
    +
    + ', $txt['package_install_options_ftp_why'], ' +
    + +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +

    +
    + + +
    +
    +
    + +
    +
    +
    '; +} + +function template_control_chmod() +{ + global $context, $settings, $options, $txt, $scripturl; + + // Nothing to do? Brilliant! + if (empty($context['package_ftp'])) + return false; + + if (empty($context['package_ftp']['form_elements_only'])) + { + echo ' + ', sprintf($txt['package_ftp_why'], 'document.getElementById(\'need_writable_list\').style.display = \'\'; return false;'), '
    +
    + ', $txt['package_ftp_why_file_list'], ' +
      '; + if (!empty($context['notwritable_files'])) + foreach ($context['notwritable_files'] as $file) + echo ' +
    • ', $file, '
    • '; + + echo ' +
    +
    '; + } + + echo ' +
    + ', !empty($context['package_ftp']['error']) ? $context['package_ftp']['error'] : '', ' +
    '; + + if (!empty($context['package_ftp']['destination'])) + echo ' +
    '; + + echo ' +
    +
    +
    + +
    +
    + + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    '; + + if (empty($context['package_ftp']['form_elements_only'])) + echo ' + +
    + + +
    '; + + if (!empty($context['package_ftp']['destination'])) + echo ' + +
    '; + + // Hide the details of the list. + if (empty($context['package_ftp']['form_elements_only'])) + echo ' + '; + + // Quick generate the test button. + echo ' + '; + + // Make sure the button gets generated last. + $context['insert_after_template'] .= ' + '; +} + +function template_ftp_required() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    + + ', $txt['package_ftp_necessary'], ' + +
    + ', template_control_chmod(), ' +
    +
    '; +} + +function template_view_operations() +{ + global $context, $txt, $settings; + + echo ' + + + ', $txt['operation_title'], ' + + + + + + + +
    +
    + ', $context['operations']['search'], ' +
    +
    + ', $context['operations']['replace'], ' +
    +
    + +'; +} + +function template_file_permissions() +{ + global $txt, $scripturl, $context, $settings; + + // This will handle expanding the selection. + echo ' + '; + + echo ' +
    +
    + ', $txt['package_file_perms_warning'], ': +
    +
      + ', $txt['package_file_perms_warning_desc'], ' +
    +
    +
    +
    + +
    +
    +

    + ', $txt['package_file_perms'], '', $txt['package_file_perms_new_status'], ' +

    +
    + + + + + + + + + + + + '; + + foreach ($context['file_tree'] as $name => $dir) + { + echo ' + + + + + + + + + + + '; + + if (!empty($dir['contents'])) + template_permission_show_contents($name, $dir['contents'], 1); + } + + echo ' + +
     ', $txt['package_file_perms_name'], ' ', $txt['package_file_perms_status'], '', $txt['package_file_perms_status_read'], '', $txt['package_file_perms_status_write'], '', $txt['package_file_perms_status_execute'], '', $txt['package_file_perms_status_custom'], '', $txt['package_file_perms_status_no_change'], '
    '; + + if (!empty($dir['type']) && ($dir['type'] == 'dir' || $dir['type'] == 'dir_recursive')) + echo ' + *'; + + echo ' + ', $name, ' + + ', ($dir['perms']['chmod'] ? $txt['package_file_perms_writable'] : $txt['package_file_perms_not_writable']), ' + ', ($dir['perms']['perms'] ? ' (' . $txt['package_file_perms_chmod'] . ': ' . substr(sprintf('%o', $dir['perms']['perms']), -4) . ')' : ''), ' +
    +
    +
    +

    ', $txt['package_file_perms_change'], '

    +
    +
    + +
    +
    +
    +
    + + +
    +
    + ', $txt['package_file_perms_custom'], ':  (?) +
    +
    + + + +
    +
    + ', $txt['package_file_perms_predefined_note'], ' +
    +
    +
    '; + + // Likely to need FTP? + if (empty($context['ftp_connected'])) + echo ' +

    + ', $txt['package_file_perms_ftp_details'], ': +

    + ', template_control_chmod(), ' +
    ', $txt['package_file_perms_ftp_retain'], '
    '; + + echo ' + +
    + + +
    +
    + +
    '; + + // Any looks fors we've already done? + foreach ($context['look_for'] as $path) + echo ' + '; + echo ' +

    '; +} + +function template_permission_show_contents($ident, $contents, $level, $has_more = false) +{ + global $settings, $txt, $scripturl, $context; + $js_ident = preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $ident); + // Have we actually done something? + $drawn_div = false; + + foreach ($contents as $name => $dir) + { + if (isset($dir['perms'])) + { + if (!$drawn_div) + { + $drawn_div = true; + echo ' + + '; + } + + $cur_ident = preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $ident . '/' . $name); + echo ' + + + + + + + + + + '; + + if (!empty($dir['contents'])) + { + template_permission_show_contents($ident . '/' . $name, $dir['contents'], $level + 1, !empty($dir['more_files'])); + + } + } + } + + // We have more files to show? + if ($has_more) + echo ' + + + + '; + + if ($drawn_div) + { + // Hide anything too far down the tree. + $isFound = false; + foreach ($context['look_for'] as $tree) + { + if (substr($tree, 0, strlen($ident)) == $ident) + $isFound = true; + } + + if ($level > 1 && !$isFound) + echo ' +
    ' . str_repeat(' ', $level * 5), ' + ', (!empty($dir['type']) && $dir['type'] == 'dir_recursive') || !empty($dir['list_contents']) ? '' : ''; + + if (!empty($dir['type']) && ($dir['type'] == 'dir' || $dir['type'] == 'dir_recursive')) + echo ' + *'; + + echo ' + ', $name, ' + ', (!empty($dir['type']) && $dir['type'] == 'dir_recursive') || !empty($dir['list_contents']) ? '' : '', ' + + ', ($dir['perms']['chmod'] ? $txt['package_file_perms_writable'] : $txt['package_file_perms_not_writable']), ' + ', ($dir['perms']['perms'] ? ' (' . $txt['package_file_perms_chmod'] . ': ' . substr(sprintf('%o', $dir['perms']['perms']), -4) . ')' : ''), ' +
    ' . str_repeat(' ', $level * 5), ' + « ', $txt['package_file_perms_more_files'], ' » +
    + + '; + } +} + +function template_action_permissions() +{ + global $txt, $scripturl, $context, $settings; + + $countDown = 3; + + echo ' +
    +
    +
    +

    ', $txt['package_file_perms_applying'], '

    +
    '; + + if (!empty($context['skip_ftp'])) + echo ' +
    + ', $txt['package_file_perms_skipping_ftp'], ' +
    '; + + // How many have we done? + $remaining_items = count($context['method'] == 'individual' ? $context['to_process'] : $context['directory_list']); + $progress_message = sprintf($context['method'] == 'individual' ? $txt['package_file_perms_items_done'] : $txt['package_file_perms_dirs_done'], $context['total_items'] - $remaining_items, $context['total_items']); + $progress_percent = round(($context['total_items'] - $remaining_items) / $context['total_items'] * 100, 1); + + echo ' +
    + +
    +
    + ', $progress_message, ' +
    +
    ', $progress_percent, '%
    +
     
    +
    +
    '; + + // Second progress bar for a specific directory? + if ($context['method'] != 'individual' && !empty($context['total_files'])) + { + $file_progress_message = sprintf($txt['package_file_perms_files_done'], $context['file_offset'], $context['total_files']); + $file_progress_percent = round($context['file_offset'] / $context['total_files'] * 100, 1); + + echo ' +
    +
    + ', $file_progress_message, ' +
    +
    ', $file_progress_percent, '%
    +
     
    +
    +
    '; + } + + echo ' +
    '; + + // Put out the right hidden data. + if ($context['method'] == 'individual') + echo ' + + + '; + else + echo ' + + + + + '; + + // Are we not using FTP for whatever reason. + if (!empty($context['skip_ftp'])) + echo ' + '; + + // Retain state. + foreach ($context['back_look_data'] as $path) + echo ' + '; + + echo ' + + +
    + +
    +
    + +
    + +
    +
    '; + + // Just the countdown stuff + echo ' + '; + +} + +?> \ No newline at end of file diff --git a/Themes/default/PersonalMessage.template.php b/Themes/default/PersonalMessage.template.php new file mode 100644 index 0000000..6ff12f1 --- /dev/null +++ b/Themes/default/PersonalMessage.template.php @@ -0,0 +1,1747 @@ +'; + + // Show the capacity bar, if available. + if (!empty($context['limit_bar'])) + echo ' +
    +

    + ', $txt['pm_capacity'], ': + + + + ', $context['limit_bar']['text'], ' +

    +
    '; + + // Message sent? Show a small indication. + if (isset($context['pm_sent'])) + echo ' +
    + ', $txt['pm_sent'], ' +
    '; +} + +// Just the end of the index bar, nothing special. +function template_pm_below() +{ + global $context, $settings, $options; + + echo ' + '; +} + +function template_folder() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + // The every helpful javascript! + echo ' + '; + + echo ' +'; + + // If we are not in single display mode show the subjects on the top! + if ($context['display_mode'] != 1) + { + template_subject_list(); + echo '

    '; + } + + // Got some messages to display? + if ($context['get_pmessage']('message', true)) + { + // Show the helpful titlebar - generally. + if ($context['display_mode'] != 1) + echo ' +
    +

    + ', $txt['author'], ' + ', $txt[$context['display_mode'] == 0 ? 'messages' : 'conversation'], ' +

    +
    '; + + // Show a few buttons if we are in conversation mode and outputting the first message. + if ($context['display_mode'] == 2) + { + // Build the normal button array. + $conversation_buttons = array( + 'reply' => array('text' => 'reply_to_all', 'image' => 'reply.gif', 'lang' => true, 'url' => $scripturl . '?action=pm;sa=send;f=' . $context['folder'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';pmsg=' . $context['current_pm'] . ';u=all', 'active' => true), + 'delete' => array('text' => 'delete_conversation', 'image' => 'delete.gif', 'lang' => true, 'url' => $scripturl . '?action=pm;sa=pmactions;pm_actions[' . $context['current_pm'] . ']=delete;conversation;f=' . $context['folder'] . ';start=' . $context['start'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';' . $context['session_var'] . '=' . $context['session_id'], 'custom' => 'onclick="return confirm(\'' . addslashes($txt['remove_message']) . '?\');"'), + ); + + // Show the conversation buttons. + echo ' +
    '; + + template_button_strip($conversation_buttons, 'right'); + + echo ' +
    '; + } + + while ($message = $context['get_pmessage']('message')) + { + $window_class = $message['alternate'] == 0 ? 'windowbg' : 'windowbg2'; + + echo ' +
    + +
    + +

    '; + + // Show online and offline buttons? + if (!empty($modSettings['onlineEnable']) && !$message['member']['is_guest']) + echo ' + ', $message['member']['online']['text'], ''; + + echo ' + ', $message['member']['link'], ' +

    +
      '; + + // Show the member's custom title, if they have one. + if (isset($message['member']['title']) && $message['member']['title'] != '') + echo ' +
    • ', $message['member']['title'], '
    • '; + + // Show the member's primary group (like 'Administrator') if they have one. + if (isset($message['member']['group']) && $message['member']['group'] != '') + echo ' +
    • ', $message['member']['group'], '
    • '; + + // Don't show these things for guests. + if (!$message['member']['is_guest']) + { + // Show the post group if and only if they have no other group or the option is on, and they are in a post group. + if ((empty($settings['hide_post_group']) || $message['member']['group'] == '') && $message['member']['post_group'] != '') + echo ' +
    • ', $message['member']['post_group'], '
    • '; + echo ' +
    • ', $message['member']['group_stars'], '
    • '; + + // Show avatars, images, etc.? + if (!empty($settings['show_user_images']) && empty($options['show_no_avatars']) && !empty($message['member']['avatar']['image'])) + echo ' +
    • + + ', $message['member']['avatar']['image'], ' + +
    • '; + + // Show how many posts they have made. + if (!isset($context['disabled_fields']['posts'])) + echo ' +
    • ', $txt['member_postcount'], ': ', $message['member']['posts'], '
    • '; + + // Is karma display enabled? Total or +/-? + if ($modSettings['karmaMode'] == '1') + echo ' +
    • ', $modSettings['karmaLabel'], ' ', $message['member']['karma']['good'] - $message['member']['karma']['bad'], '
    • '; + elseif ($modSettings['karmaMode'] == '2') + echo ' +
    • ', $modSettings['karmaLabel'], ' +', $message['member']['karma']['good'], '/-', $message['member']['karma']['bad'], '
    • '; + + // Is this user allowed to modify this member's karma? + if ($message['member']['karma']['allow']) + echo ' +
    • + ', $modSettings['karmaApplaudLabel'], ' ', $modSettings['karmaSmiteLabel'], ' +
    • '; + + // Show the member's gender icon? + if (!empty($settings['show_gender']) && $message['member']['gender']['image'] != '' && !isset($context['disabled_fields']['gender'])) + echo ' +
    • ', $txt['gender'], ': ', $message['member']['gender']['image'], '
    • '; + + // Show their personal text? + if (!empty($settings['show_blurb']) && $message['member']['blurb'] != '') + echo ' +
    • ', $message['member']['blurb'], '
    • '; + + // Any custom fields to show as icons? + if (!empty($message['member']['custom_fields'])) + { + $shown = false; + foreach ($message['member']['custom_fields'] as $custom) + { + if ($custom['placement'] != 1 || empty($custom['value'])) + continue; + if (empty($shown)) + { + $shown = true; + echo ' +
    • +
        '; + } + echo ' +
      • ', $custom['value'], '
      • '; + } + if ($shown) + echo ' +
      +
    • '; + } + + // This shows the popular messaging icons. + if ($message['member']['has_messenger'] && $message['member']['can_view_profile']) + echo ' +
    • +
        ', !isset($context['disabled_fields']['icq']) && !empty($message['member']['icq']['link']) ? ' +
      • ' . $message['member']['icq']['link'] . '
      • ' : '', !isset($context['disabled_fields']['msn']) && !empty($message['member']['msn']['link']) ? ' +
      • ' . $message['member']['msn']['link'] . '
      • ' : '', !isset($context['disabled_fields']['aim']) && !empty($message['member']['aim']['link']) ? ' +
      • ' . $message['member']['aim']['link'] . '
      • ' : '', !isset($context['disabled_fields']['yim']) && !empty($message['member']['yim']['link']) ? ' +
      • ' . $message['member']['yim']['link'] . '
      • ' : '', ' +
      +
    • '; + + // Show the profile, website, email address, and personal message buttons. + if ($settings['show_profile_buttons']) + { + echo ' +
    • + +
    • '; + } + + // Any custom fields for standard placement? + if (!empty($message['member']['custom_fields'])) + { + foreach ($message['member']['custom_fields'] as $custom) + if (empty($custom['placement']) || empty($custom['value'])) + echo ' +
    • ', $custom['title'], ': ', $custom['value'], '
    • '; + } + + // Are we showing the warning status? + if ($message['member']['can_see_warning']) + echo ' +
    • ', $context['can_issue_warning'] ? '' : '', '', $txt['user_warn_' . $message['member']['warning_status']], '', $context['can_issue_warning'] ? '' : '', '', $txt['warn_' . $message['member']['warning_status']], '
    • '; + } + + // Done with the information about the poster... on to the post itself. + echo ' +
    +
    +
    +
    +
    +
    + ', $message['subject'], ' +
    '; + + // Show who the message was sent to. + echo ' + « ', $txt['sent_to'], ': '; + + // People it was sent directly to.... + if (!empty($message['recipients']['to'])) + echo implode(', ', $message['recipients']['to']); + // Otherwise, we're just going to say "some people"... + elseif ($context['folder'] != 'sent') + echo '(', $txt['pm_undisclosed_recipients'], ')'; + + echo ' + ', $txt['on'], ': ', $message['time'], ' » + '; + + // If we're in the sent items, show who it was sent to besides the "To:" people. + if (!empty($message['recipients']['bcc'])) + echo ' +
    « ', $txt['pm_bcc'], ': ', implode(', ', $message['recipients']['bcc']), ' »'; + + if (!empty($message['is_replied_to'])) + echo ' +
    « ', $txt['pm_is_replied_to'], ' »'; + + echo ' +
    +
      '; + + // Show reply buttons if you have the permission to send PMs. + if ($context['can_send_pm']) + { + // You can't really reply if the member is gone. + if (!$message['member']['is_guest']) + { + // Is there than more than one recipient you can reply to? + if ($message['number_recipients'] > 1 && $context['display_mode'] != 2) + echo ' +
    • ', $txt['reply_to_all'], '
    • '; + + echo ' +
    • ', $txt['reply'], '
    • +
    • ', $txt['quote'], '
    • '; + } + // This is for "forwarding" - even if the member is gone. + else + echo ' +
    • ', $txt['reply_quote'], '
    • '; + } + echo ' +
    • ', $txt['delete'], '
    • '; + + if (empty($context['display_mode'])) + echo ' +
    • '; + + echo ' +
    +
    +
    +
    ', $message['body'], '
    + '; + + // Are there any custom profile fields for above the signature? + if (!empty($message['member']['custom_fields'])) + { + $shown = false; + foreach ($message['member']['custom_fields'] as $custom) + { + if ($custom['placement'] != 2 || empty($custom['value'])) + continue; + if (!$shown) + { + $shown = true; + echo ' +
    +
      '; + } + echo ' +
    • ', $custom['value'], '
    • '; + } + if ($shown) + echo ' +
    +
    '; + } + + // Show the member's signature? + if (!empty($message['member']['signature']) && empty($options['show_no_signatures']) && $context['signature_enabled']) + echo ' +
    ', $message['member']['signature'], '
    '; + + // Add an extra line at the bottom if we have labels enabled. + if ($context['folder'] != 'sent' && !empty($context['currently_using_labels']) && $context['display_mode'] != 2) + { + echo ' +
    '; + // Add the label drop down box. + if (!empty($context['currently_using_labels'])) + { + echo ' + + '; + } + echo ' +
    '; + } + + echo ' +
    +
    +
    +
    +
    + +
    '; + } + + if (empty($context['display_mode'])) + echo ' + +
    +
    ', $txt['pages'], ': ', $context['page_index'], '
    +
    +
    '; + + // Show a few buttons if we are in conversation mode and outputting the first message. + elseif ($context['display_mode'] == 2 && isset($conversation_buttons)) + { + echo ' + +
    '; + + template_button_strip($conversation_buttons, 'right'); + + echo ' +
    '; + } + + echo ' +
    '; + } + + // Individual messages = buttom list! + if ($context['display_mode'] == 1) + { + template_subject_list(); + echo '
    '; + } + + echo ' + +'; +} + +// Just list all the personal message subjects - to make templates easier. +function template_subject_list() +{ + global $context, $options, $settings, $modSettings, $txt, $scripturl; + + echo ' +
    + + + + + + + + + + '; + if (!$context['show_delete']) + echo ' + + + '; + $next_alternate = false; + + while ($message = $context['get_pmessage']('subject')) + { + echo ' + + + + + + + '; + $next_alternate = !$next_alternate; + } + + echo ' + +
    + ', $txt['pm_change_view'], ' + + ', $txt['date'], $context['sort_by'] == 'date' ? ' ' : '', ' + + ', $txt['subject'], $context['sort_by'] == 'subject' ? ' ' : '', ' + + ', ($context['from_or_to'] == 'from' ? $txt['from'] : $txt['to']), $context['sort_by'] == 'name' ? ' ' : '', ' + + +
    ', $txt['msg_alert_none'], '
    + + ', $message['is_replied_to'] ? '' . $txt['pm_replied'] . '' : '' . $txt['pm_read'] . '', '', $message['time'], '', ($context['display_mode'] != 0 && $context['current_pm'] == $message['id'] ? '*' : ''), '', $message['subject'], '', $message['is_unread'] ? ' ' . $txt['new'] . '' : '', '', ($context['from_or_to'] == 'from' ? $message['member']['link'] : (empty($message['recipients']['to']) ? '' : implode(', ', $message['recipients']['to']))), '
    +
    +
    ', $txt['pages'], ': ', $context['page_index'], '
    +
     '; + + if ($context['show_delete']) + { + if (!empty($context['currently_using_labels']) && $context['folder'] != 'sent') + { + echo ' + + '; + } + + echo ' + '; + } + + echo ' +
    +
    '; +} + +function template_search() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + echo ' + +
    +
    +

    ', $txt['pm_search_title'], '

    +
    '; + + if (!empty($context['search_errors'])) + { + echo ' +
    + ', implode('
    ', $context['search_errors']['messages']), ' +
    '; + } + + if ($context['simple_search']) + { + echo ' + '; + } + + // Advanced search! + else + { + echo ' + '; + + // Do we have some labels setup? If so offer to search by them! + if ($context['currently_using_labels']) + { + echo ' +
    + +
    + +
      '; + + foreach ($context['search_labels'] as $label) + echo ' +
    • + +
    • '; + + echo ' +
    +

    + + +


    +
    + +
    '; + } + } + + echo ' +
    '; +} + +function template_search_results() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + echo ' +
    +

    ', $txt['pm_search_results'], '

    +
    +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    '; + + // complete results ? + if (empty($context['search_params']['show_complete']) && !empty($context['personal_messages'])) + echo ' + + + + + + + + + '; + + $alternate = true; + // Print each message out... + foreach ($context['personal_messages'] as $message) + { + // We showing it all? + if (!empty($context['search_params']['show_complete'])) + { + echo ' +
    +

    + ', $txt['search_on'], ': ', $message['time'], ' + ', $message['counter'], '  ', $message['subject'], ' +

    +
    +
    +

    ', $txt['from'], ': ', $message['member']['link'], ', ', $txt['to'], ': '; + + // Show the recipients. + // !!! This doesn't deal with the sent item searching quite right for bcc. + if (!empty($message['recipients']['to'])) + echo implode(', ', $message['recipients']['to']); + // Otherwise, we're just going to say "some people"... + elseif ($context['folder'] != 'sent') + echo '(', $txt['pm_undisclosed_recipients'], ')'; + + echo ' +

    +
    +
    + +
    + ', $message['body'], ' +

    '; + + if ($context['can_send_pm']) + { + $quote_button = create_button('quote.gif', 'reply_quote', 'reply_quote', 'align="middle"'); + $reply_button = create_button('im_reply.gif', 'reply', 'reply', 'align="middle"'); + // You can only reply if they are not a guest... + if (!$message['member']['is_guest']) + echo ' + ', $quote_button , '', $context['menu_separator'], ' + ', $reply_button , ' ', $context['menu_separator']; + // This is for "forwarding" - even if the member is gone. + else + echo ' + ', $quote_button , '', $context['menu_separator']; + } + + echo ' +

    +
    + +
    '; + } + // Otherwise just a simple list! + else + { + // !!! No context at all of the search? + echo ' + + + + + '; + } + + $alternate = !$alternate; + } + + // Finish off the page... + if (empty($context['search_params']['show_complete']) && !empty($context['personal_messages'])) + echo ' + +
    ', $txt['date'], '', $txt['subject'], '', $txt['from'], '
    ', $message['time'], '', $message['link'], '', $message['member']['link'], '
    '; + + // No results? + if (empty($context['personal_messages'])) + echo ' +
    + +
    +

    ', $txt['pm_search_none_found'], '

    +
    + +
    '; + + echo ' +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    '; + +} + +function template_send() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + // Show which messages were sent successfully and which failed. + if (!empty($context['send_log'])) + { + echo ' +
    +

    ', $txt['pm_send_report'], '

    +
    +
    + +
    '; + if (!empty($context['send_log']['sent'])) + foreach ($context['send_log']['sent'] as $log_entry) + echo '', $log_entry, '
    '; + if (!empty($context['send_log']['failed'])) + foreach ($context['send_log']['failed'] as $log_entry) + echo '', $log_entry, '
    '; + echo ' +
    + +
    +
    '; + } + + // Show the preview of the personal message. + if (isset($context['preview_message'])) + echo ' +
    +

    ', $context['preview_subject'], '

    +
    +
    + +
    + ', $context['preview_message'], ' +
    + +
    +
    '; + + // Main message editing box. + echo ' +
    +

    + ', $txt['new_message'], ' ', $txt['new_message'], ' +

    +
    '; + + echo ' +
    +
    + +

    '; + + // If there were errors for sending the PM, show them. + if (!empty($context['post_error']['messages'])) + { + echo ' +
    + ', $txt['error_while_submitting'], ' +
      '; + + foreach ($context['post_error']['messages'] as $error) + echo ' +
    • ', $error, '
    • '; + + echo ' +
    +
    '; + } + + echo ' +
    '; + + // To and bcc. Include a button to search for members. + echo ' +
    + ', $txt['pm_to'], ': +
    '; + + // Autosuggest will be added by the JavaScript later on. + echo ' +
    + '; + + // A link to add BCC, only visible with JavaScript enabled. + echo ' + '; + + // A div that'll contain the items found by the autosuggest. + echo ' +
    '; + + echo ' +
    '; + + // This BCC row will be hidden by default if JavaScript is enabled. + echo ' +
    + ', $txt['pm_bcc'], ': +
    +
    + +
    +
    '; + + // The subject of the PM. + echo ' +
    + ', $txt['subject'], ': +
    +
    + +
    +

    '; + + // Showing BBC? + if ($context['show_bbc']) + { + echo ' +
    '; + } + + // What about smileys? + if (!empty($context['smileys']['postform']) || !empty($context['smileys']['popup'])) + echo ' +
    '; + + // Show BBC buttons, smileys and textbox. + echo ' + ', template_control_richedit($context['post_box_name'], 'smileyBox_message', 'bbcBox_message'); + + // Require an image to be typed to save spamming? + if ($context['require_verification']) + { + echo ' +
    + ', $txt['pm_visual_verification_label'], ': + ', template_control_verification($context['visual_verification_id'], 'all'), ' +
    '; + } + + // Send, Preview, spellcheck buttons. + echo ' +

    +

    + ', $context['browser']['is_firefox'] ? $txt['shortcuts_firefox'] : $txt['shortcuts'], ' +

    +

    + ', template_control_richedit_buttons($context['post_box_name']), ' +

    + + + + + + +
    +
    + +
    +
    '; + + // Show the message you're replying to. + if ($context['reply']) + echo ' +
    +
    +
    +

    ', $txt['subject'], ': ', $context['quoted_message']['subject'], '

    +
    +
    + +
    +
    + ', $txt['on'], ': ', $context['quoted_message']['time'], ' + ', $txt['from'], ': ', $context['quoted_message']['member']['name'], ' +

    + ', $context['quoted_message']['body'], ' +
    + +

    '; + + echo ' + + + '; +} + +// This template asks the user whether they wish to empty out their folder/messages. +function template_ask_delete() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + echo ' +
    +

    ', ($context['delete_all'] ? $txt['delete_message'] : $txt['delete_all']), '

    +
    +
    + +
    +

    ', $txt['delete_all_confirm'], '


    + ', $txt['yes'], ' - ', $txt['no'], ' +
    + +
    '; +} + +// This template asks the user what messages they want to prune. +function template_prune() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    +

    ', $txt['pm_prune'], '

    +
    +
    + +
    +

    ', $txt['pm_prune_desc1'], ' ', $txt['pm_prune_desc2'], '

    +
    + +
    +
    + +
    + +
    '; +} + +// Here we allow the user to setup labels, remove labels and change rules for labels (i.e, do quite a bit) +function template_labels() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    +

    ', $txt['pm_manage_labels'], '

    +
    +
    + ', $txt['pm_labels_desc'], ' +
    + + + + + + + + '; + if (count($context['labels']) < 2) + echo ' + + + '; + else + { + $alternate = true; + foreach ($context['labels'] as $label) + { + if ($label['id'] == -1) + continue; + + echo ' + + + + '; + + $alternate = !$alternate; + } + } + echo ' + +
    + ', $txt['pm_label_name'], ' + '; + + if (count($context['labels']) > 2) + echo ' + '; + + echo ' +
    ', $txt['pm_labels_no_exist'], '
    + +
    '; + + if (!count($context['labels']) < 2) + echo ' +
    + + +
    '; + + echo ' + +
    +
    +
    +

    ', $txt['pm_label_add_new'], '

    +
    +
    + +
    +
    +
    + : +
    +
    + +
    +
    +
    + +
    +
    + +
    + +

    '; +} + +// Template for reporting a personal message. +function template_report_message() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    + +
    +

    ', $txt['pm_report_title'], '

    +
    +
    + ', $txt['pm_report_desc'], ' +
    +
    + +
    +
    '; + + // If there is more than one admin on the forum, allow the user to choose the one they want to direct to. + // !!! Why? + if ($context['admin_count'] > 1) + { + echo ' +
    + ', $txt['pm_report_admins'], ': +
    +
    + +
    '; + } + + echo ' +
    + ', $txt['pm_report_reason'], ': +
    +
    + +
    +
    +
    + +
    +
    + +
    + +
    '; +} + +// Little template just to say "Yep, it's been submitted" +function template_report_message_complete() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +

    ', $txt['pm_report_title'], '

    +
    +
    + +
    +

    ', $txt['pm_report_done'], '

    + ', $txt['pm_report_return'], ' +
    + +
    '; +} + +// Manage rules. +function template_rules() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +

    ', $txt['pm_manage_rules'], '

    +
    +
    + ', $txt['pm_manage_rules_desc'], ' +
    + + + + + + + + '; + + if (empty($context['rules'])) + echo ' + + + '; + + $alternate = false; + foreach ($context['rules'] as $rule) + { + echo ' + + + + '; + $alternate = !$alternate; + } + + echo ' + +
    + ', $txt['pm_rule_title'], ' + '; + + if (!empty($context['rules'])) + echo ' + '; + + echo ' +
    + ', $txt['pm_rules_none'], ' +
    + ', $rule['name'], ' + + +
    +
    + [', $txt['pm_add_rule'], ']'; + + if (!empty($context['rules'])) + echo ' + [', $txt['pm_apply_rules'], ']'; + + if (!empty($context['rules'])) + echo ' + + '; + + echo ' +
    +
    '; + +} + +// Template for adding/editing a rule. +function template_add_rule() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' + '; + + echo ' +
    +
    +

    ', $context['rid'] == 0 ? $txt['pm_add_rule'] : $txt['pm_edit_rule'], '

    +
    +
    + +
    +
    +
    + ', $txt['pm_rule_name'], ':
    + ', $txt['pm_rule_name_desc'], ' +
    +
    + +
    +
    +
    + ', $txt['pm_rule_criteria'], ''; + + // Add a dummy criteria to allow expansion for none js users. + $context['rule']['criteria'][] = array('t' => '', 'v' => ''); + + // For each criteria print it out. + $isFirst = true; + foreach ($context['rule']['criteria'] as $k => $criteria) + { + if (!$isFirst && $criteria['t'] == '') + echo '
    '; + elseif (!$isFirst) + echo '
    '; + + echo ' + + + + + + + '; + + // If this is the dummy we add a means to hide for non js users. + if ($isFirst) + $isFirst = false; + elseif ($criteria['t'] == '') + echo '
    '; + } + + echo ' +
    + +

    + ', $txt['pm_rule_logic'], ': + +
    +
    + ', $txt['pm_rule_actions'], ''; + + // As with criteria - add a dummy action for "expansion". + $context['rule']['actions'][] = array('t' => '', 'v' => ''); + + // Print each action. + $isFirst = true; + foreach ($context['rule']['actions'] as $k => $action) + { + if (!$isFirst && $action['t'] == '') + echo '
    '; + elseif (!$isFirst) + echo '
    '; + + echo ' + + + + '; + + if ($isFirst) + $isFirst = false; + elseif ($action['t'] == '') + echo ' +
    '; + } + + echo ' +
    + +
    +
    + +

    +
    +

    ', $txt['pm_rule_description'], '

    +
    +
    +
    ', $txt['pm_rule_js_disabled'], '
    +
    +
    + + +
    +
    '; + + // Now setup all the bits! + echo ' + '; +} + +?> \ No newline at end of file diff --git a/Themes/default/Poll.template.php b/Themes/default/Poll.template.php new file mode 100644 index 0000000..0f2e805 --- /dev/null +++ b/Themes/default/Poll.template.php @@ -0,0 +1,156 @@ +:
  8. '; + + // Start the main poll form. + echo ' +
    +
    +
    +

    ', $context['page_title'], '

    +
    '; + + if (!empty($context['poll_error']['messages'])) + echo ' +
    +
    +
    + ', $context['is_edit'] ? $txt['error_while_editing_poll'] : $txt['error_while_adding_poll'], ': +
    +
    + ', empty($context['poll_error']['messages']) ? '' : implode('
    ', $context['poll_error']['messages']), ' +
    +
    +
    '; + + echo ' +
    + +
    + +
    + ', $txt['poll_question'], ': + +
      '; + + foreach ($context['choices'] as $choice) + { + echo ' +
    • + : + '; + + // Does this option have a vote count yet, or is it new? + if ($choice['votes'] != -1) + echo ' (', $choice['votes'], ' ', $txt['votes'], ')'; + + echo ' +
    • '; + } + + echo ' +
    • +
    + (', $txt['poll_add_option'], ') +
    +
    + ', $txt['poll_options'], ': +
    '; + + if ($context['can_moderate_poll']) + { + echo ' +
    + +
    +
    + +
    +
    +
    + ', $txt['poll_run_limit'], ' +
    +
    + ', $txt['days_word'], ' +
    +
    + +
    +
    + +
    '; + + if ($context['poll']['guest_vote_allowed']) + echo ' +
    + +
    +
    + +
    '; + } + + echo ' +
    + ', $txt['poll_results_visibility'], ': +
    +
    +
    +
    + +
    +
    +
    '; + // If this is an edit, we can allow them to reset the vote counts. + if ($context['is_edit']) + echo ' +
    + ', $txt['reset_votes'], ' + ' . $txt['reset_votes_check'] . ' +
    '; + echo ' +
    + +
    +
    + +
    + + +
    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/Post.template.php b/Themes/default/Post.template.php new file mode 100644 index 0000000..df09f65 --- /dev/null +++ b/Themes/default/Post.template.php @@ -0,0 +1,1096 @@ +:
  9. '), '); + }'; + + // If we are making a calendar event we want to ensure we show the current days in a month etc... this is done here. + if ($context['make_event']) + echo ' + var monthLength = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + + function generateDays() + { + var dayElement = document.getElementById(\'day\'), yearElement = document.getElementById(\'year\'), monthElement = document.getElementById(\'month\'); + var days, selected = dayElement.selectedIndex; + + monthLength[1] = yearElement.options[yearElement.selectedIndex].value % 4 == 0 ? 29 : 28; + days = monthLength[monthElement.value - 1]; + + while (dayElement.options.length) + dayElement.options[0] = null; + + for (i = 1; i <= days; i++) + dayElement.options[dayElement.length] = new Option(i, i); + + if (selected < days) + dayElement.selectedIndex = selected; + }'; + + // End of the javascript, start the form and display the link tree. + echo ' + // ]]> +
    '; + + // If the user wants to see how their message looks - the preview section is where it's at! + echo ' +
    '; + + if ($context['make_event'] && (!$context['event']['new'] || !empty($context['current_board']))) + echo ' + '; + + // Start the main table. + echo ' +
    +

    ', $context['page_title'], '

    +
    +
    + +
    ', isset($context['current_topic']) ? ' + ' : ''; + + // If an error occurred, explain what happened. + echo ' + '; + + // If this won't be approved let them know! + if (!$context['becomes_approved']) + { + echo ' +

    + ', $txt['wait_for_approval'], ' + +

    '; + } + + // If it's locked, show a message to warn the replyer. + echo ' + '; + + // The post header... important stuff + echo ' +
    '; + + // Guests have to put in their name and email... + if (isset($context['name']) && isset($context['email'])) + { + echo ' +
    + ', $txt['name'], ': +
    +
    + +
    '; + + if (empty($modSettings['guest_post_no_email'])) + echo ' +
    + ', $txt['email'], ': +
    +
    + +
    '; + } + + // Now show the subject box for this post. + echo ' +
    + ', $txt['subject'], ': +
    +
    + +
    +
    + ', $txt['message_icon'], ': +
    +
    + + +
    +

    '; + + // Are you posting a calendar event? + if ($context['make_event']) + { + echo ' +
    +
    + ', $txt['calendar_event_title'], ' + +
    + ', $txt['calendar_year'], ' + + ', $txt['calendar_month'], ' + + ', $txt['calendar_day'], ' + +
    +
    '; + + if (!empty($modSettings['cal_allowspan']) || ($context['event']['new'] && $context['is_new_post'])) + { + echo ' +
    + ', $txt['calendar_event_options'], ' +
    +
      '; + + // If events can span more than one day then allow the user to select how long it should last. + if (!empty($modSettings['cal_allowspan'])) + { + echo ' +
    • + ', $txt['calendar_numb_days'], ' + +
    • '; + } + + // If this is a new event let the user specify which board they want the linked post to be put into. + if ($context['event']['new'] && $context['is_new_post']) + { + echo ' +
    • + ', $txt['calendar_post_in'], ' + +
    • '; + } + + echo ' +
    +
    +
    '; + } + + echo ' +
    '; + } + + // If this is a poll then display all the poll options! + if ($context['make_poll']) + { + echo ' +
    +
    + ', $txt['poll_question'], ' + +
      '; + + // Loop through all the choices and print them out. + foreach ($context['choices'] as $choice) + { + echo ' +
    • + : + +
    • '; + } + + echo ' +
    • +
    + (', $txt['poll_add_option'], ') +
    +
    + ', $txt['poll_options'], ' +
    +
    + +
    +
    + +
    +
    +
    + ', $txt['poll_run_limit'], ' +
    +
    + ', $txt['days_word'], ' +
    +
    + +
    +
    + +
    '; + + if ($context['poll_options']['guest_vote_enabled']) + echo ' +
    + +
    +
    + +
    '; + + echo ' +
    + ', $txt['poll_results_visibility'], ': +
    +
    +
    +
    + +
    +
    +
    +
    '; + } + + // Show the actual posting area... + if ($context['show_bbc']) + { + echo ' +
    '; + } + + // What about smileys? + if (!empty($context['smileys']['postform']) || !empty($context['smileys']['popup'])) + echo ' +
    '; + + echo ' + ', template_control_richedit($context['post_box_name'], 'smileyBox_message', 'bbcBox_message'); + + // If this message has been edited in the past - display when it was. + if (isset($context['last_modified'])) + echo ' +
    + ', $txt['last_edit'], ': + ', $context['last_modified'], ' +
    '; + + // If the admin has enabled the hiding of the additional options - show a link and image for it. + if (!empty($settings['additional_options_collapsable'])) + echo ' + '; + + // Display the check boxes for all the standard options - if they are available to the user! + echo ' +
    +
      + ', $context['can_notify'] ? '
    • ' : '', ' + ', $context['can_lock'] ? '
    • ' : '', ' +
    • + ', $context['can_sticky'] ? '
    • ' : '', ' +
    • ', ' + ', $context['can_move'] ? '
    • ' : '', ' + ', $context['can_announce'] && $context['is_first_post'] ? '
    • ' : '', ' + ', $context['show_approval'] ? '
    • ' : '', ' +
    +
    '; + + // If this post already has attachments on it - give information about them. + if (!empty($context['current_attachments'])) + { + echo ' +
    +
    + ', $txt['attached'], ': +
    +
    + + ', $txt['uncheck_unwatchd_attach'], ': +
    '; + foreach ($context['current_attachments'] as $attachment) + echo ' +
    + +
    '; + echo ' +
    '; + } + + // Is the user allowed to post any additional ones? If so give them the boxes to do it! + if ($context['can_post_attachment']) + { + echo ' +
    +
    + ', $txt['attach'], ': +
    +
    + (', $txt['clean_attach'], ')'; + + // Show more boxes only if they aren't approaching their limit. + if ($context['num_allowed_attachments'] > 1) + echo ' + +
    +
    (', $txt['more_attachments'], ')
    '; + + echo ' +
    '; + + // Show some useful information such as allowed extensions, maximum size and amount of attachments allowed. + if (!empty($modSettings['attachmentCheckExtensions'])) + echo ' + ', $txt['allowed_types'], ': ', $context['allowed_extensions'], '
    '; + + if (!empty($context['attachment_restrictions'])) + echo ' + ', $txt['attach_restrictions'], ' ', implode(', ', $context['attachment_restrictions']), '
    '; + + if (!$context['can_post_attachment_unapproved']) + echo ' + ', $txt['attachment_requires_approval'], '', '
    '; + + echo ' +
    +
    '; + } + + // Is visual verification enabled? + if ($context['require_verification']) + { + echo ' +
    + + ', $txt['verification'], ': + + ', template_control_verification($context['visual_verification_id'], 'all'), ' +
    '; + } + + // Finally, the submit buttons. + echo ' +

    + ', $context['browser']['is_firefox'] ? $txt['shortcuts_firefox'] : $txt['shortcuts'], ' +

    +

    + ', template_control_richedit_buttons($context['post_box_name']); + + // Option to delete an event if user is editing one. + if ($context['make_event'] && !$context['event']['new']) + echo ' + '; + + echo ' +

    +
    + +
    +
    '; + + // Assuming this isn't a new topic pass across the last message id. + if (isset($context['topic_last_message'])) + echo ' + '; + + echo ' + + + +
    '; + + echo ' + '; + + // If the user is replying to a topic show the previous posts. + if (isset($context['previous_posts']) && count($context['previous_posts']) > 0) + { + echo ' +
    +
    +

    ', $txt['topic_summary'], '

    +
    + '; + + $ignored_posts = array(); + foreach ($context['previous_posts'] as $post) + { + $ignoring = false; + if (!empty($post['is_ignored'])) + $ignored_posts[] = $ignoring = $post['id']; + + echo ' +
    + +
    +
    +
    ', $txt['posted_by'], ': ', $post['poster'], '
    + « ', $txt['on'], ': ', $post['time'], ' » +
    '; + + if ($context['can_quote']) + { + echo ' + '; + } + + echo ' +
    '; + + if ($ignoring) + { + echo ' +
    + ', $txt['ignoring_user'], ' + +
    '; + } + + echo ' +
    ', $post['message'], '
    +
    + +
    '; + } + + echo ' +
    + '; + } +} + +// The template for the spellchecker. +function template_spellcheck() +{ + global $context, $settings, $options, $txt; + + // The style information that makes the spellchecker look... like the forum hopefully! + echo ' + + + ', $txt['spell_check'], ' + + + + + + + + + +
    +
     
    + + + +
    + ', $txt['spellcheck_change_to'], '
    + +
    + ', $txt['spellcheck_suggest'], '
    + +
    +
    + + + + +
    +
    + +'; +} + +function template_quotefast() +{ + global $context, $settings, $options, $txt; + + echo ' + + + + ', $txt['retrieving_quote'], ' + + + + ', $txt['retrieving_quote'], ' + + + +'; +} + +function template_announce() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +
    +

    ', $txt['announce_title'], '

    +
    +
    + ', $txt['announce_desc'], ' +
    +
    + +
    +

    + ', $txt['announce_this_topic'], ' ', $context['topic_subject'], ' +

    +
      '; + + foreach ($context['groups'] as $group) + echo ' +
    • + (', $group['member_count'], ') +
    • '; + + echo ' +
    • + +
    • +
    +
    + + + + + +
    +
    + +
    +
    +
    +
    '; +} + +function template_announcement_send() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +
    + +
    +

    ', $txt['announce_sending'], ' ', $context['topic_subject'], '

    +

    ', $context['percentage_done'], '% ', $txt['announce_done'], '

    +
    + + + + + + + +
    +
    + +
    +
    +
    +
    + '; +} + +?> \ No newline at end of file diff --git a/Themes/default/Printpage.template.php b/Themes/default/Printpage.template.php new file mode 100644 index 0000000..bf93af2 --- /dev/null +++ b/Themes/default/Printpage.template.php @@ -0,0 +1,137 @@ + + + + + + + ', $txt['print_page'], ' - ', $context['topic_subject'], ' + + + +

    ', $context['forum_name_html_safe'], '

    +

    ', $context['category_name'], ' => ', (!empty($context['parent_boards']) ? implode(' => ', $context['parent_boards']) . ' => ' : ''), $context['board_name'], ' => ', $txt['topic_started'], ': ', $context['poster_name'], ' ', $txt['search_on'], ' ', $context['post_time'], '

    +
    '; +} + +function template_main() +{ + global $context, $settings, $options, $txt; + + foreach ($context['posts'] as $post) + echo ' +
    + ', $txt['title'], ': ', $post['subject'], '
    + ', $txt['post_by'], ': ', $post['member'], ' ', $txt['search_on'], ' ', $post['time'], ' +
    +
    + ', $post['body'], ' +
    '; +} + +function template_print_below() +{ + global $context, $settings, $options; + + echo ' +
    + + +'; +} + +?> \ No newline at end of file diff --git a/Themes/default/Profile.template.php b/Themes/default/Profile.template.php new file mode 100644 index 0000000..a0fe876 --- /dev/null +++ b/Themes/default/Profile.template.php @@ -0,0 +1,2982 @@ +'; + + // Prevent Chrome from auto completing fields when viewing/editing other members profiles + if ($context['browser']['is_chrome'] && !$context['user']['is_owner']) + echo ' + '; + + // If an error occurred while trying to save previously, give the user a clue! + if (!empty($context['post_errors'])) + echo ' + ', template_error_message(); + + // If the profile was update successfully, let the user know this. + if (!empty($context['profile_updated'])) + echo ' +
    + ', $context['profile_updated'], ' +
    '; +} + +// Template for closing off table started in profile_above. +function template_profile_below() +{ +} + +// This template displays users details without any option to edit them. +function template_summary() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + // Display the basic information about the user + echo ' +
    +
    +

    + ', $txt['summary'], ' +

    +
    +
    +
    + +
    +

    ', $context['member']['name'], ' ', (!empty($context['member']['group']) ? $context['member']['group'] : $context['member']['post_group']), '

    + ', $context['member']['avatar']['image'], ' +
      '; + + // What about if we allow email only via the forum?? + if ($context['member']['show_email'] === 'yes' || $context['member']['show_email'] === 'no_through_forum' || $context['member']['show_email'] === 'yes_permission_override') + echo ' +
    • ', $txt['email'], '
    • '; + + // Don't show an icon if they haven't specified a website. + if ($context['member']['website']['url'] !== '' && !isset($context['disabled_fields']['website'])) + echo ' +
    • ', ($settings['use_image_buttons'] ? '' . $context['member']['website']['title'] . '' : $txt['www']), '
    • '; + + // Are there any custom profile fields for the summary? + if (!empty($context['custom_fields'])) + { + foreach ($context['custom_fields'] as $field) + if (($field['placement'] == 1 || empty($field['output_html'])) && !empty($field['value'])) + echo ' +
    • ', $field['output_html'], '
    • '; + } + + echo ' + ', !isset($context['disabled_fields']['icq']) && !empty($context['member']['icq']['link']) ? '
    • ' . $context['member']['icq']['link'] . '
    • ' : '', ' + ', !isset($context['disabled_fields']['msn']) && !empty($context['member']['msn']['link']) ? '
    • ' . $context['member']['msn']['link'] . '
    • ' : '', ' + ', !isset($context['disabled_fields']['aim']) && !empty($context['member']['aim']['link']) ? '
    • ' . $context['member']['aim']['link'] . '
    • ' : '', ' + ', !isset($context['disabled_fields']['yim']) && !empty($context['member']['yim']['link']) ? '
    • ' . $context['member']['yim']['link'] . '
    • ' : '', ' +
    + ', $context['can_send_pm'] ? '' : '', $settings['use_image_buttons'] ? '' . $context['member']['online']['text'] . '' : $context['member']['online']['text'], $context['can_send_pm'] ? '' : '', $settings['use_image_buttons'] ? ' ' . $context['member']['online']['text'] . '' : ''; + + // Can they add this member as a buddy? + if (!empty($context['can_have_buddy']) && !$context['user']['is_owner']) + echo ' +
    [', $txt['buddy_' . ($context['member']['is_buddy'] ? 'remove' : 'add')], ']'; + + echo ' +
    '; + + echo ' + '; + + echo ' +
    + +
    +
    +
    +
    + +
    +
    '; + + if ($context['user']['is_owner'] || $context['user']['is_admin']) + echo ' +
    ', $txt['username'], ':
    +
    ', $context['member']['username'], '
    '; + + if (!isset($context['disabled_fields']['posts'])) + echo ' +
    ', $txt['profile_posts'], ':
    +
    ', $context['member']['posts'], ' (', $context['member']['posts_per_day'], ' ', $txt['posts_per_day'], ')
    '; + + // Only show the email address fully if it's not hidden - and we reveal the email. + if ($context['member']['show_email'] == 'yes') + echo ' +
    ', $txt['email'], ':
    +
    ', $context['member']['email'], '
    '; + + // ... Or if the one looking at the profile is an admin they can see it anyway. + elseif ($context['member']['show_email'] == 'yes_permission_override') + echo ' +
    ', $txt['email'], ':
    +
    ', $context['member']['email'], '
    '; + + if (!empty($modSettings['titlesEnable']) && !empty($context['member']['title'])) + echo ' +
    ', $txt['custom_title'], ':
    +
    ', $context['member']['title'], '
    '; + + if (!empty($context['member']['blurb'])) + echo ' +
    ', $txt['personal_text'], ':
    +
    ', $context['member']['blurb'], '
    '; + + // If karma enabled show the members karma. + if ($modSettings['karmaMode'] == '1') + echo ' +
    ', $modSettings['karmaLabel'], '
    +
    ', ($context['member']['karma']['good'] - $context['member']['karma']['bad']), '
    '; + + elseif ($modSettings['karmaMode'] == '2') + echo ' +
    ', $modSettings['karmaLabel'], '
    +
    +', $context['member']['karma']['good'], '/-', $context['member']['karma']['bad'], '
    '; + + if (!isset($context['disabled_fields']['gender']) && !empty($context['member']['gender']['name'])) + echo ' +
    ', $txt['gender'], ':
    +
    ', $context['member']['gender']['name'], '
    '; + + echo ' +
    ', $txt['age'], ':
    +
    ', $context['member']['age'] . ($context['member']['today_is_birthday'] ? '   ' : ''), '
    '; + + if (!isset($context['disabled_fields']['location']) && !empty($context['member']['location'])) + echo ' +
    ', $txt['location'], ':
    +
    ', $context['member']['location'], '
    '; + + echo ' +
    '; + + // Any custom fields for standard placement? + if (!empty($context['custom_fields'])) + { + $shown = false; + foreach ($context['custom_fields'] as $field) + { + if ($field['placement'] != 0 || empty($field['output_html'])) + continue; + + if (empty($shown)) + { + echo ' +
    '; + $shown = true; + } + + echo ' +
    ', $field['name'], ':
    +
    ', $field['output_html'], '
    '; + } + + if (!empty($shown)) + echo ' +
    '; + } + + echo ' +
    '; + + // Can they view/issue a warning? + if ($context['can_view_warning'] && $context['member']['warning']) + { + echo ' +
    ', $txt['profile_warning_level'], ':
    +
    + ', $context['member']['warning'], '%'; + + // Can we provide information on what this means? + if (!empty($context['warning_status'])) + echo ' + (', $context['warning_status'], ')'; + + echo ' +
    '; + } + + // Is this member requiring activation and/or banned? + if (!empty($context['activate_message']) || !empty($context['member']['bans'])) + { + + // If the person looking at the summary has permission, and the account isn't activated, give the viewer the ability to do it themselves. + if (!empty($context['activate_message'])) + echo ' +
    ', $context['activate_message'], ' (', $context['activate_link_text'], ')
    '; + + // If the current member is banned, show a message and possibly a link to the ban. + if (!empty($context['member']['bans'])) + { + echo ' +
    ', $txt['user_is_banned'], ' [' . $txt['view_ban'] . ']
    + '; + } + } + + echo ' +
    ', $txt['date_registered'], ':
    +
    ', $context['member']['registered'], '
    '; + + // If the person looking is allowed, they can check the members IP address and hostname. + if ($context['can_see_ip']) + { + if (!empty($context['member']['ip'])) + echo ' +
    ', $txt['ip'], ':
    +
    ', $context['member']['ip'], '
    '; + + if (empty($modSettings['disableHostnameLookup']) && !empty($context['member']['ip'])) + echo ' +
    ', $txt['hostname'], ':
    +
    ', $context['member']['hostname'], '
    '; + } + + echo ' +
    ', $txt['local_time'], ':
    +
    ', $context['member']['local_time'], '
    '; + + if (!empty($modSettings['userLanguage']) && !empty($context['member']['language'])) + echo ' +
    ', $txt['language'], ':
    +
    ', $context['member']['language'], '
    '; + + echo ' +
    ', $txt['lastLoggedIn'], ':
    +
    ', $context['member']['last_login'], '
    +
    '; + + // Are there any custom profile fields for the summary? + if (!empty($context['custom_fields'])) + { + $shown = false; + foreach ($context['custom_fields'] as $field) + { + if ($field['placement'] != 2 || empty($field['output_html'])) + continue; + if (empty($shown)) + { + $shown = true; + echo ' +
    +
      '; + } + echo ' +
    • ', $field['output_html'], '
    • '; + } + if ($shown) + echo ' +
    +
    '; + } + + // Show the users signature. + if ($context['signature_enabled'] && !empty($context['member']['signature'])) + echo ' +
    +
    ', $txt['signature'], ':
    + ', $context['member']['signature'], ' +
    '; + + echo ' +
    + +
    +
    +
    +
    '; +} + +// Template for showing all the posts of the user, in chronological order. +function template_showPosts() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + echo ' +
    +

    + ', (!isset($context['attachments']) && empty($context['is_topics']) ? $txt['showMessages'] : (!empty($context['is_topics']) ? $txt['showTopics'] : $txt['showAttachments'])), ' - ', $context['member']['name'], ' +

    +
    +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    '; + + // Button shortcuts + $quote_button = create_button('quote.gif', 'reply_quote', 'quote', 'align="middle"'); + $reply_button = create_button('reply_sm.gif', 'reply', 'reply', 'align="middle"'); + $remove_button = create_button('delete.gif', 'remove_message', 'remove', 'align="middle"'); + $notify_button = create_button('notify_sm.gif', 'notify_replies', 'notify', 'align="middle"'); + + // Are we displaying posts or attachments? + if (!isset($context['attachments'])) + { + // For every post to be displayed, give it its own div, and show the important details of the post. + foreach ($context['posts'] as $post) + { + echo ' +
    +
    + +
    +
    ', $post['counter'], '
    +
    +
    ', $post['board']['name'], ' / ', $post['subject'], '
    + « ', $txt['on'], ': ', $post['time'], ' » +
    +
    '; + + if (!$post['approved']) + echo ' +
    + ', $txt['post_awaiting_approval'], ' +
    '; + + echo ' + ', $post['body'], ' +
    +
    '; + + if ($post['can_reply'] || $post['can_mark_notify'] || $post['can_delete']) + echo ' +
    +
      '; + + // If they *can* reply? + if ($post['can_reply']) + echo ' +
    • ', $txt['reply'], '
    • '; + + // If they *can* quote? + if ($post['can_quote']) + echo ' +
    • ', $txt['quote'], '
    • '; + + // Can we request notification of topics? + if ($post['can_mark_notify']) + echo ' +
    • ', $txt['notify'], '
    • '; + + // How about... even... remove it entirely?! + if ($post['can_delete']) + echo ' +
    • ', $txt['remove'], '
    • '; + + if ($post['can_reply'] || $post['can_mark_notify'] || $post['can_delete']) + echo ' +
    +
    '; + + echo ' +
    + +
    +
    '; + } + } + else + { + echo ' + + + + + + + + + + '; + + // Looks like we need to do all the attachments instead! + $alternate = false; + foreach ($context['attachments'] as $attachment) + { + echo ' + + + + + + '; + $alternate = !$alternate; + } + + // No posts? Just end the table with a informative message. + if ((isset($context['attachments']) && empty($context['attachments'])) || (!isset($context['attachments']) && empty($context['posts']))) + echo ' + + + '; + + echo ' + +
    + + ', $txt['show_attach_filename'], ' + ', ($context['sort_order'] == 'filename' ? '' : ''), ' + + + + ', $txt['show_attach_downloads'], ' + ', ($context['sort_order'] == 'downloads' ? '' : ''), ' + + + + ', $txt['message'], ' + ', ($context['sort_order'] == 'subject' ? '' : ''), ' + + + + ', $txt['show_attach_posted'], ' + ', ($context['sort_order'] == 'posted' ? '' : ''), ' + +
    ', $attachment['filename'], '', !$attachment['approved'] ? ' (' . $txt['awaiting_approval'] . ')' : '', '', $attachment['downloads'], '', $attachment['subject'], '', $attachment['posted'], '
    + ', isset($context['attachments']) ? $txt['show_attachments_none'] : ($context['is_topics'] ? $txt['show_topics_none'] : $txt['show_posts_none']), ' +
    '; + } + // Show more page numbers. + echo ' +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    '; +} + +// Template for showing all the buddies of the current user. +function template_editBuddies() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + echo ' +
    +

    + ', $txt['editBuddies'], ' +

    +
    + + + + + + + + + + + '; + + // If they don't have any buddies don't list them! + if (empty($context['buddies'])) + echo ' + + + '; + + // Now loop through each buddy showing info on each. + $alternate = false; + foreach ($context['buddies'] as $buddy) + { + echo ' + + + + + + + + + + '; + + $alternate = !$alternate; + } + + echo ' +
    ', $txt['name'], '', $txt['status'], '', $txt['email'], '', $txt['icq'], '', $txt['aim'], '', $txt['yim'], '', $txt['msn'], '
    ', $txt['no_buddies'], '
    ', $buddy['link'], '', $buddy['online']['label'], '', ($buddy['show_email'] == 'no' ? '' : '' . $txt['email'] . ''), '', $buddy['icq']['link'], '', $buddy['aim']['link'], '', $buddy['yim']['link'], '', $buddy['msn']['link'], '', $txt['buddy_remove'], '
    '; + + // Add a new buddy? + echo ' +
    +
    +
    +
    +

    ', $txt['buddy_add'], '

    +
    + +
    + + + +
    + +
    +
    + + '; +} + +// Template for showing the ignore list of the current user. +function template_editIgnoreList() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + echo ' +
    +

    + ', $txt['editIgnoreList'], ' +

    +
    + + + + + + + + + + + '; + + // If they don't have anyone on their ignore list, don't list it! + if (empty($context['ignore_list'])) + echo ' + + + '; + + // Now loop through each buddy showing info on each. + $alternate = false; + foreach ($context['ignore_list'] as $member) + { + echo ' + + + + + + + + + + '; + + $alternate = !$alternate; + } + + echo ' +
    ', $txt['name'], '', $txt['status'], '', $txt['email'], '', $txt['icq'], '', $txt['aim'], '', $txt['yim'], '', $txt['msn'], '
    ', $txt['no_ignore'], '
    ', $member['link'], '', $member['online']['label'], '', ($member['show_email'] == 'no' ? '' : '' . $txt['email'] . ''), '', $member['icq']['link'], '', $member['aim']['link'], '', $member['yim']['link'], '', $member['msn']['link'], '', $txt['ignore_remove'], '
    '; + + // Add a new buddy? + echo ' +
    +
    +
    +
    +

    ', $txt['ignore_add'], '

    +
    + +
    + + + +
    + +
    +
    + + '; +} + +// This template shows an admin information on a users IP addresses used and errors attributed to them. +function template_trackActivity() +{ + global $context, $settings, $options, $scripturl, $txt; + + // The first table shows IP information about the user. + echo ' +
    +

    ', $txt['view_ips_by'], ' ', $context['member']['name'], '

    +
    '; + + // The last IP the user used. + echo ' +
    + +
    +
    +
    ', $txt['most_recent_ip'], ': + ', (empty($context['last_ip2']) ? '' : '
    + (' . $txt['why_two_ip_address'] . ')'), ' +
    +
    + ', $context['last_ip'], ''; + + // Second address detected? + if (!empty($context['last_ip2'])) + echo ' + , ', $context['last_ip2'], ''; + + echo ' +
    '; + + // Lists of IP addresses used in messages / error messages. + echo ' +
    ', $txt['ips_in_messages'], ':
    +
    + ', (count($context['ips']) > 0 ? implode(', ', $context['ips']) : '(' . $txt['none'] . ')'), ' +
    +
    ', $txt['ips_in_errors'], ':
    +
    + ', (count($context['ips']) > 0 ? implode(', ', $context['error_ips']) : '(' . $txt['none'] . ')'), ' +
    '; + + // List any members that have used the same IP addresses as the current member. + echo ' +
    ', $txt['members_in_range'], ':
    +
    + ', (count($context['members_in_range']) > 0 ? implode(', ', $context['members_in_range']) : '(' . $txt['none'] . ')'), ' +
    +
    +
    + +
    +
    '; + + // Show the track user list. + template_show_list('track_user_list'); +} + +// The template for trackIP, allowing the admin to see where/who a certain IP has been used. +function template_trackIP() +{ + global $context, $settings, $options, $scripturl, $txt; + + // This function always defaults to the last IP used by a member but can be set to track any IP. + // The first table in the template gives an input box to allow the admin to enter another IP to track. + echo ' +
    +

    ', $txt['trackIP'], '

    +
    +
    + +
    +
    ', $txt['enter_ip'], ':    
    +
    + +
    +
    '; + + // The table inbetween the first and second table shows links to the whois server for every region. + if ($context['single_ip']) + { + echo ' +
    +

    ', $txt['whois_title'], ' ', $context['ip'], '

    +
    +
    + +
    '; + foreach ($context['whois_servers'] as $server) + echo ' + ', $server['name'], '
    '; + echo ' +
    + +
    +
    '; + } + + // The second table lists all the members who have been logged as using this IP address. + echo ' +
    +

    ', $txt['members_from_ip'], ' ', $context['ip'], '

    +
    '; + if (empty($context['ips'])) + echo ' +

    ', $txt['no_members_from_ip'], '

    '; + else + { + echo ' + + + + + + + + '; + + // Loop through each of the members and display them. + foreach ($context['ips'] as $ip => $memberlist) + echo ' + + + + '; + + echo ' + +
    ', $txt['ip_address'], '', $txt['display_name'], '
    ', $ip, '', implode(', ', $memberlist), '
    +
    '; + } + + template_show_list('track_message_list'); + + echo '
    '; + + template_show_list('track_user_list'); +} + +function template_showPermissions() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +

    + ', $txt['showPermissions'], ' +

    +
    '; + + if ($context['member']['has_all_permissions']) + { + echo ' +

    ', $txt['showPermissions_all'], '

    '; + } + else + { + echo ' +

    ',$txt['showPermissions_help'],'

    +
    '; + + if (!empty($context['no_access_boards'])) + { + echo ' +
    +

    ', $txt['showPermissions_restricted_boards'], '

    +
    +
    + +
    ', $txt['showPermissions_restricted_boards_desc'], ':
    '; + foreach ($context['no_access_boards'] as $no_access_board) + echo ' + ', $no_access_board['name'], '', $no_access_board['is_last'] ? '' : ', '; + echo ' +
    + +
    '; + } + + // General Permissions section. + echo ' +
    +
    +

    ', $txt['showPermissions_general'], '

    +
    '; + if (!empty($context['member']['permissions']['general'])) + { + echo ' + + + + + + + + '; + + foreach ($context['member']['permissions']['general'] as $permission) + { + echo ' + + + + '; + } + echo ' + +
    ', $txt['showPermissions_permission'], '', $txt['showPermissions_status'], '
    + ', $permission['is_denied'] ? '' . $permission['name'] . '' : $permission['name'], ' + '; + + if ($permission['is_denied']) + echo ' + ', $txt['showPermissions_denied'], ': ', implode(', ', $permission['groups']['denied']),''; + else + echo ' + ', $txt['showPermissions_given'], ': ', implode(', ', $permission['groups']['allowed']); + + echo ' +
    +

    '; + } + else + echo ' +

    ', $txt['showPermissions_none_general'], '

    '; + + // Board permission section. + echo ' +
    +
    +
    +

    + ', $txt['showPermissions_select'], ': + +

    +
    +
    '; + if (!empty($context['member']['permissions']['board'])) + { + echo ' + + + + + + + + '; + foreach ($context['member']['permissions']['board'] as $permission) + { + echo ' + + + + '; + } + echo ' + +
    ', $txt['showPermissions_permission'], '', $txt['showPermissions_status'], '
    + ', $permission['is_denied'] ? '' . $permission['name'] . '' : $permission['name'], ' + '; + + if ($permission['is_denied']) + { + echo ' + ', $txt['showPermissions_denied'], ': ', implode(', ', $permission['groups']['denied']), ''; + } + else + { + echo ' + ', $txt['showPermissions_given'], ':  ', implode(', ', $permission['groups']['allowed']); + } + echo ' +
    '; + } + else + echo ' +

    ', $txt['showPermissions_none_board'], '

    '; + echo ' +
    +
    '; + } +} + +// Template for user statistics, showing graphs and the like. +function template_statPanel() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + // First, show a few text statistics such as post/topic count. + echo ' +
    +
    +
    +

    + + ', $txt['statPanel_generalStats'], ' - ', $context['member']['name'], ' + +

    +
    +
    + +
    +
    +
    ', $txt['statPanel_total_time_online'], ':
    +
    ', $context['time_logged_in'], '
    +
    ', $txt['statPanel_total_posts'], ':
    +
    ', $context['num_posts'], ' ', $txt['statPanel_posts'], '
    +
    ', $txt['statPanel_total_topics'], ':
    +
    ', $context['num_topics'], ' ', $txt['statPanel_topics'], '
    +
    ', $txt['statPanel_users_polls'], ':
    +
    ', $context['num_polls'], ' ', $txt['statPanel_polls'], '
    +
    ', $txt['statPanel_users_votes'], ':
    +
    ', $context['num_votes'], ' ', $txt['statPanel_votes'], '
    +
    +
    + +
    +
    '; + + // This next section draws a graph showing what times of day they post the most. + echo ' +
    +
    +

    + ', $txt['statPanel_activityTime'], ' +

    +
    +
    + +
    '; + + // If they haven't post at all, don't draw the graph. + if (empty($context['posts_by_time'])) + echo ' + ', $txt['statPanel_noPosts'], ''; + // Otherwise do! + else + { + echo ' +
      '; + + // The labels. + foreach ($context['posts_by_time'] as $time_of_day) + { + echo ' + +
      +
      + ', sprintf($txt['statPanel_activityTime_posts'], $time_of_day['posts'], $time_of_day['posts_percent']), ' +
      +
      + ', $time_of_day['hour_format'], ' + '; + } + + echo ' + +
    '; + } + + echo ' + +
    + +
    +
    '; + + // Two columns with the most popular boards by posts and activity (activity = users posts / total posts). + echo ' +
    +
    +
    +

    + ', $txt['statPanel_topBoards'], ' +

    +
    +
    + +
    '; + + if (empty($context['popular_boards'])) + echo ' + ', $txt['statPanel_noPosts'], ''; + + else + { + echo ' +
    '; + + // Draw a bar for every board. + foreach ($context['popular_boards'] as $board) + { + echo ' +
    ', $board['link'], '
    +
    +
    + ', sprintf($txt['statPanel_topBoards_memberposts'], $board['posts'], $board['total_posts_member'], $board['posts_percent']), ' +
    + ', empty($context['hide_num_posts']) ? $board['posts'] : '', ' +
    '; + } + + echo ' +
    '; + } + echo ' +
    + +
    +
    '; + echo ' +
    +
    +

    + ', $txt['statPanel_topBoardsActivity'], ' +

    +
    +
    + +
    '; + + if (empty($context['board_activity'])) + echo ' + ', $txt['statPanel_noPosts'], ''; + else + { + echo ' +
    '; + + // Draw a bar for every board. + foreach ($context['board_activity'] as $activity) + { + echo ' +
    ', $activity['link'], '
    +
    +
    + ', sprintf($txt['statPanel_topBoards_posts'], $activity['posts'], $activity['total_posts'], $activity['posts_percent']), ' +
    + ', $activity['percent'], '% +
    '; + } + + echo ' +
    '; + } + echo ' +
    + +
    +
    +
    '; + + echo ' +
    +
    '; +} + +// Template for editing profile options. +function template_edit_options() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + // The main header! + echo ' +
    +
    +

    + '; + + // Don't say "Profile" if this isn't the profile... + if (!empty($context['profile_header_text'])) + echo ' + ', $context['profile_header_text']; + else + echo ' + ', $txt['profile']; + + echo ' + +

    +
    '; + + // Have we some description? + if ($context['page_desc']) + echo ' +

    ', $context['page_desc'], '

    '; + + echo ' +
    + +
    '; + + // Any bits at the start? + if (!empty($context['profile_prehtml'])) + echo ' +
    ', $context['profile_prehtml'], '
    '; + + if (!empty($context['profile_fields'])) + echo ' +
    '; + + // Start the big old loop 'of love. + $lastItem = 'hr'; + foreach ($context['profile_fields'] as $key => $field) + { + // We add a little hack to be sure we never get more than one hr in a row! + if ($lastItem == 'hr' && $field['type'] == 'hr') + continue; + + $lastItem = $field['type']; + if ($field['type'] == 'hr') + { + echo ' +
    +
    +
    '; + } + elseif ($field['type'] == 'callback') + { + if (isset($field['callback_func']) && function_exists('template_profile_' . $field['callback_func'])) + { + $callback_func = 'template_profile_' . $field['callback_func']; + $callback_func(); + } + } + else + { + echo ' +
    + ', $field['label'], ''; + + // Does it have any subtext to show? + if (!empty($field['subtext'])) + echo ' +
    + ', $field['subtext'], ''; + + echo ' +
    +
    '; + + // Want to put something infront of the box? + if (!empty($field['preinput'])) + echo ' + ', $field['preinput']; + + // What type of data are we showing? + if ($field['type'] == 'label') + echo ' + ', $field['value']; + + // Maybe it's a text box - very likely! + elseif (in_array($field['type'], array('int', 'float', 'text', 'password'))) + echo ' + '; + + // You "checking" me out? ;) + elseif ($field['type'] == 'check') + echo ' + '; + + // Always fun - select boxes! + elseif ($field['type'] == 'select') + { + echo ' + '; + } + + // Something to end with? + if (!empty($field['postinput'])) + echo ' + ', $field['postinput']; + + echo ' +
    '; + } + } + + if (!empty($context['profile_fields'])) + echo ' +
    '; + + // Are there any custom profile fields - if so print them! + if (!empty($context['custom_fields'])) + { + if ($lastItem != 'hr') + echo ' +
    '; + + echo ' +
    '; + + foreach ($context['custom_fields'] as $field) + { + echo ' +
    + ', $field['name'], ':
    + ', $field['desc'], ' +
    +
    + ', $field['input_html'], ' +
    '; + } + + echo ' +
    '; + + } + + // Any closing HTML? + if (!empty($context['profile_posthtml'])) + echo ' +
    ', $context['profile_posthtml'], '
    '; + elseif ($lastItem != 'hr') + echo ' +
    '; + + // Only show the password box if it's actually needed. + if ($context['require_password']) + echo ' +
    +
    + ', $txt['current_password'], ':
    + ', $txt['required_security_reasons'], ' +
    +
    + +
    +
    '; + + echo ' +
    '; + + // The button shouldn't say "Change profile" unless we're changing the profile... + if (!empty($context['submit_button_text'])) + echo ' + '; + else + echo ' + '; + + echo ' + + + +
    +
    + +
    +
    +
    '; + + // Some javascript! + echo ' + '; + + // Any final spellchecking stuff? + if (!empty($context['show_spellchecking'])) + echo ' +
    '; +} + +// Personal Message settings. +function template_profile_pm_settings() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + echo ' +
    + +
    +
    + +
    +
    + +
    +
    + + +
    + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    '; + +} + +// Template for showing theme settings. Note: template_options() actually adds the theme specific options. +function template_profile_theme_settings() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + echo ' +
    +
    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • '; + + if ($settings['allow_no_censored']) + echo ' +
    • + + +
    • '; + + echo ' +
    • + + +
    • +
    • + + +
    • '; + + if (!empty($modSettings['enable_buddylist'])) + echo ' +
    • + + +
    • '; + + echo ' +
    • + + +
    • '; + + // Choose WYSIWYG settings? + if (empty($modSettings['disable_wysiwyg'])) + echo ' +
    • + + +
    • '; + + if (empty($modSettings['disableCustomPerPage'])) + { + echo ' +
    • + + +
    • +
    • + + +
    • '; + } + + if (!empty($modSettings['cal_enabled'])) + echo ' +
    • + + +
    • '; + + echo ' +
    • + + +
    • +
    • + + +
    • +
    +
    +
    '; +} + +function template_notification() +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings; + + // The main containing header. + echo ' +
    +

    + ', $txt['profile'], ' +

    +
    +

    ', $txt['notification_info'], '

    +
    + +
    +
    '; + + // Allow notification on announcements to be disabled? + if (!empty($modSettings['allow_disableAnnounce'])) + echo ' + +
    '; + + // More notification options. + echo ' + +
    '; + + if (empty($modSettings['disallow_sendBody'])) + echo ' + +
    '; + + echo ' +
    + + +

    + +
    + +
    + + + + +

    +
    +
    + +
    +
    '; + + template_show_list('topic_notification_list'); + + echo ' +
    '; + + template_show_list('board_notification_list'); +} + +// Template for choosing group membership. +function template_groupMembership() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + // The main containing header. + echo ' +
    +
    +

    + ', $txt['profile'], ' +

    +
    +

    ', $txt['groupMembership_info'], '

    '; + + // Do we have an update message? + if (!empty($context['update_message'])) + echo ' +
    + ', $context['update_message'], '. +
    '; + + // Requesting membership to a group? + if (!empty($context['group_request'])) + { + echo ' +
    +
    +

    ', $txt['request_group_membership'], '

    +
    + +
    + ', $txt['request_group_membership_desc'], ': + +
    + + +
    +
    + +
    '; + } + else + { + echo ' + + + + + + + + '; + + $alternate = true; + foreach ($context['groups']['member'] as $group) + { + echo ' + '; + + if ($context['can_edit_primary']) + echo ' + '; + + echo ' + + + '; + $alternate = !$alternate; + } + + echo ' + +
    ', $txt['current_membergroups'], '
    + + + + '; + + // Can they leave their group? + if ($group['can_leave']) + echo ' + ' . $txt['leave_group'] . ''; + echo ' +
    '; + + if ($context['can_edit_primary']) + echo ' +
    + +
    '; + + // Any groups they can join? + if (!empty($context['groups']['available'])) + { + echo ' +
    + + + + + + + + '; + + $alternate = true; + foreach ($context['groups']['available'] as $group) + { + echo ' + + + + '; + $alternate = !$alternate; + } + echo ' + +
    + ', $txt['available_groups'], ' +
    + ', (empty($group['color']) ? $group['name'] : '' . $group['name'] . ''), '', (!empty($group['desc']) ? '
    ' . $group['desc'] . '' : ''), ' +
    '; + + if ($group['type'] == 3) + echo ' + ', $txt['join_group'], ''; + elseif ($group['type'] == 2 && $group['pending']) + echo ' + ', $txt['approval_pending']; + elseif ($group['type'] == 2) + echo ' + ', $txt['request_group'], ''; + + echo ' +
    '; + } + + // Javascript for the selector stuff. + echo ' + '; + } + + echo ' + + +
    '; +} + +function template_ignoreboards() +{ + global $context, $txt, $settings, $scripturl; + // The main containing header. + echo ' + + +
    +
    +

    + ', $txt['profile'], ' +

    +
    +

    ', $txt['ignoreboards_info'], '

    +
    + +
    +
      '; + + $i = 0; + $limit = ceil($context['num_boards'] / 2); + foreach ($context['categories'] as $category) + { + if ($i == $limit) + { + echo ' +
    +
      '; + + $i++; + } + + echo ' +
    • + ', $category['name'], ' +
        '; + + foreach ($category['boards'] as $board) + { + if ($i == $limit) + echo ' +
      +
    • +
    +
      +
    • +
        '; + + echo ' +
      • + +
      • '; + + $i++; + } + + echo ' +
      +
    • '; + } + + echo ' +
    +
    '; + + // Show the standard "Save Settings" profile button. + template_profile_save(); + + echo ' +
    + +
    +
    +
    '; +} + +// Simple load some theme variables common to several warning templates. +function template_load_warning_variables() +{ + global $modSettings, $context; + + $context['warningBarWidth'] = 200; + // Setup the colors - this is a little messy for theming. + $context['colors'] = array( + 0 => 'green', + $modSettings['warning_watch'] => 'darkgreen', + $modSettings['warning_moderate'] => 'orange', + $modSettings['warning_mute'] => 'red', + ); + + // Work out the starting color. + $context['current_color'] = $context['colors'][0]; + foreach ($context['colors'] as $limit => $color) + if ($context['member']['warning'] >= $limit) + $context['current_color'] = $color; +} + +// Show all warnings of a user? +function template_viewWarning() +{ + global $context, $txt, $scripturl, $settings; + + template_load_warning_variables(); + + echo ' +
    +

    + + ', sprintf($txt['profile_viewwarning_for_user'], $context['member']['name']), ' + +

    +
    +
    + +
    +
    +
    + ', $txt['profile_warning_name'], ': +
    +
    + ', $context['member']['name'], ' +
    +
    + ', $txt['profile_warning_level'], ': +
    +
    +
    +
    +
    +
    ', $context['member']['warning'], '%
    +
     
    +
    +
    +
    +
    '; + + // There's some impact of this? + if (!empty($context['level_effects'][$context['current_level']])) + echo ' +
    + ', $txt['profile_viewwarning_impact'], ': +
    +
    + ', $context['level_effects'][$context['current_level']], ' +
    '; + + echo ' +
    +
    + +
    '; + + template_show_list('view_warnings'); +} + +// Show a lovely interface for issuing warnings. +function template_issueWarning() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + template_load_warning_variables(); + + echo ' + '; + + echo ' +
    +
    +

    + + ', $context['user']['is_owner'] ? $txt['profile_warning_level'] : $txt['profile_issue_warning'], ' + +

    +
    '; + + if (!$context['user']['is_owner']) + echo ' +

    ', $txt['profile_warning_desc'], '

    '; + + echo ' +
    + +
    +
    '; + + if (!$context['user']['is_owner']) + echo ' +
    + ', $txt['profile_warning_name'], ': +
    +
    + ', $context['member']['name'], ' +
    '; + + echo ' +
    + ', $txt['profile_warning_level'], ':'; + + // Is there only so much they can apply? + if ($context['warning_limit']) + echo ' +
    ', sprintf($txt['profile_warning_limit_attribute'], $context['warning_limit']), ''; + + echo ' +
    +
    + +
    +  ', $txt['profile_warning_max'], ' +
    ', $txt['profile_warning_impact'], ':
    '; + // For non-javascript give a better list. + foreach ($context['level_effects'] as $limit => $effect) + echo ' + ', sprintf($txt['profile_warning_effect_text'], $limit, $effect), '
    '; + + echo ' +
    +
    +
    '; + + if (!$context['user']['is_owner']) + { + echo ' +
    + ', $txt['profile_warning_reason'], ':
    + ', $txt['profile_warning_reason_desc'], ' +
    +
    + +
    +
    +
    +
    +
    + ', $txt['profile_warning_notify'], ': +
    +
    + +
    +
    + ', $txt['profile_warning_notify_subject'], ': +
    +
    + +
    +
    + ', $txt['profile_warning_notify_body'], ': +
    +
    + +
    + +
    '; + } + echo ' +
    +
    + + +
    +
    + +
    +
    '; + + // Previous warnings? + echo '
    +
    +

    + ', $txt['profile_warning_previous'], ' +

    +
    + + + + + + + + + + '; + + // Print the warnings. + $alternate = 0; + foreach ($context['previous_warnings'] as $warning) + { + $alternate = !$alternate; + echo ' + + + + + + '; + } + + if (empty($context['previous_warnings'])) + echo ' + + + '; + + echo ' + +
    ', $txt['profile_warning_previous_issued'], '', $txt['profile_warning_previous_time'], '', $txt['profile_warning_previous_reason'], '', $txt['profile_warning_previous_level'], '
    ', $warning['issuer']['link'], '', $warning['time'], ' +
    + ', $warning['reason'], ' +
    '; + + if (!empty($warning['id_notice'])) + echo ' +
    + +
    '; + echo ' +
    ', $warning['counter'], '
    + ', $txt['profile_warning_previous_none'], ' +
    +
    ', $txt['pages'], ': ', $context['page_index'], '
    '; + + // Do our best to get pretty javascript enabled. + echo ' + '; +} + +// Template to show for deleting a users account - now with added delete post capability! +function template_deleteAccount() +{ + global $context, $settings, $options, $scripturl, $txt, $scripturl; + + // The main containing header. + echo ' +
    +
    +

    + ', $txt['deleteAccount'], ' +

    +
    '; + // If deleting another account give them a lovely info box. + if (!$context['user']['is_owner']) + echo ' +

    ', $txt['deleteAccount_desc'], '

    '; + echo ' +
    + +
    '; + + // If they are deleting their account AND the admin needs to approve it - give them another piece of info ;) + if ($context['needs_approval']) + echo ' +
    ', $txt['deleteAccount_approval'], '
    '; + + // If the user is deleting their own account warn them first - and require a password! + if ($context['user']['is_owner']) + { + echo ' +
    ', $txt['own_profile_confirm'], '
    +
    + ', $txt['current_password'], ': +      + + + + +
    '; + } + // Otherwise an admin doesn't need to enter a password - but they still get a warning - plus the option to delete lovely posts! + else + { + echo ' +
    ', $txt['deleteAccount_warning'], '
    '; + + // Only actually give these options if they are kind of important. + if ($context['can_delete_posts']) + echo ' +
    + ', $txt['deleteAccount_posts'], ': + +
    '; + + echo ' +
    + +
    +
    + + + + +
    '; + } + echo ' +
    + +
    +
    +
    '; +} + +// Template for the password box/save button stuck at the bottom of every profile page. +function template_profile_save() +{ + global $context, $settings, $options, $txt; + + echo ' + +
    '; + + // Only show the password box if it's actually needed. + if ($context['require_password']) + echo ' +
    +
    + ', $txt['current_password'], ':
    + ', $txt['required_security_reasons'], ' +
    +
    + +
    +
    '; + + echo ' +
    + + + + +
    '; +} + +// Small template for showing an error message upon a save problem in the profile. +function template_error_message() +{ + global $context, $txt; + + echo ' +
    + ', !empty($context['custom_error_title']) ? $context['custom_error_title'] : $txt['profile_errors_occurred'], ': +
      '; + + // Cycle through each error and display an error message. + foreach ($context['post_errors'] as $error) + echo ' +
    • ', isset($txt['profile_error_' . $error]) ? $txt['profile_error_' . $error] : $error, '.
    • '; + + echo ' +
    +
    '; +} + +// Display a load of drop down selectors for allowing the user to change group. +function template_profile_group_manage() +{ + global $context, $txt, $scripturl; + + echo ' +
    + ', $txt['primary_membergroup'], ':
    + (', $txt['moderator_why_missing'], ') +
    +
    + +
    +
    + ', $txt['additional_membergroups'], ': +
    +
    + + '; + // For each membergroup show a checkbox so members can be assigned to more than one group. + foreach ($context['member_groups'] as $member_group) + if ($member_group['can_be_additional']) + echo ' +
    '; + echo ' +
    + + +
    '; + +} + +// Callback function for entering a birthdate! +function template_profile_birthdate() +{ + global $txt, $context; + + // Just show the pretty box! + echo ' +
    + ', $txt['dob'], ':
    + ', $txt['dob_year'], ' - ', $txt['dob_month'], ' - ', $txt['dob_day'], ' +
    +
    + - + - + +
    '; +} + +// Show the signature editing box? +function template_profile_signature_modify() +{ + global $txt, $context, $settings; + + echo ' +
    + ', $txt['signature'], ':
    + ', $txt['sig_info'], '
    +
    '; + + if ($context['show_spellchecking']) + echo ' + '; + + echo ' +
    +
    +
    '; + + // If there is a limit at all! + if (!empty($context['signature_limits']['max_length'])) + echo ' + ', sprintf($txt['max_sig_characters'], $context['signature_limits']['max_length']), ' ', $context['signature_limits']['max_length'], '
    '; + + if ($context['signature_warning']) + echo ' + ', $context['signature_warning'], ''; + + // Load the spell checker? + if ($context['show_spellchecking']) + echo ' + '; + + // Some javascript used to count how many characters have been used so far in the signature. + echo ' + +
    '; +} + +function template_profile_avatar_select() +{ + global $context, $txt, $modSettings; + + // Start with the upper menu + echo ' +
    + ', $txt['personal_picture'], ' +
    + ', !empty($context['member']['avatar']['allow_server_stored']) ? '
    ' : '', ' + ', !empty($context['member']['avatar']['allow_external']) ? '
    ' : '', ' + ', !empty($context['member']['avatar']['allow_upload']) ? '' : '', ' +
    +
    '; + + // If users are allowed to choose avatars stored on the server show selection boxes to choice them from. + if (!empty($context['member']['avatar']['allow_server_stored'])) + { + echo ' +
    +
    + +
    +
    + +
    +
    Do Nothing
    + +
    '; + } + + // If the user can link to an off server avatar, show them a box to input the address. + if (!empty($context['member']['avatar']['allow_external'])) + { + echo ' +
    +
    ', $txt['avatar_by_url'], '
    + +
    '; + } + + // If the user is able to upload avatars to the server show them an upload box. + if (!empty($context['member']['avatar']['allow_upload'])) + { + echo ' +
    + + ', ($context['member']['avatar']['id_attach'] > 0 ? '

    ' : ''), ' +
    '; + } + + echo ' + +
    '; +} + +// Callback for modifying karam. +function template_profile_karma_modify() +{ + global $context, $modSettings, $txt; + + echo ' +
    + ', $modSettings['karmaLabel'], ' +
    +
    + ', $modSettings['karmaApplaudLabel'], ' ', $modSettings['karmaSmiteLabel'], '
    + (', $txt['total'], ': ', ($context['member']['karma']['good'] - $context['member']['karma']['bad']), ') +
    '; +} + +// Select the time format! +function template_profile_timeformat_modify() +{ + global $context, $modSettings, $txt, $scripturl, $settings; + + echo ' +
    + ', $txt['time_format'], ':
    + ', $txt['help'], ' +  ', $txt['date_format'], ' +
    +
    +
    + +
    '; +} + +// Time offset? +function template_profile_timeoffset_modify() +{ + global $txt, $context; + + echo ' +
    + ', $txt['time_offset'], ':
    + ', $txt['personal_time_offset'], ' +
    +
    + ', $txt['timeoffset_autodetect'], '
    ', $txt['current_time'], ': ', $context['current_forum_time'], ' +
    '; +} + +// Theme? +function template_profile_theme_pick() +{ + global $txt, $context, $scripturl; + + echo ' +
    + ', $txt['current_theme'], ': +
    +
    + ', $context['member']['theme']['name'], ' ', $txt['change'], ' +
    '; +} + +// Smiley set picker. +function template_profile_smiley_pick() +{ + global $txt, $context, $modSettings, $settings; + + echo ' +
    + ', $txt['smileys_current'], ': +
    +
    + :) +
    '; +} + +// Change the way you login to the forum. +function template_authentication_method() +{ + global $context, $settings, $options, $scripturl, $modSettings, $txt; + + // The main header! + echo ' + +
    +
    +

    + ', $txt['authentication'], ' +

    +
    +

    ', $txt['change_authentication'], '

    +
    + +
    +
    +
    +  (?)
    + +
    +
    +
    +
    + ', $txt['authenticate_openid_url'], ': +
    +
    + +
    +
    +
    +
    + ', $txt['choose_pass'], ': +
    +
    + + +
    +
    + ', $txt['verify_pass'], ': +
    +
    + + +
    +
    +
    +
    '; + + if ($context['require_password']) + echo ' +
    +
    +
    + ', $txt['current_password'], ':
    + ', $txt['required_security_reasons'], ' +
    +
    + +
    +
    '; + +echo ' +
    + + + + +
    +
    + +
    +
    '; + + // The password stuff. + echo ' + '; +} + +?> \ No newline at end of file diff --git a/Themes/default/Recent.template.php b/Themes/default/Recent.template.php new file mode 100644 index 0000000..900dd7e --- /dev/null +++ b/Themes/default/Recent.template.php @@ -0,0 +1,446 @@ + +
    +

    + ',$txt['recent_posts'],' +

    +
    +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    '; + + foreach ($context['posts'] as $post) + { + echo ' +
    + +
    +
    ', $post['counter'], '
    +
    +
    ', $post['board']['link'], ' / ', $post['link'], '
    + « ', $txt['last_post'], ' ', $txt['by'], ' ', $post['poster']['link'], ' ', $txt['on'], ' ', $post['time'], ' » +
    +
    ', $post['message'], '
    +
    '; + + if ($post['can_reply'] || $post['can_mark_notify'] || $post['can_delete']) + echo ' +
    +
      '; + + // If they *can* reply? + if ($post['can_reply']) + echo ' +
    • ', $txt['reply'], '
    • '; + + // If they *can* quote? + if ($post['can_quote']) + echo ' +
    • ', $txt['quote'], '
    • '; + + // Can we request notification of topics? + if ($post['can_mark_notify']) + echo ' +
    • ', $txt['notify'], '
    • '; + + // How about... even... remove it entirely?! + if ($post['can_delete']) + echo ' +
    • ', $txt['remove'], '
    • '; + + if ($post['can_reply'] || $post['can_mark_notify'] || $post['can_delete']) + echo ' +
    +
    '; + + echo ' + +
    '; + + } + + echo ' +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    + '; +} + +function template_unread() +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings; + + echo ' +
    '; + + $showCheckboxes = !empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1 && $settings['show_mark_read']; + + if ($showCheckboxes) + echo ' +
    + + + '; + + if ($settings['show_mark_read']) + { + // Generate the button strip. + $mark_read = array( + 'markread' => array('text' => !empty($context['no_board_limits']) ? 'mark_as_read' : 'mark_read_short', 'image' => 'markread.gif', 'lang' => true, 'url' => $scripturl . '?action=markasread;sa=' . (!empty($context['no_board_limits']) ? 'all' : 'board' . $context['querystring_board_limits']) . ';' . $context['session_var'] . '=' . $context['session_id']), + ); + + if ($showCheckboxes) + $mark_read['markselectread'] = array( + 'text' => 'quick_mod_markread', + 'image' => 'markselectedread.gif', + 'lang' => true, + 'url' => 'javascript:document.quickModForm.submit();', + ); + } + + if (!empty($context['topics'])) + { + echo ' +
    '; + + if (!empty($mark_read) && !empty($settings['use_tabs'])) + template_button_strip($mark_read, 'right'); + + echo ' + ', $txt['pages'], ': ', $context['page_index'], ' +
    '; + + echo ' +
    + + + + + + '; + + // Show a "select all" box for quick moderation? + if ($showCheckboxes) + echo ' + + '; + else + echo ' + '; + echo ' + + + '; + + foreach ($context['topics'] as $topic) + { + // Calculate the color class of the topic. + $color_class = ''; + if (strpos($topic['class'], 'sticky') !== false) + $color_class = 'stickybg'; + if (strpos($topic['class'], 'locked') !== false) + $color_class .= 'lockedbg'; + + $color_class2 = !empty($color_class) ? $color_class . '2' : ''; + + echo ' + + + + + + '; + + if ($showCheckboxes) + echo ' + '; + echo ' + '; + } + + if (!empty($context['topics']) && !$context['showing_all_topics']) + $mark_read['readall'] = array('text' => 'unread_topics_all', 'image' => 'markreadall.gif', 'lang' => true, 'url' => $scripturl . '?action=unread;all' . $context['querystring_board_limits'], 'active' => true); + + if (empty($settings['use_tabs']) && !empty($mark_read)) + echo ' + + + '; + + if (empty($context['topics'])) + echo ' + '; + + echo ' + +
      + ', $txt['subject'], $context['sort_by'] == 'subject' ? ' ' : '', ' + + ', $txt['replies'], $context['sort_by'] == 'replies' ? ' ' : '', ' + + ', $txt['last_post'], $context['sort_by'] == 'last_post' ? ' ' : '', ' + + + + ', $txt['last_post'], $context['sort_by'] == 'last_post' ? ' ' : '', ' +
    + + + + +
    + ', $topic['is_sticky'] ? '' : '', '', $topic['first_post']['link'], '', $topic['is_sticky'] ? '' : '', ' + ', $txt['new'], ' +

    + ', $txt['started_by'], ' ', $topic['first_post']['member']['link'], ' + ', $txt['in'], ' ', $topic['board']['link'], ' + ', $topic['pages'], ' +

    +
    +
    + ', $topic['replies'], ' ', $txt['replies'], ' +
    + ', $topic['views'], ' ', $txt['views'], ' +
    + ', $txt['last_post'], ' + ', $topic['last_post']['time'], '
    + ', $txt['by'], ' ', $topic['last_post']['member']['link'], ' +
    + +
    + ', template_button_strip($mark_read, 'top'), ' +
    +
    +
    '; + + if (!empty($settings['use_tabs']) && !empty($mark_read)) + template_button_strip($mark_read, 'right'); + + echo ' + ', $txt['pages'], ': ', $context['page_index'], ' +
    '; + } + else + echo ' +
    +

    + ', $context['showing_all_topics'] ? $txt['msg_alert_none'] : $txt['unread_topics_visit_none'], ' +

    +
    '; + + if ($showCheckboxes) + echo ' +
    '; + + echo ' +
    +

    + ', !empty($modSettings['enableParticipation']) ? ' + ' . $txt['participation_caption'] . '
    ' : '', ' + ', $txt['normal_topic'], '
    + ', sprintf($txt['hot_topics'], $modSettings['hotTopicPosts']), '
    + ', sprintf($txt['very_hot_topics'], $modSettings['hotTopicVeryPosts']), ' +

    +

    + ', $txt['locked_topic'], '
    ', ($modSettings['enableStickyTopics'] == '1' ? ' + ' . $txt['sticky_topic'] . '
    ' : ''), ($modSettings['pollMode'] == '1' ? ' + ' . $txt['poll'] : ''), ' +

    +
    +
    '; +} + +function template_replies() +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings; + + echo ' +
    '; + + $showCheckboxes = !empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1 && $settings['show_mark_read']; + + if ($showCheckboxes) + echo ' +
    + + + '; + + if (isset($context['topics_to_mark']) && !empty($settings['show_mark_read'])) + { + // Generate the button strip. + $mark_read = array( + 'markread' => array('text' => 'mark_as_read', 'image' => 'markread.gif', 'lang' => true, 'url' => $scripturl . '?action=markasread;sa=unreadreplies;topics=' . $context['topics_to_mark'] . ';' . $context['session_var'] . '=' . $context['session_id']), + ); + + if ($showCheckboxes) + $mark_read['markselectread'] = array( + 'text' => 'quick_mod_markread', + 'image' => 'markselectedread.gif', + 'lang' => true, + 'url' => 'javascript:document.quickModForm.submit();', + ); + } + + if (!empty($context['topics'])) + { + echo ' +
    '; + + if (!empty($mark_read) && !empty($settings['use_tabs'])) + template_button_strip($mark_read, 'right'); + + echo ' + ', $txt['pages'], ': ', $context['page_index'], ' +
    '; + + echo ' +
    + + + + + + '; + + // Show a "select all" box for quick moderation? + if ($showCheckboxes) + echo ' + + '; + else + echo ' + '; + echo ' + + + '; + + foreach ($context['topics'] as $topic) + { + // Calculate the color class of the topic. + $color_class = ''; + if (strpos($topic['class'], 'sticky') !== false) + $color_class = 'stickybg'; + if (strpos($topic['class'], 'locked') !== false) + $color_class .= 'lockedbg'; + + $color_class2 = !empty($color_class) ? $color_class . '2' : ''; + + echo ' + + + + + + '; + + if ($showCheckboxes) + echo ' + '; + echo ' + '; + } + + if (empty($settings['use_tabs']) && !empty($mark_read)) + echo ' + + + '; + + echo ' + +
      + ', $txt['subject'], $context['sort_by'] === 'subject' ? ' ' : '', ' + + ', $txt['replies'], $context['sort_by'] === 'replies' ? ' ' : '', ' + + ', $txt['last_post'], $context['sort_by'] === 'last_post' ? ' ' : '', ' + + + + ', $txt['last_post'], $context['sort_by'] === 'last_post' ? ' ' : '', ' +
    + + + + +
    + ', $topic['is_sticky'] ? '' : '', '', $topic['first_post']['link'], '', $topic['is_sticky'] ? '' : '', ' + ', $txt['new'], ' +

    + ', $txt['started_by'], ' ', $topic['first_post']['member']['link'], ' + ', $txt['in'], ' ', $topic['board']['link'], ' + ', $topic['pages'], ' +

    +
    +
    + ', $topic['replies'], ' ', $txt['replies'], ' +
    + ', $topic['views'], ' ', $txt['views'], ' +
    + ', $txt['last_post'], ' + ', $topic['last_post']['time'], '
    + ', $txt['by'], ' ', $topic['last_post']['member']['link'], ' +
    + +
    + ', template_button_strip($mark_read, 'top'), ' +
    +
    +
    '; + + if (!empty($settings['use_tabs']) && !empty($mark_read)) + template_button_strip($mark_read, 'right'); + + echo ' + ', $txt['pages'], ': ', $context['page_index'], ' +
    '; + } + else + echo ' +
    +

    + ', $context['showing_all_topics'] ? $txt['msg_alert_none'] : $txt['unread_topics_visit_none'], ' +

    +
    '; + + if ($showCheckboxes) + echo ' +
    '; + + echo ' +
    +

    + ', !empty($modSettings['enableParticipation']) ? ' + ' . $txt['participation_caption'] . '
    ' : '', ' + ', $txt['normal_topic'], '
    + ', sprintf($txt['hot_topics'], $modSettings['hotTopicPosts']), '
    + ', sprintf($txt['very_hot_topics'], $modSettings['hotTopicVeryPosts']), ' +

    +

    + ', $txt['locked_topic'], '
    ', ($modSettings['enableStickyTopics'] == '1' ? ' + ' . $txt['sticky_topic'] . '
    ' : '') . ($modSettings['pollMode'] == '1' ? ' + ' . $txt['poll'] : '') . ' +

    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/Register.template.php b/Themes/default/Register.template.php new file mode 100644 index 0000000..3497cac --- /dev/null +++ b/Themes/default/Register.template.php @@ -0,0 +1,715 @@ + +
    +

    ', $txt['registration_agreement'], '

    +
    + +
    +

    ', $context['agreement'], '

    +
    + +
    '; + + // Age restriction in effect? + if ($context['show_coppa']) + echo ' +

    + '; + else + echo ' + '; + + echo ' +
    + + '; + +} + +// Before registering - get their information. +function template_registration_form() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' + + '; + + // Any errors? + if (!empty($context['registration_errors'])) + { + echo ' +
    + ', $txt['registration_errors_occurred'], ' +
      '; + + // Cycle through each error and display an error message. + foreach ($context['registration_errors'] as $error) + echo ' +
    • ', $error, '
    • '; + + echo ' +
    +
    '; + } + + echo ' +
    +
    +

    ', $txt['registration_form'], '

    +
    +
    +

    ', $txt['required_info'], '

    +
    +
    + +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    +
    + +
    +
    '; + + // If OpenID is enabled, give the user a choice between password and OpenID. + if (!empty($modSettings['enableOpenID'])) + { + echo ' +
    +
    + ', $txt['authenticate_label'], ': + (?) +
    +
    + + +
    +
    '; + } + + echo ' +
    +
    +
    + + +
    +
    +
    +
    +
    + + +
    +
    '; + + // If OpenID is enabled, give the user a choice between password and OpenID. + if (!empty($modSettings['enableOpenID'])) + { + echo ' + +
    +
    ', $txt['authenticate_openid_url'], ':
    +
    + +
    +
    '; + + } + + echo ' +
    + +
    '; + + // If we have either of these, show the extra group. + if (!empty($context['profile_fields']) || !empty($context['custom_fields'])) + { + echo ' +
    +

    ', $txt['additional_information'], '

    +
    +
    + +
    +
    '; + } + + if (!empty($context['profile_fields'])) + { + // Any fields we particularly want? + foreach ($context['profile_fields'] as $key => $field) + { + if ($field['type'] == 'callback') + { + if (isset($field['callback_func']) && function_exists('template_profile_' . $field['callback_func'])) + { + $callback_func = 'template_profile_' . $field['callback_func']; + $callback_func(); + } + } + else + { + echo ' +
    + ', $field['label'], ':'; + + // Does it have any subtext to show? + if (!empty($field['subtext'])) + echo ' + ', $field['subtext'], ''; + + echo ' +
    +
    '; + + // Want to put something infront of the box? + if (!empty($field['preinput'])) + echo ' + ', $field['preinput']; + + // What type of data are we showing? + if ($field['type'] == 'label') + echo ' + ', $field['value']; + + // Maybe it's a text box - very likely! + elseif (in_array($field['type'], array('int', 'float', 'text', 'password'))) + echo ' + '; + + // You "checking" me out? ;) + elseif ($field['type'] == 'check') + echo ' + '; + + // Always fun - select boxes! + elseif ($field['type'] == 'select') + { + echo ' + '; + } + + // Something to end with? + if (!empty($field['postinput'])) + echo ' + ', $field['postinput']; + + echo ' +
    '; + } + } + } + + // Are there any custom fields? + if (!empty($context['custom_fields'])) + { + foreach ($context['custom_fields'] as $field) + echo ' +
    + ', $field['name'], ': + ', $field['desc'], ' +
    +
    ', $field['input_html'], '
    '; + } + + // If we have either of these, close the list like a proper gent. + if (!empty($context['profile_fields']) || !empty($context['custom_fields'])) + { + echo ' +
    +
    + +
    '; + } + + if ($context['visual_verification']) + { + echo ' +
    +

    ', $txt['verification'], '

    +
    +
    + +
    + ', template_control_verification($context['visual_verification_id'], 'all'), ' +
    + +
    '; + } + + echo ' +
    + +
    + +
    + '; +} + +// After registration... all done ;). +function template_after() +{ + global $context, $settings, $options, $txt, $scripturl; + + // Not much to see here, just a quick... "you're now registered!" or what have you. + echo ' +
    +
    +

    ', $context['title'], '

    +
    +
    + +

    ', $context['description'], '

    + +
    +
    '; +} + +// Template for giving instructions about COPPA activation. +function template_coppa() +{ + global $context, $settings, $options, $txt, $scripturl; + + // Formulate a nice complicated message! + echo ' +
    +

    ', $context['page_title'], '

    +
    +
    + +
    +

    ', $context['coppa']['body'], '

    +

    + ', $txt['coppa_form_link_popup'], ' | ', $txt['coppa_form_link_download'], ' +

    +

    ', $context['coppa']['many_options'] ? $txt['coppa_send_to_two_options'] : $txt['coppa_send_to_one_option'], '

    '; + + // Can they send by post? + if (!empty($context['coppa']['post'])) + { + echo ' +

    1) ', $txt['coppa_send_by_post'], '

    +
    + ', $context['coppa']['post'], ' +
    '; + } + + // Can they send by fax?? + if (!empty($context['coppa']['fax'])) + { + echo ' +

    ', !empty($context['coppa']['post']) ? '2' : '1', ') ', $txt['coppa_send_by_fax'], '

    +
    + ', $context['coppa']['fax'], ' +
    '; + } + + // Offer an alternative Phone Number? + if ($context['coppa']['phone']) + { + echo ' +

    ', $context['coppa']['phone'], '

    '; + } + echo ' +
    + +
    '; +} + +// An easily printable form for giving permission to access the forum for a minor. +function template_coppa_form() +{ + global $context, $settings, $options, $txt, $scripturl; + + // Show the form (As best we can) + echo ' + + + + + + + + + + +
    ', $context['forum_contacts'], '
    + ', $txt['coppa_form_address'], ': ', $context['ul'], '
    + ', $context['ul'], '
    + ', $context['ul'], '
    + ', $context['ul'], ' +
    + ', $txt['coppa_form_date'], ': ', $context['ul'], ' +

    +
    + ', $context['coppa_body'], ' +
    +
    '; +} + +// Show a window containing the spoken verification code. +function template_verification_sound() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' + + + + ', $context['page_title'], ' + + + + + + + +'; +} + +function template_admin_register() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    +
    +

    ', $txt['admin_browse_register_new'], '

    +
    +
    + + +
    '; + + if (!empty($context['registration_done'])) + echo ' +
    + ', $context['registration_done'], ' +
    '; + + echo ' +
    +
    + + ', $txt['admin_register_username_desc'], ' +
    +
    + +
    +
    + + ', $txt['admin_register_email_desc'], ' +
    +
    + +
    +
    + + ', $txt['admin_register_password_desc'], ' +
    +
    + +
    '; + + if (!empty($context['member_groups'])) + { + echo ' +
    + + ', $txt['admin_register_group_desc'], ' +
    +
    + +
    '; + } + + echo ' +
    + + ', $txt['admin_register_email_detail_desc'], ' +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + + +
    +
    + + +
    +
    +
    '; +} + +// Form for editing the agreement shown for people registering to the forum. +function template_edit_agreement() +{ + global $context, $settings, $options, $scripturl, $txt; + + // Just a big box to edit the text file ;). + echo ' +
    +

    ', $txt['registration_agreement'], '

    +
    '; + + // Warning for if the file isn't writable. + if (!empty($context['warning'])) + echo ' +

    ', $context['warning'], '

    '; + + echo ' +
    + +
    '; + + // Is there more than one language to choose from? + if (count($context['editable_agreements']) > 1) + { + echo ' +
    +
    + ', $txt['admin_agreement_select_language'], ':  + +
    + + + +
    +
    +
    '; + } + + echo ' +
    '; + + // Show the actual agreement in an oversized text box. + echo ' +

    + +

    +

    + +

    +
    + + + + +
    +
    +
    + +
    +
    '; +} + +function template_edit_reserved_words() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +

    ', $txt['admin_reserved_set'], '

    +
    +
    + +
    +

    ', $txt['admin_reserved_line'], '

    +

    + +

    +
      +
    • +
    • +
    • +
    • +
    +
    + +
    +
    + + + +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/Reminder.template.php b/Themes/default/Reminder.template.php new file mode 100644 index 0000000..e3c44ec --- /dev/null +++ b/Themes/default/Reminder.template.php @@ -0,0 +1,197 @@ + +
    + + +
    '; +} + +function template_reminder_pick() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    + + + +
    '; +} + +function template_sent() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    + '; +} + +function template_set_password() +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings; + + echo ' + +
    +
    + + + + +
    + '; +} + +function template_ask() +{ + global $context, $settings, $options, $txt, $scripturl, $modSettings; + + echo ' + +
    +
    + + + +
    '; + + if ($context['account_type'] == 'password') + echo ' +'; + +} + +?> \ No newline at end of file diff --git a/Themes/default/Reports.template.php b/Themes/default/Reports.template.php new file mode 100644 index 0000000..9d0bd68 --- /dev/null +++ b/Themes/default/Reports.template.php @@ -0,0 +1,255 @@ + +
    +
    +

    ', $txt['generate_reports'], '

    +
    +
    + ', $txt['generate_reports_desc'], ' +
    +
    +

    ', $txt['generate_reports_type'], '

    +
    +
    + +
    +
    '; + + // Go through each type of report they can run. + foreach ($context['report_types'] as $type) + { + echo ' +
    + + +
    '; + if (isset($type['description'])) + echo ' +
    ', $type['description'], '
    '; + } + echo ' +
    +
    + + +
    +
    + +
    +
    + +
    '; +} + +// This is the standard template for showing reports in. +function template_main() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + // Build the reports button array. + $report_buttons = array( + 'generate_reports' => array('text' => 'generate_reports', 'image' => 'print.gif', 'lang' => true, 'url' => $scripturl . '?action=admin;area=reports', 'active' => true), + 'print' => array('text' => 'print', 'image' => 'print.gif', 'lang' => true, 'url' => $scripturl . '?action=admin;area=reports;rt=' . $context['report_type']. ';st=print', 'custom' => 'target="_blank"'), + ); + + echo ' +
    +
    +

    ', $txt['results'], '

    +
    +
    '; + + if (!empty($report_buttons) && !empty($settings['use_tabs'])) + template_button_strip($report_buttons, 'right'); + + echo ' +
    '; + + // Go through each table! + foreach ($context['tables'] as $table) + { + echo ' + '; + + if (!empty($table['title'])) + echo ' + + + + + + '; + + // Now do each row! + $row_number = 0; + $alternate = false; + foreach ($table['data'] as $row) + { + if ($row_number == 0 && !empty($table['shading']['top'])) + echo ' + '; + else + echo ' + '; + + // Now do each column. + $column_number = 0; + + foreach ($row as $key => $data) + { + // If this is a special separator, skip over! + if (!empty($data['separator']) && $column_number == 0) + { + echo ' + '; + break; + } + + // Shaded? + if ($column_number == 0 && !empty($table['shading']['left'])) + echo ' + '; + else + echo ' + '; + + $column_number++; + } + + echo ' + '; + + $row_number++; + $alternate = !$alternate; + } + echo ' + +
    ', $table['title'], '
    + ', $data['v'], ': + + ', $data['v'] == $table['default_value'] ? '' : ($data['v'] . (empty($data['v']) ? '' : ':')), ' + + ', $data['v'], ' +
    '; + } + echo ' +
    +
    '; +} + +// Header of the print page! +function template_print_above() +{ + global $context, $settings, $options, $txt; + + echo ' + + + + ', $context['page_title'], ' + + + '; +} + +function template_print() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + // Go through each table! + foreach ($context['tables'] as $table) + { + echo ' +
    + '; + + if (!empty($table['title'])) + echo ' + + + '; + + // Now do each row! + $alternate = false; + $row_number = 0; + foreach ($table['data'] as $row) + { + if ($row_number == 0 && !empty($table['shading']['top'])) + echo ' + '; + else + echo ' + '; + + // Now do each column!! + $column_number = 0; + foreach ($row as $key => $data) + { + // If this is a special separator, skip over! + if (!empty($data['separator']) && $column_number == 0) + { + echo ' + '; + break; + } + + // Shaded? + if ($column_number == 0 && !empty($table['shading']['left'])) + echo ' + '; + else + echo ' + '; + + $column_number++; + } + + echo ' + '; + + $row_number++; + $alternate = !$alternate; + } + echo ' +
    + ', $table['title'], ' +
    + ', $data['v'], ': + + ', $data['v'] == $table['default_value'] ? '' : ($data['v'] . (empty($data['v']) ? '' : ':')), ' + + ', $data['v'], ' +
    +

    '; + } +} + +// Footer of the print page. +function template_print_below() +{ + global $context, $settings, $options; + + echo ' + + +'; +} + +?> \ No newline at end of file diff --git a/Themes/default/Search.template.php b/Themes/default/Search.template.php new file mode 100644 index 0000000..9f9a1d1 --- /dev/null +++ b/Themes/default/Search.template.php @@ -0,0 +1,519 @@ + +
    +

    + ', !empty($settings['use_buttons']) ? '' : ' ', $txt['set_parameters'], ' +

    +
    '; + + if (!empty($context['search_errors'])) + echo ' +

    ', implode('
    ', $context['search_errors']['messages']), '

    '; + + // Simple Search? + if ($context['simple_search']) + { + echo ' + '; + } + + // Advanced search! + else + { + echo ' + '; + + if (empty($context['search_params']['topic'])) + { + echo ' +
    + +
    + + +
    '; + + echo ' +
    + + + +
    +
    +
    + +
    '; + } + + } + + echo ' + + + '; +} + +function template_results() +{ + global $context, $settings, $options, $txt, $scripturl, $message; + + if (isset($context['did_you_mean']) || empty($context['topics'])) + { + echo ' +
    +
    +

    + ', $txt['search_adjust_query'], ' +

    +
    + +
    '; + + // Did they make any typos or mistakes, perhaps? + if (isset($context['did_you_mean'])) + echo ' +

    ', $txt['search_did_you_mean'], ' ', $context['did_you_mean'], '.

    '; + + echo ' +
    + ', $txt['search_for'], ': + + + + + + + + + '; + + if (!empty($context['search_params']['brd'])) + foreach ($context['search_params']['brd'] as $board_id) + echo ' + '; + + echo ' +
    +
    + +

    '; + } + + if ($context['compact']) + { + // Quick moderation set to checkboxes? Oh, how fun :/. + if (!empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1) + echo ' +
    '; + + echo ' +
    +

    + '; + if (!empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1) + echo ' + '; + echo ' + +  ', $txt['mlist_search_results'],': ',$context['search_params']['search'],' +

    +
    +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    '; + + while ($topic = $context['get_topics']()) + { + $color_class = ''; + if ($topic['is_sticky']) + $color_class = 'stickybg'; + if ($topic['is_locked']) + $color_class .= 'lockedbg'; + + echo ' +
    +
    + +
    '; + + foreach ($topic['matches'] as $message) + { + echo ' +
    +
    ', $message['counter'], '
    +
    ', $topic['board']['link'], ' / ', $message['subject_highlighted'], '
    + « ',$txt['by'],' ', $message['member']['link'], ' ',$txt['on'],' ', $message['time'], ' » +
    '; + + if (!empty($options['display_quick_mod'])) + { + echo ' +
    '; + + if ($options['display_quick_mod'] == 1) + { + echo ' + '; + } + else + { + if ($topic['quick_mod']['remove']) + echo ' + ', $txt['remove_topic'], ''; + + if ($topic['quick_mod']['lock']) + echo ' + ', $txt['set_lock'], ''; + + if ($topic['quick_mod']['lock'] || $topic['quick_mod']['remove']) + echo ' +
    '; + + if ($topic['quick_mod']['sticky']) + echo ' + ', $txt['set_sticky'], ''; + + if ($topic['quick_mod']['move']) + echo ' + ', $txt['move_topic'], ''; + } + + echo ' +
    '; + } + + if ($message['body_highlighted'] != '') + echo ' +
    +
    ', $message['body_highlighted'], '
    '; + } + + echo ' +
    + +
    +
    '; + + } + if (!empty($context['topics'])) + echo ' +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    '; + + if (!empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1 && !empty($context['topics'])) + { + echo ' +
    +
    + '; + + if ($context['can_move']) + { + echo ' + '; + } + + echo ' + + +
    +
    +
    '; + } + + + if (!empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1 && !empty($context['topics'])) + echo ' + +
    '; + + } + else + { + echo ' +
    +

    +  ', $txt['mlist_search_results'],': ',$context['search_params']['search'],' +

    +
    +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    '; + + if (empty($context['topics'])) + echo ' +
    (', $txt['search_no_results'], ')
    '; + + while ($topic = $context['get_topics']()) + { + foreach ($topic['matches'] as $message) + { + echo ' +
    +
    + +
    +
    ', $message['counter'], '
    +
    +
    ', $topic['board']['link'], ' / ', $message['subject_highlighted'], '
    + « ', $txt['message'], ' ', $txt['by'], ' ', $message['member']['link'], ' ', $txt['on'], ' ', $message['time'], ' » +
    +
    ', $message['body_highlighted'], '
    '; + + if ($topic['can_reply'] || $topic['can_mark_notify']) + echo ' +
    +
      '; + + // If they *can* reply? + if ($topic['can_reply']) + echo ' +
    • ', $txt['reply'], '
    • '; + + // If they *can* quote? + if ($topic['can_quote']) + echo ' +
    • ', $txt['quote'], '
    • '; + + // Can we request notification of topics? + if ($topic['can_mark_notify']) + echo ' +
    • ', $txt['notify'], '
    • '; + + if ($topic['can_reply'] || $topic['can_mark_notify']) + echo ' +
    +
    '; + echo ' +
    +
    + +
    +
    '; + } + } + + echo ' +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    '; + } + + // Show a jump to box for easy navigation. + echo ' +
    +
     
    + '; + +} + +?> \ No newline at end of file diff --git a/Themes/default/SendTopic.template.php b/Themes/default/SendTopic.template.php new file mode 100644 index 0000000..e4e2616 --- /dev/null +++ b/Themes/default/SendTopic.template.php @@ -0,0 +1,280 @@ + +
    +
    +

    + ', $context['page_title'], ' +

    +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    + +
    + +
    '; +} + +// Send an email to a user! +function template_custom_email() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +
    +

    + ', $context['page_title'], ' +

    +
    +
    + +
    +
    +
    + ', $txt['sendtopic_receiver_name'], ': +
    +
    + ', $context['recipient']['link'], ' +
    '; + + // Can the user see the persons email? + if ($context['can_view_receipient_email']) + echo ' +
    + ', $txt['sendtopic_receiver_email'], ': +
    +
    + ', $context['recipient']['email_link'], ' +
    +
    +
    +
    '; + + // If it's a guest we need their details. + if ($context['user']['is_guest']) + echo ' +
    + +
    +
    + +
    +
    +
    + ', $txt['send_email_disclosed'], ' +
    +
    + + '; + // Otherwise show the user that we know their email. + else + echo ' +
    + ', $txt['sendtopic_sender_email'], ':
    + ', $txt['send_email_disclosed'], ' +
    +
    + ', $context['user']['email'], ' +
    '; + + echo ' +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + +
    +
    + +
    '; + + foreach ($context['form_hidden_vars'] as $key => $value) + echo ' + '; + + echo ' + +
    +
    +
    '; +} + +function template_report() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    + +
    +

    ', $txt['report_to_mod'], '

    +
    +
    + +
    '; + + if (!empty($context['post_errors'])) + { + echo ' +
    +
      '; + + foreach ($context['post_errors'] as $error) + echo ' +
    • ', $error, '
    • '; + + echo ' +
    +
    '; + } + + echo ' +

    ', $txt['report_to_mod_func'], '

    +
    +
    '; + + if ($context['user']['is_guest']) + { + echo ' +
    + : +
    +
    + +
    '; + } + + echo ' +
    + : +
    +
    + +
    '; + + if ($context['require_verification']) + { + echo ' +
    + ', $txt['verification'], ': +
    +
    + ', template_control_verification($context['visual_verification_id'], 'all'), ' +
    '; + } + + echo ' +
    +
    + +
    +
    + +
    + +
    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/Settings.template.php b/Themes/default/Settings.template.php new file mode 100644 index 0000000..3126fa0 --- /dev/null +++ b/Themes/default/Settings.template.php @@ -0,0 +1,278 @@ + 'show_board_desc', + 'label' => $txt['board_desc_inside'], + 'default' => true, + ), + array( + 'id' => 'show_children', + 'label' => $txt['show_children'], + 'default' => true, + ), + array( + 'id' => 'use_sidebar_menu', + 'label' => $txt['use_sidebar_menu'], + 'default' => true, + ), + array( + 'id' => 'show_no_avatars', + 'label' => $txt['show_no_avatars'], + 'default' => true, + ), + array( + 'id' => 'show_no_signatures', + 'label' => $txt['show_no_signatures'], + 'default' => true, + ), + array( + 'id' => 'show_no_censored', + 'label' => $txt['show_no_censored'], + 'default' => true, + ), + array( + 'id' => 'return_to_post', + 'label' => $txt['return_to_post'], + 'default' => true, + ), + array( + 'id' => 'no_new_reply_warning', + 'label' => $txt['no_new_reply_warning'], + 'default' => true, + ), + array( + 'id' => 'view_newest_first', + 'label' => $txt['recent_posts_at_top'], + 'default' => true, + ), + array( + 'id' => 'view_newest_pm_first', + 'label' => $txt['recent_pms_at_top'], + 'default' => true, + ), + array( + 'id' => 'posts_apply_ignore_list', + 'label' => $txt['posts_apply_ignore_list'], + 'default' => false, + ), + array( + 'id' => 'wysiwyg_default', + 'label' => $txt['wysiwyg_default'], + 'default' => false, + ), + array( + 'id' => 'popup_messages', + 'label' => $txt['popup_messages'], + 'default' => true, + ), + array( + 'id' => 'copy_to_outbox', + 'label' => $txt['copy_to_outbox'], + 'default' => true, + ), + array( + 'id' => 'pm_remove_inbox_label', + 'label' => $txt['pm_remove_inbox_label'], + 'default' => true, + ), + array( + 'id' => 'auto_notify', + 'label' => $txt['auto_notify'], + 'default' => true, + ), + array( + 'id' => 'topics_per_page', + 'label' => $txt['topics_per_page'], + 'options' => array( + 0 => $txt['per_page_default'], + 5 => 5, + 10 => 10, + 25 => 25, + 50 => 50, + ), + 'default' => true, + ), + array( + 'id' => 'messages_per_page', + 'label' => $txt['messages_per_page'], + 'options' => array( + 0 => $txt['per_page_default'], + 5 => 5, + 10 => 10, + 25 => 25, + 50 => 50, + ), + 'default' => true, + ), + array( + 'id' => 'calendar_start_day', + 'label' => $txt['calendar_start_day'], + 'options' => array( + 0 => $txt['days'][0], + 1 => $txt['days'][1], + 6 => $txt['days'][6], + ), + 'default' => true, + ), + array( + 'id' => 'display_quick_reply', + 'label' => $txt['display_quick_reply'], + 'options' => array( + 0 => $txt['display_quick_reply1'], + 1 => $txt['display_quick_reply2'], + 2 => $txt['display_quick_reply3'] + ), + 'default' => true, + ), + array( + 'id' => 'display_quick_mod', + 'label' => $txt['display_quick_mod'], + 'options' => array( + 0 => $txt['display_quick_mod_none'], + 1 => $txt['display_quick_mod_check'], + 2 => $txt['display_quick_mod_image'], + ), + 'default' => true, + ), + ); +} + +function template_settings() +{ + global $context, $settings, $options, $scripturl, $txt; + + $context['theme_settings'] = array( + array( + 'id' => 'header_logo_url', + 'label' => $txt['header_logo_url'], + 'description' => $txt['header_logo_url_desc'], + 'type' => 'text', + ), + array( + 'id' => 'site_slogan', + 'label' => $txt['site_slogan'], + 'description' => $txt['site_slogan_desc'], + 'type' => 'text', + ), + array( + 'id' => 'smiley_sets_default', + 'label' => $txt['smileys_default_set_for_theme'], + 'options' => $context['smiley_sets'], + 'type' => 'text', + ), + array( + 'id' => 'forum_width', + 'label' => $txt['forum_width'], + 'description' => $txt['forum_width_desc'], + 'type' => 'text', + 'size' => 8, + ), + '', + array( + 'id' => 'linktree_link', + 'label' => $txt['current_pos_text_img'], + ), + array( + 'id' => 'show_mark_read', + 'label' => $txt['enable_mark_as_read'], + ), + array( + 'id' => 'allow_no_censored', + 'label' => $txt['allow_no_censored'], + ), + array( + 'id' => 'enable_news', + 'label' => $txt['enable_random_news'], + ), + '', + array( + 'id' => 'show_newsfader', + 'label' => $txt['news_fader'], + ), + array( + 'id' => 'newsfader_time', + 'label' => $txt['admin_fader_delay'], + 'type' => 'number', + ), + array( + 'id' => 'number_recent_posts', + 'label' => $txt['number_recent_posts'], + 'description' => $txt['number_recent_posts_desc'], + 'type' => 'number', + ), + array( + 'id' => 'show_stats_index', + 'label' => $txt['show_stats_index'], + ), + array( + 'id' => 'show_latest_member', + 'label' => $txt['latest_members'], + ), + array( + 'id' => 'show_group_key', + 'label' => $txt['show_group_key'], + ), + array( + 'id' => 'display_who_viewing', + 'label' => $txt['who_display_viewing'], + 'options' => array( + 0 => $txt['who_display_viewing_off'], + 1 => $txt['who_display_viewing_numbers'], + 2 => $txt['who_display_viewing_names'], + ), + 'type' => 'number', + ), + '', + array( + 'id' => 'show_modify', + 'label' => $txt['last_modification'], + ), + array( + 'id' => 'show_profile_buttons', + 'label' => $txt['show_view_profile_button'], + ), + array( + 'id' => 'show_user_images', + 'label' => $txt['user_avatars'], + ), + array( + 'id' => 'show_blurb', + 'label' => $txt['user_text'], + ), + array( + 'id' => 'show_gender', + 'label' => $txt['gender_images'], + ), + array( + 'id' => 'hide_post_group', + 'label' => $txt['hide_post_group'], + 'description' => $txt['hide_post_group_desc'], + ), + '', + array( + 'id' => 'show_bbc', + 'label' => $txt['admin_bbc'], + ), + array( + 'id' => 'additional_options_collapsable', + 'label' => $txt['additional_options_collapsable'], + ), + ); +} + +?> \ No newline at end of file diff --git a/Themes/default/SplitTopics.template.php b/Themes/default/SplitTopics.template.php new file mode 100644 index 0000000..523f93f --- /dev/null +++ b/Themes/default/SplitTopics.template.php @@ -0,0 +1,486 @@ + +
    + +
    +

    ', $txt['split'], '

    +
    +
    + +
    +

    + : + +

    +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    +
    + +
    +
    + +
    + +
    + '; +} + +function template_main() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +

    ', $txt['split'], '

    +
    +
    + +
    +

    ', $txt['split_successful'], '

    + +
    + +
    +
    '; +} + +function template_select() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +
    +
    +

    ', $txt['split'], ' - ', $txt['select_split_posts'], '

    +
    +
    + ', $txt['please_select_split'], ' +
    +
    + ', $txt['pages'], ': ', $context['not_selected']['page_index'], ' +
    +
      '; + + foreach ($context['not_selected']['messages'] as $message) + echo ' +
    • + +
      +
      + -> + ', $message['subject'], ' ', $txt['by'], ' ', $message['poster'], '
      + ', $message['time'], ' +
      +
      ', $message['body'], '
      +
      + +
    • '; + + echo ' +
    • +
    +
    +
    +
    +

    + ', $txt['split_selected_posts'], ' (', $txt['split_reset_selection'], ') +

    +
    +
    + ', $txt['split_selected_posts_desc'], ' +
    +
    + ', $txt['pages'], ': ', $context['selected']['page_index'], ' +
    +
      '; + + if (!empty($context['selected']['messages'])) + foreach ($context['selected']['messages'] as $message) + echo ' +
    • + +
      +
      + <- + ', $message['subject'], ' ', $txt['by'], ' ', $message['poster'], '
      + ', $message['time'], ' +
      +
      ', $message['body'], '
      +
      + +
    • '; + + echo ' +
    • +
    +
    +
    +

    + + + + +

    +
    +
    +
    + '; +} + +function template_merge_done() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +

    ', $txt['merge'], '

    +
    +
    + +
    +

    ', $txt['merge_successful'], '

    +
    + +
    + +
    +
    +
    '; +} + +function template_merge() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +

    ', $txt['merge'], '

    +
    +
    + ', $txt['merge_desc'], ' +
    +
    + +
    +
    +
    + ', $txt['topic_to_merge'], ': +
    +
    + ', $context['origin_subject'], ' +
    '; + + if (!empty($context['boards']) && count($context['boards']) > 1) + { + echo ' +
    + ', $txt['target_board'], ': +
    +
    +
    + + + +
    +
    '; + } + + echo ' +
    +
    +
    +
    + ', $txt['merge_to_topic_id'], ': +
    +
    +
    + + + + +
    +
    '; + + echo ' +
    +
    + +

    +
    +

    ', $txt['target_topic'], '

    +
    +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    +
    + +
    +
      '; + + $merge_button = create_button('merge.gif', 'merge', ''); + + foreach ($context['topics'] as $topic) + echo ' +
    • + ', $merge_button, '  + ', $topic['subject'], ' ', $txt['started_by'], ' ', $topic['poster']['link'], ' +
    • '; + + echo ' +
    +
    + +
    +
    + ', $txt['pages'], ': ', $context['page_index'], ' +
    +
    +
    '; +} + +function template_merge_extra_options() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +
    +
    +
    +

    ', $txt['merge_topic_list'], '

    +
    + + + + + + + + + + + '; + foreach ($context['topics'] as $topic) + echo ' + + + + + + + '; + echo ' + +
    ', $txt['merge_check'], '', $txt['subject'], '', $txt['started_by'], '', $txt['last_post'], '' . $txt['merge_include_notifications'] . '
    + + + ' . $topic['subject'] . ' + + ', $topic['started']['link'], '
    + ', $topic['started']['time'], ' +
    + ' . $topic['updated']['link'] . '
    + ', $topic['updated']['time'], ' +
    + +
    +
    +
    + +
    '; + + echo ' +
    + ', $txt['merge_select_subject'], ' + +
    +
    + +
    '; + + if (!empty($context['boards']) && count($context['boards']) > 1) + { + echo ' +
    + ', $txt['merge_select_target_board'], ' +
      '; + foreach ($context['boards'] as $board) + echo ' +
    • + ' . $board['name'] . ' +
    • '; + echo ' +
    +
    '; + } + if (!empty($context['polls'])) + { + echo ' +
    + ' . $txt['merge_select_poll'] . ' +
      '; + foreach ($context['polls'] as $poll) + echo ' +
    • + ' . $poll['question'] . ' (' . $txt['topic'] . ': ' . $poll['topic']['subject'] . ') +
    • '; + echo ' +
    • + (' . $txt['merge_no_poll'] . ') +
    • +
    +
    '; + } + echo ' + + +
    +
    + +
    +
    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/Stats.template.php b/Themes/default/Stats.template.php new file mode 100644 index 0000000..2867937 --- /dev/null +++ b/Themes/default/Stats.template.php @@ -0,0 +1,473 @@ + +
    +

    ', $context['page_title'], '

    +
    +
    +

    + + ', $txt['general_stats'], ' + +

    +
    +
    +
    +
    + +
    +
    +
    ', $txt['total_members'], ':
    +
    ', $context['show_member_list'] ? '' . $context['num_members'] . '' : $context['num_members'], '
    +
    ', $txt['total_posts'], ':
    +
    ', $context['num_posts'], '
    +
    ', $txt['total_topics'], ':
    +
    ', $context['num_topics'], '
    +
    ', $txt['total_cats'], ':
    +
    ', $context['num_categories'], '
    +
    ', $txt['users_online'], ':
    +
    ', $context['users_online'], '
    +
    ', $txt['most_online'], ':
    +
    ', $context['most_members_online']['number'], ' - ', $context['most_members_online']['date'], '
    +
    ', $txt['users_online_today'], ':
    +
    ', $context['online_today'], '
    '; + + if (!empty($modSettings['hitStats'])) + echo ' +
    ', $txt['num_hits'], ':
    +
    ', $context['num_hits'], '
    '; + + echo ' +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    ', $txt['average_members'], ':
    +
    ', $context['average_members'], '
    +
    ', $txt['average_posts'], ':
    +
    ', $context['average_posts'], '
    +
    ', $txt['average_topics'], ':
    +
    ', $context['average_topics'], '
    +
    ', $txt['total_boards'], ':
    +
    ', $context['num_boards'], '
    +
    ', $txt['latest_member'], ':
    +
    ', $context['common_stats']['latest_member']['link'], '
    +
    ', $txt['average_online'], ':
    +
    ', $context['average_online'], '
    +
    ', $txt['gender_ratio'], ':
    +
    ', $context['gender']['ratio'], '
    '; + + if (!empty($modSettings['hitStats'])) + echo ' +
    ', $txt['average_hits'], ':
    +
    ', $context['average_hits'], '
    '; + + echo ' +
    +
    +
    + +
    +
    +
    +
    +
    +
    +

    + + ', $txt['top_posters'], ' + +

    +
    +
    + +
    +
    '; + + foreach ($context['top_posters'] as $poster) + { + echo ' +
    + ', $poster['link'], ' +
    +
    '; + + if (!empty($poster['post_percent'])) + echo ' +
    +
    +
    '; + + echo ' + ', $poster['num_posts'], ' +
    '; + } + + echo ' +
    +
    +
    + +
    +
    +
    +
    +

    + + ', $txt['top_boards'], ' + +

    +
    +
    + +
    +
    '; + + foreach ($context['top_boards'] as $board) + { + echo ' +
    + ', $board['link'], ' +
    +
    '; + + if (!empty($board['post_percent'])) + echo ' +
    +
    +
    '; + echo ' + ', $board['num_posts'], ' +
    '; + } + + echo ' +
    +
    +
    + +
    +
    +
    +
    +
    +
    +

    + + ', $txt['top_topics_replies'], ' + +

    +
    +
    + +
    +
    '; + + foreach ($context['top_topics_replies'] as $topic) + { + echo ' +
    + ', $topic['link'], ' +
    +
    '; + if (!empty($topic['post_percent'])) + echo ' +
    +
    +
    '; + + echo ' + ' . $topic['num_replies'] . ' +
    '; + } + echo ' +
    +
    +
    + +
    +
    + +
    +
    +

    + + ', $txt['top_topics_views'], ' + +

    +
    +
    + +
    +
    '; + + foreach ($context['top_topics_views'] as $topic) + { + echo ' +
    ', $topic['link'], '
    +
    '; + + if (!empty($topic['post_percent'])) + echo ' +
    +
    +
    '; + + echo ' + ' . $topic['num_views'] . ' +
    '; + } + + echo ' +
    +
    +
    + +
    +
    +
    +
    +
    +
    +

    + + ', $txt['top_starters'], ' + +

    +
    +
    + +
    +
    '; + + foreach ($context['top_starters'] as $poster) + { + echo ' +
    + ', $poster['link'], ' +
    +
    '; + + if (!empty($poster['post_percent'])) + echo ' +
    +
    +
    '; + + echo ' + ', $poster['num_topics'], ' +
    '; + } + + echo ' +
    +
    +
    + +
    +
    +
    +
    +

    + + ', $txt['most_time_online'], ' + +

    +
    +
    + +
    +
    '; + + foreach ($context['top_time_online'] as $poster) + { + echo ' +
    + ', $poster['link'], ' +
    +
    '; + + if (!empty($poster['time_percent'])) + echo ' +
    +
    +
    '; + + echo ' + ', $poster['time_online'], ' +
    '; + } + + echo ' +
    +
    +
    + +
    +
    +
    +
    +
    +
    +

    + + ', $txt['forum_history'], ' + +

    +
    '; + + if (!empty($context['yearly'])) + { + echo ' + + + + + + + + ', $txt['smf_stats_14'], ''; + + if (!empty($modSettings['hitStats'])) + echo ' + '; + + echo ' + + + '; + + foreach ($context['yearly'] as $id => $year) + { + echo ' + + + + + + '; + + if (!empty($modSettings['hitStats'])) + echo ' + '; + + echo ' + '; + + foreach ($year['months'] as $month) + { + echo ' + + + + + + '; + + if (!empty($modSettings['hitStats'])) + echo ' + '; + + echo ' + '; + + if ($month['expanded']) + { + foreach ($month['days'] as $day) + { + echo ' + + + + + + '; + + if (!empty($modSettings['hitStats'])) + echo ' + '; + + echo ' + '; + } + } + } + } + + echo ' + +
    ', $txt['yearly_summary'], '', $txt['stats_new_topics'], '', $txt['stats_new_posts'], '', $txt['stats_new_members'], '', $txt['page_views'], '
    + * ', $year['year'], ' + ', $year['new_topics'], '', $year['new_posts'], '', $year['new_members'], '', $year['most_members_online'], '', $year['hits'], '
    + ', $month['month'], ' ', $month['year'], ' + ', $month['new_topics'], '', $month['new_posts'], '', $month['new_members'], '', $month['most_members_online'], '', $month['hits'], '
    ', $day['year'], '-', $day['month'], '-', $day['day'], '', $day['new_topics'], '', $day['new_posts'], '', $day['new_members'], '', $day['most_members_online'], '', $day['hits'], '
    +
    + + + '; + } +} + +?> \ No newline at end of file diff --git a/Themes/default/Themes.template.php b/Themes/default/Themes.template.php new file mode 100644 index 0000000..4bdd47e --- /dev/null +++ b/Themes/default/Themes.template.php @@ -0,0 +1,1180 @@ + +
    + +
    +

    + ', $txt['help'], ' + ', $txt['themeadmin_title'], ' + +

    +
    +
    + ', $txt['themeadmin_explain'], ' +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    + : +
    +
    +
    '; + foreach ($context['themes'] as $theme) + echo ' +
    '; + + echo ' +
    + + +
    +
    + +
    +
    + + ', $txt['theme_select'], ' +
    +
    + : +
    +
    + + ', $txt['theme_select'], ' +
    +
    +
    + +
    +
    + +
    + +
    '; + + // Link to simplemachines.org for latest themes and info! + echo ' +
    +
    +

    + ', $txt['help'], ' ', $txt['theme_latest'], ' +

    +
    +
    + +
    +
    + ', $txt['theme_latest_fetch'], ' +
    +
    + +
    +
    '; + + // Warn them if theme creation isn't possible! + if (!$context['can_create_new']) + echo ' +
    ', $txt['theme_install_writable'], '
    '; + + echo ' +
    +
    +

    + ', $txt['help'], ' ', $txt['theme_install'], ' +

    +
    +
    + +
    +
    '; + + // Here's a little box for installing a new theme. + // !!! Should the value="theme_gz" be there?! + if ($context['can_create_new']) + echo ' +
    + : +
    +
    + +
    '; + + echo ' +
    + : +
    +
    + +
    '; + + if ($context['can_create_new']) + echo ' +
    + +
    +
    + +
    '; + + echo ' +
    +
    + +
    +
    + +
    + +
    + +
    + + '; + + if (empty($modSettings['disable_smf_js'])) + echo ' + '; + + echo ' + '; + + // Gotta love IE4, and its hatefulness... + if ($context['browser']['is_ie4']) + echo ' + '; + else + echo ' + '; +} + +function template_list_themes() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    +

    ', $txt['themeadmin_list_heading'], '

    +
    +
    + ', $txt['themeadmin_list_tip'], ' +
    '; + + // Show each theme.... with X for delete and a link to settings. + foreach ($context['themes'] as $theme) + { + echo ' +
    +

    + ', $theme['name'], '', !empty($theme['version']) ? ' (' . $theme['version'] . ')' : '', ''; + + // You *cannot* delete the default theme. It's important! + if ($theme['id'] != 1) + echo ' + ', $txt['theme_remove'], ''; + + echo ' +

    +
    +
    + +
    +
    +
    ', $txt['themeadmin_list_theme_dir'], ':
    + ', $theme['theme_dir'], $theme['valid_path'] ? '' : ' ' . $txt['themeadmin_list_invalid'], ' +
    ', $txt['themeadmin_list_theme_url'], ':
    +
    ', $theme['theme_url'], '
    +
    ', $txt['themeadmin_list_images_url'], ':
    +
    ', $theme['images_url'], '
    +
    +
    + +
    '; + } + + echo ' + +
    +
    +

    ', $txt['themeadmin_list_reset'], '

    +
    +
    + +
    +
    +
    + : +
    +
    + +
    +
    + : +
    +
    + +
    +
    + + +
    + +
    + +
    +
    +
    '; +} + +function template_reset_list() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    +

    ', $txt['themeadmin_reset_title'], '

    +
    +
    + ', $txt['themeadmin_reset_tip'], ' +
    '; + + // Show each theme.... with X for delete and a link to settings. + $alternate = false; + + foreach ($context['themes'] as $theme) + { + $alternate = !$alternate; + + echo ' +
    +

    ', $theme['name'], '

    +
    +
    + +
    + +
    + +
    '; + } + + echo ' +
    +
    '; +} + +function template_set_options() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    + +
    +

    ', $txt['theme_options_title'], ' - ', $context['theme_settings']['name'], '

    +
    +
    + ', $context['theme_options_reset'] ? $txt['themeadmin_reset_options_info'] : $txt['theme_options_defaults'], ' +
    +
    + +
    +
      '; + + foreach ($context['options'] as $setting) + { + echo ' +
    • '; + + if ($context['theme_options_reset']) + echo ' + '; + + if ($setting['type'] == 'checkbox') + { + echo ' + + '; + } + elseif ($setting['type'] == 'list') + { + echo ' +   + '; + } + else + echo ' +   + '; + + if (isset($setting['description'])) + echo ' +
      ', $setting['description'], ''; + + echo ' +
    • '; + } + + echo ' +
    +
    + + +
    +
    + +
    +
    +
    +
    '; +} + +function template_set_settings() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    +
    +

    + ', $txt['help'], ' ', $txt['theme_settings'], ' - ', $context['theme_settings']['name'], ' +

    +
    '; + + // !!! Why can't I edit the default theme popup. + if ($context['theme_settings']['theme_id'] != 1) + echo ' +
    +

    + ', $txt['theme_edit'], ' +

    +
    + '; + + echo ' +
    +

    + ', $txt['theme_url_config'], ' +

    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + +
    '; + + // Do we allow theme variants? + if (!empty($context['theme_variants'])) + { + echo ' +
    +

    + ', $txt['theme_variants'], ' +

    +
    +
    + +
    +
    +
    + : +
    +
    + +
    +
    + : +
    +
    + + +
    +
    + +
    + +
    '; + } + + echo ' +
    +

    + ', $txt['theme_options'], ' +

    +
    +
    + +
    +
    '; + + foreach ($context['settings'] as $setting) + { + // Is this a separator? + if (empty($setting)) + { + echo ' +
    +
    +
    '; + } + // A checkbox? + elseif ($setting['type'] == 'checkbox') + { + echo ' +
    + :'; + + if (isset($setting['description'])) + echo '
    + ', $setting['description'], ''; + + echo ' +
    +
    + + +
    '; + } + // A list with options? + elseif ($setting['type'] == 'list') + { + echo ' +
    + :'; + + if (isset($setting['description'])) + echo '
    + ', $setting['description'], ''; + + echo ' +
    +
    + +
    '; + } + // A regular input box, then? + else + { + echo ' +
    + :'; + + if (isset($setting['description'])) + echo '
    + ', $setting['description'], ''; + + echo ' +
    +
    + +
    '; + } + } + + echo ' +
    +
    + +
    +
    + +
    + +
    +
    +
    '; + + if (!empty($context['theme_variants'])) + { + echo ' + '; + } +} + +// This template allows for the selection of different themes ;). +function template_pick() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    '; + + // Just go through each theme and show its information - thumbnail, etc. + foreach ($context['available_themes'] as $theme) + { + echo ' + +
    + +
    +
    +

    ', $theme['description'], '

    '; + + if (!empty($theme['variants'])) + { + echo ' + : + + '; + } + + echo ' +
    +

    + ', $theme['num_users'], ' ', ($theme['num_users'] == 1 ? $txt['theme_user'] : $txt['theme_users']), ' +

    +
    + +
    + +
    '; + + if (!empty($theme['variants'])) + { + echo ' + '; + } + } + + echo ' +
    +
    +
    '; +} + +// Okay, that theme was installed successfully! +function template_installed() +{ + global $context, $settings, $options, $scripturl, $txt; + + // Not much to show except a link back... + echo ' +
    +
    +

    ', $context['page_title'], '

    +
    +
    + +
    +

    + ', $context['installed_theme']['name'], ' ', $txt['theme_installed_message'], ' +

    +

    + ', $txt['back'], ' +

    +
    + +
    +
    +
    '; +} + +function template_edit_list() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    +

    ', $txt['themeadmin_edit_title'], '

    +
    '; + + $alternate = false; + + foreach ($context['themes'] as $theme) + { + $alternate = !$alternate; + + echo ' +
    +

    + ', $theme['name'], '', !empty($theme['version']) ? ' + (' . $theme['version'] . ')' : '', ' +

    +
    + '; + } + + echo ' +
    +
    '; +} + +function template_copy_template() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    +
    +

    ', $txt['themeadmin_edit_filename'], '

    +
    +
    + ', $txt['themeadmin_edit_copy_warning'], ' +
    +
    + +
    +
      '; + + $alternate = false; + foreach ($context['available_templates'] as $template) + { + $alternate = !$alternate; + + echo ' +
    • + ', $template['filename'], $template['already_exists'] ? ' (' . $txt['themeadmin_edit_exists'] . ')' : '', ' + '; + + if ($template['can_copy']) + echo '', $txt['themeadmin_edit_do_copy'], ''; + else + echo $txt['themeadmin_edit_no_copy']; + + echo ' + +
    • '; + } + + echo ' +
    +
    + +
    +
    +
    '; +} + +function template_edit_browse() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +
    + + + + + + + + + '; + + $alternate = false; + + foreach ($context['theme_files'] as $file) + { + $alternate = !$alternate; + + echo ' + + + + + '; + } + + echo ' + +
    ', $txt['themeadmin_edit_filename'], '', $txt['themeadmin_edit_modified'], '', $txt['themeadmin_edit_size'], '
    '; + + if ($file['is_editable']) + echo '', $file['filename'], ''; + + elseif ($file['is_directory']) + echo '', $file['filename'], ''; + + else + echo $file['filename']; + + echo ' + ', !empty($file['last_modified']) ? $file['last_modified'] : '', '', $file['size'], '
    +
    +
    '; +} + +// Wanna edit the stylesheet? +function template_edit_style() +{ + global $context, $settings, $options, $scripturl, $txt; + + if ($context['session_error']) + echo ' +
    + ', $txt['error_session_timeout'], ' +
    '; + + // From now on no one can complain that editing css is difficult. If you disagree, go to www.w3schools.com. + echo ' +
    + + '; + + // Just show a big box.... gray out the Save button if it's not saveable... (ie. not 777.) + echo ' +
    +
    +

    ', $txt['theme_edit'], ' - ', $context['edit_filename'], '

    +
    +
    + +
    '; + + if (!$context['allow_save']) + echo ' + ', $txt['theme_edit_no_save'], ': ', $context['allow_save_filename'], '
    '; + + echo ' +
    +
    + + +
    +
    + +
    + + +
    +
    +
    '; +} + +// This edits the template... +function template_edit_template() +{ + global $context, $settings, $options, $scripturl, $txt; + + if ($context['session_error']) + echo ' +
    + ', $txt['error_session_timeout'], ' +
    '; + + if (isset($context['parse_error'])) + echo ' +
    + ', $txt['themeadmin_edit_error'], ' +
    ', $context['parse_error'], '
    +
    '; + + // Just show a big box.... gray out the Save button if it's not saveable... (ie. not 777.) + echo ' +
    +
    +
    +

    ', $txt['theme_edit'], ' - ', $context['edit_filename'], '

    +
    +
    + +
    '; + + if (!$context['allow_save']) + echo ' + ', $txt['theme_edit_no_save'], ': ', $context['allow_save_filename'], '
    '; + + foreach ($context['file_parts'] as $part) + echo ' + :
    +
    + +
    '; + + echo ' +
    + + + +
    +
    + +
    +
    +
    '; +} + +function template_edit_file() +{ + global $context, $settings, $options, $scripturl, $txt; + + if ($context['session_error']) + echo ' +
    + ', $txt['error_session_timeout'], ' +
    '; + + //Is this file writeable? + if (!$context['allow_save']) + echo ' +
    + ', $txt['theme_edit_no_save'], ': ', $context['allow_save_filename'], ' +
    '; + + // Just show a big box.... gray out the Save button if it's not saveable... (ie. not 777.) + echo ' +
    +
    +
    +

    ', $txt['theme_edit'], ' - ', $context['edit_filename'], '

    +
    +
    + +
    +
    + + + +
    + +
    + +
    +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/Who.template.php b/Themes/default/Who.template.php new file mode 100644 index 0000000..a8ffaec --- /dev/null +++ b/Themes/default/Who.template.php @@ -0,0 +1,227 @@ + +
    +
    +

    ', $txt['who_title'], '

    +
    +
    +
    + '; + echo ' +
    ', $txt['who_show1'], ' + + +
    +
    + + + + + + + + + '; + + // For every member display their name, time and action (and more for admin). + $alternate = 0; + + foreach ($context['members'] as $member) + { + // $alternate will either be true or false. If it's true, use "windowbg2" and otherwise use "windowbg". + echo ' + + + + + '; + + // Switch alternate to whatever it wasn't this time. (true -> false -> true -> false, etc.) + $alternate = !$alternate; + } + + // No members? + if (empty($context['members'])) + { + echo ' + + + '; + } + + echo ' + +
    ', $txt['who_user'], ' ', $context['sort_by'] == 'user' ? '' : '', '', $txt['who_time'], ' ', $context['sort_by'] == 'time' ? '' : '', '', $txt['who_action'], '
    '; + + // Guests don't have information like icq, msn, y!, and aim... and they can't be messaged. + if (!$member['is_guest']) + { + echo ' + + ', $context['can_send_pm'] ? '' : '', $settings['use_image_buttons'] ? '' . $member['online']['text'] . '' : $member['online']['text'], $context['can_send_pm'] ? '' : '', ' + ', isset($context['disabled_fields']['icq']) ? '' : $member['icq']['link'] , ' ', isset($context['disabled_fields']['msn']) ? '' : $member['msn']['link'], ' ', isset($context['disabled_fields']['yim']) ? '' : $member['yim']['link'], ' ', isset($context['disabled_fields']['aim']) ? '' : $member['aim']['link'], ' + '; + } + + echo ' + + ', $member['is_guest'] ? $member['name'] : '' . $member['name'] . '', ' + '; + + if (!empty($member['ip'])) + echo ' + (' . $member['ip'] . ')'; + + echo ' + ', $member['time'], '', $member['action'], '
    + ', $txt['who_no_online_' . ($context['show_by'] == 'guests' || $context['show_by'] == 'spiders' ? $context['show_by'] : 'members')], ' +
    +
    +
    + '; + + echo ' +
    ', $txt['who_show1'], ' + + +
    +
    +
    + '; +} + +function template_credits() +{ + global $context, $txt; + + // The most important part - the credits :P. + echo ' +
    +
    +

    ', $txt['credits'], '

    +
    '; + + foreach ($context['credits'] as $section) + { + if (isset($section['pretext'])) + echo ' +
    + +
    +

    ', $section['pretext'], '

    +
    + +
    '; + + if (isset($section['title'])) + echo ' +
    +

    ', $section['title'], '

    +
    '; + + echo ' +
    + +
    +
    '; + + foreach ($section['groups'] as $group) + { + if (isset($group['title'])) + echo ' +
    + ', $group['title'], ' +
    +
    '; + + // Try to make this read nicely. + if (count($group['members']) <= 2) + echo implode(' ' . $txt['credits_and'] . ' ', $group['members']); + else + { + $last_peep = array_pop($group['members']); + echo implode(', ', $group['members']), ' ', $txt['credits_and'], ' ', $last_peep; + } + + echo ' +
    '; + } + + echo ' +
    '; + + if (isset($section['posttext'])) + echo ' +

    ', $section['posttext'], '

    '; + + echo ' +
    + +
    '; + } + + echo ' +
    +

    ', $txt['credits_copyright'], '

    +
    +
    + +
    +
    +
    ', $txt['credits_forum'], '
    ', ' +
    ', $context['copyrights']['smf']; + + echo ' +
    +
    '; + + if (!empty($context['copyrights']['mods'])) + { + echo ' +
    +
    ', $txt['credits_modifications'], '
    +
    ', implode('
    ', $context['copyrights']['mods']), '
    +
    '; + } + + echo ' +
    + +
    +
    '; +} +?> \ No newline at end of file diff --git a/Themes/default/Wireless.template.php b/Themes/default/Wireless.template.php new file mode 100644 index 0000000..c830fac --- /dev/null +++ b/Themes/default/Wireless.template.php @@ -0,0 +1,1521 @@ + + + + +'; +} + +// This is the board index (main page) in WAP 1.1. +function template_wap_boardindex() +{ + global $context, $settings, $options, $scripturl; + + // This is the "main" card... + echo ' + +

    ', $context['forum_name_html_safe'], '

    '; + + // Show an anchor for each category. + foreach ($context['categories'] as $category) + { + // Skip it if it's empty. + if (!empty($category['boards'])) + echo ' +

    ', $category['name'], '

    '; + } + + // Okay, that's it for the main card. + echo ' +
    '; + + // Now fill out the deck of cards with the boards in each category. + foreach ($context['categories'] as $category) + { + // Begin the card, and make the name available. + echo ' + +

    ', strip_tags($category['name']), '

    '; + + // Now show a link for each board. + foreach ($category['boards'] as $board) + echo ' +

    ', $board['name'], '

    '; + + echo ' +
    '; + } +} + +// This is the message index (list of topics in a board) for WAP 1.1. +function template_wap_messageindex() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' + +

    ', $context['name'], '

    '; + + if (isset($context['boards']) && count($context['boards']) > 0) + { + foreach ($context['boards'] as $board) + echo ' +

    - ', $board['name'], '

    '; + echo ' +


    '; + } + + if (!empty($context['topics'])) + { + echo ' +

    ', $txt['pages'], ': ', !empty($context['links']['prev']) ? '<< < ' : '', '(', $context['page_info']['current_page'], '/', $context['page_info']['num_pages'], ')', !empty($context['links']['next']) ? ' > >> ' : '', '

    '; + + foreach ($context['topics'] as $topic) + echo ' +

    ', $topic['first_post']['subject'], '', (!$topic['approved'] ? ' (' . $txt['awaiting_approval'] . ')' : ''), ' - ', $topic['first_post']['member']['name'], '

    '; + + echo ' +

    ', $txt['pages'], ': ', !empty($context['links']['prev']) ? '<< < ' : '', '(', $context['page_info']['current_page'], '/', $context['page_info']['num_pages'], ')', !empty($context['links']['next']) ? ' > >> ' : '', '

    '; + } + + echo ' +
    '; +} + +function template_wap_display() +{ + global $context, $settings, $options, $txt; + + echo ' + +

    ' . $context['linktree'][1]['name'] . ' > ' . $context['linktree'][count($context['linktree']) - 2]['name'] . '

    +

    ', $context['subject'], '

    +

    ', $txt['pages'], ': ', !empty($context['links']['prev']) ? '<< < ' : '', '(', $context['page_info']['current_page'], '/', $context['page_info']['num_pages'], ')', !empty($context['links']['next']) ? ' > >> ' : '', '

    '; + + while ($message = $context['get_message']()) + { + // This is a special modification to the post so it will work on phones: + $message['body'] = preg_replace('~
    (.+?)
    ~', '
    --- $1 ---', $message['body']); + $message['body'] = strip_tags(str_replace( + array( + '
    ', + '
    ', + '', + '', + '
  10. ', + $txt['code_select'], + ), + array( + '
    ', + '
    --- ' . $txt['wireless_end_quote'] . ' ---
    ', + '
    ', + '
    --- ' . $txt['wireless_end_code'] . ' ---
    ', + '
    * ', + '', + ), $message['body']), '
    '); + + echo ' +

    ', $message['member']['name'], ':', (!$message['approved'] ? ' (' . $txt['awaiting_approval'] . ')' : ''), '

    +

    ', $message['body'], '

    '; + } + + echo ' +

    ', $txt['pages'], ': ', !empty($context['links']['prev']) ? '<< < ' : '', '(', $context['page_info']['current_page'], '/', $context['page_info']['num_pages'], ')', !empty($context['links']['next']) ? ' > >> ' : '', '

    + '; +} + +function template_wap_login() +{ + global $context, $modSettings, $scripturl, $txt; + + echo ' + '; + + if (isset($context['login_errors'])) + foreach ($context['login_errors'] as $error) + echo ' +

    ', $error, '

    '; + + echo ' +

    ', $txt['username'], ':
    +

    + +

    ', $txt['password'], ':
    +

    '; + + // Open ID? + if (!empty($modSettings['enableOpenID'])) + echo ' +

    —', $txt['or'], '—

    + +

    ', $txt['openid'], ':
    +

    '; + + echo ' +

    + + + + + +

    +
    '; +} + +function template_wap_recent() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' + +

    ', $_REQUEST['action'] == 'unread' ? $txt['wireless_recent_unread_posts'] : $txt['wireless_recent_unread_replies'], '

    '; + + if (empty($context['topics'])) + echo ' +

    ', $txt['old_posts'], '

    '; + else + { + echo ' +

    ', $txt['pages'], ': ', !empty($context['links']['prev']) ? '<< < ' : '', '(', $context['page_info']['current_page'], '/', $context['page_info']['num_pages'], ')', !empty($context['links']['next']) ? ' > >> ' : '', '

    '; + foreach ($context['topics'] as $topic) + { + echo ' +

    ', $topic['first_post']['subject'], '

    '; + } + } + + echo ' +
    '; +} + +function template_wap_error() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' + +

    ', $context['error_title'], '

    +

    ', $context['error_message'], '

    +

    ', $txt['wireless_error_home'], '

    +
    '; +} + +function template_wap_below() +{ + global $context, $settings, $options, $txt; + + echo ' + +

    + ', $txt['wireless_go_to_full_version'], ' +

    +
    +'; +} + +// The cHTML protocol used for i-mode starts here. +function template_imode_above() +{ + global $context, $settings, $options, $user_info; + + echo ' + + + '; + + // Present a canonical url for search engines to prevent duplicate content in their indices. + if ($user_info['is_guest'] && !empty($context['canonical_url'])) + echo ' + '; + + echo ' + ', $context['page_title'], ' + + '; +} + +function template_imode_boardindex() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' + + '; + $count = 0; + foreach ($context['categories'] as $category) + { + if (!empty($category['boards']) || $category['is_collapsed']) + echo ' + '; + + foreach ($category['boards'] as $board) + { + $count++; + echo ' + '; + } + } + echo ' + '; + if ($context['user']['is_guest']) + echo ' + '; + else + { + if ($context['allow_pm']) + echo ' + '; + echo ' + + + '; + } + echo ' +
    ', $context['forum_name_html_safe'], '
    ', $category['can_collapse'] ? '' : '', $category['name'], $category['can_collapse'] ? '' : '', '
    ', $board['new'] ? '' : '', $count < 10 ? '&#' . (59105 + $count) . ';' : '-', $board['new'] ? '' : ($board['children_new'] ? '.' : ''), ' ', $board['name'], '
    ', $txt['wireless_options'], '
    ', $txt['wireless_options_login'], '
    ', empty($context['user']['unread_messages']) ? $txt['wireless_pm_inbox'] : sprintf($txt['wireless_pm_inbox_new'], $context['user']['unread_messages']), '
    ', $txt['wireless_recent_unread_posts'], '
    ', $txt['wireless_recent_unread_replies'], '
    ', $txt['wireless_options_logout'], '
    '; +} + +function template_imode_messageindex() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' + + '; + + if (!empty($context['boards'])) + { + echo ' + '; + foreach ($context['boards'] as $board) + echo ' + '; + } + + $count = 0; + if (!empty($context['topics'])) + { + echo ' + + '; + foreach ($context['topics'] as $topic) + { + $count++; + echo ' + '; + } + } + echo ' + + ', !empty($context['links']['next']) ? ' + ' : '', !empty($context['links']['prev']) ? ' + ' : '', $context['can_post_new'] ? ' + ' : '', ' +
    ', $context['name'], '
    ', $txt['parent_boards'], '
    ', $board['new'] ? '- ' : ($board['children_new'] ? '-.' : '- '), '', $board['name'], '
    ', $txt['topics'], '
    ', !empty($context['links']['prev']) ? '<< < ' : '', '(', $context['page_info']['current_page'], '/', $context['page_info']['num_pages'], ')', !empty($context['links']['next']) ? ' > >> ' : '', '
    ', $count < 10 ? '&#' . (59105 + $count) . '; ' : '', '', $topic['first_post']['subject'], '', (!$topic['approved'] ? ' (' . $txt['awaiting_approval'] . ')' : ''), $topic['new'] && $context['user']['is_logged'] ? ' [' . $txt['new'] . ']' : '', '
    ', $txt['wireless_navigation'], '
    ', $txt['wireless_navigation_up'], '
    ' . $txt['wireless_navigation_next'] . '
    [*] ' . $txt['wireless_navigation_prev'] . '
    ' . $txt['start_new_topic'] . '
    '; +} + +function template_imode_display() +{ + global $context, $settings, $options, $scripturl, $board, $txt; + + echo ' + + + + '; + while ($message = $context['get_message']()) + { + // This is a special modification to the post so it will work on phones: + $message['body'] = preg_replace('~
    (.+?)
    ~', '
    --- $1 ---', $message['body']); + $message['body'] = strip_tags(str_replace( + array( + '
    ', + '
    ', + '', + '', + '
  11. ', + $txt['code_select'], + ), + array( + '
    ', + '
    --- ' . $txt['wireless_end_quote'] . ' ---
    ', + '
    ', + '
    --- ' . $txt['wireless_end_code'] . ' ---
    ', + '
    * ', + '', + ), $message['body']), '
    '); + + echo ' +
  12. '; + } + echo ' + + ', $context['user']['is_logged'] ? ' + ' : '', !empty($context['links']['next']) ? ' + ' : '', !empty($context['links']['prev']) ? ' + ' : '', $context['can_reply'] ? ' + ' : ''; + + if (!empty($context['wireless_more']) && empty($context['wireless_moderate'])) + echo ' + '; + elseif (!empty($context['wireless_moderate'])) + { + if ($context['can_sticky']) + echo ' + '; + if ($context['can_lock']) + echo ' + '; + } + + echo ' +
    ' . $context['linktree'][1]['name'] . ' > ' . $context['linktree'][count($context['linktree']) - 2]['name'] . '
    ', $context['subject'], '
    ', !empty($context['links']['prev']) ? '<< < ' : '', '(', $context['page_info']['current_page'], '/', $context['page_info']['num_pages'], ')', !empty($context['links']['next']) ? ' > >> ' : '', '
    ', $message['first_new'] ? ' + ' : '', + $context['wireless_moderate'] && $message['member']['id'] ? '' . $message['member']['name'] . '' : '' . $message['member']['name'] . '', ': + ', ((empty($context['wireless_more']) && $message['can_modify']) || !empty($context['wireless_moderate']) ? '[' . $txt['wireless_display_edit'] . ']' : ''), (!$message['approved'] ? ' (' . $txt['awaiting_approval'] . ')' : ''), '
    + ', $message['body'], ' +
    ', $txt['wireless_navigation'], '
    ', $txt['wireless_navigation_index'], '
    ' . $txt['mark_unread'] . '
    ' . $txt['wireless_navigation_next'] . '
    ' . $txt['wireless_navigation_prev'] . '
    ' . $txt['reply'] . '
    ', $txt['wireless_display_moderate'], '
    ', $txt['wireless_display_' . ($context['is_sticky'] ? 'unsticky' : 'sticky')], '
    ', $txt['wireless_display_' . ($context['is_locked'] ? 'unlock' : 'lock')], '
    '; +} + +function template_imode_post() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + // !!! $modSettings['guest_post_no_email'] + echo ' +
    + '; + + if (!$context['becomes_approved']) + echo ' + '; + + if ($context['locked']) + echo ' + '; + + if (isset($context['name']) && isset($context['email'])) + { + echo ' + + '; + + if (empty($modSettings['guest_post_no_email'])) + echo ' + + '; + } + + // !!! Needs a more specific imode template. + if ($context['require_verification']) + echo ' + + '; + + echo ' + + + + + + +
    ' . $txt['wait_for_approval'] . '
    ' . $txt['topic_locked_no_reply'] . '
    ', isset($context['post_error']['long_name']) || isset($context['post_error']['no_name']) ? '' . $txt['username'] . '' : $txt['username'], ':
    ', isset($context['post_error']['no_email']) || isset($context['post_error']['bad_email']) ? '' . $txt['email'] . '' : $txt['email'], ':
    ', !empty($context['post_error']['need_qr_verification']) ? '' . $txt['verification'] . '' : $txt['verification'], ':
    ', template_control_verification($context['visual_verification_id'], 'all'), '
    ', isset($context['post_error']['no_subject']) ? '' . $txt['subject'] . '' : $txt['subject'], ':
    ', isset($context['post_error']['no_message']) || isset($context['post_error']['long_message']) ? '' . $txt['message'] . '' : $txt['message'], ':
    + + + + + ', isset($context['current_topic']) ? ' + ' : '', ' + +
    +  ', !empty($context['current_topic']) ? '' . $txt['wireless_navigation_topic'] . '' : '' . $txt['wireless_navigation_index'] . '', ' +
    +
    '; +} + +function template_imode_login() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    + + '; + if (isset($context['login_errors'])) + foreach ($context['login_errors'] as $error) + echo ' + '; + echo ' + + + + '; + + // Open ID? + if (!empty($modSettings['enableOpenID'])) + echo ' + + + '; + + echo ' + + + +
    ', $txt['login'], '
    ', $error, '
    ', $txt['username'], ':
    ', $txt['password'], ':
    —', $txt['or'], '—
    ', $txt['openid'], ':
    ', $txt['wireless_navigation'], '
    [0] ', $txt['wireless_navigation_up'], '
    +
    '; +} + +function template_imode_pm() +{ + global $context, $settings, $options, $scripturl, $txt, $user_info; + + if ($_REQUEST['action'] == 'findmember') + { + echo ' +
    + + + + + '; + if (!empty($context['last_search'])) + { + echo ' + '; + if (empty($context['results'])) + echo ' + '; + else + { + echo ' + '; + $count = 0; + foreach ($context['results'] as $result) + { + $count++; + echo ' + '; + } + } + } + echo ' + + '; + if (!empty($context['results'])) + echo empty($context['links']['next']) ? '' : ' + ', empty($context['links']['prev']) ? '' : ' + '; + echo ' +
    ', $txt['wireless_pm_search_member'], '
    ', $txt['find_members'], '
    + ', $txt['wireless_pm_search_name'], ': + ', empty($_REQUEST['u']) ? '' : ' + ', ' +
    ', $txt['find_results'], '
    [-] ', $txt['find_no_results'], '
    ', empty($context['links']['prev']) ? '' : '<< < ', '(', $context['page_info']['current_page'], '/', $context['page_info']['num_pages'], ')', empty($context['links']['next']) ? '' : ' > >> ', '
    + ', $count < 10 ? '&#' . (59105 + $count) . '; ' : '', '', $result['name'], ' +
    ', $txt['wireless_navigation'], '
    [0] ', $txt['wireless_navigation_up'], '
    [#] ' . $txt['wireless_navigation_next'] . '
    [*] ' . $txt['wireless_navigation_prev'] . '
    +
    '; + } + elseif (!empty($_GET['sa'])) + { + echo ' + '; + if ($_GET['sa'] == 'addbuddy') + { + echo ' + + '; + $count = 0; + foreach ($context['buddies'] as $buddy) + { + $count++; + if ($buddy['selected']) + echo ' + '; + else + echo ' + '; + } + echo ' + + +
    ', $txt['wireless_pm_add_buddy'], '
    ', $txt['wireless_pm_select_buddy'], '
    [-] ', $buddy['name'], '
    + ', $count < 10 ? '&#' . (59105 + $count) . '; ' : '', '', $buddy['name'], ' +
    ', $txt['wireless_navigation'], '
    [0] ', $txt['wireless_navigation_up'], '
    '; + } + if ($_GET['sa'] == 'send' || $_GET['sa'] == 'send2') + { + echo ' +
    + + ', empty($context['post_error']['messages']) ? '' : ' + ', ' + + + + '; + if ($context['reply']) + echo ' + + + '; + echo ' + + +
    ', $txt['new_message'], '
    ' . implode('
    ', $context['post_error']['messages']) . '
    + ', $txt['pm_to'], ': '; + if (empty($context['recipients']['to'])) + echo $txt['wireless_pm_no_recipients']; + else + { + $to_names = array(); + $ids = array(); + foreach ($context['recipients']['to'] as $to) + { + $ids[] = $to['id']; + $to_names[] = $to['name']; + } + echo implode(', ', $to_names); + $ids = implode(',', $ids); + } + echo ' + ', empty($ids) ? '' : '', '
    + ', $txt['wireless_pm_search_member'], '', empty($user_info['buddies']) ? '' : '
    + ' . $txt['wireless_pm_add_buddy'] . '', ' +
    + ', $txt['subject'], ': +
    + ', $txt['message'], ':
    + +
    + + + + + + + + +
    ', $txt['wireless_pm_reply_to'], '
    ', $context['quoted_message']['subject'], '
    ', $context['quoted_message']['body'], '
    ', $txt['wireless_navigation'], '
    [0] ', $txt['wireless_navigation_up'], '
    +
    '; + } + } + elseif (empty($_GET['pmsg'])) + { + echo ' + + + '; + $count = 0; + while ($message = $context['get_pmessage']()) + { + $count++; + echo ' + '; + } + + if ($context['currently_using_labels']) + { + $labels = array(); + ksort($context['labels']); + foreach ($context['labels'] as $label) + $labels[] = '' . $label['name'] . '' . (!empty($label['unread_messages']) ? ' (' . $label['unread_messages'] . ')' : ''); + echo ' + + '; + } + echo ' + + ', empty($context['links']['next']) ? '' : ' + ', empty($context['links']['prev']) ? '' : ' + ', $context['can_send_pm'] ? ' + ' : '', ' +
    ', $context['current_label_id'] == -1 ? $txt['wireless_pm_inbox'] : $txt['pm_current_label'] . ': ' . $context['current_label'], '
    ', empty($context['links']['prev']) ? '' : '<< < ', '(', $context['page_info']['current_page'], '/', $context['page_info']['num_pages'], ')', empty($context['links']['next']) ? '' : ' > >> ', '
    + ', $count < 10 ? '&#' . (59105 + $count) . '; ' : '', '', $message['subject'], ' ', $txt['wireless_pm_by'], ' ', $message['member']['name'], '', $message['is_unread'] ? ' [' . $txt['new'] . ']' : '', ' +
    ', $txt['pm_labels'], '
    + ', implode(', ', $labels), ' +
    ', $txt['wireless_navigation'], '
    [0] ', $txt['wireless_navigation_up'], '
    [#] ' . $txt['wireless_navigation_next'] . '
    [*] ' . $txt['wireless_navigation_prev'] . '
    ' . $txt['new_message'] . '
    '; + } + else + { + $message = $context['get_pmessage'](); + $message['body'] = preg_replace('~
    (.+?)
    ~', '
    --- $1 ---', $message['body']); + $message['body'] = strip_tags(str_replace( + array( + '
    ', + '
    ', + '', + '', + '
  13. ', + $txt['code_select'], + ), + array( + '
    ', + '
    --- ' . $txt['wireless_end_quote'] . ' ---
    ', + '
    ', + '
    --- ' . $txt['wireless_end_code'] . ' ---
    ', + '
    * ', + '', + ), $message['body']), '
    '); + + echo ' + + + + + + '; + if ($context['can_send_pm']) + echo ' + '; + + if ($context['can_send_pm'] && $message['number_recipients'] > 1) + echo ' + '; + + echo ' +
    ', $message['subject'], '
    + ', $txt['wireless_pm_by'], ': ', $message['member']['name'], '
    + ', $txt['on'], ': ', $message['time'], ' +
    + ', $message['body'], ' +
    ', $txt['wireless_navigation'], '
    [0] ', $txt['wireless_navigation_up'], '
    ', $txt['wireless_pm_reply'], '
    ', $txt['wireless_pm_reply_all'], '
    '; + } +} + +function template_imode_recent() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' + + '; + + $count = 0; + if (empty($context['topics'])) + echo ' + '; + else + { + echo ' + '; + foreach ($context['topics'] as $topic) + { + $count++; + echo ' + '; + } + } + echo ' + + ', !empty($context['links']['next']) ? ' + ' : '', !empty($context['links']['prev']) ? ' + ' : '', ' +
    ', $_REQUEST['action'] == 'unread' ? $txt['wireless_recent_unread_posts'] : $txt['wireless_recent_unread_replies'], '
    ', $txt['old_posts'], '
    ', !empty($context['links']['prev']) ? '<< < ' : '', '(', $context['page_info']['current_page'], '/', $context['page_info']['num_pages'], ')', !empty($context['links']['next']) ? ' > >> ' : '', '
    ', $count < 10 ? '&#' . (59105 + $count) . '; ' : '', '', $topic['first_post']['subject'], '
    ', $txt['wireless_navigation'], '
    [0] ', $txt['wireless_navigation_up'], '
    [#] ' . $txt['wireless_navigation_next'] . '
    [*] ' . $txt['wireless_navigation_prev'] . '
    '; +} + +function template_imode_error() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' + + + + +
    ', $context['error_title'], '
    ', $context['error_message'], '
    [0] ', $txt['wireless_error_home'], '
    '; +} + +function template_imode_profile() +{ + global $context, $settings, $options, $scripturl, $board, $txt; + + echo ' + + + + + '; + + if (!empty($context['member']['bans'])) + { + echo ' + '; + } + + echo ' + + '; + + if (!$context['user']['is_owner'] && $context['can_send_pm']) + echo ' + '; + + if (!$context['user']['is_owner'] && !empty($context['can_edit_ban'])) + echo ' + '; + + echo ' + '; + + echo ' +
    ', $txt['summary'], ' - ', $context['member']['name'], '
    + ', $txt['name'], ': ', $context['member']['name'], ' +
    + ', $txt['position'], ': ', (!empty($context['member']['group']) ? $context['member']['group'] : $context['member']['post_group']), ' +
    + ', $txt['lastLoggedIn'], ': ', $context['member']['last_login'], ' +
    + ', $txt['user_banned_by_following'], ':'; + + foreach ($context['member']['bans'] as $ban) + echo ' +
    ', $ban['explanation'], ''; + + echo ' +
    ', $txt['additional_info'], '
    ', $txt['wireless_profile_pm'], '.
    ', $txt['profileBanUser'], '.
    ', $txt['wireless_error_home'], '.
    '; +} + +function template_imode_ban_edit() +{ + global $context, $settings, $options, $scripturl, $board, $txt, $modSettings; + + echo ' +
    + + + + + + + '; + + if (!empty($context['ban_suggestions'])) + { + echo ' + + '; + + if (empty($modSettings['disableHostnameLookup'])) + echo ' + '; + + echo ' + + '; + } + + echo ' + + + '; + + echo ' +
    ', $context['ban']['is_new'] ? $txt['ban_add_new'] : $txt['ban_edit'] . ' \'' . $context['ban']['name'] . '\'', '
    + ', $txt['ban_name'], ': + +
    + ', $txt['ban_expiration'], ':
    + ', $txt['never'], '
    + ', $txt['ban_will_expire_within'], ' ', $txt['ban_days'], '
    + ', $txt['ban_expired'], '
    +
    + ', $txt['ban_reason'], ': + +
    + ', $txt['ban_notes'], ':
    + +
    + ', $txt['ban_restriction'], ':
    + ', $txt['ban_full_ban'], '
    + ', $txt['ban_cannot_post'], '
    + ', $txt['ban_cannot_register'], '
    + ', $txt['ban_cannot_login'], ' +
    ', $txt['ban_triggers'], '
    + ', $txt['wireless_ban_ip'], ':
    +      +
    + ', $txt['wireless_ban_hostname'], ':
    +      +
    + ', $txt['wireless_ban_email'], ':
    +      +
    + ', $txt['ban_on_username'], ':
    '; + + if (empty($context['ban_suggestions']['member']['id'])) + echo ' +     '; + else + echo ' +     ', $context['ban_suggestions']['member']['name'], ' + '; + + echo ' +
    ', $txt['wireless_additional_info'], '
    ', $txt['wireless_error_home'], '.
    + + + +
    '; +} + +function template_imode_below() +{ + global $context, $settings, $options, $txt; + + echo ' +
    ', $txt['wireless_go_to_full_version'], ' + +'; +} + +// XHTMLMP (XHTML Mobile Profile) templates used for WAP 2.0 start here +function template_wap2_above() +{ + global $context, $settings, $options, $user_info; + + echo ' + + + + ', $context['page_title'], ''; + + // Present a canonical url for search engines to prevent duplicate content in their indices. + if ($user_info['is_guest'] && !empty($context['canonical_url'])) + echo ' + '; + + echo ' + + + '; +} + +function template_wap2_boardindex() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +

    ', $context['forum_name_html_safe'], '

    '; + + $count = 0; + foreach ($context['categories'] as $category) + { + if (!empty($category['boards']) || $category['is_collapsed']) + echo ' +

    ', $category['can_collapse'] ? '' : '', $category['name'], $category['can_collapse'] ? '' : '', '

    '; + + foreach ($category['boards'] as $board) + { + $count++; + echo ' +

    ', $board['new'] ? '' : '', $count < 10 ? '[' . $count . '' : '[-', $board['children_new'] && !$board['new'] ? '' : '', '] ', $board['new'] || $board['children_new'] ? '' : '', '', $board['name'], '

    '; + } + } + + echo ' +

    ', $txt['wireless_options'], '

    '; + if ($context['user']['is_guest']) + echo ' +

    ', $txt['wireless_options_login'], '

    '; + else + { + if ($context['allow_pm']) + echo ' +

    ', empty($context['user']['unread_messages']) ? $txt['wireless_pm_inbox'] : sprintf($txt['wireless_pm_inbox_new'], $context['user']['unread_messages']), '

    '; + echo ' +

    ', $txt['wireless_recent_unread_posts'], '

    +

    ', $txt['wireless_recent_unread_replies'], '

    +

    ', $txt['wireless_options_logout'], '

    '; + } +} + +function template_wap2_messageindex() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +

    ', $context['name'], '

    '; + + if (!empty($context['boards'])) + { + echo ' +

    ', $txt['parent_boards'], '

    '; + foreach ($context['boards'] as $board) + echo ' +

    ', $board['new'] ? '[-] ' : ($board['children_new'] ? '[-] ' : '[-] '), '', $board['name'], '

    '; + } + + $count = 0; + if (!empty($context['topics'])) + { + echo ' +

    ', $txt['topics'], '

    +

    ', !empty($context['links']['prev']) ? '<< < ' : '', '(', $context['page_info']['current_page'], '/', $context['page_info']['num_pages'], ')', !empty($context['links']['next']) ? ' > >> ' : '', '

    '; + foreach ($context['topics'] as $topic) + { + $count++; + echo ' +

    ', $count < 10 ? '[' . $count . '] ' : '', '', $topic['first_post']['subject'], '', (!$topic['approved'] ? ' (' . $txt['awaiting_approval'] . ')' : ''), $topic['new'] && $context['user']['is_logged'] ? ' [' . $txt['new'] . ']' : '', '

    '; + } + } + + echo ' +

    ', $txt['wireless_navigation'], '

    +

    [0] ', $txt['wireless_navigation_up'], '

    ', !empty($context['links']['next']) ? ' +

    [#] ' . $txt['wireless_navigation_next'] . '

    ' : '', !empty($context['links']['prev']) ? ' +

    [*] ' . $txt['wireless_navigation_prev'] . '

    ' : '', $context['can_post_new'] ? ' +

    ' . $txt['start_new_topic'] . '

    ' : ''; +} + +function template_wap2_display() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +

    ' . $context['linktree'][1]['name'] . ' > ' . $context['linktree'][count($context['linktree']) - 2]['name'] . '

    +

    ', $context['subject'], '

    +

    ', !empty($context['links']['prev']) ? '<< < ' : '', '(', $context['page_info']['current_page'], '/', $context['page_info']['num_pages'], ')', !empty($context['links']['next']) ? ' > >> ' : '', '

    '; + $alternate = true; + while ($message = $context['get_message']()) + { + // This is a special modification to the post so it will work on phones: + $message['body'] = preg_replace('~
    (.+?)
    ~', '
    --- $1 ---', $message['body']); + $message['body'] = strip_tags(str_replace( + array( + '
    ', + '
    ', + '', + '', + '
  14. ', + $txt['code_select'], + ), + array( + '
    ', + '
    --- ' . $txt['wireless_end_quote'] . ' ---
    ', + '
    ', + '
    --- ' . $txt['wireless_end_code'] . ' ---
    ', + '
    * ', + '', + ), $message['body']), '
    '); + + echo $message['first_new'] ? ' + ' : '', ' +

    + ', $context['wireless_moderate'] && $message['member']['id'] ? '' . $message['member']['name'] . '' : '' . $message['member']['name'] . '', ': + ', ((empty($context['wireless_more']) && $message['can_modify']) || !empty($context['wireless_moderate']) ? '[' . $txt['wireless_display_edit'] . ']' : ''), (!$message['approved'] ? ' (' . $txt['awaiting_approval'] . ')' : ''), '
    + ', $message['body'], ' +

    '; + $alternate = !$alternate; + } + echo ' +

    ', $txt['wireless_navigation'], '

    +

    [0] ', $txt['wireless_navigation_index'], '

    ', $context['user']['is_logged'] ? ' +

    [1] ' . $txt['mark_unread'] . '

    ' : '', !empty($context['links']['next']) ? ' +

    [#] ' . $txt['wireless_navigation_next'] . '

    ' : '', !empty($context['links']['prev']) ? ' +

    [*] ' . $txt['wireless_navigation_prev'] . '

    ' : '', $context['can_reply'] ? ' +

    ' . $txt['reply'] . '

    ' : ''; + + if (!empty($context['wireless_more']) && empty($context['wireless_moderate'])) + echo ' +

    ', $txt['wireless_display_moderate'], '

    '; + elseif (!empty($context['wireless_moderate'])) + { + if ($context['can_sticky']) + echo ' +

    ', $txt['wireless_display_' . ($context['is_sticky'] ? 'unsticky' : 'sticky')], '

    '; + if ($context['can_lock']) + echo ' +

    ', $txt['wireless_display_' . ($context['is_locked'] ? 'unlock' : 'lock')], '

    '; + } +} + +function template_wap2_login() +{ + global $context, $modSettings, $scripturl, $txt; + + echo ' +
    +

    ', $txt['login'], '

    '; + + if (isset($context['login_errors'])) + foreach ($context['login_errors'] as $error) + echo ' +

    ', $error, '

    '; + + echo ' +

    ', $txt['username'], ':

    +

    +

    ', $txt['password'], ':

    +

    '; + + // Open ID? + if (!empty($modSettings['enableOpenID'])) + echo ' +

    —', $txt['or'], '—

    +

    ', $txt['openid'], ':

    +

    '; + + echo ' +

    +

    ', $txt['wireless_navigation'], '

    +

    [0] ', $txt['wireless_navigation_up'], '

    +
    '; +} + +function template_wap2_post() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    +

    ', $context['page_title'], '

    '; + + if (!$context['becomes_approved']) + echo ' +

    + ' . $txt['wait_for_approval'] . ' + +

    '; + + if ($context['locked']) + echo ' +

    + ' . $txt['topic_locked_no_reply'] . ' +

    '; + + if (isset($context['name']) && isset($context['email'])) + { + echo ' +

    + ' . $txt['username'] . ': +

    '; + + if (empty($modSettings['guest_post_no_email'])) + echo ' +

    + ' . $txt['email'] . ': +

    '; + } + + if ($context['require_verification']) + echo ' +

    + ' . $txt['verification'] . ': ', template_control_verification($context['visual_verification_id'], 'all'), ' +

    '; + + echo ' +

    + ', $txt['subject'], ': +

    +

    + ', $txt['message'], ':
    + +

    +

    + + + + + ', isset($context['current_topic']) ? ' + ' : '', ' + +

    +

    [0] ', !empty($context['current_topic']) ? '' . $txt['wireless_navigation_topic'] . '' : '' . $txt['wireless_navigation_index'] . '', '

    +
    '; +} + +function template_wap2_pm() +{ + global $context, $settings, $options, $scripturl, $txt, $user_info; + + if ($_REQUEST['action'] == 'findmember') + { + echo ' +
    +

    ', $txt['wireless_pm_search_member'], '

    +

    ', $txt['find_members'], '

    +

    + ', $txt['wireless_pm_search_name'], ': + ', empty($_REQUEST['u']) ? '' : ' + ', ' +

    +

    +
    '; + if (!empty($context['last_search'])) + { + echo ' +

    ', $txt['find_results'], '

    '; + if (empty($context['results'])) + echo ' +

    [-] ', $txt['find_no_results'], '

    '; + else + { + echo ' +

    ', empty($context['links']['prev']) ? '' : '<< < ', '(', $context['page_info']['current_page'], '/', $context['page_info']['num_pages'], ')', empty($context['links']['next']) ? '' : ' > >> ', '

    '; + $count = 0; + foreach ($context['results'] as $result) + { + $count++; + echo ' +

    + [', $count < 10 ? $count : '-', '] ', $result['name'], ' +

    '; + } + } + } + echo ' +

    ', $txt['wireless_navigation'], '

    +

    [0] ', $txt['wireless_navigation_up'], '

    '; + if (!empty($context['results'])) + echo empty($context['links']['next']) ? '' : ' +

    [#] ' . $txt['wireless_navigation_next'] . '

    ', empty($context['links']['prev']) ? '' : ' +

    [*] ' . $txt['wireless_navigation_prev'] . '

    '; + } + elseif (!empty($_GET['sa'])) + { + if ($_GET['sa'] == 'addbuddy') + { + echo ' +

    ', $txt['wireless_pm_add_buddy'], '

    +

    ', $txt['wireless_pm_select_buddy'], '

    '; + $count = 0; + foreach ($context['buddies'] as $buddy) + { + $count++; + if ($buddy['selected']) + echo ' +

    [-] ', $buddy['name'], '

    '; + else + echo ' +

    + [', $count < 10 ? $count : '-', '] ', $buddy['name'], ' +

    '; + } + echo ' +

    ', $txt['wireless_navigation'], '

    +

    [0] ', $txt['wireless_navigation_up'], '

    '; + } + if ($_GET['sa'] == 'send' || $_GET['sa'] == 'send2') + { + echo ' +
    +

    ', $txt['new_message'], '

    ', empty($context['post_error']['messages']) ? '' : ' +

    ' . implode('
    ', $context['post_error']['messages']) . '

    ', ' +

    + ', $txt['pm_to'], ': '; + if (empty($context['recipients']['to'])) + echo $txt['wireless_pm_no_recipients']; + else + { + $to_names = array(); + $ids = array(); + foreach ($context['recipients']['to'] as $to) + { + $ids[] = $to['id']; + $to_names[] = $to['name']; + } + echo implode(', ', $to_names); + $ids = implode(',', $ids); + } + echo ' + ', empty($ids) ? '' : '', '
    + ', $txt['wireless_pm_search_member'], '', empty($user_info['buddies']) ? '' : '
    + ' . $txt['wireless_pm_add_buddy'] . '', ' +

    +

    + ', $txt['subject'], ': +

    +

    + ', $txt['message'], ':
    + +

    +

    + + + + + + + + +

    '; + if ($context['reply']) + echo ' +

    ', $txt['wireless_pm_reply_to'], '

    +

    ', $context['quoted_message']['subject'], '

    +

    ', $context['quoted_message']['body'], '

    '; + echo ' +

    ', $txt['wireless_navigation'], '

    +

    [0] ', $txt['wireless_navigation_up'], '

    +
    '; + } + } + elseif (empty($_GET['pmsg'])) + { + echo ' +

    ', $context['current_label_id'] == -1 ? $txt['wireless_pm_inbox'] : $txt['pm_current_label'] . ': ' . $context['current_label'], '

    +

    ', empty($context['links']['prev']) ? '' : '<< < ', '(', $context['page_info']['current_page'], '/', $context['page_info']['num_pages'], ')', empty($context['links']['next']) ? '' : ' > >> ', '

    '; + $count = 0; + while ($message = $context['get_pmessage']()) + { + $count++; + echo ' +

    + [', $count < 10 ? $count : '-', '] ', $message['subject'], ' ', $txt['wireless_pm_by'], ' ', $message['member']['name'], '', $message['is_unread'] ? ' [' . $txt['new'] . ']' : '', ' +

    '; + } + + if ($context['currently_using_labels']) + { + $labels = array(); + ksort($context['labels']); + foreach ($context['labels'] as $label) + $labels[] = '' . $label['name'] . '' . (!empty($label['unread_messages']) ? ' (' . $label['unread_messages'] . ')' : ''); + echo ' +

    + ', $txt['pm_labels'], ' +

    +

    + ', implode(', ', $labels), ' +

    '; + } + + echo ' +

    ', $txt['wireless_navigation'], '

    +

    [0] ', $txt['wireless_navigation_up'], '

    ', empty($context['links']['next']) ? '' : ' +

    [#] ' . $txt['wireless_navigation_next'] . '

    ', empty($context['links']['prev']) ? '' : ' +

    [*] ' . $txt['wireless_navigation_prev'] . '

    ', $context['can_send_pm'] ? ' +

    ' . $txt['new_message'] . '

    ' : ''; + } + else + { + $message = $context['get_pmessage'](); + $message['body'] = preg_replace('~
    (.+?)
    ~', '
    --- $1 ---', $message['body']); + $message['body'] = strip_tags(str_replace( + array( + '
    ', + '
    ', + '', + '', + '
  15. ', + $txt['code_select'], + ), + array( + '
    ', + '
    --- ' . $txt['wireless_end_quote'] . ' ---
    ', + '
    ', + '
    --- ' . $txt['wireless_end_code'] . ' ---
    ', + '
    * ', + '', + ), $message['body']), '
    '); + + echo ' +

    ', $message['subject'], '

    +

    + ', $txt['wireless_pm_by'], ': ', $message['member']['name'], '
    + ', $txt['on'], ': ', $message['time'], ' +

    +

    + ', $message['body'], ' +

    +

    ', $txt['wireless_navigation'], '

    +

    [0] ', $txt['wireless_navigation_up'], '

    '; + if ($context['can_send_pm']) + echo ' +

    ', $txt['wireless_pm_reply'], '

    '; + + if ($context['can_send_pm'] && $message['number_recipients'] > 1) + echo ' +

    ', $txt['wireless_pm_reply_all'], '

    '; + + } +} + +function template_wap2_recent() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' +

    ', $_REQUEST['action'] == 'unread' ? $txt['wireless_recent_unread_posts'] : $txt['wireless_recent_unread_replies'], '

    '; + + $count = 0; + if (empty($context['topics'])) + echo ' +

    ', $txt['old_posts'], '

    '; + else + { + echo ' +

    ', !empty($context['links']['prev']) ? '<< < ' : '', '(', $context['page_info']['current_page'], '/', $context['page_info']['num_pages'], ')', !empty($context['links']['next']) ? ' > >> ' : '', '

    '; + foreach ($context['topics'] as $topic) + { + $count++; + echo ' +

    ', ($count < 10 ? '[' . $count . '] ' : ''), '', $topic['first_post']['subject'], '

    '; + } + } + echo ' +

    ', $txt['wireless_navigation'], '

    +

    [0] ', $txt['wireless_navigation_up'], '

    ', !empty($context['links']['next']) ? ' +

    [#] ' . $txt['wireless_navigation_next'] . '

    ' : '', !empty($context['links']['prev']) ? ' +

    [*] ' . $txt['wireless_navigation_prev'] . '

    ' : ''; +} + +function template_wap2_error() +{ + global $context, $settings, $options, $txt, $scripturl; + + echo ' +

    ', $context['error_title'], '

    +

    ', $context['error_message'], '

    +

    [0] ', $txt['wireless_error_home'], '

    '; +} + +function template_wap2_profile() +{ + global $context, $settings, $options, $scripturl, $board, $txt; + + echo ' +

    ', $txt['summary'], ' - ', $context['member']['name'], '

    +

    ', $txt['name'], ': ', $context['member']['name'], '

    +

    ', $txt['position'], ': ', (!empty($context['member']['group']) ? $context['member']['group'] : $context['member']['post_group']), '

    +

    ', $txt['lastLoggedIn'], ': ', $context['member']['last_login'], '

    '; + + if (!empty($context['member']['bans'])) + { + echo ' +

    ', $txt['user_banned_by_following'], ':

    '; + + foreach ($context['member']['bans'] as $ban) + echo ' +

    ', $ban['explanation'], '

    '; + + } + + echo ' + +

    ', $txt['additional_info'], '

    '; + + if (!$context['user']['is_owner'] && $context['can_send_pm']) + echo ' +

    ', $txt['wireless_profile_pm'], '.

    '; + + if (!$context['user']['is_owner'] && !empty($context['can_edit_ban'])) + echo ' +

    ', $txt['profileBanUser'], '.

    '; + + echo ' +

    ', $txt['wireless_error_home'], '.

    '; + +} + +function template_wap2_ban_edit() +{ + global $context, $settings, $options, $scripturl, $board, $txt, $modSettings; + + echo ' +
    +

    ', $context['ban']['is_new'] ? $txt['ban_add_new'] : $txt['ban_edit'] . ' \'' . $context['ban']['name'] . '\'', '

    +

    + ', $txt['ban_name'], ': + +

    +

    + ', $txt['ban_expiration'], ':
    + ', $txt['never'], '
    + ', $txt['ban_will_expire_within'], ' ', $txt['ban_days'], '
    + ', $txt['ban_expired'], '
    +

    +

    + ', $txt['ban_reason'], ': + +

    +

    + ', $txt['ban_notes'], ':
    + +

    +

    + ', $txt['ban_restriction'], ':
    + ', $txt['ban_full_ban'], '
    + ', $txt['ban_cannot_post'], '
    + ', $txt['ban_cannot_register'], '
    + ', $txt['ban_cannot_login'], ' +

    '; + + if (!empty($context['ban_suggestions'])) + { + echo ' +

    ', $txt['ban_triggers'], '

    +

    + ', $txt['wireless_ban_ip'], ':
    +      +

    '; + + if (empty($modSettings['disableHostnameLookup'])) + echo ' +

    + ', $txt['wireless_ban_hostname'], ':
    +      +

    '; + + echo ' +

    + ', $txt['wireless_ban_email'], ':
    +      +

    +

    + ', $txt['ban_on_username'], ':
    '; + + if (empty($context['ban_suggestions']['member']['id'])) + echo ' +     '; + else + echo ' +     ', $context['ban_suggestions']['member']['name'], ' + '; + + echo ' +

    '; + } + + echo ' + +

    +

    ', $txt['wireless_additional_info'], '

    +

    ', $txt['wireless_error_home'], '.

    '; + + echo ' + + + +
    '; +} + +function template_wap2_below() +{ + global $context, $settings, $options, $txt; + + echo ' + ', $txt['wireless_go_to_full_version'], ' + +'; +} + +?> \ No newline at end of file diff --git a/Themes/default/Xml.template.php b/Themes/default/Xml.template.php new file mode 100644 index 0000000..e7e46a4 --- /dev/null +++ b/Themes/default/Xml.template.php @@ -0,0 +1,389 @@ + + + ', cleanXml($context['message']), ' +'; +} + +function template_quotefast() +{ + global $context, $settings, $options, $txt; + + echo '<', '?xml version="1.0" encoding="', $context['character_set'], '"?', '> + + ', cleanXml($context['quote']['xml']), ' +'; +} + +function template_modifyfast() +{ + global $context, $settings, $options, $txt; + + echo '<', '?xml version="1.0" encoding="', $context['character_set'], '"?', '> + + + +'; + +} + +function template_modifydone() +{ + global $context, $settings, $options, $txt; + + echo '<', '?xml version="1.0" encoding="', $context['character_set'], '"?', '> + + '; + if (empty($context['message']['errors'])) + { + echo ' + ' . $txt['last_edit'] . ': ' . $context['message']['modified']['time'] . ' ' . $txt['by'] . ' ' . $context['message']['modified']['name'] . ' »'), ']]> + + '; + } + else + echo ' + ', $context['message']['errors']), ']]>'; + echo ' + +'; +} + +function template_modifytopicdone() +{ + global $context, $settings, $options, $txt; + + echo '<', '?xml version="1.0" encoding="', $context['character_set'], '"?', '> + + '; + if (empty($context['message']['errors'])) + { + echo ' + ' . $txt['last_edit'] . ': ' . $context['message']['modified']['time'] . ' ' . $txt['by'] . ' ' . $context['message']['modified']['name'] . ' »'), ']]>'; + if (!empty($context['message']['subject'])) + echo ' + '; + } + else + echo ' + ', $context['message']['errors'])), ']]>'; + echo ' + +'; +} + +function template_post() +{ + global $context, $settings, $options, $txt; + + echo '<', '?xml version="1.0" encoding="', $context['character_set'], '"?', '> + + + + + + '; + if (!empty($context['post_error']['messages'])) + foreach ($context['post_error']['messages'] as $message) + echo ' + '; + echo ' + + + + + ', isset($context['post_error']['no_message']) || isset($context['post_error']['long_message']) ? ' + ' : '', ' + + ', isset($context['topic_last_message']) ? $context['topic_last_message'] : '0', ''; + + if (!empty($context['previous_posts'])) + { + echo ' + '; + foreach ($context['previous_posts'] as $post) + echo ' + + + + + ', $post['is_ignored'] ? '1' : '0', ' + '; + echo ' + '; + } + + echo ' +'; +} + +function template_stats() +{ + global $context, $settings, $options, $txt, $modSettings; + + echo '<', '?xml version="1.0" encoding="', $context['character_set'], '"?', '> +'; + foreach ($context['yearly'] as $year) + foreach ($year['months'] as $month); + { + echo ' + '; + foreach ($month['days'] as $day) + echo ' + '; + echo ' + '; + } + echo ' +'; +} + +function template_split() +{ + global $context, $settings, $options; + + echo '<', '?xml version="1.0" encoding="', $context['character_set'], '"?', '> + + + '; + foreach ($context['changes'] as $change) + { + if ($change['type'] == 'remove') + echo ' + '; + else + echo ' + + + + + + '; + } + echo ' +'; +} + +// This is just to hold off some errors if people are stupid. +if (!function_exists('template_button_strip')) +{ + function template_button_strip($button_strip, $direction = 'top', $strip_options = array()) + { + } + function template_menu() + { + } + function theme_linktree() + { + } +} + +function template_results() +{ + global $context, $settings, $options, $txt; + echo '<', '?xml version="1.0" encoding="', $context['character_set'], '"?', '> +'; + + if (empty($context['topics'])) + echo ' + ', $txt['search_no_results'], ''; + else + { + echo ' + '; + + while ($topic = $context['get_topics']()) + { + echo ' + + ', $topic['id'], ' + ', $topic['relevance'], ' + + ', $topic['board']['id'], ' + ', cleanXml($topic['board']['name']), ' + ', $topic['board']['href'], ' + + + ', $topic['category']['id'], ' + ', cleanXml($topic['category']['name']), ' + ', $topic['category']['href'], ' + + '; + foreach ($topic['matches'] as $message) + { + echo ' + + ', $message['id'], ' + + + + ', $message['timestamp'], ' + ', $message['start'], ' + + + ', $message['member']['id'], ' + ', cleanXml($message['member']['name']), ' + ', $message['member']['href'], ' + + '; + } + echo ' + + '; + } + + echo ' + '; + } + + echo ' +'; +} + +function template_jump_to() +{ + global $context, $settings, $options; + + echo '<', '?xml version="1.0" encoding="', $context['character_set'], '"?', '> +'; + foreach ($context['jump_to'] as $category) + { + echo ' + '; + foreach ($category['boards'] as $board) + echo ' + '; + } + echo ' +'; +} + +function template_message_icons() +{ + global $context, $settings, $options; + + echo '<', '?xml version="1.0" encoding="', $context['character_set'], '"?', '> +'; + foreach ($context['icons'] as $icon) + echo ' + '; + echo ' +'; +} + +function template_check_username() +{ + global $context, $settings, $options, $txt; + + echo '<', '?xml version="1.0" encoding="', $context['character_set'], '"?', '> + + ', cleanXml($context['checked_username']), ' +'; +} + +// This prints XML in it's most generic form. +function template_generic_xml() +{ + global $context, $settings, $options, $txt; + + echo '<', '?xml version="1.0" encoding="', $context['character_set'], '"?', '>'; + + // Show the data. + template_generic_xml_recursive($context['xml_data'], 'smf', '', -1); +} + +// Recursive function for displaying generic XML data. +function template_generic_xml_recursive($xml_data, $parent_ident, $child_ident, $level) +{ + // This is simply for neat indentation. + $level++; + + echo "\n" . str_repeat("\t", $level), '<', $parent_ident, '>'; + + foreach ($xml_data as $key => $data) + { + // A group? + if (is_array($data) && isset($data['identifier'])) + template_generic_xml_recursive($data['children'], $key, $data['identifier'], $level); + // An item... + elseif (is_array($data) && isset($data['value'])) + { + echo "\n", str_repeat("\t", $level), '<', $child_ident; + + if (!empty($data['attributes'])) + foreach ($data['attributes'] as $k => $v) + echo ' ' . $k . '="' . $v . '"'; + echo '>'; + } + + } + + echo "\n", str_repeat("\t", $level), ''; +} + +function template_webslice_header_above() +{ + global $settings; + + echo ' + '; +} + +function template_webslice_header_below() +{ +} + +// This shows a webslice of the recent posts. +function template_webslice_recent_posts() +{ + global $context, $scripturl, $txt; + + echo ' +
    +
    + ', cleanXml($txt['recent_posts']), ' +
    '; + + $alternate = 0; + foreach ($context['recent_posts_data'] as $item) + { + echo ' +
    + ', cleanXml($item['subject']), ' ', cleanXml($txt['by']), ' ', cleanXml(!empty($item['poster']['link']) ? '' . $item['poster']['name'] . '' : $item['poster']['name']), ' +
    '; + $alternate = !$alternate; + } + + echo ' +
    +
    +
    '; + + if ($context['user']['is_guest']) + echo ' + ', $txt['login'], ''; + else + echo ' + ', cleanXml($context['user']['name']), ', ', cleanXml($txt['msg_alert_you_have']), ' ', cleanXml($context['user']['messages']), ' ', cleanXml($context['user']['messages'] != 1 ? $txt['msg_alert_messages'] : $txt['message_lowercase']), '', cleanXml($txt['newmessages4'] . ' ' . $context['user']['unread_messages']), ' ', cleanXml($context['user']['unread_messages'] == 1 ? $txt['newmessages0'] : $txt['newmessages1']); + + echo ' +
    +
    '; +} + +?> \ No newline at end of file diff --git a/Themes/default/css/admin.css b/Themes/default/css/admin.css new file mode 100644 index 0000000..f900d77 --- /dev/null +++ b/Themes/default/css/admin.css @@ -0,0 +1,627 @@ +/* Styles for the admin quick search. +------------------------------------------------------- */ + +#quick_search form, h3.catbg #quick_search form +{ + padding: 7px; + line-height: 0.9em; + font-size: 0.8em !important; +} + +ol.search_results +{ + margin-top: 0; + padding-top: 0; +} +ol.search_results li +{ + padding-top: 1em; + border-bottom: 1px solid #ccc; +} + +/* Styles for the core features screen. +------------------------------------------------------- */ +.features +{ + padding: 0 1em !important; + overflow: auto; +} +.features_image +{ + float: left; + margin: 0 2em 0.5em 1em; +} +.features_switch +{ + margin: 0.2em 1em 1em 1em; + float: right; +} +.features h4 +{ + padding: 1em 0 0.5em 0.5em; + margin: 0; + font-size: 1.1em; +} +.features p +{ + padding: 0 1em; + margin: 0; +} + +/* Styles for the admin home screen bar. +------------------------------------------------------- */ +#admin_main_section +{ + overflow: hidden; + margin: 1em 0; +} +#admincenter .content +{ + padding: 1em; +} + +#live_news +{ + width: 64%; + font-size: 0.85em; +} +#live_news div.content +{ + padding: 0; +} +#live_news div.content dl +{ + padding: 0.5em 0 0 0.5em; +} + +#supportVersionsTable +{ + width: 34%; +} +#version_details +{ + overflow: auto; + height: 9.5em; +} +#smfAnnouncements +{ + height: 13.5em; + padding: 0 0.5em; + overflow: auto; +} +#smfAnnouncements dt +{ + border-bottom: 1px dashed #000; +} +#smfAnnouncements dd +{ + padding: 0; + margin: 0 0 1em 1.5em; +} +#update_section +{ + margin: 0.5em 0 0; +} + +#quick_tasks, #quick_tasks ul +{ + margin: 0; + padding: 0; +} +#quick_tasks li +{ + float: left; + list-style-type: none; + margin: 0; + padding: 0.5em 0; + width: 49.5%; + height: 4.5em; +} +.quick_task +{ + display: block; + width: 100%; + margin: 0 1em; + padding: 0; +} +.home_image +{ + float: left; + margin: 0 1em 1em 1em; +} + +/* Common admin center classes. +------------------------------------------------------- */ +hr.hrcolor +{ + margin: 10px 0; +} +h3.titlebg form +{ + font-size: 80%; +} +.windowbg.nopadding +{ + margin: 0.3em 0 0 0; + padding: 0; +} +.windowbg ol +{ + margin-top: 0; + margin-bottom: 0; +} + +.table_caption, tr.table_caption td +{ + color: #000; + font-size: 10px; + font-weight: bold; +} +.additional_row div.floatleft +{ + padding: 0 0.8em; +} +fieldset +{ + margin-bottom: 0.5em; + border: 1px solid #cacdd3; + padding: 0.5em; +} +fieldset dl +{ + margin: 0; +} +legend +{ + font-weight: bold; + color: #000; +} +.information a +{ + font-weight: bold; +} + +/* Styles for the package manager. +------------------------------------------------- */ +#package_list .tborder +{ + margin: .25em 0 .25em 26px; +} +#package_list ol, #package_list ol li +{ + list-style: decimal; + margin-left: 50px; + border: none; +} +#package_list ol ul, #package_list ol ul li +{ + margin-left: 0; + list-style: none; +} +#package_list +{ + list-style-type: none; +} +#package_list li +{ + border: 1px solid #cacdd3; + padding: 0.2em; + margin: 1px; +} +.description +{ + max-height: 15em; + overflow: auto; + padding-bottom: .5em; +} +.information +{ + max-height: 15em; + overflow: auto; + padding-bottom: .5em; +} +.package_section +{ + border: 1px solid #cacdd3; +} +ul.packages li +{ + border: none !important; + list-style-type: none; +} +code#find_code, code#replace_code +{ + display: block; + font-family: "dejavu sans mono", "monaco", "lucida console", "courier new", monospace; + font-size: x-small; + background: #eef; + line-height: 1.5em; + padding: 3px 1em; + overflow: auto; + white-space: pre; + /* Show a scrollbar after about 24 lines. */ + max-height: 24em; +} +span.package_server +{ + padding: 0 3em; +} +ul.package_servers +{ + margin: 0; + padding: 0; +} +ul.package_servers li +{ + list-style-type: none; +} +pre.file_content +{ + overflow: auto; + width: 100%; + padding-bottom: 1em; +} +.operation +{ + padding: 0 1em; +} + +/* Styles for the file permissions section. +------------------------------------------------- */ +.filepermissions +{ + font-size: 0.8em; + white-space: nowrap; +} +.fperm +{ + display: block; + width: 35%; + text-align: center; +} +.perm_read +{ + background-color: #d1f7bf; +} +.perm_write +{ + background-color: #ffbbbb; +} +.perm_execute +{ + background-color: #fdd7af; +} +.perm_custom +{ + background-color: #c2c6c0; +} +.perm_nochange +{ + background-color: #eee; +} + +/* Styles for the BBC permissions +------------------------------------------------- */ +.list_bbc +{ + width: 33%; +} + +/* Styles for the manage boards section. +------------------------------------------------- */ +#manage_boards ul +{ + padding: 0; + margin: 0 0 0.6em 0; + max-height: 30em; + overflow: auto; +} +#manage_boards li +{ + list-style-type: none; + border: 1px solid #cacdd3; + padding: 0.2em; + margin: 1px; + clear: right; +} +#manage_boards li img +{ + vertical-align: middle; + padding-bottom: 3px; +} +#manage_boards li#recycle_board +{ + background-color: #dee; +} +.move_links +{ + padding: 0 13px 0 0; +} +.modify_boards +{ + padding: 0 0.5em; +} +#manage_boards span.post_group, #manage_boards span.regular_members +{ + border-bottom: 1px dotted #000; + cursor: help; +} + +/* Styles for the manage members section. +------------------------------------------------- */ +.msearch_details +{ + display: block; + width: 49%; +} +dl.right dt +{ + padding-right: 10px; +} + +/* Styles for the manage maintenance section. +------------------------------------------------- */ +.maintenance_finished, #task_completed +{ + margin: 1ex; + padding: 1ex 2ex; + border: 1px dashed green; + color: green; + background: #efe; +} +/* Styles for the manage calendar section. +------------------------------------------------- */ +dl.settings dt.small_caption +{ + width: 20%; +} +dl.settings dd.small_caption +{ + width: 79%; +} +/* Styles for the manage permissions section. +------------------------------------------------- */ +dl.admin_permissions dt +{ + width: 35%; +} +dl.admin_permissions dd +{ + width: 64%; +} + +/* Styles for the manage search section. +------------------------------------------------- */ +dl.settings dt.large_caption +{ + width: 70%; +} +dl.settings dd.large_caption +{ + width: 29%; +} +span.search_weight +{ + width: 40px; + padding: 0 0.5em; + text-align: right; + display: inline-block; +} +.search_settings +{ + width: 47%; +} + +/* Styles for the manage bans section. +------------------------------------------------- */ +.ban_restriction +{ + margin: 0.2em 0 0.2em 2.2em; +} +.ban_settings +{ + width: 46%; +} +#manage_bans dl +{ + margin-bottom: 1em; +} +#manage_bans fieldset dl.settings +{ + margin-bottom: 0; +} + +/* Styles for the manage subscriptions section. +------------------------------------------------- */ +#fixed_area +{ + width: 97%; +} +ul.pending_payments +{ + margin: 0; + padding: 0; +} +ul.pending_payments li +{ + list-style-type: none; +} + +/* Styles for the manage permissions section. +------------------------------------------------- */ +.perm_name, .perm_profile, .perm_board +{ + display: block; + width: 40%; +} +.perm_boards +{ + padding: 0; + margin: 0 0 0.6em 0; +} +.perm_boards li +{ + list-style-type: none; + border: 1px solid #cacdd3; + padding: 0.2em; + margin: 1px; +} +.perm_groups +{ + background-color: #fff; +} +.perm_classic +{ + margin: 0.2em; +} +.permission_groups +{ + padding: 0; + margin: 0; +} +.permission_groups li +{ + list-style-type: none; + padding: 0.2em; + margin: 1px; +} +.perms +{ + width: 20px; + display: inline-block; + text-align: center; +} + +/* Styles for the themes section. +------------------------------------------------- */ +ul.theme_options +{ + padding: 0; + margin: 0; +} +ul.theme_options li +{ + list-style: none; + padding: 0.4em; +} +.is_directory +{ + padding-left: 18px; + background: url(../images/admin/boards.gif) no-repeat; +} +.edit_file +{ + width: 96%; + font-family: monospace; + margin-top: 1ex; + white-space: pre; +} + +dl.themes_list +{ + margin: 0; +} +dl.themes_list dt +{ + margin-bottom: 3px; +} +dl.themes_list dd +{ + font-style: italic; + white-space: nowrap; +} + +/* Styles for the registration center. +------------------------------------------------- */ +.agreement, .reserved_names +{ + padding: 0; +} +#agreement, #reserved +{ + width: 99%; +} + +/* Styles for the moderation center. +------------------------------------------------- */ +#modcenter +{ + display: block; + width: 100%; +} +.modblock_left +{ + width: 49%; + float: left; + clear: right; + margin: 0 0 1em 0; +} +.modblock_right +{ + width: 49%; + float: right; + margin: 0 0 1em 0; +} + +.modbox +{ + height: 150px; + overflow: auto; +} +/* Moderation Notes */ +ul.moderation_notes +{ + margin: 0; + padding: 0; + list-style: none; + overflow: auto; + height: 8.5em; +} +ul.moderation_notes li +{ + padding: 4px 0 4px 4px; + border-bottom: 1px solid #cccccc; +} +.notes +{ + margin: 0.5em 0; +} +.post_note +{ + width: 85%; +} + +/* Styles for the error log. +------------------------------------------------- */ + +h3.grid_header +{ + height: 25px; +} +#error_log +{ + width: 100%; +} +#error_log tr.windowbg td, #error_log tr.windowbg2 td +{ + padding: 8px; + line-height: 160%; +} +#error_log td.half_width +{ + width: 50%; +} +#error_log td.checkbox_column +{ + width: 15px; + vertical-align: top; + text-align: center; +} +#error_log td div.marginleft +{ + margin: 0 0 0 1ex; +} +#manage_boards span.botslice, #manage_maintenance span.botslice, #manage_mail span.botslice +{ + margin-bottom: 4px; +} diff --git a/Themes/default/css/compat.css b/Themes/default/css/compat.css new file mode 100644 index 0000000..7fe7e32 --- /dev/null +++ b/Themes/default/css/compat.css @@ -0,0 +1,2416 @@ +/************************************************************************************************** + This file will *attempt* to make themes designed for older versions of SMF usable with SMF 2.0. + Unfortunately, the end result will be far from perfect, in most cases. Therefore, we encourage + theme designers to rebase their themes on either the default or core theme. +**************************************************************************************************/ + +/* Styles for the general looks of things +------------------------------------------ */ + +/* Help popups require a different styling of the body element. */ +body#help_popup +{ + width: auto; + padding: 1em; + min-width: 0; +} + +/* The main content area. +------------------------------------------------------- */ +.content, .roundframe +{ + padding: 0.5em 1.2em; + margin: 0; + border: 1px solid #adadad; + color: #000; + background-color: #ecedf3; +} +.content p, .roundframe p +{ + margin: 0 0 0.5em 0; +} +.content fieldset +{ + border: 2px groove #fff; + padding: 1em; + margin: 0 0 0.3em 0; +} + +/* Reset header margins. */ +h1, h2, h3, h4, h5, h6 +{ + font-size: 1em; + margin: 0; + padding: 0; +} + +/* Alternative for u tag */ +.underline +{ + text-decoration: underline; +} + +/* Common classes for easy styling. +------------------------------------------------------- */ + +.floatright +{ + float: right; +} +.floatleft +{ + float: left; +} + +.flow_auto +{ + overflow: auto; +} +.flow_hidden +{ + overflow: hidden; +} +.clear +{ + clear: both; +} +.clear_left +{ + clear: left; +} +.clear_right +{ + clear: right; +} + +/* Default font sizes: small (8pt), normal (10pt), and large (14pt). */ +.smalltext, tr.smalltext th +{ + font-size: 0.85em; + font-family: verdana, sans-serif; +} +.middletext +{ + font-size: 0.9em; + font-family: verdana, sans-serif; +} +.normaltext +{ + font-size: 1em; + line-height: 1.2em; +} +.largetext +{ + font-size: 1.4em; +} +.centertext +{ + margin: 0 auto; + text-align: center; +} +.righttext +{ + margin-left: auto; + margin-right: 0; + text-align: right; +} +.lefttext +{ + margin-left: 0; + margin-right: auto; + text-align: left; +} +/* some common padding styles */ +.padding +{ + padding: 0.7em; +} +.main_section, .lower_padding +{ + padding-bottom: 0.5em; +} +/* a quick reset list class. */ +ul.reset, ul.reset li +{ + padding: 0; + margin: 0; + list-style: none; +} + +/* the page navigation area */ +.pagesection +{ + font-size: 0.9em; + padding: 0.5em; + overflow: hidden; +} +.pagesection .pagelinks +{ + padding: 0.5em 0; +} + +/* GenericList */ +table.table_grid thead tr.catbg th.smalltext +{ + white-space: nowrap; +} + +.custom_fields_above_signature +{ + clear: right; + padding: 1em 0 3px 0; + width: 98%; + border-top: 1px solid #666; + line-height: 1.4em; + font-size: 0.85em; +} + +/* Semantic classes introduced per RC2, used as alternatives for .windowbg and .windowbg2 +------------------------------------------------------------------------------------------ */ +.description +{ + padding: 1em; + font-size: 0.9em; + line-height: 1.5em; + border: 1px solid #bbb; + background: #f5f5f0; + margin: 0 0 1em 0; +} +.information +{ + padding: 0.5em 1em; + font-size: 0.9em; + line-height: 1.5em; + border: 1px solid #bbb; + background: #f0f6f0; + margin: 0 0 1em 0; +} +.information p +{ + padding: 1em; + margin: 0; +} + +/* Lists with settings use these a lot. +------------------------------------------------------- */ +dl.settings +{ + clear: right; + overflow: auto; + margin: 0 0 10px 0; + padding: 0; +} +dl.settings dt +{ + width: 48%; + float: left; + margin: 0 0 10px 0; + padding: 0; + clear: both; +} +dl.settings dt.settings_title +{ + width: 100%; + float: none; + margin: 0 0 10px 0; + padding: 5px 0 0 0; + font-weight: bold; + clear: both; +} +dl.settings dt.windowbg +{ + width: 98%; + float: left; + margin: 0 0 3px 0; + padding: 0 0 5px 0; + clear: both; +} +dl.settings dd +{ + width: 48%; + float: left; + margin: 0 0 3px 0; + padding: 0; +} +dl.settings img +{ + margin: 0 10px 0 0; +} + +/* Styles for a very basic dropdown menu implementation. +------------------------------------------------------- */ +div#admin_menu +{ + margin: 1em 0 0 0; +} + +ul.dropmenu, ul.dropmenu li ul +{ + margin: 0; + padding: 0; + list-style: none; +} +ul.dropmenu +{ + margin: 0 0 0 15px; +} +ul.dropmenu li +{ + position: relative; + float: left; + padding-right: 4px; + text-transform: uppercase; +} +ul.dropmenu li a.firstlevel +{ + margin: 0; + padding: 5px; + cursor: default; + font-size: x-small; + color: #000; + background: #f0f0f0; + border: 1px solid #818181; + text-decoration: none; +} +ul.dropmenu li a.active +{ + padding-left: 3px; +} +ul.dropmenu li a.active.firstlevel +{ + background: #819db5; + color: #fff; +} +ul.dropmenu li ul li +{ + background: none; + width: 14em; + float: none; + margin: 0; + padding: 0; +} +ul.dropmenu li ul +{ + margin: 5px 0 0 0; + z-index: 90; + display: none; + position: absolute; + top: 100%; + border: 1px solid #808080; + background: #f8f8fb; +} +ul.dropmenu li ul li ul, ul.dropmenu li ul li.over ul +{ + display: none; + position: absolute; + left: -999em; + top: 0; + border: 1px solid #a0a0a0; + background: #fff; +} +ul.dropmenu li ul li a +{ + display: block; + padding: 5px; + font-size: x-small; + text-decoration: none; + background: none; + text-transform: none; + color: #000; +} +ul.dropmenu li ul li a.active +{ + font-weight: bold; +} +ul.dropmenu li ul li a:hover, #dropmenu ul li ul li:hover +{ + background: #c8e2fb; +} +ul.dropmenu li:hover ul, ul.dropmenu li.over ul +{ + display: block; +} +ul.dropmenu li ul li:hover ul, ul.dropmenu li ul li.over ul +{ + display: block; + left: 13em; +} + +/* The dropdown menu toggle image */ +#menu_toggle +{ + float: right; + margin-right: 10px; + padding-top: 3px; +} +#menu_toggle span +{ + position: relative; + right: 5000px; +} + +.generic_tab_strip +{ + margin: 0 1em 2em; +} +.generic_tab_strip .buttonlist +{ + float: left !important; +} + + +/* The linktree. +----------------- */ +ul.linktree +{ + clear: both; + list-style: none; + margin: 1.5em 0.5em 0.5em 0.5em; + padding: 0; +} +ul.linktree li +{ + margin: 0; + padding: 0; + display: inline; + font-size: 0.8em; +} +ul.linktree li a +{ + color: #000; +} +ul.linktree li a:hover +{ + color: #cc3333; +} +ul.linktree li span +{ + font-weight: bold; +} + +/* Styles for a typical table. +------------------------------------------------------- */ +table.table_list +{ + width: 100%; +} +table.table_list p +{ + padding: 0; + margin: 0; +} +table.table_list td,table.table_list th +{ + padding: 5px; +} +table.table_list tbody.header td +{ + padding: 0; +} +table.table_list tbody.content td.stats +{ + font-size: 90%; + width: 15%; + text-align: center; +} +table.table_list tbody.content td.lastpost +{ + line-height: 1.2em; + font-size: 85%; + width: 24%; +} +table.table_list tbody.content td.icon +{ + text-align: center; + width: 6%; +} + +/* Styles for headers used in Curve templates. +------------------------------------------------------- */ +h3.catbg, h3.catbg2, h3.titlebg, h4.titlebg, h4.catbg, div.titlebg, .table_list tbody.header td +{ + overflow: hidden; + line-height: 2em; + font-weight: bold; +} +h3.titlebg, h4.titlebg, h3.catbg, h4.catbg +{ + border-left: 1px solid #adadad; + border-right: 1px solid #adadad; +} +h3.titlebg, h4.catbg +{ + padding: 0 0 0 0.5em; +} +h3.catbg img.icon, div.titlebg img.icon, h3.catbg img +{ + float: left; + margin: 5px 8px 0 0; +} +h4.catbg a.toggle img +{ + vertical-align: middle; + margin: -2px 5px 0 5px; +} + +/* Styles for the board index. +------------------------------------------------- */ + +p#stats +{ + text-align: right; +} +h3#newsfader +{ + font-size: 1em; +} +#smfNewsFader +{ + font-weight: bold; + line-height: 1.4em; + padding: 1em; + font-size: 1em; + text-align: center; +} +#upshrink_ic +{ + margin-right: 2ex; + text-align: right; +} +.categoryframe +{ + margin-top: 0.4em; +} +.categoryframe h3 +{ + margin: 0; +} +table.boardsframe +{ + width: 100%; +} +table.boardsframe td.icon +{ + text-align: center; + padding: 0.5em; + width: 6%; +} +table.boardsframe td.info +{ + width: 60%; + padding: 0; +} +table.boardsframe td.info h4 +{ + padding: 0.4em 0.4em 0 0.4em; + margin: 0; +} +table.boardsframe td.info p +{ + padding: 0 0.4em 0.5em 0.4em; + margin: 0; +} +table.boardsframe td.info p.moderators +{ + font-size: 0.8em; + font-family: verdana, sans-serif; +} +table.boardsframe td.stats +{ + width: 8%; + vertical-align: middle; + text-align: center; +} +table.boardsframe td.lastpost +{ + width: 20%; + vertical-align: top; + padding: 0.5em; +} +#posticons +{ + clear: both; + width: 100%; +} +#posticons .buttonlist +{ + margin-right: 1em; + float: right; +} + +/* the newsfader */ +#smfFadeScroller +{ + text-align: center; + overflow: auto; + color: #000000; /* shouldn't be shorthand style due to JS bug in IE! */ +} + +/* Styles for the info center on the board index. +---------------------------------------------------- */ + +#infocenterframe +{ + margin-top: 2em; + clear: both; +} +/* each section in infocenter has this class */ +.infocenter_section +{ + clear: both; +} +.infocenter_section p.section +{ + display: block; + margin: 0; + width: 30px; + text-align: center; + float: left; + padding: 0.5em 0 0 0; +} +.infocenter_section div.sectionbody +{ + margin-left: 30px; + padding: 0.3em; + border-left: 1px solid #a0a0a0; + min-height: 25px; + height: auto !important; +} +/* recent posts - or just one recent post */ +dl#infocenter_recentposts +{ + float: left; + width: 100%; + padding: 0; + margin: 0; +} +dl#infocenter_recentposts dt +{ + clear: left; + float: left; + padding: 0.1em; + width: 68%; + white-space: nowrap; + overflow: hidden; +} +dl#infocenter_recentposts dd +{ + clear: right; + float: right; + padding: 0.1em; + width: 25%; + text-align: right; + white-space: nowrap; + overflow: hidden; +} +/* login form */ +form#infocenter_login ul.horizlist label +{ + white-space: nowrap; + font-size: 90%; + font-weight: bold; +} + +/* Styles for the message (topic) index. +---------------------------------------------------- */ + +#childboards table +{ + width: 100%; +} +.modbuttons +{ + clear: both; + width: 100%; +} +.buttonlist, .buttonlist_bottom +{ + margin-right: 1em; + float: right; +} +#messageindex td.icon1, #messageindex td.icon2 +{ + text-align: center; + padding: 0.5em; + width: 5%; +} +#messageindex td.subject +{ + padding: 0.5em; +} +#messageindex td.starter +{ + text-align: center; + padding: 0.5em; + width: 14%; +} +#messageindex td.replies +{ + text-align: center; + padding: 0.5em; + width: 4%; +} +#messageindex td.views +{ + text-align: center; + padding: 0.5em; + width: 4%; +} +#messageindex td.lastpost +{ + padding: 0.5em; + width: 22%; +} +#messageindex td.moderation +{ + text-align: center; + padding: 0.5em; + width: 4%; +} +#topic_icons p +{ + display: block; + padding: 0.5em 0.5em 0.1em 0.5em; + margin: 0; + border-bottom: none; + font-weight: normal !important; +} +#topic_icons ul +{ + display: block; + padding: 0.5em 1em 0.1em 1em; + margin: 0; + border-bottom: none; + font-weight: normal !important; +} +#message_index_jump_to +{ + margin: 2em 4em 0 2em; +} +.lastpost img +{ + float: right; +} + +/* Styles for the display template (topic view). +---------------------------------------------------- */ +.linked_events +{ + clear: both; + margin: 1em 0; +} +.linked_events .edit_event +{ + color: #f00; +} +#moderationbuttons +{ + margin-left: 0.5em; +} +#postbuttons .nav, #postbuttons_lower .nav +{ + margin: 0.5em 0.5em 0 0; + text-align: right; +} +#postbuttons_lower .nav +{ + margin: 0 0.5em 0.5em 0; +} +#postbuttons, #postbuttons_lower +{ + text-align: right; +} + +/* Poll question */ +h4#pollquestion +{ + padding: 1em 0 1em 2em; +} + +/* Poll vote options */ +#poll_options ul.options +{ + border-top: 1px solid #696969; + padding: 1em 2.5em 0 2em; + margin: 0 0 1em 0; +} +#poll_options div.submitbutton +{ + clear: both; + padding: 0 0 1em 2em; +} + +#poll_options div.submitbutton.border +{ + border-bottom: 1px solid #696969; + margin: 0 0 1em 0; +} + +/* Poll results */ +#poll_options dl.options +{ + border: solid #696969; + border-width: 1px 0; + padding: 1em 2.5em 0 2em; + margin: 0 0 1em 0; +} +#poll_options dl.options dt.voted +{ + font-weight: bold; +} +#poll_options dl.options dd +{ + margin: 0.5em 0 1em 0; +} + +/* Poll notices */ +#poll_options p +{ + margin: 0 1.5em 0.2em 1.5em; + padding: 0 0.5em 0.5em 0.5em; +} + +div#pollmoderation +{ + margin: -1em 0 0 2em; + padding: 0; +} + +.approve_post +{ + margin: 2ex; + padding: 1ex; + border: 2px dashed #cc3344; + color: #000; + font-weight: bold; +} +#forumposts h3.catbg3 +{ + font-weight: normal; + padding: 0.4em; + overflow: hidden; +} +#forumposts h3.catbg3 img +{ + float: left; + vertical-align: middle; +} +#forumposts h3.catbg3 span +{ + float: left; + padding-left: 2%; +} +#forumposts h3.catbg3 span#top_subject +{ + padding-left: 9.5em; +} +.poster +{ + width: 15em; + float: left; +} +.post +{ + clear: right; +} +.postarea +{ + margin-left: 16em; +} +.messageicon +{ + float: left; + margin: 0 0.5em 0.5em 0; +} +.messageicon img +{ + padding: 6px 3px; +} +.keyinfo +{ + float: left; + clear: none; + width: 50%; + min-height: 3em; +} +ul.postingbuttons +{ + float: right; + padding: 0 0.5em 0 0; +} +ul.postingbuttons li +{ + float: left; + margin: 0 0.5em 0 0; +} +.modifybutton +{ + float: right; + margin: 0 0.5em 0.5em 0; +} +.attachments hr +{ + clear: both; + margin: 1em 0 1em 0; +} +.attachments +{ + padding-top: 1em; +} +.postfooter +{ + margin-left: 16em; +} +.topborder +{ + border-top: 1px solid #bbb; +} +.moderatorbar +{ + clear: right; + margin: 1em 0 0 16em; +} +#pollmoderation, #moderationbuttons_strip +{ + float: left; +} + +/* Styles for the quick reply area. +---------------------------------------------------- */ + +#quickReplyOptions #quickReplyWarning +{ + border: none; + text-align: left; + margin: 0; + width: 25%; + float: left; +} +#quickReplyOptions #quickReplyContent +{ + text-align: right; + float: left; + width: 67.5%; + padding: 1em; + border-left: 1px solid #aaa; +} + +#quickReplyOptions #quickReplyContent textarea, #quickReplyOptions #quickReplyContent input +{ + margin-bottom: .5em; +} + +#quickReplyWarning +{ + width: 20%; + float: left; + padding: 0.5em 1em; +} +#quickReplyContent +{ + width: 75%; + float: right; + padding: 0.5em 0; +} +#quickReplyOptions .roundframe +{ + overflow: hidden; +} + +/* The jump to box */ +#display_jump_to +{ + clear: both; + padding: 5px; +} + +/* Separator of posts. More useful in the print stylesheet. */ +#forumposts .post_separator +{ + display: none; +} + +/* Styles for edit post section +---------------------------------------------------- */ +form#postmodify .roundframe +{ + padding: 0 12%; +} +#post_header +{ + margin-bottom: 0.5em; + border-bottom: 1px solid #666; + padding: 0.5em; + overflow: hidden; +} +#post_header dt +{ + float: left; + margin: 0; + padding: 0; + width: 15%; + margin: .3em 0; + font-weight: bold; +} +#post_header dd +{ + float: left; + margin: 0; + padding: 0; + width: 83%; + margin: .3em 0; +} +#post_header img +{ + vertical-align: middle; +} +ul.post_options +{ + margin: 0 0 0 1em; + padding: 0; + list-style: none; + overflow: hidden; +} +ul.post_options li +{ + margin: 0.2em 0; + width: 49%; + float: left; +} +#postAdditionalOptionsHeader +{ + margin-top: 1em; +} +#postMoreOptions +{ + border-bottom: 1px solid #666; + padding: 0.5em; +} +#postAttachment, #postAttachment2 +{ + overflow: hidden; + margin: .5em 0; + padding: 0; + border-bottom: 1px solid #666; + padding: 0.5em; +} +#postAttachment dd, #postAttachment2 dd +{ + margin: .3em 0 .3em 1em; +} +#postAttachment dt, #postAttachment2 dt +{ + font-weight: bold; +} +#postAttachment3 +{ + margin-left: 1em; +} +#post_confirm_strip, #shortcuts +{ + padding: 1em 0 0 0; +} +.post_verification +{ + margin-top: .5em; +} +.post_verification #verification_control +{ + margin: .3em 0 .3em 1em; +} +/* The BBC buttons */ +#bbcBox_message +{ + margin: 1em 0 0.5em 0; +} +#bbcBox_message div +{ + margin: 0.2em 0; + vertical-align: top; +} +#bbcBox_message div img +{ + margin: 0 1px 0 0; + vertical-align: top; +} +#bbcBox_message select +{ + margin: 0 2px; +} +/* The smiley strip */ +#smileyBox_message +{ + margin: 0.75em 0 0.5em 0; +} + +/* Styles for edit event section +---------------------------------------------------- */ +#post_event .roundframe +{ + padding: 1% 12%; +} +#post_event fieldset +{ + margin-bottom: 0.5em; + border: none; + border-bottom: 1px solid #666; + padding: 0.5em; + clear: both; +} +#post_event legend +{ + font-weight: bold; + color: #000; +} +#post_event div.event_options +{ + width: 49%; + float: left; +} +#post_event ul.event_main, ul.event_options +{ + padding: 0; + overflow: hidden; +} +#post_event ul.event_main li +{ + list-style-type: none; + margin: 0.2em 0; + width: 49%; + float: left; +} +#post_event ul.event_options +{ + margin: 0; + padding: 0 0 .7em .7em; +} +#post_event ul.event_options li +{ + list-style-type: none; + margin: 0.3em 0 0 0; +} + +/* Styles for edit poll section. +---------------------------------------------------- */ + +#edit_poll fieldset +{ + margin-bottom: 0.5em; + border: none; + border-bottom: 1px solid #666; + padding: 0.5em; + clear: both; +} +#edit_poll legend +{ + font-weight: bold; + color: #000; +} +#edit_poll ul.poll_main, dl.poll_options +{ + overflow: hidden; + padding: 0 0 0 .7em; + list-style: none; +} +#edit_poll ul.poll_main li +{ + margin: 0.2em 0; +} +#edit_poll dl.poll_options dt +{ + width: 35%; +} +#edit_poll dl.poll_options dd +{ + width: 63%; +} + +/* Styles for the recent messages section. +---------------------------------------------------- */ + +.readbuttons +{ + clear: both; + width: 100%; +} +.buttonlist, .buttonlist_bottom +{ + margin-right: 1em; + float: right; +} + +/* Styles for the move topic section. +---------------------------------------------------- */ + +#move_topic dl +{ + margin-bottom: 0; +} +.move_topic +{ + width: 710px; + margin: auto; + text-align: left; +} +div.move_topic fieldset +{ + margin: 0.5em 0; + border: 1px solid #cacdd3; + padding: 0.5em; +} + +/* Styles for the send topic section. +---------------------------------------------------- */ + +fieldset.send_topic +{ + margin-bottom: 0.5em; + border: none; + padding: 0.5em; +} +dl.send_topic +{ + margin-bottom: 0; +} +dl.send_mail dt +{ + width: 35%; +} +dl.send_mail dd +{ + width: 64%; +} + +/* Styles for the split topic section. +---------------------------------------------------- */ + +div#selected, div#not_selected +{ + width: 49%; +} +ul.split_messages li.windowbg, ul.split_messages li.windowbg2 +{ + border: 1px solid #adadad; + padding: 1em; + margin: 1px; +} +ul.split_messages li a.split_icon +{ + padding: 0 0.5em; +} +ul.split_messages div.post +{ + padding: 1em 0 0 0; + border-top: 1px solid #fff; +} + +/* Styles for the merge topic section. +---------------------------------------------------- */ + +ul.merge_topics li +{ + list-style-type: none; +} +dl.merge_topic dt +{ + width: 25%; +} +dl.merge_topic dd +{ + width: 74%; +} +fieldset.merge_options +{ + margin-bottom: 0.5em; +} +fieldset.merge_options legend +{ + font-weight: bold; +} +.custom_subject +{ + margin: 0.5em 0; +} + +/* Styles for the login areas. +------------------------------------------------------- */ +.login +{ + width: 540px; + margin: 0 auto; +} +.login dl +{ + overflow: auto; + clear: right; +} +.login dt, .login dd +{ + margin: 0 0 0.4em 0; + width: 44%; + padding: 0.1em; +} +.login dt +{ + float: left; + clear: both; + text-align: right; + font-weight: bold; +} +.login dd +{ + width: 54%; + float: right; + text-align: left; +} +.login p +{ + text-align: center; +} +.login h3 img +{ + float: left; + margin: 4px 0.5em 0 0; +} + +/* Styles for the registration section. +------------------------------------------------------- */ +.register_error +{ + border: 1px dashed red; + padding: 5px; + margin: 0 1ex 1ex 1ex; +} +.register_error span +{ + text-decoration: underline; +} + +/* Additional profile fields */ +dl.register_form +{ + margin: 0; + clear: right; + overflow: auto; +} + +dl.register_form dt +{ + font-weight: normal; + float: left; + clear: both; + width: 50%; + margin: 0.5em 0 0 0; +} + +dl.register_form dt strong +{ + font-weight: bold; +} + +dl.register_form dt span +{ + display: block; +} + +dl.register_form dd +{ + float: left; + width: 49%; + margin: 0.5em 0 0 0; +} + +#confirm_buttons +{ + text-align: center; + padding: 1em 0; +} + +.coppa_contact +{ + padding: 4px; + width: 32ex; + background-color: #fff; + color: #000; + margin-left: 5ex; + border: 1px solid #000; +} + +/* Styles for maintenance mode. +------------------------------------------------------- */ +#maintenance_mode +{ + width: 75%; + min-width: 520px; + text-align: left; +} +#maintenance_mode img.floatleft +{ + margin-right: 1em; +} + +/* common for all admin sections */ +h3.titlebg img +{ + vertical-align: middle; + margin-right: 0.5em; +} +tr.titlebg td +{ + padding-left: 0.7em; +} +#admin_menu +{ + min-height: 2em; + padding-left: 0; +} +#admin_content +{ + clear: left; +} +#admin_login .centertext +{ + padding: 1em; +} +#admin_login .centertext .error +{ + padding: 0 0 1em 0; +} + +/* Styles for sidebar menus. +------------------------------------------------------- */ +.left_admmenu, .left_admmenu ul, .left_admmenu li +{ + padding: 0; + margin: 0; + list-style: none; +} +#left_admsection +{ + background-color: #ecedf3; + padding: 1px; + border: 1px solid #ADADAD; + width: 160px; + float: left; + margin-right: 10px; +} +.adm_section h4.titlebg +{ + font-size: 95%; + margin-bottom: 5px; +} +.left_admmenu li +{ + padding: 0 0 0 0.5em; +} +.left_admmenu +{ + margin-bottom: 1.1em; +} +#main_admsection +{ + margin-left: 174px; +} + +tr.windowbg td, tr.windowbg2 td +{ + padding: 0.3em 0.7em; +} +#credits p +{ + padding: 0; + font-style: italic; + margin: 0; +} + +/* Styles for generic tables. +------------------------------------------------------- */ +.topic_table table +{ + width: 100%; +} +.topic_table .icon1, .topic_table .icon2, .topic_table .stats +{ + text-align: center; +} +#topic_icons +{ + margin-top: 1em; +} +#topic_icons .description +{ + margin: 0; +} +.topic_table table thead +{ + border-bottom: 1px solid #fff; +} +/* the subject column */ +.topic_table td +{ + font-size: 1em; +} +.topic_table td.subject +{ + padding: 4px; +} +.topic_table td.subject p, .topic_table td.stats, .topic_table td.lastpost +{ + font-size: 0.85em; + padding: 0; + margin: 0; +} +.topic_table td.lastpost, .topic_table td.lastpost +{ + font-size: 0.9em; + line-height: 100%; + padding: 4px; +} +.topic_table td.stickybg2 +{ + background-image: url(../images/icons/quick_sticky.gif); + background-repeat: no-repeat; + background-position: 98% 4px; +} +.topic_table td.lockedbg2 +{ + background-image: url(../images/icons/quick_lock.gif); + background-repeat: no-repeat; + background-position: 98% 4px; +} +.topic_table td.lastpost +{ + background-image: none; +} + +/* Styles for (fatal) errors. +------------------------------------------------- */ + +#fatal_error +{ + border: 1px solid #aaa; +} + +.errorbox +{ + padding: 1em; + border: 1px solid #cc3344; + color: #000; + background-color: #ffe4e9; + margin: 1em 0; +} +.errorbox h3 +{ + padding: 0; + margin: 0; + font-size: 1.1em; + text-decoration: underline; +} +.errorbox p +{ + margin: 1em 0 0 0; +} +.errorbox p.alert +{ + padding: 0; + margin: 0; + float: left; + width: 1em; + font-size: 1.5em; +} + +/* Styles for the profile section. +------------------------------------------------- */ + +dl +{ + overflow: auto; + margin: 0; + padding: 0; +} + +/* Fixes for the core theme */ +#profileview +{ + padding: 1px; + border: 1px solid #696969; + background-color: #ecedf3; +} +#profileview .content +{ + border: none; +} +#basicinfo .content +{ + padding: 1em; +} +#detailedinfo .content +{ + padding: 0.7em 1.2em; + border-left: 1px solid #aaa; +} + +/* The basic user info on the left */ +#basicinfo +{ + width: 20%; + float: left; +} +#detailedinfo +{ + width: 78%; + float: right; +} +#basicinfo h4 +{ + font-size: 135%; + font-weight: 100; + line-height: 105%; + white-space: pre-wrap; /* css-2.1 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ + overflow: hidden; +} +#basicinfo h4 span.position +{ + font-size: 80%; + font-weight: 100; + display: block; +} +#basicinfo img.avatar +{ + display: block; + margin: 10px 0 0 0; +} +#basicinfo ul +{ + list-style-type: none; + margin: 10px 0 0 0; +} +#basicinfo ul li +{ + display: block; + float: left; + margin-right: 5px; + height: 20px; +} +#basicinfo span#userstatus +{ + display: block; + clear: both; +} +#basicinfo span#userstatus img +{ + vertical-align: middle; +} +#detailedinfo div.content dl, #tracking div.content dl +{ + clear: right; + overflow: auto; + margin: 0 0 18px 0; + padding: 0 0 15px 0; + border-bottom: 1px solid #ccc; +} +#detailedinfo div.content dt, #tracking div.content dt +{ + width: 30%; + float: left; + margin: 0 0 3px 0; + padding: 0; + font-weight: bold; + clear: both; +} +#detailedinfo div.content dd, #tracking div.content dd +{ + width: 70%; + float: left; + margin: 0 0 3px 0; + padding: 0; +} +#detailedinfo div.content dl.noborder +{ + border-bottom: 0; +} +#detailedinfo div.content dt.clear +{ + width: 100%; +} +.signature, .custom_fields_above_signature, .attachments +{ + width: 98%; + overflow: auto; + clear: right; + border-top: 1px solid #666; +} +.signature h5 +{ + font-size: 100%; + margin-bottom: 10px; +} +#personal_picture +{ + display: block; + margin-bottom: 0.3em; +} +#avatar_server_stored div +{ + float: left; +} + +#main_admsection #basicinfo, #main_admsection #detailedinfo +{ + width: 100%; +} +#main_admsection #detailedinfo .content +{ + border: none !important; +} +#main_admsection #basicinfo +{ + border-bottom: 1px solid #ccc; +} +#main_admsection #basicinfo h4 +{ + float: left; +} +#main_admsection #basicinfo img.avatar +{ + float: right; + vertical-align: top; +} +#main_admsection #basicinfo ul +{ + clear: left; + padding-top: 10px; +} +#main_admsection #basicinfo span#userstatus +{ + clear: left; +} +#main_admsection #basicinfo p#infolinks +{ + display: none; + clear: both; +} +#main_admsection #basicinfo .botslice +{ + clear: both; +} + +/* Simple feedback messages */ +div#profile_error, div#profile_success +{ + margin: 0 0 1em 0; + padding: 1em 2em; + border: 1px solid; +} +div#profile_error +{ + border-color: red; + color: red; + background: #fee; +} + +div#profile_error span +{ + text-decoration: underline; +} + +div#profile_success +{ + border-color: green; + color: green; + background: #efe; +} + +/* Profile statistics */ +#generalstats div.content dt +{ + width: 50%; + float: left; + margin: 0 0 3px 0; + padding: 0; + font-weight: bold; + clear: both; +} +#generalstats div.content dd +{ + width: 50%; + float: left; + margin: 0 0 3px 0; + padding: 0; +} + +/* Activity by time */ +.activity_stats +{ + margin: 0; + padding: 0; + list-style: none; +} +.activity_stats li +{ + width: 4.16%; + float: left; +} +.activity_stats li span +{ + display: block; + border: solid #000; + border-width: 1px 1px 0 0; + text-align: center; +} +.activity_stats li.last span +{ + border-right: none; +} +.activity_stats li div.bar +{ + margin: 0 auto; + width: 15px; +} +.activity_stats li div.bar div +{ + background: #6294CE; +} +.activity_stats li div.bar span +{ + position: absolute; + top: -1000em; + left: -1000em; +} + +/* Most popular boards by posts and activity */ +#popularposts +{ + width: 50%; + float: left; +} +#popularactivity +{ + width: 50%; + float: right; +} + +#popularposts div.content dt, #popularactivity div.content dt +{ + width: 65%; + float: left; + margin: 0 0 3px 0; + padding: 0; + font-weight: bold; + clear: both; +} +#popularposts div.content dd, #popularactivity div.content dd +{ + width: 35%; + float: left; + margin: 0 0 3px 0; + padding: 0; +} + +.profile_pie +{ + background-image: url(../images/stats_pie.png); + float: left; + height: 20px; + width: 20px; + margin: 0 1em 0 0; + padding: 0; + text-indent: -1000em; +} + +/* View posts */ +.time +{ + float: right; +} +.counter +{ + margin: 0 0 0 0; + padding: 0.2em 0.5em 0.1em 0.2em; + font-size: 2.2em; + font-weight: bold; + color: #354c5f; + float: left; +} +.list_posts +{ + border-top: 1px solid #adadad; + padding-top: 1em; + margin-top: 0.5em; +} +div.core_posts +{ + border: 1px solid #adadad; + margin-bottom: 3px; +} +div.core_posts div.content +{ + background: none; + border: none; +} +.topic h4 +{ + margin: 3px 0; +} + +.mod_icons +{ + text-align: right; + margin-right: 1em; +} + +#permissions dt +{ + width: 48%; + float: left; + line-height: 1.2em; + margin: 0; + padding: 1%; + clear: both; + border-top: 1px solid #fff; +} + +#permissions dd +{ + width: 48%; + float: left; + margin: 0; + padding: 1%; + border-top: 1px solid #fff; +} + +#tracking div.content dl +{ + border-bottom: 0; + margin: 0; + padding: 0; +} + +#creator dl +{ + margin: 0; +} +#creator dt +{ + width: 40%; + float: left; + clear: both; + margin: 0 0 10px 0; +} +#creator dd +{ + float: left; + width: 60%; + margin: 0 0 10px 0; +} + +.ignoreboards +{ + margin: 0; + padding: 0; + width: 49%; + overflow: auto; +} +.ignoreboards a +{ + text-decoration: underline; +} +.ignoreboards ul +{ + overflow: auto; + margin: 0 0 0 1em; + padding: 0; +} +.ignoreboards li +{ + list-style: none; + float: left; + clear: both; +} + +#theme_settings +{ + overflow: auto; + margin: 0; + padding: 0; +} + +#theme_settings li +{ + list-style: none; + margin: 10px 0; + padding: 0; +} +/*Paid Subscriptions*/ +#paid_subscription +{ + width: 100%; +} +#paid_subscription dl.settings +{ + margin-bottom: 0; +} +#paid_subscription dl.settings dd, #paid_subscription dl.settings dt +{ + margin-bottom: 4px; +} +/*pick theme*/ +#pick_theme +{ + width: 100%; + float: left; +} + +/* Styles for the statistics center. +------------------------------------------------- */ +#statistics +{ + padding-bottom: 0.5em; +} +#statistics h4.titlebg +{ + text-align: center; + margin-bottom: 5px; +} +#stats_left, #top_posters, #top_topics_replies, #top_topics_starter +{ + float: left; + width: 49.5%; +} +#stats_right, #top_boards, #top_topics_views, #most_online +{ + float: right; + width: 49.5%; +} +dl.stats +{ + clear: both; + overflow: hidden; + margin: 0; + padding: 0; +} +dl.stats dt +{ + width: 49%; + float: left; + margin: 0 0 4px 0; + line-height: 16px; + padding: 0; + clear: both; + font-size: 1em; +} +dl.stats dd +{ + text-align: right; + width: 50%; + font-size: 1em; + float: right; + margin: 0 0 4px 0; + line-height: 16px; + padding: 0; +} +.stats_bar +{ + float: left; + background-image: url(../images/bar_stats.png); + height: 16px; + font-size: 0.9em; + display: block; + text-align: left; + color: #fff; + font-weight: bold; + background-position: top center; +} +.stats_bar span +{ + padding-left: 2px; +} + +/* Styles for the personal messages section. +------------------------------------------------- */ + +#personal_messages +{ + padding: 1px; +} +#personal_messages #top_subject +{ + padding-left: 11.75em !important; +} +#personal_messages div.labels +{ + padding: 0 1em 0 0; +} +#personal_messages .capacity_bar +{ + background: #fff; + border: 1px solid #000; + height: 7px; + width: 75%; + margin: 0 auto; +} +#personal_messages .capacity_bar div +{ + border: none; + height: 7px; +} +#personal_messages .capacity_bar div.empty +{ + background: #468008; +} +#personal_messages .capacity_bar div.filled +{ + background: #EEA800; +} +#personal_messages .capacity_bar div.full +{ + background: #A53D05; +} +#personal_messages .reportlinks +{ + padding: 0.5em 1.3em; +} + +/* Styles for the calendar section. +------------------------------------------------- */ +.calendar_table +{ + margin-bottom: 0.7em; +} + +/* Used to indicate the current day in the grid. */ +.calendar_today +{ + background-color: #fff; +} + +#month_grid +{ + width: 200px; + text-align: center; + float: left; +} + +#month_grid table +{ + width: 200px; + border-collapse: collapse; + border: 1px solid #adadad; +} + +#month_grid table td, #month_grid table th +{ + border: 1px solid #adadad; +} + +#main_grid table +{ + width: 100%; + padding-bottom: 4px; + border-collapse: collapse; + border: 1px solid #adadad; +} + +#main_grid table td, #main_grid table th +{ + border: 1px solid #adadad; +} + +#main_grid table h3.catbg +{ + text-align: center; + + border-top: 1px solid #adadad; + border-bottom: none; +} + +#main_grid table h4 +{ + border: none; +} + +#main_grid table.weeklist td.windowbg +{ + text-align: center; + height: 49px; + width: 25px; + font-size: large; + padding: 0 7px; + border-bottom: 1px solid #adadad; +} + +#main_grid table.weeklist td.weekdays +{ + height: 49px; + width: 100%; + padding: 4px; + text-align: left; + vertical-align: middle; + border-right: 1px solid #adadad; + border-bottom: 1px solid #adadad; +} + +#main_grid h3.weekly +{ + text-align: center; + padding-left: 0; + font-size: large; + height: 29px; +} + +#main_grid h3 span.floatleft, #main_grid h3 span.floatright +{ + display: block; + +} + +#main_grid table th.days +{ + width: 14%; +} + +#main_grid table td.weeks +{ + vertical-align: middle; + text-align: center; +} + +#main_grid table td.days +{ + vertical-align: top; + +} + +a.modify_event +{ + color: red; +} + +span.hidelink +{ + font-style: italic; +} + +#calendar_navigation +{ + text-align: center; +} + +#calendar .buttonlist_bottom +{ + border-bottom: 1px solid #adadad; + padding: 0 0 0 1ex; + margin: 0 0 1ex 0; +} + +/* Styles for the memberlist section. +------------------------------------------------- */ +#mlist_search +{ + margin: auto; + width: 500px; +} +span.memberstatsbar, span.memberstatsbar span +{ + height: 1.1em; + display: block; +} +span.memberstatsbar +{ + background: #fff; + border: 1px solid #888; +} +span.memberstatsbar span +{ + background: #fe9540; +} + +/* Styles for the basic search section. +------------------------------------------------- */ +#simple_search p +{ + padding: 0.5em; +} +#simple_search, #simple_search p, #advanced_search +{ + text-align: center !important; + margin: 0; +} +#search_error +{ + font-style: italic; + padding: 0.3em 1em; +} +#search_term_input +{ + font-size: 115%; + margin: 0 0 1em; +} + +/* Styles for the advanced search section. +------------------------------------------------- */ +#searchform fieldset +{ + text-align: left; + padding: 0; + margin: 0.5em 0; + border: none; +} +#advanced_search dl#search_options +{ + margin: 0 auto; + width: 600px; + padding-top: 1em; + overflow: hidden; +} +#advanced_search dt +{ + clear: both; + float: left; + padding: 0.2em; + text-align: right; + width: 20%; +} +#advanced_search dd +{ + width: 75%; + float: left; + padding: 0.2em; + margin: 0 0 0 0.5em; + text-align: left; +} +#searchform p.clear +{ + clear: both; +} + +/* Boards picker */ +#searchform fieldset div#searchBoardsExpand ul +{ + overflow: auto; + margin: 0 0 0 1em; + padding: 0; + width: 48%; +} +#searchform fieldset div#searchBoardsExpand ul ul +{ + width: auto; +} +#searchform fieldset div#searchBoardsExpand a +{ + text-decoration: underline; +} +#searchform fieldset div#searchBoardsExpand li +{ + list-style: none; + float: left; + clear: both; +} +#searchform fieldset p +{ + padding: 4px; + text-align: left; + margin-top: 5px; +} + +/* Styles for the search results page. +------------------------------------------------- */ +.pagelinks +{ + padding: 0.5em; +} +.topic_table td blockquote, .topic_table td .quoteheader +{ + margin: 0.5em; +} +.search_results_posts +{ + overflow: hidden; +} +.search_results_posts .inner +{ + padding: 0.5em 1em; + overflow: hidden; +} +.search_results_posts .windowbg2 +{ + margin-top: 4px; +} +.search_results_posts .buttons +{ + padding: 5px 1em 0 0; +} + +/* Styles for the help section. +------------------------------------------------- */ + +#helpmain +{ + padding: 1em; + border: 1px solid #696969; +} + +/* Samples should be easily distinguishable. */ +#helpmain .help_sample +{ + border: 1px solid #99a; + background: #fff; + padding: 1em; + overflow: auto; + margin-bottom: 1em; +} +#helpmain .help_sample .linktree +{ + font-weight: bold; +} + +/* We need some air between the lines */ +#helpmain p +{ + margin: 0 0 1.5em 0; + line-height: 1.5em; +} + +#helpmain ol +{ + font-weight: bold; + list-style-type: disc; + margin-bottom: 1em; + margin-top: 1em; + line-height: 1.5em; +} +#helpmain ol.la +{ + font-weight: normal; + list-style-type: circle; + margin: 0.5em 0 1em 0; + padding-left: 1.5em; +} + +ul.basic_helplist +{ + padding: 0.8em 1.5em; + line-height: 1.5em; +} +#helpmain .boardsframe p +{ + margin: 0; +} +#helpmain #messageindex +{ + clear: right; +} + +/* ...but leave the tab strips alone! */ +#helpmain .buttonlist_bottom ul, #helpmain .buttonlist ul +{ + margin: 0 !important; + padding: 0 0 0 1em !important; +} + +#helpmain .buttonlist_bottom ul li, #helpmain .buttonlist ul li +{ + margin: 0 0.2em 0 0 !important; + padding: 0 !important; +} \ No newline at end of file diff --git a/Themes/default/css/editor.css b/Themes/default/css/editor.css new file mode 100644 index 0000000..a2db499 --- /dev/null +++ b/Themes/default/css/editor.css @@ -0,0 +1,32 @@ +/* This is the editor's playground (textarea for non-wysiwyg, iframe for wysiwyg). */ +.editor +{ + width: 100%; + max-width: 100%; + min-width: 100%; +} + +.editor, .rich_editor_frame +{ + border: 1px solid #808080; + padding: 2px !important; + margin: 0; +} + +.rich_editor_frame +{ + background: #fff; +} + +/* The resize handle. */ +.richedit_resize +{ + height: 5px; + font-size: 0; + background: #eee url(../images/bbc/resize-handle.gif) no-repeat 50% 1px; + border: 1px solid #ddd; + border-top-width: 0; + cursor: s-resize; + width: 100%; + padding: 0 2px; +} \ No newline at end of file diff --git a/Themes/default/css/editor_ie.css b/Themes/default/css/editor_ie.css new file mode 100644 index 0000000..0c05537 --- /dev/null +++ b/Themes/default/css/editor_ie.css @@ -0,0 +1,45 @@ +/* This is the editor's playground (textarea for non-wysiwyg, iframe for wysiwyg). */ +.editor +{ + width: 635px; + max-width: 100%; + min-width: 100%; +} + +/* This is the IFRAME that holds the editor. */ +.rich_editor_frame +{ + border: 1px solid #808080; +} + +/* This is the WYSIWYG editor */ +.rich_editor +{ + background-color: #fff; + color: #000; + font-family: verdana; + font-size: x-small; + border: none; +} + +.rich_editor p +{ + margin: 0; +} + +.rich_editor a img +{ + border: 0; +} + +/* The resize handle. */ +.richedit_resize +{ + height: 5px; + font-size: 0; + background: #eee url(../images/bbc/resize-handle.gif) no-repeat 50% 1px; + border: 1px solid #ddd; + border-top-width: 0; + cursor: s-resize; + width: 100%; +} \ No newline at end of file diff --git a/Themes/default/css/ie6.css b/Themes/default/css/ie6.css new file mode 100644 index 0000000..4445215 --- /dev/null +++ b/Themes/default/css/ie6.css @@ -0,0 +1,208 @@ +.codeheader, code.bbc_code +{ + width: 96%; + margin: 0 auto; +} +code.bbc_code +{ + white-space: normal; +} +h3.catbg input.input_check +{ + margin: 0 4px; +} +h3.catbg img.icon, h4.titlebg img.icon +{ + margin: 1px 3px 0 0; +} +h3.catbg span.ie6_header, h4.catbg span.ie6_header, h3.titlebg span.ie6_header, h4.titlebg span.ie6_header +{ + padding: 6px 0; +} +#statistics h4.titlebg span.ie6_header +{ + padding: 0; +} +#statistics h4.titlebg span.ie6_header img.icon +{ + padding: 5px 0; +} +/* The dropdown menus +------------------------------------------------------- */ + +.dropmenu li +{ + width: 1px; +} +.dropmenu li a span +{ + white-space: nowrap; +} +.dropmenu li a:hover +{ + text-decoration: none; +} +.dropmenu li.iehover +{ + z-index: 120; +} + +/* the page section */ +.pagesection +{ + overflow: auto; +} +/* the user section needs some attention */ +#main_menu +{ + width: 98%; +} +#top_section +{ + height: 65px; +} + +/* the tabled definition lists */ +/* I commented the following out. Not sure why it was there. +/* Changing float: left; to float: right; sorts the settings dd class in index.css*/ +/* All the others seem fine too.*/ +/*dl.settings dd, #creator dd, dl.stats dd, dl.register_form dd, #poll_options dl.options dd, .login dd +{ + float: none !important; + width: auto; +}*/ +/* generic lists header */ +/* Side paddings must NOT be defined here.*/ +.table_grid thead th +{ + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +/* overflow: hidden doesn't help in IE6. */ +h3.titlebg a, h3.titlebg, h4.titlebg, h4.titlebg a +{ + display: inline-block; +} + +#upper_section +{ + display: inline-block; +} + +/* Overrides for the message index template +------------------------------------------------------- */ +#messageindex table +{ + margin-top: 5px; +} +#messageindex table th +{ + border-bottom: 1px solid #fff; +} +#topic_icons .description +{ + padding: 2em 1em 1em 1em; + overflow: auto; +} + +/* Overrides for the display template +------------------------------------------------------- */ +#forumposts .postarea +{ + margin-left: 0; + margin-right: 0; + float: right; +} +.keyinfo +{ + padding-bottom: 6px; +} +.inner +{ + clear: both; +} +.post +{ + word-wrap: break-word; +} +.buttonlist ul li +{ + width: 1%; + white-space: nowrap; +} +#forumposts h3.catbg +{ + clear: both; +} +#quickReplyOptions form textarea +{ + width: 98%; +} + +/* Styles for the statistics center. +------------------------------------------------- */ +#statistics div.content +{ + height: 210px; + overflow: hidden; +} +#statistics div.top_row +{ + height: 150px; +} + +/* Overrides for the admin template +------------------------------------------------------- */ +#main_admsection +{ + height: 100%; +} +#main_admsection table +{ + width: 99%; +} + +/* Overrides for the profile template +------------------------------------------------------- */ +#basicinfo h4 +{ + word-wrap: break-word; +} +.ignoreboards +{ + margin: 0 1%; + padding: 0; + width: 45%; +} + +/* Overrides for the personal messages template +------------------------------------------------------- */ +#personal_messages .postarea +{ + margin-left: 0; + margin-right: 0; + float: right; +} + +/* Overrides for the admin section of the register template +------------------------------------------------------- */ +#registration_agreement +{ + width: 99.5%; + margin: 0 auto; +} + +#edit_poll ul.poll_main li +{ + padding-left: 0; + margin: 0 -2em; +} +#postmodify div.roundframe { margin-right: 0;} + +/* Overrides for the recent posts template +------------------------------------------------------- */ +.list_posts +{ + word-wrap: break-word; +} \ No newline at end of file diff --git a/Themes/default/css/ie7.css b/Themes/default/css/ie7.css new file mode 100644 index 0000000..409cf96 --- /dev/null +++ b/Themes/default/css/ie7.css @@ -0,0 +1,103 @@ +code.bbc_code +{ + white-space: normal; +} +h3.catbg input.input_check +{ + margin: 0 4px; +} + +/* The dropdown menus +------------------------------------------------------- */ +/* the dropmenu - RTL tweak */ +.dropmenu li ul +{ + margin: 0 -50px 0 0; +} +/* the hover effects */ +.dropmenu li.iehover +{ + z-index: 120; +} +/* the tabled definition lists +/* I commented the following out. Not sure why it was there. +/* Changing float: left; to float: right; sorts the settings dd class in index.css*/ +/* All the others seem fine too.*/ +/*dl.settings dd, #creator dd, dl.stats dd, dl.register_form dd, #poll_options dl.options dd, .login dd +{ + float: none !important; + width: auto; +}*/ +/* generic lists header */ +/* Side paddings must NOT be defined here.*/ +.table_grid thead th +{ + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +/* Overrides for the messageindex template +------------------------------------------------------- */ +#messageindex table +{ + margin-top: 5px; +} +#messageindex table th +{ + border-bottom: 1px solid #fff; +} +#topic_icons .description +{ + padding: 2em 1em 1em 1em; + overflow: auto; +} + +/* Overrides for the display template +------------------------------------------------------- */ +.post +{ + padding-top: 1em; + float: none; + word-wrap: break-word; +} +#content_section #forumposts div.cat_bar +{ + margin-top: 8px; + clear: both; +} +#content_section .pagesection +{ + height: 1%; +} +#quickReplyOptions form textarea +{ + width: 98%; +} + +/* Overrides for the profile template +------------------------------------------------------- */ +#basicinfo h4 +{ + word-wrap: break-word; +} + +/* Overrides for the calendar template +------------------------------------------------- */ +#main_grid table.weeklist h4.titlebg +{ + margin: 2px 0 -4px 0; +} + +/* Overrides for the personal messages template +------------------------------------------------------- */ +#postmodify dl #pm_to, #postmodify dl #bcc_div2, #postmodify dl #pm_subject +{ + clear:both !important; +} + +/* Overrides for the recent posts template +------------------------------------------------------- */ +.list_posts +{ + word-wrap: break-word; +} \ No newline at end of file diff --git a/Themes/default/css/index.css b/Themes/default/css/index.css new file mode 100644 index 0000000..fc47ff4 --- /dev/null +++ b/Themes/default/css/index.css @@ -0,0 +1,3620 @@ +/* Styles for the general looks for the Curve theme. +------------------------------------------------------- */ + +/* Normal, standard links. */ +a:link, a:visited +{ + color: #346; + text-decoration: none; +} +a:hover +{ + text-decoration: underline; + cursor: pointer; +} + +/* Links that open in a new window. */ +a.new_win:link, a.new_win:visited +{ + color: #346; + text-decoration: none; +} +a.new_win:hover +{ + text-decoration: underline; +} + +/* Tables should show empty cells. */ +table +{ + empty-cells: show; +} + +/* Set a fontsize that will look the same in all browsers. */ +body +{ + background: #E9EEF2 url(../images/theme/backdrop.png) repeat-x; + font: 78%/130% "Verdana", "Arial", "Helvetica", sans-serif; + margin: 0 auto; + padding: 15px 0; +} + +/* Help popups require a different styling of the body element. */ +body#help_popup +{ + padding: 1em; +} + +/* use dark grey for the text, leaving #000 for headers etc */ +body, td, th, tr +{ + color: #444; +} + +/* This division wraps the entire forum when a forum width is set. */ +div#wrapper +{ + margin: 0 auto; + min-width: 764px; + max-width: 2300px; +} + +/* lets give all forms zero padding/margins */ +form +{ + padding: 0; + margin: 0; +} + +/* We can style the different types of input buttons to be uniform throughout different browsers and their color themes. + .button_submit - covers input[type=submit], input[type=button], button[type=submit] and button[type=button] in all browsers + .button_reset - covers input[type=reset] and button[type=reset] throughout all browsers + .input_check - covers input[type=checkbox] throughout all browsers + .input_radio - covers input[type=radio] throughout all browsers + .input_text - covers input[type=text] throughout all browsers + .input_file - covers input[type=file] throughout all browsers +*/ + +input, button, select, textarea +{ + font: 95%/115% verdana, Helvetica, sans-serif; + color: #000; + background: #fff; + border: 1px solid #7f9db9; + padding: 2px; +} + +/* Select elements look horrible with the extra padding, so leave them unpadded. */ +select +{ + padding: 0; +} + +/* Add some padding to the options instead. */ +select option +{ + padding: 1px; +} + +/* The font size of textareas should be just a little bit larger. */ +textarea +{ + font: 100%/130% verdana, Helvetica, sans-serif; +} + +/* Buttons should be styled a bit differently, in order to make them look more button'ish. */ +.button_submit, .button_reset +{ + background: #cde7ff url(../images/theme/submit_bg.png) no-repeat; + border: 1px solid #aaa; + cursor: pointer; + font-weight: normal; +} +input:hover, textarea:hover, button:hover, select:hover +{ + border: 1px solid #454545; +} +.button_submit:hover, .button_reset:hover +{ + border: 1px solid #aaa; + background: url(../images/theme/submit_bg.png) no-repeat 0 -140px #cde7ff; +} +input:focus, textarea:focus, button:focus, select:focus +{ + border: 1px solid #454545; +} + +/* All input elements that are checkboxes or radio buttons shouldn't have a border around them. */ +input.input_check, input.input_radio +{ + border: none; + background: none; +} +h3.catbg input.input_check +{ + margin: 9px 7px 0 7px; +} + +/* Give disabled text input elements a different background color. */ +input[disabled].input_text +{ + background-color: #eee; +} + +/* Standard horizontal rule.. ([hr], etc.) */ +hr, .hrcolor +{ + height: 1px; + border: 0; + color: #ccc; + background-color: #ccc; +} + +/* By default set the color on these tags as #000. */ +h1, h2, h3, h4, h5, h6 +{ + color: #000; + font-size: 1em; + margin: 0; + padding: 0; +} + +/* Fieldsets are used to group elements. */ +fieldset +{ + border: 1px solid #c4c4c4; + padding: 1em; + margin: 0 0 0.5em 0; +} +fieldset legend +{ + font-weight: bold; + color: #444; +} +/* No image should have a border when linked. */ +a img +{ + border: 0; +} + +/* Define strong as bold, and em as italics */ +strong +{ + font-weight: bold; +} + +em +{ + font-style: italic; +} +/* Alternative for u tag */ +.underline +{ + text-decoration: underline; +} + +/* Common classes to easy styling. +------------------------------------------------------- */ + +.floatright +{ + float: right; +} +.floatleft +{ + float: left; +} + +.flow_auto +{ + overflow: auto; +} +.flow_hidden +{ + overflow: hidden; +} +.flow_hidden .windowbg, .flow_hidden .windowbg2 +{ + margin-top: 2px; +} +.clear +{ + clear: both; +} +.clear_left +{ + clear: left; +} +.clear_right +{ + clear: right; +} + +/* Default font sizes: small (8pt), normal (10pt), and large (14pt). */ +.smalltext, tr.smalltext th +{ + font-size: 0.85em; + font-family: verdana, sans-serif; +} +.middletext +{ + font-size: 0.9em; + line-height: 1em; + font-family: verdana, sans-serif; +} +.normaltext +{ + font-size: 1em; + line-height: 1.2em; +} +.largetext +{ + font-size: 1.4em; +} +.centertext +{ + margin: 0 auto; + text-align: center; +} +.righttext +{ + margin-left: auto; + margin-right: 0; + text-align: right; +} +.lefttext +{ + margin-left: 0; + margin-right: auto; + text-align: left; +} +.double_height +{ + line-height: 2em; +} +/* some common padding styles */ +.padding +{ + padding: 0.7em; +} +.main_section, .lower_padding +{ + padding-bottom: 0.5em; +} +/* a quick reset list class. */ +ul.reset, ul.reset li +{ + padding: 0; + margin: 0; + list-style: none; +} + +/* Some BBC related styles. +------------------------------------------------------- */ + +/* A quote, perhaps from another post. */ +blockquote.bbc_standard_quote, blockquote.bbc_alternate_quote +{ + font-size: x-small; + color: #000; + line-height: 1.4em; + background: url(../images/theme/quote.png) 0.1em 0.1em no-repeat; + border-top: 2px solid #99A; + border-bottom: 2px solid #99A; + padding: 1.1em 1.4em; + margin: 0.1em 0 0.3em 0; + overflow: auto; +} + +/* Alterate blockquote stylings */ +blockquote.bbc_standard_quote +{ + background-color: #d7daec; +} +blockquote.bbc_alternate_quote +{ + background-color: #e7eafc; +} + +/* A code block - maybe PHP ;). */ +code.bbc_code +{ + display: block; + font-family: "dejavu sans mono", "monaco", "lucida console", "courier new", monospace; + font-size: x-small; + background: #eef; + border-top: 2px solid #999; + border-bottom: 2px solid #999; + line-height: 1.5em; + padding: 3px 1em; + overflow: auto; + white-space: nowrap; + /* Show a scrollbar after about 24 lines. */ + max-height: 24em; +} + +/* The "Quote:" and "Code:" header parts... */ +.codeheader, .quoteheader +{ + color: #666; + font-size: x-small; + font-weight: bold; + padding: 0 0.3em; +} + +/* For links to change the code stuff... */ +.codeoperation +{ + font-weight: normal; +} + +/* Styling for BBC tags */ +.bbc_link:link, .bbc_link:visited +{ + border-bottom: 1px solid #A8B6CF; +} +.bbc_link:hover +{ + text-decoration: none; + border-bottom: 1px solid #346; +} +.bbc_size +{ + line-height: 1.4em; +} +.bbc_color a +{ + color: inherit; +} +.bbc_img +{ + border: 0; +} +.bbc_table +{ + font: inherit; + color: inherit; +} +.bbc_table td +{ + font: inherit; + color: inherit; + vertical-align: top; +} +.bbc_u +{ + text-decoration: underline; +} +.bbc_list +{ + text-align: left; +} +.bbc_tt +{ + font-family: "dejavu sans mono", "monaco", "lucida console", "courier new", monospace; +} + +/* Generally, those [?] icons. This makes your cursor a help icon. */ +.help +{ + cursor: help; +} + +/* /me uses this a lot. (emote, try typing /me in a post.) */ +.meaction +{ + color: red; +} + +/* Highlighted text - such as search results. */ +.highlight +{ + font-weight: bold; + color: #ff7200 !important; + font-size: 1.1em; +} + +/* A more discreet highlight color, for selected membergroups etc. */ +.highlight2 +{ + background-color: #D1E1EF; + color: #000 !important; +} + +/* Generic, mostly color-related, classes. +------------------------------------------------------- */ + +.titlebg, .titlebg2, tr.titlebg th, tr.titlebg td, tr.titlebg2 td +{ + color: #222; + font-family: arial, helvetica, sans-serif; + font-size: 1.1em; + font-weight: bold; + background: #e3e9ef url(../images/theme/main_block.png) no-repeat -10px -380px; +} +.catbg, .catbg2, tr.catbg td, tr.catbg2 td, tr.catbg th, tr.catbg2 th +{ + color: #fff; + font-family: arial, helvetica, sans-serif; + font-size: 1.1em; + font-weight: bold; + background: #a7b9cd url(../images/theme/main_block.png) no-repeat -10px -280px; +} + +/* adjust the table versions of headers */ +tr.titlebg th, tr.titlebg2 th, td.titlebg, td.titlebg2, tr.catbg th, tr.catbg2 th, td.catbg, td.catbg2 +{ + padding: 0 6px; +} +tr.titlebg th a:link, tr.titlebg th a:visited, tr.titlebg2 td a:link, tr.titlebg2 td a:visited +{ + color: #222; +} +tr.catbg th a:link, tr.catbg th a:visited, tr.catbg2 td a:link, tr.catbg2 td a:visited +{ + color: #fff; +} +.catbg select +{ + height: 1.5em; + font-size: 0.85em; +} + +/* Alternating backgrounds for posts, and several other sections of the forum. */ +.windowbg, #preview_body +{ + color: #000; + background-color: #e7eaef; +} +.windowbg2 +{ + color: #000; + background-color: #f0f4f7; +} +.windowbg3 +{ + color: #000; + background-color: #cacdd3; +} + +/* the page navigation area */ +.pagesection +{ + font-size: 0.9em; + padding: 0.2em; + overflow: hidden; + margin-bottom: 1px; +} +div.pagesection div.floatright input +{ + margin-top: 3px; +} + +.pagelinks +{ + padding: 0.6em 0 0.4em 0; +} + +/* Colors for background of posts requiring approval */ +.approvebg +{ + color: #000; + background-color: #ffeaea; +} +.approvebg2 +{ + color: #000; + background-color: #fff2f2; +} + +/* Color for background of *topics* requiring approval */ +.approvetbg +{ + color: #000; + background-color: #e4a17c; +} +.approvetbg2 +{ + color: #000; + background-color: #f3bd9f; +} + +/* Sticky topics get a different background */ +.stickybg +{ + background: #e8d8cf; +} +.stickybg2 +{ + background: #f2e3d9; +} + +/* Locked posts get a different shade, too! */ +.lockedbg +{ + background: #d4dce2; + font-style: italic; +} +.lockedbg2 +{ + background: #d8e1e7; + font-style: italic; +} + +/* Posts and personal messages displayed throughout the forum. */ +.post, .personalmessage +{ + overflow: auto; + line-height: 1.4em; + padding: 0.1em 0; +} + +/* All the signatures used in the forum. If your forum users use Mozilla, Opera, or Safari, you might add max-height here ;). */ +.signature, .attachments +{ + width: 98%; + overflow: auto; + clear: right; + padding: 1em 0 3px 0; + border-top: 1px solid #aaa; + line-height: 1.4em; + font-size: 0.85em; +} +.custom_fields_above_signature +{ + width: 98%; + clear: right; + padding: 1em 0 3px 0; + border-top: 1px solid #aaa; + line-height: 1.4em; + font-size: 0.85em; +} + +/* Sometimes there will be an error when you post */ +.error +{ + color: red; +} + +/* Messages that somehow need to attract the attention. */ +.alert +{ + color: red; +} + +/* Calendar colors for birthdays, events and holidays */ +.birthday +{ + color: #920ac4; +} + +.event +{ + color: #078907; +} + +.holiday +{ + color: #000080; +} + +/* Colors for warnings */ +.warn_mute +{ + color: red; +} + +.warn_moderate +{ + color: #ffa500; +} + +.warn_watch, .success +{ + color: green; +} + +a.moderation_link, a.moderation_link:visited +{ + color: red; + font-weight: bold; +} + +.openid_login +{ + background: white url(../images/openid.gif) no-repeat; + padding-left: 18px; +} + +/* a descriptive style */ +.description, .description_board, .plainbox +{ + padding: 0.5em 1em; + font-size: 0.9em; + line-height: 1.4em; + border: 1px solid #bbb; + background: #f5f5f0; + margin: 0.2em 1px 1em 1px; +} +.description_board +{ + margin: 1em 1px 0 1px; +} + +/* an informative style */ +.information +{ + padding: 0.5em 1em; + font-size: 0.9em; + line-height: 1.3em; + border: 1px solid #bbb; + background: #f0f6f0; + margin: 0.2em 1px 1em 1px; +} +.information p +{ + padding: 1em; + margin: 0; +} +p.para2 +{ + padding: 1em 0 3.5em 0; + margin: 0; +} +/* AJAX notification bar +------------------------------------------------------- */ +#ajax_in_progress +{ + background: url(../images/theme/loadingbar.png) repeat-x; + color: #f96f00; + text-align: center; + font-size: 16pt; + padding: 8px; + width: 100%; + height: 66px; + line-height: 25px; + position: fixed; + top: 0; + left: 0; +} + +#ajax_in_progress a +{ + color: orange; + text-decoration: underline; + font-size: smaller; + float: right; + margin-right: 20px; +} + +/* Lists with settings use these a lot. +------------------------------------------------------- */ +dl.settings +{ + clear: right; + overflow: auto; + margin: 0 0 10px 0; + padding: 0; +} +dl.settings dt +{ + width: 40%; + float: left; + margin: 0 0 10px 0; + padding: 0; + clear: both; +} +dl.settings dt.settings_title +{ + width: 100%; + float: none; + margin: 0 0 10px 0; + padding: 5px 0 0 0; + font-weight: bold; + clear: both; +} +dl.settings dt.windowbg +{ + width: 98%; + float: left; + margin: 0 0 3px 0; + padding: 0 0 5px 0; + clear: both; +} +dl.settings dd +{ + width: 56%; + float: right; + overflow: auto; + margin: 0 0 3px 0; + padding: 0; +} +dl.settings img +{ + margin: 0 10px 0 0; +} +/* help icons */ +dl.settings dt a img +{ + position: relative; + top: 2px; +} + +/* Styles for rounded headers. +------------------------------------------------------- */ +h3.catbg, h3.catbg2, h3.titlebg, h4.titlebg, h4.catbg +{ + overflow: hidden; + height: 31px; + line-height: 31px; + font-size: 1.2em; + font-weight: bold; +} +h3.catbg a:link, h3.catbg a:visited, h4.catbg a:link, h4.catbg a:visited, h3.catbg, .table_list tbody.header td, .table_list tbody.header td a +{ + color: #fff; +} +h3.catbg2 a, h3.catbg2 +{ + color: #feb; +} +h3.catbg a:hover, h4.catbg a:hover, .table_list tbody.header td a:hover +{ + color: #fd9; + text-decoration: none; +} +h3.catbg2 a:hover +{ + color: #fff; + text-decoration: none; +} +h3.titlebg a, h3.titlebg, h4.titlebg, h4.titlebg a +{ + color: #222; +} +h3.titlebg a:hover, h4.titlebg a:hover +{ + color: #53616f; + text-decoration: none; +} +h3.catbg img.icon, h4.titlebg img.icon +{ + vertical-align: middle; + margin: -2px 5px 0 0; +} +h4.catbg a.toggle img +{ + vertical-align: middle; + margin: -2px 5px 0 5px; +} +h4.catbg, h4.catbg2 , h3.catbg , h3.catbg2 , .table_list tbody.header td.catbg +{ + background: url(../images/theme/main_block.png) no-repeat 100% -160px; + padding-right: 9px; +} +h4.titlebg, h3.titlebg +{ + background: url(../images/theme/main_block.png) no-repeat 100% -200px; + padding-right: 9px; +} +h4.titlebg img.icon +{ + float: left; + margin: 5px 8px 0 0; +} +div.cat_bar +{ + background: #99abbf url(../images/theme/main_block.png) no-repeat 0 -160px; + padding-left: 9px; + height: 31px; + overflow: hidden; + margin-bottom: 1px; +} +div.title_bar +{ + background: #e3e9ef url(../images/theme/main_block.png) no-repeat 0 -200px; + padding-left: 9px; + height: 31px; + overflow: hidden; + margin-bottom: 1px; +} + +/* rounded bars needs a different background here */ + +div.roundframe div.cat_bar +{ + background: #99abbf url(../images/theme/main_block.png) no-repeat 0 -240px; + margin-bottom: 0; +} +div.roundframe div.cat_bar h3.catbg +{ + background: url(../images/theme/main_block.png) no-repeat 100% -240px; +} +div.title_barIC +{ + background: #dadfe6 url(../images/theme/main_block.png) no-repeat 0 -120px; + padding-left: 9px; + height: 31px; + overflow: hidden; + margin-bottom: 1px; +} +div.title_barIC h4.titlebg +{ + background: url(../images/theme/main_block.png) no-repeat 100% -120px; +} +#upshrinkHeaderIC p.pminfo +{ + margin: 0; + padding: 0.5em; +} +img#upshrink_ic, img#newsupshrink +{ + float: right; + margin: 10px 5px 0 0; +} +table.table_list a.unreadlink, table.table_list a.collapse +{ + float: right; +} +table.table_list a.collapse +{ + margin: 10px 5px 0 1em; + height: 31px; + line-height: 31px; +} + +/* The half-round header bars for some tables. */ +.table_grid tr.catbg, .table_grid tr.titlebg +{ + font-size: 0.95em; + border-bottom: 1px solid #fff; +} +.table_grid tr.catbg th, .table_grid tr.titlebg th +{ + height: 28px; + line-height: 28px; +} +tr.catbg th.first_th +{ + background: #a7b9cd url(../images/theme/main_block.png) no-repeat 0 -280px; +} +tr.catbg th.last_th +{ + background: #a7b9cd url(../images/theme/main_block.png) no-repeat 100% -280px; +} +tr.titlebg th.first_th +{ + background: #e3e9ef url(../images/theme/main_block.png) no-repeat 0 -380px; +} +tr.titlebg th.last_th +{ + background: #e3e9ef url(../images/theme/main_block.png) no-repeat 100% -380px; +} +.table_grid th.last_th input +{ + margin: 0 2px; +} +.table_grid th.lefttext +{ + padding: 0 0.7em; +} + +/* a general table class */ +table.table_grid +{ + border-collapse: collapse; + margin-top: 0.1em; +} +table.table_grid td +{ + padding: 3px; + border-bottom: 1px solid #fff; + border-right: 1px solid #fff; +} + +/* GenericList */ +.additional_row +{ + padding: 0.5em 0 0.5em 0; +} +table.table_grid thead tr.catbg th +{ + white-space: nowrap; +} + +/* table_grid styles for Profile > Show Permissions. */ +#permissions table.table_grid td +{ + padding: 0.4em 0.8em; + cursor: default; +} + +/* Common styles used to add corners to divisions. +------------------------------------------------------- */ +.windowbg span.topslice +{ + display: block; + padding-left: 20px; + background: url(../images/theme/main_block.png) 0 -30px no-repeat; +} +.windowbg span.topslice span +{ + display: block; + background: url(../images/theme/main_block.png) 100% -30px no-repeat; + height: 11px; +} +.windowbg span.botslice +{ + display: block; + padding-left: 20px; + background: url(../images/theme/main_block.png) 0 -40px no-repeat; + font-size: 5px; + line-height: 5px; + margin-bottom: 0.2em; +} +.windowbg span.botslice span +{ + display: block; + background: url(../images/theme/main_block.png) 100% -40px no-repeat; + height: 11px; +} + +.windowbg2 span.topslice +{ + display: block; + padding-left: 20px; + background: url(../images/theme/main_block.png) 0 -60px no-repeat; +} +.windowbg2 span.topslice span +{ + display: block; + background: url(../images/theme/main_block.png) 100% -60px no-repeat; + height: 11px; +} +.windowbg2 span.botslice +{ + display: block; + padding-left: 20px; + background: url(../images/theme/main_block.png) 0 -71px no-repeat; + font-size: 5px; + line-height: 5px; + margin-bottom: 0.2em; +} +.windowbg2 span.botslice span +{ + display: block; + background: url(../images/theme/main_block.png) 100% -71px no-repeat; + height: 11px; +} +.approvebg span.topslice +{ + display: block; + padding-left: 20px; + background: url(../images/theme/main_block.png) 0 0 no-repeat; +} +.approvebg span.topslice span +{ + display: block; + background: url(../images/theme/main_block.png) 100% 0 no-repeat; + height: 11px; +} +.approvebg span.botslice +{ + display: block; + padding-left: 20px; + background: url(../images/theme/main_block.png) 0 -11px no-repeat; + margin-bottom: 0.2em; +} +.approvebg span.botslice span +{ + display: block; + background: url(../images/theme/main_block.png) 100% -11px no-repeat; + height: 11px; +} +.postbg +{ + border-left: 1px solid #7f7f7f; + border-right: 1px solid #7f7f7f; +} + +/* Used for sections that need somewhat larger corners. +----------------------------------------------------------- */ +.roundframe +{ + padding: 0 10px; + background: #f5f5f5; + border-left: 1px solid #c5c5c5; + border-right: 1px solid #c5c5c5; +} +.roundframe dl, .roundframe dt, .roundframe p +{ + margin: 0; +} +.roundframe p +{ + padding: 0.5em; +} +span.upperframe +{ + padding: 0; + display: block; + background: url(../images/theme/main_block.png) 0 -90px no-repeat; + padding-left: 20px; +} +span.upperframe span +{ + padding: 0; + height: 12px; + display: block; + background: url(../images/theme/main_block.png) 100% -90px no-repeat; +} +span.lowerframe +{ + padding: 0; + display: block; + background: url(../images/theme/main_block.png) 0 -102px no-repeat; + padding-left: 20px; +} +span.lowerframe span +{ + padding: 0; + height: 12px; + display: block; + background: url(../images/theme/main_block.png) 100% -102px no-repeat; +} + +/* The main content area. +------------------------------------------------------- */ +.content +{ + padding: 0.5em 1.2em; + margin: 0; + border: none; +} +.content p +{ + margin: 0 0 0.5em 0; +} + +/* Styles used by the auto suggest control. +------------------------------------------------------- */ +.auto_suggest_div +{ + border: 1px solid #000; + position: absolute; + visibility: hidden; +} +.auto_suggest_item +{ + background-color: #ddd; +} +.auto_suggest_item_hover +{ + background-color: #888; + cursor: pointer; + color: #eee; +} + +/* Styles for the standard dropdown menus. +------------------------------------------------------- */ +#main_menu +{ + padding: 0 0.5em; + float: left; + margin: 0; + width: 98%; +} + +.dropmenu, .dropmenu ul +{ + list-style: none; + line-height: 1em; + padding: 0; + margin: 0; +} +.dropmenu +{ + padding: 0 0.5em; +} +.dropmenu a +{ + display: block; + color: #000; + text-decoration: none; +} +.dropmenu a span +{ + display: block; + padding: 0 0 0 5px; + font-size: 0.9em; +} +/* the background's first level only */ +.dropmenu li a.firstlevel +{ + margin-right: 8px; +} +.dropmenu li a.firstlevel span.firstlevel +{ + display: block; + position: relative; + left: -5px; + padding-left: 5px; + height: 22px; + line-height: 19px; + white-space: pre; +} +.dropmenu li +{ + float: left; + padding: 0; + margin: 0; + position: relative; +} +.dropmenu li ul +{ + z-index: 90; + display: none; + position: absolute; + width: 19.2em; + font-weight: normal; + border-bottom: 1px solid #999; + background: url(../images/theme/menu_gfx.png) 0 -130px no-repeat; + padding: 7px 0 0 0; +} +.dropmenu li li +{ + width: 19em; + margin: 0; + border-left: 1px solid #999; + border-right: 1px solid #999; +} +.dropmenu li li a span +{ + display: block; + padding: 8px; +} +.dropmenu li ul ul +{ + margin: -1.8em 0 0 13em; +} + +/* the active button */ +.dropmenu li a.active +{ + background: url(../images/theme/menu_gfx.png) no-repeat 100% 0; + color: #fff; + font-weight: bold; +} +.dropmenu li a.active span.firstlevel +{ + background: url(../images/theme/menu_gfx.png) no-repeat 0 0; +} +/* the hover effects */ +.dropmenu li a.firstlevel:hover, .dropmenu li:hover a.firstlevel +{ + background: url(../images/theme/menu_gfx.png) no-repeat 100% -30px; + color: #fff; + cursor: pointer; + text-decoration: none; +} +.dropmenu li a.firstlevel:hover span.firstlevel, .dropmenu li:hover a.firstlevel span.firstlevel +{ + background: url(../images/theme/menu_gfx.png) no-repeat 0 -30px; +} +/* the hover effects on level2 and 3 */ +.dropmenu li li a:hover, .dropmenu li li:hover>a +{ + background: #d4dbe4; + color: #000; + text-decoration: none; +} +.dropmenu li:hover ul ul, .dropmenu li:hover ul ul ul +{ + top: -999em; +} +.dropmenu li li:hover ul +{ + top: auto; +} +.dropmenu li:hover ul +{ + display: block; +} +.dropmenu li li.additional_items +{ + background-color: #fff; +} + +/* The dropdown menu toggle image */ +#menu_toggle +{ + float: right; + margin-right: 10px; + padding-top: 3px; +} +#menu_toggle span +{ + position: relative; + right: 5000px; +} + +/* Styles for the standard button lists. +------------------------------------------------------- */ + +.buttonlist ul +{ + z-index: 100; + padding: 5px; + margin: 0 0.2em 5px 0; +} +.buttonlist ul li +{ + margin: 0; + padding: 0; + list-style: none; + float: left; +} +.buttonlist ul li a +{ + display: block; + font-size: 0.8em; + color: #000; + background: #e8e8e8 url(../images/theme/menu_gfx.png) no-repeat 0 -60px; + padding: 0 0 0 8px; + margin-left: 12px; + text-transform: uppercase; + cursor: pointer; +} +.buttonlist ul li a:hover +{ + background: url(../images/theme/menu_gfx.png) no-repeat 0 0; + color: #fff; + text-decoration: none; +} +.buttonlist ul li a span +{ + background: url(../images/theme/menu_gfx.png) no-repeat 100% -60px; + display: block; + height: 19px; + line-height: 19px; + padding: 0 8px 0 0; +} +.buttonlist ul li a:hover span +{ + background: #fff url(../images/theme/menu_gfx.png) no-repeat 100% 0; +} +/* the active one */ +.buttonlist ul li a.active +{ + background: #5a6c85 url(../images/theme/menu_gfx.png) no-repeat 0 -90px; + color: #fff; + font-weight: bold; +} +.buttonlist ul li a.active span +{ + background: url(../images/theme/menu_gfx.png) no-repeat 100% -90px; +} +.buttonlist ul li a.active +{ + font-weight: bold; +} +.buttonlist ul li a.active:hover +{ + color: #ddf; +} +.align_top ul li a, .align_bottom ul li a +{ + margin: 0 12px 0 0; +} + +/* the navigation list */ +ul#navigation +{ + margin: 0; + font-size: 0.9em; + padding: 1em 0.4em; +} +ul#navigation li +{ + float: none; + font-size: 0.95em; + display: inline; +} + +#adm_submenus +{ + padding-left: 2em; + overflow: hidden; +} + +/* Styles for the general looks for the Curve theme. +------------------------------------------------------- */ + +/* the framing graphics */ +#header +{ + background: url(../images/theme/main_block.png) #fefefe no-repeat 0 -480px; + padding-left: 20px; +} +#header div.frame +{ + background: url(../images/theme/main_block.png) no-repeat 100% -480px; + display: block; + padding: 5px 20px 1em 0; +} +/* the content section */ +#content_section +{ + background: #FFFFFF url(../images/theme/frame_repeat.png) repeat-y top left; + padding-left: 20px; +} +#content_section div.frame +{ + background: url(../images/theme/frame_repeat.png) repeat-y top right; + display: block; + padding: 0 20px 0 0; +} +#main_content_section +{ + width: 100%; + min-height: 200px; +} + +/* the main title, always stay at 45 pixels in height! */ +h1.forumtitle +{ + line-height: 45px; + font-size: 1.8em; + font-family: Geneva, verdana, sans-serif; + margin: 0; + padding: 0; + float: left; +} +/* float these items to the right */ +#siteslogan, img#smflogo +{ + margin: 0; + padding: 0; + float: right; + line-height: 3em; +} +h3, h4 +{ + padding-bottom: 3px; +} +/* the upshrink image needs some tweaking */ +img#upshrink +{ + float: right; + margin: 1em; +} +/* ..so does the SMF logo */ +img#smflogo +{ + margin-left: 1em; +} +/* the upper_section, float the two each way */ +#upper_section +{ + padding: 5px; + margin-bottom: 1.5em; +} +#upper_section ul li.greeting +{ + font-size: 1.3em; + font-weight: bold; + line-height: 1.5em; +} +#upper_section div.news +{ + width: 50%; + float: right; + text-align: right; +} +#guest_form +{ + overflow: hidden; +} +#guest_form .info +{ + padding: 4px 0 ; + line-height: 1.3em; +} +div#upper_section div.user +{ + width: 50%; + float: left; + overflow: auto; +} +div#upper_section div.user p +{ + float: left; + margin: 0 1em 1em 0; + padding: 0; +} +div#upper_section div.user ul +{ + margin: 0; + padding-left: 10px; +} +div#upper_section div.user ul li +{ + margin-bottom: 2px; +} +div#upper_section div.news p +{ + display: inline; +} +div#upper_section div.news form +{ + padding-bottom: 10px; +} +/* clearing the floats */ +#top_section +{ + min-height: 65px; + overflow: hidden; + margin-bottom: 3px; +} +#upper_section +{ + overflow: hidden; +} + +/* The navigation list (i.e. linktree) */ +.navigate_section +{ + padding: 0.5em; + margin: 0 0 0 0; +} +.navigate_section ul +{ + display: block; + margin: 0; + font-size: 0.9em; + padding: 1em 0 0.5em 0; + border-top: 1px solid #ccc; + overflow: hidden; + list-style: none; + clear: both; + width: 100%; +} +.navigate_section ul li +{ + float: left; + padding: 0 0.5em 0 0; + font-size: 0.95em; +} +.navigate_section ul li a +{ + white-space: pre; +} + +/* The footer wih copyright links etc. */ +#footer_section +{ + text-align: center; + background: url(../images/theme/main_block.png) no-repeat 0 -820px; + padding-left: 20px; +} +#footer_section span.smalltext +{ + font-size: 100%; +} +#footer_section div.frame +{ + background: url(../images/theme/main_block.png) no-repeat 100% -820px; + display: block; + padding: 60px 0 0 0; +} +#footer_section ul li, #footer_section p +{ + font-size: 0.8em; +} +#footer_section ul li +{ + display: inline; + padding-right: 5px; +} +#footer_section ul li.copyright +{ + display: block; +} +select.qaction, input.qaction +{ + font-size: 0.85em; + padding: 0; +} +#mlist table tbody td.windowbg2 +{ + text-align: center; +} + +/* Styles for a typical table. +------------------------------------------------------- */ +table.table_list +{ + width: 100%; +} +table.table_list p +{ + padding: 0; + margin: 0; +} +table.table_list td, table.table_list th +{ + padding: 5px; +} +table.table_list tbody.header td +{ + padding: 0; +} +table.table_list tbody.content td.stats +{ + font-size: 90%; + width: 15%; + text-align: center; +} +table.table_list tbody.content td.lastpost +{ + line-height: 1.3em; + font-size: 85%; + width: 24%; +} +table.table_list tbody.content td.icon +{ + text-align: center; + width: 6%; +} + +/* Styles for the board index. +------------------------------------------------- */ + +/* the board title! */ +.table_list tbody.content td.info a.subject +{ + font-weight: bold; + font-size: 110%; + color: #d97b33; +} +.table_list tbody.content td.children +{ + color: #555; + font-size: 85%; +} +p.moderators +{ + font-size: 0.8em; + font-family: verdana, sans-serif; +} +/* hide the table header/footer parts - but its here for those needing to style it */ +#boardindex_table .table_list thead, #boardindex_table .table_list tfoot +{ + display: none; +} + +/* the posting icons */ +#posting_icons +{ + padding: 0 1em 0.5em 1em; + margin: 0 0 1em 0; + line-height: 1em; +} +#posting_icons ul +{ + font-size: 0.8em; +} +#posting_icons img +{ + vertical-align: middle; + margin: 0 0 0 4ex; +} +#postbuttons_upper ul li a span +{ + line-height: 19px; + padding: 0 0 0 6px; +} +.nextlinks +{ + text-align: right; + margin-top: -1px; +} +.nextlinks_bottom +{ + clear: right; + text-align: right; +} +.mark_read +{ + padding: 0 0.5em; +} + +/* the newsfader */ +#newsfader +{ + margin: 0 2px; +} +#smfFadeScroller +{ + text-align: center; + padding: 0 2em; + overflow: auto; + margin: 1em 0; + color: #575757; /* shouldn't be shorthand style due to a JS bug in IE! */ +} + +/* Styles for the info center on the board index. +---------------------------------------------------- */ + +#upshrinkHeaderIC +{ + margin-top: 4px; +} +dl#ic_recentposts +{ + margin: 0 0 0.5em 0; + padding: 0.5em; + line-height: 1.3em; +} +dl#ic_recentposts dt +{ + float: left; +} +dl#ic_recentposts dd +{ + text-align: right; +} +#upshrinkHeaderIC p +{ + margin: 0 0 0.5em 0; + padding: 0.5em; +} +#upshrinkHeaderIC p.last +{ + margin: 0; + padding: 0.5em; + border-top: 2px dotted #bbb; +} +#upshrinkHeaderIC p.inline +{ + border: none; + margin: 0; + padding: 0.2em 0.5em 0.2em 0.5em; +} +#upshrinkHeaderIC p.stats +{ + font-size: 1.1em; + padding-top: 8px; +} +form#ic_login +{ + padding: 0.5em; + height: 2em; +} +form#ic_login ul li +{ + margin: 0; + padding: 0; + float: left; + width: 20%; + text-align: center; +} +form#ic_login ul li label +{ + display: block; +} + +/* the small stats */ +#index_common_stats +{ + display: block; + margin: 0 0 0.5em 0; + text-align: right; + font-size: 0.9em; + position: relative; + top: -20px; + line-height: 1px; +} + +img.new_posts +{ + padding: 0 0.1em; +} +/* Styles for the message (topic) index. +---------------------------------------------------- */ +div.table_frame .table_list +{ + border-collapse: collapse; + margin: 2px 0; +} +.table_frame .table_list td.icon, .table_frame .table_list td.info, .table_frame .table_list td.stats +{ + border-right: 2px solid white; +} +#messageindex +{ + clear: both; +} +/* the page navigation area */ +.childboards +{ + margin-bottom: 0.2em; +} +#childboards h3 +{ + padding-bottom: 0; +} +#childboards .table_list thead +{ + display: none; +} +#childboards .table_list +{ + margin-bottom: 1em; +} +.lastpost img +{ + float: right; + padding: 4px; +} + +/* Styles for the display template (topic view). +---------------------------------------------------- */ + +#postbuttons div.buttons +{ + padding: 0.5em; + width: 40%; + float: right; +} +#postbuttons div.middletext +{ + width: 60%; +} +#postbuttons span +{ + display: block; + text-align: right; +} +#postbuttons span.lower +{ + clear: right; +} +#postbuttons .buttonlist +{ + float: right; +} +#postbuttons #pagelinks +{ + padding-top: 1em; +} +#moderationbuttons +{ + overflow: hidden; +} +/* Events */ +.linked_events +{ + padding: 1em 0; +} +.edit_event +{ + margin: 0 1em; + vertical-align: middle; +} +/* Poll question */ +#poll +{ + overflow: hidden; +} +#poll .content +{ + padding: 0 1em; +} +h4#pollquestion +{ + padding: 0 0 0.5em 2em; +} + +/* Poll vote options */ +#poll_options ul.options +{ + border-top: 1px solid #9999aa; + padding: 1em 2.5em 0 2em; + margin: 0 0 1em 0; +} +#poll_options div.submitbutton +{ + border-bottom: 1px solid #9999aa; + clear: both; + padding: 0 0 1em 2em; + margin: 0 0 1em 0; +} + +/* Poll results */ +#poll_options dl.options +{ + border: solid #9999aa; + border-width: 1px 0; + padding: 1em 2.5em 1em 2em; + margin: 0 1em 1em 0; + line-height: 1.1em !important; +} + +#poll_options dl.options dt +{ + padding: 0.3em 0; + width: 30%; + float: left; + margin: 0; + clear: left; +} + +#poll_options dl.options .voted +{ + font-weight: bold; +} + +#poll_options dl.options dd +{ + margin: 0 0 0 2em; + padding: 0.1em 0 0 0; + width: 60%; + max-width: 450px; + float: left; +} + +#poll_options dl.options .percentage +{ + display: block; + float: right; + padding: 0.2em 0 0.3em 0; +} + +/* Poll notices */ +#poll_options p +{ + margin: 0 1.5em 0.2em 1.5em; + padding: 0 0.5em 0.5em 0.5em; +} + +div#pollmoderation +{ + margin: 0; + padding: 0; + overflow: auto; +} + +/* onto the posts */ +#forumposts +{ + clear: both; +} +#forumposts .cat_bar +{ + margin: 0 0 2px 0; +} +/* author and topic information */ +#forumposts h3 span#author +{ + margin: 0 7.7em 0 0; +} +#forumposts h3 img +{ + float: left; + margin: 4px 0.5em 0 0; +} +#forumposts h3.catbg +{ + margin-bottom: 3px; +} +p#whoisviewing +{ + margin: 0; + padding: 0.5em; +} +/* poster and postarea + moderation area underneath */ +.post_wrapper +{ + float:left; + width:100%; +} +.poster +{ + float: left; + width: 15em; +} +.postarea, .moderatorbar +{ + margin: 0 0 0 16em; +} +.postarea div.flow_hidden +{ + width: 100%; +} + +.moderatorbar +{ + clear: right; +} +/* poster details and list of items */ +.poster h4, .poster ul +{ + padding: 0; + margin: 0 1em 0 1.5em; +} +.poster h4 +{ + margin: 0.2em 0 0.4em 1.1em; + font-size: 120%; +} +.poster h4, .poster h4 a +{ + color: #c06002; +} +.poster ul ul +{ + margin: 0.3em 1em 0 0; + padding: 0; +} +.poster ul ul li +{ + display: inline; +} +.poster li.stars, .poster li.avatar, .poster li.blurb, li.postcount, li.im_icons ul +{ + margin-top: 0.5em; +} +.poster li.avatar +{ + overflow: hidden; +} +.poster li.warning +{ + line-height: 1.2em; + padding-top: 1em; +} +.poster li.warning a img +{ + vertical-align: bottom; + padding: 0 0.2em; +} +.messageicon +{ + float: left; + margin: 0 0.5em 0 0; +} +.messageicon img +{ + padding: 6px 3px; +} +.keyinfo +{ + float: left; + width: 50%; +} +.modifybutton +{ + clear: right; + float: right; + margin: 6px 20px 10px 0; + text-align: right; + font: bold 0.85em arial, sans-serif; + color: #334466; +} + +/* The quick buttons */ +div.quickbuttons_wrap +{ + padding: 0.2em 0; + width: 100%; + float: left; +} + +ul.quickbuttons +{ + margin: 0.9em 11px 0 0; + clear: right; + float: right; + text-align: right; + font: bold 0.85em arial, sans-serif; +} +ul.quickbuttons li +{ + float: left; + display: inline; + margin: 0 0 0 11px; +} +ul.quickbuttons li a +{ + padding: 0 0 0 20px; + display: block; + height: 20px; + line-height: 18px; + float: left; +} +ul.quickbuttons a:hover +{ + color: #a70; +} +ul.quickbuttons li.quote_button +{ + background: url(../images/theme/quickbuttons.png) no-repeat 0 0; +} +ul.quickbuttons li.remove_button +{ + background: url(../images/theme/quickbuttons.png) no-repeat 0 -30px; +} +ul.quickbuttons li.modify_button +{ + background: url(../images/theme/quickbuttons.png) no-repeat 0 -60px; +} +ul.quickbuttons li.approve_button +{ + background: url(../images/theme/quickbuttons.png) no-repeat 0 -90px; +} +ul.quickbuttons li.restore_button +{ + background: url(../images/theme/quickbuttons.png) no-repeat 0 -120px; +} +ul.quickbuttons li.split_button +{ + background: url(../images/theme/quickbuttons.png) no-repeat 0 -150px; +} +ul.quickbuttons li.reply_button +{ + background: url(../images/theme/quickbuttons.png) no-repeat 0 -180px; +} +ul.quickbuttons li.reply_all_button +{ + background: url(../images/theme/quickbuttons.png) no-repeat 0 -180px; +} +ul.quickbuttons li.notify_button +{ + background: url(../images/theme/quickbuttons.png) no-repeat 0 -210px; +} +ul.quickbuttons li.inline_mod_check +{ + margin: 0 0 0 5px; +} + +.post +{ + margin-top: 0.5em; + clear: right; +} +.inner +{ + padding: 1em 1em 2px 0; + margin: 0 1em 0 0; + border-top: 1px solid #99a; +} +img.smiley +{ + vertical-align: bottom; +} +#forumposts .modified +{ + float: left; +} +#forumposts .reportlinks +{ + margin-right: 1.5em; + text-align: right; + clear: right; +} +#forumposts .signature, .post .signature +{ + margin: 1em 0 0 0; +} +#forumposts span.botslice +{ + clear: both; +} +.attachments hr +{ + clear: both; + margin: 1em 0 1em 0; +} +.attachments +{ + padding: 1em 0 2em 0; +} +.attachments div +{ + padding: 0 0.5em; +} + +/* Styles for the quick reply area. +---------------------------------------------------- */ + +#quickreplybox +{ + padding-bottom: 1px; +} +#quickReplyOptions .roundframe +{ + padding: 0 10%; +} +#quickReplyOptions form textarea +{ + height: 100px; + width: 635px; + max-width: 100%; + min-width: 100%; + margin: 0.25em 0 1em 0; +} +/* The jump to box */ +#display_jump_to +{ + clear: both; + padding: 5px; + margin-top: 6px; + text-align: right; +} + +/* Separator of posts. More useful in the print stylesheet. */ +#forumposts .post_separator +{ + display: none; +} + +/* Styles for edit post section +---------------------------------------------------- */ +form#postmodify .roundframe +{ + padding: 0 12%; +} +#post_header, .postbox +{ + padding: 0.5em; + overflow: hidden; +} +#post_header dt, .postbox dt +{ + float: left; + padding: 0; + width: 15%; + margin: .5em 0 0 0; + font-weight: bold; +} +#post_header dd, .postbox dd +{ + float: left; + padding: 0; + width: 83%; + margin: .3em 0; +} +#post_header img +{ + vertical-align: middle; +} +ul.post_options +{ + margin: 0 0 0 1em; + padding: 0; + list-style: none; + overflow: hidden; +} +ul.post_options li +{ + margin: 0.2em 0; + width: 49%; + float: left; +} +#postAdditionalOptionsHeader +{ + margin-top: 1em; +} +#postMoreOptions +{ + border-bottom: 1px solid #cacdd3; + padding: 0.5em; +} +#postAttachment, #postAttachment2 +{ + overflow: hidden; + margin: .5em 0; + padding: 0; + border-bottom: 1px solid #cacdd3; + padding: 0.5em; +} +#postAttachment dd, #postAttachment2 dd +{ + margin: .3em 0 .3em 1em; +} +#postAttachment dt, #postAttachment2 dt +{ + font-weight: bold; +} +#postAttachment3 +{ + margin-left: 1em; +} +#post_confirm_strip, #shortcuts +{ + padding: 1em 0 0 0; +} +.post_verification +{ + margin-top: .5em; +} +.post_verification #verification_control +{ + margin: .3em 0 .3em 1em; +} +/* The BBC buttons */ +#bbcBox_message +{ + margin: 0.75em 0.5em; +} +#bbcBox_message div +{ + margin: 0.2em 0; + vertical-align: top; +} +#bbcBox_message div img +{ + margin: 0 1px 0 0; + vertical-align: top; +} +#bbcBox_message select +{ + margin: 0 2px; +} +/* The smiley strip */ +#smileyBox_message +{ + margin: 0.5em; +} + +/* Styles for edit event section +---------------------------------------------------- */ +#post_event .roundframe +{ + padding: 0 12%; +} +#post_event fieldset +{ + padding: 0.5em; + clear: both; +} +#post_event #event_main input +{ + margin: 0 0 1em 0; + float: left; +} +#post_event #event_main div.smalltext +{ + width: 33em; + float: right; +} +#post_event div.event_options +{ + float: right; +} +#post_event ul.event_main, ul.event_options +{ + padding: 0; + overflow: hidden; +} +#post_event ul.event_main li +{ + list-style-type: none; + margin: 0.2em 0; + width: 49%; + float: left; +} +#post_event ul.event_options +{ + margin: 0; + padding: 0 0 .7em .7em; +} +#post_event ul.event_options li +{ + list-style-type: none; + margin: 0; + float: left; +} +#post_event #event_main select, #post_event ul.event_options li select, #post_event ul.event_options li .input_check +{ + margin: 0 1em 0 0; +} + +/* Styles for edit poll section. +---------------------------------------------------- */ + +#edit_poll +{ + overflow: hidden; +} +#edit_poll fieldset +{ + padding: 0.5em; + clear: both; + overflow: hidden; +} +#edit_poll fieldset input +{ + margin-left: 8.1em; +} +#edit_poll ul.poll_main li +{ + padding-left: 1em; +} +#edit_poll ul.poll_main input +{ + margin-left: 1em; +} +#edit_poll ul.poll_main, dl.poll_options +{ + overflow: hidden; + padding: 0 0 .7em .7em; + list-style: none; +} +#edit_poll ul.poll_main li +{ + margin: 0.2em 0; +} +#edit_poll dl.poll_options dt +{ + width: 33%; + padding: 0 0 0 1em; +} +#edit_poll dl.poll_options dd +{ + width: 65%; +} +#edit_poll dl.poll_options dd input +{ + margin-left: 0; +} + +/* Styles for the recent messages section. +---------------------------------------------------- */ + +#readbuttons_top .pagelinks, #readbuttons .pagelinks +{ + padding-bottom: 1em; + width: 60%; +} +#readbuttons .pagelinks +{ + padding-top: 1em; +} +#recent +{ + clear: both; +} + +/* Styles for the move topic section. +---------------------------------------------------- */ + +#move_topic dl +{ + margin-bottom: 0; +} +#move_topic dl.settings dt +{ + width: 40%; +} +#move_topic dl.settings dd +{ + width: 59%; +} +.move_topic +{ + width: 710px; + margin: auto; + text-align: left; +} +div.move_topic fieldset +{ + padding: 0.5em; +} + +/* Styles for the send topic section. +---------------------------------------------------- */ + +fieldset.send_topic +{ + border: none; + padding: 0.5em; +} +dl.send_topic +{ + margin-bottom: 0; +} +dl.send_mail dt +{ + width: 35%; +} +dl.send_mail dd +{ + width: 64%; +} + +/* Styles for the report topic section. +---------------------------------------------------- */ + +#report_topic dl +{ + margin-bottom: 0; +} +#report_topic dl.settings dt +{ + width: 20%; +} +#report_topic dl.settings dd +{ + width: 79%; +} + +/* Styles for the split topic section. +---------------------------------------------------- */ + +div#selected, div#not_selected +{ + width: 49%; +} +ul.split_messages li.windowbg, ul.split_messages li.windowbg2 +{ + margin: 1px; +} +ul.split_messages li a.split_icon +{ + padding: 0 0.5em; +} +ul.split_messages div.post +{ + padding: 1em 0 0 0; + border-top: 1px solid #fff; +} + +/* Styles for the merge topic section. +---------------------------------------------------- */ +ul.merge_topics li +{ + list-style-type: none; +} +dl.merge_topic dt +{ + width: 25%; +} +dl.merge_topic dd +{ + width: 74%; +} +fieldset.merge_options +{ + clear: both; +} +.custom_subject +{ + margin: 0.5em 0; +} + +/* Styles for the login areas. +------------------------------------------------------- */ +.login +{ + width: 540px; + margin: 0 auto; +} +.login dl +{ + overflow: auto; + clear: right; +} +.login dt, .login dd +{ + margin: 0 0 0.4em 0; + width: 44%; + padding: 0.1em; +} +.login dt +{ + float: left; + clear: both; + text-align: right; + font-weight: bold; +} +.login dd +{ + width: 54%; + float: right; + text-align: left; +} +.login p +{ + text-align: center; +} + +/* Styles for the registration section. +------------------------------------------------------- */ +.register_error +{ + border: 1px dashed red; + padding: 5px; + margin: 0 1ex 1ex 1ex; +} +.register_error span +{ + text-decoration: underline; +} + +/* Additional profile fields */ +dl.register_form +{ + margin: 0; + clear: right; +} + +dl.register_form dt +{ + font-weight: normal; + float: left; + clear: both; + width: 50%; + margin: 0.5em 0 0 0; +} + +dl.register_form dt strong +{ + font-weight: bold; +} + +dl.register_form dt span +{ + display: block; +} + +dl.register_form dd +{ + float: left; + width: 49%; + margin: 0.5em 0 0 0; +} + +#confirm_buttons +{ + text-align: center; + padding: 1em 0; +} + +.coppa_contact +{ + padding: 4px; + width: 32ex; + background-color: #fff; + color: #000; + margin-left: 5ex; + border: 1px solid #000; +} + +.valid_input +{ + background-color: #f5fff0; +} +.invalid_input +{ + background-color: #fff0f0; +} + +/* Styles for maintenance mode. +------------------------------------------------------- */ +#maintenance_mode +{ + width: 75%; + min-width: 520px; + text-align: left; +} +#maintenance_mode img.floatleft +{ + margin-right: 1em; +} + +/* common for all admin sections */ +h3.titlebg img +{ + vertical-align: middle; + margin-right: 0.5em; + margin-top: -1px; +} +tr.titlebg td +{ + padding-left: 0.7em; +} +#admin_menu +{ + min-height: 2em; + padding-left: 0; +} +#admin_content +{ + clear: left; + padding-top: 0.5em; +} +/* Custom profile fields like to play with us some times. */ +#admin_content .custom_field +{ + margin-bottom: 15px; +} +#admin_login .centertext +{ + padding: 1em; +} +#admin_login .centertext .error +{ + padding: 0 0 1em 0; +} + +/* Styles for sidebar menus. +------------------------------------------------------- */ +.left_admmenu, .left_admmenu ul, .left_admmenu li +{ + padding: 0; + margin: 0; + list-style: none; +} +#left_admsection +{ + width: 160px; + float: left; + padding-right: 10px; +} +.adm_section h4.titlebg +{ + font-size: 95%; + margin-bottom: 5px; +} +#main_container +{ + position: relative; +} +.left_admmenu li +{ + padding: 0 0 0 0.5em; +} +.left_admmenu +{ + margin-bottom: 0.5em; +} +#main_admsection +{ + position: relative; + left: 0; + right: 0; + overflow: hidden; +} + +tr.windowbg td, tr.windowbg2 td, tr.approvebg td, tr.highlight2 td +{ + padding: 0.3em 0.7em; +} +#credits p +{ + padding: 0; + font-style: italic; + margin: 0; +} + +/* Styles for generic tables. +------------------------------------------------------- */ +.topic_table table +{ + width: 100%; +} +.topic_table .icon1, .topic_table .icon2, .topic_table .stats +{ + text-align: center; +} +#topic_icons +{ + margin: 1em 0 0 0; +} +#topic_icons .description +{ + margin: 0; +} +.topic_table table thead +{ + border-bottom: 1px solid #fff; +} +/* the subject column */ +.topic_table td +{ + font-size: 1em; +} +.topic_table td.subject p, .topic_table td.stats +{ + font-size: 0.85em; + padding: 0; + margin: 0; +} +.topic_table td.lastpost +{ + font-size: 0.85em; + line-height: 1.3em; + padding: 4px; +} +.topic_table td.stickybg2 +{ + background-image: url(../images/icons/quick_sticky.gif); + background-repeat: no-repeat; + background-position: 98% 4px; +} +.topic_table td.lockedbg2 +{ + background-image: url(../images/icons/quick_lock.gif); + background-repeat: no-repeat; + background-position: 98% 4px; +} +.topic_table td.locked_sticky2 +{ + background-image: url(../images/icons/quick_sticky_lock.gif); + background-repeat: no-repeat; + background-position: 98% 4px; +} +.topic_table td.lastpost +{ + background-image: none; +} + +/* Styles for (fatal) errors. +------------------------------------------------- */ + +#fatal_error +{ + width: 80%; + margin: auto; +} + +.errorbox +{ + padding: 1em; + border: 1px solid #cc3344; + color: #000; + background-color: #ffe4e9; + margin-bottom: 1em; +} +.errorbox h3 +{ + padding: 0; + margin: 0; + font-size: 1.1em; + text-decoration: underline; +} +.errorbox p +{ + margin: 1em 0 0 0; +} +.errorbox p.alert +{ + padding: 0; + margin: 0; + float: left; + width: 1em; + font-size: 1.5em; +} + +/* Styles for the profile section. +------------------------------------------------- */ + +dl +{ + overflow: auto; + margin: 0; + padding: 0; +} + +/* The basic user info on the left */ +#basicinfo +{ + width: 20%; + float: left; +} +#basicinfo .windowbg .content +{ + padding-left: 20px; +} +#detailedinfo +{ + width: 79.5%; + float: right; +} +#basicinfo h4 +{ + font-size: 135%; + font-weight: 100; + line-height: 105%; + white-space: pre-wrap; + overflow: hidden; +} +#basicinfo h4 span.position +{ + font-size: 80%; + font-weight: 100; + display: block; +} +#basicinfo img.avatar +{ + display: block; + margin: 10px 0 0 0; +} +#basicinfo ul +{ + list-style-type: none; + margin: 10px 0 0 0; +} +#basicinfo ul li +{ + display: block; + float: left; + margin-right: 5px; + height: 20px; +} +#basicinfo span#userstatus +{ + display: block; + clear: both; +} +#basicinfo span#userstatus img +{ + vertical-align: middle; +} +#detailedinfo div.content dl, #tracking div.content dl +{ + clear: right; + overflow: auto; + margin: 0 0 18px 0; + padding: 0 0 15px 0; + border-bottom: 1px #ccc solid; +} +#detailedinfo div.content dt, #tracking div.content dt +{ + width: 35%; + float: left; + margin: 0 0 3px 0; + padding: 0; + font-weight: bold; + clear: both; +} +#detailedinfo div.content dd, #tracking div.content dd +{ + width: 65%; + float: left; + margin: 0 0 3px 0; + padding: 0; +} +#detailedinfo div.content dl.noborder +{ + border-bottom: 0; +} +#detailedinfo div.content dt.clear +{ + width: 100%; +} +.signature, .custom_fields_above_signature +{ + border-top: 1px #ccc solid; +} +.signature h5 +{ + font-size: 0.85em; + margin-bottom: 10px; +} +#personal_picture +{ + display: block; + margin-bottom: 0.3em; +} +#avatar_server_stored div +{ + float: left; +} +#avatar_upload +{ + overflow: auto; +} +#main_admsection #basicinfo, #main_admsection #detailedinfo +{ + width: 100%; +} +#main_admsection #basicinfo h4 +{ + float: left; + width: 35%; +} +#main_admsection #basicinfo img.avatar +{ + float: right; + vertical-align: top; +} +#main_admsection #basicinfo ul +{ + clear: left; +} +#main_admsection #basicinfo span#userstatus +{ + clear: left; +} +#main_admsection #basicinfo p#infolinks +{ + display: none; + clear: both; +} +#main_admsection #basicinfo .botslice +{ + clear: both; +} + +/* Simple feedback messages */ +div#profile_error, div#profile_success +{ + margin: 0 0 1em 0; + padding: 1em 2em; + border: 1px solid; +} +div#profile_error +{ + border-color: red; + color: red; + background: #fee; +} + +div#profile_error span +{ + text-decoration: underline; +} + +div#profile_success +{ + border-color: green; + color: green; + background: #efe; +} + +/* Profile statistics */ +#generalstats div.content dt +{ + width: 50%; + float: left; + margin: 0 0 3px 0; + padding: 0; + font-weight: bold; + clear: both; +} +#generalstats div.content dd +{ + width: 50%; + float: left; + margin: 0 0 3px 0; + padding: 0; +} + +/* Activity by time */ +#activitytime +{ + margin: 6px 0; +} +.activity_stats +{ + margin: 0; + padding: 0; + list-style: none; +} +.activity_stats li +{ + margin: 0; + padding: 0; + width: 4.16%; + float: left; +} +.activity_stats li span +{ + display: block; + border: solid #000; + border-width: 1px 1px 0 0; + text-align: center; +} +.activity_stats li.last span +{ + border-right: none; +} +.activity_stats li div.bar +{ + margin: 0 auto; + width: 15px; +} +.activity_stats li div.bar div +{ + background: #6294CE; +} +.activity_stats li div.bar span +{ + position: absolute; + top: -1000em; + left: -1000em; +} + +/* Most popular boards by posts and activity */ +#popularposts +{ + width: 49.5%; + float: left; +} +#popularactivity +{ + width: 49.5%; + float: right; +} + +#popularposts div.content dt, #popularactivity div.content dt +{ + width: 65%; + float: left; + margin: 0 0 3px 0; + padding: 0; + font-weight: bold; + clear: both; +} +#popularposts div.content dd, #popularactivity div.content dd +{ + width: 35%; + float: left; + margin: 0 0 3px 0; + padding: 0; +} + +.profile_pie +{ + background-image: url(../images/stats_pie.png); + float: left; + height: 20px; + width: 20px; + margin: 0 1em 0 0; + padding: 0; + text-indent: -1000em; +} + +/* View posts */ +.topic .time +{ + float: right; +} + +.counter +{ + margin: 0 0 0 0; + padding: 0.2em 0.5em 0.1em 0.2em; + font-size: 2.2em; + font-weight: bold; + color: #3f3f3f; + float: left; +} +.list_posts +{ + border-top: 2px solid #b3b3bf; + padding-top: 12px; + margin-top: 6px; + overflow: auto; +} + +.core_posts +{ + margin-bottom: 3px; +} + +.topic h4 +{ + margin: 3px 0; +} + +.topic .post +{ + margin: 0 1em; + min-height: 80px; + height: auto !important; + height: 80px; +} + +.topic .mod_icons +{ + text-align: right; + margin-right: 1em; +} + +#tracking div.content dl +{ + border-bottom: 0; + margin: 0; + padding: 0; +} + +#creator dl +{ + margin: 0; +} +#creator dt +{ + width: 40%; + float: left; + clear: both; + margin: 0 0 10px 0; +} +#creator dd +{ + float: right; + width: 55%; + margin: 0 0 10px 2px; + overflow: auto; +} + +.ignoreboards +{ + margin: 0 2%; + padding: 0; + width: 45%; +} +.ignoreboards a +{ + font-weight: bold; + border-bottom: 1px solid #c4c4c4; + padding: 0.1em 0; +} +.ignoreboards a:hover +{ + text-decoration: none; + border-bottom: 1px solid #334466; +} +.ignoreboards ul +{ + margin: 0; + padding: 0; +} +.ignoreboards li +{ + list-style: none; + float: left; + clear: both; +} +.ignoreboards li.category +{ + margin: 0.7em 0 0 0; + width: 100%; +} +.ignoreboards li ul +{ + margin: 0.2em 0 0 0; +} +.ignoreboards li.category ul li.board +{ + width: 93%; +} + +#theme_settings +{ + overflow: auto; + margin: 0; + padding: 0; +} + +#theme_settings li +{ + list-style: none; + margin: 10px 0; + padding: 0; +} +/* Paid Subscriptions */ +#paid_subscription +{ + width: 100%; +} +#paid_subscription dl.settings +{ + margin-bottom: 0; +} +#paid_subscription dl.settings dd, #paid_subscription dl.settings dt +{ + margin-bottom: 4px; +} +/* Pick theme */ +#pick_theme +{ + width: 100%; + float: left; +} +/*Issue a warning*/ +#warn_body{ + width: 80%; + font-size: 0.9em; +} + +/* Styles for the statistics center. +------------------------------------------------- */ +#statistics +{ + padding: 0.5em 0; +} +#statistics div.title_bar +{ + margin: 4px 0 -2px 0; +} +#statistics h3.catbg +{ + text-align: center; +} +#statistics div.content +{ + min-height: 210px; +} +#statistics div.top_row +{ + min-height: 150px; +} +#stats_left, #top_posters, #top_topics_replies, #top_topics_starter +{ + float: left; + width: 49.5%; +} +#stats_right, #top_boards, #top_topics_views, #most_online +{ + float: right; + width: 49.5%; +} +dl.stats +{ + clear: both; + overflow: hidden; + margin: 0; + padding: 0; +} +dl.stats dt +{ + width: 49%; + float: left; + margin: 0 0 4px 0; + line-height: 16px; + padding: 0; + clear: both; + font-size: 1em; +} +dl.stats dd +{ + text-align: right; + width: 50%; + font-size: 1em; + float: right; + margin: 0 0 4px 0; + line-height: 16px; + padding: 0; +} +.statsbar div.bar +{ + float: left; + background: url(../images/bar_stats.png) no-repeat; + display: block; + margin: 0 4px; + height: 16px; +} +.statsbar div.bar div +{ + position: relative; + right: -4px; + padding: 0 4px 0 0; + background: url(../images/bar_stats.png) no-repeat 100%; + height: 16px; +} +tr.windowbg2 th.stats_month +{ + width: 25%; + padding: 0 2em; + text-align: left; +} +tr.windowbg2 td.stats_day +{ + padding: 0 3.5em; + text-align: left; +} + +/* Styles for the personal messages section. +------------------------------------------------- */ + +#personal_messages h3 span#author, #personal_messages h3 span#topic_title +{ + float: left; +} +#personal_messages h3 span#author +{ + margin: 0 0 0 0.5em; +} +#personal_messages h3 span#topic_title +{ + margin: 0 0 0 9em; +} +#personal_messages div.labels +{ + padding: 0 1em 0 0; +} +#personal_messages .capacity_bar +{ + background: #f0f4f7; + display: block; + margin: 0.5em 0 0 1em; + height: 1em; + border: 1px solid #adadad; + width: 10em; +} +#personal_messages .capacity_bar span +{ + border-right: 1px solid #adadad; + display: block; + height: 1em; +} +#personal_messages .capacity_bar span.empty +{ + background: #a6d69d; +} +#personal_messages .capacity_bar span.filled +{ + background: #eea800; +} +#personal_messages .capacity_bar span.full +{ + background: #f10909; +} +#personal_messages .reportlinks +{ + padding: 0.5em 1.3em; +} +#searchLabelsExpand li +{ + padding: 0.3em 0.5em; +} +#manrules div.righttext +{ + padding: 0.3em 0.1em; +} +dl.addrules dt.floatleft +{ + width: 15em; + color: #333; + padding: 0 1.25em 0.5em 1.25em; +} +#addrule fieldset +{ + clear: both; +} + +/* Styles for the calendar section. +------------------------------------------------- */ +.calendar_table +{ + margin-bottom: 0.7em; +} + +/* Used to indicate the current day in the grid. */ +.calendar_today +{ + background-color: #fff; +} + +#month_grid +{ + width: 200px; + text-align: center; + float: left; +} +#month_grid div.cat_bar +{ + height: 25px; +} +#month_grid h3.catbg +{ + height: 25px; + line-height: 27px; +} +#month_grid table +{ + width: 200px; +} +#main_grid table +{ + width: 100%; + padding-bottom: 4px; +} +#main_grid table h3.catbg +{ + text-align: center; + height: 29px; + border-top: 2px solid #fff; + border-bottom: none; +} +#main_grid table.weeklist td.windowbg +{ + text-align: center; + height: 49px; + width: 25px; + font-size: large; + padding: 0 7px; + border-bottom: 2px solid #fff; +} +#main_grid table.weeklist td.weekdays +{ + height: 49px; + width: 100%; + padding: 4px; + text-align: left; + vertical-align: middle; + border-bottom: 2px solid #fff; +} +#main_grid h3.weekly +{ + text-align: center; + padding-left: 0; + font-size: large; + height: 29px; +} +#main_grid h3 span.floatleft, #main_grid h3 span.floatright +{ + display: block; + font-weight: bold; +} +#main_grid table th.days +{ + width: 14%; + padding: 4px 0; +} +#main_grid table.weeklist h4.titlebg +{ + margin: 0 0 0 0; + height: 23px; + line-height: 27px; +} +#main_grid table td.weeks +{ + vertical-align: middle; + text-align: center; + font-weight: bold; + font-size: large; +} +#main_grid table td.days +{ + vertical-align: top; + text-align: center; +} + +a.modify_event +{ + color: red; +} + +span.hidelink +{ + font-style: italic; +} + +#calendar_navigation +{ + text-align: center; +} + +/* Styles for the memberlist section. +------------------------------------------------- */ +#mlist_search +{ + margin: auto; + width: 500px; +} + +/* Styles for the basic search section. +------------------------------------------------- */ +#searchform, #simple_search p +{ + padding: 0.5em; + margin: 0; +} +#simple_search, #simple_search p, #advanced_search +{ + text-align: center !important; + margin: 0; +} +#search_error +{ + font-style: italic; + padding: 0.3em 1em; +} +#search_term_input +{ + font-size: 115%; + margin: 0 0 1em; +} + +/* Styles for the advanced search section. +------------------------------------------------- */ +#searchform fieldset +{ + text-align: left; + padding: 0; + border: none; +} +#advanced_search dl#search_options +{ + margin: 0 auto; + width: 600px; + padding-top: 1em; + overflow: hidden; +} +#advanced_search dt +{ + clear: both; + float: left; + padding: 0.2em; + text-align: right; + width: 20%; +} +#advanced_search dd +{ + width: 75%; + float: left; + padding: 0.2em; + margin: 0 0 0 0.5em; + text-align: left; +} +#searchform p.clear +{ + clear: both; +} + +/* Styles for the search results page. +------------------------------------------------- */ +.topic_table td blockquote, .topic_table td .quoteheader +{ + margin: 0.5em; +} +.search_results_posts +{ + overflow: hidden; +} +.search_results_posts .buttons +{ + padding: 5px 1em 0 0; +} + +/* Styles for the help section. +------------------------------------------------- */ + +#help_container +{ + margin: 4px 0 0 0; + padding: 0 0 8px 0; +} +#helpmain +{ + padding: 0 1em; +} +#helpmain p +{ + margin: 0 0 1.5em 0; + line-height: 1.5em; +} +#helpmain ul +{ + line-height: 1.5em; +} + +/* Styles for print media. +------------------------------------------------------- */ +@media print +{ + #headerarea + { + display: none; + } + + .tborder + { + border: none; + } +} \ No newline at end of file diff --git a/Themes/default/css/index.php b/Themes/default/css/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/css/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/css/install.css b/Themes/default/css/install.css new file mode 100644 index 0000000..41a279a --- /dev/null +++ b/Themes/default/css/install.css @@ -0,0 +1,91 @@ +body +{ + width: 90%; +} +#top_section +{ + height: 70px; + min-height: 65px; +} +#upper_section +{ + margin-bottom: 0; + padding: 0; +} +#upper_section .user +{ + height: 4em; +} +#upper_section .news +{ + height: 80px; +} +#main_screen +{ + padding: 0 40px; +} +#main_screen h2 +{ + font-size: 1.5em; + border-bottom: 1px solid #d05800; + line-height: 1.5em; + margin: 0 0 0.5em 0; + color: #d05800; +} +#main-steps +{ + float: right; + width: 50%; + margin-top: -60px; +} +#main-steps h2 +{ + font-size: 1.1em; + border-bottom: 1px solid #d05800; + line-height: 1.1em; + margin: 0 0 0.5em 0; + color: #d05800; + margin-right: 40px; +} +#main-steps ul +{ + list-style: none; + padding-left: 0; + margin: 0; +} +#main-steps ul li +{ + padding: 1px 0; + font-size: 0.9em; +} +#main-steps ul li.stepdone +{ + color: #aaa; +} +#main-steps ul li.stepcurrent +{ + color: #000; + font-weight: bold; +} +#main-steps ul li.stepwaiting +{ + color: #666; +} +.panel +{ + font-weight: normal; +} +a:link, a:hover, a:visited +{ + text-decoration: underline; +} +.progress +{ + position: relative; + margin: -16px 3px 0 3px; +} +.overall_progress +{ + position: relative; + margin: -25px 3px 0 3px; +} \ No newline at end of file diff --git a/Themes/default/css/report.css b/Themes/default/css/report.css new file mode 100644 index 0000000..a62181b --- /dev/null +++ b/Themes/default/css/report.css @@ -0,0 +1,59 @@ +body +{ + color: #000; + background-color: #fff; + zoom: 1; +} +body, td, .normaltext +{ + font-family: Verdana, arial, helvetica, serif; + font-size: small; +} +*, a:link, a:visited, a:hover, a:active +{ + color: #000 !important; +} +.smalltext, .quoteheader, .codeheader +{ + font-size: x-small; +} +.largetext +{ + font-size: large; +} +hr +{ + height: 1px; + border: 0; + color: #000; + background-color: #000; +} +.catbg +{ + background-color: #d6d6d6; + font-weight: bold; +} +.titlebg, tr.titlebg td, .titlebg a:link, .titlebg a:visited +{ + font-style: normal; + background-color: #f0f4f7; +} +.bordercolor +{ + background-color: #333; +} +.windowbg +{ + color: #000; + background-color: #fff; +} +.windowbg2 +{ + color: #000; + background-color: #f1f1f1; +} +.copyright +{ + font-size: x-small; + text-align: center; +} \ No newline at end of file diff --git a/Themes/default/css/rtl.css b/Themes/default/css/rtl.css new file mode 100644 index 0000000..ef70e8a --- /dev/null +++ b/Themes/default/css/rtl.css @@ -0,0 +1,1082 @@ +/* Common classes to ease styling. +------------------------------------------------------- */ + +.floatright +{ + float: left; +} +.floatleft +{ + float: right; +} +.clear_left +{ + clear: right; +} +.clear_right +{ + clear: left; +} +.righttext +{ + margin-left: auto; + margin-right: 0; + text-align: left; +} +.lefttext +{ + margin-left: 0; + margin-right: auto; + text-align: right; +} + +/* Styling for BBC tags */ +.bbc_list +{ + text-align: right; +} + +/* GenericList */ +.additional_row input +{ + margin-left: 0; + margin-right: 1em; +} +/* All the signatures used in the forum. If your forum users use Mozilla, Opera, or Safari, you might add max-height here ;). */ +.signature, .attachments +{ + clear: left; +} +.custom_fields_above_signature +{ + clear: left; +} +.openid_login +{ + padding-right: 18px; + padding-left: 0; +} + +/* Lists with settings use these a lot. +------------------------------------------------------- */ +dl.settings +{ + clear: left; +} +dl.settings dt +{ + float: right; + clear: both; +} +dl.settings dt.windowbg +{ + float: right; +} +dl.settings dd +{ + float: left; +} +dl.settings img +{ + margin: 0 0 0 10px; +} + +/* Styles for rounded headers. +------------------------------------------------------- */ + +h3.catbg img.icon, h4.titlebg img.icon +{ + vertical-align: middle; + margin: -2px 0 0 5px; +} +h4.titlebg, h3.titlebg +{ + padding-right: 9px; + padding-left: 0; +} +h4.titlebg img.icon +{ + float: right; + margin: 5px 0 0 8px; +} + +table.table_list a.unreadlink, table.table_list a.collapse +{ + float: left; +} +table.table_list a.collapse +{ + margin: 10px 1em 0 5px; +} +.table_grid th.first_th, tr.catbg th.first_th +{ + background: #a8bace url(../images/theme/main_block.png) no-repeat 100% -240px; +} +.table_grid th.last_th, tr.catbg th.last_th +{ + background: #a8bace url(../images/theme/main_block.png) no-repeat 0 -240px; +} +tr.titlebg th.first_th +{ + background: #e3e9ef url(../images/theme/main_block.png) no-repeat 100% -340px; +} +tr.titlebg th.last_th +{ + background: #e3e9ef url(../images/theme/main_block.png) no-repeat 0 -340px; +} + +/* Styles for the standard dropdown menus. +------------------------------------------------------- */ +#main_menu +{ + padding: 0 0.5em; + float: right; + text-align: right; +} +.dropmenu li +{ + float: right; + margin: 0 0 0 8px; +} +.dropmenu li ul ul +{ + right: 15em; +} +.dropmenu li ul +{ + background: url(../images/theme/menu_gfx.png) 100% -130px no-repeat; + right: 5px; +} + +/* The dropdown menu toggle image */ +#menu_toggle +{ + float: left; + margin-right: 0; + margin-left: 10px; + padding-top: 3px; +} +#menu_toggle span +{ + position: relative; + left: 0; +} + +/* Styles for the standard button lists. +------------------------------------------------------- */ +.buttonlist ul +{ + margin: 0 0 0 0.2em; +} +.buttonlist ul li a +{ + margin-left: 0; + margin-right: 12px; +} +.buttonlist ul li a span +{ + left: 8px; +} +.align_top ul li a, .align_bottom ul li a +{ + margin: 0 0 0 12px; +} +#adm_submenus +{ + padding-left: 0; + padding-right: 2em; +} +/* the main title, always stay at 45 pixels in height! */ +h1.forumtitle +{ + float: right; +} +/* float these items to the left */ +#siteslogan, img#smflogo +{ + float: left; +} +/* the upshrink image needs some tweaking */ +img#upshrink +{ + float: left; +} +/* ..so does the SMF logo */ +img#smflogo +{ + margin-right: 1em; +} +#upper_section div.news +{ + float: left; + text-align: left; +} +div#upper_section div.user +{ + float: right; +} +div#upper_section div.user p +{ + float: right; + margin: 0 0 1em 1em; +} +div#upper_section div.user ul +{ + padding-left: 0; + padding-right: 10px; +} + +/* The navigation list (i.e. linktree) */ +.navigate_section ul li +{ + float: right; + padding: 0 0 0 0.5em; +} + +/* Styles for the board index. +------------------------------------------------- */ + +/* the posting icons */ +#posting_icons +{ + padding: 0 1em 0.5em 1em; +} +#posting_icons img +{ + margin: 0 4ex 0 0; +} +#posting_icons .buttonlist +{ + float: left; +} +#postbuttons_upper ul li a span +{ + line-height: 19px; + padding: 0 6px 0 0; +} + +dl#ic_recentposts dt +{ + float: right; +} +dl#ic_recentposts dd +{ + text-align: left; +} +form#ic_login ul li +{ + float: right; + width: 20%; +} + +/* the small stats */ +#index_common_stats +{ + text-align: left; +} +img#upshrink_ic, img#newsupshrink +{ + float: right; + margin: 10px 0 0 5px; +} + +/* Styles for the message (topic) index. +---------------------------------------------------- */ +.table_frame .table_list td.icon, .table_frame .table_list td.info, .table_frame .table_list td.stats +{ + border-right: none; + border-left: 2px solid white; +} +.lastpost img +{ + float: left; +} + +/* Styles for the display template (topic view). +---------------------------------------------------- */ +#postbuttons div.buttons +{ + float: right; +} +#postbuttons span +{ + text-align: left; +} +#postbuttons span.lower +{ + clear: left; +} +#postbuttons .buttonlist +{ + float: left; +} + +h4#pollquestion +{ + padding: 0.5em 2em 0.5em 0; +} +/* Poll vote options */ +#poll_options ul.options +{ + padding: 1em 2em 0 2.5em; + margin: 0 0 1em 0; +} +#poll_options div.submitbutton +{ + clear: both; + padding: 0 2em 1em 0; + margin: 0 0 1em 0; +} + +/* Poll results */ +#poll_options dl.options +{ + padding: 1em 2em 1em 2.5em; + margin: 0 0 1em 1em; +} +#poll_options dl.options dt +{ + float: right; + clear: right; +} +#poll_options dl.options dd +{ + margin: 0 2em 0 0; + float: right; +} +span.percent +{ + float: left; +} + +/* author and topic information */ +#forumposts h3 span#author +{ + margin: 0 0 0 7.7em; +} +#forumposts h3 img +{ + float: right; + margin: 4px 0 0 0.5em; +} +/* poster and postarea + moderation area underneath */ +.poster +{ + float: right; + width: 15em; +} +.postarea, .moderatorbar +{ + margin: 0 16em 0 0; +} +.moderatorbar +{ + clear: left; +} +/* poster details and list of items */ +.poster h4, .poster ul +{ + padding: 0; + margin: 0 1.5em 0 1em; +} +.poster h4 +{ + margin: 0.2em 1.1em 0.4em 0; +} +.poster ul ul +{ + margin: 0.3em 0 0 1em; +} +.messageicon +{ + float: right; + margin: 0 0 0 0.5em; +} + +.keyinfo +{ + float: right; +} +.modifybutton +{ + clear: left; + float: left; + margin: 8px 0 10px 20px; + text-align: left; +} + +/* The quick buttons */ +ul.quickbuttons +{ + margin: 0.9em 0 0 11px; + clear: left; + float: left; + text-align: left; +} +ul.quickbuttons li +{ + float: left; + margin: 0 11px 0 0; +} +ul.quickbuttons li a +{ + padding: 0 20px 0 0; + float: left; +} +ul.quickbuttons li.quote_button +{ + background-position: 100% 0; +} +ul.quickbuttons li.remove_button +{ + background-position: 100% -30px; +} +ul.quickbuttons li.modify_button +{ + background-position: 100% -60px; +} +ul.quickbuttons li.approve_button +{ + background-position: 100% -90px; +} +ul.quickbuttons li.restore_button +{ + background-position: 100% -120px; +} +ul.quickbuttons li.split_button +{ + background-position: 100% -150px; +} +ul.quickbuttons li.reply_button +{ + background-position: 100% -180px; +} +ul.quickbuttons li.reply_all_button +{ + background-position: 100% -180px; +} +ul.quickbuttons li.notify_button +{ + background-position: 100% -210px; +} +ul.quickbuttons li.inline_mod_check +{ + margin: 0 5px 0 0; +} +.post +{ + clear: left; +} +.inner +{ + padding: 1em 0 0 1em; + margin: 0 0 0 1em; +} +#forumposts .modified +{ + float: right; +} +#forumposts .reportlinks +{ + margin-left: 1.5em; + text-align: left; + clear: left; +} + +#moderationbuttons_strip +{ + float: right; +} +#moderationbuttons_strip ul +{ + margin: 0 0.2em 0 0; + padding: 0 1em 0 0; +} +/* The jump to box */ +#display_jump_to +{ + text-align: left; +} + +/* Styles for edit post section +---------------------------------------------------- */ +#post_header dt +{ + float: right; +} +#post_header dd +{ + float: right; +} +ul.post_options +{ + margin: 0 1em 0 0; +} +ul.post_options li +{ + float: right; +} +#postAttachment dd, #postAttachment2 dd +{ + margin: .3em 1em .3em 0; +} +#postAttachment dt, #postAttachment2 dt +{ + font-weight: bold; +} +#postAttachment3 +{ + margin-left: 0; + margin-left: 1em; +} +.post_verification #verification_control +{ + margin: .3em 1em .3em 0; +} + +/* Styles for edit event section +---------------------------------------------------- */ +#post_event div.event_options +{ + float: left; +} +#post_event #event_main input +{ + margin: 0 0 1em 0; + float: right; +} +#post_event #event_main div.smalltext +{ + float: left; +} +#post_event ul.event_main li +{ + float: left; +} +#post_event ul.event_options +{ + padding: 0 .7em .7em 0; +} +#post_event #event_main select, #post_event ul.event_options li select, #post_event ul.event_options li .input_check +{ + margin: 0 0 0 1em; +} + +/* Styles for edit poll section. +---------------------------------------------------- */ + +#edit_poll fieldset input +{ + margin-right: 7em; +} +#edit_poll ul.poll_main li +{ + padding-right: 1em; +} +#edit_poll ul.poll_main input +{ + margin-right: 1em; +} +#edit_poll div.poll_options +{ + float: right; +} +#edit_poll ul.poll_main, dl.poll_options +{ + padding: 0 .7em 0 0; +} +#edit_poll dl.poll_options dt +{ + padding: 0 1em 0 0; +} +#edit_poll dl.poll_options dd input +{ + margin-right: 0; +} + +/* Styles for the personal messages section. +------------------------------------------------- */ + +#personal_messages h3 span#author, #personal_messages h3 span#topic_title +{ + float: right; +} +#personal_messages h3 span#author +{ + margin: 0 0.5em 0 0; +} +#personal_messages h3 span#topic_title +{ + margin: 0 9em 0 0; +} +#personal_messages .labels +{ + padding: 0 0 0 1em; +} + +/* Styles for the move topic section. +---------------------------------------------------- */ +.move_topic +{ + text-align: right; +} +/* Styles for the login areas. +------------------------------------------------------- */ +.login dt +{ + float: right; +} +.login dd +{ + float: right; + text-align: right; +} +.login h3 img +{ + margin: 0 0 0.5em; +} + +/* Additional profile fields */ +dl.register_form +{ + clear: left; +} + +dl.register_form dt +{ + float: right; +} +/* Styles for maintenance mode. +------------------------------------------------------- */ +#maintenance_mode +{ + text-align: right; +} +#maintenance_mode img.floatleft +{ + margin-left: 1em; +} +/* common for all admin sections */ +h3.titlebg img +{ + margin-left: 0.5em; +} +tr.titlebg td +{ + padding-right: 0.7em; +} +#admin_menu +{ + padding-right: 0; +} +#admin_content +{ + clear: right; +} +/* Styles for sidebar menus. +------------------------------------------------------- */ +#left_admsection +{ + float: right; + padding-right: 0; + padding-left: 10px; +} +.left_admmenu li +{ + padding: 0 0.5em 0 0; +} +/* Styles for generic tables. +------------------------------------------------------- */ +.topic_table td.stickybg2 +{ + background-image: url(../images/icons/quick_sticky.gif); + background-repeat: no-repeat; + background-position: 2% 4px; +} +.topic_table td.lockedbg2 +{ + background-image: url(../images/icons/quick_lock.gif); + background-repeat: no-repeat; + background-position: 2% 4px; +} +.topic_table td.locked_sticky2 +{ + background-image: url(../images/icons/quick_sticky_lock.gif); + background-repeat: no-repeat; + background-position: 2% 4px; +} +.topic_table td.lastpost +{ + background-image: none; +} +/* Styles for (fatal) errors. +------------------------------------------------- */ +.errorbox p.alert +{ + float: right; +} +/* Styles for the profile section. +------------------------------------------------- */ +#basicinfo +{ + float: right; +} +#detailedinfo +{ + float: left; +} +#basicinfo ul li +{ + float: right; + margin-right: 0; + margin-left: 5px; +} +#detailedinfo div.content dl, #tracking div.content dl +{ + clear: left; +} +#detailedinfo div.content dt, #tracking div.content dt +{ + float: right; +} +#detailedinfo div.content dd, #tracking div.content dd +{ + float: right; +} +#avatar_server_stored div +{ + float: right; +} + +#main_admsection #basicinfo h4 +{ + float: right; +} +#main_admsection #basicinfo img.avatar +{ + float: left; +} +#main_admsection #basicinfo ul +{ + clear: right; +} +#main_admsection #basicinfo span#userstatus +{ + clear: right; +} + +/* Profile statistics */ +#generalstats div.content dt +{ + float: right; +} +#generalstats div.content dd +{ + float: right; +} + +/* Activity by time */ +#activitytime +{ + clear: right; +} +.activity_stats li +{ + float: right; +} +.activity_stats li span +{ + border-width: 1px 0 0 1px; +} +.activity_stats li.last span +{ + border-left: none; +} + +/* Most popular boards by posts and activity */ +#popularposts +{ + float: right; +} +#popularactivity +{ + float: left; +} + +#popularposts div.content dt, #popularactivity div.content dt +{ + float: right; +} +#popularposts div.content dd, #popularactivity div.content dd +{ + float: right; +} + +.profile_pie +{ + background-image: url(../images/stats_pie_rtl.png); + float: right; + margin-right: 0; + margin-left: 1em; +} + +/* View posts */ +.topic .time +{ + float: left; +} +.counter +{ + padding: 0.2em 0.2em 0.1em 0.5em; + float: right; +} +.topic .mod_icons +{ + text-align: left; + margin-right: 0; + margin-left: 1em; +} +#permissions div.permission_name +{ + margin: 0 0 0 1%; +} + +#ip_list li.header, #ip_list li.ip +{ + float: right; +} +#creator dt +{ + float: right; +} +#creator dd +{ + float: right; +} + +.ignoreboards ul +{ + margin: 0 1em 0 0; +} +.ignoreboards li +{ + float: right; +} + +#pick_theme +{ + float: right; +} +/* Styles for the statistics center. +------------------------------------------------- */ +#stats_left, #top_posters, #top_topics_replies, #top_topics_starter +{ + float: right; +} +#stats_right, #top_boards, #top_topics_views, #most_online +{ + float: left; +} +dl.stats dt +{ + float: right; +} +dl.stats dd +{ + text-align: left; +} +.statsbar div.bar +{ + float: right; +} +.statsbar div.bar div +{ + right: -6px; + padding: 0 0 0 6px; +} +tr.windowbg2 th.stats_month, tr.windowbg2 td.stats_day +{ + text-align: right; +} + +/* Styles for the calendar section. +------------------------------------------------- */ +#month_grid +{ + float: right; +} + +#main_grid table.weeklist td.windowbg +{ + + border-left: 2px solid #fff; + border-bottom: 2px solid #fff; +} + +#main_grid table.weeklist td.weekdays +{ + text-align: left; + vertical-align: middle; + border-right: 2px solid #fff; + border-bottom: 2px solid #fff; +} + +/* Styles for the advanced search section. +------------------------------------------------- */ +#searchform fieldset +{ + text-align: right; +} +#advanced_search dt +{ + float: right; + text-align: left; +} +#advanced_search dd +{ + float: right; + margin: 0 0.5em 0 0; + text-align: right; +} +/* Boards picker */ +#searchform fieldset div#searchBoardsExpand ul +{ + margin: 0 1em 0 0; +} +#searchform fieldset div#searchBoardsExpand li +{ + float: right; +} +#searchform fieldset p +{ + text-align: right; +} + +.search_results_posts .buttons +{ + padding: 5px 0 0 1em; +} + +/* Styles for the help section. +------------------------------------------------- */ +#helpmain h3.section +{ + padding: 0 0.5em 0.5em 0; +} +/* put back the bullets please */ +#helpmain ul +{ + margin: 0 2em 1em 0; + padding-left: 0; + padding-right: 1em; +} +#helpmain #messageindex +{ + clear: left; +} + +/* Styles for the admincenter (reverse admin.css). +------------------------------------------------- */ +#quick_search +{ + margin-left: 5px; +} +.features_image +{ + float: right; + margin: 0 1em 0.5em 2em; +} +.features_switch +{ + float: left; +} +.features h4 +{ + padding: 1em 0.5em 0.5em 0; +} +/* admin home */ +#live_news div.content dl +{ + padding: 0.5em 0.5em 0 0; +} +#smfAnnouncements dd +{ + padding: 0; + margin: 0 1.5em 1em 0; +} +#quick_tasks li +{ + float: right; + list-style-type: none; +} +.home_image +{ + float: right; +} +/* common admin classes */ +.additional_row input +{ + margin-left: 0; + margin-right: 2em; +} +#error_log td div.marginleft +{ + margin: 0 1ex 0 0 !important; +} + +/* Styles for the package manager. +------------------------------------------------- */ +#package_list .tborder +{ + margin: .25em 26px .25em 0; +} +#package_list ol, #package_list ol li +{ + margin-left: 0; + margin-right: 50px; +} +/* ManageBoards */ +#manage_boards ul +{ + overflow: hidden; +} +#manage_boards li +{ + overflow: hidden; +} +.move_links +{ + padding: 0 0 0 13px; +} + +span.search_weight +{ + text-align: left; +} +/* Manage Bans */ +.ban_restriction +{ + margin: 0.2em 2.2em 0.2em 0; +} +/* Themes */ +.is_directory +{ + padding-right: 18px; + background: url(../images/admin/boards.gif) no-repeat; + background-position: 100% 0; +} +/* Styles for the moderation center. +------------------------------------------------- */ +.modblock_left +{ + float: right; + clear: left; +} +.modblock_right +{ + float: left; +} +ul.moderation_notes li +{ + padding: 4px 4px 4px 0; +} \ No newline at end of file diff --git a/Themes/default/css/webkit.css b/Themes/default/css/webkit.css new file mode 100644 index 0000000..bfd0c99 --- /dev/null +++ b/Themes/default/css/webkit.css @@ -0,0 +1,10 @@ +/* + Special styles for Safari (and other Webkit-based browsers like Chrome) + Webkit needs this otherwise the post goes off to the right. + Causes issues in IE browsers, and breaks cached search engines pages. +*/ + +table.table_list tbody.header td div.cat_bar +{ + margin-bottom: -1px; +} diff --git a/Themes/default/css/wireless.css b/Themes/default/css/wireless.css new file mode 100644 index 0000000..c12b041 --- /dev/null +++ b/Themes/default/css/wireless.css @@ -0,0 +1,35 @@ +.catbg, tr.catbg td +{ + background-color: #6d92aa; + color: #fff; +} +.titlebg, .titlebg a, .titlebg a:link, .titlebg a:visited +{ + background-color: #b6dbff; + color: #000; + text-decoration: none; +} +.windowbg, tr.windowbg td +{ + background-color: #fff; + color: #000; +} +.windowbg2, tr.windowbg2 td +{ + background-color: #c0c0c0; + color: #000; +} +.new, a:link.new, a:visited.new +{ + background-color: #2f2fc0; + color: #fff; +} +.updated +{ + color: red; +} +/* Resize our post area as needed */ +#message +{ + width: 98%; +} \ No newline at end of file diff --git a/Themes/default/fonts/Candice.gdf b/Themes/default/fonts/Candice.gdf new file mode 100644 index 0000000..ab8c3da Binary files /dev/null and b/Themes/default/fonts/Candice.gdf differ diff --git a/Themes/default/fonts/Candice/a.gif b/Themes/default/fonts/Candice/a.gif new file mode 100644 index 0000000..dd63a6a Binary files /dev/null and b/Themes/default/fonts/Candice/a.gif differ diff --git a/Themes/default/fonts/Candice/b.gif b/Themes/default/fonts/Candice/b.gif new file mode 100644 index 0000000..7561217 Binary files /dev/null and b/Themes/default/fonts/Candice/b.gif differ diff --git a/Themes/default/fonts/Candice/c.gif b/Themes/default/fonts/Candice/c.gif new file mode 100644 index 0000000..d5f73b3 Binary files /dev/null and b/Themes/default/fonts/Candice/c.gif differ diff --git a/Themes/default/fonts/Candice/d.gif b/Themes/default/fonts/Candice/d.gif new file mode 100644 index 0000000..5e2153f Binary files /dev/null and b/Themes/default/fonts/Candice/d.gif differ diff --git a/Themes/default/fonts/Candice/e.gif b/Themes/default/fonts/Candice/e.gif new file mode 100644 index 0000000..d504eac Binary files /dev/null and b/Themes/default/fonts/Candice/e.gif differ diff --git a/Themes/default/fonts/Candice/f.gif b/Themes/default/fonts/Candice/f.gif new file mode 100644 index 0000000..c4fc42f Binary files /dev/null and b/Themes/default/fonts/Candice/f.gif differ diff --git a/Themes/default/fonts/Candice/g.gif b/Themes/default/fonts/Candice/g.gif new file mode 100644 index 0000000..8fa8444 Binary files /dev/null and b/Themes/default/fonts/Candice/g.gif differ diff --git a/Themes/default/fonts/Candice/h.gif b/Themes/default/fonts/Candice/h.gif new file mode 100644 index 0000000..14f65e9 Binary files /dev/null and b/Themes/default/fonts/Candice/h.gif differ diff --git a/Themes/default/fonts/Candice/i.gif b/Themes/default/fonts/Candice/i.gif new file mode 100644 index 0000000..0d3cc45 Binary files /dev/null and b/Themes/default/fonts/Candice/i.gif differ diff --git a/Themes/default/fonts/Candice/index.php b/Themes/default/fonts/Candice/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/fonts/Candice/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/fonts/Candice/j.gif b/Themes/default/fonts/Candice/j.gif new file mode 100644 index 0000000..0657cf9 Binary files /dev/null and b/Themes/default/fonts/Candice/j.gif differ diff --git a/Themes/default/fonts/Candice/k.gif b/Themes/default/fonts/Candice/k.gif new file mode 100644 index 0000000..d74970f Binary files /dev/null and b/Themes/default/fonts/Candice/k.gif differ diff --git a/Themes/default/fonts/Candice/l.gif b/Themes/default/fonts/Candice/l.gif new file mode 100644 index 0000000..6ce29d9 Binary files /dev/null and b/Themes/default/fonts/Candice/l.gif differ diff --git a/Themes/default/fonts/Candice/m.gif b/Themes/default/fonts/Candice/m.gif new file mode 100644 index 0000000..f6e7d4d Binary files /dev/null and b/Themes/default/fonts/Candice/m.gif differ diff --git a/Themes/default/fonts/Candice/n.gif b/Themes/default/fonts/Candice/n.gif new file mode 100644 index 0000000..571e2f0 Binary files /dev/null and b/Themes/default/fonts/Candice/n.gif differ diff --git a/Themes/default/fonts/Candice/o.gif b/Themes/default/fonts/Candice/o.gif new file mode 100644 index 0000000..4d99349 Binary files /dev/null and b/Themes/default/fonts/Candice/o.gif differ diff --git a/Themes/default/fonts/Candice/p.gif b/Themes/default/fonts/Candice/p.gif new file mode 100644 index 0000000..d299641 Binary files /dev/null and b/Themes/default/fonts/Candice/p.gif differ diff --git a/Themes/default/fonts/Candice/q.gif b/Themes/default/fonts/Candice/q.gif new file mode 100644 index 0000000..fc6f057 Binary files /dev/null and b/Themes/default/fonts/Candice/q.gif differ diff --git a/Themes/default/fonts/Candice/r.gif b/Themes/default/fonts/Candice/r.gif new file mode 100644 index 0000000..47ef493 Binary files /dev/null and b/Themes/default/fonts/Candice/r.gif differ diff --git a/Themes/default/fonts/Candice/s.gif b/Themes/default/fonts/Candice/s.gif new file mode 100644 index 0000000..d881ed2 Binary files /dev/null and b/Themes/default/fonts/Candice/s.gif differ diff --git a/Themes/default/fonts/Candice/t.gif b/Themes/default/fonts/Candice/t.gif new file mode 100644 index 0000000..6649db6 Binary files /dev/null and b/Themes/default/fonts/Candice/t.gif differ diff --git a/Themes/default/fonts/Candice/u.gif b/Themes/default/fonts/Candice/u.gif new file mode 100644 index 0000000..ecf1831 Binary files /dev/null and b/Themes/default/fonts/Candice/u.gif differ diff --git a/Themes/default/fonts/Candice/v.gif b/Themes/default/fonts/Candice/v.gif new file mode 100644 index 0000000..b10f143 Binary files /dev/null and b/Themes/default/fonts/Candice/v.gif differ diff --git a/Themes/default/fonts/Candice/w.gif b/Themes/default/fonts/Candice/w.gif new file mode 100644 index 0000000..2a205ea Binary files /dev/null and b/Themes/default/fonts/Candice/w.gif differ diff --git a/Themes/default/fonts/Candice/x.gif b/Themes/default/fonts/Candice/x.gif new file mode 100644 index 0000000..ed53597 Binary files /dev/null and b/Themes/default/fonts/Candice/x.gif differ diff --git a/Themes/default/fonts/Candice/y.gif b/Themes/default/fonts/Candice/y.gif new file mode 100644 index 0000000..2419c31 Binary files /dev/null and b/Themes/default/fonts/Candice/y.gif differ diff --git a/Themes/default/fonts/Candice/z.gif b/Themes/default/fonts/Candice/z.gif new file mode 100644 index 0000000..3854bb3 Binary files /dev/null and b/Themes/default/fonts/Candice/z.gif differ diff --git a/Themes/default/fonts/Forgottb.ttf b/Themes/default/fonts/Forgottb.ttf new file mode 100644 index 0000000..3f4589d Binary files /dev/null and b/Themes/default/fonts/Forgottb.ttf differ diff --git a/Themes/default/fonts/Hootie.gdf b/Themes/default/fonts/Hootie.gdf new file mode 100644 index 0000000..2e1a383 Binary files /dev/null and b/Themes/default/fonts/Hootie.gdf differ diff --git a/Themes/default/fonts/Hootie/a.gif b/Themes/default/fonts/Hootie/a.gif new file mode 100644 index 0000000..5436f54 Binary files /dev/null and b/Themes/default/fonts/Hootie/a.gif differ diff --git a/Themes/default/fonts/Hootie/b.gif b/Themes/default/fonts/Hootie/b.gif new file mode 100644 index 0000000..03d906f Binary files /dev/null and b/Themes/default/fonts/Hootie/b.gif differ diff --git a/Themes/default/fonts/Hootie/c.gif b/Themes/default/fonts/Hootie/c.gif new file mode 100644 index 0000000..52e9d45 Binary files /dev/null and b/Themes/default/fonts/Hootie/c.gif differ diff --git a/Themes/default/fonts/Hootie/d.gif b/Themes/default/fonts/Hootie/d.gif new file mode 100644 index 0000000..d272fcd Binary files /dev/null and b/Themes/default/fonts/Hootie/d.gif differ diff --git a/Themes/default/fonts/Hootie/e.gif b/Themes/default/fonts/Hootie/e.gif new file mode 100644 index 0000000..7f0201e Binary files /dev/null and b/Themes/default/fonts/Hootie/e.gif differ diff --git a/Themes/default/fonts/Hootie/f.gif b/Themes/default/fonts/Hootie/f.gif new file mode 100644 index 0000000..7256b9e Binary files /dev/null and b/Themes/default/fonts/Hootie/f.gif differ diff --git a/Themes/default/fonts/Hootie/g.gif b/Themes/default/fonts/Hootie/g.gif new file mode 100644 index 0000000..9a55c16 Binary files /dev/null and b/Themes/default/fonts/Hootie/g.gif differ diff --git a/Themes/default/fonts/Hootie/h.gif b/Themes/default/fonts/Hootie/h.gif new file mode 100644 index 0000000..607dfbf Binary files /dev/null and b/Themes/default/fonts/Hootie/h.gif differ diff --git a/Themes/default/fonts/Hootie/i.gif b/Themes/default/fonts/Hootie/i.gif new file mode 100644 index 0000000..dd7a13d Binary files /dev/null and b/Themes/default/fonts/Hootie/i.gif differ diff --git a/Themes/default/fonts/Hootie/index.php b/Themes/default/fonts/Hootie/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/fonts/Hootie/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/fonts/Hootie/j.gif b/Themes/default/fonts/Hootie/j.gif new file mode 100644 index 0000000..f854411 Binary files /dev/null and b/Themes/default/fonts/Hootie/j.gif differ diff --git a/Themes/default/fonts/Hootie/k.gif b/Themes/default/fonts/Hootie/k.gif new file mode 100644 index 0000000..1044f71 Binary files /dev/null and b/Themes/default/fonts/Hootie/k.gif differ diff --git a/Themes/default/fonts/Hootie/l.gif b/Themes/default/fonts/Hootie/l.gif new file mode 100644 index 0000000..fb51d8a Binary files /dev/null and b/Themes/default/fonts/Hootie/l.gif differ diff --git a/Themes/default/fonts/Hootie/m.gif b/Themes/default/fonts/Hootie/m.gif new file mode 100644 index 0000000..773af11 Binary files /dev/null and b/Themes/default/fonts/Hootie/m.gif differ diff --git a/Themes/default/fonts/Hootie/n.gif b/Themes/default/fonts/Hootie/n.gif new file mode 100644 index 0000000..bb60edf Binary files /dev/null and b/Themes/default/fonts/Hootie/n.gif differ diff --git a/Themes/default/fonts/Hootie/o.gif b/Themes/default/fonts/Hootie/o.gif new file mode 100644 index 0000000..dbe409e Binary files /dev/null and b/Themes/default/fonts/Hootie/o.gif differ diff --git a/Themes/default/fonts/Hootie/p.gif b/Themes/default/fonts/Hootie/p.gif new file mode 100644 index 0000000..9d6329d Binary files /dev/null and b/Themes/default/fonts/Hootie/p.gif differ diff --git a/Themes/default/fonts/Hootie/q.gif b/Themes/default/fonts/Hootie/q.gif new file mode 100644 index 0000000..655ca1b Binary files /dev/null and b/Themes/default/fonts/Hootie/q.gif differ diff --git a/Themes/default/fonts/Hootie/r.gif b/Themes/default/fonts/Hootie/r.gif new file mode 100644 index 0000000..c964e12 Binary files /dev/null and b/Themes/default/fonts/Hootie/r.gif differ diff --git a/Themes/default/fonts/Hootie/s.gif b/Themes/default/fonts/Hootie/s.gif new file mode 100644 index 0000000..481b65a Binary files /dev/null and b/Themes/default/fonts/Hootie/s.gif differ diff --git a/Themes/default/fonts/Hootie/t.gif b/Themes/default/fonts/Hootie/t.gif new file mode 100644 index 0000000..d000db5 Binary files /dev/null and b/Themes/default/fonts/Hootie/t.gif differ diff --git a/Themes/default/fonts/Hootie/u.gif b/Themes/default/fonts/Hootie/u.gif new file mode 100644 index 0000000..3f26f8f Binary files /dev/null and b/Themes/default/fonts/Hootie/u.gif differ diff --git a/Themes/default/fonts/Hootie/v.gif b/Themes/default/fonts/Hootie/v.gif new file mode 100644 index 0000000..32eeea4 Binary files /dev/null and b/Themes/default/fonts/Hootie/v.gif differ diff --git a/Themes/default/fonts/Hootie/w.gif b/Themes/default/fonts/Hootie/w.gif new file mode 100644 index 0000000..c25215b Binary files /dev/null and b/Themes/default/fonts/Hootie/w.gif differ diff --git a/Themes/default/fonts/Hootie/x.gif b/Themes/default/fonts/Hootie/x.gif new file mode 100644 index 0000000..5a63d85 Binary files /dev/null and b/Themes/default/fonts/Hootie/x.gif differ diff --git a/Themes/default/fonts/Hootie/y.gif b/Themes/default/fonts/Hootie/y.gif new file mode 100644 index 0000000..8957c1d Binary files /dev/null and b/Themes/default/fonts/Hootie/y.gif differ diff --git a/Themes/default/fonts/Hootie/z.gif b/Themes/default/fonts/Hootie/z.gif new file mode 100644 index 0000000..a2aae99 Binary files /dev/null and b/Themes/default/fonts/Hootie/z.gif differ diff --git a/Themes/default/fonts/Kimbalt.ttf b/Themes/default/fonts/Kimbalt.ttf new file mode 100644 index 0000000..526b797 Binary files /dev/null and b/Themes/default/fonts/Kimbalt.ttf differ diff --git a/Themes/default/fonts/President.gdf b/Themes/default/fonts/President.gdf new file mode 100644 index 0000000..d6cab3b Binary files /dev/null and b/Themes/default/fonts/President.gdf differ diff --git a/Themes/default/fonts/President/a.gif b/Themes/default/fonts/President/a.gif new file mode 100644 index 0000000..bf231a8 Binary files /dev/null and b/Themes/default/fonts/President/a.gif differ diff --git a/Themes/default/fonts/President/b.gif b/Themes/default/fonts/President/b.gif new file mode 100644 index 0000000..4204365 Binary files /dev/null and b/Themes/default/fonts/President/b.gif differ diff --git a/Themes/default/fonts/President/c.gif b/Themes/default/fonts/President/c.gif new file mode 100644 index 0000000..68babb7 Binary files /dev/null and b/Themes/default/fonts/President/c.gif differ diff --git a/Themes/default/fonts/President/d.gif b/Themes/default/fonts/President/d.gif new file mode 100644 index 0000000..c245259 Binary files /dev/null and b/Themes/default/fonts/President/d.gif differ diff --git a/Themes/default/fonts/President/e.gif b/Themes/default/fonts/President/e.gif new file mode 100644 index 0000000..5c2d1a6 Binary files /dev/null and b/Themes/default/fonts/President/e.gif differ diff --git a/Themes/default/fonts/President/f.gif b/Themes/default/fonts/President/f.gif new file mode 100644 index 0000000..3e8a33b Binary files /dev/null and b/Themes/default/fonts/President/f.gif differ diff --git a/Themes/default/fonts/President/g.gif b/Themes/default/fonts/President/g.gif new file mode 100644 index 0000000..6c398f3 Binary files /dev/null and b/Themes/default/fonts/President/g.gif differ diff --git a/Themes/default/fonts/President/h.gif b/Themes/default/fonts/President/h.gif new file mode 100644 index 0000000..fcf4355 Binary files /dev/null and b/Themes/default/fonts/President/h.gif differ diff --git a/Themes/default/fonts/President/i.gif b/Themes/default/fonts/President/i.gif new file mode 100644 index 0000000..e915b80 Binary files /dev/null and b/Themes/default/fonts/President/i.gif differ diff --git a/Themes/default/fonts/President/index.php b/Themes/default/fonts/President/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/fonts/President/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/fonts/President/j.gif b/Themes/default/fonts/President/j.gif new file mode 100644 index 0000000..7b98c49 Binary files /dev/null and b/Themes/default/fonts/President/j.gif differ diff --git a/Themes/default/fonts/President/k.gif b/Themes/default/fonts/President/k.gif new file mode 100644 index 0000000..1379132 Binary files /dev/null and b/Themes/default/fonts/President/k.gif differ diff --git a/Themes/default/fonts/President/l.gif b/Themes/default/fonts/President/l.gif new file mode 100644 index 0000000..859241b Binary files /dev/null and b/Themes/default/fonts/President/l.gif differ diff --git a/Themes/default/fonts/President/m.gif b/Themes/default/fonts/President/m.gif new file mode 100644 index 0000000..b0fac23 Binary files /dev/null and b/Themes/default/fonts/President/m.gif differ diff --git a/Themes/default/fonts/President/n.gif b/Themes/default/fonts/President/n.gif new file mode 100644 index 0000000..04e801b Binary files /dev/null and b/Themes/default/fonts/President/n.gif differ diff --git a/Themes/default/fonts/President/o.gif b/Themes/default/fonts/President/o.gif new file mode 100644 index 0000000..8b7a0d8 Binary files /dev/null and b/Themes/default/fonts/President/o.gif differ diff --git a/Themes/default/fonts/President/p.gif b/Themes/default/fonts/President/p.gif new file mode 100644 index 0000000..24aeea2 Binary files /dev/null and b/Themes/default/fonts/President/p.gif differ diff --git a/Themes/default/fonts/President/q.gif b/Themes/default/fonts/President/q.gif new file mode 100644 index 0000000..a6e685a Binary files /dev/null and b/Themes/default/fonts/President/q.gif differ diff --git a/Themes/default/fonts/President/r.gif b/Themes/default/fonts/President/r.gif new file mode 100644 index 0000000..250a5ec Binary files /dev/null and b/Themes/default/fonts/President/r.gif differ diff --git a/Themes/default/fonts/President/s.gif b/Themes/default/fonts/President/s.gif new file mode 100644 index 0000000..7e361bf Binary files /dev/null and b/Themes/default/fonts/President/s.gif differ diff --git a/Themes/default/fonts/President/t.gif b/Themes/default/fonts/President/t.gif new file mode 100644 index 0000000..e7399f7 Binary files /dev/null and b/Themes/default/fonts/President/t.gif differ diff --git a/Themes/default/fonts/President/u.gif b/Themes/default/fonts/President/u.gif new file mode 100644 index 0000000..43a3f0d Binary files /dev/null and b/Themes/default/fonts/President/u.gif differ diff --git a/Themes/default/fonts/President/v.gif b/Themes/default/fonts/President/v.gif new file mode 100644 index 0000000..cc7fd22 Binary files /dev/null and b/Themes/default/fonts/President/v.gif differ diff --git a/Themes/default/fonts/President/w.gif b/Themes/default/fonts/President/w.gif new file mode 100644 index 0000000..a8979c6 Binary files /dev/null and b/Themes/default/fonts/President/w.gif differ diff --git a/Themes/default/fonts/President/x.gif b/Themes/default/fonts/President/x.gif new file mode 100644 index 0000000..142cd1a Binary files /dev/null and b/Themes/default/fonts/President/x.gif differ diff --git a/Themes/default/fonts/President/y.gif b/Themes/default/fonts/President/y.gif new file mode 100644 index 0000000..bd35e24 Binary files /dev/null and b/Themes/default/fonts/President/y.gif differ diff --git a/Themes/default/fonts/President/z.gif b/Themes/default/fonts/President/z.gif new file mode 100644 index 0000000..ff06d3b Binary files /dev/null and b/Themes/default/fonts/President/z.gif differ diff --git a/Themes/default/fonts/Screenge.ttf b/Themes/default/fonts/Screenge.ttf new file mode 100644 index 0000000..ccbeafb Binary files /dev/null and b/Themes/default/fonts/Screenge.ttf differ diff --git a/Themes/default/fonts/Venusris.ttf b/Themes/default/fonts/Venusris.ttf new file mode 100644 index 0000000..1d6e6a4 Binary files /dev/null and b/Themes/default/fonts/Venusris.ttf differ diff --git a/Themes/default/fonts/Walshes.ttf b/Themes/default/fonts/Walshes.ttf new file mode 100644 index 0000000..c7cacd6 Binary files /dev/null and b/Themes/default/fonts/Walshes.ttf differ diff --git a/Themes/default/fonts/index.php b/Themes/default/fonts/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/fonts/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/fonts/sound/a.english.wav b/Themes/default/fonts/sound/a.english.wav new file mode 100644 index 0000000..24aadb9 Binary files /dev/null and b/Themes/default/fonts/sound/a.english.wav differ diff --git a/Themes/default/fonts/sound/b.english.wav b/Themes/default/fonts/sound/b.english.wav new file mode 100644 index 0000000..98944b1 Binary files /dev/null and b/Themes/default/fonts/sound/b.english.wav differ diff --git a/Themes/default/fonts/sound/c.english.wav b/Themes/default/fonts/sound/c.english.wav new file mode 100644 index 0000000..9a0e694 Binary files /dev/null and b/Themes/default/fonts/sound/c.english.wav differ diff --git a/Themes/default/fonts/sound/d.english.wav b/Themes/default/fonts/sound/d.english.wav new file mode 100644 index 0000000..4ae05e6 Binary files /dev/null and b/Themes/default/fonts/sound/d.english.wav differ diff --git a/Themes/default/fonts/sound/e.english.wav b/Themes/default/fonts/sound/e.english.wav new file mode 100644 index 0000000..4b38bd0 Binary files /dev/null and b/Themes/default/fonts/sound/e.english.wav differ diff --git a/Themes/default/fonts/sound/f.english.wav b/Themes/default/fonts/sound/f.english.wav new file mode 100644 index 0000000..32bc06d Binary files /dev/null and b/Themes/default/fonts/sound/f.english.wav differ diff --git a/Themes/default/fonts/sound/g.english.wav b/Themes/default/fonts/sound/g.english.wav new file mode 100644 index 0000000..a320bdd Binary files /dev/null and b/Themes/default/fonts/sound/g.english.wav differ diff --git a/Themes/default/fonts/sound/h.english.wav b/Themes/default/fonts/sound/h.english.wav new file mode 100644 index 0000000..4a18ffb Binary files /dev/null and b/Themes/default/fonts/sound/h.english.wav differ diff --git a/Themes/default/fonts/sound/i.english.wav b/Themes/default/fonts/sound/i.english.wav new file mode 100644 index 0000000..5653de5 Binary files /dev/null and b/Themes/default/fonts/sound/i.english.wav differ diff --git a/Themes/default/fonts/sound/index.php b/Themes/default/fonts/sound/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/fonts/sound/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/fonts/sound/j.english.wav b/Themes/default/fonts/sound/j.english.wav new file mode 100644 index 0000000..0a90133 Binary files /dev/null and b/Themes/default/fonts/sound/j.english.wav differ diff --git a/Themes/default/fonts/sound/k.english.wav b/Themes/default/fonts/sound/k.english.wav new file mode 100644 index 0000000..1dc5b7c Binary files /dev/null and b/Themes/default/fonts/sound/k.english.wav differ diff --git a/Themes/default/fonts/sound/l.english.wav b/Themes/default/fonts/sound/l.english.wav new file mode 100644 index 0000000..c3ff6bf Binary files /dev/null and b/Themes/default/fonts/sound/l.english.wav differ diff --git a/Themes/default/fonts/sound/m.english.wav b/Themes/default/fonts/sound/m.english.wav new file mode 100644 index 0000000..c190e75 Binary files /dev/null and b/Themes/default/fonts/sound/m.english.wav differ diff --git a/Themes/default/fonts/sound/n.english.wav b/Themes/default/fonts/sound/n.english.wav new file mode 100644 index 0000000..7654671 Binary files /dev/null and b/Themes/default/fonts/sound/n.english.wav differ diff --git a/Themes/default/fonts/sound/o.english.wav b/Themes/default/fonts/sound/o.english.wav new file mode 100644 index 0000000..e97e870 Binary files /dev/null and b/Themes/default/fonts/sound/o.english.wav differ diff --git a/Themes/default/fonts/sound/p.english.wav b/Themes/default/fonts/sound/p.english.wav new file mode 100644 index 0000000..226e485 Binary files /dev/null and b/Themes/default/fonts/sound/p.english.wav differ diff --git a/Themes/default/fonts/sound/q.english.wav b/Themes/default/fonts/sound/q.english.wav new file mode 100644 index 0000000..f97e012 Binary files /dev/null and b/Themes/default/fonts/sound/q.english.wav differ diff --git a/Themes/default/fonts/sound/r.english.wav b/Themes/default/fonts/sound/r.english.wav new file mode 100644 index 0000000..d76f476 Binary files /dev/null and b/Themes/default/fonts/sound/r.english.wav differ diff --git a/Themes/default/fonts/sound/s.english.wav b/Themes/default/fonts/sound/s.english.wav new file mode 100644 index 0000000..c29c030 Binary files /dev/null and b/Themes/default/fonts/sound/s.english.wav differ diff --git a/Themes/default/fonts/sound/t.english.wav b/Themes/default/fonts/sound/t.english.wav new file mode 100644 index 0000000..ee2fa96 Binary files /dev/null and b/Themes/default/fonts/sound/t.english.wav differ diff --git a/Themes/default/fonts/sound/u.english.wav b/Themes/default/fonts/sound/u.english.wav new file mode 100644 index 0000000..34af33c Binary files /dev/null and b/Themes/default/fonts/sound/u.english.wav differ diff --git a/Themes/default/fonts/sound/v.english.wav b/Themes/default/fonts/sound/v.english.wav new file mode 100644 index 0000000..a40c0f3 Binary files /dev/null and b/Themes/default/fonts/sound/v.english.wav differ diff --git a/Themes/default/fonts/sound/w.english.wav b/Themes/default/fonts/sound/w.english.wav new file mode 100644 index 0000000..0ab70c9 Binary files /dev/null and b/Themes/default/fonts/sound/w.english.wav differ diff --git a/Themes/default/fonts/sound/x.english.wav b/Themes/default/fonts/sound/x.english.wav new file mode 100644 index 0000000..56c601d Binary files /dev/null and b/Themes/default/fonts/sound/x.english.wav differ diff --git a/Themes/default/fonts/sound/y.english.wav b/Themes/default/fonts/sound/y.english.wav new file mode 100644 index 0000000..76cb401 Binary files /dev/null and b/Themes/default/fonts/sound/y.english.wav differ diff --git a/Themes/default/fonts/sound/z.english.wav b/Themes/default/fonts/sound/z.english.wav new file mode 100644 index 0000000..c2b4fe4 Binary files /dev/null and b/Themes/default/fonts/sound/z.english.wav differ diff --git a/Themes/default/images/Female.gif b/Themes/default/images/Female.gif new file mode 100644 index 0000000..12446d1 Binary files /dev/null and b/Themes/default/images/Female.gif differ diff --git a/Themes/default/images/Male.gif b/Themes/default/images/Male.gif new file mode 100644 index 0000000..3cd01eb Binary files /dev/null and b/Themes/default/images/Male.gif differ diff --git a/Themes/default/images/admin/administration.gif b/Themes/default/images/admin/administration.gif new file mode 100644 index 0000000..c634703 Binary files /dev/null and b/Themes/default/images/admin/administration.gif differ diff --git a/Themes/default/images/admin/attachment.gif b/Themes/default/images/admin/attachment.gif new file mode 100644 index 0000000..506491f Binary files /dev/null and b/Themes/default/images/admin/attachment.gif differ diff --git a/Themes/default/images/admin/ban.gif b/Themes/default/images/admin/ban.gif new file mode 100644 index 0000000..3f5a497 Binary files /dev/null and b/Themes/default/images/admin/ban.gif differ diff --git a/Themes/default/images/admin/boards.gif b/Themes/default/images/admin/boards.gif new file mode 100644 index 0000000..2f3b303 Binary files /dev/null and b/Themes/default/images/admin/boards.gif differ diff --git a/Themes/default/images/admin/calendar.gif b/Themes/default/images/admin/calendar.gif new file mode 100644 index 0000000..66accbf Binary files /dev/null and b/Themes/default/images/admin/calendar.gif differ diff --git a/Themes/default/images/admin/change_menu.png b/Themes/default/images/admin/change_menu.png new file mode 100644 index 0000000..c326e87 Binary files /dev/null and b/Themes/default/images/admin/change_menu.png differ diff --git a/Themes/default/images/admin/change_menu2.png b/Themes/default/images/admin/change_menu2.png new file mode 100644 index 0000000..7a66564 Binary files /dev/null and b/Themes/default/images/admin/change_menu2.png differ diff --git a/Themes/default/images/admin/corefeatures.gif b/Themes/default/images/admin/corefeatures.gif new file mode 100644 index 0000000..902a437 Binary files /dev/null and b/Themes/default/images/admin/corefeatures.gif differ diff --git a/Themes/default/images/admin/current_theme.gif b/Themes/default/images/admin/current_theme.gif new file mode 100644 index 0000000..b661fd3 Binary files /dev/null and b/Themes/default/images/admin/current_theme.gif differ diff --git a/Themes/default/images/admin/engines.gif b/Themes/default/images/admin/engines.gif new file mode 100644 index 0000000..1517f0e Binary files /dev/null and b/Themes/default/images/admin/engines.gif differ diff --git a/Themes/default/images/admin/feature_cd.png b/Themes/default/images/admin/feature_cd.png new file mode 100644 index 0000000..5bf830f Binary files /dev/null and b/Themes/default/images/admin/feature_cd.png differ diff --git a/Themes/default/images/admin/feature_cp.png b/Themes/default/images/admin/feature_cp.png new file mode 100644 index 0000000..9b93c6b Binary files /dev/null and b/Themes/default/images/admin/feature_cp.png differ diff --git a/Themes/default/images/admin/feature_k.png b/Themes/default/images/admin/feature_k.png new file mode 100644 index 0000000..7709462 Binary files /dev/null and b/Themes/default/images/admin/feature_k.png differ diff --git a/Themes/default/images/admin/feature_ml.png b/Themes/default/images/admin/feature_ml.png new file mode 100644 index 0000000..d124448 Binary files /dev/null and b/Themes/default/images/admin/feature_ml.png differ diff --git a/Themes/default/images/admin/feature_pm.png b/Themes/default/images/admin/feature_pm.png new file mode 100644 index 0000000..89725ad Binary files /dev/null and b/Themes/default/images/admin/feature_pm.png differ diff --git a/Themes/default/images/admin/feature_ps.png b/Themes/default/images/admin/feature_ps.png new file mode 100644 index 0000000..038cc85 Binary files /dev/null and b/Themes/default/images/admin/feature_ps.png differ diff --git a/Themes/default/images/admin/feature_rg.png b/Themes/default/images/admin/feature_rg.png new file mode 100644 index 0000000..4acb947 Binary files /dev/null and b/Themes/default/images/admin/feature_rg.png differ diff --git a/Themes/default/images/admin/feature_sp.png b/Themes/default/images/admin/feature_sp.png new file mode 100644 index 0000000..0d25f73 Binary files /dev/null and b/Themes/default/images/admin/feature_sp.png differ diff --git a/Themes/default/images/admin/feature_w.png b/Themes/default/images/admin/feature_w.png new file mode 100644 index 0000000..bec0b0a Binary files /dev/null and b/Themes/default/images/admin/feature_w.png differ diff --git a/Themes/default/images/admin/features.gif b/Themes/default/images/admin/features.gif new file mode 100644 index 0000000..a098a2d Binary files /dev/null and b/Themes/default/images/admin/features.gif differ diff --git a/Themes/default/images/admin/features_and_options.png b/Themes/default/images/admin/features_and_options.png new file mode 100644 index 0000000..af362c3 Binary files /dev/null and b/Themes/default/images/admin/features_and_options.png differ diff --git a/Themes/default/images/admin/forum_maintenance.png b/Themes/default/images/admin/forum_maintenance.png new file mode 100644 index 0000000..15d591c Binary files /dev/null and b/Themes/default/images/admin/forum_maintenance.png differ diff --git a/Themes/default/images/admin/ignore.gif b/Themes/default/images/admin/ignore.gif new file mode 100644 index 0000000..df68cca Binary files /dev/null and b/Themes/default/images/admin/ignore.gif differ diff --git a/Themes/default/images/admin/index.php b/Themes/default/images/admin/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/images/admin/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/images/admin/languages.gif b/Themes/default/images/admin/languages.gif new file mode 100644 index 0000000..0020f45 Binary files /dev/null and b/Themes/default/images/admin/languages.gif differ diff --git a/Themes/default/images/admin/logs.gif b/Themes/default/images/admin/logs.gif new file mode 100644 index 0000000..597a35b Binary files /dev/null and b/Themes/default/images/admin/logs.gif differ diff --git a/Themes/default/images/admin/mail.gif b/Themes/default/images/admin/mail.gif new file mode 100644 index 0000000..ba4f6cc Binary files /dev/null and b/Themes/default/images/admin/mail.gif differ diff --git a/Themes/default/images/admin/maintain.gif b/Themes/default/images/admin/maintain.gif new file mode 100644 index 0000000..68cb4f7 Binary files /dev/null and b/Themes/default/images/admin/maintain.gif differ diff --git a/Themes/default/images/admin/membergroups.gif b/Themes/default/images/admin/membergroups.gif new file mode 100644 index 0000000..ba8eb89 Binary files /dev/null and b/Themes/default/images/admin/membergroups.gif differ diff --git a/Themes/default/images/admin/members.gif b/Themes/default/images/admin/members.gif new file mode 100644 index 0000000..cac6d1e Binary files /dev/null and b/Themes/default/images/admin/members.gif differ diff --git a/Themes/default/images/admin/members.png b/Themes/default/images/admin/members.png new file mode 100644 index 0000000..e974928 Binary files /dev/null and b/Themes/default/images/admin/members.png differ diff --git a/Themes/default/images/admin/modifications.gif b/Themes/default/images/admin/modifications.gif new file mode 100644 index 0000000..1512046 Binary files /dev/null and b/Themes/default/images/admin/modifications.gif differ diff --git a/Themes/default/images/admin/news.gif b/Themes/default/images/admin/news.gif new file mode 100644 index 0000000..e8f6ac5 Binary files /dev/null and b/Themes/default/images/admin/news.gif differ diff --git a/Themes/default/images/admin/package_ops.gif b/Themes/default/images/admin/package_ops.gif new file mode 100644 index 0000000..8c612d8 Binary files /dev/null and b/Themes/default/images/admin/package_ops.gif differ diff --git a/Themes/default/images/admin/packages.gif b/Themes/default/images/admin/packages.gif new file mode 100644 index 0000000..2ffec4f Binary files /dev/null and b/Themes/default/images/admin/packages.gif differ diff --git a/Themes/default/images/admin/packages.png b/Themes/default/images/admin/packages.png new file mode 100644 index 0000000..6c56b53 Binary files /dev/null and b/Themes/default/images/admin/packages.png differ diff --git a/Themes/default/images/admin/paid.gif b/Themes/default/images/admin/paid.gif new file mode 100644 index 0000000..645ba22 Binary files /dev/null and b/Themes/default/images/admin/paid.gif differ diff --git a/Themes/default/images/admin/permissions.gif b/Themes/default/images/admin/permissions.gif new file mode 100644 index 0000000..6e32f3e Binary files /dev/null and b/Themes/default/images/admin/permissions.gif differ diff --git a/Themes/default/images/admin/permissions.png b/Themes/default/images/admin/permissions.png new file mode 100644 index 0000000..03316ea Binary files /dev/null and b/Themes/default/images/admin/permissions.png differ diff --git a/Themes/default/images/admin/post_moderation_allow.gif b/Themes/default/images/admin/post_moderation_allow.gif new file mode 100644 index 0000000..0d855ed Binary files /dev/null and b/Themes/default/images/admin/post_moderation_allow.gif differ diff --git a/Themes/default/images/admin/post_moderation_deny.gif b/Themes/default/images/admin/post_moderation_deny.gif new file mode 100644 index 0000000..0c9f775 Binary files /dev/null and b/Themes/default/images/admin/post_moderation_deny.gif differ diff --git a/Themes/default/images/admin/post_moderation_moderate.gif b/Themes/default/images/admin/post_moderation_moderate.gif new file mode 100644 index 0000000..c13ec64 Binary files /dev/null and b/Themes/default/images/admin/post_moderation_moderate.gif differ diff --git a/Themes/default/images/admin/posts.gif b/Themes/default/images/admin/posts.gif new file mode 100644 index 0000000..9418230 Binary files /dev/null and b/Themes/default/images/admin/posts.gif differ diff --git a/Themes/default/images/admin/regcenter.gif b/Themes/default/images/admin/regcenter.gif new file mode 100644 index 0000000..5f0b840 Binary files /dev/null and b/Themes/default/images/admin/regcenter.gif differ diff --git a/Themes/default/images/admin/reports.gif b/Themes/default/images/admin/reports.gif new file mode 100644 index 0000000..72b9c64 Binary files /dev/null and b/Themes/default/images/admin/reports.gif differ diff --git a/Themes/default/images/admin/scheduled.gif b/Themes/default/images/admin/scheduled.gif new file mode 100644 index 0000000..f502811 Binary files /dev/null and b/Themes/default/images/admin/scheduled.gif differ diff --git a/Themes/default/images/admin/search.gif b/Themes/default/images/admin/search.gif new file mode 100644 index 0000000..934077c Binary files /dev/null and b/Themes/default/images/admin/search.gif differ diff --git a/Themes/default/images/admin/security.gif b/Themes/default/images/admin/security.gif new file mode 100644 index 0000000..ffb2842 Binary files /dev/null and b/Themes/default/images/admin/security.gif differ diff --git a/Themes/default/images/admin/server.gif b/Themes/default/images/admin/server.gif new file mode 100644 index 0000000..fb988e5 Binary files /dev/null and b/Themes/default/images/admin/server.gif differ diff --git a/Themes/default/images/admin/smiley.gif b/Themes/default/images/admin/smiley.gif new file mode 100644 index 0000000..d739b74 Binary files /dev/null and b/Themes/default/images/admin/smiley.gif differ diff --git a/Themes/default/images/admin/smilies_and_messageicons.png b/Themes/default/images/admin/smilies_and_messageicons.png new file mode 100644 index 0000000..b898a82 Binary files /dev/null and b/Themes/default/images/admin/smilies_and_messageicons.png differ diff --git a/Themes/default/images/admin/subsection.gif b/Themes/default/images/admin/subsection.gif new file mode 100644 index 0000000..7f82fe5 Binary files /dev/null and b/Themes/default/images/admin/subsection.gif differ diff --git a/Themes/default/images/admin/support.gif b/Themes/default/images/admin/support.gif new file mode 100644 index 0000000..a2a53f0 Binary files /dev/null and b/Themes/default/images/admin/support.gif differ diff --git a/Themes/default/images/admin/support_and_credits.png b/Themes/default/images/admin/support_and_credits.png new file mode 100644 index 0000000..1bf62bf Binary files /dev/null and b/Themes/default/images/admin/support_and_credits.png differ diff --git a/Themes/default/images/admin/switch_off.png b/Themes/default/images/admin/switch_off.png new file mode 100644 index 0000000..f2937d0 Binary files /dev/null and b/Themes/default/images/admin/switch_off.png differ diff --git a/Themes/default/images/admin/switch_on.png b/Themes/default/images/admin/switch_on.png new file mode 100644 index 0000000..b3ba675 Binary files /dev/null and b/Themes/default/images/admin/switch_on.png differ diff --git a/Themes/default/images/admin/themes.gif b/Themes/default/images/admin/themes.gif new file mode 100644 index 0000000..a5e1450 Binary files /dev/null and b/Themes/default/images/admin/themes.gif differ diff --git a/Themes/default/images/admin/themes_and_layout.png b/Themes/default/images/admin/themes_and_layout.png new file mode 100644 index 0000000..d511809 Binary files /dev/null and b/Themes/default/images/admin/themes_and_layout.png differ diff --git a/Themes/default/images/aim.gif b/Themes/default/images/aim.gif new file mode 100644 index 0000000..4eb8df0 Binary files /dev/null and b/Themes/default/images/aim.gif differ diff --git a/Themes/default/images/bar_stats.png b/Themes/default/images/bar_stats.png new file mode 100644 index 0000000..18ca46b Binary files /dev/null and b/Themes/default/images/bar_stats.png differ diff --git a/Themes/default/images/bbc/bbc_bg.gif b/Themes/default/images/bbc/bbc_bg.gif new file mode 100644 index 0000000..c238e89 Binary files /dev/null and b/Themes/default/images/bbc/bbc_bg.gif differ diff --git a/Themes/default/images/bbc/bbc_hoverbg.gif b/Themes/default/images/bbc/bbc_hoverbg.gif new file mode 100644 index 0000000..49c4e91 Binary files /dev/null and b/Themes/default/images/bbc/bbc_hoverbg.gif differ diff --git a/Themes/default/images/bbc/bold.gif b/Themes/default/images/bbc/bold.gif new file mode 100644 index 0000000..075d0f5 Binary files /dev/null and b/Themes/default/images/bbc/bold.gif differ diff --git a/Themes/default/images/bbc/center.gif b/Themes/default/images/bbc/center.gif new file mode 100644 index 0000000..736bc87 Binary files /dev/null and b/Themes/default/images/bbc/center.gif differ diff --git a/Themes/default/images/bbc/code.gif b/Themes/default/images/bbc/code.gif new file mode 100644 index 0000000..3e89df1 Binary files /dev/null and b/Themes/default/images/bbc/code.gif differ diff --git a/Themes/default/images/bbc/divider.gif b/Themes/default/images/bbc/divider.gif new file mode 100644 index 0000000..d4f35e1 Binary files /dev/null and b/Themes/default/images/bbc/divider.gif differ diff --git a/Themes/default/images/bbc/email.gif b/Themes/default/images/bbc/email.gif new file mode 100644 index 0000000..52d01ad Binary files /dev/null and b/Themes/default/images/bbc/email.gif differ diff --git a/Themes/default/images/bbc/flash.gif b/Themes/default/images/bbc/flash.gif new file mode 100644 index 0000000..9ca282c Binary files /dev/null and b/Themes/default/images/bbc/flash.gif differ diff --git a/Themes/default/images/bbc/ftp.gif b/Themes/default/images/bbc/ftp.gif new file mode 100644 index 0000000..7d2384b Binary files /dev/null and b/Themes/default/images/bbc/ftp.gif differ diff --git a/Themes/default/images/bbc/glow.gif b/Themes/default/images/bbc/glow.gif new file mode 100644 index 0000000..0f0dce7 Binary files /dev/null and b/Themes/default/images/bbc/glow.gif differ diff --git a/Themes/default/images/bbc/hr.gif b/Themes/default/images/bbc/hr.gif new file mode 100644 index 0000000..433f8a7 Binary files /dev/null and b/Themes/default/images/bbc/hr.gif differ diff --git a/Themes/default/images/bbc/img.gif b/Themes/default/images/bbc/img.gif new file mode 100644 index 0000000..1bb838d Binary files /dev/null and b/Themes/default/images/bbc/img.gif differ diff --git a/Themes/default/images/bbc/index.php b/Themes/default/images/bbc/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/images/bbc/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/images/bbc/italicize.gif b/Themes/default/images/bbc/italicize.gif new file mode 100644 index 0000000..cf84788 Binary files /dev/null and b/Themes/default/images/bbc/italicize.gif differ diff --git a/Themes/default/images/bbc/left.gif b/Themes/default/images/bbc/left.gif new file mode 100644 index 0000000..87bd529 Binary files /dev/null and b/Themes/default/images/bbc/left.gif differ diff --git a/Themes/default/images/bbc/list.gif b/Themes/default/images/bbc/list.gif new file mode 100644 index 0000000..95494c0 Binary files /dev/null and b/Themes/default/images/bbc/list.gif differ diff --git a/Themes/default/images/bbc/move.gif b/Themes/default/images/bbc/move.gif new file mode 100644 index 0000000..a0d65ec Binary files /dev/null and b/Themes/default/images/bbc/move.gif differ diff --git a/Themes/default/images/bbc/orderlist.gif b/Themes/default/images/bbc/orderlist.gif new file mode 100644 index 0000000..85f4a9c Binary files /dev/null and b/Themes/default/images/bbc/orderlist.gif differ diff --git a/Themes/default/images/bbc/pre.gif b/Themes/default/images/bbc/pre.gif new file mode 100644 index 0000000..e42bfd6 Binary files /dev/null and b/Themes/default/images/bbc/pre.gif differ diff --git a/Themes/default/images/bbc/quote.gif b/Themes/default/images/bbc/quote.gif new file mode 100644 index 0000000..8e97d78 Binary files /dev/null and b/Themes/default/images/bbc/quote.gif differ diff --git a/Themes/default/images/bbc/resize-handle.gif b/Themes/default/images/bbc/resize-handle.gif new file mode 100644 index 0000000..3fa6e0f Binary files /dev/null and b/Themes/default/images/bbc/resize-handle.gif differ diff --git a/Themes/default/images/bbc/right.gif b/Themes/default/images/bbc/right.gif new file mode 100644 index 0000000..54d74aa Binary files /dev/null and b/Themes/default/images/bbc/right.gif differ diff --git a/Themes/default/images/bbc/shadow.gif b/Themes/default/images/bbc/shadow.gif new file mode 100644 index 0000000..4b31037 Binary files /dev/null and b/Themes/default/images/bbc/shadow.gif differ diff --git a/Themes/default/images/bbc/strike.gif b/Themes/default/images/bbc/strike.gif new file mode 100644 index 0000000..b6570bc Binary files /dev/null and b/Themes/default/images/bbc/strike.gif differ diff --git a/Themes/default/images/bbc/sub.gif b/Themes/default/images/bbc/sub.gif new file mode 100644 index 0000000..03fb567 Binary files /dev/null and b/Themes/default/images/bbc/sub.gif differ diff --git a/Themes/default/images/bbc/sup.gif b/Themes/default/images/bbc/sup.gif new file mode 100644 index 0000000..fa936d0 Binary files /dev/null and b/Themes/default/images/bbc/sup.gif differ diff --git a/Themes/default/images/bbc/table.gif b/Themes/default/images/bbc/table.gif new file mode 100644 index 0000000..dcef680 Binary files /dev/null and b/Themes/default/images/bbc/table.gif differ diff --git a/Themes/default/images/bbc/tele.gif b/Themes/default/images/bbc/tele.gif new file mode 100644 index 0000000..bc9c078 Binary files /dev/null and b/Themes/default/images/bbc/tele.gif differ diff --git a/Themes/default/images/bbc/toggle.gif b/Themes/default/images/bbc/toggle.gif new file mode 100644 index 0000000..3b71a8b Binary files /dev/null and b/Themes/default/images/bbc/toggle.gif differ diff --git a/Themes/default/images/bbc/underline.gif b/Themes/default/images/bbc/underline.gif new file mode 100644 index 0000000..398e662 Binary files /dev/null and b/Themes/default/images/bbc/underline.gif differ diff --git a/Themes/default/images/bbc/unformat.gif b/Themes/default/images/bbc/unformat.gif new file mode 100644 index 0000000..c7ba2f4 Binary files /dev/null and b/Themes/default/images/bbc/unformat.gif differ diff --git a/Themes/default/images/bbc/url.gif b/Themes/default/images/bbc/url.gif new file mode 100644 index 0000000..e911abb Binary files /dev/null and b/Themes/default/images/bbc/url.gif differ diff --git a/Themes/default/images/blank.gif b/Themes/default/images/blank.gif new file mode 100644 index 0000000..5bfd67a Binary files /dev/null and b/Themes/default/images/blank.gif differ diff --git a/Themes/default/images/board.gif b/Themes/default/images/board.gif new file mode 100644 index 0000000..b3bc440 Binary files /dev/null and b/Themes/default/images/board.gif differ diff --git a/Themes/default/images/board_select_spot.gif b/Themes/default/images/board_select_spot.gif new file mode 100644 index 0000000..86b9d06 Binary files /dev/null and b/Themes/default/images/board_select_spot.gif differ diff --git a/Themes/default/images/board_select_spot_child.gif b/Themes/default/images/board_select_spot_child.gif new file mode 100644 index 0000000..b4e9e20 Binary files /dev/null and b/Themes/default/images/board_select_spot_child.gif differ diff --git a/Themes/default/images/buddy_useroff.gif b/Themes/default/images/buddy_useroff.gif new file mode 100644 index 0000000..7d4a14f Binary files /dev/null and b/Themes/default/images/buddy_useroff.gif differ diff --git a/Themes/default/images/buddy_useron.gif b/Themes/default/images/buddy_useron.gif new file mode 100644 index 0000000..dae11b9 Binary files /dev/null and b/Themes/default/images/buddy_useron.gif differ diff --git a/Themes/default/images/buttons/approve.gif b/Themes/default/images/buttons/approve.gif new file mode 100644 index 0000000..bb309ab Binary files /dev/null and b/Themes/default/images/buttons/approve.gif differ diff --git a/Themes/default/images/buttons/calendarpe.gif b/Themes/default/images/buttons/calendarpe.gif new file mode 100644 index 0000000..02e6b54 Binary files /dev/null and b/Themes/default/images/buttons/calendarpe.gif differ diff --git a/Themes/default/images/buttons/close.gif b/Themes/default/images/buttons/close.gif new file mode 100644 index 0000000..f0c02a2 Binary files /dev/null and b/Themes/default/images/buttons/close.gif differ diff --git a/Themes/default/images/buttons/delete.gif b/Themes/default/images/buttons/delete.gif new file mode 100644 index 0000000..93ee060 Binary files /dev/null and b/Themes/default/images/buttons/delete.gif differ diff --git a/Themes/default/images/buttons/details.gif b/Themes/default/images/buttons/details.gif new file mode 100644 index 0000000..305c311 Binary files /dev/null and b/Themes/default/images/buttons/details.gif differ diff --git a/Themes/default/images/buttons/ignore.gif b/Themes/default/images/buttons/ignore.gif new file mode 100644 index 0000000..b2fa1fc Binary files /dev/null and b/Themes/default/images/buttons/ignore.gif differ diff --git a/Themes/default/images/buttons/im_reply.gif b/Themes/default/images/buttons/im_reply.gif new file mode 100644 index 0000000..88c5767 Binary files /dev/null and b/Themes/default/images/buttons/im_reply.gif differ diff --git a/Themes/default/images/buttons/im_reply_all.gif b/Themes/default/images/buttons/im_reply_all.gif new file mode 100644 index 0000000..8c612d8 Binary files /dev/null and b/Themes/default/images/buttons/im_reply_all.gif differ diff --git a/Themes/default/images/buttons/index.php b/Themes/default/images/buttons/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/images/buttons/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/images/buttons/merge.gif b/Themes/default/images/buttons/merge.gif new file mode 100644 index 0000000..5e5d95c Binary files /dev/null and b/Themes/default/images/buttons/merge.gif differ diff --git a/Themes/default/images/buttons/modify.gif b/Themes/default/images/buttons/modify.gif new file mode 100644 index 0000000..61698f0 Binary files /dev/null and b/Themes/default/images/buttons/modify.gif differ diff --git a/Themes/default/images/buttons/notify_sm.gif b/Themes/default/images/buttons/notify_sm.gif new file mode 100644 index 0000000..5020ab3 Binary files /dev/null and b/Themes/default/images/buttons/notify_sm.gif differ diff --git a/Themes/default/images/buttons/quote.gif b/Themes/default/images/buttons/quote.gif new file mode 100644 index 0000000..305c311 Binary files /dev/null and b/Themes/default/images/buttons/quote.gif differ diff --git a/Themes/default/images/buttons/reply.gif b/Themes/default/images/buttons/reply.gif new file mode 100644 index 0000000..8c612d8 Binary files /dev/null and b/Themes/default/images/buttons/reply.gif differ diff --git a/Themes/default/images/buttons/reply_sm.gif b/Themes/default/images/buttons/reply_sm.gif new file mode 100644 index 0000000..3e4e382 Binary files /dev/null and b/Themes/default/images/buttons/reply_sm.gif differ diff --git a/Themes/default/images/buttons/restore_topic.gif b/Themes/default/images/buttons/restore_topic.gif new file mode 100644 index 0000000..91e5c67 Binary files /dev/null and b/Themes/default/images/buttons/restore_topic.gif differ diff --git a/Themes/default/images/buttons/search.gif b/Themes/default/images/buttons/search.gif new file mode 100644 index 0000000..2fa0346 Binary files /dev/null and b/Themes/default/images/buttons/search.gif differ diff --git a/Themes/default/images/buttons/split.gif b/Themes/default/images/buttons/split.gif new file mode 100644 index 0000000..0bbce35 Binary files /dev/null and b/Themes/default/images/buttons/split.gif differ diff --git a/Themes/default/images/cake.png b/Themes/default/images/cake.png new file mode 100644 index 0000000..b596899 Binary files /dev/null and b/Themes/default/images/cake.png differ diff --git a/Themes/default/images/collapse.gif b/Themes/default/images/collapse.gif new file mode 100644 index 0000000..a3d5384 Binary files /dev/null and b/Themes/default/images/collapse.gif differ diff --git a/Themes/default/images/construction.png b/Themes/default/images/construction.png new file mode 100644 index 0000000..ca4424d Binary files /dev/null and b/Themes/default/images/construction.png differ diff --git a/Themes/default/images/email_sm.gif b/Themes/default/images/email_sm.gif new file mode 100644 index 0000000..466bb22 Binary files /dev/null and b/Themes/default/images/email_sm.gif differ diff --git a/Themes/default/images/english/index.php b/Themes/default/images/english/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/images/english/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/images/english/new.gif b/Themes/default/images/english/new.gif new file mode 100644 index 0000000..9ed9f1c Binary files /dev/null and b/Themes/default/images/english/new.gif differ diff --git a/Themes/default/images/expand.gif b/Themes/default/images/expand.gif new file mode 100644 index 0000000..bacea16 Binary files /dev/null and b/Themes/default/images/expand.gif differ diff --git a/Themes/default/images/filter.gif b/Themes/default/images/filter.gif new file mode 100644 index 0000000..f16261f Binary files /dev/null and b/Themes/default/images/filter.gif differ diff --git a/Themes/default/images/helptopics.gif b/Themes/default/images/helptopics.gif new file mode 100644 index 0000000..33b1787 Binary files /dev/null and b/Themes/default/images/helptopics.gif differ diff --git a/Themes/default/images/icons/assist.gif b/Themes/default/images/icons/assist.gif new file mode 100644 index 0000000..d0f8624 Binary files /dev/null and b/Themes/default/images/icons/assist.gif differ diff --git a/Themes/default/images/icons/calendar.gif b/Themes/default/images/icons/calendar.gif new file mode 100644 index 0000000..296a129 Binary files /dev/null and b/Themes/default/images/icons/calendar.gif differ diff --git a/Themes/default/images/icons/clip.gif b/Themes/default/images/icons/clip.gif new file mode 100644 index 0000000..0803d0e Binary files /dev/null and b/Themes/default/images/icons/clip.gif differ diff --git a/Themes/default/images/icons/config_sm.gif b/Themes/default/images/icons/config_sm.gif new file mode 100644 index 0000000..304639e Binary files /dev/null and b/Themes/default/images/icons/config_sm.gif differ diff --git a/Themes/default/images/icons/delete.gif b/Themes/default/images/icons/delete.gif new file mode 100644 index 0000000..b3e13cb Binary files /dev/null and b/Themes/default/images/icons/delete.gif differ diff --git a/Themes/default/images/icons/field_check.gif b/Themes/default/images/icons/field_check.gif new file mode 100644 index 0000000..9905d2a Binary files /dev/null and b/Themes/default/images/icons/field_check.gif differ diff --git a/Themes/default/images/icons/field_invalid.gif b/Themes/default/images/icons/field_invalid.gif new file mode 100644 index 0000000..64d0a1f Binary files /dev/null and b/Themes/default/images/icons/field_invalid.gif differ diff --git a/Themes/default/images/icons/field_valid.gif b/Themes/default/images/icons/field_valid.gif new file mode 100644 index 0000000..897aeaa Binary files /dev/null and b/Themes/default/images/icons/field_valid.gif differ diff --git a/Themes/default/images/icons/im_newmsg.gif b/Themes/default/images/icons/im_newmsg.gif new file mode 100644 index 0000000..18b6687 Binary files /dev/null and b/Themes/default/images/icons/im_newmsg.gif differ diff --git a/Themes/default/images/icons/index.php b/Themes/default/images/icons/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/images/icons/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/images/icons/info.gif b/Themes/default/images/icons/info.gif new file mode 100644 index 0000000..191e40a Binary files /dev/null and b/Themes/default/images/icons/info.gif differ diff --git a/Themes/default/images/icons/last_post.gif b/Themes/default/images/icons/last_post.gif new file mode 100644 index 0000000..17c2316 Binary files /dev/null and b/Themes/default/images/icons/last_post.gif differ diff --git a/Themes/default/images/icons/login.gif b/Themes/default/images/icons/login.gif new file mode 100644 index 0000000..1fb4082 Binary files /dev/null and b/Themes/default/images/icons/login.gif differ diff --git a/Themes/default/images/icons/login_sm.gif b/Themes/default/images/icons/login_sm.gif new file mode 100644 index 0000000..5da1e73 Binary files /dev/null and b/Themes/default/images/icons/login_sm.gif differ diff --git a/Themes/default/images/icons/members.gif b/Themes/default/images/icons/members.gif new file mode 100644 index 0000000..dd5b1a5 Binary files /dev/null and b/Themes/default/images/icons/members.gif differ diff --git a/Themes/default/images/icons/modify_inline.gif b/Themes/default/images/icons/modify_inline.gif new file mode 100644 index 0000000..61698f0 Binary files /dev/null and b/Themes/default/images/icons/modify_inline.gif differ diff --git a/Themes/default/images/icons/modify_small.gif b/Themes/default/images/icons/modify_small.gif new file mode 100644 index 0000000..d26e893 Binary files /dev/null and b/Themes/default/images/icons/modify_small.gif differ diff --git a/Themes/default/images/icons/notify_sm.gif b/Themes/default/images/icons/notify_sm.gif new file mode 100644 index 0000000..6d254c9 Binary files /dev/null and b/Themes/default/images/icons/notify_sm.gif differ diff --git a/Themes/default/images/icons/online.gif b/Themes/default/images/icons/online.gif new file mode 100644 index 0000000..7a8e22e Binary files /dev/null and b/Themes/default/images/icons/online.gif differ diff --git a/Themes/default/images/icons/package_installed.gif b/Themes/default/images/icons/package_installed.gif new file mode 100644 index 0000000..e96b5e3 Binary files /dev/null and b/Themes/default/images/icons/package_installed.gif differ diff --git a/Themes/default/images/icons/package_old.gif b/Themes/default/images/icons/package_old.gif new file mode 100644 index 0000000..b7f4f56 Binary files /dev/null and b/Themes/default/images/icons/package_old.gif differ diff --git a/Themes/default/images/icons/pm_read.gif b/Themes/default/images/icons/pm_read.gif new file mode 100644 index 0000000..0dac32b Binary files /dev/null and b/Themes/default/images/icons/pm_read.gif differ diff --git a/Themes/default/images/icons/pm_replied.gif b/Themes/default/images/icons/pm_replied.gif new file mode 100644 index 0000000..6548d99 Binary files /dev/null and b/Themes/default/images/icons/pm_replied.gif differ diff --git a/Themes/default/images/icons/profile_sm.gif b/Themes/default/images/icons/profile_sm.gif new file mode 100644 index 0000000..f442830 Binary files /dev/null and b/Themes/default/images/icons/profile_sm.gif differ diff --git a/Themes/default/images/icons/quick_lock.gif b/Themes/default/images/icons/quick_lock.gif new file mode 100644 index 0000000..2711ac7 Binary files /dev/null and b/Themes/default/images/icons/quick_lock.gif differ diff --git a/Themes/default/images/icons/quick_move.gif b/Themes/default/images/icons/quick_move.gif new file mode 100644 index 0000000..1ec1b10 Binary files /dev/null and b/Themes/default/images/icons/quick_move.gif differ diff --git a/Themes/default/images/icons/quick_remove.gif b/Themes/default/images/icons/quick_remove.gif new file mode 100644 index 0000000..5c2a15f Binary files /dev/null and b/Themes/default/images/icons/quick_remove.gif differ diff --git a/Themes/default/images/icons/quick_sticky.gif b/Themes/default/images/icons/quick_sticky.gif new file mode 100644 index 0000000..25181e2 Binary files /dev/null and b/Themes/default/images/icons/quick_sticky.gif differ diff --git a/Themes/default/images/icons/quick_sticky_lock.gif b/Themes/default/images/icons/quick_sticky_lock.gif new file mode 100644 index 0000000..dae3924 Binary files /dev/null and b/Themes/default/images/icons/quick_sticky_lock.gif differ diff --git a/Themes/default/images/icons/show_sticky.gif b/Themes/default/images/icons/show_sticky.gif new file mode 100644 index 0000000..b7d6ef3 Binary files /dev/null and b/Themes/default/images/icons/show_sticky.gif differ diff --git a/Themes/default/images/im_off.gif b/Themes/default/images/im_off.gif new file mode 100644 index 0000000..771136a Binary files /dev/null and b/Themes/default/images/im_off.gif differ diff --git a/Themes/default/images/im_on.gif b/Themes/default/images/im_on.gif new file mode 100644 index 0000000..7f1f8ac Binary files /dev/null and b/Themes/default/images/im_on.gif differ diff --git a/Themes/default/images/im_sm_newmsg.gif b/Themes/default/images/im_sm_newmsg.gif new file mode 100644 index 0000000..e3de228 Binary files /dev/null and b/Themes/default/images/im_sm_newmsg.gif differ diff --git a/Themes/default/images/im_sm_prefs.gif b/Themes/default/images/im_sm_prefs.gif new file mode 100644 index 0000000..f68e75e Binary files /dev/null and b/Themes/default/images/im_sm_prefs.gif differ diff --git a/Themes/default/images/im_switch.gif b/Themes/default/images/im_switch.gif new file mode 100644 index 0000000..0d4c78d Binary files /dev/null and b/Themes/default/images/im_switch.gif differ diff --git a/Themes/default/images/index.php b/Themes/default/images/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/images/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/images/ip.gif b/Themes/default/images/ip.gif new file mode 100644 index 0000000..e3ac911 Binary files /dev/null and b/Themes/default/images/ip.gif differ diff --git a/Themes/default/images/loading.gif b/Themes/default/images/loading.gif new file mode 100644 index 0000000..53edda2 Binary files /dev/null and b/Themes/default/images/loading.gif differ diff --git a/Themes/default/images/message_sm.gif b/Themes/default/images/message_sm.gif new file mode 100644 index 0000000..0e5f36a Binary files /dev/null and b/Themes/default/images/message_sm.gif differ diff --git a/Themes/default/images/msntalk.gif b/Themes/default/images/msntalk.gif new file mode 100644 index 0000000..207f55a Binary files /dev/null and b/Themes/default/images/msntalk.gif differ diff --git a/Themes/default/images/new_none.png b/Themes/default/images/new_none.png new file mode 100644 index 0000000..e9c19b6 Binary files /dev/null and b/Themes/default/images/new_none.png differ diff --git a/Themes/default/images/new_redirect.png b/Themes/default/images/new_redirect.png new file mode 100644 index 0000000..982e771 Binary files /dev/null and b/Themes/default/images/new_redirect.png differ diff --git a/Themes/default/images/new_some.png b/Themes/default/images/new_some.png new file mode 100644 index 0000000..0883999 Binary files /dev/null and b/Themes/default/images/new_some.png differ diff --git a/Themes/default/images/off.png b/Themes/default/images/off.png new file mode 100644 index 0000000..4b76791 Binary files /dev/null and b/Themes/default/images/off.png differ diff --git a/Themes/default/images/on.png b/Themes/default/images/on.png new file mode 100644 index 0000000..2161447 Binary files /dev/null and b/Themes/default/images/on.png differ diff --git a/Themes/default/images/on2.png b/Themes/default/images/on2.png new file mode 100644 index 0000000..18ef3fc Binary files /dev/null and b/Themes/default/images/on2.png differ diff --git a/Themes/default/images/openid.gif b/Themes/default/images/openid.gif new file mode 100644 index 0000000..23a3613 Binary files /dev/null and b/Themes/default/images/openid.gif differ diff --git a/Themes/default/images/pm_recipient_delete.gif b/Themes/default/images/pm_recipient_delete.gif new file mode 100644 index 0000000..02d4fbf Binary files /dev/null and b/Themes/default/images/pm_recipient_delete.gif differ diff --git a/Themes/default/images/post/angry.gif b/Themes/default/images/post/angry.gif new file mode 100644 index 0000000..0981315 Binary files /dev/null and b/Themes/default/images/post/angry.gif differ diff --git a/Themes/default/images/post/cheesy.gif b/Themes/default/images/post/cheesy.gif new file mode 100644 index 0000000..ea7a12f Binary files /dev/null and b/Themes/default/images/post/cheesy.gif differ diff --git a/Themes/default/images/post/clip.gif b/Themes/default/images/post/clip.gif new file mode 100644 index 0000000..e594837 Binary files /dev/null and b/Themes/default/images/post/clip.gif differ diff --git a/Themes/default/images/post/exclamation.gif b/Themes/default/images/post/exclamation.gif new file mode 100644 index 0000000..fe10fa5 Binary files /dev/null and b/Themes/default/images/post/exclamation.gif differ diff --git a/Themes/default/images/post/grin.gif b/Themes/default/images/post/grin.gif new file mode 100644 index 0000000..c6b75e5 Binary files /dev/null and b/Themes/default/images/post/grin.gif differ diff --git a/Themes/default/images/post/index.php b/Themes/default/images/post/index.php new file mode 100644 index 0000000..be9895a --- /dev/null +++ b/Themes/default/images/post/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/images/post/lamp.gif b/Themes/default/images/post/lamp.gif new file mode 100644 index 0000000..d894edd Binary files /dev/null and b/Themes/default/images/post/lamp.gif differ diff --git a/Themes/default/images/post/moved.gif b/Themes/default/images/post/moved.gif new file mode 100644 index 0000000..aad605b Binary files /dev/null and b/Themes/default/images/post/moved.gif differ diff --git a/Themes/default/images/post/question.gif b/Themes/default/images/post/question.gif new file mode 100644 index 0000000..79b326a Binary files /dev/null and b/Themes/default/images/post/question.gif differ diff --git a/Themes/default/images/post/recycled.gif b/Themes/default/images/post/recycled.gif new file mode 100644 index 0000000..91e5c67 Binary files /dev/null and b/Themes/default/images/post/recycled.gif differ diff --git a/Themes/default/images/post/sad.gif b/Themes/default/images/post/sad.gif new file mode 100644 index 0000000..b757809 Binary files /dev/null and b/Themes/default/images/post/sad.gif differ diff --git a/Themes/default/images/post/smiley.gif b/Themes/default/images/post/smiley.gif new file mode 100644 index 0000000..9165734 Binary files /dev/null and b/Themes/default/images/post/smiley.gif differ diff --git a/Themes/default/images/post/thumbdown.gif b/Themes/default/images/post/thumbdown.gif new file mode 100644 index 0000000..ab7e040 Binary files /dev/null and b/Themes/default/images/post/thumbdown.gif differ diff --git a/Themes/default/images/post/thumbup.gif b/Themes/default/images/post/thumbup.gif new file mode 100644 index 0000000..62e0d02 Binary files /dev/null and b/Themes/default/images/post/thumbup.gif differ diff --git a/Themes/default/images/post/wink.gif b/Themes/default/images/post/wink.gif new file mode 100644 index 0000000..251b079 Binary files /dev/null and b/Themes/default/images/post/wink.gif differ diff --git a/Themes/default/images/post/wireless.gif b/Themes/default/images/post/wireless.gif new file mode 100644 index 0000000..9b66c37 Binary files /dev/null and b/Themes/default/images/post/wireless.gif differ diff --git a/Themes/default/images/post/xx.gif b/Themes/default/images/post/xx.gif new file mode 100644 index 0000000..ca68750 Binary files /dev/null and b/Themes/default/images/post/xx.gif differ diff --git a/Themes/default/images/redirect.png b/Themes/default/images/redirect.png new file mode 100644 index 0000000..e197578 Binary files /dev/null and b/Themes/default/images/redirect.png differ diff --git a/Themes/default/images/selected.gif b/Themes/default/images/selected.gif new file mode 100644 index 0000000..4d2d54e Binary files /dev/null and b/Themes/default/images/selected.gif differ diff --git a/Themes/default/images/smflogo.png b/Themes/default/images/smflogo.png new file mode 100644 index 0000000..01d6fe7 Binary files /dev/null and b/Themes/default/images/smflogo.png differ diff --git a/Themes/default/images/smiley_select_spot.gif b/Themes/default/images/smiley_select_spot.gif new file mode 100644 index 0000000..96d6168 Binary files /dev/null and b/Themes/default/images/smiley_select_spot.gif differ diff --git a/Themes/default/images/sort_down.gif b/Themes/default/images/sort_down.gif new file mode 100644 index 0000000..6c755db Binary files /dev/null and b/Themes/default/images/sort_down.gif differ diff --git a/Themes/default/images/sort_up.gif b/Themes/default/images/sort_up.gif new file mode 100644 index 0000000..7e5731a Binary files /dev/null and b/Themes/default/images/sort_up.gif differ diff --git a/Themes/default/images/split_deselect.gif b/Themes/default/images/split_deselect.gif new file mode 100644 index 0000000..852316b Binary files /dev/null and b/Themes/default/images/split_deselect.gif differ diff --git a/Themes/default/images/split_select.gif b/Themes/default/images/split_select.gif new file mode 100644 index 0000000..80eddbc Binary files /dev/null and b/Themes/default/images/split_select.gif differ diff --git a/Themes/default/images/star.gif b/Themes/default/images/star.gif new file mode 100644 index 0000000..d0be4ad Binary files /dev/null and b/Themes/default/images/star.gif differ diff --git a/Themes/default/images/staradmin.gif b/Themes/default/images/staradmin.gif new file mode 100644 index 0000000..ac32d90 Binary files /dev/null and b/Themes/default/images/staradmin.gif differ diff --git a/Themes/default/images/stargmod.gif b/Themes/default/images/stargmod.gif new file mode 100644 index 0000000..7c5de8b Binary files /dev/null and b/Themes/default/images/stargmod.gif differ diff --git a/Themes/default/images/starmod.gif b/Themes/default/images/starmod.gif new file mode 100644 index 0000000..e979fb0 Binary files /dev/null and b/Themes/default/images/starmod.gif differ diff --git a/Themes/default/images/stats_board.gif b/Themes/default/images/stats_board.gif new file mode 100644 index 0000000..1fb4082 Binary files /dev/null and b/Themes/default/images/stats_board.gif differ diff --git a/Themes/default/images/stats_boards.gif b/Themes/default/images/stats_boards.gif new file mode 100644 index 0000000..1fb4082 Binary files /dev/null and b/Themes/default/images/stats_boards.gif differ diff --git a/Themes/default/images/stats_history.gif b/Themes/default/images/stats_history.gif new file mode 100644 index 0000000..019d2e4 Binary files /dev/null and b/Themes/default/images/stats_history.gif differ diff --git a/Themes/default/images/stats_info.gif b/Themes/default/images/stats_info.gif new file mode 100644 index 0000000..1fb4082 Binary files /dev/null and b/Themes/default/images/stats_info.gif differ diff --git a/Themes/default/images/stats_pie.png b/Themes/default/images/stats_pie.png new file mode 100644 index 0000000..1f692c9 Binary files /dev/null and b/Themes/default/images/stats_pie.png differ diff --git a/Themes/default/images/stats_pie_rtl.png b/Themes/default/images/stats_pie_rtl.png new file mode 100644 index 0000000..91a6218 Binary files /dev/null and b/Themes/default/images/stats_pie_rtl.png differ diff --git a/Themes/default/images/stats_posters.gif b/Themes/default/images/stats_posters.gif new file mode 100644 index 0000000..27a28a5 Binary files /dev/null and b/Themes/default/images/stats_posters.gif differ diff --git a/Themes/default/images/stats_replies.gif b/Themes/default/images/stats_replies.gif new file mode 100644 index 0000000..e579e15 Binary files /dev/null and b/Themes/default/images/stats_replies.gif differ diff --git a/Themes/default/images/stats_views.gif b/Themes/default/images/stats_views.gif new file mode 100644 index 0000000..1fb4082 Binary files /dev/null and b/Themes/default/images/stats_views.gif differ diff --git a/Themes/default/images/theme/backdrop.png b/Themes/default/images/theme/backdrop.png new file mode 100644 index 0000000..4609bb3 Binary files /dev/null and b/Themes/default/images/theme/backdrop.png differ diff --git a/Themes/default/images/theme/frame_repeat.png b/Themes/default/images/theme/frame_repeat.png new file mode 100644 index 0000000..143eb6e Binary files /dev/null and b/Themes/default/images/theme/frame_repeat.png differ diff --git a/Themes/default/images/theme/loadingbar.png b/Themes/default/images/theme/loadingbar.png new file mode 100644 index 0000000..999339d Binary files /dev/null and b/Themes/default/images/theme/loadingbar.png differ diff --git a/Themes/default/images/theme/main_block.png b/Themes/default/images/theme/main_block.png new file mode 100644 index 0000000..f422b81 Binary files /dev/null and b/Themes/default/images/theme/main_block.png differ diff --git a/Themes/default/images/theme/menu_gfx.png b/Themes/default/images/theme/menu_gfx.png new file mode 100644 index 0000000..ce96116 Binary files /dev/null and b/Themes/default/images/theme/menu_gfx.png differ diff --git a/Themes/default/images/theme/quickbuttons.png b/Themes/default/images/theme/quickbuttons.png new file mode 100644 index 0000000..d49de87 Binary files /dev/null and b/Themes/default/images/theme/quickbuttons.png differ diff --git a/Themes/default/images/theme/quote.png b/Themes/default/images/theme/quote.png new file mode 100644 index 0000000..59e9c75 Binary files /dev/null and b/Themes/default/images/theme/quote.png differ diff --git a/Themes/default/images/theme/submit_bg.png b/Themes/default/images/theme/submit_bg.png new file mode 100644 index 0000000..67cc37c Binary files /dev/null and b/Themes/default/images/theme/submit_bg.png differ diff --git a/Themes/default/images/thumbnail.gif b/Themes/default/images/thumbnail.gif new file mode 100644 index 0000000..eb3dcd7 Binary files /dev/null and b/Themes/default/images/thumbnail.gif differ diff --git a/Themes/default/images/topic/hot_poll.gif b/Themes/default/images/topic/hot_poll.gif new file mode 100644 index 0000000..fae050d Binary files /dev/null and b/Themes/default/images/topic/hot_poll.gif differ diff --git a/Themes/default/images/topic/hot_poll_locked.gif b/Themes/default/images/topic/hot_poll_locked.gif new file mode 100644 index 0000000..9e3cb0c Binary files /dev/null and b/Themes/default/images/topic/hot_poll_locked.gif differ diff --git a/Themes/default/images/topic/hot_poll_locked_sticky.gif b/Themes/default/images/topic/hot_poll_locked_sticky.gif new file mode 100644 index 0000000..7195d53 Binary files /dev/null and b/Themes/default/images/topic/hot_poll_locked_sticky.gif differ diff --git a/Themes/default/images/topic/hot_poll_sticky.gif b/Themes/default/images/topic/hot_poll_sticky.gif new file mode 100644 index 0000000..a8eb168 Binary files /dev/null and b/Themes/default/images/topic/hot_poll_sticky.gif differ diff --git a/Themes/default/images/topic/hot_post.gif b/Themes/default/images/topic/hot_post.gif new file mode 100644 index 0000000..a1c66ad Binary files /dev/null and b/Themes/default/images/topic/hot_post.gif differ diff --git a/Themes/default/images/topic/hot_post_locked.gif b/Themes/default/images/topic/hot_post_locked.gif new file mode 100644 index 0000000..26a2a1a Binary files /dev/null and b/Themes/default/images/topic/hot_post_locked.gif differ diff --git a/Themes/default/images/topic/hot_post_locked_sticky.gif b/Themes/default/images/topic/hot_post_locked_sticky.gif new file mode 100644 index 0000000..c548918 Binary files /dev/null and b/Themes/default/images/topic/hot_post_locked_sticky.gif differ diff --git a/Themes/default/images/topic/hot_post_sticky.gif b/Themes/default/images/topic/hot_post_sticky.gif new file mode 100644 index 0000000..13a0750 Binary files /dev/null and b/Themes/default/images/topic/hot_post_sticky.gif differ diff --git a/Themes/default/images/topic/index.php b/Themes/default/images/topic/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/images/topic/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/images/topic/my_hot_poll.gif b/Themes/default/images/topic/my_hot_poll.gif new file mode 100644 index 0000000..3a131ae Binary files /dev/null and b/Themes/default/images/topic/my_hot_poll.gif differ diff --git a/Themes/default/images/topic/my_hot_poll_locked.gif b/Themes/default/images/topic/my_hot_poll_locked.gif new file mode 100644 index 0000000..51dbb55 Binary files /dev/null and b/Themes/default/images/topic/my_hot_poll_locked.gif differ diff --git a/Themes/default/images/topic/my_hot_poll_locked_sticky.gif b/Themes/default/images/topic/my_hot_poll_locked_sticky.gif new file mode 100644 index 0000000..c74ab42 Binary files /dev/null and b/Themes/default/images/topic/my_hot_poll_locked_sticky.gif differ diff --git a/Themes/default/images/topic/my_hot_poll_sticky.gif b/Themes/default/images/topic/my_hot_poll_sticky.gif new file mode 100644 index 0000000..98685e5 Binary files /dev/null and b/Themes/default/images/topic/my_hot_poll_sticky.gif differ diff --git a/Themes/default/images/topic/my_hot_post.gif b/Themes/default/images/topic/my_hot_post.gif new file mode 100644 index 0000000..9c7d295 Binary files /dev/null and b/Themes/default/images/topic/my_hot_post.gif differ diff --git a/Themes/default/images/topic/my_hot_post_locked.gif b/Themes/default/images/topic/my_hot_post_locked.gif new file mode 100644 index 0000000..1c458eb Binary files /dev/null and b/Themes/default/images/topic/my_hot_post_locked.gif differ diff --git a/Themes/default/images/topic/my_hot_post_locked_sticky.gif b/Themes/default/images/topic/my_hot_post_locked_sticky.gif new file mode 100644 index 0000000..9d271d2 Binary files /dev/null and b/Themes/default/images/topic/my_hot_post_locked_sticky.gif differ diff --git a/Themes/default/images/topic/my_hot_post_sticky.gif b/Themes/default/images/topic/my_hot_post_sticky.gif new file mode 100644 index 0000000..0cc107c Binary files /dev/null and b/Themes/default/images/topic/my_hot_post_sticky.gif differ diff --git a/Themes/default/images/topic/my_normal_poll.gif b/Themes/default/images/topic/my_normal_poll.gif new file mode 100644 index 0000000..5959126 Binary files /dev/null and b/Themes/default/images/topic/my_normal_poll.gif differ diff --git a/Themes/default/images/topic/my_normal_poll_locked.gif b/Themes/default/images/topic/my_normal_poll_locked.gif new file mode 100644 index 0000000..0848a56 Binary files /dev/null and b/Themes/default/images/topic/my_normal_poll_locked.gif differ diff --git a/Themes/default/images/topic/my_normal_poll_locked_sticky.gif b/Themes/default/images/topic/my_normal_poll_locked_sticky.gif new file mode 100644 index 0000000..c877dcb Binary files /dev/null and b/Themes/default/images/topic/my_normal_poll_locked_sticky.gif differ diff --git a/Themes/default/images/topic/my_normal_poll_sticky.gif b/Themes/default/images/topic/my_normal_poll_sticky.gif new file mode 100644 index 0000000..aef574d Binary files /dev/null and b/Themes/default/images/topic/my_normal_poll_sticky.gif differ diff --git a/Themes/default/images/topic/my_normal_post.gif b/Themes/default/images/topic/my_normal_post.gif new file mode 100644 index 0000000..0dcefa3 Binary files /dev/null and b/Themes/default/images/topic/my_normal_post.gif differ diff --git a/Themes/default/images/topic/my_normal_post_locked.gif b/Themes/default/images/topic/my_normal_post_locked.gif new file mode 100644 index 0000000..3a5e182 Binary files /dev/null and b/Themes/default/images/topic/my_normal_post_locked.gif differ diff --git a/Themes/default/images/topic/my_normal_post_locked_sticky.gif b/Themes/default/images/topic/my_normal_post_locked_sticky.gif new file mode 100644 index 0000000..ff1743f Binary files /dev/null and b/Themes/default/images/topic/my_normal_post_locked_sticky.gif differ diff --git a/Themes/default/images/topic/my_normal_post_sticky.gif b/Themes/default/images/topic/my_normal_post_sticky.gif new file mode 100644 index 0000000..b7830a6 Binary files /dev/null and b/Themes/default/images/topic/my_normal_post_sticky.gif differ diff --git a/Themes/default/images/topic/my_veryhot_poll.gif b/Themes/default/images/topic/my_veryhot_poll.gif new file mode 100644 index 0000000..dbcbb3f Binary files /dev/null and b/Themes/default/images/topic/my_veryhot_poll.gif differ diff --git a/Themes/default/images/topic/my_veryhot_poll_locked.gif b/Themes/default/images/topic/my_veryhot_poll_locked.gif new file mode 100644 index 0000000..e66e940 Binary files /dev/null and b/Themes/default/images/topic/my_veryhot_poll_locked.gif differ diff --git a/Themes/default/images/topic/my_veryhot_poll_locked_sticky.gif b/Themes/default/images/topic/my_veryhot_poll_locked_sticky.gif new file mode 100644 index 0000000..f039468 Binary files /dev/null and b/Themes/default/images/topic/my_veryhot_poll_locked_sticky.gif differ diff --git a/Themes/default/images/topic/my_veryhot_poll_sticky.gif b/Themes/default/images/topic/my_veryhot_poll_sticky.gif new file mode 100644 index 0000000..79fae52 Binary files /dev/null and b/Themes/default/images/topic/my_veryhot_poll_sticky.gif differ diff --git a/Themes/default/images/topic/my_veryhot_post.gif b/Themes/default/images/topic/my_veryhot_post.gif new file mode 100644 index 0000000..995e903 Binary files /dev/null and b/Themes/default/images/topic/my_veryhot_post.gif differ diff --git a/Themes/default/images/topic/my_veryhot_post_locked.gif b/Themes/default/images/topic/my_veryhot_post_locked.gif new file mode 100644 index 0000000..3504f45 Binary files /dev/null and b/Themes/default/images/topic/my_veryhot_post_locked.gif differ diff --git a/Themes/default/images/topic/my_veryhot_post_locked_sticky.gif b/Themes/default/images/topic/my_veryhot_post_locked_sticky.gif new file mode 100644 index 0000000..52b8aaf Binary files /dev/null and b/Themes/default/images/topic/my_veryhot_post_locked_sticky.gif differ diff --git a/Themes/default/images/topic/my_veryhot_post_sticky.gif b/Themes/default/images/topic/my_veryhot_post_sticky.gif new file mode 100644 index 0000000..3f73a80 Binary files /dev/null and b/Themes/default/images/topic/my_veryhot_post_sticky.gif differ diff --git a/Themes/default/images/topic/normal_poll.gif b/Themes/default/images/topic/normal_poll.gif new file mode 100644 index 0000000..ff8e364 Binary files /dev/null and b/Themes/default/images/topic/normal_poll.gif differ diff --git a/Themes/default/images/topic/normal_poll_locked.gif b/Themes/default/images/topic/normal_poll_locked.gif new file mode 100644 index 0000000..812e1c3 Binary files /dev/null and b/Themes/default/images/topic/normal_poll_locked.gif differ diff --git a/Themes/default/images/topic/normal_poll_locked_sticky.gif b/Themes/default/images/topic/normal_poll_locked_sticky.gif new file mode 100644 index 0000000..284f3a7 Binary files /dev/null and b/Themes/default/images/topic/normal_poll_locked_sticky.gif differ diff --git a/Themes/default/images/topic/normal_poll_sticky.gif b/Themes/default/images/topic/normal_poll_sticky.gif new file mode 100644 index 0000000..69c13b3 Binary files /dev/null and b/Themes/default/images/topic/normal_poll_sticky.gif differ diff --git a/Themes/default/images/topic/normal_post.gif b/Themes/default/images/topic/normal_post.gif new file mode 100644 index 0000000..bcb144e Binary files /dev/null and b/Themes/default/images/topic/normal_post.gif differ diff --git a/Themes/default/images/topic/normal_post_locked.gif b/Themes/default/images/topic/normal_post_locked.gif new file mode 100644 index 0000000..46938d6 Binary files /dev/null and b/Themes/default/images/topic/normal_post_locked.gif differ diff --git a/Themes/default/images/topic/normal_post_locked_sticky.gif b/Themes/default/images/topic/normal_post_locked_sticky.gif new file mode 100644 index 0000000..e3e5089 Binary files /dev/null and b/Themes/default/images/topic/normal_post_locked_sticky.gif differ diff --git a/Themes/default/images/topic/normal_post_sticky.gif b/Themes/default/images/topic/normal_post_sticky.gif new file mode 100644 index 0000000..089644f Binary files /dev/null and b/Themes/default/images/topic/normal_post_sticky.gif differ diff --git a/Themes/default/images/topic/veryhot_poll.gif b/Themes/default/images/topic/veryhot_poll.gif new file mode 100644 index 0000000..8425653 Binary files /dev/null and b/Themes/default/images/topic/veryhot_poll.gif differ diff --git a/Themes/default/images/topic/veryhot_poll_locked.gif b/Themes/default/images/topic/veryhot_poll_locked.gif new file mode 100644 index 0000000..ed845b9 Binary files /dev/null and b/Themes/default/images/topic/veryhot_poll_locked.gif differ diff --git a/Themes/default/images/topic/veryhot_poll_locked_sticky.gif b/Themes/default/images/topic/veryhot_poll_locked_sticky.gif new file mode 100644 index 0000000..79a348b Binary files /dev/null and b/Themes/default/images/topic/veryhot_poll_locked_sticky.gif differ diff --git a/Themes/default/images/topic/veryhot_poll_sticky.gif b/Themes/default/images/topic/veryhot_poll_sticky.gif new file mode 100644 index 0000000..59e8daa Binary files /dev/null and b/Themes/default/images/topic/veryhot_poll_sticky.gif differ diff --git a/Themes/default/images/topic/veryhot_post.gif b/Themes/default/images/topic/veryhot_post.gif new file mode 100644 index 0000000..3af2b5d Binary files /dev/null and b/Themes/default/images/topic/veryhot_post.gif differ diff --git a/Themes/default/images/topic/veryhot_post_locked.gif b/Themes/default/images/topic/veryhot_post_locked.gif new file mode 100644 index 0000000..d5b265a Binary files /dev/null and b/Themes/default/images/topic/veryhot_post_locked.gif differ diff --git a/Themes/default/images/topic/veryhot_post_locked_sticky.gif b/Themes/default/images/topic/veryhot_post_locked_sticky.gif new file mode 100644 index 0000000..a2b6458 Binary files /dev/null and b/Themes/default/images/topic/veryhot_post_locked_sticky.gif differ diff --git a/Themes/default/images/topic/veryhot_post_sticky.gif b/Themes/default/images/topic/veryhot_post_sticky.gif new file mode 100644 index 0000000..dfdabe9 Binary files /dev/null and b/Themes/default/images/topic/veryhot_post_sticky.gif differ diff --git a/Themes/default/images/upshrink.png b/Themes/default/images/upshrink.png new file mode 100644 index 0000000..d42eae6 Binary files /dev/null and b/Themes/default/images/upshrink.png differ diff --git a/Themes/default/images/upshrink2.png b/Themes/default/images/upshrink2.png new file mode 100644 index 0000000..f57aa5e Binary files /dev/null and b/Themes/default/images/upshrink2.png differ diff --git a/Themes/default/images/useroff.gif b/Themes/default/images/useroff.gif new file mode 100644 index 0000000..6fc680d Binary files /dev/null and b/Themes/default/images/useroff.gif differ diff --git a/Themes/default/images/useron.gif b/Themes/default/images/useron.gif new file mode 100644 index 0000000..8391baf Binary files /dev/null and b/Themes/default/images/useron.gif differ diff --git a/Themes/default/images/warn.gif b/Themes/default/images/warn.gif new file mode 100644 index 0000000..90cdb7a Binary files /dev/null and b/Themes/default/images/warn.gif differ diff --git a/Themes/default/images/warning_moderate.gif b/Themes/default/images/warning_moderate.gif new file mode 100644 index 0000000..90cdb7a Binary files /dev/null and b/Themes/default/images/warning_moderate.gif differ diff --git a/Themes/default/images/warning_mute.gif b/Themes/default/images/warning_mute.gif new file mode 100644 index 0000000..60838b4 Binary files /dev/null and b/Themes/default/images/warning_mute.gif differ diff --git a/Themes/default/images/warning_watch.gif b/Themes/default/images/warning_watch.gif new file mode 100644 index 0000000..cf34eea Binary files /dev/null and b/Themes/default/images/warning_watch.gif differ diff --git a/Themes/default/images/www.gif b/Themes/default/images/www.gif new file mode 100644 index 0000000..a7cfe20 Binary files /dev/null and b/Themes/default/images/www.gif differ diff --git a/Themes/default/images/www_sm.gif b/Themes/default/images/www_sm.gif new file mode 100644 index 0000000..a7cfe20 Binary files /dev/null and b/Themes/default/images/www_sm.gif differ diff --git a/Themes/default/index.php b/Themes/default/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/index.template.php b/Themes/default/index.template.php new file mode 100644 index 0000000..821d814 --- /dev/null +++ b/Themes/default/index.template.php @@ -0,0 +1,505 @@ + + +'; + + // The ?fin20 part of this link is just here to make sure browsers don't cache it wrongly. + echo ' + '; + + // Some browsers need an extra stylesheet due to bugs/compatibility issues. + foreach (array('ie7', 'ie6', 'webkit') as $cssfix) + if ($context['browser']['is_' . $cssfix]) + echo ' + '; + + // RTL languages require an additional stylesheet. + if ($context['right_to_left']) + echo ' + '; + + // Here comes the JavaScript bits! + echo ' + + + '; + + echo ' + + ', !empty($context['meta_keywords']) ? ' + ' : '', ' + ', $context['page_title_html_safe'], ''; + + // Please don't index these Mr Robot. + if (!empty($context['robot_no_index'])) + echo ' + '; + + // Present a canonical url for search engines to prevent duplicate content in their indices. + if (!empty($context['canonical_url'])) + echo ' + '; + + // Show all the relative links, such as help, search, contents, and the like. + echo ' + + + '; + + // If RSS feeds are enabled, advertise the presence of one. + if (!empty($modSettings['xmlnews_enable']) && (!empty($modSettings['allow_guestAccess']) || $context['user']['is_logged'])) + echo ' + '; + + // If we're viewing a topic, these should be the previous and next topics, respectively. + if (!empty($context['current_topic'])) + echo ' + + '; + + // If we're in a board, or a topic for that matter, the index will be the board's index. + if (!empty($context['current_board'])) + echo ' + '; + + // Output any remaining HTML headers. (from mods, maybe?) + echo $context['html_headers']; + + echo ' + +'; +} + +function template_body_above() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo !empty($settings['forum_width']) ? ' +
    ' : '', ' + '; + + // The main content should go here. + echo ' +
    +
    '; + + // Custom banners and shoutboxes should be placed here, before the linktree. + + // Show the navigation tree. + theme_linktree(); +} + +function template_body_below() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +
    +
    '; + + // Show the "Powered by" and "Valid" logos, as well as the copyright. Remember, the copyright must be somewhere! + echo ' + ', !empty($settings['forum_width']) ? ' +
    ' : ''; +} + +function template_html_below() +{ + global $context, $settings, $options, $scripturl, $txt, $modSettings; + + echo ' +'; +} + +// Show a linktree. This is that thing that shows "My Community | General Category | General Discussion".. +function theme_linktree($force_show = false) +{ + global $context, $settings, $options, $shown_linktree; + + // If linktree is empty, just return - also allow an override. + if (empty($context['linktree']) || (!empty($context['dont_default_linktree']) && !$force_show)) + return; + + echo ' + '; + + $shown_linktree = true; +} + +// Show the menu up top. Something like [home] [help] [profile] [logout]... +function template_menu() +{ + global $context, $settings, $options, $scripturl, $txt; + + echo ' + '; +} + +// Generate a strip of buttons. +function template_button_strip($button_strip, $direction = 'top', $strip_options = array()) +{ + global $settings, $context, $txt, $scripturl; + + if (!is_array($strip_options)) + $strip_options = array(); + + // List the buttons in reverse order for RTL languages. + if ($context['right_to_left']) + $button_strip = array_reverse($button_strip, true); + + // Create the buttons... + $buttons = array(); + foreach ($button_strip as $key => $value) + { + if (!isset($value['test']) || !empty($context[$value['test']])) + $buttons[] = ' +
  16. ' . $txt[$value['text']] . '
  17. '; + } + + // No buttons? No button strip either. + if (empty($buttons)) + return; + + // Make the last one, as easy as possible. + $buttons[count($buttons) - 1] = str_replace('', '', $buttons[count($buttons) - 1]); + + echo ' + '; +} + +?> \ No newline at end of file diff --git a/Themes/default/languages/Admin.english.php b/Themes/default/languages/Admin.english.php new file mode 100644 index 0000000..1b1cb14 --- /dev/null +++ b/Themes/default/languages/Admin.english.php @@ -0,0 +1,619 @@ +Blank a box to remove that word.'; +$txt['admin_reserved_names'] = 'Reserved Names'; +$txt['admin_template_edit'] = 'Edit Your Forum Template'; +$txt['admin_modifications'] = 'Modification Settings'; +$txt['admin_security_moderation'] = 'Security and Moderation'; +$txt['admin_server_settings'] = 'Server Settings'; +$txt['admin_reserved_set'] = 'Set Reserved Names'; +$txt['admin_reserved_line'] = 'One reserved word per line.'; +$txt['admin_basic_settings'] = 'This page allows you to change the basic settings for your forum. Be very careful with these settings, as they may render the forum dysfunctional.'; +$txt['admin_maintain'] = 'Enable Maintenance Mode'; +$txt['admin_title'] = 'Forum Title'; +$txt['admin_url'] = 'Forum URL'; +$txt['cookie_name'] = 'Cookie Name'; +$txt['admin_webmaster_email'] = 'Webmaster Email Address'; +$txt['boarddir'] = 'SMF Directory'; +$txt['sourcesdir'] = 'Sources Directory'; +$txt['cachedir'] = 'Cache Directory'; +$txt['admin_news'] = 'Enable News'; +$txt['admin_guest_post'] = 'Enable Guest Posting'; +$txt['admin_manage_members'] = 'Members'; +$txt['admin_main'] = 'Main'; +$txt['admin_config'] = 'Configuration'; +$txt['admin_version_check'] = 'Detailed Version Check'; +$txt['admin_smffile'] = 'SMF File'; +$txt['admin_smfpackage'] = 'SMF Package'; +$txt['admin_maintenance'] = 'Maintenance'; +$txt['admin_image_text'] = 'Show buttons as images instead of text'; +$txt['admin_credits'] = 'Credits'; +$txt['admin_agreement'] = 'Show and require agreement letter when registering'; +$txt['admin_agreement_default'] = 'Default'; +$txt['admin_agreement_select_language'] = 'Language to edit'; +$txt['admin_agreement_select_language_change'] = 'Change'; +$txt['admin_delete_members'] = 'Delete Selected Members'; +$txt['admin_repair'] = 'Repair All Boards and Topics'; +$txt['admin_main_welcome'] = 'This is your "%1$s". From here, you can edit settings, maintain your forum, view logs, install packages, manage themes, and many other things.
    If you have any trouble, please look at the "Support & Credits" page. If the information there doesn\'t help you, feel free to look to us for help with the problem.
    You may also find answers to your questions or problems by clicking the %2$s symbols for more information on the related functions.'; +$txt['admin_news_desc'] = 'Please place one news item per box. BBC tags, such as [b], [i] and [u] are allowed in your news, as well as smileys. Clear a news item\'s text box to remove it.'; +$txt['administrators'] = 'Forum Administrators'; +$txt['admin_reserved_desc'] = 'Reserved names will keep members from registering certain usernames or using these words in their displayed names. Choose the options you wish to use from the bottom before submitting.'; +$txt['admin_activation_email'] = 'Send activation email to new members upon registration'; +$txt['admin_match_whole'] = 'Match whole name only. If unchecked, search within names.'; +$txt['admin_match_case'] = 'Match case. If unchecked, search will be case insensitive.'; +$txt['admin_check_user'] = 'Check username.'; +$txt['admin_check_display'] = 'Check display name.'; +$txt['admin_newsletter_send'] = 'You can email anyone from this page. The email addresses of the selected membergroups should appear below, but you may remove or add any email addresses you wish. Be sure that each address is separated in this fashion: \'address1; address2\'.'; +$txt['admin_fader_delay'] = 'Fading delay between items for the news fader'; +$txt['admin_bbc'] = 'Show BBC Buttons on Posting and PM Send Pages'; + +$txt['admin_backup_fail'] = 'Failed to make backup of Settings.php - make sure Settings_bak.php exists and is writable.'; +$txt['modSettings_info'] = 'Change or set options that control how this forum operates.'; +$txt['database_server'] = 'Database Server'; +$txt['database_user'] = 'Database Username'; +$txt['database_password'] = 'Database Password'; +$txt['database_name'] = 'Database Name'; +$txt['registration_agreement'] = 'Registration Agreement'; +$txt['registration_agreement_desc'] = 'This agreement is shown when a user registers an account on this forum and has to be accepted before users can continue registration.'; +$txt['database_prefix'] = 'Database Tables Prefix'; +$txt['errors_list'] = 'Listing of forum errors'; +$txt['errors_found'] = 'The following errors are fouling up your forum'; +$txt['errors_fix'] = 'Would you like to attempt to fix these errors?'; +$txt['errors_do_recount'] = 'All errors fixed - a salvage area has been created! Please click the button below to recount some key statistics.'; +$txt['errors_recount_now'] = 'Recount Statistics'; +$txt['errors_fixing'] = 'Fixing forum errors'; +$txt['errors_fixed'] = 'All errors fixed! Please check on any categories, boards, or topics created to decide what to do with them.'; +$txt['attachments_avatars'] = 'Attachments and Avatars'; +$txt['attachments_desc'] = 'From here you can administer the attached files on your system. You can delete attachments by size and by date from your system. Statistics on attachments are also displayed below.'; +$txt['attachment_stats'] = 'File Attachment Statistics'; +$txt['attachment_integrity_check'] = 'Attachment Integrity Check'; +$txt['attachment_integrity_check_desc'] = 'This function will check the integrity and sizes of attachments and filenames listed in the database and, if necessary, fix errors it encounters.'; +$txt['attachment_check_now'] = 'Run check now'; +$txt['attachment_pruning'] = 'Attachment Pruning'; +$txt['attachment_pruning_message'] = 'Message to add to post'; +$txt['attachment_pruning_warning'] = 'Are you sure you want to delete these attachments?\\nThis cannot be undone!'; +$txt['attachment_total'] = 'Total Attachments'; +$txt['attachmentdir_size'] = 'Total Size of Attachment Directory'; +$txt['attachmentdir_size_current'] = 'Total Size of Current Attachment Directory'; +$txt['attachment_space'] = 'Total Space Available in Attachment Directory'; +$txt['attachment_space_current'] = 'Total Space Available in Current Attachment Directory'; +$txt['attachment_options'] = 'File Attachment Options'; +$txt['attachment_log'] = 'Attachment Log'; +$txt['attachment_remove_old'] = 'Remove attachments older than'; +$txt['attachment_remove_size'] = 'Remove attachments larger than'; +$txt['attachment_name'] = 'Attachment Name'; +$txt['attachment_file_size'] = 'File Size'; +$txt['attachmentdir_size_not_set'] = 'No maximum directory size is currently set'; +$txt['attachment_delete_admin'] = '[attachment deleted by admin]'; +$txt['live'] = 'Live from Simple Machines...'; +$txt['remove_all'] = 'Remove All'; +$txt['approve_new_members'] = 'Admin must approve all new members'; +$txt['agreement_not_writable'] = 'Warning - agreement.txt is not writable, any changes you make will NOT be saved.'; + +$txt['version_check_desc'] = 'This shows you the versions of your installation\'s files versus those of the latest version. If any of these files are out of date, you should download and upgrade to the latest version at www.simplemachines.org.'; +$txt['version_check_more'] = '(more detailed)'; + +$txt['lfyi'] = 'You are unable to connect to simplemachines.org\'s latest news file.'; + +$txt['manage_calendar'] = 'Calendar'; +$txt['manage_search'] = 'Search'; + +$txt['smileys_manage'] = 'Smileys and Message Icons'; +$txt['smileys_manage_info'] = 'Install new smiley sets, add smileys to existing ones, or manage your message icons.'; +$txt['package_info'] = 'Install new features or modify existing ones with this interface.'; +$txt['theme_admin'] = 'Themes and Layout'; +$txt['theme_admin_info'] = 'Setup and manage your themes and set or reset theme options.'; +$txt['registration_center'] = 'Registration'; +$txt['member_center_info'] = 'View the member list, search for members and manage not-yet-approved members and members who haven\'t activated their account yet.'; + +$txt['viewmembers_name'] = 'Username (display name)'; +$txt['viewmembers_online'] = 'Last Online'; +$txt['viewmembers_today'] = 'Today'; +$txt['viewmembers_day_ago'] = 'day ago'; +$txt['viewmembers_days_ago'] = 'days ago'; + +$txt['display_name'] = 'Display name'; +$txt['email_address'] = 'Email Address'; +$txt['ip_address'] = 'IP address'; +$txt['member_id'] = 'ID'; + +$txt['unknown'] = 'unknown'; +$txt['security_wrong'] = 'Administration login attempt!' . "\n" . 'Referer: %1$s' . "\n" . 'User agent: %2$s' . "\n" . 'IP: %3$s'; + +$txt['email_as_html'] = 'Send in HTML format. (with this you can put normal HTML in the email.)'; +$txt['email_parsed_html'] = 'Add <br />s and &nbsp;s to this message.'; +$txt['email_variables'] = 'In this message you can use a few "variables". Click here for more information.'; +$txt['email_force'] = 'Send this to members even if they have chosen not to receive announcements.'; +$txt['email_as_pms'] = 'Send this to these groups using personal messages.'; +$txt['email_continue'] = 'Continue'; +$txt['email_done'] = 'done.'; + +$txt['ban_title'] = 'Ban List'; +$txt['ban_ip'] = 'IP banning: (e.g. 192.168.12.213 or 128.0.*.*) - one entry per line'; +$txt['ban_email'] = 'Email banning: (e.g. badguy@somewhere.com) - one entry per line'; +$txt['ban_username'] = 'User name banning: (e.g. l33tuser) - one entry per line'; + +$txt['ban_description'] = 'Here you can ban troublesome people either by IP, hostname, username, or email.'; +$txt['ban_add_new'] = 'Add New Ban'; +$txt['ban_banned_entity'] = 'Banned entity'; +$txt['ban_on_ip'] = 'Ban on IP (e.g. 192.168.10-20.*)'; +$txt['ban_on_hostname'] = 'Ban on Hostname (e.g. *.mil)'; +$txt['ban_on_email'] = 'Ban on Email Address (e.g. *@badsite.com)'; +$txt['ban_on_username'] = 'Ban on Username'; +$txt['ban_notes'] = 'Notes'; +$txt['ban_restriction'] = 'Restriction'; +$txt['ban_full_ban'] = 'Full ban'; +$txt['ban_partial_ban'] = 'Partial ban'; +$txt['ban_cannot_post'] = 'Cannot post'; +$txt['ban_cannot_register'] = 'Cannot register'; +$txt['ban_cannot_login'] = 'Cannot login'; +$txt['ban_add'] = 'Add'; +$txt['ban_edit_list'] = 'Ban List'; +$txt['ban_type'] = 'Ban Type'; +$txt['ban_days'] = 'day(s)'; +$txt['ban_will_expire_within'] = 'Ban will expire after'; +$txt['ban_added'] = 'Added'; +$txt['ban_expires'] = 'Expires'; +$txt['ban_hits'] = 'Hits'; +$txt['ban_actions'] = 'Actions'; +$txt['ban_expiration'] = 'Expiration'; +$txt['ban_reason_desc'] = 'Reason for ban, to be displayed to banned member.'; +$txt['ban_notes_desc'] = 'Notes that may assist other staff members.'; +$txt['ban_remove_selected'] = 'Remove selected'; +// Escape any single quotes in here twice.. 'it\'s' -> 'it\\\'s'. +$txt['ban_remove_selected_confirm'] = 'Are you sure you want to remove the selected bans?'; +$txt['ban_modify'] = 'Modify'; +$txt['ban_name'] = 'Ban name'; +// Escape any single quotes in here twice.. 'it\'s' -> 'it\\\'s'. +$txt['ban_edit'] = 'Edit ban'; +$txt['ban_add_notes'] = 'Note: after creating the above ban, you can add additional entries that trigger the ban, like IP addresses, hostnames and email addresses.'; +$txt['ban_expired'] = 'Expired / disabled'; +// Escape any single quotes in here twice.. 'it\'s' -> 'it\\\'s'. +$txt['ban_restriction_empty'] = 'No restriction selected.'; + +$txt['ban_triggers'] = 'Triggers'; +$txt['ban_add_trigger'] = 'Add ban trigger'; +$txt['ban_add_trigger_submit'] = 'Add'; +$txt['ban_edit_trigger'] = 'Modify'; +$txt['ban_edit_trigger_title'] = 'Edit ban trigger'; +$txt['ban_edit_trigger_submit'] = 'Modify'; +$txt['ban_remove_selected_triggers'] = 'Remove selected ban triggers'; +$txt['ban_no_entries'] = 'There are currently no bans in effect.'; + +// Escape any single quotes in here twice.. 'it\'s' -> 'it\\\'s'. +$txt['ban_remove_selected_triggers_confirm'] = 'Are you sure you want to remove the selected ban triggers?'; +$txt['ban_trigger_browse'] = 'Browse Ban Triggers'; +$txt['ban_trigger_browse_description'] = 'This screen shows all banned entities grouped by IP address, hostname, email address and username.'; + +$txt['ban_log'] = 'Ban Log'; +$txt['ban_log_description'] = 'The ban log shows all attempts to enter the forum by banned users (\'full ban\' and \'cannot register\' ban only).'; +$txt['ban_log_no_entries'] = 'There are currently no ban log entries.'; +$txt['ban_log_ip'] = 'IP'; +$txt['ban_log_email'] = 'Email address'; +$txt['ban_log_member'] = 'Member'; +$txt['ban_log_date'] = 'Date'; +$txt['ban_log_remove_all'] = 'Remove all'; +$txt['ban_log_remove_all_confirm'] = 'Are you sure you want to delete all ban log entries?'; +$txt['ban_log_remove_selected'] = 'Remove selected'; +$txt['ban_log_remove_selected_confirm'] = 'Are you sure you want to delete all selected ban log entries?'; +$txt['ban_no_triggers'] = 'There are currently no ban triggers.'; + +$txt['settings_not_writable'] = 'These settings cannot be changed because Settings.php is read only.'; + +$txt['maintain_title'] = 'Forum Maintenance'; +$txt['maintain_info'] = 'Optimize tables, make backups, check for errors, and prune boards with these tools.'; +$txt['maintain_sub_database'] = 'Database'; +$txt['maintain_sub_routine'] = 'Routine'; +$txt['maintain_sub_members'] = 'Members'; +$txt['maintain_sub_topics'] = 'Topics'; +$txt['maintain_done'] = 'The maintenance task \'%1$s\' was executed successfully.'; +$txt['maintain_no_errors'] = 'Congratulations, no errors found! Thanks for checking.'; + +$txt['maintain_tasks'] = 'Scheduled Tasks'; +$txt['maintain_tasks_desc'] = 'Manage all the tasks scheduled by SMF.'; + +$txt['scheduled_log'] = 'Task Log'; +$txt['scheduled_log_desc'] = 'Lists logs of the tasks that have be ran.'; +$txt['admin_log'] = 'Administration Log'; +$txt['admin_log_desc'] = 'Lists administrative tasks that have been performed by admins of your forum.'; +$txt['moderation_log'] = 'Moderation Log'; +$txt['moderation_log_desc'] = 'Lists moderation activities that have been performed by moderators on your forum.'; +$txt['spider_log_desc'] = 'Review the entries related to search engine spider activity on your forum.'; +$txt['pruning_log_desc'] = 'Use these tools to prune older entries in the various logs.'; + +$txt['mailqueue_title'] = 'Mail'; + +$txt['db_error_send'] = 'Send emails on database connection error'; +$txt['db_persist'] = 'Use a persistent connection'; +$txt['ssi_db_user'] = 'Database username to use in SSI mode'; +$txt['ssi_db_passwd'] = 'Database password to use in SSI mode'; + +$txt['default_language'] = 'Default Forum Language'; + +$txt['maintenance_subject'] = 'Subject for display'; +$txt['maintenance_message'] = 'Message for display'; + +$txt['errlog_desc'] = 'The error log tracks every error encountered by your forum. To delete any errors from the database, mark the checkbox, and click the %1$s button at the bottom of the page.'; +$txt['errlog_no_entries'] = 'There are currently no error log entries.'; + +$txt['theme_settings'] = 'Theme Settings'; +$txt['theme_current_settings'] = 'Current Theme'; + +$txt['dvc_your'] = 'Your Version'; +$txt['dvc_current'] = 'Current Version'; +$txt['dvc_sources'] = 'Sources'; +$txt['dvc_default'] = 'Default Templates'; +$txt['dvc_templates'] = 'Current Templates'; +$txt['dvc_languages'] = 'Language Files'; + +$txt['smileys_default_set_for_theme'] = 'Select default smiley set for this theme'; +$txt['smileys_no_default'] = '(use global default smiley set)'; + +$txt['censor_test'] = 'Test Censored Words'; +$txt['censor_test_save'] = 'Test'; +$txt['censor_case'] = 'Ignore case when censoring'; +$txt['censor_whole_words'] = 'Check only whole words'; + +$txt['admin_confirm_password'] = '(confirm)'; +$txt['admin_incorrect_password'] = 'Incorrect Password'; + +$txt['date_format'] = '(YYYY-MM-DD)'; +$txt['undefined_gender'] = 'Undefined'; +$txt['age'] = 'User age'; +$txt['activation_status'] = 'Activation Status'; +$txt['activated'] = 'Activated'; +$txt['not_activated'] = 'Not activated'; +$txt['primary'] = 'Primary'; +$txt['additional'] = 'Additional'; +$txt['messenger_address'] = 'Messenger Address'; +$txt['wild_cards_allowed'] = 'wildcard characters * and ? are allowed'; +$txt['search_for'] = 'Search for'; +$txt['member_part_of_these_membergroups'] = 'Member is part of these membergroups'; +$txt['membergroups'] = 'Membergroups'; +$txt['confirm_delete_members'] = 'Are you sure you want to delete the selected members?'; + +$txt['support_credits_title'] = 'Support and Credits'; +$txt['support_credits_info'] = 'Get support on common issues and version information to give if you have problems.'; +$txt['support_title'] = 'Support Information'; +$txt['support_versions_current'] = 'Current SMF version'; +$txt['support_versions_forum'] = 'Forum version'; +$txt['support_versions_php'] = 'PHP version'; +$txt['support_versions_db'] = '%1$s version'; +$txt['support_versions_server'] = 'Server version'; +$txt['support_versions_gd'] = 'GD version'; +$txt['support_versions'] = 'Version Information'; +$txt['support_resources'] = 'Support Resources'; +$txt['support_resources_p1'] = 'Our Online Manual provides the main documentation for SMF. The SMF Online Manual has many documents to help answer support questions and explain Features, Settings, Themes, Packages, etc. The Online Manual documents each area of SMF thoroughly and should answer most questions quickly.'; +$txt['support_resources_p2'] = 'If you can\'t find the answers to your questions in the Online Manual, you may want to search our Support Community or ask for assistance in either our English or one of our many international support boards. The SMF Support Community can be used for support, customization, and many other things such as discussing SMF, finding a host, and discussing administrative issues with other forum administrators.'; + +$txt['support_latest'] = 'Common Support & Issues'; +$txt['support_latest_fetch'] = 'Retrieving support information...'; + +$txt['edit_permissions_info'] = 'Change restrictions and available features globally or to specific boards.'; +$txt['membergroups_members'] = 'Regular Members'; +$txt['membergroups_guests'] = 'Guests'; +$txt['membergroups_guests_na'] = 'n/a'; +$txt['membergroups_add_group'] = 'Add group'; +$txt['membergroups_permissions'] = 'Permissions'; + +$txt['permitgroups_restrict'] = 'Restrictive'; +$txt['permitgroups_standard'] = 'Standard'; +$txt['permitgroups_moderator'] = 'Moderator'; +$txt['permitgroups_maintenance'] = 'Maintenance'; +$txt['permitgroups_inherit'] = 'Inherit'; + +$txt['confirm_delete_attachments_all'] = 'Are you sure you want to delete all attachments?'; +$txt['confirm_delete_attachments'] = 'Are you sure you want to delete the selected attachments?'; +$txt['attachment_manager_browse_files'] = 'Browse Files'; +$txt['attachment_manager_repair'] = 'Maintain'; +$txt['attachment_manager_avatars'] = 'Avatars'; +$txt['attachment_manager_attachments'] = 'Attachments'; +$txt['attachment_manager_thumbs'] = 'Thumbnails'; +$txt['attachment_manager_last_active'] = 'Last Active'; +$txt['attachment_manager_member'] = 'Member'; +$txt['attachment_manager_avatars_older'] = 'Remove avatars from members not active for more than'; +$txt['attachment_manager_total_avatars'] = 'Total Avatars'; + +$txt['attachment_manager_avatars_no_entries'] = 'There are currently no avatars.'; +$txt['attachment_manager_attachments_no_entries'] = 'There are currently no attachments.'; +$txt['attachment_manager_thumbs_no_entries'] = 'There are currently no thumbnails.'; + +$txt['attachment_manager_settings'] = 'Attachment Settings'; +$txt['attachment_manager_avatar_settings'] = 'Avatar Settings'; +$txt['attachment_manager_browse'] = 'Browse Files'; +$txt['attachment_manager_maintenance'] = 'File Maintenance'; +$txt['attachment_manager_save'] = 'Save'; + +$txt['attachmentEnable'] = 'Attachments mode'; +$txt['attachmentEnable_deactivate'] = 'Disable attachments'; +$txt['attachmentEnable_enable_all'] = 'Enable all attachments'; +$txt['attachmentEnable_disable_new'] = 'Disable new attachments'; +$txt['attachmentCheckExtensions'] = 'Check attachment\'s extension'; +$txt['attachmentExtensions'] = 'Allowed attachment extensions'; +$txt['attachmentRecodeLineEndings'] = 'Recode line endings in textual attachments'; +$txt['attachmentShowImages'] = 'Display image attachments as pictures under post'; +$txt['attachmentEncryptFilenames'] = 'Encrypt stored filenames'; +$txt['attachmentUploadDir'] = 'Attachments directory'; +$txt['attachmentUploadDir_multiple'] = 'Attachments directory'; +$txt['attachmentUploadDir_multiple_configure'] = '[Configure multiple attachment directories]'; +$txt['attachmentDirSizeLimit'] = 'Max attachment folder space
    (0 for no limit)
    '; +$txt['attachmentPostLimit'] = 'Max attachment size per post
    (0 for no limit)
    '; +$txt['attachmentSizeLimit'] = 'Max size per attachment
    (0 for no limit)
    '; +$txt['attachmentNumPerPostLimit'] = 'Max number of attachments per post
    (0 for no limit)
    '; +$txt['attachment_gd_warning'] = 'The GD module is currently not installed. Image re-encoding is not possible.'; +$txt['attachment_image_reencode'] = 'Re-encode potentially dangerous image attachments'; +$txt['attachment_image_reencode_note'] = '(requires GD module)'; +$txt['attachment_image_paranoid_warning'] = 'The extensive security checks can result in a large number of rejected attachments.'; +$txt['attachment_image_paranoid'] = 'Perform extensive security checks on uploaded image attachments'; +$txt['attachmentThumbnails'] = 'Resize images when showing under posts'; +$txt['attachment_thumb_png'] = 'Save thumbnails as PNG'; +$txt['attachmentThumbWidth'] = 'Maximum width of thumbnails'; +$txt['attachmentThumbHeight'] = 'Maximum height of thumbnails'; + +$txt['attach_dir_does_not_exist'] = 'Does Not Exist'; +$txt['attach_dir_not_writable'] = 'Not Writable'; +$txt['attach_dir_files_missing'] = 'Files Missing (Repair)'; +$txt['attach_dir_unused'] = 'Unused'; +$txt['attach_dir_ok'] = 'OK'; + +$txt['attach_path_manage'] = 'Manage Attachment Paths'; +$txt['attach_paths'] = 'Attachment Paths'; +$txt['attach_current_dir'] = 'Current Directory'; +$txt['attach_path'] = 'Path'; +$txt['attach_current_size'] = 'Current Size (KB)'; +$txt['attach_num_files'] = 'Files'; +$txt['attach_dir_status'] = 'Status'; +$txt['attach_add_path'] = 'Add Path'; +$txt['attach_path_current_bad'] = 'Invalid current attachment path.'; + +$txt['mods_cat_avatars'] = 'Avatars'; +$txt['avatar_directory'] = 'Avatars directory'; +$txt['avatar_url'] = 'Avatars URL'; +$txt['avatar_dimension_note'] = '(0 = no limit)'; +$txt['avatar_max_width_external'] = 'Maximum width of external avatar
    (0 for no limit)
    '; +$txt['avatar_max_height_external'] = 'Maximum height of external avatar
    (0 for no limit)
    '; +$txt['avatar_action_too_large'] = 'If the avatar is too large...'; +$txt['option_refuse'] = 'Refuse it'; +$txt['option_html_resize'] = 'Let the HTML resize it'; +$txt['option_js_resize'] = 'Resize it with JavaScript'; +$txt['option_download_and_resize'] = 'Download and resize it (requires GD module)'; +$txt['avatar_max_width_upload'] = 'Maximum width of uploaded avatar
    (0 for no limit)
    '; +$txt['avatar_max_height_upload'] = 'Maximum height of uploaded avatar
    (0 for no limit)
    '; +$txt['avatar_resize_upload'] = 'Resize oversized large avatars'; +$txt['avatar_resize_upload_note'] = '(requires GD module)'; +$txt['avatar_download_png'] = 'Use PNG for resized avatars'; +$txt['avatar_gd_warning'] = 'The GD module is currently not installed. Some avatar features are disabled.'; +$txt['avatar_external'] = 'External avatars'; +$txt['avatar_upload'] = 'Uploadable avatars'; +$txt['avatar_server_stored'] = 'Server-stored avatars'; +$txt['avatar_server_stored_groups'] = 'Membergroups allowed to select a server stored avatar'; +$txt['avatar_upload_groups'] = 'Membergroups allowed to upload an avatar to the server'; +$txt['avatar_external_url_groups'] = 'Membergroups allowed to select an external URL'; +$txt['avatar_select_permission'] = 'Select permissions for each group'; +$txt['avatar_download_external'] = 'Download avatar at given URL'; +$txt['custom_avatar_enabled'] = 'Upload avatars to...'; +$txt['option_attachment_dir'] = 'Attachment directory'; +$txt['option_specified_dir'] = 'Specific directory...'; +$txt['custom_avatar_dir'] = 'Upload directory'; +$txt['custom_avatar_dir_desc'] = 'This should not be the same as the server-stored directory.'; +$txt['custom_avatar_url'] = 'Upload URL'; +$txt['custom_avatar_check_empty'] = 'The custom avatar directory you have specified may be empty or invalid. Please ensure these settings are correct.'; +$txt['avatar_reencode'] = 'Re-encode potentially dangerous avatars'; +$txt['avatar_reencode_note'] = '(requires GD module)'; +$txt['avatar_paranoid_warning'] = 'The extensive security checks can result in a large number of rejected avatars.'; +$txt['avatar_paranoid'] = 'Perform extensive security checks on uploaded avatars'; + +$txt['repair_attachments'] = 'Maintain Attachments'; +$txt['repair_attachments_complete'] = 'Maintenance Complete'; +$txt['repair_attachments_complete_desc'] = 'All selected errors have now been corrected'; +$txt['repair_attachments_no_errors'] = 'No errors were found!'; +$txt['repair_attachments_error_desc'] = 'The follow errors were found during maintenance. Check the box next to the errors you wish to fix and hit continue.'; +$txt['repair_attachments_continue'] = 'Continue'; +$txt['repair_attachments_cancel'] = 'Cancel'; +$txt['attach_repair_missing_thumbnail_parent'] = '%1$d thumbnails are missing a parent attachment'; +$txt['attach_repair_parent_missing_thumbnail'] = '%1$d parents are flagged as having thumbnails but don\'t'; +$txt['attach_repair_file_missing_on_disk'] = '%1$d attachments/avatars have an entry but no longer exist on disk'; +$txt['attach_repair_file_wrong_size'] = '%1$d attachments/avatars are being reported as the wrong filesize'; +$txt['attach_repair_file_size_of_zero'] = '%1$d attachments/avatars have a size of zero on disk. (These will be deleted)'; +$txt['attach_repair_attachment_no_msg'] = '%1$d attachments no longer have a message associated with them'; +$txt['attach_repair_avatar_no_member'] = '%1$d avatars no longer have a member associated with them'; +$txt['attach_repair_wrong_folder'] = '%1$d attachments are in the wrong folder'; + +$txt['news_title'] = 'News and Newsletters'; +$txt['news_settings_desc'] = 'Here you can change the settings and permissions related to news and newsletters.'; +$txt['news_settings_submit'] = 'Save'; +$txt['news_mailing_desc'] = 'From this menu you can send messages to all members who\'ve registered and entered their email addresses. You may edit the distribution list, or send messages to all. Useful for important update/news information.'; +$txt['groups_edit_news'] = 'Groups allowed to edit news items'; +$txt['groups_send_mail'] = 'Groups allowed to send out forum newsletters'; +$txt['xmlnews_enable'] = 'Enable XML/RSS news'; +$txt['xmlnews_maxlen'] = 'Maximum message length:
    (0 to disable, bad idea.)
    '; +$txt['editnews_clickadd'] = 'Click here to add another item.'; +$txt['editnews_remove_selected'] = 'Remove selected'; +$txt['editnews_remove_confirm'] = 'Are you sure you want to delete the selected news items?'; +$txt['censor_clickadd'] = 'Click here to add another word.'; + +$txt['layout_controls'] = 'Forum'; +$txt['logs'] = 'Logs'; +$txt['generate_reports'] = 'Reports'; + +$txt['update_available'] = 'Update Available!'; +$txt['update_message'] = 'You\'re using an outdated version of SMF, which contains some bugs which have since been fixed. + It is recommended that you update your forum to the latest version as soon as possible. It only takes a minute!'; + +$txt['manageposts'] = 'Posts and Topics'; +$txt['manageposts_title'] = 'Manage Posts and Topics'; +$txt['manageposts_description'] = 'Here you can manage all settings related to topics and posts.'; + +$txt['manageposts_seconds'] = 'seconds'; +$txt['manageposts_minutes'] = 'minutes'; +$txt['manageposts_characters'] = 'characters'; +$txt['manageposts_days'] = 'days'; +$txt['manageposts_posts'] = 'posts'; +$txt['manageposts_topics'] = 'topics'; + +$txt['manageposts_settings'] = 'Post Settings'; +$txt['manageposts_settings_description'] = 'Here you can set everything related to posts and posting.'; +$txt['manageposts_settings_submit'] = 'Save'; + +$txt['manageposts_bbc_settings'] = 'Bulletin Board Code'; +$txt['manageposts_bbc_settings_description'] = 'Bulletin board code can be used to add markup to forum messages. For example, to highlight the word \'house\' you can type [b]house[/b]. All Bulletin board code tags are surrounded by square brackets (\'[\' and \']\').'; +$txt['manageposts_bbc_settings_title'] = 'Bulletin Board Code Settings'; +$txt['manageposts_bbc_settings_submit'] = 'Save'; + +$txt['manageposts_topic_settings'] = 'Topic Settings'; +$txt['manageposts_topic_settings_description'] = 'Here you can set all settings involving topics.'; +$txt['manageposts_topic_settings_submit'] = 'Save'; + +$txt['removeNestedQuotes'] = 'Remove nested quotes when quoting'; +$txt['enableEmbeddedFlash'] = 'Embed flash into posts'; +$txt['enableEmbeddedFlash_warning'] = 'may be a security risk!'; +$txt['enableSpellChecking'] = 'Enable spell checking'; +$txt['enableSpellChecking_warning'] = 'this does not work on all servers!'; +$txt['disable_wysiwyg'] = 'Disable WYSIWYG editor'; +$txt['max_messageLength'] = 'Maximum allowed post size'; +$txt['max_messageLength_zero'] = '0 for no max.'; +$txt['fixLongWords'] = 'Break up words with more letters than'; +$txt['fixLongWords_zero'] = '0 to disable.'; +$txt['fixLongWords_warning'] = 'this does not work on all servers!'; +$txt['topicSummaryPosts'] = 'Posts to show on topic summary'; +$txt['spamWaitTime'] = 'Time required between posts from the same IP'; +$txt['edit_wait_time'] = 'Courtesy edit wait time'; +$txt['edit_disable_time'] = 'Maximum time after posting to allow edit'; +$txt['edit_disable_time_zero'] = '0 to disable'; + +$txt['enableBBC'] = 'Enable bulletin board code (BBC)'; +$txt['enablePostHTML'] = 'Enable basic HTML in posts'; +$txt['autoLinkUrls'] = 'Automatically link posted URLs'; +$txt['disabledBBC'] = 'Enabled BBC tags'; +$txt['bbcTagsToUse'] = 'Enabled BBC tags'; +$txt['bbcTagsToUse_select'] = 'Select the tags allowed to be used'; +$txt['bbcTagsToUse_select_all'] = 'Select all tags'; + +$txt['enableStickyTopics'] = 'Enable sticky topics'; +$txt['enableParticipation'] = 'Enable participation icons'; +$txt['oldTopicDays'] = 'Time before topic is warned as old on reply'; +$txt['oldTopicDays_zero'] = '0 to disable'; +$txt['defaultMaxTopics'] = 'Number of topics per page in the message index'; +$txt['defaultMaxMessages'] = 'Number of posts per page in a topic page'; +$txt['hotTopicPosts'] = 'Number of posts for a hot topic'; +$txt['hotTopicVeryPosts'] = 'Number of posts for a very hot topic'; +$txt['enableAllMessages'] = 'Max topic size to show "All" posts'; +$txt['enableAllMessages_zero'] = '0 to never show "All"'; +$txt['disableCustomPerPage'] = 'Disable user defined topic/message count per page'; +$txt['enablePreviousNext'] = 'Enable previous/next topic links'; + +$txt['not_done_title'] = 'Not done yet!'; +$txt['not_done_reason'] = 'To avoid overloading your server, the process has been temporarily paused. It should automatically continue in a few seconds. If it doesn\'t, please click continue below.'; +$txt['not_done_continue'] = 'Continue'; + +$txt['general_settings'] = 'General'; +$txt['database_paths_settings'] = 'Database and Paths'; +$txt['cookies_sessions_settings'] = 'Cookies and Sessions'; +$txt['caching_settings'] = 'Caching'; +$txt['load_balancing_settings'] = 'Load Balancing'; + +$txt['language_configuration'] = 'Languages'; +$txt['language_description'] = 'This section allows you to edit languages installed on your forum, download new ones from the Simple Machines website. You may also edit language-related settings here.'; +$txt['language_edit'] = 'Edit Languages'; +$txt['language_add'] = 'Add Language'; +$txt['language_settings'] = 'Settings'; + +$txt['advanced'] = 'Advanced'; +$txt['simple'] = 'Simple'; + +$txt['admin_news_select_recipients'] = 'Please select who should receive a copy of the newsletter'; +$txt['admin_news_select_group'] = 'Membergroups'; +$txt['admin_news_select_group_desc'] = 'Select the groups to receive this newsletter.'; +$txt['admin_news_select_members'] = 'Members'; +$txt['admin_news_select_members_desc'] = 'Additional members to receive newsletter.'; +$txt['admin_news_select_excluded_members'] = 'Excluded Members'; +$txt['admin_news_select_excluded_members_desc'] = 'Members who should not receive newsletter.'; +$txt['admin_news_select_excluded_groups'] = 'Excluded Groups'; +$txt['admin_news_select_excluded_groups_desc'] = 'Select groups who should definitely not receive the newsletter.'; +$txt['admin_news_select_email'] = 'Email Addresses'; +$txt['admin_news_select_email_desc'] = 'A semi-colon separated list of email addresses which should be sent newsletter. (i.e. address1; address2)'; +$txt['admin_news_select_override_notify'] = 'Override Notification Settings'; +// Use entities in below. +$txt['admin_news_cannot_pm_emails_js'] = 'You cannot send a personal message to an email address. If you continue all entered email addresses will be ignored.\\n\\nAre you sure you wish to do this?'; + +$txt['mailqueue_browse'] = 'Browse Queue'; +$txt['mailqueue_settings'] = 'Settings'; + +$txt['admin_search'] = 'Quick Search'; +$txt['admin_search_type_internal'] = 'Task/Setting'; +$txt['admin_search_type_member'] = 'Member'; +$txt['admin_search_type_online'] = 'Online Manual'; +$txt['admin_search_go'] = 'Go'; +$txt['admin_search_results'] = 'Search Results'; +$txt['admin_search_results_desc'] = 'Results for search: "%1$s"'; +$txt['admin_search_results_again'] = 'Search again'; +$txt['admin_search_results_none'] = 'No results found!'; + +$txt['admin_search_section_sections'] = 'Section'; +$txt['admin_search_section_settings'] = 'Setting'; + +$txt['core_settings_title'] = 'Core Features'; +$txt['mods_cat_features'] = 'General'; +$txt['mods_cat_security_general'] = 'General'; +$txt['antispam_title'] = 'Anti-Spam'; +$txt['mods_cat_modifications_misc'] = 'Miscellaneous'; +$txt['mods_cat_layout'] = 'Layout'; +$txt['karma'] = 'Karma'; +$txt['moderation_settings_short'] = 'Moderation'; +$txt['signature_settings_short'] = 'Signatures'; +$txt['custom_profile_shorttitle'] = 'Profile Fields'; +$txt['pruning_title'] = 'Log Pruning'; + +$txt['boardsEdit'] = 'Modify Boards'; +$txt['mboards_new_cat'] = 'Create New Category'; +$txt['manage_holidays'] = 'Manage Holidays'; +$txt['calendar_settings'] = 'Calendar Settings'; +$txt['search_weights'] = 'Weights'; +$txt['search_method'] = 'Search Method'; + +$txt['smiley_sets'] = 'Smiley Sets'; +$txt['smileys_add'] = 'Add Smiley'; +$txt['smileys_edit'] = 'Edit Smileys'; +$txt['smileys_set_order'] = 'Set Smiley Order'; +$txt['icons_edit_message_icons'] = 'Edit Message Icons'; + +$txt['membergroups_new_group'] = 'Add Membergroup'; +$txt['membergroups_edit_groups'] = 'Edit Membergroups'; +$txt['permissions_groups'] = 'General Permissions'; +$txt['permissions_boards'] = 'Board Permissions'; +$txt['permissions_profiles'] = 'Edit Profiles'; +$txt['permissions_post_moderation'] = 'Post Moderation'; + +$txt['browse_packages'] = 'Browse Packages'; +$txt['download_packages'] = 'Download Packages'; +$txt['installed_packages'] = 'Installed Packages'; +$txt['package_file_perms'] = 'File Permissions'; +$txt['package_settings'] = 'Options'; +$txt['themeadmin_admin_title'] = 'Manage and Install'; +$txt['themeadmin_list_title'] = 'Theme Settings'; +$txt['themeadmin_reset_title'] = 'Member Options'; +$txt['themeadmin_edit_title'] = 'Modify Themes'; +$txt['admin_browse_register_new'] = 'Register New Member'; + +$txt['search_engines'] = 'Search Engines'; +$txt['spiders'] = 'Spiders'; +$txt['spider_logs'] = 'Spider Log'; +$txt['spider_stats'] = 'Stats'; + +$txt['paid_subscriptions'] = 'Paid Subscriptions'; +$txt['paid_subs_view'] = 'View Subscriptions'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/EmailTemplates.english.php b/Themes/default/languages/EmailTemplates.english.php new file mode 100644 index 0000000..bade6a4 --- /dev/null +++ b/Themes/default/languages/EmailTemplates.english.php @@ -0,0 +1,1062 @@ + array( + /* + @additional_params: resend_activate_message + REALNAME: The display name for the member receiving the email. + USERNAME: The user name for the member receiving the email. + ACTIVATIONLINK: The url link to activate the member's account. + ACTIVATIONCODE: The code needed to activate the member's account. + ACTIVATIONLINKWITHOUTCODE: The url to the page where the activation code can be entered. + FORGOTPASSWORDLINK: The url to the "forgot password" page. + @description: + */ + 'subject' => 'Welcome to {FORUMNAME}', + 'body' => 'Thank you for registering at {FORUMNAME}. Your username is {USERNAME}. If you forget your password, you can reset it by visiting {FORGOTPASSWORDLINK}. + +Before you can login, you must first activate your account by selecting the following link: + +{ACTIVATIONLINK} + +Should you have any problems with the activation, please visit {ACTIVATIONLINKWITHOUTCODE} and enter the code "{ACTIVATIONCODE}". + +{REGARDS}', + ), + + 'resend_pending_message' => array( + /* + @additional_params: resend_pending_message + REALNAME: The display name for the member receiving the email. + USERNAME: The user name for the member receiving the email. + @description: + */ + 'subject' => 'Welcome to {FORUMNAME}', + 'body' => 'Your registration request at {FORUMNAME} has been received, {REALNAME}. + +The username you registered with was {USERNAME}. + +Before you can login and start using the forum, your request will be reviewed and approved. When this happens, you will receive another email from this address. + +{REGARDS}', + ), + 'mc_group_approve' => array( + /* + @additional_params: mc_group_approve + USERNAME: The user name for the member receiving the email. + GROUPNAME: The name of the membergroup that the user was accepted into. + @description: The request to join a particular membergroup has been accepted. + */ + 'subject' => 'Group Membership Approval', + 'body' => '{USERNAME}, + +We\'re pleased to notify you that your application to join the "{GROUPNAME}" group at {FORUMNAME} has been accepted, and your account has been updated to include this new membergroup. + +{REGARDS}', + ), + 'mc_group_reject' => array( + /* + @additional_params: mc_group_reject + USERNAME: The user name for the member receiving the email. + GROUPNAME: The name of the membergroup that the user was rejected from. + @description: The request to join a particular membergroup has been rejected. + */ + 'subject' => 'Group Membership Rejection', + 'body' => '{USERNAME}, + +We\'re sorry to notify you that your application to join the "{GROUPNAME}" group at {FORUMNAME} has been rejected. + +{REGARDS}', + ), + 'mc_group_reject_reason' => array( + /* + @additional_params: mc_group_reject_reason + USERNAME: The user name for the member receiving the email. + GROUPNAME: The name of the membergroup that the user was rejected from. + REASON: Reason for the rejection. + @description: The request to join a particular membergroup has been rejected with a reason given. + */ + 'subject' => 'Group Membership Rejection', + 'body' => '{USERNAME}, + +We\'re sorry to notify you that your application to join the "{GROUPNAME}" group at {FORUMNAME} has been rejected. + +This is due to the following reason: {REASON} + +{REGARDS}', + ), + 'admin_approve_accept' => array( + /* + @additional_params: admin_approve_accept + NAME: The display name of the member. + USERNAME: The user name for the member receiving the email. + PROFILELINK: The URL of the profile page. + FORGOTPASSWORDLINK: The URL of the "forgot password" page. + @description: + */ + 'subject' => 'Welcome to {FORUMNAME}', + 'body' => 'Welcome, {NAME}! + +Your account has been activated manually by the admin and you can now login and post. Your username is: {USERNAME}. If you forget your password, you can change it at {FORGOTPASSWORDLINK}. + +{REGARDS}', + ), + 'admin_approve_activation' => array( + /* + @additional_params: admin_approve_activation + USERNAME: The user name for the member receiving the email. + ACTIVATIONLINK: The url link to activate the member's account. + ACTIVATIONLINKWITHOUTCODE: The url to the page where the activation code can be entered. + ACTIVATIONCODE: The activation code. + @description: + */ + 'subject' => 'Welcome to {FORUMNAME}', + 'body' => 'Welcome, {USERNAME}! + +Your account on {FORUMNAME} has been approved by the forum administrator. Before you can login, you must first activate your account by selecting the following link: + +{ACTIVATIONLINK} + +Should you have any problems with the activation, please visit {ACTIVATIONLINKWITHOUTCODE} and enter the code "{ACTIVATIONCODE}". + +{REGARDS}', + ), + 'admin_approve_reject' => array( + /* + @additional_params: admin_approve_reject + USERNAME: The user name for the member receiving the email. + @description: + */ + 'subject' => 'Registration Rejected', + 'body' => '{USERNAME}, + +Regrettably, your application to join {FORUMNAME} has been rejected. + +{REGARDS}', + ), + 'admin_approve_delete' => array( + /* + @additional_params: admin_approve_delete + USERNAME: The user name for the member receiving the email. + @description: + */ + 'subject' => 'Account Deleted', + 'body' => '{USERNAME}, + +Your account on {FORUMNAME} has been deleted. This may be because you never activated your account, in which case you should be able to register again. + +{REGARDS}', + ), + 'admin_approve_remind' => array( + /* + @additional_params: admin_approve_remind + USERNAME: The user name for the member receiving the email. + ACTIVATIONLINK: The url link to activate the member's account. + ACTIVATIONLINKWITHOUTCODE: The url to the page where the activation code can be entered. + ACTIVATIONCODE: The activation code. + @description: + */ + 'subject' => 'Registration Reminder', + 'body' => '{USERNAME}, +You still have not activated your account at {FORUMNAME}. + +Please use the link below to activate your account: +{ACTIVATIONLINK} + +Should you have any problems with the activation, please visit {ACTIVATIONLINKWITHOUTCODE} and enter the code "{ACTIVATIONCODE}". + +{REGARDS}', + ), + 'admin_register_activate' => array( + /* + @additional_params: + USERNAME: The user name for the member receiving the email. + ACTIVATIONLINK: The url link to activate the member's account. + ACTIVATIONLINKWITHOUTCODE: The url to the page where the activation code can be entered. + ACTIVATIONCODE: The activation code. + @description: + */ + 'subject' => 'Welcome to {FORUMNAME}', + 'body' => 'Thank you for registering at {FORUMNAME}. Your username is {USERNAME} and your password is {PASSWORD}. + +Before you can login, you must first activate your account by selecting the following link: + +{ACTIVATIONLINK} + +Should you have any problems with the activation, please visit {ACTIVATIONLINKWITHOUTCODE} and enter the code "{ACTIVATIONCODE}". + +{REGARDS}', + ), + 'admin_register_immediate' => array( + 'subject' => 'Welcome to {FORUMNAME}', + 'body' => 'Thank you for registering at {FORUMNAME}. Your username is {USERNAME} and your password is {PASSWORD}. + +{REGARDS}', + ), + 'new_announcement' => array( + /* + @additional_params: new_announcement + TOPICSUBJECT: The subject of the topic being announced. + MESSAGE: The message body of the first post of the announced topic. + TOPICLINK: A link to the topic being announced. + @description: + + */ + 'subject' => 'New announcement: {TOPICSUBJECT}', + 'body' => '{MESSAGE} + +To unsubscribe from these announcements, login to the forum and uncheck "Receive forum announcements and important notifications by email." in your profile. + +You can view the full announcement by following this link: +{TOPICLINK} + +{REGARDS}', + ), + 'notify_boards_once_body' => array( + /* + @additional_params: notify_boards_once_body + TOPICSUBJECT: The subject of the topic causing the notification + TOPICLINK: A link to the topic. + MESSAGE: This is the body of the message. + UNSUBSCRIBELINK: Link to unsubscribe from notifications. + @description: + */ + 'subject' => 'New Topic: {TOPICSUBJECT}', + 'body' => 'A new topic, \'{TOPICSUBJECT}\', has been made on a board you are watching. + +You can see it at +{TOPICLINK} + +More topics may be posted, but you won\'t receive more email notifications until you return to the board and read some of them. + +The text of the topic is shown below: +{MESSAGE} + +Unsubscribe to new topics from this board by using this link: +{UNSUBSCRIBELINK} + +{REGARDS}', + ), + 'notify_boards_once' => array( + /* + @additional_params: notify_boards_once + TOPICSUBJECT: The subject of the topic causing the notification + TOPICLINK: A link to the topic. + UNSUBSCRIBELINK: Link to unsubscribe from notifications. + @description: + */ + 'subject' => 'New Topic: {TOPICSUBJECT}', + 'body' => 'A new topic, \'{TOPICSUBJECT}\', has been made on a board you are watching. + +You can see it at +{TOPICLINK} + +More topics may be posted, but you won\'t receive more email notifications until you return to the board and read some of them. + +Unsubscribe to new topics from this board by using this link: +{UNSUBSCRIBELINK} + +{REGARDS}', + ), + 'notify_boards_body' => array( + /* + @additional_params: notify_boards_body + TOPICSUBJECT: The subject of the topic causing the notification + TOPICLINK: A link to the topic. + MESSAGE: This is the body of the message. + UNSUBSCRIBELINK: Link to unsubscribe from notifications. + @description: + */ + 'subject' => 'New Topic: {TOPICSUBJECT}', + 'body' => 'A new topic, \'{TOPICSUBJECT}\', has been made on a board you are watching. + +You can see it at +{TOPICLINK} + +The text of the topic is shown below: +{MESSAGE} + +Unsubscribe to new topics from this board by using this link: +{UNSUBSCRIBELINK} + +{REGARDS}', + ), + 'notify_boards' => array( + /* + @additional_params: notify_boards + TOPICSUBJECT: The subject of the topic causing the notification + TOPICLINK: A link to the topic. + UNSUBSCRIBELINK: Link to unsubscribe from notifications. + @description: + */ + 'subject' => 'New Topic: {TOPICSUBJECT}', + 'body' => 'A new topic, \'{TOPICSUBJECT}\', has been made on a board you are watching. + +You can see it at +{TOPICLINK} + +Unsubscribe to new topics from this board by using this link: +{UNSUBSCRIBELINK} + +{REGARDS}', + ), + 'request_membership' => array( + /* + @additional_params: request_membership + RECPNAME: The name of the person recieving the email + APPYNAME: The name of the person applying for group membership + GROUPNAME: The name of the group being applied to. + REASON: The reason given by the applicant for wanting to join the group. + MODLINK: Link to the group moderation page. + @description: + */ + 'subject' => 'New Group Application', + 'body' => '{RECPNAME}, + +{APPYNAME} has requested membership to the "{GROUPNAME}" group. The user has given the following reason: + +{REASON} + +You can approve or reject this application by clicking the link below: + +{MODLINK} + +{REGARDS}', + ), + 'paid_subscription_reminder' => array( + /* + @additional_params: scheduled_approval + REALNAME: The real (display) name of the person receiving the email. + PROFILE_LINK: Link to profile of member receiving email where can renew. + SUBSCRIPTION: Name of the subscription. + END_DATE: Date it expires. + @description: + */ + 'subject' => 'Subscription about to expire at {FORUMNAME}', + 'body' => '{REALNAME}, + +A subscription you are subscribed to at {FORUMNAME} is about to expire. If when you took out the subscription you selected to auto-renew you need take no action - otherwise you may wish to consider subscribing once more. Details are below: + +Subscription Name: {SUBSCRIPTION} +Expires: {END_DATE} + +To edit your subscriptions visit the following URL: +{PROFILE_LINK} + +{REGARDS}', + ), + 'activate_reactivate' => array( + /* + @additional_params: activate_reactivate + ACTIVATIONLINK: The url link to reactivate the member's account. + ACTIVATIONCODE: The code needed to reactivate the member's account. + ACTIVATIONLINKWITHOUTCODE: The url to the page where the activation code can be entered. + @description: + */ + 'subject' => 'Welcome back to {FORUMNAME}', + 'body' => 'In order to re-validate your email address, your account has been deactivated. Click the following link to activate it again: +{ACTIVATIONLINK} + +Should you have any problems with activation, please visit {ACTIVATIONLINKWITHOUTCODE} and use the code "{ACTIVATIONCODE}". + +{REGARDS}', + ), + 'forgot_password' => array( + /* + @additional_params: forgot_password + REALNAME: The real (display) name of the person receiving the reminder. + REMINDLINK: The link to reset the password. + IP: The IP address of the requester. + MEMBERNAME: + @description: + */ + 'subject' => 'New password for {FORUMNAME}', + 'body' => 'Dear {REALNAME}, +This mail was sent because the \'forgot password\' function has been applied to your account. To set a new password, click the following link: +{REMINDLINK} + +IP: {IP} +Username: {MEMBERNAME} + +{REGARDS}', + ), + 'forgot_openid' => array( + /* + @additional_params: forgot_password + REALNAME: The real (display) name of the person receiving the reminder. + IP: The IP address of the requester. + OPENID: The members OpenID identity. + @description: + */ + 'subject' => 'OpenID reminder for {FORUMNAME}', + 'body' => 'Dear {REALNAME}, +This mail was sent because the \'forgot OpenID\' function has been applied to your account. Below is the OpenID that your account is associated with: +{OPENID} + +IP: {IP} +Username: {MEMBERNAME} + +{REGARDS}', + ), + 'scheduled_approval' => array( + /* + @additional_params: scheduled_approval + REALNAME: The real (display) name of the person receiving the email. + BODY: The generated body of the mail. + @description: + */ + 'subject' => 'Summary of posts awaiting approval at {FORUMNAME}', + 'body' => '{REALNAME}, + +This email contains a summary of all items awaiting approval at {FORUMNAME}. + +{BODY} + +Please log in to the forum to review these items. +{SCRIPTURL} + +{REGARDS}', + ), + 'send_topic' => array( + /* + @additional_params: send_topic + TOPICSUBJECT: The subject of the topic being sent. + SENDERNAME: The name of the member sending the topic. + RECPNAME: The name of the person receiving the email. + TOPICLINK: A link to the topic being sent. + @description: + */ + 'subject' => 'Topic: {TOPICSUBJECT} (From: {SENDERNAME})', + 'body' => 'Dear {RECPNAME}, +I want you to check out "{TOPICSUBJECT}" on {FORUMNAME}. To view it, please click this link: + +{TOPICLINK} + +Thanks, + +{SENDERNAME}', + ), + 'send_topic_comment' => array( + /* + @additional_params: send_topic_comment + TOPICSUBJECT: The subject of the topic being sent. + SENDERNAME: The name of the member sending the topic. + RECPNAME: The name of the person receiving the email. + TOPICLINK: A link to the topic being sent. + COMMENT: A comment left by the sender. + @description: + */ + 'subject' => 'Topic: {TOPICSUBJECT} (From: {SENDERNAME})', + 'body' => 'Dear {RECPNAME}, +I want you to check out "{TOPICSUBJECT}" on {FORUMNAME}. To view it, please click this link: + +{TOPICLINK} + +A comment has also been added regarding this topic: +{COMMENT} + +Thanks, + +{SENDERNAME}', + ), + 'send_email' => array( + /* + @additional_params: send_email + EMAILSUBJECT: The subject the user wants to email. + EMAILBODY: The body the user wants to email. + SENDERNAME: The name of the member sending the email. + RECPNAME: The name of the person receiving the email. + @description: + */ + 'subject' => '{EMAILSUBJECT}', + 'body' => '{EMAILBODY}', + ), + 'report_to_moderator' => array( + /* + @additional_params: report_to_moderator + TOPICSUBJECT: The subject of the reported post. + POSTERNAME: The report post's author's name. + REPORTERNAME: The name of the person reporting the post. + TOPICLINK: The url of the post that is being reported. + REPORTLINK: The url of the moderation center report. + COMMENT: The comment left by the reporter, hopefully to explain why they are reporting the post. + @description: When a user reports a post this email is sent out to moderators and admins of that board. + */ + 'subject' => 'Reported post: {TOPICSUBJECT} by {POSTERNAME}', + 'body' => 'The following post, "{TOPICSUBJECT}" by {POSTERNAME} has been reported by {REPORTERNAME} on a board you moderate: + +The topic: {TOPICLINK} +Moderation center: {REPORTLINK} + +The reporter has made the following comment: +{COMMENT} + +{REGARDS}', + ), + 'change_password' => array( + /* + @additional_params: change_password + USERNAME: The user name for the member receiving the email. + PASSWORD: The password for the member. + @description: + */ + 'subject' => 'New Password Details', + 'body' => 'Hey, {USERNAME}! + +Your login details at {FORUMNAME} have been changed and your password reset. Below are your new login details. + +Your username is "{USERNAME}" and your password is "{PASSWORD}". + +You may change it after you login by going to the profile page, or by visiting this page after you login: +{SCRIPTURL}?action=profile + +{REGARDS}', + ), + 'register_activate' => array( + /* + @additional_params: register_activate + REALNAME: The display name for the member receiving the email. + USERNAME: The user name for the member receiving the email. + PASSWORD: The password for the member. + ACTIVATIONLINK: The url link to reactivate the member's account. + ACTIVATIONLINKWITHOUTCODE: The url to the page where the activation code can be entered. + ACTIVATIONCODE: The code needed to reactivate the member's account. + FORGOTPASSWORDLINK: The url to the "forgot password" page. + @description: + */ + 'subject' => 'Welcome to {FORUMNAME}', + 'body' => 'Thank you for registering at {FORUMNAME}. Your username is {USERNAME}. If you forget your password, you can reset it by visiting {FORGOTPASSWORDLINK}. + +Before you can login, you first need to activate your account. To do so, please follow this link: + +{ACTIVATIONLINK} + +Should you have any problems with activation, please visit {ACTIVATIONLINKWITHOUTCODE} use the code "{ACTIVATIONCODE}". + +{REGARDS}', + ), + 'register_openid_activate' => array( + /* + @additional_params: register_activate + REALNAME: The display name for the member receiving the email. + USERNAME: The user name for the member receiving the email. + OPENID: The openID identity for the member. + ACTIVATIONLINK: The url link to reactivate the member's account. + ACTIVATIONLINKWITHOUTCODE: The url to the page where the activation code can be entered. + ACTIVATIONCODE: The code needed to reactivate the member's account. + @description: + */ + 'subject' => 'Welcome to {FORUMNAME}', + 'body' => 'Thank you for registering at {FORUMNAME}. Your username is {USERNAME}. You have chosen to authenticate using the following OpenID identity: +{OPENID} + +Before you can login, you first need to activate your account. To do so, please follow this link: + +{ACTIVATIONLINK} + +Should you have any problems with activation, please visit {ACTIVATIONLINKWITHOUTCODE} and use the code "{ACTIVATIONCODE}". + +{REGARDS}', + ), + 'register_coppa' => array( + /* + @additional_params: register_coppa + REALNAME: The display name for the member receiving the email. + USERNAME: The user name for the member receiving the email. + PASSWORD: The password for the member. + COPPALINK: The url link to the coppa form. + FORGOTPASSWORDLINK: The url to the "forgot password" page. + @description: + */ + 'subject' => 'Welcome to {FORUMNAME}', + 'body' => 'Thank you for registering at {FORUMNAME}. Your username is {USERNAME}. If you forget your password, you can change it at {FORGOTPASSWORDLINK} + +Before you can login, the admin requires consent from your parent/guardian for you to join the community. You can obtain more information at the link below: + +{COPPALINK} + +{REGARDS}', + ), + 'register_openid_coppa' => array( + /* + @additional_params: register_coppa + REALNAME: The display name for the member receiving the email. + USERNAME: The user name for the member receiving the email. + OPENID: The openID identity for the member. + COPPALINK: The url link to the coppa form. + @description: + */ + 'subject' => 'Welcome to {FORUMNAME}', + 'body' => 'Thank you for registering at {FORUMNAME}. Your username is {USERNAME}. + +You have chosen to authenticate using the following OpenID identity: +{OPENID} + +Before you can login, the admin requires consent from your parent/guardian for you to join the community. You can obtain more information at the link below: + +{COPPALINK} + +{REGARDS}', + ), + 'register_immediate' => array( + /* + @additional_params: register_immediate + REALNAME: The display name for the member receiving the email. + USERNAME: The user name for the member receiving the email. + PASSWORD: The password for the member. + FORGOTPASSWORDLINK: The url to the "forgot password" page. + @description: + */ + 'subject' => 'Welcome to {FORUMNAME}', + 'body' => 'Thank you for registering at {FORUMNAME}. Your username is {USERNAME}. If you forget your password, you may change it at {FORGOTPASSWORDLINK}. + +{REGARDS}', + ), + 'register_openid_immediate' => array( + /* + @additional_params: register_immediate + REALNAME: The display name for the member receiving the email. + USERNAME: The user name for the member receiving the email. + OPENID: The openID identity for the member. + @description: + */ + 'subject' => 'Welcome to {FORUMNAME}', + 'body' => 'Thank you for registering at {FORUMNAME}. Your username is {USERNAME}. + +You have chosen to authenticate using the following OpenID identity: +{OPENID} + +You may update your profile by visiting this page after you login: + +{SCRIPTURL}?action=profile + +{REGARDS}', + ), + 'register_pending' => array( + /* + @additional_params: register_pending + REALNAME: The display name for the member receiving the email. + USERNAME: The user name for the member receiving the email. + PASSWORD: The password for the member. + FORGOTPASSWORDLINK: The url to the "forgot password" page. + @description: + */ + 'subject' => 'Welcome to {FORUMNAME}', + 'body' => 'Your registration request at {FORUMNAME} has been received, {REALNAME}. + +The username you registered with was {USERNAME}. If you forget your password, you can change it at {FORGOTPASSWORDLINK}. + +Before you can login and start using the forum, your request will be reviewed and approved. When this happens, you will receive another email from this address. + +{REGARDS}', + ), + 'register_openid_pending' => array( + /* + @additional_params: register_pending + REALNAME: The display name for the member receiving the email. + USERNAME: The user name for the member receiving the email. + OPENID: The openID identity for the member. + @description: + */ + 'subject' => 'Welcome to {FORUMNAME}', + 'body' => 'Your registration request at {FORUMNAME} has been received, {REALNAME}. + +The username you registered with was {USERNAME}. + +You have chosen to authenticate using the following OpenID identity: +{OPENID} + +Before you can login and start using the forum, your request will be reviewed and approved. When this happens, you will receive another email from this address. + +{REGARDS}', + ), + 'notification_reply' => array( + /* + @additional_params: notification_reply + TOPICSUBJECT: + POSTERNAME: + TOPICLINK: + UNSUBSCRIBELINK: + @description: + */ + 'subject' => 'Topic reply: {TOPICSUBJECT}', + 'body' => 'A reply has been posted to a topic you are watching by {POSTERNAME}. + +View the reply at: {TOPICLINK} + +Unsubscribe to this topic by using this link: {UNSUBSCRIBELINK} + +{REGARDS}', + ), + 'notification_reply_body' => array( + /* + @additional_params: notification_reply_body + TOPICSUBJECT: + POSTERNAME: + TOPICLINK: + UNSUBSCRIBELINK: + MESSAGE: + @description: + */ + 'subject' => 'Topic reply: {TOPICSUBJECT}', + 'body' => 'A reply has been posted to a topic you are watching by {POSTERNAME}. + +View the reply at: {TOPICLINK} + +Unsubscribe to this topic by using this link: {UNSUBSCRIBELINK} + +The text of the reply is shown below: +{MESSAGE} + +{REGARDS}', + ), + 'notification_reply_once' => array( + /* + @additional_params: notification_reply_once + TOPICSUBJECT: + POSTERNAME: + TOPICLINK: + UNSUBSCRIBELINK: + @description: + */ + 'subject' => 'Topic reply: {TOPICSUBJECT}', + 'body' => 'A reply has been posted to a topic you are watching by {POSTERNAME}. + +View the reply at: {TOPICLINK} + +Unsubscribe to this topic by using this link: {UNSUBSCRIBELINK} + +More replies may be posted, but you won\'t receive any more notifications until you read the topic. + +{REGARDS}', + ), + 'notification_reply_body_once' => array( + /* + @additional_params: notification_reply_body_once + TOPICSUBJECT: + POSTERNAME: + TOPICLINK: + UNSUBSCRIBELINK: + MESSAGE: + @description: + */ + 'subject' => 'Topic reply: {TOPICSUBJECT}', + 'body' => 'A reply has been posted to a topic you are watching by {POSTERNAME}. + +View the reply at: {TOPICLINK} + +Unsubscribe to this topic by using this link: {UNSUBSCRIBELINK} + +The text of the reply is shown below: +{MESSAGE} + +More replies may be posted, but you won\'t receive any more notifications until you read the topic. + +{REGARDS}', + ), + 'notification_sticky' => array( + /* + @additional_params: notification_sticky + @description: + */ + 'subject' => 'Topic stickied: {TOPICSUBJECT}', + 'body' => 'A topic you are watching has been marked as a sticky topic by {POSTERNAME}. + +View the topic at: {TOPICLINK} + +Unsubscribe to this topic by using this link: {UNSUBSCRIBELINK} + +{REGARDS}', + ), + 'notification_lock' => array( + /* + @additional_params: notification_lock + @description: + */ + 'subject' => 'Topic locked: {TOPICSUBJECT}', + 'body' => 'A topic you are watching has been locked by {POSTERNAME}. + +View the topic at: {TOPICLINK} + +Unsubscribe to this topic by using this link: {UNSUBSCRIBELINK} + +{REGARDS}', + ), + 'notification_unlock' => array( + /* + @additional_params: notification_unlock + @description: + */ + 'subject' => 'Topic unlocked: {TOPICSUBJECT}', + 'body' => 'A topic you are watching has been unlocked by {POSTERNAME}. + +View the topic at: {TOPICLINK} + +Unsubscribe to this topic by using this link: {UNSUBSCRIBELINK} + +{REGARDS}', + ), + 'notification_remove' => array( + /* + @additional_params: notification_remove + @description: + */ + 'subject' => 'Topic removed: {TOPICSUBJECT}', + 'body' => 'A topic you are watching has been removed by {POSTERNAME}. + +{REGARDS}', + ), + 'notification_move' => array( + /* + @additional_params: notification_move + @description: + */ + 'subject' => 'Topic moved: {TOPICSUBJECT}', + 'body' => 'A topic you are watching has been moved to another board by {POSTERNAME}. + +View the topic at: {TOPICLINK} + +Unsubscribe to this topic by using this link: {UNSUBSCRIBELINK} + +{REGARDS}', + ), + 'notification_merge' => array( + /* + @additional_params: notification_merged + @description: + */ + 'subject' => 'Topic merged: {TOPICSUBJECT}', + 'body' => 'A topic you are watching has been merged with another topic by {POSTERNAME}. + +View the new merged topic at: {TOPICLINK} + +Unsubscribe to this topic by using this link: {UNSUBSCRIBELINK} + +{REGARDS}', + ), + 'notification_split' => array( + /* + @additional_params: notification_split + @description: + */ + 'subject' => 'Topic split: {TOPICSUBJECT}', + 'body' => 'A topic you are watching has been split into two or more topics by {POSTERNAME}. + +View what remains of this topic at: {TOPICLINK} + +Unsubscribe to this topic by using this link: {UNSUBSCRIBELINK} + +{REGARDS}', + ), + 'admin_notify' => array( + /* + @additional_params: admin_notify + USERNAME: + PROFILELINK: + @description: + */ + 'subject' => 'A new member has joined', + 'body' => '{USERNAME} has just signed up as a new member of your forum. Click the link below to view their profile. +{PROFILELINK} + +{REGARDS}', + ), + 'admin_notify_approval' => array( + /* + @additional_params: admin_notify_approval + USERNAME: + PROFILELINK: + APPROVALLINK: + @description: + */ + 'subject' => 'A new member has joined', + 'body' => '{USERNAME} has just signed up as a new member of your forum. Click the link below to view their profile. +{PROFILELINK} + +Before this member can begin posting they must first have their account approved. Click the link below to go to the approval screen. +{APPROVALLINK} + +{REGARDS}', + ), + 'admin_attachments_full' => array( + /* + @additional_params: admin_attachments_full + REALNAME: + @description: + */ + 'subject' => 'Urgent! Attachments folder almost full', + 'body' => '{REALNAME}, + +The attachments folder at {FORUMNAME} is almost full. Please visit the forum to resolve this problem. + +Once the attachments folder reaches it\'s maximum permitted size users will not be able to continue to post attachments or upload custom avatars (If enabled). + +{REGARDS}', + ), + 'paid_subscription_refund' => array( + /* + @additional_params: paid_subscription_refund + NAME: Subscription title. + REALNAME: Recipients name + REFUNDUSER: Username who took out the subscription. + REFUNDNAME: User's display name who took out the subscription. + DATE: Today's date. + PROFILELINK: Link to members profile. + @description: + */ + 'subject' => 'Refunded Paid Subscription', + 'body' => '{REALNAME}, + +A member has received a refund on a paid subscription. Below are the details of this subscription: + + Subscription: {NAME} + User Name: {REFUNDNAME} ({REFUNDUSER}) + Date: {DATE} + +You can view this members profile by clicking the link below: +{PROFILELINK} + +{REGARDS}', + ), + 'paid_subscription_new' => array( + /* + @additional_params: paid_subscription_new + NAME: Subscription title. + REALNAME: Recipients name + SUBEMAIL: Email address of the user who took out the subscription + SUBUSER: Username who took out the subscription. + SUBNAME: User's display name who took out the subscription. + DATE: Today's date. + PROFILELINK: Link to members profile. + @description: + */ + 'subject' => 'New Paid Subscription', + 'body' => '{REALNAME}, + +A member has taken out a new paid subscription. Below are the details of this subscription: + + Subscription: {NAME} + User Name: {SUBNAME} ({SUBUSER}) + User Email: {SUBEMAIL} + Price: {PRICE} + Date: {DATE} + +You can view this members profile by clicking the link below: +{PROFILELINK} + +{REGARDS}', + ), + 'paid_subscription_error' => array( + /* + @additional_params: paid_subscription_error + ERROR: Error message. + REALNAME: Recipients name + @description: + */ + 'subject' => 'Paid Subscription Error Occurred', + 'body' => '{REALNAME}, + +The following error occurred when processing a paid subscription +--------------------------------------------------------------- +{ERROR} + +{REGARDS}', + ), +); + +/* + @additional_params: happy_birthday + REALNAME: The real (display) name of the person receiving the birthday message. + @description: A message sent to members on their birthday. +*/ +$birthdayEmails = array( + 'happy_birthday' => array( + 'subject' => 'Happy birthday from {FORUMNAME}.', + 'body' => 'Dear {REALNAME}, + +We here at {FORUMNAME} would like to wish you a happy birthday. May this day and the year to follow be full of joy. + +{REGARDS}', + 'author' => 'Thantos', + ), + 'karlbenson1' => array( + 'subject' => 'On your Birthday...', + 'body' => 'We could have sent you a birthday card. We could have sent you some flowers or a cake. + +But we didn\'t. + +We could have even sent you one of those automatically generated messages to wish you happy birthday where we don\'t even have to replace INSERT NAME. + +But we didn\'t + +We wrote this birthday greeting just for you. + +We would like to wish you a very special birthday. + +{REGARDS} + +//:: This message was automatically generated :://', + 'author' => 'karlbenson', + ), + 'nite0859' => array( + 'subject' => 'Happy Birthday!', + 'body' => 'Your friends at {FORUMNAME} would like to take a moment of your time to wish you a happy birthday, {REALNAME}. If you have not done so recently, please visit our community in order for others to have the opportunity to pass along their warm regards. + +Even though today is your birthday, {REALNAME}, we would like to remind you that your membership in our community has been the best gift to us thus far. + +Best Wishes, +The Staff of {FORUMNAME}', + 'author' => 'nite0859', + ), + 'zwaldowski' => array( + 'subject' => 'Birthday Wishes to {REALNAME}', + 'body' => 'Dear {REALNAME}, + +Another year in your life has passed. We at {FORUMNAME} hope it has been filled with happiness, and wish you luck in the coming one. + +{REGARDS}', + 'author' => 'zwaldowski', + ), + 'geezmo' => array( + 'subject' => 'Happy birthday, {REALNAME}!', + 'body' => 'Do you know who\'s having a birthday today, {REALNAME}? + +We know... YOU! + +Happy birthday! + +You\'re now a year older but we hope you\'re a lot happier than last year. + +Enjoy your day today, {REALNAME}! + +- From your {FORUMNAME} family', + 'author' => 'geezmo', + ), + 'karlbenson2' => array( + 'subject' => 'Your Birthday Greeting', + 'body' => 'We hope your birthday is the best ever cloudy, sunny or whatever the weather. +Have lots of birthday cake and fun, and tell us what you have done. + +We hope this message brought you cheer, and make it last, until same time same place, next year. + +{REGARDS}', + 'author' => 'karlbenson', + ), +); +?> \ No newline at end of file diff --git a/Themes/default/languages/Errors.english.php b/Themes/default/languages/Errors.english.php new file mode 100644 index 0000000..ef2cad6 --- /dev/null +++ b/Themes/default/languages/Errors.english.php @@ -0,0 +1,407 @@ +register.'; +$txt['passwords_dont_match'] = 'Passwords aren\'t the same.'; +$txt['register_to_use'] = 'Sorry, you must register before using this feature.'; +$txt['password_invalid_character'] = 'Invalid character used in password.'; +$txt['name_invalid_character'] = 'Invalid character used in name.'; +$txt['email_invalid_character'] = 'Invalid character used in email.'; +$txt['username_reserved'] = 'The username you tried to use contains the reserved name \'%1$s\'. Please try another username.'; +$txt['numbers_one_to_nine'] = 'This field only accepts numbers from 0-9'; +$txt['not_a_user'] = 'The user whose profile you are trying to view does not exist.'; +$txt['not_a_topic'] = 'This topic doesn\'t exist on this board.'; +$txt['not_approved_topic'] = 'This topic has not been approved yet.'; +$txt['email_in_use'] = 'That email address (%1$s) is being used by a registered member already. If you feel this is a mistake, go to the login page and use the password reminder with that address.'; + +$txt['didnt_select_vote'] = 'You didn\'t select a vote option.'; +$txt['poll_error'] = 'Either that poll doesn\'t exist, the poll has been locked, or you tried to vote twice.'; +$txt['members_only'] = 'This option is only available to registered members.'; +$txt['locked_by_admin'] = 'This was locked by an administrator. You cannot unlock it.'; +$txt['not_enough_posts_karma'] = 'Sorry, you don\'t have enough posts to modify karma - you need at least %1$d.'; +$txt['cant_change_own_karma'] = 'Sorry, you are not permitted to modify your own karma.'; +$txt['karma_wait_time'] = 'Sorry, you can\'t repeat a karma action without waiting %1$s %2$s.'; +$txt['feature_disabled'] = 'Sorry, this feature is disabled.'; +$txt['cant_access_upload_path'] = 'Cannot access attachments upload path!'; +$txt['file_too_big'] = 'Your file is too large. The maximum attachment size allowed is %1$d KB.'; +$txt['attach_timeout'] = 'Your attachment couldn\'t be saved. This might happen because it took too long to upload or the file is bigger than the server will allow.

    Please consult your server administrator for more information.'; +$txt['filename_exists'] = 'Sorry! There is already an attachment with the same filename as the one you tried to upload. Please rename the file and try again.'; +$txt['bad_attachment'] = 'Your attachment has failed security checks and cannot be uploaded. Please consult the forum administrator.'; +$txt['ran_out_of_space'] = 'The upload folder is full. Please try a smaller file and/or contact an administrator.'; +$txt['couldnt_connect'] = 'Could not connect to server or could not find file'; +$txt['no_board'] = 'The board you specified doesn\'t exist'; +$txt['cant_split'] = 'You are not allowed to split topics'; +$txt['cant_merge'] = 'You are not allowed to merge topics'; +$txt['no_topic_id'] = 'You specified an invalid topic ID.'; +$txt['split_first_post'] = 'You cannot split a topic at the first post.'; +$txt['topic_one_post'] = 'This topic only contains one message and cannot be split.'; +$txt['no_posts_selected'] = 'No messages selected'; +$txt['selected_all_posts'] = 'Unable to split. You have selected every message.'; +$txt['cant_find_messages'] = 'Unable to find messages'; +$txt['cant_find_user_email'] = 'Unable to find user\'s email address.'; +$txt['cant_insert_topic'] = 'Unable to insert topic'; +$txt['already_a_mod'] = 'You have chosen a username of an already existing moderator. Please choose another username'; +$txt['session_timeout'] = 'Your session timed out while posting. Please go back and try again.'; +$txt['session_verify_fail'] = 'Session verification failed. Please try logging out and back in again, and then try again.'; +$txt['verify_url_fail'] = 'Unable to verify referring url. Please go back and try again.'; +$txt['guest_vote_disabled'] = 'Guests cannot vote in this poll.'; + +$txt['cannot_access_mod_center'] = 'You do not have permission to access the moderation center.'; +$txt['cannot_admin_forum'] = 'You are not allowed to administrate this forum.'; +$txt['cannot_announce_topic'] = 'You are not allowed to announce topics on this board.'; +$txt['cannot_approve_posts'] = 'You do not have permission to approve items.'; +$txt['cannot_post_unapproved_attachments'] = 'You do not have permission to post unapproved attachments.'; +$txt['cannot_post_unapproved_topics'] = 'You do not have permission to post unapproved topics.'; +$txt['cannot_post_unapproved_replies_own'] = 'You do not have permission to post unapproved replies to your topics.'; +$txt['cannot_post_unapproved_replies_any'] = 'You do not have permission to post unapproved replies to other users\' topics.'; +$txt['cannot_calendar_edit_any'] = 'You cannot edit calendar events.'; +$txt['cannot_calendar_edit_own'] = 'You don\'t have the privileges necessary to edit your own events.'; +$txt['cannot_calendar_post'] = 'Event posting isn\'t allowed - sorry.'; +$txt['cannot_calendar_view'] = 'Sorry, but you are not allowed to view the calendar.'; +$txt['cannot_remove_any'] = 'Sorry, but you don\'t have the privilege to remove just any topic. Check to make sure this topic wasn\'t just moved to another board.'; +$txt['cannot_remove_own'] = 'You cannot delete your own topics in this board. Check to make sure this topic wasn\'t just moved to another board.'; +$txt['cannot_edit_news'] = 'You are not allowed to edit news items on this forum.'; +$txt['cannot_pm_read'] = 'Sorry, you can\'t read your personal messages.'; +$txt['cannot_pm_send'] = 'You are not allowed to send personal messages.'; +$txt['cannot_karma_edit'] = 'You aren\'t permitted to modify other people\'s karma.'; +$txt['cannot_lock_any'] = 'You are not allowed to lock just any topic here.'; +$txt['cannot_lock_own'] = 'Apologies, but you cannot lock your own topics here.'; +$txt['cannot_make_sticky'] = 'You don\'t have permission to sticky this topic.'; +$txt['cannot_manage_attachments'] = 'You\'re not allowed to manage attachments or avatars.'; +$txt['cannot_manage_bans'] = 'You\'re not allowed to change the list of bans.'; +$txt['cannot_manage_boards'] = 'You are not allowed to manage boards and categories.'; +$txt['cannot_manage_membergroups'] = 'You don\'t have permission to modify or assign membergroups.'; +$txt['cannot_manage_permissions'] = 'You don\'t have permission to manage permissions.'; +$txt['cannot_manage_smileys'] = 'You\'re not allowed to manage smileys and message icons.'; +$txt['cannot_mark_any_notify'] = 'You don\'t have the permissions necessary to get notifications from this topic.'; +$txt['cannot_mark_notify'] = 'Sorry, but you are not permitted to request notifications from this board.'; +$txt['cannot_merge_any'] = 'You aren\'t allowed to merge topics on one of the selected board(s).'; +$txt['cannot_moderate_forum'] = 'You are not allowed to moderate this forum.'; +$txt['cannot_moderate_board'] = 'You are not allowed to moderate this board.'; +$txt['cannot_modify_any'] = 'You aren\'t allowed to modify just any post.'; +$txt['cannot_modify_own'] = 'Sorry, but you aren\'t allowed to edit your own posts.'; +$txt['cannot_modify_replies'] = 'Even though this post is a reply to your topic, you cannot edit it.'; +$txt['cannot_move_own'] = 'You are not allowed to move your own topics in this board.'; +$txt['cannot_move_any'] = 'You are not allowed to move topics in this board.'; +$txt['cannot_poll_add_own'] = 'Sorry, you aren\'t allowed to add polls to your own topics in this board.'; +$txt['cannot_poll_add_any'] = 'You don\'t have the access to add polls to this topic.'; +$txt['cannot_poll_edit_own'] = 'You cannot edit this poll, even though it is your own.'; +$txt['cannot_poll_edit_any'] = 'You have been denied access to editing polls in this board.'; +$txt['cannot_poll_lock_own'] = 'You are not allowed to lock your own polls in this board.'; +$txt['cannot_poll_lock_any'] = 'Sorry, but you aren\'t allowed to lock just any poll.'; +$txt['cannot_poll_post'] = 'You aren\'t allowed to post polls in the current board.'; +$txt['cannot_poll_remove_own'] = 'You are not permitted to remove this poll from your topic.'; +$txt['cannot_poll_remove_any'] = 'You cannot remove just any poll on this board.'; +$txt['cannot_poll_view'] = 'You are not allowed to view polls in this board.'; +$txt['cannot_poll_vote'] = 'Sorry, but you cannot vote in polls in this board.'; +$txt['cannot_post_attachment'] = 'You don\'t have permission to post attachments here.'; +$txt['cannot_post_new'] = 'Sorry, you cannot post new topics in this board.'; +$txt['cannot_post_reply_any'] = 'You are not permitted to post replies to topics on this board.'; +$txt['cannot_post_reply_own'] = 'You are not allowed to post replies even to your own topics in this board.'; +$txt['cannot_profile_remove_own'] = 'Sorry, but you aren\'t allowed to delete your own account.'; +$txt['cannot_profile_remove_any'] = 'You don\'t have the permissions to go about removing people\'s accounts!'; +$txt['cannot_profile_extra_any'] = 'You are not permitted to modify profile settings.'; +$txt['cannot_profile_identity_any'] = 'You aren\'t allowed to edit account settings.'; +$txt['cannot_profile_title_any'] = 'You cannot edit people\'s custom titles.'; +$txt['cannot_profile_extra_own'] = 'Sorry, but you don\'t have the necessary permissions to edit your profile data.'; +$txt['cannot_profile_identity_own'] = 'You can\'t change your identity at the current moment.'; +$txt['cannot_profile_title_own'] = 'You are not allowed to change your custom title.'; +$txt['cannot_profile_server_avatar'] = 'You are not permitted to use a server stored avatar.'; +$txt['cannot_profile_upload_avatar'] = 'You do not have permission to upload an avatar.'; +$txt['cannot_profile_remote_avatar'] = 'You don\'t have the privilege of using a remote avatar.'; +$txt['cannot_profile_view_own'] = 'Many apologies, but you can\'t view your own profile.'; +$txt['cannot_profile_view_any'] = 'Many apologies, but you can\'t view just any profile.'; +$txt['cannot_delete_own'] = 'You are not, on this board, allowed to delete your own posts.'; +$txt['cannot_delete_replies'] = 'Sorry, but you cannot remove these posts, even though they are replies to your topic.'; +$txt['cannot_delete_any'] = 'Deleting just any posts in this board is not allowed.'; +$txt['cannot_report_any'] = 'You are not allowed to report posts in this board.'; +$txt['cannot_search_posts'] = 'You are not allowed to search for posts in this forum.'; +$txt['cannot_send_mail'] = 'You don\'t have the privilege of sending out emails to everyone.'; +$txt['cannot_issue_warning'] = 'Sorry, you do not have permission to issue warnings to members.'; +$txt['cannot_send_topic'] = 'Sorry, but the administrator has disallowed sending topics on this board.'; +$txt['cannot_split_any'] = 'Splitting just any topic is not allowed in this board.'; +$txt['cannot_view_attachments'] = 'It seems that you are not allowed to download or view attachments on this board.'; +$txt['cannot_view_mlist'] = 'You can\'t view the memberlist because you don\'t have permission to.'; +$txt['cannot_view_stats'] = 'You aren\'t allowed to view the forum statistics.'; +$txt['cannot_who_view'] = 'Sorry - you don\'t have the proper permissions to view the Who\'s Online list.'; + +$txt['no_theme'] = 'That theme does not exist.'; +$txt['theme_dir_wrong'] = 'The default theme\'s directory is wrong, please correct it by clicking this text.'; +$txt['registration_disabled'] = 'Sorry, registration is currently disabled.'; +$txt['registration_no_secret_question'] = 'Sorry, there is no secret question set for this member.'; +$txt['poll_range_error'] = 'Sorry, the poll must run for more than 0 days.'; +$txt['delFirstPost'] = 'You are not allowed to delete the first post in a topic.

    If you want to delete this topic, click on the Remove Topic link, or ask a moderator/administrator to do it for you.

    '; +$txt['parent_error'] = 'Unable to create board!'; +$txt['login_cookie_error'] = 'You were unable to login. Please check your cookie settings.'; +$txt['incorrect_answer'] = 'Sorry, but you did not answer your question correctly. Please click back to try again, or click back twice to use the default method of obtaining your password.'; +$txt['no_mods'] = 'No moderators found!'; +$txt['parent_not_found'] = 'Board structure corrupt: unable to find parent board'; +$txt['modify_post_time_passed'] = 'You may not modify this post as the time limit for edits has passed.'; + +$txt['calendar_off'] = 'You cannot access the calendar right now because it is disabled.'; +$txt['invalid_month'] = 'Invalid month value.'; +$txt['invalid_year'] = 'Invalid year value.'; +$txt['invalid_day'] = 'Invalid day value.'; +$txt['event_month_missing'] = 'Event month is missing.'; +$txt['event_year_missing'] = 'Event year is missing.'; +$txt['event_day_missing'] = 'Event day is missing.'; +$txt['event_title_missing'] = 'Event title is missing.'; +$txt['invalid_date'] = 'Invalid date.'; +$txt['no_event_title'] = 'No event title was entered.'; +$txt['missing_event_id'] = 'Missing event ID.'; +$txt['cant_edit_event'] = 'You do not have permission to edit this event.'; +$txt['missing_board_id'] = 'Board ID is missing.'; +$txt['missing_topic_id'] = 'Topic ID is missing.'; +$txt['topic_doesnt_exist'] = 'Topic doesn\'t exist.'; +$txt['not_your_topic'] = 'You are not the owner of this topic.'; +$txt['board_doesnt_exist'] = 'The board does not exist.'; +$txt['no_span'] = 'The span feature is currently disabled.'; +$txt['invalid_days_numb'] = 'Invalid number of days to span.'; + +$txt['moveto_noboards'] = 'There are no boards to move this topic to!'; + +$txt['already_activated'] = 'Your account has already been activated.'; +$txt['still_awaiting_approval'] = 'Your account is still awaiting admin approval.'; + +$txt['invalid_email'] = 'Invalid email address / email address range.
    Example of a valid email address: evil.user@badsite.com.
    Example of a valid email address range: *@*.badsite.com'; +$txt['invalid_expiration_date'] = 'Expiration date is not valid'; +$txt['invalid_hostname'] = 'Invalid host name / host name range.
    Example of a valid host name: proxy4.badhost.com
    Example of a valid host name range: *.badhost.com'; +$txt['invalid_ip'] = 'Invalid IP / IP range.
    Example of a valid IP address: 127.0.0.1
    Example of a valid IP range: 127.0.0-20.*'; +$txt['invalid_tracking_ip'] = 'Invalid IP / IP range.
    Example of a valid IP address: 127.0.0.1
    Example of a valid IP range: 127.0.0.*'; +$txt['invalid_username'] = 'Member name not found'; +$txt['no_ban_admin'] = 'You may not ban an admin - You must demote them first!'; +$txt['no_bantype_selected'] = 'No ban type was selected'; +$txt['ban_not_found'] = 'Ban not found'; +$txt['ban_unknown_restriction_type'] = 'Restriction type unknown'; +$txt['ban_name_empty'] = 'The name of the ban was left empty'; +$txt['ban_name_exists'] = 'The name of this ban (%1$s) already exists. Please choose a different name.'; +$txt['ban_trigger_already_exists'] = 'This ban trigger (%1$s) already exists in %2$s.'; + +$txt['recycle_no_valid_board'] = 'No valid board selected for recycled topics'; + +$txt['login_threshold_fail'] = 'Sorry, you are out of login chances. Please come back and try again later.'; +$txt['login_threshold_brute_fail'] = 'Sorry, but you\'ve reached your login attempts threshold. Please wait 30 seconds and try again later.'; + +$txt['who_off'] = 'You cannot access Who\'s Online right now because it is disabled.'; + +$txt['merge_create_topic_failed'] = 'Error creating a new topic.'; +$txt['merge_need_more_topics'] = 'Merge topics require at least two topics to merge.'; + +$txt['postWaitTime_broken'] = 'The last posting from your IP was less than %1$d seconds ago. Please try again later.'; +$txt['registerWaitTime_broken'] = 'You already registered just %1$d seconds ago!'; +$txt['loginWaitTime_broken'] = 'You will have to wait about %1$d seconds to login again, sorry.'; +$txt['pmWaitTime_broken'] = 'The last personal message from your IP was less than %1$d seconds ago. Please try again later.'; +$txt['reporttmWaitTime_broken'] = 'The last topic report from your IP was less than %1$d seconds ago. Please try again later.'; +$txt['sendtopcWaitTime_broken'] = 'The last topic sent from your IP was less than %1$d seconds ago. Please try again later.'; +$txt['sendmailWaitTime_broken'] = 'The last email sent from your IP was less than %1$d seconds ago. Please try again later.'; +$txt['searchWaitTime_broken'] = 'Your last search was less than %1$d seconds ago. Please try again later.'; + +$txt['email_missing_data'] = 'You must enter something in both the subject and message boxes.'; + +$txt['topic_gone'] = 'The topic or board you are looking for appears to be either missing or off limits to you.'; +$txt['theme_edit_missing'] = 'The file you are trying to edit... can\'t even be found!'; + +$txt['attachments_no_write'] = 'The attachments upload directory is not writable. Your attachment or avatar cannot be saved.'; +$txt['attachments_limit_per_post'] = 'You may not upload more than %1$d attachments per post'; + +$txt['no_dump_database'] = 'Only administrators can make database backups!'; +$txt['pm_not_yours'] = 'The personal message you\'re trying to quote is not your own or does not exist, please go back and try again.'; +$txt['mangled_post'] = 'Mangled form data - please go back and try again.'; +$txt['quoted_post_deleted'] = 'The post you are trying to quote either does not exist, was deleted, or is no longer viewable by you.'; +$txt['pm_too_many_per_hour'] = 'You have exceeded the limit of %1$d personal messages per hour.'; +$txt['labels_too_many'] = 'Sorry, %1$s messages already had the maximum amount of labels allowed!'; + +$txt['register_only_once'] = 'Sorry, but you\'re not allowed to register multiple accounts at the same time from the same computer.'; +$txt['admin_setting_coppa_require_contact'] = 'You must enter either a postal or fax contact if parent/guardian approval is required.'; + +$txt['error_long_name'] = 'The name you tried to use was too long.'; +$txt['error_no_name'] = 'No name was provided.'; +$txt['error_bad_name'] = 'The name you submitted cannot be used, because it is or contains a reserved name.'; +$txt['error_no_email'] = 'No email address was provided.'; +$txt['error_bad_email'] = 'An invalid email address was given.'; +$txt['error_no_event'] = 'No event name has been given.'; +$txt['error_no_subject'] = 'No subject was filled in.'; +$txt['error_no_question'] = 'No question was filled in for this poll.'; +$txt['error_no_message'] = 'The message body was left empty.'; +$txt['error_long_message'] = 'The message exceeds the maximum allowed length (%1$d characters).'; +$txt['error_no_comment'] = 'The comment field was left empty.'; +$txt['error_session_timeout'] = 'Your session timed out while posting. Please try to re-submit your message.'; +$txt['error_no_to'] = 'No recipients specified.'; +$txt['error_bad_to'] = 'One or more \'to\'-recipients could not be found.'; +$txt['error_bad_bcc'] = 'One or more \'bcc\'-recipients could not be found.'; +$txt['error_form_already_submitted'] = 'You already submitted this post! You might have accidentally double clicked or tried to refresh the page.'; +$txt['error_poll_few'] = 'You must have at least two choices!'; +$txt['error_need_qr_verification'] = 'Please complete the verification section below to complete your post.'; +$txt['error_wrong_verification_code'] = 'The letters you typed don\'t match the letters that were shown in the picture.'; +$txt['error_wrong_verification_answer'] = 'You did not answer the verification questions correctly.'; +$txt['error_need_verification_code'] = 'Please enter the verification code below to continue to the results.'; +$txt['error_bad_file'] = 'Sorry but the file specified could not be opened: %1$s'; +$txt['error_bad_line'] = 'The line you specified is invalid.'; + +$txt['smiley_not_found'] = 'Smiley not found.'; +$txt['smiley_has_no_code'] = 'No code for this smiley was given.'; +$txt['smiley_has_no_filename'] = 'No filename for this smiley was given.'; +$txt['smiley_not_unique'] = 'A smiley with that code already exists.'; +$txt['smiley_set_already_exists'] = 'A smiley set with that URL already exists'; +$txt['smiley_set_not_found'] = 'Smiley set not found'; +$txt['smiley_set_path_already_used'] = 'The URL of the smiley set is already being used by another smiley set.'; +$txt['smiley_set_unable_to_import'] = 'Unable to import smiley set. Either the directory is invalid or cannot be accessed.'; + +$txt['smileys_upload_error'] = 'Failed to upload file.'; +$txt['smileys_upload_error_blank'] = 'All smiley sets must have an image!'; +$txt['smileys_upload_error_name'] = 'All smileys must have the same filename!'; +$txt['smileys_upload_error_illegal'] = 'Illegal Type.'; + +$txt['search_invalid_weights'] = 'Search weights are not properly configured. At least one weight should be configure to be non-zero. Please report this error to an administrator.'; +$txt['unable_to_create_temporary'] = 'The search function was unable to create temporary tables. Please try again.'; + +$txt['package_no_file'] = 'Unable to find package file!'; +$txt['packageget_unable'] = 'Unable to connect to the server. Please try using this URL instead.'; +$txt['not_on_simplemachines'] = 'Sorry, packages can only be downloaded like this from the simplemachines.org server.'; +$txt['package_cant_uninstall'] = 'This package was either never installed or was already uninstalled - you can\'t uninstall it now.'; +$txt['package_cant_download'] = 'You cannot download or install new packages because the Packages directory or one of the files in it are not writable!'; +$txt['package_upload_error_nofile'] = 'You did not select a package to upload.'; +$txt['package_upload_error_failed'] = 'Could not upload package, please check directory permissions!'; +$txt['package_upload_error_exists'] = 'The file you are uploading already exists on the server. Please delete it first then try again.'; +$txt['package_upload_error_supports'] = 'The package manager currently allows only these file types: %1$s.'; +$txt['package_upload_error_broken'] = 'Package upload failed due to the following error:
    "%1$s"'; + +$txt['package_get_error_not_found'] = 'The package you are trying to install cannot be located. You may want to manually upload the package to your Packages directory.'; +$txt['package_get_error_missing_xml'] = 'The package you are attempting to install is missing the package-info.xml that must be in the root package directory.'; +$txt['package_get_error_is_zero'] = 'Although the package was downloaded to the server it appears to be empty. Please check the Packages directory, and the "temp" sub-directory are both writable. If you continue to experience this problem you should try extracting the package on your PC and uploading the extracted files into a subdirectory in your Packages directory and try again. For example, if the package was called shout.tar.gz you should:
    1) Download the package to your local PC and extract it into files.
    2) Using an FTP client create a new directory in your "Packages" folder, in this example you may call it "shout".
    3) Upload all the files from the extracted package to this directory.
    4) Go back to the package manager browse page and the package will be automatically found by SMF.'; +$txt['package_get_error_packageinfo_corrupt'] = 'SMF was unable to find any valid information within the package-info.xml file included within the Package. There may be an error with the modification, or the package may be corrupt.'; + +$txt['no_membergroup_selected'] = 'No membergroup selected'; +$txt['membergroup_does_not_exist'] = 'The membergroup doesn\'t exist or is invalid.'; + +$txt['at_least_one_admin'] = 'There must be at least one administrator on a forum!'; + +$txt['error_functionality_not_windows'] = 'Sorry, this functionality is currently not available for servers running Windows.'; + +// Don't use entities in the below string. +$txt['attachment_not_found'] = 'Attachment Not Found'; + +$txt['error_no_boards_selected'] = 'No valid boards were selected!'; +$txt['error_invalid_search_string'] = 'Did you forget to put something to search for?'; +$txt['error_invalid_search_string_blacklist'] = 'Your search query contained too trivial words. Please try again with a different query.'; +$txt['error_search_string_small_words'] = 'Each word must be at least two characters long.'; +$txt['error_query_not_specific_enough'] = 'Your search query didn\'t return any matches.'; +$txt['error_no_messages_in_time_frame'] = 'No messages found in selected time frame.'; +$txt['error_no_labels_selected'] = 'No labels were selected!'; +$txt['error_no_search_daemon'] = 'Unable to access the search daemon'; + +$txt['profile_errors_occurred'] = 'The following errors occurred when trying to save your profile'; +$txt['profile_error_bad_offset'] = 'The time offset is out of range'; +$txt['profile_error_no_name'] = 'The name field was left blank'; +$txt['profile_error_name_taken'] = 'The selected username/display name has already been taken'; +$txt['profile_error_name_too_long'] = 'The selected name is too long. It should be no greater than 60 characters long'; +$txt['profile_error_no_email'] = 'The email field was left blank'; +$txt['profile_error_bad_email'] = 'You have not entered a valid email address'; +$txt['profile_error_email_taken'] = 'Another user is already registered with that email address'; +$txt['profile_error_no_password'] = 'You did not enter your password'; +$txt['profile_error_bad_new_password'] = 'The new passwords you entered do not match'; +$txt['profile_error_bad_password'] = 'The password you entered was not correct'; +$txt['profile_error_bad_avatar'] = 'The avatar you have selected is either too large or not an avatar'; +$txt['profile_error_password_short'] = 'Your password must be at least ' . (empty($modSettings['password_strength']) ? 4 : 8) . ' characters long.'; +$txt['profile_error_password_restricted_words'] = 'Your password must not contain your username, email address or other commonly used words.'; +$txt['profile_error_password_chars'] = 'Your password must contain a mix of upper and lower case letters, as well as digits.'; +$txt['profile_error_already_requested_group'] = 'You already have an outstanding request for this group!'; +$txt['profile_error_openid_in_use'] = 'Another user is already using that OpenID authentication URL'; + +$txt['mysql_error_space'] = ' - check database storage space or contact the server administrator.'; + +$txt['icon_not_found'] = 'The icon image could not be found in the default theme - please ensure the image has been uploaded and try again.'; +$txt['icon_after_itself'] = 'The icon cannot be positioned after itself!'; +$txt['icon_name_too_long'] = 'Icon filenames cannot be more than 16 characters long'; + +$txt['name_censored'] = 'Sorry, the name you tried to use, %1$s, contains words which have been censored. Please try another name.'; + +$txt['poll_already_exists'] = 'A topic can only have one poll associated with it!'; +$txt['poll_not_found'] = 'There is no poll associated with this topic!'; + +$txt['error_while_adding_poll'] = 'The following error or errors occurred while adding this poll'; +$txt['error_while_editing_poll'] = 'The following error or errors occurred while editing this poll'; + +$txt['loadavg_search_disabled'] = 'Due to high stress on the server, the search function has been automatically and temporarily disabled. Please try again in a short while.'; +$txt['loadavg_generic_disabled'] = 'Sorry, because of high stress on the server, this feature is currently unavailable.'; +$txt['loadavg_allunread_disabled'] = 'The server\'s resources are temporarily under too high a demand to find all the topics you have not read.'; +$txt['loadavg_unreadreplies_disabled'] = 'The server is currently under high stress. Please try again shortly.'; +$txt['loadavg_show_posts_disabled'] = 'Please try again later. This member\'s posts are not currently available due to high load on the server.'; +$txt['loadavg_unread_disabled'] = 'The server\'s resources are temporarily under too high a demand to list out the topics you have not read.'; + +$txt['cannot_edit_permissions_inherited'] = 'You cannot edit inherited permissions directly, you must either edit the parent group or edit the membergroup inheritance.'; + +$txt['mc_no_modreport_specified'] = 'You need to specify which report you wish to view.'; +$txt['mc_no_modreport_found'] = 'The specified report either doesn\'t exist or is off limits to you'; + +$txt['st_cannot_retrieve_file'] = 'Could not retrieve the file %1$s.'; +$txt['admin_file_not_found'] = 'Could not load the requested file: %1$s.'; + +$txt['themes_none_selectable'] = 'At least one theme must be selectable.'; +$txt['themes_default_selectable'] = 'The overall forum default theme must be a selectable theme.'; +$txt['ignoreboards_disallowed'] = 'The option to ignore boards has not been enabled.'; + +$txt['mboards_delete_error'] = 'No category selected!'; +$txt['mboards_delete_board_error'] = 'No board selected!'; + +$txt['mboards_parent_own_child_error'] = 'Unable to make a parent its own child!'; +$txt['mboards_board_own_child_error'] = 'Unable to make a board its own child!'; + +$txt['smileys_upload_error_notwritable'] = 'The following smiley directories are not writable: %1$s'; +$txt['smileys_upload_error_types'] = 'Smiley images can only have the following extensions: %1$s.'; + +$txt['change_email_success'] = 'Your email address has been changed, and a new activation email has been sent to it.'; +$txt['resend_email_success'] = 'A new activation email has successfully been sent.'; + +$txt['custom_option_need_name'] = 'The profile option must have a name!'; +$txt['custom_option_not_unique'] = 'Field name is not unique!'; + +$txt['warning_no_reason'] = 'You must enter a reason for altering the warning state of a member'; +$txt['warning_notify_blank'] = 'You selected to notify the user but did not fill in the subject/message fields'; + +$txt['cannot_connect_doc_site'] = 'Could not connect to the Simple Machines Online Manual. Please check that your server configuration allows external internet connections and try again later.'; + +$txt['movetopic_no_reason'] = 'You must enter a reason for moving the topic, or uncheck the option to \'post a redirection topic\'.'; + +// OpenID error strings +$txt['openid_server_bad_response'] = 'The requested identifier did not return the proper information.'; +$txt['openid_return_no_mode'] = 'The identity provider did not respond with the OpenID mode.'; +$txt['openid_not_resolved'] = 'The identity provider did not approve your request.'; +$txt['openid_no_assoc'] = 'Could not find the requested association with the identity provider.'; +$txt['openid_sig_invalid'] = 'The signature from the identity provider is invalid.'; +$txt['openid_load_data'] = 'Could not load the data from your login request. Please try again.'; +$txt['openid_not_verified'] = 'The OpenID address given has not been verified yet. Please log in to verify.'; + +$txt['error_custom_field_too_long'] = 'The "%1$s" field cannot be greater than %2$d characters in length.'; +$txt['error_custom_field_invalid_email'] = 'The "%1$s" field must be a valid email address.'; +$txt['error_custom_field_not_number'] = 'The "%1$s" field must be numeric.'; +$txt['error_custom_field_inproper_format'] = 'The "%1$s" field is an invalid format.'; +$txt['error_custom_field_empty'] = 'The "%1$s" field cannot be left blank.'; + +$txt['email_no_template'] = 'The email template "%1$s" could not be found.'; + +$txt['search_api_missing'] = 'The search API could not be found! Please contact the admin to check they have uploaded the correct files.'; +$txt['search_api_not_compatible'] = 'The selected search API the forum is using is out of date - falling back to standard search. Please check file %1$s.'; + +// Restore topic/posts +$txt['cannot_restore_first_post'] = 'You cannot restore the first post in a topic.'; +$txt['parent_topic_missing'] = 'The parent topic of the post you are trying to restore has been deleted.'; +$txt['restored_disabled'] = 'The restoration of topics has been disabled.'; +$txt['restore_not_found'] = 'The following messages could not be restored; the original topic may have been removed:
      %1$s
    You will need to move these manually.'; + +$txt['error_invalid_dir'] = 'The directory you entered is invalid.'; + +$txt['error_sqlite_optimizing'] = 'Sqlite is optimizing the database, the forum can not be accessed until it has finished. Please try refreshing this page momentarily.'; +?> \ No newline at end of file diff --git a/Themes/default/languages/Help.english.php b/Themes/default/languages/Help.english.php new file mode 100644 index 0000000..1fc5579 --- /dev/null +++ b/Themes/default/languages/Help.english.php @@ -0,0 +1,604 @@ +Edit Boards
    + In this menu you can create/reorder/remove boards, and the categories + above them. For example, if you had a wide-ranging + site that offered information on "Sports" and "Cars" and "Music", these + would be the top-level Categories you\'d create. Under each of these + categories you\'d likely want to create hierarchical "sub-categories", + or "Boards" for topics within each. It\'s a simple hierarchy, with this structure:
    +
      +
    • + Sports +  - A "category" +
    • +
        +
      • + Baseball +  - A board under the category of "Sports" +
      • +
          +
        • + Stats +  - A child board under the board of "Baseball" +
        • +
        +
      • Football +  - A board under the category of "Sports"
      • +
      +
    + Categories allow you to break down the board into broad topics ("Cars, + Sports"), and the "Boards" under them are the actual topics under which + members can post. A user interested in Pintos + would post a message under "Cars->Pinto". Categories allow people to + quickly find what their interests are: Instead of a "Store" you have + "Hardware" and "Clothing" stores you can go to. This simplifies your + search for "pipe joint compound" because you can go to the Hardware + Store "category" instead of the Clothing Store (where you\'re unlikely + to find pipe joint compound).
    + As noted above, a Board is a key topic underneath a broad category. + If you want to discuss "Pintos" you\'d go to the "Auto" category and + jump into the "Pinto" board to post your thoughts in that board.
    + Administrative functions for this menu item are to create new boards + under each category, to reorder them (put "Pinto" behind "Chevy"), or + to delete the board entirely.'; + +$helptxt['edit_news'] = ' +
      +
    • + News
      + This section allows you to set the text for news items displayed on the Board Index page. + Add any item you want (e.g., "Don\'t miss the conference this Tuesday"). Each news item is displayed randomly and should be placed in a separate box. +
    • +
    • + Newsletters
      + This section allows you to send out newsletters to the members of the forum via personal message or email. First select the groups that you want to receive the newsletter, and those you don\'t want to receive the newsletter. If you wish, you can add additional members and email addresses that will receive the newsletter. Finally, input the message you want to send and select whether you want it to be sent to members as a personal message or as an email. +
    • +
    • + Settings
      + This section contains a few settings that relate to news and newsletters, including selecting what groups can edit forum news or send newsletters. There is also an setting to configure whether you want news feeds enabled on the forum, as well as a setting to configure the length (how many characters are displayed) for each news post from a news feed. +
    • +
    '; + +$helptxt['view_members'] = ' +
      +
    • + View all Members
      + View all members in the board. You are presented with a hyperlinked list of member names. You may click + on any of the names to find details of the members (homepage, age, etc.), and as Administrator + you are able to modify these parameters. You have complete control over members, including the + ability to delete them from the forum.

      +
    • +
    • + Awaiting Approval
      + This section is only shown if you have enabled admin approval of all new registrations. Anyone who registers to join your + forum will only become a full member once they have been approved by an admin. The section lists all those members who + are still awaiting approval, along with their email and IP address. You can choose to either accept or reject (delete) + any member on the list by checking the box next to the member and choosing the action from the drop-down box at the bottom + of the screen. When rejecting a member you can choose to delete the member either with or without notifying them of your decision.

      +
    • +
    • + Awaiting Activation
      + This section will only be visible if you have activation of member accounts enabled on the forum. This section will list all + members who have still not activated their new accounts. From this screen you can choose to either accept, reject or remind + members with outstanding registrations. As above you can also choose to email the member to inform them of the + action you have taken.

      +
    • +
    '; + +$helptxt['ban_members'] = 'Ban Members
    + SMF provides the ability to "ban" users, to prevent people who have violated the trust of the board + by spamming, trolling, etc. This allows you to those users who are detrimental to your forum. As an admin, + when you view messages, you can see each user\'s IP address used to post at that time. In the ban list, + you simply type that IP address in, save, and they can no longer post from that location.
    You can also + ban people through their email address.'; + +$helptxt['featuresettings'] = 'Features and Options
    + There are several features in this section that can be changed to your preference.'; + +$helptxt['securitysettings'] = 'Security and Moderation
    + This section contains settings relating to the security and moderation of your forum.'; + +$helptxt['modsettings'] = 'Modification Settings
    + This section should contain any settings added by modifications installed on your forum.'; + +$helptxt['number_format'] = 'Number Format
    + You can use this setting to format the way in which numbers on your forum will be displayed to the user. The format of this setting is:
    +
    1,234.00

    + Where \',\' is the character used to split up groups of thousands, \'.\' is the character used as the decimal point and the number of zeros dictate the accuracy of rounding.'; + +$helptxt['time_format'] = 'Time Format
    + You have the power to adjust how the time and date look for yourself. There are a lot of little letters, but it\'s quite simple. + The conventions follow PHP\'s strftime function and are described as below (more details can be found at php.net).
    +
    + The following characters are recognized in the format string:
    + +   %a - abbreviated weekday name
    +   %A - full weekday name
    +   %b - abbreviated month name
    +   %B - full month name
    +   %d - day of the month (01 to 31)
    +   %D* - same as %m/%d/%y
    +   %e* - day of the month (1 to 31)
    +   %H - hour using a 24-hour clock (range 00 to 23)
    +   %I - hour using a 12-hour clock (range 01 to 12)
    +   %m - month as a number (01 to 12)
    +   %M - minute as a number
    +   %p - either "am" or "pm" according to the given time
    +   %R* - time in 24 hour notation
    +   %S - second as a decimal number
    +   %T* - current time, equal to %H:%M:%S
    +   %y - 2 digit year (00 to 99)
    +   %Y - 4 digit year
    +   %% - a literal \'%\' character
    +
    + * Does not work on Windows-based servers.
    '; + +$helptxt['live_news'] = 'Live announcements
    + This box shows recently updated announcements from www.simplemachines.org. + You should check here every now and then for updates, new releases, and important information from Simple Machines.'; + +$helptxt['registrations'] = 'Registration Management
    + This section contains all the functions that could be necessary to manage new registrations on the forum. It contains up to four + sections which are visible depending on your forum settings. These are:

    +
      +
    • + Register new member
      + From this screen you can choose to register accounts for new members on their behalf. This can be useful in forums where registration is closed + to new members, or in cases where the admin wishes to create a test account. If the option to require activation of the account + is selected the member will be emailed a activation link which must be clicked before they can use the account. Similarly you can + select to email the users new password to the stated email address.

      +
    • +
    • + Edit Registration Agreement
      + This allows you to set the text for the registration agreement displayed when members sign up for your forum. + You can add or remove anything from the default registration agreement, which is included in SMF.

      +
    • +
    • + Set Reserved Names
      + Using this interface you can specify words or names which may not be used by your users.

      +
    • +
    • + Settings
      + This section will only be visible if you have permission to administrate the forum. From this screen you can decide on the registration method + is use on your forum, as well as other registration related settings. +
    • +
    '; + +$helptxt['modlog'] = 'Moderation Log
    + This section allows members of the moderation team to track all the moderation actions that the forum moderators have performed. To ensure that + moderators cannot remove references to the actions they have performed, entries may not be deleted until 24 hours after the action was taken.'; +$helptxt['adminlog'] = 'Administration Log
    + This section allows members of the admin team to track some of the administrative actions that have occurred on the forum. To ensure that + admins cannot remove references to the actions they have performed, entries may not be deleted until 24 hours after the action was taken.'; +$helptxt['warning_enable'] = 'User Warning System
    + This feature enables members of the admin and moderation team to issue warnings to members - and to use a members warning level to determine the + actions available to them on the forum. Upon enabling this feature a permission will be available within the permissions section to define + which groups may assign warnings to members. Warning levels can be adjusted from a members profile. The following additional options are available: +
      +
    • + Warning Level for Member Watch
      + This setting defines the percentage warning level a member must reach to automatically assign a "watch" to the member. + Any member who is being "watched" will appear in the relevant area of the moderation center. +
    • +
    • + Warning Level for Post Moderation
      + Any member passing the value of this setting will find all their posts require moderator approval before they appear to the forum + community. This will override any local board permissions which may exist related to post moderation. +
    • +
    • + Warning Level for Member Muting
      + If this warning level is passed by a member they will find themselves under a post ban. The member will lose all posting rights. +
    • +
    • + Maximum Member Warning Point per Day
      + This setting limits the amount of points a moderator may add/remove to any particular member in a twenty four hour period. This will + can be used to limit what a moderator can do in a small period of time. This setting can be disabled by setting to a value of zero. Note that + any member with administrator permissions are not affected by this value. +
    • +
    '; +$helptxt['error_log'] = 'Error Log
    + The error log tracks logs every serious error encountered by users using your forum. It lists all of these errors by date which can be sorted + by clicking the black arrow next to each date. Additionally you can filter the errors by clicking the picture next to each error statistic. This + allows you to filter, for example, by member. When a filter is active the only results that will be displayed will be those that match that filter.'; +$helptxt['theme_settings'] = 'Theme Settings
    + The settings screen allows you to change settings specific to a theme. These settings include options such as the themes directory and URL information but + also options that affect the layout of a theme on your forum. Most themes will have a variety of user configurable options, allowing you to adapt a theme + to suit your individual forum needs.'; +$helptxt['smileys'] = 'Smiley Center
    + Here you can add and remove smileys, and smiley sets. Note importantly that if a smiley is in one set, it\'s in all sets - otherwise, it might + get confusing for your users using different sets.

    + + You are also able to edit message icons from here, if you have them enabled on the settings page.'; +$helptxt['calendar'] = 'Manage Calendar
    + Here you can modify the current calendar settings as well as add and remove holidays that appear on the calendar.'; + +$helptxt['serversettings'] = 'Server Settings
    + Here you can perform the core configuration for your forum. This section includes the database and url settings, as well as other + important configuration items such as mail settings and caching. Think carefully whenever editing these settings as an error may + render the forum inaccessible'; +$helptxt['manage_files'] = ' +
      +
    • + Browse Files
      + Browse through all the attachments, avatars and thumbnails stored by SMF.

      +
    • + Attachment Settings
      + Configure where attachments are stored and set restrictions on the types of attachments.

      +
    • + Avatar Settings
      + Configure where avatars are stored and manage resizing of avatars.

      +
    • + File Maintenance
      + Check and repair any error in the attachment directory and delete selected attachments.

      +
    • +
    '; + +$helptxt['topicSummaryPosts'] = 'This allows you to set the number of previous posts shown in the topic summary at the reply screen.'; +$helptxt['enableAllMessages'] = 'Set this to the maximum number of posts a topic can have to show the all link. Setting this lower than "Maximum messages to display in a topic page" will simply mean it never gets shown, and setting it too high could slow down your forum.'; +$helptxt['enableStickyTopics'] = 'Stickies are topics that remain on top of the topic list. They are mostly used for important + messages. Although you can change this with permissions, by default only moderators and administrators can make topics sticky.'; +$helptxt['allow_guestAccess'] = 'Unchecking this box will stop guests from doing anything but very basic actions - login, register, password reminder, etc. - on your forum. This is not the same as disallowing guest access to boards.'; +$helptxt['userLanguage'] = 'Turning this option on will allow users to select which language file they use. It will not affect the + default selection.'; +$helptxt['trackStats'] = 'Stats:
    This will allow users to see the latest posts and the most popular topics on your forum. + It will also show several statistics, like the most members online, new members and new topics.
    + Page views:
    Adds another column to the stats page with the number of pageviews on your forum.'; +$helptxt['titlesEnable'] = 'Switching Custom Titles on will allow members with the relevant permission to create a special title for themselves. + This will be shown underneath the name.
    For example:
    Jeff
    Cool Guy'; +$helptxt['topbottomEnable'] = 'This will add go up and go down buttons, so that member can go to the top and bottom of a page + without scrolling.'; +$helptxt['onlineEnable'] = 'This will show an image to indicate whether the member is online or offline'; +$helptxt['todayMod'] = 'This will show "Today" or "Yesterday" instead of the date.

    + Examples:

    +
    +
    Disabled
    +
    October 3, 2009 at 12:59:18 am
    +
    Only Today
    +
    Today at 12:59:18 am
    +
    Today & Yesterday
    +
    Yesterday at 09:36:55 pm
    + '; +$helptxt['disableCustomPerPage'] = 'Check this option to stop users from customizing the amount of messages and topics to display per page on the Message Index and Topic Display page respectively.'; +$helptxt['enablePreviousNext'] = 'This will show a link to the next and previous topic.'; +$helptxt['pollMode'] = 'This selects whether polls are enabled or not. If polls are disabled, any existing polls will be hidden + from the topic listing. You can choose to continue to show the regular topic without their polls by selecting + "Show Existing Polls as Topics".

    To choose who can post polls, view polls, and similar, you + can allow and disallow those permissions. Remember this if polls are not working.'; +$helptxt['enableVBStyleLogin'] = 'This will show a more compact login on every page of the forum for guests.'; +$helptxt['enableCompressedOutput'] = 'This option will compress output to lower bandwidth consumption, but it requires + zlib to be installed.'; +$helptxt['disableTemplateEval'] = 'By default, templates are evaluated instead of just included. This helps with showing more useful debug information in case a template contains an error.

    + On large forums however, this customised inclusion process may be significantly slower. Therefore, advanced users may wish to disable it.'; +$helptxt['databaseSession_enable'] = 'This option makes use of the database for session storage - it is best for load balanced servers, but helps with all timeout issues and can make the forum faster.'; +$helptxt['databaseSession_loose'] = 'Turning this on will decrease the bandwidth your forum uses, and make it so clicking back will not reload the page - the downside is that the (new) icons won\'t update, among other things. (unless you click to that page instead of going back to it.)'; +$helptxt['databaseSession_lifetime'] = 'This is the number of seconds for sessions to last after they haven\'t been accessed. If a session is not accessed for too long, it is said to have "timed out". Anything higher than 2400 is recommended.'; +$helptxt['enableErrorLogging'] = 'This will log any errors, like a failed login, so you can see what went wrong.'; +$helptxt['enableErrorQueryLogging'] = 'This will include the full query sent to the database in the error log. Requires error logging to be turned on.

    Note: This will affect the ability to filter the error log by the error message.'; +$helptxt['allow_disableAnnounce'] = 'This will allow users to opt out of notification of topics you announce by checking the "announce topic" checkbox when posting.'; +$helptxt['disallow_sendBody'] = 'This option removes the option to receive the text of replies and posts in notification emails.

    Often, members will reply to the notification email, which in most cases means the webmaster receives the reply.'; +$helptxt['compactTopicPagesEnable'] = 'This will just show a selection of the number of pages.
    Example: + "3" to display: 1 ... 4 [5] 6 ... 9
    + "5" to display: 1 ... 3 4 [5] 6 7 ... 9'; +$helptxt['timeLoadPageEnable'] = 'This will show the time in seconds SMF took to create that page at the bottom of the board.'; +$helptxt['removeNestedQuotes'] = 'This will strip nested quotes from a post when citing the post in question via a quote link.'; +$helptxt['simpleSearch'] = 'This will show a simple search form and a link to a more advanced form.'; +$helptxt['max_image_width'] = 'This allows you to set a maximum size for posted pictures. Pictures smaller than the maximum will not be affected.'; +$helptxt['mail_type'] = 'This setting allows you to choose either PHP\'s default settings, or to override those settings with SMTP. PHP doesn\'t support using authentication with SMTP (which many hosts require, now) so if you want that you should select SMTP. Please note that SMTP can be slower, and some servers will not take usernames and passwords.

    You don\'t need to fill in the SMTP settings if this is set to PHP\'s default.'; +$helptxt['attachment_manager_settings'] = 'Attachments are files that members can upload, and attach to a post.

    + Check attachment extension:
    Do you want to check the extension of the files?
    + Allowed attachment extensions:
    You can set the allowed extensions of attached files.
    + Attachments directory:
    The path to your attachment folder
    (example: /home/sites/yoursite/www/forum/attachments)
    + Max attachment folder space (in KB):
    Select how large the attachment folder can be, including all files within it.
    + Max attachment size per post (in KB):
    Select the maximum filesize of all attachments made per post. If this is lower than the per-attachment limit, this will be the limit.
    + Max size per attachment (in KB):
    Select the maximum filesize of each separate attachment.
    + Max number of attachments per post:
    Select the number of attachments a person can make, per post.
    + Display attachment as picture in posts:
    If the uploaded file is a picture, this will show it underneath the post.
    + Resize images when showing under posts:
    If the above option is selected, this will save a separate (smaller) attachment for the thumbnail to decrease bandwidth.
    + Maximum width and height of thumbnails:
    Only used with the "Resize images when showing under posts" option, the maximum width and height to resize attachments down from. They will be resized proportionally.'; +$helptxt['attachment_image_paranoid'] = 'Selecting this option will enable very strict security checks on image attachments. Warning! These extensive checks can fail on valid images too. It is strongly recommended to only use this option together with image re-encoding, in order to have SMF try to resample the images which fail the security checks: if successful, they will be sanitized and uploaded. Otherwise, if image re-encoding is not enabled, all attachments failing checks will be rejected.'; +$helptxt['attachment_image_reencode'] = 'Selecting this option will enable trying to re-encode the uploaded image attachments. Image re-encoding offers better security. Note however that image re-encoding also renders all animated images static.
    This feature is only possible if the GD module is installed on your server.'; +$helptxt['avatar_paranoid'] = 'Selecting this option will enable very strict security checks on avatars. Warning! These extensive checks can fail on valid images too. It is strongly recommended to only use this option together with avatars re-encoding, in order to have SMF try to resample the images which fail the security checks: if successful, they will be sanitized and uploaded. Otherwise, if re-encoding of avatars is not enabled, all avatars failing checks will be rejected.'; +$helptxt['avatar_reencode'] = 'Selecting this option will enable trying to re-encode the uploaded avatars. Image re-encoding offers better security. Note however that image re-encoding also renders all animated images static.
    This feature is only possible if the GD module is installed on your server.'; +$helptxt['karmaMode'] = 'Karma is a feature that shows the popularity of a member. Members, if allowed, can + \'applaud\' or \'smite\' other members, which is how their popularity is calculated. You can change the + number of posts needed to have a "karma", the time between smites or applauds, and if administrators + have to wait this time as well.

    Whether or not groups of members can smite others is controlled by + a permission. If you have trouble getting this feature to work for everyone, double check your permissions.'; +$helptxt['cal_enabled'] = 'The calendar can be used for showing birthdays, or for showing important moments happening in your community.

    + Show days as link to \'Post Event\':
    This will allow members to post events for that day, when they click on that date
    + Max days in advance on board index:
    If this is set to 7, the next week\'s worth of events will be shown.
    + Show holidays on board index:
    Show today\'s holidays in a calendar bar on the board index.
    + Show birthdays on board index:
    Show today\'s birthdays in a calendar bar on the board index.
    + Show events on board index:
    Show today\'s events in a calendar bar on the board index.
    + Default Board to Post In:
    What\'s the default board to post events in?
    + Allow events not linked to posts:
    Allow members to post events without requiring it to be linked with a post in a board.
    + Minimum year:
    Select the "first" year on the calendar list.
    + Maximum year:
    Select the "last" year on the calendar list
    + Allow events to span multiple days:
    Check to allow events to span multiple days.
    + Max number of days an event can span:
    Select the maximum days that an event can span.

    + Remember that usage of the calendar (posting events, viewing events, etc.) is controlled by permissions set on the permissions screen.'; +$helptxt['localCookies'] = 'SMF uses cookies to store login information on the client computer. + Cookies can be stored globally (myserver.com) or locally (myserver.com/path/to/forum).
    + Check this option if you\'re experiencing problems with users getting logged out automatically.
    + Globally stored cookies are less secure when used on a shared webserver (like Tripod).
    + Local cookies don\'t work outside the forum folder so, if your forum is stored at www.myserver.com/forum, pages like www.myserver.com/index.php cannot access the account information. + Especially when using SSI.php, global cookies are recommended.'; +$helptxt['enableBBC'] = 'Selecting this option will allow your members to use Bulletin Board Code (BBC) throughout the forum, allowing users to format their posts with images, type formatting and more.'; +$helptxt['time_offset'] = 'Not all forum administrators want their forum to use the same time zone as the server upon which it is hosted. Use this option to specify a time difference (in hours) from which the forum should operate from the server time. Negative and decimal values are permitted.'; +$helptxt['default_timezone'] = 'The server timezone tells PHP where your server is located. You should ensure this is set correctly, preferably to the country/city in which the city is located. You can find out more information on the PHP Site.'; +$helptxt['spamWaitTime'] = 'Here you can select the amount of time that must pass between postings. This can be used to stop people from "spamming" your forum by limiting how often they can post.'; + +$helptxt['enablePostHTML'] = 'This will allow the posting of some basic HTML tags: +
      +
    • <b>, <u>, <i>, <s>, <em>, <ins>, <del>
    • +
    • <a href="">
    • +
    • <img src="" alt="" />
    • +
    • <br />, <hr />
    • +
    • <pre>, <blockquote>
    • +
    '; + +$helptxt['themes'] = 'Here you can select whether the default theme can be chosen, what theme guests will use, + as well as other options. Click on a theme to the right to change the settings for it.'; +$helptxt['theme_install'] = 'This allows you to install new themes. You can do this from an already created directory, by uploading an archive for the theme, or by copying the default theme.

    Note that the archive or directory must have a theme_info.xml definition file.'; +$helptxt['enableEmbeddedFlash'] = 'This option will allow your users to use Flash directly inside their posts, + just like images. This could pose a security risk, although few have successfully exploited it. + USE AT YOUR OWN RISK!'; +// !!! Add more information about how to use them here. +$helptxt['xmlnews_enable'] = 'Allows people to link to Recent news + and similar data. It is also recommended that you limit the size of recent posts/news because, when rss data + is displayed in some clients, like Trillian, it is expected to be truncated.'; +$helptxt['hotTopicPosts'] = 'Change the number of posts for a topic to reach the state of a "hot" or + "very hot" topic.'; +$helptxt['globalCookies'] = 'Makes log in cookies available across subdomains. For example, if...
    + Your site is at http://www.simplemachines.org/,
    + And your forum is at http://forum.simplemachines.org/,
    + Using this option will allow you to access the forum\'s cookie on your site. Do not enable this if there are other subdomains (like hacker.simplemachines.org) not controlled by you.'; +$helptxt['secureCookies'] = 'Enabling this option will force the cookies created for users on your forum to be marked as secure. Only enable this option if you are using HTTPS throughout your site as it will break cookie handling otherwise!'; +$helptxt['securityDisable'] = 'This disables the additional password check for the administration section. This is not recommended!'; +$helptxt['securityDisable_why'] = 'This is your current password. (the same one you use to login.)

    Having to type this helps ensure that you want to do whatever administration you are doing, and that it is you doing it.'; +$helptxt['emailmembers'] = 'In this message you can use a few "variables". These are:
    + {$board_url} - The URL to your forum.
    + {$current_time} - The current time.
    + {$member.email} - The current member\'s email.
    + {$member.link} - The current member\'s link.
    + {$member.id} - The current member\'s id.
    + {$member.name} - The current member\'s name. (for personalization.)
    + {$latest_member.link} - The most recently registered member\'s link.
    + {$latest_member.id} - The most recently registered member\'s id.
    + {$latest_member.name} - The most recently registered member\'s name.'; +$helptxt['attachmentEncryptFilenames'] = 'Encrypting attachment filenames allows you to have more than one attachment of the + same name, to safely use .php files for attachments, and heightens security. It, however, could make it more + difficult to rebuild your database if something drastic happened.'; + +$helptxt['failed_login_threshold'] = 'Set the number of failed login attempts before directing the user to the password reminder screen.'; +$helptxt['oldTopicDays'] = 'If this option is enabled a warning will be displayed to the user when attempting to reply to a topic which has not had any new replies for the amount of time, in days, specified by this setting. Set this setting to 0 to disable the feature.'; +$helptxt['edit_wait_time'] = 'Number of seconds allowed for a post to be edited before logging the last edit date.'; +$helptxt['edit_disable_time'] = 'Number of minutes allowed to pass before a user can no longer edit a post they have made. Set to 0 disable.

    Note: This will not affect any user who has permission to edit other people\'s posts.'; +$helptxt['posts_require_captcha'] = 'This setting will force users to pass anti-spam bot verification each time they make a post to a board. Only users with a post count below the number set will need to enter the code - this should help combat automated spamming scripts.'; +$helptxt['enableSpellChecking'] = 'Enable spell checking. You MUST have the pspell library installed on your server and your PHP configuration set up to use the pspell library. Your server ' . (function_exists('pspell_new') ? 'DOES' : 'DOES NOT') . ' appear to have this set up.'; +$helptxt['disable_wysiwyg'] = 'This setting disallows all users from using the WYSIWYG ("What You See Is What You Get") editor on the post page.'; +$helptxt['lastActive'] = 'Set the number of minutes to show people are active in X number of minutes on the board index. Default is 15 minutes.'; + +$helptxt['customoptions'] = 'This section defines the options that a user may choose from a drop down list. There are a few key points to note in this section: +
      +
    • Default Option: Whichever option box has the "radio button" next to it selected will be the default selection for the user when they enter their profile.
    • +
    • Removing Options: To remove an option simply empty the text box for that option - all users with that selected will have their option cleared.
    • +
    • Reordering Options: You can reorder the options by moving text around between the boxes. However - an important note - you must make sure you do not change the text when reordering options as otherwise user data will be lost.
    • +
    '; + +$helptxt['autoOptDatabase'] = 'This option optimizes the database every so many days. Set it to 1 to make a daily optimization. You can also specify a maximum number of online users, so that you won\'t overload your server or inconvenience too many users.'; +$helptxt['autoFixDatabase'] = 'This will automatically fix broken tables and resume like nothing happened. This can be useful, because the only way to fix it is to REPAIR the table, and this way your forum won\'t be down until you notice. It does email you when this happens.'; + +$helptxt['enableParticipation'] = 'This shows a little icon on the topics the user has posted in.'; + +$helptxt['db_persist'] = 'Keeps the connection active to increase performance. If you aren\'t on a dedicated server, this may cause you problems with your host.'; +$helptxt['ssi_db_user'] = 'Optional setting to use a different database user and password when you are using SSI.php.'; + +$helptxt['queryless_urls'] = 'This changes the format of URLs a little so search engines will like them better. They will look like index.php/topic,1.0.html.

    This feature will ' . (isset($_SERVER['SERVER_SOFTWARE']) && (strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false || strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false) ? '' : 'not') . ' work on your server.'; +$helptxt['countChildPosts'] = 'Checking this option will mean that posts and topics in a board\'s child board will count toward its totals on the index page.

    This will make things notably slower, but means that a parent with no posts in it won\'t show \'0\'.'; +$helptxt['fixLongWords'] = 'This option breaks words longer than a certain length into pieces so they don\'t disturb the forum\'s layout. (as much...) This option should not be set to a value under 40. This option will not work with forums using UTF-8 and PHP less than 4.4.0. This ' . (empty($GLOBALS['context']['utf8']) || version_compare(PHP_VERSION, '4.4.0') != -1 ? 'WILL' : 'WILL NOT') . ' work on your server'; +$helptxt['allow_ignore_boards'] = 'Checking this option will allow users to select boards they wish to ignore.'; + +$helptxt['who_enabled'] = 'This option allows you to turn on or off the ability for users to see who is browsing the forum and what they are doing.'; + +$helptxt['recycle_enable'] = '"Recycles" deleted topics and posts to the specified board.'; + +$helptxt['enableReportPM'] = 'This option allows your users to report personal messages they receive to the administration team. This may be useful in helping to track down any abuse of the personal messaging system.'; +$helptxt['max_pm_recipients'] = 'This option allows you to set the maximum amount of recipients allowed in a single personal message sent by a forum member. This may be used to help stop spam abuse of the PM system. Note that users with permission to send newsletters are exempt from this restriction. Set to zero for no limit.'; +$helptxt['pm_posts_verification'] = 'This setting will force users to enter a code shown on a verification image each time they are sending a personal message. Only users with a post count below the number set will need to enter the code - this should help combat automated spamming scripts.'; +$helptxt['pm_posts_per_hour'] = 'This will limit the number of personal messages which may be sent by a user in a one hour period. This does not affect admins or moderators.'; + +$helptxt['default_personal_text'] = 'Sets the default text a new user will have as their "personal text."'; + +$helptxt['modlog_enabled'] = 'Logs all moderation actions.'; + +$helptxt['guest_hideContacts'] = 'If selected this option will hide the email addresses and messenger contact details + of all members from any guests on your forum'; + +$helptxt['registration_method'] = 'This option determines what method of registration is used for people wishing to join your forum. You can select from:

    +
      +
    • + Registration Disabled
      + Disables the registration process, which means that no new members can register to join your forum.
      +
    • + Immediate Registration
      + New members can login and post immediately after registering on your forum.
      +
    • + Email Activation
      + When this option is enabled any members registering with the forum will have an activation link emailed to them which they must click before they can become full members.
      +
    • + Admin Approval
      + This option will make it so all new members registering to your forum will need to be approved by the admin before they become members. +
    • +
    '; +$helptxt['register_openid'] = 'Authenticate with OpenID
    + OpenID is a means of using one username across different websites, to simplify the online experience. To use OpenID you first need to create an OpenID account - a list of providers can be found on the OpenID Official Site

    + Once you have an OpenID account simply enter your unique identification URL into the OpenID input box and submit. You will then be taken to your providers site to verify your identity before being passed back to this site.

    + On your first visit to this site you will be asked to confirm a couple of details before you will be recognized, after which you can login to this site and change your profile settings using just your OpenID.

    + For more information please visit the OpenID Official Site'; + +$helptxt['send_validation_onChange'] = 'When this option is checked all members who change their email address in their profile will have to reactivate their account from an email sent to that address'; +$helptxt['send_welcomeEmail'] = 'When this option is enabled all new members will be sent an email welcoming them to your community'; +$helptxt['password_strength'] = 'This setting determines the strength required for passwords selected by your forum users. The stronger the password, the harder it should be to compromise member\'s accounts. + Its possible options are: +
      +
    • Low: The password must be at least four characters long.
    • +
    • Medium: The password must be at least eight characters long, and can not be part of a users name or email address.
    • +
    • High: As for medium, except the password must also contain a mixture of upper and lower case letters, and at least one number.
    • +
    '; + +$helptxt['coppaAge'] = 'The value specified in this box will determine the minimum age that new members must be to be granted immediate access to the forums. + On registration they will be prompted to confirm whether they are over this age, and if not will either have their application rejected or suspended awaiting parental approval - dependant on the type of restriction chosen. + If a value of 0 is chosen for this setting then all other age restriction settings shall be ignored.'; +$helptxt['coppaType'] = 'If age restrictions are enabled, then this setting will define that happens when a user below the minimum age attempts to register with your forum. There are two possible choices: +
      +
    • + Reject Their Registration:
      + Any new member below the minimum age will have their registration rejected immediately.
      +
    • + Require Parent/Guardian Approval
      + Any new member who attempts to register and is below the minimum permitted age will have their account marked as awaiting approval, and will be presented with a form upon which their parents must give permission to become a member of the forums. + They will also be presented with the forum contact details entered on the settings page, so they can send the form to the administrator by mail or fax. +
    • +
    '; +$helptxt['coppaPost'] = 'The contact boxes are required so that forms granting permission for underage registration can be sent to the forum administrator. These details will be shown to all new minors, and are required for parent/guardian approval. At the very least a postal address or fax number must be provided.'; + +$helptxt['allow_hideOnline'] = 'With this option enabled all members will be able to hide their online status from other users (except administrators). If disabled only users who can moderate the forum can hide their presence. Note that disabling this option will not change any existing member\'s status - it just stops them from hiding themselves in the future.'; +$helptxt['make_email_viewable'] = 'If this option is enabled instead of users email addresses being hidden to normal members and guests they will be publicly viewable on the forum. Enabling this will put your users at greater risk of being victims of spam as a result of email harvesters visiting your forum. Note this setting does not override the user setting for hiding their email address from users. Enabling this setting is not recommended.'; +$helptxt['meta_keywords'] = 'These keywords are sent in the output of every page to indicate to search engines (etc) the key content of your site. They should be a comma separated list of words, and should not use HTML.'; + +$helptxt['latest_support'] = 'This panel shows you some of the most common problems and questions on your server configuration. Don\'t worry, this information isn\'t logged or anything.

    If this stays as "Retrieving support information...", your computer probably cannot connect to www.simplemachines.org.'; +$helptxt['latest_packages'] = 'Here you can see some of the most popular and some random packages or mods, with quick and easy installations.

    If this section doesn\'t show up, your computer probably cannot connect to www.simplemachines.org.'; +$helptxt['latest_themes'] = 'This area shows a few of the latest and most popular themes from www.simplemachines.org. It may not show up properly if your computer can\'t find www.simplemachines.org, though.'; + +$helptxt['secret_why_blank'] = 'For your security, your password and the answer to your secret question are encrypted so that the SMF software will never tell you, or anyone else, what they are.'; +$helptxt['moderator_why_missing'] = 'Since moderation is done on a board-by-board basis, you have to make members moderators from the board management interface.'; + +$helptxt['permissions'] = 'Permissions are how you either allow groups to, or deny groups from, doing specific things.

    You can modify multiple boards at once with the checkboxes, or look at the permissions for a specific group by clicking \'Modify.\''; +$helptxt['permissions_board'] = 'If a board is set to \'Global,\' it means that the board will not have any special permissions. \'Local\' means it will have its own permissions - separate from the global ones. This allows you to have a board that has more or less permissions than another, without requiring you to set them for each and every board.'; +$helptxt['permissions_quickgroups'] = 'These allow you to use the "default" permission setups - standard means \'nothing special\', restrictive means \'like a guest\', moderator means \'what a moderator has\', and lastly \'maintenance\' means permissions very close to those of an administrator.'; +$helptxt['permissions_deny'] = 'Denying permissions can be useful when you want take away permission from certain members. You can add a membergroup with a \'deny\'-permission to the members you wish to deny a permission.

    Use with care, a denied permission will stay denied no matter what other membergroups the member is in.'; +$helptxt['permissions_postgroups'] = 'Enabling permissions for post count based groups will allow you to attribute permissions to members that have posted a certain amount of messages. The permissions of the post count based groups are added to the permissions of the regular membergroups.'; +$helptxt['membergroup_guests'] = 'The Guests membergroup are all users that are not logged in.'; +$helptxt['membergroup_regular_members'] = 'The Regular Members are all members that are logged in, but that have no primary membergroup assigned.'; +$helptxt['membergroup_administrator'] = 'The administrator can, per definition, do anything and see any board. There are no permission settings for the administrator.'; +$helptxt['membergroup_moderator'] = 'The Moderator membergroup is a special membergroup. Permissions and settings assigned to this group apply to moderators but only on the boards they moderate. Outside these boards they\'re just like any other member.'; +$helptxt['membergroups'] = 'In SMF there are two types of groups that your members can be part of. These are: +
      +
    • Regular Groups: A regular group is a group to which members are not automatically put into. To assign a member to be in a group simply go to their profile and click "Account Settings". From here you can assign them any number of regular groups to which they will be part.
    • +
    • Post Groups: Unlike regular groups post based groups cannot be assigned. Instead, members are automatically assigned to a post based group when they reach the minimum number of posts required to be in that group.
    • +
    '; + +$helptxt['calendar_how_edit'] = 'You can edit these events by clicking on the red asterisk (*) next to their names.'; + +$helptxt['maintenance_backup'] = 'This area allows you to save a copy of all the posts, settings, members, and other information in your forum to a very large file.

    It is recommended that you do this often, perhaps weekly, for safety and security.'; +$helptxt['maintenance_rot'] = 'This allows you to completely and irrevocably remove old topics. It is recommended that you try to make a backup first, just in case you remove something you didn\'t mean to.

    Use this option with care.'; +$helptxt['maintenance_members'] = 'This allows you to completely and irrevocably remove member accounts from your forum. It is highly recommended that you try to make a backup first, just in case you remove something you didn\'t mean to.

    Use this option with care.'; + +$helptxt['avatar_server_stored'] = 'This allows your members to pick from avatars stored on your server itself. They are, generally, in the same place as SMF under the avatars folder.
    As a tip, if you create directories in that folder, you can make "categories" of avatars.'; +$helptxt['avatar_external'] = 'With this enabled, your members can type in a URL to their own avatar. The downside of this is that, in some cases, they may use avatars that are overly large or portray images you don\'t want on your forum.'; +$helptxt['avatar_download_external'] = 'With this option enabled, the URL given by the user is accessed to download the avatar at that location. On success, the avatar will be treated as uploadable avatar.'; +$helptxt['avatar_upload'] = 'This option is much like "Allow members to select an external avatar", except that you have better control over the avatars, a better time resizing them, and your members do not have to have somewhere to put avatars.

    However, the downside is that it can take a lot of space on your server.'; +$helptxt['avatar_download_png'] = 'PNGs are larger, but offer better quality compression. If this is unchecked, JPEG will be used instead - which is often smaller, but also of lesser or blurry quality.'; + +$helptxt['disableHostnameLookup'] = 'This disables host name lookups, which on some servers are very slow. Note that this will make banning less effective.'; + +$helptxt['search_weight_frequency'] = 'Weight factors are used to determine the relevancy of a search result. Change these weight factors to match the things that are specifically important for your forum. For instance, a forum of a news site, might want a relatively high value for \'age of last matching message\'. All values are relative in relation to each other and should be positive integers.

    This factor counts the amount of matching messages and divides them by the total number of messages within a topic.'; +$helptxt['search_weight_age'] = 'Weight factors are used to determine the relevancy of a search result. Change these weight factors to match the things that are specifically important for your forum. For instance, a forum of a news site, might want a relatively high value for \'age of last matching message\'. All values are relative in relation to each other and should be positive integers.

    This factor rates the age of the last matching message within a topic. The more recent this message is, the higher the score.'; +$helptxt['search_weight_length'] = 'Weight factors are used to determine the relevancy of a search result. Change these weight factors to match the things that are specifically important for your forum. For instance, a forum of a news site, might want a relatively high value for \'age of last matching message\'. All values are relative in relation to each other and should be positive integers.

    This factor is based on the topic size. The more messages are within the topic, the higher the score.'; +$helptxt['search_weight_subject'] = 'Weight factors are used to determine the relevancy of a search result. Change these weight factors to match the things that are specifically important for your forum. For instance, a forum of a news site, might want a relatively high value for \'age of last matching message\'. All values are relative in relation to each other and should be positive integers.

    This factor looks whether a search term can be found within the subject of a topic.'; +$helptxt['search_weight_first_message'] = 'Weight factors are used to determine the relevancy of a search result. Change these weight factors to match the things that are specifically important for your forum. For instance, a forum of a news site, might want a relatively high value for \'age of last matching message\'. All values are relative in relation to each other and should be positive integers.

    This factor looks whether a match can be found in the first message of a topic.'; +$helptxt['search_weight_sticky'] = 'Weight factors are used to determine the relevancy of a search result. Change these weight factors to match the things that are specifically important for your forum. For instance, a forum of a news site, might want a relatively high value for \'age of last matching message\'. All values are relative in relation to each other and should be positive integers.

    This factor looks whether a topic is sticky and increases the relevancy score if it is.'; +$helptxt['search'] = 'Adjust all settings for the search function here.'; +$helptxt['search_why_use_index'] = 'A search index can greatly improve the performance of searches on your forum. Especially when the number of messages on a forum grows bigger, searching without an index can take a long time and increase the pressure on your database. If your forum is bigger than 50.000 messages, you might want to consider creating a search index to assure peak performance of your forum.

    Note that a search index can take up quite some space. A fulltext index is a built-in index of MySQL. It\'s relatively compact (approximately the same size as the message table), but a lot of words aren\'t indexed and it can, in some search queries, turn out to be very slow. The custom index is often bigger (depending on your configuration it can be up to 3 times the size of the messages table) but it\'s performance is better than fulltext and relatively stable.'; + +$helptxt['see_admin_ip'] = 'IP addresses are shown to administrators and moderators to facilitate moderation and to make it easier to track people up to no good. Remember that IP addresses may not always be identifying, and most people\'s IP addresses change periodically.

    Members are also allowed to see their own IPs.'; +$helptxt['see_member_ip'] = 'Your IP address is shown only to you and moderators. Remember that this information is not identifying, and that most IPs change periodically.

    You cannot see other members\' IP addresses, and they cannot see yours.'; +$helptxt['whytwoip'] = 'SMF uses various methods to detect user IP addresses. Usually these two methods result in the same address but in some cases more than one address may be detected. In this case SMF logs both addresses, and uses them both for ban checks (etc). You can click on either address to track that IP and ban if necessary.'; + +$helptxt['ban_cannot_post'] = 'The \'cannot post\' restriction turns the forum into read-only mode for the banned user. The user cannot create new topics, or reply to existing ones, send personal messages or vote in polls. The banned user can however still read personal messages and topics.

    A warning message is shown to the users that are banned this way.'; + +$helptxt['posts_and_topics'] = ' +
      +
    • + Post Settings
      + Modify the settings related to the posting of messages and the way messages are shown. You can also enable the spell check here. +
    • + Bulletin Board Code
      + Enable the code that shows forum messages in the right layout. Also adjust which codes are allowed and which aren\'t. +
    • + Censored Words + In order to keep the language on your forum under control, you can censor certain words. This function allows you to convert forbidden words into innocent versions. +
    • + Topic Settings + Modify the settings related to topics. The number of topics per page, whether sticky topics are enabled or not, the number of messages needed for a topic to be hot, etc. +
    • +
    '; +$helptxt['spider_group'] = 'By selecting a restrictive group, when a guest is detected as a search crawler it will automatically be assigned any "deny" deny permissions of this group in addition to the normal permissions of a guest. You can use this to provide lesser access to a search engine than you would a normal guest. You might for example wish to create a new group called "Spiders" and select that here. You could then deny permission for that group to view profiles to stop spiders indexing your members profiles.
    Note: Spider detection is not perfect and can be simulated by users so this feature is not guaranteed to restrict content only to those search engines you have added.'; +$helptxt['show_spider_online'] = 'This setting allows you to select whether spiders should be listed in the who\'s online list on the board index and "Who\'s Online" page. Options are: +
      +
    • + Not at All
      + Spiders will simply appear as guests to all users. +
    • + Show Spider Quantity
      + The Board Index will display the number of spiders currently visiting the forum. +
    • + Show Spider Names
      + Each spider name will be revealed, so users can see how many of each spider is currently visiting the forum - this takes effect in both the Board Index and Who\'s Online page. +
    • + Show Spider Names - Admin Only
      + As above except only Administrators can see spider status - to all other users spiders appear as guests. +
    • +
    '; + +$helptxt['birthday_email'] = 'Choose the index of the birthday email message to use. A preview will be shown in the Email Subject and Email Body fields.
    Note: Setting this option does not automatically enable birthday emails. To enable birthday emails use the Scheduled Tasks page and enable the birthday email task.'; +$helptxt['pm_bcc'] = 'When sending a personal message you can choose to add a recipient as BCC or "Blind Carbon Copy". BCC recipients do not have their identities revealed to other recipients of the message.'; + +$helptxt['move_topics_maintenance'] = 'This will allow you to move all the posts from one board to another board.'; +$helptxt['maintain_reattribute_posts'] = 'You can use this function to attribute guest posts on your board to a registered member. This is useful if, for example, a user deleted their account and changed their mind and wished to have their old posts associated with their account.'; +$helptxt['chmod_flags'] = 'You can manually set the permissions you wish to set the selected files to. To do this enter the chmod value as a numeric (octet) value. Note - these flags will have no effect on Microsoft Windows operating systems.'; + +$helptxt['postmod'] = 'This section allows members of the moderation team (with sufficient permissions) to approve any posts and topics before they are shown.'; + +$helptxt['field_show_enclosed'] = 'Encloses the user input between some text or html. This will allow you to add more instant message providers, images or an embed etc. For example:

    + <a href="http://website.com/{INPUT}"><img src="{DEFAULT_IMAGES_URL}/icon.gif" alt="{INPUT}" /></a>

    + Note that you can use the following variables:
    +
      +
    • {INPUT} - The input specified by the user.
    • +
    • {SCRIPTURL} - Web address of forum.
    • +
    • {IMAGES_URL} - Url to images folder in the users current theme.
    • +
    • {DEFAULT_IMAGES_URL} - Url to the images folder in the default theme.
    • +
    '; + +$helptxt['custom_mask'] = 'The input mask is important for your forum\'s security. Validating the input from a user can help ensure that data is not used in a way you do not expect. We have provided some simple regular expressions as hints.

    +
    + "[A-Za-z]+" - Match all upper and lower case alphabet characters.
    + "[0-9]+" - Match all numeric characters.
    + "[A-Za-z0-9]{7}" - Match all upper and lower case alphabet and numeric characters seven times.
    + "[^0-9]?" - Forbid any number from being matched.
    + "^([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$" - Only allow 3 or 6 character hexcodes.
    +


    + Additionally, special metacharacters ?+*^$ and {xx} can be defined. +
    + ? - None or one match of previous expression.
    + + - One or more of previous expression.
    + * - None or more of previous expression.
    + {xx} - An exact number from previous expression.
    + {xx,} - An exact number or more from previous expression.
    + {,xx} - An exact number or less from previous expression.
    + {xx,yy} - An exact match between the two numbers from previous expression.
    + ^ - Start of string.
    + $ - End of string.
    + \ - Escapes the next character.
    +


    + More information and advanced techniques may be found on the internet.'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/Install.english.php b/Themes/default/languages/Install.english.php new file mode 100644 index 0000000..ced98ca --- /dev/null +++ b/Themes/default/languages/Install.english.php @@ -0,0 +1,235 @@ +help is available if you need it.'; +$txt['still_writable'] = 'Your installation directory is still writable. It\'s a good idea to chmod it so that it is not writable for security reasons.'; +$txt['delete_installer'] = 'Click here to delete this install.php file now.'; +$txt['delete_installer_maybe'] = '(doesn\'t work on all servers.)'; +$txt['go_to_your_forum'] = 'Now you can see your newly installed forum and begin to use it. You should first make sure you are logged in, after which you will be able to access the administration center.'; +$txt['good_luck'] = 'Good luck!
    Simple Machines'; + +$txt['install_welcome'] = 'Welcome'; +$txt['install_welcome_desc'] = 'Welcome to SMF. This script will guide you through the process for installing %1$s. We\'ll gather a few details about your forum over the next few steps, and after a couple of minutes your forum will be ready for use.'; +$txt['install_all_lovely'] = 'We\'ve completed some initial tests on your server and everything appears to be in order. Simply click the "Continue" button below to get started.'; + +$txt['user_refresh_install'] = 'Forum Refreshed'; +$txt['user_refresh_install_desc'] = 'While installing, the installer found that (with the details you provided) one or more of the tables this installer might create already existed.
    Any missing tables in your installation have been recreated with the default data, but no data was deleted from existing tables.'; + +$txt['default_topic_subject'] = 'Welcome to SMF!'; +$txt['default_topic_message'] = 'Welcome to Simple Machines Forum!

    We hope you enjoy using your forum.  If you have any problems, please feel free to [url=http://www.simplemachines.org/community/index.php]ask us for assistance[/url].

    Thanks!
    Simple Machines'; +$txt['default_board_name'] = 'General Discussion'; +$txt['default_board_description'] = 'Feel free to talk about anything and everything in this board.'; +$txt['default_category_name'] = 'General Category'; +$txt['default_time_format'] = '%B %d, %Y, %I:%M:%S %p'; +$txt['default_news'] = 'SMF - Just Installed!'; +$txt['default_karmaLabel'] = 'Karma:'; +$txt['default_karmaSmiteLabel'] = '[smite]'; +$txt['default_karmaApplaudLabel'] = '[applaud]'; +$txt['default_reserved_names'] = 'Admin\nWebmaster\nGuest\nroot'; +$txt['default_smileyset_name'] = 'Alienine\'s Set'; +$txt['default_aaron_smileyset_name'] = 'Aaron\'s Set'; +$txt['default_akyhne_smileyset_name'] = 'Akyhne\'s Set'; +$txt['default_theme_name'] = 'SMF Default Theme - Curve'; +$txt['default_core_theme_name'] = 'Core Theme'; +$txt['default_classic_theme_name'] = 'Classic YaBB SE Theme'; +$txt['default_babylon_theme_name'] = 'Babylon Theme'; + +$txt['default_administrator_group'] = 'Administrator'; +$txt['default_global_moderator_group'] = 'Global Moderator'; +$txt['default_moderator_group'] = 'Moderator'; +$txt['default_newbie_group'] = 'Newbie'; +$txt['default_junior_group'] = 'Jr. Member'; +$txt['default_full_group'] = 'Full Member'; +$txt['default_senior_group'] = 'Sr. Member'; +$txt['default_hero_group'] = 'Hero Member'; + +$txt['default_smiley_smiley'] = 'Smiley'; +$txt['default_wink_smiley'] = 'Wink'; +$txt['default_cheesy_smiley'] = 'Cheesy'; +$txt['default_grin_smiley'] = 'Grin'; +$txt['default_angry_smiley'] = 'Angry'; +$txt['default_sad_smiley'] = 'Sad'; +$txt['default_shocked_smiley'] = 'Shocked'; +$txt['default_cool_smiley'] = 'Cool'; +$txt['default_huh_smiley'] = 'Huh?'; +$txt['default_roll_eyes_smiley'] = 'Roll Eyes'; +$txt['default_tongue_smiley'] = 'Tongue'; +$txt['default_embarrassed_smiley'] = 'Embarrassed'; +$txt['default_lips_sealed_smiley'] = 'Lips Sealed'; +$txt['default_undecided_smiley'] = 'Undecided'; +$txt['default_kiss_smiley'] = 'Kiss'; +$txt['default_cry_smiley'] = 'Cry'; +$txt['default_evil_smiley'] = 'Evil'; +$txt['default_azn_smiley'] = 'Azn'; +$txt['default_afro_smiley'] = 'Afro'; +$txt['default_laugh_smiley'] = 'Laugh'; +$txt['default_police_smiley'] = 'Police'; +$txt['default_angel_smiley'] = 'Angel'; + +$txt['error_message_click'] = 'Click here'; +$txt['error_message_try_again'] = 'to try this step again.'; +$txt['error_message_bad_try_again'] = 'to try installing anyway, but note that this is strongly discouraged.'; + +$txt['install_settings'] = 'Forum Settings'; +$txt['install_settings_info'] = 'This page requires you to define a few key settings for your forum. SMF has automatically detected key settings for you.'; +$txt['install_settings_name'] = 'Forum name'; +$txt['install_settings_name_info'] = 'This is the name of your forum, ie. "The Testing Forum".'; +$txt['install_settings_name_default'] = 'My Community'; +$txt['install_settings_url'] = 'Forum URL'; +$txt['install_settings_url_info'] = 'This is the URL to your forum without the trailing \'/\'!.
    In most cases, you can leave the default value in this box alone - it is usually right.'; +$txt['install_settings_compress'] = 'Gzip Output'; +$txt['install_settings_compress_title'] = 'Compress output to save bandwidth.'; +// In this string, you can translate the word "PASS" to change what it says when the test passes. +$txt['install_settings_compress_info'] = 'This function does not work properly on all servers, but can save you a lot of bandwidth.
    Click here to test it. (it should just say "PASS".)'; +$txt['install_settings_dbsession'] = 'Database Sessions'; +$txt['install_settings_dbsession_title'] = 'Use the database for sessions instead of using files.'; +$txt['install_settings_dbsession_info1'] = 'This feature is almost always for the best, as it makes sessions more dependable.'; +$txt['install_settings_dbsession_info2'] = 'This feature is generally a good idea, but may not work properly on this server.'; +$txt['install_settings_utf8'] = 'UTF-8 Character Set'; +$txt['install_settings_utf8_title'] = 'Use UTF-8 as default character set'; +$txt['install_settings_utf8_info'] = 'This feature lets both the database and the forum use an international character set, UTF-8. This can be useful when working with multiple languages that use different character sets.'; +$txt['install_settings_stats'] = 'Allow Stat Collection'; +$txt['install_settings_stats_title'] = 'Allow Simple Machines to Collect Basic Stats Monthly'; +$txt['install_settings_stats_info'] = 'If enabled, this will allow Simple Machines to visit your site once a month to collect basic statistics. This will help us make decisions as to which configurations to optimize the software for. For more information please visit our info page.'; +$txt['install_settings_proceed'] = 'Proceed'; + +$txt['db_settings'] = 'Database Server Settings'; +$txt['db_settings_info'] = 'These are the settings to use for your database server. If you don\'t know the values, you should ask your host what they are.'; +$txt['db_settings_type'] = 'Database type'; +$txt['db_settings_type_info'] = 'Multiple supported database types were detected - which do you wish to use. Please note that running pre-SMF 2.0 RC3 along with newer SMF versions in the same PostgreSQL database is not supported. You need to upgrade your older installations in that case.'; +$txt['db_settings_server'] = 'Server name'; +$txt['db_settings_server_info'] = 'This is nearly always localhost - so if you don\'t know, try localhost.'; +$txt['db_settings_username'] = 'Username'; +$txt['db_settings_username_info'] = 'Fill in the username you need to connect to your database here.
    If you don\'t know what it is, try the username of your ftp account, most of the time they are the same.'; +$txt['db_settings_password'] = 'Password'; +$txt['db_settings_password_info'] = 'Here, put the password you need to connect to your database.
    If you don\'t know this, you should try the password to your ftp account.'; +$txt['db_settings_database'] = 'Database name'; +$txt['db_settings_database_info'] = 'Fill in the name of the database you want to use for SMF to store its data in.'; +$txt['db_settings_database_info_note'] = 'If this database does not exist, this installer will try to create it.'; +$txt['db_settings_database_file'] = 'Database filename'; +$txt['db_settings_database_file_info'] = 'This is the name of the file in which to store the SMF data. We recommend you use the randomly generated name for this and set the path of this file to be outside of the public area of your webserver.'; +$txt['db_settings_prefix'] = 'Table prefix'; +$txt['db_settings_prefix_info'] = 'The prefix for every table in the database. Do not install two forums with the same prefix!
    This value allows for multiple installations in one database.'; +$txt['db_sqlite_warning'] = 'Only recommended for small, low volume and/or intranet-type forums'; +$txt['db_populate'] = 'Populated Database'; +$txt['db_populate_info'] = 'Your settings have now been saved and the database has been populated with all the data required to get your forum up and running. Summary of population:'; +$txt['db_populate_info2'] = 'Click "Continue" to progress to the admin account creation page.'; +$txt['db_populate_inserts'] = 'Inserted %1$d rows.'; +$txt['db_populate_tables'] = 'Created %1$d tables.'; +$txt['db_populate_insert_dups'] = 'Ignored %1$d duplicated inserts.'; +$txt['db_populate_table_dups'] = 'Ignored %1$d duplicated tables.'; + +$txt['user_settings'] = 'Create Your Account'; +$txt['user_settings_info'] = 'The installer will now create a new administrator account for you.'; +$txt['user_settings_username'] = 'Your username'; +$txt['user_settings_username_info'] = 'Choose the name you want to login with.
    This can\'t be changed later, but your display name can be.'; +$txt['user_settings_password'] = 'Password'; +$txt['user_settings_password_info'] = 'Fill in your preferred password here, and remember it well!'; +$txt['user_settings_again'] = 'Password'; +$txt['user_settings_again_info'] = '(just for verification.)'; +$txt['user_settings_email'] = 'Email Address'; +$txt['user_settings_email_info'] = 'Provide your email address as well. This must be a valid email address.'; +$txt['user_settings_database'] = 'Database Password'; +$txt['user_settings_database_info'] = 'The installer requires that you supply the database password to create an administrator account, for security reasons.'; +$txt['user_settings_skip'] = 'Skip'; +$txt['user_settings_skip_sure'] = 'Are you sure you wish to skip admin account creation?'; +$txt['user_settings_proceed'] = 'Finish'; + +$txt['ftp_checking_writable'] = 'Checking Files are Writable'; +$txt['ftp_setup'] = 'FTP Connection Information'; +$txt['ftp_setup_info'] = 'This installer can connect via FTP to fix the files that need to be writable and are not. If this doesn\'t work for you, you will have to go in manually and make the files writable. Please note that this doesn\'t support SSL right now.'; +$txt['ftp_server'] = 'Server'; +$txt['ftp_server_info'] = 'This should be the server and port for your FTP server.'; +$txt['ftp_port'] = 'Port'; +$txt['ftp_username'] = 'Username'; +$txt['ftp_username_info'] = 'The username to login with. This will not be saved anywhere.'; +$txt['ftp_password'] = 'Password'; +$txt['ftp_password_info'] = 'The password to login with. This will not be saved anywhere.'; +$txt['ftp_path'] = 'Install Path'; +$txt['ftp_path_info'] = 'This is the relative path you use in your FTP server.'; +$txt['ftp_path_found_info'] = 'The path in the box above was automatically detected.'; +$txt['ftp_connect'] = 'Connect'; +$txt['ftp_setup_why'] = 'What is this step for?'; +$txt['ftp_setup_why_info'] = 'Some files need to be writable for SMF to work properly. This step allows you to let the installer make them writable for you. However, in some cases it won\'t work - in that case, please make the following files 777 (writable, 755 on some hosts):'; +$txt['ftp_setup_again'] = 'to test if these files are writable again.'; + +$txt['error_php_too_low'] = 'Warning! You do not appear to have a version of PHP installed on your webserver that meets SMF\'s minimum installations requirements.
    If you are not the host, you will need to ask your host to upgrade, or use a different host - otherwise, please upgrade PHP to a recent version.

    If you know for a fact that your PHP version is high enough you may continue, although this is strongly discouraged.'; +$txt['error_missing_files'] = 'Unable to find crucial installation files in the directory of this script!

    Please make sure you uploaded the entire installation package, including the sql file, and then try again.'; +$txt['error_session_save_path'] = 'Please inform your host that the session.save_path specified in php.ini is not valid! It needs to be changed to a directory that exists, and is writable by the user PHP is running under.
    '; +$txt['error_windows_chmod'] = 'You\'re on a windows server, and some crucial files are not writable. Please ask your host to give write permissions to the user PHP is running under for the files in your SMF installation. The following files or directories need to be writable:'; +$txt['error_ftp_no_connect'] = 'Unable to connect to FTP server with this combination of details.'; +$txt['error_db_file'] = 'Cannot find database source script! Please check file %1$s is within your forum source directory.'; +$txt['error_db_connect'] = 'Cannot connect to the database server with the supplied data.

    If you are not sure about what to type in, please contact your host.'; +$txt['error_db_too_low'] = 'The version of your database server is very old, and does not meet SMF\'s minimum requirements.

    Please ask your host to either upgrade it or supply a new one, and if they won\'t, please try a different host.'; +$txt['error_db_database'] = 'The installer was unable to access the "%1$s" database. With some hosts, you have to create the database in your administration panel before SMF can use it. Some also add prefixes - like your username - to your database names.'; +$txt['error_db_queries'] = 'Some of the queries were not executed properly. This could be caused by an unsupported (development or old) version of your database software.

    Technical information about the queries:'; +$txt['error_db_queries_line'] = 'Line #'; +$txt['error_db_missing'] = 'The installer was unable to detect any database support in PHP. Please ask your host to ensure that PHP was compiled with the desired database, or that the proper extension is being loaded.'; +$txt['error_db_script_missing'] = 'The installer could not find any install script files for the detected databases. Please check you have uploaded the necessary install script files to your forum directory, for example "%1$s"'; +$txt['error_session_missing'] = 'The installer was unable to detect sessions support in your server\'s installation of PHP. Please ask your host to ensure that PHP was compiled with session support (in fact, it has to be explicitly compiled without it.)'; +$txt['error_user_settings_again_match'] = 'You typed in two completely different passwords!'; +$txt['error_user_settings_no_password'] = 'Your password must be at least four characters long.'; +$txt['error_user_settings_taken'] = 'Sorry, a member is already registered with that username and/or email address.

    A new account has not been created.'; +$txt['error_user_settings_query'] = 'A database error occurred while trying to create an administrator. This error was:'; +$txt['error_subs_missing'] = 'Unable to find the Sources/Subs.php file. Please make sure it was uploaded properly, and then try again.'; +$txt['error_db_alter_priv'] = 'The database account you specified does not have permission to ALTER, CREATE, and/or DROP tables in the database; this is necessary for SMF to function properly.'; +$txt['error_versions_do_not_match'] = 'The installer has detected another version of SMF already installed with the specified information. If you are trying to upgrade, you should use the upgrader, not the installer.

    Otherwise, you may wish to use different information, or create a backup and then delete the data currently in the database.'; +$txt['error_mod_security'] = 'The installer has detected the mod_security module is installed on your web server. Mod_security will block submitted forms even before SMF gets a say in anything. SMF has a built-in security scanner that will work more effectively than mod_security and that won\'t block submitted forms.

    More information about disabling mod_security'; +$txt['error_mod_security_no_write'] = 'The installer has detected the mod_security module is installed on your web server. Mod_security will block submitted forms even before SMF gets a say in anything. SMF has a built-in security scanner that will work more effectively than mod_security and that won\'t block submitted forms.

    More information about disabling mod_security

    Alternatively, you may wish to use your ftp client to chmod .htaccess in the forum directory to be writable (777), and then refresh this page.'; +$txt['error_utf8_version'] = 'The current version of your database doesn\'t support the use of the UTF-8 character set. You can still install SMF without any problems, but only with UTF-8 support unchecked. If you would like to switch over to UTF-8 in the future (e.g. after the database server of your forum has been upgraded to version >= %1$s), you can convert your forum to UTF-8 through the admin panel.'; +$txt['error_valid_email_needed'] = 'You have not entered a valid email address.'; +$txt['error_already_installed'] = 'The installer has detected that you already have SMF installed. It is strongly advised that you do not try to overwrite an existing installation - continuing with installation may result in the loss or corruption of existing data.

    If you wish to upgrade please visit the Simple Machines Website and download the latest upgrade package.

    If you wish to overwrite your existing installation, including all data, it\'s recommended that you delete the existing database tables and replace Settings.php and try again.'; +$txt['error_warning_notice'] = 'Warning!'; +$txt['error_script_outdated'] = 'This install script is out of date! The current version of SMF is %1$s but this install script is for %2$s.

    + It is recommended that you visit the Simple Machines website to ensure you are installing the latest version.'; +$txt['error_db_filename'] = 'You must enter a name for the database file name for SQLite.'; +$txt['error_db_prefix_numeric'] = 'The selected database type does not support the use of numeric prefixes.'; +$txt['error_invalid_characters_username'] = 'Invalid character used in Username.'; +$txt['error_username_too_long'] = 'Username must be less than 25 characters long.'; +$txt['error_username_left_empty'] = 'Username field was left empty.'; +$txt['error_db_filename_exists'] = 'The database that you are trying to create exists. Please delete the current database file or enter another name.'; +$txt['error_db_prefix_reserved'] = 'The prefix that you entered is a reserved prefix. Please enter another prefix.'; + +$txt['upgrade_upgrade_utility'] = 'SMF Upgrade Utility'; +$txt['upgrade_warning'] = 'Warning!'; +$txt['upgrade_critical_error'] = 'Critical Error!'; +$txt['upgrade_continue'] = 'Continue'; +$txt['upgrade_skip'] = 'Skip'; +$txt['upgrade_note'] = 'Note!'; +$txt['upgrade_step'] = 'Step'; +$txt['upgrade_steps'] = 'Steps'; +$txt['upgrade_progress'] = 'Progress'; +$txt['upgrade_overall_progress'] = 'Overall Progress'; +$txt['upgrade_step_progress'] = 'Step Progress'; +$txt['upgrade_time_elapsed'] = 'Time Elapsed'; +$txt['upgrade_time_mins'] = 'mins'; +$txt['upgrade_time_secs'] = 'seconds'; + +$txt['upgrade_incomplete'] = 'Incomplete'; +$txt['upgrade_not_quite_done'] = 'Not quite done yet!'; +$txt['upgrade_paused_overload'] = 'This upgrade has been paused to avoid overloading your server. Don\'t worry, nothing\'s wrong - simply click the below to keep going.'; + +$txt['upgrade_ready_proceed'] = 'Thank you for choosing to upgrade to SMF %1$s. All files appear to be in place, and we\'re ready to proceed.'; + +$txt['upgrade_error_script_js'] = 'The upgrade script cannot find script.js or it is out of date. Make sure your theme paths are correct. You can download a setting checker tool from the Simple Machines Website'; + +$txt['upgrade_warning_lots_data'] = 'This upgrade script has detected that your forum contains a lot of data which needs upgrading. This process may take quite some time depending on your server and forum size, and for very large forums (~300,000 messages) may take several hours to complete.'; +$txt['upgrade_warning_out_of_date'] = 'This upgrade script is out of date! The current version of SMF is ?? but this upgrade script is for %1$s.

    It is recommended that you visit the Simple Machines website to ensure you are upgrading to the latest version.'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/Login.english.php b/Themes/default/languages/Login.english.php new file mode 100644 index 0000000..d21d092 --- /dev/null +++ b/Themes/default/languages/Login.english.php @@ -0,0 +1,151 @@ +If you wish to restore your account, please check the "Reactivate my account" box, and login again.'; +$txt['undelete_account'] = 'Reactivate my account'; + +// Use numeric entities in the below three strings. +$txt['change_password'] = 'New Password Details'; +$txt['change_password_login'] = 'Your login details at'; +$txt['change_password_new'] = 'have been changed and your password reset. Below are your new login details.'; + +$txt['in_maintain_mode'] = 'This board is in Maintenance Mode.'; + +// These two are used as a javascript alert; please use international characters directly, not as entities. +$txt['register_agree'] = 'Please read and accept the agreement before registering.'; +$txt['register_passwords_differ_js'] = 'The two passwords you entered are not the same!'; + +$txt['approval_after_registration'] = 'Thank you for registering. The admin must approve your registration before you may begin to use your account, you will receive an email shortly advising you of the admins decision.'; + +$txt['admin_settings_desc'] = 'Here you can change a variety of settings related to registration of new members.'; + +$txt['setting_enableOpenID'] = 'Allow users to register using OpenID'; + +$txt['setting_registration_method'] = 'Method of registration employed for new members'; +$txt['setting_registration_disabled'] = 'Registration Disabled'; +$txt['setting_registration_standard'] = 'Immediate Registration'; +$txt['setting_registration_activate'] = 'Email Activation'; +$txt['setting_registration_approval'] = 'Admin Approval'; +$txt['setting_notify_new_registration'] = 'Notify administrators when a new member joins'; +$txt['setting_send_welcomeEmail'] = 'Send welcome email to new members'; + +$txt['setting_coppaAge'] = 'Age below which to apply registration restrictions'; +$txt['setting_coppaAge_desc'] = '(Set to 0 to disable)'; +$txt['setting_coppaType'] = 'Action to take when a user below minimum age registers'; +$txt['setting_coppaType_reject'] = 'Reject their registration'; +$txt['setting_coppaType_approval'] = 'Require parent/guardian approval'; +$txt['setting_coppaPost'] = 'Postal address to which approval forms should be sent'; +$txt['setting_coppaPost_desc'] = 'Only applies if age restriction is in place'; +$txt['setting_coppaFax'] = 'Fax number to which approval forms should be faxed'; +$txt['setting_coppaPhone'] = 'Contact number for parents to contact with age restriction queries'; + +$txt['admin_register'] = 'Registration of new member'; +$txt['admin_register_desc'] = 'From here you can register new members into the forum, and if desired, email them their details.'; +$txt['admin_register_username'] = 'New Username'; +$txt['admin_register_email'] = 'Email Address'; +$txt['admin_register_password'] = 'Password'; +$txt['admin_register_username_desc'] = 'Username for the new member'; +$txt['admin_register_email_desc'] = 'Email address of the member'; +$txt['admin_register_password_desc'] = 'Password for new member'; +$txt['admin_register_email_detail'] = 'Email new password to user'; +$txt['admin_register_email_detail_desc'] = 'Email address required even if unchecked'; +$txt['admin_register_email_activate'] = 'Require user to activate the account'; +$txt['admin_register_group'] = 'Primary Membergroup'; +$txt['admin_register_group_desc'] = 'Primary membergroup new member will belong to'; +$txt['admin_register_group_none'] = '(no primary membergroup)'; +$txt['admin_register_done'] = 'Member %1$s has been registered successfully!'; + +$txt['coppa_title'] = 'Age Restricted Forum'; +$txt['coppa_after_registration'] = 'Thank you for registering with ' . $context['forum_name_html_safe'] . '.

    Because you fall under the age of {MINIMUM_AGE}, it is a legal requirement + to obtain your parent or guardian\'s permission before you may begin to use your account. To arrange for account activation please print off the form below:'; +$txt['coppa_form_link_popup'] = 'Load Form In New Window'; +$txt['coppa_form_link_download'] = 'Download Form as Text File'; +$txt['coppa_send_to_one_option'] = 'Then arrange for your parent/guardian to send the completed form by:'; +$txt['coppa_send_to_two_options'] = 'Then arrange for your parent/guardian to send the completed form by either:'; +$txt['coppa_send_by_post'] = 'Post, to the following address:'; +$txt['coppa_send_by_fax'] = 'Fax, to the following number:'; +$txt['coppa_send_by_phone'] = 'Alternatively, arrange for them to phone the administrator at {PHONE_NUMBER}.'; + +$txt['coppa_form_title'] = 'Permission form for registration at ' . $context['forum_name_html_safe']; +$txt['coppa_form_address'] = 'Address'; +$txt['coppa_form_date'] = 'Date'; +$txt['coppa_form_body'] = 'I {PARENT_NAME},

    Give permission for {CHILD_NAME} (child name) to become a fully registered member of the forum: ' . $context['forum_name_html_safe'] . ', with the username: {USER_NAME}.

    I understand that certain personal information entered by {USER_NAME} may be shown to other users of the forum.

    Signed:
    {PARENT_NAME} (Parent/Guardian).'; + +$txt['visual_verification_sound_again'] = 'Play again'; +$txt['visual_verification_sound_close'] = 'Close window'; +$txt['visual_verification_sound_direct'] = 'Having problems hearing this? Try a direct link to it.'; + +// Use numeric entities in the below. +$txt['registration_username_available'] = 'Username is available'; +$txt['registration_username_unavailable'] = 'Username is not available'; +$txt['registration_username_check'] = 'Check if username is available'; +$txt['registration_password_short'] = 'Password is too short'; +$txt['registration_password_reserved'] = 'Password contains your username/email'; +$txt['registration_password_numbercase'] = 'Password must contain both upper and lower case, and numbers'; +$txt['registration_password_no_match'] = 'Passwords do not match'; +$txt['registration_password_valid'] = 'Password is valid'; + +$txt['registration_errors_occurred'] = 'The following errors were detected in your registration. Please correct them to continue:'; + +$txt['authenticate_label'] = 'Authentication Method'; +$txt['authenticate_password'] = 'Password'; +$txt['authenticate_openid'] = 'OpenID'; +$txt['authenticate_openid_url'] = 'OpenID Authentication URL'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/ManageBoards.english.php b/Themes/default/languages/ManageBoards.english.php new file mode 100644 index 0000000..3446355 --- /dev/null +++ b/Themes/default/languages/ManageBoards.english.php @@ -0,0 +1,98 @@ +"username", "username". (these must be usernames not display names!)
    To create a new board, click the Add Board button. To make the new board a child of a current board, select "Child of..." from the Order drop down menu when creating the board.'; +$txt['parent_members_only'] = 'Regular Members'; +$txt['parent_guests_only'] = 'Guests'; +$txt['catConfirm'] = 'Do you really want to delete this category?'; +$txt['boardConfirm'] = 'Do you really want to delete this board?'; + +$txt['catEdit'] = 'Edit Category'; +$txt['collapse_enable'] = 'Collapsible'; +$txt['collapse_desc'] = 'Allow users to collapse this category'; +$txt['catModify'] = '(modify)'; + +$txt['mboards_order_after'] = 'After '; +$txt['mboards_order_inside'] = 'Inside '; +$txt['mboards_order_first'] = 'In first place'; + +$txt['mboards_new_board'] = 'Add Board'; +$txt['mboards_new_cat_name'] = 'New Category'; +$txt['mboards_add_cat_button'] = 'Add Category'; +$txt['mboards_new_board_name'] = 'New Board'; + +$txt['mboards_name'] = 'Name'; +$txt['mboards_modify'] = 'modify'; +$txt['mboards_permissions'] = 'permissions'; +// Don't use entities in the below string. +$txt['mboards_permissions_confirm'] = 'Are you sure you want to switch this board to use local permissions?'; + +$txt['mboards_delete_cat'] = 'Delete Category'; +$txt['mboards_delete_board'] = 'Delete Board'; + +$txt['mboards_delete_cat_contains'] = 'Deleting this category will also delete the below boards, including all topics, posts and attachments within each board'; +$txt['mboards_delete_option1'] = 'Delete category and all boards contained within.'; +$txt['mboards_delete_option2'] = 'Delete category and move all boards contained within to'; +$txt['mboards_delete_board_contains'] = 'Deleting this board will also move the child boards below, including all topics, posts and attachments within each board'; +$txt['mboards_delete_board_option1'] = 'Delete board and move child boards contained within to category level.'; +$txt['mboards_delete_board_option2'] = 'Delete board and move all child boards contained within to'; +$txt['mboards_delete_what_do'] = 'Please select what you would like to do with these boards'; +$txt['mboards_delete_confirm'] = 'Confirm'; +$txt['mboards_delete_cancel'] = 'Cancel'; + +$txt['mboards_category'] = 'Category'; +$txt['mboards_description'] = 'Description'; +$txt['mboards_description_desc'] = 'A short description of your board.'; +$txt['mboards_groups'] = 'Allowed Groups'; +$txt['mboards_groups_desc'] = 'Groups allowed to access this board.
    Note: if the member is in any group or post group checked, they will have access to this board.'; +$txt['mboards_groups_regular_members'] = 'This group contains all members that have no primary group set.'; +$txt['mboards_groups_post_group'] = 'This group is a post count based group.'; +$txt['mboards_moderators'] = 'Moderators'; +$txt['mboards_moderators_desc'] = 'Additional members to have moderation privileges on this board. Note that administrators don\'t have to be listed here.'; +$txt['mboards_count_posts'] = 'Count Posts'; +$txt['mboards_count_posts_desc'] = 'Makes new replies and topics raise members\' post counts.'; +$txt['mboards_unchanged'] = 'Unchanged'; +$txt['mboards_theme'] = 'Board Theme'; +$txt['mboards_theme_desc'] = 'This allows you to change the look of your forum inside only this board.'; +$txt['mboards_theme_default'] = '(overall forum default.)'; +$txt['mboards_override_theme'] = 'Override Member\'s Theme'; +$txt['mboards_override_theme_desc'] = 'Use this board\'s theme even if the member didn\'t choose to use the defaults.'; + +$txt['mboards_redirect'] = 'Redirect to a web address'; +$txt['mboards_redirect_desc'] = 'Enable this option to redirect anyone who clicks on this board to another web address.'; +$txt['mboards_redirect_url'] = 'Address to redirect users to'; +$txt['mboards_redirect_url_desc'] = 'For example: "http://www.simplemachines.org".'; +$txt['mboards_redirect_reset'] = 'Reset redirect count'; +$txt['mboards_redirect_reset_desc'] = 'Selecting this will reset the redirection count for this board to zero.'; +$txt['mboards_current_redirects'] = 'Currently: %1$s'; +$txt['mboards_redirect_disabled'] = 'Note: Board must be empty of topics to enable this option.'; +$txt['mboards_redirect_disabled_recycle'] = 'Note: You cannot set the recycle bin board to be a redirection board.'; + +$txt['mboards_order_before'] = 'Before'; +$txt['mboards_order_child_of'] = 'Child of'; +$txt['mboards_order_in_category'] = 'In category'; +$txt['mboards_current_position'] = 'Current Position'; +$txt['no_valid_parent'] = 'Board %1$s does not have a valid parent. Use the \'find and repair errors\' function to fix this.'; + +$txt['mboards_recycle_disabled_delete'] = 'Note: You must select an alternative recycle bin board or disable recycling before you can delete this board.'; + +$txt['mboards_settings_desc'] = 'Edit general board and category settings.'; +$txt['groups_manage_boards'] = 'Membergroups allowed to manage boards and categories'; +$txt['mboards_settings_submit'] = 'Save'; +$txt['recycle_enable'] = 'Enable recycling of deleted topics'; +$txt['recycle_board'] = 'Board for recycled topics'; +$txt['recycle_board_unselected_notice'] = 'You have enabled the recycling of topics without specifying a board to place them in. This feature will not be enabled until you specify a board to place recycled topics into.'; +$txt['countChildPosts'] = 'Count child\'s posts in parent\'s totals'; +$txt['allow_ignore_boards'] = 'Allow boards to be ignored'; + +$txt['mboards_select_destination'] = 'Select destination for board \'%1$s\''; +$txt['mboards_cancel_moving'] = 'Cancel moving'; +$txt['mboards_move'] = 'move'; + +$txt['mboards_no_cats'] = 'There are currently no categories or boards configured.'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/ManageCalendar.english.php b/Themes/default/languages/ManageCalendar.english.php new file mode 100644 index 0000000..dd02d9c --- /dev/null +++ b/Themes/default/languages/ManageCalendar.english.php @@ -0,0 +1,45 @@ + \ No newline at end of file diff --git a/Themes/default/languages/ManageMail.english.php b/Themes/default/languages/ManageMail.english.php new file mode 100644 index 0000000..542716b --- /dev/null +++ b/Themes/default/languages/ManageMail.english.php @@ -0,0 +1,50 @@ + \ No newline at end of file diff --git a/Themes/default/languages/ManageMaintenance.english.php b/Themes/default/languages/ManageMaintenance.english.php new file mode 100644 index 0000000..ad388e7 --- /dev/null +++ b/Themes/default/languages/ManageMaintenance.english.php @@ -0,0 +1,214 @@ +Critical
    '; +$txt['errortype_critical_desc'] = 'Critical errors. These should be taken care of as quickly as possible. Ignoring these errors can result in your forum failing and possibly security issues'; +$txt['errortype_database'] = 'Database'; +$txt['errortype_database_desc'] = 'Errors caused by faulty queries. These should be looked at and reported to the SMF team.'; +$txt['errortype_undefined_vars'] = 'Undefined'; +$txt['errortype_undefined_vars_desc'] = 'Errors caused by the use of undefined variables, indexes, or offsets.'; +$txt['errortype_template'] = 'Template'; +$txt['errortype_template_desc'] = 'Errors related to the loading of templates.'; +$txt['errortype_user'] = 'User'; +$txt['errortype_user_desc'] = 'Errors resulting from user errors. Includes failed passwords, trying to login when banned, and trying to do an action for which they do not have permission.'; + +$txt['maintain_recount'] = 'Recount all forum totals and statistics'; +$txt['maintain_recount_info'] = 'Should the total replies of a topic or the number of PMs in your inbox be incorrect: this function will recount all saved counts and statistics for you.'; +$txt['maintain_errors'] = 'Find and repair any errors'; +$txt['maintain_errors_info'] = 'If, for example, posts or topics are missing after a server crash, this function may help finding them again.'; +$txt['maintain_logs'] = 'Empty out unimportant logs'; +$txt['maintain_logs_info'] = 'This function will empty out all unimportant logs. This should be avoided unless something\'s wrong, but it doesn\'t hurt anything.'; +$txt['maintain_cache'] = 'Empty the file cache'; +$txt['maintain_cache_info'] = 'This function will empty out the file cache should you need it to be cleared.'; +$txt['maintain_optimize'] = 'Optimize all tables'; +$txt['maintain_optimize_info'] = 'This task allows you to optimize all tables. This will get rid of overhead, effectively making the tables smaller in size and your forum faster!'; +$txt['maintain_version'] = 'Check all files against current versions'; +$txt['maintain_version_info'] = 'This maintenance task allows you to do a detailed version check of all forum files against the official list of latest versions.'; +$txt['maintain_run_now'] = 'Run task now'; +$txt['maintain_return'] = 'Back to Forum Maintenance'; + +$txt['maintain_backup'] = 'Backup Database'; +$txt['maintain_backup_info'] = 'Download a backup copy of your forums database in case of emergency.'; +$txt['maintain_backup_struct'] = 'Save the table structure.'; +$txt['maintain_backup_data'] = 'Save the table data (the important stuff).'; +$txt['maintain_backup_gz'] = 'Compress the file with gzip.'; +$txt['maintain_backup_save'] = 'Download'; + +$txt['maintain_old'] = 'Remove Old Posts'; +$txt['maintain_old_since_days1'] = 'Remove all topics not posted in for '; +$txt['maintain_old_since_days2'] = ' days, which are:'; +$txt['maintain_old_nothing_else'] = 'Any sort of topic.'; +$txt['maintain_old_are_moved'] = 'Moved topic notices.'; +$txt['maintain_old_are_locked'] = 'Locked.'; +$txt['maintain_old_are_not_stickied'] = 'But don\'t count stickied topics.'; +$txt['maintain_old_all'] = 'All Boards (click to select specific boards)'; +$txt['maintain_old_choose'] = 'Specific Boards (click to select all)'; +$txt['maintain_old_remove'] = 'Remove now'; +$txt['maintain_old_confirm'] = 'Are you really sure you want to delete old posts now?\\n\\nThis cannot be undone!'; + +$txt['maintain_members'] = 'Remove Inactive Members'; +$txt['maintain_members_ungrouped'] = 'Ungrouped Members (Members with no assigned groups)'; +$txt['maintain_members_since1'] = 'Remove all members who have not'; +$txt['maintain_members_since2'] = 'for'; +$txt['maintain_members_since3'] = 'days.'; +$txt['maintain_members_activated'] = 'activated their account'; +$txt['maintain_members_logged_in'] = 'logged in'; +$txt['maintain_members_all'] = 'All Membergroups'; +$txt['maintain_members_choose'] = 'Selected Groups'; +$txt['maintain_members_confirm'] = 'Are you sure you really want to delete these member accounts?\\n\\nThis cannot be undone!'; + +$txt['utf8_title'] = 'Convert the database and data to UTF-8'; +$txt['utf8_introduction'] = 'UTF-8 is an international character set covering nearly all languages around the world. Converting your database and data to UTF-8 can make it easier to support multiple languages on the same board. It also can enhance search and sorting capabilities for languages with non-latin characters.'; +$txt['utf8_warning'] = 'If you want to convert your data and database to UTF-8, be aware of the following: +
      +
    • Converting character sets might be harmful for your data! Make sure you have backed up your database before converting.
    • +
    • Because UTF-8 is a richer character set than most other character sets, there\'s no way back, unless by restoring your database to before the conversion.
    • +
    • After converting your data and database to UTF-8, you will need UTF-8 compatible language files.
    • +
    '; +$txt['utf8_charset_not_supported'] = 'Conversion from %1$s to UTF-8 is not supported.'; +$txt['utf8_detected_charset'] = 'Based on your default language file (\'%1$s\'), the character set of your data would most likely be \'%2$s\'.'; +$txt['utf8_already_utf8'] = 'Your database and data already seem to be configured as UTF-8 data. No conversion is needed.'; +$txt['utf8_source_charset'] = 'Data character set'; +$txt['utf8_proceed'] = 'Proceed'; +$txt['utf8_database_charset'] = 'Database character set'; +$txt['utf8_target_charset'] = 'Convert data and database to'; +$txt['utf8_utf8'] = 'UTF-8'; +$txt['utf8_db_version_too_low'] = 'The version of MySQL that your database server is using is not high enough to support UTF-8 properly. A minimum version of 4.1.2 is required.'; +$txt['utf8_cannot_convert_fulltext'] = 'Your messages table is using a fulltext index for use when searching. You cannot proceed in converting to UTF-8 until that index is removed. You can re-create it after the conversion has been completed.'; + +$txt['entity_convert_title'] = 'Convert HTML-entities to UTF-8 characters'; +$txt['entity_convert_only_utf8'] = 'The database needs to be in UTF-8 format before HTML-entities can be converted to UTF-8'; +$txt['entity_convert_introduction'] = 'This function will convert all characters that are stored in the database as HTML-entities to UTF-8 characters. This is especially useful when you have just converted your forum from a character set like ISO-8859-1 while non-latin characters were used on the forum. The browser then sends all characters as HTML-entities. For example, the HTML-entity &#945; represents the greek letter α (alpha). Converting entities to UTF-8 will improve searching and sorting of text and reduce storage size.'; +$txt['entity_convert_proceed'] = 'Proceed'; + +// Move topics out. +$txt['move_topics_maintenance'] = 'Move Topics'; +$txt['move_topics_select_board'] = 'Select Board'; +$txt['move_topics_from'] = 'Move topics from'; +$txt['move_topics_to'] = 'to'; +$txt['move_topics_now'] = 'Move now'; +$txt['move_topics_confirm'] = 'Are you sure you want to move ALL the topics from "%board_from%" to "%board_to%"?'; + +$txt['maintain_reattribute_posts'] = 'Reattribute User Posts'; +$txt['reattribute_guest_posts'] = 'Attribute guest posts made with'; +$txt['reattribute_email'] = 'Email address of'; +$txt['reattribute_username'] = 'Username of'; +$txt['reattribute_current_member'] = 'Attribute posts to member'; +$txt['reattribute_increase_posts'] = 'Add posts to users post count'; +$txt['reattribute'] = 'Reattribute'; +// Don't use entities in the below string. +$txt['reattribute_confirm'] = 'Are you sure you want to attribute all guest posts with %type% of "%find%" to member "%member_to%"?'; +$txt['reattribute_confirm_username'] = 'a username'; +$txt['reattribute_confirm_email'] = 'an email address'; +$txt['reattribute_cannot_find_member'] = 'Could not find member to attribute posts to.'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/ManageMembers.english.php b/Themes/default/languages/ManageMembers.english.php new file mode 100644 index 0000000..a5e3218 --- /dev/null +++ b/Themes/default/languages/ManageMembers.english.php @@ -0,0 +1,129 @@ +Note: normally, post groups don\'t need access because the group the member is in will give them access.'; +$txt['membergroups_new_as_inherit'] = 'inherit from'; +$txt['membergroups_new_as_type'] = 'by type'; +$txt['membergroups_new_as_copy'] = 'based off of'; +$txt['membergroups_new_copy_none'] = '(none)'; +$txt['membergroups_can_edit_later'] = 'You can edit them later.'; + +$txt['membergroups_edit_group'] = 'Edit Membergroup'; +$txt['membergroups_edit_name'] = 'Group name'; +$txt['membergroups_edit_inherit_permissions'] = 'Inherit Permissions'; +$txt['membergroups_edit_inherit_permissions_desc'] = 'Select "No" to enable group to have own permission set.'; +$txt['membergroups_edit_inherit_permissions_no'] = 'No - Use Unique Permissions'; +$txt['membergroups_edit_inherit_permissions_from'] = 'Inherit From'; +$txt['membergroups_edit_hidden'] = 'Visibility'; +$txt['membergroups_edit_hidden_no'] = 'Visible'; +$txt['membergroups_edit_hidden_boardindex'] = 'Visible - Except in Group Key'; +$txt['membergroups_edit_hidden_all'] = 'Invisible'; +// Do not use numeric entities in the below string. +$txt['membergroups_edit_hidden_warning'] = 'Are you sure you want to disallow assignment of this group as a users primary group?\\n\\nDoing so will restrict assignment to additional groups only, and will update all current "primary" members to have it as an additional group only.'; +$txt['membergroups_edit_desc'] = 'Group description'; +$txt['membergroups_edit_group_type'] = 'Group Type'; +$txt['membergroups_edit_select_group_type'] = 'Select Group Type'; +$txt['membergroups_group_type_private'] = 'Private (Membership must be assigned)'; +$txt['membergroups_group_type_protected'] = 'Protected (Only administrators can manage and assign)'; +$txt['membergroups_group_type_request'] = 'Requestable (User may request membership)'; +$txt['membergroups_group_type_free'] = 'Free (User may leave and join group at will)'; +$txt['membergroups_group_type_post'] = 'Post Based (Membership based on post count)'; +$txt['membergroups_min_posts'] = 'Required posts'; +$txt['membergroups_online_color'] = 'Color in online list'; +$txt['membergroups_star_count'] = 'Number of star images'; +$txt['membergroups_star_image'] = 'Star image filename'; +$txt['membergroups_star_image_note'] = 'you can use $language for the language of the user'; +$txt['membergroups_max_messages'] = 'Max personal messages'; +$txt['membergroups_max_messages_note'] = '0 = unlimited'; +$txt['membergroups_edit_save'] = 'Save'; +$txt['membergroups_delete'] = 'Delete'; +$txt['membergroups_confirm_delete'] = 'Are you sure you want to delete this group?!'; + +$txt['membergroups_members_title'] = 'Viewing Group'; +$txt['membergroups_members_group_members'] = 'Group Members'; +$txt['membergroups_members_no_members'] = 'This group is currently empty'; +$txt['membergroups_members_add_title'] = 'Add a member to this group'; +$txt['membergroups_members_add_desc'] = 'List of Members to Add'; +$txt['membergroups_members_add'] = 'Add Members'; +$txt['membergroups_members_remove'] = 'Remove from Group'; +$txt['membergroups_members_last_active'] = 'Last Active'; +$txt['membergroups_members_additional_only'] = 'Add as additional group only.'; +$txt['membergroups_members_group_moderators'] = 'Group Moderators'; +$txt['membergroups_members_description'] = 'Description'; +// Use javascript escaping in the below. +$txt['membergroups_members_deadmin_confirm'] = 'Are you sure you wish to remove yourself from the Administration group?'; + +$txt['membergroups_postgroups'] = 'Post groups'; +$txt['membergroups_settings'] = 'Membergroup Settings'; +$txt['groups_manage_membergroups'] = 'Groups allowed to change membergroups'; +$txt['membergroups_select_permission_type'] = 'Select permission profile'; +$txt['membergroups_images_url'] = '{theme URL}/images/'; +$txt['membergroups_select_visible_boards'] = 'Show boards'; +$txt['membergroups_members_top'] = 'Members'; +$txt['membergroups_name'] = 'Name'; +$txt['membergroups_stars'] = 'Stars'; + +$txt['admin_browse_approve'] = 'Members whose accounts are awaiting approval'; +$txt['admin_browse_approve_desc'] = 'From here you can manage all members who are waiting to have their accounts approved.'; +$txt['admin_browse_activate'] = 'Members whose accounts are awaiting activation'; +$txt['admin_browse_activate_desc'] = 'This screen lists all the members who have still not activated their accounts at your forum.'; +$txt['admin_browse_awaiting_approval'] = 'Awaiting Approval (%1$d)'; +$txt['admin_browse_awaiting_activate'] = 'Awaiting Activation (%1$d)'; + +$txt['admin_browse_username'] = 'Username'; +$txt['admin_browse_email'] = 'Email Address'; +$txt['admin_browse_ip'] = 'IP Address'; +$txt['admin_browse_registered'] = 'Registered'; +$txt['admin_browse_id'] = 'ID'; +$txt['admin_browse_with_selected'] = 'With Selected'; +$txt['admin_browse_no_members_approval'] = 'No members currently await approval.'; +$txt['admin_browse_no_members_activate'] = 'No members currently have not activated their accounts.'; + +// Don't use entities in the below strings, except the main ones. (lt, gt, quot.) +$txt['admin_browse_warn'] = 'all selected members?'; +$txt['admin_browse_outstanding_warn'] = 'all affected members?'; +$txt['admin_browse_w_approve'] = 'Approve'; +$txt['admin_browse_w_activate'] = 'Activate'; +$txt['admin_browse_w_delete'] = 'Delete'; +$txt['admin_browse_w_reject'] = 'Reject'; +$txt['admin_browse_w_remind'] = 'Remind'; +$txt['admin_browse_w_approve_deletion'] = 'Approve (Delete Accounts)'; +$txt['admin_browse_w_email'] = 'and send email'; +$txt['admin_browse_w_approve_require_activate'] = 'Approve and Require Activation'; + +$txt['admin_browse_filter_by'] = 'Filter By'; +$txt['admin_browse_filter_show'] = 'Displaying'; +$txt['admin_browse_filter_type_0'] = 'Unactivated New Accounts'; +$txt['admin_browse_filter_type_2'] = 'Unactivated Email Changes'; +$txt['admin_browse_filter_type_3'] = 'Unapproved New Accounts'; +$txt['admin_browse_filter_type_4'] = 'Unapproved Account Deletions'; +$txt['admin_browse_filter_type_5'] = 'Unapproved "Under Age" Accounts'; + +$txt['admin_browse_outstanding'] = 'Outstanding Members'; +$txt['admin_browse_outstanding_days_1'] = 'With all members who registered longer than'; +$txt['admin_browse_outstanding_days_2'] = 'days ago'; +$txt['admin_browse_outstanding_perform'] = 'Perform the following action'; +$txt['admin_browse_outstanding_go'] = 'Perform Action'; + +$txt['check_for_duplicate'] = 'Check for Duplicates'; +$txt['dont_check_for_duplicate'] = 'Don\'t Check for Duplicates'; +$txt['duplicates'] = 'Duplicates'; + +$txt['not_activated'] = 'Not activated'; +?> \ No newline at end of file diff --git a/Themes/default/languages/ManagePaid.english.php b/Themes/default/languages/ManagePaid.english.php new file mode 100644 index 0000000..b264253 --- /dev/null +++ b/Themes/default/languages/ManagePaid.english.php @@ -0,0 +1,209 @@ +Note:
    For subscriptions to be automatically updated for your users, you + will need to setup a return URL for each of your payment methods. For all payment types, this return URL should be set as:

    +   •  ' . $boardurl . '/subscriptions.php

    + You can edit the link for paypal directly, by clicking here.
    + For the other gateways (If installed) you can normally find it in your customer panels, usually under the term "Return URL" or "Callback URL".'; + +// View subscription strings. +$txt['paid_name'] = 'Name'; +$txt['paid_status'] = 'Status'; +$txt['paid_cost'] = 'Cost'; +$txt['paid_duration'] = 'Duration'; +$txt['paid_active'] = 'Active'; +$txt['paid_pending'] = 'Pending Payment'; +$txt['paid_finished'] = 'Finished'; +$txt['paid_total'] = 'Total'; +$txt['paid_is_active'] = 'Activated'; +$txt['paid_none_yet'] = 'You haven\'t set up any subscriptions yet.'; +$txt['paid_payments_pending'] = 'Payments Pending'; +$txt['paid_order'] = 'Order'; + +$txt['yes'] = 'Yes'; +$txt['no'] = 'No'; + +// Add/Edit/Delete subscription. +$txt['paid_add_subscription'] = 'Add Subscription'; +$txt['paid_edit_subscription'] = 'Edit Subscription'; +$txt['paid_delete_subscription'] = 'Delete Subscription'; + +$txt['paid_mod_name'] = 'Subscription Name'; +$txt['paid_mod_desc'] = 'Description'; +$txt['paid_mod_reminder'] = 'Send Reminder Email'; +$txt['paid_mod_reminder_desc'] = 'Days before subscription is due to expire to send reminder. (In days, 0 to disable)'; +$txt['paid_mod_email'] = 'Email to Send upon Completion'; +$txt['paid_mod_email_desc'] = 'Where {NAME} is members name; {FORUM} is community name. Email subject should be on first line. Blank for no email notification.'; +$txt['paid_mod_cost_usd'] = 'Cost (USD)'; +$txt['paid_mod_cost_eur'] = 'Cost (EUR)'; +$txt['paid_mod_cost_gbp'] = 'Cost (GBP)'; +$txt['paid_mod_cost_blank'] = 'Leave this blank to not offer this currency.'; +$txt['paid_mod_span'] = 'Length of Subscription'; +$txt['paid_mod_span_days'] = 'Days'; +$txt['paid_mod_span_weeks'] = 'Weeks'; +$txt['paid_mod_span_months'] = 'Months'; +$txt['paid_mod_span_years'] = 'Years'; +$txt['paid_mod_active'] = 'Active'; +$txt['paid_mod_active_desc'] = 'A subscription must be active for new members to join.'; +$txt['paid_mod_prim_group'] = 'Primary Group upon Subscription'; +$txt['paid_mod_prim_group_desc'] = 'Primary group to put the user into when they subscribe.'; +$txt['paid_mod_add_groups'] = 'Additional Groups upon Subscription'; +$txt['paid_mod_add_groups_desc'] = 'Additional groups to add the user to after subscription.'; +$txt['paid_mod_no_group'] = 'Don\'t Change'; +$txt['paid_mod_edit_note'] = 'Note that as this group has existing subscribers the group settings cannot be changed!'; +$txt['paid_mod_delete_warning'] = 'WARNING

    If you delete this subscription all users currently subscribed will lose any access rights granted by the subscription. Unless you are sure you want to do this it is recommended that you simply deactivate a subscription rather than delete it.
    '; +$txt['paid_mod_repeatable'] = 'Allow user to auto-renew this subscription'; +$txt['paid_mod_allow_partial'] = 'Allow partial subscription'; +$txt['paid_mod_allow_partial_desc'] = 'If this option is enabled, in the case where the user pays less than required they will be granted a subscription for the percentage of the duration they have paid for.'; +$txt['paid_mod_fixed_price'] = 'Subscription for fixed price and period'; +$txt['paid_mod_flexible_price'] = 'Subscription price varies on duration ordered'; +$txt['paid_mod_price_breakdown'] = 'Flexible Price Breakdown'; +$txt['paid_mod_price_breakdown_desc'] = 'Define here how much the subscription should cost dependant on the period they subscribe for. For example, it could cost 12USD to subscribe for a month, but only 100USD for a year. If you don\'t want to define a price for a particular period of time leave it blank.'; +$txt['flexible'] = 'Flexible'; + +$txt['paid_per_day'] = 'Price Per Day'; +$txt['paid_per_week'] = 'Price Per Week'; +$txt['paid_per_month'] = 'Price Per Month'; +$txt['paid_per_year'] = 'Price Per Year'; +$txt['day'] = 'Day'; +$txt['week'] = 'Week'; +$txt['month'] = 'Month'; +$txt['year'] = 'Year'; + +// View subscribed users. +$txt['viewing_users_subscribed'] = 'Viewing Users'; +$txt['view_users_subscribed'] = 'Viewing users subscribed to: "%1$s"'; +$txt['no_subscribers'] = 'There are currently no subscribers to this subscription!'; +$txt['add_subscriber'] = 'Add New Subscriber'; +$txt['edit_subscriber'] = 'Edit Subscriber'; +$txt['delete_selected'] = 'Delete Selected'; +$txt['complete_selected'] = 'Complete Selected'; + +// !!! These strings are used in conjunction with JavaScript. Use numeric entities. +$txt['delete_are_sure'] = 'Are you sure you want to delete all records of the selected subscriptions?'; +$txt['complete_are_sure'] = 'Are you sure you want to complete the selected subscriptions?'; + +$txt['start_date'] = 'Start Date'; +$txt['end_date'] = 'End Date'; +$txt['start_date_and_time'] = 'Start Date and Time'; +$txt['end_date_and_time'] = 'End Date and Time'; +$txt['edit'] = 'EDIT'; +$txt['one_username'] = 'Please enter one username only.'; +$txt['hour'] = 'Hour'; +$txt['minute'] = 'Minute'; +$txt['error_member_not_found'] = 'The member entered could not be found'; +$txt['member_already_subscribed'] = 'This member is already subscribed to this subscription. Please edit their existing subscription.'; +$txt['search_sub'] = 'Find User'; + +// Make payment. +$txt['paid_confirm_payment'] = 'Confirm Payment'; +$txt['paid_confirm_desc'] = 'To continue through to payment please check the details below and hit "Order"'; +$txt['paypal'] = 'PayPal'; +$txt['paid_confirm_paypal'] = 'To pay using PayPal please click the button below. You will be directed to the PayPal site for payment.'; +$txt['paid_paypal_order'] = 'Order with PayPal'; +$txt['worldpay'] = 'WorldPay'; +$txt['paid_confirm_worldpay'] = 'To pay using WorldPay please click the button below. You will be directed to the WorldPay site for payment.'; +$txt['paid_worldpay_order'] = 'Order with WorldPay'; +$txt['nochex'] = 'Nochex'; +$txt['paid_confirm_nochex'] = 'To pay using Nochex please click the button below. You will be directed to the Nochex site for payment.'; +$txt['paid_nochex_order'] = 'Order with Nochex'; +$txt['authorize'] = 'Authorize.Net'; +$txt['paid_confirm_authorize'] = 'To pay using Authorize.Net please click the button below. You will be directed to the Authorize.Net site for payment.'; +$txt['paid_authorize_order'] = 'Order with Authorize.Net'; +$txt['2co'] = '2checkout'; +$txt['paid_confirm_2co'] = 'To pay using 2co.com please click the button below. You will be directed to the 2co.com site for payment.'; +$txt['paid_2co_order'] = 'Order with 2co.com'; +$txt['paid_done'] = 'Payment Complete'; +$txt['paid_done_desc'] = 'Thank you for your payment. Once the transaction has been verified the subscription will be activated.'; +$txt['paid_sub_return'] = 'Return to Subscriptions'; +$txt['paid_current_desc'] = 'Below is a list of all your current and previous subscriptions. To extend an existing subscription simply select it from the list above.'; +$txt['paid_admin_add'] = 'Add This Subscription'; + +$txt['paid_not_set_currency'] = 'You have not setup your currency yet. Please do so from the Settings section before continuing.'; +$txt['paid_no_cost_value'] = 'You must enter a cost and subscription length.'; +$txt['paid_all_freq_blank'] = 'You must enter a cost for at least one of the four durations.'; + +// Some error strings. +$txt['paid_no_data'] = 'No valid data was sent to the script.'; + +$txt['paypal_could_not_connect'] = 'Could not connect to PayPal server'; +$txt['paid_sub_not_active'] = 'That subscription is not taking any new users!'; +$txt['paid_disabled'] = 'Paid subscriptions are currently disabled!'; +$txt['paid_unknown_transaction_type'] = 'Unknown Paid Subscriptions transaction type.'; +$txt['paid_empty_member'] = 'Paid subscription handler could not recover member ID'; +$txt['paid_could_not_find_member'] = 'Paid subscription handler could not find member with ID: %1$d'; +$txt['paid_count_not_find_subscription'] = 'Paid subscription handler could not find subscription for member ID: %1$s, subscription ID: %2$s'; +$txt['paid_count_not_find_subscription_log'] = 'Paid subscription handler could not find subscription log entry for member ID: %1$s, subscription ID: %2$s'; +$txt['paid_count_not_find_outstanding_payment'] = 'Could not find outstanding payment entry for member ID: %1$s, subscription ID: %2$s so ignoring'; +$txt['paid_admin_not_setup_gateway'] = 'Sorry, the admin has not yet finished setting up paid subscriptions. Please check back later.'; +$txt['paid_make_recurring'] = 'Make this a recurring payment'; + +$txt['subscriptions'] = 'Subscriptions'; +$txt['subscription'] = 'Subscription'; +$txt['paid_subs_desc'] = 'Below is a list of all the subscriptions which are available on this forum.'; +$txt['paid_subs_none'] = 'There are currently no paid subscriptions available!'; + +$txt['paid_current'] = 'Existing Subscriptions'; +$txt['pending_payments'] = 'Pending Payments'; +$txt['pending_payments_desc'] = 'This member has attempted to make the following payments for this subscription but the confirmation has not been received by the forum. If you are sure the payment has been received click "accept" to action to subscription. Alternatively you can click "Remove" to remove all reference to the payment.'; +$txt['pending_payments_value'] = 'Value'; +$txt['pending_payments_accept'] = 'Accept'; +$txt['pending_payments_remove'] = 'Remove'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/ManagePermissions.english.php b/Themes/default/languages/ManagePermissions.english.php new file mode 100644 index 0000000..ea74ccb --- /dev/null +++ b/Themes/default/languages/ManagePermissions.english.php @@ -0,0 +1,345 @@ +Note: You cannot edit this permission profile as it is a predefined profile included within the forum software by default. If you wish to change the permissions of this profile you must first create a duplicate profile. You can carry out this task by clicking here.'; + +$txt['permissions_for_profile'] = 'Permissions for Profile'; +$txt['permissions_boards_desc'] = 'The list below shows which set of permissions has been assigned to each board on your forum. You may edit the assigned permission profile by either clicking the board name or select "edit all" from the bottom of the page. To edit the profile itself simply click the profile name.'; +$txt['permissions_board_all'] = 'Edit All'; +$txt['permission_profile'] = 'Permission Profile'; +$txt['permission_profile_desc'] = 'Which permission set the board should use.'; +$txt['permission_profile_inherit'] = 'Inherit from parent board'; + +$txt['permissions_profile'] = 'Profile'; +$txt['permissions_profiles_desc'] = 'Permission profiles are assigned to individual boards to allow you to easily manage your security settings. From this area you can create, edit and delete permission profiles.'; +$txt['permissions_profiles_change_for_board'] = 'Edit Permission Profile For: "%1$s"'; +$txt['permissions_profile_default'] = 'Default'; +$txt['permissions_profile_no_polls'] = 'No Polls'; +$txt['permissions_profile_reply_only'] = 'Reply Only'; +$txt['permissions_profile_read_only'] = 'Read Only'; + +$txt['permissions_profile_rename'] = 'Rename'; +$txt['permissions_profile_edit'] = 'Edit Profiles'; +$txt['permissions_profile_new'] = 'New Profile'; +$txt['permissions_profile_new_create'] = 'Create'; +$txt['permissions_profile_name'] = 'Profile Name'; +$txt['permissions_profile_used_by'] = 'Used By'; +$txt['permissions_profile_used_by_one'] = '1 Board'; +$txt['permissions_profile_used_by_many'] = '%1$d Boards'; +$txt['permissions_profile_used_by_none'] = 'No Boards'; +$txt['permissions_profile_do_edit'] = 'Edit'; +$txt['permissions_profile_do_delete'] = 'Delete'; +$txt['permissions_profile_copy_from'] = 'Copy Permissions From'; + +$txt['permissions_includes_inherited'] = 'Inherited Groups'; + +$txt['permissions_all'] = 'all'; +$txt['permissions_none'] = 'none'; +$txt['permissions_set_permissions'] = 'Set permissions'; + +$txt['permissions_advanced_options'] = 'Advanced Options'; +$txt['permissions_with_selection'] = 'With selection'; +$txt['permissions_apply_pre_defined'] = 'Apply pre-defined permission set'; +$txt['permissions_select_pre_defined'] = 'Select a pre-defined profile'; +$txt['permissions_copy_from_board'] = 'Copy permissions from this board'; +$txt['permissions_select_board'] = 'Select a board'; +$txt['permissions_like_group'] = 'Set permissions like this group'; +$txt['permissions_select_membergroup'] = 'Select a membergroup'; +$txt['permissions_add'] = 'Add permission'; +$txt['permissions_remove'] = 'Clear permission'; +$txt['permissions_deny'] = 'Deny permission'; +$txt['permissions_select_permission'] = 'Select a permission'; + +// All of the following block of strings should not use entities, instead use \\" for " etc. +$txt['permissions_only_one_option'] = 'You can only select one action to modify the permissions'; +$txt['permissions_no_action'] = 'No action selected'; +$txt['permissions_deny_dangerous'] = 'You are about to deny one or more permissions.\\nThis can be dangerous and cause unexpected results if you haven\'t made sure no one is \\"accidentally\\" in the group or groups you are denying permissions to.\\n\\nAre you sure you want to continue?'; + +$txt['permissions_modify_group'] = 'Modify Group'; +$txt['permissions_general'] = 'General Permissions'; +$txt['permissions_board'] = 'Default Board Profile Permissions'; +$txt['permissions_board_desc'] = 'Note: changing these board permissions will affect all boards currently assigned the "Default" permissions profile. Boards not using the "Default" profile will not be affected by changes to this page.'; +$txt['permissions_commit'] = 'Save changes'; +$txt['permissions_on'] = 'in profile'; +$txt['permissions_local_for'] = 'Permissions for group'; +$txt['permissions_option_on'] = 'A'; +$txt['permissions_option_off'] = 'X'; +$txt['permissions_option_deny'] = 'D'; +$txt['permissions_option_desc'] = 'For each permission you can pick either \'Allow\' (A), \'Disallow\' (X), or \'Deny\' (D).

    Remember that if you deny a permission, any member - whether moderator or otherwise - that is in that group will be denied that as well.
    For this reason, you should use deny carefully, only when necessary. Disallow, on the other hand, denies unless otherwise granted.'; +$txt['permissions_change_view'] = 'Change View'; +$txt['permissions_view_simple'] = 'Simple'; +$txt['permissions_view_classic'] = 'Classic'; + +$txt['permissiongroup_general'] = 'General'; +$txt['permissionname_view_stats'] = 'View forum statistics'; +$txt['permissionhelp_view_stats'] = 'The forum statistics is a page summarizing all statistics of the forum, like member count, daily number of posts, and several top 10 statistics. Enabling this permission adds a link to the bottom of the board index (\'[More Stats]\').'; +$txt['permissionname_view_mlist'] = 'View the memberlist and groups'; +$txt['permissionhelp_view_mlist'] = 'The memberlist shows all members that have registered on your forum. The list can be sorted and searched. The memberlist is linked from both the boardindex and the stats page, by clicking on the number of members. It also applies to the groups page which is a mini memberlist of people in that group.'; +$txt['permissionname_who_view'] = 'View Who\'s Online'; +$txt['permissionhelp_who_view'] = 'Who\'s online shows all members that are currently online and what they are doing at that moment. This permission will only work if you also have enabled it in \'Features and Options\'. You can access the \'Who\'s Online\' screen by clicking the link in the \'Users Online\' section of the board index. Even if this is denied, members will still be able to see who\'s online, just not where they are.'; +$txt['permissionname_search_posts'] = 'Search for posts and topics'; +$txt['permissionhelp_search_posts'] = 'The Search permission allows the user to search all boards he or she is allowed to access. When the search permission is enabled, a \'Search\' button will be added to the forum button bar.'; +$txt['permissionname_karma_edit'] = 'Change other people\'s karma'; +$txt['permissionhelp_karma_edit'] = 'Karma is a feature that shows the popularity of a member. In order to use this feature, you need to have it enabled in \'Features and Options\'. This permission will allow a membergroup to cast a vote. This permission has no effect on guests.'; + +$txt['permissiongroup_pm'] = 'Personal Messaging'; +$txt['permissionname_pm_read'] = 'Read personal messages'; +$txt['permissionhelp_pm_read'] = 'This permission allows users to access the Personal Messages section and read their Personal Messages. Without this permission a user is unable to send Personal Messages.'; +$txt['permissionname_pm_send'] = 'Send personal messages'; +$txt['permissionhelp_pm_send'] = 'Send personal messages to other registered members. Requires the \'Read personal messages\' permission.'; + +$txt['permissiongroup_calendar'] = 'Calendar'; +$txt['permissionname_calendar_view'] = 'View the calendar'; +$txt['permissionhelp_calendar_view'] = 'The calendar shows for each month the birthdays, events and holidays. This permission allows access to this calendar. When this permission is enabled, a button will be added to the top button bar and a list will be shown at the bottom of the board index with current and upcoming birthdays, events and holidays. The calendar needs be enabled from \'Configuration - Core Features\'.'; +$txt['permissionname_calendar_post'] = 'Create events in the calendar'; +$txt['permissionhelp_calendar_post'] = 'An Event is a topic linked to a certain date or date range. Creating events can be done from the calendar. An event can only be created if the user that creates the event is allowed to post new topics.'; +$txt['permissionname_calendar_edit'] = 'Edit events in the calendar'; +$txt['permissionhelp_calendar_edit'] = 'An Event is a topic linked to a certain date or date range. The event can be edited by clicking the red asterisk (*) next to the event in the calendar view. In order to be able to edit an event, a user must have sufficient permissions to edit the first message of the topic that is linked to the event.'; +$txt['permissionname_calendar_edit_own'] = 'Own events'; +$txt['permissionname_calendar_edit_any'] = 'Any events'; + +$txt['permissiongroup_maintenance'] = 'Forum administration'; +$txt['permissionname_admin_forum'] = 'Administrate forum and database'; +$txt['permissionhelp_admin_forum'] = 'This permission allows a user to:
    • change forum, database and theme settings
    • manage packages
    • use the forum and database maintenance tools
    • view the error and mod logs
    Use this permission with caution, as it is very powerful.'; +$txt['permissionname_manage_boards'] = 'Manage boards and categories'; +$txt['permissionhelp_manage_boards'] = 'This permission allows creation, editing and removal of boards and categories.'; +$txt['permissionname_manage_attachments'] = 'Manage attachments and avatars'; +$txt['permissionhelp_manage_attachments'] = 'This permission allows access to the attachment center, where all forum attachments and avatars are listed and can be removed.'; +$txt['permissionname_manage_smileys'] = 'Manage smileys and message icons'; +$txt['permissionhelp_manage_smileys'] = 'This allows access to the smiley center. In the smiley center you can add, edit and remove smileys and smiley sets. If you\'ve enabled customized message icons you are also able to add and edit message icons with this permission.'; +$txt['permissionname_edit_news'] = 'Edit news'; +$txt['permissionhelp_edit_news'] = 'The news function allows a random news line to appear on each screen. In order to use the news function, enabled it in the forum settings.'; +$txt['permissionname_access_mod_center'] = 'Access the moderation center'; +$txt['permissionhelp_access_mod_center'] = 'With this permission any members of this group can access the moderation center from where they will have access to functionality to ease moderation. Note that this does not in itself grant any moderation privileges.'; + +$txt['permissiongroup_member_admin'] = 'Member administration'; +$txt['permissionname_moderate_forum'] = 'Moderate forum members'; +$txt['permissionhelp_moderate_forum'] = 'This permission includes all important member moderation functions:
    • access to registration management
    • access to the view/delete members screen
    • extensive profile info, including track IP/user and (hidden) online status
    • activate accounts
    • get approval notifications and approve accounts
    • immune to ignore PM
    • several small things
    '; +$txt['permissionname_manage_membergroups'] = 'Manage and assign membergroups'; +$txt['permissionhelp_manage_membergroups'] = 'This permission allows a user to edit membergroups and assign membergroups to other members.'; +$txt['permissionname_manage_permissions'] = 'Manage permissions'; +$txt['permissionhelp_manage_permissions'] = 'This permission allows a user to edit all permissions of a membergroup, globally or for individual boards.'; +$txt['permissionname_manage_bans'] = 'Manage ban list'; +$txt['permissionhelp_manage_bans'] = 'This permission allows a user to add or remove usernames, IP addresses, hostnames and email addresses to a list of banned users. It also allows a user to view and remove log entries of banned users that attempted to login.'; +$txt['permissionname_send_mail'] = 'Send a forum email to members'; +$txt['permissionhelp_send_mail'] = 'Mass mail all forum members, or just a few membergroups by email or personal message (the latter requires \'Send Personal Message\' permission).'; +$txt['permissionname_issue_warning'] = 'Issue warnings to members'; +$txt['permissionhelp_issue_warning'] = 'Issue a warning to members of the forum and change that members\' warning level. Requires the warning system to be enabled.'; + +$txt['permissiongroup_profile'] = 'Member Profiles'; +$txt['permissionname_profile_view'] = 'View profile summary and stats'; +$txt['permissionhelp_profile_view'] = 'This permission allows users clicking on a username to see a summary of profile settings, some statistics and all posts of the user.'; +$txt['permissionname_profile_view_own'] = 'Own profile'; +$txt['permissionname_profile_view_any'] = 'Any profile'; +$txt['permissionname_profile_identity'] = 'Edit account settings'; +$txt['permissionhelp_profile_identity'] = 'Account settings are the basic settings of a profile, like password, email address, membergroup and preferred language.'; +$txt['permissionname_profile_identity_own'] = 'Own profile'; +$txt['permissionname_profile_identity_any'] = 'Any profile'; +$txt['permissionname_profile_extra'] = 'Edit additional profile settings'; +$txt['permissionhelp_profile_extra'] = 'Additional profile settings include settings for avatars, theme preferences, notifications and Personal Messages.'; +$txt['permissionname_profile_extra_own'] = 'Own profile'; +$txt['permissionname_profile_extra_any'] = 'Any profile'; +$txt['permissionname_profile_title'] = 'Edit custom title'; +$txt['permissionhelp_profile_title'] = 'The custom title is shown on the topic display page, under the profile of each user that has a custom title.'; +$txt['permissionname_profile_title_own'] = 'Own profile'; +$txt['permissionname_profile_title_any'] = 'Any profile'; +$txt['permissionname_profile_remove'] = 'Delete account'; +$txt['permissionhelp_profile_remove'] = 'This permission allows a user to delete his account, when set to \'Own Account\'.'; +$txt['permissionname_profile_remove_own'] = 'Own account'; +$txt['permissionname_profile_remove_any'] = 'Any account'; +$txt['permissionname_profile_server_avatar'] = 'Select an avatar from the server'; +$txt['permissionhelp_profile_server_avatar'] = 'If enabled this will allow a user to select an avatar from the avatar collections installed on the server.'; +$txt['permissionname_profile_upload_avatar'] = 'Upload an avatar to the server'; +$txt['permissionhelp_profile_upload_avatar'] = 'This permission will allow a user to upload their personal avatar to the server.'; +$txt['permissionname_profile_remote_avatar'] = 'Choose a remotely stored avatar'; +$txt['permissionhelp_profile_remote_avatar'] = 'Because avatars might influence the page creation time negatively, it is possible to disallow certain membergroups to use avatars from external servers.'; + +$txt['permissiongroup_general_board'] = 'General'; +$txt['permissionname_moderate_board'] = 'Moderate board'; +$txt['permissionhelp_moderate_board'] = 'The moderate board permission adds a few small permissions that make a moderator a real moderator. Permissions include replying to locked topics, changing the poll expire time and viewing poll results.'; + +$txt['permissiongroup_topic'] = 'Topics'; +$txt['permissionname_post_new'] = 'Post new topics'; +$txt['permissionhelp_post_new'] = 'This permission allows users to post new topics. It doesn\'t allow to post replies to topics.'; +$txt['permissionname_merge_any'] = 'Merge any topic'; +$txt['permissionhelp_merge_any'] = 'Merge two or more topic into one. The order of messages within the merged topic will be based on the time the messages were created. A user can only merge topics on those boards a user is allowed to merge. In order to merge multiple topics at once, a user has to enable quickmoderation in their profile settings.'; +$txt['permissionname_split_any'] = 'Split any topic'; +$txt['permissionhelp_split_any'] = 'Split a topic into two separate topics.'; +$txt['permissionname_send_topic'] = 'Send topics to friends'; +$txt['permissionhelp_send_topic'] = 'This permission allows a user to mail a topic to a friend, by entering their email address and allows adding a message.'; +$txt['permissionname_make_sticky'] = 'Make topics sticky'; +$txt['permissionhelp_make_sticky'] = 'Sticky topics are topics that always remain on top of a board. They can be useful for announcements or other important messages.'; +$txt['permissionname_move'] = 'Move topic'; +$txt['permissionhelp_move'] = 'Move a topic from one board to the other. Users can only select target boards they are allowed to access.'; +$txt['permissionname_move_own'] = 'Own topic'; +$txt['permissionname_move_any'] = 'Any topic'; +$txt['permissionname_lock'] = 'Lock topics'; +$txt['permissionhelp_lock'] = 'This permission allows a user to lock a topic. This can be done in order to make sure no one can reply to a topic. Only uses with a \'Moderate board\' permission can still post in locked topics.'; +$txt['permissionname_lock_own'] = 'Own topic'; +$txt['permissionname_lock_any'] = 'Any topic'; +$txt['permissionname_remove'] = 'Remove topics'; +$txt['permissionhelp_remove'] = 'Delete topics as a whole. Note that this permission doesn\'t allow to delete specific messages within the topic!'; +$txt['permissionname_remove_own'] = 'Own topic'; +$txt['permissionname_remove_any'] = 'Any topics'; +$txt['permissionname_post_reply'] = 'Post replies to topics'; +$txt['permissionhelp_post_reply'] = 'This permission allows replying to topics.'; +$txt['permissionname_post_reply_own'] = 'Own topic'; +$txt['permissionname_post_reply_any'] = 'Any topic'; +$txt['permissionname_modify_replies'] = 'Modify replies to own topics'; +$txt['permissionhelp_modify_replies'] = 'This permission allows a user that started a topic to modify all replies to their topic.'; +$txt['permissionname_delete_replies'] = 'Delete replies to own topics'; +$txt['permissionhelp_delete_replies'] = 'This permission allows a user that started a topic to remove all replies to their topic.'; +$txt['permissionname_announce_topic'] = 'Announce topic'; +$txt['permissionhelp_announce_topic'] = 'This allows a user to send an announcement e-mail about a topic to all members or to a few membergroups.'; + +$txt['permissiongroup_post'] = 'Posts'; +$txt['permissionname_delete'] = 'Delete posts'; +$txt['permissionhelp_delete'] = 'Remove posts. This does not allow a user to delete the first post of a topic.'; +$txt['permissionname_delete_own'] = 'Own post'; +$txt['permissionname_delete_any'] = 'Any post'; +$txt['permissionname_modify'] = 'Modify posts'; +$txt['permissionhelp_modify'] = 'Edit posts'; +$txt['permissionname_modify_own'] = 'Own post'; +$txt['permissionname_modify_any'] = 'Any post'; +$txt['permissionname_report_any'] = 'Report posts to the moderators'; +$txt['permissionhelp_report_any'] = 'This permission adds a link to each message, allowing a user to report a post to a moderator. On reporting, all moderators on that board will receive an email with a link to the reported post and a description of the problem (as given by the reporting user).'; + +$txt['permissiongroup_poll'] = 'Polls'; +$txt['permissionname_poll_view'] = 'View polls'; +$txt['permissionhelp_poll_view'] = 'This permission allows a user to view a poll. Without this permission, the user will only see the topic.'; +$txt['permissionname_poll_vote'] = 'Vote in polls'; +$txt['permissionhelp_poll_vote'] = 'This permission allows a (registered) user to cast one vote. It doesn\'t apply to guests.'; +$txt['permissionname_poll_post'] = 'Post polls'; +$txt['permissionhelp_poll_post'] = 'This permission allows a user to post a new poll. The user needs to have the \'Post new topics\' permission.'; +$txt['permissionname_poll_add'] = 'Add poll to topics'; +$txt['permissionhelp_poll_add'] = 'Add poll to topics allows a user to add a poll after the topic has been created. This permission requires sufficient rights to edit the first post of a topic.'; +$txt['permissionname_poll_add_own'] = 'Own topics'; +$txt['permissionname_poll_add_any'] = 'Any topics'; +$txt['permissionname_poll_edit'] = 'Edit polls'; +$txt['permissionhelp_poll_edit'] = 'This permission allows a user to edit the options of a poll and to reset the poll. In order to edit the maximum number of votes and the expiration time, a user needs to have the \'Moderate board\' permission.'; +$txt['permissionname_poll_edit_own'] = 'Own poll'; +$txt['permissionname_poll_edit_any'] = 'Any poll'; +$txt['permissionname_poll_lock'] = 'Lock polls'; +$txt['permissionhelp_poll_lock'] = 'Locking polls prevents the poll from accepting any more votes.'; +$txt['permissionname_poll_lock_own'] = 'Own poll'; +$txt['permissionname_poll_lock_any'] = 'Any poll'; +$txt['permissionname_poll_remove'] = 'Remove polls'; +$txt['permissionhelp_poll_remove'] = 'This permission allows removal of polls.'; +$txt['permissionname_poll_remove_own'] = 'Own poll'; +$txt['permissionname_poll_remove_any'] = 'Any poll'; + +$txt['permissiongroup_approval'] = 'Post Moderation'; +$txt['permissionname_approve_posts'] = 'Approve items awaiting moderation'; +$txt['permissionhelp_approve_posts'] = 'This permission allows a user to approve all unapproved items on a board.'; +$txt['permissionname_post_unapproved_replies'] = 'Post replies to topics, but hide until approved'; +$txt['permissionhelp_post_unapproved_replies'] = 'This permission allows a user to post replies to a topic. The replies will not be shown until approved by a moderator.'; +$txt['permissionname_post_unapproved_replies_own'] = 'Own topic'; +$txt['permissionname_post_unapproved_replies_any'] = 'Any topic'; +$txt['permissionname_post_unapproved_topics'] = 'Post new topics, but hide until approved'; +$txt['permissionhelp_post_unapproved_topics'] = 'This permission allows a user to post a new topic which will require approval before being shown.'; +$txt['permissionname_post_unapproved_attachments'] = 'Post attachments, but hide until approved'; +$txt['permissionhelp_post_unapproved_attachments'] = 'This permission allows a user to attach files to their posts. The attached files will then require approval before being shown to other users.'; + +$txt['permissiongroup_notification'] = 'Notifications'; +$txt['permissionname_mark_any_notify'] = 'Request notification on replies'; +$txt['permissionhelp_mark_any_notify'] = 'This feature allows users to receive a notification whenever someone replies to a topic they subscribed to.'; +$txt['permissionname_mark_notify'] = 'Request notification on new topics'; +$txt['permissionhelp_mark_notify'] = 'Notification on new topics is a feature that allows a user to receive an email every time a new topic is created on the board they subscribe to.'; + +$txt['permissiongroup_attachment'] = 'Attachments'; +$txt['permissionname_view_attachments'] = 'View attachments'; +$txt['permissionhelp_view_attachments'] = 'Attachments are files that are attached to posted messages. This feature can be enabled and configured in \'Attachments and avatars\'. Since attachments are not directly accessed, you can protect them from being downloaded by users that don\'t have this permission.'; +$txt['permissionname_post_attachment'] = 'Post attachments'; +$txt['permissionhelp_post_attachment'] = 'Attachments are files that are attached to posted messages. One message can contain multiple attachments.'; + +$txt['permissiongroup_simple_view_basic_info'] = 'Use basic forum functionality'; +$txt['permissiongroup_simple_use_pm_system'] = 'Contact members using the personal messaging system'; +$txt['permissiongroup_simple_post_calendar'] = 'Post events onto the calendar'; +$txt['permissiongroup_simple_edit_profile'] = 'Personalize their profile'; +$txt['permissiongroup_simple_delete_account'] = 'Delete their account'; +$txt['permissiongroup_simple_use_avatar'] = 'Select or upload an avatar'; +$txt['permissiongroup_simple_moderate_general'] = 'Moderate the entire forum'; +$txt['permissiongroup_simple_administrate'] = 'Carry out administrative duties'; + +$txt['permissionname_simple_calendar_edit_own'] = 'Edit their own calendar events'; +$txt['permissionname_simple_calendar_edit_any'] = 'Edit other people\'s calendar events'; +$txt['permissionname_simple_profile_view_own'] = 'View their own profile'; +$txt['permissionname_simple_profile_view_any'] = 'View other people\'s profiles'; +$txt['permissionname_simple_profile_identity_own'] = 'Edit their account settings'; +$txt['permissionname_simple_profile_identity_any'] = 'Edit other people\'s account settings'; +$txt['permissionname_simple_profile_extra_own'] = 'Edit their additional profile options'; +$txt['permissionname_simple_profile_extra_any'] = 'Edit other people\'s profile options'; +$txt['permissionname_simple_profile_title_own'] = 'Choose a custom title for themselves'; +$txt['permissionname_simple_profile_title_any'] = 'Edit other people\'s custom titles'; +$txt['permissionname_simple_profile_remove_own'] = 'Delete their own account'; +$txt['permissionname_simple_profile_remove_any'] = 'Delete other user\'s accounts'; + +$txt['permissiongroup_simple_make_unapproved_posts'] = 'Post topics and replies to the board only after they have been approved'; +$txt['permissiongroup_simple_make_posts'] = 'Post topics and replies to the board'; +$txt['permissiongroup_simple_post_polls'] = 'Make new polls'; +$txt['permissiongroup_simple_participate'] = 'View additional board content'; +$txt['permissiongroup_simple_modify'] = 'Modify their posts'; +$txt['permissiongroup_simple_notification'] = 'Request notifications'; +$txt['permissiongroup_simple_attach'] = 'Post attachments'; +$txt['permissiongroup_simple_moderate'] = 'Moderate the board'; + +$txt['permissionname_simple_post_unapproved_replies_own'] = 'Post replies to their own topic - but require approval'; +$txt['permissionname_simple_post_unapproved_replies_any'] = 'Post replies to any topic - but require approval'; +$txt['permissionname_simple_post_reply_own'] = 'Post replies to a topic they started'; +$txt['permissionname_simple_post_reply_any'] = 'Post replies to any topic'; +$txt['permissionname_simple_move_own'] = 'Move their own topics'; +$txt['permissionname_simple_move_any'] = 'Move anyone\'s topic'; +$txt['permissionname_simple_lock_own'] = 'Lock their own topic'; +$txt['permissionname_simple_lock_any'] = 'Lock anyone\'s topic'; +$txt['permissionname_simple_remove_own'] = 'Remove their own topic'; +$txt['permissionname_simple_remove_any'] = 'Remove anyone\'s topic'; +$txt['permissionname_simple_delete_own'] = 'Delete a post that they made'; +$txt['permissionname_simple_delete_any'] = 'Delete a post made by anyone'; +$txt['permissionname_simple_modify_own'] = 'Modify their own post'; +$txt['permissionname_simple_modify_any'] = 'Modify someone else\'s post'; +$txt['permissionname_simple_poll_add_own'] = 'Add a poll to a topic they created'; +$txt['permissionname_simple_poll_add_any'] = 'Add a poll to any topic'; +$txt['permissionname_simple_poll_edit_own'] = 'Edit a poll they created'; +$txt['permissionname_simple_poll_edit_any'] = 'Edit anyone\'s poll'; +$txt['permissionname_simple_poll_lock_own'] = 'Lock their own poll'; +$txt['permissionname_simple_poll_lock_any'] = 'Lock anyone\'s poll'; +$txt['permissionname_simple_poll_remove_own'] = 'Remove a poll they created'; +$txt['permissionname_simple_poll_remove_any'] = 'Remove anyone\'s poll'; + +$txt['permissionicon'] = ''; + +$txt['permission_settings_title'] = 'Permission Settings'; +$txt['groups_manage_permissions'] = 'Membergroups allowed to manage permissions'; +$txt['permission_settings_submit'] = 'Save'; +$txt['permission_settings_enable_deny'] = 'Enable the option to deny permissions'; +// Escape any single quotes in here twice.. 'it\'s' -> 'it\\\'s'. +$txt['permission_disable_deny_warning'] = 'Turning off this option will update \\\'Deny\\\'-permissions to \\\'Disallow\\\'.'; +$txt['permission_by_board_desc'] = 'Here you can set which permissions profile a board uses. You can create new permission profiles from the "Edit Profiles" menu.'; +$txt['permission_settings_desc'] = 'Here you can set who has permission to change permissions, as well as how sophisticated the permission system should be.'; +$txt['permission_settings_enable_postgroups'] = 'Enable permissions for post count based groups'; +// Escape any single quotes in here twice.. 'it\'s' -> 'it\\\'s'. +$txt['permission_disable_postgroups_warning'] = 'Disabling this setting will remove permissions currently set to post count based groups.'; + +$txt['permissions_post_moderation_desc'] = 'From this page you can easily change which groups have their posts moderated for a particular permissions profile.'; +$txt['permissions_post_moderation_deny_note'] = 'Note that while you have advanced permissions enabled you cannot apply the "deny" permission from this page. Please edit the permissions directly if you wish to apply a deny permission.'; +$txt['permissions_post_moderation_select'] = 'Select Profile'; +$txt['permissions_post_moderation_new_topics'] = 'New Topics'; +$txt['permissions_post_moderation_replies_own'] = 'Own Replies'; +$txt['permissions_post_moderation_replies_any'] = 'Any Replies'; +$txt['permissions_post_moderation_attachments'] = 'Attachments'; +$txt['permissions_post_moderation_legend'] = 'Legend'; +$txt['permissions_post_moderation_allow'] = 'Can create'; +$txt['permissions_post_moderation_moderate'] = 'Can create but requires approval'; +$txt['permissions_post_moderation_disallow'] = 'Cannot create'; +$txt['permissions_post_moderation_group'] = 'Group'; + +$txt['auto_approve_topics'] = 'Post new topics, without requiring approval'; +$txt['auto_approve_replies'] = 'Post replies to topics, without requiring approval'; +$txt['auto_approve_attachments'] = 'Post attachments, without requiring approval'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/ManageScheduledTasks.english.php b/Themes/default/languages/ManageScheduledTasks.english.php new file mode 100644 index 0000000..efe00d3 --- /dev/null +++ b/Themes/default/languages/ManageScheduledTasks.english.php @@ -0,0 +1,57 @@ +Note: All times given below are server time and do not take any time offsets setup within SMF into account.'; +$txt['scheduled_tasks_were_run'] = 'All selected tasks were completed'; + +$txt['scheduled_tasks_na'] = 'N/A'; +$txt['scheduled_task_approval_notification'] = 'Approval Notifications'; +$txt['scheduled_task_desc_approval_notification'] = 'Send out emails to all moderators summarizing posts awaiting approval.'; +$txt['scheduled_task_auto_optimize'] = 'Optimize Database'; +$txt['scheduled_task_desc_auto_optimize'] = 'Optimize the database to resolve fragmentation issues.'; +$txt['scheduled_task_daily_maintenance'] = 'Daily Maintenance'; +$txt['scheduled_task_desc_daily_maintenance'] = 'Runs essential daily maintenance on the forum - should not be disabled.'; +$txt['scheduled_task_daily_digest'] = 'Daily Notification Summary'; +$txt['scheduled_task_desc_daily_digest'] = 'Emails out the daily digest for notification subscribers.'; +$txt['scheduled_task_weekly_digest'] = 'Weekly Notification Summary'; +$txt['scheduled_task_desc_weekly_digest'] = 'Emails out the weekly digest for notification subscribers.'; +$txt['scheduled_task_fetchSMfiles'] = 'Fetch Simple Machines Files'; +$txt['scheduled_task_desc_fetchSMfiles'] = 'Retrieves javascript files containing notifications of updates and other information.'; +$txt['scheduled_task_birthdayemails'] = 'Send Birthday Emails'; +$txt['scheduled_task_desc_birthdayemails'] = 'Sends out emails wishing members a happy birthday.'; +$txt['scheduled_task_weekly_maintenance'] = 'Weekly Maintenance'; +$txt['scheduled_task_desc_weekly_maintenance'] = 'Runs essential weekly maintenance on the forum - should not be disabled.'; +$txt['scheduled_task_paid_subscriptions'] = 'Paid Subscription Checks'; +$txt['scheduled_task_desc_paid_subscriptions'] = 'Sends out any necessary paid subscription reminders and removes expired member subscriptions.'; + +$txt['scheduled_task_reg_starting'] = 'Starting at %1$s'; +$txt['scheduled_task_reg_repeating'] = 'repeating every %1$d %2$s'; +$txt['scheduled_task_reg_unit_m'] = 'minute(s)'; +$txt['scheduled_task_reg_unit_h'] = 'hour(s)'; +$txt['scheduled_task_reg_unit_d'] = 'day(s)'; +$txt['scheduled_task_reg_unit_w'] = 'week(s)'; + +$txt['scheduled_task_edit'] = 'Edit Scheduled Task'; +$txt['scheduled_task_edit_repeat'] = 'Repeat task every'; +$txt['scheduled_task_edit_pick_unit'] = 'Pick Unit'; +$txt['scheduled_task_edit_interval'] = 'Interval'; +$txt['scheduled_task_edit_start_time'] = 'Start Time'; +$txt['scheduled_task_edit_start_time_desc'] = 'Time the first instance of the day should start (hours:minutes)'; +$txt['scheduled_task_time_offset'] = 'Note the start time should be the offset against the current server time. Current server time is: %1$s'; + +$txt['scheduled_view_log'] = 'View Log'; +$txt['scheduled_log_empty'] = 'There are currently no task log entries.'; +$txt['scheduled_log_time_run'] = 'Time Run'; +$txt['scheduled_log_time_taken'] = 'Time taken'; +$txt['scheduled_log_time_taken_seconds'] = '%1$d seconds'; +$txt['scheduled_log_empty_log'] = 'Empty Log'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/ManageSettings.english.php b/Themes/default/languages/ManageSettings.english.php new file mode 100644 index 0000000..b7859c9 --- /dev/null +++ b/Themes/default/languages/ManageSettings.english.php @@ -0,0 +1,359 @@ +theme settings for more options. Click the help icons for more information about a setting.'; +$txt['security_settings_desc'] = 'This page allows you to set options specifically related to the security and moderation of your forum, including anti-spam options.'; +$txt['modification_settings_desc'] = 'This page contains settings added by any modifications to your forum'; + +$txt['modification_no_misc_settings'] = 'There are no modifications installed that have added any settings to this area yet.'; + +$txt['pollMode'] = 'Poll mode'; +$txt['disable_polls'] = 'Disable polls'; +$txt['enable_polls'] = 'Enable polls'; +$txt['polls_as_topics'] = 'Show existing polls as topics'; +$txt['allow_guestAccess'] = 'Allow guests to browse the forum'; +$txt['userLanguage'] = 'Enable user-selectable language support'; +$txt['allow_editDisplayName'] = 'Allow users to edit their displayed name'; +$txt['allow_hideOnline'] = 'Allow non-administrators to hide their online status'; +$txt['guest_hideContacts'] = 'Do not reveal contact details of members to guests'; +$txt['titlesEnable'] = 'Enable custom titles'; +$txt['enable_buddylist'] = 'Enable buddy/ignore lists'; +$txt['default_personal_text'] = 'Default personal text
    Personal text to assign to newly registered members.
    '; +$txt['number_format'] = 'Default number format'; +$txt['time_format'] = 'Default time format'; +$txt['setting_time_offset'] = 'Overall time offset
    (added to the member specific option.)
    '; +$txt['setting_default_timezone'] = 'Server timezone'; +$txt['failed_login_threshold'] = 'Failed login threshold'; +$txt['lastActive'] = 'User online time threshold'; +$txt['trackStats'] = 'Track daily statistics'; +$txt['hitStats'] = 'Track daily page views (must have stats enabled)'; +$txt['enableCompressedOutput'] = 'Enable compressed output'; +$txt['disableTemplateEval'] = 'Disable evaluation of templates'; +$txt['databaseSession_enable'] = 'Use database driven sessions'; +$txt['databaseSession_loose'] = 'Allow browsers to go back to cached pages'; +$txt['databaseSession_lifetime'] = 'Seconds before an unused session timeout'; +$txt['enableErrorLogging'] = 'Enable error logging'; +$txt['enableErrorQueryLogging'] = 'Include database query in the error log'; +$txt['pruningOptions'] = 'Enable pruning of log entries'; +$txt['pruneErrorLog'] = 'Remove error log entries older than
    (0 to disable)
    '; +$txt['pruneModLog'] = 'Remove moderation log entries older than
    (0 to disable)
    '; +$txt['pruneBanLog'] = 'Remove ban hit log entries older than
    (0 to disable)
    '; +$txt['pruneReportLog'] = 'Remove report to moderator log entries older than
    (0 to disable)
    '; +$txt['pruneScheduledTaskLog'] = 'Remove scheduled task log entries older than
    (0 to disable)
    '; +$txt['pruneSpiderHitLog'] = 'Remove search engine hit logs older than
    (0 to disable)
    '; +$txt['cookieTime'] = 'Default login cookies length (in minutes)'; +$txt['localCookies'] = 'Enable local storage of cookies
    (SSI won\'t work well with this on.)
    '; +$txt['globalCookies'] = 'Use subdomain independent cookies
    (turn off local cookies first!)
    '; +$txt['secureCookies'] = 'Force cookies to be secure
    (This only applies if you are using HTTPS - don\'t use otherwise!)
    '; +$txt['securityDisable'] = 'Disable administration security'; +$txt['send_validation_onChange'] = 'Require reactivation after email change'; +$txt['approveAccountDeletion'] = 'Require admin approval when member deletes account'; +$txt['autoOptMaxOnline'] = 'Maximum users online when optimizing
    (0 for no max.)
    '; +$txt['autoFixDatabase'] = 'Automatically fix broken tables'; +$txt['allow_disableAnnounce'] = 'Allow users to disable announcements'; +$txt['disallow_sendBody'] = 'Don\'t allow post text in notifications'; +$txt['queryless_urls'] = 'Search engine friendly URLs
    Apache/Lighttpd only!
    '; +$txt['max_image_width'] = 'Max width of posted pictures (0 = disable)'; +$txt['max_image_height'] = 'Max height of posted pictures (0 = disable)'; +$txt['enableReportPM'] = 'Enable reporting of personal messages'; +$txt['max_pm_recipients'] = 'Maximum number of recipients allowed in a personal message
    (0 for no limit, admins are exempt)
    '; +$txt['pm_posts_verification'] = 'Post count under which users must pass verification when sending personal messages
    (0 for no limit, admins are exempt)
    '; +$txt['pm_posts_per_hour'] = 'Number of personal messages a user may send in an hour
    (0 for no limit, moderators are exempt)
    '; +$txt['compactTopicPagesEnable'] = 'Limit number of displayed page links'; +$txt['contiguous_page_display'] = 'Contiguous pages to display'; +$txt['to_display'] = 'to display'; +$txt['todayMod'] = 'Enable shorthand date display'; +$txt['today_disabled'] = 'Disabled'; +$txt['today_only'] = 'Only Today'; +$txt['yesterday_today'] = 'Today & Yesterday'; +$txt['topbottomEnable'] = 'Enable Go Up/Go Down buttons'; +$txt['onlineEnable'] = 'Show online/offline in posts and PMs'; +$txt['enableVBStyleLogin'] = 'Show a quick login on every page'; +$txt['defaultMaxMembers'] = 'Members per page in member list'; +$txt['timeLoadPageEnable'] = 'Display time taken to create every page'; +$txt['disableHostnameLookup'] = 'Disable hostname lookups'; +$txt['who_enabled'] = 'Enable who\'s online list'; +$txt['make_email_viewable'] = 'Allow viewable email addresses'; +$txt['meta_keywords'] = 'Meta keywords associated with forum
    For search engines. Leave blank for default.
    '; + +$txt['karmaMode'] = 'Karma mode'; +$txt['karma_options'] = 'Disable karma|Enable karma total|Enable karma positive/negative'; +$txt['karmaMinPosts'] = 'Set the minimum posts needed to modify karma'; +$txt['karmaWaitTime'] = 'Set wait time in hours'; +$txt['karmaTimeRestrictAdmins'] = 'Restrict administrators to wait time'; +$txt['karmaLabel'] = 'Karma label'; +$txt['karmaApplaudLabel'] = 'Karma applaud label'; +$txt['karmaSmiteLabel'] = 'Karma smite label'; + +$txt['caching_information'] = '
    Important! Read this first before enabling these features.

    + SMF supports caching through the use of accelerators. The currently supported accelerators include:
    +
      +
    • APC
    • +
    • eAccelerator
    • +
    • Turck MMCache
    • +
    • Memcached
    • +
    • Zend Platform/Performance Suite (Not Zend Optimizer)
    • +
    • XCache
    • +
    + Caching will work best if you have PHP compiled with one of the above optimizers, or have memcache + available. If you do not have any optimizer installed SMF will do file based caching.

    + SMF performs caching at a variety of levels. The higher the level of caching enabled the more CPU time will be spent + retrieving cached information. If caching is available on your machine it is recommended that you try caching at level 1 first. +

    + Note that if you use memcached you need to provide the server details in the setting below. This should be entered as a comma separated list + as shown in the example below:
    + "server1,server2,server3:port,server4"

    + Note that if no port is specified SMF will use port 11211. SMF will attempt to perform rough/random load balancing across the servers. +

    + %1$s'; + +$txt['detected_no_caching'] = 'SMF has not been able to detect a compatible accelerator on your server.'; +$txt['detected_APC'] = 'SMF has detected that your server has APC installed.'; +$txt['detected_eAccelerator'] = 'SMF has detected that your server has eAccelerator installed.'; +$txt['detected_MMCache'] = 'SMF has detected that your server has MMCache installed.'; +$txt['detected_Zend'] = 'SMF has detected that your server has Zend installed.'; +$txt['detected_Memcached'] = 'SMF has detected that your server has Memcached installed.'; +$txt['detected_XCache'] = 'SMF has detected that your server has XCache installed.'; + +$txt['cache_enable'] = 'Caching Level'; +$txt['cache_off'] = 'No caching'; +$txt['cache_level1'] = 'Level 1 Caching (Recommended)'; +$txt['cache_level2'] = 'Level 2 Caching'; +$txt['cache_level3'] = 'Level 3 Caching (Not Recommended)'; +$txt['cache_memcached'] = 'Memcache settings'; + +$txt['loadavg_warning'] = 'Please note: the settings below are to be edited with care. Setting any of them too low may render your forum unusable! The current load average is %01.2f'; +$txt['loadavg_enable'] = 'Enable load balancing by load averages'; +$txt['loadavg_auto_opt'] = 'Threshold to disabling automatic database optimization'; +$txt['loadavg_search'] = 'Threshold to disabling search'; +$txt['loadavg_allunread'] = 'Threshold to disabling all unread topics'; +$txt['loadavg_unreadreplies'] = 'Threshold to disabling unread replies'; +$txt['loadavg_show_posts'] = 'Threshold to disabling showing user posts'; +$txt['loadavg_forum'] = 'Threshold to disabling the forum completely'; +$txt['loadavg_disabled_windows'] = 'Load balancing support is not available on Windows.'; +$txt['loadavg_disabled_conf'] = 'Load balancing support is disabled by your host configuration.'; + +$txt['setting_password_strength'] = 'Required strength for user passwords'; +$txt['setting_password_strength_low'] = 'Low - 4 character minimum'; +$txt['setting_password_strength_medium'] = 'Medium - cannot contain username'; +$txt['setting_password_strength_high'] = 'High - mixture of different characters'; + +$txt['antispam_Settings'] = 'Anti-Spam Verification'; +$txt['antispam_Settings_desc'] = 'This section allows you to setup verification checks to ensure the user is a human (and not a bot), and tweak how and where these apply.'; +$txt['setting_reg_verification'] = 'Require verification on registration page'; +$txt['posts_require_captcha'] = 'Post count under which users must pass verification to make a post'; +$txt['posts_require_captcha_desc'] = '(0 for no limit, moderators are exempt)'; +$txt['search_enable_captcha'] = 'Require verification on all guest searches'; +$txt['setting_guests_require_captcha'] = 'Guests must pass verification when making a post'; +$txt['setting_guests_require_captcha_desc'] = '(Automatically set if you specify a minimum post count below)'; +$txt['guests_report_require_captcha'] = 'Guests must pass verification when reporting a post'; + +$txt['configure_verification_means'] = 'Configure Verification Methods'; +$txt['setting_qa_verification_number'] = 'Number of verification questions user must answer'; +$txt['setting_qa_verification_number_desc'] = '(0 to disable; questions are set below)'; +$txt['configure_verification_means_desc'] = 'Below you can set which anti-spam features you wish to have enabled whenever a user needs to verify they are a human. Note that the user will have to pass all verification so if you enable both a verification image and a question/answer test they need to complete both to proceed.'; +$txt['setting_visual_verification_type'] = 'Visual verification image to display'; +$txt['setting_visual_verification_type_desc'] = 'The more complex the image the harder it is for bots to bypass'; +$txt['setting_image_verification_off'] = 'None'; +$txt['setting_image_verification_vsimple'] = 'Very Simple - Plain text on image'; +$txt['setting_image_verification_simple'] = 'Simple - Overlapping colored letters, no noise'; +$txt['setting_image_verification_medium'] = 'Medium - Overlapping colored letters, with noise/lines'; +$txt['setting_image_verification_high'] = 'High - Angled letters, considerable noise/lines'; +$txt['setting_image_verification_extreme'] = 'Extreme - Angled letters, noise, lines and blocks'; +$txt['setting_image_verification_sample'] = 'Sample'; +$txt['setting_image_verification_nogd'] = 'Note: as this server does not have the GD library installed the different complexity settings will have no effect.'; +$txt['setup_verification_questions'] = 'Verification Questions'; +$txt['setup_verification_questions_desc'] = 'If you want users to answer verification questions in order to stop spam bots you should setup a number of questions in the table below. You should pick relatively simple questions; answers are not case sensitive. You may use BBC in the questions for formatting, to remove a question simply delete the contents of that line.'; +$txt['setup_verification_question'] = 'Question'; +$txt['setup_verification_answer'] = 'Answer'; +$txt['setup_verification_add_more'] = 'Add another question'; + +$txt['moderation_settings'] = 'Moderation Settings'; +$txt['setting_warning_enable'] = 'Enable User Warning System'; +$txt['setting_warning_watch'] = 'Warning level for user watch
    The user warning level after which a user watch is put in place - 0 to disable.
    '; +$txt['setting_warning_moderate'] = 'Warning level for post moderation
    The user warning level after which a user has all posts moderated - 0 to disable.
    '; +$txt['setting_warning_mute'] = 'Warning level for user muting
    The user warning level after which a user cannot post any further - 0 to disable.
    '; +$txt['setting_user_limit'] = 'Maximum user warning points per day
    This value is the maximum amount of warning points a single moderator can assign to a user in a 24 hour period - 0 for no limit.
    '; +$txt['setting_warning_decrement'] = 'Warning points to decrement from users every 24 hours
    Only applies to users not warned within last 24 hours - set to 0 to disable.
    '; +$txt['setting_warning_show'] = 'Users who can see warning status
    Determines who can see the warning level of users on the forum.
    '; +$txt['setting_warning_show_mods'] = 'Moderators Only'; +$txt['setting_warning_show_user'] = 'Moderators and Warned Users'; +$txt['setting_warning_show_all'] = 'All Users'; + +$txt['signature_settings'] = 'Signature Settings'; +$txt['signature_settings_desc'] = 'Use the settings on this page to decide how member signatures should be treated in SMF.'; +$txt['signature_settings_warning'] = 'Note that settings are not applied to existing signatures by default. Click here to apply rules to all existing signatures.'; +$txt['signature_enable'] = 'Enable signatures'; +$txt['signature_max_length'] = 'Maximum allowed characters
    (0 for no max.)
    '; +$txt['signature_max_lines'] = 'Maximum amount of lines
    (0 for no max)
    '; +$txt['signature_max_images'] = 'Maximum image count
    (0 for no max - excludes smileys)
    '; +$txt['signature_allow_smileys'] = 'Allow smileys in signatures'; +$txt['signature_max_smileys'] = 'Maximum smiley count
    (0 for no max)
    '; +$txt['signature_max_image_width'] = 'Maximum width of signature images (pixels)
    (0 for no max)
    '; +$txt['signature_max_image_height'] = 'Maximum height of signature images (pixels)
    (0 for no max)
    '; +$txt['signature_max_font_size'] = 'Maximum font size allowed in signatures
    (0 for no max, in pixels)
    '; +$txt['signature_bbc'] = 'Enabled BBC tags'; + +$txt['custom_profile_title'] = 'Custom Profile Fields'; +$txt['custom_profile_desc'] = 'From this page you can create your own custom profile fields that fit in with your own forums requirements'; +$txt['custom_profile_active'] = 'Active'; +$txt['custom_profile_fieldname'] = 'Field Name'; +$txt['custom_profile_fieldtype'] = 'Field Type'; +$txt['custom_profile_make_new'] = 'New Field'; +$txt['custom_profile_none'] = 'You have not created any custom profile fields yet!'; +$txt['custom_profile_icon'] = 'Icon'; + +$txt['custom_profile_type_text'] = 'Text'; +$txt['custom_profile_type_textarea'] = 'Large Text'; +$txt['custom_profile_type_select'] = 'Select Box'; +$txt['custom_profile_type_radio'] = 'Radio Button'; +$txt['custom_profile_type_check'] = 'Checkbox'; + +$txt['custom_add_title'] = 'Add Profile Field'; +$txt['custom_edit_title'] = 'Edit Profile Field'; +$txt['custom_edit_general'] = 'Display Settings'; +$txt['custom_edit_input'] = 'Input Settings'; +$txt['custom_edit_advanced'] = 'Advanced Settings'; +$txt['custom_edit_name'] = 'Name'; +$txt['custom_edit_desc'] = 'Description'; +$txt['custom_edit_profile'] = 'Profile Section'; +$txt['custom_edit_profile_desc'] = 'Section of profile this is edited in.'; +$txt['custom_edit_profile_none'] = 'None'; +$txt['custom_edit_registration'] = 'Show on Registration'; +$txt['custom_edit_registration_disable'] = 'No'; +$txt['custom_edit_registration_allow'] = 'Yes'; +$txt['custom_edit_registration_require'] = 'Yes, and require entry'; +$txt['custom_edit_display'] = 'Show on Topic View'; +$txt['custom_edit_picktype'] = 'Field Type'; + +$txt['custom_edit_max_length'] = 'Maximum Length'; +$txt['custom_edit_max_length_desc'] = '(0 for no limit)'; +$txt['custom_edit_dimension'] = 'Dimensions'; +$txt['custom_edit_dimension_row'] = 'Rows'; +$txt['custom_edit_dimension_col'] = 'Columns'; +$txt['custom_edit_bbc'] = 'Allow BBC'; +$txt['custom_edit_options'] = 'Options'; +$txt['custom_edit_options_desc'] = 'Leave option box blank to remove. Radio button selects default option.'; +$txt['custom_edit_options_more'] = 'More'; +$txt['custom_edit_default'] = 'Default State'; +$txt['custom_edit_active'] = 'Active'; +$txt['custom_edit_active_desc'] = 'If not selected this field will not be shown to anyone.'; +$txt['custom_edit_privacy'] = 'Privacy'; +$txt['custom_edit_privacy_desc'] = 'Who can see and edit this field.'; +$txt['custom_edit_privacy_all'] = 'Users can see this field; owner can edit it'; +$txt['custom_edit_privacy_see'] = 'Users can see this field; only admins can edit it'; +$txt['custom_edit_privacy_owner'] = 'Users cannot see this field; owner and admins can edit it.'; +$txt['custom_edit_privacy_none'] = 'This field is only visible to admins'; +$txt['custom_edit_can_search'] = 'Searchable'; +$txt['custom_edit_can_search_desc'] = 'Can this field be searched from the members list.'; +$txt['custom_edit_mask'] = 'Input Mask'; +$txt['custom_edit_mask_desc'] = 'For text fields an input mask can be selected to validate the data.'; +$txt['custom_edit_mask_email'] = 'Valid Email'; +$txt['custom_edit_mask_number'] = 'Numeric'; +$txt['custom_edit_mask_nohtml'] = 'No HTML'; +$txt['custom_edit_mask_regex'] = 'Regex (Advanced)'; +$txt['custom_edit_enclose'] = 'Show Enclosed Within Text (Optional)'; +$txt['custom_edit_enclose_desc'] = 'We strongly recommend to use an input mask to validate the input supplied by the user.'; + +$txt['custom_edit_placement'] = 'Choose Placement'; +$txt['custom_edit_placement_standard'] = 'Standard (with title)'; +$txt['custom_edit_placement_withicons'] = 'With Icons'; +$txt['custom_edit_placement_abovesignature'] = 'Above Signature'; +$txt['custom_profile_placement'] = 'Placement'; +$txt['custom_profile_placement_standard'] = 'Standard'; +$txt['custom_profile_placement_withicons'] = 'With Icons'; +$txt['custom_profile_placement_abovesignature'] = 'Above Signature'; + +// Use numeric entities in the string below! +$txt['custom_edit_delete_sure'] = 'Are you sure you wish to delete this field - all related user data will be lost!'; + +$txt['standard_profile_title'] = 'Standard Profile Fields'; +$txt['standard_profile_field'] = 'Field'; + +$txt['core_settings_welcome_msg'] = 'Welcome to Your New Forum'; +$txt['core_settings_welcome_msg_desc'] = 'To get you started we suggest you select which of SMF\'s core features you want to enable. We\'d recommend only enabling with those features you need!'; +$txt['core_settings_item_cd'] = 'Calendar'; +$txt['core_settings_item_cd_desc'] = 'Enabling this feature will open up a selection of options to enable your users to view the calendar, add and review events, see users birthdates on a calendar and much, much more.'; +$txt['core_settings_item_cp'] = 'Advanced Profile Fields'; +$txt['core_settings_item_cp_desc'] = 'This enables you to hide standard profile fields, add profile fields to registration, and create new profile fields for your forum.'; +$txt['core_settings_item_k'] = 'Karma'; +$txt['core_settings_item_k_desc'] = 'Karma is a feature that shows the popularity of a member. Members, if allowed, can \'applaud\' or \'smite\' other members, which is how their popularity is calculated.'; +$txt['core_settings_item_ml'] = 'Moderation, Administration and User Logs'; +$txt['core_settings_item_ml_desc'] = 'Enable the moderation and administration logs to keep an audit trail of all the key actions taken on your forum. Also allows forum moderators to view a history of key changes a user makes to their profile.'; +$txt['core_settings_item_pm'] = 'Post Moderation'; +$txt['core_settings_item_pm_desc'] = 'Post moderation enables you to select groups and boards within which posts must be approved before they become public. Upon enabling this feature be sure to visit the permission section to set up the relevant permissions.'; +$txt['core_settings_item_ps'] = 'Paid Subscriptions'; +$txt['core_settings_item_ps_desc'] = 'Paid subscriptions allow users to pay for subscriptions to change membergroup within the forum and thus change their access rights.'; +$txt['core_settings_item_rg'] = 'Report Generation'; +$txt['core_settings_item_rg_desc'] = 'This administration feature allows the generation of reports (Which can be printed) to present your current forum setup in an easy to view manner - particularly useful for large forums.'; +$txt['core_settings_item_sp'] = 'Search Engine Tracking'; +$txt['core_settings_item_sp_desc'] = 'Enabling this feature will allow administrators to track search engines as they index your forum.'; +$txt['core_settings_item_w'] = 'Warning System'; +$txt['core_settings_item_w_desc'] = 'This functionality allows administrators and moderators to issue warnings to users; it also includes advanced functionality for automatically removing user rights as their warning level increases. Note to take full advantage of this function "Post Moderation" should be enabled.'; +$txt['core_settings_switch_on'] = 'Click to Enable'; +$txt['core_settings_switch_off'] = 'Click to Disable'; +$txt['core_settings_enabled'] = 'Enabled'; +$txt['core_settings_disabled'] = 'Disabled'; + +$txt['languages_lang_name'] = 'Language Name'; +$txt['languages_locale'] = 'Locale'; +$txt['languages_default'] = 'Default'; +$txt['languages_character_set'] = 'Character Set'; +$txt['languages_users'] = 'Users'; +$txt['language_settings_writable'] = 'Warning: Settings.php is not writable so the default language setting cannot be saved.'; +$txt['edit_languages'] = 'Edit Languages'; +$txt['lang_file_not_writable'] = 'Warning: The primary language file (%1$s) is not writable. You must make this writable before you can make any changes.'; +$txt['lang_entries_not_writable'] = 'Warning: The language file you wish to edit (%1$s) is not writable. You must make this writable before you can make any changes.'; +$txt['languages_ltr'] = 'Right to Left'; + +$txt['add_language'] = 'Add Language'; +$txt['add_language_smf'] = 'Download from Simple Machines'; +$txt['add_language_smf_browse'] = 'Type name of language to search for or leave blank to search for all.'; +$txt['add_language_smf_install'] = 'Install'; +$txt['add_language_smf_found'] = 'The following languages were found. Click the install link next to the language you wish to install, you will then be taken to the package manager to install.'; +$txt['add_language_error_no_response'] = 'The Simple Machines site is not responding. Please try again later.'; +$txt['add_language_error_no_files'] = 'No files could be found.'; +$txt['add_language_smf_desc'] = 'Description'; +$txt['add_language_smf_utf8'] = 'UTF-8'; +$txt['add_language_smf_version'] = 'Version'; + +$txt['edit_language_entries_primary'] = 'Below are the primary language settings for this language pack.'; +$txt['edit_language_entries'] = 'Edit Language Entries'; +$txt['edit_language_entries_file'] = 'Select entries to edit'; +$txt['languages_dictionary'] = 'Dictionary'; +$txt['languages_spelling'] = 'Spelling'; +$txt['languages_for_pspell'] = 'This is for pSpell - if installed'; +$txt['languages_rtl'] = 'Enable "Right to Left" Mode'; + +$txt['lang_file_desc_index'] = 'General Strings'; +$txt['lang_file_desc_EmailTemplates'] = 'Email Templates'; + +$txt['languages_download'] = 'Download Language Pack'; +$txt['languages_download_note'] = 'This page lists all the files that are contained within the language pack and some useful information about each one. All files that have their associated check box marked will be copied.'; +$txt['languages_download_info'] = 'Note: +
      +
    • Files which have the status "Not Writable" means SMF will not be able to copy this file to the directory at the present and you must make the destination writable either using an FTP client or by filling in your details at the bottom of the page.
    • +
    • The Version information for a file displays the last SMF version which it was updated for. If it is indicated in green then this is a newer version than you have at current. If amber this indicates it\'s the same version number as at current, red indicates you have a newer version installed than contained in the pack.
    • +
    • Where a file already exists on your forum the "Already Exists" column will have one of two values. "Identical" indicates that the file already exists in an identical form and need not be overwritten. "Different" means that the contents vary in some way and overwriting is probably the optimum solution.
    • +
    '; + +$txt['languages_download_main_files'] = 'Primary Files'; +$txt['languages_download_theme_files'] = 'Theme-related Files'; +$txt['languages_download_filename'] = 'File Name'; +$txt['languages_download_dest'] = 'Destination'; +$txt['languages_download_writable'] = 'Writable'; +$txt['languages_download_version'] = 'Version'; +$txt['languages_download_older'] = 'You have a newer version of this file installed, overwriting is not recommended.'; +$txt['languages_download_exists'] = 'Already Exists'; +$txt['languages_download_exists_same'] = 'Identical'; +$txt['languages_download_exists_different'] = 'Different'; +$txt['languages_download_copy'] = 'Copy'; +$txt['languages_download_not_chmod'] = 'You cannot proceed with the installation until all files selected to be copied are writable.'; +$txt['languages_download_illegal_paths'] = 'Package contains illegal paths - please contact Simple Machines'; +$txt['languages_download_complete'] = 'Installation Complete'; +$txt['languages_download_complete_desc'] = 'Language pack installed successfully. Please click here to return to the languages page'; +$txt['languages_delete_confirm'] = 'Are you sure you want to delete this language?'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/ManageSmileys.english.php b/Themes/default/languages/ManageSmileys.english.php new file mode 100644 index 0000000..f60db9c --- /dev/null +++ b/Themes/default/languages/ManageSmileys.english.php @@ -0,0 +1,95 @@ +Here you can change the name and location of each smiley set - remember, however, that all sets share the same smileys.'; +$txt['smiley_editsmileys_explain'] = 'Change your smileys here by clicking on the smiley you want to modify. Remember that these smileys all have to exist in all the sets or some smileys won\'t show up! Don\'t forget to save after you are done editing!'; +$txt['smiley_setorder_explain'] = 'Change the order of the smileys here.'; +$txt['smiley_addsmiley_explain'] = 'Here you can add a new smiley - either from an existing file or by uploading new ones.'; + +$txt['smiley_set_select_default'] = 'Default Smiley Set'; +$txt['smiley_set_new'] = 'Create new smiley set'; +$txt['smiley_set_modify_existing'] = 'Modify existing smiley set'; +$txt['smiley_set_modify'] = 'Modify'; +$txt['smiley_set_import_directory'] = 'Import smileys already in this directory'; +$txt['smiley_set_import_single'] = 'There is one smiley in this smiley set not yet imported. Click'; +$txt['smiley_set_import_multiple'] = 'There are %1$d smileys in the directory that have not yet been imported. Click'; +$txt['smiley_set_to_import_single'] = 'to import it now.'; +$txt['smiley_set_to_import_multiple'] = 'to import them now.'; + +$txt['smileys_location'] = 'Location'; +$txt['smileys_location_form'] = 'Post form'; +$txt['smileys_location_hidden'] = 'Hidden'; +$txt['smileys_location_popup'] = 'Popup'; +$txt['smileys_modify'] = 'Modify'; +$txt['smileys_not_found_in_set'] = 'Smiley not found in set(s)'; +$txt['smileys_default_description'] = '(Insert a description)'; +$txt['smiley_new'] = 'Add new smiley'; +$txt['smiley_modify_existing'] = 'Modify smiley'; +$txt['smiley_preview'] = 'Preview'; +$txt['smiley_preview_using'] = 'using smiley set'; +$txt['smileys_confirm'] = 'Are you sure you want to remove these smileys?\\n\\nNote: This won\\\'t remove the images, just the choices.'; +$txt['smileys_location_form_description'] = 'These smileys will appear above the text area, when posting a new forum message or Personal Message.'; +$txt['smileys_location_popup_description'] = 'These smileys will be shown in a popup, that is shown after a user has clicked \'[more]\''; +$txt['smileys_move_select_destination'] = 'Select smiley destination'; +$txt['smileys_move_select_smiley'] = 'Select smiley to move'; +$txt['smileys_move_here'] = 'Move smiley to this location'; +$txt['smileys_no_entries'] = 'There are currently no smileys configured.'; + +$txt['icons_edit_icons_explain'] = 'From here you can change which message icons are available throughout your board. You can add, edit and remove icons, as well as limit their use to certain boards.'; +$txt['icons_edit_icons_all_boards'] = 'Available In All Boards'; +$txt['icons_board'] = 'Board'; +$txt['icons_confirm'] = 'Are you sure you wish to remove these icons?\\n\\nNote this will only stop new posters from using the icons, the images will remain.'; +$txt['icons_add_new'] = 'Add New Icon'; + +$txt['icons_edit_icon'] = 'Edit Message Icon'; +$txt['icons_new_icon'] = 'New Message Icon'; +$txt['icons_location_first_icon'] = 'As First Icon'; +$txt['icons_location_after'] = 'After'; +$txt['icons_filename_all_gif'] = 'All files must be "gif" files'; +$txt['icons_no_entries'] = 'There are currently no message icons configured.'; +?> \ No newline at end of file diff --git a/Themes/default/languages/Manual.english.php b/Themes/default/languages/Manual.english.php new file mode 100644 index 0000000..f12cf48 --- /dev/null +++ b/Themes/default/languages/Manual.english.php @@ -0,0 +1,36 @@ +Simple Machines Documentation Wiki and check out the credits to find out who has made SMF what it is today.'; + +$txt['manual_section_registering_title'] = 'Registering'; +$txt['manual_section_logging_in_title'] = 'Logging In'; +$txt['manual_section_profile_title'] = 'Profile'; +$txt['manual_section_search_title'] = 'Search'; +$txt['manual_section_posting_title'] = 'Posting'; +$txt['manual_section_bbc_title'] = 'Bulletin Board Code (BBC)'; +$txt['manual_section_personal_messages_title'] = 'Personal Messages'; +$txt['manual_section_memberlist_title'] = 'Memberlist'; +$txt['manual_section_calendar_title'] = 'Calendar'; +$txt['manual_section_features_title'] = 'Features'; + +$txt['manual_section_registering_desc'] = 'Many forums require users to register to gain full access.'; +$txt['manual_section_logging_in_desc'] = 'Once registered, users must login to access their account.'; +$txt['manual_section_profile_desc'] = 'Each member has their own personal profile.'; +$txt['manual_section_search_desc'] = 'Searching is an extremely helpful tool for finding information in posts and topics.'; +$txt['manual_section_posting_desc'] = 'The whole point of a forum, posting allows users to express themselves.'; +$txt['manual_section_bbc_desc'] = 'Posts can be spiced up with a little BBC.'; +$txt['manual_section_personal_messages_desc'] = 'Users can send personal messages to each other.'; +$txt['manual_section_memberlist_desc'] = 'The memberlist shows all the members of a forum.'; +$txt['manual_section_calendar_desc'] = 'Users can keep track of events, holidays, and birthdays with the calendar.'; +$txt['manual_section_features_desc'] = 'Here is a list of the most popular features in SMF.'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/ModerationCenter.english.php b/Themes/default/languages/ModerationCenter.english.php new file mode 100644 index 0000000..8195463 --- /dev/null +++ b/Themes/default/languages/ModerationCenter.english.php @@ -0,0 +1,145 @@ +here.'; +$txt['mc_group_requests'] = 'Membergroup Requests'; +$txt['mc_unapproved_posts'] = 'Unapproved Posts'; +$txt['mc_watched_users'] = 'Recent Watched Members'; +$txt['mc_watched_topics'] = 'Watched Topics'; +$txt['mc_scratch_board'] = 'Moderator Scratch Board'; +$txt['mc_latest_news'] = 'Simple Machines Latest News'; +$txt['mc_recent_reports'] = 'Recent Topic Reports'; +$txt['mc_warnings'] = 'Warnings'; +$txt['mc_notes'] = 'Moderator Notes'; + +$txt['mc_cannot_connect_sm'] = 'You are unable to connect to simplemachines.org\'s latest news file.'; + +$txt['mc_recent_reports_none'] = 'There are no outstanding reports'; +$txt['mc_watched_users_none'] = 'There are not currently any watches in place.'; +$txt['mc_group_requests_none'] = 'There are no open requests for group membership.'; + +$txt['mc_seen'] = '%1$s last seen %2$s'; +$txt['mc_seen_never'] = '%1$s never seen'; +$txt['mc_groupr_by'] = 'by'; + +$txt['mc_reported_posts_desc'] = 'Here you can review all the post reports raised by members of the community.'; +$txt['mc_reportedp_active'] = 'Active Reports'; +$txt['mc_reportedp_closed'] = 'Old Reports'; +$txt['mc_reportedp_by'] = 'by'; +$txt['mc_reportedp_reported_by'] = 'Reported By'; +$txt['mc_reportedp_last_reported'] = 'Last Reported'; +$txt['mc_reportedp_none_found'] = 'No Reports Found'; + +$txt['mc_reportedp_details'] = 'Details'; +$txt['mc_reportedp_close'] = 'Close'; +$txt['mc_reportedp_open'] = 'Open'; +$txt['mc_reportedp_ignore'] = 'Ignore'; +$txt['mc_reportedp_unignore'] = 'Un-Ignore'; +// Do not use numeric entries in the below string. +$txt['mc_reportedp_ignore_confirm'] = 'Are you sure you wish to ignore further reports about this message?\\n\\nThis will turn off further reports for all moderators of the forum.'; +$txt['mc_reportedp_close_selected'] = 'Close Selected'; + +$txt['mc_groupr_group'] = 'Membergroups'; +$txt['mc_groupr_member'] = 'Member'; +$txt['mc_groupr_reason'] = 'Reason'; +$txt['mc_groupr_none_found'] = 'There are currently no outstanding membergroup requests.'; +$txt['mc_groupr_submit'] = 'Submit'; +$txt['mc_groupr_reason_desc'] = 'Reason to reject %1$s\'s request to join "%2$s"'; +$txt['mc_groups_reason_title'] = 'Reasons for Rejection'; +$txt['with_selected'] = 'With Selected'; +$txt['mc_groupr_approve'] = 'Approve Request'; +$txt['mc_groupr_reject'] = 'Reject Request (No Reason)'; +$txt['mc_groupr_reject_w_reason'] = 'Reject Request with Reason'; +// Do not use numeric entries in the below string. +$txt['mc_groupr_warning'] = 'Are you sure you wish to do this?'; + +$txt['mc_unapproved_attachments_none_found'] = 'There are currently no attachments awaiting approval'; +$txt['mc_unapproved_replies_none_found'] = 'There are currently no posts awaiting approval'; +$txt['mc_unapproved_topics_none_found'] = 'There are currently no topics awaiting approval'; +$txt['mc_unapproved_posts_desc'] = 'From here you can approve or delete any posts awaiting moderation.'; +$txt['mc_unapproved_replies'] = 'Replies'; +$txt['mc_unapproved_topics'] = 'Topics'; +$txt['mc_unapproved_by'] = 'by'; +$txt['mc_unapproved_sure'] = 'Are you sure you want to do this?'; +$txt['mc_unapproved_attach_name'] = 'Attachment Name'; +$txt['mc_unapproved_attach_size'] = 'Filesize'; +$txt['mc_unapproved_attach_poster'] = 'Poster'; +$txt['mc_viewmodreport'] = 'Moderation Report for %1$s by %2$s'; +$txt['mc_modreport_summary'] = 'There have been %1$d report(s) concerning this post. The last report was %2$s.'; +$txt['mc_modreport_whoreported_title'] = 'Members who have reported this post'; +$txt['mc_modreport_whoreported_data'] = 'Reported by %1$s on %2$s. They left the following message:'; +$txt['mc_modreport_modactions'] = 'Actions taken by other moderators'; +$txt['mc_modreport_mod_comments'] = 'Moderator Comments'; +$txt['mc_modreport_no_mod_comment'] = 'There are not currently any moderator comments'; +$txt['mc_modreport_add_mod_comment'] = 'Add Comment'; + +$txt['show_notice'] = 'Notice Text'; +$txt['show_notice_subject'] = 'Subject'; +$txt['show_notice_text'] = 'Text'; + +$txt['mc_watched_users_title'] = 'Watched Members'; +$txt['mc_watched_users_desc'] = 'Here you can keep a track of all members who have been assigned a "watch" by the moderation team.'; +$txt['mc_watched_users_post'] = 'View by Post'; +$txt['mc_watched_users_warning'] = 'Warning Level'; +$txt['mc_watched_users_last_login'] = 'Last Login'; +$txt['mc_watched_users_last_post'] = 'Last Post'; +$txt['mc_watched_users_no_posts'] = 'There are no posts from watched members.'; +// Don't use entities in the two strings below. +$txt['mc_watched_users_delete_post'] = 'Are you sure you want to delete this post?'; +$txt['mc_watched_users_delete_posts'] = 'Are you sure you want to delete these posts?'; +$txt['mc_watched_users_posted'] = 'Posted'; +$txt['mc_watched_users_member'] = 'Member'; + +$txt['mc_warnings_description'] = 'From this section you can see which warnings have been issued to members of the forum. You can also add and modify the notification templates used when sending a warning to a member.'; +$txt['mc_warning_log'] = 'Log'; +$txt['mc_warning_templates'] = 'Custom Templates'; +$txt['mc_warning_log_title'] = 'Viewing Warning Log'; +$txt['mc_warning_templates_title'] = 'Custom Warning Templates'; + +$txt['mc_warnings_none'] = 'No warnings have been issued yet!'; +$txt['mc_warnings_recipient'] = 'Recipient'; + +$txt['mc_warning_templates_none'] = 'No warning templates have been created yet'; +$txt['mc_warning_templates_time'] = 'Time Created'; +$txt['mc_warning_templates_name'] = 'Template'; +$txt['mc_warning_templates_creator'] = 'Created By'; +$txt['mc_warning_template_add'] = 'Add Template'; +$txt['mc_warning_template_modify'] = 'Edit Template'; +$txt['mc_warning_template_delete'] = 'Delete Selected'; +$txt['mc_warning_template_delete_confirm'] = 'Are you sure you want to delete the selected templates?'; + +$txt['mc_warning_template_desc'] = 'Use this page to fill in the details of the template. Note that the subject for the email is not part of the template. Note that as the notification is sent by PM you can use BBC within the template. Note if you use the {MESSAGE} variable then this template will not be available when issuing a generic warning (i.e. A warning not linked to a post).'; +$txt['mc_warning_template_title'] = 'Template Title'; +$txt['mc_warning_template_body_desc'] = 'The content of the notification message. Note that you can use the following shortcuts in this template.
    • {MEMBER} - Member Name.
    • {MESSAGE} - Link to Offending Post. (If Applicable)
    • {FORUMNAME} - Forum Name.
    • {SCRIPTURL} - Web address of forum.
    • {REGARDS} - Standard email sign-off.
    '; +$txt['mc_warning_template_body_default'] = '{MEMBER},' . "\n\n" . 'You have received a warning for inappropriate activity. Please cease these activities and abide by the forum rules otherwise we will take further action.' . "\n\n" . '{REGARDS}'; +$txt['mc_warning_template_personal'] = 'Personal Template'; +$txt['mc_warning_template_personal_desc'] = 'If you select this option only you will be able to see, edit and use this template. If not selected all moderators will be able to use this template.'; +$txt['mc_warning_template_error_empty'] = 'You must set both a title and notification body.'; + +$txt['mc_prefs'] = 'Preferences'; +$txt['mc_settings'] = 'Change Settings'; +$txt['mc_prefs_title'] = 'Moderation Preferences'; +$txt['mc_prefs_desc'] = 'This section allows you to set some personal preferences for moderation related activities such as email notifications.'; +$txt['mc_prefs_homepage'] = 'Items to show on moderation homepage'; +$txt['mc_prefs_latest_news'] = 'SM News'; +$txt['mc_prefs_show_reports'] = 'Show open report count in forum header'; +$txt['mc_prefs_notify_report'] = 'Notify of topic reports'; +$txt['mc_prefs_notify_report_never'] = 'Never'; +$txt['mc_prefs_notify_report_moderator'] = 'Only if it\'s a board I moderate'; +$txt['mc_prefs_notify_report_always'] = 'Always'; +$txt['mc_prefs_notify_approval'] = 'Notify of items awaiting approval'; + +// Use entities in the below string. +$txt['mc_click_add_note'] = 'Add a new note'; +$txt['mc_add_note'] = 'Add'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/Modifications.english.php b/Themes/default/languages/Modifications.english.php new file mode 100644 index 0000000..4faae72 --- /dev/null +++ b/Themes/default/languages/Modifications.english.php @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/Themes/default/languages/Modlog.english.php b/Themes/default/languages/Modlog.english.php new file mode 100644 index 0000000..dd19970 --- /dev/null +++ b/Themes/default/languages/Modlog.english.php @@ -0,0 +1,87 @@ +Please note: Entries cannot be removed from this log until they are at least twenty-four hours old.'; +$txt['modlog_no_entries_found'] = 'There are currently no moderation log entries.'; +$txt['modlog_remove'] = 'Remove'; +$txt['modlog_removeall'] = 'Remove All'; +$txt['modlog_go'] = 'Go'; +$txt['modlog_add'] = 'Add'; +$txt['modlog_search'] = 'Quick Search'; +$txt['modlog_by'] = 'By'; +$txt['modlog_id'] = 'Deleted - ID:%1$d'; + +$txt['modlog_ac_add_warn_template'] = 'Added warning template: "{template}"'; +$txt['modlog_ac_modify_warn_template'] = 'Edited the warning template: "{template}"'; +$txt['modlog_ac_delete_warn_template'] = 'Deleted the warning template: "{template}"'; + +$txt['modlog_ac_ban'] = 'Added ban triggers:'; +$txt['modlog_ac_ban_trigger_member'] = ' Member: {member}'; +$txt['modlog_ac_ban_trigger_email'] = ' Email: {email}'; +$txt['modlog_ac_ban_trigger_ip_range'] = ' IP: {ip_range}'; +$txt['modlog_ac_ban_trigger_hostname'] = ' Hostname: {hostname}'; + +$txt['modlog_admin_log'] = 'Administration Log'; +$txt['modlog_admin_log_desc'] = 'Below is a list of administration actions which have been logged on your forum.
    Please note: Entries cannot be removed from this log until they are at least twenty-four hours old.'; +$txt['modlog_admin_log_no_entries_found'] = 'There are currently no administration log entries.'; + +// Admin type strings. +$txt['modlog_ac_upgrade'] = 'Upgraded the forum to version {version}'; +$txt['modlog_ac_install'] = 'Installed version {version}'; +$txt['modlog_ac_add_board'] = 'Added a new board: "{board}"'; +$txt['modlog_ac_edit_board'] = 'Edited the "{board}" board'; +$txt['modlog_ac_delete_board'] = 'Deleted the "{boardname}" board'; +$txt['modlog_ac_add_cat'] = 'Added a new category, "{catname}"'; +$txt['modlog_ac_edit_cat'] = 'Edited the "{catname}" category'; +$txt['modlog_ac_delete_cat'] = 'Deleted the "{catname}" category'; + +$txt['modlog_ac_delete_group'] = 'Deleted the "{group}" group'; +$txt['modlog_ac_add_group'] = 'Added the "{group}" group'; +$txt['modlog_ac_edited_group'] = 'Edited the "{group}" group'; +$txt['modlog_ac_added_to_group'] = 'Added "{member}" to the "{group}" group'; +$txt['modlog_ac_removed_from_group'] = 'Removed "{member}" from the "{group}" group'; +$txt['modlog_ac_removed_all_groups'] = 'Removed "{member}" from all groups'; + +$txt['modlog_ac_remind_member'] = 'Sent out a reminder to "{member}" to activate their account'; +$txt['modlog_ac_approve_member'] = 'Approved/Activated the account of "{member}"'; +$txt['modlog_ac_newsletter'] = 'Sent Newsletter'; + +$txt['modlog_ac_install_package'] = 'Installed new package: "{package}", version {version}'; +$txt['modlog_ac_upgrade_package'] = 'Upgraded package: "{package}" to version {version}'; +$txt['modlog_ac_uninstall_package'] = 'Uninstalled package: "{package}", version {version}'; + +// Restore topic. +$txt['modlog_ac_restore_topic'] = 'Restored topic "{topic}" from "{board}" to "{board_to}"'; +$txt['modlog_ac_restore_posts'] = 'Restored posts from "{subject}" to the topic "{topic}" in the "{board}" board.'; + +$txt['modlog_parameter_guest'] = 'Guest'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/Packages.english.php b/Themes/default/languages/Packages.english.php new file mode 100644 index 0000000..efa7b36 --- /dev/null +++ b/Themes/default/languages/Packages.english.php @@ -0,0 +1,262 @@ +strongly recommended that you do not continue with installation unless you know what you are doing, and have made a backup very recently. + This error may be caused by a conflict between the package you\'re trying to install and another package you have already installed, an error in the package, a package which requires another package that you don\'t have installed yet, or a package designed for another version of SMF.'; +// Don't use entities in the below string. +$txt['package_will_fail_popup'] = 'Are you sure you wish to continue installing this modification, even though it will not install successfully?'; +$txt['package_will_fail_popup_uninstall'] = 'Are you sure you wish to continue uninstalling this modification, even though it will not uninstall successfully?'; +$txt['package_install_now'] = 'Install Now'; +$txt['package_uninstall_now'] = 'Uninstall Now'; +$txt['package_other_themes'] = 'Install in Other Themes'; +$txt['package_other_themes_uninstall'] = 'UnInstall in Other Themes'; +$txt['package_other_themes_desc'] = 'To use this modification in themes other than the default, the package manager needs to make additional changes to the other themes. If you\'d like to install this modification in the other themes, please select these themes below.'; +// Don't use entities in the below string. +$txt['package_theme_failure_warning'] = 'At least one error was encountered during a test install of this theme. Are you sure you wish to attempt installation?'; + +$txt['package_bytes'] = 'bytes'; + +$txt['package_action_missing'] = 'File not found'; +$txt['package_action_error'] = 'Modification parse error'; +$txt['package_action_failure'] = 'Test failed'; +$txt['package_action_success'] = 'Test successful'; +$txt['package_action_skipping'] = 'Skipping file'; + +$txt['package_uninstall_actions'] = 'Uninstall Actions'; +$txt['package_uninstall_done'] = 'The package has been uninstalled, it should no longer take effect.'; +$txt['package_uninstall_cannot'] = 'This package cannot be uninstalled, because there is no uninstaller!

    Please contact the mod author for more information.'; + +$txt['package_install_options'] = 'Installation Options'; +$txt['package_install_options_ftp_why'] = 'Using the package manager\'s FTP functionality is the easiest way to avoid having to manually chmod the files writable through FTP yourself for the package manager to work.
    Here you can set the default values for some fields.'; +$txt['package_install_options_ftp_server'] = 'FTP Server'; +$txt['package_install_options_ftp_port'] = 'Port'; +$txt['package_install_options_ftp_user'] = 'Username'; +$txt['package_install_options_make_backups'] = 'Create Backup versions of replaced files with a tilde (~) on the end of their names.'; + +$txt['package_ftp_necessary'] = 'FTP Information Required'; +$txt['package_ftp_why'] = 'Some of the files the package manager needs to modify are not writable. This needs to be changed by logging into FTP and using it to chmod or create the files and folders. Your FTP information may be temporarily cached for proper operation of the package manager. Note you can also do this manually using an FTP client - to view a list of the affected files please click here.'; +$txt['package_ftp_why_file_list'] = 'The following files need to made writable to continue installation:'; +$txt['package_ftp_why_download'] = 'To download packages, the Packages directory and files in it need to be writable - and they are not currently. The package manager can use your FTP information to fix this.'; +$txt['package_ftp_server'] = 'FTP Server'; +$txt['package_ftp_port'] = 'Port'; +$txt['package_ftp_username'] = 'Username'; +$txt['package_ftp_password'] = 'Password'; +$txt['package_ftp_path'] = 'Local path to SMF'; +$txt['package_ftp_test'] = 'Test'; +$txt['package_ftp_test_connection'] = 'Test Connection'; +$txt['package_ftp_test_success'] = 'FTP connection established.'; +$txt['package_ftp_test_failed'] = 'Could not contact server.'; + +// For a break, use \\n instead of
    ... and don't use entities. +$txt['package_delete_bad'] = 'The package you are about to delete is currently installed! If you delete it, you may not be able to uninstall it later.\\n\\nAre you sure?'; + +$txt['package_examine_file'] = 'View file in package'; +$txt['package_file_contents'] = 'Contents of file'; + +$txt['package_upload_title'] = 'Upload a Package'; +$txt['package_upload_select'] = 'Package to Upload'; +$txt['package_upload'] = 'Upload'; +$txt['package_uploaded_success'] = 'Package uploaded successfully'; +$txt['package_uploaded_successfully'] = 'The package has been uploaded successfully'; + +$txt['package_modification_malformed'] = 'Malformed or invalid modification file.'; +$txt['package_modification_missing'] = 'The file could not be found.'; +$txt['package_no_zlib'] = 'Sorry, your PHP configuration doesn\'t have support for zlib. Without this, the package manager cannot function. Please contact your host about this for more information.'; + +$txt['package_cleanperms_title'] = 'Cleanup Permissions'; +$txt['package_cleanperms_desc'] = 'This interface allows you to reset the permissions for files throughout your installation, so as to increase security or solve any permission problems you may encounter while installing packages.'; +$txt['package_cleanperms_type'] = 'Change all file permissions throughout the forum such that'; +$txt['package_cleanperms_standard'] = 'Only the standard files are writable.'; +$txt['package_cleanperms_free'] = 'All files are writable.'; +$txt['package_cleanperms_restrictive'] = 'The minimum files are writable.'; +$txt['package_cleanperms_go'] = 'Change file permissions'; + +$txt['package_download_by_url'] = 'Download a package by url'; +$txt['package_download_filename'] = 'Name of the file'; +$txt['package_download_filename_info'] = 'Optional value. Should be used when the url does not end in the filename. For example: index.php?mod=5'; + +$txt['package_db_uninstall'] = 'Remove all data associated with this modification.'; +$txt['package_db_uninstall_details'] = 'Details'; +$txt['package_db_uninstall_actions'] = 'Checking this option will result in the following database changes'; +$txt['package_db_remove_table'] = 'Drop table "%1$s"'; +$txt['package_db_remove_column'] = 'Remove column "%2$s" from "%1$s"'; +$txt['package_db_remove_index'] = 'Remove index "%1$s" from "%2$s"'; + +$txt['package_advanced_button'] = 'Advanced'; +$txt['package_advanced_options'] = 'Advanced Options'; +$txt['package_apply'] = 'Apply'; +$txt['package_emulate'] = 'Emulate Version'; +$txt['package_emulate_revert'] = 'Revert'; +$txt['package_emulate_desc'] = 'Sometimes packages are locked to early versions of SMF but remain compatible with a newer version. Here you can choose to "emulate" a different SMF version within the package manager.'; + +// Operations. +$txt['operation_find'] = 'Find'; +$txt['operation_replace'] = 'Replace'; +$txt['operation_after'] = 'Add After'; +$txt['operation_before'] = 'Add Before'; +$txt['operation_title'] = 'Operations'; +$txt['operation_ignore'] = 'Ignore Errors'; +$txt['operation_invalid'] = 'The operation that you selected is invalid.'; + +$txt['package_file_perms_desc'] = 'You can use this section to review the writable status of critical files and folders within your forum directory. Note this only considers key forum folders and files - use an FTP client for additional options.'; +$txt['package_file_perms_name'] = 'File/Folder Name'; +$txt['package_file_perms_status'] = 'Current Status'; +$txt['package_file_perms_new_status'] = 'New Status'; +$txt['package_file_perms_status_read'] = 'Read'; +$txt['package_file_perms_status_write'] = 'Write'; +$txt['package_file_perms_status_execute'] = 'Execute'; +$txt['package_file_perms_status_custom'] = 'Custom'; +$txt['package_file_perms_status_no_change'] = 'No Change'; +$txt['package_file_perms_writable'] = 'Writable'; +$txt['package_file_perms_not_writable'] = 'Not Writable'; +$txt['package_file_perms_chmod'] = 'chmod'; +$txt['package_file_perms_more_files'] = 'More Files'; + +$txt['package_file_perms_change'] = 'Change File Permissions'; +$txt['package_file_perms_predefined'] = 'Use predefined permission profile'; +$txt['package_file_perms_predefined_note'] = 'Note that this only applies the predefined profile to key SMF folders and files.'; +$txt['package_file_perms_apply'] = 'Apply individual file permissions settings selected above.'; +$txt['package_file_perms_custom'] = 'If "Custom" has been selected use chmod value of'; +$txt['package_file_perms_pre_restricted'] = 'Restricted - minimum files writable'; +$txt['package_file_perms_pre_standard'] = 'Standard - key files writable'; +$txt['package_file_perms_pre_free'] = 'Free - all files writable'; +$txt['package_file_perms_ftp_details'] = 'On most servers it is only possible to change file permissions using an FTP account. Please enter your FTP details below'; +$txt['package_file_perms_ftp_retain'] = 'Note, SMF will only retain the password information temporarily to aid operation of the package manager.'; +$txt['package_file_perms_go'] = 'Make Changes'; + +$txt['package_file_perms_applying'] = 'Applying Changes'; +$txt['package_file_perms_items_done'] = '%1$d of %2$d items completed'; +$txt['package_file_perms_skipping_ftp'] = 'Warning: Failed to connect to FTP server, attempting to change permissions without. This is likely to fail - please check the results upon completion and try again with correct FTP details if necessary.'; + +$txt['package_file_perms_dirs_done'] = '%1$d of %2$d directories completed'; +$txt['package_file_perms_files_done'] = '%1$d of %2$d files done in current directory'; + +$txt['chmod_value_invalid'] = 'You have tried to enter an invalid chmod value. Chmod must be between 0444 and 0777'; + +$txt['package_restore_permissions'] = 'Restore File Permissions'; +$txt['package_restore_permissions_desc'] = 'The following file permissions were changed by SMF to install the selected package(s). You can return these files back to their original status by clicking "Restore" below.'; +$txt['package_restore_permissions_restore'] = 'Restore'; +$txt['package_restore_permissions_filename'] = 'Filename'; +$txt['package_restore_permissions_orig_status'] = 'Original Status'; +$txt['package_restore_permissions_cur_status'] = 'Current Status'; +$txt['package_restore_permissions_result'] = 'Result'; +$txt['package_restore_permissions_pre_change'] = '%1$s (%3$s)'; +$txt['package_restore_permissions_post_change'] = '%2$s (%3$s - was %2$s)'; +$txt['package_restore_permissions_action_skipped'] = 'Skipped'; +$txt['package_restore_permissions_action_success'] = 'Success'; +$txt['package_restore_permissions_action_failure'] = 'Failed'; +$txt['package_restore_permissions_action_done'] = 'SMF has attempted to restore the selected files back to their original permissions, the results can be seen below. If a change failed, or for a more detailed view of file permissions, please see the File Permissions section.'; + +$txt['package_file_perms_warning'] = 'Please Note'; +$txt['package_file_perms_warning_desc'] = ' +
  18. Be careful when changing file permissions from this section - incorrect permissions can adversely affect the operation of your forum!
  19. +
  20. On some server configurations selecting the wrong permissions may stop SMF from operating.
  21. +
  22. Certain directories such as attachments need to be writable to use that functionality.
  23. +
  24. This functionality is mainly applicable on non-Windows based servers - it will not work as expected on Windows in regards to permission flags.
  25. +
  26. Before proceeding make sure you have an FTP client installed in case you do make an error and need to FTP into the server to remedy it.
  27. '; + +$txt['package_confirm_view_package_content'] = 'Are you sure you want to view the package contents from this location:

    %1$s'; +$txt['package_confirm_proceed'] = 'Proceed'; +$txt['package_confirm_go_back'] = 'Go back'; + +$txt['package_readme_default'] = 'Default'; +$txt['package_available_readme_language'] = 'Available Readme Languages:'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/PersonalMessage.english.php b/Themes/default/languages/PersonalMessage.english.php new file mode 100644 index 0000000..ef51d59 --- /dev/null +++ b/Themes/default/languages/PersonalMessage.english.php @@ -0,0 +1,196 @@ +Note: You appear to have javascript disabled. We highly recommend you enable javascript to use this feature.
    '; +$txt['pm_rule_criteria'] = 'Criteria'; +$txt['pm_rule_criteria_add'] = 'Add Criteria'; +$txt['pm_rule_criteria_pick'] = 'Choose Criteria'; +$txt['pm_rule_mid'] = 'Sender Name'; +$txt['pm_rule_gid'] = 'Sender\'s Group'; +$txt['pm_rule_sub'] = 'Message Subject Contains'; +$txt['pm_rule_msg'] = 'Message Body Contains'; +$txt['pm_rule_bud'] = 'Sender is Buddy'; +$txt['pm_rule_sel_group'] = 'Select Group'; +$txt['pm_rule_logic'] = 'When Checking Criteria'; +$txt['pm_rule_logic_and'] = 'All criteria must be met'; +$txt['pm_rule_logic_or'] = 'Any criteria can be met'; +$txt['pm_rule_actions'] = 'Actions'; +$txt['pm_rule_sel_action'] = 'Select an Action'; +$txt['pm_rule_add_action'] = 'Add Action'; +$txt['pm_rule_label'] = 'Label message with'; +$txt['pm_rule_sel_label'] = 'Select Label'; +$txt['pm_rule_delete'] = 'Delete Message'; +$txt['pm_rule_no_name'] = 'You forgot to enter a name for the rule.'; +$txt['pm_rule_no_criteria'] = 'A rule must have at least one criteria and one action set.'; +$txt['pm_rule_too_complex'] = 'The rule you are creating is too long for SMF to store. Try breaking it up into smaller rules.'; + +$txt['pm_readable_and'] = 'and'; +$txt['pm_readable_or'] = 'or'; +$txt['pm_readable_start'] = 'If '; +$txt['pm_readable_end'] = '.'; +$txt['pm_readable_member'] = 'message is from "{MEMBER}"'; +$txt['pm_readable_group'] = 'sender is from the "{GROUP}" group'; +$txt['pm_readable_subject'] = 'message subject contains "{SUBJECT}"'; +$txt['pm_readable_body'] = 'message body contains "{BODY}"'; +$txt['pm_readable_buddy'] = 'sender is a buddy'; +$txt['pm_readable_label'] = 'apply label "{LABEL}"'; +$txt['pm_readable_delete'] = 'delete the message'; +$txt['pm_readable_then'] = 'then'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/Post.english.php b/Themes/default/languages/Post.english.php new file mode 100644 index 0000000..fae6d4d --- /dev/null +++ b/Themes/default/languages/Post.english.php @@ -0,0 +1,196 @@ + 'it\\\'s'. +$txt['bbc_quote'] = 'Insert Quote'; +$txt['list'] = 'Insert List'; +$txt['list_unordered'] = 'Insert Unordered List'; +$txt['list_ordered'] = 'Insert Ordered List'; + +$txt['change_color'] = 'Change Color'; +$txt['black'] = 'Black'; +$txt['red'] = 'Red'; +$txt['yellow'] = 'Yellow'; +$txt['pink'] = 'Pink'; +$txt['green'] = 'Green'; +$txt['orange'] = 'Orange'; +$txt['purple'] = 'Purple'; +$txt['blue'] = 'Blue'; +$txt['beige'] = 'Beige'; +$txt['brown'] = 'Brown'; +$txt['teal'] = 'Teal'; +$txt['navy'] = 'Navy'; +$txt['maroon'] = 'Maroon'; +$txt['lime_green'] = 'Lime Green'; +$txt['white'] = 'White'; +$txt['disable_smileys'] = 'Disable Smileys'; +$txt['dont_use_smileys'] = 'Don\'t use smileys.'; +// Escape any single quotes in here twice.. 'it\'s' -> 'it\\\'s'. +$txt['posted_on'] = 'Posted on'; +$txt['standard'] = 'Standard'; +$txt['thumbs_up'] = 'Thumb Up'; +$txt['thumbs_down'] = 'Thumb Down'; +$txt['excamation_point'] = 'Exclamation point'; +$txt['question_mark'] = 'Question mark'; +$txt['lamp'] = 'Lamp'; +$txt['add_smileys'] = 'Add Smileys'; +$txt['flash'] = 'Insert Flash'; +$txt['ftp'] = 'Insert FTP Link'; +$txt['image'] = 'Insert Image'; +$txt['table'] = 'Insert Table'; +$txt['table_td'] = 'Insert Table Column'; +$txt['topic_notify_no'] = 'There are no topics with Notification.'; +$txt['marquee'] = 'Marquee'; +$txt['teletype'] = 'Teletype'; +$txt['strike'] = 'Strikethrough'; +$txt['glow'] = 'Glow'; +$txt['shadow'] = 'Shadow'; +$txt['preformatted'] = 'Preformatted Text'; +$txt['left_align'] = 'Left Align'; +$txt['right_align'] = 'Right Align'; +$txt['superscript'] = 'Superscript'; +$txt['subscript'] = 'Subscript'; +$txt['table_tr'] = 'Insert Table Row'; +$txt['post_too_long'] = 'Your message is too long. Please go back and shorten it, then try again.'; +$txt['horizontal_rule'] = 'Horizontal Rule'; +$txt['font_size'] = 'Font Size'; +$txt['font_face'] = 'Font Face'; +$txt['toggle_view'] = 'Toggle View'; +$txt['unformat_text'] = 'Remove Formatting'; + +$txt['rich_edit_wont_work'] = 'Your browser does not support Rich Text editing.'; +$txt['rich_edit_function_disabled'] = 'Your browser does not support this function.'; + +// Use numeric entities in the below five strings. +$txt['notifyUnsubscribe'] = 'Unsubscribe to this topic by clicking here'; + +$txt['lock_after_post'] = 'Lock after Post'; +$txt['notify_replies'] = 'Notify me of replies.'; +$txt['lock_topic'] = 'Lock this topic.'; +$txt['shortcuts'] = 'shortcuts: hit alt+s to submit/post or alt+p to preview'; +$txt['shortcuts_firefox'] = 'shortcuts: hit shift+alt+s to submit/post or shift+alt+p to preview'; +$txt['option'] = 'Option'; +$txt['reset_votes'] = 'Reset Vote Count'; +$txt['reset_votes_check'] = 'Check this if you want to reset all vote counts to 0.'; +$txt['votes'] = 'votes'; +$txt['attach'] = 'Attach'; +$txt['clean_attach'] = 'Clear Attachment'; +$txt['attached'] = 'Attached'; +$txt['allowed_types'] = 'Allowed file types'; +$txt['cant_upload_type'] = 'You cannot upload that type of file. The only allowed extensions are'; +$txt['uncheck_unwatchd_attach'] = 'Uncheck the attachments you no longer want attached'; +$txt['restricted_filename'] = 'That is a restricted filename. Please try a different filename.'; +$txt['topic_locked_no_reply'] = 'Warning: topic is currently/will be locked!
    Only admins and moderators can reply.'; +$txt['awaiting_approval'] = 'Awaiting approval'; +$txt['attachment_requires_approval'] = 'Note that any files attached will not be displayed until approved by a moderator.'; +$txt['error_temp_attachments'] = 'There are attachments found, which you have attached before but not posted. These attachments are now attached to this post. If you do not want to include them in this post, you can remove them here.'; +// Use numeric entities in the below string. +$txt['js_post_will_require_approval'] = 'Reminder: This post will not appear until approved by a moderator.'; + +$txt['enter_comment'] = 'Enter comment'; +// Use numeric entities in the below two strings. +$txt['reported_post'] = 'Reported post'; +$txt['reported_to_mod_by'] = 'by'; +$txt['rtm10'] = 'Submit'; +// Use numeric entities in the below four strings. +$txt['report_following_post'] = 'The following post, "%1$s" by'; +$txt['reported_by'] = 'has been reported by'; +$txt['board_moderate'] = 'on a board you moderate'; +$txt['report_comment'] = 'The reporter has made the following comment'; + +$txt['attach_restrict_attachmentPostLimit'] = 'maximum total size %1$dKB'; +$txt['attach_restrict_attachmentSizeLimit'] = 'maximum individual size %1$dKB'; +$txt['attach_restrict_attachmentNumPerPostLimit'] = '%1$d per post'; +$txt['attach_restrictions'] = 'Restrictions:'; + +$txt['post_additionalopt'] = 'Attachments and other options'; +$txt['sticky_after'] = 'Sticky this topic.'; +$txt['move_after2'] = 'Move this topic.'; +$txt['back_to_topic'] = 'Return to this topic.'; +$txt['approve_this_post'] = 'Approve this Post'; + +$txt['retrieving_quote'] = 'Retrieving Quote...'; + +$txt['post_visual_verification_label'] = 'Verification'; +$txt['post_visual_verification_desc'] = 'Please enter the code in the image above to make this post.'; + +$txt['poll_options'] = 'Poll Options'; +$txt['poll_run'] = 'Run the poll for'; +$txt['poll_run_limit'] = '(Leave blank for no limit.)'; +$txt['poll_results_visibility'] = 'Result visibility'; +$txt['poll_results_anyone'] = 'Show the poll\'s results to anyone.'; +$txt['poll_results_voted'] = 'Only show the results after someone has voted.'; +$txt['poll_results_after'] = 'Only show the results after the poll has expired.'; +$txt['poll_max_votes'] = 'Maximum votes per user'; +$txt['poll_do_change_vote'] = 'Allow users to change vote'; +$txt['poll_too_many_votes'] = 'You selected too many options. For this poll, you may only select %1$s options.'; +$txt['poll_add_option'] = 'Add Option'; +$txt['poll_guest_vote'] = 'Allow guests to vote'; + +$txt['spellcheck_done'] = 'Spell checking complete.'; +$txt['spellcheck_change_to'] = 'Change To:'; +$txt['spellcheck_suggest'] = 'Suggestions:'; +$txt['spellcheck_change'] = 'Change'; +$txt['spellcheck_change_all'] = 'Change All'; +$txt['spellcheck_ignore'] = 'Ignore'; +$txt['spellcheck_ignore_all'] = 'Ignore All'; + +$txt['more_attachments'] = 'more attachments'; +// Don't use entities in the below string. +$txt['more_attachments_error'] = 'Sorry, you aren\'t allowed to post any more attachments.'; + +$txt['more_smileys'] = 'more'; +$txt['more_smileys_title'] = 'Additional smileys'; +$txt['more_smileys_pick'] = 'Pick a smiley'; +$txt['more_smileys_close_window'] = 'Close Window'; + +$txt['error_new_reply'] = 'Warning - while you were typing a new reply has been posted. You may wish to review your post.'; +$txt['error_new_replies'] = 'Warning - while you were typing %1$d new replies have been posted. You may wish to review your post.'; +$txt['error_new_reply_reading'] = 'Warning - while you were reading a new reply has been posted. You may wish to review your post.'; +$txt['error_new_replies_reading'] = 'Warning - while you were reading %1$d new replies have been posted. You may wish to review your post.'; + +$txt['announce_this_topic'] = 'Send an announcement about this topic to the members:'; +$txt['announce_title'] = 'Send an announcement'; +$txt['announce_desc'] = 'This form allows you to send an announcement to the selected membergroups about this topic.'; +$txt['announce_sending'] = 'Sending announcement of topic'; +$txt['announce_done'] = 'done'; +$txt['announce_continue'] = 'Continue'; +$txt['announce_topic'] = 'Announce topic.'; +$txt['announce_regular_members'] = 'Regular Members'; + +$txt['digest_subject_daily'] = 'Daily Digest'; +$txt['digest_subject_weekly'] = 'Weekly Digest'; +$txt['digest_intro_daily'] = 'Below is a summary of all activity in your subscribed boards and topics at %1$s today. To unsubscribe please visit the link below.'; +$txt['digest_intro_weekly'] = 'Below is a summary of all activity in your subscribed boards and topics at %1$s this week. To unsubscribe please visit the link below.'; +$txt['digest_new_topics'] = 'The following topics have been started'; +$txt['digest_new_topics_line'] = '"%1$s" in "%2$s"'; +$txt['digest_new_replies'] = 'Replies have been made in the following topics'; +$txt['digest_new_replies_one'] = '1 reply in "%1$s"'; +$txt['digest_new_replies_many'] = '%1$d replies in "%2$s"'; +$txt['digest_mod_actions'] = 'The following moderation actions have taken place'; +$txt['digest_mod_act_sticky'] = '"%1$s" was stickied'; +$txt['digest_mod_act_lock'] = '"%1$s" was locked'; +$txt['digest_mod_act_unlock'] = '"%1$s" was unlocked'; +$txt['digest_mod_act_remove'] = '"%1$s" was removed'; +$txt['digest_mod_act_move'] = '"%1$s" was moved'; +$txt['digest_mod_act_merge'] = '"%1$s" was merged'; +$txt['digest_mod_act_split'] = '"%1$s" was split'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/Profile.english.php b/Themes/default/languages/Profile.english.php new file mode 100644 index 0000000..3c959f8 --- /dev/null +++ b/Themes/default/languages/Profile.english.php @@ -0,0 +1,471 @@ +http://www.mypage.com/mypic.gif)'; +$txt['my_own_pic'] = 'Specify avatar by URL'; +$txt['date_format'] = 'The format here will be used to show dates throughout this forum.'; +$txt['time_format'] = 'Time Format'; +$txt['display_name_desc'] = 'This is the displayed name that people will see.'; +$txt['personal_time_offset'] = 'Number of hours to +/- to make displayed time equal to your local time.'; +$txt['dob'] = 'Birthdate'; +$txt['dob_month'] = 'Month (MM)'; +$txt['dob_day'] = 'Day (DD)'; +$txt['dob_year'] = 'Year (YYYY)'; +$txt['password_strength'] = 'For best security, you should use eight or more characters with a combination of letters, numbers, and symbols.'; +$txt['include_website_url'] = 'This must be included if you specify a URL below.'; +$txt['complete_url'] = 'This must be a complete URL.'; +$txt['your_icq'] = 'This is your ICQ number.'; +$txt['your_aim'] = 'This is your AOL Instant Messenger nickname.'; +$txt['your_yim'] = 'This is your Yahoo! Instant Messenger nickname.'; +$txt['sig_info'] = 'Signatures are displayed at the bottom of each post or personal message. BBCode and smileys may be used in your signature.'; +$txt['max_sig_characters'] = 'Max characters: %1$d; characters remaining: '; +$txt['send_member_pm'] = 'Send this member a personal message'; +$txt['hidden'] = 'hidden'; +$txt['current_time'] = 'Current forum time'; +$txt['digits_only'] = 'The \'number of posts\' box can only contain digits.'; + +$txt['language'] = 'Language'; +$txt['avatar_too_big'] = 'Avatar image is too big, please resize it and try again (max'; +$txt['invalid_registration'] = 'Invalid Date Registered value, valid example:'; +$txt['msn_email_address'] = 'Your MSN messenger email address'; +$txt['current_password'] = 'Current Password'; +// Don't use entities in the below string, except the main ones. (lt, gt, quot.) +$txt['required_security_reasons'] = 'For security reasons, your current password is required to make changes to your account.'; + +$txt['timeoffset_autodetect'] = '(auto detect)'; + +$txt['secret_question'] = 'Secret Question'; +$txt['secret_desc'] = 'To help retrieve your password, enter a question here with an answer that only you know.'; +$txt['secret_desc2'] = 'Choose carefully, you wouldn\'t want someone guessing your answer!'; +$txt['secret_answer'] = 'Answer'; +$txt['secret_ask'] = 'Ask me my question'; +$txt['cant_retrieve'] = 'You can\'t retrieve your password, but you can set a new one by following a link sent to you by email. You also have the option of setting a new password by answering your secret question.'; +$txt['incorrect_answer'] = 'Sorry, but you did not specify a valid combination of Secret Question and Answer in your profile. Please click on the back button, and use the default method of obtaining your password.'; +$txt['enter_new_password'] = 'Please enter the answer to your question, and the password you would like to use. Your password will be changed to the one you select provided you answer the question correctly.'; +$txt['password_success'] = 'Your password was changed successfully.
    Click here to login.'; +$txt['secret_why_blank'] = 'why is this blank?'; + +$txt['authentication_reminder'] = 'Authentication Reminder'; +$txt['password_reminder_desc'] = 'If you\'ve forgotten your login details, don\'t worry, they can be retrieved. To start this process please enter your username or email address below.'; +$txt['authentication_options'] = 'Please select one of the two options below'; +$txt['authentication_openid_email'] = 'Email me a reminder of my OpenID identity'; +$txt['authentication_openid_secret'] = 'Answer my "secret question" to display my OpenID identity'; +$txt['authentication_password_email'] = 'Email me a new password'; +$txt['authentication_password_secret'] = 'Let me set a new password by answering my "secret question"'; +$txt['openid_secret_reminder'] = 'Please enter your answer to the question below. If you get it correct your OpenID identity will be shown.'; +$txt['reminder_openid_is'] = 'The OpenID identity associated with your account is:
        %1$s

    Please make a note of this for future reference.'; +$txt['reminder_continue'] = 'Continue'; + +$txt['current_theme'] = 'Current Theme'; +$txt['change'] = '(change)'; +$txt['theme_preferences'] = 'Theme preferences'; +$txt['theme_forum_default'] = 'Forum or Board Default'; +$txt['theme_forum_default_desc'] = 'This is the default theme, which means your theme will change along with the administrator\'s settings and the board you are viewing.'; + +$txt['profileConfirm'] = 'Do you really want to delete this member?'; + +$txt['custom_title'] = 'Custom Title'; + +$txt['lastLoggedIn'] = 'Last Active'; + +$txt['notify_settings'] = 'Notification Settings:'; +$txt['notify_save'] = 'Save settings'; +$txt['notify_important_email'] = 'Receive forum newsletters, announcements and important notifications by email.'; +$txt['notify_regularity'] = 'For topics and boards I\'ve requested notification on, notify me'; +$txt['notify_regularity_instant'] = 'Instantly'; +$txt['notify_regularity_first_only'] = 'Instantly - but only for the first unread reply'; +$txt['notify_regularity_daily'] = 'Daily'; +$txt['notify_regularity_weekly'] = 'Weekly'; +$txt['auto_notify'] = 'Turn notification on when you post or reply to a topic.'; +$txt['notify_send_types'] = 'For topics and boards I\'ve requested notification on, notify me of'; +$txt['notify_send_type_everything'] = 'Replies and moderation'; +$txt['notify_send_type_everything_own'] = 'Moderation only if I started the topic'; +$txt['notify_send_type_only_replies'] = 'Only replies'; +$txt['notify_send_type_nothing'] = 'Nothing at all'; +$txt['notify_send_body'] = 'When sending notification of a reply to a topic, send the post in the email (but please don\'t reply to these emails.)'; + +$txt['notifications_topics'] = 'Current Topic Notifications'; +$txt['notifications_topics_list'] = 'You are being notified of replies to the following topics'; +$txt['notifications_topics_none'] = 'You are not currently receiving any notifications from topics.'; +$txt['notifications_topics_howto'] = 'To receive notifications from a topic, click the "notify" button while viewing it.'; +$txt['notifications_boards'] = 'Current Board Notifications'; +$txt['notifications_boards_list'] = 'You are being notified of new topics posted in the following boards'; +$txt['notifications_boards_none'] = 'You aren\'t receiving notifications on any boards right now.'; +$txt['notifications_boards_howto'] = 'To request notifications from a specific board, click the "notify" button in the index of that board.'; +$txt['notifications_update'] = 'Unnotify'; + +$txt['statPanel_showStats'] = 'User statistics for: '; +$txt['statPanel_users_votes'] = 'Number of Votes Cast'; +$txt['statPanel_users_polls'] = 'Number of Polls Created'; +$txt['statPanel_total_time_online'] = 'Total Time Spent Online'; +$txt['statPanel_noPosts'] = 'No posts to speak of!'; +$txt['statPanel_generalStats'] = 'General Statistics'; +$txt['statPanel_posts'] = 'posts'; +$txt['statPanel_topics'] = 'topics'; +$txt['statPanel_total_posts'] = 'Total Posts'; +$txt['statPanel_total_topics'] = 'Total Topics Started'; +$txt['statPanel_votes'] = 'votes'; +$txt['statPanel_polls'] = 'polls'; +$txt['statPanel_topBoards'] = 'Most Popular Boards By Posts'; +$txt['statPanel_topBoards_posts'] = '%1$d posts of the board\'s %2$d posts (%3$01.2f%%)'; +$txt['statPanel_topBoards_memberposts'] = '%1$d posts of the member\'s %2$d posts (%3$01.2f%%)'; +$txt['statPanel_topBoardsActivity'] = 'Most Popular Boards By Activity'; +$txt['statPanel_activityTime'] = 'Posting Activity By Time'; +$txt['statPanel_activityTime_posts'] = '%1$d posts (%2$d%%)'; +$txt['statPanel_timeOfDay'] = 'Time of Day'; + +$txt['deleteAccount_warning'] = 'Warning - These actions are irreversible!'; +$txt['deleteAccount_desc'] = 'From this page you can delete this user\'s account and posts.'; +$txt['deleteAccount_member'] = 'Delete this member\'s account'; +$txt['deleteAccount_posts'] = 'Remove posts made by this member'; +$txt['deleteAccount_none'] = 'None'; +$txt['deleteAccount_all_posts'] = 'All Posts'; +$txt['deleteAccount_topics'] = 'Topics and Posts'; +$txt['deleteAccount_confirm'] = 'Are you completely sure you want to delete this account?'; +$txt['deleteAccount_approval'] = 'Please note that the forum moderators will have to approve this account\'s deletion before it will be removed.'; + +$txt['profile_of_username'] = 'Profile of %1$s'; +$txt['profileInfo'] = 'Profile Info'; +$txt['showPosts'] = 'Show Posts'; +$txt['showPosts_help'] = 'This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.'; +$txt['showMessages'] = 'Messages'; +$txt['showTopics'] = 'Topics'; +$txt['showAttachments'] = 'Attachments'; +$txt['statPanel'] = 'Show Stats'; +$txt['editBuddyIgnoreLists'] = 'Buddies/Ignore List'; +$txt['editBuddies'] = 'Edit Buddies'; +$txt['editIgnoreList'] = 'Edit Ignore List'; +$txt['trackUser'] = 'Track User'; +$txt['trackActivity'] = 'Activity'; +$txt['trackIP'] = 'IP Address'; + +$txt['authentication'] = 'Authentication'; +$txt['change_authentication'] = 'From this section you can change how you login to the forum. You may choose to either use an OpenID account for your authentication, or alternatively switch to use a username and password.'; + +$txt['profileEdit'] = 'Modify Profile'; +$txt['account_info'] = 'These are your account settings. This page holds all critical information that identifies you on this forum. For security reasons, you will need to enter your (current) password to make changes to this information.'; +$txt['forumProfile_info'] = 'You can change your personal information on this page. This information will be displayed throughout ' . $context['forum_name_html_safe'] . '. If you aren\'t comfortable with sharing some information, simply skip it - nothing here is required.'; +$txt['theme'] = 'Look and Layout'; +$txt['theme_info'] = 'This section allows you to customize the look and layout of the forum.'; +$txt['notification'] = 'Notifications'; +$txt['notification_info'] = 'SMF allows you to be notified of replies to posts, newly posted topics, and forum announcements. You can change those settings here, or oversee the topics and boards you are currently receiving notifications for.'; +$txt['groupmembership'] = 'Group Membership'; +$txt['groupMembership_info'] = 'In this section of your profile you can change which groups you belong to.'; +$txt['ignoreboards'] = 'Ignore Boards Options'; +$txt['ignoreboards_info'] = 'This page lets you ignore particular boards. When a board is ignored, the new post indicator will not show up on the board index. New posts will not show up using the "unread post" search link (when searching it will not look in those boards) however, ignored boards will still appear on the board index and upon entering will show which topics have new posts. When using the "unread replies" link, new posts in an ignored board will still be shown.'; +$txt['pmprefs'] = 'Personal Messaging'; + +$txt['profileAction'] = 'Actions'; +$txt['deleteAccount'] = 'Delete this account'; +$txt['profileSendIm'] = 'Send personal message'; +$txt['profile_sendpm_short'] = 'Send PM'; + +$txt['profileBanUser'] = 'Ban this user'; + +$txt['display_name'] = 'Display name'; +$txt['enter_ip'] = 'Enter IP (range)'; +$txt['errors_by'] = 'Error messages by'; +$txt['errors_desc'] = 'Below is a list of all the recent errors that this user has generated/experienced.'; +$txt['errors_from_ip'] = 'Error messages from IP (range)'; +$txt['errors_from_ip_desc'] = 'Below is a list of all recent error messages generated by this IP (range).'; +$txt['ip_address'] = 'IP address'; +$txt['ips_in_errors'] = 'IPs used in error messages'; +$txt['ips_in_messages'] = 'IPs used in recent posts'; +$txt['members_from_ip'] = 'Members from IP (range)'; +$txt['members_in_range'] = 'Members possibly in the same range'; +$txt['messages_from_ip'] = 'Messages posted from IP (range)'; +$txt['messages_from_ip_desc'] = 'Below is a list of all messages posted from this IP (range).'; +$txt['most_recent_ip'] = 'Most recent IP address'; +$txt['why_two_ip_address'] = 'Why are there two IP addresses listed?'; +$txt['no_errors_from_ip'] = 'No error messages from the specified IP (range) found'; +$txt['no_errors_from_user'] = 'No error messages from the specified user found'; +$txt['no_members_from_ip'] = 'No members from the specified IP (range) found'; +$txt['no_messages_from_ip'] = 'No messages from the specified IP (range) found'; +$txt['none'] = 'None'; +$txt['own_profile_confirm'] = 'Are you sure you want to delete your account?'; +$txt['view_ips_by'] = 'View IPs used by'; + +$txt['avatar_will_upload'] = 'Upload an avatar'; + +$txt['activate_changed_email_title'] = 'Email Address Changed'; +$txt['activate_changed_email_desc'] = 'You\'ve changed your email address. In order to validate this address you will receive an email. Click the link in that email to reactivate your account.'; + +// Use numeric entities in the below three strings. +$txt['no_reminder_email'] = 'Unable to send reminder email.'; +$txt['send_email'] = 'Send an email to'; +$txt['to_ask_password'] = 'to ask for your authentication details'; + +$txt['user_email'] = 'Username/Email'; + +// Use numeric entities in the below two strings. +$txt['reminder_subject'] = 'New password for ' . $context['forum_name']; +$txt['reminder_mail'] = 'This mail was sent because the \'forgot password\' function has been applied to your account. To set a new password, click the following link'; +$txt['reminder_sent'] = 'A mail has been sent to your email address. Click the link in that mail to set a new password.'; +$txt['reminder_openid_sent'] = 'Your current OpenID identity has been sent to your email address.'; +$txt['reminder_set_password'] = 'Set Password'; +$txt['reminder_password_set'] = 'Password successfully set'; +$txt['reminder_error'] = '%1$s failed to answer their secret question correctly when attempting to change a forgotten password.'; + +$txt['registration_not_approved'] = 'Sorry, this account has not yet been approved. If you need to change your email address please click'; +$txt['registration_not_activated'] = 'Sorry, this account has not yet been activated. If you need to resend the activation email please click'; + +$txt['primary_membergroup'] = 'Primary Membergroup'; +$txt['additional_membergroups'] = 'Additional Membergroups'; +$txt['additional_membergroups_show'] = '[ show additional groups ]'; +$txt['no_primary_membergroup'] = '(no primary membergroup)'; +$txt['deadmin_confirm'] = 'Are you sure you wish to irrevocably remove your admin status?'; + +$txt['account_activate_method_2'] = 'Account requires reactivation after email change'; +$txt['account_activate_method_3'] = 'Account is not approved'; +$txt['account_activate_method_4'] = 'Account is awaiting approval for deletion'; +$txt['account_activate_method_5'] = 'Account is an "under age" account awaiting approval'; +$txt['account_not_activated'] = 'Account is currently not activated'; +$txt['account_activate'] = 'activate'; +$txt['account_approve'] = 'approve'; +$txt['user_is_banned'] = 'User is currently banned'; +$txt['view_ban'] = 'View'; +$txt['user_banned_by_following'] = 'This user is currently affected by the following bans'; +$txt['user_cannot_due_to'] = 'User cannot %1$s as a result of ban: "%2$s"'; +$txt['ban_type_post'] = 'post'; +$txt['ban_type_register'] = 'register'; +$txt['ban_type_login'] = 'login'; +$txt['ban_type_access'] = 'access forum'; + +$txt['show_online'] = 'Show others my online status'; + +$txt['return_to_post'] = 'Return to topics after posting by default.'; +$txt['no_new_reply_warning'] = 'Don\'t warn on new replies made while posting.'; +$txt['posts_apply_ignore_list'] = 'Hide messages posted by members on my ignore list.'; +$txt['recent_posts_at_top'] = 'Show most recent posts at the top.'; +$txt['recent_pms_at_top'] = 'Show most recent personal messages at top.'; +$txt['wysiwyg_default'] = 'Show WYSIWYG editor on post page by default.'; + +$txt['timeformat_default'] = '(Forum Default)'; +$txt['timeformat_easy1'] = 'Month Day, Year, HH:MM:SS am/pm'; +$txt['timeformat_easy2'] = 'Month Day, Year, HH:MM:SS (24 hour)'; +$txt['timeformat_easy3'] = 'YYYY-MM-DD, HH:MM:SS'; +$txt['timeformat_easy4'] = 'DD Month YYYY, HH:MM:SS'; +$txt['timeformat_easy5'] = 'DD-MM-YYYY, HH:MM:SS'; + +$txt['poster'] = 'Poster'; + +$txt['board_desc_inside'] = 'Show board descriptions inside boards.'; +$txt['show_children'] = 'Show child boards on every page inside boards, not just the first.'; +$txt['use_sidebar_menu'] = 'Use sidebar menus instead of dropdown menus when possible.'; +$txt['show_no_avatars'] = 'Don\'t show users\' avatars.'; +$txt['show_no_signatures'] = 'Don\'t show users\' signatures.'; +$txt['show_no_censored'] = 'Leave words uncensored.'; +$txt['topics_per_page'] = 'Topics to display per page:'; +$txt['messages_per_page'] = 'Messages to display per page:'; +$txt['per_page_default'] = 'forum default'; +$txt['calendar_start_day'] = 'First day of the week on the calendar'; +$txt['display_quick_reply'] = 'Use quick reply on topic display: '; +$txt['display_quick_reply1'] = 'don\'t show at all'; +$txt['display_quick_reply2'] = 'show, off by default'; +$txt['display_quick_reply3'] = 'show, on by default'; +$txt['display_quick_mod'] = 'Show quick-moderation as '; +$txt['display_quick_mod_none'] = 'don\'t show.'; +$txt['display_quick_mod_check'] = 'checkboxes.'; +$txt['display_quick_mod_image'] = 'icons.'; + +$txt['whois_title'] = 'Look up IP on a regional whois-server'; +$txt['whois_afrinic'] = 'AfriNIC (Africa)'; +$txt['whois_apnic'] = 'APNIC (Asia Pacific region)'; +$txt['whois_arin'] = 'ARIN (North America, a portion of the Caribbean and sub-Saharan Africa)'; +$txt['whois_lacnic'] = 'LACNIC (Latin American and Caribbean region)'; +$txt['whois_ripe'] = 'RIPE (Europe, the Middle East and parts of Africa and Asia)'; + +$txt['moderator_why_missing'] = 'why isn\'t moderator here?'; +$txt['username_change'] = 'change'; +$txt['username_warning'] = 'To change this member\'s username, the forum must also reset their password, which will be emailed to the member with their new username.'; + +$txt['show_member_posts'] = 'View Member Posts'; +$txt['show_member_topics'] = 'View Member Topics'; +$txt['show_member_attachments'] = 'View Member Attachments'; +$txt['show_posts_none'] = 'No posts have been posted yet.'; +$txt['show_topics_none'] = 'No topics have been posted yet.'; +$txt['show_attachments_none'] = 'No attachments have been posted yet.'; +$txt['show_attach_filename'] = 'Filename'; +$txt['show_attach_downloads'] = 'Downloads'; +$txt['show_attach_posted'] = 'Posted'; + +$txt['showPermissions'] = 'Show Permissions'; +$txt['showPermissions_status'] = 'Permission status'; +$txt['showPermissions_help'] = 'This section allows you to view all permissions for this member (denied permissions are struck out).'; +$txt['showPermissions_given'] = 'Given by'; +$txt['showPermissions_denied'] = 'Denied by'; +$txt['showPermissions_permission'] = 'Permission (denied permissions are struck out)'; +$txt['showPermissions_none_general'] = 'This member has no general permissions set.'; +$txt['showPermissions_none_board'] = 'This member has no board specific permissions set.'; +$txt['showPermissions_all'] = 'As an administrator, this member has all possible permissions.'; +$txt['showPermissions_select'] = 'Board specific permissions for'; +$txt['showPermissions_general'] = 'General Permissions'; +$txt['showPermissions_global'] = 'All boards'; +$txt['showPermissions_restricted_boards'] = 'Restricted boards'; +$txt['showPermissions_restricted_boards_desc'] = 'The following boards are not accessible by this user'; + +$txt['local_time'] = 'Local Time'; +$txt['posts_per_day'] = 'per day'; + +$txt['buddy_ignore_desc'] = 'This area allows you to maintain your buddy and ignore lists for this forum. Adding members to these lists will, amongst other things, help control mail and PM traffic, depending on your preferences.'; + +$txt['buddy_add'] = 'Add To Buddy List'; +$txt['buddy_remove'] = 'Remove From Buddy List'; +$txt['buddy_add_button'] = 'Add'; +$txt['no_buddies'] = 'Your buddy list is currently empty'; + +$txt['ignore_add'] = 'Add To Ignore List'; +$txt['ignore_remove'] = 'Remove From Ignore List'; +$txt['ignore_add_button'] = 'Add'; +$txt['no_ignore'] = 'Your ignore list is currently empty'; + +$txt['regular_members'] = 'Registered Members'; +$txt['regular_members_desc'] = 'Every member of the forum is a member of this group.'; +$txt['group_membership_msg_free'] = 'Your group membership was successfully updated.'; +$txt['group_membership_msg_request'] = 'Your request has been submitted, please be patient while the request is considered.'; +$txt['group_membership_msg_primary'] = 'Your primary group has been updated'; +$txt['current_membergroups'] = 'Current Membergroups'; +$txt['available_groups'] = 'Available Groups'; +$txt['join_group'] = 'Join Group'; +$txt['leave_group'] = 'Leave Group'; +$txt['request_group'] = 'Request Membership'; +$txt['approval_pending'] = 'Approval Pending'; +$txt['make_primary'] = 'Make Primary Group'; + +$txt['request_group_membership'] = 'Request Group Membership'; +$txt['request_group_membership_desc'] = 'Before you can join this group your membership must be approved by the moderator. Please give a reason for joining this group'; +$txt['submit_request'] = 'Submit Request'; + +$txt['profile_updated_own'] = 'Your profile has been updated successfully.'; +$txt['profile_updated_else'] = 'The profile for %1$s has been updated successfully.'; + +$txt['profile_error_signature_max_length'] = 'Your signature cannot be greater than %1$d characters'; +$txt['profile_error_signature_max_lines'] = 'Your signature cannot span more than %1$d lines'; +$txt['profile_error_signature_max_image_size'] = 'Images in your signature must be no greater than %1$dx%2$d pixels'; +$txt['profile_error_signature_max_image_width'] = 'Images in your signature must be no wider than %1$d pixels'; +$txt['profile_error_signature_max_image_height'] = 'Images in your signature must be no higher than %1$d pixels'; +$txt['profile_error_signature_max_image_count'] = 'You cannot have more than %1$d images in your signature'; +$txt['profile_error_signature_max_font_size'] = 'Text in your signature must be smaller than %1$s in size'; +$txt['profile_error_signature_allow_smileys'] = 'You are not allowed to use any smileys within your signature'; +$txt['profile_error_signature_max_smileys'] = 'You are not allowed to use more than %1$d smileys within your signature'; +$txt['profile_error_signature_disabled_bbc'] = 'The following BBC is not allowed within your signature: %1$s'; + +$txt['profile_view_warnings'] = 'View Warnings'; +$txt['profile_issue_warning'] = 'Issue a Warning'; +$txt['profile_warning_level'] = 'Warning Level'; +$txt['profile_warning_desc'] = 'From this section you can adjust the user\'s warning level and issue them with a written warning if necessary. You can also track their warning history and view the effects of their current warning level as determined by the administrator.'; +$txt['profile_warning_name'] = 'Member Name'; +$txt['profile_warning_impact'] = 'Result'; +$txt['profile_warning_reason'] = 'Reason for Warning'; +$txt['profile_warning_reason_desc'] = 'This is required and will be logged.'; +$txt['profile_warning_effect_none'] = 'None.'; +$txt['profile_warning_effect_watch'] = 'User will be added to moderator watch list.'; +$txt['profile_warning_effect_own_watched'] = 'You are on the moderator watch list.'; +$txt['profile_warning_is_watch'] = 'being watched'; +$txt['profile_warning_effect_moderation'] = 'All users posts will be moderated.'; +$txt['profile_warning_effect_own_moderated'] = 'All your posts will be moderated.'; +$txt['profile_warning_is_moderation'] = 'posts are moderated'; +$txt['profile_warning_effect_mute'] = 'User will not be able to post.'; +$txt['profile_warning_effect_own_muted'] = 'You will not be able to post.'; +$txt['profile_warning_is_muted'] = 'cannot post'; +$txt['profile_warning_effect_text'] = 'Level >= %1$d: %2$s'; +$txt['profile_warning_notify'] = 'Send a Notification'; +$txt['profile_warning_notify_template'] = 'Select template:'; +$txt['profile_warning_notify_subject'] = 'Notification Subject'; +$txt['profile_warning_notify_body'] = 'Notification Message'; +$txt['profile_warning_notify_template_subject'] = 'You have received a warning'; +// Use numeric entities in below string. +$txt['profile_warning_notify_template_outline'] = '{MEMBER},' . "\n\n" . 'You have received a warning for %1$s. Please cease these activities and abide by the forum rules otherwise we will take further action.' . "\n\n" . '{REGARDS}'; +$txt['profile_warning_notify_template_outline_post'] = '{MEMBER},' . "\n\n" . 'You have received a warning for %1$s in regards to the message:' . "\n" . '{MESSAGE}.' . "\n\n" . 'Please cease these activities and abide by the forum rules otherwise we will take further action.' . "\n\n" . '{REGARDS}'; +$txt['profile_warning_notify_for_spamming'] = 'spamming'; +$txt['profile_warning_notify_title_spamming'] = 'Spamming'; +$txt['profile_warning_notify_for_offence'] = 'posting offensive material'; +$txt['profile_warning_notify_title_offence'] = 'Posting Offensive Material'; +$txt['profile_warning_notify_for_insulting'] = 'insulting other users and/or staff members'; +$txt['profile_warning_notify_title_insulting'] = 'Insulting Users/Staff'; +$txt['profile_warning_issue'] = 'Issue Warning'; +$txt['profile_warning_max'] = '(Max 100)'; +$txt['profile_warning_limit_attribute'] = 'Note you can not adjust this user\'s level by more than %1$d%% in a 24 hour period.'; +$txt['profile_warning_errors_occured'] = 'Warning has not been sent due to following errors'; +$txt['profile_warning_success'] = 'Warning Successfully Issued'; +$txt['profile_warning_new_template'] = 'New Template'; + +$txt['profile_warning_previous'] = 'Previous Warnings'; +$txt['profile_warning_previous_none'] = 'This user has not received any previous warnings.'; +$txt['profile_warning_previous_issued'] = 'Issued By'; +$txt['profile_warning_previous_time'] = 'Time'; +$txt['profile_warning_previous_level'] = 'Points'; +$txt['profile_warning_previous_reason'] = 'Reason'; +$txt['profile_warning_previous_notice'] = 'View Notice Sent to Member'; + +$txt['viewwarning'] = 'View Warnings'; +$txt['profile_viewwarning_for_user'] = 'Warnings for %1$s'; +$txt['profile_viewwarning_no_warnings'] = 'No warnings have yet been issued.'; +$txt['profile_viewwarning_desc'] = 'Below is a summary of all the warnings that have been issued by the forum moderation team.'; +$txt['profile_viewwarning_previous_warnings'] = 'Previous Warnings'; +$txt['profile_viewwarning_impact'] = 'Warning Impact'; + +$txt['subscriptions'] = 'Paid Subscriptions'; + +$txt['pm_settings_desc'] = 'From this page you can change a variety of personal messaging options, including how messages are displayed and who may send them to you.'; +$txt['email_notify'] = 'Notify by email every time you receive a personal message:'; +$txt['email_notify_never'] = 'Never'; +$txt['email_notify_buddies'] = 'From Buddies Only'; +$txt['email_notify_always'] = 'Always'; + +$txt['pm_receive_from'] = 'Receive personal messages from:'; +$txt['pm_receive_from_everyone'] = 'All members'; +$txt['pm_receive_from_ignore'] = 'All members, except those on my ignore list'; +$txt['pm_receive_from_admins'] = 'Administrators only'; +$txt['pm_receive_from_buddies'] = 'Buddies and Administrators only'; + +$txt['copy_to_outbox'] = 'Save a copy of each personal message in my sent items by default.'; +$txt['popup_messages'] = 'Show a popup when I receive new messages.'; +$txt['pm_remove_inbox_label'] = 'Remove the inbox label when applying another label'; +$txt['pm_display_mode'] = 'Display personal messages'; +$txt['pm_display_mode_all'] = 'All at once'; +$txt['pm_display_mode_one'] = 'One at a time'; +$txt['pm_display_mode_linked'] = 'As a conversation'; +// Use entities in the below string. +$txt['pm_recommend_enable_outbox'] = 'To make the most of this setting we suggest you enable "Save a copy of each Personal Message in my sent items by default"\\n\\nThis will help ensure that the conversations flow better as you can see both sides of the conversation.'; + +$txt['tracking'] = 'Tracking'; +$txt['tracking_description'] = 'This section allows you to review certain profile actions performed on this member\'s profile as well as track their IP address.'; + +$txt['trackEdits'] = 'Profile Edits'; +$txt['trackEdit_deleted_member'] = 'Deleted Member'; +$txt['trackEdit_no_edits'] = 'No edits have so far been recorded for this member.'; +$txt['trackEdit_action'] = 'Field'; +$txt['trackEdit_before'] = 'Value Before'; +$txt['trackEdit_after'] = 'Value After'; +$txt['trackEdit_applicator'] = 'Changed By'; + +$txt['trackEdit_action_real_name'] = 'Member Name'; +$txt['trackEdit_action_usertitle'] = 'Custom Title'; +$txt['trackEdit_action_member_name'] = 'Username'; +$txt['trackEdit_action_email_address'] = 'Email Address'; +$txt['trackEdit_action_id_group'] = 'Primary Membergroup'; +$txt['trackEdit_action_additional_groups'] = 'Additional Membergroups'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/Reports.english.php b/Themes/default/languages/Reports.english.php new file mode 100644 index 0000000..e5d4a88 --- /dev/null +++ b/Themes/default/languages/Reports.english.php @@ -0,0 +1,140 @@ + \ No newline at end of file diff --git a/Themes/default/languages/Search.english.php b/Themes/default/languages/Search.english.php new file mode 100644 index 0000000..cb77f7c --- /dev/null +++ b/Themes/default/languages/Search.english.php @@ -0,0 +1,159 @@ +e.g. Orwell "Animal Farm" -movie'; + +$txt['search_engines_description'] = 'From this area you can decide in what detail you wish to track search engines as they index your forum as well as review search engine logs.'; +$txt['spider_mode'] = 'Search Engine Tracking Level
    Note higher level tracking increases server resource requirement.
    '; +$txt['spider_mode_off'] = 'Disabled'; +$txt['spider_mode_standard'] = 'Standard - Logs minimal spider activity.'; +$txt['spider_mode_high'] = 'High - Provides more accurate statistics.'; +$txt['spider_mode_vhigh'] = 'Very High - As for "High" but logs data about each page visited.'; +$txt['spider_settings_desc'] = 'You can change settings for spider tracking from this page. Note, if you wish to enable automatic pruning of the hit logs you can set this up here'; + +$txt['spider_group'] = 'Apply restrictive permissions from group
    To enable you to stop spiders indexing some pages.
    '; +$txt['spider_group_none'] = 'Disabled'; + +$txt['show_spider_online'] = 'Show spiders in the online list'; +$txt['show_spider_online_no'] = 'Not at all'; +$txt['show_spider_online_summary'] = 'Show spider quantity'; +$txt['show_spider_online_detail'] = 'Show spider names'; +$txt['show_spider_online_detail_admin'] = 'Show spider names - admin only'; + +$txt['spider_name'] = 'Spider Name'; +$txt['spider_last_seen'] = 'Last Seen'; +$txt['spider_last_never'] = 'Never'; +$txt['spider_agent'] = 'User Agent'; +$txt['spider_ip_info'] = 'IP Addresses'; +$txt['spiders_add'] = 'Add New Spider'; +$txt['spiders_edit'] = 'Edit Spider'; +$txt['spiders_remove_selected'] = 'Remove Selected Spiders'; +$txt['spider_remove_selected_confirm'] = 'Are you sure you wish to remove these spiders?\\n\\nAll associated statistics will also be deleted!'; +$txt['spiders_no_entries'] = 'There are currently no spiders configured.'; + +$txt['add_spider_desc'] = 'From this page you can edit the parameters against which a spider is categorised. If a guest\'s user agent/IP address matches those entered below it will be detected as a search engine spider and tracked as per the forum preferences.'; +$txt['spider_name_desc'] = 'Name by which the spider will be referred.'; +$txt['spider_agent_desc'] = 'User agent associated with this spider.'; +$txt['spider_ip_info_desc'] = 'Comma separated list of IP addresses associated with this spider.'; + +$txt['spider'] = 'Spider'; +$txt['spider_time'] = 'Time'; +$txt['spider_viewing'] = 'Viewing'; +$txt['spider_logs_empty'] = 'There are currently no spider log entries.'; +$txt['spider_logs_info'] = 'Note that logging of every spider action only occurs if tracking is set to either "high" or "very high". Detail of every spiders action is only logged if tracking is set to "very high".'; +$txt['spider_disabled'] = 'Disabled'; + +$txt['spider_logs_delete'] = 'Delete Entries'; +$txt['spider_logs_delete_older'] = 'Delete all entries older than'; +$txt['spider_logs_delete_day'] = 'days.'; +$txt['spider_logs_delete_submit'] = 'Delete'; +// Don't use entities in the below string. +$txt['spider_logs_delete_confirm'] = 'Are you sure you wish to empty out all log entries?'; + +$txt['spider_stats_select_month'] = 'Jump To Month'; +$txt['spider_stats_page_hits'] = 'Page Hits'; +$txt['spider_stats_no_entries'] = 'There are currently no spider statistics available.'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/Settings.english.php b/Themes/default/languages/Settings.english.php new file mode 100644 index 0000000..6e6d752 --- /dev/null +++ b/Themes/default/languages/Settings.english.php @@ -0,0 +1,9 @@ +
    Author: The Simple Machines Team'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/Stats.english.php b/Themes/default/languages/Stats.english.php new file mode 100644 index 0000000..fe906cc --- /dev/null +++ b/Themes/default/languages/Stats.english.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/Themes/default/languages/Themes.english.php b/Themes/default/languages/Themes.english.php new file mode 100644 index 0000000..27bb184 --- /dev/null +++ b/Themes/default/languages/Themes.english.php @@ -0,0 +1,148 @@ +
    Don\'t forget to look at the theme settings for your themes for layout options.'; +$txt['themeadmin_list_desc'] = 'From here, you can view the list of themes you currently have installed, change their paths and settings, and uninstall them.'; +$txt['themeadmin_reset_desc'] = 'Below you will see an interface to change the current theme-specific options for all your members. You will only see those themes that have their own set of settings.'; +$txt['themeadmin_edit_desc'] = 'Modify the stylesheet and source code of your installed themes. Please consult the documentation for more information.'; + +$txt['themeadmin_list_heading'] = 'Theme Settings Overview'; +$txt['themeadmin_list_tip'] = 'Remember, the layout settings may be different between one theme and another. Click on the theme\'s names below to set their options, change their directory or URL settings, or to find other options.'; +$txt['themeadmin_list_theme_dir'] = 'Theme directory (templates)'; +$txt['themeadmin_list_invalid'] = '(warning, this path is not correct!)'; +$txt['themeadmin_list_theme_url'] = 'URL to above directory'; +$txt['themeadmin_list_images_url'] = 'URL to images directory'; +$txt['themeadmin_list_reset'] = 'Reset Theme URLs and Directories'; +$txt['themeadmin_list_reset_dir'] = 'Base path to Themes directory'; +$txt['themeadmin_list_reset_url'] = 'Base URL to the same directory'; +$txt['themeadmin_list_reset_go'] = 'Attempt to reset all themes'; + +$txt['themeadmin_reset_tip'] = 'Each theme may have its own custom options for selection by your members. These include things like "quick reply", avatars and signatures, layout options, and other similar options. Here you can change the defaults or reset everyone\'s options.

    Please note that some themes may use the default options, in which case they will not have their own options.'; +$txt['themeadmin_reset_defaults'] = 'Configure guest and new user options for this theme'; +$txt['themeadmin_reset_defaults_current'] = 'options currently set.'; +$txt['themeadmin_reset_members'] = 'Change current options for all members using this theme'; +$txt['themeadmin_reset_remove'] = 'Remove all members\' options and use the defaults'; +$txt['themeadmin_reset_remove_current'] = 'members currently using their own options.'; +// Don't use entities in the below string. +$txt['themeadmin_reset_remove_confirm'] = 'Are you sure you want to remove all theme options?\\nThis may reset some custom profile fields as well.'; +$txt['themeadmin_reset_options_info'] = 'The options below will reset options for everyone. To change an option, select "change" in the box next to it, and then select a value for it. To use the default, select "remove". Otherwise, use "don\'t change" to keep it as-is.'; +$txt['themeadmin_reset_options_change'] = 'Change'; +$txt['themeadmin_reset_options_none'] = 'Don\'t change'; +$txt['themeadmin_reset_options_remove'] = 'Remove'; + +$txt['themeadmin_edit_browse'] = 'Browse the templates and files in this theme.'; +$txt['themeadmin_edit_style'] = 'Edit this theme\'s stylesheets.'; +$txt['themeadmin_edit_copy_template'] = 'Copy a template from the theme this is based on.'; +$txt['themeadmin_edit_exists'] = 'already exists'; +$txt['themeadmin_edit_do_copy'] = 'copy'; +$txt['themeadmin_edit_copy_warning'] = 'When SMF needs a template or language file which is not in the current theme, it looks in the theme it is based on, or the default theme.
    Unless you need to modify a template, it\'s better not to copy it.'; +$txt['themeadmin_edit_copy_confirm'] = 'Are you sure you want to copy this template?'; +$txt['themeadmin_edit_overwrite_confirm'] = 'Are you sure you want to copy this template over the one that already exists?\nThis will OVERWRITE any changes you\\\'ve made!'; +$txt['themeadmin_edit_no_copy'] = '(can\'t copy)'; +$txt['themeadmin_edit_filename'] = 'Filename'; +$txt['themeadmin_edit_modified'] = 'Last Modified'; +$txt['themeadmin_edit_size'] = 'Size'; +$txt['themeadmin_edit_bytes'] = 'B'; +$txt['themeadmin_edit_kilobytes'] = 'KB'; +$txt['themeadmin_edit_error'] = 'The file you tried to save generated the following error:'; +$txt['themeadmin_edit_on_line'] = 'Beginning on line'; +$txt['themeadmin_edit_preview'] = 'Preview'; +$txt['themeadmin_selectable'] = 'Themes that the user is able to select'; +$txt['themeadmin_themelist_link'] = 'Show the list of themes'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/Who.english.php b/Themes/default/languages/Who.english.php new file mode 100644 index 0000000..2f17bf2 --- /dev/null +++ b/Themes/default/languages/Who.english.php @@ -0,0 +1,156 @@ +Nothing, or nothing you can see...'; +$txt['who_unknown'] = 'Unknown Action'; +$txt['who_user'] = 'User'; +$txt['who_time'] = 'Time'; +$txt['who_action'] = 'Action'; +$txt['who_show1'] = 'Show '; +$txt['who_show_members_only'] = 'Members Only'; +$txt['who_show_guests_only'] = 'Guests Only'; +$txt['who_show_spiders_only'] = 'Spiders Only'; +$txt['who_show_all'] = 'Everyone'; +$txt['who_no_online_spiders'] = 'There are currently no spiders online.'; +$txt['who_no_online_guests'] = 'There are currently no guests online.'; +$txt['who_no_online_members'] = 'There are currently no members online.'; + +$txt['whospider_login'] = 'Viewing the login page.'; +$txt['whospider_register'] = 'Viewing the registration page.'; +$txt['whospider_reminder'] = 'Viewing the reminder page.'; + +$txt['whoall_activate'] = 'Activating their account.'; +$txt['whoall_buddy'] = 'Modifying their buddy list.'; +$txt['whoall_coppa'] = 'Filling out parent/guardian consent form.'; +$txt['whoall_credits'] = 'Viewing credits page.'; +$txt['whoall_emailuser'] = 'Sending email to another member.'; +$txt['whoall_groups'] = 'Viewing the member groups page.'; +$txt['whoall_help'] = 'Viewing the help page.'; +$txt['whoall_helpadmin'] = 'Viewing a help popup.'; +$txt['whoall_pm'] = 'Viewing their messages.'; +$txt['whoall_login'] = 'Logging into the forum.'; +$txt['whoall_login2'] = 'Logging into the forum.'; +$txt['whoall_logout'] = 'Logging out of the forum.'; +$txt['whoall_markasread'] = 'Marking topics read or unread.'; +$txt['whoall_modifykarma_applaud'] = 'Applauding a member.'; +$txt['whoall_modifykarma_smite'] = 'Smiting a member.'; +$txt['whoall_news'] = 'Viewing the news.'; +$txt['whoall_notify'] = 'Changing their notification settings.'; +$txt['whoall_notifyboard'] = 'Changing their notification settings.'; +$txt['whoall_openidreturn'] = 'Logging in using OpenID.'; +$txt['whoall_quickmod'] = 'Moderating a board.'; +$txt['whoall_recent'] = 'Viewing a list of recent topics.'; +$txt['whoall_register'] = 'Registering for an account on the forum.'; +$txt['whoall_register2'] = 'Registering for an account on the forum.'; +$txt['whoall_reminder'] = 'Requesting a password reminder.'; +$txt['whoall_reporttm'] = 'Reporting a topic to a moderator.'; +$txt['whoall_spellcheck'] = 'Using the spellchecker'; +$txt['whoall_unread'] = 'Viewing unread topics since their last visit.'; +$txt['whoall_unreadreplies'] = 'Viewing unread replies since their last visit.'; +$txt['whoall_who'] = 'Viewing Who\'s Online.'; + +$txt['whoall_collapse_collapse'] = 'Collapsing a category.'; +$txt['whoall_collapse_expand'] = 'Expanding a category.'; +$txt['whoall_pm_removeall'] = 'Removing all their messages.'; +$txt['whoall_pm_send'] = 'Sending a message.'; +$txt['whoall_pm_send2'] = 'Sending a message.'; + +$txt['whotopic_announce'] = 'Announcing the topic "%2$s".'; +$txt['whotopic_attachapprove'] = 'Approving an attachment.'; +$txt['whotopic_dlattach'] = 'Viewing an attachment.'; +$txt['whotopic_deletemsg'] = 'Deleting a message.'; +$txt['whotopic_editpoll'] = 'Editing the poll in "%2$s".'; +$txt['whotopic_editpoll2'] = 'Editing the poll in "%2$s".'; +$txt['whotopic_jsmodify'] = 'Modifying a post in "%2$s".'; +$txt['whotopic_lock'] = 'Locking the topic "%2$s".'; +$txt['whotopic_lockvoting'] = 'Locking the poll in "%2$s".'; +$txt['whotopic_mergetopics'] = 'Merging the topic "%2$s" with another topic.'; +$txt['whotopic_movetopic'] = 'Moving the topic "%2$s" to another board.'; +$txt['whotopic_movetopic2'] = 'Moving the topic "%2$s" to another board.'; +$txt['whotopic_post'] = 'Posting in %2$s.'; +$txt['whotopic_post2'] = 'Posting in %2$s.'; +$txt['whotopic_printpage'] = 'Printing the topic "%2$s".'; +$txt['whotopic_quickmod2'] = 'Moderating the topic %2$s.'; +$txt['whotopic_removepoll'] = 'Removing the poll in "%2$s".'; +$txt['whotopic_removetopic2'] = 'Removing the topic %2$s.'; +$txt['whotopic_sendtopic'] = 'Sending the topic "%2$s" to a friend.'; +$txt['whotopic_splittopics'] = 'Splitting the topic "%2$s" into two topics.'; +$txt['whotopic_sticky'] = 'Setting the topic "%2$s" as sticky.'; +$txt['whotopic_vote'] = 'Voting in %2$s.'; + +$txt['whopost_quotefast'] = 'Quoting a post from "%2$s".'; + +$txt['whoadmin_editagreement'] = 'Editing the registration agreement.'; +$txt['whoadmin_featuresettings'] = 'Editing forum features and options.'; +$txt['whoadmin_modlog'] = 'Viewing the moderator log.'; +$txt['whoadmin_serversettings'] = 'Editing the forum settings.'; +$txt['whoadmin_packageget'] = 'Getting packages.'; +$txt['whoadmin_packages'] = 'Viewing the package manager.'; +$txt['whoadmin_permissions'] = 'Editing the forum permissions.'; +$txt['whoadmin_pgdownload'] = 'Downloading a package.'; +$txt['whoadmin_theme'] = 'Editing the theme settings.'; +$txt['whoadmin_trackip'] = 'Tracking an IP address.'; + +$txt['whoallow_manageboards'] = 'Editing the board and category settings.'; +$txt['whoallow_admin'] = 'Viewing the administration center.'; +$txt['whoallow_ban'] = 'Editing the ban list.'; +$txt['whoallow_boardrecount'] = 'Recounting the forum totals.'; +$txt['whoallow_calendar'] = 'Viewing the calendar.'; +$txt['whoallow_editnews'] = 'Editing the news.'; +$txt['whoallow_mailing'] = 'Sending a forum email.'; +$txt['whoallow_maintain'] = 'Performing routine forum maintenance.'; +$txt['whoallow_manageattachments'] = 'Managing the attachments.'; +$txt['whoallow_moderate'] = 'Viewing the Moderation Center.'; +$txt['whoallow_mlist'] = 'Viewing the memberlist.'; +$txt['whoallow_optimizetables'] = 'Optimizing the database tables.'; +$txt['whoallow_repairboards'] = 'Repairing the database tables.'; +$txt['whoallow_search'] = 'Searching the forum.'; +$txt['whoallow_search2'] = 'Viewing the results of a search.'; +$txt['whoallow_setcensor'] = 'Editing the censor text.'; +$txt['whoallow_setreserve'] = 'Editing the reserved names.'; +$txt['whoallow_stats'] = 'Viewing the forum stats.'; +$txt['whoallow_viewErrorLog'] = 'Viewing the error log.'; +$txt['whoallow_viewmembers'] = 'Viewing a list of members.'; + +$txt['who_topic'] = 'Viewing the topic %2$s.'; +$txt['who_board'] = 'Viewing the board %2$s.'; +$txt['who_index'] = 'Viewing the board index of ' . $context['forum_name'] . '.'; +$txt['who_viewprofile'] = 'Viewing %2$s\'s profile.'; +$txt['who_profile'] = 'Editing the profile of %2$s.'; +$txt['who_post'] = 'Posting a new topic in %2$s.'; +$txt['who_poll'] = 'Posting a new poll in %2$s.'; + +// Credits text +$txt['credits'] = 'Credits'; +$txt['credits_intro'] = 'Simple Machines wants to thank everyone who helped make SMF 2.0 what it is today; shaping and directing our project, all through the thick and the thin. It wouldn\'t have been possible without you. This includes our users and especially Charter Members - thanks for installing and using our software as well as providing valuable feedback, bug reports, and opinions.'; +$txt['credits_team'] = 'The Team'; +$txt['credits_special'] = 'Special Thanks'; +$txt['credits_and'] = 'and'; +$txt['credits_anyone'] = 'And for anyone we may have missed, thank you!'; +$txt['credits_copyright'] = 'Copyrights'; +$txt['credits_forum'] = 'Forum'; +$txt['credits_modifications'] = 'Modifications'; +$txt['credits_groups_ps'] = 'Project Support'; +$txt['credits_groups_dev'] = 'Developers'; +$txt['credits_groups_support'] = 'Support Specialists'; +$txt['credits_groups_customize'] = 'Customizers'; +$txt['credits_groups_docs'] = 'Documentation Writers'; +$txt['credits_groups_marketing'] = 'Marketing'; +$txt['credits_groups_internationalizers'] = 'Localizers'; +$txt['credits_groups_servers'] = 'Servers Administrators'; +$txt['credits_groups_site'] = 'Site Administrators'; +// Replace "English" with the name of this language pack in the string below. +$txt['credits_groups_translation'] = 'English Translation'; +$txt['credits_groups_translators'] = 'Language Translators'; +$txt['credits_translators_message'] = 'Thank you for your efforts which make it possible for people all around the world to use SMF.'; +$txt['credits_groups_consultants'] = 'Consulting Developers'; +$txt['credits_groups_beta'] = 'Beta Testers'; +$txt['credits_beta_message'] = 'The invaluable few who tirelessly find bugs, provide feedback, and drive the developers crazier.'; +$txt['credits_groups_founder'] = 'Founding Father of SMF'; +$txt['credits_groups_orignal_pm'] = 'Original Project Managers'; + +// List of people who have made more than a token contribution to this translation. (blank for English) +$txt['translation_credits'] = array(); +?> \ No newline at end of file diff --git a/Themes/default/languages/Wireless.english.php b/Themes/default/languages/Wireless.english.php new file mode 100644 index 0000000..e244e7a --- /dev/null +++ b/Themes/default/languages/Wireless.english.php @@ -0,0 +1,49 @@ +%1$d new)'; +$txt['wireless_pm_by'] = 'by'; +$txt['wireless_pm_add_buddy'] = 'Add buddy'; +$txt['wireless_pm_select_buddy'] = 'Select a buddy'; +$txt['wireless_pm_search_member'] = 'Search member'; +$txt['wireless_pm_search_name'] = 'Name'; +$txt['wireless_pm_no_recipients'] = 'No recipients (yet)'; +$txt['wireless_pm_reply'] = 'Reply'; +$txt['wireless_pm_reply_all'] = 'Reply All'; +$txt['wireless_pm_reply_to'] = 'Reply to'; + +$txt['wireless_recent_unread_posts'] = 'Unread posts'; +$txt['wireless_recent_unread_replies'] = 'Unread replies'; + +$txt['wireless_display_edit'] = 'Edit'; +$txt['wireless_display_moderate'] = 'Moderate'; +$txt['wireless_display_sticky'] = 'Sticky'; +$txt['wireless_display_unsticky'] = 'Un-Sticky'; +$txt['wireless_display_lock'] = 'Lock'; +$txt['wireless_display_unlock'] = 'Unlock'; + +$txt['wireless_end_code'] = 'End code'; +$txt['wireless_end_quote'] = 'End quote'; + +$txt['wireless_profile_pm'] = 'Send Personal Message'; +$txt['wireless_ban_ip'] = 'Ban on IP'; +$txt['wireless_ban_hostname'] = 'Ban on Hostname'; +$txt['wireless_ban_email'] = 'Ban on Email'; +$txt['wireless_additional_info'] = 'Additional Information'; +$txt['wireless_go_to_full_version'] = 'Go to full version'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/index.english.php b/Themes/default/languages/index.english.php new file mode 100644 index 0000000..94e2e2e --- /dev/null +++ b/Themes/default/languages/index.english.php @@ -0,0 +1,753 @@ + 'January'. (or translated, of course.) +$txt['months'] = array(1 => 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'); +$txt['months_titles'] = array(1 => 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'); +$txt['months_short'] = array(1 => 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'); + +$txt['time_am'] = 'am'; +$txt['time_pm'] = 'pm'; + +$txt['newmessages0'] = 'is new'; +$txt['newmessages1'] = 'are new'; +$txt['newmessages3'] = 'New'; +$txt['newmessages4'] = ','; + +$txt['admin'] = 'Admin'; +$txt['moderate'] = 'Moderate'; + +$txt['save'] = 'Save'; + +$txt['modify'] = 'Modify'; +$txt['forum_index'] = '%1$s - Index'; +$txt['members'] = 'Members'; +$txt['board_name'] = 'Board name'; +$txt['posts'] = 'Posts'; + +$txt['member_postcount'] = 'Posts'; +$txt['no_subject'] = '(No subject)'; +$txt['view_profile'] = 'View Profile'; +$txt['guest_title'] = 'Guest'; +$txt['author'] = 'Author'; +$txt['on'] = 'on'; +$txt['remove'] = 'Remove'; +$txt['start_new_topic'] = 'Start new topic'; + +$txt['login'] = 'Login'; +// Use numeric entities in the below string. +$txt['username'] = 'Username'; +$txt['password'] = 'Password'; + +$txt['username_no_exist'] = 'That username does not exist.'; +$txt['no_user_with_email'] = 'There are no usernames associated with that email.'; + +$txt['board_moderator'] = 'Board Moderator'; +$txt['remove_topic'] = 'Remove Topic'; +$txt['topics'] = 'Topics'; +$txt['modify_msg'] = 'Modify message'; +$txt['name'] = 'Name'; +$txt['email'] = 'Email'; +$txt['subject'] = 'Subject'; +$txt['message'] = 'Message'; +$txt['redirects'] = 'Redirects'; +$txt['quick_modify'] = 'Modify Inline'; + +$txt['choose_pass'] = 'Choose password'; +$txt['verify_pass'] = 'Verify password'; +$txt['position'] = 'Position'; + +$txt['profile_of'] = 'View the profile of'; +$txt['total'] = 'Total'; +$txt['posts_made'] = 'Posts'; +$txt['website'] = 'Website'; +$txt['register'] = 'Register'; +$txt['warning_status'] = 'Warning Status'; +$txt['user_warn_watch'] = 'User is on moderator watch list'; +$txt['user_warn_moderate'] = 'User posts join approval queue'; +$txt['user_warn_mute'] = 'User is banned from posting'; +$txt['warn_watch'] = 'Watched'; +$txt['warn_moderate'] = 'Moderated'; +$txt['warn_mute'] = 'Muted'; + +$txt['message_index'] = 'Message Index'; +$txt['news'] = 'News'; +$txt['home'] = 'Home'; + +$txt['lock_unlock'] = 'Lock/Unlock Topic'; +$txt['post'] = 'Post'; +$txt['error_occured'] = 'An Error Has Occurred!'; +$txt['at'] = 'at'; +$txt['logout'] = 'Logout'; +$txt['started_by'] = 'Started by'; +$txt['replies'] = 'Replies'; +$txt['last_post'] = 'Last post'; +$txt['admin_login'] = 'Administration Login'; +// Use numeric entities in the below string. +$txt['topic'] = 'Topic'; +$txt['help'] = 'Help'; +$txt['notify'] = 'Notify'; +$txt['unnotify'] = 'Unnotify'; +$txt['notify_request'] = 'Do you want a notification email if someone replies to this topic?'; +// Use numeric entities in the below string. +$txt['regards_team'] = 'Regards,' . "\n" . 'The ' . $context['forum_name'] . ' Team.'; +$txt['notify_replies'] = 'Notify of replies'; +$txt['move_topic'] = 'Move Topic'; +$txt['move_to'] = 'Move to'; +$txt['pages'] = 'Pages'; +$txt['users_active'] = 'Users active in past %1$d minutes'; +$txt['personal_messages'] = 'Personal Messages'; +$txt['reply_quote'] = 'Reply with quote'; +$txt['reply'] = 'Reply'; +$txt['reply_noun'] = 'Reply'; +$txt['approve'] = 'Approve'; +$txt['approve_all'] = 'approve all'; +$txt['awaiting_approval'] = 'Awaiting Approval'; +$txt['attach_awaiting_approve'] = 'Attachments awaiting approval'; +$txt['post_awaiting_approval'] = 'Note: This message is awaiting approval by a moderator.'; +$txt['there_are_unapproved_topics'] = 'There are %1$s topics and %2$s posts awaiting approval in this board. Click here to view them all.'; + +$txt['msg_alert_none'] = 'No messages...'; +$txt['msg_alert_you_have'] = 'you have'; +$txt['msg_alert_messages'] = 'messages'; +$txt['remove_message'] = 'Remove this message'; + +$txt['online_users'] = 'Users Online'; +$txt['personal_message'] = 'Personal Message'; +$txt['jump_to'] = 'Jump to'; +$txt['go'] = 'go'; +$txt['are_sure_remove_topic'] = 'Are you sure you want to remove this topic?'; +$txt['yes'] = 'Yes'; +$txt['no'] = 'No'; + +$txt['search_end_results'] = 'End of results'; +$txt['search_on'] = 'on'; + +$txt['search'] = 'Search'; +$txt['all'] = 'All'; + +$txt['back'] = 'Back'; +$txt['password_reminder'] = 'Password reminder'; +$txt['topic_started'] = 'Topic started by'; +$txt['title'] = 'Title'; +$txt['post_by'] = 'Post by'; +$txt['memberlist_searchable'] = 'Searchable list of all registered members.'; +$txt['welcome_member'] = 'Please welcome'; +$txt['admin_center'] = 'Administration Center'; +$txt['last_edit'] = 'Last Edit'; +$txt['notify_deactivate'] = 'Would you like to deactivate notification on this topic?'; + +$txt['recent_posts'] = 'Recent Posts'; + +$txt['location'] = 'Location'; +$txt['gender'] = 'Gender'; +$txt['date_registered'] = 'Date Registered'; + +$txt['recent_view'] = 'View the most recent posts on the forum.'; +$txt['recent_updated'] = 'is the most recently updated topic'; + +$txt['male'] = 'Male'; +$txt['female'] = 'Female'; + +$txt['error_invalid_characters_username'] = 'Invalid character used in Username.'; + +$txt['welcome_guest'] = 'Welcome, %1$s. Please login or register.'; +$txt['login_or_register'] = 'Please login or register.'; +$txt['welcome_guest_activate'] = '
    Did you miss your activation email?'; +$txt['hello_member'] = 'Hey,'; +// Use numeric entities in the below string. +$txt['hello_guest'] = 'Welcome,'; +$txt['welmsg_hey'] = 'Hey,'; +$txt['welmsg_welcome'] = 'Welcome,'; +$txt['welmsg_please'] = 'Please'; +$txt['select_destination'] = 'Please select a destination'; + +// Escape any single quotes in here twice.. 'it\'s' -> 'it\\\'s'. +$txt['posted_by'] = 'Posted by'; + +$txt['icon_smiley'] = 'Smiley'; +$txt['icon_angry'] = 'Angry'; +$txt['icon_cheesy'] = 'Cheesy'; +$txt['icon_laugh'] = 'Laugh'; +$txt['icon_sad'] = 'Sad'; +$txt['icon_wink'] = 'Wink'; +$txt['icon_grin'] = 'Grin'; +$txt['icon_shocked'] = 'Shocked'; +$txt['icon_cool'] = 'Cool'; +$txt['icon_huh'] = 'Huh'; +$txt['icon_rolleyes'] = 'Roll Eyes'; +$txt['icon_tongue'] = 'Tongue'; +$txt['icon_embarrassed'] = 'Embarrassed'; +$txt['icon_lips'] = 'Lips sealed'; +$txt['icon_undecided'] = 'Undecided'; +$txt['icon_kiss'] = 'Kiss'; +$txt['icon_cry'] = 'Cry'; + +$txt['moderator'] = 'Moderator'; +$txt['moderators'] = 'Moderators'; + +$txt['mark_board_read'] = 'Mark Topics as Read for this Board'; +$txt['views'] = 'Views'; +$txt['new'] = 'New'; + +$txt['view_all_members'] = 'View All Members'; +$txt['view'] = 'View'; + +$txt['viewing_members'] = 'Viewing Members %1$s to %2$s'; +$txt['of_total_members'] = 'of %1$s total members'; + +$txt['forgot_your_password'] = 'Forgot your password?'; + +$txt['date'] = 'Date'; +// Use numeric entities in the below string. +$txt['from'] = 'From'; +$txt['check_new_messages'] = 'Check for new messages'; +$txt['to'] = 'To'; + +$txt['board_topics'] = 'Topics'; +$txt['members_title'] = 'Members'; +$txt['members_list'] = 'Members List'; +$txt['new_posts'] = 'New Posts'; +$txt['old_posts'] = 'No New Posts'; +$txt['redirect_board'] = 'Redirect Board'; + +$txt['sendtopic_send'] = 'Send'; +$txt['report_sent'] = 'Your report has been sent successfully.'; + +$txt['time_offset'] = 'Time Offset'; +$txt['or'] = 'or'; + +$txt['no_matches'] = 'Sorry, no matches were found'; + +$txt['notification'] = 'Notification'; + +$txt['your_ban'] = 'Sorry %1$s, you are banned from using this forum!'; +$txt['your_ban_expires'] = 'This ban is set to expire %1$s.'; +$txt['your_ban_expires_never'] = 'This ban is not set to expire.'; +$txt['ban_continue_browse'] = 'You may continue to browse the forum as a guest.'; + +$txt['mark_as_read'] = 'Mark ALL messages as read'; + +$txt['hot_topics'] = 'Hot Topic (More than %1$d replies)'; +$txt['very_hot_topics'] = 'Very Hot Topic (More than %1$d replies)'; +$txt['locked_topic'] = 'Locked Topic'; +$txt['normal_topic'] = 'Normal Topic'; +$txt['participation_caption'] = 'Topic you have posted in'; + +$txt['go_caps'] = 'GO'; + +$txt['print'] = 'Print'; +$txt['profile'] = 'Profile'; +$txt['topic_summary'] = 'Topic Summary'; +$txt['not_applicable'] = 'N/A'; +$txt['message_lowercase'] = 'message'; +$txt['name_in_use'] = 'This name is already in use by another member.'; + +$txt['total_members'] = 'Total Members'; +$txt['total_posts'] = 'Total Posts'; +$txt['total_topics'] = 'Total Topics'; + +$txt['mins_logged_in'] = 'Minutes to stay logged in'; + +$txt['preview'] = 'Preview'; +$txt['always_logged_in'] = 'Always stay logged in'; + +$txt['logged'] = 'Logged'; +// Use numeric entities in the below string. +$txt['ip'] = 'IP'; + +$txt['www'] = 'WWW'; + +$txt['by'] = 'by'; + +$txt['hours'] = 'hours'; +$txt['days_word'] = 'days'; + +$txt['newest_member'] = ', our newest member.'; + +$txt['search_for'] = 'Search for'; + +$txt['aim'] = 'AIM'; +// In this string, please use +'s for spaces. +$txt['aim_default_message'] = 'Hi.+Are+you+there?'; +$txt['aim_title'] = 'AOL Instant Messenger'; +$txt['icq'] = 'ICQ'; +$txt['icq_title'] = 'ICQ Messenger'; +$txt['msn'] = 'MSN'; +$txt['msn_title'] = 'MSN Messenger'; +$txt['yim'] = 'YIM'; +$txt['yim_title'] = 'Yahoo Instant Messenger'; + +$txt['maintain_mode_on'] = 'Remember, this forum is in \'Maintenance Mode\'.'; + +$txt['read'] = 'Read'; +$txt['times'] = 'times'; + +$txt['forum_stats'] = 'Forum Stats'; +$txt['latest_member'] = 'Latest Member'; +$txt['total_cats'] = 'Total Categories'; +$txt['latest_post'] = 'Latest Post'; + +$txt['you_have'] = 'You\'ve got'; +$txt['click'] = 'Click'; +$txt['here'] = 'here'; +$txt['to_view'] = 'to view them.'; + +$txt['total_boards'] = 'Total Boards'; + +$txt['print_page'] = 'Print Page'; + +$txt['valid_email'] = 'This must be a valid email address.'; + +$txt['geek'] = 'I am a geek!!'; +$txt['info_center_title'] = '%1$s - Info Center'; + +$txt['send_topic'] = 'Send this topic'; + +$txt['sendtopic_title'] = 'Send the topic "%1$s" to a friend.'; +$txt['sendtopic_sender_name'] = 'Your name'; +$txt['sendtopic_sender_email'] = 'Your email address'; +$txt['sendtopic_receiver_name'] = 'Recipient\'s name'; +$txt['sendtopic_receiver_email'] = 'Recipient\'s email address'; +$txt['sendtopic_comment'] = 'Add a comment'; + +$txt['allow_user_email'] = 'Allow users to email me'; + +$txt['check_all'] = 'Check all'; + +// Use numeric entities in the below string. +$txt['database_error'] = 'Database Error'; +$txt['try_again'] = 'Please try again. If you come back to this error screen, report the error to an administrator.'; +$txt['file'] = 'File'; +$txt['line'] = 'Line'; +// Use numeric entities in the below string. +$txt['tried_to_repair'] = 'SMF has detected and automatically tried to repair an error in your database. If you continue to have problems, or continue to receive these emails, please contact your host.'; +$txt['database_error_versions'] = 'Note: It appears that your database may require an upgrade. Your forum\'s files are currently at version %1$s, while your database is at version %2$s. The above error might possibly go away if you execute the latest version of upgrade.php.'; +$txt['template_parse_error'] = 'Template Parse Error!'; +$txt['template_parse_error_message'] = 'It seems something has gone sour on the forum with the template system. This problem should only be temporary, so please come back later and try again. If you continue to see this message, please contact the administrator.

    You can also try refreshing this page.'; +$txt['template_parse_error_details'] = 'There was a problem loading the %1$s template or language file. Please check the syntax and try again - remember, single quotes (\') often have to be escaped with a slash (\\). To see more specific error information from PHP, try accessing the file directly.

    You may want to try to refresh this page or use the default theme.'; + +$txt['today'] = 'Today at '; +$txt['yesterday'] = 'Yesterday at '; +$txt['new_poll'] = 'New poll'; +$txt['poll_question'] = 'Question'; +$txt['poll_vote'] = 'Submit Vote'; +$txt['poll_total_voters'] = 'Total Members Voted'; +$txt['shortcuts'] = 'shortcuts: hit alt+s to submit/post or alt+p to preview'; +$txt['shortcuts_firefox'] = 'shortcuts: hit shift+alt+s to submit/post or shift+alt+p to preview'; +$txt['poll_results'] = 'View results'; +$txt['poll_lock'] = 'Lock Voting'; +$txt['poll_unlock'] = 'Unlock Voting'; +$txt['poll_edit'] = 'Edit Poll'; +$txt['poll'] = 'Poll'; +$txt['one_day'] = '1 Day'; +$txt['one_week'] = '1 Week'; +$txt['one_month'] = '1 Month'; +$txt['forever'] = 'Forever'; +$txt['quick_login_dec'] = 'Login with username, password and session length'; +$txt['one_hour'] = '1 Hour'; +$txt['moved'] = 'MOVED'; +$txt['moved_why'] = 'Please enter a brief description as to
    why this topic is being moved.'; +$txt['board'] = 'Board'; +$txt['in'] = 'in'; +$txt['sticky_topic'] = 'Sticky Topic'; + +$txt['delete'] = 'Delete'; + +$txt['your_pms'] = 'Your Personal Messages'; + +$txt['kilobyte'] = 'kB'; + +$txt['more_stats'] = '[More Stats]'; + +// Use numeric entities in the below three strings. +$txt['code'] = 'Code'; +$txt['code_select'] = '[Select]'; +$txt['quote_from'] = 'Quote from'; +$txt['quote'] = 'Quote'; + +$txt['merge_to_topic_id'] = 'ID of target topic'; +$txt['split'] = 'Split Topic'; +$txt['merge'] = 'Merge Topics'; +$txt['subject_new_topic'] = 'Subject For New Topic'; +$txt['split_this_post'] = 'Only split this post.'; +$txt['split_after_and_this_post'] = 'Split topic after and including this post.'; +$txt['select_split_posts'] = 'Select posts to split.'; +$txt['new_topic'] = 'New Topic'; +$txt['split_successful'] = 'Topic successfully split into two topics.'; +$txt['origin_topic'] = 'Origin Topic'; +$txt['please_select_split'] = 'Please select which posts you wish to split.'; +$txt['merge_successful'] = 'Topics successfully merged.'; +$txt['new_merged_topic'] = 'Newly Merged Topic'; +$txt['topic_to_merge'] = 'Topic to be merged'; +$txt['target_board'] = 'Target board'; +$txt['target_topic'] = 'Target topic'; +$txt['merge_confirm'] = 'Are you sure you want to merge'; +$txt['with'] = 'with'; +$txt['merge_desc'] = 'This function will merge the messages of two topics into one topic. The messages will be sorted according to the time of posting. Therefore the earliest posted message will be the first message of the merged topic.'; + +$txt['set_sticky'] = 'Set topic sticky'; +$txt['set_nonsticky'] = 'Set topic non-sticky'; +$txt['set_lock'] = 'Lock topic'; +$txt['set_unlock'] = 'Unlock topic'; + +$txt['search_advanced'] = 'Advanced search'; + +$txt['security_risk'] = 'MAJOR SECURITY RISK:'; +$txt['not_removed'] = 'You have not removed '; +$txt['not_removed_extra'] ='%1$s is a backup of %2$s that was not generated by SMF. It can be accessed directly and used to gain unauthorised access to your forum. You should delete it immediately.'; + +$txt['cache_writable_head'] = 'Performance Warning'; +$txt['cache_writable'] = 'The cache directory is not writable - this will adversely affect the performance of your forum.'; + +$txt['page_created'] = 'Page created in '; +$txt['seconds_with'] = ' seconds with '; +$txt['queries'] = ' queries.'; + +$txt['report_to_mod_func'] = 'Use this function to inform the moderators and administrators of an abusive or wrongly posted message.
    Please note that your email address will be revealed to the moderators if you use this.'; + +$txt['online'] = 'Online'; +$txt['offline'] = 'Offline'; +$txt['pm_online'] = 'Personal Message (Online)'; +$txt['pm_offline'] = 'Personal Message (Offline)'; +$txt['status'] = 'Status'; + +$txt['go_up'] = 'Go Up'; +$txt['go_down'] = 'Go Down'; + +$forum_copyright = '%1$s | + SMF © 2014, Simple Machines'; + +$txt['birthdays'] = 'Birthdays:'; +$txt['events'] = 'Events:'; +$txt['birthdays_upcoming'] = 'Upcoming Birthdays:'; +$txt['events_upcoming'] = 'Upcoming Events:'; +// Prompt for holidays in the calendar, leave blank to just display the holiday's name. +$txt['calendar_prompt'] = ''; +$txt['calendar_month'] = 'Month:'; +$txt['calendar_year'] = 'Year:'; +$txt['calendar_day'] = 'Day:'; +$txt['calendar_event_title'] = 'Event Title'; +$txt['calendar_event_options'] = 'Event Options'; +$txt['calendar_post_in'] = 'Post In:'; +$txt['calendar_edit'] = 'Edit Event'; +$txt['event_delete_confirm'] = 'Delete this event?'; +$txt['event_delete'] = 'Delete Event'; +$txt['calendar_post_event'] = 'Post Event'; +$txt['calendar'] = 'Calendar'; +$txt['calendar_link'] = 'Link to Calendar'; +$txt['calendar_upcoming'] = 'Upcoming Calendar'; +$txt['calendar_today'] = 'Today\'s Calendar'; +$txt['calendar_week'] = 'Week'; +$txt['calendar_week_title'] = 'Week %1$d of %2$d'; +$txt['calendar_numb_days'] = 'Number of Days:'; +$txt['calendar_how_edit'] = 'how do you edit these events?'; +$txt['calendar_link_event'] = 'Link Event To Post:'; +$txt['calendar_confirm_delete'] = 'Are you sure you want to delete this event?'; +$txt['calendar_linked_events'] = 'Linked Events'; +$txt['calendar_click_all'] = 'click to see all %1$s'; + +$txt['moveTopic1'] = 'Post a redirection topic'; +$txt['moveTopic2'] = 'Change the topic\'s subject'; +$txt['moveTopic3'] = 'New subject'; +$txt['moveTopic4'] = 'Change every message\'s subject'; +$txt['move_topic_unapproved_js'] = 'Warning! This topic has not yet been approved.\\n\\nIt is not recommended that you create a redirection topic unless you intend to approve the post immediately following the move.'; + +$txt['theme_template_error'] = 'Unable to load the \'%1$s\' template.'; +$txt['theme_language_error'] = 'Unable to load the \'%1$s\' language file.'; + +$txt['parent_boards'] = 'Child Boards'; + +$txt['smtp_no_connect'] = 'Could not connect to SMTP host'; +$txt['smtp_port_ssl'] = 'SMTP port setting incorrect; it should be 465 for SSL servers.'; +$txt['smtp_bad_response'] = 'Couldn\'t get mail server response codes'; +$txt['smtp_error'] = 'Ran into problems sending Mail. Error: '; +$txt['mail_send_unable'] = 'Unable to send mail to the email address \'%1$s\''; + +$txt['mlist_search'] = 'Search For Members'; +$txt['mlist_search_again'] = 'Search again'; +$txt['mlist_search_email'] = 'Search by email address'; +$txt['mlist_search_messenger'] = 'Search by messenger nickname'; +$txt['mlist_search_group'] = 'Search by position'; +$txt['mlist_search_name'] = 'Search by name'; +$txt['mlist_search_website'] = 'Search by website'; +$txt['mlist_search_results'] = 'Search results for'; +$txt['mlist_search_by'] = 'Search by %1$s'; +$txt['mlist_menu_view'] = 'View the memberlist'; + +$txt['attach_downloaded'] = 'downloaded'; +$txt['attach_viewed'] = 'viewed'; +$txt['attach_times'] = 'times'; + +$txt['settings'] = 'Settings'; +$txt['never'] = 'Never'; +$txt['more'] = 'more'; + +$txt['hostname'] = 'Hostname'; +$txt['you_are_post_banned'] = 'Sorry %1$s, you are banned from posting and sending personal messages on this forum.'; +$txt['ban_reason'] = 'Reason'; + +$txt['tables_optimized'] = 'Database tables optimized'; + +$txt['add_poll'] = 'Add poll'; +$txt['poll_options6'] = 'You may only select up to %1$s options.'; +$txt['poll_remove'] = 'Remove Poll'; +$txt['poll_remove_warn'] = 'Are you sure you want to remove this poll from the topic?'; +$txt['poll_results_expire'] = 'Results will be shown when voting has closed'; +$txt['poll_expires_on'] = 'Voting closes'; +$txt['poll_expired_on'] = 'Voting closed'; +$txt['poll_change_vote'] = 'Remove Vote'; +$txt['poll_return_vote'] = 'Voting options'; +$txt['poll_cannot_see'] = 'You cannot see the results of this poll at the moment.'; + +$txt['quick_mod_approve'] = 'Approve selected'; +$txt['quick_mod_remove'] = 'Remove selected'; +$txt['quick_mod_lock'] = 'Lock/Unlock selected'; +$txt['quick_mod_sticky'] = 'Sticky/Unsticky selected'; +$txt['quick_mod_move'] = 'Move selected to'; +$txt['quick_mod_merge'] = 'Merge selected'; +$txt['quick_mod_markread'] = 'Mark selected read'; +$txt['quick_mod_go'] = 'Go!'; +$txt['quickmod_confirm'] = 'Are you sure you want to do this?'; + +$txt['spell_check'] = 'Spell Check'; + +$txt['quick_reply'] = 'Quick Reply'; +$txt['quick_reply_desc'] = 'With Quick-Reply you can write a post when viewing a topic without loading a new page. You can still use bulletin board code and smileys as you would in a normal post.'; +$txt['quick_reply_warning'] = 'Warning: this topic is currently locked! Only admins and moderators can reply.'; +$txt['quick_reply_verification'] = 'After submitting your post you will be directed to the regular post page to verify your post %1$s.'; +$txt['quick_reply_verification_guests'] = '(required for all guests)'; +$txt['quick_reply_verification_posts'] = '(required for all users with less than %1$d posts)'; +$txt['wait_for_approval'] = 'Note: this post will not display until it\'s been approved by a moderator.'; + +$txt['notification_enable_board'] = 'Are you sure you wish to enable notification of new topics for this board?'; +$txt['notification_disable_board'] = 'Are you sure you wish to disable notification of new topics for this board?'; +$txt['notification_enable_topic'] = 'Are you sure you wish to enable notification of new replies for this topic?'; +$txt['notification_disable_topic'] = 'Are you sure you wish to disable notification of new replies for this topic?'; + +$txt['report_to_mod'] = 'Report to moderator'; +$txt['issue_warning_post'] = 'Issue a warning because of this message'; + +$txt['unread_topics_visit'] = 'Recent Unread Topics'; +$txt['unread_topics_visit_none'] = 'No unread topics found since your last visit. Click here to try all unread topics.'; +$txt['unread_topics_all'] = 'All Unread Topics'; +$txt['unread_replies'] = 'Updated Topics'; + +$txt['who_title'] = 'Who\'s Online'; +$txt['who_and'] = ' and '; +$txt['who_viewing_topic'] = ' are viewing this topic.'; +$txt['who_viewing_board'] = ' are viewing this board.'; +$txt['who_member'] = 'Member'; + +// No longer used by default theme, but for backwards compat +$txt['powered_by_php'] = 'Powered by PHP'; +$txt['powered_by_mysql'] = 'Powered by MySQL'; +$txt['valid_css'] = 'Valid CSS!'; + +// Current footer strings +$txt['valid_html'] = 'Valid HTML 4.01!'; +$txt['valid_xhtml'] = 'Valid XHTML 1.0!'; +$txt['wap2'] = 'WAP2'; +$txt['rss'] = 'RSS'; +$txt['xhtml'] = 'XHTML'; +$txt['html'] = 'HTML'; + +$txt['guest'] = 'Guest'; +$txt['guests'] = 'Guests'; +$txt['user'] = 'User'; +$txt['users'] = 'Users'; +$txt['hidden'] = 'Hidden'; +$txt['buddy'] = 'Buddy'; +$txt['buddies'] = 'Buddies'; +$txt['most_online_ever'] = 'Most Online Ever'; +$txt['most_online_today'] = 'Most Online Today'; + +$txt['merge_select_target_board'] = 'Select the target board of the merged topic'; +$txt['merge_select_poll'] = 'Select which poll the merged topic should have'; +$txt['merge_topic_list'] = 'Select topics to be merged'; +$txt['merge_select_subject'] = 'Select subject of merged topic'; +$txt['merge_custom_subject'] = 'Custom subject'; +$txt['merge_enforce_subject'] = 'Change the subject of all the messages'; +$txt['merge_include_notifications'] = 'Include notifications?'; +$txt['merge_check'] = 'Merge?'; +$txt['merge_no_poll'] = 'No poll'; + +$txt['response_prefix'] = 'Re: '; +$txt['current_icon'] = 'Current Icon'; +$txt['message_icon'] = 'Message Icon'; + +$txt['smileys_current'] = 'Current Smiley Set'; +$txt['smileys_none'] = 'No Smileys'; +$txt['smileys_forum_board_default'] = 'Forum/Board Default'; + +$txt['search_results'] = 'Search Results'; +$txt['search_no_results'] = 'Sorry, no matches were found'; + +$txt['totalTimeLogged1'] = 'Total time logged in: '; +$txt['totalTimeLogged2'] = ' days, '; +$txt['totalTimeLogged3'] = ' hours and '; +$txt['totalTimeLogged4'] = ' minutes.'; +$txt['totalTimeLogged5'] = 'd '; +$txt['totalTimeLogged6'] = 'h '; +$txt['totalTimeLogged7'] = 'm'; + +$txt['approve_thereis'] = 'There is'; +$txt['approve_thereare'] = 'There are'; +$txt['approve_member'] = 'one member'; +$txt['approve_members'] = 'members'; +$txt['approve_members_waiting'] = 'awaiting approval.'; + +$txt['notifyboard_turnon'] = 'Do you want a notification email when someone posts a new topic in this board?'; +$txt['notifyboard_turnoff'] = 'Are you sure you do not want to receive new topic notifications for this board?'; + +$txt['activate_code'] = 'Your activation code is'; + +$txt['find_members'] = 'Find Members'; +$txt['find_username'] = 'Name, username, or email address'; +$txt['find_buddies'] = 'Show Buddies Only?'; +$txt['find_wildcards'] = 'Allowed Wildcards: *, ?'; +$txt['find_no_results'] = 'No results found'; +$txt['find_results'] = 'Results'; +$txt['find_close'] = 'Close'; + +$txt['unread_since_visit'] = 'Show unread posts since last visit.'; +$txt['show_unread_replies'] = 'Show new replies to your posts.'; + +$txt['change_color'] = 'Change Color'; + +$txt['quickmod_delete_selected'] = 'Remove Selected'; + +// In this string, don't use entities. (&, etc.) +$txt['show_personal_messages'] = 'You have received one or more new personal messages.\\nWould you like to open a new window to view them?'; + +$txt['previous_next_back'] = '« previous'; +$txt['previous_next_forward'] = 'next »'; + +$txt['movetopic_auto_board'] = '[BOARD]'; +$txt['movetopic_auto_topic'] = '[TOPIC LINK]'; +$txt['movetopic_default'] = 'This topic has been moved to ' . $txt['movetopic_auto_board'] . ".\n\n" . $txt['movetopic_auto_topic']; + +$txt['upshrink_description'] = 'Shrink or expand the header.'; + +$txt['mark_unread'] = 'Mark unread'; + +$txt['ssi_not_direct'] = 'Please don\'t access SSI.php by URL directly; you may want to use the path (%1$s) or add ?ssi_function=something.'; +$txt['ssi_session_broken'] = 'SSI.php was unable to load a session! This may cause problems with logout and other functions - please make sure SSI.php is included before *anything* else in all your scripts!'; + +// Escape any single quotes in here twice.. 'it\'s' -> 'it\\\'s'. +$txt['preview_title'] = 'Preview post'; +$txt['preview_fetch'] = 'Fetching preview...'; +$txt['preview_new'] = 'New message'; +$txt['error_while_submitting'] = 'The following error or errors occurred while posting this message:'; +$txt['error_old_topic'] = 'Warning: this topic has not been posted in for at least %1$d days.
    Unless you\'re sure you want to reply, please consider starting a new topic.'; + +$txt['split_selected_posts'] = 'Selected posts'; +$txt['split_selected_posts_desc'] = 'The posts below will form a new topic after splitting.'; +$txt['split_reset_selection'] = 'reset selection'; + +$txt['modify_cancel'] = 'Cancel'; +$txt['mark_read_short'] = 'Mark Read'; + +$txt['pm_short'] = 'My Messages'; +$txt['pm_menu_read'] = 'Read your messages'; +$txt['pm_menu_send'] = 'Send a message'; + +$txt['hello_member_ndt'] = 'Hello'; + +$txt['unapproved_posts'] = 'Unapproved Posts (Topics: %1$d, Posts: %2$d)'; + +$txt['ajax_in_progress'] = 'Loading...'; + +$txt['mod_reports_waiting'] = 'There are currently %1$d moderator reports open.'; + +$txt['view_unread_category'] = 'Unread Posts'; +$txt['verification'] = 'Verification'; +$txt['visual_verification_description'] = 'Type the letters shown in the picture'; +$txt['visual_verification_sound'] = 'Listen to the letters'; +$txt['visual_verification_request_new'] = 'Request another image'; + +// Sub menu labels +$txt['summary'] = 'Summary'; +$txt['account'] = 'Account Settings'; +$txt['forumprofile'] = 'Forum Profile'; + +$txt['modSettings_title'] = 'Features and Options'; +$txt['package'] = 'Package Manager'; +$txt['errlog'] = 'Error Log'; +$txt['edit_permissions'] = 'Permissions'; +$txt['mc_unapproved_attachments'] = 'Unapproved Attachments'; +$txt['mc_unapproved_poststopics'] = 'Unapproved Posts and Topics'; +$txt['mc_reported_posts'] = 'Reported Posts'; +$txt['modlog_view'] = 'Moderation Log'; +$txt['calendar_menu'] = 'View Calendar'; + +//!!! Send email strings - should move? +$txt['send_email'] = 'Send Email'; +$txt['send_email_disclosed'] = 'Note this will be visible to the recipient.'; +$txt['send_email_subject'] = 'Email Subject'; + +$txt['ignoring_user'] = 'You are ignoring this user.'; +$txt['show_ignore_user_post'] = 'Show me the post.'; + +$txt['spider'] = 'Spider'; +$txt['spiders'] = 'Spiders'; +$txt['openid'] = 'OpenID'; + +$txt['downloads'] = 'Downloads'; +$txt['filesize'] = 'Filesize'; +$txt['subscribe_webslice'] = 'Subscribe to Webslice'; + +// Restore topic +$txt['restore_topic'] = 'Restore Topic'; +$txt['restore_message'] = 'Restore'; +$txt['quick_mod_restore'] = 'Restore Selected'; + +// Editor prompt. +$txt['prompt_text_email'] = 'Please enter the email address.'; +$txt['prompt_text_ftp'] = 'Please enter the ftp address.'; +$txt['prompt_text_url'] = 'Please enter the URL you wish to link to.'; +$txt['prompt_text_img'] = 'Enter image location'; + +// Escape any single quotes in here twice.. 'it\'s' -> 'it\\\'s'. +$txt['autosuggest_delete_item'] = 'Delete Item'; + +// Debug related - when $db_show_debug is true. +$txt['debug_templates'] = 'Templates: '; +$txt['debug_subtemplates'] = 'Sub templates: '; +$txt['debug_language_files'] = 'Language files: '; +$txt['debug_stylesheets'] = 'Style sheets: '; +$txt['debug_files_included'] = 'Files included: '; +$txt['debug_kb'] = 'KB.'; +$txt['debug_show'] = 'show'; +$txt['debug_cache_hits'] = 'Cache hits: '; +$txt['debug_cache_seconds_bytes'] = '%1$ss - %2$s bytes'; +$txt['debug_cache_seconds_bytes_total'] = '%1$ss for %2$s bytes'; +$txt['debug_queries_used'] = 'Queries used: %1$d.'; +$txt['debug_queries_used_and_warnings'] = 'Queries used: %1$d, %2$d warnings.'; +$txt['debug_query_in_line'] = 'in %1$s line %2$s, '; +$txt['debug_query_which_took'] = 'which took %1$s seconds.'; +$txt['debug_query_which_took_at'] = 'which took %1$s seconds at %2$s into request.'; +$txt['debug_show_queries'] = '[Show Queries]'; +$txt['debug_hide_queries'] = '[Hide Queries]'; + +?> \ No newline at end of file diff --git a/Themes/default/languages/index.php b/Themes/default/languages/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/languages/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/license.txt b/Themes/default/license.txt new file mode 100644 index 0000000..7e09434 --- /dev/null +++ b/Themes/default/license.txt @@ -0,0 +1,27 @@ +Copyright © 2011 Simple Machines. All rights reserved. + +Developed by: Simple Machines Forum Project + Simple Machines + http://www.simplemachines.org + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + 3. Neither the names of Simple Machines Forum, Simple Machines, nor + the names of its contributors may be used to endorse or promote + products derived from this Software without specific prior written + permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +WITH THE SOFTWARE. + +This license may be viewed online at http://www.simplemachines.org/about/smf/license.php \ No newline at end of file diff --git a/Themes/default/scripts/PersonalMessage.js b/Themes/default/scripts/PersonalMessage.js new file mode 100644 index 0000000..099c063 --- /dev/null +++ b/Themes/default/scripts/PersonalMessage.js @@ -0,0 +1,88 @@ + +// Handle the JavaScript surrounding personal messages send form. +function smf_PersonalMessageSend(oOptions) +{ + this.opt = oOptions; + this.oBccDiv = null; + this.oBccDiv2 = null; + this.oToAutoSuggest = null; + this.oBccAutoSuggest = null; + this.oToListContainer = null; + this.init(); +} + +smf_PersonalMessageSend.prototype.init = function() +{ + if (!this.opt.bBccShowByDefault) + { + // Hide the BCC control. + this.oBccDiv = document.getElementById(this.opt.sBccDivId); + this.oBccDiv.style.display = 'none'; + this.oBccDiv2 = document.getElementById(this.opt.sBccDivId2); + this.oBccDiv2.style.display = 'none'; + + // Show the link to bet the BCC control back. + var oBccLinkContainer = document.getElementById(this.opt.sBccLinkContainerId); + oBccLinkContainer.style.display = ''; + setInnerHTML(oBccLinkContainer, this.opt.sShowBccLinkTemplate); + + // Make the link show the BCC control. + var oBccLink = document.getElementById(this.opt.sBccLinkId); + oBccLink.instanceRef = this; + oBccLink.onclick = function () { + this.instanceRef.showBcc(); + return false; + }; + } + + var oToControl = document.getElementById(this.opt.sToControlId); + this.oToAutoSuggest = new smc_AutoSuggest({ + sSelf: this.opt.sSelf + '.oToAutoSuggest', + sSessionId: this.opt.sSessionId, + sSessionVar: this.opt.sSessionVar, + sSuggestId: 'to_suggest', + sControlId: this.opt.sToControlId, + sSearchType: 'member', + sPostName: 'recipient_to', + sURLMask: 'action=profile;u=%item_id%', + sTextDeleteItem: this.opt.sTextDeleteItem, + bItemList: true, + sItemListContainerId: 'to_item_list_container', + aListItems: this.opt.aToRecipients + }); + this.oToAutoSuggest.registerCallback('onBeforeAddItem', this.opt.sSelf + '.callbackAddItem'); + + this.oBccAutoSuggest = new smc_AutoSuggest({ + sSelf: this.opt.sSelf + '.oBccAutoSuggest', + sSessionId: this.opt.sSessionId, + sSessionVar: this.opt.sSessionVar, + sSuggestId: 'bcc_suggest', + sControlId: this.opt.sBccControlId, + sSearchType: 'member', + sPostName: 'recipient_bcc', + sURLMask: 'action=profile;u=%item_id%', + sTextDeleteItem: this.opt.sTextDeleteItem, + bItemList: true, + sItemListContainerId: 'bcc_item_list_container', + aListItems: this.opt.aBccRecipients + }); + this.oBccAutoSuggest.registerCallback('onBeforeAddItem', this.opt.sSelf + '.callbackAddItem'); + +} + +smf_PersonalMessageSend.prototype.showBcc = function() +{ + // No longer hide it, show it to the world! + this.oBccDiv.style.display = ''; + this.oBccDiv2.style.display = ''; +} + + +// Prevent items to be added twice or to both the 'To' and 'Bcc'. +smf_PersonalMessageSend.prototype.callbackAddItem = function(oAutoSuggestInstance, sSuggestId) +{ + this.oToAutoSuggest.deleteAddedItem(sSuggestId); + this.oBccAutoSuggest.deleteAddedItem(sSuggestId); + + return true; +} diff --git a/Themes/default/scripts/admin.js b/Themes/default/scripts/admin.js new file mode 100644 index 0000000..e99e041 --- /dev/null +++ b/Themes/default/scripts/admin.js @@ -0,0 +1,330 @@ +/* + smf_AdminIndex(oOptions) + { + public init() + public loadAdminIndex() + public setAnnouncements() + public showCurrentVersion() + public checkUpdateAvailable() + } + + smf_ViewVersions(oOptions) + { + public init() + public loadViewVersions + public swapOption(oSendingElement, sName) + public compareVersions(sCurrent, sTarget) + public determineVersions() + } +*/ + + + +// Handle the JavaScript surrounding the admin and moderation center. +function smf_AdminIndex(oOptions) +{ + this.opt = oOptions; + this.init(); +} + +smf_AdminIndex.prototype.init = function () +{ + window.adminIndexInstanceRef = this; + var fHandlePageLoaded = function () { + window.adminIndexInstanceRef.loadAdminIndex(); + } + addLoadEvent(fHandlePageLoaded); +} + +smf_AdminIndex.prototype.loadAdminIndex = function () +{ + // Load the text box containing the latest news items. + if (this.opt.bLoadAnnouncements) + this.setAnnouncements(); + + // Load the current SMF and your SMF version numbers. + if (this.opt.bLoadVersions) + this.showCurrentVersion(); + + // Load the text box that sais there's a new version available. + if (this.opt.bLoadUpdateNotification) + this.checkUpdateAvailable(); +} + + +smf_AdminIndex.prototype.setAnnouncements = function () +{ + if (!('smfAnnouncements' in window) || !('length' in window.smfAnnouncements)) + return; + + var sMessages = ''; + for (var i = 0; i < window.smfAnnouncements.length; i++) + sMessages += this.opt.sAnnouncementMessageTemplate.replace('%href%', window.smfAnnouncements[i].href).replace('%subject%', window.smfAnnouncements[i].subject).replace('%time%', window.smfAnnouncements[i].time).replace('%message%', window.smfAnnouncements[i].message); + + setInnerHTML(document.getElementById(this.opt.sAnnouncementContainerId), this.opt.sAnnouncementTemplate.replace('%content%', sMessages)); +} + +smf_AdminIndex.prototype.showCurrentVersion = function () +{ + if (!('smfVersion' in window)) + return; + + var oSmfVersionContainer = document.getElementById(this.opt.sSmfVersionContainerId); + var oYourVersionContainer = document.getElementById(this.opt.sYourVersionContainerId); + + setInnerHTML(oSmfVersionContainer, window.smfVersion); + + var sCurrentVersion = getInnerHTML(oYourVersionContainer); + if (sCurrentVersion != window.smfVersion) + setInnerHTML(oYourVersionContainer, this.opt.sVersionOutdatedTemplate.replace('%currentVersion%', sCurrentVersion)); +} + +smf_AdminIndex.prototype.checkUpdateAvailable = function () +{ + if (!('smfUpdatePackage' in window)) + return; + + var oContainer = document.getElementById(this.opt.sUpdateNotificationContainerId); + + // Are we setting a custom title and message? + var sTitle = 'smfUpdateTitle' in window ? window.smfUpdateTitle : this.opt.sUpdateNotificationDefaultTitle; + var sMessage = 'smfUpdateNotice' in window ? window.smfUpdateNotice : this.opt.sUpdateNotificationDefaultMessage; + + setInnerHTML(oContainer, this.opt.sUpdateNotificationTemplate.replace('%title%', sTitle).replace('%message%', sMessage)); + + // Parse in the package download URL if it exists in the string. + document.getElementById('update-link').href = this.opt.sUpdateNotificationLink.replace('%package%', window.smfUpdatePackage); + + // If we decide to override life into "red" mode, do it. + if ('smfUpdateCritical' in window) + { + document.getElementById('update_table').style.backgroundColor = '#aa2222'; + document.getElementById('update_title').style.backgroundColor = '#dd2222'; + document.getElementById('update_title').style.color = 'white'; + document.getElementById('update_message').style.backgroundColor = '#eebbbb'; + document.getElementById('update_message').style.color = 'black'; + } +} + + + +function smf_ViewVersions (oOptions) +{ + this.opt = oOptions; + this.oSwaps = {}; + this.init(); +} + +smf_ViewVersions.prototype.init = function () +{ + // Load this on loading of the page. + window.viewVersionsInstanceRef = this; + var fHandlePageLoaded = function () { + window.viewVersionsInstanceRef.loadViewVersions(); + } + addLoadEvent(fHandlePageLoaded); +} + +smf_ViewVersions.prototype.loadViewVersions = function () +{ + this.determineVersions(); +} + +smf_ViewVersions.prototype.swapOption = function (oSendingElement, sName) +{ + // If it is undefined, or currently off, turn it on - otherwise off. + this.oSwaps[sName] = !(sName in this.oSwaps) || !this.oSwaps[sName]; + document.getElementById(sName).style.display = this.oSwaps[sName] ? '' : 'none'; + + // Unselect the link and return false. + oSendingElement.blur(); + return false; +} + +smf_ViewVersions.prototype.compareVersions = function (sCurrent, sTarget) +{ + var aVersions = aParts = new Array(); + var aCompare = new Array(sCurrent, sTarget); + + for (var i = 0; i < 2; i++) + { + // Clean the version and extract the version parts. + var sClean = aCompare[i].toLowerCase().replace(/ /g, '').replace(/2.0rc1-1/, '2.0rc1.1'); + aParts = sClean.match(/(\d+)(?:\.(\d+|))?(?:\.)?(\d+|)(?:(alpha|beta|rc)(\d+|)(?:\.)?(\d+|))?(?:(dev))?(\d+|)/); + + // No matches? + if (aParts == null) + return false; + + // Build an array of parts. + aVersions[i] = [ + aParts[1] > 0 ? parseInt(aParts[1]) : 0, + aParts[2] > 0 ? parseInt(aParts[2]) : 0, + aParts[3] > 0 ? parseInt(aParts[3]) : 0, + typeof(aParts[4]) == 'undefined' ? 'stable' : aParts[4], + aParts[5] > 0 ? parseInt(aParts[5]) : 0, + aParts[6] > 0 ? parseInt(aParts[6]) : 0, + typeof(aParts[7]) != 'undefined', + ]; + } + + // Loop through each category. + for (i = 0; i < 7; i++) + { + // Is there something for us to calculate? + if (aVersions[0][i] != aVersions[1][i]) + { + // Dev builds are a problematic exception. + // (stable) dev < (stable) but (unstable) dev = (unstable) + if (i == 3) + return aVersions[0][i] < aVersions[1][i] ? !aVersions[1][6] : aVersions[0][6]; + else if (i == 6) + return aVersions[0][6] ? aVersions[1][3] == 'stable' : false; + // Otherwise a simple comparison. + else + return aVersions[0][i] < aVersions[1][i]; + } + } + + // They are the same! + return false; +} + +smf_ViewVersions.prototype.determineVersions = function () +{ + var oHighYour = { + Sources: '??', + Default: '??', + Languages: '??', + Templates: '??' + }; + var oHighCurrent = { + Sources: '??', + Default: '??', + Languages: '??', + Templates: '??' + }; + var oLowVersion = { + Sources: false, + Default: false, + Languages: false, + Templates: false + }; + + var sSections = [ + 'Sources', + 'Default', + 'Languages', + 'Templates' + ]; + + for (var i = 0, n = sSections.length; i < n; i++) + { + // Collapse all sections. + var oSection = document.getElementById(sSections[i]); + if (typeof(oSection) == 'object' && oSection != null) + oSection.style.display = 'none'; + + // Make all section links clickable. + var oSectionLink = document.getElementById(sSections[i] + '-link'); + if (typeof(oSectionLink) == 'object' && oSectionLink != null) + { + oSectionLink.instanceRef = this; + oSectionLink.sSection = sSections[i]; + oSectionLink.onclick = function () { + this.instanceRef.swapOption(this, this.sSection); + return false; + }; + } + } + + if (!('smfVersions' in window)) + window.smfVersions = {}; + + for (var sFilename in window.smfVersions) + { + if (!document.getElementById('current' + sFilename)) + continue; + + var sYourVersion = getInnerHTML(document.getElementById('your' + sFilename)); + + var sCurVersionType; + for (var sVersionType in oLowVersion) + if (sFilename.substr(0, sVersionType.length) == sVersionType) + { + sCurVersionType = sVersionType; + break; + } + + if (typeof(sCurVersionType) != 'undefined') + { + if ((this.compareVersions(oHighYour[sCurVersionType], sYourVersion) || oHighYour[sCurVersionType] == '??') && !oLowVersion[sCurVersionType]) + oHighYour[sCurVersionType] = sYourVersion; + if (this.compareVersions(oHighCurrent[sCurVersionType], smfVersions[sFilename]) || oHighCurrent[sCurVersionType] == '??') + oHighCurrent[sCurVersionType] = smfVersions[sFilename]; + + if (this.compareVersions(sYourVersion, smfVersions[sFilename])) + { + oLowVersion[sCurVersionType] = sYourVersion; + document.getElementById('your' + sFilename).style.color = 'red'; + } + } + else if (this.compareVersions(sYourVersion, smfVersions[sFilename])) + oLowVersion[sCurVersionType] = sYourVersion; + + setInnerHTML(document.getElementById('current' + sFilename), smfVersions[sFilename]); + setInnerHTML(document.getElementById('your' + sFilename), sYourVersion); + } + + if (!('smfLanguageVersions' in window)) + window.smfLanguageVersions = {}; + + for (sFilename in window.smfLanguageVersions) + { + for (var i = 0; i < this.opt.aKnownLanguages.length; i++) + { + if (!document.getElementById('current' + sFilename + this.opt.aKnownLanguages[i])) + continue; + + setInnerHTML(document.getElementById('current' + sFilename + this.opt.aKnownLanguages[i]), smfLanguageVersions[sFilename]); + + sYourVersion = getInnerHTML(document.getElementById('your' + sFilename + this.opt.aKnownLanguages[i])); + setInnerHTML(document.getElementById('your' + sFilename + this.opt.aKnownLanguages[i]), sYourVersion); + + if ((this.compareVersions(oHighYour.Languages, sYourVersion) || oHighYour.Languages == '??') && !oLowVersion.Languages) + oHighYour.Languages = sYourVersion; + if (this.compareVersions(oHighCurrent.Languages, smfLanguageVersions[sFilename]) || oHighCurrent.Languages == '??') + oHighCurrent.Languages = smfLanguageVersions[sFilename]; + + if (this.compareVersions(sYourVersion, smfLanguageVersions[sFilename])) + { + oLowVersion.Languages = sYourVersion; + document.getElementById('your' + sFilename + this.opt.aKnownLanguages[i]).style.color = 'red'; + } + } + } + + setInnerHTML(document.getElementById('yourSources'), oLowVersion.Sources ? oLowVersion.Sources : oHighYour.Sources); + setInnerHTML(document.getElementById('currentSources'), oHighCurrent.Sources); + if (oLowVersion.Sources) + document.getElementById('yourSources').style.color = 'red'; + + setInnerHTML(document.getElementById('yourDefault'), oLowVersion.Default ? oLowVersion.Default : oHighYour.Default); + setInnerHTML(document.getElementById('currentDefault'), oHighCurrent.Default); + if (oLowVersion.Default) + document.getElementById('yourDefault').style.color = 'red'; + + if (document.getElementById('Templates')) + { + setInnerHTML(document.getElementById('yourTemplates'), oLowVersion.Templates ? oLowVersion.Templates : oHighYour.Templates); + setInnerHTML(document.getElementById('currentTemplates'), oHighCurrent.Templates); + + if (oLowVersion.Templates) + document.getElementById('yourTemplates').style.color = 'red'; + } + + setInnerHTML(document.getElementById('yourLanguages'), oLowVersion.Languages ? oLowVersion.Languages : oHighYour.Languages); + setInnerHTML(document.getElementById('currentLanguages'), oHighCurrent.Languages); + if (oLowVersion.Languages) + document.getElementById('yourLanguages').style.color = 'red'; +} \ No newline at end of file diff --git a/Themes/default/scripts/captcha.js b/Themes/default/scripts/captcha.js new file mode 100644 index 0000000..f74a544 --- /dev/null +++ b/Themes/default/scripts/captcha.js @@ -0,0 +1,79 @@ +// This file contains javascript associated with the captcha visual verification stuffs. + +function smfCaptcha(imageURL, uniqueID, useLibrary, letterCount) +{ + // By default the letter count is five. + if (!letterCount) + letterCount = 5; + + uniqueID = uniqueID ? '_' + uniqueID : ''; + autoCreate(); + + // Automatically get the captcha event handlers in place and the like. + function autoCreate() + { + // Is there anything to cycle images with - if so attach the refresh image function? + var cycleHandle = document.getElementById('visual_verification' + uniqueID + '_refresh'); + if (cycleHandle) + { + createEventListener(cycleHandle); + cycleHandle.addEventListener('click', refreshImages, false); + } + + // Maybe a voice is here to spread light? + var soundHandle = document.getElementById('visual_verification' + uniqueID + '_sound'); + if (soundHandle) + { + createEventListener(soundHandle); + soundHandle.addEventListener('click', playSound, false); + } + } + + // Change the images. + function refreshImages() + { + // Make sure we are using a new rand code. + var new_url = new String(imageURL); + new_url = new_url.substr(0, new_url.indexOf("rand=") + 5); + + // Quick and dirty way of converting decimal to hex + var hexstr = "0123456789abcdef"; + for(var i=0; i < 32; i++) + new_url = new_url + hexstr.substr(Math.floor(Math.random() * 16), 1); + + if (useLibrary && document.getElementById("verification_image" + uniqueID)) + { + document.getElementById("verification_image" + uniqueID).src = new_url; + } + else if (document.getElementById("verification_image" + uniqueID)) + { + for (i = 1; i <= letterCount; i++) + if (document.getElementById("verification_image" + uniqueID + "_" + i)) + document.getElementById("verification_image" + uniqueID + "_" + i).src = new_url + ";letter=" + i; + } + + return false; + } + + // Request a sound... play it Mr Soundman... + function playSound(ev) + { + if (!ev) + ev = window.event; + + popupFailed = reqWin(imageURL + ";sound", 400, 120); + // Don't follow the link if the popup worked, which it would have done! + if (!popupFailed) + { + if (is_ie && ev.cancelBubble) + ev.cancelBubble = true; + else if (ev.stopPropagation) + { + ev.stopPropagation(); + ev.preventDefault(); + } + } + + return popupFailed; + } +} \ No newline at end of file diff --git a/Themes/default/scripts/editor.js b/Themes/default/scripts/editor.js new file mode 100644 index 0000000..1aecb76 --- /dev/null +++ b/Themes/default/scripts/editor.js @@ -0,0 +1,1751 @@ +// *** smc_Editor class. +function smc_Editor(oOptions) +{ + this.opt = oOptions; + + // Create some links to the editor object. + this.oTextHandle = null; + this.sCurrentText = 'sText' in this.opt ? this.opt.sText : ''; + + // How big? + this.sEditWidth = 'sEditWidth' in this.opt ? this.opt.sEditWidth : '70%'; + this.sEditHeight = 'sEditHeight' in this.opt ? this.opt.sEditHeight : '150px'; + + this.showDebug = false; + this.bRichTextEnabled = 'bWysiwyg' in this.opt && this.opt.bWysiwyg; + // This doesn't work on Opera as they cannot restore focus after clicking a BBC button. + this.bRichTextPossible = !this.opt.bRichEditOff && ((is_ie5up && !is_ie50) || is_ff || is_opera95up || is_safari || is_chrome) && !(is_iphone || is_android); + + this.oFrameHandle = null; + this.oFrameDocument = null; + this.oFrameWindow = null; + + // These hold the breadcrumb. + this.oBreadHandle = null; + this.oResizerElement = null; + + // Kinda holds all the useful stuff. + this.aKeyboardShortcuts = new Array(); + + // This tracks the cursor position on IE to avoid refocus problems. + this.cursorX = 0; + this.cursorY = 0; + + // This is all the elements that can have a simple execCommand. + this.oSimpleExec = { + b: 'bold', + u: 'underline', + i: 'italic', + s: 'strikethrough', + left: 'justifyleft', + center: 'justifycenter', + right: 'justifyright', + hr: 'inserthorizontalrule', + list: 'insertunorderedlist', + orderlist: 'insertorderedlist', + sub: 'subscript', + sup: 'superscript', + indent: 'indent', + outdent: 'outdent' + } + + // Codes to call a private function + this.oSmfExec = { + unformat: 'removeFormatting', + toggle: 'toggleView' + } + + // Any special breadcrumb mappings to ensure we show a consistant tag name. + this.breadCrumbNameTags = { + strike: 's', + strong: 'b', + em: 'i' + } + + this.aBreadCrumbNameStyles = [ + { + sStyleType: 'text-decoration', + sStyleValue: 'underline', + sBbcTag: 'u' + }, + { + sStyleType: 'text-decoration', + sStyleValue: 'line-through', + sBbcTag: 's' + }, + { + sStyleType: 'text-align', + sStyleValue: 'left', + sBbcTag: 'left' + }, + { + sStyleType: 'text-align', + sStyleValue: 'center', + sBbcTag: 'center' + }, + { + sStyleType: 'text-align', + sStyleValue: 'right', + sBbcTag: 'right' + }, + { + sStyleType: 'font-weight', + sStyleValue: 'bold', + sBbcTag: 'b' + }, + { + sStyleType: 'font-style', + sStyleValue: 'italic', + sBbcTag: 'i' + } + ]; + + // All the fonts in the world. + this.aFontFaces = [ + 'Arial', + 'Arial Black', + 'Impact', + 'Verdana', + 'Times New Roman', + 'Georgia', + 'Andale Mono', + 'Trebuchet MS', + 'Comic Sans MS' + ]; + // Font maps (HTML => CSS size) + this.aFontSizes = [ + 0, + 8, + 10, + 12, + 14, + 18, + 24, + 36 + ]; + // Color maps! (hex => name) + this.oFontColors = { + black: '#000000', + red: '#ff0000', + yellow: '#ffff00', + pink: '#ffc0cb', + green: '#008000', + orange: '#ffa500', + purple: '#800080', + blue: '#0000ff', + beige: '#f5f5dc', + brown: '#a52a2a', + teal: '#008080', + navy: '#000080', + maroon: '#800000', + limegreen: '#32cd32' + } + + this.sFormId = 'sFormId' in this.opt ? this.opt.sFormId : 'postmodify'; + this.iArrayPosition = smf_editorArray.length; + + // Current resize state. + this.osmc_EditorCurrentResize = {}; + + this.init(); +} + +smc_Editor.prototype.init = function() +{ + // Define the event wrapper functions. + var oCaller = this; + this.aEventWrappers = { + editorKeyUp: function(oEvent) {return oCaller.editorKeyUp(oEvent);}, + shortcutCheck: function(oEvent) {return oCaller.shortcutCheck(oEvent);}, + editorBlur: function(oEvent) {return oCaller.editorBlur(oEvent);}, + editorFocus: function(oEvent) {return oCaller.editorFocus(oEvent);}, + startResize: function(oEvent) {return oCaller.startResize(oEvent);}, + resizeOverDocument: function(oEvent) {return oCaller.resizeOverDocument(oEvent);}, + endResize: function(oEvent) {return oCaller.endResize(oEvent);}, + resizeOverIframe: function(oEvent) {return oCaller.resizeOverIframe(oEvent);} + }; + + // Set the textHandle. + this.oTextHandle = document.getElementById(this.opt.sUniqueId); + + // Ensure the currentText is set correctly depending on the mode. + if (this.sCurrentText == '' && !this.bRichTextEnabled) + this.sCurrentText = getInnerHTML(this.oTextHandle).php_unhtmlspecialchars(); + + // Only try to do this if rich text is supported. + if (this.bRichTextPossible) + { + // Make the iframe itself, stick it next to the current text area, and give it an ID. + this.oFrameHandle = document.createElement('iframe'); + this.oFrameHandle.src = 'about:blank'; + this.oFrameHandle.id = 'html_' + this.opt.sUniqueId; + this.oFrameHandle.className = 'rich_editor_frame'; + this.oFrameHandle.style.display = 'none'; + this.oFrameHandle.style.margin = '0px'; + this.oFrameHandle.tabIndex = this.oTextHandle.tabIndex; + this.oTextHandle.parentNode.appendChild(this.oFrameHandle); + + // Create some handy shortcuts. + this.oFrameDocument = this.oFrameHandle.contentDocument ? this.oFrameHandle.contentDocument : ('contentWindow' in this.oFrameHandle ? this.oFrameHandle.contentWindow.document : this.oFrameHandle.document); + this.oFrameWindow = 'contentWindow' in this.oFrameHandle ? this.oFrameHandle.contentWindow : this.oFrameHandle.document.parentWindow; + + // Create the debug window... and stick this under the main frame - make it invisible by default. + this.oBreadHandle = document.createElement('div'); + this.oBreadHandle.id = 'bread_' . uid; + this.oBreadHandle.style.visibility = 'visible'; + this.oBreadHandle.style.display = 'none'; + this.oFrameHandle.parentNode.appendChild(this.oBreadHandle); + + // Size the iframe dimensions to something sensible. + this.oFrameHandle.style.width = this.sEditWidth; + this.oFrameHandle.style.height = this.sEditHeight; + this.oFrameHandle.style.visibility = 'visible'; + + // Only bother formatting the debug window if debug is enabled. + if (this.showDebug) + { + this.oBreadHandle.style.width = this.sEditWidth; + this.oBreadHandle.style.height = '20px'; + this.oBreadHandle.className = 'windowbg2'; + this.oBreadHandle.style.border = '1px black solid'; + this.oBreadHandle.style.display = ''; + } + + // Populate the editor with nothing by default. + if (!is_opera95up) + { + this.oFrameDocument.open(); + this.oFrameDocument.write(''); + this.oFrameDocument.close(); + } + + // Right to left mode? + if (this.opt.bRTL) + { + this.oFrameDocument.dir = "rtl"; + this.oFrameDocument.body.dir = "rtl"; + } + + // Mark it as editable... + if (this.oFrameDocument.body.contentEditable) + this.oFrameDocument.body.contentEditable = true; + else + { + this.oFrameHandle.style.display = ''; + this.oFrameDocument.designMode = 'on'; + this.oFrameHandle.style.display = 'none'; + } + + // Now we need to try and style the editor - internet explorer allows us to do the whole lot. + if (document.styleSheets['editor_css'] || document.styleSheets['editor_ie_css']) + { + var oMyStyle = this.oFrameDocument.createElement('style'); + this.oFrameDocument.documentElement.firstChild.appendChild(oMyStyle); + oMyStyle.styleSheet.cssText = document.styleSheets['editor_ie_css'] ? document.styleSheets['editor_ie_css'].cssText : document.styleSheets['editor_css'].cssText; + } + // Otherwise we seem to have to try to rip out each of the styles one by one! + else if (document.styleSheets.length) + { + var bFoundSomething = false; + // First we need to find the right style sheet. + for (var i = 0, iNumStyleSheets = document.styleSheets.length; i < iNumStyleSheets; i++) + { + // Start off looking for the right style sheet. + if (!document.styleSheets[i].href || document.styleSheets[i].href.indexOf('editor') < 1) + continue; + + // Firefox won't allow us to get a CSS file which ain't in the right URL. + try + { + if (document.styleSheets[i].cssRules.length < 1) + continue; + } + catch (e) + { + continue; + } + + // Manually try to find the rich_editor class. + for (var r = 0, iNumRules = document.styleSheets[i].cssRules.length; r < iNumRules; r++) + { + // Got the main editor? + if (document.styleSheets[i].cssRules[r].selectorText == '.rich_editor') + { + // Set some possible styles. + if (document.styleSheets[i].cssRules[r].style.color) + this.oFrameDocument.body.style.color = document.styleSheets[i].cssRules[r].style.color; + if (document.styleSheets[i].cssRules[r].style.backgroundColor) + this.oFrameDocument.body.style.backgroundColor = document.styleSheets[i].cssRules[r].style.backgroundColor; + if (document.styleSheets[i].cssRules[r].style.fontSize) + this.oFrameDocument.body.style.fontSize = document.styleSheets[i].cssRules[r].style.fontSize; + if (document.styleSheets[i].cssRules[r].style.fontFamily) + this.oFrameDocument.body.style.fontFamily = document.styleSheets[i].cssRules[r].style.fontFamily; + if (document.styleSheets[i].cssRules[r].style.border) + this.oFrameDocument.body.style.border = document.styleSheets[i].cssRules[r].style.border; + bFoundSomething = true; + } + // The frame? + else if (document.styleSheets[i].cssRules[r].selectorText == '.rich_editor_frame') + { + if (document.styleSheets[i].cssRules[r].style.border) + this.oFrameHandle.style.border = document.styleSheets[i].cssRules[r].style.border; + } + } + } + + // Didn't find it? + if (!bFoundSomething) + { + // Do something that is better than nothing. + this.oFrameDocument.body.style.color = 'black'; + this.oFrameDocument.body.style.backgroundColor = 'white'; + this.oFrameDocument.body.style.fontSize = '78%'; + this.oFrameDocument.body.style.fontFamily = '"Verdana", "Arial", "Helvetica", "sans-serif"'; + this.oFrameDocument.body.style.border = 'none'; + this.oFrameHandle.style.border = '1px solid #808080'; + if (is_opera) + this.oFrameDocument.body.style.height = '99%'; + } + } + + // Apply the class... + this.oFrameDocument.body.className = 'rich_editor'; + + // Set the frame padding/margin inside the editor. + this.oFrameDocument.body.style.padding = '1px'; + this.oFrameDocument.body.style.margin = '0'; + + // Listen for input. + this.oFrameDocument.instanceRef = this; + this.oFrameHandle.instanceRef = this; + this.oTextHandle.instanceRef = this; + + // Attach addEventListener for those browsers that don't support it. + createEventListener(this.oFrameHandle); + createEventListener(this.oFrameDocument); + createEventListener(this.oTextHandle); + createEventListener(window); + createEventListener(document); + + // Attach functions to the key and mouse events. + this.oFrameDocument.addEventListener('keyup', this.aEventWrappers.editorKeyUp, true); + this.oFrameDocument.addEventListener('mouseup', this.aEventWrappers.editorKeyUp, true); + this.oFrameDocument.addEventListener('keydown', this.aEventWrappers.shortcutCheck, true); + this.oTextHandle.addEventListener('keydown', this.aEventWrappers.shortcutCheck, true); + + if (is_ie) + { + this.oFrameDocument.addEventListener('blur', this.aEventWrappers.editorBlur, true); + this.oFrameDocument.addEventListener('focus', this.aEventWrappers.editorFocus, true); + } + + // Show the iframe only if wysiwyrg is on - and hide the text area. + this.oTextHandle.style.display = this.bRichTextEnabled ? 'none' : ''; + this.oFrameHandle.style.display = this.bRichTextEnabled ? '' : 'none'; + this.oBreadHandle.style.display = this.bRichTextEnabled ? '' : 'none'; + } + // If we can't do advanced stuff then just do the basics. + else + { + // Cannot have WYSIWYG anyway! + this.bRichTextEnabled = false; + + // We need some of the event handlers. + createEventListener(this.oTextHandle); + createEventListener(window); + createEventListener(document); + } + + // Make sure we set the message mode correctly. + document.getElementById(this.opt.sUniqueId + '_mode').value = this.bRichTextEnabled ? 1 : 0; + + // Show the resizer. + if (document.getElementById(this.opt.sUniqueId + '_resizer') && (!is_opera || is_opera95up) && !(is_chrome && !this.bRichTextEnabled)) + { + // Currently nothing is being resized...I assume! + window.smf_oCurrentResizeEditor = null; + + this.oResizerElement = document.getElementById(this.opt.sUniqueId + '_resizer'); + this.oResizerElement.style.display = ''; + + createEventListener(this.oResizerElement); + this.oResizerElement.addEventListener('mousedown', this.aEventWrappers.startResize, false); + } + + // Set the text - if WYSIWYG is enabled that is. + if (this.bRichTextEnabled) + { + this.insertText(this.sCurrentText, true); + + // Better make us the focus! + this.setFocus(); + } + + // Finally, register shortcuts. + this.registerDefaultShortcuts(); + this.updateEditorControls(); +} + +// Return the current text. +smc_Editor.prototype.getText = function(bPrepareEntities, bModeOverride) +{ + var bCurMode = typeof(bModeOverride) != 'undefined' ? bModeOverride : this.bRichTextEnabled; + + if (!bCurMode || this.oFrameDocument == null) + { + var sText = this.oTextHandle.value; + if (bPrepareEntities) + sText = sText.replace(//g, '#smgt#').replace(/&/g, '#smamp#'); + } + else + { + var sText = this.oFrameDocument.body.innerHTML; + if (bPrepareEntities) + sText = sText.replace(/</g, '#smlt#').replace(/>/g, '#smgt#').replace(/&/g, '#smamp#'); + } + + // Clean it up - including removing semi-colons. + if (bPrepareEntities) + sText = sText.replace(/ /g, ' ').replace(/;/g, '#smcol#'); + + // Return it. + return sText; +} + +// Return the current text. +smc_Editor.prototype.unprotectText = function(sText) +{ + var bCurMode = typeof(bModeOverride) != 'undefined' ? bModeOverride : this.bRichTextEnabled; + + // This restores smlt, smgt and smamp into boring entities, to unprotect against XML'd information like quotes. + sText = sText.replace(/#smlt#/g, '<').replace(/#smgt#/g, '>').replace(/#smamp#/g, '&'); + + // Return it. + return sText; +} + +smc_Editor.prototype.editorKeyUp = function() +{ + // Rebuild the breadcrumb. + this.updateEditorControls(); +} + +smc_Editor.prototype.editorBlur = function() +{ + if (!is_ie) + return; + + // Need to do something here. +} + +smc_Editor.prototype.editorFocus = function() +{ + if (!is_ie) + return; + + // Need to do something here. +} + +// Rebuild the breadcrumb etc - and set things to the correct context. +smc_Editor.prototype.updateEditorControls = function() +{ + // Everything else is specific to HTML mode. + if (!this.bRichTextEnabled) + { + // Set none of the buttons active. + if (this.opt.oBBCBox) + this.opt.oBBCBox.setActive([]); + return; + } + + var aCrumb = new Array(); + var aAllCrumbs = new Array(); + var iMaxLength = 6; + + // What is the current element? + var oCurTag = this.getCurElement(); + + var i = 0; + while (typeof(oCurTag) == 'object' && oCurTag != null && oCurTag.nodeName.toLowerCase() != 'body' && i < iMaxLength) + { + aCrumb[i++] = oCurTag; + oCurTag = oCurTag.parentNode; + } + + // Now print out the tree. + var sTree = ''; + var sCurFontName = ''; + var sCurFontSize = ''; + var sCurFontColor = ''; + for (var i = 0, iNumCrumbs = aCrumb.length; i < iNumCrumbs; i++) + { + var sCrumbName = aCrumb[i].nodeName.toLowerCase(); + + // Does it have an alternative name? + if (sCrumbName in this.breadCrumbNameTags) + sCrumbName = this.breadCrumbNameTags[sCrumbName]; + // Don't bother with this... + else if (sCrumbName == 'p') + continue; + // A link? + else if (sCrumbName == 'a') + { + var sUrlInfo = aCrumb[i].getAttribute('href'); + sCrumbName = 'url'; + if (typeof(sUrlInfo) == 'string') + { + if (sUrlInfo.substr(0, 3) == 'ftp') + sCrumbName = 'ftp'; + else if (sUrlInfo.substr(0, 6) == 'mailto') + sCrumbName = 'email'; + } + } + else if (sCrumbName == 'span' || sCrumbName == 'div') + { + if (aCrumb[i].style) + { + for (var j = 0, iNumStyles = this.aBreadCrumbNameStyles.length; j < iNumStyles; j++) + { + // Do we have a font? + if (aCrumb[i].style.fontFamily && aCrumb[i].style.fontFamily != '' && sCurFontName == '') + { + sCurFontName = aCrumb[i].style.fontFamily; + sCrumbName = 'face'; + } + // ... or a font size? + if (aCrumb[i].style.fontSize && aCrumb[i].style.fontSize != '' && sCurFontSize == '') + { + sCurFontSize = aCrumb[i].style.fontSize; + sCrumbName = 'size'; + } + // ... even color? + if (aCrumb[i].style.color && aCrumb[i].style.color != '' && sCurFontColor == '') + { + sCurFontColor = aCrumb[i].style.color; + if (in_array(sCurFontColor, this.oFontColors)) + sCurFontColor = array_search(sCurFontColor, this.oFontColors); + sCrumbName = 'color'; + } + + if (this.aBreadCrumbNameStyles[j].sStyleType == 'text-align' && aCrumb[i].style.textAlign && aCrumb[i].style.textAlign == this.aBreadCrumbNameStyles[j].sStyleValue) + sCrumbName = this.aBreadCrumbNameStyles[j].sBbcTag; + else if (this.aBreadCrumbNameStyles[j].sStyleType == 'text-decoration' && aCrumb[i].style.textDecoration && aCrumb[i].style.textDecoration == this.aBreadCrumbNameStyles[j].sStyleValue) + sCrumbName = this.aBreadCrumbNameStyles[j].sBbcTag; + else if (this.aBreadCrumbNameStyles[j].sStyleType == 'font-weight' && aCrumb[i].style.fontWeight && aCrumb[i].style.fontWeight == this.aBreadCrumbNameStyles[j].sStyleValue) + sCrumbName = this.aBreadCrumbNameStyles[j].sBbcTag; + else if (this.aBreadCrumbNameStyles[j].sStyleType == 'font-style' && aCrumb[i].style.fontStyle && aCrumb[i].style.fontStyle == this.aBreadCrumbNameStyles[j].sStyleValue) + sCrumbName = this.aBreadCrumbNameStyles[j].sBbcTag; + } + } + } + // Do we have a font? + else if (sCrumbName == 'font') + { + if (aCrumb[i].getAttribute('face') && sCurFontName == '') + { + sCurFontName = aCrumb[i].getAttribute('face').toLowerCase(); + sCrumbName = 'face'; + } + if (aCrumb[i].getAttribute('size') && sCurFontSize == '') + { + sCurFontSize = aCrumb[i].getAttribute('size'); + sCrumbName = 'size'; + } + if (aCrumb[i].getAttribute('color') && sCurFontColor == '') + { + sCurFontColor = aCrumb[i].getAttribute('color'); + if (in_array(sCurFontColor, this.oFontColors)) + sCurFontColor = array_search(sCurFontColor, this.oFontColors); + sCrumbName = 'color'; + } + // Something else - ignore. + if (sCrumbName == 'font') + continue; + } + + sTree += (i != 0 ? ' >' : '') + ' ' + sCrumbName; + aAllCrumbs[aAllCrumbs.length] = sCrumbName; + } + + // Since we're in WYSIWYG state, show the toggle button as active. + aAllCrumbs[aAllCrumbs.length] = 'toggle'; + + this.opt.oBBCBox.setActive(aAllCrumbs); + + // Try set the font boxes correct. + this.opt.oBBCBox.setSelect('sel_face', sCurFontName); + this.opt.oBBCBox.setSelect('sel_size', sCurFontSize); + this.opt.oBBCBox.setSelect('sel_color', sCurFontColor); + + if (this.showDebug) + setInnerHTML(this.oBreadHandle, sTree); +} + +// Set the HTML content to be that of the text box - if we are in wysiwyg mode. +smc_Editor.prototype.doSubmit = function() +{ + if (this.bRichTextEnabled) + this.oTextHandle.value = this.oFrameDocument.body.innerHTML; +} + +// Populate the box with text. +smc_Editor.prototype.insertText = function(sText, bClear, bForceEntityReverse, iMoveCursorBack) +{ + if (bForceEntityReverse) + sText = this.unprotectText(sText); + + // Erase it all? + if (bClear) + { + if (this.bRichTextEnabled) + { + // This includes a work around for FF to get the cursor to show! + this.oFrameDocument.body.innerHTML = sText; + + // If FF trick the cursor into coming back! + if (is_ff || is_opera) + { + // For some entirely unknown reason FF3 Beta 2 and some Opera versions + // require this. + this.oFrameDocument.body.contentEditable = false; + + this.oFrameDocument.designMode = 'off'; + this.oFrameDocument.designMode = 'on'; + } + } + else + this.oTextHandle.value = sText; + } + else + { + this.setFocus(); + if (this.bRichTextEnabled) + { + // IE croaks if you have an image selected and try to insert! + if ('selection' in this.oFrameDocument && this.oFrameDocument.selection.type != 'Text' && this.oFrameDocument.selection.type != 'None' && this.oFrameDocument.selection.clear) + this.oFrameDocument.selection.clear(); + + var oRange = this.getRange(); + + if (oRange.pasteHTML) + { + oRange.pasteHTML(sText); + + // Do we want to move the cursor back at all? + if (iMoveCursorBack) + oRange.moveEnd('character', -iMoveCursorBack); + + oRange.select(); + } + else + { + // If the cursor needs to be positioned, insert the last fragment first. + if (typeof(iMoveCursorBack) != 'undefined' && iMoveCursorBack > 0 && sText.length > iMoveCursorBack) + { + var oSelection = this.getSelect(false, false); + var oRange = oSelection.getRangeAt(0); + oRange.insertNode(this.oFrameDocument.createTextNode(sText.substr(sText.length - iMoveCursorBack))); + } + + this.smf_execCommand('inserthtml', false, typeof(iMoveCursorBack) == 'undefined' ? sText : sText.substr(0, sText.length - iMoveCursorBack)); + } + } + else + { + replaceText(sText, this.oTextHandle); + } + } +} + + +// Special handler for WYSIWYG. +smc_Editor.prototype.smf_execCommand = function(sCommand, bUi, sValue) +{ + return this.oFrameDocument.execCommand(sCommand, bUi, sValue); +} + +smc_Editor.prototype.insertSmiley = function(oSmileyProperties) +{ + // In text mode we just add it in as we always did. + if (!this.bRichTextEnabled) + this.insertText(' ' + oSmileyProperties.sCode); + + // Otherwise we need to do a whole image... + else + { + var iUniqueSmileyId = 1000 + Math.floor(Math.random() * 100000); + this.insertText(''); + } +} + +smc_Editor.prototype.handleButtonClick = function (oButtonProperties) +{ + this.setFocus(); + + // A special SMF function? + if (oButtonProperties.sCode in this.oSmfExec) + this[this.oSmfExec[oButtonProperties.sCode]](); + + else + { + // In text this is easy... + if (!this.bRichTextEnabled) + { + // Replace? + if (!('sAfter' in oButtonProperties) || oButtonProperties.sAfter == null) + replaceText(oButtonProperties.sBefore.replace(/\\n/g, '\n'), this.oTextHandle) + + // Surround! + else + surroundText(oButtonProperties.sBefore.replace(/\\n/g, '\n'), oButtonProperties.sAfter.replace(/\\n/g, '\n'), this.oTextHandle) + } + else + { + // Is it easy? + if (oButtonProperties.sCode in this.oSimpleExec) + this.smf_execCommand(this.oSimpleExec[oButtonProperties.sCode], false, null); + + // A link? + else if (oButtonProperties.sCode == 'url' || oButtonProperties.sCode == 'email' || oButtonProperties.sCode == 'ftp') + this.insertLink(oButtonProperties.sCode); + + // Maybe an image? + else if (oButtonProperties.sCode == 'img') + this.insertImage(); + + // Everything else means doing something ourselves. + else if ('sBefore' in oButtonProperties) + this.insertCustomHTML(oButtonProperties.sBefore.replace(/\\n/g, '\n'), oButtonProperties.sAfter.replace(/\\n/g, '\n')); + + } + } + + this.updateEditorControls(); + + // Finally set the focus. + this.setFocus(); +} + +// Changing a select box? +smc_Editor.prototype.handleSelectChange = function (oSelectProperties) +{ + this.setFocus(); + + var sValue = oSelectProperties.oSelect.value; + if (sValue == '') + return true; + + // Changing font face? + if (oSelectProperties.sName == 'sel_face') + { + // Not in HTML mode? + if (!this.bRichTextEnabled) + { + sValue = sValue.replace(/"/, ''); + surroundText('[font=' + sValue + ']', '[/font]', this.oTextHandle); + oSelectProperties.oSelect.selectedIndex = 0; + } + else + { + if (is_webkit) + this.smf_execCommand('styleWithCSS', false, true); + this.smf_execCommand('fontname', false, sValue); + } + } + + // Font size? + else if (oSelectProperties.sName == 'sel_size') + { + // Are we in boring mode? + if (!this.bRichTextEnabled) + { + surroundText('[size=' + this.aFontSizes[sValue] + 'pt]', '[/size]', this.oTextHandle); + oSelectProperties.oSelect.selectedIndex = 0; + } + + else + this.smf_execCommand('fontsize', false, sValue); + } + // Or color even? + else if (oSelectProperties.sName == 'sel_color') + { + // Are we in boring mode? + if (!this.bRichTextEnabled) + { + surroundText('[color=' + sValue + ']', '[/color]', this.oTextHandle); + oSelectProperties.oSelect.selectedIndex = 0; + } + + else + this.smf_execCommand('forecolor', false, sValue); + } + + this.updateEditorControls(); + + return true; +} + +// Put in some custom HTML. +smc_Editor.prototype.insertCustomHTML = function(sLeftTag, sRightTag) +{ + var sSelection = this.getSelect(true, true); + if (sSelection.length == 0) + sSelection = ''; + + // Are we overwriting? + if (sRightTag == '') + this.insertText(sLeftTag); + // If something was selected, replace and position cursor at the end of it. + else if (sSelection.length > 0) + this.insertText(sLeftTag + sSelection + sRightTag, false, false, 0); + // Wrap the tags around the cursor position. + else + this.insertText(sLeftTag + sRightTag, false, false, sRightTag.length); + +} + +// Insert a URL link. +smc_Editor.prototype.insertLink = function(sType) +{ + if (sType == 'email') + var sPromptText = oEditorStrings['prompt_text_email']; + else if (sType == 'ftp') + var sPromptText = oEditorStrings['prompt_text_ftp']; + else + var sPromptText = oEditorStrings['prompt_text_url']; + + // IE has a nice prompt for this - others don't. + if (sType != 'email' && sType != 'ftp' && is_ie) + this.smf_execCommand('createlink', true, 'http://'); + + else + { + // Ask them where to link to. + var sText = prompt(sPromptText, sType == 'email' ? '' : (sType == 'ftp' ? 'ftp://' : 'http://')); + if (!sText) + return; + + if (sType == 'email' && sText.indexOf('mailto:') != 0) + sText = 'mailto:' + sText; + + // Check if we have text selected and if not force us to have some. + var oCurText = this.getSelect(true, true); + + if (oCurText.toString().length != 0) + { + this.smf_execCommand('unlink'); + this.smf_execCommand('createlink', false, sText); + } + else + this.insertText('' + sText + ''); + } +} + +smc_Editor.prototype.insertImage = function(sSrc) +{ + if (!sSrc) + { + sSrc = prompt(oEditorStrings['prompt_text_img'], 'http://'); + if (!sSrc || sSrc.length < 10) + return; + } + this.smf_execCommand('insertimage', false, sSrc); +} + +smc_Editor.prototype.getSelect = function(bWantText, bWantHTMLText) +{ + if (is_ie && 'selection' in this.oFrameDocument) + { + // Just want plain text? + if (bWantText && !bWantHTMLText) + return this.oFrameDocument.selection.createRange().text; + // We want the HTML flavoured variety? + else if (bWantHTMLText) + return this.oFrameDocument.selection.createRange().htmlText; + + return this.oFrameDocument.selection; + } + + // This is mainly Firefox. + if ('getSelection' in this.oFrameWindow) + { + // Plain text? + if (bWantText && !bWantHTMLText) + return this.oFrameWindow.getSelection().toString(); + + // HTML is harder - currently using: http://www.faqts.com/knowledge_base/view.phtml/aid/32427 + else if (bWantHTMLText) + { + var oSelection = this.oFrameWindow.getSelection(); + if (oSelection.rangeCount > 0) + { + var oRange = oSelection.getRangeAt(0); + var oClonedSelection = oRange.cloneContents(); + var oDiv = this.oFrameDocument.createElement('div'); + oDiv.appendChild(oClonedSelection); + return oDiv.innerHTML; + } + else + return ''; + } + + // Want the whole object then. + return this.oFrameWindow.getSelection(); + } + + // If we're here it's not good. + return this.oFrameDocument.getSelection(); +} + +smc_Editor.prototype.getRange = function() +{ + // Get the current selection. + var oSelection = this.getSelect(); + + if (!oSelection) + return null; + + if (is_ie && oSelection.createRange) + return oSelection.createRange(); + + return oSelection.rangeCount == 0 ? null : oSelection.getRangeAt(0); +} + +// Get the current element. +smc_Editor.prototype.getCurElement = function() +{ + var oRange = this.getRange(); + + if (!oRange) + return null; + + if (is_ie) + { + if (oRange.item) + return oRange.item(0); + else + return oRange.parentElement(); + } + else + { + var oElement = oRange.commonAncestorContainer; + return this.getParentElement(oElement); + } +} + +smc_Editor.prototype.getParentElement = function(oNode) +{ + if (oNode.nodeType == 1) + return oNode; + + for (var i = 0; i < 50; i++) + { + if (!oNode.parentNode) + break; + + oNode = oNode.parentNode; + if (oNode.nodeType == 1) + return oNode; + } + return null; +} + +// Remove formatting for the selected text. +smc_Editor.prototype.removeFormatting = function() +{ + // Do both at once. + if (this.bRichTextEnabled) + { + this.smf_execCommand('removeformat'); + this.smf_execCommand('unlink'); + } + // Otherwise do a crude move indeed. + else + { + // Get the current selection first. + if (this.oTextHandle.caretPos) + var sCurrentText = this.oTextHandle.caretPos.text; + + else if ('selectionStart' in this.oTextHandle) + var sCurrentText = this.oTextHandle.value.substr(this.oTextHandle.selectionStart, (this.oTextHandle.selectionEnd - this.oTextHandle.selectionStart)); + + else + return; + + // Do bits that are likely to have attributes. + sCurrentText = sCurrentText.replace(RegExp("\\[/?(url|img|iurl|ftp|email|img|color|font|size|list|bdo).*?\\]", "g"), ''); + // Then just anything that looks like BBC. + sCurrentText = sCurrentText.replace(RegExp("\\[/?[A-Za-z]+\\]", "g"), ''); + + replaceText(sCurrentText, this.oTextHandle); + } +} + +// Toggle wysiwyg/normal mode. +smc_Editor.prototype.toggleView = function(bView) +{ + if (!this.bRichTextPossible) + { + alert(oEditorStrings['wont_work']); + return false; + } + + // Overriding or alternating? + if (typeof(bView) == 'undefined') + bView = !this.bRichTextEnabled; + + this.requestParsedMessage(bView); + + return true; +} + +// Request the message in a different form. +smc_Editor.prototype.requestParsedMessage = function(bView) +{ + // Replace with a force reload. + if (!window.XMLHttpRequest) + { + alert(oEditorStrings['func_disabled']); + return; + } + + // Get the text. + var sText = this.getText(true, !bView).replace(/&#/g, "&#").php_to8bit().php_urlencode(); + + this.tmpMethod = sendXMLDocument; + this.tmpMethod(smf_prepareScriptUrl(smf_scripturl) + 'action=jseditor;view=' + (bView ? 1 : 0) + ';' + this.opt.sSessionVar + '=' + this.opt.sSessionId + ';xml', 'message=' + sText, this.onToggleDataReceived); + delete tmpMethod; +} + +smc_Editor.prototype.onToggleDataReceived = function(oXMLDoc) +{ + var sText = ''; + for (var i = 0; i < oXMLDoc.getElementsByTagName('message')[0].childNodes.length; i++) + sText += oXMLDoc.getElementsByTagName('message')[0].childNodes[i].nodeValue; + + // What is this new view we have? + this.bRichTextEnabled = oXMLDoc.getElementsByTagName('message')[0].getAttribute('view') != '0'; + + if (this.bRichTextEnabled) + { + this.oFrameHandle.style.display = ''; + if (this.showDebug) + this.oBreadHandle.style.display = ''; + this.oTextHandle.style.display = 'none'; + } + else + { + sText = sText.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'); + this.oFrameHandle.style.display = 'none'; + this.oBreadHandle.style.display = 'none'; + this.oTextHandle.style.display = ''; + } + + // First we focus. + this.setFocus(); + + this.insertText(sText, true); + + // Record the new status. + document.getElementById(this.opt.sUniqueId + '_mode').value = this.bRichTextEnabled ? '1' : '0'; + + // Rebuild the bread crumb! + this.updateEditorControls(); +} + +// Set the focus for the editing window. +smc_Editor.prototype.setFocus = function(force_both) +{ + if (!this.bRichTextEnabled) + this.oTextHandle.focus(); + else if (is_ff || is_opera) + this.oFrameHandle.focus(); + else + this.oFrameWindow.focus(); +} + +// Start up the spellchecker! +smc_Editor.prototype.spellCheckStart = function() +{ + if (!spellCheck) + return false; + + // If we're in HTML mode we need to get the non-HTML text. + if (this.bRichTextEnabled) + { + var sText = escape(this.getText(true, 1).php_to8bit()); + + this.tmpMethod = sendXMLDocument; + this.tmpMethod(smf_prepareScriptUrl(smf_scripturl) + 'action=jseditor;view=0;' + this.opt.sSessionVar + '=' + this.opt.sSessionId + ';xml', 'message=' + sText, this.onSpellCheckDataReceived); + delete tmpMethod; + } + // Otherwise start spellchecking right away. + else + spellCheck(this.sFormId, this.opt.sUniqueId); + + return true; +} + +// This contains the spellcheckable text. +smc_Editor.prototype.onSpellCheckDataReceived = function(oXMLDoc) +{ + var sText = ''; + for (var i = 0; i < oXMLDoc.getElementsByTagName('message')[0].childNodes.length; i++) + sText += oXMLDoc.getElementsByTagName('message')[0].childNodes[i].nodeValue; + + sText = sText.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'); + + this.oTextHandle.value = sText; + spellCheck(this.sFormId, this.opt.sUniqueId); +} + +// Function called when the Spellchecker is finished and ready to pass back. +smc_Editor.prototype.spellCheckEnd = function() +{ + // If HTML edit put the text back! + if (this.bRichTextEnabled) + { + var sText = escape(this.getText(true, 0).php_to8bit()); + + this.tmpMethod = sendXMLDocument; + this.tmpMethod(smf_prepareScriptUrl(smf_scripturl) + 'action=jseditor;view=1;' + this.opt.sSessionVar + '=' + this.opt.sSessionId + ';xml', 'message=' + sText, smf_editorArray[this.iArrayPosition].onSpellCheckCompleteDataReceived); + delete tmpMethod; + } + else + this.setFocus(); +} + +// The corrected text. +smc_Editor.prototype.onSpellCheckCompleteDataReceived = function(oXMLDoc) +{ + var sText = ''; + for (var i = 0; i < oXMLDoc.getElementsByTagName('message')[0].childNodes.length; i++) + sText += oXMLDoc.getElementsByTagName('message')[0].childNodes[i].nodeValue; + + this.insertText(sText, true); + this.setFocus(); +} + +smc_Editor.prototype.resizeTextArea = function(newHeight, newWidth, is_change) +{ + // Work out what the new height is. + if (is_change) + { + // We'll assume pixels but may not be. + newHeight = this._calculateNewDimension(this.oTextHandle.style.height, newHeight); + if (newWidth) + newWidth = this._calculateNewDimension(this.oTextHandle.style.width, newWidth); + } + + // Do the HTML editor - but only if it's enabled! + if (this.bRichTextPossible) + { + this.oFrameHandle.style.height = newHeight; + if (newWidth) + this.oFrameHandle.style.width = newWidth; + } + // Do the text box regardless! + this.oTextHandle.style.height = newHeight; + if (newWidth) + this.oTextHandle.style.width = newWidth; +} + +// A utility instruction to save repetition when trying to work out what to change on a height/width. +smc_Editor.prototype._calculateNewDimension = function(old_size, change_size) +{ + // We'll assume pixels but may not be. + changeReg = change_size.toString().match(/(-)?(\d+)(\D*)/); + curReg = old_size.toString().match(/(\d+)(\D*)/); + + if (!changeReg[3]) + changeReg[3] = 'px'; + + if (changeReg[1] == '-') + changeReg[2] = 0 - changeReg[2]; + + // Both the same type? + if (changeReg[3] == curReg[2]) + { + new_size = parseInt(changeReg[2]) + parseInt(curReg[1]); + if (new_size < 50) + new_size = 50; + new_size = new_size.toString() + changeReg[3]; + } + // Is the change a percentage? + else if (changeReg[3] == '%') + new_size = (parseInt(curReg[1]) + parseInt((parseInt(changeReg[2]) * parseInt(curReg[1])) / 100)).toString() + 'px'; + // Otherwise just guess! + else + new_size = (parseInt(curReg[1]) + (parseInt(changeReg[2]) / 10)).toString() + '%'; + + return new_size; +} + +// Register default keyboard shortcuts. +smc_Editor.prototype.registerDefaultShortcuts = function() +{ + if (is_ff) + { + this.registerShortcut('b', 'ctrl', 'b'); + this.registerShortcut('u', 'ctrl', 'u'); + this.registerShortcut('i', 'ctrl', 'i'); + this.registerShortcut('p', 'alt', 'preview'); + this.registerShortcut('s', 'alt', 'submit'); + } +} + +// Register a keyboard shortcut. +smc_Editor.prototype.registerShortcut = function(sLetter, sModifiers, sCodeName) +{ + if (!sCodeName) + return; + + var oNewShortcut = { + code : sCodeName, + key: sLetter.toUpperCase().charCodeAt(0), + alt : false, + ctrl : false + }; + + var aSplitModifiers = sModifiers.split(','); + for(var i = 0, n = aSplitModifiers.length; i < n; i++) + if (aSplitModifiers[i] in oNewShortcut) + oNewShortcut[aSplitModifiers[i]] = true; + + this.aKeyboardShortcuts[this.aKeyboardShortcuts.length] = oNewShortcut; +} + +// Check whether the key has triggered a shortcut? +smc_Editor.prototype.checkShortcut = function(oEvent) +{ + // To be a shortcut it needs to be one of these, duh! + if (!oEvent.altKey && !oEvent.ctrlKey) + return false; + + var sReturnCode = false; + + // Let's take a look at each of our shortcuts shall we? + for (var i = 0, n = this.aKeyboardShortcuts.length; i < n; i++) + { + // Found something? + if (oEvent.altKey == this.aKeyboardShortcuts[i].alt && oEvent.ctrlKey == this.aKeyboardShortcuts[i].ctrl && oEvent.keyCode == this.aKeyboardShortcuts[i].key) + sReturnCode = this.aKeyboardShortcuts[i].code; + } + + return sReturnCode; +} + +// The actual event check for the above! +smc_Editor.prototype.shortcutCheck = function(oEvent) +{ + var sFoundCode = this.checkShortcut(oEvent); + + // Run it and exit. + if (typeof(sFoundCode) == 'string' && sFoundCode != '') + { + var bCancelEvent = false; + if (sFoundCode == 'submit') + { + // So much to do! + var oForm = document.getElementById(this.sFormId); + submitThisOnce(oForm); + submitonce(oForm); + smc_saveEntities(oForm.name, ['subject', this.opt.sUniqueId, 'guestname', 'evtitle', 'question']); + oForm.submit(); + + bCancelEvent = true; + } + else if (sFoundCode == 'preview') + { + previewPost(); + bCancelEvent = true; + } + else + bCancelEvent = this.opt.oBBCBox.emulateClick(sFoundCode); + + if (bCancelEvent) + { + if (is_ie && oEvent.cancelBubble) + oEvent.cancelBubble = true; + + else if (oEvent.stopPropagation) + { + oEvent.stopPropagation(); + oEvent.preventDefault(); + } + + return false; + } + } + + return true; +} + +// This is the method called after clicking the resize bar. +smc_Editor.prototype.startResize = function(oEvent) +{ + if ('event' in window) + oEvent = window.event; + + if (!oEvent || window.smf_oCurrentResizeEditor != null) + return true; + + window.smf_oCurrentResizeEditor = this.iArrayPosition; + + var aCurCoordinates = smf_mousePose(oEvent); + this.osmc_EditorCurrentResize.old_y = aCurCoordinates[1]; + this.osmc_EditorCurrentResize.old_rel_y = null; + this.osmc_EditorCurrentResize.cur_height = parseInt(this.oTextHandle.style.height); + + // Set the necessary events for resizing. + var oResizeEntity = is_ie ? document : window; + oResizeEntity.addEventListener('mousemove', this.aEventWrappers.resizeOverDocument, false); + + if (this.bRichTextPossible) + this.oFrameDocument.addEventListener('mousemove', this.aEventWrappers.resizeOverIframe, false); + + document.addEventListener('mouseup', this.aEventWrappers.endResize, true); + + if (this.bRichTextPossible) + this.oFrameDocument.addEventListener('mouseup', this.aEventWrappers.endResize, true); + + return false; +} + +// This is kind of a cheat, as it only works over the IFRAME. +smc_Editor.prototype.resizeOverIframe = function(oEvent) +{ + if ('event' in window) + oEvent = window.event; + + if (!oEvent || window.smf_oCurrentResizeEditor == null) + return true; + + var newCords = smf_mousePose(oEvent); + + if (this.osmc_EditorCurrentResize.old_rel_y == null) + this.osmc_EditorCurrentResize.old_rel_y = newCords[1]; + else + { + var iNewHeight = newCords[1] - this.osmc_EditorCurrentResize.old_rel_y + this.osmc_EditorCurrentResize.cur_height; + if (iNewHeight < 0) + this.endResize(); + else + this.resizeTextArea(iNewHeight + 'px', 0, false); + } + + return false; +} + +// This resizes an editor. +smc_Editor.prototype.resizeOverDocument = function (oEvent) +{ + if ('event' in window) + oEvent = window.event; + + if (!oEvent || window.smf_oCurrentResizeEditor == null) + return true; + + var newCords = smf_mousePose(oEvent); + + var iNewHeight = newCords[1] - this.osmc_EditorCurrentResize.old_y + this.osmc_EditorCurrentResize.cur_height; + if (iNewHeight < 0) + this.endResize(); + else + this.resizeTextArea(iNewHeight + 'px', 0, false); + + return false; +} + +smc_Editor.prototype.endResize = function (oEvent) +{ + if ('event' in window) + oEvent = window.event; + + if (window.smf_oCurrentResizeEditor == null) + return true; + + window.smf_oCurrentResizeEditor = null; + + // Remove the event... + var oResizeEntity = is_ie ? document : window; + oResizeEntity.removeEventListener('mousemove', this.aEventWrappers.resizeOverDocument, false); + + if (this.bRichTextPossible) + this.oFrameDocument.removeEventListener('mousemove', this.aEventWrappers.resizeOverIframe, false); + + document.removeEventListener('mouseup', this.aEventWrappers.endResize, true); + + if (this.bRichTextPossible) + this.oFrameDocument.removeEventListener('mouseup', this.aEventWrappers.endResize, true); + + return false; +} + +// *** smc_SmileyBox class. +function smc_SmileyBox(oOptions) +{ + this.opt = oOptions; + this.oSmileyRowsContent = {}; + this.oSmileyPopupWindow = null; + this.init(); +} + +smc_SmileyBox.prototype.init = function () +{ + // Get the HTML content of the smileys visible on the post screen. + this.getSmileyRowsContent('postform'); + + // Inject the HTML. + setInnerHTML(document.getElementById(this.opt.sContainerDiv), this.opt.sSmileyBoxTemplate.easyReplace({ + smileyRows: this.oSmileyRowsContent.postform, + moreSmileys: this.opt.oSmileyLocations.popup.length == 0 ? '' : this.opt.sMoreSmileysTemplate.easyReplace({ + moreSmileysId: this.opt.sUniqueId + '_addMoreSmileys' + }) + })); + + // Initialize the smileys. + this.initSmileys('postform', document); + + // Initialize the [more] button. + if (this.opt.oSmileyLocations.popup.length > 0) + { + var oMoreLink = document.getElementById(this.opt.sUniqueId + '_addMoreSmileys'); + oMoreLink.instanceRef = this; + oMoreLink.onclick = function () { + this.instanceRef.handleShowMoreSmileys(); + return false; + } + } +} + +// Loop through the smileys to setup the HTML. +smc_SmileyBox.prototype.getSmileyRowsContent = function (sLocation) +{ + // If it's already defined, don't bother. + if (sLocation in this.oSmileyRowsContent) + return; + + this.oSmileyRowsContent[sLocation] = ''; + + for (var iSmileyRowIndex = 0, iSmileyRowCount = this.opt.oSmileyLocations[sLocation].length; iSmileyRowIndex < iSmileyRowCount; iSmileyRowIndex++) + { + var sSmileyRowContent = ''; + for (var iSmileyIndex = 0, iSmileyCount = this.opt.oSmileyLocations[sLocation][iSmileyRowIndex].length; iSmileyIndex < iSmileyCount; iSmileyIndex++) + sSmileyRowContent += this.opt.sSmileyTemplate.easyReplace({ + smileySource: this.opt.oSmileyLocations[sLocation][iSmileyRowIndex][iSmileyIndex].sSrc.php_htmlspecialchars(), + smileyDescription: this.opt.oSmileyLocations[sLocation][iSmileyRowIndex][iSmileyIndex].sDescription.php_htmlspecialchars(), + smileyCode: this.opt.oSmileyLocations[sLocation][iSmileyRowIndex][iSmileyIndex].sCode.php_htmlspecialchars(), + smileyId: this.opt.sUniqueId + '_' + sLocation + '_' + iSmileyRowIndex.toString() + '_' + iSmileyIndex.toString() + }); + + this.oSmileyRowsContent[sLocation] += this.opt.sSmileyRowTemplate.easyReplace({ + smileyRow: sSmileyRowContent + }); + } +} + +smc_SmileyBox.prototype.initSmileys = function (sLocation, oDocument) +{ + for (var iSmileyRowIndex = 0, iSmileyRowCount = this.opt.oSmileyLocations[sLocation].length; iSmileyRowIndex < iSmileyRowCount; iSmileyRowIndex++) + { + for (var iSmileyIndex = 0, iSmileyCount = this.opt.oSmileyLocations[sLocation][iSmileyRowIndex].length; iSmileyIndex < iSmileyCount; iSmileyIndex++) + { + var oSmiley = oDocument.getElementById(this.opt.sUniqueId + '_' + sLocation + '_' + iSmileyRowIndex.toString() + '_' + iSmileyIndex.toString()); + oSmiley.instanceRef = this; + oSmiley.style.cursor = 'pointer'; + oSmiley.onclick = function () { + this.instanceRef.clickHandler(this); + return false; + } + } + } +} + +smc_SmileyBox.prototype.clickHandler = function (oSmileyImg) +{ + // Dissect the id... + var aMatches = oSmileyImg.id.match(/([^_]+)_(\d+)_(\d+)$/); + if (aMatches.length != 4) + return false; + + // ...to determine its exact smiley properties. + var sLocation = aMatches[1]; + var iSmileyRowIndex = aMatches[2]; + var iSmileyIndex = aMatches[3]; + var oProperties = this.opt.oSmileyLocations[sLocation][iSmileyRowIndex][iSmileyIndex]; + + if ('sClickHandler' in this.opt) + eval(this.opt.sClickHandler + '(oProperties)'); + + return false; +} + +smc_SmileyBox.prototype.handleShowMoreSmileys = function () +{ + // Focus the window if it's already opened. + if (this.oSmileyPopupWindow != null && 'closed' in this.oSmileyPopupWindow && !this.oSmileyPopupWindow.closed) + { + this.oSmileyPopupWindow.focus(); + return; + } + + // Get the smiley HTML. + this.getSmileyRowsContent('popup'); + + // Open the popup. + this.oSmileyPopupWindow = window.open('about:blank', this.opt.sUniqueId + '_addMoreSmileysPopup', 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,width=480,height=220,resizable=yes'); + + // Paste the template in the popup. + this.oSmileyPopupWindow.document.open('text/html', 'replace'); + this.oSmileyPopupWindow.document.write(this.opt.sMoreSmileysPopupTemplate.easyReplace({ + smileyRows: this.oSmileyRowsContent.popup, + moreSmileysCloseLinkId: this.opt.sUniqueId + '_closeMoreSmileys' + })); + this.oSmileyPopupWindow.document.close(); + + // Initialize the smileys that are in the popup window. + this.initSmileys('popup', this.oSmileyPopupWindow.document); + + // Add a function to the close window button. + var aCloseLink = this.oSmileyPopupWindow.document.getElementById(this.opt.sUniqueId + '_closeMoreSmileys'); + aCloseLink.instanceRef = this; + aCloseLink.onclick = function () { + this.instanceRef.oSmileyPopupWindow.close(); + return false; + } +} + + +// *** smc_BBCButtonBox class. +function smc_BBCButtonBox(oOptions) +{ + this.opt = oOptions; + this.init(); + + var items = ['sActiveButtonBackgroundImageHover', 'sActiveButtonBackgroundImage', 'sButtonBackgroundImageHover', 'sButtonBackgroundImage']; + for (var i = 0; i < items.length; i++) + { + if (items[i] in this.opt) + this.opt[items[i]] = this.opt[items[i]].replace(' ', '%20'); + } +} + +smc_BBCButtonBox.prototype.init = function () +{ + var sBbcContent = ''; + for (var iButtonRowIndex = 0, iRowCount = this.opt.aButtonRows.length; iButtonRowIndex < iRowCount; iButtonRowIndex++) + { + var sRowContent = ''; + var bPreviousWasDivider = false; + for (var iButtonIndex = 0, iButtonCount = this.opt.aButtonRows[iButtonRowIndex].length; iButtonIndex < iButtonCount; iButtonIndex++) + { + var oCurButton = this.opt.aButtonRows[iButtonRowIndex][iButtonIndex]; + switch (oCurButton.sType) + { + case 'button': + if (oCurButton.bEnabled) + { + sRowContent += this.opt.sButtonTemplate.easyReplace({ + buttonId: this.opt.sUniqueId.php_htmlspecialchars() + '_button_' + iButtonRowIndex.toString() + '_' + iButtonIndex.toString(), + buttonSrc: oCurButton.sImage.php_htmlspecialchars(), + buttonDescription: oCurButton.sDescription.php_htmlspecialchars() + }); + + bPreviousWasDivider = false; + } + break; + + case 'divider': + if (!bPreviousWasDivider) + sRowContent += this.opt.sDividerTemplate; + + bPreviousWasDivider = true; + break; + + case 'select': + var sOptions = ''; + + // Fighting javascript's idea of order in a for loop... :P + if ('' in oCurButton.oOptions) + sOptions = ''; + for (var sSelectValue in oCurButton.oOptions) + // we've been through this before + if (sSelectValue != '') + sOptions += ''; + + sRowContent += this.opt.sSelectTemplate.easyReplace({ + selectName: oCurButton.sName, + selectId: this.opt.sUniqueId.php_htmlspecialchars() + '_select_' + iButtonRowIndex.toString() + '_' + iButtonIndex.toString(), + selectOptions: sOptions + }); + + bPreviousWasDivider = false; + break; + } + } + sBbcContent += this.opt.sButtonRowTemplate.easyReplace({ + buttonRow: sRowContent + }); + } + + var oBbcContainer = document.getElementById(this.opt.sContainerDiv); + setInnerHTML(oBbcContainer, sBbcContent); + + for (var iButtonRowIndex = 0, iRowCount = this.opt.aButtonRows.length; iButtonRowIndex < iRowCount; iButtonRowIndex++) + { + for (var iButtonIndex = 0, iButtonCount = this.opt.aButtonRows[iButtonRowIndex].length; iButtonIndex < iButtonCount; iButtonIndex++) + { + var oCurControl = this.opt.aButtonRows[iButtonRowIndex][iButtonIndex]; + switch (oCurControl.sType) + { + case 'button': + if (!oCurControl.bEnabled) + break; + + oCurControl.oImg = document.getElementById(this.opt.sUniqueId.php_htmlspecialchars() + '_button_' + iButtonRowIndex.toString() + '_' + iButtonIndex.toString()); + oCurControl.oImg.style.cursor = 'pointer'; + if ('sButtonBackgroundImage' in this.opt) + oCurControl.oImg.style.backgroundImage = 'url(' + this.opt.sButtonBackgroundImage + ')'; + + oCurControl.oImg.instanceRef = this; + oCurControl.oImg.onmouseover = function () { + this.instanceRef.handleButtonMouseOver(this); + }; + oCurControl.oImg.onmouseout = function () { + this.instanceRef.handleButtonMouseOut(this); + }; + oCurControl.oImg.onclick = function () { + this.instanceRef.handleButtonClick(this); + }; + + oCurControl.oImg.bIsActive = false; + oCurControl.oImg.bHover = false; + break; + + case 'select': + oCurControl.oSelect = document.getElementById(this.opt.sUniqueId.php_htmlspecialchars() + '_select_' + iButtonRowIndex.toString() + '_' + iButtonIndex.toString()); + + oCurControl.oSelect.instanceRef = this; + oCurControl.oSelect.onchange = oCurControl.onchange = function () { + this.instanceRef.handleSelectChange(this); + } + break; + } + } + } +} + +smc_BBCButtonBox.prototype.handleButtonMouseOver = function (oButtonImg) +{ + oButtonImg.bHover = true; + this.updateButtonStatus(oButtonImg); +} + +smc_BBCButtonBox.prototype.handleButtonMouseOut = function (oButtonImg) +{ + oButtonImg.bHover = false; + this.updateButtonStatus(oButtonImg); +} + +smc_BBCButtonBox.prototype.updateButtonStatus = function (oButtonImg) +{ + var sNewURL = ''; + if (oButtonImg.bHover && oButtonImg.bIsActive && 'sActiveButtonBackgroundImageHover' in this.opt) + sNewURL = 'url(' + this.opt.sActiveButtonBackgroundImageHover + ')'; + else if (!oButtonImg.bHover && oButtonImg.bIsActive && 'sActiveButtonBackgroundImage' in this.opt) + sNewURL = 'url(' + this.opt.sActiveButtonBackgroundImage + ')'; + else if (oButtonImg.bHover && 'sButtonBackgroundImageHover' in this.opt) + sNewURL = 'url(' + this.opt.sButtonBackgroundImageHover + ')'; + else if ('sButtonBackgroundImage' in this.opt) + sNewURL = 'url(' + this.opt.sButtonBackgroundImage + ')'; + + if (oButtonImg.style.backgroundImage != sNewURL) + oButtonImg.style.backgroundImage = sNewURL; +} + +smc_BBCButtonBox.prototype.handleButtonClick = function (oButtonImg) +{ + // Dissect the id attribute... + var aMatches = oButtonImg.id.match(/(\d+)_(\d+)$/); + if (aMatches.length != 3) + return false; + + // ...so that we can point to the exact button. + var iButtonRowIndex = aMatches[1]; + var iButtonIndex = aMatches[2]; + var oProperties = this.opt.aButtonRows[iButtonRowIndex][iButtonIndex]; + oProperties.bIsActive = oButtonImg.bIsActive; + + if ('sButtonClickHandler' in this.opt) + eval(this.opt.sButtonClickHandler + '(oProperties)'); + + return false; +} + +smc_BBCButtonBox.prototype.handleSelectChange = function (oSelectControl) +{ + // Dissect the id attribute... + var aMatches = oSelectControl.id.match(/(\d+)_(\d+)$/); + if (aMatches.length != 3) + return false; + + // ...so that we can point to the exact button. + var iButtonRowIndex = aMatches[1]; + var iButtonIndex = aMatches[2]; + var oProperties = this.opt.aButtonRows[iButtonRowIndex][iButtonIndex]; + + if ('sSelectChangeHandler' in this.opt) + eval(this.opt.sSelectChangeHandler + '(oProperties)'); + + return true; +} + +smc_BBCButtonBox.prototype.setActive = function (aButtons) +{ + for (var iButtonRowIndex = 0, iRowCount = this.opt.aButtonRows.length; iButtonRowIndex < iRowCount; iButtonRowIndex++) + { + for (var iButtonIndex = 0, iButtonCount = this.opt.aButtonRows[iButtonRowIndex].length; iButtonIndex < iButtonCount; iButtonIndex++) + { + var oCurControl = this.opt.aButtonRows[iButtonRowIndex][iButtonIndex]; + if (oCurControl.sType == 'button' && oCurControl.bEnabled) + { + oCurControl.oImg.bIsActive = in_array(oCurControl.sCode, aButtons); + this.updateButtonStatus(oCurControl.oImg); + } + } + } +} + +smc_BBCButtonBox.prototype.emulateClick = function (sCode) +{ + for (var iButtonRowIndex = 0, iRowCount = this.opt.aButtonRows.length; iButtonRowIndex < iRowCount; iButtonRowIndex++) + { + for (var iButtonIndex = 0, iButtonCount = this.opt.aButtonRows[iButtonRowIndex].length; iButtonIndex < iButtonCount; iButtonIndex++) + { + var oCurControl = this.opt.aButtonRows[iButtonRowIndex][iButtonIndex]; + if (oCurControl.sType == 'button' && oCurControl.sCode == sCode) + { + eval(this.opt.sButtonClickHandler + '(oCurControl)'); + return true; + } + } + } + return false; +} + +smc_BBCButtonBox.prototype.setSelect = function (sSelectName, sValue) +{ + if (!('sButtonClickHandler' in this.opt)) + return; + + for (var iButtonRowIndex = 0, iRowCount = this.opt.aButtonRows.length; iButtonRowIndex < iRowCount; iButtonRowIndex++) + { + for (var iButtonIndex = 0, iButtonCount = this.opt.aButtonRows[iButtonRowIndex].length; iButtonIndex < iButtonCount; iButtonIndex++) + { + var oCurControl = this.opt.aButtonRows[iButtonRowIndex][iButtonIndex]; + if (oCurControl.sType == 'select' && oCurControl.sName == sSelectName) + oCurControl.oSelect.value = sValue; + } + } +} diff --git a/Themes/default/scripts/fader.js b/Themes/default/scripts/fader.js new file mode 100644 index 0000000..bad2c01 --- /dev/null +++ b/Themes/default/scripts/fader.js @@ -0,0 +1,217 @@ +function smf_NewsFader(oOptions) +{ + this.opt = oOptions; + + this.oFaderHandle = document.getElementById(this.opt.sFaderControlId); + + // Fade from... what text color? Default to black. + this.oFadeFrom = 'oFadeFrom' in this.opt ? this.opt.oFadeFrom : { + r: 0, + g: 0, + b: 0 + }; + + // To which background color? Default to white. + this.oFadeTo = 'oFadeTo' in this.opt ? this.opt.oFadeTo : { + r: 255, + g: 255, + b: 255 + }; + + // Surround each item with... anything special? + this.sItemTemplate = 'sItemTemplate' in this.opt ? this.opt.sItemTemplate : '%1$s'; + + // Fade delay (in milliseconds). + this.iFadeDelay = 'iFadeDelay' in this.opt ? this.opt.iFadeDelay : 5000; + + // The array that contains all the lines of the news for display. + this.aFaderItems = 'aFaderItems' in this.opt ? this.opt.aFaderItems : []; + + // Should we look for fader data, still? + this.bReceivedItemsOnConstruction = 'aFaderItems' in this.opt; + + // The current item in smfFadeContent. + this.iFadeIndex = -1; + + // Percent of fade (-64 to 510). + this.iFadePercent = 510 + + // Direction (in or out). + this.bFadeSwitch = false; + + // Just make sure the page is loaded before calling the init. + setTimeout(this.opt.sSelf + '.init();', 1); +} + +smf_NewsFader.prototype.init = function init() +{ + var oForeEl, oForeColor, oBackEl, oBackColor; + + // Try to find the fore- and background colors. + if ('currentStyle' in this.oFaderHandle) + { + oForeColor = this.oFaderHandle.currentStyle.color.match(/#([\da-f][\da-f])([\da-f][\da-f])([\da-f][\da-f])/); + this.oFadeFrom = { + r: parseInt(oForeColor[1]), + g: parseInt(oForeColor[2]), + b: parseInt(oForeColor[3]) + }; + + oBackEl = this.oFaderHandle; + while (oBackEl.currentStyle.backgroundColor == 'transparent' && 'parentNode' in oBackEl) + oBackEl = oBackEl.parentNode; + + oBackColor = oBackEl.currentStyle.backgroundColor.match(/#([\da-f][\da-f])([\da-f][\da-f])([\da-f][\da-f])/); + this.oFadeTo = { + r: eval('0x' + oBackColor[1]), + g: eval('0x' + oBackColor[2]), + b: eval('0x' + oBackColor[3]) + }; + } + else if (!('opera' in window) && 'defaultView' in document) + { + oForeEl = this.oFaderHandle; + while (document.defaultView.getComputedStyle(oForeEl, null).getPropertyCSSValue('color') == null && 'parentNode' in oForeEl && 'tagName' in oForeEl.parentNode) + oForeEl = oForeEl.parentNode; + + oForeColor = document.defaultView.getComputedStyle(oForeEl, null).getPropertyValue('color').match(/rgb\((\d+), (\d+), (\d+)\)/); + this.oFadeFrom = { + r: parseInt(oForeColor[1]), + g: parseInt(oForeColor[2]), + b: parseInt(oForeColor[3]) + }; + + oBackEl = this.oFaderHandle; + while (document.defaultView.getComputedStyle(oBackEl, null).getPropertyCSSValue('background-color') == null && 'parentNode' in oBackEl && 'tagName' in oBackEl.parentNode) + oBackEl = oBackEl.parentNode; + + oBackColor = document.defaultView.getComputedStyle(oBackEl, null).getPropertyValue('background-color'); + this.oFadeTo = { + r: parseInt(oBackColor[1]), + g: parseInt(oBackColor[2]), + b: parseInt(oBackColor[3]) + }; + } + + // Did we get our fader items on construction, or should we be gathering them instead? + if (!this.bReceivedItemsOnConstruction) + { + // Get the news from the list in boardindex + var oNewsItems = this.oFaderHandle.getElementsByTagName('li'); + + // Fill the array that has previously been created + for (var i = 0, n = oNewsItems.length; i < n; i ++) + this.aFaderItems[i] = oNewsItems[i].innerHTML; + } + + // The ranges to fade from for R, G, and B. (how far apart they are.) + this.oFadeRange = { + 'r': this.oFadeFrom.r - this.oFadeTo.r, + 'g': this.oFadeFrom.g - this.oFadeTo.g, + 'b': this.oFadeFrom.b - this.oFadeTo.b + }; + + // Divide by 20 because we are doing it 20 times per one ms. + this.iFadeDelay /= 20; + + // Start the fader! + window.setTimeout(this.opt.sSelf + '.fade();', 20); +} + +// Main fading function... called 50 times every second. +smf_NewsFader.prototype.fade = function fade() +{ + if (this.aFaderItems.length <= 1) + return; + + // A fix for Internet Explorer 4: wait until the document is loaded so we can use setInnerHTML(). + if ('readyState' in document && document.readyState != 'complete') + { + window.setTimeout(this.opt.sSelf + '.fade();', 20); + return; + } + + // Starting out? Set up the first item. + if (this.iFadeIndex == -1) + { + setInnerHTML(this.oFaderHandle, this.sItemTemplate.replace('%1$s', this.aFaderItems[0])); + this.iFadeIndex = 1; + + // In Mozilla, text jumps around from this when 1 or 0.5, etc... + if ('MozOpacity' in this.oFaderHandle.style) + this.oFaderHandle.style.MozOpacity = '0.90'; + else if ('opacity' in this.oFaderHandle.style) + this.oFaderHandle.style.opacity = '0.90'; + // In Internet Explorer, we have to define this to use it. + else if ('filter' in this.oFaderHandle.style) + this.oFaderHandle.style.filter = 'alpha(opacity=100)'; + } + + // Are we already done fading in? If so, fade out. + if (this.iFadePercent >= 510) + this.bFadeSwitch = !this.bFadeSwitch; + + // All the way faded out? + else if (this.iFadePercent <= -64) + { + this.bFadeSwitch = !this.bFadeSwitch; + + // Go to the next item, or first if we're out of items. + setInnerHTML(this.oFaderHandle, this.sItemTemplate.replace('%1$s', this.aFaderItems[this.iFadeIndex ++])); + if (this.iFadeIndex >= this.aFaderItems.length) + this.iFadeIndex = 0; + } + + // Increment or decrement the fade percentage. + if (this.bFadeSwitch) + this.iFadePercent -= 255 / this.iFadeDelay * 2; + else + this.iFadePercent += 255 / this.iFadeDelay * 2; + + // If it's not outside 0 and 256... (otherwise it's just delay time.) + if (this.iFadePercent < 256 && this.iFadePercent > 0) + { + // Easier... also faster... + var tempPercent = this.iFadePercent / 255, rounded; + + if ('MozOpacity' in this.oFaderHandle.style) + { + rounded = Math.round(tempPercent * 100) / 100; + this.oFaderHandle.style.MozOpacity = rounded == 1 ? '0.99' : rounded; + } + else if ('opacity' in this.oFaderHandle.style) + { + rounded = Math.round(tempPercent * 100) / 100; + this.oFaderHandle.style.opacity = rounded == 1 ? '0.99' : rounded; + } + else + { + var done = false; + if ('alpha' in this.oFaderHandle.filters) + { + try + { + this.oFaderHandle.filters.alpha.opacity = Math.round(tempPercent * 100); + done = true; + } + catch (err) + { + } + } + + if (!done) + { + // Get the new R, G, and B. (it should be bottom + (range of color * percent)...) + var r = Math.ceil(this.oFadeTo.r + this.oFadeRange.r * tempPercent); + var g = Math.ceil(this.oFadeTo.g + this.oFadeRange.g * tempPercent); + var b = Math.ceil(this.oFadeTo.b + this.oFadeRange.b * tempPercent); + + // Set the color in the style, thereby fading it. + this.oFaderHandle.style.color = 'rgb(' + r + ', ' + g + ', ' + b + ')'; + } + } + } + + // Keep going. + window.setTimeout(this.opt.sSelf + '.fade();', 20); +} \ No newline at end of file diff --git a/Themes/default/scripts/index.php b/Themes/default/scripts/index.php new file mode 100644 index 0000000..69278ce --- /dev/null +++ b/Themes/default/scripts/index.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Themes/default/scripts/profile.js b/Themes/default/scripts/profile.js new file mode 100644 index 0000000..f240fdc --- /dev/null +++ b/Themes/default/scripts/profile.js @@ -0,0 +1,40 @@ +var localTime = new Date(); +function autoDetectTimeOffset(currentTime) +{ + if (typeof(currentTime) != 'string') + var serverTime = currentTime; + else + var serverTime = new Date(currentTime); + + // Something wrong? + if (!localTime.getTime() || !serverTime.getTime()) + return 0; + + // Get the difference between the two, set it up so that the sign will tell us who is ahead of who. + var diff = Math.round((localTime.getTime() - serverTime.getTime())/3600000); + + // Make sure we are limiting this to one day's difference. + diff %= 24; + + return diff; +} + +// Prevent Chrome from auto completing fields when viewing/editing other members profiles +function disableAutoComplete() +{ + if (is_chrome && document.addEventListener) + document.addEventListener("DOMContentLoaded", disableAutoCompleteNow, false); +} + +// Once DOMContentLoaded is triggered, call the function +function disableAutoCompleteNow() +{ + for (var i = 0, n = document.forms.length; i < n; i++) + { + var die = document.forms[i].elements; + for (var j = 0, m = die.length; j < m; j++) + // Only bother with text/password fields? + if (die[j].type == "text" || die[j].type == "password") + die[j].setAttribute("autocomplete", "off"); + } +} \ No newline at end of file diff --git a/Themes/default/scripts/register.js b/Themes/default/scripts/register.js new file mode 100644 index 0000000..1a3b9fb --- /dev/null +++ b/Themes/default/scripts/register.js @@ -0,0 +1,266 @@ +function smfRegister(formID, passwordDifficultyLevel, regTextStrings) +{ + this.addVerify = addVerificationField; + this.autoSetup = autoSetup; + this.refreshMainPassword = refreshMainPassword; + this.refreshVerifyPassword = refreshVerifyPassword; + + var verificationFields = new Array(); + var verificationFieldLength = 0; + var textStrings = regTextStrings ? regTextStrings : new Array(); + var passwordLevel = passwordDifficultyLevel ? passwordDifficultyLevel : 0; + + // Setup all the fields! + autoSetup(formID); + + // This is a field which requires some form of verification check. + function addVerificationField(fieldType, fieldID) + { + // Check the field exists. + if (!document.getElementById(fieldID)) + return; + + // Get the handles. + var inputHandle = document.getElementById(fieldID); + var imageHandle = document.getElementById(fieldID + '_img') ? document.getElementById(fieldID + '_img') : false; + var divHandle = document.getElementById(fieldID + '_div') ? document.getElementById(fieldID + '_div') : false; + + // What is the event handler? + var eventHandler = false; + if (fieldType == 'pwmain') + eventHandler = refreshMainPassword; + else if (fieldType == 'pwverify') + eventHandler = refreshVerifyPassword; + else if (fieldType == 'username') + eventHandler = refreshUsername; + else if (fieldType == 'reserved') + eventHandler = refreshMainPassword; + + // Store this field. + var vFieldIndex = fieldType == 'reserved' ? fieldType + verificationFieldLength : fieldType; + verificationFields[vFieldIndex] = Array(6); + verificationFields[vFieldIndex][0] = fieldID; + verificationFields[vFieldIndex][1] = inputHandle; + verificationFields[vFieldIndex][2] = imageHandle; + verificationFields[vFieldIndex][3] = divHandle; + verificationFields[vFieldIndex][4] = fieldType; + verificationFields[vFieldIndex][5] = inputHandle.className; + + // Keep a count to it! + verificationFieldLength++; + + // Step to it! + if (eventHandler) + { + createEventListener(inputHandle); + inputHandle.addEventListener('keyup', eventHandler, false); + eventHandler(); + + // Username will auto check on blur! + inputHandle.addEventListener('blur', autoCheckUsername, false); + } + + // Make the div visible! + if (divHandle) + divHandle.style.display = ''; + } + + // A button to trigger a username search? + function addUsernameSearchTrigger(elementID) + { + var buttonHandle = document.getElementById(elementID); + + // Attach the event to this element. + createEventListener(buttonHandle); + buttonHandle.addEventListener('click', checkUsername, false); + } + + // This function will automatically pick up all the necessary verification fields and initialise their visual status. + function autoSetup(formID) + { + if (!document.getElementById(formID)) + return false; + + var curElement, curType; + for (var i = 0, n = document.getElementById(formID).elements.length; i < n; i++) + { + curElement = document.getElementById(formID).elements[i]; + + // Does the ID contain the keyword 'autov'? + if (curElement.id.indexOf('autov') != -1 && (curElement.type == 'text' || curElement.type == 'password')) + { + // This is probably it - but does it contain a field type? + curType = 0; + // Username can only be done with XML. + if (curElement.id.indexOf('username') != -1 && window.XMLHttpRequest) + curType = 'username'; + else if (curElement.id.indexOf('pwmain') != -1) + curType = 'pwmain'; + else if (curElement.id.indexOf('pwverify') != -1) + curType = 'pwverify'; + // This means this field is reserved and cannot be contained in the password! + else if (curElement.id.indexOf('reserve') != -1) + curType = 'reserved'; + + // If we're happy let's add this element! + if (curType) + addVerificationField(curType, curElement.id); + + // If this is the username do we also have a button to find the user? + if (curType == 'username' && document.getElementById(curElement.id + '_link')) + { + addUsernameSearchTrigger(curElement.id + '_link'); + } + } + } + + return true; + } + + // What is the password state? + function refreshMainPassword(called_from_verify) + { + if (!verificationFields['pwmain']) + return false; + + var curPass = verificationFields['pwmain'][1].value; + var stringIndex = ''; + + // Is it a valid length? + if ((curPass.length < 8 && passwordLevel >= 1) || curPass.length < 4) + stringIndex = 'password_short'; + + // More than basic? + if (passwordLevel >= 1) + { + // If there is a username check it's not in the password! + if (verificationFields['username'] && verificationFields['username'][1].value && curPass.indexOf(verificationFields['username'][1].value) != -1) + stringIndex = 'password_reserved'; + + // Any reserved fields? + for (var i in verificationFields) + { + if (verificationFields[i][4] == 'reserved' && verificationFields[i][1].value && curPass.indexOf(verificationFields[i][1].value) != -1) + stringIndex = 'password_reserved'; + } + + // Finally - is it hard and as such requiring mixed cases and numbers? + if (passwordLevel > 1) + { + if (curPass == curPass.toLowerCase()) + stringIndex = 'password_numbercase'; + if (!curPass.match(/(\D\d|\d\D)/)) + stringIndex = 'password_numbercase'; + } + } + + var isValid = stringIndex == '' ? true : false; + if (stringIndex == '') + stringIndex = 'password_valid'; + + // Set the image. + setVerificationImage(verificationFields['pwmain'][2], isValid, textStrings[stringIndex] ? textStrings[stringIndex] : ''); + verificationFields['pwmain'][1].className = verificationFields['pwmain'][5] + ' ' + (isValid ? 'valid_input' : 'invalid_input'); + + // As this has changed the verification one may have too! + if (verificationFields['pwverify'] && !called_from_verify) + refreshVerifyPassword(); + + return isValid; + } + + // Check that the verification password matches the main one! + function refreshVerifyPassword() + { + // Can't do anything without something to check again! + if (!verificationFields['pwmain']) + return false; + + // Check and set valid status! + var isValid = verificationFields['pwmain'][1].value == verificationFields['pwverify'][1].value && refreshMainPassword(true); + var alt = textStrings[isValid == 1 ? 'password_valid' : 'password_no_match'] ? textStrings[isValid == 1 ? 'password_valid' : 'password_no_match'] : ''; + setVerificationImage(verificationFields['pwverify'][2], isValid, alt); + verificationFields['pwverify'][1].className = verificationFields['pwverify'][5] + ' ' + (isValid ? 'valid_input' : 'invalid_input'); + + return true; + } + + // If the username is changed just revert the status of whether it's valid! + function refreshUsername() + { + if (!verificationFields['username']) + return false; + + // Restore the class name. + if (verificationFields['username'][1].className) + verificationFields['username'][1].className = verificationFields['username'][5]; + // Check the image is correct. + var alt = textStrings['username_check'] ? textStrings['username_check'] : ''; + setVerificationImage(verificationFields['username'][2], 'check', alt); + + // Check the password is still OK. + refreshMainPassword(); + + return true; + } + + // This is a pass through function that ensures we don't do any of the AJAX notification stuff. + function autoCheckUsername() + { + checkUsername(true); + } + + // Check whether the username exists? + function checkUsername(is_auto) + { + if (!verificationFields['username']) + return false; + + // Get the username and do nothing without one! + var curUsername = verificationFields['username'][1].value; + if (!curUsername) + return false; + + if (!is_auto) + ajax_indicator(true); + + // Request a search on that username. + checkName = curUsername.php_to8bit().php_urlencode(); + getXMLDocument(smf_prepareScriptUrl(smf_scripturl) + 'action=register;sa=usernamecheck;xml;username=' + checkName, checkUsernameCallback); + + return true; + } + + // Callback for getting the username data. + function checkUsernameCallback(XMLDoc) + { + if (XMLDoc.getElementsByTagName("username")) + isValid = XMLDoc.getElementsByTagName("username")[0].getAttribute("valid"); + else + isValid = true; + + // What to alt? + var alt = textStrings[isValid == 1 ? 'username_valid' : 'username_invalid'] ? textStrings[isValid == 1 ? 'username_valid' : 'username_invalid'] : ''; + + verificationFields['username'][1].className = verificationFields['username'][5] + ' ' + (isValid == 1 ? 'valid_input' : 'invalid_input'); + setVerificationImage(verificationFields['username'][2], isValid == 1, alt); + + ajax_indicator(false); + } + + // Set the image to be the correct type. + function setVerificationImage(imageHandle, imageIcon, alt) + { + if (!imageHandle) + return false; + if (!alt) + alt = '*'; + + var curImage = imageIcon ? (imageIcon == 'check' ? 'field_check.gif' : 'field_valid.gif') : 'field_invalid.gif'; + imageHandle.src = smf_images_url + '/icons/' + curImage; + imageHandle.alt = alt; + imageHandle.title = alt; + + return true; + } +} \ No newline at end of file diff --git a/Themes/default/scripts/script.js b/Themes/default/scripts/script.js new file mode 100644 index 0000000..e301940 --- /dev/null +++ b/Themes/default/scripts/script.js @@ -0,0 +1,1412 @@ +var smf_formSubmitted = false; +var lastKeepAliveCheck = new Date().getTime(); +var smf_editorArray = new Array(); + +// Some very basic browser detection - from Mozilla's sniffer page. +var ua = navigator.userAgent.toLowerCase(); + +var is_opera = ua.indexOf('opera') != -1; +var is_opera5 = ua.indexOf('opera/5') != -1 || ua.indexOf('opera 5') != -1; +var is_opera6 = ua.indexOf('opera/6') != -1 || ua.indexOf('opera 6') != -1; +var is_opera7 = ua.indexOf('opera/7') != -1 || ua.indexOf('opera 7') != -1; +var is_opera8 = ua.indexOf('opera/8') != -1 || ua.indexOf('opera 8') != -1; +var is_opera9 = ua.indexOf('opera/9') != -1 || ua.indexOf('opera 9') != -1; +var is_opera95 = ua.indexOf('opera/9.5') != -1 || ua.indexOf('opera 9.5') != -1; +var is_opera96 = ua.indexOf('opera/9.6') != -1 || ua.indexOf('opera 9.6') != -1; +var is_opera10 = (ua.indexOf('opera/9.8') != -1 || ua.indexOf('opera 9.8') != -1 || ua.indexOf('opera/10.') != -1 || ua.indexOf('opera 10.') != -1) || ua.indexOf('version/10.') != -1; +var is_opera95up = is_opera95 || is_opera96 || is_opera10; + +var is_ff = (ua.indexOf('firefox') != -1 || ua.indexOf('iceweasel') != -1 || ua.indexOf('icecat') != -1 || ua.indexOf('shiretoko') != -1 || ua.indexOf('minefield') != -1) && !is_opera; +var is_gecko = ua.indexOf('gecko') != -1 && !is_opera; + +var is_chrome = ua.indexOf('chrome') != -1; +var is_safari = ua.indexOf('applewebkit') != -1 && !is_chrome; +var is_webkit = ua.indexOf('applewebkit') != -1; + +var is_ie = ua.indexOf('msie') != -1 && !is_opera; +var is_ie4 = is_ie && ua.indexOf('msie 4') != -1; +var is_ie5 = is_ie && ua.indexOf('msie 5') != -1; +var is_ie50 = is_ie && ua.indexOf('msie 5.0') != -1; +var is_ie55 = is_ie && ua.indexOf('msie 5.5') != -1; +var is_ie5up = is_ie && !is_ie4; +var is_ie6 = is_ie && ua.indexOf('msie 6') != -1; +var is_ie6up = is_ie5up && !is_ie55 && !is_ie5; +var is_ie6down = is_ie6 || is_ie5 || is_ie4; +var is_ie7 = is_ie && ua.indexOf('msie 7') != -1; +var is_ie7up = is_ie6up && !is_ie6; +var is_ie7down = is_ie7 || is_ie6 || is_ie5 || is_ie4; + +var is_ie8 = is_ie && ua.indexOf('msie 8') != -1; +var is_ie8up = is_ie8 && !is_ie7down; + +var is_iphone = ua.indexOf('iphone') != -1 || ua.indexOf('ipod') != -1; +var is_android = ua.indexOf('android') != -1; + +var ajax_indicator_ele = null; + +// Define document.getElementById for Internet Explorer 4. +if (!('getElementById' in document) && 'all' in document) + document.getElementById = function (sId) { + return document.all[sId]; + } + +// Define XMLHttpRequest for IE 5 and above. (don't bother for IE 4 :/.... works in Opera 7.6 and Safari 1.2!) +else if (!('XMLHttpRequest' in window) && 'ActiveXObject' in window) + window.XMLHttpRequest = function () { + return new ActiveXObject(is_ie5 ? 'Microsoft.XMLHTTP' : 'MSXML2.XMLHTTP'); + }; + +// Ensure the getElementsByTagName exists. +if (!'getElementsByTagName' in document && 'all' in document) + document.getElementsByTagName = function (sName) { + return document.all.tags[sName]; + } + +// Some older versions of Mozilla don't have this, for some reason. +if (!('forms' in document)) + document.forms = document.getElementsByTagName('form'); + +// Load an XML document using XMLHttpRequest. +function getXMLDocument(sUrl, funcCallback) +{ + if (!window.XMLHttpRequest) + return null; + + var oMyDoc = new XMLHttpRequest(); + var bAsync = typeof(funcCallback) != 'undefined'; + var oCaller = this; + if (bAsync) + { + oMyDoc.onreadystatechange = function () { + if (oMyDoc.readyState != 4) + return; + + if (oMyDoc.responseXML != null && oMyDoc.status == 200) + { + if (funcCallback.call) + { + funcCallback.call(oCaller, oMyDoc.responseXML); + } + // A primitive substitute for the call method to support IE 5.0. + else + { + oCaller.tmpMethod = funcCallback; + oCaller.tmpMethod(oMyDoc.responseXML); + delete oCaller.tmpMethod; + } + } + }; + } + oMyDoc.open('GET', sUrl, bAsync); + oMyDoc.send(null); + + return oMyDoc; +} + +// Send a post form to the server using XMLHttpRequest. +function sendXMLDocument(sUrl, sContent, funcCallback) +{ + if (!window.XMLHttpRequest) + return false; + + var oSendDoc = new window.XMLHttpRequest(); + var oCaller = this; + if (typeof(funcCallback) != 'undefined') + { + oSendDoc.onreadystatechange = function () { + if (oSendDoc.readyState != 4) + return; + + if (oSendDoc.responseXML != null && oSendDoc.status == 200) + funcCallback.call(oCaller, oSendDoc.responseXML); + else + funcCallback.call(oCaller, false); + }; + } + oSendDoc.open('POST', sUrl, true); + if ('setRequestHeader' in oSendDoc) + oSendDoc.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + oSendDoc.send(sContent); + + return true; +} + +// A property we'll be needing for php_to8bit. +String.prototype.oCharsetConversion = { + from: '', + to: '' +}; + +// Convert a string to an 8 bit representation (like in PHP). +String.prototype.php_to8bit = function () +{ + if (smf_charset == 'UTF-8') + { + var n, sReturn = ''; + + for (var i = 0, iTextLen = this.length; i < iTextLen; i++) + { + n = this.charCodeAt(i); + if (n < 128) + sReturn += String.fromCharCode(n) + else if (n < 2048) + sReturn += String.fromCharCode(192 | n >> 6) + String.fromCharCode(128 | n & 63); + else if (n < 65536) + sReturn += String.fromCharCode(224 | n >> 12) + String.fromCharCode(128 | n >> 6 & 63) + String.fromCharCode(128 | n & 63); + else + sReturn += String.fromCharCode(240 | n >> 18) + String.fromCharCode(128 | n >> 12 & 63) + String.fromCharCode(128 | n >> 6 & 63) + String.fromCharCode(128 | n & 63); + } + + return sReturn; + } + + else if (this.oCharsetConversion.from.length == 0) + { + switch (smf_charset) + { + case 'ISO-8859-1': + this.oCharsetConversion = { + from: '\xa0-\xff', + to: '\xa0-\xff' + }; + break; + + case 'ISO-8859-2': + this.oCharsetConversion = { + from: '\xa0\u0104\u02d8\u0141\xa4\u013d\u015a\xa7\xa8\u0160\u015e\u0164\u0179\xad\u017d\u017b\xb0\u0105\u02db\u0142\xb4\u013e\u015b\u02c7\xb8\u0161\u015f\u0165\u017a\u02dd\u017e\u017c\u0154\xc1\xc2\u0102\xc4\u0139\u0106\xc7\u010c\xc9\u0118\xcb\u011a\xcd\xce\u010e\u0110\u0143\u0147\xd3\xd4\u0150\xd6\xd7\u0158\u016e\xda\u0170\xdc\xdd\u0162\xdf\u0155\xe1\xe2\u0103\xe4\u013a\u0107\xe7\u010d\xe9\u0119\xeb\u011b\xed\xee\u010f\u0111\u0144\u0148\xf3\xf4\u0151\xf6\xf7\u0159\u016f\xfa\u0171\xfc\xfd\u0163\u02d9', + to: '\xa0-\xff' + }; + break; + + case 'ISO-8859-5': + this.oCharsetConversion = { + from: '\xa0\u0401-\u040c\xad\u040e-\u044f\u2116\u0451-\u045c\xa7\u045e\u045f', + to: '\xa0-\xff' + }; + break; + + case 'ISO-8859-9': + this.oCharsetConversion = { + from: '\xa0-\xcf\u011e\xd1-\xdc\u0130\u015e\xdf-\xef\u011f\xf1-\xfc\u0131\u015f\xff', + to: '\xa0-\xff' + }; + break; + + case 'ISO-8859-15': + this.oCharsetConversion = { + from: '\xa0-\xa3\u20ac\xa5\u0160\xa7\u0161\xa9-\xb3\u017d\xb5-\xb7\u017e\xb9-\xbb\u0152\u0153\u0178\xbf-\xff', + to: '\xa0-\xff' + }; + break; + + case 'tis-620': + this.oCharsetConversion = { + from: '\u20ac\u2026\u2018\u2019\u201c\u201d\u2022\u2013\u2014\xa0\u0e01-\u0e3a\u0e3f-\u0e5b', + to: '\x80\x85\x91-\x97\xa0-\xda\xdf-\xfb' + }; + break; + + case 'windows-1251': + this.oCharsetConversion = { + from: '\u0402\u0403\u201a\u0453\u201e\u2026\u2020\u2021\u20ac\u2030\u0409\u2039\u040a\u040c\u040b\u040f\u0452\u2018\u2019\u201c\u201d\u2022\u2013\u2014\u2122\u0459\u203a\u045a\u045c\u045b\u045f\xa0\u040e\u045e\u0408\xa4\u0490\xa6\xa7\u0401\xa9\u0404\xab-\xae\u0407\xb0\xb1\u0406\u0456\u0491\xb5-\xb7\u0451\u2116\u0454\xbb\u0458\u0405\u0455\u0457\u0410-\u044f', + to: '\x80-\x97\x99-\xff' + }; + break; + + case 'windows-1253': + this.oCharsetConversion = { + from: '\u20ac\u201a\u0192\u201e\u2026\u2020\u2021\u2030\u2039\u2018\u2019\u201c\u201d\u2022\u2013\u2014\u2122\u203a\xa0\u0385\u0386\xa3-\xa9\xab-\xae\u2015\xb0-\xb3\u0384\xb5-\xb7\u0388-\u038a\xbb\u038c\xbd\u038e-\u03a1\u03a3-\u03ce', + to: '\x80\x82-\x87\x89\x8b\x91-\x97\x99\x9b\xa0-\xa9\xab-\xd1\xd3-\xfe' + }; + break; + + case 'windows-1255': + this.oCharsetConversion = { + from: '\u20ac\u201a\u0192\u201e\u2026\u2020\u2021\u02c6\u2030\u2039\u2018\u2019\u201c\u201d\u2022\u2013\u2014\u02dc\u2122\u203a\xa0-\xa3\u20aa\xa5-\xa9\xd7\xab-\xb9\xf7\xbb-\xbf\u05b0-\u05b9\u05bb-\u05c3\u05f0-\u05f4\u05d0-\u05ea\u200e\u200f', + to: '\x80\x82-\x89\x8b\x91-\x99\x9b\xa0-\xc9\xcb-\xd8\xe0-\xfa\xfd\xfe' + }; + break; + + case 'windows-1256': + this.oCharsetConversion = { + from: '\u20ac\u067e\u201a\u0192\u201e\u2026\u2020\u2021\u02c6\u2030\u0679\u2039\u0152\u0686\u0698\u0688\u06af\u2018\u2019\u201c\u201d\u2022\u2013\u2014\u06a9\u2122\u0691\u203a\u0153\u200c\u200d\u06ba\xa0\u060c\xa2-\xa9\u06be\xab-\xb9\u061b\xbb-\xbe\u061f\u06c1\u0621-\u0636\xd7\u0637-\u063a\u0640-\u0643\xe0\u0644\xe2\u0645-\u0648\xe7-\xeb\u0649\u064a\xee\xef\u064b-\u064e\xf4\u064f\u0650\xf7\u0651\xf9\u0652\xfb\xfc\u200e\u200f\u06d2', + to: '\x80-\xff' + }; + break; + + default: + this.oCharsetConversion = { + from: '', + to: '' + }; + break; + } + var funcExpandString = function (sSearch) { + var sInsert = ''; + for (var i = sSearch.charCodeAt(0), n = sSearch.charCodeAt(2); i <= n; i++) + sInsert += String.fromCharCode(i); + return sInsert; + }; + this.oCharsetConversion.from = this.oCharsetConversion.from.replace(/.\-./g, funcExpandString); + this.oCharsetConversion.to = this.oCharsetConversion.to.replace(/.\-./g, funcExpandString); + } + + var sReturn = '', iOffsetFrom = 0; + for (var i = 0, n = this.length; i < n; i++) + { + iOffsetFrom = this.oCharsetConversion.from.indexOf(this.charAt(i)); + sReturn += iOffsetFrom > -1 ? this.oCharsetConversion.to.charAt(iOffsetFrom) : (this.charCodeAt(i) > 127 ? '&#' + this.charCodeAt(i) + ';' : this.charAt(i)); + } + + return sReturn +} + +// Character-level replacement function. +String.prototype.php_strtr = function (sFrom, sTo) +{ + return this.replace(new RegExp('[' + sFrom + ']', 'g'), function (sMatch) { + return sTo.charAt(sFrom.indexOf(sMatch)); + }); +} + +// Simulate PHP's strtolower (in SOME cases PHP uses ISO-8859-1 case folding). +String.prototype.php_strtolower = function () +{ + return typeof(smf_iso_case_folding) == 'boolean' && smf_iso_case_folding == true ? this.php_strtr( + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ\x8a\x8c\x8e\x9f\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde', + 'abcdefghijklmnopqrstuvwxyz\x9a\x9c\x9e\xff\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe' + ) : this.php_strtr('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); +} + +String.prototype.php_urlencode = function() +{ + return escape(this).replace(/\+/g, '%2b').replace('*', '%2a').replace('/', '%2f').replace('@', '%40'); +} + +String.prototype.php_htmlspecialchars = function() +{ + return this.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); +} + +String.prototype.php_unhtmlspecialchars = function() +{ + return this.replace(/"/g, '"').replace(/>/g, '>').replace(/</g, '<').replace(/&/g, '&'); +} + +String.prototype.php_addslashes = function() +{ + return this.replace(/\\/g, '\\\\').replace(/'/g, '\\\''); +} + +String.prototype._replaceEntities = function(sInput, sDummy, sNum) +{ + return String.fromCharCode(parseInt(sNum)); +} + +String.prototype.removeEntities = function() +{ + return this.replace(/&(amp;)?#(\d+);/g, this._replaceEntities); +} + +String.prototype.easyReplace = function (oReplacements) +{ + var sResult = this; + for (var sSearch in oReplacements) + sResult = sResult.replace(new RegExp('%' + sSearch + '%', 'g'), oReplacements[sSearch]); + + return sResult; +} + + +// Open a new window. +function reqWin(desktopURL, alternateWidth, alternateHeight, noScrollbars) +{ + if ((alternateWidth && self.screen.availWidth * 0.8 < alternateWidth) || (alternateHeight && self.screen.availHeight * 0.8 < alternateHeight)) + { + noScrollbars = false; + alternateWidth = Math.min(alternateWidth, self.screen.availWidth * 0.8); + alternateHeight = Math.min(alternateHeight, self.screen.availHeight * 0.8); + } + else + noScrollbars = typeof(noScrollbars) == 'boolean' && noScrollbars == true; + + window.open(desktopURL, 'requested_popup', 'toolbar=no,location=no,status=no,menubar=no,scrollbars=' + (noScrollbars ? 'no' : 'yes') + ',width=' + (alternateWidth ? alternateWidth : 480) + ',height=' + (alternateHeight ? alternateHeight : 220) + ',resizable=no'); + + // Return false so the click won't follow the link ;). + return false; +} + +// Remember the current position. +function storeCaret(oTextHandle) +{ + // Only bother if it will be useful. + if ('createTextRange' in oTextHandle) + oTextHandle.caretPos = document.selection.createRange().duplicate(); +} + +// Replaces the currently selected text with the passed text. +function replaceText(text, oTextHandle) +{ + // Attempt to create a text range (IE). + if ('caretPos' in oTextHandle && 'createTextRange' in oTextHandle) + { + var caretPos = oTextHandle.caretPos; + + caretPos.text = caretPos.text.charAt(caretPos.text.length - 1) == ' ' ? text + ' ' : text; + caretPos.select(); + } + // Mozilla text range replace. + else if ('selectionStart' in oTextHandle) + { + var begin = oTextHandle.value.substr(0, oTextHandle.selectionStart); + var end = oTextHandle.value.substr(oTextHandle.selectionEnd); + var scrollPos = oTextHandle.scrollTop; + + oTextHandle.value = begin + text + end; + + if (oTextHandle.setSelectionRange) + { + oTextHandle.focus(); + var goForward = is_opera ? text.match(/\n/g).length : 0; + oTextHandle.setSelectionRange(begin.length + text.length + goForward, begin.length + text.length + goForward); + } + oTextHandle.scrollTop = scrollPos; + } + // Just put it on the end. + else + { + oTextHandle.value += text; + oTextHandle.focus(oTextHandle.value.length - 1); + } +} + +// Surrounds the selected text with text1 and text2. +function surroundText(text1, text2, oTextHandle) +{ + // Can a text range be created? + if ('caretPos' in oTextHandle && 'createTextRange' in oTextHandle) + { + var caretPos = oTextHandle.caretPos, temp_length = caretPos.text.length; + + caretPos.text = caretPos.text.charAt(caretPos.text.length - 1) == ' ' ? text1 + caretPos.text + text2 + ' ' : text1 + caretPos.text + text2; + + if (temp_length == 0) + { + caretPos.moveStart('character', -text2.length); + caretPos.moveEnd('character', -text2.length); + caretPos.select(); + } + else + oTextHandle.focus(caretPos); + } + // Mozilla text range wrap. + else if ('selectionStart' in oTextHandle) + { + var begin = oTextHandle.value.substr(0, oTextHandle.selectionStart); + var selection = oTextHandle.value.substr(oTextHandle.selectionStart, oTextHandle.selectionEnd - oTextHandle.selectionStart); + var end = oTextHandle.value.substr(oTextHandle.selectionEnd); + var newCursorPos = oTextHandle.selectionStart; + var scrollPos = oTextHandle.scrollTop; + + oTextHandle.value = begin + text1 + selection + text2 + end; + + if (oTextHandle.setSelectionRange) + { + var goForward = is_opera ? text1.match(/\n/g).length : 0, goForwardAll = is_opera ? (text1 + text2).match(/\n/g).length : 0; + if (selection.length == 0) + oTextHandle.setSelectionRange(newCursorPos + text1.length + goForward, newCursorPos + text1.length + goForward); + else + oTextHandle.setSelectionRange(newCursorPos, newCursorPos + text1.length + selection.length + text2.length + goForwardAll); + oTextHandle.focus(); + } + oTextHandle.scrollTop = scrollPos; + } + // Just put them on the end, then. + else + { + oTextHandle.value += text1 + text2; + oTextHandle.focus(oTextHandle.value.length - 1); + } +} + +// Checks if the passed input's value is nothing. +function isEmptyText(theField) +{ + // Copy the value so changes can be made.. + var theValue = theField.value; + + // Strip whitespace off the left side. + while (theValue.length > 0 && (theValue.charAt(0) == ' ' || theValue.charAt(0) == '\t')) + theValue = theValue.substring(1, theValue.length); + // Strip whitespace off the right side. + while (theValue.length > 0 && (theValue.charAt(theValue.length - 1) == ' ' || theValue.charAt(theValue.length - 1) == '\t')) + theValue = theValue.substring(0, theValue.length - 1); + + if (theValue == '') + return true; + else + return false; +} + +// Only allow form submission ONCE. +function submitonce(theform) +{ + smf_formSubmitted = true; + + // If there are any editors warn them submit is coming! + for (var i = 0; i < smf_editorArray.length; i++) + smf_editorArray[i].doSubmit(); +} +function submitThisOnce(oControl) +{ + // Hateful, hateful fix for Safari 1.3 beta. + if (is_safari) + return !smf_formSubmitted; + + // oControl might also be a form. + var oForm = 'form' in oControl ? oControl.form : oControl; + + var aTextareas = oForm.getElementsByTagName('textarea'); + for (var i = 0, n = aTextareas.length; i < n; i++) + aTextareas[i].readOnly = true; + + return !smf_formSubmitted; +} + +// Deprecated, as innerHTML is supported everywhere. +function setInnerHTML(oElement, sToValue) +{ + oElement.innerHTML = sToValue; +} + +function getInnerHTML(oElement) +{ + return oElement.innerHTML; +} + +// Set the "outer" HTML of an element. +function setOuterHTML(oElement, sToValue) +{ + if ('outerHTML' in oElement) + oElement.outerHTML = sToValue; + else + { + var range = document.createRange(); + range.setStartBefore(oElement); + oElement.parentNode.replaceChild(range.createContextualFragment(sToValue), oElement); + } +} + +// Checks for variable in theArray. +function in_array(variable, theArray) +{ + for (var i in theArray) + if (theArray[i] == variable) + return true; + + return false; +} + +// Checks for variable in theArray. +function array_search(variable, theArray) +{ + for (var i in theArray) + if (theArray[i] == variable) + return i; + + return null; +} + +// Find a specific radio button in its group and select it. +function selectRadioByName(oRadioGroup, sName) +{ + if (!('length' in oRadioGroup)) + return oRadioGroup.checked = true; + + for (var i = 0, n = oRadioGroup.length; i < n; i++) + if (oRadioGroup[i].value == sName) + return oRadioGroup[i].checked = true; + + return false; +} + +// Invert all checkboxes at once by clicking a single checkbox. +function invertAll(oInvertCheckbox, oForm, sMask, bIgnoreDisabled) +{ + for (var i = 0; i < oForm.length; i++) + { + if (!('name' in oForm[i]) || (typeof(sMask) == 'string' && oForm[i].name.substr(0, sMask.length) != sMask && oForm[i].id.substr(0, sMask.length) != sMask)) + continue; + + if (!oForm[i].disabled || (typeof(bIgnoreDisabled) == 'boolean' && bIgnoreDisabled)) + oForm[i].checked = oInvertCheckbox.checked; + } +} + +// Keep the session alive - always! +var lastKeepAliveCheck = new Date().getTime(); +function smf_sessionKeepAlive() +{ + var curTime = new Date().getTime(); + + // Prevent a Firefox bug from hammering the server. + if (smf_scripturl && curTime - lastKeepAliveCheck > 900000) + { + var tempImage = new Image(); + tempImage.src = smf_prepareScriptUrl(smf_scripturl) + 'action=keepalive;time=' + curTime; + lastKeepAliveCheck = curTime; + } + + window.setTimeout('smf_sessionKeepAlive();', 1200000); +} +window.setTimeout('smf_sessionKeepAlive();', 1200000); + +// Set a theme option through javascript. +function smf_setThemeOption(option, value, theme, cur_session_id, cur_session_var, additional_vars) +{ + // Compatibility. + if (cur_session_id == null) + cur_session_id = smf_session_id; + if (typeof(cur_session_var) == 'undefined') + cur_session_var = 'sesc'; + + if (additional_vars == null) + additional_vars = ''; + + var tempImage = new Image(); + tempImage.src = smf_prepareScriptUrl(smf_scripturl) + 'action=jsoption;var=' + option + ';val=' + value + ';' + cur_session_var + '=' + cur_session_id + additional_vars + (theme == null ? '' : '&th=' + theme) + ';time=' + (new Date().getTime()); +} + +function smf_avatarResize() +{ + var possibleAvatars = document.getElementsByTagName('img'); + + for (var i = 0; i < possibleAvatars.length; i++) + { + var tempAvatars = []; j = 0; + if (possibleAvatars[i].className != 'avatar') + continue; + + // Image.prototype.avatar = possibleAvatars[i]; + tempAvatars[j] = new Image(); + tempAvatars[j].avatar = possibleAvatars[i]; + + tempAvatars[j].onload = function() + { + this.avatar.width = this.width; + this.avatar.height = this.height; + if (smf_avatarMaxWidth != 0 && this.width > smf_avatarMaxWidth) + { + this.avatar.height = (smf_avatarMaxWidth * this.height) / this.width; + this.avatar.width = smf_avatarMaxWidth; + } + if (smf_avatarMaxHeight != 0 && this.avatar.height > smf_avatarMaxHeight) + { + this.avatar.width = (smf_avatarMaxHeight * this.avatar.width) / this.avatar.height; + this.avatar.height = smf_avatarMaxHeight; + } + } + tempAvatars[j].src = possibleAvatars[i].src; + j++; + } + + if (typeof(window_oldAvatarOnload) != 'undefined' && window_oldAvatarOnload) + { + window_oldAvatarOnload(); + window_oldAvatarOnload = null; + } +} + + +function hashLoginPassword(doForm, cur_session_id) +{ + // Compatibility. + if (cur_session_id == null) + cur_session_id = smf_session_id; + + if (typeof(hex_sha1) == 'undefined') + return; + // Are they using an email address? + if (doForm.user.value.indexOf('@') != -1) + return; + + // Unless the browser is Opera, the password will not save properly. + if (!('opera' in window)) + doForm.passwrd.autocomplete = 'off'; + + doForm.hash_passwrd.value = hex_sha1(hex_sha1(doForm.user.value.php_to8bit().php_strtolower() + doForm.passwrd.value.php_to8bit()) + cur_session_id); + + // It looks nicer to fill it with asterisks, but Firefox will try to save that. + if (is_ff != -1) + doForm.passwrd.value = ''; + else + doForm.passwrd.value = doForm.passwrd.value.replace(/./g, '*'); +} + +function hashAdminPassword(doForm, username, cur_session_id) +{ + // Compatibility. + if (cur_session_id == null) + cur_session_id = smf_session_id; + + if (typeof(hex_sha1) == 'undefined') + return; + + doForm.admin_hash_pass.value = hex_sha1(hex_sha1(username.php_to8bit().php_strtolower() + doForm.admin_pass.value.php_to8bit()) + cur_session_id); + doForm.admin_pass.value = doForm.admin_pass.value.replace(/./g, '*'); +} + +// Shows the page numbers by clicking the dots (in compact view). +function expandPages(spanNode, baseURL, firstPage, lastPage, perPage) +{ + var replacement = '', i, oldLastPage = 0; + var perPageLimit = 50; + + // The dots were bold, the page numbers are not (in most cases). + spanNode.style.fontWeight = 'normal'; + spanNode.onclick = ''; + + // Prevent too many pages to be loaded at once. + if ((lastPage - firstPage) / perPage > perPageLimit) + { + oldLastPage = lastPage; + lastPage = firstPage + perPageLimit * perPage; + } + + // Calculate the new pages. + for (i = firstPage; i < lastPage; i += perPage) + replacement += '' + (1 + i / perPage) + ' '; + + if (oldLastPage > 0) + replacement += ' ... '; + + // Replace the dots by the new page links. + setInnerHTML(spanNode, replacement); +} + +function smc_preCacheImage(sSrc) +{ + if (!('smc_aCachedImages' in window)) + window.smc_aCachedImages = []; + + if (!in_array(sSrc, window.smc_aCachedImages)) + { + var oImage = new Image(); + oImage.src = sSrc; + } +} + + +// *** smc_Cookie class. +function smc_Cookie(oOptions) +{ + this.opt = oOptions; + this.oCookies = {}; + this.init(); +} + +smc_Cookie.prototype.init = function() +{ + if ('cookie' in document && document.cookie != '') + { + var aCookieList = document.cookie.split(';'); + for (var i = 0, n = aCookieList.length; i < n; i++) + { + var aNameValuePair = aCookieList[i].split('='); + this.oCookies[aNameValuePair[0].replace(/^\s+|\s+$/g, '')] = decodeURIComponent(aNameValuePair[1]); + } + } +} + +smc_Cookie.prototype.get = function(sKey) +{ + return sKey in this.oCookies ? this.oCookies[sKey] : null; +} + +smc_Cookie.prototype.set = function(sKey, sValue) +{ + document.cookie = sKey + '=' + encodeURIComponent(sValue); +} + + +// *** smc_Toggle class. +function smc_Toggle(oOptions) +{ + this.opt = oOptions; + this.bCollapsed = false; + this.oCookie = null; + this.init(); +} + +smc_Toggle.prototype.init = function () +{ + // The master switch can disable this toggle fully. + if ('bToggleEnabled' in this.opt && !this.opt.bToggleEnabled) + return; + + // If cookies are enabled and they were set, override the initial state. + if ('oCookieOptions' in this.opt && this.opt.oCookieOptions.bUseCookie) + { + // Initialize the cookie handler. + this.oCookie = new smc_Cookie({}); + + // Check if the cookie is set. + var cookieValue = this.oCookie.get(this.opt.oCookieOptions.sCookieName) + if (cookieValue != null) + this.opt.bCurrentlyCollapsed = cookieValue == '1'; + } + + // If the init state is set to be collapsed, collapse it. + if (this.opt.bCurrentlyCollapsed) + this.changeState(true, true); + + // Initialize the images to be clickable. + if ('aSwapImages' in this.opt) + { + for (var i = 0, n = this.opt.aSwapImages.length; i < n; i++) + { + var oImage = document.getElementById(this.opt.aSwapImages[i].sId); + if (typeof(oImage) == 'object' && oImage != null) + { + // Display the image in case it was hidden. + if (oImage.style.display == 'none') + oImage.style.display = ''; + + oImage.instanceRef = this; + oImage.onclick = function () { + this.instanceRef.toggle(); + this.blur(); + } + oImage.style.cursor = 'pointer'; + + // Preload the collapsed image. + smc_preCacheImage(this.opt.aSwapImages[i].srcCollapsed); + } + } + } + + // Initialize links. + if ('aSwapLinks' in this.opt) + { + for (var i = 0, n = this.opt.aSwapLinks.length; i < n; i++) + { + var oLink = document.getElementById(this.opt.aSwapLinks[i].sId); + if (typeof(oLink) == 'object' && oLink != null) + { + // Display the link in case it was hidden. + if (oLink.style.display == 'none') + oLink.style.display = ''; + + oLink.instanceRef = this; + oLink.onclick = function () { + this.instanceRef.toggle(); + this.blur(); + return false; + } + } + } + } +} + +// Collapse or expand the section. +smc_Toggle.prototype.changeState = function(bCollapse, bInit) +{ + // Default bInit to false. + bInit = typeof(bInit) == 'undefined' ? false : true; + + // Handle custom function hook before collapse. + if (!bInit && bCollapse && 'funcOnBeforeCollapse' in this.opt) + { + this.tmpMethod = this.opt.funcOnBeforeCollapse; + this.tmpMethod(); + delete this.tmpMethod; + } + + // Handle custom function hook before expand. + else if (!bInit && !bCollapse && 'funcOnBeforeExpand' in this.opt) + { + this.tmpMethod = this.opt.funcOnBeforeExpand; + this.tmpMethod(); + delete this.tmpMethod; + } + + // Loop through all the images that need to be toggled. + if ('aSwapImages' in this.opt) + { + for (var i = 0, n = this.opt.aSwapImages.length; i < n; i++) + { + var oImage = document.getElementById(this.opt.aSwapImages[i].sId); + if (typeof(oImage) == 'object' && oImage != null) + { + // Only (re)load the image if it's changed. + var sTargetSource = bCollapse ? this.opt.aSwapImages[i].srcCollapsed : this.opt.aSwapImages[i].srcExpanded; + if (oImage.src != sTargetSource) + oImage.src = sTargetSource; + + oImage.alt = oImage.title = bCollapse ? this.opt.aSwapImages[i].altCollapsed : this.opt.aSwapImages[i].altExpanded; + } + } + } + + // Loop through all the links that need to be toggled. + if ('aSwapLinks' in this.opt) + { + for (var i = 0, n = this.opt.aSwapLinks.length; i < n; i++) + { + var oLink = document.getElementById(this.opt.aSwapLinks[i].sId); + if (typeof(oLink) == 'object' && oLink != null) + setInnerHTML(oLink, bCollapse ? this.opt.aSwapLinks[i].msgCollapsed : this.opt.aSwapLinks[i].msgExpanded); + } + } + + // Now go through all the sections to be collapsed. + for (var i = 0, n = this.opt.aSwappableContainers.length; i < n; i++) + { + if (this.opt.aSwappableContainers[i] == null) + continue; + + var oContainer = document.getElementById(this.opt.aSwappableContainers[i]); + if (typeof(oContainer) == 'object' && oContainer != null) + oContainer.style.display = bCollapse ? 'none' : ''; + } + + // Update the new state. + this.bCollapsed = bCollapse; + + // Update the cookie, if desired. + if ('oCookieOptions' in this.opt && this.opt.oCookieOptions.bUseCookie) + this.oCookie.set(this.opt.oCookieOptions.sCookieName, this.bCollapsed ? '1' : '0'); + + if ('oThemeOptions' in this.opt && this.opt.oThemeOptions.bUseThemeSettings) + smf_setThemeOption(this.opt.oThemeOptions.sOptionName, this.bCollapsed ? '1' : '0', 'sThemeId' in this.opt.oThemeOptions ? this.opt.oThemeOptions.sThemeId : null, this.opt.oThemeOptions.sSessionId, this.opt.oThemeOptions.sSessionVar, 'sAdditionalVars' in this.opt.oThemeOptions ? this.opt.oThemeOptions.sAdditionalVars : null); +} + +smc_Toggle.prototype.toggle = function() +{ + // Change the state by reversing the current state. + this.changeState(!this.bCollapsed); +} + + +function ajax_indicator(turn_on) +{ + if (ajax_indicator_ele == null) + { + ajax_indicator_ele = document.getElementById('ajax_in_progress'); + + if (ajax_indicator_ele == null && typeof(ajax_notification_text) != null) + { + create_ajax_indicator_ele(); + } + } + + if (ajax_indicator_ele != null) + { + if (navigator.appName == 'Microsoft Internet Explorer' && !is_ie7up) + { + ajax_indicator_ele.style.position = 'absolute'; + ajax_indicator_ele.style.top = document.documentElement.scrollTop; + } + + ajax_indicator_ele.style.display = turn_on ? 'block' : 'none'; + } +} + +function create_ajax_indicator_ele() +{ + // Create the div for the indicator. + ajax_indicator_ele = document.createElement('div'); + + // Set the id so it'll load the style properly. + ajax_indicator_ele.id = 'ajax_in_progress'; + + // Add the image in and link to turn it off. + var cancel_link = document.createElement('a'); + cancel_link.href = 'javascript:ajax_indicator(false)'; + var cancel_img = document.createElement('img'); + cancel_img.src = smf_images_url + '/icons/quick_remove.gif'; + + if (typeof(ajax_notification_cancel_text) != 'undefined') + { + cancel_img.alt = ajax_notification_cancel_text; + cancel_img.title = ajax_notification_cancel_text; + } + + // Add the cancel link and image to the indicator. + cancel_link.appendChild(cancel_img); + ajax_indicator_ele.appendChild(cancel_link); + + // Set the text. (Note: You MUST append here and not overwrite.) + ajax_indicator_ele.innerHTML += ajax_notification_text; + + // Finally attach the element to the body. + document.body.appendChild(ajax_indicator_ele); +} + +function createEventListener(oTarget) +{ + if (!('addEventListener' in oTarget)) + { + if (oTarget.attachEvent) + { + oTarget.addEventListener = function (sEvent, funcHandler, bCapture) { + oTarget.attachEvent('on' + sEvent, funcHandler); + } + oTarget.removeEventListener = function (sEvent, funcHandler, bCapture) { + oTarget.detachEvent('on' + sEvent, funcHandler); + } + } + else + { + oTarget.addEventListener = function (sEvent, funcHandler, bCapture) { + oTarget['on' + sEvent] = funcHandler; + } + oTarget.removeEventListener = function (sEvent, funcHandler, bCapture) { + oTarget['on' + sEvent] = null; + } + } + } +} + +// This function will retrieve the contents needed for the jump to boxes. +function grabJumpToContent() +{ + var oXMLDoc = getXMLDocument(smf_prepareScriptUrl(smf_scripturl) + 'action=xmlhttp;sa=jumpto;xml'); + var aBoardsAndCategories = new Array(); + + ajax_indicator(true); + + if (oXMLDoc.responseXML) + { + var items = oXMLDoc.responseXML.getElementsByTagName('smf')[0].getElementsByTagName('item'); + for (var i = 0, n = items.length; i < n; i++) + { + aBoardsAndCategories[aBoardsAndCategories.length] = { + id: parseInt(items[i].getAttribute('id')), + isCategory: items[i].getAttribute('type') == 'category', + name: items[i].firstChild.nodeValue.removeEntities(), + is_current: false, + childLevel: parseInt(items[i].getAttribute('childlevel')) + } + } + } + + ajax_indicator(false); + + for (var i = 0, n = aJumpTo.length; i < n; i++) + aJumpTo[i].fillSelect(aBoardsAndCategories); +} + +// This'll contain all JumpTo objects on the page. +var aJumpTo = new Array(); + +// *** JumpTo class. +function JumpTo(oJumpToOptions) +{ + this.opt = oJumpToOptions; + this.dropdownList = null; + this.showSelect(); +} + +// Show the initial select box (onload). Method of the JumpTo class. +JumpTo.prototype.showSelect = function () +{ + var sChildLevelPrefix = ''; + for (var i = this.opt.iCurBoardChildLevel; i > 0; i--) + sChildLevelPrefix += this.opt.sBoardChildLevelIndicator; + setInnerHTML(document.getElementById(this.opt.sContainerId), this.opt.sJumpToTemplate.replace(/%select_id%/, this.opt.sContainerId + '_select').replace(/%dropdown_list%/, ' ')); + this.dropdownList = document.getElementById(this.opt.sContainerId + '_select'); +} + +// Fill the jump to box with entries. Method of the JumpTo class. +JumpTo.prototype.fillSelect = function (aBoardsAndCategories) +{ + var bIE5x = !('implementation' in document); + var iIndexPointer = 0; + + // Create an option that'll be above and below the category. + var oDashOption = document.createElement('option'); + oDashOption.appendChild(document.createTextNode(this.opt.sCatSeparator)); + oDashOption.disabled = 'disabled'; + oDashOption.value = ''; + + // Reset the events and clear the list (IE5.x only). + if (bIE5x) + { + this.dropdownList.onmouseover = null; + this.dropdownList.remove(0); + } + if ('onbeforeactivate' in document) + this.dropdownList.onbeforeactivate = null; + else + this.dropdownList.onfocus = null; + + // Create a document fragment that'll allowing inserting big parts at once. + var oListFragment = bIE5x ? this.dropdownList : document.createDocumentFragment(); + + // Loop through all items to be added. + for (var i = 0, n = aBoardsAndCategories.length; i < n; i++) + { + var j, sChildLevelPrefix, oOption; + + // If we've reached the currently selected board add all items so far. + if (!aBoardsAndCategories[i].isCategory && aBoardsAndCategories[i].id == this.opt.iCurBoardId) + { + if (bIE5x) + iIndexPointer = this.dropdownList.options.length; + else + { + this.dropdownList.insertBefore(oListFragment, this.dropdownList.options[0]); + oListFragment = document.createDocumentFragment(); + continue; + } + } + + if (aBoardsAndCategories[i].isCategory) + oListFragment.appendChild(oDashOption.cloneNode(true)); + else + for (j = aBoardsAndCategories[i].childLevel, sChildLevelPrefix = ''; j > 0; j--) + sChildLevelPrefix += this.opt.sBoardChildLevelIndicator; + + oOption = document.createElement('option'); + oOption.appendChild(document.createTextNode((aBoardsAndCategories[i].isCategory ? this.opt.sCatPrefix : sChildLevelPrefix + this.opt.sBoardPrefix) + aBoardsAndCategories[i].name)); + oOption.value = aBoardsAndCategories[i].isCategory ? '#c' + aBoardsAndCategories[i].id : '?board=' + aBoardsAndCategories[i].id + '.0'; + oListFragment.appendChild(oOption); + + if (aBoardsAndCategories[i].isCategory) + oListFragment.appendChild(oDashOption.cloneNode(true)); + } + + // Add the remaining items after the currently selected item. + this.dropdownList.appendChild(oListFragment); + + if (bIE5x) + this.dropdownList.options[iIndexPointer].selected = true; + + // Internet Explorer needs this to keep the box dropped down. + this.dropdownList.style.width = 'auto'; + this.dropdownList.focus(); + + // Add an onchange action + this.dropdownList.onchange = function() { + if (this.selectedIndex > 0 && this.options[this.selectedIndex].value) + window.location.href = smf_scripturl + this.options[this.selectedIndex].value.substr(smf_scripturl.indexOf('?') == -1 || this.options[this.selectedIndex].value.substr(0, 1) != '?' ? 0 : 1); + } +} + +// A global array containing all IconList objects. +var aIconLists = new Array(); + +// *** IconList object. +function IconList(oOptions) +{ + if (!window.XMLHttpRequest) + return; + + this.opt = oOptions; + this.bListLoaded = false; + this.oContainerDiv = null; + this.funcMousedownHandler = null; + this.funcParent = this; + this.iCurMessageId = 0; + this.iCurTimeout = 0; + + // Add backwards compatibility with old themes. + if (!('sSessionVar' in this.opt)) + this.opt.sSessionVar = 'sesc'; + + this.initIcons(); +} + +// Replace all message icons by icons with hoverable and clickable div's. +IconList.prototype.initIcons = function () +{ + for (var i = document.images.length - 1, iPrefixLength = this.opt.sIconIdPrefix.length; i >= 0; i--) + if (document.images[i].id.substr(0, iPrefixLength) == this.opt.sIconIdPrefix) + setOuterHTML(document.images[i], '
    ' + document.images[i].alt + '
    '); +} + +// Event for the mouse hovering over the original icon. +IconList.prototype.onBoxHover = function (oDiv, bMouseOver) +{ + oDiv.style.border = bMouseOver ? this.opt.iBoxBorderWidthHover + 'px solid ' + this.opt.sBoxBorderColorHover : ''; + oDiv.style.background = bMouseOver ? this.opt.sBoxBackgroundHover : this.opt.sBoxBackground; + oDiv.style.padding = bMouseOver ? (3 - this.opt.iBoxBorderWidthHover) + 'px' : '3px' +} + +// Show the list of icons after the user clicked the original icon. +IconList.prototype.openPopup = function (oDiv, iMessageId) +{ + this.iCurMessageId = iMessageId; + + if (!this.bListLoaded && this.oContainerDiv == null) + { + // Create a container div. + this.oContainerDiv = document.createElement('div'); + this.oContainerDiv.id = 'iconList'; + this.oContainerDiv.style.display = 'none'; + this.oContainerDiv.style.cursor = is_ie && !is_ie6up ? 'hand' : 'pointer'; + this.oContainerDiv.style.position = 'absolute'; + this.oContainerDiv.style.width = oDiv.offsetWidth + 'px'; + this.oContainerDiv.style.background = this.opt.sContainerBackground; + this.oContainerDiv.style.border = this.opt.sContainerBorder; + this.oContainerDiv.style.padding = '1px'; + this.oContainerDiv.style.textAlign = 'center'; + document.body.appendChild(this.oContainerDiv); + + // Start to fetch its contents. + ajax_indicator(true); + this.tmpMethod = getXMLDocument; + this.tmpMethod(smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=xmlhttp;sa=messageicons;board=' + this.opt.iBoardId + ';xml', this.onIconsReceived); + delete this.tmpMethod; + + createEventListener(document.body); + } + + // Set the position of the container. + var aPos = smf_itemPos(oDiv); + if (is_ie50) + aPos[1] += 4; + + this.oContainerDiv.style.top = (aPos[1] + oDiv.offsetHeight) + 'px'; + this.oContainerDiv.style.left = (aPos[0] - 1) + 'px'; + this.oClickedIcon = oDiv; + + if (this.bListLoaded) + this.oContainerDiv.style.display = 'block'; + + document.body.addEventListener('mousedown', this.onWindowMouseDown, false); +} + +// Setup the list of icons once it is received through xmlHTTP. +IconList.prototype.onIconsReceived = function (oXMLDoc) +{ + var icons = oXMLDoc.getElementsByTagName('smf')[0].getElementsByTagName('icon'); + var sItems = ''; + + for (var i = 0, n = icons.length; i < n; i++) + sItems += '
    ' + icons[i].getAttribute('name') + '
    '; + + setInnerHTML(this.oContainerDiv, sItems); + this.oContainerDiv.style.display = 'block'; + this.bListLoaded = true; + + if (is_ie) + this.oContainerDiv.style.width = this.oContainerDiv.clientWidth + 'px'; + + ajax_indicator(false); +} + +// Event handler for hovering over the icons. +IconList.prototype.onItemHover = function (oDiv, bMouseOver) +{ + oDiv.style.background = bMouseOver ? this.opt.sItemBackgroundHover : this.opt.sItemBackground; + oDiv.style.border = bMouseOver ? this.opt.sItemBorderHover : this.opt.sItemBorder; + if (this.iCurTimeout != 0) + window.clearTimeout(this.iCurTimeout); + if (bMouseOver) + this.onBoxHover(this.oClickedIcon, true); + else + this.iCurTimeout = window.setTimeout(this.opt.sBackReference + '.collapseList();', 500); +} + +// Event handler for clicking on one of the icons. +IconList.prototype.onItemMouseDown = function (oDiv, sNewIcon) +{ + if (this.iCurMessageId != 0) + { + ajax_indicator(true); + this.tmpMethod = getXMLDocument; + var oXMLDoc = this.tmpMethod(smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=jsmodify;topic=' + this.opt.iTopicId + ';msg=' + this.iCurMessageId + ';' + this.opt.sSessionVar + '=' + this.opt.sSessionId + ';icon=' + sNewIcon + ';xml'); + delete this.tmpMethod; + ajax_indicator(false); + + var oMessage = oXMLDoc.responseXML.getElementsByTagName('smf')[0].getElementsByTagName('message')[0]; + if (oMessage.getElementsByTagName('error').length == 0) + { + if (this.opt.bShowModify && oMessage.getElementsByTagName('modified').length != 0) + setInnerHTML(document.getElementById('modified_' + this.iCurMessageId), oMessage.getElementsByTagName('modified')[0].childNodes[0].nodeValue); + this.oClickedIcon.getElementsByTagName('img')[0].src = oDiv.getElementsByTagName('img')[0].src; + } + } +} + +// Event handler for clicking outside the list (will make the list disappear). +IconList.prototype.onWindowMouseDown = function () +{ + for (var i = aIconLists.length - 1; i >= 0; i--) + { + aIconLists[i].funcParent.tmpMethod = aIconLists[i].collapseList; + aIconLists[i].funcParent.tmpMethod(); + delete aIconLists[i].funcParent.tmpMethod; + } +} + +// Collapse the list of icons. +IconList.prototype.collapseList = function() +{ + this.onBoxHover(this.oClickedIcon, false); + this.oContainerDiv.style.display = 'none'; + this.iCurMessageId = 0; + document.body.removeEventListener('mousedown', this.onWindowMouseDown, false); +} + +// Handy shortcuts for getting the mouse position on the screen - only used for IE at the moment. +function smf_mousePose(oEvent) +{ + var x = 0; + var y = 0; + + if (oEvent.pageX) + { + y = oEvent.pageY; + x = oEvent.pageX; + } + else if (oEvent.clientX) + { + x = oEvent.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft); + y = oEvent.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop); + } + + return [x, y]; +} + +// Short function for finding the actual position of an item. +function smf_itemPos(itemHandle) +{ + var itemX = 0; + var itemY = 0; + + if ('offsetParent' in itemHandle) + { + itemX = itemHandle.offsetLeft; + itemY = itemHandle.offsetTop; + while (itemHandle.offsetParent && typeof(itemHandle.offsetParent) == 'object') + { + itemHandle = itemHandle.offsetParent; + itemX += itemHandle.offsetLeft; + itemY += itemHandle.offsetTop; + } + } + else if ('x' in itemHandle) + { + itemX = itemHandle.x; + itemY = itemHandle.y; + } + + return [itemX, itemY]; +} + +// This function takes the script URL and prepares it to allow the query string to be appended to it. +function smf_prepareScriptUrl(sUrl) +{ + return sUrl.indexOf('?') == -1 ? sUrl + '?' : sUrl + (sUrl.charAt(sUrl.length - 1) == '?' || sUrl.charAt(sUrl.length - 1) == '&' || sUrl.charAt(sUrl.length - 1) == ';' ? '' : ';'); +} + +var aOnloadEvents = new Array(); +function addLoadEvent(fNewOnload) +{ + // If there's no event set, just set this one + if (typeof(fNewOnload) == 'function' && (!('onload' in window) || typeof(window.onload) != 'function')) + window.onload = fNewOnload; + + // If there's just one event, setup the array. + else if (aOnloadEvents.length == 0) + { + aOnloadEvents[0] = window.onload; + aOnloadEvents[1] = fNewOnload; + window.onload = function() { + for (var i = 0, n = aOnloadEvents.length; i < n; i++) + { + if (typeof(aOnloadEvents[i]) == 'function') + aOnloadEvents[i](); + else if (typeof(aOnloadEvents[i]) == 'string') + eval(aOnloadEvents[i]); + } + } + } + + // This isn't the first event function, add it to the list. + else + aOnloadEvents[aOnloadEvents.length] = fNewOnload; +} + +function smfFooterHighlight(element, value) +{ + element.src = smf_images_url + '/' + (value ? 'h_' : '') + element.id + '.gif'; +} + +// Get the text in a code tag. +function smfSelectText(oCurElement, bActOnElement) +{ + // The place we're looking for is one div up, and next door - if it's auto detect. + if (typeof(bActOnElement) == 'boolean' && bActOnElement) + var oCodeArea = document.getElementById(oCurElement); + else + var oCodeArea = oCurElement.parentNode.nextSibling; + + if (typeof(oCodeArea) != 'object' || oCodeArea == null) + return false; + + // Start off with my favourite, internet explorer. + if ('createTextRange' in document.body) + { + var oCurRange = document.body.createTextRange(); + oCurRange.moveToElementText(oCodeArea); + oCurRange.select(); + } + // Firefox at el. + else if (window.getSelection) + { + var oCurSelection = window.getSelection(); + // Safari is special! + if (oCurSelection.setBaseAndExtent) + { + var oLastChild = oCodeArea.lastChild; + oCurSelection.setBaseAndExtent(oCodeArea, 0, oLastChild, 'innerText' in oLastChild ? oLastChild.innerText.length : oLastChild.textContent.length); + } + else + { + var curRange = document.createRange(); + curRange.selectNodeContents(oCodeArea); + + oCurSelection.removeAllRanges(); + oCurSelection.addRange(curRange); + } + } + + return false; +} + +// A function needed to discern HTML entities from non-western characters. +function smc_saveEntities(sFormName, aElementNames, sMask) +{ + if (typeof(sMask) == 'string') + { + for (var i = 0, n = document.forms[sFormName].elements.length; i < n; i++) + if (document.forms[sFormName].elements[i].id.substr(0, sMask.length) == sMask) + aElementNames[aElementNames.length] = document.forms[sFormName].elements[i].name; + } + + for (var i = 0, n = aElementNames.length; i < n; i++) + { + if (aElementNames[i] in document.forms[sFormName]) + document.forms[sFormName][aElementNames[i]].value = document.forms[sFormName][aElementNames[i]].value.replace(/&#/g, '&#'); + } +} + +// A function used to clean the attachments on post page +function cleanFileInput(idElement) +{ + // Simpler solutions work in Opera, IE, Safari and Chrome. + if (is_opera || is_ie || is_safari || is_chrome) + { + document.getElementById(idElement).outerHTML = document.getElementById(idElement).outerHTML; + } + // What else can we do? By the way, this doesn't work in Chrome and Mac's Safari. + else + { + document.getElementById(idElement).type = 'input'; + document.getElementById(idElement).type = 'file'; + } +} diff --git a/Themes/default/scripts/sha1.js b/Themes/default/scripts/sha1.js new file mode 100644 index 0000000..62dd8dd --- /dev/null +++ b/Themes/default/scripts/sha1.js @@ -0,0 +1,205 @@ +/* + * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined + * in FIPS PUB 180-1 + * Version 2.1 Copyright Paul Johnston 2000 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for details. + */ + +/* + * Configurable variables. You may need to tweak these to be compatible with + * the server-side, but the defaults work in most cases. + */ +var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ +var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ +var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ + +/* + * These are the functions you'll usually want to call + * They take string arguments and return either hex or base-64 encoded strings + */ +function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));} +function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));} +function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));} +function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));} +function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));} +function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));} + +/* + * Perform a simple self-test to see if the VM is working + */ +function sha1_vm_test() +{ + return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d"; +} + +/* + * Calculate the SHA-1 of an array of big-endian words, and a bit length + */ +function core_sha1(x, len) +{ + /* append padding */ + x[len >> 5] |= 0x80 << (24 - len % 32); + x[((len + 64 >> 9) << 4) + 15] = len; + + var w = Array(80); + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + var e = -1009589776; + + for (var i = 0; i < x.length; i += 16) + { + var olda = a; + var oldb = b; + var oldc = c; + var oldd = d; + var olde = e; + + for (var j = 0; j < 80; j++) + { + if (j < 16) w[j] = x[i + j]; + else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); + var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), safe_add(safe_add(e, w[j]), sha1_kt(j))); + e = d; + d = c; + c = rol(b, 30); + b = a; + a = t; + } + + a = safe_add(a, olda); + b = safe_add(b, oldb); + c = safe_add(c, oldc); + d = safe_add(d, oldd); + e = safe_add(e, olde); + } + return Array(a, b, c, d, e); +} + +/* + * Perform the appropriate triplet combination function for the current + * iteration + */ +function sha1_ft(t, b, c, d) +{ + if (t < 20) return (b & c) | ((~b) & d); + if (t < 40) return b ^ c ^ d; + if (t < 60) return (b & c) | (b & d) | (c & d); + return b ^ c ^ d; +} + +/* + * Determine the appropriate additive constant for the current iteration + */ +function sha1_kt(t) +{ + return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : + (t < 60) ? -1894007588 : -899497514; +} + +/* + * Calculate the HMAC-SHA1 of a key and some data + */ +function core_hmac_sha1(key, data) +{ + var bkey = str2binb(key); + if (bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz); + + var ipad = Array(16), opad = Array(16); + for (var i = 0; i < 16; i++) + { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz); + return core_sha1(opad.concat(hash), 512 + 160); +} + +/* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ +function safe_add(x, y) +{ + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); +} + +/* + * Bitwise rotate a 32-bit number to the left. + */ +function rol(num, cnt) +{ + return (num << cnt) | (num >>> (32 - cnt)); +} + +/* + * Convert an 8-bit or 16-bit string to an array of big-endian words + * In 8-bit function, characters >255 have their hi-byte silently ignored. + */ +function str2binb(str) +{ + var bin = Array(); + + for (var i = 0, n = 1 + ((str.length * chrsz) >> 5); i < n; i++) + bin[i] = 0; + + var mask = (1 << chrsz) - 1; + for (var i = 0; i < str.length * chrsz; i += chrsz) + bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i % 32); + return bin; +} + +/* + * Convert an array of big-endian words to a string + */ +function binb2str(bin) +{ + var str = ""; + var mask = (1 << chrsz) - 1; + for (var i = 0; i < bin.length * 32; i += chrsz) + str += String.fromCharCode((bin[i>>5] >>> (24 - i%32)) & mask); + return str; +} + +/* + * Convert an array of big-endian words to a hex string. + */ +function binb2hex(binarray) +{ + var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var str = ""; + for (var i = 0; i < binarray.length * 4; i++) + { + str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) + + hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF); + } + return str; +} + +/* + * Convert an array of big-endian words to a base-64 string + */ +function binb2b64(binarray) +{ + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var str = ""; + for (var i = 0; i < binarray.length * 4; i += 3) + { + var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) + | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) + | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); + for (var j = 0; j < 4; j++) + { + if (i * 8 + j * 6 > binarray.length * 32) str += b64pad; + else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); + } + } + return str; +} + diff --git a/Themes/default/scripts/spellcheck.js b/Themes/default/scripts/spellcheck.js new file mode 100644 index 0000000..a258ee9 --- /dev/null +++ b/Themes/default/scripts/spellcheck.js @@ -0,0 +1,297 @@ +// These are variables the popup is going to want to access... +var spell_formname, spell_fieldname; + +// Spell check the specified field in the specified form. +function spellCheck(formName, fieldName) +{ + // Grab the (hidden) spell checking form. + var spellform = document.forms.spell_form; + + // Register the name of the editing form for future reference. + spell_formname = formName; + spell_fieldname = fieldName; + + // This should match a word (most of the time). + var regexpWordMatch = /(?:<[^>]+>)|(?:\[[^ ][^\]]*\])|(?:&[^; ]+;)|(?:[^0-9\s\]\[{};:"\\|,<.>\/?`~!@#$%^&*()_+=]+)/g; + + // These characters can appear within words. + var aWordCharacters = ['-', '\'']; + + var aWords = new Array(), aResult = new Array(); + var sText = document.forms[formName][fieldName].value; + var bInCode = false; + var iOffset1, iOffset2; + + // Loop through all words. + while ((aResult = regexpWordMatch.exec(sText)) && typeof(aResult) != 'undefined') + { + iOffset1 = 0; + iOffset2 = aResult[0].length - 1; + + // Strip the dashes and hyphens from the begin of the word. + while (in_array(aResult[0].charAt(iOffset1), aWordCharacters) && iOffset1 < iOffset2) + iOffset1++; + + // Strip the dashes and hyphens from the end of the word. + while (in_array(aResult[0].charAt(iOffset2), aWordCharacters) && iOffset1 < iOffset2) + iOffset2--; + + // I guess there's only dashes and hyphens in this word... + if (iOffset1 == iOffset2) + continue; + + // Ignore code blocks. + if (aResult[0].substr(0, 5).toLowerCase() == '[code') + bInCode = true; + + // Glad we're out of that code block! + else if (bInCode && aResult[0].substr(0, 7).toLowerCase() == '[/code]') + bInCode = false; + + // Now let's get to business. + else if (!bInCode && !in_array(aResult[0].charAt(0), ['[', '<']) && aResult[0].toUpperCase() != aResult[0]) + aWords[aWords.length] = aResult[0].substr(iOffset1, iOffset2 - iOffset1 + 1) + '|' + (iOffset1 + sText.substr(0, aResult.index).length) + '|' + (iOffset2 + sText.substr(0, aResult.index).length); + } + + // Open the window... + openSpellWin(640, 480); + + // Pass the data to a form... + spellform.spellstring.value = aWords.join('\n'); + + // and go! + spellform.submit(); + + return true; +} + +// Private functions ------------------------------- + +// Globals... +var wordindex = -1, offsetindex = 0; +var ignoredWords = []; + +// A "misspelled word" object. +function misp(word, start, end, suggestions) +{ + // The word, start index, end index, and array of suggestions. + this.word = word; + this.start = start; + this.end = end; + this.suggestions = suggestions; +} + +// Replace the word in the misps array at the "wordindex" index. The +// misps array is generated by a PHP script after the string to be spell +// checked is evaluated with pspell. +function replaceWord() +{ + var strstart = ""; + var strend; + + // If this isn't the beginning of the string then get all of the string + // that is before the word we are replacing. + if (misps[wordindex].start != 0) + strstart = mispstr.slice(0, misps[wordindex].start + offsetindex); + + // Get the end of the string after the word we are replacing. + strend = mispstr.slice(misps[wordindex].end + 1 + offsetindex); + + // Rebuild the string with the new word. + mispstr = strstart + document.forms.spellingForm.changeto.value + strend; + + // Update offsetindex to compensate for replacing a word with a word + // of a different length. + offsetindex += document.forms.spellingForm.changeto.value.length - misps[wordindex].word.length; + + // Update the word so future replaceAll calls don't change it. + misps[wordindex].word = document.forms.spellingForm.changeto.value; + + nextWord(false); +} + +// Replaces all instances of currently selected word with contents chosen by user. +// Note: currently only replaces words after highlighted word. I think we can re-index +// all words at replacement or ignore time to have it wrap to the beginning if we want +// to. +function replaceAll() +{ + var strend; + var idx; + var origword; + var localoffsetindex = offsetindex; + + origword = misps[wordindex].word; + + // Re-index everything past the current word. + for (idx = wordindex; idx < misps.length; idx++) + { + misps[idx].start += localoffsetindex; + misps[idx].end += localoffsetindex; + } + + localoffsetindex = 0; + + for (idx = 0; idx < misps.length; idx++) + { + if (misps[idx].word == origword) + { + var strstart = ""; + if (misps[idx].start != 0) + strstart = mispstr.slice(0, misps[idx].start + localoffsetindex); + + // Get the end of the string after the word we are replacing. + strend = mispstr.slice(misps[idx].end + 1 + localoffsetindex); + + // Rebuild the string with the new word. + mispstr = strstart + document.forms.spellingForm.changeto.value + strend; + + // Update offsetindex to compensate for replacing a word with a word + // of a different length. + localoffsetindex += document.forms.spellingForm.changeto.value.length - misps[idx].word.length; + } + + // We have to re-index everything after replacements. + misps[idx].start += localoffsetindex; + misps[idx].end += localoffsetindex; + } + + // Add the word to the ignore array. + ignoredWords[origword] = true; + + // Reset offsetindex since we re-indexed. + offsetindex = 0; + + nextWord(false); +} + +// Highlight the word that was selected using the nextWord function. +function highlightWord() +{ + var strstart = ""; + var strend; + + // If this isn't the beginning of the string then get all of the string + // that is before the word we are replacing. + if (misps[wordindex].start != 0) + strstart = mispstr.slice(0, misps[wordindex].start + offsetindex); + + // Get the end of the string after the word we are replacing. + strend = mispstr.slice(misps[wordindex].end + 1 + offsetindex); + + // Rebuild the string with a span wrapped around the misspelled word + // so we can highlight it in the div the user is viewing the string in. + var divptr, newValue; + divptr = document.getElementById("spellview"); + + newValue = htmlspecialchars(strstart) + '' + misps[wordindex].word + '' + htmlspecialchars(strend); + setInnerHTML(divptr, newValue.replace(/_\|_/g, '
    ')); + + // We could use scrollIntoView, but it's just not that great anyway. + var spellview_height = typeof(document.getElementById("spellview").currentStyle) != "undefined" ? parseInt(document.getElementById("spellview").currentStyle.height) : document.getElementById("spellview").offsetHeight; + var word_position = document.getElementById("h1").offsetTop; + var current_position = document.getElementById("spellview").scrollTop; + + // The spellview is not tall enough! Scroll down! + if (spellview_height <= (word_position + current_position)) + document.getElementById("spellview").scrollTop = word_position + current_position - spellview_height + 32; +} + +// Display the next misspelled word to the user and populate the suggested spellings box. +function nextWord(ignoreall) +{ + // Push ignored word onto ignoredWords array. + if (ignoreall) + ignoredWords[misps[wordindex].word] = true; + + // Update the index of all words we have processed... + // This must be done to accomodate the replaceAll function. + if (wordindex >= 0) + { + misps[wordindex].start += offsetindex; + misps[wordindex].end += offsetindex; + } + + // Increment the counter for the array of misspelled words. + wordindex++; + + // Draw it and quit if there are no more misspelled words to evaluate. + if (misps.length <= wordindex) + { + var divptr; + divptr = document.getElementById("spellview"); + setInnerHTML(divptr, htmlspecialchars(mispstr).replace(/_\|_/g, "
    ")); + + while (document.forms.spellingForm.suggestions.options.length > 0) + document.forms.spellingForm.suggestions.options[0] = null; + + alert(txt['done']); + document.forms.spellingForm.change.disabled = true; + document.forms.spellingForm.changeall.disabled = true; + document.forms.spellingForm.ignore.disabled = true; + document.forms.spellingForm.ignoreall.disabled = true; + + // Put line feeds back... + mispstr = mispstr.replace(/_\|_/g, "\n"); + + // Get a handle to the field we need to re-populate. + window.opener.document.forms[spell_formname][spell_fieldname].value = mispstr; + if (!window.opener.spellCheckDone) + window.opener.document.forms[spell_formname][spell_fieldname].focus(); + else + window.opener.spellCheckDone(); + + window.close(); + return true; + } + + // Check to see if word is supposed to be ignored. + if (typeof(ignoredWords[misps[wordindex].word]) != "undefined") + { + nextWord(false); + return false; + } + + // Clear out the suggestions box! + while (document.forms.spellingForm.suggestions.options.length > 0) + document.forms.spellingForm.suggestions.options[0] = null; + + // Re-populate the suggestions box if there are any suggested spellings for the word. + if (misps[wordindex].suggestions.length) + { + for (var sugidx = 0; sugidx < misps[wordindex].suggestions.length; sugidx++) + { + var newopt = new Option(misps[wordindex].suggestions[sugidx], misps[wordindex].suggestions[sugidx]); + document.forms.spellingForm.suggestions.options[sugidx] = newopt; + + if (sugidx == 0) + { + newopt.selected = true; + document.forms.spellingForm.changeto.value = newopt.value; + document.forms.spellingForm.changeto.select(); + } + } + } + + if (document.forms.spellingForm.suggestions.options.length == 0) + document.forms.spellingForm.changeto.value = ""; + + highlightWord(); + + return false; +} + +function htmlspecialchars(thetext) +{ + thetext = thetext.replace(/\/g, ">"); + thetext = thetext.replace(/\n/g, "
    "); + thetext = thetext.replace(/\ \ /g, "  "); + + return thetext; +} + +function openSpellWin(width, height) +{ + window.open("", "spellWindow", "toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=no,width=" + width + ",height=" + height); +} \ No newline at end of file diff --git a/Themes/default/scripts/stats.js b/Themes/default/scripts/stats.js new file mode 100644 index 0000000..077e8cc --- /dev/null +++ b/Themes/default/scripts/stats.js @@ -0,0 +1,239 @@ +function smf_StatsCenter(oOptions) +{ + this.opt = oOptions; + + this.oTable = null; + this.oYears = {}; + + this.bIsLoading = false; + + this.init(); +} + +smf_StatsCenter.prototype.init = function () +{ + this.oTable = document.getElementById(this.opt.sTableId); + + // Is the table actually present? + if (typeof(this.oTable) != 'object') + return; + + // Find all months and years defined in the table. + var aRows = this.oTable.getElementsByTagName('tr'); + var aResults = []; + + var sYearId = null; + var oCurYear = null; + + var sMonthId = null; + var oCurMonth = null; + for (var i = 0, n = aRows.length; i < n; i++) + { + // Check if the current row represents a year. + if ((aResults = this.opt.reYearPattern.exec(aRows[i].id)) != null) + { + // The id is part of the pattern match. + sYearId = aResults[1]; + + // Setup the object that'll have the state information of the year. + this.oYears[sYearId] = { + oCollapseImage: document.getElementById(this.opt.sYearImageIdPrefix + sYearId), + oMonths: {} + }; + + // Create a shortcut, makes things more readable. + oCurYear = this.oYears[sYearId]; + + // Use the collapse image to determine the current state. + oCurYear.bIsCollapsed = oCurYear.oCollapseImage.src.indexOf(this.opt.sYearImageCollapsed) >= 0; + + // Setup the toggle element for the year. + oCurYear.oToggle = new smc_Toggle({ + bToggleEnabled: true, + bCurrentlyCollapsed: oCurYear.bIsCollapsed, + instanceRef: this, + sYearId: sYearId, + funcOnBeforeCollapse: function () { + this.opt.instanceRef.onBeforeCollapseYear(this); + }, + aSwappableContainers: [ + ], + aSwapImages: [ + { + sId: this.opt.sYearImageIdPrefix + sYearId, + srcExpanded: smf_images_url + '/' + this.opt.sYearImageExpanded, + altExpanded: '-', + srcCollapsed: smf_images_url + '/' + this.opt.sYearImageCollapsed, + altCollapsed: '+' + } + ], + aSwapLinks: [ + { + sId: this.opt.sYearLinkIdPrefix + sYearId, + msgExpanded: sYearId, + msgCollapsed: sYearId + } + ] + }); + } + + // Or maybe the current row represents a month. + else if ((aResults = this.opt.reMonthPattern.exec(aRows[i].id)) != null) + { + // Set the id to the matched pattern. + sMonthId = aResults[1]; + + // Initialize the month as a child object of the year. + oCurYear.oMonths[sMonthId] = { + oCollapseImage: document.getElementById(this.opt.sMonthImageIdPrefix + sMonthId) + }; + + // Create a shortcut to the current month. + oCurMonth = oCurYear.oMonths[sMonthId]; + + // Determine whether the month is currently collapsed or expanded.. + oCurMonth.bIsCollapsed = oCurMonth.oCollapseImage.src.indexOf(this.opt.sMonthImageCollapsed) >= 0; + + var sLinkText = getInnerHTML(document.getElementById(this.opt.sMonthLinkIdPrefix + sMonthId)); + + // Setup the toggle element for the month. + oCurMonth.oToggle = new smc_Toggle({ + bToggleEnabled: true, + bCurrentlyCollapsed: oCurMonth.bIsCollapsed, + instanceRef: this, + sMonthId: sMonthId, + funcOnBeforeCollapse: function () { + this.opt.instanceRef.onBeforeCollapseMonth(this); + }, + funcOnBeforeExpand: function () { + this.opt.instanceRef.onBeforeExpandMonth(this); + }, + aSwappableContainers: [ + ], + aSwapImages: [ + { + sId: this.opt.sMonthImageIdPrefix + sMonthId, + srcExpanded: smf_images_url + '/' + this.opt.sMonthImageExpanded, + altExpanded: '-', + srcCollapsed: smf_images_url + '/' + this.opt.sMonthImageCollapsed, + altCollapsed: '+' + } + ], + aSwapLinks: [ + { + sId: this.opt.sMonthLinkIdPrefix + sMonthId, + msgExpanded: sLinkText, + msgCollapsed: sLinkText + } + ] + }); + + oCurYear.oToggle.opt.aSwappableContainers[oCurYear.oToggle.opt.aSwappableContainers.length] = aRows[i].id; + } + + else if((aResults = this.opt.reDayPattern.exec(aRows[i].id)) != null) + { + oCurMonth.oToggle.opt.aSwappableContainers[oCurMonth.oToggle.opt.aSwappableContainers.length] = aRows[i].id; + oCurYear.oToggle.opt.aSwappableContainers[oCurYear.oToggle.opt.aSwappableContainers.length] = aRows[i].id; + } + } + + // Collapse all collapsed years! + for (i = 0; i < this.opt.aCollapsedYears.length; i++) + this.oYears[this.opt.aCollapsedYears[i]].oToggle.toggle(); +} + +smf_StatsCenter.prototype.onBeforeCollapseYear = function (oToggle) +{ + // Tell SMF that all underlying months have disappeared. + for (var sMonth in this.oYears[oToggle.opt.sYearId].oMonths) + if (this.oYears[oToggle.opt.sYearId].oMonths[sMonth].oToggle.opt.aSwappableContainers.length > 0) + this.oYears[oToggle.opt.sYearId].oMonths[sMonth].oToggle.changeState(true); +} + + +smf_StatsCenter.prototype.onBeforeCollapseMonth = function (oToggle) +{ + if (!oToggle.bCollapsed) + { + // Tell SMF that it the state has changed. + getXMLDocument(smf_prepareScriptUrl(smf_scripturl) + 'action=stats;collapse=' + oToggle.opt.sMonthId + ';xml'); + + // Remove the month rows from the year toggle. + var aNewContainers = []; + var oYearToggle = this.oYears[oToggle.opt.sMonthId.substr(0, 4)].oToggle; + + for (var i = 0, n = oYearToggle.opt.aSwappableContainers.length; i < n; i++) + if (!in_array(oYearToggle.opt.aSwappableContainers[i], oToggle.opt.aSwappableContainers)) + aNewContainers[aNewContainers.length] = oYearToggle.opt.aSwappableContainers[i]; + + oYearToggle.opt.aSwappableContainers = aNewContainers; + } +} + + +smf_StatsCenter.prototype.onBeforeExpandMonth = function (oToggle) +{ + // Ignore if we're still loading the previous batch. + if (this.bIsLoading) + return; + + if (oToggle.opt.aSwappableContainers.length == 0) + { + // A complicated way to call getXMLDocument, but stay in scope. + this.tmpMethod = getXMLDocument; + this.oXmlRequestHandle = this.tmpMethod(smf_prepareScriptUrl(smf_scripturl) + 'action=stats;expand=' + oToggle.opt.sMonthId + ';xml', this.onDocReceived); + delete this.tmpMethod; + + if ('ajax_indicator' in window) + ajax_indicator(true); + + this.bIsLoading = true; + } + + // Silently let SMF know this one is expanded. + else + getXMLDocument(smf_prepareScriptUrl(smf_scripturl) + 'action=stats;expand=' + oToggle.opt.sMonthId + ';xml'); +} + +smf_StatsCenter.prototype.onDocReceived = function (oXMLDoc) +{ + // Loop through all the months we got from the XML. + var aMonthNodes = oXMLDoc.getElementsByTagName('month'); + for (var iMonthIndex = 0, iNumMonths = aMonthNodes.length; iMonthIndex < iNumMonths; iMonthIndex++) + { + var sMonthId = aMonthNodes[iMonthIndex].getAttribute('id'); + var iStart = document.getElementById('tr_month_' + sMonthId).rowIndex + 1; + var sYearId = sMonthId.substr(0, 4); + + // Within the current months, check out all the days. + var aDayNodes = aMonthNodes[iMonthIndex].getElementsByTagName('day'); + for (var iDayIndex = 0, iNumDays = aDayNodes.length; iDayIndex < iNumDays; iDayIndex++) + { + var oCurRow = this.oTable.insertRow(iStart + iDayIndex); + oCurRow.className = this.opt.sDayRowClassname; + oCurRow.id = this.opt.sDayRowIdPrefix + aDayNodes[iDayIndex].getAttribute('date'); + + for (var iCellIndex = 0, iNumCells = this.opt.aDataCells.length; iCellIndex < iNumCells; iCellIndex++) + { + var oCurCell = oCurRow.insertCell(-1); + + if (this.opt.aDataCells[iCellIndex] == 'date') + oCurCell.style.paddingLeft = '6ex'; + else + oCurCell.style.textAlign = 'center'; + + var sCurData = aDayNodes[iDayIndex].getAttribute(this.opt.aDataCells[iCellIndex]); + oCurCell.appendChild(document.createTextNode(sCurData)); + } + + // Add these day rows to the toggle objects in case of collapse. + this.oYears[sYearId].oMonths[sMonthId].oToggle.opt.aSwappableContainers[this.oYears[sYearId].oMonths[sMonthId].oToggle.opt.aSwappableContainers.length] = oCurRow.id; + this.oYears[sYearId].oToggle.opt.aSwappableContainers[this.oYears[sYearId].oToggle.opt.aSwappableContainers.length] = oCurRow.id; + } + } + + this.bIsLoading = false; + if (typeof(window.ajax_indicator) == 'function') + ajax_indicator(false); +} \ No newline at end of file diff --git a/Themes/default/scripts/suggest.js b/Themes/default/scripts/suggest.js new file mode 100644 index 0000000..feb7c0e --- /dev/null +++ b/Themes/default/scripts/suggest.js @@ -0,0 +1,630 @@ +// This file contains javascript associated with a autosuggest control +function smc_AutoSuggest(oOptions) +{ + this.opt = oOptions; + + // Store the handle to the text box. + this.oTextHandle = document.getElementById(this.opt.sControlId); + this.oRealTextHandle = null; + + this.oSuggestDivHandle = null; + this.sLastSearch = ''; + this.sLastDirtySearch = ''; + this.oSelectedDiv = null; + this.aCache = []; + this.aDisplayData = []; + + this.sRetrieveURL = 'sRetrieveURL' in this.opt ? this.opt.sRetrieveURL : '%scripturl%action=suggest;suggest_type=%suggest_type%;search=%search%;%sessionVar%=%sessionID%;xml;time=%time%'; + + // How many objects can we show at once? + this.iMaxDisplayQuantity = 'iMaxDisplayQuantity' in this.opt ? this.opt.iMaxDisplayQuantity : 15; + + // How many characters shall we start searching on? + this.iMinimumSearchChars = 'iMinimumSearchChars' in this.opt ? this.opt.iMinimumSearchChars : 3; + + // Should selected items be added to a list? + this.bItemList = 'bItemList' in this.opt ? this.opt.bItemList : false; + + // Are there any items that should be added in advance? + this.aListItems = 'aListItems' in this.opt ? this.opt.aListItems : []; + + this.sItemTemplate = 'sItemTemplate' in this.opt ? this.opt.sItemTemplate : '%item_name% %delete_text%'; + + this.sTextDeleteItem = 'sTextDeleteItem' in this.opt ? this.opt.sTextDeleteItem : ''; + + this.oCallback = {}; + this.bDoAutoAdd = false; + this.iItemCount = 0; + + this.oHideTimer = null; + this.bPositionComplete = false; + + this.oXmlRequestHandle = null; + + // Just make sure the page is loaded before calling the init. + addLoadEvent(this.opt.sSelf + '.init();'); +} + +smc_AutoSuggest.prototype.init = function() +{ + if (!window.XMLHttpRequest) + return false; + + // Create a div that'll contain the results later on. + this.oSuggestDivHandle = document.createElement('div'); + this.oSuggestDivHandle.className = 'auto_suggest_div'; + document.body.appendChild(this.oSuggestDivHandle); + + // Create a backup text input. + this.oRealTextHandle = document.createElement('input'); + this.oRealTextHandle.type = 'hidden'; + this.oRealTextHandle.name = this.oTextHandle.name; + this.oRealTextHandle.value = this.oTextHandle.value; + this.oTextHandle.form.appendChild(this.oRealTextHandle); + + // Disable autocomplete in any browser by obfuscating the name. + this.oTextHandle.name = 'dummy_' + Math.floor(Math.random() * 1000000); + this.oTextHandle.autocomplete = 'off'; + + this.oTextHandle.instanceRef = this; + + var fOnKeyDown = function (oEvent) { + return this.instanceRef.handleKey(oEvent); + }; + is_opera ? this.oTextHandle.onkeypress = fOnKeyDown : this.oTextHandle.onkeydown = fOnKeyDown; + + this.oTextHandle.onkeyup = function (oEvent) { + return this.instanceRef.autoSuggestUpdate(oEvent); + }; + + this.oTextHandle.onchange = function (oEvent) { + return this.instanceRef.autoSuggestUpdate(oEvent); + }; + + this.oTextHandle.onblur = function (oEvent) { + return this.instanceRef.autoSuggestHide(oEvent); + }; + + this.oTextHandle.onfocus = function (oEvent) { + return this.instanceRef.autoSuggestUpdate(oEvent); + }; + + if (this.bItemList) + { + if ('sItemListContainerId' in this.opt) + this.oItemList = document.getElementById(this.opt.sItemListContainerId); + else + { + this.oItemList = document.createElement('div'); + this.oTextHandle.parentNode.insertBefore(this.oItemList, this.oTextHandle.nextSibling); + } + } + + if (this.aListItems.length > 0) + for (var i = 0, n = this.aListItems.length; i < n; i++) + this.addItemLink(this.aListItems[i].sItemId, this.aListItems[i].sItemName); + + return true; +} + +// Was it an enter key - if so assume they are trying to select something. +smc_AutoSuggest.prototype.handleKey = function(oEvent) +{ + // Grab the event object, one way or the other + if (!oEvent) + oEvent = window.event; + + // Get the keycode of the key that was pressed. + var iKeyPress = 0; + if ('keyCode' in oEvent) + iKeyPress = oEvent.keyCode; + else if ('which' in oEvent) + iKeyPress = oEvent.which; + + switch (iKeyPress) + { + // Tab. + case 9: + if (this.aDisplayData.length > 0) + { + if (this.oSelectedDiv != null) + this.itemClicked(this.oSelectedDiv); + else + this.handleSubmit(); + } + + // Continue to the next control. + return true; + break; + + // Enter. + case 13: + if (this.aDisplayData.length > 0 && this.oSelectedDiv != null) + { + this.itemClicked(this.oSelectedDiv); + + // Do our best to stop it submitting the form! + return false; + } + else + return true; + + break; + + // Up/Down arrow? + case 38: + case 40: + if (this.aDisplayData.length && this.oSuggestDivHandle.style.visibility != 'hidden') + { + // Loop through the display data trying to find our entry. + var bPrevHandle = false; + var oToHighlight = null; + for (var i = 0; i < this.aDisplayData.length; i++) + { + // If we're going up and yet the top one was already selected don't go around. + if (this.oSelectedDiv != null && this.oSelectedDiv == this.aDisplayData[i] && i == 0 && iKeyPress == 38) + { + oToHighlight = this.oSelectedDiv; + break; + } + // If nothing is selected and we are going down then we select the first one. + if (this.oSelectedDiv == null && iKeyPress == 40) + { + oToHighlight = this.aDisplayData[i]; + break; + } + + // If the previous handle was the actual previously selected one and we're hitting down then this is the one we want. + if (bPrevHandle != false && bPrevHandle == this.oSelectedDiv && iKeyPress == 40) + { + oToHighlight = this.aDisplayData[i]; + break; + } + // If we're going up and this is the previously selected one then we want the one before, if there was one. + if (bPrevHandle != false && this.aDisplayData[i] == this.oSelectedDiv && iKeyPress == 38) + { + oToHighlight = bPrevHandle; + break; + } + // Make the previous handle this! + bPrevHandle = this.aDisplayData[i]; + } + + // If we don't have one to highlight by now then it must be the last one that we're after. + if (oToHighlight == null) + oToHighlight = bPrevHandle; + + // Remove any old highlighting. + if (this.oSelectedDiv != null) + this.itemMouseOut(this.oSelectedDiv); + // Mark what the selected div now is. + this.oSelectedDiv = oToHighlight; + this.itemMouseOver(this.oSelectedDiv); + } + break; + } + return true; +} + +// Functions for integration. +smc_AutoSuggest.prototype.registerCallback = function(sCallbackType, sCallback) +{ + switch (sCallbackType) + { + case 'onBeforeAddItem': + this.oCallback.onBeforeAddItem = sCallback; + break; + + case 'onAfterAddItem': + this.oCallback.onAfterAddItem = sCallback; + break; + + case 'onAfterDeleteItem': + this.oCallback.onAfterDeleteItem = sCallback; + break; + + case 'onBeforeUpdate': + this.oCallback.onBeforeUpdate = sCallback; + break; + } +} + +// User hit submit? +smc_AutoSuggest.prototype.handleSubmit = function() +{ + var bReturnValue = true; + var oFoundEntry = null; + + // Do we have something that matches the current text? + for (var i = 0; i < this.aCache.length; i++) + { + if (this.sLastSearch.toLowerCase() == this.aCache[i].sItemName.toLowerCase().substr(0, this.sLastSearch.length)) + { + // Exact match? + if (this.sLastSearch.length == this.aCache[i].sItemName.length) + { + // This is the one! + oFoundEntry = { + sItemId: this.aCache[i].sItemId, + sItemName: this.aCache[i].sItemName + }; + break; + } + + // Not an exact match, but it'll do for now. + else + { + // If we have two matches don't find anything. + if (oFoundEntry != null) + bReturnValue = false; + else + oFoundEntry = { + sItemId: this.aCache[i].sItemId, + sItemName: this.aCache[i].sItemName + }; + } + } + } + + if (oFoundEntry == null || bReturnValue == false) + return bReturnValue; + else + { + this.addItemLink(oFoundEntry.sItemId, oFoundEntry.sItemName, true); + return false; + } +} + +// Positions the box correctly on the window. +smc_AutoSuggest.prototype.positionDiv = function() +{ + // Only do it once. + if (this.bPositionComplete) + return true; + + this.bPositionComplete = true; + + // Put the div under the text box. + var aParentPos = smf_itemPos(this.oTextHandle); + + this.oSuggestDivHandle.style.left = aParentPos[0] + 'px'; + this.oSuggestDivHandle.style.top = (aParentPos[1] + this.oTextHandle.offsetHeight) + 'px'; + this.oSuggestDivHandle.style.width = this.oTextHandle.style.width; + + return true; +} + +// Do something after clicking an item. +smc_AutoSuggest.prototype.itemClicked = function(oCurElement) +{ + // Is there a div that we are populating? + if (this.bItemList) + this.addItemLink(oCurElement.sItemId, oCurElement.innerHTML); + + // Otherwise clear things down. + else + this.oTextHandle.value = oCurElement.innerHTML.php_unhtmlspecialchars(); + + this.oRealTextHandle.value = this.oTextHandle.value; + this.autoSuggestActualHide(); + this.oSelectedDiv = null; +} + +// Remove the last searched for name from the search box. +smc_AutoSuggest.prototype.removeLastSearchString = function () +{ + // Remove the text we searched for from the div. + var sTempText = this.oTextHandle.value.toLowerCase(); + var iStartString = sTempText.indexOf(this.sLastSearch.toLowerCase()); + // Just attempt to remove the bits we just searched for. + if (iStartString != -1) + { + while (iStartString > 0) + { + if (sTempText.charAt(iStartString - 1) == '"' || sTempText.charAt(iStartString - 1) == ',' || sTempText.charAt(iStartString - 1) == ' ') + { + iStartString--; + if (sTempText.charAt(iStartString - 1) == ',') + break; + } + else + break; + } + + // Now remove anything from iStartString upwards. + this.oTextHandle.value = this.oTextHandle.value.substr(0, iStartString); + } + // Just take it all. + else + this.oTextHandle.value = ''; +} + +// Add a result if not already done. +smc_AutoSuggest.prototype.addItemLink = function (sItemId, sItemName, bFromSubmit) +{ + // Increase the internal item count. + this.iItemCount ++; + + // If there's a callback then call it. + if ('oCallback' in this && 'onBeforeAddItem' in this.oCallback && typeof(this.oCallback.onBeforeAddItem) == 'string') + { + // If it returns false the item must not be added. + if (!eval(this.oCallback.onBeforeAddItem + '(' + this.opt.sSelf + ', \'' + sItemId + '\');')) + return; + } + + var oNewDiv = document.createElement('div'); + oNewDiv.id = 'suggest_' + this.opt.sSuggestId + '_' + sItemId; + setInnerHTML(oNewDiv, this.sItemTemplate.replace(/%post_name%/g, this.opt.sPostName).replace(/%item_id%/g, sItemId).replace(/%item_href%/g, smf_prepareScriptUrl(smf_scripturl) + this.opt.sURLMask.replace(/%item_id%/g, sItemId)).replace(/%item_name%/g, sItemName).replace(/%images_url%/g, smf_images_url).replace(/%self%/g, this.opt.sSelf).replace(/%delete_text%/g, this.sTextDeleteItem)); + this.oItemList.appendChild(oNewDiv); + + // If there's a registered callback, call it. + if ('oCallback' in this && 'onAfterAddItem' in this.oCallback && typeof(this.oCallback.onAfterAddItem) == 'string') + eval(this.oCallback.onAfterAddItem + '(' + this.opt.sSelf + ', \'' + oNewDiv.id + '\', ' + this.iItemCount + ');'); + + // Clear the div a bit. + this.removeLastSearchString(); + + // If we came from a submit, and there's still more to go, turn on auto add for all the other things. + this.bDoAutoAdd = this.oTextHandle.value != '' && bFromSubmit; + + // Update the fellow.. + this.autoSuggestUpdate(); +} + +// Delete an item that has been added, if at all? +smc_AutoSuggest.prototype.deleteAddedItem = function (sItemId) +{ + var oDiv = document.getElementById('suggest_' + this.opt.sSuggestId + '_' + sItemId); + + // Remove the div if it exists. + if (typeof(oDiv) == 'object' && oDiv != null) + { + oDiv.parentNode.removeChild(document.getElementById('suggest_' + this.opt.sSuggestId + '_' + sItemId)); + + // Decrease the internal item count. + this.iItemCount --; + + // If there's a registered callback, call it. + if ('oCallback' in this && 'onAfterDeleteItem' in this.oCallback && typeof(this.oCallback.onAfterDeleteItem) == 'string') + eval(this.oCallback.onAfterDeleteItem + '(' + this.opt.sSelf + ', ' + this.iItemCount + ');'); + } + + return false; +} + +// Hide the box. +smc_AutoSuggest.prototype.autoSuggestHide = function () +{ + // Delay to allow events to propogate through.... + this.oHideTimer = setTimeout(this.opt.sSelf + '.autoSuggestActualHide();', 250); +} + +// Do the actual hiding after a timeout. +smc_AutoSuggest.prototype.autoSuggestActualHide = function() +{ + this.oSuggestDivHandle.style.display = 'none'; + this.oSuggestDivHandle.style.visibility = 'hidden'; + this.oSelectedDiv = null; +} + +// Show the box. +smc_AutoSuggest.prototype.autoSuggestShow = function() +{ + if (this.oHideTimer) + { + clearTimeout(this.oHideTimer); + this.oHideTimer = false; + } + + this.positionDiv(); + + this.oSuggestDivHandle.style.visibility = 'visible'; + this.oSuggestDivHandle.style.display = ''; +} + +// Populate the actual div. +smc_AutoSuggest.prototype.populateDiv = function(aResults) +{ + // Cannot have any children yet. + while (this.oSuggestDivHandle.childNodes.length > 0) + { + // Tidy up the events etc too. + this.oSuggestDivHandle.childNodes[0].onmouseover = null; + this.oSuggestDivHandle.childNodes[0].onmouseout = null; + this.oSuggestDivHandle.childNodes[0].onclick = null; + + this.oSuggestDivHandle.removeChild(this.oSuggestDivHandle.childNodes[0]); + } + + // Something to display? + if (typeof(aResults) == 'undefined') + { + this.aDisplayData = []; + return false; + } + + var aNewDisplayData = []; + for (var i = 0; i < (aResults.length > this.iMaxDisplayQuantity ? this.iMaxDisplayQuantity : aResults.length); i++) + { + // Create the sub element + var oNewDivHandle = document.createElement('div'); + oNewDivHandle.sItemId = aResults[i].sItemId; + oNewDivHandle.className = 'auto_suggest_item'; + oNewDivHandle.innerHTML = aResults[i].sItemName; + //oNewDivHandle.style.width = this.oTextHandle.style.width; + + this.oSuggestDivHandle.appendChild(oNewDivHandle); + + // Attach some events to it so we can do stuff. + oNewDivHandle.instanceRef = this; + oNewDivHandle.onmouseover = function (oEvent) + { + this.instanceRef.itemMouseOver(this); + } + oNewDivHandle.onmouseout = function (oEvent) + { + this.instanceRef.itemMouseOut(this); + } + oNewDivHandle.onclick = function (oEvent) + { + this.instanceRef.itemClicked(this); + } + + + aNewDisplayData[i] = oNewDivHandle; + } + + this.aDisplayData = aNewDisplayData; + + return true; +} + +// Refocus the element. +smc_AutoSuggest.prototype.itemMouseOver = function (oCurElement) +{ + this.oSelectedDiv = oCurElement; + oCurElement.className = 'auto_suggest_item_hover'; +} + +// Onfocus the element +smc_AutoSuggest.prototype.itemMouseOut = function (oCurElement) +{ + oCurElement.className = 'auto_suggest_item'; +} + +smc_AutoSuggest.prototype.onSuggestionReceived = function (oXMLDoc) +{ + var sQuoteText = ''; + var aItems = oXMLDoc.getElementsByTagName('item'); + this.aCache = []; + for (var i = 0; i < aItems.length; i++) + { + this.aCache[i] = { + sItemId: aItems[i].getAttribute('id'), + sItemName: aItems[i].childNodes[0].nodeValue + }; + + // If we're doing auto add and we find the exact person, then add them! + if (this.bDoAutoAdd && this.sLastSearch == this.aCache[i].sItemName) + { + var oReturnValue = { + sItemId: this.aCache[i].sItemId, + sItemName: this.aCache[i].sItemName + }; + this.aCache = []; + return this.addItemLink(oReturnValue.sItemId, oReturnValue.sItemName, true); + } + } + + // Check we don't try to keep auto updating! + this.bDoAutoAdd = false; + + // Populate the div. + this.populateDiv(this.aCache); + + // Make sure we can see it - if we can. + if (aItems.length == 0) + this.autoSuggestHide(); + else + this.autoSuggestShow(); + + return true; +} + +// Get a new suggestion. +smc_AutoSuggest.prototype.autoSuggestUpdate = function () +{ + // If there's a callback then call it. + if ('onBeforeUpdate' in this.oCallback && typeof(this.oCallback.onBeforeUpdate) == 'string') + { + // If it returns false the item must not be added. + if (!eval(this.oCallback.onBeforeUpdate + '(' + this.opt.sSelf + ');')) + return false; + } + + this.oRealTextHandle.value = this.oTextHandle.value; + + if (isEmptyText(this.oTextHandle)) + { + this.aCache = []; + + this.populateDiv(); + + this.autoSuggestHide(); + + return true; + } + + // Nothing changed? + if (this.oTextHandle.value == this.sLastDirtySearch) + return true; + this.sLastDirtySearch = this.oTextHandle.value; + + // We're only actually interested in the last string. + var sSearchString = this.oTextHandle.value.replace(/^("[^"]+",[ ]*)+/, '').replace(/^([^,]+,[ ]*)+/, ''); + if (sSearchString.substr(0, 1) == '"') + sSearchString = sSearchString.substr(1); + + // Stop replication ASAP. + var sRealLastSearch = this.sLastSearch; + this.sLastSearch = sSearchString; + + // Either nothing or we've completed a sentance. + if (sSearchString == '' || sSearchString.substr(sSearchString.length - 1) == '"') + { + this.populateDiv(); + return true; + } + + // Nothing? + if (sRealLastSearch == sSearchString) + return true; + + // Too small? + else if (sSearchString.length < this.iMinimumSearchChars) + { + this.aCache = []; + this.autoSuggestHide(); + return true; + } + else if (sSearchString.substr(0, sRealLastSearch.length) == sRealLastSearch) + { + // Instead of hitting the server again, just narrow down the results... + var aNewCache = []; + var j = 0; + var sLowercaseSearch = sSearchString.toLowerCase(); + for (var k = 0; k < this.aCache.length; k++) + { + if (this.aCache[k].sItemName.substr(0, sSearchString.length).toLowerCase() == sLowercaseSearch) + aNewCache[j++] = this.aCache[k]; + } + + this.aCache = []; + if (aNewCache.length != 0) + { + this.aCache = aNewCache; + // Repopulate. + this.populateDiv(this.aCache); + + // Check it can be seen. + this.autoSuggestShow(); + + return true; + } + } + + // In progress means destroy! + if (typeof(this.oXmlRequestHandle) == 'object' && this.oXmlRequestHandle != null) + this.oXmlRequestHandle.abort(); + + // Clean the text handle. + sSearchString = sSearchString.php_to8bit().php_urlencode(); + + // Get the document. + this.tmpMethod = getXMLDocument; + this.oXmlRequestHandle = this.tmpMethod(this.sRetrieveURL.replace(/%scripturl%/g, smf_prepareScriptUrl(smf_scripturl)).replace(/%suggest_type%/g, this.opt.sSearchType).replace(/%search%/g, sSearchString).replace(/%sessionVar%/g, this.opt.sSessionVar).replace(/%sessionID%/g, this.opt.sSessionId).replace(/%time%/g, new Date().getTime()), this.onSuggestionReceived); + delete this.tmpMethod; + + return true; +} \ No newline at end of file diff --git a/Themes/default/scripts/theme.js b/Themes/default/scripts/theme.js new file mode 100644 index 0000000..f9eb8c6 --- /dev/null +++ b/Themes/default/scripts/theme.js @@ -0,0 +1,96 @@ +// The purpose of this code is to fix the height of overflow: auto blocks, because some browsers can't figure it out for themselves. +function smf_codeBoxFix() +{ + var codeFix = document.getElementsByTagName('code'); + for (var i = codeFix.length - 1; i >= 0; i--) + { + if (is_webkit && codeFix[i].offsetHeight < 20) + codeFix[i].style.height = (codeFix[i].offsetHeight + 20) + 'px'; + + else if (is_ff && (codeFix[i].scrollWidth > codeFix[i].clientWidth || codeFix[i].clientWidth == 0)) + codeFix[i].style.overflow = 'scroll'; + + else if ('currentStyle' in codeFix[i] && codeFix[i].currentStyle.overflow == 'auto' && (codeFix[i].currentStyle.height == '' || codeFix[i].currentStyle.height == 'auto') && (codeFix[i].scrollWidth > codeFix[i].clientWidth || codeFix[i].clientWidth == 0) && (codeFix[i].offsetHeight != 0)) + codeFix[i].style.height = (codeFix[i].offsetHeight + 24) + 'px'; + } +} + +// Add a fix for code stuff? +if ((is_ie && !is_ie4) || is_webkit || is_ff) + addLoadEvent(smf_codeBoxFix); + +// Toggles the element height and width styles of an image. +function smc_toggleImageDimensions() +{ + var oImages = document.getElementsByTagName('IMG'); + for (oImage in oImages) + { + // Not a resized image? Skip it. + if (oImages[oImage].className == undefined || oImages[oImage].className.indexOf('bbc_img resized') == -1) + continue; + + oImages[oImage].style.cursor = 'pointer'; + oImages[oImage].onclick = function() { + this.style.width = this.style.height = this.style.width == 'auto' ? null : 'auto'; + }; + } +} + +// Add a load event for the function above. +addLoadEvent(smc_toggleImageDimensions); + +// Adds a button to a certain button strip. +function smf_addButton(sButtonStripId, bUseImage, oOptions) +{ + var oButtonStrip = document.getElementById(sButtonStripId); + var aItems = oButtonStrip.getElementsByTagName('span'); + + // Remove the 'last' class from the last item. + if (aItems.length > 0) + { + var oLastSpan = aItems[aItems.length - 1]; + oLastSpan.className = oLastSpan.className.replace(/\s*last/, 'position_holder'); + } + + // Add the button. + var oButtonStripList = oButtonStrip.getElementsByTagName('ul')[0]; + var oNewButton = document.createElement('li'); + setInnerHTML(oNewButton, '' + oOptions.sText + ''); + + oButtonStripList.appendChild(oNewButton); +} + +// Adds hover events to list items. Used for a versions of IE that don't support this by default. +var smf_addListItemHoverEvents = function() +{ + var cssRule, newSelector; + + // Add a rule for the list item hover event to every stylesheet. + for (var iStyleSheet = 0; iStyleSheet < document.styleSheets.length; iStyleSheet ++) + for (var iRule = 0; iRule < document.styleSheets[iStyleSheet].rules.length; iRule ++) + { + oCssRule = document.styleSheets[iStyleSheet].rules[iRule]; + if (oCssRule.selectorText.indexOf('LI:hover') != -1) + { + sNewSelector = oCssRule.selectorText.replace(/LI:hover/gi, 'LI.iehover'); + document.styleSheets[iStyleSheet].addRule(sNewSelector, oCssRule.style.cssText); + } + } + + // Now add handling for these hover events. + var oListItems = document.getElementsByTagName('LI'); + for (oListItem in oListItems) + { + oListItems[oListItem].onmouseover = function() { + this.className += ' iehover'; + }; + + oListItems[oListItem].onmouseout = function() { + this.className = this.className.replace(new RegExp(' iehover\\b'), ''); + }; + } +} + +// Add hover events to list items if the browser requires it. +if (is_ie7down && 'attachEvent' in window) + window.attachEvent('onload', smf_addListItemHoverEvents); diff --git a/Themes/default/scripts/topic.js b/Themes/default/scripts/topic.js new file mode 100644 index 0000000..6efed38 --- /dev/null +++ b/Themes/default/scripts/topic.js @@ -0,0 +1,541 @@ +var cur_topic_id, cur_msg_id, buff_subject, cur_subject_div, in_edit_mode = 0; +var hide_prefixes = Array(); + +function modify_topic(topic_id, first_msg_id) +{ + if (!('XMLHttpRequest' in window)) + return; + + if ('opera' in window) + { + var oTest = new XMLHttpRequest(); + if (!('setRequestHeader' in oTest)) + return; + } + + // Add backwards compatibility with old themes. + if (typeof(cur_session_var) == 'undefined') + cur_session_var = 'sesc'; + + if (in_edit_mode == 1) + { + if (cur_topic_id == topic_id) + return; + else + modify_topic_cancel(); + } + + in_edit_mode = 1; + mouse_on_div = 1; + cur_topic_id = topic_id; + + if (typeof window.ajax_indicator == "function") + ajax_indicator(true); + getXMLDocument(smf_prepareScriptUrl(smf_scripturl) + "action=quotefast;quote=" + first_msg_id + ";modify;xml", onDocReceived_modify_topic); +} + +function onDocReceived_modify_topic(XMLDoc) +{ + cur_msg_id = XMLDoc.getElementsByTagName("message")[0].getAttribute("id"); + + cur_subject_div = document.getElementById('msg_' + cur_msg_id.substr(4)); + buff_subject = getInnerHTML(cur_subject_div); + + // Here we hide any other things they want hiding on edit. + set_hidden_topic_areas('none'); + + modify_topic_show_edit(XMLDoc.getElementsByTagName("subject")[0].childNodes[0].nodeValue); + if (typeof window.ajax_indicator == "function") + ajax_indicator(false); +} + +function modify_topic_cancel() +{ + setInnerHTML(cur_subject_div, buff_subject); + set_hidden_topic_areas(''); + + in_edit_mode = 0; + return false; +} + +function modify_topic_save(cur_session_id, cur_session_var) +{ + if (!in_edit_mode) + return true; + + // Add backwards compatibility with old themes. + if (typeof(cur_session_var) == 'undefined') + cur_session_var = 'sesc'; + + var i, x = new Array(); + x[x.length] = 'subject=' + document.forms.quickModForm['subject'].value.replace(/&#/g, "&#").php_to8bit().php_urlencode(); + x[x.length] = 'topic=' + parseInt(document.forms.quickModForm.elements['topic'].value); + x[x.length] = 'msg=' + parseInt(document.forms.quickModForm.elements['msg'].value); + + if (typeof window.ajax_indicator == "function") + ajax_indicator(true); + sendXMLDocument(smf_prepareScriptUrl(smf_scripturl) + "action=jsmodify;topic=" + parseInt(document.forms.quickModForm.elements['topic'].value) + ";" + cur_session_var + "=" + cur_session_id + ";xml", x.join("&"), modify_topic_done); + + return false; +} + +function modify_topic_done(XMLDoc) +{ + if (!XMLDoc) + { + modify_topic_cancel(); + return true; + } + + var message = XMLDoc.getElementsByTagName("smf")[0].getElementsByTagName("message")[0]; + var subject = message.getElementsByTagName("subject")[0]; + var error = message.getElementsByTagName("error")[0]; + + if (typeof window.ajax_indicator == "function") + ajax_indicator(false); + + if (!subject || error) + return false; + + subjectText = subject.childNodes[0].nodeValue; + + modify_topic_hide_edit(subjectText); + + set_hidden_topic_areas(''); + + in_edit_mode = 0; + + return false; +} + +// Simply restore any hidden bits during topic editing. +function set_hidden_topic_areas(set_style) +{ + for (var i = 0; i < hide_prefixes.length; i++) + { + if (document.getElementById(hide_prefixes[i] + cur_msg_id.substr(4)) != null) + document.getElementById(hide_prefixes[i] + cur_msg_id.substr(4)).style.display = set_style; + } +} + +// *** QuickReply object. +function QuickReply(oOptions) +{ + this.opt = oOptions; + this.bCollapsed = this.opt.bDefaultCollapsed; +} + +// When a user presses quote, put it in the quick reply box (if expanded). +QuickReply.prototype.quote = function (iMessageId, xDeprecated) +{ + // Compatibility with older templates. + if (typeof(xDeprecated) != 'undefined') + return true; + + if (this.bCollapsed) + { + window.location.href = smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=post;quote=' + iMessageId + ';topic=' + this.opt.iTopicId + '.' + this.opt.iStart; + return false; + } + else + { + // Doing it the XMLhttp way? + if (window.XMLHttpRequest) + { + ajax_indicator(true); + getXMLDocument(smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=quotefast;quote=' + iMessageId + ';xml', this.onQuoteReceived); + } + // Or with a smart popup! + else + reqWin(smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=quotefast;quote=' + iMessageId, 240, 90); + + // Move the view to the quick reply box. + if (navigator.appName == 'Microsoft Internet Explorer') + window.location.hash = this.opt.sJumpAnchor; + else + window.location.hash = '#' + this.opt.sJumpAnchor; + + return false; + } +} + +// This is the callback function used after the XMLhttp request. +QuickReply.prototype.onQuoteReceived = function (oXMLDoc) +{ + var sQuoteText = ''; + + for (var i = 0; i < oXMLDoc.getElementsByTagName('quote')[0].childNodes.length; i++) + sQuoteText += oXMLDoc.getElementsByTagName('quote')[0].childNodes[i].nodeValue; + + replaceText(sQuoteText, document.forms.postmodify.message); + + ajax_indicator(false); +} + +// The function handling the swapping of the quick reply. +QuickReply.prototype.swap = function () +{ + document.getElementById(this.opt.sImageId).src = this.opt.sImagesUrl + "/" + (this.bCollapsed ? this.opt.sImageCollapsed : this.opt.sImageExpanded); + document.getElementById(this.opt.sContainerId).style.display = this.bCollapsed ? '' : 'none'; + + this.bCollapsed = !this.bCollapsed; +} + +// *** QuickModify object. +function QuickModify(oOptions) +{ + this.opt = oOptions; + this.bInEditMode = false; + this.sCurMessageId = ''; + this.oCurMessageDiv = null; + this.oCurSubjectDiv = null; + this.sMessageBuffer = ''; + this.sSubjectBuffer = ''; + this.bXmlHttpCapable = this.isXmlHttpCapable(); + + // Show the edit buttons + if (this.bXmlHttpCapable) + { + for (var i = document.images.length - 1; i >= 0; i--) + if (document.images[i].id.substr(0, 14) == 'modify_button_') + document.images[i].style.display = ''; + } +} + +// Determine whether the quick modify can actually be used. +QuickModify.prototype.isXmlHttpCapable = function () +{ + if (typeof(window.XMLHttpRequest) == 'undefined') + return false; + + // Opera didn't always support POST requests. So test it first. + if ('opera' in window) + { + var oTest = new XMLHttpRequest(); + if (!('setRequestHeader' in oTest)) + return false; + } + + return true; +} + +// Function called when a user presses the edit button. +QuickModify.prototype.modifyMsg = function (iMessageId) +{ + if (!this.bXmlHttpCapable) + return; + + // Add backwards compatibility with old themes. + if (typeof(sSessionVar) == 'undefined') + sSessionVar = 'sesc'; + + // First cancel if there's another message still being edited. + if (this.bInEditMode) + this.modifyCancel(); + + // At least NOW we're in edit mode + this.bInEditMode = true; + + // Send out the XMLhttp request to get more info + ajax_indicator(true); + + // For IE 5.0 support, 'call' is not yet used. + this.tmpMethod = getXMLDocument; + this.tmpMethod(smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=quotefast;quote=' + iMessageId + ';modify;xml', this.onMessageReceived); + delete this.tmpMethod; +} + +// The callback function used for the XMLhttp request retrieving the message. +QuickModify.prototype.onMessageReceived = function (XMLDoc) +{ + var sBodyText = '', sSubjectText = ''; + + // No longer show the 'loading...' sign. + ajax_indicator(false); + + // Grab the message ID. + this.sCurMessageId = XMLDoc.getElementsByTagName('message')[0].getAttribute('id'); + + // If this is not valid then simply give up. + if (!document.getElementById(this.sCurMessageId)) + return this.modifyCancel(); + + // Replace the body part. + for (var i = 0; i < XMLDoc.getElementsByTagName("message")[0].childNodes.length; i++) + sBodyText += XMLDoc.getElementsByTagName("message")[0].childNodes[i].nodeValue; + this.oCurMessageDiv = document.getElementById(this.sCurMessageId); + this.sMessageBuffer = getInnerHTML(this.oCurMessageDiv); + + // We have to force the body to lose its dollar signs thanks to IE. + sBodyText = sBodyText.replace(/\$/g, '{&dollarfix;$}'); + + // Actually create the content, with a bodge for disappearing dollar signs. + setInnerHTML(this.oCurMessageDiv, this.opt.sTemplateBodyEdit.replace(/%msg_id%/g, this.sCurMessageId.substr(4)).replace(/%body%/, sBodyText).replace(/\{&dollarfix;\$\}/g, '$')); + + // Replace the subject part. + this.oCurSubjectDiv = document.getElementById('subject_' + this.sCurMessageId.substr(4)); + this.sSubjectBuffer = getInnerHTML(this.oCurSubjectDiv); + + sSubjectText = XMLDoc.getElementsByTagName('subject')[0].childNodes[0].nodeValue.replace(/\$/g, '{&dollarfix;$}'); + setInnerHTML(this.oCurSubjectDiv, this.opt.sTemplateSubjectEdit.replace(/%subject%/, sSubjectText).replace(/\{&dollarfix;\$\}/g, '$')); + + return true; +} + +// Function in case the user presses cancel (or other circumstances cause it). +QuickModify.prototype.modifyCancel = function () +{ + // Roll back the HTML to its original state. + if (this.oCurMessageDiv) + { + setInnerHTML(this.oCurMessageDiv, this.sMessageBuffer); + setInnerHTML(this.oCurSubjectDiv, this.sSubjectBuffer); + } + + // No longer in edit mode, that's right. + this.bInEditMode = false; + + return false; +} + +// The function called after a user wants to save his precious message. +QuickModify.prototype.modifySave = function (sSessionId, sSessionVar) +{ + // We cannot save if we weren't in edit mode. + if (!this.bInEditMode) + return true; + + // Add backwards compatibility with old themes. + if (typeof(sSessionVar) == 'undefined') + sSessionVar = 'sesc'; + + var i, x = new Array(); + x[x.length] = 'subject=' + escape(document.forms.quickModForm['subject'].value.replace(/&#/g, "&#").php_to8bit()).replace(/\+/g, "%2B"); + x[x.length] = 'message=' + escape(document.forms.quickModForm['message'].value.replace(/&#/g, "&#").php_to8bit()).replace(/\+/g, "%2B"); + x[x.length] = 'topic=' + parseInt(document.forms.quickModForm.elements['topic'].value); + x[x.length] = 'msg=' + parseInt(document.forms.quickModForm.elements['msg'].value); + + // Send in the XMLhttp request and let's hope for the best. + ajax_indicator(true); + sendXMLDocument.call(this, smf_prepareScriptUrl(this.opt.sScriptUrl) + "action=jsmodify;topic=" + this.opt.iTopicId + ";" + sSessionVar + "=" + sSessionId + ";xml", x.join("&"), this.onModifyDone); + + return false; +} + +// Callback function of the XMLhttp request sending the modified message. +QuickModify.prototype.onModifyDone = function (XMLDoc) +{ + // We've finished the loading stuff. + ajax_indicator(false); + + // If we didn't get a valid document, just cancel. + if (!XMLDoc || !XMLDoc.getElementsByTagName('smf')[0]) + { + // Mozilla will nicely tell us what's wrong. + if (XMLDoc.childNodes.length > 0 && XMLDoc.firstChild.nodeName == 'parsererror') + setInnerHTML(document.getElementById('error_box'), XMLDoc.firstChild.textContent); + else + this.modifyCancel(); + return; + } + + var message = XMLDoc.getElementsByTagName('smf')[0].getElementsByTagName('message')[0]; + var body = message.getElementsByTagName('body')[0]; + var error = message.getElementsByTagName('error')[0]; + + if (body) + { + // Show new body. + var bodyText = ''; + for (var i = 0; i < body.childNodes.length; i++) + bodyText += body.childNodes[i].nodeValue; + + this.sMessageBuffer = this.opt.sTemplateBodyNormal.replace(/%body%/, bodyText.replace(/\$/g, '{&dollarfix;$}')).replace(/\{&dollarfix;\$\}/g,'$'); + setInnerHTML(this.oCurMessageDiv, this.sMessageBuffer); + + // Show new subject. + var oSubject = message.getElementsByTagName('subject')[0]; + var sSubjectText = oSubject.childNodes[0].nodeValue.replace(/\$/g, '{&dollarfix;$}'); + this.sSubjectBuffer = this.opt.sTemplateSubjectNormal.replace(/%msg_id%/g, this.sCurMessageId.substr(4)).replace(/%subject%/, sSubjectText).replace(/\{&dollarfix;\$\}/g,'$'); + setInnerHTML(this.oCurSubjectDiv, this.sSubjectBuffer); + + // If this is the first message, also update the topic subject. + if (oSubject.getAttribute('is_first') == '1') + setInnerHTML(document.getElementById('top_subject'), this.opt.sTemplateTopSubject.replace(/%subject%/, sSubjectText).replace(/\{&dollarfix;\$\}/g, '$')); + + // Show this message as 'modified on x by y'. + if (this.opt.bShowModify) + setInnerHTML(document.getElementById('modified_' + this.sCurMessageId.substr(4)), message.getElementsByTagName('modified')[0].childNodes[0].nodeValue); + } + else if (error) + { + setInnerHTML(document.getElementById('error_box'), error.childNodes[0].nodeValue); + document.forms.quickModForm.message.style.border = error.getAttribute('in_body') == '1' ? this.opt.sErrorBorderStyle : ''; + document.forms.quickModForm.subject.style.border = error.getAttribute('in_subject') == '1' ? this.opt.sErrorBorderStyle : ''; + } +} + +function InTopicModeration(oOptions) +{ + this.opt = oOptions; + this.bButtonsShown = false; + this.iNumSelected = 0; + + // Add backwards compatibility with old themes. + if (typeof(this.opt.sSessionVar) == 'undefined') + this.opt.sSessionVar = 'sesc'; + + this.init(); +} + +InTopicModeration.prototype.init = function() +{ + // Add checkboxes to all the messages. + for (var i = 0, n = this.opt.aMessageIds.length; i < n; i++) + { + // Create the checkbox. + var oCheckbox = document.createElement('input'); + oCheckbox.type = 'checkbox'; + oCheckbox.className = 'input_check'; + oCheckbox.name = 'msgs[]'; + oCheckbox.value = this.opt.aMessageIds[i]; + oCheckbox.instanceRef = this; + oCheckbox.onclick = function () { + this.instanceRef.handleClick(this); + } + + // Append it to the container + var oCheckboxContainer = document.getElementById(this.opt.sCheckboxContainerMask + this.opt.aMessageIds[i]); + oCheckboxContainer.appendChild(oCheckbox); + oCheckboxContainer.style.display = ''; + } +} + +InTopicModeration.prototype.handleClick = function(oCheckbox) +{ + if (!this.bButtonsShown && this.opt.sButtonStripDisplay) + { + var oButtonStrip = document.getElementById(this.opt.sButtonStrip); + var oButtonStripDisplay = document.getElementById(this.opt.sButtonStripDisplay); + + // Make sure it can go somewhere. + if (typeof(oButtonStripDisplay) == 'object' && oButtonStripDisplay != null) + oButtonStripDisplay.style.display = ""; + else + { + var oNewDiv = document.createElement('div'); + var oNewList = document.createElement('ul'); + + oNewDiv.id = this.opt.sButtonStripDisplay; + oNewDiv.className = this.opt.sButtonStripClass ? this.opt.sButtonStripClass : 'buttonlist floatbottom'; + + oNewDiv.appendChild(oNewList); + oButtonStrip.appendChild(oNewDiv); + } + + // Add the 'remove selected items' button. + if (this.opt.bCanRemove) + smf_addButton(this.opt.sButtonStrip, this.opt.bUseImageButton, { + sId: this.opt.sSelf + '_remove_button', + sText: this.opt.sRemoveButtonLabel, + sImage: this.opt.sRemoveButtonImage, + sUrl: '#', + sCustom: ' onclick="return ' + this.opt.sSelf + '.handleSubmit(\'remove\')"' + }); + + // Add the 'restore selected items' button. + if (this.opt.bCanRestore) + smf_addButton(this.opt.sButtonStrip, this.opt.bUseImageButton, { + sId: this.opt.sSelf + '_restore_button', + sText: this.opt.sRestoreButtonLabel, + sImage: this.opt.sRestoreButtonImage, + sUrl: '#', + sCustom: ' onclick="return ' + this.opt.sSelf + '.handleSubmit(\'restore\')"' + }); + + // Adding these buttons once should be enough. + this.bButtonsShown = true; + } + + // Keep stats on how many items were selected. + this.iNumSelected += oCheckbox.checked ? 1 : -1; + + // Show the number of messages selected in the button. + if (this.opt.bCanRemove && !this.opt.bUseImageButton) + { + setInnerHTML(document.getElementById(this.opt.sSelf + '_remove_button'), this.opt.sRemoveButtonLabel + ' [' + this.iNumSelected + ']'); + document.getElementById(this.opt.sSelf + '_remove_button').style.display = this.iNumSelected < 1 ? "none" : ""; + } + + if (this.opt.bCanRestore && !this.opt.bUseImageButton) + { + setInnerHTML(document.getElementById(this.opt.sSelf + '_restore_button'), this.opt.sRestoreButtonLabel + ' [' + this.iNumSelected + ']'); + document.getElementById(this.opt.sSelf + '_restore_button').style.display = this.iNumSelected < 1 ? "none" : ""; + } + + // Try to restore the correct position. + var aItems = document.getElementById(this.opt.sButtonStrip).getElementsByTagName('span'); + if (aItems.length > 3) + { + if (this.iNumSelected < 1) + { + aItems[aItems.length - 3].className = aItems[aItems.length - 3].className.replace(/\s*position_holder/, 'last'); + aItems[aItems.length - 2].className = aItems[aItems.length - 2].className.replace(/\s*position_holder/, 'last'); + } + else + { + aItems[aItems.length - 2].className = aItems[aItems.length - 2].className.replace(/\s*last/, 'position_holder'); + aItems[aItems.length - 3].className = aItems[aItems.length - 3].className.replace(/\s*last/, 'position_holder'); + } + } +} + +InTopicModeration.prototype.handleSubmit = function (sSubmitType) +{ + var oForm = document.getElementById(this.opt.sFormId); + + // Make sure this form isn't submitted in another way than this function. + var oInput = document.createElement('input'); + oInput.type = 'hidden'; + oInput.name = this.opt.sSessionVar; + oInput.value = this.opt.sSessionId; + oForm.appendChild(oInput); + + switch (sSubmitType) + { + case 'remove': + if (!confirm(this.opt.sRemoveButtonConfirm)) + return false; + + oForm.action = oForm.action.replace(/;restore_selected=1/, ''); + break; + + case 'restore': + if (!confirm(this.opt.sRestoreButtonConfirm)) + return false; + + oForm.action = oForm.action + ';restore_selected=1'; + break; + + default: + return false; + break; + } + + oForm.submit(); + return true; +} + + +// *** Other functions... +function expandThumb(thumbID) +{ + var img = document.getElementById('thumb_' + thumbID); + var link = document.getElementById('link_' + thumbID); + var tmp = img.src; + img.src = link.href; + link.href = tmp; + img.style.width = ''; + img.style.height = ''; + return false; +} \ No newline at end of file diff --git a/Themes/index.php b/Themes/index.php new file mode 100644 index 0000000..2480ce0 --- /dev/null +++ b/Themes/index.php @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/agreement.txt b/agreement.txt new file mode 100644 index 0000000..668f15b --- /dev/null +++ b/agreement.txt @@ -0,0 +1,13 @@ +You agree, through your use of this forum, that you will not post any material which is false, defamatory, inaccurate, abusive, vulgar, hateful, harassing, obscene, profane, sexually oriented, threatening, invasive of a person's privacy, adult material, or otherwise in violation of any International or United States Federal law. You also agree not to post any copyrighted material unless you own the copyright or you have written consent from the owner of the copyrighted material. Spam, flooding, advertisements, chain letters, pyramid schemes, and solicitations are also forbidden on this forum. + +Note that it is impossible for the staff or the owners of this forum to confirm the validity of posts. Please remember that we do not actively monitor the posted messages, and as such, are not responsible for the content contained within. We do not warrant the accuracy, completeness, or usefulness of any information presented. The posted messages express the views of the author, and not necessarily the views of this forum, its staff, its subsidiaries, or this forum's owner. Anyone who feels that a posted message is objectionable is encouraged to notify an administrator or moderator of this forum immediately. The staff and the owner of this forum reserve the right to remove objectionable content, within a reasonable time frame, if they determine that removal is necessary. This is a manual process, however, please realize that they may not be able to remove or edit particular messages immediately. This policy applies to member profile information as well. + +You remain solely responsible for the content of your posted messages. Furthermore, you agree to indemnify and hold harmless the owners of this forum, any related websites to this forum, its staff, and its subsidiaries. The owners of this forum also reserve the right to reveal your identity (or any other related information collected on this service) in the event of a formal complaint or legal action arising from any situation caused by your use of this forum. + +You have the ability, as you register, to choose your username. We advise that you keep the name appropriate. With this user account you are about to register, you agree to never give your password out to another person except an administrator, for your protection and for validity reasons. You also agree to NEVER use another person's account for any reason. We also HIGHLY recommend you use a complex and unique password for your account, to prevent account theft. + +After you register and login to this forum, you will be able to fill out a detailed profile. It is your responsibility to present clean and accurate information. Any information the forum owner or staff determines to be inaccurate or vulgar in nature will be removed, with or without prior notice. Appropriate sanctions may be applicable. + +Please note that with each post, your IP address is recorded, in the event that you need to be banned from this forum or your ISP contacted. This will only happen in the event of a major violation of this agreement. + +Also note that the software places a cookie, a text file containing bits of information (such as your username and password), in your browser's cache. This is ONLY used to keep you logged in/out. The software does not collect or send any other form of information to your computer. \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..efe2757 --- /dev/null +++ b/index.php @@ -0,0 +1,362 @@ + array('Source-File.php', 'FunctionToCall'), + + Then, you can access the FunctionToCall() function from Source-File.php + with the URL index.php?action=action-in-url. Relatively simple, no? +*/ + +$forum_version = 'SMF 2.0.7'; + +// Get everything started up... +define('SMF', 1); +if (function_exists('set_magic_quotes_runtime')) + @set_magic_quotes_runtime(0); +error_reporting(defined('E_STRICT') ? E_ALL | E_STRICT : E_ALL); +$time_start = microtime(); + +// This makes it so headers can be sent! +ob_start(); + +// Do some cleaning, just in case. +foreach (array('db_character_set', 'cachedir') as $variable) + if (isset($GLOBALS[$variable])) + unset($GLOBALS[$variable], $GLOBALS[$variable]); + +// Load the settings... +require_once(dirname(__FILE__) . '/Settings.php'); + +// Make absolutely sure the cache directory is defined. +if ((empty($cachedir) || !file_exists($cachedir)) && file_exists($boarddir . '/cache')) + $cachedir = $boarddir . '/cache'; + +// And important includes. +require_once($sourcedir . '/QueryString.php'); +require_once($sourcedir . '/Subs.php'); +require_once($sourcedir . '/Errors.php'); +require_once($sourcedir . '/Load.php'); +require_once($sourcedir . '/Security.php'); + +// Using an pre-PHP 5.1 version? +if (@version_compare(PHP_VERSION, '5.1') == -1) + require_once($sourcedir . '/Subs-Compat.php'); + +// If $maintenance is set specifically to 2, then we're upgrading or something. +if (!empty($maintenance) && $maintenance == 2) + db_fatal_error(); + +// Create a variable to store some SMF specific functions in. +$smcFunc = array(); + +// Initate the database connection and define some database functions to use. +loadDatabase(); + +// Load the settings from the settings table, and perform operations like optimizing. +reloadSettings(); +// Clean the request variables, add slashes, etc. +cleanRequest(); +$context = array(); + +// Seed the random generator. +if (empty($modSettings['rand_seed']) || mt_rand(1, 250) == 69) + smf_seed_generator(); + +// Before we get carried away, are we doing a scheduled task? If so save CPU cycles by jumping out! +if (isset($_GET['scheduled'])) +{ + require_once($sourcedir . '/ScheduledTasks.php'); + AutoTask(); +} + +// Check if compressed output is enabled, supported, and not already being done. +if (!empty($modSettings['enableCompressedOutput']) && !headers_sent()) +{ + // If zlib is being used, turn off output compression. + if (@ini_get('zlib.output_compression') == '1' || @ini_get('output_handler') == 'ob_gzhandler' || @version_compare(PHP_VERSION, '4.2.0') == -1) + $modSettings['enableCompressedOutput'] = '0'; + else + { + ob_end_clean(); + ob_start('ob_gzhandler'); + } +} + +// Emit some headers for some modicum of protection against nasties. +if (!headers_sent()) +{ + // Future versions will make some of this configurable. This is primarily a 'safe' configuration for most cases for now. + header('X-Frame-Options: SAMEORIGIN'); + header('X-XSS-Protection: 1'); + header('X-Content-Type-Options: nosniff'); +} + +// Register an error handler. +set_error_handler('error_handler'); + +// Start the session. (assuming it hasn't already been.) +loadSession(); + +// Determine if this is using WAP, WAP2, or imode. Technically, we should check that wap comes before application/xhtml or text/html, but this doesn't work in practice as much as it should. +if (isset($_REQUEST['wap']) || isset($_REQUEST['wap2']) || isset($_REQUEST['imode'])) + unset($_SESSION['nowap']); +elseif (isset($_REQUEST['nowap'])) + $_SESSION['nowap'] = true; +elseif (!isset($_SESSION['nowap'])) +{ + if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/vnd.wap.xhtml+xml') !== false) + $_REQUEST['wap2'] = 1; + elseif (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'text/vnd.wap.wml') !== false) + { + if (strpos($_SERVER['HTTP_USER_AGENT'], 'DoCoMo/') !== false || strpos($_SERVER['HTTP_USER_AGENT'], 'portalmmm/') !== false) + $_REQUEST['imode'] = 1; + else + $_REQUEST['wap'] = 1; + } +} + +if (!defined('WIRELESS')) + define('WIRELESS', isset($_REQUEST['wap']) || isset($_REQUEST['wap2']) || isset($_REQUEST['imode'])); + +// Some settings and headers are different for wireless protocols. +if (WIRELESS) +{ + define('WIRELESS_PROTOCOL', isset($_REQUEST['wap']) ? 'wap' : (isset($_REQUEST['wap2']) ? 'wap2' : (isset($_REQUEST['imode']) ? 'imode' : ''))); + + // Some cellphones can't handle output compression... + $modSettings['enableCompressedOutput'] = '0'; + // !!! Do we want these hard coded? + $modSettings['defaultMaxMessages'] = 5; + $modSettings['defaultMaxTopics'] = 9; + + // Wireless protocol header. + if (WIRELESS_PROTOCOL == 'wap') + header('Content-Type: text/vnd.wap.wml'); +} + +// Restore post data if we are revalidating OpenID. +if (isset($_GET['openid_restore_post']) && !empty($_SESSION['openid']['saved_data'][$_GET['openid_restore_post']]['post']) && empty($_POST)) +{ + $_POST = $_SESSION['openid']['saved_data'][$_GET['openid_restore_post']]['post']; + unset($_SESSION['openid']['saved_data'][$_GET['openid_restore_post']]); +} + +// What function shall we execute? (done like this for memory's sake.) +call_user_func(smf_main()); + +// Call obExit specially; we're coming from the main area ;). +obExit(null, null, true); + +// The main controlling function. +function smf_main() +{ + global $modSettings, $settings, $user_info, $board, $topic, $board_info, $maintenance, $sourcedir; + + // Special case: session keep-alive, output a transparent pixel. + if (isset($_GET['action']) && $_GET['action'] == 'keepalive') + { + header('Content-Type: image/gif'); + die("\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x21\xF9\x04\x01\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3B"); + } + + // Load the user's cookie (or set as guest) and load their settings. + loadUserSettings(); + + // Load the current board's information. + loadBoard(); + + // Load the current user's permissions. + loadPermissions(); + + // Attachments don't require the entire theme to be loaded. + if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'dlattach' && (!empty($modSettings['allow_guestAccess']) && $user_info['is_guest'])) + detectBrowser(); + // Load the current theme. (note that ?theme=1 will also work, may be used for guest theming.) + else + loadTheme(); + + // Check if the user should be disallowed access. + is_not_banned(); + + // If we are in a topic and don't have permission to approve it then duck out now. + if (!empty($topic) && empty($board_info['cur_topic_approved']) && !allowedTo('approve_posts') && ($user_info['id'] != $board_info['cur_topic_starter'] || $user_info['is_guest'])) + fatal_lang_error('not_a_topic', false); + + // Do some logging, unless this is an attachment, avatar, toggle of editor buttons, theme option, XML feed etc. + if (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], array('dlattach', 'findmember', 'jseditor', 'jsoption', 'requestmembers', 'smstats', '.xml', 'xmlhttp', 'verificationcode', 'viewquery', 'viewsmfile'))) + { + // Log this user as online. + writeLog(); + + // Track forum statistics and hits...? + if (!empty($modSettings['hitStats'])) + trackStats(array('hits' => '+')); + } + + // Is the forum in maintenance mode? (doesn't apply to administrators.) + if (!empty($maintenance) && !allowedTo('admin_forum')) + { + // You can only login.... otherwise, you're getting the "maintenance mode" display. + if (isset($_REQUEST['action']) && ($_REQUEST['action'] == 'login2' || $_REQUEST['action'] == 'logout')) + { + require_once($sourcedir . '/LogInOut.php'); + return $_REQUEST['action'] == 'login2' ? 'Login2' : 'Logout'; + } + // Don't even try it, sonny. + else + { + require_once($sourcedir . '/Subs-Auth.php'); + return 'InMaintenance'; + } + } + // If guest access is off, a guest can only do one of the very few following actions. + elseif (empty($modSettings['allow_guestAccess']) && $user_info['is_guest'] && (!isset($_REQUEST['action']) || !in_array($_REQUEST['action'], array('coppa', 'login', 'login2', 'register', 'register2', 'reminder', 'activate', 'help', 'smstats', 'mailq', 'verificationcode', 'openidreturn')))) + { + require_once($sourcedir . '/Subs-Auth.php'); + return 'KickGuest'; + } + elseif (empty($_REQUEST['action'])) + { + // Action and board are both empty... BoardIndex! + if (empty($board) && empty($topic)) + { + require_once($sourcedir . '/BoardIndex.php'); + return 'BoardIndex'; + } + // Topic is empty, and action is empty.... MessageIndex! + elseif (empty($topic)) + { + require_once($sourcedir . '/MessageIndex.php'); + return 'MessageIndex'; + } + // Board is not empty... topic is not empty... action is empty.. Display! + else + { + require_once($sourcedir . '/Display.php'); + return 'Display'; + } + } + + // Here's the monstrous $_REQUEST['action'] array - $_REQUEST['action'] => array($file, $function). + $actionArray = array( + 'activate' => array('Register.php', 'Activate'), + 'admin' => array('Admin.php', 'AdminMain'), + 'announce' => array('Post.php', 'AnnounceTopic'), + 'attachapprove' => array('ManageAttachments.php', 'ApproveAttach'), + 'buddy' => array('Subs-Members.php', 'BuddyListToggle'), + 'calendar' => array('Calendar.php', 'CalendarMain'), + 'clock' => array('Calendar.php', 'clock'), + 'collapse' => array('BoardIndex.php', 'CollapseCategory'), + 'coppa' => array('Register.php', 'CoppaForm'), + 'credits' => array('Who.php', 'Credits'), + 'deletemsg' => array('RemoveTopic.php', 'DeleteMessage'), + 'display' => array('Display.php', 'Display'), + 'dlattach' => array('Display.php', 'Download'), + 'editpoll' => array('Poll.php', 'EditPoll'), + 'editpoll2' => array('Poll.php', 'EditPoll2'), + 'emailuser' => array('SendTopic.php', 'EmailUser'), + 'findmember' => array('Subs-Auth.php', 'JSMembers'), + 'groups' => array('Groups.php', 'Groups'), + 'help' => array('Help.php', 'ShowHelp'), + 'helpadmin' => array('Help.php', 'ShowAdminHelp'), + 'im' => array('PersonalMessage.php', 'MessageMain'), + 'jseditor' => array('Subs-Editor.php', 'EditorMain'), + 'jsmodify' => array('Post.php', 'JavaScriptModify'), + 'jsoption' => array('Themes.php', 'SetJavaScript'), + 'lock' => array('LockTopic.php', 'LockTopic'), + 'lockvoting' => array('Poll.php', 'LockVoting'), + 'login' => array('LogInOut.php', 'Login'), + 'login2' => array('LogInOut.php', 'Login2'), + 'logout' => array('LogInOut.php', 'Logout'), + 'markasread' => array('Subs-Boards.php', 'MarkRead'), + 'mergetopics' => array('SplitTopics.php', 'MergeTopics'), + 'mlist' => array('Memberlist.php', 'Memberlist'), + 'moderate' => array('ModerationCenter.php', 'ModerationMain'), + 'modifycat' => array('ManageBoards.php', 'ModifyCat'), + 'modifykarma' => array('Karma.php', 'ModifyKarma'), + 'movetopic' => array('MoveTopic.php', 'MoveTopic'), + 'movetopic2' => array('MoveTopic.php', 'MoveTopic2'), + 'notify' => array('Notify.php', 'Notify'), + 'notifyboard' => array('Notify.php', 'BoardNotify'), + 'openidreturn' => array('Subs-OpenID.php', 'smf_openID_return'), + 'pm' => array('PersonalMessage.php', 'MessageMain'), + 'post' => array('Post.php', 'Post'), + 'post2' => array('Post.php', 'Post2'), + 'printpage' => array('Printpage.php', 'PrintTopic'), + 'profile' => array('Profile.php', 'ModifyProfile'), + 'quotefast' => array('Post.php', 'QuoteFast'), + 'quickmod' => array('MessageIndex.php', 'QuickModeration'), + 'quickmod2' => array('Display.php', 'QuickInTopicModeration'), + 'recent' => array('Recent.php', 'RecentPosts'), + 'register' => array('Register.php', 'Register'), + 'register2' => array('Register.php', 'Register2'), + 'reminder' => array('Reminder.php', 'RemindMe'), + 'removepoll' => array('Poll.php', 'RemovePoll'), + 'removetopic2' => array('RemoveTopic.php', 'RemoveTopic2'), + 'reporttm' => array('SendTopic.php', 'ReportToModerator'), + 'requestmembers' => array('Subs-Auth.php', 'RequestMembers'), + 'restoretopic' => array('RemoveTopic.php', 'RestoreTopic'), + 'search' => array('Search.php', 'PlushSearch1'), + 'search2' => array('Search.php', 'PlushSearch2'), + 'sendtopic' => array('SendTopic.php', 'EmailUser'), + 'smstats' => array('Stats.php', 'SMStats'), + 'suggest' => array('Subs-Editor.php', 'AutoSuggestHandler'), + 'spellcheck' => array('Subs-Post.php', 'SpellCheck'), + 'splittopics' => array('SplitTopics.php', 'SplitTopics'), + 'stats' => array('Stats.php', 'DisplayStats'), + 'sticky' => array('LockTopic.php', 'Sticky'), + 'theme' => array('Themes.php', 'ThemesMain'), + 'trackip' => array('Profile-View.php', 'trackIP'), + 'about:mozilla' => array('Karma.php', 'BookOfUnknown'), + 'about:unknown' => array('Karma.php', 'BookOfUnknown'), + 'unread' => array('Recent.php', 'UnreadTopics'), + 'unreadreplies' => array('Recent.php', 'UnreadTopics'), + 'verificationcode' => array('Register.php', 'VerificationCode'), + 'viewprofile' => array('Profile.php', 'ModifyProfile'), + 'vote' => array('Poll.php', 'Vote'), + 'viewquery' => array('ViewQuery.php', 'ViewQuery'), + 'viewsmfile' => array('Admin.php', 'DisplayAdminFile'), + 'who' => array('Who.php', 'Who'), + '.xml' => array('News.php', 'ShowXmlFeed'), + 'xmlhttp' => array('Xml.php', 'XMLhttpMain'), + ); + + // Allow modifying $actionArray easily. + call_integration_hook('integrate_actions', array(&$actionArray)); + + // Get the function and file to include - if it's not there, do the board index. + if (!isset($_REQUEST['action']) || !isset($actionArray[$_REQUEST['action']])) + { + // Catch the action with the theme? + if (!empty($settings['catch_action'])) + { + require_once($sourcedir . '/Themes.php'); + return 'WrapAction'; + } + + // Fall through to the board index then... + require_once($sourcedir . '/BoardIndex.php'); + return 'BoardIndex'; + } + + // Otherwise, it was set - so let's go to that action. + require_once($sourcedir . '/' . $actionArray[$_REQUEST['action']][0]); + return $actionArray[$_REQUEST['action']][1]; +} + +?> \ No newline at end of file diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..7e09434 --- /dev/null +++ b/license.txt @@ -0,0 +1,27 @@ +Copyright © 2011 Simple Machines. All rights reserved. + +Developed by: Simple Machines Forum Project + Simple Machines + http://www.simplemachines.org + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + 3. Neither the names of Simple Machines Forum, Simple Machines, nor + the names of its contributors may be used to endorse or promote + products derived from this Software without specific prior written + permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +WITH THE SOFTWARE. + +This license may be viewed online at http://www.simplemachines.org/about/smf/license.php \ No newline at end of file diff --git a/news_readme.html b/news_readme.html new file mode 100644 index 0000000..62257b7 --- /dev/null +++ b/news_readme.html @@ -0,0 +1,60 @@ + + + + News Scripting for SMF 2.0 + + + +

    News Scripting for SMF 2.0

    +
      +
    1. + Setup the include statement on the page you wish your news to appear on according to the type of file you will be using (either PHP or SHTML.) This example will use PHP. +
    2. +
    3. + Start with the include to point to SSI.php in your root SMF directory.
      + + e.g. <?php include('http://www.simplemachines.org/community/SSI.php'); ?> +
    4. +
    5. + Add ?ssi_function=boardNews;board=n to the end of the URL above. Make sure you replace the n with the number of the board you want news to appear from.
      + + e.g. <?php echo file_get_contents('http://www.simplemachines.org/community/SSI.php?ssi_function=boardNews;board=9'); ?> +
    6. +
    + + That is all you need to do to get your basic script running. You can add any of the following to the end of that, but make sure you put a semicolon (;) between each variable. These variables aren't needed, but provide better versatility.
    + +
    +
    limit=n
    +
    Limits the amount of news items to show (the default is 5.)
    + +
    length=n
    +
    Limits the length of each post to n characters (the default is to impose no limit.)
    + +
    start=n
    +
    Starts the news items at a certain offset. (the default is 0)
    +
    + + e.g. <?php echo file_get_contents('http://www.simplemachines.org/community/SSI.php?ssi_function=boardNews;board=9;limit=7'); ?>
    +
    + This shows an example of how one might change the limit on the news items. +

    + Copyright ©2014 Simple Machines.
    + + \ No newline at end of file diff --git a/readme.html b/readme.html new file mode 100644 index 0000000..3c21e46 --- /dev/null +++ b/readme.html @@ -0,0 +1,244 @@ + + + + SMF 2.0 Upgrade Guide + + + + + +
    +
    +
    +
    +

    Upgrading your forum

    +

    Thank you for deciding to upgrade to SMF. Before you get started, please remember that there is a place for help at www.simplemachines.org if you run into any problems at all.

    +

    You can find the following information in this file:

    + +
    +
    +

    Minimum installation requirements

    +

    Your server must meet a few requirements to be able to run SMF. If you are unsure as to whether your webserver meets these, please try to upgrade anyway - it should detect any problems.

    +
      +
    • Any webserver that properly supports PHP, such as Apache or Internet Information Services (IIS).
    • +
    • + PHP 4.1.0 or higher. The following directives are required to be set correctly in php.ini: + +
    • +
    • Any of the following database systems + +
    • +
    • at least 2 megabytes of storage space in the database, although more is highly recommended.
    • +
    • The database user must have at least the following privileges: SELECT, INSERT, UPDATE, DELETE, ALTER, and INDEX.
    • +
    • about 20 megabytes of storage space on the web server, although more is recommended.
    • +
    +

    Recommendations for best performance:

    + +

    If your server does not meet these requirements, SMF may not work properly.

    +
    +
    +

    Backing up data

    +

    Before starting the upgrade process, a backup of the live database should be taken. This protects the forum from accidental damage and any issues from upgrading. Although all steps are taken, and extensive testing carried out, sometimes issues develop. Therefore, having a backup is crucial. The upgrading tool can backup all database tables before it runs, however the best practice is to have a full backup available.

    +

    Back up a database using SMF

    +

    From SMF, navigate to Forum Maintenance. (Administration Center -> Maintenance -> Forum Maintenance) On the database section, save the data and the structure. Then, compress the file. Select "Download" and wait for the database to complete the download completely. It is recommended if you use this method to verify that the backup is complete by opening the file and checking the last line. If the file is not complete and has an error please try one of the other methods to backup your database.

    +

    Back up a database using PHPMyAdmin

    +

    PHPMyAdmin gives the option to export a database, from the initial page, select the "Export" option and follow the instructions. Select your SMF database. These are different based on host.

    +

    Back up a database using a control panel

    +

    If your hosting service provides a control panel interface, this can be used to back up a database. Selecting the "Backups" or "Backups Wizard" options should take you to a page, prompting you to back up your database. With different hosts, these options may have different titles.

    +
    +
    +

    Upload files: using FTP

    +

    You can use an FTP client and an FTP access to upload the files to your server.

    +

    All you need to do is upload all of the files in this package, excluding this file itself, to your server. You should upload it to the same directory as your previous installation of SMF or YaBB SE. If you are given the option to "resume" uploads, make sure you do not do that - you must upload all of the files. You may wish to make sure that all of the files were uploaded, such as those in Themes/default/languages, because some FTP clients have been known to drop files.

    +

    Language files

    +

    If you are using additional languages it will be useful to upload also the updated versions of the language files along with the upgrading packages. Doing so all updated text strings will appear correctly after the upgrade, and will allow the upgrade to run in your selected language.

    +
    +
    +

    Set file permissions

    +

    After the upgrade archive has been uploaded and extracted, you need to set the files' permissions. This is commonly done by use of the Unix utility CHMOD. The correct CHMOD value for SMF files is either 777, 775 or 755, depending on your hosting service. There are two methods for this step, the method used depends on the hosting service that you use.

    +

    Setting File Permissions With the Upgrader

    +

    The SMF upgrader can set file permissions simply and easily. Navigating to the directory where SMF is located should redirect you to the upgrade.php file and prompt the upgrader. For example: www.yourdomain.com/forum/upgrade.php. If the upgrader detects files that need their permissions adjusted it will prompt for FTP details so it can CHMOD the files it requires for the upgrade. This may not work on some servers. +

    +

    Setting File Permissions With FTP

    +

    Using a control panel or FTP client, file permissions can be changed quickly and easily. Usually, FTP programs will allow permissions to be changed by right-clicking files/directories and selecting "Properties", "Attributes" or "Permissions". The desired numerical value can be entered, or if provided, check boxes can be changed.

    +

    The following files and directories must be writable. Depending on how your server is set up, this could mean that they must have CHMOD values of 644, 664 or 666 for files, and 755, 775 or 777 for folders:

    +
    • /attachments +
    • /avatars +
    • /Packages +
    • /Packages/installed.list +
    • /Smileys +
    • /Themes +
    • agreement.txt +
    • Settings.php +
    • Settings_bak.php +
    • upgrade.php +
    +

    If the permission on your files or folders does not make them writable, the SMF upgrader will report the problem. In that case, use your FTP client or host panel to reset the permissions for the files or folders the upgrader reports.

    +
    +
    +

    Run the upgrading tool

    +

    The final step in upgrading SMF, is to run the upgrading tool. Navigate to the directory where SMF is located. It should redirect you to the upgrade.php file and prompt you to run the upgrade. In example: www.yourdomain.com/forum/upgrade.php.

    +

    The first page you see may request your FTP information. If you see this screen, it is because the installer found some files or folders with inadequate permissions for SMF to run properly. If you enter your FTP information here, the installer can automatically fix these permissions for you. Please note that the path should be the same path you see in your FTP client. For example, it might be "public_html/forum". And remember, the installer will not save your FTP password anywhere.

    +

    Upgrade settings

    +
    +
    Backup database with the prefix "backup_"
    +
    Selecting this option will get the upgrade tool to copy all data in the database before upgrading within the original database.
    +
    Maintenance Mode
    +
    Selecting this option will place the forum into maintenance mode while upgrading rather than showing errors, this is highly recommended.
    +
    Output extra debugging information.
    +
    The upgrade tool can give detailed information while performing an upgrade by selecting this option, it will aid the support team to solve any errors if they occur while upgrading.
    +
    +
    +
    +

    Finishing the upgrade and cleaning up

    +

    Once all parts of the upgrade have completed, check the box to remove the upgrade files from the server. If this does not work, they will need to be deleted via FTP. All upgrade files should be removed from the server once the upgrade process is complete. These files are upgrade.php and the .sql files whose name starts with 'upgrade'. They are a major security risk if they are left on a server unattended. Once SMF has been upgraded, they are no longer needed.

    +

    Good luck!
    + Simple Machines

    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/ssi_examples.php b/ssi_examples.php new file mode 100644 index 0000000..1aca176 --- /dev/null +++ b/ssi_examples.php @@ -0,0 +1,686 @@ + + +

    SMF SSI.php Functions

    +

    Current Version: 2.0

    +

    This file is used to demonstrate the capabilities of SSI.php using PHP include functions. The examples show the include tag, then the results of it.

    + +

    Include Code

    +

    To use SSI.php in your page add at the very top of your page before the <html> tag on line 1 of your php file:

    +
    Code: [Select]
    <?php require(""); ?> + +

    Some notes on usage

    +

    All the functions have an output method parameter. This can either be "echo" (the default) or "array"

    +

    If it is "echo", the function will act normally - otherwise, it will return an array containing information about the requested task. For example, it might return a list of topics for ssi_recentTopics.

    +

    This functionality can be used to allow you to present the information in any way you wish.

    + +

    Additional Guides & FAQ

    +

    Need more information on using SSI.php? Check out Using SSI.php article or the SSI FAQ.

    + +
    + +
    +

    Function List

    +

    Recent Items

    + +

    Top Items

    + +

    Members

    + +

    Authentication

    + +

    Calendar

    + +

    Miscellaneous

    + + +

    Advanced Functions

    + + +

    Website Samples

    + +

    Other

    + +
    + +
    + +
    + +
    + + +
    +

    Recent Topics Function

    +

    Code (simple mode)

    +
    Code: [Select]
    <?php ssi_recentTopics(); ?> +

    Code (advanced mode)

    +
    Code: [Select]
    <?php ssi_recentTopics($num_recent = 8, $exclude_boards = null, $include_boards = null, $output_method = 'echo'); ?> +

    Result

    +
    +
    + +
    +

    Recent Posts Function

    +

    Code

    +
    Code: [Select]
    <?php ssi_recentPosts(); ?> +

    Result

    +
    +
    + +
    +

    Recent Poll Function

    +

    Code

    +
    Code: [Select]
    <?php ssi_recentPoll(); ?> +

    Result

    +
    +
    + + +
    +

    Top Boards Function

    +

    Shows top boards by the number of posts.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_topBoards(); ?> +

    Result

    +
    +
    + +
    +

    Top Topics

    +

    Shows top topics by the number of replies or views.

    + +

    Code (show by number of views)

    +
    Code: [Select]
    <?php ssi_topTopicsViews(); ?> +

    Result

    +
    + +

    Code (show by number of replies)

    +
    Code: [Select]
    <?php ssi_topTopicsReplies(); ?> +

    Result

    +
    +
    + +
    +

    Top Poll Function

    +

    Shows the most-voted-in poll.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_topPoll(); ?> +

    Result

    +
    +
    + +
    +

    Top Poster Function

    + Shows the top poster's name and profile link. + +

    Code

    +
    Code: [Select]
    <?php ssi_topPoster(); ?> +

    Result

    +
    +
    + + +
    +

    Latest Member Function

    +

    Shows the latest member's name and profile link.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_latestMember(); ?> +

    Result

    +
    +
    + +
    +

    Member of the Day

    +

    Shows one random member of the day. This changes once a day.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_randomMember('day'); ?> +

    Result

    +
    +
    + +
    +

    Who's Online Function

    +

    This function shows who are online inside the forum.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_whosOnline(); ?> +

    Result

    +
    + +

    Log Online Presence

    +

    This function logs the SSI page's visitor, then shows the Who's Online list. In other words, this function shows who are online inside and outside the forum.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_logOnline(); ?> +

    Result

    +
    +
    + + +
    +

    Login Function

    +

    Shows a login box only when user is not logged in.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_login(); ?> +

    Result

    +
    + +

    Logout Function

    +

    Shows a logout link only when user is logged in.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_logout(); ?> +

    Result

    +
    + +

    Welcome Function

    +

    Greets users or guests, also shows user's messages if logged in.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_welcome(); ?> +

    Result

    +
    +
    + + +
    +

    Today's Calendar Function

    +

    Code

    +
    Code: [Select]
    <?php ssi_todaysCalendar(); ?> +

    Result

    +
    + +

    Today's Birthdays Function

    +

    Code

    +
    Code: [Select]
    <?php ssi_todaysBirthdays(); ?> +

    Result

    +
    + +

    Today's Holidays Function

    +

    Code

    +
    Code: [Select]
    <?php ssi_todaysHolidays(); ?> +

    Result

    +
    + +

    Today's Events Function

    +

    Code

    +
    Code: [Select]
    <?php ssi_todaysEvents(); ?> +

    Result

    +
    +
    + +
    +

    Recent Calendar Events Function

    + +

    Code

    +
    Code: [Select]
    <?php ssi_recentEvents(); ?> +

    Result

    +
    +
    + + +
    +

    Forum Stats

    +

    Shows some basic forum stats: total members, posts, topics, boards, etc.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_boardStats(); ?> +

    Result

    +
    +
    + +
    +

    News Function

    +

    Shows random forum news.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_news(); ?> +

    Result

    +
    +
    + +
    +

    Board News Function

    +

    Shows the latest posts from read only boards, or a specific board.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_boardNews(); ?> +

    Result

    +
    +
    + +
    +

    Menubar Function

    +

    Displays a menu bar, like one displayed at the top of the forum.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_menubar(); ?> +

    Result

    +
    +
    + +
    +

    Quick Search Function

    + +

    Code

    +
    Code: [Select]
    <?php ssi_quickSearch(); ?> +

    Result

    +
    +
    + +
    +

    Recent Attachments Function

    + +

    Code

    +
    Code: [Select]
    <?php ssi_recentAttachments(); ?> +

    Result

    +
    +
    + + +
    +

    Show Single Poll

    +

    Shows a poll in the specified topic.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_showPoll($topicID); ?> +

    Result

    +
    Not shown because it needs specific topic ID that contains a poll.
    +
    + +
    +

    Show Single Post

    +

    Fetches a post with a particular IDs. By default will only show if you have permission to the see + the board in question. This can be overriden by passing the 2nd parameter as true.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_fetchPosts($postIDs, $isOverride); ?> +

    Result

    +
    Not shown because it needs a specific post ID.
    +
    + +
    +

    Show Single Member

    +

    Shows the specified member's name and profile link.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_fetchMember($memberIDs); ?> +

    Result

    +
    Not shown because it needs a specific member ID.
    +
    + +
    +

    Show Group Members

    +

    Shows all members in a specified group.

    + +

    Code

    +
    Code: [Select]
    <?php ssi_fetchGroupMembers($groupIDs); ?> +

    Result

    +
    Not shown because it needs specific membergroup IDs.
    +
    + +
    +

    Home Page Sample

    + This sample uses the following features: ssi_recentTopics(), ssi_logOnline(), ssi_welcome(), and ssi_boardNews(). + ssi_recentTopics() is fetched using the array method, to allow further customizations on the output. + +

    Code

    +
    Code: [Select]
    +

    Result

    + +
    +
    + +
    + + + + + SMF 2.0 SSI.php Examples + + + + + + +
    + +
    +
    +
    '; +} + +function template_ssi_below() +{ + global $time_start; + + echo ' + +
    +
    +
    + +
    + +'; +} + +function template_homepage_sample1($method = 'source') +{ + global $user_info, $boarddir; + + $header = ' + + + SSI.php example for home page + + + +
    + +
    +

    Recent Forum Topics

    +
      '; + + $footer = ' + +
    + +'; + + if ($method == 'source') + { + $header = '' . "\n" . $header; + return $header . template_homepage_sample1_html() . $footer; + } + else + { + echo $header; + template_homepage_sample1_php(); + echo $footer; + } + +} + +function template_homepage_sample1_php() +{ + global $txt; + + $topics = ssi_recentTopics(8, null, null, 'array'); + + foreach ($topics as $topic) + echo ' +
  28. ', $topic['subject'], ' ', $txt['by'], ' ', $topic['poster']['link'], '
  29. '; + + unset($topics); + + echo ' + +
    + +

    Online Users

    '; + ssi_logOnline(); + + echo ' +
    + +
    '; + + ssi_welcome(); + echo ' +

    + +

    News

    '; + + ssi_boardNews(); + + echo ' +
    '; + +} + +function template_homepage_sample1_html() +{ + $result = ' +\', print_r($topic), \'\'; + + echo \' +
  30. \', $topic[\'subject\'], \' \', $txt[\'by\'], \' \', $topics[$i][\'poster\'][\'link\'], \'
  31. \'; +} + +unset($topics); +?> +
    +

    Online Users

    + + +
    +

    +

    News

    + +
    '; + + return $result; +} + +?> \ No newline at end of file diff --git a/ssi_examples.shtml b/ssi_examples.shtml new file mode 100644 index 0000000..0e93ab2 --- /dev/null +++ b/ssi_examples.shtml @@ -0,0 +1,162 @@ + + + + << :: SMF SSI.php 2.0 RC4 :: >> + + +

    SMF SSI.php Functions

    + Current Version 2.0 RC4
    +
    + This file is used to demonstrate the capabilities of SSI.php using SHTML include functions.
    + The examples the include tag, then the results of it. Examples are separated by horizontal rules.
    + +
    + +

    Recent Topics Function: <!--#include virtual="./SSI.php?ssi_function=recentTopics" -->

    + + +
    + +

    Recent Posts Function: <!--#include virtual="./SSI.php?ssi_function=recentPosts" -->

    + + +
    + +

    Recent Poll Function: <!--#include virtual="./SSI.php?ssi_function=recentPoll" -->

    + + +
    + +

    Top Boards Function: <!--#include virtual="./SSI.php?ssi_function=topBoards" -->

    + + +
    + +

    Top Topics by View Function: <!--#include virtual="./SSI.php?ssi_function=topTopicsViews" -->

    + + +
    + +

    Top Topics by Replies Function: <!--#include virtual="./SSI.php?ssi_function=topTopicsReplies" -->

    + + +
    + +

    Top Poll Function: <!--#include virtual="./SSI.php?ssi_function=topPoll" -->

    + + +
    + +

    Top Poster Function: <!--#include virtual="./SSI.php?ssi_function=topPoster" -->

    + + +
    + +

    Topic's Poll Function: <!--#include virtual="./SSI.php?ssi_function=showPoll;ssi_topic=##" -->

    + + +
    + +

    Latest Member Function: <!--#include virtual="./SSI.php?ssi_function=latestMember" -->

    + + +
    + +

    Random Member Function: <!--#include virtual="./SSI.php?ssi_function=randomMember" -->

    + + +
    + +

    Board Stats: <!--#include virtual="./SSI.php?ssi_function=boardStats" -->

    + + +
    + +

    Who's Online Function: <!--#include virtual="./SSI.php?ssi_function=whosOnline" -->

    + + +
    + +

    Log Online Presence + Who's Online Function: <!--#include virtual="./SSI.php?ssi_function=logOnline" -->

    + + +
    + +

    Welcome Function: <!--#include virtual="./SSI.php?ssi_function=welcome" -->

    + + +
    + +

    News Function: <!--#include virtual="./SSI.php?ssi_function=news" -->

    + + +
    + +

    Board News Function: <!--#include virtual="./SSI.php?ssi_function=boardNews" -->

    + + +
    + +

    Menubar Function: <!--#include virtual="./SSI.php?ssi_function=menubar" -->

    + + +
    + +

    Quick Search Function: <!--#include virtual="./SSI.php?ssi_function=quickSearch" -->

    + + +
    + +

    Login Function: <!--#include virtual="./SSI.php?ssi_function=login" -->

    + + +
    + +

    Log Out Function: <!--#include virtual="./SSI.php?ssi_function=logout" -->

    + + +
    + +

    Today's Birthdays Function: <!--#include virtual="./SSI.php?ssi_function=todaysBirthdays" -->

    + + +
    + +

    Today's Holidays Function: <!--#include virtual="./SSI.php?ssi_function=todaysHolidays" -->

    + + +
    + +

    Today's Events Function: <!--#include virtual="./SSI.php?ssi_function=todaysEvents" -->

    + + +
    + +

    Today's Calendar Function: <!--#include virtual="./SSI.php?ssi_function=todaysCalendar" -->

    + + +
    + +

    Recent Calendar Events Function: <!--#include virtual="./SSI.php?ssi_function=recentEvents" -->

    + + +
    + +

    Recent Attachments Function <!--#include virtual="./SSI.php?ssi_function=recentAttachments" -->

    + + +
    + +
    +
    + + SMF © 2006–2010, Simple Machines LLC + +
    +
    + + *ssi_examples.shtml last modified on + + + + \ No newline at end of file diff --git a/subscriptions.php b/subscriptions.php new file mode 100644 index 0000000..f2a4a9e --- /dev/null +++ b/subscriptions.php @@ -0,0 +1,294 @@ + $email, + 'name' => $txt['who_member'], + 'id' => 0, + ); + +// We need to see whether we can find the correct payment gateway, +// we'll going to go through all our gateway scripts and find out +// if they are happy with what we have. +$txnType = ''; +$gatewayHandles = loadPaymentGateways(); +foreach ($gatewayHandles as $gateway) +{ + $gatewayClass = new $gateway['payment_class'](); + if ($gatewayClass->isValid()) + { + $txnType = $gateway['code']; + break; + } +} + +if (empty($txnType)) + generateSubscriptionError($txt['paid_unknown_transaction_type']); + +// Get the subscription and member ID amoungst others... +@list ($subscription_id, $member_id) = $gatewayClass->precheck(); + +// Integer these just in case. +$subscription_id = (int) $subscription_id; +$member_id = (int) $member_id; + +// This would be bad... +if (empty($member_id)) + generateSubscriptionError($txt['paid_empty_member']); + +// Verify the member. +$request = $smcFunc['db_query']('', ' + SELECT id_member, member_name, real_name, email_address + FROM {db_prefix}members + WHERE id_member = {int:current_member}', + array( + 'current_member' => $member_id, + ) +); +// Didn't find them? +if ($smcFunc['db_num_rows']($request) == 0) + generateSubscriptionError(sprintf($txt['paid_could_not_find_member'], $member_id)); +$member_info = $smcFunc['db_fetch_assoc']($request); +$smcFunc['db_free_result']($request); + +// Get the subscription details. +$request = $smcFunc['db_query']('', ' + SELECT cost, length, name + FROM {db_prefix}subscriptions + WHERE id_subscribe = {int:current_subscription}', + array( + 'current_subscription' => $subscription_id, + ) +); + +// Didn't find it? +if ($smcFunc['db_num_rows']($request) == 0) + generateSubscriptionError(sprintf($txt['paid_count_not_find_subscription'], $member_id, $subscription_id)); + +$subscription_info = $smcFunc['db_fetch_assoc']($request); +$smcFunc['db_free_result']($request); + +// We wish to check the pending payments to make sure we are expecting this. +$request = $smcFunc['db_query']('', ' + SELECT id_sublog, payments_pending, pending_details, end_time + FROM {db_prefix}log_subscribed + WHERE id_subscribe = {int:current_subscription} + AND id_member = {int:current_member} + LIMIT 1', + array( + 'current_subscription' => $subscription_id, + 'current_member' => $member_id, + ) +); +if ($smcFunc['db_num_rows']($request) == 0) + generateSubscriptionError(sprintf($txt['paid_count_not_find_subscription_log'], $member_id, $subscription_id)); +$subscription_info += $smcFunc['db_fetch_assoc']($request); +$smcFunc['db_free_result']($request); + +// Is this a refund etc? +if ($gatewayClass->isRefund()) +{ + // If the end time subtracted by current time, is not greater + // than the duration (ie length of subscription), then we close it. + if ($subscription_info['end_time'] - time() < $subscription_info['length']) + { + // Delete user subscription. + removeSubscription($subscription_id, $member_id); + $subscription_act = time(); + $status = 0; + } + else + { + loadSubscriptions(); + $subscription_act = $subscription_info['end_time'] - $context['subscriptions'][$subscription_id]['num_length']; + $status = 1; + } + + // Mark it as complete so we have a record. + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_subscribed + SET end_time = {int:current_time} + WHERE id_subscribe = {int:current_subscription} + AND id_member = {int:current_member} + AND status = {int:status}', + array( + 'current_time' => $subscription_act, + 'current_subscription' => $subscription_id, + 'current_member' => $member_id, + 'status' => $status, + ) + ); + + // Receipt? + if (!empty($modSettings['paid_email']) && $modSettings['paid_email'] == 2) + { + $replacements = array( + 'NAME' => $subscription_info['name'], + 'REFUNDNAME' => $member_info['member_name'], + 'REFUNDUSER' => $member_info['real_name'], + 'PROFILELINK' => $scripturl . '?action=profile;u=' . $member_id, + 'DATE' => timeformat(time(), false), + ); + + emailAdmins('paid_subscription_refund', $replacements, $notify_users); + } + +} +// Otherwise is it what we want, a purchase? +elseif ($gatewayClass->isPayment() || $gatewayClass->isSubscription()) +{ + $cost = unserialize($subscription_info['cost']); + $total_cost = $gatewayClass->getCost(); + $notify = false; + + // For one off's we want to only capture them once! + if (!$gatewayClass->isSubscription()) + { + $real_details = @unserialize($subscription_info['pending_details']); + if (empty($real_details)) + generateSubscriptionError(sprintf($txt['paid_count_not_find_outstanding_payment'], $member_id, $subscription_id)); + // Now we just try to find anything pending. + // We don't really care which it is as security happens later. + foreach ($real_details as $id => $detail) + { + unset($real_details[$id]); + if ($detail[3] == 'payback' && $subscription_info['payments_pending']) + $subscription_info['payments_pending']--; + break; + } + $subscription_info['pending_details'] = empty($real_details) ? '' : serialize($real_details); + + $smcFunc['db_query']('', ' + UPDATE {db_prefix}log_subscribed + SET payments_pending = {int:payments_pending}, pending_details = {string:pending_details} + WHERE id_sublog = {int:current_subscription_item}', + array( + 'payments_pending' => $subscription_info['payments_pending'], + 'current_subscription_item' => $subscription_info['id_sublog'], + 'pending_details' => $subscription_info['pending_details'], + ) + ); + } + + // Is this flexible? + if ($subscription_info['length'] == 'F') + { + $found_duration = 0; + // This is a little harder, can we find the right duration? + foreach ($cost as $duration => $value) + { + if ($duration == 'fixed') + continue; + elseif ((float) $value == (float) $total_cost) + $found_duration = strtoupper(substr($duration, 0, 1)); + } + + // If we have the duration then we're done. + if ($found_duration!== 0) + { + $notify = true; + addSubscription($subscription_id, $member_id, $found_duration); + } + } + else + { + $actual_cost = $cost['fixed']; + // It must be at least the right amount. + if ($total_cost != 0 && $total_cost >= $actual_cost) + { + // Add the subscription. + $notify = true; + addSubscription($subscription_id, $member_id); + } + } + + // Send a receipt? + if (!empty($modSettings['paid_email']) && $modSettings['paid_email'] == 2 && $notify) + { + $replacements = array( + 'NAME' => $subscription_info['name'], + 'SUBNAME' => $member_info['member_name'], + 'SUBUSER' => $member_info['real_name'], + 'SUBEMAIL' => $member_info['email_address'], + 'PRICE' => sprintf($modSettings['paid_currency_symbol'], $total_cost), + 'PROFILELINK' => $scripturl . '?action=profile;u=' . $member_id, + 'DATE' => timeformat(time(), false), + ); + + emailAdmins('paid_subscription_new', $replacements, $notify_users); + } +} + +// In case we have anything specific to do. +$gatewayClass->close(); + +// Log an error then die. +function generateSubscriptionError($text) +{ + global $modSettings, $notify_users, $smcFunc; + + // Send an email? + if (!empty($modSettings['paid_email'])) + { + $replacements = array( + 'ERROR' => $text, + ); + + emailAdmins('paid_subscription_error', $replacements, $notify_users); + } + + // Maybe we can try to give them the post data? + if (!empty($_POST)) + foreach ($_POST as $key => $val) + $text .= '
    ' . $smcFunc['htmlspecialchars']($key) . ': ' . $smcFunc['htmlspecialchars']($val); + + // Then just log and die. + log_error($text); + + exit; +} + +?> \ No newline at end of file diff --git a/upgrade.php b/upgrade.php new file mode 100644 index 0000000..8395201 --- /dev/null +++ b/upgrade.php @@ -0,0 +1,4528 @@ + array( + 'name' => 'MySQL', + 'version' => '4.0.18', + 'version_check' => 'return min(mysql_get_server_info(), mysql_get_client_info());', + 'utf8_support' => true, + 'utf8_version' => '4.1.0', + 'utf8_version_check' => 'return mysql_get_server_info();', + 'alter_support' => true, + ), + 'postgresql' => array( + 'name' => 'PostgreSQL', + 'version' => '8.0', + 'version_check' => '$version = pg_version(); return $version[\'client\'];', + 'always_has_db' => true, + ), + 'sqlite' => array( + 'name' => 'SQLite', + 'version' => '1', + 'version_check' => 'return 1;', + 'always_has_db' => true, + ), +); + +// General options for the script. +$timeLimitThreshold = 3; +$upgrade_path = dirname(__FILE__); +$upgradeurl = $_SERVER['PHP_SELF']; +// Where the SMF images etc are kept. +$smfsite = 'http://www.simplemachines.org/smf'; +// Disable the need for admins to login? +$disable_security = 0; +// How long, in seconds, must admin be inactive to allow someone else to run? +$upcontext['inactive_timeout'] = 10; + +// All the steps in detail. +// Number,Name,Function,Progress Weight. +$upcontext['steps'] = array( + 0 => array(1, 'Login', 'WelcomeLogin', 2), + 1 => array(2, 'Upgrade Options', 'UpgradeOptions', 2), + 2 => array(3, 'Backup', 'BackupDatabase', 10), + 3 => array(4, 'Database Changes', 'DatabaseChanges', 70), + // This is removed as it doesn't really work right at the moment. + //4 => array(5, 'Cleanup Mods', 'CleanupMods', 10), + 4 => array(5, 'Delete Upgrade', 'DeleteUpgrade', 1), +); +// Just to remember which one has files in it. +$upcontext['database_step'] = 3; +@set_time_limit(600); +// Clean the upgrade path if this is from the client. +if (!empty($_SERVER['argv']) && php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR'])) + for ($i = 1; $i < $_SERVER['argc']; $i++) + { + if (preg_match('~^--path=(.+)$~', $_SERVER['argv'][$i], $match) != 0) + $upgrade_path = substr($match[1], -1) == '/' ? substr($match[1], 0, -1) : $match[1]; + } + +// Are we from the client? +if (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR'])) +{ + $command_line = true; + $disable_security = 1; +} +else + $command_line = false; + +// Load this now just because we can. +require_once($upgrade_path . '/Settings.php'); + +// Are we logged in? +if (isset($upgradeData)) +{ + $upcontext['user'] = unserialize(base64_decode($upgradeData)); + + // Check for sensible values. + if (empty($upcontext['user']['started']) || $upcontext['user']['started'] < time() - 86400) + $upcontext['user']['started'] = time(); + if (empty($upcontext['user']['updated']) || $upcontext['user']['updated'] < time() - 86400) + $upcontext['user']['updated'] = 0; + + $upcontext['started'] = $upcontext['user']['started']; + $upcontext['updated'] = $upcontext['user']['updated']; +} + +// Nothing sensible? +if (empty($upcontext['updated'])) +{ + $upcontext['started'] = time(); + $upcontext['updated'] = 0; + $upcontext['user'] = array( + 'id' => 0, + 'name' => 'Guest', + 'pass' => 0, + 'started' => $upcontext['started'], + 'updated' => $upcontext['updated'], + ); +} + +// Load up some essential data... +loadEssentialData(); + +// Are we going to be mimic'ing SSI at this point? +if (isset($_GET['ssi'])) +{ + require_once($sourcedir . '/Subs.php'); + require_once($sourcedir . '/Errors.php'); + require_once($sourcedir . '/Load.php'); + require_once($sourcedir . '/Security.php'); + require_once($sourcedir . '/Subs-Package.php'); + + loadUserSettings(); + loadPermissions(); +} + +// All the non-SSI stuff. +if (!function_exists('ip2range')) + require_once($sourcedir . '/Subs.php'); + +if (!function_exists('un_htmlspecialchars')) +{ + function un_htmlspecialchars($string) + { + return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES)) + array(''' => '\'', ' ' => ' ')); + } +} + +if (!function_exists('text2words')) +{ + function text2words($text) + { + global $smcFunc; + + // Step 1: Remove entities/things we don't consider words: + $words = preg_replace('~(?:[\x0B\0\xA0\t\r\s\n(){}\\[\\]<>!@$%^*.,:+=`\~\?/\\\\]+|&(?:amp|lt|gt|quot);)+~', ' ', $text); + + // Step 2: Entities we left to letters, where applicable, lowercase. + $words = preg_replace('~([^&\d]|^)[#;]~', '$1 ', un_htmlspecialchars(strtolower($words))); + + // Step 3: Ready to split apart and index! + $words = explode(' ', $words); + $returned_words = array(); + foreach ($words as $word) + { + $word = trim($word, '-_\''); + + if ($word != '') + $returned_words[] = substr($word, 0, 20); + } + + return array_unique($returned_words); + } +} + +if (!function_exists('clean_cache')) +{ + // Empty out the cache folder. + function clean_cache($type = '') + { + global $cachedir, $sourcedir; + + // No directory = no game. + if (!is_dir($cachedir)) + return; + + // Remove the files in SMF's own disk cache, if any + $dh = opendir($cachedir); + while ($file = readdir($dh)) + { + if ($file != '.' && $file != '..' && $file != 'index.php' && $file != '.htaccess' && (!$type || substr($file, 0, strlen($type)) == $type)) + @unlink($cachedir . '/' . $file); + } + closedir($dh); + + // Invalidate cache, to be sure! + // ... as long as Load.php can be modified, anyway. + @touch($sourcedir . '/' . 'Load.php'); + clearstatcache(); + } +} + +// MD5 Encryption. +if (!function_exists('md5_hmac')) +{ + function md5_hmac($data, $key) + { + if (strlen($key) > 64) + $key = pack('H*', md5($key)); + $key = str_pad($key, 64, chr(0x00)); + + $k_ipad = $key ^ str_repeat(chr(0x36), 64); + $k_opad = $key ^ str_repeat(chr(0x5c), 64); + + return md5($k_opad . pack('H*', md5($k_ipad . $data))); + } +} + +// http://www.faqs.org/rfcs/rfc959.html +if (!class_exists('ftp_connection')) +{ + class ftp_connection + { + var $connection = 'no_connection', $error = false, $last_message, $pasv = array(); + + // Create a new FTP connection... + function ftp_connection($ftp_server, $ftp_port = 21, $ftp_user = 'anonymous', $ftp_pass = 'ftpclient@simplemachines.org') + { + if ($ftp_server !== null) + $this->connect($ftp_server, $ftp_port, $ftp_user, $ftp_pass); + } + + function connect($ftp_server, $ftp_port = 21, $ftp_user = 'anonymous', $ftp_pass = 'ftpclient@simplemachines.org') + { + if (substr($ftp_server, 0, 6) == 'ftp://') + $ftp_server = substr($ftp_server, 6); + elseif (substr($ftp_server, 0, 7) == 'ftps://') + $ftp_server = 'ssl://' . substr($ftp_server, 7); + if (substr($ftp_server, 0, 7) == 'http://') + $ftp_server = substr($ftp_server, 7); + $ftp_server = strtr($ftp_server, array('/' => '', ':' => '', '@' => '')); + + // Connect to the FTP server. + $this->connection = @fsockopen($ftp_server, $ftp_port, $err, $err, 5); + if (!$this->connection) + { + $this->error = 'bad_server'; + return; + } + + // Get the welcome message... + if (!$this->check_response(220)) + { + $this->error = 'bad_response'; + return; + } + + // Send the username, it should ask for a password. + fwrite($this->connection, 'USER ' . $ftp_user . "\r\n"); + if (!$this->check_response(331)) + { + $this->error = 'bad_username'; + return; + } + + // Now send the password... and hope it goes okay. + fwrite($this->connection, 'PASS ' . $ftp_pass . "\r\n"); + if (!$this->check_response(230)) + { + $this->error = 'bad_password'; + return; + } + } + + function chdir($ftp_path) + { + if (!is_resource($this->connection)) + return false; + + // No slash on the end, please... + if (substr($ftp_path, -1) == '/' && $ftp_path !== '/') + $ftp_path = substr($ftp_path, 0, -1); + + fwrite($this->connection, 'CWD ' . $ftp_path . "\r\n"); + if (!$this->check_response(250)) + { + $this->error = 'bad_path'; + return false; + } + + return true; + } + + function chmod($ftp_file, $chmod) + { + if (!is_resource($this->connection)) + return false; + + // Convert the chmod value from octal (0777) to text ("777"). + fwrite($this->connection, 'SITE CHMOD ' . decoct($chmod) . ' ' . $ftp_file . "\r\n"); + if (!$this->check_response(200)) + { + $this->error = 'bad_file'; + return false; + } + + return true; + } + + function unlink($ftp_file) + { + // We are actually connected, right? + if (!is_resource($this->connection)) + return false; + + // Delete file X. + fwrite($this->connection, 'DELE ' . $ftp_file . "\r\n"); + if (!$this->check_response(250)) + { + fwrite($this->connection, 'RMD ' . $ftp_file . "\r\n"); + + // Still no love? + if (!$this->check_response(250)) + { + $this->error = 'bad_file'; + return false; + } + } + + return true; + } + + function check_response($desired) + { + // Wait for a response that isn't continued with -, but don't wait too long. + $time = time(); + do + $this->last_message = fgets($this->connection, 1024); + while (substr($this->last_message, 3, 1) != ' ' && time() - $time < 5); + + // Was the desired response returned? + return is_array($desired) ? in_array(substr($this->last_message, 0, 3), $desired) : substr($this->last_message, 0, 3) == $desired; + } + + function passive() + { + // We can't create a passive data connection without a primary one first being there. + if (!is_resource($this->connection)) + return false; + + // Request a passive connection - this means, we'll talk to you, you don't talk to us. + @fwrite($this->connection, 'PASV' . "\r\n"); + $time = time(); + do + $response = fgets($this->connection, 1024); + while (substr($response, 3, 1) != ' ' && time() - $time < 5); + + // If it's not 227, we weren't given an IP and port, which means it failed. + if (substr($response, 0, 4) != '227 ') + { + $this->error = 'bad_response'; + return false; + } + + // Snatch the IP and port information, or die horribly trying... + if (preg_match('~\((\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))\)~', $response, $match) == 0) + { + $this->error = 'bad_response'; + return false; + } + + // This is pretty simple - store it for later use ;). + $this->pasv = array('ip' => $match[1] . '.' . $match[2] . '.' . $match[3] . '.' . $match[4], 'port' => $match[5] * 256 + $match[6]); + + return true; + } + + function create_file($ftp_file) + { + // First, we have to be connected... very important. + if (!is_resource($this->connection)) + return false; + + // I'd like one passive mode, please! + if (!$this->passive()) + return false; + + // Seems logical enough, so far... + fwrite($this->connection, 'STOR ' . $ftp_file . "\r\n"); + + // Okay, now we connect to the data port. If it doesn't work out, it's probably "file already exists", etc. + $fp = @fsockopen($this->pasv['ip'], $this->pasv['port'], $err, $err, 5); + if (!$fp || !$this->check_response(150)) + { + $this->error = 'bad_file'; + @fclose($fp); + return false; + } + + // This may look strange, but we're just closing it to indicate a zero-byte upload. + fclose($fp); + if (!$this->check_response(226)) + { + $this->error = 'bad_response'; + return false; + } + + return true; + } + + function list_dir($ftp_path = '', $search = false) + { + // Are we even connected...? + if (!is_resource($this->connection)) + return false; + + // Passive... non-agressive... + if (!$this->passive()) + return false; + + // Get the listing! + fwrite($this->connection, 'LIST -1' . ($search ? 'R' : '') . ($ftp_path == '' ? '' : ' ' . $ftp_path) . "\r\n"); + + // Connect, assuming we've got a connection. + $fp = @fsockopen($this->pasv['ip'], $this->pasv['port'], $err, $err, 5); + if (!$fp || !$this->check_response(array(150, 125))) + { + $this->error = 'bad_response'; + @fclose($fp); + return false; + } + + // Read in the file listing. + $data = ''; + while (!feof($fp)) + $data .= fread($fp, 4096); + fclose($fp); + + // Everything go okay? + if (!$this->check_response(226)) + { + $this->error = 'bad_response'; + return false; + } + + return $data; + } + + function locate($file, $listing = null) + { + if ($listing === null) + $listing = $this->list_dir('', true); + $listing = explode("\n", $listing); + + @fwrite($this->connection, 'PWD' . "\r\n"); + $time = time(); + do + $response = fgets($this->connection, 1024); + while (substr($response, 3, 1) != ' ' && time() - $time < 5); + + // Check for 257! + if (preg_match('~^257 "(.+?)" ~', $response, $match) != 0) + $current_dir = strtr($match[1], array('""' => '"')); + else + $current_dir = ''; + + for ($i = 0, $n = count($listing); $i < $n; $i++) + { + if (trim($listing[$i]) == '' && isset($listing[$i + 1])) + { + $current_dir = substr(trim($listing[++$i]), 0, -1); + $i++; + } + + // Okay, this file's name is: + $listing[$i] = $current_dir . '/' . trim(strlen($listing[$i]) > 30 ? strrchr($listing[$i], ' ') : $listing[$i]); + + if (substr($file, 0, 1) == '*' && substr($listing[$i], -(strlen($file) - 1)) == substr($file, 1)) + return $listing[$i]; + if (substr($file, -1) == '*' && substr($listing[$i], 0, strlen($file) - 1) == substr($file, 0, -1)) + return $listing[$i]; + if (basename($listing[$i]) == $file || $listing[$i] == $file) + return $listing[$i]; + } + + return false; + } + + function create_dir($ftp_dir) + { + // We must be connected to the server to do something. + if (!is_resource($this->connection)) + return false; + + // Make this new beautiful directory! + fwrite($this->connection, 'MKD ' . $ftp_dir . "\r\n"); + if (!$this->check_response(257)) + { + $this->error = 'bad_file'; + return false; + } + + return true; + } + + function detect_path($filesystem_path, $lookup_file = null) + { + $username = ''; + + if (isset($_SERVER['DOCUMENT_ROOT'])) + { + if (preg_match('~^/home[2]?/([^/]+?)/public_html~', $_SERVER['DOCUMENT_ROOT'], $match)) + { + $username = $match[1]; + + $path = strtr($_SERVER['DOCUMENT_ROOT'], array('/home/' . $match[1] . '/' => '', '/home2/' . $match[1] . '/' => '')); + + if (substr($path, -1) == '/') + $path = substr($path, 0, -1); + + if (strlen(dirname($_SERVER['PHP_SELF'])) > 1) + $path .= dirname($_SERVER['PHP_SELF']); + } + elseif (substr($filesystem_path, 0, 9) == '/var/www/') + $path = substr($filesystem_path, 8); + else + $path = strtr(strtr($filesystem_path, array('\\' => '/')), array($_SERVER['DOCUMENT_ROOT'] => '')); + } + else + $path = ''; + + if (is_resource($this->connection) && $this->list_dir($path) == '') + { + $data = $this->list_dir('', true); + + if ($lookup_file === null) + $lookup_file = $_SERVER['PHP_SELF']; + + $found_path = dirname($this->locate('*' . basename(dirname($lookup_file)) . '/' . basename($lookup_file), $data)); + if ($found_path == false) + $found_path = dirname($this->locate(basename($lookup_file))); + if ($found_path != false) + $path = $found_path; + } + elseif (is_resource($this->connection)) + $found_path = true; + + return array($username, $path, isset($found_path)); + } + + function close() + { + // Goodbye! + fwrite($this->connection, 'QUIT' . "\r\n"); + fclose($this->connection); + + return true; + } + } +} + +// Don't do security check if on Yabbse +if (!isset($modSettings['smfVersion'])) + $disable_security = true; + +// This only exists if we're on SMF ;) +if (isset($modSettings['smfVersion'])) +{ + $request = $smcFunc['db_query']('', ' + SELECT variable, value + FROM {db_prefix}themes + WHERE id_theme = {int:id_theme} + AND variable IN ({string:theme_url}, {string:theme_dir}, {string:images_url})', + array( + 'id_theme' => 1, + 'theme_url' => 'theme_url', + 'theme_dir' => 'theme_dir', + 'images_url' => 'images_url', + 'db_error_skip' => true, + ) + ); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $modSettings[$row['variable']] = $row['value']; + $smcFunc['db_free_result']($request); +} + +if (!isset($modSettings['theme_url'])) +{ + $modSettings['theme_dir'] = $boarddir . '/Themes/default'; + $modSettings['theme_url'] = 'Themes/default'; + $modSettings['images_url'] = 'Themes/default/images'; +} +if (!isset($settings['default_theme_url'])) + $settings['default_theme_url'] = $modSettings['theme_url']; +if (!isset($settings['default_theme_dir'])) + $settings['default_theme_dir'] = $modSettings['theme_dir']; + +$upcontext['is_large_forum'] = (empty($modSettings['smfVersion']) || $modSettings['smfVersion'] <= '1.1 RC1') && !empty($modSettings['totalMessages']) && $modSettings['totalMessages'] > 75000; +// Default title... +$upcontext['page_title'] = isset($modSettings['smfVersion']) ? 'Updating Your SMF Install!' : 'Upgrading from YaBB SE!'; + +$upcontext['right_to_left'] = isset($txt['lang_rtl']) ? $txt['lang_rtl'] : false; + +// Have we got tracking data - if so use it (It will be clean!) +if (isset($_GET['data'])) +{ + $upcontext['upgrade_status'] = unserialize(base64_decode($_GET['data'])); + $upcontext['current_step'] = $upcontext['upgrade_status']['curstep']; + $upcontext['language'] = $upcontext['upgrade_status']['lang']; + $upcontext['rid'] = $upcontext['upgrade_status']['rid']; + $is_debug = $upcontext['upgrade_status']['debug']; + $support_js = $upcontext['upgrade_status']['js']; + + // Load the language. + if (file_exists($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php')) + require_once($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php'); +} +// Set the defaults. +else +{ + $upcontext['current_step'] = 0; + $upcontext['rid'] = mt_rand(0, 5000); + $upcontext['upgrade_status'] = array( + 'curstep' => 0, + 'lang' => isset($_GET['lang']) ? $_GET['lang'] : basename($language, '.lng'), + 'rid' => $upcontext['rid'], + 'pass' => 0, + 'debug' => 0, + 'js' => 0, + ); + $upcontext['language'] = $upcontext['upgrade_status']['lang']; +} + +// If this isn't the first stage see whether they are logging in and resuming. +if ($upcontext['current_step'] != 0 || !empty($upcontext['user']['step'])) + checkLogin(); + +if ($command_line) + cmdStep0(); + +// Don't error if we're using xml. +if (isset($_GET['xml'])) + $upcontext['return_error'] = true; + +// These three checks are necessary to workaround an issue with the upgrade stucking when encountering a db error +if (!isset($context)) +{ + $context = Array(); + $context['error'] = ''; + if (!isset($txt['database_error'])) + $txt['database_error'] = 'Database Error'; +} + +if (!function_exists('allowedTo')) +{ + function allowedTo($p,$b) + { + return true; + } +} + +if (!function_exists('fatal_error')) +{ + function fatal_error($error,$log) + { + return ($error); + } +} + +// Loop through all the steps doing each one as required. +$upcontext['overall_percent'] = 0; +foreach ($upcontext['steps'] as $num => $step) +{ + if ($num >= $upcontext['current_step']) + { + // The current weight of this step in terms of overall progress. + $upcontext['step_weight'] = $step[3]; + // Make sure we reset the skip button. + $upcontext['skip'] = false; + + // We cannot proceed if we're not logged in. + if ($num != 0 && !$disable_security && $upcontext['user']['pass'] != $upcontext['upgrade_status']['pass']) + { + $upcontext['steps'][0][2](); + break; + } + + // Call the step and if it returns false that means pause! + if (function_exists($step[2]) && $step[2]() === false) + break; + elseif (function_exists($step[2])) + $upcontext['current_step']++; + } + $upcontext['overall_percent'] += $step[3]; +} + +upgradeExit(); + +// Exit the upgrade script. +function upgradeExit($fallThrough = false) +{ + global $upcontext, $upgradeurl, $boarddir, $command_line; + + // Save where we are... + if (!empty($upcontext['current_step']) && !empty($upcontext['user']['id'])) + { + $upcontext['user']['step'] = $upcontext['current_step']; + $upcontext['user']['substep'] = $_GET['substep']; + $upcontext['user']['updated'] = time(); + $upgradeData = base64_encode(serialize($upcontext['user'])); + copy($boarddir . '/Settings.php', $boarddir . '/Settings_bak.php'); + changeSettings(array('upgradeData' => '"' . $upgradeData . '"')); + } + + // Handle the progress of the step, if any. + if (!empty($upcontext['step_progress']) && isset($upcontext['steps'][$upcontext['current_step']])) + { + $upcontext['step_progress'] = round($upcontext['step_progress'], 1); + $upcontext['overall_percent'] += $upcontext['step_progress'] * ($upcontext['steps'][$upcontext['current_step']][3] / 100); + } + $upcontext['overall_percent'] = (int) $upcontext['overall_percent']; + + // We usually dump our templates out. + if (!$fallThrough) + { + // This should not happen my dear... HELP ME DEVELOPERS!! + if (!empty($command_line)) + { + if (function_exists('debug_print_backtrace')) + debug_print_backtrace(); + + echo "\n" . 'Error: Unexpected call to use the ' . (isset($upcontext['sub_template']) ? $upcontext['sub_template'] : '') . ' template. Please copy and paste all the text above and visit the SMF support forum to tell the Developers that they\'ve made a boo boo; they\'ll get you up and running again.'; + flush(); + die(); + } + + if (!isset($_GET['xml'])) + template_upgrade_above(); + else + { + header('Content-Type: text/xml; charset=ISO-8859-1'); + // Sadly we need to retain the $_GET data thanks to the old upgrade scripts. + $upcontext['get_data'] = array(); + foreach ($_GET as $k => $v) + { + if (substr($k, 0, 3) != 'amp' && !in_array($k, array('xml', 'substep', 'lang', 'data', 'step', 'filecount'))) + { + $upcontext['get_data'][$k] = $v; + } + } + template_xml_above(); + } + + // Call the template. + if (isset($upcontext['sub_template'])) + { + $upcontext['upgrade_status']['curstep'] = $upcontext['current_step']; + $upcontext['form_url'] = $upgradeurl . '?step=' . $upcontext['current_step'] . '&substep=' . $_GET['substep'] . '&data=' . base64_encode(serialize($upcontext['upgrade_status'])); + + // Custom stuff to pass back? + if (!empty($upcontext['query_string'])) + $upcontext['form_url'] .= $upcontext['query_string']; + + call_user_func('template_' . $upcontext['sub_template']); + } + + // Was there an error? + if (!empty($upcontext['forced_error_message'])) + echo $upcontext['forced_error_message']; + + // Show the footer. + if (!isset($_GET['xml'])) + template_upgrade_below(); + else + template_xml_below(); + } + + // Bang - gone! + die(); +} + +// Used to direct the user to another location. +function redirectLocation($location, $addForm = true) +{ + global $upgradeurl, $upcontext, $command_line; + + // Command line users can't be redirected. + if ($command_line) + upgradeExit(true); + + // Are we providing the core info? + if ($addForm) + { + $upcontext['upgrade_status']['curstep'] = $upcontext['current_step']; + $location = $upgradeurl . '?step=' . $upcontext['current_step'] . '&substep=' . $_GET['substep'] . '&data=' . base64_encode(serialize($upcontext['upgrade_status'])) . $location; + } + + while (@ob_end_clean()); + header('Location: ' . strtr($location, array('&' => '&'))); + + // Exit - saving status as we go. + upgradeExit(true); +} + +// Load all essential data and connect to the DB as this is pre SSI.php +function loadEssentialData() +{ + global $db_server, $db_user, $db_passwd, $db_name, $db_connection, $db_prefix, $db_character_set, $db_type; + global $modSettings, $sourcedir, $smcFunc, $upcontext; + + // Do the non-SSI stuff... + @set_magic_quotes_runtime(0); + error_reporting(E_ALL); + define('SMF', 1); + + // Start the session. + if (@ini_get('session.save_handler') == 'user') + @ini_set('session.save_handler', 'files'); + @session_start(); + + if (empty($smcFunc)) + $smcFunc = array(); + + // Check we don't need some compatibility. + if (@version_compare(PHP_VERSION, '5') == -1) + require_once($sourcedir . '/Subs-Compat.php'); + + // Initialize everything... + initialize_inputs(); + + // Get the database going! + if (empty($db_type)) + $db_type = 'mysql'; + if (file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php')) + { + require_once($sourcedir . '/Subs-Db-' . $db_type . '.php'); + + // Make the connection... + $db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, array('non_fatal' => true)); + + // Oh dear god!! + if ($db_connection === null) + die('Unable to connect to database - please check username and password are correct in Settings.php'); + + if ($db_type == 'mysql' && isset($db_character_set) && preg_match('~^\w+$~', $db_character_set) === 1) + $smcFunc['db_query']('', ' + SET NAMES ' . $db_character_set, + array( + 'db_error_skip' => true, + ) + ); + + // Load the modSettings data... + $request = $smcFunc['db_query']('', ' + SELECT variable, value + FROM {db_prefix}settings', + array( + 'db_error_skip' => true, + ) + ); + $modSettings = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + $modSettings[$row['variable']] = $row['value']; + $smcFunc['db_free_result']($request); + } + else + { + return throw_error('Cannot find ' . $sourcedir . '/Subs-Db-' . $db_type . '.php' . '. Please check you have uploaded all source files and have the correct paths set.'); + } + + // If they don't have the file, they're going to get a warning anyway so we won't need to clean request vars. + if (file_exists($sourcedir . '/QueryString.php')) + { + require_once($sourcedir . '/QueryString.php'); + cleanRequest(); + } + + if (!isset($_GET['substep'])) + $_GET['substep'] = 0; +} + +function initialize_inputs() +{ + global $sourcedir, $start_time, $upcontext, $db_type; + + $start_time = time(); + + umask(0); + + // Fun. Low PHP version... + if (!isset($_GET)) + { + $GLOBALS['_GET']['step'] = 0; + return; + } + + ob_start(); + + // Better to upgrade cleanly and fall apart than to screw everything up if things take too long. + ignore_user_abort(true); + + // This is really quite simple; if ?delete is on the URL, delete the upgrader... + if (isset($_GET['delete'])) + { + @unlink(__FILE__); + + // And the extra little files ;). + @unlink(dirname(__FILE__) . '/upgrade_1-0.sql'); + @unlink(dirname(__FILE__) . '/upgrade_1-1.sql'); + @unlink(dirname(__FILE__) . '/webinstall.php'); + + $dh = opendir(dirname(__FILE__)); + while ($file = readdir($dh)) + { + if (preg_match('~upgrade_\d-\d_([A-Za-z])+\.sql~i', $file, $matches) && isset($matches[1])) + @unlink(dirname(__FILE__) . '/' . $file); + } + closedir($dh); + + header('Location: http://' . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT']) . dirname($_SERVER['PHP_SELF']) . '/Themes/default/images/blank.gif'); + exit; + } + + // Are we calling the backup css file? + if (isset($_GET['infile_css'])) + { + header('Content-Type: text/css'); + template_css(); + exit; + } + + // Anybody home? + if (!isset($_GET['xml'])) + { + $upcontext['remote_files_available'] = false; + $test = @fsockopen('www.simplemachines.org', 80, $errno, $errstr, 1); + if ($test) + $upcontext['remote_files_available'] = true; + @fclose($test); + } + + // Something is causing this to happen, and it's annoying. Stop it. + $temp = 'upgrade_php?step'; + while (strlen($temp) > 4) + { + if (isset($_GET[$temp])) + unset($_GET[$temp]); + $temp = substr($temp, 1); + } + + // Force a step, defaulting to 0. + $_GET['step'] = (int) @$_GET['step']; + $_GET['substep'] = (int) @$_GET['substep']; +} + +// Step 0 - Let's welcome them in and ask them to login! +function WelcomeLogin() +{ + global $boarddir, $sourcedir, $db_prefix, $language, $modSettings, $cachedir, $upgradeurl, $upcontext, $disable_security; + global $smcFunc, $db_type, $databases, $txt; + + $upcontext['sub_template'] = 'welcome_message'; + + // Check for some key files - one template, one language, and a new and an old source file. + $check = @file_exists($modSettings['theme_dir'] . '/index.template.php') + && @file_exists($sourcedir . '/QueryString.php') + && @file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php') + && @file_exists(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql'); + + // Need legacy scripts? + if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 2.0) + $check &= @file_exists(dirname(__FILE__) . '/upgrade_1-1.sql'); + if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 1.1) + $check &= @file_exists(dirname(__FILE__) . '/upgrade_1-0.sql'); + + if (!$check) + // Don't tell them what files exactly because it's a spot check - just like teachers don't tell which problems they are spot checking, that's dumb. + return throw_error('The upgrader was unable to find some crucial files.

    Please make sure you uploaded all of the files included in the package, including the Themes, Sources, and other directories.'); + + // Do they meet the install requirements? + if (!php_version_check()) + return throw_error('Warning! You do not appear to have a version of PHP installed on your webserver that meets SMF\'s minimum installations requirements.

    Please ask your host to upgrade.'); + + if (!db_version_check()) + return throw_error('Your ' . $databases[$db_type]['name'] . ' version does not meet the minimum requirements of SMF.

    Please ask your host to upgrade.'); + + // Do they have ALTER privileges? + if (!empty($databases[$db_type]['alter_support']) && $smcFunc['db_query']('alter_boards', 'ALTER TABLE {db_prefix}boards ORDER BY id_board', array()) === false) + return throw_error('The ' . $databases[$db_type]['name'] . ' user you have set in Settings.php does not have proper privileges.

    Please ask your host to give this user the ALTER, CREATE, and DROP privileges.'); + + // Do a quick version spot check. + $temp = substr(@implode('', @file($boarddir . '/index.php')), 0, 4096); + preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match); + if (empty($match[1]) || $match[1] != SMF_VERSION) + return throw_error('The upgrader found some old or outdated files.

    Please make certain you uploaded the new versions of all the files included in the package.'); + + // What absolutely needs to be writable? + $writable_files = array( + $boarddir . '/Settings.php', + $boarddir . '/Settings_bak.php', + ); + + // Check the cache directory. + $cachedir_temp = empty($cachedir) ? $boarddir . '/cache' : $cachedir; + if (!file_exists($cachedir_temp)) + @mkdir($cachedir_temp); + if (!file_exists($cachedir_temp)) + return throw_error('The cache directory could not be found.

    Please make sure you have a directory called "cache" in your forum directory before continuing.'); + + if (!file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php') && !isset($modSettings['smfVersion']) && !isset($_GET['lang'])) + return throw_error('The upgrader was unable to find language files for the language specified in Settings.php.
    SMF will not work without the primary language files installed.

    Please either install them, or use english instead.'); + elseif (!isset($_GET['skiplang'])) + { + $temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php')), 0, 4096); + preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match); + + if (empty($match[1]) || $match[1] != SMF_LANG_VERSION) + return throw_error('The upgrader found some old or outdated language files, for the forum default language, ' . $upcontext['language'] . '.

    Please make certain you uploaded the new versions of all the files included in the package, even the theme and language files for the default theme.
       [SKIP] [Try English]'); + } + + // This needs to exist! + if (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php')) + return throw_error('The upgrader could not find the "Install" language file for the forum default language, ' . $upcontext['language'] . '.

    Please make certain you uploaded all the files included in the package, even the theme and language files for the default theme.
       [Try English]'); + else + require_once($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php'); + + if (!makeFilesWritable($writable_files)) + return false; + + // Check agreement.txt. (it may not exist, in which case $boarddir must be writable.) + if (isset($modSettings['agreement']) && (!is_writable($boarddir) || file_exists($boarddir . '/agreement.txt')) && !is_writable($boarddir . '/agreement.txt')) + return throw_error('The upgrader was unable to obtain write access to agreement.txt.

    If you are using a linux or unix based server, please ensure that the file is chmod\'d to 777, or if it does not exist that the directory this upgrader is in is 777.
    If your server is running Windows, please ensure that the internet guest account has the proper permissions on it or its folder.'); + + // Upgrade the agreement. + elseif (isset($modSettings['agreement'])) + { + $fp = fopen($boarddir . '/agreement.txt', 'w'); + fwrite($fp, $modSettings['agreement']); + fclose($fp); + } + + // We're going to check that their board dir setting is right incase they've been moving stuff around. + if (strtr($boarddir, array('/' => '', '\\' => '')) != strtr(dirname(__FILE__), array('/' => '', '\\' => ''))) + $upcontext['warning'] = ' + It looks as if your board directory settings might be incorrect. Your board directory is currently set to "' . $boarddir . '" but should probably be "' . dirname(__FILE__) . '". Settings.php currently lists your paths as:
    +
      +
    • Board Directory: ' . $boarddir . '
    • +
    • Source Directory: ' . $boarddir . '
    • +
    • Cache Directory: ' . $cachedir_temp . '
    • +
    + If these seem incorrect please open Settings.php in a text editor before proceeding with this upgrade. If they are incorrect due to you moving your forum to a new location please download and execute the Repair Settings tool from the Simple Machines website before continuing.'; + + // Either we're logged in or we're going to present the login. + if (checkLogin()) + return true; + + return false; +} + +// Step 0.5: Does the login work? +function checkLogin() +{ + global $boarddir, $sourcedir, $db_prefix, $language, $modSettings, $cachedir, $upgradeurl, $upcontext, $disable_security; + global $smcFunc, $db_type, $databases, $support_js, $txt; + + // Are we trying to login? + if (isset($_POST['contbutt']) && (!empty($_POST['user']) || $disable_security)) + { + // If we've disabled security pick a suitable name! + if (empty($_POST['user'])) + $_POST['user'] = 'Administrator'; + + // Before 2.0 these column names were different! + $oldDB = false; + if (empty($db_type) || $db_type == 'mysql') + { + $request = $smcFunc['db_query']('', ' + SHOW COLUMNS + FROM {db_prefix}members + LIKE {string:member_name}', + array( + 'member_name' => 'memberName', + 'db_error_skip' => true, + ) + ); + if ($smcFunc['db_num_rows']($request) != 0) + $oldDB = true; + $smcFunc['db_free_result']($request); + } + + // Get what we believe to be their details. + if (!$disable_security) + { + if ($oldDB) + $request = $smcFunc['db_query']('', ' + SELECT id_member, memberName AS member_name, passwd, id_group, + additionalGroups AS additional_groups, lngfile + FROM {db_prefix}members + WHERE memberName = {string:member_name}', + array( + 'member_name' => $_POST['user'], + 'db_error_skip' => true, + ) + ); + else + $request = $smcFunc['db_query']('', ' + SELECT id_member, member_name, passwd, id_group, additional_groups, lngfile + FROM {db_prefix}members + WHERE member_name = {string:member_name}', + array( + 'member_name' => $_POST['user'], + 'db_error_skip' => true, + ) + ); + if ($smcFunc['db_num_rows']($request) != 0) + { + list ($id_member, $name, $password, $id_group, $addGroups, $user_language) = $smcFunc['db_fetch_row']($request); + + $groups = explode(',', $addGroups); + $groups[] = $id_group; + + foreach ($groups as $k => $v) + $groups[$k] = (int) $v; + + // Figure out the password using SMF's encryption - if what they typed is right. + if (isset($_REQUEST['hash_passwrd']) && strlen($_REQUEST['hash_passwrd']) == 40) + { + // Challenge passed. + if ($_REQUEST['hash_passwrd'] == sha1($password . $upcontext['rid'])) + $sha_passwd = $password; + } + else + $sha_passwd = sha1(strtolower($name) . un_htmlspecialchars($_REQUEST['passwrd'])); + } + else + $upcontext['username_incorrect'] = true; + $smcFunc['db_free_result']($request); + } + $upcontext['username'] = $_POST['user']; + + // Track whether javascript works! + if (!empty($_POST['js_works'])) + { + $upcontext['upgrade_status']['js'] = 1; + $support_js = 1; + } + else + $support_js = 0; + + // Note down the version we are coming from. + if (!empty($modSettings['smfVersion']) && empty($upcontext['user']['version'])) + $upcontext['user']['version'] = $modSettings['smfVersion']; + + // Didn't get anywhere? + if ((empty($sha_passwd) || $password != $sha_passwd) && empty($upcontext['username_incorrect']) && !$disable_security) + { + // MD5? + $md5pass = md5_hmac($_REQUEST['passwrd'], strtolower($_POST['user'])); + if ($md5pass != $password) + { + $upcontext['password_failed'] = true; + // Disable the hashing this time. + $upcontext['disable_login_hashing'] = true; + } + } + + if ((empty($upcontext['password_failed']) && !empty($name)) || $disable_security) + { + // Set the password. + if (!$disable_security) + { + // Do we actually have permission? + if (!in_array(1, $groups)) + { + $request = $smcFunc['db_query']('', ' + SELECT permission + FROM {db_prefix}permissions + WHERE id_group IN ({array_int:groups}) + AND permission = {string:admin_forum}', + array( + 'groups' => $groups, + 'admin_forum' => 'admin_forum', + 'db_error_skip' => true, + ) + ); + if ($smcFunc['db_num_rows']($request) == 0) + return throw_error('You need to be an admin to perform an upgrade!'); + $smcFunc['db_free_result']($request); + } + + $upcontext['user']['id'] = $id_member; + $upcontext['user']['name'] = $name; + } + else + { + $upcontext['user']['id'] = 1; + $upcontext['user']['name'] = 'Administrator'; + } + $upcontext['user']['pass'] = mt_rand(0,60000); + // This basically is used to match the GET variables to Settings.php. + $upcontext['upgrade_status']['pass'] = $upcontext['user']['pass']; + + // Set the language to that of the user? + if (isset($user_language) && $user_language != $upcontext['language'] && file_exists($modSettings['theme_dir'] . '/languages/index.' . basename($user_language, '.lng') . '.php')) + { + $user_language = basename($user_language, '.lng'); + $temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $user_language . '.php')), 0, 4096); + preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match); + + if (empty($match[1]) || $match[1] != SMF_LANG_VERSION) + $upcontext['upgrade_options_warning'] = 'The language files for your selected language, ' . $user_language . ', have not been updated to the latest version. Upgrade will continue with the forum default, ' . $upcontext['language'] . '.'; + elseif (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . basename($user_language, '.lng') . '.php')) + $upcontext['upgrade_options_warning'] = 'The language files for your selected language, ' . $user_language . ', have not been uploaded/updated as the "Install" language file is missing. Upgrade will continue with the forum default, ' . $upcontext['language'] . '.'; + else + { + // Set this as the new language. + $upcontext['language'] = $user_language; + $upcontext['upgrade_status']['lang'] = $upcontext['language']; + + // Include the file. + require_once($modSettings['theme_dir'] . '/languages/Install.' . $user_language . '.php'); + } + } + + // If we're resuming set the step and substep to be correct. + if (isset($_POST['cont'])) + { + $upcontext['current_step'] = $upcontext['user']['step']; + $_GET['substep'] = $upcontext['user']['substep']; + } + + return true; + } + } + + return false; +} + +// Step 1: Do the maintenance and backup. +function UpgradeOptions() +{ + global $db_prefix, $command_line, $modSettings, $is_debug, $smcFunc; + global $boarddir, $boardurl, $sourcedir, $maintenance, $mmessage, $cachedir, $upcontext, $db_type; + + $upcontext['sub_template'] = 'upgrade_options'; + $upcontext['page_title'] = 'Upgrade Options'; + + // If we've not submitted then we're done. + if (empty($_POST['upcont'])) + return false; + + // Firstly, if they're enabling SM stat collection just do it. + if (!empty($_POST['stats']) && substr($boardurl, 0, 16) != 'http://localhost' && empty($modSettings['allow_sm_stats'])) + { + // Attempt to register the site etc. + $fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr); + if ($fp) + { + $out = 'GET /smf/stats/register_stats.php?site=' . base64_encode($boardurl) . ' HTTP/1.1' . "\r\n"; + $out .= 'Host: www.simplemachines.org' . "\r\n"; + $out .= 'Connection: Close' . "\r\n\r\n"; + fwrite($fp, $out); + + $return_data = ''; + while (!feof($fp)) + $return_data .= fgets($fp, 128); + + fclose($fp); + + // Get the unique site ID. + preg_match('~SITE-ID:\s(\w{10})~', $return_data, $ID); + + if (!empty($ID[1])) + $smcFunc['db_insert']('replace', + $db_prefix . 'settings', + array('variable' => 'string', 'value' => 'string'), + array('allow_sm_stats', $ID[1]), + array('variable') + ); + } + } + else + $smcFunc['db_query']('', ' + DELETE FROM {db_prefix}settings + WHERE variable = {string:allow_sm_stats}', + array( + 'allow_sm_stats' => 'allow_sm_stats', + 'db_error_skip' => true, + ) + ); + + // Emptying the error log? + if (!empty($_POST['empty_error'])) + $smcFunc['db_query']('truncate_table', ' + TRUNCATE {db_prefix}log_errors', + array( + ) + ); + + $changes = array(); + + // If we're overriding the language follow it through. + if (isset($_GET['lang']) && file_exists($modSettings['theme_dir'] . '/languages/index.' . $_GET['lang'] . '.php')) + $changes['language'] = '\'' . $_GET['lang'] . '\''; + + if (!empty($_POST['maint'])) + { + $changes['maintenance'] = '2'; + // Remember what it was... + $upcontext['user']['main'] = $maintenance; + + if (!empty($_POST['maintitle'])) + { + $changes['mtitle'] = '\'' . addslashes($_POST['maintitle']) . '\''; + $changes['mmessage'] = '\'' . addslashes($_POST['mainmessage']) . '\''; + } + else + { + $changes['mtitle'] = '\'Upgrading the forum...\''; + $changes['mmessage'] = '\'Don\\\'t worry, we will be back shortly with an updated forum. It will only be a minute ;).\''; + } + } + + if ($command_line) + echo ' * Updating Settings.php...'; + + // Backup the current one first. + copy($boarddir . '/Settings.php', $boarddir . '/Settings_bak.php'); + + // Fix some old paths. + if (substr($boarddir, 0, 1) == '.') + $changes['boarddir'] = '\'' . fixRelativePath($boarddir) . '\''; + + if (substr($sourcedir, 0, 1) == '.') + $changes['sourcedir'] = '\'' . fixRelativePath($sourcedir) . '\''; + + if (empty($cachedir) || substr($cachedir, 0, 1) == '.') + $changes['cachedir'] = '\'' . fixRelativePath($boarddir) . '/cache\''; + + // Not had the database type added before? + if (empty($db_type)) + $changes['db_type'] = 'mysql'; + + // !!! Maybe change the cookie name if going to 1.1, too? + + // Update Settings.php with the new settings. + changeSettings($changes); + + if ($command_line) + echo ' Successful.' . "\n"; + + // Are we doing debug? + if (isset($_POST['debug'])) + { + $upcontext['upgrade_status']['debug'] = true; + $is_debug = true; + } + + // If we're not backing up then jump one. + if (empty($_POST['backup'])) + $upcontext['current_step']++; + + // If we've got here then let's proceed to the next step! + return true; +} + +// Backup the database - why not... +function BackupDatabase() +{ + global $upcontext, $db_prefix, $command_line, $is_debug, $support_js, $file_steps, $smcFunc; + + $upcontext['sub_template'] = isset($_GET['xml']) ? 'backup_xml' : 'backup_database'; + $upcontext['page_title'] = 'Backup Database'; + + // Done it already - js wise? + if (!empty($_POST['backup_done'])) + return true; + + // Some useful stuff here. + db_extend(); + + // Get all the table names. + $filter = str_replace('_', '\_', preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? $match[2] : $db_prefix) . '%'; + $db = preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? strtr($match[1], array('`' => '')) : false; + $tables = $smcFunc['db_list_tables']($db, $filter); + + $table_names = array(); + foreach ($tables as $table) + if (substr($table, 0, 7) !== 'backup_') + $table_names[] = $table; + + $upcontext['table_count'] = count($table_names); + $upcontext['cur_table_num'] = $_GET['substep']; + $upcontext['cur_table_name'] = str_replace($db_prefix, '', isset($table_names[$_GET['substep']]) ? $table_names[$_GET['substep']] : $table_names[0]); + $upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100); + // For non-java auto submit... + $file_steps = $upcontext['table_count']; + + // What ones have we already done? + foreach ($table_names as $id => $table) + if ($id < $_GET['substep']) + $upcontext['previous_tables'][] = $table; + + if ($command_line) + echo 'Backing Up Tables.'; + + // If we don't support javascript we backup here. + if (!$support_js || isset($_GET['xml'])) + { + // Backup each table! + for ($substep = $_GET['substep'], $n = count($table_names); $substep < $n; $substep++) + { + $upcontext['cur_table_name'] = str_replace($db_prefix, '', (isset($table_names[$substep + 1]) ? $table_names[$substep + 1] : $table_names[$substep])); + $upcontext['cur_table_num'] = $substep + 1; + + $upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100); + + // Do we need to pause? + nextSubstep($substep); + + backupTable($table_names[$substep]); + + // If this is XML to keep it nice for the user do one table at a time anyway! + if (isset($_GET['xml'])) + return upgradeExit(); + } + + if ($is_debug && $command_line) + { + echo "\n" . ' Successful.\'' . "\n"; + flush(); + } + $upcontext['step_progress'] = 100; + + $_GET['substep'] = 0; + // Make sure we move on! + return true; + } + + // Either way next place to post will be database changes! + $_GET['substep'] = 0; + return false; +} + +// Backup one table... +function backupTable($table) +{ + global $is_debug, $command_line, $db_prefix, $smcFunc; + + if ($is_debug && $command_line) + { + echo "\n" . ' +++ Backing up \"' . str_replace($db_prefix, '', $table) . '"...'; + flush(); + } + + $smcFunc['db_backup_table']($table, 'backup_' . $table); + + if ($is_debug && $command_line) + echo ' done.'; +} + +// Step 2: Everything. +function DatabaseChanges() +{ + global $db_prefix, $modSettings, $command_line, $smcFunc; + global $language, $boardurl, $sourcedir, $boarddir, $upcontext, $support_js, $db_type; + + // Have we just completed this? + if (!empty($_POST['database_done'])) + return true; + + $upcontext['sub_template'] = isset($_GET['xml']) ? 'database_xml' : 'database_changes'; + $upcontext['page_title'] = 'Database Changes'; + + // All possible files. + // Name, 'string', 'value' => 'string'), + array('smfVersion', $file[2]), + array('variable') + ); + + $modSettings['smfVersion'] = $file[2]; + } + + // If this is XML we only do this stuff once. + if (isset($_GET['xml'])) + { + // Flag to move on to the next. + $upcontext['completed_step'] = true; + // Did we complete the whole file? + if ($nextFile) + $upcontext['current_debug_item_num'] = -1; + return upgradeExit(); + } + elseif ($support_js) + break; + } + // Set the progress bar to be right as if we had - even if we hadn't... + $upcontext['step_progress'] = ($upcontext['cur_file_num'] / $upcontext['file_count']) * 100; + } + } + + $_GET['substep'] = 0; + // So the template knows we're done. + if (!$support_js) + { + $upcontext['changes_complete'] = true; + + // If this is the command line we can't do any more. + if ($command_line) + return DeleteUpgrade(); + + return true; + } + return false; +} + +// Clean up any mods installed... +function CleanupMods() +{ + global $db_prefix, $modSettings, $upcontext, $boarddir, $sourcedir, $settings, $smcFunc, $command_line; + + // Sorry. Not supported for command line users. + if ($command_line) + return true; + + // Skipping first? + if (!empty($_POST['skip'])) + { + unset($_POST['skip']); + return true; + } + + // If we get here withOUT SSI we need to redirect to ensure we get it! + if (!isset($_GET['ssi']) || !function_exists('mktree')) + redirectLocation('&ssi=1'); + + $upcontext['sub_template'] = 'clean_mods'; + $upcontext['page_title'] = 'Cleanup Modifications'; + + // This can be skipped. + $upcontext['skip'] = true; + + // If we're on the second redirect continue... + if (isset($_POST['cleandone2'])) + return true; + + // Do we already know about some writable files? + if (isset($_POST['writable_files'])) + { + $writable_files = unserialize(base64_decode($_POST['writable_files'])); + if (!makeFilesWritable($writable_files)) + { + // What have we left? + $upcontext['writable_files'] = $writable_files; + return false; + } + } + + // Load all theme paths.... + $request = $smcFunc['db_query']('', ' + SELECT id_theme, variable, value + FROM {db_prefix}themes + WHERE id_member = {int:id_member} + AND variable IN ({string:theme_dir}, {string:images_url})', + array( + 'id_member' => 0, + 'theme_dir' => 'theme_dir', + 'images_url' => 'images_url', + 'db_error_skip' => true, + ) + ); + $theme_paths = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if ($row['id_theme'] == 1) + $settings['default_' . $row['variable']] = $row['value']; + elseif ($row['variable'] == 'theme_dir') + $theme_paths[$row['id_theme']][$row['variable']] = $row['value']; + } + $smcFunc['db_free_result']($request); + + // Are there are mods installed that may need uninstalling? + $request = $smcFunc['db_query']('', ' + SELECT id_install, filename, name, themes_installed, version + FROM {db_prefix}log_packages + WHERE install_state = {int:installed} + ORDER BY time_installed DESC', + array( + 'installed' => 1, + 'db_error_skip' => true, + ) + ); + $upcontext['packages'] = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + // Work out the status. + if (!file_exists($boarddir . '/Packages/' . $row['filename'])) + { + $status = 'Missing'; + $status_color = 'red'; + $result = 'Removed'; + } + else + { + $status = 'Installed'; + $status_color = 'green'; + $result = 'No Action Needed'; + } + + $upcontext['packages'][$row['id_install']] = array( + 'id' => $row['id_install'], + 'themes' => explode(',', $row['themes_installed']), + 'name' => $row['name'], + 'filename' => $row['filename'], + 'missing_file' => file_exists($boarddir . '/Packages/' . $row['filename']) ? 0 : 1, + 'files' => array(), + 'file_count' => 0, + 'status' => $status, + 'result' => $result, + 'color' => $status_color, + 'version' => $row['version'], + 'needs_removing' => false, + ); + } + $smcFunc['db_free_result']($request); + + // Don't carry on if there are none. + if (empty($upcontext['packages'])) + return true; + + // Setup some basics. + if (!empty($upcontext['user']['version'])) + $_SESSION['version_emulate'] = $upcontext['user']['version']; + + // Before we get started, don't report notice errors. + $oldErrorReporting = error_reporting(E_ALL ^ E_NOTICE); + + if (!mktree($boarddir . '/Packages/temp', 0755)) + { + deltree($boarddir . '/Packages/temp', false); + if (!mktree($boarddir . '/Packages/temp', 0777)) + { + deltree($boarddir . '/Packages/temp', false); + //!!! Error here - plus chmod! + } + } + + // Anything which reinstalled should not have its entry removed. + $reinstall_worked = array(); + + // We're gonna be doing some removin' + $test = isset($_POST['cleandone']) ? false : true; + foreach ($upcontext['packages'] as $id => $package) + { + // Can't do anything about this.... + if ($package['missing_file']) + continue; + + // Not testing *and* this wasn't checked? + if (!$test && (!isset($_POST['remove']) || !isset($_POST['remove'][$id]))) + continue; + + // What are the themes this was installed into? + $cur_theme_paths = array(); + foreach ($theme_paths as $tid => $data) + if ($tid != 1 && in_array($tid, $package['themes'])) + $cur_theme_paths[$tid] = $data; + + // Get the modifications data if applicable. + $filename = $package['filename']; + $packageInfo = getPackageInfo($filename); + if (!is_array($packageInfo)) + continue; + + $info = parsePackageInfo($packageInfo['xml'], $test, 'uninstall'); + // Also get the reinstall details... + if (isset($_POST['remove'])) + $infoInstall = parsePackageInfo($packageInfo['xml'], true); + + if (is_file($boarddir . '/Packages/' . $filename)) + read_tgz_file($boarddir . '/Packages/' . $filename, $boarddir . '/Packages/temp'); + else + copytree($boarddir . '/Packages/' . $filename, $boarddir . '/Packages/temp'); + + // Work out how we uninstall... + $files = array(); + foreach ($info as $change) + { + // Work out two things: + // 1) Whether it's installed at the moment - and if so whether its fully installed, and: + // 2) Whether it could be installed on the new version. + if ($change['type'] == 'modification') + { + $contents = @file_get_contents($boarddir . '/Packages/temp/' . $upcontext['base_path'] . $change['filename']); + if ($change['boardmod']) + $results = parseBoardMod($contents, $test, $change['reverse'], $cur_theme_paths); + else + $results = parseModification($contents, $test, $change['reverse'], $cur_theme_paths); + + foreach ($results as $action) + { + // Something we can remove? Probably means it existed! + if (($action['type'] == 'replace' || $action['type'] == 'append' || (!empty($action['filename']) && $action['type'] == 'failure')) && !in_array($action['filename'], $files)) + $files[] = $action['filename']; + if ($action['type'] == 'failure') + { + $upcontext['packages'][$id]['needs_removing'] = true; + $upcontext['packages'][$id]['status'] = 'Reinstall Required'; + $upcontext['packages'][$id]['color'] = '#FD6435'; + } + } + } + } + + // Store this info for the template as appropriate. + $upcontext['packages'][$id]['files'] = $files; + $upcontext['packages'][$id]['file_count'] = count($files); + + // If we've done something save the changes! + if (!$test) + package_flush_cache(); + + // Are we attempting to reinstall this thing? + if (isset($_POST['remove']) && !$test && isset($infoInstall)) + { + // Need to extract again I'm afraid. + if (is_file($boarddir . '/Packages/' . $filename)) + read_tgz_file($boarddir . '/Packages/' . $filename, $boarddir . '/Packages/temp'); + else + copytree($boarddir . '/Packages/' . $filename, $boarddir . '/Packages/temp'); + + $errors = false; + $upcontext['packages'][$id]['result'] = 'Removed'; + foreach ($infoInstall as $change) + { + if ($change['type'] == 'modification') + { + $contents = @file_get_contents($boarddir . '/Packages/temp/' . $upcontext['base_path'] . $change['filename']); + if ($change['boardmod']) + $results = parseBoardMod($contents, true, $change['reverse'], $cur_theme_paths); + else + $results = parseModification($contents, true, $change['reverse'], $cur_theme_paths); + + // Are there any errors? + foreach ($results as $action) + if ($action['type'] == 'failure') + $errors = true; + } + } + if (!$errors) + { + $reinstall_worked[] = $id; + $upcontext['packages'][$id]['result'] = 'Reinstalled'; + $upcontext['packages'][$id]['color'] = 'green'; + foreach ($infoInstall as $change) + { + if ($change['type'] == 'modification') + { + $contents = @file_get_contents($boarddir . '/Packages/temp/' . $upcontext['base_path'] . $change['filename']); + if ($change['boardmod']) + $results = parseBoardMod($contents, false, $change['reverse'], $cur_theme_paths); + else + $results = parseModification($contents, false, $change['reverse'], $cur_theme_paths); + } + } + + // Save the changes. + package_flush_cache(); + } + } + } + + // Put errors back on a sec. + error_reporting($oldErrorReporting); + + // Check everything is writable. + if ($test && !empty($upcontext['packages'])) + { + $writable_files = array(); + foreach ($upcontext['packages'] as $package) + { + if (!empty($package['files'])) + foreach ($package['files'] as $file) + $writable_files[] = $file; + } + + if (!empty($writable_files)) + { + $writable_files = array_unique($writable_files); + $upcontext['writable_files'] = $writable_files; + + if (!makeFilesWritable($writable_files)) + return false; + } + } + + if (file_exists($boarddir . '/Packages/temp')) + deltree($boarddir . '/Packages/temp'); + + // Removing/Reinstalling any packages? + if (isset($_POST['remove'])) + { + $deletes = array(); + foreach ($_POST['remove'] as $id => $dummy) + { + if (!in_array((int) $id, $reinstall_worked)) + $deletes[] = (int) $id; + } + + if (!empty($deletes)) + upgrade_query( ' + UPDATE ' . $db_prefix . 'log_packages + SET install_state = 0 + WHERE id_install IN (' . implode(',', $deletes) . ')'); + + // Ensure we don't lose our changes! + package_put_contents($boarddir . '/Packages/installed.list', time()); + + $upcontext['sub_template'] = 'cleanup_done'; + return false; + } + else + { + $allgood = true; + // Is there actually anything that needs our attention? + foreach ($upcontext['packages'] as $package) + if ($package['color'] != 'green') + $allgood = false; + + if ($allgood) + return true; + } + + $_GET['substep'] = 0; + return isset($_POST['cleandone']) ? true : false; +} + + +// Delete the damn thing! +function DeleteUpgrade() +{ + global $command_line, $language, $upcontext, $boarddir, $sourcedir, $forum_version, $user_info, $maintenance, $smcFunc, $db_type; + + // Now it's nice to have some of the basic SMF source files. + if (!isset($_GET['ssi']) && !$command_line) + redirectLocation('&ssi=1'); + + $upcontext['sub_template'] = 'upgrade_complete'; + $upcontext['page_title'] = 'Upgrade Complete'; + + $endl = $command_line ? "\n" : '
    ' . "\n"; + + $changes = array( + 'language' => '\'' . (substr($language, -4) == '.lng' ? substr($language, 0, -4) : $language) . '\'', + 'db_error_send' => '1', + 'upgradeData' => '#remove#', + ); + + // Are we in maintenance mode? + if (isset($upcontext['user']['main'])) + { + if ($command_line) + echo ' * '; + $upcontext['removed_maintenance'] = true; + $changes['maintenance'] = $upcontext['user']['main']; + } + // Otherwise if somehow we are in 2 let's go to 1. + elseif (!empty($maintenance) && $maintenance == 2) + $changes['maintenance'] = 1; + + // Wipe this out... + $upcontext['user'] = array(); + + // Make a backup of Settings.php first as otherwise earlier changes are lost. + copy($boarddir . '/Settings.php', $boarddir . '/Settings_bak.php'); + changeSettings($changes); + + // Clean any old cache files away. + clean_cache(); + + // Can we delete the file? + $upcontext['can_delete_script'] = is_writable(dirname(__FILE__)) || is_writable(__FILE__); + + // Now is the perfect time to fetch the SM files. + if ($command_line) + cli_scheduled_fetchSMfiles(); + else + { + require_once($sourcedir . '/ScheduledTasks.php'); + $forum_version = SMF_VERSION; // The variable is usually defined in index.php so lets just use the constant to do it for us. + scheduled_fetchSMfiles(); // Now go get those files! + } + + // Log what we've done. + if (empty($user_info['id'])) + $user_info['id'] = !empty($upcontext['user']['id']) ? $upcontext['user']['id'] : 0; + + // Log the action manually, so CLI still works. + $smcFunc['db_insert']('', + '{db_prefix}log_actions', + array( + 'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'string-16', 'action' => 'string', + 'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534', + ), + array( + time(), 3, $user_info['id'], $command_line ? '127.0.0.1' : $user_info['ip'], 'upgrade', + 0, 0, 0, serialize(array('version' => $forum_version, 'member' => $user_info['id'])), + ), + array('id_action') + ); + $user_info['id'] = 0; + + // Save the current database version. + $server_version = $smcFunc['db_server_info'](); + if ($db_type == 'mysql' && in_array(substr($server_version, 0, 6), array('5.0.50', '5.0.51'))) + updateSettings(array('db_mysql_group_by_fix' => '1')); + + if ($command_line) + { + echo $endl; + echo 'Upgrade Complete!', $endl; + echo 'Please delete this file as soon as possible for security reasons.', $endl; + exit; + } + + // Make sure it says we're done. + $upcontext['overall_percent'] = 100; + if (isset($upcontext['step_progress'])) + unset($upcontext['step_progress']); + + $_GET['substep'] = 0; + return false; +} + +// Just like the built in one, but setup for CLI to not use themes. +function cli_scheduled_fetchSMfiles() +{ + global $sourcedir, $txt, $language, $settings, $forum_version, $modSettings, $smcFunc; + + if (empty($modSettings['time_format'])) + $modSettings['time_format'] = '%B %d, %Y, %I:%M:%S %p'; + + // What files do we want to get + $request = $smcFunc['db_query']('', ' + SELECT id_file, filename, path, parameters + FROM {db_prefix}admin_info_files', + array( + ) + ); + + $js_files = array(); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $js_files[$row['id_file']] = array( + 'filename' => $row['filename'], + 'path' => $row['path'], + 'parameters' => sprintf($row['parameters'], $language, urlencode($modSettings['time_format']), urlencode($forum_version)), + ); + } + $smcFunc['db_free_result']($request); + + // We're gonna need fetch_web_data() to pull this off. + require_once($sourcedir . '/Subs-Package.php'); + + foreach ($js_files as $ID_FILE => $file) + { + // Create the url + $server = empty($file['path']) || substr($file['path'], 0, 7) != 'http://' ? 'http://www.simplemachines.org' : ''; + $url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : ''); + + // Get the file + $file_data = fetch_web_data($url); + + // If we got an error - give up - the site might be down. + if ($file_data === false) + return throw_error(sprintf('Could not retrieve the file %1$s.', $url)); + + // Save the file to the database. + $smcFunc['db_query']('substring', ' + UPDATE {db_prefix}admin_info_files + SET data = SUBSTRING({string:file_data}, 1, 65534) + WHERE id_file = {int:id_file}', + array( + 'id_file' => $ID_FILE, + 'file_data' => $file_data, + ) + ); + } + return true; +} + +function convertSettingsToTheme() +{ + global $db_prefix, $modSettings, $smcFunc; + + $values = array( + 'show_latest_member' => @$GLOBALS['showlatestmember'], + 'show_bbc' => isset($GLOBALS['showyabbcbutt']) ? $GLOBALS['showyabbcbutt'] : @$GLOBALS['showbbcbutt'], + 'show_modify' => @$GLOBALS['showmodify'], + 'show_user_images' => @$GLOBALS['showuserpic'], + 'show_blurb' => @$GLOBALS['showusertext'], + 'show_gender' => @$GLOBALS['showgenderimage'], + 'show_newsfader' => @$GLOBALS['shownewsfader'], + 'display_recent_bar' => @$GLOBALS['Show_RecentBar'], + 'show_member_bar' => @$GLOBALS['Show_MemberBar'], + 'linktree_link' => @$GLOBALS['curposlinks'], + 'show_profile_buttons' => @$GLOBALS['profilebutton'], + 'show_mark_read' => @$GLOBALS['showmarkread'], + 'show_board_desc' => @$GLOBALS['ShowBDescrip'], + 'newsfader_time' => @$GLOBALS['fadertime'], + 'use_image_buttons' => empty($GLOBALS['MenuType']) ? 1 : 0, + 'enable_news' => @$GLOBALS['enable_news'], + 'linktree_inline' => @$modSettings['enableInlineLinks'], + 'return_to_post' => @$modSettings['returnToPost'], + ); + + $themeData = array(); + foreach ($values as $variable => $value) + { + if (!isset($value) || $value === null) + $value = 0; + + $themeData[] = array(0, 1, $variable, $value); + } + if (!empty($themeData)) + { + $smcFunc['db_insert']('ignore', + $db_prefix . 'themes', + array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'), + $themeData, + array('id_member', 'id_theme', 'variable') + ); + } +} + +// This function only works with MySQL but that's fine as it is only used for v1.0. +function convertSettingstoOptions() +{ + global $db_prefix, $modSettings, $smcFunc; + + // Format: new_setting -> old_setting_name. + $values = array( + 'calendar_start_day' => 'cal_startmonday', + 'view_newest_first' => 'viewNewestFirst', + 'view_newest_pm_first' => 'viewNewestFirst', + ); + + foreach ($values as $variable => $value) + { + if (empty($modSettings[$value[0]])) + continue; + + $smcFunc['db_query']('', ' + INSERT IGNORE INTO {db_prefix}themes + (id_member, id_theme, variable, value) + SELECT id_member, 1, {string:variable}, {string:value} + FROM {db_prefix}members', + array( + 'variable' => $variable, + 'value' => $modSettings[$value[0]], + 'db_error_skip' => true, + ) + ); + + $smcFunc['db_query']('', ' + INSERT IGNORE INTO {db_prefix}themes + (id_member, id_theme, variable, value) + VALUES (-1, 1, {string:variable}, {string:value})', + array( + 'variable' => $variable, + 'value' => $modSettings[$value[0]], + 'db_error_skip' => true, + ) + ); + } +} + +function changeSettings($config_vars) +{ + global $boarddir; + + $settingsArray = file($boarddir . '/Settings_bak.php'); + + if (count($settingsArray) == 1) + $settingsArray = preg_split('~[\r\n]~', $settingsArray[0]); + + for ($i = 0, $n = count($settingsArray); $i < $n; $i++) + { + // Don't trim or bother with it if it's not a variable. + if (substr($settingsArray[$i], 0, 1) == '$') + { + $settingsArray[$i] = trim($settingsArray[$i]) . "\n"; + + foreach ($config_vars as $var => $val) + { + if (isset($settingsArray[$i]) && strncasecmp($settingsArray[$i], '$' . $var, 1 + strlen($var)) == 0) + { + if ($val == '#remove#') + unset($settingsArray[$i]); + else + { + $comment = strstr(substr($settingsArray[$i], strpos($settingsArray[$i], ';')), '#'); + $settingsArray[$i] = '$' . $var . ' = ' . $val . ';' . ($comment != '' ? "\t\t" . $comment : "\n"); + } + + unset($config_vars[$var]); + } + } + } + if (isset($settingsArray[$i])) + { + if (trim(substr($settingsArray[$i], 0, 2)) == '?' . '>') + $end = $i; + } + } + + // Assume end-of-file if the end wasn't found. + if (empty($end) || $end < 10) + $end = count($settingsArray); + + if (!empty($config_vars)) + { + $settingsArray[$end++] = ''; + foreach ($config_vars as $var => $val) + { + if ($val != '#remove#') + $settingsArray[$end++] = '$' . $var . ' = ' . $val . ';' . "\n"; + } + } + // This should be the last line and even last bytes of the file. + $settingsArray[$end] = '?' . '>'; + + // Blank out the file - done to fix a oddity with some servers. + $fp = fopen($boarddir . '/Settings.php', 'w'); + fclose($fp); + + $fp = fopen($boarddir . '/Settings.php', 'r+'); + for ($i = 0; $i < $end; $i++) + { + if (isset($settingsArray[$i])) + fwrite($fp, strtr($settingsArray[$i], "\r", '')); + } + fwrite($fp, rtrim($settingsArray[$i])); + fclose($fp); +} + +function php_version_check() +{ + $minver = explode('.', $GLOBALS['required_php_version']); + $curver = explode('.', PHP_VERSION); + + return !(($curver[0] <= $minver[0]) && ($curver[1] <= $minver[1]) && ($curver[1] <= $minver[1]) && ($curver[2][0] < $minver[2][0])); +} + +function db_version_check() +{ + global $db_type, $databases; + + $curver = eval($databases[$db_type]['version_check']); + $curver = preg_replace('~\-.+?$~', '', $curver); + + return version_compare($databases[$db_type]['version'], $curver) <= 0; +} + +function getMemberGroups() +{ + global $db_prefix, $smcFunc; + static $member_groups = array(); + + if (!empty($member_groups)) + return $member_groups; + + $request = $smcFunc['db_query']('', ' + SELECT group_name, id_group + FROM {db_prefix}membergroups + WHERE id_group = {int:admin_group} OR id_group > {int:old_group}', + array( + 'admin_group' => 1, + 'old_group' => 7, + 'db_error_skip' => true, + ) + ); + if ($request === false) + { + $request = $smcFunc['db_query']('', ' + SELECT membergroup, id_group + FROM {db_prefix}membergroups + WHERE id_group = {int:admin_group} OR id_group > {int:old_group}', + array( + 'admin_group' => 1, + 'old_group' => 7, + 'db_error_skip' => true, + ) + ); + } + while ($row = $smcFunc['db_fetch_row']($request)) + $member_groups[trim($row[0])] = $row[1]; + $smcFunc['db_free_result']($request); + + return $member_groups; +} + +function fixRelativePath($path) +{ + global $install_path; + + // Fix the . at the start, clear any duplicate slashes, and fix any trailing slash... + return addslashes(preg_replace(array('~^\.([/\\\]|$)~', '~[/]+~', '~[\\\]+~', '~[/\\\]$~'), array($install_path . '$1', '/', '\\', ''), $path)); +} + +function parse_sql($filename) +{ + global $db_prefix, $db_collation, $boarddir, $boardurl, $command_line, $file_steps, $step_progress, $custom_warning; + global $upcontext, $support_js, $is_debug, $smcFunc, $db_connection, $databases, $db_type, $db_character_set; + +/* + Failure allowed on: + - INSERT INTO but not INSERT IGNORE INTO. + - UPDATE IGNORE but not UPDATE. + - ALTER TABLE and ALTER IGNORE TABLE. + - DROP TABLE. + Yes, I realize that this is a bit confusing... maybe it should be done differently? + + If a comment... + - begins with --- it is to be output, with a break only in debug mode. (and say successful\n\n if there was one before.) + - begins with ---# it is a debugging statement, no break - only shown at all in debug. + - is only ---#, it is "done." and then a break - only shown in debug. + - begins with ---{ it is a code block terminating at ---}. + + Every block of between "--- ..."s is a step. Every "---#" section represents a substep. + + Replaces the following variables: + - {$boarddir} + - {$boardurl} + - {$db_prefix} + - {$db_collation} +*/ + + // May want to use extended functionality. + db_extend(); + db_extend('packages'); + + // Our custom error handler - does nothing but does stop public errors from XML! + if (!function_exists('sql_error_handler')) + { + function sql_error_handler($errno, $errstr, $errfile, $errline) + { + global $support_js; + + if ($support_js) + return true; + else + echo 'Error: ' . $errstr . ' File: ' . $errfile . ' Line: ' . $errline; + } + } + + // Make our own error handler. + set_error_handler('sql_error_handler'); + + // If we're on MySQL supporting collations then let's find out what the members table uses and put it in a global var - to allow upgrade script to match collations! + if (!empty($databases[$db_type]['utf8_support']) && version_compare($databases[$db_type]['utf8_version'], eval($databases[$db_type]['utf8_version_check'])) != 1) + { + $request = $smcFunc['db_query']('', ' + SHOW TABLE STATUS + LIKE {string:table_name}', + array( + 'table_name' => "{$db_prefix}members", + 'db_error_skip' => true, + ) + ); + if ($smcFunc['db_num_rows']($request) === 0) + die('Unable to find members table!'); + $table_status = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + if (!empty($table_status['Collation'])) + { + $request = $smcFunc['db_query']('', ' + SHOW COLLATION + LIKE {string:collation}', + array( + 'collation' => $table_status['Collation'], + 'db_error_skip' => true, + ) + ); + // Got something? + if ($smcFunc['db_num_rows']($request) !== 0) + $collation_info = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // Excellent! + if (!empty($collation_info['Collation']) && !empty($collation_info['Charset'])) + $db_collation = ' CHARACTER SET ' . $collation_info['Charset'] . ' COLLATE ' . $collation_info['Collation']; + } + } + if (empty($db_collation)) + $db_collation = ''; + + $endl = $command_line ? "\n" : '
    ' . "\n"; + + $lines = file($filename); + + $current_type = 'sql'; + $current_data = ''; + $substep = 0; + $last_step = ''; + + // Make sure all newly created tables will have the proper characters set. + if (isset($db_character_set) && $db_character_set === 'utf8') + $lines = str_replace(') ENGINE=MyISAM;', ') ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;', $lines); + + // Count the total number of steps within this file - for progress. + $file_steps = substr_count(implode('', $lines), '---#'); + $upcontext['total_items'] = substr_count(implode('', $lines), '--- '); + $upcontext['debug_items'] = $file_steps; + $upcontext['current_item_num'] = 0; + $upcontext['current_item_name'] = ''; + $upcontext['current_debug_item_num'] = 0; + $upcontext['current_debug_item_name'] = ''; + // This array keeps a record of what we've done in case java is dead... + $upcontext['actioned_items'] = array(); + + $done_something = false; + + foreach ($lines as $line_number => $line) + { + $do_current = $substep >= $_GET['substep']; + + // Get rid of any comments in the beginning of the line... + if (substr(trim($line), 0, 2) === '/*') + $line = preg_replace('~/\*.+?\*/~', '', $line); + + // Always flush. Flush, flush, flush. Flush, flush, flush, flush! FLUSH! + if ($is_debug && !$support_js && $command_line) + flush(); + + if (trim($line) === '') + continue; + + if (trim(substr($line, 0, 3)) === '---') + { + $type = substr($line, 3, 1); + + // An error?? + if (trim($current_data) != '' && $type !== '}') + { + $upcontext['error_message'] = 'Error in upgrade script - line ' . $line_number . '!' . $endl; + if ($command_line) + echo $upcontext['error_message']; + } + + if ($type == ' ') + { + if (!$support_js && $do_current && $_GET['substep'] != 0 && $command_line) + { + echo ' Successful.', $endl; + flush(); + } + + $last_step = htmlspecialchars(rtrim(substr($line, 4))); + $upcontext['current_item_num']++; + $upcontext['current_item_name'] = $last_step; + + if ($do_current) + { + $upcontext['actioned_items'][] = $last_step; + if ($command_line) + echo ' * '; + } + } + elseif ($type == '#') + { + $upcontext['step_progress'] += (100 / $upcontext['file_count']) / $file_steps; + + $upcontext['current_debug_item_num']++; + if (trim($line) != '---#') + $upcontext['current_debug_item_name'] = htmlspecialchars(rtrim(substr($line, 4))); + + // Have we already done something? + if (isset($_GET['xml']) && $done_something) + { + restore_error_handler(); + return $upcontext['current_debug_item_num'] >= $upcontext['debug_items'] ? true : false; + } + + if ($do_current) + { + if (trim($line) == '---#' && $command_line) + echo ' done.', $endl; + elseif ($command_line) + echo ' +++ ', rtrim(substr($line, 4)); + elseif (trim($line) != '---#') + { + if ($is_debug) + $upcontext['actioned_items'][] = htmlspecialchars(rtrim(substr($line, 4))); + } + } + + if ($substep < $_GET['substep'] && $substep + 1 >= $_GET['substep']) + { + if ($command_line) + echo ' * '; + else + $upcontext['actioned_items'][] = $last_step; + } + + // Small step - only if we're actually doing stuff. + if ($do_current) + nextSubstep(++$substep); + else + $substep++; + } + elseif ($type == '{') + $current_type = 'code'; + elseif ($type == '}') + { + $current_type = 'sql'; + + if (!$do_current) + { + $current_data = ''; + continue; + } + + if (eval('global $db_prefix, $modSettings, $smcFunc; ' . $current_data) === false) + { + $upcontext['error_message'] = 'Error in upgrade script ' . basename($filename) . ' on line ' . $line_number . '!' . $endl; + if ($command_line) + echo $upcontext['error_message']; + } + + // Done with code! + $current_data = ''; + $done_something = true; + } + + continue; + } + + $current_data .= $line; + if (substr(rtrim($current_data), -1) === ';' && $current_type === 'sql') + { + if ((!$support_js || isset($_GET['xml']))) + { + if (!$do_current) + { + $current_data = ''; + continue; + } + + $current_data = strtr(substr(rtrim($current_data), 0, -1), array('{$db_prefix}' => $db_prefix, '{$boarddir}' => $boarddir, '{$sboarddir}' => addslashes($boarddir), '{$boardurl}' => $boardurl, '{$db_collation}' => $db_collation)); + + upgrade_query($current_data); + + // !!! This will be how it kinda does it once mysql all stripped out - needed for postgre (etc). + /* + $result = $smcFunc['db_query']('', $current_data, false, false); + // Went wrong? + if (!$result) + { + // Bit of a bodge - do we want the error? + if (!empty($upcontext['return_error'])) + { + $upcontext['error_message'] = $smcFunc['db_error']($db_connection); + return false; + } + }*/ + $done_something = true; + } + $current_data = ''; + } + // If this is xml based and we're just getting the item name then that's grand. + elseif ($support_js && !isset($_GET['xml']) && $upcontext['current_debug_item_name'] != '' && $do_current) + { + restore_error_handler(); + return false; + } + + // Clean up by cleaning any step info. + $step_progress = array(); + $custom_warning = ''; + } + + // Put back the error handler. + restore_error_handler(); + + if ($command_line) + { + echo ' Successful.' . "\n"; + flush(); + } + + $_GET['substep'] = 0; + return true; +} + +function upgrade_query($string, $unbuffered = false) +{ + global $db_connection, $db_server, $db_user, $db_passwd, $db_type, $command_line, $upcontext, $upgradeurl, $modSettings; + global $db_name, $db_unbuffered, $smcFunc; + + // Get the query result - working around some SMF specific security - just this once! + $modSettings['disableQueryCheck'] = true; + $db_unbuffered = $unbuffered; + $result = $smcFunc['db_query']('', $string, Array('security_override'=>1,'db_error_skip'=>1)); + $db_unbuffered = false; + + // Failure?! + if ($result !== false) + return $result; + + $db_error_message = $smcFunc['db_error']($db_connection); + // If MySQL we do something more clever. + if ($db_type == 'mysql') + { + $mysql_errno = mysql_errno($db_connection); + $error_query = in_array(substr(trim($string), 0, 11), array('INSERT INTO', 'UPDATE IGNO', 'ALTER TABLE', 'DROP TABLE ', 'ALTER IGNOR')); + + // Error numbers: + // 1016: Can't open file '....MYI' + // 1050: Table already exists. + // 1054: Unknown column name. + // 1060: Duplicate column name. + // 1061: Duplicate key name. + // 1062: Duplicate entry for unique key. + // 1068: Multiple primary keys. + // 1072: Key column '%s' doesn't exist in table. + // 1091: Can't drop key, doesn't exist. + // 1146: Table doesn't exist. + // 2013: Lost connection to server during query. + + if ($mysql_errno == 1016) + { + if (preg_match('~\'([^\.\']+)~', $db_error_message, $match) != 0 && !empty($match[1])) + mysql_query( ' + REPAIR TABLE `' . $match[1] . '`'); + + $result = mysql_query($string); + if ($result !== false) + return $result; + } + elseif ($mysql_errno == 2013) + { + $db_connection = mysql_connect($db_server, $db_user, $db_passwd); + mysql_select_db($db_name, $db_connection); + + if ($db_connection) + { + $result = mysql_query($string); + + if ($result !== false) + return $result; + } + } + // Duplicate column name... should be okay ;). + elseif (in_array($mysql_errno, array(1060, 1061, 1068, 1091))) + return false; + // Duplicate insert... make sure it's the proper type of query ;). + elseif (in_array($mysql_errno, array(1054, 1062, 1146)) && $error_query) + return false; + // Creating an index on a non-existent column. + elseif ($mysql_errno == 1072) + return false; + elseif ($mysql_errno == 1050 && substr(trim($string), 0, 12) == 'RENAME TABLE') + return false; + } + // If a table already exists don't go potty. + else + { + if (in_array(substr(trim($string), 0, 8), array('CREATE T', 'CREATE S', 'DROP TABL', 'ALTER TA', 'CREATE I'))) + { + if (strpos($db_error_message, 'exist') !== false) + return true; + // SQLite + if (strpos($db_error_message, 'missing') !== false) + return true; + } + elseif (strpos(trim($string), 'INSERT ') !== false) + { + if (strpos($db_error_message, 'duplicate') !== false) + return true; + } + } + + // Get the query string so we pass everything. + $query_string = ''; + foreach ($_GET as $k => $v) + $query_string .= ';' . $k . '=' . $v; + if (strlen($query_string) != 0) + $query_string = '?' . substr($query_string, 1); + + if ($command_line) + { + echo 'Unsuccessful! Database error message:', "\n", $db_error_message, "\n"; + die; + } + + // Bit of a bodge - do we want the error? + if (!empty($upcontext['return_error'])) + { + $upcontext['error_message'] = $db_error_message; + return false; + } + + // Otherwise we have to display this somewhere appropriate if possible. + $upcontext['forced_error_message'] = ' + Unsuccessful!
    + +
    + This query: +
    ' . nl2br(htmlspecialchars(trim($string))) . ';
    + + Caused the error: +
    ' . nl2br(htmlspecialchars($db_error_message)) . '
    +
    + +
    + +
    + '; + + upgradeExit(); +} + +// This performs a table alter, but does it unbuffered so the script can time out professionally. +function protected_alter($change, $substep, $is_test = false) +{ + global $db_prefix, $smcFunc; + + db_extend('packages'); + + // Firstly, check whether the current index/column exists. + $found = false; + if ($change['type'] === 'column') + { + $columns = $smcFunc['db_list_columns']('{db_prefix}' . $change['table'], true); + foreach ($columns as $column) + { + // Found it? + if ($column['name'] === $change['name']) + { + $found |= 1; + // Do some checks on the data if we have it set. + if (isset($change['col_type'])) + $found &= $change['col_type'] === $column['type']; + if (isset($change['null_allowed'])) + $found &= $column['null'] == $change['null_allowed']; + if (isset($change['default'])) + $found &= $change['default'] === $column['default']; + } + } + } + elseif ($change['type'] === 'index') + { + $request = upgrade_query( ' + SHOW INDEX + FROM ' . $db_prefix . $change['table']); + if ($request !== false) + { + $cur_index = array(); + + while ($row = $smcFunc['db_fetch_assoc']($request)) + if ($row['Key_name'] === $change['name']) + $cur_index[(int) $row['Seq_in_index']] = $row['Column_name']; + + ksort($cur_index, SORT_NUMERIC); + $found = array_values($cur_index) === $change['target_columns']; + + $smcFunc['db_free_result']($request); + } + } + + // If we're trying to add and it's added, we're done. + if ($found && in_array($change['method'], array('add', 'change'))) + return true; + // Otherwise if we're removing and it wasn't found we're also done. + elseif (!$found && in_array($change['method'], array('remove', 'change_remove'))) + return true; + // Otherwise is it just a test? + elseif ($is_test) + return false; + + // Not found it yet? Bummer! How about we see if we're currently doing it? + $running = false; + $found = false; + while (1 == 1) + { + $request = upgrade_query(' + SHOW FULL PROCESSLIST'); + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + if (strpos($row['Info'], 'ALTER TABLE ' . $db_prefix . $change['table']) !== false && strpos($row['Info'], $change['text']) !== false) + $found = true; + } + + // Can't find it? Then we need to run it fools! + if (!$found && !$running) + { + $smcFunc['db_free_result']($request); + + $success = upgrade_query(' + ALTER TABLE ' . $db_prefix . $change['table'] . ' + ' . $change['text'], true) !== false; + + if (!$success) + return false; + + // Return + $running = true; + } + // What if we've not found it, but we'd ran it already? Must of completed. + elseif (!$found) + { + $smcFunc['db_free_result']($request); + return true; + } + + // Pause execution for a sec or three. + sleep(3); + + // Can never be too well protected. + nextSubstep($substep); + } + + // Protect it. + nextSubstep($substep); +} + +// Alter a text column definition preserving its character set. +function textfield_alter($change, $substep) +{ + global $db_prefix, $databases, $db_type, $smcFunc; + + // Versions of MySQL < 4.1 wouldn't benefit from character set detection. + if (empty($databases[$db_type]['utf8_support']) || version_compare($databases[$db_type]['utf8_version'], eval($databases[$db_type]['utf8_version_check'])) > 0) + { + $column_fix = true; + $null_fix = !$change['null_allowed']; + } + else + { + $request = $smcFunc['db_query']('', ' + SHOW FULL COLUMNS + FROM {db_prefix}' . $change['table'] . ' + LIKE {string:column}', + array( + 'column' => $change['column'], + 'db_error_skip' => true, + ) + ); + if ($smcFunc['db_num_rows']($request) === 0) + die('Unable to find column ' . $change['column'] . ' inside table ' . $db_prefix . $change['table']); + $table_row = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + + // If something of the current column definition is different, fix it. + $column_fix = $table_row['Type'] !== $change['type'] || (strtolower($table_row['Null']) === 'yes') !== $change['null_allowed'] || ($table_row['Default'] == NULL) !== !isset($change['default']) || (isset($change['default']) && $change['default'] !== $table_row['Default']); + + // Columns that previously allowed null, need to be converted first. + $null_fix = strtolower($table_row['Null']) === 'yes' && !$change['null_allowed']; + + // Get the character set that goes with the collation of the column. + if ($column_fix && !empty($table_row['Collation'])) + { + $request = $smcFunc['db_query']('', ' + SHOW COLLATION + LIKE {string:collation}', + array( + 'collation' => $table_row['Collation'], + 'db_error_skip' => true, + ) + ); + // No results? Just forget it all together. + if ($smcFunc['db_num_rows']($request) === 0) + unset($table_row['Collation']); + else + $collation_info = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + } + } + + if ($column_fix) + { + // Make sure there are no NULL's left. + if ($null_fix) + $smcFunc['db_query']('', ' + UPDATE {db_prefix}' . $change['table'] . ' + SET ' . $change['column'] . ' = {string:default} + WHERE ' . $change['column'] . ' IS NULL', + array( + 'default' => isset($change['default']) ? $change['default'] : '', + 'db_error_skip' => true, + ) + ); + + // Do the actual alteration. + $smcFunc['db_query']('', ' + ALTER TABLE {db_prefix}' . $change['table'] . ' + CHANGE COLUMN ' . $change['column'] . ' ' . $change['column'] . ' ' . $change['type'] . (isset($collation_info['Charset']) ? ' CHARACTER SET ' . $collation_info['Charset'] . ' COLLATE ' . $collation_info['Collation'] : '') . ($change['null_allowed'] ? '' : ' NOT NULL') . (isset($change['default']) ? ' default {string:default}' : ''), + array( + 'default' => isset($change['default']) ? $change['default'] : '', + 'db_error_skip' => true, + ) + ); + } + nextSubstep($substep); +} + +// Check if we need to alter this query. +function checkChange(&$change) +{ + global $smcFunc, $db_type, $databases; + static $database_version, $where_field_support; + + // Attempt to find a database_version. + if (empty($database_version)) + { + $database_version = $databases[$db_type]['version_check']; + $where_field_support = $db_type == 'mysql' && version_compare('5.0', $database_version) <= 0; + } + + // Not a column we need to check on? + if (!in_array($change['name'], array('memberGroups', 'passwordSalt'))) + return; + + // Break it up you (six|seven). + $temp = explode(' ', str_replace('NOT NULL', 'NOT_NULL', $change['text'])); + + // Can we support a shortcut method? + if ($where_field_support) + { + // Get the details about this change. + $request = $smcFunc['db_query']('', ' + SHOW FIELDS + FROM {db_prefix}{raw:table} + WHERE Field = {string:old_name} OR Field = {string:new_name}', + array( + 'table' => $change['table'], + 'old_name' => $temp[1], + 'new_name' => $temp[2], + )); + if ($smcFunc['db_num_rows'] != 1) + return; + + list (, $current_type) = $smcFunc['db_fetch_assoc']($request); + $smcFunc['db_free_result']($request); + } + else + { + // Do this the old fashion, sure method way. + $request = $smcFunc['db_query']('', ' + SHOW FIELDS + FROM {db_prefix}{raw:table}', + array( + 'table' => $change['table'], + )); + // Mayday! + if ($smcFunc['db_num_rows'] == 0) + return; + + // Oh where, oh where has my little field gone. Oh where can it be... + while ($row = $smcFunc['db_query']($request)) + if ($row['Field'] == $temp[1] || $row['Field'] == $temp[2]) + { + $current_type = $row['Type']; + break; + } + } + + // If this doesn't match, the column may of been altered for a reason. + if (trim($current_type) != trim($temp[3])) + $temp[3] = $current_type; + + // Piece this back together. + $change['text'] = str_replace('NOT_NULL', 'NOT NULL', implode(' ', $temp)); +} + +// The next substep. +function nextSubstep($substep) +{ + global $start_time, $timeLimitThreshold, $command_line, $file_steps, $modSettings, $custom_warning; + global $step_progress, $is_debug, $upcontext; + + if ($_GET['substep'] < $substep) + $_GET['substep'] = $substep; + + if ($command_line) + { + if (time() - $start_time > 1 && empty($is_debug)) + { + echo '.'; + $start_time = time(); + } + return; + } + + @set_time_limit(300); + if (function_exists('apache_reset_timeout')) + @apache_reset_timeout(); + + if (time() - $start_time <= $timeLimitThreshold) + return; + + // Do we have some custom step progress stuff? + if (!empty($step_progress)) + { + $upcontext['substep_progress'] = 0; + $upcontext['substep_progress_name'] = $step_progress['name']; + if ($step_progress['current'] > $step_progress['total']) + $upcontext['substep_progress'] = 99.9; + else + $upcontext['substep_progress'] = ($step_progress['current'] / $step_progress['total']) * 100; + + // Make it nicely rounded. + $upcontext['substep_progress'] = round($upcontext['substep_progress'], 1); + } + + // If this is XML we just exit right away! + if (isset($_GET['xml'])) + return upgradeExit(); + + // We're going to pause after this! + $upcontext['pause'] = true; + + $upcontext['query_string'] = ''; + foreach ($_GET as $k => $v) + { + if ($k != 'data' && $k != 'substep' && $k != 'step') + $upcontext['query_string'] .= ';' . $k . '=' . $v; + } + + // Custom warning? + if (!empty($custom_warning)) + $upcontext['custom_warning'] = $custom_warning; + + upgradeExit(); +} + +function cmdStep0() +{ + global $boarddir, $sourcedir, $db_prefix, $language, $modSettings, $start_time, $cachedir, $databases, $db_type, $smcFunc, $upcontext; + global $language, $is_debug, $txt; + $start_time = time(); + + ob_end_clean(); + ob_implicit_flush(true); + @set_time_limit(600); + + if (!isset($_SERVER['argv'])) + $_SERVER['argv'] = array(); + $_GET['maint'] = 1; + + foreach ($_SERVER['argv'] as $i => $arg) + { + if (preg_match('~^--language=(.+)$~', $arg, $match) != 0) + $_GET['lang'] = $match[1]; + elseif (preg_match('~^--path=(.+)$~', $arg) != 0) + continue; + elseif ($arg == '--no-maintenance') + $_GET['maint'] = 0; + elseif ($arg == '--debug') + $is_debug = true; + elseif ($arg == '--backup') + $_POST['backup'] = 1; + elseif ($arg == '--template' && (file_exists($boarddir . '/template.php') || file_exists($boarddir . '/template.html') && !file_exists($modSettings['theme_dir'] . '/converted'))) + $_GET['conv'] = 1; + elseif ($i != 0) + { + echo 'SMF Command-line Upgrader +Usage: /path/to/php -f ' . basename(__FILE__) . ' -- [OPTION]... + + --language=LANG Reset the forum\'s language to LANG. + --no-maintenance Don\'t put the forum into maintenance mode. + --debug Output debugging information. + --backup Create backups of tables with "backup_" prefix.'; + echo "\n"; + exit; + } + } + + if (!php_version_check()) + print_error('Error: PHP ' . PHP_VERSION . ' does not match version requirements.', true); + if (!db_version_check()) + print_error('Error: ' . $databases[$db_type]['name'] . ' ' . $databases[$db_type]['version'] . ' does not match minimum requirements.', true); + + if (!empty($databases[$db_type]['alter_support']) && $smcFunc['db_query']('alter_boards', 'ALTER TABLE {db_prefix}boards ORDER BY id_board', array()) === false) + print_error('Error: The ' . $databases[$db_type]['name'] . ' account in Settings.php does not have sufficient privileges.', true); + + $check = @file_exists($modSettings['theme_dir'] . '/index.template.php') + && @file_exists($sourcedir . '/QueryString.php') + && @file_exists($sourcedir . '/ManageBoards.php'); + if (!$check && !isset($modSettings['smfVersion'])) + print_error('Error: Some files are missing or out-of-date.', true); + + // Do a quick version spot check. + $temp = substr(@implode('', @file($boarddir . '/index.php')), 0, 4096); + preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match); + if (empty($match[1]) || $match[1] != SMF_VERSION) + print_error('Error: Some files have not yet been updated properly.'); + + // Make sure Settings.php is writable. + if (!is_writable($boarddir . '/Settings.php')) + @chmod($boarddir . '/Settings.php', 0777); + if (!is_writable($boarddir . '/Settings.php')) + print_error('Error: Unable to obtain write access to "Settings.php".', true); + + // Make sure Settings.php is writable. + if (!is_writable($boarddir . '/Settings_bak.php')) + @chmod($boarddir . '/Settings_bak.php', 0777); + if (!is_writable($boarddir . '/Settings_bak.php')) + print_error('Error: Unable to obtain write access to "Settings_bak.php".'); + + if (isset($modSettings['agreement']) && (!is_writable($boarddir) || file_exists($boarddir . '/agreement.txt')) && !is_writable($boarddir . '/agreement.txt')) + print_error('Error: Unable to obtain write access to "agreement.txt".'); + elseif (isset($modSettings['agreement'])) + { + $fp = fopen($boarddir . '/agreement.txt', 'w'); + fwrite($fp, $modSettings['agreement']); + fclose($fp); + } + + // Make sure Themes is writable. + if (!is_writable($modSettings['theme_dir'])) + @chmod($modSettings['theme_dir'], 0777); + + if (!is_writable($modSettings['theme_dir']) && !isset($modSettings['smfVersion'])) + print_error('Error: Unable to obtain write access to "Themes".'); + + // Make sure cache directory exists and is writable! + $cachedir_temp = empty($cachedir) ? $boarddir . '/cache' : $cachedir; + if (!file_exists($cachedir_temp)) + @mkdir($cachedir_temp); + + if (!is_writable($cachedir_temp)) + @chmod($cachedir_temp, 0777); + + if (!is_writable($cachedir_temp)) + print_error('Error: Unable to obtain write access to "cache".', true); + + if (!file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php') && !isset($modSettings['smfVersion']) && !isset($_GET['lang'])) + print_error('Error: Unable to find language files!', true); + else + { + $temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php')), 0, 4096); + preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match); + + if (empty($match[1]) || $match[1] != SMF_LANG_VERSION) + print_error('Error: Language files out of date.', true); + if (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php')) + print_error('Error: Install language is missing for selected language.', true); + + // Otherwise include it! + require_once($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php'); + } + + // Make sure we skip the HTML for login. + $_POST['upcont'] = true; + $upcontext['current_step'] = 1; +} + +function print_error($message, $fatal = false) +{ + static $fp = null; + + if ($fp === null) + $fp = fopen('php://stderr', 'wb'); + + fwrite($fp, $message . "\n"); + + if ($fatal) + exit; +} + +function throw_error($message) +{ + global $upcontext; + + $upcontext['error_msg'] = $message; + $upcontext['sub_template'] = 'error_message'; + + return false; +} + +// Check files are writable - make them writable if necessary... +function makeFilesWritable(&$files) +{ + global $upcontext, $boarddir; + + if (empty($files)) + return true; + + $failure = false; + // On linux, it's easy - just use is_writable! + if (substr(__FILE__, 1, 2) != ':\\') + { + foreach ($files as $k => $file) + { + if (!is_writable($file)) + { + @chmod($file, 0755); + + // Well, 755 hopefully worked... if not, try 777. + if (!is_writable($file) && !@chmod($file, 0777)) + $failure = true; + // Otherwise remove it as it's good! + else + unset($files[$k]); + } + else + unset($files[$k]); + } + } + // Windows is trickier. Let's try opening for r+... + else + { + foreach ($files as $k => $file) + { + // Folders can't be opened for write... but the index.php in them can ;). + if (is_dir($file)) + $file .= '/index.php'; + + // Funny enough, chmod actually does do something on windows - it removes the read only attribute. + @chmod($file, 0777); + $fp = @fopen($file, 'r+'); + + // Hmm, okay, try just for write in that case... + if (!$fp) + $fp = @fopen($file, 'w'); + + if (!$fp) + $failure = true; + else + unset($files[$k]); + @fclose($fp); + } + } + + if (empty($files)) + return true; + + if (!isset($_SERVER)) + return !$failure; + + // What still needs to be done? + $upcontext['chmod']['files'] = $files; + + // If it's windows it's a mess... + if ($failure && substr(__FILE__, 1, 2) == ':\\') + { + $upcontext['chmod']['ftp_error'] = 'total_mess'; + + return false; + } + // We're going to have to use... FTP! + elseif ($failure) + { + // Load any session data we might have... + if (!isset($_POST['ftp_username']) && isset($_SESSION['installer_temp_ftp'])) + { + $upcontext['chmod']['server'] = $_SESSION['installer_temp_ftp']['server']; + $upcontext['chmod']['port'] = $_SESSION['installer_temp_ftp']['port']; + $upcontext['chmod']['username'] = $_SESSION['installer_temp_ftp']['username']; + $upcontext['chmod']['password'] = $_SESSION['installer_temp_ftp']['password']; + $upcontext['chmod']['path'] = $_SESSION['installer_temp_ftp']['path']; + } + // Or have we submitted? + elseif (isset($_POST['ftp_username'])) + { + $upcontext['chmod']['server'] = $_POST['ftp_server']; + $upcontext['chmod']['port'] = $_POST['ftp_port']; + $upcontext['chmod']['username'] = $_POST['ftp_username']; + $upcontext['chmod']['password'] = $_POST['ftp_password']; + $upcontext['chmod']['path'] = $_POST['ftp_path']; + } + + if (isset($upcontext['chmod']['username'])) + { + $ftp = new ftp_connection($upcontext['chmod']['server'], $upcontext['chmod']['port'], $upcontext['chmod']['username'], $upcontext['chmod']['password']); + + if ($ftp->error === false) + { + // Try it without /home/abc just in case they messed up. + if (!$ftp->chdir($upcontext['chmod']['path'])) + { + $upcontext['chmod']['ftp_error'] = $ftp->last_message; + $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $upcontext['chmod']['path'])); + } + } + } + + if (!isset($ftp) || $ftp->error !== false) + { + if (!isset($ftp)) + $ftp = new ftp_connection(null); + // Save the error so we can mess with listing... + elseif ($ftp->error !== false && !isset($upcontext['chmod']['ftp_error'])) + $upcontext['chmod']['ftp_error'] = $ftp->last_message === null ? '' : $ftp->last_message; + + list ($username, $detect_path, $found_path) = $ftp->detect_path(dirname(__FILE__)); + + if ($found_path || !isset($upcontext['chmod']['path'])) + $upcontext['chmod']['path'] = $detect_path; + + if (!isset($upcontext['chmod']['username'])) + $upcontext['chmod']['username'] = $username; + + return false; + } + else + { + // We want to do a relative path for FTP. + if (!in_array($upcontext['chmod']['path'], array('', '/'))) + { + $ftp_root = strtr($boarddir, array($upcontext['chmod']['path'] => '')); + if (substr($ftp_root, -1) == '/' && ($upcontext['chmod']['path'] == '' || substr($upcontext['chmod']['path'], 0, 1) == '/')) + $ftp_root = substr($ftp_root, 0, -1); + } + else + $ftp_root = $boarddir; + + // Save the info for next time! + $_SESSION['installer_temp_ftp'] = array( + 'server' => $upcontext['chmod']['server'], + 'port' => $upcontext['chmod']['port'], + 'username' => $upcontext['chmod']['username'], + 'password' => $upcontext['chmod']['password'], + 'path' => $upcontext['chmod']['path'], + 'root' => $ftp_root, + ); + + foreach ($files as $k => $file) + { + if (!is_writable($file)) + $ftp->chmod($file, 0755); + if (!is_writable($file)) + $ftp->chmod($file, 0777); + + // Assuming that didn't work calculate the path without the boarddir. + if (!is_writable($file)) + { + if (strpos($file, $boarddir) === 0) + { + $ftp_file = strtr($file, array($_SESSION['installer_temp_ftp']['root'] => '')); + $ftp->chmod($ftp_file, 0755); + if (!is_writable($file)) + $ftp->chmod($ftp_file, 0777); + // Sometimes an extra slash can help... + $ftp_file = '/' . $ftp_file; + if (!is_writable($file)) + $ftp->chmod($ftp_file, 0755); + if (!is_writable($file)) + $ftp->chmod($ftp_file, 0777); + } + } + + if (is_writable($file)) + unset($files[$k]); + } + + $ftp->close(); + } + } + + // What remains? + $upcontext['chmod']['files'] = $files; + + if (empty($files)) + return true; + + return false; +} + +/****************************************************************************** +******************* Templates are below this point **************************** +******************************************************************************/ + +// This is what is displayed if there's any chmod to be done. If not it returns nothing... +function template_chmod() +{ + global $upcontext, $upgradeurl, $settings; + + // Don't call me twice! + if (!empty($upcontext['chmod_called'])) + return; + + $upcontext['chmod_called'] = true; + + // Nothing? + if (empty($upcontext['chmod']['files']) && empty($upcontext['chmod']['ftp_error'])) + return; + + //!!! Temporary! + $txt['error_ftp_no_connect'] = 'Unable to connect to FTP server with this combination of details.'; + $txt['ftp_login'] = 'Your FTP connection information'; + $txt['ftp_login_info'] = 'This web installer needs your FTP information in order to automate the installation for you. Please note that none of this information is saved in your installation, it is just used to setup SMF.'; + $txt['ftp_server'] = 'Server'; + $txt['ftp_server_info'] = 'The address (often localhost) and port for your FTP server.'; + $txt['ftp_port'] = 'Port'; + $txt['ftp_username'] = 'Username'; + $txt['ftp_username_info'] = 'The username to login with. This will not be saved anywhere.'; + $txt['ftp_password'] = 'Password'; + $txt['ftp_password_info'] = 'The password to login with. This will not be saved anywhere.'; + $txt['ftp_path'] = 'Install Path'; + $txt['ftp_path_info'] = 'This is the relative path you use in your FTP client (more help).'; + $txt['ftp_path_found_info'] = 'The path in the box above was automatically detected.'; + $txt['ftp_path_help'] = 'Your FTP path is the path you see when you log in to your FTP client. It commonly starts with "www", "public_html", or "httpdocs" - but it should include the directory SMF is in too, such as "/public_html/forum". It is different from your URL and full path.

    Files in this path may be overwritten, so make sure it\'s correct.'; + $txt['ftp_path_help_close'] = 'Close'; + $txt['ftp_connect'] = 'Connect'; + + // Was it a problem with Windows? + if (!empty($upcontext['chmod']['ftp_error']) && $upcontext['chmod']['ftp_error'] == 'total_mess') + { + echo ' +
    +
    The following files need to be writable to continue the upgrade. Please ensure the Windows permissions are correctly set to allow this:
    +
      +
    • ' . implode('
    • +
    • ', $upcontext['chmod']['files']). '
    • +
    +
    '; + + return false; + } + + echo ' +
    +

    Your FTP connection information

    +

    The upgrader can fix any issues with file permissions to make upgrading as simple as possible. Simply enter your connection information below or alternatively click here for a list of files which need to be changed.

    + '; + + if (!empty($upcontext['chmod']['ftp_error'])) + echo ' +
    +
    + The following error was encountered when trying to connect:
    +
    + ', $upcontext['chmod']['ftp_error'], ' +
    +
    +
    '; + + if (empty($upcontext['chmod_in_form'])) + echo ' +
    '; + + echo ' + + + + + + + + + + + + + + +
    +
    + +
    ', $txt['ftp_server_info'], '
    +
    + +
    ', $txt['ftp_username_info'], '
    +
    + +
    ', $txt['ftp_password_info'], '
    +
    + +
    ', !empty($upcontext['chmod']['path']) ? $txt['ftp_path_found_info'] : $txt['ftp_path_info'], '
    +
    + +
    +
    '; + + if (empty($upcontext['chmod_in_form'])) + echo ' + '; +} + +function template_upgrade_above() +{ + global $modSettings, $txt, $smfsite, $settings, $upcontext, $upgradeurl; + + echo ' + + + + + ', $txt['upgrade_upgrade_utility'], ' + + + + + + + +
    +
    +
    +

    ', $txt['upgrade_progress'], '

    +
      '; + + foreach ($upcontext['steps'] as $num => $step) + echo ' +
    • ', $txt['upgrade_step'], ' ', $step[0], ': ', $step[1], '
    • '; + + echo ' +
    +
    +
    +
    +
    ', $upcontext['overall_percent'], '%
    +
     
    +
    ', $txt['upgrade_overall_progress'], '
    +
    + '; + + if (isset($upcontext['step_progress'])) + echo ' +
    +
    ', $upcontext['step_progress'], '%
    +
     
    +
    ', $txt['upgrade_step_progress'], '
    +
    + '; + + echo ' +
    ', isset($upcontext['substep_progress_name']) ? trim(strtr($upcontext['substep_progress_name'], array('.' => ''))) : '', ':
    +
    +
    ', isset($upcontext['substep_progress']) ? $upcontext['substep_progress'] : '', '%
    +
     
    +
    '; + + // How long have we been running this? + $elapsed = time() - $upcontext['started']; + $mins = (int) ($elapsed / 60); + $seconds = $elapsed - $mins * 60; + echo ' +
    ', $txt['upgrade_time_elapsed'], ': + ', $mins, ' ', $txt['upgrade_time_mins'], ', ', $seconds, ' ', $txt['upgrade_time_secs'], '. +
    '; + echo ' +
    +
    +

    ', $upcontext['page_title'], '

    +
    +
    '; +} + +function template_upgrade_below() +{ + global $upcontext, $txt; + + if (!empty($upcontext['pause'])) + echo ' + ', $txt['upgrade_incomplete'], '.
    + +

    ', $txt['upgrade_not_quite_done'], '

    +

    + ', $txt['upgrade_paused_overload'], ' +

    '; + + if (!empty($upcontext['custom_warning'])) + echo ' +
    +
    !!
    + ', $txt['upgrade_note'], '
    +
    ', $upcontext['custom_warning'], '
    +
    '; + + echo ' +
    '; + + if (!empty($upcontext['continue'])) + echo ' + '; + if (!empty($upcontext['skip'])) + echo ' + '; + + echo ' +
    + +
    +
    +
    +
    +
    + + +'; + + // Are we on a pause? + if (!empty($upcontext['pause'])) + { + echo ' + '; + } +} + +function template_xml_above() +{ + global $upcontext; + + echo '<', '?xml version="1.0" encoding="ISO-8859-1"?', '> + '; + + if (!empty($upcontext['get_data'])) + foreach ($upcontext['get_data'] as $k => $v) + echo ' + ', $v, ''; +} + +function template_xml_below() +{ + global $upcontext; + + echo ' + '; +} + +function template_error_message() +{ + global $upcontext; + + echo ' +
    +
    + ', $upcontext['error_msg'], ' +
    +
    + Click here to try again. +
    '; +} + +function template_welcome_message() +{ + global $upcontext, $modSettings, $upgradeurl, $disable_security, $settings, $txt; + + echo ' + + +

    ', sprintf($txt['upgrade_ready_proceed'], SMF_VERSION), '

    +
    + '; + + $upcontext['chmod_in_form'] = true; + template_chmod(); + + // For large, pre 1.1 RC2 forums give them a warning about the possible impact of this upgrade! + if ($upcontext['is_large_forum']) + echo ' +
    +
    !!
    + ', $txt['upgrade_warning'], '
    +
    + ', $txt['upgrade_warning_lots_data'], ' +
    +
    '; + + // A warning message? + if (!empty($upcontext['warning'])) + echo ' +
    +
    !!
    + ', $txt['upgrade_warning'], '
    +
    + ', $upcontext['warning'], ' +
    +
    '; + + // Paths are incorrect? + echo ' +
    +
    !!
    + ', $txt['upgrade_critical_error'], '
    +
    + ', $txt['upgrade_error_script_js'], ' +
    +
    '; + + // Is there someone already doing this? + if (!empty($upcontext['user']['id']) && (time() - $upcontext['started'] < 72600 || time() - $upcontext['updated'] < 3600)) + { + $ago = time() - $upcontext['started']; + if ($ago < 60) + $ago = $ago . ' seconds'; + elseif ($ago < 3600) + $ago = (int) ($ago / 60) . ' minutes'; + else + $ago = (int) ($ago / 3600) . ' hours'; + + $active = time() - $upcontext['updated']; + if ($active < 60) + $updated = $active . ' seconds'; + elseif ($active < 3600) + $updated = (int) ($active / 60) . ' minutes'; + else + $updated = (int) ($active / 3600) . ' hours'; + + echo ' +
    +
    !!
    + ', $txt['upgrade_warning'], '
    +
    + "', $upcontext['user']['name'], '" has been running the upgrade script for the last ', $ago, ' - and was last active ', $updated, ' ago.'; + + if ($active < 600) + echo ' + We recommend that you do not run this script unless you are sure that ', $upcontext['user']['name'], ' has completed their upgrade.'; + + if ($active > $upcontext['inactive_timeout']) + echo ' +

    You can choose to either run the upgrade again from the beginning - or alternatively continue from the last step reached during the last upgrade.'; + else + echo ' +

    This upgrade script cannot be run until ', $upcontext['user']['name'], ' has been inactive for at least ', ($upcontext['inactive_timeout'] > 120 ? round($upcontext['inactive_timeout'] / 60, 1) . ' minutes!' : $upcontext['inactive_timeout'] . ' seconds!'); + + echo ' +
    +
    '; + } + + echo ' + Admin Login: ', $disable_security ? '(DISABLED)' : '', ' +

    For security purposes please login with your admin account to proceed with the upgrade.

    + + + + + + + + + '; + + // Can they continue? + if (!empty($upcontext['user']['id']) && time() - $upcontext['user']['updated'] >= $upcontext['inactive_timeout'] && $upcontext['user']['step'] > 1) + { + echo ' + + + '; + } + + echo ' +
    Username: + '; + + if (!empty($upcontext['username_incorrect'])) + echo ' +
    Username Incorrect
    '; + + echo ' +
    Password: + + '; + + if (!empty($upcontext['password_failed'])) + echo ' +
    Password Incorrect
    '; + + echo ' +
    + +

    + + Note: If necessary the above security check can be bypassed for users who may administrate a server but not have admin rights on the forum. In order to bypass the above check simply open "upgrade.php" in a text editor and replace "$disable_security = 0;" with "$disable_security = 1;" and refresh this page. + + + '; + + // Say we want the continue button! + $upcontext['continue'] = !empty($upcontext['user']['id']) && time() - $upcontext['user']['updated'] < $upcontext['inactive_timeout'] ? 2 : 1; + + // This defines whether javascript is going to work elsewhere :D + echo ' + '; +} + +function template_upgrade_options() +{ + global $upcontext, $modSettings, $upgradeurl, $disable_security, $settings, $boarddir, $db_prefix, $mmessage, $mtitle, $db_type; + + echo ' +

    Before the upgrade gets underway please review the options below - and hit continue when you\'re ready to begin.

    + '; + + // Warning message? + if (!empty($upcontext['upgrade_options_warning'])) + echo ' +
    +
    !!
    + Warning!
    +
    + ', $upcontext['upgrade_options_warning'], ' +
    +
    '; + + echo ' + + + + + + + + + + + + + + + + + + + + + +
    + + + ', isset($modSettings['smfVersion']) ? '' : ' (recommended!)', ' +
    + + + (Customize) + +
    + + + +
    + + + +
    + + + +
    + '; + + // We need a normal continue button here! + $upcontext['continue'] = 1; +} + +// Template for the database backup tool/ +function template_backup_database() +{ + global $upcontext, $modSettings, $upgradeurl, $disable_security, $settings, $support_js, $is_debug; + + echo ' +

    Please wait while a backup is created. For large forums this may take some time!

    '; + + echo ' + + + Completed ', $upcontext['cur_table_num'], ' out of ', $upcontext['table_count'], ' tables. + '; + + // Dont any tables so far? + if (!empty($upcontext['previous_tables'])) + foreach ($upcontext['previous_tables'] as $table) + echo ' +
    Completed Table: "', $table, '".'; + + echo ' +

    Current Table: "', $upcontext['cur_table_name'], '"

    +
    Backup Complete! Click Continue to Proceed.'; + + // Continue please! + $upcontext['continue'] = $support_js ? 2 : 1; + + // If javascript allows we want to do this using XML. + if ($support_js) + { + echo ' + '; + } +} + +function template_backup_xml() +{ + global $upcontext, $settings, $options, $txt; + + echo ' + ', $upcontext['cur_table_name'], '
    '; +} + +// Here is the actual "make the changes" template! +function template_database_changes() +{ + global $upcontext, $modSettings, $upgradeurl, $disable_security, $settings, $support_js, $is_debug, $timeLimitThreshold; + + echo ' +

    Executing database changes

    +

    Please be patient - this may take some time on large forums. The time elapsed increments from the server to show progress is being made!

    '; + + echo ' + + '; + + // No javascript looks rubbish! + if (!$support_js) + { + foreach ($upcontext['actioned_items'] as $num => $item) + { + if ($num != 0) + echo ' Successful!'; + echo '
    ' . $item; + } + if (!empty($upcontext['changes_complete'])) + echo ' Successful!

    Database Updates Complete! Click Continue to Proceed.
    '; + } + else + { + // Tell them how many files we have in total. + if ($upcontext['file_count'] > 1) + echo ' + Executing upgrade script ', $upcontext['cur_file_num'], ' of ', $upcontext['file_count'], '.'; + + echo ' +

    Executing: "', $upcontext['current_item_name'], '" (', $upcontext['current_item_num'], ' of ', $upcontext['total_items'], '', $upcontext['file_count'] > 1 ? ' - of this script' : '', ')

    +
    Database Updates Complete! Click Continue to Proceed.'; + + if ($is_debug) + { + echo ' +
    + +
    '; + } + } + + // Place for the XML error message. + echo ' +
    +
    !!
    + Error!
    +
    ', isset($upcontext['error_message']) ? $upcontext['error_message'] : 'Unknown Error!', '
    +
    '; + + // We want to continue at some point! + $upcontext['continue'] = $support_js ? 2 : 1; + + // If javascript allows we want to do this using XML. + if ($support_js) + { + echo ' + '; + } + return; +} + +function template_database_xml() +{ + global $upcontext, $settings, $options, $txt; + + echo ' + ', $upcontext['cur_file_name'], ' + ', $upcontext['current_item_name'], ' + ', $upcontext['current_debug_item_name'], ''; + + if (!empty($upcontext['error_message'])) + echo ' + ', $upcontext['error_message'], ''; +} + +function template_clean_mods() +{ + global $upcontext, $modSettings, $upgradeurl, $disable_security, $settings, $boarddir, $db_prefix, $boardurl; + + $upcontext['chmod_in_form'] = true; + + echo ' +

    SMF has detected some packages which were installed but not fully removed prior to upgrade. We recommend you remove the following mods and reinstall upon completion of the upgrade.

    + '; + + // In case it's required. + template_chmod(); + + echo ' + + + + + + + + '; + + foreach ($upcontext['packages'] as $package) + { + echo ' + + + + + + + '; + } + echo ' +
    Modification NameVersionFiles AffectedStatusFix?
    ', $package['name'], '', $package['version'], '', $package['file_count'], ' [details]', $package['status'], ' + + +
    + '; + + // Files to make writable? + if (!empty($upcontext['writable_files'])) + echo ' + '; + + // We'll want a continue button... + if (empty($upcontext['chmod']['files'])) + $upcontext['continue'] = 1; +} + +// Finished with the mods - let them know what we've done. +function template_cleanup_done() +{ + global $upcontext, $modSettings, $upgradeurl, $disable_security, $settings, $boarddir, $db_prefix, $boardurl; + + echo ' +

    SMF has attempted to fix and reinstall mods as required. We recommend you visit the package manager upon completing upgrade to check the status of your modifications.

    + + + + + '; + + foreach ($upcontext['packages'] as $package) + { + echo ' + + + '; + } + echo ' +
    Actions Completed:
    ', $package['name'], '... ', $package['result'], '
    + '; + + // We'll want a continue button... + $upcontext['continue'] = 1; +} + +// Do they want to upgrade their templates? +function template_upgrade_templates() +{ + global $upcontext, $modSettings, $upgradeurl, $disable_security, $settings, $boarddir, $db_prefix, $boardurl; + + echo ' +

    There have been numerous language and template changes since the previous version of SMF. On this step the upgrader can attempt to automatically make these changes in your templates to save you from doing so manually.

    + '; + + // Any files need to be writable? + $upcontext['chmod_in_form'] = true; + template_chmod(); + + // Language/Template files need an update? + if ($upcontext['temp_progress'] == 0 && !$upcontext['is_test'] && (!empty($upcontext['languages']) || !empty($upcontext['themes']))) + { + echo ' + The following template files will be updated to ensure they are compatible with this version of SMF. Note that this can only fix a limited number of compatibility issues and in general you should seek out the latest version of these themes/language files. + + + + + '; + + foreach ($upcontext['languages'] as $language) + { + echo ' + + + + '; + } + + foreach ($upcontext['themes'] as $theme) + { + echo ' + + + + '; + } + + echo ' +
    AreaChanges Required
    + "', $language['name'], '" Language Pack +
    ('; + + foreach ($language['files'] as $k => $file) + echo $file['name'], $k + 1 != count($language['files']) ? ', ' : ')'; + + echo ' +
    +
    ', $language['edit_count'] == 0 ? 1 : $language['edit_count'], '
    + "', $theme['name'], '" Theme +
    ('; + + foreach ($theme['files'] as $k => $file) + echo $file['name'], $k + 1 != count($theme['files']) ? ', ' : ')'; + + echo ' +
    +
    ', $theme['edit_count'] == 0 ? 1 : $theme['edit_count'], '
    '; + } + else + { + $langFiles = 0; + $themeFiles = 0; + if (!empty($upcontext['languages'])) + foreach ($upcontext['languages'] as $lang) + $langFiles += count($lang['files']); + if (!empty($upcontext['themes'])) + foreach ($upcontext['themes'] as $theme) + $themeFiles += count($theme['files']); + echo sprintf('Found %d language files and %d templates requiring an update so far.', $langFiles, $themeFiles) . '
    '; + + // What we're currently doing? + if (!empty($upcontext['current_message'])) + echo ' + ', $upcontext['current_message']; + } + + echo ' + '; + + if (!empty($upcontext['languages'])) + echo ' + '; + if (!empty($upcontext['themes'])) + echo ' + '; + if (!empty($upcontext['writable_files'])) + echo ' + '; + + // Offer them the option to upgrade from YaBB SE? + if (!empty($upcontext['can_upgrade_yabbse'])) + echo ' +

    '; + + // We'll want a continue button... assuming chmod is OK (Otherwise let them use connect!) + if (empty($upcontext['chmod']['files']) || $upcontext['is_test']) + $upcontext['continue'] = 1; +} + +function template_upgrade_complete() +{ + global $upcontext, $modSettings, $upgradeurl, $disable_security, $settings, $boarddir, $db_prefix, $boardurl; + + echo ' +

    That wasn\'t so hard, was it? Now you are ready to use your installation of SMF. Hope you like it!

    + '; + + if (!empty($upcontext['can_delete_script'])) + echo ' + (doesn\'t work on all servers.) + +
    '; + + echo '
    + If you had any problems with this upgrade, or have any problems using SMF, please don\'t hesitate to look to us for assistance.
    +
    + Best of luck,
    + Simple Machines'; +} + +?> \ No newline at end of file diff --git a/upgrade_1-0.sql b/upgrade_1-0.sql new file mode 100644 index 0000000..89349c3 --- /dev/null +++ b/upgrade_1-0.sql @@ -0,0 +1,2017 @@ +/* ATTENTION: You don't need to run or use this file! The upgrade.php script does everything for you! */ + +/******************************************************************************/ +--- Creating new tables and inserting default data... +/******************************************************************************/ + +---# Creating "themes"... +CREATE TABLE IF NOT EXISTS {$db_prefix}themes ( + ID_MEMBER mediumint(8) NOT NULL default '0', + ID_THEME tinyint(4) unsigned NOT NULL default '1', + variable tinytext NOT NULL default '', + value text NOT NULL default '', + PRIMARY KEY (ID_MEMBER, ID_THEME, variable(30)) +) ENGINE=MyISAM; + +ALTER TABLE {$db_prefix}themes +CHANGE COLUMN ID_MEMBER ID_MEMBER mediumint(8) NOT NULL default '0'; + +ALTER TABLE {$db_prefix}themes +CHANGE COLUMN value value text NOT NULL default ''; + +INSERT IGNORE INTO {$db_prefix}themes + (ID_MEMBER, ID_THEME, variable, value) +VALUES (0, 1, 'name', 'SMF Default Theme'), + (0, 1, 'theme_url', '{$boardurl}/Themes/default'), + (0, 1, 'images_url', '{$boardurl}/Themes/default/images'), + (0, 1, 'theme_dir', '{$sboarddir}/Themes/default'), + (0, 1, 'allow_no_censored', '0'), + (0, 1, 'additional_options_collapsable', '1'), + (0, 2, 'name', 'Classic YaBB SE Theme'), + (0, 2, 'theme_url', '{$boardurl}/Themes/classic'), + (0, 2, 'images_url', '{$boardurl}/Themes/classic/images'), + (0, 2, 'theme_dir', '{$sboarddir}/Themes/classic'); +---# + +---# Creating "collapsed_categories"... +CREATE TABLE IF NOT EXISTS {$db_prefix}collapsed_categories ( + ID_CAT tinyint(4) unsigned NOT NULL default '0', + ID_MEMBER mediumint(8) unsigned NOT NULL default '0', + PRIMARY KEY (ID_CAT, ID_MEMBER) +) ENGINE=MyISAM; +---# + +---# Creating and verifying "permissions"... +CREATE TABLE IF NOT EXISTS {$db_prefix}permissions ( + ID_GROUP smallint(6) NOT NULL default '0', + permission varchar(30) NOT NULL default '', + addDeny tinyint(4) NOT NULL default '1', + PRIMARY KEY (ID_GROUP, permission) +) ENGINE=MyISAM; + +ALTER TABLE {$db_prefix}permissions +ADD addDeny tinyint(4) NOT NULL default '1'; +ALTER TABLE {$db_prefix}permissions +CHANGE COLUMN permission permission varchar(30) NOT NULL default ''; + +UPDATE IGNORE {$db_prefix}permissions +SET + permission = REPLACE(permission, 'profile_own_identity', 'profile_identity_own'), + permission = REPLACE(permission, 'profile_any_identity', 'profile_identity_any'), + permission = REPLACE(permission, 'profile_own_extra', 'profile_extra_own'), + permission = REPLACE(permission, 'profile_any_extra', 'profile_extra_any'), + permission = REPLACE(permission, 'profile_own_title', 'profile_title_own'), + permission = REPLACE(permission, 'profile_any_title', 'profile_title_any'), + permission = REPLACE(permission, 'im_read', 'pm_read'), + permission = REPLACE(permission, 'im_send', 'pm_send'); +---# + +---# Inserting data into "permissions"... +INSERT INTO {$db_prefix}permissions + (ID_GROUP, permission) +VALUES (-1, 'search_posts'), (-1, 'calendar_view'), (-1, 'view_stats'), (-1, 'profile_view_any'), + (2, 'calendar_post'), (2, 'calendar_edit_any'), (2, 'calendar_edit_own'); +---# + +---# Creating and verifying "board_permissions"... +CREATE TABLE IF NOT EXISTS {$db_prefix}board_permissions ( + ID_GROUP smallint(6) NOT NULL default '0', + ID_BOARD smallint(5) unsigned NOT NULL default '0', + permission varchar(30) NOT NULL default '', + addDeny tinyint(4) NOT NULL default '1', + PRIMARY KEY (ID_GROUP, ID_BOARD, permission) +) ENGINE=MyISAM; + +ALTER TABLE {$db_prefix}board_permissions +ADD addDeny tinyint(4) NOT NULL default '1'; +ALTER TABLE {$db_prefix}board_permissions +CHANGE COLUMN permission permission varchar(30) NOT NULL default ''; +---# + +---# Inserting data into "board_permissions"... +INSERT INTO {$db_prefix}board_permissions + (ID_GROUP, ID_BOARD, permission) +VALUES (-1, 0, 'poll_view'), (3, 0, 'make_sticky'), (3, 0, 'lock_any'), + (3, 0, 'remove_any'), (3, 0, 'move_any'), (3, 0, 'merge_any'), (3, 0, 'split_any'), + (3, 0, 'delete_any'), (3, 0, 'modify_any'), (2, 0, 'make_sticky'), (2, 0, 'lock_any'), + (2, 0, 'remove_any'), (2, 0, 'move_any'), (2, 0, 'merge_any'), (2, 0, 'split_any'), + (2, 0, 'delete_any'), (2, 0, 'modify_any'), (2, 0, 'poll_lock_any'), (2, 0, 'poll_lock_any'), + (2, 0, 'poll_add_any'), (2, 0, 'poll_remove_any'), (2, 0, 'poll_remove_any'); +INSERT IGNORE INTO {$db_prefix}board_permissions + (ID_GROUP, ID_BOARD, permission) +VALUES (3, 0, 'moderate_board'), (2, 0, 'moderate_board'); +---# + +---# Creating "moderators"... +CREATE TABLE IF NOT EXISTS {$db_prefix}moderators ( + ID_BOARD smallint(5) unsigned NOT NULL default '0', + ID_MEMBER mediumint(8) unsigned NOT NULL default '0', + PRIMARY KEY (ID_BOARD, ID_MEMBER) +) ENGINE=MyISAM; +---# + +---# Creating "attachments"... +CREATE TABLE IF NOT EXISTS {$db_prefix}attachments ( + ID_ATTACH int(11) unsigned NOT NULL auto_increment, + ID_MSG int(10) unsigned NOT NULL default '0', + ID_MEMBER int(10) unsigned NOT NULL default '0', + filename tinytext NOT NULL default '', + size int(10) unsigned NOT NULL default '0', + downloads mediumint(8) unsigned NOT NULL default '0', + PRIMARY KEY (ID_ATTACH), + UNIQUE ID_MEMBER (ID_MEMBER, ID_ATTACH), + KEY ID_MSG (ID_MSG) +) ENGINE=MyISAM; +---# + +---# Creating "log_notify"... +CREATE TABLE IF NOT EXISTS {$db_prefix}log_notify ( + ID_MEMBER mediumint(8) unsigned NOT NULL default '0', + ID_TOPIC mediumint(8) unsigned NOT NULL default '0', + ID_BOARD smallint(5) unsigned NOT NULL default '0', + sent tinyint(1) unsigned NOT NULL default '0', + PRIMARY KEY (ID_MEMBER, ID_TOPIC, ID_BOARD) +) ENGINE=MyISAM; +---# + +---# Creating "log_polls"... +CREATE TABLE IF NOT EXISTS {$db_prefix}log_polls ( + ID_POLL mediumint(8) unsigned NOT NULL default '0', + ID_MEMBER mediumint(8) unsigned NOT NULL default '0', + ID_CHOICE tinyint(4) unsigned NOT NULL default '0', + PRIMARY KEY (ID_POLL, ID_MEMBER, ID_CHOICE) +) ENGINE=MyISAM; +---# + +---# Creating "log_actions"... +CREATE TABLE IF NOT EXISTS {$db_prefix}log_actions ( + ID_ACTION int(10) unsigned NOT NULL auto_increment, + logTime int(10) unsigned NOT NULL default '0', + ID_MEMBER mediumint(8) unsigned NOT NULL default '0', + IP tinytext NOT NULL default '', + action varchar(30) NOT NULL default '', + extra text NOT NULL default '', + PRIMARY KEY (ID_ACTION), + KEY logTime (logTime), + KEY ID_MEMBER (ID_MEMBER) +) ENGINE=MyISAM; +---# + +---# Creating "poll_choices"... +CREATE TABLE IF NOT EXISTS {$db_prefix}poll_choices ( + ID_POLL mediumint(8) unsigned NOT NULL default '0', + ID_CHOICE tinyint(4) unsigned NOT NULL default '0', + label tinytext NOT NULL default '', + votes smallint(5) unsigned NOT NULL default '0', + PRIMARY KEY (ID_POLL, ID_CHOICE) +) ENGINE=MyISAM; +---# + +---# Creating "smileys"... +CREATE TABLE IF NOT EXISTS {$db_prefix}smileys ( + id_smiley smallint(5) unsigned NOT NULL auto_increment, + code varchar(30) NOT NULL default '', + filename varchar(48) NOT NULL default '', + description varchar(80) NOT NULL default '', + smileyRow tinyint(4) unsigned NOT NULL default '0', + smileyOrder tinyint(4) unsigned NOT NULL default '0', + hidden tinyint(4) unsigned NOT NULL default '0', + PRIMARY KEY (id_smiley), + KEY smileyOrder (smileyOrder) +) ENGINE=MyISAM; +---# + +---# Loading default smileys... +INSERT IGNORE INTO {$db_prefix}smileys + (id_smiley, code, filename, description, smileyOrder, hidden) +VALUES (1, ':)', 'smiley.gif', 'Smiley', 0, 0), + (2, ';)', 'wink.gif', 'Wink', 1, 0), + (3, ':D', 'cheesy.gif', 'Cheesy', 2, 0), + (4, ';D', 'grin.gif', 'Grin', 3, 0), + (5, '>:(', 'angry.gif', 'Angry', 4, 0), + (6, ':(', 'sad.gif', 'Sad', 5, 0), + (7, ':o', 'shocked.gif', 'Shocked', 6, 0), + (8, '8)', 'cool.gif', 'Cool', 7, 0), + (9, '???', 'huh.gif', 'Huh', 8, 0), + (10, '::)', 'rolleyes.gif', 'Roll Eyes', 9, 0), + (11, ':P', 'tongue.gif', 'Tongue', 10, 0), + (12, ':-[', 'embarassed.gif', 'Embarrassed', 11, 0), + (13, ':-X', 'lipsrsealed.gif', 'Lips Sealed', 12, 0), + (14, ':-\\', 'undecided.gif', 'Undecided', 13, 0), + (15, ':-*', 'kiss.gif', 'Kiss', 14, 0), + (16, ':\'(', 'cry.gif', 'Cry', 15, 0), + (17, '>:D', 'evil.gif', 'Evil', 16, 1), + (18, '^-^', 'azn.gif', 'Azn', 17, 1), + (19, 'O0', 'afro.gif', 'Afro', 18, 1); +---# + +---# Dropping "log_search" and recreating it... +DROP TABLE IF EXISTS {$db_prefix}log_search; +CREATE TABLE {$db_prefix}log_search ( + ID_SEARCH tinyint(3) unsigned NOT NULL default '0', + ID_TOPIC mediumint(8) unsigned NOT NULL default '0', + ID_MSG int(10) unsigned NOT NULL default '0', + relevance smallint(5) unsigned NOT NULL default '0', + num_matches smallint(5) unsigned NOT NULL default '0', + PRIMARY KEY (ID_SEARCH, ID_TOPIC) +) ENGINE=MyISAM; +---# + +---# Dropping "sessions" and recreating it... +DROP TABLE IF EXISTS {$db_prefix}sessions; +CREATE TABLE {$db_prefix}sessions ( + session_id char(32) NOT NULL, + last_update int(10) unsigned NOT NULL, + data text NOT NULL, + PRIMARY KEY (session_id) +) ENGINE=MyISAM; +---# + +---# Verifying "settings"... +ALTER IGNORE TABLE {$db_prefix}settings +DROP PRIMARY KEY, +ADD PRIMARY KEY (variable(30)); +---# + +/******************************************************************************/ +--- Converting activity logs... +/******************************************************************************/ + +---# Converting "log_online"... +DROP TABLE IF EXISTS {$db_prefix}log_online; +CREATE TABLE {$db_prefix}log_online ( + session char(32) NOT NULL default ' ', + logTime timestamp, + ID_MEMBER mediumint(8) unsigned NOT NULL default '0', + ip int(11) unsigned NOT NULL default '0', + url text NOT NULL default '', + PRIMARY KEY (session), + KEY online (logTime, ID_MEMBER), + KEY ID_MEMBER (ID_MEMBER) +) ENGINE=MyISAM; +---# + +---# Converting "log_floodcontrol"... +DROP TABLE IF EXISTS {$db_prefix}log_floodcontrol; +CREATE TABLE {$db_prefix}log_floodcontrol ( + ip tinytext NOT NULL default '', + logTime int(10) unsigned NOT NULL default '0', + PRIMARY KEY (ip(16)), + KEY logTime (logTime) +) ENGINE=MyISAM; +---# + +---# Converting "log_karma"... +DROP TABLE IF EXISTS {$db_prefix}log_karma; +CREATE TABLE {$db_prefix}log_karma ( + ID_TARGET mediumint(8) unsigned NOT NULL default '0', + ID_EXECUTOR mediumint(8) unsigned NOT NULL default '0', + logTime int(10) unsigned NOT NULL default '0', + action tinyint(4) NOT NULL default '0', + PRIMARY KEY (ID_TARGET, ID_EXECUTOR), + KEY logTime (logTime) +) ENGINE=MyISAM; +---# + +---# Retiring "log_clicks"... +DROP TABLE IF EXISTS {$db_prefix}log_clicks; +---# + +---# Converting "log_notify"... +INSERT INTO {$db_prefix}log_notify +SELECT ID_MEMBER, ID_TOPIC, 0, notificationSent +FROM {$db_prefix}log_topics +WHERE notificationSent != 0; + +ALTER TABLE {$db_prefix}log_topics +DROP notificationSent; +---# + +---# Converting "log_errors"... +ALTER TABLE {$db_prefix}log_errors +CHANGE COLUMN ID_ERROR ID_ERROR mediumint(8) unsigned NOT NULL auto_increment, +ADD session char(32) NOT NULL default ' '; +---# + +---# Converting "log_boards"... +---{ +$request = upgrade_query(" + SELECT lmr.ID_BOARD, lmr.ID_MEMBER, lmr.logTime + FROM {$db_prefix}log_mark_read AS lmr + LEFT JOIN {$db_prefix}log_boards AS lb ON (lb.ID_BOARD = lmr.ID_BOARD AND lb.ID_MEMBER = lmr.ID_MEMBER) + WHERE lb.logTime < lmr.logTime"); +$replaceRows = ''; +while ($row = mysql_fetch_assoc($request)) + $replaceRows .= "($row[ID_BOARD], $row[ID_MEMBER], $row[logTime]),"; +mysql_free_result($request); +if (!empty($replaceRows)) +{ + $replaceRows = substr($replaceRows, 0, -1); + + upgrade_query(" + REPLACE INTO {$db_prefix}log_boards + (ID_BOARD, ID_MEMBER, logTime) + VALUES $replaceRows"); +} +---} +---# + +---# Converting "log_activity"... +ALTER TABLE {$db_prefix}log_activity +ADD date date NOT NULL default '0001-01-01'; + +ALTER TABLE {$db_prefix}log_activity +DROP PRIMARY KEY; + +UPDATE IGNORE {$db_prefix}log_activity +SET date = year * 10000 + month * 100 + day; + +ALTER TABLE {$db_prefix}log_activity +DROP day, +DROP month, +DROP year; + +ALTER TABLE {$db_prefix}log_activity +ADD INDEX hits (hits); +ALTER TABLE {$db_prefix}log_activity +ADD PRIMARY KEY (date); + +ALTER TABLE {$db_prefix}log_activity +CHANGE COLUMN hits hits mediumint(8) unsigned NOT NULL default '0', +CHANGE COLUMN topics topics smallint(5) unsigned NOT NULL default '0', +CHANGE COLUMN posts posts smallint(5) unsigned NOT NULL default '0', +CHANGE COLUMN registers registers smallint(5) unsigned NOT NULL default '0', +CHANGE COLUMN most_on most_on smallint(5) unsigned NOT NULL default '0'; +---# + +/******************************************************************************/ +--- Converting Boards and Categories... +/******************************************************************************/ + +---# Adding new columns to "boards"... +ALTER TABLE {$db_prefix}boards +CHANGE COLUMN count countPosts tinyint(4) NOT NULL default '0', +ADD lastUpdated int(11) unsigned NOT NULL default '0', +ADD ID_PARENT smallint(5) unsigned NOT NULL default '0', +ADD ID_LAST_MSG int(10) unsigned NOT NULL default '0', +ADD childLevel tinyint(4) unsigned NOT NULL default '0'; +---# + +---# Updating the structure of "boards"... +ALTER TABLE {$db_prefix}boards +CHANGE COLUMN boardOrder boardOrder smallint(5) NOT NULL default '0'; + +ALTER TABLE {$db_prefix}boards +DROP isAnnouncement; +ALTER TABLE {$db_prefix}boards +ADD ID_THEME tinyint(4) unsigned NOT NULL default '0'; +ALTER TABLE {$db_prefix}boards +ADD use_local_permissions tinyint(4) unsigned NOT NULL default '0'; +ALTER TABLE {$db_prefix}boards +ADD override_theme tinyint(4) unsigned NOT NULL default '0'; +---# + +---# Reindexing "boards" (part 1)... +ALTER TABLE {$db_prefix}boards +DROP INDEX ID_CAT, +DROP ID_LAST_TOPIC; +ALTER TABLE {$db_prefix}boards +DROP INDEX memberGroups; +---# + +---# Reindexing "boards" (part 2)... +ALTER TABLE {$db_prefix}boards +ADD INDEX lastUpdated (lastUpdated), +ADD INDEX memberGroups (memberGroups(48)), +ADD UNIQUE INDEX categories (ID_CAT, ID_BOARD); +---# + +---# Updating the column sizes on "boards"... +ALTER TABLE {$db_prefix}boards +DROP PRIMARY KEY, +CHANGE COLUMN ID_CAT ID_CAT tinyint(4) unsigned NOT NULL default '0', +CHANGE COLUMN numTopics numTopics mediumint(8) unsigned NOT NULL default '0', +CHANGE COLUMN numPosts numPosts mediumint(8) unsigned NOT NULL default '0', +CHANGE COLUMN description description text NOT NULL default '', +CHANGE COLUMN ID_BOARD ID_BOARD smallint(5) unsigned NOT NULL auto_increment PRIMARY KEY; +---# + +---# Updating access permissions... +---{ +$member_groups = getMemberGroups(); + +$result = upgrade_query(" + ALTER TABLE {$db_prefix}boards + ADD memberGroups varchar(128) NOT NULL default '-1,0'"); +if ($result !== false) +{ + $result = upgrade_query(" + SELECT TRIM(memberGroups) AS memberGroups, ID_CAT + FROM {$db_prefix}categories"); + while ($row = mysql_fetch_assoc($result)) + { + if (trim($row['memberGroups']) == '') + $groups = '-1,0,2'; + else + { + $memberGroups = array_unique(explode(',', $row['memberGroups'])); + $groups = array(2); + foreach ($memberGroups as $k => $check) + { + $memberGroups[$k] = trim($memberGroups[$k]); + if ($memberGroups[$k] == '' || !isset($member_groups[$memberGroups[$k]]) || $member_groups[$memberGroups[$k]] == 8) + continue; + + $groups[] = $member_groups[$memberGroups[$k]]; + } + + $groups = implode(',', array_unique($groups)); + } + + upgrade_query(" + UPDATE {$db_prefix}boards + SET memberGroups = '$groups', lastUpdated = " . time() . " + WHERE ID_CAT = $row[ID_CAT]"); + } +} +---} + +ALTER TABLE {$db_prefix}categories +DROP memberGroups; + +ALTER TABLE {$db_prefix}boards +CHANGE COLUMN memberGroups memberGroups varchar(128) NOT NULL default '-1,0'; +---# + +---# Converting "categories"... +ALTER TABLE {$db_prefix}categories +DROP PRIMARY KEY, +ADD canCollapse tinyint(1) NOT NULL default '1', +CHANGE COLUMN ID_CAT ID_CAT tinyint(4) unsigned NOT NULL auto_increment PRIMARY KEY; +---# + +---# Converting announcement permissions... +---{ +$request = upgrade_query(" + SHOW COLUMNS + FROM {$db_prefix}boards + LIKE 'notifyAnnouncements'"); +if (mysql_num_rows($request) > 0) +{ + $conversions = array( + 'moderate_forum' => array('manage_membergroups', 'manage_bans'), + 'admin_forum' => array('manage_permissions'), + 'edit_forum' => array('manage_boards', 'manage_smileys', 'manage_attachments'), + ); + foreach ($conversions as $original_permission => $new_permissions) + { + $setString = ''; + $result = upgrade_query(" + SELECT ID_GROUP, addDeny + FROM {$db_prefix}permissions + WHERE permission = '$original_permission'"); + while ($row = mysql_fetch_assoc($result)) + $setString .= " + ('" . implode("', $row[ID_GROUP], $row[addDeny]), + ('", $new_permissions) . "', $row[ID_GROUP], $row[addDeny]),"; + mysql_free_result($result); + + if ($setString != '') + upgrade_query(" + INSERT IGNORE INTO {$db_prefix}permissions + (permission, ID_GROUP, addDeny) + VALUES" . substr($setString, 0, -1)); + } +} +mysql_free_result($request); +---} + +DELETE FROM {$db_prefix}permissions +WHERE permission = 'edit_forum'; + +ALTER TABLE {$db_prefix}boards +DROP COLUMN notifyAnnouncements; +---# + +---# Converting board statistics... +---{ +$result = upgrade_query(" + SELECT MAX(m.ID_MSG) AS ID_LAST_MSG, t.ID_BOARD + FROM ({$db_prefix}messages AS m, {$db_prefix}topics AS t) + WHERE m.ID_MSG = t.ID_LAST_MSG + GROUP BY t.ID_BOARD"); +$last_msgs = array(); +while ($row = mysql_fetch_assoc($result)) + $last_msgs[] = $row['ID_LAST_MSG']; +mysql_free_result($result); + +if (!empty($last_msgs)) +{ + $result = upgrade_query(" + SELECT m.ID_MSG, m.posterTime, t.ID_BOARD + FROM ({$db_prefix}messages AS m, {$db_prefix}topics AS t) + WHERE t.ID_TOPIC = m.ID_TOPIC + AND m.ID_MSG IN (" . implode(',', $last_msgs) . ") + LIMIT " . count($last_msgs)); + while ($row = mysql_fetch_assoc($result)) + { + upgrade_query(" + UPDATE {$db_prefix}boards + SET ID_LAST_MSG = $row[ID_MSG], lastUpdated = " . (int) $row['posterTime'] . " + WHERE ID_BOARD = $row[ID_BOARD] + LIMIT 1"); + } + mysql_free_result($result); +} +---} +---# + +---# Converting "moderators"... +---{ +$request = upgrade_query(" + SHOW COLUMNS + FROM {$db_prefix}boards + LIKE 'moderators'"); +$do_moderators = mysql_num_rows($request) > 0; +mysql_free_result($request); + +if ($do_moderators) +{ + $result = upgrade_query(" + SELECT TRIM(moderators) AS moderators, ID_BOARD + FROM {$db_prefix}boards + WHERE TRIM(moderators) != ''"); + while ($row = mysql_fetch_assoc($result)) + { + $moderators = array_unique(explode(',', $row['moderators'])); + foreach ($moderators as $k => $dummy) + { + $moderators[$k] = addslashes(trim($moderators[$k])); + if ($moderators[$k] == '') + unset($moderators[$k]); + } + + if (!empty($moderators)) + { + upgrade_query(" + INSERT IGNORE INTO {$db_prefix}moderators + (ID_BOARD, ID_MEMBER) + SELECT $row[ID_BOARD], ID_MEMBER + FROM {$db_prefix}members + WHERE memberName IN ('" . implode("', '", $moderators) . "') + LIMIT " . count($moderators)); + } + } +} +---} + +ALTER TABLE {$db_prefix}boards +DROP moderators; +---# + +---# Updating board order... +---{ +$request = upgrade_query(" + SELECT c.ID_CAT, c.catOrder, b.ID_BOARD, b.boardOrder + FROM {$db_prefix}categories AS c + LEFT JOIN {$db_prefix}boards AS b ON (b.ID_CAT = c.ID_CAT) + ORDER BY c.catOrder, b.childLevel, b.boardOrder, b.ID_BOARD"); +$catOrder = -1; +$boardOrder = -1; +$curCat = -1; +while ($row = mysql_fetch_assoc($request)) +{ + if ($curCat != $row['ID_CAT']) + { + $curCat = $row['ID_CAT']; + if (++$catOrder != $row['catOrder']) + upgrade_query(" + UPDATE {$db_prefix}categories + SET catOrder = $catOrder + WHERE ID_CAT = $row[ID_CAT] + LIMIT 1"); + } + if (!empty($row['ID_BOARD']) && ++$boardOrder != $row['boardOrder']) + upgrade_query(" + UPDATE {$db_prefix}boards + SET boardOrder = $boardOrder + WHERE ID_BOARD = $row[ID_BOARD] + LIMIT 1"); +} +mysql_free_result($request); +---} +---# + +---# Fixing possible issues with board access (part 1)... +---{ +if (empty($modSettings['smfVersion']) || (substr($modSettings['smfVersion'], 0, 9) == '1.0 Beta ' && $modSettings['smfVersion'][9] <= 5)) +{ + $all_groups = array(); + $result = upgrade_query(" + SELECT ID_GROUP + FROM {$db_prefix}membergroups"); + while ($row = mysql_fetch_assoc($result)) + $all_groups[] = $row['ID_GROUP']; + mysql_free_result($result); + + $result = upgrade_query(" + SELECT ID_BOARD, memberGroups + FROM {$db_prefix}boards + WHERE FIND_IN_SET(0, memberGroups)"); + while ($row = mysql_fetch_assoc($result)) + { + upgrade_query(" + UPDATE {$db_prefix}boards + SET memberGroups = '" . implode(',', array_unique(array_merge(explode(',', $row['memberGroups']), $all_groups))) . "' + WHERE ID_BOARD = $row[ID_BOARD] + LIMIT 1"); + } + mysql_free_result($result); +} +---} +---# + +---# Fixing possible issues with board access. (part 2).. +UPDATE {$db_prefix}boards +SET memberGroups = SUBSTRING(memberGroups, 2) +WHERE SUBSTRING(memberGroups, 1, 1) = ','; + +UPDATE {$db_prefix}boards +SET memberGroups = SUBSTRING(memberGroups, 1, LENGTH(memberGroups) - 1) +WHERE SUBSTRING(memberGroups, LENGTH(memberGroups)) = ','; + +UPDATE {$db_prefix}boards +SET memberGroups = REPLACE(',,', ',', REPLACE(',,', ',', memberGroups)) +WHERE LOCATE(',,', memberGroups); +---# + +/******************************************************************************/ +--- Converting attachments, topics, and messages... +/******************************************************************************/ + +---# Converting "attachments"... +INSERT INTO {$db_prefix}attachments + (ID_MSG, filename, size) +SELECT ID_MSG, SUBSTRING(attachmentFilename, 1, 255), attachmentSize +FROM {$db_prefix}messages +WHERE attachmentFilename IS NOT NULL + AND attachmentFilename != ''; + +ALTER TABLE {$db_prefix}messages +DROP attachmentSize, +DROP attachmentFilename; +---# + +---# Updating "attachments"... +ALTER TABLE {$db_prefix}attachments +DROP INDEX ID_MEMBER, +ADD UNIQUE ID_MEMBER (ID_MEMBER, ID_ATTACH); + +ALTER TABLE {$db_prefix}attachments +CHANGE COLUMN size size int(10) unsigned NOT NULL default '0'; +---# + +---# Updating columns on "messages" (part 1)... +ALTER TABLE {$db_prefix}messages +DROP PRIMARY KEY, +CHANGE COLUMN ID_MSG ID_MSG int(10) unsigned NOT NULL auto_increment PRIMARY KEY; +---# + +---# Updating columns on "messages" (part 2)... +ALTER TABLE {$db_prefix}messages +CHANGE COLUMN ID_TOPIC ID_TOPIC mediumint(8) unsigned NOT NULL default '0'; +ALTER TABLE {$db_prefix}messages +CHANGE COLUMN smiliesEnabled smileysEnabled tinyint(4) NOT NULL default '1'; +---# + +---# Updating columns on "messages" (part 3)... +ALTER TABLE {$db_prefix}messages +CHANGE COLUMN posterTime posterTime int(10) unsigned NOT NULL default '0', +CHANGE COLUMN modifiedTime modifiedTime int(10) unsigned NOT NULL default '0'; + +ALTER TABLE {$db_prefix}messages +ADD INDEX participation (ID_MEMBER, ID_TOPIC); +ALTER TABLE {$db_prefix}messages +ADD INDEX ipIndex (posterIP(15), ID_TOPIC); +---# + +---# Updating columns on "messages" (part 4)... +ALTER TABLE {$db_prefix}messages +CHANGE COLUMN ID_MEMBER ID_MEMBER mediumint(8) unsigned NOT NULL default '0', +CHANGE COLUMN icon icon varchar(16) NOT NULL default 'xx'; + +ALTER TABLE {$db_prefix}messages +ADD INDEX ID_MEMBER (ID_MEMBER); +ALTER TABLE {$db_prefix}messages +ADD UNIQUE INDEX topic (ID_TOPIC, ID_MSG); +---# + +---# Updating columns on "messages" (part 5)... +ALTER TABLE {$db_prefix}messages +ADD COLUMN ID_BOARD smallint(5) unsigned NOT NULL default '0'; +---# + +---# Updating data in "messages"... +---{ +while (true) +{ + nextSubstep($substep); + + $request = upgrade_query(" + SELECT DISTINCT t.ID_BOARD, t.ID_TOPIC + FROM ({$db_prefix}messages AS m, {$db_prefix}topics AS t) + WHERE t.ID_TOPIC = m.ID_TOPIC + AND m.ID_BOARD = 0 + LIMIT 1400"); + $boards = array(); + while ($row = mysql_fetch_assoc($request)) + $boards[$row['ID_BOARD']][] = $row['ID_TOPIC']; + + foreach ($boards as $board => $topics) + upgrade_query(" + UPDATE {$db_prefix}messages + SET ID_BOARD = $board + WHERE ID_TOPIC IN (" . implode(', ', $topics) . ')'); + + if (mysql_num_rows($request) < 1400) + break; + + mysql_free_result($request); +} +---} +---# + +---# Cleaning up "messages"... +ALTER TABLE {$db_prefix}messages +ADD INDEX ID_BOARD (ID_BOARD); + +ALTER TABLE {$db_prefix}messages +DROP INDEX posterTime_2; +ALTER TABLE {$db_prefix}messages +DROP INDEX posterTime_3; + +ALTER TABLE {$db_prefix}messages +DROP INDEX ID_MEMBER_2; +ALTER TABLE {$db_prefix}messages +DROP INDEX ID_MEMBER_3; +---# + +---# Updating indexes on "topics" (part 1)... +ALTER TABLE {$db_prefix}topics +DROP INDEX ID_FIRST_MSG; + +ALTER TABLE {$db_prefix}topics +DROP INDEX ID_LAST_MSG; + +ALTER TABLE {$db_prefix}topics +ADD INDEX isSticky (isSticky); +---# + +---# Updating indexes on "topics" (part 2)... +ALTER IGNORE TABLE {$db_prefix}topics +ADD UNIQUE INDEX lastMessage (ID_LAST_MSG, ID_BOARD), +ADD UNIQUE INDEX firstMessage (ID_FIRST_MSG, ID_BOARD), +ADD UNIQUE INDEX poll (ID_POLL, ID_TOPIC); +---# + +---# Updating columns on "topics" (part 1)... +ALTER TABLE {$db_prefix}topics +DROP PRIMARY KEY, +CHANGE COLUMN ID_TOPIC ID_TOPIC mediumint(8) unsigned NOT NULL auto_increment PRIMARY KEY, +CHANGE COLUMN ID_BOARD ID_BOARD smallint(5) unsigned NOT NULL default '0'; +---# + +---# Updating columns on "topics" (part 2)... +ALTER TABLE {$db_prefix}topics +CHANGE COLUMN ID_MEMBER_STARTED ID_MEMBER_STARTED mediumint(8) unsigned NOT NULL default '0', +CHANGE COLUMN ID_MEMBER_UPDATED ID_MEMBER_UPDATED mediumint(8) unsigned NOT NULL default '0'; +---# + +---# Updating columns on "topics" (part 3)... +ALTER TABLE {$db_prefix}topics +CHANGE COLUMN ID_FIRST_MSG ID_FIRST_MSG int(10) unsigned NOT NULL default '0', +CHANGE COLUMN ID_LAST_MSG ID_LAST_MSG int(10) unsigned NOT NULL default '0'; +---# + +---# Updating columns on "topics" (part 4)... +ALTER TABLE {$db_prefix}topics +CHANGE COLUMN ID_POLL ID_POLL mediumint(8) unsigned NOT NULL default '0'; +---# + +/******************************************************************************/ +--- Converting members and personal messages... +/******************************************************************************/ + +---# Updating data in "members" (part 1)... +UPDATE IGNORE {$db_prefix}members +SET im_ignore_list = '*' +WHERE im_ignore_list RLIKE '([\n,]|^)[*]([\n,]|$)'; +---# + +---# Updating data in "members" (part 2)... +---{ +$request = upgrade_query(" + SHOW COLUMNS + FROM {$db_prefix}members + LIKE 'im_ignore_list'"); +$do_it = mysql_num_rows($request) != 0; +mysql_free_result($request); + +while ($do_it) +{ + nextSubstep($substep); + + $request = upgrade_query(" + SELECT ID_MEMBER, im_ignore_list + FROM {$db_prefix}members + WHERE im_ignore_list RLIKE '[a-z]' + LIMIT 512"); + while ($row = mysql_fetch_assoc($request)) + { + $request2 = upgrade_query(" + SELECT ID_MEMBER + FROM {$db_prefix}members + WHERE FIND_IN_SET(memberName, '" . addslashes($row['im_ignore_list']) . "')"); + $im_ignore_list = ''; + while ($row2 = mysql_fetch_assoc($request2)) + $im_ignore_list .= ',' . $row2['ID_MEMBER']; + mysql_free_result($request2); + + upgrade_query(" + UPDATE {$db_prefix}members + SET im_ignore_list = '" . substr($im_ignore_list, 1) . "' + WHERE ID_MEMBER = $row[ID_MEMBER] + LIMIT 1"); + } + if (mysql_num_rows($request) < 512) + break; + mysql_free_result($request); +} +---} +---# + +---# Updating data in "members" (part 3)... +UPDATE {$db_prefix}members +SET realName = memberName +WHERE IFNULL(realName, '') = ''; +---# + +---# Updating data in "members" (part 4)... +UPDATE {$db_prefix}members +SET lngfile = REPLACE(lngfile, '.lng', '') +WHERE lngfile LIKE '%.lng'; +---# + +---# Cleaning up "members"... +ALTER TABLE {$db_prefix}members +DROP INDEX memberID; +ALTER TABLE {$db_prefix}members +DROP INDEX memberID_2; +---# + +---# Adding new columns to "members"... +ALTER TABLE {$db_prefix}members +DROP PRIMARY KEY, +CHANGE COLUMN ID_MEMBER ID_MEMBER mediumint(8) unsigned NOT NULL auto_increment PRIMARY KEY, +ADD instantMessages smallint(5) NOT NULL default 0, +ADD unreadMessages smallint(5) NOT NULL default 0, +ADD ID_THEME tinyint(4) unsigned NOT NULL default 0, +ADD ID_GROUP smallint(5) unsigned NOT NULL default 0, +ADD is_activated tinyint(3) unsigned NOT NULL default '1', +ADD validation_code varchar(10) NOT NULL default '', +ADD ID_MSG_LAST_VISIT int(10) unsigned NOT NULL default '0', +ADD additionalGroups tinytext NOT NULL default ''; +---# + +---# Updating columns on "members"... +ALTER TABLE {$db_prefix}members +CHANGE COLUMN ID_THEME ID_THEME tinyint(4) unsigned NOT NULL default 0; +ALTER TABLE {$db_prefix}members +ADD showOnline tinyint(4) NOT NULL default '1'; +ALTER TABLE {$db_prefix}members +ADD smileySet varchar(48) NOT NULL default ''; +ALTER TABLE {$db_prefix}members +ADD totalTimeLoggedIn int(10) unsigned NOT NULL default '0'; +ALTER TABLE {$db_prefix}members +ADD passwordSalt varchar(5) NOT NULL default ''; +---# + +---# Updating data in "members" (part 5)... +UPDATE {$db_prefix}members +SET gender = CASE gender + WHEN '0' THEN 0 + WHEN 'Male' THEN 1 + WHEN 'Female' THEN 2 + ELSE 0 END, secretAnswer = IF(secretAnswer = '', '', MD5(secretAnswer)) +WHERE gender NOT IN ('0', '1', '2'); +---# + +---# Updating data in "members" (part 6)... +---{ +$member_groups = getMemberGroups(); + +foreach ($member_groups as $name => $id) +{ + upgrade_query(" + UPDATE IGNORE {$db_prefix}members + SET ID_GROUP = $id + WHERE memberGroup = '" . addslashes($name) . "'"); + + nextSubstep($substep); +} +---} +UPDATE IGNORE {$db_prefix}members +SET ID_GROUP = 1 +WHERE memberGroup = 'Administrator'; +UPDATE IGNORE {$db_prefix}members +SET ID_GROUP = 2 +WHERE memberGroup = 'Global Moderator'; + +ALTER TABLE {$db_prefix}members +DROP memberGroup; +---# + +---# Changing column sizes on "members" (part 1)... +ALTER TABLE {$db_prefix}members +CHANGE COLUMN timeOffset timeOffset float NOT NULL default '0', +CHANGE COLUMN posts posts mediumint(8) unsigned NOT NULL default '0', +CHANGE COLUMN timeFormat timeFormat varchar(80) NOT NULL default '', +CHANGE COLUMN lastLogin lastLogin int(11) NOT NULL default '0', +CHANGE COLUMN karmaBad karmaBad smallint(5) unsigned NOT NULL default '0', +CHANGE COLUMN karmaGood karmaGood smallint(5) unsigned NOT NULL default '0', +CHANGE COLUMN gender gender tinyint(4) unsigned NOT NULL default '0', +CHANGE COLUMN hideEmail hideEmail tinyint(4) NOT NULL default '0'; +---# + +---# Changing column sizes on "members" (part 2)... +ALTER TABLE {$db_prefix}members +DROP INDEX realName; + +ALTER TABLE {$db_prefix}members +CHANGE COLUMN AIM AIM varchar(16) NOT NULL default '', +CHANGE COLUMN YIM YIM varchar(32) NOT NULL default '', +CHANGE COLUMN ICQ ICQ tinytext NOT NULL default '', +CHANGE COLUMN realName realName tinytext NOT NULL default '', +CHANGE COLUMN emailAddress emailAddress tinytext NOT NULL default '', +CHANGE COLUMN dateRegistered dateRegistered int(10) unsigned NOT NULL default '0', +CHANGE COLUMN passwd passwd varchar(64) NOT NULL default '', +CHANGE COLUMN personalText personalText tinytext NOT NULL default '', +CHANGE COLUMN websiteTitle websiteTitle tinytext NOT NULL default ''; +---# + +---# Changing column sizes on "members" (part 3)... +ALTER TABLE {$db_prefix}members +DROP INDEX lngfile; + +ALTER TABLE {$db_prefix}members +CHANGE COLUMN websiteUrl websiteUrl tinytext NOT NULL default '', +CHANGE COLUMN location location tinytext NOT NULL default '', +CHANGE COLUMN avatar avatar tinytext NOT NULL default '', +CHANGE COLUMN im_ignore_list im_ignore_list tinytext NOT NULL default '', +CHANGE COLUMN usertitle usertitle tinytext NOT NULL default '', +CHANGE COLUMN lngfile lngfile tinytext NOT NULL default '', +CHANGE COLUMN MSN MSN tinytext NOT NULL default '', +CHANGE COLUMN memberIP memberIP tinytext NOT NULL default '', +ADD INDEX lngfile (lngfile(24)); +---# + +---# Updating keys on "members"... +ALTER TABLE {$db_prefix}members +ADD INDEX ID_GROUP (ID_GROUP), +ADD INDEX birthdate (birthdate), +ADD INDEX lngfile (lngfile(30)); +---# + +---# Converting member statistics... +REPLACE INTO {$db_prefix}settings + (variable, value) +SELECT 'latestMember', ID_MEMBER +FROM {$db_prefix}members +ORDER BY ID_MEMBER DESC +LIMIT 1; + +REPLACE INTO {$db_prefix}settings + (variable, value) +SELECT 'latestRealName', IFNULL(realName, memberName) +FROM {$db_prefix}members +ORDER BY ID_MEMBER DESC +LIMIT 1; + +REPLACE INTO {$db_prefix}settings + (variable, value) +SELECT 'maxMsgID', ID_MSG +FROM {$db_prefix}messages +ORDER BY ID_MSG DESC +LIMIT 1; +---# + +---# Adding new columns to "instant_messages"... +ALTER IGNORE TABLE {$db_prefix}instant_messages +ADD COLUMN deletedBySender tinyint(3) unsigned NOT NULL default '0' AFTER ID_MEMBER_FROM; +---# + +---# Changing column sizes on "instant_messages" (part 1)... +ALTER TABLE {$db_prefix}instant_messages +CHANGE COLUMN ID_MEMBER_FROM ID_MEMBER_FROM mediumint(8) unsigned NOT NULL default 0, +CHANGE COLUMN msgtime msgtime int(10) unsigned NOT NULL default '0', +CHANGE COLUMN subject subject tinytext NOT NULL; +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX fromName, +DROP INDEX ID_MEMBER_FROM; +---# + +---# Changing column sizes on "instant_messages" (part 2)... +ALTER TABLE {$db_prefix}instant_messages +DROP PRIMARY KEY, +CHANGE COLUMN ID_IM ID_PM int(10) unsigned NOT NULL auto_increment PRIMARY KEY; +ALTER TABLE {$db_prefix}instant_messages +ADD INDEX msgtime (msgtime); +---# + +---# Cleaning up "instant_messages"... +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX ID_MEMBER_FROM_2; +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX ID_MEMBER_FROM_3; +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX ID_MEMBER_FROM_4; +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX ID_MEMBER_FROM_5; +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX ID_MEMBER_TO_2; +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX ID_MEMBER_TO_3; +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX ID_MEMBER_TO_4; +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX ID_MEMBER_TO_5; +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX deletedBy_2; +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX deletedBy_3; +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX deletedBy_4; +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX deletedBy_5; +---# + +---# Creating "im_recipients"... +CREATE TABLE IF NOT EXISTS {$db_prefix}im_recipients ( + ID_PM int(10) unsigned NOT NULL default '0', + ID_MEMBER mediumint(8) unsigned NOT NULL default '0', + bcc tinyint(3) unsigned NOT NULL default '0', + is_read tinyint(3) unsigned NOT NULL default '0', + deleted tinyint(3) unsigned NOT NULL default '0', + PRIMARY KEY (ID_PM, ID_MEMBER), + KEY ID_MEMBER (ID_MEMBER, deleted) +) ENGINE=MyISAM; +---# + +---# Updating "im_recipients"... +ALTER TABLE {$db_prefix}im_recipients +DROP PRIMARY KEY, +CHANGE COLUMN ID_IM ID_PM int(10) unsigned NOT NULL default '0', +ADD PRIMARY KEY (ID_PM, ID_MEMBER); +---# + +---# Updating data in "instant_messages" (part 1)... +---{ +$request = mysql_query(" + SHOW COLUMNS + FROM {$db_prefix}instant_messages + LIKE 'readBy'"); +$do_it = $request !== false; + +if ($do_it) +{ + $adv_im = mysql_num_rows($request) == 0; + mysql_free_result($request); + + mysql_query(" + INSERT IGNORE INTO {$db_prefix}im_recipients + (ID_PM, ID_MEMBER, bcc, is_read, deleted) + SELECT ID_PM, ID_MEMBER_TO, 0, IF(" . (!$adv_im ? 'readBy' : 'alerted') . " != 0, 1, 0), IF(deletedBy = '1', 1, 0) + FROM {$db_prefix}instant_messages"); +} +---} + +UPDATE IGNORE {$db_prefix}instant_messages +SET deletedBySender = 1 +WHERE deletedBy = 0; +---# + +---# Updating data in "instant_messages" (part 2)... +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX ID_MEMBER_TO; +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX deletedBy; +ALTER TABLE {$db_prefix}instant_messages +DROP INDEX readBy; + +ALTER TABLE {$db_prefix}instant_messages +DROP COLUMN ID_MEMBER_TO, +DROP COLUMN deletedBy, +DROP COLUMN toName, +DROP COLUMN readBy; + +ALTER TABLE {$db_prefix}instant_messages +ADD INDEX ID_MEMBER (ID_MEMBER_FROM, deletedBySender); +---# + +---# Recounting personal message totals... +---{ +$request = mysql_query(" + SHOW CREATE TABLE {$db_prefix}instant_messages"); +$do_it = $request !== false; +@mysql_free_result($request); + +$request = upgrade_query(" + SELECT COUNT(*) + FROM {$db_prefix}members"); +list ($totalMembers) = mysql_fetch_row($request); +mysql_free_result($request); + +$_GET['m'] = (int) @$_GET['m']; + +while ($_GET['m'] < $totalMembers && $do_it) +{ + nextSubstep($substep); + + $mrequest = upgrade_query(" + SELECT mem.ID_MEMBER, COUNT(pmr.ID_PM) AS instantMessages_real, mem.instantMessages + FROM {$db_prefix}members AS mem + LEFT JOIN {$db_prefix}im_recipients AS pmr ON (pmr.ID_MEMBER = mem.ID_MEMBER AND pmr.deleted = 0) + WHERE mem.ID_MEMBER > $_GET[m] + AND mem.ID_MEMBER <= $_GET[m] + 512 + GROUP BY mem.ID_MEMBER + HAVING instantMessages_real != instantMessages + LIMIT 512"); + while ($row = mysql_fetch_assoc($mrequest)) + { + upgrade_query(" + UPDATE {$db_prefix}members + SET instantMessages = $row[instantMessages_real] + WHERE ID_MEMBER = $row[ID_MEMBER] + LIMIT 1"); + } + + $_GET['m'] += 512; +} +unset($_GET['m']); +---} +---{ +$request = mysql_query(" + SHOW CREATE TABLE {$db_prefix}instant_messages"); +$do_it = $request !== false; +@mysql_free_result($request); + +$request = upgrade_query(" + SELECT COUNT(*) + FROM {$db_prefix}members"); +list ($totalMembers) = mysql_fetch_row($request); +mysql_free_result($request); + +$_GET['m'] = (int) @$_GET['m']; + +while ($_GET['m'] < $totalMembers && $do_it) +{ + nextSubstep($substep); + + $mrequest = upgrade_query(" + SELECT mem.ID_MEMBER, COUNT(pmr.ID_PM) AS unreadMessages_real, mem.unreadMessages + FROM {$db_prefix}members AS mem + LEFT JOIN {$db_prefix}im_recipients AS pmr ON (pmr.ID_MEMBER = mem.ID_MEMBER AND pmr.deleted = 0 AND pmr.is_read = 0) + WHERE mem.ID_MEMBER > $_GET[m] + AND mem.ID_MEMBER <= $_GET[m] + 512 + GROUP BY mem.ID_MEMBER + HAVING unreadMessages_real != unreadMessages + LIMIT 512"); + while ($row = mysql_fetch_assoc($mrequest)) + { + upgrade_query(" + UPDATE {$db_prefix}members + SET unreadMessages = $row[unreadMessages_real] + WHERE ID_MEMBER = $row[ID_MEMBER] + LIMIT 1"); + } + + $_GET['m'] += 512; +} +unset($_GET['m']); +---} +---# + +---# Converting "membergroups"... +---{ +global $JrPostNum, $FullPostNum, $SrPostNum, $GodPostNum; + +$result = mysql_query(" + SELECT minPosts + FROM {$db_prefix}membergroups + LIMIT 1"); +if ($result === false) +{ + upgrade_query(" + RENAME TABLE {$db_prefix}membergroups TO {$db_prefix}old_membergroups"); + + upgrade_query(" + CREATE TABLE {$db_prefix}membergroups ( + ID_GROUP smallint(5) unsigned NOT NULL auto_increment, + groupName varchar(80) NOT NULL default '', + onlineColor varchar(20) NOT NULL default '', + minPosts mediumint(9) NOT NULL default '-1', + maxMessages smallint(5) unsigned NOT NULL default '0', + stars tinytext NOT NULL default '', + PRIMARY KEY (ID_GROUP), + KEY minPosts (minPosts) + ) ENGINE=MyISAM"); + + upgrade_query(" + INSERT INTO {$db_prefix}membergroups + (ID_GROUP, groupName, onlineColor, minPosts, stars) + SELECT ID_GROUP, membergroup, '#FF0000', -1, '5#staradmin.gif' + FROM {$db_prefix}old_membergroups + WHERE ID_GROUP = 1"); + + upgrade_query(" + INSERT INTO {$db_prefix}membergroups + (ID_GROUP, groupName, onlineColor, minPosts, stars) + SELECT 2, membergroup, '#0000FF', -1, '5#stargmod.gif' + FROM {$db_prefix}old_membergroups + WHERE ID_GROUP = 8"); + + upgrade_query(" + INSERT INTO {$db_prefix}membergroups + (ID_GROUP, groupName, onlineColor, minPosts, stars) + SELECT 3, membergroup, '', -1, '5#starmod.gif' + FROM {$db_prefix}old_membergroups + WHERE ID_GROUP = 2"); + + upgrade_query(" + INSERT INTO {$db_prefix}membergroups + (ID_GROUP, groupName, onlineColor, minPosts, stars) + SELECT + ID_GROUP + 1, membergroup, '', CASE ID_GROUP + WHEN 3 THEN 0 + WHEN 4 THEN '$JrPostNum' + WHEN 5 THEN '$FullPostNum' + WHEN 6 THEN '$SrPostNum' + WHEN 7 THEN '$GodPostNum' + END, CONCAT(ID_GROUP - 2, '#star.gif') + FROM {$db_prefix}old_membergroups + WHERE ID_GROUP IN (3, 4, 5, 6, 7)"); + + upgrade_query(" + INSERT INTO {$db_prefix}membergroups + (ID_GROUP, groupName, onlineColor, minPosts, stars) + SELECT ID_GROUP, membergroup, '', -1, '' + FROM {$db_prefix}old_membergroups + WHERE ID_GROUP > 8"); + + upgrade_query(" + DROP TABLE IF EXISTS {$db_prefix}old_membergroups"); + + $permissions = array( + 'view_mlist', + 'search_posts', + 'profile_view_own', + 'profile_view_any', + 'pm_read', + 'pm_send', + 'calendar_view', + 'view_stats', + 'who_view', + 'profile_identity_own', + 'profile_extra_own', + 'profile_remote_avatar', + 'profile_remove_own', + ); + + foreach ($permissions as $perm) + upgrade_query(" + INSERT INTO {$db_prefix}permissions + (ID_GROUP, permission) + SELECT IF(ID_GROUP = 1, 0, ID_GROUP), '$perm' + FROM {$db_prefix}membergroups + WHERE ID_GROUP != 3 + AND minPosts = -1"); + + $board_permissions = array( + 'remove_own', + 'lock_own', + 'mark_any_notify', + 'mark_notify', + 'modify_own', + 'poll_add_own', + 'poll_edit_own', + 'poll_lock_own', + 'poll_post', + 'poll_view', + 'poll_vote', + 'post_attachment', + 'post_new', + 'post_reply_any', + 'post_reply_own', + 'delete_own', + 'report_any', + 'send_topic', + 'view_attachments', + ); + + foreach ($board_permissions as $perm) + upgrade_query(" + INSERT INTO {$db_prefix}board_permissions + (ID_GROUP, permission) + SELECT IF(ID_GROUP = 1, 0, ID_GROUP), '$perm' + FROM {$db_prefix}membergroups + WHERE minPosts = -1"); +} +---} +---# + +---# Converting "reserved_names"... +---{ +$request = mysql_query(" + SELECT setting, value + FROM {$db_prefix}reserved_names"); +if ($request !== false) +{ + $words = array(); + $match_settings = array(); + while ($row = mysql_fetch_assoc($request)) + { + if (substr($row['setting'], 0, 5) == 'match') + $match_settings[$row['setting']] = $row['value']; + else + $words[] = $row['value']; + } + mysql_free_result($request); + + upgrade_query(" + INSERT IGNORE INTO {$db_prefix}settings + VALUES ('reserveWord', '" . (int) @$match_settings['matchword'] . "'), + ('reserveCase', '" . (int) @$match_settings['matchcase'] . "'), + ('reserveUser', '" . (int) @$match_settings['matchuser'] . "'), + ('reserveName', '" . (int) @$match_settings['matchname'] . "'), + ('reserveNames', '" . implode("\n", $words) . "')"); + + upgrade_query(" + DROP TABLE {$db_prefix}reserved_names"); +} +---} +---# + +---# Converting member's groups... +ALTER TABLE {$db_prefix}members +ADD COLUMN ID_POST_GROUP smallint(5) unsigned NOT NULL default '0', +ADD INDEX ID_POST_GROUP (ID_POST_GROUP); + +---{ +$request = upgrade_query(" + SELECT ID_GROUP, minPosts + FROM {$db_prefix}membergroups + WHERE minPosts != -1 + ORDER BY minPosts DESC"); +$post_groups = array(); +while ($row = mysql_fetch_assoc($request)) + $post_groups[$row['minPosts']] = $row['ID_GROUP']; +mysql_free_result($request); + +$request = upgrade_query(" + SELECT ID_MEMBER, posts + FROM {$db_prefix}members"); +$mg_updates = array(); +while ($row = mysql_fetch_assoc($request)) +{ + $group = 4; + foreach ($post_groups as $min_posts => $group_id) + if ($row['posts'] > $min_posts) + { + $group = $group_id; + break; + } + + $mg_updates[$group][] = $row['ID_MEMBER']; +} +mysql_free_result($request); + +foreach ($mg_updates as $group_to => $update_members) + upgrade_query(" + UPDATE {$db_prefix}members + SET ID_POST_GROUP = $group_to + WHERE ID_MEMBER IN (" . implode(', ', $update_members) . ") + LIMIT " . count($update_members)); +---} +---# + +/******************************************************************************/ +--- Converting the calendar, notifications, and miscellaneous... +/******************************************************************************/ + +---# Converting censored words... +---{ +if (!isset($modSettings['censor_vulgar']) || !isset($modSettings['censor_proper'])) +{ + $request = upgrade_query(" + SELECT vulgar, proper + FROM {$db_prefix}censor"); + $censor_vulgar = array(); + $censor_proper = array(); + while ($row = mysql_fetch_row($request)) + { + $censor_vulgar[] = trim($row[0]); + $censor_proper[] = trim($row[1]); + } + mysql_free_result($request); + + $modSettings['censor_vulgar'] = addslashes(implode("\n", $censor_vulgar)); + $modSettings['censor_proper'] = addslashes(implode("\n", $censor_proper)); + + upgrade_query(" + INSERT IGNORE INTO {$db_prefix}settings + (variable, value) + VALUES + ('censor_vulgar', '$modSettings[censor_vulgar]'), + ('censor_proper', '$modSettings[censor_proper]')"); + + upgrade_query(" + DROP TABLE IF EXISTS {$db_prefix}censor"); +} +---} +---# + +---# Converting topic notifications... +---{ +$result = mysql_query(" + SELECT COUNT(*) + FROM {$db_prefix}topics + WHERE notifies != ''"); +if ($result !== false) +{ + list ($numNotifies) = mysql_fetch_row($result); + mysql_free_result($result); + + $_GET['t'] = (int) @$_GET['t']; + + while ($_GET['t'] < $numNotifies) + { + nextSubstep($substep); + + upgrade_query(" + INSERT IGNORE INTO {$db_prefix}log_notify + (ID_MEMBER, ID_TOPIC) + SELECT mem.ID_MEMBER, t.ID_TOPIC + FROM ({$db_prefix}topics AS t, {$db_prefix}members AS mem) + WHERE FIND_IN_SET(mem.ID_MEMBER, t.notifies) + AND t.notifies != '' + LIMIT $_GET[t], 512"); + + $_GET['t'] += 512; + } + unset($_GET['t']); +} +---} + +ALTER TABLE {$db_prefix}topics +DROP notifies; +---# + +---# Converting "banned"... +---{ +$request = mysql_query(" + SELECT type, value + FROM {$db_prefix}banned + WHERE type = 'ip'"); +if ($request !== false) +{ + $insertEntries = array(); + while ($row = mysql_fetch_assoc($request)) + { + if (preg_match('~^\d{1,3}\.(\d{1,3}|\*)\.(\d{1,3}|\*)\.(\d{1,3}|\*)$~', $row['value']) == 0) + continue; + + $ip_parts = ip2range($row['value']); + $insertEntries[] = "('ip_ban', {$ip_parts[0]['low']}, {$ip_parts[0]['high']}, {$ip_parts[1]['low']}, {$ip_parts[1]['high']}, {$ip_parts[2]['low']}, {$ip_parts[2]['high']}, {$ip_parts[3]['low']}, {$ip_parts[3]['high']}, '', '', 0, " . time() . ", NULL, 'full_ban', '', 'Imported from YaBB SE')"; + } + mysql_free_result($request); + + upgrade_query(" + CREATE TABLE IF NOT EXISTS {$db_prefix}banned2 ( + id_ban mediumint(8) unsigned NOT NULL auto_increment, + ban_type varchar(30) NOT NULL default '', + ip_low1 tinyint(3) unsigned NOT NULL default '0', + ip_high1 tinyint(3) unsigned NOT NULL default '0', + ip_low2 tinyint(3) unsigned NOT NULL default '0', + ip_high2 tinyint(3) unsigned NOT NULL default '0', + ip_low3 tinyint(3) unsigned NOT NULL default '0', + ip_high3 tinyint(3) unsigned NOT NULL default '0', + ip_low4 tinyint(3) unsigned NOT NULL default '0', + ip_high4 tinyint(3) unsigned NOT NULL default '0', + hostname tinytext NOT NULL default '', + email_address tinytext NOT NULL default '', + ID_MEMBER mediumint(8) unsigned NOT NULL default '0', + ban_time int(10) unsigned NOT NULL default '0', + expire_time int(10) unsigned, + restriction_type varchar(30) NOT NULL default '', + reason tinytext NOT NULL default '', + notes text NOT NULL default '', + PRIMARY KEY (id_ban) + ) ENGINE=MyISAM"); + + upgrade_query(" + INSERT INTO {$db_prefix}banned2 + (ban_type, ip_low1, ip_high1, ip_low2, ip_high2, ip_low3, ip_high3, ip_low4, ip_high4, hostname, email_address, ID_MEMBER, ban_time, expire_time, restriction_type, reason, notes) + SELECT 'email_ban', 0, 0, 0, 0, 0, 0, 0, 0, '', value, 0, " . time() . ", NULL, 'full_ban', '', 'Imported from YaBB SE' + FROM {$db_prefix}banned + WHERE type = 'email'"); + + upgrade_query(" + INSERT INTO {$db_prefix}banned2 + (ban_type, ip_low1, ip_high1, ip_low2, ip_high2, ip_low3, ip_high3, ip_low4, ip_high4, hostname, email_address, ID_MEMBER, ban_time, expire_time, restriction_type, reason, notes) + SELECT 'user_ban', 0, 0, 0, 0, 0, 0, 0, 0, '', '', mem.ID_MEMBER, " . time() . ", NULL, 'full_ban', '', 'Imported from YaBB SE' + FROM ({$db_prefix}banned AS ban, {$db_prefix}members AS mem) + WHERE ban.type = 'username' + AND mem.memberName = ban.value"); + + upgrade_query(" + DROP TABLE {$db_prefix}banned"); + upgrade_query(" + RENAME TABLE {$db_prefix}banned2 TO {$db_prefix}banned"); + + if (!empty($insertEntries)) + { + upgrade_query(" + INSERT INTO {$db_prefix}banned + (ban_type, ip_low1, ip_high1, ip_low2, ip_high2, ip_low3, ip_high3, ip_low4, ip_high4, hostname, email_address, ID_MEMBER, ban_time, expire_time, restriction_type, reason, notes) + VALUES " . implode(',', $insertEntries)); + } +} +---} +---# + +---# Updating "log_banned"... +ALTER TABLE {$db_prefix}log_banned +CHANGE COLUMN logTime logTime int(10) unsigned NOT NULL default '0'; +ALTER TABLE {$db_prefix}log_banned +ADD COLUMN id_ban_log mediumint(8) unsigned NOT NULL auto_increment PRIMARY KEY FIRST, +ADD COLUMN ID_MEMBER mediumint(8) unsigned NOT NULL default '0' AFTER id_ban_log, +ADD INDEX logTime (logTime); +---# + +---# Updating columns on "calendar"... +ALTER TABLE {$db_prefix}calendar +DROP PRIMARY KEY, +CHANGE COLUMN id ID_EVENT smallint(5) unsigned NOT NULL auto_increment PRIMARY KEY, +CHANGE COLUMN id_board ID_BOARD smallint(5) unsigned NOT NULL default '0', +CHANGE COLUMN id_topic ID_TOPIC mediumint(8) unsigned NOT NULL default '0', +CHANGE COLUMN id_member ID_MEMBER mediumint(8) unsigned NOT NULL default '0'; +ALTER TABLE {$db_prefix}calendar +CHANGE COLUMN title title varchar(48) NOT NULL default ''; + +ALTER TABLE {$db_prefix}calendar +ADD eventDate date NOT NULL default '0000-00-00'; +---# + +---# Updating indexes on "calendar"... +ALTER TABLE {$db_prefix}calendar +DROP INDEX idx_year_month; +ALTER TABLE {$db_prefix}calendar +DROP INDEX year; +---# + +---# Updating data in "calendar"... +UPDATE IGNORE {$db_prefix}calendar +SET eventDate = CONCAT(year, '-', month + 1, '-', day); + +ALTER TABLE {$db_prefix}calendar +DROP year, +DROP month, +DROP day, +ADD INDEX eventDate (eventDate); +---# + +---# Updating structure on "calendar_holidays"... +CREATE TABLE IF NOT EXISTS {$db_prefix}calendar_holidays ( + ID_HOLIDAY smallint(5) unsigned NOT NULL auto_increment, + eventDate date NOT NULL default '0000-00-00', + title varchar(30) NOT NULL default '', + PRIMARY KEY (ID_HOLIDAY), + KEY eventDate (eventDate) +) ENGINE=MyISAM; +---# + +---# Updating data in "calendar_holidays"... +---{ +$result = upgrade_query(" + SELECT COUNT(*) + FROM {$db_prefix}calendar_holidays"); +list ($size) = mysql_fetch_row($result); +mysql_free_result($result); + +if (empty($size)) +{ + upgrade_query(" + INSERT INTO {$db_prefix}calendar_holidays + (eventDate, title) + SELECT IF(year IS NULL, CONCAT('0000-', month, '-', day), CONCAT(year, '-', month, '-', day)), title + FROM {$db_prefix}calendar_holiday"); + + upgrade_query(" + INSERT INTO {$db_prefix}calendar_holidays + (eventDate, title) + VALUES ('0000-06-06', 'D-Day')"); +} +---} + +UPDATE {$db_prefix}calendar_holidays +SET title = 'New Year\'s' +WHERE title = 'New Years'; +---# + +/******************************************************************************/ +--- Converting polls and choices... +/******************************************************************************/ + +---# Converting data to "poll_choices"... +INSERT INTO {$db_prefix}poll_choices + (ID_POLL, ID_CHOICE, label, votes) +SELECT ID_POLL, 0, option1, votes1 +FROM {$db_prefix}polls; + +INSERT INTO {$db_prefix}poll_choices + (ID_POLL, ID_CHOICE, label, votes) +SELECT ID_POLL, 1, option2, votes2 +FROM {$db_prefix}polls; + +INSERT INTO {$db_prefix}poll_choices + (ID_POLL, ID_CHOICE, label, votes) +SELECT ID_POLL, 2, option3, votes3 +FROM {$db_prefix}polls; + +INSERT INTO {$db_prefix}poll_choices + (ID_POLL, ID_CHOICE, label, votes) +SELECT ID_POLL, 3, option4, votes4 +FROM {$db_prefix}polls; + +INSERT INTO {$db_prefix}poll_choices + (ID_POLL, ID_CHOICE, label, votes) +SELECT ID_POLL, 4, option5, votes5 +FROM {$db_prefix}polls; + +INSERT INTO {$db_prefix}poll_choices + (ID_POLL, ID_CHOICE, label, votes) +SELECT ID_POLL, 5, option6, votes6 +FROM {$db_prefix}polls; + +INSERT INTO {$db_prefix}poll_choices + (ID_POLL, ID_CHOICE, label, votes) +SELECT ID_POLL, 6, option7, votes7 +FROM {$db_prefix}polls; + +INSERT INTO {$db_prefix}poll_choices + (ID_POLL, ID_CHOICE, label, votes) +SELECT ID_POLL, 7, option8, votes8 +FROM {$db_prefix}polls; +---# + +---# Converting data to "log_polls"... +---{ +$query = mysql_query(" + SELECT ID_POLL, votedMemberIDs + FROM {$db_prefix}polls"); +if ($query !== false) +{ + $setStringLog = ''; + while ($row = mysql_fetch_assoc($query)) + { + $members = explode(',', $row['votedMemberIDs']); + foreach ($members as $member) + if (is_numeric($member) && !empty($member)) + $setStringLog .= " + ($row[ID_POLL], $member, 0),"; + } + + if (!empty($setStringLog)) + { + upgrade_query(" + INSERT IGNORE INTO {$db_prefix}log_polls + (ID_POLL, ID_MEMBER, ID_CHOICE) + VALUES " . substr($setStringLog, 0, -1)); + } +} +---} +---# + +---# Updating "polls"... +ALTER TABLE {$db_prefix}polls +DROP option1, DROP option2, DROP option3, DROP option4, DROP option5, DROP option6, DROP option7, DROP option8, +DROP votes1, DROP votes2, DROP votes3, DROP votes4, DROP votes5, DROP votes6, DROP votes7, DROP votes8, +DROP votedMemberIDs, +DROP PRIMARY KEY, +CHANGE COLUMN ID_POLL ID_POLL mediumint(8) unsigned NOT NULL auto_increment PRIMARY KEY, +CHANGE COLUMN votingLocked votingLocked tinyint(1) NOT NULL default '0', +CHANGE COLUMN question question tinytext NOT NULL default '', +ADD maxVotes tinyint(4) unsigned NOT NULL default '1', +ADD expireTime int(10) unsigned NOT NULL default '0', +ADD hideResults tinyint(4) unsigned NOT NULL default '0'; + +ALTER TABLE {$db_prefix}polls +ADD ID_MEMBER mediumint(8) unsigned NOT NULL default '0', +ADD posterName tinytext NOT NULL default ''; +ALTER TABLE {$db_prefix}polls +ADD changeVote tinyint(4) unsigned NOT NULL default '0'; +---# + +---# Updating data in "polls"... +---{ +$result = upgrade_query(" + SELECT p.ID_POLL, t.ID_MEMBER_STARTED + FROM ({$db_prefix}topics AS t, {$db_prefix}messages AS m, {$db_prefix}polls AS p) + WHERE m.ID_MSG = t.ID_FIRST_MSG + AND p.ID_POLL = t.ID_POLL + AND p.ID_MEMBER = 0 + AND t.ID_MEMBER_STARTED != 0"); +while ($row = mysql_fetch_assoc($result)) +{ + upgrade_query(" + UPDATE {$db_prefix}polls + SET ID_MEMBER = $row[ID_MEMBER_STARTED] + WHERE ID_POLL = $row[ID_POLL] + LIMIT 1"); +} +mysql_free_result($result); +---} +---# + +/******************************************************************************/ +--- Converting settings... +/******************************************************************************/ + +---# Updating news... +---{ +if (!isset($modSettings['smfVersion'])) +{ + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('news', SUBSTRING('" . htmlspecialchars(stripslashes($modSettings['news']), ENT_QUOTES) . "', 1, 65534))"); +} +---} +---# + +---# Updating "themes"... +---{ +convertSettingsToTheme(); + +$insertRows = ''; +$request = upgrade_query(" + SELECT ID_THEME, IF(value = '2', 5, value) AS value + FROM {$db_prefix}themes + WHERE variable = 'display_recent_bar'"); +while ($row = mysql_fetch_assoc($request)) + $insertRows .= "($row[ID_THEME], 'number_recent_posts', '$row[value]'),"; +mysql_free_result($request); +if (!empty($insertRows)) +{ + $insertRows = substr($insertRows, 0, -1); + upgrade_query(" + INSERT IGNORE INTO {$db_prefix}themes + (ID_THEME, variable, value) + VALUES $insertRows"); +} +---} +---# + +---# Updating "settings"... +ALTER TABLE {$db_prefix}settings +DROP INDEX variable; + +UPDATE IGNORE {$db_prefix}settings +SET variable = 'guest_hideContacts' +WHERE variable = 'guest_hideEmail' +LIMIT 1; +---# + +---# Adding new settings (part 1)... +INSERT IGNORE INTO {$db_prefix}settings + (variable, value) +VALUES + ('news', ''), + ('compactTopicPagesContiguous', '5'), + ('compactTopicPagesEnable', '1'), + ('enableStickyTopics', '1'), + ('todayMod', '1'), + ('karmaMode', '0'), + ('karmaTimeRestrictAdmins', '1'), + ('enablePreviousNext', '1'), + ('pollMode', '1'), + ('enableVBStyleLogin', '1'), + ('enableCompressedOutput', '1'), + ('karmaWaitTime', '1'), + ('karmaMinPosts', '0'), + ('karmaLabel', 'Karma:'), + ('karmaSmiteLabel', '[smite]'), + ('karmaApplaudLabel', '[applaud]'), + ('attachmentSizeLimit', '128'), + ('attachmentPostLimit', '192'), + ('attachmentNumPerPostLimit', '4'), + ('attachmentDirSizeLimit', '10240'), + ('attachmentUploadDir', '{$sboarddir}/attachments'), + ('attachmentExtensions', 'txt,doc,pdf,jpg,gif,mpg,png'), + ('attachmentCheckExtensions', '1'), + ('attachmentShowImages', '1'), + ('attachmentEnable', '1'), + ('attachmentEncryptFilenames', '1'), + ('censorIgnoreCase', '1'), + ('mostOnline', '1'); +INSERT IGNORE INTO {$db_prefix}settings + (variable, value) +VALUES + ('mostOnlineToday', '1'), + ('mostDate', UNIX_TIMESTAMP()), + ('trackStats', '1'), + ('userLanguage', '1'), + ('titlesEnable', '1'), + ('topicSummaryPosts', '15'), + ('enableErrorLogging', '1'), + ('onlineEnable', '0'), + ('cal_holidaycolor', '000080'), + ('cal_bdaycolor', '920AC4'), + ('cal_eventcolor', '078907'), + ('cal_enabled', '0'), + ('cal_maxyear', '2010'), + ('cal_minyear', '2002'), + ('cal_daysaslink', '0'), + ('cal_defaultboard', ''), + ('cal_showeventsonindex', '0'), + ('cal_showbdaysonindex', '0'), + ('cal_showholidaysonindex', '0'), + ('cal_showweeknum', '0'), + ('cal_maxspan', '7'), + ('smtp_host', ''), + ('smtp_username', ''), + ('smtp_password', ''), + ('mail_type', '0'), + ('timeLoadPageEnable', '0'), + ('totalTopics', '1'), + ('totalMessages', '1'), + ('simpleSearch', '0'), + ('censor_vulgar', ''), + ('censor_proper', ''), + ('mostOnlineToday', '1'), + ('enablePostHTML', '0'), + ('theme_allow', '1'), + ('theme_default', '1'), + ('theme_guests', '1'), + ('enableEmbeddedFlash', '0'), + ('xmlnews_enable', '1'), + ('xmlnews_maxlen', '255'), + ('hotTopicPosts', '15'), + ('hotTopicVeryPosts', '25'), + ('allow_editDisplayName', '1'), + ('number_format', '1234.00'), + ('attachmentEncryptFilenames', '1'), + ('autoLinkUrls', '1'); +INSERT IGNORE INTO {$db_prefix}settings + (variable, value) +VALUES + ('avatar_allow_server_stored', '1'), + ('avatar_check_size', '0'), + ('avatar_action_too_large', 'option_user_resize'), + ('avatar_resize_upload', '1'), + ('avatar_download_png', '1'), + ('failed_login_threshold', '3'), + ('edit_wait_time', '90'), + ('autoFixDatabase', '1'), + ('autoOptDatabase', '7'), + ('autoOptMaxOnline', '0'), + ('autoOptLastOpt', '0'), + ('enableParticipation', '1'), + ('recycle_enable', '0'), + ('recycle_board', '0'), + ('banLastUpdated', '0'), + ('enableAllMessages', '0'), + ('fixLongWords', '0'), + ('knownThemes', '1,2'), + ('who_enabled', '1'), + ('lastActive', '15'), + ('allow_hideOnline', '1'), + ('guest_hideContacts', '0'); +---# + +---# Adding new settings (part 2)... +---{ +upgrade_query(" + INSERT IGNORE INTO {$db_prefix}settings + (variable, value) + VALUES + ('registration_method', '" . (!empty($modSettings['registration_disabled']) ? 3 : (!empty($modSettings['approve_registration']) ? 2 : (!empty($GLOBALS['emailpassword']) || !empty($modSettings['send_validation']) ? 1 : 0))) . "'), + ('send_validation_onChange', '" . @$GLOBALS['emailnewpass'] . "'), + ('send_welcomeEmail', '" . @$GLOBALS['emailwelcome'] . "'), + ('allow_hideEmail', '" . @$GLOBALS['allow_hide_email'] . "'), + ('allow_guestAccess', '" . @$GLOBALS['guestaccess'] . "'), + ('time_format', '" . (!empty($GLOBALS['timeformatstring']) ? $GLOBALS['timeformatstring'] : '%B %d, %Y, %I:%M:%S %p') . "'), + ('enableBBC', '" . (!isset($GLOBALS['enable_ubbc']) ? 1 : $GLOBALS['enable_ubbc']) . "'), + ('max_messageLength', '" . (empty($GLOBALS['MaxMessLen']) ? 10000 : $GLOBALS['MaxMessLen']) . "'), + ('max_signatureLength', '" . @$GLOBALS['MaxSigLen'] . "'), + ('spamWaitTime', '" . @$GLOBALS['timeout'] . "'), + ('avatar_directory', '" . (isset($GLOBALS['facesdir']) ? fixRelativePath($GLOBALS['facesdir']) : fixRelativePath('./avatars')) . "'), + ('avatar_url', '" . @$GLOBALS['facesurl'] . "'), + ('avatar_max_height_external', '" . @$GLOBALS['userpic_height'] . "'), + ('avatar_max_width_external', '" . @$GLOBALS['userpic_width'] . "'), + ('avatar_max_height_upload', '" . @$GLOBALS['userpic_height'] . "'), + ('avatar_max_width_upload', '" . @$GLOBALS['userpic_width'] . "'), + ('defaultMaxMessages', '" . (empty($GLOBALS['maxmessagedisplay']) ? 15 : $GLOBALS['maxmessagedisplay']) . "'), + ('defaultMaxTopics', '" . (empty($GLOBALS['maxdisplay']) ? 20 : $GLOBALS['maxdisplay']) . "'), + ('defaultMaxMembers', '" . (empty($GLOBALS['MembersPerPage']) ? 20 : $GLOBALS['MembersPerPage']) . "'), + ('time_offset', '" . @$GLOBALS['timeoffset'] . "'), + ('cookieTime', '" . (empty($GLOBALS['Cookie_Length']) ? 60 : $GLOBALS['Cookie_Length']) . "'), + ('requireAgreement', '" . @$GLOBALS['RegAgree'] . "')"); +---} + +INSERT IGNORE INTO {$db_prefix}settings + (variable, value) +VALUES + ('smileys_dir', '{$sboarddir}/Smileys'), + ('smileys_url', '{$boardurl}/Smileys'), + ('smiley_sets_known', 'default,classic'), + ('smiley_sets_names', 'Default\nClassic'), + ('smiley_sets_default', 'default'), + ('censorIgnoreCase', '1'), + ('cal_days_for_index', '7'), + ('unapprovedMembers', '0'), + ('default_personalText', ''), + ('attachmentPostLimit', '192'), + ('attachmentNumPerPostLimit', '4'), + ('package_make_backups', '1'), + ('databaseSession_loose', '1'), + ('databaseSession_lifetime', '2880'), + ('smtp_port', '25'), + ('search_cache_size', '50'), + ('search_results_per_page', '30'), + ('search_weight_frequency', '30'), + ('search_weight_age', '25'), + ('search_weight_length', '20'), + ('search_weight_subject', '15'), + ('search_weight_first_message', '10'); + +DELETE FROM {$db_prefix}settings +WHERE variable = 'agreement' +LIMIT 1; +---# + +---# Converting settings to options... +---{ +convertSettingsToOptions(); +---} +---# + +---# Updating statistics... +REPLACE INTO {$db_prefix}settings + (variable, value) +SELECT 'latestMember', ID_MEMBER +FROM {$db_prefix}members +ORDER BY ID_MEMBER DESC +LIMIT 1; + +REPLACE INTO {$db_prefix}settings + (variable, value) +SELECT 'latestRealName', IFNULL(realName, memberName) +FROM {$db_prefix}members +ORDER BY ID_MEMBER DESC +LIMIT 1; + +REPLACE INTO {$db_prefix}settings + (variable, value) +SELECT 'maxMsgID', ID_MSG +FROM {$db_prefix}messages +ORDER BY ID_MSG DESC +LIMIT 1; + +REPLACE INTO {$db_prefix}settings + (variable, value) +SELECT 'totalMembers', COUNT(*) +FROM {$db_prefix}members; + +REPLACE INTO {$db_prefix}settings + (variable, value) +SELECT 'unapprovedMembers', COUNT(*) +FROM {$db_prefix}members +WHERE is_activated = 0 + AND validation_code = ''; + +REPLACE INTO {$db_prefix}settings + (variable, value) +SELECT 'totalMessages', COUNT(*) +FROM {$db_prefix}messages; + +REPLACE INTO {$db_prefix}settings + (variable, value) +SELECT 'totalTopics', COUNT(*) +FROM {$db_prefix}topics; + +REPLACE INTO {$db_prefix}settings + (variable, value) +VALUES ('cal_today_updated', '00000000'); +---# \ No newline at end of file diff --git a/upgrade_1-1.sql b/upgrade_1-1.sql new file mode 100644 index 0000000..0575ae3 --- /dev/null +++ b/upgrade_1-1.sql @@ -0,0 +1,2655 @@ +/* ATTENTION: You don't need to run or use this file! The upgrade.php script does everything for you! */ + +/******************************************************************************/ +--- Updating and creating indexes... +/******************************************************************************/ + +---# Updating indexes on "messages"... +---{ +$request = upgrade_query(" + SHOW KEYS + FROM {$db_prefix}messages"); +$found = false; +while ($row = mysql_fetch_assoc($request)) + $found |= $row['Key_name'] == 'ID_BOARD' && $row['Column_name'] == 'ID_MSG'; +mysql_free_result($request); + +if (!$found) + upgrade_query(" + ALTER TABLE {$db_prefix}messages + DROP INDEX ID_BOARD"); +---} +---# + +---# Updating table indexes... +---{ +$_GET['mess_ind'] = isset($_GET['mess_ind']) ? (int) $_GET['mess_ind'] : 0; +$step_progress['name'] = 'Updating table indexes'; +$step_progress['current'] = $_GET['mess_ind']; +$custom_warning = 'On a very large board these indexes may take a few minutes to create.'; + +$index_changes = array( + array( + 'table' => 'log_errors', + 'type' => 'index', + 'method' => 'add', + 'name' => 'ID_MEMBER', + 'target_columns' => array('ID_MEMBER'), + 'text' => 'ADD INDEX ID_MEMBER (ID_MEMBER)', + ), + array( + 'table' => 'log_errors', + 'type' => 'index', + 'method' => 'add', + 'name' => 'IP', + 'target_columns' => array('IP'), + 'text' => 'ADD INDEX IP (IP(15))', + ), + array( + 'table' => 'log_online', + 'type' => 'index', + 'method' => 'add', + 'name' => 'logTime', + 'target_columns' => array('logTime'), + 'text' => 'ADD INDEX logTime (logTime)', + ), + array( + 'table' => 'log_online', + 'type' => 'index', + 'method' => 'remove', + 'name' => 'online', + 'target_columns' => array('online'), + 'text' => 'DROP INDEX online', + ), + array( + 'table' => 'smileys', + 'type' => 'index', + 'method' => 'remove', + 'name' => 'smileyOrder', + 'target_columns' => array('smileyOrder'), + 'text' => 'DROP INDEX smileyOrder', + ), + array( + 'table' => 'boards', + 'type' => 'index', + 'method' => 'add', + 'name' => 'ID_PARENT', + 'target_columns' => array('ID_PARENT'), + 'text' => 'ADD INDEX ID_PARENT (ID_PARENT)', + ), + array( + 'table' => 'boards', + 'type' => 'index', + 'method' => 'remove', + 'name' => 'children', + 'target_columns' => array('children'), + 'text' => 'DROP INDEX children', + ), + array( + 'table' => 'boards', + 'type' => 'index', + 'method' => 'remove', + 'name' => 'boardOrder', + 'target_columns' => array('boardOrder'), + 'text' => 'DROP INDEX boardOrder', + ), + array( + 'table' => 'categories', + 'type' => 'index', + 'method' => 'remove', + 'name' => 'catOrder', + 'target_columns' => array('catOrder'), + 'text' => 'DROP INDEX catOrder', + ), + array( + 'table' => 'messages', + 'type' => 'index', + 'method' => 'add', + 'name' => 'ID_TOPIC', + 'target_columns' => array('ID_TOPIC'), + 'text' => 'ADD INDEX ID_TOPIC (ID_TOPIC)', + ), + array( + 'table' => 'messages', + 'type' => 'index', + 'method' => 'remove', + 'name' => 'ID_MEMBER', + 'target_columns' => array('ID_MEMBER'), + 'text' => 'DROP INDEX ID_MEMBER', + ), + array( + 'table' => 'messages', + 'type' => 'index', + 'method' => 'add', + 'name' => 'ID_BOARD', + 'target_columns' => array('ID_BOARD', 'ID_MSG'), + 'text' => 'ADD UNIQUE ID_BOARD (ID_BOARD, ID_MSG)', + ), + array( + 'table' => 'messages', + 'type' => 'index', + 'method' => 'add', + 'name' => 'ID_MEMBER', + 'target_columns' => array('ID_MEMBER', 'ID_MSG'), + 'text' => 'ADD UNIQUE ID_MEMBER (ID_MEMBER, ID_MSG)', + ), + array( + 'table' => 'messages', + 'type' => 'index', + 'method' => 'add', + 'name' => 'showPosts', + 'target_columns' => array('ID_MEMBER', 'ID_BOARD'), + 'text' => 'ADD INDEX showPosts (ID_MEMBER, ID_BOARD)', + ), +); + +$step_progress['total'] = count($index_changes); + +// Now we loop through the changes and work out where the hell we are. +foreach ($index_changes as $ind => $change) +{ + // Already done it? + if ($_GET['mess_ind'] > $ind) + continue; + + // Make the index, with all the protection and all. + protected_alter($change, $substep); + + // Store this for the next table. + $_GET['mess_ind']++; + $step_progress['current'] = $_GET['mess_ind']; +} + +// Clean up. +unset($_GET['mess_ind']); +---} +---# + +---# Reordering boards and categories... +ALTER TABLE {$db_prefix}categories +ORDER BY catOrder; + +ALTER TABLE {$db_prefix}boards +ORDER BY boardOrder; +---# + +---# Updating indexes and data on "smileys"... +ALTER TABLE {$db_prefix}smileys +CHANGE COLUMN smileyOrder smileyOrder smallint(5) unsigned NOT NULL default '0'; + +UPDATE {$db_prefix}smileys +SET filename = 'embarrassed.gif' +WHERE filename = 'embarassed.gif'; +---# + +---# Updating indexes on "log_boards"... +ALTER TABLE {$db_prefix}log_boards +DROP PRIMARY KEY, +ADD PRIMARY KEY (ID_MEMBER, ID_BOARD); +---# + +---# Updating indexes on "log_mark_read"... +ALTER TABLE {$db_prefix}log_mark_read +DROP PRIMARY KEY, +ADD PRIMARY KEY (ID_MEMBER, ID_BOARD); +---# + +---# Updating indexes on "themes"... +ALTER TABLE {$db_prefix}themes +DROP PRIMARY KEY, +ADD PRIMARY KEY (ID_THEME, ID_MEMBER, variable(30)), +ADD INDEX ID_MEMBER (ID_MEMBER); +---# + +/******************************************************************************/ +--- Reorganizing configuration settings... +/******************************************************************************/ + +---# Updating data in "settings"... +REPLACE INTO {$db_prefix}settings + (variable, value) +SELECT 'totalMembers', COUNT(*) +FROM {$db_prefix}members; + +UPDATE {$db_prefix}settings +SET variable = 'notify_new_registration' +WHERE variable = 'notify_on_new_registration' +LIMIT 1; + +UPDATE IGNORE {$db_prefix}settings +SET variable = 'max_image_width' +WHERE variable = 'maxwidth' +LIMIT 1; + +UPDATE IGNORE {$db_prefix}settings +SET variable = 'max_image_height' +WHERE variable = 'maxheight' +LIMIT 1; + +UPDATE {$db_prefix}settings +SET value = IF(value = 'sendmail' OR value = '0', '0', '1') +WHERE variable = 'mail_type' +LIMIT 1; + +UPDATE IGNORE {$db_prefix}settings +SET variable = 'search_method' +WHERE variable = 'search_match_complete_words' +LIMIT 1; + +UPDATE IGNORE {$db_prefix}settings +SET variable = 'allow_disableAnnounce' +WHERE variable = 'notifyAnncmnts_UserDisable' +LIMIT 1; +---# + +---# Adding new settings... +INSERT IGNORE INTO {$db_prefix}settings + (variable, value) +VALUES ('edit_disable_time', '0'), + ('oldTopicDays', '120'), + ('cal_showeventsoncalendar', '1'), + ('cal_showbdaysoncalendar', '1'), + ('cal_showholidaysoncalendar', '1'), + ('allow_disableAnnounce', '1'), + ('attachmentThumbnails', '1'), + ('attachmentThumbWidth', '150'), + ('attachmentThumbHeight', '150'), + ('max_pm_recipients', '10'); + +---{ +if (@$modSettings['smfVersion'] < '1.1') +{ + // Hopefully 90 days is enough? + upgrade_query(" + INSERT INTO {$db_prefix}settings + (variable, value) + VALUES ('disableHashTime', " . (time() + 7776000) . ")"); +} + +if (isset($modSettings['smfVersion']) && $modSettings['smfVersion'] <= '1.1 Beta 4') +{ + // Enable the buddy list for those used to it. + upgrade_query(" + INSERT INTO {$db_prefix}settings + (variable, value) + VALUES ('enable_buddylist', '1')"); +} +---} +---# + +---# Adding PM spam protection settings. +---{ +if (empty($modSettings['pm_spam_settings'])) +{ + if (isset($modSettings['max_pm_recipients'])) + $modSettings['pm_spam_settings'] = (int) $modSettings['max_pm_recipients'] . ',5,20'; + else + $modSettings['pm_spam_settings'] = '10,5,20'; + + upgrade_query(" + INSERT IGNORE INTO {$db_prefix}settings + (variable, value) + VALUES + ('pm_spam_settings', '$modSettings[pm_spam_settings]')"); +} +upgrade_query(" + DELETE FROM {$db_prefix}settings + WHERE variable = 'max_pm_recipients'"); +---} +---# + +---# Cleaning old values from "settings"... +DELETE FROM {$db_prefix}settings +WHERE variable IN ('modlog_enabled', 'localCookies', 'globalCookies', 'send_welcomeEmail', 'search_method', 'notify_new_registration', 'removeNestedQuotes', 'smiley_enable', 'smiley_sets_enable') + AND value = '0'; + +DELETE FROM {$db_prefix}settings +WHERE variable IN ('allow_guestAccess', 'userLanguage', 'allow_editDisplayName', 'allow_hideOnline', 'allow_hideEmail', 'guest_hideContacts', 'titlesEnable', 'search_match_complete_words') + AND value = '0'; + +DELETE FROM {$db_prefix}settings +WHERE variable IN ('cal_allowspan', 'hitStats', 'queryless_urls', 'disableHostnameLookup', 'messageIcons_enable', 'disallow_sendBody', 'censorWholeWord') + AND value = '0'; + +DELETE FROM {$db_prefix}settings +WHERE variable IN ( + 'totalMessag', + 'redirectMetaRefresh', + 'memberCount', + 'cal_today_u', + 'approve_registration', + 'registration_disabled', + 'requireRegistrationVerification', + 'returnToPost', + 'send_validation', + 'search_max_cached_results', + 'disableTemporaryTables', + 'search_cache_size', + 'enableReportToMod' +); +---# + +---# Encoding SMTP password... +---{ +// Can't do this more than once, we just can't... +if ((!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] <= '1.1 RC1') && empty($modSettings['dont_repeat_smtp'])) +{ + if (!empty($modSettings['smtp_password'])) + { + upgrade_query(" + UPDATE {$db_prefix}settings + SET value = '" . base64_encode($modSettings['smtp_password']) . "' + WHERE variable = 'smtp_password'"); + } + // Don't let this run twice! + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('dont_repeat_smtp', '1')"); +} + +---} +---# + +---# Adjusting timezone settings... +---{ + if (!isset($modSettings['default_timezone']) && function_exists('date_default_timezone_set')) + { + $server_offset = mktime(0, 0, 0, 1, 1, 1970); + $timezone_id = 'Etc/GMT' . ($server_offset > 0 ? '+' : '') . ($server_offset / 3600); + if (date_default_timezone_set($timezone_id)) + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('default_timezone', '$timezone_id')"); + } +---} +---# + +/******************************************************************************/ +--- Installing new default theme... +/******************************************************************************/ + +---# Installing theme settings... +---{ +// This is Grudge's secret "I'm not a developer" theme install code - keep this quiet ;) + +// Firstly, I'm going out of my way to not do this twice! +if ((!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] <= '1.1 RC1') && empty($modSettings['dont_repeat_theme'])) +{ + // Check it's not already here, just incase. + $theme_request = upgrade_query(" + SELECT ID_THEME + FROM {$db_prefix}themes + WHERE variable = 'theme_dir' + AND value LIKE '%babylon'"); + // Only do the upgrade if it doesn't find the theme already. + if (mysql_num_rows($theme_request) == 0) + { + // Try to get some settings from the current default theme. + $request = upgrade_query(" + SELECT t1.value AS theme_dir, t2.value AS theme_url, t3.value AS images_url + FROM ({$db_prefix}themes AS t1, {$db_prefix}themes AS t2, {$db_prefix}themes AS t3) + WHERE t1.ID_THEME = 1 + AND t1.ID_MEMBER = 0 + AND t1.variable = 'theme_dir' + AND t2.ID_THEME = 1 + AND t2.ID_MEMBER = 0 + AND t2.variable = 'theme_url' + AND t3.ID_THEME = 1 + AND t3.ID_MEMBER = 0 + AND t3.variable = 'images_url' + LIMIT 1"); + if (mysql_num_rows($request) != 0) + { + $core = mysql_fetch_assoc($request); + + if (substr_count($core['theme_dir'], 'default') === 1) + $babylon['theme_dir'] = strtr($core['theme_dir'], array('default' => 'babylon')); + if (substr_count($core['theme_url'], 'default') === 1) + $babylon['theme_url'] = strtr($core['theme_url'], array('default' => 'babylon')); + if (substr_count($core['images_url'], 'default') === 1) + $babylon['images_url'] = strtr($core['images_url'], array('default' => 'babylon')); + } + mysql_free_result($request); + + if (!isset($babylon['theme_dir'])) + $babylon['theme_dir'] = addslashes($GLOBALS['boarddir']) . '/Themes/babylon'; + if (!isset($babylon['theme_url'])) + $babylon['theme_url'] = $GLOBALS['boardurl'] . '/Themes/babylon'; + if (!isset($babylon['images_url'])) + $babylon['images_url'] = $GLOBALS['boardurl'] . '/Themes/babylon/images'; + + // Get an available ID_THEME first... + $request = upgrade_query(" + SELECT MAX(ID_THEME) + 1 + FROM {$db_prefix}themes"); + list ($ID_OLD_THEME) = mysql_fetch_row($request); + mysql_free_result($request); + + // Insert the babylon theme into the tables. + upgrade_query(" + INSERT INTO {$db_prefix}themes + (ID_MEMBER, ID_THEME, variable, value) + VALUES + (0, $ID_OLD_THEME, 'name', 'Babylon Theme'), + (0, $ID_OLD_THEME, 'theme_url', '$babylon[theme_url]'), + (0, $ID_OLD_THEME, 'images_url', '$babylon[images_url]'), + (0, $ID_OLD_THEME, 'theme_dir', '$babylon[theme_dir]')"); + + $newSettings = array(); + // Now that we have the old theme details - switch anyone who used the default to it (Make sense?!) + if (!empty($modSettings['theme_default']) && $modSettings['theme_default'] == 1) + $newSettings[] = "('theme_default', $ID_OLD_THEME)"; + // Did guests use to use the default? + if (!empty($modSettings['theme_guests']) && $modSettings['theme_guests'] == 1) + $newSettings[] = "('theme_guests', $ID_OLD_THEME)"; + + // If known themes aren't set, let's just pick all themes available. + if (empty($modSettings['knownThemes'])) + { + $request = upgrade_query(" + SELECT DISTINCT ID_THEME + FROM {$db_prefix}themes"); + $themes = array(); + while ($row = mysql_fetch_assoc($request)) + $themes[] = $row['ID_THEME']; + $modSettings['knownThemes'] = implode(',', $themes); + upgrade_query(" + UPDATE {$db_prefix}settings + SET value = '$modSettings[knownThemes]' + WHERE variable = 'knownThemes'"); + } + + // Known themes. + $allThemes = explode(',', $modSettings['knownThemes']); + $allThemes[] = $ID_OLD_THEME; + $newSettings[] = "('knownThemes', '" . implode(',', $allThemes) . "')"; + + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + " . implode(', ', $newSettings)); + + // What about members? + upgrade_query(" + UPDATE {$db_prefix}members + SET ID_THEME = $ID_OLD_THEME + WHERE ID_THEME = 1"); + + // Boards? + upgrade_query(" + UPDATE {$db_prefix}boards + SET ID_THEME = $ID_OLD_THEME + WHERE ID_THEME = 1"); + + // The other themes used to use babylon as their base theme. + if (isset($babylon['theme_dir']) && isset($babylon['theme_url'])) + { + $babylonBasedThemes = array_diff($allThemes, array(1)); + + // Exclude the themes that already have a base_theme_dir. + $request = upgrade_query(" + SELECT DISTINCT ID_THEME + FROM {$db_prefix}themes + WHERE variable = 'base_theme_dir'"); + while ($row = mysql_fetch_assoc($request)) + $babylonBasedThemes = array_diff($babylonBasedThemes, array($row['ID_THEME'])); + mysql_free_result($request); + + // Only base themes if there are templates that need a fall-back. + $insertRows = array(); + $request = upgrade_query(" + SELECT ID_THEME, value AS theme_dir + FROM {$db_prefix}themes + WHERE ID_THEME IN (" . implode(', ', $babylonBasedThemes) . ") + AND ID_MEMBER = 0 + AND variable = 'theme_dir'"); + while ($row = mysql_fetch_assoc($request)) + { + if (!file_exists($row['theme_dir'] . '/BoardIndex.template.php') || !file_exists($row['theme_dir'] . '/Display.template.php') || !file_exists($row['theme_dir'] . '/index.template.php') || !file_exists($row['theme_dir'] . '/MessageIndex.template.php') || !file_exists($row['theme_dir'] . '/Settings.template.php')) + { + $insertRows[] = "(0, $row[ID_THEME], 'base_theme_dir', '" . addslashes($babylon['theme_dir']) . "')"; + $insertRows[] = "(0, $row[ID_THEME], 'base_theme_url', '" . addslashes($babylon['theme_url']) . "')"; + } + } + mysql_free_result($request); + + if (!empty($insertRows)) + upgrade_query(" + INSERT IGNORE INTO {$db_prefix}themes + (ID_MEMBER, ID_THEME, variable, value) + VALUES + " . implode(', + ', $insertRows)); + } + } + mysql_free_result($theme_request); + + // This ain't running twice either - not with the risk of log_tables timing us all out! + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('dont_repeat_theme', '1')"); +} + +---} +---# + +/******************************************************************************/ +--- Adding and updating member data... +/******************************************************************************/ + +---# Renaming personal message tables... +RENAME TABLE {$db_prefix}instant_messages +TO {$db_prefix}personal_messages; + +RENAME TABLE {$db_prefix}im_recipients +TO {$db_prefix}pm_recipients; +---# + +---# Updating indexes on "pm_recipients"... +ALTER TABLE {$db_prefix}pm_recipients +DROP INDEX ID_MEMBER, +ADD UNIQUE ID_MEMBER (ID_MEMBER, deleted, ID_PM); +---# + +---# Updating columns on "pm_recipients"... +ALTER TABLE {$db_prefix}pm_recipients +ADD COLUMN labels varchar(60) NOT NULL default '-1'; + +ALTER TABLE {$db_prefix}pm_recipients +CHANGE COLUMN labels labels varchar(60) NOT NULL default '-1'; + +UPDATE {$db_prefix}pm_recipients +SET labels = '-1' +WHERE labels NOT RLIKE '[0-9,\-]' OR labels = ''; +---# + +---# Updating columns on "members"... +ALTER TABLE {$db_prefix}members +ADD COLUMN messageLabels text NOT NULL, +ADD COLUMN buddy_list tinytext NOT NULL, +ADD COLUMN notifySendBody tinyint(4) NOT NULL default '0', +ADD COLUMN notifyTypes tinyint(4) NOT NULL default '2', +CHANGE COLUMN im_ignore_list pm_ignore_list tinytext NOT NULL, +CHANGE COLUMN im_email_notify pm_email_notify tinyint(4) NOT NULL default '0'; +---# + +---# Updating columns on "members" - part 2... +ALTER TABLE {$db_prefix}members +CHANGE COLUMN secretAnswer secretAnswer varchar(64) NOT NULL default ''; + +ALTER TABLE {$db_prefix}members +ADD COLUMN memberIP2 tinytext NOT NULL; +---# + +---# Updating member approval... +---{ +// Although it *shouldn't* matter, best to do it just once to be sure. +if (@$modSettings['smfVersion'] < '1.1') +{ + upgrade_query(" + UPDATE {$db_prefix}members + SET is_activated = 3 + WHERE validation_code = '' + AND is_activated = 0"); +} +---} +---# + +/******************************************************************************/ +--- Updating holidays and calendar... +/******************************************************************************/ + +---# Adding new holidays... +---{ +$result = upgrade_query(" + SELECT ID_HOLIDAY + FROM {$db_prefix}calendar_holidays + WHERE YEAR(eventDate) > 2010 + LIMIT 1"); +$do_it = mysql_num_rows($result) == 0; +mysql_free_result($result); + +if ($do_it) +{ + upgrade_query(" + INSERT INTO {$db_prefix}calendar_holidays + (title, eventDate) + VALUES + ('Mother\\'s Day', '2011-05-08'), + ('Mother\\'s Day', '2012-05-13'), + ('Mother\\'s Day', '2013-05-12'), + ('Mother\\'s Day', '2014-05-11'), + ('Mother\\'s Day', '2015-05-10'), + ('Mother\\'s Day', '2016-05-08'), + ('Mother\\'s Day', '2017-05-14'), + ('Mother\\'s Day', '2018-05-13'), + ('Mother\\'s Day', '2019-05-12'), + ('Mother\\'s Day', '2020-05-10'), + ('Father\\'s Day', '2011-06-19'), + ('Father\\'s Day', '2012-06-17'), + ('Father\\'s Day', '2013-06-16'), + ('Father\\'s Day', '2014-06-15'), + ('Father\\'s Day', '2015-06-21'), + ('Father\\'s Day', '2016-06-19'), + ('Father\\'s Day', '2017-06-18'), + ('Father\\'s Day', '2018-06-17'), + ('Father\\'s Day', '2019-06-16'), + ('Father\\'s Day', '2020-06-21'), + ('Summer Solstice', '2011-06-21'), + ('Summer Solstice', '2012-06-20'), + ('Summer Solstice', '2013-06-21'), + ('Summer Solstice', '2014-06-21'), + ('Summer Solstice', '2015-06-21'), + ('Summer Solstice', '2016-06-20'), + ('Summer Solstice', '2017-06-20'), + ('Summer Solstice', '2018-06-21'), + ('Summer Solstice', '2019-06-21'), + ('Summer Solstice', '2020-06-20'), + ('Vernal Equinox', '2011-03-20'), + ('Vernal Equinox', '2012-03-20'), + ('Vernal Equinox', '2013-03-20'), + ('Vernal Equinox', '2014-03-20'), + ('Vernal Equinox', '2015-03-20'), + ('Vernal Equinox', '2016-03-19'), + ('Vernal Equinox', '2017-03-20'), + ('Vernal Equinox', '2018-03-20'), + ('Vernal Equinox', '2019-03-20'), + ('Vernal Equinox', '2020-03-19'), + ('Winter Solstice', '2011-12-22'), + ('Winter Solstice', '2012-12-21'), + ('Winter Solstice', '2013-12-21'), + ('Winter Solstice', '2014-12-21'), + ('Winter Solstice', '2015-12-21'), + ('Winter Solstice', '2016-12-21'), + ('Winter Solstice', '2017-12-21'), + ('Winter Solstice', '2018-12-21'), + ('Winter Solstice', '2019-12-21'), + ('Winter Solstice', '2020-12-21'), + ('Autumnal Equinox', '2011-09-23'), + ('Autumnal Equinox', '2012-09-22'), + ('Autumnal Equinox', '2013-09-22'), + ('Autumnal Equinox', '2014-09-22'), + ('Autumnal Equinox', '2015-09-23'), + ('Autumnal Equinox', '2016-09-22'), + ('Autumnal Equinox', '2017-09-22'), + ('Autumnal Equinox', '2018-09-22'), + ('Autumnal Equinox', '2019-09-23'), + ('Autumnal Equinox', '2020-09-22'), + ('Thanksgiving', '2011-11-24'), + ('Thanksgiving', '2012-11-22'), + ('Thanksgiving', '2013-11-21'), + ('Thanksgiving', '2014-11-20'), + ('Thanksgiving', '2015-11-26'), + ('Thanksgiving', '2016-11-24'), + ('Thanksgiving', '2017-11-23'), + ('Thanksgiving', '2018-11-22'), + ('Thanksgiving', '2019-11-21'), + ('Thanksgiving', '2020-11-26'), + ('Memorial Day', '2011-05-30'), + ('Memorial Day', '2012-05-28'), + ('Memorial Day', '2013-05-27'), + ('Memorial Day', '2014-05-26'), + ('Memorial Day', '2015-05-25'), + ('Memorial Day', '2016-05-30'), + ('Memorial Day', '2017-05-29'), + ('Memorial Day', '2018-05-28'), + ('Memorial Day', '2019-05-27'), + ('Memorial Day', '2020-05-25'), + ('Labor Day', '2011-09-05'), + ('Labor Day', '2012-09-03'), + ('Labor Day', '2013-09-09'), + ('Labor Day', '2014-09-08'), + ('Labor Day', '2015-09-07'), + ('Labor Day', '2016-09-05'), + ('Labor Day', '2017-09-04'), + ('Labor Day', '2018-09-03'), + ('Labor Day', '2019-09-09'), + ('Labor Day', '2020-09-07')"); +} +---} +---# + +---# Updating event start and end dates... +ALTER TABLE {$db_prefix}calendar +DROP INDEX eventDate; + +ALTER TABLE {$db_prefix}calendar +CHANGE COLUMN eventDate startDate date NOT NULL default '0001-01-01'; + +ALTER TABLE {$db_prefix}calendar +CHANGE COLUMN startDate startDate date NOT NULL default '0001-01-01'; + +UPDATE {$db_prefix}calendar +SET startDate = '0001-01-01' +WHERE startDate = '0000-00-00'; + +ALTER TABLE {$db_prefix}calendar +ADD COLUMN endDate date NOT NULL default '0001-01-01'; + +ALTER TABLE {$db_prefix}calendar +CHANGE COLUMN endDate endDate date NOT NULL default '0001-01-01'; + +UPDATE {$db_prefix}calendar +SET endDate = startDate +WHERE endDate = '0001-01-01' + OR endDate = '0000-00-00'; + +ALTER TABLE {$db_prefix}calendar +ADD INDEX startDate (startDate), +ADD INDEX endDate (endDate); + +ALTER TABLE {$db_prefix}calendar +DROP INDEX ID_TOPIC; + +ALTER TABLE {$db_prefix}calendar +ADD INDEX topic (ID_TOPIC, ID_MEMBER); + +ALTER TABLE {$db_prefix}calendar_holidays +CHANGE COLUMN eventDate eventDate date NOT NULL default '0001-01-01'; + +UPDATE {$db_prefix}calendar_holidays +SET eventDate = '0001-01-01' +WHERE eventDate = '0000-00-00'; + +UPDATE {$db_prefix}calendar_holidays +SET eventDate = CONCAT('0004-', MONTH(eventDate), '-', DAYOFMONTH(eventDate)) +WHERE YEAR(eventDate) = 0; +---# + +---# Converting other date columns... +ALTER TABLE {$db_prefix}log_activity +CHANGE COLUMN startDate date date NOT NULL default '0001-01-01'; + +ALTER TABLE {$db_prefix}log_activity +CHANGE COLUMN date date date NOT NULL default '0001-01-01'; + +UPDATE {$db_prefix}log_activity +SET date = '0001-01-01' +WHERE date = '0000-00-00'; + +ALTER TABLE {$db_prefix}members +CHANGE COLUMN birthdate birthdate date NOT NULL default '0001-01-01'; + +UPDATE {$db_prefix}members +SET birthdate = '0001-01-01' +WHERE birthdate = '0000-00-00'; + +UPDATE {$db_prefix}members +SET birthdate = CONCAT('0004-', MONTH(birthdate), '-', DAYOFMONTH(birthdate)) +WHERE YEAR(birthdate) = 0; +---# + +/******************************************************************************/ +--- Adding custom message icons... +/******************************************************************************/ + +---# Checking for an old table... +---{ +$request = mysql_query(" + SHOW COLUMNS + FROM {$db_prefix}message_icons"); +$test = false; +while ($request && $row = mysql_fetch_row($request)) + $test |= $row[0] == 'Name'; +if ($request) + mysql_free_result($request); + +if ($test) +{ + upgrade_query(" + ALTER TABLE {$db_prefix}message_icons + DROP PRIMARY KEY, + CHANGE COLUMN id_icon id_icon smallint(5) unsigned NOT NULL auto_increment PRIMARY KEY, + CHANGE COLUMN Name filename varchar(80) NOT NULL default '', + CHANGE COLUMN Description title varchar(80) NOT NULL default '', + CHANGE COLUMN ID_BOARD ID_BOARD mediumint(8) unsigned NOT NULL default '0', + DROP INDEX id_icon, + ADD COLUMN iconOrder smallint(5) unsigned NOT NULL default '0'"); +} +---} +---# + +---# Creating "message_icons"... +CREATE TABLE IF NOT EXISTS {$db_prefix}message_icons ( + id_icon smallint(5) unsigned NOT NULL auto_increment, + title varchar(80) NOT NULL default '', + filename varchar(80) NOT NULL default '', + ID_BOARD mediumint(8) unsigned NOT NULL default 0, + iconOrder smallint(5) unsigned NOT NULL default 0, + PRIMARY KEY (id_icon), + KEY ID_BOARD (ID_BOARD) +) ENGINE=MyISAM; +---# + +---# Inserting "message_icons"... +---{ +// We do not want to do this twice! +if (@$modSettings['smfVersion'] < '1.1') +{ + upgrade_query(" + INSERT INTO {$db_prefix}message_icons + (filename, title, iconOrder) + VALUES ('xx', 'Standard', '0'), + ('thumbup', 'Thumb Up', '1'), + ('thumbdown', 'Thumb Down', '2'), + ('exclamation', 'Exclamation point', '3'), + ('question', 'Question mark', '4'), + ('lamp', 'Lamp', '5'), + ('smiley', 'Smiley', '6'), + ('angry', 'Angry', '7'), + ('cheesy', 'Cheesy', '8'), + ('grin', 'Grin', '9'), + ('sad', 'Sad', '10'), + ('wink', 'Wink', '11')"); +} +---} +---# + +/******************************************************************************/ +--- Adding package servers... +/******************************************************************************/ + +---# Creating "package_servers"... +CREATE TABLE IF NOT EXISTS {$db_prefix}package_servers ( + id_server smallint(5) unsigned NOT NULL auto_increment, + name tinytext NOT NULL, + url tinytext NOT NULL, + PRIMARY KEY (id_server) +) ENGINE=MyISAM; +---# + +---# Inserting "package_servers"... +INSERT IGNORE INTO {$db_prefix}package_servers + (id_server, name, url) +VALUES + (1, 'Simple Machines Third-party Mod Site', 'http://mods.simplemachines.org'); +---# + +/******************************************************************************/ +--- Cleaning up database... +/******************************************************************************/ + +---# Updating flood control log... +ALTER IGNORE TABLE {$db_prefix}log_floodcontrol +CHANGE COLUMN ip ip char(16) NOT NULL default ' '; + +ALTER TABLE {$db_prefix}log_floodcontrol +DROP INDEX logTime; +---# + +---# Updating ip address storage... +ALTER IGNORE TABLE {$db_prefix}log_actions +CHANGE COLUMN IP ip char(16) NOT NULL default ' '; + +ALTER IGNORE TABLE {$db_prefix}log_banned +CHANGE COLUMN IP ip char(16) NOT NULL default ' '; + +ALTER IGNORE TABLE {$db_prefix}log_banned +DROP COLUMN ban_ids; + +ALTER IGNORE TABLE {$db_prefix}log_errors +DROP INDEX IP, +CHANGE COLUMN IP ip char(16) NOT NULL default ' ', +ADD INDEX ip (ip(16)); +---# + +---# Converting "log_online"... +DROP TABLE IF EXISTS {$db_prefix}log_online; +CREATE TABLE {$db_prefix}log_online ( + session char(32) NOT NULL default ' ', + logTime timestamp /*!40102 NOT NULL default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP */, + ID_MEMBER mediumint(8) unsigned NOT NULL default '0', + ip int(10) unsigned NOT NULL default '0', + url text NOT NULL, + PRIMARY KEY (session), + KEY online (logTime, ID_MEMBER), + KEY ID_MEMBER (ID_MEMBER) +) ENGINE=MyISAM; +---# + +---# Updating poll column sizes... +ALTER TABLE {$db_prefix}polls +CHANGE COLUMN maxVotes maxVotes tinyint(3) unsigned NOT NULL default '1', +CHANGE COLUMN hideResults hideResults tinyint(3) unsigned NOT NULL default '0', +CHANGE COLUMN changeVote changeVote tinyint(3) unsigned NOT NULL default '0'; + +ALTER TABLE {$db_prefix}poll_choices +CHANGE COLUMN ID_CHOICE ID_CHOICE tinyint(3) unsigned NOT NULL default '0'; + +ALTER TABLE {$db_prefix}log_polls +CHANGE COLUMN ID_CHOICE ID_CHOICE tinyint(3) unsigned NOT NULL default '0'; +---# + +---# Updating attachments table... +ALTER TABLE {$db_prefix}attachments +DROP PRIMARY KEY, +CHANGE COLUMN ID_ATTACH ID_ATTACH int(10) unsigned NOT NULL auto_increment PRIMARY KEY; +---# + +---# Updating boards and topics... +ALTER TABLE {$db_prefix}topics +CHANGE COLUMN numReplies numReplies int(10) unsigned NOT NULL default 0, +CHANGE COLUMN numViews numViews int(10) unsigned NOT NULL default 0; +---# + +---# Updating members... +ALTER TABLE {$db_prefix}members +CHANGE COLUMN lastLogin lastLogin int(10) unsigned NOT NULL default 0; +---# + +---# Recounting member pm totals (step 1)... +---{ +$request = upgrade_query(" + SELECT COUNT(*) + FROM {$db_prefix}members"); +list ($totalMembers) = mysql_fetch_row($request); +mysql_free_result($request); + +$_GET['m'] = isset($_GET['m']) ? (int) $_GET['m'] : 0; + +while ($_GET['m'] < $totalMembers) +{ + nextSubstep($substep); + + $mrequest = upgrade_query(" + SELECT mem.ID_MEMBER, COUNT(pmr.ID_PM) AS instantMessages_real, mem.instantMessages + FROM {$db_prefix}members AS mem + LEFT JOIN {$db_prefix}pm_recipients AS pmr ON (pmr.ID_MEMBER = mem.ID_MEMBER AND pmr.deleted = 0) + WHERE mem.ID_MEMBER > $_GET[m] + AND mem.ID_MEMBER <= $_GET[m] + 128 + GROUP BY mem.ID_MEMBER + HAVING instantMessages_real != instantMessages + LIMIT 256"); + while ($row = mysql_fetch_assoc($mrequest)) + { + upgrade_query(" + UPDATE {$db_prefix}members + SET instantMessages = $row[instantMessages_real] + WHERE ID_MEMBER = $row[ID_MEMBER] + LIMIT 1"); + } + + $_GET['m'] += 128; +} +unset($_GET['m']); +---} +---# + +---# Recounting member pm totals (step 2)... +---{ +$request = upgrade_query(" + SELECT COUNT(*) + FROM {$db_prefix}members"); +list ($totalMembers) = mysql_fetch_row($request); +mysql_free_result($request); + +$_GET['m'] = isset($_GET['m']) ? (int) $_GET['m'] : 0; + +while ($_GET['m'] < $totalMembers) +{ + nextSubstep($substep); + + $mrequest = upgrade_query(" + SELECT mem.ID_MEMBER, COUNT(pmr.ID_PM) AS unreadMessages_real, mem.unreadMessages + FROM {$db_prefix}members AS mem + LEFT JOIN {$db_prefix}pm_recipients AS pmr ON (pmr.ID_MEMBER = mem.ID_MEMBER AND pmr.deleted = 0 AND pmr.is_read = 0) + WHERE mem.ID_MEMBER > $_GET[m] + AND mem.ID_MEMBER <= $_GET[m] + 128 + GROUP BY mem.ID_MEMBER + HAVING unreadMessages_real != unreadMessages + LIMIT 256"); + while ($row = mysql_fetch_assoc($mrequest)) + { + upgrade_query(" + UPDATE {$db_prefix}members + SET unreadMessages = $row[unreadMessages_real] + WHERE ID_MEMBER = $row[ID_MEMBER] + LIMIT 1"); + } + + $_GET['m'] += 128; +} +unset($_GET['m']); +---} +---# + +/******************************************************************************/ +--- Converting avatar permissions... +/******************************************************************************/ + +---# Converting server stored setting... +---{ +if (!empty($modSettings['avatar_allow_server_stored'])) +{ + // Create permissions for existing membergroups. + upgrade_query(" + INSERT INTO {$db_prefix}permissions + (ID_GROUP, permission) + SELECT IF(ID_GROUP = 1, 0, ID_GROUP), 'profile_server_avatar' + FROM {$db_prefix}membergroups + WHERE ID_GROUP != 3 + AND minPosts = -1"); +} +---} +---# + +---# Converting avatar upload setting... +---{ +// Do the same, but for uploading avatars. +if (!empty($modSettings['avatar_allow_upload'])) +{ + // Put in these permissions + upgrade_query(" + INSERT INTO {$db_prefix}permissions + (ID_GROUP, permission) + SELECT IF(ID_GROUP = 1, 0, ID_GROUP), 'profile_upload_avatar' + FROM {$db_prefix}membergroups + WHERE ID_GROUP != 3 + AND minPosts = -1"); +} +---} +---# + +/******************************************************************************/ +--- Adjusting uploadable avatars... +/******************************************************************************/ + +---# Updating attachments... +ALTER TABLE {$db_prefix}attachments +CHANGE COLUMN ID_MEMBER ID_MEMBER mediumint(8) unsigned NOT NULL default '0'; +---# + +---# Updating settings... +DELETE FROM {$db_prefix}settings +WHERE variable IN ('avatar_allow_external_url', 'avatar_check_size', 'avatar_allow_upload', 'avatar_allow_server_stored'); +---# + +/******************************************************************************/ +--- Updating thumbnails... +/******************************************************************************/ + +---# Registering thumbs... +---{ +// Checkout the current structure of the attachment table. +$request = mysql_query(" + SHOW COLUMNS + FROM {$db_prefix}attachments"); +$has_customAvatarDir_column = false; +$has_attachmentType_column = false; +while ($row = mysql_fetch_assoc($request)) +{ + $has_customAvatarDir_column |= $row['Field'] == 'customAvatarDir'; + $has_attachmentType_column |= $row['Field'] == 'attachmentType'; +} +mysql_free_result($request); + +// Post SMF 1.1 Beta 1. +if ($has_customAvatarDir_column) + $request = upgrade_query(" + ALTER TABLE {$db_prefix}attachments + CHANGE COLUMN customAvatarDir attachmentType tinyint(3) unsigned NOT NULL default '0'"); +// Pre SMF 1.1. +elseif (!$has_attachmentType_column) + $request = upgrade_query(" + ALTER TABLE {$db_prefix}attachments + ADD COLUMN attachmentType tinyint(3) unsigned NOT NULL default '0'"); + +if (!$has_attachmentType_column) +{ + $request = upgrade_query(" + ALTER TABLE {$db_prefix}attachments + ADD COLUMN id_thumb int(10) unsigned NOT NULL default '0' AFTER ID_ATTACH, + ADD COLUMN width mediumint(8) unsigned NOT NULL default '0', + ADD COLUMN height mediumint(8) unsigned NOT NULL default '0'"); + + // Get a list of attachments currently stored in the database. + $request = upgrade_query(" + SELECT ID_ATTACH, ID_MSG, filename + FROM {$db_prefix}attachments"); + $filenames = array(); + $encrypted_filenames = array(); + $ID_MSG = array(); + while ($row = mysql_fetch_assoc($request)) + { + $clean_name = strtr($row['filename'], '', 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'); + $clean_name = strtr($clean_name, array('' => 'TH', '' => 'th', '' => 'DH', '' => 'dh', '' => 'ss', '' => 'OE', '' => 'oe', '' => 'AE', '' => 'ae', '' => 'u')); + $clean_name = preg_replace(array('/\s/', '/[^\w_\.\-]/'), array('_', ''), $clean_name); + $enc_name = $row['ID_ATTACH'] . '_' . strtr($clean_name, '.', '_') . md5($clean_name); + $clean_name = preg_replace('~\.[\.]+~', '.', $clean_name); + + if (file_exists($modSettings['attachmentUploadDir'] . '/' . $enc_name)) + $filename = $enc_name; + elseif (file_exists($modSettings['attachmentUploadDir'] . '/' . $clean_name)) + $filename = $clean_name; + else + $filename = $row['filename']; + + $filenames[$row['ID_ATTACH']] = $clean_name; + $encrypted_filenames[$row['ID_ATTACH']] = $filename; + $ID_MSG[$row['ID_ATTACH']] = $row['ID_MSG']; + } + mysql_free_result($request); + + // Let's loop through the attachments + if (is_dir($modSettings['attachmentUploadDir']) && $dir = @opendir($modSettings['attachmentUploadDir'])) + { + while ($file = readdir($dir)) + { + if (substr($file, -6) == '_thumb') + { + // We found a thumbnail, now find the attachment it represents. + $attach_realFilename = substr($file, 0, -6); + if (in_array($attach_realFilename, $filenames)) + { + $attach_id = array_search($attach_realFilename, $filenames); + $attach_filename = $attach_realFilename; + } + elseif (in_array($attach_realFilename, $encrypted_filenames)) + { + $attach_id = array_search($attach_realFilename, $encrypted_filenames); + $attach_filename = $filenames[$attach_id]; + } + else + continue; + + // No need to register thumbs of non-existent attachments. + if (!file_exists($modSettings['attachmentUploadDir'] . '/' . $attach_realFilename) || strlen($attach_filename) > 249) + continue; + + // Determine the dimensions of the thumb. + list ($thumb_width, $thumb_height) = @getimagesize($modSettings['attachmentUploadDir'] . '/' . $file); + $thumb_size = filesize($modSettings['attachmentUploadDir'] . '/' . $file); + $thumb_filename = $attach_filename . '_thumb'; + + // Insert the thumbnail in the attachment database. + upgrade_query(" + INSERT INTO {$db_prefix}attachments + (ID_MSG, attachmentType, filename, size, width, height) + VALUES (" . $ID_MSG[$attach_id] . ", 3, '$thumb_filename', " . (int) $thumb_size . ', ' . (int) $thumb_width . ', ' . (int) $thumb_height . ')'); + $thumb_attach_id = mysql_insert_id(); + + // Determine the dimensions of the original attachment. + $attach_width = $attach_height = 0; + list ($attach_width, $attach_height) = @getimagesize($modSettings['attachmentUploadDir'] . '/' . $attach_realFilename); + + // Link the original attachment to its thumb. + upgrade_query(" + UPDATE {$db_prefix}attachments + SET + id_thumb = $thumb_attach_id, + width = " . (int) $attach_width . ", + height = " . (int) $attach_height . " + WHERE ID_ATTACH = $attach_id + LIMIT 1"); + + // Since it's an attachment now, we might as well encrypt it. + if (!empty($modSettings['attachmentEncryptFilenames'])) + @rename($modSettings['attachmentUploadDir'] . '/' . $file, $modSettings['attachmentUploadDir'] . '/' . $thumb_attach_id . '_' . strtr($thumb_filename, '.', '_') . md5($thumb_filename)); + } + } + closedir($dir); + } +} +---} +---# + +---# Adding image dimensions... +---{ +// Now add dimension to the images that have no thumb (yet). +$request = upgrade_query(" + SELECT ID_ATTACH, filename, attachmentType + FROM {$db_prefix}attachments + WHERE id_thumb = 0 + AND (RIGHT(filename, 4) IN ('.gif', '.jpg', '.png', '.bmp') OR RIGHT(filename, 5) = '.jpeg') + AND width = 0 + AND height = 0"); +while ($row = mysql_fetch_assoc($request)) +{ + if ($row['attachmentType'] == 1) + $filename = $modSettings['custom_avatar_dir'] . '/' . $row['filename']; + else + { + $clean_name = strtr($row['filename'], '', 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'); + $clean_name = strtr($clean_name, array('' => 'TH', '' => 'th', '' => 'DH', '' => 'dh', '' => 'ss', '' => 'OE', '' => 'oe', '' => 'AE', '' => 'ae', '' => 'u')); + $clean_name = preg_replace(array('/\s/', '/[^\w_\.\-]/'), array('_', ''), $clean_name); + $enc_name = $row['ID_ATTACH'] . '_' . strtr($clean_name, '.', '_') . md5($clean_name); + $clean_name = preg_replace('~\.[\.]+~', '.', $clean_name); + + if (file_exists($modSettings['attachmentUploadDir'] . '/' . $enc_name)) + $filename = $modSettings['attachmentUploadDir'] . '/' . $enc_name; + elseif (file_exists($modSettings['attachmentUploadDir'] . '/' . $clean_name)) + $filename = $modSettings['attachmentUploadDir'] . '/' . $clean_name; + else + $filename = $modSettings['attachmentUploadDir'] . '/' . $row['filename']; + } + + $width = 0; + $height = 0; + list ($width, $height) = @getimagesize($filename); + if (!empty($width) && !empty($height)) + upgrade_query(" + UPDATE {$db_prefix}attachments + SET + width = $width, + height = $height + WHERE ID_ATTACH = $row[ID_ATTACH] + LIMIT 1"); +} +mysql_free_result($request); +---} +---# + +/******************************************************************************/ +--- Updating ban system... +/******************************************************************************/ + +---# Splitting ban table... +---{ +// Checkout the current structure of the attachment table. +$request = mysql_query(" + SHOW TABLES + LIKE '{$db_prefix}banned'"); +$upgradeBanTable = mysql_num_rows($request) == 1; +mysql_free_result($request); + +if ($upgradeBanTable) +{ + upgrade_query(" + RENAME TABLE {$db_prefix}banned + TO {$db_prefix}ban_groups"); + upgrade_query(" + ALTER TABLE {$db_prefix}ban_groups + CHANGE COLUMN id_ban id_ban_group mediumint(8) unsigned NOT NULL auto_increment"); + + upgrade_query(" + CREATE TABLE IF NOT EXISTS {$db_prefix}ban_items ( + id_ban mediumint(8) unsigned NOT NULL auto_increment, + id_ban_group smallint(5) unsigned NOT NULL default '0', + ip_low1 tinyint(3) unsigned NOT NULL default '0', + ip_high1 tinyint(3) unsigned NOT NULL default '0', + ip_low2 tinyint(3) unsigned NOT NULL default '0', + ip_high2 tinyint(3) unsigned NOT NULL default '0', + ip_low3 tinyint(3) unsigned NOT NULL default '0', + ip_high3 tinyint(3) unsigned NOT NULL default '0', + ip_low4 tinyint(3) unsigned NOT NULL default '0', + ip_high4 tinyint(3) unsigned NOT NULL default '0', + hostname tinytext NOT NULL, + email_address tinytext NOT NULL, + ID_MEMBER mediumint(8) unsigned NOT NULL default '0', + hits mediumint(8) unsigned NOT NULL default '0', + PRIMARY KEY (id_ban), + KEY id_ban_group (id_ban_group) + ) ENGINE=MyISAM"); + + upgrade_query(" + INSERT INTO {$db_prefix}ban_items + (id_ban_group, ip_low1, ip_high1, ip_low2, ip_high2, ip_low3, ip_high3, ip_low4, ip_high4, hostname, email_address, ID_MEMBER) + SELECT id_ban_group, ip_low1, ip_high1, ip_low2, ip_high2, ip_low3, ip_high3, ip_low4, ip_high4, hostname, email_address, ID_MEMBER + FROM {$db_prefix}ban_groups"); + + upgrade_query(" + ALTER TABLE {$db_prefix}ban_groups + DROP COLUMN ban_type, + DROP COLUMN ip_low1, + DROP COLUMN ip_high1, + DROP COLUMN ip_low2, + DROP COLUMN ip_high2, + DROP COLUMN ip_low3, + DROP COLUMN ip_high3, + DROP COLUMN ip_low4, + DROP COLUMN ip_high4, + DROP COLUMN hostname, + DROP COLUMN email_address, + DROP COLUMN ID_MEMBER, + ADD COLUMN cannot_access tinyint(3) unsigned NOT NULL default '0' AFTER expire_time, + ADD COLUMN cannot_register tinyint(3) unsigned NOT NULL default '0' AFTER cannot_access, + ADD COLUMN cannot_post tinyint(3) unsigned NOT NULL default '0' AFTER cannot_register, + ADD COLUMN cannot_login tinyint(3) unsigned NOT NULL default '0' AFTER cannot_post"); + + // Generate names for existing bans. + upgrade_query(" + ALTER TABLE {$db_prefix}ban_groups + ADD COLUMN name varchar(20) NOT NULL default '' AFTER id_ban_group"); + + $request = mysql_query(" + SELECT id_ban_group, restriction_type + FROM {$db_prefix}ban_groups + ORDER BY ban_time ASC"); + $ban_names = array( + 'full_ban' => 1, + 'cannot_register' => 1, + 'cannot_post' => 1, + ); + if ($request != false) + { + while ($row = mysql_fetch_assoc($request)) + upgrade_query(" + UPDATE {$db_prefix}ban_groups + SET name = '" . $row['restriction_type'] . '_' . str_pad($ban_names[$row['restriction_type']]++, 3, '0', STR_PAD_LEFT) . "' + WHERE id_ban_group = $row[id_ban_group]"); + mysql_free_result($request); + } + + // Move each restriction type to its own column. + mysql_query(" + UPDATE {$db_prefix}ban_groups + SET + cannot_access = IF(restriction_type = 'full_ban', 1, 0), + cannot_register = IF(restriction_type = 'cannot_register', 1, 0), + cannot_post = IF(restriction_type = 'cannot_post', 1, 0)"); + upgrade_query(" + ALTER TABLE {$db_prefix}ban_groups + DROP COLUMN restriction_type"); + + // Make sure everybody's ban situation is re-evaluated. + upgrade_query(" + UPDATE {$db_prefix}settings + SET value = '" . time() . "' + WHERE variable = 'banLastUpdated'"); +} +---} +---# + +---# Updating ban statistics... +---{ + $request = upgrade_query(" + SELECT mem.ID_MEMBER, mem.is_activated + 10 AS new_value + FROM ({$db_prefix}ban_groups AS bg, {$db_prefix}ban_items AS bi, {$db_prefix}members AS mem) + WHERE bg.id_ban_group = bi.id_ban_group + AND bg.cannot_access = 1 + AND (bg.expire_time IS NULL OR bg.expire_time > " . time() . ") + AND (mem.ID_MEMBER = bi.ID_MEMBER OR mem.emailAddress LIKE bi.email_address) + AND mem.is_activated < 10"); + $updates = array(); + while ($row = mysql_fetch_assoc($request)) + $updates[$row['new_value']][] = $row['ID_MEMBER']; + mysql_free_result($request); + + // Find members that are wrongfully marked as banned. + $request = upgrade_query(" + SELECT mem.ID_MEMBER, mem.is_activated - 10 AS new_value + FROM {$db_prefix}members AS mem + LEFT JOIN {$db_prefix}ban_items AS bi ON (bi.ID_MEMBER = mem.ID_MEMBER OR mem.emailAddress LIKE bi.email_address) + LEFT JOIN {$db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group AND bg.cannot_access = 1 AND (bg.expire_time IS NULL OR bg.expire_time > " . time() . ")) + WHERE (bi.id_ban IS NULL OR bg.id_ban_group IS NULL) + AND mem.is_activated >= 10"); + while ($row = mysql_fetch_assoc($request)) + $updates[$row['new_value']][] = $row['ID_MEMBER']; + mysql_free_result($request); + + if (!empty($updates)) + foreach ($updates as $newStatus => $members) + upgrade_query(" + UPDATE {$db_prefix}members + SET is_activated = $newStatus + WHERE ID_MEMBER IN (" . implode(', ', $members) . ") + LIMIT " . count($members)); +---} +---# + +/******************************************************************************/ +--- Updating permissions... +/******************************************************************************/ + +---# Deleting some very old permissions... +DELETE FROM {$db_prefix}board_permissions +WHERE permission IN ('view_threads', 'poll_delete_own', 'poll_delete_any', 'profile_edit_own', 'profile_edit_any'); +---# + +---# Renaming permissions... +---{ +// We *cannot* do this twice! +if (@$modSettings['smfVersion'] < '1.1') +{ + upgrade_query(" + UPDATE {$db_prefix}board_permissions + SET + permission = REPLACE(permission, 'remove_replies', 'delete_replies'), + permission = REPLACE(permission, 'remove_own', 'delete2_own'), + permission = REPLACE(permission, 'remove_any', 'delete2_any')"); + upgrade_query(" + UPDATE {$db_prefix}board_permissions + SET + permission = REPLACE(permission, 'delete_own', 'remove_own'), + permission = REPLACE(permission, 'delete_any', 'remove_any')"); + upgrade_query(" + UPDATE {$db_prefix}board_permissions + SET + permission = REPLACE(permission, 'delete2_own', 'delete_own'), + permission = REPLACE(permission, 'delete2_any', 'delete_any')"); +} +---} +---# + +---# Upgrading "deny"-permissions... +---{ +if (!isset($modSettings['permission_enable_deny'])) +{ + // Only disable if no deny permissions are used. + $request = upgrade_query(" + SELECT permission + FROM {$db_prefix}permissions + WHERE addDeny = 0 + LIMIT 1"); + $disable_deny_permissions = mysql_num_rows($request) == 0; + mysql_free_result($request); + + // Still wanna disable deny permissions? Check board permissions. + if ($disable_deny_permissions) + { + $request = upgrade_query(" + SELECT permission + FROM {$db_prefix}board_permissions + WHERE addDeny = 0 + LIMIT 1"); + $disable_deny_permissions &= mysql_num_rows($request) == 0; + mysql_free_result($request); + } + + $request = upgrade_query(" + INSERT INTO {$db_prefix}settings + (variable, value) + VALUES ('permission_enable_deny', '" . ($disable_deny_permissions ? '0' : '1') . "')"); +} +---} +---# + +---# Upgrading post based group permissions... +---{ +if (!isset($modSettings['permission_enable_postgroups'])) +{ + // Only disable if no post group permissions are used. + $disable_postgroup_permissions = true; + $request = upgrade_query(" + SELECT p.permission + FROM ({$db_prefix}permissions AS p, {$db_prefix}membergroups AS mg) + WHERE mg.ID_GROUP = p.ID_GROUP + AND mg.minPosts != -1 + LIMIT 1"); + $disable_postgroup_permissions &= mysql_num_rows($request) == 0; + mysql_free_result($request); + + // Still wanna disable postgroup permissions? Check board permissions. + if ($disable_postgroup_permissions) + { + $request = upgrade_query(" + SELECT bp.permission + FROM ({$db_prefix}board_permissions AS bp, {$db_prefix}membergroups AS mg) + WHERE mg.ID_GROUP = bp.ID_GROUP + AND mg.minPosts != -1 + LIMIT 1"); + $disable_postgroup_permissions &= mysql_num_rows($request) == 0; + mysql_free_result($request); + } + + $request = upgrade_query(" + INSERT INTO {$db_prefix}settings + (variable, value) + VALUES ('permission_enable_postgroups', '" . ($disable_postgroup_permissions ? '0' : '1') . "')"); +} +---} +---# + +---# Upgrading by-board permissions... +ALTER TABLE {$db_prefix}boards +CHANGE COLUMN use_local_permissions permission_mode tinyint(4) unsigned NOT NULL default '0'; + +---{ +if (!isset($modSettings['permission_enable_by_board'])) +{ + // Enable by-board permissions if there's >= 1 local permission board. + $request = upgrade_query(" + SELECT ID_BOARD + FROM {$db_prefix}boards + WHERE permission_mode = 1 + LIMIT 1"); + $enable_by_board = mysql_num_rows($request) == 1 ? '1' : '0'; + mysql_free_result($request); + + $request = upgrade_query(" + INSERT INTO {$db_prefix}settings + (variable, value) + VALUES ('permission_enable_by_board', '$enable_by_board')"); +} +---} +---# + +---# Removing all guest deny permissions... +DELETE FROM {$db_prefix}permissions +WHERE ID_GROUP = -1 + AND addDeny = 0; + +DELETE FROM {$db_prefix}board_permissions +WHERE ID_GROUP = -1 + AND addDeny = 0; +---# + +---# Removing guest admin permissions (if any)... +DELETE FROM {$db_prefix}permissions +WHERE ID_GROUP = -1 + AND permission IN ('admin_forum', 'manage_boards', 'manage_attachments', 'manage_smileys', 'edit_news', 'moderate_forum', 'manage_membergroups', 'manage_permissions', 'manage_bans', 'send_mail'); + +DELETE FROM {$db_prefix}board_permissions +WHERE ID_GROUP = -1 + AND permission IN ('admin_forum', 'manage_boards', 'manage_attachments', 'manage_smileys', 'edit_news', 'moderate_forum', 'manage_membergroups', 'manage_permissions', 'manage_bans', 'send_mail'); +---# + +/******************************************************************************/ +--- Updating search cache... +/******************************************************************************/ + +---# Creating search cache tables... +DROP TABLE IF EXISTS {$db_prefix}log_search_fulltext; +DROP TABLE IF EXISTS {$db_prefix}log_search_messages; +DROP TABLE IF EXISTS {$db_prefix}log_search_topics; +DROP TABLE IF EXISTS {$db_prefix}log_search; + +CREATE TABLE IF NOT EXISTS {$db_prefix}log_search_messages ( + id_search tinyint(3) unsigned NOT NULL default '0', + ID_MSG int(10) NOT NULL default '0', + PRIMARY KEY (id_search, ID_MSG) +) ENGINE=MyISAM; + +CREATE TABLE IF NOT EXISTS {$db_prefix}log_search_topics ( + id_search tinyint(3) unsigned NOT NULL default '0', + ID_TOPIC mediumint(9) NOT NULL default '0', + PRIMARY KEY (id_search, ID_TOPIC) +) ENGINE=MyISAM; + +CREATE TABLE IF NOT EXISTS {$db_prefix}log_search_results ( + id_search tinyint(3) unsigned NOT NULL default '0', + ID_TOPIC mediumint(8) unsigned NOT NULL default '0', + ID_MSG int(10) unsigned NOT NULL default '0', + relevance smallint(5) unsigned NOT NULL default '0', + num_matches smallint(5) unsigned NOT NULL default '0', + PRIMARY KEY (id_search, ID_TOPIC), + KEY relevance (relevance) +) ENGINE=MyISAM; + +CREATE TABLE IF NOT EXISTS {$db_prefix}log_search_subjects ( + word varchar(20) NOT NULL default '', + ID_TOPIC mediumint(8) unsigned NOT NULL default '0', + PRIMARY KEY (word, ID_TOPIC), + KEY ID_TOPIC (ID_TOPIC) +) ENGINE=MyISAM; +---# + +---# Rebuilding fulltext index... +---{ +$request = upgrade_query(" + SHOW KEYS + FROM {$db_prefix}messages"); +$found = false; +while ($row = mysql_fetch_assoc($request)) + $found |= $row['Key_name'] == 'subject' && $row['Column_name'] == 'subject'; +mysql_free_result($request); +if ($found) +{ + $request = upgrade_query(" + ALTER TABLE {$db_prefix}messages + DROP INDEX subject, + DROP INDEX body, + ADD FULLTEXT body (body)"); +} +---} +---# + +---# Indexing topic subjects... +---{ +$request = upgrade_query(" + SELECT COUNT(*) + FROM {$db_prefix}log_search_subjects"); +list ($numIndexedWords) = mysql_fetch_row($request); +mysql_free_result($request); +if ($numIndexedWords == 0 || isset($_GET['lt'])) +{ + $request = upgrade_query(" + SELECT COUNT(*) + FROM {$db_prefix}topics"); + list ($maxTopics) = mysql_fetch_row($request); + mysql_free_result($request); + + $_GET['lt'] = isset($_GET['lt']) ? (int) $_GET['lt'] : 0; + $step_progress['name'] = 'Indexing Topic Subjects'; + $step_progress['current'] = $_GET['lt']; + $step_progress['total'] = $maxTopics; + + while ($_GET['lt'] <= $maxTopics) + { + $request = upgrade_query(" + SELECT t.ID_TOPIC, m.subject + FROM ({$db_prefix}topics AS t, {$db_prefix}messages AS m) + WHERE m.ID_MSG = t.ID_FIRST_MSG + LIMIT $_GET[lt], 250"); + $inserts = array(); + while ($row = mysql_fetch_assoc($request)) + { + foreach (text2words($row['subject']) as $word) + $inserts[] = "'" . mysql_real_escape_string($word) . "', $row[ID_TOPIC]"; + } + mysql_free_result($request); + + if (!empty($inserts)) + upgrade_query(" + INSERT INTO {$db_prefix}log_search_subjects + (word, ID_TOPIC) + VALUES (" . implode('), + (', array_unique($inserts)) . ")"); + + $_GET['lt'] += 250; + $step_progress['current'] = $_GET['lt']; + nextSubstep($substep); + } + unset($_GET['lt']); +} +---} +---# + +---# Converting settings... +---{ +if (isset($modSettings['search_method'])) +{ + if (!empty($modSettings['search_method'])) + $request = upgrade_query(" + INSERT INTO {$db_prefix}settings + (variable, value) + VALUES + ('search_match_words', '1')"); + + if ($modSettings['search_method'] > 1) + $request = upgrade_query(" + INSERT INTO {$db_prefix}settings + (variable, value) + VALUES + ('search_index', 'fulltext')"); + + if ($modSettings['search_method'] == 3) + $request = upgrade_query(" + INSERT INTO {$db_prefix}settings + (variable, value) + VALUES + ('search_force_index', '1')"); + + $request = upgrade_query(" + DELETE FROM {$db_prefix}settings + WHERE variable = 'search_method'"); +} +---} +---# + +/******************************************************************************/ +--- Upgrading log system... +/******************************************************************************/ + +---# Creating log table indexes (this might take some time!)... +---{ +$request = upgrade_query(" + SHOW COLUMNS + FROM {$db_prefix}log_topics"); +$upgradeLogTable = false; +while ($request && $row = mysql_fetch_row($request)) + $upgradeLogTable |= $row[0] == 'logTime'; +if ($request !== false) + mysql_free_result($request); + +if ($upgradeLogTable) +{ + $_GET['preprep_lt'] = isset($_GET['preprep_lt']) ? (int) $_GET['preprep_lt'] : 0; + $step_progress['name'] = 'Creating index\'s for log table'; + $step_progress['current'] = $_GET['preprep_lt']; + $custom_warning = 'On a very large board these index\'s may take a few minutes to create.'; + + $log_additions = array( + array( + 'table' => 'log_boards', + 'type' => 'index', + 'method' => 'add', + 'name' => 'logTime', + 'target_columns' => array('logTime'), + 'text' => 'ADD INDEX logTime (logTime)', + ), + array( + 'table' => 'log_mark_read', + 'type' => 'index', + 'method' => 'add', + 'name' => 'logTime', + 'target_columns' => array('logTime'), + 'text' => 'ADD INDEX logTime (logTime)', + ), + array( + 'table' => 'messages', + 'type' => 'index', + 'method' => 'add', + 'name' => 'modifiedTime', + 'target_columns' => array('modifiedTime'), + 'text' => 'ADD INDEX modifiedTime (modifiedTime)', + ), + ); + + $step_progress['total'] = count($log_additions); + + // Now we loop through the changes and work out where the hell we are. + foreach ($log_additions as $ind => $change) + { + // Already done it? + if ($_GET['preprep_lt'] > $ind) + continue; + + // Make the index, with all the protection and all. + protected_alter($change, $substep); + + // Store this for the next table. + $_GET['preprep_lt']++; + $step_progress['current'] = $_GET['preprep_lt']; + } + + // Clean up. + unset($_GET['preprep_lt']); +} +---} +---# + +---# Preparing log table upgrade... +---{ +$request = upgrade_query(" + SHOW COLUMNS + FROM {$db_prefix}log_topics"); +$upgradeLogTable = false; +while ($request && $row = mysql_fetch_row($request)) + $upgradeLogTable |= $row[0] == 'logTime'; +if ($request !== false) + mysql_free_result($request); + +if ($upgradeLogTable) +{ + $_GET['prep_lt'] = isset($_GET['prep_lt']) ? (int) $_GET['prep_lt'] : 0; + $step_progress['name'] = 'Preparing log table update'; + $step_progress['current'] = $_GET['prep_lt']; + $custom_warning = 'This step may take quite some time. During this time it may appear that nothing is happening while + the databases MySQL tables are expanded. Please be patient.'; + + // All these changes need to be made, they may take a while, so let's timeout neatly. + $log_additions = array( + array( + 'table' => 'log_topics', + 'type' => 'index', + 'method' => 'remove', + 'name' => 'ID_MEMBER', + 'target_columns' => array('ID_MEMBER'), + 'text' => 'DROP INDEX ID_MEMBER', + ), + array( + 'table' => 'log_topics', + 'type' => 'index', + 'method' => 'change', + 'name' => 'PRIMARY', + 'target_columns' => array('ID_MEMBER', 'ID_TOPIC'), + 'text' => ' + DROP PRIMARY KEY, + ADD PRIMARY KEY (ID_MEMBER, ID_TOPIC)', + ), + array( + 'table' => 'log_topics', + 'type' => 'index', + 'method' => 'add', + 'name' => 'logTime', + 'target_columns' => array('logTime'), + 'text' => 'ADD INDEX logTime (logTime)', + ), + array( + 'table' => 'log_boards', + 'type' => 'column', + 'method' => 'add', + 'name' => 'ID_MSG', + 'text' => 'ADD COLUMN ID_MSG mediumint(8) unsigned NOT NULL default \'0\'', + ), + array( + 'table' => 'log_mark_read', + 'type' => 'column', + 'method' => 'add', + 'name' => 'ID_MSG', + 'text' => 'ADD COLUMN ID_MSG mediumint(8) unsigned NOT NULL default \'0\'', + ), + array( + 'table' => 'log_topics', + 'type' => 'column', + 'method' => 'add', + 'name' => 'ID_MSG', + 'text' => 'ADD COLUMN ID_MSG mediumint(8) unsigned NOT NULL default \'0\'', + ), + array( + 'table' => 'messages', + 'type' => 'column', + 'method' => 'add', + 'name' => 'ID_MSG_MODIFIED', + 'text' => 'ADD COLUMN ID_MSG_MODIFIED mediumint(8) unsigned NOT NULL default \'0\' AFTER ID_MEMBER', + ), + array( + 'table' => 'boards', + 'type' => 'column', + 'method' => 'add', + 'name' => 'ID_MSG_UPDATED', + 'text' => 'ADD COLUMN ID_MSG_UPDATED mediumint(8) unsigned NOT NULL default \'0\' AFTER ID_LAST_MSG', + ), + array( + 'table' => 'boards', + 'type' => 'index', + 'method' => 'add', + 'name' => 'ID_MSG_UPDATED', + 'target_columns' => array('ID_MSG_UPDATED'), + 'text' => 'ADD INDEX ID_MSG_UPDATED (ID_MSG_UPDATED)', + ), + ); + $step_progress['total'] = count($log_additions); + + // Now we loop through the changes and work out where the hell we are. + foreach ($log_additions as $ind => $change) + { + // Already done it? + if ($_GET['prep_lt'] > $ind) + continue; + + // Make the index, with all the protection and all. + protected_alter($change, $substep); + + // Store this for the next table. + $_GET['prep_lt']++; + $step_progress['current'] = $_GET['prep_lt']; + } + + // Clean up. + unset($_GET['prep_lt']); +} +---} +---# + +---# Converting log tables (this might take some time!)... +---{ +$request = upgrade_query(" + SHOW COLUMNS + FROM {$db_prefix}log_topics"); +$upgradeLogTable = false; +while ($request && $row = mysql_fetch_row($request)) + $upgradeLogTable |= $row[0] == 'logTime'; +if ($request !== false) + mysql_free_result($request); + +if ($upgradeLogTable) +{ + $request = upgrade_query(" + SELECT MAX(ID_MSG) + FROM {$db_prefix}messages"); + list($maxMsg) = mysql_fetch_row($request); + mysql_free_result($request); + + if (empty($maxMsg)) + $maxMsg = 0; + + $_GET['m'] = isset($_GET['m']) ? (int) $_GET['m'] : 0; + $step_progress['name'] = 'Converting Log Tables'; + $step_progress['current'] = $_GET['m']; + $step_progress['total'] = $maxMsg; + $custom_warning = 'This step is converting all your log tables and may take quite some time on a large forum (Several hours for a forum with ~500,000 messages).'; + + // Only adjust the structure if this is the first message. + if ($_GET['m'] === 0) + { + // By default a message is modified when it was written. + upgrade_query(" + UPDATE {$db_prefix}messages + SET ID_MSG_MODIFIED = ID_MSG"); + + $request = upgrade_query(" + SELECT posterTime + FROM {$db_prefix}messages + WHERE ID_MSG = $maxMsg"); + list($maxPosterTime) = mysql_fetch_row($request); + mysql_free_result($request); + + if (empty($maxPosterTime)) + $maxPosterTime = 0; + + upgrade_query(" + UPDATE {$db_prefix}log_boards + SET ID_MSG = $maxMsg + WHERE logTime >= $maxPosterTime"); + upgrade_query(" + UPDATE {$db_prefix}log_mark_read + SET ID_MSG = $maxMsg + WHERE logTime >= $maxPosterTime"); + upgrade_query(" + UPDATE {$db_prefix}log_topics + SET ID_MSG = $maxMsg + WHERE logTime >= $maxPosterTime"); + upgrade_query(" + UPDATE {$db_prefix}messages + SET ID_MSG_MODIFIED = $maxMsg + WHERE modifiedTime >= $maxPosterTime"); + + // Timestamp 1 is where it all starts. + $lower_limit = 1; + } + else + { + // Determine the lower limit. + $request = upgrade_query(" + SELECT MAX(posterTime) + 1 + FROM {$db_prefix}messages + WHERE ID_MSG < $_GET[m]"); + list($lower_limit) = mysql_fetch_row($request); + mysql_free_result($request); + + if (empty($lower_limit)) + $lower_limit = 1; + + if (empty($maxPosterTime)) + $maxPosterTime = 1; + } + + while ($_GET['m'] <= $maxMsg) + { + $condition = ''; + $lowest_limit = $lower_limit; + $request = upgrade_query(" + SELECT MAX(ID_MSG) AS ID_MSG, posterTime + FROM {$db_prefix}messages + WHERE ID_MSG BETWEEN $_GET[m] AND " . ($_GET['m'] + 300) . " + GROUP BY posterTime + ORDER BY posterTime + LIMIT 300"); + while ($row = mysql_fetch_assoc($request)) + { + if ($condition === '') + $condition = "IF(logTime BETWEEN $lower_limit AND $row[posterTime], $row[ID_MSG], %else%)"; + else + $condition = strtr($condition, array('%else%' => "IF(logTime <= $row[posterTime], $row[ID_MSG], %else%)")); + + $lower_limit = $row['posterTime'] + 1; + } + mysql_free_result($request); + + if ($condition !== '') + { + $condition = strtr($condition, array('%else%' => '0')); + $highest_limit = $lower_limit; + + upgrade_query(" + UPDATE {$db_prefix}log_boards + SET ID_MSG = $condition + WHERE logTime BETWEEN $lowest_limit AND $highest_limit + AND ID_MSG = 0"); + upgrade_query(" + UPDATE {$db_prefix}log_mark_read + SET ID_MSG = $condition + WHERE logTime BETWEEN $lowest_limit AND $highest_limit + AND ID_MSG = 0"); + upgrade_query(" + UPDATE {$db_prefix}log_topics + SET ID_MSG = $condition + WHERE logTime BETWEEN $lowest_limit AND $highest_limit + AND ID_MSG = 0"); + upgrade_query(" + UPDATE {$db_prefix}messages + SET ID_MSG_MODIFIED = " . strtr($condition, array('logTime' => 'modifiedTime')) . " + WHERE modifiedTime BETWEEN $lowest_limit AND $highest_limit + AND modifiedTime > 0"); + } + + $_GET['m'] += 300; + nextSubstep($substep); + } + unset($_GET['m']); +} +---} +---# + +---# Updating last message IDs for boards. +---{ + +$request = upgrade_query(" + SHOW COLUMNS + FROM {$db_prefix}boards"); +$upgradeBoardsTable = false; +while ($request && $row = mysql_fetch_row($request)) + $upgradeBoardsTable |= $row[0] == 'lastUpdated'; +if ($request !== false) + mysql_free_result($request); + +if ($upgradeBoardsTable) +{ + $request = upgrade_query(" + SELECT MAX(ID_BOARD) + FROM {$db_prefix}boards"); + list ($maxBoard) = mysql_fetch_row($request); + mysql_free_result($request); + + $_GET['bdi'] = isset($_GET['bdi']) ? (int) $_GET['bdi'] : 0; + $step_progress['name'] = 'Updating Last Board ID'; + $step_progress['current'] = $_GET['bdi']; + $step_progress['total'] = $maxBoard; + + // OK, we need to get the last updated message. + $request = upgrade_query(" + SELECT ID_BOARD, lastUpdated + FROM {$db_prefix}boards"); + while ($row = mysql_fetch_assoc($request)) + { + // Done this? + if ($row['ID_BOARD'] < $_GET['bdi']) + continue; + + // Maybe we don't have any? + if ($row['lastUpdated'] == 0) + $ID_MSG = 0; + // Otherwise need to query it? + else + { + $request2 = upgrade_query(" + SELECT MIN(ID_MSG) + FROM {$db_prefix}messages + WHERE posterTime >= $row[lastUpdated]"); + list ($ID_MSG) = mysql_fetch_row($request2); + + if (empty($ID_MSG)) + $ID_MSG = 0; + } + + upgrade_query(" + UPDATE {$db_prefix}boards + SET ID_MSG_UPDATED = $ID_MSG + WHERE ID_BOARD = $row[ID_BOARD]"); + + $_GET['bdi']++; + $step_progress['current'] = $_GET['bdi']; + nextSubstep($substep); + } + unset($_GET['bdi']); +} +---} +---# + +---# Cleaning up old log indexes... +---{ +$request = upgrade_query(" + SHOW COLUMNS + FROM {$db_prefix}log_topics"); +$upgradeLogTable = false; +while ($request && $row = mysql_fetch_row($request)) + $upgradeLogTable |= $row[0] == 'logTime'; +if ($request !== false) + mysql_free_result($request); + +if ($upgradeLogTable) +{ + $_GET['prep_lt'] = isset($_GET['prep_lt']) ? (int) $_GET['prep_lt'] : 0; + $step_progress['name'] = 'Cleaning up old log table index\'s'; + $step_progress['current'] = $_GET['prep_lt']; + $custom_warning = 'This step may take quite some time. During this time it may appear that nothing is happening while + the databases MySQL tables are cleaned. Please be patient.'; + + // Here we remove all the unused indexes + $log_deletions = array( + array( + 'table' => 'boards', + 'type' => 'index', + 'method' => 'remove', + 'name' => 'lastUpdated', + 'target_columns' => array('lastUpdated'), + 'text' => 'DROP INDEX lastUpdated', + ), + array( + 'table' => 'messages', + 'type' => 'index', + 'method' => 'remove', + 'name' => 'posterTime', + 'target_columns' => array('posterTime'), + 'text' => 'DROP INDEX posterTime', + ), + array( + 'table' => 'messages', + 'type' => 'index', + 'method' => 'remove', + 'name' => 'modifiedTime', + 'target_columns' => array('modifiedTime'), + 'text' => 'DROP INDEX modifiedTime', + ), + array( + 'table' => 'log_topics', + 'type' => 'column', + 'method' => 'remove', + 'name' => 'logTime', + 'text' => 'DROP COLUMN logTime', + ), + array( + 'table' => 'log_boards', + 'type' => 'column', + 'method' => 'remove', + 'name' => 'logTime', + 'text' => 'DROP COLUMN logTime', + ), + array( + 'table' => 'log_mark_read', + 'type' => 'column', + 'method' => 'remove', + 'name' => 'logTime', + 'text' => 'DROP COLUMN logTime', + ), + array( + 'table' => 'boards', + 'type' => 'column', + 'method' => 'remove', + 'name' => 'lastUpdated', + 'text' => 'DROP COLUMN lastUpdated', + ), + ); + $step_progress['total'] = count($log_deletions); + + // Now we loop through the changes and work out where the hell we are. + foreach ($log_deletions as $ind => $change) + { + // Already done it? + if ($_GET['prep_lt'] > $ind) + continue; + + // Make the index, with all the protection and all. + protected_alter($change, $substep); + + // Store this for the next table. + $_GET['prep_lt']++; + $step_progress['current'] = $_GET['prep_lt']; + } + + // Clean up. + unset($_GET['prep_lt']); + $step_progress = array(); +} +---} +---# + +/******************************************************************************/ +--- Making SMF MySQL strict compatible... +/******************************************************************************/ + +---# Preparing messages table for strict upgrade +ALTER IGNORE TABLE {$db_prefix}messages +DROP INDEX ipIndex; +---# + +---# Adjusting text fields +---# +---{ +// Note we move on by one as there is no point ALTER'ing the same thing twice. +$_GET['strict_step'] = isset($_GET['strict_step']) ? (int) $_GET['strict_step'] + 1 : 0; +$step_progress['name'] = 'Adding MySQL strict compatibility'; +$step_progress['current'] = $_GET['strict_step']; + +// Take care with the body column from messages, just in case it's been enlarged by others. +$request = upgrade_query(" + SHOW COLUMNS + FROM {$db_prefix}messages + LIKE 'body'"); +$body_row = mysql_fetch_assoc($request); +mysql_free_result($request); + +$body_type = $body_row['Type']; + +$textfield_updates = array( + array( + 'table' => 'attachments', + 'column' => 'filename', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'ban_groups', + 'column' => 'reason', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'ban_items', + 'column' => 'hostname', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'ban_items', + 'column' => 'email_address', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'boards', + 'column' => 'name', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'boards', + 'column' => 'description', + 'type' => 'text', + 'null_allowed' => false, + ), + array( + 'table' => 'categories', + 'column' => 'name', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'log_actions', + 'column' => 'extra', + 'type' => 'text', + 'null_allowed' => false, + ), + array( + 'table' => 'log_banned', + 'column' => 'email', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'log_banned', + 'column' => 'email', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'log_errors', + 'column' => 'url', + 'type' => 'text', + 'null_allowed' => false, + ), + array( + 'table' => 'log_errors', + 'column' => 'message', + 'type' => 'text', + 'null_allowed' => false, + ), + array( + 'table' => 'log_online', + 'column' => 'url', + 'type' => 'text', + 'null_allowed' => false, + ), + array( + 'table' => 'membergroups', + 'column' => 'stars', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'lngfile', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'realName', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'buddy_list', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'pm_ignore_list', + 'type' => 'text', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'messageLabels', + 'type' => 'text', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'emailAddress', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'personalText', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'websiteTitle', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'websiteUrl', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'location', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'ICQ', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'MSN', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'signature', + 'type' => 'text', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'avatar', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'usertitle', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'memberIP', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'secretQuestion', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'members', + 'column' => 'additionalGroups', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'messages', + 'column' => 'subject', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'messages', + 'column' => 'posterName', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'messages', + 'column' => 'posterEmail', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'messages', + 'column' => 'posterIP', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'messages', + 'column' => 'modifiedName', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'messages', + 'column' => 'body', + 'type' => $body_type, + 'null_allowed' => false, + ), + array( + 'table' => 'personal_messages', + 'column' => 'body', + 'type' => 'text', + 'null_allowed' => false, + ), + array( + 'table' => 'package_servers', + 'column' => 'name', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'personal_messages', + 'column' => 'fromName', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'personal_messages', + 'column' => 'subject', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'personal_messages', + 'column' => 'body', + 'type' => 'text', + 'null_allowed' => false, + ), + array( + 'table' => 'polls', + 'column' => 'question', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'polls', + 'column' => 'posterName', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'poll_choices', + 'column' => 'label', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'settings', + 'column' => 'variable', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'settings', + 'column' => 'value', + 'type' => 'text', + 'null_allowed' => false, + ), + array( + 'table' => 'sessions', + 'column' => 'data', + 'type' => 'text', + 'null_allowed' => false, + ), + array( + 'table' => 'themes', + 'column' => 'variable', + 'type' => 'tinytext', + 'null_allowed' => false, + ), + array( + 'table' => 'themes', + 'column' => 'value', + 'type' => 'text', + 'null_allowed' => false, + ), +); +$step_progress['total'] = count($textfield_updates); + +foreach ($textfield_updates as $ind => $change) +{ + // Already done it? + if ($_GET['strict_step'] > $ind) + continue; + + // Make the index, with all the protection and all. + textfield_alter($change, $substep); + + // Store this for the next table. + $_GET['strict_step']++; + $step_progress['current'] = $_GET['strict_step']; +} + +$step_progress = array(); +---} +---# + +---# Replacing messages index. +ALTER TABLE {$db_prefix}messages +ADD INDEX ipIndex (posterIP(15), ID_TOPIC); +---# + +---# Adding log_topics index. +---{ +upgrade_query(" + ALTER TABLE {$db_prefix}log_topics + ADD INDEX ID_TOPIC (ID_TOPIC)", true); +---} +---# + +/******************************************************************************/ +--- Adding more room for the buddy list +/******************************************************************************/ + +---# Updating the members table ... +ALTER TABLE {$db_prefix}members +CHANGE COLUMN buddy_list buddy_list text NOT NULL; +---# + +/******************************************************************************/ +--- Change some column types to accomodate more messages. +/******************************************************************************/ + +---# Expanding message column size. +---{ +$_GET['msg_change'] = isset($_GET['msg_change']) ? (int) $_GET['msg_change'] : 0; +$step_progress['name'] = 'Expanding Message Capacity'; +$step_progress['current'] = $_GET['msg_change']; + +// The array holding all the changes. +$columnChanges = array( + array( + 'table' => 'boards', + 'type' => 'column', + 'method' => 'change', + 'name' => 'ID_LAST_MSG', + 'text' => 'CHANGE ID_LAST_MSG ID_LAST_MSG int(10) unsigned NOT NULL default \'0\'', + ), + array( + 'table' => 'boards', + 'type' => 'column', + 'method' => 'change', + 'name' => 'ID_MSG_UPDATED', + 'text' => 'CHANGE ID_MSG_UPDATED ID_MSG_UPDATED int(10) unsigned NOT NULL default \'0\'', + ), + array( + 'table' => 'log_boards', + 'type' => 'column', + 'method' => 'change', + 'name' => 'ID_MSG', + 'text' => 'CHANGE ID_MSG ID_MSG int(10) unsigned NOT NULL default \'0\'', + ), + array( + 'table' => 'log_mark_read', + 'type' => 'column', + 'method' => 'change', + 'name' => 'ID_MSG', + 'text' => 'CHANGE ID_MSG ID_MSG int(10) unsigned NOT NULL default \'0\'', + ), + array( + 'table' => 'log_topics', + 'type' => 'column', + 'method' => 'change', + 'name' => 'ID_MSG', + 'text' => 'CHANGE ID_MSG ID_MSG int(10) unsigned NOT NULL default \'0\'', + ), + array( + 'table' => 'messages', + 'type' => 'column', + 'method' => 'change', + 'name' => 'ID_MSG_MODIFIED', + 'text' => 'CHANGE ID_MSG_MODIFIED ID_MSG_MODIFIED int(10) unsigned NOT NULL default \'0\'', + ), +); + +if (!empty($modSettings['search_custom_index_config'])) + $columnChanges[] = array( + 'table' => 'log_search_words', + 'type' => 'column', + 'method' => 'change', + 'name' => 'ID_MSG', + 'text' => 'CHANGE ID_MSG ID_MSG int(10) unsigned NOT NULL default \'0\'', + ); + +$step_progress['total'] = count($columnChanges); + +// Now we do all the changes... +foreach ($columnChanges as $index => $change) +{ + // Already done it? + if ($_GET['msg_change'] > $ind) + continue; + + // Now change the column at last. + protected_alter($change, $substep); + + // Update where we are... + $_GET['msg_change']++; + $step_progress['current'] = $_GET['msg_change']; +} + +// Clean up. +unset($_GET['msg_change']); +---} +---# + +/******************************************************************************/ +--- Final clean up... +/******************************************************************************/ + +---# Sorting the boards... +ALTER TABLE {$db_prefix}categories +ORDER BY catOrder; + +ALTER TABLE {$db_prefix}boards +ORDER BY boardOrder; +---# + +---# Removing upgrade loop protection... +DELETE FROM {$db_prefix}settings +WHERE variable IN ('dont_repeat_smtp', 'dont_repeat_theme'); +---# \ No newline at end of file diff --git a/upgrade_2-0_mysql.sql b/upgrade_2-0_mysql.sql new file mode 100644 index 0000000..de2ac4d --- /dev/null +++ b/upgrade_2-0_mysql.sql @@ -0,0 +1,3166 @@ +/* ATTENTION: You don't need to run or use this file! The upgrade.php script does everything for you! */ + +/******************************************************************************/ +--- Changing column names. +/******************************************************************************/ + +---# Renaming table columns. +---{ +// The array holding all the changes. +$nameChanges = array( + 'admin_info_files' => array( + 'ID_FILE' => 'ID_FILE id_file tinyint(4) unsigned NOT NULL auto_increment', + ), + 'approval_queue' => array( + 'ID_MSG' => 'ID_MSG id_msg int(10) unsigned NOT NULL default \'0\'', + 'ID_ATTACH' => 'ID_ATTACH id_attach int(10) unsigned NOT NULL default \'0\'', + 'ID_EVENT' => 'ID_EVENT id_event smallint(5) unsigned NOT NULL default \'0\'', + 'attachmentType' => 'attachmentType attachment_type tinyint(3) unsigned NOT NULL default \'0\'', + ), + 'attachments' => array( + 'ID_ATTACH' => 'ID_ATTACH id_attach int(10) unsigned NOT NULL auto_increment', + 'ID_THUMB' => 'ID_THUMB id_thumb int(10) unsigned NOT NULL default \'0\'', + 'ID_MSG' => 'ID_MSG id_msg int(10) unsigned NOT NULL default \'0\'', + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'attachmentType' => 'attachmentType attachment_type tinyint(3) unsigned NOT NULL default \'0\'', + ), + 'ban_groups' => array( + 'ID_BAN_GROUP' => 'ID_BAN_GROUP id_ban_group mediumint(8) unsigned NOT NULL auto_increment', + ), + 'ban_items' => array( + 'ID_BAN' => 'ID_BAN id_ban mediumint(8) unsigned NOT NULL auto_increment', + 'ID_BAN_GROUP' => 'ID_BAN_GROUP id_ban_group smallint(5) unsigned NOT NULL default \'0\'', + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + ), + 'board_permissions' => array( + 'ID_GROUP' => 'ID_GROUP id_group smallint(5) NOT NULL default \'0\'', + 'ID_PROFILE' => 'ID_PROFILE id_profile smallint(5) NOT NULL default \'0\'', + 'addDeny' => 'addDeny add_deny tinyint(4) NOT NULL default \'1\'', + ), + 'boards' => array( + 'ID_BOARD' => 'ID_BOARD id_board smallint(5) unsigned NOT NULL auto_increment', + 'ID_CAT' => 'ID_CAT id_cat tinyint(4) unsigned NOT NULL default \'0\'', + 'childLevel' => 'childLevel child_level tinyint(4) unsigned NOT NULL default \'0\'', + 'ID_PARENT' => 'ID_PARENT id_parent smallint(5) unsigned NOT NULL default \'0\'', + 'boardOrder' => 'boardOrder board_order smallint(5) NOT NULL default \'0\'', + 'ID_LAST_MSG' => 'ID_LAST_MSG id_last_msg int(10) unsigned NOT NULL default \'0\'', + 'ID_MSG_UPDATED' => 'ID_MSG_UPDATED id_msg_updated int(10) unsigned NOT NULL default \'0\'', + 'memberGroups' => 'memberGroups member_groups varchar(255) NOT NULL default \'-1,0\'', + 'ID_PROFILE' => 'ID_PROFILE id_profile smallint(5) unsigned NOT NULL default \'1\'', + 'numTopics' => 'numTopics num_topics mediumint(8) unsigned NOT NULL default \'0\'', + 'numPosts' => 'numPosts num_posts mediumint(8) unsigned NOT NULL default \'0\'', + 'countPosts' => 'countPosts count_posts tinyint(4) NOT NULL default \'0\'', + 'ID_THEME' => 'ID_THEME id_theme tinyint(4) unsigned NOT NULL default \'0\'', + 'unapprovedPosts' => 'unapprovedPosts unapproved_posts smallint(5) NOT NULL default \'0\'', + 'unapprovedTopics' => 'unapprovedTopics unapproved_topics smallint(5) NOT NULL default \'0\'', + ), + 'calendar' => array( + 'ID_EVENT' => 'ID_EVENT id_event smallint(5) unsigned NOT NULL auto_increment', + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_BOARD' => 'ID_BOARD id_board smallint(5) unsigned NOT NULL default \'0\'', + 'ID_TOPIC' => 'ID_TOPIC id_topic mediumint(8) unsigned NOT NULL default \'0\'', + 'startDate' => 'startDate start_date date NOT NULL default \'0001-01-01\'', + 'endDate' => 'endDate end_date date NOT NULL default \'0001-01-01\'', + ), + 'calendar_holidays' => array( + 'ID_HOLIDAY' => 'ID_HOLIDAY id_holiday smallint(5) unsigned NOT NULL auto_increment', + 'eventDate' => 'eventDate event_date date NOT NULL default \'0001-01-01\'', + ), + 'categories' => array( + 'ID_CAT' => 'ID_CAT id_cat tinyint(4) unsigned NOT NULL auto_increment', + 'catOrder' => 'catOrder cat_order tinyint(4) NOT NULL default \'0\'', + 'canCollapse' => 'canCollapse can_collapse tinyint(1) NOT NULL default \'1\'', + ), + 'collapsed_categories' => array( + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_CAT' => 'ID_CAT id_cat tinyint(4) unsigned NOT NULL', + ), + 'custom_fields' => array( + 'ID_FIELD' => 'ID_FIELD id_field smallint(5) NOT NULL auto_increment', + 'colName' => 'colName col_name varchar(12) NOT NULL default \'\'', + 'fieldName' => 'fieldName field_name varchar(40) NOT NULL default \'\'', + 'fieldDesc' => 'fieldDesc field_desc varchar(255) NOT NULL default \'\'', + 'fieldType' => 'fieldType field_type varchar(8) NOT NULL default \'text\'', + 'fieldLength' => 'fieldLength field_length smallint(5) NOT NULL default \'255\'', + 'fieldOptions' => 'fieldOptions field_options text NOT NULL', + 'showReg' => 'showReg show_reg tinyint(3) NOT NULL default \'0\'', + 'showDisplay' => 'showDisplay show_display tinyint(3) NOT NULL default \'0\'', + 'showProfile' => 'showProfile show_profile varchar(20) NOT NULL default \'forumprofile\'', + 'defaultValue' => 'defaultValue default_value varchar(8) NOT NULL default \'0\'', + ), + 'group_moderators' => array( + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_GROUP' => 'ID_GROUP id_group smallint(5) unsigned NOT NULL default \'0\'', + ), + 'log_actions' => array( + 'ID_ACTION' => 'ID_ACTION id_action int(10) unsigned NOT NULL auto_increment', + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'logTime' => 'logTime log_time int(10) unsigned NOT NULL default \'0\'', + 'ID_MSG' => 'ID_MSG id_msg int(10) unsigned NOT NULL default \'0\'', + 'ID_TOPIC' => 'ID_TOPIC id_topic mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_BOARD' => 'ID_BOARD id_board smallint(5) unsigned NOT NULL default \'0\'', + ), + 'log_activity' => array( + 'mostOn' => 'mostOn most_on smallint(5) unsigned NOT NULL default \'0\'', + ), + 'log_banned' => array( + 'ID_BAN_LOG' => 'ID_BAN_LOG id_ban_log mediumint(8) unsigned NOT NULL auto_increment', + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'logTime' => 'logTime log_time int(10) unsigned NOT NULL default \'0\'', + ), + 'log_boards' => array( + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_MSG' => 'ID_MSG id_msg int(10) unsigned NOT NULL default \'0\'', + 'ID_BOARD' => 'ID_BOARD id_board smallint(5) unsigned NOT NULL default \'0\'', + ), + 'log_digest' => array( + 'ID_TOPIC' => 'ID_TOPIC id_topic mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_MSG' => 'ID_MSG id_msg int(10) unsigned NOT NULL default \'0\'', + ), + 'log_errors' => array( + 'ID_ERROR' => 'ID_ERROR id_error mediumint(8) unsigned NOT NULL auto_increment', + 'logTime' => 'logTime log_time int(10) unsigned NOT NULL default \'0\'', + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'errorType' => 'errorType error_type char(15) NOT NULL default \'general\'', + ), + 'log_floodcontrol' => array( + 'logTime' => 'logTime log_time int(10) unsigned NOT NULL default \'0\'', + ), + 'log_group_requests' => array( + 'ID_REQUEST' => 'ID_REQUEST id_request mediumint(8) unsigned NOT NULL auto_increment', + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_GROUP' => 'ID_GROUP id_group smallint(5) unsigned NOT NULL default \'0\'', + ), + 'log_karma' => array( + 'ID_TARGET' => 'ID_TARGET id_target mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_EXECUTOR' => 'ID_EXECUTOR id_executor mediumint(8) unsigned NOT NULL default \'0\'', + 'logTime' => 'logTime log_time int(10) unsigned NOT NULL default \'0\'', + ), + 'log_mark_read' => array( + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_MSG' => 'ID_MSG id_msg int(10) unsigned NOT NULL default \'0\'', + 'ID_BOARD' => 'ID_BOARD id_board smallint(5) unsigned NOT NULL default \'0\'', + ), + 'log_notify' => array( + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_TOPIC' => 'ID_TOPIC id_topic mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_BOARD' => 'ID_BOARD id_board smallint(5) unsigned NOT NULL default \'0\'', + ), + 'log_packages' => array( + 'ID_INSTALL' => 'ID_INSTALL id_install int(10) NOT NULL auto_increment', + 'ID_MEMBER_INSTALLED' => 'ID_MEMBER_INSTALLED id_member_installed mediumint(8) NOT NULL default \'0\'', + 'ID_MEMBER_REMOVED' => 'ID_MEMBER_REMOVED id_member_removed mediumint(8) NOT NULL default \'0\'', + ), + 'log_polls' => array( + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_CHOICE' => 'ID_CHOICE id_choice tinyint(3) unsigned NOT NULL default \'0\'', + 'ID_POLL' => 'ID_POLL id_poll mediumint(8) unsigned NOT NULL default \'0\'', + ), + 'log_reported' => array( + 'ID_REPORT' => 'ID_REPORT id_report mediumint(8) unsigned NOT NULL auto_increment', + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_MSG' => 'ID_MSG id_msg int(10) unsigned NOT NULL default \'0\'', + 'ID_TOPIC' => 'ID_TOPIC id_topic mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_BOARD' => 'ID_BOARD id_board smallint(5) unsigned NOT NULL default \'0\'', + ), + 'log_reported_comments' => array( + 'ID_COMMENT' => 'ID_COMMENT id_comment mediumint(8) unsigned NOT NULL auto_increment', + 'ID_REPORT' => 'ID_REPORT id_report mediumint(8) NOT NULL default \'0\'', + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + ), + 'log_scheduled_tasks' => array( + 'ID_LOG' => 'ID_LOG id_log mediumint(8) NOT NULL auto_increment', + 'ID_TASK' => 'ID_TASK id_task smallint(5) NOT NULL default \'0\'', + 'timeRun' => 'timeRun time_run int(10) NOT NULL default \'0\'', + 'timeTaken' => 'timeTaken time_taken float NOT NULL default \'0\'', + ), + 'log_search_messages' => array( + 'ID_SEARCH' => 'ID_SEARCH id_search tinyint(3) unsigned NOT NULL default \'0\'', + 'ID_MSG' => 'ID_MSG id_msg int(10) unsigned NOT NULL default \'0\'', + ), + 'log_search_results' => array( + 'ID_TOPIC' => 'ID_TOPIC id_topic mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_MSG' => 'ID_MSG id_msg int(10) unsigned NOT NULL default \'0\'', + 'ID_SEARCH' => 'ID_SEARCH id_search tinyint(3) unsigned NOT NULL default \'0\'', + ), + 'log_search_subjects' => array( + 'ID_TOPIC' => 'ID_TOPIC id_topic mediumint(8) unsigned NOT NULL default \'0\'', + ), + 'log_search_topics' => array( + 'ID_SEARCH' => 'ID_SEARCH id_search tinyint(3) unsigned NOT NULL default \'0\'', + 'ID_TOPIC' => 'ID_TOPIC id_topic mediumint(8) unsigned NOT NULL default \'0\'', + ), + 'log_subscribed' => array( + 'ID_SUBLOG' => 'ID_SUBLOG id_sublog int(10) unsigned NOT NULL auto_increment', + 'ID_SUBSCRIBE' => 'ID_SUBSCRIBE id_subscribe mediumint(8) unsigned NOT NULL default \'0\'', + 'OLD_ID_GROUP' => 'OLD_ID_GROUP old_id_group smallint(5) NOT NULL default \'0\'', + 'startTime' => 'startTime start_time int(10) NOT NULL default \'0\'', + 'endTime' => 'endTime end_time int(10) NOT NULL default \'0\'', + ), + 'log_topics' => array( + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_MSG' => 'ID_MSG id_msg int(10) unsigned NOT NULL default \'0\'', + 'ID_TOPIC' => 'ID_TOPIC id_topic mediumint(8) unsigned NOT NULL default \'0\'', + ), + 'mail_queue' => array( + 'ID_MAIL' => 'ID_MAIL id_mail int(10) unsigned NOT NULL auto_increment', + ), + 'members' => array( + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL auto_increment', + 'memberName' => 'memberName member_name varchar(80) NOT NULL default \'\'', + 'dateRegistered' => 'dateRegistered date_registered int(10) unsigned NOT NULL default \'0\'', + 'ID_GROUP' => 'ID_GROUP id_group smallint(5) unsigned NOT NULL default \'0\'', + 'lastLogin' => 'lastLogin last_login int(10) unsigned NOT NULL default \'0\'', + 'realName' => 'realName real_name varchar(255) NOT NULL default \'\'', + 'instantMessages' => 'instantMessages instant_messages smallint(5) NOT NULL default \'0\'', + 'unreadMessages' => 'unreadMessages unread_messages smallint(5) NOT NULL default \'0\'', + 'messageLabels' => 'messageLabels message_labels text NOT NULL', + 'emailAddress' => 'emailAddress email_address varchar(255) NOT NULL default \'\'', + 'personalText' => 'personalText personal_text varchar(255) NOT NULL default \'\'', + 'websiteTitle' => 'websiteTitle website_title varchar(255) NOT NULL default \'\'', + 'websiteUrl' => 'websiteUrl website_url varchar(255) NOT NULL default \'\'', + 'ICQ' => 'ICQ icq varchar(255) NOT NULL default \'\'', + 'AIM' => 'AIM aim varchar(255) NOT NULL default \'\'', + 'YIM' => 'YIM yim varchar(32) NOT NULL default \'\'', + 'MSN' => 'MSN msn varchar(255) NOT NULL default \'\'', + 'hideEmail' => 'hideEmail hide_email tinyint(4) NOT NULL default \'0\'', + 'showOnline' => 'showOnline show_online tinyint(4) NOT NULL default \'1\'', + 'timeFormat' => 'timeFormat time_format varchar(80) NOT NULL default \'\'', + 'timeOffset' => 'timeOffset time_offset float NOT NULL default \'0\'', + 'karmaBad' => 'karmaBad karma_bad smallint(5) unsigned NOT NULL default \'0\'', + 'karmaGood' => 'karmaGood karma_good smallint(5) unsigned NOT NULL default \'0\'', + 'notifyAnnouncements' => 'notifyAnnouncements notify_announcements tinyint(4) NOT NULL default \'1\'', + 'notifyRegularity' => 'notifyRegularity notify_regularity tinyint(4) NOT NULL default \'1\'', + 'notifySendBody' => 'notifySendBody notify_send_body tinyint(4) NOT NULL default \'0\'', + 'notifyTypes' => 'notifyTypes notify_types tinyint(4) NOT NULL default \'2\'', + 'memberIP' => 'memberIP member_ip varchar(255) NOT NULL default \'\'', + 'secretQuestion' => 'secretQuestion secret_question varchar(255) NOT NULL default \'\'', + 'secretAnswer' => 'secretAnswer secret_answer varchar(64) NOT NULL default \'\'', + 'ID_THEME' => 'ID_THEME id_theme tinyint(4) unsigned NOT NULL default \'0\'', + 'ID_MSG_LAST_VISIT' => 'ID_MSG_LAST_VISIT id_msg_last_visit int(10) unsigned NOT NULL default \'0\'', + 'additionalGroups' => 'additionalGroups additional_groups varchar(255) NOT NULL default \'\'', + 'smileySet' => 'smileySet smiley_set varchar(48) NOT NULL default \'\'', + 'ID_POST_GROUP' => 'ID_POST_GROUP id_post_group smallint(5) unsigned NOT NULL default \'0\'', + 'totalTimeLoggedIn' => 'totalTimeLoggedIn total_time_logged_in int(10) unsigned NOT NULL default \'0\'', + 'passwordSalt' => 'passwordSalt password_salt varchar(255) NOT NULL default \'\'', + 'ignoreBoards' => 'ignoreBoards ignore_boards text NOT NULL', + 'memberIP2' => 'memberIP2 member_ip2 varchar(255) NOT NULL default \'\'', + ), + 'messages' => array( + 'ID_MSG' => 'ID_MSG id_msg int(10) unsigned NOT NULL auto_increment', + 'ID_TOPIC' => 'ID_TOPIC id_topic mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_BOARD' => 'ID_BOARD id_board smallint(5) unsigned NOT NULL default \'0\'', + 'posterTime' => 'posterTime poster_time int(10) unsigned NOT NULL default \'0\'', + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_MSG_MODIFIED' => 'ID_MSG_MODIFIED id_msg_modified int(10) unsigned NOT NULL default \'0\'', + 'posterName' => 'posterName poster_name varchar(255) NOT NULL default \'\'', + 'posterEmail' => 'posterEmail poster_email varchar(255) NOT NULL default \'\'', + 'posterIP' => 'posterIP poster_ip varchar(255) NOT NULL default \'\'', + 'smileysEnabled' => 'smileysEnabled smileys_enabled tinyint(4) NOT NULL default \'1\'', + 'modifiedTime' => 'modifiedTime modified_time int(10) unsigned NOT NULL default \'0\'', + 'modifiedName' => 'modifiedName modified_name varchar(255) NOT NULL default \'\'', + ), + 'membergroups' => array( + 'ID_GROUP' => 'ID_GROUP id_group smallint(5) unsigned NOT NULL auto_increment', + 'ID_PARENT' => 'ID_PARENT id_parent smallint(5) NOT NULL default \'-2\'', + 'groupName' => 'groupName group_name varchar(80) NOT NULL default \'\'', + 'onlineColor' => 'onlineColor online_color varchar(20) NOT NULL default \'\'', + 'minPosts' => 'minPosts min_posts mediumint(9) NOT NULL default \'-1\'', + 'maxMessages' => 'maxMessages max_messages smallint(5) unsigned NOT NULL default \'0\'', + 'groupType' => 'groupType group_type tinyint(3) NOT NULL default \'0\'', + ), + 'message_icons' => array( + 'ID_ICON' => 'ID_ICON id_icon smallint(5) unsigned NOT NULL auto_increment', + 'iconOrder' => 'iconOrder icon_order smallint(5) unsigned NOT NULL default \'0\'', + 'ID_BOARD' => 'ID_BOARD id_board smallint(5) unsigned NOT NULL default \'0\'', + ), + 'moderators' => array( + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_BOARD' => 'ID_BOARD id_board smallint(5) unsigned NOT NULL default \'0\'', + ), + 'package_servers' => array( + 'ID_SERVER' => 'ID_SERVER id_server smallint(5) unsigned NOT NULL auto_increment', + ), + 'personal_messages' => array( + 'ID_PM' => 'ID_PM id_pm int(10) unsigned NOT NULL auto_increment', + 'ID_MEMBER_FROM' => 'ID_MEMBER_FROM id_member_from mediumint(8) unsigned NOT NULL default \'0\'', + 'deletedBySender' => 'deletedBySender deleted_by_sender tinyint(3) unsigned NOT NULL default \'0\'', + 'fromName' => 'fromName from_name varchar(255) NOT NULL default \'\'', + ), + 'permission_profiles' => array( + 'ID_PROFILE' => 'ID_PROFILE id_profile smallint(5) NOT NULL auto_increment', + ), + 'permissions' => array( + 'ID_GROUP' => 'ID_GROUP id_group smallint(5) NOT NULL default \'0\'', + 'addDeny' => 'addDeny add_deny tinyint(4) NOT NULL default \'1\'', + ), + 'pm_recipients' => array( + 'ID_PM' => 'ID_PM id_pm int(10) unsigned NOT NULL default \'0\'', + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + ), + 'polls' => array( + 'ID_POLL' => 'ID_POLL id_poll mediumint(8) unsigned NOT NULL auto_increment', + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) unsigned NOT NULL default \'0\'', + 'votingLocked' => 'votingLocked voting_locked tinyint(1) NOT NULL default \'0\'', + 'maxVotes' => 'maxVotes max_votes tinyint(3) unsigned NOT NULL default \'1\'', + 'expireTime' => 'expireTime expire_time int(10) unsigned NOT NULL default \'0\'', + 'hideResults' => 'hideResults hide_results tinyint(3) unsigned NOT NULL default \'0\'', + 'changeVote' => 'changeVote change_vote tinyint(3) unsigned NOT NULL default \'0\'', + 'posterName' => 'posterName poster_name varchar(255) NOT NULL default \'\'', + ), + 'poll_choices' => array( + 'ID_CHOICE' => 'ID_CHOICE id_choice tinyint(3) unsigned NOT NULL default \'0\'', + 'ID_POLL' => 'ID_POLL id_poll mediumint(8) unsigned NOT NULL default \'0\'', + ), + 'scheduled_tasks' => array( + 'ID_TASK' => 'ID_TASK id_task smallint(5) NOT NULL auto_increment', + 'nextTime' => 'nextTime next_time int(10) NOT NULL default \'0\'', + 'timeRegularity' => 'timeRegularity time_regularity smallint(5) NOT NULL default \'0\'', + 'timeOffset' => 'timeOffset time_offset int(10) NOT NULL default \'0\'', + 'timeUnit' => 'timeUnit time_unit varchar(1) NOT NULL default \'h\'', + ), + 'smileys' => array( + 'ID_SMILEY' => 'ID_SMILEY id_smiley smallint(5) unsigned NOT NULL auto_increment', + 'smileyRow' => 'smileyRow smiley_row tinyint(4) unsigned NOT NULL default \'0\'', + 'smileyOrder' => 'smileyOrder smiley_order smallint(5) unsigned NOT NULL default \'0\'', + ), + 'subscriptions' => array( + 'ID_SUBSCRIBE' => 'ID_SUBSCRIBE id_subscribe mediumint(8) unsigned NOT NULL auto_increment', + 'ID_GROUP' => 'ID_GROUP id_group smallint(5) NOT NULL default \'0\'', + 'addGroups' => 'addGroups add_groups varchar(40) NOT NULL default \'\'', + 'allowPartial' => 'allowPartial allow_partial tinyint(3) NOT NULL default \'0\'', + ), + 'themes' => array( + 'ID_MEMBER' => 'ID_MEMBER id_member mediumint(8) NOT NULL default \'0\'', + 'ID_THEME' => 'ID_THEME id_theme tinyint(4) unsigned NOT NULL default \'1\'', + ), + 'topics' => array( + 'ID_TOPIC' => 'ID_TOPIC id_topic mediumint(8) unsigned NOT NULL auto_increment', + 'isSticky' => 'isSticky is_sticky tinyint(4) NOT NULL default \'0\'', + 'ID_BOARD' => 'ID_BOARD id_board smallint(5) unsigned NOT NULL default \'0\'', + 'ID_FIRST_MSG' => 'ID_FIRST_MSG id_first_msg int(10) unsigned NOT NULL default \'0\'', + 'ID_LAST_MSG' => 'ID_LAST_MSG id_last_msg int(10) unsigned NOT NULL default \'0\'', + 'ID_MEMBER_STARTED' => 'ID_MEMBER_STARTED id_member_started mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_MEMBER_UPDATED' => 'ID_MEMBER_UPDATED id_member_updated mediumint(8) unsigned NOT NULL default \'0\'', + 'ID_POLL' => 'ID_POLL id_poll mediumint(8) unsigned NOT NULL default \'0\'', + 'numReplies' => 'numReplies num_replies int(10) unsigned NOT NULL default \'0\'', + 'numViews' => 'numViews num_views int(10) unsigned NOT NULL default \'0\'', + 'unapprovedPosts' => 'unapprovedPosts unapproved_posts smallint(5) NOT NULL default \'0\'', + ), +); + +$_GET['ren_col'] = isset($_GET['ren_col']) ? (int) $_GET['ren_col'] : 0; +$step_progress['name'] = 'Renaming columns'; +$step_progress['current'] = $_GET['ren_col']; +$step_progress['total'] = count($nameChanges); + +$count = 0; +// Now do every table... +foreach ($nameChanges as $table_name => $table) +{ + // Already done this? + $count++; + if ($_GET['ren_col'] > $count) + continue; + $_GET['ren_col'] = $count; + + // Check the table exists! + $request = upgrade_query(" + SHOW TABLES + LIKE '{$db_prefix}$table_name'"); + if (mysql_num_rows($request) == 0) + { + mysql_free_result($request); + continue; + } + mysql_free_result($request); + + // Check each column! + $actualChanges = array(); + foreach ($table as $colname => $coldef) + { + $change = array( + 'table' => $table_name, + 'name' => $colname, + 'type' => 'column', + 'method' => 'change_remove', + 'text' => 'CHANGE ' . $coldef, + ); + + // Check if this change may need a special edit. + checkChange($change); + + if (protected_alter($change, $substep, true) == false) + $actualChanges[] = ' CHANGE COLUMN ' . $coldef; + } + + // Do the query - if it needs doing. + if (!empty($actualChanges)) + { + $change = array( + 'table' => $table_name, + 'name' => 'na', + 'type' => 'table', + 'method' => 'full_change', + 'text' => implode(', ', $actualChanges), + ); + + // Here we go - hold on! + protected_alter($change, $substep); + } + + // Update where we are! + $step_progress['current'] = $_GET['ren_col']; +} + +// All done! +unset($_GET['ren_col']); +---} +---# + +---# Converting "log_online". +DROP TABLE IF EXISTS {$db_prefix}log_online; +CREATE TABLE {$db_prefix}log_online ( + session varchar(32) NOT NULL default '', + log_time int(10) NOT NULL default '0', + id_member mediumint(8) unsigned NOT NULL default '0', + id_spider smallint(5) unsigned NOT NULL default '0', + ip int(10) unsigned NOT NULL default '0', + url text NOT NULL, + PRIMARY KEY (session), + KEY log_time (log_time), + KEY id_member (id_member) +) ENGINE=MyISAM{$db_collation}; +---# + +/******************************************************************************/ +--- Adding new board specific features. +/******************************************************************************/ + +---# Implementing board redirects. +ALTER TABLE {$db_prefix}boards +ADD COLUMN redirect varchar(255) NOT NULL default ''; +---# + +/******************************************************************************/ +--- Adding search engine tracking. +/******************************************************************************/ + +---# Creating spider table. +CREATE TABLE IF NOT EXISTS {$db_prefix}spiders ( + id_spider smallint(5) unsigned NOT NULL auto_increment, + spider_name varchar(255) NOT NULL default '', + user_agent varchar(255) NOT NULL default '', + ip_info varchar(255) NOT NULL default '', + PRIMARY KEY id_spider(id_spider) +) ENGINE=MyISAM{$db_collation}; + +INSERT IGNORE INTO {$db_prefix}spiders + (id_spider, spider_name, user_agent, ip_info) +VALUES + (1, 'Google', 'googlebot', ''), + (2, 'Yahoo!', 'slurp', ''), + (3, 'MSN', 'msnbot', ''), + (4, 'Google (Mobile)', 'Googlebot-Mobile', ''), + (5, 'Google (Image)', 'Googlebot-Image', ''), + (6, 'Google (AdSense)', 'Mediapartners-Google', ''), + (7, 'Google (Adwords)', 'AdsBot-Google', ''), + (8, 'Yahoo! (Mobile)', 'YahooSeeker/M1A1-R2D2', ''), + (9, 'Yahoo! (Image)', 'Yahoo-MMCrawler', ''), + (10, 'MSN (Mobile)', 'MSNBOT_Mobile', ''), + (11, 'MSN (Media)', 'msnbot-media', ''), + (12, 'Cuil', 'twiceler', ''), + (13, 'Ask', 'Teoma', ''), + (14, 'Baidu', 'Baiduspider', ''), + (15, 'Gigablast', 'Gigabot', ''), + (16, 'InternetArchive', 'ia_archiver-web.archive.org', ''), + (17, 'Alexa', 'ia_archiver', ''), + (18, 'Omgili', 'omgilibot', ''), + (19, 'EntireWeb', 'Speedy Spider', ''); +---# + +---# Removing a spider. +---{ + upgrade_query(" + DELETE FROM {$db_prefix}spiders + WHERE user_agent = 'yahoo' + AND spider_name = 'Yahoo! (Publisher)' + "); +---} +---# + +---# Creating spider hit tracking table. +CREATE TABLE IF NOT EXISTS {$db_prefix}log_spider_hits ( + id_hit int(10) unsigned NOT NULL auto_increment, + id_spider smallint(5) unsigned NOT NULL default '0', + log_time int(10) unsigned NOT NULL default '0', + url varchar(255) NOT NULL default '', + processed tinyint(3) unsigned NOT NULL default '0', + PRIMARY KEY (id_hit), + KEY id_spider(id_spider), + KEY log_time(log_time), + KEY processed (processed) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Making some changes to spider hit table... +ALTER TABLE {$db_prefix}log_spider_hits +ADD COLUMN id_hit int(10) unsigned NOT NULL auto_increment, +ADD PRIMARY KEY (id_hit); +---# + +---# Creating spider statistic table. +CREATE TABLE IF NOT EXISTS {$db_prefix}log_spider_stats ( + id_spider smallint(5) unsigned NOT NULL default '0', + page_hits smallint(5) unsigned NOT NULL default '0', + last_seen int(10) unsigned NOT NULL default '0', + stat_date date NOT NULL default '0001-01-01', + PRIMARY KEY (stat_date, id_spider) +) ENGINE=MyISAM{$db_collation}; +---# + +/******************************************************************************/ +--- Adding new forum settings. +/******************************************************************************/ + +---# Resetting settings_updated. +REPLACE INTO {$db_prefix}settings + (variable, value) +VALUES + ('settings_updated', '0'), + ('last_mod_report_action', '0'), + ('search_floodcontrol_time', '5'), + ('next_task_time', UNIX_TIMESTAMP()); +---# + +---# Changing stats settings. +---{ +$request = upgrade_query(" + SELECT value + FROM {$db_prefix}themes + WHERE variable = 'show_sp1_info'"); +if (mysql_num_rows($request) != 0) +{ + upgrade_query(" + DELETE FROM {$db_prefix}themes + WHERE variable = 'show_stats_index'"); + + upgrade_query(" + UPDATE {$db_prefix}themes + SET variable = 'show_stats_index' + WHERE variable = 'show_sp1_info'"); +} +upgrade_query(" + DELETE FROM {$db_prefix}themes + WHERE variable = 'show_sp1_info'"); +---} +---# + +---# Enable cache if upgrading from 1.1 and lower. +---{ +if (isset($modSettings['smfVersion']) && $modSettings['smfVersion'] <= '2.0 Beta 1') +{ + $request = upgrade_query(" + SELECT value + FROM {$db_prefix}settings + WHERE variable = 'cache_enable'"); + list ($cache_enable) = $smcFunc['db_fetch_row']($request); + + // No cache before 1.1. + if ($smcFunc['db_num_rows']($request) == 0) + upgrade_query(" + INSERT INTO {$db_prefix}settings + (variable, value) + VALUES ('cache_enable', '1')"); + elseif (empty($cache_enable)) + upgrade_query(" + UPDATE {$db_prefix}settings + SET value = '1' + WHERE variable = 'cache_enable'"); +} +---} +---# + +---# Changing visual verification setting. +---{ +$request = upgrade_query(" + SELECT value + FROM {$db_prefix}settings + WHERE variable = 'disable_visual_verification'"); +if (mysql_num_rows($request) != 0) +{ + list ($oldValue) = mysql_fetch_row($request); + if ($oldValue != 0) + { + // We have changed the medium setting from SMF 1.1.2. + if ($oldValue == 4) + $oldValue = 5; + + upgrade_query(" + UPDATE {$db_prefix}settings + SET variable = 'visual_verification_type', value = $oldValue + WHERE variable = 'disable_visual_verification'"); + } +} +upgrade_query(" + DELETE FROM {$db_prefix}settings + WHERE variable = 'disable_visual_verification'"); +---} +---# + +---# Changing visual verification setting, again. +---{ +$request = upgrade_query(" + SELECT value + FROM {$db_prefix}settings + WHERE variable = 'reg_verification'"); +if (mysql_num_rows($request) == 0) +{ + // Upgrade visual verification again! + if (!empty($modSettings['visual_verification_type'])) + { + upgrade_query(" + UPDATE {$db_prefix}settings + SET value = value - 1 + WHERE variable = 'visual_verification_type'"); + $modSettings['visual_verification_type']--; + } + // Never set? + elseif (!isset($modSettings['visual_verification_type'])) + { + upgrade_query(" + INSERT INTO {$db_prefix}settings + (variable, value) + VALUES + ('visual_verification_type', '3')"); + $modSettings['visual_verification_type'] = 3; + } + + upgrade_query(" + INSERT INTO {$db_prefix}settings + (variable, value) + VALUES + ('reg_verification', '" . (!empty($modSettings['visual_verification_type']) ? 1 : 0) . "')"); +} +---} +---# + +---# Changing default personal text setting. +UPDATE {$db_prefix}settings +SET variable = 'default_personal_text' +WHERE variable = 'default_personalText'; + +DELETE FROM {$db_prefix}settings +WHERE variable = 'default_personalText'; +---# + +---# Removing allow hide email setting. +DELETE FROM {$db_prefix}settings +WHERE variable = 'allow_hideEmail' + OR variable = 'allow_hide_email'; +---# + +---# Ensuring stats index setting present... +INSERT IGNORE INTO {$db_prefix}themes + (id_theme, variable, value) +VALUES + (1, 'show_stats_index', '0'); +---# + +---# Ensuring forum width setting present... +INSERT IGNORE INTO {$db_prefix}themes + (id_theme, variable, value) +VALUES + (1, 'forum_width', '90%'); +---# + +---# Replacing old calendar settings... +---{ +// Only try it if one of the "new" settings doesn't yet exist. +if (!isset($modSettings['cal_showholidays']) || !isset($modSettings['cal_showbdays']) || !isset($modSettings['cal_showevents'])) +{ + // Default to just the calendar setting. + $modSettings['cal_showholidays'] = empty($modSettings['cal_showholidaysoncalendar']) ? 0 : 1; + $modSettings['cal_showbdays'] = empty($modSettings['cal_showbdaysoncalendar']) ? 0 : 1; + $modSettings['cal_showevents'] = empty($modSettings['cal_showeventsoncalendar']) ? 0 : 1; + + // Then take into account board index. + if (!empty($modSettings['cal_showholidaysonindex'])) + $modSettings['cal_showholidays'] = $modSettings['cal_showholidays'] === 1 ? 2 : 3; + if (!empty($modSettings['cal_showbdaysonindex'])) + $modSettings['cal_showbdays'] = $modSettings['cal_showbdays'] === 1 ? 2 : 3; + if (!empty($modSettings['cal_showeventsonindex'])) + $modSettings['cal_showevents'] = $modSettings['cal_showevents'] === 1 ? 2 : 3; + + // Actually save the settings. + upgrade_query(" + INSERT IGNORE INTO {$db_prefix}settings + (variable, value) + VALUES + ('cal_showholidays', $modSettings[cal_showholidays]), + ('cal_showbdays', $modSettings[cal_showbdays]), + ('cal_showevents', $modSettings[cal_showevents])"); +} + +---} +---# + +---# Fixing the calendar holidays if they were somehow wrong before... +UPDATE {$db_prefix}calendar_holidays +SET event_date = '2013-11-28' +WHERE event_date = '2013-11-21' + AND title = 'Thanksgiving'; + +UPDATE {$db_prefix}calendar_holidays +SET event_date = '2014-11-27' +WHERE event_date = '2014-11-20' + AND title = 'Thanksgiving'; + +UPDATE {$db_prefix}calendar_holidays +SET event_date = '2019-11-28' +WHERE event_date = '2019-11-21' + AND title = 'Thanksgiving'; + +UPDATE {$db_prefix}calendar_holidays +SET event_date = '2013-09-02' +WHERE event_date = '2013-09-09' + AND title = 'Labor Day'; + +UPDATE {$db_prefix}calendar_holidays +SET event_date = '2014-09-01' +WHERE event_date = '2014-09-08' + AND title = 'Labor Day'; + +UPDATE {$db_prefix}calendar_holidays +SET event_date = '2019-09-02' +WHERE event_date = '2019-09-09' + AND title = 'Labor Day'; +---# + +---# Deleting old calendar settings... + DELETE FROM {$db_prefix}settings + WHERE VARIABLE IN ('cal_showholidaysonindex', 'cal_showbdaysonindex', 'cal_showeventsonindex', + 'cal_showholidaysoncalendar', 'cal_showbdaysoncalendar', 'cal_showeventsoncalendar', + 'cal_holidaycolor', 'cal_bdaycolor', 'cal_eventcolor'); +---# + +---# Adjusting calendar maximum year... +---{ +if (!isset($modSettings['cal_maxyear']) || $modSettings['cal_maxyear'] == '2010') +{ + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('cal_maxyear', '2020')"); +} +---} +---# + +---# Adding advanced signature settings... +---{ +if (empty($modSettings['signature_settings'])) +{ + if (isset($modSettings['max_signatureLength'])) + $modSettings['signature_settings'] = '1,' . $modSettings['max_signatureLength'] . ',0,0,0,0,0,0:'; + else + $modSettings['signature_settings'] = '1,300,0,0,0,0,0,0:'; + + upgrade_query(" + INSERT IGNORE INTO {$db_prefix}settings + (variable, value) + VALUES + ('signature_settings', '$modSettings[signature_settings]')"); + + upgrade_query(" + DELETE FROM {$db_prefix}settings + WHERE variable = 'max_signatureLength'"); +} +---} +---# + +---# Updating spam protection settings. +---{ +if (empty($modSettings['pm_spam_settings'])) +{ + if (isset($modSettings['max_pm_recipients'])) + $modSettings['pm_spam_settings'] = $modSettings['max_pm_recipients'] . ',5,20'; + else + $modSettings['pm_spam_settings'] = '10,5,20'; +} +elseif (substr_count($modSettings['pm_spam_settings'], ',') == 1) +{ + $modSettings['pm_spam_settings'] .= ',20'; +} + +upgrade_query(" + INSERT IGNORE INTO {$db_prefix}settings + (variable, value) + VALUES + ('pm_spam_settings', '$modSettings[pm_spam_settings]')"); + +upgrade_query(" + DELETE FROM {$db_prefix}settings + WHERE variable = 'max_pm_recipients'"); +---} +---# + +---# Adjusting timezone settings... +---{ + if (!isset($modSettings['default_timezone']) && function_exists('date_default_timezone_set')) + { + $server_offset = mktime(0, 0, 0, 1, 1, 1970); + $timezone_id = 'Etc/GMT' . ($server_offset > 0 ? '+' : '') . ($server_offset / 3600); + if (date_default_timezone_set($timezone_id)) + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('default_timezone', '$timezone_id')"); + } +---} +---# + +---# Checking theme layers are correct for default themes. +---{ +$request = upgrade_query(" + SELECT id_theme, value, variable + FROM {$db_prefix}themes + WHERE variable = 'theme_layers' + OR variable = 'theme_dir'"); +$themeLayerChanges = array(); +while ($row = mysql_fetch_assoc($request)) +{ + $themeLayerChanges[$row['id_theme']][$row['variable']] = $row['value']; +} +mysql_free_result($request); + +foreach ($themeLayerChanges as $id_theme => $data) +{ + // Has to be a SMF provided theme and have custom layers defined. + if (!isset($data['theme_layers']) || !isset($data['theme_dir']) || !in_array(substr($data['theme_dir'], -7), array('default', 'babylon', 'classic'))) + continue; + + $layers = explode(',', $data['theme_layers']); + foreach ($layers as $k => $v) + if ($v == 'main') + { + $layers[$k] = 'html,body'; + upgrade_query(" + UPDATE {$db_prefix}themes + SET value = '" . implode(',', $layers) . "' + WHERE id_theme = $id_theme + AND variable = 'theme_layers'"); + break; + } +} +---} +---# + +---# Adding index to log_notify table... +ALTER TABLE {$db_prefix}log_notify +ADD INDEX id_topic (id_topic, id_member); +---# + +/******************************************************************************/ +--- Adding custom profile fields. +/******************************************************************************/ + +---# Creating "custom_fields" table... +CREATE TABLE IF NOT EXISTS {$db_prefix}custom_fields ( + id_field smallint(5) NOT NULL auto_increment, + col_name varchar(12) NOT NULL default '', + field_name varchar(40) NOT NULL default '', + field_desc varchar(255) NOT NULL default '', + field_type varchar(8) NOT NULL default 'text', + field_length smallint(5) NOT NULL default '255', + field_options text NOT NULL, + mask varchar(255) NOT NULL default '', + show_reg tinyint(3) NOT NULL default '0', + show_display tinyint(3) NOT NULL default '0', + show_profile varchar(20) NOT NULL default 'forumprofile', + private tinyint(3) NOT NULL default '0', + active tinyint(3) NOT NULL default '1', + bbc tinyint(3) NOT NULL default '0', + default_value varchar(255) NOT NULL default '', + PRIMARY KEY (id_field), + UNIQUE col_name (col_name) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Adding search ability to custom fields. +ALTER TABLE {$db_prefix}custom_fields +ADD COLUMN can_search tinyint(3) NOT NULL default '0' AFTER bbc; +---# + +---# Fixing default value field length. +ALTER TABLE {$db_prefix}custom_fields +CHANGE COLUMN default_value default_value varchar(255) NOT NULL default ''; +---# + +---# Enhancing privacy settings for custom fields. +---{ +if (isset($modSettings['smfVersion']) && $modSettings['smfVersion'] <= '2.0 Beta 1') +{ +upgrade_query(" + UPDATE {$db_prefix}custom_fields + SET private = 2 + WHERE private = 1"); +} +if (isset($modSettings['smfVersion']) && $modSettings['smfVersion'] < '2.0 Beta 4') +{ +upgrade_query(" + UPDATE {$db_prefix}custom_fields + SET private = 3 + WHERE private = 2"); +} +---} +---# + +---# Checking display fields setup correctly.. +---{ +if (isset($modSettings['smfVersion']) && $modSettings['smfVersion'] <= '2.0 Beta 1' && isset($modSettings['displayFields']) && @unserialize($modSettings['displayFields']) == false) +{ +$request = upgrade_query(" + SELECT col_name, field_name, bbc + FROM {$db_prefix}custom_fields + WHERE show_display = 1 + AND active = 1 + AND private != 2"); +$fields = array(); +while ($row = mysql_fetch_assoc($request)) +{ + $fields[] = array( + 'c' => strtr($row['col_name'], array('|' => '', ';' => '')), + 'f' => strtr($row['field_name'], array('|' => '', ';' => '')), + 'b' => ($row['bbc'] ? '1' : '0') + ); +} +mysql_free_result($request); + +upgrade_query(" + UPDATE {$db_prefix}settings + SET value = '" . mysql_real_escape_string(serialize($fields)) . "' + WHERE variable = 'displayFields'"); +} +---} +---# + +---# Adding new custom fields columns. +ALTER TABLE {$db_prefix}custom_fields +ADD enclose text NOT NULL; + +ALTER TABLE {$db_prefix}custom_fields +ADD placement tinyint(3) NOT NULL default '0'; +---# + +/******************************************************************************/ +--- Adding email digest functionality. +/******************************************************************************/ + +---# Creating "log_digest" table... +CREATE TABLE IF NOT EXISTS {$db_prefix}log_digest ( + id_topic mediumint(8) unsigned NOT NULL default '0', + id_msg int(10) unsigned NOT NULL default '0', + note_type varchar(10) NOT NULL default 'post', + daily tinyint(3) unsigned NOT NULL default '0', + exclude mediumint(8) unsigned NOT NULL default '0' +) ENGINE=MyISAM{$db_collation}; +---# + +---# Adding digest option to "members" table... +ALTER TABLE {$db_prefix}members +CHANGE COLUMN notifyOnce notify_regularity tinyint(4) unsigned NOT NULL default '1'; +---# + +/******************************************************************************/ +--- Making changes to the package manager. +/******************************************************************************/ + +---# Creating "log_packages" table... +CREATE TABLE IF NOT EXISTS {$db_prefix}log_packages ( + id_install int(10) NOT NULL auto_increment, + filename varchar(255) NOT NULL default '', + package_id varchar(255) NOT NULL default '', + name varchar(255) NOT NULL default '', + version varchar(255) NOT NULL default '', + id_member_installed mediumint(8) NOT NULL default '0', + member_installed varchar(255) NOT NULL default '', + time_installed int(10) NOT NULL default '0', + id_member_removed mediumint(8) NOT NULL default '0', + member_removed varchar(255) NOT NULL default '', + time_removed int(10) NOT NULL default '0', + install_state tinyint(3) NOT NULL default '1', + failed_steps text NOT NULL, + themes_installed varchar(255) NOT NULL default '', + db_changes text NOT NULL, + PRIMARY KEY (id_install), + KEY filename (filename(15)) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Adding extra "log_packages" columns... +ALTER TABLE {$db_prefix}log_packages +ADD db_changes text NOT NULL AFTER themes_installed; +---# + +---# Changing URL to SMF package server... +UPDATE {$db_prefix}package_servers +SET url = 'http://custom.simplemachines.org/packages/mods' +WHERE url = 'http://mods.simplemachines.org'; +---# + +/******************************************************************************/ +--- Creating mail queue functionality. +/******************************************************************************/ + +---# Creating "mail_queue" table... +CREATE TABLE IF NOT EXISTS {$db_prefix}mail_queue ( + id_mail int(10) unsigned NOT NULL auto_increment, + time_sent int(10) NOT NULL default '0', + recipient varchar(255) NOT NULL default '', + body text NOT NULL, + subject varchar(255) NOT NULL default '', + headers text NOT NULL, + send_html tinyint(3) NOT NULL default '0', + priority tinyint(3) NOT NULL default '1', + PRIMARY KEY (id_mail), + KEY time_sent (time_sent), + KEY mail_priority (priority, id_mail) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Adding new mail queue settings... +---{ +if (!isset($modSettings['mail_next_send'])) +{ + upgrade_query(" + INSERT INTO {$db_prefix}settings + (variable, value) + VALUES + ('mail_next_send', '0'), + ('mail_recent', '0000000000|0')"); +} +---} +---# + +---# Change mail queue indexes... +ALTER TABLE {$db_prefix}mail_queue +DROP INDEX priority; + +ALTER TABLE {$db_prefix}mail_queue +ADD INDEX mail_priority (priority, id_mail); +---# + +---# Adding type to mail queue... +ALTER TABLE {$db_prefix}mail_queue +ADD private tinyint(1) NOT NULL default '0'; +---# + +/******************************************************************************/ +--- Creating moderation center tables. +/******************************************************************************/ + +---# Creating "log_reported" table... +CREATE TABLE IF NOT EXISTS {$db_prefix}log_reported ( + id_report mediumint(8) unsigned NOT NULL auto_increment, + id_msg int(10) unsigned NOT NULL default '0', + id_topic mediumint(8) unsigned NOT NULL default '0', + id_board smallint(5) unsigned NOT NULL default '0', + id_member mediumint(8) unsigned NOT NULL default '0', + membername varchar(255) NOT NULL default '', + subject varchar(255) NOT NULL default '', + body text NOT NULL, + time_started int(10) NOT NULL default '0', + time_updated int(10) NOT NULL default '0', + num_reports mediumint(6) NOT NULL default '0', + closed tinyint(3) NOT NULL default '0', + ignore_all tinyint(3) NOT NULL default '0', + PRIMARY KEY (id_report), + KEY id_member (id_member), + KEY id_topic (id_topic), + KEY closed (closed), + KEY time_started (time_started), + KEY id_msg (id_msg) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Creating "log_reported_comments" table... +CREATE TABLE IF NOT EXISTS {$db_prefix}log_reported_comments ( + id_comment mediumint(8) unsigned NOT NULL auto_increment, + id_report mediumint(8) NOT NULL default '0', + id_member mediumint(8) NOT NULL, + membername varchar(255) NOT NULL default '', + comment varchar(255) NOT NULL default '', + time_sent int(10) NOT NULL, + PRIMARY KEY (id_comment), + KEY id_report (id_report), + KEY id_member (id_member), + KEY time_sent (time_sent) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Adding moderator center permissions... +---{ +// Don't do this twice! +if (@$modSettings['smfVersion'] < '2.0') +{ + // Try find people who probably should see the moderation center. + $request = upgrade_query(" + SELECT id_group, add_deny, permission + FROM {$db_prefix}permissions + WHERE permission = 'calendar_edit_any'"); + $inserts = array(); + while ($row = mysql_fetch_assoc($request)) + { + $inserts[] = "($row[id_group], 'access_mod_center', $row[add_deny])"; + } + mysql_free_result($request); + + if (!empty($inserts)) + upgrade_query(" + INSERT IGNORE INTO {$db_prefix}permissions + (id_group, permission, add_deny) + VALUES + " . implode(',', $inserts)); +} +---} +---# + +---# Adding moderation center preferences... +ALTER TABLE {$db_prefix}members +ADD mod_prefs varchar(20) NOT NULL default ''; +---# + +/******************************************************************************/ +--- Adding user warnings. +/******************************************************************************/ + +---# Creating member notices table... +CREATE TABLE IF NOT EXISTS {$db_prefix}log_member_notices ( + id_notice mediumint(8) unsigned NOT NULL auto_increment, + subject varchar(255) NOT NULL default '', + body text NOT NULL, + PRIMARY KEY (id_notice) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Creating comments table... +CREATE TABLE IF NOT EXISTS {$db_prefix}log_comments ( + id_comment mediumint(8) unsigned NOT NULL auto_increment, + id_member mediumint(8) unsigned NOT NULL default '0', + member_name varchar(80) NOT NULL default '', + comment_type varchar(8) NOT NULL default 'warning', + id_recipient mediumint(8) unsigned NOT NULL default '0', + recipient_name varchar(255) NOT NULL default '', + log_time int(10) NOT NULL default '0', + id_notice mediumint(8) unsigned NOT NULL default '0', + counter tinyint(3) NOT NULL default '0', + body text NOT NULL, + PRIMARY KEY (id_comment), + KEY id_recipient (id_recipient), + KEY log_time (log_time), + KEY comment_type (comment_type(8)) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Adding user warning column... +ALTER TABLE {$db_prefix}members +ADD warning tinyint(4) NOT NULL default '0'; + +ALTER TABLE {$db_prefix}members +ADD INDEX warning (warning); +---# + +---# Ensuring warning settings are present... +---{ +// Only do this if not already done. +if (empty($modSettings['warning_settings'])) +{ + upgrade_query(" + INSERT IGNORE INTO {$db_prefix}settings + (variable, value) + VALUES + ('warning_settings', '1,20,0'), + ('warning_watch', '10'), + ('warning_moderate', '35'), + ('warning_mute', '60')"); +} +---} +---# + +/******************************************************************************/ +--- Enhancing membergroups. +/******************************************************************************/ + +---# Creating "log_group_requests" table... +CREATE TABLE IF NOT EXISTS {$db_prefix}log_group_requests ( + id_request mediumint(8) unsigned NOT NULL auto_increment, + id_member mediumint(8) unsigned NOT NULL default '0', + id_group smallint(5) unsigned NOT NULL default '0', + time_applied int(10) unsigned NOT NULL default '0', + reason text NOT NULL, + PRIMARY KEY (id_request), + UNIQUE id_member (id_member, id_group) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Adding new membergroup table columns... +ALTER TABLE {$db_prefix}membergroups +ADD description text NOT NULL AFTER group_name; + +ALTER TABLE {$db_prefix}membergroups +ADD group_type tinyint(3) NOT NULL default '0'; + +ALTER TABLE {$db_prefix}membergroups +ADD hidden tinyint(3) NOT NULL default '0'; +---# + +---# Creating "group_moderators" table... +CREATE TABLE IF NOT EXISTS {$db_prefix}group_moderators ( + id_group smallint(5) unsigned NOT NULL default '0', + id_member mediumint(8) unsigned NOT NULL default '0', + PRIMARY KEY (id_group, id_member) +) ENGINE=MyISAM{$db_collation}; +---# + +/******************************************************************************/ +--- Updating attachment data... +/******************************************************************************/ + +---# Altering attachment table. +ALTER TABLE {$db_prefix}attachments +ADD COLUMN fileext varchar(8) NOT NULL default '', +ADD COLUMN mime_type varchar(20) NOT NULL default ''; + +ALTER TABLE {$db_prefix}attachments +ADD COLUMN id_folder tinyint(3) NOT NULL default '1'; +---# + +---# Adding file hash. +ALTER TABLE {$db_prefix}attachments +ADD COLUMN file_hash varchar(40) NOT NULL default ''; +---# + +---# Populate the attachment extension. +UPDATE {$db_prefix}attachments +SET fileext = LOWER(SUBSTRING(filename, 1 - (INSTR(REVERSE(filename), '.')))) +WHERE fileext = '' + AND INSTR(filename, '.') + AND attachment_type != 3; +---# + +---# Updating thumbnail attachments JPG. +UPDATE {$db_prefix}attachments +SET fileext = 'jpg' +WHERE attachment_type = 3 + AND fileext = '' + AND RIGHT(filename, 9) = 'JPG_thumb'; +---# + +---# Updating thumbnail attachments PNG. +UPDATE {$db_prefix}attachments +SET fileext = 'png' +WHERE attachment_type = 3 + AND fileext = '' + AND RIGHT(filename, 9) = 'PNG_thumb'; +---# + +---# Calculating attachment mime types. +---{ +// Don't ever bother doing this twice. +if (@$modSettings['smfVersion'] < '2.0' || @$modSettings['smfVersion'] === '2.0 a') +{ + $request = upgrade_query(" + SELECT MAX(id_attach) + FROM {$db_prefix}attachments"); + list ($step_progress['total']) = $smcFunc['db_fetch_row']($request); + $smcFunc['db_free_result']($request); + + $_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; + $step_progress['name'] = 'Calculating MIME Types'; + $step_progress['current'] = $_GET['a']; + + if (!function_exists('getAttachmentFilename')) + { + function getAttachmentFilename($filename, $attachment_id) + { + global $modSettings; + + $clean_name = strtr($filename, 'ŠŽšžŸÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖØÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöøùúûüýÿ', 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'); + $clean_name = strtr($clean_name, array('Þ' => 'TH', 'þ' => 'th', 'Ð' => 'DH', 'ð' => 'dh', 'ß' => 'ss', 'Œ' => 'OE', 'œ' => 'oe', 'Æ' => 'AE', 'æ' => 'ae', 'µ' => 'u')); + $clean_name = preg_replace(array('/\s/', '/[^\w_\.\-]/'), array('_', ''), $clean_name); + $enc_name = $attachment_id . '_' . strtr($clean_name, '.', '_') . md5($clean_name); + $clean_name = preg_replace('~\.[\.]+~', '.', $clean_name); + + if ($attachment_id == false) + return $clean_name; + + if (file_exists($modSettings['attachmentUploadDir'] . '/' . $enc_name)) + $filename = $modSettings['attachmentUploadDir'] . '/' . $enc_name; + else + $filename = $modSettings['attachmentUploadDir'] . '/' . $clean_name; + + return $filename; + } + } + + $ext_updates = array(); + + // What headers are valid results for getimagesize? + $validTypes = array( + 1 => 'gif', + 2 => 'jpeg', + 3 => 'png', + 5 => 'psd', + 6 => 'bmp', + 7 => 'tiff', + 8 => 'tiff', + 9 => 'jpeg', + 14 => 'iff', + ); + + $is_done = false; + while (!$is_done) + { + nextSubStep($substep); + + $request = upgrade_query(" + SELECT id_attach, filename, fileext + FROM {$db_prefix}attachments + WHERE fileext != '' + AND mime_type = '' + LIMIT $_GET[a], 100"); + // Finished? + if ($smcFunc['db_num_rows']($request) == 0) + $is_done = true; + while ($row = $smcFunc['db_fetch_assoc']($request)) + { + $filename = getAttachmentFilename($row['filename'], $row['id_attach']); + if (!file_exists($filename)) + continue; + + // Is it an image? + $size = @getimagesize($filename); + // Nothing valid? + if (empty($size) || empty($size[0])) + continue; + // Got the mime? + elseif (!empty($size['mime'])) + $mime = $size['mime']; + // Otherwise is it valid? + elseif (!isset($validTypes[$size[2]])) + continue; + else + $mime = 'image/' . $validTypes[$size[2]]; + + // Let's try keep updates to a minimum. + if (!isset($ext_updates[$row['fileext'] . $size['mime']])) + $ext_updates[$row['fileext'] . $size['mime']] = array( + 'fileext' => $row['fileext'], + 'mime' => $mime, + 'files' => array(), + ); + $ext_updates[$row['fileext'] . $size['mime']]['files'][] = $row['id_attach']; + } + $smcFunc['db_free_result']($request); + + // Do the updates? + foreach ($ext_updates as $key => $update) + { + upgrade_query(" + UPDATE {$db_prefix}attachments + SET mime_type = '$update[mime]' + WHERE id_attach IN (" . implode(',', $update['files']) . ")"); + + // Remove it. + unset($ext_updates[$key]); + } + + $_GET['a'] += 100; + $step_progress['current'] = $_GET['a']; + } + + unset($_GET['a']); +} +---} +---# + +/******************************************************************************/ +--- Adding Post Moderation. +/******************************************************************************/ + +---# Creating "approval_queue" table... +CREATE TABLE IF NOT EXISTS {$db_prefix}approval_queue ( + id_msg int(10) unsigned NOT NULL default '0', + id_attach int(10) unsigned NOT NULL default '0', + id_event smallint(5) unsigned NOT NULL default '0' +) ENGINE=MyISAM{$db_collation}; +---# + +---# Adding approved column to attachments table... +ALTER TABLE {$db_prefix}attachments +ADD approved tinyint(3) NOT NULL default '1'; +---# + +---# Adding approved column to messages table... +ALTER TABLE {$db_prefix}messages +ADD approved tinyint(3) NOT NULL default '1'; + +ALTER TABLE {$db_prefix}messages +ADD INDEX approved (approved); +---# + +---# Adding unapproved count column to topics table... +ALTER TABLE {$db_prefix}topics +ADD unapproved_posts smallint(5) NOT NULL default '0'; +---# + +---# Adding approved column to topics table... +ALTER TABLE {$db_prefix}topics +ADD approved tinyint(3) NOT NULL default '1', +ADD INDEX approved (approved); +---# + +---# Adding approved columns to boards table... +ALTER TABLE {$db_prefix}boards +ADD unapproved_posts smallint(5) NOT NULL default '0', +ADD unapproved_topics smallint(5) NOT NULL default '0'; +---# + +---# Adding post moderation permissions... +---{ +// We *cannot* do this twice! +if (@$modSettings['smfVersion'] < '2.0') +{ + // Anyone who can currently edit posts we assume can approve them... + $request = upgrade_query(" + SELECT id_group, id_board, add_deny, permission + FROM {$db_prefix}board_permissions + WHERE permission = 'modify_any'"); + $inserts = array(); + while ($row = mysql_fetch_assoc($request)) + { + $inserts[] = "($row[id_group], $row[id_board], 'approve_posts', $row[add_deny])"; + } + mysql_free_result($request); + + if (!empty($inserts)) + upgrade_query(" + INSERT IGNORE INTO {$db_prefix}board_permissions + (id_group, id_board, permission, add_deny) + VALUES + " . implode(',', $inserts)); +} +---} +---# + +/******************************************************************************/ +--- Upgrading the error log. +/******************************************************************************/ + +---# Adding columns to log_errors table... +ALTER TABLE {$db_prefix}log_errors +ADD error_type char(15) NOT NULL default 'general'; +ALTER TABLE {$db_prefix}log_errors +ADD file varchar(255) NOT NULL default '', +ADD line mediumint(8) unsigned NOT NULL default '0'; +---# + +---# Updating error log table... +---{ +$request = upgrade_query(" + SELECT COUNT(*) + FROM {$db_prefix}log_errors"); +list($totalActions) = mysql_fetch_row($request); +mysql_free_result($request); + +$_GET['m'] = !empty($_GET['m']) ? (int) $_GET['m'] : '0'; +$step_progress['total'] = $totalActions; +$step_progress['current'] = $_GET['m']; + +while ($_GET['m'] < $totalActions) +{ + nextSubStep($substep); + + $request = upgrade_query(" + SELECT id_error, message, file, line + FROM {$db_prefix}log_errors + LIMIT $_GET[m], 500"); + while($row = mysql_fetch_assoc($request)) + { + preg_match('~
    (%1\$s: )?([\w\. \\\\/\-_:]+)
    (%2\$s: )?([\d]+)~', $row['message'], $matches); + if (!empty($matches[2]) && !empty($matches[4]) && empty($row['file']) && empty($row['line'])) + { + $row['file'] = addslashes(str_replace('\\', '/', $matches[2])); + $row['line'] = (int) $matches[4]; + $row['message'] = addslashes(preg_replace('~
    (%1\$s: )?([\w\. \\\\/\-_:]+)
    (%2\$s: )?([\d]+)~', '', $row['message'])); + } + else + continue; + + upgrade_query(" + UPDATE {$db_prefix}log_errors + SET file = SUBSTRING('$row[file]', 1, 255), + line = $row[line], + message = SUBSTRING('$row[message]', 1, 65535) + WHERE id_error = $row[id_error] + LIMIT 1"); + } + + $_GET['m'] += 500; + $step_progress['current'] = $_GET['m']; +} +unset($_GET['m']); +---} +---# + +/******************************************************************************/ +--- Adding Scheduled Tasks Data. +/******************************************************************************/ + +---# Creating Scheduled Task Table... +CREATE TABLE IF NOT EXISTS {$db_prefix}scheduled_tasks ( + id_task smallint(5) NOT NULL auto_increment, + next_time int(10) NOT NULL default '0', + time_offset int(10) NOT NULL default '0', + time_regularity smallint(5) NOT NULL default '0', + time_unit varchar(1) NOT NULL default 'h', + disabled tinyint(3) NOT NULL default '0', + task varchar(24) NOT NULL default '', + PRIMARY KEY (id_task), + KEY next_time (next_time), + KEY disabled (disabled), + UNIQUE task (task) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Populating Scheduled Task Table... +INSERT IGNORE INTO {$db_prefix}scheduled_tasks + (next_time, time_offset, time_regularity, time_unit, disabled, task) +VALUES + (0, 0, 2, 'h', 0, 'approval_notification'), + (0, 0, 7, 'd', 0, 'auto_optimize'), + (0, 60, 1, 'd', 0, 'daily_maintenance'), + (0, 0, 1, 'd', 0, 'daily_digest'), + (0, 0, 1, 'w', 0, 'weekly_digest'), + (0, 0, 1, 'd', 1, 'birthdayemails'), + (0, 120, 1, 'd', 0, 'paid_subscriptions'); +---# + +---# Adding the simple machines scheduled task. +---{ +// Randomise the time. +$randomTime = 82800 + rand(0, 86399); +upgrade_query(" + INSERT IGNORE INTO {$db_prefix}scheduled_tasks + (next_time, time_offset, time_regularity, time_unit, disabled, task) + VALUES + (0, {$randomTime}, 1, 'd', 0, 'fetchSMfiles')"); +---} +---# + +---# Deleting old scheduled task items... +DELETE FROM {$db_prefix}scheduled_tasks +WHERE task = 'clean_cache'; +---# + +---# Moving auto optimise settings to scheduled task... +---{ +if (!isset($modSettings['next_task_time']) && isset($modSettings['autoOptLastOpt'])) +{ + // Try move over the regularity... + if (isset($modSettings['autoOptDatabase'])) + { + $disabled = empty($modSettings['autoOptDatabase']) ? 1 : 0; + $regularity = $disabled ? 7 : $modSettings['autoOptDatabase']; + $next_time = $modSettings['autoOptLastOpt'] + 3600 * 24 * $modSettings['autoOptDatabase']; + + // Update the task accordingly. + upgrade_query(" + UPDATE {$db_prefix}scheduled_tasks + SET disabled = $disabled, time_regularity = $regularity, next_time = $next_time + WHERE task = 'auto_optimize'"); + } + + // Delete the old settings! + upgrade_query(" + DELETE FROM {$db_prefix}settings + WHERE VARIABLE IN ('autoOptLastOpt', 'autoOptDatabase')"); +} +---} +---# + +---# Creating Scheduled Task Log Table... +CREATE TABLE IF NOT EXISTS {$db_prefix}log_scheduled_tasks ( + id_log mediumint(8) NOT NULL auto_increment, + id_task smallint(5) NOT NULL default '0', + time_run int(10) NOT NULL default '0', + time_taken float NOT NULL default '0', + PRIMARY KEY (id_log) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Adding new scheduled task setting... +---{ +if (!isset($modSettings['next_task_time'])) +{ + upgrade_query(" + INSERT INTO {$db_prefix}settings + (variable, value) + VALUES + ('next_task_time', '0')"); +} +---} +---# + +---# Setting the birthday email template if not set... +---{ +if (!isset($modSettings['birthday_email'])) +{ + upgrade_query(" + INSERT INTO {$db_prefix}settings + (variable, value) + VALUES + ('birthday_email', 'happy_birthday')"); +} +---} +---# + +/******************************************************************************/ +--- Adding permission profiles for boards. +/******************************************************************************/ + +---# Creating "permission_profiles" table... +CREATE TABLE IF NOT EXISTS {$db_prefix}permission_profiles ( + id_profile smallint(5) NOT NULL auto_increment, + profile_name varchar(255) NOT NULL default '', + PRIMARY KEY (id_profile) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Adding profile columns to boards table... +ALTER TABLE {$db_prefix}boards +ADD id_profile smallint(5) unsigned NOT NULL default '1' AFTER member_groups; +---# + +---# Adding profile columns to board permission table... +ALTER TABLE {$db_prefix}board_permissions +ADD id_profile smallint(5) unsigned NOT NULL default '1' AFTER id_group; + +ALTER TABLE {$db_prefix}board_permissions +DROP PRIMARY KEY; + +ALTER TABLE {$db_prefix}board_permissions +ADD PRIMARY KEY (id_group, id_profile, permission); +---# + +---# Cleaning up some 2.0 Beta 1 permission profile bits... +---{ +$request = upgrade_query(" + SELECT id_profile + FROM {$db_prefix}permission_profiles + WHERE profile_name = ''"); +$profiles = array(); +while ($row = mysql_fetch_assoc($request)) + $profiles[] = $row['id_profile']; +mysql_free_result($request); + +if (!empty($profiles)) +{ + $request = upgrade_query(" + SELECT id_profile, name + FROM {$db_prefix}boards + WHERE id_profile IN (" . implode(',', $profiles) . ")"); + $done_ids = array(); + while ($row = mysql_fetch_assoc($request)) + { + if (isset($done_ids[$row['id_profile']])) + continue; + $done_ids[$row['id_profile']] = true; + + $row['name'] = mysql_real_escape_string($row['name']); + + upgrade_query(" + UPDATE {$db_prefix}permission_profiles + SET profile_name = '$row[name]' + WHERE id_profile = $row[id_profile]"); + } + mysql_free_result($request); +} +---} +---# + +---# Migrating old board profiles to profile system +---{ + +// Doing this twice would be awful! +$request = upgrade_query(" + SELECT COUNT(*) + FROM {$db_prefix}permission_profiles"); +list ($profileCount) = mysql_fetch_row($request); +mysql_free_result($request); + +if ($profileCount == 0) +{ + // Everything starts off invalid. + upgrade_query(" + UPDATE {$db_prefix}board_permissions + SET id_profile = 0"); + + // Insert a boat load of default profile permissions. + upgrade_query(" + INSERT INTO {$db_prefix}permission_profiles + (id_profile, profile_name) + VALUES + (1, 'default'), + (2, 'no_polls'), + (3, 'reply_only'), + (4, 'read_only')"); + + // Update the default permissions, this is easy! + upgrade_query(" + UPDATE {$db_prefix}board_permissions + SET id_profile = 1 + WHERE id_board = 0"); + + // Load all the other permissions + $request = upgrade_query(" + SELECT id_board, id_group, permission, add_deny + FROM {$db_prefix}board_permissions + WHERE id_profile = 0"); + $all_perms = array(); + while ($row = mysql_fetch_assoc($request)) + $all_perms[$row['id_board']][$row['id_group']][$row['permission']] = $row['add_deny']; + mysql_free_result($request); + + // Now we have the profile profiles for this installation. We now need to go through each board and work out what the permission profile should be! + $request = upgrade_query(" + SELECT id_board, name, permission_mode + FROM {$db_prefix}boards"); + $board_updates = array(); + while ($row = mysql_fetch_assoc($request)) + { + $row['name'] = addslashes($row['name']); + + // Is it a truely local permission board? If so this is a new profile! + if ($row['permission_mode'] == 1) + { + // I know we could cache this, but I think we need to be practical - this is slow but guaranteed to work. + upgrade_query(" + INSERT INTO {$db_prefix}permission_profiles + (profile_name) + VALUES + ('$row[name]')"); + $board_updates[mysql_insert_id()][] = $row['id_board']; + } + // Otherwise, dear god, this is an old school "simple" permission... + elseif ($row['permission_mode'] > 1 && $row['permission_mode'] < 5) + { + $board_updates[$row['permission_mode']][] = $row['id_board']; + } + // Otherwise this is easy. It becomes default. + else + $board_updates[1][] = $row['id_board']; + } + mysql_free_result($request); + + // Update the board tables. + foreach ($board_updates as $profile => $boards) + { + if (empty($boards)) + continue; + + $boards = implode(',', $boards); + + upgrade_query(" + UPDATE {$db_prefix}boards + SET id_profile = $profile + WHERE id_board IN ($boards)"); + + // If it's a custom profile then update this too. + if ($profile > 4) + upgrade_query(" + UPDATE {$db_prefix}board_permissions + SET id_profile = $profile + WHERE id_board IN ($boards) + AND id_profile = 0"); + } + + // Just in case we have any random permissions that didn't have boards. + upgrade_query(" + DELETE FROM {$db_prefix}board_permissions + WHERE id_profile = 0"); +} +---} +---# + +---# Removing old board permissions column... +ALTER TABLE {$db_prefix}board_permissions +DROP COLUMN id_board; +---# + +---# Check the predefined profiles all have the right permissions. +---{ +// What are all the permissions people can have. +$mod_permissions = array( + 'moderate_board', 'post_new', 'post_reply_own', 'post_reply_any', 'poll_post', 'poll_add_any', + 'poll_remove_any', 'poll_view', 'poll_vote', 'poll_lock_any', 'poll_edit_any', 'report_any', + 'lock_own', 'send_topic', 'mark_any_notify', 'mark_notify', 'delete_own', 'modify_own', 'make_sticky', + 'lock_any', 'remove_any', 'move_any', 'merge_any', 'split_any', 'delete_any', 'modify_any', 'approve_posts', + 'post_attachment', 'view_attachments', 'post_unapproved_replies_any', 'post_unapproved_replies_own', + 'post_unapproved_attachments', 'post_unapproved_topics', +); + +$no_poll_reg = array( + 'post_new', 'post_reply_own', 'post_reply_any', 'poll_view', 'poll_vote', 'report_any', + 'lock_own', 'send_topic', 'mark_any_notify', 'mark_notify', 'delete_own', 'modify_own', + 'post_attachment', 'view_attachments', 'remove_own', 'post_unapproved_replies_any', 'post_unapproved_replies_own', + 'post_unapproved_attachments', 'post_unapproved_topics', +); + +$reply_only_reg = array( + 'post_reply_own', 'post_reply_any', 'poll_view', 'poll_vote', 'report_any', + 'lock_own', 'send_topic', 'mark_any_notify', 'mark_notify', 'delete_own', 'modify_own', + 'post_attachment', 'view_attachments', 'remove_own', 'post_unapproved_replies_any', 'post_unapproved_replies_own', + 'post_unapproved_attachments', +); + +$read_only_reg = array( + 'poll_view', 'poll_vote', 'report_any', 'send_topic', 'mark_any_notify', 'mark_notify', 'view_attachments', +); + +// Clear all the current predefined profiles. +upgrade_query(" + DELETE FROM {$db_prefix}board_permissions + WHERE id_profile IN (2,3,4)"); + +// Get all the membergroups - cheating to use the fact id_group = 1 exists to get a group of 0. +$request = upgrade_query(" + SELECT IF(id_group = 1, 0, id_group) AS id_group + FROM {$db_prefix}membergroups + WHERE id_group != 0 + AND min_posts = -1"); +$inserts = array(); +while ($row = mysql_fetch_assoc($request)) +{ + if ($row['id_group'] == 2 || $row['id_group'] == 3) + { + foreach ($mod_permissions as $permission) + { + $inserts[] = "($row[id_group], 2, '$permission')"; + $inserts[] = "($row[id_group], 3, '$permission')"; + $inserts[] = "($row[id_group], 4, '$permission')"; + } + } + else + { + foreach ($no_poll_reg as $permission) + $inserts[] = "($row[id_group], 2, '$permission')"; + foreach ($reply_only_reg as $permission) + $inserts[] = "($row[id_group], 3, '$permission')"; + foreach ($read_only_reg as $permission) + $inserts[] = "($row[id_group], 4, '$permission')"; + } +} +mysql_free_result($request); + +upgrade_query(" + INSERT INTO {$db_prefix}board_permissions + (id_group, id_profile, permission) + VALUES (-1, 2, 'poll_view'), + (-1, 3, 'poll_view'), + (-1, 4, 'poll_view'), + " . implode(', ', $inserts)); + +---} +---# + +---# Adding inherited permissions... +ALTER TABLE {$db_prefix}membergroups +ADD id_parent smallint(5) NOT NULL default '-2'; +---# + +---# Make sure admins and moderators don't inherit... +UPDATE {$db_prefix}membergroups +SET id_parent = -2 +WHERE id_group = 1 + OR id_group = 3; +---# + +---# Deleting old permission settings... +DELETE FROM {$db_prefix}settings +WHERE VARIABLE IN ('permission_enable_by_board', 'autoOptDatabase'); +---# + +---# Removing old permission_mode column... +ALTER TABLE {$db_prefix}boards +DROP COLUMN permission_mode; +---# + +/******************************************************************************/ +--- Adding Some Additional Functionality. +/******************************************************************************/ + +---# Adding column to hold the boards being ignored ... +ALTER TABLE {$db_prefix}members +ADD ignore_boards text NOT NULL; +---# + +---# Purge flood control ... +DELETE FROM {$db_prefix}log_floodcontrol; +---# + +---# Adding advanced flood control ... +ALTER TABLE {$db_prefix}log_floodcontrol +ADD log_type varchar(8) NOT NULL default 'post'; +---# + +---# Sorting out flood control keys ... +ALTER TABLE {$db_prefix}log_floodcontrol +DROP PRIMARY KEY, +ADD PRIMARY KEY (ip(16), log_type(8)); +---# + +---# Adding guest voting ... +ALTER TABLE {$db_prefix}polls +ADD guest_vote tinyint(3) NOT NULL default '0'; + +DELETE FROM {$db_prefix}log_polls +WHERE id_member < 0; + +ALTER TABLE {$db_prefix}log_polls +DROP PRIMARY KEY; + +ALTER TABLE {$db_prefix}log_polls +ADD INDEX id_poll (id_poll, id_member, id_choice); +---# + +---# Implementing admin feature toggles. +---{ +if (!isset($modSettings['admin_features'])) +{ + // Work out what they used to have enabled. + $enabled_features = array('rg'); + if (!empty($modSettings['cal_enabled'])) + $enabled_features[] = 'cd'; + if (!empty($modSettings['karmaMode'])) + $enabled_features[] = 'k'; + if (!empty($modSettings['modlog_enabled'])) + $enabled_features[] = 'ml'; + if (!empty($modSettings['paid_enabled'])) + $enabled_features[] = 'ps'; + + $enabled_features = implode(',', $enabled_features); + + upgrade_query(" + INSERT INTO {$db_prefix}settings + (variable, value) + VALUES + ('admin_features', '$enabled_features')"); +} +---} +---# + +---# Adding advanced password brute force protection to "members" table... +ALTER TABLE {$db_prefix}members +ADD passwd_flood varchar(12) NOT NULL default ''; +---# + +/******************************************************************************/ +--- Adding some columns to moderation log +/******************************************************************************/ +---# Add the columns and the keys to log_actions ... +ALTER TABLE {$db_prefix}log_actions +ADD id_board smallint(5) unsigned NOT NULL default '0', +ADD id_topic mediumint(8) unsigned NOT NULL default '0', +ADD id_msg int(10) unsigned NOT NULL default '0', +ADD KEY id_board (id_board), +ADD KEY id_msg (id_msg); +---# + +---# Add the user log... +ALTER TABLE {$db_prefix}log_actions +ADD id_log tinyint(3) unsigned NOT NULL default '1', +ADD KEY id_log (id_log); +---# + +---# Update the information already in log_actions +---{ +$request = upgrade_query(" + SELECT COUNT(*) + FROM {$db_prefix}log_actions"); +list($totalActions) = mysql_fetch_row($request); +mysql_free_result($request); + +$_GET['m'] = !empty($_GET['m']) ? (int) $_GET['m'] : '0'; +$step_progress['total'] = $totalActions; +$step_progress['current'] = $_GET['m']; + +while ($_GET['m'] < $totalActions) +{ + nextSubStep($substep); + + $mrequest = upgrade_query(" + SELECT id_action, extra, id_board, id_topic, id_msg + FROM {$db_prefix}log_actions + LIMIT $_GET[m], 500"); + + while ($row = mysql_fetch_assoc($mrequest)) + { + if (!empty($row['id_board']) || !empty($row['id_topic']) || !empty($row['id_msg'])) + continue; + $row['extra'] = @unserialize($row['extra']); + // Corrupt? + $row['extra'] = is_array($row['extra']) ? $row['extra'] : array(); + if (!empty($row['extra']['board'])) + { + $board_id = (int) $row['extra']['board']; + unset($row['extra']['board']); + } + else + $board_id = '0'; + if (!empty($row['extra']['board_to']) && empty($board_id)) + { + $board_id = (int) $row['extra']['board_to']; + unset($row['extra']['board_to']); + } + + if (!empty($row['extra']['topic'])) + { + $topic_id = (int) $row['extra']['topic']; + unset($row['extra']['topic']); + if (empty($board_id)) + { + $trequest = upgrade_query(" + SELECT id_board + FROM {$db_prefix}topics + WHERE id_topic=$topic_id + LIMIT 1"); + if (mysql_num_rows($trequest)) + list($board_id) = mysql_fetch_row($trequest); + mysql_free_result($trequest); + } + } + else + $topic_id = '0'; + + if(!empty($row['extra']['message'])) + { + $msg_id = (int) $row['extra']['message']; + unset($row['extra']['message']); + if (empty($topic_id) || empty($board_id)) + { + $trequest = upgrade_query(" + SELECT id_board, id_topic + FROM {$db_prefix}messages + WHERE id_msg=$msg_id + LIMIT 1"); + if (mysql_num_rows($trequest)) + list($board_id, $topic_id) = mysql_fetch_row($trequest); + mysql_free_result($trequest); + } + } + else + $msg_id = '0'; + $row['extra'] = addslashes(serialize($row['extra'])); + upgrade_query("UPDATE {$db_prefix}log_actions SET id_board=$board_id, id_topic=$topic_id, id_msg=$msg_id, extra='$row[extra]' WHERE id_action=$row[id_action]"); + } + $_GET['m'] += 500; + $step_progress['current'] = $_GET['m']; +} +unset($_GET['m']); +---} +---# + +/******************************************************************************/ +--- Create a repository for the javascript files from Simple Machines... +/******************************************************************************/ + +---# Creating repository table ... +CREATE TABLE IF NOT EXISTS {$db_prefix}admin_info_files ( + id_file tinyint(4) unsigned NOT NULL auto_increment, + filename varchar(255) NOT NULL default '', + path varchar(255) NOT NULL default '', + parameters varchar(255) NOT NULL default '', + data text NOT NULL, + filetype varchar(255) NOT NULL default '', + PRIMARY KEY (id_file), + KEY filename (filename(30)) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Add in the files to get from Simple Machines... +INSERT IGNORE INTO {$db_prefix}admin_info_files + (id_file, filename, path, parameters) +VALUES + (1, 'current-version.js', '/smf/', 'version=%3$s'), + (2, 'detailed-version.js', '/smf/', 'language=%1$s&version=%3$s'), + (3, 'latest-news.js', '/smf/', 'language=%1$s&format=%2$s'), + (4, 'latest-packages.js', '/smf/', 'language=%1$s&version=%3$s'), + (5, 'latest-smileys.js', '/smf/', 'language=%1$s&version=%3$s'), + (6, 'latest-support.js', '/smf/', 'language=%1$s&version=%3$s'), + (7, 'latest-themes.js', '/smf/', 'language=%1$s&version=%3$s'); +---# + +---# Ensure that the table has the filetype column +ALTER TABLE {$db_prefix}admin_info_files +ADD filetype varchar(255) NOT NULL default ''; +---# + +---# Set the filetype for the files +UPDATE {$db_prefix}admin_info_files +SET filetype='text/javascript' +WHERE id_file IN (1,2,3,4,5,6,7); +---# + +---# Ensure that the files from Simple Machines get updated +UPDATE {$db_prefix}scheduled_tasks +SET next_time = UNIX_TIMESTAMP() +WHERE id_task = 7 +LIMIT 1; +---# + +/******************************************************************************/ +--- Adding new personal messaging functionality. +/******************************************************************************/ + +---# Adding personal message rules table... +CREATE TABLE IF NOT EXISTS {$db_prefix}pm_rules ( + id_rule int(10) unsigned NOT NULL auto_increment, + id_member int(10) unsigned NOT NULL default '0', + rule_name varchar(60) NOT NULL, + criteria text NOT NULL, + actions text NOT NULL, + delete_pm tinyint(3) unsigned NOT NULL default '0', + is_or tinyint(3) unsigned NOT NULL default '0', + PRIMARY KEY (id_rule), + KEY id_member (id_member), + KEY delete_pm (delete_pm) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Adding new message status columns... +ALTER TABLE {$db_prefix}members +ADD COLUMN new_pm tinyint(3) NOT NULL default '0'; + +ALTER TABLE {$db_prefix}members +ADD COLUMN pm_prefs mediumint(8) NOT NULL default '0'; + +ALTER TABLE {$db_prefix}pm_recipients +ADD COLUMN is_new tinyint(3) NOT NULL default '0'; +---# + +---# Set the new status to be correct.... +---{ +// Don't do this twice! +if (@$modSettings['smfVersion'] < '2.0') +{ + // Set all unread messages as new. + upgrade_query(" + UPDATE {$db_prefix}pm_recipients + SET is_new = 1 + WHERE is_read = 0"); + + // Also set members to have a new pm if they have any unread. + upgrade_query(" + UPDATE {$db_prefix}members + SET new_pm = 1 + WHERE unread_messages > 0"); +} +---} +---# + +---# Adding personal message tracking column... +ALTER TABLE {$db_prefix}personal_messages +ADD id_pm_head int(10) unsigned default '0' NOT NULL AFTER id_pm, +ADD INDEX id_pm_head (id_pm_head); +---# + +---# Adding personal message tracking column... +UPDATE {$db_prefix}personal_messages +SET id_pm_head = id_pm +WHERE id_pm_head = 0; +---# + +/******************************************************************************/ +--- Adding Open ID support. +/******************************************************************************/ + +---# Adding Open ID Assocation table... +CREATE TABLE IF NOT EXISTS {$db_prefix}openid_assoc ( + server_url text NOT NULL, + handle varchar(255) NOT NULL default '', + secret text NOT NULL, + issued int(10) NOT NULL default '0', + expires int(10) NOT NULL default '0', + assoc_type varchar(64) NOT NULL, + PRIMARY KEY (server_url(125), handle(125)), + KEY expires (expires) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Adding column to hold Open ID URL... +ALTER TABLE {$db_prefix}members +ADD openid_uri text NOT NULL; +---# + +/******************************************************************************/ +--- Adding paid subscriptions. +/******************************************************************************/ + +---# Creating subscriptions table... +CREATE TABLE IF NOT EXISTS {$db_prefix}subscriptions( + id_subscribe mediumint(8) unsigned NOT NULL auto_increment, + name varchar(60) NOT NULL default '', + description varchar(255) NOT NULL default '', + cost text NOT NULL, + length varchar(6) NOT NULL default '', + id_group smallint(5) NOT NULL default '0', + add_groups varchar(40) NOT NULL default '', + active tinyint(3) NOT NULL default '1', + repeatable tinyint(3) NOT NULL default '0', + allow_partial tinyint(3) NOT NULL default '0', + reminder tinyint(3) NOT NULL default '0', + email_complete text NOT NULL, + PRIMARY KEY (id_subscribe), + KEY active (active) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Creating log_subscribed table... +CREATE TABLE IF NOT EXISTS {$db_prefix}log_subscribed( + id_sublog int(10) unsigned NOT NULL auto_increment, + id_subscribe mediumint(8) unsigned NOT NULL default '0', + id_member int(10) NOT NULL default '0', + old_id_group smallint(5) NOT NULL default '0', + start_time int(10) NOT NULL default '0', + end_time int(10) NOT NULL default '0', + status tinyint(3) NOT NULL default '0', + payments_pending tinyint(3) NOT NULL default '0', + pending_details text NOT NULL, + reminder_sent tinyint(3) NOT NULL default '0', + vendor_ref varchar(255) NOT NULL default '', + PRIMARY KEY (id_sublog), + UNIQUE KEY id_subscribe (id_subscribe, id_member), + KEY end_time (end_time), + KEY reminder_sent (reminder_sent), + KEY payments_pending (payments_pending), + KEY id_member (id_member) +) ENGINE=MyISAM{$db_collation}; +---# + +---# Clean up any pre-2.0 mod settings. +UPDATE {$db_prefix}settings +SET variable = 'paid_currency_code' +WHERE variable = 'currency_code'; + +UPDATE {$db_prefix}settings +SET variable = 'paid_currency_symbol' +WHERE variable = 'currency_symbol'; + +DELETE FROM {$db_prefix}settings +WHERE variable = 'currency_code' + OR variable = 'currency_symbol'; +---# + +---# Clean up any pre-2.0 mod settings (part 2). +---{ +$request = upgrade_query(" + SHOW COLUMNS + FROM {$db_prefix}subscriptions"); +$new_cols = array('repeatable', 'reminder', 'email_complete', 'allow_partial'); +$new_cols = array_flip($new_cols); +while ($request && $row = mysql_fetch_row($request)) +{ + $row[0] = strtolower($row[0]); + if (isset($new_cols[$row[0]])) + unset($new_cols[$row[0]]); +} +if ($request) + mysql_free_result($request); + +if (isset($new_cols['repeatable'])) + upgrade_query(" + ALTER TABLE {$db_prefix}subscriptions + ADD COLUMN Repeatable tinyint(3) NOT NULL default '0'"); +if (isset($new_cols['reminder'])) + upgrade_query(" + ALTER TABLE {$db_prefix}subscriptions + ADD COLUMN reminder tinyint(3) NOT NULL default '0'"); +if (isset($new_cols['email_complete'])) + upgrade_query(" + ALTER TABLE {$db_prefix}subscriptions + ADD COLUMN email_complete text NOT NULL"); +if (isset($new_cols['allowpartial'])) + upgrade_query(" + ALTER TABLE {$db_prefix}subscriptions + ADD COLUMN allow_partial tinyint(3) NOT NULL default '0'"); + +$request = upgrade_query(" + SHOW COLUMNS + FROM {$db_prefix}log_subscribed"); +$new_cols = array('reminder_sent', 'vendor_ref', 'payments_pending', 'pending_details'); +$new_cols = array_flip($new_cols); +while ($request && $row = mysql_fetch_row($request)) +{ + if (isset($new_cols[$row[0]])) + unset($new_cols[$row[0]]); +} +if ($request) + mysql_free_result($request); + +if (isset($new_cols['reminder_sent'])) + upgrade_query(" + ALTER TABLE {$db_prefix}log_subscribed + ADD COLUMN reminder_sent tinyint(3) NOT NULL default '0'"); +if (isset($new_cols['vendor_ref'])) + upgrade_query(" + ALTER TABLE {$db_prefix}log_subscribed + ADD COLUMN vendor_ref varchar(255) NOT NULL default ''"); +if (isset($new_cols['payments_pending'])) + upgrade_query(" + ALTER TABLE {$db_prefix}log_subscribed + ADD COLUMN payments_pending tinyint(3) NOT NULL default '0'"); +if (isset($new_cols['pending_details'])) +{ + upgrade_query(" + UPDATE {$db_prefix}log_subscribed + SET status = 0 + WHERE status = 1"); + upgrade_query(" + UPDATE {$db_prefix}log_subscribed + SET status = 1 + WHERE status = 2"); + upgrade_query(" + ALTER TABLE {$db_prefix}log_subscribed + ADD COLUMN pending_details text NOT NULL"); +} +---} +---# + +---# Confirming paid subscription keys are in place ... +ALTER TABLE {$db_prefix}log_subscribed +ADD KEY reminder_sent (reminder_sent), +ADD KEY end_time (end_time), +ADD KEY payments_pending (payments_pending), +ADD KEY status (status); +---# + +/******************************************************************************/ +--- Adding weekly maintenance task. +/******************************************************************************/ + +---# Adding scheduled task... +INSERT IGNORE INTO {$db_prefix}scheduled_tasks (next_time, time_offset, time_regularity, time_unit, disabled, task) VALUES (0, 0, 1, 'w', 0, 'weekly_maintenance'); +---# + +/******************************************************************************/ +--- Adding log pruning. +/******************************************************************************/ + +---# Adding pruning option... +INSERT IGNORE INTO {$db_prefix}settings (variable, value) VALUES ('pruningOptions', '30,180,180,180,30,0'); +---# + +/******************************************************************************/ +--- Adding restore topic from recycle. +/******************************************************************************/ + +---# Adding restore from recycle feature... +ALTER TABLE {$db_prefix}topics +ADD COLUMN id_previous_board smallint(5) NOT NULL default '0', +ADD COLUMN id_previous_topic mediumint(8) NOT NULL default '0'; +---# + +/******************************************************************************/ +--- Providing more room for apf options. +/******************************************************************************/ + +---# Changing field_options column to a larger field type... +ALTER TABLE {$db_prefix}custom_fields +CHANGE field_options field_options text NOT NULL; +---# + +/******************************************************************************/ +--- Providing more room for ignoring boards. +/******************************************************************************/ + +---# Changing ignore_boards column to a larger field type... +ALTER TABLE {$db_prefix}members +CHANGE ignore_boards ignore_boards text NOT NULL; +---# + +/******************************************************************************/ +--- Allow for longer calendar event/holiday titles. +/******************************************************************************/ + +---# Changing event title column to a larger field type... +ALTER TABLE {$db_prefix}calendar +CHANGE title title varchar(255) NOT NULL default ''; +---# + +---# Changing holidays title column to a larger field type... +ALTER TABLE {$db_prefix}calendar_holidays +CHANGE title title varchar(255) NOT NULL default ''; +---# + +/******************************************************************************/ +--- Adding extra columns to polls. +/******************************************************************************/ + +---# Adding reset poll timestamp and guest voters counter... +ALTER TABLE {$db_prefix}polls +ADD COLUMN reset_poll int(10) unsigned NOT NULL default '0' AFTER guest_vote, +ADD COLUMN num_guest_voters int(10) unsigned NOT NULL default '0' AFTER guest_vote; +---# + +---# Fixing guest voter tallys on existing polls... +---{ +$request = upgrade_query(" + SELECT p.id_poll, count(lp.id_member) as guest_voters + FROM {$db_prefix}polls AS p + LEFT JOIN {$db_prefix}log_polls AS lp ON (lp.id_poll = p.id_poll AND lp.id_member = 0) + WHERE lp.id_member = 0 + AND p.num_guest_voters = 0 + GROUP BY p.id_poll"); + +while ($request && $row = $smcFunc['db_fetch_assoc']($request)) + upgrade_query(" + UPDATE {$db_prefix}polls + SET num_guest_voters = ". $row['guest_voters']. " + WHERE id_poll = " . $row['id_poll'] . " + AND num_guest_voters = 0"); +---} +---# + +/******************************************************************************/ +--- Changing all tinytext columns to varchar(255). +/******************************************************************************/ + +---# Changing all tinytext columns to varchar(255)... +---{ +// The array holding all the changes. +$nameChanges = array( + 'admin_info_files' => array( + 'filename' => 'filename filename varchar(255) NOT NULL default \'\'', + 'path' => 'path path varchar(255) NOT NULL default \'\'', + 'parameters' => 'parameters parameters varchar(255) NOT NULL default \'\'', + 'filetype' => 'filetype filetype varchar(255) NOT NULL default \'\'', + ), + 'attachments' => array( + 'filename' => 'filename filename varchar(255) NOT NULL default \'\'', + ), + 'ban_groups' => array( + 'reason' => 'reason reason varchar(255) NOT NULL default \'\'', + ), + 'ban_items' => array( + 'hostname' => 'hostname hostname varchar(255) NOT NULL default \'\'', + 'email_address' => 'email_address email_address varchar(255) NOT NULL default \'\'', + ), + 'boards' => array( + 'name' => 'name name varchar(255) NOT NULL default \'\'', + ), + 'categories' => array( + 'name' => 'name name varchar(255) NOT NULL default \'\'', + ), + 'custom_fields' => array( + 'field_desc' => 'field_desc field_desc varchar(255) NOT NULL default \'\'', + 'mask' => 'mask mask varchar(255) NOT NULL default \'\'', + 'default_value' => 'default_value default_value varchar(255) NOT NULL default \'\'', + ), + 'log_banned' => array( + 'email' => 'email email varchar(255) NOT NULL default \'\'', + ), + 'log_comments' => array( + 'recipient_name' => 'recipient_name recipient_name varchar(255) NOT NULL default \'\'', + ), + 'log_errors' => array( + 'file' => 'file file varchar(255) NOT NULL default \'\'', + ), + 'log_member_notices' => array( + 'subject' => 'subject subject varchar(255) NOT NULL default \'\'', + ), + 'log_packages' => array( + 'filename' => 'filename filename varchar(255) NOT NULL default \'\'', + 'package_id' => 'package_id package_id varchar(255) NOT NULL default \'\'', + 'name' => 'name name varchar(255) NOT NULL default \'\'', + 'version' => 'version version varchar(255) NOT NULL default \'\'', + 'member_installed' => 'member_installed member_installed varchar(255) NOT NULL default \'\'', + 'member_removed' => 'member_removed member_removed varchar(255) NOT NULL default \'\'', + 'themes_installed' => 'themes_installed themes_installed varchar(255) NOT NULL default \'\'', + ), + 'log_reported' => array( + 'membername' => 'membername membername varchar(255) NOT NULL default \'\'', + 'subject' => 'subject subject varchar(255) NOT NULL default \'\'', + ), + 'log_reported_comments' => array( + 'membername' => 'membername membername varchar(255) NOT NULL default \'\'', + 'comment' => 'comment comment varchar(255) NOT NULL default \'\'', + ), + 'log_spider_hits' => array( + 'url' => 'url url varchar(255) NOT NULL default \'\'', + ), + 'log_subscribed' => array( + 'vendor_ref' => 'vendor_ref vendor_ref varchar(255) NOT NULL default \'\'', + ), + 'mail_queue' => array( + 'recipient' => 'recipient recipient varchar(255) NOT NULL default \'\'', + 'subject' => 'subject subject varchar(255) NOT NULL default \'\'', + ), + 'membergroups' => array( + 'stars' => 'stars stars varchar(255) NOT NULL default \'\'', + ), + 'members' => array( + 'lngfile' => 'lngfile lngfile varchar(255) NOT NULL default \'\'', + 'real_name' => 'real_name real_name varchar(255) NOT NULL default \'\'', + 'pm_ignore_list' => 'pm_ignore_list pm_ignore_list varchar(255) NOT NULL default \'\'', + 'email_address' => 'email_address email_address varchar(255) NOT NULL default \'\'', + 'personal_text' => 'personal_text personal_text varchar(255) NOT NULL default \'\'', + 'website_title' => 'website_title website_title varchar(255) NOT NULL default \'\'', + 'website_url' => 'website_url website_url varchar(255) NOT NULL default \'\'', + 'location' => 'location location varchar(255) NOT NULL default \'\'', + 'icq' => 'icq icq varchar(255) NOT NULL default \'\'', + 'aim' => 'aim aim varchar(255) NOT NULL default \'\'', + 'msn' => 'msn msn varchar(255) NOT NULL default \'\'', + 'avatar' => 'avatar avatar varchar(255) NOT NULL default \'\'', + 'usertitle' => 'usertitle usertitle varchar(255) NOT NULL default \'\'', + 'member_ip' => 'member_ip member_ip varchar(255) NOT NULL default \'\'', + 'member_ip2' => 'member_ip2 member_ip2 varchar(255) NOT NULL default \'\'', + 'secret_question' => 'secret_question secret_question varchar(255) NOT NULL default \'\'', + 'additional_groups' => 'additional_groups additional_groups varchar(255) NOT NULL default \'\'', + ), + 'messages' => array( + 'subject' => 'subject subject varchar(255) NOT NULL default \'\'', + 'poster_name' => 'poster_name poster_name varchar(255) NOT NULL default \'\'', + 'poster_email' => 'poster_email poster_email varchar(255) NOT NULL default \'\'', + 'poster_ip' => 'poster_ip poster_ip varchar(255) NOT NULL default \'\'', + 'modified_name' => 'modified_name modified_name varchar(255) NOT NULL default \'\'', + ), + 'openid_assoc' => array( + 'handle' => 'handle handle varchar(255) NOT NULL default \'\'', + ), + 'package_servers' => array( + 'name' => 'name name varchar(255) NOT NULL default \'\'', + 'url' => 'url url varchar(255) NOT NULL default \'\'', + ), + 'permission_profiles' => array( + 'profile_name' => 'profile_name profile_name varchar(255) NOT NULL default \'\'', + ), + 'personal_messages' => array( + 'from_name' => 'from_name from_name varchar(255) NOT NULL default \'\'', + 'subject' => 'subject subject varchar(255) NOT NULL default \'\'', + ), + 'polls' => array( + 'question' => 'question question varchar(255) NOT NULL default \'\'', + 'poster_name' => 'poster_name poster_name varchar(255) NOT NULL default \'\'', + ), + 'poll_choices' => array( + 'label' => 'label label varchar(255) NOT NULL default \'\'', + ), + 'settings' => array( + 'variable' => 'variable variable varchar(255) NOT NULL default \'\'', + ), + 'spiders' => array( + 'spider_name' => 'spider_name spider_name varchar(255) NOT NULL default \'\'', + 'user_agent' => 'user_agent user_agent varchar(255) NOT NULL default \'\'', + 'ip_info' => 'ip_info ip_info varchar(255) NOT NULL default \'\'', + ), + 'subscriptions' => array( + 'description' => 'description description varchar(255) NOT NULL default \'\'', + ), + 'themes' => array( + 'variable' => 'variable variable varchar(255) NOT NULL default \'\'', + ), +); + +$_GET['ren_col'] = isset($_GET['ren_col']) ? (int) $_GET['ren_col'] : 0; +$step_progress['name'] = 'Changing tinytext columns to varchar(255)'; +$step_progress['current'] = $_GET['ren_col']; +$step_progress['total'] = count($nameChanges); + +$count = 0; +// Now do every table... +foreach ($nameChanges as $table_name => $table) +{ + // Already done this? + $count++; + if ($_GET['ren_col'] > $count) + continue; + $_GET['ren_col'] = $count; + + // Check the table exists! + $request = upgrade_query(" + SHOW TABLES + LIKE '{$db_prefix}$table_name'"); + if (mysql_num_rows($request) == 0) + { + mysql_free_result($request); + continue; + } + mysql_free_result($request); + + // Converting is intensive, so make damn sure that we need to do it. + $request = upgrade_query(" + SHOW FIELDS + FROM `{$db_prefix}$table_name`"); + $tinytextColumns = array(); + while($row = mysql_fetch_assoc($request)) + { + // Tinytext detected so store column name. + if ($row['Type'] == 'tinytext') + $tinytextColumns[$row['Field']] = $row['Field']; + } + mysql_free_result($request); + + // Check each column! + $actualChanges = array(); + foreach ($table as $colname => $coldef) + { + // Column was not detected as tinytext so skip it + // Either it was already converted or was changed eg text (so do not break it) + if (!isset($tinytextColumns[$colname])) + continue; + + $change = array( + 'table' => $table_name, + 'name' => $colname, + 'type' => 'column', + 'method' => 'change_remove', + 'text' => 'CHANGE ' . $coldef, + ); + if (protected_alter($change, $substep, true) == false) + $actualChanges[] = ' CHANGE COLUMN ' . $coldef; + } + + // Do the query - if it needs doing. + if (!empty($actualChanges)) + { + $change = array( + 'table' => $table_name, + 'name' => 'na', + 'type' => 'table', + 'method' => 'full_change', + 'text' => implode(', ', $actualChanges), + ); + + // Here we go - hold on! + protected_alter($change, $substep); + } + + // Update where we are! + $step_progress['current'] = $_GET['ren_col']; +} + +// All done! +unset($_GET['ren_col']); +---} +---# + +/******************************************************************************/ +--- Adding new personal message setting. +/******************************************************************************/ + +---# Adding column that stores the PM receiving setting... +ALTER TABLE {$db_prefix}members +ADD COLUMN pm_receive_from tinyint(4) unsigned NOT NULL default '1'; +---# + +---# Enable the buddy and ignore lists if we have not done so thus far... +---{ + +// Don't do this if we've done this already. +if (empty($modSettings['dont_repeat_buddylists'])) +{ + // Make sure the pm_receive_from column has the right default value - early adoptors might have a '0' set here. + upgrade_query(" + ALTER TABLE {$db_prefix}members + CHANGE pm_receive_from pm_receive_from tinyint(3) unsigned NOT NULL default '1'"); + + // Update previous ignore lists if they're set to ignore all. + upgrade_query(" + UPDATE {$db_prefix}members + SET pm_receive_from = 3, pm_ignore_list = '' + WHERE pm_ignore_list = '*'"); + + // Ignore posts made by ignored users by default. + upgrade_query(" + REPLACE INTO {$db_prefix}themes + (id_member, id_theme, variable, value) + VALUES + (-1, 1, 'posts_apply_ignore_list', '1')"); + + // Enable buddy and ignore lists, and make sure not to skip this step next time we run this. + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('enable_buddylist', '1'), + ('dont_repeat_buddylists', '1')"); +} + +// And yet, and yet... We might have a small hiccup here... +if (!empty($modSettings['dont_repeat_buddylists']) && !isset($modSettings['enable_buddylist'])) +{ + // Correct RC3 adopters setting here... + if (isset($modSettings['enable_buddylists'])) + { + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('enable_buddylist', '" . $modSettings['enable_buddylists'] . "')"); + } + else + { + // This should never happen :) + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('enable_buddylist', '1')"); + } +} + +---} +---# + +/******************************************************************************/ +--- Adding settings for attachments and avatars. +/******************************************************************************/ + +---# Add new security settings for attachments and avatars... +---{ + +// Don't do this if we've done this already. +if (!isset($modSettings['attachment_image_reencode'])) +{ + // Enable image re-encoding by default. + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('attachment_image_reencode', '1')"); +} +if (!isset($modSettings['attachment_image_paranoid'])) +{ + // Disable draconic checks by default. + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('attachment_image_paranoid', '0')"); +} +if (!isset($modSettings['avatar_reencode'])) +{ + // Enable image re-encoding by default. + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('avatar_reencode', '1')"); +} +if (!isset($modSettings['avatar_paranoid'])) +{ + // Disable draconic checks by default. + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('avatar_paranoid', '0')"); +} + +---} +---# + +---# Add other attachment settings... +---{ +if (!isset($modSettings['attachment_thumb_png'])) +{ + // Make image attachment thumbnail as PNG by default. + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('attachment_thumb_png', '1')"); +} + +---} +---# + +/******************************************************************************/ +--- Installing new default theme... +/******************************************************************************/ + +---# Installing theme settings... +---{ +// This is Grudge's secret "I'm not a developer" theme install code - keep this quiet ;) + +// Firstly, I'm going out of my way to not do this twice! +if ((!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] <= '2.0 RC5' || $modSettings['smfVersion'] === '2.0 a') && empty($modSettings['dont_repeat_theme_core'])) +{ + // Check it's not already here, just in case. + $theme_request = upgrade_query(" + SELECT id_theme + FROM {$db_prefix}themes + WHERE variable = 'theme_dir' + AND value LIKE '%core'"); + // Only do the upgrade if it doesn't find the theme already. + if (mysql_num_rows($theme_request) == 0) + { + // Try to get some settings from the current default theme. + $request = upgrade_query(" + SELECT t1.value AS theme_dir, t2.value AS theme_url, t3.value AS images_url + FROM ({$db_prefix}themes AS t1, {$db_prefix}themes AS t2, {$db_prefix}themes AS t3) + WHERE t1.id_theme = 1 + AND t1.id_member = 0 + AND t1.variable = 'theme_dir' + AND t2.id_theme = 1 + AND t2.id_member = 0 + AND t2.variable = 'theme_url' + AND t3.id_theme = 1 + AND t3.id_member = 0 + AND t3.variable = 'images_url' + LIMIT 1"); + if (mysql_num_rows($request) != 0) + { + $curve = mysql_fetch_assoc($request); + + if (substr_count($curve['theme_dir'], 'default') === 1) + $core['theme_dir'] = strtr($curve['theme_dir'], array('default' => 'core')); + if (substr_count($curve['theme_url'], 'default') === 1) + $core['theme_url'] = strtr($curve['theme_url'], array('default' => 'core')); + if (substr_count($curve['images_url'], 'default') === 1) + $core['images_url'] = strtr($curve['images_url'], array('default' => 'core')); + } + mysql_free_result($request); + + if (!isset($core['theme_dir'])) + $core['theme_dir'] = addslashes($GLOBALS['boarddir']) . '/Themes/core'; + if (!isset($core['theme_url'])) + $core['theme_url'] = $GLOBALS['boardurl'] . '/Themes/core'; + if (!isset($core['images_url'])) + $core['images_url'] = $GLOBALS['boardurl'] . '/Themes/core/images'; + + // Get an available id_theme first... + $request = upgrade_query(" + SELECT MAX(id_theme) + 1 + FROM {$db_prefix}themes"); + list ($id_core_theme) = mysql_fetch_row($request); + mysql_free_result($request); + + // Insert the core theme into the tables. + upgrade_query(" + INSERT INTO {$db_prefix}themes + (id_member, id_theme, variable, value) + VALUES + (0, $id_core_theme, 'name', 'Core Theme'), + (0, $id_core_theme, 'theme_url', '$core[theme_url]'), + (0, $id_core_theme, 'images_url', '$core[images_url]'), + (0, $id_core_theme, 'theme_dir', '$core[theme_dir]')"); + + // Update the name of the default theme in the database. + upgrade_query(" + UPDATE {$db_prefix}themes + SET value = 'SMF Default Theme - Curve' + WHERE id_theme = 1 + AND variable = 'name'"); + + // If known themes aren't set, let's just pick all themes available. + if (empty($modSettings['knownThemes'])) + { + $request = upgrade_query(" + SELECT DISTINCT id_theme + FROM {$db_prefix}themes"); + $themes = array(); + while ($row = mysql_fetch_assoc($request)) + $themes[] = $row['id_theme']; + $modSettings['knownThemes'] = implode(',', $themes); + upgrade_query(" + UPDATE {$db_prefix}settings + SET value = '$modSettings[knownThemes]' + WHERE variable = 'knownThemes'"); + } + + // Known themes. + $allThemes = explode(',', $modSettings['knownThemes']); + $allThemes[] = $id_core_theme; + $newSettings = array(); + $newSettings[] = "('knownThemes', '" . implode(',', $allThemes) . "')"; + + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + " . implode(', ', $newSettings)); + + // The other themes used to use core as their base theme. + if (isset($core['theme_dir']) && isset($core['theme_url'])) + { + $coreBasedThemes = array_diff($allThemes, array(1)); + + // Exclude the themes that already have a base_theme_dir. + $request = upgrade_query(" + SELECT DISTINCT id_theme + FROM {$db_prefix}themes + WHERE variable = 'base_theme_dir'"); + while ($row = mysql_fetch_assoc($request)) + $coreBasedThemes = array_diff($coreBasedThemes, array($row['id_theme'])); + mysql_free_result($request); + + // Only base themes if there are templates that need a fall-back. + $insertRows = array(); + $request = upgrade_query(" + SELECT id_theme, value AS theme_dir + FROM {$db_prefix}themes + WHERE id_theme IN (" . implode(', ', $coreBasedThemes) . ") + AND id_member = 0 + AND variable = 'theme_dir'"); + while ($row = mysql_fetch_assoc($request)) + { + if (!file_exists($row['theme_dir'] . '/BoardIndex.template.php') || !file_exists($row['theme_dir'] . '/Display.template.php') || !file_exists($row['theme_dir'] . '/index.template.php') || !file_exists($row['theme_dir'] . '/MessageIndex.template.php') || !file_exists($row['theme_dir'] . '/Settings.template.php')) + { + $insertRows[] = "(0, $row[id_theme], 'base_theme_dir', '" . addslashes($core['theme_dir']) . "')"; + $insertRows[] = "(0, $row[id_theme], 'base_theme_url', '" . addslashes($core['theme_url']) . "')"; + } + } + mysql_free_result($request); + + if (!empty($insertRows)) + upgrade_query(" + INSERT IGNORE INTO {$db_prefix}themes + (id_member, id_theme, variable, value) + VALUES + " . implode(', + ', $insertRows)); + } + } + mysql_free_result($theme_request); + + // This ain't running twice either. + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('dont_repeat_theme_core', '1')"); +} + +---} +---# + +/******************************************************************************/ +--- Installing new smileys sets... +/******************************************************************************/ + +---# Installing new smiley sets... +---{ +// Don't do this twice! +if (empty($modSettings['installed_new_smiley_sets_20'])) +{ + // First, the entries. + upgrade_query(" + UPDATE {$db_prefix}settings + SET value = CONCAT(value, ',aaron,akyhne') + WHERE variable = 'smiley_sets_known'"); + + // Second, the names. + upgrade_query(" + UPDATE {$db_prefix}settings + SET value = CONCAT(value, '\nAaron\nAkyhne') + WHERE variable = 'smiley_sets_names'"); + + // This ain't running twice either. + upgrade_query(" + REPLACE INTO {$db_prefix}settings + (variable, value) + VALUES + ('installed_new_smiley_sets_20', '1')"); +} +---} +---# + +/******************************************************************************/ +--- Adding new indexes to the topics table. +/******************************************************************************/ + +---# Adding index member_started... +ALTER TABLE {$db_prefix}topics +ADD INDEX member_started (id_member_started, id_board); +---# + +---# Adding index last_message_sticky... +ALTER TABLE {$db_prefix}topics +ADD INDEX last_message_sticky (id_board, is_sticky, id_last_msg); +---# + +---# Adding index board_news... +ALTER TABLE {$db_prefix}topics +ADD INDEX board_news (id_board, id_first_msg); +---# + +/******************************************************************************/ +--- Adding new indexes to members table. +/******************************************************************************/ + +---# Adding index on total_time_logged_in... +ALTER TABLE {$db_prefix}members +ADD INDEX total_time_logged_in (total_time_logged_in); +---# + +---# Adding index on id_theme... +ALTER TABLE {$db_prefix}members +ADD INDEX id_theme (id_theme); +---# + +---# Dropping index on real_name(30) ... +---{ +// Detect existing index with limited length +$request = upgrade_query(" + SHOW INDEXES + FROM {$db_prefix}members" +); + +// Drop the existing index before we recreate it. +while ($row = mysql_fetch_assoc($request)) +{ + if ($row['Key_name'] === 'real_name' && $row['Sub_part'] == 30) + { + upgrade_query(" + ALTER TABLE {$db_prefix}members + DROP INDEX real_name" + ); + break; + } +} + +mysql_free_result($request); +---} +---# + +---# Adding index on real_name... +ALTER TABLE {$db_prefix}members +ADD INDEX real_name (real_name); +---# + +---# Dropping index member_name(30)... +---{ +// Detect existing index with limited length +$request = upgrade_query(" + SHOW INDEXES + FROM {$db_prefix}members" +); + +// Drop the existing index before we recreate it. +while ($row = mysql_fetch_assoc($request)) +{ + if ($row['Key_name'] === 'member_name' && $row['Sub_part'] == 30) + { + upgrade_query(" + ALTER TABLE {$db_prefix}members + DROP INDEX member_name" + ); + break; + } +} + +mysql_free_result($request); + +---} +---# + +---# Adding index on member_name... +ALTER TABLE {$db_prefix}members +ADD INDEX member_name (member_name); +---# + +/******************************************************************************/ +--- Adding new indexes to messages table. +/******************************************************************************/ + +---# Adding index id_member_msg... +ALTER TABLE {$db_prefix}messages +ADD INDEX id_member_msg (id_member, approved, id_msg); +---# + +---# Adding index current_topic... +ALTER TABLE {$db_prefix}messages +ADD INDEX current_topic (id_topic, id_msg, id_member, approved); +---# + +---# Adding index related_ip... +ALTER TABLE {$db_prefix}messages +ADD INDEX related_ip (id_member, poster_ip, id_msg); +---# + +/******************************************************************************/ +--- Adding new indexes to attachments table. +/******************************************************************************/ + +---# Adding index on attachment_type... +ALTER TABLE {$db_prefix}attachments +ADD INDEX attachment_type (attachment_type); +---# + +/******************************************************************************/ +--- Dropping unnecessary indexes... +/******************************************************************************/ + +---# Removing index on hits... +ALTER TABLE {$db_prefix}log_activity +DROP INDEX hits; +---# + +/******************************************************************************/ +--- Adding extra columns to reported post comments +/******************************************************************************/ + +---# Adding email address and member ip columns... +ALTER TABLE {$db_prefix}log_reported_comments +ADD COLUMN member_ip varchar(255) NOT NULL default '' AFTER membername, +ADD COLUMN email_address varchar(255) NOT NULL default '' AFTER membername; +---# + +/******************************************************************************/ +--- Adjusting group types. +/******************************************************************************/ + +---# Fixing the group types. +---{ +// Get the admin group type. +$request = upgrade_query(" + SELECT group_type + FROM {$db_prefix}membergroups + WHERE id_group = 1 + LIMIT 1"); +list ($admin_group_type) = mysql_fetch_row($request); +mysql_free_result($request); + +// Not protected means we haven't updated yet! +if ($admin_group_type != 1) +{ + // Increase by one. + upgrade_query(" + UPDATE {$db_prefix}membergroups + SET group_type = group_type + 1 + WHERE group_type > 0"); +} +---} +---# + +---# Changing the group type for Administrator group. +UPDATE {$db_prefix}membergroups +SET group_type = 1 +WHERE id_group = 1; +---# + +/******************************************************************************/ +--- Final clean up... +/******************************************************************************/ + +---# Sorting the boards... +ALTER TABLE {$db_prefix}categories +ORDER BY cat_order; + +ALTER TABLE {$db_prefix}boards +ORDER BY board_order; +---#