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