/** * Loads article previews for display with the portal index template */ public function action_sportal_index() { global $context, $modSettings; // Showing articles on the index page? if (!empty($modSettings['sp_articles_index'])) { require_once SUBSDIR . '/PortalArticle.subs.php'; $context['sub_template'] = 'portal_index'; // Set up the pages $total_articles = sportal_get_articles_count(); $total = min($total_articles, !empty($modSettings['sp_articles_index_total']) ? $modSettings['sp_articles_index_total'] : 20); $per_page = min($total, !empty($modSettings['sp_articles_index_per_page']) ? $modSettings['sp_articles_index_per_page'] : 5); $start = !empty($_REQUEST['articles']) ? (int) $_REQUEST['articles'] : 0; if ($total > $per_page) { $context['article_page_index'] = constructPageIndex($context['portal_url'] . '?articles=%1$d', $start, $total, $per_page, true); } // If we have some articles require_once SUBSDIR . '/PortalArticle.subs.php'; $context['articles'] = sportal_get_articles(0, true, true, 'spa.id_article DESC', 0, $per_page, $start); foreach ($context['articles'] as $article) { if (empty($modSettings['sp_articles_length']) && ($cutoff = Util::strpos($article['body'], '[cutoff]')) !== false) { $article['body'] = Util::substr($article['body'], 0, $cutoff); if ($article['type'] === 'bbc') { require_once SUBSDIR . '/Post.subs.php'; preparsecode($article['body']); } } $context['articles'][$article['id']]['preview'] = sportal_parse_content($article['body'], $article['type'], 'return'); $context['articles'][$article['id']]['date'] = htmlTime($article['date']); // Just want a shorter look on the index page if (!empty($modSettings['sp_articles_length'])) { $context['articles'][$article['id']]['preview'] = Util::shorten_html($context['articles'][$article['id']]['preview'], $modSettings['sp_articles_length']); } } } }
/** * Load all articles for selection */ public function action_sportal_articles() { global $context, $scripturl, $txt, $modSettings; // Set up for pagination $total_articles = sportal_get_articles_count(); $per_page = min($total_articles, !empty($modSettings['sp_articles_per_page']) ? $modSettings['sp_articles_per_page'] : 10); $start = !empty($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0; if ($total_articles > $per_page) { $context['page_index'] = constructPageIndex($scripturl . '?action=portal;sa=articles;start=%1$d', $start, $total_articles, $per_page, true); } // Fetch the article page $context['articles'] = sportal_get_articles(0, true, true, 'spa.id_article DESC', 0, $per_page, $start); foreach ($context['articles'] as $article) { // Want to cut this one a bit short? if (($cutoff = Util::strpos($article['body'], '[cutoff]')) !== false) { $article['body'] = Util::substr($article['body'], 0, $cutoff); if ($article['type'] === 'bbc') { require_once SUBSDIR . '/Post.subs.php'; preparsecode($article['body']); } } $context['articles'][$article['id']]['preview'] = sportal_parse_content($article['body'], $article['type'], 'return'); $context['articles'][$article['id']]['date'] = htmlTime($article['date']); } $context['linktree'][] = array('url' => $scripturl . '?action=portal;sa=articles', 'name' => $txt['sp-articles']); $context['page_title'] = $txt['sp-articles']; $context['sub_template'] = 'view_articles'; }
/** * View a specific category, showing all articles it contains */ public function action_sportal_category() { global $context, $scripturl, $modSettings; // Basic article support require_once SUBSDIR . '/PortalArticle.subs.php'; $category_id = !empty($_REQUEST['category']) ? $_REQUEST['category'] : 0; if (is_int($category_id)) { $category_id = (int) $category_id; } else { $category_id = Util::htmlspecialchars($category_id, ENT_QUOTES); } $context['category'] = sportal_get_categories($category_id, true, true); if (empty($context['category']['id'])) { fatal_lang_error('error_sp_category_not_found', false); } // Set up the pages $total_articles = sportal_get_articles_in_cat_count($context['category']['id']); $per_page = min($total_articles, !empty($modSettings['sp_articles_per_page']) ? $modSettings['sp_articles_per_page'] : 10); $start = !empty($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0; if ($total_articles > $per_page) { $context['page_index'] = constructPageIndex($context['category']['href'] . ';start=%1$d', $start, $total_articles, $per_page, true); } // Load the articles in this category $context['articles'] = sportal_get_articles(0, true, true, 'spa.id_article DESC', $context['category']['id'], $per_page, $start); foreach ($context['articles'] as $article) { // Cut me mick if (($cutoff = Util::strpos($article['body'], '[cutoff]')) !== false) { $article['body'] = Util::substr($article['body'], 0, $cutoff); if ($article['type'] === 'bbc') { require_once SUBSDIR . '/Post.subs.php'; preparsecode($article['body']); } } $context['articles'][$article['id']]['preview'] = sportal_parse_content($article['body'], $article['type'], 'return'); $context['articles'][$article['id']]['date'] = htmlTime($article['date']); } $context['linktree'][] = array('url' => $scripturl . '?category=' . $context['category']['category_id'], 'name' => $context['category']['name']); $context['page_title'] = $context['category']['name']; $context['sub_template'] = 'view_category'; }
/** * Set merge options and do the actual merge of two or more topics. * * the merge options screen: * * shows topics to be merged and allows to set some merge options. * * is accessed by ?action=mergetopics;sa=options.and can also internally be called by action_quickmod(). * * uses 'merge_extra_options' sub template of the MergeTopics template. * * the actual merge: * * is accessed with ?action=mergetopics;sa=execute. * * updates the statistics to reflect the merge. * * logs the action in the moderation log. * * sends a notification is sent to all users monitoring this topic. * * redirects to ?action=mergetopics;sa=done. * * @param int[] $topics = array() of topic ids */ public function action_mergeExecute($topics = array()) { global $user_info, $txt, $context, $scripturl, $modSettings; $db = database(); // Check the session. checkSession('request'); require_once SUBSDIR . '/Topic.subs.php'; require_once SUBSDIR . '/Post.subs.php'; // Handle URLs from action_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 = !empty($user_info['mod_cache']['ap']) ? $user_info['mod_cache']['ap'] : boardsAllowedTo('approve_posts'); } // Get info about the topics and polls that will be merged. $request = $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 ($db->num_rows($request) < 2) { fatal_lang_error('no_topic_id'); } $num_views = 0; $is_sticky = 0; $boardTotals = array(); $topic_data = array(); $boards = array(); $polls = array(); $firstTopic = 0; while ($row = $db->fetch_assoc($request)) { // Make a note for the board counts... if (!isset($boardTotals[$row['id_board']])) { $boardTotals[$row['id_board']] = array('num_posts' => 0, 'num_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']]['num_topics']++; } $boardTotals[$row['id_board']]['unapproved_posts'] += $row['unapproved_posts']; $boardTotals[$row['id_board']]['num_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' => standardTime($row['time_started']), 'html_time' => htmlTime($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'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_started'] . '">' . $row['name_started'] . '</a>'), 'updated' => array('time' => standardTime($row['time_updated']), 'html_time' => htmlTime($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'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_updated'] . '">' . $row['name_updated'] . '</a>')); $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']); } $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 action_mergeExecute were set, so this must've been an internal call. if (!empty($topics)) { isAllowedTo('merge_any', $boards); loadTemplate('MergeTopics'); } // 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'); } require_once SUBSDIR . '/Boards.subs.php'; // Make sure they can see all boards.... $query_boards = array('boards' => $boards); if (!in_array(0, $merge_boards)) { $query_boards['boards'] = array_merge($query_boards['boards'], $merge_boards); } // Saved in a variable to (potentially) save a query later $boards_info = fetchBoardsInfo($query_boards); // This happens when a member is moderator of a board he cannot see foreach ($boards as $board) { if (!isset($boards_info[$board])) { fatal_lang_error('no_board'); } } if (empty($_REQUEST['sa']) || $_REQUEST['sa'] == 'options') { if (count($polls) > 1) { $request = $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 = $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); } $db->free_result($request); } if (count($boards) > 1) { foreach ($boards_info as $row) { $context['boards'][] = array('id' => $row['id_board'], 'name' => $row['name'], 'selected' => $row['id_board'] == $topic_data[$firstTopic]['board']); } } $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(Util::htmltrim(Util::htmlspecialchars($_POST['custom_subject'])), array("\r" => '', "\n" => '', "\t" => '')); // Keep checking the length. if (Util::strlen($target_subject) > 100) { $target_subject = Util::substr($target_subject, 0, 100); } // Nothing left - odd but pick the first topics subject. if ($target_subject == '') { $target_subject = $topic_data[$firstTopic]['subject']; } } elseif (!empty($topic_data[(int) $_POST['subject']]['subject'])) { $target_subject = $topic_data[(int) $_POST['subject']]['subject']; } else { $target_subject = $topic_data[$firstTopic]['subject']; } // Get the first and last message and the number of messages.... $request = $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; $first_msg = 0; while ($row = $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']; } } $db->free_result($request); // Ensure we have a board stat for the target board. if (!isset($boardTotals[$target_board])) { $boardTotals[$target_board] = array('num_posts' => 0, 'num_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]['num_topics']--; } else { $boardTotals[$target_board]['unapproved_topics']--; } $boardTotals[$target_board]['unapproved_posts'] -= $num_unapproved; $boardTotals[$target_board]['num_posts'] -= $topic_approved ? $num_replies + 1 : $num_replies; // Get the member ID of the first and last message. $request = $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) = $db->fetch_row($request); list($member_updated) = $db->fetch_row($request); // First and last message are the same, so only row was returned. if ($member_updated === null) { $member_updated = $member_started; } $db->free_result($request); // Obtain all the message ids we are going to affect. $affected_msgs = messagesInTopics($topics); // Assign the first topic ID to be the merged topic. $id_topic = min($topics); // Grab the response prefix (like 'Re: ') in the default forum language. $context['response_prefix'] = response_prefix(); $enforce_subject = isset($_POST['enforce_subject']) ? Util::htmlspecialchars(trim($_POST['enforce_subject'])) : ''; // Merge topic notifications. $notifications = isset($_POST['notifications']) && is_array($_POST['notifications']) ? array_intersect($topics, $_POST['notifications']) : array(); fixMergedTopics($first_msg, $topics, $id_topic, $target_board, $target_subject, $enforce_subject, $notifications); // Asssign the properties of the newly merged topic. $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)); // Get rid of the redundant polls. if (!empty($deleted_polls)) { require_once SUBSDIR . '/Poll.subs.php'; removePoll($deleted_polls); } // Cycle through each board... foreach ($boardTotals as $id_board => $stats) { decrementBoard($id_board, $stats); } // Determine the board the final topic resides in $topic_info = getTopicInfo($id_topic); $id_board = $topic_info['id_board']; // 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? require_once SUBSDIR . '/Notification.subs.php'; sendNotifications($id_topic, 'merge'); // If there's a search index that needs updating, update it... require_once SUBSDIR . '/Search.subs.php'; $searchAPI = findSearchAPI(); if (is_callable(array($searchAPI, 'topicMerge'))) { $searchAPI->topicMerge($id_topic, $topics, $affected_msgs, empty($enforce_subject) ? null : array($context['response_prefix'], $target_subject)); } // Send them to the all done page. redirectexit('action=mergetopics;sa=done;to=' . $id_topic . ';targetboard=' . $target_board); }
/** * Display a monthly calendar grid. * * @param string $grid_name */ function template_show_month_grid($grid_name) { global $context, $settings, $txt, $scripturl, $modSettings; if (!isset($context['calendar_grid_' . $grid_name])) { return false; } $calendar_data =& $context['calendar_grid_' . $grid_name]; if (empty($calendar_data['disable_title'])) { echo ' <h2 class="category_header">'; if (empty($calendar_data['previous_calendar']['disabled']) && $calendar_data['show_next_prev']) { echo ' <a href="', $calendar_data['previous_calendar']['href'], '" class="previous_month"> <span class="fa-stack"><i class="fa fa-circle-thin fa-stack-2x"></i><i class="fa fa-chevron-left fa-stack-1x"></i></span> </a>'; } if (empty($calendar_data['next_calendar']['disabled']) && $calendar_data['show_next_prev']) { echo ' <a href="', $calendar_data['next_calendar']['href'], '" class="next_month"> <span class="fa-stack"><i class="fa fa-circle-thin fa-stack-2x"></i><i class="fa fa-chevron-right fa-stack-1x"></i></span> </a>'; } if ($calendar_data['show_next_prev']) { echo ' ', $txt['months_titles'][$calendar_data['current_month']], ' ', $calendar_data['current_year']; } else { echo ' <a href="', $scripturl, '?action=calendar;year=', $calendar_data['current_year'], ';month=', $calendar_data['current_month'], '"> <i class="fa fa-calendar-o"></i> ', $txt['months_titles'][$calendar_data['current_month']], ' ', $calendar_data['current_year'], ' </a>'; } echo ' </h2>'; } // Show the sidebar months echo ' <table class="calendar_table">'; // Show each day of the week. if (empty($calendar_data['disable_day_titles'])) { echo ' <tr class="table_head">'; if (!empty($calendar_data['show_week_links'])) { echo ' <th> </th>'; } foreach ($calendar_data['week_days'] as $day) { echo ' <th scope="col" class="days">', !empty($calendar_data['short_day_titles']) ? Util::substr($txt['days'][$day], 0, 1) : $txt['days'][$day], '</th>'; } echo ' </tr>'; } // Each week in weeks contains the following: // days (a list of days), number (week # in the year.) foreach ($calendar_data['weeks'] as $week) { echo ' <tr>'; if (!empty($calendar_data['show_week_links'])) { echo ' <td class="windowbg2 weeks"> <a href="', $scripturl, '?action=calendar;viewweek;year=', $calendar_data['current_year'], ';month=', $calendar_data['current_month'], ';day=', $week['days'][0]['day'], '"><i class="fa fa-angle-double-right fa-lg"></i></a> </td>'; } // 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 ' <td class="', $day['is_today'] ? 'calendar_today' : 'windowbg', ' days">'; // 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 ' <a href="', $scripturl, '?action=calendar;sa=post;month=', $calendar_data['current_month'], ';year=', $calendar_data['current_year'], ';day=', $day['day'], ';', $context['session_var'], '=', $context['session_id'], '">', $day['day'], '</a>'; } 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 ' - <a href="', $scripturl, '?action=calendar;viewweek;year=', $calendar_data['current_year'], ';month=', $calendar_data['current_month'], ';day=', $day['day'], '">', $txt['calendar_week'], ' ', $week['number'], '</a>'; } // Are there any holidays? if (!empty($day['holidays'])) { echo ' <div class="holiday">', $txt['calendar_prompt'], ' ', implode(', ', $day['holidays']), '</div>'; } // Show any birthdays... if (!empty($day['birthdays'])) { echo ' <div> <span class="birthday">', $txt['birthdays'], '</span>'; // 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 ' <a href="', $scripturl, '?action=profile;u=', $member['id'], '"><span class="fix_rtl_names">', $member['name'], '</span>', isset($member['age']) ? ' (' . $member['age'] . ')' : '', '</a>', $member['is_last'] || $count == 10 && $use_js_hide ? '' : ', '; // Stop at ten? if ($count == 10 && $use_js_hide) { echo '<span class="hidelink" id="bdhidelink_', $day['day'], '">...<br /><a href="', $scripturl, '?action=calendar;month=', $calendar_data['current_month'], ';year=', $calendar_data['current_year'], ';showbd" onclick="document.getElementById(\'bdhide_', $day['day'], '\').style.display = \'\'; document.getElementById(\'bdhidelink_', $day['day'], '\').style.display = \'none\'; return false;">(', sprintf($txt['calendar_click_all'], count($day['birthdays'])), ')</a></span><span id="bdhide_', $day['day'], '" style="display: none;">, '; } $count++; } if ($use_js_hide) { echo ' </span>'; } echo ' </div>'; } // Any special posted events? if (!empty($day['events'])) { echo ' <div class="lefttext"> <span class="event">', $txt['events'], '</span><br />'; // 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 an icon they can click on.... if ($event['can_edit']) { echo ' <a class="modify_event" href="', $event['modify_href'], '"><img src="' . $settings['images_url'] . '/icons/calendar_modify.png" alt="*" title="' . $txt['modify'] . '" /></a>'; } if ($event['can_export']) { echo ' <a class="modify_event" href="', $event['export_href'], '"><img src="' . $settings['images_url'] . '/icons/calendar_export.png" alt=">" title="' . $txt['save'] . '"/></a>'; } echo ' ', $event['link'], $event['is_last'] ? '' : '<br />'; } echo ' </div>'; } } echo ' </td>'; } echo ' </tr>'; } echo ' </table>'; }
/** * Recent topic list: * [board] Subject by Poster Date * * @param int $num_recent * @param int[]|null $exclude_boards * @param bool|null $include_boards * @param string $output_method = 'echo' */ function ssi_recentTopics($num_recent = 8, $exclude_boards = null, $include_boards = null, $output_method = 'echo') { global $settings, $scripturl, $txt, $user_info, $modSettings; $db = database(); 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(); } require_once SUBSDIR . '/MessageIndex.subs.php'; $icon_sources = MessageTopicIcons(); // Find all the posts in distinct topics. Newer ones will have higher IDs. $request = $db->query('', ' SELECT t.id_topic, b.id_board, b.name AS board_name FROM {db_prefix}topics AS t 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) 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 ml.approved = {int:is_approved}' : '') . ' ORDER BY t.id_last_msg DESC LIMIT ' . $num_recent, array('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)); $topics = array(); while ($row = $db->fetch_assoc($request)) { $topics[$row['id_topic']] = $row; } $db->free_result($request); // Did we find anything? If not, bail. if (empty($topics)) { return array(); } // Find all the posts in distinct topics. Newer ones will have higher IDs. $request = $db->query('substring', ' SELECT mf.poster_time, mf.subject, ml.id_topic, mf.id_member, ml.id_msg, t.num_replies, t.num_views, mg.online_color, IFNULL(mem.real_name, mf.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)) >= ml.id_msg_modified AS is_read, IFNULL(lt.id_msg, IFNULL(lmr.id_msg, -1)) + 1 AS new_from') . ', SUBSTRING(mf.body, 1, 384) AS body, mf.smileys_enabled, mf.icon 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_last_msg) LEFT JOIN {db_prefix}members AS mem ON (mem.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 = t.id_board AND lmr.id_member = {int:current_member})' : '') . ' LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group) WHERE t.id_topic IN ({array_int:topic_list})', array('current_member' => $user_info['id'], 'topic_list' => array_keys($topics))); $posts = array(); while ($row = $db->fetch_assoc($request)) { $row['body'] = strip_tags(strtr(parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']), array('<br />' => ' '))); if (Util::strlen($row['body']) > 128) { $row['body'] = Util::substr($row['body'], 0, 128) . '...'; } // Censor the subject. censorText($row['subject']); censorText($row['body']); if (!empty($modSettings['messageIconChecks_enable']) && !isset($icon_sources[$row['icon']])) { $icon_sources[$row['icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $row['icon'] . '.png') ? 'images_url' : 'default_images_url'; } // Build the array. $posts[] = array('board' => array('id' => $topics[$row['id_topic']]['id_board'], 'name' => $topics[$row['id_topic']]['board_name'], 'href' => $scripturl . '?board=' . $topics[$row['id_topic']]['id_board'] . '.0', 'link' => '<a href="' . $scripturl . '?board=' . $topics[$row['id_topic']]['id_board'] . '.0">' . $topics[$row['id_topic']]['board_name'] . '</a>'), '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'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['poster_name'] . '</a>'), 'subject' => $row['subject'], 'replies' => $row['num_replies'], 'views' => $row['num_views'], 'short_subject' => Util::shorten_text($row['subject'], 25), 'preview' => $row['body'], 'time' => standardTime($row['poster_time']), 'html_time' => htmlTime($row['poster_time']), 'timestamp' => forum_time(true, $row['poster_time']), 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . ';topicseen#new', 'link' => '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#new" rel="nofollow">' . $row['subject'] . '</a>', 'new' => !empty($row['is_read']), 'is_new' => empty($row['is_read']), 'new_from' => $row['new_from'], 'icon' => '<img src="' . $settings[$icon_sources[$row['icon']]] . '/post/' . $row['icon'] . '.png" style="vertical-align: middle;" alt="' . $row['icon'] . '" />'); } $db->free_result($request); // Just return it. if ($output_method != 'echo' || empty($posts)) { return $posts; } echo ' <table class="ssi_table">'; foreach ($posts as $post) { echo ' <tr> <td class="righttext top"> [', $post['board']['link'], '] </td> <td class="top"> <a href="', $post['href'], '">', $post['subject'], '</a> ', $txt['by'], ' ', $post['poster']['link'], ' ', !$post['is_new'] ? '' : '<a href="' . $scripturl . '?topic=' . $post['topic'] . '.msg' . $post['new_from'] . ';topicseen#new" rel="nofollow"><span class="new_posts">' . $txt['new'] . '</span></a>', ' </td> <td class="righttext"> ', $post['time'], ' </td> </tr>'; } echo ' </table>'; }
/** * General function to split off a topic. * creates a new topic and moves the messages with the IDs in * array messagesToBeSplit to the new topic. * the subject of the newly created topic is set to 'newSubject'. * marks the newly created message as read for the user splitting it. * updates the statistics to reflect a newly created topic. * logs the action in the moderation log. * a notification is sent to all users monitoring this topic. * * @param int $split1_ID_TOPIC * @param int[] $splitMessages * @param string $new_subject * @return int the topic ID of the new split topic. */ function splitTopic($split1_ID_TOPIC, $splitMessages, $new_subject) { global $txt; $db = database(); // Nothing to split? if (empty($splitMessages)) { fatal_lang_error('no_posts_selected', false); } // Get some board info. $request = $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) = $db->fetch_row($request); $db->free_result($request); // Find the new first and last not in the list. (old topic) $request = $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 ($db->num_rows($request) == 0) { fatal_lang_error('selected_all_posts', false); } $split1_first_msg = null; $split1_last_msg = null; while ($row = $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; } elseif (!$split1_approved) { $split1_replies++; } $split1_unapprovedposts = $row['message_count']; } } $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 = $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 = $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; } elseif (!$split2_approved) { $split2_replies++; } $split2_unapprovedposts = $row['message_count']; } } $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. $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 = $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(Util::htmltrim(Util::htmlspecialchars($new_subject)), array("\r" => '', "\n" => '', "\t" => '')); // Check the subject length. if (Util::strlen($new_subject) > 100) { $new_subject = Util::substr($new_subject, 0, 100); } // Valid subject? if ($new_subject != '') { $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... require_once SUBSDIR . '/Topic.subs.php'; updateSplitTopics(array('splitMessages' => $splitMessages, 'split2_ID_TOPIC' => $split2_ID_TOPIC, 'split1_replies' => $split1_replies, 'split1_first_msg' => $split1_first_msg, 'split1_last_msg' => $split1_last_msg, 'split1_firstMem' => $split1_firstMem, 'split1_lastMem' => $split1_lastMem, 'split1_unapprovedposts' => $split1_unapprovedposts, 'split1_ID_TOPIC' => $split1_ID_TOPIC, 'split2_first_msg' => $split2_first_msg, 'split2_last_msg' => $split2_last_msg, 'split2_ID_TOPIC' => $split2_ID_TOPIC, 'split2_approved' => $split2_approved), $id_board); require_once SUBSDIR . '/FollowUps.subs.php'; // Let's see if we can create a stronger bridge between the two topics // @todo not sure what message from the oldest topic I should link to the new one, so I'll go with the first linkMessages($split1_first_msg, $split2_ID_TOPIC); // Copy log topic entries. // @todo This should really be chunked. $request = $db->query('', ' SELECT id_member, id_msg, unwatched FROM {db_prefix}log_topics WHERE id_topic = {int:id_topic}', array('id_topic' => (int) $split1_ID_TOPIC)); if ($db->num_rows($request) > 0) { $replaceEntries = array(); while ($row = $db->fetch_assoc($request)) { $replaceEntries[] = array($row['id_member'], $split2_ID_TOPIC, $row['id_msg'], $row['unwatched']); } require_once SUBSDIR . '/Topic.subs.php'; markTopicsRead($replaceEntries, false); unset($replaceEntries); } $db->free_result($request); // Housekeeping. updateTopicStats(); 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? require_once SUBSDIR . '/Notification.subs.php'; sendNotifications($split1_ID_TOPIC, 'split'); // If there's a search index that needs updating, update it... require_once SUBSDIR . '/Search.subs.php'; $searchAPI = findSearchAPI(); if (is_callable(array($searchAPI, 'topicSplit'))) { $searchAPI->topicSplit($split2_ID_TOPIC, $splitMessages); } // Return the ID of the newly created topic. return $split2_ID_TOPIC; }
/** * Save any changes to the custom profile fields * * @param int $memID * @param string $area * @param bool $sanitize = true */ function makeCustomFieldChanges($memID, $area, $sanitize = true) { global $context, $user_profile, $user_info, $modSettings; $db = database(); 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 = $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 = $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; } } } else { $value = isset($_POST['customfield'][$row['col_name']]) ? $_POST['customfield'][$row['col_name']] : ''; if ($row['field_length']) { $value = Util::substr($value, 0, $row['field_length']); } // Any masks? if ($row['field_type'] == 'text' && !empty($row['mask']) && $row['mask'] != 'none') { // @todo 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'], 'log_type' => 'user', 'extra' => array('previous' => !empty($user_profile[$memID]['options'][$row['col_name']]) ? $user_profile[$memID]['options'][$row['col_name']] : '', 'new' => $value, 'applicator' => $user_info['id'], 'member_affected' => $memID)); $changes[] = array($row['col_name'], $value, $memID); $user_profile[$memID]['options'][$row['col_name']] = $value; } } $db->free_result($request); call_integration_hook('integrate_save_custom_profile_fields', array(&$changes, &$log_changes, $memID, $area, $sanitize)); // Make those changes! if (!empty($changes) && empty($context['password_auth_failed'])) { $db->insert('replace', '{db_prefix}custom_fields_data', array('variable' => 'string-255', 'value' => 'string-65534', 'id_member' => 'int'), $changes, array('variable', 'id_member')); if (!empty($log_changes) && !empty($modSettings['modlog_enabled'])) { logActions($log_changes); } } }
/** * Create a new topic by email * * What it does: * - Called by pbe_topic to create a new topic or by pbe_main to create a new topic via a subject change * - checks posting permissions, but requires all email validation checks are complete * - Calls pbe_load_text to prepare text for the post * - Uses createPost to do the actual "posting" * - Calls sendNotifications to announce the new post * - Calls query_update_member_stats to show they did something * - Requires the pbe, email_message and board_info arrays to be populated. * * @package Maillist * @param mixed[] $pbe array of pbe 'user_info' values * @param Email_Parse $email_message * @param mixed[] $board_info */ function pbe_create_topic($pbe, $email_message, $board_info) { global $txt, $modSettings; // It does not work like that if (empty($pbe) || empty($email_message)) { return false; } // We have the board info, and their permissions - do they have a right to start a new topic? $becomesApproved = true; if (!$pbe['user_info']['is_admin']) { if (!in_array('postby_email', $pbe['user_info']['permissions'])) { return pbe_emailError('error_permission', $email_message); } elseif ($modSettings['postmod_active'] && in_array('post_unapproved_topics', $pbe['user_info']['permissions']) && !in_array('post_new', $pbe['user_info']['permissions'])) { $becomesApproved = false; } elseif (!in_array('post_new', $pbe['user_info']['permissions'])) { return pbe_emailError('error_cant_start', $email_message); } } // Approving all new topics by email anyway, smart admin this one is ;) if (!empty($modSettings['maillist_newtopic_needsapproval'])) { $becomesApproved = false; } // First on the agenda the subject $subject = pbe_clean_email_subject($email_message->subject); $subject = strtr(Util::htmlspecialchars($subject), array("\r" => '', "\n" => '', "\t" => '')); // Not to long not to short if (Util::strlen($subject) > 100) { $subject = Util::substr($subject, 0, 100); } elseif ($subject == '') { return pbe_emailError('error_no_subject', $email_message); } // The message itself will need a bit of work $html = $email_message->html_found; $text = pbe_load_text($html, $email_message, $pbe); if (empty($text)) { return pbe_emailError('error_no_message', $email_message); } // Build the attachment array if needed if (!empty($email_message->attachments) && !empty($modSettings['maillist_allow_attachments']) && !empty($modSettings['attachmentEnable']) && $modSettings['attachmentEnable'] == 1) { if ($modSettings['postmod_active'] && in_array('post_unapproved_attachments', $pbe['user_info']['permissions']) || in_array('post_attachment', $pbe['user_info']['permissions'])) { $attachIDs = pbe_email_attachments($pbe, $email_message); } else { $text .= "\n\n" . $txt['error_no_attach'] . "\n"; } } // If we get to this point ... then its time to play, lets start a topic ! require_once SUBSDIR . '/Post.subs.php'; // Setup the topic variables. $msgOptions = array('id' => 0, 'subject' => $subject, 'smileys_enabled' => true, 'body' => $text, 'attachments' => empty($attachIDs) ? array() : $attachIDs, 'approved' => $becomesApproved); $topicOptions = array('id' => 0, 'board' => $board_info['id_board'], 'mark_as_read' => false); $posterOptions = array('id' => $pbe['profile']['id_member'], 'name' => $pbe['profile']['real_name'], 'email' => $pbe['profile']['email_address'], 'update_post_count' => empty($board_info['count_posts']), 'ip' => isset($email_message->ip) ? $email_message->ip : $pbe['profile']['member_ip']); // Attempt to make the new topic. createPost($msgOptions, $topicOptions, $posterOptions); // The auto_notify setting $theme_settings = query_get_theme($pbe['profile']['id_member'], $pbe['profile']['id_theme'], $board_info); $auto_notify = isset($theme_settings['auto_notify']) ? $theme_settings['auto_notify'] : 0; // Notifications on or off query_notifications($pbe['profile']['id_member'], $board_info['id_board'], $topicOptions['id'], $auto_notify, $pbe['user_info']['permissions']); // Notify members who have notification turned on for this, (if it's approved) if ($becomesApproved) { require_once SUBSDIR . '/Notification.subs.php'; sendNotifications($topicOptions['id'], 'reply', array(), array(), $pbe); } // Update this users info so the log shows them as active query_update_member_stats($pbe, $email_message, $topicOptions); return true; }
/** * This function processes posting/editing/deleting a calendar event. * * - calls action_post() function if event is linked to a post. * - calls insertEvent() to insert the event if not linked to post. * * It requires the calendar_post permission to use. * It uses the event_post sub template in the Calendar template. * It is accessed with ?action=calendar;sa=post. */ public function action_post() { global $context, $txt, $user_info, $scripturl, $modSettings, $topic; // You need to view what you're doing :P isAllowedTo('calendar_view'); // Well - can they post? isAllowedTo('calendar_post'); // We need this for all kinds of useful functions. require_once SUBSDIR . '/Calendar.subs.php'; // Cast this for safety... $event_id = isset($_REQUEST['eventid']) ? (int) $_REQUEST['eventid'] : null; // Submitting? if (isset($_POST[$context['session_var']], $event_id)) { 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 ($event_id > 0 && !allowedTo('calendar_edit_any')) { isAllowedTo('calendar_edit_' . (!empty($user_info['id']) && getEventPoster($event_id) == $user_info['id'] ? 'own' : 'any')); } // New - and directing? if ($event_id == -1 && isset($_POST['link_to_board'])) { $_REQUEST['calendar'] = 1; require_once CONTROLLERDIR . '/Post.controller.php'; $controller = new Post_Controller(); return $controller->action_post(); } elseif ($event_id == -1) { $eventOptions = array('id_board' => 0, 'id_topic' => 0, 'title' => Util::substr($_REQUEST['evtitle'], 0, 100), '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($_REQUEST['deleteevent'])) { removeEvent($event_id); } else { // There could be already a topic you are not allowed to modify if (!allowedTo('post_new') && empty($modSettings['disableNoPostingCalendarEdits'])) { $eventProperties = getEventProperties($event_id, true); } $eventOptions = array('title' => Util::substr($_REQUEST['evtitle'], 0, 100), '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'])), 'id_board' => isset($eventProperties['id_board']) ? (int) $eventProperties['id_board'] : 0, 'id_topic' => isset($eventProperties['id_topic']) ? (int) $eventProperties['id_topic'] : 0); modifyEvent($event_id, $eventOptions); } // 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($event_id)) { $_REQUEST['calendar'] = 1; require_once CONTROLLERDIR . '/Post.controller.php'; $controller = new Post_Controller(); return $controller->action_post(); } // New? if (!isset($event_id)) { $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 SUBSDIR . '/Boards.subs.php'; $boardListOptions = array('included_boards' => in_array(0, $boards) ? null : $boards, 'not_redirection' => true, 'selected_board' => $modSettings['cal_defaultboard']); $context += getBoardList($boardListOptions); } else { // Reload the event after making changes $context['event'] = getEventProperties($event_id); 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'] = 'unlinked_event_post'; $context['page_title'] = isset($event_id) ? $txt['calendar_edit'] : $txt['calendar_post_event']; $context['linktree'][] = array('name' => $context['page_title']); }
/** * Used to edit the body or subject of a message inline * called from action=jsmodify from script and topic js */ public function action_jsmodify() { global $modSettings, $board, $topic; global $user_info, $context; $db = database(); // We have to have a topic! if (empty($topic)) { obExit(false); } checkSession('get'); require_once SUBSDIR . '/Post.subs.php'; // Assume the first message if no message ID was given. $request = $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('modify_any') || 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 ($db->num_rows($request) == 0) { fatal_lang_error('no_board', false); } $row = $db->fetch_assoc($request); $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'); } } 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 = Error_Context::context('post', 1); if (isset($_POST['subject']) && Util::htmltrim(Util::htmlspecialchars($_POST['subject'])) !== '') { $_POST['subject'] = strtr(Util::htmlspecialchars($_POST['subject']), array("\r" => '', "\n" => '', "\t" => '')); // Maximum number of characters. if (Util::strlen($_POST['subject']) > 100) { $_POST['subject'] = Util::substr($_POST['subject'], 0, 100); } } elseif (isset($_POST['subject'])) { $post_errors->addError('no_subject'); unset($_POST['subject']); } if (isset($_POST['message'])) { if (Util::htmltrim(Util::htmlspecialchars($_POST['message'])) === '') { $post_errors->addError('no_message'); unset($_POST['message']); } elseif (!empty($modSettings['max_messageLength']) && Util::strlen($_POST['message']) > $modSettings['max_messageLength']) { $post_errors->addError(array('long_message', array($modSettings['max_messageLength']))); unset($_POST['message']); } else { $_POST['message'] = Util::htmlspecialchars($_POST['message'], ENT_QUOTES); preparsecode($_POST['message']); if (Util::htmltrim(strip_tags(parse_bbc($_POST['message'], false), '<img>')) === '') { $post_errors->addError('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 (!$post_errors->hasErrors()) { $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' => false); $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']; } } 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. $context['response_prefix'] = response_prefix(); $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 (!$post_errors->hasErrors() && isset($msgOptions['subject']) && isset($msgOptions['body'])) { $context['message'] = array('id' => $row['id_msg'], 'modified' => array('time' => isset($msgOptions['modify_time']) ? standardTime($msgOptions['modify_time']) : '', 'html_time' => isset($msgOptions['modify_time']) ? htmlTime($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(']]>' => ']]]]><![CDATA[>'))); censorText($context['message']['subject']); censorText($context['message']['body']); $context['message']['body'] = parse_bbc($context['message']['body'], $row['smileys_enabled'], $row['id_msg']); } elseif (!$post_errors->hasErrors()) { $context['sub_template'] = 'modifytopicdone'; $context['message'] = array('id' => $row['id_msg'], 'modified' => array('time' => isset($msgOptions['modify_time']) ? standardTime($msgOptions['modify_time']) : '', 'html_time' => isset($msgOptions['modify_time']) ? htmlTime($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' => $post_errors->hasError('no_subject'), 'error_in_body' => $post_errors->hasError('no_message') || $post_errors->hasError('long_message')); $context['message']['errors'] = $post_errors->prepareErrors(); } } else { obExit(false); } }
/** * This function handles adding, deleting and editing labels on messages. */ public function action_manlabels() { global $txt, $context, $user_info, $scripturl; require_once SUBSDIR . '/PersonalMessage.subs.php'; // Build the link tree elements... $context['linktree'][] = array('url' => $scripturl . '?action=pm;sa=manlabels', 'name' => $txt['pm_manage_labels']); // Some things for the template $context['page_title'] = $txt['pm_manage_labels']; $context['sub_template'] = 'labels'; // Add all existing labels to the array to save, slashing them as necessary... $the_labels = array(); foreach ($context['labels'] as $label) { if ($label['id'] != -1) { $the_labels[$label['id']] = $label['name']; } } // Submitting changes? if (isset($_POST['add']) || isset($_POST['delete']) || isset($_POST['save'])) { 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(Util::htmlspecialchars(trim($_POST['label'])), array(',' => ',')); if (Util::strlen($_POST['label']) > 30) { $_POST['label'] = Util::substr($_POST['label'], 0, 30); } if ($_POST['label'] != '') { $the_labels[] = $_POST['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++; } } } 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])) { // Prepare the label name $_POST['label_name'][$id] = trim(strtr(Util::htmlspecialchars($_POST['label_name'][$id]), array(',' => ','))); // Has to fit in the database as well if (Util::strlen($_POST['label_name'][$id]) > 30) { $_POST['label_name'][$id] = Util::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; } } updateLabelsToPM($searchArray, $new_labels, $user_info['id']); // 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'])) { updatePMRuleAction($id, $user_info['id'], $context['rules'][$id]['actions']); unset($rule_changes[$k]); } } // Anything left here means it's lost all actions... if (!empty($rule_changes)) { deletePMRules($user_info['id'], $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'); } }
/** * Saves a PM draft in the user_drafts table * * - The core draft feature must be enabled, as well as the pm draft option * - Determines if this is a new or and update to an existing pm draft * * @package Drafts * @param mixed[] $recipientList */ function savePMDraft($recipientList) { global $context, $user_info, $modSettings; // Ajax calling if (!isset($context['drafts_pm_save'])) { $context['drafts_pm_save'] = !empty($modSettings['drafts_enabled']) && !empty($modSettings['drafts_pm_enabled']) && allowedTo('pm_draft'); } // PM survey says ... can you stay or must you go if (empty($context['drafts_pm_save']) || !isset($_POST['save_draft']) || !isset($_POST['id_pm_draft'])) { return false; } // Read in what was sent $id_pm_draft = empty($_POST['id_pm_draft']) ? 0 : (int) $_POST['id_pm_draft']; $draft_info = loadDraft($id_pm_draft, 1); $post_errors = Error_Context::context('pm', 1); // 5 seconds is the same limit we have for posting if (isset($_REQUEST['xml']) && !empty($draft_info['poster_time']) && time() < $draft_info['poster_time'] + 5) { // Send something back to the javascript caller if (!empty($id_pm_draft)) { loadTemplate('Xml'); $context['sub_template'] = 'xml_draft'; $context['id_draft'] = $id_pm_draft; $context['draft_saved_on'] = $draft_info['poster_time']; obExit(); } return true; } // Determine who this is being sent to if (isset($_REQUEST['xml'])) { $recipientList['to'] = isset($_POST['recipient_to']) ? explode(',', $_POST['recipient_to']) : array(); $recipientList['bcc'] = isset($_POST['recipient_bcc']) ? explode(',', $_POST['recipient_bcc']) : array(); } elseif (!empty($draft_info['to_list']) && empty($recipientList)) { $recipientList = unserialize($draft_info['to_list']); } // Prepare the data $draft = array('id_pm_draft' => $id_pm_draft, 'reply_id' => empty($_POST['replied_to']) ? 0 : (int) $_POST['replied_to'], 'body' => Util::htmlspecialchars($_POST['message'], ENT_QUOTES), 'subject' => strtr(Util::htmlspecialchars($_POST['subject']), array("\r" => '', "\n" => '', "\t" => '')), 'id_member' => $user_info['id']); // message and subject always need a bit more work preparsecode($draft['body']); if (Util::strlen($draft['subject']) > 100) { $draft['subject'] = Util::substr($draft['subject'], 0, 100); } // Modifying an existing PM draft? if (!empty($id_pm_draft) && !empty($draft_info)) { modify_pm_draft($draft, $recipientList); // some items to return to the form $context['draft_saved'] = true; $context['id_pm_draft'] = $id_pm_draft; } else { $id_pm_draft = create_pm_draft($draft, $recipientList); // Everything go as expected, if not toss back an error if (!empty($id_pm_draft)) { $context['draft_saved'] = true; $context['id_pm_draft'] = $id_pm_draft; } else { $post_errors->addError('draft_not_saved'); } } // if we were called from the autosave function, send something back if (!empty($id_pm_draft) && isset($_REQUEST['xml']) && !$post_errors->hasError('session_timeout')) { loadTemplate('Xml'); $context['sub_template'] = 'xml_draft'; $context['id_draft'] = $id_pm_draft; $context['draft_saved_on'] = time(); obExit(); } return; }
/** * Breaks a string up so its no more than width characters long * * - Will break at word boundaries * - If no natural space is found will break mid-word * * @param string $string * @param int $width * @param string $break */ private function _utf8_wordwrap($string, $width = 75, $break = "\n") { $lines = array(); while (!empty($string)) { // Get the next #width characters before a break (space, tab etc) if (preg_match('~^(.{1,' . $width . '})(?:\\s|$)~', $string, $matches)) { // Add the #width to the output and set up for the next pass $lines[] = $matches[1]; $string = Util::substr($string, Util::strlen($matches[0])); } else { $lines[] = Util::substr($string, 0, $width); $string = Util::substr($string, $width); } } // Join it all the shortened sections up on our break characters return implode($break, $lines); }
/** * Converts the first character of a multi-byte string to uppercase * * @param string $string */ public static function ucfirst($string) { return Util::strtoupper(Util::substr($string, 0, 1)) . Util::substr($string, 1); }
/** * Callback to return messages - saves memory. * * @todo Fix this, update it, whatever... from Display.controller.php mainly. * Note that the call to loadAttachmentContext() doesn't work: * this function doesn't fulfill the pre-condition to fill $attachments global... * So all it does is to fallback and return. * * What it does: * - callback function for the results sub template. * - loads the necessary contextual data to show a search result. * * @param boolean $reset = false * @return array of messages that match the search */ public function prepareSearchContext_callback($reset = false) { global $txt, $modSettings, $scripturl, $user_info; global $memberContext, $context, $settings, $options, $messages_request; global $boards_can, $participants; // Remember which message this is. (ie. reply #83) static $counter = null; if ($counter == null || $reset) { $counter = $_REQUEST['start'] + 1; } // Start from the beginning... if ($reset) { return currentContext($messages_request, $reset); } // Attempt to get the next in line $message = currentContext($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" => ' ', '<br />' => "\n")); $message['body'] = parse_bbc($message['body'], $message['smileys_enabled'], $message['id_msg']); $message['body'] = strip_tags(strtr($message['body'], array('</div>' => '<br />', '</li>' => '<br />')), '<br>'); if (Util::strlen($message['body']) > $charLimit) { if (empty($context['key_words'])) { $message['body'] = Util::substr($message['body'], 0, $charLimit) . '<strong>...</strong>'; } else { $matchString = ''; $force_partial_word = false; foreach ($context['key_words'] as $keyword) { $keyword = un_htmlspecialchars($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 = un_htmlspecialchars(substr($matchString, 0, -1)); $message['body'] = un_htmlspecialchars(strtr($message['body'], array(' ' => ' ', '<br />' => "\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]{0,' . $charLimit . '})/isu', $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]{0,' . $charLimit . '})/isu', $message['body'], $matches); } $message['body'] = ''; foreach ($matches[0] as $index => $match) { $match = strtr(htmlspecialchars($match, ENT_QUOTES, 'UTF-8'), array("\n" => ' ')); $message['body'] .= '<strong>......</strong> ' . $match . ' <strong>......</strong>'; } } // 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 that the icon is not broken. if (!empty($modSettings['messageIconChecks_enable'])) { if (!isset($context['icon_sources'][$message['first_icon']])) { $context['icon_sources'][$message['first_icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['first_icon'] . '.png') ? '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'] . '.png') ? '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'] . '.png') ? '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' => !empty($modSettings['pollMode']) && $message['id_poll'] > 0, 'is_hot' => !empty($modSettings['useLikesNotViews']) ? $message['num_likes'] >= $modSettings['hotTopicPosts'] : $message['num_replies'] >= $modSettings['hotTopicPosts'], 'is_very_hot' => !empty($modSettings['useLikesNotViews']) ? $message['num_likes'] >= $modSettings['hotTopicVeryPosts'] : $message['num_replies'] >= $modSettings['hotTopicVeryPosts'], 'posted_in' => !empty($participants[$message['id_topic']]), 'views' => $message['num_views'], 'replies' => $message['num_replies'], 'tests' => array('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' => standardTime($message['first_poster_time']), 'html_time' => htmlTime($message['first_poster_time']), 'timestamp' => forum_time(true, $message['first_poster_time']), 'subject' => $message['first_subject'], 'href' => $scripturl . '?topic=' . $message['id_topic'] . '.0', 'link' => '<a href="' . $scripturl . '?topic=' . $message['id_topic'] . '.0">' . $message['first_subject'] . '</a>', 'icon' => $message['first_icon'], 'icon_url' => $settings[$context['icon_sources'][$message['first_icon']]] . '/post/' . $message['first_icon'] . '.png', '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']) ? '<a href="' . $scripturl . '?action=profile;u=' . $message['first_member_id'] . '" title="' . $txt['profile_of'] . ' ' . $message['first_member_name'] . '">' . $message['first_member_name'] . '</a>' : $message['first_member_name'])), 'last_post' => array('id' => $message['last_msg'], 'time' => standardTime($message['last_poster_time']), 'html_time' => htmlTime($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' => '<a href="' . $scripturl . '?topic=' . $message['id_topic'] . ($message['num_replies'] == 0 ? '.0' : '.msg' . $message['last_msg']) . '#msg' . $message['last_msg'] . '">' . $message['last_subject'] . '</a>', 'icon' => $message['last_icon'], 'icon_url' => $settings[$context['icon_sources'][$message['last_icon']]] . '/post/' . $message['last_icon'] . '.png', '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']) ? '<a href="' . $scripturl . '?action=profile;u=' . $message['last_member_id'] . '" title="' . $txt['profile_of'] . ' ' . $message['last_member_name'] . '">' . $message['last_member_name'] . '</a>' : $message['last_member_name'])), 'board' => array('id' => $message['id_board'], 'name' => $message['board_name'], 'href' => $scripturl . '?board=' . $message['id_board'] . '.0', 'link' => '<a href="' . $scripturl . '?board=' . $message['id_board'] . '.0">' . $message['board_name'] . '</a>'), 'category' => array('id' => $message['id_cat'], 'name' => $message['cat_name'], 'href' => $scripturl . '#c' . $message['id_cat'], 'link' => '<a href="' . $scripturl . '#c' . $message['id_cat'] . '">' . $message['cat_name'] . '</a>'))); 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']); $context['can_markread'] = $context['user']['is_logged']; $context['qmod_actions'] = array('remove', 'lock', 'sticky', 'move', 'markread'); call_integration_hook('integrate_quick_mod_actions_search'); } foreach ($context['key_words'] as $query) { // Fix the international characters in the keyword too. $query = un_htmlspecialchars($query); $query = trim($query, '\\*+'); $query = strtr(Util::htmlspecialchars($query), array('\\\'' => '\'')); $body_highlighted = preg_replace_callback('/((<[^>]*)|' . preg_quote(strtr($query, array('\'' => ''')), '/') . ')/iu', array($this, '_highlighted_callback'), $body_highlighted); $subject_highlighted = preg_replace('/(' . preg_quote($query, '/') . ')/iu', '<strong class="highlight">$1</strong>', $subject_highlighted); } require_once SUBSDIR . '/Attachments.subs.php'; $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'] . '.png', 'subject' => $message['subject'], 'subject_highlighted' => $subject_highlighted, 'time' => standardTime($message['poster_time']), 'html_time' => htmlTime($message['poster_time']), 'timestamp' => forum_time(true, $message['poster_time']), 'counter' => $counter, 'modified' => array('time' => standardTime($message['modified_time']), 'html_time' => htmlTime($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++; if (!$context['compact']) { $output['buttons'] = array('notify' => array('href' => $scripturl . '?action=notify;topic=' . $output['id'] . '.msg' . $message['id_msg'], 'text' => $txt['notify'], 'test' => 'can_mark_notify'), 'reply' => array('href' => $scripturl . '?action=post;topic=' . $output['id'] . '.msg' . $message['id_msg'], 'text' => $txt['reply'], 'test' => 'can_reply'), 'quote' => array('href' => $scripturl . '?action=post;topic=' . $output['id'] . '.msg' . $message['id_msg'] . ';quote=' . $message['id_msg'], 'text' => $txt['quote'], 'test' => 'can_quote')); } call_integration_hook('integrate_search_message_context', array($counter, &$output)); return $output; }
/** * Ensures supplied data is properly encpsulated in cdata xml tags * Called from action_xmlprofile in News.controller.php * * @param string $data * @param string $ns */ function cdata_parse($data, $ns = '') { global $cdata_override; // Are we not doing it? if (!empty($cdata_override)) { return $data; } $cdata = '<![CDATA['; for ($pos = 0, $n = Util::strlen($data); $pos < $n; null) { $positions = array(Util::strpos($data, '&', $pos), Util::strpos($data, ']', $pos)); if ($ns != '') { $positions[] = Util::strpos($data, '<', $pos); } foreach ($positions as $k => $dummy) { if ($dummy === false) { unset($positions[$k]); } } $old = $pos; $pos = empty($positions) ? $n : min($positions); if ($pos - $old > 0) { $cdata .= Util::substr($data, $old, $pos - $old); } if ($pos >= $n) { break; } if (Util::substr($data, $pos, 1) == '<') { $pos2 = Util::strpos($data, '>', $pos); if ($pos2 === false) { $pos2 = $n; } if (Util::substr($data, $pos + 1, 1) == '/') { $cdata .= ']]></' . $ns . ':' . Util::substr($data, $pos + 2, $pos2 - $pos - 1) . '<![CDATA['; } else { $cdata .= ']]><' . $ns . ':' . Util::substr($data, $pos + 1, $pos2 - $pos) . '<![CDATA['; } $pos = $pos2 + 1; } elseif (Util::substr($data, $pos, 1) == ']') { $cdata .= ']]>]<![CDATA['; $pos++; } elseif (Util::substr($data, $pos, 1) == '&') { $pos2 = Util::strpos($data, ';', $pos); if ($pos2 === false) { $pos2 = $n; } $ent = Util::substr($data, $pos + 1, $pos2 - $pos - 1); if (Util::substr($data, $pos + 1, 1) == '#') { $cdata .= ']]>' . Util::substr($data, $pos, $pos2 - $pos + 1) . '<![CDATA['; } elseif (in_array($ent, array('amp', 'lt', 'gt', 'quot'))) { $cdata .= ']]>' . Util::substr($data, $pos, $pos2 - $pos + 1) . '<![CDATA['; } $pos = $pos2 + 1; } } $cdata .= ']]>'; return strtr($cdata, array('<![CDATA[]]>' => '')); }
/** * Sends a personal message from the specified person to the specified people * ($from defaults to the user) * * @package PersonalMessage * @param mixed[] $recipients - an array containing the arrays 'to' and 'bcc', both containing id_member's. * @param string $subject - should have no slashes and no html entities * @param string $message - should have no slashes and no html entities * @param bool $store_outbox * @param mixed[]|null $from - an array with the id, name, and username of the member. * @param int $pm_head - the ID of the chain being replied to - if any. * @return mixed[] an array with log entries telling how many recipients were successful and which recipients it failed to send to. */ function sendpm($recipients, $subject, $message, $store_outbox = true, $from = null, $pm_head = 0) { global $scripturl, $txt, $user_info, $language, $modSettings, $webmaster_email; $db = database(); // Make sure the PM language file is loaded, we might need something out of it. loadLanguage('PersonalMessage'); // Needed for our email and post functions require_once SUBSDIR . '/Mail.subs.php'; require_once SUBSDIR . '/Post.subs.php'; // 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']); } else { $user_info['name'] = $from['name']; } // This is the one that will go in their inbox. $htmlmessage = Util::htmlspecialchars($message, ENT_QUOTES, 'UTF-8', true); preparsecode($htmlmessage); $htmlsubject = strtr(Util::htmlspecialchars($subject), array("\r" => '', "\n" => '', "\t" => '')); if (Util::strlen($htmlsubject) > 100) { $htmlsubject = Util::substr($htmlsubject, 0, 100); } // Make sure is an array if (!is_array($recipients)) { $recipients = array($recipients); } // Integrated PMs call_integration_hook('integrate_personal_message', array(&$recipients, &$from, &$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] = Util::strtolower(trim(preg_replace('/[<>&"\'=\\\\]/', '', $recipients[$rec_type][$id]))); $usernames[$recipients[$rec_type][$id]] = 0; } } } if (!empty($usernames)) { $request = $db->query('pm_find_username', ' SELECT id_member, member_name FROM {db_prefix}members WHERE ' . (defined('DB_CASE_SENSITIVE') ? 'LOWER(member_name)' : 'member_name') . ' IN ({array_string:usernames})', array('usernames' => array_keys($usernames))); while ($row = $db->fetch_assoc($request)) { if (isset($usernames[Util::strtolower($row['member_name'])])) { $usernames[Util::strtolower($row['member_name'])] = $row['id_member']; } } $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 = $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 = $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) { 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; } elseif (!$row['is_or']) { $delete = false; break; } } if ($delete) { $deletes[$row['id_member']] = 1; } } $db->free_result($request); // Load the membergrounp message limits. static $message_limit_cache = array(); if (!allowedTo('moderate_forum') && empty($message_limit_cache)) { $request = $db->query('', ' SELECT id_group, max_messages FROM {db_prefix}membergroups', array()); while ($row = $db->fetch_assoc($request)) { $message_limit_cache[$row['id_group']] = $row['max_messages']; } $db->free_result($request); } // Load the groups that are allowed to read PMs. // @todo move into a separate function on $permission. $allowed_groups = array(); $disallowed_groups = array(); $request = $db->query('', ' SELECT id_group, add_deny FROM {db_prefix}permissions WHERE permission = {string:read_permission}', array('read_permission' => 'pm_read')); while ($row = $db->fetch_assoc($request)) { if (empty($row['add_deny'])) { $disallowed_groups[] = $row['id_group']; } else { $allowed_groups[] = $row['id_group']; } } $db->free_result($request); if (empty($modSettings['permission_enable_deny'])) { $disallowed_groups = array(); } $request = $db->query('', ' SELECT member_name, real_name, id_member, email_address, lngfile, pm_email_notify, personal_messages,' . (allowedTo('moderate_forum') ? ' 0' : ' (receive_from = {int:admins_only}' . (empty($modSettings['enable_buddylist']) ? '' : ' OR (receive_from = {int:buddies_only} AND FIND_IN_SET({string:from_id}, buddy_list) = 0) OR (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 = $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['personal_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']); } $db->free_result($request); // Only 'send' the message if there are any recipients left. if (empty($all_to)) { return $log; } // Track the pm count for our stats if (!empty($modSettings['trackStats'])) { trackStats(array('pm' => '+')); } // Insert the message itself and then grab the last insert id. $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 = $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)) { $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 :) $db->query('', ' DELETE FROM {db_prefix}pm_recipients WHERE id_pm = {int:id_pm}', array('id_pm' => $id_pm)); $insertRows = array(); $to_list = array(); foreach ($all_to as $to) { $insertRows[] = array($id_pm, $to, in_array($to, $recipients['bcc']) ? 1 : 0, isset($deletes[$to]) ? 1 : 0, 1); if (!in_array($to, $recipients['bcc'])) { $to_list[] = $to; } } $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')); } $maillist = !empty($modSettings['maillist_enabled']) && !empty($modSettings['pbe_pm_enabled']); // If they have post by email enabled, override disallow_sendBody if (!$maillist && !empty($modSettings['disallow_sendBody'])) { $message = ''; censorText($subject); } else { require_once SUBSDIR . '/Emailpost.subs.php'; pbe_prepare_text($message, $subject); } $to_names = array(); if (count($to_list) > 1) { require_once SUBSDIR . '/Members.subs.php'; $result = getBasicMemberData($to_list); foreach ($result as $row) { $to_names[] = un_htmlspecialchars($row['real_name']); } } $replacements = array('SUBJECT' => $subject, 'MESSAGE' => $message, 'SENDER' => un_htmlspecialchars($from['name']), 'READLINK' => $scripturl . '?action=pm;pmsg=' . $id_pm . '#msg' . $id_pm, 'REPLYLINK' => $scripturl . '?action=pm;sa=send;f=inbox;pmsg=' . $id_pm . ';quote;u=' . $from['id'], 'TOLIST' => implode(', ', $to_names)); // Select the right template $email_template = ($maillist && empty($modSettings['disallow_sendBody']) ? 'pbe_' : '') . 'new_pm' . (empty($modSettings['disallow_sendBody']) ? '_body' : '') . (!empty($to_names) ? '_tolist' : ''); foreach ($notifications as $lang => $notification_list) { // Using maillist functionality if ($maillist) { $sender_details = query_sender_wrapper($from['id']); $from_wrapper = !empty($modSettings['maillist_mail_from']) ? $modSettings['maillist_mail_from'] : (empty($modSettings['maillist_sitename_address']) ? $webmaster_email : $modSettings['maillist_sitename_address']); // Add in the signature $replacements['SIGNATURE'] = $sender_details['signature']; // And off it goes, looking a bit more personal $mail = loadEmailTemplate($email_template, $replacements, $lang); $reference = !empty($pm_head) ? $pm_head : null; sendmail($notification_list, $mail['subject'], $mail['body'], $from['name'], 'p' . $id_pm, false, 2, null, true, $from_wrapper, $reference); } else { // Off the notification email goes! $mail = loadEmailTemplate($email_template, $replacements, $lang); sendmail($notification_list, $mail['subject'], $mail['body'], null, 'p' . $id_pm, false, 2, null, true); } } // Integrated After PMs call_integration_hook('integrate_personal_message_after', array(&$id_pm, &$log, &$recipients, &$from, &$subject, &$message)); // 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('personal_messages' => '+', 'unread_messages' => '+', 'new_pm' => 1)); } return $log; }
/** * Execute the move of a topic. * It is called on the submit of action_movetopic. * This function logs that topics have been moved in the moderation log. * If the member is the topic starter requires the move_own permission, * otherwise requires the move_any permission. * Upon successful completion redirects to message index. * Accessed via ?action=movetopic2. * * @uses subs/Post.subs.php. */ public function action_movetopic2() { global $txt, $board, $topic, $scripturl, $context, $language, $user_info; 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); } // You have to tell us were you are moving to if (!isset($_POST['toboard'])) { fatal_lang_error('movetopic_no_board', false); } // We will need this require_once SUBSDIR . '/Topic.subs.php'; moveTopicConcurrence(); // Make sure this form hasn't been submitted before. checkSubmitOnce('check'); // Get the basic details on this topic $topic_info = getTopicInfo($topic); $context['is_approved'] = $topic_info['approved']; // Can they see it? if (!$context['is_approved']) { isAllowedTo('approve_posts'); } // Can they move topics on this board? if (!allowedTo('move_any')) { if ($topic_info['id_member_started'] == $user_info['id']) { isAllowedTo('move_own'); } else { isAllowedTo('move_any'); } } checkSession(); require_once SUBSDIR . '/Post.subs.php'; require_once SUBSDIR . '/Boards.subs.php'; // The destination board must be numeric. $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). $board_info = boardInfo($toboard, $topic); if (empty($board_info)) { fatal_lang_error('no_board'); } // Remember this for later. $_SESSION['move_to_topic'] = array('move_to' => $toboard); // Rename the topic... if (isset($_POST['reset_subject'], $_POST['custom_subject']) && $_POST['custom_subject'] != '') { $custom_subject = strtr(Util::htmltrim(Util::htmlspecialchars($_POST['custom_subject'])), array("\r" => '', "\n" => '', "\t" => '')); // Keep checking the length. if (Util::strlen($custom_subject) > 100) { $custom_subject = Util::substr($custom_subject, 0, 100); } // If it's still valid move onwards and upwards. if ($custom_subject != '') { $all_messages = isset($_POST['enforce_subject']); if ($all_messages) { // Get a response prefix, but in the forum's default language. $context['response_prefix'] = response_prefix(); topicSubject($topic_info, $custom_subject, $context['response_prefix'], $all_messages); } else { topicSubject($topic_info, $custom_subject); } // Fix the subject cache. updateStats('subject', $topic, $custom_subject); } } // Create a link to this in the old board. // @todo 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); } $reason = Util::htmlspecialchars($_POST['reason'], ENT_QUOTES); preparsecode($reason); // Add a URL onto the message. $reason = strtr($reason, array($txt['movetopic_auto_board'] => '[url=' . $scripturl . '?board=' . $toboard . '.0]' . $board_info['name'] . '[/url]', $txt['movetopic_auto_topic'] => '[iurl]' . $scripturl . '?topic=' . $topic . '.0[/iurl]')); // Auto remove this MOVED redirection topic in the future? $redirect_expires = !empty($_POST['redirect_expires']) ? (int) $_POST['redirect_expires'] : 0; // Redirect to the MOVED topic from topic list? $redirect_topic = isset($_POST['redirect_topic']) ? $topic : 0; // And remember the last expiry period too. $_SESSION['move_to_topic']['redirect_topic'] = $redirect_topic; $_SESSION['move_to_topic']['redirect_expires'] = $redirect_expires; $msgOptions = array('subject' => $txt['moved'] . ': ' . $board_info['subject'], 'body' => $reason, 'icon' => 'moved', 'smileys_enabled' => 1); $topicOptions = array('board' => $board, 'lock_mode' => 1, 'mark_as_read' => true, 'redirect_expires' => empty($redirect_expires) ? 0 : $redirect_expires * 60 + time(), 'redirect_topic' => $redirect_topic); $posterOptions = array('id' => $user_info['id'], 'update_post_count' => empty($board_info['count_posts'])); createPost($msgOptions, $topicOptions, $posterOptions); } $board_from = boardInfo($board); if ($board_from['count_posts'] != $board_info['count_posts']) { $posters = postersCount($topic); foreach ($posters as $id_member => $posts) { // The board we're moving from counted posts, but not to. if (empty($board_from['count_posts'])) { updateMemberData($id_member, array('posts' => 'posts - ' . $posts)); } else { updateMemberData($id_member, array('posts' => 'posts + ' . $posts)); } } } // Do the move (includes statistics update needed for the redirect topic). moveTopics($topic, $toboard); // Log that they moved this topic. if (!allowedTo('move_own') || $topic_info['id_member_started'] != $user_info['id']) { logAction('move', array('topic' => $topic, 'board_from' => $board, 'board_to' => $toboard)); } // Notify people that this topic has been moved? require_once SUBSDIR . '/Notification.subs.php'; 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'); } }
/** * Actually logs you in. * * What it does: * - checks credentials and checks that login was successful. * - it employs protection against a specific IP or user trying to brute force * a login to an account. * - upgrades password encryption on login, if necessary. * - after successful login, redirects you to $_SESSION['login_url']. * - accessed from ?action=login2, by forms. * * On error, uses the same templates action_login() uses. */ public function action_login2() { global $txt, $scripturl, $user_info, $user_settings, $modSettings, $context, $sc; // Load cookie authentication and all stuff. require_once SUBSDIR . '/Auth.subs.php'; // 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? checkSession('post'); validateToken('login'); 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 || isset($_GET['quicklogin']) && isset($_SESSION['old_url']) && strpos($_SESSION['old_url'], 'login') === false) { $_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 loadTemplate('Login'); loadJavascriptFile('sha256.js', array('defer' => true)); $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'], ENT_COMPAT, 'UTF-8')) : ''; $context['default_password'] = ''; $context['never_expire'] = $modSettings['cookieTime'] == 525600 || $modSettings['cookieTime'] == 3153600; $context['login_errors'] = array($txt['error_occurred']); $context['page_title'] = $txt['login']; // Add the login chain to the link tree. $context['linktree'][] = array('url' => $scripturl . '?action=login', 'name' => $txt['login']); // This is an OpenID login. Let's validate... if (!empty($_POST['openid_identifier']) && !empty($modSettings['enableOpenID'])) { require_once SUBSDIR . '/OpenID.subs.php'; $open_id = new OpenID(); if ($open_id->validate($_POST['openid_identifier']) !== 'no_data') { return $open_id; } else { $context['login_errors'] = array($txt['openid_not_found']); return; } } // You forgot to type your username, dummy! if (!isset($_POST['user']) || $_POST['user'] == '') { $context['login_errors'] = array($txt['need_username']); return; } // No one needs a username that long, plus we only support 80 chars in the db if (Util::strlen($_POST['user']) > 80) { $_POST['user'] = Util::substr($_POST['user'], 0, 80); } // Can't use a password > 64 characters sorry, to long and only good for a DoS attack // Plus we expect a 64 character one from SHA-256 if (isset($_POST['passwrd']) && strlen($_POST['passwrd']) > 64 || isset($_POST['hash_passwrd']) && strlen($_POST['hash_passwrd']) > 64) { $context['login_errors'] = array($txt['improper_password']); 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']) != 64)) { $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; } // 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; } // Find them... if we can $user_settings = loadExistingMember($_POST['user']); // Let them try again, it didn't match anything... if (empty($user_settings)) { $context['login_errors'] = array($txt['username_no_exist']); return; } // Figure out if the password is using Elk's encryption - if what they typed is right. if (isset($_POST['hash_passwrd']) && strlen($_POST['hash_passwrd']) === 64) { // Challenge what was passed $valid_password = validateLoginPassword($_POST['hash_passwrd'], $user_settings['passwd']); // Let them in if ($valid_password) { $sha_passwd = $_POST['hash_passwrd']; $valid_password = true; } elseif (preg_match('/^[0-9a-f]{40}$/i', $user_settings['passwd']) && isset($_POST['old_hash_passwrd']) && $_POST['old_hash_passwrd'] === hash('sha1', $user_settings['passwd'] . $sc)) { // Old password passed, turn off hashing and ask for it again so we can update the db to something more secure. $context['login_errors'] = array($txt['login_hash_error']); $context['disable_login_hashing'] = true; unset($user_settings); return; } else { // Don't allow this! validatePasswordFlood($user_settings['id_member'], $user_settings['passwd_flood']); $_SESSION['failed_login'] = isset($_SESSION['failed_login']) ? $_SESSION['failed_login'] + 1 : 1; // To many tries, maybe they need a reminder if ($_SESSION['failed_login'] >= $modSettings['failed_login_threshold']) { redirectexit('action=reminder'); } else { log_error($txt['incorrect_password'] . ' - <span class="remove">' . $user_settings['member_name'] . '</span>', 'user'); // Wrong password, lets enable plain text responses in case form hashing is causing problems $context['disable_login_hashing'] = true; $context['login_errors'] = array($txt['incorrect_password']); unset($user_settings); return; } } } else { // validateLoginPassword will hash this like the form normally would and check its valid $sha_passwd = $_POST['passwrd']; $valid_password = validateLoginPassword($sha_passwd, $user_settings['passwd'], $user_settings['member_name']); } // Bad password! Thought you could fool the database?! if ($valid_password === false) { // 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 = $this->_other_passwords($user_settings); // Whichever encryption it was using, let's make it use ElkArte's now ;). if (in_array($user_settings['passwd'], $other_passwords)) { $user_settings['passwd'] = validateLoginPassword($sha_passwd, '', '', true); $user_settings['password_salt'] = substr(md5(mt_rand()), 0, 4); // Update the password hash and set up the salt. updateMemberData($user_settings['id_member'], array('passwd' => $user_settings['passwd'], 'password_salt' => $user_settings['password_salt'], 'passwd_flood' => '')); } else { // They've messed up again - keep a count to see if they need a hand. $_SESSION['failed_login'] = isset($_SESSION['failed_login']) ? $_SESSION['failed_login'] + 1 : 1; // Hmm... don't remember it, do you? Here, try the password reminder ;). if ($_SESSION['failed_login'] >= $modSettings['failed_login_threshold']) { redirectexit('action=reminder'); } else { // Log an error so we know that it didn't go well in the error log. log_error($txt['incorrect_password'] . ' - <span class="remove">' . $user_settings['member_name'] . '</span>', '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(); }
/** * Article Block, show the list of articles in the system * * @param mixed[] $parameters * 'category' => list of categories to choose article from * 'limit' => number of articles to show * 'type' => 0 latest 1 random * 'length' => length for the body text preview * 'avatar' => whether to show the author avatar or not * * @param int $id - not used in this block * @param boolean $return_parameters if true returns the configuration options for the block */ function sp_articles($parameters, $id, $return_parameters = false) { global $scripturl, $txt, $color_profile; $block_parameters = array('category' => array(0 => $txt['sp_all']), 'limit' => 'int', 'type' => 'select', 'length' => 'int', 'avatar' => 'check'); if ($return_parameters) { require_once SUBSDIR . '/PortalAdmin.subs.php'; $categories = sp_load_categories(); foreach ($categories as $category) { $block_parameters['category'][$category['id']] = $category['name']; } return $block_parameters; } // Needed utilities require_once SUBSDIR . '/Post.subs.php'; require_once SUBSDIR . '/PortalArticle.subs.php'; // Set up for the query $category = empty($parameters['category']) ? 0 : (int) $parameters['category']; $limit = empty($parameters['limit']) ? 5 : (int) $parameters['limit']; $type = empty($parameters['type']) ? 0 : 1; $length = isset($parameters['length']) ? (int) $parameters['length'] : 250; $avatar = empty($parameters['avatar']) ? 0 : (int) $parameters['avatar']; $articles = sportal_get_articles(null, true, true, $type ? 'RAND()' : 'spa.date DESC', $category, $limit); $colorids = array(); foreach ($articles as $article) { if (!empty($article['author']['id'])) { $colorids[$article['author']['id']] = $article['author']['id']; } } // No articles in the system or none they can see if (empty($articles)) { echo ' ', $txt['error_sp_no_articles_found']; return; } // Doing the color thing if (!empty($colorids) && sp_loadColors($colorids) !== false) { foreach ($articles as $k => $p) { if (!empty($color_profile[$p['author']['id']]['link'])) { $articles[$k]['author']['link'] = $color_profile[$p['author']['id']]['link']; } } } // Not showing avatars, just use a compact link view if (empty($avatar)) { echo ' <ul class="sp_list">'; foreach ($articles as $article) { echo ' <li>', sp_embed_image('topic'), ' ', $article['link'], '</li>'; } echo ' </ul>'; } else { echo ' <table class="sp_fullwidth">'; foreach ($articles as $article) { // Using the cutoff tag? $limited = false; if (($cutoff = Util::strpos($article['body'], '[cutoff]')) !== false) { $article['body'] = Util::substr($article['body'], 0, $cutoff); preparsecode($article['body']); $limited = true; } // Good time to do this is ... now censorText($article['subject']); censorText($article['body']); $article['body'] = sportal_parse_content($article['body'], $article['type'], 'return'); // Shorten the text, link the ellipsis, etc as needed if ($limited || !empty($length)) { $ellip = '<a href="' . $scripturl . '?article=' . $article['article_id'] . '">…</a>'; $article['body'] = $limited ? $article['body'] . $ellip : Util::shorten_html($article['body'], $length, $ellip, false); } echo ' <tr class="sp_articles_row"> <td class="sp_articles centertext">'; // If we have an avatar to show, show it if ($avatar && !empty($article['author']['avatar']['href'])) { echo ' <a href="', $scripturl, '?action=profile;u=', $article['author']['id'], '"> <img src="', $article['author']['avatar']['href'], '" alt="', $article['author']['name'], '" style="max-width:40px" /> </a>'; } echo ' </td> <td> <span class="sp_articles_title">', $article['author']['link'], '</span><br /> ', $article['link'], ' </td> <td>', $article['body'], '</td> </tr> <tr> <td colspan="3" class="sp_articles_row"></td> </tr>'; } echo ' </table>'; } }
/** * This function is used for adding/editing a specific holiday * * @uses ManageCalendar template, edit_holiday sub template */ public function action_editholiday() { global $txt, $context; //We need this, really.. require_once SUBSDIR . '/Calendar.subs.php'; 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'] = Util::substr($_REQUEST['title'], 0, 60); $_REQUEST['holiday'] = isset($_REQUEST['holiday']) ? (int) $_REQUEST['holiday'] : 0; if (isset($_REQUEST['delete'])) { removeHolidays($_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'])) { editHoliday($_REQUEST['holiday'], $date, $_REQUEST['title']); } else { insertHoliday($date, $_REQUEST['title']); } } 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' => ''); } else { $context['holiday'] = getHoliday($_REQUEST['holiday']); } // 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'])); }
/** * List all members, page by page, with sorting. * Called from MemberList(). * Can be passed a sort parameter, to order the display of members. * Calls printMemberListRows to retrieve the results of the query. */ public function action_mlall() { global $txt, $scripturl, $modSettings, $context; // The chunk size for the cached index. $cache_step_size = 500; require_once SUBSDIR . '/Memberlist.subs.php'; // 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']) { $memberlist_cache = ml_memberCache($cache_step_size); } $context['num_members'] = $memberlist_cache['num_members']; } else { $context['num_members'] = ml_memberCount(); } // 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('~^[^\'\\\\/]~u', Util::strtolower($_REQUEST['start']), $match) === 0) { fatal_error('Hacker?', false); } $_REQUEST['start'] = ml_alphaStart($match[0]); } // Build out the letter selection link bar $context['letter_links'] = ''; for ($i = 97; $i < 123; $i++) { $context['letter_links'] .= '<a href="' . $scripturl . '?action=memberlist;sa=all;start=' . chr($i) . '#letter' . chr($i) . '">' . chr($i - 32) . '</a> '; } // Sort out the column information. foreach ($context['columns'] as $col => $column_details) { $context['columns'][$col]['href'] = $scripturl . '?action=memberlist;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'] = '<a href="' . $context['columns'][$col]['href'] . '" rel="nofollow">' . $context['columns'][$col]['label'] . '</a>'; $context['columns'][$col]['selected'] = $_REQUEST['sort'] == $col; if ($context['columns'][$col]['selected']) { $context['columns'][$col]['class'] .= ' selected'; } } // Are we sorting the results $context['sort_by'] = $_REQUEST['sort']; $context['sort_direction'] = !isset($_REQUEST['desc']) ? 'up' : 'down'; // Construct the page index. $context['page_index'] = constructPageIndex($scripturl . '?action=memberlist;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=memberlist;sort=' . $_REQUEST['sort'] . ';start=' . $_REQUEST['start'], 'name' => &$context['page_title'], 'extra_after' => ' (' . sprintf($txt['of_total_members'], $context['num_members']) . ')'); $limit = $_REQUEST['start']; $where = ''; $query_parameters = array('regular_id_group' => 0, 'is_activated' => 1, 'sort' => $context['columns'][$_REQUEST['sort']]['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; } 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); } // Add custom fields parameters too. if (!empty($context['custom_profile_fields']['parameters'])) { $query_parameters += $context['custom_profile_fields']['parameters']; } // Select the members from the database. ml_selectMembers($query_parameters, $where, $limit, $_REQUEST['sort']); // Add anchors at the start of each letter. if ($_REQUEST['sort'] === 'real_name') { $last_letter = ''; foreach ($context['members'] as $i => $dummy) { $this_letter = Util::strtolower(Util::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, ENT_COMPAT, 'UTF-8'); $last_letter = $this_letter; } } } }
/** * Makes sure the calendar post is valid. * * @package Calendar */ function validateEventPost() { global $modSettings; 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 (Util::htmltrim($_POST['evtitle']) === '') { fatal_lang_error('no_event_title', false); } if (Util::strlen($_POST['evtitle']) > 100) { $_POST['evtitle'] = Util::substr($_POST['evtitle'], 0, 100); } $_POST['evtitle'] = str_replace(';', '', $_POST['evtitle']); } }
/** * Update the settings for a poll, or add a new one. * Must be called with a topic specified in the URL. * The user must have poll_edit_any/poll_add_any permission * for the relevant action. Otherwise they must be poll starter * with poll_edit_own permission for editing, or be topic starter * with poll_add_any permission for adding. * In the case of an error, this function will redirect back to * action_editpoll and display the relevant error message. * Upon successful completion of action will direct user back to topic. * Accessed via ?action=editpoll2. */ public function action_editpoll2() { global $topic, $board, $user_info; // Sneaking off, are we? if (empty($_POST)) { redirectexit('action=editpoll;topic=' . $topic . '.0'); } $poll_errors = Error_Context::context('poll'); if (checkSession('post', '', false) != '') { $poll_errors->addError('session_timeout'); } if (isset($_POST['preview'])) { return $this->action_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; // Make sure we have our stuff. require_once SUBSDIR . '/Poll.subs.php'; // Get the starter and the poll's ID - if it's an edit. $bcinfo = getPollStarter($topic); // Check their adding/editing is valid. if (!$isEdit && !empty($bcinfo['id_poll'])) { fatal_lang_error('poll_already_exists'); } 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; $idCount = 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++; $idCount = max($idCount, $k); } } if ($optionCount < 2) { $poll_errors->addError('poll_few'); } elseif ($optionCount > 256 || $idCount > 255) { $poll_errors->addError('poll_many'); } // Also - ensure they are not removing the question. if (trim($_POST['question']) == '') { $poll_errors->addError('no_question'); } // Got any errors to report? if ($poll_errors->hasErrors()) { return $this->action_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'] = Util::htmlspecialchars($_POST['question']); $_POST['question'] = Util::substr($_POST['question'], 0, 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 SUBSDIR . '/Members.subs.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) { modifyPoll($bcinfo['id_poll'], $_POST['question'], !empty($_POST['poll_max_votes']) ? $_POST['poll_max_votes'] : 0, $_POST['poll_hide'], !empty($_POST['poll_expire']) ? $_POST['poll_expire'] : 0, $_POST['poll_change_vote'], $_POST['poll_guest_vote']); } else { // Create the poll. $bcinfo['id_poll'] = createPoll($_POST['question'], $user_info['id'], $user_info['username'], $_POST['poll_max_votes'], $_POST['poll_hide'], $_POST['poll_expire'], $_POST['poll_change_vote'], $_POST['poll_guest_vote']); // Link the poll to the topic. associatedPoll($topic, $bcinfo['id_poll']); } // Get all the choices. (no better way to remove all emptied and add previously non-existent ones.) $choices = array_keys(pollOptions($bcinfo['id_poll'])); $add_options = array(); $update_options = array(); $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 = Util::htmlspecialchars($option); // If it's already there, update it. If it's not... add it. if (in_array($k, $choices)) { $update_options[] = array($bcinfo['id_poll'], $k, $option); } else { $add_options[] = array($bcinfo['id_poll'], $k, $option, 0); } } if (!empty($update_options)) { modifyPollOption($update_options); } if (!empty($add_options)) { insertPollOptions($add_options); } // I'm sorry, but... well, no one was choosing you. Poor options, I'll put you out of your misery. if (!empty($delete_options)) { deletePollOptions($bcinfo['id_poll'], $delete_options); } // Shall I reset the vote count, sir? if (isset($_POST['resetVoteCount'])) { resetVotes($bcinfo['id_poll']); } call_integration_hook('integrate_poll_add_edit', array($bcinfo['id_poll'], $isEdit)); // Off we go. redirectexit('topic=' . $topic . '.' . $_REQUEST['start']); }
/** * Edit some profile fields? * * - Accessed with ?action=admin;area=featuresettings;sa=profileedit * * @uses sub template edit_profile_field */ public function action_profileedit() { global $txt, $scripturl, $context; require_once SUBSDIR . '/ManageFeatures.subs.php'; loadTemplate('ManageFeatures'); // 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'; // any errors messages to show? if (isset($_GET['msg'])) { loadLanguage('Errors'); if (isset($txt['custom_option_' . $_GET['msg']])) { $context['custom_option__error'] = $txt['custom_option_' . $_GET['msg']]; } } // Load the profile language for section names. loadLanguage('Profile'); // Load up the profile field, if one was supplied if ($context['fid']) { $context['field'] = getProfileField($context['fid']); } // Setup the default values as needed. if (empty($context['field'])) { $context['field'] = array('name' => '', 'colname' => '???', 'desc' => '', 'profile_area' => 'forumprofile', 'reg' => false, 'display' => false, 'memberlist' => 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); } // All the javascript for this page... everything else is in admin.js addJavascriptVar(array('startOptID' => count($context['field']['options']))); addInlineJavascript('updateInputBoxes();', true); // Are we toggling which ones are active? if (isset($_POST['onoff'])) { checkSession(); validateToken('admin-scp'); // Enable and disable custom fields as required. $enabled = array(0); foreach ($_POST['cust'] as $id) { $enabled[] = (int) $id; } updateRenamedProfileStatus($enabled); } elseif (isset($_POST['save'])) { checkSession(); validateToken('admin-ecp'); // Everyone needs a name - even the (bracket) unknown... if (trim($_POST['field_name']) == '') { redirectexit($scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=' . $_GET['fid'] . ';msg=need_name'); } // Regex you say? Do a very basic test to see if the pattern is valid if (!empty($_POST['regex']) && @preg_match($_POST['regex'], 'dummy') === false) { redirectexit($scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=' . $_GET['fid'] . ';msg=regex_error'); } $_POST['field_name'] = Util::htmlspecialchars($_POST['field_name']); $_POST['field_desc'] = Util::htmlspecialchars($_POST['field_desc']); // Checkboxes... $show_reg = isset($_POST['reg']) ? (int) $_POST['reg'] : 0; $show_display = isset($_POST['display']) ? 1 : 0; $show_memberlist = isset($_POST['memberlist']) ? 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 = Util::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; } } if (isset($_POST['default_select']) && $_POST['default_select'] == 'no_default') { $default = 'no_default'; } $field_options = substr($field_options, 0, -1); } // Text area by 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 = Util::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 our own - for foreign languages etc. if (isset($matches[1])) { $colname = $initial_colname = 'cust_' . strtolower($matches[1]); } else { $colname = $initial_colname = 'cust_' . mt_rand(1, 999999); } $unique = ensureUniqueProfileField($colname, $initial_colname); // Still not a unique colum name? Leave it up to the user, then. if (!$unique) { fatal_lang_error('custom_option_not_unique'); } } 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') { deleteProfileFieldUserData($context['field']['colname']); } 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])) { updateRenamedProfileField($k, $newOptions, $context['field']['colname'], $option); } } } // @todo Maybe we should adjust based on new text length limits? } // Updating an existing field? if ($context['fid']) { $field_data = array('field_length' => $field_length, 'show_reg' => $show_reg, 'show_display' => $show_display, 'show_memberlist' => $show_memberlist, '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); updateProfileField($field_data); // Just clean up any old selects - these are a pain! if (($_POST['field_type'] == 'select' || $_POST['field_type'] == 'radio') && !empty($newOptions)) { deleteOldProfileFieldSelects($newOptions, $context['field']['colname']); } } else { $new_field = array('col_name' => $colname, 'field_name' => $_POST['field_name'], 'field_desc' => $_POST['field_desc'], 'field_type' => $_POST['field_type'], 'field_length' => $field_length, 'field_options' => $field_options, 'show_reg' => $show_reg, 'show_display' => $show_display, 'show_memberlist' => $show_memberlist, 'show_profile' => $show_profile, 'private' => $private, 'active' => $active, 'default' => $default, 'can_search' => $can_search, 'bbc' => $bbc, 'mask' => $mask, 'enclose' => $enclose, 'placement' => $placement, 'vieworder' => list_getProfileFieldSize() + 1); addProfileField($new_field); } } elseif (isset($_POST['delete']) && $context['field']['colname']) { checkSession(); validateToken('admin-ecp'); // Delete the old data first, then the field. deleteProfileFieldUserData($context['field']['colname']); deleteProfileField($context['fid']); } // Rebuild display cache etc. if (isset($_POST['delete']) || isset($_POST['save']) || isset($_POST['onoff'])) { checkSession(); // Update the display cache updateDisplayCache(); redirectexit('action=admin;area=featuresettings;sa=profile'); } createToken('admin-ecp'); }
/** * Breaks a string up so its no more than width characters long * * - Will break at word boundaries * - If no natural space is found will break mid-word * * @param string $string * @param int $width * @param string $break */ private function _utf8_wordwrap($string, $width = 75, $break = "\n") { $strings = explode($break, $string); $lines = array(); foreach ($strings as $string) { $in_quote = isset($string[0]) && $string[0] === '>'; while (!empty($string)) { // Get the next #width characters before a break (space, punctuation tab etc) if (preg_match('~^(.{1,' . $width . '})(?:\\s|$|,|\\.)~', $string, $matches)) { // Add the #width to the output and set up for the next pass $lines[] = ($in_quote && $matches[1][0] !== '>' ? '> ' : '') . $matches[1]; $string = Util::substr($string, Util::strlen($matches[1])); } else { $lines[] = ($in_quote && $string[0] !== '>' ? '> ' : '') . Util::substr($string, 0, $width); $string = Util::substr($string, $width); } } } // Join it all the shortened sections up on our break characters return implode($break, $lines); }