From d69ff9623655b6950ffde408213d02baccd8a9f0 Mon Sep 17 00:00:00 2001
From: javiexin <javiexin@gmail.com>
Date: Sun, 9 Jul 2017 20:07:16 +0200
Subject: [PATCH 1/4] [ticket/15266] Fix events in content_visibility

Fixes core.phpbb_content_visibility_get_visibility_sql_before,
core.phpbb_content_visibility_get_forums_visibility_before,
core.phpbb_content_visibility_get_global_visibility_before

Does not modify the events, but fixes some comments, and changes
the rest of the function to allow (at least) the documented
event functionality.

PHPBB3-15266
---
 phpBB/phpbb/content_visibility.php | 70 +++++++++++-------------------
 1 file changed, 26 insertions(+), 44 deletions(-)

diff --git a/phpBB/phpbb/content_visibility.php b/phpBB/phpbb/content_visibility.php
index bf7dc2c703..bbff7a1fae 100644
--- a/phpBB/phpbb/content_visibility.php
+++ b/phpBB/phpbb/content_visibility.php
@@ -176,10 +176,14 @@ class content_visibility
 
 		if ($this->auth->acl_get('m_approve', $forum_id))
 		{
-			return $where_sql . '1 = 1';
+			$where_sql .= '1 = 1';
+		}
+		else
+		{
+			$where_sql .= $table_alias . $mode . '_visibility = ' . ITEM_APPROVED;
 		}
 
-		return $where_sql . $table_alias . $mode . '_visibility = ' . ITEM_APPROVED;
+		return '(' . $where_sql . ')';
 	}
 
 	/**
@@ -195,16 +199,21 @@ class content_visibility
 	*/
 	public function get_forums_visibility_sql($mode, $forum_ids = array(), $table_alias = '')
 	{
-		$where_sql = '(';
+		$where_sql = '';
 
-		$approve_forums = array_intersect($forum_ids, array_keys($this->auth->acl_getf('m_approve', true)));
+		$approve_forums = array_keys($this->auth->acl_getf('m_approve', true));
+		if ($forum_ids && $approve_forums)
+		{
+			$approve_forums = array_intersect($forum_ids, $approve_forums);
+			$forum_ids = array_diff($forum_ids, $approve_forums);
+		}
 
 		$get_forums_visibility_sql_overwrite = false;
 		/**
 		* Allow changing the result of calling get_forums_visibility_sql
 		*
 		* @event core.phpbb_content_visibility_get_forums_visibility_before
-		* @var	string		where_sql							The action the user tried to execute
+		* @var	string		where_sql							Extra visibility conditions. It must end with either an SQL "AND" or an "OR"
 		* @var	string		mode								Either "topic" or "post" depending on the query this is being used in
 		* @var	array		forum_ids							Array of forum ids which the posts/topics are limited to
 		* @var	string		table_alias							Table alias to prefix in SQL queries
@@ -229,33 +238,13 @@ class content_visibility
 			return $get_forums_visibility_sql_overwrite;
 		}
 
-		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 $where_sql . $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 $where_sql . $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . '
-				AND ' . $this->db->sql_in_set($table_alias . 'forum_id', $forum_ids, false, true) . ')';
-		}
-
+		// Moderator can view all posts/topics in the moderated forums
+		$where_sql .= '(' . $this->db->sql_in_set($table_alias . 'forum_id', $approve_forums, false, true) . ' OR ';
+		// Normal user can view approved items only
 		$where_sql .= '(' . $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . '
-			AND ' . $this->db->sql_in_set($table_alias . 'forum_id', $forum_ids) . '))';
+			AND ' . $this->db->sql_in_set($table_alias . 'forum_id', $forum_ids, false, true) . '))';
 
-		return $where_sql;
+		return '(' . $where_sql . ')';
 	}
 
 	/**
@@ -281,12 +270,12 @@ class content_visibility
 		* Allow changing the result of calling get_global_visibility_sql
 		*
 		* @event core.phpbb_content_visibility_get_global_visibility_before
-		* @var	array		where_sqls							The action the user tried to execute
+		* @var	array		where_sqls							Array of extra visibility conditions. Will be joined by imploding with "OR".
 		* @var	string		mode								Either "topic" or "post" depending on the query this is being used in
 		* @var	array		exclude_forum_ids					Array of forum ids the current user doesn't have access to
 		* @var	string		table_alias							Table alias to prefix in SQL queries
 		* @var	array		approve_forums						Array of forums where the user has m_approve permissions
-		* @var	string		visibility_sql_overwrite	Forces the function to return an implosion of where_sqls (joined by "OR")
+		* @var	string		visibility_sql_overwrite	If a string, forces the function to return visibility_sql_overwrite after executing the event
 		* @since 3.1.3-RC1
 		*/
 		$vars = array(
@@ -304,24 +293,17 @@ class content_visibility
 			return $visibility_sql_overwrite;
 		}
 
-		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;
-		}
+		// Include approved items in all forums but the excluded
+		$where_sqls[] = '(' . $this->db->sql_in_set($table_alias . 'forum_id', $exclude_forum_ids, true, true) . '
+			AND ' . $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . ')';
 
