diff options
Diffstat (limited to 'phpBB/phpbb/content_visibility.php')
-rw-r--r-- | phpBB/phpbb/content_visibility.php | 651 |
1 files changed, 651 insertions, 0 deletions
diff --git a/phpBB/phpbb/content_visibility.php b/phpBB/phpbb/content_visibility.php new file mode 100644 index 0000000000..4ad5f6793e --- /dev/null +++ b/phpBB/phpbb/content_visibility.php @@ -0,0 +1,651 @@ +<?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 +{ + /** + * Database object + * @var phpbb_db_driver + */ + protected $db; + + /** + * User object + * @var phpbb_user + */ + protected $user; + + /** + * Auth object + * @var phpbb_auth + */ + protected $auth; + + /** + * phpBB root path + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP Extension + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param phpbb_auth $auth Auth object + * @param phpbb_db_driver $db Database object + * @param phpbb_user $user User object + * @param string $phpbb_root_path Root path + * @param string $php_ext PHP Extension + * @return null + */ + public function __construct(phpbb_auth $auth, phpbb_db_driver $db, phpbb_user $user, $phpbb_root_path, $php_ext, $forums_table, $posts_table, $topics_table, $users_table) + { + $this->auth = $auth; + $this->db = $db; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->forums_table = $forums_table; + $this->posts_table = $posts_table; + $this->topics_table = $topics_table; + $this->users_table = $users_table; + } + + /** + * 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 + */ + public function can_soft_delete($forum_id, $poster_id, $post_locked) + { + if ($this->auth->acl_get('m_softdelete', $forum_id)) + { + return true; + } + else if ($this->auth->acl_get('f_softdelete', $forum_id) && $poster_id == $this->user->data['user_id'] && !$post_locked) + { + return true; + } + + return false; + } + + /** + * Get the topics post count or the forums post/topic count based on permissions + * + * @param $mode string One of topic_posts, forum_posts or forum_topics + * @param $data array Array with the topic/forum data to calculate from + * @param $forum_id int The forum id is used for permission checks + * @return int Number of posts/topics the user can see in the topic/forum + */ + public function get_count($mode, $data, $forum_id) + { + if (!$this->auth->acl_get('m_approve', $forum_id)) + { + return (int) $data[$mode . '_approved']; + } + + return (int) $data[$mode . '_approved'] + (int) $data[$mode . '_unapproved'] + (int) $data[$mode . '_softdeleted']; + } + + /** + * 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 + */ + public function get_visibility_sql($mode, $forum_id, $table_alias = '') + { + if ($this->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 + */ + public function get_forums_visibility_sql($mode, $forum_ids = array(), $table_alias = '') + { + $where_sql = '('; + + $approve_forums = array_intersect($forum_ids, array_keys($this->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 $this->db->sql_in_set($table_alias . 'forum_id', $approve_forums); + } + else + { + // Moderator can view all posts/topics in some forums + $where_sql .= $this->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 ' . $this->db->sql_in_set($table_alias . 'forum_id', $forum_ids, false, true); + } + + $where_sql .= '(' . $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . ' + AND ' . $this->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 + */ + public function get_global_visibility_sql($mode, $exclude_forum_ids = array(), $table_alias = '') + { + $where_sqls = array(); + + $approve_forums = array_diff(array_keys($this->auth->acl_getf('m_approve', true)), $exclude_forum_ids); + + if (sizeof($exclude_forum_ids)) + { + $where_sqls[] = '(' . $this->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[] = $this->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]; + } + + /** + * Change visibility status of one post or all posts of a topic + * + * @param $visibility int Element of {ITEM_APPROVED, ITEM_DELETED} + * @param $post_id mixed Post ID or array of post IDs 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 array Changed post data, empty array if an error occured. + */ + 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) + { + if (!in_array($visibility, array(ITEM_APPROVED, ITEM_DELETED))) + { + return array(); + } + + if ($post_id) + { + if (is_array($post_id)) + { + $where_sql = $this->db->sql_in_set('post_id', array_map('intval', $post_id)); + } + else + { + $where_sql = 'post_id = ' . (int) $post_id; + } + $where_sql .= ' AND topic_id = ' . (int) $topic_id; + } + else + { + $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; + } + } + + $sql = 'SELECT poster_id, post_id, post_postcount, post_visibility + FROM ' . $this->posts_table . ' + WHERE ' . $where_sql; + $result = $this->db->sql_query($sql); + + $post_ids = $poster_postcounts = $postcounts = $postcount_visibility = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $post_ids[] = (int) $row['post_id']; + + if ($row['post_visibility'] != $visibility) + { + if ($row['post_postcount'] && !isset($poster_postcounts[(int) $row['poster_id']])) + { + $poster_postcounts[(int) $row['poster_id']] = 1; + } + else if ($row['post_postcount']) + { + $poster_postcounts[(int) $row['poster_id']]++; + } + + if (!isset($postcount_visibility[$row['post_visibility']])) + { + $postcount_visibility[$row['post_visibility']] = 1; + } + else + { + $postcount_visibility[$row['post_visibility']]++; + } + } + } + $this->db->sql_freeresult($result); + + if (empty($post_ids)) + { + return array(); + } + + $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 ' . $this->posts_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $data) . ' + WHERE ' . $this->db->sql_in_set('post_id', $post_ids); + $this->db->sql_query($sql); + + // Group the authors by post count, to reduce the number of queries + foreach ($poster_postcounts as $poster_id => $num_posts) + { + $postcounts[$num_posts][] = $poster_id; + } + + // Update users postcounts + foreach ($postcounts as $num_posts => $poster_ids) + { + if ($visibility == ITEM_DELETED) + { + $sql = 'UPDATE ' . $this->users_table . ' + SET user_posts = 0 + WHERE ' . $this->db->sql_in_set('user_id', $poster_ids) . ' + AND user_posts < ' . $num_posts; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->users_table . ' + SET user_posts = user_posts - ' . $num_posts . ' + WHERE ' . $this->db->sql_in_set('user_id', $poster_ids) . ' + AND user_posts >= ' . $num_posts; + $this->db->sql_query($sql); + } + else + { + $sql = 'UPDATE ' . $this->users_table . ' + SET user_posts = user_posts + ' . $num_posts . ' + WHERE ' . $this->db->sql_in_set('user_id', $poster_ids); + $this->db->sql_query($sql); + } + } + + $update_topic_postcount = true; + + // 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) + { + if (!function_exists('sync')) + { + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); + } + + // ... 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); + + // sync recalculates the topic replies and forum posts by itself, so we don't do that. + $update_topic_postcount = false; + } + + // Update the topic's reply count and the forum's post count + if ($update_topic_postcount) + { + $cur_posts = $cur_unapproved_posts = $cur_softdeleted_posts = 0; + foreach ($postcount_visibility as $post_visibility => $visibility_posts) + { + // We need to substract the posts from the counters ... + if ($post_visibility == ITEM_APPROVED) + { + $cur_posts += $visibility_posts; + } + else if ($post_visibility == ITEM_UNAPPROVED) + { + $cur_unapproved_posts += $visibility_posts; + } + else if ($post_visibility == ITEM_DELETED) + { + $cur_softdeleted_posts += $visibility_posts; + } + } + + $sql_ary = array(); + if ($visibility == ITEM_DELETED) + { + if ($cur_posts) + { + $sql_ary['posts_approved'] = ' - ' . $cur_posts; + } + if ($cur_unapproved_posts) + { + $sql_ary['posts_unapproved'] = ' - ' . $cur_unapproved_posts; + } + if ($cur_posts + $cur_unapproved_posts) + { + $sql_ary['posts_softdeleted'] = ' + ' . ($cur_posts + $cur_unapproved_posts); + } + } + else + { + if ($cur_unapproved_posts) + { + $sql_ary['posts_unapproved'] = ' - ' . $cur_unapproved_posts; + } + if ($cur_softdeleted_posts) + { + $sql_ary['posts_softdeleted'] = ' - ' . $cur_softdeleted_posts; + } + if ($cur_softdeleted_posts + $cur_unapproved_posts) + { + $sql_ary['posts_approved'] = ' + ' . ($cur_softdeleted_posts + $cur_unapproved_posts); + } + } + + if (sizeof($sql_ary)) + { + $topic_sql = $forum_sql = array(); + + foreach ($sql_ary as $field => $value_change) + { + $topic_sql[] = 'topic_' . $field . ' = topic_' . $field . $value_change; + $forum_sql[] = 'forum_' . $field . ' = forum_' . $field . $value_change; + } + + // Update the number for replies and posts + $sql = 'UPDATE ' . $this->topics_table . ' + SET ' . implode(', ', $topic_sql) . ' + WHERE topic_id = ' . (int) $topic_id; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->forums_table . ' + SET ' . implode(', ', $forum_sql) . ' + WHERE forum_id = ' . (int) $forum_id; + $this->db->sql_query($sql); + } + } + + return $data; + } + + /** + * 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 array Changed topic data, empty array if an error occured. + */ + public function set_topic_visibility($visibility, $topic_id, $forum_id, $user_id, $time, $reason, $force_update_all = false) + { + if (!in_array($visibility, array(ITEM_APPROVED, ITEM_DELETED))) + { + return array(); + } + + if (!$force_update_all) + { + $sql = 'SELECT topic_visibility, topic_delete_time + FROM ' . $this->topics_table . ' + WHERE topic_id = ' . (int) $topic_id; + $result = $this->db->sql_query($sql); + $original_topic_data = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$original_topic_data) + { + // The topic does not exist... + return array(); + } + } + + // 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 ' . $this->topics_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $data) . ' + WHERE topic_id = ' . (int) $topic_id; + $this->db->sql_query($sql); + + if (!$this->db->sql_affectedrows()) + { + return array(); + } + + 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); + } + + return $data; + } + + /** + * Add post to topic and forum statistics + * + * @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 + */ + public function add_post_to_statistic($data, &$sql_data) + { + $sql_data[$this->topics_table] = (($sql_data[$this->topics_table]) ? $sql_data[$this->topics_table] . ', ' : '') . 'topic_posts_approved = topic_posts_approved + 1'; + + $sql_data[$this->forums_table] = (($sql_data[$this->forums_table]) ? $sql_data[$this->forums_table] . ', ' : '') . 'forum_posts_approved = forum_posts_approved + 1'; + + if ($data['post_postcount']) + { + $sql_data[$this->users_table] = (($sql_data[$this->users_table]) ? $sql_data[$this->users_table] . ', ' : '') . 'user_posts = user_posts + 1'; + } + + set_config_count('num_posts', 1, true); + } + + /** + * Remove post from topic and forum statistics + * + * @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 + */ + public function remove_post_from_statistic($data, &$sql_data) + { + $sql_data[$this->topics_table] = ((!empty($sql_data[$this->topics_table])) ? $sql_data[$this->topics_table] . ', ' : '') . 'topic_posts_approved = topic_posts_approved - 1'; + $sql_data[$this->forums_table] = ((!empty($sql_data[$this->forums_table])) ? $sql_data[$this->forums_table] . ', ' : '') . 'forum_posts_approved = forum_posts_approved - 1'; + + if ($data['post_postcount']) + { + $sql_data[$this->users_table] = ((!empty($sql_data[$this->users_table])) ? $sql_data[$this->users_table] . ', ' : '') . 'user_posts = user_posts - 1'; + } + + set_config_count('num_posts', -1, true); + } + + /** + * Remove topic from forum statistics + * + * @param $topic_id int The topic to act on + * @param $forum_id int Forum where the topic is found + * @param $topic_row array Contains information from 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 + */ + public function remove_topic_from_statistic($topic_id, $forum_id, &$topic_row, &$sql_data) + { + // Do we need to grab some topic informations? + if (!sizeof($topic_row)) + { + $sql = 'SELECT topic_type, topic_posts_approved, topic_posts_unapproved, topic_posts_softdeleted, topic_visibility + FROM ' . $this->topics_table . ' + WHERE topic_id = ' . (int) $topic_id; + $result = $this->db->sql_query($sql); + $topic_row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + } + + // If this is an edited topic or the first post the topic gets completely disapproved later on... + $sql_data[$this->forums_table] = (($sql_data[$this->forums_table]) ? $sql_data[$this->forums_table] . ', ' : '') . 'forum_topics_approved = forum_topics_approved - 1'; + $sql_data[$this->forums_table] .= ', forum_posts_approved = forum_posts_approved - ' . $topic_row['topic_posts_approved']; + $sql_data[$this->forums_table] .= ', forum_posts_unapproved = forum_posts_unapproved - ' . $topic_row['topic_posts_unapproved']; + $sql_data[$this->forums_table] .= ', forum_posts_softdeleted = forum_posts_softdeleted - ' . $topic_row['topic_posts_softdeleted']; + + set_config_count('num_topics', -1, true); + set_config_count('num_posts', $topic_row['topic_posts_approved'] * (-1), true); + + // Get user post count information + $sql = 'SELECT poster_id, COUNT(post_id) AS num_posts + FROM ' . $this->posts_table . ' + WHERE topic_id = ' . (int) $topic_id . ' + AND post_postcount = 1 + AND post_visibility = ' . ITEM_APPROVED . ' + GROUP BY poster_id'; + $result = $this->db->sql_query($sql); + + $postcounts = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $postcounts[(int) $row['num_posts']][] = (int) $row['poster_id']; + } + $this->db->sql_freeresult($result); + + // Decrement users post count + foreach ($postcounts as $num_posts => $poster_ids) + { + $sql = 'UPDATE ' . $this->users_table . ' + SET user_posts = 0 + WHERE user_posts < ' . $num_posts . ' + AND ' . $this->db->sql_in_set('user_id', $poster_ids); + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->users_table . ' + SET user_posts = user_posts - ' . $num_posts . ' + WHERE user_posts >= ' . $num_posts . ' + AND ' . $this->db->sql_in_set('user_id', $poster_ids); + $this->db->sql_query($sql); + } + } +} |