From a7d75702cc43b13b6100d993df6d8d409e5e2351 Mon Sep 17 00:00:00 2001
From: Michael <heluecht@pirati.ca>
Date: Tue, 4 Aug 2020 04:47:02 +0000
Subject: [PATCH] "Contact\User" class created

---
 mod/suggest.php                |   2 +-
 src/Core/Session.php           |   2 +-
 src/Model/Contact.php          | 283 +--------------------------------
 src/Model/Contact/Relation.php | 104 ++++++++++++
 src/Model/Contact/User.php     | 204 ++++++++++++++++++++++++
 src/Model/Item.php             |   8 +-
 src/Module/Contact.php         |  20 +--
 update.php                     |   4 +-
 view/theme/vier/theme.php      |   2 +-
 9 files changed, 330 insertions(+), 299 deletions(-)
 create mode 100644 src/Model/Contact/User.php

diff --git a/mod/suggest.php b/mod/suggest.php
index 192f59d910..0965978ce0 100644
--- a/mod/suggest.php
+++ b/mod/suggest.php
@@ -39,7 +39,7 @@ function suggest_content(App $a)
 	DI::page()['aside'] .= Widget::findPeople();
 	DI::page()['aside'] .= Widget::follow();
 
-	$contacts = Contact::getSuggestions(local_user());
+	$contacts = Contact\Relation::getSuggestions(local_user());
 	if (!DBA::isResult($contacts)) {
 		return DI::l10n()->t('No suggestions available. If this is a new site, please try again in 24 hours.');
 	}
