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