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