diff --git a/src/Core/Session.php b/src/Core/Session.php
index f08c68ed08..767dcb513f 100644
--- a/src/Core/Session.php
+++ b/src/Core/Session.php
@@ -111,7 +111,7 @@ class Session
 
 		$remote_contacts = DBA::select('contact', ['id', 'uid'], ['nurl' => Strings::normaliseLink($session->get('my_url')), 'rel' => [Contact::FOLLOWER, Contact::FRIEND], 'self' => false]);
 		while ($contact = DBA::fetch($remote_contacts)) {
-			if (($contact['uid'] == 0) || Contact::isBlockedByUser($contact['id'], $contact['uid'])) {
+			if (($contact['uid'] == 0) || Contact\User::isBlocked($contact['id'], $contact['uid'])) {
 				continue;
 			}
 
diff --git a/src/Model/Contact.php b/src/Model/Contact.php
index 3bd826945f..08cde31eeb 100644
--- a/src/Model/Contact.php
+++ b/src/Model/Contact.php
@@ -306,7 +306,7 @@ class Contact
 	 */
 	public static function isFollower($cid, $uid)
 	{
-		if (self::isBlockedByUser($cid, $uid)) {
+		if (Contact\User::isBlocked($cid, $uid)) {
 			return false;
 		}
 
@@ -352,7 +352,7 @@ class Contact
 	 */
 	public static function isSharing($cid, $uid)
 	{
-		if (self::isBlockedByUser($cid, $uid)) {
+		if (Contact\User::isBlocked($cid, $uid)) {
 			return false;
 		}
 
@@ -537,179 +537,6 @@ class Contact
 		}
 	}
 
-	/**
-	 * Block contact id for user id
-	 *
-	 * @param int     $cid     Either public contact id or user's contact id
-	 * @param int     $uid     User ID
-	 * @param boolean $blocked Is the contact blocked or unblocked?
-	 * @throws \Exception
-	 */
-	public static function setBlockedForUser($cid, $uid, $blocked)
-	{
-		$cdata = self::getPublicAndUserContacID($cid, $uid);
-		if (empty($cdata)) {
-			return;
-		}
-
-		if ($cdata['user'] != 0) {
-			DBA::update('contact', ['blocked' => $blocked], ['id' => $cdata['user'], 'pending' => false]);
-		}
-
-		DBA::update('user-contact', ['blocked' => $blocked], ['cid' => $cdata['public'], 'uid' => $uid], true);
-	}
-
-	/**
-	 * Returns "block" state for contact id and user id
-	 *
-	 * @param int $cid Either public contact id or user's contact id
-	 * @param int $uid User ID
-	 *
-	 * @return boolean is the contact id blocked for the given user?
-	 * @throws \Exception
-	 */
-	public static function isBlockedByUser($cid, $uid)
-	{
-		$cdata = self::getPublicAndUserContacID($cid, $uid);
-		if (empty($cdata)) {
-			return;
-		}
-
-		$public_blocked = false;
-
-		if (!empty($cdata['public'])) {
-			$public_contact = DBA::selectFirst('user-contact', ['blocked'], ['cid' => $cdata['public'], 'uid' => $uid]);
-			if (DBA::isResult($public_contact)) {
-				$public_blocked = $public_contact['blocked'];
-			}
-		}
-
-		$user_blocked = $public_blocked;
-
-		if (!empty($cdata['user'])) {
-			$user_contact = DBA::selectFirst('contact', ['blocked'], ['id' => $cdata['user'], 'pending' => false]);
-			if (DBA::isResult($user_contact)) {
-				$user_blocked = $user_contact['blocked'];
-			}
-		}
-
-		if ($user_blocked != $public_blocked) {
-			DBA::update('user-contact', ['blocked' => $user_blocked], ['cid' => $cdata['public'], 'uid' => $uid], true);
-		}
-
-		return $user_blocked;
-	}
-
-	/**
-	 * Ignore contact id for user id
-	 *
-	 * @param int     $cid     Either public contact id or user's contact id
-	 * @param int     $uid     User ID
-	 * @param boolean $ignored Is the contact ignored or unignored?
-	 * @throws \Exception
-	 */
-	public static function setIgnoredForUser($cid, $uid, $ignored)
-	{
-		$cdata = self::getPublicAndUserContacID($cid, $uid);
-		if (empty($cdata)) {
-			return;
-		}
-
-		if ($cdata['user'] != 0) {
-			DBA::update('contact', ['readonly' => $ignored], ['id' => $cdata['user'], 'pending' => false]);
-		}
-
-		DBA::update('user-contact', ['ignored' => $ignored], ['cid' => $cdata['public'], 'uid' => $uid], true);
-	}
-
-	/**
-	 * Returns "ignore" state for contact id and user id
-	 *
-	 * @param int $cid Either public contact id or user's contact id
-	 * @param int $uid User ID
-	 *
-	 * @return boolean is the contact id ignored for the given user?
-	 * @throws \Exception
-	 */
-	public static function isIgnoredByUser($cid, $uid)
-	{
-		$cdata = self::getPublicAndUserContacID($cid, $uid);
-		if (empty($cdata)) {
-			return;
-		}
-
-		$public_ignored = false;
-
-		if (!empty($cdata['public'])) {
-			$public_contact = DBA::selectFirst('user-contact', ['ignored'], ['cid' => $cdata['public'], 'uid' => $uid]);
-			if (DBA::isResult($public_contact)) {
-				$public_ignored = $public_contact['ignored'];
-			}
-		}
-
-		$user_ignored = $public_ignored;
-
-		if (!empty($cdata['user'])) {
-			$user_contact = DBA::selectFirst('contact', ['readonly'], ['id' => $cdata['user'], 'pending' => false]);
-			if (DBA::isResult($user_contact)) {
-				$user_ignored = $user_contact['readonly'];
-			}
-		}
-
-		if ($user_ignored != $public_ignored) {
-			DBA::update('user-contact', ['ignored' => $user_ignored], ['cid' => $cdata['public'], 'uid' => $uid], true);
-		}
-
-		return $user_ignored;
-	}
-
-	/**
-	 * Set "collapsed" for contact id and user id
-	 *
-	 * @param int     $cid       Either public contact id or user's contact id
-	 * @param int     $uid       User ID
-	 * @param boolean $collapsed are the contact's posts collapsed or uncollapsed?
-	 * @throws \Exception
-	 */
-	public static function setCollapsedForUser($cid, $uid, $collapsed)
-	{
-		$cdata = self::getPublicAndUserContacID($cid, $uid);
-		if (empty($cdata)) {
-			return;
-		}
-
-		DBA::update('user-contact', ['collapsed' => $collapsed], ['cid' => $cdata['public'], 'uid' => $uid], true);
-	}
-
-	/**
-	 * Returns "collapsed" state for contact id and user id
-	 *
-	 * @param int $cid Either public contact id or user's contact id
-	 * @param int $uid User ID
-	 *
-	 * @return boolean is the contact id blocked for the given user?
-	 * @throws HTTPException\InternalServerErrorException
-	 * @throws \ImagickException
-	 */
-	public static function isCollapsedByUser($cid, $uid)
-	{
-		$cdata = self::getPublicAndUserContacID($cid, $uid);
-		if (empty($cdata)) {
-			return;
-		}
-
-		$collapsed = false;
-
-		if (!empty($cdata['public'])) {
-			$public_contact = DBA::selectFirst('user-contact', ['collapsed'], ['cid' => $cdata['public'], 'uid' => $uid]);
-			if (DBA::isResult($public_contact)) {
-				$collapsed = $public_contact['collapsed'];
-			}
-		}
-
-		return $collapsed;
-	}
-
 	/**
 	 * Returns a list of contacts belonging in a group
 	 *
@@ -2588,7 +2415,7 @@ class Contact
 
 			// Contact is blocked at user-level
 			if (!empty($contact['id']) && !empty($importer['id']) &&
-				self::isBlockedByUser($contact['id'], $importer['id'])) {
+				Contact\User::isBlocked($contact['id'], $importer['id'])) {
 				return false;
 			}
 
@@ -2952,110 +2779,6 @@ class Contact
 		return $contacts;
 	}
 
-	/**
-	 * @param int $uid   user
-	 * @param int $start optional, default 0
-	 * @param int $limit optional, default 80
-	 * @return array
-	 */
-	static public function getSuggestions(int $uid, int $start = 0, int $limit = 80)
-	{
-		$cid = self::getPublicIdByUserId($uid);
-		$totallimit = $start + $limit;
-		$contacts = [];
-
-		Logger::info('Collecting suggestions', ['uid' => $uid, 'cid' => $cid, 'start' => $start, 'limit' => $limit]);
-
-		$diaspora = DI::config()->get('system', 'diaspora_enabled') ? Protocol::DIASPORA : Protocol::ACTIVITYPUB;
-		$ostatus = !DI::config()->get('system', 'ostatus_disabled') ? Protocol::OSTATUS : Protocol::ACTIVITYPUB;
-
-		// The query returns contacts where contacts interacted with whom the given user follows.
-		// Contacts who already are in the user's contact table are ignored.
-		$results = DBA::select('contact', [],
-			["`id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` IN
-				(SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ?)
-					AND NOT `cid` IN (SELECT `id` FROM `contact` WHERE `uid` = ? AND `nurl` IN
-						(SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))))
-			AND NOT `hidden` AND `network` IN (?, ?, ?, ?)",
-			$cid, 0, $uid, Contact::FRIEND, Contact::SHARING,
-			Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus],
-			['order' => ['last-item' => true], 'limit' => $totallimit]
-		);
-
-		while ($contact = DBA::fetch($results)) {
-			$contacts[$contact['id']] = $contact;
-		}
-		DBA::close($results);
-
-		Logger::info('Contacts of contacts who are followed by the given user', ['uid' => $uid, 'cid' => $cid, 'count' => count($contacts)]);
-
-		if (count($contacts) >= $totallimit) {
-			return array_slice($contacts, $start, $limit);
-		}
-
-		// The query returns contacts where contacts interacted with whom also interacted with the given user.
-		// Contacts who already are in the user's contact table are ignored.
-		$results = DBA::select('contact', [],
-			["`id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` IN
-				(SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ?)
-					AND NOT `cid` IN (SELECT `id` FROM `contact` WHERE `uid` = ? AND `nurl` IN
-						(SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))))
-			AND NOT `hidden` AND `network` IN (?, ?, ?, ?)",
-			$cid, 0, $uid, Contact::FRIEND, Contact::SHARING,
-			Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus],
-			['order' => ['last-item' => true], 'limit' => $totallimit]
-		);
-
-		while ($contact = DBA::fetch($results)) {
-			$contacts[$contact['id']] = $contact;
-		}
-		DBA::close($results);
-
-		Logger::info('Contacts of contacts who are following the given user', ['uid' => $uid, 'cid' => $cid, 'count' => count($contacts)]);
-
-		if (count($contacts) >= $totallimit) {
-			return array_slice($contacts, $start, $limit);
-		}
-
-		// The query returns contacts that follow the given user but aren't followed by that user.
-		$results = DBA::select('contact', [],
-			["`nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` = ?)
-			AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)",
-			$uid, Contact::FOLLOWER, 0, 
-			Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus],
-			['order' => ['last-item' => true], 'limit' => $totallimit]
-		);
-
-		while ($contact = DBA::fetch($results)) {
-			$contacts[$contact['id']] = $contact;
-		}
-		DBA::close($results);
-
-		Logger::info('Followers that are not followed by the given user', ['uid' => $uid, 'cid' => $cid, 'count' => count($contacts)]);
-
-		if (count($contacts) >= $totallimit) {
-			return array_slice($contacts, $start, $limit);
-		}
-
-		// The query returns any contact that isn't followed by that user.
-		$results = DBA::select('contact', [],
-			["NOT `nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))
-			AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)",
-			$uid, Contact::FRIEND, Contact::SHARING, 0, 
-			Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus],
-			['order' => ['last-item' => true], 'limit' => $totallimit]
-		);
-
-		while ($contact = DBA::fetch($results)) {
-			$contacts[$contact['id']] = $contact;
-		}
-		DBA::close($results);
-
-		Logger::info('Any contact', ['uid' => $uid, 'cid' => $cid, 'count' => count($contacts)]);
-
-		return array_slice($contacts, $start, $limit);
-	}
-
 	/**
 	 * Add public contacts from an array
 	 *
diff --git a/src/Model/Contact/Relation.php b/src/Model/Contact/Relation.php
index b6e612c7d8..a5bd0e9b7d 100644
--- a/src/Model/Contact/Relation.php
+++ b/src/Model/Contact/Relation.php
@@ -209,6 +209,110 @@ class Relation
 		return true;
 	}
 
+	/**
+	 * @param int $uid   user
+	 * @param int $start optional, default 0
+	 * @param int $limit optional, default 80
+	 * @return array
+	 */
+	static public function getSuggestions(int $uid, int $start = 0, int $limit = 80)
+	{
+		$cid = Contact::getPublicIdByUserId($uid);
+		$totallimit = $start + $limit;
+		$contacts = [];
+
+		Logger::info('Collecting suggestions', ['uid' => $uid, 'cid' => $cid, 'start' => $start, 'limit' => $limit]);
+
+		$diaspora = DI::config()->get('system', 'diaspora_enabled') ? Protocol::DIASPORA : Protocol::ACTIVITYPUB;
+		$ostatus = !DI::config()->get('system', 'ostatus_disabled') ? Protocol::OSTATUS : Protocol::ACTIVITYPUB;
+
+		// The query returns contacts where contacts interacted with whom the given user follows.
+		// Contacts who already are in the user's contact table are ignored.
+		$results = DBA::select('contact', [],
+			["`id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` IN
+				(SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ?)
+					AND NOT `cid` IN (SELECT `id` FROM `contact` WHERE `uid` = ? AND `nurl` IN
+						(SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))))
+			AND NOT `hidden` AND `network` IN (?, ?, ?, ?)",
+			$cid, 0, $uid, Contact::FRIEND, Contact::SHARING,
+			Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus],
+			['order' => ['last-item' => true], 'limit' => $totallimit]
+		);
+
+		while ($contact = DBA::fetch($results)) {
+			$contacts[$contact['id']] = $contact;
+		}
+		DBA::close($results);
+
+		Logger::info('Contacts of contacts who are followed by the given user', ['uid' => $uid, 'cid' => $cid, 'count' => count($contacts)]);
+
+		if (count($contacts) >= $totallimit) {
+			return array_slice($contacts, $start, $limit);
+		}
+
+		// The query returns contacts where contacts interacted with whom also interacted with the given user.
+		// Contacts who already are in the user's contact table are ignored.
+		$results = DBA::select('contact', [],
+			["`id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` IN
+				(SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ?)
+					AND NOT `cid` IN (SELECT `id` FROM `contact` WHERE `uid` = ? AND `nurl` IN
+						(SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))))
+			AND NOT `hidden` AND `network` IN (?, ?, ?, ?)",
+			$cid, 0, $uid, Contact::FRIEND, Contact::SHARING,
+			Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus],
+			['order' => ['last-item' => true], 'limit' => $totallimit]
+		);
+
+		while ($contact = DBA::fetch($results)) {
+			$contacts[$contact['id']] = $contact;
+		}
+		DBA::close($results);
+
+		Logger::info('Contacts of contacts who are following the given user', ['uid' => $uid, 'cid' => $cid, 'count' => count($contacts)]);
+
+		if (count($contacts) >= $totallimit) {
+			return array_slice($contacts, $start, $limit);
+		}
+
+		// The query returns contacts that follow the given user but aren't followed by that user.
+		$results = DBA::select('contact', [],
+			["`nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` = ?)
+			AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)",
+			$uid, Contact::FOLLOWER, 0, 
+			Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus],
+			['order' => ['last-item' => true], 'limit' => $totallimit]
+		);
+
+		while ($contact = DBA::fetch($results)) {
+			$contacts[$contact['id']] = $contact;
+		}
+		DBA::close($results);
+
+		Logger::info('Followers that are not followed by the given user', ['uid' => $uid, 'cid' => $cid, 'count' => count($contacts)]);
+
+		if (count($contacts) >= $totallimit) {
+			return array_slice($contacts, $start, $limit);
+		}
+
+		// The query returns any contact that isn't followed by that user.
+		$results = DBA::select('contact', [],
+			["NOT `nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))
+			AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)",
+			$uid, Contact::FRIEND, Contact::SHARING, 0, 
+			Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus],
+			['order' => ['last-item' => true], 'limit' => $totallimit]
+		);
+
+		while ($contact = DBA::fetch($results)) {
+			$contacts[$contact['id']] = $contact;
+		}
+		DBA::close($results);
+
+		Logger::info('Any contact', ['uid' => $uid, 'cid' => $cid, 'count' => count($contacts)]);
+
+		return array_slice($contacts, $start, $limit);
+	}
+
 	/**
 	 * Counts all the known follows of the provided public contact
 	 *
diff --git a/src/Model/Contact/User.php b/src/Model/Contact/User.php
new file mode 100644
index 0000000000..34a3d6f341
--- /dev/null
+++ b/src/Model/Contact/User.php
@@ -0,0 +1,204 @@
+<?php
+/**
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Model\Contact;
+
+use Friendica\Database\DBA;
+use Friendica\Model\Contact;
+
+/**
+ * This class provides information about user related contacts based on the "user-contact" table.
+ */
+class User
+{
+	/**
+	 * Block contact id for user id
+	 *
+	 * @param int     $cid     Either public contact id or user's contact id
+	 * @param int     $uid     User ID
+	 * @param boolean $blocked Is the contact blocked or unblocked?
+	 * @throws \Exception
+	 */
+	public static function setBlocked($cid, $uid, $blocked)
+	{
+		$cdata = Contact::getPublicAndUserContacID($cid, $uid);
+		if (empty($cdata)) {
+			return;
+		}
+
+		if ($cdata['user'] != 0) {
+			DBA::update('contact', ['blocked' => $blocked], ['id' => $cdata['user'], 'pending' => false]);
+		}
+
+		DBA::update('user-contact', ['blocked' => $blocked], ['cid' => $cdata['public'], 'uid' => $uid], true);
+	}
+
+	/**
+	 * Returns "block" state for contact id and user id
+	 *
+	 * @param int $cid Either public contact id or user's contact id
+	 * @param int $uid User ID
+	 *
+	 * @return boolean is the contact id blocked for the given user?
+	 * @throws \Exception
+	 */
+	public static function isBlocked($cid, $uid)
+	{
+		$cdata = Contact::getPublicAndUserContacID($cid, $uid);
+		if (empty($cdata)) {
+			return;
+		}
+
+		$public_blocked = false;
+
+		if (!empty($cdata['public'])) {
+			$public_contact = DBA::selectFirst('user-contact', ['blocked'], ['cid' => $cdata['public'], 'uid' => $uid]);
+			if (DBA::isResult($public_contact)) {
+				$public_blocked = $public_contact['blocked'];
+			}
+		}
+
+		$user_blocked = $public_blocked;
+
+		if (!empty($cdata['user'])) {
+			$user_contact = DBA::selectFirst('contact', ['blocked'], ['id' => $cdata['user'], 'pending' => false]);
+			if (DBA::isResult($user_contact)) {
+				$user_blocked = $user_contact['blocked'];
+			}
+		}
+
+		if ($user_blocked != $public_blocked) {
+			DBA::update('user-contact', ['blocked' => $user_blocked], ['cid' => $cdata['public'], 'uid' => $uid], true);
+		}
+
+		return $user_blocked;
+	}
+
+	/**
+	 * Ignore contact id for user id
+	 *
+	 * @param int     $cid     Either public contact id or user's contact id
+	 * @param int     $uid     User ID
+	 * @param boolean $ignored Is the contact ignored or unignored?
+	 * @throws \Exception
+	 */
+	public static function setIgnored($cid, $uid, $ignored)
+	{
+		$cdata = Contact::getPublicAndUserContacID($cid, $uid);
+		if (empty($cdata)) {
+			return;
+		}
+
+		if ($cdata['user'] != 0) {
+			DBA::update('contact', ['readonly' => $ignored], ['id' => $cdata['user'], 'pending' => false]);
+		}
+
+		DBA::update('user-contact', ['ignored' => $ignored], ['cid' => $cdata['public'], 'uid' => $uid], true);
+	}
+
+	/**
+	 * Returns "ignore" state for contact id and user id
+	 *
+	 * @param int $cid Either public contact id or user's contact id
+	 * @param int $uid User ID
+	 *
+	 * @return boolean is the contact id ignored for the given user?
+	 * @throws \Exception
+	 */
+	public static function isIgnored($cid, $uid)
+	{
+		$cdata = Contact::getPublicAndUserContacID($cid, $uid);
+		if (empty($cdata)) {
+			return;
+		}
+
+		$public_ignored = false;
+
+		if (!empty($cdata['public'])) {
+			$public_contact = DBA::selectFirst('user-contact', ['ignored'], ['cid' => $cdata['public'], 'uid' => $uid]);
+			if (DBA::isResult($public_contact)) {
+				$public_ignored = $public_contact['ignored'];
+			}
+		}
+
+		$user_ignored = $public_ignored;
+
+		if (!empty($cdata['user'])) {
+			$user_contact = DBA::selectFirst('contact', ['readonly'], ['id' => $cdata['user'], 'pending' => false]);
+			if (DBA::isResult($user_contact)) {
+				$user_ignored = $user_contact['readonly'];
+			}
+		}
+
+		if ($user_ignored != $public_ignored) {
+			DBA::update('user-contact', ['ignored' => $user_ignored], ['cid' => $cdata['public'], 'uid' => $uid], true);
+		}
+
+		return $user_ignored;
+	}
+
+	/**
+	 * Set "collapsed" for contact id and user id
+	 *
+	 * @param int     $cid       Either public contact id or user's contact id
+	 * @param int     $uid       User ID
+	 * @param boolean $collapsed are the contact's posts collapsed or uncollapsed?
+	 * @throws \Exception
+	 */
+	public static function setCollapsed($cid, $uid, $collapsed)
+	{
+		$cdata = Contact::getPublicAndUserContacID($cid, $uid);
+		if (empty($cdata)) {
+			return;
+		}
+
+		DBA::update('user-contact', ['collapsed' => $collapsed], ['cid' => $cdata['public'], 'uid' => $uid], true);
+	}
+
+	/**
+	 * Returns "collapsed" state for contact id and user id
+	 *
+	 * @param int $cid Either public contact id or user's contact id
+	 * @param int $uid User ID
+	 *
+	 * @return boolean is the contact id blocked for the given user?
+	 * @throws HTTPException\InternalServerErrorException
+	 * @throws \ImagickException
+	 */
+	public static function isCollapsed($cid, $uid)
+	{
+		$cdata = Contact::getPublicAndUserContacID($cid, $uid);
+		if (empty($cdata)) {
+			return;
+		}
+
+		$collapsed = false;
+
+		if (!empty($cdata['public'])) {
+			$public_contact = DBA::selectFirst('user-contact', ['collapsed'], ['cid' => $cdata['public'], 'uid' => $uid]);
+			if (DBA::isResult($public_contact)) {
+				$collapsed = $public_contact['collapsed'];
+			}
+		}
+
+		return $collapsed;
+	}
+}
diff --git a/src/Model/Item.php b/src/Model/Item.php
index 54cd4a5db6..0a1038049b 100644
--- a/src/Model/Item.php
+++ b/src/Model/Item.php
@@ -1394,7 +1394,7 @@ class Item
 			return false;
 		}
 
