<?php /** * * @package phpbb * @copyright (c) 2012 phpBB Group * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 * */ /** * @ignore */ if (!defined('IN_PHPBB')) { exit; } /** * phpbb_visibility * Handle fetching and setting the visibility for topics and posts * @package phpbb */ class phpbb_content_visibility { /** * Create topic/post visibility SQL for a given forum ID * * Note: Read permissions are not checked. * * @param $mode string Either "topic" or "post" * @param $forum_id int The forum id is used for permission checks * @param $table_alias string Table alias to prefix in SQL queries * @return string The appropriate combination SQL logic for topic/post_visibility */ static public function get_visibility_sql($mode, $forum_id, $table_alias = '') { global $auth; if ($auth->acl_get('m_approve', $forum_id)) { return '1 = 1'; } return $table_alias . $mode . '_visibility = ' . ITEM_APPROVED; } /** * Create topic/post visibility SQL for a set of forums * * Note: Read permissions are not checked. Forums without read permissions * should not be in $forum_ids * * @param $mode string Either "topic" or "post" * @param $forum_ids array Array of forum ids which the posts/topics are limited to * @param $table_alias string Table alias to prefix in SQL queries * @return string The appropriate combination SQL logic for topic/post_visibility */ static public function get_forums_visibility_sql($mode, $forum_ids = array(), $table_alias = '') { global $auth, $db; $where_sql = '('; $approve_forums = array_intersect($forum_ids, array_keys($auth->acl_getf('m_approve', true))); if (sizeof($approve_forums)) { // Remove moderator forums from the rest $forum_ids = array_diff($forum_ids, $approve_forums); if (!sizeof($forum_ids)) { // The user can see all posts/topics in all specified forums return $db->sql_in_set($table_alias . 'forum_id', $approve_forums); } else { // Moderator can view all posts/topics in some forums $where_sql .= $db->sql_in_set($table_alias . 'forum_id', $approve_forums) . ' OR '; } } else { // The user is just a normal user return "$table_alias{$mode}_visibility = " . ITEM_APPROVED . ' AND ' . $db->sql_in_set($table_alias . 'forum_id', $forum_ids, false, true); } $where_sql .= "($table_alias{$mode}_visibility = " . ITEM_APPROVED . ' AND ' . $db->sql_in_set($table_alias . 'forum_id', $forum_ids) . '))'; return $where_sql; } /** * Create topic/post visibility SQL for all forums on the board * * Note: Read permissions are not checked. Forums without read permissions * should be in $exclude_forum_ids * * @param $mode string Either "topic" or "post" * @param $exclude_forum_ids array Array of forum ids which are excluded * @param $table_alias string Table alias to prefix in SQL queries * @return string The appropriate combination SQL logic for topic/post_visibility */ static public function get_global_visibility_sql($mode, $exclude_forum_ids = array(), $table_alias = '') { global $auth, $db; $where_sqls = array(); $approve_forums = array_diff(array_keys($auth->acl_getf('m_approve', true)), $exclude_forum_ids); if (sizeof($exclude_forum_ids)) { $where_sqls[] = '(' . $db->sql_in_set($table_alias . 'forum_id', $exclude_forum_ids, true) . " AND $table_alias{$mode}_visibility = " . ITEM_APPROVED . ')'; } else { $where_sqls[] = "$table_alias{$mode}_visibility = " . ITEM_APPROVED; } if (sizeof($approve_forums)) { $where_sqls[] = $db->sql_in_set($table_alias . 'forum_id', $approve_forums); return '(' . implode(' OR ', $where_sqls) . ')'; } // There is only one element, so we just return that one return $where_sqls[0]; } /** * Set topic visibility * * Allows approving (which is akin to undeleting/restore) or soft deleting an entire topic. * Calls set_post_visibility as needed. * * Note: By default, when a soft deleted topic is restored. Only posts that * were approved at the time of soft deleting, are being restored. * Same applies to soft deleting. Only approved posts will be marked * as soft deleted. * If you want to update all posts, use the force option. * * @param $visibility int Element of {ITEM_APPROVED, ITEM_DELETED} * @param $topic_id mixed Topic ID to act on * @param $forum_id int Forum where $topic_id is found * @param $user_id int User performing the action * @param $time int Timestamp when the action is performed * @param $reason string Reason why the visibilty was changed. * @param $force_update_all bool Force to update all posts within the topic * @return void */ static public function set_topic_visibility($visibility, $topic_id, $forum_id, $user_id, $time, $reason, $force_update_all = false) { global $db; if (!in_array($visibility, array(ITEM_APPROVED, ITEM_DELETED))) { return; } if (!$force_update_all) { $sql = 'SELECT topic_visibility, topic_delete_time FROM ' . TOPICS_TABLE . ' WHERE topic_id = ' . (int) $topic_id; $result = $db->sql_query($sql); $original_topic_data = $db->sql_fetchrow($result); $db->sql_freeresult($result); if (!$original_topic_data) { // The topic does not exist... return; } } // Note, we do not set a reason for the posts, just for the topic $data = array( 'topic_visibility' => (int) $visibility, 'topic_delete_user' => (int) $user_id, 'topic_delete_time' => ((int) $time) ?: time(), 'topic_delete_reason' => truncate_string($reason, 255, 255, false), ); $sql = 'UPDATE ' . TOPICS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $data) . ' WHERE topic_id = ' . (int) $topic_id; $db->sql_query($sql); if (!$force_update_all && $original_topic_data['topic_delete_time'] && $original_topic_data['topic_visibility'] == ITEM_DELETED && $visibility == ITEM_APPROVED) { // If we're restoring a topic we only restore posts, that were soft deleted through the topic soft deletion. self::set_post_visibility($visibility, false, $topic_id, $forum_id, $user_id, $time, '', true, true, $original_topic_data['topic_visibility'], $original_topic_data['topic_delete_time']); } else if (!$force_update_all && $original_topic_data['topic_visibility'] == ITEM_APPROVED && $visibility == ITEM_DELETED) { // If we're soft deleting a topic we only approved posts are soft deleted. self::set_post_visibility($visibility, false, $topic_id, $forum_id, $user_id, $time, '', true, true, $original_topic_data['topic_visibility']); } else { self::set_post_visibility($visibility, false, $topic_id, $forum_id, $user_id, $time, '', true, true); } } /** * Change visibility status of one post or a hole topic * * @param $visibility int Element of {ITEM_APPROVED, ITEM_DELETED} * @param $post_id mixed Post ID to act on, if it is empty, * all posts of topic_id will be modified * @param $topic_id int Topic where $post_id is found * @param $forum_id int Forum where $topic_id is found * @param $user_id int User performing the action * @param $time int Timestamp when the action is performed * @param $reason string Reason why the visibilty was changed. * @param $is_starter bool Is this the first post of the topic changed? * @param $is_latest bool Is this the last post of the topic changed? * @param $limit_visibility mixed Limit updating per topic_id to a certain visibility * @param $limit_delete_time mixed Limit updating per topic_id to a certain deletion time * @return void */ static public function set_post_visibility($visibility, $post_id, $topic_id, $forum_id, $user_id, $time, $reason, $is_starter, $is_latest, $limit_visibility = false, $limit_delete_time = false) { global $db; if (!in_array($visibility, array(ITEM_APPROVED, ITEM_DELETED))) { return; } if ($post_id) { $where_sql = 'post_id = ' . (int) $post_id; } else if ($topic_id) { $where_sql = 'topic_id = ' . (int) $topic_id; // Limit the posts to a certain visibility and deletion time // This allows us to only restore posts, that were approved // when the topic got soft deleted. So previous soft deleted // and unapproved posts are still soft deleted/unapproved if ($limit_visibility !== false) { $where_sql .= ' AND post_visibility = ' . (int) $limit_visibility; } if ($limit_delete_time !== false) { $where_sql .= ' AND post_delete_time = ' . (int) $limit_delete_time; } } else { return; } $data = array( 'post_visibility' => (int) $visibility, 'post_delete_user' => (int) $user_id, 'post_delete_time' => ((int) $time) ?: time(), 'post_delete_reason' => truncate_string($reason, 255, 255, false), ); $sql = 'UPDATE ' . POSTS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $data) . ' WHERE ' . $where_sql; $db->sql_query($sql); // Sync the first/last topic information if needed if (!$is_starter && $is_latest) { // update_post_information can only update the last post info ... if ($topic_id) { update_post_information('topic', $topic_id, false); } if ($forum_id) { update_post_information('forum', $forum_id, false); } } else if ($is_starter && $topic_id) { // ... so we need to use sync, if the first post is changed. // The forum is resynced recursive by sync() itself. sync('topic', 'topic_id', $topic_id, true); } } /** * Can the current logged-in user soft-delete posts? * * @param $forum_id int Forum ID whose permissions to check * @param $poster_id int Poster ID of the post in question * @param $post_locked bool Is the post locked? * @return bool */ static function can_soft_delete($forum_id, $poster_id, $post_locked) { global $auth, $user; if ($auth->acl_get('m_softdelete', $forum_id)) { return true; } else if ($auth->acl_get('f_softdelete', $forum_id) && $poster_id == $user->data['user_id'] && !$post_locked) { return true; } return false; } /** * Do the required math to hide a complete topic (going from approved to deleted) * @param $topic_id - int - the topic to act on * @param $forum_id - int - the forum where the topic resides * @param $topic_row - array - data about the topic, may be empty at call time * @param $sql_data - array - populated with the SQL changes, may be empty at call time * @return void */ static public function hide_topic($topic_id, $forum_id, &$topic_row, &$sql_data) { global $db; // Do we need to grab some topic informations? if (!sizeof($topic_row)) { $sql = 'SELECT topic_type, topic_replies, topic_replies_real, topic_visibility FROM ' . TOPICS_TABLE . ' WHERE topic_id = ' . $topic_id; $result = $db->sql_query($sql); $topic_row = $db->sql_fetchrow($result); $db->sql_freeresult($result); } // If this is an edited topic or the first post the topic gets completely disapproved later on... $sql_data[FORUMS_TABLE] = 'forum_topics = forum_topics - 1'; $sql_data[FORUMS_TABLE] .= ', forum_posts = forum_posts - ' . ($topic_row['topic_replies'] + 1); set_config_count('num_topics', -1, true); set_config_count('num_posts', ($topic_row['topic_replies'] + 1) * (-1), true); // Only decrement this post, since this is the one non-approved now // /** * @todo: this is wrong, it should rely on post_postcount * also a user might have more than one post in the topic * if ($auth->acl_get('f_postcount', $forum_id)) { $sql_data[USERS_TABLE] = 'user_posts = user_posts - 1'; } */ } /** * Do the required math to hide a single post (going from approved to deleted) * Notably, we do _not_ need the post ID to do this operation. We're only changing statistic caches * @param $forum_id - int - the forum where the topic resides * @param $current_time - int - passed for consistency instead of calling time() internally * @param $data - array - contains information from the topics table about given topic * @param $sql_data - array - populated with the SQL changes, may be empty at call time * @return void */ //static public function remove_post_from_postcount($forum_id, $current_time, $data, &$sql_data) static public function hide_post($forum_id, $current_time, $data, &$sql_data) { $sql_data[TOPICS_TABLE] = 'topic_last_view_time = ' . $current_time; if ($data['topic_replies'] > 0) { $sql_data[TOPICS_TABLE] .= ', topic_replies = topic_replies - 1,'; } $sql_data[FORUMS_TABLE] = 'forum_posts = forum_posts - 1'; if ($data['post_postcount']) { $sql_data[USERS_TABLE] = 'user_posts = user_posts - 1'; } set_config_count('num_posts', -1, true); } /** * One function to rule them all ... and unhide posts and topics. This could * reasonably be broken up, I straight copied this code from the mcp_queue.php * file here for global access. * @param $mode - string - member of the set {'approve', 'restore'} * @param $post_info - array - Contains info from post U topics table about * the posts/topics in question * @param $post_id_list - array of ints - the set of posts being worked on */ static public function unhide_posts_topics($mode, $post_info, $post_id_list) { global $db, $config; // If Topic -> total_topics = total_topics+1, total_posts = total_posts+1, forum_topics = forum_topics+1, forum_posts = forum_posts+1 // If Post -> total_posts = total_posts+1, forum_posts = forum_posts+1, topic_replies = topic_replies+1 $total_topics = $total_posts = 0; $topic_approve_sql = $post_approve_sql = $topic_id_list = $forum_id_list = $approve_log = array(); $user_posts_sql = $post_approved_list = array(); foreach ($post_info as $post_id => $post_data) { if ($post_data['post_visibility'] == ITEM_APPROVED) { $post_approved_list[] = $post_id; continue; } $topic_id_list[$post_data['topic_id']] = 1; if ($post_data['forum_id']) { $forum_id_list[$post_data['forum_id']] = 1; } // User post update (we do not care about topic or post, since user posts are strictly connected to posts) // But we care about forums where post counts get not increased. ;) if ($post_data['post_postcount']) { $user_posts_sql[$post_data['poster_id']] = (empty($user_posts_sql[$post_data['poster_id']])) ? 1 : $user_posts_sql[$post_data['poster_id']] + 1; } // Topic or Post. ;) if ($post_data['topic_first_post_id'] == $post_id) { if ($post_data['forum_id']) { $total_topics++; } $topic_approve_sql[] = $post_data['topic_id']; $approve_log[] = array( 'type' => 'topic', 'post_subject' => $post_data['post_subject'], 'forum_id' => $post_data['forum_id'], 'topic_id' => $post_data['topic_id'], ); } else { $approve_log[] = array( 'type' => 'post', 'post_subject' => $post_data['post_subject'], 'forum_id' => $post_data['forum_id'], 'topic_id' => $post_data['topic_id'], ); } if ($post_data['forum_id']) { $total_posts++; // Increment by topic_replies if we approve a topic... // This works because we do not adjust the topic_replies when re-approving a topic after an edit. if ($post_data['topic_first_post_id'] == $post_id && $post_data['topic_replies']) { $total_posts += $post_data['topic_replies']; } } $post_approve_sql[] = $post_id; } $post_id_list = array_values(array_diff($post_id_list, $post_approved_list)); for ($i = 0, $size = sizeof($post_approved_list); $i < $size; $i++) { unset($post_info[$post_approved_list[$i]]); } if (sizeof($topic_approve_sql)) { $sql = 'UPDATE ' . TOPICS_TABLE . ' SET topic_visibility = ' . ITEM_APPROVED . ' WHERE ' . $db->sql_in_set('topic_id', $topic_approve_sql); $db->sql_query($sql); } if (sizeof($post_approve_sql)) { $sql = 'UPDATE ' . POSTS_TABLE . ' SET post_visibility = ' . ITEM_APPROVED . ' WHERE ' . $db->sql_in_set('post_id', $post_approve_sql); $db->sql_query($sql); } unset($topic_approve_sql, $post_approve_sql); foreach ($approve_log as $log_data) { add_log('mod', $log_data['forum_id'], $log_data['topic_id'], ($log_data['type'] == 'topic') ? 'LOG_TOPIC_' . strtoupper($mode) . 'D' : 'LOG_POST_' . strtoupper($mode) . 'D', $log_data['post_subject']); } if (sizeof($user_posts_sql)) { // Try to minimize the query count by merging users with the same post count additions $user_posts_update = array(); foreach ($user_posts_sql as $user_id => $user_posts) { $user_posts_update[$user_posts][] = $user_id; } foreach ($user_posts_update as $user_posts => $user_id_ary) { $sql = 'UPDATE ' . USERS_TABLE . ' SET user_posts = user_posts + ' . $user_posts . ' WHERE ' . $db->sql_in_set('user_id', $user_id_ary); $db->sql_query($sql); } } if ($total_topics) { set_config_count('num_topics', $total_topics, true); } if ($total_posts) { set_config_count('num_posts', $total_posts, true); } if (!function_exists('sync')) { global $phpbb_root_path, $phpEx; include ($phpbb_root_path . 'includes/functions_admin.'.$phpEx); } sync('topic', 'topic_id', array_keys($topic_id_list), true); sync('forum', 'forum_id', array_keys($forum_id_list), true, true); unset($topic_id_list, $forum_id_list); if ($total_topics) { $success_msg = ($total_topics == 1) ? 'TOPIC_APPROVED_SUCCESS' : 'TOPICS_APPROVED_SUCCESS'; } else { $success_msg = (sizeof($post_id_list) + sizeof($post_approved_list) == 1) ? 'POST_APPROVED_SUCCESS' : 'POSTS_APPROVED_SUCCESS'; } return $success_msg; } }