]> git.mxchange.org Git - friendica.git/blob - src/Module/Contact.php
Adapt BaseURL calls to new UriInterface
[friendica.git] / src / Module / Contact.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2023, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Module;
23
24 use Friendica\BaseModule;
25 use Friendica\Content\ContactSelector;
26 use Friendica\Content\Nav;
27 use Friendica\Content\Pager;
28 use Friendica\Content\Widget;
29 use Friendica\Core\Logger;
30 use Friendica\Core\Protocol;
31 use Friendica\Core\Renderer;
32 use Friendica\Core\System;
33 use Friendica\Core\Theme;
34 use Friendica\Core\Worker;
35 use Friendica\Database\DBA;
36 use Friendica\DI;
37 use Friendica\Model;
38 use Friendica\Model\User;
39 use Friendica\Module\Security\Login;
40 use Friendica\Network\HTTPException\InternalServerErrorException;
41 use Friendica\Network\HTTPException\NotFoundException;
42 use Friendica\Worker\UpdateContact;
43
44 /**
45  *  Manages and show Contacts and their content
46  */
47 class Contact extends BaseModule
48 {
49         const TAB_CONVERSATIONS = 1;
50         const TAB_POSTS = 2;
51         const TAB_PROFILE = 3;
52         const TAB_CONTACTS = 4;
53         const TAB_ADVANCED = 5;
54         const TAB_MEDIA = 6;
55
56         private static function batchActions()
57         {
58                 if (empty($_POST['contact_batch']) || !is_array($_POST['contact_batch'])) {
59                         return;
60                 }
61
62                 $redirectUrl = $_POST['redirect_url'] ?? 'contact';
63
64                 self::checkFormSecurityTokenRedirectOnError($redirectUrl, 'contact_batch_actions');
65
66                 $orig_records = Model\Contact::selectToArray(['id', 'uid'], ['id' => $_POST['contact_batch'], 'uid' => [0, DI::userSession()->getLocalUserId()], 'self' => false, 'deleted' => false]);
67
68                 $count_actions = 0;
69                 foreach ($orig_records as $orig_record) {
70                         $cdata = Model\Contact::getPublicAndUserContactID($orig_record['id'], DI::userSession()->getLocalUserId());
71                         if (empty($cdata) || DI::userSession()->getPublicContactId() === $cdata['public']) {
72                                 // No action available on your own contact
73                                 continue;
74                         }
75
76                         if (!empty($_POST['contacts_batch_update']) && $cdata['user']) {
77                                 self::updateContactFromPoll($cdata['user']);
78                                 $count_actions++;
79                         }
80
81                         if (!empty($_POST['contacts_batch_block'])) {
82                                 self::toggleBlockContact($cdata['public'], DI::userSession()->getLocalUserId());
83                                 $count_actions++;
84                         }
85
86                         if (!empty($_POST['contacts_batch_ignore'])) {
87                                 self::toggleIgnoreContact($cdata['public']);
88                                 $count_actions++;
89                         }
90
91                         if (!empty($_POST['contacts_batch_collapse'])) {
92                                 self::toggleCollapseContact($cdata['public']);
93                                 $count_actions++;
94                         }
95                 }
96                 if ($count_actions > 0) {
97                         DI::sysmsg()->addInfo(DI::l10n()->tt('%d contact edited.', '%d contacts edited.', $count_actions));
98                 }
99
100                 DI::baseUrl()->redirect($redirectUrl);
101         }
102
103         protected function post(array $request = [])
104         {
105                 if (!DI::userSession()->getLocalUserId()) {
106                         return;
107                 }
108
109                 // @TODO: Replace with parameter from router
110                 if (DI::args()->getArgv()[1] === 'batch') {
111                         self::batchActions();
112                 }
113         }
114
115         /* contact actions */
116
117         /**
118          * @param int $contact_id Id of contact with uid != 0
119          * @throws NotFoundException
120          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
121          * @throws \ImagickException
122          */
123         public static function updateContactFromPoll(int $contact_id)
124         {
125                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'network'], ['id' => $contact_id, 'uid' => DI::userSession()->getLocalUserId(), 'deleted' => false]);
126                 if (!DBA::isResult($contact)) {
127                         return;
128                 }
129
130                 if ($contact['network'] == Protocol::OSTATUS) {
131                         $result = Model\Contact::createFromProbeForUser($contact['uid'], $contact['url'], $contact['network']);
132
133                         if ($result['success']) {
134                                 Model\Contact::update(['subhub' => 1], ['id' => $contact_id]);
135                         }
136
137                         // pull feed and consume it, which should subscribe to the hub.
138                         Worker::add(Worker::PRIORITY_HIGH, 'OnePoll', $contact_id, 'force');
139                 } else {
140                         try {
141                                 UpdateContact::add(Worker::PRIORITY_HIGH, $contact_id);
142                         } catch (\InvalidArgumentException $e) {
143                                 Logger::notice($e->getMessage(), ['contact' => $contact, 'callstack' => System::callstack()]);
144                         }
145                 }
146         }
147
148         /**
149          * Toggles the blocked status of a contact identified by id.
150          *
151          * @param int $contact_id Id of the contact with uid = 0
152          * @param int $owner_id   Id of the user we want to block the contact for
153          * @throws \Exception
154          */
155         private static function toggleBlockContact(int $contact_id, int $owner_id)
156         {
157                 $blocked = !Model\Contact\User::isBlocked($contact_id, $owner_id);
158                 Model\Contact\User::setBlocked($contact_id, $owner_id, $blocked);
159         }
160
161         /**
162          * Toggles the ignored status of a contact identified by id.
163          *
164          * @param int $contact_id Id of the contact with uid = 0
165          * @throws \Exception
166          */
167         private static function toggleIgnoreContact(int $contact_id)
168         {
169                 $ignored = !Model\Contact\User::isIgnored($contact_id, DI::userSession()->getLocalUserId());
170                 Model\Contact\User::setIgnored($contact_id, DI::userSession()->getLocalUserId(), $ignored);
171         }
172
173         /**
174          * Toggles the collapsed status of a contact identified by id.
175          *
176          * @param int $contact_id Id of the contact with uid = 0
177          * @throws \Exception
178          */
179         private static function toggleCollapseContact(int $contact_id)
180         {
181                 $collapsed = !Model\Contact\User::isCollapsed($contact_id, DI::userSession()->getLocalUserId());
182                 Model\Contact\User::setCollapsed($contact_id, DI::userSession()->getLocalUserId(), $collapsed);
183         }
184
185         protected function content(array $request = []): string
186         {
187                 if (!DI::userSession()->getLocalUserId()) {
188                         return Login::form($_SERVER['REQUEST_URI']);
189                 }
190
191                 $search = trim($_GET['search'] ?? '');
192                 $nets   = trim($_GET['nets']   ?? '');
193                 $rel    = trim($_GET['rel']    ?? '');
194                 $group  = trim($_GET['group']  ?? '');
195
196                 $accounttype = $_GET['accounttype'] ?? '';
197                 $accounttypeid = User::getAccountTypeByString($accounttype);
198
199                 $page = DI::page();
200
201                 $page->registerFooterScript(Theme::getPathForFile('asset/typeahead.js/dist/typeahead.bundle.js'));
202                 $page->registerFooterScript(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.js'));
203                 $page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
204                 $page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css'));
205
206                 $vcard_widget = '';
207                 $findpeople_widget = Widget::findPeople();
208                 if (isset($_GET['add'])) {
209                         $follow_widget = Widget::follow($_GET['add']);
210                 } else {
211                         $follow_widget = Widget::follow();
212                 }
213
214                 $account_widget = Widget::accountTypes($_SERVER['REQUEST_URI'], $accounttype);
215                 $networks_widget = Widget::networks($_SERVER['REQUEST_URI'], $nets);
216                 $rel_widget = Widget::contactRels($_SERVER['REQUEST_URI'], $rel);
217                 $groups_widget = Widget::groups($_SERVER['REQUEST_URI'], $group);
218
219                 DI::page()['aside'] .= $vcard_widget . $findpeople_widget . $follow_widget . $rel_widget . $groups_widget . $networks_widget . $account_widget;
220
221                 $tpl = Renderer::getMarkupTemplate('contacts-head.tpl');
222                 DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [
223                         '$baseurl' => DI::baseUrl(),
224                 ]);
225
226                 $o = '';
227                 Nav::setSelected('contact');
228
229                 $_SESSION['return_path'] = DI::args()->getQueryString();
230
231                 $sql_values = [DI::userSession()->getLocalUserId()];
232
233                 // @TODO: Replace with parameter from router
234                 $type = DI::args()->getArgv()[1] ?? '';
235
236                 switch ($type) {
237                         case 'blocked':
238                                 $sql_extra = " AND `id` IN (SELECT `cid` FROM `user-contact` WHERE `user-contact`.`uid` = ? AND `user-contact`.`blocked`)";
239                                 // This makes the query look for contact.uid = 0
240                                 array_unshift($sql_values, 0);
241                                 break;
242                         case 'hidden':
243                                 $sql_extra = " AND `hidden` AND NOT `blocked` AND NOT `pending`";
244                                 break;
245                         case 'ignored':
246                                 $sql_extra = " AND `id` IN (SELECT `cid` FROM `user-contact` WHERE `user-contact`.`uid` = ? AND `user-contact`.`ignored`)";
247                                 // This makes the query look for contact.uid = 0
248                                 array_unshift($sql_values, 0);
249                                 break;
250                         case 'collapsed':
251                                 $sql_extra = " AND `id` IN (SELECT `cid` FROM `user-contact` WHERE `user-contact`.`uid` = ? AND `user-contact`.`collapsed`)";
252                                 // This makes the query look for contact.uid = 0
253                                 array_unshift($sql_values, 0);
254                                 break;
255                         case 'archived':
256                                 $sql_extra = " AND `archive` AND NOT `blocked` AND NOT `pending`";
257                                 break;
258                         case 'pending':
259                                 $sql_extra = " AND `pending` AND NOT `archive` AND NOT `failed` AND ((`rel` = ?)
260                                         OR `id` IN (SELECT `contact-id` FROM `intro` WHERE `intro`.`uid` = ? AND NOT `ignore`))";
261                                 $sql_values[] = Model\Contact::SHARING;
262                                 $sql_values[] = DI::userSession()->getLocalUserId();
263                                 break;
264                         default:
265                                 $sql_extra = " AND NOT `archive` AND NOT `blocked` AND NOT `pending`";
266                                 break;
267                 }
268
269                 if (isset($accounttypeid)) {
270                         $sql_extra .= " AND `contact-type` = ?";
271                         $sql_values[] = $accounttypeid;
272                 }
273
274                 $searching = false;
275                 $search_hdr = null;
276                 if ($search) {
277                         $searching = true;
278                         $search_hdr = $search;
279                         $search_txt = preg_quote(trim($search, ' @!'));
280                         $sql_extra .= " AND (`name` REGEXP ? OR `url` REGEXP ? OR `nick` REGEXP ? OR `addr` REGEXP ? OR `alias` REGEXP ?)";
281                         $sql_values[] = $search_txt;
282                         $sql_values[] = $search_txt;
283                         $sql_values[] = $search_txt;
284                         $sql_values[] = $search_txt;
285                         $sql_values[] = $search_txt;
286                 }
287
288                 if ($nets) {
289                         $sql_extra .= " AND network = ? ";
290                         $sql_values[] = $nets;
291                 }
292
293                 switch ($rel) {
294                         case 'followers':
295                                 $sql_extra .= " AND `rel` IN (?, ?)";
296                                 $sql_values[] = Model\Contact::FOLLOWER;
297                                 $sql_values[] = Model\Contact::FRIEND;
298                                 break;
299                         case 'following':
300                                 $sql_extra .= " AND `rel` IN (?, ?)";
301                                 $sql_values[] = Model\Contact::SHARING;
302                                 $sql_values[] = Model\Contact::FRIEND;
303                                 break;
304                         case 'mutuals':
305                                 $sql_extra .= " AND `rel` = ?";
306                                 $sql_values[] = Model\Contact::FRIEND;
307                                 break;
308                 }
309
310                 if ($group) {
311                         $sql_extra .= " AND `id` IN (SELECT `contact-id` FROM `group_member` WHERE `gid` = ?)";
312                         $sql_values[] = $group;
313                 }
314
315                 $networks = Widget::unavailableNetworks();
316                 $sql_extra .= " AND NOT `network` IN (" . substr(str_repeat("?, ", count($networks)), 0, -2) . ")";
317                 $sql_values = array_merge($sql_values, $networks);
318
319                 $condition = ["`uid` = ? AND NOT `self` AND NOT `deleted`" . $sql_extra];
320                 $condition = array_merge($condition, $sql_values);
321
322                 $total = DBA::count('contact', $condition);
323
324                 $pager = new Pager(DI::l10n(), DI::args()->getQueryString());
325
326                 $contacts = [];
327
328                 $stmt = DBA::select('contact', [], $condition, ['order' => ['name'], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]]);
329
330                 while ($contact = DBA::fetch($stmt)) {
331                         $contact['blocked'] = Model\Contact\User::isBlocked($contact['id'], DI::userSession()->getLocalUserId());
332                         $contact['readonly'] = Model\Contact\User::isIgnored($contact['id'], DI::userSession()->getLocalUserId());
333                         $contacts[] = self::getContactTemplateVars($contact);
334                 }
335                 DBA::close($stmt);
336
337                 $tabs = [
338                         [
339                                 'label' => DI::l10n()->t('All Contacts'),
340                                 'url'   => 'contact',
341                                 'sel'   => !$type ? 'active' : '',
342                                 'title' => DI::l10n()->t('Show all contacts'),
343                                 'id'    => 'showall-tab',
344                                 'accesskey' => 'l',
345                         ],
346                         [
347                                 'label' => DI::l10n()->t('Pending'),
348                                 'url'   => 'contact/pending',
349                                 'sel'   => $type == 'pending' ? 'active' : '',
350                                 'title' => DI::l10n()->t('Only show pending contacts'),
351                                 'id'    => 'showpending-tab',
352                                 'accesskey' => 'p',
353                         ],
354                         [
355                                 'label' => DI::l10n()->t('Blocked'),
356                                 'url'   => 'contact/blocked',
357                                 'sel'   => $type == 'blocked' ? 'active' : '',
358                                 'title' => DI::l10n()->t('Only show blocked contacts'),
359                                 'id'    => 'showblocked-tab',
360                                 'accesskey' => 'b',
361                         ],
362                         [
363                                 'label' => DI::l10n()->t('Ignored'),
364                                 'url'   => 'contact/ignored',
365                                 'sel'   => $type == 'ignored' ? 'active' : '',
366                                 'title' => DI::l10n()->t('Only show ignored contacts'),
367                                 'id'    => 'showignored-tab',
368                                 'accesskey' => 'i',
369                         ],
370                         [
371                                 'label' => DI::l10n()->t('Collapsed'),
372                                 'url'   => 'contact/collapsed',
373                                 'sel'   => $type == 'collapsed' ? 'active' : '',
374                                 'title' => DI::l10n()->t('Only show collapsed contacts'),
375                                 'id'    => 'showcollapsed-tab',
376                                 'accesskey' => 'c',
377                         ],
378                         [
379                                 'label' => DI::l10n()->t('Archived'),
380                                 'url'   => 'contact/archived',
381                                 'sel'   => $type == 'archived' ? 'active' : '',
382                                 'title' => DI::l10n()->t('Only show archived contacts'),
383                                 'id'    => 'showarchived-tab',
384                                 'accesskey' => 'y',
385                         ],
386                         [
387                                 'label' => DI::l10n()->t('Hidden'),
388                                 'url'   => 'contact/hidden',
389                                 'sel'   => $type == 'hidden' ? 'active' : '',
390                                 'title' => DI::l10n()->t('Only show hidden contacts'),
391                                 'id'    => 'showhidden-tab',
392                                 'accesskey' => 'h',
393                         ],
394                         [
395                                 'label' => DI::l10n()->t('Groups'),
396                                 'url'   => 'group',
397                                 'sel'   => '',
398                                 'title' => DI::l10n()->t('Organize your contact groups'),
399                                 'id'    => 'contactgroups-tab',
400                                 'accesskey' => 'e',
401                         ],
402                 ];
403
404                 $tabs_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
405                 $tabs_html = Renderer::replaceMacros($tabs_tpl, ['$tabs' => $tabs]);
406
407                 switch ($rel) {
408                         case 'followers': $header = DI::l10n()->t('Followers'); break;
409                         case 'following': $header = DI::l10n()->t('Following'); break;
410                         case 'mutuals':   $header = DI::l10n()->t('Mutual friends'); break;
411                         default:          $header = DI::l10n()->t('Contacts');
412                 }
413
414                 switch ($type) {
415                         case 'pending':   $header .= ' - ' . DI::l10n()->t('Pending'); break;
416                         case 'blocked':   $header .= ' - ' . DI::l10n()->t('Blocked'); break;
417                         case 'hidden':    $header .= ' - ' . DI::l10n()->t('Hidden'); break;
418                         case 'ignored':   $header .= ' - ' . DI::l10n()->t('Ignored'); break;
419                         case 'collapsed': $header .= ' - ' . DI::l10n()->t('Collapsed'); break;
420                         case 'archived':  $header .= ' - ' . DI::l10n()->t('Archived'); break;
421                 }
422
423                 $header .= $nets ? ' - ' . ContactSelector::networkToName($nets) : '';
424
425                 $tpl = Renderer::getMarkupTemplate('contacts-template.tpl');
426                 $o .= Renderer::replaceMacros($tpl, [
427                         '$header'     => $header,
428                         '$tabs'       => $tabs_html,
429                         '$total'      => $total,
430                         '$search'     => $search_hdr,
431                         '$desc'       => DI::l10n()->t('Search your contacts'),
432                         '$finding'    => $searching ? DI::l10n()->t('Results for: %s', $search) : '',
433                         '$submit'     => DI::l10n()->t('Find'),
434                         '$cmd'        => DI::args()->getCommand(),
435                         '$contacts'   => $contacts,
436                         '$form_security_token'  => BaseModule::getFormSecurityToken('contact_batch_actions'),
437                         'multiselect' => 1,
438                         '$batch_actions' => [
439                                 'contacts_batch_update'    => DI::l10n()->t('Update'),
440                                 'contacts_batch_block'     => DI::l10n()->t('Block') . '/' . DI::l10n()->t('Unblock'),
441                                 'contacts_batch_ignore'    => DI::l10n()->t('Ignore') . '/' . DI::l10n()->t('Unignore'),
442                                 'contacts_batch_collapse'  => DI::l10n()->t('Collapse') . '/' . DI::l10n()->t('Uncollapse'),
443                         ],
444                         '$h_batch_actions' => DI::l10n()->t('Batch Actions'),
445                         '$paginate'   => $pager->renderFull($total),
446                 ]);
447
448                 return $o;
449         }
450
451         /**
452          * List of pages for the Contact TabBar
453          *
454          * Available Pages are 'Conversations', 'Profile', 'Contacts' and 'Common Friends'
455          *
456          * @param array $contact    The contact array
457          * @param int   $active_tab 1 if tab should be marked as active
458          *
459          * @return string HTML string of the contact page tabs buttons.
460          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
461          * @throws \ImagickException
462          */
463         public static function getTabsHTML(array $contact, int $active_tab)
464         {
465                 $cid = $pcid = $contact['id'];
466                 $data = Model\Contact::getPublicAndUserContactID($contact['id'], DI::userSession()->getLocalUserId());
467                 if (!empty($data['user']) && ($contact['id'] == $data['public'])) {
468                         $cid = $data['user'];
469                 } elseif (!empty($data['public'])) {
470                         $pcid = $data['public'];
471                 }
472
473                 // tabs
474                 $tabs = [
475                         [
476                                 'label' => DI::l10n()->t('Profile'),
477                                 'url'   => 'contact/' . $cid,
478                                 'sel'   => (($active_tab == self::TAB_PROFILE) ? 'active' : ''),
479                                 'title' => DI::l10n()->t('Profile Details'),
480                                 'id'    => 'profile-tab',
481                                 'accesskey' => 'o',
482                         ],
483                         [
484                                 'label' => DI::l10n()->t('Conversations'),
485                                 'url'   => 'contact/' . $pcid . '/conversations',
486                                 'sel'   => (($active_tab == self::TAB_CONVERSATIONS) ? 'active' : ''),
487                                 'title' => DI::l10n()->t('Conversations started by this contact'),
488                                 'id'    => 'status-tab',
489                                 'accesskey' => 'm',
490                         ],
491                         [
492                                 'label' => DI::l10n()->t('Posts and Comments'),
493                                 'url'   => 'contact/' . $pcid . '/posts',
494                                 'sel'   => (($active_tab == self::TAB_POSTS) ? 'active' : ''),
495                                 'title' => DI::l10n()->t('Individual Posts and Replies'),
496                                 'id'    => 'posts-tab',
497                                 'accesskey' => 'p',
498                         ],
499                         [
500                                 'label' => DI::l10n()->t('Media'),
501                                 'url'   => 'contact/' . $pcid . '/media',
502                                 'sel'   => (($active_tab == self::TAB_MEDIA) ? 'active' : ''),
503                                 'title' => DI::l10n()->t('Posts containing media objects'),
504                                 'id'    => 'media-tab',
505                                 'accesskey' => 'd',
506                         ],
507                         ['label' => DI::l10n()->t('Contacts'),
508                                 'url'   => 'contact/' . $pcid . '/contacts',
509                                 'sel'   => (($active_tab == self::TAB_CONTACTS) ? 'active' : ''),
510                                 'title' => DI::l10n()->t('View all known contacts'),
511                                 'id'    => 'contacts-tab',
512                                 'accesskey' => 't'
513                         ],
514                 ];
515
516                 if (!empty($contact['network']) && in_array($contact['network'], [Protocol::FEED, Protocol::MAIL]) && ($cid != $pcid)) {
517                         $tabs[] = ['label' => DI::l10n()->t('Advanced'),
518                                 'url'   => 'contact/' . $cid . '/advanced/',
519                                 'sel'   => (($active_tab == self::TAB_ADVANCED) ? 'active' : ''),
520                                 'title' => DI::l10n()->t('Advanced Contact Settings'),
521                                 'id'    => 'advanced-tab',
522                                 'accesskey' => 'r'
523                         ];
524                 }
525
526                 $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
527                 $tab_str = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
528
529                 return $tab_str;
530         }
531
532         /**
533          * Return the fields for the contact template
534          *
535          * @param array $contact Contact array
536          * @return array Template fields
537          * @throws InternalServerErrorException
538          * @throws \ImagickException
539          */
540         public static function getContactTemplateVars(array $contact): array
541         {
542                 $alt_text = '';
543
544                 if (!empty($contact['url']) && isset($contact['uid']) && ($contact['uid'] == 0) && DI::userSession()->getLocalUserId()) {
545                         $personal = Model\Contact::getByURL($contact['url'], false, ['uid', 'rel', 'self'], DI::userSession()->getLocalUserId());
546                         if (!empty($personal)) {
547                                 $contact['uid']  = $personal['uid'];
548                                 $contact['rel']  = $personal['rel'];
549                                 $contact['self'] = $personal['self'];
550                         }
551                 }
552
553                 if (!empty($contact['uid']) && !empty($contact['rel']) && DI::userSession()->getLocalUserId() == $contact['uid']) {
554                         switch ($contact['rel']) {
555                                 case Model\Contact::FRIEND:
556                                         $alt_text = DI::l10n()->t('Mutual Friendship');
557                                         break;
558
559                                 case Model\Contact::FOLLOWER;
560                                         $alt_text = DI::l10n()->t('is a fan of yours');
561                                         break;
562
563                                 case Model\Contact::SHARING;
564                                         $alt_text = DI::l10n()->t('you are a fan of');
565                                         break;
566
567                                 default:
568                                         break;
569                         }
570                 }
571
572                 $url = Model\Contact::magicLinkByContact($contact);
573
574                 if (strpos($url, 'contact/redir/') === 0) {
575                         $sparkle = ' class="sparkle" ';
576                 } else {
577                         $sparkle = '';
578                 }
579
580                 if ($contact['pending']) {
581                         if (in_array($contact['rel'], [Model\Contact::FRIEND, Model\Contact::SHARING])) {
582                                 $alt_text = DI::l10n()->t('Pending outgoing contact request');
583                         } else {
584                                 $alt_text = DI::l10n()->t('Pending incoming contact request');
585                         }
586                 }
587
588                 if ($contact['self']) {
589                         $alt_text = DI::l10n()->t('This is you');
590                         $url      = $contact['url'];
591                         $sparkle  = '';
592                 }
593
594                 return [
595                         'id'           => $contact['id'],
596                         'url'          => $url,
597                         'img_hover'    => DI::l10n()->t('Visit %s\'s profile [%s]', $contact['name'], $contact['url']),
598                         'photo_menu'   => Model\Contact::photoMenu($contact, DI::userSession()->getLocalUserId()),
599                         'thumb'        => Model\Contact::getThumb($contact, true),
600                         'alt_text'     => $alt_text,
601                         'name'         => $contact['name'],
602                         'nick'         => $contact['nick'],
603                         'details'      => $contact['location'],
604                         'tags'         => $contact['keywords'],
605                         'about'        => $contact['about'],
606                         'account_type' => Model\Contact::getAccountType($contact['contact-type']),
607                         'sparkle'      => $sparkle,
608                         'itemurl'      => ($contact['addr'] ?? '') ?: $contact['url'],
609                         'network'      => ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol'], $contact['gsid']),
610                 ];
611         }
612 }