diff --git a/boot.php b/boot.php index 512238b52e..b43fd6dc2f 100644 --- a/boot.php +++ b/boot.php @@ -266,7 +266,7 @@ function public_contact() } /** - * Returns contact id of authenticated site visitor or false + * Returns public contact id of authenticated site visitor or false * * @return int|bool visitor_id or false */ diff --git a/src/Content/Widget.php b/src/Content/Widget.php index e8e70c0e11..a7ce52cc46 100644 --- a/src/Content/Widget.php +++ b/src/Content/Widget.php @@ -374,81 +374,59 @@ class Widget } /** - * Return common friends visitor widget + * Show a random selection of five common contacts between the visitor and the viewed profile user. * - * @param string $profile_uid uid + * @param int $uid Viewed profile user ID + * @param string $nickname Viewed profile user nickname * @return string|void * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \ImagickException */ - public static function commonFriendsVisitor($profile_uid) + public static function commonFriendsVisitor(int $uid, string $nickname) { - if (local_user() == $profile_uid) { - return; + if (local_user() == $uid) { + return ''; } - $zcid = 0; - - $cid = Session::getRemoteContactID($profile_uid); - - if (!$cid) { - if (Profile::getMyURL()) { - $contact = DBA::selectFirst('contact', ['id'], - ['nurl' => Strings::normaliseLink(Profile::getMyURL()), 'uid' => $profile_uid]); - if (DBA::isResult($contact)) { - $cid = $contact['id']; - } else { - $gcontact = DBA::selectFirst('gcontact', ['id'], ['nurl' => Strings::normaliseLink(Profile::getMyURL())]); - if (DBA::isResult($gcontact)) { - $zcid = $gcontact['id']; - } - } - } + $visitorPCid = local_user() ? Contact::getPublicIdByUserId(local_user()) : remote_user(); + if (!$visitorPCid) { + return ''; } - if ($cid == 0 && $zcid == 0) { - return; + $localPCid = Contact::getPublicIdByUserId($uid); + + $condition = [ + 'NOT `self` AND NOT `blocked` AND NOT `hidden` AND `id` != ?', + $localPCid, + ]; + + $total = Contact\Relation::countCommon($localPCid, $visitorPCid, $condition); + if (!$total) { + return ''; } - if ($cid) { - $t = GContact::countCommonFriends($profile_uid, $cid); - } else { - $t = GContact::countCommonFriendsZcid($profile_uid, $zcid); - } - - if (!$t) { - return; - } - - if ($cid) { - $r = GContact::commonFriends($profile_uid, $cid, 0, 5, true); - } else { - $r = GContact::commonFriendsZcid($profile_uid, $zcid, 0, 5, true); - } - - if (!DBA::isResult($r)) { - return; + $commonContacts = Contact\Relation::listCommon($localPCid, $visitorPCid, $condition, 0, 5, true); + if (!DBA::isResult($commonContacts)) { + return ''; } $entries = []; - foreach ($r as $rr) { - $contact = Contact::getByURL($rr['url']); - $entry = [ - 'url' => Contact::magicLink($rr['url']), - 'name' => $contact['name'] ?? $rr['name'], - 'photo' => Contact::getThumb($contact, $rr['photo']), + foreach ($commonContacts as $contact) { + $entries[] = [ + 'url' => Contact::magicLink($contact['url']), + 'name' => $contact['name'], + 'photo' => Contact::getThumb($contact), ]; - $entries[] = $entry; } $tpl = Renderer::getMarkupTemplate('widget/remote_friends_common.tpl'); return Renderer::replaceMacros($tpl, [ - '$desc' => DI::l10n()->tt("%d contact in common", "%d contacts in common", $t), + '$desc' => DI::l10n()->tt("%d contact in common", "%d contacts in common", $total), '$base' => DI::baseUrl(), - '$uid' => $profile_uid, - '$cid' => (($cid) ? $cid : '0'), - '$linkmore' => (($t > 5) ? 'true' : ''), + '$nickname' => $nickname, + '$linkmore' => $total > 5 ? 'true' : '', '$more' => DI::l10n()->t('show more'), - '$items' => $entries + '$contacts' => $entries ]); } diff --git a/src/Core/Session.php b/src/Core/Session.php index 767dcb513f..b15b53c4ee 100644 --- a/src/Core/Session.php +++ b/src/Core/Session.php @@ -65,10 +65,10 @@ class Session } /** - * Returns contact ID for given user ID + * Return the user contact ID of a visitor for the given user ID they are visiting * * @param integer $uid User ID - * @return integer Contact ID of visitor for given user ID + * @return integer */ public static function getRemoteContactID($uid) { diff --git a/src/Model/Contact/Relation.php b/src/Model/Contact/Relation.php index a5bd0e9b7d..cc0570ec63 100644 --- a/src/Model/Contact/Relation.php +++ b/src/Model/Contact/Relation.php @@ -431,7 +431,7 @@ class Relation * @return array * @throws Exception */ - public static function listCommon(int $sourceId, int $targetId, array $fields = [], array $condition = [], int $count = 30, int $offset = 0, bool $shuffle = false) + public static function listCommon(int $sourceId, int $targetId, array $condition = [], int $count = 30, int $offset = 0, bool $shuffle = false) { $condition = DBA::mergeConditions($condition, ["`id` IN (SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ? AND `follows`) @@ -439,7 +439,7 @@ class Relation $sourceId, $targetId] ); - return DI::dba()->selectToArray('contact', $fields, $condition, + return DI::dba()->selectToArray('contact', [], $condition, ['limit' => [$offset, $count], 'order' => [$shuffle ? 'name' : 'RAND()']] ); } @@ -470,7 +470,6 @@ class Relation * * @param int $sourceId Public contact id * @param int $targetId Public contact id - * @param array $field Field list * @param array $condition Additional condition array on the contact table * @param int $count * @param int $offset @@ -478,7 +477,7 @@ class Relation * @return array * @throws Exception */ - public static function listCommonFollows(int $sourceId, int $targetId, array $fields = [], array $condition = [], int $count = 30, int $offset = 0, bool $shuffle = false) + public static function listCommonFollows(int $sourceId, int $targetId, array $condition = [], int $count = 30, int $offset = 0, bool $shuffle = false) { $condition = DBA::mergeConditions($condition, ["`id` IN (SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ? AND `follows`) @@ -486,7 +485,7 @@ class Relation $sourceId, $targetId] ); - return DI::dba()->selectToArray('contact', $fields, $condition, + return DI::dba()->selectToArray('contact', [], $condition, ['limit' => [$offset, $count], 'order' => [$shuffle ? 'name' : 'RAND()']] ); } @@ -516,7 +515,6 @@ class Relation * * @param int $sourceId Public contact id * @param int $targetId Public contact id - * @param array $field Field list * @param array $condition Additional condition on the contact table * @param int $count * @param int $offset @@ -524,7 +522,7 @@ class Relation * @return array * @throws Exception */ - public static function listCommonFollowers(int $sourceId, int $targetId, array $fields = [], array $condition = [], int $count = 30, int $offset = 0, bool $shuffle = false) + public static function listCommonFollowers(int $sourceId, int $targetId, array $condition = [], int $count = 30, int $offset = 0, bool $shuffle = false) { $condition = DBA::mergeConditions($condition, ["`id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND `follows`) @@ -532,7 +530,7 @@ class Relation $sourceId, $targetId] ); - return DI::dba()->selectToArray('contact', $fields, $condition, + return DI::dba()->selectToArray('contact', [], $condition, ['limit' => [$offset, $count], 'order' => [$shuffle ? 'name' : 'RAND()']] ); } diff --git a/src/Model/GContact.php b/src/Model/GContact.php index 097b43456a..3fe123e15f 100644 --- a/src/Model/GContact.php +++ b/src/Model/GContact.php @@ -46,7 +46,7 @@ class GContact $sourceId, ]; - return Contact\Relation::countCommonFollows($sourceId, $targetIds['public'] ?? 0, [], $condition); + return Contact\Relation::countCommonFollows($sourceId, $targetIds['public'] ?? 0, $condition); } /** @@ -74,7 +74,7 @@ LIMIT 1", $sourceId, ]; - return Contact\Relation::countCommonFollowers($sourceId, $targetPublicContact['id'] ?? 0, [], $condition); + return Contact\Relation::countCommonFollowers($sourceId, $targetPublicContact['id'] ?? 0, $condition); } /** @@ -100,7 +100,7 @@ LIMIT 1", $sourceId, ]; - return Contact\Relation::listCommonFollows($sourceId, $targetIds['public'] ?? 0, [], $condition, $limit, $start, $shuffle); + return Contact\Relation::listCommonFollows($sourceId, $targetIds['public'] ?? 0, $condition, $limit, $start, $shuffle); } /** @@ -134,6 +134,6 @@ LIMIT 1", $sourceId, ]; - return Contact\Relation::listCommonFollows($sourceId, $targetPublicContact['id'] ?? 0, [], $condition, $limit, $start, $shuffle); + return Contact\Relation::listCommonFollows($sourceId, $targetPublicContact['id'] ?? 0, $condition, $limit, $start, $shuffle); } } diff --git a/src/Module/Contact.php b/src/Module/Contact.php index dc2d4dc4ff..750a927e88 100644 --- a/src/Module/Contact.php +++ b/src/Module/Contact.php @@ -1030,7 +1030,7 @@ class Contact extends BaseModule } } - if (!empty($contact['uid']) && !empty($contact['rel'])) { + if (!empty($contact['uid']) && !empty($contact['rel']) && local_user() == $contact['uid']) { switch ($contact['rel']) { case Model\Contact::FRIEND: $alt_text = DI::l10n()->t('Mutual Friendship'); diff --git a/src/Module/Profile/Common.php b/src/Module/Profile/Common.php new file mode 100644 index 0000000000..0b08d327c9 --- /dev/null +++ b/src/Module/Profile/Common.php @@ -0,0 +1,112 @@ +. + * + */ + +namespace Friendica\Module\Profile; + +use Friendica\Content\Nav; +use Friendica\Content\Pager; +use Friendica\Core\Protocol; +use Friendica\Core\Renderer; +use Friendica\Core\Session; +use Friendica\Module; +use Friendica\DI; +use Friendica\Model\Contact; +use Friendica\Model\Profile; +use Friendica\Module\BaseProfile; +use Friendica\Network\HTTPException; + +class Common extends BaseProfile +{ + public static function content(array $parameters = []) + { + if (DI::config()->get('system', 'block_public') && !Session::isAuthenticated()) { + throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.')); + } + + $a = DI::app(); + + Nav::setSelected('home'); + + $nickname = $parameters['nickname']; + + Profile::load($a, $nickname); + + if (empty($a->profile)) { + throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.')); + } + + $o = self::getTabsHTML($a, 'contacts', false, $nickname); + + if (!empty($a->profile['hide-friends'])) { + throw new HTTPException\ForbiddenException(DI::l10n()->t('Permission denied.')); + } + + $displayCommonTab = Session::isAuthenticated() && $a->profile['uid'] != local_user(); + + if (!$displayCommonTab) { + $a->redirect('profile/' . $nickname . '/contacts'); + }; + + $sourceId = Contact::getIdForURL(Profile::getMyURL()); + $targetId = Contact::getPublicIdByUserId($a->profile['uid']); + + $condition = [ + 'blocked' => false, + 'deleted' => false, + 'network' => [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, Protocol::FEED], + ]; + + $total = Contact\Relation::countCommon($sourceId, $targetId, $condition); + + $pager = new Pager(DI::l10n(), DI::args()->getQueryString()); + + $commonFollows = Contact\Relation::listCommon($sourceId, $targetId, $condition, $pager->getItemsPerPage(), $pager->getStart()); + + $contacts = array_map([Module\Contact::class, 'getContactTemplateVars'], $commonFollows); + + $title = DI::l10n()->tt('Common contact (%s)', 'Common contacts (%s)', $total); + $desc = DI::l10n()->t( + 'Both %s and yourself have publicly interacted with these contacts (follow, comment or likes on public posts).', + htmlentities($a->profile['name'], ENT_COMPAT, 'UTF-8') + ); + + $tpl = Renderer::getMarkupTemplate('profile/contacts.tpl'); + $o .= Renderer::replaceMacros($tpl, [ + '$title' => $title, + '$desc' => $desc, + '$nickname' => $nickname, + '$type' => 'common', + '$displayCommonTab' => $displayCommonTab, + + '$all_label' => DI::l10n()->t('All contacts'), + '$followers_label' => DI::l10n()->t('Followers'), + '$following_label' => DI::l10n()->t('Following'), + '$mutuals_label' => DI::l10n()->t('Mutual friends'), + '$common_label' => DI::l10n()->t('Common'), + '$noresult_label' => DI::l10n()->t('No common contacts.'), + + '$contacts' => $contacts, + '$paginate' => $pager->renderFull($total), + ]); + + return $o; + } +} diff --git a/src/Module/Profile/Contacts.php b/src/Module/Profile/Contacts.php index e55c0d9a84..938dbd3d05 100644 --- a/src/Module/Profile/Contacts.php +++ b/src/Module/Profile/Contacts.php @@ -28,57 +28,53 @@ use Friendica\Core\Renderer; use Friendica\Core\Session; use Friendica\Database\DBA; use Friendica\DI; -use Friendica\Model\Profile; -use Friendica\Module\BaseProfile; -use Friendica\Module\Contact as ModuleContact; +use Friendica\Model; +use Friendica\Module; +use Friendica\Network\HTTPException; -class Contacts extends BaseProfile +class Contacts extends Module\BaseProfile { public static function content(array $parameters = []) { if (DI::config()->get('system', 'block_public') && !Session::isAuthenticated()) { - throw new \Friendica\Network\HTTPException\NotFoundException(DI::l10n()->t('User not found.')); + throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.')); } $a = DI::app(); - //@TODO: Get value from router parameters - $nickname = $a->argv[1]; - $type = ($a->argv[3] ?? '') ?: 'all'; + $nickname = $parameters['nickname']; + $type = $parameters['type'] ?? 'all'; - Nav::setSelected('home'); + Model\Profile::load($a, $nickname); - $user = DBA::selectFirst('user', [], ['nickname' => $nickname, 'blocked' => false]); - if (!DBA::isResult($user)) { - throw new \Friendica\Network\HTTPException\NotFoundException(DI::l10n()->t('User not found.')); + if (empty($a->profile)) { + throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.')); } - $a->profile_uid = $user['uid']; + if (!empty($a->profile['hide-friends'])) { + throw new HTTPException\ForbiddenException(DI::l10n()->t('Permission denied.')); + } - Profile::load($a, $nickname); + Nav::setSelected('home'); $is_owner = $a->profile['uid'] == local_user(); $o = self::getTabsHTML($a, 'contacts', $is_owner, $nickname); - if (!count($a->profile) || $a->profile['hide-friends']) { - notice(DI::l10n()->t('Permission denied.')); - return $o; - } - $condition = [ 'uid' => $a->profile['uid'], 'blocked' => false, 'pending' => false, 'hidden' => false, 'archive' => false, + 'self' => false, 'network' => [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, Protocol::FEED] ]; switch ($type) { - case 'followers': $condition['rel'] = [1, 3]; break; - case 'following': $condition['rel'] = [2, 3]; break; - case 'mutuals': $condition['rel'] = 3; break; + case 'followers': $condition['rel'] = [Model\Contact::FOLLOWER, Model\Contact::FRIEND]; break; + case 'following': $condition['rel'] = [Model\Contact::SHARING, Model\Contact::FRIEND]; break; + case 'mutuals': $condition['rel'] = Model\Contact::FRIEND; break; } $total = DBA::count('contact', $condition); @@ -87,42 +83,46 @@ class Contacts extends BaseProfile $params = ['order' => ['name' => false], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]]; - $contacts_stmt = DBA::select('contact', [], $condition, $params); - - if (!DBA::isResult($contacts_stmt)) { - notice(DI::l10n()->t('No contacts.')); - return $o; - } - - $contacts = []; - - while ($contact = DBA::fetch($contacts_stmt)) { - if ($contact['self']) { - continue; - } - $contacts[] = ModuleContact::getContactTemplateVars($contact); - } - - DBA::close($contacts_stmt); + $contacts = array_map( + [Module\Contact::class, 'getContactTemplateVars'], + Model\Contact::selectToArray([], $condition, $params) + ); + $desc = ''; switch ($type) { - case 'followers': $title = DI::l10n()->tt('Follower (%s)', 'Followers (%s)', $total); break; - case 'following': $title = DI::l10n()->tt('Following (%s)', 'Following (%s)', $total); break; - case 'mutuals': $title = DI::l10n()->tt('Mutual friend (%s)', 'Mutual friends (%s)', $total); break; - - case 'all': default: $title = DI::l10n()->tt('Contact (%s)', 'Contacts (%s)', $total); break; + case 'followers': + $title = DI::l10n()->tt('Follower (%s)', 'Followers (%s)', $total); + break; + case 'following': + $title = DI::l10n()->tt('Following (%s)', 'Following (%s)', $total); + break; + case 'mutuals': + $title = DI::l10n()->tt('Mutual friend (%s)', 'Mutual friends (%s)', $total); + $desc = DI::l10n()->t( + 'These contacts both follow and are followed by %s.', + htmlentities($a->profile['name'], ENT_COMPAT, 'UTF-8') + ); + break; + case 'all': + default: + $title = DI::l10n()->tt('Contact (%s)', 'Contacts (%s)', $total); + break; } $tpl = Renderer::getMarkupTemplate('profile/contacts.tpl'); $o .= Renderer::replaceMacros($tpl, [ '$title' => $title, + '$desc' => $desc, '$nickname' => $nickname, '$type' => $type, + '$displayCommonTab' => Session::isAuthenticated() && $a->profile['uid'] != local_user(), - '$all_label' => DI::l10n()->t('All contacts'), + '$all_label' => DI::l10n()->t('All contacts'), '$followers_label' => DI::l10n()->t('Followers'), '$following_label' => DI::l10n()->t('Following'), - '$mutuals_label' => DI::l10n()->t('Mutual friends'), + '$mutuals_label' => DI::l10n()->t('Mutual friends'), + '$common_label' => DI::l10n()->t('Common'), + '$noresult_label' => DI::l10n()->t('No contacts.'), '$contacts' => $contacts, '$paginate' => $pager->renderFull($total), diff --git a/src/Module/Profile/Status.php b/src/Module/Profile/Status.php index 4b36cdd4de..200e03ca75 100644 --- a/src/Module/Profile/Status.php +++ b/src/Module/Profile/Status.php @@ -108,7 +108,7 @@ class Status extends BaseProfile $o .= self::getTabsHTML($a, 'status', $is_owner, $a->profile['nickname']); - $o .= Widget::commonFriendsVisitor($a->profile['uid']); + $o .= Widget::commonFriendsVisitor($a->profile['uid'], $a->profile['nickname']); $commpage = $a->profile['page-flags'] == User::PAGE_FLAGS_COMMUNITY; $commvisitor = $commpage && $remote_contact; diff --git a/static/routes.config.php b/static/routes.config.php index ddfabd7780..15eec298e9 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -252,6 +252,7 @@ return [ '/profile' => [ '/{nickname}' => [Module\Profile\Index::class, [R::GET]], '/{nickname}/profile' => [Module\Profile\Profile::class, [R::GET]], + '/{nickname}/contacts/common' => [Module\Profile\Common::class, [R::GET]], '/{nickname}/contacts[/{type}]' => [Module\Profile\Contacts::class, [R::GET]], '/{nickname}/status[/{category}[/{date1}[/{date2}]]]' => [Module\Profile\Status::class, [R::GET]], ], diff --git a/view/templates/profile/contacts.tpl b/view/templates/profile/contacts.tpl index 4e78a7a7f8..4a1379f17b 100644 --- a/view/templates/profile/contacts.tpl +++ b/view/templates/profile/contacts.tpl @@ -1,18 +1,29 @@
{{include file="section_title.tpl"}} +{{if $desc}} +

{{$desc nofilter}}

+{{/if}} + - +{{if $contacts}}
-{{foreach $contacts as $contact}} + {{foreach $contacts as $contact}} {{include file="contact_template.tpl"}} -{{/foreach}} + {{/foreach}}
+{{else}} + +{{/if}} +
diff --git a/view/templates/widget/remote_friends_common.tpl b/view/templates/widget/remote_friends_common.tpl index 4ae682f436..74d8e66804 100644 --- a/view/templates/widget/remote_friends_common.tpl +++ b/view/templates/widget/remote_friends_common.tpl @@ -1,22 +1,18 @@ -
-
{{$desc nofilter}}      {{if $linkmore}}{{$more}}{{/if}}
- {{if $items}} - {{foreach $items as $item}} +
{{$desc nofilter}}      {{if $linkmore}}{{$more}}{{/if}}
+ {{foreach $contacts as $contact}} {{/foreach}} - {{/if}}
- diff --git a/view/theme/frio/templates/contact_template.tpl b/view/theme/frio/templates/contact_template.tpl index 92eff17056..8bb817ed59 100644 --- a/view/theme/frio/templates/contact_template.tpl +++ b/view/theme/frio/templates/contact_template.tpl @@ -6,7 +6,9 @@
{{* For very small displays we use a dropdown menu for contact relating actions *}} diff --git a/view/theme/frio/templates/profile/contacts.tpl b/view/theme/frio/templates/profile/contacts.tpl index 69d34d05ab..000b976774 100644 --- a/view/theme/frio/templates/profile/contacts.tpl +++ b/view/theme/frio/templates/profile/contacts.tpl @@ -1,18 +1,38 @@
{{include file="section_title.tpl"}} - +{{if $desc}} +

{{$desc nofilter}}

+{{/if}} -