+		// If user has moderator permissions, add everything in the moderated forums
 		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];
+		return '(' . implode(' OR ', $where_sqls) . ')';
 	}
 
 	/**

From 31b93280ee906f7ac4052540cffc210bf323f056 Mon Sep 17 00:00:00 2001
From: javiexin <javiexin@gmail.com>
Date: Mon, 10 Jul 2017 15:11:22 +0200
Subject: [PATCH 2/4] [ticket/15266] Fix events in content_visibility

Additional errors found.  The event parameter 'timestamp' does
not exist, it is really called 'time'.  Affects four events:
core.set_post_visibility_before_sql
core.set_post_visibility_after
core.set_topic_visibility_before_sql
core.set_topic_visibility_after

PHPBB3-15266
---
 phpBB/phpbb/content_visibility.php | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/phpBB/phpbb/content_visibility.php b/phpBB/phpbb/content_visibility.php
index bbff7a1fae..6abf8f996e 100644
--- a/phpBB/phpbb/content_visibility.php
+++ b/phpBB/phpbb/content_visibility.php
@@ -419,7 +419,7 @@ class content_visibility
 		 * @var			int			topic_id		Topic of the post IDs to be modified.
 		 * @var			int			forum_id		Forum ID that the topic_id resides in.
 		 * @var			int			user_id			User ID doing this action.
-		 * @var			int			timestamp		Timestamp of this action.
+		 * @var			int			time			Timestamp of this action.
 		 * @var			string		reason			Reason specified by the user for this change.
 		 * @var			bool		is_starter		Are we changing the topic's starter?
 		 * @var			bool		is_latest		Are we changing the topic's latest post?
@@ -432,7 +432,7 @@ class content_visibility
 			'topic_id',
 			'forum_id',
 			'user_id',
-			'timestamp',
+			'time',
 			'reason',
 			'is_starter',
 			'is_latest',
@@ -604,7 +604,7 @@ class content_visibility
 		 * @var			int			topic_id		Topic of the post IDs to be modified.
 		 * @var			int			forum_id		Forum ID that the topic_id resides in.
 		 * @var			int			user_id			User ID doing this action.
-		 * @var			int			timestamp		Timestamp of this action.
+		 * @var			int			time			Timestamp of this action.
 		 * @var			string		reason			Reason specified by the user for this change.
 		 * @var			bool		is_starter		Are we changing the topic's starter?
 		 * @var			bool		is_latest		Are we changing the topic's latest post?
@@ -617,7 +617,7 @@ class content_visibility
 			'topic_id',
 			'forum_id',
 			'user_id',
-			'timestamp',
+			'time',
 			'reason',
 			'is_starter',
 			'is_latest',
@@ -691,7 +691,7 @@ class content_visibility
 		 * @var			int			topic_id			Topic of the post IDs to be modified.
 		 * @var			int			forum_id			Forum ID that the topic_id resides in.
 		 * @var			int			user_id				User ID doing this action.
-		 * @var			int			timestamp			Timestamp of this action.
+		 * @var			int			time				Timestamp of this action.
 		 * @var			string		reason				Reason specified by the user for this change.
 		 * @var			bool		force_update_all	Force an update on all posts within the topic, regardless of their current approval state.
 		 * @var			array		data				The data array for this action.
@@ -702,7 +702,7 @@ class content_visibility
 			'topic_id',
 			'forum_id',
 			'user_id',
-			'timestamp',
+			'time',
 			'reason',
 			'force_update_all',
 			'data',
@@ -740,7 +740,7 @@ class content_visibility
 		 * @var			int			topic_id			Topic of the post IDs to be modified.
 		 * @var			int			forum_id			Forum ID that the topic_id resides in.
 		 * @var			int			user_id				User ID doing this action.
-		 * @var			int			timestamp			Timestamp of this action.
+		 * @var			int			time				Timestamp of this action.
 		 * @var			string		reason				Reason specified by the user for this change.
 		 * @var			bool		force_update_all	Force an update on all posts within the topic, regardless of their current approval state.
 		 * @var			array		data				The data array for this action.
@@ -751,7 +751,7 @@ class content_visibility
 			'topic_id',
 			'forum_id',
 			'user_id',
-			'timestamp',
+			'time',
 			'reason',
 			'force_update_all',
 			'data',

From bd81af3b9e3174d1ea2dbf405b694e535e8b1b40 Mon Sep 17 00:00:00 2001
From: javiexin <javiexin@gmail.com>
Date: Wed, 12 Jul 2017 13:25:22 +0200
Subject: [PATCH 3/4] [ticket/15266] Expand functionality of content_visibility

Added new function "is_visible", and replaced several immediate
uses of the above, including a single event in the new function
to handle change in all places consistently, and much simpler.

PHPBB3-15266
---
 phpBB/download/file.php               |  4 ++-
 phpBB/includes/functions_download.php |  6 ++++-
 phpBB/includes/functions_mcp.php      |  6 +++--
 phpBB/phpbb/content_visibility.php    | 36 +++++++++++++++++++++++++++
 phpBB/viewforum.php                   |  2 +-
 phpBB/viewtopic.php                   |  2 +-
 6 files changed, 50 insertions(+), 6 deletions(-)

diff --git a/phpBB/download/file.php b/phpBB/download/file.php
index e60ffad6b0..c0837ab7a9 100644
--- a/phpBB/download/file.php
+++ b/phpBB/download/file.php
@@ -149,6 +149,8 @@ $user->session_begin(false);
 $auth->acl($user->data);
 $user->setup('viewtopic');
 
+$phpbb_content_visibility = $phpbb_container->get('content.visibility');
+
 if (!$config['allow_attachments'] && !$config['allow_pm_attach'])
 {
 	send_status_line(404, 'Not Found');
@@ -215,7 +217,7 @@ else
 			$post_row = $db->sql_fetchrow($result);
 			$db->sql_freeresult($result);
 
-			if (!$post_row || ($post_row['post_visibility'] != ITEM_APPROVED && !$auth->acl_get('m_approve', $post_row['forum_id'])))
+			if (!$post_row || !$phpbb_content_visibility->is_visible('post', $post_row['forum_id'], $post_row))
 			{
 				// Attachment of a soft deleted post and the user is not allowed to see the post
 				send_status_line(404, 'Not Found');
diff --git a/phpBB/includes/functions_download.php b/phpBB/includes/functions_download.php
index 053e362682..ad1762da63 100644
--- a/phpBB/includes/functions_download.php
+++ b/phpBB/includes/functions_download.php
@@ -650,6 +650,8 @@ function phpbb_increment_downloads($db, $ids)
 */
 function phpbb_download_handle_forum_auth($db, $auth, $topic_id)
 {
+	global $phpbb_container;
+
 	$sql_array = array(
 		'SELECT'	=> 't.topic_visibility, t.forum_id, f.forum_name, f.forum_password, f.parent_id',
 		'FROM'		=> array(
@@ -665,7 +667,9 @@ function phpbb_download_handle_forum_auth($db, $auth, $topic_id)
 	$row = $db->sql_fetchrow($result);
 	$db->sql_freeresult($result);
 
-	if ($row && $row['topic_visibility'] != ITEM_APPROVED && !$auth->acl_get('m_approve', $row['forum_id']))
+	$phpbb_content_visibility = $phpbb_container->get('content.visibility');
+
+	if ($row && !$phpbb_content_visibility->is_visible('topic', $row['forum_id'], $row))
 	{
 		send_status_line(404, 'Not Found');
 		trigger_error('ERROR_NO_ATTACHMENT');
diff --git a/phpBB/includes/functions_mcp.php b/phpBB/includes/functions_mcp.php
index 1e08864bdc..0e26ca9b2a 100644
--- a/phpBB/includes/functions_mcp.php
+++ b/phpBB/includes/functions_mcp.php
@@ -197,7 +197,7 @@ function phpbb_get_topic_data($topic_ids, $acl_list = false, $read_tracking = fa
 */
 function phpbb_get_post_data($post_ids, $acl_list = false, $read_tracking = false)
 {
-	global $db, $auth, $config, $user;
+	global $db, $auth, $config, $user, $phpbb_container;
 
 	$rowset = array();
 
@@ -246,6 +246,8 @@ function phpbb_get_post_data($post_ids, $acl_list = false, $read_tracking = fals
 	$result = $db->sql_query($sql);
 	unset($sql_array);
 
+	$phpbb_content_visibility = $phpbb_container->get('content.visibility');
+
 	while ($row = $db->sql_fetchrow($result))
 	{
 		if ($acl_list && !$auth->acl_gets($acl_list, $row['forum_id']))
@@ -253,7 +255,7 @@ function phpbb_get_post_data($post_ids, $acl_list = false, $read_tracking = fals
 			continue;
 		}
 
-		if ($row['post_visibility'] != ITEM_APPROVED && !$auth->acl_get('m_approve', $row['forum_id']))
+		if (!$phpbb_content_visibility->is_visible('post', $row['forum_id'], $row))
 		{
 			// Moderators without the permission to approve post should at least not see them. ;)
 			continue;
diff --git a/phpBB/phpbb/content_visibility.php b/phpBB/phpbb/content_visibility.php
index 6abf8f996e..be552c7761 100644
--- a/phpBB/phpbb/content_visibility.php
+++ b/phpBB/phpbb/content_visibility.php
@@ -131,6 +131,42 @@ class content_visibility
 		return (int) $data[$mode . '_approved'] + (int) $data[$mode . '_unapproved'] + (int) $data[$mode . '_softdeleted'];
 	}
 
+
+	/**
+	* Check topic/post visibility 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 $data		array	Array with item information to check visibility
+	* @return bool		True if the item is visible, false if not
+	*/
+	public function is_visible($mode, $forum_id, $data)
+	{
+		$is_visible = $this->auth->acl_get('m_approve', $forum_id) || $data[$mode . '_visibility'] == ITEM_APPROVED;
+
+		/**
+		* Allow changing the result of calling is_visible
+		*
+		* @event core.phpbb_content_visibility_is_visible
+		* @var	bool		is_visible			Default visibility condition, to be modified by extensions if needed.
+		* @var	string		mode				Either "topic" or "post"
+		* @var	int			forum_id			Forum id of the current item
+		* @var	array		data				Array of item information
+		* @since 3.1.12-RC1
+		*/
+		$vars = array(
+			'is_visible',
+			'mode',
+			'forum_id',
+			'data',
+		);
+		extract($this->phpbb_dispatcher->trigger_event('core.phpbb_content_visibility_is_visible', compact($vars)));
+
+		return $is_visible;
+	}
+
 	/**
 	* Create topic/post visibility SQL for a given forum ID
 	*
diff --git a/phpBB/viewforum.php b/phpBB/viewforum.php
index 5c51975150..5e62b3c68a 100644
--- a/phpBB/viewforum.php
+++ b/phpBB/viewforum.php
@@ -520,7 +520,7 @@ if ($forum_data['forum_type'] == FORUM_POST)
 
 	while ($row = $db->sql_fetchrow($result))
 	{
-		if ($row['topic_visibility'] != ITEM_APPROVED && !$auth->acl_get('m_approve', $row['forum_id']))
+		if (!$phpbb_content_visibility->is_visible('topic', $row['forum_id'], $row))
 		{
 			// Do not display announcements that are waiting for approval or soft deleted.
 			continue;
diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php
index 378e2d8f97..0dad2796b3 100644
--- a/phpBB/viewtopic.php
+++ b/phpBB/viewtopic.php
@@ -262,7 +262,7 @@ if (!$topic_data)
 $forum_id = (int) $topic_data['forum_id'];
 
 // Now we know the forum_id and can check the permissions
-if ($topic_data['topic_visibility'] != ITEM_APPROVED && !$auth->acl_get('m_approve', $forum_id))
+if (!$phpbb_content_visibility->is_visible('topic', $forum_id, $topic_data))
 {
 	trigger_error('NO_TOPIC');
 }

From dc48f28da1d4ff4ae2956648c70b8291de1d904e Mon Sep 17 00:00:00 2001
From: Marc Alexander <admin@m-a-styles.de>
Date: Wed, 27 Dec 2017 12:24:02 +0100
Subject: [PATCH 4/4] [ticket/15266] Update since, add changed, and use empty
 where applicable

PHPBB3-15266
---
 phpBB/phpbb/content_visibility.php | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/phpBB/phpbb/content_visibility.php b/phpBB/phpbb/content_visibility.php
index be552c7761..237300894b 100644
--- a/phpBB/phpbb/content_visibility.php
+++ b/phpBB/phpbb/content_visibility.php
@@ -154,7 +154,7 @@ class content_visibility
 		* @var	string		mode				Either "topic" or "post"
 		* @var	int			forum_id			Forum id of the current item
 		* @var	array		data				Array of item information
-		* @since 3.1.12-RC1
+		* @since 3.2.2-RC1
 		*/
 		$vars = array(
 			'is_visible',
@@ -238,7 +238,7 @@ class content_visibility
 		$where_sql = '';
 
 		$approve_forums = array_keys($this->auth->acl_getf('m_approve', true));
-		if ($forum_ids && $approve_forums)
+		if (!empty($forum_ids) && !empty($approve_forums))
 		{
 			$approve_forums = array_intersect($forum_ids, $approve_forums);
 			$forum_ids = array_diff($forum_ids, $approve_forums);
@@ -311,7 +311,7 @@ class content_visibility
 		* @var	array		exclude_forum_ids					Array of forum ids the current user doesn't have access to
 		* @var	string		table_alias							Table alias to prefix in SQL queries
 		* @var	array		approve_forums						Array of forums where the user has m_approve permissions
-		* @var	string		visibility_sql_overwrite	If a string, forces the function to return visibility_sql_overwrite after executing the event
+		* @var	string		visibility_sql_overwrite			If not empty, forces the function to return visibility_sql_overwrite after executing the event
 		* @since 3.1.3-RC1
 		*/
 		$vars = array(
@@ -461,6 +461,7 @@ class content_visibility
 		 * @var			bool		is_latest		Are we changing the topic's latest post?
 		 * @var			array		data			The data array for this action.
 		 * @since 3.1.10-RC1
+		 * @changed 3.2.2-RC1 Use time instead of non-existent timestamp
 		 */
 		$vars = array(
 			'visibility',
@@ -646,6 +647,7 @@ class content_visibility
 		 * @var			bool		is_latest		Are we changing the topic's latest post?
 		 * @var			array		data			The data array for this action.
 		 * @since 3.1.10-RC1
+		 * @changed 3.2.2-RC1 Use time instead of non-existent timestamp
 		 */
 		$vars = array(
 			'visibility',
@@ -732,6 +734,7 @@ class content_visibility
 		 * @var			bool		force_update_all	Force an update on all posts within the topic, regardless of their current approval state.
 		 * @var			array		data				The data array for this action.
 		 * @since 3.1.10-RC1
+		 * @changed 3.2.2-RC1 Use time instead of non-existent timestamp
 		 */
 		$vars = array(
 			'visibility',
@@ -781,6 +784,7 @@ class content_visibility
 		 * @var			bool		force_update_all	Force an update on all posts within the topic, regardless of their current approval state.
 		 * @var			array		data				The data array for this action.
 		 * @since 3.1.10-RC1
+		 * @changed 3.2.2-RC1 Use time instead of non-existent timestamp
 		 */
 		$vars = array(
 			'visibility',