-		if (!empty($item['uid']) && Contact::isBlockedByUser($item['author-id'], $item['uid'])) {
+		if (!empty($item['uid']) && Contact\User::isBlocked($item['author-id'], $item['uid'])) {
 			Logger::notice('Author is blocked by user', ['author-link' => $item['author-link'], 'uid' => $item['uid'], 'item-uri' => $item['uri']]);
 			return false;
 		}
@@ -1409,18 +1409,18 @@ class Item
 			return false;
 		}
 
-		if (!empty($item['uid']) && Contact::isBlockedByUser($item['owner-id'], $item['uid'])) {
+		if (!empty($item['uid']) && Contact\User::isBlocked($item['owner-id'], $item['uid'])) {
 			Logger::notice('Owner is blocked by user', ['owner-link' => $item['owner-link'], 'uid' => $item['uid'], 'item-uri' => $item['uri']]);
 			return false;
 		}
 
 		// The causer is set during a thread completion, for example because of a reshare. It countains the responsible actor.
-		if (!empty($item['uid']) && !empty($item['causer-id']) && Contact::isBlockedByUser($item['causer-id'], $item['uid'])) {
+		if (!empty($item['uid']) && !empty($item['causer-id']) && Contact\User::isBlocked($item['causer-id'], $item['uid'])) {
 			Logger::notice('Causer is blocked by user', ['causer-link' => $item['causer-link'], 'uid' => $item['uid'], 'item-uri' => $item['uri']]);
 			return false;
 		}
 
-		if (!empty($item['uid']) && !empty($item['causer-id']) && ($item['parent-uri'] == $item['uri']) && Contact::isIgnoredByUser($item['causer-id'], $item['uid'])) {
+		if (!empty($item['uid']) && !empty($item['causer-id']) && ($item['parent-uri'] == $item['uri']) && Contact\User::isIgnored($item['causer-id'], $item['uid'])) {
 			Logger::notice('Causer is ignored by user', ['causer-link' => $item['causer-link'], 'uid' => $item['uid'], 'item-uri' => $item['uri']]);
 			return false;
 		}
diff --git a/src/Module/Contact.php b/src/Module/Contact.php
index 7ddd5c9f4d..dc2d4dc4ff 100644
--- a/src/Module/Contact.php
+++ b/src/Module/Contact.php
@@ -196,8 +196,8 @@ class Contact extends BaseModule
 	 */
 	private static function blockContact($contact_id)
 	{
-		$blocked = !Model\Contact::isBlockedByUser($contact_id, local_user());
-		Model\Contact::setBlockedForUser($contact_id, local_user(), $blocked);
+		$blocked = !Model\Contact\User::isBlocked($contact_id, local_user());
+		Model\Contact\User::setBlocked($contact_id, local_user(), $blocked);
 	}
 
 	/**
@@ -208,8 +208,8 @@ class Contact extends BaseModule
 	 */
 	private static function ignoreContact($contact_id)
 	{
-		$ignored = !Model\Contact::isIgnoredByUser($contact_id, local_user());
-		Model\Contact::setIgnoredForUser($contact_id, local_user(), $ignored);
+		$ignored = !Model\Contact\User::isIgnored($contact_id, local_user());
+		Model\Contact\User::setIgnored($contact_id, local_user(), $ignored);
 	}
 
 	/**
@@ -395,7 +395,7 @@ class Contact extends BaseModule
 			if ($cmd === 'block') {
 				self::blockContact($contact_id);
 
-				$blocked = Model\Contact::isBlockedByUser($contact_id, local_user());
+				$blocked = Model\Contact\User::isBlocked($contact_id, local_user());
 				info(($blocked ? DI::l10n()->t('Contact has been blocked') : DI::l10n()->t('Contact has been unblocked')));
 
 				DI::baseUrl()->redirect('contact/' . $contact_id);
@@ -405,7 +405,7 @@ class Contact extends BaseModule
 			if ($cmd === 'ignore') {
 				self::ignoreContact($contact_id);
 
-				$ignored = Model\Contact::isIgnoredByUser($contact_id, local_user());
+				$ignored = Model\Contact\User::isIgnored($contact_id, local_user());
 				info(($ignored ? DI::l10n()->t('Contact has been ignored') : DI::l10n()->t('Contact has been unignored')));
 
 				DI::baseUrl()->redirect('contact/' . $contact_id);
@@ -479,8 +479,8 @@ class Contact extends BaseModule
 				'$baseurl' => DI::baseUrl()->get(true),
 			]);
 
-			$contact['blocked']  = Model\Contact::isBlockedByUser($contact['id'], local_user());
-			$contact['readonly'] = Model\Contact::isIgnoredByUser($contact['id'], local_user());
+			$contact['blocked']  = Model\Contact\User::isBlocked($contact['id'], local_user());
+			$contact['readonly'] = Model\Contact\User::isIgnored($contact['id'], local_user());
 
 			$relation_text = '';
 			switch ($contact['rel']) {
@@ -738,8 +738,8 @@ class Contact extends BaseModule
 			$sql_values
 		);
 		while ($contact = DBA::fetch($stmt)) {
-			$contact['blocked'] = Model\Contact::isBlockedByUser($contact['id'], local_user());
-			$contact['readonly'] = Model\Contact::isIgnoredByUser($contact['id'], local_user());
+			$contact['blocked'] = Model\Contact\User::isBlocked($contact['id'], local_user());
+			$contact['readonly'] = Model\Contact\User::isIgnored($contact['id'], local_user());
 			$contacts[] = self::getContactTemplateVars($contact);
 		}
 		DBA::close($stmt);
diff --git a/update.php b/update.php
index f335b5292e..2d25ac83d2 100644
--- a/update.php
+++ b/update.php
@@ -376,8 +376,8 @@ function update_1327()
 {
 	$contacts = DBA::select('contact', ['uid', 'id', 'blocked', 'readonly'], ["`uid` != ? AND (`blocked` OR `readonly`) AND NOT `pending`", 0]);
 	while ($contact = DBA::fetch($contacts)) {
-		Contact::setBlockedForUser($contact['id'], $contact['uid'], $contact['blocked']);
-		Contact::setIgnoredForUser($contact['id'], $contact['uid'], $contact['readonly']);
+		Contact\User::setBlocked($contact['id'], $contact['uid'], $contact['blocked']);
+		Contact\User::setIgnored($contact['id'], $contact['uid'], $contact['readonly']);
 	}
 	DBA::close($contacts);
 
diff --git a/view/theme/vier/theme.php b/view/theme/vier/theme.php
index a688d68452..c644165246 100644
--- a/view/theme/vier/theme.php
+++ b/view/theme/vier/theme.php
@@ -117,7 +117,7 @@ function vier_community_info()
 
 	// comunity_profiles
 	if ($show_profiles) {
-		$contacts = Contact::getSuggestions(local_user(), 0, 9);
+		$contacts = Contact\Relation::getSuggestions(local_user(), 0, 9);
 
 		$tpl = Renderer::getMarkupTemplate('ch_directory_item.tpl');
 		if (DBA::isResult($contacts)) {