]> git.mxchange.org Git - friendica.git/blob - src/Module/Contact.php
Merge branch 'friendica:develop' into doc_faq_client-clean-up
[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                         return;
109                 }
110
111                 // @TODO: Replace with parameter from router
112                 $contact_id = intval(DI::args()->getArgv()[1]);
113                 if (!$contact_id) {
114                         return;
115                 }
116
117                 if (!DBA::exists('contact', ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false])) {
118                         notice(DI::l10n()->t('Could not access contact record.'));
119                         DI::baseUrl()->redirect('contact');
120                         return; // NOTREACHED
121                 }
122
123                 Hook::callAll('contact_edit_post', $_POST);
124
125                 $hidden = !empty($_POST['hidden']);
126
127                 $notify = !empty($_POST['notify']);
128
129                 $fetch_further_information = intval($_POST['fetch_further_information'] ?? 0);
130
131                 $remote_self = $_POST['remote_self'] ?? false;
132
133                 $ffi_keyword_denylist = Strings::escapeHtml(trim($_POST['ffi_keyword_denylist'] ?? ''));
134
135                 $priority = intval($_POST['poll'] ?? 0);
136                 if ($priority > 5 || $priority < 0) {
137                         $priority = 0;
138                 }
139
140                 $info = Strings::escapeHtml(trim($_POST['info'] ?? ''));
141
142                 $r = Model\Contact::update([
143                         'priority'   => $priority,
144                         'info'       => $info,
145                         'hidden'     => $hidden,
146                         'notify_new_posts' => $notify,
147                         'fetch_further_information' => $fetch_further_information,
148                         'remote_self' => $remote_self,
149                         'ffi_keyword_denylist'     => $ffi_keyword_denylist],
150                         ['id' => $contact_id, 'uid' => local_user()]
151                 );
152
153                 if (!DBA::isResult($r)) {
154                         notice(DI::l10n()->t('Failed to update contact record.'));
155                 }
156                 return;
157         }
158
159         /* contact actions */
160
161         /**
162          * @param int $contact_id Id of contact with uid != 0
163          * @throws NotFoundException
164          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
165          * @throws \ImagickException
166          */
167         private static function updateContactFromPoll(int $contact_id)
168         {
169                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'network'], ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false]);
170                 if (!DBA::isResult($contact)) {
171                         return;
172                 }
173
174                 if ($contact['network'] == Protocol::OSTATUS) {
175                         $result = Model\Contact::createFromProbeForUser($contact['uid'], $contact['url'], $contact['network']);
176
177                         if ($result['success']) {
178                                 Model\Contact::update(['subhub' => 1], ['id' => $contact_id]);
179                         }
180
181                         // pull feed and consume it, which should subscribe to the hub.
182                         Worker::add(PRIORITY_HIGH, 'OnePoll', $contact_id, 'force');
183                 } else {
184                         Worker::add(PRIORITY_HIGH, 'UpdateContact', $contact_id);
185                 }
186         }
187
188         /**
189          * @param int $contact_id Id of the contact with uid != 0
190          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
191          * @throws \ImagickException
192          */
193         private static function updateContactFromProbe(int $contact_id)
194         {
195                 $contact = DBA::selectFirst('contact', ['url'], ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false]);
196                 if (!DBA::isResult($contact)) {
197                         return;
198                 }
199
200                 // Update the entry in the contact table
201                 Model\Contact::updateFromProbe($contact_id);
202         }
203
204         /**
205          * Toggles the blocked status of a contact identified by id.
206          *
207          * @param int $contact_id Id of the contact with uid = 0
208          * @param int $owner_id   Id of the user we want to block the contact for
209          * @throws \Exception
210          */
211         private static function toggleBlockContact(int $contact_id, int $owner_id)
212         {
213                 $blocked = !Model\Contact\User::isBlocked($contact_id, $owner_id);
214                 Model\Contact\User::setBlocked($contact_id, $owner_id, $blocked);
215         }
216
217         /**
218          * Toggles the ignored status of a contact identified by id.
219          *
220          * @param int $contact_id Id of the contact with uid = 0
221          * @throws \Exception
222          */
223         private static function toggleIgnoreContact(int $contact_id)
224         {
225                 $ignored = !Model\Contact\User::isIgnored($contact_id, local_user());
226                 Model\Contact\User::setIgnored($contact_id, local_user(), $ignored);
227         }
228
229         public function content($update = 0): string
230         {
231                 if (!local_user()) {
232                         return Login::form($_SERVER['REQUEST_URI']);
233                 }
234
235                 $a = DI::app();
236
237                 $search = trim($_GET['search'] ?? '');
238                 $nets   = trim($_GET['nets']   ?? '');
239                 $rel    = trim($_GET['rel']    ?? '');
240                 $group  = trim($_GET['group']  ?? '');
241
242                 $accounttype = $_GET['accounttype'] ?? '';
243                 $accounttypeid = User::getAccountTypeByString($accounttype);
244
245                 $page = DI::page();
246
247                 $page->registerFooterScript(Theme::getPathForFile('asset/typeahead.js/dist/typeahead.bundle.js'));
248                 $page->registerFooterScript(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.js'));
249                 $page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
250                 $page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css'));
251
252                 $contact = null;
253                 // @TODO: Replace with parameter from router
254                 if (DI::args()->getArgc() == 2 && intval(DI::args()->getArgv()[1])
255                         || DI::args()->getArgc() == 3 && intval(DI::args()->getArgv()[1]) && in_array(DI::args()->getArgv()[2], ['posts', 'conversations'])
256                 ) {
257                         $contact_id = intval(DI::args()->getArgv()[1]);
258
259                         // Ensure to use the user contact when the public contact was provided
260                         $data = Model\Contact::getPublicAndUserContactID($contact_id, local_user());
261                         if (!empty($data['user']) && ($contact_id == $data['public'])) {
262                                 $contact_id = $data['user'];
263                         }
264
265                         if (!empty($data)) {
266                                 $contact = DBA::selectFirst('contact', [], [
267                                         'id'      => $contact_id,
268                                         'uid'     => [0, local_user()],
269                                         'deleted' => false
270                                 ]);
271
272                                 // Don't display contacts that are about to be deleted
273                                 if (DBA::isResult($contact) && !empty($contact['network']) && $contact['network'] == Protocol::PHANTOM) {
274                                         $contact = false;
275                                 }
276                         }
277                 }
278
279                 if (DBA::isResult($contact)) {
280                         if ($contact['self']) {
281                                 // @TODO: Replace with parameter from router
282                                 if ((DI::args()->getArgc() == 3) && intval(DI::args()->getArgv()[1]) && in_array(DI::args()->getArgv()[2], ['posts', 'conversations'])) {
283                                         DI::baseUrl()->redirect('profile/' . $contact['nick']);
284                                 } else {
285                                         DI::baseUrl()->redirect('profile/' . $contact['nick'] . '/profile');
286                                 }
287                         }
288
289                         $vcard_widget = Widget\VCard::getHTML($contact);
290
291                         $findpeople_widget = '';
292                         $follow_widget = '';
293                         $account_widget = '';
294                         $networks_widget = '';
295                         $rel_widget = '';
296
297                         if ($contact['uid'] != 0) {
298                                 $groups_widget = Model\Group::sidebarWidget('contact', 'group', 'full', 'everyone', $contact_id);
299                         } else {
300                                 $groups_widget = '';
301                         }
302                 } else {
303                         $vcard_widget = '';
304                         $findpeople_widget = Widget::findPeople();
305                         if (isset($_GET['add'])) {
306                                 $follow_widget = Widget::follow($_GET['add']);
307                         } else {
308                                 $follow_widget = Widget::follow();
309                         }
310
311                         $account_widget = Widget::accounttypes($_SERVER['REQUEST_URI'], $accounttype);
312                         $networks_widget = Widget::networks($_SERVER['REQUEST_URI'], $nets);
313                         $rel_widget = Widget::contactRels($_SERVER['REQUEST_URI'], $rel);
314                         $groups_widget = Widget::groups($_SERVER['REQUEST_URI'], $group);
315                 }
316
317                 DI::page()['aside'] .= $vcard_widget . $findpeople_widget . $follow_widget . $account_widget . $groups_widget . $networks_widget . $rel_widget;
318
319                 $tpl = Renderer::getMarkupTemplate('contacts-head.tpl');
320                 DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [
321                         '$baseurl' => DI::baseUrl()->get(true),
322                 ]);
323
324                 $o = '';
325                 Nav::setSelected('contact');
326
327                 if (!local_user()) {
328                         notice(DI::l10n()->t('Permission denied.'));
329                         return Login::form();
330                 }
331
332                 if (DI::args()->getArgc() == 3) {
333                         $contact_id = intval(DI::args()->getArgv()[1]);
334                         if (!$contact_id) {
335                                 throw new BadRequestException();
336                         }
337
338                         // @TODO: Replace with parameter from router
339                         $cmd = DI::args()->getArgv()[2];
340
341                         $orig_record = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => [0, local_user()], 'self' => false, 'deleted' => false]);
342                         if (!DBA::isResult($orig_record)) {
343                                 throw new NotFoundException(DI::l10n()->t('Contact not found'));
344                         }
345
346                         if ($cmd === 'posts') {
347                                 return self::getPostsHTML($contact_id);
348                         }
349
350                         if ($cmd === 'conversations') {
351                                 return self::getConversationsHMTL($a, $contact_id, $update);
352                         }
353
354                         self::checkFormSecurityTokenRedirectOnError('contact/' . $contact_id, 'contact_action', 't');
355
356                         $cdata = Model\Contact::getPublicAndUserContactID($orig_record['id'], local_user());
357                         if (empty($cdata)) {
358                                 throw new NotFoundException(DI::l10n()->t('Contact not found'));
359                         }
360
361                         if ($cmd === 'update' && $cdata['user']) {
362                                 self::updateContactFromPoll($cdata['user']);
363                                 DI::baseUrl()->redirect('contact/' . $contact_id);
364                                 // NOTREACHED
365                         }
366
367                         if ($cmd === 'updateprofile' && $cdata['user']) {
368                                 self::updateContactFromProbe($cdata['user']);
369                                 DI::baseUrl()->redirect('contact/' . $contact_id);
370                                 // NOTREACHED
371                         }
372
373                         if ($cmd === 'block') {
374                                 if (public_contact() === $cdata['public']) {
375                                         throw new BadRequestException(DI::l10n()->t('You can\'t block yourself'));
376                                 }
377
378                                 self::toggleBlockContact($cdata['public'], local_user());
379
380                                 $blocked = Model\Contact\User::isBlocked($contact_id, local_user());
381                                 info(($blocked ? DI::l10n()->t('Contact has been blocked') : DI::l10n()->t('Contact has been unblocked')));
382
383                                 DI::baseUrl()->redirect('contact/' . $contact_id);
384                                 // NOTREACHED
385                         }
386
387                         if ($cmd === 'ignore') {
388                                 if (public_contact() === $cdata['public']) {
389                                         throw new BadRequestException(DI::l10n()->t('You can\'t ignore yourself'));
390                                 }
391
392                                 self::toggleIgnoreContact($cdata['public']);
393
394                                 $ignored = Model\Contact\User::isIgnored($cdata['public'], local_user());
395                                 info(($ignored ? DI::l10n()->t('Contact has been ignored') : DI::l10n()->t('Contact has been unignored')));
396
397                                 DI::baseUrl()->redirect('contact/' . $contact_id);
398                                 // NOTREACHED
399                         }
400                 }
401
402                 $_SESSION['return_path'] = DI::args()->getQueryString();
403
404                 if (!empty($contact)) {
405                         DI::page()['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('contact_head.tpl'), [
406                                 '$baseurl' => DI::baseUrl()->get(true),
407                         ]);
408
409                         $contact['blocked']  = Model\Contact\User::isBlocked($contact['id'], local_user());
410                         $contact['readonly'] = Model\Contact\User::isIgnored($contact['id'], local_user());
411
412                         $relation_text = '';
413                         switch ($contact['rel']) {
414                                 case Model\Contact::FRIEND:
415                                         $relation_text = DI::l10n()->t('You are mutual friends with %s');
416                                         break;
417
418                                 case Model\Contact::FOLLOWER;
419                                         $relation_text = DI::l10n()->t('You are sharing with %s');
420                                         break;
421
422                                 case Model\Contact::SHARING;
423                                         $relation_text = DI::l10n()->t('%s is sharing with you');
424                                         break;
425
426                                 default:
427                                         break;
428                         }
429
430                         if ($contact['uid'] == 0) {
431                                 $relation_text = '';
432                         }
433
434                         if (!in_array($contact['network'], array_merge(Protocol::FEDERATED, [Protocol::TWITTER]))) {
435                                 $relation_text = '';
436                         }
437
438                         $relation_text = sprintf($relation_text, $contact['name']);
439
440                         $url = Model\Contact::magicLinkByContact($contact);
441                         if (strpos($url, 'redir/') === 0) {
442                                 $sparkle = ' class="sparkle" ';
443                         } else {
444                                 $sparkle = '';
445                         }
446
447                         $insecure = DI::l10n()->t('Private communications are not available for this contact.');
448
449                         $last_update = (($contact['last-update'] <= DBA::NULL_DATETIME) ? DI::l10n()->t('Never') : DateTimeFormat::local($contact['last-update'], 'D, j M Y, g:i A'));
450
451                         if ($contact['last-update'] > DBA::NULL_DATETIME) {
452                                 $last_update .= ' ' . ($contact['failed'] ? DI::l10n()->t('(Update was not successful)') : DI::l10n()->t('(Update was successful)'));
453                         }
454                         $lblsuggest = (($contact['network'] === Protocol::DFRN) ? DI::l10n()->t('Suggest friends') : '');
455
456                         $poll_enabled = in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
457
458                         $nettype = DI::l10n()->t('Network type: %s', ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol'], $contact['gsid']));
459
460                         // tabs
461                         $tab_str = self::getTabsHTML($contact, self::TAB_PROFILE);
462
463                         $lost_contact = (($contact['archive'] && $contact['term-date'] > DBA::NULL_DATETIME && $contact['term-date'] < DateTimeFormat::utcNow()) ? DI::l10n()->t('Communications lost with this contact!') : '');
464
465                         $fetch_further_information = null;
466                         if ($contact['network'] == Protocol::FEED) {
467                                 $fetch_further_information = [
468                                         'fetch_further_information',
469                                         DI::l10n()->t('Fetch further information for feeds'),
470                                         $contact['fetch_further_information'],
471                                         DI::l10n()->t('Fetch information like preview pictures, title and teaser from the feed item. You can activate this if the feed doesn\'t contain much text. Keywords are taken from the meta header in the feed item and are posted as hash tags.'),
472                                         [
473                                                 '0' => DI::l10n()->t('Disabled'),
474                                                 '1' => DI::l10n()->t('Fetch information'),
475                                                 '3' => DI::l10n()->t('Fetch keywords'),
476                                                 '2' => DI::l10n()->t('Fetch information and keywords')
477                                         ]
478                                 ];
479                         }
480
481                         // Disable remote self for everything except feeds.
482                         // There is an issue when you repeat an item from maybe twitter and you got comments from friendica and twitter
483                         // Problem is, you couldn't reply to both networks.
484                         $allow_remote_self = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::FEED, Protocol::DFRN, Protocol::DIASPORA, Protocol::TWITTER])
485                                 && DI::config()->get('system', 'allow_users_remote_self');
486
487                         if ($contact['network'] == Protocol::FEED) {
488                                 $remote_self_options = [Model\Contact::MIRROR_DEACTIVATED => DI::l10n()->t('No mirroring'),
489                                         Model\Contact::MIRROR_FORWARDED => DI::l10n()->t('Mirror as forwarded posting'),
490                                         Model\Contact::MIRROR_OWN_POST => DI::l10n()->t('Mirror as my own posting')];
491                         } elseif (in_array($contact['network'], [Protocol::ACTIVITYPUB])) {
492                                 $remote_self_options = [Model\Contact::MIRROR_DEACTIVATED => DI::l10n()->t('No mirroring'),
493                                 Model\Contact::MIRROR_NATIVE_RESHARE => DI::l10n()->t('Native reshare')];
494                         } elseif (in_array($contact['network'], [Protocol::DFRN])) {
495                                 $remote_self_options = [Model\Contact::MIRROR_DEACTIVATED => DI::l10n()->t('No mirroring'),
496                                 Model\Contact::MIRROR_OWN_POST => DI::l10n()->t('Mirror as my own posting'),
497                                 Model\Contact::MIRROR_NATIVE_RESHARE => DI::l10n()->t('Native reshare')];
498                         } else {
499                                 $remote_self_options = [Model\Contact::MIRROR_DEACTIVATED => DI::l10n()->t('No mirroring'),
500                                         Model\Contact::MIRROR_OWN_POST => DI::l10n()->t('Mirror as my own posting')];
501                         }
502
503                         $poll_interval = null;
504                         if ((($contact['network'] == Protocol::FEED) && !DI::config()->get('system', 'adjust_poll_frequency')) || ($contact['network'] == Protocol::MAIL)) {
505                                 $poll_interval = ContactSelector::pollInterval($contact['priority'], !$poll_enabled);
506                         }
507
508                         // Load contactact related actions like hide, suggest, delete and others
509                         $contact_actions = self::getContactActions($contact);
510
511                         if ($contact['uid'] != 0) {
512                                 $lbl_info1 = DI::l10n()->t('Contact Information / Notes');
513                                 $contact_settings_label = DI::l10n()->t('Contact Settings');
514                         } else {
515                                 $lbl_info1 = null;
516                                 $contact_settings_label = null;
517                         }
518
519                         $tpl = Renderer::getMarkupTemplate('contact_edit.tpl');
520                         $o .= Renderer::replaceMacros($tpl, [
521                                 '$header'         => DI::l10n()->t('Contact'),
522                                 '$tab_str'        => $tab_str,
523                                 '$submit'         => DI::l10n()->t('Submit'),
524                                 '$lbl_info1'      => $lbl_info1,
525                                 '$lbl_info2'      => DI::l10n()->t('Their personal note'),
526                                 '$reason'         => trim($contact['reason']),
527                                 '$infedit'        => DI::l10n()->t('Edit contact notes'),
528                                 '$common_link'    => 'contact/' . $contact['id'] . '/contacts/common',
529                                 '$relation_text'  => $relation_text,
530                                 '$visit'          => DI::l10n()->t('Visit %s\'s profile [%s]', $contact['name'], $contact['url']),
531                                 '$blockunblock'   => DI::l10n()->t('Block/Unblock contact'),
532                                 '$ignorecont'     => DI::l10n()->t('Ignore contact'),
533                                 '$lblrecent'      => DI::l10n()->t('View conversations'),
534                                 '$lblsuggest'     => $lblsuggest,
535                                 '$nettype'        => $nettype,
536                                 '$poll_interval'  => $poll_interval,
537                                 '$poll_enabled'   => $poll_enabled,
538                                 '$lastupdtext'    => DI::l10n()->t('Last update:'),
539                                 '$lost_contact'   => $lost_contact,
540                                 '$updpub'         => DI::l10n()->t('Update public posts'),
541                                 '$last_update'    => $last_update,
542                                 '$udnow'          => DI::l10n()->t('Update now'),
543                                 '$contact_id'     => $contact['id'],
544                                 '$block_text'     => ($contact['blocked'] ? DI::l10n()->t('Unblock') : DI::l10n()->t('Block')),
545                                 '$ignore_text'    => ($contact['readonly'] ? DI::l10n()->t('Unignore') : DI::l10n()->t('Ignore')),
546                                 '$insecure'       => (in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA]) ? '' : $insecure),
547                                 '$info'           => $contact['info'],
548                                 '$cinfo'          => ['info', '', $contact['info'], ''],
549                                 '$blocked'        => ($contact['blocked'] ? DI::l10n()->t('Currently blocked') : ''),
550                                 '$ignored'        => ($contact['readonly'] ? DI::l10n()->t('Currently ignored') : ''),
551                                 '$archived'       => ($contact['archive'] ? DI::l10n()->t('Currently archived') : ''),
552                                 '$pending'        => ($contact['pending'] ? DI::l10n()->t('Awaiting connection acknowledge') : ''),
553                                 '$hidden'         => ['hidden', DI::l10n()->t('Hide this contact from others'), ($contact['hidden'] == 1), DI::l10n()->t('Replies/likes to your public posts <strong>may</strong> still be visible')],
554                                 '$notify'         => ['notify', DI::l10n()->t('Notification for new posts'), ($contact['notify_new_posts'] == 1), DI::l10n()->t('Send a notification of every new post of this contact')],
555                                 '$fetch_further_information' => $fetch_further_information,
556                                 '$ffi_keyword_denylist' => ['ffi_keyword_denylist', DI::l10n()->t('Keyword Deny List'), $contact['ffi_keyword_denylist'], DI::l10n()->t('Comma separated list of keywords that should not be converted to hashtags, when "Fetch information and keywords" is selected')],
557                                 '$photo'          => Model\Contact::getPhoto($contact),
558                                 '$name'           => $contact['name'],
559                                 '$sparkle'        => $sparkle,
560                                 '$url'            => $url,
561                                 '$profileurllabel'=> DI::l10n()->t('Profile URL'),
562                                 '$profileurl'     => $contact['url'],
563                                 '$account_type'   => Model\Contact::getAccountType($contact),
564                                 '$location'       => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['location']),
565                                 '$location_label' => DI::l10n()->t('Location:'),
566                                 '$xmpp'           => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['xmpp']),
567                                 '$xmpp_label'     => DI::l10n()->t('XMPP:'),
568                                 '$matrix'         => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['matrix']),
569                                 '$matrix_label'   => DI::l10n()->t('Matrix:'),
570                                 '$about'          => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['about'], BBCode::EXTERNAL),
571                                 '$about_label'    => DI::l10n()->t('About:'),
572                                 '$keywords'       => $contact['keywords'],
573                                 '$keywords_label' => DI::l10n()->t('Tags:'),
574                                 '$contact_action_button' => DI::l10n()->t('Actions'),
575                                 '$contact_actions'=> $contact_actions,
576                                 '$contact_status' => DI::l10n()->t('Status'),
577                                 '$contact_settings_label' => $contact_settings_label,
578                                 '$contact_profile_label' => DI::l10n()->t('Profile'),
579                                 '$allow_remote_self' => $allow_remote_self,
580                                 '$remote_self'       => ['remote_self',
581                                         DI::l10n()->t('Mirror postings from this contact'),
582                                         $contact['remote_self'],
583                                         DI::l10n()->t('Mark this contact as remote_self, this will cause friendica to repost new entries from this contact.'),
584                                         $remote_self_options
585                                 ],
586                         ]);
587
588                         $arr = ['contact' => $contact, 'output' => $o];
589
590                         Hook::callAll('contact_edit', $arr);
591
592                         return $arr['output'];
593                 }
594
595                 $sql_values = [local_user()];
596
597                 // @TODO: Replace with parameter from router
598                 $type = DI::args()->getArgv()[1] ?? '';
599
600                 switch ($type) {
601                         case 'blocked':
602                                 $sql_extra = " AND EXISTS(SELECT `id` from `user-contact` WHERE `contact`.`id` = `user-contact`.`cid` and `user-contact`.`uid` = ? and `user-contact`.`blocked`)";
603                                 // This makes the query look for contact.uid = 0
604                                 array_unshift($sql_values, 0);
605                                 break;
606                         case 'hidden':
607                                 $sql_extra = " AND `hidden` AND NOT `blocked` AND NOT `pending`";
608                                 break;
609                         case 'ignored':
610                                 $sql_extra = " AND EXISTS(SELECT `id` from `user-contact` WHERE `contact`.`id` = `user-contact`.`cid` and `user-contact`.`uid` = ? and `user-contact`.`ignored`)";
611                                 // This makes the query look for contact.uid = 0
612                                 array_unshift($sql_values, 0);
613                                 break;
614                         case 'archived':
615                                 $sql_extra = " AND `archive` AND NOT `blocked` AND NOT `pending`";
616                                 break;
617                         case 'pending':
618                                 $sql_extra = " AND `pending` AND NOT `archive` AND NOT `failed` AND ((`rel` = ?)
619                                         OR EXISTS (SELECT `id` FROM `intro` WHERE `contact-id` = `contact`.`id` AND NOT `ignore`))";
620                                 $sql_values[] = Model\Contact::SHARING;
621                                 break;
622                         default:
623                                 $sql_extra = " AND NOT `archive` AND NOT `blocked` AND NOT `pending`";
624                                 break;
625                 }
626
627                 if (isset($accounttypeid)) {
628                         $sql_extra .= " AND `contact-type` = ?";
629                         $sql_values[] = $accounttypeid;
630                 }
631
632                 $searching = false;
633                 $search_hdr = null;
634                 if ($search) {
635                         $searching = true;
636                         $search_hdr = $search;
637                         $search_txt = preg_quote($search);
638                         $sql_extra .= " AND (name REGEXP ? OR url REGEXP ? OR nick REGEXP ?)";
639                         $sql_values[] = $search_txt;
640                         $sql_values[] = $search_txt;
641                         $sql_values[] = $search_txt;
642                 }
643
644                 if ($nets) {
645                         $sql_extra .= " AND network = ? ";
646                         $sql_values[] = $nets;
647                 }
648
649                 switch ($rel) {
650                         case 'followers':
651                                 $sql_extra .= " AND `rel` IN (?, ?)";
652                                 $sql_values[] = Model\Contact::FOLLOWER;
653                                 $sql_values[] = Model\Contact::FRIEND;
654                                 break;
655                         case 'following':
656                                 $sql_extra .= " AND `rel` IN (?, ?)";
657                                 $sql_values[] = Model\Contact::SHARING;
658                                 $sql_values[] = Model\Contact::FRIEND;
659                                 break;
660                         case 'mutuals':
661                                 $sql_extra .= " AND `rel` = ?";
662                                 $sql_values[] = Model\Contact::FRIEND;
663                                 break;
664                 }
665
666                 if ($group) {
667                         $sql_extra .= " AND EXISTS(SELECT `id` FROM `group_member` WHERE `gid` = ? AND `contact`.`id` = `contact-id`)";
668                         $sql_values[] = $group;
669                 }
670
671                 $networks = Widget::unavailableNetworks();
672                 $sql_extra .= " AND NOT `network` IN (" . substr(str_repeat("?, ", count($networks)), 0, -2) . ")";
673                 $sql_values = array_merge($sql_values, $networks);
674
675                 $condition = ["`uid` = ? AND NOT `self` AND NOT `deleted`" . $sql_extra];
676                 $condition = array_merge($condition, $sql_values);
677
678                 $total = DBA::count('contact', $condition);
679
680                 $pager = new Pager(DI::l10n(), DI::args()->getQueryString());
681
682                 $contacts = [];
683
684                 $stmt = DBA::select('contact', [], $condition, ['order' => ['name'], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]]);
685
686                 while ($contact = DBA::fetch($stmt)) {
687                         $contact['blocked'] = Model\Contact\User::isBlocked($contact['id'], local_user());
688                         $contact['readonly'] = Model\Contact\User::isIgnored($contact['id'], local_user());
689                         $contacts[] = self::getContactTemplateVars($contact);
690                 }
691                 DBA::close($stmt);
692
693                 $tabs = [
694                         [
695                                 'label' => DI::l10n()->t('All Contacts'),
696                                 'url'   => 'contact',
697                                 'sel'   => !$type ? 'active' : '',
698                                 'title' => DI::l10n()->t('Show all contacts'),
699                                 'id'    => 'showall-tab',
700                                 'accesskey' => 'l',
701                         ],
702                         [
703                                 'label' => DI::l10n()->t('Pending'),
704                                 'url'   => 'contact/pending',
705                                 'sel'   => $type == 'pending' ? 'active' : '',
706                                 'title' => DI::l10n()->t('Only show pending contacts'),
707                                 'id'    => 'showpending-tab',
708                                 'accesskey' => 'p',
709                         ],
710                         [
711                                 'label' => DI::l10n()->t('Blocked'),
712                                 'url'   => 'contact/blocked',
713                                 'sel'   => $type == 'blocked' ? 'active' : '',
714                                 'title' => DI::l10n()->t('Only show blocked contacts'),
715                                 'id'    => 'showblocked-tab',
716                                 'accesskey' => 'b',
717                         ],
718                         [
719                                 'label' => DI::l10n()->t('Ignored'),
720                                 'url'   => 'contact/ignored',
721                                 'sel'   => $type == 'ignored' ? 'active' : '',
722                                 'title' => DI::l10n()->t('Only show ignored contacts'),
723                                 'id'    => 'showignored-tab',
724                                 'accesskey' => 'i',
725                         ],
726                         [
727                                 'label' => DI::l10n()->t('Archived'),
728                                 'url'   => 'contact/archived',
729                                 'sel'   => $type == 'archived' ? 'active' : '',
730                                 'title' => DI::l10n()->t('Only show archived contacts'),
731                                 'id'    => 'showarchived-tab',
732                                 'accesskey' => 'y',
733                         ],
734                         [
735                                 'label' => DI::l10n()->t('Hidden'),
736                                 'url'   => 'contact/hidden',
737                                 'sel'   => $type == 'hidden' ? 'active' : '',
738                                 'title' => DI::l10n()->t('Only show hidden contacts'),
739                                 'id'    => 'showhidden-tab',
740                                 'accesskey' => 'h',
741                         ],
742                         [
743                                 'label' => DI::l10n()->t('Groups'),
744                                 'url'   => 'group',
745                                 'sel'   => '',
746                                 'title' => DI::l10n()->t('Organize your contact groups'),
747                                 'id'    => 'contactgroups-tab',
748                                 'accesskey' => 'e',
749                         ],
750                 ];
751
752                 $tabs_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
753                 $tabs_html = Renderer::replaceMacros($tabs_tpl, ['$tabs' => $tabs]);
754
755                 switch ($rel) {
756                         case 'followers': $header = DI::l10n()->t('Followers'); break;
757                         case 'following': $header = DI::l10n()->t('Following'); break;
758                         case 'mutuals':   $header = DI::l10n()->t('Mutual friends'); break;
759                         default:          $header = DI::l10n()->t('Contacts');
760                 }
761
762                 switch ($type) {
763                         case 'pending':  $header .= ' - ' . DI::l10n()->t('Pending'); break;
764                         case 'blocked':  $header .= ' - ' . DI::l10n()->t('Blocked'); break;
765                         case 'hidden':   $header .= ' - ' . DI::l10n()->t('Hidden'); break;
766                         case 'ignored':  $header .= ' - ' . DI::l10n()->t('Ignored'); break;
767                         case 'archived': $header .= ' - ' . DI::l10n()->t('Archived'); break;
768                 }
769
770                 $header .= $nets ? ' - ' . ContactSelector::networkToName($nets) : '';
771
772                 $tpl = Renderer::getMarkupTemplate('contacts-template.tpl');
773                 $o .= Renderer::replaceMacros($tpl, [
774                         '$header'     => $header,
775                         '$tabs'       => $tabs_html,
776                         '$total'      => $total,
777                         '$search'     => $search_hdr,
778                         '$desc'       => DI::l10n()->t('Search your contacts'),
779                         '$finding'    => $searching ? DI::l10n()->t('Results for: %s', $search) : '',
780                         '$submit'     => DI::l10n()->t('Find'),
781                         '$cmd'        => DI::args()->getCommand(),
782                         '$contacts'   => $contacts,
783                         '$form_security_token'  => BaseModule::getFormSecurityToken('contact_batch_actions'),
784                         'multiselect' => 1,
785                         '$batch_actions' => [
786                                 'contacts_batch_update'  => DI::l10n()->t('Update'),
787                                 'contacts_batch_block'   => DI::l10n()->t('Block') . '/' . DI::l10n()->t('Unblock'),
788                                 'contacts_batch_ignore'  => DI::l10n()->t('Ignore') . '/' . DI::l10n()->t('Unignore'),
789                         ],
790                         '$h_batch_actions' => DI::l10n()->t('Batch Actions'),
791                         '$paginate'   => $pager->renderFull($total),
792                 ]);
793
794                 return $o;
795         }
796
797         /**
798          * List of pages for the Contact TabBar
799          *
800          * Available Pages are 'Status', 'Profile', 'Contacts' and 'Common Friends'
801          *
802          * @param array $contact    The contact array
803          * @param int   $active_tab 1 if tab should be marked as active
804          *
805          * @return string HTML string of the contact page tabs buttons.
806          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
807          * @throws \ImagickException
808          */
809         public static function getTabsHTML(array $contact, int $active_tab)
810         {
811                 $cid = $pcid = $contact['id'];
812                 $data = Model\Contact::getPublicAndUserContactID($contact['id'], local_user());
813                 if (!empty($data['user']) && ($contact['id'] == $data['public'])) {
814                         $cid = $data['user'];
815                 } elseif (!empty($data['public'])) {
816                         $pcid = $data['public'];
817                 }
818
819                 // tabs
820                 $tabs = [
821                         [
822                                 'label' => DI::l10n()->t('Status'),
823                                 'url'   => 'contact/' . $pcid . '/conversations',
824                                 'sel'   => (($active_tab == self::TAB_CONVERSATIONS) ? 'active' : ''),
825                                 'title' => DI::l10n()->t('Conversations started by this contact'),
826                                 'id'    => 'status-tab',
827                                 'accesskey' => 'm',
828                         ],
829                         [
830                                 'label' => DI::l10n()->t('Posts and Comments'),
831                                 'url'   => 'contact/' . $pcid . '/posts',
832                                 'sel'   => (($active_tab == self::TAB_POSTS) ? 'active' : ''),
833                                 'title' => DI::l10n()->t('Status Messages and Posts'),
834                                 'id'    => 'posts-tab',
835                                 'accesskey' => 'p',
836                         ],
837                         [
838                                 'label' => DI::l10n()->t('Media'),
839                                 'url'   => 'contact/' . $pcid . '/media',
840                                 'sel'   => (($active_tab == self::TAB_MEDIA) ? 'active' : ''),
841                                 'title' => DI::l10n()->t('Posts containing media objects'),
842                                 'id'    => 'media-tab',
843                                 'accesskey' => 'd',
844                         ],
845                         [
846                                 'label' => DI::l10n()->t('Profile'),
847                                 'url'   => 'contact/' . $cid,
848                                 'sel'   => (($active_tab == self::TAB_PROFILE) ? 'active' : ''),
849                                 'title' => DI::l10n()->t('Profile Details'),
850                                 'id'    => 'profile-tab',
851                                 'accesskey' => 'o',
852                         ],
853                         ['label' => DI::l10n()->t('Contacts'),
854                                 'url'   => 'contact/' . $pcid . '/contacts',
855                                 'sel'   => (($active_tab == self::TAB_CONTACTS) ? 'active' : ''),
856                                 'title' => DI::l10n()->t('View all known contacts'),
857                                 'id'    => 'contacts-tab',
858                                 'accesskey' => 't'
859                         ],
860                 ];
861
862                 if (!empty($contact['network']) && in_array($contact['network'], [Protocol::FEED, Protocol::MAIL]) && ($cid != $pcid)) {
863                         $tabs[] = ['label' => DI::l10n()->t('Advanced'),
864                                 'url'   => 'contact/' . $cid . '/advanced/',
865                                 'sel'   => (($active_tab == self::TAB_ADVANCED) ? 'active' : ''),
866                                 'title' => DI::l10n()->t('Advanced Contact Settings'),
867                                 'id'    => 'advanced-tab',
868                                 'accesskey' => 'r'
869                         ];
870                 }
871
872                 $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
873                 $tab_str = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
874
875                 return $tab_str;
876         }
877
878         public static function getConversationsHMTL($a, $contact_id, $update, $parent = 0)
879         {
880                 $o = '';
881
882                 if (!$update) {
883                         // We need the editor here to be able to reshare an item.
884                         if (local_user()) {
885                                 $o = DI::conversation()->statusEditor([], 0, true);
886                         }
887                 }
888
889                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
890
891                 if (!$update) {
892                         $o .= self::getTabsHTML($contact, self::TAB_CONVERSATIONS);
893                 }
894
895                 if (DBA::isResult($contact)) {
896                         if (!$update) {
897                                 $profiledata = Model\Contact::getByURLForUser($contact['url'], local_user());
898                                 DI::page()['aside'] = Widget\VCard::getHTML($profiledata);
899                         } else {
900                                 DI::page()['aside'] = '';
901                         }
902
903                         if ($contact['uid'] == 0) {
904                                 $o .= Model\Contact::getPostsFromId($contact['id'], true, $update, $parent);
905                         } else {
906                                 $o .= Model\Contact::getPostsFromUrl($contact['url'], true, $update, $parent);
907                         }
908                 }
909
910                 return $o;
911         }
912
913         private static function getPostsHTML(int $contact_id)
914         {
915                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
916
917                 $o = self::getTabsHTML($contact, self::TAB_POSTS);
918
919                 if (DBA::isResult($contact)) {
920                         $profiledata = Model\Contact::getByURLForUser($contact['url'], local_user());
921
922                         if (local_user() && in_array($profiledata['network'], Protocol::FEDERATED)) {
923                                 $profiledata['remoteconnect'] = DI::baseUrl() . '/follow?url=' . urlencode($profiledata['url']);
924                         }
925
926                         DI::page()['aside'] = Widget\VCard::getHTML($profiledata);
927
928                         if ($contact['uid'] == 0) {
929                                 $o .= Model\Contact::getPostsFromId($contact['id']);
930                         } else {
931                                 $o .= Model\Contact::getPostsFromUrl($contact['url']);
932                         }
933                 }
934
935                 return $o;
936         }
937
938         /**
939          * Return the fields for the contact template
940          *
941          * @param array $contact Contact array
942          * @return array Template fields
943          */
944         public static function getContactTemplateVars(array $contact)
945         {
946                 $alt_text = '';
947
948                 if (!empty($contact['url']) && isset($contact['uid']) && ($contact['uid'] == 0) && local_user()) {
949                         $personal = Model\Contact::getByURL($contact['url'], false, ['uid', 'rel', 'self'], local_user());
950                         if (!empty($personal)) {
951                                 $contact['uid'] = $personal['uid'];
952                                 $contact['rel'] = $personal['rel'];
953                                 $contact['self'] = $personal['self'];
954                         }
955                 }
956
957                 if (!empty($contact['uid']) && !empty($contact['rel']) && local_user() == $contact['uid']) {
958                         switch ($contact['rel']) {
959                                 case Model\Contact::FRIEND:
960                                         $alt_text = DI::l10n()->t('Mutual Friendship');
961                                         break;
962
963                                 case Model\Contact::FOLLOWER;
964                                         $alt_text = DI::l10n()->t('is a fan of yours');
965                                         break;
966
967                                 case Model\Contact::SHARING;
968                                         $alt_text = DI::l10n()->t('you are a fan of');
969                                         break;
970
971                                 default:
972                                         break;
973                         }
974                 }
975
976                 $url = Model\Contact::magicLinkByContact($contact);
977
978                 if (strpos($url, 'redir/') === 0) {
979                         $sparkle = ' class="sparkle" ';
980                 } else {
981                         $sparkle = '';
982                 }
983
984                 if ($contact['pending']) {
985                         if (in_array($contact['rel'], [Model\Contact::FRIEND, Model\Contact::SHARING])) {
986                                 $alt_text = DI::l10n()->t('Pending outgoing contact request');
987                         } else {
988                                 $alt_text = DI::l10n()->t('Pending incoming contact request');
989                         }
990                 }
991
992                 if ($contact['self']) {
993                         $alt_text = DI::l10n()->t('This is you');
994                         $url = $contact['url'];
995                         $sparkle = '';
996                 }
997
998                 return [
999                         'id'           => $contact['id'],
1000                         'url'          => $url,
1001                         'img_hover'    => DI::l10n()->t('Visit %s\'s profile [%s]', $contact['name'], $contact['url']),
1002                         'photo_menu'   => Model\Contact::photoMenu($contact),
1003                         'thumb'        => Model\Contact::getThumb($contact, true),
1004                         'alt_text'     => $alt_text,
1005                         'name'         => $contact['name'],
1006                         'nick'         => $contact['nick'],
1007                         'details'      => $contact['location'],
1008                         'tags'         => $contact['keywords'],
1009                         'about'        => $contact['about'],
1010                         'account_type' => Model\Contact::getAccountType($contact),
1011                         'sparkle'      => $sparkle,
1012                         'itemurl'      => ($contact['addr'] ?? '') ?: $contact['url'],
1013                         'network'      => ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol'], $contact['gsid']),
1014                 ];
1015         }
1016
1017         /**
1018          * Gives a array with actions which can performed to a given contact
1019          *
1020          * This includes actions like e.g. 'block', 'hide', 'delete' and others
1021          *
1022          * @param array $contact Data about the Contact
1023          * @return array with contact related actions
1024          */
1025         private static function getContactActions($contact)
1026         {
1027                 $poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
1028                 $contact_actions = [];
1029
1030                 $formSecurityToken = self::getFormSecurityToken('contact_action');
1031
1032                 // Provide friend suggestion only for Friendica contacts
1033                 if ($contact['network'] === Protocol::DFRN) {
1034                         $contact_actions['suggest'] = [
1035                                 'label' => DI::l10n()->t('Suggest friends'),
1036                                 'url'   => 'fsuggest/' . $contact['id'],
1037                                 'title' => '',
1038                                 'sel'   => '',
1039                                 'id'    => 'suggest',
1040                         ];
1041                 }
1042
1043                 if ($poll_enabled) {
1044                         $contact_actions['update'] = [
1045                                 'label' => DI::l10n()->t('Update now'),
1046                                 'url'   => 'contact/' . $contact['id'] . '/update?t=' . $formSecurityToken,
1047                                 'title' => '',
1048                                 'sel'   => '',
1049                                 'id'    => 'update',
1050                         ];
1051                 }
1052
1053                 if (in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
1054                         $contact_actions['updateprofile'] = [
1055                                 'label' => DI::l10n()->t('Refetch contact data'),
1056                                 'url'   => 'contact/' . $contact['id'] . '/updateprofile?t=' . $formSecurityToken,
1057                                 'title' => '',
1058                                 'sel'   => '',
1059                                 'id'    => 'updateprofile',
1060                         ];
1061                 }
1062
1063                 $contact_actions['block'] = [
1064                         'label' => (intval($contact['blocked']) ? DI::l10n()->t('Unblock') : DI::l10n()->t('Block')),
1065                         'url'   => 'contact/' . $contact['id'] . '/block?t=' . $formSecurityToken,
1066                         'title' => DI::l10n()->t('Toggle Blocked status'),
1067                         'sel'   => (intval($contact['blocked']) ? 'active' : ''),
1068                         'id'    => 'toggle-block',
1069                 ];
1070
1071                 $contact_actions['ignore'] = [
1072                         'label' => (intval($contact['readonly']) ? DI::l10n()->t('Unignore') : DI::l10n()->t('Ignore')),
1073                         'url'   => 'contact/' . $contact['id'] . '/ignore?t=' . $formSecurityToken,
1074                         'title' => DI::l10n()->t('Toggle Ignored status'),
1075                         'sel'   => (intval($contact['readonly']) ? 'active' : ''),
1076                         'id'    => 'toggle-ignore',
1077                 ];
1078
1079                 if ($contact['uid'] != 0 && Protocol::supportsRevokeFollow($contact['network']) && in_array($contact['rel'], [Model\Contact::FOLLOWER, Model\Contact::FRIEND])) {
1080                         $contact_actions['revoke_follow'] = [
1081                                 'label' => DI::l10n()->t('Revoke Follow'),
1082                                 'url'   => 'contact/' . $contact['id'] . '/revoke',
1083                                 'title' => DI::l10n()->t('Revoke the follow from this contact'),
1084                                 'sel'   => '',
1085                                 'id'    => 'revoke_follow',
1086                         ];
1087                 }
1088
1089                 return $contact_actions;
1090         }
1091 }